@opengeni/jev 0.1.0-canary.36199476632001
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/LICENSE +190 -0
- package/README.md +140 -0
- package/dist/circuit-breaker.d.ts +58 -0
- package/dist/client.d.ts +173 -0
- package/dist/code-search/config.d.ts +142 -0
- package/dist/code-search/judge.d.ts +169 -0
- package/dist/code-search/leads.d.ts +96 -0
- package/dist/code-search/pack.d.ts +106 -0
- package/dist/code-search/recall.d.ts +155 -0
- package/dist/code-search/search.d.ts +93 -0
- package/dist/code-search/session.d.ts +33 -0
- package/dist/code-search/text.d.ts +35 -0
- package/dist/code-search/tool.d.ts +34 -0
- package/dist/code-search/windows.d.ts +85 -0
- package/dist/code-search/workspace.d.ts +51 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3110 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
- package/src/circuit-breaker.ts +135 -0
- package/src/client.ts +577 -0
- package/src/code-search/config.ts +282 -0
- package/src/code-search/judge.ts +413 -0
- package/src/code-search/leads.ts +442 -0
- package/src/code-search/pack.ts +354 -0
- package/src/code-search/recall.ts +648 -0
- package/src/code-search/search.ts +773 -0
- package/src/code-search/session.ts +89 -0
- package/src/code-search/text.ts +159 -0
- package/src/code-search/tool.ts +209 -0
- package/src/code-search/windows.ts +617 -0
- package/src/code-search/workspace.ts +55 -0
- package/src/index.ts +71 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pack.ts - bounded evidence pack.
|
|
3
|
+
*
|
|
4
|
+
* Priority: (1) for each sub-question, the best passage covering it (coverage >= T2), (2) passages with
|
|
5
|
+
* relevance >= T2 by relevance, (3) if fewer than minPassages passed, the next best above minRelevance,
|
|
6
|
+
* (4) optionally (fillBudget) the rest. Greedy under a char budget (tokens * charsPerToken); a passage that
|
|
7
|
+
* does not fit is trimmed to whole lines around its hits (>= minTrimLines) and marked, never cut mid-line.
|
|
8
|
+
* Output groups passages by file (files by best relevance, passages by line).
|
|
9
|
+
*/
|
|
10
|
+
import type { CodeSearchConfig } from "./config";
|
|
11
|
+
import { isChangelogPath, isDocPath, isTestPath } from "./text";
|
|
12
|
+
import { renderLines, type RenderOpts } from "./windows";
|
|
13
|
+
|
|
14
|
+
export interface EvidencePassage {
|
|
15
|
+
id: string;
|
|
16
|
+
path: string;
|
|
17
|
+
start: number;
|
|
18
|
+
end: number;
|
|
19
|
+
fileLines: string[];
|
|
20
|
+
hits: number[];
|
|
21
|
+
label?: { line: number; text: string } | undefined;
|
|
22
|
+
kind: "hit" | "header" | "def";
|
|
23
|
+
rel: number;
|
|
24
|
+
cov: number[];
|
|
25
|
+
/** Lexical passage score (used by pack.lexWeight). */
|
|
26
|
+
lex?: number | undefined;
|
|
27
|
+
lead?: string | undefined;
|
|
28
|
+
/** How lines are rendered (line cap + needles for cutting very long lines around a hit). */
|
|
29
|
+
render?: RenderOpts | undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface PackedPassage {
|
|
33
|
+
id: string;
|
|
34
|
+
path: string;
|
|
35
|
+
start: number;
|
|
36
|
+
end: number;
|
|
37
|
+
origStart: number;
|
|
38
|
+
origEnd: number;
|
|
39
|
+
trimmed: boolean;
|
|
40
|
+
rel: number;
|
|
41
|
+
cov: number[];
|
|
42
|
+
kind: EvidencePassage["kind"];
|
|
43
|
+
lead?: string | undefined;
|
|
44
|
+
label?: { line: number; text: string } | undefined;
|
|
45
|
+
/** rendered block including its `==` header line */
|
|
46
|
+
block: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface PackOptions {
|
|
50
|
+
subQuestions: string[];
|
|
51
|
+
T2: number;
|
|
52
|
+
/** Chars available for passage blocks (header/footer excluded). */
|
|
53
|
+
bodyChars: number;
|
|
54
|
+
cfg: CodeSearchConfig;
|
|
55
|
+
/** Apply pack.changelogPrior (false when the question is about history / releases). */
|
|
56
|
+
downweightChangelogs?: boolean;
|
|
57
|
+
/** Apply pack.testPrior (false when the question is about tests). */
|
|
58
|
+
downweightTests?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface PackBody {
|
|
62
|
+
included: PackedPassage[];
|
|
63
|
+
excluded: EvidencePassage[];
|
|
64
|
+
body: string;
|
|
65
|
+
priority: string[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const r2 = (x: number) => (Number.isFinite(x) ? x.toFixed(2) : "?");
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Greedy file-diverse order: repeatedly take the passage with the highest eff(x) - penalty x (passages already
|
|
72
|
+
* taken from its file). penalty 0 = plain descending eff.
|
|
73
|
+
*/
|
|
74
|
+
export function diverseOrder(
|
|
75
|
+
xs: EvidencePassage[],
|
|
76
|
+
eff: (x: EvidencePassage) => number,
|
|
77
|
+
penalty: number,
|
|
78
|
+
taken: Map<string, number>,
|
|
79
|
+
): EvidencePassage[] {
|
|
80
|
+
const tie = (a: EvidencePassage, b: EvidencePassage) =>
|
|
81
|
+
a.path < b.path ? -1 : a.path > b.path ? 1 : a.start - b.start;
|
|
82
|
+
if (penalty <= 0) return [...xs].sort((a, b) => eff(b) - eff(a) || tie(a, b));
|
|
83
|
+
const left = [...xs];
|
|
84
|
+
const out: EvidencePassage[] = [];
|
|
85
|
+
while (left.length) {
|
|
86
|
+
let bi = 0;
|
|
87
|
+
let bv = -Infinity;
|
|
88
|
+
left.forEach((x, i) => {
|
|
89
|
+
const v = eff(x) - penalty * (taken.get(x.path) ?? 0);
|
|
90
|
+
if (v > bv || (v === bv && tie(x, left[bi]!) < 0)) {
|
|
91
|
+
bv = v;
|
|
92
|
+
bi = i;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
const x = left.splice(bi, 1)[0]!;
|
|
96
|
+
taken.set(x.path, (taken.get(x.path) ?? 0) + 1);
|
|
97
|
+
out.push(x);
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Path prior for pack ordering (release notes, prose docs, tests); 1 for code. */
|
|
103
|
+
export function pathPrior(
|
|
104
|
+
path: string,
|
|
105
|
+
o: Pick<PackOptions, "cfg" | "downweightChangelogs" | "downweightTests">,
|
|
106
|
+
): number {
|
|
107
|
+
const p = o.cfg.pack;
|
|
108
|
+
if (isChangelogPath(path)) return o.downweightChangelogs !== false ? p.changelogPrior : 1;
|
|
109
|
+
if (isTestPath(path)) return o.downweightTests !== false ? p.testPrior : 1;
|
|
110
|
+
if (isDocPath(path)) return p.docPrior;
|
|
111
|
+
return 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Pack ordering score: (rel + lexWeight x lex) x path prior. Inclusion floors (T2, minRelevance) still use raw rel. */
|
|
115
|
+
export function packScore(
|
|
116
|
+
x: EvidencePassage,
|
|
117
|
+
o: Pick<PackOptions, "cfg" | "downweightChangelogs" | "downweightTests">,
|
|
118
|
+
): number {
|
|
119
|
+
return (x.rel + o.cfg.pack.lexWeight * (x.lex ?? 0)) * pathPrior(x.path, o);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function priorityOrder(
|
|
123
|
+
passages: EvidencePassage[],
|
|
124
|
+
o: Pick<PackOptions, "subQuestions" | "T2" | "cfg" | "downweightChangelogs" | "downweightTests">,
|
|
125
|
+
): EvidencePassage[] {
|
|
126
|
+
const p = o.cfg.pack;
|
|
127
|
+
const effCache = new Map<EvidencePassage, number>();
|
|
128
|
+
const eff = (x: EvidencePassage) => {
|
|
129
|
+
let v = effCache.get(x);
|
|
130
|
+
if (v === undefined) effCache.set(x, (v = packScore(x, o)));
|
|
131
|
+
return v;
|
|
132
|
+
};
|
|
133
|
+
const byRel = [...passages].sort(
|
|
134
|
+
(a, b) => eff(b) - eff(a) || (a.path < b.path ? -1 : a.path > b.path ? 1 : a.start - b.start),
|
|
135
|
+
);
|
|
136
|
+
const out: EvidencePassage[] = [];
|
|
137
|
+
const taken = new Map<string, number>();
|
|
138
|
+
const add = (x: EvidencePassage) => {
|
|
139
|
+
if (!out.includes(x)) {
|
|
140
|
+
out.push(x);
|
|
141
|
+
taken.set(x.path, (taken.get(x.path) ?? 0) + 1);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
o.subQuestions.forEach((_, j) => {
|
|
145
|
+
const best = [...passages]
|
|
146
|
+
.filter((x) => (x.cov[j] ?? 0) >= o.T2 && x.rel >= p.minRelevance)
|
|
147
|
+
.sort((a, b) => (b.cov[j] ?? 0) - (a.cov[j] ?? 0) || b.rel - a.rel)[0];
|
|
148
|
+
if (best) add(best);
|
|
149
|
+
});
|
|
150
|
+
for (const x of diverseOrder(
|
|
151
|
+
byRel.filter((y) => y.rel >= o.T2 && !out.includes(y)),
|
|
152
|
+
eff,
|
|
153
|
+
p.filePenalty,
|
|
154
|
+
new Map(taken),
|
|
155
|
+
))
|
|
156
|
+
add(x);
|
|
157
|
+
for (const x of byRel) {
|
|
158
|
+
if (out.length >= p.minPassages) break;
|
|
159
|
+
if (x.rel >= p.minRelevance) add(x);
|
|
160
|
+
}
|
|
161
|
+
if (p.fillBudget)
|
|
162
|
+
for (const x of diverseOrder(
|
|
163
|
+
byRel.filter((y) => y.rel >= p.minRelevance && !out.includes(y)),
|
|
164
|
+
eff,
|
|
165
|
+
p.filePenalty,
|
|
166
|
+
new Map(taken),
|
|
167
|
+
))
|
|
168
|
+
add(x);
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function blockHeader(x: EvidencePassage, start: number, end: number, o: PackOptions): string {
|
|
173
|
+
const covTags = o.subQuestions
|
|
174
|
+
.map((_, j) => ((x.cov[j] ?? 0) >= o.T2 ? `s${j + 1}` : ""))
|
|
175
|
+
.filter(Boolean);
|
|
176
|
+
const parts = [`== ${x.path}:${start}-${end} rel ${r2(x.rel)}`];
|
|
177
|
+
if (covTags.length) parts.push(`[${covTags.join(" ")}]`);
|
|
178
|
+
if (x.kind === "def" && x.lead) parts.push(`(definition of ${x.lead})`);
|
|
179
|
+
if (start !== x.start || end !== x.end) parts.push(`(trimmed from ${x.start}-${x.end})`);
|
|
180
|
+
let h = parts.join(" ");
|
|
181
|
+
if (x.label && x.label.line < start) h += `\n in L${x.label.line}: ${x.label.text}`;
|
|
182
|
+
return h;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function renderBlock(
|
|
186
|
+
x: EvidencePassage,
|
|
187
|
+
start: number,
|
|
188
|
+
end: number,
|
|
189
|
+
o: PackOptions,
|
|
190
|
+
): string {
|
|
191
|
+
return `${blockHeader(x, start, end, o)}\n${renderLines(x.fileLines, start, end, x.render ?? o.cfg.wave2.maxLineChars)}`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Largest contiguous whole-line sub-range around the passage's hits whose block fits in maxChars. */
|
|
195
|
+
export function trimToFit(
|
|
196
|
+
x: EvidencePassage,
|
|
197
|
+
maxChars: number,
|
|
198
|
+
o: PackOptions,
|
|
199
|
+
): { start: number; end: number } | null {
|
|
200
|
+
const lineLen = (i: number) =>
|
|
201
|
+
renderLines(x.fileLines, i, i, x.render ?? o.cfg.wave2.maxLineChars).length + 1;
|
|
202
|
+
const hdr = blockHeader(x, x.start, x.end, o).length + 40; // + "(trimmed from a-b)"
|
|
203
|
+
const inside = x.hits.filter((h) => h >= x.start && h <= x.end).sort((a, b) => a - b);
|
|
204
|
+
const center = inside.length ? inside[Math.floor((inside.length - 1) / 2)]! : x.start;
|
|
205
|
+
let s = center;
|
|
206
|
+
let e = center;
|
|
207
|
+
let used = hdr + lineLen(center);
|
|
208
|
+
if (used > maxChars) return null;
|
|
209
|
+
// grow 2 lines down per line up (code after a hit usually matters more), until neither side fits
|
|
210
|
+
let stuckUp = false;
|
|
211
|
+
let stuckDown = false;
|
|
212
|
+
let turn = 0;
|
|
213
|
+
while (!(stuckUp && stuckDown)) {
|
|
214
|
+
const goDown = !stuckDown && (stuckUp || turn % 3 !== 2);
|
|
215
|
+
turn++;
|
|
216
|
+
if (goDown) {
|
|
217
|
+
if (e + 1 > x.end) {
|
|
218
|
+
stuckDown = true;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const c = lineLen(e + 1);
|
|
222
|
+
if (used + c > maxChars) stuckDown = true;
|
|
223
|
+
else {
|
|
224
|
+
e++;
|
|
225
|
+
used += c;
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
if (s - 1 < x.start) {
|
|
229
|
+
stuckUp = true;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const c = lineLen(s - 1);
|
|
233
|
+
if (used + c > maxChars) stuckUp = true;
|
|
234
|
+
else {
|
|
235
|
+
s--;
|
|
236
|
+
used += c;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (e - s + 1 < o.cfg.pack.minTrimLines && e - s + 1 < x.end - x.start + 1) return null;
|
|
241
|
+
return { start: s, end: e };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function packBody(passages: EvidencePassage[], o: PackOptions): PackBody {
|
|
245
|
+
const order = priorityOrder(passages, o);
|
|
246
|
+
let remaining = o.bodyChars;
|
|
247
|
+
const included: PackedPassage[] = [];
|
|
248
|
+
for (const x of order) {
|
|
249
|
+
if (remaining < 200) break;
|
|
250
|
+
let s = x.start;
|
|
251
|
+
let e = x.end;
|
|
252
|
+
let block = renderBlock(x, s, e, o);
|
|
253
|
+
const cap = o.cfg.pack.maxPassageChars;
|
|
254
|
+
if (cap > 0 && block.length > cap) {
|
|
255
|
+
const t = trimToFit(x, cap, o);
|
|
256
|
+
if (t) {
|
|
257
|
+
s = t.start;
|
|
258
|
+
e = t.end;
|
|
259
|
+
block = renderBlock(x, s, e, o);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (block.length + 2 > remaining) {
|
|
263
|
+
const t = trimToFit({ ...x, start: s, end: e }, remaining - 2, o);
|
|
264
|
+
if (!t) continue;
|
|
265
|
+
s = t.start;
|
|
266
|
+
e = t.end;
|
|
267
|
+
block = renderBlock(x, s, e, o);
|
|
268
|
+
if (block.length + 2 > remaining) continue;
|
|
269
|
+
}
|
|
270
|
+
remaining -= block.length + 2;
|
|
271
|
+
included.push({
|
|
272
|
+
id: x.id,
|
|
273
|
+
path: x.path,
|
|
274
|
+
start: s,
|
|
275
|
+
end: e,
|
|
276
|
+
origStart: x.start,
|
|
277
|
+
origEnd: x.end,
|
|
278
|
+
trimmed: s !== x.start || e !== x.end,
|
|
279
|
+
rel: x.rel,
|
|
280
|
+
cov: x.cov,
|
|
281
|
+
kind: x.kind,
|
|
282
|
+
lead: x.lead,
|
|
283
|
+
label: x.label,
|
|
284
|
+
block,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
const inc = new Set(included.map((p) => p.id));
|
|
288
|
+
const excluded = passages.filter((x) => !inc.has(x.id)).sort((a, b) => b.rel - a.rel);
|
|
289
|
+
// group by file: files by best relevance, passages by line
|
|
290
|
+
const fileBest = new Map<string, number>();
|
|
291
|
+
for (const p of included) fileBest.set(p.path, Math.max(fileBest.get(p.path) ?? 0, p.rel));
|
|
292
|
+
const grouped = [...included].sort(
|
|
293
|
+
(a, b) =>
|
|
294
|
+
fileBest.get(b.path)! - fileBest.get(a.path)! ||
|
|
295
|
+
(a.path < b.path ? -1 : a.path > b.path ? 1 : a.start - b.start),
|
|
296
|
+
);
|
|
297
|
+
return {
|
|
298
|
+
included: grouped,
|
|
299
|
+
excluded,
|
|
300
|
+
body: grouped.map((p) => p.block).join("\n\n"),
|
|
301
|
+
priority: order.map((x) => x.id),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export interface FooterInput {
|
|
306
|
+
excluded: EvidencePassage[];
|
|
307
|
+
otherFiles: Array<{ path: string; score: number }>;
|
|
308
|
+
leadsNotFollowed: Array<{ name: string; score: number; seenAt: string; note?: string }>;
|
|
309
|
+
zeroHitKeywords: Array<{ raw: string; fragments: string[] }>;
|
|
310
|
+
widenedNote?: string;
|
|
311
|
+
cfg: CodeSearchConfig;
|
|
312
|
+
maxChars: number;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Footer lines, dropping list entries from the end until it fits maxChars. */
|
|
316
|
+
export function renderFooter(f: FooterInput): string {
|
|
317
|
+
const p = f.cfg.pack;
|
|
318
|
+
const more: string[] = f.excluded
|
|
319
|
+
.slice(0, p.moreCandidates)
|
|
320
|
+
.map((x) => `${x.path}:${x.start}-${x.end} (${r2(x.rel)})`);
|
|
321
|
+
const moreFiles = f.otherFiles
|
|
322
|
+
.slice(0, Math.max(0, p.moreCandidates - more.length))
|
|
323
|
+
.map((x) => `${x.path} (${r2(x.score)})`);
|
|
324
|
+
const leads = f.leadsNotFollowed
|
|
325
|
+
.slice(0, p.leadsNotFollowed)
|
|
326
|
+
.map((l) => `${l.name} (${r2(l.score)}${l.note ? `, ${l.note}` : ""}) @${l.seenAt}`);
|
|
327
|
+
const zero = f.zeroHitKeywords.map((k) =>
|
|
328
|
+
k.fragments.length ? `${k.raw} (matched fragments: ${k.fragments.join(", ")})` : k.raw,
|
|
329
|
+
);
|
|
330
|
+
const build = (m: string[], mf: string[], l: string[]) => {
|
|
331
|
+
const lines: string[] = [];
|
|
332
|
+
if (m.length || mf.length) {
|
|
333
|
+
lines.push("More candidates (not included; read if needed):");
|
|
334
|
+
if (m.length) lines.push(` ${m.join(", ")}`);
|
|
335
|
+
if (mf.length) lines.push(` files: ${mf.join(", ")}`);
|
|
336
|
+
}
|
|
337
|
+
if (l.length) lines.push(`Leads not followed: ${l.join(", ")}`);
|
|
338
|
+
if (zero.length) lines.push(`Keywords with zero hits: ${zero.join(", ")}`);
|
|
339
|
+
if (f.widenedNote) lines.push(f.widenedNote);
|
|
340
|
+
return lines.join("\n");
|
|
341
|
+
};
|
|
342
|
+
let m = more;
|
|
343
|
+
let mf = moreFiles;
|
|
344
|
+
let l = leads;
|
|
345
|
+
let out = build(m, mf, l);
|
|
346
|
+
while (out.length > f.maxChars && (m.length || mf.length || l.length)) {
|
|
347
|
+
if (mf.length) mf = mf.slice(0, -1);
|
|
348
|
+
else if (l.length > 2) l = l.slice(0, -1);
|
|
349
|
+
else if (m.length) m = m.slice(0, -1);
|
|
350
|
+
else l = l.slice(0, -1);
|
|
351
|
+
out = build(m, mf, l);
|
|
352
|
+
}
|
|
353
|
+
return out;
|
|
354
|
+
}
|