mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-28 07:32:14 +02:00
* fix(memory-ingest): --scan-secrets scans the rendered page and fails closed --scan-secrets ran gitleaks on the raw transcript .jsonl, then imported a page rendered from it. gitleaks' assignment rules don't match across a JSON-escaped quote (KEY=\"v\" on disk), so a secret the rendered page shows as KEY="v" was imported unflagged. And the gate skipped a file only on scanner "gitleaks" with findings, so a scan that errored (non-zero exit, 16MB maxBuffer overflow on a file with many findings, unparseable report) or could not run (gitleaks missing, slow-probe cooldown) imported the file unscanned. Scan the rendered page body, the exact bytes writeStaged() writes, via a new secretScanText() helper, and skip the file whenever the scan did not complete. Skipped files stay out of the state file, so the next run retries them. Reword the helper warnings and setup-gbrain/memory.md, which described the fail-open as intended. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(test): reconcile Bun failure markers and footer counts * fix(sync-gbrain): verify source-scoped reads without mutation * fix(test): recognize grounded TTHW target choices structurally * fix(aside): make the readiness probe work under zsh and report why it failed The probe built its deadline into `_T` and expanded it unquoted, so `$_T aside repl …` only worked in a shell that word-splits. zsh does not: it looked for a command literally named "gtimeout 30", the probe answered ASIDE_NOT_RUNNING with Aside installed and ready, and every browsing skill fell back to the bundled Chromium in silence. zsh is the macOS default and Aside is macOS-only, so on a stock Mac the probe could never report READY. The deadline becomes a function, `_gs_d`. It receives the command as "$@", already split, so sh, bash and zsh all behave the same, and the gtimeout → timeout → perl alarm chain is unchanged. A 4th arm runs the call unbounded when none of the three is present, which is what the empty `_T` did before. Not `eval`: it re-parses the string, so the parens and `;` of the perl arm become syntax and that arm dies in bash *and* zsh — on a stock Mac, the arm that actually runs. On failure the probe now prints the CLI's reason after ASIDE_NOT_RUNNING:, the shape gstack-render already uses: the first line that starts with a capital letter, i.e. the CLI's own sentence or Node's `Error:` line below its loader frame. "Not running" covers states with different fixes — no window open for the profile, a NODE_OPTIONS preload that kills the CLI — and a bare verdict sent all of them to "open the Aside app". The BROWSER SETUP prose quotes that reason before asking the user to open the app. The text pin asserted the broken invocation verbatim, so it now pins the function and asserts neither `$_T aside repl` nor an eval form comes back. A second test executes the rendered probe in sh, bash and zsh on each of the four deadline arms with stubbed binaries on a narrowed PATH, plus two failing CLIs: one that prints its own sentence, one that crashes like Node with the useful line below the frame. The deadline function costs zero bytes against the lines it replaces; the reason costs 53 per copy of the probe (44 where the reworded BROWSER SETUP line gives 9 back). That moves four guards by the measured amount: plan-devex-review's skeleton cap to 68,550 (measured 68,544), plan-ceo-review's skeleton cap to 80,150 (measured 80,111) and union ratio to 1.081 (measured 1.0803), and plan-eng-review's union ratio to 1.151 (measured 1.1504). Fixes #2842, #2941. * Clarify engineering review startup and decision flow * Fix Windows readiness fixture PATH and command shim * fix(test): recognize grounded TTHW target choices structurally * Clarify engineering review startup and decision flow * fix(test): restrict QA-only fixture tools to its no-Edit contract * v1.90.0.0 fix(sync-gbrain): guard readiness verdicts and refresh metadata * fix(browse): validate canonical upload targets * fix(gbrain): classify structured PGLite busy response * fix(browse): preserve native extension runtime APIs * Fix displayless browser handoff ownership * Accept unique installed autoplan methodology aliases * fix(skills): preserve positional literals during installation * fix(browse): checksum installer contents through stdin * fix(test): normalize Windows checksum fixture paths * test: emulate unavailable shasum in Windows checksum fixture * fix(investigate): preserve owned freeze lifecycle * fix(review): preserve N+1 retry and Red Team completion * fix: bound Aside readiness and preserve safe fallback * test: exercise setup and Chromium on native ARM * fix: preserve install ownership and ARM browser selection * Fix gbrain ingest scan boundaries and seed observation * Refresh managed ship hooks and supervise expanded paid census * Reject resumed gbrain pages excluded by current policy * Recover zombie agent locks safely and enable CI Python venv * Repair paid actor declarations and Aside pitch assertions * Bump consolidated wave to next free minor release * Clarify CEO review admin choices and option tradeoffs * Preserve CEO mode handoff anchors in clarified workflow * Make Windows portability fixtures use shell-native paths * Restore ARM Bun alias and clarify ship review gates * Refresh ship workflow golden snapshots * Fix Windows DX documentation controls without piped stdin * Decode Codex child pipes without Bun's encoded-stream stall * Bound DX pre-review audit before product questions * Clarify trusted review-start read in paid revalidation * Bump consolidated wave to next free minor release * Clarify CEO review admin choices and option tradeoffs * Preserve CEO mode handoff anchors in clarified workflow * Make Windows portability fixtures use shell-native paths * Restore ARM Bun alias and clarify ship review gates * Refresh ship workflow golden snapshots * Fix Windows DX documentation controls without piped stdin * Decode Codex child pipes without Bun's encoded-stream stall * Bound DX pre-review audit before product questions * Clarify trusted review-start read in paid revalidation * Reconcile new main planning flow and paid judge census * fix: reconcile rebased planning and source-bound validation * test: pin cookie workflow judge to scored Sonnet model * fix: keep terminal agent boot out of module imports * fix: preserve pending-question uncertainty in engineering review * fix: stabilize Windows reliability-wave fixtures * fix: clarify design consultation research workflow * fix: preserve independent design consultation inputs * fix: resolve design taste scope and browser research guidance * fix: make consultation opt-in preflight unambiguous * test: await native Edge owner readiness or terminal result --------- Co-authored-by: Bruce Krysiak <brucek@alum.mit.edu> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Antonio Vitalic <antoninte99@gmail.com>
751 lines
36 KiB
TypeScript
751 lines
36 KiB
TypeScript
/**
|
|
* Unit tests for diff-based test selection.
|
|
* Free (no API calls), runs with `bun test`.
|
|
*/
|
|
|
|
import { describe, test, expect } from 'bun:test';
|
|
import { spawnSync } from 'child_process';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import * as os from 'os';
|
|
import {
|
|
matchGlob,
|
|
selectTests,
|
|
detectBaseBranch,
|
|
E2E_TOUCHFILES,
|
|
E2E_TIERS,
|
|
LLM_JUDGE_TOUCHFILES,
|
|
GLOBAL_TOUCHFILES,
|
|
} from './helpers/touchfiles';
|
|
|
|
import { readWorkflowExcerpt } from './helpers/workflow-excerpt';
|
|
import { sharedLibsPlanExcerpt } from './helpers/shared-libs-plan-excerpt';
|
|
|
|
const ROOT = path.resolve(import.meta.dir, '..');
|
|
const SHIP_GUARD_ONLY = ['ship-managed-hook-refresh', 'ship-unmanaged-hook-consent', 'ship-local-hook-preservation'];
|
|
|
|
function registeredJudgeTestNames(source: string): string[] {
|
|
// Inspect registrations, not arbitrary `name` fields such as Error.name.
|
|
const registrations = [...source.matchAll(/^\s*testIfSelected\s*\(/gm)];
|
|
const names = [...source.matchAll(/^\s*testIfSelected\s*\(\s*(['"`])([^'"`\r\n]+)\1\s*,/gm)]
|
|
.map(match => match[2]);
|
|
if (names.length !== registrations.length) {
|
|
throw new Error('LLM-judge test registrations must use literal case names for the TOUCHFILES inventory');
|
|
}
|
|
return [...new Set(names)];
|
|
}
|
|
|
|
// --- matchGlob ---
|
|
|
|
describe('matchGlob', () => {
|
|
test('** matches any depth of path segments', () => {
|
|
expect(matchGlob('browse/src/commands.ts', 'browse/src/**')).toBe(true);
|
|
expect(matchGlob('browse/src/deep/nested/file.ts', 'browse/src/**')).toBe(true);
|
|
expect(matchGlob('browse/src/cli.ts', 'browse/src/**')).toBe(true);
|
|
});
|
|
|
|
test('** does not match unrelated paths', () => {
|
|
expect(matchGlob('browse/src/commands.ts', 'qa/**')).toBe(false);
|
|
expect(matchGlob('review/SKILL.md', 'qa/**')).toBe(false);
|
|
});
|
|
|
|
test('exact match works', () => {
|
|
expect(matchGlob('SKILL.md', 'SKILL.md')).toBe(true);
|
|
expect(matchGlob('SKILL.md.tmpl', 'SKILL.md')).toBe(false);
|
|
expect(matchGlob('qa/SKILL.md', 'SKILL.md')).toBe(false);
|
|
});
|
|
|
|
test('* matches within a single segment', () => {
|
|
expect(matchGlob('test/fixtures/review-eval-enum.rb', 'test/fixtures/review-eval-enum*.rb')).toBe(true);
|
|
expect(matchGlob('test/fixtures/review-eval-enum-diff.rb', 'test/fixtures/review-eval-enum*.rb')).toBe(true);
|
|
expect(matchGlob('test/fixtures/review-eval-vuln.rb', 'test/fixtures/review-eval-enum*.rb')).toBe(false);
|
|
});
|
|
|
|
test('dots in patterns are escaped correctly', () => {
|
|
expect(matchGlob('SKILL.md', 'SKILL.md')).toBe(true);
|
|
expect(matchGlob('SKILLxmd', 'SKILL.md')).toBe(false);
|
|
});
|
|
|
|
test('** at end matches files in the directory', () => {
|
|
expect(matchGlob('qa/SKILL.md', 'qa/**')).toBe(true);
|
|
expect(matchGlob('qa/SKILL.md.tmpl', 'qa/**')).toBe(true);
|
|
expect(matchGlob('qa/templates/report.md', 'qa/**')).toBe(true);
|
|
});
|
|
});
|
|
|
|
// --- selectTests ---
|
|
|
|
describe('selectTests', () => {
|
|
test('learnings rendering selects the Eng workflow consumers without unrelated review cases', () => {
|
|
expect(fs.readFileSync(path.join(ROOT, 'plan-eng-review/sections/review-sections.md.tmpl'), 'utf8'))
|
|
.toContain('{{LEARNINGS_SEARCH}}');
|
|
const consumers = selectTests(['plan-eng-review/sections/review-sections.md'], E2E_TOUCHFILES).selected
|
|
// These cases use an outside-only/Code Quality excerpt or descriptive metadata.
|
|
.filter(id => !['outside-plan-disabled-no-fallback', 'plan-ceo-review-prosons-cadence',
|
|
'plan-review-prosons-format', 'shared-libs-plan-callers'].includes(id));
|
|
const existing = ['learnings-show', 'codex-plan-ceo-format-mode', 'codex-plan-ceo-format-approach',
|
|
'codex-plan-eng-format-coverage', 'codex-plan-eng-format-kind'];
|
|
const result = selectTests(['scripts/resolvers/learnings.ts'], E2E_TOUCHFILES);
|
|
expect(result.reason).toBe('diff');
|
|
expect(result.selected.sort()).toEqual([...new Set([...consumers, ...existing])].sort());
|
|
expect(result.selected).toContain('plan-eng-review');
|
|
expect(result.selected).toContain('plan-eng-finding-count');
|
|
expect(result.selected).not.toContain('plan-ceo-review-prosons-cadence');
|
|
expect(result.selected).not.toContain('outside-plan-disabled-no-fallback');
|
|
});
|
|
|
|
test('learnings rendering selects the Eng judge that consumes the changed instruction', () => {
|
|
const result = selectTests(['scripts/resolvers/learnings.ts'], LLM_JUDGE_TOUCHFILES);
|
|
expect(result.reason).toBe('diff');
|
|
expect(result.selected).toEqual(['plan-eng-review/SKILL.md sections']);
|
|
});
|
|
|
|
test.each(['bin/gstack-paths', 'bin/gstack-slug', 'scripts/resolvers/design.ts'])(
|
|
'Design artifact dependencies select their native consumers: %s', (file) => {
|
|
const result = selectTests([file], E2E_TOUCHFILES);
|
|
expect(result.reason).toBe('diff');
|
|
expect(result.selected).toContain('plan-design-finding-count');
|
|
expect(result.selected).toContain('plan-design-with-ui-scope');
|
|
if (file !== 'bin/gstack-slug') expect(result.selected).toContain('autoplan-chain-pty');
|
|
expect(E2E_TIERS['plan-design-finding-count']).toBe('periodic');
|
|
expect(E2E_TIERS['plan-design-with-ui-scope']).toBe('gate');
|
|
expect(E2E_TIERS['autoplan-chain-pty']).toBe('periodic');
|
|
},
|
|
);
|
|
|
|
test.each(['test/helpers/owned-claude-transcript.ts', 'test/helpers/plan-skill-completion.ts'])(
|
|
'native completion changes select the Design UI gate: %s', (file) => {
|
|
const result = selectTests([file], E2E_TOUCHFILES);
|
|
expect(result.selected).toContain('plan-design-with-ui-scope');
|
|
expect(E2E_TIERS['plan-design-with-ui-scope']).toBe('gate');
|
|
},
|
|
);
|
|
|
|
test.each(['lib/cso/cli.ts', 'lib/cso/state.ts', 'test/cso-cli.test.ts', 'test/cso-snapshot-state.test.ts'])(
|
|
'CSO runtime and report regressions select all three audit cases: %s', (file) => {
|
|
const result = selectTests([file], E2E_TOUCHFILES);
|
|
expect(result.reason).toBe('diff');
|
|
expect(result.selected.sort()).toEqual(['cso-diff-mode', 'cso-full-audit', 'cso-infra-scope']);
|
|
expect(E2E_TIERS['cso-diff-mode']).toBe('gate');
|
|
expect(E2E_TIERS['cso-full-audit']).toBe('periodic');
|
|
expect(E2E_TIERS['cso-infra-scope']).toBe('periodic');
|
|
},
|
|
);
|
|
|
|
test.each(['lib/redact-engine.ts', 'lib/redact-patterns.ts'])(
|
|
'shared redaction changes retain CSO audit consumers: %s', (file) => {
|
|
const result = selectTests([file], E2E_TOUCHFILES);
|
|
for (const id of ['cso-diff-mode', 'cso-full-audit', 'cso-infra-scope']) {
|
|
expect(result.selected).toContain(id);
|
|
}
|
|
},
|
|
);
|
|
|
|
test.each(['test/helpers/coverage-audit.ts', 'test/coverage-audit.test.ts'])(
|
|
'coverage-audit validation changes select all three gate cases: %s', (file) => {
|
|
const result = selectTests([file], E2E_TOUCHFILES);
|
|
expect(result.selected.sort()).toEqual(['plan-eng-coverage-audit', 'review-coverage-audit', 'ship-coverage-audit']);
|
|
expect(result.selected.every(id => E2E_TIERS[id] === 'gate')).toBe(true);
|
|
},
|
|
);
|
|
|
|
test('testing resolver source selects the same E2E consumers as its generated content', () => {
|
|
const consumers = [
|
|
['plan-eng-review/sections/review-sections.md', 'TEST_COVERAGE_AUDIT_PLAN'],
|
|
['ship/sections/tests.md', 'TEST_BOOTSTRAP'],
|
|
['ship/sections/test-coverage.md', 'TEST_COVERAGE_AUDIT_SHIP'],
|
|
['qa/sections/test-bootstrap.md', 'TEST_BOOTSTRAP'],
|
|
['design-review/SKILL.md', 'TEST_BOOTSTRAP'],
|
|
];
|
|
for (const [output, token] of consumers) {
|
|
expect(fs.readFileSync(path.join(ROOT, `${output}.tmpl`), 'utf8')).toContain(`{{${token}}}`);
|
|
}
|
|
const generated = selectTests(consumers.map(([output]) => output), E2E_TOUCHFILES);
|
|
// These two CEO-format cases already depend on every resolver through
|
|
// scripts/resolvers/**; keep that existing selection alongside consumers.
|
|
// The bounded Code Quality fixture stops before Test review.
|
|
const expected = [...new Set([...generated.selected.filter(id => id !== 'shared-libs-plan-callers' && !SHIP_GUARD_ONLY.includes(id)),
|
|
'codex-plan-ceo-format-mode', 'codex-plan-ceo-format-approach',
|
|
])].sort();
|
|
const actual = selectTests(['scripts/resolvers/testing.ts'], E2E_TOUCHFILES);
|
|
expect(actual.reason).toBe('diff');
|
|
expect(actual.selected.sort()).toEqual(expected);
|
|
for (const id of ['plan-eng-finding-count', 'plan-eng-multi-finding-batching',
|
|
'autoplan-chain-pty', 'plan-eng-review-format-coverage', 'ship-section-loading', 'qa-fix-loop']) {
|
|
expect(actual.selected).toContain(id);
|
|
expect(E2E_TIERS[id]).toBe('periodic');
|
|
}
|
|
for (const id of ['plan-eng-coverage-audit', 'ship-coverage-audit']) {
|
|
expect(actual.selected).toContain(id);
|
|
expect(E2E_TIERS[id]).toBe('gate');
|
|
}
|
|
for (const unrelated of ['browse-basic', 'retro', 'office-hours-section-loading', 'review-coverage-audit']) {
|
|
expect(actual.selected).not.toContain(unrelated);
|
|
}
|
|
});
|
|
|
|
test('testing resolver does not select guard-only ship actors', () => {
|
|
const resolver = selectTests(['scripts/resolvers/testing.ts'], E2E_TOUCHFILES);
|
|
const ship = selectTests(['ship/SKILL.md'], E2E_TOUCHFILES);
|
|
for (const id of SHIP_GUARD_ONLY) {
|
|
expect(ship.selected).toContain(id);
|
|
expect(resolver.selected).not.toContain(id);
|
|
}
|
|
});
|
|
|
|
test('testing resolver source selects only judges that consume its generated workflow text', () => {
|
|
const result = selectTests(['scripts/resolvers/testing.ts'], LLM_JUDGE_TOUCHFILES);
|
|
expect(result.reason).toBe('diff');
|
|
expect(result.selected.sort()).toEqual(['plan-eng-review/SKILL.md sections', 'ship/SKILL.md workflow']);
|
|
});
|
|
|
|
test('bounded shared-code planning selects its consumed resolvers, excluding other Eng sections', () => {
|
|
const entrypoint = fs.readFileSync(path.join(ROOT, 'plan-eng-review/SKILL.md'), 'utf8');
|
|
const review = fs.readFileSync(path.join(ROOT, 'plan-eng-review/sections/review-sections.md'), 'utf8');
|
|
const excerpt = sharedLibsPlanExcerpt(entrypoint, review);
|
|
for (const [start, end] of [
|
|
['## Prior Learnings', '## Retrospective learning'],
|
|
['### 3. Test review', '### 4. Performance review'],
|
|
]) {
|
|
const begin = review.indexOf(start), finish = review.indexOf(end, begin);
|
|
expect(begin).toBeGreaterThan(0);
|
|
expect(finish).toBeGreaterThan(begin);
|
|
const changed = review.slice(0, begin + start.length) + '\nChanged excluded instructions.\n' + review.slice(finish);
|
|
expect(sharedLibsPlanExcerpt(entrypoint, changed)).toBe(excerpt);
|
|
}
|
|
for (const source of ['scripts/resolvers/learnings.ts', 'scripts/resolvers/testing.ts']) {
|
|
expect(selectTests([source], E2E_TOUCHFILES).selected).not.toContain('shared-libs-plan-callers');
|
|
}
|
|
expect(excerpt).toContain('## Confidence Calibration');
|
|
expect(excerpt).toContain('## AskUserQuestion Format');
|
|
expect(excerpt).toContain('### Shared-code evaluation rubric');
|
|
for (const source of ['scripts/resolvers/confidence.ts',
|
|
'scripts/resolvers/preamble/generate-ask-user-format.ts', 'scripts/resolvers/shared-libs.ts',
|
|
'test/helpers/shared-libs-plan-excerpt.ts']) {
|
|
expect(selectTests([source], E2E_TOUCHFILES).selected).toContain('shared-libs-plan-callers');
|
|
}
|
|
});
|
|
|
|
test.each([
|
|
['plan-eng-review/sections/review-sections.md', 'plan-eng-review/SKILL.md sections',
|
|
'plan-eng-review/SKILL.md', '## Scope gate', '## Section self-check', '### REGRESSION RULE (mandatory)'],
|
|
['ship/sections/tests.md', 'ship/SKILL.md workflow',
|
|
'ship/SKILL.md', '# Ship:', '## Important Rules', '## Test Framework Bootstrap'],
|
|
['ship/sections/test-coverage.md', 'ship/SKILL.md workflow',
|
|
'ship/SKILL.md', '# Ship:', '## Important Rules', '### REGRESSION RULE (mandatory)'],
|
|
['plan-design-review/sections/review-sections.md', 'plan-design-review/SKILL.md passes',
|
|
'plan-design-review/SKILL.md', '## Review Sections', '## CRITICAL RULE',
|
|
'## Review Sections (7 passes, after scope is agreed)'],
|
|
])('expanded judge content remains selected by its section alone: %s', (file, judge, skill, start, end, marker) => {
|
|
const body = fs.readFileSync(path.join(ROOT, file), 'utf8').replace(/^<!--[^\n]*-->\n/gm, '').trim();
|
|
const paragraph = body.split(`${marker}\n\n`)[1]?.split('\n\n')[0];
|
|
expect(paragraph?.length).toBeGreaterThan(100);
|
|
expect(readWorkflowExcerpt(skill, start, end)).toContain(`${marker}\n\n${paragraph}`);
|
|
for (const changed of [file, `${file}.tmpl`]) {
|
|
expect(selectTests([changed], LLM_JUDGE_TOUCHFILES).selected).toEqual([judge]);
|
|
}
|
|
});
|
|
|
|
test.each(['scripts/resolvers/design.ts', 'scripts/resolvers/review.ts'])(
|
|
'Design rendering source selects its workflow judge: %s', (file) => {
|
|
const result = selectTests([file], LLM_JUDGE_TOUCHFILES);
|
|
expect(result.reason).toBe('diff');
|
|
expect(result.selected).toContain('plan-design-review/SKILL.md passes');
|
|
});
|
|
|
|
test('the shared recording lifecycle selects every bounded attempt', () => {
|
|
const result = selectTests(['test/helpers/office-hours-attempt.ts'], E2E_TOUCHFILES);
|
|
const expected = {
|
|
'office-hours-forcing-energy': 'periodic',
|
|
'office-hours-builder-wildness': 'periodic',
|
|
'office-hours-brain-writeback': 'periodic',
|
|
'plan-design-review-plan-mode': 'periodic',
|
|
'plan-ceo-review-format-mode': 'periodic',
|
|
'plan-ceo-review-format-approach': 'periodic',
|
|
'plan-eng-review-format-coverage': 'periodic',
|
|
'plan-eng-review-format-kind': 'periodic',
|
|
'plan-ceo-review-prosons-cadence': 'periodic',
|
|
'plan-review-prosons-format': 'periodic',
|
|
'plan-review-prosons-hardstop-neg': 'periodic',
|
|
'plan-review-prosons-neutral-neg': 'periodic',
|
|
'setup-gbrain-bad-token': 'periodic',
|
|
'setup-gbrain-path4-local-pglite': 'periodic',
|
|
'setup-gbrain-remote': 'periodic',
|
|
'review-army-red-team': 'periodic',
|
|
'review-coverage-audit': 'gate',
|
|
'plan-eng-coverage-audit': 'gate',
|
|
'ship-coverage-audit': 'gate',
|
|
'plan-review-report': 'gate',
|
|
};
|
|
expect(result.selected.sort()).toEqual(Object.keys(expected).sort());
|
|
for (const [id, tier] of Object.entries(expected)) expect(E2E_TIERS[id]).toBe(tier);
|
|
});
|
|
|
|
test('browse/src change selects browse and qa tests', () => {
|
|
const result = selectTests(['browse/src/commands.ts'], E2E_TOUCHFILES);
|
|
expect(result.selected).toContain('browse-basic');
|
|
expect(result.selected).toContain('browse-snapshot');
|
|
expect(result.selected).toContain('qa-quick');
|
|
expect(result.selected).toContain('qa-fix-loop');
|
|
expect(result.selected).toContain('design-review-fix');
|
|
expect(result.reason).toBe('diff');
|
|
// Should NOT include unrelated tests
|
|
expect(result.selected).not.toContain('plan-ceo-review');
|
|
expect(result.selected).not.toContain('retro');
|
|
expect(result.selected).not.toContain('document-release');
|
|
});
|
|
|
|
test('aside resolver change selects the Aside-driven skill tests', () => {
|
|
const result = selectTests(['scripts/resolvers/aside.ts'], E2E_TOUCHFILES);
|
|
expect(result.selected).toContain('aside-browse-basic');
|
|
expect(result.selected).toContain('aside-browse-flow');
|
|
expect(result.selected).toContain('qa-quick');
|
|
expect(result.selected).toContain('qa-fix-loop');
|
|
expect(result.selected).toContain('design-review-fix');
|
|
expect(result.reason).toBe('diff');
|
|
expect(result.selected).not.toContain('plan-ceo-review');
|
|
expect(result.selected).not.toContain('retro');
|
|
});
|
|
|
|
test('mode-question capture dependencies select its native gate', () => {
|
|
for (const file of [
|
|
'test/auq-mode-capture.test.ts', 'test/skill-ceo-section-ordering.test.ts',
|
|
'test/helpers/agent-sdk-runner.ts', 'test/helpers/auq-native-capture.ts',
|
|
'test/helpers/hermetic-env.ts', 'test/helpers/eval-store.ts',
|
|
'lib/claude-bin.ts', 'test/workflow-excerpt.test.ts',
|
|
]) {
|
|
expect(selectTests([file], E2E_TOUCHFILES).selected).toContain('auq-format-gate');
|
|
}
|
|
});
|
|
|
|
test('shared-code evidence regressions select their consuming evaluations', () => {
|
|
expect(selectTests(['test/fixtures/shared-libs-readonly-substitution-ci16358.json'], E2E_TOUCHFILES).selected.sort())
|
|
.toEqual(['shared-libs-codex-read-only', 'shared-libs-opportunity-judgment', 'shared-libs-pr-coverage',
|
|
'shared-libs-read-only', 'shared-libs-unsupported-git'].sort());
|
|
for (const file of ['test/helpers/shared-libs-review-start-evidence.ts', 'test/shared-libs-review-start-evidence.test.ts',
|
|
'test/fixtures/shared-libs-review-start-public.json',
|
|
'test/fixtures/shared-libs-revalidation-max-turns-public.json']) {
|
|
expect(selectTests([file], E2E_TOUCHFILES).selected).toEqual(['shared-libs-review-revalidation']);
|
|
}
|
|
const pathCases = ['shared-libs-review-path-eligibility', 'shared-libs-review-index-flags',
|
|
'shared-libs-review-prior-coverage'];
|
|
expect(selectTests(['test/shared-libs-revalidation-prompt.test.ts'], E2E_TOUCHFILES).selected.sort())
|
|
.toEqual([...pathCases, 'shared-libs-review-revalidation'].sort());
|
|
expect(selectTests(['test/fixtures/shared-libs-index-flags-skip-question.json'], E2E_TOUCHFILES).selected.sort())
|
|
.toEqual([...pathCases, 'shared-libs-review-revalidation', 'shared-libs-review-lifecycle'].sort());
|
|
expect(selectTests(['test/fixtures/shared-libs-paths-max-turns-public.json'], E2E_TOUCHFILES).selected)
|
|
.toEqual(['shared-libs-review-index-flags']);
|
|
});
|
|
|
|
test('skill-specific change selects only that skill and related tests', () => {
|
|
const result = selectTests(['plan-ceo-review/SKILL.md'], E2E_TOUCHFILES);
|
|
expect(result.selected).toContain('plan-ceo-review');
|
|
expect(result.selected).toContain('plan-ceo-review-selective');
|
|
expect(result.selected).toContain('plan-ceo-review-benefits');
|
|
expect(result.selected).toContain('plan-ceo-review-expansion-energy');
|
|
expect(result.selected).toContain('codex-offered-ceo-review');
|
|
expect(result.selected).toContain('plan-ceo-review-format-mode');
|
|
expect(result.selected).toContain('plan-ceo-review-format-approach');
|
|
// v1.10.2.0 plan-mode handshake entries also depend on plan-ceo-review/**
|
|
expect(result.selected).toContain('plan-ceo-review-plan-mode');
|
|
expect(result.selected).toContain('plan-mode-no-op');
|
|
expect(result.selected).toContain('plan-ceo-review-prosons-cadence');
|
|
expect(result.selected).toContain('plan-review-prosons-format');
|
|
expect(result.selected).toContain('plan-review-prosons-hardstop-neg');
|
|
expect(result.selected).toContain('plan-review-prosons-neutral-neg');
|
|
// v1.13.x real-PTY E2E batch entries that also depend on plan-ceo-review/**
|
|
expect(result.selected).toContain('auq-format-gate');
|
|
expect(result.selected).toContain('plan-ceo-mode-routing');
|
|
expect(result.selected).toContain('autoplan-chain-pty');
|
|
// The dual-voice fixture loads the CEO skill as its Phase 1 dependency.
|
|
expect(result.selected).toContain('autoplan-dual-voice');
|
|
// Per-finding count + review-report-at-bottom (v1.21.x)
|
|
expect(result.selected).toContain('plan-ceo-finding-count');
|
|
// v1.22+ AskUserQuestion-blocked regression: auto-decide-preserved
|
|
// also depends on plan-ceo-review/** (autoplan-auto-mode test was
|
|
// removed in v1.28 — see commit message for the rationale).
|
|
expect(result.selected).toContain('auto-decide-preserved');
|
|
// v1.27+ gate-tier reviewCount-floor regression for transcript bug
|
|
expect(result.selected).toContain('plan-ceo-finding-floor');
|
|
// garrytan/askuserquestion-split-on-overflow: split-overflow periodic
|
|
// E2E test also depends on plan-ceo-review/** (5-option scope decision
|
|
// regression for the "drop to fit 4 options" failure mode).
|
|
expect(result.selected).toContain('plan-ceo-split-overflow');
|
|
// v2 plan Phase B carve: the section-loading E2E depends on plan-ceo-review/**.
|
|
expect(result.selected).toContain('plan-ceo-section-loading');
|
|
expect(result.selected).toContain('codex-plan-ceo-format-mode');
|
|
expect(result.selected).toContain('codex-plan-ceo-format-approach');
|
|
expect(result.selected).toContain('outside-plan-disabled-no-fallback');
|
|
expect(result.selected.length).toBe(25);
|
|
expect(result.skipped.length).toBe(Object.keys(E2E_TOUCHFILES).length - 25);
|
|
});
|
|
|
|
test('global touchfile triggers ALL tests', () => {
|
|
for (const file of ['test/helpers/session-runner.ts', 'scripts/test-strict-output.ts']) {
|
|
const result = selectTests([file], E2E_TOUCHFILES);
|
|
expect(result.selected.length).toBe(Object.keys(E2E_TOUCHFILES).length);
|
|
expect(result.skipped.length).toBe(0);
|
|
expect(result.reason).toContain('global');
|
|
}
|
|
});
|
|
|
|
test.each([
|
|
'test/helpers/hermetic-skill-runtime.ts',
|
|
'test/hermetic-skill-runtime.test.ts',
|
|
'lib/fs-atomic.ts',
|
|
])('live runtime dependency selects PTY consumers: %s', (file) => {
|
|
const result = selectTests([file], E2E_TOUCHFILES);
|
|
expect(result.reason).toBe('diff');
|
|
expect(result.selected).toContain('autoplan-chain-pty');
|
|
expect(result.selected).toContain('plan-ceo-mode-routing');
|
|
expect(result.selected).not.toContain('codex-plan-ceo-format-mode');
|
|
expect(result.selected).not.toContain('retro');
|
|
});
|
|
|
|
test('session tool isolation regression selects its capture workflows', () => {
|
|
const result = selectTests(['test/session-runner-tools.test.ts'], E2E_TOUCHFILES);
|
|
expect(result.selected.sort()).toEqual([
|
|
'auq-format-gate', 'carve-section-loading', 'plan-ceo-section-loading', 'plan-design-review-plan-mode', 'ship-section-loading',
|
|
]);
|
|
expect(result.reason).toBe('diff');
|
|
});
|
|
|
|
test('gen-skill-docs.ts is a scoped touchfile, not global', () => {
|
|
const result = selectTests(['scripts/gen-skill-docs.ts'], E2E_TOUCHFILES);
|
|
// Should select tests that list gen-skill-docs.ts in their touchfiles, not ALL tests
|
|
expect(result.selected.length).toBeGreaterThan(0);
|
|
expect(result.selected.length).toBeLessThan(Object.keys(E2E_TOUCHFILES).length);
|
|
expect(result.reason).toBe('diff');
|
|
// Should include tests that depend on gen-skill-docs.ts
|
|
expect(result.selected).toContain('skillmd-setup-discovery');
|
|
expect(result.selected).toContain('session-awareness');
|
|
expect(result.selected).toContain('journey-ideation');
|
|
// Should NOT include tests that don't depend on it
|
|
expect(result.selected).not.toContain('retro');
|
|
expect(result.selected).not.toContain('cso-full-audit');
|
|
});
|
|
|
|
test.each(['test/helpers/ceo-finding-fixture.ts', 'test/ceo-finding-fixture.test.ts', 'test/ceo-mode-routing-fixture.test.ts'])('mode input dependency selects its periodic eval: %s', file => {
|
|
const result = selectTests([file], E2E_TOUCHFILES);
|
|
expect(result.selected).toContain('plan-ceo-mode-routing');
|
|
expect(result.reason).toBe('diff');
|
|
});
|
|
|
|
test('unrelated file selects nothing', () => {
|
|
const result = selectTests(['README.md'], E2E_TOUCHFILES);
|
|
expect(result.selected).toEqual([]);
|
|
expect(result.skipped.length).toBe(Object.keys(E2E_TOUCHFILES).length);
|
|
});
|
|
|
|
test('empty changed files selects nothing', () => {
|
|
const result = selectTests([], E2E_TOUCHFILES);
|
|
expect(result.selected).toEqual([]);
|
|
});
|
|
|
|
test('multiple changed files union their selections', () => {
|
|
const result = selectTests(
|
|
['plan-ceo-review/SKILL.md', 'retro/SKILL.md.tmpl'],
|
|
E2E_TOUCHFILES,
|
|
);
|
|
expect(result.selected).toContain('plan-ceo-review');
|
|
expect(result.selected).toContain('plan-ceo-review-selective');
|
|
expect(result.selected).toContain('retro');
|
|
expect(result.selected).toContain('retro-base-branch');
|
|
// Also selects journey routing tests (*/SKILL.md.tmpl matches retro/SKILL.md.tmpl)
|
|
expect(result.selected.length).toBeGreaterThanOrEqual(4);
|
|
});
|
|
|
|
test('works with LLM_JUDGE_TOUCHFILES', () => {
|
|
const result = selectTests(['qa/SKILL.md'], LLM_JUDGE_TOUCHFILES);
|
|
expect(result.selected).toContain('qa/SKILL.md workflow');
|
|
expect(result.selected).toContain('qa/SKILL.md health rubric');
|
|
expect(result.selected).toContain('qa/SKILL.md anti-refusal');
|
|
expect(result.selected.length).toBe(3);
|
|
});
|
|
|
|
test('SKILL.md.tmpl root template selects root-dependent tests and routing tests', () => {
|
|
const result = selectTests(['SKILL.md.tmpl'], E2E_TOUCHFILES);
|
|
// Should select the 7 tests that depend on root SKILL.md
|
|
expect(result.selected).toContain('skillmd-setup-discovery');
|
|
expect(result.selected).toContain('session-awareness');
|
|
expect(result.selected).toContain('session-awareness');
|
|
// Also selects journey routing tests (SKILL.md.tmpl in their touchfiles)
|
|
expect(result.selected).toContain('journey-ideation');
|
|
// Should NOT select unrelated non-routing tests
|
|
expect(result.selected).not.toContain('plan-ceo-review');
|
|
expect(result.selected).not.toContain('retro');
|
|
});
|
|
|
|
test('global touchfiles work for LLM-judge tests too', () => {
|
|
const result = selectTests(['test/helpers/session-runner.ts'], LLM_JUDGE_TOUCHFILES);
|
|
expect(result.selected.length).toBe(Object.keys(LLM_JUDGE_TOUCHFILES).length);
|
|
});
|
|
});
|
|
|
|
// --- detectBaseBranch ---
|
|
|
|
describe('detectBaseBranch', () => {
|
|
test('detects local main branch', () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'touchfiles-test-'));
|
|
const run = (cmd: string, args: string[]) =>
|
|
spawnSync(cmd, args, { cwd: dir, stdio: 'pipe', timeout: 5000 });
|
|
|
|
run('git', ['init']);
|
|
run('git', ['config', 'user.email', 'test@test.com']);
|
|
run('git', ['config', 'user.name', 'Test']);
|
|
fs.writeFileSync(path.join(dir, 'test.txt'), 'hello\n');
|
|
run('git', ['add', '.']);
|
|
run('git', ['commit', '-m', 'init']);
|
|
|
|
const result = detectBaseBranch(dir);
|
|
// Should find 'main' (or 'master' depending on git default)
|
|
expect(result).toMatch(/^(main|master)$/);
|
|
|
|
try { fs.rmSync(dir, { recursive: true, force: true }); } catch {}
|
|
});
|
|
|
|
test('returns null for empty repo with no branches', () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'touchfiles-test-'));
|
|
const run = (cmd: string, args: string[]) =>
|
|
spawnSync(cmd, args, { cwd: dir, stdio: 'pipe', timeout: 5000 });
|
|
|
|
run('git', ['init']);
|
|
// No commits = no branches
|
|
const result = detectBaseBranch(dir);
|
|
expect(result).toBeNull();
|
|
|
|
try { fs.rmSync(dir, { recursive: true, force: true }); } catch {}
|
|
});
|
|
|
|
test('returns null for non-git directory', () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'touchfiles-test-'));
|
|
const result = detectBaseBranch(dir);
|
|
expect(result).toBeNull();
|
|
|
|
try { fs.rmSync(dir, { recursive: true, force: true }); } catch {}
|
|
});
|
|
});
|
|
|
|
// --- Completeness: every testName in skill-e2e-*.test.ts has a TOUCHFILES entry ---
|
|
|
|
describe('TOUCHFILES completeness', () => {
|
|
test('every E2E testName has a TOUCHFILES entry', () => {
|
|
// Read all split E2E test files
|
|
const testDir = path.join(ROOT, 'test');
|
|
const e2eFiles = fs.readdirSync(testDir).filter(f => f.startsWith('skill-e2e-') && f.endsWith('.test.ts'));
|
|
let e2eContent = '';
|
|
for (const f of e2eFiles) {
|
|
e2eContent += fs.readFileSync(path.join(testDir, f), 'utf-8') + '\n';
|
|
}
|
|
|
|
// Extract all testName: 'value' entries
|
|
const testNameRegex = /testName:\s*['"`]([^'"`]+)['"`]/g;
|
|
const testNames: string[] = [];
|
|
let match;
|
|
while ((match = testNameRegex.exec(e2eContent)) !== null) {
|
|
let name = match[1];
|
|
// Handle template literals like `qa-${label}` — these expand to
|
|
// qa-b6-static, qa-b7-spa, qa-b8-checkout
|
|
if (name.includes('${')) continue; // skip template literals, check expanded forms below
|
|
testNames.push(name);
|
|
}
|
|
|
|
// Add the template-expanded testNames from runPlantedBugEval calls
|
|
const plantedBugRegex = /runPlantedBugEval\([^,]+,\s*[^,]+,\s*['"`]([^'"`]+)['"`]\)/g;
|
|
while ((match = plantedBugRegex.exec(e2eContent)) !== null) {
|
|
testNames.push(`qa-${match[1]}`);
|
|
}
|
|
|
|
expect(testNames.length).toBeGreaterThan(0);
|
|
|
|
const missing = testNames.filter(name => !(name in E2E_TOUCHFILES));
|
|
if (missing.length > 0) {
|
|
throw new Error(
|
|
`E2E tests missing TOUCHFILES entries: ${missing.join(', ')}\n` +
|
|
`Add these to E2E_TOUCHFILES in test/helpers/touchfiles.ts`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('E2E_TIERS covers exactly the same tests as E2E_TOUCHFILES', () => {
|
|
const touchfileKeys = new Set(Object.keys(E2E_TOUCHFILES));
|
|
const tierKeys = new Set(Object.keys(E2E_TIERS));
|
|
|
|
const missingFromTiers = [...touchfileKeys].filter(k => !tierKeys.has(k));
|
|
const extraInTiers = [...tierKeys].filter(k => !touchfileKeys.has(k));
|
|
|
|
if (missingFromTiers.length > 0) {
|
|
throw new Error(
|
|
`E2E tests missing TIER entries: ${missingFromTiers.join(', ')}\n` +
|
|
`Add these to E2E_TIERS in test/helpers/touchfiles.ts`,
|
|
);
|
|
}
|
|
if (extraInTiers.length > 0) {
|
|
throw new Error(
|
|
`E2E_TIERS has extra entries not in E2E_TOUCHFILES: ${extraInTiers.join(', ')}\n` +
|
|
`Remove these from E2E_TIERS or add to E2E_TOUCHFILES`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('E2E_TIERS only contains valid tier values', () => {
|
|
const validTiers = ['gate', 'periodic'];
|
|
for (const [name, tier] of Object.entries(E2E_TIERS)) {
|
|
if (!validTiers.includes(tier)) {
|
|
throw new Error(`E2E_TIERS['${name}'] has invalid tier '${tier}'. Valid: ${validTiers.join(', ')}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('every LLM-judge test has a TOUCHFILES entry', () => {
|
|
const llmContent = fs.readFileSync(
|
|
path.join(ROOT, 'test', 'skill-llm-eval.test.ts'),
|
|
'utf-8',
|
|
);
|
|
|
|
const unique = registeredJudgeTestNames(llmContent);
|
|
expect(unique).toHaveLength(27);
|
|
|
|
const missing = unique.filter(name => !(name in LLM_JUDGE_TOUCHFILES));
|
|
if (missing.length > 0) {
|
|
throw new Error(
|
|
`LLM-judge tests missing TOUCHFILES entries: ${missing.join(', ')}\n` +
|
|
`Add these to LLM_JUDGE_TOUCHFILES in test/helpers/touchfiles.ts`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('judge inventory ignores error names and catches an unmapped registration', () => {
|
|
const source = fs.readFileSync(path.join(ROOT, 'test', 'skill-llm-eval.test.ts'), 'utf8');
|
|
const withUnmappedCase = `${source}\n
|
|
Object.assign(new Error('deadline'), { name: 'WorkflowJudgeDeadline' });
|
|
Object.assign(new Error('retry'), { name: 'WorkflowJudgeSuperseded' });
|
|
testIfSelected('unmapped judge case', async () => {}, 120_000);
|
|
`;
|
|
const names = registeredJudgeTestNames(withUnmappedCase);
|
|
expect(names).toHaveLength(28);
|
|
expect(names.filter(name => !(name in LLM_JUDGE_TOUCHFILES))).toEqual(['unmapped judge case']);
|
|
});
|
|
|
|
test('judge inventory rejects dynamic registrations it cannot account for', () => {
|
|
expect(() => registeredJudgeTestNames('testIfSelected(computedName, async () => {}, 120_000);'))
|
|
.toThrow('must use literal case names');
|
|
});
|
|
});
|
|
|
|
// --- dependency paths exist on disk ---
|
|
//
|
|
// The axis nobody guarded: a dep-list entry can point at a file that was
|
|
// deleted long ago (browse/src/sidebar-agent.ts sat in three entries for 48
|
|
// versions), and diff-based selection then silently never triggers those
|
|
// tests. Globs are skipped (they describe patterns, not files); every literal
|
|
// path must exist.
|
|
|
|
describe('touchfile dependency paths exist', () => {
|
|
const allEntries: Array<[string, string]> = [];
|
|
for (const [name, deps] of Object.entries(E2E_TOUCHFILES)) {
|
|
for (const dep of deps) allEntries.push([name, dep]);
|
|
}
|
|
for (const [name, deps] of Object.entries(LLM_JUDGE_TOUCHFILES)) {
|
|
for (const dep of deps) allEntries.push([name, dep]);
|
|
}
|
|
for (const dep of GLOBAL_TOUCHFILES) allEntries.push(['(global)', dep]);
|
|
|
|
test('every non-glob dependency path exists', () => {
|
|
const stale = allEntries
|
|
.filter(([, dep]) => !dep.includes('*'))
|
|
.filter(([, dep]) => !fs.existsSync(path.join(ROOT, dep)));
|
|
if (stale.length > 0) {
|
|
throw new Error(
|
|
`Touchfile dep lists reference files that do not exist:\n` +
|
|
stale.map(([name, dep]) => ` ${name} -> ${dep}`).join('\n') +
|
|
`\nDelete or update these entries in test/helpers/touchfiles.ts — ` +
|
|
`diff-based selection silently skips tests whose deps are gone.`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('every glob dependency anchors to a directory that exists', () => {
|
|
// Cheap sanity for globs, two shapes: 'dir/**' (prefix ends with '/')
|
|
// must have the directory itself; 'dir/file-prefix*.ext' must have the
|
|
// containing directory. Catches 'deleted-dir/**' rot without a full
|
|
// filesystem walk; deliberately does not chase file-prefix staleness.
|
|
const stale = allEntries
|
|
.filter(([, dep]) => dep.includes('*'))
|
|
.map(([name, dep]) => {
|
|
const prefix = dep.split('*')[0];
|
|
const anchor = prefix.endsWith('/') ? prefix.slice(0, -1) : path.dirname(prefix);
|
|
return [name, dep, anchor] as const;
|
|
})
|
|
.filter(([, , anchor]) => anchor.length > 0 && anchor !== '.' && !fs.existsSync(path.join(ROOT, anchor)));
|
|
if (stale.length > 0) {
|
|
throw new Error(
|
|
`Touchfile glob deps whose anchor directory does not exist:\n` +
|
|
stale.map(([name, dep]) => ` ${name} -> ${dep}`).join('\n'),
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
// --- Reverse invariant: every selection key names a LIVING test ---
|
|
// The forward invariants above catch stale dep PATHS; nothing caught stale
|
|
// KEYS. The 2026-08 audit found 15 phantom E2E keys (6 gate-tier) selecting
|
|
// tests that existed nowhere — the merge-blocking census counted work that
|
|
// could not run. A key earns its place by appearing as a quoted testName in
|
|
// a living paid test file; constructed names get a reasoned exception.
|
|
|
|
describe('reverse invariant — keys must name living paid tests', () => {
|
|
const { isPaidTestFile } = require('./helpers/paid-test-set') as typeof import('./helpers/paid-test-set');
|
|
|
|
/** Keys whose testNames are CONSTRUCTED at runtime (template literals), so
|
|
* a quoted-occurrence scan cannot see them. Each entry needs the file that
|
|
* constructs it. Shrink-only: prefer literal names in new tests. */
|
|
const CONSTRUCTED_NAME_EXCEPTIONS: Record<string, string> = {};
|
|
|
|
const paidSources: string[] = [];
|
|
for (const name of fs.readdirSync(path.join(ROOT, 'test'))) {
|
|
const rel = `test/${name}`;
|
|
if (!isPaidTestFile(rel)) continue;
|
|
paidSources.push(fs.readFileSync(path.join(ROOT, rel), 'utf-8'));
|
|
}
|
|
|
|
const quotedSomewhere = (key: string): boolean =>
|
|
paidSources.some((src) =>
|
|
src.includes(`'${key}'`) || src.includes(`"${key}"`) || src.includes('`' + key + '`'));
|
|
|
|
/** Clause (b) of liveness: constructed testNames (template literals) bind
|
|
* through SELF-REGISTRATION — the 2026-08 dep-list sweep put each test
|
|
* FILE into its key's dep list, and the parent mapper keeps a shard on
|
|
* that registration union. So a key is alive when its name is quoted in a
|
|
* paid file OR its dep list names an existing paid test file. */
|
|
const registeredToLivingFile = (key: string): boolean =>
|
|
(E2E_TOUCHFILES[key] ?? []).some((dep) =>
|
|
/\.test\.ts$/.test(dep) && isPaidTestFile(dep) && fs.existsSync(path.join(ROOT, dep)));
|
|
|
|
test('every E2E_TOUCHFILES key is declared in a living paid test file', () => {
|
|
expect(paidSources.length).toBeGreaterThan(50); // scan-rot guard
|
|
const phantoms = Object.keys(E2E_TOUCHFILES)
|
|
.filter((key) => !(key in CONSTRUCTED_NAME_EXCEPTIONS))
|
|
.filter((key) => !quotedSomewhere(key) && !registeredToLivingFile(key));
|
|
expect(
|
|
phantoms,
|
|
`E2E_TOUCHFILES key(s) with NO declaring paid test — the census counts tests that cannot run. ` +
|
|
`Delete the key (both maps) or implement the test:\n ${phantoms.join('\n ')}`,
|
|
).toEqual([]);
|
|
});
|
|
|
|
test('every LLM_JUDGE_TOUCHFILES key is declared in a living paid test file', () => {
|
|
const phantoms = Object.keys(LLM_JUDGE_TOUCHFILES).filter((key) => !quotedSomewhere(key));
|
|
expect(
|
|
phantoms,
|
|
`LLM_JUDGE_TOUCHFILES key(s) with NO declaring test:\n ${phantoms.join('\n ')}`,
|
|
).toEqual([]);
|
|
});
|
|
|
|
test('constructed-name exceptions stay live (files exist and construct them)', () => {
|
|
const stale = Object.entries(CONSTRUCTED_NAME_EXCEPTIONS)
|
|
.filter(([, file]) => !fs.existsSync(path.join(ROOT, file)));
|
|
expect(stale.map(([k]) => k), 'exception points at a deleted file — remove the entry').toEqual([]);
|
|
});
|
|
});
|