diff --git a/sdk/src/query/validate.test.ts b/sdk/src/query/validate.test.ts index 4f370f541..aa9844db3 100644 --- a/sdk/src/query/validate.test.ts +++ b/sdk/src/query/validate.test.ts @@ -643,6 +643,30 @@ describe('validateHealth', () => { expect(w006s.some(w => String(w.message).includes('Phase 7'))).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'), [ + '# Roadmap', + '', + '## v1.1: Current', + '', + '- [x] **Phase 21: Active**', + '- [ ] **Phase 22: Future planned**', + '', + '### Phase 21: Active', + '', + '### Phase 22: Future planned', + '', + ].join('\n')); + await mkdir(join(tmpDir, '.planning', 'phases', '21-active'), { recursive: true }); + + const result = await validateHealth([], tmpDir); + const data = result.data as Record; + const warnings = data.warnings as Array>; + const w006s = warnings.filter(w => w.code === 'W006'); + expect(w006s.some(w => String(w.message).includes('Phase 22'))).toBe(false); + }); + it('does not emit I001 when plan file has descriptor but summary uses canonical stem (#3473)', async () => { await createHealthyPlanning(); await mkdir(join(tmpDir, '.planning', 'phases', '68-bug-surface'), { recursive: true }); diff --git a/sdk/src/query/validate.ts b/sdk/src/query/validate.ts index e68e072a8..c958c2816 100644 --- a/sdk/src/query/validate.ts +++ b/sdk/src/query/validate.ts @@ -598,6 +598,13 @@ export const validateHealth: QueryHandler = async (args, projectDir, workstream) while ((m = phasePattern.exec(roadmapContent)) !== null) { roadmapPhases.add(m[1]); } + const notStartedPhases = new Set(); + const uncheckedPattern = /-\s*\[\s\]\s*\*{0,2}Phase\s+(\d+[A-Z]?(?:\.\d+)*)[:\s*]/gi; + let um: RegExpExecArray | null; + while ((um = uncheckedPattern.exec(roadmapContent)) !== null) { + notStartedPhases.add(um[1]); + notStartedPhases.add(String(parseInt(um[1], 10)).padStart(2, '0')); + } const diskPhases = new Set(); try { @@ -627,6 +634,7 @@ export const validateHealth: QueryHandler = async (args, projectDir, workstream) for (const p of roadmapPhases) { const padded = String(parseInt(p, 10)).padStart(2, '0'); if (!diskPhases.has(p) && !diskPhases.has(padded)) { + if (notStartedPhases.has(p) || notStartedPhases.has(padded)) continue; addIssue('warning', 'W006', `Phase ${p} in ROADMAP.md but no directory on disk`, 'Create phase directory or remove from roadmap'); } }