Files
msd-core/tests/completion-predicate-drift-guard.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

630 lines
32 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
process.env.MSD_TEST_MODE = '1';
// D3 below reads src/plan-scan.cts / msd-core/bin/lib/plan-scan.cjs and
// src/verification.cts and regex-tests them for require/import statements.
// This asserts a DEPENDENCY-DIRECTION invariant (the owner consumes plan
// counts, never the reverse — ADR-3180 §7.4 HARD CONSTRAINT) that has no
// behavioral/runtime surface: `require`-ing plan-scan.cjs in-process cannot
// distinguish "verification.cjs is absent from its dependency graph" from
// "verification.cjs happens to already be in require.cache because an
// earlier test in this same file required it directly" (line 36 above does
// exactly that) — only source inspection can tell which import edge exists.
// #3186 review finding 6(a).
/**
* Unit + whole-repo coverage for the PHASE-COMPLETION drift guard
* (scripts/lint-completion-predicate-drift.cjs, epic #3180, issue #3186,
* ADR-3180 §7.4, Decision 4). Modelled on tests/milestone-window-drift-guard.test.cjs
* / tests/completion-ratio-single-owner.test.cjs's guard sections: behavioral
* throughout — every assertion drives the guard's exported pure functions
* directly, never `readFileSync().includes()`.
*
* Covers 50-test-matrix.md section E (the guard) plus D3 (the dependency-
* direction guard: plan-scan.cts does not import verification.cts).
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const drift = require('../scripts/lint-completion-predicate-drift.cjs');
const { sanitizeForReport } = require('../scripts/lib/drift-scan.cjs');
const { createTempDir, cleanup } = require('./helpers.cjs');
const ROOT = path.join(__dirname, '..');
const OWNER_RELPATH = drift.OWNER_FILE; // path.join('src', 'verification.cts')
// ═════════════════════════════════════════════════════════════════════════
// E1 — the real repo tree, post-migration: 0 violations, earned per shape.
// ═════════════════════════════════════════════════════════════════════════
describe('E1 — scanRepo(repoRoot) against the real repo: earned zero', () => {
test('zero violations across the whole scan surface (src/ + prompt layer)', () => {
const violations = drift.scanRepo(ROOT);
assert.deepStrictEqual(
violations,
[],
'unsanctioned phase-completion re-derivation(s) — route through src/verification.cts '
+ '`isPhaseComplete` (issue #3186, ADR-3180 §7.4):\n'
+ violations.map((d) => ` ${d.file}:${d.line} [shape ${d.shape}] ${d.found}`).join('\n'),
);
});
test('per-shape proof: a deliberate (a)/(b)/(c) fixture is NOT silently swallowed by scanRepo', (t) => {
// Distinguishes "0 because nothing to find" from "0 because the detector
// is broken" — scanRepo on a synthetic tree carrying all three shapes
// must report exactly 3, proving the same code path scanRepo(ROOT) took
// is capable of finding violations at all.
const root = createTempDir('msd-completion-predicate-drift-');
t.after(() => cleanup(root));
fs.mkdirSync(path.join(root, 'src'), { recursive: true });
fs.writeFileSync(
path.join(root, 'src', 'fake.cts'),
[
'function fakeConsumer(roadmapComplete, planCount, summaryCount) {',
" let status = 'pending';",
' if (roadmapComplete && status !== \'complete\') {',
" status = 'complete';",
' }',
' const verificationStatus = planCount > 0',
' ? readVerificationStatus(phaseDir)',
" : { status: 'not_required' };",
' const done = summaryCount >= planCount && planCount > 0;',
' return { status, verificationStatus, done };',
'}',
].join('\n'),
);
const violations = drift.scanRepo(root);
const shapes = violations.map((v) => v.shape).sort();
assert.deepStrictEqual(shapes, ['a', 'b', 'c']);
});
});
// ═════════════════════════════════════════════════════════════════════════
// E2 — fixture reintroducing shapes (a), (b), (c): each flagged, one hit
// apiece.
// ═════════════════════════════════════════════════════════════════════════
describe('E2 — each shape flagged in isolation, exactly one hit apiece', () => {
test('shape (a): checkbox-derived completion override', () => {
const text = [
'function fakeConsumer(roadmapComplete) {',
" let diskStatus = 'planned';",
' if (roadmapComplete && diskStatus !== \'complete\') {',
" diskStatus = 'complete';",
' }',
' return diskStatus;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts'));
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'a');
});
test('shape (b): plan-count precondition gating a verification read', () => {
const text = [
'function fakeConsumer(planCount, phaseDir) {',
' return planCount > 0',
' ? readVerificationStatus(phaseDir)',
" : { status: 'not_required' };",
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts'));
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'b');
});
test('shape (c): local re-implementation of "complete" from counts', () => {
const text = [
'function fakeConsumer(summaryCount, planCount) {',
' return summaryCount >= planCount && planCount > 0;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts'));
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'c');
});
test('shape (c): the reversed operand order (planCount <= summaryCount) is also flagged', () => {
const text = [
'function fakeConsumer(summaryCount, planCount) {',
' return planCount <= summaryCount;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts'));
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'c');
});
});
// ═════════════════════════════════════════════════════════════════════════
// E3 — shape (a) with a nested-paren if-condition: flagged. (The first draft
// missed exactly this on cmdInitManager's `(completion.phase_complete ||
// planCount === 0)` nested group.)
// ═════════════════════════════════════════════════════════════════════════
describe('E3 — shape (a): nested-paren if-condition', () => {
test('a second parenthesised group inside the if-condition is still detected', () => {
const text = [
'function fakeConsumer(roadmapComplete, completion, planCount) {',
" let diskStatus = 'planned';",
' if (roadmapComplete && (completion.phase_complete || planCount === 0) && diskStatus !== \'complete\') {',
" diskStatus = 'complete';",
' }',
' return diskStatus;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts'));
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'a');
});
});
// ═════════════════════════════════════════════════════════════════════════
// E4 — an unconditional readVerificationStatus( call (the cmdPhaseComplete
// shape): NOT flagged.
// ═════════════════════════════════════════════════════════════════════════
describe('E4 — an unconditional readVerificationStatus( call is not flagged', () => {
test('no ternary gate on the call, no count-gate in the function: clean', () => {
const text = [
'function fakeConsumer(phaseDir) {',
' const verificationStatus = readVerificationStatus(phaseDir, { runtime: \'claude\' });',
' return verificationStatus;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts'));
assert.deepStrictEqual(out, []);
});
test('a count-gate present elsewhere in the SAME function but the read is unconditional: still clean', () => {
// Shape (b) requires the readVerificationStatus( call ITSELF to be
// ternary-gated on the same line — a count-gate merely coexisting with
// an unconditional call must not fire.
const text = [
'function fakeConsumer(phaseDir, retryCount) {',
' if (retryCount > 0) { /* retry bookkeeping, unrelated */ }',
' const verificationStatus = readVerificationStatus(phaseDir);',
' return verificationStatus;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts'));
assert.deepStrictEqual(out, []);
});
});
// ═════════════════════════════════════════════════════════════════════════
// E5 — all three shapes inside `//` and `/* */` comments: NOT flagged.
// ═════════════════════════════════════════════════════════════════════════
describe('E5 — commented-out shapes are not flagged', () => {
test('shape (a) inside a // line comment', () => {
const text = [
"// if (roadmapComplete && diskStatus !== 'complete') diskStatus = 'complete';",
].join('\n');
assert.deepStrictEqual(drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts')), []);
});
test('shape (b) inside a /* */ block comment', () => {
const text = [
'/*',
' const v = planCount > 0 ? readVerificationStatus(phaseDir) : { status: "not_required" };',
'*/',
].join('\n');
assert.deepStrictEqual(drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts')), []);
});
test('shape (c) inside a JSDoc continuation comment', () => {
const text = [
'/**',
' * e.g. `summaryCount >= planCount && planCount > 0` is the old shape.',
' */',
].join('\n');
assert.deepStrictEqual(drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts')), []);
});
});
// ═════════════════════════════════════════════════════════════════════════
// E6 — retryCount > 0 and other unrelated count gates: NOT flagged.
// ═════════════════════════════════════════════════════════════════════════
describe('E6 — unrelated count gates alone are not flagged', () => {
test('retryCount > 0 with no readVerificationStatus( call anywhere in the function', () => {
const text = [
'function fakeRetry(retryCount) {',
' if (retryCount > 0) return doRetry();',
' return doOnce();',
'}',
].join('\n');
assert.deepStrictEqual(drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts')), []);
});
test('itemCount > 0 gating an unrelated ternary (no verification call at all)', () => {
const text = [
'function fakeList(itemCount) {',
" return itemCount > 0 ? 'has items' : 'empty';",
'}',
].join('\n');
assert.deepStrictEqual(drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts')), []);
});
});
// ═════════════════════════════════════════════════════════════════════════
// E7 — owner file: `isPhaseComplete` exempt BY FUNCTION NAME, never whole-file.
// ═════════════════════════════════════════════════════════════════════════
describe('E7 — owner-file exemption is function-scoped, not file-scoped', () => {
test('the canonical isPhaseComplete body is exempt in the owner file', () => {
const text = [
'function isPhaseComplete(phaseDir, deps) {',
' const verification = readVerificationStatus(phaseDir, deps);',
" return { value: { complete: verification.status === 'passed', verification }, scope: SCOPE.COMPLETE };",
'}',
].join('\n');
assert.deepStrictEqual(drift.findCompletionPredicateDrift(text, OWNER_RELPATH), []);
});
test('a differently-named function in the SAME owner file carrying shape (c) IS flagged', () => {
const text = [
'function isPhaseComplete(phaseDir) { return true; }',
'',
'function someOtherHelper(summaryCount, planCount) {',
' return summaryCount >= planCount && planCount > 0;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, OWNER_RELPATH);
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].fn, 'someOtherHelper');
});
});
// ═════════════════════════════════════════════════════════════════════════
// E8 — a SECOND, unrelated predicate added elsewhere IN THE OWNER FILE:
// flagged (the Amendment-4 blind spot — a whole-file exemption on the owner
// is precisely how a prior guard's owner grew an invisible second copy).
// ═════════════════════════════════════════════════════════════════════════
describe('E8 — a second predicate elsewhere in the owner file is flagged (Amendment-4 blind spot)', () => {
test('shape (a) added to a non-canonical function in src/verification.cts is caught', () => {
const text = [
'function isPhaseComplete(phaseDir) { return true; }',
'',
'function cmdSomeNewVerb(roadmapComplete) {',
" let status = 'pending';",
' if (roadmapComplete && status !== \'complete\') {',
" status = 'complete';",
' }',
' return status;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, OWNER_RELPATH);
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'a');
assert.strictEqual(out[0].fn, 'cmdSomeNewVerb');
});
});
// ═════════════════════════════════════════════════════════════════════════
// E9 — prompt-layer fixture reintroducing the checkbox OR: flagged (the
// scan surface really covers msd-core/workflows, not just src/).
// ═════════════════════════════════════════════════════════════════════════
describe('E9 — prompt-layer checkbox-OR re-derivation is flagged', () => {
test('findPromptCompletionDrift flags the two-line assign/test pairing', () => {
const lines = [
'PHASE_COMPLETE=$(echo "$PHASE_INFO" | jq -r \'.roadmap_complete // false\')',
'DISK_STATUS=$(echo "$ANALYZE" | jq -r \'.disk_status\')',
'if [[ "$DISK_STATUS" == "complete" || "$PHASE_COMPLETE" == "true" ]]; then',
' STATUS="completed"',
'fi',
].join('\n');
const out = drift.findPromptCompletionDrift(lines, path.join('msd-core', 'workflows', 'fake.md'));
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'a');
});
test('scanRepo finds the same fixture when it is a real .md file under msd-core/workflows', (t) => {
const root = createTempDir('msd-completion-predicate-drift-');
t.after(() => cleanup(root));
fs.mkdirSync(path.join(root, 'msd-core', 'workflows'), { recursive: true });
fs.writeFileSync(
path.join(root, 'msd-core', 'workflows', 'fake.md'),
[
'PHASE_COMPLETE=$(echo "$PHASE_INFO" | jq -r \'.roadmap_complete // false\')',
'if [[ "$DISK_STATUS" == "complete" || "$PHASE_COMPLETE" == "true" ]]; then',
' STATUS="completed"',
'fi',
].join('\n'),
);
const violations = drift.scanRepo(root);
assert.strictEqual(violations.length, 1);
assert.strictEqual(violations[0].shape, 'a');
});
test('a DISK_STATUS-only check (no checkbox OR) is not flagged', () => {
const lines = [
'if [[ "$DISK_STATUS" == "complete" ]]; then',
' STATUS="completed"',
'fi',
].join('\n');
assert.deepStrictEqual(drift.findPromptCompletionDrift(lines, path.join('msd-core', 'workflows', 'fake.md')), []);
});
});
// ═════════════════════════════════════════════════════════════════════════
// E10 — filename with control bytes / bidi: sanitized in report output.
// ═════════════════════════════════════════════════════════════════════════
describe('E10 — sanitizeForReport neutralizes control bytes and bidi overrides', () => {
test('a C0 control byte in a violation "found" fragment is escaped, not passed through raw', () => {
const raw = 'diskStatus = \x1b[31m\'complete\'\x1b[0m;';
const sanitized = sanitizeForReport(raw);
assert.ok(!sanitized.includes('\x1b'), 'raw ESC byte must not survive sanitization');
assert.ok(sanitized.includes('\\x1b'), 'ESC byte must be rendered as a visible \\xNN escape');
});
test('a bidi right-to-left override codepoint in a reported file path is escaped', () => {
const raw = 'src/‮evil.cts';
const sanitized = sanitizeForReport(raw);
assert.ok(!sanitized.includes('‮'), 'raw RLO codepoint must not survive sanitization');
assert.ok(sanitized.includes('\\u202e'), 'RLO codepoint must be rendered as a visible \\uNNNN escape');
});
});
// ═════════════════════════════════════════════════════════════════════════
// E11 — #3186 review finding 4: two evasion shapes the pre-review guard
// produced ZERO hits on, now caught.
// ═════════════════════════════════════════════════════════════════════════
describe('E11 — finding 4(a): the BLOCK form of shape (b) is now caught', () => {
test('if (planCount > 0) { … readVerificationStatus(…) … } is flagged (was zero hits before)', () => {
const text = [
'function fakeConsumer(planCount, phaseDir) {',
' let verificationStatus = { status: \'not_required\' };',
' if (planCount > 0) {',
' verificationStatus = readVerificationStatus(phaseDir);',
' }',
' return verificationStatus;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts'));
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'b');
});
test('an unrelated if-block (no count-gate condition) wrapping an unconditional call stays clean', () => {
// A generic nested-brace check (not "is this specifically an if-block
// whose OWN condition is a count-gate") would false-positive here.
const text = [
'function fakeConsumer(phaseDir, flag) {',
' let verificationStatus = null;',
' if (flag) {',
' verificationStatus = readVerificationStatus(phaseDir);',
' }',
' return verificationStatus;',
'}',
].join('\n');
assert.deepStrictEqual(drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts')), []);
});
test('a non-conditional wrapper (a callback passed to another function) is NOT treated as gating', () => {
// Regression guard for the naive "any brace nesting deeper than the
// function's own top level = gated" approach, which false-positived on
// cmdPhaseComplete's real `withPlanningLock(cwd, () => { … })` shape:
// an UNCONDITIONAL readVerificationStatus( call wrapped only in a
// callback, with an unrelated count-gate elsewhere in the function.
const text = [
'function fakeConsumer(phaseDir, retryCount) {',
' if (retryCount > 0) { /* unrelated */ }',
' return withPlanningLock(phaseDir, () => {',
' return readVerificationStatus(phaseDir);',
' });',
'}',
].join('\n');
assert.deepStrictEqual(drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts')), []);
});
});
describe('E11 — finding 4(b): the algebraic-restatement evasion of shape (c) is now caught', () => {
test('summaryCount - planCount >= 0 is flagged (was zero hits before)', () => {
const text = [
'function fakeConsumer(summaryCount, planCount) {',
' return summaryCount - planCount >= 0;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts'));
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'c');
});
test('the mirrored form planCount - summaryCount <= 0 is also flagged', () => {
const text = [
'function fakeConsumer(summaryCount, planCount) {',
' return planCount - summaryCount <= 0;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts'));
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'c');
});
test('an unrelated count-difference comparison (not summary/plan) stays clean', () => {
const text = [
'function fakeConsumer(retryCount, maxCount) {',
' return retryCount - maxCount >= 0;',
'}',
].join('\n');
assert.deepStrictEqual(drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts')), []);
});
});
// ═════════════════════════════════════════════════════════════════════════
// E12 — shape (d): a bare `.completed` read off a `scanPhasePlans(` result,
// used as a completion verdict outside the owner (src/plan-scan.cts). The
// #3186 remote-matrix finding: cmdStateSync (src/state.cts, now fixed)
// destructured `scanPhasePlans(dirPath).completed` directly with no
// comparison for shapes (a)/(b)/(c) to catch.
// ═════════════════════════════════════════════════════════════════════════
describe('E12 — shape (d): scanPhasePlans(...).completed read as a completion verdict', () => {
test('direct chained form: scanPhasePlans(dir).completed is flagged', () => {
const text = [
'function cmdSomeVerb(dirPath) {',
' return scanPhasePlans(dirPath).completed;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts'));
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'd');
assert.strictEqual(out[0].fn, 'cmdSomeVerb');
});
test('direct chained form with a nested-paren call argument is still flagged (path.join(...) inside the call)', () => {
// A naive `[^)]*` regex would stop at path.join(...)'s OWN closing paren
// and miss the `.completed` that follows the call's TRUE closing paren —
// the real shape most scanPhasePlans( call sites in this tree use.
const text = [
'function cmdSomeVerb(phasesDir, dir) {',
' return scanPhasePlans(path.join(phasesDir, dir)).completed;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts'));
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'd');
});
test('destructured form: const { completed } = scanPhasePlans(dirPath) is flagged (the exact #3186 cmdStateSync shape)', () => {
const text = [
'function cmdStateSyncLike(dirPath) {',
' const { completed } = scanPhasePlans(dirPath);',
' return completed;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts'));
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'd');
assert.strictEqual(out[0].fn, 'cmdStateSyncLike');
});
test('destructured renamed-alias form: const { completed: isDone } = scanPhasePlans(dirPath) is flagged', () => {
const text = [
'function cmdSomeVerb(dirPath) {',
' const { completed: isDone } = scanPhasePlans(dirPath);',
' return isDone;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts'));
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'd');
});
test('indirect form: the call and the .completed read sit on DIFFERENT lines in the SAME function — flagged, proving no line window', () => {
const text = [
'function cmdSomeVerb(dirPath) {',
' const scan = scanPhasePlans(dirPath);',
' const summaryCount = scan.summaryFiles.length;',
' const planCount = scan.planFiles.length;',
' // several unrelated lines of bookkeeping in between',
' const x = summaryCount + planCount;',
' const y = x * 2;',
' return scan.completed;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts'));
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'd');
assert.strictEqual(out[0].line, 8);
});
test('a `.completed` read inside src/plan-scan.cts itself (the owner, exempt function) is NOT flagged', () => {
const text = [
'function scanPhasePlans(phaseDir) {',
' const inner = scanPhasePlans(phaseDir);',
' return { completed: inner.completed, extra: true };',
'}',
].join('\n');
assert.deepStrictEqual(drift.findCompletionPredicateDrift(text, path.join('src', 'plan-scan.cts')), []);
});
test('an exempted function is not flagged, but a DIFFERENT function in the SAME file still is (function-scoped, never whole-file)', () => {
// OWNER_RELPATH (src/verification.cts) has `isPhaseComplete` exempt in
// FUNCTION_SCOPED_EXEMPTIONS — reused here to prove shape (d) shares that
// same per-function map rather than a whole-file allowlist.
const text = [
'function isPhaseComplete(phaseDir) {',
' const scan = scanPhasePlans(phaseDir);',
' return scan.completed;',
'}',
'',
'function cmdSomeNewVerb(phaseDir) {',
' return scanPhasePlans(phaseDir).completed;',
'}',
].join('\n');
const out = drift.findCompletionPredicateDrift(text, OWNER_RELPATH);
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].shape, 'd');
assert.strictEqual(out[0].fn, 'cmdSomeNewVerb');
});
test('an unrelated .completed property on a non-scanPhasePlans object is NOT flagged', () => {
const text = [
'function cmdSomeVerb(job) {',
' const result = someOtherFunction(job);',
' return result.completed;',
'}',
].join('\n');
assert.deepStrictEqual(drift.findCompletionPredicateDrift(text, path.join('src', 'unrelated.cts')), []);
});
test('scanRepo against the real repo tree is capable of finding a deliberate shape (d) fixture (not silently swallowed)', (t) => {
const root = createTempDir('msd-completion-predicate-drift-');
t.after(() => cleanup(root));
fs.mkdirSync(path.join(root, 'src'), { recursive: true });
fs.writeFileSync(
path.join(root, 'src', 'fake-shape-d.cts'),
[
'function cmdSomeVerb(dirPath) {',
' const { completed } = scanPhasePlans(dirPath);',
' return completed;',
'}',
].join('\n'),
);
const violations = drift.scanRepo(root);
assert.strictEqual(violations.length, 1);
assert.strictEqual(violations[0].shape, 'd');
});
});
// ═════════════════════════════════════════════════════════════════════════
// D3 — the 0.x-split over-consolidation guard: plan-scan.cts does NOT
// import verification.cts (the owner consumes plan counts, never the
// reverse — ADR-3180 §7.4 HARD CONSTRAINT).
// ═════════════════════════════════════════════════════════════════════════
describe('D3 — dependency direction: plan-scan.cts does not import verification.cts', () => {
test('the compiled plan-scan.cjs source contains no reference to verification.cjs', () => {
const compiled = fs.readFileSync(path.join(ROOT, 'msd-core', 'bin', 'lib', 'plan-scan.cjs'), 'utf-8');
// allow-test-rule: structural-regression-guard — D3 reads compiled plan-scan.cjs and regex-tests it for a require() of verification.cjs; asserts a dependency-direction invariant (ADR-3180 §7.4) with no runtime/behavioral surface (see #3186)
assert.ok(!/require\(['"]\.\/verification(\.cjs)?['"]\)/.test(compiled), 'plan-scan.cjs must not require verification.cjs');
});
test('the plan-scan.cts source has no import/require of verification.cjs/.cts (a prose comment MENTIONING it, e.g. explaining why a field is not routed through it, is not an import and must not false-positive)', () => {
const source = fs.readFileSync(path.join(ROOT, 'src', 'plan-scan.cts'), 'utf-8');
assert.ok(
// allow-test-rule: structural-regression-guard — same D3 dependency-direction invariant as above, checked against the .cts source instead of the compiled .cjs (see #3186)
!/\b(?:require|import)\s*(?:\(|\{|[A-Za-z_$][\w$]*\s*=)[^;\r\n]*verification\.c(?:j|t)s/.test(source),
'src/plan-scan.cts must not import/require verification.cts/.cjs',
);
});
test('src/verification.cts DOES import plan-scan.cjs (the direction that is allowed: owner consumes counts)', () => {
// Documents the ALLOWED direction so the pair of assertions above reads
// as a genuine one-way constraint, not an accidental total decoupling.
const source = fs.readFileSync(path.join(ROOT, 'src', 'verification.cts'), 'utf-8');
// allow-test-rule: structural-regression-guard — documents the ALLOWED direction of the D3 dependency invariant: verification.cts consumes plan-scan.cjs (see #3186)
assert.ok(/require\(['"]\.\/plan-scan\.cjs['"]\)/.test(source), 'src/verification.cts is expected to import plan-scan.cjs (for staleness-check summary listing, pre-existing/unrelated to isPhaseComplete)');
});
});