From 603593d41d5cf898e68261c3ec4e9e5374d53d79 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Tue, 7 Jul 2026 11:07:48 -0400 Subject: [PATCH] fix(#1857): test gates normalize to one-shot + bounded timeout (no watch-mode hang) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A GSD verification gate resolves a project's test command and runs it. vitest defaults to WATCH mode in an interactive TTY — exactly where a user runs `gsd-execute-phase` — so a resolved `npm test`/`pnpm test` backed by vitest never exited and the orchestrator waited indefinitely. Recovery needed the user to manually prompt "something blocking?". Fix — one shared helper + a bounded, surfacing timeout on the test-command gates: - New pure module src/normalize-test-command.cts + `gsd-tools query normalize-test-command` verb: rewrites a resolved command to a best-effort one-shot form (direct vitest → `vitest run`; jest `--watch` → `--watchAll=false`; a package-manager `test` script whose package.json runner is watch-vitest → `CI=true` prefix; handles `--dir`; already-one-shot commands unchanged — never double-flagged). Named `normalize-test-command` (not `test-*`) so the file does not match node --test's default `test-*` discovery glob. - The three gates that HUNG or silently-continued route through that ONE helper and bound execution with `timeout $(config-get workflow.test_gate_timeout)` (new config key, default 600s): the regression gate (extracted to execute-phase/steps/regression-gate.md since execute-phase.md is size-frozen — it shrank 93528→93132; ABORTS on exit 124), the post-merge gate, and the audit-fix gate (previously an UNBOUNDED `eval`). All name watch/dev mode on 124. - verify-phase's gate was ALREADY bounded (a fixed `timeout 300`, not a hang), so it only gains the normalizer (so a watch runner exits fast) + a watch-mode hint on 124, staying under its frozen 40960-byte tier cap. Security hardening (review): the normalizer only rewrites a runner named as a standalone command TOKEN (so `run-vitest.js`/`make test-vitest`/paths are never mangled), is length-capped and uses only linear-time split-based scanning (no super-linear backtracking on an adversarial `workflow.test_command`), and reads package.json only when it is a regular file (never blocks on a FIFO via `--dir`). Config key `workflow.test_gate_timeout` (seconds, default 600) registered in the schema manifest + templates/config.json + docs/CONFIGURATION.md (mirrors workflow.cross_ai_timeout). New module registered in .gitignore, eslint ignores, inventory manifest/index. All 16 golden-install-parity fixtures + workflow size baseline regenerated for the changed shipped files; bin/lib is excluded from the parity manifest. Tests: tests/normalize-test-command.test.cjs (normalizer units incl. security hardening) and tests/test-gate-watch-mode.test.cjs (the three core gates route through the shared helper + configured timeout + exit-124 watch-mode hint; verify-phase asserted as normalize-only/already-bounded). tests/execute-phase-active-flags.test.cjs repointed at the extracted step; tests/planner-language-regression.test.cjs allowlist comment updated. Co-Authored-By: Claude Opus 4.8 --- .../1857-test-gate-watch-mode-timeout.md | 5 + .gitignore | 1 + docs/CONFIGURATION.md | 2 + docs/INVENTORY-MANIFEST.json | 1 + docs/INVENTORY.md | 1 + eslint.config.mjs | 1 + gsd-core/bin/gsd-tools.cjs | 12 +- .../bin/shared/config-schema.manifest.json | 1 + gsd-core/templates/config.json | 3 +- gsd-core/workflows/audit-fix.md | 10 +- gsd-core/workflows/execute-phase.md | 26 +-- .../execute-phase/steps/post-merge-gate.md | 12 +- .../execute-phase/steps/regression-gate.md | 42 ++++ gsd-core/workflows/verify-phase.md | 5 +- src/normalize-test-command.cts | 198 ++++++++++++++++++ tests/execute-phase-active-flags.test.cjs | 11 +- .../golden-install-parity/antigravity.json | 15 +- .../golden-install-parity/augment.json | 15 +- .../golden-install-parity/claude.json | 15 +- .../fixtures/golden-install-parity/cline.json | 15 +- .../golden-install-parity/codebuddy.json | 15 +- .../fixtures/golden-install-parity/codex.json | 15 +- .../golden-install-parity/copilot.json | 15 +- .../golden-install-parity/cursor.json | 15 +- .../golden-install-parity/hermes.json | 15 +- .../fixtures/golden-install-parity/kilo.json | 15 +- .../fixtures/golden-install-parity/kimi.json | 15 +- .../golden-install-parity/opencode.json | 15 +- .../fixtures/golden-install-parity/qwen.json | 15 +- .../fixtures/golden-install-parity/trae.json | 15 +- .../golden-install-parity/windsurf.json | 15 +- .../fixtures/golden-install-parity/zcode.json | 15 +- tests/normalize-test-command.test.cjs | 158 ++++++++++++++ tests/planner-language-regression.test.cjs | 4 +- tests/test-gate-watch-mode.test.cjs | 93 ++++++++ tests/workflow-size-baseline.json | 6 +- 36 files changed, 677 insertions(+), 155 deletions(-) create mode 100644 .changeset/1857-test-gate-watch-mode-timeout.md create mode 100644 gsd-core/workflows/execute-phase/steps/regression-gate.md create mode 100644 src/normalize-test-command.cts create mode 100644 tests/normalize-test-command.test.cjs create mode 100644 tests/test-gate-watch-mode.test.cjs diff --git a/.changeset/1857-test-gate-watch-mode-timeout.md b/.changeset/1857-test-gate-watch-mode-timeout.md new file mode 100644 index 000000000..7d9054ff7 --- /dev/null +++ b/.changeset/1857-test-gate-watch-mode-timeout.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2060 +--- +**Test gates can no longer hang forever on a watch-mode test runner.** vitest defaults to watch mode in an interactive terminal (exactly where `gsd-execute-phase` runs), so a resolved `npm test` / `pnpm test` that maps to vitest never exited and the orchestrator waited indefinitely until the user manually intervened. Every GSD test-command gate — the regression gate, the post-merge gate, the audit-fix gate, and the verify-phase gate — now routes the resolved command through a shared `normalize-test-command` helper that rewrites it to a one-shot form (direct vitest → `vitest run`; jest `--watch` → `--watchAll=false`; a package-manager `test` script backed by watch-vitest → `CI=true` prefix; already-one-shot commands are left unchanged). The three gates that previously hung or silently continued — the regression, post-merge, and audit-fix gates — additionally bound execution with a configurable `workflow.test_gate_timeout` (default 600s), aborting or surfacing the cause on timeout instead of hanging; the verify-phase gate was already bounded (a fixed 5-minute limit) and keeps it, now naming watch mode on timeout. The normalizer only rewrites a runner named as a standalone command token (so paths/targets like `run-vitest.js` are never mangled), is length-capped and linear-time on adversarial input, and only reads a regular-file `package.json`. diff --git a/.gitignore b/.gitignore index 9d3fae1fb..7e954bbf4 100644 --- a/.gitignore +++ b/.gitignore @@ -166,6 +166,7 @@ build/ /gsd-core/bin/lib/core-utils.cjs /gsd-core/bin/lib/io.cjs /gsd-core/bin/lib/phase-id.cjs +/gsd-core/bin/lib/normalize-test-command.cjs /gsd-core/bin/lib/config-loader.cjs /gsd-core/bin/lib/model-resolver.cjs /gsd-core/bin/lib/loop-resolver.cjs diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 4e1a09b4b..f1c4357a8 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -55,6 +55,7 @@ GSD stores project settings in `.planning/config.json`. Created during `/gsd-new "cross_ai_execution": false, "cross_ai_command": null, "cross_ai_timeout": 300, + "test_gate_timeout": 600, "security_enforcement": true, "security_asvs_level": 1, "security_block_on": "high", @@ -322,6 +323,7 @@ All workflow toggles follow the **absent = enabled** pattern. If a key is missin | `workflow.cross_ai_execution` | boolean | `false` | Delegate phase execution to an external AI CLI instead of spawning local executor agents. Useful for leveraging a different model's strengths for specific phases. Added in v1.36 | | `workflow.cross_ai_command` | string | (none) | Shell command template for cross-AI execution. Receives the phase prompt via stdin. Must produce SUMMARY.md-compatible output. Required when `cross_ai_execution` is `true`. Added in v1.36 | | `workflow.cross_ai_timeout` | number | `300` | Timeout in seconds for cross-AI execution commands. Prevents runaway external processes. Added in v1.36 | +| `workflow.test_gate_timeout` | number | `600` | Wall-clock timeout (seconds) for a verification test gate; a watch-mode runner (vitest/jest) that never exits is aborted after this budget instead of hanging the orchestrator (#1857) | | `workflow.ai_integration_phase` | boolean | `true` | Enable the `/gsd-ai-integration-phase` command. When `false`, the command exits with a configuration gate message | | `workflow.auto_prune_state` | boolean | `false` | When `true`, automatically prune stale entries from STATE.md at phase boundaries instead of prompting | | `workflow.pattern_mapper` | boolean | `true` | Run the `gsd-pattern-mapper` agent between research and planning to map new files to existing codebase analogs | diff --git a/docs/INVENTORY-MANIFEST.json b/docs/INVENTORY-MANIFEST.json index bb03ddcf2..8a5fd93fa 100644 --- a/docs/INVENTORY-MANIFEST.json +++ b/docs/INVENTORY-MANIFEST.json @@ -373,6 +373,7 @@ "model-catalog.cjs", "model-profiles.cjs", "model-resolver.cjs", + "normalize-test-command.cjs", "onboard-projection.cjs", "package-identity.cjs", "package-legitimacy.cjs", diff --git a/docs/INVENTORY.md b/docs/INVENTORY.md index 3e2b8824b..8743b4305 100644 --- a/docs/INVENTORY.md +++ b/docs/INVENTORY.md @@ -510,6 +510,7 @@ Full listing: `gsd-core/bin/lib/*.cjs`. | `surface.cjs` | Runtime surface module — manages the runtime enable/disable surface state independently of the install-time profile marker (ADR-0011 Phase 2) | | `task-command-router.cjs` | Thin CJS subcommand router adapter for `gsd-tools task` | | `template.cjs` | Template selection and filling with variable substitution | +| `normalize-test-command.cjs` | Normalizes a resolved test command to a one-shot form so a watch-mode runner (vitest/jest) cannot hang a verification gate (#1857); shared by all four test-command gates (regression, post-merge, audit-fix, verify-phase) | | `uat.cjs` | UAT file parsing, verification debt tracking, audit-uat support | | `uat-predicate.cjs` | UAT-passed predicate — markdown-aware evaluation of HUMAN-UAT results; returns pass only when all required checks pass; ignores false-positive contexts (frontmatter, fenced code, blockquotes, HTML comments) | | `ui-safety-gate.cjs` | Shell-free word-boundary UI token detector (#3706, #3718); reads phase-section text from stdin, exits 0 (UI found) or 1 (no UI); also deployed to `gsd-core/bin/lib/` so the GSD installer ships it to `$RUNTIME_DIR` (#448) | diff --git a/eslint.config.mjs b/eslint.config.mjs index 861f86b6d..493b9a219 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -149,6 +149,7 @@ export default tseslint.config( 'gsd-core/bin/lib/core-utils.cjs', 'gsd-core/bin/lib/io.cjs', 'gsd-core/bin/lib/phase-id.cjs', + 'gsd-core/bin/lib/normalize-test-command.cjs', 'gsd-core/bin/lib/config-loader.cjs', 'gsd-core/bin/lib/phase-locator.cjs', 'gsd-core/bin/lib/roadmap-parser.cjs', diff --git a/gsd-core/bin/gsd-tools.cjs b/gsd-core/bin/gsd-tools.cjs index ffc8ac912..376c0a0b0 100755 --- a/gsd-core/bin/gsd-tools.cjs +++ b/gsd-core/bin/gsd-tools.cjs @@ -688,7 +688,7 @@ async function main() { // phase / roadmap / milestone / progress / etc. const TOP_LEVEL_USAGE = 'Usage: gsd-tools [args] [--raw] [--pick ] [--cwd ] [--ws ] [--json-errors]\n' + 'Commands: agent, agent-skills, assumption-delta, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, pr-subrepo, ' + - 'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, ' + + 'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, normalize-test-command, ' + 'current-timestamp, detect-custom-files, docs-init, drift-guard, effort, extract-messages, find-phase, ' + 'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' + 'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' + @@ -1291,6 +1291,16 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand break; } + case 'normalize-test-command': { + // #1857: rewrite a resolved test command to a one-shot form so a + // watch-mode runner (vitest/jest) cannot hang a verification gate. Shared + // by the regression gate and the post-merge gate. args[1] is the raw + // resolved command; --cwd (already parsed into `cwd`) locates package.json. + const testCommandNormalizer = require('./lib/normalize-test-command.cjs'); + testCommandNormalizer.cmdNormalizeTestCommand(cwd, args[1]); + break; + } + case 'dispatch-should-flatten': { // #1708 / #853: typed query replacing the `RUNTIME === 'codex'` prose rule. // diff --git a/gsd-core/bin/shared/config-schema.manifest.json b/gsd-core/bin/shared/config-schema.manifest.json index bf445f31d..eeadb2d2a 100644 --- a/gsd-core/bin/shared/config-schema.manifest.json +++ b/gsd-core/bin/shared/config-schema.manifest.json @@ -58,6 +58,7 @@ "workflow.cross_ai_command", "workflow.cross_ai_timeout", "workflow.subagent_timeout", + "workflow.test_gate_timeout", "workflow.test_command", "workflow.build_command", "workflow.mvp_mode", diff --git a/gsd-core/templates/config.json b/gsd-core/templates/config.json index efe4c0aa6..a14b51d46 100644 --- a/gsd-core/templates/config.json +++ b/gsd-core/templates/config.json @@ -18,7 +18,8 @@ "plan_bounce_passes": 2, "cross_ai_execution": false, "cross_ai_command": "", - "cross_ai_timeout": 300 + "cross_ai_timeout": 300, + "test_gate_timeout": 600 }, "ship": { "pr_body_sections": [] diff --git a/gsd-core/workflows/audit-fix.md b/gsd-core/workflows/audit-fix.md index c59692423..4dbb21395 100644 --- a/gsd-core/workflows/audit-fix.md +++ b/gsd-core/workflows/audit-fix.md @@ -124,7 +124,15 @@ if [ -z "$AUDIT_TEST_CMD" ]; then AUDIT_TEST_CMD="true" fi fi -eval "$AUDIT_TEST_CMD" 2>&1 | tail -20 +# #1857: normalize to one-shot (defeat vitest/jest watch mode) + bound with a +# timeout so a watch-mode runner cannot hang the audit gate indefinitely. +AUDIT_TEST_CMD=$(gsd_run query normalize-test-command "$AUDIT_TEST_CMD" --cwd . 2>/dev/null || echo "$AUDIT_TEST_CMD") +TEST_GATE_TIMEOUT=$(gsd_run query config-get workflow.test_gate_timeout 2>/dev/null || echo "600") +timeout "$TEST_GATE_TIMEOUT" bash -c "$AUDIT_TEST_CMD" 2>&1 | tail -20 +AUDIT_TEST_EXIT=${PIPESTATUS[0]} +if [ "$AUDIT_TEST_EXIT" -eq 124 ]; then + echo "✗ Audit test gate timed out after ${TEST_GATE_TIMEOUT}s — likely stuck in watch/dev mode (e.g. vitest without 'run'). Run tests one-shot (e.g. 'vitest run') or raise workflow.test_gate_timeout." +fi ``` **c. If tests pass** — commit atomically: diff --git a/gsd-core/workflows/execute-phase.md b/gsd-core/workflows/execute-phase.md index 4e4cfb84a..212b5c293 100644 --- a/gsd-core/workflows/execute-phase.md +++ b/gsd-core/workflows/execute-phase.md @@ -1289,31 +1289,7 @@ For each VERIFICATION.md found, look for test file references: Collect all unique test file paths into `REGRESSION_FILES`. -**Step 3: Run regression tests (if any found)** - -```bash -# Resolve test command: project config > Makefile > language sniff -REG_TEST_CMD=$(gsd_run query config-get workflow.test_command --default "" 2>/dev/null || true) -if [ -z "$REG_TEST_CMD" ]; then - if [ -f "Makefile" ] && grep -q "^test:" Makefile; then - REG_TEST_CMD="make test" - elif [ -f "Justfile" ] || [ -f "justfile" ]; then - REG_TEST_CMD="just test" - elif [ -f "package.json" ]; then - REG_TEST_CMD="npm test" - elif [ -f "Cargo.toml" ]; then - REG_TEST_CMD="cargo test" - elif [ -f "go.mod" ]; then - REG_TEST_CMD="go test ./..." - elif [ -f "requirements.txt" ] || [ -f "pyproject.toml" ]; then - REG_TEST_CMD="python -m pytest ${REGRESSION_FILES} -q --tb=short" - else - REG_TEST_CMD="true" - fi -fi -# Detect test runner and run prior phase tests -eval "$REG_TEST_CMD" 2>&1 -``` +**Step 3: Run regression tests (if any found)** — Read and execute `gsd-core/workflows/execute-phase/steps/regression-gate.md`. It resolves the project test command, normalizes it to a one-shot form (defeating vitest/jest watch mode via the shared `normalize-test-command` helper), runs it under `workflow.test_gate_timeout`, and aborts on timeout with a watch-mode hint (#1857). On `REGRESSION GATE ABORTED` (exit 124), HALT — do not proceed to verification. **Step 4: Report results** diff --git a/gsd-core/workflows/execute-phase/steps/post-merge-gate.md b/gsd-core/workflows/execute-phase/steps/post-merge-gate.md index 9b9eb7761..68f4e0ce6 100644 --- a/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +++ b/gsd-core/workflows/execute-phase/steps/post-merge-gate.md @@ -94,14 +94,18 @@ if [ -z "$TEST_CMD" ]; then echo "⚠ No test runner detected — skipping post-merge test gate" fi fi -# Run test suite with 5-minute timeout +# #1857: normalize to a one-shot form (defeat vitest/jest watch mode) via the +# same shared normalize-test-command helper the regression gate uses, then bound +# with the configured timeout so a watch-mode runner cannot hang the gate. +TEST_CMD=$(gsd_run query normalize-test-command "$TEST_CMD" --cwd . 2>/dev/null || echo "$TEST_CMD") +TEST_GATE_TIMEOUT=$(gsd_run query config-get workflow.test_gate_timeout 2>/dev/null || echo "600") TEST_EXIT=0 -timeout 300 bash -c "$TEST_CMD" 2>&1 +timeout "$TEST_GATE_TIMEOUT" bash -c "$TEST_CMD" 2>&1 TEST_EXIT=$? if [ "${TEST_EXIT}" -eq 0 ]; then echo "✓ Post-merge test gate passed — no cross-plan conflicts" elif [ "${TEST_EXIT}" -eq 124 ]; then - echo "⚠ Post-merge test gate timed out after 5 minutes" + echo "⚠ POST-MERGE TEST GATE TIMED OUT after ${TEST_GATE_TIMEOUT}s — the runner did not exit, likely stuck in watch/dev mode (e.g. vitest without 'run'). Verify tests with a one-shot command (e.g. 'vitest run') or raise workflow.test_gate_timeout." else echo "✗ Post-merge test gate failed (exit code ${TEST_EXIT})" WAVE_FAILURE_COUNT=$((WAVE_FAILURE_COUNT + 1)) @@ -110,7 +114,7 @@ fi **If `TEST_EXIT` is 0 (pass):** `✓ Post-merge test gate: {N} tests passed — no cross-plan conflicts` → continue to orchestrator tracking update. -**If `TEST_EXIT` is 124 (timeout):** Log warning, treat as non-blocking, continue. Tests may need a longer budget or manual run. +**If `TEST_EXIT` is 124 (timeout):** The runner did not exit within the budget — surface the printed message clearly (watch/dev mode is the likely cause; #1857). Treated as non-blocking (a genuinely long suite may just need a larger `workflow.test_gate_timeout`), but it is NEVER silently ignored — the watch-mode cause is named so the user can fix it (one-shot command / `workflow.test_command` / larger timeout). **If `TEST_EXIT` is non-zero (test failure):** Increment `WAVE_FAILURE_COUNT` to track cumulative failures across waves. Subsequent waves should report: diff --git a/gsd-core/workflows/execute-phase/steps/regression-gate.md b/gsd-core/workflows/execute-phase/steps/regression-gate.md new file mode 100644 index 000000000..5a26c3d62 --- /dev/null +++ b/gsd-core/workflows/execute-phase/steps/regression-gate.md @@ -0,0 +1,42 @@ +# Step: regression_gate_run + +Run the resolved prior-phase test command one-shot, bounded by a timeout, so a +watch-mode runner (vitest defaults to watch in a TTY; jest `--watch`) cannot +hang this gate forever (#1857). Uses the shared `normalize-test-command` helper +— the same one the post-merge gate uses — so the two gate paths cannot drift. + +Expects `REGRESSION_FILES` (from the prior step) in scope for the pytest branch. + +```bash +_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi +# Resolve test command: project config > Makefile > language sniff +REG_TEST_CMD=$(gsd_run query config-get workflow.test_command --default "" 2>/dev/null || true) +if [ -z "$REG_TEST_CMD" ]; then + if [ -f "Makefile" ] && grep -q "^test:" Makefile; then + REG_TEST_CMD="make test" + elif [ -f "Justfile" ] || [ -f "justfile" ]; then + REG_TEST_CMD="just test" + elif [ -f "package.json" ]; then + REG_TEST_CMD="npm test" + elif [ -f "Cargo.toml" ]; then + REG_TEST_CMD="cargo test" + elif [ -f "go.mod" ]; then + REG_TEST_CMD="go test ./..." + elif [ -f "requirements.txt" ] || [ -f "pyproject.toml" ]; then + REG_TEST_CMD="python -m pytest ${REGRESSION_FILES} -q --tb=short" + else + REG_TEST_CMD="true" + fi +fi +# #1857: normalize to a one-shot form (defeat vitest/jest watch mode) and bound +# with a timeout so a watch-mode runner cannot hang the gate indefinitely. +REG_TEST_CMD=$(gsd_run query normalize-test-command "$REG_TEST_CMD" --cwd . 2>/dev/null || echo "$REG_TEST_CMD") +TEST_GATE_TIMEOUT=$(gsd_run query config-get workflow.test_gate_timeout 2>/dev/null || echo "600") +timeout "$TEST_GATE_TIMEOUT" bash -c "$REG_TEST_CMD" 2>&1 +REG_TEST_EXIT=$? +if [ "$REG_TEST_EXIT" -eq 124 ]; then + echo "✗ REGRESSION GATE ABORTED — test runner did not exit within ${TEST_GATE_TIMEOUT}s, likely stuck in watch/dev mode (e.g. vitest without 'run'). Run tests one-shot (e.g. 'vitest run'), set workflow.test_command, or raise workflow.test_gate_timeout." +fi +``` + +**On `REG_TEST_EXIT` 124 (`REGRESSION GATE ABORTED`):** HALT — do not proceed to verification. The runner did not exit within the budget (watch/dev mode is the likely cause). Surface the watch-mode cause and the recovery options; never silently continue. diff --git a/gsd-core/workflows/verify-phase.md b/gsd-core/workflows/verify-phase.md index a52aa4ba4..eeeccf447 100644 --- a/gsd-core/workflows/verify-phase.md +++ b/gsd-core/workflows/verify-phase.md @@ -288,14 +288,15 @@ if [ -z "$TEST_CMD" ]; then echo "⚠ No test runner detected — skipping test suite" fi fi -# Detect test runner and run all tests (timeout: 5 minutes) +# Run all tests (timeout: 5 min). #1857: normalize to one-shot so watch mode exits. +TEST_CMD=$(gsd_run query normalize-test-command "$TEST_CMD" --cwd . 2>/dev/null || echo "$TEST_CMD") TEST_EXIT=0 timeout 300 bash -c "$TEST_CMD" 2>&1 TEST_EXIT=$? if [ "${TEST_EXIT}" -eq 0 ]; then echo "✓ Test suite passed" elif [ "${TEST_EXIT}" -eq 124 ]; then - echo "⚠ Test suite timed out after 5 minutes" + echo "⚠ Test suite timed out after 5 minutes — likely watch/dev mode" else echo "✗ Test suite failed (exit code ${TEST_EXIT})" fi diff --git a/src/normalize-test-command.cts b/src/normalize-test-command.cts new file mode 100644 index 000000000..fd5a3bcd6 --- /dev/null +++ b/src/normalize-test-command.cts @@ -0,0 +1,198 @@ +/** + * Test-command normalizer (#1857). + * + * A GSD verification gate resolves a project's test command and runs it. When + * that command is a watch/dev-mode runner (vitest defaults to WATCH in an + * interactive TTY — which is exactly a user running `gsd-execute-phase` in their + * terminal — and jest with `--watch`/`--watchAll`), the process never exits and + * the orchestrator waits forever. + * + * `normalizeTestCommand` rewrites a resolved command to a best-effort one-shot + * form so a gate cannot hang on watch mode. It is intentionally conservative: + * - it NEVER double-adds flags (already-one-shot commands are returned verbatim), + * - it only touches commands it positively recognises as a watch runner, + * - anything it cannot classify is returned unchanged. + * The gate's wall-clock `timeout` is the ultimate guarantee for anything this + * best-effort pass cannot defeat (e.g. an explicit `--watch` baked into a + * project's `test` script, which even `CI=1` cannot override per vitest docs). + * + * Bounded by design (#1857 security review): the input is length-capped, all + * scanning is linear-time (no super-linear regex backtracking), and package.json + * is only read when it is a regular file — so normalization itself can never + * hang or take super-linear time on an adversarial `workflow.test_command`. + * + * Single source of truth: all four test-command gates (regression, post-merge, + * audit-fix, verify-phase) route their resolved command through this helper so + * the paths cannot drift. + * + * Leaf module — depends only on node:fs / node:path. + */ + +import fs from 'node:fs'; +import path from 'node:path'; + +// A resolved test command is never realistically this long; anything larger is +// not a real runner invocation. We skip normalization above this bound (the +// gate's own timeout still bounds the actual run) so no regex ever scans an +// adversarial multi-KB string. +const MAX_COMMAND_LENGTH = 4096; + +// Markers proving a command is already one-shot / non-watch. If any is present +// we return the command unchanged so we never double-add flags or fight an +// explicit user choice. +const ONE_SHOT_MARKERS: RegExp[] = [ + /(?:^|\s)vitest\s+run\b/, // vitest run … + /(?:^|\s)--run\b/, // vitest --run + /(?:^|\s)--no-watch\b/, // vitest --no-watch + /(?:^|\s)--watchAll=false\b/, // jest --watchAll=false + /(?:^|\s)--watch=false\b/, + /(?:^|\s)--ci\b/, // jest --ci + /^\s*CI=/, // already forced into CI/run mode via env +]; + +/** True if the command already runs one-shot (so normalization is a no-op). */ +function isAlreadyOneShot(cmd: string): boolean { + return ONE_SHOT_MARKERS.some((re) => re.test(cmd)); +} + +// Match a runner as a standalone command TOKEN (whitespace-delimited), NOT as a +// substring — so "vitest.config.js" / "jest-environment" / "node vitest-x.js" +// are not treated as the runner and are never mangled. +const VITEST_TOKEN = /(?:^|\s)vitest(?=\s|$)/; +const JEST_TOKEN = /(?:^|\s)jest(?=\s|$)/; + +/** + * Linear-time detection of a package-manager `test` script invocation + * (`npm test`, `pnpm run test`, `pnpm --dir app test`, `yarn test`, …). We split + * on shell separators FIRST (linear), then test each bounded segment with simple + * anchored regexes — no tempered-greedy scan, so no super-linear backtracking on + * adversarial input. + */ +function isScriptInvocation(cmd: string): boolean { + return cmd.split(/&&|\|\||;/).some((seg) => { + const s = seg.trim(); + return /^(?:npm|pnpm|yarn|bun)\b/.test(s) && /\btest\b/.test(s); + }); +} + +/** + * Resolve the package.json directory for a command that may target a different + * working dir via `--dir

` (pnpm), `-C

` (pnpm), or `--prefix

` (npm). + */ +function resolvePackageDir(cmd: string, cwd: string): string { + const m = cmd.match(/(?:--dir|--prefix|-C)[=\s]+(\S+)/); + if (m && m[1]) { + const p = m[1].replace(/^['"]|['"]$/g, ''); + return path.isAbsolute(p) ? p : path.join(cwd, p); + } + return cwd; +} + +type WatchRunner = 'vitest' | 'jest' | null; + +/** + * Inspect package.json `scripts.test` for the given command's target dir and + * report whether it resolves to a watch-by-default / explicitly-watching runner. + * Only ever reads `scripts.test` as a STRING for classification — it is never + * executed or spliced into the output. + */ +function scriptTestRunner(cmd: string, cwd: string): WatchRunner { + try { + const pkgPath = path.join(resolvePackageDir(cmd, cwd), 'package.json'); + // Only read a REGULAR file — never block on a FIFO/socket/dir named + // "package.json" reachable via --dir (#1857 security review). + let stat: fs.Stats; + try { + stat = fs.statSync(pkgPath); + } catch { + return null; + } + if (!stat.isFile()) return null; + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as { + scripts?: Record; + }; + const testScript = pkg.scripts?.test ?? ''; + if (!testScript) return null; + // vitest watches by default unless the script itself is already one-shot. + if (VITEST_TOKEN.test(testScript) && !isAlreadyOneShot(testScript)) return 'vitest'; + // jest only watches when explicitly asked to. + if ( + JEST_TOKEN.test(testScript) && + /(?:^|\s)--watch(?:All)?\b/.test(testScript) && + !/--watch(?:All)?=false\b/.test(testScript) + ) { + return 'jest'; + } + return null; + } catch { + return null; + } +} + +/** Strip a bare/explicit-true `--watch` / `--watchAll` (never `=false`). */ +function stripWatchFlags(cmd: string): string { + return cmd + .replace(/(?:^|\s)--watch(?:All)?(?:=true)?(?=\s|$)/g, ' ') + .replace(/\s{2,}/g, ' ') + .trim(); +} + +/** + * Normalize a resolved test command to a best-effort one-shot form. + * + * @param rawCmd The resolved test command (e.g. from `config-get + * workflow.test_command` or the gate's runner sniff). + * @param cwd Directory used to locate package.json for script invocations. + * @returns A one-shot command, or `rawCmd` unchanged when it is already + * one-shot / not a recognised watch runner / too long to be a real runner. + */ +function normalizeTestCommand(rawCmd: string, cwd: string): string { + const cmd = (rawCmd ?? '').trim(); + if (!cmd || cmd === 'true') return rawCmd; + if (cmd.length > MAX_COMMAND_LENGTH) return rawCmd; // bound all downstream scanning + if (isAlreadyOneShot(cmd)) return rawCmd; + + const isScript = isScriptInvocation(cmd); + + // 1. Direct vitest invocation (`vitest`, `npx vitest`, `pnpm exec vitest`, …): + // force the explicit one-shot `run` command and drop any bare --watch. + if (VITEST_TOKEN.test(cmd) && !isScript) { + return stripWatchFlags(cmd).replace(/(^|\s)vitest(?=\s|$)/, '$1vitest run'); + } + + // 2. Direct jest invocation with an explicit watch flag: make it one-shot. + if (JEST_TOKEN.test(cmd) && !isScript) { + if (/(?:^|\s)--watch(?:All)?\b/.test(cmd)) { + return `${stripWatchFlags(cmd)} --watchAll=false`; + } + return rawCmd; // jest without --watch already runs once + } + + // 3. Package-manager `test` script invocation: the runner is inside + // package.json. If it resolves to a watch runner, force CI/run mode via + // the CI env — robust across --dir and pnpm/yarn `--` propagation quirks + // (vitest & jest both switch to run/non-interactive mode when CI is set). + if (isScript) { + const runner = scriptTestRunner(cmd, cwd); + if (runner === 'vitest' || runner === 'jest') { + return `CI=true ${cmd}`; + } + return rawCmd; + } + + return rawCmd; +} + +/** + * CLI handler for `gsd-tools query normalize-test-command `: prints the + * normalized one-shot command to stdout (the gates capture it via `$(…)`). + */ +function cmdNormalizeTestCommand(cwd: string, rawCmd: string | undefined): void { + process.stdout.write(normalizeTestCommand(rawCmd ?? '', cwd)); +} + +export = { + normalizeTestCommand, + cmdNormalizeTestCommand, + isAlreadyOneShot, +}; diff --git a/tests/execute-phase-active-flags.test.cjs b/tests/execute-phase-active-flags.test.cjs index d2ee66fdc..0bfdf892d 100644 --- a/tests/execute-phase-active-flags.test.cjs +++ b/tests/execute-phase-active-flags.test.cjs @@ -97,6 +97,9 @@ const path = require('path'); const EXECUTE_PHASE_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'execute-phase.md'); const VERIFY_PHASE_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'verify-phase.md'); const AUDIT_FIX_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'audit-fix.md'); +// #1857: execute-phase's regression-gate test-command resolution was extracted +// to this step file (execute-phase.md is size-frozen — phase-6 capstone). +const REGRESSION_GATE_PATH = path.join(__dirname, '..', 'gsd-core', 'workflows', 'execute-phase', 'steps', 'regression-gate.md'); function assertMakefileCheckBeforeNpmTest(filePath, label) { const content = fs.readFileSync(filePath, 'utf-8'); @@ -166,8 +169,8 @@ describe('bug-2396: Makefile test target must take priority over hardcoded comma assert.ok(fs.existsSync(AUDIT_FIX_PATH), 'audit-fix.md should exist'); }); - test('execute-phase.md: Makefile check precedes npm test (post-merge gate)', () => { - assertMakefileCheckBeforeNpmTest(EXECUTE_PHASE_PATH, 'execute-phase.md'); + test('regression-gate step: Makefile check precedes npm test (#1857 — extracted from execute-phase.md)', () => { + assertMakefileCheckBeforeNpmTest(REGRESSION_GATE_PATH, 'regression-gate.md'); }); test('verify-phase.md: Makefile check precedes npm test', () => { @@ -178,8 +181,8 @@ describe('bug-2396: Makefile test target must take priority over hardcoded comma assertMakefileCheckBeforeNpmTest(AUDIT_FIX_PATH, 'audit-fix.md'); }); - test('execute-phase.md: workflow.test_command config checked first (within bash block)', () => { - assertConfigGetBeforeMakefile(EXECUTE_PHASE_PATH, 'execute-phase.md'); + test('regression-gate step: workflow.test_command config checked first (within bash block) (#1857)', () => { + assertConfigGetBeforeMakefile(REGRESSION_GATE_PATH, 'regression-gate.md'); }); test('verify-phase.md: workflow.test_command config checked first (within bash block)', () => { diff --git a/tests/fixtures/golden-install-parity/antigravity.json b/tests/fixtures/golden-install-parity/antigravity.json index 37c8e5990..cb9ae5c8d 100644 --- a/tests/fixtures/golden-install-parity/antigravity.json +++ b/tests/fixtures/golden-install-parity/antigravity.json @@ -38,10 +38,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "ea841e2865248e74", - "gsd-core/bin/gsd-tools.cjs": "b518bc939b3e031e", + "gsd-core/bin/gsd-tools.cjs": "e8ccbbe8cefc2e9a", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "8bc541aabc2e143c", @@ -162,7 +162,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "6c505bdf5af338c8", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "593d4e6d20fdde7c", "gsd-core/templates/continue-here.md": "8b1dce4832c7922b", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -200,7 +200,7 @@ "gsd-core/workflows/add-todo.md": "de1ac76acfcc0133", "gsd-core/workflows/ai-integration-phase.md": "22f5466085c47c00", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", - "gsd-core/workflows/audit-fix.md": "44e9b0a78eb79311", + "gsd-core/workflows/audit-fix.md": "3ecffffe78021e0a", "gsd-core/workflows/audit-milestone.md": "77089ccd7b45c0f0", "gsd-core/workflows/audit-uat.md": "ea8ddd910ed16ba4", "gsd-core/workflows/autonomous.md": "603ce2019835f00e", @@ -231,10 +231,11 @@ "gsd-core/workflows/docs-update.md": "8e986e26d0e6e1a0", "gsd-core/workflows/edit-phase.md": "fc932e82ba1f585a", "gsd-core/workflows/eval-review.md": "eb4040eaa5b8497f", - "gsd-core/workflows/execute-phase.md": "97453d3b8f49f27f", + "gsd-core/workflows/execute-phase.md": "82beafd82b24210c", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "55d0706e80a2554a", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "687513ef1010180e", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "9b7107b31b60a3b9", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "e0fa99178ae544f0", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "907af77eafc3d97b", "gsd-core/workflows/explore.md": "934c00f9f216dbdb", @@ -306,7 +307,7 @@ "gsd-core/workflows/undo.md": "6ab639d1fc7e0721", "gsd-core/workflows/update.md": "2c58df5e21c41c31", "gsd-core/workflows/validate-phase.md": "6c0ab739d15709fa", - "gsd-core/workflows/verify-phase.md": "8070a098ab538b33", + "gsd-core/workflows/verify-phase.md": "0eefbb6bb1b0bed6", "gsd-core/workflows/verify-work.md": "be699ed7920f61b0", "hooks/gsd-check-update-worker.js": "fa301e6366270d5f", "hooks/gsd-check-update.js": "4617a98bf529e4c3", diff --git a/tests/fixtures/golden-install-parity/augment.json b/tests/fixtures/golden-install-parity/augment.json index 0d3ffaa56..d4f58026c 100644 --- a/tests/fixtures/golden-install-parity/augment.json +++ b/tests/fixtures/golden-install-parity/augment.json @@ -109,10 +109,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "2c08276ad3758a7d", + "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -233,7 +233,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "222997133232a6f5", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "69b01e7909ea3f66", "gsd-core/templates/continue-here.md": "f522a51b6895fba8", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -271,7 +271,7 @@ "gsd-core/workflows/add-todo.md": "cc0efe270004c8fb", "gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", - "gsd-core/workflows/audit-fix.md": "514d02c730349a35", + "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", "gsd-core/workflows/audit-milestone.md": "6866ce260980c21e", "gsd-core/workflows/audit-uat.md": "fcfbec501620b564", "gsd-core/workflows/autonomous.md": "7e5683728ef33707", @@ -302,10 +302,11 @@ "gsd-core/workflows/docs-update.md": "f35922d15b7061c9", "gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04", "gsd-core/workflows/eval-review.md": "f898936e2cfe4130", - "gsd-core/workflows/execute-phase.md": "346e443b260e4f38", + "gsd-core/workflows/execute-phase.md": "1b2d0b868f574e80", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "fca31a68773b37da", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "8ccc16a6c7cf6000", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "2c412310dce31a0b", "gsd-core/workflows/explore.md": "6c04f2e658d93261", @@ -377,7 +378,7 @@ "gsd-core/workflows/undo.md": "96d2775f008b3a85", "gsd-core/workflows/update.md": "fd160e13f8b7e83c", "gsd-core/workflows/validate-phase.md": "2c6d7671fcaabcaa", - "gsd-core/workflows/verify-phase.md": "98a995046bdb6c3c", + "gsd-core/workflows/verify-phase.md": "22f18492581f1da5", "gsd-core/workflows/verify-work.md": "34e980a6950cd83c", "hooks/gsd-check-update-worker.js": "cc1ef5f840f9dfc9", "hooks/gsd-check-update.js": "7b3a7983d5f1f5d3", diff --git a/tests/fixtures/golden-install-parity/claude.json b/tests/fixtures/golden-install-parity/claude.json index f0eb6840c..9127cacfc 100644 --- a/tests/fixtures/golden-install-parity/claude.json +++ b/tests/fixtures/golden-install-parity/claude.json @@ -37,10 +37,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "2c08276ad3758a7d", + "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -161,7 +161,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "222997133232a6f5", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "69b01e7909ea3f66", "gsd-core/templates/continue-here.md": "f522a51b6895fba8", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -199,7 +199,7 @@ "gsd-core/workflows/add-todo.md": "8488f10f56ac24ca", "gsd-core/workflows/ai-integration-phase.md": "a898d99b8d844215", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", - "gsd-core/workflows/audit-fix.md": "514d02c730349a35", + "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", "gsd-core/workflows/audit-milestone.md": "23c1e35f449933b8", "gsd-core/workflows/audit-uat.md": "e11db0d74c2a7405", "gsd-core/workflows/autonomous.md": "722397c04272dfaa", @@ -230,10 +230,11 @@ "gsd-core/workflows/docs-update.md": "63082608d3ae92be", "gsd-core/workflows/edit-phase.md": "8323bfe10faa0c0a", "gsd-core/workflows/eval-review.md": "f59e8329dae1e528", - "gsd-core/workflows/execute-phase.md": "4aa452149551956b", + "gsd-core/workflows/execute-phase.md": "4aa35e8cb9d68cde", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "facb0e816d87a0c7", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "fca31a68773b37da", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "8ccc16a6c7cf6000", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "cce1a33fe9a0a32d", "gsd-core/workflows/explore.md": "b9eea1bac358c9ce", @@ -305,7 +306,7 @@ "gsd-core/workflows/undo.md": "791e0bf96d9a057f", "gsd-core/workflows/update.md": "f9e7d8a760d0d3c8", "gsd-core/workflows/validate-phase.md": "2ac231dc541441c2", - "gsd-core/workflows/verify-phase.md": "0d4ffabc1caa473a", + "gsd-core/workflows/verify-phase.md": "e0957e153788a222", "gsd-core/workflows/verify-work.md": "efe57bdbbb3af03f", "hooks/gsd-check-update-worker.js": "a530efdb5fdc0da3", "hooks/gsd-check-update.js": "25cde66a12d6b886", diff --git a/tests/fixtures/golden-install-parity/cline.json b/tests/fixtures/golden-install-parity/cline.json index f56a42ddb..bd83660d1 100644 --- a/tests/fixtures/golden-install-parity/cline.json +++ b/tests/fixtures/golden-install-parity/cline.json @@ -41,10 +41,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "476aa24e8c4f03cf", - "gsd-core/bin/gsd-tools.cjs": "3dce0048d7aed8f4", + "gsd-core/bin/gsd-tools.cjs": "93273f4eda2750d2", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -165,7 +165,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "988307dc093216d1", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "69b01e7909ea3f66", "gsd-core/templates/continue-here.md": "f522a51b6895fba8", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -203,7 +203,7 @@ "gsd-core/workflows/add-todo.md": "b71e80304fe484eb", "gsd-core/workflows/ai-integration-phase.md": "5159c6bdf102f74b", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", - "gsd-core/workflows/audit-fix.md": "47c29d04bab02b27", + "gsd-core/workflows/audit-fix.md": "9bb427cd3b4075d5", "gsd-core/workflows/audit-milestone.md": "2266710d0ae48932", "gsd-core/workflows/audit-uat.md": "1fb7b2ab9d587c8f", "gsd-core/workflows/autonomous.md": "cc5217b1b2238a4c", @@ -234,10 +234,11 @@ "gsd-core/workflows/docs-update.md": "39f288623a8f6f32", "gsd-core/workflows/edit-phase.md": "9c9fadc047c61d74", "gsd-core/workflows/eval-review.md": "3e1d7829ed2ed494", - "gsd-core/workflows/execute-phase.md": "5c56112513a04340", + "gsd-core/workflows/execute-phase.md": "ae12e044fb2f3b57", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "67ebc93f51968cb6", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "e3cb789a350dcadc", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "82e6cfe1e1b1ec0e", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "36b463d72d3e11c5", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "0c5551f99ee0a017", "gsd-core/workflows/explore.md": "e83af8ceae314cf9", @@ -309,7 +310,7 @@ "gsd-core/workflows/undo.md": "96d2775f008b3a85", "gsd-core/workflows/update.md": "165beec33490bd28", "gsd-core/workflows/validate-phase.md": "5b4ae14c87859bd2", - "gsd-core/workflows/verify-phase.md": "20e90407e1e531fc", + "gsd-core/workflows/verify-phase.md": "a4f918c92c927268", "gsd-core/workflows/verify-work.md": "6fa216623778c541", "scripts/changeset/README.md": "86ff89331dfd94b2", "scripts/changeset/cli.cjs": "68f92a344b199271", diff --git a/tests/fixtures/golden-install-parity/codebuddy.json b/tests/fixtures/golden-install-parity/codebuddy.json index d213081fb..71d7569c7 100644 --- a/tests/fixtures/golden-install-parity/codebuddy.json +++ b/tests/fixtures/golden-install-parity/codebuddy.json @@ -109,10 +109,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "2c08276ad3758a7d", + "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -233,7 +233,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "222997133232a6f5", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "69b01e7909ea3f66", "gsd-core/templates/continue-here.md": "f522a51b6895fba8", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -271,7 +271,7 @@ "gsd-core/workflows/add-todo.md": "cc0efe270004c8fb", "gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", - "gsd-core/workflows/audit-fix.md": "514d02c730349a35", + "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", "gsd-core/workflows/audit-milestone.md": "6866ce260980c21e", "gsd-core/workflows/audit-uat.md": "fcfbec501620b564", "gsd-core/workflows/autonomous.md": "7e5683728ef33707", @@ -302,10 +302,11 @@ "gsd-core/workflows/docs-update.md": "f35922d15b7061c9", "gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04", "gsd-core/workflows/eval-review.md": "f898936e2cfe4130", - "gsd-core/workflows/execute-phase.md": "2f9b53aac81a8f27", + "gsd-core/workflows/execute-phase.md": "cf72b6db051f9189", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "fca31a68773b37da", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "8ccc16a6c7cf6000", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "933d10547116794a", "gsd-core/workflows/explore.md": "6c04f2e658d93261", @@ -377,7 +378,7 @@ "gsd-core/workflows/undo.md": "96d2775f008b3a85", "gsd-core/workflows/update.md": "5ff1f77222977648", "gsd-core/workflows/validate-phase.md": "2c6d7671fcaabcaa", - "gsd-core/workflows/verify-phase.md": "98a995046bdb6c3c", + "gsd-core/workflows/verify-phase.md": "22f18492581f1da5", "gsd-core/workflows/verify-work.md": "34e980a6950cd83c", "hooks/gsd-check-update-worker.js": "bdc9324a2f080ddd", "hooks/gsd-check-update.js": "b7669f605631e506", diff --git a/tests/fixtures/golden-install-parity/codex.json b/tests/fixtures/golden-install-parity/codex.json index ac9aa3935..ad2b46d1f 100644 --- a/tests/fixtures/golden-install-parity/codex.json +++ b/tests/fixtures/golden-install-parity/codex.json @@ -73,10 +73,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "2c08276ad3758a7d", + "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -197,7 +197,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "5b5dd37145241462", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "593d4e6d20fdde7c", "gsd-core/templates/continue-here.md": "8b1dce4832c7922b", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -235,7 +235,7 @@ "gsd-core/workflows/add-todo.md": "097e171f797737df", "gsd-core/workflows/ai-integration-phase.md": "1dfa15d8f28c022d", "gsd-core/workflows/analyze-dependencies.md": "f799abc00907377f", - "gsd-core/workflows/audit-fix.md": "514d02c730349a35", + "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", "gsd-core/workflows/audit-milestone.md": "46edd152d8b63bfc", "gsd-core/workflows/audit-uat.md": "2564078edb15b5e9", "gsd-core/workflows/autonomous.md": "92f08626d4aea668", @@ -266,10 +266,11 @@ "gsd-core/workflows/docs-update.md": "e255317df939e302", "gsd-core/workflows/edit-phase.md": "e592a4d85ce5380f", "gsd-core/workflows/eval-review.md": "63d0d0670b54c244", - "gsd-core/workflows/execute-phase.md": "b6f4765843dc05f7", + "gsd-core/workflows/execute-phase.md": "e62b664cf4db4d55", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "f4cacd27d37bac65", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "fca31a68773b37da", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "8ccc16a6c7cf6000", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "ac1f1d9ada00a91e", "gsd-core/workflows/explore.md": "2ef10d17c8864a04", @@ -341,7 +342,7 @@ "gsd-core/workflows/undo.md": "5ff7d63b0a2f46d5", "gsd-core/workflows/update.md": "5c35c0ec0f462ea6", "gsd-core/workflows/validate-phase.md": "020201a41049679f", - "gsd-core/workflows/verify-phase.md": "b9f70235e777aa81", + "gsd-core/workflows/verify-phase.md": "2e12c3cb97a9122a", "gsd-core/workflows/verify-work.md": "69e27f6f419d0bba", "hooks/gsd-check-update.js": "ef48957eb6ac6a10", "hooks/gsd-context-monitor.js": "76fecaaa2babd6c1", diff --git a/tests/fixtures/golden-install-parity/copilot.json b/tests/fixtures/golden-install-parity/copilot.json index 8d8bb2f6b..0e194d4b8 100644 --- a/tests/fixtures/golden-install-parity/copilot.json +++ b/tests/fixtures/golden-install-parity/copilot.json @@ -39,10 +39,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "ea841e2865248e74", - "gsd-core/bin/gsd-tools.cjs": "b518bc939b3e031e", + "gsd-core/bin/gsd-tools.cjs": "e8ccbbe8cefc2e9a", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "10226e9512dd44bf", @@ -163,7 +163,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "27318be7b7c984e1", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "593d4e6d20fdde7c", "gsd-core/templates/continue-here.md": "8b1dce4832c7922b", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -201,7 +201,7 @@ "gsd-core/workflows/add-todo.md": "10253591112b7d06", "gsd-core/workflows/ai-integration-phase.md": "ef0c2474cee76b00", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", - "gsd-core/workflows/audit-fix.md": "d25431af0ecf6355", + "gsd-core/workflows/audit-fix.md": "9fdfe8a7fbcd1417", "gsd-core/workflows/audit-milestone.md": "19e03e6d34d96f38", "gsd-core/workflows/audit-uat.md": "5ea0e30548ed0933", "gsd-core/workflows/autonomous.md": "dd972ddde9663295", @@ -232,10 +232,11 @@ "gsd-core/workflows/docs-update.md": "9292cfa3c52c434e", "gsd-core/workflows/edit-phase.md": "8667c28b22b1599f", "gsd-core/workflows/eval-review.md": "81c8e72ba3862856", - "gsd-core/workflows/execute-phase.md": "bc0ec0e19ce96269", + "gsd-core/workflows/execute-phase.md": "6f7aa92705d3527e", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "63b712920f21a40f", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "15f872e83ad87196", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "1b73ab2fc47c9dbf", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "b26edeee455481a7", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "c5e9dae726db15cc", "gsd-core/workflows/explore.md": "5fd91a8510e1114b", @@ -307,7 +308,7 @@ "gsd-core/workflows/undo.md": "ba1ef7aa80bef6bd", "gsd-core/workflows/update.md": "f444a7cfcd246cfb", "gsd-core/workflows/validate-phase.md": "2f705775a4b76d42", - "gsd-core/workflows/verify-phase.md": "59d1046b3a2bc481", + "gsd-core/workflows/verify-phase.md": "5c72780e34214e27", "gsd-core/workflows/verify-work.md": "58e9b1b16f773b53", "hooks/gsd-session.json": "0a462834f2a28fee", "scripts/changeset/README.md": "86ff89331dfd94b2", diff --git a/tests/fixtures/golden-install-parity/cursor.json b/tests/fixtures/golden-install-parity/cursor.json index 835644697..dcca3bbc3 100644 --- a/tests/fixtures/golden-install-parity/cursor.json +++ b/tests/fixtures/golden-install-parity/cursor.json @@ -109,10 +109,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "2525f1ae8b086828", - "gsd-core/bin/gsd-tools.cjs": "7fd5fbaf1bbd9db5", + "gsd-core/bin/gsd-tools.cjs": "3fa86b509ea5c8fc", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -233,7 +233,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "762b16f9a6488474", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "69b01e7909ea3f66", "gsd-core/templates/continue-here.md": "f522a51b6895fba8", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -271,7 +271,7 @@ "gsd-core/workflows/add-todo.md": "d483ebe8edcf193a", "gsd-core/workflows/ai-integration-phase.md": "b79f320d59a68773", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", - "gsd-core/workflows/audit-fix.md": "514d02c730349a35", + "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", "gsd-core/workflows/audit-milestone.md": "23c1e35f449933b8", "gsd-core/workflows/audit-uat.md": "e11db0d74c2a7405", "gsd-core/workflows/autonomous.md": "4b20eda9a0b587ce", @@ -302,10 +302,11 @@ "gsd-core/workflows/docs-update.md": "e86d7d7e2e3dac6d", "gsd-core/workflows/edit-phase.md": "8323bfe10faa0c0a", "gsd-core/workflows/eval-review.md": "a86279dd98dd5c03", - "gsd-core/workflows/execute-phase.md": "f9c628754cd74d0a", + "gsd-core/workflows/execute-phase.md": "cf1b15f505b519d8", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "facb0e816d87a0c7", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "fca31a68773b37da", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "8ccc16a6c7cf6000", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "83dc1bf7f73735c0", "gsd-core/workflows/explore.md": "b9eea1bac358c9ce", @@ -377,7 +378,7 @@ "gsd-core/workflows/undo.md": "18dec684fb1076f9", "gsd-core/workflows/update.md": "23e294ba707c3580", "gsd-core/workflows/validate-phase.md": "2df0c6e298a5f249", - "gsd-core/workflows/verify-phase.md": "0d4ffabc1caa473a", + "gsd-core/workflows/verify-phase.md": "e0957e153788a222", "gsd-core/workflows/verify-work.md": "145596b2542c457a", "hooks/gsd-cursor-post-tool.js": "019d503aee8b4a3f", "hooks/gsd-cursor-session-start.js": "c6e04ed597ea7020", diff --git a/tests/fixtures/golden-install-parity/hermes.json b/tests/fixtures/golden-install-parity/hermes.json index 8b226a2b9..a76235b8d 100644 --- a/tests/fixtures/golden-install-parity/hermes.json +++ b/tests/fixtures/golden-install-parity/hermes.json @@ -38,10 +38,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "3a3409215044af9f", - "gsd-core/bin/gsd-tools.cjs": "e666de93d4fd49fc", + "gsd-core/bin/gsd-tools.cjs": "e979f092ce2b15ee", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -162,7 +162,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "6f968b6bb6e1982f", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "69b01e7909ea3f66", "gsd-core/templates/continue-here.md": "f522a51b6895fba8", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -200,7 +200,7 @@ "gsd-core/workflows/add-todo.md": "5fe3ddc3227e0e22", "gsd-core/workflows/ai-integration-phase.md": "ddab2912d025db65", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", - "gsd-core/workflows/audit-fix.md": "cc6270e912471cc2", + "gsd-core/workflows/audit-fix.md": "2eee4a0be82ef951", "gsd-core/workflows/audit-milestone.md": "806d346ee5bfe9f8", "gsd-core/workflows/audit-uat.md": "86d9131fccabf2ad", "gsd-core/workflows/autonomous.md": "dd572ca89862e5ec", @@ -231,10 +231,11 @@ "gsd-core/workflows/docs-update.md": "4d6c06e611d83b6d", "gsd-core/workflows/edit-phase.md": "7f27003f20e88fb8", "gsd-core/workflows/eval-review.md": "f510e5762212dc6f", - "gsd-core/workflows/execute-phase.md": "c449a87fcca14433", + "gsd-core/workflows/execute-phase.md": "f36c7b810285838c", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "ceb8758c22660c1a", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "3ab8bc8a07133068", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "c9ad17d6cc6dfe45", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "5a78d5dfea6a911a", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "4dbe9b6f0c976245", "gsd-core/workflows/explore.md": "48770d68e8b9c132", @@ -306,7 +307,7 @@ "gsd-core/workflows/undo.md": "791e0bf96d9a057f", "gsd-core/workflows/update.md": "7499bb4cb2a3ce6f", "gsd-core/workflows/validate-phase.md": "75e8971d3981d06b", - "gsd-core/workflows/verify-phase.md": "84e00bd685006870", + "gsd-core/workflows/verify-phase.md": "5c8d1305b47fbef4", "gsd-core/workflows/verify-work.md": "e56e07475d5eb51e", "hooks/gsd-check-update-worker.js": "7989cc2bedd1138d", "hooks/gsd-check-update.js": "25f5ad726f76fc11", diff --git a/tests/fixtures/golden-install-parity/kilo.json b/tests/fixtures/golden-install-parity/kilo.json index 7e737fb76..87f53dc78 100644 --- a/tests/fixtures/golden-install-parity/kilo.json +++ b/tests/fixtures/golden-install-parity/kilo.json @@ -109,10 +109,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "2c08276ad3758a7d", + "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -233,7 +233,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "5b5dd37145241462", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "593d4e6d20fdde7c", "gsd-core/templates/continue-here.md": "8b1dce4832c7922b", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -271,7 +271,7 @@ "gsd-core/workflows/add-todo.md": "5082510bf0449204", "gsd-core/workflows/ai-integration-phase.md": "c85c6afdc64f0da6", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", - "gsd-core/workflows/audit-fix.md": "514d02c730349a35", + "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", "gsd-core/workflows/audit-milestone.md": "23c1e35f449933b8", "gsd-core/workflows/audit-uat.md": "e11db0d74c2a7405", "gsd-core/workflows/autonomous.md": "73f429f7472c9949", @@ -302,10 +302,11 @@ "gsd-core/workflows/docs-update.md": "79afaaf19fd527cc", "gsd-core/workflows/edit-phase.md": "8323bfe10faa0c0a", "gsd-core/workflows/eval-review.md": "926eda8bbee28b23", - "gsd-core/workflows/execute-phase.md": "17cf87593b833b6d", + "gsd-core/workflows/execute-phase.md": "ec448a1e25854abf", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "facb0e816d87a0c7", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "fca31a68773b37da", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "8ccc16a6c7cf6000", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "8dc89b35582407f7", "gsd-core/workflows/explore.md": "14242d36d4822df6", @@ -377,7 +378,7 @@ "gsd-core/workflows/undo.md": "7cd2153f8b15e30d", "gsd-core/workflows/update.md": "07dc2fba78ad1865", "gsd-core/workflows/validate-phase.md": "557e3251e3b9349a", - "gsd-core/workflows/verify-phase.md": "0d4ffabc1caa473a", + "gsd-core/workflows/verify-phase.md": "e0957e153788a222", "gsd-core/workflows/verify-work.md": "b68ac37f6301a3b5", "hooks/gsd-check-update-worker.js": "c992bbad91d0e994", "hooks/gsd-check-update.js": "fdd77abe7ef26a2d", diff --git a/tests/fixtures/golden-install-parity/kimi.json b/tests/fixtures/golden-install-parity/kimi.json index feedd61a8..795b3c198 100644 --- a/tests/fixtures/golden-install-parity/kimi.json +++ b/tests/fixtures/golden-install-parity/kimi.json @@ -74,10 +74,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "2c08276ad3758a7d", + "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -198,7 +198,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "222997133232a6f5", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "69b01e7909ea3f66", "gsd-core/templates/continue-here.md": "f522a51b6895fba8", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -236,7 +236,7 @@ "gsd-core/workflows/add-todo.md": "cc0efe270004c8fb", "gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", - "gsd-core/workflows/audit-fix.md": "514d02c730349a35", + "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", "gsd-core/workflows/audit-milestone.md": "6866ce260980c21e", "gsd-core/workflows/audit-uat.md": "fcfbec501620b564", "gsd-core/workflows/autonomous.md": "7e5683728ef33707", @@ -267,10 +267,11 @@ "gsd-core/workflows/docs-update.md": "f35922d15b7061c9", "gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04", "gsd-core/workflows/eval-review.md": "f898936e2cfe4130", - "gsd-core/workflows/execute-phase.md": "5d3377bc8ebbae58", + "gsd-core/workflows/execute-phase.md": "91fe17be2d178f24", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "fca31a68773b37da", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "8ccc16a6c7cf6000", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "c8502b7475d797a7", "gsd-core/workflows/explore.md": "6c04f2e658d93261", @@ -342,7 +343,7 @@ "gsd-core/workflows/undo.md": "96d2775f008b3a85", "gsd-core/workflows/update.md": "6718e0632bba26ca", "gsd-core/workflows/validate-phase.md": "2c6d7671fcaabcaa", - "gsd-core/workflows/verify-phase.md": "98a995046bdb6c3c", + "gsd-core/workflows/verify-phase.md": "22f18492581f1da5", "gsd-core/workflows/verify-work.md": "34e980a6950cd83c", "scripts/changeset/README.md": "86ff89331dfd94b2", "scripts/changeset/cli.cjs": "68f92a344b199271", diff --git a/tests/fixtures/golden-install-parity/opencode.json b/tests/fixtures/golden-install-parity/opencode.json index 2a8b8fbd8..1da46755a 100644 --- a/tests/fixtures/golden-install-parity/opencode.json +++ b/tests/fixtures/golden-install-parity/opencode.json @@ -109,10 +109,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "2c08276ad3758a7d", + "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -233,7 +233,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "bb76f9538f2bc4e0", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "593d4e6d20fdde7c", "gsd-core/templates/continue-here.md": "8b1dce4832c7922b", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -271,7 +271,7 @@ "gsd-core/workflows/add-todo.md": "72fcef9fd0cafda5", "gsd-core/workflows/ai-integration-phase.md": "90e5f97018715b18", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", - "gsd-core/workflows/audit-fix.md": "497a33eb4ee38d47", + "gsd-core/workflows/audit-fix.md": "2871c5d0a4b671fa", "gsd-core/workflows/audit-milestone.md": "7aa0db364a4b67e2", "gsd-core/workflows/audit-uat.md": "c800099fab1e1c1b", "gsd-core/workflows/autonomous.md": "6cb0c014f5f56965", @@ -302,10 +302,11 @@ "gsd-core/workflows/docs-update.md": "850366c2ef8fb780", "gsd-core/workflows/edit-phase.md": "1876c855fb0a0a39", "gsd-core/workflows/eval-review.md": "5394694d29ad7543", - "gsd-core/workflows/execute-phase.md": "ec2ef02d93b17a24", + "gsd-core/workflows/execute-phase.md": "9026c312d564381f", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "1804215577f1ad35", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "647ec0d29a0d6365", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "baa2c401af10a80a", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "b86ca98268dd3705", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "e8de8ea661c1fe81", "gsd-core/workflows/explore.md": "7f5f9231cfd3089b", @@ -377,7 +378,7 @@ "gsd-core/workflows/undo.md": "0bba5e7f6196c894", "gsd-core/workflows/update.md": "71b6cd852f38b4bc", "gsd-core/workflows/validate-phase.md": "abcdbc1b56780565", - "gsd-core/workflows/verify-phase.md": "5bd04b040cd071dd", + "gsd-core/workflows/verify-phase.md": "126be1d026900102", "gsd-core/workflows/verify-work.md": "f0d205568abfaf74", "hooks/gsd-check-update-worker.js": "385fb7c67810baf6", "hooks/gsd-check-update.js": "4549451414ffa7d7", diff --git a/tests/fixtures/golden-install-parity/qwen.json b/tests/fixtures/golden-install-parity/qwen.json index 3c2e758f5..bf6e3e38e 100644 --- a/tests/fixtures/golden-install-parity/qwen.json +++ b/tests/fixtures/golden-install-parity/qwen.json @@ -38,10 +38,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "6e98d76e955e35a2", - "gsd-core/bin/gsd-tools.cjs": "1caaaac4e81c7105", + "gsd-core/bin/gsd-tools.cjs": "0341a6cf54b9acbe", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -162,7 +162,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "fe32daf7235d2a93", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "69b01e7909ea3f66", "gsd-core/templates/continue-here.md": "f522a51b6895fba8", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -200,7 +200,7 @@ "gsd-core/workflows/add-todo.md": "5e037b444e657557", "gsd-core/workflows/ai-integration-phase.md": "afdc7c15f03a95fc", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", - "gsd-core/workflows/audit-fix.md": "da62236c7b3161a0", + "gsd-core/workflows/audit-fix.md": "aaa1b74e001322b6", "gsd-core/workflows/audit-milestone.md": "b7617cf590d92a56", "gsd-core/workflows/audit-uat.md": "97d441d07e7972b3", "gsd-core/workflows/autonomous.md": "3014ff90115f0daf", @@ -231,10 +231,11 @@ "gsd-core/workflows/docs-update.md": "45d2f0d173c84e07", "gsd-core/workflows/edit-phase.md": "0fb5e0123cfc6f36", "gsd-core/workflows/eval-review.md": "6dee8a1e40ececd4", - "gsd-core/workflows/execute-phase.md": "6bb6f3c6a33c4233", + "gsd-core/workflows/execute-phase.md": "60deeb0a791c3cd5", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "36af8d91e4ae8b9c", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "5c30e38cdc265e20", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "98db1ba4c39cd784", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "56fc78db4de210df", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "503b0ced0731dc38", "gsd-core/workflows/explore.md": "e1a83a8982532e5b", @@ -306,7 +307,7 @@ "gsd-core/workflows/undo.md": "791e0bf96d9a057f", "gsd-core/workflows/update.md": "b34cb866152d4de3", "gsd-core/workflows/validate-phase.md": "e989cbaa4228564c", - "gsd-core/workflows/verify-phase.md": "a8679ba1d84145c5", + "gsd-core/workflows/verify-phase.md": "0dc52b9e629a5f5a", "gsd-core/workflows/verify-work.md": "b5afb65fdf311301", "hooks/gsd-check-update-worker.js": "4bb354044e0dff91", "hooks/gsd-check-update.js": "d2065cb3e725a42a", diff --git a/tests/fixtures/golden-install-parity/trae.json b/tests/fixtures/golden-install-parity/trae.json index 3c43712ee..5aecb31d3 100644 --- a/tests/fixtures/golden-install-parity/trae.json +++ b/tests/fixtures/golden-install-parity/trae.json @@ -38,10 +38,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "de4627dff103d527", - "gsd-core/bin/gsd-tools.cjs": "172d8fce13b74b2d", + "gsd-core/bin/gsd-tools.cjs": "75bf5691ded4927b", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -162,7 +162,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "c9c5df7a8911bca9", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "69b01e7909ea3f66", "gsd-core/templates/continue-here.md": "f522a51b6895fba8", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -200,7 +200,7 @@ "gsd-core/workflows/add-todo.md": "0fe07cbd29ec252b", "gsd-core/workflows/ai-integration-phase.md": "d469eb120e52de0f", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", - "gsd-core/workflows/audit-fix.md": "ac71c1179444fdc3", + "gsd-core/workflows/audit-fix.md": "76607add3257cb37", "gsd-core/workflows/audit-milestone.md": "a379eda51d821bce", "gsd-core/workflows/audit-uat.md": "6e768b1c208a787a", "gsd-core/workflows/autonomous.md": "c482645c5d1ead46", @@ -231,10 +231,11 @@ "gsd-core/workflows/docs-update.md": "f13571f08e083356", "gsd-core/workflows/edit-phase.md": "7facd0faa33c8cad", "gsd-core/workflows/eval-review.md": "37d545d4f0db4927", - "gsd-core/workflows/execute-phase.md": "0f90fb13e63cf2b2", + "gsd-core/workflows/execute-phase.md": "aa5abb8c77a00153", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "c985a30317a1aa6b", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "1e0cc4675bcf1496", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "0a9e915170c7121c", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "4b00ef3484a84954", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "4910f75bab2040ab", "gsd-core/workflows/explore.md": "8a5437aa0c239c38", @@ -306,7 +307,7 @@ "gsd-core/workflows/undo.md": "59b8baa54efc4110", "gsd-core/workflows/update.md": "1f935251fca1f276", "gsd-core/workflows/validate-phase.md": "1c0ebe56d96a14d1", - "gsd-core/workflows/verify-phase.md": "53cbdce394dcdac0", + "gsd-core/workflows/verify-phase.md": "a151ed36eb51813b", "gsd-core/workflows/verify-work.md": "d3a0970205acc6a5", "scripts/changeset/README.md": "86ff89331dfd94b2", "scripts/changeset/cli.cjs": "68f92a344b199271", diff --git a/tests/fixtures/golden-install-parity/windsurf.json b/tests/fixtures/golden-install-parity/windsurf.json index 6dd136a20..71f434e76 100644 --- a/tests/fixtures/golden-install-parity/windsurf.json +++ b/tests/fixtures/golden-install-parity/windsurf.json @@ -38,10 +38,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "5636ca0b726871b2", - "gsd-core/bin/gsd-tools.cjs": "d4fb7a7431f2b7d2", + "gsd-core/bin/gsd-tools.cjs": "dd746ae946645155", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -162,7 +162,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "f7e0345b72e84e6e", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "69b01e7909ea3f66", "gsd-core/templates/continue-here.md": "f522a51b6895fba8", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -200,7 +200,7 @@ "gsd-core/workflows/add-todo.md": "cab0d8215579fbdd", "gsd-core/workflows/ai-integration-phase.md": "8948988717506320", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", - "gsd-core/workflows/audit-fix.md": "babe6812b86cec8f", + "gsd-core/workflows/audit-fix.md": "aa052e15623d759d", "gsd-core/workflows/audit-milestone.md": "ac2238ea6c11ae9e", "gsd-core/workflows/audit-uat.md": "d496e39402e160de", "gsd-core/workflows/autonomous.md": "fe7bb7c978f305a2", @@ -231,10 +231,11 @@ "gsd-core/workflows/docs-update.md": "70f73cc8c27e0ca0", "gsd-core/workflows/edit-phase.md": "c0ae7d0063f3e789", "gsd-core/workflows/eval-review.md": "b28be79ef29f16fd", - "gsd-core/workflows/execute-phase.md": "7cf72d7c95491ece", + "gsd-core/workflows/execute-phase.md": "1dac1bdda0696dc0", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "47ae5482f8e64100", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "0f9ccd7edea1d6a9", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "15bca39a75c664be", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "02324a66cb42fae3", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "c8567fb4438b4404", "gsd-core/workflows/explore.md": "04e461ff8159a24e", @@ -306,7 +307,7 @@ "gsd-core/workflows/undo.md": "18dec684fb1076f9", "gsd-core/workflows/update.md": "79aaf4b8f1f83045", "gsd-core/workflows/validate-phase.md": "2db47bf5547d7b9d", - "gsd-core/workflows/verify-phase.md": "ea7f8a705b297e27", + "gsd-core/workflows/verify-phase.md": "f961cdb3ef03ff05", "gsd-core/workflows/verify-work.md": "5ad63a5edfb6acac", "scripts/changeset/README.md": "86ff89331dfd94b2", "scripts/changeset/cli.cjs": "68f92a344b199271", diff --git a/tests/fixtures/golden-install-parity/zcode.json b/tests/fixtures/golden-install-parity/zcode.json index 36a418191..9b0b55d69 100644 --- a/tests/fixtures/golden-install-parity/zcode.json +++ b/tests/fixtures/golden-install-parity/zcode.json @@ -109,10 +109,10 @@ "gsd-core/VERSION": "ef0deccd81a6723c", "gsd-core/bin/check-latest-version.cjs": "e4a224058c8f4d74", "gsd-core/bin/ensure-runtime-build.cjs": "51bc64467ab30f62", - "gsd-core/bin/gsd-tools.cjs": "2c08276ad3758a7d", + "gsd-core/bin/gsd-tools.cjs": "6a7616125440c8b1", "gsd-core/bin/gsd_run": "62d9b647ede212e6", "gsd-core/bin/shared/config-defaults.manifest.json": "517e6a7c1e9f4f16", - "gsd-core/bin/shared/config-schema.manifest.json": "1318633d27964e7a", + "gsd-core/bin/shared/config-schema.manifest.json": "6bba2b9c9fa47cb8", "gsd-core/bin/shared/model-catalog.json": "e554a288fcbc1b2e", "gsd-core/bin/shared/runtime-aliases.manifest.json": "2df2c5ac1957911a", "gsd-core/bin/verify-reapply-patches.cjs": "caec5dbce11e3904", @@ -233,7 +233,7 @@ "gsd-core/templates/codebase/stack.md": "116e7e67dd87ddec", "gsd-core/templates/codebase/structure.md": "222997133232a6f5", "gsd-core/templates/codebase/testing.md": "76abff7f2050c9ea", - "gsd-core/templates/config.json": "1473dff11272d3c4", + "gsd-core/templates/config.json": "a4b783ef759a0f37", "gsd-core/templates/context.md": "69b01e7909ea3f66", "gsd-core/templates/continue-here.md": "f522a51b6895fba8", "gsd-core/templates/copilot-instructions.md": "aea34bc52ff548ea", @@ -271,7 +271,7 @@ "gsd-core/workflows/add-todo.md": "cc0efe270004c8fb", "gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", - "gsd-core/workflows/audit-fix.md": "514d02c730349a35", + "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", "gsd-core/workflows/audit-milestone.md": "6866ce260980c21e", "gsd-core/workflows/audit-uat.md": "fcfbec501620b564", "gsd-core/workflows/autonomous.md": "7e5683728ef33707", @@ -302,10 +302,11 @@ "gsd-core/workflows/docs-update.md": "f35922d15b7061c9", "gsd-core/workflows/edit-phase.md": "966a3eadd1bebc04", "gsd-core/workflows/eval-review.md": "f898936e2cfe4130", - "gsd-core/workflows/execute-phase.md": "49f793a605e4665e", + "gsd-core/workflows/execute-phase.md": "bb4f1120804c5a90", "gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md": "4e265392b3f2ba0e", "gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md": "7ebb7d1af6082028", - "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "fca31a68773b37da", + "gsd-core/workflows/execute-phase/steps/post-merge-gate.md": "811b6d8489571581", + "gsd-core/workflows/execute-phase/steps/regression-gate.md": "8ccc16a6c7cf6000", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md": "be84efbd71e1513e", "gsd-core/workflows/execute-plan.md": "ed874410972d32b7", "gsd-core/workflows/explore.md": "6c04f2e658d93261", @@ -377,7 +378,7 @@ "gsd-core/workflows/undo.md": "96d2775f008b3a85", "gsd-core/workflows/update.md": "dc580dee13f881a6", "gsd-core/workflows/validate-phase.md": "2c6d7671fcaabcaa", - "gsd-core/workflows/verify-phase.md": "98a995046bdb6c3c", + "gsd-core/workflows/verify-phase.md": "22f18492581f1da5", "gsd-core/workflows/verify-work.md": "34e980a6950cd83c", "hooks/gsd-check-update-worker.js": "6660bcf03ed849a0", "hooks/gsd-check-update.js": "901ea3bd75fd5bbc", diff --git a/tests/normalize-test-command.test.cjs b/tests/normalize-test-command.test.cjs new file mode 100644 index 000000000..7a0a3b41b --- /dev/null +++ b/tests/normalize-test-command.test.cjs @@ -0,0 +1,158 @@ +'use strict'; + +/** + * Regression tests for the test-command normalizer (#1857). + * + * A GSD test gate must not hang forever on a watch-mode runner. The normalizer + * rewrites a resolved test command to a best-effort one-shot form; the gate's + * wall-clock timeout is the ultimate guarantee (asserted separately as workflow + * content). These tests cover the two normalization acceptance criteria: + * (a) an already-one-shot command is invoked UNCHANGED (never double-flagged), + * (b) a watch-mode command is normalized to a one-shot form. + */ + +const { test, describe, beforeEach, afterEach } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); + +const { cleanup } = require('./helpers.cjs'); + +const normalizer = require('../gsd-core/bin/lib/normalize-test-command.cjs'); +const { normalizeTestCommand, isAlreadyOneShot } = normalizer; + +function tmpProject(testScript) { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-1857-')); + fs.writeFileSync( + path.join(dir, 'package.json'), + JSON.stringify({ scripts: { test: testScript } }), + ); + return dir; +} + +describe('normalizeTestCommand: direct vitest (watch by default) → one-shot (#1857)', () => { + test('bare vitest gains "run"', () => { + assert.strictEqual(normalizeTestCommand('vitest', '/tmp'), 'vitest run'); + }); + test('npx vitest with flags gains "run" and keeps flags', () => { + assert.strictEqual(normalizeTestCommand('npx vitest --coverage', '/tmp'), 'npx vitest run --coverage'); + }); + test('vitest --watch is stripped and made one-shot', () => { + assert.strictEqual(normalizeTestCommand('vitest --watch', '/tmp'), 'vitest run'); + }); +}); + +describe('normalizeTestCommand: already one-shot is a no-op (#1857)', () => { + for (const cmd of ['vitest run', 'vitest --run', 'npx vitest run -t foo', 'jest --watchAll=false']) { + test(`"${cmd}" is recognised one-shot and returned unchanged`, () => { + assert.strictEqual(normalizeTestCommand(cmd, '/tmp'), cmd); + assert.ok(isAlreadyOneShot(cmd), `"${cmd}" should be recognised as one-shot`); + }); + } + // jest is one-shot by default (no --watch); --runInBand doesn't disable watch, + // so it isn't a one-shot MARKER — but a jest command without a watch flag is + // still returned unchanged via the jest branch. + for (const cmd of ['jest', 'jest --runInBand']) { + test(`"${cmd}" (jest, no watch flag) is returned unchanged`, () => { + assert.strictEqual(normalizeTestCommand(cmd, '/tmp'), cmd); + }); + } +}); + +describe('normalizeTestCommand: direct jest with watch flag → one-shot (#1857)', () => { + test('jest --watch → --watchAll=false', () => { + assert.strictEqual(normalizeTestCommand('jest --watch', '/tmp'), 'jest --watchAll=false'); + }); + test('jest --watchAll → --watchAll=false', () => { + assert.strictEqual(normalizeTestCommand('jest --watchAll', '/tmp'), 'jest --watchAll=false'); + }); +}); + +describe('normalizeTestCommand: package-manager script invocation inspects package.json (#1857)', () => { + let vitestDir; let vitestRunDir; let jestDir; + beforeEach(() => { + vitestDir = tmpProject('vitest'); // watches by default + vitestRunDir = tmpProject('vitest run'); // already one-shot + jestDir = tmpProject('jest'); // one-shot by default + }); + afterEach(() => { + for (const d of [vitestDir, vitestRunDir, jestDir]) { + cleanup(d); + } + }); + + test('npm test whose script is watch-vitest → CI=true prefix', () => { + assert.strictEqual(normalizeTestCommand('npm test', vitestDir), 'CI=true npm test'); + }); + test('pnpm test whose script is watch-vitest → CI=true prefix', () => { + assert.strictEqual(normalizeTestCommand('pnpm test', vitestDir), 'CI=true pnpm test'); + }); + test('npm test whose script is already "vitest run" → unchanged', () => { + assert.strictEqual(normalizeTestCommand('npm test', vitestRunDir), 'npm test'); + }); + test('npm test whose script is jest (one-shot) → unchanged', () => { + assert.strictEqual(normalizeTestCommand('npm test', jestDir), 'npm test'); + }); + + test('pnpm --dir test inspects the target package.json (#1857 comment 5)', () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-1857-root-')); + try { + fs.mkdirSync(path.join(root, 'app')); + fs.writeFileSync(path.join(root, 'app', 'package.json'), JSON.stringify({ scripts: { test: 'vitest' } })); + assert.strictEqual(normalizeTestCommand('pnpm --dir app test', root), 'CI=true pnpm --dir app test'); + // An explicit --run the user already passed is respected (no-op); the + // gate timeout backstops a project that still watches despite --run. + assert.strictEqual(normalizeTestCommand('pnpm --dir app test -- --run', root), 'pnpm --dir app test -- --run'); + } finally { + cleanup(root); + } + }); +}); + +describe('normalizeTestCommand: non-JS / unknown runners are untouched (#1857)', () => { + for (const cmd of ['cargo test', 'go test ./...', 'make test', 'python -m pytest -q', 'true', '']) { + test(`"${cmd}" is returned unchanged`, () => { + assert.strictEqual(normalizeTestCommand(cmd, '/tmp'), cmd); + }); + } + test('npm test with no package.json is left unchanged (cannot classify)', () => { + const empty = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-1857-empty-')); + try { + assert.strictEqual(normalizeTestCommand('npm test', empty), 'npm test'); + } finally { + cleanup(empty); + } + }); +}); + +describe('normalizeTestCommand: security hardening (#1857 review)', () => { + // "vitest"/"jest" as a token that isn't the invoked binary must NOT be mangled. + for (const cmd of ['make test-vitest', 'node ./scripts/run-vitest.js', './bin/vitest-wrapper.sh', 'cat vitest.config.js', 'node jest-runner.js']) { + test(`"${cmd}" is not mangled (word-token, not substring)`, () => { + assert.strictEqual(normalizeTestCommand(cmd, '/tmp'), cmd); + }); + } + + test('an oversized command is returned unchanged and in linear time (no ReDoS)', () => { + // The blow-up input from the review: a long "npm " run with no `test` token. + const huge = 'npm '.repeat(200000); // ~800 KB + const start = Date.now(); + const out = normalizeTestCommand(huge, '/tmp'); + const elapsedMs = Date.now() - start; + assert.strictEqual(out, huge, 'oversized input must be returned unchanged'); + assert.ok(elapsedMs < 250, `normalization must be fast even on adversarial input (took ${elapsedMs}ms)`); + }); + + test('a package.json that is not a regular file is ignored (no FIFO hang)', () => { + // resolvePackageDir would point here; a non-regular "package.json" must be skipped. + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-1857-notfile-')); + try { + fs.mkdirSync(path.join(dir, 'package.json')); // a DIRECTORY named package.json + // Classified as "cannot determine runner" → command returned unchanged. + assert.strictEqual(normalizeTestCommand('npm test', dir), 'npm test'); + } finally { + cleanup(dir); + } + }); +}); diff --git a/tests/planner-language-regression.test.cjs b/tests/planner-language-regression.test.cjs index 8dfadf1dc..be1dc6514 100644 --- a/tests/planner-language-regression.test.cjs +++ b/tests/planner-language-regression.test.cjs @@ -122,9 +122,9 @@ const ALLOWLIST = { 'review.md': ['time_sizing'], // Fast uses "under 2 minutes wall time" as operational constraint 'fast.md': ['time_sizing'], - // Execute-phase uses "timeout: 5 minutes" for test runner + // Execute-phase uses a configurable test-gate timeout (workflow.test_gate_timeout, #1857) 'execute-phase.md': ['time_sizing'], - // Verify-phase uses "timeout: 5 minutes" for test runner + // Verify-phase uses a configurable test-gate timeout (workflow.test_gate_timeout, #1857) 'verify-phase.md': ['time_sizing'], // Map-codebase documents subagent_timeout 'map-codebase.md': ['time_sizing'], diff --git a/tests/test-gate-watch-mode.test.cjs b/tests/test-gate-watch-mode.test.cjs new file mode 100644 index 000000000..81d6e7606 --- /dev/null +++ b/tests/test-gate-watch-mode.test.cjs @@ -0,0 +1,93 @@ +// allow-test-rule: source-text-is-the-product #1857 +// Workflow .md files — their text IS the deployed contract the orchestrator runs. + +/** + * #1857: a GSD test gate must not hang forever on a watch-mode runner. + * + * Three gates were UNBOUNDED or silently-continued and are the core fix. They must: + * - route the resolved command through the shared `normalize-test-command` + * helper (defeats vitest/jest watch mode), so the paths cannot drift, + * - bound execution with `timeout` using the `workflow.test_gate_timeout` + * budget, and + * - surface a timeout (exit 124) with a watch-mode hint — the regression gate + * ABORTS, the others surface clearly (never silently ignored). + * + * verify-phase's gate was ALREADY bounded (a fixed `timeout 300`, not a hang), so + * it only needs the normalizer (so a watch runner exits fast) and keeps its own + * fixed 5-minute bound — asserted separately below. + */ + +'use strict'; + +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const ROOT = path.join(__dirname, '..'); +const REGRESSION_GATE = path.join(ROOT, 'gsd-core', 'workflows', 'execute-phase', 'steps', 'regression-gate.md'); +const POST_MERGE_GATE = path.join(ROOT, 'gsd-core', 'workflows', 'execute-phase', 'steps', 'post-merge-gate.md'); +const AUDIT_FIX = path.join(ROOT, 'gsd-core', 'workflows', 'audit-fix.md'); +const VERIFY_PHASE = path.join(ROOT, 'gsd-core', 'workflows', 'verify-phase.md'); +const EXECUTE_PHASE = path.join(ROOT, 'gsd-core', 'workflows', 'execute-phase.md'); + +function read(p) { return fs.readFileSync(p, 'utf-8'); } + +// The three gates that were unbounded/silently-continued: full normalize + configured timeout. +const FULL_GATES = [ + ['regression gate', REGRESSION_GATE], + ['post-merge gate', POST_MERGE_GATE], + ['audit-fix gate', AUDIT_FIX], +]; + +describe('#1857: test gates normalize to one-shot and bound with a timeout', () => { + for (const [label, file] of FULL_GATES) { + describe(label, () => { + test('routes the resolved command through the shared normalize-test-command helper', () => { + const c = read(file); + assert.match(c, /normalize-test-command/, `${label} must call the shared normalize-test-command helper`); + }); + test('bounds execution with a timeout using the workflow.test_gate_timeout budget', () => { + const c = read(file); + assert.match(c, /workflow\.test_gate_timeout/, `${label} must read workflow.test_gate_timeout`); + assert.match(c, /timeout "\$TEST_GATE_TIMEOUT"/, `${label} must wrap the test command in a timeout with the configured budget`); + }); + test('surfaces a timeout (exit 124) with a watch-mode hint — never a silent hang', () => { + const c = read(file); + assert.match(c, /-eq 124/, `${label} must handle the timeout exit code (124)`); + assert.match(c, /watch\/dev mode/, `${label} must name watch/dev mode as the likely cause on timeout`); + }); + }); + } + + // verify-phase is already bounded (fixed `timeout 300`, not a hang); it only + // needs the normalizer so a watch runner exits fast, and names watch mode on 124. + describe('verify-phase gate (already bounded — normalize-only)', () => { + test('routes the resolved command through the shared normalize-test-command helper', () => { + assert.match(read(VERIFY_PHASE), /normalize-test-command/, 'verify-phase must call the shared normalize-test-command helper'); + }); + test('surfaces its fixed timeout (exit 124) naming watch/dev mode', () => { + const c = read(VERIFY_PHASE); + assert.match(c, /-eq 124/, 'verify-phase must handle the timeout exit code (124)'); + assert.match(c, /watch\/dev mode/, 'verify-phase must name watch/dev mode as the likely cause on timeout'); + }); + }); + + test('the regression gate ABORTS (halts) on a watch-mode timeout', () => { + const c = read(REGRESSION_GATE); + assert.match(c, /REGRESSION GATE ABORTED/, 'regression gate must abort (not continue) on timeout'); + }); + + test('execute-phase.md delegates the regression gate to the extracted step (size-frozen file stays lean)', () => { + const c = read(EXECUTE_PHASE); + assert.match(c, /steps\/regression-gate\.md/, 'execute-phase.md must reference the extracted regression-gate step'); + }); + + test('the gates share ONE normalizer — the helper is a single source of truth', () => { + // The behaviour lives in src/normalize-test-command.cts; every gate invokes it + // by the same verb name, so a change to watch-defeat logic touches one place. + for (const file of [REGRESSION_GATE, POST_MERGE_GATE, AUDIT_FIX, VERIFY_PHASE]) { + assert.match(read(file), /gsd_run query normalize-test-command/); + } + }); +}); diff --git a/tests/workflow-size-baseline.json b/tests/workflow-size-baseline.json index 58e1158b4..945f3c59f 100644 --- a/tests/workflow-size-baseline.json +++ b/tests/workflow-size-baseline.json @@ -5,7 +5,7 @@ "add-todo.md": 8996, "ai-integration-phase.md": 14805, "analyze-dependencies.md": 3887, - "audit-fix.md": 11032, + "audit-fix.md": 11717, "audit-milestone.md": 17681, "audit-uat.md": 7469, "autonomous.md": 42474, @@ -24,7 +24,7 @@ "docs-update.md": 55706, "edit-phase.md": 12927, "eval-review.md": 9967, - "execute-phase.md": 93528, + "execute-phase.md": 93132, "execute-plan.md": 32655, "explore.md": 10541, "extract-learnings.md": 12893, @@ -88,6 +88,6 @@ "undo.md": 10431, "update.md": 20914, "validate-phase.md": 10789, - "verify-phase.md": 40772, + "verify-phase.md": 40923, "verify-work.md": 38267 }