Files
msd-core/tests/health-diagnostic-rules/roadmap-disk-consistency.test.cjs
Tom Boucher 71e00d426e fix(#3639): dir-aware sentinel recognition for the disk-side guards (#3698)
* test(#3639): pin bracket sentinel recognition in disk-side guards

* fix(#3639): dir-aware sentinel recognition for the disk-side guards

* chore(#3639): add changeset

* fix(#3639): disclose the digit-continuation residual, join phases-clear, load-bearing over-suppression guard

* test(#3639): match the token form W007 reports

* chore(#3639): backfill changeset pr number

---------

Co-authored-by: sim <sim@local>
2026-08-19 21:55:18 -04:00

326 lines
14 KiB
JavaScript

'use strict';
/**
* Tests for `src/health-diagnostic-rules/roadmap-disk-consistency.cts`
* (Phase 11, #3309, ADR-3180 §8.2/§8.3/§8.5) — group "ROADMAP/disk
* consistency": W006, W007.
*
* Design: .gsd/phase/refactor-3309-health-diagnostic-rule-table/40-design.md
* Test matrix: .gsd/phase/refactor-3309-health-diagnostic-rule-table/50-test-matrix.md
* | W006 | ROADMAP phase with no disk dir | reused/representative | roadmap entry added, no matching dir created |
* | W007 | disk dir with no ROADMAP entry | reused/representative | dir created, no roadmap entry |
*
* Fixture provenance (§8.5 + CONTRIBUTING "Fixture provenance (#2371)"): both
* rules use CONTENT-SHAPE/MECHANICAL-MUTATION provenance — a realistic
* multi-phase ROADMAP.md (mirroring `gsd-core/templates/roadmap.md`'s
* heading shape) paired with a matching on-disk phase-dir tree, with exactly
* ONE entry perturbed (one dir withheld for W006, one extra dir added for
* W007). Every fixture is built via the REAL `buildPlanningSnapshot(cwd)`
* against a REAL temp directory (mirrors `tests/planning-snapshot.test.cjs`
* and `tests/health-diagnostic-rules/root-existence.test.cjs`) — no
* hand-constructed fake `PlanningSnapshot` object.
*
* TDD RED: `src/health-diagnostic-rules/roadmap-disk-consistency.cts` does
* not exist yet at the start of this batch — this file's
* `require('../../gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs')`
* throws MODULE_NOT_FOUND until this batch's implementation lands.
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { createTempDir, cleanup } = require('../helpers.cjs');
const roadmapDiskConsistency = require('../../gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs');
const { RULES } = roadmapDiskConsistency;
const { buildPlanningSnapshot } = require('../../gsd-core/bin/lib/planning-snapshot.cjs');
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK } = require('../../gsd-core/bin/lib/health-diagnostic.cjs');
const { SCOPE } = require('../../gsd-core/bin/lib/planning-scope.cjs');
function planningDirOf(cwd) {
return path.join(cwd, '.planning');
}
function writeRoadmap(cwd, content) {
fs.mkdirSync(planningDirOf(cwd), { recursive: true });
fs.writeFileSync(path.join(planningDirOf(cwd), 'ROADMAP.md'), content);
}
function makePhaseDir(cwd, dirName) {
fs.mkdirSync(path.join(planningDirOf(cwd), 'phases', dirName), { recursive: true });
}
function ruleFor(code) {
const rule = RULES.find((r) => r.code === code);
assert.ok(rule, `rule ${code} not found in RULES`);
return rule;
}
// ─── RULES shape ────────────────────────────────────────────────────────────
describe('RULES (roadmap-disk-consistency group)', () => {
test('exports exactly 2 rules: W006, W007', () => {
assert.deepEqual(RULES.map((r) => r.code).sort(), ['W006', 'W007']);
});
test('both rules are severity WARNING', () => {
assert.equal(ruleFor('W006').severity, SEVERITY.WARNING);
assert.equal(ruleFor('W007').severity, SEVERITY.WARNING);
});
});
// ─── W006 — ROADMAP phase with no disk dir ─────────────────────────────────
describe('W006 — ROADMAP phase with no disk dir', () => {
test('fires for exactly the one perturbed phase (3-phase roadmap, dir withheld for phase 2)', (t) => {
const cwd = createTempDir('gsd-3309-w006-1-');
t.after(() => cleanup(cwd));
writeRoadmap(
cwd,
['## v1.0 Current 🚧', '', '### Phase 1: Foo', '', '### Phase 2: Bar', '', '### Phase 3: Baz'].join('\n'),
);
makePhaseDir(cwd, '01-foo');
// Phase 2 deliberately has no matching directory.
makePhaseDir(cwd, '03-baz');
const snapshot = buildPlanningSnapshot(cwd);
const diagnostics = ruleFor('W006').check(snapshot);
assert.equal(diagnostics.length, 1);
assert.deepEqual(diagnostics[0], {
code: 'W006',
severity: SEVERITY.WARNING,
message: 'Phase 2 in ROADMAP.md but no directory on disk',
remedy: {
action: REMEDY_ACTION.ADVISE,
risk: REMEDY_RISK.NONE,
args: { command: 'Create phase directory or remove from roadmap' },
},
});
});
test('does not fire when every declared phase resolves to a directory (padding/token tolerant)', (t) => {
const cwd = createTempDir('gsd-3309-w006-2-');
t.after(() => cleanup(cwd));
writeRoadmap(cwd, ['## v1.0 Current 🚧', '', '### Phase 1: Foo', '', '### Phase 2: Bar'].join('\n'));
makePhaseDir(cwd, '01-foo');
makePhaseDir(cwd, '02-bar');
const snapshot = buildPlanningSnapshot(cwd);
assert.deepEqual(ruleFor('W006').check(snapshot), []);
});
test('does not fire for a sentinel phase id (999.x) even with no matching directory', (t) => {
const cwd = createTempDir('gsd-3309-w006-3-');
t.after(() => cleanup(cwd));
writeRoadmap(cwd, ['## v1.0 Current 🚧', '', '### Phase 999.1: Icebox'].join('\n'));
const snapshot = buildPlanningSnapshot(cwd);
assert.deepEqual(ruleFor('W006').check(snapshot), []);
});
test('does not fire for a phase explicitly marked "not started" (unchecked checklist entry, no dir)', (t) => {
const cwd = createTempDir('gsd-3309-w006-4-');
t.after(() => cleanup(cwd));
writeRoadmap(cwd, ['## Phases', '', '- [ ] **Phase 5: Widgets** - build them'].join('\n'));
const snapshot = buildPlanningSnapshot(cwd);
// Sanity: the phase IS declared (so this is genuinely testing the
// not-started exclusion, not an empty-declared-phases no-op).
assert.ok(snapshot.roadmapDeclaredPhases.value.some((p) => p.phaseId === '5'));
assert.deepEqual(ruleFor('W006').check(snapshot), []);
});
test('DOES fire for an unrelated checked phase with no dir (not-started exclusion is per-phase, not global)', (t) => {
const cwd = createTempDir('gsd-3309-w006-5-');
t.after(() => cleanup(cwd));
writeRoadmap(
cwd,
['## Phases', '', '- [ ] **Phase 5: Widgets** - build them', '- [x] **Phase 6: Gadgets** - build them'].join(
'\n',
),
);
const snapshot = buildPlanningSnapshot(cwd);
const diagnostics = ruleFor('W006').check(snapshot);
assert.equal(diagnostics.length, 1);
assert.equal(diagnostics[0].message, 'Phase 6 in ROADMAP.md but no directory on disk');
});
test('boundary: zero declared phases and zero phase directories produces zero findings', (t) => {
const cwd = createTempDir('gsd-3309-w006-6-');
t.after(() => cleanup(cwd));
writeRoadmap(cwd, ['## Progress', '', '(no phases declared yet)'].join('\n'));
const snapshot = buildPlanningSnapshot(cwd);
assert.deepEqual(snapshot.roadmapDeclaredPhases.value, []);
assert.deepEqual(ruleFor('W006').check(snapshot), []);
});
test('guard: ROADMAP.md absent does not fire (empty declared-phase list is a non-answer, not "zero declared")', (t) => {
const cwd = createTempDir('gsd-3309-w006-7-');
t.after(() => cleanup(cwd));
fs.mkdirSync(planningDirOf(cwd), { recursive: true });
const snapshot = buildPlanningSnapshot(cwd);
assert.deepEqual(ruleFor('W006').check(snapshot), []);
});
});
// ─── W007 — disk dir with no ROADMAP entry ─────────────────────────────────
describe('W007 — disk dir with no ROADMAP entry', () => {
test('fires for exactly the one perturbed directory (3-phase roadmap, one extra orphan dir)', (t) => {
const cwd = createTempDir('gsd-3309-w007-1-');
t.after(() => cleanup(cwd));
writeRoadmap(
cwd,
['## v1.0 Current 🚧', '', '### Phase 1: Foo', '', '### Phase 2: Bar', '', '### Phase 3: Baz'].join('\n'),
);
makePhaseDir(cwd, '01-foo');
makePhaseDir(cwd, '02-bar');
makePhaseDir(cwd, '03-baz');
// Deliberately orphaned: no roadmap entry claims this directory.
makePhaseDir(cwd, '04-extra');
const snapshot = buildPlanningSnapshot(cwd);
const diagnostics = ruleFor('W007').check(snapshot);
assert.equal(diagnostics.length, 1);
assert.deepEqual(diagnostics[0], {
code: 'W007',
severity: SEVERITY.WARNING,
message: 'Phase 04 exists on disk but not in ROADMAP.md',
remedy: {
action: REMEDY_ACTION.ADVISE,
risk: REMEDY_RISK.NONE,
args: { command: 'Add to roadmap or remove directory' },
},
});
});
test('does not fire when every directory is claimed by a declared phase', (t) => {
const cwd = createTempDir('gsd-3309-w007-2-');
t.after(() => cleanup(cwd));
writeRoadmap(cwd, ['## v1.0 Current 🚧', '', '### Phase 1: Foo', '', '### Phase 2: Bar'].join('\n'));
makePhaseDir(cwd, '01-foo');
makePhaseDir(cwd, '02-bar');
const snapshot = buildPlanningSnapshot(cwd);
assert.deepEqual(ruleFor('W007').check(snapshot), []);
});
test('does not fire for a sentinel directory (999-interim) even with no roadmap entry', (t) => {
const cwd = createTempDir('gsd-3309-w007-3-');
t.after(() => cleanup(cwd));
writeRoadmap(cwd, ['## v1.0 Current 🚧', '', '### Phase 1: Foo'].join('\n'));
makePhaseDir(cwd, '01-foo');
makePhaseDir(cwd, '999-interim');
const snapshot = buildPlanningSnapshot(cwd);
assert.deepEqual(ruleFor('W007').check(snapshot), []);
});
test('#3639: does not fire for a BRACKET icebox directory (GSD.999-07-icebox) even with no roadmap entry', (t) => {
// Bracket sentinel-ness lives in the MILESTONE portion ({CODE}.999-{PP});
// the convention-less predicate read only the phase token and never saw it.
const cwd = createTempDir('gsd-3639-w007-bracket-ice-');
t.after(() => cleanup(cwd));
writeRoadmap(cwd, ['## GSD.03 Current 🚧', '', '### Phase 3.1: Foo'].join('\n'));
makePhaseDir(cwd, 'GSD.03-01-foo');
makePhaseDir(cwd, 'GSD.999-07-icebox');
const snapshot = buildPlanningSnapshot(cwd);
const diagnostics = ruleFor('W007').check(snapshot);
assert.deepEqual(
diagnostics.filter((d) => d.message.includes('999-07-icebox')),
[],
`a bracket icebox dir is never-on-roadmap by convention — no orphan warning. Got: ${JSON.stringify(diagnostics)}`,
);
});
test('#3639: does not fire for a BRACKET pre-milestone directory (GSD.00-01-backlog)', (t) => {
const cwd = createTempDir('gsd-3639-w007-bracket-pre-');
t.after(() => cleanup(cwd));
writeRoadmap(cwd, ['## GSD.03 Current 🚧', '', '### Phase 3.1: Foo'].join('\n'));
makePhaseDir(cwd, 'GSD.03-01-foo');
makePhaseDir(cwd, 'GSD.00-01-backlog');
const snapshot = buildPlanningSnapshot(cwd);
const diagnostics = ruleFor('W007').check(snapshot);
assert.deepEqual(
diagnostics.filter((d) => d.message.includes('00-01-backlog')),
[],
`a bracket pre-milestone dir is never-on-roadmap by convention. Got: ${JSON.stringify(diagnostics)}`,
);
});
test('#3639 over-suppression guard: an UNCLAIMED ordinary #1324 digit-continuation dir still fires W007', (t) => {
// The disclosed residual (#3639 review): #1324 dirs with a SENTINEL-valued
// first decimal (`P0.0-1-x`) are indistinguishable from bracket sentinels
// and read as sentinel (suppressed). This row pins that the family is not
// BLANKET-suppressed: a NON-sentinel first decimal (`P0.1-2-x`), unclaimed,
// must still fire — otherwise the recognizer would silently swallow every
// letter-prefixed real dir.
const cwd = createTempDir('gsd-3639-w007-over-sup-');
t.after(() => cleanup(cwd));
writeRoadmap(cwd, ['## v1.0 Current 🚧', '', '### Phase 1: Foo'].join('\n'));
makePhaseDir(cwd, '01-foo');
makePhaseDir(cwd, 'P0.1-2-x');
const snapshot = buildPlanningSnapshot(cwd);
const diagnostics = ruleFor('W007').check(snapshot);
assert.ok(
diagnostics.some((d) => d.message.includes('P0.1-2')),
`an unclaimed ordinary #1324 digit-continuation dir must still fire W007 (the message names the extracted token, not the full dir). Got: ${JSON.stringify(diagnostics)}`,
);
});
test('boundary: zero declared phases and zero phase directories produces zero findings', (t) => {
const cwd = createTempDir('gsd-3309-w007-4-');
t.after(() => cleanup(cwd));
writeRoadmap(cwd, ['## Progress', '', '(no phases declared yet)'].join('\n'));
const snapshot = buildPlanningSnapshot(cwd);
assert.deepEqual(snapshot.allPhaseDirNames.value, []);
assert.deepEqual(ruleFor('W007').check(snapshot), []);
});
test('regression: fires for a genuine orphan directory outside the roadmap-declared window (the W007-inert defect)', (t) => {
const cwd = createTempDir('gsd-3309-w007-6-');
t.after(() => cleanup(cwd));
// ROADMAP declares only phase 1 — "04-extra" is not declared anywhere,
// so `phaseDirs` (windowed to declared phases) would silently drop it
// and W007 would never see it; `allPhaseDirNames` must not.
writeRoadmap(cwd, ['## v1.0 Current 🚧', '', '### Phase 1: Foo'].join('\n'));
makePhaseDir(cwd, '01-foo');
makePhaseDir(cwd, '04-extra');
const snapshot = buildPlanningSnapshot(cwd);
assert.deepEqual(
snapshot.phaseDirs.value,
['01-foo'],
'sanity: the windowed phaseDirs field must NOT include the orphan (confirms the defect this test guards)',
);
assert.ok(snapshot.allPhaseDirNames.value.includes('04-extra'));
const diagnostics = ruleFor('W007').check(snapshot);
assert.equal(diagnostics.length, 1);
assert.equal(diagnostics[0].message, 'Phase 04 exists on disk but not in ROADMAP.md');
});
test('guard: ROADMAP.md absent does not fire for a pre-existing phase directory (no false positive)', (t) => {
const cwd = createTempDir('gsd-3309-w007-5-');
t.after(() => cleanup(cwd));
fs.mkdirSync(planningDirOf(cwd), { recursive: true });
makePhaseDir(cwd, '01-foo');
const snapshot = buildPlanningSnapshot(cwd);
assert.deepEqual(snapshot.roadmapDeclaredPhases, { value: [], scope: SCOPE.UNREADABLE });
assert.deepEqual(ruleFor('W007').check(snapshot), []);
});
});