Files
msd-core/tests/no-dead-sdk-refs.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

316 lines
15 KiB
JavaScript

'use strict';
// Guard: runtime-loaded markdown must not carry a reference an AI runtime will try to
// LOCATE on the filesystem. When a runtime meets a file-shaped token it cannot resolve,
// it falls back to searching for it — and on Git Bash for Windows `find /` maps to the
// drive root, so `find.exe` traverses the whole disk (orphaned processes, handle leak,
// a pegged core until someone reaps it by hand).
//
// The guard is a RULE TABLE, deliberately, because the first version of it was not.
//
// #2020 shipped a guard hardcoded to `sdk/(src|dist|handlers)/` — the three dead paths
// that had caused the storm. That is an instance fix wearing a regression test: it
// proved those three paths were gone and said nothing about the class. Seven weeks
// later #3809 reproduced the identical storm under a different token, and the guard
// was structurally incapable of seeing it. Adding a rule here must stay a one-entry
// change, so the next recurrence is a table row rather than a third incident.
//
// Scope: the markdown a runtime actually loads and resolves references against —
// agents/, msd-core/workflows/, msd-core/references/, commands/. Prose mentions inside
// *.cjs sources are out of scope: nothing tries to locate those.
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fc = require('fast-check');
const fs = require('fs');
const path = require('path');
const ROOT = path.join(__dirname, '..');
const SCAN_DIRS = ['agents', 'msd-core/workflows', 'msd-core/references', 'commands'];
// ---------------------------------------------------------------------------
// Rule A (#2020) — a dead SDK file-path reference. The SDK package was retired
// by ADR-0174, so these paths never resolve and a runtime will hunt for them.
// ---------------------------------------------------------------------------
const DEAD_SDK_REF = /sdk\/(?:src|dist|handlers)\//;
// ---------------------------------------------------------------------------
// Rule B (#3809) — the runtime shim named in COMMAND position.
//
// The shim filename is not a command on any platform. package.json `bin` ships
// `msd-core`, `msd-tools`, `msd_run`, `msd-mcp-server`; the .cjs file exists only at
// <runtime-root>/msd-core/bin/. CONTEXT.md -> Runtime Launcher Module makes `msd_run`
// the single sanctioned entry point: "Canonical space-safe shell preamble (`msd_run`)
// used by every workflow bash block to invoke the MSD runtime CLI."
//
// So a workflow that says `<shim> query phase.add` instructs the agent to run something
// that exits 127, after which the file-shaped token sends it looking for the file.
//
// What separates an INVOCATION from the four legitimate ways this filename appears is
// the token that follows it. Being lenient here is the entire point — the guard must
// not flag the launcher's own resolver, a real `node <path>/<shim>` call, a bare path,
// or prose that simply names the file. See the negative-space rows below, each of which
// is a form that exists in the tree today and must keep working.
// ---------------------------------------------------------------------------
// Built at runtime so this line is not itself an invocation the guard would flag.
const SHIM = ['msd-tools', '.cjs'].join('');
// The pattern is spelled out rather than escaped from SHIM at build time. A
// `SHIM.replace(/\./g, '\\.')` here is a hand-rolled escaper: it handles the dot and
// nothing else, which CodeQL flags as js/incomplete-sanitization (it does not escape
// backslashes) and which local/no-adhoc-regex-escape bans outright — the canonical
// escaper lives in src/pattern.cts. Since SHIM is a compile-time constant whose only
// metacharacter is the dot, the honest fix is to carry no escaping logic at all.
// SHIM_PATTERN and SHIM are pinned to each other by a test below so they cannot drift.
const SHIM_PATTERN = 'msd-tools\\.cjs';
const SHIM_RE = new RegExp(
// not preceded by a path separator, word char, or hyphen (excludes `<dir>/<shim>`)
`(?<![\\w./\\\\-])${SHIM_PATTERN}` +
// at least one space or tab, then the following token
'[ \\t]+([a-z][a-z0-9.-]*)',
'g',
);
// The verb roster is DERIVED from msd-tools.cjs's own exports, never hand-copied.
//
// That file already carries three hand-maintained rosters — TOP_LEVEL_USAGE,
// HOST_COMMAND_ROUTERS and SKIP_ROOT_RESOLUTION — whose divergence is a named repo
// defect (DEFECT.GENERATIVE-FIX), pinned by the parity test in tests/commands.test.cjs.
// A hand-copied fourth roster here would be that same defect wearing a guard's clothes,
// and it already was: the first cut of this list was transcribed from an INSTALLED
// older binary and silently missed 22 verbs this tree ships, including `websearch`,
// `windows` and `state-snapshot`. Deriving costs one require and cannot drift.
//
// The require is lazy and memoised: tests/helpers.cjs defers its own built-lib require
// for the same reason, so an unbuilt tree fails one test with an actionable message
// instead of crashing the file before a single test() registers.
let _cliVerbs = null;
function cliVerbs() {
if (_cliVerbs) return _cliVerbs;
const { HOST_COMMAND_ROUTERS, TOP_LEVEL_USAGE } = require('../msd-core/bin/msd-tools.cjs');
const listed = TOP_LEVEL_USAGE.match(/Commands: ([\s\S]*?)\n\nGlobal flags:/);
assert.ok(listed, 'TOP_LEVEL_USAGE must contain a "Commands: ...\\n\\nGlobal flags:" block');
_cliVerbs = new Set([
...Object.keys(HOST_COMMAND_ROUTERS),
...listed[1].split(',').map((s) => s.trim()).filter(Boolean),
// `query` dispatches through the Command Routing Hub ahead of the host-router
// table, so it appears in neither export — yet it is the form 45 of the 50 #3809
// offenders used. Verified live in this tree: bare `query` is a usage error while
// `query state-snapshot` dispatches and returns JSON.
'query',
]);
return _cliVerbs;
}
/**
* Subcommand tokens invoked on the bare shim in one line of markdown.
* Returns [] for every legitimate form. Pure — no filesystem access.
*/
function findShimInvocations(line) {
// The canonical launcher's own single source of truth assigns the filename.
if (line.includes('_MSD_SHIM_NAME=')) return [];
const found = [];
let m;
SHIM_RE.lastIndex = 0;
while ((m = SHIM_RE.exec(line)) !== null) {
// `node <path>/<shim> <verb>` is resolvable and fine. `node <shim> <verb>` is NOT —
// node resolves a bare filename against cwd, so it fails exactly like the bare form.
// The exemption therefore requires a real path separator before the shim.
if (/\bnode[ \t]+["']?[^ \t"']*[/\\]$/.test(line.slice(0, m.index))) continue;
// A dotted subcommand is always `<family>.<verb>` and the family is itself a roster
// verb, so testing the first segment covers `phase.add` and `state.patch` without
// resorting to "contains a dot", which also matches prose like `v1.2`.
const token = m[1];
if (cliVerbs().has(token.split('.')[0])) {
found.push(token);
}
}
return found;
}
const RULES = [
{
id: '#2020',
label: 'dead SDK file reference',
remedy: 'the SDK package was retired (ADR-0174) — point at a live path',
scan: (line) => (DEAD_SDK_REF.test(line) ? ['sdk/'] : []),
},
{
id: '#3809',
label: `bare \`${SHIM}\` invocation`,
remedy: 'call the canonical launcher instead: `msd_run <subcommand>`',
scan: findShimInvocations,
},
];
function walkMd(dir, out = []) {
let entries;
try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
catch { return out; }
for (const e of entries) {
const full = path.join(dir, e.name);
if (e.isDirectory()) walkMd(full, out);
else if (e.isFile() && e.name.endsWith('.md')) out.push(full);
}
return out;
}
/** Offenders for one rule across every runtime-loaded markdown file. */
function scanTree(rule) {
const offenders = [];
for (const rel of SCAN_DIRS) {
for (const file of walkMd(path.join(ROOT, rel))) {
const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/);
lines.forEach((line, i) => {
for (const hit of rule.scan(line)) {
offenders.push(`${path.relative(ROOT, file)}:${i + 1} (${hit})`);
}
});
}
}
return offenders;
}
describe('runtime-loaded markdown carries no unresolvable reference', () => {
for (const rule of RULES) {
test(`${rule.id} — no ${rule.label} in ${SCAN_DIRS.join(', ')}`, () => {
const offenders = scanTree(rule);
assert.deepEqual(
offenders,
[],
`${rule.id}: ${offenders.length} ${rule.label}(s) found. Runtimes resolve these by ` +
`filesystem search — on Git Bash for Windows that is a full-drive find.exe storm.\n` +
`Remedy: ${rule.remedy}.\n${offenders.join('\n')}`,
);
});
}
});
describe('#3809 — what counts as a bare shim invocation', () => {
// Positive space: forms that send an agent hunting for the file.
const INVOCATIONS = [
['inline code in prose', `**Delegate the phase addition to \`${SHIM} query phase.add\`:**`, 'query'],
['command substitution', `- \`ROADMAP=$(${SHIM} query roadmap.analyze)\``, 'query'],
['dotted subcommand', `\`${SHIM} query state.add-roadmap-evolution ...\``, 'query'],
['hyphenated subcommand', `Use \`${SHIM} detect-custom-files\``, 'detect-custom-files'],
['bare verb, no backticks', `# config settings can be fetched via ${SHIM} query config-get`, 'query'],
['commit verb', `via \`${SHIM} commit-to-subrepo\`. File paths are relative`, 'commit-to-subrepo'],
];
for (const [name, line, expected] of INVOCATIONS) {
test(`flags ${name}`, () => {
assert.deepEqual(findShimInvocations(line), [expected]);
});
test(`flags ${name} with a CRLF line ending`, () => {
// A trailing \r must not defeat the match — this repo has a documented
// bug class of regexes that only ever saw \n.
assert.deepEqual(findShimInvocations(`${line}\r`), [expected]);
});
}
// Negative space: every legitimate way the filename appears in the tree today.
// Each row is a real line; flagging any of them would be an over-broad fix.
const LEGITIMATE = [
['the launcher resolver assignment', `_MSD_SHIM_NAME="${SHIM}"; _MSD_RUNTIME_ROOT="\${RUNTIME_DIR:-$(pwd)}"`],
['a node-prefixed invocation', ` node <config-dir>/msd-core/bin/${SHIM} restore-custom-files \\`],
['a quoted node-prefixed invocation', `node "$MSD_DIR/msd-core/bin/${SHIM}" query commit`],
['a qualified path with no subcommand', ` "$PREFERRED_CONFIG_DIR/msd-core/bin/${SHIM}" \\`],
['prose naming the file', `# Resolve ${SHIM} WITHOUT yet knowing MSD_DIR. The running workflow lives`],
['prose whose next token is an English word', `# shim-only install (${SHIM} present, \`msd-tools\` not on PATH) the bare call exits`],
['prose with a lowercase English word after', `the ${SHIM} file lives under msd-core/bin`],
['the filename at end of line', `authoritative tool for THIS install is ${SHIM}`],
['prose with a hyphenated English word after', `the ${SHIM} built-in helper does X`],
['prose with a version number after', `the ${SHIM} v1.2 release notes`],
];
for (const [name, line] of LEGITIMATE) {
test(`ignores ${name}`, () => {
assert.deepEqual(findShimInvocations(line), []);
});
}
// `node <shim>` with no directory is NOT exempt: node resolves a bare filename
// against cwd, so it fails exactly like the bare form (found at
// msd-core/references/model-profiles.md:231).
// The verb roster is the whole advertised command surface, not the subset that happens
// to appear in the tree — a bare verb nobody has written yet must still be caught.
for (const verb of ['phase', 'state', 'verify', 'roadmap', 'milestone', 'worktree']) {
test(`flags the bare verb \`${verb}\`, which appears nowhere in the tree today`, () => {
assert.deepEqual(findShimInvocations(`run \`${SHIM} ${verb} list\``), [verb]);
});
}
test('flags a node-prefixed shim that carries no path', () => {
assert.deepEqual(findShimInvocations(`\`node ${SHIM} effort sync --apply\``), ['effort']);
});
// Boundary: the separator between the filename and the subcommand.
test('zero separating spaces is not an invocation (limit-1)', () => {
assert.deepEqual(findShimInvocations(`\`${SHIM}query\``), []);
});
test('exactly one separating space is an invocation (limit)', () => {
assert.deepEqual(findShimInvocations(`\`${SHIM} query\``), ['query']);
});
test('more than one separating space is still an invocation (limit+1)', () => {
assert.deepEqual(findShimInvocations(`\`${SHIM} query\``), ['query']);
assert.deepEqual(findShimInvocations(`\`${SHIM}\tquery\``), ['query']);
});
// Properties — the matcher is a parser, so pin its two directional invariants.
test('property: a path-qualified or node-prefixed shim is never flagged', () => {
fc.assert(
fc.property(
fc.constantFrom('node ', 'node "', "node '", '/', './', '../', 'msd-core/bin/', '$DIR/'),
fc.constantFrom('query', 'commit', 'phase.add', 'audit-open', 'from-gsd2'),
(prefix, verb) => {
const line = prefix.endsWith('/')
? ` ${prefix}${SHIM} ${verb}`
: ` ${prefix}msd-core/bin/${SHIM} ${verb}`;
assert.deepEqual(findShimInvocations(line), []);
},
),
{ numRuns: 200 },
);
});
test('property: a bare shim followed by a subcommand is always flagged', () => {
fc.assert(
fc.property(
fc.constantFrom('', '`', '$(', '- ', 'run ', '**via '),
fc.constantFrom('query', 'commit', 'phase.add', 'audit-open', 'from-gsd2', 'commit-to-subrepo'),
fc.integer({ min: 1, max: 4 }),
(prefix, verb, spaces) => {
const line = `${prefix}${SHIM}${' '.repeat(spaces)}${verb}`;
assert.deepEqual(findShimInvocations(line), [verb]);
},
),
{ numRuns: 300 },
);
});
});
describe('#3809 — the verb roster is derived, not copied', () => {
test('SHIM_PATTERN matches SHIM exactly, so the two cannot drift', () => {
assert.equal(new RegExp(`^${SHIM_PATTERN}$`).test(SHIM), true,
`SHIM_PATTERN (${SHIM_PATTERN}) no longer matches SHIM (${SHIM})`);
// And prove the pattern's dot is escaped rather than a wildcard.
assert.equal(new RegExp(`^${SHIM_PATTERN}$`).test('msd-toolsXcjs'), false,
'the dot in SHIM_PATTERN must be escaped, not a wildcard');
});
test('covers every command msd-tools.cjs actually dispatches', () => {
const { HOST_COMMAND_ROUTERS } = require('../msd-core/bin/msd-tools.cjs');
const missing = Object.keys(HOST_COMMAND_ROUTERS).filter((v) => !cliVerbs().has(v));
assert.deepEqual(missing, [], `verb(s) dispatched by msd-tools.cjs but invisible to this guard: ${missing.join(', ')}`);
});
test('recognises verbs that no hand-copied list had', () => {
// These ship in this tree but were absent from the hand-copied first cut.
for (const verb of ['websearch', 'windows', 'state-snapshot', 'context-predicates']) {
assert.equal(cliVerbs().has(verb), true, `roster is missing ${verb}`);
}
});
});