From 14b8add69ed07539f661b37bc11850fa4b81d1d0 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 10 Apr 2026 11:03:10 -0400 Subject: [PATCH] fix(verify): suppress W006 for phases with unchecked ROADMAP summary checkbox (#2009) (#2027) W006 (Phase in ROADMAP.md but no directory on disk) fired for every phase listed in ROADMAP.md that lacked a phase directory, including future phases that haven't been started yet. This produced false DEGRADED health status on any project with more than one phase planned. Fix: before emitting W006, check the ROADMAP summary list for a '- [ ] **Phase N:**' unchecked checkbox. Phases explicitly marked as not yet started are intentionally absent from disk -- skip W006 for them. Phases with a checked checkbox ([x]) or with no summary entry still trigger W006 as before. Adds two regression tests: one verifying W006 is suppressed for unchecked phases, and one verifying W006 still fires for checked phases with no disk directory. --- get-shit-done/bin/lib/verify.cjs | 14 +++++++ tests/verify-health.test.cjs | 70 ++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/get-shit-done/bin/lib/verify.cjs b/get-shit-done/bin/lib/verify.cjs index 749753e41..d7721bffa 100644 --- a/get-shit-done/bin/lib/verify.cjs +++ b/get-shit-done/bin/lib/verify.cjs @@ -743,10 +743,24 @@ function cmdValidateHealth(cwd, options, raw) { } } catch { /* intentionally empty */ } + // Build a set of phases explicitly marked not-yet-started in the ROADMAP + // summary list (- [ ] **Phase N:**). These phases are intentionally absent + // from disk -- W006 must not fire for them (#2009). + const notStartedPhases = new Set(); + const uncheckedPattern = /-\s*\[\s\]\s*\*{0,2}Phase\s+(\d+[A-Z]?(?:\.\d+)*)[:\s*]/gi; + let um; + while ((um = uncheckedPattern.exec(roadmapContent)) !== null) { + notStartedPhases.add(um[1]); + // Also add zero-padded variant so 1 and 01 both match + notStartedPhases.add(String(parseInt(um[1], 10)).padStart(2, '0')); + } + // Phases in ROADMAP but not on disk for (const p of roadmapPhases) { const padded = String(parseInt(p, 10)).padStart(2, '0'); if (!diskPhases.has(p) && !diskPhases.has(padded)) { + // Skip phases explicitly flagged as not-yet-started in the summary list + 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'); } } diff --git a/tests/verify-health.test.cjs b/tests/verify-health.test.cjs index e11e6f5ad..50aa0dc75 100644 --- a/tests/verify-health.test.cjs +++ b/tests/verify-health.test.cjs @@ -418,6 +418,76 @@ describe('validate health command', () => { ); }); + // ─── Check 8b: W006 false-positives for not-yet-started phases (#2009) ────── + + test('does not emit W006 for phases listed in ROADMAP summary as unchecked (not started)', () => { + // A ROADMAP with Phase 1 started (has disk dir) and Phase 2 listed but + // unchecked (- [ ]) — phase 2 has no directory because it hasn't started. + // W006 must NOT fire for phase 2. + writeMinimalProjectMd(tmpDir); + fs.writeFileSync( + path.join(tmpDir, '.planning', 'ROADMAP.md'), + [ + '# Roadmap', + '', + '## v1.0.0', + '', + '- [x] **Phase 1: Setup** - First phase', + '- [ ] **Phase 2: Build** - Not yet started', + '', + '### Phase 1: Setup', + '', + '### Phase 2: Build', + '', + ].join('\n') + ); + writeMinimalStateMd(tmpDir, '# Session State\n\nPhase 1 in progress.\n'); + writeValidConfigJson(tmpDir); + // Only phase 1 dir exists; phase 2 dir does not (not started yet) + fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-setup'), { recursive: true }); + + const result = runGsdTools('validate health', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + const w006s = output.warnings.filter(w => w.code === 'W006'); + assert.ok( + w006s.length === 0, + 'W006 must not fire for phases with an unchecked summary checkbox (not yet started), got: ' + + JSON.stringify(w006s) + ); + }); + + test('still emits W006 for a phase that was started (checked) but has no directory', () => { + // Phase 1 is marked complete ([x]) in ROADMAP summary but has no directory + // on disk — that IS a genuine inconsistency and should still trigger W006. + writeMinimalProjectMd(tmpDir); + fs.writeFileSync( + path.join(tmpDir, '.planning', 'ROADMAP.md'), + [ + '# Roadmap', + '', + '- [x] **Phase 1: Setup** - Completed', + '', + '### Phase 1: Setup', + '', + ].join('\n') + ); + writeMinimalStateMd(tmpDir, '# Session State\n\nPhase 1 done.\n'); + writeValidConfigJson(tmpDir); + // No phase 1 directory — even though roadmap says it's complete + + const result = runGsdTools('validate health', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.ok( + output.warnings.some(w => w.code === 'W006'), + 'W006 must still fire when a completed phase has no directory, warnings: ' + + JSON.stringify(output.warnings) + ); + }); + // ─── Check 7b: Nyquist VALIDATION.md consistency (W009) ────────────────── test('detects W009 when RESEARCH.md has Validation Architecture but no VALIDATION.md', () => {