Files
msd-core/tests/project-instruction-file-parity.test.cjs
Jakub Zych fe3ed06691
Some checks failed
Tests / PR mergeability (push) Successful in 18s
Tests / Base branch health (push) Successful in 9s
Tests / Detect test scope (push) Successful in 16s
Tests / lint-tests (push) Failing after 1m43s
Tests / plugin-validate (push) Successful in 58s
Tests / test (ubuntu-latest, 24, shard 1/3) (push) Failing after 19s
Tests / test (ubuntu-latest, 24, shard 2/3) (push) Failing after 20s
Tests / test (ubuntu-latest, 24, shard 3/3) (push) Failing after 20s
Tests / test (ubuntu-latest, 24) (push) Failing after 18s
Tests / test (inert CI) (push) Has been skipped
Tests / QA loop walk (smell ratchet) (push) Failing after 19s
Tests / Coverage gate (merged shards) (push) Has been skipped
Tests / Publish emitted-baseline artifact (push) Has been skipped
Duplicate auto-close sweep / sweep (push) Successful in 19s
CI timeout budget report / report (push) Failing after 14s
Close Draft PRs (sweep) / Sweep open draft PRs (push) Successful in 9s
Dismiss Unauthorized PR Approvals / dismiss-unauthorized-approval (push) Successful in 9s
Tests / conformance test (macos-latest, 24) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 1/3) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 2/3) (push) Has been cancelled
Tests / conformance test (windows-latest, 24, shard 3/3) (push) Has been cancelled
Tests / Required tests (push) Has been cancelled
chore: clear dead test and allowlist leftovers of dropped runtimes
2026-10-06 20:35:12 +02:00

155 lines
5.9 KiB
JavaScript

'use strict';
/**
* Bug #1529 parity / drift guard.
*
* The runtime → project-instruction-file mapping is shared between two
* parallel surfaces:
* (A) the Node surface — `getProjectInstructionFile` in runtime-name-policy.cjs,
* consumed by profile-output.cjs (the generate-claude-md handler).
* (B) the bash surface — `msd-tools query project-instruction-file --runtime <r>`,
* consumed by msd-core/workflows/new-project.md to set $INSTRUCTION_FILE.
*
* Per DEFECT.GENERATIVE-FIX, any shared mapping between two surfaces MUST
* carry a parity assertion that fails when they diverge. This test is that
* guard: it asserts (A) and (B) return the same filename for every runtime,
* AND that the new-project.md workflow derives $INSTRUCTION_FILE from the
* shared query rather than a hardcoded codex-only branch (the original bug).
*
* Boundary coverage (per RULESET.TESTS.boundary-coverage): claude (the
* kept-as-is case) and an unknown runtime (the AGENTS.md default) are both
* exercised alongside every runtime family in the mapping table.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { runNode } = require('./helpers/process-seam.cjs');
const { throwIfFailed } = require('./helpers/git-fixture.cjs');
const { PROBE_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
const ROOT = path.join(__dirname, '..');
const RUNTIME_NAME_POLICY_PATH = path.join(
ROOT,
'msd-core',
'bin',
'lib',
'runtime-name-policy.cjs',
);
const MSD_TOOLS_PATH = path.join(ROOT, 'msd-core', 'bin', 'msd-tools.cjs');
const NEW_PROJECT_WORKFLOW_PATH = path.join(
ROOT,
'msd-core',
'workflows',
'new-project.md',
);
const { getProjectInstructionFile } = require(RUNTIME_NAME_POLICY_PATH);
const RUNTIMES = [
'claude',
'codex',
'opencode',
'antigravity',
// 'gemini' intentionally excluded from this loop — #4709 AC#1 made it
// REFUSE (RetiredRuntimeError) on both surfaces instead of agreeing on a
// fallback value, so `getProjectInstructionFile('gemini')` now throws
// before the two sides can even be compared. Its own dedicated test below
// pins that refusal-parity instead.
'future-runtime-xyz',
'',
];
function queryInstructionFile(runtime) {
const args = [
MSD_TOOLS_PATH,
'query',
'project-instruction-file',
'--runtime',
runtime,
];
const r = runNode(args, {
cwd: ROOT,
env: { ...process.env, MSD_RUNTIME: '' },
timeoutMs: PROBE_TIMEOUT_MS,
});
throwIfFailed(r, `node ${args.join(' ')}`);
return r.stdout.trim();
}
describe('bug #1529: getProjectInstructionFile ↔ msd-tools query parity', () => {
for (const runtime of RUNTIMES) {
const label = runtime === '' ? '<empty>' : runtime;
test(`Node function and CLI query agree for runtime=${label}`, () => {
const fromFunction = getProjectInstructionFile(runtime);
const fromQuery = queryInstructionFile(runtime);
assert.strictEqual(
fromQuery,
fromFunction,
`msd-tools query project-instruction-file --runtime ${label} returned "${fromQuery}" but getProjectInstructionFile() returned "${fromFunction}"; the two surfaces drifted.`,
);
});
}
// #4709 AC#1 inverted this case: gemini used to silently agree with the CLI
// on AGENTS.md (the defect this parity guard would have pinned); both
// surfaces now refuse it outright instead, which is still parity — just
// parity-of-refusal rather than parity-of-value.
test('Node function and CLI query both refuse for runtime=gemini (retired by #1928)', () => {
assert.throws(
() => getProjectInstructionFile('gemini'),
/retired by #1928/,
'getProjectInstructionFile("gemini") must refuse, not silently agree with the CLI on AGENTS.md',
);
const args = [MSD_TOOLS_PATH, 'query', 'project-instruction-file', '--runtime', 'gemini'];
const r = runNode(args, {
cwd: ROOT,
env: { ...process.env, MSD_RUNTIME: '' },
timeoutMs: PROBE_TIMEOUT_MS,
});
assert.notStrictEqual(
r.exitCode, 0,
'msd-tools query project-instruction-file --runtime gemini must exit non-zero, not silently print AGENTS.md',
);
// The CLI must emit the same clean single-line error routeSkillsRoot emits
// for an unknown runtime — never a raw stack trace.
assert.match(
r.stderr, /Antigravity/,
'stderr must name the successor runtime (Antigravity)',
);
assert.match(
r.stderr, /#1928/,
'stderr must name the retiring issue (#1928)',
);
assert.ok(
!/\n\s+at\s/.test(r.stderr),
`stderr must not contain a dumped stack trace, got: ${r.stderr}`,
);
});
});
describe('bug #1529: new-project.md workflow uses the shared policy query', () => {
// allow-test-rule: structural-regression-guard (#1529)
// the workflow's bash block MUST invoke the
// shared `msd_run query project-instruction-file` query rather than a hardcoded
// codex-only `if/else` branch; there is no typed IR for "this bash block calls a
// specific msd-tools query instead of a hardcoded mapping".
const workflow = fs.readFileSync(NEW_PROJECT_WORKFLOW_PATH, 'utf8');
test('workflow derives INSTRUCTION_FILE from the shared query', () => {
assert.ok(
/INSTRUCTION_FILE=\$\(msd_run query project-instruction-file --runtime "\$RUNTIME"\)/.test(workflow),
'new-project.md must derive INSTRUCTION_FILE via `msd_run query project-instruction-file --runtime "$RUNTIME"` (the shared policy adapter)',
);
});
test('workflow no longer hardcodes the codex-only branch', () => {
assert.ok(
!/if \[ "\$RUNTIME" = "codex" \]; then INSTRUCTION_FILE="AGENTS\.md"; else INSTRUCTION_FILE="\.claude\/CLAUDE\.md"; fi/.test(workflow),
'new-project.md must not contain the retired codex-only `if [ "$RUNTIME" = "codex" ]; then INSTRUCTION_FILE="AGENTS.md"; else INSTRUCTION_FILE=".claude/CLAUDE.md"; fi` branch (#1529 regression guard)',
);
});
});