@wrongstack/tools 1.0.0 → 1.0.2
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/dist/auto-proceed-loop-guard.d.ts +1 -1
- package/dist/auto-proceed-loop-guard.js +1 -1
- package/dist/bash.js +1 -1
- package/dist/builtin.js +15 -15
- package/dist/{chunk-VVZPQPTY.js → chunk-3AU6CZWL.js} +4 -2
- package/dist/{chunk-N3K2SFEI.js → chunk-52UXXQMP.js} +1408 -200
- package/dist/{chunk-NN2SNP2R.js → chunk-6H4ERJP7.js} +137 -21
- package/dist/{chunk-DK4J2WMH.js → chunk-7XUEPBNT.js} +2 -2
- package/dist/{chunk-IYH4OHBP.js → chunk-DKHMPKDG.js} +2 -2
- package/dist/{chunk-NU2IZ72J.js → chunk-DL2ARI4S.js} +7 -5
- package/dist/{chunk-M6UV67BH.js → chunk-EL2NJXSK.js} +2 -2
- package/dist/{chunk-YEISRT4S.js → chunk-HWXIRVKH.js} +1408 -235
- package/dist/{chunk-GKSG5CPH.js → chunk-KVTEIUND.js} +2 -2
- package/dist/{chunk-GDDXX27P.js → chunk-MBR5VAB5.js} +2 -2
- package/dist/{chunk-KDEXUQY7.js → chunk-MGPR7VG3.js} +261 -258
- package/dist/{chunk-YULVUTK5.js → chunk-NFX2IUQA.js} +3 -3
- package/dist/{chunk-37QKUQ2Z.js → chunk-NL2S2CXD.js} +1 -1
- package/dist/{chunk-ZTCJCYLN.js → chunk-TI5COGLY.js} +49 -6
- package/dist/{chunk-CGXTR34H.js → chunk-TU5MTW2S.js} +2 -2
- package/dist/{chunk-AB2CAUOP.js → chunk-UCXFTCAI.js} +186 -36
- package/dist/{chunk-MK2JK35J.js → chunk-UOYRAS4J.js} +3 -3
- package/dist/{chunk-UCYQTZ7F.js → chunk-ZKC5UNGD.js} +56 -2
- package/dist/codebase-index/atlas-brief.d.ts +90 -0
- package/dist/codebase-index/atlas-export.d.ts +38 -0
- package/dist/codebase-index/atlas-projection.d.ts +114 -0
- package/dist/codebase-index/atlas-types.d.ts +90 -0
- package/dist/codebase-index/background-indexer.d.ts +15 -4
- package/dist/codebase-index/codebase-context-tool.d.ts +54 -0
- package/dist/codebase-index/concept-enrichment.d.ts +154 -0
- package/dist/codebase-index/context-retrieval.d.ts +91 -0
- package/dist/codebase-index/embedding-pass.d.ts +95 -0
- package/dist/codebase-index/graph-adjacency-cache.d.ts +36 -0
- package/dist/codebase-index/graph-rank-pass.d.ts +46 -0
- package/dist/codebase-index/graph-rank.d.ts +198 -0
- package/dist/codebase-index/index-service.d.ts +15 -1
- package/dist/codebase-index/index.d.ts +34 -19
- package/dist/codebase-index/index.js +126 -9
- package/dist/codebase-index/parser-worker-script.js +3 -3
- package/dist/codebase-index/perf-metrics.d.ts +19 -0
- package/dist/codebase-index/project-server-protocol.d.ts +2 -0
- package/dist/codebase-index/project-server-query-cache.d.ts +4 -0
- package/dist/codebase-index/project-server.js +54 -7
- package/dist/codebase-index/repo-map-fallback.d.ts +18 -0
- package/dist/codebase-index/repo-map-render.d.ts +42 -0
- package/dist/codebase-index/repo-map-types.d.ts +21 -0
- package/dist/codebase-index/repo-map.d.ts +28 -16
- package/dist/codebase-index/schema.d.ts +20 -0
- package/dist/codebase-index/worker-protocol.d.ts +47 -1
- package/dist/codebase-index/worker.js +11 -5
- package/dist/codebase-index/writer-concepts.d.ts +84 -0
- package/dist/codebase-index/writer-graph-decorate.d.ts +30 -0
- package/dist/codebase-index/writer-graph-reader.d.ts +22 -0
- package/dist/codebase-index/writer-rank.d.ts +78 -0
- package/dist/codebase-index/writer-schema.d.ts +51 -0
- package/dist/codebase-index/writer-vectors.d.ts +53 -0
- package/dist/codebase-index/writer.d.ts +59 -0
- package/dist/edit.js +7 -7
- package/dist/exec.js +1 -1
- package/dist/{go-parser-D7ELWQ7F.js → go-parser-QIEXU3P2.js} +4 -2
- package/dist/index.d.ts +48 -48
- package/dist/index.js +43 -19
- package/dist/pack.js +16 -16
- package/dist/{parser-dispatch-BDVCJXTV.js → parser-dispatch-37FT3EXQ.js} +4 -4
- package/dist/patch.js +7 -7
- package/dist/{py-parser-QC6MR4GB.js → py-parser-6GJTGUXK.js} +3 -3
- package/dist/read.js +7 -7
- package/dist/replace.js +7 -7
- package/dist/tool-tier.js +17 -17
- package/dist/write.js +7 -7
- package/package.json +5 -5
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseFilesContent
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DL2ARI4S.js";
|
|
4
|
+
import {
|
|
5
|
+
recordFilesystemRead
|
|
6
|
+
} from "./chunk-ZKC5UNGD.js";
|
|
4
7
|
import {
|
|
5
8
|
INDEXABLE_EXTENSIONS,
|
|
6
9
|
LANG_FAMILY_ENTRIES,
|
|
@@ -94,6 +97,567 @@ function resetIndexCircuitBreaker() {
|
|
|
94
97
|
indexCircuitBreaker.reset();
|
|
95
98
|
}
|
|
96
99
|
|
|
100
|
+
// src/codebase-index/graph-rank.ts
|
|
101
|
+
var DEFAULT_RESTART = 0.25;
|
|
102
|
+
var DEFAULT_ITERATIONS = 25;
|
|
103
|
+
var UNVERIFIED_VISIBILITY_WEIGHT = 0.25;
|
|
104
|
+
var CONTRADICTED_VISIBILITY_WEIGHT = 0.02;
|
|
105
|
+
var WALK_RELATIONS = /* @__PURE__ */ new Set([
|
|
106
|
+
"call",
|
|
107
|
+
"import",
|
|
108
|
+
"type_ref",
|
|
109
|
+
"inherit",
|
|
110
|
+
"implement"
|
|
111
|
+
]);
|
|
112
|
+
function buildWiringGraph(refs, options = {}) {
|
|
113
|
+
const index = /* @__PURE__ */ new Map();
|
|
114
|
+
const idList = [];
|
|
115
|
+
const intern = (id) => {
|
|
116
|
+
const existing = index.get(id);
|
|
117
|
+
if (existing !== void 0) return existing;
|
|
118
|
+
const dense = idList.length;
|
|
119
|
+
index.set(id, dense);
|
|
120
|
+
idList.push(id);
|
|
121
|
+
return dense;
|
|
122
|
+
};
|
|
123
|
+
const candidates = options.candidates;
|
|
124
|
+
const fileOf = options.fileOf;
|
|
125
|
+
const importsOf = options.importsOf;
|
|
126
|
+
const checkVisibility = fileOf !== void 0 && importsOf !== void 0;
|
|
127
|
+
const from = [];
|
|
128
|
+
const to = [];
|
|
129
|
+
const weight = [];
|
|
130
|
+
for (const ref of refs) {
|
|
131
|
+
if (!WALK_RELATIONS.has(ref.callType)) continue;
|
|
132
|
+
if (ref.fromId === ref.toId) continue;
|
|
133
|
+
from.push(intern(ref.fromId));
|
|
134
|
+
to.push(intern(ref.toId));
|
|
135
|
+
const homonyms = candidates?.get(ref.toId) ?? 1;
|
|
136
|
+
let w = homonyms > 1 ? 1 / homonyms : 1;
|
|
137
|
+
if (checkVisibility) {
|
|
138
|
+
const sourceFile = fileOf.get(ref.fromId);
|
|
139
|
+
const targetFile = fileOf.get(ref.toId);
|
|
140
|
+
if (sourceFile !== void 0 && targetFile !== void 0 && sourceFile !== targetFile) {
|
|
141
|
+
const imports = importsOf.get(sourceFile);
|
|
142
|
+
if (imports === void 0) {
|
|
143
|
+
w *= UNVERIFIED_VISIBILITY_WEIGHT;
|
|
144
|
+
} else if (!imports.has(targetFile)) {
|
|
145
|
+
w *= CONTRADICTED_VISIBILITY_WEIGHT;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
weight.push(w);
|
|
150
|
+
}
|
|
151
|
+
const size = idList.length;
|
|
152
|
+
const ids = Int32Array.from(idList);
|
|
153
|
+
const offsets = new Int32Array(size + 1);
|
|
154
|
+
const inDegree = new Int32Array(size);
|
|
155
|
+
const outDegree = new Int32Array(size);
|
|
156
|
+
if (size === 0) {
|
|
157
|
+
return {
|
|
158
|
+
size: 0,
|
|
159
|
+
ids,
|
|
160
|
+
index,
|
|
161
|
+
offsets,
|
|
162
|
+
neighbours: new Int32Array(0),
|
|
163
|
+
weights: new Float64Array(0),
|
|
164
|
+
inDegree,
|
|
165
|
+
outDegree
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const counts = new Int32Array(size);
|
|
169
|
+
for (let e = 0; e < from.length; e++) {
|
|
170
|
+
const u = from[e];
|
|
171
|
+
const v = to[e];
|
|
172
|
+
counts[u] = counts[u] + 1;
|
|
173
|
+
counts[v] = counts[v] + 1;
|
|
174
|
+
outDegree[u] = outDegree[u] + 1;
|
|
175
|
+
inDegree[v] = inDegree[v] + 1;
|
|
176
|
+
}
|
|
177
|
+
let running = 0;
|
|
178
|
+
for (let u = 0; u < size; u++) {
|
|
179
|
+
offsets[u] = running;
|
|
180
|
+
running += counts[u];
|
|
181
|
+
}
|
|
182
|
+
offsets[size] = running;
|
|
183
|
+
const cursor = offsets.slice(0, size);
|
|
184
|
+
const neighbours = new Int32Array(running);
|
|
185
|
+
const weights = new Float64Array(running);
|
|
186
|
+
for (let e = 0; e < from.length; e++) {
|
|
187
|
+
const u = from[e];
|
|
188
|
+
const v = to[e];
|
|
189
|
+
const w = weight[e];
|
|
190
|
+
const cu = cursor[u];
|
|
191
|
+
neighbours[cu] = v;
|
|
192
|
+
weights[cu] = w;
|
|
193
|
+
cursor[u] = cu + 1;
|
|
194
|
+
const cv = cursor[v];
|
|
195
|
+
neighbours[cv] = u;
|
|
196
|
+
weights[cv] = w;
|
|
197
|
+
cursor[v] = cv + 1;
|
|
198
|
+
}
|
|
199
|
+
return { size, ids, index, offsets, neighbours, weights, inDegree, outDegree };
|
|
200
|
+
}
|
|
201
|
+
function pageRank(graph, options = {}) {
|
|
202
|
+
const { size, offsets, neighbours, weights } = graph;
|
|
203
|
+
const scores = new Float64Array(size);
|
|
204
|
+
if (size === 0) return scores;
|
|
205
|
+
const restart = options.restart ?? DEFAULT_RESTART;
|
|
206
|
+
const iterations = options.iterations ?? DEFAULT_ITERATIONS;
|
|
207
|
+
const walk = 1 - restart;
|
|
208
|
+
const restartDist = new Float64Array(size);
|
|
209
|
+
const seeds = options.seeds;
|
|
210
|
+
let seedTotal = 0;
|
|
211
|
+
if (seeds && seeds.size > 0) {
|
|
212
|
+
for (const [node, weight] of seeds) {
|
|
213
|
+
if (node < 0 || node >= size) continue;
|
|
214
|
+
if (!(weight > 0)) continue;
|
|
215
|
+
restartDist[node] = restartDist[node] + weight;
|
|
216
|
+
seedTotal += weight;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (seedTotal > 0) {
|
|
220
|
+
for (let u = 0; u < size; u++) restartDist[u] = restartDist[u] / seedTotal;
|
|
221
|
+
} else {
|
|
222
|
+
restartDist.fill(1 / size);
|
|
223
|
+
}
|
|
224
|
+
let current = Float64Array.from(restartDist);
|
|
225
|
+
let next = new Float64Array(size);
|
|
226
|
+
for (let iter = 0; iter < iterations; iter++) {
|
|
227
|
+
next.fill(0);
|
|
228
|
+
let teleport = 0;
|
|
229
|
+
for (let u = 0; u < size; u++) {
|
|
230
|
+
const mass = current[u];
|
|
231
|
+
if (mass === 0) continue;
|
|
232
|
+
teleport += restart * mass;
|
|
233
|
+
const start = offsets[u];
|
|
234
|
+
const end = offsets[u + 1];
|
|
235
|
+
const degree = end - start;
|
|
236
|
+
if (degree === 0) {
|
|
237
|
+
teleport += walk * mass;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const outgoing = walk * mass;
|
|
241
|
+
const share = outgoing / degree;
|
|
242
|
+
let moved = 0;
|
|
243
|
+
for (let e = start; e < end; e++) {
|
|
244
|
+
const target = neighbours[e];
|
|
245
|
+
const transfer = share * weights[e];
|
|
246
|
+
next[target] = next[target] + transfer;
|
|
247
|
+
moved += transfer;
|
|
248
|
+
}
|
|
249
|
+
teleport += outgoing - moved;
|
|
250
|
+
}
|
|
251
|
+
if (teleport > 0) {
|
|
252
|
+
for (let u = 0; u < size; u++) {
|
|
253
|
+
next[u] = next[u] + teleport * restartDist[u];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const swap = current;
|
|
257
|
+
current = next;
|
|
258
|
+
next = swap;
|
|
259
|
+
}
|
|
260
|
+
let max = 0;
|
|
261
|
+
for (let u = 0; u < size; u++) {
|
|
262
|
+
const value = current[u];
|
|
263
|
+
if (value > max) max = value;
|
|
264
|
+
}
|
|
265
|
+
if (max > 0) {
|
|
266
|
+
for (let u = 0; u < size; u++) scores[u] = current[u] / max;
|
|
267
|
+
}
|
|
268
|
+
return scores;
|
|
269
|
+
}
|
|
270
|
+
function toSymbolRankRows(graph, scores) {
|
|
271
|
+
const rows = new Array(graph.size);
|
|
272
|
+
for (let u = 0; u < graph.size; u++) {
|
|
273
|
+
rows[u] = {
|
|
274
|
+
symbolId: graph.ids[u],
|
|
275
|
+
rank: scores[u],
|
|
276
|
+
inDeg: graph.inDegree[u],
|
|
277
|
+
outDeg: graph.outDegree[u]
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
return rows;
|
|
281
|
+
}
|
|
282
|
+
function aggregateFileRank(rows, fileOf) {
|
|
283
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
284
|
+
for (const row of rows) {
|
|
285
|
+
const file = fileOf.get(row.symbolId);
|
|
286
|
+
if (file === void 0) continue;
|
|
287
|
+
const existing = byFile.get(file);
|
|
288
|
+
if (existing === void 0) {
|
|
289
|
+
byFile.set(file, {
|
|
290
|
+
file,
|
|
291
|
+
rank: row.rank,
|
|
292
|
+
inDeg: row.inDeg,
|
|
293
|
+
outDeg: row.outDeg
|
|
294
|
+
});
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
existing.rank += row.rank;
|
|
298
|
+
existing.inDeg += row.inDeg;
|
|
299
|
+
existing.outDeg += row.outDeg;
|
|
300
|
+
}
|
|
301
|
+
let max = 0;
|
|
302
|
+
for (const row of byFile.values()) {
|
|
303
|
+
if (row.rank > max) max = row.rank;
|
|
304
|
+
}
|
|
305
|
+
const out = [...byFile.values()];
|
|
306
|
+
if (max > 0) {
|
|
307
|
+
for (const row of out) row.rank /= max;
|
|
308
|
+
}
|
|
309
|
+
return out;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/codebase-index/graph-adjacency-cache.ts
|
|
313
|
+
var cached = null;
|
|
314
|
+
function cacheKey(projectRoot, indexDir) {
|
|
315
|
+
return `${projectRoot}\0${indexDir ?? ""}`;
|
|
316
|
+
}
|
|
317
|
+
function contentStamp(store) {
|
|
318
|
+
const counts = store.getRankCounts();
|
|
319
|
+
return [
|
|
320
|
+
store.getMetadata("last_indexed") ?? "",
|
|
321
|
+
store.getMetadata("relation_graph_version") ?? "",
|
|
322
|
+
counts.symbols,
|
|
323
|
+
counts.files
|
|
324
|
+
].join("|");
|
|
325
|
+
}
|
|
326
|
+
function getWiringSnapshot(store, projectRoot, indexDir) {
|
|
327
|
+
const key = cacheKey(projectRoot, indexDir);
|
|
328
|
+
const stamp = contentStamp(store);
|
|
329
|
+
if (cached !== null && cached.key === key && cached.stamp === stamp) {
|
|
330
|
+
return { graph: cached.graph, fileOf: cached.fileOf };
|
|
331
|
+
}
|
|
332
|
+
const fileOf = /* @__PURE__ */ new Map();
|
|
333
|
+
for (const symbol of store.getAllSymbols()) fileOf.set(symbol.id, symbol.file);
|
|
334
|
+
const graph = buildWiringGraph(store.getAllResolvedRefs(), {
|
|
335
|
+
candidates: store.getSymbolNameCandidates(),
|
|
336
|
+
fileOf,
|
|
337
|
+
importsOf: store.getImportVisibility()
|
|
338
|
+
});
|
|
339
|
+
cached = { key, stamp, graph, fileOf };
|
|
340
|
+
return { graph, fileOf };
|
|
341
|
+
}
|
|
342
|
+
function clearWiringSnapshot() {
|
|
343
|
+
cached = null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/codebase-index/writer-helpers.ts
|
|
347
|
+
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
348
|
+
function escapeLike(value) {
|
|
349
|
+
return /[\\%_]/.test(value) ? value.replace(/[\\%_]/g, (char) => `\\${char}`) : value;
|
|
350
|
+
}
|
|
351
|
+
function ladderChunkSizes(total, max) {
|
|
352
|
+
if (total <= 0) return [];
|
|
353
|
+
const sizes = [];
|
|
354
|
+
let remaining = total;
|
|
355
|
+
while (remaining > 0) {
|
|
356
|
+
const cap = Math.min(remaining, max);
|
|
357
|
+
const pow = cap >= 1 ? 2 ** Math.floor(Math.log2(cap)) : 1;
|
|
358
|
+
const take = Math.max(1, Math.min(pow, remaining));
|
|
359
|
+
sizes.push(take);
|
|
360
|
+
remaining -= take;
|
|
361
|
+
}
|
|
362
|
+
return sizes;
|
|
363
|
+
}
|
|
364
|
+
function nextPow2(count) {
|
|
365
|
+
return count <= 1 ? 1 : 2 ** Math.ceil(Math.log2(count));
|
|
366
|
+
}
|
|
367
|
+
function padToInBucket(values) {
|
|
368
|
+
if (values.length <= 1) return values.slice();
|
|
369
|
+
const target = nextPow2(values.length);
|
|
370
|
+
const padded = values.slice();
|
|
371
|
+
while (padded.length < target) padded.push(padded[0]);
|
|
372
|
+
return padded;
|
|
373
|
+
}
|
|
374
|
+
var PLACEHOLDER_BUCKETS = {
|
|
375
|
+
1: "?",
|
|
376
|
+
2: "?,?",
|
|
377
|
+
4: "?,?,?,?",
|
|
378
|
+
8: "?,?,?,?,?,?,?,?",
|
|
379
|
+
16: "?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?",
|
|
380
|
+
32: "?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?",
|
|
381
|
+
64: "?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?",
|
|
382
|
+
128: Array.from({ length: 128 }, () => "?").join(","),
|
|
383
|
+
256: Array.from({ length: 256 }, () => "?").join(","),
|
|
384
|
+
512: Array.from({ length: 512 }, () => "?").join(","),
|
|
385
|
+
1024: Array.from({ length: 1024 }, () => "?").join(",")
|
|
386
|
+
};
|
|
387
|
+
function placeholders(count) {
|
|
388
|
+
if (count <= 0) return "";
|
|
389
|
+
return PLACEHOLDER_BUCKETS[count] ?? Array.from({ length: count }, () => "?").join(",");
|
|
390
|
+
}
|
|
391
|
+
function inListChunks(total, max) {
|
|
392
|
+
if (total <= 0) return [];
|
|
393
|
+
if (nextPow2(total) <= max) return [total];
|
|
394
|
+
const powMax = Math.max(1, 2 ** Math.floor(Math.log2(max)));
|
|
395
|
+
return ladderChunkSizes(total, powMax);
|
|
396
|
+
}
|
|
397
|
+
function posixIndexPath(file) {
|
|
398
|
+
const p = file.includes("\\") ? file.replace(/\\/g, "/") : file;
|
|
399
|
+
return p.startsWith("./") ? p.slice(2) : p;
|
|
400
|
+
}
|
|
401
|
+
function indexedFileMatchSql(column = "file") {
|
|
402
|
+
return `(${column} = ? OR replace(${column}, '\\', '/') = ? OR replace(${column}, '\\', '/') LIKE ? ESCAPE '\\')`;
|
|
403
|
+
}
|
|
404
|
+
function indexedFileMatchArgs(file) {
|
|
405
|
+
const posix4 = posixIndexPath(file.trim());
|
|
406
|
+
return [file, posix4, `%/${escapeLike(posix4)}`];
|
|
407
|
+
}
|
|
408
|
+
function matchesIndexedPackageFilter(storedFile, packageLabel, filter) {
|
|
409
|
+
if (packageLabel === filter) return true;
|
|
410
|
+
const posixFile = posixIndexPath(storedFile);
|
|
411
|
+
const posixFilter = posixIndexPath(filter.trim());
|
|
412
|
+
if (!posixFilter) return false;
|
|
413
|
+
return posixFile === posixFilter || posixFile.endsWith(`/${posixFilter}`) || posixFile.includes(`/${posixFilter}/`);
|
|
414
|
+
}
|
|
415
|
+
function assignRefsToSymbols(refs, symbols) {
|
|
416
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
417
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
418
|
+
const seen = /* @__PURE__ */ new Set();
|
|
419
|
+
const assigned = [];
|
|
420
|
+
for (const ref of refs) {
|
|
421
|
+
let owner;
|
|
422
|
+
for (const symbol of ordered) {
|
|
423
|
+
if (symbol.line > ref.line) break;
|
|
424
|
+
owner = symbol;
|
|
425
|
+
}
|
|
426
|
+
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
427
|
+
if (!owner || owner.id <= 0) continue;
|
|
428
|
+
const key = `${owner.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
429
|
+
if (seen.has(key)) continue;
|
|
430
|
+
seen.add(key);
|
|
431
|
+
assigned.push({ ...ref, fromId: owner.id });
|
|
432
|
+
}
|
|
433
|
+
return assigned;
|
|
434
|
+
}
|
|
435
|
+
function resolveIndexDir(projectRoot, override) {
|
|
436
|
+
return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
|
|
437
|
+
}
|
|
438
|
+
function codebaseIndexDirOverride(ctx) {
|
|
439
|
+
const v = ctx.meta?.["codebaseIndexDir"];
|
|
440
|
+
return typeof v === "string" ? v : void 0;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/codebase-index/context-retrieval.ts
|
|
444
|
+
var SEED_LIMIT = 40;
|
|
445
|
+
var DEFAULT_LIMIT = 12;
|
|
446
|
+
var DEFAULT_SYMBOLS_PER_FILE = 4;
|
|
447
|
+
var NODE_HYDRATION_LIMIT = 400;
|
|
448
|
+
var SEMANTIC_SYMBOLS_PER_FILE = 3;
|
|
449
|
+
var SEMANTIC_SEED_SHARE = 0.35;
|
|
450
|
+
function normalisePrefix(prefix) {
|
|
451
|
+
if (prefix === void 0) return void 0;
|
|
452
|
+
const trimmed = posixIndexPath(prefix.trim()).replace(/\/+$/, "");
|
|
453
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
454
|
+
}
|
|
455
|
+
function retrieveContext(store, projectRoot, indexDir, options, relativeOf) {
|
|
456
|
+
const query = options.query.trim();
|
|
457
|
+
const limit = Math.max(1, Math.min(options.limit ?? DEFAULT_LIMIT, 50));
|
|
458
|
+
const symbolsPerFile = Math.max(
|
|
459
|
+
1,
|
|
460
|
+
Math.min(options.symbolsPerFile ?? DEFAULT_SYMBOLS_PER_FILE, 20)
|
|
461
|
+
);
|
|
462
|
+
const prefix = normalisePrefix(options.pathPrefix);
|
|
463
|
+
const vectorFiles = options.vectorFiles ?? [];
|
|
464
|
+
const empty = (indexStatus) => ({
|
|
465
|
+
query,
|
|
466
|
+
entries: [],
|
|
467
|
+
seedCount: 0,
|
|
468
|
+
semanticSeedCount: 0,
|
|
469
|
+
totalCandidates: 0,
|
|
470
|
+
indexStatus
|
|
471
|
+
});
|
|
472
|
+
if (query.length === 0) return empty("no-matches");
|
|
473
|
+
const search = store.searchRanked(query, void 0, SEED_LIMIT);
|
|
474
|
+
if (search.results.length === 0 && vectorFiles.length === 0) {
|
|
475
|
+
return empty(store.getIndexSummary().totalFiles === 0 ? "no-index" : "no-matches");
|
|
476
|
+
}
|
|
477
|
+
const { graph, fileOf } = getWiringSnapshot(store, projectRoot, indexDir);
|
|
478
|
+
if (graph.size === 0) {
|
|
479
|
+
return {
|
|
480
|
+
...groupIntoEntries(
|
|
481
|
+
search.results,
|
|
482
|
+
/* @__PURE__ */ new Map(),
|
|
483
|
+
fileOf,
|
|
484
|
+
limit,
|
|
485
|
+
symbolsPerFile,
|
|
486
|
+
prefix,
|
|
487
|
+
relativeOf
|
|
488
|
+
),
|
|
489
|
+
query,
|
|
490
|
+
seedCount: search.results.length,
|
|
491
|
+
semanticSeedCount: vectorFiles.length,
|
|
492
|
+
indexStatus: "unranked"
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
const seeds = /* @__PURE__ */ new Map();
|
|
496
|
+
let lexicalMass = 0;
|
|
497
|
+
for (const hit of search.results) {
|
|
498
|
+
const dense = graph.index.get(hit.id);
|
|
499
|
+
if (dense === void 0) continue;
|
|
500
|
+
const weight = hit.score > 0 ? hit.score : 1;
|
|
501
|
+
lexicalMass += weight;
|
|
502
|
+
seeds.set(dense, (seeds.get(dense) ?? 0) + weight);
|
|
503
|
+
}
|
|
504
|
+
const semanticSeeds = /* @__PURE__ */ new Map();
|
|
505
|
+
for (const hit of vectorFiles) {
|
|
506
|
+
if (!(hit.score > 0)) continue;
|
|
507
|
+
for (const symbol of store.getFileSymbols(hit.file, SEMANTIC_SYMBOLS_PER_FILE)) {
|
|
508
|
+
const dense = graph.index.get(symbol.id);
|
|
509
|
+
if (dense === void 0) continue;
|
|
510
|
+
semanticSeeds.set(dense, (semanticSeeds.get(dense) ?? 0) + hit.score);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (semanticSeeds.size > 0) {
|
|
514
|
+
let semanticMass = 0;
|
|
515
|
+
for (const weight of semanticSeeds.values()) semanticMass += weight;
|
|
516
|
+
const scale = semanticMass > 0 && lexicalMass > 0 ? lexicalMass * SEMANTIC_SEED_SHARE / semanticMass : 1;
|
|
517
|
+
for (const [dense, weight] of semanticSeeds) {
|
|
518
|
+
seeds.set(dense, (seeds.get(dense) ?? 0) + weight * scale);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (seeds.size === 0) {
|
|
522
|
+
return {
|
|
523
|
+
...groupIntoEntries(
|
|
524
|
+
search.results,
|
|
525
|
+
/* @__PURE__ */ new Map(),
|
|
526
|
+
fileOf,
|
|
527
|
+
limit,
|
|
528
|
+
symbolsPerFile,
|
|
529
|
+
prefix,
|
|
530
|
+
relativeOf
|
|
531
|
+
),
|
|
532
|
+
query,
|
|
533
|
+
seedCount: search.results.length,
|
|
534
|
+
semanticSeedCount: vectorFiles.length,
|
|
535
|
+
indexStatus: "unranked"
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
const scores = pageRank(graph, { seeds });
|
|
539
|
+
const ordered = [];
|
|
540
|
+
for (let u = 0; u < graph.size; u++) {
|
|
541
|
+
const score = scores[u];
|
|
542
|
+
if (score > 0) ordered.push({ dense: u, score });
|
|
543
|
+
}
|
|
544
|
+
ordered.sort((a, b) => b.score - a.score);
|
|
545
|
+
const top = ordered.slice(0, NODE_HYDRATION_LIMIT);
|
|
546
|
+
const scoreBySymbolId = /* @__PURE__ */ new Map();
|
|
547
|
+
for (const node of top) scoreBySymbolId.set(graph.ids[node.dense], node.score);
|
|
548
|
+
const hydrated = store.getSymbolsByIds([...scoreBySymbolId.keys()]);
|
|
549
|
+
const seedIds = new Set(search.results.map((hit) => hit.id));
|
|
550
|
+
const walked = hydrated.map((row) => ({
|
|
551
|
+
id: row.id,
|
|
552
|
+
name: row.name,
|
|
553
|
+
kind: row.kind,
|
|
554
|
+
lang: row.lang,
|
|
555
|
+
file: row.file,
|
|
556
|
+
line: row.line,
|
|
557
|
+
col: 0,
|
|
558
|
+
signature: row.signature,
|
|
559
|
+
docComment: "",
|
|
560
|
+
score: scoreBySymbolId.get(row.id) ?? 0,
|
|
561
|
+
snippet: ""
|
|
562
|
+
}));
|
|
563
|
+
const grouped = groupIntoEntries(
|
|
564
|
+
walked,
|
|
565
|
+
scoreBySymbolId,
|
|
566
|
+
fileOf,
|
|
567
|
+
limit,
|
|
568
|
+
symbolsPerFile,
|
|
569
|
+
prefix,
|
|
570
|
+
relativeOf,
|
|
571
|
+
seedIds
|
|
572
|
+
);
|
|
573
|
+
return {
|
|
574
|
+
...grouped,
|
|
575
|
+
query,
|
|
576
|
+
seedCount: search.results.length,
|
|
577
|
+
semanticSeedCount: vectorFiles.length,
|
|
578
|
+
indexStatus: "ok"
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function groupIntoEntries(symbols, scoreBySymbolId, fileOf, limit, symbolsPerFile, prefix, relativeOf, seedIds = /* @__PURE__ */ new Set()) {
|
|
582
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
583
|
+
for (const symbol of symbols) {
|
|
584
|
+
const storedFile = symbol.file || fileOf.get(symbol.id) || "";
|
|
585
|
+
if (!storedFile) continue;
|
|
586
|
+
const relative3 = relativeOf(storedFile);
|
|
587
|
+
if (prefix !== void 0 && relative3 !== prefix && !relative3.startsWith(`${prefix}/`)) continue;
|
|
588
|
+
const score = scoreBySymbolId.get(symbol.id) ?? symbol.score;
|
|
589
|
+
const seed = seedIds.has(symbol.id);
|
|
590
|
+
const entry = {
|
|
591
|
+
name: symbol.name,
|
|
592
|
+
kind: symbol.kind,
|
|
593
|
+
line: symbol.line,
|
|
594
|
+
signature: symbol.signature,
|
|
595
|
+
seed,
|
|
596
|
+
score
|
|
597
|
+
};
|
|
598
|
+
const bucket = byFile.get(relative3);
|
|
599
|
+
if (bucket === void 0) {
|
|
600
|
+
byFile.set(relative3, { file: relative3, best: score, matched: seed, symbols: [entry] });
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
bucket.symbols.push(entry);
|
|
604
|
+
if (score > bucket.best) bucket.best = score;
|
|
605
|
+
if (seed) bucket.matched = true;
|
|
606
|
+
}
|
|
607
|
+
const buckets = [...byFile.values()].sort(
|
|
608
|
+
(a, b) => b.best - a.best || a.file.localeCompare(b.file)
|
|
609
|
+
);
|
|
610
|
+
const totalCandidates = buckets.length;
|
|
611
|
+
const top = buckets.slice(0, limit);
|
|
612
|
+
const max = top[0]?.best ?? 0;
|
|
613
|
+
const entries = top.map((bucket) => ({
|
|
614
|
+
file: bucket.file,
|
|
615
|
+
relevance: max > 0 ? bucket.best / max : 0,
|
|
616
|
+
matched: bucket.matched,
|
|
617
|
+
symbols: bucket.symbols.sort((a, b) => Number(b.seed) - Number(a.seed) || b.score - a.score || a.line - b.line).slice(0, symbolsPerFile).map(({ score: _score, ...rest }) => rest)
|
|
618
|
+
}));
|
|
619
|
+
return { entries, totalCandidates };
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// src/codebase-index/graph-rank-pass.ts
|
|
623
|
+
var RANK_VERSION = "1";
|
|
624
|
+
var RANK_VERSION_KEY = "rank_version";
|
|
625
|
+
var RANK_REFRESH_FILE_THRESHOLD = 25;
|
|
626
|
+
function shouldRefreshRanks(store, opts) {
|
|
627
|
+
if (opts.force) return true;
|
|
628
|
+
if (!opts.files) return true;
|
|
629
|
+
if (store.getMetadata(RANK_VERSION_KEY) !== RANK_VERSION) return true;
|
|
630
|
+
if (store.getRankCounts().symbols === 0) return true;
|
|
631
|
+
return opts.files.length >= RANK_REFRESH_FILE_THRESHOLD;
|
|
632
|
+
}
|
|
633
|
+
function runGraphRankPass(store, errors) {
|
|
634
|
+
const startMs = Date.now();
|
|
635
|
+
try {
|
|
636
|
+
const refs = store.getAllResolvedRefs();
|
|
637
|
+
const fileOf = /* @__PURE__ */ new Map();
|
|
638
|
+
for (const symbol of store.getAllSymbols()) fileOf.set(symbol.id, symbol.file);
|
|
639
|
+
const graph = buildWiringGraph(refs, {
|
|
640
|
+
candidates: store.getSymbolNameCandidates(),
|
|
641
|
+
fileOf,
|
|
642
|
+
importsOf: store.getImportVisibility()
|
|
643
|
+
});
|
|
644
|
+
const scores = pageRank(graph);
|
|
645
|
+
const symbolRows = toSymbolRankRows(graph, scores);
|
|
646
|
+
const fileRows = aggregateFileRank(symbolRows, fileOf);
|
|
647
|
+
store.replaceRanks(symbolRows, fileRows);
|
|
648
|
+
store.setMetadata(RANK_VERSION_KEY, RANK_VERSION);
|
|
649
|
+
return {
|
|
650
|
+
computed: true,
|
|
651
|
+
symbols: symbolRows.length,
|
|
652
|
+
files: fileRows.length,
|
|
653
|
+
durationMs: Date.now() - startMs
|
|
654
|
+
};
|
|
655
|
+
} catch (error) {
|
|
656
|
+
errors.push(`graph rank: ${error instanceof Error ? error.message : String(error)}`);
|
|
657
|
+
return { computed: false, symbols: 0, files: 0, durationMs: Date.now() - startMs };
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
97
661
|
// src/codebase-index/bm25.ts
|
|
98
662
|
var K1 = 1.5;
|
|
99
663
|
var B = 0.75;
|
|
@@ -232,101 +796,244 @@ var Bm25Index = class {
|
|
|
232
796
|
// src/codebase-index/schema.ts
|
|
233
797
|
var SCHEMA_VERSION = 5;
|
|
234
798
|
|
|
235
|
-
// src/codebase-index/writer-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const pow = cap >= 1 ? 2 ** Math.floor(Math.log2(cap)) : 1;
|
|
247
|
-
const take = Math.max(1, Math.min(pow, remaining));
|
|
248
|
-
sizes.push(take);
|
|
249
|
-
remaining -= take;
|
|
250
|
-
}
|
|
251
|
-
return sizes;
|
|
799
|
+
// src/codebase-index/writer-concepts.ts
|
|
800
|
+
var CONCEPT_RELATIONS = [
|
|
801
|
+
"uses",
|
|
802
|
+
"configures",
|
|
803
|
+
"validates",
|
|
804
|
+
"extends",
|
|
805
|
+
"persists",
|
|
806
|
+
"observes"
|
|
807
|
+
];
|
|
808
|
+
function isConceptRelation(value) {
|
|
809
|
+
return CONCEPT_RELATIONS.includes(value);
|
|
252
810
|
}
|
|
253
|
-
|
|
254
|
-
|
|
811
|
+
var SELECT_CONCEPT = "SELECT file, content_hash AS contentHash, summary, crux_start AS cruxStart, crux_end AS cruxEnd, state, model, updated_at AS updatedAt FROM file_concepts";
|
|
812
|
+
function toConcept(row) {
|
|
813
|
+
return {
|
|
814
|
+
file: row.file,
|
|
815
|
+
contentHash: row.contentHash,
|
|
816
|
+
summary: row.summary,
|
|
817
|
+
cruxStart: row.cruxStart,
|
|
818
|
+
cruxEnd: row.cruxEnd,
|
|
819
|
+
state: row.state === "ready" || row.state === "stale" ? row.state : "pending",
|
|
820
|
+
model: row.model,
|
|
821
|
+
updatedAt: row.updatedAt
|
|
822
|
+
};
|
|
255
823
|
}
|
|
256
|
-
function
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
824
|
+
function upsertFileConceptWithStatement(stmt, concept) {
|
|
825
|
+
stmt(
|
|
826
|
+
`INSERT INTO file_concepts(file, content_hash, summary, crux_start, crux_end, state, model, updated_at)
|
|
827
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
828
|
+
ON CONFLICT(file) DO UPDATE SET
|
|
829
|
+
content_hash = excluded.content_hash,
|
|
830
|
+
summary = excluded.summary,
|
|
831
|
+
crux_start = excluded.crux_start,
|
|
832
|
+
crux_end = excluded.crux_end,
|
|
833
|
+
state = excluded.state,
|
|
834
|
+
model = excluded.model,
|
|
835
|
+
updated_at = excluded.updated_at`
|
|
836
|
+
).run(
|
|
837
|
+
concept.file,
|
|
838
|
+
concept.contentHash,
|
|
839
|
+
concept.summary,
|
|
840
|
+
concept.cruxStart,
|
|
841
|
+
concept.cruxEnd,
|
|
842
|
+
concept.state,
|
|
843
|
+
concept.model,
|
|
844
|
+
concept.updatedAt
|
|
845
|
+
);
|
|
262
846
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
4: "?,?,?,?",
|
|
267
|
-
8: "?,?,?,?,?,?,?,?",
|
|
268
|
-
16: "?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?",
|
|
269
|
-
32: "?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?",
|
|
270
|
-
64: "?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?",
|
|
271
|
-
128: Array.from({ length: 128 }, () => "?").join(","),
|
|
272
|
-
256: Array.from({ length: 256 }, () => "?").join(","),
|
|
273
|
-
512: Array.from({ length: 512 }, () => "?").join(","),
|
|
274
|
-
1024: Array.from({ length: 1024 }, () => "?").join(",")
|
|
275
|
-
};
|
|
276
|
-
function placeholders(count) {
|
|
277
|
-
if (count <= 0) return "";
|
|
278
|
-
return PLACEHOLDER_BUCKETS[count] ?? Array.from({ length: count }, () => "?").join(",");
|
|
847
|
+
function getFileConceptWithStatement(stmt, file) {
|
|
848
|
+
const row = stmt(`${SELECT_CONCEPT} WHERE file = ?`).get(file);
|
|
849
|
+
return row === void 0 ? void 0 : toConcept(row);
|
|
279
850
|
}
|
|
280
|
-
function
|
|
281
|
-
|
|
282
|
-
if (nextPow2(total) <= max) return [total];
|
|
283
|
-
const powMax = Math.max(1, 2 ** Math.floor(Math.log2(max)));
|
|
284
|
-
return ladderChunkSizes(total, powMax);
|
|
851
|
+
function getAllFileConceptsWithStatement(stmt) {
|
|
852
|
+
return stmt(SELECT_CONCEPT).all().map(toConcept);
|
|
285
853
|
}
|
|
286
|
-
function
|
|
287
|
-
const
|
|
288
|
-
|
|
854
|
+
function getReadyConceptSummariesWithStatement(stmt) {
|
|
855
|
+
const rows = stmt(
|
|
856
|
+
"SELECT file, summary FROM file_concepts WHERE state = 'ready' AND summary <> ''"
|
|
857
|
+
).all();
|
|
858
|
+
const map = /* @__PURE__ */ new Map();
|
|
859
|
+
for (const row of rows) map.set(row.file, row.summary);
|
|
860
|
+
return map;
|
|
289
861
|
}
|
|
290
|
-
function
|
|
291
|
-
|
|
862
|
+
function getConceptCoverageWithStatement(stmt) {
|
|
863
|
+
const rows = stmt(
|
|
864
|
+
"SELECT state, COUNT(*) AS n FROM file_concepts GROUP BY state"
|
|
865
|
+
).all();
|
|
866
|
+
const coverage = { ready: 0, stale: 0, pending: 0, subsystems: 0 };
|
|
867
|
+
for (const row of rows) {
|
|
868
|
+
if (row.state === "ready") coverage.ready = row.n;
|
|
869
|
+
else if (row.state === "stale") coverage.stale = row.n;
|
|
870
|
+
else coverage.pending += row.n;
|
|
871
|
+
}
|
|
872
|
+
coverage.subsystems = Number(
|
|
873
|
+
stmt("SELECT COUNT(*) AS n FROM subsystems").get()?.n ?? 0
|
|
874
|
+
);
|
|
875
|
+
return coverage;
|
|
876
|
+
}
|
|
877
|
+
function markStaleConceptsWithStatement(stmt) {
|
|
878
|
+
const result = stmt(
|
|
879
|
+
`UPDATE file_concepts SET state = 'stale'
|
|
880
|
+
WHERE state = 'ready'
|
|
881
|
+
AND content_hash <> ''
|
|
882
|
+
AND content_hash <> COALESCE((SELECT f.content_hash FROM files f WHERE f.file = file_concepts.file), content_hash)`
|
|
883
|
+
).run();
|
|
884
|
+
return Number(result.changes ?? 0);
|
|
885
|
+
}
|
|
886
|
+
function pruneOrphanConceptsWithStatement(stmt) {
|
|
887
|
+
const result = stmt("DELETE FROM file_concepts WHERE file NOT IN (SELECT file FROM files)").run();
|
|
888
|
+
return Number(result.changes ?? 0);
|
|
889
|
+
}
|
|
890
|
+
function replaceSubsystemsWithStatement(stmt, maxSqlVars, subsystems, edges) {
|
|
891
|
+
stmt("DELETE FROM subsystems").run();
|
|
892
|
+
stmt("DELETE FROM concept_edges").run();
|
|
893
|
+
if (subsystems.length > 0) {
|
|
894
|
+
const insert = stmt(
|
|
895
|
+
`INSERT INTO subsystems(id, name, summary, member_files, model, updated_at)
|
|
896
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
897
|
+
);
|
|
898
|
+
for (const subsystem of subsystems) {
|
|
899
|
+
insert.run(
|
|
900
|
+
subsystem.id,
|
|
901
|
+
subsystem.name,
|
|
902
|
+
subsystem.summary,
|
|
903
|
+
JSON.stringify(subsystem.memberFiles),
|
|
904
|
+
subsystem.model,
|
|
905
|
+
subsystem.updatedAt
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (edges.length === 0) return;
|
|
910
|
+
const ladder = ladderChunkSizes(edges.length, Math.max(1, Math.floor(maxSqlVars / 3)));
|
|
911
|
+
let cursor = 0;
|
|
912
|
+
for (const take of ladder) {
|
|
913
|
+
const chunk = edges.slice(cursor, cursor + take);
|
|
914
|
+
cursor += take;
|
|
915
|
+
const placeholders2 = chunk.map(() => "(?, ?, ?)").join(", ");
|
|
916
|
+
stmt(
|
|
917
|
+
`INSERT OR IGNORE INTO concept_edges(from_id, to_id, relation) VALUES ${placeholders2}`
|
|
918
|
+
).run(...chunk.flatMap((edge) => [edge.fromId, edge.toId, edge.relation]));
|
|
919
|
+
}
|
|
292
920
|
}
|
|
293
|
-
function
|
|
294
|
-
const
|
|
295
|
-
|
|
921
|
+
function getSubsystemsWithStatement(stmt) {
|
|
922
|
+
const rows = stmt(
|
|
923
|
+
"SELECT id, name, summary, member_files AS memberFiles, model, updated_at AS updatedAt FROM subsystems ORDER BY id ASC"
|
|
924
|
+
).all();
|
|
925
|
+
return rows.map((row) => {
|
|
926
|
+
let memberFiles = [];
|
|
927
|
+
try {
|
|
928
|
+
const parsed = JSON.parse(row.memberFiles);
|
|
929
|
+
if (Array.isArray(parsed))
|
|
930
|
+
memberFiles = parsed.filter((f) => typeof f === "string");
|
|
931
|
+
} catch {
|
|
932
|
+
}
|
|
933
|
+
return { ...row, memberFiles };
|
|
934
|
+
});
|
|
296
935
|
}
|
|
297
|
-
function
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
936
|
+
function getConceptEdgesWithStatement(stmt) {
|
|
937
|
+
return stmt(
|
|
938
|
+
"SELECT from_id AS fromId, to_id AS toId, relation FROM concept_edges ORDER BY from_id, to_id, relation"
|
|
939
|
+
).all().flatMap(
|
|
940
|
+
(row) => isConceptRelation(row.relation) ? [{ fromId: row.fromId, toId: row.toId, relation: row.relation }] : []
|
|
941
|
+
);
|
|
303
942
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
943
|
+
|
|
944
|
+
// src/codebase-index/writer-graph-decorate.ts
|
|
945
|
+
var MAX_SQL_VARS = 900;
|
|
946
|
+
function normaliseInPlace(nodes) {
|
|
947
|
+
let localMax = 0;
|
|
948
|
+
let anyMax = 0;
|
|
949
|
+
for (const node of nodes) {
|
|
950
|
+
const rank = node.rank;
|
|
951
|
+
if (rank === void 0) continue;
|
|
952
|
+
if (rank > anyMax) anyMax = rank;
|
|
953
|
+
if (node.external !== true && rank > localMax) localMax = rank;
|
|
954
|
+
}
|
|
955
|
+
const max = localMax > 0 ? localMax : anyMax;
|
|
956
|
+
if (max <= 0) return;
|
|
957
|
+
for (const node of nodes) {
|
|
958
|
+
if (node.rank !== void 0) node.rank = Math.min(1, node.rank / max);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
function decorateGraphNodes(stmt, nodes, packageOf) {
|
|
962
|
+
if (nodes.length === 0) return;
|
|
963
|
+
const fileRank = /* @__PURE__ */ new Map();
|
|
964
|
+
for (const row of stmt("SELECT file, rank FROM file_rank").all()) {
|
|
965
|
+
fileRank.set(row.file, row.rank);
|
|
966
|
+
}
|
|
967
|
+
const mtime = /* @__PURE__ */ new Map();
|
|
968
|
+
for (const row of stmt("SELECT file, mtime_ms FROM files").all()) {
|
|
969
|
+
mtime.set(row.file, row.mtime_ms);
|
|
970
|
+
}
|
|
971
|
+
const concepts = /* @__PURE__ */ new Map();
|
|
972
|
+
for (const row of stmt(
|
|
973
|
+
"SELECT file, summary, crux_start, crux_end FROM file_concepts WHERE state = 'ready' AND summary != ''"
|
|
974
|
+
).all()) {
|
|
975
|
+
concepts.set(row.file, row);
|
|
976
|
+
}
|
|
977
|
+
const subsystems = /* @__PURE__ */ new Map();
|
|
978
|
+
for (const row of stmt("SELECT id, name, summary FROM subsystems").all()) {
|
|
979
|
+
subsystems.set(row.id, { name: row.name, summary: row.summary });
|
|
980
|
+
}
|
|
981
|
+
const symbolRank = /* @__PURE__ */ new Map();
|
|
982
|
+
const symbolIds = nodes.map((node) => node.symbolId).filter((id) => typeof id === "number");
|
|
983
|
+
for (let start = 0; start < symbolIds.length; start += MAX_SQL_VARS) {
|
|
984
|
+
const chunk = symbolIds.slice(start, start + MAX_SQL_VARS);
|
|
985
|
+
const placeholders2 = chunk.map(() => "?").join(",");
|
|
986
|
+
for (const row of stmt(
|
|
987
|
+
`SELECT symbol_id, rank FROM symbol_rank WHERE symbol_id IN (${placeholders2})`
|
|
988
|
+
).all(...chunk)) {
|
|
989
|
+
symbolRank.set(row.symbol_id, row.rank);
|
|
314
990
|
}
|
|
315
|
-
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
316
|
-
if (!owner || owner.id <= 0) continue;
|
|
317
|
-
const key = `${owner.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
318
|
-
if (seen.has(key)) continue;
|
|
319
|
-
seen.add(key);
|
|
320
|
-
assigned.push({ ...ref, fromId: owner.id });
|
|
321
991
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
992
|
+
const packageRank = /* @__PURE__ */ new Map();
|
|
993
|
+
if (nodes.some((node) => node.kind === "package")) {
|
|
994
|
+
for (const [file, rank] of fileRank) {
|
|
995
|
+
const pkg = packageOf(file);
|
|
996
|
+
packageRank.set(pkg, (packageRank.get(pkg) ?? 0) + rank);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
for (const node of nodes) {
|
|
1000
|
+
if (node.kind === "package") {
|
|
1001
|
+
const pkg = node.package;
|
|
1002
|
+
if (pkg === void 0) continue;
|
|
1003
|
+
const rank = packageRank.get(pkg);
|
|
1004
|
+
if (rank !== void 0) node.rank = rank;
|
|
1005
|
+
const subsystem2 = subsystems.get(pkg);
|
|
1006
|
+
if (subsystem2 !== void 0) {
|
|
1007
|
+
node.subsystem = subsystem2.name;
|
|
1008
|
+
if (subsystem2.summary !== "") node.concept = subsystem2.summary;
|
|
1009
|
+
}
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
const file = node.file;
|
|
1013
|
+
if (file === void 0) continue;
|
|
1014
|
+
const modified = mtime.get(file);
|
|
1015
|
+
if (modified !== void 0) node.lastModifiedMs = modified;
|
|
1016
|
+
const subsystem = subsystems.get(packageOf(file));
|
|
1017
|
+
if (subsystem !== void 0) node.subsystem = subsystem.name;
|
|
1018
|
+
if (node.kind === "file") {
|
|
1019
|
+
const rank = fileRank.get(file);
|
|
1020
|
+
if (rank !== void 0) node.rank = rank;
|
|
1021
|
+
const concept = concepts.get(file);
|
|
1022
|
+
if (concept !== void 0) {
|
|
1023
|
+
node.concept = concept.summary;
|
|
1024
|
+
if (concept.crux_start !== null && concept.crux_end !== null) {
|
|
1025
|
+
node.crux = { start: concept.crux_start, end: concept.crux_end };
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
const symbolId = node.symbolId;
|
|
1031
|
+
if (symbolId !== void 0) {
|
|
1032
|
+
const rank = symbolRank.get(symbolId);
|
|
1033
|
+
if (rank !== void 0) node.rank = rank;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
normaliseInPlace(nodes);
|
|
330
1037
|
}
|
|
331
1038
|
|
|
332
1039
|
// src/codebase-index/lsp-kind.ts
|
|
@@ -391,6 +1098,145 @@ function internalKindToLspKind(k) {
|
|
|
391
1098
|
}
|
|
392
1099
|
}
|
|
393
1100
|
|
|
1101
|
+
// src/codebase-index/vector-search.ts
|
|
1102
|
+
var RRF_K = 60;
|
|
1103
|
+
function vectorEmbeddingEnabled() {
|
|
1104
|
+
return process.env["WRONGSTACK_INDEX_VECTORS"] === "1";
|
|
1105
|
+
}
|
|
1106
|
+
var VECTOR_DIMENSIONS = 384;
|
|
1107
|
+
var NGRAM_SIZE = 3;
|
|
1108
|
+
function embedText(text) {
|
|
1109
|
+
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
1110
|
+
const normalized = text.toLowerCase().trim();
|
|
1111
|
+
if (normalized.length === 0) return vec;
|
|
1112
|
+
const target = normalized.length < NGRAM_SIZE ? ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2)) : normalized;
|
|
1113
|
+
for (let i = 0; i <= target.length - NGRAM_SIZE; i++) {
|
|
1114
|
+
const ngram = target.slice(i, i + NGRAM_SIZE);
|
|
1115
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
1116
|
+
vec[bucket] += 1;
|
|
1117
|
+
}
|
|
1118
|
+
let norm = 0;
|
|
1119
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
1120
|
+
norm += vec[i] * vec[i];
|
|
1121
|
+
}
|
|
1122
|
+
norm = Math.sqrt(norm);
|
|
1123
|
+
if (norm > 0) {
|
|
1124
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
1125
|
+
vec[i] /= norm;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return vec;
|
|
1129
|
+
}
|
|
1130
|
+
function hashNgram(str) {
|
|
1131
|
+
let hash = 2166136261;
|
|
1132
|
+
for (let i = 0; i < str.length; i++) {
|
|
1133
|
+
hash ^= str.charCodeAt(i);
|
|
1134
|
+
hash = Math.imul(hash, 16777619);
|
|
1135
|
+
}
|
|
1136
|
+
return hash >>> 0;
|
|
1137
|
+
}
|
|
1138
|
+
function cosineSimilarity(a, b) {
|
|
1139
|
+
let dot = 0;
|
|
1140
|
+
const len = Math.min(a.length, b.length);
|
|
1141
|
+
for (let i = 0; i < len; i++) {
|
|
1142
|
+
dot += a[i] * b[i];
|
|
1143
|
+
}
|
|
1144
|
+
return dot;
|
|
1145
|
+
}
|
|
1146
|
+
function encodeVector(vec) {
|
|
1147
|
+
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
1148
|
+
}
|
|
1149
|
+
function decodeVector(buf) {
|
|
1150
|
+
if (buf.byteLength === 0) return new Float32Array(0);
|
|
1151
|
+
if (buf.byteLength % 4 !== 0) {
|
|
1152
|
+
throw new Error(
|
|
1153
|
+
`decodeVector: invalid vector byteLength ${buf.byteLength} (must be a multiple of 4)`
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
const copy = new Float32Array(buf.byteLength / 4);
|
|
1157
|
+
new Uint8Array(copy.buffer).set(buf);
|
|
1158
|
+
return copy;
|
|
1159
|
+
}
|
|
1160
|
+
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
1161
|
+
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
1162
|
+
const scored = [];
|
|
1163
|
+
for (const id of allIds) {
|
|
1164
|
+
const bm25Rank = bm25Ranks.get(id);
|
|
1165
|
+
const vecRank = vectorRanks.get(id);
|
|
1166
|
+
let score = 0;
|
|
1167
|
+
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
1168
|
+
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
1169
|
+
scored.push([id, score]);
|
|
1170
|
+
}
|
|
1171
|
+
scored.sort((a, b) => b[1] - a[1]);
|
|
1172
|
+
return scored;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// src/codebase-index/writer-vectors.ts
|
|
1176
|
+
var FILE_VECTOR_PROVIDER_KEY = "file_vector_provider";
|
|
1177
|
+
function getFileVectorStatesWithStatement(stmt, provider) {
|
|
1178
|
+
const rows = stmt(
|
|
1179
|
+
"SELECT file, source_hash AS sourceHash FROM file_vectors WHERE provider = ?"
|
|
1180
|
+
).all(provider);
|
|
1181
|
+
const map = /* @__PURE__ */ new Map();
|
|
1182
|
+
for (const row of rows) map.set(row.file, row.sourceHash);
|
|
1183
|
+
return map;
|
|
1184
|
+
}
|
|
1185
|
+
function upsertFileVectorsWithStatement(stmt, maxSqlVars, rows) {
|
|
1186
|
+
if (rows.length === 0) return;
|
|
1187
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 4)));
|
|
1188
|
+
let cursor = 0;
|
|
1189
|
+
for (const take of ladder) {
|
|
1190
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
1191
|
+
cursor += take;
|
|
1192
|
+
const placeholders2 = chunk.map(() => "(?, ?, ?, ?)").join(", ");
|
|
1193
|
+
stmt(
|
|
1194
|
+
`INSERT INTO file_vectors(file, vector, source_hash, provider) VALUES ${placeholders2}
|
|
1195
|
+
ON CONFLICT(file) DO UPDATE SET
|
|
1196
|
+
vector = excluded.vector,
|
|
1197
|
+
source_hash = excluded.source_hash,
|
|
1198
|
+
provider = excluded.provider`
|
|
1199
|
+
).run(
|
|
1200
|
+
...chunk.flatMap((row) => [row.file, encodeVector(row.vector), row.sourceHash, row.provider])
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
function reconcileVectorProviderWithStatement(stmt, getMetadata, setMetadata, provider) {
|
|
1205
|
+
const stored = getMetadata(FILE_VECTOR_PROVIDER_KEY);
|
|
1206
|
+
if (stored === provider) return false;
|
|
1207
|
+
stmt("DELETE FROM file_vectors").run();
|
|
1208
|
+
setMetadata(FILE_VECTOR_PROVIDER_KEY, provider);
|
|
1209
|
+
return stored !== void 0;
|
|
1210
|
+
}
|
|
1211
|
+
function pruneOrphanFileVectorsWithStatement(stmt) {
|
|
1212
|
+
const result = stmt("DELETE FROM file_vectors WHERE file NOT IN (SELECT file FROM files)").run();
|
|
1213
|
+
return Number(result.changes ?? 0);
|
|
1214
|
+
}
|
|
1215
|
+
function countFileVectorsWithStatement(stmt) {
|
|
1216
|
+
return Number(
|
|
1217
|
+
stmt("SELECT COUNT(*) AS n FROM file_vectors").get()?.n ?? 0
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
function searchFileVectorsWithStatement(stmt, query, limit, minScore) {
|
|
1221
|
+
if (limit <= 0 || query.length === 0) return [];
|
|
1222
|
+
const rows = stmt("SELECT file, vector FROM file_vectors").all();
|
|
1223
|
+
const hits = [];
|
|
1224
|
+
for (const row of rows) {
|
|
1225
|
+
let vector;
|
|
1226
|
+
try {
|
|
1227
|
+
vector = decodeVector(row.vector);
|
|
1228
|
+
} catch {
|
|
1229
|
+
continue;
|
|
1230
|
+
}
|
|
1231
|
+
if (vector.length !== query.length) continue;
|
|
1232
|
+
let dot = 0;
|
|
1233
|
+
for (let i = 0; i < query.length; i++) dot += query[i] * vector[i];
|
|
1234
|
+
if (dot >= minScore) hits.push({ file: row.file, score: dot });
|
|
1235
|
+
}
|
|
1236
|
+
hits.sort((a, b) => b.score - a.score || a.file.localeCompare(b.file));
|
|
1237
|
+
return hits.slice(0, limit);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
394
1240
|
// src/codebase-index/writer.ts
|
|
395
1241
|
import * as fs3 from "node:fs";
|
|
396
1242
|
import * as path3 from "node:path";
|
|
@@ -924,8 +1770,8 @@ function assignPackageLabels(structure, files) {
|
|
|
924
1770
|
const owner2 = findOwningRoot(structure, portable, ["go"]);
|
|
925
1771
|
const dir = path2.posix.dirname(portable);
|
|
926
1772
|
if (owner2?.importPath) {
|
|
927
|
-
const
|
|
928
|
-
labels.set(file,
|
|
1773
|
+
const relative3 = path2.posix.relative(owner2.dir, dir);
|
|
1774
|
+
labels.set(file, relative3 ? `${owner2.importPath}/${relative3}` : owner2.importPath);
|
|
929
1775
|
} else {
|
|
930
1776
|
labels.set(file, `go:${path2.posix.relative(structure.projectRoot, dir) || "."}`);
|
|
931
1777
|
}
|
|
@@ -1111,11 +1957,11 @@ function mapWriterRefRow(row) {
|
|
|
1111
1957
|
}
|
|
1112
1958
|
|
|
1113
1959
|
// src/codebase-index/writer-graph-reader.ts
|
|
1114
|
-
var
|
|
1960
|
+
var MAX_SQL_VARS2 = 900;
|
|
1115
1961
|
function chunkedIdQuery(stmt, ids, buildSql, extraArgs = []) {
|
|
1116
1962
|
const results = [];
|
|
1117
1963
|
let cursor = 0;
|
|
1118
|
-
for (const take of inListChunks(ids.length,
|
|
1964
|
+
for (const take of inListChunks(ids.length, MAX_SQL_VARS2)) {
|
|
1119
1965
|
const chunk = padToInBucket(ids.slice(cursor, cursor + take));
|
|
1120
1966
|
cursor += take;
|
|
1121
1967
|
const sql = buildSql(placeholders(chunk.length));
|
|
@@ -1123,10 +1969,24 @@ function chunkedIdQuery(stmt, ids, buildSql, extraArgs = []) {
|
|
|
1123
1969
|
}
|
|
1124
1970
|
return results;
|
|
1125
1971
|
}
|
|
1972
|
+
function getSymbolsByIdsWithStatement(stmt, ids) {
|
|
1973
|
+
if (ids.length === 0) return [];
|
|
1974
|
+
return chunkedIdQuery(
|
|
1975
|
+
stmt,
|
|
1976
|
+
ids,
|
|
1977
|
+
(ph) => `SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${ph})`
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
function getFileSymbolsWithStatement(stmt, file, limit) {
|
|
1981
|
+
if (limit <= 0) return [];
|
|
1982
|
+
return stmt(
|
|
1983
|
+
"SELECT id, name, kind, line, signature FROM symbols WHERE file = ? ORDER BY line ASC, name ASC LIMIT ?"
|
|
1984
|
+
).all(file, limit);
|
|
1985
|
+
}
|
|
1126
1986
|
function chunkedIdScalar(stmt, ids, buildSql, extraArgs = []) {
|
|
1127
1987
|
let total = 0;
|
|
1128
1988
|
let cursor = 0;
|
|
1129
|
-
for (const take of inListChunks(ids.length,
|
|
1989
|
+
for (const take of inListChunks(ids.length, MAX_SQL_VARS2)) {
|
|
1130
1990
|
const chunk = padToInBucket(ids.slice(cursor, cursor + take));
|
|
1131
1991
|
cursor += take;
|
|
1132
1992
|
const sql = buildSql(placeholders(chunk.length));
|
|
@@ -1167,7 +2027,7 @@ function resolveSymbolIds(stmt, symbolName, file) {
|
|
|
1167
2027
|
if (indexedFiles.length === 0) return [];
|
|
1168
2028
|
const ids = [];
|
|
1169
2029
|
let cursor = 0;
|
|
1170
|
-
for (const take of inListChunks(indexedFiles.length,
|
|
2030
|
+
for (const take of inListChunks(indexedFiles.length, MAX_SQL_VARS2)) {
|
|
1171
2031
|
const files = padToInBucket(indexedFiles.slice(cursor, cursor + take));
|
|
1172
2032
|
cursor += take;
|
|
1173
2033
|
const rows = stmt(
|
|
@@ -1486,7 +2346,9 @@ function getPackageGraphWithStatement(stmt) {
|
|
|
1486
2346
|
addWeightedEdge(edgeMap, fromPkg, toPkg, "import", n);
|
|
1487
2347
|
}
|
|
1488
2348
|
const edges = materializeWeightedEdges(edgeMap, "pkg");
|
|
1489
|
-
|
|
2349
|
+
const nodes = [...pkgNodes.values()];
|
|
2350
|
+
decorateGraphNodes(stmt, nodes, packageOf);
|
|
2351
|
+
return { nodes, edges };
|
|
1490
2352
|
}
|
|
1491
2353
|
function readPackageLabeller(stmt) {
|
|
1492
2354
|
const rows = stmt("SELECT file, package FROM files WHERE package != ''").all();
|
|
@@ -1569,7 +2431,9 @@ function getFileGraphWithStatement(stmt, packageFilter) {
|
|
|
1569
2431
|
addWeightedEdge(edgeMap, fromFile, toFile, "import", n);
|
|
1570
2432
|
}
|
|
1571
2433
|
const edges = materializeWeightedEdges(edgeMap, "file");
|
|
1572
|
-
|
|
2434
|
+
const nodes = [...fileNodes.values()];
|
|
2435
|
+
decorateGraphNodes(stmt, nodes, packageOf);
|
|
2436
|
+
return { nodes, edges };
|
|
1573
2437
|
}
|
|
1574
2438
|
function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
1575
2439
|
const indexedFiles = resolveIndexedFiles(stmt, fileFilter);
|
|
@@ -1583,19 +2447,19 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
1583
2447
|
const relatedIds = new Set(syms.map((symbol) => symbol.id));
|
|
1584
2448
|
const refRows = stmt(
|
|
1585
2449
|
`SELECT from_id, to_id, call_type, COUNT(*) AS n
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
2450
|
+
FROM (
|
|
2451
|
+
SELECT r.from_id, r.to_id, r.call_type
|
|
2452
|
+
FROM refs r
|
|
2453
|
+
JOIN symbols s ON s.id = r.from_id
|
|
2454
|
+
WHERE s.file IN (${filePlaceholders})
|
|
2455
|
+
UNION ALL
|
|
2456
|
+
SELECT r.from_id, r.to_id, r.call_type
|
|
2457
|
+
FROM refs r
|
|
2458
|
+
JOIN symbols s ON s.id = r.to_id
|
|
2459
|
+
WHERE s.file IN (${filePlaceholders})
|
|
2460
|
+
)
|
|
2461
|
+
WHERE to_id IS NOT NULL
|
|
2462
|
+
GROUP BY from_id, to_id, call_type`
|
|
1599
2463
|
).all(...indexedFiles, ...indexedFiles);
|
|
1600
2464
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
1601
2465
|
for (const r of refRows) {
|
|
@@ -1615,89 +2479,17 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
1615
2479
|
).all(...missingIds);
|
|
1616
2480
|
for (const s of extras) symById.set(s.id, s);
|
|
1617
2481
|
}
|
|
2482
|
+
const packageOf = readPackageLabeller(stmt);
|
|
1618
2483
|
const nodes = buildSymbolGraphNodes(
|
|
1619
2484
|
symById,
|
|
1620
2485
|
relatedIds,
|
|
1621
2486
|
new Set(syms.map((symbol) => symbol.file)),
|
|
1622
|
-
|
|
2487
|
+
packageOf
|
|
1623
2488
|
);
|
|
2489
|
+
decorateGraphNodes(stmt, nodes, packageOf);
|
|
1624
2490
|
return { nodes, edges };
|
|
1625
2491
|
}
|
|
1626
2492
|
|
|
1627
|
-
// src/codebase-index/vector-search.ts
|
|
1628
|
-
var RRF_K = 60;
|
|
1629
|
-
function vectorEmbeddingEnabled() {
|
|
1630
|
-
return process.env["WRONGSTACK_INDEX_VECTORS"] === "1";
|
|
1631
|
-
}
|
|
1632
|
-
var VECTOR_DIMENSIONS = 384;
|
|
1633
|
-
var NGRAM_SIZE = 3;
|
|
1634
|
-
function embedText(text) {
|
|
1635
|
-
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
1636
|
-
const normalized = text.toLowerCase().trim();
|
|
1637
|
-
if (normalized.length === 0) return vec;
|
|
1638
|
-
const target = normalized.length < NGRAM_SIZE ? ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2)) : normalized;
|
|
1639
|
-
for (let i = 0; i <= target.length - NGRAM_SIZE; i++) {
|
|
1640
|
-
const ngram = target.slice(i, i + NGRAM_SIZE);
|
|
1641
|
-
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
1642
|
-
vec[bucket] += 1;
|
|
1643
|
-
}
|
|
1644
|
-
let norm = 0;
|
|
1645
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
1646
|
-
norm += vec[i] * vec[i];
|
|
1647
|
-
}
|
|
1648
|
-
norm = Math.sqrt(norm);
|
|
1649
|
-
if (norm > 0) {
|
|
1650
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
1651
|
-
vec[i] /= norm;
|
|
1652
|
-
}
|
|
1653
|
-
}
|
|
1654
|
-
return vec;
|
|
1655
|
-
}
|
|
1656
|
-
function hashNgram(str) {
|
|
1657
|
-
let hash = 2166136261;
|
|
1658
|
-
for (let i = 0; i < str.length; i++) {
|
|
1659
|
-
hash ^= str.charCodeAt(i);
|
|
1660
|
-
hash = Math.imul(hash, 16777619);
|
|
1661
|
-
}
|
|
1662
|
-
return hash >>> 0;
|
|
1663
|
-
}
|
|
1664
|
-
function cosineSimilarity(a, b) {
|
|
1665
|
-
let dot = 0;
|
|
1666
|
-
const len = Math.min(a.length, b.length);
|
|
1667
|
-
for (let i = 0; i < len; i++) {
|
|
1668
|
-
dot += a[i] * b[i];
|
|
1669
|
-
}
|
|
1670
|
-
return dot;
|
|
1671
|
-
}
|
|
1672
|
-
function encodeVector(vec) {
|
|
1673
|
-
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
1674
|
-
}
|
|
1675
|
-
function decodeVector(buf) {
|
|
1676
|
-
if (buf.byteLength === 0) return new Float32Array(0);
|
|
1677
|
-
if (buf.byteLength % 4 !== 0) {
|
|
1678
|
-
throw new Error(
|
|
1679
|
-
`decodeVector: invalid vector byteLength ${buf.byteLength} (must be a multiple of 4)`
|
|
1680
|
-
);
|
|
1681
|
-
}
|
|
1682
|
-
const copy = new Float32Array(buf.byteLength / 4);
|
|
1683
|
-
new Uint8Array(copy.buffer).set(buf);
|
|
1684
|
-
return copy;
|
|
1685
|
-
}
|
|
1686
|
-
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
1687
|
-
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
1688
|
-
const scored = [];
|
|
1689
|
-
for (const id of allIds) {
|
|
1690
|
-
const bm25Rank = bm25Ranks.get(id);
|
|
1691
|
-
const vecRank = vectorRanks.get(id);
|
|
1692
|
-
let score = 0;
|
|
1693
|
-
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
1694
|
-
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
1695
|
-
scored.push([id, score]);
|
|
1696
|
-
}
|
|
1697
|
-
scored.sort((a, b) => b[1] - a[1]);
|
|
1698
|
-
return scored;
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
2493
|
// src/codebase-index/writer-schema.ts
|
|
1702
2494
|
var METADATA_TABLE_SQL = `
|
|
1703
2495
|
CREATE TABLE IF NOT EXISTS metadata (
|
|
@@ -1781,6 +2573,64 @@ var LANG_FAMILY_TABLE_SQL = `
|
|
|
1781
2573
|
`;
|
|
1782
2574
|
var LANG_FAMILY_WILDCARD = "*";
|
|
1783
2575
|
var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'trigram')";
|
|
2576
|
+
var RANK_TABLES_SQL = `
|
|
2577
|
+
CREATE TABLE IF NOT EXISTS symbol_rank (
|
|
2578
|
+
symbol_id INTEGER PRIMARY KEY,
|
|
2579
|
+
rank REAL NOT NULL,
|
|
2580
|
+
in_deg INTEGER NOT NULL DEFAULT 0,
|
|
2581
|
+
out_deg INTEGER NOT NULL DEFAULT 0
|
|
2582
|
+
);
|
|
2583
|
+
CREATE TABLE IF NOT EXISTS file_rank (
|
|
2584
|
+
file TEXT PRIMARY KEY,
|
|
2585
|
+
rank REAL NOT NULL,
|
|
2586
|
+
in_deg INTEGER NOT NULL DEFAULT 0,
|
|
2587
|
+
out_deg INTEGER NOT NULL DEFAULT 0
|
|
2588
|
+
);
|
|
2589
|
+
`;
|
|
2590
|
+
var RANK_INDEX_SQL = [
|
|
2591
|
+
// Both tables are read "top N by rank" far more often than by key.
|
|
2592
|
+
"CREATE INDEX IF NOT EXISTS idx_sr_rank ON symbol_rank(rank DESC)",
|
|
2593
|
+
"CREATE INDEX IF NOT EXISTS idx_fr_rank ON file_rank(rank DESC)"
|
|
2594
|
+
];
|
|
2595
|
+
var CONCEPT_TABLES_SQL = `
|
|
2596
|
+
CREATE TABLE IF NOT EXISTS file_concepts (
|
|
2597
|
+
file TEXT PRIMARY KEY,
|
|
2598
|
+
content_hash TEXT NOT NULL DEFAULT '',
|
|
2599
|
+
summary TEXT NOT NULL DEFAULT '',
|
|
2600
|
+
crux_start INTEGER,
|
|
2601
|
+
crux_end INTEGER,
|
|
2602
|
+
state TEXT NOT NULL DEFAULT 'pending',
|
|
2603
|
+
model TEXT NOT NULL DEFAULT '',
|
|
2604
|
+
updated_at INTEGER NOT NULL DEFAULT 0
|
|
2605
|
+
);
|
|
2606
|
+
CREATE TABLE IF NOT EXISTS subsystems (
|
|
2607
|
+
id TEXT PRIMARY KEY,
|
|
2608
|
+
name TEXT NOT NULL,
|
|
2609
|
+
summary TEXT NOT NULL DEFAULT '',
|
|
2610
|
+
member_files TEXT NOT NULL DEFAULT '[]',
|
|
2611
|
+
model TEXT NOT NULL DEFAULT '',
|
|
2612
|
+
updated_at INTEGER NOT NULL DEFAULT 0
|
|
2613
|
+
);
|
|
2614
|
+
CREATE TABLE IF NOT EXISTS concept_edges (
|
|
2615
|
+
from_id TEXT NOT NULL,
|
|
2616
|
+
to_id TEXT NOT NULL,
|
|
2617
|
+
relation TEXT NOT NULL,
|
|
2618
|
+
PRIMARY KEY (from_id, to_id, relation)
|
|
2619
|
+
);
|
|
2620
|
+
`;
|
|
2621
|
+
var FILE_VECTORS_TABLE_SQL = `
|
|
2622
|
+
CREATE TABLE IF NOT EXISTS file_vectors (
|
|
2623
|
+
file TEXT PRIMARY KEY,
|
|
2624
|
+
vector BLOB NOT NULL,
|
|
2625
|
+
source_hash TEXT NOT NULL DEFAULT '',
|
|
2626
|
+
provider TEXT NOT NULL DEFAULT ''
|
|
2627
|
+
);
|
|
2628
|
+
`;
|
|
2629
|
+
var CONCEPT_INDEX_SQL = [
|
|
2630
|
+
// The enrichment runner's hot query is "which files still need work".
|
|
2631
|
+
"CREATE INDEX IF NOT EXISTS idx_fc_state ON file_concepts(state)",
|
|
2632
|
+
"CREATE INDEX IF NOT EXISTS idx_ce_from ON concept_edges(from_id)"
|
|
2633
|
+
];
|
|
1784
2634
|
var SYMBOL_VECTORS_TABLE_SQL = `
|
|
1785
2635
|
CREATE TABLE IF NOT EXISTS symbol_vectors (
|
|
1786
2636
|
symbol_id INTEGER PRIMARY KEY,
|
|
@@ -1856,6 +2706,12 @@ function initIndexSchema(db, stmt, getMetadata, setMetadata, maxSqlVars, invalid
|
|
|
1856
2706
|
DROP TABLE IF EXISTS files;
|
|
1857
2707
|
DROP TABLE IF EXISTS refs;
|
|
1858
2708
|
DROP TABLE IF EXISTS symbol_vectors;
|
|
2709
|
+
DROP TABLE IF EXISTS symbol_rank;
|
|
2710
|
+
DROP TABLE IF EXISTS file_rank;
|
|
2711
|
+
DROP TABLE IF EXISTS file_concepts;
|
|
2712
|
+
DROP TABLE IF EXISTS subsystems;
|
|
2713
|
+
DROP TABLE IF EXISTS concept_edges;
|
|
2714
|
+
DROP TABLE IF EXISTS file_vectors;
|
|
1859
2715
|
`);
|
|
1860
2716
|
db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
1861
2717
|
stmt("UPDATE metadata SET value = ? WHERE key = ?").run(String(SCHEMA_VERSION), "version");
|
|
@@ -1871,6 +2727,11 @@ function initIndexSchema(db, stmt, getMetadata, setMetadata, maxSqlVars, invalid
|
|
|
1871
2727
|
for (const sql of REFS_INDEX_SQL) db.exec(sql);
|
|
1872
2728
|
db.exec(LANG_FAMILY_TABLE_SQL);
|
|
1873
2729
|
seedLangFamilies(stmt);
|
|
2730
|
+
db.exec(RANK_TABLES_SQL);
|
|
2731
|
+
for (const sql of RANK_INDEX_SQL) db.exec(sql);
|
|
2732
|
+
db.exec(CONCEPT_TABLES_SQL);
|
|
2733
|
+
for (const sql of CONCEPT_INDEX_SQL) db.exec(sql);
|
|
2734
|
+
db.exec(FILE_VECTORS_TABLE_SQL);
|
|
1874
2735
|
let ftsAvailable = false;
|
|
1875
2736
|
try {
|
|
1876
2737
|
const ftsSchema = stmt(
|
|
@@ -2205,6 +3066,131 @@ function applyIndexStorePragmas(db) {
|
|
|
2205
3066
|
}
|
|
2206
3067
|
}
|
|
2207
3068
|
|
|
3069
|
+
// src/codebase-index/writer-rank.ts
|
|
3070
|
+
var ROW_PLACEHOLDERS_CACHE2 = /* @__PURE__ */ new Map();
|
|
3071
|
+
function getRowPlaceholders2(rowTemplate, count) {
|
|
3072
|
+
const key = `${rowTemplate}\0${count}`;
|
|
3073
|
+
let result = ROW_PLACEHOLDERS_CACHE2.get(key);
|
|
3074
|
+
if (result === void 0) {
|
|
3075
|
+
result = Array.from({ length: count }, () => rowTemplate).join(", ");
|
|
3076
|
+
ROW_PLACEHOLDERS_CACHE2.set(key, result);
|
|
3077
|
+
}
|
|
3078
|
+
return result;
|
|
3079
|
+
}
|
|
3080
|
+
function replaceSymbolRanksWithStatement(stmt, maxSqlVars, rows) {
|
|
3081
|
+
stmt("DELETE FROM symbol_rank").run();
|
|
3082
|
+
if (rows.length === 0) return;
|
|
3083
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 4)));
|
|
3084
|
+
let cursor = 0;
|
|
3085
|
+
for (const take of ladder) {
|
|
3086
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
3087
|
+
cursor += take;
|
|
3088
|
+
const placeholders2 = getRowPlaceholders2("(?, ?, ?, ?)", chunk.length);
|
|
3089
|
+
const insert = stmt(
|
|
3090
|
+
`INSERT INTO symbol_rank(symbol_id, rank, in_deg, out_deg) VALUES ${placeholders2}`
|
|
3091
|
+
);
|
|
3092
|
+
const binds = [];
|
|
3093
|
+
for (const row of chunk) binds.push(row.symbolId, row.rank, row.inDeg, row.outDeg);
|
|
3094
|
+
insert.run(...binds);
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
function replaceFileRanksWithStatement(stmt, maxSqlVars, rows) {
|
|
3098
|
+
stmt("DELETE FROM file_rank").run();
|
|
3099
|
+
if (rows.length === 0) return;
|
|
3100
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 4)));
|
|
3101
|
+
let cursor = 0;
|
|
3102
|
+
for (const take of ladder) {
|
|
3103
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
3104
|
+
cursor += take;
|
|
3105
|
+
const placeholders2 = getRowPlaceholders2("(?, ?, ?, ?)", chunk.length);
|
|
3106
|
+
const insert = stmt(
|
|
3107
|
+
`INSERT INTO file_rank(file, rank, in_deg, out_deg) VALUES ${placeholders2}`
|
|
3108
|
+
);
|
|
3109
|
+
const binds = [];
|
|
3110
|
+
for (const row of chunk) binds.push(row.file, row.rank, row.inDeg, row.outDeg);
|
|
3111
|
+
insert.run(...binds);
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
function getTopFileRanksWithStatement(stmt, limit) {
|
|
3115
|
+
if (limit <= 0) return [];
|
|
3116
|
+
return stmt(
|
|
3117
|
+
"SELECT file, rank, in_deg AS inDeg, out_deg AS outDeg FROM file_rank ORDER BY rank DESC, file ASC LIMIT ?"
|
|
3118
|
+
).all(limit);
|
|
3119
|
+
}
|
|
3120
|
+
function getRankedFilesWithStatement(stmt, limit) {
|
|
3121
|
+
if (limit <= 0) return [];
|
|
3122
|
+
return stmt(`
|
|
3123
|
+
SELECT fr.file AS file, fr.rank AS rank, fr.in_deg AS inDeg, fr.out_deg AS outDeg,
|
|
3124
|
+
f.package AS package, f.lang AS lang, f.symbol_count AS symbolCount
|
|
3125
|
+
FROM file_rank fr
|
|
3126
|
+
JOIN files f ON f.file = fr.file
|
|
3127
|
+
ORDER BY fr.rank DESC, fr.file ASC
|
|
3128
|
+
LIMIT ?
|
|
3129
|
+
`).all(limit);
|
|
3130
|
+
}
|
|
3131
|
+
function getPackageFileCountsWithStatement(stmt) {
|
|
3132
|
+
const rows = stmt("SELECT package, COUNT(*) AS n FROM files GROUP BY package").all();
|
|
3133
|
+
const map = /* @__PURE__ */ new Map();
|
|
3134
|
+
for (const row of rows) map.set(row.package, row.n);
|
|
3135
|
+
return map;
|
|
3136
|
+
}
|
|
3137
|
+
function getTopSymbolRanksWithStatement(stmt, limit) {
|
|
3138
|
+
if (limit <= 0) return [];
|
|
3139
|
+
return stmt(
|
|
3140
|
+
"SELECT symbol_id AS symbolId, rank, in_deg AS inDeg, out_deg AS outDeg FROM symbol_rank ORDER BY rank DESC, symbol_id ASC LIMIT ?"
|
|
3141
|
+
).all(limit);
|
|
3142
|
+
}
|
|
3143
|
+
function getSymbolNameCandidatesWithStatement(stmt) {
|
|
3144
|
+
const rows = stmt(`
|
|
3145
|
+
SELECT s.id AS symbolId, g.c AS candidates
|
|
3146
|
+
FROM symbols s
|
|
3147
|
+
JOIN lang_family lf ON lf.lang = s.lang
|
|
3148
|
+
JOIN (
|
|
3149
|
+
SELECT s2.name AS name, lf2.family AS family, COUNT(*) AS c
|
|
3150
|
+
FROM symbols s2
|
|
3151
|
+
JOIN lang_family lf2 ON lf2.lang = s2.lang
|
|
3152
|
+
GROUP BY s2.name, lf2.family
|
|
3153
|
+
HAVING COUNT(*) > 1
|
|
3154
|
+
) g ON g.name = s.name AND g.family = lf.family
|
|
3155
|
+
`).all();
|
|
3156
|
+
const map = /* @__PURE__ */ new Map();
|
|
3157
|
+
for (const row of rows) map.set(row.symbolId, row.candidates);
|
|
3158
|
+
return map;
|
|
3159
|
+
}
|
|
3160
|
+
function getImportVisibilityWithStatement(stmt) {
|
|
3161
|
+
const rows = stmt(`
|
|
3162
|
+
SELECT DISTINCT s.file AS src, r.to_file AS dst
|
|
3163
|
+
FROM refs r
|
|
3164
|
+
JOIN symbols s ON s.id = r.from_id
|
|
3165
|
+
WHERE r.call_type = 'import' AND r.to_file IS NOT NULL AND r.to_file <> ''
|
|
3166
|
+
`).all();
|
|
3167
|
+
const map = /* @__PURE__ */ new Map();
|
|
3168
|
+
for (const row of rows) {
|
|
3169
|
+
let targets = map.get(row.src);
|
|
3170
|
+
if (targets === void 0) {
|
|
3171
|
+
targets = /* @__PURE__ */ new Set();
|
|
3172
|
+
map.set(row.src, targets);
|
|
3173
|
+
}
|
|
3174
|
+
targets.add(row.dst);
|
|
3175
|
+
}
|
|
3176
|
+
return map;
|
|
3177
|
+
}
|
|
3178
|
+
function getFileRankMapWithStatement(stmt) {
|
|
3179
|
+
const rows = stmt("SELECT file, rank FROM file_rank").all();
|
|
3180
|
+
const map = /* @__PURE__ */ new Map();
|
|
3181
|
+
for (const row of rows) map.set(row.file, row.rank);
|
|
3182
|
+
return map;
|
|
3183
|
+
}
|
|
3184
|
+
function getRankCountsWithStatement(stmt) {
|
|
3185
|
+
const symbols = Number(
|
|
3186
|
+
stmt("SELECT COUNT(*) AS n FROM symbol_rank").get()?.n ?? 0
|
|
3187
|
+
);
|
|
3188
|
+
const files = Number(
|
|
3189
|
+
stmt("SELECT COUNT(*) AS n FROM file_rank").get()?.n ?? 0
|
|
3190
|
+
);
|
|
3191
|
+
return { symbols, files };
|
|
3192
|
+
}
|
|
3193
|
+
|
|
2208
3194
|
// src/codebase-index/writer-refs.ts
|
|
2209
3195
|
var FAMILY_MATCH_SQL = `(
|
|
2210
3196
|
sym.lang = refs.lang
|
|
@@ -2744,11 +3730,11 @@ var IndexStore = class _IndexStore {
|
|
|
2744
3730
|
bm25Cache = null;
|
|
2745
3731
|
bm25Dirty = true;
|
|
2746
3732
|
stmt(sql) {
|
|
2747
|
-
const
|
|
2748
|
-
if (
|
|
3733
|
+
const cached2 = this.stmtCache.get(sql);
|
|
3734
|
+
if (cached2 !== void 0) {
|
|
2749
3735
|
this.stmtCache.delete(sql);
|
|
2750
|
-
this.stmtCache.set(sql,
|
|
2751
|
-
return
|
|
3736
|
+
this.stmtCache.set(sql, cached2);
|
|
3737
|
+
return cached2;
|
|
2752
3738
|
}
|
|
2753
3739
|
const s = this.db.prepare(sql);
|
|
2754
3740
|
this.stmtCache.set(sql, s);
|
|
@@ -3036,6 +4022,12 @@ var IndexStore = class _IndexStore {
|
|
|
3036
4022
|
this.db.exec("DROP TABLE IF EXISTS metadata");
|
|
3037
4023
|
if (this.ftsAvailable) this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
3038
4024
|
this.db.exec("DROP TABLE IF EXISTS symbol_vectors");
|
|
4025
|
+
this.db.exec("DROP TABLE IF EXISTS symbol_rank");
|
|
4026
|
+
this.db.exec("DROP TABLE IF EXISTS file_rank");
|
|
4027
|
+
this.db.exec("DROP TABLE IF EXISTS file_concepts");
|
|
4028
|
+
this.db.exec("DROP TABLE IF EXISTS subsystems");
|
|
4029
|
+
this.db.exec("DROP TABLE IF EXISTS concept_edges");
|
|
4030
|
+
this.db.exec("DROP TABLE IF EXISTS file_vectors");
|
|
3039
4031
|
this.stmtCache.clear();
|
|
3040
4032
|
this.initSchema();
|
|
3041
4033
|
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
|
|
@@ -3262,9 +4254,151 @@ var IndexStore = class _IndexStore {
|
|
|
3262
4254
|
getAllSymbols() {
|
|
3263
4255
|
return this.stmt("SELECT id, name, file, kind, line FROM symbols ORDER BY id").all().map((r) => ({ ...r, kind: r.kind }));
|
|
3264
4256
|
}
|
|
4257
|
+
/** Declarations in one file, in source order. */
|
|
4258
|
+
getFileSymbols(file, limit) {
|
|
4259
|
+
return getFileSymbolsWithStatement((sql) => this.stmt(sql), file, limit);
|
|
4260
|
+
}
|
|
4261
|
+
/** Declarations behind an arbitrary id list, for the retrieval walk. */
|
|
4262
|
+
getSymbolsByIds(ids) {
|
|
4263
|
+
return getSymbolsByIdsWithStatement((sql) => this.stmt(sql), ids);
|
|
4264
|
+
}
|
|
3265
4265
|
getAllResolvedRefs() {
|
|
3266
4266
|
return getAllResolvedRefsWithStatement((sql) => this.stmt(sql));
|
|
3267
4267
|
}
|
|
4268
|
+
/**
|
|
4269
|
+
* Replace both rank tables in one write. Called once per index run, after
|
|
4270
|
+
* ref resolution has settled — a rank computed against half-resolved refs
|
|
4271
|
+
* would describe a graph that never existed.
|
|
4272
|
+
*/
|
|
4273
|
+
replaceRanks(symbols, files) {
|
|
4274
|
+
this.runWithRetry(() => {
|
|
4275
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
4276
|
+
try {
|
|
4277
|
+
replaceSymbolRanksWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, symbols);
|
|
4278
|
+
replaceFileRanksWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, files);
|
|
4279
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
4280
|
+
} catch (err) {
|
|
4281
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
4282
|
+
throw err;
|
|
4283
|
+
}
|
|
4284
|
+
});
|
|
4285
|
+
}
|
|
4286
|
+
// ── Semantic file vectors ────────────────────────────────────────────────
|
|
4287
|
+
/** Wipe stored vectors when the embedding model changed. */
|
|
4288
|
+
reconcileVectorProvider(provider) {
|
|
4289
|
+
return this.runWithRetry(
|
|
4290
|
+
() => reconcileVectorProviderWithStatement(
|
|
4291
|
+
(sql) => this.stmt(sql),
|
|
4292
|
+
(key) => this.getMetadata(key),
|
|
4293
|
+
(key, value) => this.setMetadata(key, value),
|
|
4294
|
+
provider
|
|
4295
|
+
)
|
|
4296
|
+
);
|
|
4297
|
+
}
|
|
4298
|
+
getFileVectorStates(provider) {
|
|
4299
|
+
return getFileVectorStatesWithStatement((sql) => this.stmt(sql), provider);
|
|
4300
|
+
}
|
|
4301
|
+
upsertFileVectors(rows) {
|
|
4302
|
+
this.runWithRetry(() => {
|
|
4303
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
4304
|
+
try {
|
|
4305
|
+
upsertFileVectorsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, rows);
|
|
4306
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
4307
|
+
} catch (err) {
|
|
4308
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
4309
|
+
throw err;
|
|
4310
|
+
}
|
|
4311
|
+
});
|
|
4312
|
+
}
|
|
4313
|
+
pruneOrphanFileVectors() {
|
|
4314
|
+
return this.runWithRetry(() => pruneOrphanFileVectorsWithStatement((sql) => this.stmt(sql)));
|
|
4315
|
+
}
|
|
4316
|
+
countFileVectors() {
|
|
4317
|
+
return countFileVectorsWithStatement((sql) => this.stmt(sql));
|
|
4318
|
+
}
|
|
4319
|
+
searchFileVectors(query, limit, minScore) {
|
|
4320
|
+
return searchFileVectorsWithStatement((sql) => this.stmt(sql), query, limit, minScore);
|
|
4321
|
+
}
|
|
4322
|
+
// ── Concept layer ────────────────────────────────────────────────────────
|
|
4323
|
+
upsertFileConcept(concept) {
|
|
4324
|
+
this.runWithRetry(() => {
|
|
4325
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
4326
|
+
try {
|
|
4327
|
+
upsertFileConceptWithStatement((sql) => this.stmt(sql), concept);
|
|
4328
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
4329
|
+
} catch (err) {
|
|
4330
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
4331
|
+
throw err;
|
|
4332
|
+
}
|
|
4333
|
+
});
|
|
4334
|
+
}
|
|
4335
|
+
getFileConcept(file) {
|
|
4336
|
+
return getFileConceptWithStatement((sql) => this.stmt(sql), file);
|
|
4337
|
+
}
|
|
4338
|
+
getAllFileConcepts() {
|
|
4339
|
+
return getAllFileConceptsWithStatement((sql) => this.stmt(sql));
|
|
4340
|
+
}
|
|
4341
|
+
getReadyConceptSummaries() {
|
|
4342
|
+
return getReadyConceptSummariesWithStatement((sql) => this.stmt(sql));
|
|
4343
|
+
}
|
|
4344
|
+
getConceptCoverage() {
|
|
4345
|
+
return getConceptCoverageWithStatement((sql) => this.stmt(sql));
|
|
4346
|
+
}
|
|
4347
|
+
/** Flag summaries whose file has changed since they were written. */
|
|
4348
|
+
markStaleConcepts() {
|
|
4349
|
+
return this.runWithRetry(() => markStaleConceptsWithStatement((sql) => this.stmt(sql)));
|
|
4350
|
+
}
|
|
4351
|
+
/** Drop summaries for files that are no longer indexed. */
|
|
4352
|
+
pruneOrphanConcepts() {
|
|
4353
|
+
return this.runWithRetry(() => pruneOrphanConceptsWithStatement((sql) => this.stmt(sql)));
|
|
4354
|
+
}
|
|
4355
|
+
replaceSubsystems(subsystems, edges) {
|
|
4356
|
+
this.runWithRetry(() => {
|
|
4357
|
+
const ownsTransaction = this.beginWriteTransaction();
|
|
4358
|
+
try {
|
|
4359
|
+
replaceSubsystemsWithStatement(
|
|
4360
|
+
(sql) => this.stmt(sql),
|
|
4361
|
+
_IndexStore.MAX_SQL_VARS,
|
|
4362
|
+
subsystems,
|
|
4363
|
+
edges
|
|
4364
|
+
);
|
|
4365
|
+
this.commitWriteTransaction(ownsTransaction);
|
|
4366
|
+
} catch (err) {
|
|
4367
|
+
this.rollbackWriteTransaction(ownsTransaction);
|
|
4368
|
+
throw err;
|
|
4369
|
+
}
|
|
4370
|
+
});
|
|
4371
|
+
}
|
|
4372
|
+
getSubsystems() {
|
|
4373
|
+
return getSubsystemsWithStatement((sql) => this.stmt(sql));
|
|
4374
|
+
}
|
|
4375
|
+
getConceptEdges() {
|
|
4376
|
+
return getConceptEdgesWithStatement((sql) => this.stmt(sql));
|
|
4377
|
+
}
|
|
4378
|
+
getPackageFileCounts() {
|
|
4379
|
+
return getPackageFileCountsWithStatement((sql) => this.stmt(sql));
|
|
4380
|
+
}
|
|
4381
|
+
getRankedFiles(limit) {
|
|
4382
|
+
return getRankedFilesWithStatement((sql) => this.stmt(sql), limit);
|
|
4383
|
+
}
|
|
4384
|
+
getTopFileRanks(limit) {
|
|
4385
|
+
return getTopFileRanksWithStatement((sql) => this.stmt(sql), limit);
|
|
4386
|
+
}
|
|
4387
|
+
getTopSymbolRanks(limit) {
|
|
4388
|
+
return getTopSymbolRanksWithStatement((sql) => this.stmt(sql), limit);
|
|
4389
|
+
}
|
|
4390
|
+
getFileRankMap() {
|
|
4391
|
+
return getFileRankMapWithStatement((sql) => this.stmt(sql));
|
|
4392
|
+
}
|
|
4393
|
+
getRankCounts() {
|
|
4394
|
+
return getRankCountsWithStatement((sql) => this.stmt(sql));
|
|
4395
|
+
}
|
|
4396
|
+
getSymbolNameCandidates() {
|
|
4397
|
+
return getSymbolNameCandidatesWithStatement((sql) => this.stmt(sql));
|
|
4398
|
+
}
|
|
4399
|
+
getImportVisibility() {
|
|
4400
|
+
return getImportVisibilityWithStatement((sql) => this.stmt(sql));
|
|
4401
|
+
}
|
|
3268
4402
|
getAllImportRefs() {
|
|
3269
4403
|
return getAllImportRefsWithStatement((sql) => this.stmt(sql));
|
|
3270
4404
|
}
|
|
@@ -3636,11 +4770,11 @@ var ModuleResolver = class {
|
|
|
3636
4770
|
}
|
|
3637
4771
|
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
3638
4772
|
resolveInclude(fromFile, spec) {
|
|
3639
|
-
const
|
|
4773
|
+
const relative3 = this.lookupWithExtensions(
|
|
3640
4774
|
path4.posix.join(path4.posix.dirname(fromFile), spec),
|
|
3641
4775
|
"c"
|
|
3642
4776
|
);
|
|
3643
|
-
if (
|
|
4777
|
+
if (relative3) return relative3;
|
|
3644
4778
|
for (const base of [
|
|
3645
4779
|
path4.posix.join(this.structure.projectRoot, "include"),
|
|
3646
4780
|
this.structure.projectRoot
|
|
@@ -3652,11 +4786,11 @@ var ModuleResolver = class {
|
|
|
3652
4786
|
}
|
|
3653
4787
|
/** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
|
|
3654
4788
|
resolveRuby(fromFile, spec) {
|
|
3655
|
-
const
|
|
4789
|
+
const relative3 = this.lookupWithExtensions(
|
|
3656
4790
|
path4.posix.join(path4.posix.dirname(fromFile), spec),
|
|
3657
4791
|
"ruby"
|
|
3658
4792
|
);
|
|
3659
|
-
if (
|
|
4793
|
+
if (relative3) return relative3;
|
|
3660
4794
|
for (const base of [
|
|
3661
4795
|
path4.posix.join(this.structure.projectRoot, "lib"),
|
|
3662
4796
|
this.structure.projectRoot
|
|
@@ -3859,7 +4993,7 @@ var ParserWorkerPool = class {
|
|
|
3859
4993
|
*/
|
|
3860
4994
|
async reparseInline(batch, orphaned, workerId) {
|
|
3861
4995
|
try {
|
|
3862
|
-
const { parseFileContent } = await import("./parser-dispatch-
|
|
4996
|
+
const { parseFileContent } = await import("./parser-dispatch-37FT3EXQ.js");
|
|
3863
4997
|
for (const item of orphaned) {
|
|
3864
4998
|
if (batch.settled) return;
|
|
3865
4999
|
try {
|
|
@@ -3952,6 +5086,10 @@ function isAbortError(err) {
|
|
|
3952
5086
|
}
|
|
3953
5087
|
var DEFAULT_IGNORE = DEFAULT_WALK_IGNORE_DIRS;
|
|
3954
5088
|
var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-lock.yaml", "pnpm-lock.yml"]);
|
|
5089
|
+
var ATLAS_PROJECTION_PREFIX = ".wrongstack/atlas/";
|
|
5090
|
+
function isAtlasProjection(relativePosixPath) {
|
|
5091
|
+
return relativePosixPath.startsWith(ATLAS_PROJECTION_PREFIX);
|
|
5092
|
+
}
|
|
3955
5093
|
var INDEXABLE_EXTENSION_SET = new Set(INDEXABLE_EXTENSIONS);
|
|
3956
5094
|
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
3957
5095
|
var MAX_GIT_FILE_LIST_BYTES = 64 * 1024 * 1024;
|
|
@@ -4023,15 +5161,15 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
4023
5161
|
}
|
|
4024
5162
|
}
|
|
4025
5163
|
const files = [];
|
|
4026
|
-
for (const
|
|
4027
|
-
if (!
|
|
4028
|
-
const portable =
|
|
4029
|
-
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path5.posix.basename(portable))) {
|
|
5164
|
+
for (const relative3 of output.toString("utf8").split("\0")) {
|
|
5165
|
+
if (!relative3) continue;
|
|
5166
|
+
const portable = relative3.replace(/\\/g, "/");
|
|
5167
|
+
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path5.posix.basename(portable)) || isAtlasProjection(portable)) {
|
|
4030
5168
|
continue;
|
|
4031
5169
|
}
|
|
4032
|
-
const full = path5.resolve(projectRoot,
|
|
5170
|
+
const full = path5.resolve(projectRoot, relative3);
|
|
4033
5171
|
if (deleted.has(full)) continue;
|
|
4034
|
-
const ext = path5.extname(
|
|
5172
|
+
const ext = path5.extname(relative3).toLowerCase();
|
|
4035
5173
|
if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
|
|
4036
5174
|
}
|
|
4037
5175
|
const snapshot = createHash("sha256").update(stagedOutput).update("\0").update(statusOutput);
|
|
@@ -4091,6 +5229,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
4091
5229
|
await walk(full);
|
|
4092
5230
|
} else if (e.isFile()) {
|
|
4093
5231
|
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
5232
|
+
if (isAtlasProjection(rel)) continue;
|
|
4094
5233
|
const ext = path5.extname(e.name).toLowerCase();
|
|
4095
5234
|
if (indexableExts.has(ext) || detectLang(full) !== null) {
|
|
4096
5235
|
results.push(full);
|
|
@@ -4191,7 +5330,7 @@ async function runIndexerAtomic(store, opts) {
|
|
|
4191
5330
|
files = opts.files.map((f) => path5.resolve(projectRoot, f)).filter((f) => {
|
|
4192
5331
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
4193
5332
|
const rel = path5.relative(projectRoot, f).replace(/\\/g, "/");
|
|
4194
|
-
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path5.basename(f)) && !isGitIgnored(rel, false);
|
|
5333
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path5.basename(f)) && !isAtlasProjection(rel) && !isGitIgnored(rel, false);
|
|
4195
5334
|
});
|
|
4196
5335
|
} else {
|
|
4197
5336
|
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
@@ -4280,6 +5419,7 @@ async function runIndexerAtomic(store, opts) {
|
|
|
4280
5419
|
let content;
|
|
4281
5420
|
try {
|
|
4282
5421
|
content = await fs5.readFile(file, { encoding: "utf8", signal });
|
|
5422
|
+
recordFilesystemRead(Buffer.byteLength(content, "utf8"));
|
|
4283
5423
|
} catch (e) {
|
|
4284
5424
|
if (isAbortError(e)) throw e;
|
|
4285
5425
|
return {
|
|
@@ -4499,6 +5639,9 @@ async function runIndexerAtomic(store, opts) {
|
|
|
4499
5639
|
});
|
|
4500
5640
|
store.setMetadata("ref_resolution_version", refResolutionVersion);
|
|
4501
5641
|
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
5642
|
+
if (shouldRefreshRanks(store, { files: opts.files, force })) {
|
|
5643
|
+
runGraphRankPass(store, errors);
|
|
5644
|
+
}
|
|
4502
5645
|
const completeProjectScope = !opts.files && (!langs || langs.length === 0) && (!opts.ignore || opts.ignore.length === 0);
|
|
4503
5646
|
if (completeProjectScope && discoverySnapshotKey !== void 0) {
|
|
4504
5647
|
const finalSnapshot = await findGitSourceFiles(projectRoot, ignore, signal);
|
|
@@ -4528,6 +5671,7 @@ async function runIndexerAtomic(store, opts) {
|
|
|
4528
5671
|
}
|
|
4529
5672
|
|
|
4530
5673
|
// src/codebase-index/index-service.ts
|
|
5674
|
+
import * as path6 from "node:path";
|
|
4531
5675
|
async function indexService(args, hooks = {}) {
|
|
4532
5676
|
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
4533
5677
|
try {
|
|
@@ -4566,6 +5710,43 @@ function searchService(args) {
|
|
|
4566
5710
|
indexStorePool.release(store);
|
|
4567
5711
|
}
|
|
4568
5712
|
}
|
|
5713
|
+
function contextService(args) {
|
|
5714
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
5715
|
+
try {
|
|
5716
|
+
return retrieveContext(
|
|
5717
|
+
store,
|
|
5718
|
+
args.projectRoot,
|
|
5719
|
+
args.indexDir,
|
|
5720
|
+
{
|
|
5721
|
+
query: args.query,
|
|
5722
|
+
vectorFiles: args.vectorFiles,
|
|
5723
|
+
limit: args.limit,
|
|
5724
|
+
symbolsPerFile: args.symbolsPerFile,
|
|
5725
|
+
pathPrefix: args.pathPrefix
|
|
5726
|
+
},
|
|
5727
|
+
(file) => {
|
|
5728
|
+
const relative3 = path6.relative(args.projectRoot, file);
|
|
5729
|
+
if (!relative3 || relative3.startsWith("..")) return posixIndexPath(file);
|
|
5730
|
+
return posixIndexPath(relative3);
|
|
5731
|
+
}
|
|
5732
|
+
);
|
|
5733
|
+
} finally {
|
|
5734
|
+
indexStorePool.release(store);
|
|
5735
|
+
}
|
|
5736
|
+
}
|
|
5737
|
+
function vectorSearchService(args) {
|
|
5738
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
5739
|
+
try {
|
|
5740
|
+
const total = store.countFileVectors();
|
|
5741
|
+
if (total === 0) return { hits: [], total };
|
|
5742
|
+
return {
|
|
5743
|
+
hits: store.searchFileVectors(Float32Array.from(args.vector), args.limit, args.minScore ?? 0),
|
|
5744
|
+
total
|
|
5745
|
+
};
|
|
5746
|
+
} finally {
|
|
5747
|
+
indexStorePool.release(store);
|
|
5748
|
+
}
|
|
5749
|
+
}
|
|
4569
5750
|
function statsService(args) {
|
|
4570
5751
|
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
4571
5752
|
try {
|
|
@@ -4628,19 +5809,46 @@ export {
|
|
|
4628
5809
|
IndexCircuitBreaker,
|
|
4629
5810
|
indexCircuitBreaker,
|
|
4630
5811
|
resetIndexCircuitBreaker,
|
|
5812
|
+
DEFAULT_RESTART,
|
|
5813
|
+
DEFAULT_ITERATIONS,
|
|
5814
|
+
UNVERIFIED_VISIBILITY_WEIGHT,
|
|
5815
|
+
CONTRADICTED_VISIBILITY_WEIGHT,
|
|
5816
|
+
buildWiringGraph,
|
|
5817
|
+
pageRank,
|
|
5818
|
+
toSymbolRankRows,
|
|
5819
|
+
aggregateFileRank,
|
|
5820
|
+
getWiringSnapshot,
|
|
5821
|
+
clearWiringSnapshot,
|
|
5822
|
+
posixIndexPath,
|
|
5823
|
+
resolveIndexDir,
|
|
5824
|
+
codebaseIndexDirOverride,
|
|
5825
|
+
SEED_LIMIT,
|
|
5826
|
+
DEFAULT_LIMIT,
|
|
5827
|
+
DEFAULT_SYMBOLS_PER_FILE,
|
|
5828
|
+
retrieveContext,
|
|
5829
|
+
xxhash64String,
|
|
5830
|
+
RANK_VERSION,
|
|
5831
|
+
RANK_VERSION_KEY,
|
|
5832
|
+
RANK_REFRESH_FILE_THRESHOLD,
|
|
5833
|
+
shouldRefreshRanks,
|
|
5834
|
+
runGraphRankPass,
|
|
4631
5835
|
tokenise,
|
|
4632
5836
|
buildIndexableText,
|
|
4633
5837
|
buildBm25Index,
|
|
4634
5838
|
SCHEMA_VERSION,
|
|
4635
|
-
|
|
4636
|
-
|
|
5839
|
+
CONCEPT_RELATIONS,
|
|
5840
|
+
isConceptRelation,
|
|
5841
|
+
decorateGraphNodes,
|
|
4637
5842
|
lspKindToInternalKind,
|
|
4638
5843
|
internalKindToLspKind,
|
|
5844
|
+
FILE_VECTOR_PROVIDER_KEY,
|
|
4639
5845
|
IndexStore,
|
|
4640
5846
|
indexStorePool,
|
|
4641
5847
|
runIndexer,
|
|
4642
5848
|
indexService,
|
|
4643
5849
|
searchService,
|
|
5850
|
+
contextService,
|
|
5851
|
+
vectorSearchService,
|
|
4644
5852
|
statsService,
|
|
4645
5853
|
packageGraphService,
|
|
4646
5854
|
fileGraphService,
|
|
@@ -4648,4 +5856,4 @@ export {
|
|
|
4648
5856
|
incomingCallsService,
|
|
4649
5857
|
outgoingCallsService
|
|
4650
5858
|
};
|
|
4651
|
-
//# sourceMappingURL=chunk-
|
|
5859
|
+
//# sourceMappingURL=chunk-52UXXQMP.js.map
|