Files
msd-core/tests/tdd-backend-wiring.test.cjs
Tom Boucher 747a3730d4 fix(#4268): harden tdd-single-statement.test.cjs against reworded restatements and backend divergence (#4297)
* test(#4268): harden reworded-restatement and backend-predicate-divergence detection

tests/tdd-single-statement.test.cjs's restatesCycle() keyed on the exact
literal `commit: `test({phase}-{plan})`` substring, so a reworded restatement
of the RED/GREEN/REFACTOR procedure shipped green. Adds
restatesCycleStructurally(), a structural (span + list-marker) detector that
stays linear-scan (per the #4228 catastrophic-backtracking incident this must
not reintroduce) and is proven, empirically, to flag a paraphrased multi-step
fixture while not flagging the real compact citations in execute-plan.md and
gsd-executor.md (#4267's legitimate pointers).

tests/tdd-backend-wiring.test.cjs never compared the two dispatch backends'
`gsd_run query phase.tdd-applicable` calls against each other, so a one-word
divergence between them (e.g. a changed --pick flag in only one backend)
shipped green. Adds a byte-identity assertion on the command-substitution
content (normalized for the two backends' differing variable-name prefixes),
proven to have teeth via a RED-first mutation check before asserting it
against the real files.

The third gap in #4268 (nothing proves TDD_APPLICABLE has a real definition)
was already covered by this file's existing assertTddApplicableIsComputed
(epic #4272 Phase 2, #4266) — verified by inspection, no new test needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(#4268): redesign restatement detector around deferral, not length

Standards+Spec review of the prior commit proved by execution that a
compact, no-list-marker restatement (under the 200-char span threshold)
sails past the span/list-marker-only signal. Redesigns the primary check:
the actual invariant is deferral, not length — a legitimate RED/GREEN/
REFACTOR mention always names tdd.md as the authority nearby, a restatement
never does. Flags when no tdd.md/canonical reference appears within a
500-char trailing window past the cycle mention, regardless of length or
list-marker shape; keeps span>200 and three-distinct-list-marker-lines as
secondary defense-in-depth OR-conditions. Verified independently against
both real files (execute-plan.md span=10, gsd-executor.md span=14, both with
a nearby deferral marker at +228/+82 chars) — no false positive, and the
reviewer's exact gap class (a 189-char no-citation paraphrase) is now
flagged.

Also fixes: boundary coverage at the span threshold (199/200/201, isolated
via a factored-out measureCycleSpan() helper), a fast-check property test
proving the fix holds for arbitrary filler text, and a fragile line-match in
tdd-backend-wiring.test.cjs that happened to work only because a FATAL echo
message containing the same substring came later in document order than the
real assignment line.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 19:48:14 -04:00

274 lines
15 KiB
JavaScript

'use strict';
/**
* #4264/#4265/#4266 (epic #4272 Phase 2) — TDD_APPLICABLE is USED (as a
* `${TDD_APPLICABLE ? ... : ...}` ternary, per #3990) in both executor
* dispatch backends but was never ASSIGNED anywhere: `phase.tdd-applicable`
* (Phase 1, #4273) computed the predicate, but no workflow ever called it.
* That is a silent "absence resolves to false" defect (ADR-3473 §8.4) — an
* un-substituted `${TDD_APPLICABLE ...}` marker is JS-truthy as literal text,
* so a real TDD plan would have silently dropped its RED/GREEN/REFACTOR
* procedure while never producing an error.
*
* These are shape assertions on the raw workflow-markdown TEXT: the deployed
* text IS the runtime-loaded product (an LLM orchestrator reads it top to
* bottom and composes/executes it) — same rationale as
* tdd-single-statement.test.cjs's header comment. `readFileSync` here targets
* `.md` files only, never a `.cjs`/`.js`/`.ts` source path, so
* `local/no-source-grep` does not apply and no `allow-test-rule` marker is
* needed (confirmed against tdd-single-statement.test.cjs, which reads the
* same two files with no such marker).
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const ROOT = path.join(__dirname, '..');
const read = (p) => fs.readFileSync(path.join(ROOT, p), 'utf8');
const HARNESS_PATH = 'gsd-core/workflows/execute-phase.md';
const WORKTREE_PATH = 'gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md';
const TDD_STEP_FRAGMENT_PATH = 'gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md';
// #4272-phase-6 byte-ceiling extraction (two commits ago): the harness
// backend's TDD_APPLICABLE assignment + phase.tdd-applicable call moved OUT
// of execute-phase.md and into the tdd-applicability-resolution.md step
// fragment; execute-phase.md itself now carries only a one-line REFERENCE to
// that fragment. The worktree backend was not affected — it still carries
// everything inline in executor-isolation-dispatch.md. `referenceFile` is
// where the reader/orchestrator encounters the backend (and, for the
// harness, where the one-line pointer + the ${TDD_APPLICABLE...} USE live);
// `assignmentFile` is where TDD_APPLICABLE is actually ASSIGNED.
const BACKENDS = {
'execute-phase.md': { referenceFile: HARNESS_PATH, assignmentFile: TDD_STEP_FRAGMENT_PATH },
'executor-isolation-dispatch.md': { referenceFile: WORKTREE_PATH, assignmentFile: WORKTREE_PATH },
};
// #4268: the two backends' `gsd_run query phase.tdd-applicable` calls differ
// today ONLY in their variable-name prefix (`TDD_APPLICABLE_RAW=$(...)` vs
// `_TDD_APPLICABLE_RAW=$(...)`), and nothing asserts the command-substitution
// CONTENT itself stays byte-identical — a one-word divergence (e.g. dropping
// `2>/dev/null` or changing `--pick applicable` in only one backend) ships
// green. This extracts the call starting at `gsd_run query
// phase.tdd-applicable` through the end of the line, stripping only the
// trailing `)` that closes the `$( ... )` command substitution — i.e.
// everything up to and including the variable-name-and-`=$(` prefix is
// discarded, exactly per #4268.
function extractQueryCall(line) {
const start = line.indexOf('gsd_run query phase.tdd-applicable');
if (start === -1) return null;
let call = line.slice(start);
if (call.endsWith(')')) call = call.slice(0, -1);
return call;
}
describe('#4266 — TDD_APPLICABLE is actually computed in both backends', () => {
// A backend may assign TDD_APPLICABLE directly from the `gsd_run query
// phase.tdd-applicable` call (harness: `TDD_APPLICABLE=$(gsd_run query
// phase.tdd-applicable ...)`) or via an intermediate raw/rc pair mirroring
// the existing ISOLATION resolution pattern (worktree: `_TDD_APPLICABLE_RAW
// =$(gsd_run query phase.tdd-applicable ...)` ... `TDD_APPLICABLE=
// "$_TDD_APPLICABLE_RAW"`) — either shape is fine as long as the assignment
// exists AND is fed by a real phase.tdd-applicable call within a small
// preceding window, not a bare/silent default.
function assertTddApplicableIsComputed(name, filePath) {
const text = read(filePath);
const lines = text.split('\n');
const assignIdx = lines.findIndex((l) => /^\s*TDD_APPLICABLE=/.test(l));
assert.ok(assignIdx !== -1, `${name} must assign TDD_APPLICABLE, not just reference it`);
const windowStart = Math.max(0, assignIdx - 8);
const window = lines.slice(windowStart, assignIdx + 1).join('\n');
assert.ok(
window.includes('phase.tdd-applicable'),
`${name}'s TDD_APPLICABLE assignment must be fed by a phase.tdd-applicable call within a few lines above it, got window: ${window}`,
);
}
test('harness backend (execute-phase.md) assigns TDD_APPLICABLE via phase.tdd-applicable', () => {
// Assignment now lives in the extracted tdd-applicability-resolution.md
// step fragment, not in execute-phase.md itself (#4272-phase-6).
assertTddApplicableIsComputed('execute-phase.md', BACKENDS['execute-phase.md'].assignmentFile);
});
test('worktree backend (executor-isolation-dispatch.md) assigns TDD_APPLICABLE via phase.tdd-applicable', () => {
assertTddApplicableIsComputed('executor-isolation-dispatch.md', BACKENDS['executor-isolation-dispatch.md'].assignmentFile);
});
for (const [name, cfg] of Object.entries(BACKENDS)) {
test(`${name}'s phase.tdd-applicable call is plan-scoped, not phase-scoped`, () => {
const text = read(cfg.assignmentFile);
const callLine = text.split('\n').find((l) => l.includes('phase.tdd-applicable') && l.includes('gsd_run query'));
assert.ok(callLine, `${name} must carry a gsd_run query phase.tdd-applicable call`);
assert.ok(
callLine.includes('{phase_dir}') && callLine.includes('{plan_file}'),
`${name}'s phase.tdd-applicable call must pass the per-plan {phase_dir}/{plan_file} path, got: ${callLine.trim()}`,
);
});
}
test('both backends assign TDD_APPLICABLE before every ${TDD_APPLICABLE...} use (document order)', () => {
// Worktree backend: assignment and every ${TDD_APPLICABLE...} use live
// in the same file — same-file ordering check.
{
const text = read(BACKENDS['executor-isolation-dispatch.md'].assignmentFile);
const lines = text.split('\n');
const assignIdx = lines.findIndex((l) => /^\s*TDD_APPLICABLE=/.test(l));
assert.ok(assignIdx !== -1, 'executor-isolation-dispatch.md has no TDD_APPLICABLE assignment');
const useIndices = [];
lines.forEach((l, i) => {
if (/\$\{TDD_APPLICABLE\b/.test(l)) useIndices.push(i);
});
assert.ok(useIndices.length > 0, 'executor-isolation-dispatch.md has no ${TDD_APPLICABLE...} use to check ordering against');
for (const useIdx of useIndices) {
assert.ok(
assignIdx < useIdx,
`executor-isolation-dispatch.md: TDD_APPLICABLE is used at line ${useIdx + 1} before it is assigned at line ${assignIdx + 1}`,
);
}
}
// Harness backend: the assignment was extracted into
// tdd-applicability-resolution.md (#4272-phase-6); execute-phase.md no
// longer contains the assignment at all, only a one-line REFERENCE to
// where it's resolved followed (later) by the ${TDD_APPLICABLE...} USE.
// Confirm the fragment does assign it, and that in execute-phase.md the
// reference precedes every use.
{
const fragmentText = read(BACKENDS['execute-phase.md'].assignmentFile);
assert.ok(
/^\s*TDD_APPLICABLE=/m.test(fragmentText),
'tdd-applicability-resolution.md has no TDD_APPLICABLE assignment',
);
const text = read(BACKENDS['execute-phase.md'].referenceFile);
const lines = text.split('\n');
const refIdx = lines.findIndex(
(l) => l.includes('tdd-applicability-resolution.md') && l.includes('TDD-applicability resolution'),
);
assert.ok(refIdx !== -1, 'execute-phase.md must carry a one-line reference to tdd-applicability-resolution.md');
const useIndices = [];
lines.forEach((l, i) => {
if (/\$\{TDD_APPLICABLE\b/.test(l)) useIndices.push(i);
});
assert.ok(useIndices.length > 0, 'execute-phase.md has no ${TDD_APPLICABLE...} use to check ordering against');
for (const useIdx of useIndices) {
assert.ok(
refIdx < useIdx,
`execute-phase.md: TDD_APPLICABLE is used at line ${useIdx + 1} before the reference to its resolution at line ${refIdx + 1}`,
);
}
}
});
test('both tdd.md embed-line comments describe the same three real sources (#4265)', () => {
for (const [name, cfg] of Object.entries(BACKENDS)) {
const text = read(cfg.referenceFile);
const line = text.split('\n').find((l) => /tdd\.md/.test(l) && /TDD_APPLICABLE \?/.test(l));
assert.ok(line, `${name} still lists tdd.md as a conditional embed entry`);
assert.ok(line.includes('type: tdd'), `${name}'s tdd.md comment must mention plan type: tdd, got: ${line.trim()}`);
assert.ok(line.includes('tdd="true"'), `${name}'s tdd.md comment must mention the tdd="true" task attribute (#4265), got: ${line.trim()}`);
assert.ok(
line.includes('workflow.tdd_mode') || line.includes('config'),
`${name}'s tdd.md comment must mention the workflow.tdd_mode config default, got: ${line.trim()}`,
);
}
});
test('worktree backend fail-closes on an un-substituted ${TDD_APPLICABLE marker, matching the ${AGENT_SKILLS} check shape', () => {
const text = read(WORKTREE_PATH);
const lines = text.split('\n');
const skillsIdx = lines.findIndex((l) => l.includes("grep -q '\\${AGENT_SKILLS}'"));
assert.ok(skillsIdx !== -1, 'worktree backend must still carry the existing ${AGENT_SKILLS} fail-closed check');
const tddCheckIdx = lines.findIndex((l) => l.includes("grep -q '\\${TDD_APPLICABLE"));
assert.ok(tddCheckIdx !== -1, 'worktree backend must carry a fail-closed grep check for an unresolved ${TDD_APPLICABLE marker');
// Same shape: FATAL message + exit 1 within the following couple of lines.
const block = lines.slice(tddCheckIdx, tddCheckIdx + 4).join('\n');
assert.ok(/FATAL:/.test(block), 'the ${TDD_APPLICABLE check must emit a FATAL message like the ${AGENT_SKILLS} check');
assert.ok(/exit 1/.test(block), 'the ${TDD_APPLICABLE check must exit 1 like the ${AGENT_SKILLS} check');
});
test('harness backend references the tdd-applicability-resolution step fragment before Agent() is called, and that fragment states a MANDATORY pre-dispatch halt covering TDD_APPLICABLE, CONTEXT_WINDOW, and AGENT_SKILLS', () => {
// #4272-phase-6 byte-ceiling extraction: the "MANDATORY pre-dispatch check"
// paragraph moved out of execute-phase.md into
// tdd-applicability-resolution.md (mirroring per-plan-executor-routing.md);
// the host file now carries only a one-line reference to it.
const text = read(HARNESS_PATH);
const lines = text.split('\n');
// The literal executor dispatch call sits alone on its own line
// (` Agent(`), inside the per-plan spawn section (step 3) — distinct
// from the many inline `` `Agent(...)` `` prose mentions elsewhere in
// this file (e.g. the runtime-compatibility table at line ~22).
const agentCallIdx = lines.findIndex((l) => /^\s*Agent\($/.test(l));
assert.ok(agentCallIdx !== -1, 'execute-phase.md must contain the literal Agent( dispatch-call line');
const before = lines.slice(0, agentCallIdx).join('\n');
assert.ok(
before.includes('execute-phase/steps/tdd-applicability-resolution.md'),
'execute-phase.md must reference the tdd-applicability-resolution.md step fragment before the Agent() call',
);
const fragment = read(TDD_STEP_FRAGMENT_PATH);
const checkIdx = fragment.indexOf('MANDATORY');
assert.ok(checkIdx !== -1, 'tdd-applicability-resolution.md must state a MANDATORY pre-dispatch check');
const checkText = fragment.slice(checkIdx);
for (const marker of ['TDD_APPLICABLE', 'CONTEXT_WINDOW', 'AGENT_SKILLS']) {
assert.ok(checkText.includes(marker), `pre-dispatch check must name ${marker}`);
}
assert.ok(/HALT/i.test(checkText), 'pre-dispatch check must instruct an explicit halt, not a soft warning');
});
test('regression: tdd-single-statement.test.cjs assertions still hold by inspection', () => {
for (const [name, cfg] of Object.entries(BACKENDS)) {
const text = read(cfg.referenceFile);
const line = text.split('\n').find((l) => /tdd\.md/.test(l) && /TDD_APPLICABLE/.test(l));
assert.ok(line, `${name} still lists tdd.md as a conditional embed entry`);
assert.ok(/TDD_APPLICABLE \?/.test(line), `${name}'s tdd.md entry must stay conditional on TDD_APPLICABLE (#3990)`);
}
});
test('RED-first: the predicate-equality comparison actually has teeth on a one-token divergence', () => {
// Prove the comparison catches a divergence BEFORE trusting it against
// the real files — a deliberately mutated in-memory pair, no file edits.
const original = 'gsd_run query phase.tdd-applicable "{phase_dir}/{plan_file}" --pick applicable 2>/dev/null';
const mutated = original.replace('--pick applicable', '--pick other');
assert.notEqual(mutated, original, 'sanity: the mutated fixture must actually differ from the original');
assert.throws(
() => assert.equal(mutated, original),
(err) => err instanceof assert.AssertionError,
'assert.equal must fail on a one-token divergence between two backends\' predicate calls — otherwise the real-file check below has no teeth',
);
});
test('both backends\' gsd_run query phase.tdd-applicable command-substitution calls are byte-identical', () => {
// #4268 Standards review: both real backend files also carry this exact
// substring inside an unrelated FATAL echo message a few lines after the
// real assignment line (`echo "FATAL: ... 'gsd_run query
// phase.tdd-applicable' failed. ..."`). A bare `.includes()` match
// happened to work only because `.find()` hits the assignment line
// first in document order. Anchor on `=$(` immediately before the call —
// only the real `..._RAW=$(gsd_run query phase.tdd-applicable ...)`
// assignment line has that shape; the FATAL message's `'gsd_run query
// phase.tdd-applicable' failed` is preceded by a quote, not `=$(`.
const harnessLine = read(BACKENDS['execute-phase.md'].assignmentFile)
.split('\n')
.find((l) => l.includes('=$(gsd_run query phase.tdd-applicable'));
const worktreeLine = read(BACKENDS['executor-isolation-dispatch.md'].assignmentFile)
.split('\n')
.find((l) => l.includes('=$(gsd_run query phase.tdd-applicable'));
assert.ok(harnessLine, 'execute-phase.md backend must carry a gsd_run query phase.tdd-applicable call');
assert.ok(worktreeLine, 'executor-isolation-dispatch.md backend must carry a gsd_run query phase.tdd-applicable call');
const harnessCall = extractQueryCall(harnessLine);
const worktreeCall = extractQueryCall(worktreeLine);
assert.equal(
harnessCall,
worktreeCall,
'#4268: the harness and worktree backends must issue byte-identical phase.tdd-applicable command-substitution calls ' +
'(only the variable-name-and-`=$(` prefix may differ), got:\n' +
` execute-phase.md (harness): ${harnessCall}\n` +
` executor-isolation-dispatch.md (worktree): ${worktreeCall}`,
);
});
});