Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions scripts/check-agent-test-spelling.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,22 @@ const SCANNED_EXTENSIONS = new Set([
'.json',
]);

const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.turbo']);
/**
* Directories the three walks below never descend into: dependencies, build
* output -- and `.cache`, which holds ANOTHER REPOSITORY.
*
* `scripts/build-console.sh` materialises objectui at the pinned SHA into
* `.cache/objectui-<sha>/`, a whole foreign checkout that every console pin
* bump MUST create because the console cannot be built without it. All three
* walks reached into it: `scannedFiles`'s loose walk read objectui's own
* `AGENTS.md` -- instructions objectui writes for objectui's agents -- and red
* this gate on it, and `deriveVitestScripts` read its manifests, so a foreign
* package's script names silently widened the set of names this gate judges in
* OUR corpus. CI never saw either, because the lint job does not build the
* console; the red landed on whoever bumped the pin, over a file their diff
* could not have touched.
*/
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.turbo', '.cache']);

/* ────────────────────────────── the classifier ────────────────────────────── */

Expand Down Expand Up @@ -847,11 +862,12 @@ const SELF_TEST_BATTERIES = Object.freeze({
'the declared lists cannot quietly become mute buttons': 4,
'the dispatch-gates declaration — both directions, derived from the scan roots': 8,
'the derivation reads THIS workspace, and reads it non-empty': 4,
'a vendored checkout under .cache/ is not our corpus — both directions': 5,
});

// DELETING an entry silences that battery's floor exactly as effectively as
// zeroing it, so the roster's own size is pinned too.
const SELF_TEST_BATTERY_FLOOR = 12;
const SELF_TEST_BATTERY_FLOOR = 13;

// The key an assertion is filed under when no battery is open. It is not a
// declared battery, so it reds by the same set difference rather than silently
Expand Down Expand Up @@ -940,6 +956,39 @@ function selfTest() {
rmSync(redTree, { recursive: true, force: true });
}

console.log('a vendored checkout under .cache/ is not our corpus — both directions');
battery('a vendored checkout under .cache/ is not our corpus — both directions');
// The plant is objectui's OWN instruction, in objectui's own AGENTS.md, in the
// place `scripts/build-console.sh` puts it. Both directions are asserted from
// the SAME BYTES: under `.cache/` the sweep is clean and the walk never
// reaches the file, and one directory higher the identical text still reds —
// an exclusion that also silenced the loose walk would be a mute button, not
// a skip. The manifest case covers the second walk: a foreign package's
// script names must not widen the set of names judged in our corpus.
const foreignInstruction = 'Run one package with `pnpm --filter <pkg> test -- --run`.\n';
const cachedTree = makeFixtureTree(
baseFixtureFiles({
'.cache/objectui-pin/AGENTS.md': foreignInstruction,
'.cache/objectui-pin/package.json': JSON.stringify({ name: 'foreign', scripts: { 'test:foreign-only': 'vitest run' } }),
}),
);
try {
t('a planted .cache/ checkout leaves the sweep CLEAN', run(cachedTree, () => {}), EXIT_CLEAN);
t('the walk never reaches it', scannedFiles(cachedTree).filter((f) => f.startsWith('.cache')), []);
t('and its manifests do not widen the vitest-script derivation', deriveVitestScripts(cachedTree).names.has('test:foreign-only'), false);
} finally {
rmSync(cachedTree, { recursive: true, force: true });
}

const vendoredTree = makeFixtureTree(baseFixtureFiles({ 'vendor/objectui-pin/AGENTS.md': foreignInstruction }));
try {
const vendoredLines = [];
t('the SAME bytes one directory outside .cache/ still RED', run(vendoredTree, (s) => vendoredLines.push(s)), EXIT_VIOLATIONS);
t('...and the finding names that file', vendoredLines.join('\n').includes('vendor/objectui-pin/AGENTS.md'), true);
} finally {
rmSync(vendoredTree, { recursive: true, force: true });
}

console.log('the same tree WITHOUT the plant is green — the red above is the plant, not the fixture');
battery('the same tree WITHOUT the plant is green — the red above is the plant, not the fixture');
const greenTree = makeFixtureTree(baseFixtureFiles());
Expand Down
69 changes: 62 additions & 7 deletions scripts/check-comment-mask-corpus.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,9 @@

// dispatch-gates: whole-tree-population -- `collectSources` walks every authored JS/TS file from the repo root, so the corpus is the whole tree; the one literal below names the masker this gate exercises, not the files it reads.

import { readdirSync, readFileSync } from 'node:fs';
import { dirname, extname, join, relative, resolve } from 'node:path';
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, extname, join, relative, resolve, sep } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { isEntrypoint } from './invoked-as.mjs';

Expand All @@ -137,11 +138,24 @@ const REPO_ROOT = resolve(HERE, '..');
export const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.mts', '.cts', '.js', '.mjs', '.cjs', '.jsx']);

/**
* Directories that hold dependencies or build output rather than source. Same
* list `js-comment-mask.mjs`'s header states, so the prose and the instrument
* cannot drift apart. Every package in this tree builds to `dist`.
* Directories that hold dependencies or build output rather than source -- plus
* one that holds ANOTHER REPOSITORY. Every package in this tree builds to
* `dist`. `js-comment-mask.mjs`'s header quotes the six this walk started from
* as part of the 2026-08-21 measurement it records; the set below is the
* instrument and has grown past that sentence since (`.git`, and now `.cache`),
* so this declaration is the list, and the header is the history.
*
* `.cache` is where `scripts/build-console.sh` materialises objectui at the
* pinned SHA: a whole foreign checkout, gitignored, that every console pin bump
* MUST create because the console cannot be built without it. Walked, it put
* ~4,300 files this repo does not author into the corpus and reported one of
* them as a disagreement -- against a masker objectui's pages have no stake in.
* The failure text below is correct for OUR sources and wrong for those: it
* sends the reader to pin a shape in `js-comment-mask.mjs`, which is the last
* thing a pin bump should be editing. CI never saw it, because the lint job
* does not build the console; every instance landed on a person instead.
*/
export const SKIPPED_DIRECTORIES = new Set(['node_modules', 'dist', '.next', 'build', '.turbo', 'coverage', '.git']);
export const SKIPPED_DIRECTORIES = new Set(['node_modules', 'dist', '.next', 'build', '.turbo', 'coverage', '.git', '.cache']);

/** Below this, the corpus is not a corpus -- see the header. */
export const CORPUS_FLOOR = 1000;
Expand Down Expand Up @@ -449,7 +463,7 @@ async function main(argv) {
// not red. A battery BELOW its floor means cases stopped running; the remedy is
// to find what stopped registering.
const SELF_TEST_BATTERIES = Object.freeze({
'check-comment-mask-corpus self-test': 12,
'check-comment-mask-corpus self-test': 17,
});

// DELETING an entry silences that battery's floor exactly as effectively as
Expand Down Expand Up @@ -611,6 +625,47 @@ async function runSelfTestCases(parse) {
ok(`the corpus walk finds at least ${CORPUS_FLOOR} files in this tree`, collectSources().length >= CORPUS_FLOOR);
ok('...and every path it returns carries a known source extension', collectSources().every((file) => SOURCE_EXTENSIONS.has(extname(file))));

// ── The walk's exclusions, on a REAL tree, in both directions ─────────────
//
// `SKIPPED_DIRECTORIES` is the kind of declaration that reads as obviously
// correct and is measured by nothing: for `.cache` it was wrong for as long
// as `scripts/build-console.sh` has existed, and the only reader who ever
// found out was an operator staring at a red gate over someone else's file.
// So the exclusion is proven the way the corpus is judged -- by walking a
// directory on disk. The SAME BYTES are planted twice, inside `.cache` and
// outside it, against a masker that disagrees with the parser on them: the
// copy outside reds, the copy inside never enters the corpus at all, and the
// only variable between the two is location.
//
// ⚠️ These cases run on the production sweep path too (`main()` calls this
// body on every sweep), which is deliberate: what they hold is a property of
// the corpus that sweep is about to report on. The fixture is two files in a
// temp dir, removed in `finally`.
const plantedSource = 'export const Probe = () => null;\n';
const fixtureRoot = mkdtempSync(join(tmpdir(), 'comment-mask-corpus-'));
try {
const outsidePath = join('src', 'probe.tsx');
const insidePath = join('.cache', 'objectui-pin', 'src', 'probe.tsx');
for (const relPath of [outsidePath, insidePath]) {
mkdirSync(dirname(join(fixtureRoot, relPath)), { recursive: true });
writeFileSync(join(fixtureRoot, relPath), plantedSource, 'utf8');
}

const collected = collectSources(fixtureRoot).map((file) => relative(fixtureRoot, file));
ok('the walk collects a planted source that sits outside .cache', collected.includes(outsidePath));
ok('...and collects NOTHING under .cache', collected.every((file) => !file.split(sep).includes('.cache')));

const swept = sweep({ root: fixtureRoot, parse, scan: flagEverything });
ok('the copy outside .cache DISAGREES -- the plant is genuinely red', swept.disagreements.length === 1 && swept.disagreements[0].file === outsidePath);
ok('...and the sweep judged exactly the one file it walked', swept.files.length === 1);
// Excluded by LOCATION, not because those bytes happen to agree: compared
// directly, the identical copy under `.cache` disagrees just as loudly.
const wouldDisagree = compareFile(join(fixtureRoot, insidePath), plantedSource, { scan: flagEverything, parse });
ok('...while the identical bytes under .cache would have disagreed if walked', wouldDisagree.overMasks > 0);
} finally {
rmSync(fixtureRoot, { recursive: true, force: true });
}

SELF_TEST_CASE_COUNT = cases.length;
for (const testCase of cases) if (!testCase.condition) failures.push(testCase.label);
return { failures, cases };
Expand Down
Loading