diff --git a/src/health-diagnostic-rules/state-consistency.cts b/src/health-diagnostic-rules/state-consistency.cts index b0a24e85c..1f88b43ff 100644 --- a/src/health-diagnostic-rules/state-consistency.cts +++ b/src/health-diagnostic-rules/state-consistency.cts @@ -55,7 +55,8 @@ type PlanningSnapshot = ReturnType { const valid = new Set(); @@ -120,6 +128,9 @@ function buildValidPhaseSet(snapshot: PlanningSnapshot): Set { for (const entry of snapshot.roadmapDeclaredPhases.value) { valid.add(entry.phaseId); } + for (const token of snapshot.archivedPhaseTokens.value) { + valid.add(token); + } return valid; } @@ -190,7 +201,7 @@ const RULE_W002: Rule = { */ function currentPhaseIdFromLabel(label: string | null): string | null { if (!label) return null; - const m = label.match(/^0*(\d+[A-Z]?(?:\.\d+)*)/); + const m = label.match(new RegExp(`^0*(${PHASE_NUMBER_TOKEN_SOURCE})`)); return m ? m[1] : null; } @@ -261,18 +272,22 @@ const RULE_W026: Rule = { const statusVal = (snapshot.stateStatus.value ?? '').trim().toLowerCase(); if (!/milestone complete|archived/.test(statusVal)) return []; - const currentMilestone = snapshot.milestone.value?.version ?? null; - if (currentMilestone === null) return []; - + // `currentMilestoneRoadmapPhaseIds` is already scoped to the current + // milestone (`extractCurrentMilestone(roadmapRaw, cwd)`, the same + // `
`/``-tolerant owner `verify.cts:2364` used) — no + // separate `currentMilestone` resolution/filter needed here (see the + // field's own doc comment on `PlanningSnapshot` for why + // `roadmapDeclaredPhases`'s `milestone` attribution is the wrong fit). const unstarted: string[] = []; - for (const entry of snapshot.roadmapDeclaredPhases.value) { - // Scoped to the current milestone only — mirrors the original's - // `extractCurrentMilestone(roadmapRaw, cwd)` narrowing before its - // phase-heading scan (`verify.cts:2363-2364`). - if (entry.milestone !== currentMilestone) continue; - const normalized = normalizePhaseName(entry.phaseId); - const hasDirectory = matchPhaseDirs(snapshot.phaseDirs.value, normalized).matches.length > 0; - if (!hasDirectory) unstarted.push(entry.phaseId); + for (const phaseId of snapshot.currentMilestoneRoadmapPhaseIds.value) { + const normalized = normalizePhaseName(phaseId); + // `allPhaseDirNames` — every directory under `phases/`, UNWINDOWED by + // ROADMAP-declaration membership — mirrors the original's own + // unwindowed `phaseDirNames2` (`verify.cts:2372-2382`, a direct + // `readdirSync` of the phases dir), not the current-milestone-windowed + // `phaseDirs`. + const hasDirectory = matchPhaseDirs(snapshot.allPhaseDirNames.value, normalized).matches.length > 0; + if (!hasDirectory) unstarted.push(phaseId); } if (unstarted.length === 0) return [];