From ecc42cefc6220c2a4d43d5f41bda9262269c7e9d Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 5 Jun 2026 10:36:19 -0400 Subject: [PATCH] fix(#669): /gsd-review --cursor actually invokes cursor-agent (#686) * fix(#669): /gsd-review --cursor actually invokes cursor-agent The Cursor reviewer branch in review.md never ran the agent: - detection probed `cursor` (the IDE launcher) instead of the headless `cursor-agent` binary - the invocation used the two-token `cursor agent` (the IDE treats `agent` as a file-path argument, so the agent never starts) - the prompt was piped via stdin, but `cursor-agent -p` reads the prompt from a command-line argument, and `2>/dev/null` hid the empty result Probe `cursor-agent`; invoke `cursor-agent -p --mode ask --trust --output-format text` with the prompt passed as a file-path-reference argument (avoids the OS arg-length limit on large prompts); capture stderr so failures are diagnosable. Invert tests/cursor-reviewer.test.cjs to assert the corrected contract, with negative guards against the two-token form and the stdin pipe. The sibling `agy` reviewer already used the argument form. Closes #669 Co-Authored-By: Claude Opus 4.8 * chore(#669): set changeset pr number to 686 Co-Authored-By: Claude Opus 4.8 --------- Co-authored-by: Claude Opus 4.8 --- .changeset/669-gsd-review-cursor-agent.md | 5 +++ gsd-core/workflows/review.md | 12 ++++-- tests/cursor-reviewer.test.cjs | 48 ++++++++++++++++++++--- 3 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 .changeset/669-gsd-review-cursor-agent.md diff --git a/.changeset/669-gsd-review-cursor-agent.md b/.changeset/669-gsd-review-cursor-agent.md new file mode 100644 index 000000000..3a3ac40ba --- /dev/null +++ b/.changeset/669-gsd-review-cursor-agent.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 686 +--- +`/gsd-review --cursor` now actually invokes the Cursor agent. Detection probes the `cursor-agent` headless binary instead of the `cursor` IDE launcher, the invocation calls the single `cursor-agent` binary in print mode (not the two-token `cursor agent`, which the IDE treats as a file path), and the review prompt is passed as a file-path argument rather than piped to stdin (which `cursor-agent -p` ignores). On failure the captured stderr is surfaced instead of a silent empty result. diff --git a/gsd-core/workflows/review.md b/gsd-core/workflows/review.md index 123da933c..7c5ed8922 100644 --- a/gsd-core/workflows/review.md +++ b/gsd-core/workflows/review.md @@ -22,7 +22,7 @@ command -v codex >/dev/null 2>&1 && echo "codex:available" || echo "codex:missin command -v coderabbit >/dev/null 2>&1 && echo "coderabbit:available" || echo "coderabbit:missing" command -v opencode >/dev/null 2>&1 && echo "opencode:available" || echo "opencode:missing" command -v qwen >/dev/null 2>&1 && echo "qwen:available" || echo "qwen:missing" -command -v cursor >/dev/null 2>&1 && echo "cursor:available" || echo "cursor:missing" +command -v cursor-agent >/dev/null 2>&1 && echo "cursor:available" || echo "cursor:missing" command -v agy >/dev/null 2>&1 && echo "antigravity:available" || echo "antigravity:missing" # Check local model servers (OpenAI-compatible HTTP API — no CLI binary required) @@ -284,9 +284,15 @@ fi **Cursor:** ```bash -cat /tmp/gsd-review-prompt-{phase}.md | cursor agent -p --mode ask --trust 2>/dev/null > /tmp/gsd-review-cursor-{phase}.md +# cursor-agent is a SEPARATE binary from the `cursor` IDE launcher; print mode (-p) takes the +# prompt as an ARGUMENT, not stdin. A full review prompt can exceed the OS argument limit, so +# reference the prompt file by path rather than inlining it. Capture stderr so a failure is +# diagnosable instead of a silent empty result. +CURSOR_PROMPT_ARG="Read the file at /tmp/gsd-review-prompt-{phase}.md in full and carry out the review request it contains. Output only the resulting markdown review. Do not edit any files." +cursor-agent -p --mode ask --trust --output-format text "$CURSOR_PROMPT_ARG" 2>/tmp/gsd-review-cursor-{phase}.err > /tmp/gsd-review-cursor-{phase}.md if [ ! -s /tmp/gsd-review-cursor-{phase}.md ]; then - echo "Cursor review failed or returned empty output." > /tmp/gsd-review-cursor-{phase}.md + echo "Cursor review failed or returned empty output. stderr:" > /tmp/gsd-review-cursor-{phase}.md + cat /tmp/gsd-review-cursor-{phase}.err >> /tmp/gsd-review-cursor-{phase}.md fi ``` diff --git a/tests/cursor-reviewer.test.cjs b/tests/cursor-reviewer.test.cjs index aafecb134..e185cd528 100644 --- a/tests/cursor-reviewer.test.cjs +++ b/tests/cursor-reviewer.test.cjs @@ -36,11 +36,11 @@ describe('Cursor CLI reviewer in /gsd-review (#1960)', () => { content = fs.readFileSync(reviewPath, 'utf-8'); }); - test('contains cursor CLI detection via command -v', () => { + test('contains cursor CLI detection via command -v cursor-agent', () => { const c = fs.readFileSync(reviewPath, 'utf-8'); assert.ok( - c.includes('command -v cursor'), - 'review.md should detect cursor CLI via "command -v cursor"' + c.includes('command -v cursor-agent'), + 'review.md should detect cursor CLI via "command -v cursor-agent" (not the cursor IDE launcher)' ); }); @@ -60,11 +60,47 @@ describe('Cursor CLI reviewer in /gsd-review (#1960)', () => { ); }); - test('contains cursor agent invocation command', () => { + test('invocation uses cursor-agent single binary with -p flag', () => { const c = fs.readFileSync(reviewPath, 'utf-8'); assert.ok( - c.includes('cursor agent -p --mode ask --trust'), - 'review.md should invoke cursor via "cursor agent -p --mode ask --trust"' + c.includes('cursor-agent -p'), + 'review.md should invoke cursor via "cursor-agent -p" (single binary, not two-token "cursor agent")' + ); + }); + + test('invocation includes --output-format text', () => { + const c = fs.readFileSync(reviewPath, 'utf-8'); + assert.ok( + c.includes('--output-format text'), + 'review.md cursor-agent invocation should include "--output-format text"' + ); + }); + + test('invocation passes prompt as a file-path argument (not via stdin pipe)', () => { + const c = fs.readFileSync(reviewPath, 'utf-8'); + assert.ok( + c.includes('Read the file at /tmp/gsd-review-prompt-'), + 'review.md cursor-agent invocation should pass prompt by referencing the file path as an argument' + ); + }); + + test('does NOT use broken two-token "cursor agent " form', () => { + const c = fs.readFileSync(reviewPath, 'utf-8'); + // Must not match "cursor agent " (cursor + space + agent + space) + // The hyphenated "cursor-agent" must NOT trip this check — the regex uses a space, not a hyphen. + assert.ok( + !/cursor agent /.test(c), + 'review.md must NOT contain the broken two-token form "cursor agent " (use "cursor-agent" instead)' + ); + }); + + test('does NOT pipe the prompt into a cursor command via stdin', () => { + const c = fs.readFileSync(reviewPath, 'utf-8'); + // Must not match a pipe feeding into a cursor command (e.g. "| cursor" or "|cursor") + // "cursor-agent" (hyphenated) must NOT trip this — the regex anchors on "cursor" not followed by "-agent" + assert.ok( + !/\| *cursor(?!-agent)/.test(c), + 'review.md must NOT pipe the prompt to a cursor command via stdin' ); });