@wrongstack/tools 0.309.0 → 0.310.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_regex.d.ts +6 -34
- package/dist/audit.js +3 -3
- package/dist/bash.js +4 -4
- package/dist/builtin.js +3020 -1680
- package/dist/codebase-index/binary-frame.d.ts +57 -8
- package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +6 -0
- package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +6 -0
- package/dist/codebase-index/codebase-search-tool.d.ts +15 -5
- package/dist/codebase-index/index-service.d.ts +3 -19
- package/dist/codebase-index/index.js +2784 -1464
- package/dist/codebase-index/indexer.d.ts +3 -0
- package/dist/codebase-index/parser-batch.d.ts +53 -0
- package/dist/codebase-index/parser-dispatch.d.ts +32 -0
- package/dist/codebase-index/parser-output.d.ts +14 -0
- package/dist/codebase-index/parser-worker-pool.d.ts +57 -4
- package/dist/codebase-index/parser-worker-script.d.ts +5 -2
- package/dist/codebase-index/parser-worker-script.js +4042 -0
- package/dist/codebase-index/project-server-cache.d.ts +16 -0
- package/dist/codebase-index/project-server-client.d.ts +2 -2
- package/dist/codebase-index/project-server-query-cache.d.ts +88 -0
- package/dist/codebase-index/project-server.js +2846 -1308
- package/dist/codebase-index/py-parser.d.ts +5 -0
- package/dist/codebase-index/schema.d.ts +14 -1
- package/dist/codebase-index/sqlite-runtime.d.ts +2 -2
- package/dist/codebase-index/tree-sitter/queries.d.ts +30 -3
- package/dist/codebase-index/tree-sitter/visitor.d.ts +2 -1
- package/dist/codebase-index/vector-search.d.ts +12 -0
- package/dist/codebase-index/wal-maintenance.d.ts +58 -0
- package/dist/codebase-index/worker-protocol/contracts.d.ts +44 -0
- package/dist/codebase-index/worker-protocol.d.ts +17 -1
- package/dist/codebase-index/worker.js +2300 -1020
- package/dist/codebase-index/writer-admin.d.ts +11 -0
- package/dist/codebase-index/writer-helpers.d.ts +31 -1
- package/dist/codebase-index/writer-mutations.d.ts +0 -6
- package/dist/codebase-index/writer-schema.d.ts +2 -2
- package/dist/codebase-index/writer.d.ts +15 -0
- package/dist/edit.js +2511 -1203
- package/dist/exec.js +8 -6
- package/dist/format.js +3 -3
- package/dist/git.js +6 -0
- package/dist/grep.js +5 -124
- package/dist/index.js +3016 -1739
- package/dist/install.js +3 -3
- package/dist/json.js +5 -124
- package/dist/kanban.js +130 -0
- package/dist/languages/index.js +3 -3
- package/dist/lint.js +3 -3
- package/dist/logs.js +5 -121
- package/dist/outdated.js +3 -3
- package/dist/pack.js +3020 -1680
- package/dist/patch.js +2524 -1216
- package/dist/plan.js +106 -0
- package/dist/process-registry.js +1 -1
- package/dist/read.js +2506 -1198
- package/dist/replace.js +2487 -1295
- package/dist/search.js +6 -2
- package/dist/session-kanban.js +24 -16
- package/dist/task.js +106 -0
- package/dist/test.js +3 -3
- package/dist/todo.js +106 -0
- package/dist/tool-tier.d.ts +11 -0
- package/dist/tool-tier.js +3028 -1680
- package/dist/tree.js +14 -3
- package/dist/typecheck.js +3 -3
- package/dist/win32.js +5 -5
- package/dist/write.js +2513 -1205
- package/package.json +5 -4
package/dist/write.js
CHANGED
|
@@ -173,238 +173,133 @@ var init_languages = __esm({
|
|
|
173
173
|
}
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
-
// src/codebase-index/
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
tsLoad2 ??= import("@typescript/typescript6").then((m) => {
|
|
184
|
-
ts = m.default ?? m;
|
|
185
|
-
return ts;
|
|
186
|
-
});
|
|
187
|
-
return tsLoad2;
|
|
188
|
-
}
|
|
189
|
-
function kindMap() {
|
|
190
|
-
kindMapCache ??= {
|
|
191
|
-
[ts.SyntaxKind.ClassDeclaration]: "class",
|
|
192
|
-
[ts.SyntaxKind.InterfaceDeclaration]: "interface",
|
|
193
|
-
[ts.SyntaxKind.EnumDeclaration]: "enum",
|
|
194
|
-
[ts.SyntaxKind.TypeAliasDeclaration]: "type",
|
|
195
|
-
[ts.SyntaxKind.FunctionDeclaration]: "function",
|
|
196
|
-
[ts.SyntaxKind.MethodDeclaration]: "method",
|
|
197
|
-
[ts.SyntaxKind.GetAccessor]: "property",
|
|
198
|
-
[ts.SyntaxKind.SetAccessor]: "property",
|
|
199
|
-
[ts.SyntaxKind.PropertyDeclaration]: "property",
|
|
200
|
-
[ts.SyntaxKind.Parameter]: "parameter",
|
|
201
|
-
[ts.SyntaxKind.NamespaceExportDeclaration]: "namespace"
|
|
202
|
-
};
|
|
203
|
-
return kindMapCache;
|
|
204
|
-
}
|
|
205
|
-
function kindOf(node) {
|
|
206
|
-
if (ts.isVariableDeclaration(node)) {
|
|
207
|
-
const parent = node.parent;
|
|
208
|
-
if (ts.isVariableDeclarationList(parent)) {
|
|
209
|
-
const flags = parent.flags;
|
|
210
|
-
if (flags & ts.NodeFlags.Let) return "let";
|
|
211
|
-
if (flags & ts.NodeFlags.Const) return "const";
|
|
212
|
-
return "var";
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
216
|
-
return kindMap()[node.kind] ?? null;
|
|
217
|
-
}
|
|
218
|
-
function getSignature(printer, node, sourceFile) {
|
|
219
|
-
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
220
|
-
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
221
|
-
}
|
|
222
|
-
function getJsDoc(node, sourceFile) {
|
|
223
|
-
const fullText = sourceFile.getFullText();
|
|
224
|
-
const nodePos = node.getFullStart();
|
|
225
|
-
const comments = ts.getLeadingCommentRanges(fullText, nodePos);
|
|
226
|
-
if (!comments) return "";
|
|
227
|
-
for (const range of comments) {
|
|
228
|
-
const commentText = fullText.slice(range.pos, range.end);
|
|
229
|
-
const trimmed = commentText.trim();
|
|
230
|
-
if (trimmed.startsWith("/**") && trimmed.endsWith("*/")) {
|
|
231
|
-
const inner = trimmed.slice(3, -2).replace(/^[ \t]*\*[ ]?/gm, "").trim();
|
|
232
|
-
return inner.split("\n")[0]?.trim().slice(0, 200) ?? "";
|
|
233
|
-
}
|
|
176
|
+
// src/codebase-index/import-extractor.ts
|
|
177
|
+
function lastSegment(specifier) {
|
|
178
|
+
const pathLike = /[/\\]|::/.test(specifier);
|
|
179
|
+
const segments = specifier.split(/[/\\]|::/).filter(Boolean);
|
|
180
|
+
let last = segments[segments.length - 1] ?? specifier;
|
|
181
|
+
if (last === "*" || last === "_") {
|
|
182
|
+
last = segments[segments.length - 2] ?? specifier;
|
|
234
183
|
}
|
|
235
|
-
return "";
|
|
184
|
+
if (pathLike) return last.replace(/\.[A-Za-z0-9]{1,8}$/, "") || last;
|
|
185
|
+
const dotted = last.split(".").filter((part) => part && part !== "*" && part !== "_");
|
|
186
|
+
return dotted[dotted.length - 1] ?? last;
|
|
236
187
|
}
|
|
237
|
-
function
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (node.name && ts.isIdentifier(node.name)) {
|
|
242
|
-
parts.push(node.name.text);
|
|
243
|
-
}
|
|
188
|
+
function newlineOffsets(content) {
|
|
189
|
+
const offsets = [];
|
|
190
|
+
for (let i = 0; i < content.length; i++) {
|
|
191
|
+
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
244
192
|
}
|
|
193
|
+
return offsets;
|
|
245
194
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
254
|
-
}
|
|
255
|
-
const symbols = [];
|
|
256
|
-
const refs = [];
|
|
257
|
-
const printer = ts.createPrinter({});
|
|
258
|
-
function visit(node, funcDepth, scopeParts) {
|
|
259
|
-
const kind = kindOf(node);
|
|
260
|
-
if (kind) {
|
|
261
|
-
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && funcDepth > 0) {
|
|
262
|
-
} else {
|
|
263
|
-
const nameNode = node.name;
|
|
264
|
-
if (!nameNode || !ts.isIdentifier(nameNode)) {
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
const name = nameNode.text;
|
|
268
|
-
const pos2 = nameNode.getStart(sourceFile);
|
|
269
|
-
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(pos2);
|
|
270
|
-
const scope = scopeParts.join(".");
|
|
271
|
-
const signature = getSignature(printer, node, sourceFile);
|
|
272
|
-
const docComment = getJsDoc(node, sourceFile);
|
|
273
|
-
const text = [name, signature, docComment].filter(Boolean).join(" | ");
|
|
274
|
-
symbols.push({
|
|
275
|
-
id: 0,
|
|
276
|
-
lang,
|
|
277
|
-
kind,
|
|
278
|
-
name,
|
|
279
|
-
file,
|
|
280
|
-
line: line2 + 1,
|
|
281
|
-
col: character,
|
|
282
|
-
signature,
|
|
283
|
-
docComment,
|
|
284
|
-
scope,
|
|
285
|
-
text
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
const pos = node.getStart(sourceFile);
|
|
290
|
-
const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
|
|
291
|
-
const lineNum = line + 1;
|
|
292
|
-
if (ts.isCallExpression(node)) {
|
|
293
|
-
const expr = node.expression;
|
|
294
|
-
if (ts.isIdentifier(expr)) {
|
|
295
|
-
refs.push({ fromId: 0, toName: expr.text, callType: "call", line: lineNum });
|
|
296
|
-
}
|
|
297
|
-
} else if (ts.isPropertyAccessExpression(node)) {
|
|
298
|
-
if (ts.isIdentifier(node.expression)) {
|
|
299
|
-
refs.push({ fromId: 0, toName: node.expression.text, callType: "call", line: lineNum });
|
|
300
|
-
}
|
|
301
|
-
} else if (ts.isTypeReferenceNode(node)) {
|
|
302
|
-
const name = getTypeName(node.typeName);
|
|
303
|
-
if (name) refs.push({ fromId: 0, toName: name, callType: "type_ref", line: lineNum });
|
|
304
|
-
} else if (ts.isHeritageClause(node)) {
|
|
305
|
-
for (const t of node.types) {
|
|
306
|
-
const name = getTypeName(t.expression);
|
|
307
|
-
if (name)
|
|
308
|
-
refs.push({
|
|
309
|
-
fromId: 0,
|
|
310
|
-
toName: name,
|
|
311
|
-
callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement",
|
|
312
|
-
line: lineNum
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
} else if (ts.isImportDeclaration(node)) {
|
|
316
|
-
emitImportSpecifierRefs(node, refs, lineNum);
|
|
317
|
-
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
|
|
318
|
-
emitExportSpecifierRefs(node, refs, lineNum);
|
|
319
|
-
}
|
|
320
|
-
const scopeIdx = scopeParts.length;
|
|
321
|
-
pushScopeName(node, scopeParts);
|
|
322
|
-
const childFuncDepth = ts.isFunctionLike(node) ? funcDepth + 1 : funcDepth;
|
|
323
|
-
ts.forEachChild(node, (child) => visit(child, childFuncDepth, scopeParts));
|
|
324
|
-
scopeParts.length = scopeIdx;
|
|
195
|
+
function lineAt(offsets, index) {
|
|
196
|
+
let low = 0;
|
|
197
|
+
let high = offsets.length;
|
|
198
|
+
while (low < high) {
|
|
199
|
+
const mid = low + high >>> 1;
|
|
200
|
+
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
201
|
+
else high = mid;
|
|
325
202
|
}
|
|
326
|
-
|
|
327
|
-
return { file, lang, symbols, refs: deduplicateRefs(refs), mtimeMs: Date.now() };
|
|
203
|
+
return low + 1;
|
|
328
204
|
}
|
|
329
|
-
function
|
|
330
|
-
|
|
331
|
-
if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
|
|
332
|
-
return "";
|
|
205
|
+
function hasImportPatterns(lang) {
|
|
206
|
+
return LANG_IMPORTS[lang] !== void 0;
|
|
333
207
|
}
|
|
334
|
-
function
|
|
208
|
+
function extractImports(opts) {
|
|
209
|
+
const patterns = LANG_IMPORTS[opts.lang];
|
|
210
|
+
if (!patterns || !opts.content) return [];
|
|
211
|
+
const limit = opts.maxImports ?? IMPORT_MAX_PER_FILE;
|
|
212
|
+
const content = opts.content.length > IMPORT_MAX_FILE_CHARS ? opts.content.slice(0, IMPORT_MAX_FILE_CHARS) : opts.content;
|
|
213
|
+
const refs = [];
|
|
335
214
|
const seen = /* @__PURE__ */ new Set();
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (!clause) {
|
|
350
|
-
if (module) {
|
|
351
|
-
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
352
|
-
}
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
if (clause.name) {
|
|
356
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
357
|
-
}
|
|
358
|
-
const bindings = clause.namedBindings;
|
|
359
|
-
if (!bindings) return;
|
|
360
|
-
if (ts.isNamedImports(bindings)) {
|
|
361
|
-
for (const element of bindings.elements) {
|
|
215
|
+
const offsets = newlineOffsets(content);
|
|
216
|
+
for (const pattern of patterns) {
|
|
217
|
+
const re = new RegExp(pattern.re.source, pattern.re.flags);
|
|
218
|
+
for (const match of content.matchAll(re)) {
|
|
219
|
+
if (refs.length >= limit) return refs;
|
|
220
|
+
const specifier = match[1]?.trim();
|
|
221
|
+
if (!specifier) continue;
|
|
222
|
+
const module = specifier;
|
|
223
|
+
const toName = pattern.name === "full" ? module : lastSegment(module);
|
|
224
|
+
if (!toName) continue;
|
|
225
|
+
const key = `${module}\0${toName}`;
|
|
226
|
+
if (seen.has(key)) continue;
|
|
227
|
+
seen.add(key);
|
|
362
228
|
refs.push({
|
|
363
229
|
fromId: 0,
|
|
364
|
-
toName
|
|
230
|
+
toName,
|
|
365
231
|
callType: "import",
|
|
366
|
-
line:
|
|
232
|
+
line: lineAt(offsets, match.index ?? 0),
|
|
233
|
+
lang: opts.lang,
|
|
367
234
|
module
|
|
368
235
|
});
|
|
369
236
|
}
|
|
370
|
-
} else if (ts.isNamespaceImport(bindings)) {
|
|
371
|
-
refs.push({
|
|
372
|
-
fromId: 0,
|
|
373
|
-
toName: bindings.name.text,
|
|
374
|
-
callType: "import",
|
|
375
|
-
line: lineNum,
|
|
376
|
-
module
|
|
377
|
-
});
|
|
378
237
|
}
|
|
238
|
+
return refs;
|
|
379
239
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
function emitExportSpecifierRefs(node, refs, lineNum) {
|
|
384
|
-
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
385
|
-
const clause = node.exportClause;
|
|
386
|
-
if (clause && ts.isNamespaceExport(clause)) {
|
|
387
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
if (clause && ts.isNamedExports(clause)) {
|
|
391
|
-
for (const element of clause.elements) {
|
|
392
|
-
const originalName = element.propertyName?.text ?? element.name.text;
|
|
393
|
-
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum, module });
|
|
394
|
-
}
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
if (module) {
|
|
398
|
-
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
var ts, tsLoad2, kindMapCache;
|
|
402
|
-
var init_ts_parser = __esm({
|
|
403
|
-
"src/codebase-index/ts-parser.ts"() {
|
|
240
|
+
var IMPORT_MAX_FILE_CHARS, IMPORT_MAX_PER_FILE, DOTTED_IMPORT, LANG_IMPORTS;
|
|
241
|
+
var init_import_extractor = __esm({
|
|
242
|
+
"src/codebase-index/import-extractor.ts"() {
|
|
404
243
|
"use strict";
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
244
|
+
IMPORT_MAX_FILE_CHARS = 512 * 1024;
|
|
245
|
+
IMPORT_MAX_PER_FILE = 400;
|
|
246
|
+
DOTTED_IMPORT = [
|
|
247
|
+
{ re: /^[ \t]*import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;?\s*$/gm }
|
|
248
|
+
];
|
|
249
|
+
LANG_IMPORTS = {
|
|
250
|
+
// Go and Python have real AST extractors; these patterns are the fallback for
|
|
251
|
+
// machines with no Go toolchain or Python interpreter installed, where the
|
|
252
|
+
// parser degrades to regex symbols and would otherwise contribute no edges.
|
|
253
|
+
go: [
|
|
254
|
+
{ re: /^[ \t]*import\s+(?:[\w.]+\s+)?"([^"]+)"/gm },
|
|
255
|
+
// Grouped form: inside `import ( … )` each line is an optional alias plus a
|
|
256
|
+
// quoted path. A stray match elsewhere resolves to no file and is dropped.
|
|
257
|
+
{ re: /^[ \t]*(?:[A-Za-z_.]\w*\s+)?"([^"]+)"\s*$/gm }
|
|
258
|
+
],
|
|
259
|
+
py: [{ re: /^[ \t]*import\s+([\w.]+)/gm }, { re: /^[ \t]*from\s+([.\w]+)\s+import\b/gm }],
|
|
260
|
+
rs: [
|
|
261
|
+
// use a::b::C; | use a::b::{C, D}; → the path before any brace
|
|
262
|
+
{ re: /^[ \t]*(?:pub\s+)?use\s+([\w:]+?)(?:::\{|\s*;|\s+as\b)/gm },
|
|
263
|
+
// mod foo; (a declaration *and* a dependency on foo.rs / foo/mod.rs)
|
|
264
|
+
{ re: /^[ \t]*(?:pub\s+)?mod\s+(\w+)\s*;/gm }
|
|
265
|
+
],
|
|
266
|
+
java: DOTTED_IMPORT,
|
|
267
|
+
kotlin: DOTTED_IMPORT,
|
|
268
|
+
scala: [{ re: /^[ \t]*import\s+([\w.]+(?:\.[_*])?)\s*$/gm }],
|
|
269
|
+
csharp: [
|
|
270
|
+
// using Foo.Bar; | using static Foo.Bar; | using Alias = Foo.Bar;
|
|
271
|
+
{ re: /^[ \t]*global\s+using\s+(?:static\s+)?([\w.]+)\s*;/gm, name: "full" },
|
|
272
|
+
{ re: /^[ \t]*using\s+(?:static\s+)?(?:\w+\s*=\s*)?([\w.]+)\s*;/gm, name: "full" }
|
|
273
|
+
],
|
|
274
|
+
// Quoted includes only: <stdio.h> is a system header with no indexed file.
|
|
275
|
+
c: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
276
|
+
cpp: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
277
|
+
ruby: [{ re: /^[ \t]*(?:require|require_relative|load)\s*\(?\s*['"]([^'"]+)['"]/gm }],
|
|
278
|
+
php: [
|
|
279
|
+
// `use A\B\C` imports the class C, which is what the index has a symbol
|
|
280
|
+
// for — the namespace symbol only covers the `A\B` prefix.
|
|
281
|
+
{ re: /^[ \t]*use\s+(?:function\s+|const\s+)?([\w\\]+)/gm },
|
|
282
|
+
{ re: /^[ \t]*(?:require|require_once|include|include_once)\s*\(?\s*['"]([^'"]+)['"]/gm }
|
|
283
|
+
],
|
|
284
|
+
swift: [{ re: /^[ \t]*import\s+(?:struct\s+|class\s+|func\s+)?([\w.]+)\s*$/gm }],
|
|
285
|
+
dart: [{ re: /^[ \t]*(?:import|export|part)\s+['"]([^'"]+)['"]/gm }],
|
|
286
|
+
lua: [{ re: /\brequire\s*\(?\s*['"]([^'"]+)['"]/g }],
|
|
287
|
+
elixir: [{ re: /^[ \t]*(?:alias|import|require|use)\s+([A-Z][\w.]*)/gm, name: "full" }],
|
|
288
|
+
haskell: [{ re: /^[ \t]*import\s+(?:qualified\s+)?([\w.]+)/gm, name: "full" }],
|
|
289
|
+
zig: [{ re: /@import\s*\(\s*"([^"]+)"\s*\)/g }],
|
|
290
|
+
proto: [{ re: /^[ \t]*import\s+(?:public\s+|weak\s+)?"([^"]+)"\s*;/gm }],
|
|
291
|
+
// `@import "x"`, `@use "x"`, `@forward "x"` — Sass and plain CSS alike.
|
|
292
|
+
css: [{ re: /^[ \t]*@(?:import|use|forward)\s+(?:url\()?\s*['"]([^'"]+)['"]/gm }],
|
|
293
|
+
// A .vue/.svelte file's <script> block is JS; the same ESM syntax applies.
|
|
294
|
+
vue: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
295
|
+
svelte: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
296
|
+
html: [
|
|
297
|
+
{ re: /<script[^>]+src\s*=\s*['"]([^'"]+)['"]/g },
|
|
298
|
+
{ re: /<link[^>]+href\s*=\s*['"]([^'"]+)['"]/g }
|
|
299
|
+
],
|
|
300
|
+
shell: [{ re: /^[ \t]*(?:source|\.)\s+(\S+)/gm }],
|
|
301
|
+
r: [{ re: /\b(?:source|library|require)\s*\(\s*['"]?([\w./-]+)['"]?\s*\)/g }]
|
|
302
|
+
};
|
|
408
303
|
}
|
|
409
304
|
});
|
|
410
305
|
|
|
@@ -490,6 +385,32 @@ function parseParserOutput(stdout, lang) {
|
|
|
490
385
|
refs: dedupeRefs(coerceRefs(record.refs, lang))
|
|
491
386
|
};
|
|
492
387
|
}
|
|
388
|
+
function parseParserBatchOutput(stdout, lang) {
|
|
389
|
+
const trimmed = stdout.trim();
|
|
390
|
+
if (!trimmed) return [];
|
|
391
|
+
let parsed;
|
|
392
|
+
try {
|
|
393
|
+
parsed = JSON.parse(trimmed);
|
|
394
|
+
} catch {
|
|
395
|
+
return [];
|
|
396
|
+
}
|
|
397
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
398
|
+
const results = parsed.results;
|
|
399
|
+
if (!Array.isArray(results)) return [];
|
|
400
|
+
return results.flatMap((entry) => {
|
|
401
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return [];
|
|
402
|
+
const candidate = entry;
|
|
403
|
+
if (typeof candidate.file !== "string" || !candidate.file) return [];
|
|
404
|
+
return [
|
|
405
|
+
{
|
|
406
|
+
file: candidate.file,
|
|
407
|
+
error: typeof candidate.error === "string" && candidate.error ? candidate.error : void 0,
|
|
408
|
+
symbols: coerceSymbols(candidate.symbols),
|
|
409
|
+
refs: dedupeRefs(coerceRefs(candidate.refs, lang))
|
|
410
|
+
}
|
|
411
|
+
];
|
|
412
|
+
});
|
|
413
|
+
}
|
|
493
414
|
function dedupeRefs(refs) {
|
|
494
415
|
const seen = /* @__PURE__ */ new Set();
|
|
495
416
|
return refs.filter((ref) => {
|
|
@@ -530,187 +451,172 @@ var init_spawn_gate = __esm({
|
|
|
530
451
|
}
|
|
531
452
|
});
|
|
532
453
|
|
|
533
|
-
// src/codebase-index/
|
|
534
|
-
var go_parser_exports = {};
|
|
535
|
-
__export(go_parser_exports, {
|
|
536
|
-
detectLang: () => detectLang,
|
|
537
|
-
parseSymbols: () => parseSymbols2
|
|
538
|
-
});
|
|
454
|
+
// src/codebase-index/parser-batch.ts
|
|
539
455
|
import { spawn } from "node:child_process";
|
|
456
|
+
import * as fsSync from "node:fs";
|
|
457
|
+
import * as fs4 from "node:fs/promises";
|
|
540
458
|
import * as os from "node:os";
|
|
541
459
|
import * as path8 from "node:path";
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
460
|
+
function chunkBatchFiles(files) {
|
|
461
|
+
const chunks = [];
|
|
462
|
+
let current = [];
|
|
463
|
+
let bytes = 0;
|
|
464
|
+
for (const file of files) {
|
|
465
|
+
const size = Buffer.byteLength(file.content, "utf8");
|
|
466
|
+
if (current.length > 0 && (current.length >= MAX_BATCH_FILES || bytes + size > MAX_BATCH_BYTES)) {
|
|
467
|
+
chunks.push(current);
|
|
468
|
+
current = [];
|
|
469
|
+
bytes = 0;
|
|
470
|
+
}
|
|
471
|
+
current.push(file);
|
|
472
|
+
bytes += size;
|
|
473
|
+
}
|
|
474
|
+
if (current.length > 0) chunks.push(current);
|
|
475
|
+
return chunks;
|
|
476
|
+
}
|
|
477
|
+
function batchTimeoutMs(fileCount) {
|
|
478
|
+
return Math.min(12e4, 15e3 + fileCount * 1500);
|
|
479
|
+
}
|
|
480
|
+
async function ensureScriptPath(cached, prefix, fileName, script) {
|
|
481
|
+
if (cached) return { path: cached, wrote: false };
|
|
482
|
+
const dir = await fs4.mkdtemp(path8.join(os.tmpdir(), prefix));
|
|
483
|
+
const scriptPath = path8.join(dir, fileName);
|
|
484
|
+
await fs4.writeFile(scriptPath, script, { encoding: "utf8", flag: "wx" });
|
|
485
|
+
process.once("exit", () => {
|
|
486
|
+
try {
|
|
487
|
+
fsSync.rmSync(dir, { recursive: true, force: true });
|
|
488
|
+
} catch {
|
|
549
489
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
} catch {
|
|
553
|
-
return fallbackParse(file, content, lang);
|
|
554
|
-
}
|
|
490
|
+
});
|
|
491
|
+
return { path: scriptPath, wrote: true };
|
|
555
492
|
}
|
|
556
|
-
function
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const fn = /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(/.exec(trimmed);
|
|
567
|
-
if (fn?.[1]) {
|
|
568
|
-
addFallbackSymbol(symbols, {
|
|
569
|
-
filePath,
|
|
570
|
-
lang,
|
|
571
|
-
kind: trimmed.startsWith("func (") ? "method" : "function",
|
|
572
|
-
name: fn[1],
|
|
573
|
-
line: idx + 1,
|
|
574
|
-
col,
|
|
575
|
-
signature: trimmed,
|
|
576
|
-
scope: packageName ? `${packageName}.${fn[1]}` : fn[1]
|
|
577
|
-
});
|
|
578
|
-
continue;
|
|
579
|
-
}
|
|
580
|
-
const typeDecl = /^type\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
581
|
-
if (typeDecl?.[1]) {
|
|
582
|
-
addFallbackSymbol(symbols, {
|
|
583
|
-
filePath,
|
|
584
|
-
lang,
|
|
585
|
-
kind: "type",
|
|
586
|
-
name: typeDecl[1],
|
|
587
|
-
line: idx + 1,
|
|
588
|
-
col,
|
|
589
|
-
signature: trimmed,
|
|
590
|
-
scope: packageName
|
|
591
|
-
});
|
|
592
|
-
continue;
|
|
593
|
-
}
|
|
594
|
-
const valueDecl = /^(const|var)\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
595
|
-
if (valueDecl?.[1] && valueDecl[2]) {
|
|
596
|
-
addFallbackSymbol(symbols, {
|
|
597
|
-
filePath,
|
|
598
|
-
lang,
|
|
599
|
-
kind: valueDecl[1],
|
|
600
|
-
name: valueDecl[2],
|
|
601
|
-
line: idx + 1,
|
|
602
|
-
col,
|
|
603
|
-
signature: trimmed,
|
|
604
|
-
scope: packageName
|
|
605
|
-
});
|
|
493
|
+
function runToolchainChild(binary, args, stdinPayload, timeoutMs) {
|
|
494
|
+
return new Promise((resolve4) => {
|
|
495
|
+
let settled = false;
|
|
496
|
+
let stdout = "";
|
|
497
|
+
let proc;
|
|
498
|
+
try {
|
|
499
|
+
proc = spawn(binary, args, { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
500
|
+
} catch {
|
|
501
|
+
resolve4(null);
|
|
502
|
+
return;
|
|
606
503
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
504
|
+
const finish = (value) => {
|
|
505
|
+
if (settled) return;
|
|
506
|
+
settled = true;
|
|
507
|
+
clearTimeout(timer);
|
|
508
|
+
resolve4(value);
|
|
509
|
+
};
|
|
510
|
+
const timer = setTimeout(() => {
|
|
511
|
+
proc.kill("SIGKILL");
|
|
512
|
+
finish(null);
|
|
513
|
+
}, timeoutMs);
|
|
514
|
+
timer.unref?.();
|
|
515
|
+
proc.on("error", () => finish(null));
|
|
516
|
+
proc.stdout?.on("data", (chunk) => {
|
|
517
|
+
stdout += chunk.toString();
|
|
518
|
+
});
|
|
519
|
+
proc.stderr?.resume();
|
|
520
|
+
proc.stdin?.on("error", () => {
|
|
521
|
+
});
|
|
522
|
+
proc.stdin?.write(stdinPayload);
|
|
523
|
+
proc.stdin?.end();
|
|
524
|
+
proc.on("close", (code) => finish({ code, stdout }));
|
|
623
525
|
});
|
|
624
526
|
}
|
|
625
|
-
function
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
527
|
+
async function runGoBatch(files, goBinary) {
|
|
528
|
+
const out = /* @__PURE__ */ new Map();
|
|
529
|
+
if (files.length === 0) return out;
|
|
530
|
+
const { path: scriptPath } = await ensureScriptPath(
|
|
531
|
+
_goBatchScriptPath,
|
|
532
|
+
"ws-go-parse",
|
|
533
|
+
"batch.go",
|
|
534
|
+
GO_BATCH_SCRIPT
|
|
535
|
+
);
|
|
536
|
+
_goBatchScriptPath = scriptPath;
|
|
537
|
+
const payload = JSON.stringify(files.map((f) => ({ file: f.file, content: f.content })));
|
|
538
|
+
const result = await withSpawnGate(
|
|
539
|
+
() => runToolchainChild(
|
|
540
|
+
goBinary ?? resolveWin32Command("go"),
|
|
541
|
+
["run", scriptPath],
|
|
542
|
+
payload,
|
|
543
|
+
batchTimeoutMs(files.length)
|
|
544
|
+
)
|
|
545
|
+
);
|
|
546
|
+
if (result?.code !== 0 || !result.stdout.trim()) return out;
|
|
547
|
+
for (const entry of parseParserBatchOutput(result.stdout, "go")) {
|
|
548
|
+
if (entry.error !== void 0) continue;
|
|
549
|
+
out.set(entry.file, {
|
|
550
|
+
file: entry.file,
|
|
551
|
+
lang: "go",
|
|
552
|
+
symbols: entry.symbols.map((s) => ({
|
|
553
|
+
id: 0,
|
|
554
|
+
lang: "go",
|
|
555
|
+
kind: s.kind,
|
|
556
|
+
name: s.name,
|
|
557
|
+
file: entry.file,
|
|
558
|
+
line: s.line,
|
|
559
|
+
col: s.col,
|
|
560
|
+
signature: s.signature ?? "",
|
|
561
|
+
docComment: "",
|
|
562
|
+
scope: s.scope ?? "",
|
|
563
|
+
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
564
|
+
})),
|
|
565
|
+
refs: entry.refs,
|
|
566
|
+
mtimeMs: Date.now()
|
|
567
|
+
});
|
|
635
568
|
}
|
|
636
|
-
return
|
|
569
|
+
return out;
|
|
637
570
|
}
|
|
638
|
-
async function
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
proc.on("close", (code2) => {
|
|
675
|
-
if (settled) return;
|
|
676
|
-
settled = true;
|
|
677
|
-
clearTimeout(timer);
|
|
678
|
-
resolve4({ code: code2, stdout: stdout2 });
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
);
|
|
682
|
-
const { code, stdout } = goResult;
|
|
683
|
-
if (code !== 0 || !stdout.trim()) {
|
|
684
|
-
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
685
|
-
}
|
|
686
|
-
const { symbols: rawSymbols, refs } = parseParserOutput(stdout, lang);
|
|
687
|
-
const symbols = rawSymbols.map((s) => ({
|
|
688
|
-
id: 0,
|
|
689
|
-
lang,
|
|
690
|
-
kind: s.kind,
|
|
691
|
-
name: s.name,
|
|
692
|
-
file: filePath,
|
|
693
|
-
line: s.line,
|
|
694
|
-
col: s.col,
|
|
695
|
-
signature: s.signature ?? "",
|
|
696
|
-
docComment: "",
|
|
697
|
-
scope: s.scope ?? "",
|
|
698
|
-
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
699
|
-
}));
|
|
700
|
-
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
701
|
-
} catch {
|
|
702
|
-
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
571
|
+
async function runPyBatch(files, pythonBinary) {
|
|
572
|
+
const out = /* @__PURE__ */ new Map();
|
|
573
|
+
if (files.length === 0) return out;
|
|
574
|
+
const { path: scriptPath } = await ensureScriptPath(
|
|
575
|
+
_pyBatchScriptPath,
|
|
576
|
+
"ws-py-parse",
|
|
577
|
+
"batch.py",
|
|
578
|
+
PY_BATCH_SCRIPT
|
|
579
|
+
);
|
|
580
|
+
_pyBatchScriptPath = scriptPath;
|
|
581
|
+
const payload = JSON.stringify(files.map((f) => ({ file: f.file, content: f.content })));
|
|
582
|
+
const result = await withSpawnGate(
|
|
583
|
+
() => runToolchainChild(pythonBinary, [scriptPath], payload, batchTimeoutMs(files.length))
|
|
584
|
+
);
|
|
585
|
+
if (result?.code !== 0 || !result.stdout.trim()) return out;
|
|
586
|
+
for (const entry of parseParserBatchOutput(result.stdout, "py")) {
|
|
587
|
+
if (entry.error !== void 0) continue;
|
|
588
|
+
out.set(entry.file, {
|
|
589
|
+
file: entry.file,
|
|
590
|
+
lang: "py",
|
|
591
|
+
symbols: entry.symbols.map((s) => ({
|
|
592
|
+
id: 0,
|
|
593
|
+
lang: "py",
|
|
594
|
+
kind: s.kind,
|
|
595
|
+
name: s.name,
|
|
596
|
+
file: entry.file,
|
|
597
|
+
line: s.line,
|
|
598
|
+
col: s.col,
|
|
599
|
+
signature: s.signature ?? "",
|
|
600
|
+
docComment: "",
|
|
601
|
+
scope: s.scope ?? "",
|
|
602
|
+
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
603
|
+
})),
|
|
604
|
+
refs: entry.refs,
|
|
605
|
+
mtimeMs: Date.now()
|
|
606
|
+
});
|
|
703
607
|
}
|
|
608
|
+
return out;
|
|
704
609
|
}
|
|
705
|
-
var
|
|
706
|
-
var
|
|
707
|
-
"src/codebase-index/
|
|
610
|
+
var MAX_BATCH_FILES, MAX_BATCH_BYTES, GO_BATCH_SCRIPT, PY_BATCH_SCRIPT, _goBatchScriptPath, _pyBatchScriptPath;
|
|
611
|
+
var init_parser_batch = __esm({
|
|
612
|
+
"src/codebase-index/parser-batch.ts"() {
|
|
708
613
|
"use strict";
|
|
709
614
|
init_win32_resolve();
|
|
710
615
|
init_parser_output();
|
|
711
616
|
init_spawn_gate();
|
|
712
|
-
|
|
713
|
-
|
|
617
|
+
MAX_BATCH_FILES = 100;
|
|
618
|
+
MAX_BATCH_BYTES = 8 * 1024 * 1024;
|
|
619
|
+
GO_BATCH_SCRIPT = `
|
|
714
620
|
package main
|
|
715
621
|
|
|
716
622
|
import (
|
|
@@ -734,8 +640,6 @@ type Sym struct {
|
|
|
734
640
|
Scope string \`json:"scope"\`
|
|
735
641
|
}
|
|
736
642
|
|
|
737
|
-
// Ref is a cross-reference emitted alongside the symbols, so one \`go run\`
|
|
738
|
-
// yields both. Module is the import path for CallType "import", else empty.
|
|
739
643
|
type Ref struct {
|
|
740
644
|
ToName string \`json:"toName"\`
|
|
741
645
|
CallType string \`json:"callType"\`
|
|
@@ -743,57 +647,53 @@ type Ref struct {
|
|
|
743
647
|
Module string \`json:"module"\`
|
|
744
648
|
}
|
|
745
649
|
|
|
746
|
-
type
|
|
747
|
-
|
|
748
|
-
|
|
650
|
+
type FileResult struct {
|
|
651
|
+
File string \`json:"file"\`
|
|
652
|
+
Error string \`json:"error,omitempty"\`
|
|
653
|
+
Symbols []Sym \`json:"symbols"\`
|
|
654
|
+
Refs []Ref \`json:"refs"\`
|
|
749
655
|
}
|
|
750
656
|
|
|
751
|
-
|
|
752
|
-
|
|
657
|
+
type BatchResult struct {
|
|
658
|
+
Version int \`json:"version"\`
|
|
659
|
+
Results []FileResult \`json:"results"\`
|
|
753
660
|
}
|
|
754
661
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
662
|
+
type inputFile struct {
|
|
663
|
+
File string \`json:"file"\`
|
|
664
|
+
Content string \`json:"content"\`
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
func parseOne(name string, src []byte) FileResult {
|
|
668
|
+
res := FileResult{File: name, Symbols: []Sym{}, Refs: []Ref{}}
|
|
761
669
|
fset := token.NewFileSet()
|
|
762
670
|
node, err := parser.ParseFile(fset, "src.go", src, 0)
|
|
763
671
|
if err != nil {
|
|
764
|
-
|
|
765
|
-
return
|
|
672
|
+
res.Error = err.Error()
|
|
673
|
+
return res
|
|
766
674
|
}
|
|
767
|
-
|
|
768
|
-
var syms []Sym
|
|
769
|
-
|
|
770
|
-
// Package-level scope
|
|
771
675
|
pkgScope := node.Name.Name
|
|
772
|
-
|
|
773
|
-
// Collect all top-level declarations
|
|
774
676
|
for _, decl := range node.Decls {
|
|
775
677
|
switch d := decl.(type) {
|
|
776
678
|
case *ast.FuncDecl:
|
|
777
|
-
|
|
679
|
+
symName := d.Name.Name
|
|
778
680
|
kind := "function"
|
|
779
681
|
scope := pkgScope
|
|
780
682
|
if d.Recv != nil && len(d.Recv.List) > 0 {
|
|
781
|
-
scope = pkgScope + "." + recvTypeName(d.Recv.List[0].Type) + "." +
|
|
683
|
+
scope = pkgScope + "." + recvTypeName(d.Recv.List[0].Type) + "." + symName
|
|
782
684
|
kind = "method"
|
|
783
685
|
} else {
|
|
784
|
-
scope = pkgScope + "." +
|
|
686
|
+
scope = pkgScope + "." + symName
|
|
785
687
|
}
|
|
786
688
|
pos := fset.Position(d.Pos())
|
|
787
|
-
|
|
788
|
-
syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: scope})
|
|
789
|
-
|
|
689
|
+
res.Symbols = append(res.Symbols, Sym{Name: symName, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: formatFuncSig(d), Scope: scope})
|
|
790
690
|
case *ast.GenDecl:
|
|
791
691
|
for _, spec := range d.Specs {
|
|
792
692
|
switch s := spec.(type) {
|
|
793
693
|
case *ast.TypeSpec:
|
|
794
|
-
|
|
694
|
+
typeName := s.Name.Name
|
|
795
695
|
pos := fset.Position(s.Pos())
|
|
796
|
-
sig := "type " +
|
|
696
|
+
sig := "type " + typeName
|
|
797
697
|
if s.TypeParams != nil {
|
|
798
698
|
sig += formatTypeParams(s.TypeParams)
|
|
799
699
|
}
|
|
@@ -804,64 +704,72 @@ func main() {
|
|
|
804
704
|
} else {
|
|
805
705
|
sig += " = " + formatType(s.Type)
|
|
806
706
|
}
|
|
807
|
-
|
|
808
|
-
|
|
707
|
+
res.Symbols = append(res.Symbols, Sym{Name: typeName, Kind: "type", Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
809
708
|
case *ast.ValueSpec:
|
|
810
709
|
for _, n := range s.Names {
|
|
811
|
-
|
|
710
|
+
valueName := n.Name
|
|
812
711
|
pos := fset.Position(n.Pos())
|
|
813
712
|
kind := "var"
|
|
814
713
|
if d.Tok == token.CONST {
|
|
815
714
|
kind = "const"
|
|
816
715
|
}
|
|
817
|
-
sig := kind + " " +
|
|
716
|
+
sig := kind + " " + valueName
|
|
818
717
|
if s.Type != nil {
|
|
819
718
|
sig += " " + formatType(s.Type)
|
|
820
719
|
}
|
|
821
|
-
|
|
720
|
+
res.Symbols = append(res.Symbols, Sym{Name: valueName, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
822
721
|
}
|
|
823
722
|
}
|
|
824
723
|
}
|
|
825
724
|
}
|
|
826
725
|
}
|
|
827
|
-
|
|
828
|
-
refs := []Ref{}
|
|
829
726
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
830
727
|
switch expr := n.(type) {
|
|
831
728
|
case *ast.CallExpr:
|
|
832
729
|
line := fset.Position(expr.Pos()).Line
|
|
833
730
|
switch fun := expr.Fun.(type) {
|
|
834
731
|
case *ast.Ident:
|
|
835
|
-
|
|
732
|
+
res.Refs = append(res.Refs, Ref{ToName: fun.Name, CallType: "call", Line: line})
|
|
836
733
|
case *ast.SelectorExpr:
|
|
837
|
-
|
|
838
|
-
// declared symbol name, so it resolves the same way the TypeScript
|
|
839
|
-
// and Python extractors' call refs do.
|
|
840
|
-
refs = append(refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
734
|
+
res.Refs = append(res.Refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
841
735
|
}
|
|
842
736
|
case *ast.ImportSpec:
|
|
843
737
|
if expr.Path != nil {
|
|
844
738
|
if importPath, uerr := strconv.Unquote(expr.Path.Value); uerr == nil {
|
|
845
739
|
line := fset.Position(expr.Pos()).Line
|
|
846
|
-
// A Go import names a package, not a symbol; the package's
|
|
847
|
-
// last path segment is the name it is referenced by.
|
|
848
740
|
name := importPath
|
|
849
741
|
if idx := strings.LastIndex(importPath, "/"); idx >= 0 {
|
|
850
742
|
name = importPath[idx+1:]
|
|
851
743
|
}
|
|
852
|
-
|
|
744
|
+
res.Refs = append(res.Refs, Ref{ToName: name, CallType: "import", Line: line, Module: importPath})
|
|
853
745
|
}
|
|
854
746
|
}
|
|
855
747
|
}
|
|
856
748
|
return true
|
|
857
749
|
})
|
|
750
|
+
return res
|
|
751
|
+
}
|
|
858
752
|
|
|
859
|
-
|
|
860
|
-
|
|
753
|
+
func main() {
|
|
754
|
+
raw, err := io.ReadAll(os.Stdin)
|
|
755
|
+
if err != nil {
|
|
756
|
+
out, _ := json.Marshal(BatchResult{Version: 1, Results: []FileResult{}})
|
|
757
|
+
fmt.Print(string(out))
|
|
758
|
+
return
|
|
861
759
|
}
|
|
862
|
-
|
|
760
|
+
var inputs []inputFile
|
|
761
|
+
if err := json.Unmarshal(raw, &inputs); err != nil {
|
|
762
|
+
out, _ := json.Marshal(BatchResult{Version: 1, Results: []FileResult{}})
|
|
763
|
+
fmt.Print(string(out))
|
|
764
|
+
return
|
|
765
|
+
}
|
|
766
|
+
results := make([]FileResult, 0, len(inputs))
|
|
767
|
+
for _, in := range inputs {
|
|
768
|
+
results = append(results, parseOne(in.File, []byte(in.Content)))
|
|
769
|
+
}
|
|
770
|
+
data, err := json.Marshal(BatchResult{Version: 1, Results: results})
|
|
863
771
|
if err != nil {
|
|
864
|
-
fmt.Print(
|
|
772
|
+
fmt.Print("{\\"version\\":1,\\"results\\":[]}")
|
|
865
773
|
return
|
|
866
774
|
}
|
|
867
775
|
fmt.Print(string(data))
|
|
@@ -981,10 +889,8 @@ func formatType(t ast.Expr) string {
|
|
|
981
889
|
case *ast.BasicLit:
|
|
982
890
|
return v.Value
|
|
983
891
|
case *ast.IndexExpr:
|
|
984
|
-
// Generic instantiation with one type arg, e.g. Logger[int].
|
|
985
892
|
return formatType(v.X) + "[" + formatType(v.Index) + "]"
|
|
986
893
|
case *ast.IndexListExpr:
|
|
987
|
-
// Generic instantiation with multiple type args, e.g. Map[K, V].
|
|
988
894
|
args := make([]string, len(v.Indices))
|
|
989
895
|
for i, idx := range v.Indices {
|
|
990
896
|
args[i] = formatType(idx)
|
|
@@ -995,7 +901,187 @@ func formatType(t ast.Expr) string {
|
|
|
995
901
|
}
|
|
996
902
|
}
|
|
997
903
|
`;
|
|
998
|
-
|
|
904
|
+
PY_BATCH_SCRIPT = `import ast, json, sys
|
|
905
|
+
|
|
906
|
+
def get_name(node):
|
|
907
|
+
if isinstance(node, ast.Name):
|
|
908
|
+
return node.id
|
|
909
|
+
elif isinstance(node, ast.Attribute):
|
|
910
|
+
return get_name(node.value) + "." + node.attr
|
|
911
|
+
elif isinstance(node, ast.Subscript):
|
|
912
|
+
return get_name(node.value)
|
|
913
|
+
elif isinstance(node, ast.Call):
|
|
914
|
+
return get_name(node.func)
|
|
915
|
+
elif isinstance(node, ast.Constant):
|
|
916
|
+
return str(node.value)
|
|
917
|
+
return ""
|
|
918
|
+
|
|
919
|
+
def leaf_name(node):
|
|
920
|
+
if isinstance(node, ast.Attribute):
|
|
921
|
+
return node.attr
|
|
922
|
+
if isinstance(node, ast.Name):
|
|
923
|
+
return node.id
|
|
924
|
+
return get_name(node).split(".")[-1]
|
|
925
|
+
|
|
926
|
+
def is_private(name):
|
|
927
|
+
return name.startswith("__") and not name.endswith("__")
|
|
928
|
+
|
|
929
|
+
def parse_one(name, source, module_name):
|
|
930
|
+
result = {"file": name, "symbols": [], "refs": []}
|
|
931
|
+
try:
|
|
932
|
+
tree = ast.parse(source, filename=name)
|
|
933
|
+
except Exception as e:
|
|
934
|
+
result["error"] = str(e)
|
|
935
|
+
return result
|
|
936
|
+
syms = []
|
|
937
|
+
refs = []
|
|
938
|
+
scope_stack = [module_name]
|
|
939
|
+
|
|
940
|
+
def sym(d):
|
|
941
|
+
return {
|
|
942
|
+
"name": d["name"], "kind": d["kind"], "line": d["line"], "col": d["col"],
|
|
943
|
+
"signature": d["signature"], "scope": d["scope"],
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
class Visitor(ast.NodeVisitor):
|
|
947
|
+
def visit_ClassDef(self, node):
|
|
948
|
+
bases = [get_name(b) for b in node.bases]
|
|
949
|
+
sig = "class " + node.name
|
|
950
|
+
if bases:
|
|
951
|
+
sig += "(" + ", ".join(bases) + ")"
|
|
952
|
+
sig += ": ..."
|
|
953
|
+
syms.append(sym({
|
|
954
|
+
"name": node.name, "kind": "class", "line": node.lineno,
|
|
955
|
+
"col": node.col_offset, "signature": sig,
|
|
956
|
+
"scope": ".".join(scope_stack) + "." + node.name,
|
|
957
|
+
}))
|
|
958
|
+
scope_stack.append(node.name)
|
|
959
|
+
self.generic_visit(node)
|
|
960
|
+
scope_stack.pop()
|
|
961
|
+
|
|
962
|
+
def visit_FunctionDef(self, node):
|
|
963
|
+
args = ", ".join(a.arg for a in node.args.args)
|
|
964
|
+
returns = get_name(node.returns) if node.returns is not None else ""
|
|
965
|
+
is_async = isinstance(node, ast.AsyncFunctionDef)
|
|
966
|
+
kind = "function"
|
|
967
|
+
prefix = "def "
|
|
968
|
+
for dec in node.decorator_list:
|
|
969
|
+
d = get_name(dec)
|
|
970
|
+
if d.endswith(".staticmethod"):
|
|
971
|
+
kind = "staticmethod"
|
|
972
|
+
elif d.endswith(".classmethod"):
|
|
973
|
+
kind = "classmethod"
|
|
974
|
+
elif d == "property":
|
|
975
|
+
kind = "property"
|
|
976
|
+
if is_async:
|
|
977
|
+
kind = "async_" + kind
|
|
978
|
+
sig = f"{prefix}{node.name}({args})"
|
|
979
|
+
if returns:
|
|
980
|
+
sig += f" -> {returns}"
|
|
981
|
+
syms.append(sym({
|
|
982
|
+
"name": node.name, "kind": kind, "line": node.lineno,
|
|
983
|
+
"col": node.col_offset, "signature": sig,
|
|
984
|
+
"scope": ".".join(scope_stack) + "." + node.name,
|
|
985
|
+
}))
|
|
986
|
+
|
|
987
|
+
def visit_AsyncFunctionDef(self, node):
|
|
988
|
+
self.visit_FunctionDef(node)
|
|
989
|
+
|
|
990
|
+
def visit_Assign(self, node):
|
|
991
|
+
for target in node.targets:
|
|
992
|
+
if isinstance(target, ast.Name):
|
|
993
|
+
tname = target.id
|
|
994
|
+
if is_private(tname):
|
|
995
|
+
continue
|
|
996
|
+
kind = "const" if tname.isupper() else "var"
|
|
997
|
+
col = target.col_offset if hasattr(target, "col_offset") else 0
|
|
998
|
+
syms.append(sym({
|
|
999
|
+
"name": tname, "kind": kind, "line": node.lineno, "col": col,
|
|
1000
|
+
"signature": f"{tname} = ...", "scope": ".".join(scope_stack),
|
|
1001
|
+
}))
|
|
1002
|
+
|
|
1003
|
+
def visit_AnnAssign(self, node):
|
|
1004
|
+
if isinstance(node.target, ast.Name):
|
|
1005
|
+
tname = node.target.id
|
|
1006
|
+
if is_private(tname):
|
|
1007
|
+
return
|
|
1008
|
+
kind = "const" if tname.isupper() else "var"
|
|
1009
|
+
col = node.target.col_offset if hasattr(node.target, "col_offset") else 0
|
|
1010
|
+
sig = f"{tname}: {get_name(node.annotation)}"
|
|
1011
|
+
if node.value:
|
|
1012
|
+
sig += " = ..."
|
|
1013
|
+
syms.append(sym({
|
|
1014
|
+
"name": tname, "kind": kind, "line": node.lineno, "col": col,
|
|
1015
|
+
"signature": sig, "scope": ".".join(scope_stack),
|
|
1016
|
+
}))
|
|
1017
|
+
|
|
1018
|
+
def visit_Import(self, node):
|
|
1019
|
+
# Parity with the single-file parser: imports are symbols too.
|
|
1020
|
+
for alias in node.names:
|
|
1021
|
+
name = alias.asname or alias.name
|
|
1022
|
+
syms.append(sym({
|
|
1023
|
+
"name": name, "kind": "import", "line": node.lineno,
|
|
1024
|
+
"col": node.col_offset, "signature": f"import {alias.name}",
|
|
1025
|
+
"scope": ".".join(scope_stack),
|
|
1026
|
+
}))
|
|
1027
|
+
|
|
1028
|
+
def visit_ImportFrom(self, node):
|
|
1029
|
+
module = node.module or ""
|
|
1030
|
+
for alias in node.names:
|
|
1031
|
+
name = alias.asname or alias.name
|
|
1032
|
+
syms.append(sym({
|
|
1033
|
+
"name": name, "kind": "import", "line": node.lineno,
|
|
1034
|
+
"col": node.col_offset, "signature": f"from {module} import {alias.name}",
|
|
1035
|
+
"scope": ".".join(scope_stack),
|
|
1036
|
+
}))
|
|
1037
|
+
|
|
1038
|
+
Visitor().visit(tree)
|
|
1039
|
+
|
|
1040
|
+
for node in ast.walk(tree):
|
|
1041
|
+
if isinstance(node, ast.Call):
|
|
1042
|
+
cname = leaf_name(node.func)
|
|
1043
|
+
if cname:
|
|
1044
|
+
refs.append({"toName": cname, "callType": "call", "line": node.lineno})
|
|
1045
|
+
elif isinstance(node, ast.Import):
|
|
1046
|
+
for alias in node.names:
|
|
1047
|
+
refs.append({
|
|
1048
|
+
"toName": alias.name.split(".")[-1], "callType": "import",
|
|
1049
|
+
"line": node.lineno, "module": alias.name,
|
|
1050
|
+
})
|
|
1051
|
+
elif isinstance(node, ast.ImportFrom):
|
|
1052
|
+
module = ("." * (node.level or 0)) + (node.module or "")
|
|
1053
|
+
for alias in node.names:
|
|
1054
|
+
refs.append({
|
|
1055
|
+
"toName": alias.name, "callType": "import",
|
|
1056
|
+
"line": node.lineno, "module": module,
|
|
1057
|
+
})
|
|
1058
|
+
elif isinstance(node, ast.ClassDef):
|
|
1059
|
+
for base in node.bases:
|
|
1060
|
+
bname = leaf_name(base)
|
|
1061
|
+
if bname:
|
|
1062
|
+
refs.append({"toName": bname, "callType": "inherit", "line": node.lineno})
|
|
1063
|
+
|
|
1064
|
+
result["symbols"] = syms
|
|
1065
|
+
result["refs"] = refs
|
|
1066
|
+
return result
|
|
1067
|
+
|
|
1068
|
+
def main():
|
|
1069
|
+
try:
|
|
1070
|
+
inputs = json.loads(sys.stdin.read())
|
|
1071
|
+
except Exception:
|
|
1072
|
+
print(json.dumps({"version": 1, "results": []}))
|
|
1073
|
+
return
|
|
1074
|
+
results = []
|
|
1075
|
+
for entry in inputs:
|
|
1076
|
+
name = entry.get("file", "")
|
|
1077
|
+
module_name = name.rsplit("/", 1)[-1].rsplit("\\\\", 1)[-1][:-3]
|
|
1078
|
+
results.append(parse_one(name, entry.get("content", ""), module_name))
|
|
1079
|
+
print(json.dumps({"version": 1, "results": results}))
|
|
1080
|
+
|
|
1081
|
+
main()
|
|
1082
|
+
`;
|
|
1083
|
+
_goBatchScriptPath = null;
|
|
1084
|
+
_pyBatchScriptPath = null;
|
|
999
1085
|
}
|
|
1000
1086
|
});
|
|
1001
1087
|
|
|
@@ -1005,7 +1091,7 @@ __export(generic_parser_exports, {
|
|
|
1005
1091
|
GENERIC_MAX_FILE_CHARS: () => GENERIC_MAX_FILE_CHARS,
|
|
1006
1092
|
GENERIC_MAX_SYMBOLS_DEFAULT: () => GENERIC_MAX_SYMBOLS_DEFAULT,
|
|
1007
1093
|
parseGeneric: () => parseGeneric,
|
|
1008
|
-
parseSymbols: () =>
|
|
1094
|
+
parseSymbols: () => parseSymbols
|
|
1009
1095
|
});
|
|
1010
1096
|
function patternsFor(lang) {
|
|
1011
1097
|
return LANG_PATTERNS[lang] ?? LANG_PATTERNS.other ?? [];
|
|
@@ -1093,7 +1179,7 @@ function parseGeneric(opts) {
|
|
|
1093
1179
|
}
|
|
1094
1180
|
return { file, lang, symbols, mtimeMs };
|
|
1095
1181
|
}
|
|
1096
|
-
async function
|
|
1182
|
+
async function parseSymbols(opts) {
|
|
1097
1183
|
return parseGeneric(opts);
|
|
1098
1184
|
}
|
|
1099
1185
|
var C_LIKE, LANG_PATTERNS, KEYWORDS, GENERIC_MAX_SYMBOLS_DEFAULT, GENERIC_MAX_FILE_CHARS;
|
|
@@ -1326,13 +1412,14 @@ var init_generic_parser = __esm({
|
|
|
1326
1412
|
var py_parser_exports = {};
|
|
1327
1413
|
__export(py_parser_exports, {
|
|
1328
1414
|
detectLang: () => detectLang,
|
|
1329
|
-
parseSymbols: () =>
|
|
1415
|
+
parseSymbols: () => parseSymbols2,
|
|
1416
|
+
resolvePythonBinary: () => resolvePythonBinary
|
|
1330
1417
|
});
|
|
1331
1418
|
import { spawn as spawn2 } from "node:child_process";
|
|
1332
1419
|
import * as fs5 from "node:fs/promises";
|
|
1333
1420
|
import * as os2 from "node:os";
|
|
1334
1421
|
import * as path9 from "node:path";
|
|
1335
|
-
async function
|
|
1422
|
+
async function parseSymbols2(opts) {
|
|
1336
1423
|
const { file, content, lang } = opts;
|
|
1337
1424
|
try {
|
|
1338
1425
|
const native = await withSpawnGate(() => syncPyParse(file, content, lang));
|
|
@@ -1406,6 +1493,10 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
|
1406
1493
|
});
|
|
1407
1494
|
});
|
|
1408
1495
|
}
|
|
1496
|
+
function resolvePythonBinary() {
|
|
1497
|
+
cachedPyBinary ??= resolvePython();
|
|
1498
|
+
return cachedPyBinary;
|
|
1499
|
+
}
|
|
1409
1500
|
async function syncPyParse(filePath, content, lang) {
|
|
1410
1501
|
try {
|
|
1411
1502
|
if (!_cachedScriptPath) {
|
|
@@ -1696,9 +1787,713 @@ for node in ast.walk(tree):
|
|
|
1696
1787
|
if name:
|
|
1697
1788
|
refs.append({"toName": name, "callType": "inherit", "line": node.lineno})
|
|
1698
1789
|
|
|
1699
|
-
print(json.dumps({"symbols": [s.to_dict() for s in syms], "refs": refs}))
|
|
1790
|
+
print(json.dumps({"symbols": [s.to_dict() for s in syms], "refs": refs}))
|
|
1791
|
+
`;
|
|
1792
|
+
_cachedScriptPath = null;
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
|
|
1796
|
+
// src/codebase-index/ts-parser.ts
|
|
1797
|
+
var ts_parser_exports = {};
|
|
1798
|
+
__export(ts_parser_exports, {
|
|
1799
|
+
detectLang: () => detectLang,
|
|
1800
|
+
parseSymbols: () => parseSymbols3
|
|
1801
|
+
});
|
|
1802
|
+
function loadTypescript2() {
|
|
1803
|
+
tsLoad2 ??= import("@typescript/typescript6").then((m) => {
|
|
1804
|
+
ts = m.default ?? m;
|
|
1805
|
+
return ts;
|
|
1806
|
+
});
|
|
1807
|
+
return tsLoad2;
|
|
1808
|
+
}
|
|
1809
|
+
function kindMap() {
|
|
1810
|
+
kindMapCache ??= {
|
|
1811
|
+
[ts.SyntaxKind.ClassDeclaration]: "class",
|
|
1812
|
+
[ts.SyntaxKind.InterfaceDeclaration]: "interface",
|
|
1813
|
+
[ts.SyntaxKind.EnumDeclaration]: "enum",
|
|
1814
|
+
[ts.SyntaxKind.TypeAliasDeclaration]: "type",
|
|
1815
|
+
[ts.SyntaxKind.FunctionDeclaration]: "function",
|
|
1816
|
+
[ts.SyntaxKind.MethodDeclaration]: "method",
|
|
1817
|
+
[ts.SyntaxKind.GetAccessor]: "property",
|
|
1818
|
+
[ts.SyntaxKind.SetAccessor]: "property",
|
|
1819
|
+
[ts.SyntaxKind.PropertyDeclaration]: "property",
|
|
1820
|
+
[ts.SyntaxKind.Parameter]: "parameter",
|
|
1821
|
+
[ts.SyntaxKind.NamespaceExportDeclaration]: "namespace"
|
|
1822
|
+
};
|
|
1823
|
+
return kindMapCache;
|
|
1824
|
+
}
|
|
1825
|
+
function kindOf(node) {
|
|
1826
|
+
if (ts.isVariableDeclaration(node)) {
|
|
1827
|
+
const parent = node.parent;
|
|
1828
|
+
if (ts.isVariableDeclarationList(parent)) {
|
|
1829
|
+
const flags = parent.flags;
|
|
1830
|
+
if (flags & ts.NodeFlags.Let) return "let";
|
|
1831
|
+
if (flags & ts.NodeFlags.Const) return "const";
|
|
1832
|
+
return "var";
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
1836
|
+
return kindMap()[node.kind] ?? null;
|
|
1837
|
+
}
|
|
1838
|
+
function getSignature(printer, node, sourceFile) {
|
|
1839
|
+
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
1840
|
+
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
1841
|
+
}
|
|
1842
|
+
function getJsDoc(node, sourceFile) {
|
|
1843
|
+
const fullText = sourceFile.getFullText();
|
|
1844
|
+
const nodePos = node.getFullStart();
|
|
1845
|
+
const comments = ts.getLeadingCommentRanges(fullText, nodePos);
|
|
1846
|
+
if (!comments) return "";
|
|
1847
|
+
for (const range of comments) {
|
|
1848
|
+
const commentText = fullText.slice(range.pos, range.end);
|
|
1849
|
+
const trimmed = commentText.trim();
|
|
1850
|
+
if (trimmed.startsWith("/**") && trimmed.endsWith("*/")) {
|
|
1851
|
+
const inner = trimmed.slice(3, -2).replace(/^[ \t]*\*[ ]?/gm, "").trim();
|
|
1852
|
+
return inner.split("\n")[0]?.trim().slice(0, 200) ?? "";
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
return "";
|
|
1856
|
+
}
|
|
1857
|
+
function pushScopeName(node, parts) {
|
|
1858
|
+
if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
|
|
1859
|
+
parts.push(node.name?.text ?? "Anon");
|
|
1860
|
+
} else if (ts.isMethodDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node) || ts.isPropertyDeclaration(node) || ts.isFunctionDeclaration(node)) {
|
|
1861
|
+
if (node.name && ts.isIdentifier(node.name)) {
|
|
1862
|
+
parts.push(node.name.text);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
async function parseSymbols3(opts) {
|
|
1867
|
+
const { file, content, lang } = opts;
|
|
1868
|
+
await loadTypescript2();
|
|
1869
|
+
let sourceFile;
|
|
1870
|
+
try {
|
|
1871
|
+
sourceFile = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true);
|
|
1872
|
+
} catch {
|
|
1873
|
+
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
1874
|
+
}
|
|
1875
|
+
const symbols = [];
|
|
1876
|
+
const refs = [];
|
|
1877
|
+
const printer = ts.createPrinter({});
|
|
1878
|
+
function visit(node, funcDepth, scopeParts) {
|
|
1879
|
+
const kind = kindOf(node);
|
|
1880
|
+
if (kind) {
|
|
1881
|
+
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && funcDepth > 0) {
|
|
1882
|
+
} else {
|
|
1883
|
+
const nameNode = node.name;
|
|
1884
|
+
if (!nameNode || !ts.isIdentifier(nameNode)) {
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
const name = nameNode.text;
|
|
1888
|
+
const pos2 = nameNode.getStart(sourceFile);
|
|
1889
|
+
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(pos2);
|
|
1890
|
+
const scope = scopeParts.join(".");
|
|
1891
|
+
const signature = getSignature(printer, node, sourceFile);
|
|
1892
|
+
const docComment = getJsDoc(node, sourceFile);
|
|
1893
|
+
const text = [name, signature, docComment].filter(Boolean).join(" | ");
|
|
1894
|
+
symbols.push({
|
|
1895
|
+
id: 0,
|
|
1896
|
+
lang,
|
|
1897
|
+
kind,
|
|
1898
|
+
name,
|
|
1899
|
+
file,
|
|
1900
|
+
line: line2 + 1,
|
|
1901
|
+
col: character,
|
|
1902
|
+
signature,
|
|
1903
|
+
docComment,
|
|
1904
|
+
scope,
|
|
1905
|
+
text
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
const pos = node.getStart(sourceFile);
|
|
1910
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
|
|
1911
|
+
const lineNum = line + 1;
|
|
1912
|
+
if (ts.isCallExpression(node)) {
|
|
1913
|
+
const expr = node.expression;
|
|
1914
|
+
if (ts.isIdentifier(expr)) {
|
|
1915
|
+
refs.push({ fromId: 0, toName: expr.text, callType: "call", line: lineNum });
|
|
1916
|
+
}
|
|
1917
|
+
} else if (ts.isPropertyAccessExpression(node)) {
|
|
1918
|
+
if (ts.isIdentifier(node.expression)) {
|
|
1919
|
+
refs.push({ fromId: 0, toName: node.expression.text, callType: "call", line: lineNum });
|
|
1920
|
+
}
|
|
1921
|
+
} else if (ts.isTypeReferenceNode(node)) {
|
|
1922
|
+
const name = getTypeName(node.typeName);
|
|
1923
|
+
if (name) refs.push({ fromId: 0, toName: name, callType: "type_ref", line: lineNum });
|
|
1924
|
+
} else if (ts.isHeritageClause(node)) {
|
|
1925
|
+
for (const t of node.types) {
|
|
1926
|
+
const name = getTypeName(t.expression);
|
|
1927
|
+
if (name)
|
|
1928
|
+
refs.push({
|
|
1929
|
+
fromId: 0,
|
|
1930
|
+
toName: name,
|
|
1931
|
+
callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement",
|
|
1932
|
+
line: lineNum
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
} else if (ts.isImportDeclaration(node)) {
|
|
1936
|
+
emitImportSpecifierRefs(node, refs, lineNum);
|
|
1937
|
+
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
|
|
1938
|
+
emitExportSpecifierRefs(node, refs, lineNum);
|
|
1939
|
+
}
|
|
1940
|
+
const scopeIdx = scopeParts.length;
|
|
1941
|
+
pushScopeName(node, scopeParts);
|
|
1942
|
+
const childFuncDepth = ts.isFunctionLike(node) ? funcDepth + 1 : funcDepth;
|
|
1943
|
+
ts.forEachChild(node, (child) => visit(child, childFuncDepth, scopeParts));
|
|
1944
|
+
scopeParts.length = scopeIdx;
|
|
1945
|
+
}
|
|
1946
|
+
visit(sourceFile, 0, []);
|
|
1947
|
+
return { file, lang, symbols, refs: deduplicateRefs(refs), mtimeMs: Date.now() };
|
|
1948
|
+
}
|
|
1949
|
+
function getTypeName(name) {
|
|
1950
|
+
if (ts.isIdentifier(name)) return name.text;
|
|
1951
|
+
if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
|
|
1952
|
+
return "";
|
|
1953
|
+
}
|
|
1954
|
+
function deduplicateRefs(refs) {
|
|
1955
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1956
|
+
return refs.filter((r) => {
|
|
1957
|
+
const key = `${r.toName}:${r.callType}:${r.line}:${r.module ?? ""}`;
|
|
1958
|
+
if (seen.has(key)) return false;
|
|
1959
|
+
seen.add(key);
|
|
1960
|
+
return true;
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
function getImportSpecifierName(spec) {
|
|
1964
|
+
return spec.propertyName?.text ?? spec.name.text;
|
|
1965
|
+
}
|
|
1966
|
+
function emitImportSpecifierRefs(node, refs, lineNum) {
|
|
1967
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
1968
|
+
const clause = node.importClause;
|
|
1969
|
+
if (!clause) {
|
|
1970
|
+
if (module) {
|
|
1971
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
1972
|
+
}
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
if (clause.name) {
|
|
1976
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
1977
|
+
}
|
|
1978
|
+
const bindings = clause.namedBindings;
|
|
1979
|
+
if (!bindings) return;
|
|
1980
|
+
if (ts.isNamedImports(bindings)) {
|
|
1981
|
+
for (const element of bindings.elements) {
|
|
1982
|
+
refs.push({
|
|
1983
|
+
fromId: 0,
|
|
1984
|
+
toName: getImportSpecifierName(element),
|
|
1985
|
+
callType: "import",
|
|
1986
|
+
line: lineNum,
|
|
1987
|
+
module
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
} else if (ts.isNamespaceImport(bindings)) {
|
|
1991
|
+
refs.push({
|
|
1992
|
+
fromId: 0,
|
|
1993
|
+
toName: bindings.name.text,
|
|
1994
|
+
callType: "import",
|
|
1995
|
+
line: lineNum,
|
|
1996
|
+
module
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
function moduleSpecifierOf(node) {
|
|
2001
|
+
return node && ts.isStringLiteral(node) ? node.text : void 0;
|
|
2002
|
+
}
|
|
2003
|
+
function emitExportSpecifierRefs(node, refs, lineNum) {
|
|
2004
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
2005
|
+
const clause = node.exportClause;
|
|
2006
|
+
if (clause && ts.isNamespaceExport(clause)) {
|
|
2007
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
if (clause && ts.isNamedExports(clause)) {
|
|
2011
|
+
for (const element of clause.elements) {
|
|
2012
|
+
const originalName = element.propertyName?.text ?? element.name.text;
|
|
2013
|
+
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum, module });
|
|
2014
|
+
}
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
if (module) {
|
|
2018
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
var ts, tsLoad2, kindMapCache;
|
|
2022
|
+
var init_ts_parser = __esm({
|
|
2023
|
+
"src/codebase-index/ts-parser.ts"() {
|
|
2024
|
+
"use strict";
|
|
2025
|
+
init_languages();
|
|
2026
|
+
tsLoad2 = null;
|
|
2027
|
+
kindMapCache = null;
|
|
2028
|
+
}
|
|
2029
|
+
});
|
|
2030
|
+
|
|
2031
|
+
// src/codebase-index/go-parser.ts
|
|
2032
|
+
var go_parser_exports = {};
|
|
2033
|
+
__export(go_parser_exports, {
|
|
2034
|
+
detectLang: () => detectLang,
|
|
2035
|
+
parseSymbols: () => parseSymbols4
|
|
2036
|
+
});
|
|
2037
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
2038
|
+
import * as os3 from "node:os";
|
|
2039
|
+
import * as path10 from "node:path";
|
|
2040
|
+
import * as fs6 from "node:fs/promises";
|
|
2041
|
+
async function parseSymbols4(opts) {
|
|
2042
|
+
const { file, content, lang } = opts;
|
|
2043
|
+
try {
|
|
2044
|
+
const parsed = await withSpawnGate(() => syncGoParse(file, content, lang));
|
|
2045
|
+
if (parsed.symbols.length > 0) {
|
|
2046
|
+
return parsed;
|
|
2047
|
+
}
|
|
2048
|
+
const fallback = fallbackParse(file, content, lang);
|
|
2049
|
+
return parsed.refs?.length ? { ...fallback, refs: parsed.refs } : fallback;
|
|
2050
|
+
} catch {
|
|
2051
|
+
return fallbackParse(file, content, lang);
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
function fallbackParse(filePath, content, lang) {
|
|
2055
|
+
if (!/^\s*package\s+[A-Za-z_]\w*/m.test(content) || hasUnbalancedDelimiters(content)) {
|
|
2056
|
+
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
2057
|
+
}
|
|
2058
|
+
const symbols = [];
|
|
2059
|
+
const packageName = content.match(/^\s*package\s+([A-Za-z_]\w*)/m)?.[1] ?? "";
|
|
2060
|
+
const lines = content.split(/\r?\n/);
|
|
2061
|
+
for (const [idx, line] of lines.entries()) {
|
|
2062
|
+
const trimmed = line.trimStart();
|
|
2063
|
+
const col = line.length - trimmed.length + 1;
|
|
2064
|
+
const fn = /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(/.exec(trimmed);
|
|
2065
|
+
if (fn?.[1]) {
|
|
2066
|
+
addFallbackSymbol(symbols, {
|
|
2067
|
+
filePath,
|
|
2068
|
+
lang,
|
|
2069
|
+
kind: trimmed.startsWith("func (") ? "method" : "function",
|
|
2070
|
+
name: fn[1],
|
|
2071
|
+
line: idx + 1,
|
|
2072
|
+
col,
|
|
2073
|
+
signature: trimmed,
|
|
2074
|
+
scope: packageName ? `${packageName}.${fn[1]}` : fn[1]
|
|
2075
|
+
});
|
|
2076
|
+
continue;
|
|
2077
|
+
}
|
|
2078
|
+
const typeDecl = /^type\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
2079
|
+
if (typeDecl?.[1]) {
|
|
2080
|
+
addFallbackSymbol(symbols, {
|
|
2081
|
+
filePath,
|
|
2082
|
+
lang,
|
|
2083
|
+
kind: "type",
|
|
2084
|
+
name: typeDecl[1],
|
|
2085
|
+
line: idx + 1,
|
|
2086
|
+
col,
|
|
2087
|
+
signature: trimmed,
|
|
2088
|
+
scope: packageName
|
|
2089
|
+
});
|
|
2090
|
+
continue;
|
|
2091
|
+
}
|
|
2092
|
+
const valueDecl = /^(const|var)\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
2093
|
+
if (valueDecl?.[1] && valueDecl[2]) {
|
|
2094
|
+
addFallbackSymbol(symbols, {
|
|
2095
|
+
filePath,
|
|
2096
|
+
lang,
|
|
2097
|
+
kind: valueDecl[1],
|
|
2098
|
+
name: valueDecl[2],
|
|
2099
|
+
line: idx + 1,
|
|
2100
|
+
col,
|
|
2101
|
+
signature: trimmed,
|
|
2102
|
+
scope: packageName
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
2107
|
+
}
|
|
2108
|
+
function addFallbackSymbol(symbols, opts) {
|
|
2109
|
+
symbols.push({
|
|
2110
|
+
id: 0,
|
|
2111
|
+
lang: opts.lang,
|
|
2112
|
+
kind: opts.kind,
|
|
2113
|
+
name: opts.name,
|
|
2114
|
+
file: opts.filePath,
|
|
2115
|
+
line: opts.line,
|
|
2116
|
+
col: opts.col,
|
|
2117
|
+
signature: opts.signature,
|
|
2118
|
+
docComment: "",
|
|
2119
|
+
scope: opts.scope,
|
|
2120
|
+
text: `${opts.name} ${opts.signature}`.trim()
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
function hasUnbalancedDelimiters(content) {
|
|
2124
|
+
const pairs = { "(": ")", "[": "]", "{": "}" };
|
|
2125
|
+
const closers = new Set(Object.values(pairs));
|
|
2126
|
+
const stack = [];
|
|
2127
|
+
for (const ch of content) {
|
|
2128
|
+
if (pairs[ch]) {
|
|
2129
|
+
stack.push(pairs[ch]);
|
|
2130
|
+
} else if (closers.has(ch) && stack.pop() !== ch) {
|
|
2131
|
+
return true;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
return stack.length > 0;
|
|
2135
|
+
}
|
|
2136
|
+
async function syncGoParse(filePath, content, lang) {
|
|
2137
|
+
try {
|
|
2138
|
+
let scriptPath = _cachedGoScriptPath;
|
|
2139
|
+
if (!scriptPath) {
|
|
2140
|
+
const tmpDir = await fs6.mkdtemp(path10.join(os3.tmpdir(), "ws-go-parse-"));
|
|
2141
|
+
scriptPath = path10.join(tmpDir, "parse.go");
|
|
2142
|
+
await fs6.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
2143
|
+
_cachedGoScriptPath = scriptPath;
|
|
2144
|
+
}
|
|
2145
|
+
const goBinary = resolveWin32Command("go");
|
|
2146
|
+
const goResult = await new Promise(
|
|
2147
|
+
(resolve4, reject) => {
|
|
2148
|
+
let settled = false;
|
|
2149
|
+
const proc = spawn3(goBinary, ["run", scriptPath], {
|
|
2150
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2151
|
+
windowsHide: true
|
|
2152
|
+
});
|
|
2153
|
+
proc.on("error", (err) => {
|
|
2154
|
+
if (settled) return;
|
|
2155
|
+
settled = true;
|
|
2156
|
+
reject(err);
|
|
2157
|
+
});
|
|
2158
|
+
let stdout2 = "";
|
|
2159
|
+
proc.stdout?.on("data", (chunk) => {
|
|
2160
|
+
stdout2 += chunk.toString();
|
|
2161
|
+
});
|
|
2162
|
+
proc.stderr?.resume();
|
|
2163
|
+
proc.stdin?.write(content);
|
|
2164
|
+
proc.stdin?.end();
|
|
2165
|
+
const timer = setTimeout(() => {
|
|
2166
|
+
if (settled) return;
|
|
2167
|
+
settled = true;
|
|
2168
|
+
proc.kill("SIGKILL");
|
|
2169
|
+
reject(new Error("timeout"));
|
|
2170
|
+
}, 15e3);
|
|
2171
|
+
timer.unref?.();
|
|
2172
|
+
proc.on("close", (code2) => {
|
|
2173
|
+
if (settled) return;
|
|
2174
|
+
settled = true;
|
|
2175
|
+
clearTimeout(timer);
|
|
2176
|
+
resolve4({ code: code2, stdout: stdout2 });
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
2179
|
+
);
|
|
2180
|
+
const { code, stdout } = goResult;
|
|
2181
|
+
if (code !== 0 || !stdout.trim()) {
|
|
2182
|
+
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
2183
|
+
}
|
|
2184
|
+
const { symbols: rawSymbols, refs } = parseParserOutput(stdout, lang);
|
|
2185
|
+
const symbols = rawSymbols.map((s) => ({
|
|
2186
|
+
id: 0,
|
|
2187
|
+
lang,
|
|
2188
|
+
kind: s.kind,
|
|
2189
|
+
name: s.name,
|
|
2190
|
+
file: filePath,
|
|
2191
|
+
line: s.line,
|
|
2192
|
+
col: s.col,
|
|
2193
|
+
signature: s.signature ?? "",
|
|
2194
|
+
docComment: "",
|
|
2195
|
+
scope: s.scope ?? "",
|
|
2196
|
+
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
2197
|
+
}));
|
|
2198
|
+
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
2199
|
+
} catch {
|
|
2200
|
+
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
var GO_PARSE_SCRIPT, _cachedGoScriptPath;
|
|
2204
|
+
var init_go_parser = __esm({
|
|
2205
|
+
"src/codebase-index/go-parser.ts"() {
|
|
2206
|
+
"use strict";
|
|
2207
|
+
init_win32_resolve();
|
|
2208
|
+
init_parser_output();
|
|
2209
|
+
init_spawn_gate();
|
|
2210
|
+
init_languages();
|
|
2211
|
+
GO_PARSE_SCRIPT = `
|
|
2212
|
+
package main
|
|
2213
|
+
|
|
2214
|
+
import (
|
|
2215
|
+
"encoding/json"
|
|
2216
|
+
"fmt"
|
|
2217
|
+
"go/ast"
|
|
2218
|
+
"go/parser"
|
|
2219
|
+
"go/token"
|
|
2220
|
+
"io"
|
|
2221
|
+
"os"
|
|
2222
|
+
"strconv"
|
|
2223
|
+
"strings"
|
|
2224
|
+
)
|
|
2225
|
+
|
|
2226
|
+
type Sym struct {
|
|
2227
|
+
Name string \`json:"name"\`
|
|
2228
|
+
Kind string \`json:"kind"\`
|
|
2229
|
+
Line int \`json:"line"\`
|
|
2230
|
+
Col int \`json:"col"\`
|
|
2231
|
+
Signature string \`json:"signature"\`
|
|
2232
|
+
Scope string \`json:"scope"\`
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
// Ref is a cross-reference emitted alongside the symbols, so one \`go run\`
|
|
2236
|
+
// yields both. Module is the import path for CallType "import", else empty.
|
|
2237
|
+
type Ref struct {
|
|
2238
|
+
ToName string \`json:"toName"\`
|
|
2239
|
+
CallType string \`json:"callType"\`
|
|
2240
|
+
Line int \`json:"line"\`
|
|
2241
|
+
Module string \`json:"module"\`
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
type Result struct {
|
|
2245
|
+
Symbols []Sym \`json:"symbols"\`
|
|
2246
|
+
Refs []Ref \`json:"refs"\`
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
func emptyResult() string {
|
|
2250
|
+
return "{\\"symbols\\":[],\\"refs\\":[]}"
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
func main() {
|
|
2254
|
+
src, err := io.ReadAll(os.Stdin)
|
|
2255
|
+
if err != nil {
|
|
2256
|
+
fmt.Print(emptyResult())
|
|
2257
|
+
return
|
|
2258
|
+
}
|
|
2259
|
+
fset := token.NewFileSet()
|
|
2260
|
+
node, err := parser.ParseFile(fset, "src.go", src, 0)
|
|
2261
|
+
if err != nil {
|
|
2262
|
+
fmt.Print(emptyResult())
|
|
2263
|
+
return
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
var syms []Sym
|
|
2267
|
+
|
|
2268
|
+
// Package-level scope
|
|
2269
|
+
pkgScope := node.Name.Name
|
|
2270
|
+
|
|
2271
|
+
// Collect all top-level declarations
|
|
2272
|
+
for _, decl := range node.Decls {
|
|
2273
|
+
switch d := decl.(type) {
|
|
2274
|
+
case *ast.FuncDecl:
|
|
2275
|
+
name := d.Name.Name
|
|
2276
|
+
kind := "function"
|
|
2277
|
+
scope := pkgScope
|
|
2278
|
+
if d.Recv != nil && len(d.Recv.List) > 0 {
|
|
2279
|
+
scope = pkgScope + "." + recvTypeName(d.Recv.List[0].Type) + "." + name
|
|
2280
|
+
kind = "method"
|
|
2281
|
+
} else {
|
|
2282
|
+
scope = pkgScope + "." + name
|
|
2283
|
+
}
|
|
2284
|
+
pos := fset.Position(d.Pos())
|
|
2285
|
+
sig := formatFuncSig(d)
|
|
2286
|
+
syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: scope})
|
|
2287
|
+
|
|
2288
|
+
case *ast.GenDecl:
|
|
2289
|
+
for _, spec := range d.Specs {
|
|
2290
|
+
switch s := spec.(type) {
|
|
2291
|
+
case *ast.TypeSpec:
|
|
2292
|
+
name := s.Name.Name
|
|
2293
|
+
pos := fset.Position(s.Pos())
|
|
2294
|
+
sig := "type " + name
|
|
2295
|
+
if s.TypeParams != nil {
|
|
2296
|
+
sig += formatTypeParams(s.TypeParams)
|
|
2297
|
+
}
|
|
2298
|
+
if st, ok := s.Type.(*ast.StructType); ok {
|
|
2299
|
+
sig += " = struct { " + formatFields(st.Fields.List) + " }"
|
|
2300
|
+
} else if it, ok := s.Type.(*ast.InterfaceType); ok {
|
|
2301
|
+
sig += " = interface { " + formatMethods(it.Methods.List) + " }"
|
|
2302
|
+
} else {
|
|
2303
|
+
sig += " = " + formatType(s.Type)
|
|
2304
|
+
}
|
|
2305
|
+
syms = append(syms, Sym{Name: name, Kind: "type", Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
2306
|
+
|
|
2307
|
+
case *ast.ValueSpec:
|
|
2308
|
+
for _, n := range s.Names {
|
|
2309
|
+
name := n.Name
|
|
2310
|
+
pos := fset.Position(n.Pos())
|
|
2311
|
+
kind := "var"
|
|
2312
|
+
if d.Tok == token.CONST {
|
|
2313
|
+
kind = "const"
|
|
2314
|
+
}
|
|
2315
|
+
sig := kind + " " + name
|
|
2316
|
+
if s.Type != nil {
|
|
2317
|
+
sig += " " + formatType(s.Type)
|
|
2318
|
+
}
|
|
2319
|
+
syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
refs := []Ref{}
|
|
2327
|
+
ast.Inspect(node, func(n ast.Node) bool {
|
|
2328
|
+
switch expr := n.(type) {
|
|
2329
|
+
case *ast.CallExpr:
|
|
2330
|
+
line := fset.Position(expr.Pos()).Line
|
|
2331
|
+
switch fun := expr.Fun.(type) {
|
|
2332
|
+
case *ast.Ident:
|
|
2333
|
+
refs = append(refs, Ref{ToName: fun.Name, CallType: "call", Line: line})
|
|
2334
|
+
case *ast.SelectorExpr:
|
|
2335
|
+
// Record the selected name (\`Join\` of \`filepath.Join\`): it is the
|
|
2336
|
+
// declared symbol name, so it resolves the same way the TypeScript
|
|
2337
|
+
// and Python extractors' call refs do.
|
|
2338
|
+
refs = append(refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
2339
|
+
}
|
|
2340
|
+
case *ast.ImportSpec:
|
|
2341
|
+
if expr.Path != nil {
|
|
2342
|
+
if importPath, uerr := strconv.Unquote(expr.Path.Value); uerr == nil {
|
|
2343
|
+
line := fset.Position(expr.Pos()).Line
|
|
2344
|
+
// A Go import names a package, not a symbol; the package's
|
|
2345
|
+
// last path segment is the name it is referenced by.
|
|
2346
|
+
name := importPath
|
|
2347
|
+
if idx := strings.LastIndex(importPath, "/"); idx >= 0 {
|
|
2348
|
+
name = importPath[idx+1:]
|
|
2349
|
+
}
|
|
2350
|
+
refs = append(refs, Ref{ToName: name, CallType: "import", Line: line, Module: importPath})
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
return true
|
|
2355
|
+
})
|
|
2356
|
+
|
|
2357
|
+
if syms == nil {
|
|
2358
|
+
syms = []Sym{}
|
|
2359
|
+
}
|
|
2360
|
+
data, err := json.Marshal(Result{Symbols: syms, Refs: refs})
|
|
2361
|
+
if err != nil {
|
|
2362
|
+
fmt.Print(emptyResult())
|
|
2363
|
+
return
|
|
2364
|
+
}
|
|
2365
|
+
fmt.Print(string(data))
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
func recvTypeName(t ast.Expr) string {
|
|
2369
|
+
switch v := t.(type) {
|
|
2370
|
+
case *ast.Ident:
|
|
2371
|
+
return v.Name
|
|
2372
|
+
case *ast.StarExpr:
|
|
2373
|
+
return recvTypeName(v.X)
|
|
2374
|
+
default:
|
|
2375
|
+
return "?"
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
func formatFuncSig(d *ast.FuncDecl) string {
|
|
2380
|
+
scope := ""
|
|
2381
|
+
if d.Recv != nil && len(d.Recv.List) > 0 {
|
|
2382
|
+
scope = "(" + formatFieldList(d.Recv.List) + ") "
|
|
2383
|
+
}
|
|
2384
|
+
scope += formatFuncType(d.Type)
|
|
2385
|
+
return "func " + scope
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
func formatFuncType(f *ast.FuncType) string {
|
|
2389
|
+
params := formatFieldList(f.Params.List)
|
|
2390
|
+
results := ""
|
|
2391
|
+
if f.Results != nil {
|
|
2392
|
+
results = " -> " + formatFieldList(f.Results.List)
|
|
2393
|
+
}
|
|
2394
|
+
return params + results
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
func formatFieldList(fields []*ast.Field) string {
|
|
2398
|
+
if len(fields) == 0 {
|
|
2399
|
+
return "()"
|
|
2400
|
+
}
|
|
2401
|
+
names := make([]string, 0, len(fields))
|
|
2402
|
+
for _, f := range fields {
|
|
2403
|
+
name := ""
|
|
2404
|
+
if len(f.Names) > 0 {
|
|
2405
|
+
name = f.Names[0].Name
|
|
2406
|
+
}
|
|
2407
|
+
t := formatType(f.Type)
|
|
2408
|
+
if name != "" {
|
|
2409
|
+
names = append(names, name+" "+t)
|
|
2410
|
+
} else {
|
|
2411
|
+
names = append(names, t)
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
return "(" + strings.Join(names, ", ") + ")"
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
func formatFields(fields []*ast.Field) string {
|
|
2418
|
+
lines := make([]string, 0)
|
|
2419
|
+
for _, f := range fields {
|
|
2420
|
+
name := ""
|
|
2421
|
+
if len(f.Names) > 0 {
|
|
2422
|
+
name = f.Names[0].Name
|
|
2423
|
+
}
|
|
2424
|
+
t := formatType(f.Type)
|
|
2425
|
+
if name != "" {
|
|
2426
|
+
lines = append(lines, name+" "+t)
|
|
2427
|
+
} else {
|
|
2428
|
+
lines = append(lines, t)
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
return strings.Join(lines, "; ")
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
func formatMethods(fields []*ast.Field) string {
|
|
2435
|
+
return formatFields(fields)
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
func formatTypeParams(tp *ast.FieldList) string {
|
|
2439
|
+
if tp == nil || len(tp.List) == 0 {
|
|
2440
|
+
return ""
|
|
2441
|
+
}
|
|
2442
|
+
params := make([]string, len(tp.List))
|
|
2443
|
+
for i, p := range tp.List {
|
|
2444
|
+
if len(p.Names) > 0 {
|
|
2445
|
+
params[i] = p.Names[0].Name
|
|
2446
|
+
} else {
|
|
2447
|
+
params[i] = "T"
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
return "[" + strings.Join(params, ", ") + "]"
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
func formatType(t ast.Expr) string {
|
|
2454
|
+
if t == nil {
|
|
2455
|
+
return "?"
|
|
2456
|
+
}
|
|
2457
|
+
switch v := t.(type) {
|
|
2458
|
+
case *ast.Ident:
|
|
2459
|
+
return v.Name
|
|
2460
|
+
case *ast.SelectorExpr:
|
|
2461
|
+
return formatType(v.X) + "." + v.Sel.Name
|
|
2462
|
+
case *ast.StarExpr:
|
|
2463
|
+
return "*" + formatType(v.X)
|
|
2464
|
+
case *ast.ArrayType:
|
|
2465
|
+
if v.Len == nil {
|
|
2466
|
+
return "[]" + formatType(v.Elt)
|
|
2467
|
+
}
|
|
2468
|
+
return "[...]" + formatType(v.Elt)
|
|
2469
|
+
case *ast.MapType:
|
|
2470
|
+
return "map[" + formatType(v.Key) + "]" + formatType(v.Value)
|
|
2471
|
+
case *ast.InterfaceType:
|
|
2472
|
+
return "interface{}"
|
|
2473
|
+
case *ast.StructType:
|
|
2474
|
+
return "struct{}"
|
|
2475
|
+
case *ast.FuncType:
|
|
2476
|
+
return formatFuncType(v)
|
|
2477
|
+
case *ast.ChanType:
|
|
2478
|
+
return "chan " + formatType(v.Value)
|
|
2479
|
+
case *ast.BasicLit:
|
|
2480
|
+
return v.Value
|
|
2481
|
+
case *ast.IndexExpr:
|
|
2482
|
+
// Generic instantiation with one type arg, e.g. Logger[int].
|
|
2483
|
+
return formatType(v.X) + "[" + formatType(v.Index) + "]"
|
|
2484
|
+
case *ast.IndexListExpr:
|
|
2485
|
+
// Generic instantiation with multiple type args, e.g. Map[K, V].
|
|
2486
|
+
args := make([]string, len(v.Indices))
|
|
2487
|
+
for i, idx := range v.Indices {
|
|
2488
|
+
args[i] = formatType(idx)
|
|
2489
|
+
}
|
|
2490
|
+
return formatType(v.X) + "[" + strings.Join(args, ", ") + "]"
|
|
2491
|
+
default:
|
|
2492
|
+
return "?"
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
1700
2495
|
`;
|
|
1701
|
-
|
|
2496
|
+
_cachedGoScriptPath = null;
|
|
1702
2497
|
}
|
|
1703
2498
|
});
|
|
1704
2499
|
|
|
@@ -1794,7 +2589,7 @@ __export(json_parser_exports, {
|
|
|
1794
2589
|
parseSymbols: () => parseSymbols6
|
|
1795
2590
|
});
|
|
1796
2591
|
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
1797
|
-
import * as
|
|
2592
|
+
import * as path11 from "node:path";
|
|
1798
2593
|
function parseSymbols6(opts) {
|
|
1799
2594
|
const { file, content, lang } = opts;
|
|
1800
2595
|
try {
|
|
@@ -1806,7 +2601,7 @@ function parseSymbols6(opts) {
|
|
|
1806
2601
|
function regexParse2(opts) {
|
|
1807
2602
|
const { file, content, lang } = opts;
|
|
1808
2603
|
const symbols = [];
|
|
1809
|
-
const basename7 =
|
|
2604
|
+
const basename7 = path11.basename(file).toLowerCase();
|
|
1810
2605
|
const isPackageJson = basename7 === "package.json";
|
|
1811
2606
|
const isTsconfig = basename7 === "tsconfig.json" || basename7 === "tsconfig.build.json";
|
|
1812
2607
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
@@ -1832,11 +2627,11 @@ function regexParse2(opts) {
|
|
|
1832
2627
|
const line = lineFromOffset(offset);
|
|
1833
2628
|
symbols.push(
|
|
1834
2629
|
makeSymbol({
|
|
1835
|
-
name:
|
|
2630
|
+
name: path11.basename(file),
|
|
1836
2631
|
kind: "object",
|
|
1837
2632
|
line,
|
|
1838
2633
|
col: 0,
|
|
1839
|
-
signature: `"${
|
|
2634
|
+
signature: `"${path11.basename(file)}" = { ... }`,
|
|
1840
2635
|
file,
|
|
1841
2636
|
lang
|
|
1842
2637
|
})
|
|
@@ -2164,6 +2959,106 @@ var init_yaml_parser = __esm({
|
|
|
2164
2959
|
});
|
|
2165
2960
|
|
|
2166
2961
|
// src/codebase-index/tree-sitter/queries.ts
|
|
2962
|
+
function parseGroupedUse(text) {
|
|
2963
|
+
const open = text.indexOf("{");
|
|
2964
|
+
const close = text.lastIndexOf("}");
|
|
2965
|
+
if (open < 0 || close <= open) return null;
|
|
2966
|
+
const prefix = text.slice(0, open).replace(/[\\/]+$/, "");
|
|
2967
|
+
const out = [];
|
|
2968
|
+
for (const rawMember of text.slice(open + 1, close).split(",")) {
|
|
2969
|
+
let member = rawMember.trim();
|
|
2970
|
+
if (!member) continue;
|
|
2971
|
+
member = member.replace(
|
|
2972
|
+
/^(static|final|type|class|struct|enum|protocol|var|func|let|typealias|function|const)\s+/i,
|
|
2973
|
+
""
|
|
2974
|
+
).trim();
|
|
2975
|
+
if (!member) continue;
|
|
2976
|
+
const aliasMatch = /\s+as\s+([A-Za-z_]\w*)\s*$/i.exec(member);
|
|
2977
|
+
if (aliasMatch) member = member.slice(0, aliasMatch.index).trim();
|
|
2978
|
+
if (!member) continue;
|
|
2979
|
+
const module = prefix ? `${prefix}\\${member}` : member;
|
|
2980
|
+
const toName = member.split(/[\\/]/).filter(Boolean).pop();
|
|
2981
|
+
if (toName) out.push({ toName, callType: "import", module });
|
|
2982
|
+
}
|
|
2983
|
+
return out.length ? out : null;
|
|
2984
|
+
}
|
|
2985
|
+
function importFromText(prefixes) {
|
|
2986
|
+
return (node) => {
|
|
2987
|
+
let text = node.text.replace(/\s+/g, " ").trim();
|
|
2988
|
+
for (const prefix of prefixes) {
|
|
2989
|
+
if (text.startsWith(prefix)) text = text.slice(prefix.length).trim();
|
|
2990
|
+
}
|
|
2991
|
+
text = text.replace(
|
|
2992
|
+
/^(static|final|type|class|struct|enum|protocol|var|func|let|typealias|function|const)\s+/i,
|
|
2993
|
+
""
|
|
2994
|
+
).trim();
|
|
2995
|
+
if (text.includes("{")) return parseGroupedUse(text);
|
|
2996
|
+
if (text.includes(",") && !text.includes("<") && !text.includes("=")) {
|
|
2997
|
+
const out = [];
|
|
2998
|
+
for (const clause of text.split(",")) {
|
|
2999
|
+
const one = oneImportClause(clause.trim());
|
|
3000
|
+
if (one) out.push(one);
|
|
3001
|
+
}
|
|
3002
|
+
return out.length ? out : null;
|
|
3003
|
+
}
|
|
3004
|
+
const single = oneImportClause(text);
|
|
3005
|
+
return single ? [single] : null;
|
|
3006
|
+
};
|
|
3007
|
+
}
|
|
3008
|
+
function oneImportClause(rawClause) {
|
|
3009
|
+
let text = rawClause;
|
|
3010
|
+
text = text.replace(
|
|
3011
|
+
/^(static|final|type|class|struct|enum|protocol|var|func|let|typealias|function|const)\s+/i,
|
|
3012
|
+
""
|
|
3013
|
+
).trim();
|
|
3014
|
+
text = text.replace(/[;}]+$/g, "").trim();
|
|
3015
|
+
const aliasMatch = /\s+as\s+([A-Za-z_]\w*)\s*$/i.exec(text);
|
|
3016
|
+
if (aliasMatch) text = text.slice(0, aliasMatch.index).trim();
|
|
3017
|
+
const eqMatch = /^([A-Za-z_]\w*)\s*=\s*(.+)$/.exec(text);
|
|
3018
|
+
if (eqMatch) text = eqMatch[2].trim();
|
|
3019
|
+
if (!text) return null;
|
|
3020
|
+
if (text.endsWith("*")) text = text.slice(0, -1).replace(/[.]$/, "");
|
|
3021
|
+
if (!text) return null;
|
|
3022
|
+
const module = text;
|
|
3023
|
+
const toName = module.split(/[.\\/]/).filter(Boolean).pop()?.replace(/<.*>$/s, "");
|
|
3024
|
+
if (!toName) return null;
|
|
3025
|
+
return { toName, callType: "import", module };
|
|
3026
|
+
}
|
|
3027
|
+
function heritageLeaf(node, depth) {
|
|
3028
|
+
if (depth > 6) return null;
|
|
3029
|
+
const named = node.childForFieldName("name");
|
|
3030
|
+
if (named) {
|
|
3031
|
+
if (named.type === "scoped_type_identifier" || named.type === "qualified_name" || named.type === "scope_resolution" || named.type === "user_type") {
|
|
3032
|
+
return heritageLeaf(named, depth + 1);
|
|
3033
|
+
}
|
|
3034
|
+
return named.text;
|
|
3035
|
+
}
|
|
3036
|
+
const children = [];
|
|
3037
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3038
|
+
const c = node.namedChild(i);
|
|
3039
|
+
if (c) children.push(c);
|
|
3040
|
+
}
|
|
3041
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
3042
|
+
const c = children[i];
|
|
3043
|
+
if (c.type === "type_arguments" || c.type === "type_argument_list" || // cpp: (template_type arguments: (template_argument_list …)) — the
|
|
3044
|
+
// descriptor's type_identifier inside it is never the declared base.
|
|
3045
|
+
c.type === "template_argument_list" || c.type === "type_parameter_list" || c.type === "type_projection" || c.type === "value_arguments") {
|
|
3046
|
+
continue;
|
|
3047
|
+
}
|
|
3048
|
+
if (c.type === "type_identifier" || c.type === "identifier" || c.type === "constant" || c.type === "name") {
|
|
3049
|
+
return c.text;
|
|
3050
|
+
}
|
|
3051
|
+
if (c.type === "scoped_type_identifier" || c.type === "qualified_name" || c.type === "scope_resolution" || c.type === "user_type") {
|
|
3052
|
+
return heritageLeaf(c, depth + 1);
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
return leafSegment(
|
|
3056
|
+
node.text.replace(/\\/g, ".").replace(/::/g, ".").replace(/<[^<>]*>$/, "")
|
|
3057
|
+
);
|
|
3058
|
+
}
|
|
3059
|
+
function leafSegment(text) {
|
|
3060
|
+
return text.split(".").filter(Boolean).pop() ?? text;
|
|
3061
|
+
}
|
|
2167
3062
|
function getQueries(lang) {
|
|
2168
3063
|
return LANG_QUERIES[lang] ?? DEFAULT_QUERIES;
|
|
2169
3064
|
}
|
|
@@ -2175,10 +3070,127 @@ function readFirstString(node) {
|
|
|
2175
3070
|
const child = node.namedChild(0);
|
|
2176
3071
|
return child ? readFirstString(child) : null;
|
|
2177
3072
|
}
|
|
2178
|
-
var DEFAULT_QUERIES, LANG_QUERIES;
|
|
3073
|
+
var heritageExtractor, cCallExtractor, rubyCallExtractor, firstIdentifierCallExtractor, cIncludeExtractor, phpConstructorExtractor, DEFAULT_QUERIES, LANG_QUERIES;
|
|
2179
3074
|
var init_queries = __esm({
|
|
2180
3075
|
"src/codebase-index/tree-sitter/queries.ts"() {
|
|
2181
3076
|
"use strict";
|
|
3077
|
+
heritageExtractor = (node) => {
|
|
3078
|
+
const out = [];
|
|
3079
|
+
const SKIP_SUBTREES = /* @__PURE__ */ new Set([
|
|
3080
|
+
"type_arguments",
|
|
3081
|
+
"type_argument_list",
|
|
3082
|
+
// tree-sitter-cpp names its argument subtree template_argument_list —
|
|
3083
|
+
// verified AST: (base_class_clause (template_type name:
|
|
3084
|
+
// (type_identifier) arguments: (template_argument_list
|
|
3085
|
+
// (type_descriptor type: (type_identifier))))). Without this entry
|
|
3086
|
+
// `class D : Base<Foo>` recurses into the descriptor and emits Foo as a
|
|
3087
|
+
// phantom inherit ref.
|
|
3088
|
+
"template_argument_list",
|
|
3089
|
+
"type_parameter_list",
|
|
3090
|
+
"type_projection",
|
|
3091
|
+
"value_arguments"
|
|
3092
|
+
]);
|
|
3093
|
+
const collect = (current, depth) => {
|
|
3094
|
+
if (depth > 4) return;
|
|
3095
|
+
for (let i = 0; i < current.namedChildCount; i++) {
|
|
3096
|
+
const child = current.namedChild(i);
|
|
3097
|
+
if (!child) continue;
|
|
3098
|
+
if (SKIP_SUBTREES.has(child.type)) continue;
|
|
3099
|
+
if (child.type === "type_identifier" || child.type === "identifier" || child.type === "named_type" || child.type === "type" || // PHP heritage carries `name`; Ruby a `constant`.
|
|
3100
|
+
child.type === "constant" || child.type === "name") {
|
|
3101
|
+
const name = child.type === "named_type" ? leafSegment(child.text) : child.text;
|
|
3102
|
+
if (name) out.push({ toName: name });
|
|
3103
|
+
continue;
|
|
3104
|
+
}
|
|
3105
|
+
if (child.type === "generic_type" || child.type === "generic_name") {
|
|
3106
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
3107
|
+
const inner = child.namedChild(j);
|
|
3108
|
+
if (inner && !SKIP_SUBTREES.has(inner.type) && (inner.type === "type_identifier" || inner.type === "identifier" || inner.type === "name")) {
|
|
3109
|
+
out.push({ toName: inner.text });
|
|
3110
|
+
break;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
continue;
|
|
3114
|
+
}
|
|
3115
|
+
if (child.type === "qualified_name" || child.type === "scoped_type_identifier" || child.type === "user_type" || child.type === "scope_resolution") {
|
|
3116
|
+
const leaf = heritageLeaf(child, 0);
|
|
3117
|
+
if (leaf) out.push({ toName: leaf });
|
|
3118
|
+
continue;
|
|
3119
|
+
}
|
|
3120
|
+
collect(child, depth + 1);
|
|
3121
|
+
}
|
|
3122
|
+
};
|
|
3123
|
+
collect(node, 0);
|
|
3124
|
+
return out;
|
|
3125
|
+
};
|
|
3126
|
+
cCallExtractor = (node) => {
|
|
3127
|
+
const fn = node.childForFieldName("function");
|
|
3128
|
+
if (!fn) return null;
|
|
3129
|
+
if (fn.type === "field_expression") {
|
|
3130
|
+
const field = fn.childForFieldName("field");
|
|
3131
|
+
if (field) return [{ toName: field.text, callType: "call" }];
|
|
3132
|
+
const seg = fn.text.split("->").filter(Boolean).pop();
|
|
3133
|
+
if (seg) return [{ toName: leafSegment(seg.split(".")[0] ?? seg), callType: "call" }];
|
|
3134
|
+
return null;
|
|
3135
|
+
}
|
|
3136
|
+
if (fn.type === "qualified_identifier") {
|
|
3137
|
+
const name = fn.childForFieldName("name");
|
|
3138
|
+
if (name) return [{ toName: name.text, callType: "call" }];
|
|
3139
|
+
const seg = fn.text.split("::").filter(Boolean).pop();
|
|
3140
|
+
if (seg) return [{ toName: seg.split(/[<(]/)[0].trim(), callType: "call" }];
|
|
3141
|
+
return null;
|
|
3142
|
+
}
|
|
3143
|
+
return [{ toName: fn.text.split(/[<(]/)[0].trim(), callType: "call" }];
|
|
3144
|
+
};
|
|
3145
|
+
rubyCallExtractor = (node) => {
|
|
3146
|
+
const emissions = [];
|
|
3147
|
+
const method = node.childForFieldName("method");
|
|
3148
|
+
if (method) {
|
|
3149
|
+
const name = method.text;
|
|
3150
|
+
if (name && !name.includes(" ")) emissions.push({ toName: name, callType: "call" });
|
|
3151
|
+
if (name === "require" || name === "require_relative") {
|
|
3152
|
+
const args = node.childForFieldName("arguments");
|
|
3153
|
+
const first = args?.namedChild(0);
|
|
3154
|
+
if (first) {
|
|
3155
|
+
const raw = first.text.replace(/^['"]|['"]$/g, "");
|
|
3156
|
+
const toName = raw.split("/").filter(Boolean).pop();
|
|
3157
|
+
if (toName) emissions.push({ toName, callType: "import", module: raw });
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
return emissions;
|
|
3162
|
+
};
|
|
3163
|
+
firstIdentifierCallExtractor = (node) => {
|
|
3164
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3165
|
+
const child = node.namedChild(i);
|
|
3166
|
+
if (child && (child.type === "simple_identifier" || child.type === "identifier")) {
|
|
3167
|
+
return [{ toName: child.text, callType: "call" }];
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
const first = node.namedChild(0);
|
|
3171
|
+
if (!first) return null;
|
|
3172
|
+
const leaf = leafSegment(first.text.split(/[<(]/)[0] ?? first.text);
|
|
3173
|
+
if (!leaf) return null;
|
|
3174
|
+
return [{ toName: leaf, callType: "call" }];
|
|
3175
|
+
};
|
|
3176
|
+
cIncludeExtractor = (node) => {
|
|
3177
|
+
const raw = node.text.replace(/^#\s*include\s*/i, "").trim();
|
|
3178
|
+
const module = raw.replace(/^["'<]|["'>]$/g, "");
|
|
3179
|
+
if (!module) return null;
|
|
3180
|
+
const toName = module.split("/").pop()?.replace(/\.h$/, "");
|
|
3181
|
+
if (!toName) return null;
|
|
3182
|
+
return [{ toName, callType: "import", module }];
|
|
3183
|
+
};
|
|
3184
|
+
phpConstructorExtractor = (node) => {
|
|
3185
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3186
|
+
const child = node.namedChild(i);
|
|
3187
|
+
if (child && (child.type === "qualified_name" || child.type === "name")) {
|
|
3188
|
+
const leaf = child.text.split(/[\\]/).filter(Boolean).pop();
|
|
3189
|
+
if (leaf) return [{ toName: leaf, callType: "call" }];
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
return null;
|
|
3193
|
+
};
|
|
2182
3194
|
DEFAULT_QUERIES = {
|
|
2183
3195
|
declKinds: {}
|
|
2184
3196
|
};
|
|
@@ -2211,7 +3223,13 @@ var init_queries = __esm({
|
|
|
2211
3223
|
"struct_specifier",
|
|
2212
3224
|
"union_specifier",
|
|
2213
3225
|
"enum_specifier"
|
|
2214
|
-
])
|
|
3226
|
+
]),
|
|
3227
|
+
refRules: {
|
|
3228
|
+
// `obj->run()` and `Cls::stat()` carry structured function fields —
|
|
3229
|
+
// cCallExtractor handles all three AST shapes.
|
|
3230
|
+
call_expression: { callType: "call", nameExtractor: cCallExtractor },
|
|
3231
|
+
preproc_include: { callType: "import", nameExtractor: cIncludeExtractor }
|
|
3232
|
+
}
|
|
2215
3233
|
},
|
|
2216
3234
|
cpp: {
|
|
2217
3235
|
declKinds: {
|
|
@@ -2242,7 +3260,13 @@ var init_queries = __esm({
|
|
|
2242
3260
|
"union_specifier",
|
|
2243
3261
|
"enum_specifier",
|
|
2244
3262
|
"namespace_definition"
|
|
2245
|
-
])
|
|
3263
|
+
]),
|
|
3264
|
+
refRules: {
|
|
3265
|
+
call_expression: { callType: "call", nameExtractor: cCallExtractor },
|
|
3266
|
+
preproc_include: { callType: "import", nameExtractor: cIncludeExtractor },
|
|
3267
|
+
// `class Foo : public Bar, private Baz` — the base-class clause.
|
|
3268
|
+
base_class_clause: { callType: "inherit", nameExtractor: heritageExtractor }
|
|
3269
|
+
}
|
|
2246
3270
|
},
|
|
2247
3271
|
java: {
|
|
2248
3272
|
declKinds: {
|
|
@@ -2277,7 +3301,22 @@ var init_queries = __esm({
|
|
|
2277
3301
|
"interface_declaration",
|
|
2278
3302
|
"enum_declaration",
|
|
2279
3303
|
"record_declaration"
|
|
2280
|
-
])
|
|
3304
|
+
]),
|
|
3305
|
+
refRules: {
|
|
3306
|
+
method_invocation: { callType: "call", field: "name" },
|
|
3307
|
+
object_creation_expression: { callType: "call", field: "type" },
|
|
3308
|
+
// Verified AST: `superclass: (superclass (type_identifier))` and
|
|
3309
|
+
// `interfaces: (super_interfaces (type_list ...))` — no underscores.
|
|
3310
|
+
superclass: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3311
|
+
super_interfaces: {
|
|
3312
|
+
callType: "implement",
|
|
3313
|
+
nameExtractor: heritageExtractor
|
|
3314
|
+
},
|
|
3315
|
+
import_declaration: {
|
|
3316
|
+
callType: "import",
|
|
3317
|
+
nameExtractor: importFromText(["import "])
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
2281
3320
|
},
|
|
2282
3321
|
csharp: {
|
|
2283
3322
|
// C# 10+ `namespace Foo.Bar;` produces this node type. The legacy block
|
|
@@ -2315,7 +3354,18 @@ var init_queries = __esm({
|
|
|
2315
3354
|
"struct_declaration",
|
|
2316
3355
|
"enum_declaration",
|
|
2317
3356
|
"record_declaration"
|
|
2318
|
-
])
|
|
3357
|
+
]),
|
|
3358
|
+
refRules: {
|
|
3359
|
+
// Verified AST: `invocation_expression function: (identifier)` — the
|
|
3360
|
+
// callee field is `function` (C-style), not `name`.
|
|
3361
|
+
invocation_expression: { callType: "call", field: "function" },
|
|
3362
|
+
object_creation_expression: { callType: "call", field: "type" },
|
|
3363
|
+
base_list: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3364
|
+
using_directive: {
|
|
3365
|
+
callType: "import",
|
|
3366
|
+
nameExtractor: importFromText(["using "])
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
2319
3369
|
},
|
|
2320
3370
|
php: {
|
|
2321
3371
|
declKinds: {
|
|
@@ -2334,7 +3384,7 @@ var init_queries = __esm({
|
|
|
2334
3384
|
interface_declaration: "name",
|
|
2335
3385
|
trait_declaration: "name",
|
|
2336
3386
|
enum_declaration: "name",
|
|
2337
|
-
|
|
3387
|
+
namespace_definition: "name"
|
|
2338
3388
|
},
|
|
2339
3389
|
scopeNodes: /* @__PURE__ */ new Set([
|
|
2340
3390
|
"program",
|
|
@@ -2343,7 +3393,24 @@ var init_queries = __esm({
|
|
|
2343
3393
|
"interface_declaration",
|
|
2344
3394
|
"trait_declaration",
|
|
2345
3395
|
"enum_declaration"
|
|
2346
|
-
])
|
|
3396
|
+
]),
|
|
3397
|
+
refRules: {
|
|
3398
|
+
function_call_expression: { callType: "call", field: "function" },
|
|
3399
|
+
// Verified AST: `new App\Model\User()` carries a BARE qualified_name
|
|
3400
|
+
// child (no `name:` field), so the field default never fires.
|
|
3401
|
+
object_creation_expression: { callType: "call", nameExtractor: phpConstructorExtractor },
|
|
3402
|
+
base_clause: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3403
|
+
class_interface_clause: {
|
|
3404
|
+
callType: "implement",
|
|
3405
|
+
nameExtractor: heritageExtractor
|
|
3406
|
+
},
|
|
3407
|
+
// Verified AST: `namespace_use_declaration (namespace_use_clause
|
|
3408
|
+
// (qualified_name ...))` — not `use_declaration`.
|
|
3409
|
+
namespace_use_declaration: {
|
|
3410
|
+
callType: "import",
|
|
3411
|
+
nameExtractor: importFromText(["use "])
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
2347
3414
|
},
|
|
2348
3415
|
// ─── Scripting / mobile ────────────────────────────────────────────────────
|
|
2349
3416
|
ruby: {
|
|
@@ -2361,7 +3428,13 @@ var init_queries = __esm({
|
|
|
2361
3428
|
module: "name",
|
|
2362
3429
|
constant: "name"
|
|
2363
3430
|
},
|
|
2364
|
-
scopeNodes: /* @__PURE__ */ new Set(["program", "class", "module", "singleton_method", "method"])
|
|
3431
|
+
scopeNodes: /* @__PURE__ */ new Set(["program", "class", "module", "singleton_method", "method"]),
|
|
3432
|
+
refRules: {
|
|
3433
|
+
// `call` covers both `foo(...)` and `obj.foo(...)` — the extractor
|
|
3434
|
+
// records the method leaf, plus `require`/`require_relative` imports.
|
|
3435
|
+
call: { callType: "call", nameExtractor: rubyCallExtractor },
|
|
3436
|
+
superclass: { callType: "inherit", nameExtractor: heritageExtractor }
|
|
3437
|
+
}
|
|
2365
3438
|
},
|
|
2366
3439
|
swift: {
|
|
2367
3440
|
declKinds: {
|
|
@@ -2394,7 +3467,18 @@ var init_queries = __esm({
|
|
|
2394
3467
|
"protocol_declaration",
|
|
2395
3468
|
"actor_declaration",
|
|
2396
3469
|
"extension_declaration"
|
|
2397
|
-
])
|
|
3470
|
+
]),
|
|
3471
|
+
refRules: {
|
|
3472
|
+
// Verified AST: `call_expression (simple_identifier) (call_suffix …)` —
|
|
3473
|
+
// the callee is a bare first child, no field name.
|
|
3474
|
+
call_expression: { callType: "call", nameExtractor: firstIdentifierCallExtractor },
|
|
3475
|
+
// Verified AST: `inheritance_specifier inherits_from: (user_type …)`.
|
|
3476
|
+
inheritance_specifier: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3477
|
+
import_declaration: {
|
|
3478
|
+
callType: "import",
|
|
3479
|
+
nameExtractor: importFromText(["import ", "import type ", "@testable import "])
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
2398
3482
|
},
|
|
2399
3483
|
kotlin: {
|
|
2400
3484
|
declKinds: {
|
|
@@ -2419,7 +3503,17 @@ var init_queries = __esm({
|
|
|
2419
3503
|
"object_declaration",
|
|
2420
3504
|
"interface_declaration",
|
|
2421
3505
|
"function_declaration"
|
|
2422
|
-
])
|
|
3506
|
+
]),
|
|
3507
|
+
refRules: {
|
|
3508
|
+
call_expression: { callType: "call", nameExtractor: firstIdentifierCallExtractor },
|
|
3509
|
+
// Verified AST: `delegation_specifier (user_type (type_identifier))` —
|
|
3510
|
+
// the `: Handler` / `: Base()` clause.
|
|
3511
|
+
delegation_specifier: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3512
|
+
import_header: {
|
|
3513
|
+
callType: "import",
|
|
3514
|
+
nameExtractor: importFromText(["import "])
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
2423
3517
|
},
|
|
2424
3518
|
elixir: {
|
|
2425
3519
|
declKinds: {
|
|
@@ -2491,7 +3585,38 @@ function visitTree(tree, content, file, lang, queries) {
|
|
|
2491
3585
|
const boundedContent = content.length > TREE_SITTER_MAX_FILE_CHARS ? content.slice(0, TREE_SITTER_MAX_FILE_CHARS) : content;
|
|
2492
3586
|
const nlOffsets = newlineOffsets3(boundedContent);
|
|
2493
3587
|
const symbols = [];
|
|
3588
|
+
const refs = [];
|
|
3589
|
+
const seenRefs = /* @__PURE__ */ new Set();
|
|
2494
3590
|
const scopeStack = [];
|
|
3591
|
+
function emitRefsForNode(node, rule) {
|
|
3592
|
+
const emissions = rule.nameExtractor?.(node) ?? defaultRefTarget(node, rule);
|
|
3593
|
+
if (!emissions) return;
|
|
3594
|
+
const { line } = lineColAt2(nlOffsets, node.startIndex);
|
|
3595
|
+
for (const emission of emissions) {
|
|
3596
|
+
if (!emission.toName) continue;
|
|
3597
|
+
const callType = emission.callType ?? rule.callType;
|
|
3598
|
+
const key = `${emission.toName}:${callType}:${line}:${emission.module ?? ""}:${node.startIndex}`;
|
|
3599
|
+
if (seenRefs.has(key)) continue;
|
|
3600
|
+
seenRefs.add(key);
|
|
3601
|
+
refs.push({
|
|
3602
|
+
fromId: 0,
|
|
3603
|
+
// assignRefsToSymbols attaches owners after insertion
|
|
3604
|
+
toName: emission.toName.slice(0, 200),
|
|
3605
|
+
callType,
|
|
3606
|
+
line,
|
|
3607
|
+
lang,
|
|
3608
|
+
module: emission.module
|
|
3609
|
+
});
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
function defaultRefTarget(node, rule) {
|
|
3613
|
+
if (!rule.field) return null;
|
|
3614
|
+
const field = node.childForFieldName(rule.field);
|
|
3615
|
+
if (!field) return null;
|
|
3616
|
+
const leaf = field.text.split(/[.:\\]/).filter(Boolean).pop()?.split(/[<(]/)[0];
|
|
3617
|
+
if (!leaf) return null;
|
|
3618
|
+
return [{ toName: leaf.trim() }];
|
|
3619
|
+
}
|
|
2495
3620
|
function visit(node, depth) {
|
|
2496
3621
|
if (symbols.length >= TREE_SITTER_MAX_SYMBOLS) return;
|
|
2497
3622
|
if (node.isMissing || node.isError) {
|
|
@@ -2510,6 +3635,8 @@ function visitTree(tree, content, file, lang, queries) {
|
|
|
2510
3635
|
);
|
|
2511
3636
|
if (emitted) symbols.push(emitted);
|
|
2512
3637
|
}
|
|
3638
|
+
const refRule = queries.refRules?.[node.type];
|
|
3639
|
+
if (refRule) emitRefsForNode(node, refRule);
|
|
2513
3640
|
}
|
|
2514
3641
|
const pushesScope = queries.scopeNodes?.has(node.type) ?? false;
|
|
2515
3642
|
const pushIdx = pushesScope ? pushScope(scopeStack, node, queries) : -1;
|
|
@@ -2527,7 +3654,7 @@ function visitTree(tree, content, file, lang, queries) {
|
|
|
2527
3654
|
if (pushIdx !== -1) scopeStack.pop();
|
|
2528
3655
|
}
|
|
2529
3656
|
visit(tree.rootNode, 0);
|
|
2530
|
-
return { symbols };
|
|
3657
|
+
return { symbols, refs };
|
|
2531
3658
|
}
|
|
2532
3659
|
function pushScope(scopeStack, node, queries) {
|
|
2533
3660
|
const name = extractName(node, queries);
|
|
@@ -2614,7 +3741,7 @@ __export(tree_sitter_parser_exports, {
|
|
|
2614
3741
|
parseSymbols: () => parseSymbols8,
|
|
2615
3742
|
parseTreeSitterAst: () => parseTreeSitterAst
|
|
2616
3743
|
});
|
|
2617
|
-
import * as
|
|
3744
|
+
import * as path12 from "node:path";
|
|
2618
3745
|
import { fileURLToPath } from "node:url";
|
|
2619
3746
|
function optInEnabled(env) {
|
|
2620
3747
|
return process.env[env] === "1" || process.env[env] === "true";
|
|
@@ -2637,7 +3764,7 @@ async function loadLanguage(lang) {
|
|
|
2637
3764
|
if (!grammarName) {
|
|
2638
3765
|
throw new Error(`tree-sitter: no grammar registered for lang "${lang}"`);
|
|
2639
3766
|
}
|
|
2640
|
-
const wasmPath =
|
|
3767
|
+
const wasmPath = path12.join(WASM_DIR, grammarName, `tree-sitter-${grammarName}.wasm`);
|
|
2641
3768
|
const { Language, init } = await getRuntime();
|
|
2642
3769
|
await init();
|
|
2643
3770
|
const languageObj = await Language.load(wasmPath);
|
|
@@ -2658,7 +3785,7 @@ function isTreeSitterSupported(lang) {
|
|
|
2658
3785
|
function getGrammarWasmPath(lang) {
|
|
2659
3786
|
const name = resolveGrammarName(lang);
|
|
2660
3787
|
if (!name) return void 0;
|
|
2661
|
-
return
|
|
3788
|
+
return path12.join(WASM_DIR, name, `tree-sitter-${name}.wasm`);
|
|
2662
3789
|
}
|
|
2663
3790
|
async function parseSymbols8(opts) {
|
|
2664
3791
|
const { file, content, lang } = opts;
|
|
@@ -2674,89 +3801,229 @@ async function parseSymbols8(opts) {
|
|
|
2674
3801
|
if (!tree) {
|
|
2675
3802
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
2676
3803
|
}
|
|
2677
|
-
const { symbols } = visitTree(tree, content, file, lang, getQueries(lang));
|
|
2678
|
-
parser.delete();
|
|
2679
|
-
tree.delete();
|
|
2680
|
-
return { file, lang, symbols, refs
|
|
2681
|
-
} catch {
|
|
2682
|
-
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
2683
|
-
}
|
|
2684
|
-
}
|
|
2685
|
-
async function loadTreeSitterLanguage(lang) {
|
|
2686
|
-
const cached = await loadLanguage(lang);
|
|
2687
|
-
return cached.Language;
|
|
2688
|
-
}
|
|
2689
|
-
async function __smokeRootType(opts) {
|
|
2690
|
-
if (!isTreeSitterSupported(opts.lang)) {
|
|
2691
|
-
throw new Error(`tree-sitter: no grammar registered for lang "${opts.lang}"`);
|
|
2692
|
-
}
|
|
2693
|
-
const { Parser } = await getRuntime();
|
|
2694
|
-
const cached = await loadLanguage(opts.lang);
|
|
2695
|
-
const parser = new Parser();
|
|
2696
|
-
parser.setLanguage(cached.Language);
|
|
2697
|
-
let tree = null;
|
|
2698
|
-
try {
|
|
2699
|
-
tree = parser.parse(opts.content);
|
|
2700
|
-
if (!tree) throw new Error("tree-sitter: parser.parse returned null");
|
|
2701
|
-
return tree.rootNode.type;
|
|
2702
|
-
} finally {
|
|
2703
|
-
tree?.delete();
|
|
2704
|
-
parser.delete();
|
|
3804
|
+
const { symbols, refs } = visitTree(tree, content, file, lang, getQueries(lang));
|
|
3805
|
+
parser.delete();
|
|
3806
|
+
tree.delete();
|
|
3807
|
+
return { file, lang, symbols, refs, mtimeMs: Date.now() };
|
|
3808
|
+
} catch {
|
|
3809
|
+
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
async function loadTreeSitterLanguage(lang) {
|
|
3813
|
+
const cached = await loadLanguage(lang);
|
|
3814
|
+
return cached.Language;
|
|
3815
|
+
}
|
|
3816
|
+
async function __smokeRootType(opts) {
|
|
3817
|
+
if (!isTreeSitterSupported(opts.lang)) {
|
|
3818
|
+
throw new Error(`tree-sitter: no grammar registered for lang "${opts.lang}"`);
|
|
3819
|
+
}
|
|
3820
|
+
const { Parser } = await getRuntime();
|
|
3821
|
+
const cached = await loadLanguage(opts.lang);
|
|
3822
|
+
const parser = new Parser();
|
|
3823
|
+
parser.setLanguage(cached.Language);
|
|
3824
|
+
let tree = null;
|
|
3825
|
+
try {
|
|
3826
|
+
tree = parser.parse(opts.content);
|
|
3827
|
+
if (!tree) throw new Error("tree-sitter: parser.parse returned null");
|
|
3828
|
+
return tree.rootNode.type;
|
|
3829
|
+
} finally {
|
|
3830
|
+
tree?.delete();
|
|
3831
|
+
parser.delete();
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
async function parseTreeSitterAst(opts) {
|
|
3835
|
+
const grammar = resolveGrammarName(opts.lang) ?? (opts.lang === "go" ? "go" : opts.lang === "py" ? "python" : opts.lang === "rs" ? "rust" : void 0);
|
|
3836
|
+
if (!grammar) return null;
|
|
3837
|
+
try {
|
|
3838
|
+
const { Parser, Language, init } = await getRuntime();
|
|
3839
|
+
await init();
|
|
3840
|
+
const wasmPath = path12.join(WASM_DIR, grammar, `tree-sitter-${grammar}.wasm`);
|
|
3841
|
+
const languageObj = await Language.load(wasmPath);
|
|
3842
|
+
const parser = new Parser();
|
|
3843
|
+
parser.setLanguage(languageObj);
|
|
3844
|
+
const tree = parser.parse(opts.content);
|
|
3845
|
+
if (!tree) {
|
|
3846
|
+
parser.delete();
|
|
3847
|
+
return null;
|
|
3848
|
+
}
|
|
3849
|
+
return { tree, parser };
|
|
3850
|
+
} catch {
|
|
3851
|
+
return null;
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
var WASM_DIR, RUNTIME_WASM, LANG_TO_GRAMMAR, GO_OPT_IN, PY_OPT_IN, RS_OPT_IN, runtimePromise, languageCache;
|
|
3855
|
+
var init_tree_sitter_parser = __esm({
|
|
3856
|
+
"src/codebase-index/tree-sitter-parser.ts"() {
|
|
3857
|
+
"use strict";
|
|
3858
|
+
init_queries();
|
|
3859
|
+
init_visitor();
|
|
3860
|
+
WASM_DIR = fileURLToPath(new URL("./wasm/", import.meta.url));
|
|
3861
|
+
RUNTIME_WASM = path12.join(WASM_DIR, "tree-sitter-runtime.wasm");
|
|
3862
|
+
LANG_TO_GRAMMAR = {
|
|
3863
|
+
c: "c",
|
|
3864
|
+
cpp: "cpp",
|
|
3865
|
+
java: "java",
|
|
3866
|
+
csharp: "c_sharp",
|
|
3867
|
+
// tree-sitter directory uses underscore
|
|
3868
|
+
php: "php",
|
|
3869
|
+
ruby: "ruby",
|
|
3870
|
+
swift: "swift",
|
|
3871
|
+
kotlin: "kotlin",
|
|
3872
|
+
shell: "bash",
|
|
3873
|
+
// we treat `.sh` / `.bash` / `.zsh` via the bash grammar
|
|
3874
|
+
// Go/Python/Rust are opt-in only (see goOptIn / pyOptIn / rsOptIn below)
|
|
3875
|
+
elixir: "elixir"
|
|
3876
|
+
};
|
|
3877
|
+
GO_OPT_IN = "WRONGSTACK_USE_TS_GO";
|
|
3878
|
+
PY_OPT_IN = "WRONGSTACK_USE_TS_PY";
|
|
3879
|
+
RS_OPT_IN = "WRONGSTACK_USE_TS_RS";
|
|
3880
|
+
runtimePromise = null;
|
|
3881
|
+
languageCache = /* @__PURE__ */ new Map();
|
|
3882
|
+
}
|
|
3883
|
+
});
|
|
3884
|
+
|
|
3885
|
+
// src/codebase-index/parser-dispatch.ts
|
|
3886
|
+
var parser_dispatch_exports = {};
|
|
3887
|
+
__export(parser_dispatch_exports, {
|
|
3888
|
+
parseFileContent: () => parseFileContent,
|
|
3889
|
+
parseFilesContent: () => parseFilesContent
|
|
3890
|
+
});
|
|
3891
|
+
async function parseFileContent(file, content, lang) {
|
|
3892
|
+
const parsed = await dispatch(file, content, lang);
|
|
3893
|
+
return withRelations(parsed, content, lang);
|
|
3894
|
+
}
|
|
3895
|
+
async function parseFilesContent(files) {
|
|
3896
|
+
if (files.length === 0) return [];
|
|
3897
|
+
const slots = files.map(() => ({ result: null }));
|
|
3898
|
+
const batchingEnabled = process.env["WRONGSTACK_TOOLCHAIN_BATCH"] !== "0";
|
|
3899
|
+
if (batchingEnabled) {
|
|
3900
|
+
const goFiles = [];
|
|
3901
|
+
const pyFiles = [];
|
|
3902
|
+
files.forEach((f, index) => {
|
|
3903
|
+
if (f.lang === "go") goFiles.push({ ...f, index });
|
|
3904
|
+
else if (f.lang === "py") pyFiles.push({ ...f, index });
|
|
3905
|
+
});
|
|
3906
|
+
if (goFiles.length > 0) {
|
|
3907
|
+
await applyBatchResults(slots, goFiles, (chunks) => runGoBatch(chunks), "go");
|
|
3908
|
+
}
|
|
3909
|
+
if (pyFiles.length > 0) {
|
|
3910
|
+
const pyBinary = await resolvePythonBinary();
|
|
3911
|
+
if (pyBinary) {
|
|
3912
|
+
await applyBatchResults(slots, pyFiles, (chunks) => runPyBatch(chunks, pyBinary), "py");
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
const jobs = [];
|
|
3917
|
+
for (let i = 0; i < files.length; i++) {
|
|
3918
|
+
if (slots[i].result !== null) continue;
|
|
3919
|
+
const { file, content, lang } = files[i];
|
|
3920
|
+
const slot = slots[i];
|
|
3921
|
+
jobs.push(
|
|
3922
|
+
(async () => {
|
|
3923
|
+
try {
|
|
3924
|
+
slot.result = await parseFileContent(file, content, lang);
|
|
3925
|
+
} catch (err) {
|
|
3926
|
+
slot.error = err instanceof Error ? err.message : String(err);
|
|
3927
|
+
}
|
|
3928
|
+
})()
|
|
3929
|
+
);
|
|
3930
|
+
}
|
|
3931
|
+
await Promise.all(jobs);
|
|
3932
|
+
return slots;
|
|
3933
|
+
}
|
|
3934
|
+
async function applyBatchResults(slots, batchFiles, runBatch, lang) {
|
|
3935
|
+
for (const chunk of chunkBatchFiles(batchFiles)) {
|
|
3936
|
+
let byFile = null;
|
|
3937
|
+
try {
|
|
3938
|
+
byFile = await runBatch(chunk);
|
|
3939
|
+
} catch {
|
|
3940
|
+
byFile = null;
|
|
3941
|
+
}
|
|
3942
|
+
if (!byFile) continue;
|
|
3943
|
+
for (const item of chunk) {
|
|
3944
|
+
const parsed = byFile.get(item.file);
|
|
3945
|
+
if (!parsed) continue;
|
|
3946
|
+
slots[item.index] = { result: withRelations(parsed, item.content, lang) };
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
async function dispatch(file, content, lang) {
|
|
3951
|
+
switch (lang) {
|
|
3952
|
+
case "ts":
|
|
3953
|
+
case "tsx":
|
|
3954
|
+
case "js":
|
|
3955
|
+
case "jsx": {
|
|
3956
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
|
|
3957
|
+
return parseSymbols9({ file, content, lang });
|
|
3958
|
+
}
|
|
3959
|
+
case "go": {
|
|
3960
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
|
|
3961
|
+
return parseSymbols9({ file, content, lang: "go" });
|
|
3962
|
+
}
|
|
3963
|
+
case "py": {
|
|
3964
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
|
|
3965
|
+
return parseSymbols9({ file, content, lang: "py" });
|
|
3966
|
+
}
|
|
3967
|
+
case "rs": {
|
|
3968
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
|
|
3969
|
+
return parseSymbols9({ file, content, lang: "rs" });
|
|
3970
|
+
}
|
|
3971
|
+
case "json": {
|
|
3972
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
|
|
3973
|
+
return parseSymbols9({ file, content, lang: "json" });
|
|
3974
|
+
}
|
|
3975
|
+
case "yaml": {
|
|
3976
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_yaml_parser(), yaml_parser_exports));
|
|
3977
|
+
return parseSymbols9({ file, content, lang: "yaml" });
|
|
3978
|
+
}
|
|
3979
|
+
// Phase 1: ten languages now route through the Tree-Sitter WASM
|
|
3980
|
+
// universal parser (`tree-sitter-parser.ts`). The dispatch falls back to
|
|
3981
|
+
// the regex extractor in `generic-parser.ts` whenever WASM loading fails
|
|
3982
|
+
// or the parser returns zero symbols — preserving the indexable-file
|
|
3983
|
+
// contract that "missing a parser must never mean skipping the file".
|
|
3984
|
+
case "c":
|
|
3985
|
+
case "cpp":
|
|
3986
|
+
case "java":
|
|
3987
|
+
case "csharp":
|
|
3988
|
+
case "php":
|
|
3989
|
+
case "ruby":
|
|
3990
|
+
case "swift":
|
|
3991
|
+
case "kotlin":
|
|
3992
|
+
case "shell":
|
|
3993
|
+
case "elixir": {
|
|
3994
|
+
try {
|
|
3995
|
+
const { parseSymbols: parseSymbols10 } = await Promise.resolve().then(() => (init_tree_sitter_parser(), tree_sitter_parser_exports));
|
|
3996
|
+
const parsed = await parseSymbols10({ file, content, lang });
|
|
3997
|
+
if (parsed.symbols.length > 0) return parsed;
|
|
3998
|
+
} catch {
|
|
3999
|
+
}
|
|
4000
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
4001
|
+
return parseSymbols9({ file, content, lang });
|
|
4002
|
+
}
|
|
4003
|
+
default: {
|
|
4004
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
4005
|
+
return parseSymbols9({ file, content, lang });
|
|
4006
|
+
}
|
|
2705
4007
|
}
|
|
2706
4008
|
}
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
if (
|
|
2710
|
-
|
|
2711
|
-
const { Parser, Language, init } = await getRuntime();
|
|
2712
|
-
await init();
|
|
2713
|
-
const wasmPath = path11.join(WASM_DIR, grammar, `tree-sitter-${grammar}.wasm`);
|
|
2714
|
-
const languageObj = await Language.load(wasmPath);
|
|
2715
|
-
const parser = new Parser();
|
|
2716
|
-
parser.setLanguage(languageObj);
|
|
2717
|
-
const tree = parser.parse(opts.content);
|
|
2718
|
-
if (!tree) {
|
|
2719
|
-
parser.delete();
|
|
2720
|
-
return null;
|
|
2721
|
-
}
|
|
2722
|
-
return { tree, parser };
|
|
2723
|
-
} catch {
|
|
2724
|
-
return null;
|
|
4009
|
+
function withRelations(parsed, content, lang) {
|
|
4010
|
+
let refs = parsed.refs ?? [];
|
|
4011
|
+
if (refs.length === 0 && hasImportPatterns(lang)) {
|
|
4012
|
+
refs = extractImports({ content, lang });
|
|
2725
4013
|
}
|
|
4014
|
+
return { ...parsed, refs: refs.map((ref) => ref.lang ? ref : { ...ref, lang }) };
|
|
2726
4015
|
}
|
|
2727
|
-
var
|
|
2728
|
-
|
|
2729
|
-
"src/codebase-index/tree-sitter-parser.ts"() {
|
|
4016
|
+
var init_parser_dispatch = __esm({
|
|
4017
|
+
"src/codebase-index/parser-dispatch.ts"() {
|
|
2730
4018
|
"use strict";
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
RUNTIME_WASM = path11.join(WASM_DIR, "tree-sitter-runtime.wasm");
|
|
2735
|
-
LANG_TO_GRAMMAR = {
|
|
2736
|
-
c: "c",
|
|
2737
|
-
cpp: "cpp",
|
|
2738
|
-
java: "java",
|
|
2739
|
-
csharp: "c_sharp",
|
|
2740
|
-
// tree-sitter directory uses underscore
|
|
2741
|
-
php: "php",
|
|
2742
|
-
ruby: "ruby",
|
|
2743
|
-
swift: "swift",
|
|
2744
|
-
kotlin: "kotlin",
|
|
2745
|
-
shell: "bash",
|
|
2746
|
-
// we treat `.sh` / `.bash` / `.zsh` via the bash grammar
|
|
2747
|
-
// Go/Python/Rust are opt-in only (see goOptIn / pyOptIn / rsOptIn below)
|
|
2748
|
-
elixir: "elixir"
|
|
2749
|
-
};
|
|
2750
|
-
GO_OPT_IN = "WRONGSTACK_USE_TS_GO";
|
|
2751
|
-
PY_OPT_IN = "WRONGSTACK_USE_TS_PY";
|
|
2752
|
-
RS_OPT_IN = "WRONGSTACK_USE_TS_RS";
|
|
2753
|
-
runtimePromise = null;
|
|
2754
|
-
languageCache = /* @__PURE__ */ new Map();
|
|
4019
|
+
init_import_extractor();
|
|
4020
|
+
init_parser_batch();
|
|
4021
|
+
init_py_parser();
|
|
2755
4022
|
}
|
|
2756
4023
|
});
|
|
2757
4024
|
|
|
2758
4025
|
// src/write.ts
|
|
2759
|
-
import * as
|
|
4026
|
+
import * as fs15 from "node:fs/promises";
|
|
2760
4027
|
import { ToolValidationError } from "@wrongstack/core/types";
|
|
2761
4028
|
import {
|
|
2762
4029
|
atomicWrite,
|
|
@@ -2942,7 +4209,7 @@ function takeHeadBytes(s, maxBytes) {
|
|
|
2942
4209
|
}
|
|
2943
4210
|
|
|
2944
4211
|
// src/codebase-index/background-indexer.ts
|
|
2945
|
-
import * as
|
|
4212
|
+
import * as fs14 from "node:fs";
|
|
2946
4213
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
2947
4214
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
2948
4215
|
|
|
@@ -3030,9 +4297,9 @@ var indexCircuitBreaker = new IndexCircuitBreaker();
|
|
|
3030
4297
|
import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
|
|
3031
4298
|
import { execFile } from "node:child_process";
|
|
3032
4299
|
import { createHash as createHash2 } from "node:crypto";
|
|
3033
|
-
import * as
|
|
4300
|
+
import * as fs10 from "node:fs/promises";
|
|
3034
4301
|
import { availableParallelism } from "node:os";
|
|
3035
|
-
import * as
|
|
4302
|
+
import * as path15 from "node:path";
|
|
3036
4303
|
import {
|
|
3037
4304
|
DEFAULT_WALK_IGNORE_DIRS,
|
|
3038
4305
|
indexParallelBatchSize,
|
|
@@ -3700,283 +4967,96 @@ var ModuleResolver = class {
|
|
|
3700
4967
|
const segments = spec.split("::").filter(Boolean);
|
|
3701
4968
|
if (segments.length === 0) return void 0;
|
|
3702
4969
|
const head = segments[0];
|
|
3703
|
-
if (head === "self" || head === "super") {
|
|
3704
|
-
let base = path6.posix.dirname(fromFile);
|
|
3705
|
-
for (const segment of segments) {
|
|
3706
|
-
if (segment === "super") base = path6.posix.dirname(base);
|
|
3707
|
-
else if (segment !== "self") break;
|
|
3708
|
-
}
|
|
3709
|
-
const rest2 = segments.filter((segment) => segment !== "self" && segment !== "super");
|
|
3710
|
-
return this.lookupWithExtensions(path6.posix.join(base, ...rest2), "rs");
|
|
3711
|
-
}
|
|
3712
|
-
const owningCrate = findOwningRoot(this.structure, fromFile, ["cargo"]);
|
|
3713
|
-
const crate = head === "crate" ? owningCrate : this.structure.roots.find(
|
|
3714
|
-
(root) => root.kind === "cargo" && root.importPath === head?.replace(/-/g, "_")
|
|
3715
|
-
);
|
|
3716
|
-
if (!crate) {
|
|
3717
|
-
return this.lookupWithExtensions(
|
|
3718
|
-
path6.posix.join(path6.posix.dirname(fromFile), ...segments),
|
|
3719
|
-
"rs"
|
|
3720
|
-
);
|
|
3721
|
-
}
|
|
3722
|
-
const rest = segments.slice(1);
|
|
3723
|
-
for (const base of crate.sourceRoots) {
|
|
3724
|
-
const parent = rest.length > 1 ? this.lookupWithExtensions(path6.posix.join(base, ...rest.slice(0, -1)), "rs") : void 0;
|
|
3725
|
-
const exact = this.lookupWithExtensions(path6.posix.join(base, ...rest), "rs");
|
|
3726
|
-
const hit = exact ?? parent ?? this.lookupWithExtensions(path6.posix.join(base, "lib"), "rs");
|
|
3727
|
-
if (hit) return hit;
|
|
3728
|
-
}
|
|
3729
|
-
return void 0;
|
|
3730
|
-
}
|
|
3731
|
-
/** `com.example.Thing` and `com.example.*` against JVM source roots. */
|
|
3732
|
-
resolveJvm(spec) {
|
|
3733
|
-
const segments = spec.split(".").filter(Boolean);
|
|
3734
|
-
if (segments.length === 0) return void 0;
|
|
3735
|
-
const sourceRoots = this.structure.roots.filter((root) => root.kind === "maven" || root.kind === "gradle").flatMap((root) => root.sourceRoots);
|
|
3736
|
-
const wildcard = segments[segments.length - 1] === "*";
|
|
3737
|
-
const parts = wildcard ? segments.slice(0, -1) : segments;
|
|
3738
|
-
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
3739
|
-
const target = path6.posix.join(base, ...parts);
|
|
3740
|
-
const hit = wildcard ? this.representativeIn(target, "jvm") : this.lookupWithExtensions(target, "jvm");
|
|
3741
|
-
if (hit) return hit;
|
|
3742
|
-
}
|
|
3743
|
-
return void 0;
|
|
3744
|
-
}
|
|
3745
|
-
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
3746
|
-
resolveInclude(fromFile, spec) {
|
|
3747
|
-
const relative3 = this.lookupWithExtensions(
|
|
3748
|
-
path6.posix.join(path6.posix.dirname(fromFile), spec),
|
|
3749
|
-
"c"
|
|
3750
|
-
);
|
|
3751
|
-
if (relative3) return relative3;
|
|
3752
|
-
for (const base of [
|
|
3753
|
-
path6.posix.join(this.structure.projectRoot, "include"),
|
|
3754
|
-
this.structure.projectRoot
|
|
3755
|
-
]) {
|
|
3756
|
-
const hit = this.lookupWithExtensions(path6.posix.join(base, spec), "c");
|
|
3757
|
-
if (hit) return hit;
|
|
3758
|
-
}
|
|
3759
|
-
return void 0;
|
|
3760
|
-
}
|
|
3761
|
-
/** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
|
|
3762
|
-
resolveRuby(fromFile, spec) {
|
|
3763
|
-
const relative3 = this.lookupWithExtensions(
|
|
3764
|
-
path6.posix.join(path6.posix.dirname(fromFile), spec),
|
|
3765
|
-
"ruby"
|
|
3766
|
-
);
|
|
3767
|
-
if (relative3) return relative3;
|
|
3768
|
-
for (const base of [
|
|
3769
|
-
path6.posix.join(this.structure.projectRoot, "lib"),
|
|
3770
|
-
this.structure.projectRoot
|
|
3771
|
-
]) {
|
|
3772
|
-
const hit = this.lookupWithExtensions(path6.posix.join(base, spec), "ruby");
|
|
3773
|
-
if (hit) return hit;
|
|
3774
|
-
}
|
|
3775
|
-
return void 0;
|
|
3776
|
-
}
|
|
3777
|
-
};
|
|
3778
|
-
|
|
3779
|
-
// src/codebase-index/import-extractor.ts
|
|
3780
|
-
var IMPORT_MAX_FILE_CHARS = 512 * 1024;
|
|
3781
|
-
var IMPORT_MAX_PER_FILE = 400;
|
|
3782
|
-
var DOTTED_IMPORT = [
|
|
3783
|
-
{ re: /^[ \t]*import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;?\s*$/gm }
|
|
3784
|
-
];
|
|
3785
|
-
var LANG_IMPORTS = {
|
|
3786
|
-
// Go and Python have real AST extractors; these patterns are the fallback for
|
|
3787
|
-
// machines with no Go toolchain or Python interpreter installed, where the
|
|
3788
|
-
// parser degrades to regex symbols and would otherwise contribute no edges.
|
|
3789
|
-
go: [
|
|
3790
|
-
{ re: /^[ \t]*import\s+(?:[\w.]+\s+)?"([^"]+)"/gm },
|
|
3791
|
-
// Grouped form: inside `import ( … )` each line is an optional alias plus a
|
|
3792
|
-
// quoted path. A stray match elsewhere resolves to no file and is dropped.
|
|
3793
|
-
{ re: /^[ \t]*(?:[A-Za-z_.]\w*\s+)?"([^"]+)"\s*$/gm }
|
|
3794
|
-
],
|
|
3795
|
-
py: [{ re: /^[ \t]*import\s+([\w.]+)/gm }, { re: /^[ \t]*from\s+([.\w]+)\s+import\b/gm }],
|
|
3796
|
-
rs: [
|
|
3797
|
-
// use a::b::C; | use a::b::{C, D}; → the path before any brace
|
|
3798
|
-
{ re: /^[ \t]*(?:pub\s+)?use\s+([\w:]+?)(?:::\{|\s*;|\s+as\b)/gm },
|
|
3799
|
-
// mod foo; (a declaration *and* a dependency on foo.rs / foo/mod.rs)
|
|
3800
|
-
{ re: /^[ \t]*(?:pub\s+)?mod\s+(\w+)\s*;/gm }
|
|
3801
|
-
],
|
|
3802
|
-
java: DOTTED_IMPORT,
|
|
3803
|
-
kotlin: DOTTED_IMPORT,
|
|
3804
|
-
scala: [{ re: /^[ \t]*import\s+([\w.]+(?:\.[_*])?)\s*$/gm }],
|
|
3805
|
-
csharp: [
|
|
3806
|
-
// using Foo.Bar; | using static Foo.Bar; | using Alias = Foo.Bar;
|
|
3807
|
-
{ re: /^[ \t]*global\s+using\s+(?:static\s+)?([\w.]+)\s*;/gm, name: "full" },
|
|
3808
|
-
{ re: /^[ \t]*using\s+(?:static\s+)?(?:\w+\s*=\s*)?([\w.]+)\s*;/gm, name: "full" }
|
|
3809
|
-
],
|
|
3810
|
-
// Quoted includes only: <stdio.h> is a system header with no indexed file.
|
|
3811
|
-
c: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
3812
|
-
cpp: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
3813
|
-
ruby: [{ re: /^[ \t]*(?:require|require_relative|load)\s*\(?\s*['"]([^'"]+)['"]/gm }],
|
|
3814
|
-
php: [
|
|
3815
|
-
// `use A\B\C` imports the class C, which is what the index has a symbol
|
|
3816
|
-
// for — the namespace symbol only covers the `A\B` prefix.
|
|
3817
|
-
{ re: /^[ \t]*use\s+(?:function\s+|const\s+)?([\w\\]+)/gm },
|
|
3818
|
-
{ re: /^[ \t]*(?:require|require_once|include|include_once)\s*\(?\s*['"]([^'"]+)['"]/gm }
|
|
3819
|
-
],
|
|
3820
|
-
swift: [{ re: /^[ \t]*import\s+(?:struct\s+|class\s+|func\s+)?([\w.]+)\s*$/gm }],
|
|
3821
|
-
dart: [{ re: /^[ \t]*(?:import|export|part)\s+['"]([^'"]+)['"]/gm }],
|
|
3822
|
-
lua: [{ re: /\brequire\s*\(?\s*['"]([^'"]+)['"]/g }],
|
|
3823
|
-
elixir: [{ re: /^[ \t]*(?:alias|import|require|use)\s+([A-Z][\w.]*)/gm, name: "full" }],
|
|
3824
|
-
haskell: [{ re: /^[ \t]*import\s+(?:qualified\s+)?([\w.]+)/gm, name: "full" }],
|
|
3825
|
-
zig: [{ re: /@import\s*\(\s*"([^"]+)"\s*\)/g }],
|
|
3826
|
-
proto: [{ re: /^[ \t]*import\s+(?:public\s+|weak\s+)?"([^"]+)"\s*;/gm }],
|
|
3827
|
-
// `@import "x"`, `@use "x"`, `@forward "x"` — Sass and plain CSS alike.
|
|
3828
|
-
css: [{ re: /^[ \t]*@(?:import|use|forward)\s+(?:url\()?\s*['"]([^'"]+)['"]/gm }],
|
|
3829
|
-
// A .vue/.svelte file's <script> block is JS; the same ESM syntax applies.
|
|
3830
|
-
vue: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
3831
|
-
svelte: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
3832
|
-
html: [
|
|
3833
|
-
{ re: /<script[^>]+src\s*=\s*['"]([^'"]+)['"]/g },
|
|
3834
|
-
{ re: /<link[^>]+href\s*=\s*['"]([^'"]+)['"]/g }
|
|
3835
|
-
],
|
|
3836
|
-
shell: [{ re: /^[ \t]*(?:source|\.)\s+(\S+)/gm }],
|
|
3837
|
-
r: [{ re: /\b(?:source|library|require)\s*\(\s*['"]?([\w./-]+)['"]?\s*\)/g }]
|
|
3838
|
-
};
|
|
3839
|
-
function lastSegment(specifier) {
|
|
3840
|
-
const pathLike = /[/\\]|::/.test(specifier);
|
|
3841
|
-
const segments = specifier.split(/[/\\]|::/).filter(Boolean);
|
|
3842
|
-
let last = segments[segments.length - 1] ?? specifier;
|
|
3843
|
-
if (last === "*" || last === "_") {
|
|
3844
|
-
last = segments[segments.length - 2] ?? specifier;
|
|
3845
|
-
}
|
|
3846
|
-
if (pathLike) return last.replace(/\.[A-Za-z0-9]{1,8}$/, "") || last;
|
|
3847
|
-
const dotted = last.split(".").filter((part) => part && part !== "*" && part !== "_");
|
|
3848
|
-
return dotted[dotted.length - 1] ?? last;
|
|
3849
|
-
}
|
|
3850
|
-
function newlineOffsets(content) {
|
|
3851
|
-
const offsets = [];
|
|
3852
|
-
for (let i = 0; i < content.length; i++) {
|
|
3853
|
-
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
3854
|
-
}
|
|
3855
|
-
return offsets;
|
|
3856
|
-
}
|
|
3857
|
-
function lineAt(offsets, index) {
|
|
3858
|
-
let low = 0;
|
|
3859
|
-
let high = offsets.length;
|
|
3860
|
-
while (low < high) {
|
|
3861
|
-
const mid = low + high >>> 1;
|
|
3862
|
-
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
3863
|
-
else high = mid;
|
|
3864
|
-
}
|
|
3865
|
-
return low + 1;
|
|
3866
|
-
}
|
|
3867
|
-
function hasImportPatterns(lang) {
|
|
3868
|
-
return LANG_IMPORTS[lang] !== void 0;
|
|
3869
|
-
}
|
|
3870
|
-
function extractImports(opts) {
|
|
3871
|
-
const patterns = LANG_IMPORTS[opts.lang];
|
|
3872
|
-
if (!patterns || !opts.content) return [];
|
|
3873
|
-
const limit = opts.maxImports ?? IMPORT_MAX_PER_FILE;
|
|
3874
|
-
const content = opts.content.length > IMPORT_MAX_FILE_CHARS ? opts.content.slice(0, IMPORT_MAX_FILE_CHARS) : opts.content;
|
|
3875
|
-
const refs = [];
|
|
3876
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3877
|
-
const offsets = newlineOffsets(content);
|
|
3878
|
-
for (const pattern of patterns) {
|
|
3879
|
-
const re = new RegExp(pattern.re.source, pattern.re.flags);
|
|
3880
|
-
for (const match of content.matchAll(re)) {
|
|
3881
|
-
if (refs.length >= limit) return refs;
|
|
3882
|
-
const specifier = match[1]?.trim();
|
|
3883
|
-
if (!specifier) continue;
|
|
3884
|
-
const module = specifier;
|
|
3885
|
-
const toName = pattern.name === "full" ? module : lastSegment(module);
|
|
3886
|
-
if (!toName) continue;
|
|
3887
|
-
const key = `${module}\0${toName}`;
|
|
3888
|
-
if (seen.has(key)) continue;
|
|
3889
|
-
seen.add(key);
|
|
3890
|
-
refs.push({
|
|
3891
|
-
fromId: 0,
|
|
3892
|
-
toName,
|
|
3893
|
-
callType: "import",
|
|
3894
|
-
line: lineAt(offsets, match.index ?? 0),
|
|
3895
|
-
lang: opts.lang,
|
|
3896
|
-
module
|
|
3897
|
-
});
|
|
3898
|
-
}
|
|
3899
|
-
}
|
|
3900
|
-
return refs;
|
|
3901
|
-
}
|
|
3902
|
-
|
|
3903
|
-
// src/codebase-index/parser-dispatch.ts
|
|
3904
|
-
async function parseFileContent(file, content, lang) {
|
|
3905
|
-
const parsed = await dispatch(file, content, lang);
|
|
3906
|
-
return withRelations(parsed, content, lang);
|
|
3907
|
-
}
|
|
3908
|
-
async function dispatch(file, content, lang) {
|
|
3909
|
-
switch (lang) {
|
|
3910
|
-
case "ts":
|
|
3911
|
-
case "tsx":
|
|
3912
|
-
case "js":
|
|
3913
|
-
case "jsx": {
|
|
3914
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
|
|
3915
|
-
return parseSymbols9({ file, content, lang });
|
|
3916
|
-
}
|
|
3917
|
-
case "go": {
|
|
3918
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
|
|
3919
|
-
return parseSymbols9({ file, content, lang: "go" });
|
|
3920
|
-
}
|
|
3921
|
-
case "py": {
|
|
3922
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
|
|
3923
|
-
return parseSymbols9({ file, content, lang: "py" });
|
|
3924
|
-
}
|
|
3925
|
-
case "rs": {
|
|
3926
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
|
|
3927
|
-
return parseSymbols9({ file, content, lang: "rs" });
|
|
4970
|
+
if (head === "self" || head === "super") {
|
|
4971
|
+
let base = path6.posix.dirname(fromFile);
|
|
4972
|
+
for (const segment of segments) {
|
|
4973
|
+
if (segment === "super") base = path6.posix.dirname(base);
|
|
4974
|
+
else if (segment !== "self") break;
|
|
4975
|
+
}
|
|
4976
|
+
const rest2 = segments.filter((segment) => segment !== "self" && segment !== "super");
|
|
4977
|
+
return this.lookupWithExtensions(path6.posix.join(base, ...rest2), "rs");
|
|
3928
4978
|
}
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
4979
|
+
const owningCrate = findOwningRoot(this.structure, fromFile, ["cargo"]);
|
|
4980
|
+
const crate = head === "crate" ? owningCrate : this.structure.roots.find(
|
|
4981
|
+
(root) => root.kind === "cargo" && root.importPath === head?.replace(/-/g, "_")
|
|
4982
|
+
);
|
|
4983
|
+
if (!crate) {
|
|
4984
|
+
return this.lookupWithExtensions(
|
|
4985
|
+
path6.posix.join(path6.posix.dirname(fromFile), ...segments),
|
|
4986
|
+
"rs"
|
|
4987
|
+
);
|
|
3932
4988
|
}
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
4989
|
+
const rest = segments.slice(1);
|
|
4990
|
+
for (const base of crate.sourceRoots) {
|
|
4991
|
+
const parent = rest.length > 1 ? this.lookupWithExtensions(path6.posix.join(base, ...rest.slice(0, -1)), "rs") : void 0;
|
|
4992
|
+
const exact = this.lookupWithExtensions(path6.posix.join(base, ...rest), "rs");
|
|
4993
|
+
const hit = exact ?? parent ?? this.lookupWithExtensions(path6.posix.join(base, "lib"), "rs");
|
|
4994
|
+
if (hit) return hit;
|
|
3936
4995
|
}
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
case "shell":
|
|
3951
|
-
case "elixir": {
|
|
3952
|
-
try {
|
|
3953
|
-
const { parseSymbols: parseSymbols10 } = await Promise.resolve().then(() => (init_tree_sitter_parser(), tree_sitter_parser_exports));
|
|
3954
|
-
const parsed = await parseSymbols10({ file, content, lang });
|
|
3955
|
-
if (parsed.symbols.length > 0) return parsed;
|
|
3956
|
-
} catch {
|
|
3957
|
-
}
|
|
3958
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
3959
|
-
return parseSymbols9({ file, content, lang });
|
|
4996
|
+
return void 0;
|
|
4997
|
+
}
|
|
4998
|
+
/** `com.example.Thing` and `com.example.*` against JVM source roots. */
|
|
4999
|
+
resolveJvm(spec) {
|
|
5000
|
+
const segments = spec.split(".").filter(Boolean);
|
|
5001
|
+
if (segments.length === 0) return void 0;
|
|
5002
|
+
const sourceRoots = this.structure.roots.filter((root) => root.kind === "maven" || root.kind === "gradle").flatMap((root) => root.sourceRoots);
|
|
5003
|
+
const wildcard = segments[segments.length - 1] === "*";
|
|
5004
|
+
const parts = wildcard ? segments.slice(0, -1) : segments;
|
|
5005
|
+
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
5006
|
+
const target = path6.posix.join(base, ...parts);
|
|
5007
|
+
const hit = wildcard ? this.representativeIn(target, "jvm") : this.lookupWithExtensions(target, "jvm");
|
|
5008
|
+
if (hit) return hit;
|
|
3960
5009
|
}
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
5010
|
+
return void 0;
|
|
5011
|
+
}
|
|
5012
|
+
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
5013
|
+
resolveInclude(fromFile, spec) {
|
|
5014
|
+
const relative3 = this.lookupWithExtensions(
|
|
5015
|
+
path6.posix.join(path6.posix.dirname(fromFile), spec),
|
|
5016
|
+
"c"
|
|
5017
|
+
);
|
|
5018
|
+
if (relative3) return relative3;
|
|
5019
|
+
for (const base of [
|
|
5020
|
+
path6.posix.join(this.structure.projectRoot, "include"),
|
|
5021
|
+
this.structure.projectRoot
|
|
5022
|
+
]) {
|
|
5023
|
+
const hit = this.lookupWithExtensions(path6.posix.join(base, spec), "c");
|
|
5024
|
+
if (hit) return hit;
|
|
3964
5025
|
}
|
|
5026
|
+
return void 0;
|
|
3965
5027
|
}
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
5028
|
+
/** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
|
|
5029
|
+
resolveRuby(fromFile, spec) {
|
|
5030
|
+
const relative3 = this.lookupWithExtensions(
|
|
5031
|
+
path6.posix.join(path6.posix.dirname(fromFile), spec),
|
|
5032
|
+
"ruby"
|
|
5033
|
+
);
|
|
5034
|
+
if (relative3) return relative3;
|
|
5035
|
+
for (const base of [
|
|
5036
|
+
path6.posix.join(this.structure.projectRoot, "lib"),
|
|
5037
|
+
this.structure.projectRoot
|
|
5038
|
+
]) {
|
|
5039
|
+
const hit = this.lookupWithExtensions(path6.posix.join(base, spec), "ruby");
|
|
5040
|
+
if (hit) return hit;
|
|
5041
|
+
}
|
|
5042
|
+
return void 0;
|
|
3971
5043
|
}
|
|
3972
|
-
|
|
3973
|
-
|
|
5044
|
+
};
|
|
5045
|
+
|
|
5046
|
+
// src/codebase-index/indexer.ts
|
|
5047
|
+
init_parser_dispatch();
|
|
3974
5048
|
|
|
3975
5049
|
// src/codebase-index/parser-worker-pool.ts
|
|
5050
|
+
import * as fs7 from "node:fs";
|
|
5051
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
3976
5052
|
import { Worker } from "node:worker_threads";
|
|
3977
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3978
|
-
import * as fs6 from "node:fs";
|
|
3979
5053
|
var WORKER_POOL_THRESHOLD = 500;
|
|
5054
|
+
function resolveWorkerPoolThreshold() {
|
|
5055
|
+
const raw = process.env["WRONGSTACK_INDEX_WORKER_THRESHOLD"];
|
|
5056
|
+
if (raw === void 0) return WORKER_POOL_THRESHOLD;
|
|
5057
|
+
if (!/^\d+$/.test(raw)) return WORKER_POOL_THRESHOLD;
|
|
5058
|
+
return Number.parseInt(raw, 10);
|
|
5059
|
+
}
|
|
3980
5060
|
var ParserWorkerPool = class {
|
|
3981
5061
|
constructor(maxWorkers = defaultWorkerCount()) {
|
|
3982
5062
|
this.maxWorkers = maxWorkers;
|
|
@@ -4020,7 +5100,8 @@ var ParserWorkerPool = class {
|
|
|
4020
5100
|
w.unref();
|
|
4021
5101
|
w.on("message", (msg) => this.handleMessage(msg));
|
|
4022
5102
|
w.on("error", (err) => this.handleError(err, w));
|
|
4023
|
-
|
|
5103
|
+
w.on("exit", () => this.retireByReference(w));
|
|
5104
|
+
this.workers.push({ worker: w, workerId: w.threadId, busy: false });
|
|
4024
5105
|
} catch {
|
|
4025
5106
|
if (this.workers.length === 0) {
|
|
4026
5107
|
this.unavailable = true;
|
|
@@ -4036,7 +5117,7 @@ var ParserWorkerPool = class {
|
|
|
4036
5117
|
}
|
|
4037
5118
|
/**
|
|
4038
5119
|
* Parse files in parallel across the worker pool. Returns a flat
|
|
4039
|
-
* `FileSymbols[]` in completion order (caller
|
|
5120
|
+
* `FileSymbols[]` in completion order (caller matches by file path).
|
|
4040
5121
|
*
|
|
4041
5122
|
* Content is pre-read by the main thread (for the content-hash check)
|
|
4042
5123
|
* and passed to workers to avoid a second disk read. Files are
|
|
@@ -4057,21 +5138,19 @@ var ParserWorkerPool = class {
|
|
|
4057
5138
|
chunks[i % workerCount].push(files[i]);
|
|
4058
5139
|
}
|
|
4059
5140
|
return new Promise((resolve4, reject) => {
|
|
5141
|
+
const pendingChunks = /* @__PURE__ */ new Map();
|
|
4060
5142
|
this.pending.set(batchId, {
|
|
4061
5143
|
resolve: resolve4,
|
|
4062
5144
|
reject,
|
|
4063
5145
|
accumulated: [],
|
|
4064
|
-
|
|
4065
|
-
|
|
5146
|
+
pendingChunks,
|
|
5147
|
+
settled: false
|
|
4066
5148
|
});
|
|
4067
5149
|
for (let i = 0; i < workerCount; i++) {
|
|
4068
5150
|
const pw = this.workers[i];
|
|
4069
5151
|
pw.busy = true;
|
|
4070
|
-
pw.
|
|
4071
|
-
|
|
4072
|
-
id: batchId,
|
|
4073
|
-
files: chunks[i]
|
|
4074
|
-
});
|
|
5152
|
+
pendingChunks.set(pw.workerId, chunks[i]);
|
|
5153
|
+
pw.worker.postMessage({ type: "parse", id: batchId, files: chunks[i] });
|
|
4075
5154
|
}
|
|
4076
5155
|
});
|
|
4077
5156
|
}
|
|
@@ -4080,6 +5159,12 @@ var ParserWorkerPool = class {
|
|
|
4080
5159
|
const workers = this.workers.map((w) => w.worker);
|
|
4081
5160
|
this.workers = [];
|
|
4082
5161
|
this.unavailable = false;
|
|
5162
|
+
for (const [, p] of this.pending) {
|
|
5163
|
+
if (p.settled) continue;
|
|
5164
|
+
p.settled = true;
|
|
5165
|
+
p.reject(new Error("ParserWorkerPool shut down"));
|
|
5166
|
+
}
|
|
5167
|
+
this.pending.clear();
|
|
4083
5168
|
for (const w of workers) {
|
|
4084
5169
|
try {
|
|
4085
5170
|
w.postMessage({ type: "shutdown" });
|
|
@@ -4100,39 +5185,117 @@ var ParserWorkerPool = class {
|
|
|
4100
5185
|
})
|
|
4101
5186
|
)
|
|
4102
5187
|
);
|
|
4103
|
-
for (const [, p] of this.pending) p.reject(new Error("ParserWorkerPool shut down"));
|
|
4104
|
-
this.pending.clear();
|
|
4105
5188
|
}
|
|
4106
5189
|
handleMessage(msg) {
|
|
4107
5190
|
const batch = this.pending.get(msg.id);
|
|
4108
5191
|
if (!batch) return;
|
|
5192
|
+
const worker2 = this.workers.find((w) => w.workerId === msg.workerId);
|
|
5193
|
+
if (worker2) worker2.busy = false;
|
|
5194
|
+
batch.pendingChunks.delete(msg.workerId);
|
|
4109
5195
|
batch.accumulated.push(...msg.results);
|
|
4110
|
-
batch.
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
if (batch.completedWorkers >= batch.expectedWorkers) {
|
|
5196
|
+
if (batch.pendingChunks.size === 0) {
|
|
5197
|
+
if (batch.settled) return;
|
|
5198
|
+
batch.settled = true;
|
|
4114
5199
|
this.pending.delete(msg.id);
|
|
4115
5200
|
batch.resolve(batch.accumulated);
|
|
4116
5201
|
}
|
|
4117
5202
|
}
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
5203
|
+
/**
|
|
5204
|
+
* Remove a worker from the pool and salvage any chunk it still owed.
|
|
5205
|
+
*
|
|
5206
|
+
* Idempotent by workerId — `error` and `exit` can both fire for one
|
|
5207
|
+
* death, and a worker may die while no batch references it. When the
|
|
5208
|
+
* dead worker owed files to an in-flight batch and other workers remain,
|
|
5209
|
+
* those files are re-parsed inline on this thread (one fewer worker
|
|
5210
|
+
* should cost latency, not correctness). When it was the last worker,
|
|
5211
|
+
* every remaining batch rejects so the indexer's existing inline
|
|
5212
|
+
* fallback takes over the whole batch.
|
|
5213
|
+
*/
|
|
5214
|
+
retireWorker(workerId) {
|
|
5215
|
+
const entry = this.workers.find((w) => w.workerId === workerId);
|
|
5216
|
+
if (!entry) return;
|
|
5217
|
+
this.workers = this.workers.filter((w) => w.workerId !== workerId);
|
|
5218
|
+
for (const [batchId, batch] of [...this.pending]) {
|
|
5219
|
+
const orphaned = batch.pendingChunks.get(workerId);
|
|
5220
|
+
if (!orphaned) continue;
|
|
5221
|
+
if (this.workers.length === 0) {
|
|
5222
|
+
this.pending.delete(batchId);
|
|
5223
|
+
this.unavailable = true;
|
|
5224
|
+
if (!batch.settled) {
|
|
5225
|
+
batch.settled = true;
|
|
5226
|
+
batch.reject(new Error("ParserWorkerPool: all workers died mid-batch"));
|
|
5227
|
+
}
|
|
5228
|
+
continue;
|
|
5229
|
+
}
|
|
5230
|
+
void this.reparseInline(batch, orphaned, workerId);
|
|
5231
|
+
}
|
|
5232
|
+
}
|
|
5233
|
+
/**
|
|
5234
|
+
* Salvage path: re-parse an orphaned chunk on this thread. Files that
|
|
5235
|
+
* fail here stay absent from the results — same contract as a per-file
|
|
5236
|
+
* error inside a live worker (see handleMessage).
|
|
5237
|
+
*/
|
|
5238
|
+
async reparseInline(batch, orphaned, workerId) {
|
|
5239
|
+
try {
|
|
5240
|
+
const { parseFileContent: parseFileContent2 } = await Promise.resolve().then(() => (init_parser_dispatch(), parser_dispatch_exports));
|
|
5241
|
+
for (const item of orphaned) {
|
|
5242
|
+
if (batch.settled) return;
|
|
5243
|
+
try {
|
|
5244
|
+
const parsed = await parseFileContent2(item.file, item.content, item.lang);
|
|
5245
|
+
batch.accumulated.push(parsed);
|
|
5246
|
+
} catch {
|
|
5247
|
+
}
|
|
5248
|
+
await new Promise((resolve4) => setImmediate(resolve4));
|
|
5249
|
+
}
|
|
5250
|
+
} finally {
|
|
5251
|
+
this.finishSalvage(batch, workerId);
|
|
5252
|
+
}
|
|
5253
|
+
}
|
|
5254
|
+
/**
|
|
5255
|
+
* Terminal tail of a salvage — runs on every exit path. Kept free of
|
|
5256
|
+
* control flow inside a `finally` (noUnsafeFinally): releases the
|
|
5257
|
+
* pending-marker and resolves the batch if this was its last chunk.
|
|
5258
|
+
*/
|
|
5259
|
+
finishSalvage(batch, workerId) {
|
|
5260
|
+
batch.pendingChunks.delete(workerId);
|
|
5261
|
+
if (batch.pendingChunks.size === 0) {
|
|
5262
|
+
for (const [batchId, tracked] of this.pending) {
|
|
5263
|
+
if (tracked === batch) {
|
|
5264
|
+
if (batch.settled) return;
|
|
5265
|
+
batch.settled = true;
|
|
5266
|
+
this.pending.delete(batchId);
|
|
5267
|
+
batch.resolve(batch.accumulated);
|
|
5268
|
+
return;
|
|
5269
|
+
}
|
|
5270
|
+
}
|
|
4124
5271
|
}
|
|
4125
5272
|
}
|
|
5273
|
+
/**
|
|
5274
|
+
* Retire by worker object rather than threadId. `threadId` is -1 before
|
|
5275
|
+
* the worker emits `online`, so a death during script load would make a
|
|
5276
|
+
* threadId-keyed lookup silently no-op and leak the entry (with its
|
|
5277
|
+
* pending chunk) — reference identity is correct in every case.
|
|
5278
|
+
*/
|
|
5279
|
+
retireByReference(source) {
|
|
5280
|
+
const entry = this.workers.find((w) => w.worker === source);
|
|
5281
|
+
if (entry) this.retireWorker(entry.workerId);
|
|
5282
|
+
}
|
|
5283
|
+
handleError(err, source) {
|
|
5284
|
+
void err;
|
|
5285
|
+
this.retireByReference(source);
|
|
5286
|
+
}
|
|
4126
5287
|
};
|
|
4127
5288
|
function defaultWorkerCount() {
|
|
4128
5289
|
const cores = globalThis.navigator?.hardwareConcurrency ?? 4;
|
|
4129
5290
|
return Math.max(1, Math.min(4, cores - 1));
|
|
4130
5291
|
}
|
|
4131
5292
|
function resolveWorkerScriptUrl() {
|
|
5293
|
+
const override = process.env["WRONGSTACK_PARSER_WORKER_SCRIPT"];
|
|
5294
|
+
if (override) return pathToFileURL(override);
|
|
4132
5295
|
for (const rel of ["./parser-worker-script.js", "./codebase-index/parser-worker-script.js"]) {
|
|
4133
5296
|
try {
|
|
4134
5297
|
const url = new URL(rel, import.meta.url);
|
|
4135
|
-
if (url.protocol === "file:" &&
|
|
5298
|
+
if (url.protocol === "file:" && fs7.existsSync(fileURLToPath2(url))) return url;
|
|
4136
5299
|
} catch {
|
|
4137
5300
|
}
|
|
4138
5301
|
}
|
|
@@ -4145,8 +5308,8 @@ function getParserPool() {
|
|
|
4145
5308
|
}
|
|
4146
5309
|
|
|
4147
5310
|
// src/codebase-index/writer.ts
|
|
4148
|
-
import * as
|
|
4149
|
-
import * as
|
|
5311
|
+
import * as fs9 from "node:fs";
|
|
5312
|
+
import * as path14 from "node:path";
|
|
4150
5313
|
|
|
4151
5314
|
// src/codebase-index/bm25.ts
|
|
4152
5315
|
var K1 = 1.5;
|
|
@@ -4241,11 +5404,11 @@ var Bm25Index = class {
|
|
|
4241
5404
|
init_languages();
|
|
4242
5405
|
|
|
4243
5406
|
// src/codebase-index/schema.ts
|
|
4244
|
-
var SCHEMA_VERSION =
|
|
5407
|
+
var SCHEMA_VERSION = 5;
|
|
4245
5408
|
|
|
4246
5409
|
// src/codebase-index/sqlite-runtime.ts
|
|
4247
|
-
import { createRequire } from "node:module";
|
|
4248
5410
|
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
5411
|
+
import { loadRuntimeDatabaseSync } from "@wrongstack/persistence";
|
|
4249
5412
|
var warningSilenced = false;
|
|
4250
5413
|
function silenceSqliteExperimentalWarning() {
|
|
4251
5414
|
if (warningSilenced) return;
|
|
@@ -4263,11 +5426,10 @@ function loadDatabaseSync() {
|
|
|
4263
5426
|
if (DatabaseSyncCtor) return DatabaseSyncCtor;
|
|
4264
5427
|
silenceSqliteExperimentalWarning();
|
|
4265
5428
|
try {
|
|
4266
|
-
|
|
4267
|
-
DatabaseSyncCtor = req("node:sqlite").DatabaseSync;
|
|
5429
|
+
DatabaseSyncCtor = loadRuntimeDatabaseSync();
|
|
4268
5430
|
} catch (err) {
|
|
4269
5431
|
throw new Error(
|
|
4270
|
-
`The codebase index needs
|
|
5432
|
+
`The codebase index needs node:sqlite (Node >= 22.5) or bun:sqlite. This runtime doesn't provide it: ${toErrorMessage(err)}`
|
|
4271
5433
|
);
|
|
4272
5434
|
}
|
|
4273
5435
|
return DatabaseSyncCtor;
|
|
@@ -4310,9 +5472,87 @@ function runSqliteWithRetry(fn) {
|
|
|
4310
5472
|
throw lastError;
|
|
4311
5473
|
}
|
|
4312
5474
|
|
|
5475
|
+
// src/codebase-index/vector-search.ts
|
|
5476
|
+
var RRF_K = 60;
|
|
5477
|
+
function vectorEmbeddingEnabled() {
|
|
5478
|
+
return process.env["WRONGSTACK_INDEX_VECTORS"] === "1";
|
|
5479
|
+
}
|
|
5480
|
+
var VECTOR_DIMENSIONS = 384;
|
|
5481
|
+
var NGRAM_SIZE = 3;
|
|
5482
|
+
function embedText(text) {
|
|
5483
|
+
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
5484
|
+
const normalized = text.toLowerCase().trim();
|
|
5485
|
+
if (normalized.length < NGRAM_SIZE) {
|
|
5486
|
+
const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
|
|
5487
|
+
for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
|
|
5488
|
+
const ngram = padded.slice(i, i + NGRAM_SIZE);
|
|
5489
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5490
|
+
vec[bucket] += 1;
|
|
5491
|
+
}
|
|
5492
|
+
} else {
|
|
5493
|
+
for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
|
|
5494
|
+
const ngram = normalized.slice(i, i + NGRAM_SIZE);
|
|
5495
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5496
|
+
vec[bucket] += 1;
|
|
5497
|
+
}
|
|
5498
|
+
}
|
|
5499
|
+
let norm = 0;
|
|
5500
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5501
|
+
norm += vec[i] * vec[i];
|
|
5502
|
+
}
|
|
5503
|
+
norm = Math.sqrt(norm);
|
|
5504
|
+
if (norm > 0) {
|
|
5505
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5506
|
+
vec[i] /= norm;
|
|
5507
|
+
}
|
|
5508
|
+
}
|
|
5509
|
+
return vec;
|
|
5510
|
+
}
|
|
5511
|
+
function hashNgram(str) {
|
|
5512
|
+
let hash = 2166136261;
|
|
5513
|
+
for (let i = 0; i < str.length; i++) {
|
|
5514
|
+
hash ^= str.charCodeAt(i);
|
|
5515
|
+
hash = Math.imul(hash, 16777619);
|
|
5516
|
+
}
|
|
5517
|
+
return hash >>> 0;
|
|
5518
|
+
}
|
|
5519
|
+
function cosineSimilarity(a, b) {
|
|
5520
|
+
let dot = 0;
|
|
5521
|
+
const len = Math.min(a.length, b.length);
|
|
5522
|
+
for (let i = 0; i < len; i++) {
|
|
5523
|
+
dot += a[i] * b[i];
|
|
5524
|
+
}
|
|
5525
|
+
return dot;
|
|
5526
|
+
}
|
|
5527
|
+
function encodeVector(vec) {
|
|
5528
|
+
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
5529
|
+
}
|
|
5530
|
+
function decodeVector(buf) {
|
|
5531
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
5532
|
+
const copy = new Float32Array(buf.byteLength / 4);
|
|
5533
|
+
for (let i = 0; i < copy.length; i++) {
|
|
5534
|
+
copy[i] = view.getFloat32(i * 4, true);
|
|
5535
|
+
}
|
|
5536
|
+
return copy;
|
|
5537
|
+
}
|
|
5538
|
+
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
5539
|
+
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
5540
|
+
const scored = [];
|
|
5541
|
+
for (const id of allIds) {
|
|
5542
|
+
const bm25Rank = bm25Ranks.get(id);
|
|
5543
|
+
const vecRank = vectorRanks.get(id);
|
|
5544
|
+
let score = 0;
|
|
5545
|
+
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
5546
|
+
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
5547
|
+
scored.push([id, score]);
|
|
5548
|
+
}
|
|
5549
|
+
scored.sort((a, b) => b[1] - a[1]);
|
|
5550
|
+
return scored;
|
|
5551
|
+
}
|
|
5552
|
+
|
|
4313
5553
|
// src/codebase-index/writer-admin.ts
|
|
4314
|
-
import * as
|
|
4315
|
-
import * as
|
|
5554
|
+
import * as fs8 from "node:fs";
|
|
5555
|
+
import * as path13 from "node:path";
|
|
4316
5556
|
var DB_FILE = "index.db";
|
|
4317
5557
|
function getAllIndexableWithStatement(stmt) {
|
|
4318
5558
|
return stmt("SELECT id, text FROM symbols").all().map(
|
|
@@ -4348,6 +5588,14 @@ function getMetadataWithStatement(stmt, key) {
|
|
|
4348
5588
|
const rows = stmt("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
4349
5589
|
return rows[0]?.value;
|
|
4350
5590
|
}
|
|
5591
|
+
function getIndexSummaryWithStatement(stmt) {
|
|
5592
|
+
const fileRows = stmt("SELECT COUNT(*) AS n FROM files").all();
|
|
5593
|
+
const lastRows = stmt("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
5594
|
+
return {
|
|
5595
|
+
totalFiles: fileRows[0] ? Number(fileRows[0].n) : 0,
|
|
5596
|
+
lastIndexed: lastRows.length ? Number(lastRows[0]?.value) : null
|
|
5597
|
+
};
|
|
5598
|
+
}
|
|
4351
5599
|
function getFileMetaWithStatement(stmt, file) {
|
|
4352
5600
|
const rows = stmt(
|
|
4353
5601
|
"SELECT file, lang, mtime_ms, symbol_count, last_indexed, content_hash FROM files WHERE file = ?"
|
|
@@ -4377,22 +5625,102 @@ function getAllFileMetasWithStatement(stmt) {
|
|
|
4377
5625
|
}
|
|
4378
5626
|
function getIndexDbSizeBytes(indexDir) {
|
|
4379
5627
|
try {
|
|
4380
|
-
return
|
|
5628
|
+
return fs8.statSync(path13.join(indexDir, DB_FILE)).size;
|
|
4381
5629
|
} catch {
|
|
4382
5630
|
return 0;
|
|
4383
5631
|
}
|
|
4384
5632
|
}
|
|
4385
5633
|
|
|
5634
|
+
// src/codebase-index/writer-helpers.ts
|
|
5635
|
+
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
5636
|
+
function escapeLike(value) {
|
|
5637
|
+
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
5638
|
+
}
|
|
5639
|
+
function ladderChunkSizes(total, max) {
|
|
5640
|
+
if (total <= 0) return [];
|
|
5641
|
+
const sizes = [];
|
|
5642
|
+
let remaining = total;
|
|
5643
|
+
while (remaining > 0) {
|
|
5644
|
+
const cap = Math.min(remaining, max);
|
|
5645
|
+
const pow = cap >= 1 ? 2 ** Math.floor(Math.log2(cap)) : 1;
|
|
5646
|
+
const take = Math.max(1, Math.min(pow, remaining));
|
|
5647
|
+
sizes.push(take);
|
|
5648
|
+
remaining -= take;
|
|
5649
|
+
}
|
|
5650
|
+
return sizes;
|
|
5651
|
+
}
|
|
5652
|
+
function nextPow2(count) {
|
|
5653
|
+
return count <= 1 ? 1 : 2 ** Math.ceil(Math.log2(count));
|
|
5654
|
+
}
|
|
5655
|
+
function padToInBucket(values) {
|
|
5656
|
+
if (values.length <= 1) return values.slice();
|
|
5657
|
+
const target = nextPow2(values.length);
|
|
5658
|
+
const padded = values.slice();
|
|
5659
|
+
while (padded.length < target) padded.push(padded[0]);
|
|
5660
|
+
return padded;
|
|
5661
|
+
}
|
|
5662
|
+
function placeholders(count) {
|
|
5663
|
+
return Array.from({ length: count }, () => "?").join(",");
|
|
5664
|
+
}
|
|
5665
|
+
function inListChunks(total, max) {
|
|
5666
|
+
if (total <= 0) return [];
|
|
5667
|
+
if (nextPow2(total) <= max) return [total];
|
|
5668
|
+
const powMax = Math.max(1, 2 ** Math.floor(Math.log2(max)));
|
|
5669
|
+
return ladderChunkSizes(total, powMax);
|
|
5670
|
+
}
|
|
5671
|
+
function posixIndexPath(file) {
|
|
5672
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
5673
|
+
}
|
|
5674
|
+
function indexedFileMatchSql(column = "file") {
|
|
5675
|
+
return `(${column} = ? OR replace(${column}, '\\', '/') = ? OR replace(${column}, '\\', '/') LIKE ? ESCAPE '\\')`;
|
|
5676
|
+
}
|
|
5677
|
+
function indexedFileMatchArgs(file) {
|
|
5678
|
+
const posix4 = posixIndexPath(file.trim());
|
|
5679
|
+
return [file, posix4, `%/${escapeLike(posix4)}`];
|
|
5680
|
+
}
|
|
5681
|
+
function matchesIndexedPackageFilter(storedFile, packageLabel, filter) {
|
|
5682
|
+
if (packageLabel === filter) return true;
|
|
5683
|
+
const posixFile = posixIndexPath(storedFile);
|
|
5684
|
+
const posixFilter = posixIndexPath(filter.trim());
|
|
5685
|
+
if (!posixFilter) return false;
|
|
5686
|
+
return posixFile === posixFilter || posixFile.endsWith(`/${posixFilter}`) || posixFile.includes(`/${posixFilter}/`);
|
|
5687
|
+
}
|
|
5688
|
+
function assignRefsToSymbols(refs, symbols) {
|
|
5689
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
5690
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
5691
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5692
|
+
const assigned = [];
|
|
5693
|
+
for (const ref of refs) {
|
|
5694
|
+
let owner;
|
|
5695
|
+
for (const symbol of ordered) {
|
|
5696
|
+
if (symbol.line > ref.line) break;
|
|
5697
|
+
owner = symbol;
|
|
5698
|
+
}
|
|
5699
|
+
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
5700
|
+
if (!owner || owner.id <= 0) continue;
|
|
5701
|
+
const key = `${owner.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
5702
|
+
if (seen.has(key)) continue;
|
|
5703
|
+
seen.add(key);
|
|
5704
|
+
assigned.push({ ...ref, fromId: owner.id });
|
|
5705
|
+
}
|
|
5706
|
+
return assigned;
|
|
5707
|
+
}
|
|
5708
|
+
function resolveIndexDir(projectRoot, override) {
|
|
5709
|
+
return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
|
|
5710
|
+
}
|
|
5711
|
+
|
|
4386
5712
|
// src/codebase-index/writer-bulk-insert.ts
|
|
4387
5713
|
function bulkInsertSymbolsWithStatement(stmt, maxSqlVars, rows) {
|
|
4388
5714
|
if (rows.length === 0) return;
|
|
4389
|
-
const
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
const
|
|
5715
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 12)));
|
|
5716
|
+
let cursor = 0;
|
|
5717
|
+
for (const take of ladder) {
|
|
5718
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
5719
|
+
cursor += take;
|
|
5720
|
+
const placeholders2 = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
4393
5721
|
const insert = stmt(
|
|
4394
|
-
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text
|
|
4395
|
-
VALUES ${
|
|
5722
|
+
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text)
|
|
5723
|
+
VALUES ${placeholders2}`
|
|
4396
5724
|
);
|
|
4397
5725
|
const binds = [];
|
|
4398
5726
|
for (const r of chunk) {
|
|
@@ -4407,8 +5735,7 @@ function bulkInsertSymbolsWithStatement(stmt, maxSqlVars, rows) {
|
|
|
4407
5735
|
r.signature,
|
|
4408
5736
|
r.docComment,
|
|
4409
5737
|
r.scope,
|
|
4410
|
-
r.text
|
|
4411
|
-
r.file
|
|
5738
|
+
r.text
|
|
4412
5739
|
);
|
|
4413
5740
|
}
|
|
4414
5741
|
insert.run(...binds);
|
|
@@ -4416,11 +5743,13 @@ function bulkInsertSymbolsWithStatement(stmt, maxSqlVars, rows) {
|
|
|
4416
5743
|
}
|
|
4417
5744
|
function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
|
|
4418
5745
|
if (!ftsAvailable || rows.length === 0) return;
|
|
4419
|
-
const
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
const
|
|
4423
|
-
|
|
5746
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 2)));
|
|
5747
|
+
let cursor = 0;
|
|
5748
|
+
for (const take of ladder) {
|
|
5749
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
5750
|
+
cursor += take;
|
|
5751
|
+
const placeholders2 = chunk.map(() => "(?, ?)").join(", ");
|
|
5752
|
+
const insert = stmt(`INSERT INTO symbols_fts(rowid, text) VALUES ${placeholders2}`);
|
|
4424
5753
|
const binds = [];
|
|
4425
5754
|
for (const r of chunk) binds.push(r.id, r.text);
|
|
4426
5755
|
insert.run(...binds);
|
|
@@ -4428,11 +5757,13 @@ function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
|
|
|
4428
5757
|
}
|
|
4429
5758
|
function bulkInsertVectorsWithStatement(stmt, maxSqlVars, rows) {
|
|
4430
5759
|
if (rows.length === 0) return;
|
|
4431
|
-
const
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
const
|
|
4435
|
-
|
|
5760
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 2)));
|
|
5761
|
+
let cursor = 0;
|
|
5762
|
+
for (const take of ladder) {
|
|
5763
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
5764
|
+
cursor += take;
|
|
5765
|
+
const placeholders2 = chunk.map(() => "(?, ?)").join(", ");
|
|
5766
|
+
const insert = stmt(`INSERT INTO symbol_vectors(symbol_id, vector) VALUES ${placeholders2}`);
|
|
4436
5767
|
const binds = [];
|
|
4437
5768
|
for (const r of chunk) binds.push(r.id, r.vector);
|
|
4438
5769
|
insert.run(...binds);
|
|
@@ -4440,13 +5771,15 @@ function bulkInsertVectorsWithStatement(stmt, maxSqlVars, rows) {
|
|
|
4440
5771
|
}
|
|
4441
5772
|
function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
|
|
4442
5773
|
if (refs.length === 0) return;
|
|
4443
|
-
const
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
const
|
|
5774
|
+
const ladder = ladderChunkSizes(refs.length, Math.max(1, Math.floor(maxSqlVars / 8)));
|
|
5775
|
+
let cursor = 0;
|
|
5776
|
+
for (const take of ladder) {
|
|
5777
|
+
const chunk = refs.slice(cursor, cursor + take);
|
|
5778
|
+
cursor += take;
|
|
5779
|
+
const placeholders2 = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
4447
5780
|
const insert = stmt(
|
|
4448
5781
|
`INSERT INTO refs(from_id, to_name, to_id, call_type, line, lang, module, to_file)
|
|
4449
|
-
VALUES ${
|
|
5782
|
+
VALUES ${placeholders2}`
|
|
4450
5783
|
);
|
|
4451
5784
|
const binds = [];
|
|
4452
5785
|
for (const ref of chunk) {
|
|
@@ -4600,52 +5933,6 @@ function materializeWeightedEdges(edgeMap, idPrefix) {
|
|
|
4600
5933
|
return edges;
|
|
4601
5934
|
}
|
|
4602
5935
|
|
|
4603
|
-
// src/codebase-index/writer-helpers.ts
|
|
4604
|
-
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
4605
|
-
function escapeLike(value) {
|
|
4606
|
-
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
4607
|
-
}
|
|
4608
|
-
function posixIndexPath(file) {
|
|
4609
|
-
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
4610
|
-
}
|
|
4611
|
-
function indexedFileMatchSql(column = "file") {
|
|
4612
|
-
return `(${column} = ? OR replace(${column}, '\\', '/') = ? OR replace(${column}, '\\', '/') LIKE ? ESCAPE '\\')`;
|
|
4613
|
-
}
|
|
4614
|
-
function indexedFileMatchArgs(file) {
|
|
4615
|
-
const posix4 = posixIndexPath(file.trim());
|
|
4616
|
-
return [file, posix4, `%/${escapeLike(posix4)}`];
|
|
4617
|
-
}
|
|
4618
|
-
function matchesIndexedPackageFilter(storedFile, packageLabel, filter) {
|
|
4619
|
-
if (packageLabel === filter) return true;
|
|
4620
|
-
const posixFile = posixIndexPath(storedFile);
|
|
4621
|
-
const posixFilter = posixIndexPath(filter.trim());
|
|
4622
|
-
if (!posixFilter) return false;
|
|
4623
|
-
return posixFile === posixFilter || posixFile.endsWith(`/${posixFilter}`) || posixFile.includes(`/${posixFilter}/`);
|
|
4624
|
-
}
|
|
4625
|
-
function assignRefsToSymbols(refs, symbols) {
|
|
4626
|
-
if (refs.length === 0 || symbols.length === 0) return [];
|
|
4627
|
-
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
4628
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4629
|
-
const assigned = [];
|
|
4630
|
-
for (const ref of refs) {
|
|
4631
|
-
let owner;
|
|
4632
|
-
for (const symbol of ordered) {
|
|
4633
|
-
if (symbol.line > ref.line) break;
|
|
4634
|
-
owner = symbol;
|
|
4635
|
-
}
|
|
4636
|
-
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
4637
|
-
if (!owner || owner.id <= 0) continue;
|
|
4638
|
-
const key = `${owner.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
4639
|
-
if (seen.has(key)) continue;
|
|
4640
|
-
seen.add(key);
|
|
4641
|
-
assigned.push({ ...ref, fromId: owner.id });
|
|
4642
|
-
}
|
|
4643
|
-
return assigned;
|
|
4644
|
-
}
|
|
4645
|
-
function resolveIndexDir(projectRoot, override) {
|
|
4646
|
-
return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
|
|
4647
|
-
}
|
|
4648
|
-
|
|
4649
5936
|
// src/codebase-index/writer-ref-mapper.ts
|
|
4650
5937
|
function mapWriterRefRow(row) {
|
|
4651
5938
|
return {
|
|
@@ -4667,20 +5954,20 @@ function mapWriterRefRow(row) {
|
|
|
4667
5954
|
var MAX_SQL_VARS = 900;
|
|
4668
5955
|
function chunkedIdQuery(stmt, ids, buildSql, extraArgs = []) {
|
|
4669
5956
|
const results = [];
|
|
4670
|
-
for (
|
|
4671
|
-
const chunk = ids.slice(
|
|
4672
|
-
|
|
4673
|
-
const sql = buildSql(placeholders);
|
|
5957
|
+
for (const take of inListChunks(ids.length, MAX_SQL_VARS)) {
|
|
5958
|
+
const chunk = padToInBucket(ids.slice(0, take));
|
|
5959
|
+
ids = ids.slice(take);
|
|
5960
|
+
const sql = buildSql(placeholders(chunk.length));
|
|
4674
5961
|
results.push(...stmt(sql).all(...chunk, ...extraArgs));
|
|
4675
5962
|
}
|
|
4676
5963
|
return results;
|
|
4677
5964
|
}
|
|
4678
5965
|
function chunkedIdScalar(stmt, ids, buildSql, extraArgs = []) {
|
|
4679
5966
|
let total = 0;
|
|
4680
|
-
for (
|
|
4681
|
-
const chunk = ids.slice(
|
|
4682
|
-
|
|
4683
|
-
const sql = buildSql(placeholders);
|
|
5967
|
+
for (const take of inListChunks(ids.length, MAX_SQL_VARS)) {
|
|
5968
|
+
const chunk = padToInBucket(ids.slice(0, take));
|
|
5969
|
+
ids = ids.slice(take);
|
|
5970
|
+
const sql = buildSql(placeholders(chunk.length));
|
|
4684
5971
|
const rows = stmt(sql).all(...chunk, ...extraArgs);
|
|
4685
5972
|
total += rows[0]?.n ?? 0;
|
|
4686
5973
|
}
|
|
@@ -4709,16 +5996,24 @@ function resolveIndexedFiles(stmt, file) {
|
|
|
4709
5996
|
}
|
|
4710
5997
|
function resolveSymbolIds(stmt, symbolName, file) {
|
|
4711
5998
|
if (!file) {
|
|
4712
|
-
const
|
|
4713
|
-
|
|
5999
|
+
const rows = stmt("SELECT id FROM symbols WHERE name = ? ORDER BY id").all(
|
|
6000
|
+
symbolName
|
|
6001
|
+
);
|
|
6002
|
+
return rows.map((r) => r.id);
|
|
4714
6003
|
}
|
|
4715
6004
|
const indexedFiles = resolveIndexedFiles(stmt, file);
|
|
4716
6005
|
if (indexedFiles.length === 0) return [];
|
|
4717
|
-
const
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
6006
|
+
const ids = [];
|
|
6007
|
+
let cursor = 0;
|
|
6008
|
+
for (const take of inListChunks(indexedFiles.length, MAX_SQL_VARS)) {
|
|
6009
|
+
const files = padToInBucket(indexedFiles.slice(cursor, cursor + take));
|
|
6010
|
+
cursor += take;
|
|
6011
|
+
const rows = stmt(
|
|
6012
|
+
`SELECT id FROM symbols WHERE name = ? AND file IN (${placeholders(files.length)}) ORDER BY id`
|
|
6013
|
+
).all(symbolName, ...files);
|
|
6014
|
+
ids.push(...rows.map((r) => r.id));
|
|
6015
|
+
}
|
|
6016
|
+
return ids;
|
|
4722
6017
|
}
|
|
4723
6018
|
function findIncomingCallsByName(stmt, symbolName, file, limit) {
|
|
4724
6019
|
const targetIds = resolveSymbolIds(stmt, symbolName, file);
|
|
@@ -5152,9 +6447,9 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
5152
6447
|
const loadedIds = new Set(syms.map((s) => s.id));
|
|
5153
6448
|
const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
|
|
5154
6449
|
if (missingIds.length > 0) {
|
|
5155
|
-
const
|
|
6450
|
+
const placeholders2 = missingIds.map(() => "?").join(",");
|
|
5156
6451
|
const extras = stmt(
|
|
5157
|
-
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${
|
|
6452
|
+
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders2})`
|
|
5158
6453
|
).all(...missingIds);
|
|
5159
6454
|
for (const s of extras) symById.set(s.id, s);
|
|
5160
6455
|
}
|
|
@@ -5167,81 +6462,6 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
5167
6462
|
return { nodes, edges };
|
|
5168
6463
|
}
|
|
5169
6464
|
|
|
5170
|
-
// src/codebase-index/vector-search.ts
|
|
5171
|
-
var RRF_K = 60;
|
|
5172
|
-
var VECTOR_DIMENSIONS = 384;
|
|
5173
|
-
var NGRAM_SIZE = 3;
|
|
5174
|
-
function embedText(text) {
|
|
5175
|
-
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
5176
|
-
const normalized = text.toLowerCase().trim();
|
|
5177
|
-
if (normalized.length < NGRAM_SIZE) {
|
|
5178
|
-
const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
|
|
5179
|
-
for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
|
|
5180
|
-
const ngram = padded.slice(i, i + NGRAM_SIZE);
|
|
5181
|
-
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5182
|
-
vec[bucket] += 1;
|
|
5183
|
-
}
|
|
5184
|
-
} else {
|
|
5185
|
-
for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
|
|
5186
|
-
const ngram = normalized.slice(i, i + NGRAM_SIZE);
|
|
5187
|
-
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5188
|
-
vec[bucket] += 1;
|
|
5189
|
-
}
|
|
5190
|
-
}
|
|
5191
|
-
let norm = 0;
|
|
5192
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5193
|
-
norm += vec[i] * vec[i];
|
|
5194
|
-
}
|
|
5195
|
-
norm = Math.sqrt(norm);
|
|
5196
|
-
if (norm > 0) {
|
|
5197
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5198
|
-
vec[i] /= norm;
|
|
5199
|
-
}
|
|
5200
|
-
}
|
|
5201
|
-
return vec;
|
|
5202
|
-
}
|
|
5203
|
-
function hashNgram(str) {
|
|
5204
|
-
let hash = 2166136261;
|
|
5205
|
-
for (let i = 0; i < str.length; i++) {
|
|
5206
|
-
hash ^= str.charCodeAt(i);
|
|
5207
|
-
hash = Math.imul(hash, 16777619);
|
|
5208
|
-
}
|
|
5209
|
-
return hash >>> 0;
|
|
5210
|
-
}
|
|
5211
|
-
function cosineSimilarity(a, b) {
|
|
5212
|
-
let dot = 0;
|
|
5213
|
-
const len = Math.min(a.length, b.length);
|
|
5214
|
-
for (let i = 0; i < len; i++) {
|
|
5215
|
-
dot += a[i] * b[i];
|
|
5216
|
-
}
|
|
5217
|
-
return dot;
|
|
5218
|
-
}
|
|
5219
|
-
function encodeVector(vec) {
|
|
5220
|
-
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
5221
|
-
}
|
|
5222
|
-
function decodeVector(buf) {
|
|
5223
|
-
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
5224
|
-
const copy = new Float32Array(buf.byteLength / 4);
|
|
5225
|
-
for (let i = 0; i < copy.length; i++) {
|
|
5226
|
-
copy[i] = view.getFloat32(i * 4, true);
|
|
5227
|
-
}
|
|
5228
|
-
return copy;
|
|
5229
|
-
}
|
|
5230
|
-
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
5231
|
-
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
5232
|
-
const scored = [];
|
|
5233
|
-
for (const id of allIds) {
|
|
5234
|
-
const bm25Rank = bm25Ranks.get(id);
|
|
5235
|
-
const vecRank = vectorRanks.get(id);
|
|
5236
|
-
let score = 0;
|
|
5237
|
-
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
5238
|
-
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
5239
|
-
scored.push([id, score]);
|
|
5240
|
-
}
|
|
5241
|
-
scored.sort((a, b) => b[1] - a[1]);
|
|
5242
|
-
return scored;
|
|
5243
|
-
}
|
|
5244
|
-
|
|
5245
6465
|
// src/codebase-index/writer-mutations.ts
|
|
5246
6466
|
function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvailable, allocateSymbolIds, invalidateIncomingRefsForFiles, resolveRefsForNamesUnsafe2, entries, options = {}) {
|
|
5247
6467
|
if (entries.length === 0 && (options.deleteForFiles?.length ?? 0) === 0) {
|
|
@@ -5253,24 +6473,29 @@ function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvail
|
|
|
5253
6473
|
for (const ref of entry.refs) affectedNames.add(ref.toName);
|
|
5254
6474
|
}
|
|
5255
6475
|
if (options.deleteForFiles && options.deleteForFiles.length > 0) {
|
|
5256
|
-
const placeholders = options.deleteForFiles.map(() => "?").join(",");
|
|
5257
6476
|
for (const name of invalidateIncomingRefsForFiles(options.deleteForFiles)) {
|
|
5258
6477
|
affectedNames.add(name);
|
|
5259
6478
|
}
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
6479
|
+
let cursor = 0;
|
|
6480
|
+
for (const take of inListChunks(options.deleteForFiles.length, Math.floor(maxSqlVars / 4))) {
|
|
6481
|
+
const bucket = padToInBucket(options.deleteForFiles.slice(cursor, cursor + take));
|
|
6482
|
+
cursor += take;
|
|
6483
|
+
const ph = placeholders(bucket.length);
|
|
6484
|
+
if (ftsAvailable) {
|
|
6485
|
+
stmtFn(
|
|
6486
|
+
`DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
6487
|
+
).run(...bucket);
|
|
6488
|
+
}
|
|
6489
|
+
if (vectorsAvailable) {
|
|
6490
|
+
stmtFn(
|
|
6491
|
+
`DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
6492
|
+
).run(...bucket);
|
|
6493
|
+
}
|
|
5266
6494
|
stmtFn(
|
|
5267
|
-
`DELETE FROM
|
|
5268
|
-
).run(...
|
|
6495
|
+
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
6496
|
+
).run(...bucket);
|
|
6497
|
+
stmtFn(`DELETE FROM symbols WHERE file IN (${ph})`).run(...bucket);
|
|
5269
6498
|
}
|
|
5270
|
-
stmtFn(
|
|
5271
|
-
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
5272
|
-
).run(...options.deleteForFiles);
|
|
5273
|
-
stmtFn(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
|
|
5274
6499
|
}
|
|
5275
6500
|
const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
|
|
5276
6501
|
let nextId = allocateSymbolIds(totalSymbols);
|
|
@@ -5302,12 +6527,14 @@ function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvail
|
|
|
5302
6527
|
text: buildIndexableText(s.name, s.signature, s.docComment)
|
|
5303
6528
|
});
|
|
5304
6529
|
}
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
6530
|
+
if (vectorsAvailable) {
|
|
6531
|
+
vectorRows.push({
|
|
6532
|
+
id,
|
|
6533
|
+
vector: encodeVector(
|
|
6534
|
+
embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
|
|
6535
|
+
)
|
|
6536
|
+
});
|
|
6537
|
+
}
|
|
5311
6538
|
const inserted = { ...s, id };
|
|
5312
6539
|
allInserted.push(inserted);
|
|
5313
6540
|
insertedForEntry.push(inserted);
|
|
@@ -5399,9 +6626,8 @@ var CORE_TABLES_SQL = `
|
|
|
5399
6626
|
signature TEXT NOT NULL DEFAULT '',
|
|
5400
6627
|
doc_comment TEXT NOT NULL DEFAULT '',
|
|
5401
6628
|
scope TEXT NOT NULL DEFAULT '',
|
|
5402
|
-
text TEXT NOT NULL DEFAULT ''
|
|
5403
|
-
|
|
5404
|
-
);
|
|
6629
|
+
text TEXT NOT NULL DEFAULT ''
|
|
6630
|
+
);
|
|
5405
6631
|
`;
|
|
5406
6632
|
var FILE_INDEX_SQL = [
|
|
5407
6633
|
"CREATE INDEX IF NOT EXISTS idx_f_package ON files(package)"
|
|
@@ -5412,7 +6638,6 @@ var SYMBOL_INDEX_SQL = [
|
|
|
5412
6638
|
"CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)",
|
|
5413
6639
|
"CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)",
|
|
5414
6640
|
"CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)",
|
|
5415
|
-
"CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)",
|
|
5416
6641
|
"CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)"
|
|
5417
6642
|
];
|
|
5418
6643
|
var REFS_TABLE_SQL = `
|
|
@@ -5487,11 +6712,12 @@ function getUnresolvedImportsWithStatement(stmtFn, maxSqlVars, onlyFiles) {
|
|
|
5487
6712
|
return stmtFn(base).all();
|
|
5488
6713
|
}
|
|
5489
6714
|
const out = [];
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
const
|
|
6715
|
+
let cursor = 0;
|
|
6716
|
+
for (const take of inListChunks(onlyFiles.length, maxSqlVars)) {
|
|
6717
|
+
const chunk = padToInBucket(onlyFiles.slice(cursor, cursor + take));
|
|
6718
|
+
cursor += take;
|
|
5493
6719
|
out.push(
|
|
5494
|
-
...stmtFn(`${base} AND s.file IN (${placeholders})`).all(...chunk)
|
|
6720
|
+
...stmtFn(`${base} AND s.file IN (${placeholders(chunk.length)})`).all(...chunk)
|
|
5495
6721
|
);
|
|
5496
6722
|
}
|
|
5497
6723
|
return out;
|
|
@@ -5562,16 +6788,18 @@ function applyImportResolutionsWithStatement(db, stmtFn, runWithRetry, maxSqlVar
|
|
|
5562
6788
|
)`
|
|
5563
6789
|
);
|
|
5564
6790
|
const chunkSize = Math.max(1, Math.floor(maxSqlVars / 4));
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
const
|
|
6791
|
+
let cursor = 0;
|
|
6792
|
+
for (const take of ladderChunkSizes(resolutions.length, chunkSize)) {
|
|
6793
|
+
const chunk = resolutions.slice(cursor, cursor + take);
|
|
6794
|
+
cursor += take;
|
|
6795
|
+
const valuesPh = chunk.map(() => "(?, ?, ?, ?)").join(", ");
|
|
5568
6796
|
const binds = [];
|
|
5569
6797
|
for (const entry of chunk) {
|
|
5570
6798
|
binds.push(entry.fromFile, entry.lang, entry.module, entry.toFile);
|
|
5571
6799
|
}
|
|
5572
6800
|
stmtFn(
|
|
5573
6801
|
`INSERT INTO temp.import_resolution(from_file, lang, module, to_file)
|
|
5574
|
-
VALUES ${
|
|
6802
|
+
VALUES ${valuesPh}`
|
|
5575
6803
|
).run(...binds);
|
|
5576
6804
|
}
|
|
5577
6805
|
db.exec(
|
|
@@ -5608,9 +6836,11 @@ function resolveRefsForNamesUnsafe(stmtFn, maxSqlVars, names) {
|
|
|
5608
6836
|
const list = [...names].filter((name) => name.length > 0);
|
|
5609
6837
|
if (list.length === 0) return 0;
|
|
5610
6838
|
let total = 0;
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
const
|
|
6839
|
+
let cursor = 0;
|
|
6840
|
+
for (const take of inListChunks(list.length, maxSqlVars)) {
|
|
6841
|
+
const chunk = padToInBucket(list.slice(cursor, cursor + take));
|
|
6842
|
+
cursor += take;
|
|
6843
|
+
const ph = placeholders(chunk.length);
|
|
5614
6844
|
try {
|
|
5615
6845
|
const result = stmtFn(
|
|
5616
6846
|
`UPDATE refs
|
|
@@ -5619,16 +6849,16 @@ function resolveRefsForNamesUnsafe(stmtFn, maxSqlVars, names) {
|
|
|
5619
6849
|
SELECT sym.name AS name, lf.family AS family, MIN(sym.id) AS id
|
|
5620
6850
|
FROM symbols sym
|
|
5621
6851
|
JOIN lang_family lf ON lf.lang = sym.lang
|
|
5622
|
-
WHERE sym.name IN (${
|
|
6852
|
+
WHERE sym.name IN (${ph})
|
|
5623
6853
|
GROUP BY sym.name, lf.family
|
|
5624
6854
|
UNION ALL
|
|
5625
6855
|
SELECT sym.name AS name, '${LANG_FAMILY_WILDCARD}' AS family, MIN(sym.id) AS id
|
|
5626
6856
|
FROM symbols sym
|
|
5627
|
-
WHERE sym.name IN (${
|
|
6857
|
+
WHERE sym.name IN (${ph})
|
|
5628
6858
|
GROUP BY sym.name
|
|
5629
6859
|
) AS s,
|
|
5630
6860
|
lang_family AS rf
|
|
5631
|
-
WHERE refs.to_name IN (${
|
|
6861
|
+
WHERE refs.to_name IN (${ph})
|
|
5632
6862
|
AND rf.lang = refs.lang
|
|
5633
6863
|
AND s.name = refs.to_name
|
|
5634
6864
|
AND s.family = rf.family`
|
|
@@ -5640,7 +6870,7 @@ function resolveRefsForNamesUnsafe(stmtFn, maxSqlVars, names) {
|
|
|
5640
6870
|
SELECT sym.id FROM symbols sym
|
|
5641
6871
|
WHERE sym.name = refs.to_name AND ${FAMILY_MATCH_SQL}
|
|
5642
6872
|
ORDER BY sym.id LIMIT 1
|
|
5643
|
-
) WHERE refs.to_name IN (${
|
|
6873
|
+
) WHERE refs.to_name IN (${ph})
|
|
5644
6874
|
AND EXISTS (
|
|
5645
6875
|
SELECT 1 FROM symbols sym
|
|
5646
6876
|
WHERE sym.name = refs.to_name AND ${FAMILY_MATCH_SQL}
|
|
@@ -5697,7 +6927,7 @@ function buildWriterSearchWhere(query, filter) {
|
|
|
5697
6927
|
const conditions = [];
|
|
5698
6928
|
const values = [];
|
|
5699
6929
|
let effectiveKind = filter?.kind;
|
|
5700
|
-
if (filter?.lspKind
|
|
6930
|
+
if (filter?.lspKind != null) {
|
|
5701
6931
|
const mapped = lspKindToInternalKind(filter.lspKind);
|
|
5702
6932
|
if (mapped !== null) {
|
|
5703
6933
|
effectiveKind = mapped;
|
|
@@ -5776,7 +7006,7 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
|
|
|
5776
7006
|
);
|
|
5777
7007
|
}
|
|
5778
7008
|
let effectiveKind = filter?.kind;
|
|
5779
|
-
if (filter?.lspKind
|
|
7009
|
+
if (filter?.lspKind != null) {
|
|
5780
7010
|
const mapped = lspKindToInternalKind(filter.lspKind);
|
|
5781
7011
|
if (mapped === null) return { results: [], total: 0 };
|
|
5782
7012
|
effectiveKind = mapped;
|
|
@@ -5813,15 +7043,14 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
|
|
|
5813
7043
|
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
5814
7044
|
}
|
|
5815
7045
|
const where = conditions.join(" AND ");
|
|
5816
|
-
const countRows = stmtFn(
|
|
5817
|
-
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
5818
|
-
).all(...values);
|
|
5819
|
-
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
5820
|
-
if (total === 0) return { results: [], total: 0 };
|
|
5821
7046
|
const bm25Rows = stmtFn(
|
|
5822
7047
|
`SELECT s.id, s.lang, s.kind, s.name, s.file, s.line, s.col, s.signature, s.doc_comment,
|
|
5823
7048
|
-bm25(symbols_fts) AS score,
|
|
5824
|
-
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
7049
|
+
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet,
|
|
7050
|
+
-- Keep this uncorrelated: referencing outer columns turns it into
|
|
7051
|
+
-- a per-row subquery and defeats the one-count-per-statement win.
|
|
7052
|
+
(SELECT COUNT(*) FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
7053
|
+
WHERE ${where}) AS total_count
|
|
5825
7054
|
FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
5826
7055
|
WHERE ${where}
|
|
5827
7056
|
ORDER BY
|
|
@@ -5830,13 +7059,15 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
|
|
|
5830
7059
|
ELSE 2 END,
|
|
5831
7060
|
bm25(symbols_fts), lower(s.name), s.file, s.line, s.col, s.id
|
|
5832
7061
|
LIMIT ?`
|
|
5833
|
-
).all(...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
5834
|
-
if (
|
|
7062
|
+
).all(...values, ...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
7063
|
+
if (bm25Rows.length === 0) return { results: [], total: 0 };
|
|
7064
|
+
const total = Number(bm25Rows[0]?.total_count ?? 0);
|
|
7065
|
+
if (vectorsAvailable) {
|
|
5835
7066
|
const queryVec = embedText(query);
|
|
5836
7067
|
const candidateIds = bm25Rows.map((r) => r.id);
|
|
5837
|
-
const
|
|
7068
|
+
const placeholders2 = candidateIds.map(() => "?").join(",");
|
|
5838
7069
|
const vecRows = stmtFn(
|
|
5839
|
-
`SELECT sv.symbol_id, sv.vector FROM symbol_vectors sv WHERE sv.symbol_id IN (${
|
|
7070
|
+
`SELECT sv.symbol_id, sv.vector FROM symbol_vectors sv WHERE sv.symbol_id IN (${placeholders2})`
|
|
5840
7071
|
).all(...candidateIds);
|
|
5841
7072
|
const vecScores = vecRows.map((r) => ({
|
|
5842
7073
|
id: r.symbol_id,
|
|
@@ -6024,9 +7255,9 @@ var IndexStore = class _IndexStore {
|
|
|
6024
7255
|
}
|
|
6025
7256
|
constructor(projectRoot, opts = {}) {
|
|
6026
7257
|
this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
|
|
6027
|
-
|
|
7258
|
+
fs9.mkdirSync(this.indexDir, { recursive: true });
|
|
6028
7259
|
const Database = loadDatabaseSync();
|
|
6029
|
-
this.db = new Database(
|
|
7260
|
+
this.db = new Database(path14.join(this.indexDir, DB_FILE2));
|
|
6030
7261
|
applyIndexStorePragmas(this.db);
|
|
6031
7262
|
this.initSchema();
|
|
6032
7263
|
}
|
|
@@ -6155,7 +7386,11 @@ var IndexStore = class _IndexStore {
|
|
|
6155
7386
|
);
|
|
6156
7387
|
if (symbolCount !== ftsCount) {
|
|
6157
7388
|
this.db.exec("DELETE FROM symbols_fts");
|
|
6158
|
-
if (
|
|
7389
|
+
if (vectorEmbeddingEnabled() && this.stmt(
|
|
7390
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='symbol_vectors'"
|
|
7391
|
+
).get() !== void 0) {
|
|
7392
|
+
this.db.exec("DELETE FROM symbol_vectors");
|
|
7393
|
+
}
|
|
6159
7394
|
const rows = this.stmt(
|
|
6160
7395
|
"SELECT id, name, signature, doc_comment FROM symbols ORDER BY id"
|
|
6161
7396
|
).all();
|
|
@@ -6174,8 +7409,13 @@ var IndexStore = class _IndexStore {
|
|
|
6174
7409
|
this.ftsAvailable = false;
|
|
6175
7410
|
}
|
|
6176
7411
|
try {
|
|
6177
|
-
|
|
6178
|
-
|
|
7412
|
+
if (vectorEmbeddingEnabled()) {
|
|
7413
|
+
this.db.exec(SYMBOL_VECTORS_TABLE_SQL);
|
|
7414
|
+
this.vectorsAvailable = true;
|
|
7415
|
+
} else {
|
|
7416
|
+
this.db.exec("DROP TABLE IF EXISTS symbol_vectors");
|
|
7417
|
+
this.vectorsAvailable = false;
|
|
7418
|
+
}
|
|
6179
7419
|
} catch {
|
|
6180
7420
|
this.vectorsAvailable = false;
|
|
6181
7421
|
}
|
|
@@ -6210,14 +7450,22 @@ var IndexStore = class _IndexStore {
|
|
|
6210
7450
|
}
|
|
6211
7451
|
invalidateIncomingRefsForFiles(files) {
|
|
6212
7452
|
if (files.length === 0) return /* @__PURE__ */ new Set();
|
|
6213
|
-
const
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
7453
|
+
const names = [];
|
|
7454
|
+
let cursor = 0;
|
|
7455
|
+
for (const take of inListChunks(files.length, _IndexStore.MAX_SQL_VARS)) {
|
|
7456
|
+
const bucket = padToInBucket(files.slice(cursor, cursor + take));
|
|
7457
|
+
cursor += take;
|
|
7458
|
+
const ph = placeholders(bucket.length);
|
|
7459
|
+
for (const row of this.stmt(`SELECT DISTINCT name FROM symbols WHERE file IN (${ph})`).all(
|
|
7460
|
+
...bucket
|
|
7461
|
+
)) {
|
|
7462
|
+
names.push(row.name);
|
|
7463
|
+
}
|
|
7464
|
+
this.stmt(
|
|
7465
|
+
`UPDATE refs SET to_id = NULL
|
|
7466
|
+
WHERE to_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
7467
|
+
).run(...bucket);
|
|
7468
|
+
}
|
|
6221
7469
|
return new Set(names);
|
|
6222
7470
|
}
|
|
6223
7471
|
resolveRefsForNamesUnsafe(names) {
|
|
@@ -6251,6 +7499,14 @@ var IndexStore = class _IndexStore {
|
|
|
6251
7499
|
if (this.ftsAvailable) {
|
|
6252
7500
|
ftsRows.push({ id, text: buildIndexableText(s.name, s.signature, s.docComment) });
|
|
6253
7501
|
}
|
|
7502
|
+
if (this.vectorsAvailable) {
|
|
7503
|
+
vectorRows.push({
|
|
7504
|
+
id,
|
|
7505
|
+
vector: encodeVector(
|
|
7506
|
+
embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
|
|
7507
|
+
)
|
|
7508
|
+
});
|
|
7509
|
+
}
|
|
6254
7510
|
result.push({ ...s, id });
|
|
6255
7511
|
}
|
|
6256
7512
|
bulkInsertSymbolsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, bulk);
|
|
@@ -6283,15 +7539,15 @@ var IndexStore = class _IndexStore {
|
|
|
6283
7539
|
const affectedNames = this.invalidateIncomingRefsForFiles([file]);
|
|
6284
7540
|
if (this.ftsAvailable) {
|
|
6285
7541
|
this.stmt(
|
|
6286
|
-
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE
|
|
7542
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6287
7543
|
).run(file);
|
|
6288
7544
|
}
|
|
6289
7545
|
if (this.vectorsAvailable) {
|
|
6290
7546
|
this.stmt(
|
|
6291
|
-
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE
|
|
7547
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6292
7548
|
).run(file);
|
|
6293
7549
|
}
|
|
6294
|
-
this.stmt("DELETE FROM symbols WHERE
|
|
7550
|
+
this.stmt("DELETE FROM symbols WHERE file = ?").run(file);
|
|
6295
7551
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
6296
7552
|
this.commitWriteTransaction(ownsTransaction);
|
|
6297
7553
|
} catch (error) {
|
|
@@ -6308,18 +7564,18 @@ var IndexStore = class _IndexStore {
|
|
|
6308
7564
|
const affectedNames = this.invalidateIncomingRefsForFiles([file]);
|
|
6309
7565
|
if (this.ftsAvailable) {
|
|
6310
7566
|
this.stmt(
|
|
6311
|
-
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE
|
|
7567
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6312
7568
|
).run(file);
|
|
6313
7569
|
}
|
|
6314
7570
|
if (this.vectorsAvailable) {
|
|
6315
7571
|
this.stmt(
|
|
6316
|
-
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE
|
|
7572
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6317
7573
|
).run(file);
|
|
6318
7574
|
}
|
|
6319
|
-
this.stmt(
|
|
6320
|
-
|
|
6321
|
-
)
|
|
6322
|
-
this.stmt("DELETE FROM symbols WHERE
|
|
7575
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
|
|
7576
|
+
file
|
|
7577
|
+
);
|
|
7578
|
+
this.stmt("DELETE FROM symbols WHERE file = ?").run(file);
|
|
6323
7579
|
this.stmt("DELETE FROM files WHERE file = ?").run(file);
|
|
6324
7580
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
6325
7581
|
this.commitWriteTransaction(ownsTransaction);
|
|
@@ -6423,6 +7679,10 @@ var IndexStore = class _IndexStore {
|
|
|
6423
7679
|
getStats() {
|
|
6424
7680
|
return getStatsWithStatement((sql) => this.stmt(sql), this.indexDir);
|
|
6425
7681
|
}
|
|
7682
|
+
/** P2.5: minimal summary for search-response piggyback (see writer-admin). */
|
|
7683
|
+
getIndexSummary() {
|
|
7684
|
+
return getIndexSummaryWithStatement((sql) => this.stmt(sql));
|
|
7685
|
+
}
|
|
6426
7686
|
setLastIndexed(ts2) {
|
|
6427
7687
|
this.runWithRetry(() => {
|
|
6428
7688
|
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(
|
|
@@ -6524,18 +7784,18 @@ var IndexStore = class _IndexStore {
|
|
|
6524
7784
|
const affectedNames = this.invalidateIncomingRefsForFiles([meta.file]);
|
|
6525
7785
|
if (this.ftsAvailable) {
|
|
6526
7786
|
this.stmt(
|
|
6527
|
-
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE
|
|
7787
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6528
7788
|
).run(meta.file);
|
|
6529
7789
|
}
|
|
6530
7790
|
if (this.vectorsAvailable) {
|
|
6531
7791
|
this.stmt(
|
|
6532
|
-
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE
|
|
7792
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6533
7793
|
).run(meta.file);
|
|
6534
7794
|
}
|
|
6535
|
-
this.stmt(
|
|
6536
|
-
|
|
6537
|
-
)
|
|
6538
|
-
this.stmt("DELETE FROM symbols WHERE
|
|
7795
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
|
|
7796
|
+
meta.file
|
|
7797
|
+
);
|
|
7798
|
+
this.stmt("DELETE FROM symbols WHERE file = ?").run(meta.file);
|
|
6539
7799
|
this.stmt(
|
|
6540
7800
|
`INSERT INTO files(file, lang, mtime_ms, content_hash, symbol_count, last_indexed)
|
|
6541
7801
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
@@ -6567,6 +7827,27 @@ var IndexStore = class _IndexStore {
|
|
|
6567
7827
|
} catch {
|
|
6568
7828
|
}
|
|
6569
7829
|
}
|
|
7830
|
+
/**
|
|
7831
|
+
* P4.14: best-effort WAL checkpoint for idle-time maintenance.
|
|
7832
|
+
*
|
|
7833
|
+
* `wal_autocheckpoint` is PASSIVE and only attempts work after a COMMIT —
|
|
7834
|
+
* once writes stop, nothing fires again, so the WAL keeps whatever frames
|
|
7835
|
+
* the last burst left. This probes with PASSIVE first (never blocks; busy=1
|
|
7836
|
+
* means readers still hold WAL snapshots) and only issues the TRUNCATE —
|
|
7837
|
+
* which resets index.db-wal to zero bytes — when the checkpointer can
|
|
7838
|
+
* proceed immediately. Callers run this on the daemon's single thread, so
|
|
7839
|
+
* never wait on readers here: busy means "retry at the next idle window".
|
|
7840
|
+
*/
|
|
7841
|
+
checkpointWal() {
|
|
7842
|
+
try {
|
|
7843
|
+
const probe = this.db.prepare("PRAGMA wal_checkpoint(PASSIVE)").get();
|
|
7844
|
+
if (Number(probe?.busy ?? 1) !== 0) return false;
|
|
7845
|
+
const done = this.db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
|
|
7846
|
+
return Number(done?.busy ?? 1) === 0;
|
|
7847
|
+
} catch {
|
|
7848
|
+
return false;
|
|
7849
|
+
}
|
|
7850
|
+
}
|
|
6570
7851
|
compactIfNeeded(options = {}) {
|
|
6571
7852
|
const minBytes = options.minBytes ?? 256 * 1024 * 1024;
|
|
6572
7853
|
const minFreeRatio = options.minFreeRatio ?? 0.35;
|
|
@@ -6652,7 +7933,9 @@ function resolveParallelBatch() {
|
|
|
6652
7933
|
return indexParallelBatchSize(availableParallelism());
|
|
6653
7934
|
}
|
|
6654
7935
|
function shouldUseParserWorkerPool(candidateFileCount, parseBatchCount) {
|
|
6655
|
-
|
|
7936
|
+
const threshold = resolveWorkerPoolThreshold();
|
|
7937
|
+
if (threshold === 0) return false;
|
|
7938
|
+
return !isFrugalPerf() && candidateFileCount >= threshold && parseBatchCount > 1;
|
|
6656
7939
|
}
|
|
6657
7940
|
function yieldEventLoop() {
|
|
6658
7941
|
return new Promise((resolve4) => setImmediate(resolve4));
|
|
@@ -6675,15 +7958,15 @@ var IndexSourceChangedError = class extends Error {
|
|
|
6675
7958
|
name = "IndexSourceChangedError";
|
|
6676
7959
|
};
|
|
6677
7960
|
function isWithinProject(projectRoot, file) {
|
|
6678
|
-
const rel =
|
|
6679
|
-
return rel !== "" && !rel.startsWith(`..${
|
|
7961
|
+
const rel = path15.relative(projectRoot, file);
|
|
7962
|
+
return rel !== "" && !rel.startsWith(`..${path15.sep}`) && rel !== ".." && !path15.isAbsolute(rel);
|
|
6680
7963
|
}
|
|
6681
7964
|
function isMissingPathError(err) {
|
|
6682
7965
|
const code = err?.code;
|
|
6683
7966
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
6684
7967
|
}
|
|
6685
7968
|
function normalizeComparablePath(value) {
|
|
6686
|
-
const resolved =
|
|
7969
|
+
const resolved = path15.resolve(value);
|
|
6687
7970
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
6688
7971
|
}
|
|
6689
7972
|
function gitOutput(projectRoot, args) {
|
|
@@ -6729,24 +8012,24 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
6729
8012
|
const record = statusRecords[i];
|
|
6730
8013
|
if (!record) continue;
|
|
6731
8014
|
const status = record.slice(0, 2);
|
|
6732
|
-
const changedPath =
|
|
8015
|
+
const changedPath = path15.resolve(projectRoot, record.slice(3));
|
|
6733
8016
|
dirty.add(changedPath);
|
|
6734
8017
|
if (status.includes("D")) deleted.add(changedPath);
|
|
6735
8018
|
if (status.includes("R") || status.includes("C")) {
|
|
6736
8019
|
const source = statusRecords[++i];
|
|
6737
|
-
if (source) dirty.add(
|
|
8020
|
+
if (source) dirty.add(path15.resolve(projectRoot, source));
|
|
6738
8021
|
}
|
|
6739
8022
|
}
|
|
6740
8023
|
const files = [];
|
|
6741
8024
|
for (const relative3 of output.toString("utf8").split("\0")) {
|
|
6742
8025
|
if (!relative3) continue;
|
|
6743
8026
|
const portable = relative3.replace(/\\/g, "/");
|
|
6744
|
-
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(
|
|
8027
|
+
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path15.posix.basename(portable))) {
|
|
6745
8028
|
continue;
|
|
6746
8029
|
}
|
|
6747
|
-
const full =
|
|
8030
|
+
const full = path15.resolve(projectRoot, relative3);
|
|
6748
8031
|
if (deleted.has(full)) continue;
|
|
6749
|
-
const ext =
|
|
8032
|
+
const ext = path15.extname(relative3).toLowerCase();
|
|
6750
8033
|
if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
|
|
6751
8034
|
}
|
|
6752
8035
|
const snapshot = createHash2("sha256").update(stagedOutput).update("\0").update(statusOutput);
|
|
@@ -6754,7 +8037,7 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
6754
8037
|
for (const dirtyFile of [...dirty].sort()) {
|
|
6755
8038
|
if (!indexedFiles.has(dirtyFile) || deleted.has(dirtyFile)) continue;
|
|
6756
8039
|
snapshot.update("\0").update(dirtyFile).update("\0");
|
|
6757
|
-
snapshot.update(xxhash64String(await
|
|
8040
|
+
snapshot.update(xxhash64String(await fs10.readFile(dirtyFile, "utf8")));
|
|
6758
8041
|
}
|
|
6759
8042
|
return {
|
|
6760
8043
|
files,
|
|
@@ -6790,7 +8073,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
6790
8073
|
}
|
|
6791
8074
|
let entries;
|
|
6792
8075
|
try {
|
|
6793
|
-
entries = await
|
|
8076
|
+
entries = await fs10.readdir(dir, { withFileTypes: true });
|
|
6794
8077
|
} catch (err) {
|
|
6795
8078
|
complete = false;
|
|
6796
8079
|
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -6799,14 +8082,14 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
6799
8082
|
dirCount++;
|
|
6800
8083
|
for (const e of entries) {
|
|
6801
8084
|
if (ignoreSet.has(e.name)) continue;
|
|
6802
|
-
const full =
|
|
6803
|
-
const rel =
|
|
8085
|
+
const full = path15.join(dir, e.name);
|
|
8086
|
+
const rel = path15.relative(projectRoot, full).replace(/\\/g, "/");
|
|
6804
8087
|
if (e.isDirectory()) {
|
|
6805
8088
|
if (isGitIgnored(rel, true)) continue;
|
|
6806
8089
|
await walk(full);
|
|
6807
8090
|
} else if (e.isFile()) {
|
|
6808
8091
|
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
6809
|
-
const ext =
|
|
8092
|
+
const ext = path15.extname(e.name).toLowerCase();
|
|
6810
8093
|
if (indexableExts.has(ext) || detectLang(full) !== null) {
|
|
6811
8094
|
results.push(full);
|
|
6812
8095
|
}
|
|
@@ -6892,10 +8175,10 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6892
8175
|
let trustedUnchanged;
|
|
6893
8176
|
let discoverySnapshotKey;
|
|
6894
8177
|
if (opts.files && opts.files.length > 0) {
|
|
6895
|
-
files = opts.files.map((f) =>
|
|
8178
|
+
files = opts.files.map((f) => path15.resolve(projectRoot, f)).filter((f) => {
|
|
6896
8179
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
6897
|
-
const rel =
|
|
6898
|
-
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(
|
|
8180
|
+
const rel = path15.relative(projectRoot, f).replace(/\\/g, "/");
|
|
8181
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path15.basename(f)) && !isGitIgnored(rel, false);
|
|
6899
8182
|
});
|
|
6900
8183
|
} else {
|
|
6901
8184
|
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
@@ -6928,7 +8211,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6928
8211
|
if (!meta || !trustedUnchanged.has(file)) return true;
|
|
6929
8212
|
langStats[meta.lang] = (langStats[meta.lang] ?? 0) + meta.symbolCount;
|
|
6930
8213
|
symbolsIndexed += meta.symbolCount;
|
|
6931
|
-
filesIndexed++;
|
|
6932
8214
|
filesSkipped++;
|
|
6933
8215
|
filesPreSkipped++;
|
|
6934
8216
|
return false;
|
|
@@ -6957,7 +8239,7 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6957
8239
|
async (file) => {
|
|
6958
8240
|
let stat3;
|
|
6959
8241
|
try {
|
|
6960
|
-
stat3 = await
|
|
8242
|
+
stat3 = await fs10.stat(file, statOpts);
|
|
6961
8243
|
} catch (e) {
|
|
6962
8244
|
if (isAbortError(e)) throw e;
|
|
6963
8245
|
return {
|
|
@@ -6984,7 +8266,7 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6984
8266
|
const meta = existingMeta.get(file);
|
|
6985
8267
|
let content;
|
|
6986
8268
|
try {
|
|
6987
|
-
content = await
|
|
8269
|
+
content = await fs10.readFile(file, { encoding: "utf8", signal });
|
|
6988
8270
|
} catch (e) {
|
|
6989
8271
|
if (isAbortError(e)) throw e;
|
|
6990
8272
|
return {
|
|
@@ -7049,22 +8331,19 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7049
8331
|
}
|
|
7050
8332
|
}
|
|
7051
8333
|
if (!pool) {
|
|
7052
|
-
await
|
|
7053
|
-
toParse.map(
|
|
7054
|
-
try {
|
|
7055
|
-
const parsed = await parseFileContent(item.file, item.content, item.lang);
|
|
7056
|
-
const settled = statReadParse[item.index];
|
|
7057
|
-
if (settled.status === "fulfilled") {
|
|
7058
|
-
settled.value.parsed = parsed;
|
|
7059
|
-
}
|
|
7060
|
-
} catch (e) {
|
|
7061
|
-
const settled = statReadParse[item.index];
|
|
7062
|
-
if (settled.status === "fulfilled") {
|
|
7063
|
-
settled.value.error = `parse error: ${e instanceof Error ? e.message : String(e)}`;
|
|
7064
|
-
}
|
|
7065
|
-
}
|
|
7066
|
-
})
|
|
8334
|
+
const parsedAll = await parseFilesContent(
|
|
8335
|
+
toParse.map((p) => ({ file: p.file, content: p.content, lang: p.lang }))
|
|
7067
8336
|
);
|
|
8337
|
+
for (let pi2 = 0; pi2 < parsedAll.length && pi2 < toParse.length; pi2++) {
|
|
8338
|
+
const settled = statReadParse[toParse[pi2].index];
|
|
8339
|
+
if (settled.status !== "fulfilled") continue;
|
|
8340
|
+
const slot = parsedAll[pi2];
|
|
8341
|
+
if (slot.result) {
|
|
8342
|
+
settled.value.parsed = slot.result;
|
|
8343
|
+
} else {
|
|
8344
|
+
settled.value.error = `parse error: ${slot.error ?? `no result for ${toParse[pi2].file}`}`;
|
|
8345
|
+
}
|
|
8346
|
+
}
|
|
7068
8347
|
}
|
|
7069
8348
|
}
|
|
7070
8349
|
const batchEntries = [];
|
|
@@ -7090,7 +8369,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7090
8369
|
if (result.skippedMeta) {
|
|
7091
8370
|
langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
|
|
7092
8371
|
symbolsIndexed += result.skippedMeta.symbolCount;
|
|
7093
|
-
filesIndexed++;
|
|
7094
8372
|
filesSkipped++;
|
|
7095
8373
|
const stored = existingMeta.get(file);
|
|
7096
8374
|
if (stored && stored.mtimeMs !== result.skippedMeta.mtimeMs) {
|
|
@@ -7115,7 +8393,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7115
8393
|
lastIndexed: Date.now(),
|
|
7116
8394
|
contentHash: result.contentHash ?? ""
|
|
7117
8395
|
});
|
|
7118
|
-
filesIndexed++;
|
|
7119
8396
|
filesEmpty++;
|
|
7120
8397
|
}
|
|
7121
8398
|
continue;
|
|
@@ -7129,7 +8406,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7129
8406
|
lastIndexed: Date.now(),
|
|
7130
8407
|
contentHash: result.contentHash ?? ""
|
|
7131
8408
|
});
|
|
7132
|
-
filesIndexed++;
|
|
7133
8409
|
filesEmpty++;
|
|
7134
8410
|
continue;
|
|
7135
8411
|
}
|
|
@@ -7259,7 +8535,7 @@ async function indexService(args, hooks = {}) {
|
|
|
7259
8535
|
function searchService(args) {
|
|
7260
8536
|
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
7261
8537
|
try {
|
|
7262
|
-
|
|
8538
|
+
const result = store.searchRanked(
|
|
7263
8539
|
args.query,
|
|
7264
8540
|
{
|
|
7265
8541
|
kind: args.kind,
|
|
@@ -7269,6 +8545,10 @@ function searchService(args) {
|
|
|
7269
8545
|
},
|
|
7270
8546
|
args.limit
|
|
7271
8547
|
);
|
|
8548
|
+
if (result.total === 0) {
|
|
8549
|
+
return { ...result, indexSummary: store.getIndexSummary() };
|
|
8550
|
+
}
|
|
8551
|
+
return result;
|
|
7272
8552
|
} finally {
|
|
7273
8553
|
indexStorePool.release(store);
|
|
7274
8554
|
}
|
|
@@ -7332,34 +8612,71 @@ function outgoingCallsService(args) {
|
|
|
7332
8612
|
init_languages();
|
|
7333
8613
|
|
|
7334
8614
|
// src/codebase-index/project-server-client.ts
|
|
7335
|
-
import { spawn as
|
|
7336
|
-
import * as
|
|
8615
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
8616
|
+
import * as fs13 from "node:fs";
|
|
7337
8617
|
import * as net from "node:net";
|
|
7338
|
-
import { StringDecoder } from "node:string_decoder";
|
|
7339
8618
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
7340
8619
|
|
|
7341
8620
|
// src/codebase-index/binary-frame.ts
|
|
7342
8621
|
import { decode, encode } from "@msgpack/msgpack";
|
|
7343
8622
|
var BINARY_FRAME_MAGIC = 87;
|
|
8623
|
+
var MAX_BINARY_FRAME_BYTES = 256 * 1024 * 1024;
|
|
8624
|
+
var MAX_INBOUND_BINARY_FRAME_BYTES = 64 * 1024 * 1024;
|
|
7344
8625
|
function isBinaryFrame(firstByte) {
|
|
7345
8626
|
return firstByte === BINARY_FRAME_MAGIC;
|
|
7346
8627
|
}
|
|
7347
8628
|
function encodeBinaryFrame(message) {
|
|
7348
|
-
const payload = encode(message);
|
|
8629
|
+
const payload = encode(normalizeUndefined(message));
|
|
7349
8630
|
const header = Buffer.allocUnsafe(5);
|
|
7350
8631
|
header[0] = BINARY_FRAME_MAGIC;
|
|
7351
8632
|
header.writeUInt32BE(payload.length, 1);
|
|
7352
8633
|
return Buffer.concat([header, payload], 5 + payload.length);
|
|
7353
8634
|
}
|
|
8635
|
+
function normalizeUndefined(value) {
|
|
8636
|
+
if (value instanceof Date) {
|
|
8637
|
+
const time = value.getTime();
|
|
8638
|
+
return Number.isNaN(time) ? null : value.toISOString();
|
|
8639
|
+
}
|
|
8640
|
+
if (value instanceof Map) return normalizeUndefined(Object.fromEntries(value));
|
|
8641
|
+
if (value instanceof Set) return normalizeUndefined([...value]);
|
|
8642
|
+
if (value instanceof Error) {
|
|
8643
|
+
return normalizeUndefined({ name: value.name, message: value.message, stack: value.stack });
|
|
8644
|
+
}
|
|
8645
|
+
if (value instanceof RegExp) return String(value);
|
|
8646
|
+
if (value instanceof URL) return value.toJSON();
|
|
8647
|
+
if (Buffer.isBuffer(value)) return { type: "Buffer", data: [...value] };
|
|
8648
|
+
if (Array.isArray(value)) return value.map((entry) => normalizeUndefined(entry));
|
|
8649
|
+
if (!isPlainObject(value)) return value;
|
|
8650
|
+
const out = {};
|
|
8651
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
8652
|
+
if (entry === void 0) continue;
|
|
8653
|
+
out[key] = normalizeUndefined(entry);
|
|
8654
|
+
}
|
|
8655
|
+
return out;
|
|
8656
|
+
}
|
|
8657
|
+
function isPlainObject(value) {
|
|
8658
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
8659
|
+
const proto = Object.getPrototypeOf(value);
|
|
8660
|
+
return proto === Object.prototype || proto === null;
|
|
8661
|
+
}
|
|
7354
8662
|
function decodeBinaryFrame(payload) {
|
|
7355
8663
|
return decode(payload);
|
|
7356
8664
|
}
|
|
8665
|
+
function encodeJsonFrame(message) {
|
|
8666
|
+
return `${JSON.stringify(normalizeUndefined(message))}
|
|
8667
|
+
`;
|
|
8668
|
+
}
|
|
8669
|
+
|
|
8670
|
+
// src/codebase-index/project-server-client-state.ts
|
|
8671
|
+
import * as fs12 from "node:fs";
|
|
8672
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
8673
|
+
import { checkUnixSocketPath } from "@wrongstack/core/utils";
|
|
7357
8674
|
|
|
7358
8675
|
// src/codebase-index/project-server-endpoint.ts
|
|
7359
8676
|
import { createHash as createHash3 } from "node:crypto";
|
|
7360
|
-
import * as
|
|
7361
|
-
import * as
|
|
7362
|
-
import * as
|
|
8677
|
+
import * as fs11 from "node:fs";
|
|
8678
|
+
import * as os4 from "node:os";
|
|
8679
|
+
import * as path16 from "node:path";
|
|
7363
8680
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7364
8681
|
var PROJECT_INDEX_SERVER_PROTOCOL_VERSION = 1;
|
|
7365
8682
|
var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
|
|
@@ -7368,21 +8685,21 @@ var buildIdCache;
|
|
|
7368
8685
|
function projectIndexServerBuildId(entrypoint) {
|
|
7369
8686
|
const href = entrypoint instanceof URL ? entrypoint.href : entrypoint;
|
|
7370
8687
|
const cleanHref = href.split(/[?#]/, 1)[0] ?? href;
|
|
7371
|
-
const file = cleanHref.startsWith("file:") ? fileURLToPath3(cleanHref) :
|
|
8688
|
+
const file = cleanHref.startsWith("file:") ? fileURLToPath3(cleanHref) : path16.resolve(cleanHref);
|
|
7372
8689
|
try {
|
|
7373
|
-
const stat3 =
|
|
8690
|
+
const stat3 = fs11.statSync(file);
|
|
7374
8691
|
if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat3.mtimeMs && buildIdCache.size === stat3.size) {
|
|
7375
8692
|
return buildIdCache.buildId;
|
|
7376
8693
|
}
|
|
7377
|
-
const buildId = createHash3("sha256").update(
|
|
8694
|
+
const buildId = createHash3("sha256").update(fs11.readFileSync(file)).digest("hex").slice(0, 24);
|
|
7378
8695
|
buildIdCache = { file, mtimeMs: stat3.mtimeMs, size: stat3.size, buildId };
|
|
7379
8696
|
return buildId;
|
|
7380
8697
|
} catch {
|
|
7381
|
-
return `unreadable:${
|
|
8698
|
+
return `unreadable:${path16.basename(file)}`;
|
|
7382
8699
|
}
|
|
7383
8700
|
}
|
|
7384
8701
|
function normalizeLocalPath(value) {
|
|
7385
|
-
const resolved =
|
|
8702
|
+
const resolved = path16.resolve(value);
|
|
7386
8703
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
7387
8704
|
}
|
|
7388
8705
|
function projectIndexServerKey(projectRoot, indexDir) {
|
|
@@ -7394,26 +8711,16 @@ function projectIndexServerEndpoint(projectRoot, indexDir) {
|
|
|
7394
8711
|
if (process.platform === "win32") {
|
|
7395
8712
|
return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
|
|
7396
8713
|
}
|
|
7397
|
-
return
|
|
8714
|
+
return path16.join(os4.tmpdir(), PROJECT_INDEX_SERVER_SOCKET_DIR, `${key}.sock`);
|
|
7398
8715
|
}
|
|
7399
8716
|
function projectIndexServerMetadataPath(projectRoot, indexDir) {
|
|
7400
|
-
return
|
|
7401
|
-
|
|
8717
|
+
return path16.join(
|
|
8718
|
+
path16.resolve(resolveIndexDir(projectRoot, indexDir)),
|
|
7402
8719
|
PROJECT_INDEX_SERVER_METADATA_FILE
|
|
7403
8720
|
);
|
|
7404
8721
|
}
|
|
7405
8722
|
|
|
7406
|
-
// src/codebase-index/project-server-protocol.ts
|
|
7407
|
-
var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
|
|
7408
|
-
function encodeProjectServerMessage(message) {
|
|
7409
|
-
return `${JSON.stringify(message)}
|
|
7410
|
-
`;
|
|
7411
|
-
}
|
|
7412
|
-
|
|
7413
8723
|
// src/codebase-index/project-server-client-state.ts
|
|
7414
|
-
import * as fs11 from "node:fs";
|
|
7415
|
-
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
7416
|
-
import { checkUnixSocketPath } from "@wrongstack/core/utils";
|
|
7417
8724
|
var CONNECT_ATTEMPT_TIMEOUT_MS = 750;
|
|
7418
8725
|
var SERVER_START_TIMEOUT_MS = 1e4;
|
|
7419
8726
|
var SERVER_CONTROL_TIMEOUT_MS = 5e3;
|
|
@@ -7444,7 +8751,7 @@ function resolveProjectIndexDaemonAvailability(projectRoot, indexDir) {
|
|
|
7444
8751
|
for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
|
|
7445
8752
|
try {
|
|
7446
8753
|
const url = new URL(rel, import.meta.url);
|
|
7447
|
-
if (url.protocol === "file:" &&
|
|
8754
|
+
if (url.protocol === "file:" && fs12.existsSync(fileURLToPath4(url))) {
|
|
7448
8755
|
builtUrl = url;
|
|
7449
8756
|
break;
|
|
7450
8757
|
}
|
|
@@ -7532,6 +8839,12 @@ function cancellationError(signal) {
|
|
|
7532
8839
|
return signal.reason instanceof Error ? signal.reason : new Error("Indexing cancelled");
|
|
7533
8840
|
}
|
|
7534
8841
|
|
|
8842
|
+
// src/codebase-index/project-server-protocol.ts
|
|
8843
|
+
var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
|
|
8844
|
+
function encodeProjectServerMessage(message) {
|
|
8845
|
+
return encodeJsonFrame(message);
|
|
8846
|
+
}
|
|
8847
|
+
|
|
7535
8848
|
// src/codebase-index/project-server-client.ts
|
|
7536
8849
|
var ProjectServerConnection = class {
|
|
7537
8850
|
constructor(projectRoot, indexDir, endpoint) {
|
|
@@ -7544,11 +8857,13 @@ var ProjectServerConnection = class {
|
|
|
7544
8857
|
indexDir;
|
|
7545
8858
|
endpoint;
|
|
7546
8859
|
socket = null;
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
8860
|
+
/**
|
|
8861
|
+
* Raw inbound bytes for the unified per-frame reader. Frames are sniffed
|
|
8862
|
+
* individually — JSON text (newline-terminated) or binary (magic 0x57) —
|
|
8863
|
+
* instead of latching a read mode, so a JSON broadcast between binary
|
|
8864
|
+
* frames cannot desynchronize the reader.
|
|
8865
|
+
*/
|
|
8866
|
+
readBuffer = Buffer.alloc(0);
|
|
7552
8867
|
/** P6: true once the server advertises binary support and client accepts. */
|
|
7553
8868
|
useBinary = false;
|
|
7554
8869
|
info = null;
|
|
@@ -7690,7 +9005,7 @@ var ProjectServerConnection = class {
|
|
|
7690
9005
|
this.activity = null;
|
|
7691
9006
|
this.health = null;
|
|
7692
9007
|
this.useBinary = false;
|
|
7693
|
-
this.
|
|
9008
|
+
this.readBuffer = Buffer.alloc(0);
|
|
7694
9009
|
this.connectReject?.(new Error("codebase-index client disconnected"));
|
|
7695
9010
|
this.connectResolve = null;
|
|
7696
9011
|
this.connectReject = null;
|
|
@@ -7711,7 +9026,7 @@ var ProjectServerConnection = class {
|
|
|
7711
9026
|
currentAuthToken() {
|
|
7712
9027
|
if (this.authToken === void 0) {
|
|
7713
9028
|
try {
|
|
7714
|
-
const raw =
|
|
9029
|
+
const raw = fs13.readFileSync(
|
|
7715
9030
|
projectIndexServerMetadataPath(this.projectRoot, this.indexDir),
|
|
7716
9031
|
"utf8"
|
|
7717
9032
|
);
|
|
@@ -7818,10 +9133,8 @@ var ProjectServerConnection = class {
|
|
|
7818
9133
|
this.info = null;
|
|
7819
9134
|
this.activity = null;
|
|
7820
9135
|
this.health = null;
|
|
7821
|
-
this.
|
|
7822
|
-
this.binaryBuffer = [];
|
|
9136
|
+
this.readBuffer = Buffer.alloc(0);
|
|
7823
9137
|
this.useBinary = false;
|
|
7824
|
-
this.textDecoder = null;
|
|
7825
9138
|
return new Promise((resolve4, reject) => {
|
|
7826
9139
|
const socket = net.createConnection(this.endpoint);
|
|
7827
9140
|
this.socket = socket;
|
|
@@ -7851,18 +9164,47 @@ var ProjectServerConnection = class {
|
|
|
7851
9164
|
socket.on("close", () => this.onClose(socket));
|
|
7852
9165
|
});
|
|
7853
9166
|
}
|
|
9167
|
+
/**
|
|
9168
|
+
* Unified per-frame reader. Each frame is sniffed by its first byte:
|
|
9169
|
+
* `0x57` ('W') → length-prefixed MessagePack binary, anything else →
|
|
9170
|
+
* newline-delimited JSON text. Sniffing per frame (instead of latching a
|
|
9171
|
+
* mode) is what makes mixed streams work: the server may interleave a JSON
|
|
9172
|
+
* `index-state` broadcast between binary responses, and an old JSON-only
|
|
9173
|
+
* server stays readable while `useBinary` is armed.
|
|
9174
|
+
*
|
|
9175
|
+
* Multibyte UTF-8 in JSON frames is safe: raw `0x0a` only occurs as the
|
|
9176
|
+
* JSON delimiter (inside JSON strings `\n` is escaped), so a complete line
|
|
9177
|
+
* is always complete UTF-8.
|
|
9178
|
+
*/
|
|
7854
9179
|
onData(socket, chunk) {
|
|
7855
9180
|
if (socket !== this.socket) return;
|
|
7856
|
-
|
|
7857
|
-
this.onBinaryData(socket, chunk);
|
|
7858
|
-
return;
|
|
7859
|
-
}
|
|
7860
|
-
if (!this.textDecoder) this.textDecoder = new StringDecoder("utf8");
|
|
7861
|
-
this.buffer += this.textDecoder.write(chunk);
|
|
9181
|
+
this.readBuffer = this.readBuffer.length === 0 ? chunk : Buffer.concat([this.readBuffer, chunk]);
|
|
7862
9182
|
while (true) {
|
|
7863
|
-
|
|
9183
|
+
if (this.readBuffer.length === 0) return;
|
|
9184
|
+
if (this.useBinary && isBinaryFrame(this.readBuffer[0])) {
|
|
9185
|
+
if (this.readBuffer.length < 5) return;
|
|
9186
|
+
const frameLen = this.readBuffer.readUInt32BE(1);
|
|
9187
|
+
if (frameLen > MAX_BINARY_FRAME_BYTES) {
|
|
9188
|
+
socket.destroy();
|
|
9189
|
+
this.transition("offline", { error: "binary frame length exceeds the IPC limit" });
|
|
9190
|
+
return;
|
|
9191
|
+
}
|
|
9192
|
+
if (this.readBuffer.length < 5 + frameLen) return;
|
|
9193
|
+
const payload = this.readBuffer.subarray(5, 5 + frameLen);
|
|
9194
|
+
this.readBuffer = this.readBuffer.subarray(5 + frameLen);
|
|
9195
|
+
let message2;
|
|
9196
|
+
try {
|
|
9197
|
+
message2 = decodeBinaryFrame(payload);
|
|
9198
|
+
} catch {
|
|
9199
|
+
socket.destroy(new Error("invalid binary codebase-index server response"));
|
|
9200
|
+
return;
|
|
9201
|
+
}
|
|
9202
|
+
this.onMessage(message2);
|
|
9203
|
+
continue;
|
|
9204
|
+
}
|
|
9205
|
+
const newline = this.readBuffer.indexOf(10);
|
|
7864
9206
|
if (newline < 0) {
|
|
7865
|
-
if (this.
|
|
9207
|
+
if (this.readBuffer.length > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
|
|
7866
9208
|
socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
|
|
7867
9209
|
}
|
|
7868
9210
|
return;
|
|
@@ -7871,8 +9213,8 @@ var ProjectServerConnection = class {
|
|
|
7871
9213
|
socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
|
|
7872
9214
|
return;
|
|
7873
9215
|
}
|
|
7874
|
-
const line = this.
|
|
7875
|
-
this.
|
|
9216
|
+
const line = this.readBuffer.subarray(0, newline).toString("utf8");
|
|
9217
|
+
this.readBuffer = this.readBuffer.subarray(newline + 1);
|
|
7876
9218
|
if (!line) continue;
|
|
7877
9219
|
let message;
|
|
7878
9220
|
try {
|
|
@@ -7884,44 +9226,6 @@ var ProjectServerConnection = class {
|
|
|
7884
9226
|
this.onMessage(message);
|
|
7885
9227
|
}
|
|
7886
9228
|
}
|
|
7887
|
-
/**
|
|
7888
|
-
* P6: Parse binary frames from the raw buffer.
|
|
7889
|
-
*
|
|
7890
|
-
* Each frame is: [1 byte magic 0x57] [4 bytes uint32 BE length] [payload].
|
|
7891
|
-
* The magic byte distinguishes binary from JSON — a JSON frame's first byte
|
|
7892
|
-
* is `{` (0x7B), so there is no ambiguity even in a mixed-mode buffer.
|
|
7893
|
-
*/
|
|
7894
|
-
onBinaryData(socket, chunk) {
|
|
7895
|
-
this.binaryBuffer.push(chunk);
|
|
7896
|
-
const all = Buffer.concat(this.binaryBuffer);
|
|
7897
|
-
let offset = 0;
|
|
7898
|
-
while (offset + 5 <= all.length) {
|
|
7899
|
-
if (!isBinaryFrame(all[offset])) {
|
|
7900
|
-
this.useBinary = false;
|
|
7901
|
-
this.buffer += all.subarray(offset).toString("utf8");
|
|
7902
|
-
this.binaryBuffer = [];
|
|
7903
|
-
return;
|
|
7904
|
-
}
|
|
7905
|
-
const frameLen = all.readUInt32BE(offset + 1);
|
|
7906
|
-
if (frameLen > 256 * 1024 * 1024) {
|
|
7907
|
-
socket.destroy();
|
|
7908
|
-
this.transition("offline", { error: "binary frame length exceeds 256MB limit" });
|
|
7909
|
-
return;
|
|
7910
|
-
}
|
|
7911
|
-
const totalLen = 5 + frameLen;
|
|
7912
|
-
if (offset + totalLen > all.length) break;
|
|
7913
|
-
const payload = all.subarray(offset + 5, offset + 5 + frameLen);
|
|
7914
|
-
try {
|
|
7915
|
-
const message = decodeBinaryFrame(payload);
|
|
7916
|
-
this.onMessage(message);
|
|
7917
|
-
} catch {
|
|
7918
|
-
socket.destroy(new Error("invalid binary codebase-index server response"));
|
|
7919
|
-
return;
|
|
7920
|
-
}
|
|
7921
|
-
offset += totalLen;
|
|
7922
|
-
}
|
|
7923
|
-
this.binaryBuffer = offset < all.length ? [all.subarray(offset)] : [];
|
|
7924
|
-
}
|
|
7925
9229
|
onMessage(message) {
|
|
7926
9230
|
if (message.type === "hello") {
|
|
7927
9231
|
if (message.protocolVersion !== PROJECT_INDEX_SERVER_PROTOCOL_VERSION) {
|
|
@@ -7941,7 +9245,7 @@ var ProjectServerConnection = class {
|
|
|
7941
9245
|
}
|
|
7942
9246
|
this.info = message;
|
|
7943
9247
|
this.markResponsive();
|
|
7944
|
-
if (message.binarySupported) this.useBinary = true;
|
|
9248
|
+
if (message.binarySupported && binaryFramingEnabled()) this.useBinary = true;
|
|
7945
9249
|
this.transition("connected", { pid: message.pid });
|
|
7946
9250
|
ensureHeartbeatLoop();
|
|
7947
9251
|
this.connectResolve?.();
|
|
@@ -8027,13 +9331,13 @@ var ProjectServerConnection = class {
|
|
|
8027
9331
|
if (!url) throw new Error("built codebase-index project server is unavailable");
|
|
8028
9332
|
if (process.platform !== "win32") {
|
|
8029
9333
|
try {
|
|
8030
|
-
|
|
9334
|
+
fs13.rmSync(this.endpoint, { force: true });
|
|
8031
9335
|
} catch {
|
|
8032
9336
|
}
|
|
8033
9337
|
}
|
|
8034
9338
|
const args = [fileURLToPath5(url), "--project-root", this.projectRoot];
|
|
8035
9339
|
if (this.indexDir) args.push("--index-dir", this.indexDir);
|
|
8036
|
-
const child =
|
|
9340
|
+
const child = spawn4(process.execPath, args, {
|
|
8037
9341
|
detached: true,
|
|
8038
9342
|
stdio: "ignore",
|
|
8039
9343
|
windowsHide: true,
|
|
@@ -8051,8 +9355,8 @@ var ProjectServerConnection = class {
|
|
|
8051
9355
|
process.kill(pid);
|
|
8052
9356
|
const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
|
|
8053
9357
|
try {
|
|
8054
|
-
const metadata = JSON.parse(
|
|
8055
|
-
if (metadata.pid === pid)
|
|
9358
|
+
const metadata = JSON.parse(fs13.readFileSync(metadataPath, "utf8"));
|
|
9359
|
+
if (metadata.pid === pid) fs13.rmSync(metadataPath, { force: true });
|
|
8056
9360
|
} catch {
|
|
8057
9361
|
}
|
|
8058
9362
|
return true;
|
|
@@ -8064,6 +9368,10 @@ var ProjectServerConnection = class {
|
|
|
8064
9368
|
var connections = /* @__PURE__ */ new Map();
|
|
8065
9369
|
var MAX_CACHED_CONNECTIONS = 8;
|
|
8066
9370
|
var heartbeatTimer;
|
|
9371
|
+
function binaryFramingEnabled() {
|
|
9372
|
+
const flag = process.env["WRONGSTACK_INDEX_BINARY"];
|
|
9373
|
+
return flag === "1" || flag === "true";
|
|
9374
|
+
}
|
|
8067
9375
|
function forgetConnection(endpoint, connection) {
|
|
8068
9376
|
if (connections.get(endpoint) === connection) connections.delete(endpoint);
|
|
8069
9377
|
connection.close();
|
|
@@ -8153,7 +9461,7 @@ function resolveWorkerUrl() {
|
|
|
8153
9461
|
for (const rel of ["./worker.js", "./codebase-index/worker.js"]) {
|
|
8154
9462
|
try {
|
|
8155
9463
|
const url = new URL(rel, import.meta.url);
|
|
8156
|
-
if (url.protocol === "file:" &&
|
|
9464
|
+
if (url.protocol === "file:" && fs14.existsSync(fileURLToPath6(url))) return url;
|
|
8157
9465
|
} catch {
|
|
8158
9466
|
}
|
|
8159
9467
|
}
|
|
@@ -8480,7 +9788,7 @@ var writeTool = {
|
|
|
8480
9788
|
required: ["path", "content"]
|
|
8481
9789
|
},
|
|
8482
9790
|
async execute(input, ctx, opts) {
|
|
8483
|
-
return
|
|
9791
|
+
return writeFile4(input, ctx, opts?.signal);
|
|
8484
9792
|
},
|
|
8485
9793
|
async *executeStream(input, ctx, opts) {
|
|
8486
9794
|
const prepared = await prepareWrite(input, ctx);
|
|
@@ -8493,7 +9801,7 @@ var writeTool = {
|
|
|
8493
9801
|
yield { type: "final", output: await finishWrite(input, ctx, prepared, opts?.signal) };
|
|
8494
9802
|
}
|
|
8495
9803
|
};
|
|
8496
|
-
async function
|
|
9804
|
+
async function writeFile4(input, ctx, signal) {
|
|
8497
9805
|
return finishWrite(input, ctx, await prepareWrite(input, ctx), signal);
|
|
8498
9806
|
}
|
|
8499
9807
|
async function prepareWrite(input, ctx) {
|
|
@@ -8513,14 +9821,14 @@ async function prepareWrite(input, ctx) {
|
|
|
8513
9821
|
let existed = false;
|
|
8514
9822
|
let prev = "";
|
|
8515
9823
|
try {
|
|
8516
|
-
const stat3 = await
|
|
9824
|
+
const stat3 = await fs15.stat(absPath);
|
|
8517
9825
|
existed = stat3.isFile();
|
|
8518
9826
|
if (existed) {
|
|
8519
9827
|
if (!ctx.hasRead(absPath)) {
|
|
8520
|
-
prev = await
|
|
9828
|
+
prev = await fs15.readFile(absPath, "utf8");
|
|
8521
9829
|
ctx.recordRead(absPath, stat3.mtimeMs, "write", sha256hex(prev));
|
|
8522
9830
|
} else {
|
|
8523
|
-
prev = await
|
|
9831
|
+
prev = await fs15.readFile(absPath, "utf8");
|
|
8524
9832
|
}
|
|
8525
9833
|
}
|
|
8526
9834
|
} catch (err) {
|
|
@@ -8541,9 +9849,9 @@ async function finishWrite(input, ctx, prepared, signal) {
|
|
|
8541
9849
|
const rawDiff = prepared.existed ? unifiedDiff(prepared.prev, content, { fromFile: input.path, toFile: input.path }) : `+++ ${input.path}
|
|
8542
9850
|
+ (new file, ${content.split("\n").length} lines)`;
|
|
8543
9851
|
const { text: diff, truncated: diffTruncated } = truncateDiffPayload(rawDiff, MAX_DIFF_BYTES);
|
|
8544
|
-
const stat3 = await
|
|
9852
|
+
const stat3 = await fs15.stat(prepared.absPath);
|
|
8545
9853
|
ctx.recordRead(prepared.absPath, stat3.mtimeMs, "write", sha256hex(content));
|
|
8546
|
-
ctx.session
|
|
9854
|
+
ctx.session?.recordFileChange?.({
|
|
8547
9855
|
path: prepared.absPath,
|
|
8548
9856
|
action: prepared.existed ? "modified" : "created",
|
|
8549
9857
|
before: prepared.existed ? prepared.prev : null,
|