@holmes-lab/holmes-kit 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/dist/.build-id +1 -1
- package/dist/holmes/assoc/assoc-arm.d.ts +94 -0
- package/dist/holmes/assoc/assoc-arm.js +187 -0
- package/dist/holmes/assoc/explore.d.ts +21 -0
- package/dist/holmes/assoc/explore.js +160 -0
- package/dist/holmes/assoc/impact-baseline.d.ts +13 -0
- package/dist/holmes/assoc/impact-baseline.js +48 -0
- package/dist/holmes/assoc/ppr.d.ts +64 -0
- package/dist/holmes/assoc/ppr.js +110 -0
- package/dist/holmes/cli/agents.d.ts +24 -11
- package/dist/holmes/cli/agents.js +93 -21
- package/dist/holmes/cli/doctor.d.ts +56 -0
- package/dist/holmes/cli/doctor.js +311 -5
- package/dist/holmes/cli/index.js +13 -1
- package/dist/holmes/cli/semantic-key.d.ts +19 -0
- package/dist/holmes/cli/semantic-key.js +93 -0
- package/dist/holmes/config/config.d.ts +9 -0
- package/dist/holmes/config/config.js +8 -1
- package/dist/holmes/cpg/language-capability.d.ts +65 -0
- package/dist/holmes/cpg/language-capability.js +145 -0
- package/dist/holmes/cpg/language-parser-walk.js +179 -34
- package/dist/holmes/cpg/language-parser.d.ts +1 -1
- package/dist/holmes/governance/ledger-rechain.d.ts +12 -0
- package/dist/holmes/governance/ledger-rechain.js +17 -2
- package/dist/holmes/governance/provenance-ledger.js +21 -0
- package/dist/holmes/guardrail/blind-spots.js +12 -1
- package/dist/holmes/guardrail/impact-gate.d.ts +77 -0
- package/dist/holmes/guardrail/impact-gate.js +263 -0
- package/dist/holmes/guardrail/write-target.d.ts +38 -1
- package/dist/holmes/guardrail/write-target.js +41 -4
- package/dist/holmes/hooks/adapters/antigravity.js +12 -1
- package/dist/holmes/hooks/corrupt-state-run.d.ts +33 -0
- package/dist/holmes/hooks/corrupt-state-run.js +16 -0
- package/dist/holmes/hooks/pre-tool-use.d.ts +26 -0
- package/dist/holmes/hooks/pre-tool-use.js +203 -11
- package/dist/holmes/hooks/stop.d.ts +65 -0
- package/dist/holmes/hooks/stop.js +200 -2
- package/dist/holmes/mcp/elicit-approval.d.ts +32 -2
- package/dist/holmes/mcp/elicit-approval.js +30 -2
- package/dist/holmes/mcp/handlers.d.ts +88 -0
- package/dist/holmes/mcp/handlers.js +636 -35
- package/dist/holmes/mcp/maintenance-analyze.d.ts +435 -0
- package/dist/holmes/mcp/maintenance-analyze.js +994 -0
- package/dist/holmes/mcp/maintenance-evidence.d.ts +140 -0
- package/dist/holmes/mcp/maintenance-evidence.js +253 -0
- package/dist/holmes/mcp/server.js +13 -10
- package/dist/holmes/mcp/tool-schemas.js +71 -0
- package/dist/holmes/project/root.js +3 -1
- package/dist/holmes/review/baseline-arm.d.ts +37 -0
- package/dist/holmes/review/baseline-arm.js +51 -0
- package/dist/holmes/review/captured-stdin-guard.d.ts +8 -0
- package/dist/holmes/review/captured-stdin-guard.js +48 -0
- package/dist/holmes/review/coherence-verify.d.ts +31 -0
- package/dist/holmes/review/coherence-verify.js +144 -0
- package/dist/holmes/review/commit-text.d.ts +50 -0
- package/dist/holmes/review/commit-text.js +76 -0
- package/dist/holmes/review/confidence-calibration.d.ts +39 -0
- package/dist/holmes/review/confidence-calibration.js +39 -0
- package/dist/holmes/review/content-baseline.d.ts +38 -0
- package/dist/holmes/review/content-baseline.js +103 -0
- package/dist/holmes/review/content-verify.d.ts +20 -0
- package/dist/holmes/review/content-verify.js +73 -0
- package/dist/holmes/review/dense-retrieval.d.ts +66 -0
- package/dist/holmes/review/dense-retrieval.js +97 -0
- package/dist/holmes/review/edge-quality.d.ts +44 -0
- package/dist/holmes/review/edge-quality.js +117 -0
- package/dist/holmes/review/evaluation-metrics.d.ts +138 -0
- package/dist/holmes/review/evaluation-metrics.js +175 -0
- package/dist/holmes/review/graph-verifier.d.ts +34 -0
- package/dist/holmes/review/graph-verifier.js +62 -0
- package/dist/holmes/review/hop-ablation.d.ts +100 -0
- package/dist/holmes/review/hop-ablation.js +89 -0
- package/dist/holmes/review/manual-baseline.d.ts +209 -0
- package/dist/holmes/review/manual-baseline.js +2846 -0
- package/dist/holmes/review/oracle-gap.d.ts +32 -0
- package/dist/holmes/review/oracle-gap.js +102 -0
- package/dist/holmes/review/point-in-time-replay.d.ts +41 -0
- package/dist/holmes/review/point-in-time-replay.js +161 -0
- package/dist/holmes/review/rank-diagnosis.d.ts +43 -0
- package/dist/holmes/review/rank-diagnosis.js +163 -0
- package/dist/holmes/review/replay-calibration.d.ts +62 -0
- package/dist/holmes/review/replay-calibration.js +83 -0
- package/dist/holmes/review/replay-corpus.d.ts +135 -0
- package/dist/holmes/review/replay-corpus.js +210 -0
- package/dist/holmes/review/run-replay.d.ts +260 -0
- package/dist/holmes/review/run-replay.js +729 -0
- package/dist/holmes/review/semantic-arm.d.ts +271 -0
- package/dist/holmes/review/semantic-arm.js +717 -0
- package/dist/holmes/review/semantic-retrieval.d.ts +55 -0
- package/dist/holmes/review/semantic-retrieval.js +156 -0
- package/dist/holmes/review/spec-layer-stats.d.ts +38 -0
- package/dist/holmes/review/spec-layer-stats.js +52 -0
- package/dist/holmes/review/temporal-prior.d.ts +33 -0
- package/dist/holmes/review/temporal-prior.js +53 -0
- package/dist/holmes/review/test-runner.d.ts +15 -0
- package/dist/holmes/review/test-runner.js +41 -4
- package/dist/holmes/review/test-selection-breadth.d.ts +75 -0
- package/dist/holmes/review/test-selection-breadth.js +57 -0
- package/dist/holmes/review/traceability-benchmark.d.ts +81 -0
- package/dist/holmes/review/traceability-benchmark.js +135 -0
- package/dist/holmes/review/union-verify.d.ts +12 -0
- package/dist/holmes/review/union-verify.js +70 -0
- package/dist/holmes/rtm/graph-store.d.ts +51 -0
- package/dist/holmes/rtm/graph-store.js +122 -0
- package/dist/holmes/rtm/incremental.d.ts +25 -1
- package/dist/holmes/rtm/incremental.js +18 -1
- package/dist/holmes/rtm/localize.d.ts +28 -0
- package/dist/holmes/rtm/localize.js +272 -14
- package/dist/holmes/rtm/rtm-builder.d.ts +118 -3
- package/dist/holmes/rtm/rtm-builder.js +265 -28
- package/dist/holmes/rtm/rtm-graph.d.ts +117 -2
- package/dist/holmes/rtm/rtm-graph.js +194 -31
- package/dist/holmes/rtm/taint-benchmark.d.ts +97 -0
- package/dist/holmes/rtm/taint-benchmark.js +141 -0
- package/dist/holmes/rtm/test-scope.js +24 -1
- package/dist/holmes/semantic/credentials.d.ts +15 -0
- package/dist/holmes/semantic/credentials.js +134 -0
- package/dist/holmes/semantic/embedder.d.ts +44 -0
- package/dist/holmes/semantic/embedder.js +185 -0
- package/dist/holmes/semantic/hit-rerank.d.ts +4 -0
- package/dist/holmes/semantic/hit-rerank.js +38 -0
- package/dist/holmes/semantic/tier.d.ts +37 -0
- package/dist/holmes/semantic/tier.js +54 -0
- package/dist/holmes/semantic/vector-cache.d.ts +11 -0
- package/dist/holmes/semantic/vector-cache.js +91 -0
- package/dist/holmes/spec/acceptance-quality.d.ts +81 -0
- package/dist/holmes/spec/acceptance-quality.js +169 -0
- package/dist/holmes/spec/validator.js +33 -1
- package/dist/holmes/spec/yaml-scalar.d.ts +1 -0
- package/dist/holmes/spec/yaml-scalar.js +43 -0
- package/package.json +1 -1
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runReplay = runReplay;
|
|
37
|
+
exports.semanticCaseRanking = semanticCaseRanking;
|
|
38
|
+
// @implements A-SPEC-346
|
|
39
|
+
// @implements A-SPEC-347
|
|
40
|
+
// @implements A-SPEC-348
|
|
41
|
+
// @implements A-SPEC-349
|
|
42
|
+
// @implements A-SPEC-378
|
|
43
|
+
// @implements A-SPEC-402
|
|
44
|
+
const fs = __importStar(require("node:fs"));
|
|
45
|
+
const os = __importStar(require("node:os"));
|
|
46
|
+
const path = __importStar(require("node:path"));
|
|
47
|
+
const cpg_scanner_1 = require("../cpg/cpg-scanner");
|
|
48
|
+
const rtm_builder_1 = require("../rtm/rtm-builder");
|
|
49
|
+
const rtm_graph_1 = require("../rtm/rtm-graph");
|
|
50
|
+
const test_scope_1 = require("../rtm/test-scope");
|
|
51
|
+
const spec_store_1 = require("../spec/spec-store");
|
|
52
|
+
const spec_types_1 = require("../spec/spec-types");
|
|
53
|
+
const maintenance_analyze_1 = require("../mcp/maintenance-analyze");
|
|
54
|
+
const point_in_time_replay_1 = require("./point-in-time-replay");
|
|
55
|
+
const evaluation_metrics_1 = require("./evaluation-metrics");
|
|
56
|
+
const replay_corpus_1 = require("./replay-corpus");
|
|
57
|
+
const semantic_arm_1 = require("./semantic-arm");
|
|
58
|
+
// @implements A-SPEC-479 — pre-emission verification of the union answer (measurement only).
|
|
59
|
+
// @implements A-SPEC-488 — lexPoverty drives the gate simulation ("uncited ∨ lexically poor").
|
|
60
|
+
const union_verify_1 = require("./union-verify");
|
|
61
|
+
// @implements A-SPEC-480 — coherence verification: the emission's internal graph relations.
|
|
62
|
+
const coherence_verify_1 = require("./coherence-verify");
|
|
63
|
+
const localize_1 = require("../rtm/localize");
|
|
64
|
+
// @implements A-SPEC-483 — content-level verification: the candidate's BODY, read from the
|
|
65
|
+
// materialized parent tree.
|
|
66
|
+
const content_verify_1 = require("./content-verify");
|
|
67
|
+
// @implements A-SPEC-467 — the associative-recall arm: measured beside the product, never inside it.
|
|
68
|
+
const assoc_arm_1 = require("../assoc/assoc-arm");
|
|
69
|
+
// @implements A-SPEC-470 — the impact axis's Phase-B external yardstick, always scored.
|
|
70
|
+
const impact_baseline_1 = require("../assoc/impact-baseline");
|
|
71
|
+
const dense_retrieval_1 = require("./dense-retrieval");
|
|
72
|
+
// @implements A-SPEC-472 — the three-arm iteration experiment (static / re-seed / judged).
|
|
73
|
+
const explore_1 = require("../assoc/explore");
|
|
74
|
+
const temporal_prior_1 = require("./temporal-prior");
|
|
75
|
+
const commit_text_1 = require("./commit-text");
|
|
76
|
+
/**
|
|
77
|
+
* Run the point-in-time replay against ANY corpus.
|
|
78
|
+
*
|
|
79
|
+
* Lifted verbatim from the benchmark that lived inside `maintenance-analyze.test.ts`, where it was
|
|
80
|
+
* bound to this repository's paths. Every verdict in this work came from that single corpus, so the
|
|
81
|
+
* measurements could not be falsified by a second one — this is what makes them falsifiable.
|
|
82
|
+
*
|
|
83
|
+
* Equivalence is the load-bearing property of the extraction: this repository's numbers must come
|
|
84
|
+
* out unchanged, or subsequent comparisons measure the refactor instead of the corpora.
|
|
85
|
+
*/
|
|
86
|
+
async function runReplay(corpus, limit, opts = {}) {
|
|
87
|
+
const started = Date.now();
|
|
88
|
+
const cases = (0, replay_corpus_1.casesFor)(corpus, limit, opts.offset ?? 0);
|
|
89
|
+
const outcomes = [];
|
|
90
|
+
const impactCases = [];
|
|
91
|
+
// @implements A-SPEC-470 — same cases, same denominator; only the predictor is poor on purpose.
|
|
92
|
+
const impactBaselineCases = [];
|
|
93
|
+
const unionCases = [];
|
|
94
|
+
const idfOutcomes = [];
|
|
95
|
+
const pprConfigs = opts.pprConfigs ?? [];
|
|
96
|
+
const pprOutcomes = new Map(pprConfigs.map((c) => [c.label, { rerank: [], admit: [] }]));
|
|
97
|
+
const PPR_IMPACT_KS = [5, 10, 20];
|
|
98
|
+
const pprImpactCases = new Map(pprConfigs.flatMap((c) => PPR_IMPACT_KS.map((k) => [`${c.label}@${k}`, []])));
|
|
99
|
+
// @implements A-SPEC-473 — arm D joins the same harness; A/B/C rows are untouched.
|
|
100
|
+
const ITER_ARMS = ['A', 'B', 'C', 'D'];
|
|
101
|
+
const ITER_E = 2, ITER_R = 10;
|
|
102
|
+
// per arm, per round index: macro-mean accumulators over cases
|
|
103
|
+
const iterAcc = new Map(opts.iterativeArms === true ? ITER_ARMS.map((a) => [a, []]) : []);
|
|
104
|
+
const semRerank = [];
|
|
105
|
+
const semAdmit = [];
|
|
106
|
+
// @implements A-SPEC-488 — one row set per simulated gate.
|
|
107
|
+
// @implements A-SPEC-491 — two admit-ACTION rows join the five rerank rows: where the lexical
|
|
108
|
+
// signal is poor, reranking the same pool cannot restore reach; only replacement can.
|
|
109
|
+
// @implements A-SPEC-492 — the compound STARVED gate: poverty alone over-fires cross-lingually
|
|
110
|
+
// (S-509), so replacement additionally requires the product's top-1 lexical score to be near
|
|
111
|
+
// zero — "the ranker found essentially nothing", a scale-free starvation verdict.
|
|
112
|
+
const SEM_GATES = ['uncited', 'poor@0.25', 'poor@0.5', 'or@0.25', 'or@0.5', 'admitPoor@0.25', 'admitPoor@0.5', 'admitStarved@1', 'admitStarved@5'];
|
|
113
|
+
const semGateRows = new Map(opts.semanticArm?.gates === true ? SEM_GATES.map((g) => [g, []]) : []);
|
|
114
|
+
// @implements A-SPEC-493 — union ∪ admit-top-K accumulators (gates runs only).
|
|
115
|
+
const UPA_KS = [3, 5];
|
|
116
|
+
const upaCases = new Map(opts.semanticArm?.gates === true ? UPA_KS.map((k) => [k, []]) : []);
|
|
117
|
+
const FRONTIER_ALPHAS = [0, 0.3, 0.5, 0.7];
|
|
118
|
+
const FRONTIER_KS = [1, 2, 3, 5, 10];
|
|
119
|
+
const frontierCases = new Map(opts.impactFrontier === true
|
|
120
|
+
? FRONTIER_ALPHAS.flatMap((a) => FRONTIER_KS.map((k) => [`${a}@${k}`, []]))
|
|
121
|
+
: []);
|
|
122
|
+
// @implements A-SPEC-479 — the verification grid: per-candidate independent accept (no position
|
|
123
|
+
// budget in the verdict) behind an explicit head guard, the separate axis.
|
|
124
|
+
// @implements A-SPEC-484 — τ grid injectable (model-conditional absolute cosines); default
|
|
125
|
+
// frozen for S-497 reproducibility.
|
|
126
|
+
const UV_SEM_TAUS = (0, union_verify_1.semTauGrid)(opts.unionVerify?.semTaus);
|
|
127
|
+
const UV_LEX_TAUS = [0.05, 0.15, 0.3];
|
|
128
|
+
const UV_GUARDS = [0, 1, 3];
|
|
129
|
+
const uvCases = new Map(opts.unionVerify !== undefined
|
|
130
|
+
? [
|
|
131
|
+
...UV_SEM_TAUS.flatMap((t) => UV_GUARDS.map((g) => [`sem@${t}@${g}`, []])),
|
|
132
|
+
...UV_LEX_TAUS.flatMap((t) => UV_GUARDS.map((g) => [`lex@${t}@${g}`, []])),
|
|
133
|
+
]
|
|
134
|
+
: []);
|
|
135
|
+
// @implements A-SPEC-480 — the coherence grid: evidence mode × head size. Graph-only, no
|
|
136
|
+
// embeddings; the head is kept by the explicit guard, the tail lives or dies by its relations.
|
|
137
|
+
const UC_MODES = ['edge', 'spec', 'any'];
|
|
138
|
+
const UC_HS = [1, 2, 3];
|
|
139
|
+
const ucCases = new Map(opts.unionCoherence === true
|
|
140
|
+
? UC_MODES.flatMap((m) => UC_HS.map((h) => [`${m}@${h}`, []]))
|
|
141
|
+
: []);
|
|
142
|
+
// @implements A-SPEC-481 — the position-independent grid: family × relation × guard.
|
|
143
|
+
const CR_RELS = ['edge', 'any'];
|
|
144
|
+
const CR_DS = [1, 2, 3];
|
|
145
|
+
const CR_GUARDS = [0, 1, 3];
|
|
146
|
+
const crCases = new Map(opts.coherenceRobust === true
|
|
147
|
+
? CR_RELS.flatMap((rel) => CR_GUARDS.flatMap((g) => [
|
|
148
|
+
...CR_DS.map((d) => [`deg@${rel}@${d}@${g}`, []]),
|
|
149
|
+
[`clu@${rel}@0@${g}`, []],
|
|
150
|
+
]))
|
|
151
|
+
: []);
|
|
152
|
+
// @implements A-SPEC-482 — the combination grid, drawn ONLY from the published S-498/499 bests.
|
|
153
|
+
const AC_ANCHORS = [['edge', 2], ['spec', 1], ['spec', 2], ['any', 1]];
|
|
154
|
+
const AC_DS = [1, 2];
|
|
155
|
+
const AC_COMBS = ['or', 'and'];
|
|
156
|
+
const acCases = new Map(opts.anchoredConsensus === true
|
|
157
|
+
? AC_ANCHORS.flatMap(([m, h]) => AC_DS.flatMap((d) => AC_COMBS.map((cb) => [`${m}@${h}@${d}@${cb}`, []])))
|
|
158
|
+
: []);
|
|
159
|
+
// @implements A-SPEC-483 — the content grid: five signals × the explicit guard axis.
|
|
160
|
+
const CV_SIGNALS = ['body1', 'body2', 'mention', 'bodyOrMention', 'bodyAndMention'];
|
|
161
|
+
const CV_GUARDS = [0, 1, 3];
|
|
162
|
+
const cvCases = new Map(opts.contentVerify === true
|
|
163
|
+
? CV_SIGNALS.flatMap((s) => CV_GUARDS.map((g) => [`${s}@${g}`, []]))
|
|
164
|
+
: []);
|
|
165
|
+
const impactCeilingCases = [];
|
|
166
|
+
const unionCeilingCases = [];
|
|
167
|
+
let unmeasured = 0;
|
|
168
|
+
let casesWithoutSpecs = 0;
|
|
169
|
+
for (const c of cases) {
|
|
170
|
+
const dest = fs.mkdtempSync(path.join(os.tmpdir(), `replay-${c.commit.slice(0, 8)}-`));
|
|
171
|
+
const parent = `${c.commit}^`;
|
|
172
|
+
try {
|
|
173
|
+
const laid = (0, point_in_time_replay_1.materializeTreeAt)(corpus.root, parent, corpus.sourcePathspec[0], dest);
|
|
174
|
+
if (!laid.ok) {
|
|
175
|
+
unmeasured++;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
for (const extra of corpus.sourcePathspec.slice(1)) {
|
|
179
|
+
(0, point_in_time_replay_1.materializeTreeAt)(corpus.root, parent, extra, dest, { allowNonEmpty: true });
|
|
180
|
+
}
|
|
181
|
+
// Proven, not trusted: a wrong tree still produces plausible numbers.
|
|
182
|
+
if ((0, point_in_time_replay_1.verifyTreeFidelity)(corpus.root, parent, corpus.sourcePathspec[0], dest).mismatches.length > 0) {
|
|
183
|
+
unmeasured++;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const laidSpecs = (0, point_in_time_replay_1.materializeTreeAt)(corpus.root, parent, corpus.specPathspec, dest, { allowNonEmpty: true });
|
|
187
|
+
if (!laidSpecs.ok)
|
|
188
|
+
casesWithoutSpecs++;
|
|
189
|
+
// Awaited, not stubbed: an earlier draft returned [] here for want of a sync reader, which
|
|
190
|
+
// would have scored every case with no specs and produced numbers that merely LOOKED like the
|
|
191
|
+
// originals. Equivalence is the whole point of this extraction.
|
|
192
|
+
const specs = laidSpecs.ok
|
|
193
|
+
? (0, spec_types_1.filterGoverned)(await new spec_store_1.LocalMarkdownRepository(path.join(dest, corpus.specPathspec)).list())
|
|
194
|
+
: [];
|
|
195
|
+
const scanned = new cpg_scanner_1.CpgScanner().scan(dest, dest);
|
|
196
|
+
const anchors = (0, test_scope_1.scanTestAnchors)(dest);
|
|
197
|
+
const graph = new rtm_graph_1.RtmGraph();
|
|
198
|
+
try {
|
|
199
|
+
(0, rtm_builder_1.buildRtm)(specs, scanned, graph);
|
|
200
|
+
// @implements A-SPEC-378
|
|
201
|
+
// Windowed at the PARENT. Reading the answer commit would make the file it touched look
|
|
202
|
+
// recently changed because it is about to be — the benchmark would be scoring a leak.
|
|
203
|
+
const prior = (0, temporal_prior_1.temporalPrior)(corpus.root, parent, 300);
|
|
204
|
+
const changePrior = {};
|
|
205
|
+
for (const [file, age] of prior.recency)
|
|
206
|
+
changePrior[file] = 1 + Math.pow(0.5, age / 10);
|
|
207
|
+
// @implements A-SPEC-402
|
|
208
|
+
// The commit-prose layer, which the MCP handler supplies and this replay did not. Without it
|
|
209
|
+
// the benchmark scored FOUR of the five layers that ship and every "current product" figure
|
|
210
|
+
// reported from it understated what a caller actually gets — a benchmark measuring something
|
|
211
|
+
// adjacent to the product is worse than one measuring nothing, because it reads as if it
|
|
212
|
+
// measured the product.
|
|
213
|
+
const known = new Set(scanned.map((f) => f.sourcePath));
|
|
214
|
+
const profile = (0, commit_text_1.commitTextProfile)(corpus.root, parent, 400);
|
|
215
|
+
const ct = (0, commit_text_1.rankByCommitText)(c.subject, profile, 300).filter((h) => known.has(h.file));
|
|
216
|
+
const top = ct[0]?.score ?? 0;
|
|
217
|
+
const commitTextBoost = {};
|
|
218
|
+
if (top > 0)
|
|
219
|
+
for (const h of ct)
|
|
220
|
+
commitTextBoost[h.file] = h.score / top;
|
|
221
|
+
const result = (0, maintenance_analyze_1.analyzeMaintenance)({
|
|
222
|
+
request: c.subject, scanned, specs, graph, testAnchors: anchors, history: [], changePrior, commitTextBoost,
|
|
223
|
+
groundTruth: { files: c.files, tests: c.tests },
|
|
224
|
+
basis: { head: parent, loadedBuild: 'b', diskBuild: 'b', specFingerprint: 'fp' },
|
|
225
|
+
coverage: { scannedFiles: scanned.length, skippedFiles: [], unsupportedLanguages: [] },
|
|
226
|
+
});
|
|
227
|
+
const ranked = result.candidates.map((x) => x.file);
|
|
228
|
+
// @implements A-SPEC-469 — the union scores what a caller actually RECEIVES as the impact
|
|
229
|
+
// answer, and that surface is now the graded rankedImpact (the closure stays gate-facing).
|
|
230
|
+
const impacted = (result.impacts?.rankedImpact ?? []).map((r) => r.file);
|
|
231
|
+
outcomes.push({ ranked, truthFiles: c.files, selectedTests: [...result.relevantTests], truthTests: c.tests });
|
|
232
|
+
// @implements A-SPEC-487 — the dump: 2-pass with semantic injected when asked (vectors
|
|
233
|
+
// only for the files the 1-pass surfaced — the hot-path lookup contract holds), 1-pass
|
|
234
|
+
// otherwise. Nothing here feeds outcomes or any pin.
|
|
235
|
+
if (opts.caseDump !== undefined) {
|
|
236
|
+
let dumpResult = result;
|
|
237
|
+
if (opts.productSemantic !== undefined) {
|
|
238
|
+
const dumpDocText = new Map(scanned.map((f) => [f.sourcePath,
|
|
239
|
+
(f.sourcePath + ' ' + f.symbols.map((sy) => sy.qualifiedName).join(' ')).slice(0, 2000)]));
|
|
240
|
+
const targets = [...new Set([...result.candidates.map((x) => x.file),
|
|
241
|
+
...(result.impacts?.rankedImpact ?? []).map((r) => r.file)])];
|
|
242
|
+
const texts = targets.map((f) => dumpDocText.get(f)).filter((t) => t !== undefined);
|
|
243
|
+
const [qv] = await opts.productSemantic.embedBatch([c.subject], 'query');
|
|
244
|
+
const dvs = texts.length > 0 ? await opts.productSemantic.embedBatch(texts, 'doc') : [];
|
|
245
|
+
const vecOf = new Map(texts.map((t, i) => [t, dvs[i] ?? null]));
|
|
246
|
+
dumpResult = (0, maintenance_analyze_1.analyzeMaintenance)({
|
|
247
|
+
request: c.subject, scanned, specs, graph, testAnchors: anchors, history: [], changePrior, commitTextBoost,
|
|
248
|
+
semantic: {
|
|
249
|
+
label: opts.productSemantic.label,
|
|
250
|
+
queryVector: qv ?? null,
|
|
251
|
+
cachedDocVector: (text) => vecOf.get(text) ?? null,
|
|
252
|
+
},
|
|
253
|
+
basis: { head: parent, loadedBuild: 'b', diskBuild: 'b', specFingerprint: 'fp' },
|
|
254
|
+
coverage: { scannedFiles: scanned.length, skippedFiles: [], unsupportedLanguages: [] },
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
// @implements A-SPEC-489 — excerpts from the materialized tree, top union-order files.
|
|
258
|
+
let bodies;
|
|
259
|
+
if (opts.dumpBodies !== undefined) {
|
|
260
|
+
bodies = {};
|
|
261
|
+
const order = [...new Set([...dumpResult.candidates.map((x) => x.file),
|
|
262
|
+
...(dumpResult.impacts?.rankedImpact ?? []).map((r) => r.file)])].slice(0, opts.dumpBodies.top);
|
|
263
|
+
for (const f of order) {
|
|
264
|
+
try {
|
|
265
|
+
const raw = fs.readFileSync(path.join(dest, f), 'utf8');
|
|
266
|
+
// @implements A-SPEC-490 — the informative excerpt: license blocks first, then cut.
|
|
267
|
+
const text = opts.dumpBodies.skipLicense === true ? (0, content_verify_1.skipLicenseHeader)(raw) : raw;
|
|
268
|
+
bodies[f] = text.slice(0, opts.dumpBodies.chars);
|
|
269
|
+
}
|
|
270
|
+
catch { /* unreadable candidate: omitted */ }
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
opts.caseDump({
|
|
274
|
+
commit: c.commit, subject: c.subject,
|
|
275
|
+
candidates: dumpResult.candidates,
|
|
276
|
+
rankedImpact: dumpResult.impacts?.rankedImpact ?? [],
|
|
277
|
+
truthFiles: c.files,
|
|
278
|
+
...(bodies !== undefined ? { bodies } : {}),
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
// Same case, same truth, same `topN` the product used — an arm scored against a different
|
|
282
|
+
// denominator would produce a comparison of definitions rather than of rankers.
|
|
283
|
+
// One variable: same bonuses, same demotion, same ordering — only the term weight differs.
|
|
284
|
+
const w = (0, semantic_arm_1.normalisedIdf)(scanned);
|
|
285
|
+
idfOutcomes.push({
|
|
286
|
+
ranked: (0, semantic_arm_1.rankWeighted)(c.subject, scanned, specs, ranked.length || 10, (t) => w.get(t) ?? 1)
|
|
287
|
+
.map((h) => h.file),
|
|
288
|
+
truthFiles: c.files,
|
|
289
|
+
selectedTests: [...result.relevantTests], // the arm changes ranking only
|
|
290
|
+
truthTests: c.tests,
|
|
291
|
+
});
|
|
292
|
+
// @implements A-SPEC-493 — captured per case for the union∪admit ceiling rows.
|
|
293
|
+
let semAdmitForCase = [];
|
|
294
|
+
// @implements A-SPEC-475 — the semantic arm, same cases and truth as everything else.
|
|
295
|
+
if (opts.semanticArm !== undefined) {
|
|
296
|
+
const sem = await semanticCaseRanking(scanned.map((f) => ({ sourcePath: f.sourcePath, symbolNames: f.symbols.map((sym) => sym.qualifiedName) })), c.subject, ranked, ranked.length || 10, opts.semanticArm.embedBatch);
|
|
297
|
+
semRerank.push({ ranked: sem.rerank, truthFiles: c.files, selectedTests: [...result.relevantTests], truthTests: c.tests });
|
|
298
|
+
semAdmit.push({ ranked: sem.admit, truthFiles: c.files, selectedTests: [...result.relevantTests], truthTests: c.tests });
|
|
299
|
+
semAdmitForCase = sem.admit;
|
|
300
|
+
// @implements A-SPEC-488 — the gate simulation: cited exactly as the product decides
|
|
301
|
+
// it, poverty as uniform-weight coverage by the product's top-1. A gate that stays
|
|
302
|
+
// shut scores the product order for that case.
|
|
303
|
+
if (opts.semanticArm.gates === true) {
|
|
304
|
+
const uncited = (0, localize_1.citationsIn)(c.subject, new Set(specs.map((sp) => sp.id))).cited.length === 0;
|
|
305
|
+
const subjTok = new Set((0, localize_1.significantTerms)(c.subject));
|
|
306
|
+
const top1 = scanned.find((f) => f.sourcePath === ranked[0]);
|
|
307
|
+
const top1Tok = top1 !== undefined ? (0, semantic_arm_1.fileTokens)(top1) : new Set();
|
|
308
|
+
const poor25 = (0, union_verify_1.lexPoverty)(subjTok, top1Tok, 0.25);
|
|
309
|
+
const poor50 = (0, union_verify_1.lexPoverty)(subjTok, top1Tok, 0.5);
|
|
310
|
+
const open = {
|
|
311
|
+
'uncited': uncited,
|
|
312
|
+
'poor@0.25': poor25,
|
|
313
|
+
'poor@0.5': poor50,
|
|
314
|
+
'or@0.25': uncited || poor25,
|
|
315
|
+
'or@0.5': uncited || poor50,
|
|
316
|
+
'admitPoor@0.25': poor25,
|
|
317
|
+
'admitPoor@0.5': poor50,
|
|
318
|
+
// @implements A-SPEC-492 — starved = poor AND the product's top-1 lexical score
|
|
319
|
+
// below a near-zero floor (consumed, not recomputed).
|
|
320
|
+
'admitStarved@1': poor25 && (result.candidates[0]?.score ?? 0) < 1,
|
|
321
|
+
'admitStarved@5': poor25 && (result.candidates[0]?.score ?? 0) < 5,
|
|
322
|
+
};
|
|
323
|
+
for (const g of SEM_GATES) {
|
|
324
|
+
// @implements A-SPEC-491 — the admit rows REPLACE the pool when their gate opens;
|
|
325
|
+
// every other row reranks it. A shut gate always scores the product order.
|
|
326
|
+
const action = g.startsWith('admit') ? sem.admit : sem.rerank;
|
|
327
|
+
semGateRows.get(g).push({ ranked: open[g] ? action : ranked,
|
|
328
|
+
truthFiles: c.files, selectedTests: [...result.relevantTests], truthTests: c.tests });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// @implements A-SPEC-467 — the PPR arm, on the PARENT-time graph only (using the live
|
|
333
|
+
// graph here would leak present-time edges into a point-in-time benchmark, the exact leak
|
|
334
|
+
// A-SPEC-378 closed for recency). One graph view and one seed set per case; the spread
|
|
335
|
+
// itself is milliseconds, so every config shares the expensive part.
|
|
336
|
+
// One parse serves both axes' arms (A-SPEC-467 localization, A-SPEC-468 impact).
|
|
337
|
+
const view = pprConfigs.length > 0 ? (0, assoc_arm_1.graphViewOf)(graph.dumpCanonical()) : null;
|
|
338
|
+
if (view !== null) {
|
|
339
|
+
const seeds = (0, assoc_arm_1.seedNodes)(c.subject, new Set(specs.map((sp) => sp.id)), view, w);
|
|
340
|
+
const topN = ranked.length || 10;
|
|
341
|
+
for (const config of pprConfigs) {
|
|
342
|
+
const { rerank, admit } = (0, assoc_arm_1.pprCaseRanking)(view, seeds, ranked, topN, config);
|
|
343
|
+
const bucket = pprOutcomes.get(config.label);
|
|
344
|
+
bucket.rerank.push({ ranked: rerank, truthFiles: c.files, selectedTests: [...result.relevantTests], truthTests: c.tests });
|
|
345
|
+
bucket.admit.push({ ranked: admit, truthFiles: c.files, selectedTests: [...result.relevantTests], truthTests: c.tests });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const unionList = [...new Set([...ranked, ...impacted])];
|
|
349
|
+
unionCases.push({ seed: '', expected: c.files, predicted: unionList });
|
|
350
|
+
// @implements A-SPEC-493 — the ceiling rows: a strict superset of the union prediction.
|
|
351
|
+
if (opts.semanticArm?.gates === true) {
|
|
352
|
+
for (const k of UPA_KS) {
|
|
353
|
+
upaCases.get(k).push({ seed: '', expected: c.files,
|
|
354
|
+
predicted: [...new Set([...unionList, ...semAdmitForCase.slice(0, k)])] });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// @implements A-SPEC-483 — the content class: bodies from the MATERIALIZED parent tree
|
|
358
|
+
// only. A body that cannot be read is a KEEP (signal absent, not verdict), matching the
|
|
359
|
+
// S-497 contract; the mention signal is bidirectional (candidate names the head, or the
|
|
360
|
+
// head names the candidate).
|
|
361
|
+
if (opts.contentVerify === true && unionList.length > 0) {
|
|
362
|
+
const bodyOf = new Map();
|
|
363
|
+
for (const f of unionList) {
|
|
364
|
+
try {
|
|
365
|
+
bodyOf.set(f, fs.readFileSync(path.join(dest, f), 'utf8').toLowerCase());
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
bodyOf.set(f, null);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const terms = (0, content_verify_1.topIdfTerms)((0, localize_1.significantTerms)(c.subject), w, 5);
|
|
372
|
+
const headFile = unionList[0];
|
|
373
|
+
const headSyms = scanned.filter((sf) => sf.sourcePath === headFile)
|
|
374
|
+
.flatMap((sf) => sf.symbols.map((sy) => sy.qualifiedName));
|
|
375
|
+
const headNames = (0, content_verify_1.mentionNames)(headFile, headSyms);
|
|
376
|
+
const headBody = bodyOf.get(headFile) ?? null;
|
|
377
|
+
const hitsOf = (f) => {
|
|
378
|
+
const b = bodyOf.get(f) ?? null;
|
|
379
|
+
return b === null ? null : (0, content_verify_1.bodyHitCount)(b, terms);
|
|
380
|
+
};
|
|
381
|
+
const mentionOf = (f) => {
|
|
382
|
+
const b = bodyOf.get(f) ?? null;
|
|
383
|
+
if (b === null)
|
|
384
|
+
return null;
|
|
385
|
+
if ((0, content_verify_1.mentionsAny)(b, headNames))
|
|
386
|
+
return true;
|
|
387
|
+
const base = (0, content_verify_1.mentionNames)(f, []);
|
|
388
|
+
return headBody !== null && base.length > 0 && (0, content_verify_1.mentionsAny)(headBody, base);
|
|
389
|
+
};
|
|
390
|
+
const accepts = {
|
|
391
|
+
body1: (f) => { const h = hitsOf(f); return h === null ? true : h >= 1; },
|
|
392
|
+
body2: (f) => { const h = hitsOf(f); return h === null ? true : h >= 2; },
|
|
393
|
+
mention: (f) => { const m = mentionOf(f); return m === null ? true : m; },
|
|
394
|
+
bodyOrMention: (f) => {
|
|
395
|
+
const h = hitsOf(f), m = mentionOf(f);
|
|
396
|
+
if (h === null && m === null)
|
|
397
|
+
return true;
|
|
398
|
+
return (h ?? 0) >= 1 || m === true;
|
|
399
|
+
},
|
|
400
|
+
bodyAndMention: (f) => {
|
|
401
|
+
const h = hitsOf(f), m = mentionOf(f);
|
|
402
|
+
if (h === null && m === null)
|
|
403
|
+
return true;
|
|
404
|
+
return (h ?? 0) >= 1 && m === true;
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
for (const s of CV_SIGNALS) {
|
|
408
|
+
for (const g of CV_GUARDS) {
|
|
409
|
+
cvCases.get(`${s}@${g}`).push({ seed: '', expected: c.files,
|
|
410
|
+
predicted: (0, union_verify_1.verifyEmission)(unionList, accepts[s], g) });
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// @implements A-SPEC-482 — prior and structure together, on the same emitted list.
|
|
415
|
+
if (opts.anchoredConsensus === true && unionList.length > 0) {
|
|
416
|
+
const aidx = (0, coherence_verify_1.buildCoherenceIndex)(view ?? (0, assoc_arm_1.graphViewOf)(graph.dumpCanonical()));
|
|
417
|
+
const acDeg = (0, coherence_verify_1.emissionDegree)(aidx, unionList, 'any');
|
|
418
|
+
for (const [m, h] of AC_ANCHORS) {
|
|
419
|
+
const anchor = (0, coherence_verify_1.coherentAccept)(aidx, new Set(unionList.slice(0, h)), m);
|
|
420
|
+
for (const d of AC_DS) {
|
|
421
|
+
for (const cb of AC_COMBS) {
|
|
422
|
+
acCases.get(`${m}@${h}@${d}@${cb}`).push({ seed: '', expected: c.files,
|
|
423
|
+
predicted: (0, union_verify_1.verifyEmission)(unionList, (0, coherence_verify_1.anchoredConsensusAccept)(anchor, acDeg, d, cb), h) });
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// @implements A-SPEC-481 — the emission judges itself: degree and largest-component
|
|
429
|
+
// verdicts over the same emitted list, no reference point taken from the ranking.
|
|
430
|
+
if (opts.coherenceRobust === true && unionList.length > 0) {
|
|
431
|
+
const ridx = (0, coherence_verify_1.buildCoherenceIndex)(view ?? (0, assoc_arm_1.graphViewOf)(graph.dumpCanonical()));
|
|
432
|
+
for (const rel of CR_RELS) {
|
|
433
|
+
const deg = (0, coherence_verify_1.emissionDegree)(ridx, unionList, rel);
|
|
434
|
+
const cluster = (0, coherence_verify_1.largestCluster)(ridx, unionList, rel);
|
|
435
|
+
for (const g of CR_GUARDS) {
|
|
436
|
+
for (const d of CR_DS) {
|
|
437
|
+
crCases.get(`deg@${rel}@${d}@${g}`).push({ seed: '', expected: c.files,
|
|
438
|
+
predicted: (0, union_verify_1.verifyEmission)(unionList, (f) => (deg.get(f) ?? 0) >= d, g) });
|
|
439
|
+
}
|
|
440
|
+
crCases.get(`clu@${rel}@0@${g}`).push({ seed: '', expected: c.files,
|
|
441
|
+
predicted: (0, union_verify_1.verifyEmission)(unionList, (f) => cluster.has(f), g) });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// @implements A-SPEC-480 — coherence over the exact emitted list, parent-time graph only.
|
|
446
|
+
if (opts.unionCoherence === true && unionList.length > 0) {
|
|
447
|
+
const cidx = (0, coherence_verify_1.buildCoherenceIndex)(view ?? (0, assoc_arm_1.graphViewOf)(graph.dumpCanonical()));
|
|
448
|
+
for (const mode of UC_MODES) {
|
|
449
|
+
for (const h of UC_HS) {
|
|
450
|
+
const head = new Set(unionList.slice(0, h));
|
|
451
|
+
ucCases.get(`${mode}@${h}`).push({ seed: '', expected: c.files,
|
|
452
|
+
predicted: (0, union_verify_1.verifyEmission)(unionList, (0, coherence_verify_1.coherentAccept)(cidx, head, mode), h) });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// @implements A-SPEC-479 — verify the exact list the union scores, in its emitted order.
|
|
457
|
+
// A candidate the signal cannot see (no doc text, no vector) is KEPT: recall is spent only
|
|
458
|
+
// where the verdict actually spoke.
|
|
459
|
+
if (opts.unionVerify !== undefined && unionList.length > 0) {
|
|
460
|
+
const docTextOf = new Map(scanned.map((f) => [f.sourcePath,
|
|
461
|
+
(f.sourcePath + ' ' + f.symbols.map((sy) => sy.qualifiedName).join(' ')).slice(0, 2000)]));
|
|
462
|
+
const known = unionList.map((f, i) => ({ text: docTextOf.get(f), i })).filter((x) => x.text !== undefined);
|
|
463
|
+
const [qv] = await opts.unionVerify.embedBatch([c.subject], 'query');
|
|
464
|
+
const dvs = known.length > 0
|
|
465
|
+
? await opts.unionVerify.embedBatch(known.map((x) => x.text), 'doc') : [];
|
|
466
|
+
const vecByFile = new Map(unionList.map((f) => [f, null]));
|
|
467
|
+
known.forEach((x, n) => vecByFile.set(unionList[x.i], dvs[n] ?? null));
|
|
468
|
+
const subjTokens = new Set((0, localize_1.significantTerms)(c.subject));
|
|
469
|
+
const tokByFile = new Map(scanned.map((f) => [f.sourcePath, (0, semantic_arm_1.fileTokens)(f)]));
|
|
470
|
+
for (const g of UV_GUARDS) {
|
|
471
|
+
for (const tau of UV_SEM_TAUS) {
|
|
472
|
+
const acc = (0, union_verify_1.semAcceptor)(qv ?? null, (f) => vecByFile.get(f) ?? null, tau);
|
|
473
|
+
uvCases.get(`sem@${tau}@${g}`).push({ seed: '', expected: c.files, predicted: (0, union_verify_1.verifyEmission)(unionList, acc, g) });
|
|
474
|
+
}
|
|
475
|
+
for (const tau of UV_LEX_TAUS) {
|
|
476
|
+
uvCases.get(`lex@${tau}@${g}`).push({
|
|
477
|
+
seed: '', expected: c.files,
|
|
478
|
+
predicted: (0, union_verify_1.verifyEmission)(unionList, (f) => {
|
|
479
|
+
const tok = tokByFile.get(f);
|
|
480
|
+
return tok === undefined ? true : (0, union_verify_1.lexScore)(subjTokens, tok, w) >= tau;
|
|
481
|
+
}, g),
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const seed = [...c.files].sort()[0];
|
|
487
|
+
const expected = c.files.filter((f) => f !== seed);
|
|
488
|
+
if (expected.length > 0) {
|
|
489
|
+
const run = (0, maintenance_analyze_1.analyzeMaintenance)({
|
|
490
|
+
request: c.subject, scanned, specs, graph, testAnchors: anchors, history: [],
|
|
491
|
+
changedFiles: [seed], changePrior, commitTextBoost,
|
|
492
|
+
basis: { head: parent, loadedBuild: 'b', diskBuild: 'b', specFingerprint: 'fp' },
|
|
493
|
+
coverage: { scannedFiles: scanned.length, skippedFiles: [], unsupportedLanguages: [] },
|
|
494
|
+
});
|
|
495
|
+
// @implements A-SPEC-469 — score the SHIPPED prediction surface (ablation must score
|
|
496
|
+
// what ships): the graded top-K, not the ungraded closure the gates keep.
|
|
497
|
+
impactCases.push({ seed, expected, predicted: (run.impacts?.rankedImpact ?? []).map((r) => r.file) });
|
|
498
|
+
// @implements A-SPEC-470 — the frozen string-property heuristic on the same case.
|
|
499
|
+
impactBaselineCases.push({ seed, expected,
|
|
500
|
+
predicted: (0, impact_baseline_1.impactBaselinePredict)(seed, scanned.map((f) => f.sourcePath)) });
|
|
501
|
+
impactCeilingCases.push({ expected, reachable: reachableFrom(graph, scanned, seed) });
|
|
502
|
+
// @implements A-SPEC-468 — the grading arm on the SAME case and the SAME expected set as
|
|
503
|
+
// the product's ungraded closure above, so the two share a denominator. Seed nodes are
|
|
504
|
+
// identified exactly as reachableFrom identifies them; the seed file cannot predict itself.
|
|
505
|
+
if (view !== null) {
|
|
506
|
+
const seedNodeIds = scanned.filter((f) => f.sourcePath === seed)
|
|
507
|
+
.flatMap((f) => f.symbols.flatMap((sym) => graph.codeNodeIds(sym.qualifiedName)))
|
|
508
|
+
.filter((id) => id.endsWith(`@${seed}`));
|
|
509
|
+
const impactSeeds = new Map(seedNodeIds.map((id) => [id, 1]));
|
|
510
|
+
for (const config of pprConfigs) {
|
|
511
|
+
for (const k of PPR_IMPACT_KS) {
|
|
512
|
+
const predicted = (0, assoc_arm_1.pprImpactPredict)(view, impactSeeds, seed, k, config);
|
|
513
|
+
pprImpactCases.get(`${config.label}@${k}`).push({ seed, expected, predicted });
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// @implements A-SPEC-472 — three arms, one oracle (membership in the case's expected
|
|
518
|
+
// set, revealed only for emitted files — the loop owns that invariant), one budget.
|
|
519
|
+
if (opts.iterativeArms === true) {
|
|
520
|
+
const iview = view ?? (0, assoc_arm_1.graphViewOf)(graph.dumpCanonical());
|
|
521
|
+
const seedNodeIds3 = scanned.filter((f) => f.sourcePath === seed)
|
|
522
|
+
.flatMap((f) => f.symbols.flatMap((sym) => graph.codeNodeIds(sym.qualifiedName)))
|
|
523
|
+
.filter((id) => id.endsWith(`@${seed}`));
|
|
524
|
+
const staticList = (0, assoc_arm_1.pprImpactRanked)(iview, new Map(seedNodeIds3.map((id) => [id, 1])), new Set([seed]), 20, assoc_arm_1.RANKED_IMPACT_CONFIG)
|
|
525
|
+
.map((r) => r.file);
|
|
526
|
+
const truthSet = new Set(expected);
|
|
527
|
+
for (const arm of ITER_ARMS) {
|
|
528
|
+
const rounds = (0, explore_1.exploreIterative)({ view: iview, seedFile: seed, seedNodeIds: seedNodeIds3, issueText: c.subject, idf: w, staticList }, arm, { E: ITER_E, R: ITER_R, verify: (f) => truthSet.has(f) });
|
|
529
|
+
// Cumulative trajectory, extended to ITER_R with the last state (fixed denominator
|
|
530
|
+
// for the macro mean — an early-terminating case keeps contributing its final state).
|
|
531
|
+
let hits = 0, emitted = 0;
|
|
532
|
+
const acc = iterAcc.get(arm);
|
|
533
|
+
for (let r = 0; r < ITER_R; r++) {
|
|
534
|
+
const row = rounds[r];
|
|
535
|
+
if (row !== undefined) {
|
|
536
|
+
hits += row.hits.length;
|
|
537
|
+
emitted += row.emitted.length;
|
|
538
|
+
}
|
|
539
|
+
const slot = acc[r] ?? (acc[r] = { recallSum: 0, precSum: 0, precN: 0, emittedSum: 0, n: 0 });
|
|
540
|
+
slot.recallSum += hits / expected.length;
|
|
541
|
+
if (emitted > 0) {
|
|
542
|
+
slot.precSum += hits / emitted;
|
|
543
|
+
slot.precN += 1;
|
|
544
|
+
}
|
|
545
|
+
slot.emittedSum += emitted;
|
|
546
|
+
slot.n += 1;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// @implements A-SPEC-471 — one scored list per case (the ADOPTED config), twenty cuts.
|
|
551
|
+
if (opts.impactFrontier === true) {
|
|
552
|
+
const fview = view ?? (0, assoc_arm_1.graphViewOf)(graph.dumpCanonical());
|
|
553
|
+
const seedNodeIds2 = scanned.filter((f) => f.sourcePath === seed)
|
|
554
|
+
.flatMap((f) => f.symbols.flatMap((sym) => graph.codeNodeIds(sym.qualifiedName)))
|
|
555
|
+
.filter((id) => id.endsWith(`@${seed}`));
|
|
556
|
+
const scoredList = (0, assoc_arm_1.pprImpactRanked)(fview, new Map(seedNodeIds2.map((id) => [id, 1])), new Set([seed]), 20, assoc_arm_1.RANKED_IMPACT_CONFIG);
|
|
557
|
+
for (const a of FRONTIER_ALPHAS) {
|
|
558
|
+
for (const k of FRONTIER_KS) {
|
|
559
|
+
frontierCases.get(`${a}@${k}`).push({ seed, expected, predicted: (0, assoc_arm_1.emitByRule)(scoredList, a, k) });
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// The lexical axis has a different, weaker denominator: ranking needs no graph path, but a
|
|
565
|
+
// file absent from the parent-time scan can never be named at all (A-SPEC-317). Conflating
|
|
566
|
+
// the two would move this axis with the impact axis's connectivity for no stated reason.
|
|
567
|
+
const scannedPaths = new Set(scanned.map((f) => f.sourcePath));
|
|
568
|
+
unionCeilingCases.push({ expected: c.files, reachable: c.files.filter((f) => scannedPaths.has(f)) });
|
|
569
|
+
}
|
|
570
|
+
finally {
|
|
571
|
+
graph.close();
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
finally {
|
|
575
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
const impact = (0, evaluation_metrics_1.impactMetrics)(impactCases);
|
|
579
|
+
const impactBaseline = (0, evaluation_metrics_1.impactMetrics)(impactBaselineCases);
|
|
580
|
+
const union = (0, evaluation_metrics_1.impactMetrics)(unionCases);
|
|
581
|
+
const impactCeiling = (0, evaluation_metrics_1.ceilingMetrics)(impactCeilingCases);
|
|
582
|
+
const unionCeiling = (0, evaluation_metrics_1.ceilingMetrics)(unionCeilingCases);
|
|
583
|
+
return {
|
|
584
|
+
corpus: path.basename(corpus.root),
|
|
585
|
+
cases: outcomes.length,
|
|
586
|
+
unmeasured,
|
|
587
|
+
casesWithoutSpecs,
|
|
588
|
+
metrics: (0, evaluation_metrics_1.evaluationMetrics)(outcomes),
|
|
589
|
+
impact, impactBaseline, union,
|
|
590
|
+
impactCeiling, unionCeiling,
|
|
591
|
+
idfArm: (0, evaluation_metrics_1.evaluationMetrics)(idfOutcomes),
|
|
592
|
+
...(pprConfigs.length > 0 ? {
|
|
593
|
+
pprArms: pprConfigs.map((c) => ({
|
|
594
|
+
label: c.label,
|
|
595
|
+
rerank: (0, evaluation_metrics_1.evaluationMetrics)(pprOutcomes.get(c.label).rerank),
|
|
596
|
+
admit: (0, evaluation_metrics_1.evaluationMetrics)(pprOutcomes.get(c.label).admit),
|
|
597
|
+
})),
|
|
598
|
+
pprImpact: pprConfigs.flatMap((c) => PPR_IMPACT_KS.map((k) => {
|
|
599
|
+
const m = (0, evaluation_metrics_1.impactMetrics)(pprImpactCases.get(`${c.label}@${k}`));
|
|
600
|
+
return { label: c.label, k, recall: m.recall, precision: m.precision };
|
|
601
|
+
})),
|
|
602
|
+
} : {}),
|
|
603
|
+
...(opts.impactFrontier === true ? {
|
|
604
|
+
impactFrontier: [...frontierCases.entries()].map(([key, cases]) => {
|
|
605
|
+
const [a, k] = key.split('@').map(Number);
|
|
606
|
+
const m = (0, evaluation_metrics_1.impactMetrics)(cases);
|
|
607
|
+
return { alpha: a, k, recall: m.recall, precision: m.precision };
|
|
608
|
+
}),
|
|
609
|
+
} : {}),
|
|
610
|
+
...(opts.iterativeArms === true ? {
|
|
611
|
+
iterative: [...iterAcc.entries()].flatMap(([arm, slots]) => slots.map((sl, i) => ({
|
|
612
|
+
arm: arm,
|
|
613
|
+
round: i + 1,
|
|
614
|
+
recall: sl.n > 0 ? sl.recallSum / sl.n : null,
|
|
615
|
+
precision: sl.precN > 0 ? sl.precSum / sl.precN : null,
|
|
616
|
+
emitted: sl.n > 0 ? sl.emittedSum / sl.n : 0,
|
|
617
|
+
}))),
|
|
618
|
+
} : {}),
|
|
619
|
+
...(opts.semanticArm !== undefined ? {
|
|
620
|
+
semanticArm: { rerank: (0, evaluation_metrics_1.evaluationMetrics)(semRerank), admit: (0, evaluation_metrics_1.evaluationMetrics)(semAdmit) },
|
|
621
|
+
} : {}),
|
|
622
|
+
...(opts.semanticArm?.gates === true ? {
|
|
623
|
+
semanticGates: [...semGateRows.entries()].map(([gate, rows]) => ({ gate, metrics: (0, evaluation_metrics_1.evaluationMetrics)(rows) })),
|
|
624
|
+
unionPlusAdmit: [...upaCases.entries()].map(([k, cases]) => {
|
|
625
|
+
const m = (0, evaluation_metrics_1.impactMetrics)(cases);
|
|
626
|
+
return { k, recall: m.recall, precision: m.precision };
|
|
627
|
+
}),
|
|
628
|
+
} : {}),
|
|
629
|
+
...(opts.unionVerify !== undefined ? {
|
|
630
|
+
unionVerify: [...uvCases.entries()].map(([key, cases]) => {
|
|
631
|
+
const [signal, tau, guard] = key.split('@');
|
|
632
|
+
const m = (0, evaluation_metrics_1.impactMetrics)(cases);
|
|
633
|
+
return { signal: signal, tau: Number(tau), guard: Number(guard), recall: m.recall, precision: m.precision };
|
|
634
|
+
}),
|
|
635
|
+
} : {}),
|
|
636
|
+
...(opts.unionCoherence === true ? {
|
|
637
|
+
unionCoherence: [...ucCases.entries()].map(([key, cases]) => {
|
|
638
|
+
const [mode, h] = key.split('@');
|
|
639
|
+
const m = (0, evaluation_metrics_1.impactMetrics)(cases);
|
|
640
|
+
return { mode: mode, h: Number(h), recall: m.recall, precision: m.precision };
|
|
641
|
+
}),
|
|
642
|
+
} : {}),
|
|
643
|
+
...(opts.contentVerify === true ? {
|
|
644
|
+
contentVerify: [...cvCases.entries()].map(([key, cases]) => {
|
|
645
|
+
const [signal, g] = key.split('@');
|
|
646
|
+
const m = (0, evaluation_metrics_1.impactMetrics)(cases);
|
|
647
|
+
return { signal, guard: Number(g), recall: m.recall, precision: m.precision };
|
|
648
|
+
}),
|
|
649
|
+
} : {}),
|
|
650
|
+
...(opts.anchoredConsensus === true ? {
|
|
651
|
+
anchoredConsensus: [...acCases.entries()].map(([key, cases]) => {
|
|
652
|
+
const [m, h, d, cb] = key.split('@');
|
|
653
|
+
const met = (0, evaluation_metrics_1.impactMetrics)(cases);
|
|
654
|
+
return { mode: m, h: Number(h), d: Number(d), comb: cb, recall: met.recall, precision: met.precision };
|
|
655
|
+
}),
|
|
656
|
+
} : {}),
|
|
657
|
+
...(opts.coherenceRobust === true ? {
|
|
658
|
+
coherenceRobust: [...crCases.entries()].map(([key, cases]) => {
|
|
659
|
+
const [fam, rel, d, g] = key.split('@');
|
|
660
|
+
const m = (0, evaluation_metrics_1.impactMetrics)(cases);
|
|
661
|
+
return {
|
|
662
|
+
family: (fam === 'deg' ? 'degree' : 'cluster'),
|
|
663
|
+
rel: rel,
|
|
664
|
+
d: fam === 'deg' ? Number(d) : null,
|
|
665
|
+
guard: Number(g), recall: m.recall, precision: m.precision,
|
|
666
|
+
};
|
|
667
|
+
}),
|
|
668
|
+
} : {}),
|
|
669
|
+
impactAchievement: (0, evaluation_metrics_1.achievementRate)(impact.recall, impactCeiling.ceiling),
|
|
670
|
+
unionAchievement: (0, evaluation_metrics_1.achievementRate)(union.recall, unionCeiling.ceiling),
|
|
671
|
+
elapsedMs: Date.now() - started,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Which files the impact walk could POSSIBLY have reached from the seed — the ceiling's numerator.
|
|
676
|
+
*
|
|
677
|
+
* CODE-to-CODE edges only. The permissive walk reaches a second file through the SPEC node they both
|
|
678
|
+
* anchor, which is a path no impact algorithm should follow; counting it would raise the denominator
|
|
679
|
+
* and make the algorithm look worse for a reason that is not about the algorithm (A-SPEC-307).
|
|
680
|
+
*/
|
|
681
|
+
function reachableFrom(graph, scanned, seed) {
|
|
682
|
+
const adj = new Map();
|
|
683
|
+
const link = (a, b) => {
|
|
684
|
+
(adj.get(a) ?? adj.set(a, []).get(a)).push(b);
|
|
685
|
+
(adj.get(b) ?? adj.set(b, []).get(b)).push(a);
|
|
686
|
+
};
|
|
687
|
+
for (const line of graph.dumpCanonical().split('\n')) {
|
|
688
|
+
const cols = line.split('\t');
|
|
689
|
+
if (cols[0] !== 'E' || !cols[1]?.startsWith('CODE:') || !cols[2]?.startsWith('CODE:'))
|
|
690
|
+
continue;
|
|
691
|
+
link(cols[1], cols[2]);
|
|
692
|
+
}
|
|
693
|
+
const seen = new Set(scanned.filter((f) => f.sourcePath === seed)
|
|
694
|
+
.flatMap((f) => f.symbols.flatMap((sym) => graph.codeNodeIds(sym.qualifiedName)))
|
|
695
|
+
.filter((id) => id.endsWith(`@${seed}`)));
|
|
696
|
+
let wave = [...seen];
|
|
697
|
+
while (wave.length > 0) {
|
|
698
|
+
const next = [];
|
|
699
|
+
for (const id of wave)
|
|
700
|
+
for (const n of adj.get(id) ?? []) {
|
|
701
|
+
if (seen.has(n))
|
|
702
|
+
continue;
|
|
703
|
+
seen.add(n);
|
|
704
|
+
next.push(n);
|
|
705
|
+
}
|
|
706
|
+
wave = next;
|
|
707
|
+
}
|
|
708
|
+
return [...seen].filter((id) => id.startsWith('CODE:') && id.includes('@'))
|
|
709
|
+
.map((id) => id.slice(id.lastIndexOf('@') + 1));
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* @implements A-SPEC-475
|
|
713
|
+
* One case of the semantic arm, pure given an embedder: documents follow the S-491 convention
|
|
714
|
+
* (`sourcePath + qualified symbol names`, 2000 chars) so numbers stay comparable across the
|
|
715
|
+
* traceability and localization experiments; the query is the commit subject as-is. rerank reuses
|
|
716
|
+
* `rerankPool` — the pool-stays-fixed invariant is shared code, not a re-promise.
|
|
717
|
+
*/
|
|
718
|
+
async function semanticCaseRanking(files, subject, pool, topN, embedBatch) {
|
|
719
|
+
const docTexts = files.map((f) => (f.sourcePath + ' ' + f.symbolNames.join(' ')).slice(0, 2000));
|
|
720
|
+
const dv = await embedBatch(docTexts, 'doc');
|
|
721
|
+
const [qv] = await embedBatch([subject], 'query');
|
|
722
|
+
const sim = new Map();
|
|
723
|
+
files.forEach((f, i) => sim.set(f.sourcePath, (0, dense_retrieval_1.cosine)(qv, dv[i])));
|
|
724
|
+
const admit = [...sim.entries()]
|
|
725
|
+
.sort((a, b) => (b[1] - a[1]) || (a[0] < b[0] ? -1 : 1))
|
|
726
|
+
.slice(0, topN)
|
|
727
|
+
.map(([f]) => f);
|
|
728
|
+
return { rerank: (0, assoc_arm_1.rerankPool)(pool, sim), admit, docTexts };
|
|
729
|
+
}
|