@wrongstack/tools 0.298.3 → 0.300.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/audit.js +6 -2
- package/dist/bash.js +20 -2
- package/dist/builtin.js +1901 -327
- package/dist/codebase-index/background-indexer.d.ts +6 -1
- package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +34 -0
- package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +34 -0
- package/dist/codebase-index/import-extractor.d.ts +39 -0
- package/dist/codebase-index/index-service.d.ts +24 -2
- package/dist/codebase-index/index.d.ts +4 -2
- package/dist/codebase-index/index.js +1779 -256
- package/dist/codebase-index/languages.d.ts +24 -0
- package/dist/codebase-index/module-resolver.d.ts +78 -0
- package/dist/codebase-index/module-roots.d.ts +81 -0
- package/dist/codebase-index/parser-output.d.ts +29 -0
- package/dist/codebase-index/project-server.js +1556 -237
- package/dist/codebase-index/rs-parser.d.ts +22 -0
- package/dist/codebase-index/schema.d.ts +47 -1
- package/dist/codebase-index/worker-protocol.d.ts +14 -0
- package/dist/codebase-index/worker.js +1545 -238
- package/dist/codebase-index/writer-graph-helpers.d.ts +17 -5
- package/dist/codebase-index/writer-graph-reader.d.ts +24 -1
- package/dist/codebase-index/writer-ref-mapper.d.ts +3 -0
- package/dist/codebase-index/writer-schema.d.ts +15 -3
- package/dist/codebase-index/writer.d.ts +97 -4
- package/dist/exec.js +39 -2
- package/dist/format.js +6 -2
- package/dist/index.js +1901 -327
- package/dist/install.js +6 -2
- package/dist/json.js +51 -2
- package/dist/languages/index.js +6 -2
- package/dist/lint.js +6 -2
- package/dist/outdated.js +6 -2
- package/dist/pack.js +1899 -327
- package/dist/process-registry.d.ts +6 -0
- package/dist/process-registry.js +6 -2
- package/dist/ps-slash.js +10 -2
- package/dist/read.js +1551 -244
- package/dist/test.js +6 -2
- package/dist/tool-tier.js +1901 -327
- package/dist/typecheck.js +6 -2
- package/package.json +3 -3
- package/dist/codebase-index/refs-extractor.d.ts +0 -11
|
@@ -14,23 +14,26 @@ var __export = (target, all) => {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
// src/codebase-index/languages.ts
|
|
17
|
-
import * as
|
|
17
|
+
import * as path from "node:path";
|
|
18
18
|
function detectLang(file) {
|
|
19
|
-
const base =
|
|
19
|
+
const base = path.basename(file);
|
|
20
20
|
const lowerBase = base.toLowerCase();
|
|
21
21
|
if (lowerBase.endsWith(".d.ts") || lowerBase.endsWith(".d.mts") || lowerBase.endsWith(".d.cts")) {
|
|
22
22
|
return "ts";
|
|
23
23
|
}
|
|
24
24
|
const special = SPECIAL_FILENAMES[lowerBase];
|
|
25
25
|
if (special) return special;
|
|
26
|
-
const ext =
|
|
26
|
+
const ext = path.extname(base).toLowerCase();
|
|
27
27
|
if (!ext) return null;
|
|
28
28
|
return EXT_TO_LANG[ext] ?? null;
|
|
29
29
|
}
|
|
30
30
|
function isIndexablePath(file) {
|
|
31
31
|
return detectLang(file) !== null;
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
function languageFamily(lang) {
|
|
34
|
+
return LANG_FAMILY[lang] ?? "other";
|
|
35
|
+
}
|
|
36
|
+
var EXT_TO_LANG, INDEXABLE_EXTENSIONS, SPECIAL_FILENAMES, LANG_FAMILY, LANG_FAMILY_ENTRIES;
|
|
34
37
|
var init_languages = __esm({
|
|
35
38
|
"src/codebase-index/languages.ts"() {
|
|
36
39
|
"use strict";
|
|
@@ -121,6 +124,52 @@ var init_languages = __esm({
|
|
|
121
124
|
procfile: "other",
|
|
122
125
|
justfile: "other"
|
|
123
126
|
};
|
|
127
|
+
LANG_FAMILY = {
|
|
128
|
+
// Single-compilation-unit family: a .vue/.svelte script block is JS/TS and
|
|
129
|
+
// imports from — and is imported by — plain .ts files.
|
|
130
|
+
ts: "js",
|
|
131
|
+
tsx: "js",
|
|
132
|
+
js: "js",
|
|
133
|
+
jsx: "js",
|
|
134
|
+
vue: "js",
|
|
135
|
+
svelte: "js",
|
|
136
|
+
go: "go",
|
|
137
|
+
py: "py",
|
|
138
|
+
rs: "rs",
|
|
139
|
+
// The JVM resolves across languages: Kotlin and Scala call Java directly.
|
|
140
|
+
java: "jvm",
|
|
141
|
+
kotlin: "jvm",
|
|
142
|
+
scala: "jvm",
|
|
143
|
+
csharp: "dotnet",
|
|
144
|
+
// A .h header is consumed by both C and C++ translation units.
|
|
145
|
+
c: "c",
|
|
146
|
+
cpp: "c",
|
|
147
|
+
ruby: "ruby",
|
|
148
|
+
php: "php",
|
|
149
|
+
swift: "swift",
|
|
150
|
+
dart: "dart",
|
|
151
|
+
elixir: "elixir",
|
|
152
|
+
haskell: "haskell",
|
|
153
|
+
zig: "zig",
|
|
154
|
+
lua: "lua",
|
|
155
|
+
r: "r",
|
|
156
|
+
shell: "shell",
|
|
157
|
+
sql: "sql",
|
|
158
|
+
json: "data",
|
|
159
|
+
yaml: "data",
|
|
160
|
+
toml: "data",
|
|
161
|
+
html: "web",
|
|
162
|
+
css: "web",
|
|
163
|
+
proto: "proto",
|
|
164
|
+
graphql: "graphql",
|
|
165
|
+
md: "other",
|
|
166
|
+
other: "other"
|
|
167
|
+
};
|
|
168
|
+
LANG_FAMILY_ENTRIES = Object.freeze(
|
|
169
|
+
Object.entries(LANG_FAMILY).map(
|
|
170
|
+
([lang, family]) => Object.freeze([lang, family])
|
|
171
|
+
)
|
|
172
|
+
);
|
|
124
173
|
}
|
|
125
174
|
});
|
|
126
175
|
|
|
@@ -285,7 +334,7 @@ function getTypeName(name) {
|
|
|
285
334
|
function deduplicateRefs(refs) {
|
|
286
335
|
const seen = /* @__PURE__ */ new Set();
|
|
287
336
|
return refs.filter((r) => {
|
|
288
|
-
const key = `${r.toName}:${r.callType}:${r.line}`;
|
|
337
|
+
const key = `${r.toName}:${r.callType}:${r.line}:${r.module ?? ""}`;
|
|
289
338
|
if (seen.has(key)) return false;
|
|
290
339
|
seen.add(key);
|
|
291
340
|
return true;
|
|
@@ -295,10 +344,16 @@ function getImportSpecifierName(spec) {
|
|
|
295
344
|
return spec.propertyName?.text ?? spec.name.text;
|
|
296
345
|
}
|
|
297
346
|
function emitImportSpecifierRefs(node, refs, lineNum) {
|
|
347
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
298
348
|
const clause = node.importClause;
|
|
299
|
-
if (!clause)
|
|
349
|
+
if (!clause) {
|
|
350
|
+
if (module) {
|
|
351
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
352
|
+
}
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
300
355
|
if (clause.name) {
|
|
301
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
|
|
356
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
302
357
|
}
|
|
303
358
|
const bindings = clause.namedBindings;
|
|
304
359
|
if (!bindings) return;
|
|
@@ -308,26 +363,40 @@ function emitImportSpecifierRefs(node, refs, lineNum) {
|
|
|
308
363
|
fromId: 0,
|
|
309
364
|
toName: getImportSpecifierName(element),
|
|
310
365
|
callType: "import",
|
|
311
|
-
line: lineNum
|
|
366
|
+
line: lineNum,
|
|
367
|
+
module
|
|
312
368
|
});
|
|
313
369
|
}
|
|
314
370
|
} else if (ts.isNamespaceImport(bindings)) {
|
|
315
|
-
refs.push({
|
|
371
|
+
refs.push({
|
|
372
|
+
fromId: 0,
|
|
373
|
+
toName: bindings.name.text,
|
|
374
|
+
callType: "import",
|
|
375
|
+
line: lineNum,
|
|
376
|
+
module
|
|
377
|
+
});
|
|
316
378
|
}
|
|
317
379
|
}
|
|
380
|
+
function moduleSpecifierOf(node) {
|
|
381
|
+
return node && ts.isStringLiteral(node) ? node.text : void 0;
|
|
382
|
+
}
|
|
318
383
|
function emitExportSpecifierRefs(node, refs, lineNum) {
|
|
384
|
+
const module = moduleSpecifierOf(node.moduleSpecifier);
|
|
319
385
|
const clause = node.exportClause;
|
|
320
386
|
if (clause && ts.isNamespaceExport(clause)) {
|
|
321
|
-
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum });
|
|
387
|
+
refs.push({ fromId: 0, toName: clause.name.text, callType: "import", line: lineNum, module });
|
|
322
388
|
return;
|
|
323
389
|
}
|
|
324
390
|
if (clause && ts.isNamedExports(clause)) {
|
|
325
391
|
for (const element of clause.elements) {
|
|
326
392
|
const originalName = element.propertyName?.text ?? element.name.text;
|
|
327
|
-
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum });
|
|
393
|
+
refs.push({ fromId: 0, toName: originalName, callType: "import", line: lineNum, module });
|
|
328
394
|
}
|
|
329
395
|
return;
|
|
330
396
|
}
|
|
397
|
+
if (module) {
|
|
398
|
+
refs.push({ fromId: 0, toName: module, callType: "import", line: lineNum, module });
|
|
399
|
+
}
|
|
331
400
|
}
|
|
332
401
|
var ts, tsLoad, kindMapCache;
|
|
333
402
|
var init_ts_parser = __esm({
|
|
@@ -340,21 +409,21 @@ var init_ts_parser = __esm({
|
|
|
340
409
|
});
|
|
341
410
|
|
|
342
411
|
// src/_win32-resolve.ts
|
|
343
|
-
import * as
|
|
344
|
-
import * as
|
|
412
|
+
import * as fs7 from "node:fs";
|
|
413
|
+
import * as path8 from "node:path";
|
|
345
414
|
function resolveWin32Command(cmd) {
|
|
346
415
|
if (process.platform !== "win32") return cmd;
|
|
347
|
-
if (cmd.includes("/") || cmd.includes("\\") ||
|
|
416
|
+
if (cmd.includes("/") || cmd.includes("\\") || path8.extname(cmd.replace(/\//g, "\\"))) {
|
|
348
417
|
return cmd;
|
|
349
418
|
}
|
|
350
419
|
const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
|
|
351
|
-
const pathDirs = (process.env["PATH"] ?? "").split(
|
|
420
|
+
const pathDirs = (process.env["PATH"] ?? "").split(path8.delimiter);
|
|
352
421
|
for (const dir of pathDirs) {
|
|
353
|
-
const base =
|
|
422
|
+
const base = path8.join(dir, cmd);
|
|
354
423
|
for (const ext of pathext) {
|
|
355
424
|
const full = `${base}${ext}`;
|
|
356
425
|
try {
|
|
357
|
-
|
|
426
|
+
fs7.accessSync(full, fs7.constants.X_OK);
|
|
358
427
|
return full;
|
|
359
428
|
} catch {
|
|
360
429
|
}
|
|
@@ -368,6 +437,82 @@ var init_win32_resolve = __esm({
|
|
|
368
437
|
}
|
|
369
438
|
});
|
|
370
439
|
|
|
440
|
+
// src/codebase-index/parser-output.ts
|
|
441
|
+
function coerceSymbols(value) {
|
|
442
|
+
if (!Array.isArray(value)) return [];
|
|
443
|
+
return value.flatMap((entry) => {
|
|
444
|
+
const candidate = entry;
|
|
445
|
+
if (typeof candidate.name !== "string" || typeof candidate.kind !== "string") return [];
|
|
446
|
+
return [
|
|
447
|
+
{
|
|
448
|
+
name: candidate.name,
|
|
449
|
+
kind: candidate.kind,
|
|
450
|
+
line: typeof candidate.line === "number" ? candidate.line : 1,
|
|
451
|
+
col: typeof candidate.col === "number" ? candidate.col : 0,
|
|
452
|
+
signature: typeof candidate.signature === "string" ? candidate.signature : "",
|
|
453
|
+
scope: typeof candidate.scope === "string" ? candidate.scope : ""
|
|
454
|
+
}
|
|
455
|
+
];
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
function coerceRefs(value, lang) {
|
|
459
|
+
if (!Array.isArray(value)) return [];
|
|
460
|
+
return value.flatMap((entry) => {
|
|
461
|
+
const candidate = entry;
|
|
462
|
+
if (typeof candidate.toName !== "string" || !candidate.toName) return [];
|
|
463
|
+
if (typeof candidate.callType !== "string" || !CALL_TYPES.has(candidate.callType)) return [];
|
|
464
|
+
const module = typeof candidate.module === "string" && candidate.module ? candidate.module : void 0;
|
|
465
|
+
return [
|
|
466
|
+
{
|
|
467
|
+
fromId: 0,
|
|
468
|
+
toName: candidate.toName,
|
|
469
|
+
callType: candidate.callType,
|
|
470
|
+
line: typeof candidate.line === "number" ? candidate.line : 1,
|
|
471
|
+
lang,
|
|
472
|
+
module
|
|
473
|
+
}
|
|
474
|
+
];
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
function parseParserOutput(stdout, lang) {
|
|
478
|
+
const trimmed = stdout.trim();
|
|
479
|
+
if (!trimmed) return { symbols: [], refs: [] };
|
|
480
|
+
let parsed;
|
|
481
|
+
try {
|
|
482
|
+
parsed = JSON.parse(trimmed);
|
|
483
|
+
} catch {
|
|
484
|
+
return { symbols: [], refs: [] };
|
|
485
|
+
}
|
|
486
|
+
if (Array.isArray(parsed)) return { symbols: coerceSymbols(parsed), refs: [] };
|
|
487
|
+
const record = parsed;
|
|
488
|
+
return {
|
|
489
|
+
symbols: coerceSymbols(record.symbols),
|
|
490
|
+
refs: dedupeRefs(coerceRefs(record.refs, lang))
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
function dedupeRefs(refs) {
|
|
494
|
+
const seen = /* @__PURE__ */ new Set();
|
|
495
|
+
return refs.filter((ref) => {
|
|
496
|
+
const key = `${ref.toName}:${ref.callType}:${ref.line}:${ref.module ?? ""}`;
|
|
497
|
+
if (seen.has(key)) return false;
|
|
498
|
+
seen.add(key);
|
|
499
|
+
return true;
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
var CALL_TYPES;
|
|
503
|
+
var init_parser_output = __esm({
|
|
504
|
+
"src/codebase-index/parser-output.ts"() {
|
|
505
|
+
"use strict";
|
|
506
|
+
CALL_TYPES = /* @__PURE__ */ new Set([
|
|
507
|
+
"call",
|
|
508
|
+
"type_ref",
|
|
509
|
+
"inherit",
|
|
510
|
+
"implement",
|
|
511
|
+
"import"
|
|
512
|
+
]);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
371
516
|
// src/codebase-index/spawn-gate.ts
|
|
372
517
|
function withSpawnGate(fn) {
|
|
373
518
|
const run = chain.then(fn, fn);
|
|
@@ -393,8 +538,8 @@ __export(go_parser_exports, {
|
|
|
393
538
|
});
|
|
394
539
|
import { spawn as spawn2 } from "node:child_process";
|
|
395
540
|
import * as os2 from "node:os";
|
|
396
|
-
import * as
|
|
397
|
-
import * as
|
|
541
|
+
import * as path9 from "node:path";
|
|
542
|
+
import * as fs8 from "node:fs/promises";
|
|
398
543
|
async function parseSymbols2(opts) {
|
|
399
544
|
const { file, content, lang } = opts;
|
|
400
545
|
try {
|
|
@@ -402,7 +547,8 @@ async function parseSymbols2(opts) {
|
|
|
402
547
|
if (parsed.symbols.length > 0) {
|
|
403
548
|
return parsed;
|
|
404
549
|
}
|
|
405
|
-
|
|
550
|
+
const fallback = fallbackParse(file, content, lang);
|
|
551
|
+
return parsed.refs?.length ? { ...fallback, refs: parsed.refs } : fallback;
|
|
406
552
|
} catch {
|
|
407
553
|
return fallbackParse(file, content, lang);
|
|
408
554
|
}
|
|
@@ -466,9 +612,9 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
466
612
|
try {
|
|
467
613
|
let scriptPath = _cachedGoScriptPath;
|
|
468
614
|
if (!scriptPath) {
|
|
469
|
-
const tmpDir = await
|
|
470
|
-
scriptPath =
|
|
471
|
-
await
|
|
615
|
+
const tmpDir = await fs8.mkdtemp(path9.join(os2.tmpdir(), "ws-go-parse-"));
|
|
616
|
+
scriptPath = path9.join(tmpDir, "parse.go");
|
|
617
|
+
await fs8.writeFile(scriptPath, GO_PARSE_SCRIPT, "utf8");
|
|
472
618
|
_cachedGoScriptPath = scriptPath;
|
|
473
619
|
}
|
|
474
620
|
const goBinary = resolveWin32Command("go");
|
|
@@ -510,8 +656,8 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
510
656
|
if (code !== 0 || !stdout.trim()) {
|
|
511
657
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
512
658
|
}
|
|
513
|
-
const
|
|
514
|
-
const symbols =
|
|
659
|
+
const { symbols: rawSymbols, refs } = parseParserOutput(stdout, lang);
|
|
660
|
+
const symbols = rawSymbols.map((s) => ({
|
|
515
661
|
id: 0,
|
|
516
662
|
lang,
|
|
517
663
|
kind: s.kind,
|
|
@@ -524,7 +670,7 @@ async function syncGoParse(filePath, content, lang) {
|
|
|
524
670
|
scope: s.scope ?? "",
|
|
525
671
|
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
526
672
|
}));
|
|
527
|
-
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
673
|
+
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
528
674
|
} catch {
|
|
529
675
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
530
676
|
}
|
|
@@ -534,6 +680,7 @@ var init_go_parser = __esm({
|
|
|
534
680
|
"src/codebase-index/go-parser.ts"() {
|
|
535
681
|
"use strict";
|
|
536
682
|
init_win32_resolve();
|
|
683
|
+
init_parser_output();
|
|
537
684
|
init_spawn_gate();
|
|
538
685
|
init_languages();
|
|
539
686
|
GO_PARSE_SCRIPT = `
|
|
@@ -547,6 +694,7 @@ import (
|
|
|
547
694
|
"go/token"
|
|
548
695
|
"io"
|
|
549
696
|
"os"
|
|
697
|
+
"strconv"
|
|
550
698
|
"strings"
|
|
551
699
|
)
|
|
552
700
|
|
|
@@ -559,16 +707,34 @@ type Sym struct {
|
|
|
559
707
|
Scope string \`json:"scope"\`
|
|
560
708
|
}
|
|
561
709
|
|
|
710
|
+
// Ref is a cross-reference emitted alongside the symbols, so one \`go run\`
|
|
711
|
+
// yields both. Module is the import path for CallType "import", else empty.
|
|
712
|
+
type Ref struct {
|
|
713
|
+
ToName string \`json:"toName"\`
|
|
714
|
+
CallType string \`json:"callType"\`
|
|
715
|
+
Line int \`json:"line"\`
|
|
716
|
+
Module string \`json:"module"\`
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
type Result struct {
|
|
720
|
+
Symbols []Sym \`json:"symbols"\`
|
|
721
|
+
Refs []Ref \`json:"refs"\`
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
func emptyResult() string {
|
|
725
|
+
return "{\\"symbols\\":[],\\"refs\\":[]}"
|
|
726
|
+
}
|
|
727
|
+
|
|
562
728
|
func main() {
|
|
563
729
|
src, err := io.ReadAll(os.Stdin)
|
|
564
730
|
if err != nil {
|
|
565
|
-
fmt.Print(
|
|
731
|
+
fmt.Print(emptyResult())
|
|
566
732
|
return
|
|
567
733
|
}
|
|
568
734
|
fset := token.NewFileSet()
|
|
569
735
|
node, err := parser.ParseFile(fset, "src.go", src, 0)
|
|
570
736
|
if err != nil {
|
|
571
|
-
fmt.Print(
|
|
737
|
+
fmt.Print(emptyResult())
|
|
572
738
|
return
|
|
573
739
|
}
|
|
574
740
|
|
|
@@ -632,9 +798,43 @@ func main() {
|
|
|
632
798
|
}
|
|
633
799
|
}
|
|
634
800
|
|
|
635
|
-
|
|
801
|
+
refs := []Ref{}
|
|
802
|
+
ast.Inspect(node, func(n ast.Node) bool {
|
|
803
|
+
switch expr := n.(type) {
|
|
804
|
+
case *ast.CallExpr:
|
|
805
|
+
line := fset.Position(expr.Pos()).Line
|
|
806
|
+
switch fun := expr.Fun.(type) {
|
|
807
|
+
case *ast.Ident:
|
|
808
|
+
refs = append(refs, Ref{ToName: fun.Name, CallType: "call", Line: line})
|
|
809
|
+
case *ast.SelectorExpr:
|
|
810
|
+
// Record the selected name (\`Join\` of \`filepath.Join\`): it is the
|
|
811
|
+
// declared symbol name, so it resolves the same way the TypeScript
|
|
812
|
+
// and Python extractors' call refs do.
|
|
813
|
+
refs = append(refs, Ref{ToName: fun.Sel.Name, CallType: "call", Line: line})
|
|
814
|
+
}
|
|
815
|
+
case *ast.ImportSpec:
|
|
816
|
+
if expr.Path != nil {
|
|
817
|
+
if importPath, uerr := strconv.Unquote(expr.Path.Value); uerr == nil {
|
|
818
|
+
line := fset.Position(expr.Pos()).Line
|
|
819
|
+
// A Go import names a package, not a symbol; the package's
|
|
820
|
+
// last path segment is the name it is referenced by.
|
|
821
|
+
name := importPath
|
|
822
|
+
if idx := strings.LastIndex(importPath, "/"); idx >= 0 {
|
|
823
|
+
name = importPath[idx+1:]
|
|
824
|
+
}
|
|
825
|
+
refs = append(refs, Ref{ToName: name, CallType: "import", Line: line, Module: importPath})
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return true
|
|
830
|
+
})
|
|
831
|
+
|
|
832
|
+
if syms == nil {
|
|
833
|
+
syms = []Sym{}
|
|
834
|
+
}
|
|
835
|
+
data, err := json.Marshal(Result{Symbols: syms, Refs: refs})
|
|
636
836
|
if err != nil {
|
|
637
|
-
fmt.Print(
|
|
837
|
+
fmt.Print(emptyResult())
|
|
638
838
|
return
|
|
639
839
|
}
|
|
640
840
|
fmt.Print(string(data))
|
|
@@ -986,9 +1186,13 @@ var init_generic_parser = __esm({
|
|
|
986
1186
|
],
|
|
987
1187
|
elixir: [
|
|
988
1188
|
{ re: /\bdef(?:p|macro|macrop)?\s+([A-Za-z_]\w*[!?]?)/g, kind: "function" },
|
|
989
|
-
|
|
1189
|
+
// Dotted module names must be captured whole: `alias Foo.Bar` resolves
|
|
1190
|
+
// against this symbol, and a `Foo`-only capture never matches it.
|
|
1191
|
+
{ re: /\bdefmodule\s+([A-Z][\w.]*)/g, kind: "namespace" }
|
|
990
1192
|
],
|
|
991
1193
|
haskell: [
|
|
1194
|
+
// Target of `import Data.List`.
|
|
1195
|
+
{ re: /^module\s+([A-Z][\w.]*)/gm, kind: "namespace" },
|
|
992
1196
|
{ re: /^([A-Za-z_]\w*)\s*::/gm, kind: "function" },
|
|
993
1197
|
{ re: /\bdata\s+([A-Za-z_]\w*)/g, kind: "type" },
|
|
994
1198
|
{ re: /\btype\s+(?:family\s+)?([A-Za-z_]\w*)/g, kind: "type" },
|
|
@@ -1079,9 +1283,9 @@ __export(py_parser_exports, {
|
|
|
1079
1283
|
parseSymbols: () => parseSymbols4
|
|
1080
1284
|
});
|
|
1081
1285
|
import { spawn as spawn3 } from "node:child_process";
|
|
1082
|
-
import * as
|
|
1286
|
+
import * as fs9 from "node:fs/promises";
|
|
1083
1287
|
import * as os3 from "node:os";
|
|
1084
|
-
import * as
|
|
1288
|
+
import * as path10 from "node:path";
|
|
1085
1289
|
async function parseSymbols4(opts) {
|
|
1086
1290
|
const { file, content, lang } = opts;
|
|
1087
1291
|
try {
|
|
@@ -1159,10 +1363,10 @@ function spawnPyParser(pyBinary, scriptPath, filePath, content) {
|
|
|
1159
1363
|
async function syncPyParse(filePath, content, lang) {
|
|
1160
1364
|
try {
|
|
1161
1365
|
if (!_cachedScriptPath) {
|
|
1162
|
-
const tmpDir =
|
|
1163
|
-
await
|
|
1164
|
-
_cachedScriptPath =
|
|
1165
|
-
await
|
|
1366
|
+
const tmpDir = path10.join(os3.tmpdir(), "ws-py-parse");
|
|
1367
|
+
await fs9.mkdir(tmpDir, { recursive: true });
|
|
1368
|
+
_cachedScriptPath = path10.join(tmpDir, "parse.py");
|
|
1369
|
+
await fs9.writeFile(_cachedScriptPath, PY_PARSE_SCRIPT, "utf8");
|
|
1166
1370
|
}
|
|
1167
1371
|
cachedPyBinary ??= resolvePython();
|
|
1168
1372
|
const pyBinary = await cachedPyBinary;
|
|
@@ -1176,7 +1380,7 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
1176
1380
|
if (code !== 0 || !stdout.trim()) {
|
|
1177
1381
|
return { file: filePath, lang, symbols: [], mtimeMs: Date.now() };
|
|
1178
1382
|
}
|
|
1179
|
-
const raw =
|
|
1383
|
+
const { symbols: raw, refs } = parseParserOutput(stdout, lang);
|
|
1180
1384
|
const symbols = raw.map((s) => ({
|
|
1181
1385
|
id: 0,
|
|
1182
1386
|
lang,
|
|
@@ -1190,7 +1394,7 @@ async function syncPyParse(filePath, content, lang) {
|
|
|
1190
1394
|
scope: s.scope ?? "",
|
|
1191
1395
|
text: `${s.name} ${s.signature ?? ""}`.trim()
|
|
1192
1396
|
}));
|
|
1193
|
-
return { file: filePath, lang, symbols, mtimeMs: Date.now() };
|
|
1397
|
+
return { file: filePath, lang, symbols, refs, mtimeMs: Date.now() };
|
|
1194
1398
|
} catch {
|
|
1195
1399
|
return null;
|
|
1196
1400
|
}
|
|
@@ -1201,6 +1405,7 @@ var init_py_parser = __esm({
|
|
|
1201
1405
|
"use strict";
|
|
1202
1406
|
init_win32_resolve();
|
|
1203
1407
|
init_generic_parser();
|
|
1408
|
+
init_parser_output();
|
|
1204
1409
|
init_spawn_gate();
|
|
1205
1410
|
init_languages();
|
|
1206
1411
|
PY_PARSE_SCRIPT = `import ast, json, sys, os
|
|
@@ -1262,7 +1467,18 @@ class Sym:
|
|
|
1262
1467
|
def is_private(name):
|
|
1263
1468
|
return name.startswith("__") and not name.endswith("__")
|
|
1264
1469
|
|
|
1470
|
+
def leaf_name(node):
|
|
1471
|
+
# Declared name of the callee: \`join\` of \`os.path.join\`. Matches how the
|
|
1472
|
+
# TypeScript and Go extractors record call refs, so resolution behaves the
|
|
1473
|
+
# same across languages.
|
|
1474
|
+
if isinstance(node, ast.Attribute):
|
|
1475
|
+
return node.attr
|
|
1476
|
+
if isinstance(node, ast.Name):
|
|
1477
|
+
return node.id
|
|
1478
|
+
return get_name(node).split(".")[-1]
|
|
1479
|
+
|
|
1265
1480
|
syms = []
|
|
1481
|
+
refs = []
|
|
1266
1482
|
errors = []
|
|
1267
1483
|
|
|
1268
1484
|
try:
|
|
@@ -1270,7 +1486,7 @@ try:
|
|
|
1270
1486
|
tree = ast.parse(source, filename=sys.argv[1])
|
|
1271
1487
|
except Exception as e:
|
|
1272
1488
|
errors.append(str(e))
|
|
1273
|
-
print("[]")
|
|
1489
|
+
print(json.dumps({"symbols": [], "refs": []}))
|
|
1274
1490
|
sys.exit(0)
|
|
1275
1491
|
|
|
1276
1492
|
# Module-level scope
|
|
@@ -1404,7 +1620,42 @@ class ModuleVisitor(ast.NodeVisitor):
|
|
|
1404
1620
|
visitor = ModuleVisitor()
|
|
1405
1621
|
visitor.visit(tree)
|
|
1406
1622
|
|
|
1407
|
-
|
|
1623
|
+
# Refs need a separate full walk: ModuleVisitor deliberately does not descend
|
|
1624
|
+
# into function bodies (it would index locals as symbols), but that is exactly
|
|
1625
|
+
# where the calls are.
|
|
1626
|
+
for node in ast.walk(tree):
|
|
1627
|
+
if isinstance(node, ast.Call):
|
|
1628
|
+
name = leaf_name(node.func)
|
|
1629
|
+
if name:
|
|
1630
|
+
refs.append({"toName": name, "callType": "call", "line": node.lineno})
|
|
1631
|
+
elif isinstance(node, ast.Import):
|
|
1632
|
+
for alias in node.names:
|
|
1633
|
+
refs.append({
|
|
1634
|
+
"toName": alias.name.split(".")[-1],
|
|
1635
|
+
"callType": "import",
|
|
1636
|
+
"line": node.lineno,
|
|
1637
|
+
"module": alias.name,
|
|
1638
|
+
})
|
|
1639
|
+
elif isinstance(node, ast.ImportFrom):
|
|
1640
|
+
# PEP 328: node.level is the number of leading dots. Preserving them is
|
|
1641
|
+
# what lets the resolver walk up from the importing file's package \u2014
|
|
1642
|
+
# dropping them made \`from .foo import X\` indistinguishable from an
|
|
1643
|
+
# absolute \`foo\`.
|
|
1644
|
+
module = ("." * (node.level or 0)) + (node.module or "")
|
|
1645
|
+
for alias in node.names:
|
|
1646
|
+
refs.append({
|
|
1647
|
+
"toName": alias.name,
|
|
1648
|
+
"callType": "import",
|
|
1649
|
+
"line": node.lineno,
|
|
1650
|
+
"module": module,
|
|
1651
|
+
})
|
|
1652
|
+
elif isinstance(node, ast.ClassDef):
|
|
1653
|
+
for base in node.bases:
|
|
1654
|
+
name = leaf_name(base)
|
|
1655
|
+
if name:
|
|
1656
|
+
refs.append({"toName": name, "callType": "inherit", "line": node.lineno})
|
|
1657
|
+
|
|
1658
|
+
print(json.dumps({"symbols": [s.to_dict() for s in syms], "refs": refs}))
|
|
1408
1659
|
`;
|
|
1409
1660
|
_cachedScriptPath = null;
|
|
1410
1661
|
}
|
|
@@ -1417,107 +1668,10 @@ __export(rs_parser_exports, {
|
|
|
1417
1668
|
parseSymbols: () => parseSymbols5
|
|
1418
1669
|
});
|
|
1419
1670
|
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
1420
|
-
import { execFile, spawn as spawn4 } from "node:child_process";
|
|
1421
|
-
import * as fs9 from "node:fs/promises";
|
|
1422
|
-
import * as path10 from "node:path";
|
|
1423
1671
|
async function parseSymbols5(opts) {
|
|
1424
1672
|
const { file, content, lang } = opts;
|
|
1425
|
-
const nativeAvailable = await checkNativeParser();
|
|
1426
|
-
if (nativeAvailable) {
|
|
1427
|
-
const result = await withSpawnGate(() => tryNativeParse(file, content));
|
|
1428
|
-
if (result) return result;
|
|
1429
|
-
}
|
|
1430
1673
|
return regexParse({ file, content, lang });
|
|
1431
1674
|
}
|
|
1432
|
-
function probe(command, args) {
|
|
1433
|
-
return new Promise((resolve4, reject) => {
|
|
1434
|
-
execFile(command, args, { timeout: 1e4, windowsHide: true }, (error) => {
|
|
1435
|
-
if (error) reject(error);
|
|
1436
|
-
else resolve4();
|
|
1437
|
-
});
|
|
1438
|
-
});
|
|
1439
|
-
}
|
|
1440
|
-
function checkNativeParser() {
|
|
1441
|
-
nativeParserAvailability ??= (async () => {
|
|
1442
|
-
try {
|
|
1443
|
-
await probe("rustc", ["--version"]);
|
|
1444
|
-
const toolsDir = path10.join(process.cwd(), "tools");
|
|
1445
|
-
await probe(
|
|
1446
|
-
"cargo",
|
|
1447
|
-
[
|
|
1448
|
-
"metadata",
|
|
1449
|
-
"--no-deps",
|
|
1450
|
-
"--format-version",
|
|
1451
|
-
"1",
|
|
1452
|
-
"--manifest-path",
|
|
1453
|
-
path10.join(toolsDir, "Cargo.toml")
|
|
1454
|
-
]
|
|
1455
|
-
);
|
|
1456
|
-
return true;
|
|
1457
|
-
} catch {
|
|
1458
|
-
return false;
|
|
1459
|
-
}
|
|
1460
|
-
})();
|
|
1461
|
-
return nativeParserAvailability;
|
|
1462
|
-
}
|
|
1463
|
-
async function tryNativeParse(file, content) {
|
|
1464
|
-
try {
|
|
1465
|
-
const toolsDir = path10.join(process.cwd(), "tools");
|
|
1466
|
-
const crateDir = path10.join(toolsDir, "syn-parser");
|
|
1467
|
-
const tmpFile = path10.join(crateDir, "src", "input.rs");
|
|
1468
|
-
await fs9.writeFile(tmpFile, content, "utf8");
|
|
1469
|
-
const cargoBinary = resolveWin32Command("cargo");
|
|
1470
|
-
const result = await new Promise(
|
|
1471
|
-
(resolve4, reject) => {
|
|
1472
|
-
let settled = false;
|
|
1473
|
-
const proc = spawn4(
|
|
1474
|
-
cargoBinary,
|
|
1475
|
-
["run", "--manifest-path", path10.join(toolsDir, "Cargo.toml")],
|
|
1476
|
-
{
|
|
1477
|
-
cwd: process.cwd(),
|
|
1478
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1479
|
-
windowsHide: true
|
|
1480
|
-
}
|
|
1481
|
-
);
|
|
1482
|
-
proc.on("error", (err) => {
|
|
1483
|
-
if (settled) return;
|
|
1484
|
-
settled = true;
|
|
1485
|
-
reject(err);
|
|
1486
|
-
});
|
|
1487
|
-
let stdout2 = "";
|
|
1488
|
-
proc.stdout?.on("data", (chunk) => {
|
|
1489
|
-
stdout2 += chunk.toString();
|
|
1490
|
-
});
|
|
1491
|
-
proc.stderr?.resume();
|
|
1492
|
-
const timer = setTimeout(() => {
|
|
1493
|
-
if (settled) return;
|
|
1494
|
-
settled = true;
|
|
1495
|
-
proc.kill("SIGKILL");
|
|
1496
|
-
reject(new Error("timeout"));
|
|
1497
|
-
}, 15e3);
|
|
1498
|
-
timer.unref?.();
|
|
1499
|
-
proc.on("close", (c) => {
|
|
1500
|
-
if (settled) return;
|
|
1501
|
-
settled = true;
|
|
1502
|
-
clearTimeout(timer);
|
|
1503
|
-
resolve4({ code: c, stdout: stdout2 });
|
|
1504
|
-
});
|
|
1505
|
-
}
|
|
1506
|
-
);
|
|
1507
|
-
const { code, stdout } = result;
|
|
1508
|
-
if (code === 0 && stdout.trim()) {
|
|
1509
|
-
const symbols = JSON.parse(stdout.trim());
|
|
1510
|
-
return {
|
|
1511
|
-
file,
|
|
1512
|
-
lang: "rs",
|
|
1513
|
-
symbols: symbols.map((s) => ({ ...s, id: 0, lang: "rs" })),
|
|
1514
|
-
mtimeMs: Date.now()
|
|
1515
|
-
};
|
|
1516
|
-
}
|
|
1517
|
-
} catch {
|
|
1518
|
-
}
|
|
1519
|
-
return null;
|
|
1520
|
-
}
|
|
1521
1675
|
function regexParse(opts) {
|
|
1522
1676
|
const { file, content, lang } = opts;
|
|
1523
1677
|
const symbols = [];
|
|
@@ -1573,12 +1727,10 @@ function regexParse(opts) {
|
|
|
1573
1727
|
});
|
|
1574
1728
|
return { file, lang, symbols: deduped, mtimeMs: Date.now() };
|
|
1575
1729
|
}
|
|
1576
|
-
var
|
|
1730
|
+
var RS_PATTERNS;
|
|
1577
1731
|
var init_rs_parser = __esm({
|
|
1578
1732
|
"src/codebase-index/rs-parser.ts"() {
|
|
1579
1733
|
"use strict";
|
|
1580
|
-
init_win32_resolve();
|
|
1581
|
-
init_spawn_gate();
|
|
1582
1734
|
init_languages();
|
|
1583
1735
|
RS_PATTERNS = [
|
|
1584
1736
|
{ regex: /fn\s+(\w+)\s*\([^)]*\)/g, kind: "function" },
|
|
@@ -1972,7 +2124,7 @@ var init_yaml_parser = __esm({
|
|
|
1972
2124
|
|
|
1973
2125
|
// src/codebase-index/project-server-client.ts
|
|
1974
2126
|
import { spawn } from "node:child_process";
|
|
1975
|
-
import * as
|
|
2127
|
+
import * as fs5 from "node:fs";
|
|
1976
2128
|
import * as net from "node:net";
|
|
1977
2129
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1978
2130
|
import { checkUnixSocketPath } from "@wrongstack/core/utils";
|
|
@@ -2062,16 +2214,16 @@ function resetIndexCircuitBreaker() {
|
|
|
2062
2214
|
|
|
2063
2215
|
// src/codebase-index/project-server-endpoint.ts
|
|
2064
2216
|
import { createHash } from "node:crypto";
|
|
2065
|
-
import * as
|
|
2217
|
+
import * as fs4 from "node:fs";
|
|
2066
2218
|
import * as os from "node:os";
|
|
2067
|
-
import * as
|
|
2219
|
+
import * as path5 from "node:path";
|
|
2068
2220
|
import { fileURLToPath } from "node:url";
|
|
2069
2221
|
import { assertUnixSocketPathWithinLimit } from "@wrongstack/core/utils";
|
|
2070
2222
|
|
|
2071
2223
|
// src/codebase-index/writer.ts
|
|
2072
2224
|
import { expectDefined } from "@wrongstack/core/utils";
|
|
2073
|
-
import * as
|
|
2074
|
-
import * as
|
|
2225
|
+
import * as fs3 from "node:fs";
|
|
2226
|
+
import * as path4 from "node:path";
|
|
2075
2227
|
|
|
2076
2228
|
// src/codebase-index/bm25.ts
|
|
2077
2229
|
var K1 = 1.5;
|
|
@@ -2162,6 +2314,9 @@ var Bm25Index = class {
|
|
|
2162
2314
|
}
|
|
2163
2315
|
};
|
|
2164
2316
|
|
|
2317
|
+
// src/codebase-index/writer.ts
|
|
2318
|
+
init_languages();
|
|
2319
|
+
|
|
2165
2320
|
// src/codebase-index/lsp-kind.ts
|
|
2166
2321
|
function lspKindToInternalKind(k) {
|
|
2167
2322
|
switch (k) {
|
|
@@ -2225,7 +2380,7 @@ function internalKindToLspKind(k) {
|
|
|
2225
2380
|
}
|
|
2226
2381
|
|
|
2227
2382
|
// src/codebase-index/schema.ts
|
|
2228
|
-
var SCHEMA_VERSION =
|
|
2383
|
+
var SCHEMA_VERSION = 4;
|
|
2229
2384
|
|
|
2230
2385
|
// src/codebase-index/sqlite-runtime.ts
|
|
2231
2386
|
import { createRequire } from "node:module";
|
|
@@ -2296,7 +2451,7 @@ function runSqliteWithRetry(fn) {
|
|
|
2296
2451
|
|
|
2297
2452
|
// src/codebase-index/writer-admin.ts
|
|
2298
2453
|
import * as fs from "node:fs";
|
|
2299
|
-
import * as
|
|
2454
|
+
import * as path2 from "node:path";
|
|
2300
2455
|
var DB_FILE = "index.db";
|
|
2301
2456
|
function getAllIndexableWithStatement(stmt) {
|
|
2302
2457
|
return stmt("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
|
|
@@ -2355,7 +2510,7 @@ function getAllFileMetasWithStatement(stmt) {
|
|
|
2355
2510
|
}
|
|
2356
2511
|
function getIndexDbSizeBytes(indexDir) {
|
|
2357
2512
|
try {
|
|
2358
|
-
return fs.statSync(
|
|
2513
|
+
return fs.statSync(path2.join(indexDir, DB_FILE)).size;
|
|
2359
2514
|
} catch {
|
|
2360
2515
|
return 0;
|
|
2361
2516
|
}
|
|
@@ -2406,49 +2561,345 @@ function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
|
|
|
2406
2561
|
}
|
|
2407
2562
|
function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
|
|
2408
2563
|
if (refs.length === 0) return;
|
|
2409
|
-
const chunkSize = Math.max(1, Math.floor(maxSqlVars /
|
|
2564
|
+
const chunkSize = Math.max(1, Math.floor(maxSqlVars / 8));
|
|
2410
2565
|
for (let i = 0; i < refs.length; i += chunkSize) {
|
|
2411
2566
|
const chunk = refs.slice(i, i + chunkSize);
|
|
2412
|
-
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?)").join(", ");
|
|
2567
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
2413
2568
|
const insert = stmt(
|
|
2414
|
-
`INSERT INTO refs(from_id, to_name, to_id, call_type, line
|
|
2569
|
+
`INSERT INTO refs(from_id, to_name, to_id, call_type, line, lang, module, to_file)
|
|
2570
|
+
VALUES ${placeholders}`
|
|
2415
2571
|
);
|
|
2416
2572
|
const binds = [];
|
|
2417
2573
|
for (const ref of chunk) {
|
|
2418
|
-
binds.push(
|
|
2574
|
+
binds.push(
|
|
2575
|
+
ref.fromId,
|
|
2576
|
+
ref.toName,
|
|
2577
|
+
ref.toId ?? null,
|
|
2578
|
+
ref.callType,
|
|
2579
|
+
ref.line,
|
|
2580
|
+
ref.lang ?? "",
|
|
2581
|
+
ref.module ?? null,
|
|
2582
|
+
ref.toFile ?? null
|
|
2583
|
+
);
|
|
2419
2584
|
}
|
|
2420
2585
|
insert.run(...binds);
|
|
2421
2586
|
}
|
|
2422
2587
|
}
|
|
2423
2588
|
|
|
2424
|
-
// src/codebase-index/writer-graph-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2589
|
+
// src/codebase-index/writer-graph-reader.ts
|
|
2590
|
+
init_languages();
|
|
2591
|
+
|
|
2592
|
+
// src/codebase-index/module-roots.ts
|
|
2593
|
+
init_languages();
|
|
2594
|
+
import * as fs2 from "node:fs/promises";
|
|
2595
|
+
import * as path3 from "node:path";
|
|
2596
|
+
function toPortablePath(file) {
|
|
2597
|
+
return file.replace(/\\/g, "/");
|
|
2598
|
+
}
|
|
2599
|
+
async function readTextIfPresent(file) {
|
|
2600
|
+
try {
|
|
2601
|
+
return await fs2.readFile(file, "utf8");
|
|
2602
|
+
} catch {
|
|
2603
|
+
return void 0;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
function parsePackageJsonName(source) {
|
|
2607
|
+
try {
|
|
2608
|
+
const parsed = JSON.parse(source);
|
|
2609
|
+
return typeof parsed.name === "string" && parsed.name ? parsed.name : void 0;
|
|
2610
|
+
} catch {
|
|
2611
|
+
return void 0;
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
function parseGoModulePath(source) {
|
|
2615
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
2616
|
+
const line = rawLine.replace(/\/\/.*$/, "").trim();
|
|
2617
|
+
const match = /^module\s+(\S+)/.exec(line);
|
|
2618
|
+
if (match?.[1]) return match[1].replace(/^["']|["']$/g, "");
|
|
2619
|
+
}
|
|
2620
|
+
return void 0;
|
|
2621
|
+
}
|
|
2622
|
+
function parseTomlTableName(source, tables) {
|
|
2623
|
+
let current = "";
|
|
2624
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
2625
|
+
const line = rawLine.replace(/#.*$/, "").trim();
|
|
2626
|
+
if (line.startsWith("[[")) {
|
|
2627
|
+
current = "\0";
|
|
2628
|
+
continue;
|
|
2629
|
+
}
|
|
2630
|
+
const table = /^\[([^\]]+)\]$/.exec(line);
|
|
2631
|
+
if (table?.[1]) {
|
|
2632
|
+
current = table[1].trim();
|
|
2633
|
+
continue;
|
|
2634
|
+
}
|
|
2635
|
+
if (!tables.includes(current)) continue;
|
|
2636
|
+
const match = /^name\s*=\s*["']([^"']+)["']/.exec(line);
|
|
2637
|
+
if (match?.[1]) return match[1];
|
|
2638
|
+
}
|
|
2639
|
+
return void 0;
|
|
2640
|
+
}
|
|
2641
|
+
function parsePomArtifactId(source) {
|
|
2642
|
+
const withoutParent = source.replace(/<parent>[\s\S]*?<\/parent>/g, "");
|
|
2643
|
+
return /<artifactId>\s*([^<\s]+)\s*<\/artifactId>/.exec(withoutParent)?.[1];
|
|
2644
|
+
}
|
|
2645
|
+
var LANGS_BY_KIND = {
|
|
2646
|
+
npm: ["ts", "tsx", "js", "jsx", "vue", "svelte"],
|
|
2647
|
+
cargo: ["rs"],
|
|
2648
|
+
go: ["go"],
|
|
2649
|
+
python: ["py"],
|
|
2650
|
+
maven: ["java", "kotlin", "scala"],
|
|
2651
|
+
gradle: ["java", "kotlin", "scala"],
|
|
2652
|
+
dotnet: ["csharp"]
|
|
2653
|
+
};
|
|
2654
|
+
function ancestorsOf(dir, stopAt) {
|
|
2655
|
+
const out = [];
|
|
2656
|
+
let current = dir;
|
|
2657
|
+
for (; ; ) {
|
|
2658
|
+
out.push(current);
|
|
2659
|
+
if (current === stopAt || current.length <= stopAt.length) break;
|
|
2660
|
+
const parent = path3.posix.dirname(current);
|
|
2661
|
+
if (parent === current) break;
|
|
2662
|
+
current = parent;
|
|
2663
|
+
}
|
|
2664
|
+
return out;
|
|
2665
|
+
}
|
|
2666
|
+
var MARKER_PROBES = [
|
|
2667
|
+
{
|
|
2668
|
+
kind: "npm",
|
|
2669
|
+
file: "package.json",
|
|
2670
|
+
build: (dir, source) => {
|
|
2671
|
+
const name = parsePackageJsonName(source) ?? path3.posix.basename(dir);
|
|
2672
|
+
return { name, importPath: name, sourceRoots: [dir] };
|
|
2673
|
+
}
|
|
2674
|
+
},
|
|
2675
|
+
{
|
|
2676
|
+
kind: "cargo",
|
|
2677
|
+
file: "Cargo.toml",
|
|
2678
|
+
build: (dir, source) => {
|
|
2679
|
+
const name = parseTomlTableName(source, ["package"]);
|
|
2680
|
+
if (!name) return void 0;
|
|
2681
|
+
return {
|
|
2682
|
+
name: `crate:${name}`,
|
|
2683
|
+
// Rust paths use underscores where crate names often use dashes.
|
|
2684
|
+
importPath: name.replace(/-/g, "_"),
|
|
2685
|
+
sourceRoots: [path3.posix.join(dir, "src")]
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
},
|
|
2689
|
+
{
|
|
2690
|
+
kind: "go",
|
|
2691
|
+
file: "go.mod",
|
|
2692
|
+
build: (dir, source) => {
|
|
2693
|
+
const modulePath = parseGoModulePath(source);
|
|
2694
|
+
if (!modulePath) return void 0;
|
|
2695
|
+
return { name: `go:${modulePath}`, importPath: modulePath, sourceRoots: [dir] };
|
|
2696
|
+
}
|
|
2697
|
+
},
|
|
2698
|
+
{
|
|
2699
|
+
kind: "python",
|
|
2700
|
+
file: "pyproject.toml",
|
|
2701
|
+
build: (dir, source) => {
|
|
2702
|
+
const name = parseTomlTableName(source, ["project", "tool.poetry"]) ?? path3.posix.basename(dir);
|
|
2703
|
+
return {
|
|
2704
|
+
name: `py:${name}`,
|
|
2705
|
+
importPath: void 0,
|
|
2706
|
+
// `src/` layout is the packaging-guide default; the root itself covers
|
|
2707
|
+
// the flat layout. Both are probed, missing ones simply never match.
|
|
2708
|
+
sourceRoots: [path3.posix.join(dir, "src"), dir]
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
},
|
|
2712
|
+
{
|
|
2713
|
+
kind: "python",
|
|
2714
|
+
file: "setup.py",
|
|
2715
|
+
build: (dir) => ({
|
|
2716
|
+
name: `py:${path3.posix.basename(dir)}`,
|
|
2717
|
+
importPath: void 0,
|
|
2718
|
+
sourceRoots: [path3.posix.join(dir, "src"), dir]
|
|
2719
|
+
})
|
|
2720
|
+
},
|
|
2721
|
+
{
|
|
2722
|
+
kind: "maven",
|
|
2723
|
+
file: "pom.xml",
|
|
2724
|
+
build: (dir, source) => {
|
|
2725
|
+
const artifactId = parsePomArtifactId(source) ?? path3.posix.basename(dir);
|
|
2726
|
+
return {
|
|
2727
|
+
name: `mvn:${artifactId}`,
|
|
2728
|
+
importPath: void 0,
|
|
2729
|
+
sourceRoots: [
|
|
2730
|
+
path3.posix.join(dir, "src/main/java"),
|
|
2731
|
+
path3.posix.join(dir, "src/main/kotlin"),
|
|
2732
|
+
path3.posix.join(dir, "src/main/scala"),
|
|
2733
|
+
path3.posix.join(dir, "src/test/java")
|
|
2734
|
+
]
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
},
|
|
2738
|
+
{
|
|
2739
|
+
kind: "gradle",
|
|
2740
|
+
file: "build.gradle",
|
|
2741
|
+
build: (dir) => buildGradleRoot(dir)
|
|
2742
|
+
},
|
|
2743
|
+
{
|
|
2744
|
+
kind: "gradle",
|
|
2745
|
+
file: "build.gradle.kts",
|
|
2746
|
+
build: (dir) => buildGradleRoot(dir)
|
|
2747
|
+
}
|
|
2748
|
+
];
|
|
2749
|
+
function buildGradleRoot(dir) {
|
|
2750
|
+
return {
|
|
2751
|
+
name: `gradle:${path3.posix.basename(dir)}`,
|
|
2752
|
+
importPath: void 0,
|
|
2753
|
+
sourceRoots: [
|
|
2754
|
+
path3.posix.join(dir, "src/main/java"),
|
|
2755
|
+
path3.posix.join(dir, "src/main/kotlin"),
|
|
2756
|
+
path3.posix.join(dir, "src/main/scala")
|
|
2757
|
+
]
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
async function probeDotnetRoot(dir) {
|
|
2761
|
+
let entries;
|
|
2762
|
+
try {
|
|
2763
|
+
entries = await fs2.readdir(dir);
|
|
2764
|
+
} catch {
|
|
2765
|
+
return void 0;
|
|
2766
|
+
}
|
|
2767
|
+
const project = entries.find((entry) => entry.toLowerCase().endsWith(".csproj"));
|
|
2768
|
+
if (!project) return void 0;
|
|
2769
|
+
const name = project.slice(0, -".csproj".length);
|
|
2770
|
+
return {
|
|
2771
|
+
dir,
|
|
2772
|
+
kind: "dotnet",
|
|
2773
|
+
name: `csproj:${name}`,
|
|
2774
|
+
importPath: void 0,
|
|
2775
|
+
sourceRoots: [dir]
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2778
|
+
async function detectModuleRoots(projectRoot, files) {
|
|
2779
|
+
const root = toPortablePath(projectRoot).replace(/\/+$/, "");
|
|
2780
|
+
const langsByDir = /* @__PURE__ */ new Map();
|
|
2781
|
+
for (const file of files) {
|
|
2782
|
+
const portable = toPortablePath(file);
|
|
2783
|
+
const lang = detectLang(portable);
|
|
2784
|
+
if (!lang) continue;
|
|
2785
|
+
const dir = path3.posix.dirname(portable);
|
|
2786
|
+
let langs = langsByDir.get(dir);
|
|
2787
|
+
if (!langs) {
|
|
2788
|
+
langs = /* @__PURE__ */ new Set();
|
|
2789
|
+
langsByDir.set(dir, langs);
|
|
2790
|
+
}
|
|
2791
|
+
langs.add(lang);
|
|
2792
|
+
}
|
|
2793
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
2794
|
+
for (const [dir, langs] of langsByDir) {
|
|
2795
|
+
for (const ancestor of ancestorsOf(dir, root)) {
|
|
2796
|
+
let merged = candidates.get(ancestor);
|
|
2797
|
+
if (!merged) {
|
|
2798
|
+
merged = /* @__PURE__ */ new Set();
|
|
2799
|
+
candidates.set(ancestor, merged);
|
|
2800
|
+
}
|
|
2801
|
+
for (const lang of langs) merged.add(lang);
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
const roots = [];
|
|
2805
|
+
await Promise.all(
|
|
2806
|
+
[...candidates].map(async ([dir, langs]) => {
|
|
2807
|
+
for (const probe of MARKER_PROBES) {
|
|
2808
|
+
if (!LANGS_BY_KIND[probe.kind].some((lang) => langs.has(lang))) continue;
|
|
2809
|
+
const source = await readTextIfPresent(path3.posix.join(dir, probe.file));
|
|
2810
|
+
if (source === void 0) continue;
|
|
2811
|
+
const built = probe.build(dir, source);
|
|
2812
|
+
if (built) roots.push({ dir, kind: probe.kind, ...built });
|
|
2813
|
+
}
|
|
2814
|
+
if (LANGS_BY_KIND.dotnet.some((lang) => langs.has(lang))) {
|
|
2815
|
+
const dotnet = await probeDotnetRoot(dir);
|
|
2816
|
+
if (dotnet) roots.push(dotnet);
|
|
2817
|
+
}
|
|
2818
|
+
})
|
|
2819
|
+
);
|
|
2820
|
+
roots.sort((a, b) => b.dir.length - a.dir.length || a.dir.localeCompare(b.dir));
|
|
2821
|
+
return { projectRoot: root, roots };
|
|
2822
|
+
}
|
|
2823
|
+
function findOwningRoot(structure, file, kinds) {
|
|
2824
|
+
const portable = toPortablePath(file);
|
|
2825
|
+
for (const root of structure.roots) {
|
|
2826
|
+
if (kinds && !kinds.includes(root.kind)) continue;
|
|
2827
|
+
if (portable === root.dir || portable.startsWith(`${root.dir}/`)) return root;
|
|
2828
|
+
}
|
|
2829
|
+
return void 0;
|
|
2830
|
+
}
|
|
2831
|
+
function derivePackageFromLayout(filePath) {
|
|
2832
|
+
const portable = toPortablePath(filePath);
|
|
2833
|
+
const packagesIdx = portable.indexOf("/packages/");
|
|
2834
|
+
if (packagesIdx !== -1) {
|
|
2835
|
+
const segment = portable.slice(packagesIdx + "/packages/".length).split("/")[0];
|
|
2836
|
+
if (segment) return `@wrongstack/${segment}`;
|
|
2837
|
+
}
|
|
2838
|
+
const appsIdx = portable.indexOf("/apps/");
|
|
2435
2839
|
if (appsIdx !== -1) {
|
|
2436
|
-
const
|
|
2437
|
-
|
|
2438
|
-
return seg ? `app:${seg}` : void 0;
|
|
2840
|
+
const segment = portable.slice(appsIdx + "/apps/".length).split("/")[0];
|
|
2841
|
+
if (segment) return `app:${segment}`;
|
|
2439
2842
|
}
|
|
2440
2843
|
return void 0;
|
|
2441
2844
|
}
|
|
2442
|
-
function
|
|
2443
|
-
|
|
2444
|
-
const
|
|
2445
|
-
|
|
2845
|
+
function pythonPackageLabel(structure, file, initDirs) {
|
|
2846
|
+
const portable = toPortablePath(file);
|
|
2847
|
+
const dir = path3.posix.dirname(portable);
|
|
2848
|
+
if (!initDirs.has(dir)) return void 0;
|
|
2849
|
+
const segments = [];
|
|
2850
|
+
let current = dir;
|
|
2851
|
+
while (initDirs.has(current) && current.length > structure.projectRoot.length) {
|
|
2852
|
+
segments.unshift(path3.posix.basename(current));
|
|
2853
|
+
current = path3.posix.dirname(current);
|
|
2854
|
+
}
|
|
2855
|
+
return segments.length > 0 ? `py:${segments.join(".")}` : void 0;
|
|
2856
|
+
}
|
|
2857
|
+
function assignPackageLabels(structure, files) {
|
|
2858
|
+
const initDirs = /* @__PURE__ */ new Set();
|
|
2859
|
+
for (const file of files) {
|
|
2860
|
+
const portable = toPortablePath(file);
|
|
2861
|
+
if (path3.posix.basename(portable) === "__init__.py") {
|
|
2862
|
+
initDirs.add(path3.posix.dirname(portable));
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
const labels = /* @__PURE__ */ new Map();
|
|
2866
|
+
for (const file of files) {
|
|
2867
|
+
const portable = toPortablePath(file);
|
|
2868
|
+
const lang = detectLang(portable);
|
|
2869
|
+
if (lang === "go") {
|
|
2870
|
+
const owner2 = findOwningRoot(structure, portable, ["go"]);
|
|
2871
|
+
const dir = path3.posix.dirname(portable);
|
|
2872
|
+
if (owner2?.importPath) {
|
|
2873
|
+
const relative2 = path3.posix.relative(owner2.dir, dir);
|
|
2874
|
+
labels.set(file, relative2 ? `${owner2.importPath}/${relative2}` : owner2.importPath);
|
|
2875
|
+
} else {
|
|
2876
|
+
labels.set(file, `go:${path3.posix.relative(structure.projectRoot, dir) || "."}`);
|
|
2877
|
+
}
|
|
2878
|
+
continue;
|
|
2879
|
+
}
|
|
2880
|
+
if (lang === "py") {
|
|
2881
|
+
const dotted = pythonPackageLabel(structure, portable, initDirs);
|
|
2882
|
+
if (dotted) {
|
|
2883
|
+
labels.set(file, dotted);
|
|
2884
|
+
continue;
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
const owner = findOwningRoot(structure, portable);
|
|
2888
|
+
const label = owner?.name ?? derivePackageFromLayout(portable) ?? "(root)";
|
|
2889
|
+
labels.set(file, label);
|
|
2890
|
+
}
|
|
2891
|
+
return labels;
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
// src/codebase-index/writer-graph-helpers.ts
|
|
2895
|
+
function createPackageLabeller(stored) {
|
|
2896
|
+
return (file) => stored.get(file) ?? derivePackageFromLayout(file) ?? "(root)";
|
|
2446
2897
|
}
|
|
2447
|
-
function buildPackageGraphNodes(fileCounts, files) {
|
|
2898
|
+
function buildPackageGraphNodes(fileCounts, files, packageOf) {
|
|
2448
2899
|
const pkgNodes = /* @__PURE__ */ new Map();
|
|
2449
2900
|
const fileToPkg = /* @__PURE__ */ new Map();
|
|
2450
2901
|
for (const { file, n } of fileCounts) {
|
|
2451
|
-
const pkg =
|
|
2902
|
+
const pkg = packageOf(file);
|
|
2452
2903
|
fileToPkg.set(file, pkg);
|
|
2453
2904
|
const node = pkgNodes.get(pkg);
|
|
2454
2905
|
if (node) {
|
|
@@ -2465,7 +2916,7 @@ function buildPackageGraphNodes(fileCounts, files) {
|
|
|
2465
2916
|
}
|
|
2466
2917
|
}
|
|
2467
2918
|
for (const { file } of files) {
|
|
2468
|
-
const pkg =
|
|
2919
|
+
const pkg = packageOf(file);
|
|
2469
2920
|
fileToPkg.set(file, pkg);
|
|
2470
2921
|
const node = pkgNodes.get(pkg);
|
|
2471
2922
|
if (node) {
|
|
@@ -2483,7 +2934,7 @@ function buildPackageGraphNodes(fileCounts, files) {
|
|
|
2483
2934
|
}
|
|
2484
2935
|
return { pkgNodes, fileToPkg };
|
|
2485
2936
|
}
|
|
2486
|
-
function buildFileGraphNodeState(pkgSyms, localFiles) {
|
|
2937
|
+
function buildFileGraphNodeState(pkgSyms, localFiles, packageOf) {
|
|
2487
2938
|
const fileNodes = /* @__PURE__ */ new Map();
|
|
2488
2939
|
const symToFile = /* @__PURE__ */ new Map();
|
|
2489
2940
|
const fileStats = /* @__PURE__ */ new Map();
|
|
@@ -2502,7 +2953,7 @@ function buildFileGraphNodeState(pkgSyms, localFiles) {
|
|
|
2502
2953
|
id: `file:${file}`,
|
|
2503
2954
|
label: file.replace(/\\/g, "/").split("/").pop() ?? file,
|
|
2504
2955
|
kind: "file",
|
|
2505
|
-
package:
|
|
2956
|
+
package: packageOf(file),
|
|
2506
2957
|
file,
|
|
2507
2958
|
symbolCount: stats?.count ?? 0,
|
|
2508
2959
|
lang: stats?.lang,
|
|
@@ -2514,7 +2965,7 @@ function buildFileGraphNodeState(pkgSyms, localFiles) {
|
|
|
2514
2965
|
}
|
|
2515
2966
|
return { fileNodes, symToFile, fileStats, ensureFileNode };
|
|
2516
2967
|
}
|
|
2517
|
-
function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
|
|
2968
|
+
function buildSymbolGraphNodes(symById, relatedIds, fileFilter, packageOf) {
|
|
2518
2969
|
return [...relatedIds].map((id) => symById.get(id)).filter((symbol) => symbol !== void 0).sort((a, b) => {
|
|
2519
2970
|
const aExternal = a.file === fileFilter ? 0 : 1;
|
|
2520
2971
|
const bExternal = b.file === fileFilter ? 0 : 1;
|
|
@@ -2526,7 +2977,7 @@ function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
|
|
|
2526
2977
|
symbolId: s.id,
|
|
2527
2978
|
symbolKind: s.kind,
|
|
2528
2979
|
file: s.file,
|
|
2529
|
-
package:
|
|
2980
|
+
package: packageOf(s.file),
|
|
2530
2981
|
lang: s.lang,
|
|
2531
2982
|
line: s.line,
|
|
2532
2983
|
signature: s.signature,
|
|
@@ -2534,29 +2985,6 @@ function buildSymbolGraphNodes(symById, relatedIds, fileFilter) {
|
|
|
2534
2985
|
external: s.file !== fileFilter
|
|
2535
2986
|
}));
|
|
2536
2987
|
}
|
|
2537
|
-
function resolveRelativeImport(fromFile, moduleName, indexedFiles) {
|
|
2538
|
-
if (!moduleName.startsWith(".")) return void 0;
|
|
2539
|
-
const normalizedFrom = fromFile.replace(/\\/g, "/");
|
|
2540
|
-
const absolute = path2.posix.normalize(
|
|
2541
|
-
path2.posix.join(path2.posix.dirname(normalizedFrom), moduleName)
|
|
2542
|
-
);
|
|
2543
|
-
const extension = path2.posix.extname(absolute);
|
|
2544
|
-
const base = extension ? absolute.slice(0, -extension.length) : absolute;
|
|
2545
|
-
const candidates = [
|
|
2546
|
-
absolute,
|
|
2547
|
-
...[".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"].map((ext) => `${base}${ext}`),
|
|
2548
|
-
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path2.posix.join(absolute, `index${ext}`)),
|
|
2549
|
-
...[".ts", ".tsx", ".js", ".jsx"].map((ext) => path2.posix.join(base, `index${ext}`))
|
|
2550
|
-
];
|
|
2551
|
-
const indexedByPortablePath = new Map(
|
|
2552
|
-
[...indexedFiles].map((file) => [file.replace(/\\/g, "/").toLocaleLowerCase(), file])
|
|
2553
|
-
);
|
|
2554
|
-
for (const candidate of candidates) {
|
|
2555
|
-
const indexed = indexedByPortablePath.get(candidate.toLocaleLowerCase());
|
|
2556
|
-
if (indexed) return indexed;
|
|
2557
|
-
}
|
|
2558
|
-
return void 0;
|
|
2559
|
-
}
|
|
2560
2988
|
function addWeightedEdge(edgeMap, source, target, callType, weight) {
|
|
2561
2989
|
const key = `${source}\0${target}`;
|
|
2562
2990
|
let edge = edgeMap.get(key);
|
|
@@ -2597,11 +3025,146 @@ function mapWriterRefRow(row) {
|
|
|
2597
3025
|
toName: row.to_name,
|
|
2598
3026
|
toId: row.to_id ?? void 0,
|
|
2599
3027
|
callType: row.call_type,
|
|
2600
|
-
line: row.line
|
|
3028
|
+
line: row.line,
|
|
3029
|
+
// `lang`/`module`/`to_file` are absent from the narrower column lists some
|
|
3030
|
+
// queries select; `undefined` keeps those rows valid Refs.
|
|
3031
|
+
lang: row.lang || void 0,
|
|
3032
|
+
module: row.module ?? void 0,
|
|
3033
|
+
toFile: row.to_file ?? void 0
|
|
2601
3034
|
};
|
|
2602
3035
|
}
|
|
2603
3036
|
|
|
2604
3037
|
// src/codebase-index/writer-graph-reader.ts
|
|
3038
|
+
var MAX_SQL_VARS = 900;
|
|
3039
|
+
function chunkedIdQuery(stmt, ids, buildSql, extraArgs = []) {
|
|
3040
|
+
const results = [];
|
|
3041
|
+
for (let start = 0; start < ids.length; start += MAX_SQL_VARS) {
|
|
3042
|
+
const chunk = ids.slice(start, start + MAX_SQL_VARS);
|
|
3043
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
3044
|
+
const sql = buildSql(placeholders);
|
|
3045
|
+
results.push(...stmt(sql).all(...chunk, ...extraArgs));
|
|
3046
|
+
}
|
|
3047
|
+
return results;
|
|
3048
|
+
}
|
|
3049
|
+
function chunkedIdScalar(stmt, ids, buildSql, extraArgs = []) {
|
|
3050
|
+
let total = 0;
|
|
3051
|
+
for (let start = 0; start < ids.length; start += MAX_SQL_VARS) {
|
|
3052
|
+
const chunk = ids.slice(start, start + MAX_SQL_VARS);
|
|
3053
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
3054
|
+
const sql = buildSql(placeholders);
|
|
3055
|
+
const rows = stmt(sql).all(...chunk, ...extraArgs);
|
|
3056
|
+
total += rows[0]?.n ?? 0;
|
|
3057
|
+
}
|
|
3058
|
+
return total;
|
|
3059
|
+
}
|
|
3060
|
+
function mapCallSiteRow(row) {
|
|
3061
|
+
return {
|
|
3062
|
+
symbol: {
|
|
3063
|
+
id: row.sym_id,
|
|
3064
|
+
name: row.sym_name,
|
|
3065
|
+
kind: row.sym_kind,
|
|
3066
|
+
lang: row.sym_lang,
|
|
3067
|
+
file: row.sym_file,
|
|
3068
|
+
line: row.sym_line,
|
|
3069
|
+
signature: row.sym_signature
|
|
3070
|
+
},
|
|
3071
|
+
callType: row.call_type,
|
|
3072
|
+
line: row.ref_line
|
|
3073
|
+
};
|
|
3074
|
+
}
|
|
3075
|
+
function resolveSymbolIds(stmt, symbolName, file) {
|
|
3076
|
+
const baseSql = file ? `SELECT id FROM symbols WHERE name = ? AND file = ? ORDER BY id` : `SELECT id FROM symbols WHERE name = ? ORDER BY id`;
|
|
3077
|
+
const args = file ? [symbolName, file] : [symbolName];
|
|
3078
|
+
const rows = stmt(baseSql).all(...args);
|
|
3079
|
+
return rows.map((r) => r.id);
|
|
3080
|
+
}
|
|
3081
|
+
function findIncomingCallsByName(stmt, symbolName, file, limit) {
|
|
3082
|
+
const targetIds = resolveSymbolIds(stmt, symbolName, file);
|
|
3083
|
+
if (targetIds.length === 0) return { calls: [], symbolFound: false, ambiguous: false, totalMatches: 0 };
|
|
3084
|
+
let matchIds = targetIds;
|
|
3085
|
+
let ambiguous = false;
|
|
3086
|
+
if (file !== void 0) {
|
|
3087
|
+
const allNamedIds = resolveSymbolIds(stmt, symbolName, void 0);
|
|
3088
|
+
if (allNamedIds.length > targetIds.length) {
|
|
3089
|
+
matchIds = allNamedIds;
|
|
3090
|
+
ambiguous = true;
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
const useFallback = !file;
|
|
3094
|
+
const rows = chunkedIdQuery(
|
|
3095
|
+
stmt,
|
|
3096
|
+
matchIds,
|
|
3097
|
+
(ph) => `SELECT
|
|
3098
|
+
s.id AS sym_id,
|
|
3099
|
+
s.name AS sym_name,
|
|
3100
|
+
s.kind AS sym_kind,
|
|
3101
|
+
s.lang AS sym_lang,
|
|
3102
|
+
s.file AS sym_file,
|
|
3103
|
+
s.line AS sym_line,
|
|
3104
|
+
s.signature AS sym_signature,
|
|
3105
|
+
r.call_type,
|
|
3106
|
+
r.line AS ref_line
|
|
3107
|
+
FROM refs r
|
|
3108
|
+
JOIN symbols s ON s.id = r.from_id
|
|
3109
|
+
WHERE r.to_id IN (${ph})
|
|
3110
|
+
ORDER BY r.line, r.id`,
|
|
3111
|
+
[]
|
|
3112
|
+
);
|
|
3113
|
+
if (useFallback) {
|
|
3114
|
+
const fallbackRows = stmt(
|
|
3115
|
+
`SELECT
|
|
3116
|
+
s.id AS sym_id,
|
|
3117
|
+
s.name AS sym_name,
|
|
3118
|
+
s.kind AS sym_kind,
|
|
3119
|
+
s.lang AS sym_lang,
|
|
3120
|
+
s.file AS sym_file,
|
|
3121
|
+
s.line AS sym_line,
|
|
3122
|
+
s.signature AS sym_signature,
|
|
3123
|
+
r.call_type,
|
|
3124
|
+
r.line AS ref_line
|
|
3125
|
+
FROM refs r
|
|
3126
|
+
JOIN symbols s ON s.id = r.from_id
|
|
3127
|
+
WHERE r.to_id IS NULL AND r.to_name = ?
|
|
3128
|
+
ORDER BY r.line, r.id`
|
|
3129
|
+
).all(symbolName);
|
|
3130
|
+
rows.push(...fallbackRows);
|
|
3131
|
+
}
|
|
3132
|
+
rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
|
|
3133
|
+
const allCalls = rows.map(mapCallSiteRow);
|
|
3134
|
+
return { calls: allCalls.slice(0, limit), symbolFound: true, ambiguous, totalMatches: allCalls.length };
|
|
3135
|
+
}
|
|
3136
|
+
function findOutgoingCallsByName(stmt, symbolName, file, limit) {
|
|
3137
|
+
const sourceIds = resolveSymbolIds(stmt, symbolName, file);
|
|
3138
|
+
if (sourceIds.length === 0) return { calls: [], symbolFound: false, unresolvedCount: 0, totalMatches: 0 };
|
|
3139
|
+
const unresolvedCount = chunkedIdScalar(
|
|
3140
|
+
stmt,
|
|
3141
|
+
sourceIds,
|
|
3142
|
+
(ph) => `SELECT COUNT(*) AS n FROM refs WHERE from_id IN (${ph}) AND to_id IS NULL`
|
|
3143
|
+
);
|
|
3144
|
+
const rows = chunkedIdQuery(
|
|
3145
|
+
stmt,
|
|
3146
|
+
sourceIds,
|
|
3147
|
+
(ph) => `SELECT
|
|
3148
|
+
s.id AS sym_id,
|
|
3149
|
+
s.name AS sym_name,
|
|
3150
|
+
s.kind AS sym_kind,
|
|
3151
|
+
s.lang AS sym_lang,
|
|
3152
|
+
s.file AS sym_file,
|
|
3153
|
+
s.line AS sym_line,
|
|
3154
|
+
s.signature AS sym_signature,
|
|
3155
|
+
r.call_type,
|
|
3156
|
+
r.line AS ref_line
|
|
3157
|
+
FROM refs r
|
|
3158
|
+
JOIN symbols s ON s.id = r.to_id
|
|
3159
|
+
WHERE r.from_id IN (${ph})
|
|
3160
|
+
AND r.to_id IS NOT NULL -- INNER JOIN already excludes NULL to_id; this is defensive belt-and-suspenders
|
|
3161
|
+
ORDER BY r.line, r.id`,
|
|
3162
|
+
[]
|
|
3163
|
+
);
|
|
3164
|
+
rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
|
|
3165
|
+
const calls = rows.map(mapCallSiteRow).slice(0, limit);
|
|
3166
|
+
return { calls, symbolFound: true, unresolvedCount, totalMatches: rows.length };
|
|
3167
|
+
}
|
|
2605
3168
|
function findRefsToWithStatement(stmt, symbolId) {
|
|
2606
3169
|
return stmt(
|
|
2607
3170
|
"SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE to_id = ? OR to_name = (SELECT name FROM symbols WHERE id = ?)"
|
|
@@ -2615,7 +3178,8 @@ function findRefsFromWithStatement(stmt, symbolId) {
|
|
|
2615
3178
|
function getPackageGraphWithStatement(stmt) {
|
|
2616
3179
|
const fileCounts = stmt("SELECT file, COUNT(*) AS n FROM symbols GROUP BY file").all();
|
|
2617
3180
|
const files = stmt("SELECT DISTINCT file FROM files").all();
|
|
2618
|
-
const
|
|
3181
|
+
const packageOf = readPackageLabeller(stmt);
|
|
3182
|
+
const { pkgNodes, fileToPkg } = buildPackageGraphNodes(fileCounts, files, packageOf);
|
|
2619
3183
|
const refRows = stmt(
|
|
2620
3184
|
`SELECT r.call_type, sf.file AS from_file, st.file AS to_file, COUNT(*) AS n
|
|
2621
3185
|
FROM refs r
|
|
@@ -2626,32 +3190,42 @@ function getPackageGraphWithStatement(stmt) {
|
|
|
2626
3190
|
).all();
|
|
2627
3191
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
2628
3192
|
for (const r of refRows) {
|
|
2629
|
-
const fromPkg = fileToPkg.get(r.from_file) ??
|
|
2630
|
-
const toPkg = fileToPkg.get(r.to_file) ??
|
|
3193
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? packageOf(r.from_file);
|
|
3194
|
+
const toPkg = fileToPkg.get(r.to_file) ?? packageOf(r.to_file);
|
|
2631
3195
|
if (fromPkg === toPkg) continue;
|
|
2632
3196
|
const n = Number(r.n) || 0;
|
|
2633
3197
|
addWeightedEdge(edgeMap, fromPkg, toPkg, r.call_type, n);
|
|
2634
3198
|
}
|
|
2635
3199
|
const importRows = stmt(
|
|
2636
|
-
`SELECT
|
|
3200
|
+
`SELECT s.file AS from_file,
|
|
3201
|
+
COALESCE(r.to_file, st.file) AS to_file,
|
|
3202
|
+
COUNT(*) AS n
|
|
2637
3203
|
FROM refs r
|
|
2638
3204
|
JOIN symbols s ON s.id = r.from_id
|
|
3205
|
+
LEFT JOIN symbols st ON st.id = r.to_id
|
|
2639
3206
|
WHERE r.call_type = 'import'
|
|
2640
|
-
|
|
3207
|
+
AND COALESCE(r.to_file, st.file) IS NOT NULL
|
|
3208
|
+
GROUP BY s.file, COALESCE(r.to_file, st.file)`
|
|
2641
3209
|
).all();
|
|
2642
3210
|
for (const r of importRows) {
|
|
2643
|
-
const fromPkg = fileToPkg.get(r.from_file) ??
|
|
2644
|
-
const toPkg =
|
|
2645
|
-
if (
|
|
3211
|
+
const fromPkg = fileToPkg.get(r.from_file) ?? packageOf(r.from_file);
|
|
3212
|
+
const toPkg = fileToPkg.get(r.to_file) ?? packageOf(r.to_file);
|
|
3213
|
+
if (fromPkg === toPkg || !pkgNodes.has(toPkg)) continue;
|
|
2646
3214
|
const n = Number(r.n) || 0;
|
|
2647
3215
|
addWeightedEdge(edgeMap, fromPkg, toPkg, "import", n);
|
|
2648
3216
|
}
|
|
2649
3217
|
const edges = materializeWeightedEdges(edgeMap, "pkg");
|
|
2650
3218
|
return { nodes: [...pkgNodes.values()], edges };
|
|
2651
3219
|
}
|
|
3220
|
+
function readPackageLabeller(stmt) {
|
|
3221
|
+
const rows = stmt("SELECT file, package FROM files WHERE package != ''").all();
|
|
3222
|
+
return createPackageLabeller(new Map(rows.map((row) => [row.file, row.package])));
|
|
3223
|
+
}
|
|
2652
3224
|
function getFileGraphWithStatement(stmt, packageFilter) {
|
|
2653
3225
|
const allFiles = stmt("SELECT DISTINCT file FROM symbols").all();
|
|
2654
|
-
const
|
|
3226
|
+
const packageOf = readPackageLabeller(stmt);
|
|
3227
|
+
const langOf = (file) => detectLang(file) ?? "other";
|
|
3228
|
+
const pkgFilePaths = allFiles.filter((f) => packageOf(f.file) === packageFilter).map((f) => f.file);
|
|
2655
3229
|
const localFiles = new Set(pkgFilePaths);
|
|
2656
3230
|
if (localFiles.size === 0) return { nodes: [], edges: [] };
|
|
2657
3231
|
const filePlaceholders = [...localFiles].map(() => "?").join(",");
|
|
@@ -2660,9 +3234,9 @@ function getFileGraphWithStatement(stmt, packageFilter) {
|
|
|
2660
3234
|
).all(...pkgFilePaths);
|
|
2661
3235
|
const { fileNodes, symToFile, fileStats, ensureFileNode } = buildFileGraphNodeState(
|
|
2662
3236
|
pkgSyms,
|
|
2663
|
-
localFiles
|
|
3237
|
+
localFiles,
|
|
3238
|
+
packageOf
|
|
2664
3239
|
);
|
|
2665
|
-
const indexedFiles = new Set(allFiles.map((f) => f.file));
|
|
2666
3240
|
const refRows = stmt(
|
|
2667
3241
|
`SELECT r.from_id, r.to_id, r.call_type, COUNT(*) AS n
|
|
2668
3242
|
FROM refs r
|
|
@@ -2685,7 +3259,7 @@ function getFileGraphWithStatement(stmt, packageFilter) {
|
|
|
2685
3259
|
for (const x of extras) {
|
|
2686
3260
|
symToFile.set(x.id, x.file);
|
|
2687
3261
|
if (!fileStats.has(x.file)) {
|
|
2688
|
-
fileStats.set(x.file, { count: 0, lang:
|
|
3262
|
+
fileStats.set(x.file, { count: 0, lang: langOf(x.file) });
|
|
2689
3263
|
}
|
|
2690
3264
|
}
|
|
2691
3265
|
}
|
|
@@ -2702,17 +3276,22 @@ function getFileGraphWithStatement(stmt, packageFilter) {
|
|
|
2702
3276
|
addWeightedEdge(edgeMap, fromFile, toFile, r.call_type, n);
|
|
2703
3277
|
}
|
|
2704
3278
|
const importRows = stmt(
|
|
2705
|
-
`SELECT r.from_id, r.
|
|
3279
|
+
`SELECT r.from_id, COALESCE(r.to_file, st.file) AS to_file, COUNT(*) AS n
|
|
2706
3280
|
FROM refs r
|
|
3281
|
+
LEFT JOIN symbols st ON st.id = r.to_id
|
|
2707
3282
|
WHERE r.call_type = 'import'
|
|
3283
|
+
AND COALESCE(r.to_file, st.file) IS NOT NULL
|
|
2708
3284
|
AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
|
|
2709
|
-
GROUP BY r.from_id, r.
|
|
3285
|
+
GROUP BY r.from_id, COALESCE(r.to_file, st.file)`
|
|
2710
3286
|
).all(...pkgFilePaths);
|
|
2711
3287
|
for (const r of importRows) {
|
|
2712
3288
|
const fromFile = symToFile.get(r.from_id);
|
|
2713
3289
|
if (!fromFile || !localFiles.has(fromFile)) continue;
|
|
2714
|
-
const toFile =
|
|
3290
|
+
const toFile = r.to_file;
|
|
2715
3291
|
if (!toFile || fromFile === toFile) continue;
|
|
3292
|
+
if (!fileStats.has(toFile)) {
|
|
3293
|
+
fileStats.set(toFile, { count: 0, lang: langOf(toFile) });
|
|
3294
|
+
}
|
|
2716
3295
|
ensureFileNode(fromFile);
|
|
2717
3296
|
ensureFileNode(toFile);
|
|
2718
3297
|
const n = Number(r.n) || 0;
|
|
@@ -2762,7 +3341,7 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
|
|
|
2762
3341
|
).all(...missingIds);
|
|
2763
3342
|
for (const s of extras) symById.set(s.id, s);
|
|
2764
3343
|
}
|
|
2765
|
-
const nodes = buildSymbolGraphNodes(symById, relatedIds, fileFilter);
|
|
3344
|
+
const nodes = buildSymbolGraphNodes(symById, relatedIds, fileFilter, readPackageLabeller(stmt));
|
|
2766
3345
|
return { nodes, edges };
|
|
2767
3346
|
}
|
|
2768
3347
|
|
|
@@ -2784,7 +3363,7 @@ function assignRefsToSymbols(refs, symbols) {
|
|
|
2784
3363
|
}
|
|
2785
3364
|
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
2786
3365
|
if (!owner || owner.id <= 0) continue;
|
|
2787
|
-
const key = `${owner.id}:${ref.toName}:${ref.callType}`;
|
|
3366
|
+
const key = `${owner.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
2788
3367
|
if (seen.has(key)) continue;
|
|
2789
3368
|
seen.add(key);
|
|
2790
3369
|
assigned.push({ ...ref, fromId: owner.id });
|
|
@@ -2830,7 +3409,11 @@ var CORE_TABLES_SQL = `
|
|
|
2830
3409
|
lang TEXT NOT NULL,
|
|
2831
3410
|
mtime_ms INTEGER NOT NULL,
|
|
2832
3411
|
symbol_count INTEGER NOT NULL DEFAULT 0,
|
|
2833
|
-
last_indexed INTEGER NOT NULL
|
|
3412
|
+
last_indexed INTEGER NOT NULL,
|
|
3413
|
+
-- Code Atlas grouping label, computed at index time from the ecosystem's
|
|
3414
|
+
-- own manifests (package.json, go.mod, Cargo.toml, \u2026). Stored rather than
|
|
3415
|
+
-- re-derived per query because the evidence lives on disk, not in the DB.
|
|
3416
|
+
package TEXT NOT NULL DEFAULT ''
|
|
2834
3417
|
);
|
|
2835
3418
|
CREATE TABLE IF NOT EXISTS symbols (
|
|
2836
3419
|
id INTEGER PRIMARY KEY,
|
|
@@ -2847,6 +3430,9 @@ var CORE_TABLES_SQL = `
|
|
|
2847
3430
|
file_fk TEXT NOT NULL
|
|
2848
3431
|
);
|
|
2849
3432
|
`;
|
|
3433
|
+
var FILE_INDEX_SQL = [
|
|
3434
|
+
"CREATE INDEX IF NOT EXISTS idx_f_package ON files(package)"
|
|
3435
|
+
];
|
|
2850
3436
|
var SYMBOL_INDEX_SQL = [
|
|
2851
3437
|
"CREATE INDEX IF NOT EXISTS idx_s_name ON symbols(name)",
|
|
2852
3438
|
"CREATE INDEX IF NOT EXISTS idx_s_kind ON symbols(kind)",
|
|
@@ -2863,15 +3449,32 @@ var REFS_TABLE_SQL = `
|
|
|
2863
3449
|
to_name TEXT NOT NULL,
|
|
2864
3450
|
to_id INTEGER,
|
|
2865
3451
|
call_type TEXT NOT NULL,
|
|
2866
|
-
line INTEGER NOT NULL
|
|
3452
|
+
line INTEGER NOT NULL,
|
|
3453
|
+
lang TEXT NOT NULL DEFAULT '',
|
|
3454
|
+
module TEXT,
|
|
3455
|
+
to_file TEXT
|
|
2867
3456
|
);
|
|
2868
3457
|
`;
|
|
2869
3458
|
var REFS_INDEX_SQL = [
|
|
2870
3459
|
"CREATE INDEX IF NOT EXISTS idx_r_from ON refs(from_id)",
|
|
2871
3460
|
"CREATE INDEX IF NOT EXISTS idx_r_to_id ON refs(to_id)",
|
|
2872
3461
|
"CREATE INDEX IF NOT EXISTS idx_r_to_name ON refs(to_name)",
|
|
2873
|
-
"CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)"
|
|
3462
|
+
"CREATE INDEX IF NOT EXISTS idx_r_call_type ON refs(call_type)",
|
|
3463
|
+
// Name resolution matches (to_name, lang) pairs; the composite keeps the
|
|
3464
|
+
// language-scoped UPDATE from degrading into a scan of every same-named row.
|
|
3465
|
+
"CREATE INDEX IF NOT EXISTS idx_r_to_name_lang ON refs(to_name, lang)",
|
|
3466
|
+
// The post-index module resolution pass groups unresolved import refs by
|
|
3467
|
+
// (module, lang); graph readers then read to_file back.
|
|
3468
|
+
"CREATE INDEX IF NOT EXISTS idx_r_module ON refs(module)",
|
|
3469
|
+
"CREATE INDEX IF NOT EXISTS idx_r_to_file ON refs(to_file)"
|
|
2874
3470
|
];
|
|
3471
|
+
var LANG_FAMILY_TABLE_SQL = `
|
|
3472
|
+
CREATE TABLE IF NOT EXISTS lang_family (
|
|
3473
|
+
lang TEXT PRIMARY KEY,
|
|
3474
|
+
family TEXT NOT NULL
|
|
3475
|
+
);
|
|
3476
|
+
`;
|
|
3477
|
+
var LANG_FAMILY_WILDCARD = "*";
|
|
2875
3478
|
var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
|
|
2876
3479
|
|
|
2877
3480
|
// src/codebase-index/writer-search-helpers.ts
|
|
@@ -3083,16 +3686,70 @@ var IndexStore = class _IndexStore {
|
|
|
3083
3686
|
}
|
|
3084
3687
|
constructor(projectRoot, opts = {}) {
|
|
3085
3688
|
this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
|
|
3086
|
-
|
|
3689
|
+
fs3.mkdirSync(this.indexDir, { recursive: true });
|
|
3087
3690
|
const Database = loadDatabaseSync();
|
|
3088
|
-
this.db = new Database(
|
|
3691
|
+
this.db = new Database(path4.join(this.indexDir, DB_FILE2));
|
|
3089
3692
|
applyIndexStorePragmas(this.db);
|
|
3090
3693
|
this.initSchema();
|
|
3091
3694
|
}
|
|
3092
3695
|
runWithRetry(fn) {
|
|
3093
3696
|
return runSqliteWithRetry(fn);
|
|
3094
3697
|
}
|
|
3095
|
-
|
|
3698
|
+
/**
|
|
3699
|
+
* Mirror the in-process language→family map into SQLite.
|
|
3700
|
+
*
|
|
3701
|
+
* Rewritten on every open rather than only on schema bumps: the mapping is
|
|
3702
|
+
* static lookup data, so a code-side change (a new language, a language
|
|
3703
|
+
* moving families) must take effect without forcing a full reindex.
|
|
3704
|
+
*/
|
|
3705
|
+
seedLangFamilies() {
|
|
3706
|
+
const insert = this.stmt("INSERT OR REPLACE INTO lang_family(lang, family) VALUES (?, ?)");
|
|
3707
|
+
for (const [lang, family] of LANG_FAMILY_ENTRIES) insert.run(lang, family);
|
|
3708
|
+
insert.run("", LANG_FAMILY_WILDCARD);
|
|
3709
|
+
}
|
|
3710
|
+
/**
|
|
3711
|
+
* Add any column the current schema expects but the on-disk table lacks.
|
|
3712
|
+
*
|
|
3713
|
+
* `CREATE TABLE IF NOT EXISTS` silently keeps an existing table's old shape,
|
|
3714
|
+
* and the version check above only rebuilds on a version *mismatch*. That
|
|
3715
|
+
* leaves a real gap: several wstack processes share this database, and while
|
|
3716
|
+
* a version upgrade is rolling out one of them may still be running the
|
|
3717
|
+
* previous build. That older process sees the newer version number, drops the
|
|
3718
|
+
* tables, and recreates them from *its* DDL — without the newer columns —
|
|
3719
|
+
* while the metadata row still reads the new version. Every later query for
|
|
3720
|
+
* one of those columns then fails with `no such column`, and no amount of
|
|
3721
|
+
* reindexing fixes it, because the version numbers already agree.
|
|
3722
|
+
*
|
|
3723
|
+
* Repairing column-by-column makes the schema self-healing from any of those
|
|
3724
|
+
* states. Table and column names are compile-time literals from this module,
|
|
3725
|
+
* never user input.
|
|
3726
|
+
*/
|
|
3727
|
+
repairMissingColumns() {
|
|
3728
|
+
const expected = [
|
|
3729
|
+
{ table: "files", columns: [["package", "TEXT NOT NULL DEFAULT ''"]] },
|
|
3730
|
+
{
|
|
3731
|
+
table: "refs",
|
|
3732
|
+
columns: [
|
|
3733
|
+
["lang", "TEXT NOT NULL DEFAULT ''"],
|
|
3734
|
+
["module", "TEXT"],
|
|
3735
|
+
["to_file", "TEXT"]
|
|
3736
|
+
]
|
|
3737
|
+
}
|
|
3738
|
+
];
|
|
3739
|
+
for (const { table, columns } of expected) {
|
|
3740
|
+
const present = new Set(
|
|
3741
|
+
this.db.prepare(`PRAGMA table_info(${table})`).all().flatMap(
|
|
3742
|
+
(row) => typeof row.name === "string" ? [row.name] : []
|
|
3743
|
+
)
|
|
3744
|
+
);
|
|
3745
|
+
if (present.size === 0) continue;
|
|
3746
|
+
for (const [name, type] of columns) {
|
|
3747
|
+
if (present.has(name)) continue;
|
|
3748
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${name} ${type}`);
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
initSchema() {
|
|
3096
3753
|
this.db.exec(METADATA_TABLE_SQL);
|
|
3097
3754
|
const storedRows = this.stmt("SELECT value FROM metadata WHERE key = ?").all("version");
|
|
3098
3755
|
const storedVersion = storedRows.length ? Number(storedRows[0]?.value) : null;
|
|
@@ -3114,9 +3771,13 @@ var IndexStore = class _IndexStore {
|
|
|
3114
3771
|
);
|
|
3115
3772
|
}
|
|
3116
3773
|
this.db.exec(CORE_TABLES_SQL);
|
|
3117
|
-
for (const sql of SYMBOL_INDEX_SQL) this.db.exec(sql);
|
|
3118
3774
|
this.db.exec(REFS_TABLE_SQL);
|
|
3775
|
+
this.repairMissingColumns();
|
|
3776
|
+
for (const sql of FILE_INDEX_SQL) this.db.exec(sql);
|
|
3777
|
+
for (const sql of SYMBOL_INDEX_SQL) this.db.exec(sql);
|
|
3119
3778
|
for (const sql of REFS_INDEX_SQL) this.db.exec(sql);
|
|
3779
|
+
this.db.exec(LANG_FAMILY_TABLE_SQL);
|
|
3780
|
+
this.seedLangFamilies();
|
|
3120
3781
|
try {
|
|
3121
3782
|
this.db.exec(SYMBOLS_FTS_SQL);
|
|
3122
3783
|
this.ftsAvailable = true;
|
|
@@ -3151,6 +3812,18 @@ var IndexStore = class _IndexStore {
|
|
|
3151
3812
|
static NEXT_SYMBOL_ID_KEY = "next_symbol_id";
|
|
3152
3813
|
/** Stay under typical SQLite SQLITE_MAX_VARIABLE_NUMBER (often 999). */
|
|
3153
3814
|
static MAX_SQL_VARS = 900;
|
|
3815
|
+
/**
|
|
3816
|
+
* Correlated predicate: the ref in `refs` and the candidate symbol aliased
|
|
3817
|
+
* `sym` belong to the same language family — or the ref carries no language,
|
|
3818
|
+
* in which case the wildcard bind matches everything.
|
|
3819
|
+
*
|
|
3820
|
+
* Each textual occurrence consumes one `?` bind of {@link LANG_FAMILY_WILDCARD}.
|
|
3821
|
+
*/
|
|
3822
|
+
static FAMILY_MATCH_SQL = `(
|
|
3823
|
+
(SELECT family FROM lang_family WHERE lang = refs.lang) = ?
|
|
3824
|
+
OR (SELECT family FROM lang_family WHERE lang = sym.lang)
|
|
3825
|
+
= (SELECT family FROM lang_family WHERE lang = refs.lang)
|
|
3826
|
+
)`;
|
|
3154
3827
|
/**
|
|
3155
3828
|
* Ensure `metadata.next_symbol_id` exists. Safe to call outside a write
|
|
3156
3829
|
* transaction on open; the first concurrent writer under BEGIN IMMEDIATE
|
|
@@ -3214,9 +3887,12 @@ var IndexStore = class _IndexStore {
|
|
|
3214
3887
|
const placeholders = chunk.map(() => "?").join(",");
|
|
3215
3888
|
const result = this.stmt(
|
|
3216
3889
|
`UPDATE refs
|
|
3217
|
-
SET to_id = (
|
|
3890
|
+
SET to_id = (
|
|
3891
|
+
SELECT MIN(sym.id) FROM symbols sym
|
|
3892
|
+
WHERE sym.name = refs.to_name AND ${_IndexStore.FAMILY_MATCH_SQL}
|
|
3893
|
+
)
|
|
3218
3894
|
WHERE to_name IN (${placeholders})`
|
|
3219
|
-
).run(...chunk);
|
|
3895
|
+
).run(LANG_FAMILY_WILDCARD, ...chunk);
|
|
3220
3896
|
changes += result.changes ?? 0;
|
|
3221
3897
|
}
|
|
3222
3898
|
return changes;
|
|
@@ -3343,6 +4019,115 @@ var IndexStore = class _IndexStore {
|
|
|
3343
4019
|
getAllFileMetas() {
|
|
3344
4020
|
return getAllFileMetasWithStatement((sql) => this.stmt(sql));
|
|
3345
4021
|
}
|
|
4022
|
+
// ─── Project structure & module resolution ──────────────────────────────────
|
|
4023
|
+
/** Store the Code Atlas grouping label for each indexed file. */
|
|
4024
|
+
setFilePackages(entries) {
|
|
4025
|
+
if (entries.size === 0) return;
|
|
4026
|
+
this.runWithRetry(() => {
|
|
4027
|
+
const update = this.stmt("UPDATE files SET package = ? WHERE file = ?");
|
|
4028
|
+
for (const [file, label] of entries) update.run(label, file);
|
|
4029
|
+
});
|
|
4030
|
+
}
|
|
4031
|
+
/**
|
|
4032
|
+
* Every indexed `namespace`/`module` declaration, for ecosystems whose import
|
|
4033
|
+
* specifiers name a namespace rather than a path (C#, PHP, Elixir, Haskell).
|
|
4034
|
+
* Ordered so the resolver's choice among duplicate declarations is stable.
|
|
4035
|
+
*/
|
|
4036
|
+
getNamespaceDeclarations() {
|
|
4037
|
+
return this.stmt(
|
|
4038
|
+
`SELECT name, file FROM symbols WHERE kind = 'namespace' ORDER BY file, id`
|
|
4039
|
+
).all();
|
|
4040
|
+
}
|
|
4041
|
+
/** `file → package` for every indexed file that has a label. */
|
|
4042
|
+
getFilePackages() {
|
|
4043
|
+
const rows = this.stmt("SELECT file, package FROM files WHERE package != ''").all();
|
|
4044
|
+
return new Map(rows.map((row) => [row.file, row.package]));
|
|
4045
|
+
}
|
|
4046
|
+
/**
|
|
4047
|
+
* Distinct `(fromFile, lang, module)` triples needing module resolution.
|
|
4048
|
+
*
|
|
4049
|
+
* Distinct rather than per-ref because resolution depends only on these three
|
|
4050
|
+
* values: a file importing the same module twenty times resolves it once.
|
|
4051
|
+
*/
|
|
4052
|
+
getUnresolvedImports(onlyFiles) {
|
|
4053
|
+
const base = `SELECT DISTINCT s.file AS fromFile, r.lang AS lang, r.module AS module
|
|
4054
|
+
FROM refs r
|
|
4055
|
+
JOIN symbols s ON s.id = r.from_id
|
|
4056
|
+
WHERE r.call_type = 'import' AND r.module IS NOT NULL`;
|
|
4057
|
+
if (!onlyFiles?.length) {
|
|
4058
|
+
return this.stmt(base).all();
|
|
4059
|
+
}
|
|
4060
|
+
const out = [];
|
|
4061
|
+
for (let i = 0; i < onlyFiles.length; i += _IndexStore.MAX_SQL_VARS) {
|
|
4062
|
+
const chunk = onlyFiles.slice(i, i + _IndexStore.MAX_SQL_VARS);
|
|
4063
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
4064
|
+
out.push(
|
|
4065
|
+
...this.stmt(`${base} AND s.file IN (${placeholders})`).all(...chunk)
|
|
4066
|
+
);
|
|
4067
|
+
}
|
|
4068
|
+
return out;
|
|
4069
|
+
}
|
|
4070
|
+
/**
|
|
4071
|
+
* Write resolved import targets back onto `refs.to_file`.
|
|
4072
|
+
*
|
|
4073
|
+
* Applied through a temp table and a single UPDATE: one statement per
|
|
4074
|
+
* resolution would mean thousands of round-trips on a first index.
|
|
4075
|
+
*/
|
|
4076
|
+
applyImportResolutions(resolutions) {
|
|
4077
|
+
if (resolutions.length === 0) return 0;
|
|
4078
|
+
return this.runWithRetry(() => {
|
|
4079
|
+
this.db.exec("DROP TABLE IF EXISTS temp.import_resolution");
|
|
4080
|
+
this.db.exec(
|
|
4081
|
+
`CREATE TEMP TABLE import_resolution (
|
|
4082
|
+
from_file TEXT NOT NULL,
|
|
4083
|
+
lang TEXT NOT NULL,
|
|
4084
|
+
module TEXT NOT NULL,
|
|
4085
|
+
to_file TEXT NOT NULL
|
|
4086
|
+
)`
|
|
4087
|
+
);
|
|
4088
|
+
const chunkSize = Math.max(1, Math.floor(_IndexStore.MAX_SQL_VARS / 4));
|
|
4089
|
+
for (let i = 0; i < resolutions.length; i += chunkSize) {
|
|
4090
|
+
const chunk = resolutions.slice(i, i + chunkSize);
|
|
4091
|
+
const placeholders = chunk.map(() => "(?, ?, ?, ?)").join(", ");
|
|
4092
|
+
const binds = [];
|
|
4093
|
+
for (const entry of chunk) {
|
|
4094
|
+
binds.push(entry.fromFile, entry.lang, entry.module, entry.toFile);
|
|
4095
|
+
}
|
|
4096
|
+
this.stmt(
|
|
4097
|
+
`INSERT INTO temp.import_resolution(from_file, lang, module, to_file)
|
|
4098
|
+
VALUES ${placeholders}`
|
|
4099
|
+
).run(...binds);
|
|
4100
|
+
}
|
|
4101
|
+
this.db.exec(
|
|
4102
|
+
`CREATE INDEX IF NOT EXISTS temp.idx_ir
|
|
4103
|
+
ON import_resolution(module, lang, from_file)`
|
|
4104
|
+
);
|
|
4105
|
+
const result = this.stmt(
|
|
4106
|
+
`UPDATE refs
|
|
4107
|
+
SET to_file = (
|
|
4108
|
+
SELECT ir.to_file
|
|
4109
|
+
FROM temp.import_resolution ir
|
|
4110
|
+
JOIN symbols s ON s.id = refs.from_id
|
|
4111
|
+
WHERE ir.module = refs.module
|
|
4112
|
+
AND ir.lang = refs.lang
|
|
4113
|
+
AND ir.from_file = s.file
|
|
4114
|
+
LIMIT 1
|
|
4115
|
+
)
|
|
4116
|
+
WHERE refs.call_type = 'import'
|
|
4117
|
+
AND refs.module IS NOT NULL
|
|
4118
|
+
AND EXISTS (
|
|
4119
|
+
SELECT 1
|
|
4120
|
+
FROM temp.import_resolution ir
|
|
4121
|
+
JOIN symbols s ON s.id = refs.from_id
|
|
4122
|
+
WHERE ir.module = refs.module
|
|
4123
|
+
AND ir.lang = refs.lang
|
|
4124
|
+
AND ir.from_file = s.file
|
|
4125
|
+
)`
|
|
4126
|
+
).run();
|
|
4127
|
+
this.db.exec("DROP TABLE IF EXISTS temp.import_resolution");
|
|
4128
|
+
return result.changes ?? 0;
|
|
4129
|
+
});
|
|
4130
|
+
}
|
|
3346
4131
|
// ─── Search ──────────────────────────────────────────────────────────────────
|
|
3347
4132
|
search(query, filter, opts) {
|
|
3348
4133
|
const built = this.buildSearchWhere(query, filter);
|
|
@@ -3729,9 +4514,12 @@ var IndexStore = class _IndexStore {
|
|
|
3729
4514
|
* Resolve `to_name` → `to_id` for all refs that have a name but no id.
|
|
3730
4515
|
* Call this after all symbols have been inserted to fill in cross-references.
|
|
3731
4516
|
*
|
|
3732
|
-
*
|
|
3733
|
-
* the
|
|
3734
|
-
*
|
|
4517
|
+
* A match additionally requires the referencing ref and the target symbol to
|
|
4518
|
+
* be in the same {@link LangFamily}. Without that guard a name match is a
|
|
4519
|
+
* cross-language accident waiting to happen — `main`, `New`, `Parse` and
|
|
4520
|
+
* `Config` are declared in most languages at once, and each collision draws a
|
|
4521
|
+
* Code Atlas edge between files that never reference each other. Refs stored
|
|
4522
|
+
* without a language keep the old global behaviour via the `'*'` wildcard row.
|
|
3735
4523
|
*/
|
|
3736
4524
|
resolveRefs() {
|
|
3737
4525
|
return this.runWithRetry(() => {
|
|
@@ -3740,20 +4528,35 @@ var IndexStore = class _IndexStore {
|
|
|
3740
4528
|
`UPDATE refs
|
|
3741
4529
|
SET to_id = s.id
|
|
3742
4530
|
FROM (
|
|
3743
|
-
|
|
3744
|
-
|
|
4531
|
+
SELECT sym.name AS name, lf.family AS family, MIN(sym.id) AS id
|
|
4532
|
+
FROM symbols sym
|
|
4533
|
+
JOIN lang_family lf ON lf.lang = sym.lang
|
|
4534
|
+
GROUP BY sym.name, lf.family
|
|
4535
|
+
UNION ALL
|
|
4536
|
+
SELECT sym.name AS name, '${LANG_FAMILY_WILDCARD}' AS family, MIN(sym.id) AS id
|
|
4537
|
+
FROM symbols sym
|
|
4538
|
+
GROUP BY sym.name
|
|
4539
|
+
) AS s,
|
|
4540
|
+
lang_family AS rf
|
|
3745
4541
|
WHERE refs.to_id IS NULL
|
|
3746
4542
|
AND refs.to_name IS NOT NULL
|
|
3747
|
-
AND
|
|
4543
|
+
AND rf.lang = refs.lang
|
|
4544
|
+
AND s.name = refs.to_name
|
|
4545
|
+
AND s.family = rf.family`
|
|
3748
4546
|
).run();
|
|
3749
4547
|
return result.changes ?? 0;
|
|
3750
4548
|
} catch {
|
|
3751
4549
|
const result = this.stmt(
|
|
3752
4550
|
`UPDATE refs SET to_id = (
|
|
3753
|
-
SELECT id FROM symbols
|
|
4551
|
+
SELECT sym.id FROM symbols sym
|
|
4552
|
+
WHERE sym.name = refs.to_name AND ${_IndexStore.FAMILY_MATCH_SQL}
|
|
4553
|
+
ORDER BY sym.id LIMIT 1
|
|
3754
4554
|
) WHERE to_id IS NULL AND to_name IS NOT NULL
|
|
3755
|
-
AND
|
|
3756
|
-
|
|
4555
|
+
AND EXISTS (
|
|
4556
|
+
SELECT 1 FROM symbols sym
|
|
4557
|
+
WHERE sym.name = refs.to_name AND ${_IndexStore.FAMILY_MATCH_SQL}
|
|
4558
|
+
)`
|
|
4559
|
+
).run(LANG_FAMILY_WILDCARD, LANG_FAMILY_WILDCARD);
|
|
3757
4560
|
return result.changes ?? 0;
|
|
3758
4561
|
}
|
|
3759
4562
|
});
|
|
@@ -3837,6 +4640,20 @@ var IndexStore = class _IndexStore {
|
|
|
3837
4640
|
return false;
|
|
3838
4641
|
}
|
|
3839
4642
|
}
|
|
4643
|
+
/**
|
|
4644
|
+
* Find all symbols that reference the named target symbol (incoming callers).
|
|
4645
|
+
* Accepts a name instead of an id so the agent doesn't need a prior lookup.
|
|
4646
|
+
*/
|
|
4647
|
+
findIncomingCallsByName(symbolName, file, limit = 100) {
|
|
4648
|
+
return findIncomingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
4649
|
+
}
|
|
4650
|
+
/**
|
|
4651
|
+
* Find all symbols that the named source symbol references (outgoing callees).
|
|
4652
|
+
* Accepts a name instead of an id so the agent doesn't need a prior lookup.
|
|
4653
|
+
*/
|
|
4654
|
+
findOutgoingCallsByName(symbolName, file, limit = 100) {
|
|
4655
|
+
return findOutgoingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
4656
|
+
}
|
|
3840
4657
|
/**
|
|
3841
4658
|
* Find all references TO a given symbol (who calls / uses this symbol?).
|
|
3842
4659
|
*/
|
|
@@ -3928,21 +4745,21 @@ var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
|
|
|
3928
4745
|
var PROJECT_INDEX_SERVER_SOCKET_DIR = `wsci-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}`;
|
|
3929
4746
|
var buildIdCache;
|
|
3930
4747
|
function projectIndexServerBuildId(entrypoint) {
|
|
3931
|
-
const file = entrypoint instanceof URL || entrypoint.startsWith("file:") ? fileURLToPath(entrypoint) :
|
|
4748
|
+
const file = entrypoint instanceof URL || entrypoint.startsWith("file:") ? fileURLToPath(entrypoint) : path5.resolve(entrypoint);
|
|
3932
4749
|
try {
|
|
3933
|
-
const stat2 =
|
|
4750
|
+
const stat2 = fs4.statSync(file);
|
|
3934
4751
|
if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat2.mtimeMs && buildIdCache.size === stat2.size) {
|
|
3935
4752
|
return buildIdCache.buildId;
|
|
3936
4753
|
}
|
|
3937
|
-
const buildId = createHash("sha256").update(
|
|
4754
|
+
const buildId = createHash("sha256").update(fs4.readFileSync(file)).digest("hex").slice(0, 24);
|
|
3938
4755
|
buildIdCache = { file, mtimeMs: stat2.mtimeMs, size: stat2.size, buildId };
|
|
3939
4756
|
return buildId;
|
|
3940
4757
|
} catch {
|
|
3941
|
-
return `unreadable:${
|
|
4758
|
+
return `unreadable:${path5.basename(file)}`;
|
|
3942
4759
|
}
|
|
3943
4760
|
}
|
|
3944
4761
|
function normalizeLocalPath(value) {
|
|
3945
|
-
const resolved =
|
|
4762
|
+
const resolved = path5.resolve(value);
|
|
3946
4763
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
3947
4764
|
}
|
|
3948
4765
|
function projectIndexServerKey(projectRoot, indexDir) {
|
|
@@ -3954,11 +4771,11 @@ function projectIndexServerEndpoint(projectRoot, indexDir) {
|
|
|
3954
4771
|
if (process.platform === "win32") {
|
|
3955
4772
|
return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
|
|
3956
4773
|
}
|
|
3957
|
-
return
|
|
4774
|
+
return path5.join(os.tmpdir(), PROJECT_INDEX_SERVER_SOCKET_DIR, `${key}.sock`);
|
|
3958
4775
|
}
|
|
3959
4776
|
function projectIndexServerMetadataPath(projectRoot, indexDir) {
|
|
3960
|
-
return
|
|
3961
|
-
|
|
4777
|
+
return path5.join(
|
|
4778
|
+
path5.resolve(resolveIndexDir(projectRoot, indexDir)),
|
|
3962
4779
|
PROJECT_INDEX_SERVER_METADATA_FILE
|
|
3963
4780
|
);
|
|
3964
4781
|
}
|
|
@@ -3998,7 +4815,7 @@ function resolveProjectIndexDaemonAvailability(projectRoot, indexDir) {
|
|
|
3998
4815
|
for (const rel of ["./project-server.js", "./codebase-index/project-server.js"]) {
|
|
3999
4816
|
try {
|
|
4000
4817
|
const url = new URL(rel, import.meta.url);
|
|
4001
|
-
if (url.protocol === "file:" &&
|
|
4818
|
+
if (url.protocol === "file:" && fs5.existsSync(fileURLToPath2(url))) {
|
|
4002
4819
|
builtUrl = url;
|
|
4003
4820
|
break;
|
|
4004
4821
|
}
|
|
@@ -4255,7 +5072,7 @@ var ProjectServerConnection = class {
|
|
|
4255
5072
|
currentAuthToken() {
|
|
4256
5073
|
if (this.authToken === void 0) {
|
|
4257
5074
|
try {
|
|
4258
|
-
const raw =
|
|
5075
|
+
const raw = fs5.readFileSync(
|
|
4259
5076
|
projectIndexServerMetadataPath(this.projectRoot, this.indexDir),
|
|
4260
5077
|
"utf8"
|
|
4261
5078
|
);
|
|
@@ -4520,7 +5337,7 @@ var ProjectServerConnection = class {
|
|
|
4520
5337
|
if (!url) throw new Error("built codebase-index project server is unavailable");
|
|
4521
5338
|
if (process.platform !== "win32") {
|
|
4522
5339
|
try {
|
|
4523
|
-
|
|
5340
|
+
fs5.rmSync(this.endpoint, { force: true });
|
|
4524
5341
|
} catch {
|
|
4525
5342
|
}
|
|
4526
5343
|
}
|
|
@@ -4544,8 +5361,8 @@ var ProjectServerConnection = class {
|
|
|
4544
5361
|
process.kill(pid);
|
|
4545
5362
|
const metadataPath = projectIndexServerMetadataPath(this.projectRoot, this.indexDir);
|
|
4546
5363
|
try {
|
|
4547
|
-
const metadata = JSON.parse(
|
|
4548
|
-
if (metadata.pid === pid)
|
|
5364
|
+
const metadata = JSON.parse(fs5.readFileSync(metadataPath, "utf8"));
|
|
5365
|
+
if (metadata.pid === pid) fs5.rmSync(metadataPath, { force: true });
|
|
4549
5366
|
} catch {
|
|
4550
5367
|
}
|
|
4551
5368
|
return true;
|
|
@@ -4649,7 +5466,7 @@ import { Worker } from "node:worker_threads";
|
|
|
4649
5466
|
|
|
4650
5467
|
// src/codebase-index/indexer.ts
|
|
4651
5468
|
import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
|
|
4652
|
-
import { execFile
|
|
5469
|
+
import { execFile } from "node:child_process";
|
|
4653
5470
|
import * as fs10 from "node:fs/promises";
|
|
4654
5471
|
import { availableParallelism } from "node:os";
|
|
4655
5472
|
import * as path12 from "node:path";
|
|
@@ -4660,8 +5477,8 @@ import {
|
|
|
4660
5477
|
} from "@wrongstack/core/utils";
|
|
4661
5478
|
|
|
4662
5479
|
// src/codebase-index/gitignore.ts
|
|
4663
|
-
import * as
|
|
4664
|
-
import * as
|
|
5480
|
+
import * as fs6 from "node:fs/promises";
|
|
5481
|
+
import * as path6 from "node:path";
|
|
4665
5482
|
import { compileGlob } from "@wrongstack/core/utils";
|
|
4666
5483
|
function globBody(glob) {
|
|
4667
5484
|
return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
|
|
@@ -4707,7 +5524,7 @@ function compileGitignore(lines) {
|
|
|
4707
5524
|
async function loadGitignoreMatcher(projectRoot) {
|
|
4708
5525
|
let lines = [];
|
|
4709
5526
|
try {
|
|
4710
|
-
const raw = await
|
|
5527
|
+
const raw = await fs6.readFile(path6.join(projectRoot, ".gitignore"), "utf8");
|
|
4711
5528
|
lines = raw.split("\n");
|
|
4712
5529
|
} catch {
|
|
4713
5530
|
}
|
|
@@ -4717,8 +5534,434 @@ async function loadGitignoreMatcher(projectRoot) {
|
|
|
4717
5534
|
// src/codebase-index/indexer.ts
|
|
4718
5535
|
init_languages();
|
|
4719
5536
|
|
|
5537
|
+
// src/codebase-index/module-resolver.ts
|
|
5538
|
+
init_languages();
|
|
5539
|
+
import * as path7 from "node:path";
|
|
5540
|
+
var EXTENSIONS = {
|
|
5541
|
+
js: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".vue", ".svelte"],
|
|
5542
|
+
py: [".py", ".pyi"],
|
|
5543
|
+
rs: [".rs"],
|
|
5544
|
+
jvm: [".java", ".kt", ".scala"],
|
|
5545
|
+
c: [".h", ".hpp", ".hh", ".hxx", ".c", ".cpp", ".cc", ".cxx"],
|
|
5546
|
+
ruby: [".rb"],
|
|
5547
|
+
go: [".go"]
|
|
5548
|
+
};
|
|
5549
|
+
var DIRECTORY_ENTRIES = {
|
|
5550
|
+
js: ["index"],
|
|
5551
|
+
py: ["__init__"],
|
|
5552
|
+
rs: ["mod"],
|
|
5553
|
+
ruby: ["index"]
|
|
5554
|
+
};
|
|
5555
|
+
function normalizeNamespace(value) {
|
|
5556
|
+
return value.replace(/::|[\\/]/g, ".").replace(/^\.+|\.+$/g, "").toLowerCase();
|
|
5557
|
+
}
|
|
5558
|
+
var ModuleResolver = class {
|
|
5559
|
+
structure;
|
|
5560
|
+
/** Lowercased portable path → the path as indexed (case is preserved). */
|
|
5561
|
+
byPath;
|
|
5562
|
+
/** Lowercased portable directory → files directly inside it, as indexed. */
|
|
5563
|
+
byDir;
|
|
5564
|
+
/** Normalized namespace → the file declaring it (first by path, stable). */
|
|
5565
|
+
byNamespace;
|
|
5566
|
+
constructor(structure, files, namespaces = []) {
|
|
5567
|
+
this.structure = structure;
|
|
5568
|
+
this.byPath = /* @__PURE__ */ new Map();
|
|
5569
|
+
this.byDir = /* @__PURE__ */ new Map();
|
|
5570
|
+
this.byNamespace = /* @__PURE__ */ new Map();
|
|
5571
|
+
const dirsByKey = /* @__PURE__ */ new Map();
|
|
5572
|
+
for (const file of files) {
|
|
5573
|
+
const portable = toPortablePath(file);
|
|
5574
|
+
const pathKey = portable.toLowerCase();
|
|
5575
|
+
const priorPath = this.byPath.get(pathKey);
|
|
5576
|
+
if (priorPath !== void 0 && priorPath !== file) this.byPath.delete(pathKey);
|
|
5577
|
+
else this.byPath.set(pathKey, file);
|
|
5578
|
+
const dir = path7.posix.dirname(portable);
|
|
5579
|
+
const dirKey = dir.toLowerCase();
|
|
5580
|
+
const knownDir = dirsByKey.get(dirKey);
|
|
5581
|
+
if (knownDir === void 0) {
|
|
5582
|
+
dirsByKey.set(dirKey, dir);
|
|
5583
|
+
this.byDir.set(dirKey, [file]);
|
|
5584
|
+
} else if (knownDir === dir) {
|
|
5585
|
+
this.byDir.get(dirKey)?.push(file);
|
|
5586
|
+
} else {
|
|
5587
|
+
dirsByKey.delete(dirKey);
|
|
5588
|
+
this.byDir.delete(dirKey);
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
5591
|
+
for (const { name, file } of namespaces) {
|
|
5592
|
+
const lang = detectLang(file);
|
|
5593
|
+
if (!lang) continue;
|
|
5594
|
+
const key = `${languageFamily(lang)}:${normalizeNamespace(name)}`;
|
|
5595
|
+
if (normalizeNamespace(name) && !this.byNamespace.has(key)) {
|
|
5596
|
+
this.byNamespace.set(key, file);
|
|
5597
|
+
}
|
|
5598
|
+
}
|
|
5599
|
+
}
|
|
5600
|
+
/**
|
|
5601
|
+
* Resolve `specifier` as written in `fromFile`.
|
|
5602
|
+
* Returns the indexed target path, or `undefined` when it is external or
|
|
5603
|
+
* cannot be located.
|
|
5604
|
+
*/
|
|
5605
|
+
resolve(fromFile, lang, specifier) {
|
|
5606
|
+
const spec = specifier.trim().replace(/\\/g, "/");
|
|
5607
|
+
if (!spec) return void 0;
|
|
5608
|
+
const from = toPortablePath(fromFile);
|
|
5609
|
+
switch (languageFamily(lang)) {
|
|
5610
|
+
case "js":
|
|
5611
|
+
return this.resolveJs(from, spec);
|
|
5612
|
+
case "go":
|
|
5613
|
+
return this.resolveGo(spec);
|
|
5614
|
+
case "py":
|
|
5615
|
+
return this.resolvePython(from, spec);
|
|
5616
|
+
case "rs":
|
|
5617
|
+
return this.resolveRust(from, spec);
|
|
5618
|
+
case "jvm":
|
|
5619
|
+
return this.resolveJvm(spec);
|
|
5620
|
+
case "c":
|
|
5621
|
+
return this.resolveInclude(from, spec);
|
|
5622
|
+
case "ruby":
|
|
5623
|
+
return this.resolveRuby(from, spec);
|
|
5624
|
+
case "dotnet":
|
|
5625
|
+
case "php":
|
|
5626
|
+
case "elixir":
|
|
5627
|
+
case "haskell":
|
|
5628
|
+
return this.resolveNamespace(lang, spec);
|
|
5629
|
+
default:
|
|
5630
|
+
return void 0;
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
/**
|
|
5634
|
+
* Resolve a namespace specifier to the file declaring it.
|
|
5635
|
+
*
|
|
5636
|
+
* Tried whole first, then with the trailing segment dropped: `using Foo.Bar`
|
|
5637
|
+
* names a namespace outright, while PHP's `use App\Models\User` names a
|
|
5638
|
+
* *class* inside `App\Models`, so the prefix is what was declared.
|
|
5639
|
+
*/
|
|
5640
|
+
resolveNamespace(lang, spec) {
|
|
5641
|
+
const family = languageFamily(lang);
|
|
5642
|
+
const normalized = normalizeNamespace(spec);
|
|
5643
|
+
const exact = this.byNamespace.get(`${family}:${normalized}`);
|
|
5644
|
+
if (exact) return exact;
|
|
5645
|
+
const segments = normalized.split(".").filter(Boolean);
|
|
5646
|
+
if (segments.length < 2) return void 0;
|
|
5647
|
+
return this.byNamespace.get(`${family}:${segments.slice(0, -1).join(".")}`);
|
|
5648
|
+
}
|
|
5649
|
+
// ─── Lookup primitives ──────────────────────────────────────────────────────
|
|
5650
|
+
lookup(candidate) {
|
|
5651
|
+
return this.byPath.get(path7.posix.normalize(candidate).toLowerCase());
|
|
5652
|
+
}
|
|
5653
|
+
/**
|
|
5654
|
+
* Try `base` verbatim, then `base` + each extension, then each directory
|
|
5655
|
+
* entry point inside `base`.
|
|
5656
|
+
*/
|
|
5657
|
+
lookupWithExtensions(base, family) {
|
|
5658
|
+
const direct = this.lookup(base);
|
|
5659
|
+
if (direct) return direct;
|
|
5660
|
+
const extensions = EXTENSIONS[family] ?? [];
|
|
5661
|
+
const suffix = path7.posix.extname(base);
|
|
5662
|
+
const stem = suffix && extensions.includes(suffix) ? base.slice(0, -suffix.length) : base;
|
|
5663
|
+
for (const ext of extensions) {
|
|
5664
|
+
const hit = this.lookup(`${stem}${ext}`);
|
|
5665
|
+
if (hit) return hit;
|
|
5666
|
+
}
|
|
5667
|
+
for (const entry of DIRECTORY_ENTRIES[family] ?? []) {
|
|
5668
|
+
for (const ext of extensions) {
|
|
5669
|
+
const hit = this.lookup(path7.posix.join(base, `${entry}${ext}`));
|
|
5670
|
+
if (hit) return hit;
|
|
5671
|
+
}
|
|
5672
|
+
}
|
|
5673
|
+
return void 0;
|
|
5674
|
+
}
|
|
5675
|
+
/**
|
|
5676
|
+
* A representative indexed file inside `dir`, for ecosystems whose import
|
|
5677
|
+
* unit is a directory rather than a file (Go packages, JVM wildcard imports).
|
|
5678
|
+
*
|
|
5679
|
+
* The choice is deterministic — a file named after the directory, else the
|
|
5680
|
+
* first by name — so the same import always produces the same edge. Package
|
|
5681
|
+
* grouping is unaffected either way: every file in the directory carries the
|
|
5682
|
+
* same package label, so the package-level edge is exact regardless of which
|
|
5683
|
+
* member represents it.
|
|
5684
|
+
*/
|
|
5685
|
+
representativeIn(dir, family) {
|
|
5686
|
+
const members = this.byDir.get(path7.posix.normalize(dir).toLowerCase());
|
|
5687
|
+
if (!members?.length) return void 0;
|
|
5688
|
+
const extensions = EXTENSIONS[family] ?? [];
|
|
5689
|
+
const eligible = members.filter((file) => extensions.includes(path7.posix.extname(toPortablePath(file)).toLowerCase())).sort((a, b) => toPortablePath(a).localeCompare(toPortablePath(b)));
|
|
5690
|
+
if (eligible.length === 0) return void 0;
|
|
5691
|
+
const base = path7.posix.basename(path7.posix.normalize(dir)).toLowerCase();
|
|
5692
|
+
const named = eligible.find(
|
|
5693
|
+
(file) => path7.posix.basename(toPortablePath(file)).split(".")[0]?.toLowerCase() === base
|
|
5694
|
+
);
|
|
5695
|
+
return named ?? eligible[0];
|
|
5696
|
+
}
|
|
5697
|
+
// ─── Per-family resolution ──────────────────────────────────────────────────
|
|
5698
|
+
/** Relative specifiers, then workspace package names and their subpaths. */
|
|
5699
|
+
resolveJs(fromFile, spec) {
|
|
5700
|
+
if (spec.startsWith(".")) {
|
|
5701
|
+
const absolute = path7.posix.join(path7.posix.dirname(fromFile), spec);
|
|
5702
|
+
return this.lookupWithExtensions(absolute, "js");
|
|
5703
|
+
}
|
|
5704
|
+
const owner = this.structure.roots.find(
|
|
5705
|
+
(root) => root.kind === "npm" && root.importPath !== void 0 && (spec === root.importPath || spec.startsWith(`${root.importPath}/`))
|
|
5706
|
+
);
|
|
5707
|
+
if (!owner?.importPath) return void 0;
|
|
5708
|
+
const subpath = spec.slice(owner.importPath.length).replace(/^\//, "");
|
|
5709
|
+
if (!subpath) {
|
|
5710
|
+
return this.lookupWithExtensions(path7.posix.join(owner.dir, "src/index"), "js") ?? this.lookupWithExtensions(path7.posix.join(owner.dir, "index"), "js");
|
|
5711
|
+
}
|
|
5712
|
+
return this.lookupWithExtensions(path7.posix.join(owner.dir, subpath), "js") ?? this.lookupWithExtensions(path7.posix.join(owner.dir, "src", subpath), "js");
|
|
5713
|
+
}
|
|
5714
|
+
/** Go import paths are absolute module paths; a package is a directory. */
|
|
5715
|
+
resolveGo(spec) {
|
|
5716
|
+
const owner = this.structure.roots.find(
|
|
5717
|
+
(root) => root.kind === "go" && root.importPath !== void 0 && (spec === root.importPath || spec.startsWith(`${root.importPath}/`))
|
|
5718
|
+
);
|
|
5719
|
+
if (!owner?.importPath) return void 0;
|
|
5720
|
+
const subpath = spec.slice(owner.importPath.length).replace(/^\//, "");
|
|
5721
|
+
return this.representativeIn(path7.posix.join(owner.dir, subpath), "go");
|
|
5722
|
+
}
|
|
5723
|
+
/**
|
|
5724
|
+
* `import a.b.c` / `from a.b import c`, plus PEP 328 relative imports whose
|
|
5725
|
+
* leading dots the extractor preserves (`.sibling`, `..parent.mod`).
|
|
5726
|
+
*/
|
|
5727
|
+
resolvePython(fromFile, spec) {
|
|
5728
|
+
const leadingDots = /^\.*/.exec(spec)?.[0].length ?? 0;
|
|
5729
|
+
if (leadingDots > 0) {
|
|
5730
|
+
let base = path7.posix.dirname(fromFile);
|
|
5731
|
+
for (let i = 1; i < leadingDots; i++) base = path7.posix.dirname(base);
|
|
5732
|
+
const rest = spec.slice(leadingDots).split(".").filter(Boolean);
|
|
5733
|
+
return this.lookupWithExtensions(path7.posix.join(base, ...rest), "py");
|
|
5734
|
+
}
|
|
5735
|
+
const segments = spec.split(".").filter(Boolean);
|
|
5736
|
+
if (segments.length === 0) return void 0;
|
|
5737
|
+
const sourceRoots = this.structure.roots.filter((root) => root.kind === "python").flatMap((root) => root.sourceRoots);
|
|
5738
|
+
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
5739
|
+
const hit = this.lookupWithExtensions(path7.posix.join(base, ...segments), "py");
|
|
5740
|
+
if (hit) return hit;
|
|
5741
|
+
if (segments.length > 1) {
|
|
5742
|
+
const parent = this.lookupWithExtensions(
|
|
5743
|
+
path7.posix.join(base, ...segments.slice(0, -1)),
|
|
5744
|
+
"py"
|
|
5745
|
+
);
|
|
5746
|
+
if (parent) return parent;
|
|
5747
|
+
}
|
|
5748
|
+
}
|
|
5749
|
+
return void 0;
|
|
5750
|
+
}
|
|
5751
|
+
/** `use crate::a::b`, `use super::x`, `use self::y`, `use other_crate::z`. */
|
|
5752
|
+
resolveRust(fromFile, spec) {
|
|
5753
|
+
const segments = spec.split("::").filter(Boolean);
|
|
5754
|
+
if (segments.length === 0) return void 0;
|
|
5755
|
+
const head = segments[0];
|
|
5756
|
+
if (head === "self" || head === "super") {
|
|
5757
|
+
let base = path7.posix.dirname(fromFile);
|
|
5758
|
+
for (const segment of segments) {
|
|
5759
|
+
if (segment === "super") base = path7.posix.dirname(base);
|
|
5760
|
+
else if (segment !== "self") break;
|
|
5761
|
+
}
|
|
5762
|
+
const rest2 = segments.filter((segment) => segment !== "self" && segment !== "super");
|
|
5763
|
+
return this.lookupWithExtensions(path7.posix.join(base, ...rest2), "rs");
|
|
5764
|
+
}
|
|
5765
|
+
const owningCrate = findOwningRoot(this.structure, fromFile, ["cargo"]);
|
|
5766
|
+
const crate = head === "crate" ? owningCrate : this.structure.roots.find(
|
|
5767
|
+
(root) => root.kind === "cargo" && root.importPath === head?.replace(/-/g, "_")
|
|
5768
|
+
);
|
|
5769
|
+
if (!crate) {
|
|
5770
|
+
return this.lookupWithExtensions(
|
|
5771
|
+
path7.posix.join(path7.posix.dirname(fromFile), ...segments),
|
|
5772
|
+
"rs"
|
|
5773
|
+
);
|
|
5774
|
+
}
|
|
5775
|
+
const rest = segments.slice(1);
|
|
5776
|
+
for (const base of crate.sourceRoots) {
|
|
5777
|
+
const parent = rest.length > 1 ? this.lookupWithExtensions(path7.posix.join(base, ...rest.slice(0, -1)), "rs") : void 0;
|
|
5778
|
+
const exact = this.lookupWithExtensions(path7.posix.join(base, ...rest), "rs");
|
|
5779
|
+
const hit = exact ?? parent ?? this.lookupWithExtensions(path7.posix.join(base, "lib"), "rs");
|
|
5780
|
+
if (hit) return hit;
|
|
5781
|
+
}
|
|
5782
|
+
return void 0;
|
|
5783
|
+
}
|
|
5784
|
+
/** `com.example.Thing` and `com.example.*` against JVM source roots. */
|
|
5785
|
+
resolveJvm(spec) {
|
|
5786
|
+
const segments = spec.split(".").filter(Boolean);
|
|
5787
|
+
if (segments.length === 0) return void 0;
|
|
5788
|
+
const sourceRoots = this.structure.roots.filter((root) => root.kind === "maven" || root.kind === "gradle").flatMap((root) => root.sourceRoots);
|
|
5789
|
+
const wildcard = segments[segments.length - 1] === "*";
|
|
5790
|
+
const parts = wildcard ? segments.slice(0, -1) : segments;
|
|
5791
|
+
for (const base of [...sourceRoots, this.structure.projectRoot]) {
|
|
5792
|
+
const target = path7.posix.join(base, ...parts);
|
|
5793
|
+
const hit = wildcard ? this.representativeIn(target, "jvm") : this.lookupWithExtensions(target, "jvm");
|
|
5794
|
+
if (hit) return hit;
|
|
5795
|
+
}
|
|
5796
|
+
return void 0;
|
|
5797
|
+
}
|
|
5798
|
+
/** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
|
|
5799
|
+
resolveInclude(fromFile, spec) {
|
|
5800
|
+
const relative2 = this.lookupWithExtensions(
|
|
5801
|
+
path7.posix.join(path7.posix.dirname(fromFile), spec),
|
|
5802
|
+
"c"
|
|
5803
|
+
);
|
|
5804
|
+
if (relative2) return relative2;
|
|
5805
|
+
for (const base of [
|
|
5806
|
+
path7.posix.join(this.structure.projectRoot, "include"),
|
|
5807
|
+
this.structure.projectRoot
|
|
5808
|
+
]) {
|
|
5809
|
+
const hit = this.lookupWithExtensions(path7.posix.join(base, spec), "c");
|
|
5810
|
+
if (hit) return hit;
|
|
5811
|
+
}
|
|
5812
|
+
return void 0;
|
|
5813
|
+
}
|
|
5814
|
+
/** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
|
|
5815
|
+
resolveRuby(fromFile, spec) {
|
|
5816
|
+
const relative2 = this.lookupWithExtensions(
|
|
5817
|
+
path7.posix.join(path7.posix.dirname(fromFile), spec),
|
|
5818
|
+
"ruby"
|
|
5819
|
+
);
|
|
5820
|
+
if (relative2) return relative2;
|
|
5821
|
+
for (const base of [
|
|
5822
|
+
path7.posix.join(this.structure.projectRoot, "lib"),
|
|
5823
|
+
this.structure.projectRoot
|
|
5824
|
+
]) {
|
|
5825
|
+
const hit = this.lookupWithExtensions(path7.posix.join(base, spec), "ruby");
|
|
5826
|
+
if (hit) return hit;
|
|
5827
|
+
}
|
|
5828
|
+
return void 0;
|
|
5829
|
+
}
|
|
5830
|
+
};
|
|
5831
|
+
|
|
5832
|
+
// src/codebase-index/import-extractor.ts
|
|
5833
|
+
var IMPORT_MAX_FILE_CHARS = 512 * 1024;
|
|
5834
|
+
var IMPORT_MAX_PER_FILE = 400;
|
|
5835
|
+
var DOTTED_IMPORT = [
|
|
5836
|
+
{ re: /^[ \t]*import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;?\s*$/gm }
|
|
5837
|
+
];
|
|
5838
|
+
var LANG_IMPORTS = {
|
|
5839
|
+
// Go and Python have real AST extractors; these patterns are the fallback for
|
|
5840
|
+
// machines with no Go toolchain or Python interpreter installed, where the
|
|
5841
|
+
// parser degrades to regex symbols and would otherwise contribute no edges.
|
|
5842
|
+
go: [
|
|
5843
|
+
{ re: /^[ \t]*import\s+(?:[\w.]+\s+)?"([^"]+)"/gm },
|
|
5844
|
+
// Grouped form: inside `import ( … )` each line is an optional alias plus a
|
|
5845
|
+
// quoted path. A stray match elsewhere resolves to no file and is dropped.
|
|
5846
|
+
{ re: /^[ \t]*(?:[A-Za-z_.]\w*\s+)?"([^"]+)"\s*$/gm }
|
|
5847
|
+
],
|
|
5848
|
+
py: [
|
|
5849
|
+
{ re: /^[ \t]*import\s+([\w.]+)/gm },
|
|
5850
|
+
{ re: /^[ \t]*from\s+([.\w]+)\s+import\b/gm }
|
|
5851
|
+
],
|
|
5852
|
+
rs: [
|
|
5853
|
+
// use a::b::C; | use a::b::{C, D}; → the path before any brace
|
|
5854
|
+
{ re: /^[ \t]*(?:pub\s+)?use\s+([\w:]+?)(?:::\{|\s*;|\s+as\b)/gm },
|
|
5855
|
+
// mod foo; (a declaration *and* a dependency on foo.rs / foo/mod.rs)
|
|
5856
|
+
{ re: /^[ \t]*(?:pub\s+)?mod\s+(\w+)\s*;/gm }
|
|
5857
|
+
],
|
|
5858
|
+
java: DOTTED_IMPORT,
|
|
5859
|
+
kotlin: DOTTED_IMPORT,
|
|
5860
|
+
scala: [{ re: /^[ \t]*import\s+([\w.]+(?:\.[_*])?)\s*$/gm }],
|
|
5861
|
+
csharp: [
|
|
5862
|
+
// using Foo.Bar; | using static Foo.Bar; | using Alias = Foo.Bar;
|
|
5863
|
+
{ re: /^[ \t]*global\s+using\s+(?:static\s+)?([\w.]+)\s*;/gm, name: "full" },
|
|
5864
|
+
{ re: /^[ \t]*using\s+(?:static\s+)?(?:\w+\s*=\s*)?([\w.]+)\s*;/gm, name: "full" }
|
|
5865
|
+
],
|
|
5866
|
+
// Quoted includes only: <stdio.h> is a system header with no indexed file.
|
|
5867
|
+
c: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
5868
|
+
cpp: [{ re: /^[ \t]*#\s*include\s+"([^"]+)"/gm }],
|
|
5869
|
+
ruby: [{ re: /^[ \t]*(?:require|require_relative|load)\s*\(?\s*['"]([^'"]+)['"]/gm }],
|
|
5870
|
+
php: [
|
|
5871
|
+
// `use A\B\C` imports the class C, which is what the index has a symbol
|
|
5872
|
+
// for — the namespace symbol only covers the `A\B` prefix.
|
|
5873
|
+
{ re: /^[ \t]*use\s+(?:function\s+|const\s+)?([\w\\]+)/gm },
|
|
5874
|
+
{ re: /^[ \t]*(?:require|require_once|include|include_once)\s*\(?\s*['"]([^'"]+)['"]/gm }
|
|
5875
|
+
],
|
|
5876
|
+
swift: [{ re: /^[ \t]*import\s+(?:struct\s+|class\s+|func\s+)?([\w.]+)\s*$/gm }],
|
|
5877
|
+
dart: [{ re: /^[ \t]*(?:import|export|part)\s+['"]([^'"]+)['"]/gm }],
|
|
5878
|
+
lua: [{ re: /\brequire\s*\(?\s*['"]([^'"]+)['"]/g }],
|
|
5879
|
+
elixir: [{ re: /^[ \t]*(?:alias|import|require|use)\s+([A-Z][\w.]*)/gm, name: "full" }],
|
|
5880
|
+
haskell: [{ re: /^[ \t]*import\s+(?:qualified\s+)?([\w.]+)/gm, name: "full" }],
|
|
5881
|
+
zig: [{ re: /@import\s*\(\s*"([^"]+)"\s*\)/g }],
|
|
5882
|
+
proto: [{ re: /^[ \t]*import\s+(?:public\s+|weak\s+)?"([^"]+)"\s*;/gm }],
|
|
5883
|
+
// `@import "x"`, `@use "x"`, `@forward "x"` — Sass and plain CSS alike.
|
|
5884
|
+
css: [{ re: /^[ \t]*@(?:import|use|forward)\s+(?:url\()?\s*['"]([^'"]+)['"]/gm }],
|
|
5885
|
+
// A .vue/.svelte file's <script> block is JS; the same ESM syntax applies.
|
|
5886
|
+
vue: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
5887
|
+
svelte: [{ re: /^[ \t]*import\s[^'"]*from\s*['"]([^'"]+)['"]/gm }],
|
|
5888
|
+
html: [
|
|
5889
|
+
{ re: /<script[^>]+src\s*=\s*['"]([^'"]+)['"]/g },
|
|
5890
|
+
{ re: /<link[^>]+href\s*=\s*['"]([^'"]+)['"]/g }
|
|
5891
|
+
],
|
|
5892
|
+
shell: [{ re: /^[ \t]*(?:source|\.)\s+(\S+)/gm }],
|
|
5893
|
+
r: [{ re: /\b(?:source|library|require)\s*\(\s*['"]?([\w./-]+)['"]?\s*\)/g }]
|
|
5894
|
+
};
|
|
5895
|
+
function lastSegment(specifier) {
|
|
5896
|
+
const pathLike = /[/\\]|::/.test(specifier);
|
|
5897
|
+
const segments = specifier.split(/[/\\]|::/).filter(Boolean);
|
|
5898
|
+
let last = segments[segments.length - 1] ?? specifier;
|
|
5899
|
+
if (last === "*" || last === "_") {
|
|
5900
|
+
last = segments[segments.length - 2] ?? specifier;
|
|
5901
|
+
}
|
|
5902
|
+
if (pathLike) return last.replace(/\.[A-Za-z0-9]{1,8}$/, "") || last;
|
|
5903
|
+
const dotted = last.split(".").filter((part) => part && part !== "*" && part !== "_");
|
|
5904
|
+
return dotted[dotted.length - 1] ?? last;
|
|
5905
|
+
}
|
|
5906
|
+
function newlineOffsets(content) {
|
|
5907
|
+
const offsets = [];
|
|
5908
|
+
for (let i = 0; i < content.length; i++) {
|
|
5909
|
+
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
5910
|
+
}
|
|
5911
|
+
return offsets;
|
|
5912
|
+
}
|
|
5913
|
+
function lineAt(offsets, index) {
|
|
5914
|
+
let low = 0;
|
|
5915
|
+
let high = offsets.length;
|
|
5916
|
+
while (low < high) {
|
|
5917
|
+
const mid = low + high >>> 1;
|
|
5918
|
+
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
5919
|
+
else high = mid;
|
|
5920
|
+
}
|
|
5921
|
+
return low + 1;
|
|
5922
|
+
}
|
|
5923
|
+
function hasImportPatterns(lang) {
|
|
5924
|
+
return LANG_IMPORTS[lang] !== void 0;
|
|
5925
|
+
}
|
|
5926
|
+
function extractImports(opts) {
|
|
5927
|
+
const patterns = LANG_IMPORTS[opts.lang];
|
|
5928
|
+
if (!patterns || !opts.content) return [];
|
|
5929
|
+
const limit = opts.maxImports ?? IMPORT_MAX_PER_FILE;
|
|
5930
|
+
const content = opts.content.length > IMPORT_MAX_FILE_CHARS ? opts.content.slice(0, IMPORT_MAX_FILE_CHARS) : opts.content;
|
|
5931
|
+
const refs = [];
|
|
5932
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5933
|
+
const offsets = newlineOffsets(content);
|
|
5934
|
+
for (const pattern of patterns) {
|
|
5935
|
+
const re = new RegExp(pattern.re.source, pattern.re.flags);
|
|
5936
|
+
for (const match of content.matchAll(re)) {
|
|
5937
|
+
if (refs.length >= limit) return refs;
|
|
5938
|
+
const specifier = match[1]?.trim();
|
|
5939
|
+
if (!specifier) continue;
|
|
5940
|
+
const module = specifier;
|
|
5941
|
+
const toName = pattern.name === "full" ? module : lastSegment(module);
|
|
5942
|
+
if (!toName) continue;
|
|
5943
|
+
const key = `${module}\0${toName}`;
|
|
5944
|
+
if (seen.has(key)) continue;
|
|
5945
|
+
seen.add(key);
|
|
5946
|
+
refs.push({
|
|
5947
|
+
fromId: 0,
|
|
5948
|
+
toName,
|
|
5949
|
+
callType: "import",
|
|
5950
|
+
line: lineAt(offsets, match.index ?? 0),
|
|
5951
|
+
lang: opts.lang,
|
|
5952
|
+
module
|
|
5953
|
+
});
|
|
5954
|
+
}
|
|
5955
|
+
}
|
|
5956
|
+
return refs;
|
|
5957
|
+
}
|
|
5958
|
+
|
|
4720
5959
|
// src/codebase-index/parser-dispatch.ts
|
|
4721
5960
|
async function parseFileContent(file, content, lang) {
|
|
5961
|
+
const parsed = await dispatch(file, content, lang);
|
|
5962
|
+
return withRelations(parsed, content, lang);
|
|
5963
|
+
}
|
|
5964
|
+
async function dispatch(file, content, lang) {
|
|
4722
5965
|
switch (lang) {
|
|
4723
5966
|
case "ts":
|
|
4724
5967
|
case "tsx":
|
|
@@ -4753,6 +5996,13 @@ async function parseFileContent(file, content, lang) {
|
|
|
4753
5996
|
}
|
|
4754
5997
|
}
|
|
4755
5998
|
}
|
|
5999
|
+
function withRelations(parsed, content, lang) {
|
|
6000
|
+
let refs = parsed.refs ?? [];
|
|
6001
|
+
if (refs.length === 0 && hasImportPatterns(lang)) {
|
|
6002
|
+
refs = extractImports({ content, lang });
|
|
6003
|
+
}
|
|
6004
|
+
return { ...parsed, refs: refs.map((ref) => ref.lang ? ref : { ...ref, lang }) };
|
|
6005
|
+
}
|
|
4756
6006
|
|
|
4757
6007
|
// src/codebase-index/indexer.ts
|
|
4758
6008
|
var YIELD_EVERY_N = 50;
|
|
@@ -4789,7 +6039,7 @@ function normalizeComparablePath(value) {
|
|
|
4789
6039
|
}
|
|
4790
6040
|
function gitOutput(projectRoot, args) {
|
|
4791
6041
|
return new Promise((resolve4, reject) => {
|
|
4792
|
-
|
|
6042
|
+
execFile(
|
|
4793
6043
|
"git",
|
|
4794
6044
|
["-C", projectRoot, ...args],
|
|
4795
6045
|
{
|
|
@@ -4920,13 +6170,40 @@ function assignRefsToSymbols2(refs, symbols) {
|
|
|
4920
6170
|
}
|
|
4921
6171
|
if (!owner && ref.callType === "import") owner = ordered[0];
|
|
4922
6172
|
if (!owner || owner.id <= 0) continue;
|
|
4923
|
-
const key = `${owner.id}:${ref.toName}:${ref.callType}`;
|
|
6173
|
+
const key = `${owner.id}:${ref.toName}:${ref.callType}:${ref.module ?? ""}`;
|
|
4924
6174
|
if (seen.has(key)) continue;
|
|
4925
6175
|
seen.add(key);
|
|
4926
6176
|
assigned.push({ ...ref, fromId: owner.id });
|
|
4927
6177
|
}
|
|
4928
6178
|
return assigned;
|
|
4929
6179
|
}
|
|
6180
|
+
async function resolveProjectRelations(store, projectRoot, opts) {
|
|
6181
|
+
if (opts.signal?.aborted) return;
|
|
6182
|
+
try {
|
|
6183
|
+
const indexedFiles = store.getAllFileMetas().map((meta) => meta.file);
|
|
6184
|
+
if (indexedFiles.length === 0) return;
|
|
6185
|
+
const structure = await detectModuleRoots(projectRoot, indexedFiles);
|
|
6186
|
+
if (opts.signal?.aborted) return;
|
|
6187
|
+
store.setFilePackages(assignPackageLabels(structure, indexedFiles));
|
|
6188
|
+
const resolver = new ModuleResolver(
|
|
6189
|
+
structure,
|
|
6190
|
+
indexedFiles,
|
|
6191
|
+
store.getNamespaceDeclarations()
|
|
6192
|
+
);
|
|
6193
|
+
const pending2 = store.getUnresolvedImports(opts.onlyFiles);
|
|
6194
|
+
const resolutions = [];
|
|
6195
|
+
for (const entry of pending2) {
|
|
6196
|
+
const toFile = resolver.resolve(entry.fromFile, entry.lang, entry.module);
|
|
6197
|
+
if (toFile && toFile !== entry.fromFile) {
|
|
6198
|
+
resolutions.push({ ...entry, toFile });
|
|
6199
|
+
}
|
|
6200
|
+
}
|
|
6201
|
+
if (opts.signal?.aborted) return;
|
|
6202
|
+
store.applyImportResolutions(resolutions);
|
|
6203
|
+
} catch (err) {
|
|
6204
|
+
opts.errors.push(`relation resolution: ${err instanceof Error ? err.message : String(err)}`);
|
|
6205
|
+
}
|
|
6206
|
+
}
|
|
4930
6207
|
async function runIndexer(_ctx, opts) {
|
|
4931
6208
|
const store = new IndexStore(opts.projectRoot, { indexDir: opts.indexDir });
|
|
4932
6209
|
try {
|
|
@@ -5182,6 +6459,14 @@ async function runIndexerWithStore(store, opts) {
|
|
|
5182
6459
|
}
|
|
5183
6460
|
}
|
|
5184
6461
|
if (needsFullRefResolution) store.resolveRefs();
|
|
6462
|
+
await resolveProjectRelations(store, projectRoot, {
|
|
6463
|
+
// A watcher run re-resolves only what it touched; a full run (or a contract
|
|
6464
|
+
// bump) re-resolves everything, because a newly indexed file can be the
|
|
6465
|
+
// target of imports written long before it.
|
|
6466
|
+
onlyFiles: needsFullRefResolution ? void 0 : opts.files,
|
|
6467
|
+
errors,
|
|
6468
|
+
signal
|
|
6469
|
+
});
|
|
5185
6470
|
store.setMetadata("ref_resolution_version", refResolutionVersion);
|
|
5186
6471
|
store.setMetadata("relation_graph_version", relationGraphVersion);
|
|
5187
6472
|
if (!opts.files || filesIndexed >= 50) store.optimize();
|
|
@@ -5264,6 +6549,22 @@ function symbolGraphService(args) {
|
|
|
5264
6549
|
indexStorePool.release(store);
|
|
5265
6550
|
}
|
|
5266
6551
|
}
|
|
6552
|
+
function incomingCallsService(args) {
|
|
6553
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
6554
|
+
try {
|
|
6555
|
+
return store.findIncomingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
6556
|
+
} finally {
|
|
6557
|
+
indexStorePool.release(store);
|
|
6558
|
+
}
|
|
6559
|
+
}
|
|
6560
|
+
function outgoingCallsService(args) {
|
|
6561
|
+
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
6562
|
+
try {
|
|
6563
|
+
return store.findOutgoingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
6564
|
+
} finally {
|
|
6565
|
+
indexStorePool.release(store);
|
|
6566
|
+
}
|
|
6567
|
+
}
|
|
5267
6568
|
|
|
5268
6569
|
// src/codebase-index/background-indexer.ts
|
|
5269
6570
|
init_languages();
|
|
@@ -5501,6 +6802,10 @@ async function callInline(op, args, opts) {
|
|
|
5501
6802
|
return fileGraphService(args);
|
|
5502
6803
|
case "symbolGraph":
|
|
5503
6804
|
return symbolGraphService(args);
|
|
6805
|
+
case "incomingCalls":
|
|
6806
|
+
return incomingCallsService(args);
|
|
6807
|
+
case "outgoingCalls":
|
|
6808
|
+
return outgoingCallsService(args);
|
|
5504
6809
|
default:
|
|
5505
6810
|
throw new Error(`unknown index op: ${String(op)}`);
|
|
5506
6811
|
}
|
|
@@ -5694,6 +6999,12 @@ async function fileGraphService2(args) {
|
|
|
5694
6999
|
async function symbolGraphService2(args) {
|
|
5695
7000
|
return callIndexOp("symbolGraph", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
|
|
5696
7001
|
}
|
|
7002
|
+
async function incomingCallsService2(args) {
|
|
7003
|
+
return callIndexOp("incomingCalls", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
|
|
7004
|
+
}
|
|
7005
|
+
async function outgoingCallsService2(args) {
|
|
7006
|
+
return callIndexOp("outgoingCalls", args, { timeoutMs: DEFAULT_QUERY_TIMEOUT_MS });
|
|
7007
|
+
}
|
|
5697
7008
|
function shutdownCodebaseIndexServer(projectRoot, indexDir, reason) {
|
|
5698
7009
|
return shutdownProjectIndexServer(projectRoot, indexDir, reason);
|
|
5699
7010
|
}
|
|
@@ -5777,6 +7088,214 @@ var codebaseIndexTool = {
|
|
|
5777
7088
|
}
|
|
5778
7089
|
};
|
|
5779
7090
|
|
|
7091
|
+
// src/codebase-index/codebase-incoming-calls-tool.ts
|
|
7092
|
+
var codebaseIncomingCallsTool = {
|
|
7093
|
+
name: "codebase-incoming-calls",
|
|
7094
|
+
category: "Project",
|
|
7095
|
+
icon: "index",
|
|
7096
|
+
description: "Find all callers of a function, method, or symbol \u2014 who invokes or references it. Uses the codebase index ref graph for instant, exact results. Always use this instead of grep when checking impact of a change.",
|
|
7097
|
+
usageHint: 'CALL THIS BEFORE REFACTORING OR CHANGING ANY FUNCTION:\n\n- NEVER use grep or manual line reading to check where a function is called.\n- ALWAYS call codebase-incoming-calls({ symbol: "funcName" }) first.\n- Returns exact files, line numbers, caller signatures, and call types in milliseconds.\n- Use `file` to disambiguate when multiple symbols share a name.\n- Combine with codebase-outgoing-calls to see what the symbol itself calls.\nIf the index is not built, run codebase-index first.',
|
|
7098
|
+
permission: "auto",
|
|
7099
|
+
mutating: false,
|
|
7100
|
+
capabilities: ["fs.read"],
|
|
7101
|
+
timeoutMs: 35e3,
|
|
7102
|
+
inputSchema: {
|
|
7103
|
+
type: "object",
|
|
7104
|
+
properties: {
|
|
7105
|
+
symbol: {
|
|
7106
|
+
type: "string",
|
|
7107
|
+
description: "The function/method/type name to find callers for"
|
|
7108
|
+
},
|
|
7109
|
+
file: {
|
|
7110
|
+
type: "string",
|
|
7111
|
+
description: "Scope to a specific file when multiple symbols share the same name"
|
|
7112
|
+
},
|
|
7113
|
+
limit: {
|
|
7114
|
+
type: "integer",
|
|
7115
|
+
description: "Maximum call sites to return (default 50, max 200)",
|
|
7116
|
+
minimum: 1,
|
|
7117
|
+
maximum: 200
|
|
7118
|
+
}
|
|
7119
|
+
},
|
|
7120
|
+
required: ["symbol"]
|
|
7121
|
+
},
|
|
7122
|
+
async execute(input, ctx) {
|
|
7123
|
+
const state = getIndexState();
|
|
7124
|
+
if (state.indexing && !state.ready) {
|
|
7125
|
+
return {
|
|
7126
|
+
symbol: input.symbol,
|
|
7127
|
+
calls: [],
|
|
7128
|
+
total: 0,
|
|
7129
|
+
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
7130
|
+
};
|
|
7131
|
+
}
|
|
7132
|
+
if (state.lastError) {
|
|
7133
|
+
const circuit = state.circuit;
|
|
7134
|
+
const retryHint = circuit.state === "open" ? `Indexing is paused (circuit open, retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s).` : "Try /codebase-reindex.";
|
|
7135
|
+
return {
|
|
7136
|
+
symbol: input.symbol,
|
|
7137
|
+
calls: [],
|
|
7138
|
+
total: 0,
|
|
7139
|
+
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
7140
|
+
};
|
|
7141
|
+
}
|
|
7142
|
+
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
|
|
7143
|
+
const { calls, symbolFound, ambiguous, totalMatches } = await incomingCallsService2(
|
|
7144
|
+
{
|
|
7145
|
+
projectRoot: ctx.projectRoot,
|
|
7146
|
+
indexDir: codebaseIndexDirOverride(ctx),
|
|
7147
|
+
symbol: input.symbol,
|
|
7148
|
+
file: input.file,
|
|
7149
|
+
limit
|
|
7150
|
+
}
|
|
7151
|
+
);
|
|
7152
|
+
if (!symbolFound) {
|
|
7153
|
+
let hasPersistedIndex = state.ready;
|
|
7154
|
+
if (!hasPersistedIndex) {
|
|
7155
|
+
try {
|
|
7156
|
+
const stats = await codebaseIndexStats({
|
|
7157
|
+
projectRoot: ctx.projectRoot,
|
|
7158
|
+
indexDir: codebaseIndexDirOverride(ctx)
|
|
7159
|
+
});
|
|
7160
|
+
hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
7161
|
+
} catch {
|
|
7162
|
+
}
|
|
7163
|
+
}
|
|
7164
|
+
if (!hasPersistedIndex) {
|
|
7165
|
+
return {
|
|
7166
|
+
symbol: input.symbol,
|
|
7167
|
+
calls: [],
|
|
7168
|
+
total: 0,
|
|
7169
|
+
indexStatus: "No persisted index data found. Run codebase-index to build it."
|
|
7170
|
+
};
|
|
7171
|
+
}
|
|
7172
|
+
return {
|
|
7173
|
+
symbol: input.symbol,
|
|
7174
|
+
calls: [],
|
|
7175
|
+
total: 0,
|
|
7176
|
+
note: `Symbol "${input.symbol}" not found in the index. Use codebase-search to verify the name.`
|
|
7177
|
+
};
|
|
7178
|
+
}
|
|
7179
|
+
const notes = [];
|
|
7180
|
+
if (totalMatches > limit) {
|
|
7181
|
+
notes.push(`Results capped at ${limit} of ${totalMatches} call sites. Increase \`limit\` or use \`file\` to narrow.`);
|
|
7182
|
+
}
|
|
7183
|
+
if (ambiguous) {
|
|
7184
|
+
notes.push(`Symbol "${input.symbol}" exists in multiple files. Results include callers of all same-named symbols. Use codebase-search to find the exact file and pass it as \`file\`.`);
|
|
7185
|
+
}
|
|
7186
|
+
return {
|
|
7187
|
+
symbol: input.symbol,
|
|
7188
|
+
calls,
|
|
7189
|
+
total: calls.length,
|
|
7190
|
+
...notes.length > 0 ? { note: notes.join(" ") } : {}
|
|
7191
|
+
};
|
|
7192
|
+
}
|
|
7193
|
+
};
|
|
7194
|
+
|
|
7195
|
+
// src/codebase-index/codebase-outgoing-calls-tool.ts
|
|
7196
|
+
var codebaseOutgoingCallsTool = {
|
|
7197
|
+
name: "codebase-outgoing-calls",
|
|
7198
|
+
category: "Project",
|
|
7199
|
+
icon: "index",
|
|
7200
|
+
description: "Find all functions/methods/symbols that a given symbol calls or depends on \u2014 its callees. Uses the codebase index ref graph for instant, exact results. Use this to understand a function's dependencies before modifying it.",
|
|
7201
|
+
usageHint: 'USE THIS TO UNDERSTAND A FUNCTION\'S DEPENDENCIES:\n\n- Call codebase-outgoing-calls({ symbol: "funcName" }) to see everything it calls.\n- Returns exact files, line numbers, callee signatures, and call types in milliseconds.\n- Use `file` to disambiguate when multiple symbols share a name.\n- Pair with codebase-incoming-calls for a complete impact picture: incoming = who calls you, outgoing = what you call.\nIf the index is not built, run codebase-index first.',
|
|
7202
|
+
permission: "auto",
|
|
7203
|
+
mutating: false,
|
|
7204
|
+
capabilities: ["fs.read"],
|
|
7205
|
+
timeoutMs: 35e3,
|
|
7206
|
+
inputSchema: {
|
|
7207
|
+
type: "object",
|
|
7208
|
+
properties: {
|
|
7209
|
+
symbol: {
|
|
7210
|
+
type: "string",
|
|
7211
|
+
description: "The function/method/type name to find callees for"
|
|
7212
|
+
},
|
|
7213
|
+
file: {
|
|
7214
|
+
type: "string",
|
|
7215
|
+
description: "Scope to a specific file when multiple symbols share the same name"
|
|
7216
|
+
},
|
|
7217
|
+
limit: {
|
|
7218
|
+
type: "integer",
|
|
7219
|
+
description: "Maximum call sites to return (default 50, max 200)",
|
|
7220
|
+
minimum: 1,
|
|
7221
|
+
maximum: 200
|
|
7222
|
+
}
|
|
7223
|
+
},
|
|
7224
|
+
required: ["symbol"]
|
|
7225
|
+
},
|
|
7226
|
+
async execute(input, ctx) {
|
|
7227
|
+
const state = getIndexState();
|
|
7228
|
+
if (state.indexing && !state.ready) {
|
|
7229
|
+
return {
|
|
7230
|
+
symbol: input.symbol,
|
|
7231
|
+
calls: [],
|
|
7232
|
+
total: 0,
|
|
7233
|
+
indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
|
|
7234
|
+
};
|
|
7235
|
+
}
|
|
7236
|
+
if (state.lastError) {
|
|
7237
|
+
const circuit = state.circuit;
|
|
7238
|
+
const retryHint = circuit.state === "open" ? `Indexing is paused (circuit open, retry in ${Math.ceil(circuit.cooldownRemainingMs / 1e3)}s).` : "Try /codebase-reindex.";
|
|
7239
|
+
return {
|
|
7240
|
+
symbol: input.symbol,
|
|
7241
|
+
calls: [],
|
|
7242
|
+
total: 0,
|
|
7243
|
+
indexStatus: `Index build failed: ${state.lastError}. ${retryHint}`
|
|
7244
|
+
};
|
|
7245
|
+
}
|
|
7246
|
+
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
|
|
7247
|
+
const { calls, symbolFound, unresolvedCount, totalMatches } = await outgoingCallsService2(
|
|
7248
|
+
{
|
|
7249
|
+
projectRoot: ctx.projectRoot,
|
|
7250
|
+
indexDir: codebaseIndexDirOverride(ctx),
|
|
7251
|
+
symbol: input.symbol,
|
|
7252
|
+
file: input.file,
|
|
7253
|
+
limit
|
|
7254
|
+
}
|
|
7255
|
+
);
|
|
7256
|
+
if (!symbolFound) {
|
|
7257
|
+
let hasPersistedIndex = state.ready;
|
|
7258
|
+
if (!hasPersistedIndex) {
|
|
7259
|
+
try {
|
|
7260
|
+
const stats = await codebaseIndexStats({
|
|
7261
|
+
projectRoot: ctx.projectRoot,
|
|
7262
|
+
indexDir: codebaseIndexDirOverride(ctx)
|
|
7263
|
+
});
|
|
7264
|
+
hasPersistedIndex = stats.totalFiles > 0 || stats.lastIndexed !== null;
|
|
7265
|
+
} catch {
|
|
7266
|
+
}
|
|
7267
|
+
}
|
|
7268
|
+
if (!hasPersistedIndex) {
|
|
7269
|
+
return {
|
|
7270
|
+
symbol: input.symbol,
|
|
7271
|
+
calls: [],
|
|
7272
|
+
total: 0,
|
|
7273
|
+
indexStatus: "No persisted index data found. Run codebase-index to build it."
|
|
7274
|
+
};
|
|
7275
|
+
}
|
|
7276
|
+
return {
|
|
7277
|
+
symbol: input.symbol,
|
|
7278
|
+
calls: [],
|
|
7279
|
+
total: 0,
|
|
7280
|
+
note: `Symbol "${input.symbol}" not found in the index. Use codebase-search to verify the name.`
|
|
7281
|
+
};
|
|
7282
|
+
}
|
|
7283
|
+
const notes = [];
|
|
7284
|
+
if (totalMatches > limit) {
|
|
7285
|
+
notes.push(`Results capped at ${limit} of ${totalMatches} call sites. Increase \`limit\` or use \`file\` to narrow.`);
|
|
7286
|
+
}
|
|
7287
|
+
if (unresolvedCount > 0) {
|
|
7288
|
+
notes.push(`${unresolvedCount} unresolved reference(s) not shown \u2014 their targets could not be resolved during indexing.`);
|
|
7289
|
+
}
|
|
7290
|
+
return {
|
|
7291
|
+
symbol: input.symbol,
|
|
7292
|
+
calls,
|
|
7293
|
+
total: calls.length,
|
|
7294
|
+
...notes.length > 0 ? { note: notes.join(" ") } : {}
|
|
7295
|
+
};
|
|
7296
|
+
}
|
|
7297
|
+
};
|
|
7298
|
+
|
|
5780
7299
|
// src/codebase-index/codebase-search-tool.ts
|
|
5781
7300
|
var codebaseSearchTool = {
|
|
5782
7301
|
name: "codebase-search",
|
|
@@ -6492,9 +8011,11 @@ export {
|
|
|
6492
8011
|
buildIndexableText,
|
|
6493
8012
|
cancelPendingReindexes,
|
|
6494
8013
|
checkCodebaseIndexServerHealth,
|
|
8014
|
+
codebaseIncomingCallsTool,
|
|
6495
8015
|
codebaseIndexDirOverride,
|
|
6496
8016
|
codebaseIndexStats,
|
|
6497
8017
|
codebaseIndexTool,
|
|
8018
|
+
codebaseOutgoingCallsTool,
|
|
6498
8019
|
codebaseSearchTool,
|
|
6499
8020
|
codebaseStatsTool,
|
|
6500
8021
|
deadCodeScanTool,
|
|
@@ -6503,6 +8024,7 @@ export {
|
|
|
6503
8024
|
ensureCodebaseIndexServer,
|
|
6504
8025
|
fileGraphService2 as fileGraphService,
|
|
6505
8026
|
getIndexState,
|
|
8027
|
+
incomingCallsService2 as incomingCallsService,
|
|
6506
8028
|
indexCircuitBreaker,
|
|
6507
8029
|
internalKindToLspKind,
|
|
6508
8030
|
isIndexReady,
|
|
@@ -6511,6 +8033,7 @@ export {
|
|
|
6511
8033
|
isIndexing,
|
|
6512
8034
|
lspKindToInternalKind,
|
|
6513
8035
|
onIndexStateChange,
|
|
8036
|
+
outgoingCallsService2 as outgoingCallsService,
|
|
6514
8037
|
packageGraphService2 as packageGraphService,
|
|
6515
8038
|
resetIndexCircuitBreaker,
|
|
6516
8039
|
resolveIndexDir,
|