Files
msd-core/tests/shipped-reference-cites.test.cjs
Tom Boucher ec7e49a64c fix(#3576): repair all 43 dead references/ cites and gate the canonical resolvable form (#3596)
* test(#3576): gate shipped reference citations on the canonical resolvable form

Failing-first gate for #3576: a backticked bare references/<name>.md cite
resolves from no install location (agents, workflows, and references all
install where a bare relative references/ path is dead). The gate walks the
runtime-loaded trees the issue prescribes, strips @~/ include tokens
PER-TOKEN (a line-skip guard would miss a bare cite sharing a line with an
include — the issue-named trap), pins the genuinely relative ../ href and
canonical forms as non-offenders, and checks canonical cite targets exist.
43 offenders today across 19 files.

* fix(#3576): repair all 43 dead references/ cites to the canonical resolvable form

Every backticked bare references/<name>.md cite across the 19 shipped files
rewritten to gsd-core/references/<name>.md — the form every required_reading
block and @~/ include already uses, and the only form that resolves from any
install location. All 20 cited targets verified to exist; the one genuinely
relative href (plan-phase.md's ../references/mvp-concepts.md) is untouched
(the repair is backtick-anchored). Growth acks: new fragment for the three
first-time paths, #3206-pattern appends to the five fragments already naming
the other grown files (two ack sources may never name the same path).
execute-phase.md lands at 93,391/93,400 and gsd-executor.md at 49,150/49,152
— exactly the issue's projections; every repair fits.

* fix(#3576): drop stale default.md growth ack (nested modes file is hash-attributed, not growth-ratcheted)

Review finding: the emitted-attribution ratchet covers only top-level
workflows/ + agents/ files; discuss-phase/modes/default.md's delta is
source-attributed, so acknowledging its growth is a stale entry the
differential lane fails on.

* chore(#3576): add changeset fragment

* chore(#3576): backfill changeset pr number

---------

Co-authored-by: sim <sim@local>
2026-08-17 15:24:24 -04:00

132 lines
5.6 KiB
JavaScript

'use strict';
// allow-test-rule: source-text-is-the-product (#3576) — this gate reads shipped
// runtime-loaded .md files and asserts on their literal citation text; the text IS
// the deployed contract, so reading it is the behavior under test.
/**
* #3576 — dead-citation gate for the shipped trees.
*
* A backticked bare `references/<name>.md` cite resolves from NO install location:
* agents install to ~/.claude/agents/, workflows to ~/.claude/gsd-core/workflows/,
* references to a sibling of workflows — a bare relative `references/` path is dead
* from every one of them. The canonical form (what every <required_reading> block
* and @~/ include already uses) is `gsd-core/references/<file>.md`.
*
* #3206 fixed one file; PR #3435 swept agents/gsd-verifier.md and stopped at its
* scope. This gate ends the class (epic #3473's B6 shape; #3518's drift guard is
* the precedent). Scope: the runtime-loaded trees the issue prescribes — agents/,
* gsd-core/{workflows,references,templates,contexts}, commands/, capabilities/.
* docs/ (incl. translations) is deliberately OUT: human-facing, per-locale drift,
* ranked lower severity by the issue — the recorded remainder.
*
* The trap the issue names: a guard that skips whole LINES containing `@~/` misses
* a bare cite sharing a line with an include — strip only the `@~/…` token.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const REPO_ROOT = path.join(__dirname, '..');
const SCAN_ROOTS = [
'agents',
'gsd-core/workflows',
'gsd-core/references',
'gsd-core/templates',
'gsd-core/contexts',
'commands',
'capabilities',
];
// A bare cite is BACKTICK-ANCHORED: `` `references/x.md` ``. The anchor is what
// excludes the genuinely relative href (`../references/…` — its backtick precedes
// `..`, not `references/`) and non-backticked prose mentions.
const BARE_CITE_RE = /`references\/([a-z0-9-]*\.md)`/g;
// The @~/ include token, stripped PER-TOKEN (never line-wise) before scanning.
const INCLUDE_TOKEN_RE = /@~\/[^\s`]+/g;
function walkShippedMarkdown() {
const files = [];
for (const root of SCAN_ROOTS) {
const rootDir = path.join(REPO_ROOT, root);
if (!fs.existsSync(rootDir)) continue;
// readdirSync returns platform-separated relative paths; normalize
// unconditionally (repo convention) so diagnostics read identically on Windows.
for (const f of fs.readdirSync(rootDir, { recursive: true })) {
const normalized = String(f).split(path.sep).join('/');
if (normalized.endsWith('.md')) files.push({ rel: `${root}/${normalized}`, abs: path.join(rootDir, f) });
}
}
return files;
}
/** Find bare cites in one document, after per-token @~/ stripping. */
function findBareCites(text) {
const stripped = text.replace(INCLUDE_TOKEN_RE, '');
const offenders = [];
let m;
while ((m = BARE_CITE_RE.exec(stripped)) !== null) {
offenders.push(`references/${m[1]}`);
}
return offenders;
}
/** Canonical `gsd-core/references/<name>` cites (backticked) — targets must exist. */
function findCanonicalCites(text) {
const re = /`gsd-core\/references\/([a-z0-9-]*\.md)`/g;
const found = [];
let m;
while ((m = re.exec(text)) !== null) found.push(m[1]);
return found;
}
describe('#3576 gate: shipped reference citations resolve', () => {
test('#3576 gate: no bare references/ cites across shipped trees', () => {
const offenders = [];
for (const { rel, abs } of walkShippedMarkdown()) {
// allow-test-rule: source-text-is-the-product (#3576) — shipped text is the runtime contract
const text = fs.readFileSync(abs, 'utf-8');
for (const cite of findBareCites(text)) {
offenders.push(`${rel}: \`${cite}\` — bare cite resolves from no install location; use \`gsd-core/${cite}\``);
}
}
assert.deepEqual(
offenders,
[],
'Bare `references/<name>.md` cites are dead pointers at runtime (#3576). '
+ 'Rewrite to the canonical `gsd-core/references/<file>.md` form:\n'
+ offenders.join('\n'),
);
});
test('#3576 gate: every canonical reference cite target exists on disk', () => {
const missing = [];
for (const { rel, abs } of walkShippedMarkdown()) {
// allow-test-rule: source-text-is-the-product (#3576) — shipped text is the runtime contract
const text = fs.readFileSync(abs, 'utf-8');
for (const name of findCanonicalCites(text)) {
if (!fs.existsSync(path.join(REPO_ROOT, 'gsd-core', 'references', name))) {
missing.push(`${rel}: \`gsd-core/references/${name}\` — target does not exist`);
}
}
}
assert.deepEqual(missing, [], 'Canonical cites must name files that exist:\n' + missing.join('\n'));
});
test('#3576 gate unit: @~/ token stripped per-token, never line-skipped; relative and canonical forms pass', () => {
const includePlusBare = 'Read @~/gsd-core/references/tdd.md and `references/tdd.md` too';
assert.deepEqual(
findBareCites(includePlusBare),
['references/tdd.md'],
'a bare cite sharing a line with an @~/ include must still be flagged (the issue-named trap)',
);
assert.deepEqual(findBareCites('see `../references/mvp-concepts.md`'), [], 'genuinely relative href is not a bare cite');
assert.deepEqual(findBareCites('see `gsd-core/references/tdd.md`'), [], 'canonical cite is not a bare cite');
assert.deepEqual(findBareCites('the references/ directory'), [], 'non-backticked prose mention is not a cite');
assert.deepEqual(findBareCites('Read @~/gsd-core/references/tdd.md now'), [], 'a lone @~/ include line is clean after stripping');
});
});