diff --git a/.changeset/sturdy-wolves-glide.md b/.changeset/sturdy-wolves-glide.md new file mode 100644 index 000000000..b8ba26567 --- /dev/null +++ b/.changeset/sturdy-wolves-glide.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3564 +--- +**validate.health no longer raises W007 for archived milestone phases** - archived `v*-phases` directories remain valid for historical W006 checks, but W007 now only reports unexpected active phase directories. Fixes #3560. diff --git a/sdk/src/query/validate.test.ts b/sdk/src/query/validate.test.ts index d63ba7001..06ca231d2 100644 --- a/sdk/src/query/validate.test.ts +++ b/sdk/src/query/validate.test.ts @@ -643,6 +643,26 @@ describe('validateHealth', () => { expect(w006s.some(w => String(w.message).includes('Phase 7'))).toBe(false); }); + it('does not emit W007 for archived milestone-only phase dirs (#3560)', async () => { + await createHealthyPlanning(); + await writeFile(join(tmpDir, '.planning', 'ROADMAP.md'), [ + '# Roadmap', + '', + '## v1.1: Current', + '', + '### Phase 21: Active', + '', + ].join('\n')); + await mkdir(join(tmpDir, '.planning', 'phases', '21-active'), { recursive: true }); + await mkdir(join(tmpDir, '.planning', 'milestones', 'v1.0-phases', '02-old-shipped-phase'), { recursive: true }); + + const result = await validateHealth([], tmpDir); + const data = result.data as Record; + const warnings = data.warnings as Array>; + const w007s = warnings.filter(w => w.code === 'W007'); + expect(w007s.some(w => String(w.message).includes('Phase 02'))).toBe(false); + }); + it('does not emit W006 for unchecked future phases with no directory (#3559)', async () => { await createHealthyPlanning(); await writeFile(join(tmpDir, '.planning', 'ROADMAP.md'), [ diff --git a/sdk/src/query/validate.ts b/sdk/src/query/validate.ts index dc48c5c67..1a0fe4a17 100644 --- a/sdk/src/query/validate.ts +++ b/sdk/src/query/validate.ts @@ -621,12 +621,16 @@ export const validateHealth: QueryHandler = async (args, projectDir, workstream) } const diskPhases = new Set(); + const activeDiskPhases = new Set(); try { const entries = await readdir(phasesDir, { withFileTypes: true }); for (const e of entries) { if (e.isDirectory()) { const dm = e.name.match(/^(\d+[A-Z]?(?:\.\d+)*)/i); - if (dm) diskPhases.add(dm[1]); + if (dm) { + diskPhases.add(dm[1]); + activeDiskPhases.add(dm[1]); + } } } } catch { /* intentionally empty */ } @@ -655,7 +659,7 @@ export const validateHealth: QueryHandler = async (args, projectDir, workstream) } } - for (const p of diskPhases) { + for (const p of activeDiskPhases) { const variants = phaseVariants(p); if (![...variants].some((variant) => roadmapPhaseVariants.has(variant))) { addIssue('warning', 'W007', `Phase ${p} exists on disk but not in ROADMAP.md`, 'Add to roadmap or remove directory');