From 94bf32a2bb1758eb478a8ecbbed07a0defe7a839 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Thu, 6 Aug 2026 18:42:39 -0400 Subject: [PATCH] fix(#2786): skip sentinel phase ids in phase-complete stage 2 heading scan (#3130) * fix(#2786): skip sentinel phase ids in phase-complete stage 2 heading scan Stage 2 of the next-phase cascade accepted any higher-numbered roadmap heading without checking the 999.x backlog sentinel convention that stage 1 already checks. A Phase 999.1: Backlog Item heading was treated as the next real phase, advancing STATE.md into the backlog and making the milestone perpetually 'Ready to plan' instead of 'All phases complete'. Added isSentinelPhaseId(pm[1]) guard (mirrors stage 1's /^999(?:\.|$)/ check) so both sentinel ranges (0.x drafts, 999.x backlog) are skipped. * chore(#2786): backfill changeset PR number 3130 --------- Co-authored-by: sim --- .changeset/clever-ravens-romp.md | 5 +++++ src/phase.cts | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/clever-ravens-romp.md diff --git a/.changeset/clever-ravens-romp.md b/.changeset/clever-ravens-romp.md new file mode 100644 index 000000000..3c806c7fc --- /dev/null +++ b/.changeset/clever-ravens-romp.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3130 +--- +**`phase complete` no longer advances `next_phase` into 999.x backlog headings** — the roadmap heading scan (stage 2 of the next-phase cascade) accepted any higher-numbered heading without checking the sentinel convention, so a `Phase 999.1: Backlog Item` heading was treated as the next real phase. Sentinel phase ids (999.x backlog, 0.x drafts) are now skipped. (#2786) diff --git a/src/phase.cts b/src/phase.cts index e49d11bc5..6f6466c48 100644 --- a/src/phase.cts +++ b/src/phase.cts @@ -2576,6 +2576,11 @@ function cmdPhaseComplete(cwd: string, phaseNum: string, raw: boolean): void { ); let pm: RegExpExecArray | null; while ((pm = phasePattern.exec(roadmapForPhases)) !== null) { + // #2786: skip sentinel phase ids (999.x backlog, 0.x drafts) — stage 1 + // already skips 999 dirs on disk; stage 2's heading scan must not + // advance into backlog headings. Mirrors the /^999(?:\.|$)/ guard + // stage 1 uses at line 2536, but via isSentinelPhaseId for both ranges. + if (isSentinelPhaseId(pm[1])) continue; if (comparePhaseNum(pm[1], phaseNum) > 0) { nextPhaseNum = pm[1]; nextPhaseName = pm[2]