// tests/lib/run-coupling-prose-pins.test.mjs // The coupling to Claude Code's built-in `run` skill (wave 4, order A2, // decisions 54 and 64). The plan's "run the thing" step, the executor's // `### End to end` and the plan template's run step each say in one sentence // that the app is seen and driven with `run`, and that the verdict is still // the `End-to-end:` command, because `run` gives no exit code. Each part is // read by its heading or marker, never by line number. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..'); const read = (rel) => readFileSync(join(ROOT, rel), 'utf8'); /** From the line holding `marker` to the first line where `stop` is true. */ function partAt(rel, marker, stop) { const lines = read(rel).split('\n'); const start = lines.findIndex((l) => l.includes(marker)); assert.ok(start !== -1, `${rel} must hold "${marker}"`); let end = lines.length; for (let i = start + 1; i < lines.length; i++) if (stop(lines[i])) { end = i; break; } return lines.slice(start, end).join(' ').replace(/\s+/g, ' '); } const PARTS = [ // An indented paragraph of a numbered list item: it ends at a blank or unindented line. ['commands/trekplan.md', '**The "run the thing" step.**', (l) => l.trim() === '' || /^\S/.test(l)], ['commands/trekexecute.md', '### End to end — "run the thing" (P9 (6))', (l) => l.startsWith('### ')], ['templates/plan-template.md', '### Step 2: {description — the "run the thing" step}', (l) => l.startsWith('### ')], ]; // The template quotes the names instead of backticking them: every backticked // span on a template step bullet is read as a runnable example // (tests/lib/skills-criteria-prose-pins.test.mjs), and `run` is no command. const RUN = '(?:`run`|"run")'; for (const [rel, marker, stop] of PARTS) { test(`run coupling: ${rel} names Claude Code's run and keeps the verdict on the End-to-end: command`, () => { const p = partAt(rel, marker, stop); assert.match(p, new RegExp(`Claude Code's built-in ${RUN} skill`), `${rel}: the run step must name Claude Code's built-in run`); assert.match(p, /the verdict is still the `?End-to-end:`? command/, `${rel}: the verdict must stay the End-to-end: command`); assert.match(p, new RegExp(`${RUN} gives no exit code`), `${rel}: the reason must be said`); }); } test('run coupling: the run step still says what it runs, through the criteria runner', () => { const p = partAt(...PARTS[0]); assert.match(p, /criteria runner's allowlist/); assert.match(p, /End-to-end: \\`bash tests\/e2e\.sh\\`/); }); test('run coupling: the writing-criteria quiet case is owned by run, with no stand-in left in its description', () => { const md = read('evals/skill-writing-criteria-quiet-2/prompt.md'); assert.match(md, /^tags: \[[^\]]*\bowner=run\b[^\]]*\]$/m); assert.doesNotMatch(md, /stands in/); assert.doesNotMatch(md, /owner=trekexecute/); });