From 2bfff17ff81911a83e000b6568e3a380b9252f8b Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Thu, 17 Sep 2026 02:23:03 -0400 Subject: [PATCH] fix(#4682): route stale verification to the verifier regeneration path (#4818) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(#4682): add failing-first coverage for stale verification routing * fix(#4682): route stale verification to the verifier regeneration path The stale routing entry sent users to /gsd-verify-work — but verify-work never rewrites VERIFICATION.md (its only write is the human_needed canonicalization), so following the advice re-ran UAT, reached the same stale check, and looped. init's projector and execute-phase's generic next_command presentation both mirror this entry, so the dead end appeared on three surfaces. The stale entry now routes to execute-phase, and execute-phase's all-plans-complete resume tree gains a stale arm (as a steps/ part, keeping the spine under its frozen ADR-857 ceiling) mirroring the missing route: skip cross_ai_delegation/execute_waves/checkpoint_handling, continue at aggregate_results, and let verify_phase_goal re-dispatch the gsd-verifier — regenerating VERIFICATION.md and its digest, marked phase or not. The non-stale fall-through. Staleness detection, the digest format (#4623), every other routing entry, and the #3684 resume arms are untouched. Emitted-Drift-Ack-Growth: verify-work.md — stale stop rewritten to dispatch the verifier and re-check (#4682) Emitted-Drift-Ack-Growth: execute-phase.md — VERIFY_STATUS == stale resume arm added to condition 3 (#4682) * test(#4682): register the stale-reverification part and align projected commands The new steps/ part must be registered in the inventory manifest and the per-runtime golden install trees (regen:derived); the projected stale next_command is /gsd-execute-phase (formatGsdSlash prefixes the runtime surface), the human_needed bare-report probe keeps routing to verify-work (unchanged semantics), and init-manager's recommended action follows the new command. * test(#4682): prefix the remaining stale routing assertions with the runtime surface Nine stale next_command assertions and the human_needed bare-report probe still carried the unprefixed or flipped forms from the earlier line-number edit; all now assert the shipped /gsd-execute-phase projection, with the human_needed probe reverted to its unchanged verify-work routing. * test(#4682): align the last stale projection assertions with the execute-phase route * docs(#4682): backfill changeset PR number * test(#4682): refresh the compact-content baseline after the rebase The rebase onto the #4670 squash brought verify-work.md's bounded reconciliation text into this branch; the committed compact-content baseline now reflects the post-rebase split sizes. Local --check is clean; the previous bench drift (+243) was the baseline, not the diff. * fix(#4682): carry the response_language directive in the stale-reverification part The new steps/ part is its own coverage unit for lint-response-language-coverage; it takes the shared canonical directive line like its sibling execute-phase parts. --------- Co-authored-by: sim --- .changeset/vivid-deer-howl.md | 5 ++++ docs/INVENTORY-MANIFEST.json | 1 + gsd-core/workflows/execute-phase.md | 2 ++ .../steps/stale-reverification.md | 24 ++++++++++++++++ gsd-core/workflows/verify-work.md | 14 ++++++++-- src/verification.cts | 15 ++++++++-- .../compact-content-benchmark-baseline.json | 18 ++++++------ tests/fixtures/install-tree/antigravity.json | 1 + tests/fixtures/install-tree/augment.json | 1 + tests/fixtures/install-tree/claude-local.json | 1 + tests/fixtures/install-tree/claude.json | 1 + tests/fixtures/install-tree/cline.json | 1 + tests/fixtures/install-tree/codebuddy.json | 1 + tests/fixtures/install-tree/codex.json | 1 + tests/fixtures/install-tree/copilot.json | 1 + tests/fixtures/install-tree/cursor.json | 1 + tests/fixtures/install-tree/hermes.json | 1 + tests/fixtures/install-tree/kilo.json | 1 + tests/fixtures/install-tree/kimi-code.json | 1 + tests/fixtures/install-tree/kimi.json | 1 + tests/fixtures/install-tree/opencode.json | 1 + tests/fixtures/install-tree/pi.json | 1 + tests/fixtures/install-tree/qwen.json | 1 + tests/fixtures/install-tree/trae.json | 1 + tests/fixtures/install-tree/windsurf.json | 1 + tests/fixtures/install-tree/zcode.json | 1 + tests/init-manager.test.cjs | 2 +- tests/phase.test.cjs | 2 +- tests/verification-status.test.cjs | 28 ++++++++++++------- tests/verify-work-auto-transition.test.cjs | 20 +++++++++++++ 30 files changed, 123 insertions(+), 27 deletions(-) create mode 100644 .changeset/vivid-deer-howl.md create mode 100644 gsd-core/workflows/execute-phase/steps/stale-reverification.md diff --git a/.changeset/vivid-deer-howl.md b/.changeset/vivid-deer-howl.md new file mode 100644 index 000000000..e378e51ee --- /dev/null +++ b/.changeset/vivid-deer-howl.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 4818 +--- +**Stale verification now routes to the verifier** — the `stale` status told users to re-run `/gsd-verify-work`, but that workflow never rewrites VERIFICATION.md, so following the advice looped forever. The routing now names the regeneration path (execute-phase resumes at the verification gates and re-runs the verifier), and verify-work's stale stop dispatches `gsd-verifier` and re-checks instead of self-referencing. (#4682) diff --git a/docs/INVENTORY-MANIFEST.json b/docs/INVENTORY-MANIFEST.json index ba83d8226..646ed0ffc 100644 --- a/docs/INVENTORY-MANIFEST.json +++ b/docs/INVENTORY-MANIFEST.json @@ -664,6 +664,7 @@ "execute-phase/steps/regression-gate-run.md", "execute-phase/steps/regression-gate.md", "execute-phase/steps/sequential-root-pin.md", + "execute-phase/steps/stale-reverification.md", "execute-phase/steps/tdd-applicability-resolution.md", "execute-phase/steps/wave-post-gate-hooks.md", "execute-phase/steps/worktree-recovery-policy.md", diff --git a/gsd-core/workflows/execute-phase.md b/gsd-core/workflows/execute-phase.md index fa6747fdc..bba0312aa 100644 --- a/gsd-core/workflows/execute-phase.md +++ b/gsd-core/workflows/execute-phase.md @@ -366,6 +366,8 @@ later conditions once one matches: 2b. **No filter is active, no blocked-plan skip occurred, and at least one filtered plan was skipped because `ready: false` (#4628)** — the phase is WAITING on incomplete predecessors, not finished: report it by name and exit before any completion state (`execute-phase/steps/ready-wave-gate.md`). 3. **No filter is active, and every filtered plan was filtered by `has_summary` alone** (no blocked-plan skip occurred): + - **`VERIFY_STATUS == stale` (#4682)**: covered source changed after the verifier ran — + re-verify per `execute-phase/steps/stale-reverification.md`. - **`VERIFY_STATUS == missing`**: the plans are all summarized but the run never reached the tail gates. Report: `"All {plan_count} plans are summarized but no VERIFICATION.md exists — resuming at the phase gates (#2868)."` diff --git a/gsd-core/workflows/execute-phase/steps/stale-reverification.md b/gsd-core/workflows/execute-phase/steps/stale-reverification.md new file mode 100644 index 000000000..bbc8e6128 --- /dev/null +++ b/gsd-core/workflows/execute-phase/steps/stale-reverification.md @@ -0,0 +1,24 @@ +Apply response_language to all user-facing prose — narration between tool calls, status updates, progress notes, and findings included; preserve code, paths, and identifiers. + + +Covered source files changed after the verifier last ran — the recorded digest no longer +matches, so the report cannot be trusted until the verifier re-runs (#4682). The plans are all +summarized: there is no wave work to do. + +Report: +``` +"Verification is stale — covered source changed after the verifier ran (#4682). Re-running the verifier for this phase." +``` + +SKIP `cross_ai_delegation`, `execute_waves` and `checkpoint_handling` — there is no wave work — +and continue directly at `aggregate_results` exactly as the `missing` route (#2868): the tail +steps re-run in their normal order (`aggregate_results` → `code_review_gate` → +`close_parent_artifacts` → `regression_gate` → `verify_phase_goal` → `update_roadmap`), and +`verify_phase_goal` re-dispatches the `gsd-verifier`, regenerating VERIFICATION.md and its +digest. This holds whether or not the phase was already marked complete — a stale report on a +marked phase is refreshed the same way. `verification.status`'s `next_command` routes here for +exactly this state. + +Never silently proceed past a stale gate: if the re-run verifier still produces a stale report, +stop and present it (#4623 covers what the digest hashes). + diff --git a/gsd-core/workflows/verify-work.md b/gsd-core/workflows/verify-work.md index f51e5799a..83de94662 100644 --- a/gsd-core/workflows/verify-work.md +++ b/gsd-core/workflows/verify-work.md @@ -673,15 +673,23 @@ if [ "$VERIFICATION_STATUS_VALUE" = "human_needed" ]; then fi ``` -If `PHASE_VERIFICATION_STATUS` is `stale`, stop before phase advancement and present: +If `PHASE_VERIFICATION_STATUS` is `stale`, the covered source files changed after the verifier +last ran — re-run the VERIFIER, not this workflow (`/gsd:verify-work` never rewrites +VERIFICATION.md; its only write is the human_needed canonicalization, #4663). Spawn the +verifier for this phase exactly as execute-phase's `verify_phase_goal` step does (subagent +`gsd-verifier`; phase directory, goal, requirement IDs, and all SUMMARYs in +``), then re-read `verification.status` and continue at the fresh/passed +case below. (#4682) ``` -All UAT tests passed, but phase advancement is blocked until canonical verification is fresh. +Verification is stale: covered source files changed after the verifier last ran. Blocking completion: verification is stale -- `/gsd:verify-work {phase}` — re-run verification against the latest summaries +- Re-run the verifier for phase {phase} (dispatch `gsd-verifier` as in execute-phase's + verify_phase_goal step) to regenerate VERIFICATION.md with a fresh digest, then re-run + `/gsd:verify-work {phase}` ``` Otherwise, check the shared UAT-plus-verification completion predicate before transition: diff --git a/src/verification.cts b/src/verification.cts index c0cf7ab78..0d4b2477d 100644 --- a/src/verification.cts +++ b/src/verification.cts @@ -112,8 +112,14 @@ const VERIFICATION_ROUTING_TABLE: Record = { }, stale: { status: 'stale', - next_action: 'Verification is stale. Re-run verify-work before transition.', - next_command: '', + // #4682: staleness means covered source files changed after the verifier + // last ran — the only remedy is re-running the verifier. + // /gsd-verify-work never rewrites VERIFICATION.md, so advising it from + // here was an advice loop. execute-phase resumes at the verification + // gates and re-runs the verifier (its resume tree routes a stale report + // to re-verification), which regenerates VERIFICATION.md and its digest. + next_action: 'Verification is stale — covered source files changed after the verifier last ran. Re-run execute-phase for this phase: it resumes at the verification gates and re-runs the verifier, regenerating VERIFICATION.md and its digest. verify-work alone cannot refresh a stale report.', + next_command: 'execute-phase', }, // INTERNAL SENTINEL: constructed when no *-VERIFICATION.md file exists or when // the file has no parseable frontmatter status. Never emitted by the verifier. @@ -1115,7 +1121,10 @@ function readVerificationStatus( return { status: entry.status, next_action: entry.next_action, - next_command: projectNextCommand('verify-work', runtime, phaseArg), + // #4682: execute-phase resumes at the verification gates and re-runs + // the verifier, regenerating VERIFICATION.md and its digest — the same + // routing the `missing` sentinel has used since #2868. + next_command: projectNextCommand('execute-phase', runtime, phaseArg), }; } diff --git a/tests/fixtures/compact-content-benchmark-baseline.json b/tests/fixtures/compact-content-benchmark-baseline.json index 46d6b7652..09f80c519 100644 --- a/tests/fixtures/compact-content-benchmark-baseline.json +++ b/tests/fixtures/compact-content-benchmark-baseline.json @@ -18,9 +18,9 @@ "reductionPct": 16.51 }, "execute-phase": { - "offTokens": 26259, - "onTokens": 23969, - "reductionPct": 8.72 + "offTokens": 26297, + "onTokens": 24007, + "reductionPct": 8.71 }, "new-project": { "offTokens": 14308, @@ -33,14 +33,14 @@ "reductionPct": 11.9 }, "verify-work": { - "offTokens": 13235, - "onTokens": 11262, - "reductionPct": 14.91 + "offTokens": 13384, + "onTokens": 11411, + "reductionPct": 14.74 } }, "aggregate": { - "offTokens": 108873, - "onTokens": 92186, - "reductionPct": 15.33 + "offTokens": 109060, + "onTokens": 92373, + "reductionPct": 15.3 } } diff --git a/tests/fixtures/install-tree/antigravity.json b/tests/fixtures/install-tree/antigravity.json index 037f26bfd..5e2861a3b 100644 --- a/tests/fixtures/install-tree/antigravity.json +++ b/tests/fixtures/install-tree/antigravity.json @@ -453,6 +453,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/augment.json b/tests/fixtures/install-tree/augment.json index b320eb9d0..3f9eb2fea 100644 --- a/tests/fixtures/install-tree/augment.json +++ b/tests/fixtures/install-tree/augment.json @@ -525,6 +525,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/claude-local.json b/tests/fixtures/install-tree/claude-local.json index e98ef95fc..c159b2c27 100644 --- a/tests/fixtures/install-tree/claude-local.json +++ b/tests/fixtures/install-tree/claude-local.json @@ -389,6 +389,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/claude.json b/tests/fixtures/install-tree/claude.json index d88c8c730..367d39b00 100644 --- a/tests/fixtures/install-tree/claude.json +++ b/tests/fixtures/install-tree/claude.json @@ -453,6 +453,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/cline.json b/tests/fixtures/install-tree/cline.json index f70183656..723ed06ac 100644 --- a/tests/fixtures/install-tree/cline.json +++ b/tests/fixtures/install-tree/cline.json @@ -455,6 +455,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/codebuddy.json b/tests/fixtures/install-tree/codebuddy.json index 7aaa0acf1..2a806c4b4 100644 --- a/tests/fixtures/install-tree/codebuddy.json +++ b/tests/fixtures/install-tree/codebuddy.json @@ -525,6 +525,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/codex.json b/tests/fixtures/install-tree/codex.json index bc33eba67..85d68f767 100644 --- a/tests/fixtures/install-tree/codex.json +++ b/tests/fixtures/install-tree/codex.json @@ -489,6 +489,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/copilot.json b/tests/fixtures/install-tree/copilot.json index 0d898bd0e..451c467fb 100644 --- a/tests/fixtures/install-tree/copilot.json +++ b/tests/fixtures/install-tree/copilot.json @@ -454,6 +454,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/cursor.json b/tests/fixtures/install-tree/cursor.json index a00ed54f1..15b8db94a 100644 --- a/tests/fixtures/install-tree/cursor.json +++ b/tests/fixtures/install-tree/cursor.json @@ -453,6 +453,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/hermes.json b/tests/fixtures/install-tree/hermes.json index 3bb9996f2..54d8d7346 100644 --- a/tests/fixtures/install-tree/hermes.json +++ b/tests/fixtures/install-tree/hermes.json @@ -453,6 +453,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/kilo.json b/tests/fixtures/install-tree/kilo.json index e98666414..5b7ed0724 100644 --- a/tests/fixtures/install-tree/kilo.json +++ b/tests/fixtures/install-tree/kilo.json @@ -525,6 +525,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/kimi-code.json b/tests/fixtures/install-tree/kimi-code.json index 73734e1ba..b30956d6d 100644 --- a/tests/fixtures/install-tree/kimi-code.json +++ b/tests/fixtures/install-tree/kimi-code.json @@ -454,6 +454,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/kimi.json b/tests/fixtures/install-tree/kimi.json index 2aff17547..6ba2149df 100644 --- a/tests/fixtures/install-tree/kimi.json +++ b/tests/fixtures/install-tree/kimi.json @@ -461,6 +461,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/opencode.json b/tests/fixtures/install-tree/opencode.json index 0e6ecb492..7cac397a2 100644 --- a/tests/fixtures/install-tree/opencode.json +++ b/tests/fixtures/install-tree/opencode.json @@ -525,6 +525,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/pi.json b/tests/fixtures/install-tree/pi.json index 8bbce1d1d..53a9c4e8e 100644 --- a/tests/fixtures/install-tree/pi.json +++ b/tests/fixtures/install-tree/pi.json @@ -255,6 +255,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/qwen.json b/tests/fixtures/install-tree/qwen.json index 4060f9d98..83bc000da 100644 --- a/tests/fixtures/install-tree/qwen.json +++ b/tests/fixtures/install-tree/qwen.json @@ -453,6 +453,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/trae.json b/tests/fixtures/install-tree/trae.json index 9c1e58ad3..39fdd07c3 100644 --- a/tests/fixtures/install-tree/trae.json +++ b/tests/fixtures/install-tree/trae.json @@ -453,6 +453,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/windsurf.json b/tests/fixtures/install-tree/windsurf.json index 69cf9410f..16953c416 100644 --- a/tests/fixtures/install-tree/windsurf.json +++ b/tests/fixtures/install-tree/windsurf.json @@ -381,6 +381,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/fixtures/install-tree/zcode.json b/tests/fixtures/install-tree/zcode.json index 988e92b4d..0edb96446 100644 --- a/tests/fixtures/install-tree/zcode.json +++ b/tests/fixtures/install-tree/zcode.json @@ -525,6 +525,7 @@ "gsd-core/workflows/execute-phase/steps/regression-gate-run.md", "gsd-core/workflows/execute-phase/steps/regression-gate.md", "gsd-core/workflows/execute-phase/steps/sequential-root-pin.md", + "gsd-core/workflows/execute-phase/steps/stale-reverification.md", "gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md", "gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md", "gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md", diff --git a/tests/init-manager.test.cjs b/tests/init-manager.test.cjs index 89d8b1282..49dc3d706 100644 --- a/tests/init-manager.test.cjs +++ b/tests/init-manager.test.cjs @@ -372,7 +372,7 @@ describe('init manager', () => { assert.strictEqual(output.phases[0].phase_complete, false); assert.strictEqual(output.recommended_actions[0].action, 'verify'); assert.match(output.recommended_actions[0].reason, /verification stale/); - assert.match(output.recommended_actions[0].command, /verify-work 1/); + assert.match(output.recommended_actions[0].command, /execute-phase 1/); }); test('checked unpadded roadmap token does not satisfy padded unverified dependency', () => { diff --git a/tests/phase.test.cjs b/tests/phase.test.cjs index a617566ad..9469560f8 100644 --- a/tests/phase.test.cjs +++ b/tests/phase.test.cjs @@ -4367,7 +4367,7 @@ describe('phase complete canonical verification gate (#1522)', () => { // runtime's installed surface. This project has no runtime configured, so it // takes the `claude` default — the canonical `/gsd-` hyphen form. The colon // form this previously asserted is the deprecated shape #2617 removed. - assert.match(errorPayload.message, /\/gsd-verify-work 0?1/); + assert.match(errorPayload.message, /\/gsd-execute-phase 0?1/); assert.equal(fs.readFileSync(roadmapPath, 'utf-8'), beforeRoadmap); assert.equal(fs.readFileSync(statePath, 'utf-8'), beforeState); }); diff --git a/tests/verification-status.test.cjs b/tests/verification-status.test.cjs index 1db70f2ee..735465cf7 100644 --- a/tests/verification-status.test.cjs +++ b/tests/verification-status.test.cjs @@ -401,7 +401,15 @@ describe('verification-status', () => { const result = readVerificationStatus(dir, { phaseCleanCommitTimesMs: () => new Map() }); assert.equal(result.status, 'stale'); assert.match(result.next_action, /stale/i); - assert.equal(result.next_command, '/gsd-verify-work 01'); + // #4682: stale means covered source changed after the verifier ran — the + // only remedy is re-running the verifier. execute-phase resumes at the + // verification gates and re-runs it (its resume tree routes a stale + // report to re-verification); /gsd-verify-work never rewrote the report, + // so routing there was an advice loop. + assert.equal(result.next_command, '/gsd-execute-phase 01'); + assert.doesNotMatch(result.next_command, /verify-work/); + assert.match(result.next_action, /verifier/i, + 'the stale action must name the verifier re-run as the remedy'); } finally { cleanup(baseDir); } @@ -444,7 +452,7 @@ describe('verification-status', () => { // git times unavailable → mtime-fallback path (#2348). const result = readVerificationStatus(dir, { phaseCleanCommitTimesMs: () => new Map() }); assert.equal(result.status, 'stale'); - assert.equal(result.next_command, '/gsd-verify-work 01'); + assert.equal(result.next_command, '/gsd-execute-phase 01'); } finally { cleanup(baseDir); } @@ -526,7 +534,7 @@ describe('verification-status', () => { const result = readVerificationStatus(dir, { phaseCleanCommitTimesMs }); assert.equal(result.status, 'stale'); - assert.equal(result.next_command, '/gsd-verify-work 02'); + assert.equal(result.next_command, '/gsd-execute-phase 02'); } finally { cleanup(baseDir); } @@ -591,7 +599,7 @@ describe('verification-status', () => { 'stale', 'a dirty summary edited after the verification must stale it via mtime, not be shadowed by an equal/earlier commit time', ); - assert.equal(result.next_command, '/gsd-verify-work 02'); + assert.equal(result.next_command, '/gsd-execute-phase 02'); } finally { cleanup(baseDir); } @@ -708,7 +716,7 @@ describe('verification-status', () => { 'stale', 'summary committed after the verification must read stale on the real git clock, and the dash-named file must resolve through the `--` pathspec guard', ); - assert.equal(result.next_command, '/gsd-verify-work 01'); + assert.equal(result.next_command, '/gsd-execute-phase 01'); } finally { cleanup(repo); } @@ -756,7 +764,7 @@ describe('verification-status', () => { 'stale', 'a committed-then-edited (dirty) summary must read stale via mtime, not be shadowed by its now-stale commit time', ); - assert.equal(result.next_command, '/gsd-verify-work 01'); + assert.equal(result.next_command, '/gsd-execute-phase 01'); } finally { cleanup(repo); } @@ -1396,7 +1404,7 @@ describe('#4187: status surface recognizes a bare VERIFICATION.md', () => { const result = readVerificationStatus(dir, { phaseCleanCommitTimesMs: () => new Map() }); assert.equal(result.status, 'stale', 'a bare report must be staleness-checked like a dashed one'); - assert.equal(result.next_command, '/gsd-verify-work 99'); + assert.equal(result.next_command, '/gsd-execute-phase 99'); }); test('#4187 unit (findStaleVerificationSummary): staleness is computed against the bare report', (t) => { @@ -1446,7 +1454,7 @@ describe('#4142: opts.convention threads through findStaleVerificationSummary', 'stale', 'opts.convention must reach the legacy staleness seam so phase 03 is compared to 03-VERIFICATION.md', ); - assert.equal(result.next_command, '/gsd-verify-work'); + assert.equal(result.next_command, '/gsd-execute-phase'); }); }); @@ -2025,7 +2033,7 @@ describe('#4155: readVerificationStatus — fingerprint supersedes legacy mtime const result = readVerificationStatus(dir, { phaseCleanCommitTimesMs: () => new Map() }); assert.equal(result.status, 'stale'); - assert.equal(result.next_command, '/gsd-verify-work 01'); + assert.equal(result.next_command, '/gsd-execute-phase 01'); }); // Ponytail #4155 review finding: "disappeared" and "escapes confinement" @@ -2825,7 +2833,7 @@ for (const { id, prefix } of RUNTIMES) { makeStale(); const result = read(id, NO_GIT); assert.equal(result.status, 'stale'); - assert.equal(result.next_command, `${prefix}verify-work 01`); + assert.equal(result.next_command, `${prefix}execute-phase 01`); }); test('passed has no next step and stays empty, not a bare prefix', () => { diff --git a/tests/verify-work-auto-transition.test.cjs b/tests/verify-work-auto-transition.test.cjs index 039deb879..e811fae0f 100644 --- a/tests/verify-work-auto-transition.test.cjs +++ b/tests/verify-work-auto-transition.test.cjs @@ -288,3 +288,23 @@ describe('bug #3381: verify-work forwards workstream context', () => { }); }); } + +// ── #4682 — the stale stop routes to the verifier, not to itself ───────────── +// A stale report means covered source files changed after the verifier ran; +// the only remedy is re-running the verifier. /gsd-verify-work never rewrites +// VERIFICATION.md, so advising it from its own stale block is an advice loop. +describe('verify-work.md — stale stop routes to the verifier (#4682)', () => { + test('the stale stop instructs re-running the verifier, not verify-work (#4682)', () => { + const content = fs.readFileSync(VERIFY_WORK, 'utf-8'); + const staleIdx = content.indexOf('If `PHASE_VERIFICATION_STATUS` is `stale`'); + assert.ok(staleIdx !== -1, 'the stale stop must exist'); + const block = content.slice(staleIdx, staleIdx + 1600); + + assert.match(block, /gsd-verifier/, 'the stale stop must route to the gsd-verifier agent'); + assert.match(block, /verification\.status/, 'it must re-check verification.status afterwards'); + assert.doesNotMatch( + block, /`\/gsd:verify-work \{phase\}` — re-run verification/, + 'the self-referential re-run advice must be gone' + ); + }); +});