@wrongstack/tools 0.309.1 → 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/bash.js +3 -3
- package/dist/builtin.js +3011 -1677
- 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 +2735 -1415
- 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 +5 -3
- package/dist/grep.js +5 -124
- package/dist/index.js +3007 -1736
- package/dist/json.js +5 -124
- package/dist/kanban.js +130 -0
- package/dist/logs.js +5 -121
- package/dist/pack.js +3011 -1677
- package/dist/patch.js +2524 -1216
- package/dist/plan.js +106 -0
- 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/todo.js +106 -0
- package/dist/tool-tier.d.ts +11 -0
- package/dist/tool-tier.js +3019 -1677
- package/dist/tree.js +14 -3
- package/dist/win32.js +3 -3
- package/dist/write.js +2513 -1205
- package/package.json +5 -4
package/dist/read.js
CHANGED
|
@@ -170,238 +170,133 @@ var init_languages = __esm({
|
|
|
170
170
|
}
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
-
// src/codebase-index/
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
tsLoad ??= import("@typescript/typescript6").then((m) => {
|
|
181
|
-
ts = m.default ?? m;
|
|
182
|
-
return ts;
|
|
183
|
-
});
|
|
184
|
-
return tsLoad;
|
|
185
|
-
}
|
|
186
|
-
function kindMap() {
|
|
187
|
-
kindMapCache ??= {
|
|
188
|
-
[ts.SyntaxKind.ClassDeclaration]: "class",
|
|
189
|
-
[ts.SyntaxKind.InterfaceDeclaration]: "interface",
|
|
190
|
-
[ts.SyntaxKind.EnumDeclaration]: "enum",
|
|
191
|
-
[ts.SyntaxKind.TypeAliasDeclaration]: "type",
|
|
192
|
-
[ts.SyntaxKind.FunctionDeclaration]: "function",
|
|
193
|
-
[ts.SyntaxKind.MethodDeclaration]: "method",
|
|
194
|
-
[ts.SyntaxKind.GetAccessor]: "property",
|
|
195
|
-
[ts.SyntaxKind.SetAccessor]: "property",
|
|
196
|
-
[ts.SyntaxKind.PropertyDeclaration]: "property",
|
|
197
|
-
[ts.SyntaxKind.Parameter]: "parameter",
|
|
198
|
-
[ts.SyntaxKind.NamespaceExportDeclaration]: "namespace"
|
|
199
|
-
};
|
|
200
|
-
return kindMapCache;
|
|
201
|
-
}
|
|
202
|
-
function kindOf(node) {
|
|
203
|
-
if (ts.isVariableDeclaration(node)) {
|
|
204
|
-
const parent = node.parent;
|
|
205
|
-
if (ts.isVariableDeclarationList(parent)) {
|
|
206
|
-
const flags = parent.flags;
|
|
207
|
-
if (flags & ts.NodeFlags.Let) return "let";
|
|
208
|
-
if (flags & ts.NodeFlags.Const) return "const";
|
|
209
|
-
return "var";
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
213
|
-
return kindMap()[node.kind] ?? null;
|
|
214
|
-
}
|
|
215
|
-
function getSignature(printer, node, sourceFile) {
|
|
216
|
-
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
217
|
-
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
218
|
-
}
|
|
219
|
-
function getJsDoc(node, sourceFile) {
|
|
220
|
-
const fullText = sourceFile.getFullText();
|
|
221
|
-
const nodePos = node.getFullStart();
|
|
222
|
-
const comments = ts.getLeadingCommentRanges(fullText, nodePos);
|
|
223
|
-
if (!comments) return "";
|
|
224
|
-
for (const range of comments) {
|
|
225
|
-
const commentText = fullText.slice(range.pos, range.end);
|
|
226
|
-
const trimmed = commentText.trim();
|
|
227
|
-
if (trimmed.startsWith("/**") && trimmed.endsWith("*/")) {
|
|
228
|
-
const inner = trimmed.slice(3, -2).replace(/^[ \t]*\*[ ]?/gm, "").trim();
|
|
229
|
-
return inner.split("\n")[0]?.trim().slice(0, 200) ?? "";
|
|
230
|
-
}
|
|
173
|
+
// src/codebase-index/import-extractor.ts
|
|
174
|
+
function lastSegment(specifier) {
|
|
175
|
+
const pathLike = /[/\\]|::/.test(specifier);
|
|
176
|
+
const segments = specifier.split(/[/\\]|::/).filter(Boolean);
|
|
177
|
+
let last = segments[segments.length - 1] ?? specifier;
|
|
178
|
+
if (last === "*" || last === "_") {
|
|
179
|
+
last = segments[segments.length - 2] ?? specifier;
|
|
231
180
|
}
|
|
232
|
-
return "";
|
|
181
|
+
if (pathLike) return last.replace(/\.[A-Za-z0-9]{1,8}$/, "") || last;
|
|
182
|
+
const dotted = last.split(".").filter((part) => part && part !== "*" && part !== "_");
|
|
183
|
+
return dotted[dotted.length - 1] ?? last;
|
|
233
184
|
}
|
|
234
|
-
function
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (node.name && ts.isIdentifier(node.name)) {
|
|
239
|
-
parts.push(node.name.text);
|
|
240
|
-
}
|
|
185
|
+
function newlineOffsets(content) {
|
|
186
|
+
const offsets = [];
|
|
187
|
+
for (let i = 0; i < content.length; i++) {
|
|
188
|
+
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
241
189
|
}
|
|
190
|
+
return offsets;
|
|
242
191
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
251
|
-
}
|
|
252
|
-
const symbols = [];
|
|
253
|
-
const refs = [];
|
|
254
|
-
const printer = ts.createPrinter({});
|
|
255
|
-
function visit(node, funcDepth, scopeParts) {
|
|
256
|
-
const kind = kindOf(node);
|
|
257
|
-
if (kind) {
|
|
258
|
-
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && funcDepth > 0) {
|
|
259
|
-
} else {
|
|
260
|
-
const nameNode = node.name;
|
|
261
|
-
if (!nameNode || !ts.isIdentifier(nameNode)) {
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
const name = nameNode.text;
|
|
265
|
-
const pos2 = nameNode.getStart(sourceFile);
|
|
266
|
-
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(pos2);
|
|
267
|
-
const scope = scopeParts.join(".");
|
|
268
|
-
const signature = getSignature(printer, node, sourceFile);
|
|
269
|
-
const docComment = getJsDoc(node, sourceFile);
|
|
270
|
-
const text = [name, signature, docComment].filter(Boolean).join(" | ");
|
|
271
|
-
symbols.push({
|
|
272
|
-
id: 0,
|
|
273
|
-
lang,
|
|
274
|
-
kind,
|
|
275
|
-
name,
|
|
276
|
-
file,
|
|
277
|
-
line: line2 + 1,
|
|
278
|
-
col: character,
|
|
279
|
-
signature,
|
|
280
|
-
docComment,
|
|
281
|
-
scope,
|
|
282
|
-
text
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
const pos = node.getStart(sourceFile);
|
|
287
|
-
const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
|
|
288
|
-
const lineNum = line + 1;
|
|
289
|
-
if (ts.isCallExpression(node)) {
|
|
290
|
-
const expr = node.expression;
|
|
291
|
-
if (ts.isIdentifier(expr)) {
|
|
292
|
-
refs.push({ fromId: 0, toName: expr.text, callType: "call", line: lineNum });
|
|
293
|
-
}
|
|
294
|
-
} else if (ts.isPropertyAccessExpression(node)) {
|
|
295
|
-
if (ts.isIdentifier(node.expression)) {
|
|
296
|
-
refs.push({ fromId: 0, toName: node.expression.text, callType: "call", line: lineNum });
|
|
297
|
-
}
|
|
298
|
-
} else if (ts.isTypeReferenceNode(node)) {
|
|
299
|
-
const name = getTypeName(node.typeName);
|
|
300
|
-
if (name) refs.push({ fromId: 0, toName: name, callType: "type_ref", line: lineNum });
|
|
301
|
-
} else if (ts.isHeritageClause(node)) {
|
|
302
|
-
for (const t of node.types) {
|
|
303
|
-
const name = getTypeName(t.expression);
|
|
304
|
-
if (name)
|
|
305
|
-
refs.push({
|
|
306
|
-
fromId: 0,
|
|
307
|
-
toName: name,
|
|
308
|
-
callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement",
|
|
309
|
-
line: lineNum
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
} else if (ts.isImportDeclaration(node)) {
|
|
313
|
-
emitImportSpecifierRefs(node, refs, lineNum);
|
|
314
|
-
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
|
|
315
|
-
emitExportSpecifierRefs(node, refs, lineNum);
|
|
316
|
-
}
|
|
317
|
-
const scopeIdx = scopeParts.length;
|
|
318
|
-
pushScopeName(node, scopeParts);
|
|
319
|
-
const childFuncDepth = ts.isFunctionLike(node) ? funcDepth + 1 : funcDepth;
|
|
320
|
-
ts.forEachChild(node, (child) => visit(child, childFuncDepth, scopeParts));
|
|
321
|
-
scopeParts.length = scopeIdx;
|
|
192
|
+
function lineAt(offsets, index) {
|
|
193
|
+
let low = 0;
|
|
194
|
+
let high = offsets.length;
|
|
195
|
+
while (low < high) {
|
|
196
|
+
const mid = low + high >>> 1;
|
|
197
|
+
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
198
|
+
else high = mid;
|
|
322
199
|
}
|
|
323
|
-
|
|
324
|
-
return { file, lang, symbols, refs: deduplicateRefs(refs), mtimeMs: Date.now() };
|
|
200
|
+
return low + 1;
|
|
325
201
|
}
|
|
326
|
-
function
|
|
327
|
-
|
|
328
|
-
if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
|
|
329
|
-
return "";
|
|
202
|
+
function hasImportPatterns(lang) {
|
|
203
|
+
return LANG_IMPORTS[lang] !== void 0;
|
|
330
204
|
}
|
|
331
|
-
function
|
|
205
|
+
function extractImports(opts) {
|
|
206
|
+
const patterns = LANG_IMPORTS[opts.lang];
|
|
207
|
+
if (!patterns || !opts.content) return [];
|
|
208
|
+
const limit = opts.maxImports ?? IMPORT_MAX_PER_FILE;
|
|
209
|
+
const content = opts.content.length > IMPORT_MAX_FILE_CHARS ? opts.content.slice(0, IMPORT_MAX_FILE_CHARS) : opts.content;
|
|
210
|
+
const refs = [];
|
|
332
211
|
const seen = /* @__PURE__ */ new Set();
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (!clause) {
|
|
347
|
-
if (module) {
|
|
348
|
-
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
349
|
-
}
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
if (clause.name) {
|
|
353
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
354
|
-
}
|
|
355
|
-
const bindings = clause.namedBindings;
|
|
356
|
-
if (!bindings) return;
|
|
357
|
-
if (ts.isNamedImports(bindings)) {
|
|
358
|
-
for (const element of bindings.elements) {
|
|
212
|
+
const offsets = newlineOffsets(content);
|
|
213
|
+
for (const pattern of patterns) {
|
|
214
|
+
const re = new RegExp(pattern.re.source, pattern.re.flags);
|
|
215
|
+
for (const match of content.matchAll(re)) {
|
|
216
|
+
if (refs.length >= limit) return refs;
|
|
217
|
+
const specifier = match[1]?.trim();
|
|
218
|
+
if (!specifier) continue;
|
|
219
|
+
const module = specifier;
|
|
220
|
+
const toName = pattern.name === "full" ? module : lastSegment(module);
|
|
221
|
+
if (!toName) continue;
|
|
222
|
+
const key = `${module}\0${toName}`;
|
|
223
|
+
if (seen.has(key)) continue;
|
|
224
|
+
seen.add(key);
|
|
359
225
|
refs.push({
|
|
360
226
|
fromId: 0,
|
|
361
|
-
toName
|
|
227
|
+
toName,
|
|
362
228
|
callType: "import",
|
|
363
|
-
line:
|
|
229
|
+
line: lineAt(offsets, match.index ?? 0),
|
|
230
|
+
lang: opts.lang,
|
|
364
231
|
module
|
|
365
232
|
});
|
|
366
233
|
}
|
|
367
|
-
} else if (ts.isNamespaceImport(bindings)) {
|
|
368
|
-
refs.push({
|
|
369
|
-
fromId: 0,
|
|
370
|
-
toName: bindings.name.text,
|
|
371
|
-
callType: "import",
|
|
372
|
-
line: lineNum,
|
|
373
|
-
module
|
|
374
|
-
});
|
|
375
234
|
}
|
|
235
|
+
return refs;
|
|
376
236
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
function emitExportSpecifierRefs(node, refs, lineNum) {
|
|
381
|
-
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
382
|
-
const clause = node.exportClause;
|
|
383
|
-
if (clause && ts.isNamespaceExport(clause)) {
|
|
384
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
if (clause && ts.isNamedExports(clause)) {
|
|
388
|
-
for (const element of clause.elements) {
|
|
389
|
-
const originalName = element.propertyName?.text ?? element.name.text;
|
|
390
|
-
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum, module });
|
|
391
|
-
}
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
if (module) {
|
|
395
|
-
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
var ts, tsLoad, kindMapCache;
|
|
399
|
-
var init_ts_parser = __esm({
|
|
400
|
-
"src/codebase-index/ts-parser.ts"() {
|
|
237
|
+
var IMPORT_MAX_FILE_CHARS, IMPORT_MAX_PER_FILE, DOTTED_IMPORT, LANG_IMPORTS;
|
|
238
|
+
var init_import_extractor = __esm({
|
|
239
|
+
"src/codebase-index/import-extractor.ts"() {
|
|
401
240
|
"use strict";
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
241
|
+
IMPORT_MAX_FILE_CHARS = 512 * 1024;
|
|
242
|
+
IMPORT_MAX_PER_FILE = 400;
|
|
243
|
+
DOTTED_IMPORT = [
|
|
244
|
+
{ re: /^[ \t]*import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;?\s*$/gm }
|
|
245
|
+
];
|
|
246
|
+
LANG_IMPORTS = {
|
|
247
|
+
// Go and Python have real AST extractors; these patterns are the fallback for
|
|
248
|
+
// machines with no Go toolchain or Python interpreter installed, where the
|
|
249
|
+
// parser degrades to regex symbols and would otherwise contribute no edges.
|
|
250
|
+
go: [
|
|
251
|
+
{ re: /^[ \t]*import\s+(?:[\w.]+\s+)?"([^"]+)"/gm },
|
|
252
|
+
// Grouped form: inside `import ( … )` each line is an optional alias plus a
|
|
253
|
+
// quoted path. A stray match elsewhere resolves to no file and is dropped.
|
|
254
|
+
{ re: /^[ \t]*(?:[A-Za-z_.]\w*\s+)?"([^"]+)"\s*$/gm }
|
|
255
|
+
],
|
|
256
|
+
py: [{ re: /^[ \t]*import\s+([\w.]+)/gm }, { re: /^[ \t]*from\s+([.\w]+)\s+import\b/gm }],
|
|
257
|
+
rs: [
|
|
258
|
+
// use a::b::C; | use a::b::{C, D}; → the path before any brace
|
|
259
|
+
{ re: /^[ \t]*(?:pub\s+)?use\s+([\w:]+?)(?:::\{|\s*;|\s+as\b)/gm },
|
|
260
|
+
// mod foo; (a declaration *and* a dependency on foo.rs / foo/mod.rs)
|
|
261
|
+
{ re: /^[ \t]*(?:pub\s+)?mod\s+(\w+)\s*;/gm }
|
|
262
|
+
],
|
|
263
|
+
java: DOTTED_IMPORT,
|
|
264
|
+
kotlin: DOTTED_IMPORT,
|
|
265
|
+
scala: [{ re: /^[ \t]*import\s+([\w.]+(?:\.[_*])?)\s*$/gm }],
|
|
266
|
+
csharp: [
|
|
267
|
+
// using Foo.Bar; | using static Foo.Bar; | using Alias = Foo.Bar;
|
|
268
|
+
{ re: /^[ \t]*global\s+using\s+(?:static\s+)?([\w.]+)\s*;/gm, name: "full" },
|
|
269
|
+
{ re: /^[ \t]*using\s+(?:static\s+)?(?:\w+\s*=\s*)?([\w.]+)\s*;/gm, name: "full" }
|
|
270
|
+
],
|
|
271
|
+
// Quoted includes only: <stdio.h> is a system header with no indexed file.
|
|
272
|
+
c: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
273
|
+
cpp: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
274
|
+
ruby: [{ re: /^[ \t]*(?:require|require_relative|load)\s*\(?\s*['"]([^'"]+)['"]/gm }],
|
|
275
|
+
php: [
|
|
276
|
+
// `use A\B\C` imports the class C, which is what the index has a symbol
|
|
277
|
+
// for — the namespace symbol only covers the `A\B` prefix.
|
|
278
|
+
{ re: /^[ \t]*use\s+(?:function\s+|const\s+)?([\w\\]+)/gm },
|
|
279
|
+
{ re: /^[ \t]*(?:require|require_once|include|include_once)\s*\(?\s*['"]([^'"]+)['"]/gm }
|
|
280
|
+
],
|
|
281
|
+
swift: [{ re: /^[ \t]*import\s+(?:struct\s+|class\s+|func\s+)?([\w.]+)\s*$/gm }],
|
|
282
|
+
dart: [{ re: /^[ \t]*(?:import|export|part)\s+['"]([^'"]+)['"]/gm }],
|
|
283
|
+
lua: [{ re: /\brequire\s*\(?\s*['"]([^'"]+)['"]/g }],
|
|
284
|
+
elixir: [{ re: /^[ \t]*(?:alias|import|require|use)\s+([A-Z][\w.]*)/gm, name: "full" }],
|
|
285
|
+
haskell: [{ re: /^[ \t]*import\s+(?:qualified\s+)?([\w.]+)/gm, name: "full" }],
|
|
286
|
+
zig: [{ re: /@import\s*\(\s*"([^"]+)"\s*\)/g }],
|
|
287
|
+
proto: [{ re: /^[ \t]*import\s+(?:public\s+|weak\s+)?"([^"]+)"\s*;/gm }],
|
|
288
|
+
// `@import "x"`, `@use "x"`, `@forward "x"` — Sass and plain CSS alike.
|
|
289
|
+
css: [{ re: /^[ \t]*@(?:import|use|forward)\s+(?:url\()?\s*['"]([^'"]+)['"]/gm }],
|
|
290
|
+
// A .vue/.svelte file's <script> block is JS; the same ESM syntax applies.
|
|
291
|
+
vue: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
292
|
+
svelte: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
293
|
+
html: [
|
|
294
|
+
{ re: /<script[^>]+src\s*=\s*['"]([^'"]+)['"]/g },
|
|
295
|
+
{ re: /<link[^>]+href\s*=\s*['"]([^'"]+)['"]/g }
|
|
296
|
+
],
|
|
297
|
+
shell: [{ re: /^[ \t]*(?:source|\.)\s+(\S+)/gm }],
|
|
298
|
+
r: [{ re: /\b(?:source|library|require)\s*\(\s*['"]?([\w./-]+)['"]?\s*\)/g }]
|
|
299
|
+
};
|
|
405
300
|
}
|
|
406
301
|
});
|
|
407
302
|
|
|
@@ -487,6 +382,32 @@ function parseParserOutput(stdout, lang) {
|
|
|
487
382
|
refs: dedupeRefs(coerceRefs(record.refs, lang))
|
|
488
383
|
};
|
|
489
384
|
}
|
|
385
|
+
function parseParserBatchOutput(stdout, lang) {
|
|
386
|
+
const trimmed = stdout.trim();
|
|
387
|
+
if (!trimmed) return [];
|
|
388
|
+
let parsed;
|
|
389
|
+
try {
|
|
390
|
+
parsed = JSON.parse(trimmed);
|
|
391
|
+
} catch {
|
|
392
|
+
return [];
|
|
393
|
+
}
|
|
394
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
395
|
+
const results = parsed.results;
|
|
396
|
+
if (!Array.isArray(results)) return [];
|
|
397
|
+
return results.flatMap((entry) => {
|
|
398
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return [];
|
|
399
|
+
const candidate = entry;
|
|
400
|
+
if (typeof candidate.file !== "string" || !candidate.file) return [];
|
|
401
|
+
return [
|
|
402
|
+
{
|
|
403
|
+
file: candidate.file,
|
|
404
|
+
error: typeof candidate.error === "string" && candidate.error ? candidate.error : void 0,
|
|
405
|
+
symbols: coerceSymbols(candidate.symbols),
|
|
406
|
+
refs: dedupeRefs(coerceRefs(candidate.refs, lang))
|
|
407
|
+
}
|
|
408
|
+
];
|
|
409
|
+
});
|
|
410
|
+
}
|
|
490
411
|
function dedupeRefs(refs) {
|
|
491
412
|
const seen = /* @__PURE__ */ new Set();
|
|
492
413
|
return refs.filter((ref) => {
|
|
@@ -527,187 +448,172 @@ var init_spawn_gate = __esm({
|
|
|
527
448
|
}
|
|
528
449
|
});
|
|
529
450
|
|
|
530
|
-
// src/codebase-index/
|
|
531
|
-
var go_parser_exports = {};
|
|
532
|
-
__export(go_parser_exports, {
|
|
533
|
-
detectLang: () => detectLang,
|
|
534
|
-
parseSymbols: () => parseSymbols2
|
|
535
|
-
});
|
|
451
|
+
// src/codebase-index/parser-batch.ts
|
|
536
452
|
import { spawn } from "node:child_process";
|
|
453
|
+
import * as fsSync from "node:fs";
|
|
454
|
+
import * as fs4 from "node:fs/promises";
|
|
537
455
|
import * as os from "node:os";
|
|
538
456
|
import * as path7 from "node:path";
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
457
|
+
function chunkBatchFiles(files) {
|
|
458
|
+
const chunks = [];
|
|
459
|
+
let current = [];
|
|
460
|
+
let bytes = 0;
|
|
461
|
+
for (const file of files) {
|
|
462
|
+
const size = Buffer.byteLength(file.content, "utf8");
|
|
463
|
+
if (current.length > 0 && (current.length >= MAX_BATCH_FILES || bytes + size > MAX_BATCH_BYTES)) {
|
|
464
|
+
chunks.push(current);
|
|
465
|
+
current = [];
|
|
466
|
+
bytes = 0;
|
|
467
|
+
}
|
|
468
|
+
current.push(file);
|
|
469
|
+
bytes += size;
|
|
470
|
+
}
|
|
471
|
+
if (current.length > 0) chunks.push(current);
|
|
472
|
+
return chunks;
|
|
473
|
+
}
|
|
474
|
+
function batchTimeoutMs(fileCount) {
|
|
475
|
+
return Math.min(12e4, 15e3 + fileCount * 1500);
|
|
476
|
+
}
|
|
477
|
+
async function ensureScriptPath(cached, prefix, fileName, script) {
|
|
478
|
+
if (cached) return { path: cached, wrote: false };
|
|
479
|
+
const dir = await fs4.mkdtemp(path7.join(os.tmpdir(), prefix));
|
|
480
|
+
const scriptPath = path7.join(dir, fileName);
|
|
481
|
+
await fs4.writeFile(scriptPath, script, { encoding: "utf8", flag: "wx" });
|
|
482
|
+
process.once("exit", () => {
|
|
483
|
+
try {
|
|
484
|
+
fsSync.rmSync(dir, { recursive: true, force: true });
|
|
485
|
+
} catch {
|
|
546
486
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
} catch {
|
|
550
|
-
return fallbackParse(file, content, lang);
|
|
551
|
-
}
|
|
487
|
+
});
|
|
488
|
+
return { path: scriptPath, wrote: true };
|
|
552
489
|
}
|
|
553
|
-
function
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
const fn = /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(/.exec(trimmed);
|
|
564
|
-
if (fn?.[1]) {
|
|
565
|
-
addFallbackSymbol(symbols, {
|
|
566
|
-
filePath,
|
|
567
|
-
lang,
|
|
568
|
-
kind: trimmed.startsWith("func (") ? "method" : "function",
|
|
569
|
-
name: fn[1],
|
|
570
|
-
line: idx + 1,
|
|
571
|
-
col,
|
|
572
|
-
signature: trimmed,
|
|
573
|
-
scope: packageName ? `${packageName}.${fn[1]}` : fn[1]
|
|
574
|
-
});
|
|
575
|
-
continue;
|
|
576
|
-
}
|
|
577
|
-
const typeDecl = /^type\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
578
|
-
if (typeDecl?.[1]) {
|
|
579
|
-
addFallbackSymbol(symbols, {
|
|
580
|
-
filePath,
|
|
581
|
-
lang,
|
|
582
|
-
kind: "type",
|
|
583
|
-
name: typeDecl[1],
|
|
584
|
-
line: idx + 1,
|
|
585
|
-
col,
|
|
586
|
-
signature: trimmed,
|
|
587
|
-
scope: packageName
|
|
588
|
-
});
|
|
589
|
-
continue;
|
|
590
|
-
}
|
|
591
|
-
const valueDecl = /^(const|var)\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
592
|
-
if (valueDecl?.[1] && valueDecl[2]) {
|
|
593
|
-
addFallbackSymbol(symbols, {
|
|
594
|
-
filePath,
|
|
595
|
-
lang,
|
|
596
|
-
kind: valueDecl[1],
|
|
597
|
-
name: valueDecl[2],
|
|
598
|
-
line: idx + 1,
|
|
599
|
-
col,
|
|
600
|
-
signature: trimmed,
|
|
601
|
-
scope: packageName
|
|
602
|
-
});
|
|
490
|
+
function runToolchainChild(binary, args, stdinPayload, timeoutMs) {
|
|
491
|
+
return new Promise((resolve4) => {
|
|
492
|
+
let settled = false;
|
|
493
|
+
let stdout = "";
|
|
494
|
+
let proc;
|
|
495
|
+
try {
|
|
496
|
+
proc = spawn(binary, args, { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
497
|
+
} catch {
|
|
498
|
+
resolve4(null);
|
|
499
|
+
return;
|
|
603
500
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
501
|
+
const finish = (value) => {
|
|
502
|
+
if (settled) return;
|
|
503
|
+
settled = true;
|
|
504
|
+
clearTimeout(timer);
|
|
505
|
+
resolve4(value);
|
|
506
|
+
};
|
|
507
|
+
const timer = setTimeout(() => {
|
|
508
|
+
proc.kill("SIGKILL");
|
|
509
|
+
finish(null);
|
|
510
|
+
}, timeoutMs);
|
|
511
|
+
timer.unref?.();
|
|
512
|
+
proc.on("error", () => finish(null));
|
|
513
|
+
proc.stdout?.on("data", (chunk) => {
|
|
514
|
+
stdout += chunk.toString();
|
|
515
|
+
});
|
|
516
|
+
proc.stderr?.resume();
|
|
517
|
+
proc.stdin?.on("error", () => {
|
|
518
|
+
});
|
|
519
|
+
proc.stdin?.write(stdinPayload);
|
|
520
|
+
proc.stdin?.end();
|
|
521
|
+
proc.on("close", (code) => finish({ code, stdout }));
|
|
620
522
|
});
|
|
621
523
|
}
|
|
622
|
-
function
|
|
623
|
-
const
|
|
624
|
-
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
524
|
+
async function runGoBatch(files, goBinary) {
|
|
525
|
+
const out = /* @__PURE__ */ new Map();
|
|
526
|
+
if (files.length === 0) return out;
|
|
527
|
+
const { path: scriptPath } = await ensureScriptPath(
|
|
528
|
+
_goBatchScriptPath,
|
|
529
|
+
"ws-go-parse",
|
|
530
|
+
"batch.go",
|
|
531
|
+
GO_BATCH_SCRIPT
|
|
532
|
+
);
|
|
533
|
+
_goBatchScriptPath = scriptPath;
|
|
534
|
+
const payload = JSON.stringify(files.map((f) => ({ file: f.file, content: f.content })));
|
|
535
|
+
const result = await withSpawnGate(
|
|
536
|
+
() => runToolchainChild(
|
|
537
|
+
goBinary ?? resolveWin32Command("go"),
|
|
538
|
+
["run", scriptPath],
|
|
539
|
+
payload,
|
|
540
|
+
batchTimeoutMs(files.length)
|
|
541
|
+
)
|
|
542
|
+
);
|
|
543
|
+
if (result?.code !== 0 || !result.stdout.trim()) return out;
|
|
544
|
+
for (const entry of parseParserBatchOutput(result.stdout, "go")) {
|
|
545
|
+
if (entry.error !== void 0) continue;
|
|
546
|
+
out.set(entry.file, {
|
|
547
|
+
file: entry.file,
|
|
548
|
+
lang: "go",
|
|
549
|
+
symbols: entry.symbols.map((s) => ({
|
|
550
|
+
id: 0,
|
|
551
|
+
lang: "go",
|
|
552
|
+
kind: s.kind,
|
|
553
|
+
name: s.name,
|
|
554
|
+
file: entry.file,
|
|
555
|
+
line: s.line,
|
|
556
|
+
col: s.col,
|
|
557
|
+
signature: s.signature ?? "",
|
|
558
|
+
docComment: "",
|
|
559
|
+
scope: s.scope ?? "",
|
|
560
|
+
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
561
|
+
})),
|
|
562
|
+
refs: entry.refs,
|
|
563
|
+
mtimeMs: Date.now()
|
|
564
|
+
});
|
|
632
565
|
}
|
|
633
|
-
return
|
|
566
|
+
return out;
|
|
634
567
|
}
|
|
635
|
-
async function
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|
-
proc.on("close", (code2) => {
|
|
672
|
-
if (settled) return;
|
|
673
|
-
settled = true;
|
|
674
|
-
clearTimeout(timer);
|
|
675
|
-
resolve4({ code: code2, stdout: stdout2 });
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
);
|
|
679
|
-
const { code, stdout } = goResult;
|
|
680
|
-
if (code !== 0 || !stdout.trim()) {
|
|
681
|
-
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
682
|
-
}
|
|
683
|
-
const { symbols: rawSymbols, refs } = parseParserOutput(stdout, lang);
|
|
684
|
-
const symbols = rawSymbols.map((s) => ({
|
|
685
|
-
id: 0,
|
|
686
|
-
lang,
|
|
687
|
-
kind: s.kind,
|
|
688
|
-
name: s.name,
|
|
689
|
-
file: filePath,
|
|
690
|
-
line: s.line,
|
|
691
|
-
col: s.col,
|
|
692
|
-
signature: s.signature ?? "",
|
|
693
|
-
docComment: "",
|
|
694
|
-
scope: s.scope ?? "",
|
|
695
|
-
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
696
|
-
}));
|
|
697
|
-
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
698
|
-
} catch {
|
|
699
|
-
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
568
|
+
async function runPyBatch(files, pythonBinary) {
|
|
569
|
+
const out = /* @__PURE__ */ new Map();
|
|
570
|
+
if (files.length === 0) return out;
|
|
571
|
+
const { path: scriptPath } = await ensureScriptPath(
|
|
572
|
+
_pyBatchScriptPath,
|
|
573
|
+
"ws-py-parse",
|
|
574
|
+
"batch.py",
|
|
575
|
+
PY_BATCH_SCRIPT
|
|
576
|
+
);
|
|
577
|
+
_pyBatchScriptPath = scriptPath;
|
|
578
|
+
const payload = JSON.stringify(files.map((f) => ({ file: f.file, content: f.content })));
|
|
579
|
+
const result = await withSpawnGate(
|
|
580
|
+
() => runToolchainChild(pythonBinary, [scriptPath], payload, batchTimeoutMs(files.length))
|
|
581
|
+
);
|
|
582
|
+
if (result?.code !== 0 || !result.stdout.trim()) return out;
|
|
583
|
+
for (const entry of parseParserBatchOutput(result.stdout, "py")) {
|
|
584
|
+
if (entry.error !== void 0) continue;
|
|
585
|
+
out.set(entry.file, {
|
|
586
|
+
file: entry.file,
|
|
587
|
+
lang: "py",
|
|
588
|
+
symbols: entry.symbols.map((s) => ({
|
|
589
|
+
id: 0,
|
|
590
|
+
lang: "py",
|
|
591
|
+
kind: s.kind,
|
|
592
|
+
name: s.name,
|
|
593
|
+
file: entry.file,
|
|
594
|
+
line: s.line,
|
|
595
|
+
col: s.col,
|
|
596
|
+
signature: s.signature ?? "",
|
|
597
|
+
docComment: "",
|
|
598
|
+
scope: s.scope ?? "",
|
|
599
|
+
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
600
|
+
})),
|
|
601
|
+
refs: entry.refs,
|
|
602
|
+
mtimeMs: Date.now()
|
|
603
|
+
});
|
|
700
604
|
}
|
|
605
|
+
return out;
|
|
701
606
|
}
|
|
702
|
-
var
|
|
703
|
-
var
|
|
704
|
-
"src/codebase-index/
|
|
607
|
+
var MAX_BATCH_FILES, MAX_BATCH_BYTES, GO_BATCH_SCRIPT, PY_BATCH_SCRIPT, _goBatchScriptPath, _pyBatchScriptPath;
|
|
608
|
+
var init_parser_batch = __esm({
|
|
609
|
+
"src/codebase-index/parser-batch.ts"() {
|
|
705
610
|
"use strict";
|
|
706
611
|
init_win32_resolve();
|
|
707
612
|
init_parser_output();
|
|
708
613
|
init_spawn_gate();
|
|
709
|
-
|
|
710
|
-
|
|
614
|
+
MAX_BATCH_FILES = 100;
|
|
615
|
+
MAX_BATCH_BYTES = 8 * 1024 * 1024;
|
|
616
|
+
GO_BATCH_SCRIPT = `
|
|
711
617
|
package main
|
|
712
618
|
|
|
713
619
|
import (
|
|
@@ -731,8 +637,6 @@ type Sym struct {
|
|
|
731
637
|
Scope string \`json:"scope"\`
|
|
732
638
|
}
|
|
733
639
|
|
|
734
|
-
// Ref is a cross-reference emitted alongside the symbols, so one \`go run\`
|
|
735
|
-
// yields both. Module is the import path for CallType "import", else empty.
|
|
736
640
|
type Ref struct {
|
|
737
641
|
ToName string \`json:"toName"\`
|
|
738
642
|
CallType string \`json:"callType"\`
|
|
@@ -740,57 +644,53 @@ type Ref struct {
|
|
|
740
644
|
Module string \`json:"module"\`
|
|
741
645
|
}
|
|
742
646
|
|
|
743
|
-
type
|
|
744
|
-
|
|
745
|
-
|
|
647
|
+
type FileResult struct {
|
|
648
|
+
File string \`json:"file"\`
|
|
649
|
+
Error string \`json:"error,omitempty"\`
|
|
650
|
+
Symbols []Sym \`json:"symbols"\`
|
|
651
|
+
Refs []Ref \`json:"refs"\`
|
|
746
652
|
}
|
|
747
653
|
|
|
748
|
-
|
|
749
|
-
|
|
654
|
+
type BatchResult struct {
|
|
655
|
+
Version int \`json:"version"\`
|
|
656
|
+
Results []FileResult \`json:"results"\`
|
|
750
657
|
}
|
|
751
658
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
659
|
+
type inputFile struct {
|
|
660
|
+
File string \`json:"file"\`
|
|
661
|
+
Content string \`json:"content"\`
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
func parseOne(name string, src []byte) FileResult {
|
|
665
|
+
res := FileResult{File: name, Symbols: []Sym{}, Refs: []Ref{}}
|
|
758
666
|
fset := token.NewFileSet()
|
|
759
667
|
node, err := parser.ParseFile(fset, "src.go", src, 0)
|
|
760
668
|
if err != nil {
|
|
761
|
-
|
|
762
|
-
return
|
|
669
|
+
res.Error = err.Error()
|
|
670
|
+
return res
|
|
763
671
|
}
|
|
764
|
-
|
|
765
|
-
var syms []Sym
|
|
766
|
-
|
|
767
|
-
// Package-level scope
|
|
768
672
|
pkgScope := node.Name.Name
|
|
769
|
-
|
|
770
|
-
// Collect all top-level declarations
|
|
771
673
|
for _, decl := range node.Decls {
|
|
772
674
|
switch d := decl.(type) {
|
|
773
675
|
case *ast.FuncDecl:
|
|
774
|
-
|
|
676
|
+
symName := d.Name.Name
|
|
775
677
|
kind := "function"
|
|
776
678
|
scope := pkgScope
|
|
777
679
|
if d.Recv != nil && len(d.Recv.List) > 0 {
|
|
778
|
-
scope = pkgScope + "." + recvTypeName(d.Recv.List[0].Type) + "." +
|
|
680
|
+
scope = pkgScope + "." + recvTypeName(d.Recv.List[0].Type) + "." + symName
|
|
779
681
|
kind = "method"
|
|
780
682
|
} else {
|
|
781
|
-
scope = pkgScope + "." +
|
|
683
|
+
scope = pkgScope + "." + symName
|
|
782
684
|
}
|
|
783
685
|
pos := fset.Position(d.Pos())
|
|
784
|
-
|
|
785
|
-
syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: scope})
|
|
786
|
-
|
|
686
|
+
res.Symbols = append(res.Symbols, Sym{Name: symName, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: formatFuncSig(d), Scope: scope})
|
|
787
687
|
case *ast.GenDecl:
|
|
788
688
|
for _, spec := range d.Specs {
|
|
789
689
|
switch s := spec.(type) {
|
|
790
690
|
case *ast.TypeSpec:
|
|
791
|
-
|
|
691
|
+
typeName := s.Name.Name
|
|
792
692
|
pos := fset.Position(s.Pos())
|
|
793
|
-
sig := "type " +
|
|
693
|
+
sig := "type " + typeName
|
|
794
694
|
if s.TypeParams != nil {
|
|
795
695
|
sig += formatTypeParams(s.TypeParams)
|
|
796
696
|
}
|
|
@@ -801,64 +701,72 @@ func main() {
|
|
|
801
701
|
} else {
|
|
802
702
|
sig += " = " + formatType(s.Type)
|
|
803
703
|
}
|
|
804
|
-
|
|
805
|
-
|
|
704
|
+
res.Symbols = append(res.Symbols, Sym{Name: typeName, Kind: "type", Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
806
705
|
case *ast.ValueSpec:
|
|
807
706
|
for _, n := range s.Names {
|
|
808
|
-
|
|
707
|
+
valueName := n.Name
|
|
809
708
|
pos := fset.Position(n.Pos())
|
|
810
709
|
kind := "var"
|
|
811
710
|
if d.Tok == token.CONST {
|
|
812
711
|
kind = "const"
|
|
813
712
|
}
|
|
814
|
-
sig := kind + " " +
|
|
713
|
+
sig := kind + " " + valueName
|
|
815
714
|
if s.Type != nil {
|
|
816
715
|
sig += " " + formatType(s.Type)
|
|
817
716
|
}
|
|
818
|
-
|
|
717
|
+
res.Symbols = append(res.Symbols, Sym{Name: valueName, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
819
718
|
}
|
|
820
719
|
}
|
|
821
720
|
}
|
|
822
721
|
}
|
|
823
722
|
}
|
|
824
|
-
|
|
825
|
-
refs := []Ref{}
|
|
826
723
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
827
724
|
switch expr := n.(type) {
|
|
828
725
|
case *ast.CallExpr:
|
|
829
726
|
line := fset.Position(expr.Pos()).Line
|
|
830
727
|
switch fun := expr.Fun.(type) {
|
|
831
728
|
case *ast.Ident:
|
|
832
|
-
|
|
729
|
+
res.Refs = append(res.Refs, Ref{ToName: fun.Name, CallType: "call", Line: line})
|
|
833
730
|
case *ast.SelectorExpr:
|
|
834
|
-
|
|
835
|
-
// declared symbol name, so it resolves the same way the TypeScript
|
|
836
|
-
// and Python extractors' call refs do.
|
|
837
|
-
refs = append(refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
731
|
+
res.Refs = append(res.Refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
838
732
|
}
|
|
839
733
|
case *ast.ImportSpec:
|
|
840
734
|
if expr.Path != nil {
|
|
841
735
|
if importPath, uerr := strconv.Unquote(expr.Path.Value); uerr == nil {
|
|
842
736
|
line := fset.Position(expr.Pos()).Line
|
|
843
|
-
// A Go import names a package, not a symbol; the package's
|
|
844
|
-
// last path segment is the name it is referenced by.
|
|
845
737
|
name := importPath
|
|
846
738
|
if idx := strings.LastIndex(importPath, "/"); idx >= 0 {
|
|
847
739
|
name = importPath[idx+1:]
|
|
848
740
|
}
|
|
849
|
-
|
|
741
|
+
res.Refs = append(res.Refs, Ref{ToName: name, CallType: "import", Line: line, Module: importPath})
|
|
850
742
|
}
|
|
851
743
|
}
|
|
852
744
|
}
|
|
853
745
|
return true
|
|
854
746
|
})
|
|
747
|
+
return res
|
|
748
|
+
}
|
|
855
749
|
|
|
856
|
-
|
|
857
|
-
|
|
750
|
+
func main() {
|
|
751
|
+
raw, err := io.ReadAll(os.Stdin)
|
|
752
|
+
if err != nil {
|
|
753
|
+
out, _ := json.Marshal(BatchResult{Version: 1, Results: []FileResult{}})
|
|
754
|
+
fmt.Print(string(out))
|
|
755
|
+
return
|
|
858
756
|
}
|
|
859
|
-
|
|
757
|
+
var inputs []inputFile
|
|
758
|
+
if err := json.Unmarshal(raw, &inputs); err != nil {
|
|
759
|
+
out, _ := json.Marshal(BatchResult{Version: 1, Results: []FileResult{}})
|
|
760
|
+
fmt.Print(string(out))
|
|
761
|
+
return
|
|
762
|
+
}
|
|
763
|
+
results := make([]FileResult, 0, len(inputs))
|
|
764
|
+
for _, in := range inputs {
|
|
765
|
+
results = append(results, parseOne(in.File, []byte(in.Content)))
|
|
766
|
+
}
|
|
767
|
+
data, err := json.Marshal(BatchResult{Version: 1, Results: results})
|
|
860
768
|
if err != nil {
|
|
861
|
-
fmt.Print(
|
|
769
|
+
fmt.Print("{\\"version\\":1,\\"results\\":[]}")
|
|
862
770
|
return
|
|
863
771
|
}
|
|
864
772
|
fmt.Print(string(data))
|
|
@@ -978,10 +886,8 @@ func formatType(t ast.Expr) string {
|
|
|
978
886
|
case *ast.BasicLit:
|
|
979
887
|
return v.Value
|
|
980
888
|
case *ast.IndexExpr:
|
|
981
|
-
// Generic instantiation with one type arg, e.g. Logger[int].
|
|
982
889
|
return formatType(v.X) + "[" + formatType(v.Index) + "]"
|
|
983
890
|
case *ast.IndexListExpr:
|
|
984
|
-
// Generic instantiation with multiple type args, e.g. Map[K, V].
|
|
985
891
|
args := make([]string, len(v.Indices))
|
|
986
892
|
for i, idx := range v.Indices {
|
|
987
893
|
args[i] = formatType(idx)
|
|
@@ -992,7 +898,187 @@ func formatType(t ast.Expr) string {
|
|
|
992
898
|
}
|
|
993
899
|
}
|
|
994
900
|
`;
|
|
995
|
-
|
|
901
|
+
PY_BATCH_SCRIPT = `import ast, json, sys
|
|
902
|
+
|
|
903
|
+
def get_name(node):
|
|
904
|
+
if isinstance(node, ast.Name):
|
|
905
|
+
return node.id
|
|
906
|
+
elif isinstance(node, ast.Attribute):
|
|
907
|
+
return get_name(node.value) + "." + node.attr
|
|
908
|
+
elif isinstance(node, ast.Subscript):
|
|
909
|
+
return get_name(node.value)
|
|
910
|
+
elif isinstance(node, ast.Call):
|
|
911
|
+
return get_name(node.func)
|
|
912
|
+
elif isinstance(node, ast.Constant):
|
|
913
|
+
return str(node.value)
|
|
914
|
+
return ""
|
|
915
|
+
|
|
916
|
+
def leaf_name(node):
|
|
917
|
+
if isinstance(node, ast.Attribute):
|
|
918
|
+
return node.attr
|
|
919
|
+
if isinstance(node, ast.Name):
|
|
920
|
+
return node.id
|
|
921
|
+
return get_name(node).split(".")[-1]
|
|
922
|
+
|
|
923
|
+
def is_private(name):
|
|
924
|
+
return name.startswith("__") and not name.endswith("__")
|
|
925
|
+
|
|
926
|
+
def parse_one(name, source, module_name):
|
|
927
|
+
result = {"file": name, "symbols": [], "refs": []}
|
|
928
|
+
try:
|
|
929
|
+
tree = ast.parse(source, filename=name)
|
|
930
|
+
except Exception as e:
|
|
931
|
+
result["error"] = str(e)
|
|
932
|
+
return result
|
|
933
|
+
syms = []
|
|
934
|
+
refs = []
|
|
935
|
+
scope_stack = [module_name]
|
|
936
|
+
|
|
937
|
+
def sym(d):
|
|
938
|
+
return {
|
|
939
|
+
"name": d["name"], "kind": d["kind"], "line": d["line"], "col": d["col"],
|
|
940
|
+
"signature": d["signature"], "scope": d["scope"],
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
class Visitor(ast.NodeVisitor):
|
|
944
|
+
def visit_ClassDef(self, node):
|
|
945
|
+
bases = [get_name(b) for b in node.bases]
|
|
946
|
+
sig = "class " + node.name
|
|
947
|
+
if bases:
|
|
948
|
+
sig += "(" + ", ".join(bases) + ")"
|
|
949
|
+
sig += ": ..."
|
|
950
|
+
syms.append(sym({
|
|
951
|
+
"name": node.name, "kind": "class", "line": node.lineno,
|
|
952
|
+
"col": node.col_offset, "signature": sig,
|
|
953
|
+
"scope": ".".join(scope_stack) + "." + node.name,
|
|
954
|
+
}))
|
|
955
|
+
scope_stack.append(node.name)
|
|
956
|
+
self.generic_visit(node)
|
|
957
|
+
scope_stack.pop()
|
|
958
|
+
|
|
959
|
+
def visit_FunctionDef(self, node):
|
|
960
|
+
args = ", ".join(a.arg for a in node.args.args)
|
|
961
|
+
returns = get_name(node.returns) if node.returns is not None else ""
|
|
962
|
+
is_async = isinstance(node, ast.AsyncFunctionDef)
|
|
963
|
+
kind = "function"
|
|
964
|
+
prefix = "def "
|
|
965
|
+
for dec in node.decorator_list:
|
|
966
|
+
d = get_name(dec)
|
|
967
|
+
if d.endswith(".staticmethod"):
|
|
968
|
+
kind = "staticmethod"
|
|
969
|
+
elif d.endswith(".classmethod"):
|
|
970
|
+
kind = "classmethod"
|
|
971
|
+
elif d == "property":
|
|
972
|
+
kind = "property"
|
|
973
|
+
if is_async:
|
|
974
|
+
kind = "async_" + kind
|
|
975
|
+
sig = f"{prefix}{node.name}({args})"
|
|
976
|
+
if returns:
|
|
977
|
+
sig += f" -> {returns}"
|
|
978
|
+
syms.append(sym({
|
|
979
|
+
"name": node.name, "kind": kind, "line": node.lineno,
|
|
980
|
+
"col": node.col_offset, "signature": sig,
|
|
981
|
+
"scope": ".".join(scope_stack) + "." + node.name,
|
|
982
|
+
}))
|
|
983
|
+
|
|
984
|
+
def visit_AsyncFunctionDef(self, node):
|
|
985
|
+
self.visit_FunctionDef(node)
|
|
986
|
+
|
|
987
|
+
def visit_Assign(self, node):
|
|
988
|
+
for target in node.targets:
|
|
989
|
+
if isinstance(target, ast.Name):
|
|
990
|
+
tname = target.id
|
|
991
|
+
if is_private(tname):
|
|
992
|
+
continue
|
|
993
|
+
kind = "const" if tname.isupper() else "var"
|
|
994
|
+
col = target.col_offset if hasattr(target, "col_offset") else 0
|
|
995
|
+
syms.append(sym({
|
|
996
|
+
"name": tname, "kind": kind, "line": node.lineno, "col": col,
|
|
997
|
+
"signature": f"{tname} = ...", "scope": ".".join(scope_stack),
|
|
998
|
+
}))
|
|
999
|
+
|
|
1000
|
+
def visit_AnnAssign(self, node):
|
|
1001
|
+
if isinstance(node.target, ast.Name):
|
|
1002
|
+
tname = node.target.id
|
|
1003
|
+
if is_private(tname):
|
|
1004
|
+
return
|
|
1005
|
+
kind = "const" if tname.isupper() else "var"
|
|
1006
|
+
col = node.target.col_offset if hasattr(node.target, "col_offset") else 0
|
|
1007
|
+
sig = f"{tname}: {get_name(node.annotation)}"
|
|
1008
|
+
if node.value:
|
|
1009
|
+
sig += " = ..."
|
|
1010
|
+
syms.append(sym({
|
|
1011
|
+
"name": tname, "kind": kind, "line": node.lineno, "col": col,
|
|
1012
|
+
"signature": sig, "scope": ".".join(scope_stack),
|
|
1013
|
+
}))
|
|
1014
|
+
|
|
1015
|
+
def visit_Import(self, node):
|
|
1016
|
+
# Parity with the single-file parser: imports are symbols too.
|
|
1017
|
+
for alias in node.names:
|
|
1018
|
+
name = alias.asname or alias.name
|
|
1019
|
+
syms.append(sym({
|
|
1020
|
+
"name": name, "kind": "import", "line": node.lineno,
|
|
1021
|
+
"col": node.col_offset, "signature": f"import {alias.name}",
|
|
1022
|
+
"scope": ".".join(scope_stack),
|
|
1023
|
+
}))
|
|
1024
|
+
|
|
1025
|
+
def visit_ImportFrom(self, node):
|
|
1026
|
+
module = node.module or ""
|
|
1027
|
+
for alias in node.names:
|
|
1028
|
+
name = alias.asname or alias.name
|
|
1029
|
+
syms.append(sym({
|
|
1030
|
+
"name": name, "kind": "import", "line": node.lineno,
|
|
1031
|
+
"col": node.col_offset, "signature": f"from {module} import {alias.name}",
|
|
1032
|
+
"scope": ".".join(scope_stack),
|
|
1033
|
+
}))
|
|
1034
|
+
|
|
1035
|
+
Visitor().visit(tree)
|
|
1036
|
+
|
|
1037
|
+
for node in ast.walk(tree):
|
|
1038
|
+
if isinstance(node, ast.Call):
|
|
1039
|
+
cname = leaf_name(node.func)
|
|
1040
|
+
if cname:
|
|
1041
|
+
refs.append({"toName": cname, "callType": "call", "line": node.lineno})
|
|
1042
|
+
elif isinstance(node, ast.Import):
|
|
1043
|
+
for alias in node.names:
|
|
1044
|
+
refs.append({
|
|
1045
|
+
"toName": alias.name.split(".")[-1], "callType": "import",
|
|
1046
|
+
"line": node.lineno, "module": alias.name,
|
|
1047
|
+
})
|
|
1048
|
+
elif isinstance(node, ast.ImportFrom):
|
|
1049
|
+
module = ("." * (node.level or 0)) + (node.module or "")
|
|
1050
|
+
for alias in node.names:
|
|
1051
|
+
refs.append({
|
|
1052
|
+
"toName": alias.name, "callType": "import",
|
|
1053
|
+
"line": node.lineno, "module": module,
|
|
1054
|
+
})
|
|
1055
|
+
elif isinstance(node, ast.ClassDef):
|
|
1056
|
+
for base in node.bases:
|
|
1057
|
+
bname = leaf_name(base)
|
|
1058
|
+
if bname:
|
|
1059
|
+
refs.append({"toName": bname, "callType": "inherit", "line": node.lineno})
|
|
1060
|
+
|
|
1061
|
+
result["symbols"] = syms
|
|
1062
|
+
result["refs"] = refs
|
|
1063
|
+
return result
|
|
1064
|
+
|
|
1065
|
+
def main():
|
|
1066
|
+
try:
|
|
1067
|
+
inputs = json.loads(sys.stdin.read())
|
|
1068
|
+
except Exception:
|
|
1069
|
+
print(json.dumps({"version": 1, "results": []}))
|
|
1070
|
+
return
|
|
1071
|
+
results = []
|
|
1072
|
+
for entry in inputs:
|
|
1073
|
+
name = entry.get("file", "")
|
|
1074
|
+
module_name = name.rsplit("/", 1)[-1].rsplit("\\\\", 1)[-1][:-3]
|
|
1075
|
+
results.append(parse_one(name, entry.get("content", ""), module_name))
|
|
1076
|
+
print(json.dumps({"version": 1, "results": results}))
|
|
1077
|
+
|
|
1078
|
+
main()
|
|
1079
|
+
`;
|
|
1080
|
+
_goBatchScriptPath = null;
|
|
1081
|
+
_pyBatchScriptPath = null;
|
|
996
1082
|
}
|
|
997
1083
|
});
|
|
998
1084
|
|
|
@@ -1002,7 +1088,7 @@ __export(generic_parser_exports, {
|
|
|
1002
1088
|
GENERIC_MAX_FILE_CHARS: () => GENERIC_MAX_FILE_CHARS,
|
|
1003
1089
|
GENERIC_MAX_SYMBOLS_DEFAULT: () => GENERIC_MAX_SYMBOLS_DEFAULT,
|
|
1004
1090
|
parseGeneric: () => parseGeneric,
|
|
1005
|
-
parseSymbols: () =>
|
|
1091
|
+
parseSymbols: () => parseSymbols
|
|
1006
1092
|
});
|
|
1007
1093
|
function patternsFor(lang) {
|
|
1008
1094
|
return LANG_PATTERNS[lang] ?? LANG_PATTERNS.other ?? [];
|
|
@@ -1090,7 +1176,7 @@ function parseGeneric(opts) {
|
|
|
1090
1176
|
}
|
|
1091
1177
|
return { file, lang, symbols, mtimeMs };
|
|
1092
1178
|
}
|
|
1093
|
-
async function
|
|
1179
|
+
async function parseSymbols(opts) {
|
|
1094
1180
|
return parseGeneric(opts);
|
|
1095
1181
|
}
|
|
1096
1182
|
var C_LIKE, LANG_PATTERNS, KEYWORDS, GENERIC_MAX_SYMBOLS_DEFAULT, GENERIC_MAX_FILE_CHARS;
|
|
@@ -1323,13 +1409,14 @@ var init_generic_parser = __esm({
|
|
|
1323
1409
|
var py_parser_exports = {};
|
|
1324
1410
|
__export(py_parser_exports, {
|
|
1325
1411
|
detectLang: () => detectLang,
|
|
1326
|
-
parseSymbols: () =>
|
|
1412
|
+
parseSymbols: () => parseSymbols2,
|
|
1413
|
+
resolvePythonBinary: () => resolvePythonBinary
|
|
1327
1414
|
});
|
|
1328
1415
|
import { spawn as spawn2 } from "node:child_process";
|
|
1329
1416
|
import * as fs5 from "node:fs/promises";
|
|
1330
1417
|
import * as os2 from "node:os";
|
|
1331
1418
|
import * as path8 from "node:path";
|
|
1332
|
-
async function
|
|
1419
|
+
async function parseSymbols2(opts) {
|
|
1333
1420
|
const { file, content, lang } = opts;
|
|
1334
1421
|
try {
|
|
1335
1422
|
const native = await withSpawnGate(() => syncPyParse(file, content, lang));
|
|
@@ -1403,6 +1490,10 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
|
1403
1490
|
});
|
|
1404
1491
|
});
|
|
1405
1492
|
}
|
|
1493
|
+
function resolvePythonBinary() {
|
|
1494
|
+
cachedPyBinary ??= resolvePython();
|
|
1495
|
+
return cachedPyBinary;
|
|
1496
|
+
}
|
|
1406
1497
|
async function syncPyParse(filePath, content, lang) {
|
|
1407
1498
|
try {
|
|
1408
1499
|
if (!_cachedScriptPath) {
|
|
@@ -1693,9 +1784,713 @@ for node in ast.walk(tree):
|
|
|
1693
1784
|
if name:
|
|
1694
1785
|
refs.append({"toName": name, "callType": "inherit", "line": node.lineno})
|
|
1695
1786
|
|
|
1696
|
-
print(json.dumps({"symbols": [s.to_dict() for s in syms], "refs": refs}))
|
|
1787
|
+
print(json.dumps({"symbols": [s.to_dict() for s in syms], "refs": refs}))
|
|
1788
|
+
`;
|
|
1789
|
+
_cachedScriptPath = null;
|
|
1790
|
+
}
|
|
1791
|
+
});
|
|
1792
|
+
|
|
1793
|
+
// src/codebase-index/ts-parser.ts
|
|
1794
|
+
var ts_parser_exports = {};
|
|
1795
|
+
__export(ts_parser_exports, {
|
|
1796
|
+
detectLang: () => detectLang,
|
|
1797
|
+
parseSymbols: () => parseSymbols3
|
|
1798
|
+
});
|
|
1799
|
+
function loadTypescript() {
|
|
1800
|
+
tsLoad ??= import("@typescript/typescript6").then((m) => {
|
|
1801
|
+
ts = m.default ?? m;
|
|
1802
|
+
return ts;
|
|
1803
|
+
});
|
|
1804
|
+
return tsLoad;
|
|
1805
|
+
}
|
|
1806
|
+
function kindMap() {
|
|
1807
|
+
kindMapCache ??= {
|
|
1808
|
+
[ts.SyntaxKind.ClassDeclaration]: "class",
|
|
1809
|
+
[ts.SyntaxKind.InterfaceDeclaration]: "interface",
|
|
1810
|
+
[ts.SyntaxKind.EnumDeclaration]: "enum",
|
|
1811
|
+
[ts.SyntaxKind.TypeAliasDeclaration]: "type",
|
|
1812
|
+
[ts.SyntaxKind.FunctionDeclaration]: "function",
|
|
1813
|
+
[ts.SyntaxKind.MethodDeclaration]: "method",
|
|
1814
|
+
[ts.SyntaxKind.GetAccessor]: "property",
|
|
1815
|
+
[ts.SyntaxKind.SetAccessor]: "property",
|
|
1816
|
+
[ts.SyntaxKind.PropertyDeclaration]: "property",
|
|
1817
|
+
[ts.SyntaxKind.Parameter]: "parameter",
|
|
1818
|
+
[ts.SyntaxKind.NamespaceExportDeclaration]: "namespace"
|
|
1819
|
+
};
|
|
1820
|
+
return kindMapCache;
|
|
1821
|
+
}
|
|
1822
|
+
function kindOf(node) {
|
|
1823
|
+
if (ts.isVariableDeclaration(node)) {
|
|
1824
|
+
const parent = node.parent;
|
|
1825
|
+
if (ts.isVariableDeclarationList(parent)) {
|
|
1826
|
+
const flags = parent.flags;
|
|
1827
|
+
if (flags & ts.NodeFlags.Let) return "let";
|
|
1828
|
+
if (flags & ts.NodeFlags.Const) return "const";
|
|
1829
|
+
return "var";
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
1833
|
+
return kindMap()[node.kind] ?? null;
|
|
1834
|
+
}
|
|
1835
|
+
function getSignature(printer, node, sourceFile) {
|
|
1836
|
+
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
1837
|
+
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
1838
|
+
}
|
|
1839
|
+
function getJsDoc(node, sourceFile) {
|
|
1840
|
+
const fullText = sourceFile.getFullText();
|
|
1841
|
+
const nodePos = node.getFullStart();
|
|
1842
|
+
const comments = ts.getLeadingCommentRanges(fullText, nodePos);
|
|
1843
|
+
if (!comments) return "";
|
|
1844
|
+
for (const range of comments) {
|
|
1845
|
+
const commentText = fullText.slice(range.pos, range.end);
|
|
1846
|
+
const trimmed = commentText.trim();
|
|
1847
|
+
if (trimmed.startsWith("/**") && trimmed.endsWith("*/")) {
|
|
1848
|
+
const inner = trimmed.slice(3, -2).replace(/^[ \t]*\*[ ]?/gm, "").trim();
|
|
1849
|
+
return inner.split("\n")[0]?.trim().slice(0, 200) ?? "";
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
return "";
|
|
1853
|
+
}
|
|
1854
|
+
function pushScopeName(node, parts) {
|
|
1855
|
+
if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
|
|
1856
|
+
parts.push(node.name?.text ?? "Anon");
|
|
1857
|
+
} else if (ts.isMethodDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node) || ts.isPropertyDeclaration(node) || ts.isFunctionDeclaration(node)) {
|
|
1858
|
+
if (node.name && ts.isIdentifier(node.name)) {
|
|
1859
|
+
parts.push(node.name.text);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
async function parseSymbols3(opts) {
|
|
1864
|
+
const { file, content, lang } = opts;
|
|
1865
|
+
await loadTypescript();
|
|
1866
|
+
let sourceFile;
|
|
1867
|
+
try {
|
|
1868
|
+
sourceFile = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true);
|
|
1869
|
+
} catch {
|
|
1870
|
+
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
1871
|
+
}
|
|
1872
|
+
const symbols = [];
|
|
1873
|
+
const refs = [];
|
|
1874
|
+
const printer = ts.createPrinter({});
|
|
1875
|
+
function visit(node, funcDepth, scopeParts) {
|
|
1876
|
+
const kind = kindOf(node);
|
|
1877
|
+
if (kind) {
|
|
1878
|
+
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && funcDepth > 0) {
|
|
1879
|
+
} else {
|
|
1880
|
+
const nameNode = node.name;
|
|
1881
|
+
if (!nameNode || !ts.isIdentifier(nameNode)) {
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
const name = nameNode.text;
|
|
1885
|
+
const pos2 = nameNode.getStart(sourceFile);
|
|
1886
|
+
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(pos2);
|
|
1887
|
+
const scope = scopeParts.join(".");
|
|
1888
|
+
const signature = getSignature(printer, node, sourceFile);
|
|
1889
|
+
const docComment = getJsDoc(node, sourceFile);
|
|
1890
|
+
const text = [name, signature, docComment].filter(Boolean).join(" | ");
|
|
1891
|
+
symbols.push({
|
|
1892
|
+
id: 0,
|
|
1893
|
+
lang,
|
|
1894
|
+
kind,
|
|
1895
|
+
name,
|
|
1896
|
+
file,
|
|
1897
|
+
line: line2 + 1,
|
|
1898
|
+
col: character,
|
|
1899
|
+
signature,
|
|
1900
|
+
docComment,
|
|
1901
|
+
scope,
|
|
1902
|
+
text
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
const pos = node.getStart(sourceFile);
|
|
1907
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
|
|
1908
|
+
const lineNum = line + 1;
|
|
1909
|
+
if (ts.isCallExpression(node)) {
|
|
1910
|
+
const expr = node.expression;
|
|
1911
|
+
if (ts.isIdentifier(expr)) {
|
|
1912
|
+
refs.push({ fromId: 0, toName: expr.text, callType: "call", line: lineNum });
|
|
1913
|
+
}
|
|
1914
|
+
} else if (ts.isPropertyAccessExpression(node)) {
|
|
1915
|
+
if (ts.isIdentifier(node.expression)) {
|
|
1916
|
+
refs.push({ fromId: 0, toName: node.expression.text, callType: "call", line: lineNum });
|
|
1917
|
+
}
|
|
1918
|
+
} else if (ts.isTypeReferenceNode(node)) {
|
|
1919
|
+
const name = getTypeName(node.typeName);
|
|
1920
|
+
if (name) refs.push({ fromId: 0, toName: name, callType: "type_ref", line: lineNum });
|
|
1921
|
+
} else if (ts.isHeritageClause(node)) {
|
|
1922
|
+
for (const t of node.types) {
|
|
1923
|
+
const name = getTypeName(t.expression);
|
|
1924
|
+
if (name)
|
|
1925
|
+
refs.push({
|
|
1926
|
+
fromId: 0,
|
|
1927
|
+
toName: name,
|
|
1928
|
+
callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement",
|
|
1929
|
+
line: lineNum
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
} else if (ts.isImportDeclaration(node)) {
|
|
1933
|
+
emitImportSpecifierRefs(node, refs, lineNum);
|
|
1934
|
+
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
|
|
1935
|
+
emitExportSpecifierRefs(node, refs, lineNum);
|
|
1936
|
+
}
|
|
1937
|
+
const scopeIdx = scopeParts.length;
|
|
1938
|
+
pushScopeName(node, scopeParts);
|
|
1939
|
+
const childFuncDepth = ts.isFunctionLike(node) ? funcDepth + 1 : funcDepth;
|
|
1940
|
+
ts.forEachChild(node, (child) => visit(child, childFuncDepth, scopeParts));
|
|
1941
|
+
scopeParts.length = scopeIdx;
|
|
1942
|
+
}
|
|
1943
|
+
visit(sourceFile, 0, []);
|
|
1944
|
+
return { file, lang, symbols, refs: deduplicateRefs(refs), mtimeMs: Date.now() };
|
|
1945
|
+
}
|
|
1946
|
+
function getTypeName(name) {
|
|
1947
|
+
if (ts.isIdentifier(name)) return name.text;
|
|
1948
|
+
if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
|
|
1949
|
+
return "";
|
|
1950
|
+
}
|
|
1951
|
+
function deduplicateRefs(refs) {
|
|
1952
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1953
|
+
return refs.filter((r) => {
|
|
1954
|
+
const key = `${r.toName}:${r.callType}:${r.line}:${r.module ?? ""}`;
|
|
1955
|
+
if (seen.has(key)) return false;
|
|
1956
|
+
seen.add(key);
|
|
1957
|
+
return true;
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1960
|
+
function getImportSpecifierName(spec) {
|
|
1961
|
+
return spec.propertyName?.text ?? spec.name.text;
|
|
1962
|
+
}
|
|
1963
|
+
function emitImportSpecifierRefs(node, refs, lineNum) {
|
|
1964
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
1965
|
+
const clause = node.importClause;
|
|
1966
|
+
if (!clause) {
|
|
1967
|
+
if (module) {
|
|
1968
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
1969
|
+
}
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
if (clause.name) {
|
|
1973
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
1974
|
+
}
|
|
1975
|
+
const bindings = clause.namedBindings;
|
|
1976
|
+
if (!bindings) return;
|
|
1977
|
+
if (ts.isNamedImports(bindings)) {
|
|
1978
|
+
for (const element of bindings.elements) {
|
|
1979
|
+
refs.push({
|
|
1980
|
+
fromId: 0,
|
|
1981
|
+
toName: getImportSpecifierName(element),
|
|
1982
|
+
callType: "import",
|
|
1983
|
+
line: lineNum,
|
|
1984
|
+
module
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
} else if (ts.isNamespaceImport(bindings)) {
|
|
1988
|
+
refs.push({
|
|
1989
|
+
fromId: 0,
|
|
1990
|
+
toName: bindings.name.text,
|
|
1991
|
+
callType: "import",
|
|
1992
|
+
line: lineNum,
|
|
1993
|
+
module
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
function moduleSpecifierOf(node) {
|
|
1998
|
+
return node && ts.isStringLiteral(node) ? node.text : void 0;
|
|
1999
|
+
}
|
|
2000
|
+
function emitExportSpecifierRefs(node, refs, lineNum) {
|
|
2001
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
2002
|
+
const clause = node.exportClause;
|
|
2003
|
+
if (clause && ts.isNamespaceExport(clause)) {
|
|
2004
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
if (clause && ts.isNamedExports(clause)) {
|
|
2008
|
+
for (const element of clause.elements) {
|
|
2009
|
+
const originalName = element.propertyName?.text ?? element.name.text;
|
|
2010
|
+
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum, module });
|
|
2011
|
+
}
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
if (module) {
|
|
2015
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
var ts, tsLoad, kindMapCache;
|
|
2019
|
+
var init_ts_parser = __esm({
|
|
2020
|
+
"src/codebase-index/ts-parser.ts"() {
|
|
2021
|
+
"use strict";
|
|
2022
|
+
init_languages();
|
|
2023
|
+
tsLoad = null;
|
|
2024
|
+
kindMapCache = null;
|
|
2025
|
+
}
|
|
2026
|
+
});
|
|
2027
|
+
|
|
2028
|
+
// src/codebase-index/go-parser.ts
|
|
2029
|
+
var go_parser_exports = {};
|
|
2030
|
+
__export(go_parser_exports, {
|
|
2031
|
+
detectLang: () => detectLang,
|
|
2032
|
+
parseSymbols: () => parseSymbols4
|
|
2033
|
+
});
|
|
2034
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
2035
|
+
import * as os3 from "node:os";
|
|
2036
|
+
import * as path9 from "node:path";
|
|
2037
|
+
import * as fs6 from "node:fs/promises";
|
|
2038
|
+
async function parseSymbols4(opts) {
|
|
2039
|
+
const { file, content, lang } = opts;
|
|
2040
|
+
try {
|
|
2041
|
+
const parsed = await withSpawnGate(() => syncGoParse(file, content, lang));
|
|
2042
|
+
if (parsed.symbols.length > 0) {
|
|
2043
|
+
return parsed;
|
|
2044
|
+
}
|
|
2045
|
+
const fallback = fallbackParse(file, content, lang);
|
|
2046
|
+
return parsed.refs?.length ? { ...fallback, refs: parsed.refs } : fallback;
|
|
2047
|
+
} catch {
|
|
2048
|
+
return fallbackParse(file, content, lang);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
function fallbackParse(filePath, content, lang) {
|
|
2052
|
+
if (!/^\s*package\s+[A-Za-z_]\w*/m.test(content) || hasUnbalancedDelimiters(content)) {
|
|
2053
|
+
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
2054
|
+
}
|
|
2055
|
+
const symbols = [];
|
|
2056
|
+
const packageName = content.match(/^\s*package\s+([A-Za-z_]\w*)/m)?.[1] ?? "";
|
|
2057
|
+
const lines = content.split(/\r?\n/);
|
|
2058
|
+
for (const [idx, line] of lines.entries()) {
|
|
2059
|
+
const trimmed = line.trimStart();
|
|
2060
|
+
const col = line.length - trimmed.length + 1;
|
|
2061
|
+
const fn = /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(/.exec(trimmed);
|
|
2062
|
+
if (fn?.[1]) {
|
|
2063
|
+
addFallbackSymbol(symbols, {
|
|
2064
|
+
filePath,
|
|
2065
|
+
lang,
|
|
2066
|
+
kind: trimmed.startsWith("func (") ? "method" : "function",
|
|
2067
|
+
name: fn[1],
|
|
2068
|
+
line: idx + 1,
|
|
2069
|
+
col,
|
|
2070
|
+
signature: trimmed,
|
|
2071
|
+
scope: packageName ? `${packageName}.${fn[1]}` : fn[1]
|
|
2072
|
+
});
|
|
2073
|
+
continue;
|
|
2074
|
+
}
|
|
2075
|
+
const typeDecl = /^type\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
2076
|
+
if (typeDecl?.[1]) {
|
|
2077
|
+
addFallbackSymbol(symbols, {
|
|
2078
|
+
filePath,
|
|
2079
|
+
lang,
|
|
2080
|
+
kind: "type",
|
|
2081
|
+
name: typeDecl[1],
|
|
2082
|
+
line: idx + 1,
|
|
2083
|
+
col,
|
|
2084
|
+
signature: trimmed,
|
|
2085
|
+
scope: packageName
|
|
2086
|
+
});
|
|
2087
|
+
continue;
|
|
2088
|
+
}
|
|
2089
|
+
const valueDecl = /^(const|var)\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
2090
|
+
if (valueDecl?.[1] && valueDecl[2]) {
|
|
2091
|
+
addFallbackSymbol(symbols, {
|
|
2092
|
+
filePath,
|
|
2093
|
+
lang,
|
|
2094
|
+
kind: valueDecl[1],
|
|
2095
|
+
name: valueDecl[2],
|
|
2096
|
+
line: idx + 1,
|
|
2097
|
+
col,
|
|
2098
|
+
signature: trimmed,
|
|
2099
|
+
scope: packageName
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
2104
|
+
}
|
|
2105
|
+
function addFallbackSymbol(symbols, opts) {
|
|
2106
|
+
symbols.push({
|
|
2107
|
+
id: 0,
|
|
2108
|
+
lang: opts.lang,
|
|
2109
|
+
kind: opts.kind,
|
|
2110
|
+
name: opts.name,
|
|
2111
|
+
file: opts.filePath,
|
|
2112
|
+
line: opts.line,
|
|
2113
|
+
col: opts.col,
|
|
2114
|
+
signature: opts.signature,
|
|
2115
|
+
docComment: "",
|
|
2116
|
+
scope: opts.scope,
|
|
2117
|
+
text: `${opts.name} ${opts.signature}`.trim()
|
|
2118
|
+
});
|
|
2119
|
+
}
|
|
2120
|
+
function hasUnbalancedDelimiters(content) {
|
|
2121
|
+
const pairs = { "(": ")", "[": "]", "{": "}" };
|
|
2122
|
+
const closers = new Set(Object.values(pairs));
|
|
2123
|
+
const stack = [];
|
|
2124
|
+
for (const ch of content) {
|
|
2125
|
+
if (pairs[ch]) {
|
|
2126
|
+
stack.push(pairs[ch]);
|
|
2127
|
+
} else if (closers.has(ch) && stack.pop() !== ch) {
|
|
2128
|
+
return true;
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
return stack.length > 0;
|
|
2132
|
+
}
|
|
2133
|
+
async function syncGoParse(filePath, content, lang) {
|
|
2134
|
+
try {
|
|
2135
|
+
let scriptPath = _cachedGoScriptPath;
|
|
2136
|
+
if (!scriptPath) {
|
|
2137
|
+
const tmpDir = await fs6.mkdtemp(path9.join(os3.tmpdir(), "ws-go-parse-"));
|
|
2138
|
+
scriptPath = path9.join(tmpDir, "parse.go");
|
|
2139
|
+
await fs6.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
2140
|
+
_cachedGoScriptPath = scriptPath;
|
|
2141
|
+
}
|
|
2142
|
+
const goBinary = resolveWin32Command("go");
|
|
2143
|
+
const goResult = await new Promise(
|
|
2144
|
+
(resolve4, reject) => {
|
|
2145
|
+
let settled = false;
|
|
2146
|
+
const proc = spawn3(goBinary, ["run", scriptPath], {
|
|
2147
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2148
|
+
windowsHide: true
|
|
2149
|
+
});
|
|
2150
|
+
proc.on("error", (err) => {
|
|
2151
|
+
if (settled) return;
|
|
2152
|
+
settled = true;
|
|
2153
|
+
reject(err);
|
|
2154
|
+
});
|
|
2155
|
+
let stdout2 = "";
|
|
2156
|
+
proc.stdout?.on("data", (chunk) => {
|
|
2157
|
+
stdout2 += chunk.toString();
|
|
2158
|
+
});
|
|
2159
|
+
proc.stderr?.resume();
|
|
2160
|
+
proc.stdin?.write(content);
|
|
2161
|
+
proc.stdin?.end();
|
|
2162
|
+
const timer = setTimeout(() => {
|
|
2163
|
+
if (settled) return;
|
|
2164
|
+
settled = true;
|
|
2165
|
+
proc.kill("SIGKILL");
|
|
2166
|
+
reject(new Error("timeout"));
|
|
2167
|
+
}, 15e3);
|
|
2168
|
+
timer.unref?.();
|
|
2169
|
+
proc.on("close", (code2) => {
|
|
2170
|
+
if (settled) return;
|
|
2171
|
+
settled = true;
|
|
2172
|
+
clearTimeout(timer);
|
|
2173
|
+
resolve4({ code: code2, stdout: stdout2 });
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
);
|
|
2177
|
+
const { code, stdout } = goResult;
|
|
2178
|
+
if (code !== 0 || !stdout.trim()) {
|
|
2179
|
+
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
2180
|
+
}
|
|
2181
|
+
const { symbols: rawSymbols, refs } = parseParserOutput(stdout, lang);
|
|
2182
|
+
const symbols = rawSymbols.map((s) => ({
|
|
2183
|
+
id: 0,
|
|
2184
|
+
lang,
|
|
2185
|
+
kind: s.kind,
|
|
2186
|
+
name: s.name,
|
|
2187
|
+
file: filePath,
|
|
2188
|
+
line: s.line,
|
|
2189
|
+
col: s.col,
|
|
2190
|
+
signature: s.signature ?? "",
|
|
2191
|
+
docComment: "",
|
|
2192
|
+
scope: s.scope ?? "",
|
|
2193
|
+
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
2194
|
+
}));
|
|
2195
|
+
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
2196
|
+
} catch {
|
|
2197
|
+
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
var GO_PARSE_SCRIPT, _cachedGoScriptPath;
|
|
2201
|
+
var init_go_parser = __esm({
|
|
2202
|
+
"src/codebase-index/go-parser.ts"() {
|
|
2203
|
+
"use strict";
|
|
2204
|
+
init_win32_resolve();
|
|
2205
|
+
init_parser_output();
|
|
2206
|
+
init_spawn_gate();
|
|
2207
|
+
init_languages();
|
|
2208
|
+
GO_PARSE_SCRIPT = `
|
|
2209
|
+
package main
|
|
2210
|
+
|
|
2211
|
+
import (
|
|
2212
|
+
"encoding/json"
|
|
2213
|
+
"fmt"
|
|
2214
|
+
"go/ast"
|
|
2215
|
+
"go/parser"
|
|
2216
|
+
"go/token"
|
|
2217
|
+
"io"
|
|
2218
|
+
"os"
|
|
2219
|
+
"strconv"
|
|
2220
|
+
"strings"
|
|
2221
|
+
)
|
|
2222
|
+
|
|
2223
|
+
type Sym struct {
|
|
2224
|
+
Name string \`json:"name"\`
|
|
2225
|
+
Kind string \`json:"kind"\`
|
|
2226
|
+
Line int \`json:"line"\`
|
|
2227
|
+
Col int \`json:"col"\`
|
|
2228
|
+
Signature string \`json:"signature"\`
|
|
2229
|
+
Scope string \`json:"scope"\`
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
// Ref is a cross-reference emitted alongside the symbols, so one \`go run\`
|
|
2233
|
+
// yields both. Module is the import path for CallType "import", else empty.
|
|
2234
|
+
type Ref struct {
|
|
2235
|
+
ToName string \`json:"toName"\`
|
|
2236
|
+
CallType string \`json:"callType"\`
|
|
2237
|
+
Line int \`json:"line"\`
|
|
2238
|
+
Module string \`json:"module"\`
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
type Result struct {
|
|
2242
|
+
Symbols []Sym \`json:"symbols"\`
|
|
2243
|
+
Refs []Ref \`json:"refs"\`
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
func emptyResult() string {
|
|
2247
|
+
return "{\\"symbols\\":[],\\"refs\\":[]}"
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
func main() {
|
|
2251
|
+
src, err := io.ReadAll(os.Stdin)
|
|
2252
|
+
if err != nil {
|
|
2253
|
+
fmt.Print(emptyResult())
|
|
2254
|
+
return
|
|
2255
|
+
}
|
|
2256
|
+
fset := token.NewFileSet()
|
|
2257
|
+
node, err := parser.ParseFile(fset, "src.go", src, 0)
|
|
2258
|
+
if err != nil {
|
|
2259
|
+
fmt.Print(emptyResult())
|
|
2260
|
+
return
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
var syms []Sym
|
|
2264
|
+
|
|
2265
|
+
// Package-level scope
|
|
2266
|
+
pkgScope := node.Name.Name
|
|
2267
|
+
|
|
2268
|
+
// Collect all top-level declarations
|
|
2269
|
+
for _, decl := range node.Decls {
|
|
2270
|
+
switch d := decl.(type) {
|
|
2271
|
+
case *ast.FuncDecl:
|
|
2272
|
+
name := d.Name.Name
|
|
2273
|
+
kind := "function"
|
|
2274
|
+
scope := pkgScope
|
|
2275
|
+
if d.Recv != nil && len(d.Recv.List) > 0 {
|
|
2276
|
+
scope = pkgScope + "." + recvTypeName(d.Recv.List[0].Type) + "." + name
|
|
2277
|
+
kind = "method"
|
|
2278
|
+
} else {
|
|
2279
|
+
scope = pkgScope + "." + name
|
|
2280
|
+
}
|
|
2281
|
+
pos := fset.Position(d.Pos())
|
|
2282
|
+
sig := formatFuncSig(d)
|
|
2283
|
+
syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: scope})
|
|
2284
|
+
|
|
2285
|
+
case *ast.GenDecl:
|
|
2286
|
+
for _, spec := range d.Specs {
|
|
2287
|
+
switch s := spec.(type) {
|
|
2288
|
+
case *ast.TypeSpec:
|
|
2289
|
+
name := s.Name.Name
|
|
2290
|
+
pos := fset.Position(s.Pos())
|
|
2291
|
+
sig := "type " + name
|
|
2292
|
+
if s.TypeParams != nil {
|
|
2293
|
+
sig += formatTypeParams(s.TypeParams)
|
|
2294
|
+
}
|
|
2295
|
+
if st, ok := s.Type.(*ast.StructType); ok {
|
|
2296
|
+
sig += " = struct { " + formatFields(st.Fields.List) + " }"
|
|
2297
|
+
} else if it, ok := s.Type.(*ast.InterfaceType); ok {
|
|
2298
|
+
sig += " = interface { " + formatMethods(it.Methods.List) + " }"
|
|
2299
|
+
} else {
|
|
2300
|
+
sig += " = " + formatType(s.Type)
|
|
2301
|
+
}
|
|
2302
|
+
syms = append(syms, Sym{Name: name, Kind: "type", Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
2303
|
+
|
|
2304
|
+
case *ast.ValueSpec:
|
|
2305
|
+
for _, n := range s.Names {
|
|
2306
|
+
name := n.Name
|
|
2307
|
+
pos := fset.Position(n.Pos())
|
|
2308
|
+
kind := "var"
|
|
2309
|
+
if d.Tok == token.CONST {
|
|
2310
|
+
kind = "const"
|
|
2311
|
+
}
|
|
2312
|
+
sig := kind + " " + name
|
|
2313
|
+
if s.Type != nil {
|
|
2314
|
+
sig += " " + formatType(s.Type)
|
|
2315
|
+
}
|
|
2316
|
+
syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
refs := []Ref{}
|
|
2324
|
+
ast.Inspect(node, func(n ast.Node) bool {
|
|
2325
|
+
switch expr := n.(type) {
|
|
2326
|
+
case *ast.CallExpr:
|
|
2327
|
+
line := fset.Position(expr.Pos()).Line
|
|
2328
|
+
switch fun := expr.Fun.(type) {
|
|
2329
|
+
case *ast.Ident:
|
|
2330
|
+
refs = append(refs, Ref{ToName: fun.Name, CallType: "call", Line: line})
|
|
2331
|
+
case *ast.SelectorExpr:
|
|
2332
|
+
// Record the selected name (\`Join\` of \`filepath.Join\`): it is the
|
|
2333
|
+
// declared symbol name, so it resolves the same way the TypeScript
|
|
2334
|
+
// and Python extractors' call refs do.
|
|
2335
|
+
refs = append(refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
2336
|
+
}
|
|
2337
|
+
case *ast.ImportSpec:
|
|
2338
|
+
if expr.Path != nil {
|
|
2339
|
+
if importPath, uerr := strconv.Unquote(expr.Path.Value); uerr == nil {
|
|
2340
|
+
line := fset.Position(expr.Pos()).Line
|
|
2341
|
+
// A Go import names a package, not a symbol; the package's
|
|
2342
|
+
// last path segment is the name it is referenced by.
|
|
2343
|
+
name := importPath
|
|
2344
|
+
if idx := strings.LastIndex(importPath, "/"); idx >= 0 {
|
|
2345
|
+
name = importPath[idx+1:]
|
|
2346
|
+
}
|
|
2347
|
+
refs = append(refs, Ref{ToName: name, CallType: "import", Line: line, Module: importPath})
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
return true
|
|
2352
|
+
})
|
|
2353
|
+
|
|
2354
|
+
if syms == nil {
|
|
2355
|
+
syms = []Sym{}
|
|
2356
|
+
}
|
|
2357
|
+
data, err := json.Marshal(Result{Symbols: syms, Refs: refs})
|
|
2358
|
+
if err != nil {
|
|
2359
|
+
fmt.Print(emptyResult())
|
|
2360
|
+
return
|
|
2361
|
+
}
|
|
2362
|
+
fmt.Print(string(data))
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
func recvTypeName(t ast.Expr) string {
|
|
2366
|
+
switch v := t.(type) {
|
|
2367
|
+
case *ast.Ident:
|
|
2368
|
+
return v.Name
|
|
2369
|
+
case *ast.StarExpr:
|
|
2370
|
+
return recvTypeName(v.X)
|
|
2371
|
+
default:
|
|
2372
|
+
return "?"
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
func formatFuncSig(d *ast.FuncDecl) string {
|
|
2377
|
+
scope := ""
|
|
2378
|
+
if d.Recv != nil && len(d.Recv.List) > 0 {
|
|
2379
|
+
scope = "(" + formatFieldList(d.Recv.List) + ") "
|
|
2380
|
+
}
|
|
2381
|
+
scope += formatFuncType(d.Type)
|
|
2382
|
+
return "func " + scope
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
func formatFuncType(f *ast.FuncType) string {
|
|
2386
|
+
params := formatFieldList(f.Params.List)
|
|
2387
|
+
results := ""
|
|
2388
|
+
if f.Results != nil {
|
|
2389
|
+
results = " -> " + formatFieldList(f.Results.List)
|
|
2390
|
+
}
|
|
2391
|
+
return params + results
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
func formatFieldList(fields []*ast.Field) string {
|
|
2395
|
+
if len(fields) == 0 {
|
|
2396
|
+
return "()"
|
|
2397
|
+
}
|
|
2398
|
+
names := make([]string, 0, len(fields))
|
|
2399
|
+
for _, f := range fields {
|
|
2400
|
+
name := ""
|
|
2401
|
+
if len(f.Names) > 0 {
|
|
2402
|
+
name = f.Names[0].Name
|
|
2403
|
+
}
|
|
2404
|
+
t := formatType(f.Type)
|
|
2405
|
+
if name != "" {
|
|
2406
|
+
names = append(names, name+" "+t)
|
|
2407
|
+
} else {
|
|
2408
|
+
names = append(names, t)
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
return "(" + strings.Join(names, ", ") + ")"
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
func formatFields(fields []*ast.Field) string {
|
|
2415
|
+
lines := make([]string, 0)
|
|
2416
|
+
for _, f := range fields {
|
|
2417
|
+
name := ""
|
|
2418
|
+
if len(f.Names) > 0 {
|
|
2419
|
+
name = f.Names[0].Name
|
|
2420
|
+
}
|
|
2421
|
+
t := formatType(f.Type)
|
|
2422
|
+
if name != "" {
|
|
2423
|
+
lines = append(lines, name+" "+t)
|
|
2424
|
+
} else {
|
|
2425
|
+
lines = append(lines, t)
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
return strings.Join(lines, "; ")
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
func formatMethods(fields []*ast.Field) string {
|
|
2432
|
+
return formatFields(fields)
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
func formatTypeParams(tp *ast.FieldList) string {
|
|
2436
|
+
if tp == nil || len(tp.List) == 0 {
|
|
2437
|
+
return ""
|
|
2438
|
+
}
|
|
2439
|
+
params := make([]string, len(tp.List))
|
|
2440
|
+
for i, p := range tp.List {
|
|
2441
|
+
if len(p.Names) > 0 {
|
|
2442
|
+
params[i] = p.Names[0].Name
|
|
2443
|
+
} else {
|
|
2444
|
+
params[i] = "T"
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
return "[" + strings.Join(params, ", ") + "]"
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
func formatType(t ast.Expr) string {
|
|
2451
|
+
if t == nil {
|
|
2452
|
+
return "?"
|
|
2453
|
+
}
|
|
2454
|
+
switch v := t.(type) {
|
|
2455
|
+
case *ast.Ident:
|
|
2456
|
+
return v.Name
|
|
2457
|
+
case *ast.SelectorExpr:
|
|
2458
|
+
return formatType(v.X) + "." + v.Sel.Name
|
|
2459
|
+
case *ast.StarExpr:
|
|
2460
|
+
return "*" + formatType(v.X)
|
|
2461
|
+
case *ast.ArrayType:
|
|
2462
|
+
if v.Len == nil {
|
|
2463
|
+
return "[]" + formatType(v.Elt)
|
|
2464
|
+
}
|
|
2465
|
+
return "[...]" + formatType(v.Elt)
|
|
2466
|
+
case *ast.MapType:
|
|
2467
|
+
return "map[" + formatType(v.Key) + "]" + formatType(v.Value)
|
|
2468
|
+
case *ast.InterfaceType:
|
|
2469
|
+
return "interface{}"
|
|
2470
|
+
case *ast.StructType:
|
|
2471
|
+
return "struct{}"
|
|
2472
|
+
case *ast.FuncType:
|
|
2473
|
+
return formatFuncType(v)
|
|
2474
|
+
case *ast.ChanType:
|
|
2475
|
+
return "chan " + formatType(v.Value)
|
|
2476
|
+
case *ast.BasicLit:
|
|
2477
|
+
return v.Value
|
|
2478
|
+
case *ast.IndexExpr:
|
|
2479
|
+
// Generic instantiation with one type arg, e.g. Logger[int].
|
|
2480
|
+
return formatType(v.X) + "[" + formatType(v.Index) + "]"
|
|
2481
|
+
case *ast.IndexListExpr:
|
|
2482
|
+
// Generic instantiation with multiple type args, e.g. Map[K, V].
|
|
2483
|
+
args := make([]string, len(v.Indices))
|
|
2484
|
+
for i, idx := range v.Indices {
|
|
2485
|
+
args[i] = formatType(idx)
|
|
2486
|
+
}
|
|
2487
|
+
return formatType(v.X) + "[" + strings.Join(args, ", ") + "]"
|
|
2488
|
+
default:
|
|
2489
|
+
return "?"
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
1697
2492
|
`;
|
|
1698
|
-
|
|
2493
|
+
_cachedGoScriptPath = null;
|
|
1699
2494
|
}
|
|
1700
2495
|
});
|
|
1701
2496
|
|
|
@@ -1791,7 +2586,7 @@ __export(json_parser_exports, {
|
|
|
1791
2586
|
parseSymbols: () => parseSymbols6
|
|
1792
2587
|
});
|
|
1793
2588
|
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
1794
|
-
import * as
|
|
2589
|
+
import * as path10 from "node:path";
|
|
1795
2590
|
function parseSymbols6(opts) {
|
|
1796
2591
|
const { file, content, lang } = opts;
|
|
1797
2592
|
try {
|
|
@@ -1803,7 +2598,7 @@ function parseSymbols6(opts) {
|
|
|
1803
2598
|
function regexParse2(opts) {
|
|
1804
2599
|
const { file, content, lang } = opts;
|
|
1805
2600
|
const symbols = [];
|
|
1806
|
-
const basename6 =
|
|
2601
|
+
const basename6 = path10.basename(file).toLowerCase();
|
|
1807
2602
|
const isPackageJson = basename6 === "package.json";
|
|
1808
2603
|
const isTsconfig = basename6 === "tsconfig.json" || basename6 === "tsconfig.build.json";
|
|
1809
2604
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
@@ -1829,11 +2624,11 @@ function regexParse2(opts) {
|
|
|
1829
2624
|
const line = lineFromOffset(offset);
|
|
1830
2625
|
symbols.push(
|
|
1831
2626
|
makeSymbol({
|
|
1832
|
-
name:
|
|
2627
|
+
name: path10.basename(file),
|
|
1833
2628
|
kind: "object",
|
|
1834
2629
|
line,
|
|
1835
2630
|
col: 0,
|
|
1836
|
-
signature: `"${
|
|
2631
|
+
signature: `"${path10.basename(file)}" = { ... }`,
|
|
1837
2632
|
file,
|
|
1838
2633
|
lang
|
|
1839
2634
|
})
|
|
@@ -2161,6 +2956,106 @@ var init_yaml_parser = __esm({
|
|
|
2161
2956
|
});
|
|
2162
2957
|
|
|
2163
2958
|
// src/codebase-index/tree-sitter/queries.ts
|
|
2959
|
+
function parseGroupedUse(text) {
|
|
2960
|
+
const open = text.indexOf("{");
|
|
2961
|
+
const close = text.lastIndexOf("}");
|
|
2962
|
+
if (open < 0 || close <= open) return null;
|
|
2963
|
+
const prefix = text.slice(0, open).replace(/[\\/]+$/, "");
|
|
2964
|
+
const out = [];
|
|
2965
|
+
for (const rawMember of text.slice(open + 1, close).split(",")) {
|
|
2966
|
+
let member = rawMember.trim();
|
|
2967
|
+
if (!member) continue;
|
|
2968
|
+
member = member.replace(
|
|
2969
|
+
/^(static|final|type|class|struct|enum|protocol|var|func|let|typealias|function|const)\s+/i,
|
|
2970
|
+
""
|
|
2971
|
+
).trim();
|
|
2972
|
+
if (!member) continue;
|
|
2973
|
+
const aliasMatch = /\s+as\s+([A-Za-z_]\w*)\s*$/i.exec(member);
|
|
2974
|
+
if (aliasMatch) member = member.slice(0, aliasMatch.index).trim();
|
|
2975
|
+
if (!member) continue;
|
|
2976
|
+
const module = prefix ? `${prefix}\\${member}` : member;
|
|
2977
|
+
const toName = member.split(/[\\/]/).filter(Boolean).pop();
|
|
2978
|
+
if (toName) out.push({ toName, callType: "import", module });
|
|
2979
|
+
}
|
|
2980
|
+
return out.length ? out : null;
|
|
2981
|
+
}
|
|
2982
|
+
function importFromText(prefixes) {
|
|
2983
|
+
return (node) => {
|
|
2984
|
+
let text = node.text.replace(/\s+/g, " ").trim();
|
|
2985
|
+
for (const prefix of prefixes) {
|
|
2986
|
+
if (text.startsWith(prefix)) text = text.slice(prefix.length).trim();
|
|
2987
|
+
}
|
|
2988
|
+
text = text.replace(
|
|
2989
|
+
/^(static|final|type|class|struct|enum|protocol|var|func|let|typealias|function|const)\s+/i,
|
|
2990
|
+
""
|
|
2991
|
+
).trim();
|
|
2992
|
+
if (text.includes("{")) return parseGroupedUse(text);
|
|
2993
|
+
if (text.includes(",") && !text.includes("<") && !text.includes("=")) {
|
|
2994
|
+
const out = [];
|
|
2995
|
+
for (const clause of text.split(",")) {
|
|
2996
|
+
const one = oneImportClause(clause.trim());
|
|
2997
|
+
if (one) out.push(one);
|
|
2998
|
+
}
|
|
2999
|
+
return out.length ? out : null;
|
|
3000
|
+
}
|
|
3001
|
+
const single = oneImportClause(text);
|
|
3002
|
+
return single ? [single] : null;
|
|
3003
|
+
};
|
|
3004
|
+
}
|
|
3005
|
+
function oneImportClause(rawClause) {
|
|
3006
|
+
let text = rawClause;
|
|
3007
|
+
text = text.replace(
|
|
3008
|
+
/^(static|final|type|class|struct|enum|protocol|var|func|let|typealias|function|const)\s+/i,
|
|
3009
|
+
""
|
|
3010
|
+
).trim();
|
|
3011
|
+
text = text.replace(/[;}]+$/g, "").trim();
|
|
3012
|
+
const aliasMatch = /\s+as\s+([A-Za-z_]\w*)\s*$/i.exec(text);
|
|
3013
|
+
if (aliasMatch) text = text.slice(0, aliasMatch.index).trim();
|
|
3014
|
+
const eqMatch = /^([A-Za-z_]\w*)\s*=\s*(.+)$/.exec(text);
|
|
3015
|
+
if (eqMatch) text = eqMatch[2].trim();
|
|
3016
|
+
if (!text) return null;
|
|
3017
|
+
if (text.endsWith("*")) text = text.slice(0, -1).replace(/[.]$/, "");
|
|
3018
|
+
if (!text) return null;
|
|
3019
|
+
const module = text;
|
|
3020
|
+
const toName = module.split(/[.\\/]/).filter(Boolean).pop()?.replace(/<.*>$/s, "");
|
|
3021
|
+
if (!toName) return null;
|
|
3022
|
+
return { toName, callType: "import", module };
|
|
3023
|
+
}
|
|
3024
|
+
function heritageLeaf(node, depth) {
|
|
3025
|
+
if (depth > 6) return null;
|
|
3026
|
+
const named = node.childForFieldName("name");
|
|
3027
|
+
if (named) {
|
|
3028
|
+
if (named.type === "scoped_type_identifier" || named.type === "qualified_name" || named.type === "scope_resolution" || named.type === "user_type") {
|
|
3029
|
+
return heritageLeaf(named, depth + 1);
|
|
3030
|
+
}
|
|
3031
|
+
return named.text;
|
|
3032
|
+
}
|
|
3033
|
+
const children = [];
|
|
3034
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3035
|
+
const c = node.namedChild(i);
|
|
3036
|
+
if (c) children.push(c);
|
|
3037
|
+
}
|
|
3038
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
3039
|
+
const c = children[i];
|
|
3040
|
+
if (c.type === "type_arguments" || c.type === "type_argument_list" || // cpp: (template_type arguments: (template_argument_list …)) — the
|
|
3041
|
+
// descriptor's type_identifier inside it is never the declared base.
|
|
3042
|
+
c.type === "template_argument_list" || c.type === "type_parameter_list" || c.type === "type_projection" || c.type === "value_arguments") {
|
|
3043
|
+
continue;
|
|
3044
|
+
}
|
|
3045
|
+
if (c.type === "type_identifier" || c.type === "identifier" || c.type === "constant" || c.type === "name") {
|
|
3046
|
+
return c.text;
|
|
3047
|
+
}
|
|
3048
|
+
if (c.type === "scoped_type_identifier" || c.type === "qualified_name" || c.type === "scope_resolution" || c.type === "user_type") {
|
|
3049
|
+
return heritageLeaf(c, depth + 1);
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
return leafSegment(
|
|
3053
|
+
node.text.replace(/\\/g, ".").replace(/::/g, ".").replace(/<[^<>]*>$/, "")
|
|
3054
|
+
);
|
|
3055
|
+
}
|
|
3056
|
+
function leafSegment(text) {
|
|
3057
|
+
return text.split(".").filter(Boolean).pop() ?? text;
|
|
3058
|
+
}
|
|
2164
3059
|
function getQueries(lang) {
|
|
2165
3060
|
return LANG_QUERIES[lang] ?? DEFAULT_QUERIES;
|
|
2166
3061
|
}
|
|
@@ -2172,10 +3067,127 @@ function readFirstString(node) {
|
|
|
2172
3067
|
const child = node.namedChild(0);
|
|
2173
3068
|
return child ? readFirstString(child) : null;
|
|
2174
3069
|
}
|
|
2175
|
-
var DEFAULT_QUERIES, LANG_QUERIES;
|
|
3070
|
+
var heritageExtractor, cCallExtractor, rubyCallExtractor, firstIdentifierCallExtractor, cIncludeExtractor, phpConstructorExtractor, DEFAULT_QUERIES, LANG_QUERIES;
|
|
2176
3071
|
var init_queries = __esm({
|
|
2177
3072
|
"src/codebase-index/tree-sitter/queries.ts"() {
|
|
2178
3073
|
"use strict";
|
|
3074
|
+
heritageExtractor = (node) => {
|
|
3075
|
+
const out = [];
|
|
3076
|
+
const SKIP_SUBTREES = /* @__PURE__ */ new Set([
|
|
3077
|
+
"type_arguments",
|
|
3078
|
+
"type_argument_list",
|
|
3079
|
+
// tree-sitter-cpp names its argument subtree template_argument_list —
|
|
3080
|
+
// verified AST: (base_class_clause (template_type name:
|
|
3081
|
+
// (type_identifier) arguments: (template_argument_list
|
|
3082
|
+
// (type_descriptor type: (type_identifier))))). Without this entry
|
|
3083
|
+
// `class D : Base<Foo>` recurses into the descriptor and emits Foo as a
|
|
3084
|
+
// phantom inherit ref.
|
|
3085
|
+
"template_argument_list",
|
|
3086
|
+
"type_parameter_list",
|
|
3087
|
+
"type_projection",
|
|
3088
|
+
"value_arguments"
|
|
3089
|
+
]);
|
|
3090
|
+
const collect = (current, depth) => {
|
|
3091
|
+
if (depth > 4) return;
|
|
3092
|
+
for (let i = 0; i < current.namedChildCount; i++) {
|
|
3093
|
+
const child = current.namedChild(i);
|
|
3094
|
+
if (!child) continue;
|
|
3095
|
+
if (SKIP_SUBTREES.has(child.type)) continue;
|
|
3096
|
+
if (child.type === "type_identifier" || child.type === "identifier" || child.type === "named_type" || child.type === "type" || // PHP heritage carries `name`; Ruby a `constant`.
|
|
3097
|
+
child.type === "constant" || child.type === "name") {
|
|
3098
|
+
const name = child.type === "named_type" ? leafSegment(child.text) : child.text;
|
|
3099
|
+
if (name) out.push({ toName: name });
|
|
3100
|
+
continue;
|
|
3101
|
+
}
|
|
3102
|
+
if (child.type === "generic_type" || child.type === "generic_name") {
|
|
3103
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
3104
|
+
const inner = child.namedChild(j);
|
|
3105
|
+
if (inner && !SKIP_SUBTREES.has(inner.type) && (inner.type === "type_identifier" || inner.type === "identifier" || inner.type === "name")) {
|
|
3106
|
+
out.push({ toName: inner.text });
|
|
3107
|
+
break;
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
continue;
|
|
3111
|
+
}
|
|
3112
|
+
if (child.type === "qualified_name" || child.type === "scoped_type_identifier" || child.type === "user_type" || child.type === "scope_resolution") {
|
|
3113
|
+
const leaf = heritageLeaf(child, 0);
|
|
3114
|
+
if (leaf) out.push({ toName: leaf });
|
|
3115
|
+
continue;
|
|
3116
|
+
}
|
|
3117
|
+
collect(child, depth + 1);
|
|
3118
|
+
}
|
|
3119
|
+
};
|
|
3120
|
+
collect(node, 0);
|
|
3121
|
+
return out;
|
|
3122
|
+
};
|
|
3123
|
+
cCallExtractor = (node) => {
|
|
3124
|
+
const fn = node.childForFieldName("function");
|
|
3125
|
+
if (!fn) return null;
|
|
3126
|
+
if (fn.type === "field_expression") {
|
|
3127
|
+
const field = fn.childForFieldName("field");
|
|
3128
|
+
if (field) return [{ toName: field.text, callType: "call" }];
|
|
3129
|
+
const seg = fn.text.split("->").filter(Boolean).pop();
|
|
3130
|
+
if (seg) return [{ toName: leafSegment(seg.split(".")[0] ?? seg), callType: "call" }];
|
|
3131
|
+
return null;
|
|
3132
|
+
}
|
|
3133
|
+
if (fn.type === "qualified_identifier") {
|
|
3134
|
+
const name = fn.childForFieldName("name");
|
|
3135
|
+
if (name) return [{ toName: name.text, callType: "call" }];
|
|
3136
|
+
const seg = fn.text.split("::").filter(Boolean).pop();
|
|
3137
|
+
if (seg) return [{ toName: seg.split(/[<(]/)[0].trim(), callType: "call" }];
|
|
3138
|
+
return null;
|
|
3139
|
+
}
|
|
3140
|
+
return [{ toName: fn.text.split(/[<(]/)[0].trim(), callType: "call" }];
|
|
3141
|
+
};
|
|
3142
|
+
rubyCallExtractor = (node) => {
|
|
3143
|
+
const emissions = [];
|
|
3144
|
+
const method = node.childForFieldName("method");
|
|
3145
|
+
if (method) {
|
|
3146
|
+
const name = method.text;
|
|
3147
|
+
if (name && !name.includes(" ")) emissions.push({ toName: name, callType: "call" });
|
|
3148
|
+
if (name === "require" || name === "require_relative") {
|
|
3149
|
+
const args = node.childForFieldName("arguments");
|
|
3150
|
+
const first = args?.namedChild(0);
|
|
3151
|
+
if (first) {
|
|
3152
|
+
const raw = first.text.replace(/^['"]|['"]$/g, "");
|
|
3153
|
+
const toName = raw.split("/").filter(Boolean).pop();
|
|
3154
|
+
if (toName) emissions.push({ toName, callType: "import", module: raw });
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
return emissions;
|
|
3159
|
+
};
|
|
3160
|
+
firstIdentifierCallExtractor = (node) => {
|
|
3161
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3162
|
+
const child = node.namedChild(i);
|
|
3163
|
+
if (child && (child.type === "simple_identifier" || child.type === "identifier")) {
|
|
3164
|
+
return [{ toName: child.text, callType: "call" }];
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
const first = node.namedChild(0);
|
|
3168
|
+
if (!first) return null;
|
|
3169
|
+
const leaf = leafSegment(first.text.split(/[<(]/)[0] ?? first.text);
|
|
3170
|
+
if (!leaf) return null;
|
|
3171
|
+
return [{ toName: leaf, callType: "call" }];
|
|
3172
|
+
};
|
|
3173
|
+
cIncludeExtractor = (node) => {
|
|
3174
|
+
const raw = node.text.replace(/^#\s*include\s*/i, "").trim();
|
|
3175
|
+
const module = raw.replace(/^["'<]|["'>]$/g, "");
|
|
3176
|
+
if (!module) return null;
|
|
3177
|
+
const toName = module.split("/").pop()?.replace(/\.h$/, "");
|
|
3178
|
+
if (!toName) return null;
|
|
3179
|
+
return [{ toName, callType: "import", module }];
|
|
3180
|
+
};
|
|
3181
|
+
phpConstructorExtractor = (node) => {
|
|
3182
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3183
|
+
const child = node.namedChild(i);
|
|
3184
|
+
if (child && (child.type === "qualified_name" || child.type === "name")) {
|
|
3185
|
+
const leaf = child.text.split(/[\\]/).filter(Boolean).pop();
|
|
3186
|
+
if (leaf) return [{ toName: leaf, callType: "call" }];
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
return null;
|
|
3190
|
+
};
|
|
2179
3191
|
DEFAULT_QUERIES = {
|
|
2180
3192
|
declKinds: {}
|
|
2181
3193
|
};
|
|
@@ -2208,7 +3220,13 @@ var init_queries = __esm({
|
|
|
2208
3220
|
"struct_specifier",
|
|
2209
3221
|
"union_specifier",
|
|
2210
3222
|
"enum_specifier"
|
|
2211
|
-
])
|
|
3223
|
+
]),
|
|
3224
|
+
refRules: {
|
|
3225
|
+
// `obj->run()` and `Cls::stat()` carry structured function fields —
|
|
3226
|
+
// cCallExtractor handles all three AST shapes.
|
|
3227
|
+
call_expression: { callType: "call", nameExtractor: cCallExtractor },
|
|
3228
|
+
preproc_include: { callType: "import", nameExtractor: cIncludeExtractor }
|
|
3229
|
+
}
|
|
2212
3230
|
},
|
|
2213
3231
|
cpp: {
|
|
2214
3232
|
declKinds: {
|
|
@@ -2239,7 +3257,13 @@ var init_queries = __esm({
|
|
|
2239
3257
|
"union_specifier",
|
|
2240
3258
|
"enum_specifier",
|
|
2241
3259
|
"namespace_definition"
|
|
2242
|
-
])
|
|
3260
|
+
]),
|
|
3261
|
+
refRules: {
|
|
3262
|
+
call_expression: { callType: "call", nameExtractor: cCallExtractor },
|
|
3263
|
+
preproc_include: { callType: "import", nameExtractor: cIncludeExtractor },
|
|
3264
|
+
// `class Foo : public Bar, private Baz` — the base-class clause.
|
|
3265
|
+
base_class_clause: { callType: "inherit", nameExtractor: heritageExtractor }
|
|
3266
|
+
}
|
|
2243
3267
|
},
|
|
2244
3268
|
java: {
|
|
2245
3269
|
declKinds: {
|
|
@@ -2274,7 +3298,22 @@ var init_queries = __esm({
|
|
|
2274
3298
|
"interface_declaration",
|
|
2275
3299
|
"enum_declaration",
|
|
2276
3300
|
"record_declaration"
|
|
2277
|
-
])
|
|
3301
|
+
]),
|
|
3302
|
+
refRules: {
|
|
3303
|
+
method_invocation: { callType: "call", field: "name" },
|
|
3304
|
+
object_creation_expression: { callType: "call", field: "type" },
|
|
3305
|
+
// Verified AST: `superclass: (superclass (type_identifier))` and
|
|
3306
|
+
// `interfaces: (super_interfaces (type_list ...))` — no underscores.
|
|
3307
|
+
superclass: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3308
|
+
super_interfaces: {
|
|
3309
|
+
callType: "implement",
|
|
3310
|
+
nameExtractor: heritageExtractor
|
|
3311
|
+
},
|
|
3312
|
+
import_declaration: {
|
|
3313
|
+
callType: "import",
|
|
3314
|
+
nameExtractor: importFromText(["import "])
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
2278
3317
|
},
|
|
2279
3318
|
csharp: {
|
|
2280
3319
|
// C# 10+ `namespace Foo.Bar;` produces this node type. The legacy block
|
|
@@ -2312,7 +3351,18 @@ var init_queries = __esm({
|
|
|
2312
3351
|
"struct_declaration",
|
|
2313
3352
|
"enum_declaration",
|
|
2314
3353
|
"record_declaration"
|
|
2315
|
-
])
|
|
3354
|
+
]),
|
|
3355
|
+
refRules: {
|
|
3356
|
+
// Verified AST: `invocation_expression function: (identifier)` — the
|
|
3357
|
+
// callee field is `function` (C-style), not `name`.
|
|
3358
|
+
invocation_expression: { callType: "call", field: "function" },
|
|
3359
|
+
object_creation_expression: { callType: "call", field: "type" },
|
|
3360
|
+
base_list: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3361
|
+
using_directive: {
|
|
3362
|
+
callType: "import",
|
|
3363
|
+
nameExtractor: importFromText(["using "])
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
2316
3366
|
},
|
|
2317
3367
|
php: {
|
|
2318
3368
|
declKinds: {
|
|
@@ -2331,7 +3381,7 @@ var init_queries = __esm({
|
|
|
2331
3381
|
interface_declaration: "name",
|
|
2332
3382
|
trait_declaration: "name",
|
|
2333
3383
|
enum_declaration: "name",
|
|
2334
|
-
|
|
3384
|
+
namespace_definition: "name"
|
|
2335
3385
|
},
|
|
2336
3386
|
scopeNodes: /* @__PURE__ */ new Set([
|
|
2337
3387
|
"program",
|
|
@@ -2340,7 +3390,24 @@ var init_queries = __esm({
|
|
|
2340
3390
|
"interface_declaration",
|
|
2341
3391
|
"trait_declaration",
|
|
2342
3392
|
"enum_declaration"
|
|
2343
|
-
])
|
|
3393
|
+
]),
|
|
3394
|
+
refRules: {
|
|
3395
|
+
function_call_expression: { callType: "call", field: "function" },
|
|
3396
|
+
// Verified AST: `new App\Model\User()` carries a BARE qualified_name
|
|
3397
|
+
// child (no `name:` field), so the field default never fires.
|
|
3398
|
+
object_creation_expression: { callType: "call", nameExtractor: phpConstructorExtractor },
|
|
3399
|
+
base_clause: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3400
|
+
class_interface_clause: {
|
|
3401
|
+
callType: "implement",
|
|
3402
|
+
nameExtractor: heritageExtractor
|
|
3403
|
+
},
|
|
3404
|
+
// Verified AST: `namespace_use_declaration (namespace_use_clause
|
|
3405
|
+
// (qualified_name ...))` — not `use_declaration`.
|
|
3406
|
+
namespace_use_declaration: {
|
|
3407
|
+
callType: "import",
|
|
3408
|
+
nameExtractor: importFromText(["use "])
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
2344
3411
|
},
|
|
2345
3412
|
// ─── Scripting / mobile ────────────────────────────────────────────────────
|
|
2346
3413
|
ruby: {
|
|
@@ -2358,7 +3425,13 @@ var init_queries = __esm({
|
|
|
2358
3425
|
module: "name",
|
|
2359
3426
|
constant: "name"
|
|
2360
3427
|
},
|
|
2361
|
-
scopeNodes: /* @__PURE__ */ new Set(["program", "class", "module", "singleton_method", "method"])
|
|
3428
|
+
scopeNodes: /* @__PURE__ */ new Set(["program", "class", "module", "singleton_method", "method"]),
|
|
3429
|
+
refRules: {
|
|
3430
|
+
// `call` covers both `foo(...)` and `obj.foo(...)` — the extractor
|
|
3431
|
+
// records the method leaf, plus `require`/`require_relative` imports.
|
|
3432
|
+
call: { callType: "call", nameExtractor: rubyCallExtractor },
|
|
3433
|
+
superclass: { callType: "inherit", nameExtractor: heritageExtractor }
|
|
3434
|
+
}
|
|
2362
3435
|
},
|
|
2363
3436
|
swift: {
|
|
2364
3437
|
declKinds: {
|
|
@@ -2391,7 +3464,18 @@ var init_queries = __esm({
|
|
|
2391
3464
|
"protocol_declaration",
|
|
2392
3465
|
"actor_declaration",
|
|
2393
3466
|
"extension_declaration"
|
|
2394
|
-
])
|
|
3467
|
+
]),
|
|
3468
|
+
refRules: {
|
|
3469
|
+
// Verified AST: `call_expression (simple_identifier) (call_suffix …)` —
|
|
3470
|
+
// the callee is a bare first child, no field name.
|
|
3471
|
+
call_expression: { callType: "call", nameExtractor: firstIdentifierCallExtractor },
|
|
3472
|
+
// Verified AST: `inheritance_specifier inherits_from: (user_type …)`.
|
|
3473
|
+
inheritance_specifier: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3474
|
+
import_declaration: {
|
|
3475
|
+
callType: "import",
|
|
3476
|
+
nameExtractor: importFromText(["import ", "import type ", "@testable import "])
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
2395
3479
|
},
|
|
2396
3480
|
kotlin: {
|
|
2397
3481
|
declKinds: {
|
|
@@ -2416,7 +3500,17 @@ var init_queries = __esm({
|
|
|
2416
3500
|
"object_declaration",
|
|
2417
3501
|
"interface_declaration",
|
|
2418
3502
|
"function_declaration"
|
|
2419
|
-
])
|
|
3503
|
+
]),
|
|
3504
|
+
refRules: {
|
|
3505
|
+
call_expression: { callType: "call", nameExtractor: firstIdentifierCallExtractor },
|
|
3506
|
+
// Verified AST: `delegation_specifier (user_type (type_identifier))` —
|
|
3507
|
+
// the `: Handler` / `: Base()` clause.
|
|
3508
|
+
delegation_specifier: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3509
|
+
import_header: {
|
|
3510
|
+
callType: "import",
|
|
3511
|
+
nameExtractor: importFromText(["import "])
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
2420
3514
|
},
|
|
2421
3515
|
elixir: {
|
|
2422
3516
|
declKinds: {
|
|
@@ -2488,7 +3582,38 @@ function visitTree(tree, content, file, lang, queries) {
|
|
|
2488
3582
|
const boundedContent = content.length > TREE_SITTER_MAX_FILE_CHARS ? content.slice(0, TREE_SITTER_MAX_FILE_CHARS) : content;
|
|
2489
3583
|
const nlOffsets = newlineOffsets3(boundedContent);
|
|
2490
3584
|
const symbols = [];
|
|
3585
|
+
const refs = [];
|
|
3586
|
+
const seenRefs = /* @__PURE__ */ new Set();
|
|
2491
3587
|
const scopeStack = [];
|
|
3588
|
+
function emitRefsForNode(node, rule) {
|
|
3589
|
+
const emissions = rule.nameExtractor?.(node) ?? defaultRefTarget(node, rule);
|
|
3590
|
+
if (!emissions) return;
|
|
3591
|
+
const { line } = lineColAt2(nlOffsets, node.startIndex);
|
|
3592
|
+
for (const emission of emissions) {
|
|
3593
|
+
if (!emission.toName) continue;
|
|
3594
|
+
const callType = emission.callType ?? rule.callType;
|
|
3595
|
+
const key = `${emission.toName}:${callType}:${line}:${emission.module ?? ""}:${node.startIndex}`;
|
|
3596
|
+
if (seenRefs.has(key)) continue;
|
|
3597
|
+
seenRefs.add(key);
|
|
3598
|
+
refs.push({
|
|
3599
|
+
fromId: 0,
|
|
3600
|
+
// assignRefsToSymbols attaches owners after insertion
|
|
3601
|
+
toName: emission.toName.slice(0, 200),
|
|
3602
|
+
callType,
|
|
3603
|
+
line,
|
|
3604
|
+
lang,
|
|
3605
|
+
module: emission.module
|
|
3606
|
+
});
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
function defaultRefTarget(node, rule) {
|
|
3610
|
+
if (!rule.field) return null;
|
|
3611
|
+
const field = node.childForFieldName(rule.field);
|
|
3612
|
+
if (!field) return null;
|
|
3613
|
+
const leaf = field.text.split(/[.:\\]/).filter(Boolean).pop()?.split(/[<(]/)[0];
|
|
3614
|
+
if (!leaf) return null;
|
|
3615
|
+
return [{ toName: leaf.trim() }];
|
|
3616
|
+
}
|
|
2492
3617
|
function visit(node, depth) {
|
|
2493
3618
|
if (symbols.length >= TREE_SITTER_MAX_SYMBOLS) return;
|
|
2494
3619
|
if (node.isMissing || node.isError) {
|
|
@@ -2507,6 +3632,8 @@ function visitTree(tree, content, file, lang, queries) {
|
|
|
2507
3632
|
);
|
|
2508
3633
|
if (emitted) symbols.push(emitted);
|
|
2509
3634
|
}
|
|
3635
|
+
const refRule = queries.refRules?.[node.type];
|
|
3636
|
+
if (refRule) emitRefsForNode(node, refRule);
|
|
2510
3637
|
}
|
|
2511
3638
|
const pushesScope = queries.scopeNodes?.has(node.type) ?? false;
|
|
2512
3639
|
const pushIdx = pushesScope ? pushScope(scopeStack, node, queries) : -1;
|
|
@@ -2524,7 +3651,7 @@ function visitTree(tree, content, file, lang, queries) {
|
|
|
2524
3651
|
if (pushIdx !== -1) scopeStack.pop();
|
|
2525
3652
|
}
|
|
2526
3653
|
visit(tree.rootNode, 0);
|
|
2527
|
-
return { symbols };
|
|
3654
|
+
return { symbols, refs };
|
|
2528
3655
|
}
|
|
2529
3656
|
function pushScope(scopeStack, node, queries) {
|
|
2530
3657
|
const name = extractName(node, queries);
|
|
@@ -2611,7 +3738,7 @@ __export(tree_sitter_parser_exports, {
|
|
|
2611
3738
|
parseSymbols: () => parseSymbols8,
|
|
2612
3739
|
parseTreeSitterAst: () => parseTreeSitterAst
|
|
2613
3740
|
});
|
|
2614
|
-
import * as
|
|
3741
|
+
import * as path11 from "node:path";
|
|
2615
3742
|
import { fileURLToPath } from "node:url";
|
|
2616
3743
|
function optInEnabled(env) {
|
|
2617
3744
|
return process.env[env] === "1" || process.env[env] === "true";
|
|
@@ -2634,7 +3761,7 @@ async function loadLanguage(lang) {
|
|
|
2634
3761
|
if (!grammarName) {
|
|
2635
3762
|
throw new Error(`tree-sitter: no grammar registered for lang "${lang}"`);
|
|
2636
3763
|
}
|
|
2637
|
-
const wasmPath =
|
|
3764
|
+
const wasmPath = path11.join(WASM_DIR, grammarName, `tree-sitter-${grammarName}.wasm`);
|
|
2638
3765
|
const { Language, init } = await getRuntime();
|
|
2639
3766
|
await init();
|
|
2640
3767
|
const languageObj = await Language.load(wasmPath);
|
|
@@ -2655,7 +3782,7 @@ function isTreeSitterSupported(lang) {
|
|
|
2655
3782
|
function getGrammarWasmPath(lang) {
|
|
2656
3783
|
const name = resolveGrammarName(lang);
|
|
2657
3784
|
if (!name) return void 0;
|
|
2658
|
-
return
|
|
3785
|
+
return path11.join(WASM_DIR, name, `tree-sitter-${name}.wasm`);
|
|
2659
3786
|
}
|
|
2660
3787
|
async function parseSymbols8(opts) {
|
|
2661
3788
|
const { file, content, lang } = opts;
|
|
@@ -2671,89 +3798,229 @@ async function parseSymbols8(opts) {
|
|
|
2671
3798
|
if (!tree) {
|
|
2672
3799
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
2673
3800
|
}
|
|
2674
|
-
const { symbols } = visitTree(tree, content, file, lang, getQueries(lang));
|
|
2675
|
-
parser.delete();
|
|
2676
|
-
tree.delete();
|
|
2677
|
-
return { file, lang, symbols, refs
|
|
2678
|
-
} catch {
|
|
2679
|
-
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
2680
|
-
}
|
|
2681
|
-
}
|
|
2682
|
-
async function loadTreeSitterLanguage(lang) {
|
|
2683
|
-
const cached = await loadLanguage(lang);
|
|
2684
|
-
return cached.Language;
|
|
2685
|
-
}
|
|
2686
|
-
async function __smokeRootType(opts) {
|
|
2687
|
-
if (!isTreeSitterSupported(opts.lang)) {
|
|
2688
|
-
throw new Error(`tree-sitter: no grammar registered for lang "${opts.lang}"`);
|
|
2689
|
-
}
|
|
2690
|
-
const { Parser } = await getRuntime();
|
|
2691
|
-
const cached = await loadLanguage(opts.lang);
|
|
2692
|
-
const parser = new Parser();
|
|
2693
|
-
parser.setLanguage(cached.Language);
|
|
2694
|
-
let tree = null;
|
|
2695
|
-
try {
|
|
2696
|
-
tree = parser.parse(opts.content);
|
|
2697
|
-
if (!tree) throw new Error("tree-sitter: parser.parse returned null");
|
|
2698
|
-
return tree.rootNode.type;
|
|
2699
|
-
} finally {
|
|
2700
|
-
tree?.delete();
|
|
2701
|
-
parser.delete();
|
|
3801
|
+
const { symbols, refs } = visitTree(tree, content, file, lang, getQueries(lang));
|
|
3802
|
+
parser.delete();
|
|
3803
|
+
tree.delete();
|
|
3804
|
+
return { file, lang, symbols, refs, mtimeMs: Date.now() };
|
|
3805
|
+
} catch {
|
|
3806
|
+
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
async function loadTreeSitterLanguage(lang) {
|
|
3810
|
+
const cached = await loadLanguage(lang);
|
|
3811
|
+
return cached.Language;
|
|
3812
|
+
}
|
|
3813
|
+
async function __smokeRootType(opts) {
|
|
3814
|
+
if (!isTreeSitterSupported(opts.lang)) {
|
|
3815
|
+
throw new Error(`tree-sitter: no grammar registered for lang "${opts.lang}"`);
|
|
3816
|
+
}
|
|
3817
|
+
const { Parser } = await getRuntime();
|
|
3818
|
+
const cached = await loadLanguage(opts.lang);
|
|
3819
|
+
const parser = new Parser();
|
|
3820
|
+
parser.setLanguage(cached.Language);
|
|
3821
|
+
let tree = null;
|
|
3822
|
+
try {
|
|
3823
|
+
tree = parser.parse(opts.content);
|
|
3824
|
+
if (!tree) throw new Error("tree-sitter: parser.parse returned null");
|
|
3825
|
+
return tree.rootNode.type;
|
|
3826
|
+
} finally {
|
|
3827
|
+
tree?.delete();
|
|
3828
|
+
parser.delete();
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
async function parseTreeSitterAst(opts) {
|
|
3832
|
+
const grammar = resolveGrammarName(opts.lang) ?? (opts.lang === "go" ? "go" : opts.lang === "py" ? "python" : opts.lang === "rs" ? "rust" : void 0);
|
|
3833
|
+
if (!grammar) return null;
|
|
3834
|
+
try {
|
|
3835
|
+
const { Parser, Language, init } = await getRuntime();
|
|
3836
|
+
await init();
|
|
3837
|
+
const wasmPath = path11.join(WASM_DIR, grammar, `tree-sitter-${grammar}.wasm`);
|
|
3838
|
+
const languageObj = await Language.load(wasmPath);
|
|
3839
|
+
const parser = new Parser();
|
|
3840
|
+
parser.setLanguage(languageObj);
|
|
3841
|
+
const tree = parser.parse(opts.content);
|
|
3842
|
+
if (!tree) {
|
|
3843
|
+
parser.delete();
|
|
3844
|
+
return null;
|
|
3845
|
+
}
|
|
3846
|
+
return { tree, parser };
|
|
3847
|
+
} catch {
|
|
3848
|
+
return null;
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
var WASM_DIR, RUNTIME_WASM, LANG_TO_GRAMMAR, GO_OPT_IN, PY_OPT_IN, RS_OPT_IN, runtimePromise, languageCache;
|
|
3852
|
+
var init_tree_sitter_parser = __esm({
|
|
3853
|
+
"src/codebase-index/tree-sitter-parser.ts"() {
|
|
3854
|
+
"use strict";
|
|
3855
|
+
init_queries();
|
|
3856
|
+
init_visitor();
|
|
3857
|
+
WASM_DIR = fileURLToPath(new URL("./wasm/", import.meta.url));
|
|
3858
|
+
RUNTIME_WASM = path11.join(WASM_DIR, "tree-sitter-runtime.wasm");
|
|
3859
|
+
LANG_TO_GRAMMAR = {
|
|
3860
|
+
c: "c",
|
|
3861
|
+
cpp: "cpp",
|
|
3862
|
+
java: "java",
|
|
3863
|
+
csharp: "c_sharp",
|
|
3864
|
+
// tree-sitter directory uses underscore
|
|
3865
|
+
php: "php",
|
|
3866
|
+
ruby: "ruby",
|
|
3867
|
+
swift: "swift",
|
|
3868
|
+
kotlin: "kotlin",
|
|
3869
|
+
shell: "bash",
|
|
3870
|
+
// we treat `.sh` / `.bash` / `.zsh` via the bash grammar
|
|
3871
|
+
// Go/Python/Rust are opt-in only (see goOptIn / pyOptIn / rsOptIn below)
|
|
3872
|
+
elixir: "elixir"
|
|
3873
|
+
};
|
|
3874
|
+
GO_OPT_IN = "WRONGSTACK_USE_TS_GO";
|
|
3875
|
+
PY_OPT_IN = "WRONGSTACK_USE_TS_PY";
|
|
3876
|
+
RS_OPT_IN = "WRONGSTACK_USE_TS_RS";
|
|
3877
|
+
runtimePromise = null;
|
|
3878
|
+
languageCache = /* @__PURE__ */ new Map();
|
|
3879
|
+
}
|
|
3880
|
+
});
|
|
3881
|
+
|
|
3882
|
+
// src/codebase-index/parser-dispatch.ts
|
|
3883
|
+
var parser_dispatch_exports = {};
|
|
3884
|
+
__export(parser_dispatch_exports, {
|
|
3885
|
+
parseFileContent: () => parseFileContent,
|
|
3886
|
+
parseFilesContent: () => parseFilesContent
|
|
3887
|
+
});
|
|
3888
|
+
async function parseFileContent(file, content, lang) {
|
|
3889
|
+
const parsed = await dispatch(file, content, lang);
|
|
3890
|
+
return withRelations(parsed, content, lang);
|
|
3891
|
+
}
|
|
3892
|
+
async function parseFilesContent(files) {
|
|
3893
|
+
if (files.length === 0) return [];
|
|
3894
|
+
const slots = files.map(() => ({ result: null }));
|
|
3895
|
+
const batchingEnabled = process.env["WRONGSTACK_TOOLCHAIN_BATCH"] !== "0";
|
|
3896
|
+
if (batchingEnabled) {
|
|
3897
|
+
const goFiles = [];
|
|
3898
|
+
const pyFiles = [];
|
|
3899
|
+
files.forEach((f, index) => {
|
|
3900
|
+
if (f.lang === "go") goFiles.push({ ...f, index });
|
|
3901
|
+
else if (f.lang === "py") pyFiles.push({ ...f, index });
|
|
3902
|
+
});
|
|
3903
|
+
if (goFiles.length > 0) {
|
|
3904
|
+
await applyBatchResults(slots, goFiles, (chunks) => runGoBatch(chunks), "go");
|
|
3905
|
+
}
|
|
3906
|
+
if (pyFiles.length > 0) {
|
|
3907
|
+
const pyBinary = await resolvePythonBinary();
|
|
3908
|
+
if (pyBinary) {
|
|
3909
|
+
await applyBatchResults(slots, pyFiles, (chunks) => runPyBatch(chunks, pyBinary), "py");
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
const jobs = [];
|
|
3914
|
+
for (let i = 0; i < files.length; i++) {
|
|
3915
|
+
if (slots[i].result !== null) continue;
|
|
3916
|
+
const { file, content, lang } = files[i];
|
|
3917
|
+
const slot = slots[i];
|
|
3918
|
+
jobs.push(
|
|
3919
|
+
(async () => {
|
|
3920
|
+
try {
|
|
3921
|
+
slot.result = await parseFileContent(file, content, lang);
|
|
3922
|
+
} catch (err) {
|
|
3923
|
+
slot.error = err instanceof Error ? err.message : String(err);
|
|
3924
|
+
}
|
|
3925
|
+
})()
|
|
3926
|
+
);
|
|
3927
|
+
}
|
|
3928
|
+
await Promise.all(jobs);
|
|
3929
|
+
return slots;
|
|
3930
|
+
}
|
|
3931
|
+
async function applyBatchResults(slots, batchFiles, runBatch, lang) {
|
|
3932
|
+
for (const chunk of chunkBatchFiles(batchFiles)) {
|
|
3933
|
+
let byFile = null;
|
|
3934
|
+
try {
|
|
3935
|
+
byFile = await runBatch(chunk);
|
|
3936
|
+
} catch {
|
|
3937
|
+
byFile = null;
|
|
3938
|
+
}
|
|
3939
|
+
if (!byFile) continue;
|
|
3940
|
+
for (const item of chunk) {
|
|
3941
|
+
const parsed = byFile.get(item.file);
|
|
3942
|
+
if (!parsed) continue;
|
|
3943
|
+
slots[item.index] = { result: withRelations(parsed, item.content, lang) };
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
async function dispatch(file, content, lang) {
|
|
3948
|
+
switch (lang) {
|
|
3949
|
+
case "ts":
|
|
3950
|
+
case "tsx":
|
|
3951
|
+
case "js":
|
|
3952
|
+
case "jsx": {
|
|
3953
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
|
|
3954
|
+
return parseSymbols9({ file, content, lang });
|
|
3955
|
+
}
|
|
3956
|
+
case "go": {
|
|
3957
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
|
|
3958
|
+
return parseSymbols9({ file, content, lang: "go" });
|
|
3959
|
+
}
|
|
3960
|
+
case "py": {
|
|
3961
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
|
|
3962
|
+
return parseSymbols9({ file, content, lang: "py" });
|
|
3963
|
+
}
|
|
3964
|
+
case "rs": {
|
|
3965
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
|
|
3966
|
+
return parseSymbols9({ file, content, lang: "rs" });
|
|
3967
|
+
}
|
|
3968
|
+
case "json": {
|
|
3969
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
|
|
3970
|
+
return parseSymbols9({ file, content, lang: "json" });
|
|
3971
|
+
}
|
|
3972
|
+
case "yaml": {
|
|
3973
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_yaml_parser(), yaml_parser_exports));
|
|
3974
|
+
return parseSymbols9({ file, content, lang: "yaml" });
|
|
3975
|
+
}
|
|
3976
|
+
// Phase 1: ten languages now route through the Tree-Sitter WASM
|
|
3977
|
+
// universal parser (`tree-sitter-parser.ts`). The dispatch falls back to
|
|
3978
|
+
// the regex extractor in `generic-parser.ts` whenever WASM loading fails
|
|
3979
|
+
// or the parser returns zero symbols — preserving the indexable-file
|
|
3980
|
+
// contract that "missing a parser must never mean skipping the file".
|
|
3981
|
+
case "c":
|
|
3982
|
+
case "cpp":
|
|
3983
|
+
case "java":
|
|
3984
|
+
case "csharp":
|
|
3985
|
+
case "php":
|
|
3986
|
+
case "ruby":
|
|
3987
|
+
case "swift":
|
|
3988
|
+
case "kotlin":
|
|
3989
|
+
case "shell":
|
|
3990
|
+
case "elixir": {
|
|
3991
|
+
try {
|
|
3992
|
+
const { parseSymbols: parseSymbols10 } = await Promise.resolve().then(() => (init_tree_sitter_parser(), tree_sitter_parser_exports));
|
|
3993
|
+
const parsed = await parseSymbols10({ file, content, lang });
|
|
3994
|
+
if (parsed.symbols.length > 0) return parsed;
|
|
3995
|
+
} catch {
|
|
3996
|
+
}
|
|
3997
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
3998
|
+
return parseSymbols9({ file, content, lang });
|
|
3999
|
+
}
|
|
4000
|
+
default: {
|
|
4001
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
4002
|
+
return parseSymbols9({ file, content, lang });
|
|
4003
|
+
}
|
|
2702
4004
|
}
|
|
2703
4005
|
}
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
if (
|
|
2707
|
-
|
|
2708
|
-
const { Parser, Language, init } = await getRuntime();
|
|
2709
|
-
await init();
|
|
2710
|
-
const wasmPath = path10.join(WASM_DIR, grammar, `tree-sitter-${grammar}.wasm`);
|
|
2711
|
-
const languageObj = await Language.load(wasmPath);
|
|
2712
|
-
const parser = new Parser();
|
|
2713
|
-
parser.setLanguage(languageObj);
|
|
2714
|
-
const tree = parser.parse(opts.content);
|
|
2715
|
-
if (!tree) {
|
|
2716
|
-
parser.delete();
|
|
2717
|
-
return null;
|
|
2718
|
-
}
|
|
2719
|
-
return { tree, parser };
|
|
2720
|
-
} catch {
|
|
2721
|
-
return null;
|
|
4006
|
+
function withRelations(parsed, content, lang) {
|
|
4007
|
+
let refs = parsed.refs ?? [];
|
|
4008
|
+
if (refs.length === 0 && hasImportPatterns(lang)) {
|
|
4009
|
+
refs = extractImports({ content, lang });
|
|
2722
4010
|
}
|
|
4011
|
+
return { ...parsed, refs: refs.map((ref) => ref.lang ? ref : { ...ref, lang }) };
|
|
2723
4012
|
}
|
|
2724
|
-
var
|
|
2725
|
-
|
|
2726
|
-
"src/codebase-index/tree-sitter-parser.ts"() {
|
|
4013
|
+
var init_parser_dispatch = __esm({
|
|
4014
|
+
"src/codebase-index/parser-dispatch.ts"() {
|
|
2727
4015
|
"use strict";
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
RUNTIME_WASM = path10.join(WASM_DIR, "tree-sitter-runtime.wasm");
|
|
2732
|
-
LANG_TO_GRAMMAR = {
|
|
2733
|
-
c: "c",
|
|
2734
|
-
cpp: "cpp",
|
|
2735
|
-
java: "java",
|
|
2736
|
-
csharp: "c_sharp",
|
|
2737
|
-
// tree-sitter directory uses underscore
|
|
2738
|
-
php: "php",
|
|
2739
|
-
ruby: "ruby",
|
|
2740
|
-
swift: "swift",
|
|
2741
|
-
kotlin: "kotlin",
|
|
2742
|
-
shell: "bash",
|
|
2743
|
-
// we treat `.sh` / `.bash` / `.zsh` via the bash grammar
|
|
2744
|
-
// Go/Python/Rust are opt-in only (see goOptIn / pyOptIn / rsOptIn below)
|
|
2745
|
-
elixir: "elixir"
|
|
2746
|
-
};
|
|
2747
|
-
GO_OPT_IN = "WRONGSTACK_USE_TS_GO";
|
|
2748
|
-
PY_OPT_IN = "WRONGSTACK_USE_TS_PY";
|
|
2749
|
-
RS_OPT_IN = "WRONGSTACK_USE_TS_RS";
|
|
2750
|
-
runtimePromise = null;
|
|
2751
|
-
languageCache = /* @__PURE__ */ new Map();
|
|
4016
|
+
init_import_extractor();
|
|
4017
|
+
init_parser_batch();
|
|
4018
|
+
init_py_parser();
|
|
2752
4019
|
}
|
|
2753
4020
|
});
|
|
2754
4021
|
|
|
2755
4022
|
// src/read.ts
|
|
2756
|
-
import * as
|
|
4023
|
+
import * as fs15 from "node:fs/promises";
|
|
2757
4024
|
import { FsError, ToolValidationError } from "@wrongstack/core/types";
|
|
2758
4025
|
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
2759
4026
|
|
|
@@ -2829,7 +4096,7 @@ function isBinaryBuffer(buf) {
|
|
|
2829
4096
|
}
|
|
2830
4097
|
|
|
2831
4098
|
// src/codebase-index/background-indexer.ts
|
|
2832
|
-
import * as
|
|
4099
|
+
import * as fs14 from "node:fs";
|
|
2833
4100
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
2834
4101
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
2835
4102
|
|
|
@@ -2914,9 +4181,9 @@ var indexCircuitBreaker = new IndexCircuitBreaker();
|
|
|
2914
4181
|
import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
|
|
2915
4182
|
import { execFile } from "node:child_process";
|
|
2916
4183
|
import { createHash as createHash2 } from "node:crypto";
|
|
2917
|
-
import * as
|
|
4184
|
+
import * as fs10 from "node:fs/promises";
|
|
2918
4185
|
import { availableParallelism } from "node:os";
|
|
2919
|
-
import * as
|
|
4186
|
+
import * as path14 from "node:path";
|
|
2920
4187
|
import {
|
|
2921
4188
|
DEFAULT_WALK_IGNORE_DIRS,
|
|
2922
4189
|
indexParallelBatchSize,
|
|
@@ -3591,276 +4858,89 @@ var ModuleResolver = class {
|
|
|
3591
4858
|
else if (segment !== "self") break;
|
|
3592
4859
|
}
|
|
3593
4860
|
const rest2 = segments.filter((segment) => segment !== "self" && segment !== "super");
|
|
3594
|
-
return this.lookupWithExtensions(path5.posix.join(base, ...rest2), "rs");
|
|
3595
|
-
}
|
|
3596
|
-
const owningCrate = findOwningRoot(this.structure, fromFile, ["cargo"]);
|
|
3597
|
-
const crate = head === "crate" ? owningCrate : this.structure.roots.find(
|
|
3598
|
-
(root) => root.kind === "cargo" && root.importPath === head?.replace(/-/g, "_")
|
|
3599
|
-
);
|
|
3600
|
-
if (!crate) {
|
|
3601
|
-
return this.lookupWithExtensions(
|
|
3602
|
-
path5.posix.join(path5.posix.dirname(fromFile), ...segments),
|
|
3603
|
-
"rs"
|
|
3604
|
-
);
|
|
3605
|
-
}
|
|
3606
|
-
const rest = segments.slice(1);
|
|
3607
|
-
for (const base of crate.sourceRoots) {
|
|
3608
|
-
const parent = rest.length > 1 ? this.lookupWithExtensions(path5.posix.join(base, ...rest.slice(0, -1)), "rs") : void 0;
|
|
3609
|
-
const exact = this.lookupWithExtensions(path5.posix.join(base, ...rest), "rs");
|
|
3610
|
-
const hit = exact ?? parent ?? this.lookupWithExtensions(path5.posix.join(base, "lib"), "rs");
|
|
3611
|
-
if (hit) return hit;
|
|
3612
|
-
}
|
|
3613
|
-
return void 0;
|
|
3614
|
-
}
|
|
3615
|
-
/** `com.example.Thing` and `com.example.*` against JVM source roots. */
|
|
3616
|
-
resolveJvm(spec) {
|
|
3617
|
-
const segments = spec.split(".").filter(Boolean);
|
|
3618
|
-
if (segments.length === 0) return void 0;
|
|
3619
|
-
const sourceRoots = this.structure.roots.filter((root) => root.kind === "maven" || root.kind === "gradle").flatMap((root) => root.sourceRoots);
|
|
3620
|
-
const wildcard = segments[segments.length - 1] === "*";
|
|
3621
|
-
const parts = wildcard ? segments.slice(0, -1) : segments;
|
|
3622
|
-
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
3623
|
-
const target = path5.posix.join(base, ...parts);
|
|
3624
|
-
const hit = wildcard ? this.representativeIn(target, "jvm") : this.lookupWithExtensions(target, "jvm");
|
|
3625
|
-
if (hit) return hit;
|
|
3626
|
-
}
|
|
3627
|
-
return void 0;
|
|
3628
|
-
}
|
|
3629
|
-
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
3630
|
-
resolveInclude(fromFile, spec) {
|
|
3631
|
-
const relative3 = this.lookupWithExtensions(
|
|
3632
|
-
path5.posix.join(path5.posix.dirname(fromFile), spec),
|
|
3633
|
-
"c"
|
|
3634
|
-
);
|
|
3635
|
-
if (relative3) return relative3;
|
|
3636
|
-
for (const base of [
|
|
3637
|
-
path5.posix.join(this.structure.projectRoot, "include"),
|
|
3638
|
-
this.structure.projectRoot
|
|
3639
|
-
]) {
|
|
3640
|
-
const hit = this.lookupWithExtensions(path5.posix.join(base, spec), "c");
|
|
3641
|
-
if (hit) return hit;
|
|
3642
|
-
}
|
|
3643
|
-
return void 0;
|
|
3644
|
-
}
|
|
3645
|
-
/** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
|
|
3646
|
-
resolveRuby(fromFile, spec) {
|
|
3647
|
-
const relative3 = this.lookupWithExtensions(
|
|
3648
|
-
path5.posix.join(path5.posix.dirname(fromFile), spec),
|
|
3649
|
-
"ruby"
|
|
3650
|
-
);
|
|
3651
|
-
if (relative3) return relative3;
|
|
3652
|
-
for (const base of [
|
|
3653
|
-
path5.posix.join(this.structure.projectRoot, "lib"),
|
|
3654
|
-
this.structure.projectRoot
|
|
3655
|
-
]) {
|
|
3656
|
-
const hit = this.lookupWithExtensions(path5.posix.join(base, spec), "ruby");
|
|
3657
|
-
if (hit) return hit;
|
|
3658
|
-
}
|
|
3659
|
-
return void 0;
|
|
3660
|
-
}
|
|
3661
|
-
};
|
|
3662
|
-
|
|
3663
|
-
// src/codebase-index/import-extractor.ts
|
|
3664
|
-
var IMPORT_MAX_FILE_CHARS = 512 * 1024;
|
|
3665
|
-
var IMPORT_MAX_PER_FILE = 400;
|
|
3666
|
-
var DOTTED_IMPORT = [
|
|
3667
|
-
{ re: /^[ \t]*import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;?\s*$/gm }
|
|
3668
|
-
];
|
|
3669
|
-
var LANG_IMPORTS = {
|
|
3670
|
-
// Go and Python have real AST extractors; these patterns are the fallback for
|
|
3671
|
-
// machines with no Go toolchain or Python interpreter installed, where the
|
|
3672
|
-
// parser degrades to regex symbols and would otherwise contribute no edges.
|
|
3673
|
-
go: [
|
|
3674
|
-
{ re: /^[ \t]*import\s+(?:[\w.]+\s+)?"([^"]+)"/gm },
|
|
3675
|
-
// Grouped form: inside `import ( … )` each line is an optional alias plus a
|
|
3676
|
-
// quoted path. A stray match elsewhere resolves to no file and is dropped.
|
|
3677
|
-
{ re: /^[ \t]*(?:[A-Za-z_.]\w*\s+)?"([^"]+)"\s*$/gm }
|
|
3678
|
-
],
|
|
3679
|
-
py: [{ re: /^[ \t]*import\s+([\w.]+)/gm }, { re: /^[ \t]*from\s+([.\w]+)\s+import\b/gm }],
|
|
3680
|
-
rs: [
|
|
3681
|
-
// use a::b::C; | use a::b::{C, D}; → the path before any brace
|
|
3682
|
-
{ re: /^[ \t]*(?:pub\s+)?use\s+([\w:]+?)(?:::\{|\s*;|\s+as\b)/gm },
|
|
3683
|
-
// mod foo; (a declaration *and* a dependency on foo.rs / foo/mod.rs)
|
|
3684
|
-
{ re: /^[ \t]*(?:pub\s+)?mod\s+(\w+)\s*;/gm }
|
|
3685
|
-
],
|
|
3686
|
-
java: DOTTED_IMPORT,
|
|
3687
|
-
kotlin: DOTTED_IMPORT,
|
|
3688
|
-
scala: [{ re: /^[ \t]*import\s+([\w.]+(?:\.[_*])?)\s*$/gm }],
|
|
3689
|
-
csharp: [
|
|
3690
|
-
// using Foo.Bar; | using static Foo.Bar; | using Alias = Foo.Bar;
|
|
3691
|
-
{ re: /^[ \t]*global\s+using\s+(?:static\s+)?([\w.]+)\s*;/gm, name: "full" },
|
|
3692
|
-
{ re: /^[ \t]*using\s+(?:static\s+)?(?:\w+\s*=\s*)?([\w.]+)\s*;/gm, name: "full" }
|
|
3693
|
-
],
|
|
3694
|
-
// Quoted includes only: <stdio.h> is a system header with no indexed file.
|
|
3695
|
-
c: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
3696
|
-
cpp: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
3697
|
-
ruby: [{ re: /^[ \t]*(?:require|require_relative|load)\s*\(?\s*['"]([^'"]+)['"]/gm }],
|
|
3698
|
-
php: [
|
|
3699
|
-
// `use A\B\C` imports the class C, which is what the index has a symbol
|
|
3700
|
-
// for — the namespace symbol only covers the `A\B` prefix.
|
|
3701
|
-
{ re: /^[ \t]*use\s+(?:function\s+|const\s+)?([\w\\]+)/gm },
|
|
3702
|
-
{ re: /^[ \t]*(?:require|require_once|include|include_once)\s*\(?\s*['"]([^'"]+)['"]/gm }
|
|
3703
|
-
],
|
|
3704
|
-
swift: [{ re: /^[ \t]*import\s+(?:struct\s+|class\s+|func\s+)?([\w.]+)\s*$/gm }],
|
|
3705
|
-
dart: [{ re: /^[ \t]*(?:import|export|part)\s+['"]([^'"]+)['"]/gm }],
|
|
3706
|
-
lua: [{ re: /\brequire\s*\(?\s*['"]([^'"]+)['"]/g }],
|
|
3707
|
-
elixir: [{ re: /^[ \t]*(?:alias|import|require|use)\s+([A-Z][\w.]*)/gm, name: "full" }],
|
|
3708
|
-
haskell: [{ re: /^[ \t]*import\s+(?:qualified\s+)?([\w.]+)/gm, name: "full" }],
|
|
3709
|
-
zig: [{ re: /@import\s*\(\s*"([^"]+)"\s*\)/g }],
|
|
3710
|
-
proto: [{ re: /^[ \t]*import\s+(?:public\s+|weak\s+)?"([^"]+)"\s*;/gm }],
|
|
3711
|
-
// `@import "x"`, `@use "x"`, `@forward "x"` — Sass and plain CSS alike.
|
|
3712
|
-
css: [{ re: /^[ \t]*@(?:import|use|forward)\s+(?:url\()?\s*['"]([^'"]+)['"]/gm }],
|
|
3713
|
-
// A .vue/.svelte file's <script> block is JS; the same ESM syntax applies.
|
|
3714
|
-
vue: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
3715
|
-
svelte: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
3716
|
-
html: [
|
|
3717
|
-
{ re: /<script[^>]+src\s*=\s*['"]([^'"]+)['"]/g },
|
|
3718
|
-
{ re: /<link[^>]+href\s*=\s*['"]([^'"]+)['"]/g }
|
|
3719
|
-
],
|
|
3720
|
-
shell: [{ re: /^[ \t]*(?:source|\.)\s+(\S+)/gm }],
|
|
3721
|
-
r: [{ re: /\b(?:source|library|require)\s*\(\s*['"]?([\w./-]+)['"]?\s*\)/g }]
|
|
3722
|
-
};
|
|
3723
|
-
function lastSegment(specifier) {
|
|
3724
|
-
const pathLike = /[/\\]|::/.test(specifier);
|
|
3725
|
-
const segments = specifier.split(/[/\\]|::/).filter(Boolean);
|
|
3726
|
-
let last = segments[segments.length - 1] ?? specifier;
|
|
3727
|
-
if (last === "*" || last === "_") {
|
|
3728
|
-
last = segments[segments.length - 2] ?? specifier;
|
|
3729
|
-
}
|
|
3730
|
-
if (pathLike) return last.replace(/\.[A-Za-z0-9]{1,8}$/, "") || last;
|
|
3731
|
-
const dotted = last.split(".").filter((part) => part && part !== "*" && part !== "_");
|
|
3732
|
-
return dotted[dotted.length - 1] ?? last;
|
|
3733
|
-
}
|
|
3734
|
-
function newlineOffsets(content) {
|
|
3735
|
-
const offsets = [];
|
|
3736
|
-
for (let i = 0; i < content.length; i++) {
|
|
3737
|
-
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
3738
|
-
}
|
|
3739
|
-
return offsets;
|
|
3740
|
-
}
|
|
3741
|
-
function lineAt(offsets, index) {
|
|
3742
|
-
let low = 0;
|
|
3743
|
-
let high = offsets.length;
|
|
3744
|
-
while (low < high) {
|
|
3745
|
-
const mid = low + high >>> 1;
|
|
3746
|
-
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
3747
|
-
else high = mid;
|
|
3748
|
-
}
|
|
3749
|
-
return low + 1;
|
|
3750
|
-
}
|
|
3751
|
-
function hasImportPatterns(lang) {
|
|
3752
|
-
return LANG_IMPORTS[lang] !== void 0;
|
|
3753
|
-
}
|
|
3754
|
-
function extractImports(opts) {
|
|
3755
|
-
const patterns = LANG_IMPORTS[opts.lang];
|
|
3756
|
-
if (!patterns || !opts.content) return [];
|
|
3757
|
-
const limit = opts.maxImports ?? IMPORT_MAX_PER_FILE;
|
|
3758
|
-
const content = opts.content.length > IMPORT_MAX_FILE_CHARS ? opts.content.slice(0, IMPORT_MAX_FILE_CHARS) : opts.content;
|
|
3759
|
-
const refs = [];
|
|
3760
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3761
|
-
const offsets = newlineOffsets(content);
|
|
3762
|
-
for (const pattern of patterns) {
|
|
3763
|
-
const re = new RegExp(pattern.re.source, pattern.re.flags);
|
|
3764
|
-
for (const match of content.matchAll(re)) {
|
|
3765
|
-
if (refs.length >= limit) return refs;
|
|
3766
|
-
const specifier = match[1]?.trim();
|
|
3767
|
-
if (!specifier) continue;
|
|
3768
|
-
const module = specifier;
|
|
3769
|
-
const toName = pattern.name === "full" ? module : lastSegment(module);
|
|
3770
|
-
if (!toName) continue;
|
|
3771
|
-
const key = `${module}\0${toName}`;
|
|
3772
|
-
if (seen.has(key)) continue;
|
|
3773
|
-
seen.add(key);
|
|
3774
|
-
refs.push({
|
|
3775
|
-
fromId: 0,
|
|
3776
|
-
toName,
|
|
3777
|
-
callType: "import",
|
|
3778
|
-
line: lineAt(offsets, match.index ?? 0),
|
|
3779
|
-
lang: opts.lang,
|
|
3780
|
-
module
|
|
3781
|
-
});
|
|
3782
|
-
}
|
|
3783
|
-
}
|
|
3784
|
-
return refs;
|
|
3785
|
-
}
|
|
3786
|
-
|
|
3787
|
-
// src/codebase-index/parser-dispatch.ts
|
|
3788
|
-
async function parseFileContent(file, content, lang) {
|
|
3789
|
-
const parsed = await dispatch(file, content, lang);
|
|
3790
|
-
return withRelations(parsed, content, lang);
|
|
3791
|
-
}
|
|
3792
|
-
async function dispatch(file, content, lang) {
|
|
3793
|
-
switch (lang) {
|
|
3794
|
-
case "ts":
|
|
3795
|
-
case "tsx":
|
|
3796
|
-
case "js":
|
|
3797
|
-
case "jsx": {
|
|
3798
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
|
|
3799
|
-
return parseSymbols9({ file, content, lang });
|
|
3800
|
-
}
|
|
3801
|
-
case "go": {
|
|
3802
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
|
|
3803
|
-
return parseSymbols9({ file, content, lang: "go" });
|
|
3804
|
-
}
|
|
3805
|
-
case "py": {
|
|
3806
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
|
|
3807
|
-
return parseSymbols9({ file, content, lang: "py" });
|
|
3808
|
-
}
|
|
3809
|
-
case "rs": {
|
|
3810
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
|
|
3811
|
-
return parseSymbols9({ file, content, lang: "rs" });
|
|
3812
|
-
}
|
|
3813
|
-
case "json": {
|
|
3814
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
|
|
3815
|
-
return parseSymbols9({ file, content, lang: "json" });
|
|
4861
|
+
return this.lookupWithExtensions(path5.posix.join(base, ...rest2), "rs");
|
|
3816
4862
|
}
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
4863
|
+
const owningCrate = findOwningRoot(this.structure, fromFile, ["cargo"]);
|
|
4864
|
+
const crate = head === "crate" ? owningCrate : this.structure.roots.find(
|
|
4865
|
+
(root) => root.kind === "cargo" && root.importPath === head?.replace(/-/g, "_")
|
|
4866
|
+
);
|
|
4867
|
+
if (!crate) {
|
|
4868
|
+
return this.lookupWithExtensions(
|
|
4869
|
+
path5.posix.join(path5.posix.dirname(fromFile), ...segments),
|
|
4870
|
+
"rs"
|
|
4871
|
+
);
|
|
3820
4872
|
}
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
case "cpp":
|
|
3828
|
-
case "java":
|
|
3829
|
-
case "csharp":
|
|
3830
|
-
case "php":
|
|
3831
|
-
case "ruby":
|
|
3832
|
-
case "swift":
|
|
3833
|
-
case "kotlin":
|
|
3834
|
-
case "shell":
|
|
3835
|
-
case "elixir": {
|
|
3836
|
-
try {
|
|
3837
|
-
const { parseSymbols: parseSymbols10 } = await Promise.resolve().then(() => (init_tree_sitter_parser(), tree_sitter_parser_exports));
|
|
3838
|
-
const parsed = await parseSymbols10({ file, content, lang });
|
|
3839
|
-
if (parsed.symbols.length > 0) return parsed;
|
|
3840
|
-
} catch {
|
|
3841
|
-
}
|
|
3842
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
3843
|
-
return parseSymbols9({ file, content, lang });
|
|
4873
|
+
const rest = segments.slice(1);
|
|
4874
|
+
for (const base of crate.sourceRoots) {
|
|
4875
|
+
const parent = rest.length > 1 ? this.lookupWithExtensions(path5.posix.join(base, ...rest.slice(0, -1)), "rs") : void 0;
|
|
4876
|
+
const exact = this.lookupWithExtensions(path5.posix.join(base, ...rest), "rs");
|
|
4877
|
+
const hit = exact ?? parent ?? this.lookupWithExtensions(path5.posix.join(base, "lib"), "rs");
|
|
4878
|
+
if (hit) return hit;
|
|
3844
4879
|
}
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
4880
|
+
return void 0;
|
|
4881
|
+
}
|
|
4882
|
+
/** `com.example.Thing` and `com.example.*` against JVM source roots. */
|
|
4883
|
+
resolveJvm(spec) {
|
|
4884
|
+
const segments = spec.split(".").filter(Boolean);
|
|
4885
|
+
if (segments.length === 0) return void 0;
|
|
4886
|
+
const sourceRoots = this.structure.roots.filter((root) => root.kind === "maven" || root.kind === "gradle").flatMap((root) => root.sourceRoots);
|
|
4887
|
+
const wildcard = segments[segments.length - 1] === "*";
|
|
4888
|
+
const parts = wildcard ? segments.slice(0, -1) : segments;
|
|
4889
|
+
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
4890
|
+
const target = path5.posix.join(base, ...parts);
|
|
4891
|
+
const hit = wildcard ? this.representativeIn(target, "jvm") : this.lookupWithExtensions(target, "jvm");
|
|
4892
|
+
if (hit) return hit;
|
|
3848
4893
|
}
|
|
4894
|
+
return void 0;
|
|
3849
4895
|
}
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
4896
|
+
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
4897
|
+
resolveInclude(fromFile, spec) {
|
|
4898
|
+
const relative3 = this.lookupWithExtensions(
|
|
4899
|
+
path5.posix.join(path5.posix.dirname(fromFile), spec),
|
|
4900
|
+
"c"
|
|
4901
|
+
);
|
|
4902
|
+
if (relative3) return relative3;
|
|
4903
|
+
for (const base of [
|
|
4904
|
+
path5.posix.join(this.structure.projectRoot, "include"),
|
|
4905
|
+
this.structure.projectRoot
|
|
4906
|
+
]) {
|
|
4907
|
+
const hit = this.lookupWithExtensions(path5.posix.join(base, spec), "c");
|
|
4908
|
+
if (hit) return hit;
|
|
4909
|
+
}
|
|
4910
|
+
return void 0;
|
|
3855
4911
|
}
|
|
3856
|
-
|
|
3857
|
-
|
|
4912
|
+
/** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
|
|
4913
|
+
resolveRuby(fromFile, spec) {
|
|
4914
|
+
const relative3 = this.lookupWithExtensions(
|
|
4915
|
+
path5.posix.join(path5.posix.dirname(fromFile), spec),
|
|
4916
|
+
"ruby"
|
|
4917
|
+
);
|
|
4918
|
+
if (relative3) return relative3;
|
|
4919
|
+
for (const base of [
|
|
4920
|
+
path5.posix.join(this.structure.projectRoot, "lib"),
|
|
4921
|
+
this.structure.projectRoot
|
|
4922
|
+
]) {
|
|
4923
|
+
const hit = this.lookupWithExtensions(path5.posix.join(base, spec), "ruby");
|
|
4924
|
+
if (hit) return hit;
|
|
4925
|
+
}
|
|
4926
|
+
return void 0;
|
|
4927
|
+
}
|
|
4928
|
+
};
|
|
4929
|
+
|
|
4930
|
+
// src/codebase-index/indexer.ts
|
|
4931
|
+
init_parser_dispatch();
|
|
3858
4932
|
|
|
3859
4933
|
// src/codebase-index/parser-worker-pool.ts
|
|
4934
|
+
import * as fs7 from "node:fs";
|
|
4935
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
3860
4936
|
import { Worker } from "node:worker_threads";
|
|
3861
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3862
|
-
import * as fs6 from "node:fs";
|
|
3863
4937
|
var WORKER_POOL_THRESHOLD = 500;
|
|
4938
|
+
function resolveWorkerPoolThreshold() {
|
|
4939
|
+
const raw = process.env["WRONGSTACK_INDEX_WORKER_THRESHOLD"];
|
|
4940
|
+
if (raw === void 0) return WORKER_POOL_THRESHOLD;
|
|
4941
|
+
if (!/^\d+$/.test(raw)) return WORKER_POOL_THRESHOLD;
|
|
4942
|
+
return Number.parseInt(raw, 10);
|
|
4943
|
+
}
|
|
3864
4944
|
var ParserWorkerPool = class {
|
|
3865
4945
|
constructor(maxWorkers = defaultWorkerCount()) {
|
|
3866
4946
|
this.maxWorkers = maxWorkers;
|
|
@@ -3904,7 +4984,8 @@ var ParserWorkerPool = class {
|
|
|
3904
4984
|
w.unref();
|
|
3905
4985
|
w.on("message", (msg) => this.handleMessage(msg));
|
|
3906
4986
|
w.on("error", (err) => this.handleError(err, w));
|
|
3907
|
-
|
|
4987
|
+
w.on("exit", () => this.retireByReference(w));
|
|
4988
|
+
this.workers.push({ worker: w, workerId: w.threadId, busy: false });
|
|
3908
4989
|
} catch {
|
|
3909
4990
|
if (this.workers.length === 0) {
|
|
3910
4991
|
this.unavailable = true;
|
|
@@ -3920,7 +5001,7 @@ var ParserWorkerPool = class {
|
|
|
3920
5001
|
}
|
|
3921
5002
|
/**
|
|
3922
5003
|
* Parse files in parallel across the worker pool. Returns a flat
|
|
3923
|
-
* `FileSymbols[]` in completion order (caller
|
|
5004
|
+
* `FileSymbols[]` in completion order (caller matches by file path).
|
|
3924
5005
|
*
|
|
3925
5006
|
* Content is pre-read by the main thread (for the content-hash check)
|
|
3926
5007
|
* and passed to workers to avoid a second disk read. Files are
|
|
@@ -3941,21 +5022,19 @@ var ParserWorkerPool = class {
|
|
|
3941
5022
|
chunks[i % workerCount].push(files[i]);
|
|
3942
5023
|
}
|
|
3943
5024
|
return new Promise((resolve4, reject) => {
|
|
5025
|
+
const pendingChunks = /* @__PURE__ */ new Map();
|
|
3944
5026
|
this.pending.set(batchId, {
|
|
3945
5027
|
resolve: resolve4,
|
|
3946
5028
|
reject,
|
|
3947
5029
|
accumulated: [],
|
|
3948
|
-
|
|
3949
|
-
|
|
5030
|
+
pendingChunks,
|
|
5031
|
+
settled: false
|
|
3950
5032
|
});
|
|
3951
5033
|
for (let i = 0; i < workerCount; i++) {
|
|
3952
5034
|
const pw = this.workers[i];
|
|
3953
5035
|
pw.busy = true;
|
|
3954
|
-
pw.
|
|
3955
|
-
|
|
3956
|
-
id: batchId,
|
|
3957
|
-
files: chunks[i]
|
|
3958
|
-
});
|
|
5036
|
+
pendingChunks.set(pw.workerId, chunks[i]);
|
|
5037
|
+
pw.worker.postMessage({ type: "parse", id: batchId, files: chunks[i] });
|
|
3959
5038
|
}
|
|
3960
5039
|
});
|
|
3961
5040
|
}
|
|
@@ -3964,6 +5043,12 @@ var ParserWorkerPool = class {
|
|
|
3964
5043
|
const workers = this.workers.map((w) => w.worker);
|
|
3965
5044
|
this.workers = [];
|
|
3966
5045
|
this.unavailable = false;
|
|
5046
|
+
for (const [, p] of this.pending) {
|
|
5047
|
+
if (p.settled) continue;
|
|
5048
|
+
p.settled = true;
|
|
5049
|
+
p.reject(new Error("ParserWorkerPool shut down"));
|
|
5050
|
+
}
|
|
5051
|
+
this.pending.clear();
|
|
3967
5052
|
for (const w of workers) {
|
|
3968
5053
|
try {
|
|
3969
5054
|
w.postMessage({ type: "shutdown" });
|
|
@@ -3984,39 +5069,117 @@ var ParserWorkerPool = class {
|
|
|
3984
5069
|
})
|
|
3985
5070
|
)
|
|
3986
5071
|
);
|
|
3987
|
-
for (const [, p] of this.pending) p.reject(new Error("ParserWorkerPool shut down"));
|
|
3988
|
-
this.pending.clear();
|
|
3989
5072
|
}
|
|
3990
5073
|
handleMessage(msg) {
|
|
3991
5074
|
const batch = this.pending.get(msg.id);
|
|
3992
5075
|
if (!batch) return;
|
|
5076
|
+
const worker2 = this.workers.find((w) => w.workerId === msg.workerId);
|
|
5077
|
+
if (worker2) worker2.busy = false;
|
|
5078
|
+
batch.pendingChunks.delete(msg.workerId);
|
|
3993
5079
|
batch.accumulated.push(...msg.results);
|
|
3994
|
-
batch.
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
if (batch.completedWorkers >= batch.expectedWorkers) {
|
|
5080
|
+
if (batch.pendingChunks.size === 0) {
|
|
5081
|
+
if (batch.settled) return;
|
|
5082
|
+
batch.settled = true;
|
|
3998
5083
|
this.pending.delete(msg.id);
|
|
3999
5084
|
batch.resolve(batch.accumulated);
|
|
4000
5085
|
}
|
|
4001
5086
|
}
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
5087
|
+
/**
|
|
5088
|
+
* Remove a worker from the pool and salvage any chunk it still owed.
|
|
5089
|
+
*
|
|
5090
|
+
* Idempotent by workerId — `error` and `exit` can both fire for one
|
|
5091
|
+
* death, and a worker may die while no batch references it. When the
|
|
5092
|
+
* dead worker owed files to an in-flight batch and other workers remain,
|
|
5093
|
+
* those files are re-parsed inline on this thread (one fewer worker
|
|
5094
|
+
* should cost latency, not correctness). When it was the last worker,
|
|
5095
|
+
* every remaining batch rejects so the indexer's existing inline
|
|
5096
|
+
* fallback takes over the whole batch.
|
|
5097
|
+
*/
|
|
5098
|
+
retireWorker(workerId) {
|
|
5099
|
+
const entry = this.workers.find((w) => w.workerId === workerId);
|
|
5100
|
+
if (!entry) return;
|
|
5101
|
+
this.workers = this.workers.filter((w) => w.workerId !== workerId);
|
|
5102
|
+
for (const [batchId, batch] of [...this.pending]) {
|
|
5103
|
+
const orphaned = batch.pendingChunks.get(workerId);
|
|
5104
|
+
if (!orphaned) continue;
|
|
5105
|
+
if (this.workers.length === 0) {
|
|
5106
|
+
this.pending.delete(batchId);
|
|
5107
|
+
this.unavailable = true;
|
|
5108
|
+
if (!batch.settled) {
|
|
5109
|
+
batch.settled = true;
|
|
5110
|
+
batch.reject(new Error("ParserWorkerPool: all workers died mid-batch"));
|
|
5111
|
+
}
|
|
5112
|
+
continue;
|
|
5113
|
+
}
|
|
5114
|
+
void this.reparseInline(batch, orphaned, workerId);
|
|
5115
|
+
}
|
|
5116
|
+
}
|
|
5117
|
+
/**
|
|
5118
|
+
* Salvage path: re-parse an orphaned chunk on this thread. Files that
|
|
5119
|
+
* fail here stay absent from the results — same contract as a per-file
|
|
5120
|
+
* error inside a live worker (see handleMessage).
|
|
5121
|
+
*/
|
|
5122
|
+
async reparseInline(batch, orphaned, workerId) {
|
|
5123
|
+
try {
|
|
5124
|
+
const { parseFileContent: parseFileContent2 } = await Promise.resolve().then(() => (init_parser_dispatch(), parser_dispatch_exports));
|
|
5125
|
+
for (const item of orphaned) {
|
|
5126
|
+
if (batch.settled) return;
|
|
5127
|
+
try {
|
|
5128
|
+
const parsed = await parseFileContent2(item.file, item.content, item.lang);
|
|
5129
|
+
batch.accumulated.push(parsed);
|
|
5130
|
+
} catch {
|
|
5131
|
+
}
|
|
5132
|
+
await new Promise((resolve4) => setImmediate(resolve4));
|
|
5133
|
+
}
|
|
5134
|
+
} finally {
|
|
5135
|
+
this.finishSalvage(batch, workerId);
|
|
5136
|
+
}
|
|
5137
|
+
}
|
|
5138
|
+
/**
|
|
5139
|
+
* Terminal tail of a salvage — runs on every exit path. Kept free of
|
|
5140
|
+
* control flow inside a `finally` (noUnsafeFinally): releases the
|
|
5141
|
+
* pending-marker and resolves the batch if this was its last chunk.
|
|
5142
|
+
*/
|
|
5143
|
+
finishSalvage(batch, workerId) {
|
|
5144
|
+
batch.pendingChunks.delete(workerId);
|
|
5145
|
+
if (batch.pendingChunks.size === 0) {
|
|
5146
|
+
for (const [batchId, tracked] of this.pending) {
|
|
5147
|
+
if (tracked === batch) {
|
|
5148
|
+
if (batch.settled) return;
|
|
5149
|
+
batch.settled = true;
|
|
5150
|
+
this.pending.delete(batchId);
|
|
5151
|
+
batch.resolve(batch.accumulated);
|
|
5152
|
+
return;
|
|
5153
|
+
}
|
|
5154
|
+
}
|
|
4008
5155
|
}
|
|
4009
5156
|
}
|
|
5157
|
+
/**
|
|
5158
|
+
* Retire by worker object rather than threadId. `threadId` is -1 before
|
|
5159
|
+
* the worker emits `online`, so a death during script load would make a
|
|
5160
|
+
* threadId-keyed lookup silently no-op and leak the entry (with its
|
|
5161
|
+
* pending chunk) — reference identity is correct in every case.
|
|
5162
|
+
*/
|
|
5163
|
+
retireByReference(source) {
|
|
5164
|
+
const entry = this.workers.find((w) => w.worker === source);
|
|
5165
|
+
if (entry) this.retireWorker(entry.workerId);
|
|
5166
|
+
}
|
|
5167
|
+
handleError(err, source) {
|
|
5168
|
+
void err;
|
|
5169
|
+
this.retireByReference(source);
|
|
5170
|
+
}
|
|
4010
5171
|
};
|
|
4011
5172
|
function defaultWorkerCount() {
|
|
4012
5173
|
const cores = globalThis.navigator?.hardwareConcurrency ?? 4;
|
|
4013
5174
|
return Math.max(1, Math.min(4, cores - 1));
|
|
4014
5175
|
}
|
|
4015
5176
|
function resolveWorkerScriptUrl() {
|
|
5177
|
+
const override = process.env["WRONGSTACK_PARSER_WORKER_SCRIPT"];
|
|
5178
|
+
if (override) return pathToFileURL(override);
|
|
4016
5179
|
for (const rel of ["./parser-worker-script.js", "./codebase-index/parser-worker-script.js"]) {
|
|
4017
5180
|
try {
|
|
4018
5181
|
const url = new URL(rel, import.meta.url);
|
|
4019
|
-
if (url.protocol === "file:" &&
|
|
5182
|
+
if (url.protocol === "file:" && fs7.existsSync(fileURLToPath2(url))) return url;
|
|
4020
5183
|
} catch {
|
|
4021
5184
|
}
|
|
4022
5185
|
}
|
|
@@ -4029,8 +5192,8 @@ function getParserPool() {
|
|
|
4029
5192
|
}
|
|
4030
5193
|
|
|
4031
5194
|
// src/codebase-index/writer.ts
|
|
4032
|
-
import * as
|
|
4033
|
-
import * as
|
|
5195
|
+
import * as fs9 from "node:fs";
|
|
5196
|
+
import * as path13 from "node:path";
|
|
4034
5197
|
|
|
4035
5198
|
// src/codebase-index/bm25.ts
|
|
4036
5199
|
var K1 = 1.5;
|
|
@@ -4125,11 +5288,11 @@ var Bm25Index = class {
|
|
|
4125
5288
|
init_languages();
|
|
4126
5289
|
|
|
4127
5290
|
// src/codebase-index/schema.ts
|
|
4128
|
-
var SCHEMA_VERSION =
|
|
5291
|
+
var SCHEMA_VERSION = 5;
|
|
4129
5292
|
|
|
4130
5293
|
// src/codebase-index/sqlite-runtime.ts
|
|
4131
|
-
import { createRequire } from "node:module";
|
|
4132
5294
|
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
5295
|
+
import { loadRuntimeDatabaseSync } from "@wrongstack/persistence";
|
|
4133
5296
|
var warningSilenced = false;
|
|
4134
5297
|
function silenceSqliteExperimentalWarning() {
|
|
4135
5298
|
if (warningSilenced) return;
|
|
@@ -4147,11 +5310,10 @@ function loadDatabaseSync() {
|
|
|
4147
5310
|
if (DatabaseSyncCtor) return DatabaseSyncCtor;
|
|
4148
5311
|
silenceSqliteExperimentalWarning();
|
|
4149
5312
|
try {
|
|
4150
|
-
|
|
4151
|
-
DatabaseSyncCtor = req("node:sqlite").DatabaseSync;
|
|
5313
|
+
DatabaseSyncCtor = loadRuntimeDatabaseSync();
|
|
4152
5314
|
} catch (err) {
|
|
4153
5315
|
throw new Error(
|
|
4154
|
-
`The codebase index needs
|
|
5316
|
+
`The codebase index needs node:sqlite (Node >= 22.5) or bun:sqlite. This runtime doesn't provide it: ${toErrorMessage(err)}`
|
|
4155
5317
|
);
|
|
4156
5318
|
}
|
|
4157
5319
|
return DatabaseSyncCtor;
|
|
@@ -4194,9 +5356,87 @@ function runSqliteWithRetry(fn) {
|
|
|
4194
5356
|
throw lastError;
|
|
4195
5357
|
}
|
|
4196
5358
|
|
|
5359
|
+
// src/codebase-index/vector-search.ts
|
|
5360
|
+
var RRF_K = 60;
|
|
5361
|
+
function vectorEmbeddingEnabled() {
|
|
5362
|
+
return process.env["WRONGSTACK_INDEX_VECTORS"] === "1";
|
|
5363
|
+
}
|
|
5364
|
+
var VECTOR_DIMENSIONS = 384;
|
|
5365
|
+
var NGRAM_SIZE = 3;
|
|
5366
|
+
function embedText(text) {
|
|
5367
|
+
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
5368
|
+
const normalized = text.toLowerCase().trim();
|
|
5369
|
+
if (normalized.length < NGRAM_SIZE) {
|
|
5370
|
+
const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
|
|
5371
|
+
for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
|
|
5372
|
+
const ngram = padded.slice(i, i + NGRAM_SIZE);
|
|
5373
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5374
|
+
vec[bucket] += 1;
|
|
5375
|
+
}
|
|
5376
|
+
} else {
|
|
5377
|
+
for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
|
|
5378
|
+
const ngram = normalized.slice(i, i + NGRAM_SIZE);
|
|
5379
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5380
|
+
vec[bucket] += 1;
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5383
|
+
let norm = 0;
|
|
5384
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5385
|
+
norm += vec[i] * vec[i];
|
|
5386
|
+
}
|
|
5387
|
+
norm = Math.sqrt(norm);
|
|
5388
|
+
if (norm > 0) {
|
|
5389
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5390
|
+
vec[i] /= norm;
|
|
5391
|
+
}
|
|
5392
|
+
}
|
|
5393
|
+
return vec;
|
|
5394
|
+
}
|
|
5395
|
+
function hashNgram(str) {
|
|
5396
|
+
let hash = 2166136261;
|
|
5397
|
+
for (let i = 0; i < str.length; i++) {
|
|
5398
|
+
hash ^= str.charCodeAt(i);
|
|
5399
|
+
hash = Math.imul(hash, 16777619);
|
|
5400
|
+
}
|
|
5401
|
+
return hash >>> 0;
|
|
5402
|
+
}
|
|
5403
|
+
function cosineSimilarity(a, b) {
|
|
5404
|
+
let dot = 0;
|
|
5405
|
+
const len = Math.min(a.length, b.length);
|
|
5406
|
+
for (let i = 0; i < len; i++) {
|
|
5407
|
+
dot += a[i] * b[i];
|
|
5408
|
+
}
|
|
5409
|
+
return dot;
|
|
5410
|
+
}
|
|
5411
|
+
function encodeVector(vec) {
|
|
5412
|
+
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
5413
|
+
}
|
|
5414
|
+
function decodeVector(buf) {
|
|
5415
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
5416
|
+
const copy = new Float32Array(buf.byteLength / 4);
|
|
5417
|
+
for (let i = 0; i < copy.length; i++) {
|
|
5418
|
+
copy[i] = view.getFloat32(i * 4, true);
|
|
5419
|
+
}
|
|
5420
|
+
return copy;
|
|
5421
|
+
}
|
|
5422
|
+
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
5423
|
+
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
5424
|
+
const scored = [];
|
|
5425
|
+
for (const id of allIds) {
|
|
5426
|
+
const bm25Rank = bm25Ranks.get(id);
|
|
5427
|
+
const vecRank = vectorRanks.get(id);
|
|
5428
|
+
let score = 0;
|
|
5429
|
+
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
5430
|
+
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
5431
|
+
scored.push([id, score]);
|
|
5432
|
+
}
|
|
5433
|
+
scored.sort((a, b) => b[1] - a[1]);
|
|
5434
|
+
return scored;
|
|
5435
|
+
}
|
|
5436
|
+
|
|
4197
5437
|
// src/codebase-index/writer-admin.ts
|
|
4198
|
-
import * as
|
|
4199
|
-
import * as
|
|
5438
|
+
import * as fs8 from "node:fs";
|
|
5439
|
+
import * as path12 from "node:path";
|
|
4200
5440
|
var DB_FILE = "index.db";
|
|
4201
5441
|
function getAllIndexableWithStatement(stmt) {
|
|
4202
5442
|
return stmt("SELECT id, text FROM symbols").all().map(
|
|
@@ -4232,6 +5472,14 @@ function getMetadataWithStatement(stmt, key) {
|
|
|
4232
5472
|
const rows = stmt("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
4233
5473
|
return rows[0]?.value;
|
|
4234
5474
|
}
|
|
5475
|
+
function getIndexSummaryWithStatement(stmt) {
|
|
5476
|
+
const fileRows = stmt("SELECT COUNT(*) AS n FROM files").all();
|
|
5477
|
+
const lastRows = stmt("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
5478
|
+
return {
|
|
5479
|
+
totalFiles: fileRows[0] ? Number(fileRows[0].n) : 0,
|
|
5480
|
+
lastIndexed: lastRows.length ? Number(lastRows[0]?.value) : null
|
|
5481
|
+
};
|
|
5482
|
+
}
|
|
4235
5483
|
function getFileMetaWithStatement(stmt, file) {
|
|
4236
5484
|
const rows = stmt(
|
|
4237
5485
|
"SELECT file, lang, mtime_ms, symbol_count, last_indexed, content_hash FROM files WHERE file = ?"
|
|
@@ -4261,22 +5509,106 @@ function getAllFileMetasWithStatement(stmt) {
|
|
|
4261
5509
|
}
|
|
4262
5510
|
function getIndexDbSizeBytes(indexDir) {
|
|
4263
5511
|
try {
|
|
4264
|
-
return
|
|
5512
|
+
return fs8.statSync(path12.join(indexDir, DB_FILE)).size;
|
|
4265
5513
|
} catch {
|
|
4266
5514
|
return 0;
|
|
4267
5515
|
}
|
|
4268
5516
|
}
|
|
4269
5517
|
|
|
5518
|
+
// src/codebase-index/writer-helpers.ts
|
|
5519
|
+
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
5520
|
+
function escapeLike(value) {
|
|
5521
|
+
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
5522
|
+
}
|
|
5523
|
+
function ladderChunkSizes(total, max) {
|
|
5524
|
+
if (total <= 0) return [];
|
|
5525
|
+
const sizes = [];
|
|
5526
|
+
let remaining = total;
|
|
5527
|
+
while (remaining > 0) {
|
|
5528
|
+
const cap = Math.min(remaining, max);
|
|
5529
|
+
const pow = cap >= 1 ? 2 ** Math.floor(Math.log2(cap)) : 1;
|
|
5530
|
+
const take = Math.max(1, Math.min(pow, remaining));
|
|
5531
|
+
sizes.push(take);
|
|
5532
|
+
remaining -= take;
|
|
5533
|
+
}
|
|
5534
|
+
return sizes;
|
|
5535
|
+
}
|
|
5536
|
+
function nextPow2(count) {
|
|
5537
|
+
return count <= 1 ? 1 : 2 ** Math.ceil(Math.log2(count));
|
|
5538
|
+
}
|
|
5539
|
+
function padToInBucket(values) {
|
|
5540
|
+
if (values.length <= 1) return values.slice();
|
|
5541
|
+
const target = nextPow2(values.length);
|
|
5542
|
+
const padded = values.slice();
|
|
5543
|
+
while (padded.length < target) padded.push(padded[0]);
|
|
5544
|
+
return padded;
|
|
5545
|
+
}
|
|
5546
|
+
function placeholders(count) {
|
|
5547
|
+
return Array.from({ length: count }, () => "?").join(",");
|
|
5548
|
+
}
|
|
5549
|
+
function inListChunks(total, max) {
|
|
5550
|
+
if (total <= 0) return [];
|
|
5551
|
+
if (nextPow2(total) <= max) return [total];
|
|
5552
|
+
const powMax = Math.max(1, 2 ** Math.floor(Math.log2(max)));
|
|
5553
|
+
return ladderChunkSizes(total, powMax);
|
|
5554
|
+
}
|
|
5555
|
+
function posixIndexPath(file) {
|
|
5556
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
5557
|
+
}
|
|
5558
|
+
function indexedFileMatchSql(column = "file") {
|
|
5559
|
+
return `(${column} = ? OR replace(${column}, '\\', '/') = ? OR replace(${column}, '\\', '/') LIKE ? ESCAPE '\\')`;
|
|
5560
|
+
}
|
|
5561
|
+
function indexedFileMatchArgs(file) {
|
|
5562
|
+
const posix4 = posixIndexPath(file.trim());
|
|
5563
|
+
return [file, posix4, `%/${escapeLike(posix4)}`];
|
|
5564
|
+
}
|
|
5565
|
+
function matchesIndexedPackageFilter(storedFile, packageLabel, filter) {
|
|
5566
|
+
if (packageLabel === filter) return true;
|
|
5567
|
+
const posixFile = posixIndexPath(storedFile);
|
|
5568
|
+
const posixFilter = posixIndexPath(filter.trim());
|
|
5569
|
+
if (!posixFilter) return false;
|
|
5570
|
+
return posixFile === posixFilter || posixFile.endsWith(`/${posixFilter}`) || posixFile.includes(`/${posixFilter}/`);
|
|
5571
|
+
}
|
|
5572
|
+
function assignRefsToSymbols(refs, symbols) {
|
|
5573
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
5574
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
5575
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5576
|
+
const assigned = [];
|
|
5577
|
+
for (const ref of refs) {
|
|
5578
|
+
let owner;
|
|
5579
|
+
for (const symbol of ordered) {
|
|
5580
|
+
if (symbol.line > ref.line) break;
|
|
5581
|
+
owner = symbol;
|
|
5582
|
+
}
|
|
5583
|
+
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
5584
|
+
if (!owner || owner.id <= 0) continue;
|
|
5585
|
+
const key = `${owner.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
5586
|
+
if (seen.has(key)) continue;
|
|
5587
|
+
seen.add(key);
|
|
5588
|
+
assigned.push({ ...ref, fromId: owner.id });
|
|
5589
|
+
}
|
|
5590
|
+
return assigned;
|
|
5591
|
+
}
|
|
5592
|
+
function resolveIndexDir(projectRoot, override) {
|
|
5593
|
+
return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
|
|
5594
|
+
}
|
|
5595
|
+
function codebaseIndexDirOverride(ctx) {
|
|
5596
|
+
const v = ctx.meta?.["codebaseIndexDir"];
|
|
5597
|
+
return typeof v === "string" ? v : void 0;
|
|
5598
|
+
}
|
|
5599
|
+
|
|
4270
5600
|
// src/codebase-index/writer-bulk-insert.ts
|
|
4271
5601
|
function bulkInsertSymbolsWithStatement(stmt, maxSqlVars, rows) {
|
|
4272
5602
|
if (rows.length === 0) return;
|
|
4273
|
-
const
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
const
|
|
5603
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 12)));
|
|
5604
|
+
let cursor = 0;
|
|
5605
|
+
for (const take of ladder) {
|
|
5606
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
5607
|
+
cursor += take;
|
|
5608
|
+
const placeholders2 = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
4277
5609
|
const insert = stmt(
|
|
4278
|
-
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text
|
|
4279
|
-
VALUES ${
|
|
5610
|
+
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text)
|
|
5611
|
+
VALUES ${placeholders2}`
|
|
4280
5612
|
);
|
|
4281
5613
|
const binds = [];
|
|
4282
5614
|
for (const r of chunk) {
|
|
@@ -4291,8 +5623,7 @@ function bulkInsertSymbolsWithStatement(stmt, maxSqlVars, rows) {
|
|
|
4291
5623
|
r.signature,
|
|
4292
5624
|
r.docComment,
|
|
4293
5625
|
r.scope,
|
|
4294
|
-
r.text
|
|
4295
|
-
r.file
|
|
5626
|
+
r.text
|
|
4296
5627
|
);
|
|
4297
5628
|
}
|
|
4298
5629
|
insert.run(...binds);
|
|
@@ -4300,11 +5631,13 @@ function bulkInsertSymbolsWithStatement(stmt, maxSqlVars, rows) {
|
|
|
4300
5631
|
}
|
|
4301
5632
|
function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
|
|
4302
5633
|
if (!ftsAvailable || rows.length === 0) return;
|
|
4303
|
-
const
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
const
|
|
4307
|
-
|
|
5634
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 2)));
|
|
5635
|
+
let cursor = 0;
|
|
5636
|
+
for (const take of ladder) {
|
|
5637
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
5638
|
+
cursor += take;
|
|
5639
|
+
const placeholders2 = chunk.map(() => "(?, ?)").join(", ");
|
|
5640
|
+
const insert = stmt(`INSERT INTO symbols_fts(rowid, text) VALUES ${placeholders2}`);
|
|
4308
5641
|
const binds = [];
|
|
4309
5642
|
for (const r of chunk) binds.push(r.id, r.text);
|
|
4310
5643
|
insert.run(...binds);
|
|
@@ -4312,11 +5645,13 @@ function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
|
|
|
4312
5645
|
}
|
|
4313
5646
|
function bulkInsertVectorsWithStatement(stmt, maxSqlVars, rows) {
|
|
4314
5647
|
if (rows.length === 0) return;
|
|
4315
|
-
const
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
const
|
|
4319
|
-
|
|
5648
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 2)));
|
|
5649
|
+
let cursor = 0;
|
|
5650
|
+
for (const take of ladder) {
|
|
5651
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
5652
|
+
cursor += take;
|
|
5653
|
+
const placeholders2 = chunk.map(() => "(?, ?)").join(", ");
|
|
5654
|
+
const insert = stmt(`INSERT INTO symbol_vectors(symbol_id, vector) VALUES ${placeholders2}`);
|
|
4320
5655
|
const binds = [];
|
|
4321
5656
|
for (const r of chunk) binds.push(r.id, r.vector);
|
|
4322
5657
|
insert.run(...binds);
|
|
@@ -4324,13 +5659,15 @@ function bulkInsertVectorsWithStatement(stmt, maxSqlVars, rows) {
|
|
|
4324
5659
|
}
|
|
4325
5660
|
function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
|
|
4326
5661
|
if (refs.length === 0) return;
|
|
4327
|
-
const
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
const
|
|
5662
|
+
const ladder = ladderChunkSizes(refs.length, Math.max(1, Math.floor(maxSqlVars / 8)));
|
|
5663
|
+
let cursor = 0;
|
|
5664
|
+
for (const take of ladder) {
|
|
5665
|
+
const chunk = refs.slice(cursor, cursor + take);
|
|
5666
|
+
cursor += take;
|
|
5667
|
+
const placeholders2 = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
4331
5668
|
const insert = stmt(
|
|
4332
5669
|
`INSERT INTO refs(from_id, to_name, to_id, call_type, line, lang, module, to_file)
|
|
4333
|
-
VALUES ${
|
|
5670
|
+
VALUES ${placeholders2}`
|
|
4334
5671
|
);
|
|
4335
5672
|
const binds = [];
|
|
4336
5673
|
for (const ref of chunk) {
|
|
@@ -4484,56 +5821,6 @@ function materializeWeightedEdges(edgeMap, idPrefix) {
|
|
|
4484
5821
|
return edges;
|
|
4485
5822
|
}
|
|
4486
5823
|
|
|
4487
|
-
// src/codebase-index/writer-helpers.ts
|
|
4488
|
-
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
4489
|
-
function escapeLike(value) {
|
|
4490
|
-
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
4491
|
-
}
|
|
4492
|
-
function posixIndexPath(file) {
|
|
4493
|
-
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
4494
|
-
}
|
|
4495
|
-
function indexedFileMatchSql(column = "file") {
|
|
4496
|
-
return `(${column} = ? OR replace(${column}, '\\', '/') = ? OR replace(${column}, '\\', '/') LIKE ? ESCAPE '\\')`;
|
|
4497
|
-
}
|
|
4498
|
-
function indexedFileMatchArgs(file) {
|
|
4499
|
-
const posix4 = posixIndexPath(file.trim());
|
|
4500
|
-
return [file, posix4, `%/${escapeLike(posix4)}`];
|
|
4501
|
-
}
|
|
4502
|
-
function matchesIndexedPackageFilter(storedFile, packageLabel, filter) {
|
|
4503
|
-
if (packageLabel === filter) return true;
|
|
4504
|
-
const posixFile = posixIndexPath(storedFile);
|
|
4505
|
-
const posixFilter = posixIndexPath(filter.trim());
|
|
4506
|
-
if (!posixFilter) return false;
|
|
4507
|
-
return posixFile === posixFilter || posixFile.endsWith(`/${posixFilter}`) || posixFile.includes(`/${posixFilter}/`);
|
|
4508
|
-
}
|
|
4509
|
-
function assignRefsToSymbols(refs, symbols) {
|
|
4510
|
-
if (refs.length === 0 || symbols.length === 0) return [];
|
|
4511
|
-
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
4512
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4513
|
-
const assigned = [];
|
|
4514
|
-
for (const ref of refs) {
|
|
4515
|
-
let owner;
|
|
4516
|
-
for (const symbol of ordered) {
|
|
4517
|
-
if (symbol.line > ref.line) break;
|
|
4518
|
-
owner = symbol;
|
|
4519
|
-
}
|
|
4520
|
-
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
4521
|
-
if (!owner || owner.id <= 0) continue;
|
|
4522
|
-
const key = `${owner.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
4523
|
-
if (seen.has(key)) continue;
|
|
4524
|
-
seen.add(key);
|
|
4525
|
-
assigned.push({ ...ref, fromId: owner.id });
|
|
4526
|
-
}
|
|
4527
|
-
return assigned;
|
|
4528
|
-
}
|
|
4529
|
-
function resolveIndexDir(projectRoot, override) {
|
|
4530
|
-
return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
|
|
4531
|
-
}
|
|
4532
|
-
function codebaseIndexDirOverride(ctx) {
|
|
4533
|
-
const v = ctx.meta?.["codebaseIndexDir"];
|
|
4534
|
-
return typeof v === "string" ? v : void 0;
|
|
4535
|
-
}
|
|
4536
|
-
|
|
4537
5824
|
// src/codebase-index/writer-ref-mapper.ts
|
|
4538
5825
|
function mapWriterRefRow(row) {
|
|
4539
5826
|
return {
|
|
@@ -4555,20 +5842,20 @@ function mapWriterRefRow(row) {
|
|
|
4555
5842
|
var MAX_SQL_VARS = 900;
|
|
4556
5843
|
function chunkedIdQuery(stmt, ids, buildSql, extraArgs = []) {
|
|
4557
5844
|
const results = [];
|
|
4558
|
-
for (
|
|
4559
|
-
const chunk = ids.slice(
|
|
4560
|
-
|
|
4561
|
-
const sql = buildSql(placeholders);
|
|
5845
|
+
for (const take of inListChunks(ids.length, MAX_SQL_VARS)) {
|
|
5846
|
+
const chunk = padToInBucket(ids.slice(0, take));
|
|
5847
|
+
ids = ids.slice(take);
|
|
5848
|
+
const sql = buildSql(placeholders(chunk.length));
|
|
4562
5849
|
results.push(...stmt(sql).all(...chunk, ...extraArgs));
|
|
4563
5850
|
}
|
|
4564
5851
|
return results;
|
|
4565
5852
|
}
|
|
4566
5853
|
function chunkedIdScalar(stmt, ids, buildSql, extraArgs = []) {
|
|
4567
5854
|
let total = 0;
|
|
4568
|
-
for (
|
|
4569
|
-
const chunk = ids.slice(
|
|
4570
|
-
|
|
4571
|
-
const sql = buildSql(placeholders);
|
|
5855
|
+
for (const take of inListChunks(ids.length, MAX_SQL_VARS)) {
|
|
5856
|
+
const chunk = padToInBucket(ids.slice(0, take));
|
|
5857
|
+
ids = ids.slice(take);
|
|
5858
|
+
const sql = buildSql(placeholders(chunk.length));
|
|
4572
5859
|
const rows = stmt(sql).all(...chunk, ...extraArgs);
|
|
4573
5860
|
total += rows[0]?.n ?? 0;
|
|
4574
5861
|
}
|
|
@@ -4597,16 +5884,24 @@ function resolveIndexedFiles(stmt, file) {
|
|
|
4597
5884
|
}
|
|
4598
5885
|
function resolveSymbolIds(stmt, symbolName, file) {
|
|
4599
5886
|
if (!file) {
|
|
4600
|
-
const
|
|
4601
|
-
|
|
5887
|
+
const rows = stmt("SELECT id FROM symbols WHERE name = ? ORDER BY id").all(
|
|
5888
|
+
symbolName
|
|
5889
|
+
);
|
|
5890
|
+
return rows.map((r) => r.id);
|
|
4602
5891
|
}
|
|
4603
5892
|
const indexedFiles = resolveIndexedFiles(stmt, file);
|
|
4604
5893
|
if (indexedFiles.length === 0) return [];
|
|
4605
|
-
const
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
5894
|
+
const ids = [];
|
|
5895
|
+
let cursor = 0;
|
|
5896
|
+
for (const take of inListChunks(indexedFiles.length, MAX_SQL_VARS)) {
|
|
5897
|
+
const files = padToInBucket(indexedFiles.slice(cursor, cursor + take));
|
|
5898
|
+
cursor += take;
|
|
5899
|
+
const rows = stmt(
|
|
5900
|
+
`SELECT id FROM symbols WHERE name = ? AND file IN (${placeholders(files.length)}) ORDER BY id`
|
|
5901
|
+
).all(symbolName, ...files);
|
|
5902
|
+
ids.push(...rows.map((r) => r.id));
|
|
5903
|
+
}
|
|
5904
|
+
return ids;
|
|
4610
5905
|
}
|
|
4611
5906
|
function findIncomingCallsByName(stmt, symbolName, file, limit) {
|
|
4612
5907
|
const targetIds = resolveSymbolIds(stmt, symbolName, file);
|
|
@@ -5040,9 +6335,9 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
5040
6335
|
const loadedIds = new Set(syms.map((s) => s.id));
|
|
5041
6336
|
const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
|
|
5042
6337
|
if (missingIds.length > 0) {
|
|
5043
|
-
const
|
|
6338
|
+
const placeholders2 = missingIds.map(() => "?").join(",");
|
|
5044
6339
|
const extras = stmt(
|
|
5045
|
-
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${
|
|
6340
|
+
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders2})`
|
|
5046
6341
|
).all(...missingIds);
|
|
5047
6342
|
for (const s of extras) symById.set(s.id, s);
|
|
5048
6343
|
}
|
|
@@ -5055,81 +6350,6 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
5055
6350
|
return { nodes, edges };
|
|
5056
6351
|
}
|
|
5057
6352
|
|
|
5058
|
-
// src/codebase-index/vector-search.ts
|
|
5059
|
-
var RRF_K = 60;
|
|
5060
|
-
var VECTOR_DIMENSIONS = 384;
|
|
5061
|
-
var NGRAM_SIZE = 3;
|
|
5062
|
-
function embedText(text) {
|
|
5063
|
-
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
5064
|
-
const normalized = text.toLowerCase().trim();
|
|
5065
|
-
if (normalized.length < NGRAM_SIZE) {
|
|
5066
|
-
const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
|
|
5067
|
-
for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
|
|
5068
|
-
const ngram = padded.slice(i, i + NGRAM_SIZE);
|
|
5069
|
-
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5070
|
-
vec[bucket] += 1;
|
|
5071
|
-
}
|
|
5072
|
-
} else {
|
|
5073
|
-
for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
|
|
5074
|
-
const ngram = normalized.slice(i, i + NGRAM_SIZE);
|
|
5075
|
-
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5076
|
-
vec[bucket] += 1;
|
|
5077
|
-
}
|
|
5078
|
-
}
|
|
5079
|
-
let norm = 0;
|
|
5080
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5081
|
-
norm += vec[i] * vec[i];
|
|
5082
|
-
}
|
|
5083
|
-
norm = Math.sqrt(norm);
|
|
5084
|
-
if (norm > 0) {
|
|
5085
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5086
|
-
vec[i] /= norm;
|
|
5087
|
-
}
|
|
5088
|
-
}
|
|
5089
|
-
return vec;
|
|
5090
|
-
}
|
|
5091
|
-
function hashNgram(str) {
|
|
5092
|
-
let hash = 2166136261;
|
|
5093
|
-
for (let i = 0; i < str.length; i++) {
|
|
5094
|
-
hash ^= str.charCodeAt(i);
|
|
5095
|
-
hash = Math.imul(hash, 16777619);
|
|
5096
|
-
}
|
|
5097
|
-
return hash >>> 0;
|
|
5098
|
-
}
|
|
5099
|
-
function cosineSimilarity(a, b) {
|
|
5100
|
-
let dot = 0;
|
|
5101
|
-
const len = Math.min(a.length, b.length);
|
|
5102
|
-
for (let i = 0; i < len; i++) {
|
|
5103
|
-
dot += a[i] * b[i];
|
|
5104
|
-
}
|
|
5105
|
-
return dot;
|
|
5106
|
-
}
|
|
5107
|
-
function encodeVector(vec) {
|
|
5108
|
-
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
5109
|
-
}
|
|
5110
|
-
function decodeVector(buf) {
|
|
5111
|
-
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
5112
|
-
const copy = new Float32Array(buf.byteLength / 4);
|
|
5113
|
-
for (let i = 0; i < copy.length; i++) {
|
|
5114
|
-
copy[i] = view.getFloat32(i * 4, true);
|
|
5115
|
-
}
|
|
5116
|
-
return copy;
|
|
5117
|
-
}
|
|
5118
|
-
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
5119
|
-
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
5120
|
-
const scored = [];
|
|
5121
|
-
for (const id of allIds) {
|
|
5122
|
-
const bm25Rank = bm25Ranks.get(id);
|
|
5123
|
-
const vecRank = vectorRanks.get(id);
|
|
5124
|
-
let score = 0;
|
|
5125
|
-
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
5126
|
-
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
5127
|
-
scored.push([id, score]);
|
|
5128
|
-
}
|
|
5129
|
-
scored.sort((a, b) => b[1] - a[1]);
|
|
5130
|
-
return scored;
|
|
5131
|
-
}
|
|
5132
|
-
|
|
5133
6353
|
// src/codebase-index/writer-mutations.ts
|
|
5134
6354
|
function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvailable, allocateSymbolIds, invalidateIncomingRefsForFiles, resolveRefsForNamesUnsafe2, entries, options = {}) {
|
|
5135
6355
|
if (entries.length === 0 && (options.deleteForFiles?.length ?? 0) === 0) {
|
|
@@ -5141,24 +6361,29 @@ function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvail
|
|
|
5141
6361
|
for (const ref of entry.refs) affectedNames.add(ref.toName);
|
|
5142
6362
|
}
|
|
5143
6363
|
if (options.deleteForFiles && options.deleteForFiles.length > 0) {
|
|
5144
|
-
const placeholders = options.deleteForFiles.map(() => "?").join(",");
|
|
5145
6364
|
for (const name of invalidateIncomingRefsForFiles(options.deleteForFiles)) {
|
|
5146
6365
|
affectedNames.add(name);
|
|
5147
6366
|
}
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
6367
|
+
let cursor = 0;
|
|
6368
|
+
for (const take of inListChunks(options.deleteForFiles.length, Math.floor(maxSqlVars / 4))) {
|
|
6369
|
+
const bucket = padToInBucket(options.deleteForFiles.slice(cursor, cursor + take));
|
|
6370
|
+
cursor += take;
|
|
6371
|
+
const ph = placeholders(bucket.length);
|
|
6372
|
+
if (ftsAvailable) {
|
|
6373
|
+
stmtFn(
|
|
6374
|
+
`DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
6375
|
+
).run(...bucket);
|
|
6376
|
+
}
|
|
6377
|
+
if (vectorsAvailable) {
|
|
6378
|
+
stmtFn(
|
|
6379
|
+
`DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
6380
|
+
).run(...bucket);
|
|
6381
|
+
}
|
|
5154
6382
|
stmtFn(
|
|
5155
|
-
`DELETE FROM
|
|
5156
|
-
).run(...
|
|
6383
|
+
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
6384
|
+
).run(...bucket);
|
|
6385
|
+
stmtFn(`DELETE FROM symbols WHERE file IN (${ph})`).run(...bucket);
|
|
5157
6386
|
}
|
|
5158
|
-
stmtFn(
|
|
5159
|
-
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
5160
|
-
).run(...options.deleteForFiles);
|
|
5161
|
-
stmtFn(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
|
|
5162
6387
|
}
|
|
5163
6388
|
const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
|
|
5164
6389
|
let nextId = allocateSymbolIds(totalSymbols);
|
|
@@ -5190,12 +6415,14 @@ function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvail
|
|
|
5190
6415
|
text: buildIndexableText(s.name, s.signature, s.docComment)
|
|
5191
6416
|
});
|
|
5192
6417
|
}
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
6418
|
+
if (vectorsAvailable) {
|
|
6419
|
+
vectorRows.push({
|
|
6420
|
+
id,
|
|
6421
|
+
vector: encodeVector(
|
|
6422
|
+
embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
|
|
6423
|
+
)
|
|
6424
|
+
});
|
|
6425
|
+
}
|
|
5199
6426
|
const inserted = { ...s, id };
|
|
5200
6427
|
allInserted.push(inserted);
|
|
5201
6428
|
insertedForEntry.push(inserted);
|
|
@@ -5287,9 +6514,8 @@ var CORE_TABLES_SQL = `
|
|
|
5287
6514
|
signature TEXT NOT NULL DEFAULT '',
|
|
5288
6515
|
doc_comment TEXT NOT NULL DEFAULT '',
|
|
5289
6516
|
scope TEXT NOT NULL DEFAULT '',
|
|
5290
|
-
text TEXT NOT NULL DEFAULT ''
|
|
5291
|
-
|
|
5292
|
-
);
|
|
6517
|
+
text TEXT NOT NULL DEFAULT ''
|
|
6518
|
+
);
|
|
5293
6519
|
`;
|
|
5294
6520
|
var FILE_INDEX_SQL = [
|
|
5295
6521
|
"CREATE INDEX IF NOT EXISTS idx_f_package ON files(package)"
|
|
@@ -5300,7 +6526,6 @@ var SYMBOL_INDEX_SQL = [
|
|
|
5300
6526
|
"CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)",
|
|
5301
6527
|
"CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)",
|
|
5302
6528
|
"CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)",
|
|
5303
|
-
"CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)",
|
|
5304
6529
|
"CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)"
|
|
5305
6530
|
];
|
|
5306
6531
|
var REFS_TABLE_SQL = `
|
|
@@ -5375,11 +6600,12 @@ function getUnresolvedImportsWithStatement(stmtFn, maxSqlVars, onlyFiles) {
|
|
|
5375
6600
|
return stmtFn(base).all();
|
|
5376
6601
|
}
|
|
5377
6602
|
const out = [];
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
const
|
|
6603
|
+
let cursor = 0;
|
|
6604
|
+
for (const take of inListChunks(onlyFiles.length, maxSqlVars)) {
|
|
6605
|
+
const chunk = padToInBucket(onlyFiles.slice(cursor, cursor + take));
|
|
6606
|
+
cursor += take;
|
|
5381
6607
|
out.push(
|
|
5382
|
-
...stmtFn(`${base} AND s.file IN (${placeholders})`).all(...chunk)
|
|
6608
|
+
...stmtFn(`${base} AND s.file IN (${placeholders(chunk.length)})`).all(...chunk)
|
|
5383
6609
|
);
|
|
5384
6610
|
}
|
|
5385
6611
|
return out;
|
|
@@ -5450,16 +6676,18 @@ function applyImportResolutionsWithStatement(db, stmtFn, runWithRetry, maxSqlVar
|
|
|
5450
6676
|
)`
|
|
5451
6677
|
);
|
|
5452
6678
|
const chunkSize = Math.max(1, Math.floor(maxSqlVars / 4));
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
const
|
|
6679
|
+
let cursor = 0;
|
|
6680
|
+
for (const take of ladderChunkSizes(resolutions.length, chunkSize)) {
|
|
6681
|
+
const chunk = resolutions.slice(cursor, cursor + take);
|
|
6682
|
+
cursor += take;
|
|
6683
|
+
const valuesPh = chunk.map(() => "(?, ?, ?, ?)").join(", ");
|
|
5456
6684
|
const binds = [];
|
|
5457
6685
|
for (const entry of chunk) {
|
|
5458
6686
|
binds.push(entry.fromFile, entry.lang, entry.module, entry.toFile);
|
|
5459
6687
|
}
|
|
5460
6688
|
stmtFn(
|
|
5461
6689
|
`INSERT INTO temp.import_resolution(from_file, lang, module, to_file)
|
|
5462
|
-
VALUES ${
|
|
6690
|
+
VALUES ${valuesPh}`
|
|
5463
6691
|
).run(...binds);
|
|
5464
6692
|
}
|
|
5465
6693
|
db.exec(
|
|
@@ -5496,9 +6724,11 @@ function resolveRefsForNamesUnsafe(stmtFn, maxSqlVars, names) {
|
|
|
5496
6724
|
const list = [...names].filter((name) => name.length > 0);
|
|
5497
6725
|
if (list.length === 0) return 0;
|
|
5498
6726
|
let total = 0;
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
const
|
|
6727
|
+
let cursor = 0;
|
|
6728
|
+
for (const take of inListChunks(list.length, maxSqlVars)) {
|
|
6729
|
+
const chunk = padToInBucket(list.slice(cursor, cursor + take));
|
|
6730
|
+
cursor += take;
|
|
6731
|
+
const ph = placeholders(chunk.length);
|
|
5502
6732
|
try {
|
|
5503
6733
|
const result = stmtFn(
|
|
5504
6734
|
`UPDATE refs
|
|
@@ -5507,16 +6737,16 @@ function resolveRefsForNamesUnsafe(stmtFn, maxSqlVars, names) {
|
|
|
5507
6737
|
SELECT sym.name AS name, lf.family AS family, MIN(sym.id) AS id
|
|
5508
6738
|
FROM symbols sym
|
|
5509
6739
|
JOIN lang_family lf ON lf.lang = sym.lang
|
|
5510
|
-
WHERE sym.name IN (${
|
|
6740
|
+
WHERE sym.name IN (${ph})
|
|
5511
6741
|
GROUP BY sym.name, lf.family
|
|
5512
6742
|
UNION ALL
|
|
5513
6743
|
SELECT sym.name AS name, '${LANG_FAMILY_WILDCARD}' AS family, MIN(sym.id) AS id
|
|
5514
6744
|
FROM symbols sym
|
|
5515
|
-
WHERE sym.name IN (${
|
|
6745
|
+
WHERE sym.name IN (${ph})
|
|
5516
6746
|
GROUP BY sym.name
|
|
5517
6747
|
) AS s,
|
|
5518
6748
|
lang_family AS rf
|
|
5519
|
-
WHERE refs.to_name IN (${
|
|
6749
|
+
WHERE refs.to_name IN (${ph})
|
|
5520
6750
|
AND rf.lang = refs.lang
|
|
5521
6751
|
AND s.name = refs.to_name
|
|
5522
6752
|
AND s.family = rf.family`
|
|
@@ -5528,7 +6758,7 @@ function resolveRefsForNamesUnsafe(stmtFn, maxSqlVars, names) {
|
|
|
5528
6758
|
SELECT sym.id FROM symbols sym
|
|
5529
6759
|
WHERE sym.name = refs.to_name AND ${FAMILY_MATCH_SQL}
|
|
5530
6760
|
ORDER BY sym.id LIMIT 1
|
|
5531
|
-
) WHERE refs.to_name IN (${
|
|
6761
|
+
) WHERE refs.to_name IN (${ph})
|
|
5532
6762
|
AND EXISTS (
|
|
5533
6763
|
SELECT 1 FROM symbols sym
|
|
5534
6764
|
WHERE sym.name = refs.to_name AND ${FAMILY_MATCH_SQL}
|
|
@@ -5585,7 +6815,7 @@ function buildWriterSearchWhere(query, filter) {
|
|
|
5585
6815
|
const conditions = [];
|
|
5586
6816
|
const values = [];
|
|
5587
6817
|
let effectiveKind = filter?.kind;
|
|
5588
|
-
if (filter?.lspKind
|
|
6818
|
+
if (filter?.lspKind != null) {
|
|
5589
6819
|
const mapped = lspKindToInternalKind(filter.lspKind);
|
|
5590
6820
|
if (mapped !== null) {
|
|
5591
6821
|
effectiveKind = mapped;
|
|
@@ -5664,7 +6894,7 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
|
|
|
5664
6894
|
);
|
|
5665
6895
|
}
|
|
5666
6896
|
let effectiveKind = filter?.kind;
|
|
5667
|
-
if (filter?.lspKind
|
|
6897
|
+
if (filter?.lspKind != null) {
|
|
5668
6898
|
const mapped = lspKindToInternalKind(filter.lspKind);
|
|
5669
6899
|
if (mapped === null) return { results: [], total: 0 };
|
|
5670
6900
|
effectiveKind = mapped;
|
|
@@ -5701,15 +6931,14 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
|
|
|
5701
6931
|
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
5702
6932
|
}
|
|
5703
6933
|
const where = conditions.join(" AND ");
|
|
5704
|
-
const countRows = stmtFn(
|
|
5705
|
-
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
5706
|
-
).all(...values);
|
|
5707
|
-
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
5708
|
-
if (total === 0) return { results: [], total: 0 };
|
|
5709
6934
|
const bm25Rows = stmtFn(
|
|
5710
6935
|
`SELECT s.id, s.lang, s.kind, s.name, s.file, s.line, s.col, s.signature, s.doc_comment,
|
|
5711
6936
|
-bm25(symbols_fts) AS score,
|
|
5712
|
-
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
6937
|
+
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet,
|
|
6938
|
+
-- Keep this uncorrelated: referencing outer columns turns it into
|
|
6939
|
+
-- a per-row subquery and defeats the one-count-per-statement win.
|
|
6940
|
+
(SELECT COUNT(*) FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
6941
|
+
WHERE ${where}) AS total_count
|
|
5713
6942
|
FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
5714
6943
|
WHERE ${where}
|
|
5715
6944
|
ORDER BY
|
|
@@ -5718,13 +6947,15 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
|
|
|
5718
6947
|
ELSE 2 END,
|
|
5719
6948
|
bm25(symbols_fts), lower(s.name), s.file, s.line, s.col, s.id
|
|
5720
6949
|
LIMIT ?`
|
|
5721
|
-
).all(...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
5722
|
-
if (
|
|
6950
|
+
).all(...values, ...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
6951
|
+
if (bm25Rows.length === 0) return { results: [], total: 0 };
|
|
6952
|
+
const total = Number(bm25Rows[0]?.total_count ?? 0);
|
|
6953
|
+
if (vectorsAvailable) {
|
|
5723
6954
|
const queryVec = embedText(query);
|
|
5724
6955
|
const candidateIds = bm25Rows.map((r) => r.id);
|
|
5725
|
-
const
|
|
6956
|
+
const placeholders2 = candidateIds.map(() => "?").join(",");
|
|
5726
6957
|
const vecRows = stmtFn(
|
|
5727
|
-
`SELECT sv.symbol_id, sv.vector FROM symbol_vectors sv WHERE sv.symbol_id IN (${
|
|
6958
|
+
`SELECT sv.symbol_id, sv.vector FROM symbol_vectors sv WHERE sv.symbol_id IN (${placeholders2})`
|
|
5728
6959
|
).all(...candidateIds);
|
|
5729
6960
|
const vecScores = vecRows.map((r) => ({
|
|
5730
6961
|
id: r.symbol_id,
|
|
@@ -5912,9 +7143,9 @@ var IndexStore = class _IndexStore {
|
|
|
5912
7143
|
}
|
|
5913
7144
|
constructor(projectRoot, opts = {}) {
|
|
5914
7145
|
this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
|
|
5915
|
-
|
|
7146
|
+
fs9.mkdirSync(this.indexDir, { recursive: true });
|
|
5916
7147
|
const Database = loadDatabaseSync();
|
|
5917
|
-
this.db = new Database(
|
|
7148
|
+
this.db = new Database(path13.join(this.indexDir, DB_FILE2));
|
|
5918
7149
|
applyIndexStorePragmas(this.db);
|
|
5919
7150
|
this.initSchema();
|
|
5920
7151
|
}
|
|
@@ -6043,7 +7274,11 @@ var IndexStore = class _IndexStore {
|
|
|
6043
7274
|
);
|
|
6044
7275
|
if (symbolCount !== ftsCount) {
|
|
6045
7276
|
this.db.exec("DELETE FROM symbols_fts");
|
|
6046
|
-
if (
|
|
7277
|
+
if (vectorEmbeddingEnabled() && this.stmt(
|
|
7278
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='symbol_vectors'"
|
|
7279
|
+
).get() !== void 0) {
|
|
7280
|
+
this.db.exec("DELETE FROM symbol_vectors");
|
|
7281
|
+
}
|
|
6047
7282
|
const rows = this.stmt(
|
|
6048
7283
|
"SELECT id, name, signature, doc_comment FROM symbols ORDER BY id"
|
|
6049
7284
|
).all();
|
|
@@ -6062,8 +7297,13 @@ var IndexStore = class _IndexStore {
|
|
|
6062
7297
|
this.ftsAvailable = false;
|
|
6063
7298
|
}
|
|
6064
7299
|
try {
|
|
6065
|
-
|
|
6066
|
-
|
|
7300
|
+
if (vectorEmbeddingEnabled()) {
|
|
7301
|
+
this.db.exec(SYMBOL_VECTORS_TABLE_SQL);
|
|
7302
|
+
this.vectorsAvailable = true;
|
|
7303
|
+
} else {
|
|
7304
|
+
this.db.exec("DROP TABLE IF EXISTS symbol_vectors");
|
|
7305
|
+
this.vectorsAvailable = false;
|
|
7306
|
+
}
|
|
6067
7307
|
} catch {
|
|
6068
7308
|
this.vectorsAvailable = false;
|
|
6069
7309
|
}
|
|
@@ -6098,14 +7338,22 @@ var IndexStore = class _IndexStore {
|
|
|
6098
7338
|
}
|
|
6099
7339
|
invalidateIncomingRefsForFiles(files) {
|
|
6100
7340
|
if (files.length === 0) return /* @__PURE__ */ new Set();
|
|
6101
|
-
const
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
7341
|
+
const names = [];
|
|
7342
|
+
let cursor = 0;
|
|
7343
|
+
for (const take of inListChunks(files.length, _IndexStore.MAX_SQL_VARS)) {
|
|
7344
|
+
const bucket = padToInBucket(files.slice(cursor, cursor + take));
|
|
7345
|
+
cursor += take;
|
|
7346
|
+
const ph = placeholders(bucket.length);
|
|
7347
|
+
for (const row of this.stmt(`SELECT DISTINCT name FROM symbols WHERE file IN (${ph})`).all(
|
|
7348
|
+
...bucket
|
|
7349
|
+
)) {
|
|
7350
|
+
names.push(row.name);
|
|
7351
|
+
}
|
|
7352
|
+
this.stmt(
|
|
7353
|
+
`UPDATE refs SET to_id = NULL
|
|
7354
|
+
WHERE to_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
7355
|
+
).run(...bucket);
|
|
7356
|
+
}
|
|
6109
7357
|
return new Set(names);
|
|
6110
7358
|
}
|
|
6111
7359
|
resolveRefsForNamesUnsafe(names) {
|
|
@@ -6139,6 +7387,14 @@ var IndexStore = class _IndexStore {
|
|
|
6139
7387
|
if (this.ftsAvailable) {
|
|
6140
7388
|
ftsRows.push({ id, text: buildIndexableText(s.name, s.signature, s.docComment) });
|
|
6141
7389
|
}
|
|
7390
|
+
if (this.vectorsAvailable) {
|
|
7391
|
+
vectorRows.push({
|
|
7392
|
+
id,
|
|
7393
|
+
vector: encodeVector(
|
|
7394
|
+
embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
|
|
7395
|
+
)
|
|
7396
|
+
});
|
|
7397
|
+
}
|
|
6142
7398
|
result.push({ ...s, id });
|
|
6143
7399
|
}
|
|
6144
7400
|
bulkInsertSymbolsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, bulk);
|
|
@@ -6171,15 +7427,15 @@ var IndexStore = class _IndexStore {
|
|
|
6171
7427
|
const affectedNames = this.invalidateIncomingRefsForFiles([file]);
|
|
6172
7428
|
if (this.ftsAvailable) {
|
|
6173
7429
|
this.stmt(
|
|
6174
|
-
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE
|
|
7430
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6175
7431
|
).run(file);
|
|
6176
7432
|
}
|
|
6177
7433
|
if (this.vectorsAvailable) {
|
|
6178
7434
|
this.stmt(
|
|
6179
|
-
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE
|
|
7435
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6180
7436
|
).run(file);
|
|
6181
7437
|
}
|
|
6182
|
-
this.stmt("DELETE FROM symbols WHERE
|
|
7438
|
+
this.stmt("DELETE FROM symbols WHERE file = ?").run(file);
|
|
6183
7439
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
6184
7440
|
this.commitWriteTransaction(ownsTransaction);
|
|
6185
7441
|
} catch (error) {
|
|
@@ -6196,18 +7452,18 @@ var IndexStore = class _IndexStore {
|
|
|
6196
7452
|
const affectedNames = this.invalidateIncomingRefsForFiles([file]);
|
|
6197
7453
|
if (this.ftsAvailable) {
|
|
6198
7454
|
this.stmt(
|
|
6199
|
-
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE
|
|
7455
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6200
7456
|
).run(file);
|
|
6201
7457
|
}
|
|
6202
7458
|
if (this.vectorsAvailable) {
|
|
6203
7459
|
this.stmt(
|
|
6204
|
-
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE
|
|
7460
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6205
7461
|
).run(file);
|
|
6206
7462
|
}
|
|
6207
|
-
this.stmt(
|
|
6208
|
-
|
|
6209
|
-
)
|
|
6210
|
-
this.stmt("DELETE FROM symbols WHERE
|
|
7463
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
|
|
7464
|
+
file
|
|
7465
|
+
);
|
|
7466
|
+
this.stmt("DELETE FROM symbols WHERE file = ?").run(file);
|
|
6211
7467
|
this.stmt("DELETE FROM files WHERE file = ?").run(file);
|
|
6212
7468
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
6213
7469
|
this.commitWriteTransaction(ownsTransaction);
|
|
@@ -6311,6 +7567,10 @@ var IndexStore = class _IndexStore {
|
|
|
6311
7567
|
getStats() {
|
|
6312
7568
|
return getStatsWithStatement((sql) => this.stmt(sql), this.indexDir);
|
|
6313
7569
|
}
|
|
7570
|
+
/** P2.5: minimal summary for search-response piggyback (see writer-admin). */
|
|
7571
|
+
getIndexSummary() {
|
|
7572
|
+
return getIndexSummaryWithStatement((sql) => this.stmt(sql));
|
|
7573
|
+
}
|
|
6314
7574
|
setLastIndexed(ts2) {
|
|
6315
7575
|
this.runWithRetry(() => {
|
|
6316
7576
|
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(
|
|
@@ -6412,18 +7672,18 @@ var IndexStore = class _IndexStore {
|
|
|
6412
7672
|
const affectedNames = this.invalidateIncomingRefsForFiles([meta.file]);
|
|
6413
7673
|
if (this.ftsAvailable) {
|
|
6414
7674
|
this.stmt(
|
|
6415
|
-
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE
|
|
7675
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6416
7676
|
).run(meta.file);
|
|
6417
7677
|
}
|
|
6418
7678
|
if (this.vectorsAvailable) {
|
|
6419
7679
|
this.stmt(
|
|
6420
|
-
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE
|
|
7680
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6421
7681
|
).run(meta.file);
|
|
6422
7682
|
}
|
|
6423
|
-
this.stmt(
|
|
6424
|
-
|
|
6425
|
-
)
|
|
6426
|
-
this.stmt("DELETE FROM symbols WHERE
|
|
7683
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
|
|
7684
|
+
meta.file
|
|
7685
|
+
);
|
|
7686
|
+
this.stmt("DELETE FROM symbols WHERE file = ?").run(meta.file);
|
|
6427
7687
|
this.stmt(
|
|
6428
7688
|
`INSERT INTO files(file, lang, mtime_ms, content_hash, symbol_count, last_indexed)
|
|
6429
7689
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
@@ -6455,6 +7715,27 @@ var IndexStore = class _IndexStore {
|
|
|
6455
7715
|
} catch {
|
|
6456
7716
|
}
|
|
6457
7717
|
}
|
|
7718
|
+
/**
|
|
7719
|
+
* P4.14: best-effort WAL checkpoint for idle-time maintenance.
|
|
7720
|
+
*
|
|
7721
|
+
* `wal_autocheckpoint` is PASSIVE and only attempts work after a COMMIT —
|
|
7722
|
+
* once writes stop, nothing fires again, so the WAL keeps whatever frames
|
|
7723
|
+
* the last burst left. This probes with PASSIVE first (never blocks; busy=1
|
|
7724
|
+
* means readers still hold WAL snapshots) and only issues the TRUNCATE —
|
|
7725
|
+
* which resets index.db-wal to zero bytes — when the checkpointer can
|
|
7726
|
+
* proceed immediately. Callers run this on the daemon's single thread, so
|
|
7727
|
+
* never wait on readers here: busy means "retry at the next idle window".
|
|
7728
|
+
*/
|
|
7729
|
+
checkpointWal() {
|
|
7730
|
+
try {
|
|
7731
|
+
const probe = this.db.prepare("PRAGMA wal_checkpoint(PASSIVE)").get();
|
|
7732
|
+
if (Number(probe?.busy ?? 1) !== 0) return false;
|
|
7733
|
+
const done = this.db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
|
|
7734
|
+
return Number(done?.busy ?? 1) === 0;
|
|
7735
|
+
} catch {
|
|
7736
|
+
return false;
|
|
7737
|
+
}
|
|
7738
|
+
}
|
|
6458
7739
|
compactIfNeeded(options = {}) {
|
|
6459
7740
|
const minBytes = options.minBytes ?? 256 * 1024 * 1024;
|
|
6460
7741
|
const minFreeRatio = options.minFreeRatio ?? 0.35;
|
|
@@ -6540,7 +7821,9 @@ function resolveParallelBatch() {
|
|
|
6540
7821
|
return indexParallelBatchSize(availableParallelism());
|
|
6541
7822
|
}
|
|
6542
7823
|
function shouldUseParserWorkerPool(candidateFileCount, parseBatchCount) {
|
|
6543
|
-
|
|
7824
|
+
const threshold = resolveWorkerPoolThreshold();
|
|
7825
|
+
if (threshold === 0) return false;
|
|
7826
|
+
return !isFrugalPerf() && candidateFileCount >= threshold && parseBatchCount > 1;
|
|
6544
7827
|
}
|
|
6545
7828
|
function yieldEventLoop() {
|
|
6546
7829
|
return new Promise((resolve4) => setImmediate(resolve4));
|
|
@@ -6563,15 +7846,15 @@ var IndexSourceChangedError = class extends Error {
|
|
|
6563
7846
|
name = "IndexSourceChangedError";
|
|
6564
7847
|
};
|
|
6565
7848
|
function isWithinProject(projectRoot, file) {
|
|
6566
|
-
const rel =
|
|
6567
|
-
return rel !== "" && !rel.startsWith(`..${
|
|
7849
|
+
const rel = path14.relative(projectRoot, file);
|
|
7850
|
+
return rel !== "" && !rel.startsWith(`..${path14.sep}`) && rel !== ".." && !path14.isAbsolute(rel);
|
|
6568
7851
|
}
|
|
6569
7852
|
function isMissingPathError(err) {
|
|
6570
7853
|
const code = err?.code;
|
|
6571
7854
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
6572
7855
|
}
|
|
6573
7856
|
function normalizeComparablePath(value) {
|
|
6574
|
-
const resolved =
|
|
7857
|
+
const resolved = path14.resolve(value);
|
|
6575
7858
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
6576
7859
|
}
|
|
6577
7860
|
function gitOutput(projectRoot, args) {
|
|
@@ -6617,24 +7900,24 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
6617
7900
|
const record = statusRecords[i];
|
|
6618
7901
|
if (!record) continue;
|
|
6619
7902
|
const status = record.slice(0, 2);
|
|
6620
|
-
const changedPath =
|
|
7903
|
+
const changedPath = path14.resolve(projectRoot, record.slice(3));
|
|
6621
7904
|
dirty.add(changedPath);
|
|
6622
7905
|
if (status.includes("D")) deleted.add(changedPath);
|
|
6623
7906
|
if (status.includes("R") || status.includes("C")) {
|
|
6624
7907
|
const source = statusRecords[++i];
|
|
6625
|
-
if (source) dirty.add(
|
|
7908
|
+
if (source) dirty.add(path14.resolve(projectRoot, source));
|
|
6626
7909
|
}
|
|
6627
7910
|
}
|
|
6628
7911
|
const files = [];
|
|
6629
7912
|
for (const relative3 of output.toString("utf8").split("\0")) {
|
|
6630
7913
|
if (!relative3) continue;
|
|
6631
7914
|
const portable = relative3.replace(/\\/g, "/");
|
|
6632
|
-
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(
|
|
7915
|
+
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path14.posix.basename(portable))) {
|
|
6633
7916
|
continue;
|
|
6634
7917
|
}
|
|
6635
|
-
const full =
|
|
7918
|
+
const full = path14.resolve(projectRoot, relative3);
|
|
6636
7919
|
if (deleted.has(full)) continue;
|
|
6637
|
-
const ext =
|
|
7920
|
+
const ext = path14.extname(relative3).toLowerCase();
|
|
6638
7921
|
if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
|
|
6639
7922
|
}
|
|
6640
7923
|
const snapshot = createHash2("sha256").update(stagedOutput).update("\0").update(statusOutput);
|
|
@@ -6642,7 +7925,7 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
6642
7925
|
for (const dirtyFile of [...dirty].sort()) {
|
|
6643
7926
|
if (!indexedFiles.has(dirtyFile) || deleted.has(dirtyFile)) continue;
|
|
6644
7927
|
snapshot.update("\0").update(dirtyFile).update("\0");
|
|
6645
|
-
snapshot.update(xxhash64String(await
|
|
7928
|
+
snapshot.update(xxhash64String(await fs10.readFile(dirtyFile, "utf8")));
|
|
6646
7929
|
}
|
|
6647
7930
|
return {
|
|
6648
7931
|
files,
|
|
@@ -6678,7 +7961,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
6678
7961
|
}
|
|
6679
7962
|
let entries;
|
|
6680
7963
|
try {
|
|
6681
|
-
entries = await
|
|
7964
|
+
entries = await fs10.readdir(dir, { withFileTypes: true });
|
|
6682
7965
|
} catch (err) {
|
|
6683
7966
|
complete = false;
|
|
6684
7967
|
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -6687,14 +7970,14 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
6687
7970
|
dirCount++;
|
|
6688
7971
|
for (const e of entries) {
|
|
6689
7972
|
if (ignoreSet.has(e.name)) continue;
|
|
6690
|
-
const full =
|
|
6691
|
-
const rel =
|
|
7973
|
+
const full = path14.join(dir, e.name);
|
|
7974
|
+
const rel = path14.relative(projectRoot, full).replace(/\\/g, "/");
|
|
6692
7975
|
if (e.isDirectory()) {
|
|
6693
7976
|
if (isGitIgnored(rel, true)) continue;
|
|
6694
7977
|
await walk(full);
|
|
6695
7978
|
} else if (e.isFile()) {
|
|
6696
7979
|
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
6697
|
-
const ext =
|
|
7980
|
+
const ext = path14.extname(e.name).toLowerCase();
|
|
6698
7981
|
if (indexableExts.has(ext) || detectLang(full) !== null) {
|
|
6699
7982
|
results.push(full);
|
|
6700
7983
|
}
|
|
@@ -6780,10 +8063,10 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6780
8063
|
let trustedUnchanged;
|
|
6781
8064
|
let discoverySnapshotKey;
|
|
6782
8065
|
if (opts.files && opts.files.length > 0) {
|
|
6783
|
-
files = opts.files.map((f) =>
|
|
8066
|
+
files = opts.files.map((f) => path14.resolve(projectRoot, f)).filter((f) => {
|
|
6784
8067
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
6785
|
-
const rel =
|
|
6786
|
-
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(
|
|
8068
|
+
const rel = path14.relative(projectRoot, f).replace(/\\/g, "/");
|
|
8069
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path14.basename(f)) && !isGitIgnored(rel, false);
|
|
6787
8070
|
});
|
|
6788
8071
|
} else {
|
|
6789
8072
|
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
@@ -6816,7 +8099,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6816
8099
|
if (!meta || !trustedUnchanged.has(file)) return true;
|
|
6817
8100
|
langStats[meta.lang] = (langStats[meta.lang] ?? 0) + meta.symbolCount;
|
|
6818
8101
|
symbolsIndexed += meta.symbolCount;
|
|
6819
|
-
filesIndexed++;
|
|
6820
8102
|
filesSkipped++;
|
|
6821
8103
|
filesPreSkipped++;
|
|
6822
8104
|
return false;
|
|
@@ -6845,7 +8127,7 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6845
8127
|
async (file) => {
|
|
6846
8128
|
let stat3;
|
|
6847
8129
|
try {
|
|
6848
|
-
stat3 = await
|
|
8130
|
+
stat3 = await fs10.stat(file, statOpts);
|
|
6849
8131
|
} catch (e) {
|
|
6850
8132
|
if (isAbortError(e)) throw e;
|
|
6851
8133
|
return {
|
|
@@ -6872,7 +8154,7 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6872
8154
|
const meta = existingMeta.get(file);
|
|
6873
8155
|
let content;
|
|
6874
8156
|
try {
|
|
6875
|
-
content = await
|
|
8157
|
+
content = await fs10.readFile(file, { encoding: "utf8", signal });
|
|
6876
8158
|
} catch (e) {
|
|
6877
8159
|
if (isAbortError(e)) throw e;
|
|
6878
8160
|
return {
|
|
@@ -6937,22 +8219,19 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6937
8219
|
}
|
|
6938
8220
|
}
|
|
6939
8221
|
if (!pool) {
|
|
6940
|
-
await
|
|
6941
|
-
toParse.map(
|
|
6942
|
-
try {
|
|
6943
|
-
const parsed = await parseFileContent(item.file, item.content, item.lang);
|
|
6944
|
-
const settled = statReadParse[item.index];
|
|
6945
|
-
if (settled.status === "fulfilled") {
|
|
6946
|
-
settled.value.parsed = parsed;
|
|
6947
|
-
}
|
|
6948
|
-
} catch (e) {
|
|
6949
|
-
const settled = statReadParse[item.index];
|
|
6950
|
-
if (settled.status === "fulfilled") {
|
|
6951
|
-
settled.value.error = `parse error: ${e instanceof Error ? e.message : String(e)}`;
|
|
6952
|
-
}
|
|
6953
|
-
}
|
|
6954
|
-
})
|
|
8222
|
+
const parsedAll = await parseFilesContent(
|
|
8223
|
+
toParse.map((p) => ({ file: p.file, content: p.content, lang: p.lang }))
|
|
6955
8224
|
);
|
|
8225
|
+
for (let pi2 = 0; pi2 < parsedAll.length && pi2 < toParse.length; pi2++) {
|
|
8226
|
+
const settled = statReadParse[toParse[pi2].index];
|
|
8227
|
+
if (settled.status !== "fulfilled") continue;
|
|
8228
|
+
const slot = parsedAll[pi2];
|
|
8229
|
+
if (slot.result) {
|
|
8230
|
+
settled.value.parsed = slot.result;
|
|
8231
|
+
} else {
|
|
8232
|
+
settled.value.error = `parse error: ${slot.error ?? `no result for ${toParse[pi2].file}`}`;
|
|
8233
|
+
}
|
|
8234
|
+
}
|
|
6956
8235
|
}
|
|
6957
8236
|
}
|
|
6958
8237
|
const batchEntries = [];
|
|
@@ -6978,7 +8257,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6978
8257
|
if (result.skippedMeta) {
|
|
6979
8258
|
langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
|
|
6980
8259
|
symbolsIndexed += result.skippedMeta.symbolCount;
|
|
6981
|
-
filesIndexed++;
|
|
6982
8260
|
filesSkipped++;
|
|
6983
8261
|
const stored = existingMeta.get(file);
|
|
6984
8262
|
if (stored && stored.mtimeMs !== result.skippedMeta.mtimeMs) {
|
|
@@ -7003,7 +8281,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7003
8281
|
lastIndexed: Date.now(),
|
|
7004
8282
|
contentHash: result.contentHash ?? ""
|
|
7005
8283
|
});
|
|
7006
|
-
filesIndexed++;
|
|
7007
8284
|
filesEmpty++;
|
|
7008
8285
|
}
|
|
7009
8286
|
continue;
|
|
@@ -7017,7 +8294,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7017
8294
|
lastIndexed: Date.now(),
|
|
7018
8295
|
contentHash: result.contentHash ?? ""
|
|
7019
8296
|
});
|
|
7020
|
-
filesIndexed++;
|
|
7021
8297
|
filesEmpty++;
|
|
7022
8298
|
continue;
|
|
7023
8299
|
}
|
|
@@ -7147,7 +8423,7 @@ async function indexService(args, hooks = {}) {
|
|
|
7147
8423
|
function searchService(args) {
|
|
7148
8424
|
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
7149
8425
|
try {
|
|
7150
|
-
|
|
8426
|
+
const result = store.searchRanked(
|
|
7151
8427
|
args.query,
|
|
7152
8428
|
{
|
|
7153
8429
|
kind: args.kind,
|
|
@@ -7157,6 +8433,10 @@ function searchService(args) {
|
|
|
7157
8433
|
},
|
|
7158
8434
|
args.limit
|
|
7159
8435
|
);
|
|
8436
|
+
if (result.total === 0) {
|
|
8437
|
+
return { ...result, indexSummary: store.getIndexSummary() };
|
|
8438
|
+
}
|
|
8439
|
+
return result;
|
|
7160
8440
|
} finally {
|
|
7161
8441
|
indexStorePool.release(store);
|
|
7162
8442
|
}
|
|
@@ -7217,34 +8497,71 @@ function outgoingCallsService(args) {
|
|
|
7217
8497
|
}
|
|
7218
8498
|
|
|
7219
8499
|
// src/codebase-index/project-server-client.ts
|
|
7220
|
-
import { spawn as
|
|
7221
|
-
import * as
|
|
8500
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
8501
|
+
import * as fs13 from "node:fs";
|
|
7222
8502
|
import * as net from "node:net";
|
|
7223
|
-
import { StringDecoder } from "node:string_decoder";
|
|
7224
8503
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
7225
8504
|
|
|
7226
8505
|
// src/codebase-index/binary-frame.ts
|
|
7227
8506
|
import { decode, encode } from "@msgpack/msgpack";
|
|
7228
8507
|
var BINARY_FRAME_MAGIC = 87;
|
|
8508
|
+
var MAX_BINARY_FRAME_BYTES = 256 * 1024 * 1024;
|
|
8509
|
+
var MAX_INBOUND_BINARY_FRAME_BYTES = 64 * 1024 * 1024;
|
|
7229
8510
|
function isBinaryFrame(firstByte) {
|
|
7230
8511
|
return firstByte === BINARY_FRAME_MAGIC;
|
|
7231
8512
|
}
|
|
7232
8513
|
function encodeBinaryFrame(message) {
|
|
7233
|
-
const payload = encode(message);
|
|
8514
|
+
const payload = encode(normalizeUndefined(message));
|
|
7234
8515
|
const header = Buffer.allocUnsafe(5);
|
|
7235
8516
|
header[0] = BINARY_FRAME_MAGIC;
|
|
7236
8517
|
header.writeUInt32BE(payload.length, 1);
|
|
7237
8518
|
return Buffer.concat([header, payload], 5 + payload.length);
|
|
7238
8519
|
}
|
|
8520
|
+
function normalizeUndefined(value) {
|
|
8521
|
+
if (value instanceof Date) {
|
|
8522
|
+
const time = value.getTime();
|
|
8523
|
+
return Number.isNaN(time) ? null : value.toISOString();
|
|
8524
|
+
}
|
|
8525
|
+
if (value instanceof Map) return normalizeUndefined(Object.fromEntries(value));
|
|
8526
|
+
if (value instanceof Set) return normalizeUndefined([...value]);
|
|
8527
|
+
if (value instanceof Error) {
|
|
8528
|
+
return normalizeUndefined({ name: value.name, message: value.message, stack: value.stack });
|
|
8529
|
+
}
|
|
8530
|
+
if (value instanceof RegExp) return String(value);
|
|
8531
|
+
if (value instanceof URL) return value.toJSON();
|
|
8532
|
+
if (Buffer.isBuffer(value)) return { type: "Buffer", data: [...value] };
|
|
8533
|
+
if (Array.isArray(value)) return value.map((entry) => normalizeUndefined(entry));
|
|
8534
|
+
if (!isPlainObject(value)) return value;
|
|
8535
|
+
const out = {};
|
|
8536
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
8537
|
+
if (entry === void 0) continue;
|
|
8538
|
+
out[key] = normalizeUndefined(entry);
|
|
8539
|
+
}
|
|
8540
|
+
return out;
|
|
8541
|
+
}
|
|
8542
|
+
function isPlainObject(value) {
|
|
8543
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
8544
|
+
const proto = Object.getPrototypeOf(value);
|
|
8545
|
+
return proto === Object.prototype || proto === null;
|
|
8546
|
+
}
|
|
7239
8547
|
function decodeBinaryFrame(payload) {
|
|
7240
8548
|
return decode(payload);
|
|
7241
8549
|
}
|
|
8550
|
+
function encodeJsonFrame(message) {
|
|
8551
|
+
return `${JSON.stringify(normalizeUndefined(message))}
|
|
8552
|
+
`;
|
|
8553
|
+
}
|
|
8554
|
+
|
|
8555
|
+
// src/codebase-index/project-server-client-state.ts
|
|
8556
|
+
import * as fs12 from "node:fs";
|
|
8557
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
8558
|
+
import { checkUnixSocketPath } from "@wrongstack/core/utils";
|
|
7242
8559
|
|
|
7243
8560
|
// src/codebase-index/project-server-endpoint.ts
|
|
7244
8561
|
import { createHash as createHash3 } from "node:crypto";
|
|
7245
|
-
import * as
|
|
7246
|
-
import * as
|
|
7247
|
-
import * as
|
|
8562
|
+
import * as fs11 from "node:fs";
|
|
8563
|
+
import * as os4 from "node:os";
|
|
8564
|
+
import * as path15 from "node:path";
|
|
7248
8565
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7249
8566
|
var PROJECT_INDEX_SERVER_PROTOCOL_VERSION = 1;
|
|
7250
8567
|
var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
|
|
@@ -7253,21 +8570,21 @@ var buildIdCache;
|
|
|
7253
8570
|
function projectIndexServerBuildId(entrypoint) {
|
|
7254
8571
|
const href = entrypoint instanceof URL ? entrypoint.href : entrypoint;
|
|
7255
8572
|
const cleanHref = href.split(/[?#]/, 1)[0] ?? href;
|
|
7256
|
-
const file = cleanHref.startsWith("file:") ? fileURLToPath3(cleanHref) :
|
|
8573
|
+
const file = cleanHref.startsWith("file:") ? fileURLToPath3(cleanHref) : path15.resolve(cleanHref);
|
|
7257
8574
|
try {
|
|
7258
|
-
const stat3 =
|
|
8575
|
+
const stat3 = fs11.statSync(file);
|
|
7259
8576
|
if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat3.mtimeMs && buildIdCache.size === stat3.size) {
|
|
7260
8577
|
return buildIdCache.buildId;
|
|
7261
8578
|
}
|
|
7262
|
-
const buildId = createHash3("sha256").update(
|
|
8579
|
+
const buildId = createHash3("sha256").update(fs11.readFileSync(file)).digest("hex").slice(0, 24);
|
|
7263
8580
|
buildIdCache = { file, mtimeMs: stat3.mtimeMs, size: stat3.size, buildId };
|
|
7264
8581
|
return buildId;
|
|
7265
8582
|
} catch {
|
|
7266
|
-
return `unreadable:${
|
|
8583
|
+
return `unreadable:${path15.basename(file)}`;
|
|
7267
8584
|
}
|
|
7268
8585
|
}
|
|
7269
8586
|
function normalizeLocalPath(value) {
|
|
7270
|
-
const resolved =
|
|
8587
|
+
const resolved = path15.resolve(value);
|
|
7271
8588
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
7272
8589
|
}
|
|
7273
8590
|
function projectIndexServerKey(projectRoot, indexDir) {
|
|
@@ -7279,26 +8596,16 @@ function projectIndexServerEndpoint(projectRoot, indexDir) {
|
|
|
7279
8596
|
if (process.platform === "win32") {
|
|
7280
8597
|
return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
|
|
7281
8598
|
}
|
|
7282
|
-
return
|
|
8599
|
+
return path15.join(os4.tmpdir(), PROJECT_INDEX_SERVER_SOCKET_DIR, `${key}.sock`);
|
|
7283
8600
|
}
|
|
7284
8601
|
function projectIndexServerMetadataPath(projectRoot, indexDir) {
|
|
7285
|
-
return
|
|
7286
|
-
|
|
8602
|
+
return path15.join(
|
|
8603
|
+
path15.resolve(resolveIndexDir(projectRoot, indexDir)),
|
|
7287
8604
|
PROJECT_INDEX_SERVER_METADATA_FILE
|
|
7288
8605
|
);
|
|
7289
8606
|
}
|
|
7290
8607
|
|
|
7291
|
-
// src/codebase-index/project-server-protocol.ts
|
|
7292
|
-
var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
|
|
7293
|
-
function encodeProjectServerMessage(message) {
|
|
7294
|
-
return `${JSON.stringify(message)}
|
|
7295
|
-
`;
|
|
7296
|
-
}
|
|
7297
|
-
|
|
7298
8608
|
// src/codebase-index/project-server-client-state.ts
|
|
7299
|
-
import * as fs11 from "node:fs";
|
|
7300
|
-
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
7301
|
-
import { checkUnixSocketPath } from "@wrongstack/core/utils";
|
|
7302
8609
|
var CONNECT_ATTEMPT_TIMEOUT_MS = 750;
|
|
7303
8610
|
var SERVER_START_TIMEOUT_MS = 1e4;
|
|
7304
8611
|
var SERVER_CONTROL_TIMEOUT_MS = 5e3;
|
|
@@ -7329,7 +8636,7 @@ function resolveProjectIndexDaemonAvailability(projectRoot, indexDir) {
|
|
|
7329
8636
|
for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
|
|
7330
8637
|
try {
|
|
7331
8638
|
const url = new URL(rel, import.meta.url);
|
|
7332
|
-
if (url.protocol === "file:" &&
|
|
8639
|
+
if (url.protocol === "file:" && fs12.existsSync(fileURLToPath4(url))) {
|
|
7333
8640
|
builtUrl = url;
|
|
7334
8641
|
break;
|
|
7335
8642
|
}
|
|
@@ -7417,6 +8724,12 @@ function cancellationError(signal) {
|
|
|
7417
8724
|
return signal.reason instanceof Error ? signal.reason : new Error("Indexing cancelled");
|
|
7418
8725
|
}
|
|
7419
8726
|
|
|
8727
|
+
// src/codebase-index/project-server-protocol.ts
|
|
8728
|
+
var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
|
|
8729
|
+
function encodeProjectServerMessage(message) {
|
|
8730
|
+
return encodeJsonFrame(message);
|
|
8731
|
+
}
|
|
8732
|
+
|
|
7420
8733
|
// src/codebase-index/project-server-client.ts
|
|
7421
8734
|
var ProjectServerConnection = class {
|
|
7422
8735
|
constructor(projectRoot, indexDir, endpoint) {
|
|
@@ -7429,11 +8742,13 @@ var ProjectServerConnection = class {
|
|
|
7429
8742
|
indexDir;
|
|
7430
8743
|
endpoint;
|
|
7431
8744
|
socket = null;
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
8745
|
+
/**
|
|
8746
|
+
* Raw inbound bytes for the unified per-frame reader. Frames are sniffed
|
|
8747
|
+
* individually — JSON text (newline-terminated) or binary (magic 0x57) —
|
|
8748
|
+
* instead of latching a read mode, so a JSON broadcast between binary
|
|
8749
|
+
* frames cannot desynchronize the reader.
|
|
8750
|
+
*/
|
|
8751
|
+
readBuffer = Buffer.alloc(0);
|
|
7437
8752
|
/** P6: true once the server advertises binary support and client accepts. */
|
|
7438
8753
|
useBinary = false;
|
|
7439
8754
|
info = null;
|
|
@@ -7575,7 +8890,7 @@ var ProjectServerConnection = class {
|
|
|
7575
8890
|
this.activity = null;
|
|
7576
8891
|
this.health = null;
|
|
7577
8892
|
this.useBinary = false;
|
|
7578
|
-
this.
|
|
8893
|
+
this.readBuffer = Buffer.alloc(0);
|
|
7579
8894
|
this.connectReject?.(new Error("codebase-index client disconnected"));
|
|
7580
8895
|
this.connectResolve = null;
|
|
7581
8896
|
this.connectReject = null;
|
|
@@ -7596,7 +8911,7 @@ var ProjectServerConnection = class {
|
|
|
7596
8911
|
currentAuthToken() {
|
|
7597
8912
|
if (this.authToken === void 0) {
|
|
7598
8913
|
try {
|
|
7599
|
-
const raw =
|
|
8914
|
+
const raw = fs13.readFileSync(
|
|
7600
8915
|
projectIndexServerMetadataPath(this.projectRoot, this.indexDir),
|
|
7601
8916
|
"utf8"
|
|
7602
8917
|
);
|
|
@@ -7703,10 +9018,8 @@ var ProjectServerConnection = class {
|
|
|
7703
9018
|
this.info = null;
|
|
7704
9019
|
this.activity = null;
|
|
7705
9020
|
this.health = null;
|
|
7706
|
-
this.
|
|
7707
|
-
this.binaryBuffer = [];
|
|
9021
|
+
this.readBuffer = Buffer.alloc(0);
|
|
7708
9022
|
this.useBinary = false;
|
|
7709
|
-
this.textDecoder = null;
|
|
7710
9023
|
return new Promise((resolve4, reject) => {
|
|
7711
9024
|
const socket = net.createConnection(this.endpoint);
|
|
7712
9025
|
this.socket = socket;
|
|
@@ -7736,18 +9049,47 @@ var ProjectServerConnection = class {
|
|
|
7736
9049
|
socket.on("close", () => this.onClose(socket));
|
|
7737
9050
|
});
|
|
7738
9051
|
}
|
|
9052
|
+
/**
|
|
9053
|
+
* Unified per-frame reader. Each frame is sniffed by its first byte:
|
|
9054
|
+
* `0x57` ('W') → length-prefixed MessagePack binary, anything else →
|
|
9055
|
+
* newline-delimited JSON text. Sniffing per frame (instead of latching a
|
|
9056
|
+
* mode) is what makes mixed streams work: the server may interleave a JSON
|
|
9057
|
+
* `index-state` broadcast between binary responses, and an old JSON-only
|
|
9058
|
+
* server stays readable while `useBinary` is armed.
|
|
9059
|
+
*
|
|
9060
|
+
* Multibyte UTF-8 in JSON frames is safe: raw `0x0a` only occurs as the
|
|
9061
|
+
* JSON delimiter (inside JSON strings `\n` is escaped), so a complete line
|
|
9062
|
+
* is always complete UTF-8.
|
|
9063
|
+
*/
|
|
7739
9064
|
onData(socket, chunk) {
|
|
7740
9065
|
if (socket !== this.socket) return;
|
|
7741
|
-
|
|
7742
|
-
this.onBinaryData(socket, chunk);
|
|
7743
|
-
return;
|
|
7744
|
-
}
|
|
7745
|
-
if (!this.textDecoder) this.textDecoder = new StringDecoder("utf8");
|
|
7746
|
-
this.buffer += this.textDecoder.write(chunk);
|
|
9066
|
+
this.readBuffer = this.readBuffer.length === 0 ? chunk : Buffer.concat([this.readBuffer, chunk]);
|
|
7747
9067
|
while (true) {
|
|
7748
|
-
|
|
9068
|
+
if (this.readBuffer.length === 0) return;
|
|
9069
|
+
if (this.useBinary && isBinaryFrame(this.readBuffer[0])) {
|
|
9070
|
+
if (this.readBuffer.length < 5) return;
|
|
9071
|
+
const frameLen = this.readBuffer.readUInt32BE(1);
|
|
9072
|
+
if (frameLen > MAX_BINARY_FRAME_BYTES) {
|
|
9073
|
+
socket.destroy();
|
|
9074
|
+
this.transition("offline", { error: "binary frame length exceeds the IPC limit" });
|
|
9075
|
+
return;
|
|
9076
|
+
}
|
|
9077
|
+
if (this.readBuffer.length < 5 + frameLen) return;
|
|
9078
|
+
const payload = this.readBuffer.subarray(5, 5 + frameLen);
|
|
9079
|
+
this.readBuffer = this.readBuffer.subarray(5 + frameLen);
|
|
9080
|
+
let message2;
|
|
9081
|
+
try {
|
|
9082
|
+
message2 = decodeBinaryFrame(payload);
|
|
9083
|
+
} catch {
|
|
9084
|
+
socket.destroy(new Error("invalid binary codebase-index server response"));
|
|
9085
|
+
return;
|
|
9086
|
+
}
|
|
9087
|
+
this.onMessage(message2);
|
|
9088
|
+
continue;
|
|
9089
|
+
}
|
|
9090
|
+
const newline = this.readBuffer.indexOf(10);
|
|
7749
9091
|
if (newline < 0) {
|
|
7750
|
-
if (this.
|
|
9092
|
+
if (this.readBuffer.length > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
|
|
7751
9093
|
socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
|
|
7752
9094
|
}
|
|
7753
9095
|
return;
|
|
@@ -7756,8 +9098,8 @@ var ProjectServerConnection = class {
|
|
|
7756
9098
|
socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
|
|
7757
9099
|
return;
|
|
7758
9100
|
}
|
|
7759
|
-
const line = this.
|
|
7760
|
-
this.
|
|
9101
|
+
const line = this.readBuffer.subarray(0, newline).toString("utf8");
|
|
9102
|
+
this.readBuffer = this.readBuffer.subarray(newline + 1);
|
|
7761
9103
|
if (!line) continue;
|
|
7762
9104
|
let message;
|
|
7763
9105
|
try {
|
|
@@ -7769,44 +9111,6 @@ var ProjectServerConnection = class {
|
|
|
7769
9111
|
this.onMessage(message);
|
|
7770
9112
|
}
|
|
7771
9113
|
}
|
|
7772
|
-
/**
|
|
7773
|
-
* P6: Parse binary frames from the raw buffer.
|
|
7774
|
-
*
|
|
7775
|
-
* Each frame is: [1 byte magic 0x57] [4 bytes uint32 BE length] [payload].
|
|
7776
|
-
* The magic byte distinguishes binary from JSON — a JSON frame's first byte
|
|
7777
|
-
* is `{` (0x7B), so there is no ambiguity even in a mixed-mode buffer.
|
|
7778
|
-
*/
|
|
7779
|
-
onBinaryData(socket, chunk) {
|
|
7780
|
-
this.binaryBuffer.push(chunk);
|
|
7781
|
-
const all = Buffer.concat(this.binaryBuffer);
|
|
7782
|
-
let offset = 0;
|
|
7783
|
-
while (offset + 5 <= all.length) {
|
|
7784
|
-
if (!isBinaryFrame(all[offset])) {
|
|
7785
|
-
this.useBinary = false;
|
|
7786
|
-
this.buffer += all.subarray(offset).toString("utf8");
|
|
7787
|
-
this.binaryBuffer = [];
|
|
7788
|
-
return;
|
|
7789
|
-
}
|
|
7790
|
-
const frameLen = all.readUInt32BE(offset + 1);
|
|
7791
|
-
if (frameLen > 256 * 1024 * 1024) {
|
|
7792
|
-
socket.destroy();
|
|
7793
|
-
this.transition("offline", { error: "binary frame length exceeds 256MB limit" });
|
|
7794
|
-
return;
|
|
7795
|
-
}
|
|
7796
|
-
const totalLen = 5 + frameLen;
|
|
7797
|
-
if (offset + totalLen > all.length) break;
|
|
7798
|
-
const payload = all.subarray(offset + 5, offset + 5 + frameLen);
|
|
7799
|
-
try {
|
|
7800
|
-
const message = decodeBinaryFrame(payload);
|
|
7801
|
-
this.onMessage(message);
|
|
7802
|
-
} catch {
|
|
7803
|
-
socket.destroy(new Error("invalid binary codebase-index server response"));
|
|
7804
|
-
return;
|
|
7805
|
-
}
|
|
7806
|
-
offset += totalLen;
|
|
7807
|
-
}
|
|
7808
|
-
this.binaryBuffer = offset < all.length ? [all.subarray(offset)] : [];
|
|
7809
|
-
}
|
|
7810
9114
|
onMessage(message) {
|
|
7811
9115
|
if (message.type === "hello") {
|
|
7812
9116
|
if (message.protocolVersion !== PROJECT_INDEX_SERVER_PROTOCOL_VERSION) {
|
|
@@ -7826,7 +9130,7 @@ var ProjectServerConnection = class {
|
|
|
7826
9130
|
}
|
|
7827
9131
|
this.info = message;
|
|
7828
9132
|
this.markResponsive();
|
|
7829
|
-
if (message.binarySupported) this.useBinary = true;
|
|
9133
|
+
if (message.binarySupported && binaryFramingEnabled()) this.useBinary = true;
|
|
7830
9134
|
this.transition("connected", { pid: message.pid });
|
|
7831
9135
|
ensureHeartbeatLoop();
|
|
7832
9136
|
this.connectResolve?.();
|
|
@@ -7912,13 +9216,13 @@ var ProjectServerConnection = class {
|
|
|
7912
9216
|
if (!url) throw new Error("built codebase-index project server is unavailable");
|
|
7913
9217
|
if (process.platform !== "win32") {
|
|
7914
9218
|
try {
|
|
7915
|
-
|
|
9219
|
+
fs13.rmSync(this.endpoint, { force: true });
|
|
7916
9220
|
} catch {
|
|
7917
9221
|
}
|
|
7918
9222
|
}
|
|
7919
9223
|
const args = [fileURLToPath5(url), "--project-root", this.projectRoot];
|
|
7920
9224
|
if (this.indexDir) args.push("--index-dir", this.indexDir);
|
|
7921
|
-
const child =
|
|
9225
|
+
const child = spawn4(process.execPath, args, {
|
|
7922
9226
|
detached: true,
|
|
7923
9227
|
stdio: "ignore",
|
|
7924
9228
|
windowsHide: true,
|
|
@@ -7936,8 +9240,8 @@ var ProjectServerConnection = class {
|
|
|
7936
9240
|
process.kill(pid);
|
|
7937
9241
|
const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
|
|
7938
9242
|
try {
|
|
7939
|
-
const metadata = JSON.parse(
|
|
7940
|
-
if (metadata.pid === pid)
|
|
9243
|
+
const metadata = JSON.parse(fs13.readFileSync(metadataPath, "utf8"));
|
|
9244
|
+
if (metadata.pid === pid) fs13.rmSync(metadataPath, { force: true });
|
|
7941
9245
|
} catch {
|
|
7942
9246
|
}
|
|
7943
9247
|
return true;
|
|
@@ -7949,6 +9253,10 @@ var ProjectServerConnection = class {
|
|
|
7949
9253
|
var connections = /* @__PURE__ */ new Map();
|
|
7950
9254
|
var MAX_CACHED_CONNECTIONS = 8;
|
|
7951
9255
|
var heartbeatTimer;
|
|
9256
|
+
function binaryFramingEnabled() {
|
|
9257
|
+
const flag = process.env["WRONGSTACK_INDEX_BINARY"];
|
|
9258
|
+
return flag === "1" || flag === "true";
|
|
9259
|
+
}
|
|
7952
9260
|
function forgetConnection(endpoint, connection) {
|
|
7953
9261
|
if (connections.get(endpoint) === connection) connections.delete(endpoint);
|
|
7954
9262
|
connection.close();
|
|
@@ -8038,7 +9346,7 @@ function resolveWorkerUrl() {
|
|
|
8038
9346
|
for (const rel of ["./worker.js", "./codebase-index/worker.js"]) {
|
|
8039
9347
|
try {
|
|
8040
9348
|
const url = new URL(rel, import.meta.url);
|
|
8041
|
-
if (url.protocol === "file:" &&
|
|
9349
|
+
if (url.protocol === "file:" && fs14.existsSync(fileURLToPath6(url))) return url;
|
|
8042
9350
|
} catch {
|
|
8043
9351
|
}
|
|
8044
9352
|
}
|
|
@@ -8291,7 +9599,7 @@ var readTool = {
|
|
|
8291
9599
|
const shouldIncludeSymbols = input.includeSymbols === true || input.includeSymbols !== false && ctx.meta[ADVANCED_MODE_META_KEY] === true;
|
|
8292
9600
|
let stat3;
|
|
8293
9601
|
try {
|
|
8294
|
-
stat3 = await
|
|
9602
|
+
stat3 = await fs15.stat(absPath);
|
|
8295
9603
|
} catch (err) {
|
|
8296
9604
|
const code = err.code;
|
|
8297
9605
|
if (code === "ENOENT") {
|
|
@@ -8343,7 +9651,7 @@ var readTool = {
|
|
|
8343
9651
|
...symResult2?.symbols ? { symbols: symResult2.symbols } : {}
|
|
8344
9652
|
};
|
|
8345
9653
|
}
|
|
8346
|
-
const buf = await
|
|
9654
|
+
const buf = await fs15.readFile(absPath);
|
|
8347
9655
|
if (isBinaryBuffer(buf)) {
|
|
8348
9656
|
throw new FsError({
|
|
8349
9657
|
message: `read: "${input.path}" appears to be binary`,
|