fix(#4682): route stale verification to the verifier regeneration path (#4818)

* 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 <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 <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 <sim@local>
This commit is contained in:
Tom Boucher
2026-09-17 02:23:03 -04:00
committed by GitHub
parent 651511d1e3
commit 2bfff17ff8
30 changed files with 123 additions and 27 deletions

View File

@@ -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)

View File

@@ -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",

View File

@@ -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)."`

View File

@@ -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.
<step name="stale_reverification">
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).
</step>

View File

@@ -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
`<required_reading>`), 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:

View File

@@ -112,8 +112,14 @@ const VERIFICATION_ROUTING_TABLE: Record<string, VerificationRoute> = {
},
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),
};
}

View File

@@ -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
}
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -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', () => {

View File

@@ -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);
});

View File

@@ -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', () => {

View File

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