scip-query 0.4.1 → 0.4.3

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 (138) hide show
  1. package/dist/{chunk-JHVQB4Y5.js → chunk-334PCFO3.js} +12 -12
  2. package/dist/{chunk-OXX3QF24.js → chunk-46ZTW4AI.js} +2 -2
  3. package/dist/{chunk-24LF6IZB.js → chunk-4YN3PE57.js} +3 -3
  4. package/dist/{chunk-3NJSJ7TE.js → chunk-5YB6UXQ3.js} +4 -15
  5. package/dist/{chunk-Z5VSUOEE.js → chunk-6PVHJ332.js} +2 -2
  6. package/dist/{chunk-EAGKJFDX.js → chunk-7KGTWDAX.js} +3 -3
  7. package/dist/{chunk-PU2254N2.js → chunk-7OGXSMLY.js} +5 -15
  8. package/dist/{chunk-R7HPHMRZ.js → chunk-AEBM56CO.js} +3 -3
  9. package/dist/{chunk-SYQR4QGK.js → chunk-B747RITP.js} +3 -3
  10. package/dist/{chunk-A6XLXV6W.js → chunk-B7LDMCUS.js} +3 -3
  11. package/dist/{chunk-KLNKDX6A.js → chunk-C2VSV54P.js} +4 -4
  12. package/dist/{chunk-VKBOLNYN.js → chunk-DIYEUFVP.js} +10 -8
  13. package/dist/{chunk-RE7POFGI.js → chunk-E74RY6AQ.js} +2 -2
  14. package/dist/{chunk-RL74LF47.js → chunk-FIMTTUGE.js} +2 -2
  15. package/dist/{chunk-ALUFWH3U.js → chunk-FMAYH7GS.js} +116 -269
  16. package/dist/{chunk-MGNMHKX3.js → chunk-FO2CBB7U.js} +10 -2
  17. package/dist/{chunk-7BS4CPJX.js → chunk-HESWGDIV.js} +3 -3
  18. package/dist/{chunk-FVJE4MQL.js → chunk-HL2LXSBW.js} +5 -8
  19. package/dist/{chunk-KG4OFQEN.js → chunk-HW76DVE4.js} +3 -3
  20. package/dist/{chunk-RJ5GULL6.js → chunk-IC5NTO47.js} +2 -2
  21. package/dist/{chunk-VY2L4TP6.js → chunk-J34HAAEQ.js} +3 -3
  22. package/dist/{chunk-PCU455MX.js → chunk-JSXGC2EH.js} +2 -2
  23. package/dist/{chunk-NXUIWD6K.js → chunk-LQXBFCP2.js} +3 -3
  24. package/dist/{chunk-GNAMV3JC.js → chunk-NML6M5AS.js} +3 -3
  25. package/dist/{chunk-UJWI5CBB.js → chunk-NNFP4ZRF.js} +4 -7
  26. package/dist/{chunk-6ECR2FLR.js → chunk-NWXTQGUE.js} +4 -15
  27. package/dist/{chunk-ZVZAIIB5.js → chunk-NYZ6INK3.js} +3 -3
  28. package/dist/{chunk-JKXHHV4B.js → chunk-OMVF3BHY.js} +2 -2
  29. package/dist/{chunk-CBIWNZZZ.js → chunk-PGQXIAJF.js} +3 -3
  30. package/dist/{chunk-J47VSL6I.js → chunk-PKDFXASW.js} +3 -3
  31. package/dist/{chunk-POLELLNM.js → chunk-PSK5BPFE.js} +3 -3
  32. package/dist/{chunk-6CH23IAS.js → chunk-QZ4FRB65.js} +9 -7
  33. package/dist/{chunk-TWVXFKJA.js → chunk-R2QBMQQN.js} +4 -4
  34. package/dist/{chunk-5GCORUNV.js → chunk-T3ALCNCP.js} +30 -17
  35. package/dist/{chunk-QMXSLHZP.js → chunk-T6UVM534.js} +2 -2
  36. package/dist/{chunk-6UZU7DFL.js → chunk-U74VYTLX.js} +3 -3
  37. package/dist/{chunk-XUVPQDXW.js → chunk-UIRCHPOU.js} +4 -4
  38. package/dist/{chunk-43A4UCS7.js → chunk-UNS6ZQVX.js} +3 -3
  39. package/dist/{chunk-J6QXMYAQ.js → chunk-VJMTX3OR.js} +3 -3
  40. package/dist/{chunk-SVLUJSY7.js → chunk-XJSPWHNT.js} +4 -15
  41. package/dist/{chunk-W46L2BXT.js → chunk-XMZAC2VU.js} +2 -2
  42. package/dist/{chunk-ELFGD5EW.js → chunk-Y7FKURZG.js} +3 -3
  43. package/dist/{chunk-TO3L4YNK.js → chunk-YMSJCSRG.js} +5 -1
  44. package/dist/{chunk-DUJNJQPO.js → chunk-YQIWS5V6.js} +3 -3
  45. package/dist/{chunk-KYPXKV64.js → chunk-ZPEI7DRJ.js} +30 -15
  46. package/dist/cli.js +471 -473
  47. package/dist/{db-C4rPbKkI.d.ts → db-6F9R9e_t.d.ts} +0 -4
  48. package/dist/index.d.ts +20 -5
  49. package/dist/index.js +237 -81
  50. package/dist/queries/affected.d.ts +1 -1
  51. package/dist/queries/affected.js +3 -3
  52. package/dist/queries/bottlenecks.d.ts +1 -1
  53. package/dist/queries/bottlenecks.js +3 -3
  54. package/dist/queries/by-kind.d.ts +1 -1
  55. package/dist/queries/by-kind.js +3 -3
  56. package/dist/queries/call-graph.d.ts +1 -1
  57. package/dist/queries/call-graph.js +3 -3
  58. package/dist/queries/change-surface.d.ts +1 -1
  59. package/dist/queries/change-surface.js +3 -3
  60. package/dist/queries/code.d.ts +1 -1
  61. package/dist/queries/code.js +3 -3
  62. package/dist/queries/complexity-hotspots.d.ts +1 -1
  63. package/dist/queries/complexity-hotspots.js +3 -3
  64. package/dist/queries/complexity.d.ts +1 -1
  65. package/dist/queries/complexity.js +3 -3
  66. package/dist/queries/convergence.d.ts +1 -1
  67. package/dist/queries/convergence.js +3 -3
  68. package/dist/queries/coupling.d.ts +1 -1
  69. package/dist/queries/coupling.js +3 -3
  70. package/dist/queries/cycles.d.ts +1 -1
  71. package/dist/queries/cycles.js +3 -3
  72. package/dist/queries/dataflow.d.ts +1 -1
  73. package/dist/queries/dataflow.js +3 -3
  74. package/dist/queries/dead.d.ts +1 -1
  75. package/dist/queries/dead.js +4 -4
  76. package/dist/queries/deep-chains.d.ts +1 -1
  77. package/dist/queries/deep-chains.js +3 -3
  78. package/dist/queries/deps.d.ts +1 -1
  79. package/dist/queries/deps.js +3 -3
  80. package/dist/queries/diff-impact.d.ts +1 -1
  81. package/dist/queries/diff-impact.js +2 -2
  82. package/dist/queries/drift.d.ts +1 -1
  83. package/dist/queries/drift.js +3 -3
  84. package/dist/queries/extract-candidates.d.ts +1 -1
  85. package/dist/queries/extract-candidates.js +3 -3
  86. package/dist/queries/fan.d.ts +1 -1
  87. package/dist/queries/fan.js +3 -3
  88. package/dist/queries/files.d.ts +1 -1
  89. package/dist/queries/files.js +1 -1
  90. package/dist/queries/health.d.ts +1 -1
  91. package/dist/queries/health.js +14 -14
  92. package/dist/queries/hierarchy.d.ts +1 -1
  93. package/dist/queries/hierarchy.js +3 -3
  94. package/dist/queries/hotspots.d.ts +1 -1
  95. package/dist/queries/hotspots.js +3 -3
  96. package/dist/queries/imports.d.ts +1 -1
  97. package/dist/queries/imports.js +3 -3
  98. package/dist/queries/index.d.ts +1 -1
  99. package/dist/queries/index.js +45 -45
  100. package/dist/queries/isolated.d.ts +1 -1
  101. package/dist/queries/isolated.js +4 -4
  102. package/dist/queries/members.d.ts +1 -1
  103. package/dist/queries/members.js +3 -3
  104. package/dist/queries/methods.d.ts +1 -1
  105. package/dist/queries/methods.js +3 -3
  106. package/dist/queries/outline.d.ts +1 -1
  107. package/dist/queries/outline.js +3 -3
  108. package/dist/queries/passthrough-candidates.d.ts +1 -1
  109. package/dist/queries/passthrough-candidates.js +3 -3
  110. package/dist/queries/redundant-reexports.d.ts +1 -1
  111. package/dist/queries/redundant-reexports.js +4 -4
  112. package/dist/queries/refs.d.ts +1 -1
  113. package/dist/queries/refs.js +3 -3
  114. package/dist/queries/similar-chains.d.ts +1 -1
  115. package/dist/queries/similar-chains.js +3 -3
  116. package/dist/queries/similar-files.d.ts +1 -1
  117. package/dist/queries/similar-files.js +3 -3
  118. package/dist/queries/similar-signatures.d.ts +1 -1
  119. package/dist/queries/similar-signatures.js +3 -3
  120. package/dist/queries/similar.d.ts +2 -1
  121. package/dist/queries/similar.js +3 -3
  122. package/dist/queries/slice.d.ts +4 -3
  123. package/dist/queries/slice.js +3 -3
  124. package/dist/queries/stale-abstractions.d.ts +1 -1
  125. package/dist/queries/stale-abstractions.js +3 -3
  126. package/dist/queries/stats.d.ts +1 -1
  127. package/dist/queries/surface.d.ts +1 -1
  128. package/dist/queries/surface.js +3 -3
  129. package/dist/queries/symbols.d.ts +1 -1
  130. package/dist/queries/symbols.js +3 -3
  131. package/dist/queries/system.d.ts +1 -1
  132. package/dist/queries/system.js +3 -3
  133. package/dist/queries/trace.d.ts +1 -1
  134. package/dist/queries/trace.js +3 -3
  135. package/dist/queries/wrapper-candidates.d.ts +1 -1
  136. package/dist/queries/wrapper-candidates.js +3 -3
  137. package/dist/reindex-worker.js +146 -5
  138. package/package.json +3 -1
package/dist/cli.js CHANGED
@@ -20,18 +20,10 @@ var ScipDatabase = class {
20
20
  this.db = new Database(config.dbPath, { readonly: true });
21
21
  this.db.pragma("busy_timeout = 5000");
22
22
  }
23
- /** Attach a gitignore-based path filter for query results */
24
- setPathFilter(filter) {
25
- this.pathFilter = filter;
26
- }
27
23
  /** Check if a path should be excluded based on .gitignore rules */
28
24
  isIgnored(relativePath) {
29
25
  return this.pathFilter?.isIgnored(relativePath) ?? false;
30
26
  }
31
- /** Filter an array of paths using the gitignore filter */
32
- filterPaths(paths) {
33
- return this.pathFilter?.filter(paths) ?? paths;
34
- }
35
27
  /**
36
28
  * The local-symbol predicate: only match symbols that are defined
37
29
  * in files NOT excluded by gitignore. This replaces the old hardcoded
@@ -115,7 +107,7 @@ var ScipDatabase = class {
115
107
  // src/gitignore-filter.ts
116
108
  import ignore from "ignore";
117
109
  import { readFileSync, existsSync } from "fs";
118
- import { join, dirname } from "path";
110
+ import { dirname, isAbsolute, join, relative, resolve } from "path";
119
111
  function createGitignoreFilter(projectRoot) {
120
112
  const ig = ignore();
121
113
  let loaded = false;
@@ -132,8 +124,8 @@ function createGitignoreFilter(projectRoot) {
132
124
  ig.add(DEFAULT_IGNORES);
133
125
  }
134
126
  return {
135
- isIgnored: (relativePath) => ig.ignores(relativePath),
136
- filter: (paths) => paths.filter((p) => !ig.ignores(p))
127
+ isIgnored: (relativePath) => safeIgnores(ig, projectRoot, relativePath),
128
+ filter: (paths) => paths.filter((p) => !safeIgnores(ig, projectRoot, p))
137
129
  };
138
130
  }
139
131
  function findGitignoreFiles(projectRoot) {
@@ -213,10 +205,35 @@ Thumbs.db
213
205
  # Type definitions (often noise in queries)
214
206
  *.d.ts
215
207
  `;
208
+ function safeIgnores(ig, projectRoot, inputPath) {
209
+ const relativePath = normalizeForIgnore(projectRoot, inputPath);
210
+ if (!relativePath) {
211
+ return false;
212
+ }
213
+ try {
214
+ return ig.ignores(relativePath);
215
+ } catch {
216
+ return false;
217
+ }
218
+ }
219
+ function normalizeForIgnore(projectRoot, inputPath) {
220
+ if (!inputPath || inputPath === ".") {
221
+ return null;
222
+ }
223
+ if (!isAbsolute(inputPath) && !inputPath.startsWith("..")) {
224
+ return inputPath.replaceAll("\\", "/");
225
+ }
226
+ const absolutePath = isAbsolute(inputPath) ? inputPath : resolve(projectRoot, inputPath);
227
+ const relativePath = relative(projectRoot, absolutePath).replaceAll("\\", "/");
228
+ if (!relativePath || relativePath === "." || relativePath.startsWith("..")) {
229
+ return null;
230
+ }
231
+ return relativePath;
232
+ }
216
233
 
217
234
  // src/config.ts
218
235
  import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
219
- import { join as join2, resolve } from "path";
236
+ import { join as join2, resolve as resolve2 } from "path";
220
237
  import { createHash } from "crypto";
221
238
  import { homedir } from "os";
222
239
  var CONFIG_FILENAME = ".scipquery.json";
@@ -247,10 +264,10 @@ function resolveWatchConfig(config) {
247
264
  function resolveCacheDir(projectRoot, config) {
248
265
  const envOverride = process.env["SCIP_QUERY_CACHE_DIR"];
249
266
  if (envOverride) return ensureDir(envOverride);
250
- if (config?.dbPath) return ensureDir(resolve(projectRoot, config.dbPath));
267
+ if (config?.dbPath) return ensureDir(resolve2(projectRoot, config.dbPath));
251
268
  const xdgCache = process.env["XDG_CACHE_HOME"];
252
269
  const cacheBase = xdgCache || join2(homedir(), ".cache");
253
- const projectHash = createHash("sha256").update(resolve(projectRoot)).digest("hex").slice(0, 12);
270
+ const projectHash = createHash("sha256").update(resolve2(projectRoot)).digest("hex").slice(0, 12);
254
271
  const dir = join2(cacheBase, "scip-query", "projects", projectHash);
255
272
  return ensureDir(dir);
256
273
  }
@@ -286,8 +303,8 @@ function ensureDir(dir) {
286
303
 
287
304
  // src/reindex/index.ts
288
305
  import { execFileSync as execFileSync3 } from "child_process";
289
- import { existsSync as existsSync6, renameSync } from "fs";
290
- import { join as join6 } from "path";
306
+ import { existsSync as existsSync6, renameSync, rmSync } from "fs";
307
+ import { basename, dirname as dirname2, extname as extname2, join as join6 } from "path";
291
308
 
292
309
  // src/scip-cli.ts
293
310
  import { execFileSync as execFileSync2 } from "child_process";
@@ -968,6 +985,128 @@ function resolveDotnetProject(projectRoot, suffixes) {
968
985
  return null;
969
986
  }
970
987
 
988
+ // src/reindex/merge.ts
989
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
990
+ import { create } from "@bufbuild/protobuf";
991
+ import {
992
+ deserializeSCIP,
993
+ serializeSCIP,
994
+ DocumentSchema,
995
+ IndexSchema,
996
+ SymbolInformationSchema
997
+ } from "@c4312/scip";
998
+ function mergeScipIndexes(indexes) {
999
+ if (indexes.length === 0) {
1000
+ throw new Error("Cannot merge zero SCIP indexes");
1001
+ }
1002
+ if (indexes.length === 1) {
1003
+ return indexes[0];
1004
+ }
1005
+ const metadata = mergeMetadata(indexes);
1006
+ const documents = mergeDocuments(indexes.flatMap((index) => index.documents ?? []));
1007
+ const externalSymbols = mergeSymbolInfos(indexes.flatMap((index) => index.externalSymbols ?? []));
1008
+ return create(IndexSchema, {
1009
+ metadata,
1010
+ documents,
1011
+ externalSymbols
1012
+ });
1013
+ }
1014
+ function mergeScipFiles(inputPaths, outputPath) {
1015
+ if (inputPaths.length === 0) {
1016
+ throw new Error("Cannot merge zero SCIP files");
1017
+ }
1018
+ const indexes = inputPaths.map((path2) => deserializeSCIP(readFileSync3(path2)));
1019
+ const merged = mergeScipIndexes(indexes);
1020
+ writeFileSync2(outputPath, Buffer.from(serializeSCIP(merged)));
1021
+ return {
1022
+ documentCount: merged.documents.length,
1023
+ externalSymbolCount: merged.externalSymbols.length,
1024
+ inputCount: inputPaths.length
1025
+ };
1026
+ }
1027
+ function mergeMetadata(indexes) {
1028
+ const first = indexes[0]?.metadata;
1029
+ if (!first) {
1030
+ return void 0;
1031
+ }
1032
+ const expectedProjectRoot = first.projectRoot;
1033
+ for (const index of indexes.slice(1)) {
1034
+ const actualProjectRoot = index.metadata?.projectRoot;
1035
+ if (expectedProjectRoot && actualProjectRoot && actualProjectRoot !== expectedProjectRoot) {
1036
+ throw new Error(
1037
+ `Cannot merge SCIP indexes with different project roots: ${expectedProjectRoot} vs ${actualProjectRoot}`
1038
+ );
1039
+ }
1040
+ }
1041
+ return first;
1042
+ }
1043
+ function mergeDocuments(documents) {
1044
+ const byPath = /* @__PURE__ */ new Map();
1045
+ for (const document of documents) {
1046
+ const existing = byPath.get(document.relativePath);
1047
+ if (!existing) {
1048
+ byPath.set(document.relativePath, document);
1049
+ continue;
1050
+ }
1051
+ byPath.set(document.relativePath, create(DocumentSchema, {
1052
+ language: existing.language || document.language,
1053
+ relativePath: existing.relativePath || document.relativePath,
1054
+ occurrences: [...existing.occurrences, ...document.occurrences],
1055
+ symbols: mergeSymbolInfos([...existing.symbols, ...document.symbols]),
1056
+ text: chooseText(existing.text, document.text),
1057
+ positionEncoding: existing.positionEncoding || document.positionEncoding
1058
+ }));
1059
+ }
1060
+ return [...byPath.values()];
1061
+ }
1062
+ function mergeSymbolInfos(symbols2) {
1063
+ const bySymbol = /* @__PURE__ */ new Map();
1064
+ for (const symbol of symbols2) {
1065
+ const existing = bySymbol.get(symbol.symbol);
1066
+ if (!existing) {
1067
+ bySymbol.set(symbol.symbol, symbol);
1068
+ continue;
1069
+ }
1070
+ bySymbol.set(symbol.symbol, create(SymbolInformationSchema, {
1071
+ symbol: existing.symbol,
1072
+ documentation: uniqueStrings([...existing.documentation, ...symbol.documentation]),
1073
+ relationships: mergeRelationships([...existing.relationships, ...symbol.relationships]),
1074
+ kind: existing.kind || symbol.kind,
1075
+ displayName: existing.displayName || symbol.displayName,
1076
+ enclosingSymbol: existing.enclosingSymbol || symbol.enclosingSymbol,
1077
+ signatureDocumentation: existing.signatureDocumentation ?? symbol.signatureDocumentation
1078
+ }));
1079
+ }
1080
+ return [...bySymbol.values()];
1081
+ }
1082
+ function mergeRelationships(relationships) {
1083
+ const seen = /* @__PURE__ */ new Set();
1084
+ const merged = [];
1085
+ for (const relationship of relationships) {
1086
+ const key = [
1087
+ relationship.symbol,
1088
+ relationship.isReference ? "1" : "0",
1089
+ relationship.isImplementation ? "1" : "0",
1090
+ relationship.isTypeDefinition ? "1" : "0",
1091
+ relationship.isDefinition ? "1" : "0"
1092
+ ].join("|");
1093
+ if (seen.has(key)) {
1094
+ continue;
1095
+ }
1096
+ seen.add(key);
1097
+ merged.push(relationship);
1098
+ }
1099
+ return merged;
1100
+ }
1101
+ function chooseText(left, right) {
1102
+ if (!left) return right;
1103
+ if (!right) return left;
1104
+ return left.length >= right.length ? left : right;
1105
+ }
1106
+ function uniqueStrings(values) {
1107
+ return [...new Set(values)];
1108
+ }
1109
+
971
1110
  // src/reindex/index.ts
972
1111
  async function reindex(opts) {
973
1112
  const {
@@ -1003,7 +1142,11 @@ async function reindex(opts) {
1003
1142
  ...process.env,
1004
1143
  NODE_OPTIONS: `--max-old-space-size=${maxHeapMb}`
1005
1144
  };
1006
- for (const lang of languages) {
1145
+ const languageOutputs = languages.map((language, index) => ({
1146
+ language,
1147
+ scipPath: languages.length > 1 ? tempScipPath(outputScip, language, index) : outputScip
1148
+ }));
1149
+ for (const { language: lang, scipPath } of languageOutputs) {
1007
1150
  const config = getIndexerConfig(lang);
1008
1151
  const binaryLabel = describeIndexerBinary(config);
1009
1152
  const projectLocalBinary = resolveProjectLocalIndexerBinary(config, projectRoot);
@@ -1033,7 +1176,7 @@ async function reindex(opts) {
1033
1176
  const indexerEnv = getIndexerExecutionEnv(config, env, resolvedBinary);
1034
1177
  const { binary, args } = config.indexArgs({
1035
1178
  projectRoot,
1036
- outputPath: outputScip,
1179
+ outputPath: scipPath,
1037
1180
  pnpmWorkspaces: opts.pnpmWorkspaces,
1038
1181
  indexerBinary: resolvedBinary
1039
1182
  });
@@ -1052,7 +1195,11 @@ Make sure ${binaryLabel} is installed and available on PATH.`,
1052
1195
  { cause: err }
1053
1196
  );
1054
1197
  }
1055
- moveDefaultOutputIfNeeded(config, projectRoot, outputScip);
1198
+ moveDefaultOutputIfNeeded(config, projectRoot, scipPath);
1199
+ }
1200
+ if (languageOutputs.length > 1) {
1201
+ onStatus(`Merging ${languageOutputs.length} language indexes...`);
1202
+ mergeScipFiles(languageOutputs.map((entry) => entry.scipPath), outputScip);
1056
1203
  }
1057
1204
  onStatus("Converting to SQLite...");
1058
1205
  if (!existsSync6(outputScip)) {
@@ -1067,6 +1214,12 @@ Make sure ${binaryLabel} is installed and available on PATH.`,
1067
1214
  } catch (err) {
1068
1215
  const msg = err instanceof Error ? err.message : String(err);
1069
1216
  throw new Error(`Failed to convert SCIP index to SQLite: ${msg}`, { cause: err });
1217
+ } finally {
1218
+ for (const { scipPath } of languageOutputs) {
1219
+ if (scipPath !== outputScip) {
1220
+ rmSync(scipPath, { force: true });
1221
+ }
1222
+ }
1070
1223
  }
1071
1224
  const durationMs = Date.now() - start;
1072
1225
  onStatus(`Done in ${(durationMs / 1e3).toFixed(1)}s`);
@@ -1081,11 +1234,16 @@ function moveDefaultOutputIfNeeded(config, projectRoot, outputScip) {
1081
1234
  renameSync(defaultOutputPath, outputScip);
1082
1235
  }
1083
1236
  }
1237
+ function tempScipPath(outputScip, language, index) {
1238
+ const extension = extname2(outputScip) || ".scip";
1239
+ const stem = basename(outputScip, extension);
1240
+ return join6(dirname2(outputScip), `${stem}.${index + 1}.${language}${extension}`);
1241
+ }
1084
1242
 
1085
1243
  // src/watch.ts
1086
1244
  import { watch } from "fs";
1087
1245
  import { existsSync as existsSync7, renameSync as renameSync2 } from "fs";
1088
- import { join as join7, relative } from "path";
1246
+ import { join as join7, relative as relative2 } from "path";
1089
1247
  import { fork } from "child_process";
1090
1248
  import ignore2 from "ignore";
1091
1249
  var Watcher = class {
@@ -1157,13 +1315,9 @@ var Watcher = class {
1157
1315
  if (this.cooldownTimer) clearTimeout(this.cooldownTimer);
1158
1316
  this.setStatus({ state: "idle" });
1159
1317
  }
1160
- /** Get current watcher status */
1161
- getStatus() {
1162
- return this.status;
1163
- }
1164
1318
  // ── Internal ─────────────────────────────────────────────
1165
1319
  handleFileChange(filename) {
1166
- const rel = relative(this.projectRoot, join7(this.projectRoot, filename));
1320
+ const rel = relative2(this.projectRoot, join7(this.projectRoot, filename));
1167
1321
  if (this.gitignoreFilter.isIgnored(rel)) return;
1168
1322
  if (this.extraIgnore.ignores(rel)) return;
1169
1323
  if (filename.endsWith("index.db") || filename.endsWith("index.scip") || filename.endsWith("index.db.tmp") || filename.endsWith(".scipquery.json")) {
@@ -1244,10 +1398,10 @@ var Watcher = class {
1244
1398
  * Writes to index.db.tmp, then atomically renames to index.db.
1245
1399
  */
1246
1400
  runReindex() {
1247
- return new Promise((resolve4, reject) => {
1401
+ return new Promise((resolve5, reject) => {
1248
1402
  const start = Date.now();
1249
1403
  const tmpDb = this.indexPaths.dbPath + ".tmp";
1250
- const tmpScip = tempScipPath(this.indexPaths.indexPath);
1404
+ const tmpScip = tempScipPath2(this.indexPaths.indexPath);
1251
1405
  const child = fork(
1252
1406
  new URL("./reindex-worker.js", import.meta.url).pathname,
1253
1407
  [],
@@ -1272,7 +1426,7 @@ var Watcher = class {
1272
1426
  if (existsSync7(tmpScip)) {
1273
1427
  renameSync2(tmpScip, this.indexPaths.indexPath);
1274
1428
  }
1275
- resolve4(Date.now() - start);
1429
+ resolve5(Date.now() - start);
1276
1430
  } catch (err) {
1277
1431
  reject(new Error(`Atomic swap failed: ${err}`));
1278
1432
  }
@@ -1288,7 +1442,7 @@ var Watcher = class {
1288
1442
  this.onStatus(status);
1289
1443
  }
1290
1444
  };
1291
- function tempScipPath(indexPath) {
1445
+ function tempScipPath2(indexPath) {
1292
1446
  return indexPath.endsWith(".scip") ? indexPath.slice(0, -".scip".length) + ".tmp.scip" : indexPath + ".tmp.scip";
1293
1447
  }
1294
1448
 
@@ -1313,31 +1467,39 @@ function stats(db) {
1313
1467
  }
1314
1468
 
1315
1469
  // src/queries/files.ts
1470
+ function globToLike(pattern) {
1471
+ const hasGlobChars = /[*?]/.test(pattern);
1472
+ if (!hasGlobChars) {
1473
+ return `%${pattern}%`;
1474
+ }
1475
+ return pattern.replace(/\*\*/g, "%").replace(/\*/g, "%").replace(/\?/g, "_");
1476
+ }
1316
1477
  function files(db, pattern) {
1478
+ const likePattern = globToLike(pattern);
1317
1479
  const rows = db.all(
1318
1480
  `SELECT relative_path FROM documents
1319
1481
  WHERE relative_path LIKE ?
1320
1482
  ORDER BY relative_path`,
1321
- `%${pattern}%`
1483
+ likePattern
1322
1484
  );
1323
1485
  return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({ relativePath: r.relative_path }));
1324
1486
  }
1325
1487
 
1326
1488
  // src/query-support.ts
1327
- import { basename as basename2 } from "path";
1489
+ import { basename as basename3 } from "path";
1328
1490
 
1329
1491
  // src/source-analysis.ts
1330
1492
  import {
1331
1493
  existsSync as existsSync8,
1332
- readFileSync as readFileSync3
1494
+ readFileSync as readFileSync4
1333
1495
  } from "fs";
1334
1496
  import {
1335
- basename,
1336
- dirname as dirname2,
1337
- extname as extname2,
1497
+ basename as basename2,
1498
+ dirname as dirname3,
1499
+ extname as extname3,
1338
1500
  join as join8,
1339
- relative as relative2,
1340
- resolve as resolve2
1501
+ relative as relative3,
1502
+ resolve as resolve3
1341
1503
  } from "path";
1342
1504
  var SOURCE_IMPORT_CACHE = /* @__PURE__ */ new WeakMap();
1343
1505
  var SOURCE_EXPORT_CACHE = /* @__PURE__ */ new WeakMap();
@@ -1366,7 +1528,7 @@ function getSourceImports(db, relativePath) {
1366
1528
  cache.set(normalized, []);
1367
1529
  return [];
1368
1530
  }
1369
- const source = readFileSync3(fullPath, "utf-8");
1531
+ const source = readFileSync4(fullPath, "utf-8");
1370
1532
  const parsed = isPythonSourcePath(normalized) ? parsePythonImports(db, normalized, source) : isJavaScriptSourcePath(normalized) ? parseJavaScriptImports(db, normalized, source) : isJvmSourcePath(normalized) ? parseJvmImports(db, normalized, source) : isRustSourcePath(normalized) ? parseRustImports(db, normalized, source) : isRubySourcePath(normalized) ? parseRubyImports(db, normalized, source) : isCLikeSourcePath(normalized) ? parseCLikeImports(db, normalized, source) : isDotNetSourcePath(normalized) ? parseDotNetImports(db, normalized, source) : isDartSourcePath(normalized) ? parseDartImports(db, normalized, source) : isPhpSourcePath(normalized) ? parsePhpImports(db, normalized, source) : [];
1371
1533
  cache.set(normalized, parsed);
1372
1534
  return parsed;
@@ -1383,7 +1545,7 @@ function getSourceExports(db, relativePath) {
1383
1545
  cache.set(normalized, []);
1384
1546
  return [];
1385
1547
  }
1386
- const source = readFileSync3(fullPath, "utf-8");
1548
+ const source = readFileSync4(fullPath, "utf-8");
1387
1549
  const parsed = isDartSourcePath(normalized) ? parseDartExports(db, normalized, source) : isRustSourcePath(normalized) ? parseRustExports(db, normalized, source) : [];
1388
1550
  cache.set(normalized, parsed);
1389
1551
  return parsed;
@@ -1680,7 +1842,7 @@ function parseRubyImports(db, importerPath, source) {
1680
1842
  return statements;
1681
1843
  }
1682
1844
  function rubyConstantName(specifier) {
1683
- return basename(specifier).replace(/\.[^.]+$/, "").split("_").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1845
+ return basename2(specifier).replace(/\.[^.]+$/, "").split("_").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1684
1846
  }
1685
1847
  function parseCLikeImports(db, importerPath, source) {
1686
1848
  const statements = [];
@@ -1689,7 +1851,7 @@ function parseCLikeImports(db, importerPath, source) {
1689
1851
  const full = match[0];
1690
1852
  if (!specifier || !full || typeof match.index !== "number") continue;
1691
1853
  const body = buildUsageBody(source, match.index, match.index + full.length);
1692
- const localName = basename(specifier).replace(/\.[^.]+$/, "");
1854
+ const localName = basename2(specifier).replace(/\.[^.]+$/, "");
1693
1855
  statements.push({
1694
1856
  importedName: specifier,
1695
1857
  localName,
@@ -2164,16 +2326,16 @@ function resolveJavaScriptImportPath(db, importerPath, specifier) {
2164
2326
  if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
2165
2327
  return null;
2166
2328
  }
2167
- const importerDir = dirname2(join8(db.config.projectRoot, importerPath));
2168
- const absolute = resolve2(importerDir, specifier);
2329
+ const importerDir = dirname3(join8(db.config.projectRoot, importerPath));
2330
+ const absolute = resolve3(importerDir, specifier);
2169
2331
  const indexedPaths = getIndexedPaths(db);
2170
2332
  for (const candidate of candidateImportPaths(absolute)) {
2171
- const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
2333
+ const relativeCandidate = normalizePath(relative3(db.config.projectRoot, candidate));
2172
2334
  if (indexedPaths.has(relativeCandidate) || existsSync8(candidate)) {
2173
2335
  return relativeCandidate;
2174
2336
  }
2175
2337
  }
2176
- return normalizePath(relative2(db.config.projectRoot, absolute));
2338
+ return normalizePath(relative3(db.config.projectRoot, absolute));
2177
2339
  }
2178
2340
  function resolvePythonImportPath(db, importerPath, specifier) {
2179
2341
  const indexedPaths = getIndexedPaths(db);
@@ -2183,16 +2345,16 @@ function resolvePythonImportPath(db, importerPath, specifier) {
2183
2345
  if (!match) return null;
2184
2346
  const dots = match[1].length;
2185
2347
  const remainder = match[2].replace(/^\./, "");
2186
- let baseDir = dirname2(join8(db.config.projectRoot, importerPath));
2348
+ let baseDir = dirname3(join8(db.config.projectRoot, importerPath));
2187
2349
  for (let i = 1; i < dots; i++) {
2188
- baseDir = dirname2(baseDir);
2350
+ baseDir = dirname3(baseDir);
2189
2351
  }
2190
- basePath = remainder ? resolve2(baseDir, remainder.replace(/\./g, "/")) : baseDir;
2352
+ basePath = remainder ? resolve3(baseDir, remainder.replace(/\./g, "/")) : baseDir;
2191
2353
  } else {
2192
- basePath = resolve2(db.config.projectRoot, specifier.replace(/\./g, "/"));
2354
+ basePath = resolve3(db.config.projectRoot, specifier.replace(/\./g, "/"));
2193
2355
  }
2194
2356
  for (const candidate of pythonCandidateImportPaths(basePath)) {
2195
- const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
2357
+ const relativeCandidate = normalizePath(relative3(db.config.projectRoot, candidate));
2196
2358
  if (indexedPaths.has(relativeCandidate) || existsSync8(candidate)) {
2197
2359
  return relativeCandidate;
2198
2360
  }
@@ -2205,17 +2367,17 @@ function resolveRustImportPath(db, importerPath, specifier) {
2205
2367
  if (!normalizedSpecifier.startsWith("crate::") && !normalizedSpecifier.startsWith("self::") && !normalizedSpecifier.startsWith("super::")) {
2206
2368
  return null;
2207
2369
  }
2208
- const importerDir = dirname2(join8(db.config.projectRoot, importerPath));
2370
+ const importerDir = dirname3(join8(db.config.projectRoot, importerPath));
2209
2371
  let basePath;
2210
2372
  if (normalizedSpecifier.startsWith("crate::")) {
2211
- basePath = resolve2(db.config.projectRoot, "src", normalizedSpecifier.slice("crate::".length).replace(/::/g, "/"));
2373
+ basePath = resolve3(db.config.projectRoot, "src", normalizedSpecifier.slice("crate::".length).replace(/::/g, "/"));
2212
2374
  } else if (normalizedSpecifier.startsWith("self::")) {
2213
- basePath = resolve2(importerDir, normalizedSpecifier.slice("self::".length).replace(/::/g, "/"));
2375
+ basePath = resolve3(importerDir, normalizedSpecifier.slice("self::".length).replace(/::/g, "/"));
2214
2376
  } else {
2215
- basePath = resolve2(dirname2(importerDir), normalizedSpecifier.slice("super::".length).replace(/::/g, "/"));
2377
+ basePath = resolve3(dirname3(importerDir), normalizedSpecifier.slice("super::".length).replace(/::/g, "/"));
2216
2378
  }
2217
2379
  for (const candidate of rustCandidateImportPaths(basePath)) {
2218
- const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
2380
+ const relativeCandidate = normalizePath(relative3(db.config.projectRoot, candidate));
2219
2381
  if (getIndexedPaths(db).has(relativeCandidate) || existsSync8(candidate)) {
2220
2382
  return relativeCandidate;
2221
2383
  }
@@ -2223,10 +2385,10 @@ function resolveRustImportPath(db, importerPath, specifier) {
2223
2385
  return null;
2224
2386
  }
2225
2387
  function resolveRubyImportPath(db, importerPath, specifier) {
2226
- const importerDir = dirname2(join8(db.config.projectRoot, importerPath));
2227
- const absolute = resolve2(importerDir, specifier);
2388
+ const importerDir = dirname3(join8(db.config.projectRoot, importerPath));
2389
+ const absolute = resolve3(importerDir, specifier);
2228
2390
  for (const candidate of rubyCandidateImportPaths(absolute)) {
2229
- const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
2391
+ const relativeCandidate = normalizePath(relative3(db.config.projectRoot, candidate));
2230
2392
  if (getIndexedPaths(db).has(relativeCandidate) || existsSync8(candidate)) {
2231
2393
  return relativeCandidate;
2232
2394
  }
@@ -2235,15 +2397,15 @@ function resolveRubyImportPath(db, importerPath, specifier) {
2235
2397
  }
2236
2398
  function resolveCLikeImportPath(db, importerPath, specifier) {
2237
2399
  const indexedPaths = getIndexedPaths(db);
2238
- const importerDir = dirname2(join8(db.config.projectRoot, importerPath));
2400
+ const importerDir = dirname3(join8(db.config.projectRoot, importerPath));
2239
2401
  const candidates = [
2240
- resolve2(importerDir, specifier),
2241
- resolve2(db.config.projectRoot, specifier),
2242
- resolve2(db.config.projectRoot, "include", specifier),
2243
- resolve2(db.config.projectRoot, "src", specifier)
2402
+ resolve3(importerDir, specifier),
2403
+ resolve3(db.config.projectRoot, specifier),
2404
+ resolve3(db.config.projectRoot, "include", specifier),
2405
+ resolve3(db.config.projectRoot, "src", specifier)
2244
2406
  ];
2245
2407
  for (const candidate of candidates) {
2246
- const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
2408
+ const relativeCandidate = normalizePath(relative3(db.config.projectRoot, candidate));
2247
2409
  if (indexedPaths.has(relativeCandidate) || existsSync8(candidate)) {
2248
2410
  return relativeCandidate;
2249
2411
  }
@@ -2261,10 +2423,10 @@ function resolveQualifiedImportPath(db, specifier, extensions) {
2261
2423
  if (exact) return exact;
2262
2424
  }
2263
2425
  for (const ext of extensions) {
2264
- const basenameMatch = [...indexedPaths].find((relativePath) => basename(relativePath) === `${basenameOnly}${ext}`);
2426
+ const basenameMatch = [...indexedPaths].find((relativePath) => basename2(relativePath) === `${basenameOnly}${ext}`);
2265
2427
  if (basenameMatch) return basenameMatch;
2266
2428
  }
2267
- const folderMatches = [...indexedPaths].filter((relativePath) => extensions.includes(extname2(relativePath).toLowerCase())).filter((relativePath) => relativePath.includes(`/${pathified}/`) || relativePath.includes(`/${basenameOnly}/`)).sort((left, right) => left.localeCompare(right));
2429
+ const folderMatches = [...indexedPaths].filter((relativePath) => extensions.includes(extname3(relativePath).toLowerCase())).filter((relativePath) => relativePath.includes(`/${pathified}/`) || relativePath.includes(`/${basenameOnly}/`)).sort((left, right) => left.localeCompare(right));
2268
2430
  if (folderMatches.length === 1) {
2269
2431
  return folderMatches[0];
2270
2432
  }
@@ -2281,10 +2443,10 @@ function resolveDartImportPath(db, importerPath, specifier) {
2281
2443
  if (indexedPaths.has(candidate)) return candidate;
2282
2444
  return null;
2283
2445
  }
2284
- const importerDir = dirname2(join8(db.config.projectRoot, importerPath));
2285
- const absolute = resolve2(importerDir, specifier);
2446
+ const importerDir = dirname3(join8(db.config.projectRoot, importerPath));
2447
+ const absolute = resolve3(importerDir, specifier);
2286
2448
  for (const candidate of dartCandidateImportPaths(absolute)) {
2287
- const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
2449
+ const relativeCandidate = normalizePath(relative3(db.config.projectRoot, candidate));
2288
2450
  if (indexedPaths.has(relativeCandidate) || existsSync8(candidate)) {
2289
2451
  return relativeCandidate;
2290
2452
  }
@@ -2292,7 +2454,7 @@ function resolveDartImportPath(db, importerPath, specifier) {
2292
2454
  return null;
2293
2455
  }
2294
2456
  function pythonCandidateImportPaths(basePath) {
2295
- const ext = extname2(basePath);
2457
+ const ext = extname3(basePath);
2296
2458
  if (PYTHON_SOURCE_EXTENSIONS.includes(ext)) {
2297
2459
  return [basePath];
2298
2460
  }
@@ -2304,7 +2466,7 @@ function pythonCandidateImportPaths(basePath) {
2304
2466
  ];
2305
2467
  }
2306
2468
  function rustCandidateImportPaths(basePath) {
2307
- const ext = extname2(basePath);
2469
+ const ext = extname3(basePath);
2308
2470
  if (RUST_SOURCE_EXTENSIONS.includes(ext)) {
2309
2471
  return [basePath];
2310
2472
  }
@@ -2314,7 +2476,7 @@ function rustCandidateImportPaths(basePath) {
2314
2476
  ];
2315
2477
  }
2316
2478
  function rubyCandidateImportPaths(basePath) {
2317
- const ext = extname2(basePath);
2479
+ const ext = extname3(basePath);
2318
2480
  if (RUBY_SOURCE_EXTENSIONS.includes(ext)) {
2319
2481
  return [basePath];
2320
2482
  }
@@ -2324,14 +2486,14 @@ function rubyCandidateImportPaths(basePath) {
2324
2486
  ];
2325
2487
  }
2326
2488
  function dartCandidateImportPaths(basePath) {
2327
- const ext = extname2(basePath);
2489
+ const ext = extname3(basePath);
2328
2490
  if (DART_SOURCE_EXTENSIONS.includes(ext)) {
2329
2491
  return [basePath];
2330
2492
  }
2331
2493
  return [`${basePath}.dart`, basePath];
2332
2494
  }
2333
2495
  function candidateImportPaths(absolute) {
2334
- const ext = extname2(absolute);
2496
+ const ext = extname3(absolute);
2335
2497
  const candidates = /* @__PURE__ */ new Set();
2336
2498
  if (ext) {
2337
2499
  candidates.add(absolute);
@@ -2373,44 +2535,55 @@ function getCachedMap(cache, db) {
2373
2535
  function normalizePath(path2) {
2374
2536
  return path2.replace(/\\/g, "/");
2375
2537
  }
2538
+ var LANGUAGE_EXTENSION_FAMILIES = [
2539
+ SOURCE_EXTENSIONS,
2540
+ PYTHON_SOURCE_EXTENSIONS,
2541
+ JVM_SOURCE_EXTENSIONS,
2542
+ RUST_SOURCE_EXTENSIONS,
2543
+ RUBY_SOURCE_EXTENSIONS,
2544
+ C_LIKE_SOURCE_EXTENSIONS,
2545
+ DOTNET_SOURCE_EXTENSIONS,
2546
+ DART_SOURCE_EXTENSIONS,
2547
+ PHP_SOURCE_EXTENSIONS
2548
+ ];
2549
+ function hasExtensionIn(relativePath, extensions) {
2550
+ return extensions.includes(extname3(relativePath).toLowerCase());
2551
+ }
2376
2552
  function isJavaScriptSourcePath(relativePath) {
2377
- return SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
2553
+ return hasExtensionIn(relativePath, SOURCE_EXTENSIONS);
2378
2554
  }
2379
2555
  function isPythonSourcePath(relativePath) {
2380
- return PYTHON_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
2556
+ return hasExtensionIn(relativePath, PYTHON_SOURCE_EXTENSIONS);
2381
2557
  }
2382
2558
  function isJvmSourcePath(relativePath) {
2383
- return JVM_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
2559
+ return hasExtensionIn(relativePath, JVM_SOURCE_EXTENSIONS);
2384
2560
  }
2385
2561
  function isRustSourcePath(relativePath) {
2386
- return RUST_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
2562
+ return hasExtensionIn(relativePath, RUST_SOURCE_EXTENSIONS);
2387
2563
  }
2388
2564
  function isRubySourcePath(relativePath) {
2389
- return RUBY_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
2565
+ return hasExtensionIn(relativePath, RUBY_SOURCE_EXTENSIONS);
2390
2566
  }
2391
2567
  function isCLikeSourcePath(relativePath) {
2392
- return C_LIKE_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
2568
+ return hasExtensionIn(relativePath, C_LIKE_SOURCE_EXTENSIONS);
2393
2569
  }
2394
2570
  function isDotNetSourcePath(relativePath) {
2395
- return DOTNET_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
2571
+ return hasExtensionIn(relativePath, DOTNET_SOURCE_EXTENSIONS);
2396
2572
  }
2397
2573
  function isVisualBasicSourcePath(relativePath) {
2398
- return extname2(relativePath).toLowerCase() === ".vb";
2574
+ return extname3(relativePath).toLowerCase() === ".vb";
2399
2575
  }
2400
2576
  function isDartSourcePath(relativePath) {
2401
- return DART_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
2577
+ return hasExtensionIn(relativePath, DART_SOURCE_EXTENSIONS);
2402
2578
  }
2403
2579
  function isPhpSourcePath(relativePath) {
2404
- return PHP_SOURCE_EXTENSIONS.includes(extname2(relativePath).toLowerCase());
2580
+ return hasExtensionIn(relativePath, PHP_SOURCE_EXTENSIONS);
2405
2581
  }
2406
2582
  function extensionFamilyFor(relativePath) {
2407
- if (isJvmSourcePath(relativePath)) return JVM_SOURCE_EXTENSIONS;
2408
- if (isDotNetSourcePath(relativePath)) return DOTNET_SOURCE_EXTENSIONS;
2409
- if (isPhpSourcePath(relativePath)) return PHP_SOURCE_EXTENSIONS;
2410
- if (isDartSourcePath(relativePath)) return DART_SOURCE_EXTENSIONS;
2411
- if (isCLikeSourcePath(relativePath)) return C_LIKE_SOURCE_EXTENSIONS;
2412
- if (isRustSourcePath(relativePath)) return RUST_SOURCE_EXTENSIONS;
2413
- if (isRubySourcePath(relativePath)) return RUBY_SOURCE_EXTENSIONS;
2583
+ const ext = extname3(relativePath).toLowerCase();
2584
+ for (const family of LANGUAGE_EXTENSION_FAMILIES) {
2585
+ if (family.includes(ext)) return family;
2586
+ }
2414
2587
  return SOURCE_EXTENSIONS;
2415
2588
  }
2416
2589
  function getSourceText(db, relativePath) {
@@ -2425,7 +2598,7 @@ function getSourceText(db, relativePath) {
2425
2598
  cache.set(normalized, "");
2426
2599
  return "";
2427
2600
  }
2428
- const source = readFileSync3(fullPath, "utf-8");
2601
+ const source = readFileSync4(fullPath, "utf-8");
2429
2602
  cache.set(normalized, source);
2430
2603
  return source;
2431
2604
  }
@@ -2612,6 +2785,9 @@ function leafSuffix(raw) {
2612
2785
  const last = sym.descriptors[sym.descriptors.length - 1];
2613
2786
  return last?.suffix ?? null;
2614
2787
  }
2788
+ function isCallableSymbol(raw) {
2789
+ return raw.endsWith("().") || leafSuffix(raw) === "method";
2790
+ }
2615
2791
  function isFunctionLikeSymbol(raw) {
2616
2792
  const suffix = leafSuffix(raw);
2617
2793
  return suffix === "method" || suffix === "term";
@@ -3065,56 +3241,41 @@ function findEnclosingDefinition(definitions, line) {
3065
3241
  }
3066
3242
  return best;
3067
3243
  }
3068
- function getPythonSourceCalleeRows(db, symbol, limit) {
3069
- const match = getFullSymbolMatch(db, symbol);
3070
- if (!match || !isPythonDocument(db, match.relativePath)) {
3071
- return [];
3072
- }
3073
- const definitions = getDefinitionsForFile(db, match.relativePath);
3074
- const current = definitions.find((definition) => definition.symbolId === match.symbolId);
3075
- if (!current) {
3076
- return [];
3077
- }
3078
- const imports2 = getSourceImports(db, match.relativePath);
3079
- const bindings = new Map(
3080
- getSourceConstructorBindings(db, match.relativePath, {
3081
- startLine: match.startLine,
3082
- endLine: match.endLine
3083
- }).map((binding) => [binding.localName, binding.typeName])
3084
- );
3085
- const rows = [];
3086
- const seen = /* @__PURE__ */ new Set();
3087
- for (const call of getSourceCalls(db, match.relativePath, {
3088
- startLine: match.startLine,
3089
- endLine: match.endLine
3090
- })) {
3091
- const resolved = resolvePythonCallTarget(
3092
- db,
3093
- current,
3094
- definitions,
3095
- imports2,
3096
- bindings,
3097
- call.receiverName,
3098
- call.calleeName
3099
- );
3100
- if (!resolved || resolved.symbolId === match.symbolId || db.isIgnored(resolved.relativePath)) continue;
3101
- const chunkId = 1e9 + call.line;
3102
- const key = `${resolved.symbol}|${resolved.relativePath}|${chunkId}`;
3103
- if (seen.has(key)) continue;
3104
- seen.add(key);
3105
- rows.push({
3106
- symbol: resolved.symbol,
3107
- file: resolved.relativePath,
3108
- chunkId
3109
- });
3110
- }
3111
- return applyLimit(rows, limit);
3112
- }
3113
- function getJavaScriptSourceCalleeRows(db, symbol, limit) {
3114
- const match = getFullSymbolMatch(db, symbol);
3115
- if (!match || !isJavaScriptDocument(db, match.relativePath)) {
3116
- return [];
3117
- }
3244
+ var LANGUAGE_CALLEE_CONFIGS = [
3245
+ // Python (index 0) — complex resolver
3246
+ { kind: "complex", languageIndex: 0, resolver: resolvePythonCallTarget },
3247
+ // JavaScript/TypeScript (index 1) — complex resolver
3248
+ { kind: "complex", languageIndex: 1, resolver: resolveJavaScriptCallTarget },
3249
+ // Java (index 2) — simple with field bindings
3250
+ { kind: "simple", languageIndex: 2, parseBindings: (_db, source) => parseJavaFieldBindings(source) },
3251
+ // Kotlin (index 3) — simple with field bindings
3252
+ { kind: "simple", languageIndex: 3, parseBindings: (_db, source) => parseKotlinFieldBindings(source) },
3253
+ // Scala (index 4) — simple, no bindings
3254
+ { kind: "simple", languageIndex: 4, parseBindings: null },
3255
+ // C# (index 5) — simple, no bindings
3256
+ { kind: "simple", languageIndex: 5, parseBindings: null },
3257
+ // Visual Basic (index 6) — simple, no bindings
3258
+ { kind: "simple", languageIndex: 6, parseBindings: null },
3259
+ // C++ (index 7) simple with receiver bindings
3260
+ { kind: "simple", languageIndex: 7, parseBindings: (_db, source) => parseCppReceiverBindings(source) },
3261
+ // Rust (index 8) — simple, no bindings
3262
+ { kind: "simple", languageIndex: 8, parseBindings: null },
3263
+ // Ruby (index 9) simple with dual-attempt logic
3264
+ {
3265
+ kind: "simple",
3266
+ languageIndex: 9,
3267
+ parseBindings: (db, source) => parseRubyReceiverBindings(db, source),
3268
+ dualAttempt: {
3269
+ baseOpts: { allowInstanceVariables: true },
3270
+ extendedOpts: { allowInstanceVariables: true, allowBareMemberCalls: true }
3271
+ }
3272
+ },
3273
+ // Dart (index 10) — simple, no bindings
3274
+ { kind: "simple", languageIndex: 10, parseBindings: null },
3275
+ // PHP (index 11) — simple, no bindings
3276
+ { kind: "simple", languageIndex: 11, parseBindings: null }
3277
+ ];
3278
+ function getComplexSourceCalleeRows(db, match, config, limit) {
3118
3279
  const definitions = getDefinitionsForFile(db, match.relativePath);
3119
3280
  const current = definitions.find((definition) => definition.symbolId === match.symbolId);
3120
3281
  if (!current) {
@@ -3133,7 +3294,7 @@ function getJavaScriptSourceCalleeRows(db, symbol, limit) {
3133
3294
  startLine: match.startLine,
3134
3295
  endLine: match.endLine
3135
3296
  })) {
3136
- const resolved = resolveJavaScriptCallTarget(
3297
+ const resolved = config.resolver(
3137
3298
  db,
3138
3299
  current,
3139
3300
  definitions,
@@ -3155,134 +3316,31 @@ function getJavaScriptSourceCalleeRows(db, symbol, limit) {
3155
3316
  }
3156
3317
  return applyLimit(rows, limit);
3157
3318
  }
3158
- function getJavaSourceCalleeRows(db, symbol, limit) {
3159
- const match = getFullSymbolMatch(db, symbol);
3160
- if (!match || !isJavaDocument(db, match.relativePath)) {
3161
- return [];
3162
- }
3163
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3164
- const bindings = parseJavaFieldBindings(getSourceText(db, match.relativePath));
3165
- return resolveSimpleSourceCallees(db, match, calls, bindings, limit);
3166
- }
3167
- function getKotlinSourceCalleeRows(db, symbol, limit) {
3168
- const match = getFullSymbolMatch(db, symbol);
3169
- if (!match || !isKotlinDocument(db, match.relativePath)) {
3170
- return [];
3171
- }
3172
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3173
- const bindings = parseKotlinFieldBindings(getSourceText(db, match.relativePath));
3174
- return resolveSimpleSourceCallees(db, match, calls, bindings, limit);
3175
- }
3176
- function getScalaSourceCalleeRows(db, symbol, limit) {
3177
- const match = getFullSymbolMatch(db, symbol);
3178
- if (!match || !isScalaDocument(db, match.relativePath)) {
3179
- return [];
3180
- }
3181
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3182
- return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
3183
- }
3184
- function getCSharpSourceCalleeRows(db, symbol, limit) {
3185
- const match = getFullSymbolMatch(db, symbol);
3186
- if (!match || !isCSharpDocument(db, match.relativePath)) {
3187
- return [];
3188
- }
3189
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3190
- return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
3191
- }
3192
- function getVisualBasicSourceCalleeRows(db, symbol, limit) {
3193
- const match = getFullSymbolMatch(db, symbol);
3194
- if (!match || !isVisualBasicDocument(db, match.relativePath)) {
3195
- return [];
3196
- }
3197
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3198
- return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
3199
- }
3200
- function getCppSourceCalleeRows(db, symbol, limit) {
3201
- const match = getFullSymbolMatch(db, symbol);
3202
- if (!match || !isCppDocument(db, match.relativePath)) {
3203
- return [];
3319
+ function getSimpleLanguageCalleeRows(db, match, config, limit) {
3320
+ let calls;
3321
+ if (config.dualAttempt) {
3322
+ const baseCalls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine, config.dualAttempt.baseOpts);
3323
+ const extendedCalls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine, config.dualAttempt.extendedOpts);
3324
+ calls = extendedCalls.length > 0 ? extendedCalls : baseCalls;
3325
+ } else {
3326
+ calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine, config.sourceCallOpts);
3204
3327
  }
3205
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3206
- const bindings = parseCppReceiverBindings(getSourceText(db, match.relativePath));
3328
+ const bindings = config.parseBindings ? config.parseBindings(db, getSourceText(db, match.relativePath)) : /* @__PURE__ */ new Map();
3207
3329
  return resolveSimpleSourceCallees(db, match, calls, bindings, limit);
3208
3330
  }
3209
- function getRustSourceCalleeRows(db, symbol, limit) {
3210
- const match = getFullSymbolMatch(db, symbol);
3211
- if (!match || !isRustDocument(db, match.relativePath)) {
3212
- return [];
3213
- }
3214
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3215
- return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
3216
- }
3217
- function getRubySourceCalleeRows(db, symbol, limit) {
3218
- const match = getFullSymbolMatch(db, symbol);
3219
- if (!match || !isRubyDocument(db, match.relativePath)) {
3220
- return [];
3221
- }
3222
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine, { allowInstanceVariables: true });
3223
- const rubyCalls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine, {
3224
- allowInstanceVariables: true,
3225
- allowBareMemberCalls: true
3226
- });
3227
- const bindings = parseRubyReceiverBindings(db, getSourceText(db, match.relativePath));
3228
- return resolveSimpleSourceCallees(db, match, rubyCalls.length > 0 ? rubyCalls : calls, bindings, limit);
3229
- }
3230
- function getDartSourceCalleeRows(db, symbol, limit) {
3231
- const match = getFullSymbolMatch(db, symbol);
3232
- if (!match || !isDartDocument(db, match.relativePath)) {
3233
- return [];
3234
- }
3235
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3236
- return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
3237
- }
3238
- function getPhpSourceCalleeRows(db, symbol, limit) {
3239
- const match = getFullSymbolMatch(db, symbol);
3240
- if (!match || !isPhpDocument(db, match.relativePath)) {
3241
- return [];
3242
- }
3243
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3244
- return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
3245
- }
3246
3331
  function getSourceBackedCalleeRows(db, symbol, limit) {
3247
3332
  const match = getFullSymbolMatch(db, symbol);
3248
3333
  if (!match) {
3249
3334
  return [];
3250
3335
  }
3251
- if (isPythonDocument(db, match.relativePath)) {
3252
- return getPythonSourceCalleeRows(db, match, limit);
3253
- }
3254
- if (isJavaScriptDocument(db, match.relativePath)) {
3255
- return getJavaScriptSourceCalleeRows(db, match, limit);
3256
- }
3257
- if (isJavaDocument(db, match.relativePath)) {
3258
- return getJavaSourceCalleeRows(db, match, limit);
3259
- }
3260
- if (isScalaDocument(db, match.relativePath)) {
3261
- return getScalaSourceCalleeRows(db, match, limit);
3262
- }
3263
- if (isKotlinDocument(db, match.relativePath)) {
3264
- return getKotlinSourceCalleeRows(db, match, limit);
3265
- }
3266
- if (isCSharpDocument(db, match.relativePath)) {
3267
- return getCSharpSourceCalleeRows(db, match, limit);
3268
- }
3269
- if (isVisualBasicDocument(db, match.relativePath)) {
3270
- return getVisualBasicSourceCalleeRows(db, match, limit);
3271
- }
3272
- if (isCppDocument(db, match.relativePath)) {
3273
- return getCppSourceCalleeRows(db, match, limit);
3274
- }
3275
- if (isRustDocument(db, match.relativePath)) {
3276
- return getRustSourceCalleeRows(db, match, limit);
3277
- }
3278
- if (isRubyDocument(db, match.relativePath)) {
3279
- return getRubySourceCalleeRows(db, match, limit);
3280
- }
3281
- if (isDartDocument(db, match.relativePath)) {
3282
- return getDartSourceCalleeRows(db, match, limit);
3283
- }
3284
- if (isPhpDocument(db, match.relativePath)) {
3285
- return getPhpSourceCalleeRows(db, match, limit);
3336
+ for (const config of LANGUAGE_CALLEE_CONFIGS) {
3337
+ if (!isDocumentLanguage(db, match.relativePath, DOCUMENT_LANGUAGE_TABLE[config.languageIndex])) {
3338
+ continue;
3339
+ }
3340
+ if (config.kind === "complex") {
3341
+ return getComplexSourceCalleeRows(db, match, config, limit);
3342
+ }
3343
+ return getSimpleLanguageCalleeRows(db, match, config, limit);
3286
3344
  }
3287
3345
  return [];
3288
3346
  }
@@ -3292,9 +3350,11 @@ function getPythonSourceCallerRows(db, target, limit) {
3292
3350
  }
3293
3351
  const rows = [];
3294
3352
  const seen = /* @__PURE__ */ new Set();
3353
+ const pythonConfig = LANGUAGE_CALLEE_CONFIGS[0];
3295
3354
  for (const candidate of getAllFunctionLikeDefinitions(db)) {
3296
3355
  if (candidate.symbolId === target.symbolId) continue;
3297
- const callees = getPythonSourceCalleeRows(db, candidate);
3356
+ if (!isDocumentLanguage(db, candidate.relativePath, DOCUMENT_LANGUAGE_TABLE[pythonConfig.languageIndex])) continue;
3357
+ const callees = getComplexSourceCalleeRows(db, candidate, pythonConfig);
3298
3358
  if (!callees.some((callee) => callee.symbol === target.symbol)) continue;
3299
3359
  const key = `${candidate.symbol}|${candidate.relativePath}`;
3300
3360
  if (seen.has(key) || db.isIgnored(candidate.relativePath)) continue;
@@ -3636,6 +3696,17 @@ function getAllDefinitions(db, opts = {}) {
3636
3696
  );
3637
3697
  return rows.filter((row) => !db.isIgnored(row.relative_path)).flatMap((row) => getDefinitionsForFile(db, row.relative_path));
3638
3698
  }
3699
+ function getScopedDefinitions(db, scope) {
3700
+ const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
3701
+ return db.all(
3702
+ `SELECT relative_path
3703
+ FROM documents
3704
+ WHERE 1 = 1
3705
+ ${db.pathExclusionsFor("documents")}
3706
+ ${scopeFilter}
3707
+ ORDER BY relative_path`
3708
+ ).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
3709
+ }
3639
3710
  function getAllFunctionLikeDefinitions(db) {
3640
3711
  return getAllDefinitions(db).filter((definition) => definition.isFunctionLike);
3641
3712
  }
@@ -4053,89 +4124,29 @@ function parentTypeName(rawSymbol) {
4053
4124
  }
4054
4125
  return null;
4055
4126
  }
4056
- function isPythonDocument(db, relativePath) {
4057
- const row = db.get(
4058
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4059
- relativePath
4060
- );
4061
- return row?.language === "python" || relativePath.endsWith(".py") || relativePath.endsWith(".pyi");
4062
- }
4063
- function isJavaScriptDocument(db, relativePath) {
4064
- const row = db.get(
4065
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4066
- relativePath
4067
- );
4068
- return row?.language === "typescript" || row?.language === "javascript" || /\.(?:[cm]?[jt]sx?)$/.test(relativePath);
4069
- }
4070
- function isJavaDocument(db, relativePath) {
4071
- const row = db.get(
4072
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4073
- relativePath
4074
- );
4075
- return row?.language === "java" || relativePath.endsWith(".java");
4076
- }
4077
- function isKotlinDocument(db, relativePath) {
4078
- const row = db.get(
4079
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4080
- relativePath
4081
- );
4082
- return row?.language === "kotlin" || relativePath.endsWith(".kt") || relativePath.endsWith(".kts");
4083
- }
4084
- function isScalaDocument(db, relativePath) {
4085
- const row = db.get(
4086
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4087
- relativePath
4088
- );
4089
- return row?.language === "scala" || relativePath.endsWith(".scala");
4090
- }
4091
- function isCSharpDocument(db, relativePath) {
4092
- const row = db.get(
4093
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4094
- relativePath
4095
- );
4096
- return row?.language === "C#" || relativePath.endsWith(".cs");
4097
- }
4098
- function isVisualBasicDocument(db, relativePath) {
4099
- const row = db.get(
4100
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4101
- relativePath
4102
- );
4103
- return row?.language === "Visual Basic" || relativePath.endsWith(".vb");
4104
- }
4105
- function isCppDocument(db, relativePath) {
4106
- const row = db.get(
4107
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4108
- relativePath
4109
- );
4110
- return row?.language === "CPP" || /\.(?:cc|cpp|cxx|hpp|hh|hxx)$/.test(relativePath);
4111
- }
4112
- function isRustDocument(db, relativePath) {
4113
- const row = db.get(
4114
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4115
- relativePath
4116
- );
4117
- return row?.language === "Rust" || relativePath.endsWith(".rs");
4118
- }
4119
- function isRubyDocument(db, relativePath) {
4120
- const row = db.get(
4121
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4122
- relativePath
4123
- );
4124
- return row?.language === "ruby" || relativePath.endsWith(".rb");
4125
- }
4126
- function isDartDocument(db, relativePath) {
4127
+ var DOCUMENT_LANGUAGE_TABLE = [
4128
+ { languages: ["python"], extensionPattern: /\.(?:py|pyi)$/ },
4129
+ { languages: ["typescript", "javascript"], extensionPattern: /\.(?:[cm]?[jt]sx?)$/ },
4130
+ { languages: ["java"], extensionPattern: /\.java$/ },
4131
+ { languages: ["kotlin"], extensionPattern: /\.(?:kt|kts)$/ },
4132
+ { languages: ["scala"], extensionPattern: /\.scala$/ },
4133
+ { languages: ["C#"], extensionPattern: /\.cs$/ },
4134
+ { languages: ["Visual Basic"], extensionPattern: /\.vb$/ },
4135
+ { languages: ["CPP"], extensionPattern: /\.(?:cc|cpp|cxx|hpp|hh|hxx)$/ },
4136
+ { languages: ["Rust"], extensionPattern: /\.rs$/ },
4137
+ { languages: ["ruby"], extensionPattern: /\.rb$/ },
4138
+ { languages: ["Dart"], extensionPattern: /\.dart$/ },
4139
+ { languages: ["PHP"], extensionPattern: /\.php$/ }
4140
+ ];
4141
+ function isDocumentLanguage(db, relativePath, entry) {
4127
4142
  const row = db.get(
4128
4143
  `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4129
4144
  relativePath
4130
4145
  );
4131
- return row?.language === "Dart" || relativePath.endsWith(".dart");
4146
+ return entry.languages.includes(row?.language ?? "") || entry.extensionPattern.test(relativePath);
4132
4147
  }
4133
- function isPhpDocument(db, relativePath) {
4134
- const row = db.get(
4135
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4136
- relativePath
4137
- );
4138
- return row?.language === "PHP" || relativePath.endsWith(".php");
4148
+ function isPythonDocument(db, relativePath) {
4149
+ return isDocumentLanguage(db, relativePath, DOCUMENT_LANGUAGE_TABLE[0]);
4139
4150
  }
4140
4151
  function applyLimit(values, limit) {
4141
4152
  return typeof limit === "number" ? values.slice(0, limit) : values;
@@ -4174,8 +4185,8 @@ function resolveDocumentCandidates(db, filePattern, opts) {
4174
4185
  }
4175
4186
  function scoreDocumentPath(relativePath, rawPattern) {
4176
4187
  const normalizedPath = normalizeLookupPath(relativePath);
4177
- const pathBase = basename2(normalizedPath);
4178
- const patternBase = basename2(rawPattern);
4188
+ const pathBase = basename3(normalizedPath);
4189
+ const patternBase = basename3(rawPattern);
4179
4190
  let score = 0;
4180
4191
  if (normalizedPath === rawPattern) score += 1200;
4181
4192
  if (normalizedPath.endsWith(`/${rawPattern}`)) score += 1100;
@@ -4221,7 +4232,7 @@ function symbols(db, filePattern) {
4221
4232
  }
4222
4233
 
4223
4234
  // src/queries/methods.ts
4224
- import { basename as basename3 } from "path";
4235
+ import { basename as basename4 } from "path";
4225
4236
  function methods(db, className) {
4226
4237
  const classMatch = findFirstSymbolMatch(db, className);
4227
4238
  if (!classMatch) {
@@ -4230,16 +4241,13 @@ function methods(db, className) {
4230
4241
  const ownerName = leafName(classMatch.symbol);
4231
4242
  const definitions = getDefinitionsForFile(db, classMatch.relativePath).filter((definition) => isCallableSymbol(definition.symbol));
4232
4243
  const directMethods = definitions.filter((definition) => definition.parentTypeName === ownerName || definition.symbol.includes(ownerName));
4233
- const fileScopedMethods = directMethods.length > 0 ? directMethods : stripExtension(basename3(classMatch.relativePath)) === ownerName ? definitions.filter((definition) => definition.symbol.includes("<invalid-global-code>")) : [];
4244
+ const fileScopedMethods = directMethods.length > 0 ? directMethods : stripExtension(basename4(classMatch.relativePath)) === ownerName ? definitions.filter((definition) => definition.symbol.includes("<invalid-global-code>")) : [];
4234
4245
  return fileScopedMethods.map((definition) => ({
4235
4246
  startLine: definition.startLine,
4236
4247
  endLine: definition.endLine,
4237
4248
  name: leafName(definition.symbol)
4238
4249
  }));
4239
4250
  }
4240
- function isCallableSymbol(rawSymbol) {
4241
- return rawSymbol.endsWith("().") || leafSuffix(rawSymbol) === "method";
4242
- }
4243
4251
  function stripExtension(relativePath) {
4244
4252
  return relativePath.replace(/\.[^.]+$/, "");
4245
4253
  }
@@ -4577,7 +4585,7 @@ function surface(db, modulePattern) {
4577
4585
  ...matchedPaths
4578
4586
  );
4579
4587
  const exposedDefinitions = matchedPaths.flatMap(
4580
- (relativePath) => getDefinitionsForFile(db, relativePath).filter((definition) => isCallableSymbol2(definition.symbol)).map((definition) => ({
4588
+ (relativePath) => getDefinitionsForFile(db, relativePath).filter((definition) => isCallableSymbol(definition.symbol)).map((definition) => ({
4581
4589
  relative_path: relativePath,
4582
4590
  symbol: definition.symbol
4583
4591
  }))
@@ -4594,9 +4602,6 @@ function surface(db, modulePattern) {
4594
4602
  shortName: shortenSymbol(r.symbol)
4595
4603
  }));
4596
4604
  }
4597
- function isCallableSymbol2(rawSymbol) {
4598
- return rawSymbol.endsWith("().") || leafSuffix(rawSymbol) === "method";
4599
- }
4600
4605
 
4601
4606
  // src/entry-surfaces.ts
4602
4607
  var liveBarrelCache = /* @__PURE__ */ new WeakMap();
@@ -4614,11 +4619,11 @@ function isWorkerEntrySurface(path2) {
4614
4619
  function isStructuralEntrySurface(path2) {
4615
4620
  const normalized = normalizePath2(path2);
4616
4621
  const segments = normalized.split("/");
4617
- const basename5 = segments[segments.length - 1] ?? normalized;
4618
- if (basename5 === "cli.ts" || basename5 === "cli.js" || basename5 === "postinstall.ts" || basename5 === "postinstall.js" || basename5 === "main.ts" || basename5 === "main.js" || basename5 === "main.rs" || basename5 === "main.go" || basename5 === "main.py") {
4622
+ const basename6 = segments[segments.length - 1] ?? normalized;
4623
+ if (basename6 === "cli.ts" || basename6 === "cli.js" || basename6 === "postinstall.ts" || basename6 === "postinstall.js" || basename6 === "main.ts" || basename6 === "main.js" || basename6 === "main.rs" || basename6 === "main.go" || basename6 === "main.py") {
4619
4624
  return true;
4620
4625
  }
4621
- if (basename5 === "index.ts" || basename5 === "index.js") {
4626
+ if (basename6 === "index.ts" || basename6 === "index.js") {
4622
4627
  return segments.length <= 2;
4623
4628
  }
4624
4629
  return normalized.endsWith("/mod.rs") || normalized.endsWith("/__init__.py");
@@ -4982,22 +4987,37 @@ function outline(db, filePattern) {
4982
4987
  ORDER BY d.relative_path, der.start_line`,
4983
4988
  ...resolvedPaths
4984
4989
  );
4990
+ const nodes = rows.map((r) => ({
4991
+ symbol: r.symbol,
4992
+ shortName: shortenSymbol(r.symbol),
4993
+ startLine: r.start_line,
4994
+ endLine: r.end_line,
4995
+ children: []
4996
+ }));
4985
4997
  const nodeMap = /* @__PURE__ */ new Map();
4998
+ for (const n of nodes) nodeMap.set(n.symbol, n);
4986
4999
  const roots = [];
4987
- for (const r of rows) {
4988
- const node = {
4989
- symbol: r.symbol,
4990
- shortName: shortenSymbol(r.symbol),
4991
- startLine: r.start_line,
4992
- endLine: r.end_line,
4993
- children: []
4994
- };
4995
- nodeMap.set(r.symbol, node);
4996
- }
4997
- for (const r of rows) {
4998
- const node = nodeMap.get(r.symbol);
5000
+ for (let i = 0; i < rows.length; i++) {
5001
+ const r = rows[i];
5002
+ const node = nodes[i];
4999
5003
  if (r.enclosing_symbol && nodeMap.has(r.enclosing_symbol)) {
5000
5004
  nodeMap.get(r.enclosing_symbol).children.push(node);
5005
+ continue;
5006
+ }
5007
+ let bestParent = null;
5008
+ let bestSize = Infinity;
5009
+ for (const candidate of nodes) {
5010
+ if (candidate === node) continue;
5011
+ if (candidate.startLine <= node.startLine && candidate.endLine >= node.endLine) {
5012
+ const size = candidate.endLine - candidate.startLine;
5013
+ if (size < bestSize) {
5014
+ bestSize = size;
5015
+ bestParent = candidate;
5016
+ }
5017
+ }
5018
+ }
5019
+ if (bestParent) {
5020
+ bestParent.children.push(node);
5001
5021
  } else {
5002
5022
  roots.push(node);
5003
5023
  }
@@ -5719,7 +5739,7 @@ function similar(db, symbolPattern, opts = {}) {
5719
5739
  return similarBySourceShape(db, symbolPattern, { minSimilarity, limit });
5720
5740
  }
5721
5741
  function similarAll(db, opts = {}) {
5722
- const { minSimilarity = 0.5, limit = 20, scope, minCallees = 4 } = opts;
5742
+ const { minSimilarity = 0.5, limit = 20, scope, minCallees = 4, crossFileOnly = false } = opts;
5723
5743
  const all = getAllCalleeFingerprints(db, { minCallees, scope });
5724
5744
  const idfWeights = computeIdf(all);
5725
5745
  const results = [];
@@ -5727,14 +5747,16 @@ function similarAll(db, opts = {}) {
5727
5747
  for (let j = i + 1; j < all.length; j++) {
5728
5748
  const a = all[i];
5729
5749
  const b = all[j];
5730
- if (a.file === b.file) continue;
5750
+ if (crossFileOnly && a.file === b.file) continue;
5731
5751
  const { similarity, significantShared } = weightedSimilarity(
5732
5752
  a.callees,
5733
5753
  b.callees,
5734
5754
  idfWeights
5735
5755
  );
5736
5756
  if (similarity < minSimilarity) continue;
5737
- if (significantShared.length < 2) continue;
5757
+ const sharedCount = intersection(a.callees, b.callees).size;
5758
+ if (significantShared.length < 2 && sharedCount < 4) continue;
5759
+ const displayShared = significantShared.length > 0 ? significantShared : [...intersection(a.callees, b.callees)];
5738
5760
  results.push({
5739
5761
  symbolA: a.symbol,
5740
5762
  shortNameA: shortenSymbol(a.symbol),
@@ -5743,7 +5765,7 @@ function similarAll(db, opts = {}) {
5743
5765
  shortNameB: shortenSymbol(b.symbol),
5744
5766
  fileB: b.file,
5745
5767
  similarity,
5746
- sharedCallees: significantShared.map(shortenSymbol),
5768
+ sharedCallees: displayShared.map(shortenSymbol),
5747
5769
  uniqueToA: [...difference(a.callees, b.callees)].map(shortenSymbol),
5748
5770
  uniqueToB: [...difference(b.callees, a.callees)].map(shortenSymbol)
5749
5771
  });
@@ -6121,8 +6143,8 @@ function similarChains(db, opts = {}) {
6121
6143
  }
6122
6144
  const structuralNames = ["index.ts", "index.js", "cli.ts", "main.ts", "health.ts", "health.js"];
6123
6145
  for (const node of nodeFreq.keys()) {
6124
- const basename5 = node.split("/").pop() ?? "";
6125
- if (structuralNames.includes(basename5)) infraNodes.add(node);
6146
+ const basename6 = node.split("/").pop() ?? "";
6147
+ if (structuralNames.includes(basename6)) infraNodes.add(node);
6126
6148
  }
6127
6149
  const filteredChains = [];
6128
6150
  for (const chain of rawChains) {
@@ -6296,8 +6318,8 @@ function extractCandidates(db, opts = {}) {
6296
6318
  const results = [];
6297
6319
  for (const sym of symbols2) {
6298
6320
  if (db.isIgnored(sym.relativePath)) continue;
6299
- const basename5 = sym.relativePath.split("/").pop() ?? "";
6300
- if (basename5.includes("types")) continue;
6321
+ const basename6 = sym.relativePath.split("/").pop() ?? "";
6322
+ if (basename6.includes("types")) continue;
6301
6323
  const calleeChunks = getCalleeRowsForSymbol(db, sym);
6302
6324
  const calleeSet = new Set(calleeChunks.map((c) => c.symbol));
6303
6325
  if (calleeSet.size < minCallees) continue;
@@ -6373,17 +6395,6 @@ function extractCandidates(db, opts = {}) {
6373
6395
  results.sort((a, b) => b.clusters.length - a.clusters.length || b.loc - a.loc);
6374
6396
  return results.slice(0, limit);
6375
6397
  }
6376
- function getScopedDefinitions(db, scope) {
6377
- const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
6378
- return db.all(
6379
- `SELECT relative_path
6380
- FROM documents
6381
- WHERE 1 = 1
6382
- ${db.pathExclusionsFor("documents")}
6383
- ${scopeFilter}
6384
- ORDER BY relative_path`
6385
- ).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
6386
- }
6387
6398
  function definitionLoc(definition) {
6388
6399
  return definition.endLine - definition.startLine + 1;
6389
6400
  }
@@ -6809,32 +6820,32 @@ function isLikelyTypeOnlyDep(dep) {
6809
6820
  function shouldSkipDriftFile(filePath) {
6810
6821
  return isStructuralRole(path.basename(filePath)) || isTestLikePath(filePath);
6811
6822
  }
6812
- function isStructuralRole(basename5) {
6813
- if (basename5 === "index.ts" || basename5 === "index.js") return true;
6814
- if (basename5 === "cli.ts" || basename5 === "main.ts" || basename5 === "main.rs") return true;
6815
- if (basename5.includes("worker.") || basename5.includes("postinstall.")) return true;
6816
- if (basename5 === "health.ts" || basename5 === "health.js") return true;
6823
+ function isStructuralRole(basename6) {
6824
+ if (basename6 === "index.ts" || basename6 === "index.js") return true;
6825
+ if (basename6 === "cli.ts" || basename6 === "main.ts" || basename6 === "main.rs") return true;
6826
+ if (basename6.includes("worker.") || basename6.includes("postinstall.")) return true;
6827
+ if (basename6 === "health.ts" || basename6 === "health.js") return true;
6817
6828
  return false;
6818
6829
  }
6819
6830
  function isTestLikePath(filePath) {
6820
6831
  const normalized = filePath.replace(/\\/g, "/");
6821
- const basename5 = path.basename(normalized);
6822
- return normalized.includes("/__tests__/") || normalized.includes("/tests/") || normalized.includes("/test/") || /\.(test|spec)\.[A-Za-z0-9]+$/.test(basename5) || /_(test|spec)\.[A-Za-z0-9]+$/.test(basename5) || /^test[_-]/.test(basename5) || /^test\./.test(basename5);
6832
+ const basename6 = path.basename(normalized);
6833
+ return normalized.includes("/__tests__/") || normalized.includes("/tests/") || normalized.includes("/test/") || /\.(test|spec)\.[A-Za-z0-9]+$/.test(basename6) || /_(test|spec)\.[A-Za-z0-9]+$/.test(basename6) || /^test[_-]/.test(basename6) || /^test\./.test(basename6);
6823
6834
  }
6824
6835
 
6825
6836
  // src/queries/wrapper-candidates.ts
6826
- import { basename as basename4, extname as extname3 } from "path";
6837
+ import { basename as basename5, extname as extname4 } from "path";
6827
6838
  function wrapperCandidates(db, opts) {
6828
6839
  const { scope, maxLoc = 15, limit = 30 } = opts ?? {};
6829
6840
  const reverseFanIn = buildReverseFileFanIn(buildFileDepGraph(db, scope));
6830
- const symbols2 = getScopedDefinitions2(db, scope).filter((definition) => definitionLoc2(definition) <= maxLoc && definitionLoc2(definition) >= 2);
6841
+ const symbols2 = getScopedDefinitions(db, scope).filter((definition) => definitionLoc2(definition) <= maxLoc && definitionLoc2(definition) >= 2);
6831
6842
  const results = [];
6832
6843
  for (const symbol of symbols2) {
6833
6844
  if (db.isIgnored(symbol.relativePath) || !isFunctionLikeSymbol(symbol.symbol)) continue;
6834
- const symbolStem = basename4(symbol.relativePath, extname3(symbol.relativePath));
6845
+ const symbolStem = basename5(symbol.relativePath, extname4(symbol.relativePath));
6835
6846
  const callerRows = dedupeRows(
6836
6847
  getCallerRowsForSymbol(db, symbol, { limit: 200 }).filter((row) => row.file !== symbol.relativePath)
6837
- ).filter((row) => basename4(row.file, extname3(row.file)) !== symbolStem);
6848
+ ).filter((row) => basename5(row.file, extname4(row.file)) !== symbolStem);
6838
6849
  if (callerRows.length !== 1) continue;
6839
6850
  const caller = callerRows[0];
6840
6851
  const callerDefinition = getDefinitionsForFile(db, caller.file).find((definition) => definition.symbol === caller.symbol);
@@ -6856,7 +6867,7 @@ function wrapperCandidates(db, opts) {
6856
6867
  endLine: symbol.endLine,
6857
6868
  loc: definitionLoc2(symbol),
6858
6869
  singleCaller: caller.symbol,
6859
- singleCallerShort: useDefinitionFanIn ? shortenSymbol(caller.symbol) : basename4(caller.file),
6870
+ singleCallerShort: useDefinitionFanIn ? shortenSymbol(caller.symbol) : basename5(caller.file),
6860
6871
  callerFanIn
6861
6872
  });
6862
6873
  }
@@ -6866,17 +6877,6 @@ function wrapperCandidates(db, opts) {
6866
6877
  function definitionLoc2(definition) {
6867
6878
  return definition.endLine - definition.startLine + 1;
6868
6879
  }
6869
- function getScopedDefinitions2(db, scope) {
6870
- const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
6871
- return db.all(
6872
- `SELECT relative_path
6873
- FROM documents
6874
- WHERE 1 = 1
6875
- ${db.pathExclusionsFor("documents")}
6876
- ${scopeFilter}
6877
- ORDER BY relative_path`
6878
- ).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
6879
- }
6880
6880
  function dedupeRows(rows) {
6881
6881
  const seen = /* @__PURE__ */ new Set();
6882
6882
  const unique = [];
@@ -6911,11 +6911,11 @@ function fallbackCallerFanIn(db, reverseFanIn, callerFile) {
6911
6911
  if (direct > 0) {
6912
6912
  return direct;
6913
6913
  }
6914
- const stem = basename4(callerFile, extname3(callerFile));
6914
+ const stem = basename5(callerFile, extname4(callerFile));
6915
6915
  let best = 0;
6916
6916
  for (const [file, fanIn2] of reverseFanIn) {
6917
6917
  if (file === callerFile) continue;
6918
- if (basename4(file, extname3(file)) !== stem) continue;
6918
+ if (basename5(file, extname4(file)) !== stem) continue;
6919
6919
  if (fanIn2 > best) {
6920
6920
  best = fanIn2;
6921
6921
  }
@@ -6926,7 +6926,7 @@ function fallbackCallerFanIn(db, reverseFanIn, callerFile) {
6926
6926
  // src/queries/passthrough-candidates.ts
6927
6927
  function passthroughCandidates(db, opts) {
6928
6928
  const { scope, maxLoc = 15, limit = 30 } = opts ?? {};
6929
- const symbols2 = getScopedDefinitions3(db, scope).filter((definition) => definitionLoc3(definition) >= 3 && definitionLoc3(definition) <= maxLoc);
6929
+ const symbols2 = getScopedDefinitions(db, scope).filter((definition) => definitionLoc3(definition) >= 3 && definitionLoc3(definition) <= maxLoc);
6930
6930
  const results = [];
6931
6931
  for (const sym of symbols2) {
6932
6932
  if (db.isIgnored(sym.relativePath) || !isFunctionLikeSymbol(sym.symbol)) continue;
@@ -6955,17 +6955,6 @@ function passthroughCandidates(db, opts) {
6955
6955
  results.sort((a, b) => a.loc - b.loc || a.file.localeCompare(b.file));
6956
6956
  return results.slice(0, limit);
6957
6957
  }
6958
- function getScopedDefinitions3(db, scope) {
6959
- const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
6960
- return db.all(
6961
- `SELECT relative_path
6962
- FROM documents
6963
- WHERE 1 = 1
6964
- ${db.pathExclusionsFor("documents")}
6965
- ${scopeFilter}
6966
- ORDER BY relative_path`
6967
- ).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
6968
- }
6969
6958
  function definitionLoc3(definition) {
6970
6959
  return definition.endLine - definition.startLine + 1;
6971
6960
  }
@@ -6973,7 +6962,7 @@ function definitionLoc3(definition) {
6973
6962
  // src/queries/stale-abstractions.ts
6974
6963
  function staleAbstractions(db, opts) {
6975
6964
  const { scope, minLoc = 3, limit = 30 } = opts ?? {};
6976
- const rows = getScopedDefinitions4(db, scope).filter((definition) => definition.isTypeLike && definitionLoc4(definition) >= minLoc).map((definition) => ({
6965
+ const rows = getScopedDefinitions(db, scope).filter((definition) => definition.isTypeLike && definitionLoc4(definition) >= minLoc).map((definition) => ({
6977
6966
  symbol: definition.symbol,
6978
6967
  file: definition.relativePath,
6979
6968
  start_line: definition.startLine,
@@ -6993,11 +6982,11 @@ function staleAbstractions(db, opts) {
6993
6982
  })).slice(0, limit);
6994
6983
  }
6995
6984
  function getFilesWithFunctions(db, scope) {
6996
- return new Set(getScopedDefinitions4(db, scope).filter((definition) => definition.isFunctionLike).map((definition) => definition.relativePath));
6985
+ return new Set(getScopedDefinitions(db, scope).filter((definition) => definition.isFunctionLike).map((definition) => definition.relativePath));
6997
6986
  }
6998
6987
  function isTrueStaleAbstraction(row, filesWithFunctions) {
6999
- const basename5 = row.file.split("/").pop() ?? "";
7000
- const isTypeFile = basename5.includes("types") || row.file.includes("/types/");
6988
+ const basename6 = row.file.split("/").pop() ?? "";
6989
+ const isTypeFile = basename6.includes("types") || row.file.includes("/types/");
7001
6990
  if (isTypeFile && row.consumers > 0) {
7002
6991
  return false;
7003
6992
  }
@@ -7006,17 +6995,6 @@ function isTrueStaleAbstraction(row, filesWithFunctions) {
7006
6995
  }
7007
6996
  return true;
7008
6997
  }
7009
- function getScopedDefinitions4(db, scope) {
7010
- const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
7011
- return db.all(
7012
- `SELECT relative_path
7013
- FROM documents
7014
- WHERE 1 = 1
7015
- ${db.pathExclusionsFor("documents")}
7016
- ${scopeFilter}
7017
- ORDER BY relative_path`
7018
- ).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
7019
- }
7020
6998
  function countCrossFileConsumers(db, definition) {
7021
6999
  const callers = db.all(
7022
7000
  `SELECT DISTINCT d.relative_path
@@ -7395,7 +7373,7 @@ function convergence(db, symbolPatternA, symbolPatternB) {
7395
7373
  }
7396
7374
 
7397
7375
  // src/queries/code.ts
7398
- import { readFileSync as readFileSync4 } from "fs";
7376
+ import { readFileSync as readFileSync5 } from "fs";
7399
7377
  import { join as join9 } from "path";
7400
7378
  function code(db, symbolPattern, opts = {}) {
7401
7379
  const { context = 0 } = opts;
@@ -7412,7 +7390,7 @@ function code(db, symbolPattern, opts = {}) {
7412
7390
  const filePath = join9(db.config.projectRoot, match.relativePath);
7413
7391
  let fileContent;
7414
7392
  try {
7415
- fileContent = readFileSync4(filePath, "utf-8");
7393
+ fileContent = readFileSync5(filePath, "utf-8");
7416
7394
  } catch {
7417
7395
  return null;
7418
7396
  }
@@ -7424,9 +7402,11 @@ function code(db, symbolPattern, opts = {}) {
7424
7402
  symbol: match.symbol,
7425
7403
  shortName: shortenSymbol(match.symbol),
7426
7404
  relativePath: match.relativePath,
7427
- startLine: startLine + 1,
7428
- // 1-indexed for display
7429
- endLine: endLine + 1,
7405
+ // 0-indexed, like every other query result. The CLI's displayLine()
7406
+ // converts once at render time. Returning 1-indexed here caused a
7407
+ // double-conversion in the CLI and printed labels off by +1.
7408
+ startLine,
7409
+ endLine,
7430
7410
  language: doc?.language ?? null,
7431
7411
  source
7432
7412
  };
@@ -7442,7 +7422,7 @@ function readFileRange(db, filePath, startLine, endLine, context) {
7442
7422
  const fullPath = join9(db.config.projectRoot, doc.relative_path);
7443
7423
  let fileContent;
7444
7424
  try {
7445
- fileContent = readFileSync4(fullPath, "utf-8");
7425
+ fileContent = readFileSync5(fullPath, "utf-8");
7446
7426
  } catch {
7447
7427
  return null;
7448
7428
  }
@@ -7454,15 +7434,15 @@ function readFileRange(db, filePath, startLine, endLine, context) {
7454
7434
  symbol: `${doc.relative_path}:${startLine}-${endLine}`,
7455
7435
  shortName: `${doc.relative_path}:${startLine}-${endLine}`,
7456
7436
  relativePath: doc.relative_path,
7457
- startLine: start + 1,
7458
- endLine: end + 1,
7437
+ startLine: start,
7438
+ endLine: end,
7459
7439
  language: doc.language,
7460
7440
  source
7461
7441
  };
7462
7442
  }
7463
7443
 
7464
7444
  // src/queries/complexity.ts
7465
- import { readFileSync as readFileSync5 } from "fs";
7445
+ import { readFileSync as readFileSync6 } from "fs";
7466
7446
  import { join as join10 } from "path";
7467
7447
  function complexity(db, symbolPattern) {
7468
7448
  const match = findFirstSymbolMatch(db, symbolPattern);
@@ -7475,7 +7455,7 @@ function complexity(db, symbolPattern) {
7475
7455
  const filePath = join10(db.config.projectRoot, match.relativePath);
7476
7456
  let source = "";
7477
7457
  try {
7478
- const lines = readFileSync5(filePath, "utf-8").split("\n");
7458
+ const lines = readFileSync6(filePath, "utf-8").split("\n");
7479
7459
  source = lines.slice(match.startLine, match.endLine + 1).join("\n");
7480
7460
  } catch {
7481
7461
  }
@@ -7635,28 +7615,40 @@ function uniqueSymbolRows(rows) {
7635
7615
 
7636
7616
  // src/queries/slice.ts
7637
7617
  function slice(db, symbolPattern, opts = {}) {
7638
- const { direction = "backward" } = opts;
7618
+ const { direction = "backward", maxDepth = 3 } = opts;
7639
7619
  const match = findFirstSymbolMatch(db, symbolPattern);
7640
7620
  if (!match) return null;
7641
7621
  if (direction === "backward") {
7642
- return backwardSlice(db, match);
7622
+ return backwardSlice(db, match, maxDepth);
7643
7623
  } else {
7644
7624
  return forwardSlice(db, match);
7645
7625
  }
7646
7626
  }
7647
- function backwardSlice(db, match) {
7648
- const callees = getCalleeRowsForSymbol(db, match);
7649
- const seen = /* @__PURE__ */ new Set();
7627
+ function backwardSlice(db, match, maxDepth) {
7650
7628
  const connected = [];
7651
- for (const c of callees) {
7652
- if (seen.has(c.symbol)) continue;
7653
- seen.add(c.symbol);
7654
- connected.push({
7655
- symbol: c.symbol,
7656
- shortName: shortenSymbol(c.symbol),
7657
- file: c.file,
7658
- relationship: "referenced within definition (callee)"
7659
- });
7629
+ const visited = /* @__PURE__ */ new Set([match.symbol]);
7630
+ let frontier = [match];
7631
+ for (let depth = 1; depth <= maxDepth; depth++) {
7632
+ if (frontier.length === 0) break;
7633
+ const nextFrontier = [];
7634
+ for (const current of frontier) {
7635
+ const callees = getCalleeRowsForSymbol(db, current);
7636
+ for (const c of callees) {
7637
+ if (visited.has(c.symbol)) continue;
7638
+ visited.add(c.symbol);
7639
+ connected.push({
7640
+ symbol: c.symbol,
7641
+ shortName: shortenSymbol(c.symbol),
7642
+ file: c.file,
7643
+ relationship: depth === 1 ? "referenced within definition (callee)" : `depth ${depth} callee`
7644
+ });
7645
+ const calleeMatch = findExactSymbolMatch(db, c.symbol);
7646
+ if (calleeMatch && !db.isIgnored(calleeMatch.relativePath)) {
7647
+ nextFrontier.push(calleeMatch);
7648
+ }
7649
+ }
7650
+ }
7651
+ frontier = nextFrontier;
7660
7652
  }
7661
7653
  return {
7662
7654
  symbol: match.symbol,
@@ -8047,7 +8039,7 @@ import {
8047
8039
  readlinkSync,
8048
8040
  unlinkSync
8049
8041
  } from "fs";
8050
- import { join as join11, dirname as dirname3, resolve as resolve3 } from "path";
8042
+ import { join as join11, dirname as dirname4, resolve as resolve4 } from "path";
8051
8043
  import { homedir as homedir2, platform as platform3 } from "os";
8052
8044
  import { fileURLToPath } from "url";
8053
8045
  var IS_WINDOWS3 = platform3() === "win32";
@@ -8062,7 +8054,7 @@ function installSkills(opts = {}) {
8062
8054
  const log = opts.quiet ? () => {
8063
8055
  } : console.log;
8064
8056
  const thisFile = fileURLToPath(import.meta.url);
8065
- const skillsSource = resolve3(dirname3(thisFile), "..", "skills");
8057
+ const skillsSource = resolve4(dirname4(thisFile), "..", "skills");
8066
8058
  const targets = [
8067
8059
  join11(homedir2(), ".claude", "skills"),
8068
8060
  join11(homedir2(), ".codex", "skills")
@@ -8073,7 +8065,7 @@ function installSkills(opts = {}) {
8073
8065
  alreadyLinked: []
8074
8066
  };
8075
8067
  for (const targetDir of targets) {
8076
- const parentDir = dirname3(targetDir);
8068
+ const parentDir = dirname4(targetDir);
8077
8069
  if (!existsSync9(parentDir)) {
8078
8070
  continue;
8079
8071
  }
@@ -8089,7 +8081,7 @@ function installSkills(opts = {}) {
8089
8081
  if (existsSync9(target)) {
8090
8082
  try {
8091
8083
  const existing = readlinkSync(target);
8092
- if (resolve3(existing) === resolve3(source)) {
8084
+ if (resolve4(existing) === resolve4(source)) {
8093
8085
  result.alreadyLinked.push(`${toolName}/${skill}`);
8094
8086
  log(` ok: ${skill} \u2192 ${toolName} (already linked)`);
8095
8087
  continue;
@@ -8216,11 +8208,15 @@ var queries = {
8216
8208
  program.name("scip-query").description("Language-agnostic code intelligence CLI powered by SCIP indexes").version(cliVersion);
8217
8209
  program.command("reindex").description("Index the codebase and convert to SQLite").option("-l, --language <lang>", "Index only this language (can be repeated)", collect, []).option("--pnpm-workspaces", "Enable pnpm workspace support (TypeScript)").action(async (opts) => {
8218
8210
  const projectRoot = resolveProjectRoot();
8211
+ const config = loadProjectConfig(projectRoot);
8212
+ const paths = resolveIndexPaths(projectRoot, config);
8219
8213
  try {
8220
8214
  const result = await reindex({
8221
8215
  projectRoot,
8222
- languages: opts.language.length > 0 ? opts.language : void 0,
8223
- pnpmWorkspaces: opts.pnpmWorkspaces
8216
+ languages: opts.language.length > 0 ? opts.language : config.languages,
8217
+ outputScip: paths.indexPath,
8218
+ outputDb: paths.dbPath,
8219
+ pnpmWorkspaces: opts.pnpmWorkspaces || config.indexer?.typescript?.pnpmWorkspaces
8224
8220
  });
8225
8221
  console.log(`Indexed ${result.languages.join(", ")} in ${(result.durationMs / 1e3).toFixed(1)}s`);
8226
8222
  } catch (err) {
@@ -8663,7 +8659,7 @@ program.command("call-graph <symbol>").description("Show incoming callers and ou
8663
8659
  }
8664
8660
  );
8665
8661
  });
8666
- program.command("similar [symbol]").description("Find functions with similar callee fingerprints (consolidation candidates)").option("--min-similarity <n>", "Minimum Jaccard similarity (0-1)", parseFloat, 0.4).option("-n, --limit <n>", "Number of results", parseIntSafe, 20).option("-s, --scope <path>", "Limit to files matching path").option("--min-callees <n>", "Minimum callees to consider", parseIntSafe, 4).action((symbol, opts) => {
8662
+ program.command("similar [symbol]").description("Find functions with similar callee fingerprints (consolidation candidates)").option("--min-similarity <n>", "Minimum Jaccard similarity (0-1)", parseFloat, 0.4).option("-n, --limit <n>", "Number of results", parseIntSafe, 20).option("-s, --scope <path>", "Limit to files matching path").option("--min-callees <n>", "Minimum callees to consider", parseIntSafe, 4).option("--cross-file-only", "Only show cross-file pairs (skip same-file matches)").action((symbol, opts) => {
8667
8663
  withDb((db) => {
8668
8664
  if (symbol) {
8669
8665
  const results = queries.similar(db, symbol, {
@@ -8688,7 +8684,8 @@ ${Math.round(r.similarity * 100)}% similar:`);
8688
8684
  minSimilarity: opts.minSimilarity,
8689
8685
  limit: opts.limit,
8690
8686
  scope: opts.scope,
8691
- minCallees: opts.minCallees
8687
+ minCallees: opts.minCallees,
8688
+ crossFileOnly: opts.crossFileOnly
8692
8689
  });
8693
8690
  if (results.length === 0) {
8694
8691
  console.log("No similar symbol pairs found.");
@@ -9083,10 +9080,10 @@ program.command("dataflow <symbol>").description("Reference-level dataflow: defi
9083
9080
  }
9084
9081
  db.close();
9085
9082
  });
9086
- program.command("slice <symbol>").description("Reference-level program slice: what affects this (backward) or what this affects (forward)").option("--forward", "Forward slice (what does this affect). Default is backward.").action((symbol, opts) => {
9083
+ program.command("slice <symbol>").description("Reference-level program slice: what affects this (backward) or what this affects (forward)").option("--forward", "Forward slice (what does this affect). Default is backward.").option("--depth <n>", "Max transitive depth for backward slice", parseIntSafe, 3).action((symbol, opts) => {
9087
9084
  const db = openDb();
9088
9085
  const direction = opts.forward ? "forward" : "backward";
9089
- const result = queries.slice(db, symbol, { direction });
9086
+ const result = queries.slice(db, symbol, { direction, maxDepth: opts.depth });
9090
9087
  if (!result) {
9091
9088
  console.log("Symbol not found.");
9092
9089
  db.close();
@@ -9205,6 +9202,7 @@ program.command("watch").description("Watch for file changes and reindex automat
9205
9202
  const watcher = new Watcher({
9206
9203
  projectRoot,
9207
9204
  config,
9205
+ languages: config.languages,
9208
9206
  onStatus: (status) => {
9209
9207
  process.stdout.write(`\r\x1B[K${formatStatus(status)}`);
9210
9208
  },