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'
+ );
+ });
+});