scip-query 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/dist/{chunk-OXX3QF24.js → chunk-2MQ5DPY6.js} +2 -2
  2. package/dist/{chunk-RL74LF47.js → chunk-2QTYIOJ5.js} +2 -2
  3. package/dist/{chunk-DUJNJQPO.js → chunk-3VI4YXCL.js} +3 -3
  4. package/dist/{chunk-XUVPQDXW.js → chunk-3VV2G6U7.js} +4 -4
  5. package/dist/{chunk-J47VSL6I.js → chunk-44PFXVXG.js} +3 -3
  6. package/dist/{chunk-SVLUJSY7.js → chunk-6SLFQR36.js} +4 -15
  7. package/dist/{chunk-JHVQB4Y5.js → chunk-7DBPRGMS.js} +12 -12
  8. package/dist/{chunk-A6XLXV6W.js → chunk-DTB724R3.js} +3 -3
  9. package/dist/{chunk-TWVXFKJA.js → chunk-FLOYI6I4.js} +4 -4
  10. package/dist/{chunk-MGNMHKX3.js → chunk-FO2CBB7U.js} +10 -2
  11. package/dist/{chunk-4TYLS5XX.js → chunk-GJT3MO2T.js} +9 -2
  12. package/dist/{chunk-EAGKJFDX.js → chunk-HAP4LJKX.js} +8 -29
  13. package/dist/{chunk-RE7POFGI.js → chunk-JCOJQ4I6.js} +2 -2
  14. package/dist/{chunk-VY2L4TP6.js → chunk-JGQMOS4V.js} +3 -3
  15. package/dist/{chunk-QMXSLHZP.js → chunk-JMD4WJ2Q.js} +2 -2
  16. package/dist/{chunk-43A4UCS7.js → chunk-JSQPZOPO.js} +11 -18
  17. package/dist/{chunk-PCU455MX.js → chunk-JSXGC2EH.js} +2 -2
  18. package/dist/chunk-JZN3DRCT.js +59 -0
  19. package/dist/{chunk-ZVZAIIB5.js → chunk-KMWYB3CX.js} +12 -36
  20. package/dist/{chunk-SYQR4QGK.js → chunk-MRM755FU.js} +8 -9
  21. package/dist/{chunk-R7HPHMRZ.js → chunk-N2XO3Z5F.js} +3 -3
  22. package/dist/{chunk-POLELLNM.js → chunk-OLW5UL36.js} +19 -25
  23. package/dist/{chunk-ALUFWH3U.js → chunk-OMCRXXDX.js} +187 -282
  24. package/dist/{chunk-Z5VSUOEE.js → chunk-OWJOHUZE.js} +2 -2
  25. package/dist/{chunk-RJ5GULL6.js → chunk-PEDH3D4G.js} +2 -2
  26. package/dist/{chunk-6UZU7DFL.js → chunk-POAN4SCR.js} +3 -3
  27. package/dist/{chunk-3NJSJ7TE.js → chunk-PTMGEBU3.js} +4 -15
  28. package/dist/{chunk-NXUIWD6K.js → chunk-PU44HK7P.js} +3 -3
  29. package/dist/{chunk-6CH23IAS.js → chunk-QJI7EECA.js} +22 -34
  30. package/dist/{chunk-KLNKDX6A.js → chunk-R5HICGMB.js} +4 -4
  31. package/dist/{chunk-6ECR2FLR.js → chunk-RJ2D6YWQ.js} +4 -15
  32. package/dist/{chunk-CBIWNZZZ.js → chunk-RZ5GYPBP.js} +3 -3
  33. package/dist/chunk-SRLQNO6O.js +101 -0
  34. package/dist/{chunk-JKXHHV4B.js → chunk-UGS7HJI4.js} +2 -2
  35. package/dist/{chunk-PU2254N2.js → chunk-VKUUXOE7.js} +5 -15
  36. package/dist/{chunk-W46L2BXT.js → chunk-VPUJSJCI.js} +2 -2
  37. package/dist/{chunk-UJWI5CBB.js → chunk-VRWVV3EP.js} +4 -7
  38. package/dist/{chunk-7BS4CPJX.js → chunk-WJWQEU4A.js} +3 -3
  39. package/dist/chunk-WJZHDUSB.js +40 -0
  40. package/dist/{chunk-FVJE4MQL.js → chunk-WWOCQ5W4.js} +5 -8
  41. package/dist/chunk-X3Q2OVRL.js +77 -0
  42. package/dist/chunk-Y3P7QKKN.js +27 -0
  43. package/dist/{chunk-VKBOLNYN.js → chunk-Y6FAHY4N.js} +10 -8
  44. package/dist/{chunk-TO3L4YNK.js → chunk-YMSJCSRG.js} +5 -1
  45. package/dist/{chunk-24LF6IZB.js → chunk-ZDL3U4W2.js} +3 -3
  46. package/dist/{chunk-KG4OFQEN.js → chunk-ZXNX5JRE.js} +3 -3
  47. package/dist/cli.js +368 -721
  48. package/dist/{db-C4rPbKkI.d.ts → db-6F9R9e_t.d.ts} +0 -4
  49. package/dist/index.d.ts +2 -4
  50. package/dist/index.js +46 -58
  51. package/dist/queries/affected.d.ts +1 -1
  52. package/dist/queries/affected.js +3 -3
  53. package/dist/queries/bottlenecks.d.ts +1 -1
  54. package/dist/queries/bottlenecks.js +3 -3
  55. package/dist/queries/by-kind.d.ts +1 -1
  56. package/dist/queries/by-kind.js +3 -3
  57. package/dist/queries/call-graph.d.ts +1 -1
  58. package/dist/queries/call-graph.js +3 -3
  59. package/dist/queries/change-surface.d.ts +4 -1
  60. package/dist/queries/change-surface.js +3 -3
  61. package/dist/queries/clean-signature.d.ts +9 -1
  62. package/dist/queries/clean-signature.js +5 -3
  63. package/dist/queries/code.d.ts +1 -1
  64. package/dist/queries/code.js +3 -3
  65. package/dist/queries/complexity-hotspots.d.ts +8 -1
  66. package/dist/queries/complexity-hotspots.js +3 -3
  67. package/dist/queries/complexity.d.ts +1 -1
  68. package/dist/queries/complexity.js +3 -3
  69. package/dist/queries/convergence.d.ts +1 -1
  70. package/dist/queries/convergence.js +3 -3
  71. package/dist/queries/coupling.d.ts +1 -1
  72. package/dist/queries/coupling.js +3 -3
  73. package/dist/queries/cycles.d.ts +1 -1
  74. package/dist/queries/cycles.js +3 -3
  75. package/dist/queries/dataflow.d.ts +1 -1
  76. package/dist/queries/dataflow.js +3 -3
  77. package/dist/queries/dead.d.ts +1 -1
  78. package/dist/queries/dead.js +4 -4
  79. package/dist/queries/deep-chains.d.ts +1 -1
  80. package/dist/queries/deep-chains.js +3 -3
  81. package/dist/queries/deps.d.ts +1 -1
  82. package/dist/queries/deps.js +3 -3
  83. package/dist/queries/diff-impact.d.ts +1 -1
  84. package/dist/queries/diff-impact.js +2 -2
  85. package/dist/queries/drift.d.ts +1 -1
  86. package/dist/queries/drift.js +3 -3
  87. package/dist/queries/extract-candidates.d.ts +1 -1
  88. package/dist/queries/extract-candidates.js +3 -3
  89. package/dist/queries/fan.d.ts +1 -1
  90. package/dist/queries/fan.js +3 -3
  91. package/dist/queries/files.d.ts +1 -1
  92. package/dist/queries/files.js +1 -1
  93. package/dist/queries/health.d.ts +1 -1
  94. package/dist/queries/health.js +14 -14
  95. package/dist/queries/hierarchy.d.ts +1 -1
  96. package/dist/queries/hierarchy.js +3 -3
  97. package/dist/queries/hotspots.d.ts +1 -1
  98. package/dist/queries/hotspots.js +3 -3
  99. package/dist/queries/imports.d.ts +1 -1
  100. package/dist/queries/imports.js +3 -3
  101. package/dist/queries/index.d.ts +1 -1
  102. package/dist/queries/index.js +46 -46
  103. package/dist/queries/isolated.d.ts +1 -1
  104. package/dist/queries/isolated.js +4 -4
  105. package/dist/queries/members.d.ts +4 -1
  106. package/dist/queries/members.js +3 -3
  107. package/dist/queries/methods.d.ts +1 -1
  108. package/dist/queries/methods.js +3 -3
  109. package/dist/queries/outline.d.ts +4 -1
  110. package/dist/queries/outline.js +3 -3
  111. package/dist/queries/passthrough-candidates.d.ts +1 -1
  112. package/dist/queries/passthrough-candidates.js +3 -3
  113. package/dist/queries/redundant-reexports.d.ts +1 -1
  114. package/dist/queries/redundant-reexports.js +4 -4
  115. package/dist/queries/refs.d.ts +1 -1
  116. package/dist/queries/refs.js +3 -3
  117. package/dist/queries/similar-chains.d.ts +1 -1
  118. package/dist/queries/similar-chains.js +3 -3
  119. package/dist/queries/similar-files.d.ts +1 -1
  120. package/dist/queries/similar-files.js +3 -3
  121. package/dist/queries/similar-signatures.d.ts +1 -1
  122. package/dist/queries/similar-signatures.js +3 -3
  123. package/dist/queries/similar.d.ts +2 -1
  124. package/dist/queries/similar.js +3 -3
  125. package/dist/queries/slice.d.ts +4 -3
  126. package/dist/queries/slice.js +3 -3
  127. package/dist/queries/stale-abstractions.d.ts +1 -1
  128. package/dist/queries/stale-abstractions.js +3 -3
  129. package/dist/queries/stats.d.ts +1 -1
  130. package/dist/queries/surface.d.ts +1 -1
  131. package/dist/queries/surface.js +3 -3
  132. package/dist/queries/symbols.d.ts +1 -1
  133. package/dist/queries/symbols.js +4 -4
  134. package/dist/queries/system.d.ts +6 -2
  135. package/dist/queries/system.js +4 -4
  136. package/dist/queries/trace.d.ts +1 -1
  137. package/dist/queries/trace.js +4 -4
  138. package/dist/queries/wrapper-candidates.d.ts +1 -1
  139. package/dist/queries/wrapper-candidates.js +3 -3
  140. package/package.json +1 -1
  141. package/dist/chunk-5GCORUNV.js +0 -100
  142. package/dist/chunk-ELFGD5EW.js +0 -130
  143. package/dist/chunk-GNAMV3JC.js +0 -37
  144. package/dist/chunk-J6QXMYAQ.js +0 -115
  145. package/dist/chunk-KYPXKV64.js +0 -51
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
@@ -1323,10 +1315,6 @@ var Watcher = class {
1323
1315
  if (this.cooldownTimer) clearTimeout(this.cooldownTimer);
1324
1316
  this.setStatus({ state: "idle" });
1325
1317
  }
1326
- /** Get current watcher status */
1327
- getStatus() {
1328
- return this.status;
1329
- }
1330
1318
  // ── Internal ─────────────────────────────────────────────
1331
1319
  handleFileChange(filename) {
1332
1320
  const rel = relative2(this.projectRoot, join7(this.projectRoot, filename));
@@ -1479,12 +1467,20 @@ function stats(db) {
1479
1467
  }
1480
1468
 
1481
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
+ }
1482
1477
  function files(db, pattern) {
1478
+ const likePattern = globToLike(pattern);
1483
1479
  const rows = db.all(
1484
1480
  `SELECT relative_path FROM documents
1485
1481
  WHERE relative_path LIKE ?
1486
1482
  ORDER BY relative_path`,
1487
- `%${pattern}%`
1483
+ likePattern
1488
1484
  );
1489
1485
  return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({ relativePath: r.relative_path }));
1490
1486
  }
@@ -2539,44 +2535,55 @@ function getCachedMap(cache, db) {
2539
2535
  function normalizePath(path2) {
2540
2536
  return path2.replace(/\\/g, "/");
2541
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
+ }
2542
2552
  function isJavaScriptSourcePath(relativePath) {
2543
- return SOURCE_EXTENSIONS.includes(extname3(relativePath).toLowerCase());
2553
+ return hasExtensionIn(relativePath, SOURCE_EXTENSIONS);
2544
2554
  }
2545
2555
  function isPythonSourcePath(relativePath) {
2546
- return PYTHON_SOURCE_EXTENSIONS.includes(extname3(relativePath).toLowerCase());
2556
+ return hasExtensionIn(relativePath, PYTHON_SOURCE_EXTENSIONS);
2547
2557
  }
2548
2558
  function isJvmSourcePath(relativePath) {
2549
- return JVM_SOURCE_EXTENSIONS.includes(extname3(relativePath).toLowerCase());
2559
+ return hasExtensionIn(relativePath, JVM_SOURCE_EXTENSIONS);
2550
2560
  }
2551
2561
  function isRustSourcePath(relativePath) {
2552
- return RUST_SOURCE_EXTENSIONS.includes(extname3(relativePath).toLowerCase());
2562
+ return hasExtensionIn(relativePath, RUST_SOURCE_EXTENSIONS);
2553
2563
  }
2554
2564
  function isRubySourcePath(relativePath) {
2555
- return RUBY_SOURCE_EXTENSIONS.includes(extname3(relativePath).toLowerCase());
2565
+ return hasExtensionIn(relativePath, RUBY_SOURCE_EXTENSIONS);
2556
2566
  }
2557
2567
  function isCLikeSourcePath(relativePath) {
2558
- return C_LIKE_SOURCE_EXTENSIONS.includes(extname3(relativePath).toLowerCase());
2568
+ return hasExtensionIn(relativePath, C_LIKE_SOURCE_EXTENSIONS);
2559
2569
  }
2560
2570
  function isDotNetSourcePath(relativePath) {
2561
- return DOTNET_SOURCE_EXTENSIONS.includes(extname3(relativePath).toLowerCase());
2571
+ return hasExtensionIn(relativePath, DOTNET_SOURCE_EXTENSIONS);
2562
2572
  }
2563
2573
  function isVisualBasicSourcePath(relativePath) {
2564
2574
  return extname3(relativePath).toLowerCase() === ".vb";
2565
2575
  }
2566
2576
  function isDartSourcePath(relativePath) {
2567
- return DART_SOURCE_EXTENSIONS.includes(extname3(relativePath).toLowerCase());
2577
+ return hasExtensionIn(relativePath, DART_SOURCE_EXTENSIONS);
2568
2578
  }
2569
2579
  function isPhpSourcePath(relativePath) {
2570
- return PHP_SOURCE_EXTENSIONS.includes(extname3(relativePath).toLowerCase());
2580
+ return hasExtensionIn(relativePath, PHP_SOURCE_EXTENSIONS);
2571
2581
  }
2572
2582
  function extensionFamilyFor(relativePath) {
2573
- if (isJvmSourcePath(relativePath)) return JVM_SOURCE_EXTENSIONS;
2574
- if (isDotNetSourcePath(relativePath)) return DOTNET_SOURCE_EXTENSIONS;
2575
- if (isPhpSourcePath(relativePath)) return PHP_SOURCE_EXTENSIONS;
2576
- if (isDartSourcePath(relativePath)) return DART_SOURCE_EXTENSIONS;
2577
- if (isCLikeSourcePath(relativePath)) return C_LIKE_SOURCE_EXTENSIONS;
2578
- if (isRustSourcePath(relativePath)) return RUST_SOURCE_EXTENSIONS;
2579
- 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
+ }
2580
2587
  return SOURCE_EXTENSIONS;
2581
2588
  }
2582
2589
  function getSourceText(db, relativePath) {
@@ -2778,6 +2785,9 @@ function leafSuffix(raw) {
2778
2785
  const last = sym.descriptors[sym.descriptors.length - 1];
2779
2786
  return last?.suffix ?? null;
2780
2787
  }
2788
+ function isCallableSymbol(raw) {
2789
+ return raw.endsWith("().") || leafSuffix(raw) === "method";
2790
+ }
2781
2791
  function isFunctionLikeSymbol(raw) {
2782
2792
  const suffix = leafSuffix(raw);
2783
2793
  return suffix === "method" || suffix === "term";
@@ -2822,10 +2832,6 @@ var TEST_FILE_PATTERNS = [
2822
2832
  var TEST_SUPPORT_PATH_PATTERNS = [
2823
2833
  "%/test-utils/%"
2824
2834
  ];
2825
- function testFileExclusionSql(alias, extraPatterns = []) {
2826
- const patterns = uniquePatterns([...TEST_FILE_PATTERNS, ...extraPatterns]);
2827
- return patterns.map((pattern) => `${alias}.relative_path NOT LIKE '${pattern}'`).join("\n AND ");
2828
- }
2829
2835
  function buildFileDepGraph(db, scope) {
2830
2836
  const scopeFilter = scope ? `AND d1.relative_path LIKE '%${scope}%'` : "";
2831
2837
  const edges = db.all(
@@ -2880,6 +2886,8 @@ function findFirstSymbolMatch(db, symbolPattern) {
2880
2886
  const fileLineMatch = symbolPattern.match(/^(.+):(\d+)-(\d+)$/);
2881
2887
  if (fileLineMatch) {
2882
2888
  const [, filePath, startStr, endStr] = fileLineMatch;
2889
+ const userStart0 = Math.max(0, parseInt(startStr, 10) - 1);
2890
+ const userEnd0 = Math.max(userStart0, parseInt(endStr, 10) - 1);
2883
2891
  let row = db.get(
2884
2892
  `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
2885
2893
  FROM global_symbols gs
@@ -2891,8 +2899,8 @@ function findFirstSymbolMatch(db, symbolPattern) {
2891
2899
  ORDER BY (der.end_line - der.start_line) ASC
2892
2900
  LIMIT 1`,
2893
2901
  `%${filePath}%`,
2894
- parseInt(startStr, 10),
2895
- parseInt(endStr, 10)
2902
+ userStart0,
2903
+ userEnd0
2896
2904
  );
2897
2905
  if (!row) {
2898
2906
  row = db.get(
@@ -2909,8 +2917,8 @@ function findFirstSymbolMatch(db, symbolPattern) {
2909
2917
  ORDER BY (MAX(c.end_line) - MIN(c.start_line)) ASC
2910
2918
  LIMIT 1`,
2911
2919
  `%${filePath}%`,
2912
- parseInt(startStr, 10),
2913
- parseInt(endStr, 10)
2920
+ userStart0,
2921
+ userEnd0
2914
2922
  );
2915
2923
  }
2916
2924
  if (row && !db.isIgnored(row.relative_path)) {
@@ -3202,6 +3210,59 @@ function getSourceReferenceSites(db, symbol) {
3202
3210
  }
3203
3211
  return sites;
3204
3212
  }
3213
+ function getResolvedReferenceSites(db, symbol) {
3214
+ const match = getFullSymbolMatch(db, symbol);
3215
+ if (!match) {
3216
+ return [];
3217
+ }
3218
+ const rows = db.all(
3219
+ `SELECT DISTINCT d.relative_path, c.start_line, c.end_line
3220
+ FROM mentions m
3221
+ JOIN chunks c ON m.chunk_id = c.id
3222
+ JOIN documents d ON c.document_id = d.id
3223
+ WHERE m.symbol_id = ?
3224
+ AND m.role != 1
3225
+ ${db.pathExclusionsFor("d")}
3226
+ ORDER BY d.relative_path, c.start_line`,
3227
+ match.symbolId
3228
+ );
3229
+ const chunksByFile = /* @__PURE__ */ new Map();
3230
+ for (const row of rows) {
3231
+ if (db.isIgnored(row.relative_path)) continue;
3232
+ let bucket = chunksByFile.get(row.relative_path);
3233
+ if (!bucket) {
3234
+ bucket = [];
3235
+ chunksByFile.set(row.relative_path, bucket);
3236
+ }
3237
+ bucket.push({ start_line: row.start_line, end_line: row.end_line });
3238
+ }
3239
+ const identifier = leafName(match.symbol);
3240
+ const sites = [];
3241
+ const seen = /* @__PURE__ */ new Set();
3242
+ for (const [file, chunks] of chunksByFile) {
3243
+ const definitions = getDefinitionsForFile(db, file);
3244
+ const excludeOpts = file === match.relativePath ? { excludeStartLine: match.startLine, excludeEndLine: match.endLine } : {};
3245
+ const allHits = identifier ? findIdentifierLines(db, file, identifier, excludeOpts) : [];
3246
+ for (const chunk of chunks) {
3247
+ const hitsInChunk = allHits.filter(
3248
+ (line) => line >= chunk.start_line && line <= chunk.end_line
3249
+ );
3250
+ const lines = hitsInChunk.length > 0 ? hitsInChunk : [chunk.start_line];
3251
+ for (const line of lines) {
3252
+ const enclosing = findEnclosingDefinition(definitions, line);
3253
+ const key = `${file}|${line}|${enclosing?.symbol ?? ""}`;
3254
+ if (seen.has(key)) continue;
3255
+ seen.add(key);
3256
+ sites.push({
3257
+ file,
3258
+ line,
3259
+ enclosingSymbol: enclosing?.symbol ?? null
3260
+ });
3261
+ }
3262
+ }
3263
+ }
3264
+ return sites;
3265
+ }
3205
3266
  function calleeQueryParams(symbol, limit) {
3206
3267
  const params = [
3207
3268
  symbol.documentId,
@@ -3231,56 +3292,41 @@ function findEnclosingDefinition(definitions, line) {
3231
3292
  }
3232
3293
  return best;
3233
3294
  }
3234
- function getPythonSourceCalleeRows(db, symbol, limit) {
3235
- const match = getFullSymbolMatch(db, symbol);
3236
- if (!match || !isPythonDocument(db, match.relativePath)) {
3237
- return [];
3238
- }
3239
- const definitions = getDefinitionsForFile(db, match.relativePath);
3240
- const current = definitions.find((definition) => definition.symbolId === match.symbolId);
3241
- if (!current) {
3242
- return [];
3243
- }
3244
- const imports2 = getSourceImports(db, match.relativePath);
3245
- const bindings = new Map(
3246
- getSourceConstructorBindings(db, match.relativePath, {
3247
- startLine: match.startLine,
3248
- endLine: match.endLine
3249
- }).map((binding) => [binding.localName, binding.typeName])
3250
- );
3251
- const rows = [];
3252
- const seen = /* @__PURE__ */ new Set();
3253
- for (const call of getSourceCalls(db, match.relativePath, {
3254
- startLine: match.startLine,
3255
- endLine: match.endLine
3256
- })) {
3257
- const resolved = resolvePythonCallTarget(
3258
- db,
3259
- current,
3260
- definitions,
3261
- imports2,
3262
- bindings,
3263
- call.receiverName,
3264
- call.calleeName
3265
- );
3266
- if (!resolved || resolved.symbolId === match.symbolId || db.isIgnored(resolved.relativePath)) continue;
3267
- const chunkId = 1e9 + call.line;
3268
- const key = `${resolved.symbol}|${resolved.relativePath}|${chunkId}`;
3269
- if (seen.has(key)) continue;
3270
- seen.add(key);
3271
- rows.push({
3272
- symbol: resolved.symbol,
3273
- file: resolved.relativePath,
3274
- chunkId
3275
- });
3276
- }
3277
- return applyLimit(rows, limit);
3278
- }
3279
- function getJavaScriptSourceCalleeRows(db, symbol, limit) {
3280
- const match = getFullSymbolMatch(db, symbol);
3281
- if (!match || !isJavaScriptDocument(db, match.relativePath)) {
3282
- return [];
3283
- }
3295
+ var LANGUAGE_CALLEE_CONFIGS = [
3296
+ // Python (index 0) — complex resolver
3297
+ { kind: "complex", languageIndex: 0, resolver: resolvePythonCallTarget },
3298
+ // JavaScript/TypeScript (index 1) — complex resolver
3299
+ { kind: "complex", languageIndex: 1, resolver: resolveJavaScriptCallTarget },
3300
+ // Java (index 2) — simple with field bindings
3301
+ { kind: "simple", languageIndex: 2, parseBindings: (_db, source) => parseJavaFieldBindings(source) },
3302
+ // Kotlin (index 3) — simple with field bindings
3303
+ { kind: "simple", languageIndex: 3, parseBindings: (_db, source) => parseKotlinFieldBindings(source) },
3304
+ // Scala (index 4) — simple, no bindings
3305
+ { kind: "simple", languageIndex: 4, parseBindings: null },
3306
+ // C# (index 5) — simple, no bindings
3307
+ { kind: "simple", languageIndex: 5, parseBindings: null },
3308
+ // Visual Basic (index 6) — simple, no bindings
3309
+ { kind: "simple", languageIndex: 6, parseBindings: null },
3310
+ // C++ (index 7) simple with receiver bindings
3311
+ { kind: "simple", languageIndex: 7, parseBindings: (_db, source) => parseCppReceiverBindings(source) },
3312
+ // Rust (index 8) — simple, no bindings
3313
+ { kind: "simple", languageIndex: 8, parseBindings: null },
3314
+ // Ruby (index 9) simple with dual-attempt logic
3315
+ {
3316
+ kind: "simple",
3317
+ languageIndex: 9,
3318
+ parseBindings: (db, source) => parseRubyReceiverBindings(db, source),
3319
+ dualAttempt: {
3320
+ baseOpts: { allowInstanceVariables: true },
3321
+ extendedOpts: { allowInstanceVariables: true, allowBareMemberCalls: true }
3322
+ }
3323
+ },
3324
+ // Dart (index 10) — simple, no bindings
3325
+ { kind: "simple", languageIndex: 10, parseBindings: null },
3326
+ // PHP (index 11) — simple, no bindings
3327
+ { kind: "simple", languageIndex: 11, parseBindings: null }
3328
+ ];
3329
+ function getComplexSourceCalleeRows(db, match, config, limit) {
3284
3330
  const definitions = getDefinitionsForFile(db, match.relativePath);
3285
3331
  const current = definitions.find((definition) => definition.symbolId === match.symbolId);
3286
3332
  if (!current) {
@@ -3299,7 +3345,7 @@ function getJavaScriptSourceCalleeRows(db, symbol, limit) {
3299
3345
  startLine: match.startLine,
3300
3346
  endLine: match.endLine
3301
3347
  })) {
3302
- const resolved = resolveJavaScriptCallTarget(
3348
+ const resolved = config.resolver(
3303
3349
  db,
3304
3350
  current,
3305
3351
  definitions,
@@ -3321,134 +3367,31 @@ function getJavaScriptSourceCalleeRows(db, symbol, limit) {
3321
3367
  }
3322
3368
  return applyLimit(rows, limit);
3323
3369
  }
3324
- function getJavaSourceCalleeRows(db, symbol, limit) {
3325
- const match = getFullSymbolMatch(db, symbol);
3326
- if (!match || !isJavaDocument(db, match.relativePath)) {
3327
- return [];
3328
- }
3329
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3330
- const bindings = parseJavaFieldBindings(getSourceText(db, match.relativePath));
3331
- return resolveSimpleSourceCallees(db, match, calls, bindings, limit);
3332
- }
3333
- function getKotlinSourceCalleeRows(db, symbol, limit) {
3334
- const match = getFullSymbolMatch(db, symbol);
3335
- if (!match || !isKotlinDocument(db, match.relativePath)) {
3336
- return [];
3337
- }
3338
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3339
- const bindings = parseKotlinFieldBindings(getSourceText(db, match.relativePath));
3340
- return resolveSimpleSourceCallees(db, match, calls, bindings, limit);
3341
- }
3342
- function getScalaSourceCalleeRows(db, symbol, limit) {
3343
- const match = getFullSymbolMatch(db, symbol);
3344
- if (!match || !isScalaDocument(db, match.relativePath)) {
3345
- return [];
3346
- }
3347
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3348
- return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
3349
- }
3350
- function getCSharpSourceCalleeRows(db, symbol, limit) {
3351
- const match = getFullSymbolMatch(db, symbol);
3352
- if (!match || !isCSharpDocument(db, match.relativePath)) {
3353
- return [];
3354
- }
3355
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3356
- return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
3357
- }
3358
- function getVisualBasicSourceCalleeRows(db, symbol, limit) {
3359
- const match = getFullSymbolMatch(db, symbol);
3360
- if (!match || !isVisualBasicDocument(db, match.relativePath)) {
3361
- return [];
3362
- }
3363
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3364
- return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
3365
- }
3366
- function getCppSourceCalleeRows(db, symbol, limit) {
3367
- const match = getFullSymbolMatch(db, symbol);
3368
- if (!match || !isCppDocument(db, match.relativePath)) {
3369
- return [];
3370
+ function getSimpleLanguageCalleeRows(db, match, config, limit) {
3371
+ let calls;
3372
+ if (config.dualAttempt) {
3373
+ const baseCalls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine, config.dualAttempt.baseOpts);
3374
+ const extendedCalls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine, config.dualAttempt.extendedOpts);
3375
+ calls = extendedCalls.length > 0 ? extendedCalls : baseCalls;
3376
+ } else {
3377
+ calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine, config.sourceCallOpts);
3370
3378
  }
3371
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3372
- const bindings = parseCppReceiverBindings(getSourceText(db, match.relativePath));
3379
+ const bindings = config.parseBindings ? config.parseBindings(db, getSourceText(db, match.relativePath)) : /* @__PURE__ */ new Map();
3373
3380
  return resolveSimpleSourceCallees(db, match, calls, bindings, limit);
3374
3381
  }
3375
- function getRustSourceCalleeRows(db, symbol, limit) {
3376
- const match = getFullSymbolMatch(db, symbol);
3377
- if (!match || !isRustDocument(db, match.relativePath)) {
3378
- return [];
3379
- }
3380
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3381
- return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
3382
- }
3383
- function getRubySourceCalleeRows(db, symbol, limit) {
3384
- const match = getFullSymbolMatch(db, symbol);
3385
- if (!match || !isRubyDocument(db, match.relativePath)) {
3386
- return [];
3387
- }
3388
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine, { allowInstanceVariables: true });
3389
- const rubyCalls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine, {
3390
- allowInstanceVariables: true,
3391
- allowBareMemberCalls: true
3392
- });
3393
- const bindings = parseRubyReceiverBindings(db, getSourceText(db, match.relativePath));
3394
- return resolveSimpleSourceCallees(db, match, rubyCalls.length > 0 ? rubyCalls : calls, bindings, limit);
3395
- }
3396
- function getDartSourceCalleeRows(db, symbol, limit) {
3397
- const match = getFullSymbolMatch(db, symbol);
3398
- if (!match || !isDartDocument(db, match.relativePath)) {
3399
- return [];
3400
- }
3401
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3402
- return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
3403
- }
3404
- function getPhpSourceCalleeRows(db, symbol, limit) {
3405
- const match = getFullSymbolMatch(db, symbol);
3406
- if (!match || !isPhpDocument(db, match.relativePath)) {
3407
- return [];
3408
- }
3409
- const calls = getSimpleSourceCalls(db, match.relativePath, match.startLine, match.endLine);
3410
- return resolveSimpleSourceCallees(db, match, calls, /* @__PURE__ */ new Map(), limit);
3411
- }
3412
3382
  function getSourceBackedCalleeRows(db, symbol, limit) {
3413
3383
  const match = getFullSymbolMatch(db, symbol);
3414
3384
  if (!match) {
3415
3385
  return [];
3416
3386
  }
3417
- if (isPythonDocument(db, match.relativePath)) {
3418
- return getPythonSourceCalleeRows(db, match, limit);
3419
- }
3420
- if (isJavaScriptDocument(db, match.relativePath)) {
3421
- return getJavaScriptSourceCalleeRows(db, match, limit);
3422
- }
3423
- if (isJavaDocument(db, match.relativePath)) {
3424
- return getJavaSourceCalleeRows(db, match, limit);
3425
- }
3426
- if (isScalaDocument(db, match.relativePath)) {
3427
- return getScalaSourceCalleeRows(db, match, limit);
3428
- }
3429
- if (isKotlinDocument(db, match.relativePath)) {
3430
- return getKotlinSourceCalleeRows(db, match, limit);
3431
- }
3432
- if (isCSharpDocument(db, match.relativePath)) {
3433
- return getCSharpSourceCalleeRows(db, match, limit);
3434
- }
3435
- if (isVisualBasicDocument(db, match.relativePath)) {
3436
- return getVisualBasicSourceCalleeRows(db, match, limit);
3437
- }
3438
- if (isCppDocument(db, match.relativePath)) {
3439
- return getCppSourceCalleeRows(db, match, limit);
3440
- }
3441
- if (isRustDocument(db, match.relativePath)) {
3442
- return getRustSourceCalleeRows(db, match, limit);
3443
- }
3444
- if (isRubyDocument(db, match.relativePath)) {
3445
- return getRubySourceCalleeRows(db, match, limit);
3446
- }
3447
- if (isDartDocument(db, match.relativePath)) {
3448
- return getDartSourceCalleeRows(db, match, limit);
3449
- }
3450
- if (isPhpDocument(db, match.relativePath)) {
3451
- return getPhpSourceCalleeRows(db, match, limit);
3387
+ for (const config of LANGUAGE_CALLEE_CONFIGS) {
3388
+ if (!isDocumentLanguage(db, match.relativePath, DOCUMENT_LANGUAGE_TABLE[config.languageIndex])) {
3389
+ continue;
3390
+ }
3391
+ if (config.kind === "complex") {
3392
+ return getComplexSourceCalleeRows(db, match, config, limit);
3393
+ }
3394
+ return getSimpleLanguageCalleeRows(db, match, config, limit);
3452
3395
  }
3453
3396
  return [];
3454
3397
  }
@@ -3458,9 +3401,11 @@ function getPythonSourceCallerRows(db, target, limit) {
3458
3401
  }
3459
3402
  const rows = [];
3460
3403
  const seen = /* @__PURE__ */ new Set();
3404
+ const pythonConfig = LANGUAGE_CALLEE_CONFIGS[0];
3461
3405
  for (const candidate of getAllFunctionLikeDefinitions(db)) {
3462
3406
  if (candidate.symbolId === target.symbolId) continue;
3463
- const callees = getPythonSourceCalleeRows(db, candidate);
3407
+ if (!isDocumentLanguage(db, candidate.relativePath, DOCUMENT_LANGUAGE_TABLE[pythonConfig.languageIndex])) continue;
3408
+ const callees = getComplexSourceCalleeRows(db, candidate, pythonConfig);
3464
3409
  if (!callees.some((callee) => callee.symbol === target.symbol)) continue;
3465
3410
  const key = `${candidate.symbol}|${candidate.relativePath}`;
3466
3411
  if (seen.has(key) || db.isIgnored(candidate.relativePath)) continue;
@@ -3683,7 +3628,16 @@ function resolveCallableDefinitionStartLine(lines, definition) {
3683
3628
  const escapedLeaf = escapeRegex2(definition.leaf);
3684
3629
  const strongPatterns = [
3685
3630
  new RegExp(`\\b(?:function|def|fn)\\s+${escapedLeaf}\\b`),
3686
- new RegExp(`\\b${escapedLeaf}\\b\\s*[:=]\\s*(?:async\\s*)?(?:function\\b|\\()`)
3631
+ new RegExp(`\\b${escapedLeaf}\\b\\s*[:=]\\s*(?:async\\s*)?(?:function\\b|\\()`),
3632
+ // Method/function declaration with optional TypeScript generic
3633
+ // parameters: matches `foo(`, `foo<T>(`, `foo<T = Record<string, unknown>>(`.
3634
+ // `[^(]*` with greedy backtracking handles nested generics as long as
3635
+ // the generic block itself doesn't contain a `(`. Anchored to start of
3636
+ // line content (optional leading whitespace + optional access modifiers)
3637
+ // so it prefers the declaration line over call sites.
3638
+ new RegExp(
3639
+ `^\\s*(?:export\\s+|public\\s+|private\\s+|protected\\s+|static\\s+|readonly\\s+|async\\s+|abstract\\s+|get\\s+|set\\s+)*${escapedLeaf}\\s*(?:<[^(]*>)?\\s*\\(`
3640
+ )
3687
3641
  ];
3688
3642
  const fallbackPatterns = [
3689
3643
  new RegExp(`\\b${escapedLeaf}\\b\\s*\\(`)
@@ -3802,6 +3756,17 @@ function getAllDefinitions(db, opts = {}) {
3802
3756
  );
3803
3757
  return rows.filter((row) => !db.isIgnored(row.relative_path)).flatMap((row) => getDefinitionsForFile(db, row.relative_path));
3804
3758
  }
3759
+ function getScopedDefinitions(db, scope) {
3760
+ const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
3761
+ return db.all(
3762
+ `SELECT relative_path
3763
+ FROM documents
3764
+ WHERE 1 = 1
3765
+ ${db.pathExclusionsFor("documents")}
3766
+ ${scopeFilter}
3767
+ ORDER BY relative_path`
3768
+ ).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
3769
+ }
3805
3770
  function getAllFunctionLikeDefinitions(db) {
3806
3771
  return getAllDefinitions(db).filter((definition) => definition.isFunctionLike);
3807
3772
  }
@@ -4219,89 +4184,29 @@ function parentTypeName(rawSymbol) {
4219
4184
  }
4220
4185
  return null;
4221
4186
  }
4222
- function isPythonDocument(db, relativePath) {
4223
- const row = db.get(
4224
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4225
- relativePath
4226
- );
4227
- return row?.language === "python" || relativePath.endsWith(".py") || relativePath.endsWith(".pyi");
4228
- }
4229
- function isJavaScriptDocument(db, relativePath) {
4230
- const row = db.get(
4231
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4232
- relativePath
4233
- );
4234
- return row?.language === "typescript" || row?.language === "javascript" || /\.(?:[cm]?[jt]sx?)$/.test(relativePath);
4235
- }
4236
- function isJavaDocument(db, relativePath) {
4237
- const row = db.get(
4238
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4239
- relativePath
4240
- );
4241
- return row?.language === "java" || relativePath.endsWith(".java");
4242
- }
4243
- function isKotlinDocument(db, relativePath) {
4244
- const row = db.get(
4245
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4246
- relativePath
4247
- );
4248
- return row?.language === "kotlin" || relativePath.endsWith(".kt") || relativePath.endsWith(".kts");
4249
- }
4250
- function isScalaDocument(db, relativePath) {
4251
- const row = db.get(
4252
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4253
- relativePath
4254
- );
4255
- return row?.language === "scala" || relativePath.endsWith(".scala");
4256
- }
4257
- function isCSharpDocument(db, relativePath) {
4258
- const row = db.get(
4259
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4260
- relativePath
4261
- );
4262
- return row?.language === "C#" || relativePath.endsWith(".cs");
4263
- }
4264
- function isVisualBasicDocument(db, relativePath) {
4265
- const row = db.get(
4266
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4267
- relativePath
4268
- );
4269
- return row?.language === "Visual Basic" || relativePath.endsWith(".vb");
4270
- }
4271
- function isCppDocument(db, relativePath) {
4272
- const row = db.get(
4273
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4274
- relativePath
4275
- );
4276
- return row?.language === "CPP" || /\.(?:cc|cpp|cxx|hpp|hh|hxx)$/.test(relativePath);
4277
- }
4278
- function isRustDocument(db, relativePath) {
4279
- const row = db.get(
4280
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4281
- relativePath
4282
- );
4283
- return row?.language === "Rust" || relativePath.endsWith(".rs");
4284
- }
4285
- function isRubyDocument(db, relativePath) {
4286
- const row = db.get(
4287
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4288
- relativePath
4289
- );
4290
- return row?.language === "ruby" || relativePath.endsWith(".rb");
4291
- }
4292
- function isDartDocument(db, relativePath) {
4187
+ var DOCUMENT_LANGUAGE_TABLE = [
4188
+ { languages: ["python"], extensionPattern: /\.(?:py|pyi)$/ },
4189
+ { languages: ["typescript", "javascript"], extensionPattern: /\.(?:[cm]?[jt]sx?)$/ },
4190
+ { languages: ["java"], extensionPattern: /\.java$/ },
4191
+ { languages: ["kotlin"], extensionPattern: /\.(?:kt|kts)$/ },
4192
+ { languages: ["scala"], extensionPattern: /\.scala$/ },
4193
+ { languages: ["C#"], extensionPattern: /\.cs$/ },
4194
+ { languages: ["Visual Basic"], extensionPattern: /\.vb$/ },
4195
+ { languages: ["CPP"], extensionPattern: /\.(?:cc|cpp|cxx|hpp|hh|hxx)$/ },
4196
+ { languages: ["Rust"], extensionPattern: /\.rs$/ },
4197
+ { languages: ["ruby"], extensionPattern: /\.rb$/ },
4198
+ { languages: ["Dart"], extensionPattern: /\.dart$/ },
4199
+ { languages: ["PHP"], extensionPattern: /\.php$/ }
4200
+ ];
4201
+ function isDocumentLanguage(db, relativePath, entry) {
4293
4202
  const row = db.get(
4294
4203
  `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4295
4204
  relativePath
4296
4205
  );
4297
- return row?.language === "Dart" || relativePath.endsWith(".dart");
4206
+ return entry.languages.includes(row?.language ?? "") || entry.extensionPattern.test(relativePath);
4298
4207
  }
4299
- function isPhpDocument(db, relativePath) {
4300
- const row = db.get(
4301
- `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
4302
- relativePath
4303
- );
4304
- return row?.language === "PHP" || relativePath.endsWith(".php");
4208
+ function isPythonDocument(db, relativePath) {
4209
+ return isDocumentLanguage(db, relativePath, DOCUMENT_LANGUAGE_TABLE[0]);
4305
4210
  }
4306
4211
  function applyLimit(values, limit) {
4307
4212
  return typeof limit === "number" ? values.slice(0, limit) : values;
@@ -4353,15 +4258,18 @@ function scoreDocumentPath(relativePath, rawPattern) {
4353
4258
  function normalizeLookupPath(filePattern) {
4354
4259
  return filePattern.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "").replace(/\/+$/, "");
4355
4260
  }
4356
- function uniquePatterns(patterns) {
4357
- return [...new Set(patterns)];
4358
- }
4359
4261
 
4360
4262
  // src/queries/clean-signature.ts
4361
4263
  function cleanSignature(sig) {
4362
4264
  if (!sig || !sig.trim()) return null;
4363
4265
  return sig.replace(/^```\w*\s*/, "").replace(/\s*```$/, "").replace(/^\(method\)\s*/, "").replace(/^\(property\)\s*/, "").replace(/^\(function\)\s*/, "").replace(/^\(class\)\s*/, "").replace(/^\(interface\)\s*/, "").replace(/^\(enum\)\s*/, "").replace(/^\(type alias\)\s*/, "").replace(/^\(const\)\s*/, "").replace(/^\(var\)\s*/, "").trim() || null;
4364
4266
  }
4267
+ function extractSignature(doc) {
4268
+ if (!doc) return null;
4269
+ const pipeIdx = doc.indexOf("|");
4270
+ if (pipeIdx === -1) return doc.replace(/\n/g, " ");
4271
+ return doc.slice(pipeIdx + 1).replace(/\n/g, " ");
4272
+ }
4365
4273
 
4366
4274
  // src/queries/symbols.ts
4367
4275
  function symbols(db, filePattern) {
@@ -4371,9 +4279,7 @@ function symbols(db, filePattern) {
4371
4279
  }
4372
4280
  return resolvedPaths.flatMap((relativePath) => getDefinitionsForFile(db, relativePath)).filter((row) => !db.isIgnored(row.relativePath)).map((row) => {
4373
4281
  const docRow = db.get(
4374
- `SELECT REPLACE(SUBSTR(documentation, INSTR(documentation, '|') + 1), char(10), ' ') AS sig
4375
- FROM global_symbols
4376
- WHERE id = ?`,
4282
+ "SELECT documentation FROM global_symbols WHERE id = ?",
4377
4283
  row.symbolId
4378
4284
  );
4379
4285
  return {
@@ -4381,7 +4287,7 @@ function symbols(db, filePattern) {
4381
4287
  endLine: row.endLine,
4382
4288
  symbol: row.symbol,
4383
4289
  shortName: shortenSymbol(row.symbol),
4384
- signature: cleanSignature(docRow?.sig ?? null)
4290
+ signature: cleanSignature(extractSignature(docRow?.documentation ?? null))
4385
4291
  };
4386
4292
  });
4387
4293
  }
@@ -4403,9 +4309,6 @@ function methods(db, className) {
4403
4309
  name: leafName(definition.symbol)
4404
4310
  }));
4405
4311
  }
4406
- function isCallableSymbol(rawSymbol) {
4407
- return rawSymbol.endsWith("().") || leafSuffix(rawSymbol) === "method";
4408
- }
4409
4312
  function stripExtension(relativePath) {
4410
4313
  return relativePath.replace(/\.[^.]+$/, "");
4411
4314
  }
@@ -4413,60 +4316,21 @@ function stripExtension(relativePath) {
4413
4316
  // src/queries/refs.ts
4414
4317
  function refs(db, symbolPattern) {
4415
4318
  const match = findFirstSymbolMatch(db, symbolPattern);
4416
- if (match) {
4417
- const includeDefinitionSite = !isFunctionLikeSymbol(match.symbol);
4418
- const definitionRows = includeDefinitionSite ? [{
4419
- relativePath: match.relativePath,
4420
- line: match.startLine
4421
- }] : [];
4422
- const sourceSites = getSourceReferenceSites(db, match).filter((site) => !db.isIgnored(site.file)).map((site) => ({
4423
- relativePath: site.file,
4424
- line: site.line
4425
- }));
4426
- if (sourceSites.length > 0) {
4427
- const seen2 = /* @__PURE__ */ new Set();
4428
- const rows2 = [...definitionRows, ...sourceSites, ...getRubySemanticRefs(db, match)].filter((site) => {
4429
- const key = `${site.relativePath}:${site.line}`;
4430
- if (seen2.has(key)) return false;
4431
- seen2.add(key);
4432
- return true;
4433
- });
4434
- return rows2;
4435
- }
4436
- }
4437
- const rows = db.all(
4438
- `SELECT DISTINCT d.relative_path, c.start_line
4439
- FROM mentions m
4440
- JOIN chunks c ON m.chunk_id = c.id
4441
- JOIN documents d ON c.document_id = d.id
4442
- JOIN global_symbols gs ON m.symbol_id = gs.id
4443
- WHERE m.symbol_id = ?
4444
- AND ${db.localSymbolPredicate}
4445
- AND m.role != 1
4446
- ORDER BY d.relative_path, c.start_line`,
4447
- match?.symbolId ?? -1
4448
- );
4449
- const referenceRows = rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
4450
- relativePath: r.relative_path,
4451
- line: r.start_line
4452
- }));
4453
- if (!match || db.isIgnored(match.relativePath) || isFunctionLikeSymbol(match.symbol)) {
4454
- return referenceRows;
4455
- }
4456
- const seen = new Set(referenceRows.map((row) => `${row.relativePath}:${row.line}`));
4457
- if (!seen.has(`${match.relativePath}:${match.startLine}`)) {
4458
- referenceRows.unshift({
4459
- relativePath: match.relativePath,
4460
- line: match.startLine
4461
- });
4462
- }
4463
- for (const row of getRubySemanticRefs(db, match)) {
4319
+ if (!match) return [];
4320
+ const includeDefinitionSite = !isFunctionLikeSymbol(match.symbol);
4321
+ const definitionRows = includeDefinitionSite && !db.isIgnored(match.relativePath) ? [{ relativePath: match.relativePath, line: match.startLine }] : [];
4322
+ const sourceSites = getSourceReferenceSites(db, match);
4323
+ const referenceSites = (sourceSites.length > 0 ? sourceSites : getResolvedReferenceSites(db, match)).filter((site) => !db.isIgnored(site.file)).map((site) => ({ relativePath: site.file, line: site.line }));
4324
+ const rubySites = getRubySemanticRefs(db, match);
4325
+ const seen = /* @__PURE__ */ new Set();
4326
+ const out = [];
4327
+ for (const row of [...definitionRows, ...referenceSites, ...rubySites]) {
4464
4328
  const key = `${row.relativePath}:${row.line}`;
4465
4329
  if (seen.has(key)) continue;
4466
4330
  seen.add(key);
4467
- referenceRows.push(row);
4331
+ out.push(row);
4468
4332
  }
4469
- return referenceRows;
4333
+ return out;
4470
4334
  }
4471
4335
  function getRubySemanticRefs(db, match) {
4472
4336
  if (!match.relativePath.endsWith(".rb")) {
@@ -4519,50 +4383,24 @@ function trace(db, symbolPattern) {
4519
4383
  return { definitions: [], referencedBy: [] };
4520
4384
  }
4521
4385
  const definitionMeta = db.get(
4522
- `SELECT
4523
- gs.display_name,
4524
- REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
4525
- FROM global_symbols gs
4526
- WHERE gs.id = ?
4527
- LIMIT 10`,
4386
+ "SELECT display_name, documentation FROM global_symbols WHERE id = ?",
4528
4387
  match.symbolId
4529
4388
  );
4389
+ const sig = extractSignature(definitionMeta?.documentation ?? null);
4530
4390
  const definitions = db.isIgnored(match.relativePath) ? [] : [{
4531
4391
  relativePath: match.relativePath,
4532
4392
  startLine: match.startLine,
4533
4393
  endLine: match.endLine,
4534
- signature: buildTraceSignature(definitionMeta?.sig ?? null, definitionMeta?.display_name ?? null, match.symbol),
4394
+ signature: buildTraceSignature(sig, definitionMeta?.display_name ?? null, match.symbol),
4535
4395
  source: definitionSource(db, match.relativePath, match.startLine, match.endLine)
4536
4396
  }];
4537
4397
  const sourceSites = getSourceReferenceSites(db, match);
4538
- const referencedBy = sourceSites.length > 0 ? sourceSites.filter((site) => !db.isIgnored(site.file)).map((site) => ({
4398
+ const resolvedSites = sourceSites.length > 0 ? sourceSites : getResolvedReferenceSites(db, match);
4399
+ const referencedBy = resolvedSites.filter((site) => !db.isIgnored(site.file)).map((site) => ({
4539
4400
  relativePath: site.file,
4540
4401
  line: site.line,
4541
4402
  enclosingSymbol: site.enclosingSymbol,
4542
4403
  enclosingShort: site.enclosingSymbol ? shortenSymbol(site.enclosingSymbol) : "(top-level)"
4543
- })) : db.all(
4544
- `SELECT DISTINCT d.relative_path, c.start_line AS line,
4545
- (SELECT enc_gs.symbol
4546
- FROM defn_enclosing_ranges enc_der
4547
- JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
4548
- WHERE enc_der.document_id = d.id
4549
- AND enc_der.start_line <= c.start_line
4550
- AND enc_der.end_line >= c.end_line
4551
- ORDER BY (enc_der.end_line - enc_der.start_line) ASC
4552
- LIMIT 1
4553
- ) AS enclosing_symbol
4554
- FROM mentions m
4555
- JOIN chunks c ON m.chunk_id = c.id
4556
- JOIN documents d ON c.document_id = d.id
4557
- WHERE m.symbol_id = ?
4558
- AND m.role != 1
4559
- ORDER BY d.relative_path, c.start_line`,
4560
- match.symbolId
4561
- ).filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
4562
- relativePath: r.relative_path,
4563
- line: r.line,
4564
- enclosingSymbol: r.enclosing_symbol,
4565
- enclosingShort: r.enclosing_symbol ? shortenSymbol(r.enclosing_symbol) : "(top-level)"
4566
4404
  }));
4567
4405
  return { definitions, referencedBy };
4568
4406
  }
@@ -4648,26 +4486,18 @@ function system(db, modulePattern) {
4648
4486
  ...matchedPaths
4649
4487
  );
4650
4488
  const files2 = fileRows.map((r) => r.relative_path).filter((p) => !db.isIgnored(p));
4651
- const symbolRows = db.all(
4652
- `SELECT der.start_line, der.end_line, gs.symbol,
4653
- REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
4654
- FROM defn_enclosing_ranges der
4655
- JOIN global_symbols gs ON der.symbol_id = gs.id
4656
- JOIN documents d ON der.document_id = d.id
4657
- WHERE d.relative_path IN (${placeholders})
4658
- AND ${db.localSymbolPredicate}
4659
- ${db.symbolNoise}
4660
- AND gs.documentation IS NOT NULL
4661
- ORDER BY d.relative_path, der.start_line`,
4662
- ...matchedPaths
4663
- );
4664
- const symbols2 = symbolRows.map((r) => ({
4665
- startLine: r.start_line,
4666
- endLine: r.end_line,
4667
- symbol: r.symbol,
4668
- shortName: shortenSymbol(r.symbol),
4669
- signature: cleanSignature(r.sig)
4670
- }));
4489
+ const symbols2 = files2.flatMap((relativePath) => getDefinitionsForFile(db, relativePath)).filter((d) => d.documentation !== null && d.documentation !== "").sort(
4490
+ (a, b) => a.relativePath.localeCompare(b.relativePath) || a.startLine - b.startLine || a.endLine - b.endLine
4491
+ ).map((d) => {
4492
+ const sig = extractSignature(d.documentation);
4493
+ return {
4494
+ startLine: d.startLine,
4495
+ endLine: d.endLine,
4496
+ symbol: d.symbol,
4497
+ shortName: shortenSymbol(d.symbol),
4498
+ signature: cleanSignature(sig)
4499
+ };
4500
+ });
4671
4501
  const depRows = db.all(
4672
4502
  `SELECT DISTINCT d2.relative_path
4673
4503
  FROM mentions m
@@ -4743,7 +4573,7 @@ function surface(db, modulePattern) {
4743
4573
  ...matchedPaths
4744
4574
  );
4745
4575
  const exposedDefinitions = matchedPaths.flatMap(
4746
- (relativePath) => getDefinitionsForFile(db, relativePath).filter((definition) => isCallableSymbol2(definition.symbol)).map((definition) => ({
4576
+ (relativePath) => getDefinitionsForFile(db, relativePath).filter((definition) => isCallableSymbol(definition.symbol)).map((definition) => ({
4747
4577
  relative_path: relativePath,
4748
4578
  symbol: definition.symbol
4749
4579
  }))
@@ -4760,9 +4590,6 @@ function surface(db, modulePattern) {
4760
4590
  shortName: shortenSymbol(r.symbol)
4761
4591
  }));
4762
4592
  }
4763
- function isCallableSymbol2(rawSymbol) {
4764
- return rawSymbol.endsWith("().") || leafSuffix(rawSymbol) === "method";
4765
- }
4766
4593
 
4767
4594
  // src/entry-surfaces.ts
4768
4595
  var liveBarrelCache = /* @__PURE__ */ new WeakMap();
@@ -5137,33 +4964,40 @@ function outline(db, filePattern) {
5137
4964
  if (resolvedPaths.length === 0) {
5138
4965
  return [];
5139
4966
  }
5140
- const placeholders = resolvedPaths.map(() => "?").join(", ");
5141
- const rows = db.all(
5142
- `SELECT gs.symbol, gs.enclosing_symbol, der.start_line, der.end_line
5143
- FROM defn_enclosing_ranges der
5144
- JOIN global_symbols gs ON der.symbol_id = gs.id
5145
- JOIN documents d ON der.document_id = d.id
5146
- WHERE d.relative_path IN (${placeholders})
5147
- ${db.symbolNoise}
5148
- ORDER BY d.relative_path, der.start_line`,
5149
- ...resolvedPaths
4967
+ const definitions = resolvedPaths.flatMap((relativePath) => getDefinitionsForFile(db, relativePath)).filter((d) => !db.isIgnored(d.relativePath)).sort(
4968
+ (a, b) => a.relativePath.localeCompare(b.relativePath) || a.startLine - b.startLine || a.endLine - b.endLine
5150
4969
  );
4970
+ const nodes = definitions.map((d) => ({
4971
+ symbol: d.symbol,
4972
+ shortName: shortenSymbol(d.symbol),
4973
+ startLine: d.startLine,
4974
+ endLine: d.endLine,
4975
+ children: []
4976
+ }));
5151
4977
  const nodeMap = /* @__PURE__ */ new Map();
4978
+ for (const n of nodes) nodeMap.set(n.symbol, n);
5152
4979
  const roots = [];
5153
- for (const r of rows) {
5154
- const node = {
5155
- symbol: r.symbol,
5156
- shortName: shortenSymbol(r.symbol),
5157
- startLine: r.start_line,
5158
- endLine: r.end_line,
5159
- children: []
5160
- };
5161
- nodeMap.set(r.symbol, node);
5162
- }
5163
- for (const r of rows) {
5164
- const node = nodeMap.get(r.symbol);
5165
- if (r.enclosing_symbol && nodeMap.has(r.enclosing_symbol)) {
5166
- nodeMap.get(r.enclosing_symbol).children.push(node);
4980
+ for (let i = 0; i < definitions.length; i++) {
4981
+ const d = definitions[i];
4982
+ const node = nodes[i];
4983
+ if (d.enclosingSymbol && nodeMap.has(d.enclosingSymbol)) {
4984
+ nodeMap.get(d.enclosingSymbol).children.push(node);
4985
+ continue;
4986
+ }
4987
+ let bestParent = null;
4988
+ let bestSize = Infinity;
4989
+ for (const candidate of nodes) {
4990
+ if (candidate === node) continue;
4991
+ if (candidate.startLine <= node.startLine && candidate.endLine >= node.endLine) {
4992
+ const size = candidate.endLine - candidate.startLine;
4993
+ if (size < bestSize) {
4994
+ bestSize = size;
4995
+ bestParent = candidate;
4996
+ }
4997
+ }
4998
+ }
4999
+ if (bestParent) {
5000
+ bestParent.children.push(node);
5167
5001
  } else {
5168
5002
  roots.push(node);
5169
5003
  }
@@ -5175,23 +5009,12 @@ function outline(db, filePattern) {
5175
5009
  function members(db, symbolPattern) {
5176
5010
  const parent = findFirstSymbolMatch(db, symbolPattern);
5177
5011
  if (!parent) return [];
5178
- const rows = db.all(
5179
- `SELECT gs.symbol, der.start_line, der.end_line
5180
- FROM global_symbols gs
5181
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
5182
- WHERE der.document_id = ?
5183
- AND gs.symbol != ?
5184
- ${db.symbolNoiseFor("gs")}
5185
- ORDER BY der.start_line`,
5186
- parent.documentId,
5187
- parent.symbol
5188
- );
5189
- return rows.filter((row) => isDirectChildSymbol(parent.symbol, row.symbol)).map((row) => ({
5190
- symbol: row.symbol,
5191
- shortName: shortenSymbol(row.symbol),
5192
- startLine: row.start_line,
5193
- endLine: row.end_line,
5194
- kind: leafSuffix(row.symbol) ?? "unknown"
5012
+ return getDefinitionsForFile(db, parent.relativePath).filter((definition) => definition.symbol !== parent.symbol).filter((definition) => isDirectChildSymbol(parent.symbol, definition.symbol)).sort((a, b) => a.startLine - b.startLine || a.endLine - b.endLine).map((definition) => ({
5013
+ symbol: definition.symbol,
5014
+ shortName: shortenSymbol(definition.symbol),
5015
+ startLine: definition.startLine,
5016
+ endLine: definition.endLine,
5017
+ kind: leafSuffix(definition.symbol) ?? "unknown"
5195
5018
  }));
5196
5019
  }
5197
5020
 
@@ -5885,7 +5708,7 @@ function similar(db, symbolPattern, opts = {}) {
5885
5708
  return similarBySourceShape(db, symbolPattern, { minSimilarity, limit });
5886
5709
  }
5887
5710
  function similarAll(db, opts = {}) {
5888
- const { minSimilarity = 0.5, limit = 20, scope, minCallees = 4 } = opts;
5711
+ const { minSimilarity = 0.5, limit = 20, scope, minCallees = 4, crossFileOnly = false } = opts;
5889
5712
  const all = getAllCalleeFingerprints(db, { minCallees, scope });
5890
5713
  const idfWeights = computeIdf(all);
5891
5714
  const results = [];
@@ -5893,14 +5716,16 @@ function similarAll(db, opts = {}) {
5893
5716
  for (let j = i + 1; j < all.length; j++) {
5894
5717
  const a = all[i];
5895
5718
  const b = all[j];
5896
- if (a.file === b.file) continue;
5719
+ if (crossFileOnly && a.file === b.file) continue;
5897
5720
  const { similarity, significantShared } = weightedSimilarity(
5898
5721
  a.callees,
5899
5722
  b.callees,
5900
5723
  idfWeights
5901
5724
  );
5902
5725
  if (similarity < minSimilarity) continue;
5903
- if (significantShared.length < 2) continue;
5726
+ const sharedCount = intersection(a.callees, b.callees).size;
5727
+ if (significantShared.length < 2 && sharedCount < 4) continue;
5728
+ const displayShared = significantShared.length > 0 ? significantShared : [...intersection(a.callees, b.callees)];
5904
5729
  results.push({
5905
5730
  symbolA: a.symbol,
5906
5731
  shortNameA: shortenSymbol(a.symbol),
@@ -5909,7 +5734,7 @@ function similarAll(db, opts = {}) {
5909
5734
  shortNameB: shortenSymbol(b.symbol),
5910
5735
  fileB: b.file,
5911
5736
  similarity,
5912
- sharedCallees: significantShared.map(shortenSymbol),
5737
+ sharedCallees: displayShared.map(shortenSymbol),
5913
5738
  uniqueToA: [...difference(a.callees, b.callees)].map(shortenSymbol),
5914
5739
  uniqueToB: [...difference(b.callees, a.callees)].map(shortenSymbol)
5915
5740
  });
@@ -5983,35 +5808,21 @@ function findCallees(db, symbolPattern) {
5983
5808
  }
5984
5809
  function getAllCalleeFingerprints(db, opts) {
5985
5810
  const { minCallees, scope, excludeSymbol } = opts;
5986
- const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
5987
- const excludeFilter = excludeSymbol ? `AND gs.symbol != '${excludeSymbol.replace(/'/g, "''")}'` : "";
5988
- const symbols2 = db.all(
5989
- `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
5990
- FROM global_symbols gs
5991
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
5992
- JOIN documents d ON der.document_id = d.id
5993
- WHERE 1 = 1
5994
- ${db.pathExclusionsFor("d")}
5995
- ${db.symbolNoiseFor("gs")}
5996
- AND (der.end_line - der.start_line + 1) >= 5
5997
- ${scopeFilter}
5998
- ${excludeFilter}
5999
- ORDER BY d.relative_path`
6000
- );
6001
5811
  const fingerprints = [];
6002
- for (const sym of symbols2) {
6003
- if (db.isIgnored(sym.relative_path)) continue;
6004
- if (!isFunctionLikeSymbol(sym.symbol)) continue;
6005
- const calleeRows = getCalleeRowsForSymbol(db, {
6006
- documentId: sym.document_id,
6007
- startLine: sym.start_line,
6008
- endLine: sym.end_line,
6009
- symbolId: sym.id
5812
+ for (const definition of getAllDefinitions(db, { scope })) {
5813
+ if (db.isIgnored(definition.relativePath)) continue;
5814
+ if (!definition.isFunctionLike) continue;
5815
+ if (excludeSymbol && definition.symbol === excludeSymbol) continue;
5816
+ if (definition.endLine - definition.startLine + 1 < 5) continue;
5817
+ const callees = new Set(
5818
+ getCalleeRowsForSymbol(db, definition).map((row) => row.symbol)
5819
+ );
5820
+ if (callees.size < minCallees) continue;
5821
+ fingerprints.push({
5822
+ symbol: definition.symbol,
5823
+ file: definition.relativePath,
5824
+ callees
6010
5825
  });
6011
- const callees = new Set(calleeRows.map((r) => r.symbol));
6012
- if (callees.size >= minCallees) {
6013
- fingerprints.push({ symbol: sym.symbol, file: sym.relative_path, callees });
6014
- }
6015
5826
  }
6016
5827
  return fingerprints;
6017
5828
  }
@@ -6539,17 +6350,6 @@ function extractCandidates(db, opts = {}) {
6539
6350
  results.sort((a, b) => b.clusters.length - a.clusters.length || b.loc - a.loc);
6540
6351
  return results.slice(0, limit);
6541
6352
  }
6542
- function getScopedDefinitions(db, scope) {
6543
- const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
6544
- return db.all(
6545
- `SELECT relative_path
6546
- FROM documents
6547
- WHERE 1 = 1
6548
- ${db.pathExclusionsFor("documents")}
6549
- ${scopeFilter}
6550
- ORDER BY relative_path`
6551
- ).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
6552
- }
6553
6353
  function definitionLoc(definition) {
6554
6354
  return definition.endLine - definition.startLine + 1;
6555
6355
  }
@@ -6640,18 +6440,10 @@ function changeSurface(db, filePattern) {
6640
6440
  resolvedFile
6641
6441
  );
6642
6442
  if (!doc || db.isIgnored(doc.relative_path)) return null;
6643
- const syms = db.all(
6644
- `SELECT DISTINCT gs.id AS symbol_id, gs.symbol, der.start_line, der.end_line
6645
- FROM defn_enclosing_ranges der
6646
- JOIN global_symbols gs ON der.symbol_id = gs.id
6647
- WHERE der.document_id = ?
6648
- ${db.symbolNoiseFor("gs")}
6649
- ORDER BY der.start_line`,
6650
- doc.id
6651
- );
6443
+ const definitions = getDefinitionsForFile(db, doc.relative_path).sort((a, b) => a.startLine - b.startLine || a.endLine - b.endLine);
6652
6444
  const symbols2 = [];
6653
6445
  let totalExternalConsumers = 0;
6654
- for (const sym of syms) {
6446
+ for (const def of definitions) {
6655
6447
  const consumerRow = db.get(
6656
6448
  `SELECT COUNT(DISTINCT c.document_id) AS consumer_count
6657
6449
  FROM mentions m
@@ -6659,7 +6451,7 @@ function changeSurface(db, filePattern) {
6659
6451
  WHERE m.symbol_id = ?
6660
6452
  AND m.role != 1
6661
6453
  AND c.document_id != ?`,
6662
- sym.symbol_id,
6454
+ def.symbolId,
6663
6455
  doc.id
6664
6456
  );
6665
6457
  const externalConsumers = consumerRow?.consumer_count ?? 0;
@@ -6673,10 +6465,10 @@ function changeSurface(db, filePattern) {
6673
6465
  }
6674
6466
  totalExternalConsumers += externalConsumers;
6675
6467
  symbols2.push({
6676
- symbol: sym.symbol,
6677
- shortName: shortenSymbol(sym.symbol),
6678
- startLine: sym.start_line,
6679
- endLine: sym.end_line,
6468
+ symbol: def.symbol,
6469
+ shortName: shortenSymbol(def.symbol),
6470
+ startLine: def.startLine,
6471
+ endLine: def.endLine,
6680
6472
  externalConsumers,
6681
6473
  riskLevel
6682
6474
  });
@@ -6993,7 +6785,7 @@ import { basename as basename5, extname as extname4 } from "path";
6993
6785
  function wrapperCandidates(db, opts) {
6994
6786
  const { scope, maxLoc = 15, limit = 30 } = opts ?? {};
6995
6787
  const reverseFanIn = buildReverseFileFanIn(buildFileDepGraph(db, scope));
6996
- const symbols2 = getScopedDefinitions2(db, scope).filter((definition) => definitionLoc2(definition) <= maxLoc && definitionLoc2(definition) >= 2);
6788
+ const symbols2 = getScopedDefinitions(db, scope).filter((definition) => definitionLoc2(definition) <= maxLoc && definitionLoc2(definition) >= 2);
6997
6789
  const results = [];
6998
6790
  for (const symbol of symbols2) {
6999
6791
  if (db.isIgnored(symbol.relativePath) || !isFunctionLikeSymbol(symbol.symbol)) continue;
@@ -7032,17 +6824,6 @@ function wrapperCandidates(db, opts) {
7032
6824
  function definitionLoc2(definition) {
7033
6825
  return definition.endLine - definition.startLine + 1;
7034
6826
  }
7035
- function getScopedDefinitions2(db, scope) {
7036
- const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
7037
- return db.all(
7038
- `SELECT relative_path
7039
- FROM documents
7040
- WHERE 1 = 1
7041
- ${db.pathExclusionsFor("documents")}
7042
- ${scopeFilter}
7043
- ORDER BY relative_path`
7044
- ).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
7045
- }
7046
6827
  function dedupeRows(rows) {
7047
6828
  const seen = /* @__PURE__ */ new Set();
7048
6829
  const unique = [];
@@ -7092,7 +6873,7 @@ function fallbackCallerFanIn(db, reverseFanIn, callerFile) {
7092
6873
  // src/queries/passthrough-candidates.ts
7093
6874
  function passthroughCandidates(db, opts) {
7094
6875
  const { scope, maxLoc = 15, limit = 30 } = opts ?? {};
7095
- const symbols2 = getScopedDefinitions3(db, scope).filter((definition) => definitionLoc3(definition) >= 3 && definitionLoc3(definition) <= maxLoc);
6876
+ const symbols2 = getScopedDefinitions(db, scope).filter((definition) => definitionLoc3(definition) >= 3 && definitionLoc3(definition) <= maxLoc);
7096
6877
  const results = [];
7097
6878
  for (const sym of symbols2) {
7098
6879
  if (db.isIgnored(sym.relativePath) || !isFunctionLikeSymbol(sym.symbol)) continue;
@@ -7121,17 +6902,6 @@ function passthroughCandidates(db, opts) {
7121
6902
  results.sort((a, b) => a.loc - b.loc || a.file.localeCompare(b.file));
7122
6903
  return results.slice(0, limit);
7123
6904
  }
7124
- function getScopedDefinitions3(db, scope) {
7125
- const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
7126
- return db.all(
7127
- `SELECT relative_path
7128
- FROM documents
7129
- WHERE 1 = 1
7130
- ${db.pathExclusionsFor("documents")}
7131
- ${scopeFilter}
7132
- ORDER BY relative_path`
7133
- ).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
7134
- }
7135
6905
  function definitionLoc3(definition) {
7136
6906
  return definition.endLine - definition.startLine + 1;
7137
6907
  }
@@ -7139,7 +6909,7 @@ function definitionLoc3(definition) {
7139
6909
  // src/queries/stale-abstractions.ts
7140
6910
  function staleAbstractions(db, opts) {
7141
6911
  const { scope, minLoc = 3, limit = 30 } = opts ?? {};
7142
- const rows = getScopedDefinitions4(db, scope).filter((definition) => definition.isTypeLike && definitionLoc4(definition) >= minLoc).map((definition) => ({
6912
+ const rows = getScopedDefinitions(db, scope).filter((definition) => definition.isTypeLike && definitionLoc4(definition) >= minLoc).map((definition) => ({
7143
6913
  symbol: definition.symbol,
7144
6914
  file: definition.relativePath,
7145
6915
  start_line: definition.startLine,
@@ -7159,7 +6929,7 @@ function staleAbstractions(db, opts) {
7159
6929
  })).slice(0, limit);
7160
6930
  }
7161
6931
  function getFilesWithFunctions(db, scope) {
7162
- return new Set(getScopedDefinitions4(db, scope).filter((definition) => definition.isFunctionLike).map((definition) => definition.relativePath));
6932
+ return new Set(getScopedDefinitions(db, scope).filter((definition) => definition.isFunctionLike).map((definition) => definition.relativePath));
7163
6933
  }
7164
6934
  function isTrueStaleAbstraction(row, filesWithFunctions) {
7165
6935
  const basename6 = row.file.split("/").pop() ?? "";
@@ -7172,17 +6942,6 @@ function isTrueStaleAbstraction(row, filesWithFunctions) {
7172
6942
  }
7173
6943
  return true;
7174
6944
  }
7175
- function getScopedDefinitions4(db, scope) {
7176
- const scopeFilter = scope ? `AND relative_path LIKE '%${scope}%'` : "";
7177
- return db.all(
7178
- `SELECT relative_path
7179
- FROM documents
7180
- WHERE 1 = 1
7181
- ${db.pathExclusionsFor("documents")}
7182
- ${scopeFilter}
7183
- ORDER BY relative_path`
7184
- ).flatMap((row) => getDefinitionsForFile(db, row.relative_path)).filter((row) => !db.isIgnored(row.relativePath));
7185
- }
7186
6945
  function countCrossFileConsumers(db, definition) {
7187
6946
  const callers = db.all(
7188
6947
  `SELECT DISTINCT d.relative_path
@@ -7203,95 +6962,6 @@ function definitionLoc4(definition) {
7203
6962
  // src/queries/complexity-hotspots.ts
7204
6963
  function complexityHotspots(db, opts) {
7205
6964
  const { scope, minLoc = 10, limit = 30 } = opts ?? {};
7206
- const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
7207
- const rows = db.all(
7208
- `SELECT
7209
- gs.symbol,
7210
- d.relative_path AS file,
7211
- der.start_line,
7212
- der.end_line,
7213
- (der.end_line - der.start_line + 1) AS loc,
7214
- -- fanIn: distinct files that reference this symbol
7215
- (SELECT COUNT(DISTINCT ref_c.document_id)
7216
- FROM mentions ref_m
7217
- JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
7218
- WHERE ref_m.symbol_id = gs.id AND ref_m.role != 1
7219
- ) AS fan_in,
7220
- -- fanOut: distinct symbols referenced within this definition range
7221
- -- that are defined in different files
7222
- (SELECT COUNT(DISTINCT out_gs.id)
7223
- FROM mentions out_m
7224
- JOIN chunks out_c ON out_m.chunk_id = out_c.id
7225
- JOIN global_symbols out_gs ON out_m.symbol_id = out_gs.id
7226
- JOIN defn_enclosing_ranges out_der ON out_gs.id = out_der.symbol_id
7227
- WHERE out_c.document_id = der.document_id
7228
- AND out_c.start_line >= der.start_line
7229
- AND out_c.end_line <= der.end_line
7230
- AND out_m.role != 1
7231
- AND out_gs.id != gs.id
7232
- AND out_der.document_id != der.document_id
7233
- ) AS fan_out,
7234
- -- calleeCount: total distinct callees within definition range
7235
- (SELECT COUNT(DISTINCT callee_gs.id)
7236
- FROM mentions callee_m
7237
- JOIN chunks callee_c ON callee_m.chunk_id = callee_c.id
7238
- JOIN global_symbols callee_gs ON callee_m.symbol_id = callee_gs.id
7239
- WHERE callee_c.document_id = der.document_id
7240
- AND callee_c.start_line >= der.start_line
7241
- AND callee_c.end_line <= der.end_line
7242
- AND callee_m.role != 1
7243
- AND callee_gs.id != gs.id
7244
- ) AS callee_count
7245
- FROM global_symbols gs
7246
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
7247
- JOIN documents d ON der.document_id = d.id
7248
- WHERE 1 = 1
7249
- ${db.pathExclusionsFor("d")}
7250
- AND ${testFileExclusionSql("d")}
7251
- ${db.symbolNoiseFor("gs")}
7252
- AND (der.end_line - der.start_line + 1) >= ?
7253
- ${scopeFilter}
7254
- ORDER BY (
7255
- CAST((der.end_line - der.start_line + 1) AS REAL) / 50.0
7256
- * CAST((SELECT COUNT(DISTINCT ref_c2.document_id)
7257
- FROM mentions ref_m2
7258
- JOIN chunks ref_c2 ON ref_m2.chunk_id = ref_c2.id
7259
- WHERE ref_m2.symbol_id = gs.id AND ref_m2.role != 1
7260
- ) AS REAL) / 5.0
7261
- * MAX(CAST((SELECT COUNT(DISTINCT out_gs2.id)
7262
- FROM mentions out_m2
7263
- JOIN chunks out_c2 ON out_m2.chunk_id = out_c2.id
7264
- JOIN global_symbols out_gs2 ON out_m2.symbol_id = out_gs2.id
7265
- JOIN defn_enclosing_ranges out_der2 ON out_gs2.id = out_der2.symbol_id
7266
- WHERE out_c2.document_id = der.document_id
7267
- AND out_c2.start_line >= der.start_line
7268
- AND out_c2.end_line <= der.end_line
7269
- AND out_m2.role != 1
7270
- AND out_gs2.id != gs.id
7271
- AND out_der2.document_id != der.document_id
7272
- ) AS REAL) / 5.0, 1.0)
7273
- ) DESC
7274
- LIMIT ?`,
7275
- minLoc,
7276
- limit
7277
- );
7278
- const indexedResults = rows.filter((r) => !db.isIgnored(r.file)).map((r) => ({
7279
- symbol: r.symbol,
7280
- shortName: shortenSymbol(r.symbol),
7281
- file: r.file,
7282
- startLine: r.start_line,
7283
- endLine: r.end_line,
7284
- loc: r.loc,
7285
- fanIn: r.fan_in,
7286
- fanOut: r.fan_out,
7287
- calleeCount: r.callee_count,
7288
- score: Math.round(
7289
- r.loc / 50 * (r.fan_in / 5) * Math.max(r.fan_out / 5, 1) * 100
7290
- ) / 100
7291
- }));
7292
- if (indexedResults.length > 0) {
7293
- return indexedResults;
7294
- }
7295
6965
  return getAllDefinitions(db, { scope }).filter((definition) => !db.isIgnored(definition.relativePath)).map((definition) => {
7296
6966
  const loc = definition.endLine - definition.startLine + 1;
7297
6967
  const callerRows = getCallerRowsForSymbol(db, definition, { limit: 500 });
@@ -7590,9 +7260,11 @@ function code(db, symbolPattern, opts = {}) {
7590
7260
  symbol: match.symbol,
7591
7261
  shortName: shortenSymbol(match.symbol),
7592
7262
  relativePath: match.relativePath,
7593
- startLine: startLine + 1,
7594
- // 1-indexed for display
7595
- endLine: endLine + 1,
7263
+ // 0-indexed, like every other query result. The CLI's displayLine()
7264
+ // converts once at render time. Returning 1-indexed here caused a
7265
+ // double-conversion in the CLI and printed labels off by +1.
7266
+ startLine,
7267
+ endLine,
7596
7268
  language: doc?.language ?? null,
7597
7269
  source
7598
7270
  };
@@ -7620,8 +7292,8 @@ function readFileRange(db, filePath, startLine, endLine, context) {
7620
7292
  symbol: `${doc.relative_path}:${startLine}-${endLine}`,
7621
7293
  shortName: `${doc.relative_path}:${startLine}-${endLine}`,
7622
7294
  relativePath: doc.relative_path,
7623
- startLine: start + 1,
7624
- endLine: end + 1,
7295
+ startLine: start,
7296
+ endLine: end,
7625
7297
  language: doc.language,
7626
7298
  source
7627
7299
  };
@@ -7734,34 +7406,12 @@ function dataflow(db, symbolPattern) {
7734
7406
  line: match.startLine
7735
7407
  }];
7736
7408
  const sourceUsageSites = getSourceReferenceSites(db, match);
7737
- const usageSites = sourceUsageSites.length > 0 ? sourceUsageSites.map((site) => ({
7409
+ const resolvedSites = sourceUsageSites.length > 0 ? sourceUsageSites : getResolvedReferenceSites(db, match);
7410
+ const normalizedUsageSites = resolvedSites.filter((site) => !db.isIgnored(site.file)).map((site) => ({
7738
7411
  file: site.file,
7739
7412
  line: site.line,
7740
- enclosing_symbol: site.enclosingSymbol
7741
- })) : db.all(
7742
- `SELECT d.relative_path AS file, c.start_line AS line,
7743
- (SELECT enc_gs.symbol
7744
- FROM defn_enclosing_ranges enc_der
7745
- JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
7746
- WHERE enc_der.document_id = d.id
7747
- AND enc_der.start_line <= c.start_line
7748
- AND enc_der.end_line >= c.end_line
7749
- ORDER BY (enc_der.end_line - enc_der.start_line) ASC
7750
- LIMIT 1
7751
- ) AS enclosing_symbol
7752
- FROM mentions m
7753
- JOIN chunks c ON m.chunk_id = c.id
7754
- JOIN documents d ON c.document_id = d.id
7755
- WHERE m.symbol_id = ? AND m.role != 1
7756
- ${db.pathExclusionsFor("d")}
7757
- ORDER BY d.relative_path, c.start_line`,
7758
- match.symbolId
7759
- );
7760
- const normalizedUsageSites = usageSites.filter((site) => !db.isIgnored(site.file)).map((site) => ({
7761
- file: site.file,
7762
- line: site.line,
7763
- enclosingSymbol: site.enclosing_symbol ?? "(top-level)",
7764
- enclosingShort: site.enclosing_symbol ? shortenSymbol(site.enclosing_symbol) : "(top-level)"
7413
+ enclosingSymbol: site.enclosingSymbol ?? "(top-level)",
7414
+ enclosingShort: site.enclosingSymbol ? shortenSymbol(site.enclosingSymbol) : "(top-level)"
7765
7415
  }));
7766
7416
  const producers = uniqueSymbolRows(getCalleeRowsForSymbol(db, match, { limit: 30 }).map((row) => ({
7767
7417
  symbol: row.symbol,
@@ -7801,28 +7451,40 @@ function uniqueSymbolRows(rows) {
7801
7451
 
7802
7452
  // src/queries/slice.ts
7803
7453
  function slice(db, symbolPattern, opts = {}) {
7804
- const { direction = "backward" } = opts;
7454
+ const { direction = "backward", maxDepth = 3 } = opts;
7805
7455
  const match = findFirstSymbolMatch(db, symbolPattern);
7806
7456
  if (!match) return null;
7807
7457
  if (direction === "backward") {
7808
- return backwardSlice(db, match);
7458
+ return backwardSlice(db, match, maxDepth);
7809
7459
  } else {
7810
7460
  return forwardSlice(db, match);
7811
7461
  }
7812
7462
  }
7813
- function backwardSlice(db, match) {
7814
- const callees = getCalleeRowsForSymbol(db, match);
7815
- const seen = /* @__PURE__ */ new Set();
7463
+ function backwardSlice(db, match, maxDepth) {
7816
7464
  const connected = [];
7817
- for (const c of callees) {
7818
- if (seen.has(c.symbol)) continue;
7819
- seen.add(c.symbol);
7820
- connected.push({
7821
- symbol: c.symbol,
7822
- shortName: shortenSymbol(c.symbol),
7823
- file: c.file,
7824
- relationship: "referenced within definition (callee)"
7825
- });
7465
+ const visited = /* @__PURE__ */ new Set([match.symbol]);
7466
+ let frontier = [match];
7467
+ for (let depth = 1; depth <= maxDepth; depth++) {
7468
+ if (frontier.length === 0) break;
7469
+ const nextFrontier = [];
7470
+ for (const current of frontier) {
7471
+ const callees = getCalleeRowsForSymbol(db, current);
7472
+ for (const c of callees) {
7473
+ if (visited.has(c.symbol)) continue;
7474
+ visited.add(c.symbol);
7475
+ connected.push({
7476
+ symbol: c.symbol,
7477
+ shortName: shortenSymbol(c.symbol),
7478
+ file: c.file,
7479
+ relationship: depth === 1 ? "referenced within definition (callee)" : `depth ${depth} callee`
7480
+ });
7481
+ const calleeMatch = findExactSymbolMatch(db, c.symbol);
7482
+ if (calleeMatch && !db.isIgnored(calleeMatch.relativePath)) {
7483
+ nextFrontier.push(calleeMatch);
7484
+ }
7485
+ }
7486
+ }
7487
+ frontier = nextFrontier;
7826
7488
  }
7827
7489
  return {
7828
7490
  symbol: match.symbol,
@@ -7832,53 +7494,37 @@ function backwardSlice(db, match) {
7832
7494
  };
7833
7495
  }
7834
7496
  function forwardSlice(db, match) {
7835
- const rows = db.all(
7836
- `SELECT DISTINCT
7837
- enc_gs.symbol AS enclosing_symbol,
7838
- enc_d.relative_path AS enclosing_file,
7839
- out_gs.symbol AS output_symbol,
7840
- out_d.relative_path AS output_file
7841
- FROM mentions ref_m
7842
- JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
7843
- JOIN documents ref_d ON ref_c.document_id = ref_d.id
7844
- -- Find enclosing function at each reference site
7845
- JOIN defn_enclosing_ranges enc_der
7846
- ON enc_der.document_id = ref_d.id
7847
- AND enc_der.start_line <= ref_c.start_line
7848
- AND enc_der.end_line >= ref_c.end_line
7849
- JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
7850
- JOIN documents enc_d ON enc_der.document_id = enc_d.id
7851
- -- Find other symbols referenced within that enclosing function
7852
- JOIN mentions out_m ON out_m.role != 1
7853
- JOIN chunks out_c ON out_m.chunk_id = out_c.id
7854
- AND out_c.document_id = enc_der.document_id
7855
- AND out_c.start_line >= enc_der.start_line
7856
- AND out_c.end_line <= enc_der.end_line
7857
- JOIN global_symbols out_gs ON out_m.symbol_id = out_gs.id
7858
- JOIN defn_enclosing_ranges out_der ON out_gs.id = out_der.symbol_id
7859
- JOIN documents out_d ON out_der.document_id = out_d.id
7860
- WHERE ref_m.symbol_id = ? AND ref_m.role != 1
7861
- AND out_gs.id != ? AND out_gs.id != enc_gs.id
7862
- AND out_d.id != ref_d.id
7863
- ${db.symbolNoiseFor("out_gs")}
7864
- ${db.pathExclusionsFor("out_d")}
7865
- ORDER BY out_d.relative_path
7866
- LIMIT 30`,
7867
- match.symbolId,
7868
- match.symbolId
7869
- );
7870
- const seen = /* @__PURE__ */ new Set();
7497
+ const sourceRefs = getSourceReferenceSites(db, match);
7498
+ const refs2 = sourceRefs.length > 0 ? sourceRefs : getResolvedReferenceSites(db, match);
7499
+ const seenOutputs = /* @__PURE__ */ new Set();
7871
7500
  const connected = [];
7872
- for (const r of rows) {
7873
- if (seen.has(r.output_symbol) || db.isIgnored(r.output_file)) continue;
7874
- seen.add(r.output_symbol);
7875
- connected.push({
7876
- symbol: r.output_symbol,
7877
- shortName: shortenSymbol(r.output_symbol),
7878
- file: r.output_file,
7879
- relationship: `used alongside target in ${shortenSymbol(r.enclosing_symbol)}`
7880
- });
7501
+ for (const ref of refs2) {
7502
+ if (connected.length >= 30) break;
7503
+ if (db.isIgnored(ref.file)) continue;
7504
+ const enclosingSymbol = ref.enclosingSymbol ?? findEnclosingDefinition(
7505
+ getDefinitionsForFile(db, ref.file),
7506
+ ref.line
7507
+ )?.symbol ?? null;
7508
+ if (!enclosingSymbol || enclosingSymbol === match.symbol) continue;
7509
+ const enclosingMatch = findExactSymbolMatch(db, enclosingSymbol);
7510
+ if (!enclosingMatch) continue;
7511
+ for (const callee of getCalleeRowsForSymbol(db, enclosingMatch)) {
7512
+ if (callee.symbol === match.symbol) continue;
7513
+ if (callee.symbol === enclosingSymbol) continue;
7514
+ if (callee.file === ref.file) continue;
7515
+ if (db.isIgnored(callee.file)) continue;
7516
+ if (seenOutputs.has(callee.symbol)) continue;
7517
+ seenOutputs.add(callee.symbol);
7518
+ connected.push({
7519
+ symbol: callee.symbol,
7520
+ shortName: shortenSymbol(callee.symbol),
7521
+ file: callee.file,
7522
+ relationship: `used alongside target in ${shortenSymbol(enclosingSymbol)}`
7523
+ });
7524
+ if (connected.length >= 30) break;
7525
+ }
7881
7526
  }
7527
+ connected.sort((a, b) => a.file.localeCompare(b.file));
7882
7528
  return {
7883
7529
  symbol: match.symbol,
7884
7530
  shortName: shortenSymbol(match.symbol),
@@ -8833,7 +8479,7 @@ program.command("call-graph <symbol>").description("Show incoming callers and ou
8833
8479
  }
8834
8480
  );
8835
8481
  });
8836
- 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) => {
8482
+ 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) => {
8837
8483
  withDb((db) => {
8838
8484
  if (symbol) {
8839
8485
  const results = queries.similar(db, symbol, {
@@ -8858,7 +8504,8 @@ ${Math.round(r.similarity * 100)}% similar:`);
8858
8504
  minSimilarity: opts.minSimilarity,
8859
8505
  limit: opts.limit,
8860
8506
  scope: opts.scope,
8861
- minCallees: opts.minCallees
8507
+ minCallees: opts.minCallees,
8508
+ crossFileOnly: opts.crossFileOnly
8862
8509
  });
8863
8510
  if (results.length === 0) {
8864
8511
  console.log("No similar symbol pairs found.");
@@ -9253,10 +8900,10 @@ program.command("dataflow <symbol>").description("Reference-level dataflow: defi
9253
8900
  }
9254
8901
  db.close();
9255
8902
  });
9256
- 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) => {
8903
+ 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) => {
9257
8904
  const db = openDb();
9258
8905
  const direction = opts.forward ? "forward" : "backward";
9259
- const result = queries.slice(db, symbol, { direction });
8906
+ const result = queries.slice(db, symbol, { direction, maxDepth: opts.depth });
9260
8907
  if (!result) {
9261
8908
  console.log("Symbol not found.");
9262
8909
  db.close();