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.
585 lines
22 KiB
JavaScript
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.',
|
|
);
|
|
});
|
|
});
|