Files
msd-core/tests/adr-612-bracket-coherence.test.cjs
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
2026-10-06 01:47:40 +02:00

585 lines
22 KiB
JavaScript

'use strict';
/**
* PR-2 (#2761 / epic #612) — verify.cts: the advisory bracket-coherence W021 and
* the milestone-complete (B6) read.
*
* Postures differ on purpose. checkBracketCoherence is a CHECK THAT CAN FAIL A
* REPO, so it is gated on `phase_id_convention === 'bracket'`. B6 is pinned by
* bug-557 with an empty config, so it fires on every repo — but its heading
* grammar is still SELECTED, never inferred: an earlier design read 'bracket'
* off the shape of a matched bracket, which ran a repo-failing check against a
* legacy ROADMAP that merely contained `### [RFC.2119] 5:`.
*
* Both commands resolve the convention through the SAME federated
* workstream->root resolver. Reading it from two different bases split
* `validate consistency` from `validate health` on workstream repos: one widened
* the ROADMAP read while the other kept the directory read narrow, so every
* bracket phase was reported either missing from disk or malformed on disk
* depending on which side you looked at.
*/
const { test, describe, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const { runMsdTools, createTempProject, cleanup } = require('./helpers.cjs');
const { PATHOLOGICAL_INPUT_TEST_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
let tmpDir;
function writeProject({ roadmap, convention, status = 'executing', phaseDirs = [], ws = null }) {
const root = path.join(tmpDir, '.planning');
const base = ws ? path.join(root, 'workstreams', ws) : root;
fs.mkdirSync(base, { recursive: true });
fs.writeFileSync(path.join(base, 'ROADMAP.md'), roadmap, 'utf-8');
fs.writeFileSync(path.join(base, 'STATE.md'), ['---', 'msd_state_version: 1.0',
'milestone: v2.0', 'milestone_name: Expansion', `status: ${status}`, '---', '',
'# Project State', '', '**Phase:** 05', ''].join('\n'), 'utf-8');
fs.writeFileSync(path.join(root, 'config.json'),
JSON.stringify(convention === undefined ? {} : { phase_id_convention: convention }), 'utf-8');
const phasesDir = path.join(base, 'phases');
fs.mkdirSync(phasesDir, { recursive: true });
for (const d of phaseDirs) fs.mkdirSync(path.join(phasesDir, d), { recursive: true });
}
const codes = (code, env = {}) => {
const r = runMsdTools(['validate', 'health'], tmpDir, env);
const out = JSON.parse(r.output);
return [...(out.issues || []), ...(out.warnings || [])]
.filter(i => i.code === code).map(i => i.message);
};
const w021 = (env) => codes('W021', env);
const COHERENT = `# Roadmap
## [MSD.02] v2.0 — Expansion
### [MSD.02] 05: Real work
**Goal:** Build it
### [MSD.02] 06: Follow-up
**Goal:** Polish it
`;
// ─── B6 / the ungated milestone-complete warning ───────────────────────────
describe('#612 PR-2: bracket phases resolve to their dirs, so W026 stays silent', () => {
beforeEach(() => { tmpDir = createTempProject('adr-612-b6-'); });
afterEach(() => { cleanup(tmpDir); });
// #3309 split the old overloaded W021: milestone-prefix coherence remains
// W021, while the milestone-complete/missing-directory subject is W026.
const w026 = (env) => codes('W026', env);
test('milestone complete + every bracket dir present => NO W021', () => {
writeProject({ roadmap: COHERENT, convention: 'bracket', status: 'milestone complete',
phaseDirs: ['MSD.02-05-real-work', 'MSD.02-06-follow-up'] });
assert.deepEqual(w026(), []);
});
test('a missing dir still fires — the check is not merely disabled', () => {
writeProject({ roadmap: COHERENT, convention: 'bracket', status: 'milestone complete',
phaseDirs: ['MSD.02-05-real-work'] });
const m = w026();
assert.equal(m.length, 1, JSON.stringify(m));
assert.match(m[0], /ROADMAP lists 1 unstarted phase/);
});
test('a legacy repo containing a citation heading gains NOTHING', () => {
// The shape-inference design ran this repo-failing check against a repo that
// never opted in: base emitted nothing, the branch emitted W006 + W021.
writeProject({ roadmap: `# Roadmap
## v2.0
### [RFC.2119] 5: Keyword definitions
**Goal:** not a phase
`, convention: undefined, status: 'milestone complete' });
assert.deepEqual(w026(), []);
assert.deepEqual(codes('W006'), []);
});
test('a bracket SENTINEL heading is not an unstarted phase', () => {
writeProject({ roadmap: `# Roadmap
## [MSD.02] v2.0
### [MSD.999] 01: Icebox item
**Goal:** Someday
### [MSD.02] 05: Real work
**Goal:** Build it
`, convention: 'bracket', status: 'milestone complete', phaseDirs: ['MSD.02-05-real-work'] });
assert.deepEqual(w026(), []);
});
test('DISCLOSED: a bracket roadmap with the convention unset is invisible, not false-firing', () => {
writeProject({ roadmap: COHERENT, convention: undefined, status: 'milestone complete' });
assert.deepEqual(w026(), [], 'silent invisibility, never a phantom unstarted phase');
});
});
// ─── The federated resolver, end to end ────────────────────────────────────
describe('#612 PR-2: workstream repos resolve one convention, not two', () => {
beforeEach(() => { tmpDir = createTempProject('adr-612-ws-'); });
afterEach(() => { cleanup(tmpDir); });
test('root config + active workstream: consistency and health agree', () => {
writeProject({ roadmap: COHERENT, convention: 'bracket', ws: 'ws1',
phaseDirs: ['MSD.02-05-real-work', 'MSD.02-06-follow-up'] });
const env = { MSD_WORKSTREAM: 'ws1' };
const consistency = JSON.parse(runMsdTools(['validate', 'consistency'], tmpDir, env).output);
assert.deepEqual(
(consistency.warnings || []).filter(w => /no directory on disk/.test(w)), [],
'the ROADMAP read and the directory read must resolve from the same config',
);
assert.deepEqual(codes('W005', env), [], 'and health must not call the same dirs malformed');
});
});
// ─── checkBracketCoherence: the gate ───────────────────────────────────────
describe('#612 PR-2: bracket-coherence is gated on the active convention', () => {
beforeEach(() => { tmpDir = createTempProject('adr-612-gate-'); });
afterEach(() => { cleanup(tmpDir); });
const INCOHERENT = `# Roadmap
## [MSD.02] v2.0 — Expansion
### [MSD.03] 05: Wrong milestone
**Goal:** Build it
`;
test('fires under the bracket convention, with the field names the right way round', () => {
writeProject({ roadmap: INCOHERENT, convention: 'bracket' });
const m = w021();
assert.equal(m.length, 1, JSON.stringify(m));
assert.match(m[0], /bracket milestone 03 does not match its section milestone 02/);
});
for (const convention of [undefined, 'milestone-prefixed', 'Bracket']) {
test(`SILENT when the convention is ${JSON.stringify(convention)}`, () => {
writeProject({ roadmap: INCOHERENT, convention });
assert.deepEqual(w021().filter(m => /bracket/.test(m)), []);
});
}
test('a coherent bracket roadmap is silent', () => {
writeProject({ roadmap: COHERENT, convention: 'bracket' });
assert.deepEqual(w021(), []);
});
});
// ─── Scope rules ───────────────────────────────────────────────────────────
describe('#612 PR-2: coherence scope rules', () => {
beforeEach(() => { tmpDir = createTempProject('adr-612-scope-'); });
afterEach(() => { cleanup(tmpDir); });
test('a non-phase level-3 heading does NOT clear the section', () => {
// `### Notes` used to reset the scope and silently disable both sub-checks
// for every phase after it.
writeProject({ roadmap: `# Roadmap
## [MSD.02] v2.0
### [MSD.02] 01: Setup
**Goal:** a
### Notes
Some prose.
### [MSD.03] 05: WRONG MILESTONE
**Goal:** b
`, convention: 'bracket' });
const m = w021();
assert.equal(m.length, 1, `a prose heading must not disable the check: ${JSON.stringify(m)}`);
assert.match(m[0], /bracket milestone 03 does not match its section milestone 02/);
});
test('an M-NN phase heading raises missing-bracket AND does not end the section', () => {
// A single M-NN heading — the mid-migration content this epic targets — used
// to be treated as a section reset and silenced everything after it.
writeProject({ roadmap: `# Roadmap
## [MSD.02] v2.0
### Phase 2-01: Mnn Legacy
**Goal:** a
### [MSD.03] 05: WRONG MILESTONE
**Goal:** b
`, convention: 'bracket' });
const m = w021();
assert.equal(m.length, 2, JSON.stringify(m));
assert.match(m[0], /Phase 2-01: heading is not in bracket form/);
assert.match(m[1], /bracket milestone 03 does not match its section milestone 02/);
});
test('a legacy MILESTONE heading DOES close the bracket section', () => {
// Phases under `## v3.0` are out of scope, not compared against — and
// reported against — a section they are not in.
writeProject({ roadmap: `# Roadmap
## [MSD.02] v2.0
### [MSD.02] 05: Real
**Goal:** a
## v3.0 — Legacy milestone
### Phase 7: Legacy phase
**Goal:** b
`, convention: 'bracket' });
assert.deepEqual(w021(), []);
});
test('a bare `N:` heading is not a phase and raises nothing', () => {
// `#### 2026: Timeline` and `### 3.5: Rollout` were flagged as phases
// needing migration.
writeProject({ roadmap: `# Roadmap
## [MSD.02] v2.0
#### 2026: Timeline
### 3.5: Rollout options
### [MSD.02] 05: Real
**Goal:** a
`, convention: 'bracket' });
assert.deepEqual(w021(), []);
});
test('h5 and h6 phase headings are checked, like every other reader counts them', () => {
writeProject({ roadmap: `# Roadmap
## [MSD.02] v2.0
##### [MSD.03] 08: Deep mismatch
**Goal:** a
`, convention: 'bracket' });
const m = w021();
assert.equal(m.length, 1, `h5 must not be invisible here: ${JSON.stringify(m)}`);
assert.match(m[0], /bracket milestone 03 does not match its section milestone 02/);
});
test('sentinel sections are exempt', () => {
writeProject({ roadmap: `# Roadmap
## [MSD.999] Backlog
### Phase 1: Icebox in legacy form
**Goal:** a
### [MSD.02] 07: Wrong milestone in an icebox section
**Goal:** b
`, convention: 'bracket' });
assert.deepEqual(w021(), []);
});
test('a fenced code block raises nothing', () => {
writeProject({ roadmap: `# Roadmap
## [MSD.02] v2.0
\`\`\`markdown
### [MSD.09] 42: An example heading in docs
### Phase 7: A legacy example
\`\`\`
### [MSD.02] 05: Real work
**Goal:** a
`, convention: 'bracket' });
assert.deepEqual(w021(), []);
});
test('BOUNDARY: a flat, section-less bracket roadmap gets no checking', () => {
writeProject({ roadmap: `# Roadmap
### [MSD.03] 05: No enclosing section
**Goal:** a
### Phase 6: Also legacy form
**Goal:** b
`, convention: 'bracket' });
assert.deepEqual(w021(), []);
});
test('the ADR-canonical name-only milestone heading opens a section', () => {
// `## [MSD.02] Foundation` — name, no version — is the form ADR-612 pins.
writeProject({ roadmap: `# Roadmap
## [MSD.02] Foundation
### [MSD.03] 05: Wrong milestone
**Goal:** a
`, convention: 'bracket' });
const m = w021();
assert.equal(m.length, 1, JSON.stringify(m));
assert.match(m[0], /section milestone 02/);
});
test('a milestone section heading is not mistaken for a phase heading', () => {
writeProject({ roadmap: `# Roadmap
## [MSD.02] v2.0
### [MSD.03] 05: Wrong
**Goal:** a
### [MSD.04] 06: Also wrong
**Goal:** b
`, convention: 'bracket' });
assert.equal(w021().length, 2);
});
});
// ─── G1: the shipped milestone-prefixed W021 gate stays root-only ──────────
describe('#612 PR-2: the M-NN W021 gate is unmoved by workstream config', () => {
beforeEach(() => { tmpDir = createTempProject('adr-612-mnn-'); });
afterEach(() => { cleanup(tmpDir); });
const MNN_ROADMAP = `# Roadmap
## [MSD] v2.0
### Phase 1-01: Setup
**Goal:** a
`;
const writeSplit = (rootCfg, wsCfg) => {
const root = path.join(tmpDir, '.planning');
const ws = path.join(root, 'workstreams', 'ws1');
fs.mkdirSync(path.join(ws, 'phases'), { recursive: true });
fs.writeFileSync(path.join(ws, 'ROADMAP.md'), MNN_ROADMAP, 'utf-8');
fs.writeFileSync(path.join(ws, 'STATE.md'), ['---', 'msd_state_version: 1.0',
'milestone: v2.0', 'status: executing', '---', '', '# Project State', '',
'**Phase:** 1-01', ''].join('\n'), 'utf-8');
fs.writeFileSync(path.join(root, 'config.json'), JSON.stringify(rootCfg), 'utf-8');
if (wsCfg !== undefined) {
fs.writeFileSync(path.join(ws, 'config.json'), JSON.stringify(wsCfg), 'utf-8');
}
};
const mnnW021 = () => w021({ MSD_WORKSTREAM: 'ws1' })
.filter(m => /integer prefix implies/.test(m));
test('ADDED-warning direction: a workstream M-NN config must not switch the gate on', () => {
// Root has no convention, so base is silent. Federating this gate made the
// workstream config turn a shipped legacy check on.
writeSplit({}, { phase_id_convention: 'milestone-prefixed' });
assert.deepEqual(mnnW021(), [], 'root config governs this gate');
});
test('VANISHING-warning direction: a workstream override must not switch it off', () => {
// Worse direction — a warning that fires at base disappears, so the repo
// looks healthier than it is.
writeSplit({ phase_id_convention: 'milestone-prefixed' }, { phase_id_convention: 'bracket' });
const m = mnnW021();
assert.equal(m.length, 1, `the root-configured gate must still fire: ${JSON.stringify(m)}`);
assert.match(m[0], /Phase 1-01: integer prefix implies v1\.0 but listed under v2\.0/);
});
test('root-configured, no workstream config: fires (base parity)', () => {
writeSplit({ phase_id_convention: 'milestone-prefixed' }, undefined);
assert.equal(mnnW021().length, 1);
});
});
// ─── G2: an unpadded bracket milestone is uniformly malformed ──────────────
describe('#612 PR-2: unpadded bracket milestones scope nothing', () => {
beforeEach(() => { tmpDir = createTempProject('adr-612-unpadded-'); });
afterEach(() => { cleanup(tmpDir); });
test('an unpadded phase heading does not re-scope the coherence check', () => {
// `### [MSD.3] 05:` was not a phase (id grammar) but WAS a section (section
// grammar), so it silently re-scoped every warning after it to milestone 03.
writeProject({ roadmap: `# Roadmap
## [MSD.02] v2.0
### [MSD.3] 05: Unpadded
**Goal:** a
### [MSD.05] 06: Real mismatch
**Goal:** b
`, convention: 'bracket' });
const m = w021();
const mismatch = m.filter(x => /does not match its section milestone/.test(x));
assert.equal(mismatch.length, 1, JSON.stringify(m));
assert.match(mismatch[0], /section milestone 02/, 'scope must stay on the real section');
});
});
describe('#612 PR-2: B6 keeps its narrow baseline behaviourally', () => {
beforeEach(() => { tmpDir = createTempProject('adr-612-b6-mode-'); });
afterEach(() => { cleanup(tmpDir); });
test('an any-bracket phantom does not reach the milestone-complete check', () => {
// Behavioural companion to the source-level call-site pin: flipping this
// site to the wider baseline makes `### [v1.2] Phase 3:` a phase, which has
// no directory, so the ungated W021 fires on a repo whose real phases are
// all on disk. The source pin catches the edit; this catches the effect.
writeProject({
roadmap: `# Roadmap
## [MSD.02] v2.0
### [v1.2] Phase 3: Not a phase heading
Some prose.
### [MSD.02] 05: Real work
**Goal:** a
`,
convention: 'bracket',
status: 'milestone complete',
phaseDirs: ['MSD.02-05-real-work'],
});
assert.deepEqual(w021(), [], 'the phantom must not be counted as unstarted');
});
});
// ─── G3: adversarial malformed bracket tokens reaching the coherence check ──
/**
* `checkBracketCoherence` compares a phase's OWN bracket milestone against the
* milestone of the section enclosing it, so it consumes two independently
* matched brackets. A structurally broken one — non-numeric, unclosed, nested —
* is the input most likely to make those two disagree about what they matched,
* and W021 is a check that can fail a repo.
*
* The contract: a malformed token is not a phase and not a section, so it can
* neither raise a W021 of its own nor re-scope the W021s around it (the G2
* failure mode, arrived at from a different shape), and `validate health` still
* exits cleanly. G2 above pins the unpadded case; these pin the broken ones.
*/
describe('#612 PR-2: malformed bracket tokens neither warn nor re-scope', () => {
beforeEach(() => { tmpDir = createTempProject('adr-612-malformed-w021-'); });
afterEach(() => { cleanup(tmpDir); });
const BROKEN_HEADINGS = [
['non-numeric milestone', '### [MSD.AB] 05: Broken'],
['unclosed bracket', '### [MSD.02 05: Broken'],
['nested bracket', '### [MSD.[02]] 05: Broken'],
['empty bracket', '### [] 05: Broken'],
['dot, no milestone', '### [MSD.] 05: Broken'],
['double dot', '### [MSD..02] 05: Broken'],
];
for (const [label, broken] of BROKEN_HEADINGS) {
test(`${label}: raises no W021 of its own`, () => {
writeProject({ roadmap: `# Roadmap
## [MSD.02] v2.0 — Expansion
${broken}
**Goal:** a
`, convention: 'bracket' });
assert.deepEqual(w021(), [], `${label} warned`);
});
test(`${label}: does not re-scope the W021 that follows it`, () => {
// The G2 shape: a heading that is not a phase but IS read as a section
// silently moves every later warning onto the wrong milestone.
writeProject({ roadmap: `# Roadmap
## [MSD.02] v2.0 — Expansion
${broken}
**Goal:** a
### [MSD.07] 06: Real mismatch
**Goal:** b
`, convention: 'bracket' });
const mismatch = w021().filter(x => /does not match its section milestone/.test(x));
assert.equal(mismatch.length, 1, `${label}: ${JSON.stringify(w021())}`);
assert.match(mismatch[0], /section milestone 02/, `${label}: scope moved off the real section`);
});
}
test('the whole broken corpus in one ROADMAP leaves validate health clean', () => {
writeProject({ roadmap: `# Roadmap
## [MSD.02] v2.0 — Expansion
${BROKEN_HEADINGS.map(([, h]) => `${h}\n**Goal:** x\n`).join('\n')}
### [MSD.02] 05: Real work
**Goal:** ok
`, convention: 'bracket', phaseDirs: ['MSD.02-05-real-work'] });
const r = runMsdTools(['validate', 'health'], tmpDir);
assert.ok(r.success, `validate health failed on the broken corpus: ${r.error}`);
assert.deepEqual(w021(), []);
});
// #2761 M2 (trek-e review): this asserted a `Date.now()` delta against a 20s
// ceiling, which measures the host machine rather than the SUT and flakes on
// a loaded CI runner (RULESET.TESTS.no-timing-assertion). The property it
// guarded — the widened bracket patterns do not backtrack catastrophically —
// is KEPT, expressed as an ALGORITHMIC bound instead of a wall-clock one: the
// same attack runs at 1x and 4x the pathological length and must produce the
// SAME correct result. Catastrophic backtracking is superlinear in input
// size, so a regression cannot satisfy the 4x leg under any ceiling, while a
// bounded matcher is indifferent to the scaling. The `timeout` option is a
// hang backstop, not an assertion: it turns a runaway into a deterministic
// failure instead of a suite that never returns.
for (const width of [4000, 16000]) {
test(`a pathological unclosed bracket (${width} chars) validates correctly`, { timeout: PATHOLOGICAL_INPUT_TEST_TIMEOUT_MS }, () => {
writeProject({ roadmap: `# Roadmap
## [MSD.02] v2.0 — Expansion
### [${'A'.repeat(width)} 05: Attack
**Goal:** a
### [MSD.02] 05: Real work
**Goal:** ok
`, convention: 'bracket', phaseDirs: ['MSD.02-05-real-work'] });
const r = runMsdTools(['validate', 'health'], tmpDir);
assert.ok(r.success, `validate health failed: ${r.error}`);
assert.deepEqual(w021(), [], 'an unclosed bracket is not a phase heading at any width');
});
}
});
// ─── #2761 round-7 Minor 2: the W021 remediation hint must name no ────────
// ─── unsupported `--convention` value ───────────────────────────────────────
//
// `checkBracketCoherence`'s W021 previously attached a `fix` string telling
// users to run `msd-tools roadmap upgrade --convention bracket` — but
// `roadmap-command-router.cts` only supports `--convention milestone-prefixed`
// (the bracket migrator is #612 PR-3, not yet landed); that command hard-errors
// with "Only --convention milestone-prefixed is supported". Nothing in the
// suite asserted the `fix` string's content, so the unfollowable hint shipped
// unpinned. This pins the corrected string and, more importantly, the
// invariant a future edit must not re-break: no unsupported `--convention`
// value named in remediation text users are expected to run verbatim.
describe('#612 PR-2 round-7 Minor 2: bracket W021 fix string names no unsupported --convention value', () => {
beforeEach(() => { tmpDir = createTempProject('adr-612-r7m2-'); });
afterEach(() => { cleanup(tmpDir); });
test('the fix string does not tell users to run `--convention bracket` (unsupported, hard-errors)', () => {
writeProject({ roadmap: `# Roadmap
## [MSD.02] v2.0
### Phase 2-01: Mnn Legacy
**Goal:** a
`, convention: 'bracket' });
const r = runMsdTools(['validate', 'health'], tmpDir);
assert.ok(r.success, `validate health failed: ${r.error}`);
const out = JSON.parse(r.output);
const issues = [...(out.issues || []), ...(out.warnings || [])].filter((i) => i.code === 'W021');
assert.equal(issues.length, 1, JSON.stringify(issues));
assert.doesNotMatch(issues[0].fix, /--convention bracket/,
'pinned before this fix — the hint named a command that hard-errors: "Only --convention milestone-prefixed is supported"');
assert.equal(
issues[0].fix,
'Bracket migration lands with the #612 migrator (PR-3); until then, manually align the bracket milestone in this heading to match the enclosing section.',
);
});
});