mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-09 03:17:54 +00:00
* fix(impact-pdg): run mutation oracle's analyze child from built dist, not tsx-over-src The nightly Impact PDG Mutation Report workflow failed at the first fixture with ERR_MODULE_NOT_FOUND for src/cli/lazy-action.js. The harness shelled the real CLI out as `node --import tsx src/cli/index.ts analyze …`; on the CI runner's Node 22.22.3, native TypeScript type-stripping is enabled by default and handles the .ts entry instead of tsx, and native stripping does NOT remap the `./lazy-action.js` import specifier to lazy-action.ts the way tsx does — so CLI startup crashes before analyze even runs. The workflow already builds dist/ (build: 'true'). Prefer the shipped dist/cli/index.js (plain compiled JS — no tsx, no strip-types, and the parse workers it spawns also resolve from dist/) for the analyze child, falling back to tsx's own CLI over src only for build-free local runs. Production-faithful and version-agnostic across the engines range (node >=22.0). Verified on a real Node 22.22.3: the dist child starts cleanly with no lazy-action resolution error; the full `--mutation --only=inter-dispatcher-thin` run scores realized recall 1.0 and gate-mutation-recall passes. Workers are independently confirmed green on 22.22.3 in CI (run 27874383902). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(impact-pdg): declare the mutation oracle's @babel/* deps `bench/impact-pdg/mutation-oracle.mjs` imports @babel/parser, @babel/traverse, @babel/generator and @babel/types to instrument + value-diff the fixture AST, but none were declared in package.json. @babel/parser and @babel/types happen to be hoisted into gitnexus/node_modules transitively, but @babel/traverse and @babel/generator are only present at the monorepo root — so a fresh `npm ci` in gitnexus/ (CI) can't resolve them and the oracle dies at module load with `Cannot find package '@babel/traverse'` right after analyze succeeds. Declare all four as devDependencies (they're already lazily imported only on the --mutation path, so they stay out of the unit-test module graph). Verified the oracle resolves them from gitnexus/node_modules and scores recall 1.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(impact-pdg): gate only recall-gated mutation checks (honor recallGated) The recall gate filtered checks by `typeof c.recall === 'number'`, which includes the UPSTREAM fixtures. The mutation oracle is a FORWARD value-diff: it mutates the criterion line and observes which downstream lines' values change, so its behavioral AIS can never intersect a reverse (upstream) PDG slice — recall is 0 by construction. measure.mjs already marks these `recallGated: false` (alongside id-discrimination corroboration cases) and excludes them from its own internal gate; the standalone gate just didn't honor that flag, so `intra-control-loop` (direction: upstream, recall 0) tripped the floor even though the oracle ran the full suite cleanly (mean recall 0.923). Filter on `c.recallGated === true` so the floor applies only to the downstream cases the forward oracle can fairly validate. Verified locally: an upstream+downstream report now scores 1 of 2 and the gate passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(impact-pdg): fail the mutation gate when it has no recall signal + fix README drift Tri-review hardening of this PR's own changes: - gate-mutation-recall.mjs: the floor check passed vacuously when `scored` was empty (`min === null` short-circuits `min !== null && min < floor`). Narrowing the filter to `recallGated === true` made an empty `scored` set reachable in more inputs (a degenerate corpus, or a harvest that silently emptied every behavioral AIS). Now fail loudly when checks exist but none are recall-gated, so a hollow gate is red rather than a green "scored cases: 0 of N". A genuinely empty report (0 checks) still passes — it's not a degenerate-corpus signal. - README.md: the harness substrate section still documented the old `node --import tsx src/cli/index.ts …` child invocation this PR replaced; update it to the dist-preferred form to match `cliChildArgs`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
63 lines
3.1 KiB
JavaScript
63 lines
3.1 KiB
JavaScript
// CI gate for the nightly impact-PDG mutation oracle (#2227 tri-review, U11).
|
|
//
|
|
// The oracle (`measure.mjs --mutation --json`) uploads a machine report as a
|
|
// nightly artifact, but nothing read it back — a realized-recall regression
|
|
// would silently sit in an artifact nobody opens. This gate:
|
|
// 1. always writes a recall summary to the GitHub job summary (visible on the
|
|
// run without downloading the artifact), and
|
|
// 2. fails the job when the MINIMUM realized recall across scored mutation
|
|
// cases drops below MUTATION_RECALL_FLOOR (tunable env, conservative
|
|
// default) — so a mutant the slicer stops catching surfaces as a red run.
|
|
//
|
|
// Usage: node bench/impact-pdg/gate-mutation-recall.mjs [report.json]
|
|
import fs from 'node:fs';
|
|
|
|
const reportPath = process.argv[2] ?? 'mutation-report.json';
|
|
const floor = Number(process.env.MUTATION_RECALL_FLOOR ?? '0.5');
|
|
|
|
const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
|
|
const checks = Array.isArray(report?.mutation?.checks) ? report.mutation.checks : [];
|
|
// Gate only the checks the oracle marked recall-gated. measure.mjs sets
|
|
// `recallGated: false` for cases a forward value-diff oracle cannot fairly
|
|
// score against the PDG slice: UPSTREAM fixtures (the oracle runs in its native
|
|
// downstream sense, so its behavioral AIS can never intersect a reverse slice —
|
|
// recall is 0 by construction) and id-discrimination corroboration fixtures.
|
|
// Those still carry a numeric `recall` for the report, so the legacy
|
|
// `typeof c.recall === 'number'` filter wrongly tripped the floor on them.
|
|
const scored = checks.filter((c) => c.recallGated === true && typeof c.recall === 'number');
|
|
const recalls = scored.map((c) => c.recall);
|
|
const min = recalls.length ? Math.min(...recalls) : null;
|
|
const mean = recalls.length ? recalls.reduce((a, b) => a + b, 0) / recalls.length : null;
|
|
const below = scored.filter((c) => c.recall < floor);
|
|
const fmt = (x) => (x === null ? 'n/a' : x.toFixed(3));
|
|
|
|
const summary = [
|
|
'## impact-PDG mutation oracle',
|
|
'',
|
|
`- scored cases: ${scored.length} of ${checks.length}`,
|
|
`- min realized recall: ${fmt(min)} (floor ${floor})`,
|
|
`- mean realized recall: ${fmt(mean)}`,
|
|
`- cases below floor: ${below.length}${below.length ? ' — ' + below.map((c) => c.name).join(', ') : ''}`,
|
|
'',
|
|
].join('\n');
|
|
|
|
if (process.env.GITHUB_STEP_SUMMARY) {
|
|
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary + '\n');
|
|
}
|
|
process.stdout.write(summary + '\n');
|
|
|
|
// A report that produced checks but gated NONE of them has no recall signal:
|
|
// the floor check below would pass vacuously (`min === null`). Fail loudly so a
|
|
// degenerate corpus, or a harvest that silently emptied every behavioral AIS,
|
|
// surfaces as a red run instead of a green "scored cases: 0 of N".
|
|
if (checks.length > 0 && scored.length === 0) {
|
|
console.error(
|
|
`Mutation gate has no signal: 0 of ${checks.length} checks were recall-gated — refusing to pass.`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (min !== null && min < floor) {
|
|
console.error(`Mutation recall regression: min realized recall ${fmt(min)} < floor ${floor}`);
|
|
process.exit(1);
|
|
}
|