@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,271 @@
|
|
|
1
|
+
import type { ScannedFile } from '../cpg/cpg-scanner';
|
|
2
|
+
import type { Spec } from '../spec/spec-parser';
|
|
3
|
+
/** Tokens of a file as the ranker sees it: its symbol names and its path. */
|
|
4
|
+
export declare function fileTokens(f: ScannedFile): Set<string>;
|
|
5
|
+
/**
|
|
6
|
+
* Inverse document frequency over the scanned corpus.
|
|
7
|
+
*
|
|
8
|
+
* Smoothed (`(N+1)/(df+1)`) and floored at zero: a term present in every file must be WEAK evidence,
|
|
9
|
+
* never negative evidence. Unsmoothed IDF would make it exactly 0 and let a file that matches only
|
|
10
|
+
* ubiquitous terms tie with a file that matches nothing, which reads as "no opinion" when the truth
|
|
11
|
+
* is "a little opinion".
|
|
12
|
+
*/
|
|
13
|
+
export declare function corpusIdf(scanned: readonly ScannedFile[]): Map<string, number>;
|
|
14
|
+
export interface ArmHit {
|
|
15
|
+
file: string;
|
|
16
|
+
score: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Rank files by IDF-weighted term overlap.
|
|
20
|
+
*
|
|
21
|
+
* Symbol matches stay worth 3× a path match, as in the product's scorer — changing two things at
|
|
22
|
+
* once would leave the measurement unable to say which one moved. The only difference from the
|
|
23
|
+
* current arm is that each match is multiplied by how rare its term is in this corpus.
|
|
24
|
+
*/
|
|
25
|
+
export declare function rankIdf(issueText: string, scanned: readonly ScannedFile[], topN: number): ArmHit[];
|
|
26
|
+
/** How much a term counts. The product's scorer is this arm with `() => 1`. */
|
|
27
|
+
export type TermWeight = (term: string) => number;
|
|
28
|
+
/**
|
|
29
|
+
* IDF, rescaled so the MEAN weight over the corpus vocabulary is 1.
|
|
30
|
+
*
|
|
31
|
+
* Without this the arm would not be a one-variable change. Raw IDF values sit well below 1 on a
|
|
32
|
+
* corpus of a few hundred files, so every lexical contribution shrinks at once while the spec-anchor
|
|
33
|
+
* bonus (+4/sqrt(anchors)) and the citation bonus (+20/sqrt(anchors)) keep their size — the arm would
|
|
34
|
+
* then be measuring how much the graph bonuses dominate, not how much term rarity helps. Rescaling
|
|
35
|
+
* keeps the lexical term's total magnitude comparable and changes only its DISTRIBUTION across
|
|
36
|
+
* terms, which is the hypothesis under test.
|
|
37
|
+
*
|
|
38
|
+
* An unseen term weighs exactly 1: a word absent from the corpus is not evidence against a file, and
|
|
39
|
+
* mapping it to 0 would silently delete the request's most distinctive vocabulary.
|
|
40
|
+
*/
|
|
41
|
+
export declare function normalisedIdf(scanned: readonly ScannedFile[]): Map<string, number>;
|
|
42
|
+
/**
|
|
43
|
+
* The product's scorer with the term weight lifted out as a parameter.
|
|
44
|
+
*
|
|
45
|
+
* This mirrors `localizeIssue` deliberately and the mirror is load-bearing, so a test pins it: with
|
|
46
|
+
* `() => 1` this function must return the product's ranking and its scores EXACTLY. If someone
|
|
47
|
+
* changes the product's scoring and not this one, that test goes red rather than the replay quietly
|
|
48
|
+
* reporting a difference between rankers that is really a difference between vintages.
|
|
49
|
+
*/
|
|
50
|
+
export declare function rankWeighted(issueText: string, scanned: readonly ScannedFile[], specs: readonly Spec[], topN: number, weight: TermWeight,
|
|
51
|
+
/**
|
|
52
|
+
* @implements A-SPEC-352
|
|
53
|
+
* Weighted spec gate. Absent = the product's `overlap >= 2` count.
|
|
54
|
+
*
|
|
55
|
+
* Passed in here rather than reimplemented by the caller for a reason paid for three times: the
|
|
56
|
+
* first IDF arm hand-rolled a scorer and silently dropped the spec and citation bonuses; a probe
|
|
57
|
+
* of this very gate hand-rolled one again and dropped the citation path, whose +20 bonus is the
|
|
58
|
+
* largest single gain this repository has measured. Both produced confident, worthless numbers.
|
|
59
|
+
* Every arm goes through this function so the features it does NOT vary cannot go missing.
|
|
60
|
+
*/
|
|
61
|
+
gateThreshold?: number): ArmHit[];
|
|
62
|
+
export interface ExpansionOptions {
|
|
63
|
+
/** How far to walk from a matched spec. 0 reproduces the unexpanded arm exactly. */
|
|
64
|
+
hops: number;
|
|
65
|
+
/** Weight multiplier per hop. 0 disables expansion; 1 would treat a neighbour like a hit. */
|
|
66
|
+
decay: number;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Rank with GraphRAG-style expansion over the spec graph.
|
|
70
|
+
*
|
|
71
|
+
* The gap this addresses is the one graph retrieval exists for: the answer does not contain the
|
|
72
|
+
* request's words, but it is the neighbour of something that does. The lexical layer scores a file
|
|
73
|
+
* only if the request's terms reach the file or a spec it anchors — one hop. A sibling requirement,
|
|
74
|
+
* written in different words for the same feature, is invisible however closely it is related.
|
|
75
|
+
*
|
|
76
|
+
* Deliberately weaker than a hit at every hop: the bonus is the matched-spec bonus times `decay^k`.
|
|
77
|
+
* A neighbour is evidence, not an answer, and if decay ever let it outrank a direct match the
|
|
78
|
+
* retrieval would be trading the answer for its neighbourhood. A test pins that ordering.
|
|
79
|
+
*
|
|
80
|
+
* Everything else is `rankWeighted` unchanged — the one-variable rule from A-SPEC-349 applies here
|
|
81
|
+
* too, or a win could not be attributed to expansion rather than to weighting.
|
|
82
|
+
*/
|
|
83
|
+
export declare function rankExpanded(issueText: string, scanned: readonly ScannedFile[], specs: readonly Spec[], topN: number, weight: TermWeight, opts: ExpansionOptions): ArmHit[];
|
|
84
|
+
/**
|
|
85
|
+
* Which A-SPECs the request is taken to have matched — the entry point into the knowledge graph.
|
|
86
|
+
*
|
|
87
|
+
* The product's rule counts overlapping terms: two shared words admit a spec, one does not (unless
|
|
88
|
+
* the request is very short). That treats a shared `handler` exactly like a shared `quarantine`, and
|
|
89
|
+
* it costs more here than it does in file scoring: an admitted spec sprays its bonus over EVERY file
|
|
90
|
+
* anchoring it, so one wrongly-opened gate promotes a whole group of unrelated files at once.
|
|
91
|
+
*
|
|
92
|
+
* Weighing the overlap instead of counting it means a request has to bring either several ordinary
|
|
93
|
+
* words or one genuinely distinctive one. With `weight = () => 1` and `threshold = 2` this is the
|
|
94
|
+
* product's original rule exactly, which is what makes any measured difference attributable.
|
|
95
|
+
*
|
|
96
|
+
* A-SPEC only: files anchor A-SPECs, so admitting any other kind can never pull a file through the
|
|
97
|
+
* hop and would only overstate what influenced the ranking.
|
|
98
|
+
*/
|
|
99
|
+
export declare function matchedSpecsWeighted(issueText: string, specs: readonly Spec[], weight: TermWeight, threshold: number): string[];
|
|
100
|
+
/**
|
|
101
|
+
* The spec bonus, damped by how much code the spec covers.
|
|
102
|
+
*
|
|
103
|
+
* The existing normalisation divides by how many anchors a FILE carries (A-SPEC-270) — it answers
|
|
104
|
+
* "how much of this file's identity is this spec?". It never asks the other question: "how much of
|
|
105
|
+
* this spec's territory is this file?". A spec spanning twenty files says little about any one of
|
|
106
|
+
* them; a spec covering one says a great deal, and today they pay the same +4.
|
|
107
|
+
*
|
|
108
|
+
* That asymmetry is invisible in a repository with hundreds of narrow specs and decisive in one with
|
|
109
|
+
* a few broad ones — which is exactly where the measured precision loss appeared (A-SPEC-362: on a
|
|
110
|
+
* 54-spec corpus the product loses Top-5 recall and Top-10 precision to plain word matching, and the
|
|
111
|
+
* weighting-only arm is indistinguishable from that baseline).
|
|
112
|
+
*
|
|
113
|
+
* sqrt, matching the existing normalisation's shape: a one-file spec is untaxed, a twenty-file spec
|
|
114
|
+
* is damped by ~4.5x rather than erased. Damping to zero would delete the very hop that carries all
|
|
115
|
+
* of this harness's measured advantage.
|
|
116
|
+
*/
|
|
117
|
+
export declare function rankBreadthDamped(issueText: string, scanned: readonly ScannedFile[], specs: readonly Spec[], topN: number, weight: TermWeight, gateThreshold?: number): ArmHit[];
|
|
118
|
+
/**
|
|
119
|
+
* How much of the corpus carries an `@implements` anchor at all.
|
|
120
|
+
*
|
|
121
|
+
* The spec layer's evidence is only representative when a decent share of the tree is governed. If
|
|
122
|
+
* five files in a thousand carry anchors, matching one of their specs says something about those
|
|
123
|
+
* five and nothing about the other nine hundred and ninety-five — yet the bonus is paid at full
|
|
124
|
+
* strength and displaces lexically correct candidates.
|
|
125
|
+
*/
|
|
126
|
+
export declare function anchorCoverage(scanned: readonly ScannedFile[], specs?: readonly Spec[]): number;
|
|
127
|
+
/**
|
|
128
|
+
* The spec bonus scaled by how much of the tree the spec layer actually covers.
|
|
129
|
+
*
|
|
130
|
+
* Measured on one repository with only the spec count varied (A-SPEC-364): 585 specs gave Top-10
|
|
131
|
+
* recall 0.770, but 146 gave 0.253 and 59 gave 0.264 — BELOW the 0.305 of no spec layer at all. A
|
|
132
|
+
* partly populated spec store is worse than an empty one, and every project that adopts Holmes
|
|
133
|
+
* passes through that valley on its way to the peak. jarvis, at 54 specs, is sitting in it.
|
|
134
|
+
*
|
|
135
|
+
* Scaling by coverage makes the layer fade in rather than switch on: at zero coverage the ranking is
|
|
136
|
+
* exactly the lexical arm (the floor a project starts from), and at full coverage the bonus is
|
|
137
|
+
* unchanged (the peak this repository measures). No threshold, because a threshold would create a
|
|
138
|
+
* cliff where authoring one more spec changes every ranking at once.
|
|
139
|
+
*/
|
|
140
|
+
export declare function rankCoverageAware(issueText: string, scanned: readonly ScannedFile[], specs: readonly Spec[], topN: number, weight: TermWeight, gateThreshold?: number): ArmHit[];
|
|
141
|
+
/**
|
|
142
|
+
* The gate, chosen by whether the request names a spec at all.
|
|
143
|
+
*
|
|
144
|
+
* No single threshold serves both conditions. Measured over 60 point-in-time cases (A-SPEC-369):
|
|
145
|
+
*
|
|
146
|
+
* threshold 1 — cited Top-10 recall 0.770, uncited 0.275 (a no-graph baseline gets 0.357)
|
|
147
|
+
* threshold 4 — cited 0.743 and Top-1 0.333 → 0.283, uncited 0.349
|
|
148
|
+
*
|
|
149
|
+
* Under a citation, the named spec locates the request in the corpus, and the specs that merely
|
|
150
|
+
* share vocabulary with it are corroborated rather than speculative — the permissive gate pays. With
|
|
151
|
+
* no citation, those same matches are twenty-six unanchored guesses per case whose bonuses bury the
|
|
152
|
+
* lexical signal, and the strict gate keeps them out.
|
|
153
|
+
*
|
|
154
|
+
* The condition is read from the request itself, and only a citation the store can RESOLVE counts:
|
|
155
|
+
* a stray `A-SPEC-999` in a bug report must not unlock the permissive path.
|
|
156
|
+
*/
|
|
157
|
+
export declare function rankCitationConditioned(issueText: string, scanned: readonly ScannedFile[], specs: readonly Spec[], topN: number, weight: TermWeight, citedThreshold: number, uncitedThreshold: number): ArmHit[];
|
|
158
|
+
/**
|
|
159
|
+
* Drop the lexically-matched specs when too many of them match.
|
|
160
|
+
*
|
|
161
|
+
* Conditioning on whether the request cited a spec repaired this repository and cost the second
|
|
162
|
+
* corpus its lead, because that corpus cites nothing and would always take the strict path. Both
|
|
163
|
+
* measurements point at the same underlying quantity: HOW MANY specs the request matched. jarvis
|
|
164
|
+
* matches 0.87 per case, this repository 26.6 (A-SPEC-367).
|
|
165
|
+
*
|
|
166
|
+
* One matching spec is a claim about where the request belongs. Twenty-six matching specs is a fact
|
|
167
|
+
* about the request's vocabulary — it was written in words the corpus uses everywhere — and their
|
|
168
|
+
* bonuses, spread over a hundred anchored files, bury the lexical ranking that would otherwise work.
|
|
169
|
+
*
|
|
170
|
+
* A citation is never capped: it names something rather than resembling it, and it is the one signal
|
|
171
|
+
* in this scorer measured to carry the localization result.
|
|
172
|
+
*/
|
|
173
|
+
export declare function rankCapped(issueText: string, scanned: readonly ScannedFile[], specs: readonly Spec[], topN: number, weight: TermWeight, gateThreshold: number, maxMatchedSpecs: number): ArmHit[];
|
|
174
|
+
/**
|
|
175
|
+
* The cap, applied only to requests that named nothing.
|
|
176
|
+
*
|
|
177
|
+
* The two halves the measurements each found: a citation corroborates the specs that merely share
|
|
178
|
+
* vocabulary with the request, so with one present they help (dropping them cost Top-10 recall
|
|
179
|
+
* 0.770 → 0.743). Without one, a large number of matches is a fact about the request's wording
|
|
180
|
+
* rather than evidence, and their bonuses bury the lexical ranking (0.275 against a 0.357 baseline).
|
|
181
|
+
* A SMALL number of matches with no citation is still meaningful — that is the second corpus, at
|
|
182
|
+
* 0.87 matches per case, and capping it there threw away the modest lead it had.
|
|
183
|
+
*
|
|
184
|
+
* So: exempt cited requests, and cap only the uncited ones that matched more specs than a request
|
|
185
|
+
* can plausibly be about.
|
|
186
|
+
*/
|
|
187
|
+
export declare function rankUncitedCapped(issueText: string, scanned: readonly ScannedFile[], specs: readonly Spec[], topN: number, weight: TermWeight, gateThreshold: number, maxMatchedSpecs: number): ArmHit[];
|
|
188
|
+
/**
|
|
189
|
+
* One `calls` hop out from the lexical hits, added at a discount.
|
|
190
|
+
*
|
|
191
|
+
* With no citation the spec layer is off and the ranking is exactly what grepping the request's
|
|
192
|
+
* words would produce. The call graph is the only structure left that could beat it, and A-SPEC-318
|
|
193
|
+
* measured the opportunity: 13 missed files one `calls` hop away, all 58 hop edges being calls, none
|
|
194
|
+
* imports or inherits. That was measured under cited requests, where the spec layer had already
|
|
195
|
+
* found most of the answer — uncited, the lexical hits are the only seed, so the hop has more to add.
|
|
196
|
+
*
|
|
197
|
+
* Half the seed's score, and never more: a callee is evidence about the seed's neighbourhood, not
|
|
198
|
+
* about itself. If a hop could outrank a direct hit, the ranking would be trading the answer for its
|
|
199
|
+
* surroundings, which is the failure the spec-graph expansion arm was rejected for.
|
|
200
|
+
*/
|
|
201
|
+
export declare function rankWithCallHop(issueText: string, scanned: readonly ScannedFile[], specs: readonly Spec[], topN: number, weight: TermWeight, gateThreshold: number, maxMatchedSpecs: number,
|
|
202
|
+
/** @implements A-SPEC-394 — use import edges to bind names the global map calls ambiguous. */
|
|
203
|
+
importAware?: boolean): ArmHit[];
|
|
204
|
+
/**
|
|
205
|
+
* @implements A-SPEC-395
|
|
206
|
+
* The product's scorer with each layer switchable, so a cost can be attributed instead of guessed.
|
|
207
|
+
*
|
|
208
|
+
* On history sampled away from the benchmarker's own commits, this ranking scores BELOW the no-graph
|
|
209
|
+
* baseline (Top-10 recall 0.618 against 0.668). The contaminated slice showed the opposite, so the
|
|
210
|
+
* regression is newly visible and it is shipped. Four layers sit between the two — corpus IDF
|
|
211
|
+
* weighting, the spec gate and anchor bonus, the citation bonus, and the vendored demotion — and
|
|
212
|
+
* turning them off one at a time is the only way to learn which one costs.
|
|
213
|
+
*/
|
|
214
|
+
export interface AblationOptions {
|
|
215
|
+
idf: boolean;
|
|
216
|
+
specBonus: boolean;
|
|
217
|
+
vendorDemotion: boolean;
|
|
218
|
+
}
|
|
219
|
+
export declare function ablate(issueText: string, scanned: readonly ScannedFile[], specs: readonly Spec[], topN: number, opts: AblationOptions): ArmHit[];
|
|
220
|
+
/**
|
|
221
|
+
* @implements A-SPEC-396
|
|
222
|
+
* Limit how many files each matched spec may lift.
|
|
223
|
+
*
|
|
224
|
+
* The ablation isolated the cost: turning the spec bonus off restores the baseline exactly, so that
|
|
225
|
+
* layer buys Top-1 (0.244 → 0.267 here, 0.400 → 0.433 on jarvis) and sells precision (0.156 → 0.104).
|
|
226
|
+
*
|
|
227
|
+
* Three earlier attempts all changed WHICH specs are admitted — breadth damping, coverage scaling,
|
|
228
|
+
* a cap on matched specs — and all three were rejected. This changes something else: once a spec is
|
|
229
|
+
* admitted, every file anchoring it rises together, and that simultaneity is the shape of the
|
|
230
|
+
* precision loss. Ranking a spec's anchored files by their own lexical evidence and lifting only the
|
|
231
|
+
* strongest few keeps the top of the list while leaving the tail alone.
|
|
232
|
+
*
|
|
233
|
+
* The cost is explicit: a file with no lexical evidence can no longer be lifted by its spec alone,
|
|
234
|
+
* so the layer loses the files it uniquely contributed. Whether that trade is positive is the
|
|
235
|
+
* measurement, not the assumption.
|
|
236
|
+
*/
|
|
237
|
+
export declare function rankSpecLiftLimited(issueText: string, scanned: readonly ScannedFile[], specs: readonly Spec[], topN: number, weight: TermWeight, gateThreshold: number, liftPerSpec: number): ArmHit[];
|
|
238
|
+
/**
|
|
239
|
+
* @implements A-SPEC-397
|
|
240
|
+
* The spec bonus with its magnitude as a parameter.
|
|
241
|
+
*
|
|
242
|
+
* Four arms failed to recover the 0.052 of precision the layer costs, and each changed a different
|
|
243
|
+
* thing: which specs are admitted (breadth damping, coverage scaling, a cap on matches) and how far
|
|
244
|
+
* an admitted one reaches (the lift limit). Magnitude was never touched.
|
|
245
|
+
*
|
|
246
|
+
* The default is 4 per anchor against a lexical symbol hit worth 3 and a path hit worth 1, so a
|
|
247
|
+
* single matched spec outweighs a symbol match — a file whose only evidence is one anchor can
|
|
248
|
+
* outrank a file whose name actually contains the request's words. That is a plausible source of the
|
|
249
|
+
* tail pollution and it is the last untried one.
|
|
250
|
+
*
|
|
251
|
+
* Reducing it should cost Top-1 as it recovers precision, so both are reported: a setting that fixes
|
|
252
|
+
* one by destroying the other is not a fix.
|
|
253
|
+
*/
|
|
254
|
+
export declare function rankWithBonusScale(issueText: string, scanned: readonly ScannedFile[], specs: readonly Spec[], topN: number, weight: TermWeight, gateThreshold: number, bonusScale: number): ArmHit[];
|
|
255
|
+
/**
|
|
256
|
+
* @implements A-SPEC-398
|
|
257
|
+
* The spec bonus may REORDER, but may not ADMIT.
|
|
258
|
+
*
|
|
259
|
+
* Five arms failed to recover the 0.052 of precision the spec layer costs, and counting the lists
|
|
260
|
+
* showed why none of them could: the baseline returns 7.09 files per case and the product returns
|
|
261
|
+
* 9.27, filling all ten slots in 36 of 45 cases against the baseline's 16. The layer is not ranking
|
|
262
|
+
* the same set differently — it is **adding about two files per case**, and those additions are
|
|
263
|
+
* mostly wrong. Every arm so far reordered or rescaled; none declined to add.
|
|
264
|
+
*
|
|
265
|
+
* So: a file with lexical evidence keeps its bonus and can move up. A file whose only evidence is an
|
|
266
|
+
* anchor does not enter the list at all.
|
|
267
|
+
*
|
|
268
|
+
* The cost is the one thing the spec layer uniquely did — reaching a file the request's words never
|
|
269
|
+
* touch. That is a real loss and the measurement has to weigh it, not assume it away.
|
|
270
|
+
*/
|
|
271
|
+
export declare function rankSpecReorderOnly(issueText: string, scanned: readonly ScannedFile[], specs: readonly Spec[], topN: number, weight: TermWeight, gateThreshold: number): ArmHit[];
|