From c071120c016165ec99b6c33d939130b1f8084f17 Mon Sep 17 00:00:00 2001 From: jawwad-ali Date: Tue, 30 Jun 2026 21:23:58 +0500 Subject: [PATCH 1/2] fix(integrations): cursor-agent ignores executable/extra-args env overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cursor-agent's build_exec_args() hardcoded self.key as argv[0] and never called _apply_extra_args_env_var(), so the documented SPECKIT_INTEGRATION_CURSOR_AGENT_EXECUTABLE (issue #2596) and SPECKIT_INTEGRATION_CURSOR_AGENT_EXTRA_ARGS (issue #2595) hooks were silently dropped — unlike every other CLI-dispatch integration (codex, devin). Route argv[0] through _resolve_executable() and apply the extra-args hook after the mandatory headless flags, mirroring the twins. Co-Authored-By: Claude Opus 4.8 --- .../integrations/cursor_agent/__init__.py | 10 +++++- .../test_integration_cursor_agent.py | 32 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/specify_cli/integrations/cursor_agent/__init__.py b/src/specify_cli/integrations/cursor_agent/__init__.py index 2c328b2fda..07f2a6318b 100644 --- a/src/specify_cli/integrations/cursor_agent/__init__.py +++ b/src/specify_cli/integrations/cursor_agent/__init__.py @@ -75,7 +75,15 @@ def build_exec_args( either drops tool calls or exits non-zero on the first approval prompt. """ - args = [self.key, "-p", "--trust", "--approve-mcps", "--force", prompt] + args = [ + self._resolve_executable(), + "-p", + "--trust", + "--approve-mcps", + "--force", + prompt, + ] + self._apply_extra_args_env_var(args) if model: args.extend(["--model", model]) if output_json: diff --git a/tests/integrations/test_integration_cursor_agent.py b/tests/integrations/test_integration_cursor_agent.py index 32318dc90f..2e9c0f88d4 100644 --- a/tests/integrations/test_integration_cursor_agent.py +++ b/tests/integrations/test_integration_cursor_agent.py @@ -125,6 +125,38 @@ def test_build_exec_args_supports_dispatch_without_requires_cli(self): assert argv is not None assert argv[0] == "cursor-agent" + def test_build_exec_args_honors_executable_override(self, monkeypatch): + """``SPECKIT_INTEGRATION_CURSOR_AGENT_EXECUTABLE`` overrides argv[0]. + + Every other CLI-dispatch integration (codex, devin, ...) routes + argv[0] through ``_resolve_executable()`` so operators can pin a + binary path (issue #2596). cursor-agent hardcoded ``self.key`` and + silently ignored the documented override. + """ + monkeypatch.setenv( + "SPECKIT_INTEGRATION_CURSOR_AGENT_EXECUTABLE", "/custom/cursor" + ) + i = get_integration("cursor-agent") + args = i.build_exec_args("/speckit-plan", output_json=False) + assert args[0] == "/custom/cursor" + # The mandatory headless flags must still be present. + for flag in ("-p", "--trust", "--approve-mcps", "--force"): + assert flag in args + + def test_build_exec_args_honors_extra_args_override(self, monkeypatch): + """``SPECKIT_INTEGRATION_CURSOR_AGENT_EXTRA_ARGS`` flags are injected. + + The ``_apply_extra_args_env_var()`` hook (issue #2595) was never + invoked by cursor-agent, so operator-supplied flags were dropped. + """ + monkeypatch.setenv( + "SPECKIT_INTEGRATION_CURSOR_AGENT_EXTRA_ARGS", "--foo bar" + ) + i = get_integration("cursor-agent") + args = i.build_exec_args("/speckit-plan", output_json=False) + assert "--foo" in args + assert "bar" in args + def test_build_command_invocation_uses_hyphenated_skill_name(self): """SkillsIntegration: /speckit-plan (not /speckit.plan).""" i = get_integration("cursor-agent") From aa20825b7fac3bd6857b9cb5da5ac769289e5736 Mon Sep 17 00:00:00 2001 From: jawwad-ali Date: Thu, 2 Jul 2026 16:11:05 +0500 Subject: [PATCH 2/2] test(integrations): pin extra-args insertion order for cursor-agent Per Copilot feedback: the extra-args override test only asserted the injected tokens were present, not that they land before Spec Kit's canonical --model / --output-format flags. Exercise build_exec_args with both a model and JSON output and assert the extra args are inserted before --model / --output-format (and the canonical flags stay intact and paired). Verified this fails if the _apply_extra_args_env_var call is moved after the flag extends. Co-Authored-By: Claude Opus 4.8 --- .../test_integration_cursor_agent.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/integrations/test_integration_cursor_agent.py b/tests/integrations/test_integration_cursor_agent.py index 2e9c0f88d4..7b6822f7e3 100644 --- a/tests/integrations/test_integration_cursor_agent.py +++ b/tests/integrations/test_integration_cursor_agent.py @@ -144,18 +144,35 @@ def test_build_exec_args_honors_executable_override(self, monkeypatch): assert flag in args def test_build_exec_args_honors_extra_args_override(self, monkeypatch): - """``SPECKIT_INTEGRATION_CURSOR_AGENT_EXTRA_ARGS`` flags are injected. + """``SPECKIT_INTEGRATION_CURSOR_AGENT_EXTRA_ARGS`` flags are injected + *before* Spec Kit's canonical ``--model`` / ``--output-format`` flags. The ``_apply_extra_args_env_var()`` hook (issue #2595) was never invoked by cursor-agent, so operator-supplied flags were dropped. + Insertion order is the real contract: extra args must land after the + mandatory headless flags but before ``--model`` / ``--output-format``, + so they cannot clobber, displace, or reorder Spec Kit's canonical + trailing flags. Exercise with both a model and JSON output so both + canonical flags are present to pin against. """ monkeypatch.setenv( "SPECKIT_INTEGRATION_CURSOR_AGENT_EXTRA_ARGS", "--foo bar" ) i = get_integration("cursor-agent") - args = i.build_exec_args("/speckit-plan", output_json=False) + args = i.build_exec_args( + "/speckit-plan", model="sonnet-4-thinking", output_json=True + ) assert "--foo" in args assert "bar" in args + # "bar" is the value of "--foo": the tokens stay adjacent and in order. + assert args.index("bar") == args.index("--foo") + 1 + # Extra args are inserted before the canonical flags, so they cannot + # clobber or reorder them (the behavioral contract this test guards). + assert args.index("--foo") < args.index("--model") + assert args.index("--foo") < args.index("--output-format") + # The canonical flags themselves remain intact and correctly paired. + assert args[args.index("--model") + 1] == "sonnet-4-thinking" + assert args[args.index("--output-format") + 1] == "json" def test_build_command_invocation_uses_hyphenated_skill_name(self): """SkillsIntegration: /speckit-plan (not /speckit.plan)."""