Skip to content

Commit 101f35b

Browse files
committed
fix(integrations): rename env var to SPECKIT_INTEGRATION_<KEY>_EXTRA_ARGS
Renames the env-var hook prefix from `SPECIFY_INTEGRATION_*` to `SPECKIT_INTEGRATION_*` to match the established codebase convention for integration-subsystem env vars (`SPECKIT_INTEGRATION_CATALOG_URL` in `integrations/catalog.py`, `SPECKIT_COPILOT_ALLOW_ALL_TOOLS` in `integrations/copilot/__init__.py`). The `SPECIFY_*` prefix is reserved for user-facing feature-resolution variables (`SPECIFY_FEATURE`, `SPECIFY_FEATURE_DIRECTORY`); reusing it for integration-subsystem scoping would introduce a second integration namespace under a different prefix, confusing operators who already set `SPECKIT_INTEGRATION_CATALOG_URL`. Also reverts the unrelated defensive `arg_placeholder` / `registrar_config is None` guard in `CopilotIntegration.setup_skills_mode` — it was a drive-by pyright cleanup mixed into this PR. Every concrete integration sets `registrar_config` so the guard never fires in practice; the typing issue belongs in a focused follow-up rather than this env-var-hook PR. Updates everywhere the old prefix appeared: - `IntegrationBase._apply_extra_args_env_var` helper + docstring - `CopilotIntegration.dispatch_command` inline comment - All `monkeypatch.setenv(...)` calls in `tests/integrations/test_extra_args.py` - The autouse fixture scope filter - Test module docstring Full suite: 3011 passed, 40 skipped, no regressions. Addresses Copilot review feedback on #2596.
1 parent 1ae8d52 commit 101f35b

3 files changed

Lines changed: 30 additions & 34 deletions

File tree

src/specify_cli/integrations/base.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,16 +141,16 @@ def build_exec_args(
141141
return None
142142

143143
def _apply_extra_args_env_var(self, args: list[str]) -> None:
144-
"""Append `SPECIFY_INTEGRATION_<KEY>_EXTRA_ARGS` env-var value to *args*.
144+
"""Append `SPECKIT_INTEGRATION_<KEY>_EXTRA_ARGS` env-var value to *args*.
145145
146146
Operators can inject extra CLI flags into the spawned agent
147147
subprocess by setting an env var named for the integration key,
148-
e.g. `SPECIFY_INTEGRATION_CLAUDE_EXTRA_ARGS="--dangerously-skip-permissions"`.
148+
e.g. `SPECKIT_INTEGRATION_CLAUDE_EXTRA_ARGS="--dangerously-skip-permissions"`.
149149
The `INTEGRATION` segment scopes the variable to this subsystem
150150
so it does not collide with other Spec Kit env-var namespaces.
151151
Hyphens in the integration key are replaced with underscores
152152
and the key is uppercased
153-
(e.g. `kiro-cli` → `SPECIFY_INTEGRATION_KIRO_CLI_EXTRA_ARGS`).
153+
(e.g. `kiro-cli` → `SPECKIT_INTEGRATION_KIRO_CLI_EXTRA_ARGS`).
154154
155155
Useful in CI / non-interactive contexts where the spawned agent
156156
needs flags that change its prompt-handling behaviour.
@@ -161,7 +161,7 @@ def _apply_extra_args_env_var(self, args: list[str]) -> None:
161161
See issue #2595.
162162
"""
163163
env_name = (
164-
f"SPECIFY_INTEGRATION_{self.key.upper().replace('-', '_')}_EXTRA_ARGS"
164+
f"SPECKIT_INTEGRATION_{self.key.upper().replace('-', '_')}_EXTRA_ARGS"
165165
)
166166
extra = os.environ.get(env_name, "").strip()
167167
if not extra:

src/specify_cli/integrations/copilot/__init__.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def dispatch_command(
218218
prompt = args or ""
219219

220220
cli_args = [_copilot_executable(), "-p", prompt]
221-
# Honour SPECIFY_INTEGRATION_COPILOT_EXTRA_ARGS for real workflow
221+
# Honour SPECKIT_INTEGRATION_COPILOT_EXTRA_ARGS for real workflow
222222
# runs. `dispatch_command` builds cli_args inline rather than
223223
# going through `build_exec_args`, so the hook must be invoked
224224
# here too — otherwise the env var is silently ignored.
@@ -374,11 +374,7 @@ def _setup_default(
374374
created: list[Path] = []
375375

376376
script_type = opts.get("script_type", "sh")
377-
arg_placeholder = (
378-
self.registrar_config.get("args", "$ARGUMENTS")
379-
if self.registrar_config
380-
else "$ARGUMENTS"
381-
)
377+
arg_placeholder = self.registrar_config.get("args", "$ARGUMENTS")
382378

383379
# 1. Process and write command files as .agent.md
384380
for src_file in templates:

tests/integrations/test_extra_args.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Tests for the per-integration `SPECIFY_INTEGRATION_<KEY>_EXTRA_ARGS` env-var hook.
1+
"""Tests for the per-integration `SPECKIT_INTEGRATION_<KEY>_EXTRA_ARGS` env-var hook.
22
33
The hook is implemented in `IntegrationBase._apply_extra_args_env_var`
44
and wired into every concrete `build_exec_args` —
@@ -128,10 +128,10 @@ class _TomlAgentStub(TomlIntegration):
128128

129129
@pytest.fixture(autouse=True)
130130
def _clean_extra_args_env(monkeypatch):
131-
"""Strip any leaked SPECIFY_INTEGRATION_*_EXTRA_ARGS from the test
131+
"""Strip any leaked SPECKIT_INTEGRATION_*_EXTRA_ARGS from the test
132132
env so a developer's shell setting doesn't pollute results."""
133133
for key in list(os.environ):
134-
if key.startswith("SPECIFY_INTEGRATION_") and key.endswith(
134+
if key.startswith("SPECKIT_INTEGRATION_") and key.endswith(
135135
"_EXTRA_ARGS"
136136
):
137137
monkeypatch.delenv(key, raising=False)
@@ -151,7 +151,7 @@ def test_env_var_set_flag_inserted_before_model_and_output_format(
151151
monkeypatch,
152152
):
153153
monkeypatch.setenv(
154-
"SPECIFY_INTEGRATION_CLAUDE_EXTRA_ARGS", "--dangerously-skip-permissions"
154+
"SPECKIT_INTEGRATION_CLAUDE_EXTRA_ARGS", "--dangerously-skip-permissions"
155155
)
156156
args = _ClaudeStub().build_exec_args("hello prompt", model="sonnet")
157157
assert args == [
@@ -168,7 +168,7 @@ def test_env_var_set_flag_inserted_before_model_and_output_format(
168168

169169
def test_env_var_multi_token_parsed_via_shlex(monkeypatch):
170170
monkeypatch.setenv(
171-
"SPECIFY_INTEGRATION_CLAUDE_EXTRA_ARGS",
171+
"SPECKIT_INTEGRATION_CLAUDE_EXTRA_ARGS",
172172
"--dangerously-skip-permissions --max-turns 3",
173173
)
174174
args = _ClaudeStub().build_exec_args("p")
@@ -189,36 +189,36 @@ def test_malformed_quoting_raises_actionable_value_error(monkeypatch):
189189
error naming the offending env var and showing the invalid value,
190190
rather than crashing workflow dispatch with a bare shlex traceback."""
191191
monkeypatch.setenv(
192-
"SPECIFY_INTEGRATION_CLAUDE_EXTRA_ARGS",
192+
"SPECKIT_INTEGRATION_CLAUDE_EXTRA_ARGS",
193193
'--flag "unterminated',
194194
)
195195
with pytest.raises(ValueError) as excinfo:
196196
_ClaudeStub().build_exec_args("p")
197197
msg = str(excinfo.value)
198-
assert "SPECIFY_INTEGRATION_CLAUDE_EXTRA_ARGS" in msg
198+
assert "SPECKIT_INTEGRATION_CLAUDE_EXTRA_ARGS" in msg
199199
assert "--flag \"unterminated" in msg
200200

201201

202202
def test_env_var_empty_or_whitespace_is_noop(monkeypatch):
203203
"""An env var set to '' or ' ' is treated as unset."""
204-
monkeypatch.setenv("SPECIFY_INTEGRATION_CLAUDE_EXTRA_ARGS", " ")
204+
monkeypatch.setenv("SPECKIT_INTEGRATION_CLAUDE_EXTRA_ARGS", " ")
205205
args = _ClaudeStub().build_exec_args("p")
206206
assert args == ["claude", "-p", "p", "--output-format", "json"]
207207

208208

209209
def test_other_integration_env_var_ignored(monkeypatch):
210-
"""`SPECIFY_INTEGRATION_GEMINI_EXTRA_ARGS` set must NOT leak into
210+
"""`SPECKIT_INTEGRATION_GEMINI_EXTRA_ARGS` set must NOT leak into
211211
Claude's argv (per-integration scoping)."""
212-
monkeypatch.setenv("SPECIFY_INTEGRATION_GEMINI_EXTRA_ARGS", "--gemini-only-flag")
212+
monkeypatch.setenv("SPECKIT_INTEGRATION_GEMINI_EXTRA_ARGS", "--gemini-only-flag")
213213
args = _ClaudeStub().build_exec_args("p")
214214
assert args == ["claude", "-p", "p", "--output-format", "json"]
215215

216216

217217
def test_key_normalization_hyphen_to_underscore_uppercase(monkeypatch):
218-
"""`kiro-cli` key looks up `SPECIFY_INTEGRATION_KIRO_CLI_EXTRA_ARGS`
218+
"""`kiro-cli` key looks up `SPECKIT_INTEGRATION_KIRO_CLI_EXTRA_ARGS`
219219
(hyphens replaced with underscores, then uppercased)."""
220220
monkeypatch.setenv(
221-
"SPECIFY_INTEGRATION_KIRO_CLI_EXTRA_ARGS", "--some-kiro-flag"
221+
"SPECKIT_INTEGRATION_KIRO_CLI_EXTRA_ARGS", "--some-kiro-flag"
222222
)
223223
args = _KiroCliStub().build_exec_args("p")
224224
assert args == [
@@ -234,7 +234,7 @@ def test_key_normalization_hyphen_to_underscore_uppercase(monkeypatch):
234234
def test_requires_cli_false_returns_none(monkeypatch):
235235
"""`requires_cli: False` short-circuits to None — the env-var
236236
hook is never reached and no argv is built."""
237-
monkeypatch.setenv("SPECIFY_INTEGRATION_NO_CLI_EXTRA_ARGS", "--should-not-appear")
237+
monkeypatch.setenv("SPECKIT_INTEGRATION_NO_CLI_EXTRA_ARGS", "--should-not-appear")
238238
assert _NoCliStub().build_exec_args("p") is None
239239

240240

@@ -254,7 +254,7 @@ def test_markdown_integration_base_honours_extra_args(monkeypatch):
254254
`build_exec_args` — must honour the env var via the base
255255
implementation. Covers the most common integration pattern."""
256256
monkeypatch.setenv(
257-
"SPECIFY_INTEGRATION_MD_AGENT_EXTRA_ARGS", "--debug --max-tokens 100"
257+
"SPECKIT_INTEGRATION_MD_AGENT_EXTRA_ARGS", "--debug --max-tokens 100"
258258
)
259259
args = _MarkdownAgentStub().build_exec_args("p")
260260
assert args == [
@@ -274,7 +274,7 @@ def test_toml_integration_base_honours_extra_args(monkeypatch):
274274
`build_exec_args` — must honour the env var via the base
275275
implementation. Covers Gemini/Tabnine-style integrations."""
276276
monkeypatch.setenv(
277-
"SPECIFY_INTEGRATION_TOML_AGENT_EXTRA_ARGS", "--yolo"
277+
"SPECKIT_INTEGRATION_TOML_AGENT_EXTRA_ARGS", "--yolo"
278278
)
279279
args = _TomlAgentStub().build_exec_args("p", model="gemini-pro")
280280
# TomlIntegration uses `-m` for model (vs Markdown's `--model`).
@@ -304,7 +304,7 @@ def test_toml_integration_base_honours_extra_args(monkeypatch):
304304
def test_codex_integration_honours_extra_args(monkeypatch):
305305
from specify_cli.integrations.codex import CodexIntegration
306306

307-
monkeypatch.setenv("SPECIFY_INTEGRATION_CODEX_EXTRA_ARGS", "--sandbox read-only")
307+
monkeypatch.setenv("SPECKIT_INTEGRATION_CODEX_EXTRA_ARGS", "--sandbox read-only")
308308
args = CodexIntegration().build_exec_args("p", model="gpt-5")
309309
assert args == [
310310
"codex",
@@ -321,15 +321,15 @@ def test_codex_integration_honours_extra_args(monkeypatch):
321321
def test_devin_integration_honours_extra_args(monkeypatch):
322322
from specify_cli.integrations.devin import DevinIntegration
323323

324-
monkeypatch.setenv("SPECIFY_INTEGRATION_DEVIN_EXTRA_ARGS", "--no-confirm")
324+
monkeypatch.setenv("SPECKIT_INTEGRATION_DEVIN_EXTRA_ARGS", "--no-confirm")
325325
args = DevinIntegration().build_exec_args("p")
326326
assert args == ["devin", "-p", "p", "--no-confirm"]
327327

328328

329329
def test_opencode_integration_honours_extra_args(monkeypatch):
330330
from specify_cli.integrations.opencode import OpencodeIntegration
331331

332-
monkeypatch.setenv("SPECIFY_INTEGRATION_OPENCODE_EXTRA_ARGS", "--quiet")
332+
monkeypatch.setenv("SPECKIT_INTEGRATION_OPENCODE_EXTRA_ARGS", "--quiet")
333333
args = OpencodeIntegration().build_exec_args("p")
334334
assert args == [
335335
"opencode",
@@ -349,13 +349,13 @@ def test_opencode_extra_args_cannot_clobber_prompt_derived_command(
349349
repeated-flag CLI semantics (last value typically takes precedence).
350350
351351
Locks against the regression where an operator setting
352-
``SPECIFY_INTEGRATION_OPENCODE_EXTRA_ARGS="--command malicious"`` could redirect
352+
``SPECKIT_INTEGRATION_OPENCODE_EXTRA_ARGS="--command malicious"`` could redirect
353353
a slash-prefixed prompt to a different command.
354354
"""
355355
from specify_cli.integrations.opencode import OpencodeIntegration
356356

357357
monkeypatch.setenv(
358-
"SPECIFY_INTEGRATION_OPENCODE_EXTRA_ARGS", "--command operator-override"
358+
"SPECKIT_INTEGRATION_OPENCODE_EXTRA_ARGS", "--command operator-override"
359359
)
360360
args = OpencodeIntegration().build_exec_args("/speckit body text")
361361
# Prompt-derived "--command speckit" appears AFTER the
@@ -383,7 +383,7 @@ def test_copilot_integration_honours_extra_args(monkeypatch):
383383
# Disable --yolo so the argv shape stays deterministic.
384384
monkeypatch.setenv("SPECKIT_COPILOT_ALLOW_ALL_TOOLS", "0")
385385
monkeypatch.setenv(
386-
"SPECIFY_INTEGRATION_COPILOT_EXTRA_ARGS", "--allow-tool 'shell(echo)'"
386+
"SPECKIT_INTEGRATION_COPILOT_EXTRA_ARGS", "--allow-tool 'shell(echo)'"
387387
)
388388
args = CopilotIntegration().build_exec_args("p")
389389
# `_copilot_executable()` returns "copilot.cmd" on Windows and
@@ -431,7 +431,7 @@ class _Result:
431431

432432
def test_copilot_dispatch_command_includes_extra_args(monkeypatch):
433433
"""Locks the bypass fix: `CopilotIntegration.dispatch_command`
434-
must honour `SPECIFY_INTEGRATION_COPILOT_EXTRA_ARGS`, not just `build_exec_args`.
434+
must honour `SPECKIT_INTEGRATION_COPILOT_EXTRA_ARGS`, not just `build_exec_args`.
435435
"""
436436
import subprocess
437437

@@ -441,7 +441,7 @@ def test_copilot_dispatch_command_includes_extra_args(monkeypatch):
441441
monkeypatch.setattr(subprocess, "run", capture)
442442
monkeypatch.setenv("SPECKIT_COPILOT_ALLOW_ALL_TOOLS", "0")
443443
monkeypatch.setenv(
444-
"SPECIFY_INTEGRATION_COPILOT_EXTRA_ARGS", "--allow-tool 'shell(echo)'"
444+
"SPECKIT_INTEGRATION_COPILOT_EXTRA_ARGS", "--allow-tool 'shell(echo)'"
445445
)
446446

447447
CopilotIntegration().dispatch_command(
@@ -469,7 +469,7 @@ def test_codex_dispatch_command_includes_extra_args(monkeypatch):
469469

470470
capture = _RunCapture()
471471
monkeypatch.setattr(subprocess, "run", capture)
472-
monkeypatch.setenv("SPECIFY_INTEGRATION_CODEX_EXTRA_ARGS", "--sandbox read-only")
472+
monkeypatch.setenv("SPECKIT_INTEGRATION_CODEX_EXTRA_ARGS", "--sandbox read-only")
473473

474474
CodexIntegration().dispatch_command(
475475
"speckit.plan", args="body", stream=False

0 commit comments

Comments
 (0)