Files
msd-core/tests/state-field-drift.test.cjs
Tom Boucher 693f12ad56 refactor(#3187): give state field extraction one canonical owner (#3283)
* refactor(#3187): give state field extraction one canonical owner

stateFieldValue in state-document.cts becomes the single owner of the #1760
frontmatter-then-body fallback chain. The new whole-repo guard found 14
independent re-derivations where the epic scoped 5, all now routed through it:
cmdStateSnapshot (11), cmdStatePrune (2) and smart-entry fmScalar (1).

state validate was a gate that could not fail. Every warning it could emit sat
behind a phase resolved without the frontmatter tier, so a STATE.md whose phase
lives only in frontmatter skipped the drift scan entirely and returned
valid:true. It also read unstripped content, letting a frontmatter status: key
shadow the body field (#1255 class). Both fixed; output gains a scope field so
could-not-look stops being output-identical to looked-and-clean.

Verified on the remote runner.

* docs(#3187): document the state validate scope field and its reason codes

Adds docs/how-to/interpret-state-validate-results.md so a reader can tell
nothing-to-report from could-not-look, updates the COMMANDS.md and USER-GUIDE.md
entries, corrects the CONTEXT.md glossary overstatement about Current Position
sole ownership, and drops the changeset fragment.

* fix(#3187): close three drift-guard evasion shapes and test the refuse path

The isolated adversarial review found the ladder detector was evadable by
ordinary reformatting, not just deliberately: a member or computed operand
(fm.key / fm[key]) missed the bare-identifier backreference, a swapped tier
order missed a hardcoded number-then-boolean sequence, and a ladder wrapped
across lines missed single-line detection. All three now caught, each with its
own test plus a proven boundary control.

The frontmatter-parse refuse path on the destructive complete-phase route was
unreachable and therefore untested. It is now driven by an injected parse
failure and asserts STATE.md is byte-identical after the refusal, rather than
shipping untested defensive code on a path that rewrites user state.

Verified on the remote runner.

* fix(#3187): widen the drift guard to the prompt layer and disclose tier-2 changes

The code-review spec axis found the guard's scan surface was src/ only, which is
Decision 4(d)'s forbidden allowlist one directory wide - and it had a live miss:
gsd-core/workflows/smart-entry.md tells an agent to read status from frontmatter
or the body, a prose expression of this same chain. The surface now covers the
prompt layer. That one site carries a permanent written exemption rather than a
ratchet: it is the gsd-tools-is-down fallback, so it cannot call the owner by
construction, and a ratchet would imply removable debt that does not exist.

Two tier-2 output changes shipped undisclosed and are now named in the changeset
and docs: complete-phase's idempotency guard consulting frontmatter, and the
workstream inventory resolving frontmatter-only fields. docs/COMMANDS.md gains a
state complete-phase entry, which it never had.

Also records Amendment 5 on ADR-3180, extracts the duplicated frontmatter-parse
block the epic's own thesis forbids, and re-points two assertions from free-form
warning prose onto the structured drift object.

Verified on the remote runner.

* chore(#3187): backfill changeset PR number

pr:0 placeholder replaced with the real PR number now that #3283 exists.

---------

Co-authored-by: sim <sim@local>
2026-08-09 22:49:39 -04:00

650 lines
27 KiB
JavaScript

'use strict';
/**
* Tests for the STATE.md field-extraction fallback-chain drift guard
* (epic #3180, issue #3187 Phase 5, ADR-3180 §7.7) —
* `scripts/lint-state-field-drift.cjs`.
*
* Per ADR-3180 Decision 4(b) the guard AND this paired test are both
* required; neither alone is sufficient. Every test here drives the
* guard's exported PURE functions (`findStateFieldDrift`, `buildFunctionInfo`,
* `scanRepo`) with string fixtures — no temp tree is needed for anything but
* `scanRepo`'s own tree-walk contract (D1), which runs against the real repo.
*
* Covers .gsd/phase/refactor-3187-state-field-extraction-single-owner/
* 50-test-matrix.md section D, rows D1-D9 (D3b/D8b/D8c included), plus the
* multi-line-signature attribution regression the guard's own module header
* documents (`buildFunctionInfo`'s `pendingDeclName` deferral).
*
* Fixtures use array `.join('\n')`, never an indented template literal —
* indentation bleed would shift every asserted line number.
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const path = require('node:path');
const fs = require('node:fs');
const drift = require('../scripts/lint-state-field-drift.cjs');
const { sanitizeForReport } = require('../scripts/lib/drift-scan.cjs');
const REPO_ROOT = path.join(__dirname, '..');
const OWNER_RELPATH = drift.OWNER_FILE;
const OTHER_RELPATH = path.join('src', 'unrelated.cts');
// ─── D1: the real repo tree reports zero re-derivations ───────────────────
describe('D1 — scanRepo against the real repo tree', () => {
test('guard reports zero on the consolidated tree', () => {
// This is the guard's actual contract, exercised through its own
// scanRepo tree-walk (not a synthetic fixture) — the test that fails
// the day an eighth copy of the fallback chain lands anywhere in src/.
const violations = drift.scanRepo(REPO_ROOT);
assert.deepStrictEqual(violations, []);
});
});
// ─── D2: the guard MUST be able to fail ────────────────────────────────────
describe('D2 — the guard must be able to fail', () => {
test('guard catches a reintroduced ladder', () => {
const text = [
'function fmScalarLadder(fm, body) {',
' const v = fm.x;',
" if (typeof v === 'number' || typeof v === 'boolean') {",
' return String(v);',
' }',
" return stateExtractField(body, 'x');",
'}',
].join('\n');
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
// A guard that cannot fail is worse than no guard: this fixture is a
// synthetic re-derivation (coercion ladder + fallback call, same
// function), and it MUST produce exactly one violation at the exact
// line of the fallback call — not merely "an array".
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].line, 6);
assert.match(out[0].found, /stateExtractField/);
});
});
// ─── D3 / D3b: body-only case-variant chain, with and without a ladder ────
describe('D3 — body-only case-variant chain without a ladder is not drift', () => {
test('body-only case-variant chain without a ladder is not drift', () => {
const text = [
'function noLadderFunction(body) {',
" const a = stateExtractField(body, 'Last Activity') ?? stateExtractField(body, 'Last activity');",
' return a;',
'}',
].join('\n');
// No ladder anywhere in this function — a bare case-variant fallback
// chain over the body alone is not the derivation this guard owns.
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
assert.deepStrictEqual(out, []);
});
});
describe('D3b — case-variant chain beside a ladder is drift', () => {
test('case-variant chain beside a ladder is drift', () => {
const text = [
'function withLadderFunction(fm, body) {',
' const v = fm.status;',
" if (typeof v === 'number' || typeof v === 'boolean') {",
' // handled elsewhere in this same function',
' }',
" const a = stateExtractField(body, 'Last Activity') ?? stateExtractField(body, 'Last activity');",
' return a;',
'}',
].join('\n');
// The SAME case-variant chain, now inside a function that also bears
// the coercion ladder — the function must route through the owner
// instead of hand-rolling its own body-side fallback.
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].line, 6);
});
});
// ─── D3c / D3d / D3e: evasion shapes found by isolated adversarial review ──
// A previous version of LADDER_RE required a bare-identifier `\1`
// backreference and hardcoded 'number' before 'boolean' on a single line.
// All three fixtures below were verified to produce ZERO violations against
// that version despite being real re-derivation shapes (ladder +
// `stateExtractField(` in the same function).
describe('D3c — member-expression / computed operand ladder is drift', () => {
test('a dotted member-expression operand (fm.key) is caught', () => {
const text = [
'function memberOperand(fm, body) {',
" if (typeof fm.key === 'number' || typeof fm.key === 'boolean') {",
' return String(fm.key);',
' }',
" return stateExtractField(body, 'x');",
'}',
].join('\n');
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].line, 5);
});
test('a computed-access operand (fm[key]) is caught', () => {
const text = [
'function computedOperand(fm, body, key) {',
" if (typeof fm[key] === 'number' || typeof fm[key] === 'boolean') {",
' return String(fm[key]);',
' }',
" return stateExtractField(body, 'x');",
'}',
].join('\n');
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].line, 5);
});
test('a control case: two DIFFERENT member operands never counts as one ladder', () => {
// fm.a and fm.b are different operands — this must never be mistaken
// for a coercion ladder over a single value.
const text = [
'function unrelatedOperands(fm, body) {',
" if (typeof fm.a === 'number' || typeof fm.b === 'boolean') {",
' return null;',
' }',
" return stateExtractField(body, 'x');",
'}',
].join('\n');
assert.deepStrictEqual(drift.findStateFieldDrift(text, OTHER_RELPATH), []);
});
});
describe('D3d — either tier order is drift (order swap)', () => {
test("'boolean' before 'number' (the opposite of the historical example) is caught", () => {
const text = [
'function orderSwap(fm, body) {',
' const v = fm.x;',
" if (typeof v === 'boolean' || typeof v === 'number') {",
' return String(v);',
' }',
" return stateExtractField(body, 'x');",
'}',
].join('\n');
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].line, 6);
});
});
describe('D3e — a ladder split across lines, or as two separate `if` statements, is drift', () => {
test('the same `||` expression wrapped across two lines is caught', () => {
const text = [
'function splitAcrossLines(fm, body) {',
' const v = fm.x;',
" if (typeof v === 'number' ||",
" typeof v === 'boolean') {",
' return String(v);',
' }',
" return stateExtractField(body, 'x');",
'}',
].join('\n');
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].line, 7);
});
test('two separate `if` statements (no `||` at all) are caught', () => {
const text = [
'function separateIfStatements(fm, body) {',
' const v = fm.x;',
" if (typeof v === 'number') { return String(v); }",
" if (typeof v === 'boolean') { return String(v); }",
" return stateExtractField(body, 'x');",
'}',
].join('\n');
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].line, 5);
});
test('two separate `if` statements FURTHER apart than LADDER_WINDOW_LINES are NOT caught (documented bound)', () => {
// Proves LADDER_WINDOW_LINES is a real, enforced bound, not decorative —
// mirrors D8's "distance is irrelevant" proof for the OTHER (unbounded)
// pairing in this same guard, but for the ladder-clause pairing this is
// intentionally the opposite: bounded, and the bound is real.
const filler = [];
for (let i = 0; i < drift.LADDER_WINDOW_LINES + 5; i++) filler.push(` // filler line ${i}`);
const text = [
'function tooFarApart(fm, body) {',
' const v = fm.x;',
" if (typeof v === 'number') { return String(v); }",
...filler,
" if (typeof v === 'boolean') { return String(v); }",
" return stateExtractField(body, 'x');",
'}',
].join('\n');
assert.deepStrictEqual(drift.findStateFieldDrift(text, OTHER_RELPATH), []);
});
});
// ─── D4: nested frontmatter object read is not drift ───────────────────────
describe('D4 — nested frontmatter read is not drift', () => {
test('nested frontmatter read is not drift', () => {
const text = [
'function fmScalarKey(fm) {',
' const v = fm.nested ? fm.nested.key : undefined;',
" if (typeof v === 'number' || typeof v === 'boolean') {",
' return String(v);',
' }',
' return null;',
'}',
].join('\n');
// Ladder-bearing (the coercion ladder is present) but call-free — this
// is the live smart-entry.cts `fmScalarKey` control case proving the
// guard distinguishes "has a ladder" from "re-derives the fallback
// chain": a ladder alone with no stateExtractField( call in the same
// function is a different question and must stay silent.
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
assert.deepStrictEqual(out, []);
});
});
// ─── D5: comment-awareness ──────────────────────────────────────────────────
describe('D5 — documented ladder in prose is not drift', () => {
test('documented ladder in prose is not drift', () => {
const text = [
'function commented(fm, body) {',
" // typeof v === 'number' || typeof v === 'boolean' then stateExtractField(body, 'x')",
' /*',
" * typeof v === 'number' || typeof v === 'boolean'",
" * stateExtractField(body, 'x')",
' */',
' return null;',
'}',
].join('\n');
// Both a `//` line comment and a `/* */` block comment carry the exact
// ladder + call text. ADR-3180 Amendment 3: a guard that reports prose
// as drift trains readers to reflexively exempt documentation, so
// scanCode must blank both before either detection regex runs.
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
assert.deepStrictEqual(out, []);
});
});
// ─── D6 / D7: owner-file exemption is function-scoped, never file-scoped ──
describe('D6 — owner file is scanned; only named functions exempt', () => {
test('owner file is scanned; only named functions exempt', () => {
const text = [
'function stateFieldValue(fm, body, fmKey, bodyField) {',
' const v = fm[fmKey];',
" if (typeof v === 'number' || typeof v === 'boolean') {",
' return String(v);',
' }',
' return stateExtractField(body, bodyField);',
'}',
].join('\n');
// The canonical owner function itself IS the derivation by
// construction — it must stay silent when scanned under the real
// owner relPath.
const out = drift.findStateFieldDrift(text, OWNER_RELPATH);
assert.deepStrictEqual(out, []);
});
});
describe('D7 — a second copy inside the owner file is still caught', () => {
test('a second copy inside the owner file is still caught', () => {
// The exact Amendment-4 blind spot: a whole-file exemption on the
// owner is precisely how getMilestoneInfo stayed invisible to an
// earlier guard. FUNCTION_SCOPED_EXEMPTIONS must never let a SECOND,
// unrelated re-derivation elsewhere in the SAME owner file go unseen.
const text = [
'function stateFieldValue(fm, body, fmKey, bodyField) {',
' const v = fm[fmKey];',
" if (typeof v === 'number' || typeof v === 'boolean') {",
' return String(v);',
' }',
' return stateExtractField(body, bodyField);',
'}',
'',
'function stateFieldValueDuplicate(fm, body) {',
' const w = fm.other;',
" if (typeof w === 'number' || typeof w === 'boolean') {",
' return String(w);',
' }',
" return stateExtractField(body, 'other');",
'}',
].join('\n');
const out = drift.findStateFieldDrift(text, OWNER_RELPATH);
// The canonical stateFieldValue (lines 1-7) stays silent; the
// unrelated second copy (stateFieldValueDuplicate, lines 9-15) is
// flagged — proving the exemption is keyed on FUNCTION NAME, not on
// the whole file.
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].line, 14);
});
});
describe('D6/D7 combined — the same line in a non-owner file is always reported', () => {
test('the same canonical-shaped line outside the owner file is reported (no exemption applies)', () => {
const text = [
'function stateFieldValue(fm, body, fmKey, bodyField) {',
' const v = fm[fmKey];',
" if (typeof v === 'number' || typeof v === 'boolean') {",
' return String(v);',
' }',
' return stateExtractField(body, bodyField);',
'}',
].join('\n');
// FUNCTION_SCOPED_EXEMPTIONS is keyed on relPath === OWNER_RELPATH; a
// function of the SAME name in a DIFFERENT file gets no exemption at
// all.
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].line, 6);
});
});
// ─── D8 / D8b: distance and attribution are per-function, not a window ────
describe('D8 — distance within a function is irrelevant', () => {
test('distance within a function is irrelevant', () => {
const filler = [];
for (let i = 0; i < 40; i++) filler.push(` // filler line ${i} — no ladder or call text here`);
const text = [
'function farApart(fm, body) {',
' const v = fm.x;',
" if (typeof v === 'number' || typeof v === 'boolean') {",
' return String(v);',
' }',
...filler,
" return stateExtractField(body, 'x');",
'}',
].join('\n');
// No fixed line-distance window anywhere in this detection — an
// earlier draft used one and was rejected (ADR-3180 Decision 4(a)):
// it produced "a zero it did not earn" on live copies further from
// their ladder than any defensible line count. 40 filler lines here
// proves the current shape has no such ceiling.
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].line, 5 + filler.length + 1);
});
});
describe('D8b — a neighbouring function is not implicated', () => {
test('a neighbouring function is not implicated', () => {
const text = [
'function ladderOwner(fm) {',
' const v = fm.x;',
" if (typeof v === 'number' || typeof v === 'boolean') {",
' return String(v);',
' }',
' return null;',
'}',
'',
'function neighbour(body) {',
" return stateExtractField(body, 'x');",
'}',
].join('\n');
// The file, taken as a whole, carries BOTH a ladder and a
// stateExtractField( call — but they sit in two DIFFERENT named
// functions with no shared enclosing scope. Attribution is
// per-function, not per-file, so neither function alone qualifies.
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
assert.deepStrictEqual(out, []);
});
});
// ─── D8c: closure-scoped ladders (both real-world shapes) ─────────────────
describe('D8c — closure-scoped ladder is attributed correctly', () => {
test('an arrow-function closure (const fmScalar = (...) => {...}) is attributed to its enclosing function', () => {
const text = [
'function cmdStateSnapshotLike(fm, body) {',
' const fmScalar = (key) => {',
' const v = fm[key];',
" if (typeof v === 'number' || typeof v === 'boolean') return String(v);",
' return null;',
' };',
" const status = fmScalar('status') ?? stateExtractField(body, 'Status');",
' return status;',
'}',
].join('\n');
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].line, 7);
// Attribution proof: the call line's innermost open frame is the
// OUTER named function, not the closure (which has already closed by
// that line) — and the ladder marks the outer frame transitively,
// because the closure's body is lexically part of the outer
// function's own body.
const lines = text.split('\n');
const { ladderBearing, innermostAt } = drift.buildFunctionInfo(lines);
assert.strictEqual(innermostAt[6], 'cmdStateSnapshotLike'); // 0-indexed line 7
assert.ok(ladderBearing.has('cmdStateSnapshotLike'));
});
test('a nested function-declaration closure (function fmScalar(...) {...}) is attributed to its enclosing function', () => {
const text = [
'function outerWithNestedFn(fm, body) {',
' function fmScalarNested(key) {',
' const v = fm[key];',
" if (typeof v === 'number' || typeof v === 'boolean') return String(v);",
' return null;',
' }',
" const status = fmScalarNested('status') ?? stateExtractField(body, 'Status');",
' return status;',
'}',
].join('\n');
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].line, 7);
const lines = text.split('\n');
const { ladderBearing, innermostAt } = drift.buildFunctionInfo(lines);
assert.strictEqual(innermostAt[6], 'outerWithNestedFn'); // 0-indexed line 7
assert.ok(ladderBearing.has('outerWithNestedFn'));
});
});
// ─── Regression: multi-line function signature must not strand its frame ──
// buildFunctionInfo's module-header documents a real bug: FUNCTION_DECL_RE
// can match a line with no `{` at all (a signature spilling across several
// lines); pushing that frame immediately recorded `openDepth` at the
// ENCLOSING scope's depth rather than the function's own, so the frame could
// NEVER pop and silently misattributed every later top-level line to it.
// `pendingDeclName` defers the push to the line whose brace count actually
// increases. 229 functions across 76 files share this multi-line-signature
// shape, so this proof is load-bearing, not decorative.
describe('regression — a multi-line function signature does not strand its frame', () => {
test('a ladder+call inside a function whose signature spans several lines is flagged and attributed there; a later top-level call is not', () => {
const text = [
'function preferNewerLike(',
' fm,',
' body,',
' fmKey',
')',
'{',
' const v = fm[fmKey];',
" if (typeof v === 'number' || typeof v === 'boolean') {",
' return String(v);',
' }',
' return stateExtractField(body, fmKey);',
'}',
'',
"stateExtractField(body, 'decoy');",
].join('\n');
const out = drift.findStateFieldDrift(text, OTHER_RELPATH);
// Only the in-function call (line 11) is flagged. If the frame had
// been stranded open (the pre-fix bug), the decoy top-level call on
// line 14 would ALSO be flagged — this assertion is the whole proof
// the fix holds.
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].line, 11);
const lines = text.split('\n');
const { innermostAt } = drift.buildFunctionInfo(lines);
assert.strictEqual(innermostAt[10], 'preferNewerLike'); // 0-indexed line 11
assert.strictEqual(innermostAt[13], null); // 0-indexed line 14 — module scope, unattributed
});
});
// ─── D9: report-output sanitization ────────────────────────────────────────
// `main()` sanitizes both a violation's file path and its matched fragment
// through `sanitizeForReport` (scripts/lib/drift-scan.cjs) before writing
// either to stderr — the exact boundary a fork-authored filename or source
// line reaches a CI log through. Direct coverage of the sanitizer itself,
// mirroring tests/plan-count-single-owner.test.cjs's `sanitizeForReport`
// describe block.
describe('D9 — report output is sanitized', () => {
test('C0 control bytes are escaped', () => {
assert.strictEqual(sanitizeForReport(String.fromCharCode(0x1b)), '\\x1b');
});
test('DEL (0x7f) is escaped', () => {
assert.strictEqual(sanitizeForReport(String.fromCharCode(0x7f)), '\\x7f');
});
test('C1 control bytes are escaped', () => {
assert.strictEqual(sanitizeForReport(String.fromCharCode(0x9b)), '\\x9b');
});
test('zero-width space (U+200B) is escaped', () => {
assert.strictEqual(sanitizeForReport(String.fromCharCode(0x200b)), '\\u200b');
});
test('Unicode LINE SEPARATOR (U+2028) is escaped', () => {
assert.strictEqual(sanitizeForReport(String.fromCharCode(0x2028)), '\\u2028');
});
test('Unicode PARAGRAPH SEPARATOR (U+2029) is escaped', () => {
assert.strictEqual(sanitizeForReport(String.fromCharCode(0x2029)), '\\u2029');
});
test('a bidi RIGHT-TO-LEFT OVERRIDE (U+202E) is escaped', () => {
assert.strictEqual(sanitizeForReport(String.fromCharCode(0x202e)), '\\u202e');
});
test('ordinary ASCII filename/fragment text passes through unchanged', () => {
const text = "src/state-document.cts:296 stateExtractField(body, 'x')";
assert.strictEqual(sanitizeForReport(text), text);
});
});
// ─── D10: prompt-layer prose detection (ADR-3180 Decision 4(d)) ───────────
// Widening the scan surface from `src/` alone to also cover the prompt-layer
// markdown (`gsd-core/workflows`, `commands`, `agents`, `skills`) is unproven
// unless (a) the surface is genuinely declared, and (b) a NEW prose
// re-derivation added there is genuinely caught — not merely that the guard
// happens to report zero because nothing was ever exercised against it.
describe('D10a — the prompt layer is in the scan surface', () => {
test('SCAN_DIRS covers gsd-core/workflows, commands, agents, skills (in addition to src)', () => {
assert.ok(drift.SCAN_DIRS.includes('src'));
assert.ok(drift.SCAN_DIRS.includes('gsd-core/workflows'));
assert.ok(drift.SCAN_DIRS.includes('commands'));
assert.ok(drift.SCAN_DIRS.includes('agents'));
assert.ok(drift.SCAN_DIRS.includes('skills'));
});
test('SCAN_EXT covers markdown', () => {
assert.ok(drift.SCAN_EXT.has('.md'));
});
});
describe('D10b — a NEW prose re-derivation in a workflow file is flagged', () => {
const OTHER_WORKFLOW_RELPATH = path.join('gsd-core', 'workflows', 'unrelated-workflow.md');
test('a synthetic workflow line carrying the fm-key/body-bold tokens plus both words is caught', () => {
const text = [
'# Some Workflow',
'',
'1. Read STATE.md. Extract: `owner` (frontmatter `owner:` or body `**Owner:**`), and proceed.',
'',
].join('\n');
const out = drift.findPromptFieldDrift(text, OTHER_WORKFLOW_RELPATH);
assert.strictEqual(out.length, 1);
assert.strictEqual(out[0].line, 3);
assert.match(out[0].found, /frontmatter/);
});
test('control case: the backtick tokens alone, with no bold body token, are not flagged', () => {
const text = '1. Read STATE.md. Extract: `owner` (frontmatter `owner:` or plain body Owner field), and proceed.';
const out = drift.findPromptFieldDrift(text, OTHER_WORKFLOW_RELPATH);
assert.deepStrictEqual(out, []);
});
test('control case: both tokens present but the word "frontmatter" absent is not flagged', () => {
const text = '1. Extract: `owner` (config `owner:` or body `**Owner:**`).';
const out = drift.findPromptFieldDrift(text, OTHER_WORKFLOW_RELPATH);
assert.deepStrictEqual(out, []);
});
test('control case: both tokens and "frontmatter" present but the word "body" absent is not flagged', () => {
const text = '1. Extract: `owner` (frontmatter `owner:` or fallback `**Owner:**`).';
const out = drift.findPromptFieldDrift(text, OTHER_WORKFLOW_RELPATH);
assert.deepStrictEqual(out, []);
});
});
describe('D10c — the smart-entry.md gsd-tools-down fallback is a written, permanent exemption', () => {
const SMART_ENTRY_RELPATH = path.join('gsd-core', 'workflows', 'smart-entry.md');
const LIVE_LINE =
"- Read `.planning/STATE.md` (frontmatter + body) with the Read tool. Extract: `status` (frontmatter `status:` or body `**Status:**`), `Phase:` from the body, `total_phases`/`percent` from a nested `progress:` frontmatter object if present, and any `## Blockers` items.";
test('the real live line is caught when NOT under the exempted relPath (proves detection genuinely fires on this exact text — it is not silent by construction)', () => {
const out = drift.findPromptFieldDrift(LIVE_LINE, path.join('gsd-core', 'workflows', 'unrelated-workflow.md'));
assert.strictEqual(out.length, 1);
});
test('the SAME line under the real smart-entry.md relPath is exempted (silent), per the written PROMPT_LAYER_EXEMPTIONS reason', () => {
const out = drift.findPromptFieldDrift(LIVE_LINE, SMART_ENTRY_RELPATH);
assert.deepStrictEqual(out, []);
// The exemption carries a written reason (Decision 4(d)) — not a bare
// suppression — and is NOT a Decision 4(e) ratchet: this site cannot be
// migrated onto the owner by construction, so there is no removal issue
// to key it against.
const reasons = drift.PROMPT_LAYER_EXEMPTIONS.get(SMART_ENTRY_RELPATH);
assert.ok(reasons.has(LIVE_LINE.trim()));
assert.match(reasons.get(LIVE_LINE.trim()), /gsd-tools|cannot run|by construction/i);
});
test('the exemption is keyed on exact trimmed text, not merely the file — a DIFFERENT re-derivation in the SAME file is still caught', () => {
const text = '- Extract: `owner` (frontmatter `owner:` or body `**Owner:**`).';
const out = drift.findPromptFieldDrift(text, SMART_ENTRY_RELPATH);
assert.strictEqual(out.length, 1);
});
test('the real smart-entry.md file on disk scans clean end-to-end (the actual contract, not just the extracted fixture)', () => {
const content = fs.readFileSync(path.join(REPO_ROOT, 'gsd-core', 'workflows', 'smart-entry.md'), 'utf8');
const out = drift.findPromptFieldDrift(content, SMART_ENTRY_RELPATH);
assert.deepStrictEqual(out, []);
});
});