From f16dce25f026cd44944baa451d2622091f6fee84 Mon Sep 17 00:00:00 2001 From: garrytan Date: Sat, 17 Oct 2026 20:25:57 +0000 Subject: [PATCH] fix(review-army): keep the state-machine detector's output only on exit 0 (W3-5 confirm round) A failing gstack-state-matrix detect prints SCOPE_STATE_MACHINE=false before exiting non-zero. The scope block now captures its output and sources it only when the detector succeeds; otherwise it discards the output, sets SCOPE_STATE_MACHINE=unknown (dispatching the State Machines specialist) and leaves the detector's stderr visible. Free tests cover false-output/non-zero, empty-output/non-zero and a late failure after printing true. The stack comment is shortened so gstack-review stays 4 chars under its pin; the five host pins are lowered to match. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- review/sections/review-army.md | 4 ++-- scripts/resolvers/review-army.ts | 4 ++-- ship/sections/review-army.md | 4 ++-- test/fixtures/skill-md-size-allowlist.json | 10 +++++----- test/review-workflow-clarity.test.ts | 8 +++++++- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/review/sections/review-army.md b/review/sections/review-army.md index 975d649e3..36d1b340c 100644 --- a/review/sections/review-army.md +++ b/review/sections/review-army.md @@ -5,10 +5,10 @@ ### Detect stack and scope ```bash -source <(~/.claude/skills/gstack/bin/gstack-state-matrix detect --base || echo SCOPE_STATE_MACHINE=unknown) +_SM=$(~/.claude/skills/gstack/bin/gstack-state-matrix detect --base ) || _SM=SCOPE_STATE_MACHINE=unknown; source <(echo "$_SM") source <(~/.claude/skills/gstack/bin/gstack-diff-scope 2>/dev/null) || true set | grep '^SCOPE_' -# Detect stack for specialist context +# Detect stack STACK="" [ -f Gemfile ] && STACK="${STACK}ruby " [ -f package.json ] && STACK="${STACK}node " diff --git a/scripts/resolvers/review-army.ts b/scripts/resolvers/review-army.ts index 980915181..1a4c4935c 100644 --- a/scripts/resolvers/review-army.ts +++ b/scripts/resolvers/review-army.ts @@ -24,10 +24,10 @@ function generateSpecialistSelection(ctx: TemplateContext): string { ### Detect stack and scope \`\`\`bash -source <(${ctx.paths.binDir}/gstack-state-matrix detect --base || echo SCOPE_STATE_MACHINE=unknown) +_SM=$(${ctx.paths.binDir}/gstack-state-matrix detect --base ) || _SM=SCOPE_STATE_MACHINE=unknown; source <(echo "$_SM") source <(${ctx.paths.binDir}/gstack-diff-scope 2>/dev/null) || true set | grep '^SCOPE_' -# Detect stack for specialist context +# Detect stack STACK="" [ -f Gemfile ] && STACK="\${STACK}ruby " [ -f package.json ] && STACK="\${STACK}node " diff --git a/ship/sections/review-army.md b/ship/sections/review-army.md index 38e0963e7..87a857032 100644 --- a/ship/sections/review-army.md +++ b/ship/sections/review-army.md @@ -267,10 +267,10 @@ distinct defects stay separate and neither pass substitutes for the other. ### Detect stack and scope ```bash -source <(~/.claude/skills/gstack/bin/gstack-state-matrix detect --base || echo SCOPE_STATE_MACHINE=unknown) +_SM=$(~/.claude/skills/gstack/bin/gstack-state-matrix detect --base ) || _SM=SCOPE_STATE_MACHINE=unknown; source <(echo "$_SM") source <(~/.claude/skills/gstack/bin/gstack-diff-scope 2>/dev/null) || true set | grep '^SCOPE_' -# Detect stack for specialist context +# Detect stack STACK="" [ -f Gemfile ] && STACK="${STACK}ruby " [ -f package.json ] && STACK="${STACK}node " diff --git a/test/fixtures/skill-md-size-allowlist.json b/test/fixtures/skill-md-size-allowlist.json index ebd02e815..82dbfe751 100644 --- a/test/fixtures/skill-md-size-allowlist.json +++ b/test/fixtures/skill-md-size-allowlist.json @@ -37,7 +37,7 @@ "cursor/gstack-plan-design-review": 138935, "cursor/gstack-plan-devex-review": 134558, "cursor/gstack-plan-eng-review": 151523, - "cursor/gstack-review": 141047, + "cursor/gstack-review": 141043, "factory/gstack-autoplan": 131397, "factory/gstack-codex": 100523, "factory/gstack-design-consultation": 108523, @@ -47,7 +47,7 @@ "factory/gstack-plan-design-review": 141716, "factory/gstack-plan-devex-review": 137927, "factory/gstack-plan-eng-review": 154637, - "factory/gstack-review": 144829, + "factory/gstack-review": 144825, "gbrain/gstack-autoplan": 129650, "gbrain/gstack-design-review": 132733, "gbrain/gstack-land-and-deploy": 116039, @@ -67,7 +67,7 @@ "kiro/gstack-plan-design-review": 138775, "kiro/gstack-plan-devex-review": 134398, "kiro/gstack-plan-eng-review": 151373, - "kiro/gstack-review": 140853, + "kiro/gstack-review": 140849, "openclaw/gstack-autoplan": 129355, "openclaw/gstack-design-review": 131538, "openclaw/gstack-land-and-deploy": 115627, @@ -82,7 +82,7 @@ "opencode/gstack-plan-design-review": 140705, "opencode/gstack-plan-devex-review": 136779, "opencode/gstack-plan-eng-review": 153275, - "opencode/gstack-review": 143295, + "opencode/gstack-review": 143291, "slate/gstack-autoplan": 128994, "slate/gstack-design-consultation": 106996, "slate/gstack-design-review": 142272, @@ -90,6 +90,6 @@ "slate/gstack-plan-design-review": 138855, "slate/gstack-plan-devex-review": 134478, "slate/gstack-plan-eng-review": 151448, - "slate/gstack-review": 140950 + "slate/gstack-review": 140946 } } diff --git a/test/review-workflow-clarity.test.ts b/test/review-workflow-clarity.test.ts index 80f97f5f2..1f78c25a9 100644 --- a/test/review-workflow-clarity.test.ts +++ b/test/review-workflow-clarity.test.ts @@ -384,7 +384,7 @@ for (const skillName of ['review', 'ship']) { test(`${skillName} clarity: the state-machine signal checks the detector's exit status and fails toward dispatch`, () => { const block = between(army, '### Detect stack and scope', '### Read specialist hit rates'); - expect(block).toContain(`source <(${HOST_PATHS.claude.binDir}/gstack-state-matrix detect --base || echo SCOPE_STATE_MACHINE=unknown)\n`); + expect(block).toContain(`_SM=$(${HOST_PATHS.claude.binDir}/gstack-state-matrix detect --base ) || _SM=SCOPE_STATE_MACHINE=unknown; source <(echo "$_SM")\n`); expect(block.indexOf('SCOPE_STATE_MACHINE=unknown')).toBeLessThan(block.indexOf("set | grep '^SCOPE_'")); const selection = between(army, '### Select specialists', '### Adaptive gating').replace(/\s+/g, ' '); expect(selection).toContain('9. **State Machines** — if SCOPE_STATE_MACHINE is true or unknown (printed by the scope block)'); @@ -407,6 +407,12 @@ for (const skillName of ['review', 'ship']) { expect(failed.stdout).not.toMatch(/^SCOPE_STATE_MACHINE=false$/m); expect(failed.stderr).toContain('detector crashed'); expect(run('exit 127').stdout).toMatch(/^SCOPE_STATE_MACHINE=unknown$/m); + const silent = run("echo 'detector found no git' >&2; exit 3"); + expect(silent.stdout).toMatch(/^SCOPE_STATE_MACHINE=unknown$/m); + expect(silent.stderr).toContain('detector found no git'); + const lateFail = run("echo SCOPE_STATE_MACHINE=true; echo SCOPE_LEAK=1; exit 1"); + expect(lateFail.stdout).toMatch(/^SCOPE_STATE_MACHINE=unknown$/m); + expect(lateFail.stdout).not.toContain('SCOPE_LEAK'); const detected = run("echo SCOPE_STATE_MACHINE=true; echo '# state-machine: src/takes.ts:4 proposal,accept'"); expect(detected.stdout).toMatch(/^SCOPE_STATE_MACHINE=true$/m); expect(detected.stdout).toMatch(/^SCOPE_FRONTEND=false$/m);