@wrongstack/tools 0.309.1 → 0.310.1
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.d.ts +17 -14
- package/dist/builtin.js +3028 -1675
- 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 +3028 -1738
- package/dist/json.js +5 -124
- package/dist/kanban.js +130 -0
- package/dist/logs.js +5 -121
- package/dist/pack.js +3028 -1675
- 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 +3040 -1679
- 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/edit.js
CHANGED
|
@@ -173,238 +173,133 @@ var init_languages = __esm({
|
|
|
173
173
|
}
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
-
// src/codebase-index/
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
tsLoad2 ??= import("@typescript/typescript6").then((m) => {
|
|
184
|
-
ts = m.default ?? m;
|
|
185
|
-
return ts;
|
|
186
|
-
});
|
|
187
|
-
return tsLoad2;
|
|
188
|
-
}
|
|
189
|
-
function kindMap() {
|
|
190
|
-
kindMapCache ??= {
|
|
191
|
-
[ts.SyntaxKind.ClassDeclaration]: "class",
|
|
192
|
-
[ts.SyntaxKind.InterfaceDeclaration]: "interface",
|
|
193
|
-
[ts.SyntaxKind.EnumDeclaration]: "enum",
|
|
194
|
-
[ts.SyntaxKind.TypeAliasDeclaration]: "type",
|
|
195
|
-
[ts.SyntaxKind.FunctionDeclaration]: "function",
|
|
196
|
-
[ts.SyntaxKind.MethodDeclaration]: "method",
|
|
197
|
-
[ts.SyntaxKind.GetAccessor]: "property",
|
|
198
|
-
[ts.SyntaxKind.SetAccessor]: "property",
|
|
199
|
-
[ts.SyntaxKind.PropertyDeclaration]: "property",
|
|
200
|
-
[ts.SyntaxKind.Parameter]: "parameter",
|
|
201
|
-
[ts.SyntaxKind.NamespaceExportDeclaration]: "namespace"
|
|
202
|
-
};
|
|
203
|
-
return kindMapCache;
|
|
204
|
-
}
|
|
205
|
-
function kindOf(node) {
|
|
206
|
-
if (ts.isVariableDeclaration(node)) {
|
|
207
|
-
const parent = node.parent;
|
|
208
|
-
if (ts.isVariableDeclarationList(parent)) {
|
|
209
|
-
const flags = parent.flags;
|
|
210
|
-
if (flags & ts.NodeFlags.Let) return "let";
|
|
211
|
-
if (flags & ts.NodeFlags.Const) return "const";
|
|
212
|
-
return "var";
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
216
|
-
return kindMap()[node.kind] ?? null;
|
|
217
|
-
}
|
|
218
|
-
function getSignature(printer, node, sourceFile) {
|
|
219
|
-
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
220
|
-
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
221
|
-
}
|
|
222
|
-
function getJsDoc(node, sourceFile) {
|
|
223
|
-
const fullText = sourceFile.getFullText();
|
|
224
|
-
const nodePos = node.getFullStart();
|
|
225
|
-
const comments = ts.getLeadingCommentRanges(fullText, nodePos);
|
|
226
|
-
if (!comments) return "";
|
|
227
|
-
for (const range of comments) {
|
|
228
|
-
const commentText = fullText.slice(range.pos, range.end);
|
|
229
|
-
const trimmed = commentText.trim();
|
|
230
|
-
if (trimmed.startsWith("/**") && trimmed.endsWith("*/")) {
|
|
231
|
-
const inner = trimmed.slice(3, -2).replace(/^[ \t]*\*[ ]?/gm, "").trim();
|
|
232
|
-
return inner.split("\n")[0]?.trim().slice(0, 200) ?? "";
|
|
233
|
-
}
|
|
176
|
+
// src/codebase-index/import-extractor.ts
|
|
177
|
+
function lastSegment(specifier) {
|
|
178
|
+
const pathLike = /[/\\]|::/.test(specifier);
|
|
179
|
+
const segments = specifier.split(/[/\\]|::/).filter(Boolean);
|
|
180
|
+
let last = segments[segments.length - 1] ?? specifier;
|
|
181
|
+
if (last === "*" || last === "_") {
|
|
182
|
+
last = segments[segments.length - 2] ?? specifier;
|
|
234
183
|
}
|
|
235
|
-
return "";
|
|
184
|
+
if (pathLike) return last.replace(/\.[A-Za-z0-9]{1,8}$/, "") || last;
|
|
185
|
+
const dotted = last.split(".").filter((part) => part && part !== "*" && part !== "_");
|
|
186
|
+
return dotted[dotted.length - 1] ?? last;
|
|
236
187
|
}
|
|
237
|
-
function
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (node.name && ts.isIdentifier(node.name)) {
|
|
242
|
-
parts.push(node.name.text);
|
|
243
|
-
}
|
|
188
|
+
function newlineOffsets(content) {
|
|
189
|
+
const offsets = [];
|
|
190
|
+
for (let i = 0; i < content.length; i++) {
|
|
191
|
+
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
244
192
|
}
|
|
193
|
+
return offsets;
|
|
245
194
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
254
|
-
}
|
|
255
|
-
const symbols = [];
|
|
256
|
-
const refs = [];
|
|
257
|
-
const printer = ts.createPrinter({});
|
|
258
|
-
function visit(node, funcDepth, scopeParts) {
|
|
259
|
-
const kind = kindOf(node);
|
|
260
|
-
if (kind) {
|
|
261
|
-
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && funcDepth > 0) {
|
|
262
|
-
} else {
|
|
263
|
-
const nameNode = node.name;
|
|
264
|
-
if (!nameNode || !ts.isIdentifier(nameNode)) {
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
const name = nameNode.text;
|
|
268
|
-
const pos2 = nameNode.getStart(sourceFile);
|
|
269
|
-
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(pos2);
|
|
270
|
-
const scope = scopeParts.join(".");
|
|
271
|
-
const signature = getSignature(printer, node, sourceFile);
|
|
272
|
-
const docComment = getJsDoc(node, sourceFile);
|
|
273
|
-
const text = [name, signature, docComment].filter(Boolean).join(" | ");
|
|
274
|
-
symbols.push({
|
|
275
|
-
id: 0,
|
|
276
|
-
lang,
|
|
277
|
-
kind,
|
|
278
|
-
name,
|
|
279
|
-
file,
|
|
280
|
-
line: line2 + 1,
|
|
281
|
-
col: character,
|
|
282
|
-
signature,
|
|
283
|
-
docComment,
|
|
284
|
-
scope,
|
|
285
|
-
text
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
const pos = node.getStart(sourceFile);
|
|
290
|
-
const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
|
|
291
|
-
const lineNum = line + 1;
|
|
292
|
-
if (ts.isCallExpression(node)) {
|
|
293
|
-
const expr = node.expression;
|
|
294
|
-
if (ts.isIdentifier(expr)) {
|
|
295
|
-
refs.push({ fromId: 0, toName: expr.text, callType: "call", line: lineNum });
|
|
296
|
-
}
|
|
297
|
-
} else if (ts.isPropertyAccessExpression(node)) {
|
|
298
|
-
if (ts.isIdentifier(node.expression)) {
|
|
299
|
-
refs.push({ fromId: 0, toName: node.expression.text, callType: "call", line: lineNum });
|
|
300
|
-
}
|
|
301
|
-
} else if (ts.isTypeReferenceNode(node)) {
|
|
302
|
-
const name = getTypeName(node.typeName);
|
|
303
|
-
if (name) refs.push({ fromId: 0, toName: name, callType: "type_ref", line: lineNum });
|
|
304
|
-
} else if (ts.isHeritageClause(node)) {
|
|
305
|
-
for (const t of node.types) {
|
|
306
|
-
const name = getTypeName(t.expression);
|
|
307
|
-
if (name)
|
|
308
|
-
refs.push({
|
|
309
|
-
fromId: 0,
|
|
310
|
-
toName: name,
|
|
311
|
-
callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement",
|
|
312
|
-
line: lineNum
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
} else if (ts.isImportDeclaration(node)) {
|
|
316
|
-
emitImportSpecifierRefs(node, refs, lineNum);
|
|
317
|
-
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
|
|
318
|
-
emitExportSpecifierRefs(node, refs, lineNum);
|
|
319
|
-
}
|
|
320
|
-
const scopeIdx = scopeParts.length;
|
|
321
|
-
pushScopeName(node, scopeParts);
|
|
322
|
-
const childFuncDepth = ts.isFunctionLike(node) ? funcDepth + 1 : funcDepth;
|
|
323
|
-
ts.forEachChild(node, (child) => visit(child, childFuncDepth, scopeParts));
|
|
324
|
-
scopeParts.length = scopeIdx;
|
|
195
|
+
function lineAt2(offsets, index) {
|
|
196
|
+
let low = 0;
|
|
197
|
+
let high = offsets.length;
|
|
198
|
+
while (low < high) {
|
|
199
|
+
const mid = low + high >>> 1;
|
|
200
|
+
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
201
|
+
else high = mid;
|
|
325
202
|
}
|
|
326
|
-
|
|
327
|
-
return { file, lang, symbols, refs: deduplicateRefs(refs), mtimeMs: Date.now() };
|
|
203
|
+
return low + 1;
|
|
328
204
|
}
|
|
329
|
-
function
|
|
330
|
-
|
|
331
|
-
if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
|
|
332
|
-
return "";
|
|
205
|
+
function hasImportPatterns(lang) {
|
|
206
|
+
return LANG_IMPORTS[lang] !== void 0;
|
|
333
207
|
}
|
|
334
|
-
function
|
|
208
|
+
function extractImports(opts) {
|
|
209
|
+
const patterns = LANG_IMPORTS[opts.lang];
|
|
210
|
+
if (!patterns || !opts.content) return [];
|
|
211
|
+
const limit = opts.maxImports ?? IMPORT_MAX_PER_FILE;
|
|
212
|
+
const content = opts.content.length > IMPORT_MAX_FILE_CHARS ? opts.content.slice(0, IMPORT_MAX_FILE_CHARS) : opts.content;
|
|
213
|
+
const refs = [];
|
|
335
214
|
const seen = /* @__PURE__ */ new Set();
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (!clause) {
|
|
350
|
-
if (module) {
|
|
351
|
-
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
352
|
-
}
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
if (clause.name) {
|
|
356
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
357
|
-
}
|
|
358
|
-
const bindings = clause.namedBindings;
|
|
359
|
-
if (!bindings) return;
|
|
360
|
-
if (ts.isNamedImports(bindings)) {
|
|
361
|
-
for (const element of bindings.elements) {
|
|
215
|
+
const offsets = newlineOffsets(content);
|
|
216
|
+
for (const pattern of patterns) {
|
|
217
|
+
const re = new RegExp(pattern.re.source, pattern.re.flags);
|
|
218
|
+
for (const match of content.matchAll(re)) {
|
|
219
|
+
if (refs.length >= limit) return refs;
|
|
220
|
+
const specifier = match[1]?.trim();
|
|
221
|
+
if (!specifier) continue;
|
|
222
|
+
const module = specifier;
|
|
223
|
+
const toName = pattern.name === "full" ? module : lastSegment(module);
|
|
224
|
+
if (!toName) continue;
|
|
225
|
+
const key = `${module}\0${toName}`;
|
|
226
|
+
if (seen.has(key)) continue;
|
|
227
|
+
seen.add(key);
|
|
362
228
|
refs.push({
|
|
363
229
|
fromId: 0,
|
|
364
|
-
toName
|
|
230
|
+
toName,
|
|
365
231
|
callType: "import",
|
|
366
|
-
line:
|
|
232
|
+
line: lineAt2(offsets, match.index ?? 0),
|
|
233
|
+
lang: opts.lang,
|
|
367
234
|
module
|
|
368
235
|
});
|
|
369
236
|
}
|
|
370
|
-
} else if (ts.isNamespaceImport(bindings)) {
|
|
371
|
-
refs.push({
|
|
372
|
-
fromId: 0,
|
|
373
|
-
toName: bindings.name.text,
|
|
374
|
-
callType: "import",
|
|
375
|
-
line: lineNum,
|
|
376
|
-
module
|
|
377
|
-
});
|
|
378
237
|
}
|
|
238
|
+
return refs;
|
|
379
239
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
function emitExportSpecifierRefs(node, refs, lineNum) {
|
|
384
|
-
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
385
|
-
const clause = node.exportClause;
|
|
386
|
-
if (clause && ts.isNamespaceExport(clause)) {
|
|
387
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
if (clause && ts.isNamedExports(clause)) {
|
|
391
|
-
for (const element of clause.elements) {
|
|
392
|
-
const originalName = element.propertyName?.text ?? element.name.text;
|
|
393
|
-
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum, module });
|
|
394
|
-
}
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
if (module) {
|
|
398
|
-
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
var ts, tsLoad2, kindMapCache;
|
|
402
|
-
var init_ts_parser = __esm({
|
|
403
|
-
"src/codebase-index/ts-parser.ts"() {
|
|
240
|
+
var IMPORT_MAX_FILE_CHARS, IMPORT_MAX_PER_FILE, DOTTED_IMPORT, LANG_IMPORTS;
|
|
241
|
+
var init_import_extractor = __esm({
|
|
242
|
+
"src/codebase-index/import-extractor.ts"() {
|
|
404
243
|
"use strict";
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
244
|
+
IMPORT_MAX_FILE_CHARS = 512 * 1024;
|
|
245
|
+
IMPORT_MAX_PER_FILE = 400;
|
|
246
|
+
DOTTED_IMPORT = [
|
|
247
|
+
{ re: /^[ \t]*import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;?\s*$/gm }
|
|
248
|
+
];
|
|
249
|
+
LANG_IMPORTS = {
|
|
250
|
+
// Go and Python have real AST extractors; these patterns are the fallback for
|
|
251
|
+
// machines with no Go toolchain or Python interpreter installed, where the
|
|
252
|
+
// parser degrades to regex symbols and would otherwise contribute no edges.
|
|
253
|
+
go: [
|
|
254
|
+
{ re: /^[ \t]*import\s+(?:[\w.]+\s+)?"([^"]+)"/gm },
|
|
255
|
+
// Grouped form: inside `import ( … )` each line is an optional alias plus a
|
|
256
|
+
// quoted path. A stray match elsewhere resolves to no file and is dropped.
|
|
257
|
+
{ re: /^[ \t]*(?:[A-Za-z_.]\w*\s+)?"([^"]+)"\s*$/gm }
|
|
258
|
+
],
|
|
259
|
+
py: [{ re: /^[ \t]*import\s+([\w.]+)/gm }, { re: /^[ \t]*from\s+([.\w]+)\s+import\b/gm }],
|
|
260
|
+
rs: [
|
|
261
|
+
// use a::b::C; | use a::b::{C, D}; → the path before any brace
|
|
262
|
+
{ re: /^[ \t]*(?:pub\s+)?use\s+([\w:]+?)(?:::\{|\s*;|\s+as\b)/gm },
|
|
263
|
+
// mod foo; (a declaration *and* a dependency on foo.rs / foo/mod.rs)
|
|
264
|
+
{ re: /^[ \t]*(?:pub\s+)?mod\s+(\w+)\s*;/gm }
|
|
265
|
+
],
|
|
266
|
+
java: DOTTED_IMPORT,
|
|
267
|
+
kotlin: DOTTED_IMPORT,
|
|
268
|
+
scala: [{ re: /^[ \t]*import\s+([\w.]+(?:\.[_*])?)\s*$/gm }],
|
|
269
|
+
csharp: [
|
|
270
|
+
// using Foo.Bar; | using static Foo.Bar; | using Alias = Foo.Bar;
|
|
271
|
+
{ re: /^[ \t]*global\s+using\s+(?:static\s+)?([\w.]+)\s*;/gm, name: "full" },
|
|
272
|
+
{ re: /^[ \t]*using\s+(?:static\s+)?(?:\w+\s*=\s*)?([\w.]+)\s*;/gm, name: "full" }
|
|
273
|
+
],
|
|
274
|
+
// Quoted includes only: <stdio.h> is a system header with no indexed file.
|
|
275
|
+
c: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
276
|
+
cpp: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
277
|
+
ruby: [{ re: /^[ \t]*(?:require|require_relative|load)\s*\(?\s*['"]([^'"]+)['"]/gm }],
|
|
278
|
+
php: [
|
|
279
|
+
// `use A\B\C` imports the class C, which is what the index has a symbol
|
|
280
|
+
// for — the namespace symbol only covers the `A\B` prefix.
|
|
281
|
+
{ re: /^[ \t]*use\s+(?:function\s+|const\s+)?([\w\\]+)/gm },
|
|
282
|
+
{ re: /^[ \t]*(?:require|require_once|include|include_once)\s*\(?\s*['"]([^'"]+)['"]/gm }
|
|
283
|
+
],
|
|
284
|
+
swift: [{ re: /^[ \t]*import\s+(?:struct\s+|class\s+|func\s+)?([\w.]+)\s*$/gm }],
|
|
285
|
+
dart: [{ re: /^[ \t]*(?:import|export|part)\s+['"]([^'"]+)['"]/gm }],
|
|
286
|
+
lua: [{ re: /\brequire\s*\(?\s*['"]([^'"]+)['"]/g }],
|
|
287
|
+
elixir: [{ re: /^[ \t]*(?:alias|import|require|use)\s+([A-Z][\w.]*)/gm, name: "full" }],
|
|
288
|
+
haskell: [{ re: /^[ \t]*import\s+(?:qualified\s+)?([\w.]+)/gm, name: "full" }],
|
|
289
|
+
zig: [{ re: /@import\s*\(\s*"([^"]+)"\s*\)/g }],
|
|
290
|
+
proto: [{ re: /^[ \t]*import\s+(?:public\s+|weak\s+)?"([^"]+)"\s*;/gm }],
|
|
291
|
+
// `@import "x"`, `@use "x"`, `@forward "x"` — Sass and plain CSS alike.
|
|
292
|
+
css: [{ re: /^[ \t]*@(?:import|use|forward)\s+(?:url\()?\s*['"]([^'"]+)['"]/gm }],
|
|
293
|
+
// A .vue/.svelte file's <script> block is JS; the same ESM syntax applies.
|
|
294
|
+
vue: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
295
|
+
svelte: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
296
|
+
html: [
|
|
297
|
+
{ re: /<script[^>]+src\s*=\s*['"]([^'"]+)['"]/g },
|
|
298
|
+
{ re: /<link[^>]+href\s*=\s*['"]([^'"]+)['"]/g }
|
|
299
|
+
],
|
|
300
|
+
shell: [{ re: /^[ \t]*(?:source|\.)\s+(\S+)/gm }],
|
|
301
|
+
r: [{ re: /\b(?:source|library|require)\s*\(\s*['"]?([\w./-]+)['"]?\s*\)/g }]
|
|
302
|
+
};
|
|
408
303
|
}
|
|
409
304
|
});
|
|
410
305
|
|
|
@@ -490,6 +385,32 @@ function parseParserOutput(stdout, lang) {
|
|
|
490
385
|
refs: dedupeRefs(coerceRefs(record.refs, lang))
|
|
491
386
|
};
|
|
492
387
|
}
|
|
388
|
+
function parseParserBatchOutput(stdout, lang) {
|
|
389
|
+
const trimmed = stdout.trim();
|
|
390
|
+
if (!trimmed) return [];
|
|
391
|
+
let parsed;
|
|
392
|
+
try {
|
|
393
|
+
parsed = JSON.parse(trimmed);
|
|
394
|
+
} catch {
|
|
395
|
+
return [];
|
|
396
|
+
}
|
|
397
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
398
|
+
const results = parsed.results;
|
|
399
|
+
if (!Array.isArray(results)) return [];
|
|
400
|
+
return results.flatMap((entry) => {
|
|
401
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return [];
|
|
402
|
+
const candidate = entry;
|
|
403
|
+
if (typeof candidate.file !== "string" || !candidate.file) return [];
|
|
404
|
+
return [
|
|
405
|
+
{
|
|
406
|
+
file: candidate.file,
|
|
407
|
+
error: typeof candidate.error === "string" && candidate.error ? candidate.error : void 0,
|
|
408
|
+
symbols: coerceSymbols(candidate.symbols),
|
|
409
|
+
refs: dedupeRefs(coerceRefs(candidate.refs, lang))
|
|
410
|
+
}
|
|
411
|
+
];
|
|
412
|
+
});
|
|
413
|
+
}
|
|
493
414
|
function dedupeRefs(refs) {
|
|
494
415
|
const seen = /* @__PURE__ */ new Set();
|
|
495
416
|
return refs.filter((ref) => {
|
|
@@ -530,187 +451,172 @@ var init_spawn_gate = __esm({
|
|
|
530
451
|
}
|
|
531
452
|
});
|
|
532
453
|
|
|
533
|
-
// src/codebase-index/
|
|
534
|
-
var go_parser_exports = {};
|
|
535
|
-
__export(go_parser_exports, {
|
|
536
|
-
detectLang: () => detectLang,
|
|
537
|
-
parseSymbols: () => parseSymbols2
|
|
538
|
-
});
|
|
454
|
+
// src/codebase-index/parser-batch.ts
|
|
539
455
|
import { spawn } from "node:child_process";
|
|
456
|
+
import * as fsSync from "node:fs";
|
|
457
|
+
import * as fs4 from "node:fs/promises";
|
|
540
458
|
import * as os from "node:os";
|
|
541
459
|
import * as path8 from "node:path";
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
460
|
+
function chunkBatchFiles(files) {
|
|
461
|
+
const chunks = [];
|
|
462
|
+
let current = [];
|
|
463
|
+
let bytes = 0;
|
|
464
|
+
for (const file of files) {
|
|
465
|
+
const size = Buffer.byteLength(file.content, "utf8");
|
|
466
|
+
if (current.length > 0 && (current.length >= MAX_BATCH_FILES || bytes + size > MAX_BATCH_BYTES)) {
|
|
467
|
+
chunks.push(current);
|
|
468
|
+
current = [];
|
|
469
|
+
bytes = 0;
|
|
470
|
+
}
|
|
471
|
+
current.push(file);
|
|
472
|
+
bytes += size;
|
|
473
|
+
}
|
|
474
|
+
if (current.length > 0) chunks.push(current);
|
|
475
|
+
return chunks;
|
|
476
|
+
}
|
|
477
|
+
function batchTimeoutMs(fileCount) {
|
|
478
|
+
return Math.min(12e4, 15e3 + fileCount * 1500);
|
|
479
|
+
}
|
|
480
|
+
async function ensureScriptPath(cached, prefix, fileName, script) {
|
|
481
|
+
if (cached) return { path: cached, wrote: false };
|
|
482
|
+
const dir = await fs4.mkdtemp(path8.join(os.tmpdir(), prefix));
|
|
483
|
+
const scriptPath = path8.join(dir, fileName);
|
|
484
|
+
await fs4.writeFile(scriptPath, script, { encoding: "utf8", flag: "wx" });
|
|
485
|
+
process.once("exit", () => {
|
|
486
|
+
try {
|
|
487
|
+
fsSync.rmSync(dir, { recursive: true, force: true });
|
|
488
|
+
} catch {
|
|
549
489
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
} catch {
|
|
553
|
-
return fallbackParse(file, content, lang);
|
|
554
|
-
}
|
|
490
|
+
});
|
|
491
|
+
return { path: scriptPath, wrote: true };
|
|
555
492
|
}
|
|
556
|
-
function
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const fn = /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(/.exec(trimmed);
|
|
567
|
-
if (fn?.[1]) {
|
|
568
|
-
addFallbackSymbol(symbols, {
|
|
569
|
-
filePath,
|
|
570
|
-
lang,
|
|
571
|
-
kind: trimmed.startsWith("func (") ? "method" : "function",
|
|
572
|
-
name: fn[1],
|
|
573
|
-
line: idx + 1,
|
|
574
|
-
col,
|
|
575
|
-
signature: trimmed,
|
|
576
|
-
scope: packageName ? `${packageName}.${fn[1]}` : fn[1]
|
|
577
|
-
});
|
|
578
|
-
continue;
|
|
579
|
-
}
|
|
580
|
-
const typeDecl = /^type\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
581
|
-
if (typeDecl?.[1]) {
|
|
582
|
-
addFallbackSymbol(symbols, {
|
|
583
|
-
filePath,
|
|
584
|
-
lang,
|
|
585
|
-
kind: "type",
|
|
586
|
-
name: typeDecl[1],
|
|
587
|
-
line: idx + 1,
|
|
588
|
-
col,
|
|
589
|
-
signature: trimmed,
|
|
590
|
-
scope: packageName
|
|
591
|
-
});
|
|
592
|
-
continue;
|
|
593
|
-
}
|
|
594
|
-
const valueDecl = /^(const|var)\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
595
|
-
if (valueDecl?.[1] && valueDecl[2]) {
|
|
596
|
-
addFallbackSymbol(symbols, {
|
|
597
|
-
filePath,
|
|
598
|
-
lang,
|
|
599
|
-
kind: valueDecl[1],
|
|
600
|
-
name: valueDecl[2],
|
|
601
|
-
line: idx + 1,
|
|
602
|
-
col,
|
|
603
|
-
signature: trimmed,
|
|
604
|
-
scope: packageName
|
|
605
|
-
});
|
|
493
|
+
function runToolchainChild(binary, args, stdinPayload, timeoutMs) {
|
|
494
|
+
return new Promise((resolve4) => {
|
|
495
|
+
let settled = false;
|
|
496
|
+
let stdout = "";
|
|
497
|
+
let proc;
|
|
498
|
+
try {
|
|
499
|
+
proc = spawn(binary, args, { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
500
|
+
} catch {
|
|
501
|
+
resolve4(null);
|
|
502
|
+
return;
|
|
606
503
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
504
|
+
const finish = (value) => {
|
|
505
|
+
if (settled) return;
|
|
506
|
+
settled = true;
|
|
507
|
+
clearTimeout(timer);
|
|
508
|
+
resolve4(value);
|
|
509
|
+
};
|
|
510
|
+
const timer = setTimeout(() => {
|
|
511
|
+
proc.kill("SIGKILL");
|
|
512
|
+
finish(null);
|
|
513
|
+
}, timeoutMs);
|
|
514
|
+
timer.unref?.();
|
|
515
|
+
proc.on("error", () => finish(null));
|
|
516
|
+
proc.stdout?.on("data", (chunk) => {
|
|
517
|
+
stdout += chunk.toString();
|
|
518
|
+
});
|
|
519
|
+
proc.stderr?.resume();
|
|
520
|
+
proc.stdin?.on("error", () => {
|
|
521
|
+
});
|
|
522
|
+
proc.stdin?.write(stdinPayload);
|
|
523
|
+
proc.stdin?.end();
|
|
524
|
+
proc.on("close", (code) => finish({ code, stdout }));
|
|
623
525
|
});
|
|
624
526
|
}
|
|
625
|
-
function
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
527
|
+
async function runGoBatch(files, goBinary) {
|
|
528
|
+
const out = /* @__PURE__ */ new Map();
|
|
529
|
+
if (files.length === 0) return out;
|
|
530
|
+
const { path: scriptPath } = await ensureScriptPath(
|
|
531
|
+
_goBatchScriptPath,
|
|
532
|
+
"ws-go-parse",
|
|
533
|
+
"batch.go",
|
|
534
|
+
GO_BATCH_SCRIPT
|
|
535
|
+
);
|
|
536
|
+
_goBatchScriptPath = scriptPath;
|
|
537
|
+
const payload = JSON.stringify(files.map((f) => ({ file: f.file, content: f.content })));
|
|
538
|
+
const result = await withSpawnGate(
|
|
539
|
+
() => runToolchainChild(
|
|
540
|
+
goBinary ?? resolveWin32Command("go"),
|
|
541
|
+
["run", scriptPath],
|
|
542
|
+
payload,
|
|
543
|
+
batchTimeoutMs(files.length)
|
|
544
|
+
)
|
|
545
|
+
);
|
|
546
|
+
if (result?.code !== 0 || !result.stdout.trim()) return out;
|
|
547
|
+
for (const entry of parseParserBatchOutput(result.stdout, "go")) {
|
|
548
|
+
if (entry.error !== void 0) continue;
|
|
549
|
+
out.set(entry.file, {
|
|
550
|
+
file: entry.file,
|
|
551
|
+
lang: "go",
|
|
552
|
+
symbols: entry.symbols.map((s) => ({
|
|
553
|
+
id: 0,
|
|
554
|
+
lang: "go",
|
|
555
|
+
kind: s.kind,
|
|
556
|
+
name: s.name,
|
|
557
|
+
file: entry.file,
|
|
558
|
+
line: s.line,
|
|
559
|
+
col: s.col,
|
|
560
|
+
signature: s.signature ?? "",
|
|
561
|
+
docComment: "",
|
|
562
|
+
scope: s.scope ?? "",
|
|
563
|
+
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
564
|
+
})),
|
|
565
|
+
refs: entry.refs,
|
|
566
|
+
mtimeMs: Date.now()
|
|
567
|
+
});
|
|
635
568
|
}
|
|
636
|
-
return
|
|
569
|
+
return out;
|
|
637
570
|
}
|
|
638
|
-
async function
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
proc.on("close", (code2) => {
|
|
675
|
-
if (settled) return;
|
|
676
|
-
settled = true;
|
|
677
|
-
clearTimeout(timer);
|
|
678
|
-
resolve4({ code: code2, stdout: stdout2 });
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
);
|
|
682
|
-
const { code, stdout } = goResult;
|
|
683
|
-
if (code !== 0 || !stdout.trim()) {
|
|
684
|
-
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
685
|
-
}
|
|
686
|
-
const { symbols: rawSymbols, refs } = parseParserOutput(stdout, lang);
|
|
687
|
-
const symbols = rawSymbols.map((s) => ({
|
|
688
|
-
id: 0,
|
|
689
|
-
lang,
|
|
690
|
-
kind: s.kind,
|
|
691
|
-
name: s.name,
|
|
692
|
-
file: filePath,
|
|
693
|
-
line: s.line,
|
|
694
|
-
col: s.col,
|
|
695
|
-
signature: s.signature ?? "",
|
|
696
|
-
docComment: "",
|
|
697
|
-
scope: s.scope ?? "",
|
|
698
|
-
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
699
|
-
}));
|
|
700
|
-
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
701
|
-
} catch {
|
|
702
|
-
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
571
|
+
async function runPyBatch(files, pythonBinary) {
|
|
572
|
+
const out = /* @__PURE__ */ new Map();
|
|
573
|
+
if (files.length === 0) return out;
|
|
574
|
+
const { path: scriptPath } = await ensureScriptPath(
|
|
575
|
+
_pyBatchScriptPath,
|
|
576
|
+
"ws-py-parse",
|
|
577
|
+
"batch.py",
|
|
578
|
+
PY_BATCH_SCRIPT
|
|
579
|
+
);
|
|
580
|
+
_pyBatchScriptPath = scriptPath;
|
|
581
|
+
const payload = JSON.stringify(files.map((f) => ({ file: f.file, content: f.content })));
|
|
582
|
+
const result = await withSpawnGate(
|
|
583
|
+
() => runToolchainChild(pythonBinary, [scriptPath], payload, batchTimeoutMs(files.length))
|
|
584
|
+
);
|
|
585
|
+
if (result?.code !== 0 || !result.stdout.trim()) return out;
|
|
586
|
+
for (const entry of parseParserBatchOutput(result.stdout, "py")) {
|
|
587
|
+
if (entry.error !== void 0) continue;
|
|
588
|
+
out.set(entry.file, {
|
|
589
|
+
file: entry.file,
|
|
590
|
+
lang: "py",
|
|
591
|
+
symbols: entry.symbols.map((s) => ({
|
|
592
|
+
id: 0,
|
|
593
|
+
lang: "py",
|
|
594
|
+
kind: s.kind,
|
|
595
|
+
name: s.name,
|
|
596
|
+
file: entry.file,
|
|
597
|
+
line: s.line,
|
|
598
|
+
col: s.col,
|
|
599
|
+
signature: s.signature ?? "",
|
|
600
|
+
docComment: "",
|
|
601
|
+
scope: s.scope ?? "",
|
|
602
|
+
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
603
|
+
})),
|
|
604
|
+
refs: entry.refs,
|
|
605
|
+
mtimeMs: Date.now()
|
|
606
|
+
});
|
|
703
607
|
}
|
|
608
|
+
return out;
|
|
704
609
|
}
|
|
705
|
-
var
|
|
706
|
-
var
|
|
707
|
-
"src/codebase-index/
|
|
610
|
+
var MAX_BATCH_FILES, MAX_BATCH_BYTES, GO_BATCH_SCRIPT, PY_BATCH_SCRIPT, _goBatchScriptPath, _pyBatchScriptPath;
|
|
611
|
+
var init_parser_batch = __esm({
|
|
612
|
+
"src/codebase-index/parser-batch.ts"() {
|
|
708
613
|
"use strict";
|
|
709
614
|
init_win32_resolve();
|
|
710
615
|
init_parser_output();
|
|
711
616
|
init_spawn_gate();
|
|
712
|
-
|
|
713
|
-
|
|
617
|
+
MAX_BATCH_FILES = 100;
|
|
618
|
+
MAX_BATCH_BYTES = 8 * 1024 * 1024;
|
|
619
|
+
GO_BATCH_SCRIPT = `
|
|
714
620
|
package main
|
|
715
621
|
|
|
716
622
|
import (
|
|
@@ -734,8 +640,6 @@ type Sym struct {
|
|
|
734
640
|
Scope string \`json:"scope"\`
|
|
735
641
|
}
|
|
736
642
|
|
|
737
|
-
// Ref is a cross-reference emitted alongside the symbols, so one \`go run\`
|
|
738
|
-
// yields both. Module is the import path for CallType "import", else empty.
|
|
739
643
|
type Ref struct {
|
|
740
644
|
ToName string \`json:"toName"\`
|
|
741
645
|
CallType string \`json:"callType"\`
|
|
@@ -743,57 +647,53 @@ type Ref struct {
|
|
|
743
647
|
Module string \`json:"module"\`
|
|
744
648
|
}
|
|
745
649
|
|
|
746
|
-
type
|
|
747
|
-
|
|
748
|
-
|
|
650
|
+
type FileResult struct {
|
|
651
|
+
File string \`json:"file"\`
|
|
652
|
+
Error string \`json:"error,omitempty"\`
|
|
653
|
+
Symbols []Sym \`json:"symbols"\`
|
|
654
|
+
Refs []Ref \`json:"refs"\`
|
|
749
655
|
}
|
|
750
656
|
|
|
751
|
-
|
|
752
|
-
|
|
657
|
+
type BatchResult struct {
|
|
658
|
+
Version int \`json:"version"\`
|
|
659
|
+
Results []FileResult \`json:"results"\`
|
|
753
660
|
}
|
|
754
661
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
662
|
+
type inputFile struct {
|
|
663
|
+
File string \`json:"file"\`
|
|
664
|
+
Content string \`json:"content"\`
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
func parseOne(name string, src []byte) FileResult {
|
|
668
|
+
res := FileResult{File: name, Symbols: []Sym{}, Refs: []Ref{}}
|
|
761
669
|
fset := token.NewFileSet()
|
|
762
670
|
node, err := parser.ParseFile(fset, "src.go", src, 0)
|
|
763
671
|
if err != nil {
|
|
764
|
-
|
|
765
|
-
return
|
|
672
|
+
res.Error = err.Error()
|
|
673
|
+
return res
|
|
766
674
|
}
|
|
767
|
-
|
|
768
|
-
var syms []Sym
|
|
769
|
-
|
|
770
|
-
// Package-level scope
|
|
771
675
|
pkgScope := node.Name.Name
|
|
772
|
-
|
|
773
|
-
// Collect all top-level declarations
|
|
774
676
|
for _, decl := range node.Decls {
|
|
775
677
|
switch d := decl.(type) {
|
|
776
678
|
case *ast.FuncDecl:
|
|
777
|
-
|
|
679
|
+
symName := d.Name.Name
|
|
778
680
|
kind := "function"
|
|
779
681
|
scope := pkgScope
|
|
780
682
|
if d.Recv != nil && len(d.Recv.List) > 0 {
|
|
781
|
-
scope = pkgScope + "." + recvTypeName(d.Recv.List[0].Type) + "." +
|
|
683
|
+
scope = pkgScope + "." + recvTypeName(d.Recv.List[0].Type) + "." + symName
|
|
782
684
|
kind = "method"
|
|
783
685
|
} else {
|
|
784
|
-
scope = pkgScope + "." +
|
|
686
|
+
scope = pkgScope + "." + symName
|
|
785
687
|
}
|
|
786
688
|
pos := fset.Position(d.Pos())
|
|
787
|
-
|
|
788
|
-
syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: scope})
|
|
789
|
-
|
|
689
|
+
res.Symbols = append(res.Symbols, Sym{Name: symName, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: formatFuncSig(d), Scope: scope})
|
|
790
690
|
case *ast.GenDecl:
|
|
791
691
|
for _, spec := range d.Specs {
|
|
792
692
|
switch s := spec.(type) {
|
|
793
693
|
case *ast.TypeSpec:
|
|
794
|
-
|
|
694
|
+
typeName := s.Name.Name
|
|
795
695
|
pos := fset.Position(s.Pos())
|
|
796
|
-
sig := "type " +
|
|
696
|
+
sig := "type " + typeName
|
|
797
697
|
if s.TypeParams != nil {
|
|
798
698
|
sig += formatTypeParams(s.TypeParams)
|
|
799
699
|
}
|
|
@@ -804,64 +704,72 @@ func main() {
|
|
|
804
704
|
} else {
|
|
805
705
|
sig += " = " + formatType(s.Type)
|
|
806
706
|
}
|
|
807
|
-
|
|
808
|
-
|
|
707
|
+
res.Symbols = append(res.Symbols, Sym{Name: typeName, Kind: "type", Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
809
708
|
case *ast.ValueSpec:
|
|
810
709
|
for _, n := range s.Names {
|
|
811
|
-
|
|
710
|
+
valueName := n.Name
|
|
812
711
|
pos := fset.Position(n.Pos())
|
|
813
712
|
kind := "var"
|
|
814
713
|
if d.Tok == token.CONST {
|
|
815
714
|
kind = "const"
|
|
816
715
|
}
|
|
817
|
-
sig := kind + " " +
|
|
716
|
+
sig := kind + " " + valueName
|
|
818
717
|
if s.Type != nil {
|
|
819
718
|
sig += " " + formatType(s.Type)
|
|
820
719
|
}
|
|
821
|
-
|
|
720
|
+
res.Symbols = append(res.Symbols, Sym{Name: valueName, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
822
721
|
}
|
|
823
722
|
}
|
|
824
723
|
}
|
|
825
724
|
}
|
|
826
725
|
}
|
|
827
|
-
|
|
828
|
-
refs := []Ref{}
|
|
829
726
|
ast.Inspect(node, func(n ast.Node) bool {
|
|
830
727
|
switch expr := n.(type) {
|
|
831
728
|
case *ast.CallExpr:
|
|
832
729
|
line := fset.Position(expr.Pos()).Line
|
|
833
730
|
switch fun := expr.Fun.(type) {
|
|
834
731
|
case *ast.Ident:
|
|
835
|
-
|
|
732
|
+
res.Refs = append(res.Refs, Ref{ToName: fun.Name, CallType: "call", Line: line})
|
|
836
733
|
case *ast.SelectorExpr:
|
|
837
|
-
|
|
838
|
-
// declared symbol name, so it resolves the same way the TypeScript
|
|
839
|
-
// and Python extractors' call refs do.
|
|
840
|
-
refs = append(refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
734
|
+
res.Refs = append(res.Refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
841
735
|
}
|
|
842
736
|
case *ast.ImportSpec:
|
|
843
737
|
if expr.Path != nil {
|
|
844
738
|
if importPath, uerr := strconv.Unquote(expr.Path.Value); uerr == nil {
|
|
845
739
|
line := fset.Position(expr.Pos()).Line
|
|
846
|
-
// A Go import names a package, not a symbol; the package's
|
|
847
|
-
// last path segment is the name it is referenced by.
|
|
848
740
|
name := importPath
|
|
849
741
|
if idx := strings.LastIndex(importPath, "/"); idx >= 0 {
|
|
850
742
|
name = importPath[idx+1:]
|
|
851
743
|
}
|
|
852
|
-
|
|
744
|
+
res.Refs = append(res.Refs, Ref{ToName: name, CallType: "import", Line: line, Module: importPath})
|
|
853
745
|
}
|
|
854
746
|
}
|
|
855
747
|
}
|
|
856
748
|
return true
|
|
857
749
|
})
|
|
750
|
+
return res
|
|
751
|
+
}
|
|
858
752
|
|
|
859
|
-
|
|
860
|
-
|
|
753
|
+
func main() {
|
|
754
|
+
raw, err := io.ReadAll(os.Stdin)
|
|
755
|
+
if err != nil {
|
|
756
|
+
out, _ := json.Marshal(BatchResult{Version: 1, Results: []FileResult{}})
|
|
757
|
+
fmt.Print(string(out))
|
|
758
|
+
return
|
|
861
759
|
}
|
|
862
|
-
|
|
760
|
+
var inputs []inputFile
|
|
761
|
+
if err := json.Unmarshal(raw, &inputs); err != nil {
|
|
762
|
+
out, _ := json.Marshal(BatchResult{Version: 1, Results: []FileResult{}})
|
|
763
|
+
fmt.Print(string(out))
|
|
764
|
+
return
|
|
765
|
+
}
|
|
766
|
+
results := make([]FileResult, 0, len(inputs))
|
|
767
|
+
for _, in := range inputs {
|
|
768
|
+
results = append(results, parseOne(in.File, []byte(in.Content)))
|
|
769
|
+
}
|
|
770
|
+
data, err := json.Marshal(BatchResult{Version: 1, Results: results})
|
|
863
771
|
if err != nil {
|
|
864
|
-
fmt.Print(
|
|
772
|
+
fmt.Print("{\\"version\\":1,\\"results\\":[]}")
|
|
865
773
|
return
|
|
866
774
|
}
|
|
867
775
|
fmt.Print(string(data))
|
|
@@ -981,10 +889,8 @@ func formatType(t ast.Expr) string {
|
|
|
981
889
|
case *ast.BasicLit:
|
|
982
890
|
return v.Value
|
|
983
891
|
case *ast.IndexExpr:
|
|
984
|
-
// Generic instantiation with one type arg, e.g. Logger[int].
|
|
985
892
|
return formatType(v.X) + "[" + formatType(v.Index) + "]"
|
|
986
893
|
case *ast.IndexListExpr:
|
|
987
|
-
// Generic instantiation with multiple type args, e.g. Map[K, V].
|
|
988
894
|
args := make([]string, len(v.Indices))
|
|
989
895
|
for i, idx := range v.Indices {
|
|
990
896
|
args[i] = formatType(idx)
|
|
@@ -995,7 +901,187 @@ func formatType(t ast.Expr) string {
|
|
|
995
901
|
}
|
|
996
902
|
}
|
|
997
903
|
`;
|
|
998
|
-
|
|
904
|
+
PY_BATCH_SCRIPT = `import ast, json, sys
|
|
905
|
+
|
|
906
|
+
def get_name(node):
|
|
907
|
+
if isinstance(node, ast.Name):
|
|
908
|
+
return node.id
|
|
909
|
+
elif isinstance(node, ast.Attribute):
|
|
910
|
+
return get_name(node.value) + "." + node.attr
|
|
911
|
+
elif isinstance(node, ast.Subscript):
|
|
912
|
+
return get_name(node.value)
|
|
913
|
+
elif isinstance(node, ast.Call):
|
|
914
|
+
return get_name(node.func)
|
|
915
|
+
elif isinstance(node, ast.Constant):
|
|
916
|
+
return str(node.value)
|
|
917
|
+
return ""
|
|
918
|
+
|
|
919
|
+
def leaf_name(node):
|
|
920
|
+
if isinstance(node, ast.Attribute):
|
|
921
|
+
return node.attr
|
|
922
|
+
if isinstance(node, ast.Name):
|
|
923
|
+
return node.id
|
|
924
|
+
return get_name(node).split(".")[-1]
|
|
925
|
+
|
|
926
|
+
def is_private(name):
|
|
927
|
+
return name.startswith("__") and not name.endswith("__")
|
|
928
|
+
|
|
929
|
+
def parse_one(name, source, module_name):
|
|
930
|
+
result = {"file": name, "symbols": [], "refs": []}
|
|
931
|
+
try:
|
|
932
|
+
tree = ast.parse(source, filename=name)
|
|
933
|
+
except Exception as e:
|
|
934
|
+
result["error"] = str(e)
|
|
935
|
+
return result
|
|
936
|
+
syms = []
|
|
937
|
+
refs = []
|
|
938
|
+
scope_stack = [module_name]
|
|
939
|
+
|
|
940
|
+
def sym(d):
|
|
941
|
+
return {
|
|
942
|
+
"name": d["name"], "kind": d["kind"], "line": d["line"], "col": d["col"],
|
|
943
|
+
"signature": d["signature"], "scope": d["scope"],
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
class Visitor(ast.NodeVisitor):
|
|
947
|
+
def visit_ClassDef(self, node):
|
|
948
|
+
bases = [get_name(b) for b in node.bases]
|
|
949
|
+
sig = "class " + node.name
|
|
950
|
+
if bases:
|
|
951
|
+
sig += "(" + ", ".join(bases) + ")"
|
|
952
|
+
sig += ": ..."
|
|
953
|
+
syms.append(sym({
|
|
954
|
+
"name": node.name, "kind": "class", "line": node.lineno,
|
|
955
|
+
"col": node.col_offset, "signature": sig,
|
|
956
|
+
"scope": ".".join(scope_stack) + "." + node.name,
|
|
957
|
+
}))
|
|
958
|
+
scope_stack.append(node.name)
|
|
959
|
+
self.generic_visit(node)
|
|
960
|
+
scope_stack.pop()
|
|
961
|
+
|
|
962
|
+
def visit_FunctionDef(self, node):
|
|
963
|
+
args = ", ".join(a.arg for a in node.args.args)
|
|
964
|
+
returns = get_name(node.returns) if node.returns is not None else ""
|
|
965
|
+
is_async = isinstance(node, ast.AsyncFunctionDef)
|
|
966
|
+
kind = "function"
|
|
967
|
+
prefix = "def "
|
|
968
|
+
for dec in node.decorator_list:
|
|
969
|
+
d = get_name(dec)
|
|
970
|
+
if d.endswith(".staticmethod"):
|
|
971
|
+
kind = "staticmethod"
|
|
972
|
+
elif d.endswith(".classmethod"):
|
|
973
|
+
kind = "classmethod"
|
|
974
|
+
elif d == "property":
|
|
975
|
+
kind = "property"
|
|
976
|
+
if is_async:
|
|
977
|
+
kind = "async_" + kind
|
|
978
|
+
sig = f"{prefix}{node.name}({args})"
|
|
979
|
+
if returns:
|
|
980
|
+
sig += f" -> {returns}"
|
|
981
|
+
syms.append(sym({
|
|
982
|
+
"name": node.name, "kind": kind, "line": node.lineno,
|
|
983
|
+
"col": node.col_offset, "signature": sig,
|
|
984
|
+
"scope": ".".join(scope_stack) + "." + node.name,
|
|
985
|
+
}))
|
|
986
|
+
|
|
987
|
+
def visit_AsyncFunctionDef(self, node):
|
|
988
|
+
self.visit_FunctionDef(node)
|
|
989
|
+
|
|
990
|
+
def visit_Assign(self, node):
|
|
991
|
+
for target in node.targets:
|
|
992
|
+
if isinstance(target, ast.Name):
|
|
993
|
+
tname = target.id
|
|
994
|
+
if is_private(tname):
|
|
995
|
+
continue
|
|
996
|
+
kind = "const" if tname.isupper() else "var"
|
|
997
|
+
col = target.col_offset if hasattr(target, "col_offset") else 0
|
|
998
|
+
syms.append(sym({
|
|
999
|
+
"name": tname, "kind": kind, "line": node.lineno, "col": col,
|
|
1000
|
+
"signature": f"{tname} = ...", "scope": ".".join(scope_stack),
|
|
1001
|
+
}))
|
|
1002
|
+
|
|
1003
|
+
def visit_AnnAssign(self, node):
|
|
1004
|
+
if isinstance(node.target, ast.Name):
|
|
1005
|
+
tname = node.target.id
|
|
1006
|
+
if is_private(tname):
|
|
1007
|
+
return
|
|
1008
|
+
kind = "const" if tname.isupper() else "var"
|
|
1009
|
+
col = node.target.col_offset if hasattr(node.target, "col_offset") else 0
|
|
1010
|
+
sig = f"{tname}: {get_name(node.annotation)}"
|
|
1011
|
+
if node.value:
|
|
1012
|
+
sig += " = ..."
|
|
1013
|
+
syms.append(sym({
|
|
1014
|
+
"name": tname, "kind": kind, "line": node.lineno, "col": col,
|
|
1015
|
+
"signature": sig, "scope": ".".join(scope_stack),
|
|
1016
|
+
}))
|
|
1017
|
+
|
|
1018
|
+
def visit_Import(self, node):
|
|
1019
|
+
# Parity with the single-file parser: imports are symbols too.
|
|
1020
|
+
for alias in node.names:
|
|
1021
|
+
name = alias.asname or alias.name
|
|
1022
|
+
syms.append(sym({
|
|
1023
|
+
"name": name, "kind": "import", "line": node.lineno,
|
|
1024
|
+
"col": node.col_offset, "signature": f"import {alias.name}",
|
|
1025
|
+
"scope": ".".join(scope_stack),
|
|
1026
|
+
}))
|
|
1027
|
+
|
|
1028
|
+
def visit_ImportFrom(self, node):
|
|
1029
|
+
module = node.module or ""
|
|
1030
|
+
for alias in node.names:
|
|
1031
|
+
name = alias.asname or alias.name
|
|
1032
|
+
syms.append(sym({
|
|
1033
|
+
"name": name, "kind": "import", "line": node.lineno,
|
|
1034
|
+
"col": node.col_offset, "signature": f"from {module} import {alias.name}",
|
|
1035
|
+
"scope": ".".join(scope_stack),
|
|
1036
|
+
}))
|
|
1037
|
+
|
|
1038
|
+
Visitor().visit(tree)
|
|
1039
|
+
|
|
1040
|
+
for node in ast.walk(tree):
|
|
1041
|
+
if isinstance(node, ast.Call):
|
|
1042
|
+
cname = leaf_name(node.func)
|
|
1043
|
+
if cname:
|
|
1044
|
+
refs.append({"toName": cname, "callType": "call", "line": node.lineno})
|
|
1045
|
+
elif isinstance(node, ast.Import):
|
|
1046
|
+
for alias in node.names:
|
|
1047
|
+
refs.append({
|
|
1048
|
+
"toName": alias.name.split(".")[-1], "callType": "import",
|
|
1049
|
+
"line": node.lineno, "module": alias.name,
|
|
1050
|
+
})
|
|
1051
|
+
elif isinstance(node, ast.ImportFrom):
|
|
1052
|
+
module = ("." * (node.level or 0)) + (node.module or "")
|
|
1053
|
+
for alias in node.names:
|
|
1054
|
+
refs.append({
|
|
1055
|
+
"toName": alias.name, "callType": "import",
|
|
1056
|
+
"line": node.lineno, "module": module,
|
|
1057
|
+
})
|
|
1058
|
+
elif isinstance(node, ast.ClassDef):
|
|
1059
|
+
for base in node.bases:
|
|
1060
|
+
bname = leaf_name(base)
|
|
1061
|
+
if bname:
|
|
1062
|
+
refs.append({"toName": bname, "callType": "inherit", "line": node.lineno})
|
|
1063
|
+
|
|
1064
|
+
result["symbols"] = syms
|
|
1065
|
+
result["refs"] = refs
|
|
1066
|
+
return result
|
|
1067
|
+
|
|
1068
|
+
def main():
|
|
1069
|
+
try:
|
|
1070
|
+
inputs = json.loads(sys.stdin.read())
|
|
1071
|
+
except Exception:
|
|
1072
|
+
print(json.dumps({"version": 1, "results": []}))
|
|
1073
|
+
return
|
|
1074
|
+
results = []
|
|
1075
|
+
for entry in inputs:
|
|
1076
|
+
name = entry.get("file", "")
|
|
1077
|
+
module_name = name.rsplit("/", 1)[-1].rsplit("\\\\", 1)[-1][:-3]
|
|
1078
|
+
results.append(parse_one(name, entry.get("content", ""), module_name))
|
|
1079
|
+
print(json.dumps({"version": 1, "results": results}))
|
|
1080
|
+
|
|
1081
|
+
main()
|
|
1082
|
+
`;
|
|
1083
|
+
_goBatchScriptPath = null;
|
|
1084
|
+
_pyBatchScriptPath = null;
|
|
999
1085
|
}
|
|
1000
1086
|
});
|
|
1001
1087
|
|
|
@@ -1005,7 +1091,7 @@ __export(generic_parser_exports, {
|
|
|
1005
1091
|
GENERIC_MAX_FILE_CHARS: () => GENERIC_MAX_FILE_CHARS,
|
|
1006
1092
|
GENERIC_MAX_SYMBOLS_DEFAULT: () => GENERIC_MAX_SYMBOLS_DEFAULT,
|
|
1007
1093
|
parseGeneric: () => parseGeneric,
|
|
1008
|
-
parseSymbols: () =>
|
|
1094
|
+
parseSymbols: () => parseSymbols
|
|
1009
1095
|
});
|
|
1010
1096
|
function patternsFor(lang) {
|
|
1011
1097
|
return LANG_PATTERNS[lang] ?? LANG_PATTERNS.other ?? [];
|
|
@@ -1093,7 +1179,7 @@ function parseGeneric(opts) {
|
|
|
1093
1179
|
}
|
|
1094
1180
|
return { file, lang, symbols, mtimeMs };
|
|
1095
1181
|
}
|
|
1096
|
-
async function
|
|
1182
|
+
async function parseSymbols(opts) {
|
|
1097
1183
|
return parseGeneric(opts);
|
|
1098
1184
|
}
|
|
1099
1185
|
var C_LIKE, LANG_PATTERNS, KEYWORDS, GENERIC_MAX_SYMBOLS_DEFAULT, GENERIC_MAX_FILE_CHARS;
|
|
@@ -1326,13 +1412,14 @@ var init_generic_parser = __esm({
|
|
|
1326
1412
|
var py_parser_exports = {};
|
|
1327
1413
|
__export(py_parser_exports, {
|
|
1328
1414
|
detectLang: () => detectLang,
|
|
1329
|
-
parseSymbols: () =>
|
|
1415
|
+
parseSymbols: () => parseSymbols2,
|
|
1416
|
+
resolvePythonBinary: () => resolvePythonBinary
|
|
1330
1417
|
});
|
|
1331
1418
|
import { spawn as spawn2 } from "node:child_process";
|
|
1332
1419
|
import * as fs5 from "node:fs/promises";
|
|
1333
1420
|
import * as os2 from "node:os";
|
|
1334
1421
|
import * as path9 from "node:path";
|
|
1335
|
-
async function
|
|
1422
|
+
async function parseSymbols2(opts) {
|
|
1336
1423
|
const { file, content, lang } = opts;
|
|
1337
1424
|
try {
|
|
1338
1425
|
const native = await withSpawnGate(() => syncPyParse(file, content, lang));
|
|
@@ -1406,6 +1493,10 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
|
1406
1493
|
});
|
|
1407
1494
|
});
|
|
1408
1495
|
}
|
|
1496
|
+
function resolvePythonBinary() {
|
|
1497
|
+
cachedPyBinary ??= resolvePython();
|
|
1498
|
+
return cachedPyBinary;
|
|
1499
|
+
}
|
|
1409
1500
|
async function syncPyParse(filePath, content, lang) {
|
|
1410
1501
|
try {
|
|
1411
1502
|
if (!_cachedScriptPath) {
|
|
@@ -1696,9 +1787,713 @@ for node in ast.walk(tree):
|
|
|
1696
1787
|
if name:
|
|
1697
1788
|
refs.append({"toName": name, "callType": "inherit", "line": node.lineno})
|
|
1698
1789
|
|
|
1699
|
-
print(json.dumps({"symbols": [s.to_dict() for s in syms], "refs": refs}))
|
|
1790
|
+
print(json.dumps({"symbols": [s.to_dict() for s in syms], "refs": refs}))
|
|
1791
|
+
`;
|
|
1792
|
+
_cachedScriptPath = null;
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
|
|
1796
|
+
// src/codebase-index/ts-parser.ts
|
|
1797
|
+
var ts_parser_exports = {};
|
|
1798
|
+
__export(ts_parser_exports, {
|
|
1799
|
+
detectLang: () => detectLang,
|
|
1800
|
+
parseSymbols: () => parseSymbols3
|
|
1801
|
+
});
|
|
1802
|
+
function loadTypescript2() {
|
|
1803
|
+
tsLoad2 ??= import("@typescript/typescript6").then((m) => {
|
|
1804
|
+
ts = m.default ?? m;
|
|
1805
|
+
return ts;
|
|
1806
|
+
});
|
|
1807
|
+
return tsLoad2;
|
|
1808
|
+
}
|
|
1809
|
+
function kindMap() {
|
|
1810
|
+
kindMapCache ??= {
|
|
1811
|
+
[ts.SyntaxKind.ClassDeclaration]: "class",
|
|
1812
|
+
[ts.SyntaxKind.InterfaceDeclaration]: "interface",
|
|
1813
|
+
[ts.SyntaxKind.EnumDeclaration]: "enum",
|
|
1814
|
+
[ts.SyntaxKind.TypeAliasDeclaration]: "type",
|
|
1815
|
+
[ts.SyntaxKind.FunctionDeclaration]: "function",
|
|
1816
|
+
[ts.SyntaxKind.MethodDeclaration]: "method",
|
|
1817
|
+
[ts.SyntaxKind.GetAccessor]: "property",
|
|
1818
|
+
[ts.SyntaxKind.SetAccessor]: "property",
|
|
1819
|
+
[ts.SyntaxKind.PropertyDeclaration]: "property",
|
|
1820
|
+
[ts.SyntaxKind.Parameter]: "parameter",
|
|
1821
|
+
[ts.SyntaxKind.NamespaceExportDeclaration]: "namespace"
|
|
1822
|
+
};
|
|
1823
|
+
return kindMapCache;
|
|
1824
|
+
}
|
|
1825
|
+
function kindOf(node) {
|
|
1826
|
+
if (ts.isVariableDeclaration(node)) {
|
|
1827
|
+
const parent = node.parent;
|
|
1828
|
+
if (ts.isVariableDeclarationList(parent)) {
|
|
1829
|
+
const flags = parent.flags;
|
|
1830
|
+
if (flags & ts.NodeFlags.Let) return "let";
|
|
1831
|
+
if (flags & ts.NodeFlags.Const) return "const";
|
|
1832
|
+
return "var";
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
1836
|
+
return kindMap()[node.kind] ?? null;
|
|
1837
|
+
}
|
|
1838
|
+
function getSignature(printer, node, sourceFile) {
|
|
1839
|
+
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
1840
|
+
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
1841
|
+
}
|
|
1842
|
+
function getJsDoc(node, sourceFile) {
|
|
1843
|
+
const fullText = sourceFile.getFullText();
|
|
1844
|
+
const nodePos = node.getFullStart();
|
|
1845
|
+
const comments = ts.getLeadingCommentRanges(fullText, nodePos);
|
|
1846
|
+
if (!comments) return "";
|
|
1847
|
+
for (const range of comments) {
|
|
1848
|
+
const commentText = fullText.slice(range.pos, range.end);
|
|
1849
|
+
const trimmed = commentText.trim();
|
|
1850
|
+
if (trimmed.startsWith("/**") && trimmed.endsWith("*/")) {
|
|
1851
|
+
const inner = trimmed.slice(3, -2).replace(/^[ \t]*\*[ ]?/gm, "").trim();
|
|
1852
|
+
return inner.split("\n")[0]?.trim().slice(0, 200) ?? "";
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
return "";
|
|
1856
|
+
}
|
|
1857
|
+
function pushScopeName(node, parts) {
|
|
1858
|
+
if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
|
|
1859
|
+
parts.push(node.name?.text ?? "Anon");
|
|
1860
|
+
} else if (ts.isMethodDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node) || ts.isPropertyDeclaration(node) || ts.isFunctionDeclaration(node)) {
|
|
1861
|
+
if (node.name && ts.isIdentifier(node.name)) {
|
|
1862
|
+
parts.push(node.name.text);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
async function parseSymbols3(opts) {
|
|
1867
|
+
const { file, content, lang } = opts;
|
|
1868
|
+
await loadTypescript2();
|
|
1869
|
+
let sourceFile;
|
|
1870
|
+
try {
|
|
1871
|
+
sourceFile = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true);
|
|
1872
|
+
} catch {
|
|
1873
|
+
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
1874
|
+
}
|
|
1875
|
+
const symbols = [];
|
|
1876
|
+
const refs = [];
|
|
1877
|
+
const printer = ts.createPrinter({});
|
|
1878
|
+
function visit(node, funcDepth, scopeParts) {
|
|
1879
|
+
const kind = kindOf(node);
|
|
1880
|
+
if (kind) {
|
|
1881
|
+
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && funcDepth > 0) {
|
|
1882
|
+
} else {
|
|
1883
|
+
const nameNode = node.name;
|
|
1884
|
+
if (!nameNode || !ts.isIdentifier(nameNode)) {
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
const name = nameNode.text;
|
|
1888
|
+
const pos2 = nameNode.getStart(sourceFile);
|
|
1889
|
+
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(pos2);
|
|
1890
|
+
const scope = scopeParts.join(".");
|
|
1891
|
+
const signature = getSignature(printer, node, sourceFile);
|
|
1892
|
+
const docComment = getJsDoc(node, sourceFile);
|
|
1893
|
+
const text = [name, signature, docComment].filter(Boolean).join(" | ");
|
|
1894
|
+
symbols.push({
|
|
1895
|
+
id: 0,
|
|
1896
|
+
lang,
|
|
1897
|
+
kind,
|
|
1898
|
+
name,
|
|
1899
|
+
file,
|
|
1900
|
+
line: line2 + 1,
|
|
1901
|
+
col: character,
|
|
1902
|
+
signature,
|
|
1903
|
+
docComment,
|
|
1904
|
+
scope,
|
|
1905
|
+
text
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
const pos = node.getStart(sourceFile);
|
|
1910
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
|
|
1911
|
+
const lineNum = line + 1;
|
|
1912
|
+
if (ts.isCallExpression(node)) {
|
|
1913
|
+
const expr = node.expression;
|
|
1914
|
+
if (ts.isIdentifier(expr)) {
|
|
1915
|
+
refs.push({ fromId: 0, toName: expr.text, callType: "call", line: lineNum });
|
|
1916
|
+
}
|
|
1917
|
+
} else if (ts.isPropertyAccessExpression(node)) {
|
|
1918
|
+
if (ts.isIdentifier(node.expression)) {
|
|
1919
|
+
refs.push({ fromId: 0, toName: node.expression.text, callType: "call", line: lineNum });
|
|
1920
|
+
}
|
|
1921
|
+
} else if (ts.isTypeReferenceNode(node)) {
|
|
1922
|
+
const name = getTypeName(node.typeName);
|
|
1923
|
+
if (name) refs.push({ fromId: 0, toName: name, callType: "type_ref", line: lineNum });
|
|
1924
|
+
} else if (ts.isHeritageClause(node)) {
|
|
1925
|
+
for (const t of node.types) {
|
|
1926
|
+
const name = getTypeName(t.expression);
|
|
1927
|
+
if (name)
|
|
1928
|
+
refs.push({
|
|
1929
|
+
fromId: 0,
|
|
1930
|
+
toName: name,
|
|
1931
|
+
callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement",
|
|
1932
|
+
line: lineNum
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
} else if (ts.isImportDeclaration(node)) {
|
|
1936
|
+
emitImportSpecifierRefs(node, refs, lineNum);
|
|
1937
|
+
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
|
|
1938
|
+
emitExportSpecifierRefs(node, refs, lineNum);
|
|
1939
|
+
}
|
|
1940
|
+
const scopeIdx = scopeParts.length;
|
|
1941
|
+
pushScopeName(node, scopeParts);
|
|
1942
|
+
const childFuncDepth = ts.isFunctionLike(node) ? funcDepth + 1 : funcDepth;
|
|
1943
|
+
ts.forEachChild(node, (child) => visit(child, childFuncDepth, scopeParts));
|
|
1944
|
+
scopeParts.length = scopeIdx;
|
|
1945
|
+
}
|
|
1946
|
+
visit(sourceFile, 0, []);
|
|
1947
|
+
return { file, lang, symbols, refs: deduplicateRefs(refs), mtimeMs: Date.now() };
|
|
1948
|
+
}
|
|
1949
|
+
function getTypeName(name) {
|
|
1950
|
+
if (ts.isIdentifier(name)) return name.text;
|
|
1951
|
+
if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
|
|
1952
|
+
return "";
|
|
1953
|
+
}
|
|
1954
|
+
function deduplicateRefs(refs) {
|
|
1955
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1956
|
+
return refs.filter((r) => {
|
|
1957
|
+
const key = `${r.toName}:${r.callType}:${r.line}:${r.module ?? ""}`;
|
|
1958
|
+
if (seen.has(key)) return false;
|
|
1959
|
+
seen.add(key);
|
|
1960
|
+
return true;
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
function getImportSpecifierName(spec) {
|
|
1964
|
+
return spec.propertyName?.text ?? spec.name.text;
|
|
1965
|
+
}
|
|
1966
|
+
function emitImportSpecifierRefs(node, refs, lineNum) {
|
|
1967
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
1968
|
+
const clause = node.importClause;
|
|
1969
|
+
if (!clause) {
|
|
1970
|
+
if (module) {
|
|
1971
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
1972
|
+
}
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
if (clause.name) {
|
|
1976
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
1977
|
+
}
|
|
1978
|
+
const bindings = clause.namedBindings;
|
|
1979
|
+
if (!bindings) return;
|
|
1980
|
+
if (ts.isNamedImports(bindings)) {
|
|
1981
|
+
for (const element of bindings.elements) {
|
|
1982
|
+
refs.push({
|
|
1983
|
+
fromId: 0,
|
|
1984
|
+
toName: getImportSpecifierName(element),
|
|
1985
|
+
callType: "import",
|
|
1986
|
+
line: lineNum,
|
|
1987
|
+
module
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
} else if (ts.isNamespaceImport(bindings)) {
|
|
1991
|
+
refs.push({
|
|
1992
|
+
fromId: 0,
|
|
1993
|
+
toName: bindings.name.text,
|
|
1994
|
+
callType: "import",
|
|
1995
|
+
line: lineNum,
|
|
1996
|
+
module
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
function moduleSpecifierOf(node) {
|
|
2001
|
+
return node && ts.isStringLiteral(node) ? node.text : void 0;
|
|
2002
|
+
}
|
|
2003
|
+
function emitExportSpecifierRefs(node, refs, lineNum) {
|
|
2004
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
2005
|
+
const clause = node.exportClause;
|
|
2006
|
+
if (clause && ts.isNamespaceExport(clause)) {
|
|
2007
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
if (clause && ts.isNamedExports(clause)) {
|
|
2011
|
+
for (const element of clause.elements) {
|
|
2012
|
+
const originalName = element.propertyName?.text ?? element.name.text;
|
|
2013
|
+
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum, module });
|
|
2014
|
+
}
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
if (module) {
|
|
2018
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
var ts, tsLoad2, kindMapCache;
|
|
2022
|
+
var init_ts_parser = __esm({
|
|
2023
|
+
"src/codebase-index/ts-parser.ts"() {
|
|
2024
|
+
"use strict";
|
|
2025
|
+
init_languages();
|
|
2026
|
+
tsLoad2 = null;
|
|
2027
|
+
kindMapCache = null;
|
|
2028
|
+
}
|
|
2029
|
+
});
|
|
2030
|
+
|
|
2031
|
+
// src/codebase-index/go-parser.ts
|
|
2032
|
+
var go_parser_exports = {};
|
|
2033
|
+
__export(go_parser_exports, {
|
|
2034
|
+
detectLang: () => detectLang,
|
|
2035
|
+
parseSymbols: () => parseSymbols4
|
|
2036
|
+
});
|
|
2037
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
2038
|
+
import * as os3 from "node:os";
|
|
2039
|
+
import * as path10 from "node:path";
|
|
2040
|
+
import * as fs6 from "node:fs/promises";
|
|
2041
|
+
async function parseSymbols4(opts) {
|
|
2042
|
+
const { file, content, lang } = opts;
|
|
2043
|
+
try {
|
|
2044
|
+
const parsed = await withSpawnGate(() => syncGoParse(file, content, lang));
|
|
2045
|
+
if (parsed.symbols.length > 0) {
|
|
2046
|
+
return parsed;
|
|
2047
|
+
}
|
|
2048
|
+
const fallback = fallbackParse(file, content, lang);
|
|
2049
|
+
return parsed.refs?.length ? { ...fallback, refs: parsed.refs } : fallback;
|
|
2050
|
+
} catch {
|
|
2051
|
+
return fallbackParse(file, content, lang);
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
function fallbackParse(filePath, content, lang) {
|
|
2055
|
+
if (!/^\s*package\s+[A-Za-z_]\w*/m.test(content) || hasUnbalancedDelimiters(content)) {
|
|
2056
|
+
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
2057
|
+
}
|
|
2058
|
+
const symbols = [];
|
|
2059
|
+
const packageName = content.match(/^\s*package\s+([A-Za-z_]\w*)/m)?.[1] ?? "";
|
|
2060
|
+
const lines = content.split(/\r?\n/);
|
|
2061
|
+
for (const [idx, line] of lines.entries()) {
|
|
2062
|
+
const trimmed = line.trimStart();
|
|
2063
|
+
const col = line.length - trimmed.length + 1;
|
|
2064
|
+
const fn = /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)\s*\(/.exec(trimmed);
|
|
2065
|
+
if (fn?.[1]) {
|
|
2066
|
+
addFallbackSymbol(symbols, {
|
|
2067
|
+
filePath,
|
|
2068
|
+
lang,
|
|
2069
|
+
kind: trimmed.startsWith("func (") ? "method" : "function",
|
|
2070
|
+
name: fn[1],
|
|
2071
|
+
line: idx + 1,
|
|
2072
|
+
col,
|
|
2073
|
+
signature: trimmed,
|
|
2074
|
+
scope: packageName ? `${packageName}.${fn[1]}` : fn[1]
|
|
2075
|
+
});
|
|
2076
|
+
continue;
|
|
2077
|
+
}
|
|
2078
|
+
const typeDecl = /^type\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
2079
|
+
if (typeDecl?.[1]) {
|
|
2080
|
+
addFallbackSymbol(symbols, {
|
|
2081
|
+
filePath,
|
|
2082
|
+
lang,
|
|
2083
|
+
kind: "type",
|
|
2084
|
+
name: typeDecl[1],
|
|
2085
|
+
line: idx + 1,
|
|
2086
|
+
col,
|
|
2087
|
+
signature: trimmed,
|
|
2088
|
+
scope: packageName
|
|
2089
|
+
});
|
|
2090
|
+
continue;
|
|
2091
|
+
}
|
|
2092
|
+
const valueDecl = /^(const|var)\s+([A-Za-z_]\w*)\b/.exec(trimmed);
|
|
2093
|
+
if (valueDecl?.[1] && valueDecl[2]) {
|
|
2094
|
+
addFallbackSymbol(symbols, {
|
|
2095
|
+
filePath,
|
|
2096
|
+
lang,
|
|
2097
|
+
kind: valueDecl[1],
|
|
2098
|
+
name: valueDecl[2],
|
|
2099
|
+
line: idx + 1,
|
|
2100
|
+
col,
|
|
2101
|
+
signature: trimmed,
|
|
2102
|
+
scope: packageName
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
2107
|
+
}
|
|
2108
|
+
function addFallbackSymbol(symbols, opts) {
|
|
2109
|
+
symbols.push({
|
|
2110
|
+
id: 0,
|
|
2111
|
+
lang: opts.lang,
|
|
2112
|
+
kind: opts.kind,
|
|
2113
|
+
name: opts.name,
|
|
2114
|
+
file: opts.filePath,
|
|
2115
|
+
line: opts.line,
|
|
2116
|
+
col: opts.col,
|
|
2117
|
+
signature: opts.signature,
|
|
2118
|
+
docComment: "",
|
|
2119
|
+
scope: opts.scope,
|
|
2120
|
+
text: `${opts.name} ${opts.signature}`.trim()
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
function hasUnbalancedDelimiters(content) {
|
|
2124
|
+
const pairs = { "(": ")", "[": "]", "{": "}" };
|
|
2125
|
+
const closers = new Set(Object.values(pairs));
|
|
2126
|
+
const stack = [];
|
|
2127
|
+
for (const ch of content) {
|
|
2128
|
+
if (pairs[ch]) {
|
|
2129
|
+
stack.push(pairs[ch]);
|
|
2130
|
+
} else if (closers.has(ch) && stack.pop() !== ch) {
|
|
2131
|
+
return true;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
return stack.length > 0;
|
|
2135
|
+
}
|
|
2136
|
+
async function syncGoParse(filePath, content, lang) {
|
|
2137
|
+
try {
|
|
2138
|
+
let scriptPath = _cachedGoScriptPath;
|
|
2139
|
+
if (!scriptPath) {
|
|
2140
|
+
const tmpDir = await fs6.mkdtemp(path10.join(os3.tmpdir(), "ws-go-parse-"));
|
|
2141
|
+
scriptPath = path10.join(tmpDir, "parse.go");
|
|
2142
|
+
await fs6.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
2143
|
+
_cachedGoScriptPath = scriptPath;
|
|
2144
|
+
}
|
|
2145
|
+
const goBinary = resolveWin32Command("go");
|
|
2146
|
+
const goResult = await new Promise(
|
|
2147
|
+
(resolve4, reject) => {
|
|
2148
|
+
let settled = false;
|
|
2149
|
+
const proc = spawn3(goBinary, ["run", scriptPath], {
|
|
2150
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2151
|
+
windowsHide: true
|
|
2152
|
+
});
|
|
2153
|
+
proc.on("error", (err) => {
|
|
2154
|
+
if (settled) return;
|
|
2155
|
+
settled = true;
|
|
2156
|
+
reject(err);
|
|
2157
|
+
});
|
|
2158
|
+
let stdout2 = "";
|
|
2159
|
+
proc.stdout?.on("data", (chunk) => {
|
|
2160
|
+
stdout2 += chunk.toString();
|
|
2161
|
+
});
|
|
2162
|
+
proc.stderr?.resume();
|
|
2163
|
+
proc.stdin?.write(content);
|
|
2164
|
+
proc.stdin?.end();
|
|
2165
|
+
const timer = setTimeout(() => {
|
|
2166
|
+
if (settled) return;
|
|
2167
|
+
settled = true;
|
|
2168
|
+
proc.kill("SIGKILL");
|
|
2169
|
+
reject(new Error("timeout"));
|
|
2170
|
+
}, 15e3);
|
|
2171
|
+
timer.unref?.();
|
|
2172
|
+
proc.on("close", (code2) => {
|
|
2173
|
+
if (settled) return;
|
|
2174
|
+
settled = true;
|
|
2175
|
+
clearTimeout(timer);
|
|
2176
|
+
resolve4({ code: code2, stdout: stdout2 });
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
2179
|
+
);
|
|
2180
|
+
const { code, stdout } = goResult;
|
|
2181
|
+
if (code !== 0 || !stdout.trim()) {
|
|
2182
|
+
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
2183
|
+
}
|
|
2184
|
+
const { symbols: rawSymbols, refs } = parseParserOutput(stdout, lang);
|
|
2185
|
+
const symbols = rawSymbols.map((s) => ({
|
|
2186
|
+
id: 0,
|
|
2187
|
+
lang,
|
|
2188
|
+
kind: s.kind,
|
|
2189
|
+
name: s.name,
|
|
2190
|
+
file: filePath,
|
|
2191
|
+
line: s.line,
|
|
2192
|
+
col: s.col,
|
|
2193
|
+
signature: s.signature ?? "",
|
|
2194
|
+
docComment: "",
|
|
2195
|
+
scope: s.scope ?? "",
|
|
2196
|
+
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
2197
|
+
}));
|
|
2198
|
+
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
2199
|
+
} catch {
|
|
2200
|
+
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
var GO_PARSE_SCRIPT, _cachedGoScriptPath;
|
|
2204
|
+
var init_go_parser = __esm({
|
|
2205
|
+
"src/codebase-index/go-parser.ts"() {
|
|
2206
|
+
"use strict";
|
|
2207
|
+
init_win32_resolve();
|
|
2208
|
+
init_parser_output();
|
|
2209
|
+
init_spawn_gate();
|
|
2210
|
+
init_languages();
|
|
2211
|
+
GO_PARSE_SCRIPT = `
|
|
2212
|
+
package main
|
|
2213
|
+
|
|
2214
|
+
import (
|
|
2215
|
+
"encoding/json"
|
|
2216
|
+
"fmt"
|
|
2217
|
+
"go/ast"
|
|
2218
|
+
"go/parser"
|
|
2219
|
+
"go/token"
|
|
2220
|
+
"io"
|
|
2221
|
+
"os"
|
|
2222
|
+
"strconv"
|
|
2223
|
+
"strings"
|
|
2224
|
+
)
|
|
2225
|
+
|
|
2226
|
+
type Sym struct {
|
|
2227
|
+
Name string \`json:"name"\`
|
|
2228
|
+
Kind string \`json:"kind"\`
|
|
2229
|
+
Line int \`json:"line"\`
|
|
2230
|
+
Col int \`json:"col"\`
|
|
2231
|
+
Signature string \`json:"signature"\`
|
|
2232
|
+
Scope string \`json:"scope"\`
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
// Ref is a cross-reference emitted alongside the symbols, so one \`go run\`
|
|
2236
|
+
// yields both. Module is the import path for CallType "import", else empty.
|
|
2237
|
+
type Ref struct {
|
|
2238
|
+
ToName string \`json:"toName"\`
|
|
2239
|
+
CallType string \`json:"callType"\`
|
|
2240
|
+
Line int \`json:"line"\`
|
|
2241
|
+
Module string \`json:"module"\`
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
type Result struct {
|
|
2245
|
+
Symbols []Sym \`json:"symbols"\`
|
|
2246
|
+
Refs []Ref \`json:"refs"\`
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
func emptyResult() string {
|
|
2250
|
+
return "{\\"symbols\\":[],\\"refs\\":[]}"
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
func main() {
|
|
2254
|
+
src, err := io.ReadAll(os.Stdin)
|
|
2255
|
+
if err != nil {
|
|
2256
|
+
fmt.Print(emptyResult())
|
|
2257
|
+
return
|
|
2258
|
+
}
|
|
2259
|
+
fset := token.NewFileSet()
|
|
2260
|
+
node, err := parser.ParseFile(fset, "src.go", src, 0)
|
|
2261
|
+
if err != nil {
|
|
2262
|
+
fmt.Print(emptyResult())
|
|
2263
|
+
return
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
var syms []Sym
|
|
2267
|
+
|
|
2268
|
+
// Package-level scope
|
|
2269
|
+
pkgScope := node.Name.Name
|
|
2270
|
+
|
|
2271
|
+
// Collect all top-level declarations
|
|
2272
|
+
for _, decl := range node.Decls {
|
|
2273
|
+
switch d := decl.(type) {
|
|
2274
|
+
case *ast.FuncDecl:
|
|
2275
|
+
name := d.Name.Name
|
|
2276
|
+
kind := "function"
|
|
2277
|
+
scope := pkgScope
|
|
2278
|
+
if d.Recv != nil && len(d.Recv.List) > 0 {
|
|
2279
|
+
scope = pkgScope + "." + recvTypeName(d.Recv.List[0].Type) + "." + name
|
|
2280
|
+
kind = "method"
|
|
2281
|
+
} else {
|
|
2282
|
+
scope = pkgScope + "." + name
|
|
2283
|
+
}
|
|
2284
|
+
pos := fset.Position(d.Pos())
|
|
2285
|
+
sig := formatFuncSig(d)
|
|
2286
|
+
syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: scope})
|
|
2287
|
+
|
|
2288
|
+
case *ast.GenDecl:
|
|
2289
|
+
for _, spec := range d.Specs {
|
|
2290
|
+
switch s := spec.(type) {
|
|
2291
|
+
case *ast.TypeSpec:
|
|
2292
|
+
name := s.Name.Name
|
|
2293
|
+
pos := fset.Position(s.Pos())
|
|
2294
|
+
sig := "type " + name
|
|
2295
|
+
if s.TypeParams != nil {
|
|
2296
|
+
sig += formatTypeParams(s.TypeParams)
|
|
2297
|
+
}
|
|
2298
|
+
if st, ok := s.Type.(*ast.StructType); ok {
|
|
2299
|
+
sig += " = struct { " + formatFields(st.Fields.List) + " }"
|
|
2300
|
+
} else if it, ok := s.Type.(*ast.InterfaceType); ok {
|
|
2301
|
+
sig += " = interface { " + formatMethods(it.Methods.List) + " }"
|
|
2302
|
+
} else {
|
|
2303
|
+
sig += " = " + formatType(s.Type)
|
|
2304
|
+
}
|
|
2305
|
+
syms = append(syms, Sym{Name: name, Kind: "type", Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
2306
|
+
|
|
2307
|
+
case *ast.ValueSpec:
|
|
2308
|
+
for _, n := range s.Names {
|
|
2309
|
+
name := n.Name
|
|
2310
|
+
pos := fset.Position(n.Pos())
|
|
2311
|
+
kind := "var"
|
|
2312
|
+
if d.Tok == token.CONST {
|
|
2313
|
+
kind = "const"
|
|
2314
|
+
}
|
|
2315
|
+
sig := kind + " " + name
|
|
2316
|
+
if s.Type != nil {
|
|
2317
|
+
sig += " " + formatType(s.Type)
|
|
2318
|
+
}
|
|
2319
|
+
syms = append(syms, Sym{Name: name, Kind: kind, Line: pos.Line, Col: pos.Column, Signature: sig, Scope: pkgScope})
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
refs := []Ref{}
|
|
2327
|
+
ast.Inspect(node, func(n ast.Node) bool {
|
|
2328
|
+
switch expr := n.(type) {
|
|
2329
|
+
case *ast.CallExpr:
|
|
2330
|
+
line := fset.Position(expr.Pos()).Line
|
|
2331
|
+
switch fun := expr.Fun.(type) {
|
|
2332
|
+
case *ast.Ident:
|
|
2333
|
+
refs = append(refs, Ref{ToName: fun.Name, CallType: "call", Line: line})
|
|
2334
|
+
case *ast.SelectorExpr:
|
|
2335
|
+
// Record the selected name (\`Join\` of \`filepath.Join\`): it is the
|
|
2336
|
+
// declared symbol name, so it resolves the same way the TypeScript
|
|
2337
|
+
// and Python extractors' call refs do.
|
|
2338
|
+
refs = append(refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
2339
|
+
}
|
|
2340
|
+
case *ast.ImportSpec:
|
|
2341
|
+
if expr.Path != nil {
|
|
2342
|
+
if importPath, uerr := strconv.Unquote(expr.Path.Value); uerr == nil {
|
|
2343
|
+
line := fset.Position(expr.Pos()).Line
|
|
2344
|
+
// A Go import names a package, not a symbol; the package's
|
|
2345
|
+
// last path segment is the name it is referenced by.
|
|
2346
|
+
name := importPath
|
|
2347
|
+
if idx := strings.LastIndex(importPath, "/"); idx >= 0 {
|
|
2348
|
+
name = importPath[idx+1:]
|
|
2349
|
+
}
|
|
2350
|
+
refs = append(refs, Ref{ToName: name, CallType: "import", Line: line, Module: importPath})
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
return true
|
|
2355
|
+
})
|
|
2356
|
+
|
|
2357
|
+
if syms == nil {
|
|
2358
|
+
syms = []Sym{}
|
|
2359
|
+
}
|
|
2360
|
+
data, err := json.Marshal(Result{Symbols: syms, Refs: refs})
|
|
2361
|
+
if err != nil {
|
|
2362
|
+
fmt.Print(emptyResult())
|
|
2363
|
+
return
|
|
2364
|
+
}
|
|
2365
|
+
fmt.Print(string(data))
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
func recvTypeName(t ast.Expr) string {
|
|
2369
|
+
switch v := t.(type) {
|
|
2370
|
+
case *ast.Ident:
|
|
2371
|
+
return v.Name
|
|
2372
|
+
case *ast.StarExpr:
|
|
2373
|
+
return recvTypeName(v.X)
|
|
2374
|
+
default:
|
|
2375
|
+
return "?"
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
func formatFuncSig(d *ast.FuncDecl) string {
|
|
2380
|
+
scope := ""
|
|
2381
|
+
if d.Recv != nil && len(d.Recv.List) > 0 {
|
|
2382
|
+
scope = "(" + formatFieldList(d.Recv.List) + ") "
|
|
2383
|
+
}
|
|
2384
|
+
scope += formatFuncType(d.Type)
|
|
2385
|
+
return "func " + scope
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
func formatFuncType(f *ast.FuncType) string {
|
|
2389
|
+
params := formatFieldList(f.Params.List)
|
|
2390
|
+
results := ""
|
|
2391
|
+
if f.Results != nil {
|
|
2392
|
+
results = " -> " + formatFieldList(f.Results.List)
|
|
2393
|
+
}
|
|
2394
|
+
return params + results
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
func formatFieldList(fields []*ast.Field) string {
|
|
2398
|
+
if len(fields) == 0 {
|
|
2399
|
+
return "()"
|
|
2400
|
+
}
|
|
2401
|
+
names := make([]string, 0, len(fields))
|
|
2402
|
+
for _, f := range fields {
|
|
2403
|
+
name := ""
|
|
2404
|
+
if len(f.Names) > 0 {
|
|
2405
|
+
name = f.Names[0].Name
|
|
2406
|
+
}
|
|
2407
|
+
t := formatType(f.Type)
|
|
2408
|
+
if name != "" {
|
|
2409
|
+
names = append(names, name+" "+t)
|
|
2410
|
+
} else {
|
|
2411
|
+
names = append(names, t)
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
return "(" + strings.Join(names, ", ") + ")"
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
func formatFields(fields []*ast.Field) string {
|
|
2418
|
+
lines := make([]string, 0)
|
|
2419
|
+
for _, f := range fields {
|
|
2420
|
+
name := ""
|
|
2421
|
+
if len(f.Names) > 0 {
|
|
2422
|
+
name = f.Names[0].Name
|
|
2423
|
+
}
|
|
2424
|
+
t := formatType(f.Type)
|
|
2425
|
+
if name != "" {
|
|
2426
|
+
lines = append(lines, name+" "+t)
|
|
2427
|
+
} else {
|
|
2428
|
+
lines = append(lines, t)
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
return strings.Join(lines, "; ")
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
func formatMethods(fields []*ast.Field) string {
|
|
2435
|
+
return formatFields(fields)
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
func formatTypeParams(tp *ast.FieldList) string {
|
|
2439
|
+
if tp == nil || len(tp.List) == 0 {
|
|
2440
|
+
return ""
|
|
2441
|
+
}
|
|
2442
|
+
params := make([]string, len(tp.List))
|
|
2443
|
+
for i, p := range tp.List {
|
|
2444
|
+
if len(p.Names) > 0 {
|
|
2445
|
+
params[i] = p.Names[0].Name
|
|
2446
|
+
} else {
|
|
2447
|
+
params[i] = "T"
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
return "[" + strings.Join(params, ", ") + "]"
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
func formatType(t ast.Expr) string {
|
|
2454
|
+
if t == nil {
|
|
2455
|
+
return "?"
|
|
2456
|
+
}
|
|
2457
|
+
switch v := t.(type) {
|
|
2458
|
+
case *ast.Ident:
|
|
2459
|
+
return v.Name
|
|
2460
|
+
case *ast.SelectorExpr:
|
|
2461
|
+
return formatType(v.X) + "." + v.Sel.Name
|
|
2462
|
+
case *ast.StarExpr:
|
|
2463
|
+
return "*" + formatType(v.X)
|
|
2464
|
+
case *ast.ArrayType:
|
|
2465
|
+
if v.Len == nil {
|
|
2466
|
+
return "[]" + formatType(v.Elt)
|
|
2467
|
+
}
|
|
2468
|
+
return "[...]" + formatType(v.Elt)
|
|
2469
|
+
case *ast.MapType:
|
|
2470
|
+
return "map[" + formatType(v.Key) + "]" + formatType(v.Value)
|
|
2471
|
+
case *ast.InterfaceType:
|
|
2472
|
+
return "interface{}"
|
|
2473
|
+
case *ast.StructType:
|
|
2474
|
+
return "struct{}"
|
|
2475
|
+
case *ast.FuncType:
|
|
2476
|
+
return formatFuncType(v)
|
|
2477
|
+
case *ast.ChanType:
|
|
2478
|
+
return "chan " + formatType(v.Value)
|
|
2479
|
+
case *ast.BasicLit:
|
|
2480
|
+
return v.Value
|
|
2481
|
+
case *ast.IndexExpr:
|
|
2482
|
+
// Generic instantiation with one type arg, e.g. Logger[int].
|
|
2483
|
+
return formatType(v.X) + "[" + formatType(v.Index) + "]"
|
|
2484
|
+
case *ast.IndexListExpr:
|
|
2485
|
+
// Generic instantiation with multiple type args, e.g. Map[K, V].
|
|
2486
|
+
args := make([]string, len(v.Indices))
|
|
2487
|
+
for i, idx := range v.Indices {
|
|
2488
|
+
args[i] = formatType(idx)
|
|
2489
|
+
}
|
|
2490
|
+
return formatType(v.X) + "[" + strings.Join(args, ", ") + "]"
|
|
2491
|
+
default:
|
|
2492
|
+
return "?"
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
1700
2495
|
`;
|
|
1701
|
-
|
|
2496
|
+
_cachedGoScriptPath = null;
|
|
1702
2497
|
}
|
|
1703
2498
|
});
|
|
1704
2499
|
|
|
@@ -1794,7 +2589,7 @@ __export(json_parser_exports, {
|
|
|
1794
2589
|
parseSymbols: () => parseSymbols6
|
|
1795
2590
|
});
|
|
1796
2591
|
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
1797
|
-
import * as
|
|
2592
|
+
import * as path11 from "node:path";
|
|
1798
2593
|
function parseSymbols6(opts) {
|
|
1799
2594
|
const { file, content, lang } = opts;
|
|
1800
2595
|
try {
|
|
@@ -1806,7 +2601,7 @@ function parseSymbols6(opts) {
|
|
|
1806
2601
|
function regexParse2(opts) {
|
|
1807
2602
|
const { file, content, lang } = opts;
|
|
1808
2603
|
const symbols = [];
|
|
1809
|
-
const basename7 =
|
|
2604
|
+
const basename7 = path11.basename(file).toLowerCase();
|
|
1810
2605
|
const isPackageJson = basename7 === "package.json";
|
|
1811
2606
|
const isTsconfig = basename7 === "tsconfig.json" || basename7 === "tsconfig.build.json";
|
|
1812
2607
|
const isJsonSchema = content.includes("$schema") || content.includes("$id") || content.includes("$ref");
|
|
@@ -1832,11 +2627,11 @@ function regexParse2(opts) {
|
|
|
1832
2627
|
const line = lineFromOffset(offset);
|
|
1833
2628
|
symbols.push(
|
|
1834
2629
|
makeSymbol({
|
|
1835
|
-
name:
|
|
2630
|
+
name: path11.basename(file),
|
|
1836
2631
|
kind: "object",
|
|
1837
2632
|
line,
|
|
1838
2633
|
col: 0,
|
|
1839
|
-
signature: `"${
|
|
2634
|
+
signature: `"${path11.basename(file)}" = { ... }`,
|
|
1840
2635
|
file,
|
|
1841
2636
|
lang
|
|
1842
2637
|
})
|
|
@@ -2164,6 +2959,106 @@ var init_yaml_parser = __esm({
|
|
|
2164
2959
|
});
|
|
2165
2960
|
|
|
2166
2961
|
// src/codebase-index/tree-sitter/queries.ts
|
|
2962
|
+
function parseGroupedUse(text) {
|
|
2963
|
+
const open = text.indexOf("{");
|
|
2964
|
+
const close = text.lastIndexOf("}");
|
|
2965
|
+
if (open < 0 || close <= open) return null;
|
|
2966
|
+
const prefix = text.slice(0, open).replace(/[\\/]+$/, "");
|
|
2967
|
+
const out = [];
|
|
2968
|
+
for (const rawMember of text.slice(open + 1, close).split(",")) {
|
|
2969
|
+
let member = rawMember.trim();
|
|
2970
|
+
if (!member) continue;
|
|
2971
|
+
member = member.replace(
|
|
2972
|
+
/^(static|final|type|class|struct|enum|protocol|var|func|let|typealias|function|const)\s+/i,
|
|
2973
|
+
""
|
|
2974
|
+
).trim();
|
|
2975
|
+
if (!member) continue;
|
|
2976
|
+
const aliasMatch = /\s+as\s+([A-Za-z_]\w*)\s*$/i.exec(member);
|
|
2977
|
+
if (aliasMatch) member = member.slice(0, aliasMatch.index).trim();
|
|
2978
|
+
if (!member) continue;
|
|
2979
|
+
const module = prefix ? `${prefix}\\${member}` : member;
|
|
2980
|
+
const toName = member.split(/[\\/]/).filter(Boolean).pop();
|
|
2981
|
+
if (toName) out.push({ toName, callType: "import", module });
|
|
2982
|
+
}
|
|
2983
|
+
return out.length ? out : null;
|
|
2984
|
+
}
|
|
2985
|
+
function importFromText(prefixes) {
|
|
2986
|
+
return (node) => {
|
|
2987
|
+
let text = node.text.replace(/\s+/g, " ").trim();
|
|
2988
|
+
for (const prefix of prefixes) {
|
|
2989
|
+
if (text.startsWith(prefix)) text = text.slice(prefix.length).trim();
|
|
2990
|
+
}
|
|
2991
|
+
text = text.replace(
|
|
2992
|
+
/^(static|final|type|class|struct|enum|protocol|var|func|let|typealias|function|const)\s+/i,
|
|
2993
|
+
""
|
|
2994
|
+
).trim();
|
|
2995
|
+
if (text.includes("{")) return parseGroupedUse(text);
|
|
2996
|
+
if (text.includes(",") && !text.includes("<") && !text.includes("=")) {
|
|
2997
|
+
const out = [];
|
|
2998
|
+
for (const clause of text.split(",")) {
|
|
2999
|
+
const one = oneImportClause(clause.trim());
|
|
3000
|
+
if (one) out.push(one);
|
|
3001
|
+
}
|
|
3002
|
+
return out.length ? out : null;
|
|
3003
|
+
}
|
|
3004
|
+
const single = oneImportClause(text);
|
|
3005
|
+
return single ? [single] : null;
|
|
3006
|
+
};
|
|
3007
|
+
}
|
|
3008
|
+
function oneImportClause(rawClause) {
|
|
3009
|
+
let text = rawClause;
|
|
3010
|
+
text = text.replace(
|
|
3011
|
+
/^(static|final|type|class|struct|enum|protocol|var|func|let|typealias|function|const)\s+/i,
|
|
3012
|
+
""
|
|
3013
|
+
).trim();
|
|
3014
|
+
text = text.replace(/[;}]+$/g, "").trim();
|
|
3015
|
+
const aliasMatch = /\s+as\s+([A-Za-z_]\w*)\s*$/i.exec(text);
|
|
3016
|
+
if (aliasMatch) text = text.slice(0, aliasMatch.index).trim();
|
|
3017
|
+
const eqMatch = /^([A-Za-z_]\w*)\s*=\s*(.+)$/.exec(text);
|
|
3018
|
+
if (eqMatch) text = eqMatch[2].trim();
|
|
3019
|
+
if (!text) return null;
|
|
3020
|
+
if (text.endsWith("*")) text = text.slice(0, -1).replace(/[.]$/, "");
|
|
3021
|
+
if (!text) return null;
|
|
3022
|
+
const module = text;
|
|
3023
|
+
const toName = module.split(/[.\\/]/).filter(Boolean).pop()?.replace(/<.*>$/s, "");
|
|
3024
|
+
if (!toName) return null;
|
|
3025
|
+
return { toName, callType: "import", module };
|
|
3026
|
+
}
|
|
3027
|
+
function heritageLeaf(node, depth) {
|
|
3028
|
+
if (depth > 6) return null;
|
|
3029
|
+
const named = node.childForFieldName("name");
|
|
3030
|
+
if (named) {
|
|
3031
|
+
if (named.type === "scoped_type_identifier" || named.type === "qualified_name" || named.type === "scope_resolution" || named.type === "user_type") {
|
|
3032
|
+
return heritageLeaf(named, depth + 1);
|
|
3033
|
+
}
|
|
3034
|
+
return named.text;
|
|
3035
|
+
}
|
|
3036
|
+
const children = [];
|
|
3037
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3038
|
+
const c = node.namedChild(i);
|
|
3039
|
+
if (c) children.push(c);
|
|
3040
|
+
}
|
|
3041
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
3042
|
+
const c = children[i];
|
|
3043
|
+
if (c.type === "type_arguments" || c.type === "type_argument_list" || // cpp: (template_type arguments: (template_argument_list …)) — the
|
|
3044
|
+
// descriptor's type_identifier inside it is never the declared base.
|
|
3045
|
+
c.type === "template_argument_list" || c.type === "type_parameter_list" || c.type === "type_projection" || c.type === "value_arguments") {
|
|
3046
|
+
continue;
|
|
3047
|
+
}
|
|
3048
|
+
if (c.type === "type_identifier" || c.type === "identifier" || c.type === "constant" || c.type === "name") {
|
|
3049
|
+
return c.text;
|
|
3050
|
+
}
|
|
3051
|
+
if (c.type === "scoped_type_identifier" || c.type === "qualified_name" || c.type === "scope_resolution" || c.type === "user_type") {
|
|
3052
|
+
return heritageLeaf(c, depth + 1);
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
return leafSegment(
|
|
3056
|
+
node.text.replace(/\\/g, ".").replace(/::/g, ".").replace(/<[^<>]*>$/, "")
|
|
3057
|
+
);
|
|
3058
|
+
}
|
|
3059
|
+
function leafSegment(text) {
|
|
3060
|
+
return text.split(".").filter(Boolean).pop() ?? text;
|
|
3061
|
+
}
|
|
2167
3062
|
function getQueries(lang) {
|
|
2168
3063
|
return LANG_QUERIES[lang] ?? DEFAULT_QUERIES;
|
|
2169
3064
|
}
|
|
@@ -2175,10 +3070,127 @@ function readFirstString(node) {
|
|
|
2175
3070
|
const child = node.namedChild(0);
|
|
2176
3071
|
return child ? readFirstString(child) : null;
|
|
2177
3072
|
}
|
|
2178
|
-
var DEFAULT_QUERIES, LANG_QUERIES;
|
|
3073
|
+
var heritageExtractor, cCallExtractor, rubyCallExtractor, firstIdentifierCallExtractor, cIncludeExtractor, phpConstructorExtractor, DEFAULT_QUERIES, LANG_QUERIES;
|
|
2179
3074
|
var init_queries = __esm({
|
|
2180
3075
|
"src/codebase-index/tree-sitter/queries.ts"() {
|
|
2181
3076
|
"use strict";
|
|
3077
|
+
heritageExtractor = (node) => {
|
|
3078
|
+
const out = [];
|
|
3079
|
+
const SKIP_SUBTREES = /* @__PURE__ */ new Set([
|
|
3080
|
+
"type_arguments",
|
|
3081
|
+
"type_argument_list",
|
|
3082
|
+
// tree-sitter-cpp names its argument subtree template_argument_list —
|
|
3083
|
+
// verified AST: (base_class_clause (template_type name:
|
|
3084
|
+
// (type_identifier) arguments: (template_argument_list
|
|
3085
|
+
// (type_descriptor type: (type_identifier))))). Without this entry
|
|
3086
|
+
// `class D : Base<Foo>` recurses into the descriptor and emits Foo as a
|
|
3087
|
+
// phantom inherit ref.
|
|
3088
|
+
"template_argument_list",
|
|
3089
|
+
"type_parameter_list",
|
|
3090
|
+
"type_projection",
|
|
3091
|
+
"value_arguments"
|
|
3092
|
+
]);
|
|
3093
|
+
const collect = (current, depth) => {
|
|
3094
|
+
if (depth > 4) return;
|
|
3095
|
+
for (let i = 0; i < current.namedChildCount; i++) {
|
|
3096
|
+
const child = current.namedChild(i);
|
|
3097
|
+
if (!child) continue;
|
|
3098
|
+
if (SKIP_SUBTREES.has(child.type)) continue;
|
|
3099
|
+
if (child.type === "type_identifier" || child.type === "identifier" || child.type === "named_type" || child.type === "type" || // PHP heritage carries `name`; Ruby a `constant`.
|
|
3100
|
+
child.type === "constant" || child.type === "name") {
|
|
3101
|
+
const name = child.type === "named_type" ? leafSegment(child.text) : child.text;
|
|
3102
|
+
if (name) out.push({ toName: name });
|
|
3103
|
+
continue;
|
|
3104
|
+
}
|
|
3105
|
+
if (child.type === "generic_type" || child.type === "generic_name") {
|
|
3106
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
3107
|
+
const inner = child.namedChild(j);
|
|
3108
|
+
if (inner && !SKIP_SUBTREES.has(inner.type) && (inner.type === "type_identifier" || inner.type === "identifier" || inner.type === "name")) {
|
|
3109
|
+
out.push({ toName: inner.text });
|
|
3110
|
+
break;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
continue;
|
|
3114
|
+
}
|
|
3115
|
+
if (child.type === "qualified_name" || child.type === "scoped_type_identifier" || child.type === "user_type" || child.type === "scope_resolution") {
|
|
3116
|
+
const leaf = heritageLeaf(child, 0);
|
|
3117
|
+
if (leaf) out.push({ toName: leaf });
|
|
3118
|
+
continue;
|
|
3119
|
+
}
|
|
3120
|
+
collect(child, depth + 1);
|
|
3121
|
+
}
|
|
3122
|
+
};
|
|
3123
|
+
collect(node, 0);
|
|
3124
|
+
return out;
|
|
3125
|
+
};
|
|
3126
|
+
cCallExtractor = (node) => {
|
|
3127
|
+
const fn = node.childForFieldName("function");
|
|
3128
|
+
if (!fn) return null;
|
|
3129
|
+
if (fn.type === "field_expression") {
|
|
3130
|
+
const field = fn.childForFieldName("field");
|
|
3131
|
+
if (field) return [{ toName: field.text, callType: "call" }];
|
|
3132
|
+
const seg = fn.text.split("->").filter(Boolean).pop();
|
|
3133
|
+
if (seg) return [{ toName: leafSegment(seg.split(".")[0] ?? seg), callType: "call" }];
|
|
3134
|
+
return null;
|
|
3135
|
+
}
|
|
3136
|
+
if (fn.type === "qualified_identifier") {
|
|
3137
|
+
const name = fn.childForFieldName("name");
|
|
3138
|
+
if (name) return [{ toName: name.text, callType: "call" }];
|
|
3139
|
+
const seg = fn.text.split("::").filter(Boolean).pop();
|
|
3140
|
+
if (seg) return [{ toName: seg.split(/[<(]/)[0].trim(), callType: "call" }];
|
|
3141
|
+
return null;
|
|
3142
|
+
}
|
|
3143
|
+
return [{ toName: fn.text.split(/[<(]/)[0].trim(), callType: "call" }];
|
|
3144
|
+
};
|
|
3145
|
+
rubyCallExtractor = (node) => {
|
|
3146
|
+
const emissions = [];
|
|
3147
|
+
const method = node.childForFieldName("method");
|
|
3148
|
+
if (method) {
|
|
3149
|
+
const name = method.text;
|
|
3150
|
+
if (name && !name.includes(" ")) emissions.push({ toName: name, callType: "call" });
|
|
3151
|
+
if (name === "require" || name === "require_relative") {
|
|
3152
|
+
const args = node.childForFieldName("arguments");
|
|
3153
|
+
const first = args?.namedChild(0);
|
|
3154
|
+
if (first) {
|
|
3155
|
+
const raw = first.text.replace(/^['"]|['"]$/g, "");
|
|
3156
|
+
const toName = raw.split("/").filter(Boolean).pop();
|
|
3157
|
+
if (toName) emissions.push({ toName, callType: "import", module: raw });
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
return emissions;
|
|
3162
|
+
};
|
|
3163
|
+
firstIdentifierCallExtractor = (node) => {
|
|
3164
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3165
|
+
const child = node.namedChild(i);
|
|
3166
|
+
if (child && (child.type === "simple_identifier" || child.type === "identifier")) {
|
|
3167
|
+
return [{ toName: child.text, callType: "call" }];
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
const first = node.namedChild(0);
|
|
3171
|
+
if (!first) return null;
|
|
3172
|
+
const leaf = leafSegment(first.text.split(/[<(]/)[0] ?? first.text);
|
|
3173
|
+
if (!leaf) return null;
|
|
3174
|
+
return [{ toName: leaf, callType: "call" }];
|
|
3175
|
+
};
|
|
3176
|
+
cIncludeExtractor = (node) => {
|
|
3177
|
+
const raw = node.text.replace(/^#\s*include\s*/i, "").trim();
|
|
3178
|
+
const module = raw.replace(/^["'<]|["'>]$/g, "");
|
|
3179
|
+
if (!module) return null;
|
|
3180
|
+
const toName = module.split("/").pop()?.replace(/\.h$/, "");
|
|
3181
|
+
if (!toName) return null;
|
|
3182
|
+
return [{ toName, callType: "import", module }];
|
|
3183
|
+
};
|
|
3184
|
+
phpConstructorExtractor = (node) => {
|
|
3185
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3186
|
+
const child = node.namedChild(i);
|
|
3187
|
+
if (child && (child.type === "qualified_name" || child.type === "name")) {
|
|
3188
|
+
const leaf = child.text.split(/[\\]/).filter(Boolean).pop();
|
|
3189
|
+
if (leaf) return [{ toName: leaf, callType: "call" }];
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
return null;
|
|
3193
|
+
};
|
|
2182
3194
|
DEFAULT_QUERIES = {
|
|
2183
3195
|
declKinds: {}
|
|
2184
3196
|
};
|
|
@@ -2211,7 +3223,13 @@ var init_queries = __esm({
|
|
|
2211
3223
|
"struct_specifier",
|
|
2212
3224
|
"union_specifier",
|
|
2213
3225
|
"enum_specifier"
|
|
2214
|
-
])
|
|
3226
|
+
]),
|
|
3227
|
+
refRules: {
|
|
3228
|
+
// `obj->run()` and `Cls::stat()` carry structured function fields —
|
|
3229
|
+
// cCallExtractor handles all three AST shapes.
|
|
3230
|
+
call_expression: { callType: "call", nameExtractor: cCallExtractor },
|
|
3231
|
+
preproc_include: { callType: "import", nameExtractor: cIncludeExtractor }
|
|
3232
|
+
}
|
|
2215
3233
|
},
|
|
2216
3234
|
cpp: {
|
|
2217
3235
|
declKinds: {
|
|
@@ -2242,7 +3260,13 @@ var init_queries = __esm({
|
|
|
2242
3260
|
"union_specifier",
|
|
2243
3261
|
"enum_specifier",
|
|
2244
3262
|
"namespace_definition"
|
|
2245
|
-
])
|
|
3263
|
+
]),
|
|
3264
|
+
refRules: {
|
|
3265
|
+
call_expression: { callType: "call", nameExtractor: cCallExtractor },
|
|
3266
|
+
preproc_include: { callType: "import", nameExtractor: cIncludeExtractor },
|
|
3267
|
+
// `class Foo : public Bar, private Baz` — the base-class clause.
|
|
3268
|
+
base_class_clause: { callType: "inherit", nameExtractor: heritageExtractor }
|
|
3269
|
+
}
|
|
2246
3270
|
},
|
|
2247
3271
|
java: {
|
|
2248
3272
|
declKinds: {
|
|
@@ -2277,7 +3301,22 @@ var init_queries = __esm({
|
|
|
2277
3301
|
"interface_declaration",
|
|
2278
3302
|
"enum_declaration",
|
|
2279
3303
|
"record_declaration"
|
|
2280
|
-
])
|
|
3304
|
+
]),
|
|
3305
|
+
refRules: {
|
|
3306
|
+
method_invocation: { callType: "call", field: "name" },
|
|
3307
|
+
object_creation_expression: { callType: "call", field: "type" },
|
|
3308
|
+
// Verified AST: `superclass: (superclass (type_identifier))` and
|
|
3309
|
+
// `interfaces: (super_interfaces (type_list ...))` — no underscores.
|
|
3310
|
+
superclass: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3311
|
+
super_interfaces: {
|
|
3312
|
+
callType: "implement",
|
|
3313
|
+
nameExtractor: heritageExtractor
|
|
3314
|
+
},
|
|
3315
|
+
import_declaration: {
|
|
3316
|
+
callType: "import",
|
|
3317
|
+
nameExtractor: importFromText(["import "])
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
2281
3320
|
},
|
|
2282
3321
|
csharp: {
|
|
2283
3322
|
// C# 10+ `namespace Foo.Bar;` produces this node type. The legacy block
|
|
@@ -2315,7 +3354,18 @@ var init_queries = __esm({
|
|
|
2315
3354
|
"struct_declaration",
|
|
2316
3355
|
"enum_declaration",
|
|
2317
3356
|
"record_declaration"
|
|
2318
|
-
])
|
|
3357
|
+
]),
|
|
3358
|
+
refRules: {
|
|
3359
|
+
// Verified AST: `invocation_expression function: (identifier)` — the
|
|
3360
|
+
// callee field is `function` (C-style), not `name`.
|
|
3361
|
+
invocation_expression: { callType: "call", field: "function" },
|
|
3362
|
+
object_creation_expression: { callType: "call", field: "type" },
|
|
3363
|
+
base_list: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3364
|
+
using_directive: {
|
|
3365
|
+
callType: "import",
|
|
3366
|
+
nameExtractor: importFromText(["using "])
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
2319
3369
|
},
|
|
2320
3370
|
php: {
|
|
2321
3371
|
declKinds: {
|
|
@@ -2334,7 +3384,7 @@ var init_queries = __esm({
|
|
|
2334
3384
|
interface_declaration: "name",
|
|
2335
3385
|
trait_declaration: "name",
|
|
2336
3386
|
enum_declaration: "name",
|
|
2337
|
-
|
|
3387
|
+
namespace_definition: "name"
|
|
2338
3388
|
},
|
|
2339
3389
|
scopeNodes: /* @__PURE__ */ new Set([
|
|
2340
3390
|
"program",
|
|
@@ -2343,7 +3393,24 @@ var init_queries = __esm({
|
|
|
2343
3393
|
"interface_declaration",
|
|
2344
3394
|
"trait_declaration",
|
|
2345
3395
|
"enum_declaration"
|
|
2346
|
-
])
|
|
3396
|
+
]),
|
|
3397
|
+
refRules: {
|
|
3398
|
+
function_call_expression: { callType: "call", field: "function" },
|
|
3399
|
+
// Verified AST: `new App\Model\User()` carries a BARE qualified_name
|
|
3400
|
+
// child (no `name:` field), so the field default never fires.
|
|
3401
|
+
object_creation_expression: { callType: "call", nameExtractor: phpConstructorExtractor },
|
|
3402
|
+
base_clause: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3403
|
+
class_interface_clause: {
|
|
3404
|
+
callType: "implement",
|
|
3405
|
+
nameExtractor: heritageExtractor
|
|
3406
|
+
},
|
|
3407
|
+
// Verified AST: `namespace_use_declaration (namespace_use_clause
|
|
3408
|
+
// (qualified_name ...))` — not `use_declaration`.
|
|
3409
|
+
namespace_use_declaration: {
|
|
3410
|
+
callType: "import",
|
|
3411
|
+
nameExtractor: importFromText(["use "])
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
2347
3414
|
},
|
|
2348
3415
|
// ─── Scripting / mobile ────────────────────────────────────────────────────
|
|
2349
3416
|
ruby: {
|
|
@@ -2361,7 +3428,13 @@ var init_queries = __esm({
|
|
|
2361
3428
|
module: "name",
|
|
2362
3429
|
constant: "name"
|
|
2363
3430
|
},
|
|
2364
|
-
scopeNodes: /* @__PURE__ */ new Set(["program", "class", "module", "singleton_method", "method"])
|
|
3431
|
+
scopeNodes: /* @__PURE__ */ new Set(["program", "class", "module", "singleton_method", "method"]),
|
|
3432
|
+
refRules: {
|
|
3433
|
+
// `call` covers both `foo(...)` and `obj.foo(...)` — the extractor
|
|
3434
|
+
// records the method leaf, plus `require`/`require_relative` imports.
|
|
3435
|
+
call: { callType: "call", nameExtractor: rubyCallExtractor },
|
|
3436
|
+
superclass: { callType: "inherit", nameExtractor: heritageExtractor }
|
|
3437
|
+
}
|
|
2365
3438
|
},
|
|
2366
3439
|
swift: {
|
|
2367
3440
|
declKinds: {
|
|
@@ -2394,7 +3467,18 @@ var init_queries = __esm({
|
|
|
2394
3467
|
"protocol_declaration",
|
|
2395
3468
|
"actor_declaration",
|
|
2396
3469
|
"extension_declaration"
|
|
2397
|
-
])
|
|
3470
|
+
]),
|
|
3471
|
+
refRules: {
|
|
3472
|
+
// Verified AST: `call_expression (simple_identifier) (call_suffix …)` —
|
|
3473
|
+
// the callee is a bare first child, no field name.
|
|
3474
|
+
call_expression: { callType: "call", nameExtractor: firstIdentifierCallExtractor },
|
|
3475
|
+
// Verified AST: `inheritance_specifier inherits_from: (user_type …)`.
|
|
3476
|
+
inheritance_specifier: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3477
|
+
import_declaration: {
|
|
3478
|
+
callType: "import",
|
|
3479
|
+
nameExtractor: importFromText(["import ", "import type ", "@testable import "])
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
2398
3482
|
},
|
|
2399
3483
|
kotlin: {
|
|
2400
3484
|
declKinds: {
|
|
@@ -2419,7 +3503,17 @@ var init_queries = __esm({
|
|
|
2419
3503
|
"object_declaration",
|
|
2420
3504
|
"interface_declaration",
|
|
2421
3505
|
"function_declaration"
|
|
2422
|
-
])
|
|
3506
|
+
]),
|
|
3507
|
+
refRules: {
|
|
3508
|
+
call_expression: { callType: "call", nameExtractor: firstIdentifierCallExtractor },
|
|
3509
|
+
// Verified AST: `delegation_specifier (user_type (type_identifier))` —
|
|
3510
|
+
// the `: Handler` / `: Base()` clause.
|
|
3511
|
+
delegation_specifier: { callType: "inherit", nameExtractor: heritageExtractor },
|
|
3512
|
+
import_header: {
|
|
3513
|
+
callType: "import",
|
|
3514
|
+
nameExtractor: importFromText(["import "])
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
2423
3517
|
},
|
|
2424
3518
|
elixir: {
|
|
2425
3519
|
declKinds: {
|
|
@@ -2491,7 +3585,38 @@ function visitTree(tree, content, file, lang, queries) {
|
|
|
2491
3585
|
const boundedContent = content.length > TREE_SITTER_MAX_FILE_CHARS ? content.slice(0, TREE_SITTER_MAX_FILE_CHARS) : content;
|
|
2492
3586
|
const nlOffsets = newlineOffsets3(boundedContent);
|
|
2493
3587
|
const symbols = [];
|
|
3588
|
+
const refs = [];
|
|
3589
|
+
const seenRefs = /* @__PURE__ */ new Set();
|
|
2494
3590
|
const scopeStack = [];
|
|
3591
|
+
function emitRefsForNode(node, rule) {
|
|
3592
|
+
const emissions = rule.nameExtractor?.(node) ?? defaultRefTarget(node, rule);
|
|
3593
|
+
if (!emissions) return;
|
|
3594
|
+
const { line } = lineColAt2(nlOffsets, node.startIndex);
|
|
3595
|
+
for (const emission of emissions) {
|
|
3596
|
+
if (!emission.toName) continue;
|
|
3597
|
+
const callType = emission.callType ?? rule.callType;
|
|
3598
|
+
const key = `${emission.toName}:${callType}:${line}:${emission.module ?? ""}:${node.startIndex}`;
|
|
3599
|
+
if (seenRefs.has(key)) continue;
|
|
3600
|
+
seenRefs.add(key);
|
|
3601
|
+
refs.push({
|
|
3602
|
+
fromId: 0,
|
|
3603
|
+
// assignRefsToSymbols attaches owners after insertion
|
|
3604
|
+
toName: emission.toName.slice(0, 200),
|
|
3605
|
+
callType,
|
|
3606
|
+
line,
|
|
3607
|
+
lang,
|
|
3608
|
+
module: emission.module
|
|
3609
|
+
});
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
function defaultRefTarget(node, rule) {
|
|
3613
|
+
if (!rule.field) return null;
|
|
3614
|
+
const field = node.childForFieldName(rule.field);
|
|
3615
|
+
if (!field) return null;
|
|
3616
|
+
const leaf = field.text.split(/[.:\\]/).filter(Boolean).pop()?.split(/[<(]/)[0];
|
|
3617
|
+
if (!leaf) return null;
|
|
3618
|
+
return [{ toName: leaf.trim() }];
|
|
3619
|
+
}
|
|
2495
3620
|
function visit(node, depth) {
|
|
2496
3621
|
if (symbols.length >= TREE_SITTER_MAX_SYMBOLS) return;
|
|
2497
3622
|
if (node.isMissing || node.isError) {
|
|
@@ -2510,6 +3635,8 @@ function visitTree(tree, content, file, lang, queries) {
|
|
|
2510
3635
|
);
|
|
2511
3636
|
if (emitted) symbols.push(emitted);
|
|
2512
3637
|
}
|
|
3638
|
+
const refRule = queries.refRules?.[node.type];
|
|
3639
|
+
if (refRule) emitRefsForNode(node, refRule);
|
|
2513
3640
|
}
|
|
2514
3641
|
const pushesScope = queries.scopeNodes?.has(node.type) ?? false;
|
|
2515
3642
|
const pushIdx = pushesScope ? pushScope(scopeStack, node, queries) : -1;
|
|
@@ -2527,7 +3654,7 @@ function visitTree(tree, content, file, lang, queries) {
|
|
|
2527
3654
|
if (pushIdx !== -1) scopeStack.pop();
|
|
2528
3655
|
}
|
|
2529
3656
|
visit(tree.rootNode, 0);
|
|
2530
|
-
return { symbols };
|
|
3657
|
+
return { symbols, refs };
|
|
2531
3658
|
}
|
|
2532
3659
|
function pushScope(scopeStack, node, queries) {
|
|
2533
3660
|
const name = extractName(node, queries);
|
|
@@ -2614,7 +3741,7 @@ __export(tree_sitter_parser_exports, {
|
|
|
2614
3741
|
parseSymbols: () => parseSymbols8,
|
|
2615
3742
|
parseTreeSitterAst: () => parseTreeSitterAst
|
|
2616
3743
|
});
|
|
2617
|
-
import * as
|
|
3744
|
+
import * as path12 from "node:path";
|
|
2618
3745
|
import { fileURLToPath } from "node:url";
|
|
2619
3746
|
function optInEnabled(env) {
|
|
2620
3747
|
return process.env[env] === "1" || process.env[env] === "true";
|
|
@@ -2637,7 +3764,7 @@ async function loadLanguage(lang) {
|
|
|
2637
3764
|
if (!grammarName) {
|
|
2638
3765
|
throw new Error(`tree-sitter: no grammar registered for lang "${lang}"`);
|
|
2639
3766
|
}
|
|
2640
|
-
const wasmPath =
|
|
3767
|
+
const wasmPath = path12.join(WASM_DIR, grammarName, `tree-sitter-${grammarName}.wasm`);
|
|
2641
3768
|
const { Language, init } = await getRuntime();
|
|
2642
3769
|
await init();
|
|
2643
3770
|
const languageObj = await Language.load(wasmPath);
|
|
@@ -2658,7 +3785,7 @@ function isTreeSitterSupported(lang) {
|
|
|
2658
3785
|
function getGrammarWasmPath(lang) {
|
|
2659
3786
|
const name = resolveGrammarName(lang);
|
|
2660
3787
|
if (!name) return void 0;
|
|
2661
|
-
return
|
|
3788
|
+
return path12.join(WASM_DIR, name, `tree-sitter-${name}.wasm`);
|
|
2662
3789
|
}
|
|
2663
3790
|
async function parseSymbols8(opts) {
|
|
2664
3791
|
const { file, content, lang } = opts;
|
|
@@ -2674,89 +3801,229 @@ async function parseSymbols8(opts) {
|
|
|
2674
3801
|
if (!tree) {
|
|
2675
3802
|
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
2676
3803
|
}
|
|
2677
|
-
const { symbols } = visitTree(tree, content, file, lang, getQueries(lang));
|
|
2678
|
-
parser.delete();
|
|
2679
|
-
tree.delete();
|
|
2680
|
-
return { file, lang, symbols, refs
|
|
2681
|
-
} catch {
|
|
2682
|
-
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
2683
|
-
}
|
|
2684
|
-
}
|
|
2685
|
-
async function loadTreeSitterLanguage(lang) {
|
|
2686
|
-
const cached = await loadLanguage(lang);
|
|
2687
|
-
return cached.Language;
|
|
2688
|
-
}
|
|
2689
|
-
async function __smokeRootType(opts) {
|
|
2690
|
-
if (!isTreeSitterSupported(opts.lang)) {
|
|
2691
|
-
throw new Error(`tree-sitter: no grammar registered for lang "${opts.lang}"`);
|
|
2692
|
-
}
|
|
2693
|
-
const { Parser } = await getRuntime();
|
|
2694
|
-
const cached = await loadLanguage(opts.lang);
|
|
2695
|
-
const parser = new Parser();
|
|
2696
|
-
parser.setLanguage(cached.Language);
|
|
2697
|
-
let tree = null;
|
|
2698
|
-
try {
|
|
2699
|
-
tree = parser.parse(opts.content);
|
|
2700
|
-
if (!tree) throw new Error("tree-sitter: parser.parse returned null");
|
|
2701
|
-
return tree.rootNode.type;
|
|
2702
|
-
} finally {
|
|
2703
|
-
tree?.delete();
|
|
2704
|
-
parser.delete();
|
|
3804
|
+
const { symbols, refs } = visitTree(tree, content, file, lang, getQueries(lang));
|
|
3805
|
+
parser.delete();
|
|
3806
|
+
tree.delete();
|
|
3807
|
+
return { file, lang, symbols, refs, mtimeMs: Date.now() };
|
|
3808
|
+
} catch {
|
|
3809
|
+
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
async function loadTreeSitterLanguage(lang) {
|
|
3813
|
+
const cached = await loadLanguage(lang);
|
|
3814
|
+
return cached.Language;
|
|
3815
|
+
}
|
|
3816
|
+
async function __smokeRootType(opts) {
|
|
3817
|
+
if (!isTreeSitterSupported(opts.lang)) {
|
|
3818
|
+
throw new Error(`tree-sitter: no grammar registered for lang "${opts.lang}"`);
|
|
3819
|
+
}
|
|
3820
|
+
const { Parser } = await getRuntime();
|
|
3821
|
+
const cached = await loadLanguage(opts.lang);
|
|
3822
|
+
const parser = new Parser();
|
|
3823
|
+
parser.setLanguage(cached.Language);
|
|
3824
|
+
let tree = null;
|
|
3825
|
+
try {
|
|
3826
|
+
tree = parser.parse(opts.content);
|
|
3827
|
+
if (!tree) throw new Error("tree-sitter: parser.parse returned null");
|
|
3828
|
+
return tree.rootNode.type;
|
|
3829
|
+
} finally {
|
|
3830
|
+
tree?.delete();
|
|
3831
|
+
parser.delete();
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
async function parseTreeSitterAst(opts) {
|
|
3835
|
+
const grammar = resolveGrammarName(opts.lang) ?? (opts.lang === "go" ? "go" : opts.lang === "py" ? "python" : opts.lang === "rs" ? "rust" : void 0);
|
|
3836
|
+
if (!grammar) return null;
|
|
3837
|
+
try {
|
|
3838
|
+
const { Parser, Language, init } = await getRuntime();
|
|
3839
|
+
await init();
|
|
3840
|
+
const wasmPath = path12.join(WASM_DIR, grammar, `tree-sitter-${grammar}.wasm`);
|
|
3841
|
+
const languageObj = await Language.load(wasmPath);
|
|
3842
|
+
const parser = new Parser();
|
|
3843
|
+
parser.setLanguage(languageObj);
|
|
3844
|
+
const tree = parser.parse(opts.content);
|
|
3845
|
+
if (!tree) {
|
|
3846
|
+
parser.delete();
|
|
3847
|
+
return null;
|
|
3848
|
+
}
|
|
3849
|
+
return { tree, parser };
|
|
3850
|
+
} catch {
|
|
3851
|
+
return null;
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
var WASM_DIR, RUNTIME_WASM, LANG_TO_GRAMMAR, GO_OPT_IN, PY_OPT_IN, RS_OPT_IN, runtimePromise, languageCache;
|
|
3855
|
+
var init_tree_sitter_parser = __esm({
|
|
3856
|
+
"src/codebase-index/tree-sitter-parser.ts"() {
|
|
3857
|
+
"use strict";
|
|
3858
|
+
init_queries();
|
|
3859
|
+
init_visitor();
|
|
3860
|
+
WASM_DIR = fileURLToPath(new URL("./wasm/", import.meta.url));
|
|
3861
|
+
RUNTIME_WASM = path12.join(WASM_DIR, "tree-sitter-runtime.wasm");
|
|
3862
|
+
LANG_TO_GRAMMAR = {
|
|
3863
|
+
c: "c",
|
|
3864
|
+
cpp: "cpp",
|
|
3865
|
+
java: "java",
|
|
3866
|
+
csharp: "c_sharp",
|
|
3867
|
+
// tree-sitter directory uses underscore
|
|
3868
|
+
php: "php",
|
|
3869
|
+
ruby: "ruby",
|
|
3870
|
+
swift: "swift",
|
|
3871
|
+
kotlin: "kotlin",
|
|
3872
|
+
shell: "bash",
|
|
3873
|
+
// we treat `.sh` / `.bash` / `.zsh` via the bash grammar
|
|
3874
|
+
// Go/Python/Rust are opt-in only (see goOptIn / pyOptIn / rsOptIn below)
|
|
3875
|
+
elixir: "elixir"
|
|
3876
|
+
};
|
|
3877
|
+
GO_OPT_IN = "WRONGSTACK_USE_TS_GO";
|
|
3878
|
+
PY_OPT_IN = "WRONGSTACK_USE_TS_PY";
|
|
3879
|
+
RS_OPT_IN = "WRONGSTACK_USE_TS_RS";
|
|
3880
|
+
runtimePromise = null;
|
|
3881
|
+
languageCache = /* @__PURE__ */ new Map();
|
|
3882
|
+
}
|
|
3883
|
+
});
|
|
3884
|
+
|
|
3885
|
+
// src/codebase-index/parser-dispatch.ts
|
|
3886
|
+
var parser_dispatch_exports = {};
|
|
3887
|
+
__export(parser_dispatch_exports, {
|
|
3888
|
+
parseFileContent: () => parseFileContent,
|
|
3889
|
+
parseFilesContent: () => parseFilesContent
|
|
3890
|
+
});
|
|
3891
|
+
async function parseFileContent(file, content, lang) {
|
|
3892
|
+
const parsed = await dispatch(file, content, lang);
|
|
3893
|
+
return withRelations(parsed, content, lang);
|
|
3894
|
+
}
|
|
3895
|
+
async function parseFilesContent(files) {
|
|
3896
|
+
if (files.length === 0) return [];
|
|
3897
|
+
const slots = files.map(() => ({ result: null }));
|
|
3898
|
+
const batchingEnabled = process.env["WRONGSTACK_TOOLCHAIN_BATCH"] !== "0";
|
|
3899
|
+
if (batchingEnabled) {
|
|
3900
|
+
const goFiles = [];
|
|
3901
|
+
const pyFiles = [];
|
|
3902
|
+
files.forEach((f, index) => {
|
|
3903
|
+
if (f.lang === "go") goFiles.push({ ...f, index });
|
|
3904
|
+
else if (f.lang === "py") pyFiles.push({ ...f, index });
|
|
3905
|
+
});
|
|
3906
|
+
if (goFiles.length > 0) {
|
|
3907
|
+
await applyBatchResults(slots, goFiles, (chunks) => runGoBatch(chunks), "go");
|
|
3908
|
+
}
|
|
3909
|
+
if (pyFiles.length > 0) {
|
|
3910
|
+
const pyBinary = await resolvePythonBinary();
|
|
3911
|
+
if (pyBinary) {
|
|
3912
|
+
await applyBatchResults(slots, pyFiles, (chunks) => runPyBatch(chunks, pyBinary), "py");
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
const jobs = [];
|
|
3917
|
+
for (let i = 0; i < files.length; i++) {
|
|
3918
|
+
if (slots[i].result !== null) continue;
|
|
3919
|
+
const { file, content, lang } = files[i];
|
|
3920
|
+
const slot = slots[i];
|
|
3921
|
+
jobs.push(
|
|
3922
|
+
(async () => {
|
|
3923
|
+
try {
|
|
3924
|
+
slot.result = await parseFileContent(file, content, lang);
|
|
3925
|
+
} catch (err) {
|
|
3926
|
+
slot.error = err instanceof Error ? err.message : String(err);
|
|
3927
|
+
}
|
|
3928
|
+
})()
|
|
3929
|
+
);
|
|
3930
|
+
}
|
|
3931
|
+
await Promise.all(jobs);
|
|
3932
|
+
return slots;
|
|
3933
|
+
}
|
|
3934
|
+
async function applyBatchResults(slots, batchFiles, runBatch, lang) {
|
|
3935
|
+
for (const chunk of chunkBatchFiles(batchFiles)) {
|
|
3936
|
+
let byFile = null;
|
|
3937
|
+
try {
|
|
3938
|
+
byFile = await runBatch(chunk);
|
|
3939
|
+
} catch {
|
|
3940
|
+
byFile = null;
|
|
3941
|
+
}
|
|
3942
|
+
if (!byFile) continue;
|
|
3943
|
+
for (const item of chunk) {
|
|
3944
|
+
const parsed = byFile.get(item.file);
|
|
3945
|
+
if (!parsed) continue;
|
|
3946
|
+
slots[item.index] = { result: withRelations(parsed, item.content, lang) };
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
async function dispatch(file, content, lang) {
|
|
3951
|
+
switch (lang) {
|
|
3952
|
+
case "ts":
|
|
3953
|
+
case "tsx":
|
|
3954
|
+
case "js":
|
|
3955
|
+
case "jsx": {
|
|
3956
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
|
|
3957
|
+
return parseSymbols9({ file, content, lang });
|
|
3958
|
+
}
|
|
3959
|
+
case "go": {
|
|
3960
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
|
|
3961
|
+
return parseSymbols9({ file, content, lang: "go" });
|
|
3962
|
+
}
|
|
3963
|
+
case "py": {
|
|
3964
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
|
|
3965
|
+
return parseSymbols9({ file, content, lang: "py" });
|
|
3966
|
+
}
|
|
3967
|
+
case "rs": {
|
|
3968
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
|
|
3969
|
+
return parseSymbols9({ file, content, lang: "rs" });
|
|
3970
|
+
}
|
|
3971
|
+
case "json": {
|
|
3972
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
|
|
3973
|
+
return parseSymbols9({ file, content, lang: "json" });
|
|
3974
|
+
}
|
|
3975
|
+
case "yaml": {
|
|
3976
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_yaml_parser(), yaml_parser_exports));
|
|
3977
|
+
return parseSymbols9({ file, content, lang: "yaml" });
|
|
3978
|
+
}
|
|
3979
|
+
// Phase 1: ten languages now route through the Tree-Sitter WASM
|
|
3980
|
+
// universal parser (`tree-sitter-parser.ts`). The dispatch falls back to
|
|
3981
|
+
// the regex extractor in `generic-parser.ts` whenever WASM loading fails
|
|
3982
|
+
// or the parser returns zero symbols — preserving the indexable-file
|
|
3983
|
+
// contract that "missing a parser must never mean skipping the file".
|
|
3984
|
+
case "c":
|
|
3985
|
+
case "cpp":
|
|
3986
|
+
case "java":
|
|
3987
|
+
case "csharp":
|
|
3988
|
+
case "php":
|
|
3989
|
+
case "ruby":
|
|
3990
|
+
case "swift":
|
|
3991
|
+
case "kotlin":
|
|
3992
|
+
case "shell":
|
|
3993
|
+
case "elixir": {
|
|
3994
|
+
try {
|
|
3995
|
+
const { parseSymbols: parseSymbols10 } = await Promise.resolve().then(() => (init_tree_sitter_parser(), tree_sitter_parser_exports));
|
|
3996
|
+
const parsed = await parseSymbols10({ file, content, lang });
|
|
3997
|
+
if (parsed.symbols.length > 0) return parsed;
|
|
3998
|
+
} catch {
|
|
3999
|
+
}
|
|
4000
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
4001
|
+
return parseSymbols9({ file, content, lang });
|
|
4002
|
+
}
|
|
4003
|
+
default: {
|
|
4004
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
4005
|
+
return parseSymbols9({ file, content, lang });
|
|
4006
|
+
}
|
|
2705
4007
|
}
|
|
2706
4008
|
}
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
if (
|
|
2710
|
-
|
|
2711
|
-
const { Parser, Language, init } = await getRuntime();
|
|
2712
|
-
await init();
|
|
2713
|
-
const wasmPath = path11.join(WASM_DIR, grammar, `tree-sitter-${grammar}.wasm`);
|
|
2714
|
-
const languageObj = await Language.load(wasmPath);
|
|
2715
|
-
const parser = new Parser();
|
|
2716
|
-
parser.setLanguage(languageObj);
|
|
2717
|
-
const tree = parser.parse(opts.content);
|
|
2718
|
-
if (!tree) {
|
|
2719
|
-
parser.delete();
|
|
2720
|
-
return null;
|
|
2721
|
-
}
|
|
2722
|
-
return { tree, parser };
|
|
2723
|
-
} catch {
|
|
2724
|
-
return null;
|
|
4009
|
+
function withRelations(parsed, content, lang) {
|
|
4010
|
+
let refs = parsed.refs ?? [];
|
|
4011
|
+
if (refs.length === 0 && hasImportPatterns(lang)) {
|
|
4012
|
+
refs = extractImports({ content, lang });
|
|
2725
4013
|
}
|
|
4014
|
+
return { ...parsed, refs: refs.map((ref) => ref.lang ? ref : { ...ref, lang }) };
|
|
2726
4015
|
}
|
|
2727
|
-
var
|
|
2728
|
-
|
|
2729
|
-
"src/codebase-index/tree-sitter-parser.ts"() {
|
|
4016
|
+
var init_parser_dispatch = __esm({
|
|
4017
|
+
"src/codebase-index/parser-dispatch.ts"() {
|
|
2730
4018
|
"use strict";
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
RUNTIME_WASM = path11.join(WASM_DIR, "tree-sitter-runtime.wasm");
|
|
2735
|
-
LANG_TO_GRAMMAR = {
|
|
2736
|
-
c: "c",
|
|
2737
|
-
cpp: "cpp",
|
|
2738
|
-
java: "java",
|
|
2739
|
-
csharp: "c_sharp",
|
|
2740
|
-
// tree-sitter directory uses underscore
|
|
2741
|
-
php: "php",
|
|
2742
|
-
ruby: "ruby",
|
|
2743
|
-
swift: "swift",
|
|
2744
|
-
kotlin: "kotlin",
|
|
2745
|
-
shell: "bash",
|
|
2746
|
-
// we treat `.sh` / `.bash` / `.zsh` via the bash grammar
|
|
2747
|
-
// Go/Python/Rust are opt-in only (see goOptIn / pyOptIn / rsOptIn below)
|
|
2748
|
-
elixir: "elixir"
|
|
2749
|
-
};
|
|
2750
|
-
GO_OPT_IN = "WRONGSTACK_USE_TS_GO";
|
|
2751
|
-
PY_OPT_IN = "WRONGSTACK_USE_TS_PY";
|
|
2752
|
-
RS_OPT_IN = "WRONGSTACK_USE_TS_RS";
|
|
2753
|
-
runtimePromise = null;
|
|
2754
|
-
languageCache = /* @__PURE__ */ new Map();
|
|
4019
|
+
init_import_extractor();
|
|
4020
|
+
init_parser_batch();
|
|
4021
|
+
init_py_parser();
|
|
2755
4022
|
}
|
|
2756
4023
|
});
|
|
2757
4024
|
|
|
2758
4025
|
// src/edit.ts
|
|
2759
|
-
import * as
|
|
4026
|
+
import * as fs15 from "node:fs/promises";
|
|
2760
4027
|
import {
|
|
2761
4028
|
atomicWrite,
|
|
2762
4029
|
detectNewlineStyle,
|
|
@@ -3155,7 +4422,7 @@ function takeHeadBytes(s, maxBytes) {
|
|
|
3155
4422
|
}
|
|
3156
4423
|
|
|
3157
4424
|
// src/codebase-index/background-indexer.ts
|
|
3158
|
-
import * as
|
|
4425
|
+
import * as fs14 from "node:fs";
|
|
3159
4426
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
3160
4427
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
3161
4428
|
|
|
@@ -3243,9 +4510,9 @@ var indexCircuitBreaker = new IndexCircuitBreaker();
|
|
|
3243
4510
|
import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
|
|
3244
4511
|
import { execFile } from "node:child_process";
|
|
3245
4512
|
import { createHash as createHash2 } from "node:crypto";
|
|
3246
|
-
import * as
|
|
4513
|
+
import * as fs10 from "node:fs/promises";
|
|
3247
4514
|
import { availableParallelism } from "node:os";
|
|
3248
|
-
import * as
|
|
4515
|
+
import * as path15 from "node:path";
|
|
3249
4516
|
import {
|
|
3250
4517
|
DEFAULT_WALK_IGNORE_DIRS,
|
|
3251
4518
|
indexParallelBatchSize,
|
|
@@ -3913,283 +5180,96 @@ var ModuleResolver = class {
|
|
|
3913
5180
|
const segments = spec.split("::").filter(Boolean);
|
|
3914
5181
|
if (segments.length === 0) return void 0;
|
|
3915
5182
|
const head = segments[0];
|
|
3916
|
-
if (head === "self" || head === "super") {
|
|
3917
|
-
let base = path6.posix.dirname(fromFile);
|
|
3918
|
-
for (const segment of segments) {
|
|
3919
|
-
if (segment === "super") base = path6.posix.dirname(base);
|
|
3920
|
-
else if (segment !== "self") break;
|
|
3921
|
-
}
|
|
3922
|
-
const rest2 = segments.filter((segment) => segment !== "self" && segment !== "super");
|
|
3923
|
-
return this.lookupWithExtensions(path6.posix.join(base, ...rest2), "rs");
|
|
3924
|
-
}
|
|
3925
|
-
const owningCrate = findOwningRoot(this.structure, fromFile, ["cargo"]);
|
|
3926
|
-
const crate = head === "crate" ? owningCrate : this.structure.roots.find(
|
|
3927
|
-
(root) => root.kind === "cargo" && root.importPath === head?.replace(/-/g, "_")
|
|
3928
|
-
);
|
|
3929
|
-
if (!crate) {
|
|
3930
|
-
return this.lookupWithExtensions(
|
|
3931
|
-
path6.posix.join(path6.posix.dirname(fromFile), ...segments),
|
|
3932
|
-
"rs"
|
|
3933
|
-
);
|
|
3934
|
-
}
|
|
3935
|
-
const rest = segments.slice(1);
|
|
3936
|
-
for (const base of crate.sourceRoots) {
|
|
3937
|
-
const parent = rest.length > 1 ? this.lookupWithExtensions(path6.posix.join(base, ...rest.slice(0, -1)), "rs") : void 0;
|
|
3938
|
-
const exact = this.lookupWithExtensions(path6.posix.join(base, ...rest), "rs");
|
|
3939
|
-
const hit = exact ?? parent ?? this.lookupWithExtensions(path6.posix.join(base, "lib"), "rs");
|
|
3940
|
-
if (hit) return hit;
|
|
3941
|
-
}
|
|
3942
|
-
return void 0;
|
|
3943
|
-
}
|
|
3944
|
-
/** `com.example.Thing` and `com.example.*` against JVM source roots. */
|
|
3945
|
-
resolveJvm(spec) {
|
|
3946
|
-
const segments = spec.split(".").filter(Boolean);
|
|
3947
|
-
if (segments.length === 0) return void 0;
|
|
3948
|
-
const sourceRoots = this.structure.roots.filter((root) => root.kind === "maven" || root.kind === "gradle").flatMap((root) => root.sourceRoots);
|
|
3949
|
-
const wildcard = segments[segments.length - 1] === "*";
|
|
3950
|
-
const parts = wildcard ? segments.slice(0, -1) : segments;
|
|
3951
|
-
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
3952
|
-
const target = path6.posix.join(base, ...parts);
|
|
3953
|
-
const hit = wildcard ? this.representativeIn(target, "jvm") : this.lookupWithExtensions(target, "jvm");
|
|
3954
|
-
if (hit) return hit;
|
|
3955
|
-
}
|
|
3956
|
-
return void 0;
|
|
3957
|
-
}
|
|
3958
|
-
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
3959
|
-
resolveInclude(fromFile, spec) {
|
|
3960
|
-
const relative3 = this.lookupWithExtensions(
|
|
3961
|
-
path6.posix.join(path6.posix.dirname(fromFile), spec),
|
|
3962
|
-
"c"
|
|
3963
|
-
);
|
|
3964
|
-
if (relative3) return relative3;
|
|
3965
|
-
for (const base of [
|
|
3966
|
-
path6.posix.join(this.structure.projectRoot, "include"),
|
|
3967
|
-
this.structure.projectRoot
|
|
3968
|
-
]) {
|
|
3969
|
-
const hit = this.lookupWithExtensions(path6.posix.join(base, spec), "c");
|
|
3970
|
-
if (hit) return hit;
|
|
3971
|
-
}
|
|
3972
|
-
return void 0;
|
|
3973
|
-
}
|
|
3974
|
-
/** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
|
|
3975
|
-
resolveRuby(fromFile, spec) {
|
|
3976
|
-
const relative3 = this.lookupWithExtensions(
|
|
3977
|
-
path6.posix.join(path6.posix.dirname(fromFile), spec),
|
|
3978
|
-
"ruby"
|
|
3979
|
-
);
|
|
3980
|
-
if (relative3) return relative3;
|
|
3981
|
-
for (const base of [
|
|
3982
|
-
path6.posix.join(this.structure.projectRoot, "lib"),
|
|
3983
|
-
this.structure.projectRoot
|
|
3984
|
-
]) {
|
|
3985
|
-
const hit = this.lookupWithExtensions(path6.posix.join(base, spec), "ruby");
|
|
3986
|
-
if (hit) return hit;
|
|
3987
|
-
}
|
|
3988
|
-
return void 0;
|
|
3989
|
-
}
|
|
3990
|
-
};
|
|
3991
|
-
|
|
3992
|
-
// src/codebase-index/import-extractor.ts
|
|
3993
|
-
var IMPORT_MAX_FILE_CHARS = 512 * 1024;
|
|
3994
|
-
var IMPORT_MAX_PER_FILE = 400;
|
|
3995
|
-
var DOTTED_IMPORT = [
|
|
3996
|
-
{ re: /^[ \t]*import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;?\s*$/gm }
|
|
3997
|
-
];
|
|
3998
|
-
var LANG_IMPORTS = {
|
|
3999
|
-
// Go and Python have real AST extractors; these patterns are the fallback for
|
|
4000
|
-
// machines with no Go toolchain or Python interpreter installed, where the
|
|
4001
|
-
// parser degrades to regex symbols and would otherwise contribute no edges.
|
|
4002
|
-
go: [
|
|
4003
|
-
{ re: /^[ \t]*import\s+(?:[\w.]+\s+)?"([^"]+)"/gm },
|
|
4004
|
-
// Grouped form: inside `import ( … )` each line is an optional alias plus a
|
|
4005
|
-
// quoted path. A stray match elsewhere resolves to no file and is dropped.
|
|
4006
|
-
{ re: /^[ \t]*(?:[A-Za-z_.]\w*\s+)?"([^"]+)"\s*$/gm }
|
|
4007
|
-
],
|
|
4008
|
-
py: [{ re: /^[ \t]*import\s+([\w.]+)/gm }, { re: /^[ \t]*from\s+([.\w]+)\s+import\b/gm }],
|
|
4009
|
-
rs: [
|
|
4010
|
-
// use a::b::C; | use a::b::{C, D}; → the path before any brace
|
|
4011
|
-
{ re: /^[ \t]*(?:pub\s+)?use\s+([\w:]+?)(?:::\{|\s*;|\s+as\b)/gm },
|
|
4012
|
-
// mod foo; (a declaration *and* a dependency on foo.rs / foo/mod.rs)
|
|
4013
|
-
{ re: /^[ \t]*(?:pub\s+)?mod\s+(\w+)\s*;/gm }
|
|
4014
|
-
],
|
|
4015
|
-
java: DOTTED_IMPORT,
|
|
4016
|
-
kotlin: DOTTED_IMPORT,
|
|
4017
|
-
scala: [{ re: /^[ \t]*import\s+([\w.]+(?:\.[_*])?)\s*$/gm }],
|
|
4018
|
-
csharp: [
|
|
4019
|
-
// using Foo.Bar; | using static Foo.Bar; | using Alias = Foo.Bar;
|
|
4020
|
-
{ re: /^[ \t]*global\s+using\s+(?:static\s+)?([\w.]+)\s*;/gm, name: "full" },
|
|
4021
|
-
{ re: /^[ \t]*using\s+(?:static\s+)?(?:\w+\s*=\s*)?([\w.]+)\s*;/gm, name: "full" }
|
|
4022
|
-
],
|
|
4023
|
-
// Quoted includes only: <stdio.h> is a system header with no indexed file.
|
|
4024
|
-
c: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
4025
|
-
cpp: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
4026
|
-
ruby: [{ re: /^[ \t]*(?:require|require_relative|load)\s*\(?\s*['"]([^'"]+)['"]/gm }],
|
|
4027
|
-
php: [
|
|
4028
|
-
// `use A\B\C` imports the class C, which is what the index has a symbol
|
|
4029
|
-
// for — the namespace symbol only covers the `A\B` prefix.
|
|
4030
|
-
{ re: /^[ \t]*use\s+(?:function\s+|const\s+)?([\w\\]+)/gm },
|
|
4031
|
-
{ re: /^[ \t]*(?:require|require_once|include|include_once)\s*\(?\s*['"]([^'"]+)['"]/gm }
|
|
4032
|
-
],
|
|
4033
|
-
swift: [{ re: /^[ \t]*import\s+(?:struct\s+|class\s+|func\s+)?([\w.]+)\s*$/gm }],
|
|
4034
|
-
dart: [{ re: /^[ \t]*(?:import|export|part)\s+['"]([^'"]+)['"]/gm }],
|
|
4035
|
-
lua: [{ re: /\brequire\s*\(?\s*['"]([^'"]+)['"]/g }],
|
|
4036
|
-
elixir: [{ re: /^[ \t]*(?:alias|import|require|use)\s+([A-Z][\w.]*)/gm, name: "full" }],
|
|
4037
|
-
haskell: [{ re: /^[ \t]*import\s+(?:qualified\s+)?([\w.]+)/gm, name: "full" }],
|
|
4038
|
-
zig: [{ re: /@import\s*\(\s*"([^"]+)"\s*\)/g }],
|
|
4039
|
-
proto: [{ re: /^[ \t]*import\s+(?:public\s+|weak\s+)?"([^"]+)"\s*;/gm }],
|
|
4040
|
-
// `@import "x"`, `@use "x"`, `@forward "x"` — Sass and plain CSS alike.
|
|
4041
|
-
css: [{ re: /^[ \t]*@(?:import|use|forward)\s+(?:url\()?\s*['"]([^'"]+)['"]/gm }],
|
|
4042
|
-
// A .vue/.svelte file's <script> block is JS; the same ESM syntax applies.
|
|
4043
|
-
vue: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
4044
|
-
svelte: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
4045
|
-
html: [
|
|
4046
|
-
{ re: /<script[^>]+src\s*=\s*['"]([^'"]+)['"]/g },
|
|
4047
|
-
{ re: /<link[^>]+href\s*=\s*['"]([^'"]+)['"]/g }
|
|
4048
|
-
],
|
|
4049
|
-
shell: [{ re: /^[ \t]*(?:source|\.)\s+(\S+)/gm }],
|
|
4050
|
-
r: [{ re: /\b(?:source|library|require)\s*\(\s*['"]?([\w./-]+)['"]?\s*\)/g }]
|
|
4051
|
-
};
|
|
4052
|
-
function lastSegment(specifier) {
|
|
4053
|
-
const pathLike = /[/\\]|::/.test(specifier);
|
|
4054
|
-
const segments = specifier.split(/[/\\]|::/).filter(Boolean);
|
|
4055
|
-
let last = segments[segments.length - 1] ?? specifier;
|
|
4056
|
-
if (last === "*" || last === "_") {
|
|
4057
|
-
last = segments[segments.length - 2] ?? specifier;
|
|
4058
|
-
}
|
|
4059
|
-
if (pathLike) return last.replace(/\.[A-Za-z0-9]{1,8}$/, "") || last;
|
|
4060
|
-
const dotted = last.split(".").filter((part) => part && part !== "*" && part !== "_");
|
|
4061
|
-
return dotted[dotted.length - 1] ?? last;
|
|
4062
|
-
}
|
|
4063
|
-
function newlineOffsets(content) {
|
|
4064
|
-
const offsets = [];
|
|
4065
|
-
for (let i = 0; i < content.length; i++) {
|
|
4066
|
-
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
4067
|
-
}
|
|
4068
|
-
return offsets;
|
|
4069
|
-
}
|
|
4070
|
-
function lineAt2(offsets, index) {
|
|
4071
|
-
let low = 0;
|
|
4072
|
-
let high = offsets.length;
|
|
4073
|
-
while (low < high) {
|
|
4074
|
-
const mid = low + high >>> 1;
|
|
4075
|
-
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
4076
|
-
else high = mid;
|
|
4077
|
-
}
|
|
4078
|
-
return low + 1;
|
|
4079
|
-
}
|
|
4080
|
-
function hasImportPatterns(lang) {
|
|
4081
|
-
return LANG_IMPORTS[lang] !== void 0;
|
|
4082
|
-
}
|
|
4083
|
-
function extractImports(opts) {
|
|
4084
|
-
const patterns = LANG_IMPORTS[opts.lang];
|
|
4085
|
-
if (!patterns || !opts.content) return [];
|
|
4086
|
-
const limit = opts.maxImports ?? IMPORT_MAX_PER_FILE;
|
|
4087
|
-
const content = opts.content.length > IMPORT_MAX_FILE_CHARS ? opts.content.slice(0, IMPORT_MAX_FILE_CHARS) : opts.content;
|
|
4088
|
-
const refs = [];
|
|
4089
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4090
|
-
const offsets = newlineOffsets(content);
|
|
4091
|
-
for (const pattern of patterns) {
|
|
4092
|
-
const re = new RegExp(pattern.re.source, pattern.re.flags);
|
|
4093
|
-
for (const match of content.matchAll(re)) {
|
|
4094
|
-
if (refs.length >= limit) return refs;
|
|
4095
|
-
const specifier = match[1]?.trim();
|
|
4096
|
-
if (!specifier) continue;
|
|
4097
|
-
const module = specifier;
|
|
4098
|
-
const toName = pattern.name === "full" ? module : lastSegment(module);
|
|
4099
|
-
if (!toName) continue;
|
|
4100
|
-
const key = `${module}\0${toName}`;
|
|
4101
|
-
if (seen.has(key)) continue;
|
|
4102
|
-
seen.add(key);
|
|
4103
|
-
refs.push({
|
|
4104
|
-
fromId: 0,
|
|
4105
|
-
toName,
|
|
4106
|
-
callType: "import",
|
|
4107
|
-
line: lineAt2(offsets, match.index ?? 0),
|
|
4108
|
-
lang: opts.lang,
|
|
4109
|
-
module
|
|
4110
|
-
});
|
|
4111
|
-
}
|
|
4112
|
-
}
|
|
4113
|
-
return refs;
|
|
4114
|
-
}
|
|
4115
|
-
|
|
4116
|
-
// src/codebase-index/parser-dispatch.ts
|
|
4117
|
-
async function parseFileContent(file, content, lang) {
|
|
4118
|
-
const parsed = await dispatch(file, content, lang);
|
|
4119
|
-
return withRelations(parsed, content, lang);
|
|
4120
|
-
}
|
|
4121
|
-
async function dispatch(file, content, lang) {
|
|
4122
|
-
switch (lang) {
|
|
4123
|
-
case "ts":
|
|
4124
|
-
case "tsx":
|
|
4125
|
-
case "js":
|
|
4126
|
-
case "jsx": {
|
|
4127
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
|
|
4128
|
-
return parseSymbols9({ file, content, lang });
|
|
4129
|
-
}
|
|
4130
|
-
case "go": {
|
|
4131
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
|
|
4132
|
-
return parseSymbols9({ file, content, lang: "go" });
|
|
4133
|
-
}
|
|
4134
|
-
case "py": {
|
|
4135
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
|
|
4136
|
-
return parseSymbols9({ file, content, lang: "py" });
|
|
4137
|
-
}
|
|
4138
|
-
case "rs": {
|
|
4139
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
|
|
4140
|
-
return parseSymbols9({ file, content, lang: "rs" });
|
|
5183
|
+
if (head === "self" || head === "super") {
|
|
5184
|
+
let base = path6.posix.dirname(fromFile);
|
|
5185
|
+
for (const segment of segments) {
|
|
5186
|
+
if (segment === "super") base = path6.posix.dirname(base);
|
|
5187
|
+
else if (segment !== "self") break;
|
|
5188
|
+
}
|
|
5189
|
+
const rest2 = segments.filter((segment) => segment !== "self" && segment !== "super");
|
|
5190
|
+
return this.lookupWithExtensions(path6.posix.join(base, ...rest2), "rs");
|
|
4141
5191
|
}
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
5192
|
+
const owningCrate = findOwningRoot(this.structure, fromFile, ["cargo"]);
|
|
5193
|
+
const crate = head === "crate" ? owningCrate : this.structure.roots.find(
|
|
5194
|
+
(root) => root.kind === "cargo" && root.importPath === head?.replace(/-/g, "_")
|
|
5195
|
+
);
|
|
5196
|
+
if (!crate) {
|
|
5197
|
+
return this.lookupWithExtensions(
|
|
5198
|
+
path6.posix.join(path6.posix.dirname(fromFile), ...segments),
|
|
5199
|
+
"rs"
|
|
5200
|
+
);
|
|
4145
5201
|
}
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
5202
|
+
const rest = segments.slice(1);
|
|
5203
|
+
for (const base of crate.sourceRoots) {
|
|
5204
|
+
const parent = rest.length > 1 ? this.lookupWithExtensions(path6.posix.join(base, ...rest.slice(0, -1)), "rs") : void 0;
|
|
5205
|
+
const exact = this.lookupWithExtensions(path6.posix.join(base, ...rest), "rs");
|
|
5206
|
+
const hit = exact ?? parent ?? this.lookupWithExtensions(path6.posix.join(base, "lib"), "rs");
|
|
5207
|
+
if (hit) return hit;
|
|
4149
5208
|
}
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
case "shell":
|
|
4164
|
-
case "elixir": {
|
|
4165
|
-
try {
|
|
4166
|
-
const { parseSymbols: parseSymbols10 } = await Promise.resolve().then(() => (init_tree_sitter_parser(), tree_sitter_parser_exports));
|
|
4167
|
-
const parsed = await parseSymbols10({ file, content, lang });
|
|
4168
|
-
if (parsed.symbols.length > 0) return parsed;
|
|
4169
|
-
} catch {
|
|
4170
|
-
}
|
|
4171
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
4172
|
-
return parseSymbols9({ file, content, lang });
|
|
5209
|
+
return void 0;
|
|
5210
|
+
}
|
|
5211
|
+
/** `com.example.Thing` and `com.example.*` against JVM source roots. */
|
|
5212
|
+
resolveJvm(spec) {
|
|
5213
|
+
const segments = spec.split(".").filter(Boolean);
|
|
5214
|
+
if (segments.length === 0) return void 0;
|
|
5215
|
+
const sourceRoots = this.structure.roots.filter((root) => root.kind === "maven" || root.kind === "gradle").flatMap((root) => root.sourceRoots);
|
|
5216
|
+
const wildcard = segments[segments.length - 1] === "*";
|
|
5217
|
+
const parts = wildcard ? segments.slice(0, -1) : segments;
|
|
5218
|
+
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
5219
|
+
const target = path6.posix.join(base, ...parts);
|
|
5220
|
+
const hit = wildcard ? this.representativeIn(target, "jvm") : this.lookupWithExtensions(target, "jvm");
|
|
5221
|
+
if (hit) return hit;
|
|
4173
5222
|
}
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
5223
|
+
return void 0;
|
|
5224
|
+
}
|
|
5225
|
+
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
5226
|
+
resolveInclude(fromFile, spec) {
|
|
5227
|
+
const relative3 = this.lookupWithExtensions(
|
|
5228
|
+
path6.posix.join(path6.posix.dirname(fromFile), spec),
|
|
5229
|
+
"c"
|
|
5230
|
+
);
|
|
5231
|
+
if (relative3) return relative3;
|
|
5232
|
+
for (const base of [
|
|
5233
|
+
path6.posix.join(this.structure.projectRoot, "include"),
|
|
5234
|
+
this.structure.projectRoot
|
|
5235
|
+
]) {
|
|
5236
|
+
const hit = this.lookupWithExtensions(path6.posix.join(base, spec), "c");
|
|
5237
|
+
if (hit) return hit;
|
|
4177
5238
|
}
|
|
5239
|
+
return void 0;
|
|
4178
5240
|
}
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
5241
|
+
/** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
|
|
5242
|
+
resolveRuby(fromFile, spec) {
|
|
5243
|
+
const relative3 = this.lookupWithExtensions(
|
|
5244
|
+
path6.posix.join(path6.posix.dirname(fromFile), spec),
|
|
5245
|
+
"ruby"
|
|
5246
|
+
);
|
|
5247
|
+
if (relative3) return relative3;
|
|
5248
|
+
for (const base of [
|
|
5249
|
+
path6.posix.join(this.structure.projectRoot, "lib"),
|
|
5250
|
+
this.structure.projectRoot
|
|
5251
|
+
]) {
|
|
5252
|
+
const hit = this.lookupWithExtensions(path6.posix.join(base, spec), "ruby");
|
|
5253
|
+
if (hit) return hit;
|
|
5254
|
+
}
|
|
5255
|
+
return void 0;
|
|
4184
5256
|
}
|
|
4185
|
-
|
|
4186
|
-
|
|
5257
|
+
};
|
|
5258
|
+
|
|
5259
|
+
// src/codebase-index/indexer.ts
|
|
5260
|
+
init_parser_dispatch();
|
|
4187
5261
|
|
|
4188
5262
|
// src/codebase-index/parser-worker-pool.ts
|
|
5263
|
+
import * as fs7 from "node:fs";
|
|
5264
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
4189
5265
|
import { Worker } from "node:worker_threads";
|
|
4190
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4191
|
-
import * as fs6 from "node:fs";
|
|
4192
5266
|
var WORKER_POOL_THRESHOLD = 500;
|
|
5267
|
+
function resolveWorkerPoolThreshold() {
|
|
5268
|
+
const raw = process.env["WRONGSTACK_INDEX_WORKER_THRESHOLD"];
|
|
5269
|
+
if (raw === void 0) return WORKER_POOL_THRESHOLD;
|
|
5270
|
+
if (!/^\d+$/.test(raw)) return WORKER_POOL_THRESHOLD;
|
|
5271
|
+
return Number.parseInt(raw, 10);
|
|
5272
|
+
}
|
|
4193
5273
|
var ParserWorkerPool = class {
|
|
4194
5274
|
constructor(maxWorkers = defaultWorkerCount()) {
|
|
4195
5275
|
this.maxWorkers = maxWorkers;
|
|
@@ -4233,7 +5313,8 @@ var ParserWorkerPool = class {
|
|
|
4233
5313
|
w.unref();
|
|
4234
5314
|
w.on("message", (msg) => this.handleMessage(msg));
|
|
4235
5315
|
w.on("error", (err) => this.handleError(err, w));
|
|
4236
|
-
|
|
5316
|
+
w.on("exit", () => this.retireByReference(w));
|
|
5317
|
+
this.workers.push({ worker: w, workerId: w.threadId, busy: false });
|
|
4237
5318
|
} catch {
|
|
4238
5319
|
if (this.workers.length === 0) {
|
|
4239
5320
|
this.unavailable = true;
|
|
@@ -4249,7 +5330,7 @@ var ParserWorkerPool = class {
|
|
|
4249
5330
|
}
|
|
4250
5331
|
/**
|
|
4251
5332
|
* Parse files in parallel across the worker pool. Returns a flat
|
|
4252
|
-
* `FileSymbols[]` in completion order (caller
|
|
5333
|
+
* `FileSymbols[]` in completion order (caller matches by file path).
|
|
4253
5334
|
*
|
|
4254
5335
|
* Content is pre-read by the main thread (for the content-hash check)
|
|
4255
5336
|
* and passed to workers to avoid a second disk read. Files are
|
|
@@ -4270,21 +5351,19 @@ var ParserWorkerPool = class {
|
|
|
4270
5351
|
chunks[i % workerCount].push(files[i]);
|
|
4271
5352
|
}
|
|
4272
5353
|
return new Promise((resolve4, reject) => {
|
|
5354
|
+
const pendingChunks = /* @__PURE__ */ new Map();
|
|
4273
5355
|
this.pending.set(batchId, {
|
|
4274
5356
|
resolve: resolve4,
|
|
4275
5357
|
reject,
|
|
4276
5358
|
accumulated: [],
|
|
4277
|
-
|
|
4278
|
-
|
|
5359
|
+
pendingChunks,
|
|
5360
|
+
settled: false
|
|
4279
5361
|
});
|
|
4280
5362
|
for (let i = 0; i < workerCount; i++) {
|
|
4281
5363
|
const pw = this.workers[i];
|
|
4282
5364
|
pw.busy = true;
|
|
4283
|
-
pw.
|
|
4284
|
-
|
|
4285
|
-
id: batchId,
|
|
4286
|
-
files: chunks[i]
|
|
4287
|
-
});
|
|
5365
|
+
pendingChunks.set(pw.workerId, chunks[i]);
|
|
5366
|
+
pw.worker.postMessage({ type: "parse", id: batchId, files: chunks[i] });
|
|
4288
5367
|
}
|
|
4289
5368
|
});
|
|
4290
5369
|
}
|
|
@@ -4293,6 +5372,12 @@ var ParserWorkerPool = class {
|
|
|
4293
5372
|
const workers = this.workers.map((w) => w.worker);
|
|
4294
5373
|
this.workers = [];
|
|
4295
5374
|
this.unavailable = false;
|
|
5375
|
+
for (const [, p] of this.pending) {
|
|
5376
|
+
if (p.settled) continue;
|
|
5377
|
+
p.settled = true;
|
|
5378
|
+
p.reject(new Error("ParserWorkerPool shut down"));
|
|
5379
|
+
}
|
|
5380
|
+
this.pending.clear();
|
|
4296
5381
|
for (const w of workers) {
|
|
4297
5382
|
try {
|
|
4298
5383
|
w.postMessage({ type: "shutdown" });
|
|
@@ -4313,39 +5398,117 @@ var ParserWorkerPool = class {
|
|
|
4313
5398
|
})
|
|
4314
5399
|
)
|
|
4315
5400
|
);
|
|
4316
|
-
for (const [, p] of this.pending) p.reject(new Error("ParserWorkerPool shut down"));
|
|
4317
|
-
this.pending.clear();
|
|
4318
5401
|
}
|
|
4319
5402
|
handleMessage(msg) {
|
|
4320
5403
|
const batch = this.pending.get(msg.id);
|
|
4321
5404
|
if (!batch) return;
|
|
5405
|
+
const worker2 = this.workers.find((w) => w.workerId === msg.workerId);
|
|
5406
|
+
if (worker2) worker2.busy = false;
|
|
5407
|
+
batch.pendingChunks.delete(msg.workerId);
|
|
4322
5408
|
batch.accumulated.push(...msg.results);
|
|
4323
|
-
batch.
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
if (batch.completedWorkers >= batch.expectedWorkers) {
|
|
5409
|
+
if (batch.pendingChunks.size === 0) {
|
|
5410
|
+
if (batch.settled) return;
|
|
5411
|
+
batch.settled = true;
|
|
4327
5412
|
this.pending.delete(msg.id);
|
|
4328
5413
|
batch.resolve(batch.accumulated);
|
|
4329
5414
|
}
|
|
4330
5415
|
}
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
5416
|
+
/**
|
|
5417
|
+
* Remove a worker from the pool and salvage any chunk it still owed.
|
|
5418
|
+
*
|
|
5419
|
+
* Idempotent by workerId — `error` and `exit` can both fire for one
|
|
5420
|
+
* death, and a worker may die while no batch references it. When the
|
|
5421
|
+
* dead worker owed files to an in-flight batch and other workers remain,
|
|
5422
|
+
* those files are re-parsed inline on this thread (one fewer worker
|
|
5423
|
+
* should cost latency, not correctness). When it was the last worker,
|
|
5424
|
+
* every remaining batch rejects so the indexer's existing inline
|
|
5425
|
+
* fallback takes over the whole batch.
|
|
5426
|
+
*/
|
|
5427
|
+
retireWorker(workerId) {
|
|
5428
|
+
const entry = this.workers.find((w) => w.workerId === workerId);
|
|
5429
|
+
if (!entry) return;
|
|
5430
|
+
this.workers = this.workers.filter((w) => w.workerId !== workerId);
|
|
5431
|
+
for (const [batchId, batch] of [...this.pending]) {
|
|
5432
|
+
const orphaned = batch.pendingChunks.get(workerId);
|
|
5433
|
+
if (!orphaned) continue;
|
|
5434
|
+
if (this.workers.length === 0) {
|
|
5435
|
+
this.pending.delete(batchId);
|
|
5436
|
+
this.unavailable = true;
|
|
5437
|
+
if (!batch.settled) {
|
|
5438
|
+
batch.settled = true;
|
|
5439
|
+
batch.reject(new Error("ParserWorkerPool: all workers died mid-batch"));
|
|
5440
|
+
}
|
|
5441
|
+
continue;
|
|
5442
|
+
}
|
|
5443
|
+
void this.reparseInline(batch, orphaned, workerId);
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5446
|
+
/**
|
|
5447
|
+
* Salvage path: re-parse an orphaned chunk on this thread. Files that
|
|
5448
|
+
* fail here stay absent from the results — same contract as a per-file
|
|
5449
|
+
* error inside a live worker (see handleMessage).
|
|
5450
|
+
*/
|
|
5451
|
+
async reparseInline(batch, orphaned, workerId) {
|
|
5452
|
+
try {
|
|
5453
|
+
const { parseFileContent: parseFileContent2 } = await Promise.resolve().then(() => (init_parser_dispatch(), parser_dispatch_exports));
|
|
5454
|
+
for (const item of orphaned) {
|
|
5455
|
+
if (batch.settled) return;
|
|
5456
|
+
try {
|
|
5457
|
+
const parsed = await parseFileContent2(item.file, item.content, item.lang);
|
|
5458
|
+
batch.accumulated.push(parsed);
|
|
5459
|
+
} catch {
|
|
5460
|
+
}
|
|
5461
|
+
await new Promise((resolve4) => setImmediate(resolve4));
|
|
5462
|
+
}
|
|
5463
|
+
} finally {
|
|
5464
|
+
this.finishSalvage(batch, workerId);
|
|
5465
|
+
}
|
|
5466
|
+
}
|
|
5467
|
+
/**
|
|
5468
|
+
* Terminal tail of a salvage — runs on every exit path. Kept free of
|
|
5469
|
+
* control flow inside a `finally` (noUnsafeFinally): releases the
|
|
5470
|
+
* pending-marker and resolves the batch if this was its last chunk.
|
|
5471
|
+
*/
|
|
5472
|
+
finishSalvage(batch, workerId) {
|
|
5473
|
+
batch.pendingChunks.delete(workerId);
|
|
5474
|
+
if (batch.pendingChunks.size === 0) {
|
|
5475
|
+
for (const [batchId, tracked] of this.pending) {
|
|
5476
|
+
if (tracked === batch) {
|
|
5477
|
+
if (batch.settled) return;
|
|
5478
|
+
batch.settled = true;
|
|
5479
|
+
this.pending.delete(batchId);
|
|
5480
|
+
batch.resolve(batch.accumulated);
|
|
5481
|
+
return;
|
|
5482
|
+
}
|
|
5483
|
+
}
|
|
4337
5484
|
}
|
|
4338
5485
|
}
|
|
5486
|
+
/**
|
|
5487
|
+
* Retire by worker object rather than threadId. `threadId` is -1 before
|
|
5488
|
+
* the worker emits `online`, so a death during script load would make a
|
|
5489
|
+
* threadId-keyed lookup silently no-op and leak the entry (with its
|
|
5490
|
+
* pending chunk) — reference identity is correct in every case.
|
|
5491
|
+
*/
|
|
5492
|
+
retireByReference(source) {
|
|
5493
|
+
const entry = this.workers.find((w) => w.worker === source);
|
|
5494
|
+
if (entry) this.retireWorker(entry.workerId);
|
|
5495
|
+
}
|
|
5496
|
+
handleError(err, source) {
|
|
5497
|
+
void err;
|
|
5498
|
+
this.retireByReference(source);
|
|
5499
|
+
}
|
|
4339
5500
|
};
|
|
4340
5501
|
function defaultWorkerCount() {
|
|
4341
5502
|
const cores = globalThis.navigator?.hardwareConcurrency ?? 4;
|
|
4342
5503
|
return Math.max(1, Math.min(4, cores - 1));
|
|
4343
5504
|
}
|
|
4344
5505
|
function resolveWorkerScriptUrl() {
|
|
5506
|
+
const override = process.env["WRONGSTACK_PARSER_WORKER_SCRIPT"];
|
|
5507
|
+
if (override) return pathToFileURL(override);
|
|
4345
5508
|
for (const rel of ["./parser-worker-script.js", "./codebase-index/parser-worker-script.js"]) {
|
|
4346
5509
|
try {
|
|
4347
5510
|
const url = new URL(rel, import.meta.url);
|
|
4348
|
-
if (url.protocol === "file:" &&
|
|
5511
|
+
if (url.protocol === "file:" && fs7.existsSync(fileURLToPath2(url))) return url;
|
|
4349
5512
|
} catch {
|
|
4350
5513
|
}
|
|
4351
5514
|
}
|
|
@@ -4358,8 +5521,8 @@ function getParserPool() {
|
|
|
4358
5521
|
}
|
|
4359
5522
|
|
|
4360
5523
|
// src/codebase-index/writer.ts
|
|
4361
|
-
import * as
|
|
4362
|
-
import * as
|
|
5524
|
+
import * as fs9 from "node:fs";
|
|
5525
|
+
import * as path14 from "node:path";
|
|
4363
5526
|
|
|
4364
5527
|
// src/codebase-index/bm25.ts
|
|
4365
5528
|
var K1 = 1.5;
|
|
@@ -4454,11 +5617,11 @@ var Bm25Index = class {
|
|
|
4454
5617
|
init_languages();
|
|
4455
5618
|
|
|
4456
5619
|
// src/codebase-index/schema.ts
|
|
4457
|
-
var SCHEMA_VERSION =
|
|
5620
|
+
var SCHEMA_VERSION = 5;
|
|
4458
5621
|
|
|
4459
5622
|
// src/codebase-index/sqlite-runtime.ts
|
|
4460
|
-
import { createRequire } from "node:module";
|
|
4461
5623
|
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
5624
|
+
import { loadRuntimeDatabaseSync } from "@wrongstack/persistence";
|
|
4462
5625
|
var warningSilenced = false;
|
|
4463
5626
|
function silenceSqliteExperimentalWarning() {
|
|
4464
5627
|
if (warningSilenced) return;
|
|
@@ -4476,11 +5639,10 @@ function loadDatabaseSync() {
|
|
|
4476
5639
|
if (DatabaseSyncCtor) return DatabaseSyncCtor;
|
|
4477
5640
|
silenceSqliteExperimentalWarning();
|
|
4478
5641
|
try {
|
|
4479
|
-
|
|
4480
|
-
DatabaseSyncCtor = req("node:sqlite").DatabaseSync;
|
|
5642
|
+
DatabaseSyncCtor = loadRuntimeDatabaseSync();
|
|
4481
5643
|
} catch (err) {
|
|
4482
5644
|
throw new Error(
|
|
4483
|
-
`The codebase index needs
|
|
5645
|
+
`The codebase index needs node:sqlite (Node >= 22.5) or bun:sqlite. This runtime doesn't provide it: ${toErrorMessage(err)}`
|
|
4484
5646
|
);
|
|
4485
5647
|
}
|
|
4486
5648
|
return DatabaseSyncCtor;
|
|
@@ -4523,9 +5685,87 @@ function runSqliteWithRetry(fn) {
|
|
|
4523
5685
|
throw lastError;
|
|
4524
5686
|
}
|
|
4525
5687
|
|
|
5688
|
+
// src/codebase-index/vector-search.ts
|
|
5689
|
+
var RRF_K = 60;
|
|
5690
|
+
function vectorEmbeddingEnabled() {
|
|
5691
|
+
return process.env["WRONGSTACK_INDEX_VECTORS"] === "1";
|
|
5692
|
+
}
|
|
5693
|
+
var VECTOR_DIMENSIONS = 384;
|
|
5694
|
+
var NGRAM_SIZE = 3;
|
|
5695
|
+
function embedText(text) {
|
|
5696
|
+
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
5697
|
+
const normalized = text.toLowerCase().trim();
|
|
5698
|
+
if (normalized.length < NGRAM_SIZE) {
|
|
5699
|
+
const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
|
|
5700
|
+
for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
|
|
5701
|
+
const ngram = padded.slice(i, i + NGRAM_SIZE);
|
|
5702
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5703
|
+
vec[bucket] += 1;
|
|
5704
|
+
}
|
|
5705
|
+
} else {
|
|
5706
|
+
for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
|
|
5707
|
+
const ngram = normalized.slice(i, i + NGRAM_SIZE);
|
|
5708
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5709
|
+
vec[bucket] += 1;
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
5712
|
+
let norm = 0;
|
|
5713
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5714
|
+
norm += vec[i] * vec[i];
|
|
5715
|
+
}
|
|
5716
|
+
norm = Math.sqrt(norm);
|
|
5717
|
+
if (norm > 0) {
|
|
5718
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5719
|
+
vec[i] /= norm;
|
|
5720
|
+
}
|
|
5721
|
+
}
|
|
5722
|
+
return vec;
|
|
5723
|
+
}
|
|
5724
|
+
function hashNgram(str) {
|
|
5725
|
+
let hash = 2166136261;
|
|
5726
|
+
for (let i = 0; i < str.length; i++) {
|
|
5727
|
+
hash ^= str.charCodeAt(i);
|
|
5728
|
+
hash = Math.imul(hash, 16777619);
|
|
5729
|
+
}
|
|
5730
|
+
return hash >>> 0;
|
|
5731
|
+
}
|
|
5732
|
+
function cosineSimilarity(a, b) {
|
|
5733
|
+
let dot = 0;
|
|
5734
|
+
const len = Math.min(a.length, b.length);
|
|
5735
|
+
for (let i = 0; i < len; i++) {
|
|
5736
|
+
dot += a[i] * b[i];
|
|
5737
|
+
}
|
|
5738
|
+
return dot;
|
|
5739
|
+
}
|
|
5740
|
+
function encodeVector(vec) {
|
|
5741
|
+
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
5742
|
+
}
|
|
5743
|
+
function decodeVector(buf) {
|
|
5744
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
5745
|
+
const copy = new Float32Array(buf.byteLength / 4);
|
|
5746
|
+
for (let i = 0; i < copy.length; i++) {
|
|
5747
|
+
copy[i] = view.getFloat32(i * 4, true);
|
|
5748
|
+
}
|
|
5749
|
+
return copy;
|
|
5750
|
+
}
|
|
5751
|
+
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
5752
|
+
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
5753
|
+
const scored = [];
|
|
5754
|
+
for (const id of allIds) {
|
|
5755
|
+
const bm25Rank = bm25Ranks.get(id);
|
|
5756
|
+
const vecRank = vectorRanks.get(id);
|
|
5757
|
+
let score = 0;
|
|
5758
|
+
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
5759
|
+
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
5760
|
+
scored.push([id, score]);
|
|
5761
|
+
}
|
|
5762
|
+
scored.sort((a, b) => b[1] - a[1]);
|
|
5763
|
+
return scored;
|
|
5764
|
+
}
|
|
5765
|
+
|
|
4526
5766
|
// src/codebase-index/writer-admin.ts
|
|
4527
|
-
import * as
|
|
4528
|
-
import * as
|
|
5767
|
+
import * as fs8 from "node:fs";
|
|
5768
|
+
import * as path13 from "node:path";
|
|
4529
5769
|
var DB_FILE = "index.db";
|
|
4530
5770
|
function getAllIndexableWithStatement(stmt) {
|
|
4531
5771
|
return stmt("SELECT id, text FROM symbols").all().map(
|
|
@@ -4561,6 +5801,14 @@ function getMetadataWithStatement(stmt, key) {
|
|
|
4561
5801
|
const rows = stmt("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
4562
5802
|
return rows[0]?.value;
|
|
4563
5803
|
}
|
|
5804
|
+
function getIndexSummaryWithStatement(stmt) {
|
|
5805
|
+
const fileRows = stmt("SELECT COUNT(*) AS n FROM files").all();
|
|
5806
|
+
const lastRows = stmt("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
5807
|
+
return {
|
|
5808
|
+
totalFiles: fileRows[0] ? Number(fileRows[0].n) : 0,
|
|
5809
|
+
lastIndexed: lastRows.length ? Number(lastRows[0]?.value) : null
|
|
5810
|
+
};
|
|
5811
|
+
}
|
|
4564
5812
|
function getFileMetaWithStatement(stmt, file) {
|
|
4565
5813
|
const rows = stmt(
|
|
4566
5814
|
"SELECT file, lang, mtime_ms, symbol_count, last_indexed, content_hash FROM files WHERE file = ?"
|
|
@@ -4590,22 +5838,102 @@ function getAllFileMetasWithStatement(stmt) {
|
|
|
4590
5838
|
}
|
|
4591
5839
|
function getIndexDbSizeBytes(indexDir) {
|
|
4592
5840
|
try {
|
|
4593
|
-
return
|
|
5841
|
+
return fs8.statSync(path13.join(indexDir, DB_FILE)).size;
|
|
4594
5842
|
} catch {
|
|
4595
5843
|
return 0;
|
|
4596
5844
|
}
|
|
4597
5845
|
}
|
|
4598
5846
|
|
|
5847
|
+
// src/codebase-index/writer-helpers.ts
|
|
5848
|
+
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
5849
|
+
function escapeLike(value) {
|
|
5850
|
+
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
5851
|
+
}
|
|
5852
|
+
function ladderChunkSizes(total, max) {
|
|
5853
|
+
if (total <= 0) return [];
|
|
5854
|
+
const sizes = [];
|
|
5855
|
+
let remaining = total;
|
|
5856
|
+
while (remaining > 0) {
|
|
5857
|
+
const cap = Math.min(remaining, max);
|
|
5858
|
+
const pow = cap >= 1 ? 2 ** Math.floor(Math.log2(cap)) : 1;
|
|
5859
|
+
const take = Math.max(1, Math.min(pow, remaining));
|
|
5860
|
+
sizes.push(take);
|
|
5861
|
+
remaining -= take;
|
|
5862
|
+
}
|
|
5863
|
+
return sizes;
|
|
5864
|
+
}
|
|
5865
|
+
function nextPow2(count) {
|
|
5866
|
+
return count <= 1 ? 1 : 2 ** Math.ceil(Math.log2(count));
|
|
5867
|
+
}
|
|
5868
|
+
function padToInBucket(values) {
|
|
5869
|
+
if (values.length <= 1) return values.slice();
|
|
5870
|
+
const target = nextPow2(values.length);
|
|
5871
|
+
const padded = values.slice();
|
|
5872
|
+
while (padded.length < target) padded.push(padded[0]);
|
|
5873
|
+
return padded;
|
|
5874
|
+
}
|
|
5875
|
+
function placeholders(count) {
|
|
5876
|
+
return Array.from({ length: count }, () => "?").join(",");
|
|
5877
|
+
}
|
|
5878
|
+
function inListChunks(total, max) {
|
|
5879
|
+
if (total <= 0) return [];
|
|
5880
|
+
if (nextPow2(total) <= max) return [total];
|
|
5881
|
+
const powMax = Math.max(1, 2 ** Math.floor(Math.log2(max)));
|
|
5882
|
+
return ladderChunkSizes(total, powMax);
|
|
5883
|
+
}
|
|
5884
|
+
function posixIndexPath(file) {
|
|
5885
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
5886
|
+
}
|
|
5887
|
+
function indexedFileMatchSql(column = "file") {
|
|
5888
|
+
return `(${column} = ? OR replace(${column}, '\\', '/') = ? OR replace(${column}, '\\', '/') LIKE ? ESCAPE '\\')`;
|
|
5889
|
+
}
|
|
5890
|
+
function indexedFileMatchArgs(file) {
|
|
5891
|
+
const posix4 = posixIndexPath(file.trim());
|
|
5892
|
+
return [file, posix4, `%/${escapeLike(posix4)}`];
|
|
5893
|
+
}
|
|
5894
|
+
function matchesIndexedPackageFilter(storedFile, packageLabel, filter) {
|
|
5895
|
+
if (packageLabel === filter) return true;
|
|
5896
|
+
const posixFile = posixIndexPath(storedFile);
|
|
5897
|
+
const posixFilter = posixIndexPath(filter.trim());
|
|
5898
|
+
if (!posixFilter) return false;
|
|
5899
|
+
return posixFile === posixFilter || posixFile.endsWith(`/${posixFilter}`) || posixFile.includes(`/${posixFilter}/`);
|
|
5900
|
+
}
|
|
5901
|
+
function assignRefsToSymbols(refs, symbols) {
|
|
5902
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
5903
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
5904
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5905
|
+
const assigned = [];
|
|
5906
|
+
for (const ref of refs) {
|
|
5907
|
+
let owner;
|
|
5908
|
+
for (const symbol of ordered) {
|
|
5909
|
+
if (symbol.line > ref.line) break;
|
|
5910
|
+
owner = symbol;
|
|
5911
|
+
}
|
|
5912
|
+
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
5913
|
+
if (!owner || owner.id <= 0) continue;
|
|
5914
|
+
const key = `${owner.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
5915
|
+
if (seen.has(key)) continue;
|
|
5916
|
+
seen.add(key);
|
|
5917
|
+
assigned.push({ ...ref, fromId: owner.id });
|
|
5918
|
+
}
|
|
5919
|
+
return assigned;
|
|
5920
|
+
}
|
|
5921
|
+
function resolveIndexDir(projectRoot, override) {
|
|
5922
|
+
return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
|
|
5923
|
+
}
|
|
5924
|
+
|
|
4599
5925
|
// src/codebase-index/writer-bulk-insert.ts
|
|
4600
5926
|
function bulkInsertSymbolsWithStatement(stmt, maxSqlVars, rows) {
|
|
4601
5927
|
if (rows.length === 0) return;
|
|
4602
|
-
const
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
const
|
|
5928
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 12)));
|
|
5929
|
+
let cursor = 0;
|
|
5930
|
+
for (const take of ladder) {
|
|
5931
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
5932
|
+
cursor += take;
|
|
5933
|
+
const placeholders2 = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
4606
5934
|
const insert = stmt(
|
|
4607
|
-
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text
|
|
4608
|
-
VALUES ${
|
|
5935
|
+
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text)
|
|
5936
|
+
VALUES ${placeholders2}`
|
|
4609
5937
|
);
|
|
4610
5938
|
const binds = [];
|
|
4611
5939
|
for (const r of chunk) {
|
|
@@ -4620,8 +5948,7 @@ function bulkInsertSymbolsWithStatement(stmt, maxSqlVars, rows) {
|
|
|
4620
5948
|
r.signature,
|
|
4621
5949
|
r.docComment,
|
|
4622
5950
|
r.scope,
|
|
4623
|
-
r.text
|
|
4624
|
-
r.file
|
|
5951
|
+
r.text
|
|
4625
5952
|
);
|
|
4626
5953
|
}
|
|
4627
5954
|
insert.run(...binds);
|
|
@@ -4629,11 +5956,13 @@ function bulkInsertSymbolsWithStatement(stmt, maxSqlVars, rows) {
|
|
|
4629
5956
|
}
|
|
4630
5957
|
function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
|
|
4631
5958
|
if (!ftsAvailable || rows.length === 0) return;
|
|
4632
|
-
const
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
const
|
|
4636
|
-
|
|
5959
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 2)));
|
|
5960
|
+
let cursor = 0;
|
|
5961
|
+
for (const take of ladder) {
|
|
5962
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
5963
|
+
cursor += take;
|
|
5964
|
+
const placeholders2 = chunk.map(() => "(?, ?)").join(", ");
|
|
5965
|
+
const insert = stmt(`INSERT INTO symbols_fts(rowid, text) VALUES ${placeholders2}`);
|
|
4637
5966
|
const binds = [];
|
|
4638
5967
|
for (const r of chunk) binds.push(r.id, r.text);
|
|
4639
5968
|
insert.run(...binds);
|
|
@@ -4641,11 +5970,13 @@ function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
|
|
|
4641
5970
|
}
|
|
4642
5971
|
function bulkInsertVectorsWithStatement(stmt, maxSqlVars, rows) {
|
|
4643
5972
|
if (rows.length === 0) return;
|
|
4644
|
-
const
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
const
|
|
4648
|
-
|
|
5973
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 2)));
|
|
5974
|
+
let cursor = 0;
|
|
5975
|
+
for (const take of ladder) {
|
|
5976
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
5977
|
+
cursor += take;
|
|
5978
|
+
const placeholders2 = chunk.map(() => "(?, ?)").join(", ");
|
|
5979
|
+
const insert = stmt(`INSERT INTO symbol_vectors(symbol_id, vector) VALUES ${placeholders2}`);
|
|
4649
5980
|
const binds = [];
|
|
4650
5981
|
for (const r of chunk) binds.push(r.id, r.vector);
|
|
4651
5982
|
insert.run(...binds);
|
|
@@ -4653,13 +5984,15 @@ function bulkInsertVectorsWithStatement(stmt, maxSqlVars, rows) {
|
|
|
4653
5984
|
}
|
|
4654
5985
|
function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
|
|
4655
5986
|
if (refs.length === 0) return;
|
|
4656
|
-
const
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
const
|
|
5987
|
+
const ladder = ladderChunkSizes(refs.length, Math.max(1, Math.floor(maxSqlVars / 8)));
|
|
5988
|
+
let cursor = 0;
|
|
5989
|
+
for (const take of ladder) {
|
|
5990
|
+
const chunk = refs.slice(cursor, cursor + take);
|
|
5991
|
+
cursor += take;
|
|
5992
|
+
const placeholders2 = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
4660
5993
|
const insert = stmt(
|
|
4661
5994
|
`INSERT INTO refs(from_id, to_name, to_id, call_type, line, lang, module, to_file)
|
|
4662
|
-
VALUES ${
|
|
5995
|
+
VALUES ${placeholders2}`
|
|
4663
5996
|
);
|
|
4664
5997
|
const binds = [];
|
|
4665
5998
|
for (const ref of chunk) {
|
|
@@ -4813,52 +6146,6 @@ function materializeWeightedEdges(edgeMap, idPrefix) {
|
|
|
4813
6146
|
return edges;
|
|
4814
6147
|
}
|
|
4815
6148
|
|
|
4816
|
-
// src/codebase-index/writer-helpers.ts
|
|
4817
|
-
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
4818
|
-
function escapeLike(value) {
|
|
4819
|
-
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
4820
|
-
}
|
|
4821
|
-
function posixIndexPath(file) {
|
|
4822
|
-
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
4823
|
-
}
|
|
4824
|
-
function indexedFileMatchSql(column = "file") {
|
|
4825
|
-
return `(${column} = ? OR replace(${column}, '\\', '/') = ? OR replace(${column}, '\\', '/') LIKE ? ESCAPE '\\')`;
|
|
4826
|
-
}
|
|
4827
|
-
function indexedFileMatchArgs(file) {
|
|
4828
|
-
const posix4 = posixIndexPath(file.trim());
|
|
4829
|
-
return [file, posix4, `%/${escapeLike(posix4)}`];
|
|
4830
|
-
}
|
|
4831
|
-
function matchesIndexedPackageFilter(storedFile, packageLabel, filter) {
|
|
4832
|
-
if (packageLabel === filter) return true;
|
|
4833
|
-
const posixFile = posixIndexPath(storedFile);
|
|
4834
|
-
const posixFilter = posixIndexPath(filter.trim());
|
|
4835
|
-
if (!posixFilter) return false;
|
|
4836
|
-
return posixFile === posixFilter || posixFile.endsWith(`/${posixFilter}`) || posixFile.includes(`/${posixFilter}/`);
|
|
4837
|
-
}
|
|
4838
|
-
function assignRefsToSymbols(refs, symbols) {
|
|
4839
|
-
if (refs.length === 0 || symbols.length === 0) return [];
|
|
4840
|
-
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
4841
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4842
|
-
const assigned = [];
|
|
4843
|
-
for (const ref of refs) {
|
|
4844
|
-
let owner;
|
|
4845
|
-
for (const symbol of ordered) {
|
|
4846
|
-
if (symbol.line > ref.line) break;
|
|
4847
|
-
owner = symbol;
|
|
4848
|
-
}
|
|
4849
|
-
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
4850
|
-
if (!owner || owner.id <= 0) continue;
|
|
4851
|
-
const key = `${owner.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
4852
|
-
if (seen.has(key)) continue;
|
|
4853
|
-
seen.add(key);
|
|
4854
|
-
assigned.push({ ...ref, fromId: owner.id });
|
|
4855
|
-
}
|
|
4856
|
-
return assigned;
|
|
4857
|
-
}
|
|
4858
|
-
function resolveIndexDir(projectRoot, override) {
|
|
4859
|
-
return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
|
|
4860
|
-
}
|
|
4861
|
-
|
|
4862
6149
|
// src/codebase-index/writer-ref-mapper.ts
|
|
4863
6150
|
function mapWriterRefRow(row) {
|
|
4864
6151
|
return {
|
|
@@ -4880,20 +6167,20 @@ function mapWriterRefRow(row) {
|
|
|
4880
6167
|
var MAX_SQL_VARS = 900;
|
|
4881
6168
|
function chunkedIdQuery(stmt, ids, buildSql, extraArgs = []) {
|
|
4882
6169
|
const results = [];
|
|
4883
|
-
for (
|
|
4884
|
-
const chunk = ids.slice(
|
|
4885
|
-
|
|
4886
|
-
const sql = buildSql(placeholders);
|
|
6170
|
+
for (const take of inListChunks(ids.length, MAX_SQL_VARS)) {
|
|
6171
|
+
const chunk = padToInBucket(ids.slice(0, take));
|
|
6172
|
+
ids = ids.slice(take);
|
|
6173
|
+
const sql = buildSql(placeholders(chunk.length));
|
|
4887
6174
|
results.push(...stmt(sql).all(...chunk, ...extraArgs));
|
|
4888
6175
|
}
|
|
4889
6176
|
return results;
|
|
4890
6177
|
}
|
|
4891
6178
|
function chunkedIdScalar(stmt, ids, buildSql, extraArgs = []) {
|
|
4892
6179
|
let total = 0;
|
|
4893
|
-
for (
|
|
4894
|
-
const chunk = ids.slice(
|
|
4895
|
-
|
|
4896
|
-
const sql = buildSql(placeholders);
|
|
6180
|
+
for (const take of inListChunks(ids.length, MAX_SQL_VARS)) {
|
|
6181
|
+
const chunk = padToInBucket(ids.slice(0, take));
|
|
6182
|
+
ids = ids.slice(take);
|
|
6183
|
+
const sql = buildSql(placeholders(chunk.length));
|
|
4897
6184
|
const rows = stmt(sql).all(...chunk, ...extraArgs);
|
|
4898
6185
|
total += rows[0]?.n ?? 0;
|
|
4899
6186
|
}
|
|
@@ -4922,16 +6209,24 @@ function resolveIndexedFiles(stmt, file) {
|
|
|
4922
6209
|
}
|
|
4923
6210
|
function resolveSymbolIds(stmt, symbolName, file) {
|
|
4924
6211
|
if (!file) {
|
|
4925
|
-
const
|
|
4926
|
-
|
|
6212
|
+
const rows = stmt("SELECT id FROM symbols WHERE name = ? ORDER BY id").all(
|
|
6213
|
+
symbolName
|
|
6214
|
+
);
|
|
6215
|
+
return rows.map((r) => r.id);
|
|
4927
6216
|
}
|
|
4928
6217
|
const indexedFiles = resolveIndexedFiles(stmt, file);
|
|
4929
6218
|
if (indexedFiles.length === 0) return [];
|
|
4930
|
-
const
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
6219
|
+
const ids = [];
|
|
6220
|
+
let cursor = 0;
|
|
6221
|
+
for (const take of inListChunks(indexedFiles.length, MAX_SQL_VARS)) {
|
|
6222
|
+
const files = padToInBucket(indexedFiles.slice(cursor, cursor + take));
|
|
6223
|
+
cursor += take;
|
|
6224
|
+
const rows = stmt(
|
|
6225
|
+
`SELECT id FROM symbols WHERE name = ? AND file IN (${placeholders(files.length)}) ORDER BY id`
|
|
6226
|
+
).all(symbolName, ...files);
|
|
6227
|
+
ids.push(...rows.map((r) => r.id));
|
|
6228
|
+
}
|
|
6229
|
+
return ids;
|
|
4935
6230
|
}
|
|
4936
6231
|
function findIncomingCallsByName(stmt, symbolName, file, limit) {
|
|
4937
6232
|
const targetIds = resolveSymbolIds(stmt, symbolName, file);
|
|
@@ -5365,9 +6660,9 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
5365
6660
|
const loadedIds = new Set(syms.map((s) => s.id));
|
|
5366
6661
|
const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
|
|
5367
6662
|
if (missingIds.length > 0) {
|
|
5368
|
-
const
|
|
6663
|
+
const placeholders2 = missingIds.map(() => "?").join(",");
|
|
5369
6664
|
const extras = stmt(
|
|
5370
|
-
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${
|
|
6665
|
+
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders2})`
|
|
5371
6666
|
).all(...missingIds);
|
|
5372
6667
|
for (const s of extras) symById.set(s.id, s);
|
|
5373
6668
|
}
|
|
@@ -5380,81 +6675,6 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
5380
6675
|
return { nodes, edges };
|
|
5381
6676
|
}
|
|
5382
6677
|
|
|
5383
|
-
// src/codebase-index/vector-search.ts
|
|
5384
|
-
var RRF_K = 60;
|
|
5385
|
-
var VECTOR_DIMENSIONS = 384;
|
|
5386
|
-
var NGRAM_SIZE = 3;
|
|
5387
|
-
function embedText(text) {
|
|
5388
|
-
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
5389
|
-
const normalized = text.toLowerCase().trim();
|
|
5390
|
-
if (normalized.length < NGRAM_SIZE) {
|
|
5391
|
-
const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
|
|
5392
|
-
for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
|
|
5393
|
-
const ngram = padded.slice(i, i + NGRAM_SIZE);
|
|
5394
|
-
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5395
|
-
vec[bucket] += 1;
|
|
5396
|
-
}
|
|
5397
|
-
} else {
|
|
5398
|
-
for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
|
|
5399
|
-
const ngram = normalized.slice(i, i + NGRAM_SIZE);
|
|
5400
|
-
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5401
|
-
vec[bucket] += 1;
|
|
5402
|
-
}
|
|
5403
|
-
}
|
|
5404
|
-
let norm = 0;
|
|
5405
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5406
|
-
norm += vec[i] * vec[i];
|
|
5407
|
-
}
|
|
5408
|
-
norm = Math.sqrt(norm);
|
|
5409
|
-
if (norm > 0) {
|
|
5410
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5411
|
-
vec[i] /= norm;
|
|
5412
|
-
}
|
|
5413
|
-
}
|
|
5414
|
-
return vec;
|
|
5415
|
-
}
|
|
5416
|
-
function hashNgram(str) {
|
|
5417
|
-
let hash = 2166136261;
|
|
5418
|
-
for (let i = 0; i < str.length; i++) {
|
|
5419
|
-
hash ^= str.charCodeAt(i);
|
|
5420
|
-
hash = Math.imul(hash, 16777619);
|
|
5421
|
-
}
|
|
5422
|
-
return hash >>> 0;
|
|
5423
|
-
}
|
|
5424
|
-
function cosineSimilarity(a, b) {
|
|
5425
|
-
let dot = 0;
|
|
5426
|
-
const len = Math.min(a.length, b.length);
|
|
5427
|
-
for (let i = 0; i < len; i++) {
|
|
5428
|
-
dot += a[i] * b[i];
|
|
5429
|
-
}
|
|
5430
|
-
return dot;
|
|
5431
|
-
}
|
|
5432
|
-
function encodeVector(vec) {
|
|
5433
|
-
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
5434
|
-
}
|
|
5435
|
-
function decodeVector(buf) {
|
|
5436
|
-
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
5437
|
-
const copy = new Float32Array(buf.byteLength / 4);
|
|
5438
|
-
for (let i = 0; i < copy.length; i++) {
|
|
5439
|
-
copy[i] = view.getFloat32(i * 4, true);
|
|
5440
|
-
}
|
|
5441
|
-
return copy;
|
|
5442
|
-
}
|
|
5443
|
-
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
5444
|
-
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
5445
|
-
const scored = [];
|
|
5446
|
-
for (const id of allIds) {
|
|
5447
|
-
const bm25Rank = bm25Ranks.get(id);
|
|
5448
|
-
const vecRank = vectorRanks.get(id);
|
|
5449
|
-
let score = 0;
|
|
5450
|
-
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
5451
|
-
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
5452
|
-
scored.push([id, score]);
|
|
5453
|
-
}
|
|
5454
|
-
scored.sort((a, b) => b[1] - a[1]);
|
|
5455
|
-
return scored;
|
|
5456
|
-
}
|
|
5457
|
-
|
|
5458
6678
|
// src/codebase-index/writer-mutations.ts
|
|
5459
6679
|
function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvailable, allocateSymbolIds, invalidateIncomingRefsForFiles, resolveRefsForNamesUnsafe2, entries, options = {}) {
|
|
5460
6680
|
if (entries.length === 0 && (options.deleteForFiles?.length ?? 0) === 0) {
|
|
@@ -5466,24 +6686,29 @@ function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvail
|
|
|
5466
6686
|
for (const ref of entry.refs) affectedNames.add(ref.toName);
|
|
5467
6687
|
}
|
|
5468
6688
|
if (options.deleteForFiles && options.deleteForFiles.length > 0) {
|
|
5469
|
-
const placeholders = options.deleteForFiles.map(() => "?").join(",");
|
|
5470
6689
|
for (const name of invalidateIncomingRefsForFiles(options.deleteForFiles)) {
|
|
5471
6690
|
affectedNames.add(name);
|
|
5472
6691
|
}
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
6692
|
+
let cursor = 0;
|
|
6693
|
+
for (const take of inListChunks(options.deleteForFiles.length, Math.floor(maxSqlVars / 4))) {
|
|
6694
|
+
const bucket = padToInBucket(options.deleteForFiles.slice(cursor, cursor + take));
|
|
6695
|
+
cursor += take;
|
|
6696
|
+
const ph = placeholders(bucket.length);
|
|
6697
|
+
if (ftsAvailable) {
|
|
6698
|
+
stmtFn(
|
|
6699
|
+
`DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
6700
|
+
).run(...bucket);
|
|
6701
|
+
}
|
|
6702
|
+
if (vectorsAvailable) {
|
|
6703
|
+
stmtFn(
|
|
6704
|
+
`DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
6705
|
+
).run(...bucket);
|
|
6706
|
+
}
|
|
5479
6707
|
stmtFn(
|
|
5480
|
-
`DELETE FROM
|
|
5481
|
-
).run(...
|
|
6708
|
+
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
6709
|
+
).run(...bucket);
|
|
6710
|
+
stmtFn(`DELETE FROM symbols WHERE file IN (${ph})`).run(...bucket);
|
|
5482
6711
|
}
|
|
5483
|
-
stmtFn(
|
|
5484
|
-
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
5485
|
-
).run(...options.deleteForFiles);
|
|
5486
|
-
stmtFn(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
|
|
5487
6712
|
}
|
|
5488
6713
|
const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
|
|
5489
6714
|
let nextId = allocateSymbolIds(totalSymbols);
|
|
@@ -5515,12 +6740,14 @@ function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvail
|
|
|
5515
6740
|
text: buildIndexableText(s.name, s.signature, s.docComment)
|
|
5516
6741
|
});
|
|
5517
6742
|
}
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
6743
|
+
if (vectorsAvailable) {
|
|
6744
|
+
vectorRows.push({
|
|
6745
|
+
id,
|
|
6746
|
+
vector: encodeVector(
|
|
6747
|
+
embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
|
|
6748
|
+
)
|
|
6749
|
+
});
|
|
6750
|
+
}
|
|
5524
6751
|
const inserted = { ...s, id };
|
|
5525
6752
|
allInserted.push(inserted);
|
|
5526
6753
|
insertedForEntry.push(inserted);
|
|
@@ -5612,9 +6839,8 @@ var CORE_TABLES_SQL = `
|
|
|
5612
6839
|
signature TEXT NOT NULL DEFAULT '',
|
|
5613
6840
|
doc_comment TEXT NOT NULL DEFAULT '',
|
|
5614
6841
|
scope TEXT NOT NULL DEFAULT '',
|
|
5615
|
-
text TEXT NOT NULL DEFAULT ''
|
|
5616
|
-
|
|
5617
|
-
);
|
|
6842
|
+
text TEXT NOT NULL DEFAULT ''
|
|
6843
|
+
);
|
|
5618
6844
|
`;
|
|
5619
6845
|
var FILE_INDEX_SQL = [
|
|
5620
6846
|
"CREATE INDEX IF NOT EXISTS idx_f_package ON files(package)"
|
|
@@ -5625,7 +6851,6 @@ var SYMBOL_INDEX_SQL = [
|
|
|
5625
6851
|
"CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)",
|
|
5626
6852
|
"CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)",
|
|
5627
6853
|
"CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)",
|
|
5628
|
-
"CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)",
|
|
5629
6854
|
"CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)"
|
|
5630
6855
|
];
|
|
5631
6856
|
var REFS_TABLE_SQL = `
|
|
@@ -5700,11 +6925,12 @@ function getUnresolvedImportsWithStatement(stmtFn, maxSqlVars, onlyFiles) {
|
|
|
5700
6925
|
return stmtFn(base).all();
|
|
5701
6926
|
}
|
|
5702
6927
|
const out = [];
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
const
|
|
6928
|
+
let cursor = 0;
|
|
6929
|
+
for (const take of inListChunks(onlyFiles.length, maxSqlVars)) {
|
|
6930
|
+
const chunk = padToInBucket(onlyFiles.slice(cursor, cursor + take));
|
|
6931
|
+
cursor += take;
|
|
5706
6932
|
out.push(
|
|
5707
|
-
...stmtFn(`${base} AND s.file IN (${placeholders})`).all(...chunk)
|
|
6933
|
+
...stmtFn(`${base} AND s.file IN (${placeholders(chunk.length)})`).all(...chunk)
|
|
5708
6934
|
);
|
|
5709
6935
|
}
|
|
5710
6936
|
return out;
|
|
@@ -5775,16 +7001,18 @@ function applyImportResolutionsWithStatement(db, stmtFn, runWithRetry, maxSqlVar
|
|
|
5775
7001
|
)`
|
|
5776
7002
|
);
|
|
5777
7003
|
const chunkSize = Math.max(1, Math.floor(maxSqlVars / 4));
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
const
|
|
7004
|
+
let cursor = 0;
|
|
7005
|
+
for (const take of ladderChunkSizes(resolutions.length, chunkSize)) {
|
|
7006
|
+
const chunk = resolutions.slice(cursor, cursor + take);
|
|
7007
|
+
cursor += take;
|
|
7008
|
+
const valuesPh = chunk.map(() => "(?, ?, ?, ?)").join(", ");
|
|
5781
7009
|
const binds = [];
|
|
5782
7010
|
for (const entry of chunk) {
|
|
5783
7011
|
binds.push(entry.fromFile, entry.lang, entry.module, entry.toFile);
|
|
5784
7012
|
}
|
|
5785
7013
|
stmtFn(
|
|
5786
7014
|
`INSERT INTO temp.import_resolution(from_file, lang, module, to_file)
|
|
5787
|
-
VALUES ${
|
|
7015
|
+
VALUES ${valuesPh}`
|
|
5788
7016
|
).run(...binds);
|
|
5789
7017
|
}
|
|
5790
7018
|
db.exec(
|
|
@@ -5821,9 +7049,11 @@ function resolveRefsForNamesUnsafe(stmtFn, maxSqlVars, names) {
|
|
|
5821
7049
|
const list = [...names].filter((name) => name.length > 0);
|
|
5822
7050
|
if (list.length === 0) return 0;
|
|
5823
7051
|
let total = 0;
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
const
|
|
7052
|
+
let cursor = 0;
|
|
7053
|
+
for (const take of inListChunks(list.length, maxSqlVars)) {
|
|
7054
|
+
const chunk = padToInBucket(list.slice(cursor, cursor + take));
|
|
7055
|
+
cursor += take;
|
|
7056
|
+
const ph = placeholders(chunk.length);
|
|
5827
7057
|
try {
|
|
5828
7058
|
const result = stmtFn(
|
|
5829
7059
|
`UPDATE refs
|
|
@@ -5832,16 +7062,16 @@ function resolveRefsForNamesUnsafe(stmtFn, maxSqlVars, names) {
|
|
|
5832
7062
|
SELECT sym.name AS name, lf.family AS family, MIN(sym.id) AS id
|
|
5833
7063
|
FROM symbols sym
|
|
5834
7064
|
JOIN lang_family lf ON lf.lang = sym.lang
|
|
5835
|
-
WHERE sym.name IN (${
|
|
7065
|
+
WHERE sym.name IN (${ph})
|
|
5836
7066
|
GROUP BY sym.name, lf.family
|
|
5837
7067
|
UNION ALL
|
|
5838
7068
|
SELECT sym.name AS name, '${LANG_FAMILY_WILDCARD}' AS family, MIN(sym.id) AS id
|
|
5839
7069
|
FROM symbols sym
|
|
5840
|
-
WHERE sym.name IN (${
|
|
7070
|
+
WHERE sym.name IN (${ph})
|
|
5841
7071
|
GROUP BY sym.name
|
|
5842
7072
|
) AS s,
|
|
5843
7073
|
lang_family AS rf
|
|
5844
|
-
WHERE refs.to_name IN (${
|
|
7074
|
+
WHERE refs.to_name IN (${ph})
|
|
5845
7075
|
AND rf.lang = refs.lang
|
|
5846
7076
|
AND s.name = refs.to_name
|
|
5847
7077
|
AND s.family = rf.family`
|
|
@@ -5853,7 +7083,7 @@ function resolveRefsForNamesUnsafe(stmtFn, maxSqlVars, names) {
|
|
|
5853
7083
|
SELECT sym.id FROM symbols sym
|
|
5854
7084
|
WHERE sym.name = refs.to_name AND ${FAMILY_MATCH_SQL}
|
|
5855
7085
|
ORDER BY sym.id LIMIT 1
|
|
5856
|
-
) WHERE refs.to_name IN (${
|
|
7086
|
+
) WHERE refs.to_name IN (${ph})
|
|
5857
7087
|
AND EXISTS (
|
|
5858
7088
|
SELECT 1 FROM symbols sym
|
|
5859
7089
|
WHERE sym.name = refs.to_name AND ${FAMILY_MATCH_SQL}
|
|
@@ -5910,7 +7140,7 @@ function buildWriterSearchWhere(query, filter) {
|
|
|
5910
7140
|
const conditions = [];
|
|
5911
7141
|
const values = [];
|
|
5912
7142
|
let effectiveKind = filter?.kind;
|
|
5913
|
-
if (filter?.lspKind
|
|
7143
|
+
if (filter?.lspKind != null) {
|
|
5914
7144
|
const mapped = lspKindToInternalKind(filter.lspKind);
|
|
5915
7145
|
if (mapped !== null) {
|
|
5916
7146
|
effectiveKind = mapped;
|
|
@@ -5989,7 +7219,7 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
|
|
|
5989
7219
|
);
|
|
5990
7220
|
}
|
|
5991
7221
|
let effectiveKind = filter?.kind;
|
|
5992
|
-
if (filter?.lspKind
|
|
7222
|
+
if (filter?.lspKind != null) {
|
|
5993
7223
|
const mapped = lspKindToInternalKind(filter.lspKind);
|
|
5994
7224
|
if (mapped === null) return { results: [], total: 0 };
|
|
5995
7225
|
effectiveKind = mapped;
|
|
@@ -6026,15 +7256,14 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
|
|
|
6026
7256
|
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
6027
7257
|
}
|
|
6028
7258
|
const where = conditions.join(" AND ");
|
|
6029
|
-
const countRows = stmtFn(
|
|
6030
|
-
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
6031
|
-
).all(...values);
|
|
6032
|
-
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
6033
|
-
if (total === 0) return { results: [], total: 0 };
|
|
6034
7259
|
const bm25Rows = stmtFn(
|
|
6035
7260
|
`SELECT s.id, s.lang, s.kind, s.name, s.file, s.line, s.col, s.signature, s.doc_comment,
|
|
6036
7261
|
-bm25(symbols_fts) AS score,
|
|
6037
|
-
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
7262
|
+
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet,
|
|
7263
|
+
-- Keep this uncorrelated: referencing outer columns turns it into
|
|
7264
|
+
-- a per-row subquery and defeats the one-count-per-statement win.
|
|
7265
|
+
(SELECT COUNT(*) FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
7266
|
+
WHERE ${where}) AS total_count
|
|
6038
7267
|
FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
6039
7268
|
WHERE ${where}
|
|
6040
7269
|
ORDER BY
|
|
@@ -6043,13 +7272,15 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
|
|
|
6043
7272
|
ELSE 2 END,
|
|
6044
7273
|
bm25(symbols_fts), lower(s.name), s.file, s.line, s.col, s.id
|
|
6045
7274
|
LIMIT ?`
|
|
6046
|
-
).all(...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
6047
|
-
if (
|
|
7275
|
+
).all(...values, ...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
7276
|
+
if (bm25Rows.length === 0) return { results: [], total: 0 };
|
|
7277
|
+
const total = Number(bm25Rows[0]?.total_count ?? 0);
|
|
7278
|
+
if (vectorsAvailable) {
|
|
6048
7279
|
const queryVec = embedText(query);
|
|
6049
7280
|
const candidateIds = bm25Rows.map((r) => r.id);
|
|
6050
|
-
const
|
|
7281
|
+
const placeholders2 = candidateIds.map(() => "?").join(",");
|
|
6051
7282
|
const vecRows = stmtFn(
|
|
6052
|
-
`SELECT sv.symbol_id, sv.vector FROM symbol_vectors sv WHERE sv.symbol_id IN (${
|
|
7283
|
+
`SELECT sv.symbol_id, sv.vector FROM symbol_vectors sv WHERE sv.symbol_id IN (${placeholders2})`
|
|
6053
7284
|
).all(...candidateIds);
|
|
6054
7285
|
const vecScores = vecRows.map((r) => ({
|
|
6055
7286
|
id: r.symbol_id,
|
|
@@ -6237,9 +7468,9 @@ var IndexStore = class _IndexStore {
|
|
|
6237
7468
|
}
|
|
6238
7469
|
constructor(projectRoot, opts = {}) {
|
|
6239
7470
|
this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
|
|
6240
|
-
|
|
7471
|
+
fs9.mkdirSync(this.indexDir, { recursive: true });
|
|
6241
7472
|
const Database = loadDatabaseSync();
|
|
6242
|
-
this.db = new Database(
|
|
7473
|
+
this.db = new Database(path14.join(this.indexDir, DB_FILE2));
|
|
6243
7474
|
applyIndexStorePragmas(this.db);
|
|
6244
7475
|
this.initSchema();
|
|
6245
7476
|
}
|
|
@@ -6368,7 +7599,11 @@ var IndexStore = class _IndexStore {
|
|
|
6368
7599
|
);
|
|
6369
7600
|
if (symbolCount !== ftsCount) {
|
|
6370
7601
|
this.db.exec("DELETE FROM symbols_fts");
|
|
6371
|
-
if (
|
|
7602
|
+
if (vectorEmbeddingEnabled() && this.stmt(
|
|
7603
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='symbol_vectors'"
|
|
7604
|
+
).get() !== void 0) {
|
|
7605
|
+
this.db.exec("DELETE FROM symbol_vectors");
|
|
7606
|
+
}
|
|
6372
7607
|
const rows = this.stmt(
|
|
6373
7608
|
"SELECT id, name, signature, doc_comment FROM symbols ORDER BY id"
|
|
6374
7609
|
).all();
|
|
@@ -6387,8 +7622,13 @@ var IndexStore = class _IndexStore {
|
|
|
6387
7622
|
this.ftsAvailable = false;
|
|
6388
7623
|
}
|
|
6389
7624
|
try {
|
|
6390
|
-
|
|
6391
|
-
|
|
7625
|
+
if (vectorEmbeddingEnabled()) {
|
|
7626
|
+
this.db.exec(SYMBOL_VECTORS_TABLE_SQL);
|
|
7627
|
+
this.vectorsAvailable = true;
|
|
7628
|
+
} else {
|
|
7629
|
+
this.db.exec("DROP TABLE IF EXISTS symbol_vectors");
|
|
7630
|
+
this.vectorsAvailable = false;
|
|
7631
|
+
}
|
|
6392
7632
|
} catch {
|
|
6393
7633
|
this.vectorsAvailable = false;
|
|
6394
7634
|
}
|
|
@@ -6423,14 +7663,22 @@ var IndexStore = class _IndexStore {
|
|
|
6423
7663
|
}
|
|
6424
7664
|
invalidateIncomingRefsForFiles(files) {
|
|
6425
7665
|
if (files.length === 0) return /* @__PURE__ */ new Set();
|
|
6426
|
-
const
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
7666
|
+
const names = [];
|
|
7667
|
+
let cursor = 0;
|
|
7668
|
+
for (const take of inListChunks(files.length, _IndexStore.MAX_SQL_VARS)) {
|
|
7669
|
+
const bucket = padToInBucket(files.slice(cursor, cursor + take));
|
|
7670
|
+
cursor += take;
|
|
7671
|
+
const ph = placeholders(bucket.length);
|
|
7672
|
+
for (const row of this.stmt(`SELECT DISTINCT name FROM symbols WHERE file IN (${ph})`).all(
|
|
7673
|
+
...bucket
|
|
7674
|
+
)) {
|
|
7675
|
+
names.push(row.name);
|
|
7676
|
+
}
|
|
7677
|
+
this.stmt(
|
|
7678
|
+
`UPDATE refs SET to_id = NULL
|
|
7679
|
+
WHERE to_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
7680
|
+
).run(...bucket);
|
|
7681
|
+
}
|
|
6434
7682
|
return new Set(names);
|
|
6435
7683
|
}
|
|
6436
7684
|
resolveRefsForNamesUnsafe(names) {
|
|
@@ -6464,6 +7712,14 @@ var IndexStore = class _IndexStore {
|
|
|
6464
7712
|
if (this.ftsAvailable) {
|
|
6465
7713
|
ftsRows.push({ id, text: buildIndexableText(s.name, s.signature, s.docComment) });
|
|
6466
7714
|
}
|
|
7715
|
+
if (this.vectorsAvailable) {
|
|
7716
|
+
vectorRows.push({
|
|
7717
|
+
id,
|
|
7718
|
+
vector: encodeVector(
|
|
7719
|
+
embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
|
|
7720
|
+
)
|
|
7721
|
+
});
|
|
7722
|
+
}
|
|
6467
7723
|
result.push({ ...s, id });
|
|
6468
7724
|
}
|
|
6469
7725
|
bulkInsertSymbolsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, bulk);
|
|
@@ -6496,15 +7752,15 @@ var IndexStore = class _IndexStore {
|
|
|
6496
7752
|
const affectedNames = this.invalidateIncomingRefsForFiles([file]);
|
|
6497
7753
|
if (this.ftsAvailable) {
|
|
6498
7754
|
this.stmt(
|
|
6499
|
-
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE
|
|
7755
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6500
7756
|
).run(file);
|
|
6501
7757
|
}
|
|
6502
7758
|
if (this.vectorsAvailable) {
|
|
6503
7759
|
this.stmt(
|
|
6504
|
-
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE
|
|
7760
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6505
7761
|
).run(file);
|
|
6506
7762
|
}
|
|
6507
|
-
this.stmt("DELETE FROM symbols WHERE
|
|
7763
|
+
this.stmt("DELETE FROM symbols WHERE file = ?").run(file);
|
|
6508
7764
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
6509
7765
|
this.commitWriteTransaction(ownsTransaction);
|
|
6510
7766
|
} catch (error) {
|
|
@@ -6521,18 +7777,18 @@ var IndexStore = class _IndexStore {
|
|
|
6521
7777
|
const affectedNames = this.invalidateIncomingRefsForFiles([file]);
|
|
6522
7778
|
if (this.ftsAvailable) {
|
|
6523
7779
|
this.stmt(
|
|
6524
|
-
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE
|
|
7780
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6525
7781
|
).run(file);
|
|
6526
7782
|
}
|
|
6527
7783
|
if (this.vectorsAvailable) {
|
|
6528
7784
|
this.stmt(
|
|
6529
|
-
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE
|
|
7785
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6530
7786
|
).run(file);
|
|
6531
7787
|
}
|
|
6532
|
-
this.stmt(
|
|
6533
|
-
|
|
6534
|
-
)
|
|
6535
|
-
this.stmt("DELETE FROM symbols WHERE
|
|
7788
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
|
|
7789
|
+
file
|
|
7790
|
+
);
|
|
7791
|
+
this.stmt("DELETE FROM symbols WHERE file = ?").run(file);
|
|
6536
7792
|
this.stmt("DELETE FROM files WHERE file = ?").run(file);
|
|
6537
7793
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
6538
7794
|
this.commitWriteTransaction(ownsTransaction);
|
|
@@ -6636,6 +7892,10 @@ var IndexStore = class _IndexStore {
|
|
|
6636
7892
|
getStats() {
|
|
6637
7893
|
return getStatsWithStatement((sql) => this.stmt(sql), this.indexDir);
|
|
6638
7894
|
}
|
|
7895
|
+
/** P2.5: minimal summary for search-response piggyback (see writer-admin). */
|
|
7896
|
+
getIndexSummary() {
|
|
7897
|
+
return getIndexSummaryWithStatement((sql) => this.stmt(sql));
|
|
7898
|
+
}
|
|
6639
7899
|
setLastIndexed(ts2) {
|
|
6640
7900
|
this.runWithRetry(() => {
|
|
6641
7901
|
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(
|
|
@@ -6737,18 +7997,18 @@ var IndexStore = class _IndexStore {
|
|
|
6737
7997
|
const affectedNames = this.invalidateIncomingRefsForFiles([meta.file]);
|
|
6738
7998
|
if (this.ftsAvailable) {
|
|
6739
7999
|
this.stmt(
|
|
6740
|
-
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE
|
|
8000
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6741
8001
|
).run(meta.file);
|
|
6742
8002
|
}
|
|
6743
8003
|
if (this.vectorsAvailable) {
|
|
6744
8004
|
this.stmt(
|
|
6745
|
-
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE
|
|
8005
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6746
8006
|
).run(meta.file);
|
|
6747
8007
|
}
|
|
6748
|
-
this.stmt(
|
|
6749
|
-
|
|
6750
|
-
)
|
|
6751
|
-
this.stmt("DELETE FROM symbols WHERE
|
|
8008
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
|
|
8009
|
+
meta.file
|
|
8010
|
+
);
|
|
8011
|
+
this.stmt("DELETE FROM symbols WHERE file = ?").run(meta.file);
|
|
6752
8012
|
this.stmt(
|
|
6753
8013
|
`INSERT INTO files(file, lang, mtime_ms, content_hash, symbol_count, last_indexed)
|
|
6754
8014
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
@@ -6780,6 +8040,27 @@ var IndexStore = class _IndexStore {
|
|
|
6780
8040
|
} catch {
|
|
6781
8041
|
}
|
|
6782
8042
|
}
|
|
8043
|
+
/**
|
|
8044
|
+
* P4.14: best-effort WAL checkpoint for idle-time maintenance.
|
|
8045
|
+
*
|
|
8046
|
+
* `wal_autocheckpoint` is PASSIVE and only attempts work after a COMMIT —
|
|
8047
|
+
* once writes stop, nothing fires again, so the WAL keeps whatever frames
|
|
8048
|
+
* the last burst left. This probes with PASSIVE first (never blocks; busy=1
|
|
8049
|
+
* means readers still hold WAL snapshots) and only issues the TRUNCATE —
|
|
8050
|
+
* which resets index.db-wal to zero bytes — when the checkpointer can
|
|
8051
|
+
* proceed immediately. Callers run this on the daemon's single thread, so
|
|
8052
|
+
* never wait on readers here: busy means "retry at the next idle window".
|
|
8053
|
+
*/
|
|
8054
|
+
checkpointWal() {
|
|
8055
|
+
try {
|
|
8056
|
+
const probe = this.db.prepare("PRAGMA wal_checkpoint(PASSIVE)").get();
|
|
8057
|
+
if (Number(probe?.busy ?? 1) !== 0) return false;
|
|
8058
|
+
const done = this.db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
|
|
8059
|
+
return Number(done?.busy ?? 1) === 0;
|
|
8060
|
+
} catch {
|
|
8061
|
+
return false;
|
|
8062
|
+
}
|
|
8063
|
+
}
|
|
6783
8064
|
compactIfNeeded(options = {}) {
|
|
6784
8065
|
const minBytes = options.minBytes ?? 256 * 1024 * 1024;
|
|
6785
8066
|
const minFreeRatio = options.minFreeRatio ?? 0.35;
|
|
@@ -6865,7 +8146,9 @@ function resolveParallelBatch() {
|
|
|
6865
8146
|
return indexParallelBatchSize(availableParallelism());
|
|
6866
8147
|
}
|
|
6867
8148
|
function shouldUseParserWorkerPool(candidateFileCount, parseBatchCount) {
|
|
6868
|
-
|
|
8149
|
+
const threshold = resolveWorkerPoolThreshold();
|
|
8150
|
+
if (threshold === 0) return false;
|
|
8151
|
+
return !isFrugalPerf() && candidateFileCount >= threshold && parseBatchCount > 1;
|
|
6869
8152
|
}
|
|
6870
8153
|
function yieldEventLoop() {
|
|
6871
8154
|
return new Promise((resolve4) => setImmediate(resolve4));
|
|
@@ -6888,15 +8171,15 @@ var IndexSourceChangedError = class extends Error {
|
|
|
6888
8171
|
name = "IndexSourceChangedError";
|
|
6889
8172
|
};
|
|
6890
8173
|
function isWithinProject(projectRoot, file) {
|
|
6891
|
-
const rel =
|
|
6892
|
-
return rel !== "" && !rel.startsWith(`..${
|
|
8174
|
+
const rel = path15.relative(projectRoot, file);
|
|
8175
|
+
return rel !== "" && !rel.startsWith(`..${path15.sep}`) && rel !== ".." && !path15.isAbsolute(rel);
|
|
6893
8176
|
}
|
|
6894
8177
|
function isMissingPathError(err) {
|
|
6895
8178
|
const code = err?.code;
|
|
6896
8179
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
6897
8180
|
}
|
|
6898
8181
|
function normalizeComparablePath(value) {
|
|
6899
|
-
const resolved =
|
|
8182
|
+
const resolved = path15.resolve(value);
|
|
6900
8183
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
6901
8184
|
}
|
|
6902
8185
|
function gitOutput(projectRoot, args) {
|
|
@@ -6942,24 +8225,24 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
6942
8225
|
const record = statusRecords[i];
|
|
6943
8226
|
if (!record) continue;
|
|
6944
8227
|
const status = record.slice(0, 2);
|
|
6945
|
-
const changedPath =
|
|
8228
|
+
const changedPath = path15.resolve(projectRoot, record.slice(3));
|
|
6946
8229
|
dirty.add(changedPath);
|
|
6947
8230
|
if (status.includes("D")) deleted.add(changedPath);
|
|
6948
8231
|
if (status.includes("R") || status.includes("C")) {
|
|
6949
8232
|
const source = statusRecords[++i];
|
|
6950
|
-
if (source) dirty.add(
|
|
8233
|
+
if (source) dirty.add(path15.resolve(projectRoot, source));
|
|
6951
8234
|
}
|
|
6952
8235
|
}
|
|
6953
8236
|
const files = [];
|
|
6954
8237
|
for (const relative3 of output.toString("utf8").split("\0")) {
|
|
6955
8238
|
if (!relative3) continue;
|
|
6956
8239
|
const portable = relative3.replace(/\\/g, "/");
|
|
6957
|
-
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(
|
|
8240
|
+
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path15.posix.basename(portable))) {
|
|
6958
8241
|
continue;
|
|
6959
8242
|
}
|
|
6960
|
-
const full =
|
|
8243
|
+
const full = path15.resolve(projectRoot, relative3);
|
|
6961
8244
|
if (deleted.has(full)) continue;
|
|
6962
|
-
const ext =
|
|
8245
|
+
const ext = path15.extname(relative3).toLowerCase();
|
|
6963
8246
|
if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
|
|
6964
8247
|
}
|
|
6965
8248
|
const snapshot = createHash2("sha256").update(stagedOutput).update("\0").update(statusOutput);
|
|
@@ -6967,7 +8250,7 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
6967
8250
|
for (const dirtyFile of [...dirty].sort()) {
|
|
6968
8251
|
if (!indexedFiles.has(dirtyFile) || deleted.has(dirtyFile)) continue;
|
|
6969
8252
|
snapshot.update("\0").update(dirtyFile).update("\0");
|
|
6970
|
-
snapshot.update(xxhash64String(await
|
|
8253
|
+
snapshot.update(xxhash64String(await fs10.readFile(dirtyFile, "utf8")));
|
|
6971
8254
|
}
|
|
6972
8255
|
return {
|
|
6973
8256
|
files,
|
|
@@ -7003,7 +8286,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
7003
8286
|
}
|
|
7004
8287
|
let entries;
|
|
7005
8288
|
try {
|
|
7006
|
-
entries = await
|
|
8289
|
+
entries = await fs10.readdir(dir, { withFileTypes: true });
|
|
7007
8290
|
} catch (err) {
|
|
7008
8291
|
complete = false;
|
|
7009
8292
|
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -7012,14 +8295,14 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
7012
8295
|
dirCount++;
|
|
7013
8296
|
for (const e of entries) {
|
|
7014
8297
|
if (ignoreSet.has(e.name)) continue;
|
|
7015
|
-
const full =
|
|
7016
|
-
const rel =
|
|
8298
|
+
const full = path15.join(dir, e.name);
|
|
8299
|
+
const rel = path15.relative(projectRoot, full).replace(/\\/g, "/");
|
|
7017
8300
|
if (e.isDirectory()) {
|
|
7018
8301
|
if (isGitIgnored(rel, true)) continue;
|
|
7019
8302
|
await walk(full);
|
|
7020
8303
|
} else if (e.isFile()) {
|
|
7021
8304
|
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
7022
|
-
const ext =
|
|
8305
|
+
const ext = path15.extname(e.name).toLowerCase();
|
|
7023
8306
|
if (indexableExts.has(ext) || detectLang(full) !== null) {
|
|
7024
8307
|
results.push(full);
|
|
7025
8308
|
}
|
|
@@ -7105,10 +8388,10 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7105
8388
|
let trustedUnchanged;
|
|
7106
8389
|
let discoverySnapshotKey;
|
|
7107
8390
|
if (opts.files && opts.files.length > 0) {
|
|
7108
|
-
files = opts.files.map((f) =>
|
|
8391
|
+
files = opts.files.map((f) => path15.resolve(projectRoot, f)).filter((f) => {
|
|
7109
8392
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
7110
|
-
const rel =
|
|
7111
|
-
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(
|
|
8393
|
+
const rel = path15.relative(projectRoot, f).replace(/\\/g, "/");
|
|
8394
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path15.basename(f)) && !isGitIgnored(rel, false);
|
|
7112
8395
|
});
|
|
7113
8396
|
} else {
|
|
7114
8397
|
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
@@ -7141,7 +8424,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7141
8424
|
if (!meta || !trustedUnchanged.has(file)) return true;
|
|
7142
8425
|
langStats[meta.lang] = (langStats[meta.lang] ?? 0) + meta.symbolCount;
|
|
7143
8426
|
symbolsIndexed += meta.symbolCount;
|
|
7144
|
-
filesIndexed++;
|
|
7145
8427
|
filesSkipped++;
|
|
7146
8428
|
filesPreSkipped++;
|
|
7147
8429
|
return false;
|
|
@@ -7170,7 +8452,7 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7170
8452
|
async (file) => {
|
|
7171
8453
|
let stat3;
|
|
7172
8454
|
try {
|
|
7173
|
-
stat3 = await
|
|
8455
|
+
stat3 = await fs10.stat(file, statOpts);
|
|
7174
8456
|
} catch (e) {
|
|
7175
8457
|
if (isAbortError(e)) throw e;
|
|
7176
8458
|
return {
|
|
@@ -7197,7 +8479,7 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7197
8479
|
const meta = existingMeta.get(file);
|
|
7198
8480
|
let content;
|
|
7199
8481
|
try {
|
|
7200
|
-
content = await
|
|
8482
|
+
content = await fs10.readFile(file, { encoding: "utf8", signal });
|
|
7201
8483
|
} catch (e) {
|
|
7202
8484
|
if (isAbortError(e)) throw e;
|
|
7203
8485
|
return {
|
|
@@ -7262,22 +8544,19 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7262
8544
|
}
|
|
7263
8545
|
}
|
|
7264
8546
|
if (!pool) {
|
|
7265
|
-
await
|
|
7266
|
-
toParse.map(
|
|
7267
|
-
try {
|
|
7268
|
-
const parsed = await parseFileContent(item.file, item.content, item.lang);
|
|
7269
|
-
const settled = statReadParse[item.index];
|
|
7270
|
-
if (settled.status === "fulfilled") {
|
|
7271
|
-
settled.value.parsed = parsed;
|
|
7272
|
-
}
|
|
7273
|
-
} catch (e) {
|
|
7274
|
-
const settled = statReadParse[item.index];
|
|
7275
|
-
if (settled.status === "fulfilled") {
|
|
7276
|
-
settled.value.error = `parse error: ${e instanceof Error ? e.message : String(e)}`;
|
|
7277
|
-
}
|
|
7278
|
-
}
|
|
7279
|
-
})
|
|
8547
|
+
const parsedAll = await parseFilesContent(
|
|
8548
|
+
toParse.map((p) => ({ file: p.file, content: p.content, lang: p.lang }))
|
|
7280
8549
|
);
|
|
8550
|
+
for (let pi2 = 0; pi2 < parsedAll.length && pi2 < toParse.length; pi2++) {
|
|
8551
|
+
const settled = statReadParse[toParse[pi2].index];
|
|
8552
|
+
if (settled.status !== "fulfilled") continue;
|
|
8553
|
+
const slot = parsedAll[pi2];
|
|
8554
|
+
if (slot.result) {
|
|
8555
|
+
settled.value.parsed = slot.result;
|
|
8556
|
+
} else {
|
|
8557
|
+
settled.value.error = `parse error: ${slot.error ?? `no result for ${toParse[pi2].file}`}`;
|
|
8558
|
+
}
|
|
8559
|
+
}
|
|
7281
8560
|
}
|
|
7282
8561
|
}
|
|
7283
8562
|
const batchEntries = [];
|
|
@@ -7303,7 +8582,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7303
8582
|
if (result.skippedMeta) {
|
|
7304
8583
|
langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
|
|
7305
8584
|
symbolsIndexed += result.skippedMeta.symbolCount;
|
|
7306
|
-
filesIndexed++;
|
|
7307
8585
|
filesSkipped++;
|
|
7308
8586
|
const stored = existingMeta.get(file);
|
|
7309
8587
|
if (stored && stored.mtimeMs !== result.skippedMeta.mtimeMs) {
|
|
@@ -7328,7 +8606,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7328
8606
|
lastIndexed: Date.now(),
|
|
7329
8607
|
contentHash: result.contentHash ?? ""
|
|
7330
8608
|
});
|
|
7331
|
-
filesIndexed++;
|
|
7332
8609
|
filesEmpty++;
|
|
7333
8610
|
}
|
|
7334
8611
|
continue;
|
|
@@ -7342,7 +8619,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7342
8619
|
lastIndexed: Date.now(),
|
|
7343
8620
|
contentHash: result.contentHash ?? ""
|
|
7344
8621
|
});
|
|
7345
|
-
filesIndexed++;
|
|
7346
8622
|
filesEmpty++;
|
|
7347
8623
|
continue;
|
|
7348
8624
|
}
|
|
@@ -7472,7 +8748,7 @@ async function indexService(args, hooks = {}) {
|
|
|
7472
8748
|
function searchService(args) {
|
|
7473
8749
|
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
7474
8750
|
try {
|
|
7475
|
-
|
|
8751
|
+
const result = store.searchRanked(
|
|
7476
8752
|
args.query,
|
|
7477
8753
|
{
|
|
7478
8754
|
kind: args.kind,
|
|
@@ -7482,6 +8758,10 @@ function searchService(args) {
|
|
|
7482
8758
|
},
|
|
7483
8759
|
args.limit
|
|
7484
8760
|
);
|
|
8761
|
+
if (result.total === 0) {
|
|
8762
|
+
return { ...result, indexSummary: store.getIndexSummary() };
|
|
8763
|
+
}
|
|
8764
|
+
return result;
|
|
7485
8765
|
} finally {
|
|
7486
8766
|
indexStorePool.release(store);
|
|
7487
8767
|
}
|
|
@@ -7545,34 +8825,71 @@ function outgoingCallsService(args) {
|
|
|
7545
8825
|
init_languages();
|
|
7546
8826
|
|
|
7547
8827
|
// src/codebase-index/project-server-client.ts
|
|
7548
|
-
import { spawn as
|
|
7549
|
-
import * as
|
|
8828
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
8829
|
+
import * as fs13 from "node:fs";
|
|
7550
8830
|
import * as net from "node:net";
|
|
7551
|
-
import { StringDecoder } from "node:string_decoder";
|
|
7552
8831
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
7553
8832
|
|
|
7554
8833
|
// src/codebase-index/binary-frame.ts
|
|
7555
8834
|
import { decode, encode } from "@msgpack/msgpack";
|
|
7556
8835
|
var BINARY_FRAME_MAGIC = 87;
|
|
8836
|
+
var MAX_BINARY_FRAME_BYTES = 256 * 1024 * 1024;
|
|
8837
|
+
var MAX_INBOUND_BINARY_FRAME_BYTES = 64 * 1024 * 1024;
|
|
7557
8838
|
function isBinaryFrame(firstByte) {
|
|
7558
8839
|
return firstByte === BINARY_FRAME_MAGIC;
|
|
7559
8840
|
}
|
|
7560
8841
|
function encodeBinaryFrame(message) {
|
|
7561
|
-
const payload = encode(message);
|
|
8842
|
+
const payload = encode(normalizeUndefined(message));
|
|
7562
8843
|
const header = Buffer.allocUnsafe(5);
|
|
7563
8844
|
header[0] = BINARY_FRAME_MAGIC;
|
|
7564
8845
|
header.writeUInt32BE(payload.length, 1);
|
|
7565
8846
|
return Buffer.concat([header, payload], 5 + payload.length);
|
|
7566
8847
|
}
|
|
8848
|
+
function normalizeUndefined(value) {
|
|
8849
|
+
if (value instanceof Date) {
|
|
8850
|
+
const time = value.getTime();
|
|
8851
|
+
return Number.isNaN(time) ? null : value.toISOString();
|
|
8852
|
+
}
|
|
8853
|
+
if (value instanceof Map) return normalizeUndefined(Object.fromEntries(value));
|
|
8854
|
+
if (value instanceof Set) return normalizeUndefined([...value]);
|
|
8855
|
+
if (value instanceof Error) {
|
|
8856
|
+
return normalizeUndefined({ name: value.name, message: value.message, stack: value.stack });
|
|
8857
|
+
}
|
|
8858
|
+
if (value instanceof RegExp) return String(value);
|
|
8859
|
+
if (value instanceof URL) return value.toJSON();
|
|
8860
|
+
if (Buffer.isBuffer(value)) return { type: "Buffer", data: [...value] };
|
|
8861
|
+
if (Array.isArray(value)) return value.map((entry) => normalizeUndefined(entry));
|
|
8862
|
+
if (!isPlainObject(value)) return value;
|
|
8863
|
+
const out = {};
|
|
8864
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
8865
|
+
if (entry === void 0) continue;
|
|
8866
|
+
out[key] = normalizeUndefined(entry);
|
|
8867
|
+
}
|
|
8868
|
+
return out;
|
|
8869
|
+
}
|
|
8870
|
+
function isPlainObject(value) {
|
|
8871
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
8872
|
+
const proto = Object.getPrototypeOf(value);
|
|
8873
|
+
return proto === Object.prototype || proto === null;
|
|
8874
|
+
}
|
|
7567
8875
|
function decodeBinaryFrame(payload) {
|
|
7568
8876
|
return decode(payload);
|
|
7569
8877
|
}
|
|
8878
|
+
function encodeJsonFrame(message) {
|
|
8879
|
+
return `${JSON.stringify(normalizeUndefined(message))}
|
|
8880
|
+
`;
|
|
8881
|
+
}
|
|
8882
|
+
|
|
8883
|
+
// src/codebase-index/project-server-client-state.ts
|
|
8884
|
+
import * as fs12 from "node:fs";
|
|
8885
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
8886
|
+
import { checkUnixSocketPath } from "@wrongstack/core/utils";
|
|
7570
8887
|
|
|
7571
8888
|
// src/codebase-index/project-server-endpoint.ts
|
|
7572
8889
|
import { createHash as createHash3 } from "node:crypto";
|
|
7573
|
-
import * as
|
|
7574
|
-
import * as
|
|
7575
|
-
import * as
|
|
8890
|
+
import * as fs11 from "node:fs";
|
|
8891
|
+
import * as os4 from "node:os";
|
|
8892
|
+
import * as path16 from "node:path";
|
|
7576
8893
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7577
8894
|
var PROJECT_INDEX_SERVER_PROTOCOL_VERSION = 1;
|
|
7578
8895
|
var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
|
|
@@ -7581,21 +8898,21 @@ var buildIdCache;
|
|
|
7581
8898
|
function projectIndexServerBuildId(entrypoint) {
|
|
7582
8899
|
const href = entrypoint instanceof URL ? entrypoint.href : entrypoint;
|
|
7583
8900
|
const cleanHref = href.split(/[?#]/, 1)[0] ?? href;
|
|
7584
|
-
const file = cleanHref.startsWith("file:") ? fileURLToPath3(cleanHref) :
|
|
8901
|
+
const file = cleanHref.startsWith("file:") ? fileURLToPath3(cleanHref) : path16.resolve(cleanHref);
|
|
7585
8902
|
try {
|
|
7586
|
-
const stat3 =
|
|
8903
|
+
const stat3 = fs11.statSync(file);
|
|
7587
8904
|
if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat3.mtimeMs && buildIdCache.size === stat3.size) {
|
|
7588
8905
|
return buildIdCache.buildId;
|
|
7589
8906
|
}
|
|
7590
|
-
const buildId = createHash3("sha256").update(
|
|
8907
|
+
const buildId = createHash3("sha256").update(fs11.readFileSync(file)).digest("hex").slice(0, 24);
|
|
7591
8908
|
buildIdCache = { file, mtimeMs: stat3.mtimeMs, size: stat3.size, buildId };
|
|
7592
8909
|
return buildId;
|
|
7593
8910
|
} catch {
|
|
7594
|
-
return `unreadable:${
|
|
8911
|
+
return `unreadable:${path16.basename(file)}`;
|
|
7595
8912
|
}
|
|
7596
8913
|
}
|
|
7597
8914
|
function normalizeLocalPath(value) {
|
|
7598
|
-
const resolved =
|
|
8915
|
+
const resolved = path16.resolve(value);
|
|
7599
8916
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
7600
8917
|
}
|
|
7601
8918
|
function projectIndexServerKey(projectRoot, indexDir) {
|
|
@@ -7607,26 +8924,16 @@ function projectIndexServerEndpoint(projectRoot, indexDir) {
|
|
|
7607
8924
|
if (process.platform === "win32") {
|
|
7608
8925
|
return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
|
|
7609
8926
|
}
|
|
7610
|
-
return
|
|
8927
|
+
return path16.join(os4.tmpdir(), PROJECT_INDEX_SERVER_SOCKET_DIR, `${key}.sock`);
|
|
7611
8928
|
}
|
|
7612
8929
|
function projectIndexServerMetadataPath(projectRoot, indexDir) {
|
|
7613
|
-
return
|
|
7614
|
-
|
|
8930
|
+
return path16.join(
|
|
8931
|
+
path16.resolve(resolveIndexDir(projectRoot, indexDir)),
|
|
7615
8932
|
PROJECT_INDEX_SERVER_METADATA_FILE
|
|
7616
8933
|
);
|
|
7617
8934
|
}
|
|
7618
8935
|
|
|
7619
|
-
// src/codebase-index/project-server-protocol.ts
|
|
7620
|
-
var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
|
|
7621
|
-
function encodeProjectServerMessage(message) {
|
|
7622
|
-
return `${JSON.stringify(message)}
|
|
7623
|
-
`;
|
|
7624
|
-
}
|
|
7625
|
-
|
|
7626
8936
|
// src/codebase-index/project-server-client-state.ts
|
|
7627
|
-
import * as fs11 from "node:fs";
|
|
7628
|
-
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
7629
|
-
import { checkUnixSocketPath } from "@wrongstack/core/utils";
|
|
7630
8937
|
var CONNECT_ATTEMPT_TIMEOUT_MS = 750;
|
|
7631
8938
|
var SERVER_START_TIMEOUT_MS = 1e4;
|
|
7632
8939
|
var SERVER_CONTROL_TIMEOUT_MS = 5e3;
|
|
@@ -7657,7 +8964,7 @@ function resolveProjectIndexDaemonAvailability(projectRoot, indexDir) {
|
|
|
7657
8964
|
for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
|
|
7658
8965
|
try {
|
|
7659
8966
|
const url = new URL(rel, import.meta.url);
|
|
7660
|
-
if (url.protocol === "file:" &&
|
|
8967
|
+
if (url.protocol === "file:" && fs12.existsSync(fileURLToPath4(url))) {
|
|
7661
8968
|
builtUrl = url;
|
|
7662
8969
|
break;
|
|
7663
8970
|
}
|
|
@@ -7745,6 +9052,12 @@ function cancellationError(signal) {
|
|
|
7745
9052
|
return signal.reason instanceof Error ? signal.reason : new Error("Indexing cancelled");
|
|
7746
9053
|
}
|
|
7747
9054
|
|
|
9055
|
+
// src/codebase-index/project-server-protocol.ts
|
|
9056
|
+
var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
|
|
9057
|
+
function encodeProjectServerMessage(message) {
|
|
9058
|
+
return encodeJsonFrame(message);
|
|
9059
|
+
}
|
|
9060
|
+
|
|
7748
9061
|
// src/codebase-index/project-server-client.ts
|
|
7749
9062
|
var ProjectServerConnection = class {
|
|
7750
9063
|
constructor(projectRoot, indexDir, endpoint) {
|
|
@@ -7757,11 +9070,13 @@ var ProjectServerConnection = class {
|
|
|
7757
9070
|
indexDir;
|
|
7758
9071
|
endpoint;
|
|
7759
9072
|
socket = null;
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
9073
|
+
/**
|
|
9074
|
+
* Raw inbound bytes for the unified per-frame reader. Frames are sniffed
|
|
9075
|
+
* individually — JSON text (newline-terminated) or binary (magic 0x57) —
|
|
9076
|
+
* instead of latching a read mode, so a JSON broadcast between binary
|
|
9077
|
+
* frames cannot desynchronize the reader.
|
|
9078
|
+
*/
|
|
9079
|
+
readBuffer = Buffer.alloc(0);
|
|
7765
9080
|
/** P6: true once the server advertises binary support and client accepts. */
|
|
7766
9081
|
useBinary = false;
|
|
7767
9082
|
info = null;
|
|
@@ -7903,7 +9218,7 @@ var ProjectServerConnection = class {
|
|
|
7903
9218
|
this.activity = null;
|
|
7904
9219
|
this.health = null;
|
|
7905
9220
|
this.useBinary = false;
|
|
7906
|
-
this.
|
|
9221
|
+
this.readBuffer = Buffer.alloc(0);
|
|
7907
9222
|
this.connectReject?.(new Error("codebase-index client disconnected"));
|
|
7908
9223
|
this.connectResolve = null;
|
|
7909
9224
|
this.connectReject = null;
|
|
@@ -7924,7 +9239,7 @@ var ProjectServerConnection = class {
|
|
|
7924
9239
|
currentAuthToken() {
|
|
7925
9240
|
if (this.authToken === void 0) {
|
|
7926
9241
|
try {
|
|
7927
|
-
const raw =
|
|
9242
|
+
const raw = fs13.readFileSync(
|
|
7928
9243
|
projectIndexServerMetadataPath(this.projectRoot, this.indexDir),
|
|
7929
9244
|
"utf8"
|
|
7930
9245
|
);
|
|
@@ -8031,10 +9346,8 @@ var ProjectServerConnection = class {
|
|
|
8031
9346
|
this.info = null;
|
|
8032
9347
|
this.activity = null;
|
|
8033
9348
|
this.health = null;
|
|
8034
|
-
this.
|
|
8035
|
-
this.binaryBuffer = [];
|
|
9349
|
+
this.readBuffer = Buffer.alloc(0);
|
|
8036
9350
|
this.useBinary = false;
|
|
8037
|
-
this.textDecoder = null;
|
|
8038
9351
|
return new Promise((resolve4, reject) => {
|
|
8039
9352
|
const socket = net.createConnection(this.endpoint);
|
|
8040
9353
|
this.socket = socket;
|
|
@@ -8064,18 +9377,47 @@ var ProjectServerConnection = class {
|
|
|
8064
9377
|
socket.on("close", () => this.onClose(socket));
|
|
8065
9378
|
});
|
|
8066
9379
|
}
|
|
9380
|
+
/**
|
|
9381
|
+
* Unified per-frame reader. Each frame is sniffed by its first byte:
|
|
9382
|
+
* `0x57` ('W') → length-prefixed MessagePack binary, anything else →
|
|
9383
|
+
* newline-delimited JSON text. Sniffing per frame (instead of latching a
|
|
9384
|
+
* mode) is what makes mixed streams work: the server may interleave a JSON
|
|
9385
|
+
* `index-state` broadcast between binary responses, and an old JSON-only
|
|
9386
|
+
* server stays readable while `useBinary` is armed.
|
|
9387
|
+
*
|
|
9388
|
+
* Multibyte UTF-8 in JSON frames is safe: raw `0x0a` only occurs as the
|
|
9389
|
+
* JSON delimiter (inside JSON strings `\n` is escaped), so a complete line
|
|
9390
|
+
* is always complete UTF-8.
|
|
9391
|
+
*/
|
|
8067
9392
|
onData(socket, chunk) {
|
|
8068
9393
|
if (socket !== this.socket) return;
|
|
8069
|
-
|
|
8070
|
-
this.onBinaryData(socket, chunk);
|
|
8071
|
-
return;
|
|
8072
|
-
}
|
|
8073
|
-
if (!this.textDecoder) this.textDecoder = new StringDecoder("utf8");
|
|
8074
|
-
this.buffer += this.textDecoder.write(chunk);
|
|
9394
|
+
this.readBuffer = this.readBuffer.length === 0 ? chunk : Buffer.concat([this.readBuffer, chunk]);
|
|
8075
9395
|
while (true) {
|
|
8076
|
-
|
|
9396
|
+
if (this.readBuffer.length === 0) return;
|
|
9397
|
+
if (this.useBinary && isBinaryFrame(this.readBuffer[0])) {
|
|
9398
|
+
if (this.readBuffer.length < 5) return;
|
|
9399
|
+
const frameLen = this.readBuffer.readUInt32BE(1);
|
|
9400
|
+
if (frameLen > MAX_BINARY_FRAME_BYTES) {
|
|
9401
|
+
socket.destroy();
|
|
9402
|
+
this.transition("offline", { error: "binary frame length exceeds the IPC limit" });
|
|
9403
|
+
return;
|
|
9404
|
+
}
|
|
9405
|
+
if (this.readBuffer.length < 5 + frameLen) return;
|
|
9406
|
+
const payload = this.readBuffer.subarray(5, 5 + frameLen);
|
|
9407
|
+
this.readBuffer = this.readBuffer.subarray(5 + frameLen);
|
|
9408
|
+
let message2;
|
|
9409
|
+
try {
|
|
9410
|
+
message2 = decodeBinaryFrame(payload);
|
|
9411
|
+
} catch {
|
|
9412
|
+
socket.destroy(new Error("invalid binary codebase-index server response"));
|
|
9413
|
+
return;
|
|
9414
|
+
}
|
|
9415
|
+
this.onMessage(message2);
|
|
9416
|
+
continue;
|
|
9417
|
+
}
|
|
9418
|
+
const newline = this.readBuffer.indexOf(10);
|
|
8077
9419
|
if (newline < 0) {
|
|
8078
|
-
if (this.
|
|
9420
|
+
if (this.readBuffer.length > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
|
|
8079
9421
|
socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
|
|
8080
9422
|
}
|
|
8081
9423
|
return;
|
|
@@ -8084,8 +9426,8 @@ var ProjectServerConnection = class {
|
|
|
8084
9426
|
socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
|
|
8085
9427
|
return;
|
|
8086
9428
|
}
|
|
8087
|
-
const line = this.
|
|
8088
|
-
this.
|
|
9429
|
+
const line = this.readBuffer.subarray(0, newline).toString("utf8");
|
|
9430
|
+
this.readBuffer = this.readBuffer.subarray(newline + 1);
|
|
8089
9431
|
if (!line) continue;
|
|
8090
9432
|
let message;
|
|
8091
9433
|
try {
|
|
@@ -8097,44 +9439,6 @@ var ProjectServerConnection = class {
|
|
|
8097
9439
|
this.onMessage(message);
|
|
8098
9440
|
}
|
|
8099
9441
|
}
|
|
8100
|
-
/**
|
|
8101
|
-
* P6: Parse binary frames from the raw buffer.
|
|
8102
|
-
*
|
|
8103
|
-
* Each frame is: [1 byte magic 0x57] [4 bytes uint32 BE length] [payload].
|
|
8104
|
-
* The magic byte distinguishes binary from JSON — a JSON frame's first byte
|
|
8105
|
-
* is `{` (0x7B), so there is no ambiguity even in a mixed-mode buffer.
|
|
8106
|
-
*/
|
|
8107
|
-
onBinaryData(socket, chunk) {
|
|
8108
|
-
this.binaryBuffer.push(chunk);
|
|
8109
|
-
const all = Buffer.concat(this.binaryBuffer);
|
|
8110
|
-
let offset = 0;
|
|
8111
|
-
while (offset + 5 <= all.length) {
|
|
8112
|
-
if (!isBinaryFrame(all[offset])) {
|
|
8113
|
-
this.useBinary = false;
|
|
8114
|
-
this.buffer += all.subarray(offset).toString("utf8");
|
|
8115
|
-
this.binaryBuffer = [];
|
|
8116
|
-
return;
|
|
8117
|
-
}
|
|
8118
|
-
const frameLen = all.readUInt32BE(offset + 1);
|
|
8119
|
-
if (frameLen > 256 * 1024 * 1024) {
|
|
8120
|
-
socket.destroy();
|
|
8121
|
-
this.transition("offline", { error: "binary frame length exceeds 256MB limit" });
|
|
8122
|
-
return;
|
|
8123
|
-
}
|
|
8124
|
-
const totalLen = 5 + frameLen;
|
|
8125
|
-
if (offset + totalLen > all.length) break;
|
|
8126
|
-
const payload = all.subarray(offset + 5, offset + 5 + frameLen);
|
|
8127
|
-
try {
|
|
8128
|
-
const message = decodeBinaryFrame(payload);
|
|
8129
|
-
this.onMessage(message);
|
|
8130
|
-
} catch {
|
|
8131
|
-
socket.destroy(new Error("invalid binary codebase-index server response"));
|
|
8132
|
-
return;
|
|
8133
|
-
}
|
|
8134
|
-
offset += totalLen;
|
|
8135
|
-
}
|
|
8136
|
-
this.binaryBuffer = offset < all.length ? [all.subarray(offset)] : [];
|
|
8137
|
-
}
|
|
8138
9442
|
onMessage(message) {
|
|
8139
9443
|
if (message.type === "hello") {
|
|
8140
9444
|
if (message.protocolVersion !== PROJECT_INDEX_SERVER_PROTOCOL_VERSION) {
|
|
@@ -8154,7 +9458,7 @@ var ProjectServerConnection = class {
|
|
|
8154
9458
|
}
|
|
8155
9459
|
this.info = message;
|
|
8156
9460
|
this.markResponsive();
|
|
8157
|
-
if (message.binarySupported) this.useBinary = true;
|
|
9461
|
+
if (message.binarySupported && binaryFramingEnabled()) this.useBinary = true;
|
|
8158
9462
|
this.transition("connected", { pid: message.pid });
|
|
8159
9463
|
ensureHeartbeatLoop();
|
|
8160
9464
|
this.connectResolve?.();
|
|
@@ -8240,13 +9544,13 @@ var ProjectServerConnection = class {
|
|
|
8240
9544
|
if (!url) throw new Error("built codebase-index project server is unavailable");
|
|
8241
9545
|
if (process.platform !== "win32") {
|
|
8242
9546
|
try {
|
|
8243
|
-
|
|
9547
|
+
fs13.rmSync(this.endpoint, { force: true });
|
|
8244
9548
|
} catch {
|
|
8245
9549
|
}
|
|
8246
9550
|
}
|
|
8247
9551
|
const args = [fileURLToPath5(url), "--project-root", this.projectRoot];
|
|
8248
9552
|
if (this.indexDir) args.push("--index-dir", this.indexDir);
|
|
8249
|
-
const child =
|
|
9553
|
+
const child = spawn4(process.execPath, args, {
|
|
8250
9554
|
detached: true,
|
|
8251
9555
|
stdio: "ignore",
|
|
8252
9556
|
windowsHide: true,
|
|
@@ -8264,8 +9568,8 @@ var ProjectServerConnection = class {
|
|
|
8264
9568
|
process.kill(pid);
|
|
8265
9569
|
const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
|
|
8266
9570
|
try {
|
|
8267
|
-
const metadata = JSON.parse(
|
|
8268
|
-
if (metadata.pid === pid)
|
|
9571
|
+
const metadata = JSON.parse(fs13.readFileSync(metadataPath, "utf8"));
|
|
9572
|
+
if (metadata.pid === pid) fs13.rmSync(metadataPath, { force: true });
|
|
8269
9573
|
} catch {
|
|
8270
9574
|
}
|
|
8271
9575
|
return true;
|
|
@@ -8277,6 +9581,10 @@ var ProjectServerConnection = class {
|
|
|
8277
9581
|
var connections = /* @__PURE__ */ new Map();
|
|
8278
9582
|
var MAX_CACHED_CONNECTIONS = 8;
|
|
8279
9583
|
var heartbeatTimer;
|
|
9584
|
+
function binaryFramingEnabled() {
|
|
9585
|
+
const flag = process.env["WRONGSTACK_INDEX_BINARY"];
|
|
9586
|
+
return flag === "1" || flag === "true";
|
|
9587
|
+
}
|
|
8280
9588
|
function forgetConnection(endpoint, connection) {
|
|
8281
9589
|
if (connections.get(endpoint) === connection) connections.delete(endpoint);
|
|
8282
9590
|
connection.close();
|
|
@@ -8366,7 +9674,7 @@ function resolveWorkerUrl() {
|
|
|
8366
9674
|
for (const rel of ["./worker.js", "./codebase-index/worker.js"]) {
|
|
8367
9675
|
try {
|
|
8368
9676
|
const url = new URL(rel, import.meta.url);
|
|
8369
|
-
if (url.protocol === "file:" &&
|
|
9677
|
+
if (url.protocol === "file:" && fs14.existsSync(fileURLToPath6(url))) return url;
|
|
8370
9678
|
} catch {
|
|
8371
9679
|
}
|
|
8372
9680
|
}
|
|
@@ -8723,7 +10031,7 @@ var editTool = {
|
|
|
8723
10031
|
});
|
|
8724
10032
|
}
|
|
8725
10033
|
const absPath = await safeResolveReal(input.path, ctx);
|
|
8726
|
-
const stat3 = await
|
|
10034
|
+
const stat3 = await fs15.stat(absPath).catch((err) => {
|
|
8727
10035
|
if (err.code === "ENOENT") {
|
|
8728
10036
|
throw new ToolValidationError({
|
|
8729
10037
|
message: `edit: file "${input.path}" does not exist. Use \`write\` instead.`,
|
|
@@ -8740,8 +10048,8 @@ var editTool = {
|
|
|
8740
10048
|
});
|
|
8741
10049
|
}
|
|
8742
10050
|
const autoRead = !ctx.hasRead(absPath);
|
|
8743
|
-
const original = await
|
|
8744
|
-
const updated = await
|
|
10051
|
+
const original = await fs15.readFile(absPath, "utf8");
|
|
10052
|
+
const updated = await fs15.stat(absPath);
|
|
8745
10053
|
const mtimeTolerance = process.platform === "win32" ? 2e3 : 1;
|
|
8746
10054
|
const originalHash = sha256hex(original);
|
|
8747
10055
|
const lastReadHash = ctx.lastReadHash?.(absPath);
|
|
@@ -8849,9 +10157,9 @@ var editTool = {
|
|
|
8849
10157
|
enqueueReindex({ projectRoot: ctx.projectRoot, files: [absPath] });
|
|
8850
10158
|
} catch {
|
|
8851
10159
|
}
|
|
8852
|
-
const written = await
|
|
10160
|
+
const written = await fs15.stat(absPath);
|
|
8853
10161
|
ctx.recordRead(absPath, written.mtimeMs, "write", sha256hex(newFile));
|
|
8854
|
-
ctx.session
|
|
10162
|
+
ctx.session?.recordFileChange?.({
|
|
8855
10163
|
path: absPath,
|
|
8856
10164
|
action: "modified",
|
|
8857
10165
|
before: original,
|