@wrongstack/tools 1.0.11 → 1.0.12

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.
Files changed (75) hide show
  1. package/dist/builtin.js +15 -15
  2. package/dist/{chunk-GU7TZE6Z.js → chunk-2BCRQAIW.js} +7 -7
  3. package/dist/{chunk-XRNN44QY.js → chunk-2IRHFHZY.js} +235 -71
  4. package/dist/{chunk-NEYUPZL6.js → chunk-6D3UIQMF.js} +651 -303
  5. package/dist/chunk-BJ5LXLKE.js +351 -0
  6. package/dist/{chunk-C6ZOXNXV.js → chunk-CSS42GRP.js} +2 -2
  7. package/dist/{chunk-SKRMUHTU.js → chunk-HSLDY55L.js} +41 -8
  8. package/dist/{chunk-ENE22I5W.js → chunk-K37AKCTN.js} +884 -362
  9. package/dist/{chunk-DKXMDO5F.js → chunk-K3VX6V5P.js} +3 -3
  10. package/dist/chunk-OIFY4XNP.js +855 -0
  11. package/dist/{chunk-7GOE2ZTZ.js → chunk-OMWC2OGT.js} +2 -2
  12. package/dist/{chunk-IE4KDAOB.js → chunk-OYQE2UNT.js} +3 -3
  13. package/dist/chunk-R3HR3QI3.js +97 -0
  14. package/dist/{chunk-XPVYZ2S6.js → chunk-RYLMSGR5.js} +2 -2
  15. package/dist/{chunk-HYHE4VNA.js → chunk-UMOP3VGK.js} +79 -45
  16. package/dist/{chunk-6OIEYJM4.js → chunk-VMNWQI4V.js} +2 -2
  17. package/dist/{chunk-2UVAIS5O.js → chunk-YPSVPCIE.js} +2 -2
  18. package/dist/{chunk-PWZUHZP7.js → chunk-Z77XMU3U.js} +2 -2
  19. package/dist/codebase-index/ast-invariant-engine.d.ts +19 -2
  20. package/dist/codebase-index/atlas-projection.d.ts +19 -0
  21. package/dist/codebase-index/background-indexer.d.ts +14 -0
  22. package/dist/codebase-index/circuit-breaker.d.ts +7 -0
  23. package/dist/codebase-index/codebase-ast-replace-tool.d.ts +1 -1
  24. package/dist/codebase-index/codebase-impact-analysis-tool.d.ts +6 -0
  25. package/dist/codebase-index/codebase-invariant-check-tool.d.ts +2 -0
  26. package/dist/codebase-index/codebase-targeted-test-tool.d.ts +26 -0
  27. package/dist/codebase-index/dead-code-scan.d.ts +4 -0
  28. package/dist/codebase-index/go-parser.d.ts +23 -3
  29. package/dist/codebase-index/graph-rank-pass.d.ts +10 -0
  30. package/dist/codebase-index/graph-rank.d.ts +8 -0
  31. package/dist/codebase-index/index.d.ts +1 -1
  32. package/dist/codebase-index/index.js +9 -9
  33. package/dist/codebase-index/lsp-kind.d.ts +3 -1
  34. package/dist/codebase-index/module-resolver.d.ts +21 -1
  35. package/dist/codebase-index/parser-batch.d.ts +6 -8
  36. package/dist/codebase-index/parser-output.d.ts +5 -1
  37. package/dist/codebase-index/parser-worker-script.js +4 -4
  38. package/dist/codebase-index/project-server-framing.d.ts +1 -17
  39. package/dist/codebase-index/project-server-watcher.d.ts +6 -0
  40. package/dist/codebase-index/project-server.js +72 -26
  41. package/dist/codebase-index/py-parser.d.ts +4 -4
  42. package/dist/codebase-index/rs-parser.d.ts +7 -0
  43. package/dist/codebase-index/toolchain-scripts.d.ts +64 -0
  44. package/dist/codebase-index/tree-sitter/queries.d.ts +14 -0
  45. package/dist/codebase-index/tree-sitter/util.d.ts +6 -1
  46. package/dist/codebase-index/tree-sitter-parser.d.ts +7 -0
  47. package/dist/codebase-index/worker.js +23 -16
  48. package/dist/codebase-index/writer-refs.d.ts +17 -2
  49. package/dist/codebase-index/writer-search-helpers.d.ts +9 -0
  50. package/dist/codebase-index/writer.d.ts +20 -1
  51. package/dist/edit.js +8 -8
  52. package/dist/{generic-parser-N2PLGBC2.js → generic-parser-IVWLFKMR.js} +2 -2
  53. package/dist/go-parser-LWV3L5V7.js +197 -0
  54. package/dist/index.js +17 -17
  55. package/dist/json-parser-PERV2JHX.js +204 -0
  56. package/dist/pack.js +16 -16
  57. package/dist/parser-dispatch-ZSWCQB6M.js +14 -0
  58. package/dist/patch.js +8 -8
  59. package/dist/{py-parser-4PFZYAJ7.js → py-parser-B6D6RGH7.js} +4 -4
  60. package/dist/read.js +8 -8
  61. package/dist/replace.js +8 -8
  62. package/dist/rs-parser-L7SP4GWJ.js +182 -0
  63. package/dist/tool-tier.js +17 -17
  64. package/dist/{tree-sitter-parser-GILYQLS4.js → tree-sitter-parser-KU6JF3ZC.js} +6 -4
  65. package/dist/{ts-parser-7W26YB26.js → ts-parser-PML6QIH7.js} +55 -23
  66. package/dist/write.js +8 -8
  67. package/dist/{yaml-parser-DFBC5FWH.js → yaml-parser-7BJN544B.js} +41 -6
  68. package/package.json +5 -5
  69. package/dist/chunk-4CE3RKEP.js +0 -390
  70. package/dist/chunk-KHUCKLOR.js +0 -899
  71. package/dist/chunk-ZKC5UNGD.js +0 -167
  72. package/dist/go-parser-Z4H6SR3R.js +0 -474
  73. package/dist/json-parser-IJD77VL4.js +0 -256
  74. package/dist/parser-dispatch-WG4DNBHH.js +0 -14
  75. package/dist/rs-parser-GIWPUA3T.js +0 -81
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  parseFilesContent
3
- } from "./chunk-KHUCKLOR.js";
3
+ } from "./chunk-BJ5LXLKE.js";
4
4
  import {
5
5
  recordFilesystemRead
6
- } from "./chunk-ZKC5UNGD.js";
6
+ } from "./chunk-OIFY4XNP.js";
7
7
  import {
8
8
  INDEXABLE_EXTENSIONS,
9
9
  LANG_FAMILY_ENTRIES,
@@ -75,6 +75,15 @@ var IndexCircuitBreaker = class {
75
75
  this.openedAt = this.now();
76
76
  }
77
77
  }
78
+ /**
79
+ * Release a probe that ended without a verdict (the caller aborted it).
80
+ * Neither success nor failure is recorded, but a half-open circuit must be
81
+ * able to admit the next probe — otherwise it refuses every request until a
82
+ * manual reset.
83
+ */
84
+ abandonProbe() {
85
+ this.probeInFlight = false;
86
+ }
78
87
  /** Force-close the circuit (manual recovery: `/codebase-reindex`). */
79
88
  reset() {
80
89
  this.state = "closed";
@@ -139,7 +148,8 @@ function buildWiringGraph(refs, options = {}) {
139
148
  const targetFile = fileOf.get(ref.toId);
140
149
  if (sourceFile !== void 0 && targetFile !== void 0 && sourceFile !== targetFile) {
141
150
  const imports = importsOf.get(sourceFile);
142
- if (imports === void 0) {
151
+ if (options.implicitlyVisible?.(sourceFile, targetFile, imports)) {
152
+ } else if (imports === void 0) {
143
153
  w *= UNVERIFIED_VISIBILITY_WEIGHT;
144
154
  } else if (!imports.has(targetFile)) {
145
155
  w *= CONTRADICTED_VISIBILITY_WEIGHT;
@@ -309,6 +319,71 @@ function aggregateFileRank(rows, fileOf) {
309
319
  return out;
310
320
  }
311
321
 
322
+ // src/codebase-index/graph-rank-pass.ts
323
+ import * as path from "node:path";
324
+ var DIRECTORY_SCOPED_FAMILIES = /* @__PURE__ */ new Set(["go", "jvm", "dotnet", "swift"]);
325
+ var DIRECTORY_IMPORT_FAMILIES = /* @__PURE__ */ new Set(["go", "jvm"]);
326
+ function familyOf(file) {
327
+ const lang = detectLang(file);
328
+ return lang ? languageFamily(lang) : void 0;
329
+ }
330
+ function createImplicitVisibility() {
331
+ const importedDirs = /* @__PURE__ */ new WeakMap();
332
+ return (sourceFile, targetFile, imports) => {
333
+ const family = familyOf(targetFile);
334
+ if (family === void 0) return false;
335
+ const targetDir = path.dirname(targetFile);
336
+ if (DIRECTORY_SCOPED_FAMILIES.has(family) && path.dirname(sourceFile) === targetDir && familyOf(sourceFile) === family) {
337
+ return true;
338
+ }
339
+ if (imports === void 0 || !DIRECTORY_IMPORT_FAMILIES.has(family)) return false;
340
+ let dirs = importedDirs.get(imports);
341
+ if (dirs === void 0) {
342
+ dirs = new Set([...imports].map((file) => path.dirname(file)));
343
+ importedDirs.set(imports, dirs);
344
+ }
345
+ return dirs.has(targetDir);
346
+ };
347
+ }
348
+ var RANK_VERSION = "1";
349
+ var RANK_VERSION_KEY = "rank_version";
350
+ var RANK_REFRESH_FILE_THRESHOLD = 25;
351
+ function shouldRefreshRanks(store, opts) {
352
+ if (opts.force) return true;
353
+ if (!opts.files) return true;
354
+ if (store.getMetadata(RANK_VERSION_KEY) !== RANK_VERSION) return true;
355
+ if (store.getRankCounts().symbols === 0) return true;
356
+ return opts.files.length >= RANK_REFRESH_FILE_THRESHOLD;
357
+ }
358
+ function runGraphRankPass(store, errors) {
359
+ const startMs = Date.now();
360
+ try {
361
+ const refs = store.getAllResolvedRefs();
362
+ const fileOf = /* @__PURE__ */ new Map();
363
+ for (const symbol of store.getAllSymbols()) fileOf.set(symbol.id, symbol.file);
364
+ const graph = buildWiringGraph(refs, {
365
+ candidates: store.getSymbolNameCandidates(),
366
+ fileOf,
367
+ importsOf: store.getImportVisibility(),
368
+ implicitlyVisible: createImplicitVisibility()
369
+ });
370
+ const scores = pageRank(graph);
371
+ const symbolRows = toSymbolRankRows(graph, scores);
372
+ const fileRows = aggregateFileRank(symbolRows, fileOf);
373
+ store.replaceRanks(symbolRows, fileRows);
374
+ store.setMetadata(RANK_VERSION_KEY, RANK_VERSION);
375
+ return {
376
+ computed: true,
377
+ symbols: symbolRows.length,
378
+ files: fileRows.length,
379
+ durationMs: Date.now() - startMs
380
+ };
381
+ } catch (error) {
382
+ errors.push(`graph rank: ${error instanceof Error ? error.message : String(error)}`);
383
+ return { computed: false, symbols: 0, files: 0, durationMs: Date.now() - startMs };
384
+ }
385
+ }
386
+
312
387
  // src/codebase-index/graph-adjacency-cache.ts
313
388
  var cached = null;
314
389
  function cacheKey(projectRoot, indexDir) {
@@ -334,7 +409,8 @@ function getWiringSnapshot(store, projectRoot, indexDir) {
334
409
  const graph = buildWiringGraph(store.getAllResolvedRefs(), {
335
410
  candidates: store.getSymbolNameCandidates(),
336
411
  fileOf,
337
- importsOf: store.getImportVisibility()
412
+ importsOf: store.getImportVisibility(),
413
+ implicitlyVisible: createImplicitVisibility()
338
414
  });
339
415
  cached = { key, stamp, graph, fileOf };
340
416
  return { graph, fileOf };
@@ -560,6 +636,17 @@ function retrieveContext(store, projectRoot, indexDir, options, relativeOf) {
560
636
  score: scoreBySymbolId.get(row.id) ?? 0,
561
637
  snippet: ""
562
638
  }));
639
+ let floor = Number.POSITIVE_INFINITY;
640
+ for (const score of scoreBySymbolId.values()) if (score < floor) floor = score;
641
+ if (!Number.isFinite(floor) || floor <= 0) floor = 1;
642
+ const unwired = search.results.filter((hit) => !scoreBySymbolId.has(hit.id));
643
+ const maxLexical = Math.max(0, ...unwired.map((hit) => hit.score));
644
+ for (const hit of unwired) {
645
+ const lexical = maxLexical > 0 && hit.score > 0 ? hit.score / maxLexical : 1;
646
+ const score = floor * 0.5 * lexical;
647
+ scoreBySymbolId.set(hit.id, score);
648
+ walked.push({ ...hit, score });
649
+ }
563
650
  const grouped = groupIntoEntries(
564
651
  walked,
565
652
  scoreBySymbolId,
@@ -619,45 +706,6 @@ function groupIntoEntries(symbols, scoreBySymbolId, fileOf, limit, symbolsPerFil
619
706
  return { entries, totalCandidates };
620
707
  }
621
708
 
622
- // src/codebase-index/graph-rank-pass.ts
623
- var RANK_VERSION = "1";
624
- var RANK_VERSION_KEY = "rank_version";
625
- var RANK_REFRESH_FILE_THRESHOLD = 25;
626
- function shouldRefreshRanks(store, opts) {
627
- if (opts.force) return true;
628
- if (!opts.files) return true;
629
- if (store.getMetadata(RANK_VERSION_KEY) !== RANK_VERSION) return true;
630
- if (store.getRankCounts().symbols === 0) return true;
631
- return opts.files.length >= RANK_REFRESH_FILE_THRESHOLD;
632
- }
633
- function runGraphRankPass(store, errors) {
634
- const startMs = Date.now();
635
- try {
636
- const refs = store.getAllResolvedRefs();
637
- const fileOf = /* @__PURE__ */ new Map();
638
- for (const symbol of store.getAllSymbols()) fileOf.set(symbol.id, symbol.file);
639
- const graph = buildWiringGraph(refs, {
640
- candidates: store.getSymbolNameCandidates(),
641
- fileOf,
642
- importsOf: store.getImportVisibility()
643
- });
644
- const scores = pageRank(graph);
645
- const symbolRows = toSymbolRankRows(graph, scores);
646
- const fileRows = aggregateFileRank(symbolRows, fileOf);
647
- store.replaceRanks(symbolRows, fileRows);
648
- store.setMetadata(RANK_VERSION_KEY, RANK_VERSION);
649
- return {
650
- computed: true,
651
- symbols: symbolRows.length,
652
- files: fileRows.length,
653
- durationMs: Date.now() - startMs
654
- };
655
- } catch (error) {
656
- errors.push(`graph rank: ${error instanceof Error ? error.message : String(error)}`);
657
- return { computed: false, symbols: 0, files: 0, durationMs: Date.now() - startMs };
658
- }
659
- }
660
-
661
709
  // src/codebase-index/bm25.ts
662
710
  var K1 = 1.5;
663
711
  var B = 0.75;
@@ -1037,36 +1085,27 @@ function decorateGraphNodes(stmt, nodes, packageOf) {
1037
1085
  }
1038
1086
 
1039
1087
  // src/codebase-index/lsp-kind.ts
1088
+ var LSP_TO_INTERNAL_KINDS = {
1089
+ [2 /* Module */]: ["mod"],
1090
+ [3 /* Namespace */]: ["namespace"],
1091
+ [5 /* Class */]: ["class"],
1092
+ [6 /* Method */]: ["method"],
1093
+ [7 /* Property */]: ["property"],
1094
+ [8 /* Field */]: ["property"],
1095
+ [10 /* Enum */]: ["enum"],
1096
+ [11 /* Interface */]: ["interface", "trait"],
1097
+ [12 /* Function */]: ["function"],
1098
+ [13 /* Variable */]: ["var", "let"],
1099
+ [14 /* Constant */]: ["const", "static"],
1100
+ [19 /* Object */]: ["object"],
1101
+ [23 /* Struct */]: ["struct"],
1102
+ [26 /* TypeParameter */]: ["type"]
1103
+ };
1104
+ function lspKindToInternalKinds(k) {
1105
+ return LSP_TO_INTERNAL_KINDS[k] ?? [];
1106
+ }
1040
1107
  function lspKindToInternalKind(k) {
1041
- switch (k) {
1042
- case 5 /* Class */:
1043
- return "class";
1044
- case 6 /* Method */:
1045
- return "method";
1046
- case 7 /* Property */:
1047
- case 8 /* Field */:
1048
- return "property";
1049
- case 9 /* Constructor */:
1050
- return "class";
1051
- case 10 /* Enum */:
1052
- return "enum";
1053
- case 11 /* Interface */:
1054
- return "interface";
1055
- case 12 /* Function */:
1056
- return "function";
1057
- case 13 /* Variable */:
1058
- return "var";
1059
- case 14 /* Constant */:
1060
- return "const";
1061
- case 22 /* EnumMember */:
1062
- return "enum";
1063
- case 26 /* TypeParameter */:
1064
- return "type";
1065
- case 3 /* Namespace */:
1066
- return "namespace";
1067
- default:
1068
- return null;
1069
- }
1108
+ return LSP_TO_INTERNAL_KINDS[k]?.[0] ?? null;
1070
1109
  }
1071
1110
  function internalKindToLspKind(k) {
1072
1111
  switch (k) {
@@ -1092,7 +1131,17 @@ function internalKindToLspKind(k) {
1092
1131
  return 3 /* Namespace */;
1093
1132
  case "type":
1094
1133
  return 26 /* TypeParameter */;
1095
- // parameter and other internal-only kinds have no LSP equivalent
1134
+ case "struct":
1135
+ return 23 /* Struct */;
1136
+ case "trait":
1137
+ return 11 /* Interface */;
1138
+ case "mod":
1139
+ return 2 /* Module */;
1140
+ case "object":
1141
+ return 19 /* Object */;
1142
+ case "static":
1143
+ return 14 /* Constant */;
1144
+ // parameter, impl, literal, schema have no LSP equivalent
1096
1145
  default:
1097
1146
  return null;
1098
1147
  }
@@ -1217,8 +1266,16 @@ function countFileVectorsWithStatement(stmt) {
1217
1266
  stmt("SELECT COUNT(*) AS n FROM file_vectors").get()?.n ?? 0
1218
1267
  );
1219
1268
  }
1269
+ function l2Norm(vector) {
1270
+ let sum = 0;
1271
+ for (let i = 0; i < vector.length; i++) sum += vector[i] * vector[i];
1272
+ return Math.sqrt(sum);
1273
+ }
1220
1274
  function searchFileVectorsWithStatement(stmt, query, limit, minScore) {
1221
- if (limit <= 0 || query.length === 0) return [];
1275
+ if (!(limit > 0) || query.length === 0) return [];
1276
+ const queryNorm = l2Norm(query);
1277
+ if (!(queryNorm > 0)) return [];
1278
+ const floor = Number.isFinite(minScore) ? minScore : Number.NEGATIVE_INFINITY;
1222
1279
  const rows = stmt("SELECT file, vector FROM file_vectors").all();
1223
1280
  const hits = [];
1224
1281
  for (const row of rows) {
@@ -1229,9 +1286,12 @@ function searchFileVectorsWithStatement(stmt, query, limit, minScore) {
1229
1286
  continue;
1230
1287
  }
1231
1288
  if (vector.length !== query.length) continue;
1289
+ const vectorNorm = l2Norm(vector);
1290
+ if (!(vectorNorm > 0)) continue;
1232
1291
  let dot = 0;
1233
1292
  for (let i = 0; i < query.length; i++) dot += query[i] * vector[i];
1234
- if (dot >= minScore) hits.push({ file: row.file, score: dot });
1293
+ const score = dot / (queryNorm * vectorNorm);
1294
+ if (score >= floor) hits.push({ file: row.file, score });
1235
1295
  }
1236
1296
  hits.sort((a, b) => b.score - a.score || a.file.localeCompare(b.file));
1237
1297
  return hits.slice(0, limit);
@@ -1239,7 +1299,7 @@ function searchFileVectorsWithStatement(stmt, query, limit, minScore) {
1239
1299
 
1240
1300
  // src/codebase-index/writer.ts
1241
1301
  import * as fs3 from "node:fs";
1242
- import * as path3 from "node:path";
1302
+ import * as path4 from "node:path";
1243
1303
 
1244
1304
  // src/codebase-index/sqlite-runtime.ts
1245
1305
  import { toErrorMessage } from "@wrongstack/core/utils";
@@ -1311,7 +1371,7 @@ function runSqliteWithRetry(fn) {
1311
1371
 
1312
1372
  // src/codebase-index/writer-admin.ts
1313
1373
  import * as fs from "node:fs";
1314
- import * as path from "node:path";
1374
+ import * as path2 from "node:path";
1315
1375
  var DB_FILE = "index.db";
1316
1376
  function getAllIndexableWithStatement(stmt) {
1317
1377
  const rows = stmt("SELECT id, name, signature, doc_comment FROM symbols").all();
@@ -1386,7 +1446,7 @@ function getAllFileMetasWithStatement(stmt) {
1386
1446
  }
1387
1447
  function getIndexDbSizeBytes(indexDir) {
1388
1448
  try {
1389
- return fs.statSync(path.join(indexDir, DB_FILE)).size;
1449
+ return fs.statSync(path2.join(indexDir, DB_FILE)).size;
1390
1450
  } catch {
1391
1451
  return 0;
1392
1452
  }
@@ -1492,7 +1552,7 @@ function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
1492
1552
 
1493
1553
  // src/codebase-index/module-roots.ts
1494
1554
  import * as fs2 from "node:fs/promises";
1495
- import * as path2 from "node:path";
1555
+ import * as path3 from "node:path";
1496
1556
  function toPortablePath(file) {
1497
1557
  return file.includes("\\") ? file.replace(/\\/g, "/") : file;
1498
1558
  }
@@ -1557,7 +1617,7 @@ function ancestorsOf(dir, stopAt) {
1557
1617
  for (; ; ) {
1558
1618
  out.push(current);
1559
1619
  if (current === stopAt || current.length <= stopAt.length) break;
1560
- const parent = path2.posix.dirname(current);
1620
+ const parent = path3.posix.dirname(current);
1561
1621
  if (parent === current) break;
1562
1622
  current = parent;
1563
1623
  }
@@ -1568,8 +1628,12 @@ var MARKER_PROBES = [
1568
1628
  kind: "npm",
1569
1629
  file: "package.json",
1570
1630
  build: (dir, source) => {
1571
- const name = parsePackageJsonName(source) ?? path2.posix.basename(dir);
1572
- return { name, importPath: name, sourceRoots: [dir] };
1631
+ const declared = parsePackageJsonName(source);
1632
+ return {
1633
+ name: declared ?? path3.posix.basename(dir),
1634
+ importPath: declared,
1635
+ sourceRoots: [dir]
1636
+ };
1573
1637
  }
1574
1638
  },
1575
1639
  {
@@ -1582,7 +1646,7 @@ var MARKER_PROBES = [
1582
1646
  name: `crate:${name}`,
1583
1647
  // Rust paths use underscores where crate names often use dashes.
1584
1648
  importPath: name.replace(/-/g, "_"),
1585
- sourceRoots: [path2.posix.join(dir, "src")]
1649
+ sourceRoots: [path3.posix.join(dir, "src")]
1586
1650
  };
1587
1651
  }
1588
1652
  },
@@ -1599,13 +1663,13 @@ var MARKER_PROBES = [
1599
1663
  kind: "python",
1600
1664
  file: "pyproject.toml",
1601
1665
  build: (dir, source) => {
1602
- const name = parseTomlTableName(source, ["project", "tool.poetry"]) ?? path2.posix.basename(dir);
1666
+ const name = parseTomlTableName(source, ["project", "tool.poetry"]) ?? path3.posix.basename(dir);
1603
1667
  return {
1604
1668
  name: `py:${name}`,
1605
1669
  importPath: void 0,
1606
1670
  // `src/` layout is the packaging-guide default; the root itself covers
1607
1671
  // the flat layout. Both are probed, missing ones simply never match.
1608
- sourceRoots: [path2.posix.join(dir, "src"), dir]
1672
+ sourceRoots: [path3.posix.join(dir, "src"), dir]
1609
1673
  };
1610
1674
  }
1611
1675
  },
@@ -1613,24 +1677,24 @@ var MARKER_PROBES = [
1613
1677
  kind: "python",
1614
1678
  file: "setup.py",
1615
1679
  build: (dir) => ({
1616
- name: `py:${path2.posix.basename(dir)}`,
1680
+ name: `py:${path3.posix.basename(dir)}`,
1617
1681
  importPath: void 0,
1618
- sourceRoots: [path2.posix.join(dir, "src"), dir]
1682
+ sourceRoots: [path3.posix.join(dir, "src"), dir]
1619
1683
  })
1620
1684
  },
1621
1685
  {
1622
1686
  kind: "maven",
1623
1687
  file: "pom.xml",
1624
1688
  build: (dir, source) => {
1625
- const artifactId = parsePomArtifactId(source) ?? path2.posix.basename(dir);
1689
+ const artifactId = parsePomArtifactId(source) ?? path3.posix.basename(dir);
1626
1690
  return {
1627
1691
  name: `mvn:${artifactId}`,
1628
1692
  importPath: void 0,
1629
1693
  sourceRoots: [
1630
- path2.posix.join(dir, "src/main/java"),
1631
- path2.posix.join(dir, "src/main/kotlin"),
1632
- path2.posix.join(dir, "src/main/scala"),
1633
- path2.posix.join(dir, "src/test/java")
1694
+ path3.posix.join(dir, "src/main/java"),
1695
+ path3.posix.join(dir, "src/main/kotlin"),
1696
+ path3.posix.join(dir, "src/main/scala"),
1697
+ path3.posix.join(dir, "src/test/java")
1634
1698
  ]
1635
1699
  };
1636
1700
  }
@@ -1648,12 +1712,12 @@ var MARKER_PROBES = [
1648
1712
  ];
1649
1713
  function buildGradleRoot(dir) {
1650
1714
  return {
1651
- name: `gradle:${path2.posix.basename(dir)}`,
1715
+ name: `gradle:${path3.posix.basename(dir)}`,
1652
1716
  importPath: void 0,
1653
1717
  sourceRoots: [
1654
- path2.posix.join(dir, "src/main/java"),
1655
- path2.posix.join(dir, "src/main/kotlin"),
1656
- path2.posix.join(dir, "src/main/scala")
1718
+ path3.posix.join(dir, "src/main/java"),
1719
+ path3.posix.join(dir, "src/main/kotlin"),
1720
+ path3.posix.join(dir, "src/main/scala")
1657
1721
  ]
1658
1722
  };
1659
1723
  }
@@ -1675,6 +1739,17 @@ async function probeDotnetRoot(dir) {
1675
1739
  sourceRoots: [dir]
1676
1740
  };
1677
1741
  }
1742
+ var MARKER_PROBE_CONCURRENCY = 16;
1743
+ async function forEachLimited(items, limit, fn) {
1744
+ let next = 0;
1745
+ const worker = async () => {
1746
+ while (next < items.length) {
1747
+ const item = items[next++];
1748
+ await fn(item);
1749
+ }
1750
+ };
1751
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
1752
+ }
1678
1753
  async function detectModuleRoots(projectRoot, files) {
1679
1754
  const root = toPortablePath(projectRoot).replace(/\/+$/, "");
1680
1755
  const langsByDir = /* @__PURE__ */ new Map();
@@ -1682,7 +1757,7 @@ async function detectModuleRoots(projectRoot, files) {
1682
1757
  const portable = toPortablePath(file);
1683
1758
  const lang = detectLang(portable);
1684
1759
  if (!lang) continue;
1685
- const dir = path2.posix.dirname(portable);
1760
+ const dir = path3.posix.dirname(portable);
1686
1761
  let langs = langsByDir.get(dir);
1687
1762
  if (!langs) {
1688
1763
  langs = /* @__PURE__ */ new Set();
@@ -1702,21 +1777,19 @@ async function detectModuleRoots(projectRoot, files) {
1702
1777
  }
1703
1778
  }
1704
1779
  const roots = [];
1705
- await Promise.all(
1706
- [...candidates].map(async ([dir, langs]) => {
1707
- for (const probe of MARKER_PROBES) {
1708
- if (!LANGS_BY_KIND[probe.kind].some((lang) => langs.has(lang))) continue;
1709
- const source = await readTextIfPresent(path2.posix.join(dir, probe.file));
1710
- if (source === void 0) continue;
1711
- const built = probe.build(dir, source);
1712
- if (built) roots.push({ dir, kind: probe.kind, ...built });
1713
- }
1714
- if (LANGS_BY_KIND.dotnet.some((lang) => langs.has(lang))) {
1715
- const dotnet = await probeDotnetRoot(dir);
1716
- if (dotnet) roots.push(dotnet);
1717
- }
1718
- })
1719
- );
1780
+ await forEachLimited([...candidates], MARKER_PROBE_CONCURRENCY, async ([dir, langs]) => {
1781
+ for (const probe of MARKER_PROBES) {
1782
+ if (!LANGS_BY_KIND[probe.kind].some((lang) => langs.has(lang))) continue;
1783
+ const source = await readTextIfPresent(path3.posix.join(dir, probe.file));
1784
+ if (source === void 0) continue;
1785
+ const built = probe.build(dir, source);
1786
+ if (built) roots.push({ dir, kind: probe.kind, ...built });
1787
+ }
1788
+ if (LANGS_BY_KIND.dotnet.some((lang) => langs.has(lang))) {
1789
+ const dotnet = await probeDotnetRoot(dir);
1790
+ if (dotnet) roots.push(dotnet);
1791
+ }
1792
+ });
1720
1793
  roots.sort((a, b) => b.dir.length - a.dir.length || a.dir.localeCompare(b.dir));
1721
1794
  return { projectRoot: root, roots };
1722
1795
  }
@@ -1744,13 +1817,13 @@ function derivePackageFromLayout(filePath) {
1744
1817
  }
1745
1818
  function pythonPackageLabel(structure, file, initDirs) {
1746
1819
  const portable = toPortablePath(file);
1747
- const dir = path2.posix.dirname(portable);
1820
+ const dir = path3.posix.dirname(portable);
1748
1821
  if (!initDirs.has(dir)) return void 0;
1749
1822
  const segments = [];
1750
1823
  let current = dir;
1751
1824
  while (initDirs.has(current) && current.length > structure.projectRoot.length) {
1752
- segments.unshift(path2.posix.basename(current));
1753
- current = path2.posix.dirname(current);
1825
+ segments.unshift(path3.posix.basename(current));
1826
+ current = path3.posix.dirname(current);
1754
1827
  }
1755
1828
  return segments.length > 0 ? `py:${segments.join(".")}` : void 0;
1756
1829
  }
@@ -1758,8 +1831,8 @@ function assignPackageLabels(structure, files) {
1758
1831
  const initDirs = /* @__PURE__ */ new Set();
1759
1832
  for (const file of files) {
1760
1833
  const portable = toPortablePath(file);
1761
- if (path2.posix.basename(portable) === "__init__.py") {
1762
- initDirs.add(path2.posix.dirname(portable));
1834
+ if (path3.posix.basename(portable) === "__init__.py") {
1835
+ initDirs.add(path3.posix.dirname(portable));
1763
1836
  }
1764
1837
  }
1765
1838
  const labels = /* @__PURE__ */ new Map();
@@ -1768,12 +1841,12 @@ function assignPackageLabels(structure, files) {
1768
1841
  const lang = detectLang(portable);
1769
1842
  if (lang === "go") {
1770
1843
  const owner2 = findOwningRoot(structure, portable, ["go"]);
1771
- const dir = path2.posix.dirname(portable);
1844
+ const dir = path3.posix.dirname(portable);
1772
1845
  if (owner2?.importPath) {
1773
- const relative3 = path2.posix.relative(owner2.dir, dir);
1846
+ const relative3 = path3.posix.relative(owner2.dir, dir);
1774
1847
  labels.set(file, relative3 ? `${owner2.importPath}/${relative3}` : owner2.importPath);
1775
1848
  } else {
1776
- labels.set(file, `go:${path2.posix.relative(structure.projectRoot, dir) || "."}`);
1849
+ labels.set(file, `go:${path3.posix.relative(structure.projectRoot, dir) || "."}`);
1777
1850
  }
1778
1851
  continue;
1779
1852
  }
@@ -1969,6 +2042,34 @@ function chunkedIdQuery(stmt, ids, buildSql, extraArgs = []) {
1969
2042
  }
1970
2043
  return results;
1971
2044
  }
2045
+ function chunkedValueQuery(stmt, values, buildSql) {
2046
+ const results = [];
2047
+ let cursor = 0;
2048
+ for (const take of inListChunks(values.length, MAX_SQL_VARS2)) {
2049
+ const chunk = padToInBucket(values.slice(cursor, cursor + take));
2050
+ cursor += take;
2051
+ results.push(...stmt(buildSql(placeholders(chunk.length))).all(...chunk));
2052
+ }
2053
+ return results;
2054
+ }
2055
+ function refCountsTouching(stmt, localIds) {
2056
+ const local = new Set(localIds);
2057
+ const outgoing = chunkedIdQuery(
2058
+ stmt,
2059
+ localIds,
2060
+ (ph) => `SELECT from_id, to_id, call_type, COUNT(*) AS n FROM refs
2061
+ WHERE from_id IN (${ph}) AND to_id IS NOT NULL
2062
+ GROUP BY from_id, to_id, call_type`
2063
+ );
2064
+ const incoming = chunkedIdQuery(
2065
+ stmt,
2066
+ localIds,
2067
+ (ph) => `SELECT from_id, to_id, call_type, COUNT(*) AS n FROM refs
2068
+ WHERE to_id IN (${ph})
2069
+ GROUP BY from_id, to_id, call_type`
2070
+ ).filter((row) => !local.has(row.from_id));
2071
+ return [...outgoing, ...incoming];
2072
+ }
1972
2073
  function getSymbolsByIdsWithStatement(stmt, ids) {
1973
2074
  if (ids.length === 0) return [];
1974
2075
  return chunkedIdQuery(
@@ -2010,6 +2111,9 @@ function mapCallSiteRow(row) {
2010
2111
  line: row.ref_line
2011
2112
  };
2012
2113
  }
2114
+ function compareCallSiteRows(a, b) {
2115
+ return a.ref_line - b.ref_line || (a.sym_file < b.sym_file ? -1 : a.sym_file > b.sym_file ? 1 : 0) || a.sym_line - b.sym_line || a.sym_id - b.sym_id;
2116
+ }
2013
2117
  function resolveIndexedFiles(stmt, file) {
2014
2118
  const rows = stmt(
2015
2119
  `SELECT DISTINCT file FROM symbols WHERE ${indexedFileMatchSql("file")} ORDER BY length(file), file`
@@ -2037,21 +2141,22 @@ function resolveSymbolIds(stmt, symbolName, file) {
2037
2141
  }
2038
2142
  return ids;
2039
2143
  }
2040
- function findIncomingCallsByName(stmt, symbolName, file, limit) {
2041
- const targetIds = resolveSymbolIds(stmt, symbolName, file);
2042
- if (targetIds.length === 0)
2043
- return { calls: [], symbolFound: false, ambiguous: false, totalMatches: 0 };
2044
- let matchIds = targetIds;
2045
- let ambiguous = false;
2046
- if (file !== void 0) {
2047
- const allNamedIds = resolveSymbolIds(stmt, symbolName, void 0);
2048
- if (allNamedIds.length > targetIds.length) {
2049
- matchIds = allNamedIds;
2050
- ambiguous = true;
2051
- }
2052
- }
2053
- const useFallback = !file;
2054
- const rows = chunkedIdQuery(
2144
+ var MAX_DISAMBIGUATION_FILES = 64;
2145
+ function directIncomingRows(stmt, matchIds, scopedTargetIds) {
2146
+ const targetFiles = scopedTargetIds === null ? [] : chunkedIdQuery(
2147
+ stmt,
2148
+ scopedTargetIds,
2149
+ (ph) => `SELECT DISTINCT file FROM symbols WHERE id IN (${ph})`
2150
+ ).map((row) => row.file);
2151
+ const refine = targetFiles.length > 0 && targetFiles.length <= MAX_DISAMBIGUATION_FILES;
2152
+ const fph = refine ? targetFiles.map(() => "?").join(",") : "";
2153
+ const evidenceFilter = refine ? `AND (r.to_file IS NULL OR r.to_file IN (${fph}))
2154
+ AND NOT EXISTS (
2155
+ SELECT 1 FROM refs ri JOIN symbols si ON si.id = ri.from_id
2156
+ WHERE si.file = s.file AND ri.call_type = 'import' AND ri.to_name = r.to_name
2157
+ AND ri.to_file IS NOT NULL AND ri.to_file NOT IN (${fph})
2158
+ )` : "";
2159
+ return chunkedIdQuery(
2055
2160
  stmt,
2056
2161
  matchIds,
2057
2162
  (ph) => `SELECT
@@ -2067,9 +2172,26 @@ function findIncomingCallsByName(stmt, symbolName, file, limit) {
2067
2172
  FROM refs r
2068
2173
  JOIN symbols s ON s.id = r.from_id
2069
2174
  WHERE r.to_id IN (${ph})
2175
+ ${evidenceFilter}
2070
2176
  ORDER BY r.line, r.id`,
2071
- []
2177
+ refine ? [...targetFiles, ...targetFiles] : []
2072
2178
  );
2179
+ }
2180
+ function findIncomingCallsByName(stmt, symbolName, file, limit) {
2181
+ const targetIds = resolveSymbolIds(stmt, symbolName, file);
2182
+ if (targetIds.length === 0)
2183
+ return { calls: [], symbolFound: false, ambiguous: false, totalMatches: 0 };
2184
+ let matchIds = targetIds;
2185
+ let ambiguous = false;
2186
+ if (file !== void 0) {
2187
+ const allNamedIds = resolveSymbolIds(stmt, symbolName, void 0);
2188
+ if (allNamedIds.length > targetIds.length) {
2189
+ matchIds = allNamedIds;
2190
+ ambiguous = true;
2191
+ }
2192
+ }
2193
+ const useFallback = !file;
2194
+ const rows = directIncomingRows(stmt, matchIds, ambiguous ? targetIds : null);
2073
2195
  if (useFallback) {
2074
2196
  const fallbackRows = stmt(
2075
2197
  `SELECT
@@ -2089,7 +2211,7 @@ function findIncomingCallsByName(stmt, symbolName, file, limit) {
2089
2211
  ).all(symbolName);
2090
2212
  rows.push(...fallbackRows);
2091
2213
  }
2092
- rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
2214
+ rows.sort(compareCallSiteRows);
2093
2215
  const allCalls = rows.map(mapCallSiteRow);
2094
2216
  return {
2095
2217
  calls: allCalls.slice(0, limit),
@@ -2127,7 +2249,7 @@ function findOutgoingCallsByName(stmt, symbolName, file, limit) {
2127
2249
  ORDER BY r.line, r.id`,
2128
2250
  []
2129
2251
  );
2130
- rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
2252
+ rows.sort(compareCallSiteRows);
2131
2253
  const calls = rows.map(mapCallSiteRow).slice(0, limit);
2132
2254
  return { calls, symbolFound: true, unresolvedCount, totalMatches: rows.length };
2133
2255
  }
@@ -2149,6 +2271,32 @@ function runCteWithSeeds(stmt, seedIds, buildSql) {
2149
2271
  stmt("DROP TABLE IF EXISTS _cte_seeds").run();
2150
2272
  }
2151
2273
  }
2274
+ function annotateDirectEdges(stmt, rows, seedIds, seedColumn, otherColumn) {
2275
+ if (rows.length === 0) return;
2276
+ const edges = chunkedIdQuery(
2277
+ stmt,
2278
+ [...seedIds],
2279
+ (ph) => `SELECT ${otherColumn} AS sym_id, call_type, line
2280
+ FROM refs
2281
+ WHERE ${seedColumn} IN (${ph}) AND ${otherColumn} IS NOT NULL`,
2282
+ []
2283
+ );
2284
+ const best = /* @__PURE__ */ new Map();
2285
+ for (const edge of edges) {
2286
+ const current = best.get(edge.sym_id);
2287
+ const edgeIsImport = edge.call_type === "import";
2288
+ const currentIsImport = current?.call_type === "import";
2289
+ if (!current || currentIsImport && !edgeIsImport || currentIsImport === edgeIsImport && edge.line < current.line) {
2290
+ best.set(edge.sym_id, { call_type: edge.call_type, line: edge.line });
2291
+ }
2292
+ }
2293
+ for (const row of rows) {
2294
+ const edge = best.get(row.sym_id);
2295
+ if (!edge) continue;
2296
+ row.call_type = edge.call_type;
2297
+ row.ref_line = edge.line;
2298
+ }
2299
+ }
2152
2300
  function findTransitiveIncomingCallsByName(stmt, symbolName, file, limit) {
2153
2301
  const targetIds = resolveSymbolIds(stmt, symbolName, file);
2154
2302
  if (targetIds.length === 0)
@@ -2162,10 +2310,11 @@ function findTransitiveIncomingCallsByName(stmt, symbolName, file, limit) {
2162
2310
  ambiguous = true;
2163
2311
  }
2164
2312
  }
2313
+ const anchorCallerIds = ambiguous ? [...new Set(directIncomingRows(stmt, matchIds, targetIds).map((row) => row.sym_id))] : null;
2165
2314
  const cteSql = (seedSource) => `WITH RECURSIVE incoming_tree(from_id) AS (
2166
- SELECT r.from_id
2315
+ ${anchorCallerIds === null ? `SELECT r.from_id
2167
2316
  FROM refs r
2168
- WHERE r.to_id IN (${seedSource})
2317
+ WHERE r.to_id IN (${seedSource})` : `SELECT s0.id FROM symbols s0 WHERE s0.id IN (${seedSource})`}
2169
2318
 
2170
2319
  UNION
2171
2320
 
@@ -2187,7 +2336,8 @@ function findTransitiveIncomingCallsByName(stmt, symbolName, file, limit) {
2187
2336
  JOIN symbols s ON s.id = it.from_id
2188
2337
  GROUP BY s.id
2189
2338
  ORDER BY s.file, s.line`;
2190
- const rows = runCteWithSeeds(stmt, matchIds, cteSql);
2339
+ const rows = anchorCallerIds !== null && anchorCallerIds.length === 0 ? [] : runCteWithSeeds(stmt, anchorCallerIds ?? matchIds, cteSql);
2340
+ annotateDirectEdges(stmt, rows, matchIds, "to_id", "from_id");
2191
2341
  if (!file) {
2192
2342
  const fallbackRows = stmt(
2193
2343
  `SELECT
@@ -2207,7 +2357,7 @@ function findTransitiveIncomingCallsByName(stmt, symbolName, file, limit) {
2207
2357
  ).all(symbolName);
2208
2358
  rows.push(...fallbackRows);
2209
2359
  }
2210
- rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
2360
+ rows.sort(compareCallSiteRows);
2211
2361
  const allCalls = rows.map(mapCallSiteRow);
2212
2362
  return {
2213
2363
  calls: allCalls.slice(0, limit),
@@ -2252,6 +2402,7 @@ function findTransitiveOutgoingCallsByName(stmt, symbolName, file, limit) {
2252
2402
  GROUP BY s.id
2253
2403
  ORDER BY s.file, s.line`;
2254
2404
  const rows = runCteWithSeeds(stmt, sourceIds, cteSql);
2405
+ annotateDirectEdges(stmt, rows, sourceIds, "from_id", "to_id");
2255
2406
  const calls = rows.map(mapCallSiteRow).slice(0, limit);
2256
2407
  return { calls, symbolFound: true, unresolvedCount, totalMatches: rows.length };
2257
2408
  }
@@ -2361,33 +2512,29 @@ function getFileGraphWithStatement(stmt, packageFilter) {
2361
2512
  const pkgFilePaths = allFiles.filter((f) => matchesIndexedPackageFilter(f.file, packageOf(f.file), packageFilter)).map((f) => f.file);
2362
2513
  const localFiles = new Set(pkgFilePaths);
2363
2514
  if (localFiles.size === 0) return { nodes: [], edges: [] };
2364
- const filePlaceholders = [...localFiles].map(() => "?").join(",");
2365
- const pkgSyms = stmt(
2366
- `SELECT file, id, name, kind, lang, line FROM symbols WHERE file IN (${filePlaceholders}) ORDER BY id`
2367
- ).all(...pkgFilePaths);
2515
+ const pkgSyms = chunkedValueQuery(
2516
+ stmt,
2517
+ [...localFiles],
2518
+ (ph) => `SELECT file, id, name, kind, lang, line FROM symbols WHERE file IN (${ph})`
2519
+ ).sort((a, b) => a.id - b.id);
2368
2520
  const { fileNodes, symToFile, fileStats, ensureFileNode } = buildFileGraphNodeState(
2369
2521
  pkgSyms,
2370
2522
  localFiles,
2371
2523
  packageOf
2372
2524
  );
2373
- const refRows = stmt(
2374
- `SELECT r.from_id, r.to_id, r.call_type, COUNT(*) AS n
2375
- FROM refs r
2376
- WHERE (r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
2377
- OR r.to_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders})))
2378
- AND r.to_id IS NOT NULL
2379
- GROUP BY r.from_id, r.to_id, r.call_type`
2380
- ).all(...pkgFilePaths, ...pkgFilePaths);
2381
- const knownSymIds = new Set(pkgSyms.map((s) => s.id));
2525
+ const localIds = pkgSyms.map((s) => s.id);
2526
+ const refRows = refCountsTouching(stmt, localIds);
2527
+ const knownSymIds = new Set(localIds);
2382
2528
  const crossRefIds = /* @__PURE__ */ new Set();
2383
2529
  for (const r of refRows) {
2384
2530
  if (!knownSymIds.has(r.from_id)) crossRefIds.add(r.from_id);
2385
2531
  if (!knownSymIds.has(r.to_id)) crossRefIds.add(r.to_id);
2386
2532
  }
2387
2533
  if (crossRefIds.size > 0) {
2388
- const crossPlaceholders = [...crossRefIds].map(() => "?").join(",");
2389
- const extras = stmt(`SELECT id, file FROM symbols WHERE id IN (${crossPlaceholders})`).all(
2390
- ...crossRefIds
2534
+ const extras = chunkedIdQuery(
2535
+ stmt,
2536
+ [...crossRefIds],
2537
+ (ph) => `SELECT id, file FROM symbols WHERE id IN (${ph})`
2391
2538
  );
2392
2539
  for (const x of extras) {
2393
2540
  symToFile.set(x.id, x.file);
@@ -2408,15 +2555,17 @@ function getFileGraphWithStatement(stmt, packageFilter) {
2408
2555
  const n = Number(r.n) || 0;
2409
2556
  addWeightedEdge(edgeMap, fromFile, toFile, r.call_type, n);
2410
2557
  }
2411
- const importRows = stmt(
2412
- `SELECT r.from_id, COALESCE(r.to_file, st.file) AS to_file, COUNT(*) AS n
2413
- FROM refs r
2414
- LEFT JOIN symbols st ON st.id = r.to_id
2415
- WHERE r.call_type = 'import'
2416
- AND COALESCE(r.to_file, st.file) IS NOT NULL
2417
- AND r.from_id IN (SELECT id FROM symbols WHERE file IN (${filePlaceholders}))
2418
- GROUP BY r.from_id, COALESCE(r.to_file, st.file)`
2419
- ).all(...pkgFilePaths);
2558
+ const importRows = chunkedIdQuery(
2559
+ stmt,
2560
+ localIds,
2561
+ (ph) => `SELECT r.from_id, COALESCE(r.to_file, st.file) AS to_file, COUNT(*) AS n
2562
+ FROM refs r
2563
+ LEFT JOIN symbols st ON st.id = r.to_id
2564
+ WHERE r.call_type = 'import'
2565
+ AND COALESCE(r.to_file, st.file) IS NOT NULL
2566
+ AND r.from_id IN (${ph})
2567
+ GROUP BY r.from_id, COALESCE(r.to_file, st.file)`
2568
+ );
2420
2569
  for (const r of importRows) {
2421
2570
  const fromFile = symToFile.get(r.from_id);
2422
2571
  if (!fromFile || !localFiles.has(fromFile)) continue;
@@ -2438,29 +2587,18 @@ function getFileGraphWithStatement(stmt, packageFilter) {
2438
2587
  function getSymbolGraphWithStatement(stmt, fileFilter) {
2439
2588
  const indexedFiles = resolveIndexedFiles(stmt, fileFilter);
2440
2589
  if (indexedFiles.length === 0) return { nodes: [], edges: [] };
2441
- const filePlaceholders = indexedFiles.map(() => "?").join(",");
2442
- const syms = stmt(
2443
- `SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE file IN (${filePlaceholders}) ORDER BY line, id`
2444
- ).all(...indexedFiles);
2590
+ const syms = chunkedValueQuery(
2591
+ stmt,
2592
+ indexedFiles,
2593
+ (ph) => `SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE file IN (${ph})`
2594
+ ).sort((a, b) => a.line - b.line || a.id - b.id);
2445
2595
  if (syms.length === 0) return { nodes: [], edges: [] };
2446
2596
  const symById = new Map(syms.map((symbol) => [symbol.id, symbol]));
2447
2597
  const relatedIds = new Set(syms.map((symbol) => symbol.id));
2448
- const refRows = stmt(
2449
- `SELECT from_id, to_id, call_type, COUNT(*) AS n
2450
- FROM (
2451
- SELECT r.from_id, r.to_id, r.call_type
2452
- FROM refs r
2453
- JOIN symbols s ON s.id = r.from_id
2454
- WHERE s.file IN (${filePlaceholders})
2455
- UNION ALL
2456
- SELECT r.from_id, r.to_id, r.call_type
2457
- FROM refs r
2458
- JOIN symbols s ON s.id = r.to_id
2459
- WHERE s.file IN (${filePlaceholders})
2460
- )
2461
- WHERE to_id IS NOT NULL
2462
- GROUP BY from_id, to_id, call_type`
2463
- ).all(...indexedFiles, ...indexedFiles);
2598
+ const refRows = refCountsTouching(
2599
+ stmt,
2600
+ syms.map((symbol) => symbol.id)
2601
+ );
2464
2602
  const edgeMap = /* @__PURE__ */ new Map();
2465
2603
  for (const r of refRows) {
2466
2604
  if (r.to_id == null) continue;
@@ -2473,11 +2611,7 @@ function getSymbolGraphWithStatement(stmt, fileFilter) {
2473
2611
  const loadedIds = new Set(syms.map((s) => s.id));
2474
2612
  const missingIds = [...relatedIds].filter((id) => !loadedIds.has(id));
2475
2613
  if (missingIds.length > 0) {
2476
- const placeholders2 = missingIds.map(() => "?").join(",");
2477
- const extras = stmt(
2478
- `SELECT id, name, kind, lang, file, line, signature, scope FROM symbols WHERE id IN (${placeholders2})`
2479
- ).all(...missingIds);
2480
- for (const s of extras) symById.set(s.id, s);
2614
+ for (const s of getSymbolsByIdsWithStatement(stmt, missingIds)) symById.set(s.id, s);
2481
2615
  }
2482
2616
  const packageOf = readPackageLabeller(stmt);
2483
2617
  const nodes = buildSymbolGraphNodes(
@@ -2799,7 +2933,7 @@ function allocateSymbolIds(stmt, count, getMaxSymbolId) {
2799
2933
  if (count <= 0) return getMaxSymbolId() + 1;
2800
2934
  ensureNextSymbolIdSeeded(stmt);
2801
2935
  const row = stmt("SELECT value FROM metadata WHERE key = ?").get(NEXT_SYMBOL_ID_KEY);
2802
- const start = Math.max(1, Number(row?.value ?? 1) || 1);
2936
+ const start = Math.max(1, Number(row?.value ?? 1) || 1, getMaxSymbolId() + 1);
2803
2937
  stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
2804
2938
  NEXT_SYMBOL_ID_KEY,
2805
2939
  String(start + count)
@@ -2880,6 +3014,9 @@ function compactIfNeeded(db, stmt, runWithRetry, options = {}) {
2880
3014
  }
2881
3015
 
2882
3016
  // src/codebase-index/writer-mutations.ts
3017
+ function rankKey(file, name, kind, scope) {
3018
+ return `${file}\0${name}\0${kind}\0${scope}`;
3019
+ }
2883
3020
  function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvailable, allocateSymbolIds2, invalidateIncomingRefsForFiles, resolveRefsForNamesUnsafe2, entries, options = {}) {
2884
3021
  if (entries.length === 0 && (options.deleteForFiles?.length ?? 0) === 0) {
2885
3022
  return [];
@@ -2889,6 +3026,7 @@ function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvail
2889
3026
  for (const symbol of entry.symbols) affectedNames.add(symbol.name);
2890
3027
  for (const ref of entry.refs) affectedNames.add(ref.toName);
2891
3028
  }
3029
+ const carriedRanks = /* @__PURE__ */ new Map();
2892
3030
  if (options.deleteForFiles && options.deleteForFiles.length > 0) {
2893
3031
  for (const name of invalidateIncomingRefsForFiles(options.deleteForFiles)) {
2894
3032
  affectedNames.add(name);
@@ -2898,6 +3036,21 @@ function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvail
2898
3036
  const bucket = padToInBucket(options.deleteForFiles.slice(cursor, cursor + take));
2899
3037
  cursor += take;
2900
3038
  const ph = placeholders(bucket.length);
3039
+ const rankRows = stmtFn(
3040
+ `SELECT s.file AS file, s.name AS name, s.kind AS kind, s.scope AS scope,
3041
+ r.rank AS rank, r.in_deg AS inDeg, r.out_deg AS outDeg
3042
+ FROM symbol_rank r JOIN symbols s ON s.id = r.symbol_id
3043
+ WHERE s.file IN (${ph})`
3044
+ ).all(...bucket);
3045
+ for (const row of rankRows) {
3046
+ const key = rankKey(row.file, row.name, row.kind, row.scope);
3047
+ if (!carriedRanks.has(key)) {
3048
+ carriedRanks.set(key, { rank: row.rank, inDeg: row.inDeg, outDeg: row.outDeg });
3049
+ }
3050
+ }
3051
+ stmtFn(
3052
+ `DELETE FROM symbol_rank WHERE symbol_id IN (SELECT id FROM symbols WHERE file IN (${ph}))`
3053
+ ).run(...bucket);
2901
3054
  if (ftsAvailable) {
2902
3055
  stmtFn(
2903
3056
  `DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${ph}))`
@@ -2961,6 +3114,24 @@ function commitBatchWithStatement(stmtFn, maxSqlVars, ftsAvailable, vectorsAvail
2961
3114
  bulkInsertVectorsWithStatement((sql) => stmtFn(sql), maxSqlVars, vectorRows);
2962
3115
  }
2963
3116
  bulkInsertRefsWithStatement((sql) => stmtFn(sql), maxSqlVars, refsToInsert);
3117
+ if (carriedRanks.size > 0) {
3118
+ const rankRows = [];
3119
+ for (const symbol of allInserted) {
3120
+ const carried = carriedRanks.get(
3121
+ rankKey(symbol.file, symbol.name, symbol.kind, symbol.scope)
3122
+ );
3123
+ if (carried) rankRows.push([symbol.id, carried.rank, carried.inDeg, carried.outDeg]);
3124
+ }
3125
+ let cursor = 0;
3126
+ for (const take of ladderChunkSizes(rankRows.length, Math.max(1, Math.floor(maxSqlVars / 4)))) {
3127
+ const chunk = rankRows.slice(cursor, cursor + take);
3128
+ cursor += take;
3129
+ stmtFn(
3130
+ `INSERT OR REPLACE INTO symbol_rank(symbol_id, rank, in_deg, out_deg)
3131
+ VALUES ${chunk.map(() => "(?, ?, ?, ?)").join(", ")}`
3132
+ ).run(...chunk.flat());
3133
+ }
3134
+ }
2964
3135
  const upsertStmt = stmtFn(
2965
3136
  `INSERT INTO files(file, lang, mtime_ms, content_hash, symbol_count, last_indexed)
2966
3137
  VALUES (?, ?, ?, ?, ?, ?)
@@ -3289,6 +3460,16 @@ function resolveRefsWithStatement(stmtFn) {
3289
3460
  return result.changes ?? 0;
3290
3461
  }
3291
3462
  }
3463
+ function getFilesWithDanglingImportsWithStatement(stmtFn) {
3464
+ return stmtFn(
3465
+ `SELECT DISTINCT s.file AS file
3466
+ FROM refs r
3467
+ JOIN symbols s ON s.id = r.from_id
3468
+ WHERE r.call_type = 'import'
3469
+ AND r.to_file IS NOT NULL
3470
+ AND NOT EXISTS (SELECT 1 FROM files f WHERE f.file = r.to_file)`
3471
+ ).all().map((row) => row.file);
3472
+ }
3292
3473
  function applyImportResolutionsWithStatement(db, stmtFn, runWithRetry, maxSqlVars, resolutions) {
3293
3474
  if (resolutions.length === 0) return 0;
3294
3475
  return runWithRetry(() => {
@@ -3298,7 +3479,7 @@ function applyImportResolutionsWithStatement(db, stmtFn, runWithRetry, maxSqlVar
3298
3479
  from_file TEXT NOT NULL,
3299
3480
  lang TEXT NOT NULL,
3300
3481
  module TEXT NOT NULL,
3301
- to_file TEXT NOT NULL
3482
+ to_file TEXT
3302
3483
  )`
3303
3484
  );
3304
3485
  const chunkSize = Math.max(1, Math.floor(maxSqlVars / 4));
@@ -3404,21 +3585,23 @@ var SEARCH_CANDIDATE_SCAN_CAP = 5e3;
3404
3585
  function normalizeSearchLimit(limit) {
3405
3586
  return typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.trunc(limit)) : void 0;
3406
3587
  }
3588
+ function resolveKindFilter(filter) {
3589
+ const lspKinds = filter?.lspKind != null ? lspKindToInternalKinds(filter.lspKind) : void 0;
3590
+ if (lspKinds !== void 0 && lspKinds.length === 0) return null;
3591
+ if (filter?.kind) {
3592
+ if (lspKinds !== void 0 && !lspKinds.includes(filter.kind)) return null;
3593
+ return [filter.kind];
3594
+ }
3595
+ return lspKinds;
3596
+ }
3407
3597
  function buildWriterSearchWhere(query, filter) {
3408
3598
  const conditions = [];
3409
3599
  const values = [];
3410
- let effectiveKind = filter?.kind;
3411
- if (filter?.lspKind != null) {
3412
- const mapped = lspKindToInternalKind(filter.lspKind);
3413
- if (mapped !== null) {
3414
- effectiveKind = mapped;
3415
- } else {
3416
- return null;
3417
- }
3418
- }
3419
- if (effectiveKind) {
3420
- conditions.push("kind = ?");
3421
- values.push(effectiveKind);
3600
+ const kinds = resolveKindFilter(filter);
3601
+ if (kinds === null) return null;
3602
+ if (kinds !== void 0) {
3603
+ conditions.push(`kind IN (${kinds.map(() => "?").join(", ")})`);
3604
+ values.push(...kinds);
3422
3605
  }
3423
3606
  if (filter?.lang) {
3424
3607
  conditions.push("lang = ?");
@@ -3493,12 +3676,8 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
3493
3676
  safeLimit
3494
3677
  );
3495
3678
  }
3496
- let effectiveKind = filter?.kind;
3497
- if (filter?.lspKind != null) {
3498
- const mapped = lspKindToInternalKind(filter.lspKind);
3499
- if (mapped === null) return { results: [], total: 0 };
3500
- effectiveKind = mapped;
3501
- }
3679
+ const kinds = resolveKindFilter(filter);
3680
+ if (kinds === null) return { results: [], total: 0 };
3502
3681
  const longTokens = tokens.filter((t) => t.length >= 3);
3503
3682
  const shortTokens = tokens.filter((t) => t.length < 3);
3504
3683
  if (longTokens.length === 0) {
@@ -3521,9 +3700,9 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
3521
3700
  );
3522
3701
  values.push(like, like, like);
3523
3702
  }
3524
- if (effectiveKind) {
3525
- conditions.push("s.kind = ?");
3526
- values.push(effectiveKind);
3703
+ if (kinds !== void 0) {
3704
+ conditions.push(`s.kind IN (${kinds.map(() => "?").join(", ")})`);
3705
+ values.push(...kinds);
3527
3706
  }
3528
3707
  if (filter?.lang) {
3529
3708
  conditions.push("s.lang = ?");
@@ -3560,10 +3739,17 @@ function searchRankedWithStatement(stmtFn, searchFn, ftsAvailable, vectorsAvaila
3560
3739
  const vecRows = stmtFn(
3561
3740
  `SELECT sv.symbol_id, sv.vector FROM symbol_vectors sv WHERE sv.symbol_id IN (${placeholders2})`
3562
3741
  ).all(...candidateIds);
3563
- const vecScores = vecRows.map((r) => ({
3564
- id: r.symbol_id,
3565
- sim: cosineSimilarity(queryVec, decodeVector(r.vector))
3566
- })).sort((a, b) => b.sim - a.sim);
3742
+ const vecScores = [];
3743
+ for (const r of vecRows) {
3744
+ try {
3745
+ vecScores.push({
3746
+ id: r.symbol_id,
3747
+ sim: cosineSimilarity(queryVec, decodeVector(r.vector))
3748
+ });
3749
+ } catch {
3750
+ }
3751
+ }
3752
+ vecScores.sort((a, b) => b.sim - a.sim);
3567
3753
  const bm25Rank = /* @__PURE__ */ new Map();
3568
3754
  bm25Rows.forEach((r, i) => {
3569
3755
  bm25Rank.set(r.id, i);
@@ -3667,7 +3853,7 @@ var StorePool = class {
3667
3853
  release(store) {
3668
3854
  const k = this.keyByStore.get(store);
3669
3855
  const entry = k === void 0 ? void 0 : this.stores.get(k);
3670
- if (entry && entry.refs > 0) entry.refs--;
3856
+ if (entry && entry.store === store && entry.refs > 0) entry.refs--;
3671
3857
  this.trim();
3672
3858
  }
3673
3859
  trim() {
@@ -3729,6 +3915,8 @@ var IndexStore = class _IndexStore {
3729
3915
  stmtCache = /* @__PURE__ */ new Map();
3730
3916
  bm25Cache = null;
3731
3917
  bm25Dirty = true;
3918
+ /** `PRAGMA data_version` the BM25 cache was built at. */
3919
+ bm25DataVersion = -1;
3732
3920
  stmt(sql) {
3733
3921
  const cached2 = this.stmtCache.get(sql);
3734
3922
  if (cached2 !== void 0) {
@@ -3748,7 +3936,7 @@ var IndexStore = class _IndexStore {
3748
3936
  this.indexDir = resolveIndexDir(projectRoot, opts.indexDir);
3749
3937
  fs3.mkdirSync(this.indexDir, { recursive: true });
3750
3938
  const Database = loadDatabaseSync();
3751
- this.db = new Database(path3.join(this.indexDir, DB_FILE2));
3939
+ this.db = new Database(path4.join(this.indexDir, DB_FILE2));
3752
3940
  applyIndexStorePragmas(this.db);
3753
3941
  this.initSchema();
3754
3942
  }
@@ -3768,6 +3956,7 @@ var IndexStore = class _IndexStore {
3768
3956
  this.db.exec("ROLLBACK");
3769
3957
  } catch {
3770
3958
  }
3959
+ this.invalidateBm25();
3771
3960
  throw error;
3772
3961
  } finally {
3773
3962
  this.atomicIndexUpdateActive = false;
@@ -3786,12 +3975,22 @@ var IndexStore = class _IndexStore {
3786
3975
  if (savepoint) this.db.exec(`RELEASE SAVEPOINT ${savepoint}`);
3787
3976
  else this.db.exec("COMMIT");
3788
3977
  }
3978
+ /**
3979
+ * Never throws: every caller is already propagating the failure that caused
3980
+ * the rollback. When SQLite has rolled the transaction back itself (disk
3981
+ * full, I/O error) the ROLLBACK fails with "no transaction is active", and
3982
+ * that message used to replace the real error.
3983
+ */
3789
3984
  rollbackWriteTransaction(savepoint) {
3790
- if (savepoint) {
3791
- this.db.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`);
3792
- this.db.exec(`RELEASE SAVEPOINT ${savepoint}`);
3793
- } else {
3794
- this.db.exec("ROLLBACK");
3985
+ this.invalidateBm25();
3986
+ try {
3987
+ if (savepoint) {
3988
+ this.db.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`);
3989
+ this.db.exec(`RELEASE SAVEPOINT ${savepoint}`);
3990
+ } else {
3991
+ this.db.exec("ROLLBACK");
3992
+ }
3993
+ } catch {
3795
3994
  }
3796
3995
  }
3797
3996
  initSchema() {
@@ -3876,6 +4075,9 @@ var IndexStore = class _IndexStore {
3876
4075
  "DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
3877
4076
  ).run(file);
3878
4077
  }
4078
+ this.stmt(
4079
+ "DELETE FROM symbol_rank WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
4080
+ ).run(file);
3879
4081
  const deletedChanges = Number(
3880
4082
  this.stmt("DELETE FROM symbols WHERE file = ?").run(file).changes
3881
4083
  );
@@ -3907,6 +4109,10 @@ var IndexStore = class _IndexStore {
3907
4109
  this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
3908
4110
  file
3909
4111
  );
4112
+ this.stmt(
4113
+ "DELETE FROM symbol_rank WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
4114
+ ).run(file);
4115
+ this.stmt("DELETE FROM file_rank WHERE file = ?").run(file);
3910
4116
  const deletedChanges = Number(
3911
4117
  this.stmt("DELETE FROM symbols WHERE file = ?").run(file).changes
3912
4118
  );
@@ -3945,6 +4151,9 @@ var IndexStore = class _IndexStore {
3945
4151
  onlyFiles
3946
4152
  );
3947
4153
  }
4154
+ getFilesWithDanglingImports() {
4155
+ return getFilesWithDanglingImportsWithStatement((sql) => this.stmt(sql));
4156
+ }
3948
4157
  applyImportResolutions(resolutions) {
3949
4158
  return applyImportResolutionsWithStatement(
3950
4159
  this.db,
@@ -3976,13 +4185,33 @@ var IndexStore = class _IndexStore {
3976
4185
  this.bm25Dirty = true;
3977
4186
  this.bm25Cache = null;
3978
4187
  }
4188
+ /**
4189
+ * The corpus is rebuilt when this connection wrote (bm25Dirty) OR another
4190
+ * connection committed since it was built. Pooled stores stay open for the
4191
+ * life of the host while the project daemon, an inline indexer or another
4192
+ * process writes the same database; the dirty flag alone never saw those
4193
+ * writes, so short-query results silently dropped every symbol added since.
4194
+ */
3979
4195
  getOrBuildBm25() {
3980
- if (this.bm25Cache && !this.bm25Dirty) return this.bm25Cache;
4196
+ const version = this.dataVersion();
4197
+ if (this.bm25Cache && !this.bm25Dirty && version === this.bm25DataVersion) {
4198
+ return this.bm25Cache;
4199
+ }
3981
4200
  const docs = this.getAllIndexable();
3982
4201
  this.bm25Cache = buildBm25Index(docs);
3983
4202
  this.bm25Dirty = false;
4203
+ this.bm25DataVersion = version;
3984
4204
  return this.bm25Cache;
3985
4205
  }
4206
+ /** Changes whenever ANOTHER connection commits to this database. */
4207
+ dataVersion() {
4208
+ try {
4209
+ const row = this.stmt("PRAGMA data_version").get();
4210
+ return Number(row?.data_version ?? -1);
4211
+ } catch {
4212
+ return -1;
4213
+ }
4214
+ }
3986
4215
  getAllIndexable() {
3987
4216
  return getAllIndexableWithStatement((sql) => this.stmt(sql));
3988
4217
  }
@@ -4128,6 +4357,9 @@ var IndexStore = class _IndexStore {
4128
4357
  this.stmt("DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file = ?)").run(
4129
4358
  meta.file
4130
4359
  );
4360
+ this.stmt(
4361
+ "DELETE FROM symbol_rank WHERE symbol_id IN (SELECT id FROM symbols WHERE file = ?)"
4362
+ ).run(meta.file);
4131
4363
  const deletedChanges = Number(
4132
4364
  this.stmt("DELETE FROM symbols WHERE file = ?").run(meta.file).changes
4133
4365
  );
@@ -4252,7 +4484,9 @@ var IndexStore = class _IndexStore {
4252
4484
  return getSymbolGraphWithStatement((sql) => this.stmt(sql), fileFilter);
4253
4485
  }
4254
4486
  getAllSymbols() {
4255
- return this.stmt("SELECT id, name, file, kind, line FROM symbols ORDER BY id").all().map((r) => ({ ...r, kind: r.kind }));
4487
+ return this.stmt(
4488
+ "SELECT id, name, file, kind, line, scope FROM symbols ORDER BY id"
4489
+ ).all().map((r) => ({ ...r, kind: r.kind }));
4256
4490
  }
4257
4491
  /** Declarations in one file, in source order. */
4258
4492
  getFileSymbols(file, limit) {
@@ -4422,7 +4656,7 @@ import { execFile } from "node:child_process";
4422
4656
  import { createHash } from "node:crypto";
4423
4657
  import * as fs5 from "node:fs/promises";
4424
4658
  import { availableParallelism } from "node:os";
4425
- import * as path5 from "node:path";
4659
+ import * as path6 from "node:path";
4426
4660
  import {
4427
4661
  DEFAULT_WALK_IGNORE_DIRS,
4428
4662
  indexParallelBatchSize,
@@ -4507,7 +4741,7 @@ function xxhash64String(content) {
4507
4741
  }
4508
4742
 
4509
4743
  // src/codebase-index/module-resolver.ts
4510
- import * as path4 from "node:path";
4744
+ import * as path5 from "node:path";
4511
4745
  var EXTENSIONS = {
4512
4746
  js: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".vue", ".svelte"],
4513
4747
  py: [".py", ".pyi"],
@@ -4526,6 +4760,13 @@ var DIRECTORY_ENTRIES = {
4526
4760
  function normalizeNamespace(value) {
4527
4761
  return value.replace(/::|[\\/]/g, ".").replace(/^\.+|\.+$/g, "").toLowerCase();
4528
4762
  }
4763
+ function rustModuleDir(file) {
4764
+ const base = path5.posix.basename(file);
4765
+ if (base === "mod.rs" || base === "lib.rs" || base === "main.rs") {
4766
+ return path5.posix.dirname(file);
4767
+ }
4768
+ return file.replace(/\.rs$/i, "");
4769
+ }
4529
4770
  var ModuleResolver = class {
4530
4771
  structure;
4531
4772
  /** Lowercased portable path → the path as indexed (case is preserved). */
@@ -4540,14 +4781,23 @@ var ModuleResolver = class {
4540
4781
  this.byDir = /* @__PURE__ */ new Map();
4541
4782
  this.byNamespace = /* @__PURE__ */ new Map();
4542
4783
  const dirsByKey = /* @__PURE__ */ new Map();
4784
+ const collidedPaths = /* @__PURE__ */ new Set();
4785
+ const collidedDirs = /* @__PURE__ */ new Set();
4543
4786
  for (const file of files) {
4544
4787
  const portable = toPortablePath(file);
4545
4788
  const pathKey = portable.toLowerCase();
4546
- const priorPath = this.byPath.get(pathKey);
4547
- if (priorPath !== void 0 && priorPath !== file) this.byPath.delete(pathKey);
4548
- else this.byPath.set(pathKey, file);
4549
- const dir = path4.posix.dirname(portable);
4789
+ if (!collidedPaths.has(pathKey)) {
4790
+ const priorPath = this.byPath.get(pathKey);
4791
+ if (priorPath !== void 0 && priorPath !== file) {
4792
+ this.byPath.delete(pathKey);
4793
+ collidedPaths.add(pathKey);
4794
+ } else {
4795
+ this.byPath.set(pathKey, file);
4796
+ }
4797
+ }
4798
+ const dir = path5.posix.dirname(portable);
4550
4799
  const dirKey = dir.toLowerCase();
4800
+ if (collidedDirs.has(dirKey)) continue;
4551
4801
  const knownDir = dirsByKey.get(dirKey);
4552
4802
  if (knownDir === void 0) {
4553
4803
  dirsByKey.set(dirKey, dir);
@@ -4557,6 +4807,7 @@ var ModuleResolver = class {
4557
4807
  } else {
4558
4808
  dirsByKey.delete(dirKey);
4559
4809
  this.byDir.delete(dirKey);
4810
+ collidedDirs.add(dirKey);
4560
4811
  }
4561
4812
  }
4562
4813
  for (const { name, file } of namespaces) {
@@ -4621,7 +4872,7 @@ var ModuleResolver = class {
4621
4872
  }
4622
4873
  // ─── Lookup primitives ──────────────────────────────────────────────────────
4623
4874
  lookup(candidate) {
4624
- return this.byPath.get(path4.posix.normalize(candidate).toLowerCase());
4875
+ return this.byPath.get(path5.posix.normalize(candidate).toLowerCase());
4625
4876
  }
4626
4877
  /**
4627
4878
  * Try `base` verbatim, then `base` + each extension, then each directory
@@ -4631,7 +4882,7 @@ var ModuleResolver = class {
4631
4882
  const direct = this.lookup(base);
4632
4883
  if (direct) return direct;
4633
4884
  const extensions = EXTENSIONS[family] ?? [];
4634
- const suffix = path4.posix.extname(base);
4885
+ const suffix = path5.posix.extname(base);
4635
4886
  const stem = suffix && extensions.includes(suffix) ? base.slice(0, -suffix.length) : base;
4636
4887
  for (const ext of extensions) {
4637
4888
  const hit = this.lookup(`${stem}${ext}`);
@@ -4639,7 +4890,7 @@ var ModuleResolver = class {
4639
4890
  }
4640
4891
  for (const entry of DIRECTORY_ENTRIES[family] ?? []) {
4641
4892
  for (const ext of extensions) {
4642
- const hit = this.lookup(path4.posix.join(base, `${entry}${ext}`));
4893
+ const hit = this.lookup(path5.posix.join(base, `${entry}${ext}`));
4643
4894
  if (hit) return hit;
4644
4895
  }
4645
4896
  }
@@ -4656,14 +4907,14 @@ var ModuleResolver = class {
4656
4907
  * member represents it.
4657
4908
  */
4658
4909
  representativeIn(dir, family) {
4659
- const members = this.byDir.get(path4.posix.normalize(dir).toLowerCase());
4910
+ const members = this.byDir.get(path5.posix.normalize(dir).toLowerCase());
4660
4911
  if (!members?.length) return void 0;
4661
4912
  const extensions = EXTENSIONS[family] ?? [];
4662
- const eligible = members.filter((file) => extensions.includes(path4.posix.extname(toPortablePath(file)).toLowerCase())).sort((a, b) => toPortablePath(a).localeCompare(toPortablePath(b)));
4913
+ const eligible = members.filter((file) => extensions.includes(path5.posix.extname(toPortablePath(file)).toLowerCase())).sort((a, b) => toPortablePath(a).localeCompare(toPortablePath(b)));
4663
4914
  if (eligible.length === 0) return void 0;
4664
- const base = path4.posix.basename(path4.posix.normalize(dir)).toLowerCase();
4915
+ const base = path5.posix.basename(path5.posix.normalize(dir)).toLowerCase();
4665
4916
  const named = eligible.find(
4666
- (file) => path4.posix.basename(toPortablePath(file)).split(".")[0]?.toLowerCase() === base
4917
+ (file) => path5.posix.basename(toPortablePath(file)).split(".")[0]?.toLowerCase() === base
4667
4918
  );
4668
4919
  return named ?? eligible[0];
4669
4920
  }
@@ -4671,7 +4922,7 @@ var ModuleResolver = class {
4671
4922
  /** Relative specifiers, then workspace package names and their subpaths. */
4672
4923
  resolveJs(fromFile, spec) {
4673
4924
  if (spec.startsWith(".")) {
4674
- const absolute = path4.posix.join(path4.posix.dirname(fromFile), spec);
4925
+ const absolute = path5.posix.join(path5.posix.dirname(fromFile), spec);
4675
4926
  return this.lookupWithExtensions(absolute, "js");
4676
4927
  }
4677
4928
  const owner = this.structure.roots.find(
@@ -4680,9 +4931,9 @@ var ModuleResolver = class {
4680
4931
  if (!owner?.importPath) return void 0;
4681
4932
  const subpath = spec.slice(owner.importPath.length).replace(/^\//, "");
4682
4933
  if (!subpath) {
4683
- return this.lookupWithExtensions(path4.posix.join(owner.dir, "src/index"), "js") ?? this.lookupWithExtensions(path4.posix.join(owner.dir, "index"), "js");
4934
+ return this.lookupWithExtensions(path5.posix.join(owner.dir, "src/index"), "js") ?? this.lookupWithExtensions(path5.posix.join(owner.dir, "index"), "js");
4684
4935
  }
4685
- return this.lookupWithExtensions(path4.posix.join(owner.dir, subpath), "js") ?? this.lookupWithExtensions(path4.posix.join(owner.dir, "src", subpath), "js");
4936
+ return this.lookupWithExtensions(path5.posix.join(owner.dir, subpath), "js") ?? this.lookupWithExtensions(path5.posix.join(owner.dir, "src", subpath), "js");
4686
4937
  }
4687
4938
  /** Go import paths are absolute module paths; a package is a directory. */
4688
4939
  resolveGo(spec) {
@@ -4691,7 +4942,7 @@ var ModuleResolver = class {
4691
4942
  );
4692
4943
  if (!owner?.importPath) return void 0;
4693
4944
  const subpath = spec.slice(owner.importPath.length).replace(/^\//, "");
4694
- return this.representativeIn(path4.posix.join(owner.dir, subpath), "go");
4945
+ return this.representativeIn(path5.posix.join(owner.dir, subpath), "go");
4695
4946
  }
4696
4947
  /**
4697
4948
  * `import a.b.c` / `from a.b import c`, plus PEP 328 relative imports whose
@@ -4700,20 +4951,20 @@ var ModuleResolver = class {
4700
4951
  resolvePython(fromFile, spec) {
4701
4952
  const leadingDots = /^\.*/.exec(spec)?.[0].length ?? 0;
4702
4953
  if (leadingDots > 0) {
4703
- let base = path4.posix.dirname(fromFile);
4704
- for (let i = 1; i < leadingDots; i++) base = path4.posix.dirname(base);
4954
+ let base = path5.posix.dirname(fromFile);
4955
+ for (let i = 1; i < leadingDots; i++) base = path5.posix.dirname(base);
4705
4956
  const rest = spec.slice(leadingDots).split(".").filter(Boolean);
4706
- return this.lookupWithExtensions(path4.posix.join(base, ...rest), "py");
4957
+ return this.lookupWithExtensions(path5.posix.join(base, ...rest), "py");
4707
4958
  }
4708
4959
  const segments = spec.split(".").filter(Boolean);
4709
4960
  if (segments.length === 0) return void 0;
4710
4961
  const sourceRoots = this.structure.roots.filter((root) => root.kind === "python").flatMap((root) => root.sourceRoots);
4711
4962
  for (const base of [...sourceRoots, this.structure.projectRoot]) {
4712
- const hit = this.lookupWithExtensions(path4.posix.join(base, ...segments), "py");
4963
+ const hit = this.lookupWithExtensions(path5.posix.join(base, ...segments), "py");
4713
4964
  if (hit) return hit;
4714
4965
  if (segments.length > 1) {
4715
4966
  const parent = this.lookupWithExtensions(
4716
- path4.posix.join(base, ...segments.slice(0, -1)),
4967
+ path5.posix.join(base, ...segments.slice(0, -1)),
4717
4968
  "py"
4718
4969
  );
4719
4970
  if (parent) return parent;
@@ -4721,39 +4972,64 @@ var ModuleResolver = class {
4721
4972
  }
4722
4973
  return void 0;
4723
4974
  }
4724
- /** `use crate::a::b`, `use super::x`, `use self::y`, `use other_crate::z`. */
4975
+ /**
4976
+ * `use crate::a::b`, `use super::x`, `use self::y`, `use other_crate::z`,
4977
+ * and `mod x;` declarations.
4978
+ *
4979
+ * Paths are resolved from the declaring file's MODULE directory, which is
4980
+ * only its filesystem directory for `mod.rs`/`lib.rs`/`main.rs`. A plain
4981
+ * `src/net.rs` owns `src/net/`: its `mod http;` is `src/net/http.rs`, its
4982
+ * `super` is the crate root. Using the file's directory put every
4983
+ * `self::`/`super::`/`mod` path from such a file one level too high, so
4984
+ * those edges silently resolved to nothing (or to an unrelated file).
4985
+ */
4725
4986
  resolveRust(fromFile, spec) {
4726
4987
  const segments = spec.split("::").filter(Boolean);
4727
4988
  if (segments.length === 0) return void 0;
4728
4989
  const head = segments[0];
4729
4990
  if (head === "self" || head === "super") {
4730
- let base = path4.posix.dirname(fromFile);
4991
+ let base = rustModuleDir(fromFile);
4992
+ let consumed = 0;
4731
4993
  for (const segment of segments) {
4732
- if (segment === "super") base = path4.posix.dirname(base);
4994
+ if (segment === "super") base = path5.posix.dirname(base);
4733
4995
  else if (segment !== "self") break;
4996
+ consumed++;
4734
4997
  }
4735
- const rest2 = segments.filter((segment) => segment !== "self" && segment !== "super");
4736
- return this.lookupWithExtensions(path4.posix.join(base, ...rest2), "rs");
4998
+ return this.lookupRustPath(base, segments.slice(consumed)) ?? this.lookupRustModuleFile(base);
4737
4999
  }
4738
- const owningCrate = findOwningRoot(this.structure, fromFile, ["cargo"]);
4739
- const crate = head === "crate" ? owningCrate : this.structure.roots.find(
5000
+ const crate = head === "crate" ? findOwningRoot(this.structure, fromFile, ["cargo"]) : this.structure.roots.find(
4740
5001
  (root) => root.kind === "cargo" && root.importPath === head?.replace(/-/g, "_")
4741
5002
  );
4742
5003
  if (!crate) {
4743
- return this.lookupWithExtensions(
4744
- path4.posix.join(path4.posix.dirname(fromFile), ...segments),
4745
- "rs"
4746
- );
5004
+ return this.lookupRustPath(rustModuleDir(fromFile), segments);
4747
5005
  }
4748
5006
  const rest = segments.slice(1);
4749
5007
  for (const base of crate.sourceRoots) {
4750
- const parent = rest.length > 1 ? this.lookupWithExtensions(path4.posix.join(base, ...rest.slice(0, -1)), "rs") : void 0;
4751
- const exact = this.lookupWithExtensions(path4.posix.join(base, ...rest), "rs");
4752
- const hit = exact ?? parent ?? this.lookupWithExtensions(path4.posix.join(base, "lib"), "rs");
5008
+ const hit = this.lookupRustPath(base, rest) ?? this.lookupRustCrateRoot(base);
5009
+ if (hit) return hit;
5010
+ }
5011
+ return void 0;
5012
+ }
5013
+ /**
5014
+ * Longest module prefix of `segments` under `base` that is an indexed file.
5015
+ * The trailing segments of a `use` are usually items (`net::http::Client::new`),
5016
+ * so each is dropped in turn until a module file matches.
5017
+ */
5018
+ lookupRustPath(base, segments) {
5019
+ for (let end = segments.length; end > 0; end--) {
5020
+ const hit = this.lookupWithExtensions(path5.posix.join(base, ...segments.slice(0, end)), "rs");
4753
5021
  if (hit) return hit;
4754
5022
  }
4755
5023
  return void 0;
4756
5024
  }
5025
+ /** The file defining the module whose children live in `dir`. */
5026
+ lookupRustModuleFile(dir) {
5027
+ return this.lookupWithExtensions(dir, "rs") ?? this.lookupRustCrateRoot(dir);
5028
+ }
5029
+ /** A crate's root file: the library root, else the binary root. */
5030
+ lookupRustCrateRoot(sourceRoot) {
5031
+ return this.lookup(path5.posix.join(sourceRoot, "lib.rs")) ?? this.lookup(path5.posix.join(sourceRoot, "main.rs"));
5032
+ }
4757
5033
  /** `com.example.Thing` and `com.example.*` against JVM source roots. */
4758
5034
  resolveJvm(spec) {
4759
5035
  const segments = spec.split(".").filter(Boolean);
@@ -4762,24 +5038,31 @@ var ModuleResolver = class {
4762
5038
  const wildcard = segments[segments.length - 1] === "*";
4763
5039
  const parts = wildcard ? segments.slice(0, -1) : segments;
4764
5040
  for (const base of [...sourceRoots, this.structure.projectRoot]) {
4765
- const target = path4.posix.join(base, ...parts);
4766
- const hit = wildcard ? this.representativeIn(target, "jvm") : this.lookupWithExtensions(target, "jvm");
4767
- if (hit) return hit;
5041
+ const target = path5.posix.join(base, ...parts);
5042
+ if (wildcard) {
5043
+ const hit = this.representativeIn(target, "jvm") ?? this.lookupWithExtensions(target, "jvm");
5044
+ if (hit) return hit;
5045
+ continue;
5046
+ }
5047
+ for (let end = parts.length; end >= Math.max(2, parts.length - 2); end--) {
5048
+ const hit = this.lookupWithExtensions(path5.posix.join(base, ...parts.slice(0, end)), "jvm");
5049
+ if (hit) return hit;
5050
+ }
4768
5051
  }
4769
5052
  return void 0;
4770
5053
  }
4771
5054
  /** `#include "foo/bar.h"` — quoted form only; `<…>` is a system header. */
4772
5055
  resolveInclude(fromFile, spec) {
4773
5056
  const relative3 = this.lookupWithExtensions(
4774
- path4.posix.join(path4.posix.dirname(fromFile), spec),
5057
+ path5.posix.join(path5.posix.dirname(fromFile), spec),
4775
5058
  "c"
4776
5059
  );
4777
5060
  if (relative3) return relative3;
4778
5061
  for (const base of [
4779
- path4.posix.join(this.structure.projectRoot, "include"),
5062
+ path5.posix.join(this.structure.projectRoot, "include"),
4780
5063
  this.structure.projectRoot
4781
5064
  ]) {
4782
- const hit = this.lookupWithExtensions(path4.posix.join(base, spec), "c");
5065
+ const hit = this.lookupWithExtensions(path5.posix.join(base, spec), "c");
4783
5066
  if (hit) return hit;
4784
5067
  }
4785
5068
  return void 0;
@@ -4787,15 +5070,15 @@ var ModuleResolver = class {
4787
5070
  /** `require_relative 'x'` is relative; `require 'x'` is looked up under lib/. */
4788
5071
  resolveRuby(fromFile, spec) {
4789
5072
  const relative3 = this.lookupWithExtensions(
4790
- path4.posix.join(path4.posix.dirname(fromFile), spec),
5073
+ path5.posix.join(path5.posix.dirname(fromFile), spec),
4791
5074
  "ruby"
4792
5075
  );
4793
5076
  if (relative3) return relative3;
4794
5077
  for (const base of [
4795
- path4.posix.join(this.structure.projectRoot, "lib"),
5078
+ path5.posix.join(this.structure.projectRoot, "lib"),
4796
5079
  this.structure.projectRoot
4797
5080
  ]) {
4798
- const hit = this.lookupWithExtensions(path4.posix.join(base, spec), "ruby");
5081
+ const hit = this.lookupWithExtensions(path5.posix.join(base, spec), "ruby");
4799
5082
  if (hit) return hit;
4800
5083
  }
4801
5084
  return void 0;
@@ -4993,7 +5276,7 @@ var ParserWorkerPool = class {
4993
5276
  */
4994
5277
  async reparseInline(batch, orphaned, workerId) {
4995
5278
  try {
4996
- const { parseFileContent } = await import("./parser-dispatch-WG4DNBHH.js");
5279
+ const { parseFileContent } = await import("./parser-dispatch-ZSWCQB6M.js");
4997
5280
  for (const item of orphaned) {
4998
5281
  if (batch.settled) return;
4999
5282
  try {
@@ -5098,15 +5381,15 @@ var IndexSourceChangedError = class extends Error {
5098
5381
  name = "IndexSourceChangedError";
5099
5382
  };
5100
5383
  function isWithinProject(projectRoot, file) {
5101
- const rel = path5.relative(projectRoot, file);
5102
- return rel !== "" && !rel.startsWith(`..${path5.sep}`) && rel !== ".." && !path5.isAbsolute(rel);
5384
+ const rel = path6.relative(projectRoot, file);
5385
+ return rel !== "" && !rel.startsWith(`..${path6.sep}`) && rel !== ".." && !path6.isAbsolute(rel);
5103
5386
  }
5104
5387
  function isMissingPathError(err) {
5105
5388
  const code = err?.code;
5106
5389
  return code === "ENOENT" || code === "ENOTDIR";
5107
5390
  }
5108
5391
  function normalizeComparablePath(value) {
5109
- const resolved = path5.resolve(value);
5392
+ const resolved = path6.resolve(value);
5110
5393
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
5111
5394
  }
5112
5395
  function gitOutput(projectRoot, args) {
@@ -5152,24 +5435,24 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
5152
5435
  const record = statusRecords[i];
5153
5436
  if (!record) continue;
5154
5437
  const status = record.slice(0, 2);
5155
- const changedPath = path5.resolve(projectRoot, record.slice(3));
5438
+ const changedPath = path6.resolve(projectRoot, record.slice(3));
5156
5439
  dirty.add(changedPath);
5157
5440
  if (status.includes("D")) deleted.add(changedPath);
5158
5441
  if (status.includes("R") || status.includes("C")) {
5159
5442
  const source = statusRecords[++i];
5160
- if (source) dirty.add(path5.resolve(projectRoot, source));
5443
+ if (source) dirty.add(path6.resolve(projectRoot, source));
5161
5444
  }
5162
5445
  }
5163
5446
  const files = [];
5164
5447
  for (const relative3 of output.toString("utf8").split("\0")) {
5165
5448
  if (!relative3) continue;
5166
5449
  const portable = relative3.replace(/\\/g, "/");
5167
- if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path5.posix.basename(portable)) || isAtlasProjection(portable)) {
5450
+ if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path6.posix.basename(portable)) || isAtlasProjection(portable)) {
5168
5451
  continue;
5169
5452
  }
5170
- const full = path5.resolve(projectRoot, relative3);
5453
+ const full = path6.resolve(projectRoot, relative3);
5171
5454
  if (deleted.has(full)) continue;
5172
- const ext = path5.extname(relative3).toLowerCase();
5455
+ const ext = path6.extname(relative3).toLowerCase();
5173
5456
  if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
5174
5457
  }
5175
5458
  const snapshot = createHash("sha256").update(stagedOutput).update("\0").update(statusOutput);
@@ -5188,6 +5471,57 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
5188
5471
  return null;
5189
5472
  }
5190
5473
  }
5474
+ async function expandTargetedDirectories(store, projectRoot, targets, isGitIgnored, signal) {
5475
+ const isIndexableName = (file) => INDEXABLE_EXTENSION_SET.has(path6.extname(file).toLowerCase()) || detectLang(file) !== null;
5476
+ const expanded = /* @__PURE__ */ new Set();
5477
+ let indexedFiles;
5478
+ for (const target of targets) {
5479
+ if (isIndexableName(target) || !isWithinProject(projectRoot, target)) {
5480
+ expanded.add(target);
5481
+ continue;
5482
+ }
5483
+ let stats;
5484
+ try {
5485
+ stats = await fs5.stat(target);
5486
+ } catch (err) {
5487
+ if (!isMissingPathError(err)) {
5488
+ expanded.add(target);
5489
+ continue;
5490
+ }
5491
+ }
5492
+ if (stats && !stats.isDirectory()) {
5493
+ expanded.add(target);
5494
+ continue;
5495
+ }
5496
+ indexedFiles ??= store.getAllFileMetas().map((meta) => meta.file);
5497
+ const prefix = normalizeComparablePath(target) + path6.sep;
5498
+ for (const file of indexedFiles) {
5499
+ if (normalizeComparablePath(file).startsWith(prefix)) expanded.add(file);
5500
+ }
5501
+ if (!stats) continue;
5502
+ const walk = async (dir) => {
5503
+ throwIfAborted(signal);
5504
+ let entries;
5505
+ try {
5506
+ entries = await fs5.readdir(dir, { withFileTypes: true });
5507
+ } catch {
5508
+ return;
5509
+ }
5510
+ for (const entry of entries) {
5511
+ if (DEFAULT_IGNORE.includes(entry.name)) continue;
5512
+ const full = path6.join(dir, entry.name);
5513
+ const rel = path6.relative(projectRoot, full).replace(/\\/g, "/");
5514
+ if (entry.isDirectory()) {
5515
+ if (!isGitIgnored(rel, true)) await walk(full);
5516
+ } else if (entry.isFile() && isIndexableName(full)) {
5517
+ expanded.add(full);
5518
+ }
5519
+ }
5520
+ };
5521
+ await walk(target);
5522
+ }
5523
+ return [...expanded];
5524
+ }
5191
5525
  async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
5192
5526
  const gitFiles = await findGitSourceFiles(projectRoot, ignore, signal);
5193
5527
  if (gitFiles) {
@@ -5222,15 +5556,15 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
5222
5556
  dirCount++;
5223
5557
  for (const e of entries) {
5224
5558
  if (ignoreSet.has(e.name)) continue;
5225
- const full = path5.join(dir, e.name);
5226
- const rel = path5.relative(projectRoot, full).replace(/\\/g, "/");
5559
+ const full = path6.join(dir, e.name);
5560
+ const rel = path6.relative(projectRoot, full).replace(/\\/g, "/");
5227
5561
  if (e.isDirectory()) {
5228
5562
  if (isGitIgnored(rel, true)) continue;
5229
5563
  await walk(full);
5230
5564
  } else if (e.isFile()) {
5231
5565
  if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
5232
5566
  if (isAtlasProjection(rel)) continue;
5233
- const ext = path5.extname(e.name).toLowerCase();
5567
+ const ext = path6.extname(e.name).toLowerCase();
5234
5568
  if (indexableExts.has(ext) || detectLang(full) !== null) {
5235
5569
  results.push(full);
5236
5570
  }
@@ -5269,13 +5603,15 @@ async function resolveProjectRelations(store, projectRoot, opts) {
5269
5603
  if (opts.signal?.aborted) return;
5270
5604
  store.setFilePackages(assignPackageLabels(structure, indexedFiles));
5271
5605
  const resolver = new ModuleResolver(structure, indexedFiles, store.getNamespaceDeclarations());
5272
- const pending = store.getUnresolvedImports(opts.onlyFiles);
5606
+ const scope = opts.onlyFiles ? [.../* @__PURE__ */ new Set([...opts.onlyFiles, ...store.getFilesWithDanglingImports()])] : void 0;
5607
+ const pending = store.getUnresolvedImports(scope);
5273
5608
  const resolutions = [];
5274
5609
  for (const entry of pending) {
5275
5610
  const toFile = resolver.resolve(entry.fromFile, entry.lang, entry.module);
5276
- if (toFile && toFile !== entry.fromFile) {
5277
- resolutions.push({ ...entry, toFile });
5278
- }
5611
+ resolutions.push({
5612
+ ...entry,
5613
+ toFile: toFile && toFile !== entry.fromFile ? toFile : null
5614
+ });
5279
5615
  }
5280
5616
  if (opts.signal?.aborted) return;
5281
5617
  store.applyImportResolutions(resolutions);
@@ -5309,7 +5645,8 @@ async function runIndexerAtomic(store, opts) {
5309
5645
  const { projectRoot, langs, ignore = [], signal } = opts;
5310
5646
  const relationGraphVersion = "2";
5311
5647
  const refResolutionVersion = "2";
5312
- const force = (opts.force ?? false) || store.getMetadata("relation_graph_version") !== relationGraphVersion;
5648
+ const graphVersionStale = store.getMetadata("relation_graph_version") !== relationGraphVersion;
5649
+ const force = (opts.force ?? false) || graphVersionStale;
5313
5650
  const needsFullRefResolution = force || store.getMetadata("ref_resolution_version") !== refResolutionVersion;
5314
5651
  const startMs = Date.now();
5315
5652
  const errors = [];
@@ -5327,10 +5664,17 @@ async function runIndexerAtomic(store, opts) {
5327
5664
  let trustedUnchanged;
5328
5665
  let discoverySnapshotKey;
5329
5666
  if (opts.files && opts.files.length > 0) {
5330
- files = opts.files.map((f) => path5.resolve(projectRoot, f)).filter((f) => {
5667
+ const targeted = await expandTargetedDirectories(
5668
+ store,
5669
+ projectRoot,
5670
+ opts.files.map((f) => path6.resolve(projectRoot, f)),
5671
+ isGitIgnored,
5672
+ signal
5673
+ );
5674
+ files = targeted.filter((f) => {
5331
5675
  if (!isWithinProject(projectRoot, f)) return false;
5332
- const rel = path5.relative(projectRoot, f).replace(/\\/g, "/");
5333
- return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path5.basename(f)) && !isAtlasProjection(rel) && !isGitIgnored(rel, false);
5676
+ const rel = path6.relative(projectRoot, f).replace(/\\/g, "/");
5677
+ return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path6.basename(f)) && !isAtlasProjection(rel) && !isGitIgnored(rel, false);
5334
5678
  });
5335
5679
  } else {
5336
5680
  const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
@@ -5348,7 +5692,8 @@ async function runIndexerAtomic(store, opts) {
5348
5692
  return lang ? langSet.has(lang) : false;
5349
5693
  });
5350
5694
  }
5351
- if (force) store.clearAll();
5695
+ const scopedRun = (langs?.length ?? 0) > 0 || (opts.files?.length ?? 0) > 0;
5696
+ if (force && (graphVersionStale || !scopedRun)) store.clearAll();
5352
5697
  const existingMeta = /* @__PURE__ */ new Map();
5353
5698
  if (!force) {
5354
5699
  for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
@@ -5513,7 +5858,10 @@ async function runIndexerAtomic(store, opts) {
5513
5858
  }
5514
5859
  const result = settled.value;
5515
5860
  if (result.error) {
5516
- if (result.missing) store.deleteFile(file);
5861
+ if (result.missing) {
5862
+ store.deleteFile(file);
5863
+ continue;
5864
+ }
5517
5865
  errors.push(`${file}: ${result.error}`);
5518
5866
  filesFailed++;
5519
5867
  continue;
@@ -5671,7 +6019,7 @@ async function runIndexerAtomic(store, opts) {
5671
6019
  }
5672
6020
 
5673
6021
  // src/codebase-index/index-service.ts
5674
- import * as path6 from "node:path";
6022
+ import * as path7 from "node:path";
5675
6023
  async function indexService(args, hooks = {}) {
5676
6024
  const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
5677
6025
  try {
@@ -5725,7 +6073,7 @@ function contextService(args) {
5725
6073
  pathPrefix: args.pathPrefix
5726
6074
  },
5727
6075
  (file) => {
5728
- const relative3 = path6.relative(args.projectRoot, file);
6076
+ const relative3 = path7.relative(args.projectRoot, file);
5729
6077
  if (!relative3 || relative3.startsWith("..")) return posixIndexPath(file);
5730
6078
  return posixIndexPath(relative3);
5731
6079
  }
@@ -5817,6 +6165,11 @@ export {
5817
6165
  pageRank,
5818
6166
  toSymbolRankRows,
5819
6167
  aggregateFileRank,
6168
+ RANK_VERSION,
6169
+ RANK_VERSION_KEY,
6170
+ RANK_REFRESH_FILE_THRESHOLD,
6171
+ shouldRefreshRanks,
6172
+ runGraphRankPass,
5820
6173
  getWiringSnapshot,
5821
6174
  clearWiringSnapshot,
5822
6175
  posixIndexPath,
@@ -5827,11 +6180,6 @@ export {
5827
6180
  DEFAULT_SYMBOLS_PER_FILE,
5828
6181
  retrieveContext,
5829
6182
  xxhash64String,
5830
- RANK_VERSION,
5831
- RANK_VERSION_KEY,
5832
- RANK_REFRESH_FILE_THRESHOLD,
5833
- shouldRefreshRanks,
5834
- runGraphRankPass,
5835
6183
  tokenise,
5836
6184
  buildIndexableText,
5837
6185
  buildBm25Index,
@@ -5856,4 +6204,4 @@ export {
5856
6204
  incomingCallsService,
5857
6205
  outgoingCallsService
5858
6206
  };
5859
- //# sourceMappingURL=chunk-NEYUPZL6.js.map
6207
+ //# sourceMappingURL=chunk-6D3UIQMF.js.map