@wrongstack/tools 0.309.0 → 0.310.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_regex.d.ts +6 -34
- package/dist/audit.js +3 -3
- package/dist/bash.js +4 -4
- package/dist/builtin.js +3020 -1680
- package/dist/codebase-index/binary-frame.d.ts +57 -8
- package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +6 -0
- package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +6 -0
- package/dist/codebase-index/codebase-search-tool.d.ts +15 -5
- package/dist/codebase-index/index-service.d.ts +3 -19
- package/dist/codebase-index/index.js +2784 -1464
- package/dist/codebase-index/indexer.d.ts +3 -0
- package/dist/codebase-index/parser-batch.d.ts +53 -0
- package/dist/codebase-index/parser-dispatch.d.ts +32 -0
- package/dist/codebase-index/parser-output.d.ts +14 -0
- package/dist/codebase-index/parser-worker-pool.d.ts +57 -4
- package/dist/codebase-index/parser-worker-script.d.ts +5 -2
- package/dist/codebase-index/parser-worker-script.js +4042 -0
- package/dist/codebase-index/project-server-cache.d.ts +16 -0
- package/dist/codebase-index/project-server-client.d.ts +2 -2
- package/dist/codebase-index/project-server-query-cache.d.ts +88 -0
- package/dist/codebase-index/project-server.js +2846 -1308
- package/dist/codebase-index/py-parser.d.ts +5 -0
- package/dist/codebase-index/schema.d.ts +14 -1
- package/dist/codebase-index/sqlite-runtime.d.ts +2 -2
- package/dist/codebase-index/tree-sitter/queries.d.ts +30 -3
- package/dist/codebase-index/tree-sitter/visitor.d.ts +2 -1
- package/dist/codebase-index/vector-search.d.ts +12 -0
- package/dist/codebase-index/wal-maintenance.d.ts +58 -0
- package/dist/codebase-index/worker-protocol/contracts.d.ts +44 -0
- package/dist/codebase-index/worker-protocol.d.ts +17 -1
- package/dist/codebase-index/worker.js +2300 -1020
- package/dist/codebase-index/writer-admin.d.ts +11 -0
- package/dist/codebase-index/writer-helpers.d.ts +31 -1
- package/dist/codebase-index/writer-mutations.d.ts +0 -6
- package/dist/codebase-index/writer-schema.d.ts +2 -2
- package/dist/codebase-index/writer.d.ts +15 -0
- package/dist/edit.js +2511 -1203
- package/dist/exec.js +8 -6
- package/dist/format.js +3 -3
- package/dist/git.js +6 -0
- package/dist/grep.js +5 -124
- package/dist/index.js +3016 -1739
- package/dist/install.js +3 -3
- package/dist/json.js +5 -124
- package/dist/kanban.js +130 -0
- package/dist/languages/index.js +3 -3
- package/dist/lint.js +3 -3
- package/dist/logs.js +5 -121
- package/dist/outdated.js +3 -3
- package/dist/pack.js +3020 -1680
- package/dist/patch.js +2524 -1216
- package/dist/plan.js +106 -0
- package/dist/process-registry.js +1 -1
- package/dist/read.js +2506 -1198
- package/dist/replace.js +2487 -1295
- package/dist/search.js +6 -2
- package/dist/session-kanban.js +24 -16
- package/dist/task.js +106 -0
- package/dist/test.js +3 -3
- package/dist/todo.js +106 -0
- package/dist/tool-tier.d.ts +11 -0
- package/dist/tool-tier.js +3028 -1680
- package/dist/tree.js +14 -3
- package/dist/typecheck.js +3 -3
- package/dist/win32.js +5 -5
- package/dist/write.js +2513 -1205
- package/package.json +5 -4
package/dist/patch.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
|
-
tsLoad ??= import("@typescript/typescript6").then((m) => {
|
|
184
|
-
ts = m.default ?? m;
|
|
185
|
-
return ts;
|
|
186
|
-
});
|
|
187
|
-
return tsLoad;
|
|
188
|
-
}
|
|
189
|
-
function kindMap() {
|
|
190
|
-
kindMapCache ??= {
|
|
191
|
-
[ts.SyntaxKind.ClassDeclaration]: "class",
|
|
192
|
-
[ts.SyntaxKind.InterfaceDeclaration]: "interface",
|
|
193
|
-
[ts.SyntaxKind.EnumDeclaration]: "enum",
|
|
194
|
-
[ts.SyntaxKind.TypeAliasDeclaration]: "type",
|
|
195
|
-
[ts.SyntaxKind.FunctionDeclaration]: "function",
|
|
196
|
-
[ts.SyntaxKind.MethodDeclaration]: "method",
|
|
197
|
-
[ts.SyntaxKind.GetAccessor]: "property",
|
|
198
|
-
[ts.SyntaxKind.SetAccessor]: "property",
|
|
199
|
-
[ts.SyntaxKind.PropertyDeclaration]: "property",
|
|
200
|
-
[ts.SyntaxKind.Parameter]: "parameter",
|
|
201
|
-
[ts.SyntaxKind.NamespaceExportDeclaration]: "namespace"
|
|
202
|
-
};
|
|
203
|
-
return kindMapCache;
|
|
204
|
-
}
|
|
205
|
-
function kindOf(node) {
|
|
206
|
-
if (ts.isVariableDeclaration(node)) {
|
|
207
|
-
const parent = node.parent;
|
|
208
|
-
if (ts.isVariableDeclarationList(parent)) {
|
|
209
|
-
const flags = parent.flags;
|
|
210
|
-
if (flags & ts.NodeFlags.Let) return "let";
|
|
211
|
-
if (flags & ts.NodeFlags.Const) return "const";
|
|
212
|
-
return "var";
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
216
|
-
return kindMap()[node.kind] ?? null;
|
|
217
|
-
}
|
|
218
|
-
function getSignature(printer, node, sourceFile) {
|
|
219
|
-
const raw = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
220
|
-
return raw.replace(/\s+/g, " ").slice(0, 500);
|
|
221
|
-
}
|
|
222
|
-
function getJsDoc(node, sourceFile) {
|
|
223
|
-
const fullText = sourceFile.getFullText();
|
|
224
|
-
const nodePos = node.getFullStart();
|
|
225
|
-
const comments = ts.getLeadingCommentRanges(fullText, nodePos);
|
|
226
|
-
if (!comments) return "";
|
|
227
|
-
for (const range of comments) {
|
|
228
|
-
const commentText = fullText.slice(range.pos, range.end);
|
|
229
|
-
const trimmed = commentText.trim();
|
|
230
|
-
if (trimmed.startsWith("/**") && trimmed.endsWith("*/")) {
|
|
231
|
-
const inner = trimmed.slice(3, -2).replace(/^[ \t]*\*[ ]?/gm, "").trim();
|
|
232
|
-
return inner.split("\n")[0]?.trim().slice(0, 200) ?? "";
|
|
233
|
-
}
|
|
176
|
+
// src/codebase-index/import-extractor.ts
|
|
177
|
+
function lastSegment(specifier) {
|
|
178
|
+
const pathLike = /[/\\]|::/.test(specifier);
|
|
179
|
+
const segments = specifier.split(/[/\\]|::/).filter(Boolean);
|
|
180
|
+
let last = segments[segments.length - 1] ?? specifier;
|
|
181
|
+
if (last === "*" || last === "_") {
|
|
182
|
+
last = segments[segments.length - 2] ?? specifier;
|
|
234
183
|
}
|
|
235
|
-
return "";
|
|
184
|
+
if (pathLike) return last.replace(/\.[A-Za-z0-9]{1,8}$/, "") || last;
|
|
185
|
+
const dotted = last.split(".").filter((part) => part && part !== "*" && part !== "_");
|
|
186
|
+
return dotted[dotted.length - 1] ?? last;
|
|
236
187
|
}
|
|
237
|
-
function
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (node.name && ts.isIdentifier(node.name)) {
|
|
242
|
-
parts.push(node.name.text);
|
|
243
|
-
}
|
|
188
|
+
function newlineOffsets(content) {
|
|
189
|
+
const offsets = [];
|
|
190
|
+
for (let i = 0; i < content.length; i++) {
|
|
191
|
+
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
244
192
|
}
|
|
193
|
+
return offsets;
|
|
245
194
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
254
|
-
}
|
|
255
|
-
const symbols = [];
|
|
256
|
-
const refs = [];
|
|
257
|
-
const printer = ts.createPrinter({});
|
|
258
|
-
function visit(node, funcDepth, scopeParts) {
|
|
259
|
-
const kind = kindOf(node);
|
|
260
|
-
if (kind) {
|
|
261
|
-
if ((kind === "const" || kind === "let" || kind === "var" || kind === "parameter") && funcDepth > 0) {
|
|
262
|
-
} else {
|
|
263
|
-
const nameNode = node.name;
|
|
264
|
-
if (!nameNode || !ts.isIdentifier(nameNode)) {
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
const name = nameNode.text;
|
|
268
|
-
const pos2 = nameNode.getStart(sourceFile);
|
|
269
|
-
const { line: line2, character } = sourceFile.getLineAndCharacterOfPosition(pos2);
|
|
270
|
-
const scope = scopeParts.join(".");
|
|
271
|
-
const signature = getSignature(printer, node, sourceFile);
|
|
272
|
-
const docComment = getJsDoc(node, sourceFile);
|
|
273
|
-
const text = [name, signature, docComment].filter(Boolean).join(" | ");
|
|
274
|
-
symbols.push({
|
|
275
|
-
id: 0,
|
|
276
|
-
lang,
|
|
277
|
-
kind,
|
|
278
|
-
name,
|
|
279
|
-
file,
|
|
280
|
-
line: line2 + 1,
|
|
281
|
-
col: character,
|
|
282
|
-
signature,
|
|
283
|
-
docComment,
|
|
284
|
-
scope,
|
|
285
|
-
text
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
const pos = node.getStart(sourceFile);
|
|
290
|
-
const { line } = sourceFile.getLineAndCharacterOfPosition(pos);
|
|
291
|
-
const lineNum = line + 1;
|
|
292
|
-
if (ts.isCallExpression(node)) {
|
|
293
|
-
const expr = node.expression;
|
|
294
|
-
if (ts.isIdentifier(expr)) {
|
|
295
|
-
refs.push({ fromId: 0, toName: expr.text, callType: "call", line: lineNum });
|
|
296
|
-
}
|
|
297
|
-
} else if (ts.isPropertyAccessExpression(node)) {
|
|
298
|
-
if (ts.isIdentifier(node.expression)) {
|
|
299
|
-
refs.push({ fromId: 0, toName: node.expression.text, callType: "call", line: lineNum });
|
|
300
|
-
}
|
|
301
|
-
} else if (ts.isTypeReferenceNode(node)) {
|
|
302
|
-
const name = getTypeName(node.typeName);
|
|
303
|
-
if (name) refs.push({ fromId: 0, toName: name, callType: "type_ref", line: lineNum });
|
|
304
|
-
} else if (ts.isHeritageClause(node)) {
|
|
305
|
-
for (const t of node.types) {
|
|
306
|
-
const name = getTypeName(t.expression);
|
|
307
|
-
if (name)
|
|
308
|
-
refs.push({
|
|
309
|
-
fromId: 0,
|
|
310
|
-
toName: name,
|
|
311
|
-
callType: node.token === ts.SyntaxKind.ExtendsKeyword ? "inherit" : "implement",
|
|
312
|
-
line: lineNum
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
} else if (ts.isImportDeclaration(node)) {
|
|
316
|
-
emitImportSpecifierRefs(node, refs, lineNum);
|
|
317
|
-
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
|
|
318
|
-
emitExportSpecifierRefs(node, refs, lineNum);
|
|
319
|
-
}
|
|
320
|
-
const scopeIdx = scopeParts.length;
|
|
321
|
-
pushScopeName(node, scopeParts);
|
|
322
|
-
const childFuncDepth = ts.isFunctionLike(node) ? funcDepth + 1 : funcDepth;
|
|
323
|
-
ts.forEachChild(node, (child) => visit(child, childFuncDepth, scopeParts));
|
|
324
|
-
scopeParts.length = scopeIdx;
|
|
195
|
+
function lineAt(offsets, index) {
|
|
196
|
+
let low = 0;
|
|
197
|
+
let high = offsets.length;
|
|
198
|
+
while (low < high) {
|
|
199
|
+
const mid = low + high >>> 1;
|
|
200
|
+
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
201
|
+
else high = mid;
|
|
325
202
|
}
|
|
326
|
-
|
|
327
|
-
return { file, lang, symbols, refs: deduplicateRefs(refs), mtimeMs: Date.now() };
|
|
203
|
+
return low + 1;
|
|
328
204
|
}
|
|
329
|
-
function
|
|
330
|
-
|
|
331
|
-
if (ts.isQualifiedName(name)) return `${getTypeName(name.left)}.${name.right.text}`;
|
|
332
|
-
return "";
|
|
205
|
+
function hasImportPatterns(lang) {
|
|
206
|
+
return LANG_IMPORTS[lang] !== void 0;
|
|
333
207
|
}
|
|
334
|
-
function
|
|
208
|
+
function extractImports(opts) {
|
|
209
|
+
const patterns = LANG_IMPORTS[opts.lang];
|
|
210
|
+
if (!patterns || !opts.content) return [];
|
|
211
|
+
const limit = opts.maxImports ?? IMPORT_MAX_PER_FILE;
|
|
212
|
+
const content = opts.content.length > IMPORT_MAX_FILE_CHARS ? opts.content.slice(0, IMPORT_MAX_FILE_CHARS) : opts.content;
|
|
213
|
+
const refs = [];
|
|
335
214
|
const seen = /* @__PURE__ */ new Set();
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (!clause) {
|
|
350
|
-
if (module) {
|
|
351
|
-
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
352
|
-
}
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
if (clause.name) {
|
|
356
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
357
|
-
}
|
|
358
|
-
const bindings = clause.namedBindings;
|
|
359
|
-
if (!bindings) return;
|
|
360
|
-
if (ts.isNamedImports(bindings)) {
|
|
361
|
-
for (const element of bindings.elements) {
|
|
215
|
+
const offsets = newlineOffsets(content);
|
|
216
|
+
for (const pattern of patterns) {
|
|
217
|
+
const re = new RegExp(pattern.re.source, pattern.re.flags);
|
|
218
|
+
for (const match of content.matchAll(re)) {
|
|
219
|
+
if (refs.length >= limit) return refs;
|
|
220
|
+
const specifier = match[1]?.trim();
|
|
221
|
+
if (!specifier) continue;
|
|
222
|
+
const module = specifier;
|
|
223
|
+
const toName = pattern.name === "full" ? module : lastSegment(module);
|
|
224
|
+
if (!toName) continue;
|
|
225
|
+
const key = `${module}\0${toName}`;
|
|
226
|
+
if (seen.has(key)) continue;
|
|
227
|
+
seen.add(key);
|
|
362
228
|
refs.push({
|
|
363
229
|
fromId: 0,
|
|
364
|
-
toName
|
|
230
|
+
toName,
|
|
365
231
|
callType: "import",
|
|
366
|
-
line:
|
|
232
|
+
line: lineAt(offsets, match.index ?? 0),
|
|
233
|
+
lang: opts.lang,
|
|
367
234
|
module
|
|
368
235
|
});
|
|
369
236
|
}
|
|
370
|
-
} else if (ts.isNamespaceImport(bindings)) {
|
|
371
|
-
refs.push({
|
|
372
|
-
fromId: 0,
|
|
373
|
-
toName: bindings.name.text,
|
|
374
|
-
callType: "import",
|
|
375
|
-
line: lineNum,
|
|
376
|
-
module
|
|
377
|
-
});
|
|
378
237
|
}
|
|
238
|
+
return refs;
|
|
379
239
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
function emitExportSpecifierRefs(node, refs, lineNum) {
|
|
384
|
-
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
385
|
-
const clause = node.exportClause;
|
|
386
|
-
if (clause && ts.isNamespaceExport(clause)) {
|
|
387
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
if (clause && ts.isNamedExports(clause)) {
|
|
391
|
-
for (const element of clause.elements) {
|
|
392
|
-
const originalName = element.propertyName?.text ?? element.name.text;
|
|
393
|
-
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum, module });
|
|
394
|
-
}
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
if (module) {
|
|
398
|
-
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
var ts, tsLoad, 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 path7 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(path7.join(os.tmpdir(), prefix));
|
|
483
|
+
const scriptPath = path7.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((resolve5) => {
|
|
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
|
+
resolve5(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
|
+
resolve5(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
|
-
resolve5({ 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 path8 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 loadTypescript() {
|
|
1803
|
+
tsLoad ??= import("@typescript/typescript6").then((m) => {
|
|
1804
|
+
ts = m.default ?? m;
|
|
1805
|
+
return ts;
|
|
1806
|
+
});
|
|
1807
|
+
return tsLoad;
|
|
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 loadTypescript();
|
|
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, tsLoad, kindMapCache;
|
|
2022
|
+
var init_ts_parser = __esm({
|
|
2023
|
+
"src/codebase-index/ts-parser.ts"() {
|
|
2024
|
+
"use strict";
|
|
2025
|
+
init_languages();
|
|
2026
|
+
tsLoad = 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 path9 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(path9.join(os3.tmpdir(), "ws-go-parse-"));
|
|
2141
|
+
scriptPath = path9.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
|
+
(resolve5, 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
|
+
resolve5({ 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 path10 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 basename6 =
|
|
2604
|
+
const basename6 = path10.basename(file).toLowerCase();
|
|
1810
2605
|
const isPackageJson = basename6 === "package.json";
|
|
1811
2606
|
const isTsconfig = basename6 === "tsconfig.json" || basename6 === "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: path10.basename(file),
|
|
1836
2631
|
kind: "object",
|
|
1837
2632
|
line,
|
|
1838
2633
|
col: 0,
|
|
1839
|
-
signature: `"${
|
|
2634
|
+
signature: `"${path10.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 path11 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 = path11.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 path11.join(WASM_DIR, name, `tree-sitter-${name}.wasm`);
|
|
2662
3789
|
}
|
|
2663
3790
|
async function parseSymbols8(opts) {
|
|
2664
3791
|
const { file, content, lang } = opts;
|
|
@@ -2674,92 +3801,232 @@ 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 = path11.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 = path11.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 = path10.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 = path10.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/patch.ts
|
|
2759
|
-
import { spawn as
|
|
2760
|
-
import * as
|
|
2761
|
-
import * as
|
|
2762
|
-
import * as
|
|
4026
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
4027
|
+
import * as fs15 from "node:fs/promises";
|
|
4028
|
+
import * as os5 from "node:os";
|
|
4029
|
+
import * as path16 from "node:path";
|
|
2763
4030
|
import { buildChildEnv, toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
2764
4031
|
|
|
2765
4032
|
// src/_util.ts
|
|
@@ -2827,7 +4094,7 @@ async function safeResolveReal(input, ctx) {
|
|
|
2827
4094
|
}
|
|
2828
4095
|
|
|
2829
4096
|
// src/codebase-index/background-indexer.ts
|
|
2830
|
-
import * as
|
|
4097
|
+
import * as fs14 from "node:fs";
|
|
2831
4098
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
2832
4099
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
2833
4100
|
|
|
@@ -2915,9 +4182,9 @@ var indexCircuitBreaker = new IndexCircuitBreaker();
|
|
|
2915
4182
|
import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
|
|
2916
4183
|
import { execFile } from "node:child_process";
|
|
2917
4184
|
import { createHash as createHash2 } from "node:crypto";
|
|
2918
|
-
import * as
|
|
4185
|
+
import * as fs10 from "node:fs/promises";
|
|
2919
4186
|
import { availableParallelism } from "node:os";
|
|
2920
|
-
import * as
|
|
4187
|
+
import * as path14 from "node:path";
|
|
2921
4188
|
import {
|
|
2922
4189
|
DEFAULT_WALK_IGNORE_DIRS,
|
|
2923
4190
|
indexParallelBatchSize,
|
|
@@ -3588,280 +4855,93 @@ var ModuleResolver = class {
|
|
|
3588
4855
|
if (head === "self" || head === "super") {
|
|
3589
4856
|
let base = path5.posix.dirname(fromFile);
|
|
3590
4857
|
for (const segment of segments) {
|
|
3591
|
-
if (segment === "super") base = path5.posix.dirname(base);
|
|
3592
|
-
else if (segment !== "self") break;
|
|
3593
|
-
}
|
|
3594
|
-
const rest2 = segments.filter((segment) => segment !== "self" && segment !== "super");
|
|
3595
|
-
return this.lookupWithExtensions(path5.posix.join(base, ...rest2), "rs");
|
|
3596
|
-
}
|
|
3597
|
-
const owningCrate = findOwningRoot(this.structure, fromFile, ["cargo"]);
|
|
3598
|
-
const crate = head === "crate" ? owningCrate : this.structure.roots.find(
|
|
3599
|
-
(root) => root.kind === "cargo" && root.importPath === head?.replace(/-/g, "_")
|
|
3600
|
-
);
|
|
3601
|
-
if (!crate) {
|
|
3602
|
-
return this.lookupWithExtensions(
|
|
3603
|
-
path5.posix.join(path5.posix.dirname(fromFile), ...segments),
|
|
3604
|
-
"rs"
|
|
3605
|
-
);
|
|
3606
|
-
}
|
|
3607
|
-
const rest = segments.slice(1);
|
|
3608
|
-
for (const base of crate.sourceRoots) {
|
|
3609
|
-
const parent = rest.length > 1 ? this.lookupWithExtensions(path5.posix.join(base, ...rest.slice(0, -1)), "rs") : void 0;
|
|
3610
|
-
const exact = this.lookupWithExtensions(path5.posix.join(base, ...rest), "rs");
|
|
3611
|
-
const hit = exact ?? parent ?? this.lookupWithExtensions(path5.posix.join(base, "lib"), "rs");
|
|
3612
|
-
if (hit) return hit;
|
|
3613
|
-
}
|
|
3614
|
-
return void 0;
|
|
3615
|
-
}
|
|
3616
|
-
/** `com.example.Thing` and `com.example.*` against JVM source roots. */
|
|
3617
|
-
resolveJvm(spec) {
|
|
3618
|
-
const segments = spec.split(".").filter(Boolean);
|
|
3619
|
-
if (segments.length === 0) return void 0;
|
|
3620
|
-
const sourceRoots = this.structure.roots.filter((root) => root.kind === "maven" || root.kind === "gradle").flatMap((root) => root.sourceRoots);
|
|
3621
|
-
const wildcard = segments[segments.length - 1] === "*";
|
|
3622
|
-
const parts = wildcard ? segments.slice(0, -1) : segments;
|
|
3623
|
-
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
3624
|
-
const target = path5.posix.join(base, ...parts);
|
|
3625
|
-
const hit = wildcard ? this.representativeIn(target, "jvm") : this.lookupWithExtensions(target, "jvm");
|
|
3626
|
-
if (hit) return hit;
|
|
3627
|
-
}
|
|
3628
|
-
return void 0;
|
|
3629
|
-
}
|
|
3630
|
-
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
3631
|
-
resolveInclude(fromFile, spec) {
|
|
3632
|
-
const relative4 = this.lookupWithExtensions(
|
|
3633
|
-
path5.posix.join(path5.posix.dirname(fromFile), spec),
|
|
3634
|
-
"c"
|
|
3635
|
-
);
|
|
3636
|
-
if (relative4) return relative4;
|
|
3637
|
-
for (const base of [
|
|
3638
|
-
path5.posix.join(this.structure.projectRoot, "include"),
|
|
3639
|
-
this.structure.projectRoot
|
|
3640
|
-
]) {
|
|
3641
|
-
const hit = this.lookupWithExtensions(path5.posix.join(base, spec), "c");
|
|
3642
|
-
if (hit) return hit;
|
|
3643
|
-
}
|
|
3644
|
-
return void 0;
|
|
3645
|
-
}
|
|
3646
|
-
/** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
|
|
3647
|
-
resolveRuby(fromFile, spec) {
|
|
3648
|
-
const relative4 = this.lookupWithExtensions(
|
|
3649
|
-
path5.posix.join(path5.posix.dirname(fromFile), spec),
|
|
3650
|
-
"ruby"
|
|
3651
|
-
);
|
|
3652
|
-
if (relative4) return relative4;
|
|
3653
|
-
for (const base of [
|
|
3654
|
-
path5.posix.join(this.structure.projectRoot, "lib"),
|
|
3655
|
-
this.structure.projectRoot
|
|
3656
|
-
]) {
|
|
3657
|
-
const hit = this.lookupWithExtensions(path5.posix.join(base, spec), "ruby");
|
|
3658
|
-
if (hit) return hit;
|
|
3659
|
-
}
|
|
3660
|
-
return void 0;
|
|
3661
|
-
}
|
|
3662
|
-
};
|
|
3663
|
-
|
|
3664
|
-
// src/codebase-index/import-extractor.ts
|
|
3665
|
-
var IMPORT_MAX_FILE_CHARS = 512 * 1024;
|
|
3666
|
-
var IMPORT_MAX_PER_FILE = 400;
|
|
3667
|
-
var DOTTED_IMPORT = [
|
|
3668
|
-
{ re: /^[ \t]*import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;?\s*$/gm }
|
|
3669
|
-
];
|
|
3670
|
-
var LANG_IMPORTS = {
|
|
3671
|
-
// Go and Python have real AST extractors; these patterns are the fallback for
|
|
3672
|
-
// machines with no Go toolchain or Python interpreter installed, where the
|
|
3673
|
-
// parser degrades to regex symbols and would otherwise contribute no edges.
|
|
3674
|
-
go: [
|
|
3675
|
-
{ re: /^[ \t]*import\s+(?:[\w.]+\s+)?"([^"]+)"/gm },
|
|
3676
|
-
// Grouped form: inside `import ( … )` each line is an optional alias plus a
|
|
3677
|
-
// quoted path. A stray match elsewhere resolves to no file and is dropped.
|
|
3678
|
-
{ re: /^[ \t]*(?:[A-Za-z_.]\w*\s+)?"([^"]+)"\s*$/gm }
|
|
3679
|
-
],
|
|
3680
|
-
py: [{ re: /^[ \t]*import\s+([\w.]+)/gm }, { re: /^[ \t]*from\s+([.\w]+)\s+import\b/gm }],
|
|
3681
|
-
rs: [
|
|
3682
|
-
// use a::b::C; | use a::b::{C, D}; → the path before any brace
|
|
3683
|
-
{ re: /^[ \t]*(?:pub\s+)?use\s+([\w:]+?)(?:::\{|\s*;|\s+as\b)/gm },
|
|
3684
|
-
// mod foo; (a declaration *and* a dependency on foo.rs / foo/mod.rs)
|
|
3685
|
-
{ re: /^[ \t]*(?:pub\s+)?mod\s+(\w+)\s*;/gm }
|
|
3686
|
-
],
|
|
3687
|
-
java: DOTTED_IMPORT,
|
|
3688
|
-
kotlin: DOTTED_IMPORT,
|
|
3689
|
-
scala: [{ re: /^[ \t]*import\s+([\w.]+(?:\.[_*])?)\s*$/gm }],
|
|
3690
|
-
csharp: [
|
|
3691
|
-
// using Foo.Bar; | using static Foo.Bar; | using Alias = Foo.Bar;
|
|
3692
|
-
{ re: /^[ \t]*global\s+using\s+(?:static\s+)?([\w.]+)\s*;/gm, name: "full" },
|
|
3693
|
-
{ re: /^[ \t]*using\s+(?:static\s+)?(?:\w+\s*=\s*)?([\w.]+)\s*;/gm, name: "full" }
|
|
3694
|
-
],
|
|
3695
|
-
// Quoted includes only: <stdio.h> is a system header with no indexed file.
|
|
3696
|
-
c: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
3697
|
-
cpp: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
3698
|
-
ruby: [{ re: /^[ \t]*(?:require|require_relative|load)\s*\(?\s*['"]([^'"]+)['"]/gm }],
|
|
3699
|
-
php: [
|
|
3700
|
-
// `use A\B\C` imports the class C, which is what the index has a symbol
|
|
3701
|
-
// for — the namespace symbol only covers the `A\B` prefix.
|
|
3702
|
-
{ re: /^[ \t]*use\s+(?:function\s+|const\s+)?([\w\\]+)/gm },
|
|
3703
|
-
{ re: /^[ \t]*(?:require|require_once|include|include_once)\s*\(?\s*['"]([^'"]+)['"]/gm }
|
|
3704
|
-
],
|
|
3705
|
-
swift: [{ re: /^[ \t]*import\s+(?:struct\s+|class\s+|func\s+)?([\w.]+)\s*$/gm }],
|
|
3706
|
-
dart: [{ re: /^[ \t]*(?:import|export|part)\s+['"]([^'"]+)['"]/gm }],
|
|
3707
|
-
lua: [{ re: /\brequire\s*\(?\s*['"]([^'"]+)['"]/g }],
|
|
3708
|
-
elixir: [{ re: /^[ \t]*(?:alias|import|require|use)\s+([A-Z][\w.]*)/gm, name: "full" }],
|
|
3709
|
-
haskell: [{ re: /^[ \t]*import\s+(?:qualified\s+)?([\w.]+)/gm, name: "full" }],
|
|
3710
|
-
zig: [{ re: /@import\s*\(\s*"([^"]+)"\s*\)/g }],
|
|
3711
|
-
proto: [{ re: /^[ \t]*import\s+(?:public\s+|weak\s+)?"([^"]+)"\s*;/gm }],
|
|
3712
|
-
// `@import "x"`, `@use "x"`, `@forward "x"` — Sass and plain CSS alike.
|
|
3713
|
-
css: [{ re: /^[ \t]*@(?:import|use|forward)\s+(?:url\()?\s*['"]([^'"]+)['"]/gm }],
|
|
3714
|
-
// A .vue/.svelte file's <script> block is JS; the same ESM syntax applies.
|
|
3715
|
-
vue: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
3716
|
-
svelte: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
3717
|
-
html: [
|
|
3718
|
-
{ re: /<script[^>]+src\s*=\s*['"]([^'"]+)['"]/g },
|
|
3719
|
-
{ re: /<link[^>]+href\s*=\s*['"]([^'"]+)['"]/g }
|
|
3720
|
-
],
|
|
3721
|
-
shell: [{ re: /^[ \t]*(?:source|\.)\s+(\S+)/gm }],
|
|
3722
|
-
r: [{ re: /\b(?:source|library|require)\s*\(\s*['"]?([\w./-]+)['"]?\s*\)/g }]
|
|
3723
|
-
};
|
|
3724
|
-
function lastSegment(specifier) {
|
|
3725
|
-
const pathLike = /[/\\]|::/.test(specifier);
|
|
3726
|
-
const segments = specifier.split(/[/\\]|::/).filter(Boolean);
|
|
3727
|
-
let last = segments[segments.length - 1] ?? specifier;
|
|
3728
|
-
if (last === "*" || last === "_") {
|
|
3729
|
-
last = segments[segments.length - 2] ?? specifier;
|
|
3730
|
-
}
|
|
3731
|
-
if (pathLike) return last.replace(/\.[A-Za-z0-9]{1,8}$/, "") || last;
|
|
3732
|
-
const dotted = last.split(".").filter((part) => part && part !== "*" && part !== "_");
|
|
3733
|
-
return dotted[dotted.length - 1] ?? last;
|
|
3734
|
-
}
|
|
3735
|
-
function newlineOffsets(content) {
|
|
3736
|
-
const offsets = [];
|
|
3737
|
-
for (let i = 0; i < content.length; i++) {
|
|
3738
|
-
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
3739
|
-
}
|
|
3740
|
-
return offsets;
|
|
3741
|
-
}
|
|
3742
|
-
function lineAt(offsets, index) {
|
|
3743
|
-
let low = 0;
|
|
3744
|
-
let high = offsets.length;
|
|
3745
|
-
while (low < high) {
|
|
3746
|
-
const mid = low + high >>> 1;
|
|
3747
|
-
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
3748
|
-
else high = mid;
|
|
3749
|
-
}
|
|
3750
|
-
return low + 1;
|
|
3751
|
-
}
|
|
3752
|
-
function hasImportPatterns(lang) {
|
|
3753
|
-
return LANG_IMPORTS[lang] !== void 0;
|
|
3754
|
-
}
|
|
3755
|
-
function extractImports(opts) {
|
|
3756
|
-
const patterns = LANG_IMPORTS[opts.lang];
|
|
3757
|
-
if (!patterns || !opts.content) return [];
|
|
3758
|
-
const limit = opts.maxImports ?? IMPORT_MAX_PER_FILE;
|
|
3759
|
-
const content = opts.content.length > IMPORT_MAX_FILE_CHARS ? opts.content.slice(0, IMPORT_MAX_FILE_CHARS) : opts.content;
|
|
3760
|
-
const refs = [];
|
|
3761
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3762
|
-
const offsets = newlineOffsets(content);
|
|
3763
|
-
for (const pattern of patterns) {
|
|
3764
|
-
const re = new RegExp(pattern.re.source, pattern.re.flags);
|
|
3765
|
-
for (const match of content.matchAll(re)) {
|
|
3766
|
-
if (refs.length >= limit) return refs;
|
|
3767
|
-
const specifier = match[1]?.trim();
|
|
3768
|
-
if (!specifier) continue;
|
|
3769
|
-
const module = specifier;
|
|
3770
|
-
const toName = pattern.name === "full" ? module : lastSegment(module);
|
|
3771
|
-
if (!toName) continue;
|
|
3772
|
-
const key = `${module}\0${toName}`;
|
|
3773
|
-
if (seen.has(key)) continue;
|
|
3774
|
-
seen.add(key);
|
|
3775
|
-
refs.push({
|
|
3776
|
-
fromId: 0,
|
|
3777
|
-
toName,
|
|
3778
|
-
callType: "import",
|
|
3779
|
-
line: lineAt(offsets, match.index ?? 0),
|
|
3780
|
-
lang: opts.lang,
|
|
3781
|
-
module
|
|
3782
|
-
});
|
|
3783
|
-
}
|
|
3784
|
-
}
|
|
3785
|
-
return refs;
|
|
3786
|
-
}
|
|
3787
|
-
|
|
3788
|
-
// src/codebase-index/parser-dispatch.ts
|
|
3789
|
-
async function parseFileContent(file, content, lang) {
|
|
3790
|
-
const parsed = await dispatch(file, content, lang);
|
|
3791
|
-
return withRelations(parsed, content, lang);
|
|
3792
|
-
}
|
|
3793
|
-
async function dispatch(file, content, lang) {
|
|
3794
|
-
switch (lang) {
|
|
3795
|
-
case "ts":
|
|
3796
|
-
case "tsx":
|
|
3797
|
-
case "js":
|
|
3798
|
-
case "jsx": {
|
|
3799
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
|
|
3800
|
-
return parseSymbols9({ file, content, lang });
|
|
3801
|
-
}
|
|
3802
|
-
case "go": {
|
|
3803
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
|
|
3804
|
-
return parseSymbols9({ file, content, lang: "go" });
|
|
3805
|
-
}
|
|
3806
|
-
case "py": {
|
|
3807
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
|
|
3808
|
-
return parseSymbols9({ file, content, lang: "py" });
|
|
3809
|
-
}
|
|
3810
|
-
case "rs": {
|
|
3811
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
|
|
3812
|
-
return parseSymbols9({ file, content, lang: "rs" });
|
|
4858
|
+
if (segment === "super") base = path5.posix.dirname(base);
|
|
4859
|
+
else if (segment !== "self") break;
|
|
4860
|
+
}
|
|
4861
|
+
const rest2 = segments.filter((segment) => segment !== "self" && segment !== "super");
|
|
4862
|
+
return this.lookupWithExtensions(path5.posix.join(base, ...rest2), "rs");
|
|
3813
4863
|
}
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
4864
|
+
const owningCrate = findOwningRoot(this.structure, fromFile, ["cargo"]);
|
|
4865
|
+
const crate = head === "crate" ? owningCrate : this.structure.roots.find(
|
|
4866
|
+
(root) => root.kind === "cargo" && root.importPath === head?.replace(/-/g, "_")
|
|
4867
|
+
);
|
|
4868
|
+
if (!crate) {
|
|
4869
|
+
return this.lookupWithExtensions(
|
|
4870
|
+
path5.posix.join(path5.posix.dirname(fromFile), ...segments),
|
|
4871
|
+
"rs"
|
|
4872
|
+
);
|
|
3817
4873
|
}
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
4874
|
+
const rest = segments.slice(1);
|
|
4875
|
+
for (const base of crate.sourceRoots) {
|
|
4876
|
+
const parent = rest.length > 1 ? this.lookupWithExtensions(path5.posix.join(base, ...rest.slice(0, -1)), "rs") : void 0;
|
|
4877
|
+
const exact = this.lookupWithExtensions(path5.posix.join(base, ...rest), "rs");
|
|
4878
|
+
const hit = exact ?? parent ?? this.lookupWithExtensions(path5.posix.join(base, "lib"), "rs");
|
|
4879
|
+
if (hit) return hit;
|
|
3821
4880
|
}
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
case "shell":
|
|
3836
|
-
case "elixir": {
|
|
3837
|
-
try {
|
|
3838
|
-
const { parseSymbols: parseSymbols10 } = await Promise.resolve().then(() => (init_tree_sitter_parser(), tree_sitter_parser_exports));
|
|
3839
|
-
const parsed = await parseSymbols10({ file, content, lang });
|
|
3840
|
-
if (parsed.symbols.length > 0) return parsed;
|
|
3841
|
-
} catch {
|
|
3842
|
-
}
|
|
3843
|
-
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
3844
|
-
return parseSymbols9({ file, content, lang });
|
|
4881
|
+
return void 0;
|
|
4882
|
+
}
|
|
4883
|
+
/** `com.example.Thing` and `com.example.*` against JVM source roots. */
|
|
4884
|
+
resolveJvm(spec) {
|
|
4885
|
+
const segments = spec.split(".").filter(Boolean);
|
|
4886
|
+
if (segments.length === 0) return void 0;
|
|
4887
|
+
const sourceRoots = this.structure.roots.filter((root) => root.kind === "maven" || root.kind === "gradle").flatMap((root) => root.sourceRoots);
|
|
4888
|
+
const wildcard = segments[segments.length - 1] === "*";
|
|
4889
|
+
const parts = wildcard ? segments.slice(0, -1) : segments;
|
|
4890
|
+
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
4891
|
+
const target = path5.posix.join(base, ...parts);
|
|
4892
|
+
const hit = wildcard ? this.representativeIn(target, "jvm") : this.lookupWithExtensions(target, "jvm");
|
|
4893
|
+
if (hit) return hit;
|
|
3845
4894
|
}
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
4895
|
+
return void 0;
|
|
4896
|
+
}
|
|
4897
|
+
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
4898
|
+
resolveInclude(fromFile, spec) {
|
|
4899
|
+
const relative4 = this.lookupWithExtensions(
|
|
4900
|
+
path5.posix.join(path5.posix.dirname(fromFile), spec),
|
|
4901
|
+
"c"
|
|
4902
|
+
);
|
|
4903
|
+
if (relative4) return relative4;
|
|
4904
|
+
for (const base of [
|
|
4905
|
+
path5.posix.join(this.structure.projectRoot, "include"),
|
|
4906
|
+
this.structure.projectRoot
|
|
4907
|
+
]) {
|
|
4908
|
+
const hit = this.lookupWithExtensions(path5.posix.join(base, spec), "c");
|
|
4909
|
+
if (hit) return hit;
|
|
3849
4910
|
}
|
|
4911
|
+
return void 0;
|
|
3850
4912
|
}
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
4913
|
+
/** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
|
|
4914
|
+
resolveRuby(fromFile, spec) {
|
|
4915
|
+
const relative4 = this.lookupWithExtensions(
|
|
4916
|
+
path5.posix.join(path5.posix.dirname(fromFile), spec),
|
|
4917
|
+
"ruby"
|
|
4918
|
+
);
|
|
4919
|
+
if (relative4) return relative4;
|
|
4920
|
+
for (const base of [
|
|
4921
|
+
path5.posix.join(this.structure.projectRoot, "lib"),
|
|
4922
|
+
this.structure.projectRoot
|
|
4923
|
+
]) {
|
|
4924
|
+
const hit = this.lookupWithExtensions(path5.posix.join(base, spec), "ruby");
|
|
4925
|
+
if (hit) return hit;
|
|
4926
|
+
}
|
|
4927
|
+
return void 0;
|
|
3856
4928
|
}
|
|
3857
|
-
|
|
3858
|
-
|
|
4929
|
+
};
|
|
4930
|
+
|
|
4931
|
+
// src/codebase-index/indexer.ts
|
|
4932
|
+
init_parser_dispatch();
|
|
3859
4933
|
|
|
3860
4934
|
// src/codebase-index/parser-worker-pool.ts
|
|
4935
|
+
import * as fs7 from "node:fs";
|
|
4936
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
|
|
3861
4937
|
import { Worker } from "node:worker_threads";
|
|
3862
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3863
|
-
import * as fs6 from "node:fs";
|
|
3864
4938
|
var WORKER_POOL_THRESHOLD = 500;
|
|
4939
|
+
function resolveWorkerPoolThreshold() {
|
|
4940
|
+
const raw = process.env["WRONGSTACK_INDEX_WORKER_THRESHOLD"];
|
|
4941
|
+
if (raw === void 0) return WORKER_POOL_THRESHOLD;
|
|
4942
|
+
if (!/^\d+$/.test(raw)) return WORKER_POOL_THRESHOLD;
|
|
4943
|
+
return Number.parseInt(raw, 10);
|
|
4944
|
+
}
|
|
3865
4945
|
var ParserWorkerPool = class {
|
|
3866
4946
|
constructor(maxWorkers = defaultWorkerCount()) {
|
|
3867
4947
|
this.maxWorkers = maxWorkers;
|
|
@@ -3905,7 +4985,8 @@ var ParserWorkerPool = class {
|
|
|
3905
4985
|
w.unref();
|
|
3906
4986
|
w.on("message", (msg) => this.handleMessage(msg));
|
|
3907
4987
|
w.on("error", (err) => this.handleError(err, w));
|
|
3908
|
-
|
|
4988
|
+
w.on("exit", () => this.retireByReference(w));
|
|
4989
|
+
this.workers.push({ worker: w, workerId: w.threadId, busy: false });
|
|
3909
4990
|
} catch {
|
|
3910
4991
|
if (this.workers.length === 0) {
|
|
3911
4992
|
this.unavailable = true;
|
|
@@ -3921,7 +5002,7 @@ var ParserWorkerPool = class {
|
|
|
3921
5002
|
}
|
|
3922
5003
|
/**
|
|
3923
5004
|
* Parse files in parallel across the worker pool. Returns a flat
|
|
3924
|
-
* `FileSymbols[]` in completion order (caller
|
|
5005
|
+
* `FileSymbols[]` in completion order (caller matches by file path).
|
|
3925
5006
|
*
|
|
3926
5007
|
* Content is pre-read by the main thread (for the content-hash check)
|
|
3927
5008
|
* and passed to workers to avoid a second disk read. Files are
|
|
@@ -3942,21 +5023,19 @@ var ParserWorkerPool = class {
|
|
|
3942
5023
|
chunks[i % workerCount].push(files[i]);
|
|
3943
5024
|
}
|
|
3944
5025
|
return new Promise((resolve5, reject) => {
|
|
5026
|
+
const pendingChunks = /* @__PURE__ */ new Map();
|
|
3945
5027
|
this.pending.set(batchId, {
|
|
3946
5028
|
resolve: resolve5,
|
|
3947
5029
|
reject,
|
|
3948
5030
|
accumulated: [],
|
|
3949
|
-
|
|
3950
|
-
|
|
5031
|
+
pendingChunks,
|
|
5032
|
+
settled: false
|
|
3951
5033
|
});
|
|
3952
5034
|
for (let i = 0; i < workerCount; i++) {
|
|
3953
5035
|
const pw = this.workers[i];
|
|
3954
5036
|
pw.busy = true;
|
|
3955
|
-
pw.
|
|
3956
|
-
|
|
3957
|
-
id: batchId,
|
|
3958
|
-
files: chunks[i]
|
|
3959
|
-
});
|
|
5037
|
+
pendingChunks.set(pw.workerId, chunks[i]);
|
|
5038
|
+
pw.worker.postMessage({ type: "parse", id: batchId, files: chunks[i] });
|
|
3960
5039
|
}
|
|
3961
5040
|
});
|
|
3962
5041
|
}
|
|
@@ -3965,6 +5044,12 @@ var ParserWorkerPool = class {
|
|
|
3965
5044
|
const workers = this.workers.map((w) => w.worker);
|
|
3966
5045
|
this.workers = [];
|
|
3967
5046
|
this.unavailable = false;
|
|
5047
|
+
for (const [, p] of this.pending) {
|
|
5048
|
+
if (p.settled) continue;
|
|
5049
|
+
p.settled = true;
|
|
5050
|
+
p.reject(new Error("ParserWorkerPool shut down"));
|
|
5051
|
+
}
|
|
5052
|
+
this.pending.clear();
|
|
3968
5053
|
for (const w of workers) {
|
|
3969
5054
|
try {
|
|
3970
5055
|
w.postMessage({ type: "shutdown" });
|
|
@@ -3985,39 +5070,117 @@ var ParserWorkerPool = class {
|
|
|
3985
5070
|
})
|
|
3986
5071
|
)
|
|
3987
5072
|
);
|
|
3988
|
-
for (const [, p] of this.pending) p.reject(new Error("ParserWorkerPool shut down"));
|
|
3989
|
-
this.pending.clear();
|
|
3990
5073
|
}
|
|
3991
5074
|
handleMessage(msg) {
|
|
3992
5075
|
const batch = this.pending.get(msg.id);
|
|
3993
5076
|
if (!batch) return;
|
|
5077
|
+
const worker2 = this.workers.find((w) => w.workerId === msg.workerId);
|
|
5078
|
+
if (worker2) worker2.busy = false;
|
|
5079
|
+
batch.pendingChunks.delete(msg.workerId);
|
|
3994
5080
|
batch.accumulated.push(...msg.results);
|
|
3995
|
-
batch.
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
if (batch.completedWorkers >= batch.expectedWorkers) {
|
|
5081
|
+
if (batch.pendingChunks.size === 0) {
|
|
5082
|
+
if (batch.settled) return;
|
|
5083
|
+
batch.settled = true;
|
|
3999
5084
|
this.pending.delete(msg.id);
|
|
4000
5085
|
batch.resolve(batch.accumulated);
|
|
4001
5086
|
}
|
|
4002
5087
|
}
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
5088
|
+
/**
|
|
5089
|
+
* Remove a worker from the pool and salvage any chunk it still owed.
|
|
5090
|
+
*
|
|
5091
|
+
* Idempotent by workerId — `error` and `exit` can both fire for one
|
|
5092
|
+
* death, and a worker may die while no batch references it. When the
|
|
5093
|
+
* dead worker owed files to an in-flight batch and other workers remain,
|
|
5094
|
+
* those files are re-parsed inline on this thread (one fewer worker
|
|
5095
|
+
* should cost latency, not correctness). When it was the last worker,
|
|
5096
|
+
* every remaining batch rejects so the indexer's existing inline
|
|
5097
|
+
* fallback takes over the whole batch.
|
|
5098
|
+
*/
|
|
5099
|
+
retireWorker(workerId) {
|
|
5100
|
+
const entry = this.workers.find((w) => w.workerId === workerId);
|
|
5101
|
+
if (!entry) return;
|
|
5102
|
+
this.workers = this.workers.filter((w) => w.workerId !== workerId);
|
|
5103
|
+
for (const [batchId, batch] of [...this.pending]) {
|
|
5104
|
+
const orphaned = batch.pendingChunks.get(workerId);
|
|
5105
|
+
if (!orphaned) continue;
|
|
5106
|
+
if (this.workers.length === 0) {
|
|
5107
|
+
this.pending.delete(batchId);
|
|
5108
|
+
this.unavailable = true;
|
|
5109
|
+
if (!batch.settled) {
|
|
5110
|
+
batch.settled = true;
|
|
5111
|
+
batch.reject(new Error("ParserWorkerPool: all workers died mid-batch"));
|
|
5112
|
+
}
|
|
5113
|
+
continue;
|
|
5114
|
+
}
|
|
5115
|
+
void this.reparseInline(batch, orphaned, workerId);
|
|
5116
|
+
}
|
|
5117
|
+
}
|
|
5118
|
+
/**
|
|
5119
|
+
* Salvage path: re-parse an orphaned chunk on this thread. Files that
|
|
5120
|
+
* fail here stay absent from the results — same contract as a per-file
|
|
5121
|
+
* error inside a live worker (see handleMessage).
|
|
5122
|
+
*/
|
|
5123
|
+
async reparseInline(batch, orphaned, workerId) {
|
|
5124
|
+
try {
|
|
5125
|
+
const { parseFileContent: parseFileContent2 } = await Promise.resolve().then(() => (init_parser_dispatch(), parser_dispatch_exports));
|
|
5126
|
+
for (const item of orphaned) {
|
|
5127
|
+
if (batch.settled) return;
|
|
5128
|
+
try {
|
|
5129
|
+
const parsed = await parseFileContent2(item.file, item.content, item.lang);
|
|
5130
|
+
batch.accumulated.push(parsed);
|
|
5131
|
+
} catch {
|
|
5132
|
+
}
|
|
5133
|
+
await new Promise((resolve5) => setImmediate(resolve5));
|
|
5134
|
+
}
|
|
5135
|
+
} finally {
|
|
5136
|
+
this.finishSalvage(batch, workerId);
|
|
5137
|
+
}
|
|
5138
|
+
}
|
|
5139
|
+
/**
|
|
5140
|
+
* Terminal tail of a salvage — runs on every exit path. Kept free of
|
|
5141
|
+
* control flow inside a `finally` (noUnsafeFinally): releases the
|
|
5142
|
+
* pending-marker and resolves the batch if this was its last chunk.
|
|
5143
|
+
*/
|
|
5144
|
+
finishSalvage(batch, workerId) {
|
|
5145
|
+
batch.pendingChunks.delete(workerId);
|
|
5146
|
+
if (batch.pendingChunks.size === 0) {
|
|
5147
|
+
for (const [batchId, tracked] of this.pending) {
|
|
5148
|
+
if (tracked === batch) {
|
|
5149
|
+
if (batch.settled) return;
|
|
5150
|
+
batch.settled = true;
|
|
5151
|
+
this.pending.delete(batchId);
|
|
5152
|
+
batch.resolve(batch.accumulated);
|
|
5153
|
+
return;
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
4009
5156
|
}
|
|
4010
5157
|
}
|
|
5158
|
+
/**
|
|
5159
|
+
* Retire by worker object rather than threadId. `threadId` is -1 before
|
|
5160
|
+
* the worker emits `online`, so a death during script load would make a
|
|
5161
|
+
* threadId-keyed lookup silently no-op and leak the entry (with its
|
|
5162
|
+
* pending chunk) — reference identity is correct in every case.
|
|
5163
|
+
*/
|
|
5164
|
+
retireByReference(source) {
|
|
5165
|
+
const entry = this.workers.find((w) => w.worker === source);
|
|
5166
|
+
if (entry) this.retireWorker(entry.workerId);
|
|
5167
|
+
}
|
|
5168
|
+
handleError(err, source) {
|
|
5169
|
+
void err;
|
|
5170
|
+
this.retireByReference(source);
|
|
5171
|
+
}
|
|
4011
5172
|
};
|
|
4012
5173
|
function defaultWorkerCount() {
|
|
4013
5174
|
const cores = globalThis.navigator?.hardwareConcurrency ?? 4;
|
|
4014
5175
|
return Math.max(1, Math.min(4, cores - 1));
|
|
4015
5176
|
}
|
|
4016
5177
|
function resolveWorkerScriptUrl() {
|
|
5178
|
+
const override = process.env["WRONGSTACK_PARSER_WORKER_SCRIPT"];
|
|
5179
|
+
if (override) return pathToFileURL(override);
|
|
4017
5180
|
for (const rel of ["./parser-worker-script.js", "./codebase-index/parser-worker-script.js"]) {
|
|
4018
5181
|
try {
|
|
4019
5182
|
const url = new URL(rel, import.meta.url);
|
|
4020
|
-
if (url.protocol === "file:" &&
|
|
5183
|
+
if (url.protocol === "file:" && fs7.existsSync(fileURLToPath2(url))) return url;
|
|
4021
5184
|
} catch {
|
|
4022
5185
|
}
|
|
4023
5186
|
}
|
|
@@ -4030,8 +5193,8 @@ function getParserPool() {
|
|
|
4030
5193
|
}
|
|
4031
5194
|
|
|
4032
5195
|
// src/codebase-index/writer.ts
|
|
4033
|
-
import * as
|
|
4034
|
-
import * as
|
|
5196
|
+
import * as fs9 from "node:fs";
|
|
5197
|
+
import * as path13 from "node:path";
|
|
4035
5198
|
|
|
4036
5199
|
// src/codebase-index/bm25.ts
|
|
4037
5200
|
var K1 = 1.5;
|
|
@@ -4126,11 +5289,11 @@ var Bm25Index = class {
|
|
|
4126
5289
|
init_languages();
|
|
4127
5290
|
|
|
4128
5291
|
// src/codebase-index/schema.ts
|
|
4129
|
-
var SCHEMA_VERSION =
|
|
5292
|
+
var SCHEMA_VERSION = 5;
|
|
4130
5293
|
|
|
4131
5294
|
// src/codebase-index/sqlite-runtime.ts
|
|
4132
|
-
import { createRequire } from "node:module";
|
|
4133
5295
|
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
5296
|
+
import { loadRuntimeDatabaseSync } from "@wrongstack/persistence";
|
|
4134
5297
|
var warningSilenced = false;
|
|
4135
5298
|
function silenceSqliteExperimentalWarning() {
|
|
4136
5299
|
if (warningSilenced) return;
|
|
@@ -4148,11 +5311,10 @@ function loadDatabaseSync() {
|
|
|
4148
5311
|
if (DatabaseSyncCtor) return DatabaseSyncCtor;
|
|
4149
5312
|
silenceSqliteExperimentalWarning();
|
|
4150
5313
|
try {
|
|
4151
|
-
|
|
4152
|
-
DatabaseSyncCtor = req("node:sqlite").DatabaseSync;
|
|
5314
|
+
DatabaseSyncCtor = loadRuntimeDatabaseSync();
|
|
4153
5315
|
} catch (err) {
|
|
4154
5316
|
throw new Error(
|
|
4155
|
-
`The codebase index needs
|
|
5317
|
+
`The codebase index needs node:sqlite (Node >= 22.5) or bun:sqlite. This runtime doesn't provide it: ${toErrorMessage(err)}`
|
|
4156
5318
|
);
|
|
4157
5319
|
}
|
|
4158
5320
|
return DatabaseSyncCtor;
|
|
@@ -4195,9 +5357,87 @@ function runSqliteWithRetry(fn) {
|
|
|
4195
5357
|
throw lastError;
|
|
4196
5358
|
}
|
|
4197
5359
|
|
|
5360
|
+
// src/codebase-index/vector-search.ts
|
|
5361
|
+
var RRF_K = 60;
|
|
5362
|
+
function vectorEmbeddingEnabled() {
|
|
5363
|
+
return process.env["WRONGSTACK_INDEX_VECTORS"] === "1";
|
|
5364
|
+
}
|
|
5365
|
+
var VECTOR_DIMENSIONS = 384;
|
|
5366
|
+
var NGRAM_SIZE = 3;
|
|
5367
|
+
function embedText(text) {
|
|
5368
|
+
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
5369
|
+
const normalized = text.toLowerCase().trim();
|
|
5370
|
+
if (normalized.length < NGRAM_SIZE) {
|
|
5371
|
+
const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
|
|
5372
|
+
for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
|
|
5373
|
+
const ngram = padded.slice(i, i + NGRAM_SIZE);
|
|
5374
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5375
|
+
vec[bucket] += 1;
|
|
5376
|
+
}
|
|
5377
|
+
} else {
|
|
5378
|
+
for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
|
|
5379
|
+
const ngram = normalized.slice(i, i + NGRAM_SIZE);
|
|
5380
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5381
|
+
vec[bucket] += 1;
|
|
5382
|
+
}
|
|
5383
|
+
}
|
|
5384
|
+
let norm = 0;
|
|
5385
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5386
|
+
norm += vec[i] * vec[i];
|
|
5387
|
+
}
|
|
5388
|
+
norm = Math.sqrt(norm);
|
|
5389
|
+
if (norm > 0) {
|
|
5390
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5391
|
+
vec[i] /= norm;
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
return vec;
|
|
5395
|
+
}
|
|
5396
|
+
function hashNgram(str) {
|
|
5397
|
+
let hash = 2166136261;
|
|
5398
|
+
for (let i = 0; i < str.length; i++) {
|
|
5399
|
+
hash ^= str.charCodeAt(i);
|
|
5400
|
+
hash = Math.imul(hash, 16777619);
|
|
5401
|
+
}
|
|
5402
|
+
return hash >>> 0;
|
|
5403
|
+
}
|
|
5404
|
+
function cosineSimilarity(a, b) {
|
|
5405
|
+
let dot = 0;
|
|
5406
|
+
const len = Math.min(a.length, b.length);
|
|
5407
|
+
for (let i = 0; i < len; i++) {
|
|
5408
|
+
dot += a[i] * b[i];
|
|
5409
|
+
}
|
|
5410
|
+
return dot;
|
|
5411
|
+
}
|
|
5412
|
+
function encodeVector(vec) {
|
|
5413
|
+
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
5414
|
+
}
|
|
5415
|
+
function decodeVector(buf) {
|
|
5416
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
5417
|
+
const copy = new Float32Array(buf.byteLength / 4);
|
|
5418
|
+
for (let i = 0; i < copy.length; i++) {
|
|
5419
|
+
copy[i] = view.getFloat32(i * 4, true);
|
|
5420
|
+
}
|
|
5421
|
+
return copy;
|
|
5422
|
+
}
|
|
5423
|
+
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
5424
|
+
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
5425
|
+
const scored = [];
|
|
5426
|
+
for (const id of allIds) {
|
|
5427
|
+
const bm25Rank = bm25Ranks.get(id);
|
|
5428
|
+
const vecRank = vectorRanks.get(id);
|
|
5429
|
+
let score = 0;
|
|
5430
|
+
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
5431
|
+
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
5432
|
+
scored.push([id, score]);
|
|
5433
|
+
}
|
|
5434
|
+
scored.sort((a, b) => b[1] - a[1]);
|
|
5435
|
+
return scored;
|
|
5436
|
+
}
|
|
5437
|
+
|
|
4198
5438
|
// src/codebase-index/writer-admin.ts
|
|
4199
|
-
import * as
|
|
4200
|
-
import * as
|
|
5439
|
+
import * as fs8 from "node:fs";
|
|
5440
|
+
import * as path12 from "node:path";
|
|
4201
5441
|
var DB_FILE = "index.db";
|
|
4202
5442
|
function getAllIndexableWithStatement(stmt) {
|
|
4203
5443
|
return stmt("SELECT id, text FROM symbols").all().map(
|
|
@@ -4233,6 +5473,14 @@ function getMetadataWithStatement(stmt, key) {
|
|
|
4233
5473
|
const rows = stmt("SELECT value FROM metadata WHERE key = ?").all(key);
|
|
4234
5474
|
return rows[0]?.value;
|
|
4235
5475
|
}
|
|
5476
|
+
function getIndexSummaryWithStatement(stmt) {
|
|
5477
|
+
const fileRows = stmt("SELECT COUNT(*) AS n FROM files").all();
|
|
5478
|
+
const lastRows = stmt("SELECT value FROM metadata WHERE key = 'last_indexed'").all();
|
|
5479
|
+
return {
|
|
5480
|
+
totalFiles: fileRows[0] ? Number(fileRows[0].n) : 0,
|
|
5481
|
+
lastIndexed: lastRows.length ? Number(lastRows[0]?.value) : null
|
|
5482
|
+
};
|
|
5483
|
+
}
|
|
4236
5484
|
function getFileMetaWithStatement(stmt, file) {
|
|
4237
5485
|
const rows = stmt(
|
|
4238
5486
|
"SELECT file, lang, mtime_ms, symbol_count, last_indexed, content_hash FROM files WHERE file = ?"
|
|
@@ -4262,22 +5510,102 @@ function getAllFileMetasWithStatement(stmt) {
|
|
|
4262
5510
|
}
|
|
4263
5511
|
function getIndexDbSizeBytes(indexDir) {
|
|
4264
5512
|
try {
|
|
4265
|
-
return
|
|
5513
|
+
return fs8.statSync(path12.join(indexDir, DB_FILE)).size;
|
|
4266
5514
|
} catch {
|
|
4267
5515
|
return 0;
|
|
4268
5516
|
}
|
|
4269
5517
|
}
|
|
4270
5518
|
|
|
5519
|
+
// src/codebase-index/writer-helpers.ts
|
|
5520
|
+
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
5521
|
+
function escapeLike(value) {
|
|
5522
|
+
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
5523
|
+
}
|
|
5524
|
+
function ladderChunkSizes(total, max) {
|
|
5525
|
+
if (total <= 0) return [];
|
|
5526
|
+
const sizes = [];
|
|
5527
|
+
let remaining = total;
|
|
5528
|
+
while (remaining > 0) {
|
|
5529
|
+
const cap = Math.min(remaining, max);
|
|
5530
|
+
const pow = cap >= 1 ? 2 ** Math.floor(Math.log2(cap)) : 1;
|
|
5531
|
+
const take = Math.max(1, Math.min(pow, remaining));
|
|
5532
|
+
sizes.push(take);
|
|
5533
|
+
remaining -= take;
|
|
5534
|
+
}
|
|
5535
|
+
return sizes;
|
|
5536
|
+
}
|
|
5537
|
+
function nextPow2(count) {
|
|
5538
|
+
return count <= 1 ? 1 : 2 ** Math.ceil(Math.log2(count));
|
|
5539
|
+
}
|
|
5540
|
+
function padToInBucket(values) {
|
|
5541
|
+
if (values.length <= 1) return values.slice();
|
|
5542
|
+
const target = nextPow2(values.length);
|
|
5543
|
+
const padded = values.slice();
|
|
5544
|
+
while (padded.length < target) padded.push(padded[0]);
|
|
5545
|
+
return padded;
|
|
5546
|
+
}
|
|
5547
|
+
function placeholders(count) {
|
|
5548
|
+
return Array.from({ length: count }, () => "?").join(",");
|
|
5549
|
+
}
|
|
5550
|
+
function inListChunks(total, max) {
|
|
5551
|
+
if (total <= 0) return [];
|
|
5552
|
+
if (nextPow2(total) <= max) return [total];
|
|
5553
|
+
const powMax = Math.max(1, 2 ** Math.floor(Math.log2(max)));
|
|
5554
|
+
return ladderChunkSizes(total, powMax);
|
|
5555
|
+
}
|
|
5556
|
+
function posixIndexPath(file) {
|
|
5557
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
5558
|
+
}
|
|
5559
|
+
function indexedFileMatchSql(column = "file") {
|
|
5560
|
+
return `(${column} = ? OR replace(${column}, '\\', '/') = ? OR replace(${column}, '\\', '/') LIKE ? ESCAPE '\\')`;
|
|
5561
|
+
}
|
|
5562
|
+
function indexedFileMatchArgs(file) {
|
|
5563
|
+
const posix4 = posixIndexPath(file.trim());
|
|
5564
|
+
return [file, posix4, `%/${escapeLike(posix4)}`];
|
|
5565
|
+
}
|
|
5566
|
+
function matchesIndexedPackageFilter(storedFile, packageLabel, filter) {
|
|
5567
|
+
if (packageLabel === filter) return true;
|
|
5568
|
+
const posixFile = posixIndexPath(storedFile);
|
|
5569
|
+
const posixFilter = posixIndexPath(filter.trim());
|
|
5570
|
+
if (!posixFilter) return false;
|
|
5571
|
+
return posixFile === posixFilter || posixFile.endsWith(`/${posixFilter}`) || posixFile.includes(`/${posixFilter}/`);
|
|
5572
|
+
}
|
|
5573
|
+
function assignRefsToSymbols(refs, symbols) {
|
|
5574
|
+
if (refs.length === 0 || symbols.length === 0) return [];
|
|
5575
|
+
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
5576
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5577
|
+
const assigned = [];
|
|
5578
|
+
for (const ref of refs) {
|
|
5579
|
+
let owner;
|
|
5580
|
+
for (const symbol of ordered) {
|
|
5581
|
+
if (symbol.line > ref.line) break;
|
|
5582
|
+
owner = symbol;
|
|
5583
|
+
}
|
|
5584
|
+
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
5585
|
+
if (!owner || owner.id <= 0) continue;
|
|
5586
|
+
const key = `${owner.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
5587
|
+
if (seen.has(key)) continue;
|
|
5588
|
+
seen.add(key);
|
|
5589
|
+
assigned.push({ ...ref, fromId: owner.id });
|
|
5590
|
+
}
|
|
5591
|
+
return assigned;
|
|
5592
|
+
}
|
|
5593
|
+
function resolveIndexDir(projectRoot, override) {
|
|
5594
|
+
return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
|
|
5595
|
+
}
|
|
5596
|
+
|
|
4271
5597
|
// src/codebase-index/writer-bulk-insert.ts
|
|
4272
5598
|
function bulkInsertSymbolsWithStatement(stmt, maxSqlVars, rows) {
|
|
4273
5599
|
if (rows.length === 0) return;
|
|
4274
|
-
const
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
const
|
|
5600
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 12)));
|
|
5601
|
+
let cursor = 0;
|
|
5602
|
+
for (const take of ladder) {
|
|
5603
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
5604
|
+
cursor += take;
|
|
5605
|
+
const placeholders2 = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
4278
5606
|
const insert = stmt(
|
|
4279
|
-
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text
|
|
4280
|
-
VALUES ${
|
|
5607
|
+
`INSERT INTO symbols(id, lang, kind, name, file, line, col, signature, doc_comment, scope, text)
|
|
5608
|
+
VALUES ${placeholders2}`
|
|
4281
5609
|
);
|
|
4282
5610
|
const binds = [];
|
|
4283
5611
|
for (const r of chunk) {
|
|
@@ -4292,8 +5620,7 @@ function bulkInsertSymbolsWithStatement(stmt, maxSqlVars, rows) {
|
|
|
4292
5620
|
r.signature,
|
|
4293
5621
|
r.docComment,
|
|
4294
5622
|
r.scope,
|
|
4295
|
-
r.text
|
|
4296
|
-
r.file
|
|
5623
|
+
r.text
|
|
4297
5624
|
);
|
|
4298
5625
|
}
|
|
4299
5626
|
insert.run(...binds);
|
|
@@ -4301,11 +5628,13 @@ function bulkInsertSymbolsWithStatement(stmt, maxSqlVars, rows) {
|
|
|
4301
5628
|
}
|
|
4302
5629
|
function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
|
|
4303
5630
|
if (!ftsAvailable || rows.length === 0) return;
|
|
4304
|
-
const
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
const
|
|
4308
|
-
|
|
5631
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 2)));
|
|
5632
|
+
let cursor = 0;
|
|
5633
|
+
for (const take of ladder) {
|
|
5634
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
5635
|
+
cursor += take;
|
|
5636
|
+
const placeholders2 = chunk.map(() => "(?, ?)").join(", ");
|
|
5637
|
+
const insert = stmt(`INSERT INTO symbols_fts(rowid, text) VALUES ${placeholders2}`);
|
|
4309
5638
|
const binds = [];
|
|
4310
5639
|
for (const r of chunk) binds.push(r.id, r.text);
|
|
4311
5640
|
insert.run(...binds);
|
|
@@ -4313,11 +5642,13 @@ function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
|
|
|
4313
5642
|
}
|
|
4314
5643
|
function bulkInsertVectorsWithStatement(stmt, maxSqlVars, rows) {
|
|
4315
5644
|
if (rows.length === 0) return;
|
|
4316
|
-
const
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
const
|
|
4320
|
-
|
|
5645
|
+
const ladder = ladderChunkSizes(rows.length, Math.max(1, Math.floor(maxSqlVars / 2)));
|
|
5646
|
+
let cursor = 0;
|
|
5647
|
+
for (const take of ladder) {
|
|
5648
|
+
const chunk = rows.slice(cursor, cursor + take);
|
|
5649
|
+
cursor += take;
|
|
5650
|
+
const placeholders2 = chunk.map(() => "(?, ?)").join(", ");
|
|
5651
|
+
const insert = stmt(`INSERT INTO symbol_vectors(symbol_id, vector) VALUES ${placeholders2}`);
|
|
4321
5652
|
const binds = [];
|
|
4322
5653
|
for (const r of chunk) binds.push(r.id, r.vector);
|
|
4323
5654
|
insert.run(...binds);
|
|
@@ -4325,13 +5656,15 @@ function bulkInsertVectorsWithStatement(stmt, maxSqlVars, rows) {
|
|
|
4325
5656
|
}
|
|
4326
5657
|
function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
|
|
4327
5658
|
if (refs.length === 0) return;
|
|
4328
|
-
const
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
const
|
|
5659
|
+
const ladder = ladderChunkSizes(refs.length, Math.max(1, Math.floor(maxSqlVars / 8)));
|
|
5660
|
+
let cursor = 0;
|
|
5661
|
+
for (const take of ladder) {
|
|
5662
|
+
const chunk = refs.slice(cursor, cursor + take);
|
|
5663
|
+
cursor += take;
|
|
5664
|
+
const placeholders2 = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
4332
5665
|
const insert = stmt(
|
|
4333
5666
|
`INSERT INTO refs(from_id, to_name, to_id, call_type, line, lang, module, to_file)
|
|
4334
|
-
VALUES ${
|
|
5667
|
+
VALUES ${placeholders2}`
|
|
4335
5668
|
);
|
|
4336
5669
|
const binds = [];
|
|
4337
5670
|
for (const ref of chunk) {
|
|
@@ -4485,52 +5818,6 @@ function materializeWeightedEdges(edgeMap, idPrefix) {
|
|
|
4485
5818
|
return edges;
|
|
4486
5819
|
}
|
|
4487
5820
|
|
|
4488
|
-
// src/codebase-index/writer-helpers.ts
|
|
4489
|
-
import { resolveWstackPaths } from "@wrongstack/core/utils";
|
|
4490
|
-
function escapeLike(value) {
|
|
4491
|
-
return value.replace(/[\\%_]/g, (char) => `\\${char}`);
|
|
4492
|
-
}
|
|
4493
|
-
function posixIndexPath(file) {
|
|
4494
|
-
return file.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
4495
|
-
}
|
|
4496
|
-
function indexedFileMatchSql(column = "file") {
|
|
4497
|
-
return `(${column} = ? OR replace(${column}, '\\', '/') = ? OR replace(${column}, '\\', '/') LIKE ? ESCAPE '\\')`;
|
|
4498
|
-
}
|
|
4499
|
-
function indexedFileMatchArgs(file) {
|
|
4500
|
-
const posix4 = posixIndexPath(file.trim());
|
|
4501
|
-
return [file, posix4, `%/${escapeLike(posix4)}`];
|
|
4502
|
-
}
|
|
4503
|
-
function matchesIndexedPackageFilter(storedFile, packageLabel, filter) {
|
|
4504
|
-
if (packageLabel === filter) return true;
|
|
4505
|
-
const posixFile = posixIndexPath(storedFile);
|
|
4506
|
-
const posixFilter = posixIndexPath(filter.trim());
|
|
4507
|
-
if (!posixFilter) return false;
|
|
4508
|
-
return posixFile === posixFilter || posixFile.endsWith(`/${posixFilter}`) || posixFile.includes(`/${posixFilter}/`);
|
|
4509
|
-
}
|
|
4510
|
-
function assignRefsToSymbols(refs, symbols) {
|
|
4511
|
-
if (refs.length === 0 || symbols.length === 0) return [];
|
|
4512
|
-
const ordered = [...symbols].sort((a, b) => a.line - b.line || a.col - b.col || a.id - b.id);
|
|
4513
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4514
|
-
const assigned = [];
|
|
4515
|
-
for (const ref of refs) {
|
|
4516
|
-
let owner;
|
|
4517
|
-
for (const symbol of ordered) {
|
|
4518
|
-
if (symbol.line > ref.line) break;
|
|
4519
|
-
owner = symbol;
|
|
4520
|
-
}
|
|
4521
|
-
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
4522
|
-
if (!owner || owner.id <= 0) continue;
|
|
4523
|
-
const key = `${owner.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
4524
|
-
if (seen.has(key)) continue;
|
|
4525
|
-
seen.add(key);
|
|
4526
|
-
assigned.push({ ...ref, fromId: owner.id });
|
|
4527
|
-
}
|
|
4528
|
-
return assigned;
|
|
4529
|
-
}
|
|
4530
|
-
function resolveIndexDir(projectRoot, override) {
|
|
4531
|
-
return override ?? resolveWstackPaths({ projectRoot }).projectCodebaseIndex;
|
|
4532
|
-
}
|
|
4533
|
-
|
|
4534
5821
|
// src/codebase-index/writer-ref-mapper.ts
|
|
4535
5822
|
function mapWriterRefRow(row) {
|
|
4536
5823
|
return {
|
|
@@ -4552,20 +5839,20 @@ function mapWriterRefRow(row) {
|
|
|
4552
5839
|
var MAX_SQL_VARS = 900;
|
|
4553
5840
|
function chunkedIdQuery(stmt, ids, buildSql, extraArgs = []) {
|
|
4554
5841
|
const results = [];
|
|
4555
|
-
for (
|
|
4556
|
-
const chunk = ids.slice(
|
|
4557
|
-
|
|
4558
|
-
const sql = buildSql(placeholders);
|
|
5842
|
+
for (const take of inListChunks(ids.length, MAX_SQL_VARS)) {
|
|
5843
|
+
const chunk = padToInBucket(ids.slice(0, take));
|
|
5844
|
+
ids = ids.slice(take);
|
|
5845
|
+
const sql = buildSql(placeholders(chunk.length));
|
|
4559
5846
|
results.push(...stmt(sql).all(...chunk, ...extraArgs));
|
|
4560
5847
|
}
|
|
4561
5848
|
return results;
|
|
4562
5849
|
}
|
|
4563
5850
|
function chunkedIdScalar(stmt, ids, buildSql, extraArgs = []) {
|
|
4564
5851
|
let total = 0;
|
|
4565
|
-
for (
|
|
4566
|
-
const chunk = ids.slice(
|
|
4567
|
-
|
|
4568
|
-
const sql = buildSql(placeholders);
|
|
5852
|
+
for (const take of inListChunks(ids.length, MAX_SQL_VARS)) {
|
|
5853
|
+
const chunk = padToInBucket(ids.slice(0, take));
|
|
5854
|
+
ids = ids.slice(take);
|
|
5855
|
+
const sql = buildSql(placeholders(chunk.length));
|
|
4569
5856
|
const rows = stmt(sql).all(...chunk, ...extraArgs);
|
|
4570
5857
|
total += rows[0]?.n ?? 0;
|
|
4571
5858
|
}
|
|
@@ -4594,16 +5881,24 @@ function resolveIndexedFiles(stmt, file) {
|
|
|
4594
5881
|
}
|
|
4595
5882
|
function resolveSymbolIds(stmt, symbolName, file) {
|
|
4596
5883
|
if (!file) {
|
|
4597
|
-
const
|
|
4598
|
-
|
|
5884
|
+
const rows = stmt("SELECT id FROM symbols WHERE name = ? ORDER BY id").all(
|
|
5885
|
+
symbolName
|
|
5886
|
+
);
|
|
5887
|
+
return rows.map((r) => r.id);
|
|
4599
5888
|
}
|
|
4600
5889
|
const indexedFiles = resolveIndexedFiles(stmt, file);
|
|
4601
5890
|
if (indexedFiles.length === 0) return [];
|
|
4602
|
-
const
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
5891
|
+
const ids = [];
|
|
5892
|
+
let cursor = 0;
|
|
5893
|
+
for (const take of inListChunks(indexedFiles.length, MAX_SQL_VARS)) {
|
|
5894
|
+
const files = padToInBucket(indexedFiles.slice(cursor, cursor + take));
|
|
5895
|
+
cursor += take;
|
|
5896
|
+
const rows = stmt(
|
|
5897
|
+
`SELECT id FROM symbols WHERE name = ? AND file IN (${placeholders(files.length)}) ORDER BY id`
|
|
5898
|
+
).all(symbolName, ...files);
|
|
5899
|
+
ids.push(...rows.map((r) => r.id));
|
|
5900
|
+
}
|
|
5901
|
+
return ids;
|
|
4607
5902
|
}
|
|
4608
5903
|
function findIncomingCallsByName(stmt, symbolName, file, limit) {
|
|
4609
5904
|
const targetIds = resolveSymbolIds(stmt, symbolName, file);
|
|
@@ -5037,9 +6332,9 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
5037
6332
|
const loadedIds = new Set(syms.map((s) => s.id));
|
|
5038
6333
|
const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
|
|
5039
6334
|
if (missingIds.length > 0) {
|
|
5040
|
-
const
|
|
6335
|
+
const placeholders2 = missingIds.map(() => "?").join(",");
|
|
5041
6336
|
const extras = stmt(
|
|
5042
|
-
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${
|
|
6337
|
+
`SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders2})`
|
|
5043
6338
|
).all(...missingIds);
|
|
5044
6339
|
for (const s of extras) symById.set(s.id, s);
|
|
5045
6340
|
}
|
|
@@ -5052,81 +6347,6 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
5052
6347
|
return { nodes, edges };
|
|
5053
6348
|
}
|
|
5054
6349
|
|
|
5055
|
-
// src/codebase-index/vector-search.ts
|
|
5056
|
-
var RRF_K = 60;
|
|
5057
|
-
var VECTOR_DIMENSIONS = 384;
|
|
5058
|
-
var NGRAM_SIZE = 3;
|
|
5059
|
-
function embedText(text) {
|
|
5060
|
-
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
5061
|
-
const normalized = text.toLowerCase().trim();
|
|
5062
|
-
if (normalized.length < NGRAM_SIZE) {
|
|
5063
|
-
const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
|
|
5064
|
-
for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
|
|
5065
|
-
const ngram = padded.slice(i, i + NGRAM_SIZE);
|
|
5066
|
-
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5067
|
-
vec[bucket] += 1;
|
|
5068
|
-
}
|
|
5069
|
-
} else {
|
|
5070
|
-
for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
|
|
5071
|
-
const ngram = normalized.slice(i, i + NGRAM_SIZE);
|
|
5072
|
-
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5073
|
-
vec[bucket] += 1;
|
|
5074
|
-
}
|
|
5075
|
-
}
|
|
5076
|
-
let norm = 0;
|
|
5077
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5078
|
-
norm += vec[i] * vec[i];
|
|
5079
|
-
}
|
|
5080
|
-
norm = Math.sqrt(norm);
|
|
5081
|
-
if (norm > 0) {
|
|
5082
|
-
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5083
|
-
vec[i] /= norm;
|
|
5084
|
-
}
|
|
5085
|
-
}
|
|
5086
|
-
return vec;
|
|
5087
|
-
}
|
|
5088
|
-
function hashNgram(str) {
|
|
5089
|
-
let hash = 2166136261;
|
|
5090
|
-
for (let i = 0; i < str.length; i++) {
|
|
5091
|
-
hash ^= str.charCodeAt(i);
|
|
5092
|
-
hash = Math.imul(hash, 16777619);
|
|
5093
|
-
}
|
|
5094
|
-
return hash >>> 0;
|
|
5095
|
-
}
|
|
5096
|
-
function cosineSimilarity(a, b) {
|
|
5097
|
-
let dot = 0;
|
|
5098
|
-
const len = Math.min(a.length, b.length);
|
|
5099
|
-
for (let i = 0; i < len; i++) {
|
|
5100
|
-
dot += a[i] * b[i];
|
|
5101
|
-
}
|
|
5102
|
-
return dot;
|
|
5103
|
-
}
|
|
5104
|
-
function encodeVector(vec) {
|
|
5105
|
-
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
5106
|
-
}
|
|
5107
|
-
function decodeVector(buf) {
|
|
5108
|
-
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
5109
|
-
const copy = new Float32Array(buf.byteLength / 4);
|
|
5110
|
-
for (let i = 0; i < copy.length; i++) {
|
|
5111
|
-
copy[i] = view.getFloat32(i * 4, true);
|
|
5112
|
-
}
|
|
5113
|
-
return copy;
|
|
5114
|
-
}
|
|
5115
|
-
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
5116
|
-
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
5117
|
-
const scored = [];
|
|
5118
|
-
for (const id of allIds) {
|
|
5119
|
-
const bm25Rank = bm25Ranks.get(id);
|
|
5120
|
-
const vecRank = vectorRanks.get(id);
|
|
5121
|
-
let score = 0;
|
|
5122
|
-
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
5123
|
-
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
5124
|
-
scored.push([id, score]);
|
|
5125
|
-
}
|
|
5126
|
-
scored.sort((a, b) => b[1] - a[1]);
|
|
5127
|
-
return scored;
|
|
5128
|
-
}
|
|
5129
|
-
|
|
5130
6350
|
// src/codebase-index/writer-mutations.ts
|
|
5131
6351
|
function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvailable, allocateSymbolIds, invalidateIncomingRefsForFiles, resolveRefsForNamesUnsafe2, entries, options = {}) {
|
|
5132
6352
|
if (entries.length === 0 && (options.deleteForFiles?.length ?? 0) === 0) {
|
|
@@ -5138,24 +6358,29 @@ function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvail
|
|
|
5138
6358
|
for (const ref of entry.refs) affectedNames.add(ref.toName);
|
|
5139
6359
|
}
|
|
5140
6360
|
if (options.deleteForFiles && options.deleteForFiles.length > 0) {
|
|
5141
|
-
const placeholders = options.deleteForFiles.map(() => "?").join(",");
|
|
5142
6361
|
for (const name of invalidateIncomingRefsForFiles(options.deleteForFiles)) {
|
|
5143
6362
|
affectedNames.add(name);
|
|
5144
6363
|
}
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
6364
|
+
let cursor = 0;
|
|
6365
|
+
for (const take of inListChunks(options.deleteForFiles.length, Math.floor(maxSqlVars / 4))) {
|
|
6366
|
+
const bucket = padToInBucket(options.deleteForFiles.slice(cursor, cursor + take));
|
|
6367
|
+
cursor += take;
|
|
6368
|
+
const ph = placeholders(bucket.length);
|
|
6369
|
+
if (ftsAvailable) {
|
|
6370
|
+
stmtFn(
|
|
6371
|
+
`DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
6372
|
+
).run(...bucket);
|
|
6373
|
+
}
|
|
6374
|
+
if (vectorsAvailable) {
|
|
6375
|
+
stmtFn(
|
|
6376
|
+
`DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
6377
|
+
).run(...bucket);
|
|
6378
|
+
}
|
|
5151
6379
|
stmtFn(
|
|
5152
|
-
`DELETE FROM
|
|
5153
|
-
).run(...
|
|
6380
|
+
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
6381
|
+
).run(...bucket);
|
|
6382
|
+
stmtFn(`DELETE FROM symbols WHERE file IN (${ph})`).run(...bucket);
|
|
5154
6383
|
}
|
|
5155
|
-
stmtFn(
|
|
5156
|
-
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
5157
|
-
).run(...options.deleteForFiles);
|
|
5158
|
-
stmtFn(`DELETE FROM symbols WHERE file IN (${placeholders})`).run(...options.deleteForFiles);
|
|
5159
6384
|
}
|
|
5160
6385
|
const totalSymbols = entries.reduce((n, e) => n + e.symbols.length, 0);
|
|
5161
6386
|
let nextId = allocateSymbolIds(totalSymbols);
|
|
@@ -5187,12 +6412,14 @@ function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvail
|
|
|
5187
6412
|
text: buildIndexableText(s.name, s.signature, s.docComment)
|
|
5188
6413
|
});
|
|
5189
6414
|
}
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
6415
|
+
if (vectorsAvailable) {
|
|
6416
|
+
vectorRows.push({
|
|
6417
|
+
id,
|
|
6418
|
+
vector: encodeVector(
|
|
6419
|
+
embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
|
|
6420
|
+
)
|
|
6421
|
+
});
|
|
6422
|
+
}
|
|
5196
6423
|
const inserted = { ...s, id };
|
|
5197
6424
|
allInserted.push(inserted);
|
|
5198
6425
|
insertedForEntry.push(inserted);
|
|
@@ -5284,9 +6511,8 @@ var CORE_TABLES_SQL = `
|
|
|
5284
6511
|
signature TEXT NOT NULL DEFAULT '',
|
|
5285
6512
|
doc_comment TEXT NOT NULL DEFAULT '',
|
|
5286
6513
|
scope TEXT NOT NULL DEFAULT '',
|
|
5287
|
-
text TEXT NOT NULL DEFAULT ''
|
|
5288
|
-
|
|
5289
|
-
);
|
|
6514
|
+
text TEXT NOT NULL DEFAULT ''
|
|
6515
|
+
);
|
|
5290
6516
|
`;
|
|
5291
6517
|
var FILE_INDEX_SQL = [
|
|
5292
6518
|
"CREATE INDEX IF NOT EXISTS idx_f_package ON files(package)"
|
|
@@ -5297,7 +6523,6 @@ var SYMBOL_INDEX_SQL = [
|
|
|
5297
6523
|
"CREATE INDEX IF NOT EXISTS idx_s_lang ON symbols(lang)",
|
|
5298
6524
|
"CREATE INDEX IF NOT EXISTS idx_s_file ON symbols(file)",
|
|
5299
6525
|
"CREATE INDEX IF NOT EXISTS idx_s_lang_kind ON symbols(lang, kind)",
|
|
5300
|
-
"CREATE INDEX IF NOT EXISTS idx_s_file_fk ON symbols(file_fk)",
|
|
5301
6526
|
"CREATE INDEX IF NOT EXISTS idx_s_name_id ON symbols(name, id)"
|
|
5302
6527
|
];
|
|
5303
6528
|
var REFS_TABLE_SQL = `
|
|
@@ -5372,11 +6597,12 @@ function getUnresolvedImportsWithStatement(stmtFn, maxSqlVars, onlyFiles) {
|
|
|
5372
6597
|
return stmtFn(base).all();
|
|
5373
6598
|
}
|
|
5374
6599
|
const out = [];
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
const
|
|
6600
|
+
let cursor = 0;
|
|
6601
|
+
for (const take of inListChunks(onlyFiles.length, maxSqlVars)) {
|
|
6602
|
+
const chunk = padToInBucket(onlyFiles.slice(cursor, cursor + take));
|
|
6603
|
+
cursor += take;
|
|
5378
6604
|
out.push(
|
|
5379
|
-
...stmtFn(`${base} AND s.file IN (${placeholders})`).all(...chunk)
|
|
6605
|
+
...stmtFn(`${base} AND s.file IN (${placeholders(chunk.length)})`).all(...chunk)
|
|
5380
6606
|
);
|
|
5381
6607
|
}
|
|
5382
6608
|
return out;
|
|
@@ -5447,16 +6673,18 @@ function applyImportResolutionsWithStatement(db, stmtFn, runWithRetry, maxSqlVar
|
|
|
5447
6673
|
)`
|
|
5448
6674
|
);
|
|
5449
6675
|
const chunkSize = Math.max(1, Math.floor(maxSqlVars / 4));
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
const
|
|
6676
|
+
let cursor = 0;
|
|
6677
|
+
for (const take of ladderChunkSizes(resolutions.length, chunkSize)) {
|
|
6678
|
+
const chunk = resolutions.slice(cursor, cursor + take);
|
|
6679
|
+
cursor += take;
|
|
6680
|
+
const valuesPh = chunk.map(() => "(?, ?, ?, ?)").join(", ");
|
|
5453
6681
|
const binds = [];
|
|
5454
6682
|
for (const entry of chunk) {
|
|
5455
6683
|
binds.push(entry.fromFile, entry.lang, entry.module, entry.toFile);
|
|
5456
6684
|
}
|
|
5457
6685
|
stmtFn(
|
|
5458
6686
|
`INSERT INTO temp.import_resolution(from_file, lang, module, to_file)
|
|
5459
|
-
VALUES ${
|
|
6687
|
+
VALUES ${valuesPh}`
|
|
5460
6688
|
).run(...binds);
|
|
5461
6689
|
}
|
|
5462
6690
|
db.exec(
|
|
@@ -5493,9 +6721,11 @@ function resolveRefsForNamesUnsafe(stmtFn, maxSqlVars, names) {
|
|
|
5493
6721
|
const list = [...names].filter((name) => name.length > 0);
|
|
5494
6722
|
if (list.length === 0) return 0;
|
|
5495
6723
|
let total = 0;
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
const
|
|
6724
|
+
let cursor = 0;
|
|
6725
|
+
for (const take of inListChunks(list.length, maxSqlVars)) {
|
|
6726
|
+
const chunk = padToInBucket(list.slice(cursor, cursor + take));
|
|
6727
|
+
cursor += take;
|
|
6728
|
+
const ph = placeholders(chunk.length);
|
|
5499
6729
|
try {
|
|
5500
6730
|
const result = stmtFn(
|
|
5501
6731
|
`UPDATE refs
|
|
@@ -5504,16 +6734,16 @@ function resolveRefsForNamesUnsafe(stmtFn, maxSqlVars, names) {
|
|
|
5504
6734
|
SELECT sym.name AS name, lf.family AS family, MIN(sym.id) AS id
|
|
5505
6735
|
FROM symbols sym
|
|
5506
6736
|
JOIN lang_family lf ON lf.lang = sym.lang
|
|
5507
|
-
WHERE sym.name IN (${
|
|
6737
|
+
WHERE sym.name IN (${ph})
|
|
5508
6738
|
GROUP BY sym.name, lf.family
|
|
5509
6739
|
UNION ALL
|
|
5510
6740
|
SELECT sym.name AS name, '${LANG_FAMILY_WILDCARD}' AS family, MIN(sym.id) AS id
|
|
5511
6741
|
FROM symbols sym
|
|
5512
|
-
WHERE sym.name IN (${
|
|
6742
|
+
WHERE sym.name IN (${ph})
|
|
5513
6743
|
GROUP BY sym.name
|
|
5514
6744
|
) AS s,
|
|
5515
6745
|
lang_family AS rf
|
|
5516
|
-
WHERE refs.to_name IN (${
|
|
6746
|
+
WHERE refs.to_name IN (${ph})
|
|
5517
6747
|
AND rf.lang = refs.lang
|
|
5518
6748
|
AND s.name = refs.to_name
|
|
5519
6749
|
AND s.family = rf.family`
|
|
@@ -5525,7 +6755,7 @@ function resolveRefsForNamesUnsafe(stmtFn, maxSqlVars, names) {
|
|
|
5525
6755
|
SELECT sym.id FROM symbols sym
|
|
5526
6756
|
WHERE sym.name = refs.to_name AND ${FAMILY_MATCH_SQL}
|
|
5527
6757
|
ORDER BY sym.id LIMIT 1
|
|
5528
|
-
) WHERE refs.to_name IN (${
|
|
6758
|
+
) WHERE refs.to_name IN (${ph})
|
|
5529
6759
|
AND EXISTS (
|
|
5530
6760
|
SELECT 1 FROM symbols sym
|
|
5531
6761
|
WHERE sym.name = refs.to_name AND ${FAMILY_MATCH_SQL}
|
|
@@ -5582,7 +6812,7 @@ function buildWriterSearchWhere(query, filter) {
|
|
|
5582
6812
|
const conditions = [];
|
|
5583
6813
|
const values = [];
|
|
5584
6814
|
let effectiveKind = filter?.kind;
|
|
5585
|
-
if (filter?.lspKind
|
|
6815
|
+
if (filter?.lspKind != null) {
|
|
5586
6816
|
const mapped = lspKindToInternalKind(filter.lspKind);
|
|
5587
6817
|
if (mapped !== null) {
|
|
5588
6818
|
effectiveKind = mapped;
|
|
@@ -5661,7 +6891,7 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
|
|
|
5661
6891
|
);
|
|
5662
6892
|
}
|
|
5663
6893
|
let effectiveKind = filter?.kind;
|
|
5664
|
-
if (filter?.lspKind
|
|
6894
|
+
if (filter?.lspKind != null) {
|
|
5665
6895
|
const mapped = lspKindToInternalKind(filter.lspKind);
|
|
5666
6896
|
if (mapped === null) return { results: [], total: 0 };
|
|
5667
6897
|
effectiveKind = mapped;
|
|
@@ -5698,15 +6928,14 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
|
|
|
5698
6928
|
values.push(`%${escapeLike(filter.file.replace(/\\/g, "/"))}%`);
|
|
5699
6929
|
}
|
|
5700
6930
|
const where = conditions.join(" AND ");
|
|
5701
|
-
const countRows = stmtFn(
|
|
5702
|
-
`SELECT COUNT(*) AS n FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid WHERE ${where}`
|
|
5703
|
-
).all(...values);
|
|
5704
|
-
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
5705
|
-
if (total === 0) return { results: [], total: 0 };
|
|
5706
6931
|
const bm25Rows = stmtFn(
|
|
5707
6932
|
`SELECT s.id, s.lang, s.kind, s.name, s.file, s.line, s.col, s.signature, s.doc_comment,
|
|
5708
6933
|
-bm25(symbols_fts) AS score,
|
|
5709
|
-
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
6934
|
+
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet,
|
|
6935
|
+
-- Keep this uncorrelated: referencing outer columns turns it into
|
|
6936
|
+
-- a per-row subquery and defeats the one-count-per-statement win.
|
|
6937
|
+
(SELECT COUNT(*) FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
6938
|
+
WHERE ${where}) AS total_count
|
|
5710
6939
|
FROM symbols_fts JOIN symbols s ON s.id = symbols_fts.rowid
|
|
5711
6940
|
WHERE ${where}
|
|
5712
6941
|
ORDER BY
|
|
@@ -5715,13 +6944,15 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
|
|
|
5715
6944
|
ELSE 2 END,
|
|
5716
6945
|
bm25(symbols_fts), lower(s.name), s.file, s.line, s.col, s.id
|
|
5717
6946
|
LIMIT ?`
|
|
5718
|
-
).all(...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
5719
|
-
if (
|
|
6947
|
+
).all(...values, ...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
6948
|
+
if (bm25Rows.length === 0) return { results: [], total: 0 };
|
|
6949
|
+
const total = Number(bm25Rows[0]?.total_count ?? 0);
|
|
6950
|
+
if (vectorsAvailable) {
|
|
5720
6951
|
const queryVec = embedText(query);
|
|
5721
6952
|
const candidateIds = bm25Rows.map((r) => r.id);
|
|
5722
|
-
const
|
|
6953
|
+
const placeholders2 = candidateIds.map(() => "?").join(",");
|
|
5723
6954
|
const vecRows = stmtFn(
|
|
5724
|
-
`SELECT sv.symbol_id, sv.vector FROM symbol_vectors sv WHERE sv.symbol_id IN (${
|
|
6955
|
+
`SELECT sv.symbol_id, sv.vector FROM symbol_vectors sv WHERE sv.symbol_id IN (${placeholders2})`
|
|
5725
6956
|
).all(...candidateIds);
|
|
5726
6957
|
const vecScores = vecRows.map((r) => ({
|
|
5727
6958
|
id: r.symbol_id,
|
|
@@ -5909,9 +7140,9 @@ var IndexStore = class _IndexStore {
|
|
|
5909
7140
|
}
|
|
5910
7141
|
constructor(projectRoot, opts = {}) {
|
|
5911
7142
|
this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
|
|
5912
|
-
|
|
7143
|
+
fs9.mkdirSync(this.indexDir, { recursive: true });
|
|
5913
7144
|
const Database = loadDatabaseSync();
|
|
5914
|
-
this.db = new Database(
|
|
7145
|
+
this.db = new Database(path13.join(this.indexDir, DB_FILE2));
|
|
5915
7146
|
applyIndexStorePragmas(this.db);
|
|
5916
7147
|
this.initSchema();
|
|
5917
7148
|
}
|
|
@@ -6040,7 +7271,11 @@ var IndexStore = class _IndexStore {
|
|
|
6040
7271
|
);
|
|
6041
7272
|
if (symbolCount !== ftsCount) {
|
|
6042
7273
|
this.db.exec("DELETE FROM symbols_fts");
|
|
6043
|
-
if (
|
|
7274
|
+
if (vectorEmbeddingEnabled() && this.stmt(
|
|
7275
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='symbol_vectors'"
|
|
7276
|
+
).get() !== void 0) {
|
|
7277
|
+
this.db.exec("DELETE FROM symbol_vectors");
|
|
7278
|
+
}
|
|
6044
7279
|
const rows = this.stmt(
|
|
6045
7280
|
"SELECT id, name, signature, doc_comment FROM symbols ORDER BY id"
|
|
6046
7281
|
).all();
|
|
@@ -6059,8 +7294,13 @@ var IndexStore = class _IndexStore {
|
|
|
6059
7294
|
this.ftsAvailable = false;
|
|
6060
7295
|
}
|
|
6061
7296
|
try {
|
|
6062
|
-
|
|
6063
|
-
|
|
7297
|
+
if (vectorEmbeddingEnabled()) {
|
|
7298
|
+
this.db.exec(SYMBOL_VECTORS_TABLE_SQL);
|
|
7299
|
+
this.vectorsAvailable = true;
|
|
7300
|
+
} else {
|
|
7301
|
+
this.db.exec("DROP TABLE IF EXISTS symbol_vectors");
|
|
7302
|
+
this.vectorsAvailable = false;
|
|
7303
|
+
}
|
|
6064
7304
|
} catch {
|
|
6065
7305
|
this.vectorsAvailable = false;
|
|
6066
7306
|
}
|
|
@@ -6095,14 +7335,22 @@ var IndexStore = class _IndexStore {
|
|
|
6095
7335
|
}
|
|
6096
7336
|
invalidateIncomingRefsForFiles(files) {
|
|
6097
7337
|
if (files.length === 0) return /* @__PURE__ */ new Set();
|
|
6098
|
-
const
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
7338
|
+
const names = [];
|
|
7339
|
+
let cursor = 0;
|
|
7340
|
+
for (const take of inListChunks(files.length, _IndexStore.MAX_SQL_VARS)) {
|
|
7341
|
+
const bucket = padToInBucket(files.slice(cursor, cursor + take));
|
|
7342
|
+
cursor += take;
|
|
7343
|
+
const ph = placeholders(bucket.length);
|
|
7344
|
+
for (const row of this.stmt(`SELECT DISTINCT name FROM symbols WHERE file IN (${ph})`).all(
|
|
7345
|
+
...bucket
|
|
7346
|
+
)) {
|
|
7347
|
+
names.push(row.name);
|
|
7348
|
+
}
|
|
7349
|
+
this.stmt(
|
|
7350
|
+
`UPDATE refs SET to_id = NULL
|
|
7351
|
+
WHERE to_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
|
|
7352
|
+
).run(...bucket);
|
|
7353
|
+
}
|
|
6106
7354
|
return new Set(names);
|
|
6107
7355
|
}
|
|
6108
7356
|
resolveRefsForNamesUnsafe(names) {
|
|
@@ -6136,6 +7384,14 @@ var IndexStore = class _IndexStore {
|
|
|
6136
7384
|
if (this.ftsAvailable) {
|
|
6137
7385
|
ftsRows.push({ id, text: buildIndexableText(s.name, s.signature, s.docComment) });
|
|
6138
7386
|
}
|
|
7387
|
+
if (this.vectorsAvailable) {
|
|
7388
|
+
vectorRows.push({
|
|
7389
|
+
id,
|
|
7390
|
+
vector: encodeVector(
|
|
7391
|
+
embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
|
|
7392
|
+
)
|
|
7393
|
+
});
|
|
7394
|
+
}
|
|
6139
7395
|
result.push({ ...s, id });
|
|
6140
7396
|
}
|
|
6141
7397
|
bulkInsertSymbolsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, bulk);
|
|
@@ -6168,15 +7424,15 @@ var IndexStore = class _IndexStore {
|
|
|
6168
7424
|
const affectedNames = this.invalidateIncomingRefsForFiles([file]);
|
|
6169
7425
|
if (this.ftsAvailable) {
|
|
6170
7426
|
this.stmt(
|
|
6171
|
-
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE
|
|
7427
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6172
7428
|
).run(file);
|
|
6173
7429
|
}
|
|
6174
7430
|
if (this.vectorsAvailable) {
|
|
6175
7431
|
this.stmt(
|
|
6176
|
-
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE
|
|
7432
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6177
7433
|
).run(file);
|
|
6178
7434
|
}
|
|
6179
|
-
this.stmt("DELETE FROM symbols WHERE
|
|
7435
|
+
this.stmt("DELETE FROM symbols WHERE file = ?").run(file);
|
|
6180
7436
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
6181
7437
|
this.commitWriteTransaction(ownsTransaction);
|
|
6182
7438
|
} catch (error) {
|
|
@@ -6193,18 +7449,18 @@ var IndexStore = class _IndexStore {
|
|
|
6193
7449
|
const affectedNames = this.invalidateIncomingRefsForFiles([file]);
|
|
6194
7450
|
if (this.ftsAvailable) {
|
|
6195
7451
|
this.stmt(
|
|
6196
|
-
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE
|
|
7452
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6197
7453
|
).run(file);
|
|
6198
7454
|
}
|
|
6199
7455
|
if (this.vectorsAvailable) {
|
|
6200
7456
|
this.stmt(
|
|
6201
|
-
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE
|
|
7457
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6202
7458
|
).run(file);
|
|
6203
7459
|
}
|
|
6204
|
-
this.stmt(
|
|
6205
|
-
|
|
6206
|
-
)
|
|
6207
|
-
this.stmt("DELETE FROM symbols WHERE
|
|
7460
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
|
|
7461
|
+
file
|
|
7462
|
+
);
|
|
7463
|
+
this.stmt("DELETE FROM symbols WHERE file = ?").run(file);
|
|
6208
7464
|
this.stmt("DELETE FROM files WHERE file = ?").run(file);
|
|
6209
7465
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
6210
7466
|
this.commitWriteTransaction(ownsTransaction);
|
|
@@ -6308,6 +7564,10 @@ var IndexStore = class _IndexStore {
|
|
|
6308
7564
|
getStats() {
|
|
6309
7565
|
return getStatsWithStatement((sql) => this.stmt(sql), this.indexDir);
|
|
6310
7566
|
}
|
|
7567
|
+
/** P2.5: minimal summary for search-response piggyback (see writer-admin). */
|
|
7568
|
+
getIndexSummary() {
|
|
7569
|
+
return getIndexSummaryWithStatement((sql) => this.stmt(sql));
|
|
7570
|
+
}
|
|
6311
7571
|
setLastIndexed(ts2) {
|
|
6312
7572
|
this.runWithRetry(() => {
|
|
6313
7573
|
this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES('last_indexed', ?)").run(
|
|
@@ -6409,18 +7669,18 @@ var IndexStore = class _IndexStore {
|
|
|
6409
7669
|
const affectedNames = this.invalidateIncomingRefsForFiles([meta.file]);
|
|
6410
7670
|
if (this.ftsAvailable) {
|
|
6411
7671
|
this.stmt(
|
|
6412
|
-
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE
|
|
7672
|
+
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6413
7673
|
).run(meta.file);
|
|
6414
7674
|
}
|
|
6415
7675
|
if (this.vectorsAvailable) {
|
|
6416
7676
|
this.stmt(
|
|
6417
|
-
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE
|
|
7677
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
|
|
6418
7678
|
).run(meta.file);
|
|
6419
7679
|
}
|
|
6420
|
-
this.stmt(
|
|
6421
|
-
|
|
6422
|
-
)
|
|
6423
|
-
this.stmt("DELETE FROM symbols WHERE
|
|
7680
|
+
this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
|
|
7681
|
+
meta.file
|
|
7682
|
+
);
|
|
7683
|
+
this.stmt("DELETE FROM symbols WHERE file = ?").run(meta.file);
|
|
6424
7684
|
this.stmt(
|
|
6425
7685
|
`INSERT INTO files(file, lang, mtime_ms, content_hash, symbol_count, last_indexed)
|
|
6426
7686
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
@@ -6452,6 +7712,27 @@ var IndexStore = class _IndexStore {
|
|
|
6452
7712
|
} catch {
|
|
6453
7713
|
}
|
|
6454
7714
|
}
|
|
7715
|
+
/**
|
|
7716
|
+
* P4.14: best-effort WAL checkpoint for idle-time maintenance.
|
|
7717
|
+
*
|
|
7718
|
+
* `wal_autocheckpoint` is PASSIVE and only attempts work after a COMMIT —
|
|
7719
|
+
* once writes stop, nothing fires again, so the WAL keeps whatever frames
|
|
7720
|
+
* the last burst left. This probes with PASSIVE first (never blocks; busy=1
|
|
7721
|
+
* means readers still hold WAL snapshots) and only issues the TRUNCATE —
|
|
7722
|
+
* which resets index.db-wal to zero bytes — when the checkpointer can
|
|
7723
|
+
* proceed immediately. Callers run this on the daemon's single thread, so
|
|
7724
|
+
* never wait on readers here: busy means "retry at the next idle window".
|
|
7725
|
+
*/
|
|
7726
|
+
checkpointWal() {
|
|
7727
|
+
try {
|
|
7728
|
+
const probe = this.db.prepare("PRAGMA wal_checkpoint(PASSIVE)").get();
|
|
7729
|
+
if (Number(probe?.busy ?? 1) !== 0) return false;
|
|
7730
|
+
const done = this.db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
|
|
7731
|
+
return Number(done?.busy ?? 1) === 0;
|
|
7732
|
+
} catch {
|
|
7733
|
+
return false;
|
|
7734
|
+
}
|
|
7735
|
+
}
|
|
6455
7736
|
compactIfNeeded(options = {}) {
|
|
6456
7737
|
const minBytes = options.minBytes ?? 256 * 1024 * 1024;
|
|
6457
7738
|
const minFreeRatio = options.minFreeRatio ?? 0.35;
|
|
@@ -6537,7 +7818,9 @@ function resolveParallelBatch() {
|
|
|
6537
7818
|
return indexParallelBatchSize(availableParallelism());
|
|
6538
7819
|
}
|
|
6539
7820
|
function shouldUseParserWorkerPool(candidateFileCount, parseBatchCount) {
|
|
6540
|
-
|
|
7821
|
+
const threshold = resolveWorkerPoolThreshold();
|
|
7822
|
+
if (threshold === 0) return false;
|
|
7823
|
+
return !isFrugalPerf() && candidateFileCount >= threshold && parseBatchCount > 1;
|
|
6541
7824
|
}
|
|
6542
7825
|
function yieldEventLoop() {
|
|
6543
7826
|
return new Promise((resolve5) => setImmediate(resolve5));
|
|
@@ -6560,15 +7843,15 @@ var IndexSourceChangedError = class extends Error {
|
|
|
6560
7843
|
name = "IndexSourceChangedError";
|
|
6561
7844
|
};
|
|
6562
7845
|
function isWithinProject(projectRoot, file) {
|
|
6563
|
-
const rel =
|
|
6564
|
-
return rel !== "" && !rel.startsWith(`..${
|
|
7846
|
+
const rel = path14.relative(projectRoot, file);
|
|
7847
|
+
return rel !== "" && !rel.startsWith(`..${path14.sep}`) && rel !== ".." && !path14.isAbsolute(rel);
|
|
6565
7848
|
}
|
|
6566
7849
|
function isMissingPathError(err) {
|
|
6567
7850
|
const code = err?.code;
|
|
6568
7851
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
6569
7852
|
}
|
|
6570
7853
|
function normalizeComparablePath(value) {
|
|
6571
|
-
const resolved =
|
|
7854
|
+
const resolved = path14.resolve(value);
|
|
6572
7855
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
6573
7856
|
}
|
|
6574
7857
|
function gitOutput(projectRoot, args) {
|
|
@@ -6614,24 +7897,24 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
6614
7897
|
const record = statusRecords[i];
|
|
6615
7898
|
if (!record) continue;
|
|
6616
7899
|
const status = record.slice(0, 2);
|
|
6617
|
-
const changedPath =
|
|
7900
|
+
const changedPath = path14.resolve(projectRoot, record.slice(3));
|
|
6618
7901
|
dirty.add(changedPath);
|
|
6619
7902
|
if (status.includes("D")) deleted.add(changedPath);
|
|
6620
7903
|
if (status.includes("R") || status.includes("C")) {
|
|
6621
7904
|
const source = statusRecords[++i];
|
|
6622
|
-
if (source) dirty.add(
|
|
7905
|
+
if (source) dirty.add(path14.resolve(projectRoot, source));
|
|
6623
7906
|
}
|
|
6624
7907
|
}
|
|
6625
7908
|
const files = [];
|
|
6626
7909
|
for (const relative4 of output.toString("utf8").split("\0")) {
|
|
6627
7910
|
if (!relative4) continue;
|
|
6628
7911
|
const portable = relative4.replace(/\\/g, "/");
|
|
6629
|
-
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(
|
|
7912
|
+
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path14.posix.basename(portable))) {
|
|
6630
7913
|
continue;
|
|
6631
7914
|
}
|
|
6632
|
-
const full =
|
|
7915
|
+
const full = path14.resolve(projectRoot, relative4);
|
|
6633
7916
|
if (deleted.has(full)) continue;
|
|
6634
|
-
const ext =
|
|
7917
|
+
const ext = path14.extname(relative4).toLowerCase();
|
|
6635
7918
|
if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
|
|
6636
7919
|
}
|
|
6637
7920
|
const snapshot = createHash2("sha256").update(stagedOutput).update("\0").update(statusOutput);
|
|
@@ -6639,7 +7922,7 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
6639
7922
|
for (const dirtyFile of [...dirty].sort()) {
|
|
6640
7923
|
if (!indexedFiles.has(dirtyFile) || deleted.has(dirtyFile)) continue;
|
|
6641
7924
|
snapshot.update("\0").update(dirtyFile).update("\0");
|
|
6642
|
-
snapshot.update(xxhash64String(await
|
|
7925
|
+
snapshot.update(xxhash64String(await fs10.readFile(dirtyFile, "utf8")));
|
|
6643
7926
|
}
|
|
6644
7927
|
return {
|
|
6645
7928
|
files,
|
|
@@ -6675,7 +7958,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
6675
7958
|
}
|
|
6676
7959
|
let entries;
|
|
6677
7960
|
try {
|
|
6678
|
-
entries = await
|
|
7961
|
+
entries = await fs10.readdir(dir, { withFileTypes: true });
|
|
6679
7962
|
} catch (err) {
|
|
6680
7963
|
complete = false;
|
|
6681
7964
|
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -6684,14 +7967,14 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
6684
7967
|
dirCount++;
|
|
6685
7968
|
for (const e of entries) {
|
|
6686
7969
|
if (ignoreSet.has(e.name)) continue;
|
|
6687
|
-
const full =
|
|
6688
|
-
const rel =
|
|
7970
|
+
const full = path14.join(dir, e.name);
|
|
7971
|
+
const rel = path14.relative(projectRoot, full).replace(/\\/g, "/");
|
|
6689
7972
|
if (e.isDirectory()) {
|
|
6690
7973
|
if (isGitIgnored(rel, true)) continue;
|
|
6691
7974
|
await walk(full);
|
|
6692
7975
|
} else if (e.isFile()) {
|
|
6693
7976
|
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
6694
|
-
const ext =
|
|
7977
|
+
const ext = path14.extname(e.name).toLowerCase();
|
|
6695
7978
|
if (indexableExts.has(ext) || detectLang(full) !== null) {
|
|
6696
7979
|
results.push(full);
|
|
6697
7980
|
}
|
|
@@ -6777,10 +8060,10 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6777
8060
|
let trustedUnchanged;
|
|
6778
8061
|
let discoverySnapshotKey;
|
|
6779
8062
|
if (opts.files && opts.files.length > 0) {
|
|
6780
|
-
files = opts.files.map((f) =>
|
|
8063
|
+
files = opts.files.map((f) => path14.resolve(projectRoot, f)).filter((f) => {
|
|
6781
8064
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
6782
|
-
const rel =
|
|
6783
|
-
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(
|
|
8065
|
+
const rel = path14.relative(projectRoot, f).replace(/\\/g, "/");
|
|
8066
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path14.basename(f)) && !isGitIgnored(rel, false);
|
|
6784
8067
|
});
|
|
6785
8068
|
} else {
|
|
6786
8069
|
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
@@ -6813,7 +8096,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6813
8096
|
if (!meta || !trustedUnchanged.has(file)) return true;
|
|
6814
8097
|
langStats[meta.lang] = (langStats[meta.lang] ?? 0) + meta.symbolCount;
|
|
6815
8098
|
symbolsIndexed += meta.symbolCount;
|
|
6816
|
-
filesIndexed++;
|
|
6817
8099
|
filesSkipped++;
|
|
6818
8100
|
filesPreSkipped++;
|
|
6819
8101
|
return false;
|
|
@@ -6842,7 +8124,7 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6842
8124
|
async (file) => {
|
|
6843
8125
|
let stat3;
|
|
6844
8126
|
try {
|
|
6845
|
-
stat3 = await
|
|
8127
|
+
stat3 = await fs10.stat(file, statOpts);
|
|
6846
8128
|
} catch (e) {
|
|
6847
8129
|
if (isAbortError(e)) throw e;
|
|
6848
8130
|
return {
|
|
@@ -6869,7 +8151,7 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6869
8151
|
const meta = existingMeta.get(file);
|
|
6870
8152
|
let content;
|
|
6871
8153
|
try {
|
|
6872
|
-
content = await
|
|
8154
|
+
content = await fs10.readFile(file, { encoding: "utf8", signal });
|
|
6873
8155
|
} catch (e) {
|
|
6874
8156
|
if (isAbortError(e)) throw e;
|
|
6875
8157
|
return {
|
|
@@ -6934,22 +8216,19 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6934
8216
|
}
|
|
6935
8217
|
}
|
|
6936
8218
|
if (!pool) {
|
|
6937
|
-
await
|
|
6938
|
-
toParse.map(
|
|
6939
|
-
try {
|
|
6940
|
-
const parsed = await parseFileContent(item.file, item.content, item.lang);
|
|
6941
|
-
const settled = statReadParse[item.index];
|
|
6942
|
-
if (settled.status === "fulfilled") {
|
|
6943
|
-
settled.value.parsed = parsed;
|
|
6944
|
-
}
|
|
6945
|
-
} catch (e) {
|
|
6946
|
-
const settled = statReadParse[item.index];
|
|
6947
|
-
if (settled.status === "fulfilled") {
|
|
6948
|
-
settled.value.error = `parse error: ${e instanceof Error ? e.message : String(e)}`;
|
|
6949
|
-
}
|
|
6950
|
-
}
|
|
6951
|
-
})
|
|
8219
|
+
const parsedAll = await parseFilesContent(
|
|
8220
|
+
toParse.map((p) => ({ file: p.file, content: p.content, lang: p.lang }))
|
|
6952
8221
|
);
|
|
8222
|
+
for (let pi2 = 0; pi2 < parsedAll.length && pi2 < toParse.length; pi2++) {
|
|
8223
|
+
const settled = statReadParse[toParse[pi2].index];
|
|
8224
|
+
if (settled.status !== "fulfilled") continue;
|
|
8225
|
+
const slot = parsedAll[pi2];
|
|
8226
|
+
if (slot.result) {
|
|
8227
|
+
settled.value.parsed = slot.result;
|
|
8228
|
+
} else {
|
|
8229
|
+
settled.value.error = `parse error: ${slot.error ?? `no result for ${toParse[pi2].file}`}`;
|
|
8230
|
+
}
|
|
8231
|
+
}
|
|
6953
8232
|
}
|
|
6954
8233
|
}
|
|
6955
8234
|
const batchEntries = [];
|
|
@@ -6975,7 +8254,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
6975
8254
|
if (result.skippedMeta) {
|
|
6976
8255
|
langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
|
|
6977
8256
|
symbolsIndexed += result.skippedMeta.symbolCount;
|
|
6978
|
-
filesIndexed++;
|
|
6979
8257
|
filesSkipped++;
|
|
6980
8258
|
const stored = existingMeta.get(file);
|
|
6981
8259
|
if (stored && stored.mtimeMs !== result.skippedMeta.mtimeMs) {
|
|
@@ -7000,7 +8278,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7000
8278
|
lastIndexed: Date.now(),
|
|
7001
8279
|
contentHash: result.contentHash ?? ""
|
|
7002
8280
|
});
|
|
7003
|
-
filesIndexed++;
|
|
7004
8281
|
filesEmpty++;
|
|
7005
8282
|
}
|
|
7006
8283
|
continue;
|
|
@@ -7014,7 +8291,6 @@ async function runIndexerAtomic(store, opts) {
|
|
|
7014
8291
|
lastIndexed: Date.now(),
|
|
7015
8292
|
contentHash: result.contentHash ?? ""
|
|
7016
8293
|
});
|
|
7017
|
-
filesIndexed++;
|
|
7018
8294
|
filesEmpty++;
|
|
7019
8295
|
continue;
|
|
7020
8296
|
}
|
|
@@ -7144,7 +8420,7 @@ async function indexService(args, hooks = {}) {
|
|
|
7144
8420
|
function searchService(args) {
|
|
7145
8421
|
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
7146
8422
|
try {
|
|
7147
|
-
|
|
8423
|
+
const result = store.searchRanked(
|
|
7148
8424
|
args.query,
|
|
7149
8425
|
{
|
|
7150
8426
|
kind: args.kind,
|
|
@@ -7154,6 +8430,10 @@ function searchService(args) {
|
|
|
7154
8430
|
},
|
|
7155
8431
|
args.limit
|
|
7156
8432
|
);
|
|
8433
|
+
if (result.total === 0) {
|
|
8434
|
+
return { ...result, indexSummary: store.getIndexSummary() };
|
|
8435
|
+
}
|
|
8436
|
+
return result;
|
|
7157
8437
|
} finally {
|
|
7158
8438
|
indexStorePool.release(store);
|
|
7159
8439
|
}
|
|
@@ -7217,34 +8497,71 @@ function outgoingCallsService(args) {
|
|
|
7217
8497
|
init_languages();
|
|
7218
8498
|
|
|
7219
8499
|
// src/codebase-index/project-server-client.ts
|
|
7220
|
-
import { spawn as
|
|
7221
|
-
import * as
|
|
8500
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
8501
|
+
import * as fs13 from "node:fs";
|
|
7222
8502
|
import * as net from "node:net";
|
|
7223
|
-
import { StringDecoder } from "node:string_decoder";
|
|
7224
8503
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
7225
8504
|
|
|
7226
8505
|
// src/codebase-index/binary-frame.ts
|
|
7227
8506
|
import { decode, encode } from "@msgpack/msgpack";
|
|
7228
8507
|
var BINARY_FRAME_MAGIC = 87;
|
|
8508
|
+
var MAX_BINARY_FRAME_BYTES = 256 * 1024 * 1024;
|
|
8509
|
+
var MAX_INBOUND_BINARY_FRAME_BYTES = 64 * 1024 * 1024;
|
|
7229
8510
|
function isBinaryFrame(firstByte) {
|
|
7230
8511
|
return firstByte === BINARY_FRAME_MAGIC;
|
|
7231
8512
|
}
|
|
7232
8513
|
function encodeBinaryFrame(message) {
|
|
7233
|
-
const payload = encode(message);
|
|
8514
|
+
const payload = encode(normalizeUndefined(message));
|
|
7234
8515
|
const header = Buffer.allocUnsafe(5);
|
|
7235
8516
|
header[0] = BINARY_FRAME_MAGIC;
|
|
7236
8517
|
header.writeUInt32BE(payload.length, 1);
|
|
7237
8518
|
return Buffer.concat([header, payload], 5 + payload.length);
|
|
7238
8519
|
}
|
|
8520
|
+
function normalizeUndefined(value) {
|
|
8521
|
+
if (value instanceof Date) {
|
|
8522
|
+
const time = value.getTime();
|
|
8523
|
+
return Number.isNaN(time) ? null : value.toISOString();
|
|
8524
|
+
}
|
|
8525
|
+
if (value instanceof Map) return normalizeUndefined(Object.fromEntries(value));
|
|
8526
|
+
if (value instanceof Set) return normalizeUndefined([...value]);
|
|
8527
|
+
if (value instanceof Error) {
|
|
8528
|
+
return normalizeUndefined({ name: value.name, message: value.message, stack: value.stack });
|
|
8529
|
+
}
|
|
8530
|
+
if (value instanceof RegExp) return String(value);
|
|
8531
|
+
if (value instanceof URL) return value.toJSON();
|
|
8532
|
+
if (Buffer.isBuffer(value)) return { type: "Buffer", data: [...value] };
|
|
8533
|
+
if (Array.isArray(value)) return value.map((entry) => normalizeUndefined(entry));
|
|
8534
|
+
if (!isPlainObject(value)) return value;
|
|
8535
|
+
const out = {};
|
|
8536
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
8537
|
+
if (entry === void 0) continue;
|
|
8538
|
+
out[key] = normalizeUndefined(entry);
|
|
8539
|
+
}
|
|
8540
|
+
return out;
|
|
8541
|
+
}
|
|
8542
|
+
function isPlainObject(value) {
|
|
8543
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
8544
|
+
const proto = Object.getPrototypeOf(value);
|
|
8545
|
+
return proto === Object.prototype || proto === null;
|
|
8546
|
+
}
|
|
7239
8547
|
function decodeBinaryFrame(payload) {
|
|
7240
8548
|
return decode(payload);
|
|
7241
8549
|
}
|
|
8550
|
+
function encodeJsonFrame(message) {
|
|
8551
|
+
return `${JSON.stringify(normalizeUndefined(message))}
|
|
8552
|
+
`;
|
|
8553
|
+
}
|
|
8554
|
+
|
|
8555
|
+
// src/codebase-index/project-server-client-state.ts
|
|
8556
|
+
import * as fs12 from "node:fs";
|
|
8557
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
8558
|
+
import { checkUnixSocketPath } from "@wrongstack/core/utils";
|
|
7242
8559
|
|
|
7243
8560
|
// src/codebase-index/project-server-endpoint.ts
|
|
7244
8561
|
import { createHash as createHash3 } from "node:crypto";
|
|
7245
|
-
import * as
|
|
7246
|
-
import * as
|
|
7247
|
-
import * as
|
|
8562
|
+
import * as fs11 from "node:fs";
|
|
8563
|
+
import * as os4 from "node:os";
|
|
8564
|
+
import * as path15 from "node:path";
|
|
7248
8565
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7249
8566
|
var PROJECT_INDEX_SERVER_PROTOCOL_VERSION = 1;
|
|
7250
8567
|
var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
|
|
@@ -7253,21 +8570,21 @@ var buildIdCache;
|
|
|
7253
8570
|
function projectIndexServerBuildId(entrypoint) {
|
|
7254
8571
|
const href = entrypoint instanceof URL ? entrypoint.href : entrypoint;
|
|
7255
8572
|
const cleanHref = href.split(/[?#]/, 1)[0] ?? href;
|
|
7256
|
-
const file = cleanHref.startsWith("file:") ? fileURLToPath3(cleanHref) :
|
|
8573
|
+
const file = cleanHref.startsWith("file:") ? fileURLToPath3(cleanHref) : path15.resolve(cleanHref);
|
|
7257
8574
|
try {
|
|
7258
|
-
const stat3 =
|
|
8575
|
+
const stat3 = fs11.statSync(file);
|
|
7259
8576
|
if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat3.mtimeMs && buildIdCache.size === stat3.size) {
|
|
7260
8577
|
return buildIdCache.buildId;
|
|
7261
8578
|
}
|
|
7262
|
-
const buildId = createHash3("sha256").update(
|
|
8579
|
+
const buildId = createHash3("sha256").update(fs11.readFileSync(file)).digest("hex").slice(0, 24);
|
|
7263
8580
|
buildIdCache = { file, mtimeMs: stat3.mtimeMs, size: stat3.size, buildId };
|
|
7264
8581
|
return buildId;
|
|
7265
8582
|
} catch {
|
|
7266
|
-
return `unreadable:${
|
|
8583
|
+
return `unreadable:${path15.basename(file)}`;
|
|
7267
8584
|
}
|
|
7268
8585
|
}
|
|
7269
8586
|
function normalizeLocalPath(value) {
|
|
7270
|
-
const resolved =
|
|
8587
|
+
const resolved = path15.resolve(value);
|
|
7271
8588
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
7272
8589
|
}
|
|
7273
8590
|
function projectIndexServerKey(projectRoot, indexDir) {
|
|
@@ -7279,26 +8596,16 @@ function projectIndexServerEndpoint(projectRoot, indexDir) {
|
|
|
7279
8596
|
if (process.platform === "win32") {
|
|
7280
8597
|
return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
|
|
7281
8598
|
}
|
|
7282
|
-
return
|
|
8599
|
+
return path15.join(os4.tmpdir(), PROJECT_INDEX_SERVER_SOCKET_DIR, `${key}.sock`);
|
|
7283
8600
|
}
|
|
7284
8601
|
function projectIndexServerMetadataPath(projectRoot, indexDir) {
|
|
7285
|
-
return
|
|
7286
|
-
|
|
8602
|
+
return path15.join(
|
|
8603
|
+
path15.resolve(resolveIndexDir(projectRoot, indexDir)),
|
|
7287
8604
|
PROJECT_INDEX_SERVER_METADATA_FILE
|
|
7288
8605
|
);
|
|
7289
8606
|
}
|
|
7290
8607
|
|
|
7291
|
-
// src/codebase-index/project-server-protocol.ts
|
|
7292
|
-
var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
|
|
7293
|
-
function encodeProjectServerMessage(message) {
|
|
7294
|
-
return `${JSON.stringify(message)}
|
|
7295
|
-
`;
|
|
7296
|
-
}
|
|
7297
|
-
|
|
7298
8608
|
// src/codebase-index/project-server-client-state.ts
|
|
7299
|
-
import * as fs11 from "node:fs";
|
|
7300
|
-
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
7301
|
-
import { checkUnixSocketPath } from "@wrongstack/core/utils";
|
|
7302
8609
|
var CONNECT_ATTEMPT_TIMEOUT_MS = 750;
|
|
7303
8610
|
var SERVER_START_TIMEOUT_MS = 1e4;
|
|
7304
8611
|
var SERVER_CONTROL_TIMEOUT_MS = 5e3;
|
|
@@ -7329,7 +8636,7 @@ function resolveProjectIndexDaemonAvailability(projectRoot, indexDir) {
|
|
|
7329
8636
|
for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
|
|
7330
8637
|
try {
|
|
7331
8638
|
const url = new URL(rel, import.meta.url);
|
|
7332
|
-
if (url.protocol === "file:" &&
|
|
8639
|
+
if (url.protocol === "file:" && fs12.existsSync(fileURLToPath4(url))) {
|
|
7333
8640
|
builtUrl = url;
|
|
7334
8641
|
break;
|
|
7335
8642
|
}
|
|
@@ -7417,6 +8724,12 @@ function cancellationError(signal) {
|
|
|
7417
8724
|
return signal.reason instanceof Error ? signal.reason : new Error("Indexing cancelled");
|
|
7418
8725
|
}
|
|
7419
8726
|
|
|
8727
|
+
// src/codebase-index/project-server-protocol.ts
|
|
8728
|
+
var PROJECT_INDEX_SERVER_MAX_FRAME_CHARS = 64 * 1024 * 1024;
|
|
8729
|
+
function encodeProjectServerMessage(message) {
|
|
8730
|
+
return encodeJsonFrame(message);
|
|
8731
|
+
}
|
|
8732
|
+
|
|
7420
8733
|
// src/codebase-index/project-server-client.ts
|
|
7421
8734
|
var ProjectServerConnection = class {
|
|
7422
8735
|
constructor(projectRoot, indexDir, endpoint) {
|
|
@@ -7429,11 +8742,13 @@ var ProjectServerConnection = class {
|
|
|
7429
8742
|
indexDir;
|
|
7430
8743
|
endpoint;
|
|
7431
8744
|
socket = null;
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
8745
|
+
/**
|
|
8746
|
+
* Raw inbound bytes for the unified per-frame reader. Frames are sniffed
|
|
8747
|
+
* individually — JSON text (newline-terminated) or binary (magic 0x57) —
|
|
8748
|
+
* instead of latching a read mode, so a JSON broadcast between binary
|
|
8749
|
+
* frames cannot desynchronize the reader.
|
|
8750
|
+
*/
|
|
8751
|
+
readBuffer = Buffer.alloc(0);
|
|
7437
8752
|
/** P6: true once the server advertises binary support and client accepts. */
|
|
7438
8753
|
useBinary = false;
|
|
7439
8754
|
info = null;
|
|
@@ -7575,7 +8890,7 @@ var ProjectServerConnection = class {
|
|
|
7575
8890
|
this.activity = null;
|
|
7576
8891
|
this.health = null;
|
|
7577
8892
|
this.useBinary = false;
|
|
7578
|
-
this.
|
|
8893
|
+
this.readBuffer = Buffer.alloc(0);
|
|
7579
8894
|
this.connectReject?.(new Error("codebase-index client disconnected"));
|
|
7580
8895
|
this.connectResolve = null;
|
|
7581
8896
|
this.connectReject = null;
|
|
@@ -7596,7 +8911,7 @@ var ProjectServerConnection = class {
|
|
|
7596
8911
|
currentAuthToken() {
|
|
7597
8912
|
if (this.authToken === void 0) {
|
|
7598
8913
|
try {
|
|
7599
|
-
const raw =
|
|
8914
|
+
const raw = fs13.readFileSync(
|
|
7600
8915
|
projectIndexServerMetadataPath(this.projectRoot, this.indexDir),
|
|
7601
8916
|
"utf8"
|
|
7602
8917
|
);
|
|
@@ -7703,10 +9018,8 @@ var ProjectServerConnection = class {
|
|
|
7703
9018
|
this.info = null;
|
|
7704
9019
|
this.activity = null;
|
|
7705
9020
|
this.health = null;
|
|
7706
|
-
this.
|
|
7707
|
-
this.binaryBuffer = [];
|
|
9021
|
+
this.readBuffer = Buffer.alloc(0);
|
|
7708
9022
|
this.useBinary = false;
|
|
7709
|
-
this.textDecoder = null;
|
|
7710
9023
|
return new Promise((resolve5, reject) => {
|
|
7711
9024
|
const socket = net.createConnection(this.endpoint);
|
|
7712
9025
|
this.socket = socket;
|
|
@@ -7736,18 +9049,47 @@ var ProjectServerConnection = class {
|
|
|
7736
9049
|
socket.on("close", () => this.onClose(socket));
|
|
7737
9050
|
});
|
|
7738
9051
|
}
|
|
9052
|
+
/**
|
|
9053
|
+
* Unified per-frame reader. Each frame is sniffed by its first byte:
|
|
9054
|
+
* `0x57` ('W') → length-prefixed MessagePack binary, anything else →
|
|
9055
|
+
* newline-delimited JSON text. Sniffing per frame (instead of latching a
|
|
9056
|
+
* mode) is what makes mixed streams work: the server may interleave a JSON
|
|
9057
|
+
* `index-state` broadcast between binary responses, and an old JSON-only
|
|
9058
|
+
* server stays readable while `useBinary` is armed.
|
|
9059
|
+
*
|
|
9060
|
+
* Multibyte UTF-8 in JSON frames is safe: raw `0x0a` only occurs as the
|
|
9061
|
+
* JSON delimiter (inside JSON strings `\n` is escaped), so a complete line
|
|
9062
|
+
* is always complete UTF-8.
|
|
9063
|
+
*/
|
|
7739
9064
|
onData(socket, chunk) {
|
|
7740
9065
|
if (socket !== this.socket) return;
|
|
7741
|
-
|
|
7742
|
-
this.onBinaryData(socket, chunk);
|
|
7743
|
-
return;
|
|
7744
|
-
}
|
|
7745
|
-
if (!this.textDecoder) this.textDecoder = new StringDecoder("utf8");
|
|
7746
|
-
this.buffer += this.textDecoder.write(chunk);
|
|
9066
|
+
this.readBuffer = this.readBuffer.length === 0 ? chunk : Buffer.concat([this.readBuffer, chunk]);
|
|
7747
9067
|
while (true) {
|
|
7748
|
-
|
|
9068
|
+
if (this.readBuffer.length === 0) return;
|
|
9069
|
+
if (this.useBinary && isBinaryFrame(this.readBuffer[0])) {
|
|
9070
|
+
if (this.readBuffer.length < 5) return;
|
|
9071
|
+
const frameLen = this.readBuffer.readUInt32BE(1);
|
|
9072
|
+
if (frameLen > MAX_BINARY_FRAME_BYTES) {
|
|
9073
|
+
socket.destroy();
|
|
9074
|
+
this.transition("offline", { error: "binary frame length exceeds the IPC limit" });
|
|
9075
|
+
return;
|
|
9076
|
+
}
|
|
9077
|
+
if (this.readBuffer.length < 5 + frameLen) return;
|
|
9078
|
+
const payload = this.readBuffer.subarray(5, 5 + frameLen);
|
|
9079
|
+
this.readBuffer = this.readBuffer.subarray(5 + frameLen);
|
|
9080
|
+
let message2;
|
|
9081
|
+
try {
|
|
9082
|
+
message2 = decodeBinaryFrame(payload);
|
|
9083
|
+
} catch {
|
|
9084
|
+
socket.destroy(new Error("invalid binary codebase-index server response"));
|
|
9085
|
+
return;
|
|
9086
|
+
}
|
|
9087
|
+
this.onMessage(message2);
|
|
9088
|
+
continue;
|
|
9089
|
+
}
|
|
9090
|
+
const newline = this.readBuffer.indexOf(10);
|
|
7749
9091
|
if (newline < 0) {
|
|
7750
|
-
if (this.
|
|
9092
|
+
if (this.readBuffer.length > PROJECT_INDEX_SERVER_MAX_FRAME_CHARS) {
|
|
7751
9093
|
socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
|
|
7752
9094
|
}
|
|
7753
9095
|
return;
|
|
@@ -7756,8 +9098,8 @@ var ProjectServerConnection = class {
|
|
|
7756
9098
|
socket.destroy(new Error("codebase-index server response exceeds the IPC limit"));
|
|
7757
9099
|
return;
|
|
7758
9100
|
}
|
|
7759
|
-
const line = this.
|
|
7760
|
-
this.
|
|
9101
|
+
const line = this.readBuffer.subarray(0, newline).toString("utf8");
|
|
9102
|
+
this.readBuffer = this.readBuffer.subarray(newline + 1);
|
|
7761
9103
|
if (!line) continue;
|
|
7762
9104
|
let message;
|
|
7763
9105
|
try {
|
|
@@ -7769,44 +9111,6 @@ var ProjectServerConnection = class {
|
|
|
7769
9111
|
this.onMessage(message);
|
|
7770
9112
|
}
|
|
7771
9113
|
}
|
|
7772
|
-
/**
|
|
7773
|
-
* P6: Parse binary frames from the raw buffer.
|
|
7774
|
-
*
|
|
7775
|
-
* Each frame is: [1 byte magic 0x57] [4 bytes uint32 BE length] [payload].
|
|
7776
|
-
* The magic byte distinguishes binary from JSON — a JSON frame's first byte
|
|
7777
|
-
* is `{` (0x7B), so there is no ambiguity even in a mixed-mode buffer.
|
|
7778
|
-
*/
|
|
7779
|
-
onBinaryData(socket, chunk) {
|
|
7780
|
-
this.binaryBuffer.push(chunk);
|
|
7781
|
-
const all = Buffer.concat(this.binaryBuffer);
|
|
7782
|
-
let offset = 0;
|
|
7783
|
-
while (offset + 5 <= all.length) {
|
|
7784
|
-
if (!isBinaryFrame(all[offset])) {
|
|
7785
|
-
this.useBinary = false;
|
|
7786
|
-
this.buffer += all.subarray(offset).toString("utf8");
|
|
7787
|
-
this.binaryBuffer = [];
|
|
7788
|
-
return;
|
|
7789
|
-
}
|
|
7790
|
-
const frameLen = all.readUInt32BE(offset + 1);
|
|
7791
|
-
if (frameLen > 256 * 1024 * 1024) {
|
|
7792
|
-
socket.destroy();
|
|
7793
|
-
this.transition("offline", { error: "binary frame length exceeds 256MB limit" });
|
|
7794
|
-
return;
|
|
7795
|
-
}
|
|
7796
|
-
const totalLen = 5 + frameLen;
|
|
7797
|
-
if (offset + totalLen > all.length) break;
|
|
7798
|
-
const payload = all.subarray(offset + 5, offset + 5 + frameLen);
|
|
7799
|
-
try {
|
|
7800
|
-
const message = decodeBinaryFrame(payload);
|
|
7801
|
-
this.onMessage(message);
|
|
7802
|
-
} catch {
|
|
7803
|
-
socket.destroy(new Error("invalid binary codebase-index server response"));
|
|
7804
|
-
return;
|
|
7805
|
-
}
|
|
7806
|
-
offset += totalLen;
|
|
7807
|
-
}
|
|
7808
|
-
this.binaryBuffer = offset < all.length ? [all.subarray(offset)] : [];
|
|
7809
|
-
}
|
|
7810
9114
|
onMessage(message) {
|
|
7811
9115
|
if (message.type === "hello") {
|
|
7812
9116
|
if (message.protocolVersion !== PROJECT_INDEX_SERVER_PROTOCOL_VERSION) {
|
|
@@ -7826,7 +9130,7 @@ var ProjectServerConnection = class {
|
|
|
7826
9130
|
}
|
|
7827
9131
|
this.info = message;
|
|
7828
9132
|
this.markResponsive();
|
|
7829
|
-
if (message.binarySupported) this.useBinary = true;
|
|
9133
|
+
if (message.binarySupported && binaryFramingEnabled()) this.useBinary = true;
|
|
7830
9134
|
this.transition("connected", { pid: message.pid });
|
|
7831
9135
|
ensureHeartbeatLoop();
|
|
7832
9136
|
this.connectResolve?.();
|
|
@@ -7912,13 +9216,13 @@ var ProjectServerConnection = class {
|
|
|
7912
9216
|
if (!url) throw new Error("built codebase-index project server is unavailable");
|
|
7913
9217
|
if (process.platform !== "win32") {
|
|
7914
9218
|
try {
|
|
7915
|
-
|
|
9219
|
+
fs13.rmSync(this.endpoint, { force: true });
|
|
7916
9220
|
} catch {
|
|
7917
9221
|
}
|
|
7918
9222
|
}
|
|
7919
9223
|
const args = [fileURLToPath5(url), "--project-root", this.projectRoot];
|
|
7920
9224
|
if (this.indexDir) args.push("--index-dir", this.indexDir);
|
|
7921
|
-
const child =
|
|
9225
|
+
const child = spawn4(process.execPath, args, {
|
|
7922
9226
|
detached: true,
|
|
7923
9227
|
stdio: "ignore",
|
|
7924
9228
|
windowsHide: true,
|
|
@@ -7936,8 +9240,8 @@ var ProjectServerConnection = class {
|
|
|
7936
9240
|
process.kill(pid);
|
|
7937
9241
|
const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
|
|
7938
9242
|
try {
|
|
7939
|
-
const metadata = JSON.parse(
|
|
7940
|
-
if (metadata.pid === pid)
|
|
9243
|
+
const metadata = JSON.parse(fs13.readFileSync(metadataPath, "utf8"));
|
|
9244
|
+
if (metadata.pid === pid) fs13.rmSync(metadataPath, { force: true });
|
|
7941
9245
|
} catch {
|
|
7942
9246
|
}
|
|
7943
9247
|
return true;
|
|
@@ -7949,6 +9253,10 @@ var ProjectServerConnection = class {
|
|
|
7949
9253
|
var connections = /* @__PURE__ */ new Map();
|
|
7950
9254
|
var MAX_CACHED_CONNECTIONS = 8;
|
|
7951
9255
|
var heartbeatTimer;
|
|
9256
|
+
function binaryFramingEnabled() {
|
|
9257
|
+
const flag = process.env["WRONGSTACK_INDEX_BINARY"];
|
|
9258
|
+
return flag === "1" || flag === "true";
|
|
9259
|
+
}
|
|
7952
9260
|
function forgetConnection(endpoint, connection) {
|
|
7953
9261
|
if (connections.get(endpoint) === connection) connections.delete(endpoint);
|
|
7954
9262
|
connection.close();
|
|
@@ -8038,7 +9346,7 @@ function resolveWorkerUrl() {
|
|
|
8038
9346
|
for (const rel of ["./worker.js", "./codebase-index/worker.js"]) {
|
|
8039
9347
|
try {
|
|
8040
9348
|
const url = new URL(rel, import.meta.url);
|
|
8041
|
-
if (url.protocol === "file:" &&
|
|
9349
|
+
if (url.protocol === "file:" && fs14.existsSync(fileURLToPath6(url))) return url;
|
|
8042
9350
|
} catch {
|
|
8043
9351
|
}
|
|
8044
9352
|
}
|
|
@@ -8376,24 +9684,24 @@ var patchTool = {
|
|
|
8376
9684
|
} catch (err) {
|
|
8377
9685
|
return refuse(`patch refused: ${toErrorMessage2(err)}`);
|
|
8378
9686
|
}
|
|
8379
|
-
const realRoot = await
|
|
9687
|
+
const realRoot = await fs15.realpath(ctx.projectRoot).catch(() => path16.resolve(ctx.projectRoot));
|
|
8380
9688
|
const targets = extractDiffTargets(input.patch);
|
|
8381
9689
|
const resolvedTargets = [];
|
|
8382
9690
|
for (const t of targets) {
|
|
8383
9691
|
const stripped = stripPathComponents(t.raw, strip);
|
|
8384
9692
|
if (!stripped) continue;
|
|
8385
|
-
if (
|
|
9693
|
+
if (path16.isAbsolute(stripped)) {
|
|
8386
9694
|
return refuse(`patch refused: target "${t.raw}" strips to absolute path`);
|
|
8387
9695
|
}
|
|
8388
|
-
const candidate =
|
|
9696
|
+
const candidate = path16.resolve(dir, stripped);
|
|
8389
9697
|
let real;
|
|
8390
9698
|
try {
|
|
8391
9699
|
real = await resolveRealInsideRoot(candidate, ctx);
|
|
8392
9700
|
} catch (err) {
|
|
8393
9701
|
return refuse(`patch refused: target "${t.raw}" ${toErrorMessage2(err)}`);
|
|
8394
9702
|
}
|
|
8395
|
-
const rel =
|
|
8396
|
-
if (rel.startsWith("..") ||
|
|
9703
|
+
const rel = path16.relative(realRoot, real);
|
|
9704
|
+
if (rel.startsWith("..") || path16.isAbsolute(rel)) {
|
|
8397
9705
|
return refuse(`patch refused: target "${t.raw}" resolves outside project root`);
|
|
8398
9706
|
}
|
|
8399
9707
|
resolvedTargets.push({ raw: t.raw, deleted: t.deleted, abs: real });
|
|
@@ -8402,17 +9710,17 @@ var patchTool = {
|
|
|
8402
9710
|
const beforeExisted = /* @__PURE__ */ new Set();
|
|
8403
9711
|
if (!dryRun) {
|
|
8404
9712
|
for (const target of resolvedTargets) {
|
|
8405
|
-
const existed = (await
|
|
9713
|
+
const existed = (await fs15.stat(target.abs).catch(() => null))?.isFile() ?? false;
|
|
8406
9714
|
if (existed) beforeExisted.add(target.abs);
|
|
8407
9715
|
beforeContents.set(target.abs, await readTextForTracking(target.abs));
|
|
8408
9716
|
}
|
|
8409
9717
|
}
|
|
8410
|
-
const tmpDir = await
|
|
9718
|
+
const tmpDir = await fs15.mkdtemp(path16.join(os5.tmpdir(), ".wstack_patch_"));
|
|
8411
9719
|
try {
|
|
8412
|
-
await
|
|
9720
|
+
await fs15.chmod(tmpDir, 448).catch(() => {
|
|
8413
9721
|
});
|
|
8414
|
-
const patchFile =
|
|
8415
|
-
await
|
|
9722
|
+
const patchFile = path16.join(tmpDir, "in.diff");
|
|
9723
|
+
await fs15.writeFile(patchFile, input.patch, { mode: 384 });
|
|
8416
9724
|
const args = [`-p${strip}`, "--merge", ...dryRun ? ["--dry-run"] : [], "-i", patchFile];
|
|
8417
9725
|
const result = await runPatch(args, dir, opts.signal, {
|
|
8418
9726
|
patchFile,
|
|
@@ -8424,7 +9732,7 @@ var patchTool = {
|
|
|
8424
9732
|
for (const target of resolvedTargets) {
|
|
8425
9733
|
const abs = target.abs;
|
|
8426
9734
|
const before = beforeContents.get(abs) ?? null;
|
|
8427
|
-
const stat3 = await
|
|
9735
|
+
const stat3 = await fs15.stat(abs).catch(() => null);
|
|
8428
9736
|
if (!stat3?.isFile()) {
|
|
8429
9737
|
if (beforeExisted.has(abs)) {
|
|
8430
9738
|
touched.push(abs);
|
|
@@ -8457,7 +9765,7 @@ var patchTool = {
|
|
|
8457
9765
|
}
|
|
8458
9766
|
if (result.exitCode !== 0) {
|
|
8459
9767
|
if (!dryRun) {
|
|
8460
|
-
const partial = touched.length > 0 ? ` ${touched.length} file(s) were still modified on disk and have been recorded for rewind: ${touched.map((p) =>
|
|
9768
|
+
const partial = touched.length > 0 ? ` ${touched.length} file(s) were still modified on disk and have been recorded for rewind: ${touched.map((p) => path16.relative(realRoot, p) || p).join(", ")}.` : "";
|
|
8461
9769
|
return {
|
|
8462
9770
|
applied: touched.length,
|
|
8463
9771
|
rejected: 1,
|
|
@@ -8465,7 +9773,7 @@ var patchTool = {
|
|
|
8465
9773
|
// success path (which returns GNU patch's dir-relative names).
|
|
8466
9774
|
// `touched` entries are realpaths from resolveRealInsideRoot, and
|
|
8467
9775
|
// realRoot is also a realpath, so path.relative is like-for-like.
|
|
8468
|
-
files: touched.map((p) =>
|
|
9776
|
+
files: touched.map((p) => path16.relative(realRoot, p) || p),
|
|
8469
9777
|
dry_run: dryRun,
|
|
8470
9778
|
message: `patch failed: ${result.stderr || result.stdout}${partial}`
|
|
8471
9779
|
};
|
|
@@ -8481,7 +9789,7 @@ var patchTool = {
|
|
|
8481
9789
|
}
|
|
8482
9790
|
const patched = result.engine === "git" ? [
|
|
8483
9791
|
...new Set(
|
|
8484
|
-
resolvedTargets.map((target) =>
|
|
9792
|
+
resolvedTargets.map((target) => path16.relative(dir, target.abs) || target.abs)
|
|
8485
9793
|
)
|
|
8486
9794
|
] : extractPatchedFiles(result.stdout);
|
|
8487
9795
|
return {
|
|
@@ -8492,7 +9800,7 @@ var patchTool = {
|
|
|
8492
9800
|
message: result.stdout || "patch applied"
|
|
8493
9801
|
};
|
|
8494
9802
|
} finally {
|
|
8495
|
-
await
|
|
9803
|
+
await fs15.rm(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
8496
9804
|
});
|
|
8497
9805
|
}
|
|
8498
9806
|
}
|
|
@@ -8500,9 +9808,9 @@ var patchTool = {
|
|
|
8500
9808
|
var MAX_TRACKING_BYTES = 5 * 1024 * 1024;
|
|
8501
9809
|
async function readTextForTracking(absPath) {
|
|
8502
9810
|
try {
|
|
8503
|
-
const stat3 = await
|
|
9811
|
+
const stat3 = await fs15.stat(absPath);
|
|
8504
9812
|
if (!stat3.isFile() || stat3.size > MAX_TRACKING_BYTES) return null;
|
|
8505
|
-
const buf = await
|
|
9813
|
+
const buf = await fs15.readFile(absPath);
|
|
8506
9814
|
if (buf.includes(0)) return null;
|
|
8507
9815
|
return buf.toString("utf8");
|
|
8508
9816
|
} catch {
|
|
@@ -8590,7 +9898,7 @@ function runPatchProcess(command, args, cwd, signal) {
|
|
|
8590
9898
|
let stdout = "";
|
|
8591
9899
|
let stderr = "";
|
|
8592
9900
|
const env = { ...buildChildEnv(), LANG: "C", LC_ALL: "C" };
|
|
8593
|
-
const child =
|
|
9901
|
+
const child = spawn5(command, args, {
|
|
8594
9902
|
cwd,
|
|
8595
9903
|
signal,
|
|
8596
9904
|
env,
|