'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.', ); }); });