eb88138d15
Five parallel lanes plus an integration pass. The header, gallery, router and sitemap are all generated from the demo registry, so adding src/demos/<slug>/ puts a demo everywhere with zero edits to shared files — which is the whole reason demo nine cannot break demo one. check-demos enforces the twelve contract rules: 142 checks over one live demo. Two worth naming. The shell may not mention a specific slug, because an 'if (slug === wordle)' in src/components/demo/ is a contract bug wearing a patch. And a spec-status demo must ship a real specification — task, actions, grader, counterweight, eval command — since a coming-soon card reads worse than an honest empty gallery. Bundle budget holds: entry 108.79 kB gzipped against a 160 kB ceiling, the demo chunk 21.15 kB against 90 kB. recharts is 108 kB gzipped and lives behind a lazy import so it never touches the entry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019mt6sHQHEnEYrJZvoMCJSB
197 lines
7.3 KiB
JavaScript
197 lines
7.3 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* The receipt checker.
|
|
*
|
|
* Every demo quotes the code that computes its reward, verbatim, next to the
|
|
* number that code produced. That quote is the page's whole claim to being
|
|
* evidence rather than marketing, so the thing it quotes has to exist and the
|
|
* region it quotes has to resolve. A receipt panel that silently renders empty,
|
|
* or renders the wrong forty lines because someone inserted a function above
|
|
* the marker, is worse than no receipt at all.
|
|
*
|
|
* ── MARKER SYNTAX ──────────────────────────────────────────────────────────
|
|
*
|
|
* `RewardSpec.source` is `{ path, code, marker? }`. Without a `marker` the
|
|
* whole file is the receipt. With one, the receipt is the region delimited by
|
|
* a matched pair of comment lines in the source file itself:
|
|
*
|
|
* # region: pig-demo/<marker>
|
|
* ... the quoted lines ...
|
|
* # endregion: pig-demo/<marker>
|
|
*
|
|
* Rules, all enforced here:
|
|
*
|
|
* · The marker lines are EXCLUSIVE — neither appears in the quoted region.
|
|
* · EXACTLY ONE pair per file per marker. Zero is a broken receipt; two or
|
|
* more is ambiguous, and an ambiguous receipt silently quotes whichever
|
|
* region the reader's implementation happened to find first. Both are
|
|
* fatal.
|
|
* · `region` must come before `endregion`, and the region must be non-empty
|
|
* once the marker lines are removed.
|
|
* · The comment token is `#` (Python) or `//` (TypeScript), then optional
|
|
* whitespace. Everything else on the line is ignored, so
|
|
* `# region: pig-demo/consistency (see PR #41)` is legal.
|
|
* · The `pig-demo/` prefix is required. It is what makes these greppable and
|
|
* stops an editor's own `#region` folding markers from being read as
|
|
* receipts.
|
|
*
|
|
* Marker names are `[a-z0-9][a-z0-9-]*`: they end up in a grep, a CI message
|
|
* and a code comment, and mixed case in all three is a bug factory.
|
|
*/
|
|
|
|
import path from 'node:path';
|
|
|
|
import {
|
|
Report,
|
|
abs,
|
|
allDemoDirs,
|
|
demoFiles,
|
|
evalLiteral,
|
|
exists,
|
|
isUnresolved,
|
|
literalsAfter,
|
|
read,
|
|
rel,
|
|
} from './_lib.mjs';
|
|
|
|
const report = new Report('check-receipts');
|
|
|
|
const RULE_PATH = 'reward.source.path';
|
|
const RULE_MARKER = 'reward.source.marker';
|
|
|
|
const MARKER_NAME = /^[a-z0-9][a-z0-9-]*$/;
|
|
const markerLine = (kind, marker) =>
|
|
new RegExp(`^[ \\t]*(?:#|//)[ \\t]*${kind}:[ \\t]*pig-demo/${marker.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![\\w-])`, 'm');
|
|
|
|
/** Every `source: { ... }` literal in a demo, wherever the demo chose to put it. */
|
|
function receipts(slug) {
|
|
const out = [];
|
|
for (const file of demoFiles(slug)) {
|
|
const src = read(file);
|
|
for (const literal of literalsAfter(src, /\bsource\s*:\s*/, '{')) {
|
|
// `code` is legitimately an identifier here — the Python arrives through
|
|
// a Vite `?raw` import, which cannot resolve in plain Node — so the
|
|
// literal is read leniently and only `path` and `marker` are trusted.
|
|
const evaluated = evalLiteral(literal.text, 'RewardSpec.source', { lenient: true });
|
|
if (!evaluated.ok) {
|
|
out.push({ file, error: evaluated.error });
|
|
continue;
|
|
}
|
|
const value = evaluated.value;
|
|
if (!value || typeof value !== 'object' || !('path' in value)) continue; // some other `source:` key
|
|
const line = src.slice(0, literal.start).split('\n').length;
|
|
out.push({ file, line, value });
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
const slugs = allDemoDirs().filter((s) => !s.startsWith('_'));
|
|
if (slugs.length === 0) {
|
|
console.log('check-receipts: no demos to check.');
|
|
process.exit(0);
|
|
}
|
|
|
|
for (const slug of slugs) {
|
|
const found = receipts(slug);
|
|
|
|
if (found.length === 0) {
|
|
report.fail(
|
|
rel(path.join(abs('src', 'demos'), slug)),
|
|
RULE_PATH,
|
|
'no `reward.source` found. Every demo quotes the code that computes its reward; without a source ' +
|
|
'the receipt panel has nothing to show and the page is asserting its numbers rather than proving them.',
|
|
);
|
|
continue;
|
|
}
|
|
|
|
for (const receipt of found) {
|
|
const where = `${rel(receipt.file)}${receipt.line ? `:${receipt.line}` : ''}`;
|
|
if (receipt.error) {
|
|
report.fail(where, RULE_PATH, receipt.error);
|
|
continue;
|
|
}
|
|
const { path: sourcePath, marker } = receipt.value;
|
|
|
|
if (typeof sourcePath !== 'string' || sourcePath.trim() === '') {
|
|
report.fail(
|
|
where,
|
|
RULE_PATH,
|
|
`\`path\` is ${isUnresolved(sourcePath) ? 'an imported constant' : JSON.stringify(sourcePath)}. ` +
|
|
'It must be a literal repo-relative path, e.g. "envs/wordle_five/wordle_five/rubric.py", ' +
|
|
'so a reader can open the same file this page quotes.',
|
|
);
|
|
continue;
|
|
}
|
|
|
|
// Repo-relative by contract. A leading slash would mean the site root,
|
|
// which is not where the Python lives, and an absolute disk path would
|
|
// only work on the machine that wrote it.
|
|
const clean = sourcePath.replace(/^\.\//, '');
|
|
if (path.isAbsolute(clean)) {
|
|
report.fail(where, RULE_PATH, `\`path\` is absolute ("${sourcePath}"). Use a repo-relative path.`);
|
|
continue;
|
|
}
|
|
const onDisk = abs(clean);
|
|
if (!report.check(exists(onDisk), where, RULE_PATH, `\`path\` is "${sourcePath}", and ${rel(onDisk)} does not exist.`)) {
|
|
continue;
|
|
}
|
|
|
|
if (marker === undefined || marker === null) continue; // whole file is the receipt
|
|
|
|
if (typeof marker !== 'string' || !MARKER_NAME.test(marker)) {
|
|
report.fail(
|
|
where,
|
|
RULE_MARKER,
|
|
`\`marker\` is ${JSON.stringify(marker)}. Markers are lower-kebab-case (${MARKER_NAME}) because they ` +
|
|
'appear in a grep, a CI message and a code comment, and mixed case in all three is a bug factory.',
|
|
);
|
|
continue;
|
|
}
|
|
|
|
const body = read(onDisk);
|
|
const lines = body.split('\n');
|
|
const opens = [];
|
|
const closes = [];
|
|
lines.forEach((line, i) => {
|
|
if (markerLine('region', marker).test(line)) opens.push(i);
|
|
if (markerLine('endregion', marker).test(line)) closes.push(i);
|
|
});
|
|
|
|
const syntax = `Expected exactly one "# region: pig-demo/${marker}" and one "# endregion: pig-demo/${marker}".`;
|
|
if (opens.length !== 1 || closes.length !== 1) {
|
|
report.fail(
|
|
rel(onDisk),
|
|
RULE_MARKER,
|
|
`marker "${marker}" (referenced from ${where}) resolves to ${opens.length} region marker(s) and ` +
|
|
`${closes.length} endregion marker(s). ${syntax} ` +
|
|
(opens.length > 1 || closes.length > 1
|
|
? 'More than one pair is ambiguous: the receipt would quote whichever region the reader found first.'
|
|
: 'A missing marker renders the receipt panel empty, which reads as the code not existing.'),
|
|
);
|
|
continue;
|
|
}
|
|
|
|
const [open] = opens;
|
|
const [close] = closes;
|
|
if (close <= open) {
|
|
report.fail(
|
|
rel(onDisk),
|
|
RULE_MARKER,
|
|
`marker "${marker}": the endregion is on line ${close + 1}, at or before the region on line ${open + 1}.`,
|
|
);
|
|
continue;
|
|
}
|
|
const region = lines.slice(open + 1, close);
|
|
report.check(
|
|
region.some((line) => line.trim() !== ''),
|
|
rel(onDisk),
|
|
RULE_MARKER,
|
|
`marker "${marker}" delimits an empty region between lines ${open + 1} and ${close + 1}. The markers are ` +
|
|
'exclusive, so a pair on adjacent lines quotes nothing.',
|
|
);
|
|
}
|
|
}
|
|
|
|
report.finish();
|