pi-supernova 0.8.2 → 0.9.0
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/README.md +188 -51
- package/docs/CHANGELOG.md +86 -1
- package/docs/TOKEN_COSTS.md +38 -0
- package/index.js +10 -175
- package/package.json +2 -1
- package/src/adapters/bash.js +14 -30
- package/src/adapters/errors.js +1 -9
- package/src/adapters/read-focus.js +98 -0
- package/src/adapters/read-image.js +51 -0
- package/src/adapters/read-json.js +42 -0
- package/src/adapters/read-text.js +71 -0
- package/src/adapters/read.js +66 -635
- package/src/bridge/catalog.js +3 -2
- package/src/bridge/host-bridge.js +35 -167
- package/src/bridge/tool-registry.js +104 -0
- package/src/bridge/trace.js +41 -0
- package/src/context/evidence-graph.js +249 -0
- package/src/context/evidence-rank.js +153 -0
- package/src/context/evidence.js +10 -424
- package/src/context/query.js +71 -0
- package/src/context/repo-index.js +8 -162
- package/src/context/search-files.js +19 -0
- package/src/context/search.js +2 -24
- package/src/context/snap-search.js +202 -0
- package/src/context/snap.js +5 -266
- package/src/context/source-entry.js +112 -0
- package/src/contract/bash.js +6 -1
- package/src/contract/program.js +36 -0
- package/src/contract/read.js +8 -53
- package/src/fs/check.js +1 -1
- package/src/fs/commit.js +161 -0
- package/src/fs/diff.js +11 -15
- package/src/fs/directory.js +79 -0
- package/src/fs/file-io.js +100 -0
- package/src/fs/glob.js +54 -0
- package/src/fs/json-size.js +54 -0
- package/src/fs/lines.js +117 -0
- package/src/fs/read-window.js +74 -0
- package/src/fs/session-resource.js +50 -0
- package/src/fs/text-ops.js +7 -227
- package/src/fs/vfs.js +5 -239
- package/src/fs/workspace.js +2 -1
- package/src/output/bottleneck.js +13 -67
- package/src/output/final.js +114 -0
- package/src/output/format.js +94 -5
- package/src/output/outcome.js +91 -0
- package/src/runtime/batch-input.js +68 -0
- package/src/runtime/guest-api.js +281 -0
- package/src/runtime/guest-worker.js +62 -333
- package/src/runtime/parallel.js +41 -39
- package/src/runtime/program-batch.js +21 -75
- package/src/runtime/program-file.js +3 -11
- package/src/runtime/program.js +141 -0
- package/src/runtime/reference.js +6 -5
- package/src/runtime/runtime.js +77 -253
- package/src/runtime/worker-pool.js +91 -0
- package/src/shared/decode.js +22 -8
- package/src/shared/image-worker.js +30 -0
- package/src/shared/image.js +78 -0
- package/src/shared/png.js +57 -0
- package/src/shared/result.js +77 -0
- package/src/shared/syntax-context.js +61 -3
- package/src/ui/host-render.js +104 -0
- package/src/ui/progress.js +51 -0
- package/src/ui/render.js +21 -421
- package/src/ui/trace.js +277 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
const HUB_FRACTION = 0.25;
|
|
2
|
+
|
|
3
|
+
const HUB_MIN = 8;
|
|
4
|
+
|
|
5
|
+
function splitIdentifier(name) {
|
|
6
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function spanLines(span) {
|
|
10
|
+
return span.lower.slice(span.start - 1, span.end);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ---- eq.3 / eq.4: entity–context graph over candidate spans ----
|
|
14
|
+
|
|
15
|
+
function countSpanEntities(span, entityNames) {
|
|
16
|
+
const counts = new Map();
|
|
17
|
+
let total = 0;
|
|
18
|
+
const { idents } = span.lines;
|
|
19
|
+
|
|
20
|
+
for (let li = span.start - 1; li < span.end; li++) {
|
|
21
|
+
for (const word of idents[li]) {
|
|
22
|
+
if (!entityNames.has(word)) continue;
|
|
23
|
+
counts.set(word, (counts.get(word) || 0) + 1);
|
|
24
|
+
total += 1;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { counts, total };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function weightsFromCounts(counts, total, entitySpans, spanId) {
|
|
32
|
+
const weights = new Map();
|
|
33
|
+
|
|
34
|
+
for (const [e, c] of counts) {
|
|
35
|
+
weights.set(e, c / total); // eq.4
|
|
36
|
+
|
|
37
|
+
if (!entitySpans.has(e)) entitySpans.set(e, new Set());
|
|
38
|
+
entitySpans.get(e).add(spanId);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return weights;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function hubEntities(entitySpans, spans) {
|
|
45
|
+
// Entities present in a large share of spans (isString, path, …) carry no query signal; keep them out of propagation.
|
|
46
|
+
const hubLimit = Math.max(HUB_MIN, Math.floor(spans.length * HUB_FRACTION));
|
|
47
|
+
const hubs = new Set();
|
|
48
|
+
|
|
49
|
+
for (const [entity, ids] of entitySpans) {
|
|
50
|
+
if (ids.size > hubLimit) hubs.add(entity);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return hubs;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildGraph(spans) {
|
|
57
|
+
const entityNames = new Set(spans.map((s) => s.name).filter((n) => n && n.length > 2));
|
|
58
|
+
const spanEntities = new Map(); // span.id → Map(entity → w(d,e))
|
|
59
|
+
const entitySpans = new Map(); // entity → Set(span.id)
|
|
60
|
+
|
|
61
|
+
for (const span of spans) {
|
|
62
|
+
const { counts, total } = countSpanEntities(span, entityNames);
|
|
63
|
+
spanEntities.set(span.id, weightsFromCounts(counts, total, entitySpans, span.id));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { entityNames, spanEntities, entitySpans, hubs: hubEntities(entitySpans, spans), byId: new Map(spans.map((s) => [s.id, s])) };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---- eq.8 / eq.9: entity activation and one propagation step ----
|
|
70
|
+
|
|
71
|
+
function lexicalSim(a, b) {
|
|
72
|
+
const ta = new Set(splitIdentifier(a));
|
|
73
|
+
const tb = new Set(splitIdentifier(b));
|
|
74
|
+
|
|
75
|
+
if (ta.size === 0 || tb.size === 0) return 0;
|
|
76
|
+
let inter = 0;
|
|
77
|
+
|
|
78
|
+
for (const t of ta) if (tb.has(t)) inter++;
|
|
79
|
+
|
|
80
|
+
return inter / (ta.size + tb.size - inter);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function activateEntities(profile, graph) {
|
|
84
|
+
const eta = new Map();
|
|
85
|
+
const anchors = profile.subjects.length ? profile.subjects : profile.stems;
|
|
86
|
+
|
|
87
|
+
for (const anchor of anchors) {
|
|
88
|
+
let best = null;
|
|
89
|
+
let bestSim = 0;
|
|
90
|
+
|
|
91
|
+
for (const e of graph.entityNames) {
|
|
92
|
+
const sim = e.toLowerCase() === anchor.toLowerCase() ? 1 : lexicalSim(e, anchor);
|
|
93
|
+
|
|
94
|
+
if (sim > bestSim) {
|
|
95
|
+
bestSim = sim;
|
|
96
|
+
best = e;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (best && bestSim >= 0.5) eta.set(best, Math.max(eta.get(best) || 0, bestSim)); // eq.8
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return eta;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function querySim(profile, lowerLine) {
|
|
107
|
+
let hits = 0;
|
|
108
|
+
|
|
109
|
+
for (const t of profile.stems) if (lowerLine.includes(t)) hits++;
|
|
110
|
+
|
|
111
|
+
return profile.stems.length ? hits / profile.stems.length : 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Co-occurring entities on one query-relevant line receive act·sim(q,z)·idf (eq.9, IDF-damped). */
|
|
115
|
+
function activateCooccurring(e, line, weight, spanId, graph, eta1) {
|
|
116
|
+
for (const [other] of graph.spanEntities.get(spanId)) {
|
|
117
|
+
if (other === e || graph.hubs.has(other) || !line.includes(other.toLowerCase())) continue;
|
|
118
|
+
const idf = 1 / Math.log2(1 + graph.entitySpans.get(other).size);
|
|
119
|
+
eta1.set(other, (eta1.get(other) || 0) + weight * idf);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function propagateFrom(e, act, graph, profile, eta1) {
|
|
124
|
+
const eLower = e.toLowerCase();
|
|
125
|
+
|
|
126
|
+
for (const spanId of graph.entitySpans.get(e) || []) {
|
|
127
|
+
for (const line of spanLines(graph.byId.get(spanId))) {
|
|
128
|
+
if (!line.includes(eLower)) continue;
|
|
129
|
+
const sim = querySim(profile, line);
|
|
130
|
+
|
|
131
|
+
if (sim > 0) activateCooccurring(e, line, act * sim, spanId, graph, eta1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function propagate(eta0, spans, graph, profile) {
|
|
137
|
+
const eta1 = new Map(eta0);
|
|
138
|
+
|
|
139
|
+
for (const [e, act] of eta0) propagateFrom(e, act, graph, profile, eta1);
|
|
140
|
+
|
|
141
|
+
return eta1;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---- eq.10: personalized PageRank over spans ----
|
|
145
|
+
|
|
146
|
+
function resetDistribution(spans, graph, eta, prior) {
|
|
147
|
+
const n = spans.length;
|
|
148
|
+
const reset = new Float64Array(n);
|
|
149
|
+
let sum = 0;
|
|
150
|
+
|
|
151
|
+
for (let i = 0; i < n; i++) {
|
|
152
|
+
let r = prior[i];
|
|
153
|
+
|
|
154
|
+
for (const [e, w] of graph.spanEntities.get(spans[i].id)) if (!graph.hubs.has(e)) r += (eta.get(e) || 0) * w;
|
|
155
|
+
reset[i] = r;
|
|
156
|
+
sum += r;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (sum > 0) for (let i = 0; i < n; i++) reset[i] /= sum;
|
|
160
|
+
|
|
161
|
+
return { reset, sum };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Transition structure d → d' = Σ_e w(d,e)·w(d',e) over shared non-hub entities plus 0.5 per in-file
|
|
166
|
+
* neighbour (Edd). Kept factored through the entity layer so an iteration costs O(nnz), never O(n²).
|
|
167
|
+
*/
|
|
168
|
+
function transitionStructure(spans, graph) {
|
|
169
|
+
const n = spans.length;
|
|
170
|
+
const entities = [];
|
|
171
|
+
|
|
172
|
+
for (const [entity, ids] of graph.entitySpans) {
|
|
173
|
+
if (ids.size >= 2 && !graph.hubs.has(entity)) entities.push(entity);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const eIndex = new Map(entities.map((e, i) => [e, i]));
|
|
177
|
+
|
|
178
|
+
const spanTerms = spans.map((s) => {
|
|
179
|
+
const terms = [];
|
|
180
|
+
|
|
181
|
+
for (const [e, w] of graph.spanEntities.get(s.id)) if (eIndex.has(e)) terms.push([eIndex.get(e), w]);
|
|
182
|
+
|
|
183
|
+
return terms;
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const entityMass = new Float64Array(entities.length);
|
|
187
|
+
|
|
188
|
+
for (let i = 0; i < n; i++) for (const [ei, w] of spanTerms[i]) entityMass[ei] += w;
|
|
189
|
+
const neighbours = spans.map((s, i) => [i - 1, i + 1].filter((j) => j >= 0 && j < n && spans[j].path === s.path));
|
|
190
|
+
const outWeight = new Float64Array(n);
|
|
191
|
+
|
|
192
|
+
for (let i = 0; i < n; i++) {
|
|
193
|
+
let out = 0.5 * neighbours[i].length;
|
|
194
|
+
|
|
195
|
+
for (const [ei, w] of spanTerms[i]) out += w * (entityMass[ei] - w);
|
|
196
|
+
outWeight[i] = out;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { spanTerms, entityCount: entities.length, neighbours, outWeight };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function pushStep(pi, next, acc, structure, gamma) {
|
|
203
|
+
const { spanTerms, neighbours, outWeight } = structure;
|
|
204
|
+
let dangling = 0;
|
|
205
|
+
|
|
206
|
+
for (let i = 0; i < pi.length; i++) {
|
|
207
|
+
if (outWeight[i] === 0) {
|
|
208
|
+
dangling += pi[i];
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const flow = (gamma * pi[i]) / outWeight[i];
|
|
213
|
+
|
|
214
|
+
for (const [ei, w] of spanTerms[i]) {
|
|
215
|
+
acc[ei] += flow * w;
|
|
216
|
+
next[i] -= flow * w * w; // remove the d → d self term
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (const j of neighbours[i]) next[j] += flow * 0.5;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return dangling;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function pageRank(spans, graph, eta, prior, { gamma, pprIterations }) {
|
|
226
|
+
const n = spans.length;
|
|
227
|
+
const { reset, sum } = resetDistribution(spans, graph, eta, prior);
|
|
228
|
+
|
|
229
|
+
if (sum === 0) return reset;
|
|
230
|
+
const structure = transitionStructure(spans, graph);
|
|
231
|
+
const acc = new Float64Array(structure.entityCount);
|
|
232
|
+
let pi = Float64Array.from(reset);
|
|
233
|
+
|
|
234
|
+
for (let iter = 0; iter < pprIterations; iter++) {
|
|
235
|
+
const next = new Float64Array(n);
|
|
236
|
+
acc.fill(0);
|
|
237
|
+
const dangling = pushStep(pi, next, acc, structure, gamma);
|
|
238
|
+
|
|
239
|
+
for (let i = 0; i < n; i++) {
|
|
240
|
+
for (const [ei, w] of structure.spanTerms[i]) next[i] += acc[ei] * w;
|
|
241
|
+
next[i] += (1 - gamma) * reset[i] + gamma * dangling * reset[i]; // dangling mass follows the reset
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
pi = next;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return pi;
|
|
248
|
+
}
|
|
249
|
+
export { splitIdentifier, spanLines, buildGraph, activateEntities, propagate, pageRank };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import {isTestPath} from '../fs/workspace.js';
|
|
2
|
+
import {splitIdentifier,spanLines,buildGraph,activateEntities,propagate,pageRank} from './evidence-graph.js';
|
|
3
|
+
|
|
4
|
+
// ---- eq.11: hierarchical view (file → span → line) ----
|
|
5
|
+
|
|
6
|
+
function nameDefinitionScore(span, profile, usage) {
|
|
7
|
+
if (usage) return 0; // eq.15 Rank_ϕ: a usage question is answered by callers, not the definer
|
|
8
|
+
const nameTokens = splitIdentifier(span.name || "");
|
|
9
|
+
let def = 0;
|
|
10
|
+
|
|
11
|
+
for (const t of profile.stems) if (nameTokens.some((n) => n.startsWith(t))) def += 40;
|
|
12
|
+
|
|
13
|
+
return def;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function mentionScore(span, profile, usage, skipDeclaration) {
|
|
17
|
+
const perHit = usage ? 15 : 5;
|
|
18
|
+
let mentions = 0;
|
|
19
|
+
let bestLine = span.start;
|
|
20
|
+
let bestHits = 0;
|
|
21
|
+
const lines = spanLines(span);
|
|
22
|
+
|
|
23
|
+
for (let i = skipDeclaration ? 1 : 0; i < lines.length; i++) {
|
|
24
|
+
let hits = 0;
|
|
25
|
+
|
|
26
|
+
for (const t of profile.stems) if (lines[i].includes(t)) hits++;
|
|
27
|
+
|
|
28
|
+
if (hits > bestHits) {
|
|
29
|
+
bestHits = hits;
|
|
30
|
+
bestLine = span.start + i;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
mentions += hits * hits * perHit; // several query stems on one line is strong evidence
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { mentions, bestLine };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hierarchicalScores(spans, fileScores, profile) {
|
|
40
|
+
const usage = profile.answerType === "usage";
|
|
41
|
+
|
|
42
|
+
return spans.map((span) => {
|
|
43
|
+
const definesSubject = profile.subjects.includes(span.name);
|
|
44
|
+
const def = nameDefinitionScore(span, profile, usage);
|
|
45
|
+
const { mentions, bestLine } = mentionScore(span, profile, usage, usage && definesSubject);
|
|
46
|
+
span.bestLine = bestLine;
|
|
47
|
+
span.support = def + mentions; // span-level lexical evidence; file-level bonuses do not count
|
|
48
|
+
const fileScore = Math.max(0, fileScores.get(span.path) || 0);
|
|
49
|
+
|
|
50
|
+
return fileScore / 2 + def + Math.min(mentions, usage ? 200 : 120) + (span.isExport ? 10 : 0);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---- eq.12 / eq.13 ----
|
|
55
|
+
|
|
56
|
+
function normalize(scores) {
|
|
57
|
+
let min = Infinity;
|
|
58
|
+
let max = -Infinity;
|
|
59
|
+
|
|
60
|
+
for (const s of scores) {
|
|
61
|
+
if (s < min) min = s;
|
|
62
|
+
|
|
63
|
+
if (s > max) max = s;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!(max > min)) return scores.map(() => 1);
|
|
67
|
+
|
|
68
|
+
return scores.map((s) => (s - min) / (max - min));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---- eq.14 / eq.15 ----
|
|
72
|
+
|
|
73
|
+
function bridgesFor(i, spans, graph, fused, definers, chosen) {
|
|
74
|
+
const byId = graph.byId;
|
|
75
|
+
const bridges = [];
|
|
76
|
+
|
|
77
|
+
for (const [e, w] of graph.spanEntities.get(spans[i].id)) {
|
|
78
|
+
const defId = definers.get(e);
|
|
79
|
+
|
|
80
|
+
if (!defId || graph.hubs.has(e) || chosen.has(defId) || defId === spans[i].id || w < 0.15) continue;
|
|
81
|
+
const definer = byId.get(defId);
|
|
82
|
+
|
|
83
|
+
if (definer.end - definer.start < 2) continue; // one-line helpers add no understanding
|
|
84
|
+
bridges.push({ id: defId, w: w * fused[definer.index0] });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return bridges.sort((a, b) => b.w - a.w).slice(0, 2);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function closure(main, spans, graph, fused, k) {
|
|
91
|
+
spans.forEach((s, i) => (s.index0 = i));
|
|
92
|
+
const chosen = new Map(main.map((i) => [spans[i].id, { i, why: "main" }]));
|
|
93
|
+
const definers = new Map();
|
|
94
|
+
|
|
95
|
+
for (const s of spans) if (s.name) definers.set(s.name, s.id);
|
|
96
|
+
const neighboursOf = (i) => [i - 1, i + 1].filter((j) => j >= 0 && j < spans.length && spans[j].path === spans[i].path);
|
|
97
|
+
|
|
98
|
+
for (const i of main) {
|
|
99
|
+
// Ng: spans that define identifiers this span uses (relational bridges).
|
|
100
|
+
for (const b of bridgesFor(i, spans, graph, fused, definers, chosen)) chosen.set(b.id, { i: graph.byId.get(b.id).index0, why: "bridge" });
|
|
101
|
+
|
|
102
|
+
// Nh: in-file neighbours that still carry query signal.
|
|
103
|
+
for (const j of neighboursOf(i)) {
|
|
104
|
+
if (fused[j] > 0.2 && !chosen.has(spans[j].id)) chosen.set(spans[j].id, { i: j, why: "neighbor" });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const supports = [...chosen.values()].filter((c) => c.why !== "main").sort((a, b) => fused[b.i] - fused[a.i]).slice(0, k);
|
|
109
|
+
|
|
110
|
+
return [...main.map((i) => ({ i, why: "main" })), ...supports];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function fuseScores(profile, graphNorm, hierNorm, rho) {
|
|
114
|
+
const [primary, secondary] = profile.route === "relational" ? [graphNorm, hierNorm] : [hierNorm, graphNorm];
|
|
115
|
+
|
|
116
|
+
return primary.map((p, i) => rho * p + (1 - rho) * secondary[i]); // eq.13
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function spanAdmissible(span, profile) {
|
|
120
|
+
const p = span.path;
|
|
121
|
+
const isDoc = /\.(md|mdx|rst|txt)$/i.test(p);
|
|
122
|
+
|
|
123
|
+
return span.support > 0
|
|
124
|
+
&& (profile.flags.wantsTest || !isTestPath(p))
|
|
125
|
+
&& (profile.flags.wantsDoc || !isDoc);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function compareFused(spans, fused) {
|
|
129
|
+
return (a, b) => fused[b] - fused[a] || spans[a].path.localeCompare(spans[b].path) || spans[a].start - spans[b].start;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function dampUsageDefiners(spans, profile, fused, admissible) {
|
|
133
|
+
const usage = profile.answerType === "usage";
|
|
134
|
+
|
|
135
|
+
for (const i of admissible) {
|
|
136
|
+
if (usage && profile.subjects.includes(spans[i].name)) fused[i] *= 0.5; // a usage question is answered by callers
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function pickEvidence(spans, fileScores, profile, opts) {
|
|
141
|
+
const graph = buildGraph(spans);
|
|
142
|
+
const hierNorm = normalize(hierarchicalScores(spans, fileScores, profile));
|
|
143
|
+
const eta = propagate(activateEntities(profile, graph), spans, graph, profile);
|
|
144
|
+
const pi = pageRank(spans, graph, eta, hierNorm.map((s) => s * 0.5), opts);
|
|
145
|
+
const fused = fuseScores(profile, normalize([...pi]), hierNorm, opts.rho);
|
|
146
|
+
// eq.15 Filter: boundary/type hard constraints and lexical support; Rank_ϕ: answer-type compatibility.
|
|
147
|
+
const admissible = spans.flatMap((span, i) => spanAdmissible(span, profile) ? [i] : []);
|
|
148
|
+
dampUsageDefiners(spans, profile, fused, admissible);
|
|
149
|
+
const main = admissible.sort(compareFused(spans, fused)).slice(0, opts.k);
|
|
150
|
+
|
|
151
|
+
return { picks: closure(main, spans, graph, fused, opts.k), fused };
|
|
152
|
+
}
|
|
153
|
+
export { pickEvidence };
|