refactor(#3309): add allPhaseDirNames field to planning-snapshot

W007 (orphan disk dir with no ROADMAP entry) cannot be sourced from
phaseDirs, which is windowed to ROADMAP-declared phases only — an
orphan dir can never appear in an already-ROADMAP-filtered set. Adds
an unwindowed allPhaseDirNames field so the rule can actually fire.
This commit is contained in:
sim
2026-08-13 01:37:01 -04:00
parent c5543e533c
commit 8c9ca3f7c6
2 changed files with 87 additions and 0 deletions

View File

@@ -1048,3 +1048,45 @@ describe('planningRootFiles field (Phase 11, #3309)', () => {
assert.deepStrictEqual(snap.planningRootFiles, { value: [], scope: SCOPE.UNREADABLE });
});
});
describe('allPhaseDirNames field (Phase 11, #3309 — health-diagnostic-rules/roadmap-disk-consistency batch)', () => {
// Found while implementing W007 (`src/health-diagnostic-rules/
// roadmap-disk-consistency.cts`): `phaseDirs` is windowed to directories
// the ROADMAP already declares, so it can never expose a genuine orphan
// directory. `allPhaseDirNames` is the unwindowed twin.
test('happy: lists every directory under phases/, including one NOT declared anywhere in ROADMAP.md', (t) => {
const cwd = createTempDir('gsd-3309-apdn1-');
t.after(() => cleanup(cwd));
writeRoadmap(cwd, ['## v1.0 Current 🚧', '', '### Phase 1: Foo'].join('\n'));
fs.mkdirSync(path.join(planningDirOf(cwd), 'phases', '01-foo'), { recursive: true });
fs.mkdirSync(path.join(planningDirOf(cwd), 'phases', '04-extra'), { recursive: true }); // undeclared
const snap = buildPlanningSnapshot(cwd);
assert.deepStrictEqual(snap.allPhaseDirNames.value.slice().sort(), ['01-foo', '04-extra']);
assert.strictEqual(snap.allPhaseDirNames.scope, SCOPE.COMPLETE);
// Sanity: `phaseDirs` (windowed) must NOT include the undeclared dir —
// this is the exact gap `allPhaseDirNames` exists to close.
assert.ok(!snap.phaseDirs.value.includes('04-extra'));
});
test('absence: no phases/ directory at all is a real empty, not a failure', (t) => {
const cwd = createTempDir('gsd-3309-apdn2-');
t.after(() => cleanup(cwd));
writeRoadmap(cwd, ['## v1.0 Current 🚧', ''].join('\n'));
const snap = buildPlanningSnapshot(cwd);
assert.deepStrictEqual(snap.allPhaseDirNames, { value: [], scope: SCOPE.COMPLETE });
});
test('hostile: an unreadable phases/ directory degrades to an empty list, scope UNREADABLE, without throwing', (t) => {
const cwd = createTempDir('gsd-3309-apdn3-');
t.after(() => cleanup(cwd));
const phasesDir = path.join(planningDirOf(cwd), 'phases');
fs.mkdirSync(phasesDir, { recursive: true });
injectPhaseDirFault(t, phasesDir);
const snap = buildPlanningSnapshot(cwd);
assert.deepStrictEqual(snap.allPhaseDirNames, { value: [], scope: SCOPE.UNREADABLE });
});
});