From dbc730d8dea7dad662cf64fb94047e35afa7681f Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 8 Jul 2026 17:53:28 -0400 Subject: [PATCH] fix(#2073): capability-probe external killer (timeout/gtimeout) for macOS Code review (HIGH): a hardcoded 'timeout 600 agy' fails with rc 127 on stock macOS (no GNU timeout/gtimeout), silently losing the agy reviewer. Probe for 'timeout'/'gtimeout' via command -v and fall back to agy's native --print-timeout alone when neither exists (mirrors scripts/base64-scan.sh). External cap (600s) stays >= --print-timeout (540s) so it only backstops a pre-session stall. Factor the prompt into _AGY_PROMPT to avoid duplicating the long -p string across both branches. Update the agy + #687 tests to assert the probe + bound + fallback, regen the 17 goldens + size baseline, refresh the maintainer-note version stamp to 1.0.16. --- gsd-core/workflows/review.md | 27 ++++++++++++++----- tests/antigravity-reviewer.test.cjs | 18 ++++++++----- .../golden-install-parity/antigravity.json | 2 +- .../golden-install-parity/augment.json | 2 +- .../golden-install-parity/claude-local.json | 2 +- .../golden-install-parity/claude.json | 2 +- .../fixtures/golden-install-parity/cline.json | 2 +- .../golden-install-parity/codebuddy.json | 2 +- .../fixtures/golden-install-parity/codex.json | 2 +- .../golden-install-parity/copilot.json | 2 +- .../golden-install-parity/cursor.json | 2 +- .../golden-install-parity/hermes.json | 2 +- .../fixtures/golden-install-parity/kilo.json | 2 +- .../fixtures/golden-install-parity/kimi.json | 2 +- .../golden-install-parity/opencode.json | 2 +- .../fixtures/golden-install-parity/qwen.json | 2 +- .../fixtures/golden-install-parity/trae.json | 2 +- .../golden-install-parity/windsurf.json | 2 +- .../fixtures/golden-install-parity/zcode.json | 2 +- ...review-default-reviewers-workflow.test.cjs | 27 +++++++++++-------- tests/workflow-size-baseline.json | 2 +- 21 files changed, 66 insertions(+), 42 deletions(-) diff --git a/gsd-core/workflows/review.md b/gsd-core/workflows/review.md index a00473f96..4e9543c4c 100644 --- a/gsd-core/workflows/review.md +++ b/gsd-core/workflows/review.md @@ -374,7 +374,7 @@ fi **Antigravity CLI:** -**Maintainer note — why this block has three layers (last updated against agy 1.0.2):** +**Maintainer note — why this block has three layers (last updated against agy 1.0.16):** `agy -p` (the `--print` non-interactive flag) works correctly on macOS and Linux: it sends the prompt, receives the model response, and writes it to stdout. On **native Windows** it silently @@ -440,10 +440,12 @@ fi # for 6 plans + CONTEXT + RESEARCH + REQUIREMENTS) overflows the exec arg list # (`bash: agy: Argument list too long`, rc 126), indistinguishable from a model # failure when stderr is suppressed. -# * EXTERNAL `timeout` wrapper — `--print-timeout` is agy's native cap but it -# CANNOT fire before agy creates a session; under concurrent heavy runs one -# process can stall pre-session (no `brain//` dir, alive at 583 s -# despite `--print-timeout 300s`). The external cap bounds wall-clock regardless. +# * EXTERNAL `timeout` wrapper when available (GNU `timeout` / `gtimeout`) — +# `--print-timeout` is agy's native cap but it CANNOT fire before agy creates a +# session; under concurrent heavy runs one process can stall pre-session (no +# `brain//` dir, alive at 583 s despite `--print-timeout 300s`). The +# external cap bounds wall-clock regardless. Stock macOS lacks `timeout`, so +# the block probes for it and falls back to --print-timeout alone there. # * `--model` from `review.models.agy` when set — escape hatch for a pinned model # that 404s server-side (exits 0 with empty stdout AND empty transcript). # * stdin tied to /dev/null so agy never blocks on a tty. @@ -454,7 +456,20 @@ if [ -n "$AGY_MODEL" ] && [ "$AGY_MODEL" != "null" ]; then else set -- fi -timeout 600 agy --print-timeout 540s "$@" -p "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." /dev/null > /tmp/gsd-review-antigravity-{phase}.md +_AGY_PROMPT="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." +# Capability-probe an external wall-clock killer (GNU coreutils `timeout` or the +# macOS Homebrew `gtimeout`). Stock macOS ships NEITHER — a bare `timeout …` would +# fail with rc 127 ("command not found") and silently lose the reviewer, so fall +# back to agy's native --print-timeout alone in that case. The external cap, when +# available, is set HIGHER than --print-timeout so it only backstops a pre-session +# stall (which --print-timeout cannot bound — #2073 mode 3) and never pre-empts a +# healthy run. Mirrors the probe in scripts/base64-scan.sh. +_AGY_KILLER="$(command -v timeout 2>/dev/null || command -v gtimeout 2>/dev/null || true)" +if [ -n "$_AGY_KILLER" ]; then + "$_AGY_KILLER" 600 agy --print-timeout 540s "$@" -p "$_AGY_PROMPT" /dev/null > /tmp/gsd-review-antigravity-{phase}.md +else + agy --print-timeout 540s "$@" -p "$_AGY_PROMPT" /dev/null > /tmp/gsd-review-antigravity-{phase}.md +fi _AGY_RC=$? if [ "$_AGY_RC" -ne 0 ]; then : > /tmp/gsd-review-antigravity-{phase}.md diff --git a/tests/antigravity-reviewer.test.cjs b/tests/antigravity-reviewer.test.cjs index 09b04fc7f..6e787cba6 100644 --- a/tests/antigravity-reviewer.test.cjs +++ b/tests/antigravity-reviewer.test.cjs @@ -57,12 +57,17 @@ describe('Antigravity (agy) reviewer invocation in /gsd-review (#2073)', () => { ); }); - test('#2073 mode 3 — invocation is wrapped in an external wall-clock timeout', () => { + test('#2073 mode 3 — pairs agy with an external wall-clock killer when available (timeout/gtimeout probe)', () => { const block = agyBashBlock(); - assert.ok( - /(^|\s)timeout\s+\d/.test(block), - 'agy invocation must be wrapped in an external `timeout ` — --print-timeout cannot fire before agy creates a session', - ); + // Capability probe for GNU `timeout` and macOS `gtimeout` (stock macOS has neither). + assert.match(block, /command -v timeout/, 'agy block should probe for the `timeout` killer'); + assert.match(block, /command -v gtimeout/, 'agy block should probe for `gtimeout` (macOS Homebrew)'); + // The external cap (600s) is >= agy's native --print-timeout (540s) so it only + // backstops a pre-session stall, never cuts a healthy run. + assert.match(block, /600 agy --print-timeout 540s/, 'external cap (600s) must be >= --print-timeout (540s)'); + // Graceful fallback when no external killer is available (stock macOS). + assert.match(block, /else\n\s*agy --print-timeout 540s/, + 'agy block must fall back to --print-timeout alone when no external killer is available (macOS)'); }); test('#2073 mode 3 — stdin is tied to /dev/null (no tty stall)', () => { @@ -75,8 +80,7 @@ describe('Antigravity (agy) reviewer invocation in /gsd-review (#2073)', () => { test('#2073 mode 2 — wires review.models.agy via --model when configured', () => { const block = agyBashBlock(); - assert.ok(/--model/.test(block), 'agy block should pass --model when AGY_MODEL is set'); - assert.ok(/AGY_MODEL/.test(block), 'agy block should reference the AGY_MODEL config variable'); + assert.match(block, /--model "\$AGY_MODEL"/, 'agy block should pass --model "$AGY_MODEL" when set'); }); test('#2073 mode 2 — Step 3 stub surfaces a diagnostic from agy cli.log (not just a generic stub)', () => { diff --git a/tests/fixtures/golden-install-parity/antigravity.json b/tests/fixtures/golden-install-parity/antigravity.json index cc1c9ee97..4f674f008 100644 --- a/tests/fixtures/golden-install-parity/antigravity.json +++ b/tests/fixtures/golden-install-parity/antigravity.json @@ -284,7 +284,7 @@ "gsd-core/workflows/remove-phase.md": "23b9eb0858a2535e", "gsd-core/workflows/remove-workspace.md": "d0bd7e0601138798", "gsd-core/workflows/resume-project.md": "98e2cf8908e73a52", - "gsd-core/workflows/review.md": "3597a1ad15eb20a3", + "gsd-core/workflows/review.md": "9ff117dd12ce68ba", "gsd-core/workflows/scan.md": "a7fecd67e5cd655f", "gsd-core/workflows/secure-phase.md": "96b199dfac00e60f", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/fixtures/golden-install-parity/augment.json b/tests/fixtures/golden-install-parity/augment.json index 133da25d2..2742f85c7 100644 --- a/tests/fixtures/golden-install-parity/augment.json +++ b/tests/fixtures/golden-install-parity/augment.json @@ -355,7 +355,7 @@ "gsd-core/workflows/remove-phase.md": "df9a45f0b1880999", "gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c", "gsd-core/workflows/resume-project.md": "f28da1200e4545f4", - "gsd-core/workflows/review.md": "fa3d682b94afcdcd", + "gsd-core/workflows/review.md": "93fe46bd0b5dd3d1", "gsd-core/workflows/scan.md": "003883d71c37da7d", "gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/fixtures/golden-install-parity/claude-local.json b/tests/fixtures/golden-install-parity/claude-local.json index 8657958a8..4f1ce96ee 100644 --- a/tests/fixtures/golden-install-parity/claude-local.json +++ b/tests/fixtures/golden-install-parity/claude-local.json @@ -354,7 +354,7 @@ "gsd-core/workflows/remove-phase.md": "8effc8742d58a11a", "gsd-core/workflows/remove-workspace.md": "10882656198d9075", "gsd-core/workflows/resume-project.md": "af9761bcec0f6fe9", - "gsd-core/workflows/review.md": "641dd9835a19c389", + "gsd-core/workflows/review.md": "f9b63577ff23c2a9", "gsd-core/workflows/scan.md": "75c670d08cee8680", "gsd-core/workflows/secure-phase.md": "64ec4d06ca85720a", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/fixtures/golden-install-parity/claude.json b/tests/fixtures/golden-install-parity/claude.json index 209dbac3c..2a35df78e 100644 --- a/tests/fixtures/golden-install-parity/claude.json +++ b/tests/fixtures/golden-install-parity/claude.json @@ -283,7 +283,7 @@ "gsd-core/workflows/remove-phase.md": "ada8a0546c686483", "gsd-core/workflows/remove-workspace.md": "f3ab3a88a7e9e1ed", "gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96", - "gsd-core/workflows/review.md": "23de3fcd9e5ba43b", + "gsd-core/workflows/review.md": "2dbfb1118613be25", "gsd-core/workflows/scan.md": "47371c2073d6c0be", "gsd-core/workflows/secure-phase.md": "59d3c50aba8c9a6c", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/fixtures/golden-install-parity/cline.json b/tests/fixtures/golden-install-parity/cline.json index b827b085e..1110eff27 100644 --- a/tests/fixtures/golden-install-parity/cline.json +++ b/tests/fixtures/golden-install-parity/cline.json @@ -287,7 +287,7 @@ "gsd-core/workflows/remove-phase.md": "e336350f8113a328", "gsd-core/workflows/remove-workspace.md": "e685dfbd736dfd90", "gsd-core/workflows/resume-project.md": "e23981178fa37b3d", - "gsd-core/workflows/review.md": "21e1b701b454ea9e", + "gsd-core/workflows/review.md": "86d03cb58f48f45b", "gsd-core/workflows/scan.md": "dfd92717caea0ce7", "gsd-core/workflows/secure-phase.md": "cf78183f06a02582", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/fixtures/golden-install-parity/codebuddy.json b/tests/fixtures/golden-install-parity/codebuddy.json index b1a767fd8..3fd1f7110 100644 --- a/tests/fixtures/golden-install-parity/codebuddy.json +++ b/tests/fixtures/golden-install-parity/codebuddy.json @@ -355,7 +355,7 @@ "gsd-core/workflows/remove-phase.md": "df9a45f0b1880999", "gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c", "gsd-core/workflows/resume-project.md": "f28da1200e4545f4", - "gsd-core/workflows/review.md": "fa3d682b94afcdcd", + "gsd-core/workflows/review.md": "93fe46bd0b5dd3d1", "gsd-core/workflows/scan.md": "003883d71c37da7d", "gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/fixtures/golden-install-parity/codex.json b/tests/fixtures/golden-install-parity/codex.json index 633fa8703..b7f1dd231 100644 --- a/tests/fixtures/golden-install-parity/codex.json +++ b/tests/fixtures/golden-install-parity/codex.json @@ -319,7 +319,7 @@ "gsd-core/workflows/remove-phase.md": "9ee0fddd11a0d9d4", "gsd-core/workflows/remove-workspace.md": "19d7465aaa50cb62", "gsd-core/workflows/resume-project.md": "9965f87eb278f7f8", - "gsd-core/workflows/review.md": "e1fdf297509bebb0", + "gsd-core/workflows/review.md": "63c61eddf20d77f1", "gsd-core/workflows/scan.md": "1a3caa5d724d39e9", "gsd-core/workflows/secure-phase.md": "db91810d16964b1e", "gsd-core/workflows/session-report.md": "dd8fa011c9394075", diff --git a/tests/fixtures/golden-install-parity/copilot.json b/tests/fixtures/golden-install-parity/copilot.json index 53c2201a9..64637655a 100644 --- a/tests/fixtures/golden-install-parity/copilot.json +++ b/tests/fixtures/golden-install-parity/copilot.json @@ -285,7 +285,7 @@ "gsd-core/workflows/remove-phase.md": "e262654e319d1bc4", "gsd-core/workflows/remove-workspace.md": "ceddfeef5f2d6754", "gsd-core/workflows/resume-project.md": "40db7f350f5866d8", - "gsd-core/workflows/review.md": "3a6786b46734397e", + "gsd-core/workflows/review.md": "5f146ed397bcfe5a", "gsd-core/workflows/scan.md": "dcc2f76d0850e2fb", "gsd-core/workflows/secure-phase.md": "d87bd706f85bcad6", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/fixtures/golden-install-parity/cursor.json b/tests/fixtures/golden-install-parity/cursor.json index 2b9bf4dad..992990115 100644 --- a/tests/fixtures/golden-install-parity/cursor.json +++ b/tests/fixtures/golden-install-parity/cursor.json @@ -355,7 +355,7 @@ "gsd-core/workflows/remove-phase.md": "ada8a0546c686483", "gsd-core/workflows/remove-workspace.md": "433affcd1a200826", "gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96", - "gsd-core/workflows/review.md": "c2ce42a245fcc972", + "gsd-core/workflows/review.md": "ed6b9f54f74204ff", "gsd-core/workflows/scan.md": "47371c2073d6c0be", "gsd-core/workflows/secure-phase.md": "c55975672c4e1895", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/fixtures/golden-install-parity/hermes.json b/tests/fixtures/golden-install-parity/hermes.json index 816be6a81..4dc7cd405 100644 --- a/tests/fixtures/golden-install-parity/hermes.json +++ b/tests/fixtures/golden-install-parity/hermes.json @@ -284,7 +284,7 @@ "gsd-core/workflows/remove-phase.md": "fce799aae3ab2715", "gsd-core/workflows/remove-workspace.md": "8facde381657dd71", "gsd-core/workflows/resume-project.md": "a0443839f1f83c2d", - "gsd-core/workflows/review.md": "41dc23cb171b336e", + "gsd-core/workflows/review.md": "c9de3987db14b94f", "gsd-core/workflows/scan.md": "b28f65d88c522767", "gsd-core/workflows/secure-phase.md": "f2957d4b88fb3746", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/fixtures/golden-install-parity/kilo.json b/tests/fixtures/golden-install-parity/kilo.json index b6c10bd8e..e2a461d1c 100644 --- a/tests/fixtures/golden-install-parity/kilo.json +++ b/tests/fixtures/golden-install-parity/kilo.json @@ -355,7 +355,7 @@ "gsd-core/workflows/remove-phase.md": "ada8a0546c686483", "gsd-core/workflows/remove-workspace.md": "fc83f362a2d0a1b7", "gsd-core/workflows/resume-project.md": "7f8dc986f0f35d96", - "gsd-core/workflows/review.md": "bf7defbff23639ab", + "gsd-core/workflows/review.md": "dcea2ffd4f54c845", "gsd-core/workflows/scan.md": "47371c2073d6c0be", "gsd-core/workflows/secure-phase.md": "e8855104c1e0417c", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/fixtures/golden-install-parity/kimi.json b/tests/fixtures/golden-install-parity/kimi.json index 0af4bc694..d8e0287ca 100644 --- a/tests/fixtures/golden-install-parity/kimi.json +++ b/tests/fixtures/golden-install-parity/kimi.json @@ -320,7 +320,7 @@ "gsd-core/workflows/remove-phase.md": "df9a45f0b1880999", "gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c", "gsd-core/workflows/resume-project.md": "f28da1200e4545f4", - "gsd-core/workflows/review.md": "fa3d682b94afcdcd", + "gsd-core/workflows/review.md": "93fe46bd0b5dd3d1", "gsd-core/workflows/scan.md": "003883d71c37da7d", "gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/fixtures/golden-install-parity/opencode.json b/tests/fixtures/golden-install-parity/opencode.json index 7adc67017..4fd76a7cd 100644 --- a/tests/fixtures/golden-install-parity/opencode.json +++ b/tests/fixtures/golden-install-parity/opencode.json @@ -355,7 +355,7 @@ "gsd-core/workflows/remove-phase.md": "dea4661e8f89596f", "gsd-core/workflows/remove-workspace.md": "446847e71aa52504", "gsd-core/workflows/resume-project.md": "ad9f06a10bab8cc0", - "gsd-core/workflows/review.md": "8e4d4a67f4b16419", + "gsd-core/workflows/review.md": "a039f632e0b89003", "gsd-core/workflows/scan.md": "ad8ebcad4626d4a8", "gsd-core/workflows/secure-phase.md": "e9a488cec3b4efdc", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/fixtures/golden-install-parity/qwen.json b/tests/fixtures/golden-install-parity/qwen.json index 942069296..0db2d73fb 100644 --- a/tests/fixtures/golden-install-parity/qwen.json +++ b/tests/fixtures/golden-install-parity/qwen.json @@ -284,7 +284,7 @@ "gsd-core/workflows/remove-phase.md": "e8ae4fbbfac700f0", "gsd-core/workflows/remove-workspace.md": "4ac64de862dc650e", "gsd-core/workflows/resume-project.md": "7f20769f302e5427", - "gsd-core/workflows/review.md": "6c639036a133a00a", + "gsd-core/workflows/review.md": "aa4674a4754438f2", "gsd-core/workflows/scan.md": "949692db4834dd27", "gsd-core/workflows/secure-phase.md": "6758f1acf4113e9e", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/fixtures/golden-install-parity/trae.json b/tests/fixtures/golden-install-parity/trae.json index 46464ac7f..1aa5e03d0 100644 --- a/tests/fixtures/golden-install-parity/trae.json +++ b/tests/fixtures/golden-install-parity/trae.json @@ -284,7 +284,7 @@ "gsd-core/workflows/remove-phase.md": "a46c2fe853bf4e86", "gsd-core/workflows/remove-workspace.md": "ae0e1c6d4438d663", "gsd-core/workflows/resume-project.md": "f242e4c8aba18ea2", - "gsd-core/workflows/review.md": "388c7e1a884767bb", + "gsd-core/workflows/review.md": "51eb79b72a09d47a", "gsd-core/workflows/scan.md": "63631467651d9ca8", "gsd-core/workflows/secure-phase.md": "6cc236e53c2e7d56", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/fixtures/golden-install-parity/windsurf.json b/tests/fixtures/golden-install-parity/windsurf.json index 417874a02..e1f88551e 100644 --- a/tests/fixtures/golden-install-parity/windsurf.json +++ b/tests/fixtures/golden-install-parity/windsurf.json @@ -284,7 +284,7 @@ "gsd-core/workflows/remove-phase.md": "e7a6af429b36e77b", "gsd-core/workflows/remove-workspace.md": "b5e60fbb33b3e33a", "gsd-core/workflows/resume-project.md": "82cfe1b8cb17c085", - "gsd-core/workflows/review.md": "846e5693ea0cba84", + "gsd-core/workflows/review.md": "31b1f08d08b8ccdc", "gsd-core/workflows/scan.md": "12c11b2edc165df9", "gsd-core/workflows/secure-phase.md": "7bf923689bf58288", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/fixtures/golden-install-parity/zcode.json b/tests/fixtures/golden-install-parity/zcode.json index 8ece548c5..d291c636b 100644 --- a/tests/fixtures/golden-install-parity/zcode.json +++ b/tests/fixtures/golden-install-parity/zcode.json @@ -355,7 +355,7 @@ "gsd-core/workflows/remove-phase.md": "df9a45f0b1880999", "gsd-core/workflows/remove-workspace.md": "a7ca66db6b7c132c", "gsd-core/workflows/resume-project.md": "f28da1200e4545f4", - "gsd-core/workflows/review.md": "fa3d682b94afcdcd", + "gsd-core/workflows/review.md": "93fe46bd0b5dd3d1", "gsd-core/workflows/scan.md": "003883d71c37da7d", "gsd-core/workflows/secure-phase.md": "29fc6b62c5c5dc62", "gsd-core/workflows/session-report.md": "2e5b1205324ddefa", diff --git a/tests/review-default-reviewers-workflow.test.cjs b/tests/review-default-reviewers-workflow.test.cjs index 3c9245551..6104a8f59 100644 --- a/tests/review-default-reviewers-workflow.test.cjs +++ b/tests/review-default-reviewers-workflow.test.cjs @@ -188,30 +188,35 @@ describe('bug #687 → #2073: agy print mode bounded by --print-timeout PAIRED w // the exec arg list on a large review prompt (Linux MAX_ARG_STRLEN // 128 KB/single-arg → rc 126). - test('invokes agy with --print-timeout AND a paired external timeout', () => { + test('invokes agy with --print-timeout AND a paired external killer when available', () => { const c = read(); assert.match(c, /--print-timeout \d+s?/, 'review.md must pass agy its native --print-timeout'); - // External `timeout agy …` — the paired backstop per agy guidance. - assert.match(c, /(^|\s)timeout\s+\d+\s+agy\b/, - 'review.md must wrap agy in an external `timeout agy` (agy guidance pairs --print-timeout with a terminal timeout)'); + // Capability probe for GNU `timeout` / macOS `gtimeout` (stock macOS has neither). + assert.match(c, /command -v timeout/, 'review.md must probe for the `timeout` killer'); + assert.match(c, /command -v gtimeout/, 'review.md must probe for `gtimeout` (macOS Homebrew)'); + // The external cap (600s) is applied ahead of agy and is >= --print-timeout (540s). + assert.match(c, /600 agy --print-timeout 540s/, + 'review.md must pair an external cap (600s) >= --print-timeout (540s) with agy (agy guidance)'); }); - test('external timeout is >= --print-timeout so it never cuts a healthy run', () => { + test('external cap is >= --print-timeout, and falls back to bare agy on macOS', () => { const c = read(); - const external = c.match(/timeout\s+(\d+)\s+agy\b/); - const native = c.match(/--print-timeout\s+(\d+)s/); - assert.ok(external && native, 'both the external timeout and --print-timeout must be present'); + const bound = c.match(/(\d+)\s+agy --print-timeout (\d+)s/); + assert.ok(bound, 'review.md must encode the external-cap + --print-timeout pair'); assert.ok( - Number(external[1]) >= Number(native[1]), - 'external `timeout` (seconds) must be >= --print-timeout (seconds) so it only backstops a stall', + Number(bound[1]) >= Number(bound[2]), + 'external cap (seconds) must be >= --print-timeout (seconds) so it only backstops a stall', ); + // Graceful fallback when no external killer is available (stock macOS). + assert.match(c, /else\n\s*agy --print-timeout/, + 'review.md must fall back to --print-timeout alone when no external killer is available (macOS)'); }); test('uses a file-reference prompt, not inline "$(cat …)" (arg-list overflow, #2073)', () => { const c = read(); assert.doesNotMatch(c, /agy[^\n]*-p "\$\(cat/, 'review.md must not feed agy the prompt inline via "$(cat …)" — a large review prompt overflows the exec arg list (rc 126)'); - assert.match(c, /agy[^\n]*-p "Read the file at \/tmp\/gsd-review-prompt-/, + assert.match(c, /Read the file at \/tmp\/gsd-review-prompt-/, 'review.md should pass agy a file-reference prompt (mirrors the Cursor block)'); }); diff --git a/tests/workflow-size-baseline.json b/tests/workflow-size-baseline.json index b67f69f44..732a96b91 100644 --- a/tests/workflow-size-baseline.json +++ b/tests/workflow-size-baseline.json @@ -64,7 +64,7 @@ "remove-phase.md": 8513, "remove-workspace.md": 7551, "resume-project.md": 17270, - "review.md": 45299, + "review.md": 46297, "scan.md": 7732, "secure-phase.md": 13520, "session-report.md": 4044,