scip-query 0.2.1 → 0.3.1

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 (131) hide show
  1. package/dist/chunk-26DOJ63W.js +161 -0
  2. package/dist/{chunk-PMJKOXOT.js → chunk-4JCSOF2O.js} +2 -2
  3. package/dist/{chunk-AKMBBKWV.js → chunk-5OMVSV6E.js} +12 -4
  4. package/dist/{chunk-R56FJU3E.js → chunk-7KIMF5PV.js} +2 -2
  5. package/dist/chunk-AXQKUYKF.js +1442 -0
  6. package/dist/chunk-C7H5WBTJ.js +46 -0
  7. package/dist/{chunk-AMNISGYR.js → chunk-CHDJXYBG.js} +2 -27
  8. package/dist/{chunk-GPJVPT3U.js → chunk-CPVAQJEC.js} +12 -4
  9. package/dist/{chunk-75RQSBTK.js → chunk-DH7G3DDV.js} +2 -2
  10. package/dist/{chunk-LTJC5ZQL.js → chunk-EOROMIFO.js} +13 -5
  11. package/dist/{chunk-BFLULBEU.js → chunk-EPWLXXBL.js} +2 -2
  12. package/dist/{chunk-MVH45PYK.js → chunk-FYYOWQXK.js} +12 -12
  13. package/dist/chunk-GEXE2T6I.js +87 -0
  14. package/dist/{chunk-IXPHLF6K.js → chunk-GJDHTTR2.js} +10 -3
  15. package/dist/chunk-GSH2FPKV.js +87 -0
  16. package/dist/{chunk-4ACRRQC4.js → chunk-HJZUSUPU.js} +2 -2
  17. package/dist/{chunk-M3NPW3FC.js → chunk-HLKAFWWJ.js} +81 -2
  18. package/dist/{chunk-CU62ZDHI.js → chunk-HLUS2HEB.js} +2 -2
  19. package/dist/{chunk-RFMT7UAZ.js → chunk-J3JSOSUO.js} +8 -5
  20. package/dist/{chunk-HDSRORNV.js → chunk-KKCHYLVI.js} +17 -11
  21. package/dist/{chunk-Y3M323OX.js → chunk-LFJQVJYJ.js} +2 -2
  22. package/dist/{chunk-6WVR5K46.js → chunk-LQJUPXQY.js} +3 -3
  23. package/dist/{chunk-ITZ3DDOG.js → chunk-MPGIHELS.js} +18 -3
  24. package/dist/{chunk-N4C3H7LH.js → chunk-NFS5W3PP.js} +2 -2
  25. package/dist/{chunk-Y4JFVQ7C.js → chunk-O7Q7FDUJ.js} +21 -13
  26. package/dist/{chunk-H6WCPKCX.js → chunk-OIDHN6GD.js} +2 -2
  27. package/dist/{chunk-4BQFSNFI.js → chunk-P3E6L7KW.js} +2 -2
  28. package/dist/{chunk-ORINICIZ.js → chunk-P4WO3BBW.js} +2 -2
  29. package/dist/{chunk-M4QGEKKD.js → chunk-SMDCNPMK.js} +8 -3
  30. package/dist/{chunk-6QSHLFSL.js → chunk-UGQKAVCD.js} +2 -2
  31. package/dist/{chunk-HMYJJ3HY.js → chunk-UQEQ6AHX.js} +3 -3
  32. package/dist/{chunk-WVK7AASK.js → chunk-VIYSWZCO.js} +2 -2
  33. package/dist/chunk-VJJKSGIX.js +121 -0
  34. package/dist/{chunk-N5KEREIA.js → chunk-VT4JBH6L.js} +19 -7
  35. package/dist/{chunk-R2I3M5B4.js → chunk-WGAD3GNR.js} +2 -2
  36. package/dist/{chunk-IJKLB2JW.js → chunk-YDBXNPYU.js} +2 -2
  37. package/dist/{chunk-VO4QI3LS.js → chunk-YY4QGUQ5.js} +2 -2
  38. package/dist/{chunk-3566TKJ5.js → chunk-ZEUCXQBN.js} +2 -2
  39. package/dist/cli.js +2310 -1351
  40. package/dist/{db-BHYam4BK.d.ts → db-ShvwGDKf.d.ts} +6 -1
  41. package/dist/index.d.ts +2 -2
  42. package/dist/index.js +38 -38
  43. package/dist/queries/affected.d.ts +1 -1
  44. package/dist/queries/affected.js +2 -2
  45. package/dist/queries/bottlenecks.d.ts +1 -1
  46. package/dist/queries/by-kind.d.ts +1 -1
  47. package/dist/queries/by-kind.js +1 -1
  48. package/dist/queries/call-graph.d.ts +1 -1
  49. package/dist/queries/call-graph.js +2 -2
  50. package/dist/queries/change-surface.d.ts +1 -1
  51. package/dist/queries/change-surface.js +2 -1
  52. package/dist/queries/code.d.ts +1 -1
  53. package/dist/queries/code.js +2 -2
  54. package/dist/queries/complexity-hotspots.d.ts +1 -1
  55. package/dist/queries/complexity-hotspots.js +2 -2
  56. package/dist/queries/complexity.d.ts +1 -1
  57. package/dist/queries/complexity.js +2 -2
  58. package/dist/queries/convergence.d.ts +1 -1
  59. package/dist/queries/convergence.js +2 -2
  60. package/dist/queries/coupling.d.ts +1 -1
  61. package/dist/queries/coupling.js +3 -1
  62. package/dist/queries/cycles.d.ts +1 -1
  63. package/dist/queries/cycles.js +2 -2
  64. package/dist/queries/dataflow.d.ts +1 -1
  65. package/dist/queries/dataflow.js +2 -2
  66. package/dist/queries/dead.d.ts +1 -1
  67. package/dist/queries/dead.js +3 -3
  68. package/dist/queries/deep-chains.d.ts +1 -1
  69. package/dist/queries/deep-chains.js +2 -2
  70. package/dist/queries/deps.d.ts +1 -1
  71. package/dist/queries/deps.js +3 -1
  72. package/dist/queries/diff-impact.d.ts +1 -1
  73. package/dist/queries/doc-coverage.d.ts +1 -1
  74. package/dist/queries/drift.d.ts +1 -1
  75. package/dist/queries/drift.js +2 -2
  76. package/dist/queries/extract-candidates.d.ts +1 -1
  77. package/dist/queries/extract-candidates.js +2 -2
  78. package/dist/queries/fan.d.ts +1 -1
  79. package/dist/queries/fan.js +2 -1
  80. package/dist/queries/files.d.ts +1 -1
  81. package/dist/queries/health.d.ts +1 -1
  82. package/dist/queries/health.js +13 -13
  83. package/dist/queries/hierarchy.d.ts +1 -1
  84. package/dist/queries/hierarchy.js +2 -2
  85. package/dist/queries/hotspots.d.ts +1 -1
  86. package/dist/queries/imports.d.ts +1 -1
  87. package/dist/queries/imports.js +2 -2
  88. package/dist/queries/index.d.ts +1 -1
  89. package/dist/queries/index.js +38 -38
  90. package/dist/queries/isolated.d.ts +1 -1
  91. package/dist/queries/isolated.js +3 -3
  92. package/dist/queries/members.d.ts +1 -1
  93. package/dist/queries/members.js +2 -2
  94. package/dist/queries/methods.d.ts +1 -1
  95. package/dist/queries/outline.d.ts +1 -1
  96. package/dist/queries/outline.js +2 -1
  97. package/dist/queries/passthrough-candidates.d.ts +1 -1
  98. package/dist/queries/passthrough-candidates.js +2 -2
  99. package/dist/queries/redundant-reexports.d.ts +1 -1
  100. package/dist/queries/redundant-reexports.js +3 -3
  101. package/dist/queries/refs.d.ts +1 -1
  102. package/dist/queries/refs.js +3 -1
  103. package/dist/queries/similar-chains.d.ts +1 -1
  104. package/dist/queries/similar-chains.js +2 -2
  105. package/dist/queries/similar-files.d.ts +1 -1
  106. package/dist/queries/similar-files.js +2 -2
  107. package/dist/queries/similar-signatures.d.ts +1 -1
  108. package/dist/queries/similar.d.ts +1 -1
  109. package/dist/queries/similar.js +2 -2
  110. package/dist/queries/slice.d.ts +2 -2
  111. package/dist/queries/slice.js +2 -2
  112. package/dist/queries/stale-abstractions.d.ts +1 -1
  113. package/dist/queries/stale-abstractions.js +2 -2
  114. package/dist/queries/stats.d.ts +1 -1
  115. package/dist/queries/surface.d.ts +1 -1
  116. package/dist/queries/surface.js +2 -1
  117. package/dist/queries/symbols.d.ts +1 -1
  118. package/dist/queries/symbols.js +2 -1
  119. package/dist/queries/system.d.ts +1 -1
  120. package/dist/queries/system.js +2 -1
  121. package/dist/queries/trace.d.ts +1 -1
  122. package/dist/queries/trace.js +2 -2
  123. package/dist/queries/wrapper-candidates.d.ts +1 -1
  124. package/dist/queries/wrapper-candidates.js +2 -2
  125. package/package.json +1 -1
  126. package/dist/chunk-34JPTNRN.js +0 -601
  127. package/dist/chunk-7JFZSOJ7.js +0 -103
  128. package/dist/chunk-DY4AFG2W.js +0 -48
  129. package/dist/chunk-LLMPAG56.js +0 -221
  130. package/dist/chunk-NVIIM34O.js +0 -65
  131. package/dist/chunk-YAFWL3RA.js +0 -55
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { program } from "commander";
5
- import { existsSync as existsSync8 } from "fs";
5
+ import { existsSync as existsSync8, realpathSync } from "fs";
6
6
  import { join as join10 } from "path";
7
7
  import { fileURLToPath as fileURLToPath2 } from "url";
8
8
 
@@ -1037,431 +1037,1052 @@ function files(db, pattern) {
1037
1037
  return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({ relativePath: r.relative_path }));
1038
1038
  }
1039
1039
 
1040
- // src/symbol-parser.ts
1041
- var SUFFIX_MAP = {
1042
- "/": "namespace",
1043
- "#": "type",
1044
- ".": "term",
1045
- "[": "type-param",
1046
- ":": "meta",
1047
- "!": "macro"
1048
- };
1049
- function parseSymbol(raw) {
1050
- if (raw.startsWith("local ")) {
1051
- return { kind: "local", id: raw.slice(6), raw };
1040
+ // src/query-support.ts
1041
+ import { basename } from "path";
1042
+
1043
+ // src/source-analysis.ts
1044
+ import {
1045
+ existsSync as existsSync6,
1046
+ readFileSync as readFileSync4
1047
+ } from "fs";
1048
+ import {
1049
+ dirname as dirname2,
1050
+ extname,
1051
+ join as join6,
1052
+ relative as relative2,
1053
+ resolve as resolve2
1054
+ } from "path";
1055
+ var SOURCE_IMPORT_CACHE = /* @__PURE__ */ new WeakMap();
1056
+ var SOURCE_TEXT_CACHE = /* @__PURE__ */ new WeakMap();
1057
+ var SOURCE_CALL_CACHE = /* @__PURE__ */ new WeakMap();
1058
+ var SOURCE_BINDING_CACHE = /* @__PURE__ */ new WeakMap();
1059
+ var INDEXED_PATH_CACHE = /* @__PURE__ */ new WeakMap();
1060
+ var SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
1061
+ var PYTHON_SOURCE_EXTENSIONS = [".py", ".pyi"];
1062
+ function getSourceImports(db, relativePath) {
1063
+ const cache = getCachedMap(SOURCE_IMPORT_CACHE, db);
1064
+ const normalized = normalizePath(relativePath);
1065
+ const cached = cache.get(normalized);
1066
+ if (cached) {
1067
+ return cached;
1052
1068
  }
1053
- const parts = raw.split(" ");
1054
- if (parts.length < 4) {
1055
- return {
1056
- scheme: parts[0] ?? "",
1057
- manager: parts[1] ?? "",
1058
- packageName: parts[2] ?? "",
1059
- version: "",
1060
- descriptors: [],
1061
- raw
1062
- };
1069
+ const fullPath = join6(db.config.projectRoot, normalized);
1070
+ if (!existsSync6(fullPath)) {
1071
+ cache.set(normalized, []);
1072
+ return [];
1063
1073
  }
1064
- const scheme = parts[0];
1065
- const manager = parts[1];
1066
- let restAfterManager = raw.slice(scheme.length + 1 + manager.length + 1);
1067
- let packageName;
1068
- if (restAfterManager.startsWith("`")) {
1069
- const closingTick = restAfterManager.indexOf("`", 1);
1070
- if (closingTick === -1) {
1071
- packageName = restAfterManager.slice(1);
1072
- restAfterManager = "";
1073
- } else {
1074
- packageName = restAfterManager.slice(1, closingTick);
1075
- restAfterManager = restAfterManager.slice(closingTick + 2);
1074
+ const source = readFileSync4(fullPath, "utf-8");
1075
+ const parsed = isPythonSourcePath(normalized) ? parsePythonImports(db, normalized, source) : parseJavaScriptImports(db, normalized, source);
1076
+ cache.set(normalized, parsed);
1077
+ return parsed;
1078
+ }
1079
+ function getSourceCalls(db, relativePath, opts = {}) {
1080
+ const normalized = normalizePath(relativePath);
1081
+ if (!isPythonSourcePath(normalized) && !isJavaScriptSourcePath(normalized)) {
1082
+ return [];
1083
+ }
1084
+ const cache = getCachedMap(SOURCE_CALL_CACHE, db);
1085
+ const key = `${normalized}:${opts.startLine ?? 0}:${opts.endLine ?? Number.MAX_SAFE_INTEGER}`;
1086
+ const cached = cache.get(key);
1087
+ if (cached) {
1088
+ return cached;
1089
+ }
1090
+ const source = getSourceText(db, normalized);
1091
+ if (!source) {
1092
+ cache.set(key, []);
1093
+ return [];
1094
+ }
1095
+ const lines = source.split("\n");
1096
+ const startLine = Math.max(0, opts.startLine ?? 0);
1097
+ const endLine = Math.min(lines.length - 1, opts.endLine ?? lines.length - 1);
1098
+ const scopedLines = lines.slice(startLine, endLine + 1);
1099
+ const calls = isPythonSourcePath(normalized) ? parsePythonCalls(scopedLines, startLine) : parseJavaScriptCalls(scopedLines, startLine);
1100
+ cache.set(key, calls);
1101
+ return calls;
1102
+ }
1103
+ function getSourceConstructorBindings(db, relativePath, opts = {}) {
1104
+ const normalized = normalizePath(relativePath);
1105
+ if (!isPythonSourcePath(normalized) && !isJavaScriptSourcePath(normalized)) {
1106
+ return [];
1107
+ }
1108
+ const cache = getCachedMap(SOURCE_BINDING_CACHE, db);
1109
+ const key = `${normalized}:${opts.startLine ?? 0}:${opts.endLine ?? Number.MAX_SAFE_INTEGER}`;
1110
+ const cached = cache.get(key);
1111
+ if (cached) {
1112
+ return cached;
1113
+ }
1114
+ const source = getSourceText(db, normalized);
1115
+ if (!source) {
1116
+ cache.set(key, []);
1117
+ return [];
1118
+ }
1119
+ const lines = source.split("\n");
1120
+ const startLine = Math.max(0, opts.startLine ?? 0);
1121
+ const endLine = Math.min(lines.length - 1, opts.endLine ?? lines.length - 1);
1122
+ const scopedLines = lines.slice(startLine, endLine + 1);
1123
+ const bindings = isPythonSourcePath(normalized) ? parsePythonConstructorBindings(scopedLines) : parseJavaScriptConstructorBindings(scopedLines);
1124
+ cache.set(key, bindings);
1125
+ return bindings;
1126
+ }
1127
+ function findIdentifierLines(db, relativePath, identifier, opts = {}) {
1128
+ if (!identifier) {
1129
+ return [];
1130
+ }
1131
+ const source = getSourceText(db, normalizePath(relativePath));
1132
+ if (!source) {
1133
+ return [];
1134
+ }
1135
+ const lines = stripCommentsAndStrings(source).split("\n");
1136
+ const regex = new RegExp(`\\b${escapeRegex(identifier)}\\b`);
1137
+ const results = [];
1138
+ for (let line = 0; line < lines.length; line++) {
1139
+ if (typeof opts.excludeStartLine === "number" && typeof opts.excludeEndLine === "number" && line >= opts.excludeStartLine && line <= opts.excludeEndLine) {
1140
+ continue;
1076
1141
  }
1077
- } else {
1078
- const spaceIdx = restAfterManager.indexOf(" ");
1079
- if (spaceIdx === -1) {
1080
- packageName = restAfterManager;
1081
- restAfterManager = "";
1082
- } else {
1083
- packageName = restAfterManager.slice(0, spaceIdx);
1084
- restAfterManager = restAfterManager.slice(spaceIdx + 1);
1142
+ if (regex.test(lines[line] ?? "")) {
1143
+ results.push(line);
1085
1144
  }
1086
1145
  }
1087
- let version;
1088
- const versionSpaceIdx = restAfterManager.indexOf(" ");
1089
- if (versionSpaceIdx === -1) {
1090
- version = restAfterManager;
1091
- restAfterManager = "";
1092
- } else {
1093
- version = restAfterManager.slice(0, versionSpaceIdx);
1094
- restAfterManager = restAfterManager.slice(versionSpaceIdx + 1);
1146
+ return results;
1147
+ }
1148
+ function parseJavaScriptImports(db, importerPath, source) {
1149
+ return parseJavaScriptImportStatements(source).flatMap((statement) => parseJavaScriptImportStatement(
1150
+ db,
1151
+ importerPath,
1152
+ statement.clause,
1153
+ statement.specifier,
1154
+ statement.start,
1155
+ statement.end,
1156
+ source
1157
+ ));
1158
+ }
1159
+ function parseJavaScriptImportStatements(source) {
1160
+ const statements = [];
1161
+ const importFromRegex = /^[ \t]*import\s+([\s\S]*?)\s+from\s+['"]([^'"]+)['"]\s*;?/gm;
1162
+ for (const match of source.matchAll(importFromRegex)) {
1163
+ const full = match[0];
1164
+ const clause = match[1];
1165
+ const specifier = match[2];
1166
+ if (!full || !specifier || typeof match.index !== "number") continue;
1167
+ statements.push({
1168
+ clause,
1169
+ specifier,
1170
+ start: match.index,
1171
+ end: match.index + full.length
1172
+ });
1095
1173
  }
1096
- const descriptors = parseDescriptors(restAfterManager);
1097
- return { scheme, manager, packageName, version, descriptors, raw };
1174
+ const sideEffectRegex = /^[ \t]*import\s+['"]([^'"]+)['"]\s*;?/gm;
1175
+ for (const match of source.matchAll(sideEffectRegex)) {
1176
+ const full = match[0];
1177
+ const specifier = match[1];
1178
+ if (!full || !specifier || typeof match.index !== "number") continue;
1179
+ statements.push({
1180
+ clause: null,
1181
+ specifier,
1182
+ start: match.index,
1183
+ end: match.index + full.length
1184
+ });
1185
+ }
1186
+ return statements.sort((a, b) => a.start - b.start);
1098
1187
  }
1099
- function parseDescriptors(input) {
1100
- const descriptors = [];
1101
- let i = 0;
1102
- while (i < input.length) {
1103
- if (input[i] === "[") {
1104
- const closeBracket = input.indexOf("]", i + 1);
1105
- if (closeBracket === -1) {
1106
- descriptors.push({ name: input.slice(i + 1), suffix: "type-param" });
1107
- break;
1108
- }
1109
- descriptors.push({ name: input.slice(i + 1, closeBracket), suffix: "type-param" });
1110
- i = closeBracket + 1;
1111
- continue;
1188
+ function parseJavaScriptImportStatement(db, importerPath, clause, specifier, start, end, source) {
1189
+ const resolvedSource = resolveImportPath(db, importerPath, specifier);
1190
+ const body = buildUsageBody(source, start, end);
1191
+ if (!clause) {
1192
+ return [{
1193
+ importedName: "*",
1194
+ localName: null,
1195
+ sourcePath: resolvedSource,
1196
+ kind: "side-effect",
1197
+ used: true,
1198
+ usedMembers: []
1199
+ }];
1200
+ }
1201
+ const bindings = parseImportClause(clause).map((binding) => ({
1202
+ ...binding,
1203
+ sourcePath: resolvedSource
1204
+ }));
1205
+ return bindings.map((binding) => {
1206
+ if (binding.kind === "namespace") {
1207
+ const usedMembers = collectNamespaceMembers(body, binding.localName);
1208
+ return {
1209
+ ...binding,
1210
+ used: usedMembers.length > 0 || hasIdentifierUsage(body, binding.localName),
1211
+ usedMembers
1212
+ };
1112
1213
  }
1113
- if (input[i] === "(" && (descriptors.length === 0 || i === 0 || isSuffixChar(input[i - 1]))) {
1114
- const closeParen = input.indexOf(")", i + 1);
1115
- if (closeParen !== -1 && input[closeParen + 1] !== ".") {
1116
- descriptors.push({ name: input.slice(i + 1, closeParen), suffix: "parameter" });
1117
- i = closeParen + 1;
1214
+ if (binding.kind === "side-effect") {
1215
+ return { ...binding, used: true, usedMembers: [] };
1216
+ }
1217
+ return {
1218
+ ...binding,
1219
+ used: binding.localName ? hasIdentifierUsage(body, binding.localName) : false,
1220
+ usedMembers: []
1221
+ };
1222
+ });
1223
+ }
1224
+ function parsePythonCalls(lines, baseLine) {
1225
+ const calls = [];
1226
+ const controlKeywords = /* @__PURE__ */ new Set([
1227
+ "if",
1228
+ "for",
1229
+ "while",
1230
+ "with",
1231
+ "except",
1232
+ "elif",
1233
+ "return",
1234
+ "yield",
1235
+ "assert",
1236
+ "raise",
1237
+ "lambda",
1238
+ "class",
1239
+ "def"
1240
+ ]);
1241
+ for (let index = 0; index < lines.length; index++) {
1242
+ const rawLine = lines[index] ?? "";
1243
+ const stripped = stripCommentsAndStrings(rawLine);
1244
+ if (!stripped.trim()) continue;
1245
+ const attributeMatches = [...stripped.matchAll(/\b([A-Za-z_][\w]*)\s*\.\s*([A-Za-z_][\w]*)\s*\(/g)];
1246
+ const attributeRanges = attributeMatches.map((match) => ({
1247
+ start: match.index ?? -1,
1248
+ end: (match.index ?? -1) + match[0].length
1249
+ }));
1250
+ for (const match of attributeMatches) {
1251
+ const receiverName = match[1];
1252
+ const calleeName = match[2];
1253
+ if (!receiverName || !calleeName) continue;
1254
+ calls.push({
1255
+ receiverName,
1256
+ calleeName,
1257
+ line: baseLine + index
1258
+ });
1259
+ }
1260
+ for (const match of stripped.matchAll(/\b([A-Za-z_][\w]*)\s*\(/g)) {
1261
+ const calleeName = match[1];
1262
+ const start = match.index ?? -1;
1263
+ if (!calleeName || start < 0) continue;
1264
+ if (controlKeywords.has(calleeName)) continue;
1265
+ if (attributeRanges.some((range) => start >= range.start && start < range.end)) continue;
1266
+ const prefix = stripped.slice(0, start).trimEnd();
1267
+ if (prefix.endsWith("def") || prefix.endsWith("class") || prefix.endsWith("async def")) {
1118
1268
  continue;
1119
1269
  }
1270
+ calls.push({
1271
+ receiverName: null,
1272
+ calleeName,
1273
+ line: baseLine + index
1274
+ });
1120
1275
  }
1121
- let name;
1122
- if (input[i] === "`") {
1123
- const closingTick = input.indexOf("`", i + 1);
1124
- if (closingTick === -1) {
1125
- name = input.slice(i + 1);
1126
- i = input.length;
1127
- descriptors.push({ name, suffix: "term" });
1128
- break;
1129
- }
1130
- name = input.slice(i + 1, closingTick);
1131
- i = closingTick + 1;
1132
- } else {
1133
- const start = i;
1134
- while (i < input.length && !isSuffixChar(input[i])) {
1135
- i++;
1276
+ }
1277
+ return calls;
1278
+ }
1279
+ function parseJavaScriptCalls(lines, baseLine) {
1280
+ const calls = [];
1281
+ const controlKeywords = /* @__PURE__ */ new Set([
1282
+ "if",
1283
+ "for",
1284
+ "while",
1285
+ "switch",
1286
+ "catch",
1287
+ "function",
1288
+ "class",
1289
+ "return",
1290
+ "typeof",
1291
+ "import"
1292
+ ]);
1293
+ for (let index = 0; index < lines.length; index++) {
1294
+ const stripped = stripCommentsAndStrings(lines[index] ?? "");
1295
+ if (!stripped.trim()) continue;
1296
+ const attributeMatches = [
1297
+ ...stripped.matchAll(/\b([A-Za-z_$][\w$]*)\s*(?:\?\.|\.)\s*([A-Za-z_$][\w$]*)\s*\(/g),
1298
+ ...stripped.matchAll(/\bthis\s*(?:\?\.|\.)\s*([A-Za-z_$][\w$]*)\s*\(/g)
1299
+ ];
1300
+ const attributeRanges = attributeMatches.map((match) => ({
1301
+ start: match.index ?? -1,
1302
+ end: (match.index ?? -1) + match[0].length
1303
+ }));
1304
+ for (const match of attributeMatches) {
1305
+ const receiverName = match[2] ? match[1] : "this";
1306
+ const calleeName = match[2] ?? match[1];
1307
+ if (!receiverName || !calleeName) continue;
1308
+ calls.push({
1309
+ receiverName,
1310
+ calleeName,
1311
+ line: baseLine + index
1312
+ });
1313
+ }
1314
+ for (const match of stripped.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g)) {
1315
+ const calleeName = match[1];
1316
+ const start = match.index ?? -1;
1317
+ if (!calleeName || start < 0) continue;
1318
+ if (controlKeywords.has(calleeName)) continue;
1319
+ if (attributeRanges.some((range) => start >= range.start && start < range.end)) continue;
1320
+ const prefix = stripped.slice(0, start).trimEnd();
1321
+ if (prefix.endsWith("function") || prefix.endsWith("class") || prefix.endsWith("new")) {
1322
+ continue;
1136
1323
  }
1137
- name = input.slice(start, i);
1324
+ calls.push({
1325
+ receiverName: null,
1326
+ calleeName,
1327
+ line: baseLine + index
1328
+ });
1138
1329
  }
1139
- if (i >= input.length) {
1140
- if (name) descriptors.push({ name, suffix: "term" });
1141
- break;
1330
+ }
1331
+ return calls;
1332
+ }
1333
+ function parsePythonConstructorBindings(lines) {
1334
+ const bindings = /* @__PURE__ */ new Map();
1335
+ for (const rawLine of lines) {
1336
+ const stripped = stripCommentsAndStrings(rawLine);
1337
+ const constructorMatch = stripped.match(/\b([A-Za-z_][\w]*)\s*=\s*([A-Z][\w]*)\s*\(/);
1338
+ if (constructorMatch?.[1] && constructorMatch[2]) {
1339
+ bindings.set(constructorMatch[1], constructorMatch[2]);
1142
1340
  }
1143
- const char = input[i];
1144
- if (char === "(") {
1145
- const closeParen = input.indexOf(")", i + 1);
1146
- if (closeParen !== -1 && input[closeParen + 1] === ".") {
1147
- descriptors.push({ name, suffix: "method" });
1148
- i = closeParen + 2;
1149
- } else if (closeParen !== -1) {
1150
- descriptors.push({ name, suffix: "method" });
1151
- i = closeParen + 1;
1152
- } else {
1153
- descriptors.push({ name, suffix: "term" });
1154
- i++;
1155
- }
1156
- } else {
1157
- const suffix = SUFFIX_MAP[char];
1158
- if (suffix) {
1159
- descriptors.push({ name, suffix });
1160
- i += 1;
1161
- } else {
1162
- i += 1;
1341
+ }
1342
+ return [...bindings.entries()].map(([localName, typeName]) => ({ localName, typeName }));
1343
+ }
1344
+ function parseJavaScriptConstructorBindings(lines) {
1345
+ const bindings = /* @__PURE__ */ new Map();
1346
+ for (const rawLine of lines) {
1347
+ const stripped = stripCommentsAndStrings(rawLine);
1348
+ for (const match of stripped.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*([A-Z][A-Za-z0-9_$]*)\b/g)) {
1349
+ const localName = match[1];
1350
+ const typeName = match[2];
1351
+ if (localName && typeName) {
1352
+ bindings.set(localName, typeName);
1163
1353
  }
1164
1354
  }
1355
+ const constructorMatch = stripped.match(/\b(?:const|let|var)?\s*([A-Za-z_$][\w$]*)\s*(?::\s*[A-Z][A-Za-z0-9_$<> ,]*)?\s*=\s*new\s+([A-Z][A-Za-z0-9_$]*)\s*\(/);
1356
+ if (constructorMatch?.[1] && constructorMatch[2]) {
1357
+ bindings.set(constructorMatch[1], constructorMatch[2]);
1358
+ }
1165
1359
  }
1166
- return descriptors;
1360
+ return [...bindings.entries()].map(([localName, typeName]) => ({ localName, typeName }));
1167
1361
  }
1168
- function isSuffixChar(c) {
1169
- return c === "/" || c === "#" || c === "." || c === "(" || c === "[" || c === ":" || c === "!";
1170
- }
1171
- function shortenSymbol(raw) {
1172
- const parsed = parseSymbol(raw);
1173
- if ("kind" in parsed && parsed.kind === "local") {
1174
- return `local:${parsed.id}`;
1175
- }
1176
- const sym = parsed;
1177
- if (sym.descriptors.length === 0) return sym.raw;
1178
- const parts = [];
1179
- for (const desc of sym.descriptors) {
1180
- let name = desc.name;
1181
- if (desc.suffix === "namespace") {
1182
- name = name.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "").replace(/\.(py|pyi)$/, "").replace(/\.(rs)$/, "").replace(/\.(java|scala|kt|kts)$/, "").replace(/\.(rb)$/, "").replace(/\.(go)$/, "").replace(/\.(cs|vb)$/, "").replace(/\.(dart)$/, "").replace(/\.(php)$/, "").replace(/\.(c|cc|cpp|cxx|h|hpp)$/, "");
1362
+ function parsePythonImports(db, importerPath, source) {
1363
+ return collectPythonImportStatements(source).flatMap(
1364
+ (statement) => parsePythonImportStatement(db, importerPath, statement, source)
1365
+ );
1366
+ }
1367
+ function collectPythonImportStatements(source) {
1368
+ const lines = source.split("\n");
1369
+ const statements = [];
1370
+ let offset = 0;
1371
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
1372
+ const line = lines[lineIndex];
1373
+ const trimmed = line.trimStart();
1374
+ const lineStart = offset;
1375
+ offset += line.length + 1;
1376
+ if (!trimmed.startsWith("import ") && !trimmed.startsWith("from ")) {
1377
+ continue;
1183
1378
  }
1184
- if (!name) continue;
1185
- if (desc.suffix === "method") {
1186
- parts.push(`${name}()`);
1187
- } else {
1188
- parts.push(name);
1379
+ let statement = line;
1380
+ let statementEnd = lineStart + line.length;
1381
+ let balance = pythonParenBalance(line);
1382
+ while (lineIndex + 1 < lines.length && (balance > 0 || statement.trimEnd().endsWith("\\"))) {
1383
+ lineIndex++;
1384
+ const nextLine = lines[lineIndex];
1385
+ statement += `
1386
+ ${nextLine}`;
1387
+ statementEnd += 1 + nextLine.length;
1388
+ balance += pythonParenBalance(nextLine);
1389
+ offset += nextLine.length + 1;
1390
+ }
1391
+ const parsed = parsePythonStatementHeader(statement);
1392
+ if (parsed) {
1393
+ statements.push({
1394
+ ...parsed,
1395
+ start: lineStart,
1396
+ end: statementEnd
1397
+ });
1189
1398
  }
1190
1399
  }
1191
- return parts.join(":");
1400
+ return statements;
1192
1401
  }
1193
- function leafName(raw) {
1194
- const parsed = parseSymbol(raw);
1195
- if ("kind" in parsed && parsed.kind === "local") {
1196
- return parsed.id;
1402
+ function parsePythonStatementHeader(statement) {
1403
+ const normalized = statement.replace(/\\\s*\n/g, " ").trim();
1404
+ if (normalized.startsWith("import ")) {
1405
+ return {
1406
+ kind: "import",
1407
+ module: null,
1408
+ clause: normalized.slice("import ".length).trim()
1409
+ };
1197
1410
  }
1198
- const sym = parsed;
1199
- if (sym.descriptors.length === 0) return "";
1200
- const last = sym.descriptors[sym.descriptors.length - 1];
1201
- return last.name;
1202
- }
1203
- function leafSuffix(raw) {
1204
- const parsed = parseSymbol(raw);
1205
- if ("kind" in parsed && parsed.kind === "local") {
1411
+ const fromMatch = normalized.match(/^from\s+([.\w]+)\s+import\s+([\s\S]+)$/);
1412
+ if (!fromMatch) {
1206
1413
  return null;
1207
1414
  }
1208
- const sym = parsed;
1209
- const last = sym.descriptors[sym.descriptors.length - 1];
1210
- return last?.suffix ?? null;
1415
+ let clause = fromMatch[2].trim();
1416
+ if (clause.startsWith("(") && clause.endsWith(")")) {
1417
+ clause = clause.slice(1, -1).trim();
1418
+ }
1419
+ return {
1420
+ kind: "from",
1421
+ module: fromMatch[1],
1422
+ clause
1423
+ };
1211
1424
  }
1212
- function isFunctionLikeSymbol(raw) {
1213
- const suffix = leafSuffix(raw);
1214
- return suffix === "method" || suffix === "term";
1425
+ function parsePythonImportStatement(db, importerPath, statement, source) {
1426
+ const body = buildUsageBody(source, statement.start, statement.end);
1427
+ const normalizedClause = statement.clause.replace(/\n/g, " ").trim();
1428
+ if (statement.kind === "import") {
1429
+ return splitTopLevel(normalizedClause).flatMap((entry) => {
1430
+ const cleaned = entry.trim().replace(/,$/, "");
1431
+ if (!cleaned) return [];
1432
+ const [moduleName, alias] = cleaned.split(/\s+as\s+/);
1433
+ const importedName = moduleName.trim();
1434
+ const localName = (alias ?? importedName.split(".")[0] ?? importedName).trim();
1435
+ const sourcePath2 = resolvePythonImportPath(db, importerPath, importedName);
1436
+ const usedMembers = collectNamespaceMembers(body, localName);
1437
+ return [{
1438
+ importedName,
1439
+ localName,
1440
+ sourcePath: sourcePath2,
1441
+ kind: "namespace",
1442
+ used: hasIdentifierUsage(body, localName) || usedMembers.length > 0,
1443
+ usedMembers
1444
+ }];
1445
+ });
1446
+ }
1447
+ const sourcePath = statement.module ? resolvePythonImportPath(db, importerPath, statement.module) : null;
1448
+ const results = [];
1449
+ for (const entry of splitTopLevel(normalizedClause)) {
1450
+ const cleaned = entry.trim().replace(/,$/, "");
1451
+ if (!cleaned) continue;
1452
+ if (cleaned === "*") {
1453
+ results.push({
1454
+ importedName: "*",
1455
+ localName: null,
1456
+ sourcePath,
1457
+ kind: "side-effect",
1458
+ used: true,
1459
+ usedMembers: []
1460
+ });
1461
+ continue;
1462
+ }
1463
+ const [importedName, alias] = cleaned.split(/\s+as\s+/);
1464
+ const localName = (alias ?? importedName).trim();
1465
+ results.push({
1466
+ importedName: importedName.trim(),
1467
+ localName,
1468
+ sourcePath,
1469
+ kind: "named",
1470
+ used: hasIdentifierUsage(body, localName),
1471
+ usedMembers: []
1472
+ });
1473
+ }
1474
+ return results;
1215
1475
  }
1216
- function isModuleLikeSymbol(raw) {
1217
- return leafSuffix(raw) === "namespace";
1476
+ function parseImportClause(clause) {
1477
+ const trimmed = clause.trim().replace(/^type\s+/, "");
1478
+ const [first, second] = splitImportClause(trimmed);
1479
+ const entries = [];
1480
+ if (first) {
1481
+ entries.push(...parseImportBinding(first));
1482
+ }
1483
+ if (second) {
1484
+ entries.push(...parseImportBinding(second));
1485
+ }
1486
+ return entries;
1218
1487
  }
1219
- function isDirectChildSymbol(parentRaw, candidateRaw) {
1220
- const parent = parseSymbol(parentRaw);
1221
- const candidate = parseSymbol(candidateRaw);
1222
- if ("kind" in parent || "kind" in candidate) {
1223
- return false;
1488
+ function parseImportBinding(binding) {
1489
+ const trimmed = binding.trim();
1490
+ if (!trimmed) return [];
1491
+ if (trimmed.startsWith("{")) {
1492
+ const inner = trimmed.slice(1, -1).trim();
1493
+ if (!inner) return [];
1494
+ return splitTopLevel(inner).map((entry) => {
1495
+ const cleaned = entry.trim().replace(/^type\s+/, "");
1496
+ const [importedName, alias] = cleaned.split(/\s+as\s+/);
1497
+ return {
1498
+ importedName: importedName.trim(),
1499
+ localName: (alias ?? importedName).trim(),
1500
+ kind: "named"
1501
+ };
1502
+ });
1224
1503
  }
1225
- const parentDescriptors = parent.descriptors;
1226
- const candidateDescriptors = candidate.descriptors;
1227
- if (candidateDescriptors.length !== parentDescriptors.length + 1) {
1228
- return false;
1504
+ if (trimmed.startsWith("* as ")) {
1505
+ return [{
1506
+ importedName: "*",
1507
+ localName: trimmed.slice(5).trim(),
1508
+ kind: "namespace"
1509
+ }];
1229
1510
  }
1230
- for (let i = 0; i < parentDescriptors.length; i++) {
1231
- const parentDesc = parentDescriptors[i];
1232
- const candidateDesc = candidateDescriptors[i];
1233
- if (parentDesc.name !== candidateDesc.name || parentDesc.suffix !== candidateDesc.suffix) {
1234
- return false;
1511
+ return [{
1512
+ importedName: "default",
1513
+ localName: trimmed,
1514
+ kind: "default"
1515
+ }];
1516
+ }
1517
+ function splitImportClause(clause) {
1518
+ let depth = 0;
1519
+ for (let i = 0; i < clause.length; i++) {
1520
+ const char = clause[i];
1521
+ if (char === "{") depth++;
1522
+ if (char === "}") depth--;
1523
+ if (char === "," && depth === 0) {
1524
+ return [clause.slice(0, i).trim(), clause.slice(i + 1).trim()];
1235
1525
  }
1236
1526
  }
1237
- return true;
1527
+ return [clause.trim(), null];
1238
1528
  }
1239
-
1240
- // src/queries/clean-signature.ts
1241
- function cleanSignature(sig) {
1242
- if (!sig || !sig.trim()) return null;
1243
- 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;
1529
+ function splitTopLevel(input) {
1530
+ const parts = [];
1531
+ let depth = 0;
1532
+ let start = 0;
1533
+ for (let i = 0; i < input.length; i++) {
1534
+ const char = input[i];
1535
+ if (char === "{" || char === "[" || char === "(") depth++;
1536
+ if (char === "}" || char === "]" || char === ")") depth--;
1537
+ if (char === "," && depth === 0) {
1538
+ parts.push(input.slice(start, i));
1539
+ start = i + 1;
1540
+ }
1541
+ }
1542
+ parts.push(input.slice(start));
1543
+ return parts;
1244
1544
  }
1245
-
1246
- // src/queries/symbols.ts
1247
- function symbols(db, filePattern) {
1248
- const rows = db.all(
1249
- `SELECT
1250
- der.start_line,
1251
- der.end_line,
1252
- REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig,
1253
- gs.symbol,
1254
- d.relative_path
1255
- FROM defn_enclosing_ranges der
1256
- JOIN global_symbols gs ON der.symbol_id = gs.id
1257
- JOIN documents d ON der.document_id = d.id
1258
- WHERE d.relative_path LIKE ?
1259
- AND ${db.localSymbolPredicate}
1260
- ${db.symbolNoise}
1261
- ORDER BY der.start_line`,
1262
- `%${filePattern}%`
1263
- );
1264
- return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
1265
- startLine: r.start_line,
1266
- endLine: r.end_line,
1267
- symbol: r.symbol,
1268
- shortName: shortenSymbol(r.symbol),
1269
- signature: cleanSignature(r.sig)
1270
- }));
1545
+ function buildUsageBody(source, start, end) {
1546
+ const masked = `${source.slice(0, start)}${" ".repeat(end - start)}${source.slice(end)}`;
1547
+ return stripCommentsAndStrings(masked);
1271
1548
  }
1272
-
1273
- // src/queries/methods.ts
1274
- function methods(db, className) {
1275
- const rows = db.all(
1276
- `SELECT der.start_line, der.end_line, gs.symbol
1277
- FROM global_symbols gs
1278
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1279
- WHERE gs.symbol LIKE ?
1280
- AND ${db.localSymbolPredicate}
1281
- AND gs.symbol LIKE '%().%'
1282
- ${db.symbolNoise}
1283
- ORDER BY der.start_line`,
1284
- `%${className}#%`
1285
- );
1286
- return rows.map((r) => ({
1287
- startLine: r.start_line,
1288
- endLine: r.end_line,
1289
- name: leafName(r.symbol)
1290
- }));
1549
+ function stripCommentsAndStrings(source) {
1550
+ return source.replace(/'''[\s\S]*?'''/g, maskPreservingLines).replace(/"""[\s\S]*?"""/g, maskPreservingLines).replace(/#.*$/gm, maskPreservingLines).replace(/\/\/.*$/gm, maskPreservingLines).replace(/\/\*[\s\S]*?\*\//g, maskPreservingLines).replace(/`(?:\\[\s\S]|[^`])*`/g, maskPreservingLines).replace(/'(?:\\.|[^'\\\r\n])*'/g, maskPreservingLines).replace(/"(?:\\.|[^"\\\r\n])*"/g, maskPreservingLines);
1291
1551
  }
1292
-
1293
- // src/queries/refs.ts
1294
- function refs(db, symbolPattern) {
1295
- const rows = db.all(
1296
- `SELECT DISTINCT d.relative_path, c.start_line
1297
- FROM mentions m
1298
- JOIN chunks c ON m.chunk_id = c.id
1299
- JOIN documents d ON c.document_id = d.id
1300
- JOIN global_symbols gs ON m.symbol_id = gs.id
1301
- WHERE gs.symbol LIKE ?
1302
- AND ${db.localSymbolPredicate}
1303
- AND m.role != 1
1304
- ORDER BY d.relative_path, c.start_line`,
1305
- `%${symbolPattern}%`
1306
- );
1307
- return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
1308
- relativePath: r.relative_path,
1309
- line: r.start_line
1310
- }));
1552
+ function maskPreservingLines(segment) {
1553
+ return segment.replace(/[^\r\n]/g, " ");
1311
1554
  }
1312
-
1313
- // src/query-support.ts
1314
- var TEST_FILE_PATTERNS = [
1315
- "%/__tests__/%",
1316
- "%.test.%",
1317
- "%.spec.%",
1318
- "%/test/%",
1319
- "%/tests/%",
1320
- "%_test.%",
1321
- "%_spec.%",
1322
- "%/test_%.%",
1323
- "%/spec_%.%"
1324
- ];
1325
- var TEST_SUPPORT_PATH_PATTERNS = [
1326
- "%/test-utils/%"
1327
- ];
1328
- function testFileExclusionSql(alias, extraPatterns = []) {
1329
- const patterns = uniquePatterns([...TEST_FILE_PATTERNS, ...extraPatterns]);
1330
- return patterns.map((pattern) => `${alias}.relative_path NOT LIKE '${pattern}'`).join("\n AND ");
1555
+ function hasIdentifierUsage(body, identifier) {
1556
+ return new RegExp(`\\b${escapeRegex(identifier)}\\b`, "m").test(body);
1331
1557
  }
1332
- function buildFileDepGraph(db, scope) {
1333
- const scopeFilter = scope ? `AND d1.relative_path LIKE '%${scope}%'` : "";
1334
- const edges = db.all(
1335
- `SELECT DISTINCT
1336
- d1.relative_path AS from_file,
1337
- d2.relative_path AS to_file
1338
- FROM mentions m
1339
- JOIN chunks c ON m.chunk_id = c.id
1340
- JOIN documents d1 ON c.document_id = d1.id
1341
- JOIN global_symbols gs ON m.symbol_id = gs.id
1342
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1343
- JOIN documents d2 ON der.document_id = d2.id
1344
- WHERE d1.id != d2.id
1345
- AND m.role != 1
1346
- ${db.pathExclusionsFor("d1", "d2")}
1347
- ${scopeFilter}`
1348
- );
1349
- const graph = /* @__PURE__ */ new Map();
1350
- for (const edge of edges) {
1351
- if (db.isIgnored(edge.from_file) || db.isIgnored(edge.to_file)) continue;
1352
- if (!graph.has(edge.from_file)) graph.set(edge.from_file, /* @__PURE__ */ new Set());
1353
- graph.get(edge.from_file).add(edge.to_file);
1558
+ function collectNamespaceMembers(body, namespaceName) {
1559
+ const members2 = /* @__PURE__ */ new Set();
1560
+ const regex = new RegExp(`\\b${escapeRegex(namespaceName)}\\s*\\.\\s*([A-Za-z_$][\\w$]*)`, "g");
1561
+ for (const match of body.matchAll(regex)) {
1562
+ const member = match[1];
1563
+ if (member) {
1564
+ members2.add(member);
1565
+ }
1354
1566
  }
1355
- return graph;
1567
+ return [...members2];
1356
1568
  }
1357
- function findFirstSymbolMatch(db, symbolPattern) {
1358
- const fileLineMatch = symbolPattern.match(/^(.+):(\d+)-(\d+)$/);
1359
- if (fileLineMatch) {
1360
- const [, filePath, startStr, endStr] = fileLineMatch;
1361
- const row = db.get(
1362
- `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
1363
- FROM global_symbols gs
1364
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1365
- JOIN documents d ON der.document_id = d.id
1366
- WHERE d.relative_path LIKE ?
1367
- AND der.start_line <= ? AND der.end_line >= ?
1368
- ${db.pathExclusionsFor("d")}
1369
- ORDER BY (der.end_line - der.start_line) ASC
1370
- LIMIT 1`,
1371
- `%${filePath}%`,
1372
- parseInt(startStr, 10),
1373
- parseInt(endStr, 10)
1374
- );
1375
- if (row && !db.isIgnored(row.relative_path)) {
1376
- return {
1377
- symbolId: row.id,
1378
- symbol: row.symbol,
1379
- documentId: row.document_id,
1380
- startLine: row.start_line,
1381
- endLine: row.end_line,
1382
- relativePath: row.relative_path
1383
- };
1569
+ function resolveImportPath(db, importerPath, specifier) {
1570
+ if (isPythonSourcePath(importerPath)) {
1571
+ return resolvePythonImportPath(db, importerPath, specifier);
1572
+ }
1573
+ return resolveJavaScriptImportPath(db, importerPath, specifier);
1574
+ }
1575
+ function resolveJavaScriptImportPath(db, importerPath, specifier) {
1576
+ if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
1577
+ return null;
1578
+ }
1579
+ const importerDir = dirname2(join6(db.config.projectRoot, importerPath));
1580
+ const absolute = resolve2(importerDir, specifier);
1581
+ const indexedPaths = getIndexedPaths(db);
1582
+ for (const candidate of candidateImportPaths(absolute)) {
1583
+ const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
1584
+ if (indexedPaths.has(relativeCandidate) || existsSync6(candidate)) {
1585
+ return relativeCandidate;
1384
1586
  }
1385
1587
  }
1386
- const cleaned = normalizeLookupPattern(symbolPattern);
1387
- const tokens = lookupTokens(symbolPattern);
1388
- const candidates = getSymbolLookupCandidates(db, tokens);
1389
- let best = null;
1390
- for (const row of candidates) {
1391
- if (db.isIgnored(row.relative_path)) continue;
1392
- const score = scoreSymbolCandidate(row, symbolPattern, cleaned, tokens);
1393
- if (score <= 0) continue;
1394
- if (!best || score > best.score) {
1395
- best = { row, score };
1588
+ return normalizePath(relative2(db.config.projectRoot, absolute));
1589
+ }
1590
+ function resolvePythonImportPath(db, importerPath, specifier) {
1591
+ const indexedPaths = getIndexedPaths(db);
1592
+ let basePath;
1593
+ if (specifier.startsWith(".")) {
1594
+ const match = specifier.match(/^(\.+)(.*)$/);
1595
+ if (!match) return null;
1596
+ const dots = match[1].length;
1597
+ const remainder = match[2].replace(/^\./, "");
1598
+ let baseDir = dirname2(join6(db.config.projectRoot, importerPath));
1599
+ for (let i = 1; i < dots; i++) {
1600
+ baseDir = dirname2(baseDir);
1396
1601
  }
1602
+ basePath = remainder ? resolve2(baseDir, remainder.replace(/\./g, "/")) : baseDir;
1603
+ } else {
1604
+ basePath = resolve2(db.config.projectRoot, specifier.replace(/\./g, "/"));
1397
1605
  }
1398
- if (best) {
1399
- return {
1400
- symbolId: best.row.id,
1401
- symbol: best.row.symbol,
1402
- documentId: best.row.document_id,
1403
- startLine: best.row.start_line,
1404
- endLine: best.row.end_line,
1405
- relativePath: best.row.relative_path
1406
- };
1606
+ for (const candidate of pythonCandidateImportPaths(basePath)) {
1607
+ const relativeCandidate = normalizePath(relative2(db.config.projectRoot, candidate));
1608
+ if (indexedPaths.has(relativeCandidate) || existsSync6(candidate)) {
1609
+ return relativeCandidate;
1610
+ }
1407
1611
  }
1408
1612
  return null;
1409
1613
  }
1410
- function normalizeLookupPattern(symbolPattern) {
1411
- return symbolPattern.trim().replace(/\(\)$/, "").replace(/\(.*$/, "");
1614
+ function pythonCandidateImportPaths(basePath) {
1615
+ const ext = extname(basePath);
1616
+ if (PYTHON_SOURCE_EXTENSIONS.includes(ext)) {
1617
+ return [basePath];
1618
+ }
1619
+ return [
1620
+ `${basePath}.py`,
1621
+ `${basePath}.pyi`,
1622
+ join6(basePath, "__init__.py"),
1623
+ join6(basePath, "__init__.pyi")
1624
+ ];
1412
1625
  }
1413
- function lookupTokens(symbolPattern) {
1414
- const cleaned = normalizeLookupPattern(symbolPattern);
1415
- const tokens = cleaned.split(/[^A-Za-z0-9_]+/).map((token) => token.trim()).filter((token) => token.length > 0);
1416
- return tokens.length > 0 ? [...new Set(tokens)] : [cleaned];
1626
+ function candidateImportPaths(absolute) {
1627
+ const ext = extname(absolute);
1628
+ const candidates = /* @__PURE__ */ new Set();
1629
+ if (ext) {
1630
+ candidates.add(absolute);
1631
+ for (const sourceExt of SOURCE_EXTENSIONS) {
1632
+ candidates.add(absolute.slice(0, -ext.length) + sourceExt);
1633
+ }
1634
+ } else {
1635
+ for (const sourceExt of SOURCE_EXTENSIONS) {
1636
+ candidates.add(`${absolute}${sourceExt}`);
1637
+ candidates.add(join6(absolute, `index${sourceExt}`));
1638
+ }
1639
+ }
1640
+ return [...candidates];
1417
1641
  }
1418
- function getSymbolLookupCandidates(db, tokens) {
1419
- const tokenClauses = tokens.map(
1420
- () => `(gs.symbol LIKE ? OR d.relative_path LIKE ? OR COALESCE(gs.display_name, '') LIKE ?)`
1421
- );
1422
- const params = tokens.flatMap((token) => {
1423
- const like = `%${token}%`;
1424
- return [like, like, like];
1425
- });
1426
- return db.all(
1427
- `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path, gs.display_name
1428
- FROM global_symbols gs
1429
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1430
- JOIN documents d ON der.document_id = d.id
1431
- WHERE ${tokenClauses.join("\n AND ")}
1432
- ${db.pathExclusionsFor("d")}
1433
- LIMIT 200`,
1434
- ...params
1642
+ function getIndexedPaths(db) {
1643
+ const cached = INDEXED_PATH_CACHE.get(db);
1644
+ if (cached) {
1645
+ return cached;
1646
+ }
1647
+ const paths = new Set(
1648
+ db.all(
1649
+ `SELECT relative_path
1650
+ FROM documents
1651
+ WHERE 1 = 1
1652
+ ${db.pathExclusionsFor("documents")}`
1653
+ ).map((row) => normalizePath(row.relative_path)).filter((relativePath) => !db.isIgnored(relativePath))
1435
1654
  );
1655
+ INDEXED_PATH_CACHE.set(db, paths);
1656
+ return paths;
1436
1657
  }
1437
- function scoreSymbolCandidate(row, originalPattern, cleanedPattern, tokens) {
1438
- const original = originalPattern.toLowerCase();
1439
- const cleaned = cleanedPattern.toLowerCase();
1440
- const noParens = cleaned.replace(/\(\)$/, "");
1441
- const raw = row.symbol.toLowerCase();
1442
- const short = shortenSymbol(row.symbol).toLowerCase();
1443
- const leaf = leafName(row.symbol).toLowerCase();
1444
- const display = (row.display_name ?? "").toLowerCase();
1445
- const path2 = row.relative_path.toLowerCase();
1446
- const looksPathLike = /[/:.]/.test(cleanedPattern);
1447
- let score = 0;
1448
- if (raw === original || raw === cleaned) score += 1e3;
1449
- if (short === original || short === cleaned) score += 950;
1450
- if (path2 === original || path2 === cleaned) score += 925;
1451
- if (path2.endsWith(`/${cleaned}`) || path2.endsWith(`/${original}`)) score += 875;
1452
- if (display === noParens) score += 850;
1453
- if (leaf === noParens) score += 825;
1454
- if (`${leaf}()` === original || `${leaf}()` === cleaned) score += 820;
1455
- if (short.endsWith(`:${cleaned}`) || short.endsWith(`:${noParens}`) || short.endsWith(`:${noParens}()`)) score += 800;
1456
- if (raw.includes(cleaned)) score += 120;
1457
- if (short.includes(cleaned)) score += 140;
1458
- if (path2.includes(cleaned)) score += 140;
1459
- if (display.includes(cleaned)) score += 110;
1460
- if (tokens.every((token) => {
1461
- const lower = token.toLowerCase();
1462
- return raw.includes(lower) || short.includes(lower) || path2.includes(lower) || display.includes(lower);
1463
- })) {
1464
- score += 100 + tokens.length * 15;
1658
+ function getCachedMap(cache, db) {
1659
+ let map = cache.get(db);
1660
+ if (!map) {
1661
+ map = /* @__PURE__ */ new Map();
1662
+ cache.set(db, map);
1663
+ }
1664
+ return map;
1665
+ }
1666
+ function normalizePath(path2) {
1667
+ return path2.replace(/\\/g, "/");
1668
+ }
1669
+ function isJavaScriptSourcePath(relativePath) {
1670
+ return SOURCE_EXTENSIONS.includes(extname(relativePath).toLowerCase());
1671
+ }
1672
+ function isPythonSourcePath(relativePath) {
1673
+ return PYTHON_SOURCE_EXTENSIONS.includes(extname(relativePath).toLowerCase());
1674
+ }
1675
+ function getSourceText(db, relativePath) {
1676
+ const cache = getCachedMap(SOURCE_TEXT_CACHE, db);
1677
+ const normalized = normalizePath(relativePath);
1678
+ const cached = cache.get(normalized);
1679
+ if (typeof cached === "string") {
1680
+ return cached;
1681
+ }
1682
+ const fullPath = join6(db.config.projectRoot, normalized);
1683
+ if (!existsSync6(fullPath)) {
1684
+ cache.set(normalized, "");
1685
+ return "";
1686
+ }
1687
+ const source = readFileSync4(fullPath, "utf-8");
1688
+ cache.set(normalized, source);
1689
+ return source;
1690
+ }
1691
+ function pythonParenBalance(value) {
1692
+ let balance = 0;
1693
+ for (const char of value) {
1694
+ if (char === "(") balance++;
1695
+ if (char === ")") balance--;
1696
+ }
1697
+ return balance;
1698
+ }
1699
+ function escapeRegex(value) {
1700
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1701
+ }
1702
+
1703
+ // src/symbol-parser.ts
1704
+ var SUFFIX_MAP = {
1705
+ "/": "namespace",
1706
+ "#": "type",
1707
+ ".": "term",
1708
+ "[": "type-param",
1709
+ ":": "meta",
1710
+ "!": "macro"
1711
+ };
1712
+ function parseSymbol(raw) {
1713
+ if (raw.startsWith("local ")) {
1714
+ return { kind: "local", id: raw.slice(6), raw };
1715
+ }
1716
+ const parts = raw.split(" ");
1717
+ if (parts.length < 4) {
1718
+ return {
1719
+ scheme: parts[0] ?? "",
1720
+ manager: parts[1] ?? "",
1721
+ packageName: parts[2] ?? "",
1722
+ version: "",
1723
+ descriptors: [],
1724
+ raw
1725
+ };
1726
+ }
1727
+ const scheme = parts[0];
1728
+ const manager = parts[1];
1729
+ let restAfterManager = raw.slice(scheme.length + 1 + manager.length + 1);
1730
+ let packageName;
1731
+ if (restAfterManager.startsWith("`")) {
1732
+ const closingTick = restAfterManager.indexOf("`", 1);
1733
+ if (closingTick === -1) {
1734
+ packageName = restAfterManager.slice(1);
1735
+ restAfterManager = "";
1736
+ } else {
1737
+ packageName = restAfterManager.slice(1, closingTick);
1738
+ restAfterManager = restAfterManager.slice(closingTick + 2);
1739
+ }
1740
+ } else {
1741
+ const spaceIdx = restAfterManager.indexOf(" ");
1742
+ if (spaceIdx === -1) {
1743
+ packageName = restAfterManager;
1744
+ restAfterManager = "";
1745
+ } else {
1746
+ packageName = restAfterManager.slice(0, spaceIdx);
1747
+ restAfterManager = restAfterManager.slice(spaceIdx + 1);
1748
+ }
1749
+ }
1750
+ let version;
1751
+ const versionSpaceIdx = restAfterManager.indexOf(" ");
1752
+ if (versionSpaceIdx === -1) {
1753
+ version = restAfterManager;
1754
+ restAfterManager = "";
1755
+ } else {
1756
+ version = restAfterManager.slice(0, versionSpaceIdx);
1757
+ restAfterManager = restAfterManager.slice(versionSpaceIdx + 1);
1758
+ }
1759
+ const descriptors = parseDescriptors(restAfterManager);
1760
+ return { scheme, manager, packageName, version, descriptors, raw };
1761
+ }
1762
+ function parseDescriptors(input) {
1763
+ const descriptors = [];
1764
+ let i = 0;
1765
+ while (i < input.length) {
1766
+ if (input[i] === "[") {
1767
+ const closeBracket = input.indexOf("]", i + 1);
1768
+ if (closeBracket === -1) {
1769
+ descriptors.push({ name: input.slice(i + 1), suffix: "type-param" });
1770
+ break;
1771
+ }
1772
+ descriptors.push({ name: input.slice(i + 1, closeBracket), suffix: "type-param" });
1773
+ i = closeBracket + 1;
1774
+ continue;
1775
+ }
1776
+ if (input[i] === "(" && (descriptors.length === 0 || i === 0 || isSuffixChar(input[i - 1]))) {
1777
+ const closeParen = input.indexOf(")", i + 1);
1778
+ if (closeParen !== -1 && input[closeParen + 1] !== ".") {
1779
+ descriptors.push({ name: input.slice(i + 1, closeParen), suffix: "parameter" });
1780
+ i = closeParen + 1;
1781
+ continue;
1782
+ }
1783
+ }
1784
+ let name;
1785
+ if (input[i] === "`") {
1786
+ const closingTick = input.indexOf("`", i + 1);
1787
+ if (closingTick === -1) {
1788
+ name = input.slice(i + 1);
1789
+ i = input.length;
1790
+ descriptors.push({ name, suffix: "term" });
1791
+ break;
1792
+ }
1793
+ name = input.slice(i + 1, closingTick);
1794
+ i = closingTick + 1;
1795
+ } else {
1796
+ const start = i;
1797
+ while (i < input.length && !isSuffixChar(input[i])) {
1798
+ i++;
1799
+ }
1800
+ name = input.slice(start, i);
1801
+ }
1802
+ if (i >= input.length) {
1803
+ if (name) descriptors.push({ name, suffix: "term" });
1804
+ break;
1805
+ }
1806
+ const char = input[i];
1807
+ if (char === "(") {
1808
+ const closeParen = input.indexOf(")", i + 1);
1809
+ if (closeParen !== -1 && input[closeParen + 1] === ".") {
1810
+ descriptors.push({ name, suffix: "method" });
1811
+ i = closeParen + 2;
1812
+ } else if (closeParen !== -1) {
1813
+ descriptors.push({ name, suffix: "method" });
1814
+ i = closeParen + 1;
1815
+ } else {
1816
+ descriptors.push({ name, suffix: "term" });
1817
+ i++;
1818
+ }
1819
+ } else {
1820
+ const suffix = SUFFIX_MAP[char];
1821
+ if (suffix) {
1822
+ descriptors.push({ name, suffix });
1823
+ i += 1;
1824
+ } else {
1825
+ i += 1;
1826
+ }
1827
+ }
1828
+ }
1829
+ return descriptors;
1830
+ }
1831
+ function isSuffixChar(c) {
1832
+ return c === "/" || c === "#" || c === "." || c === "(" || c === "[" || c === ":" || c === "!";
1833
+ }
1834
+ function shortenSymbol(raw) {
1835
+ const parsed = parseSymbol(raw);
1836
+ if ("kind" in parsed && parsed.kind === "local") {
1837
+ return `local:${parsed.id}`;
1838
+ }
1839
+ const sym = parsed;
1840
+ if (sym.descriptors.length === 0) return sym.raw;
1841
+ const parts = [];
1842
+ for (const desc of sym.descriptors) {
1843
+ let name = desc.name;
1844
+ if (desc.suffix === "namespace") {
1845
+ name = name.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "").replace(/\.(py|pyi)$/, "").replace(/\.(rs)$/, "").replace(/\.(java|scala|kt|kts)$/, "").replace(/\.(rb)$/, "").replace(/\.(go)$/, "").replace(/\.(cs|vb)$/, "").replace(/\.(dart)$/, "").replace(/\.(php)$/, "").replace(/\.(c|cc|cpp|cxx|h|hpp)$/, "");
1846
+ }
1847
+ if (!name) continue;
1848
+ if (desc.suffix === "method") {
1849
+ parts.push(`${name}()`);
1850
+ } else {
1851
+ parts.push(name);
1852
+ }
1853
+ }
1854
+ return parts.join(":");
1855
+ }
1856
+ function leafName(raw) {
1857
+ const parsed = parseSymbol(raw);
1858
+ if ("kind" in parsed && parsed.kind === "local") {
1859
+ return parsed.id;
1860
+ }
1861
+ const sym = parsed;
1862
+ if (sym.descriptors.length === 0) return "";
1863
+ const last = sym.descriptors[sym.descriptors.length - 1];
1864
+ return last.name;
1865
+ }
1866
+ function leafSuffix(raw) {
1867
+ const parsed = parseSymbol(raw);
1868
+ if ("kind" in parsed && parsed.kind === "local") {
1869
+ return null;
1870
+ }
1871
+ const sym = parsed;
1872
+ const last = sym.descriptors[sym.descriptors.length - 1];
1873
+ return last?.suffix ?? null;
1874
+ }
1875
+ function isFunctionLikeSymbol(raw) {
1876
+ const suffix = leafSuffix(raw);
1877
+ return suffix === "method" || suffix === "term";
1878
+ }
1879
+ function isModuleLikeSymbol(raw) {
1880
+ return leafSuffix(raw) === "namespace";
1881
+ }
1882
+ function isDirectChildSymbol(parentRaw, candidateRaw) {
1883
+ const parent = parseSymbol(parentRaw);
1884
+ const candidate = parseSymbol(candidateRaw);
1885
+ if ("kind" in parent || "kind" in candidate) {
1886
+ return false;
1887
+ }
1888
+ const parentDescriptors = parent.descriptors;
1889
+ const candidateDescriptors = candidate.descriptors;
1890
+ if (candidateDescriptors.length !== parentDescriptors.length + 1) {
1891
+ return false;
1892
+ }
1893
+ for (let i = 0; i < parentDescriptors.length; i++) {
1894
+ const parentDesc = parentDescriptors[i];
1895
+ const candidateDesc = candidateDescriptors[i];
1896
+ if (parentDesc.name !== candidateDesc.name || parentDesc.suffix !== candidateDesc.suffix) {
1897
+ return false;
1898
+ }
1899
+ }
1900
+ return true;
1901
+ }
1902
+
1903
+ // src/query-support.ts
1904
+ var FILE_DEFINITION_CACHE = /* @__PURE__ */ new WeakMap();
1905
+ var TEST_FILE_PATTERNS = [
1906
+ "%/__tests__/%",
1907
+ "%.test.%",
1908
+ "%.spec.%",
1909
+ "%/test/%",
1910
+ "%/tests/%",
1911
+ "%_test.%",
1912
+ "%_spec.%",
1913
+ "%/test_%.%",
1914
+ "%/spec_%.%"
1915
+ ];
1916
+ var TEST_SUPPORT_PATH_PATTERNS = [
1917
+ "%/test-utils/%"
1918
+ ];
1919
+ function testFileExclusionSql(alias, extraPatterns = []) {
1920
+ const patterns = uniquePatterns([...TEST_FILE_PATTERNS, ...extraPatterns]);
1921
+ return patterns.map((pattern) => `${alias}.relative_path NOT LIKE '${pattern}'`).join("\n AND ");
1922
+ }
1923
+ function buildFileDepGraph(db, scope) {
1924
+ const scopeFilter = scope ? `AND d1.relative_path LIKE '%${scope}%'` : "";
1925
+ const edges = db.all(
1926
+ `SELECT DISTINCT
1927
+ d1.relative_path AS from_file,
1928
+ d2.relative_path AS to_file
1929
+ FROM mentions m
1930
+ JOIN chunks c ON m.chunk_id = c.id
1931
+ JOIN documents d1 ON c.document_id = d1.id
1932
+ JOIN global_symbols gs ON m.symbol_id = gs.id
1933
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1934
+ JOIN documents d2 ON der.document_id = d2.id
1935
+ WHERE d1.id != d2.id
1936
+ AND m.role != 1
1937
+ ${db.pathExclusionsFor("d1", "d2")}
1938
+ ${scopeFilter}`
1939
+ );
1940
+ const graph = /* @__PURE__ */ new Map();
1941
+ for (const edge of edges) {
1942
+ if (db.isIgnored(edge.from_file) || db.isIgnored(edge.to_file)) continue;
1943
+ if (!graph.has(edge.from_file)) graph.set(edge.from_file, /* @__PURE__ */ new Set());
1944
+ graph.get(edge.from_file).add(edge.to_file);
1945
+ }
1946
+ return graph;
1947
+ }
1948
+ function findFirstSymbolMatch(db, symbolPattern) {
1949
+ const fileLineMatch = symbolPattern.match(/^(.+):(\d+)-(\d+)$/);
1950
+ if (fileLineMatch) {
1951
+ const [, filePath, startStr, endStr] = fileLineMatch;
1952
+ const row = db.get(
1953
+ `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
1954
+ FROM global_symbols gs
1955
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1956
+ JOIN documents d ON der.document_id = d.id
1957
+ WHERE d.relative_path LIKE ?
1958
+ AND der.start_line <= ? AND der.end_line >= ?
1959
+ ${db.pathExclusionsFor("d")}
1960
+ ORDER BY (der.end_line - der.start_line) ASC
1961
+ LIMIT 1`,
1962
+ `%${filePath}%`,
1963
+ parseInt(startStr, 10),
1964
+ parseInt(endStr, 10)
1965
+ );
1966
+ if (row && !db.isIgnored(row.relative_path)) {
1967
+ return {
1968
+ symbolId: row.id,
1969
+ symbol: row.symbol,
1970
+ documentId: row.document_id,
1971
+ startLine: row.start_line,
1972
+ endLine: row.end_line,
1973
+ relativePath: row.relative_path
1974
+ };
1975
+ }
1976
+ }
1977
+ const cleaned = normalizeLookupPattern(symbolPattern);
1978
+ const tokens = lookupTokens(symbolPattern);
1979
+ const candidates = getSymbolLookupCandidates(db, tokens);
1980
+ let best = null;
1981
+ for (const row of candidates) {
1982
+ if (db.isIgnored(row.relative_path)) continue;
1983
+ const score = scoreSymbolCandidate(row, symbolPattern, cleaned, tokens);
1984
+ if (score <= 0) continue;
1985
+ if (!best || score > best.score) {
1986
+ best = { row, score };
1987
+ }
1988
+ }
1989
+ if (best) {
1990
+ return {
1991
+ symbolId: best.row.id,
1992
+ symbol: best.row.symbol,
1993
+ documentId: best.row.document_id,
1994
+ startLine: best.row.start_line,
1995
+ endLine: best.row.end_line,
1996
+ relativePath: best.row.relative_path
1997
+ };
1998
+ }
1999
+ return null;
2000
+ }
2001
+ function findExactSymbolMatch(db, symbol) {
2002
+ const row = db.get(
2003
+ `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
2004
+ FROM global_symbols gs
2005
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2006
+ JOIN documents d ON der.document_id = d.id
2007
+ WHERE gs.symbol = ?
2008
+ ${db.pathExclusionsFor("d")}
2009
+ ORDER BY d.relative_path, der.start_line
2010
+ LIMIT 1`,
2011
+ symbol
2012
+ );
2013
+ if (!row || db.isIgnored(row.relative_path)) {
2014
+ return null;
2015
+ }
2016
+ return {
2017
+ symbolId: row.id,
2018
+ symbol: row.symbol,
2019
+ documentId: row.document_id,
2020
+ startLine: row.start_line,
2021
+ endLine: row.end_line,
2022
+ relativePath: row.relative_path
2023
+ };
2024
+ }
2025
+ function resolveIndexedFile(db, filePattern) {
2026
+ return resolveDocumentCandidates(db, filePattern, { allowMultiple: false })[0]?.relativePath ?? null;
2027
+ }
2028
+ function resolveIndexedPaths(db, filePattern) {
2029
+ return resolveDocumentCandidates(db, filePattern, { allowMultiple: true }).map((candidate) => candidate.relativePath);
2030
+ }
2031
+ function normalizeLookupPattern(symbolPattern) {
2032
+ return symbolPattern.trim().replace(/\(\)$/, "").replace(/\(.*$/, "");
2033
+ }
2034
+ function lookupTokens(symbolPattern) {
2035
+ const cleaned = normalizeLookupPattern(symbolPattern);
2036
+ const tokens = cleaned.split(/[^A-Za-z0-9_]+/).map((token) => token.trim()).filter((token) => token.length > 0);
2037
+ return tokens.length > 0 ? [...new Set(tokens)] : [cleaned];
2038
+ }
2039
+ function getSymbolLookupCandidates(db, tokens) {
2040
+ const tokenClauses = tokens.map(
2041
+ () => `(gs.symbol LIKE ? OR d.relative_path LIKE ? OR COALESCE(gs.display_name, '') LIKE ?)`
2042
+ );
2043
+ const params = tokens.flatMap((token) => {
2044
+ const like = `%${token}%`;
2045
+ return [like, like, like];
2046
+ });
2047
+ return db.all(
2048
+ `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path, gs.display_name
2049
+ FROM global_symbols gs
2050
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2051
+ JOIN documents d ON der.document_id = d.id
2052
+ WHERE ${tokenClauses.join("\n AND ")}
2053
+ ${db.pathExclusionsFor("d")}
2054
+ LIMIT 200`,
2055
+ ...params
2056
+ );
2057
+ }
2058
+ function scoreSymbolCandidate(row, originalPattern, cleanedPattern, tokens) {
2059
+ const original = originalPattern.toLowerCase();
2060
+ const cleaned = cleanedPattern.toLowerCase();
2061
+ const noParens = cleaned.replace(/\(\)$/, "");
2062
+ const raw = row.symbol.toLowerCase();
2063
+ const short = shortenSymbol(row.symbol).toLowerCase();
2064
+ const leaf = leafName(row.symbol).toLowerCase();
2065
+ const display = (row.display_name ?? "").toLowerCase();
2066
+ const path2 = row.relative_path.toLowerCase();
2067
+ const looksPathLike = /[/:.]/.test(cleanedPattern);
2068
+ let score = 0;
2069
+ if (raw === original || raw === cleaned) score += 1e3;
2070
+ if (short === original || short === cleaned) score += 950;
2071
+ if (path2 === original || path2 === cleaned) score += 925;
2072
+ if (path2.endsWith(`/${cleaned}`) || path2.endsWith(`/${original}`)) score += 875;
2073
+ if (display === noParens) score += 850;
2074
+ if (leaf === noParens) score += 825;
2075
+ if (`${leaf}()` === original || `${leaf}()` === cleaned) score += 820;
2076
+ if (short.endsWith(`:${cleaned}`) || short.endsWith(`:${noParens}`) || short.endsWith(`:${noParens}()`)) score += 800;
2077
+ if (raw.includes(cleaned)) score += 120;
2078
+ if (short.includes(cleaned)) score += 140;
2079
+ if (path2.includes(cleaned)) score += 140;
2080
+ if (display.includes(cleaned)) score += 110;
2081
+ if (tokens.every((token) => {
2082
+ const lower = token.toLowerCase();
2083
+ return raw.includes(lower) || short.includes(lower) || path2.includes(lower) || display.includes(lower);
2084
+ })) {
2085
+ score += 100 + tokens.length * 15;
1465
2086
  }
1466
2087
  if (isFunctionLikeSymbol(row.symbol) && leaf === noParens) {
1467
2088
  score += 60;
@@ -1494,820 +2115,1055 @@ function getCalleeRowsForSymbol(db, symbol, opts = {}) {
1494
2115
  ${opts.limit ? "LIMIT ?" : ""}`,
1495
2116
  ...calleeQueryParams(symbol, opts.limit)
1496
2117
  );
1497
- return rows.filter((row) => !db.isIgnored(row.file)).map((row) => ({
2118
+ const primary = rows.filter((row) => !db.isIgnored(row.file)).map((row) => ({
1498
2119
  symbol: row.symbol,
1499
2120
  file: row.file,
1500
2121
  chunkId: row.chunk_id
1501
2122
  }));
1502
- }
1503
- function calleeQueryParams(symbol, limit) {
1504
- const params = [
1505
- symbol.documentId,
1506
- symbol.startLine,
1507
- symbol.endLine,
1508
- symbol.symbolId
1509
- ];
1510
- if (typeof limit === "number") {
1511
- params.push(limit);
2123
+ const sourceFallback = getSourceBackedCalleeRows(db, symbol, opts.limit);
2124
+ if (sourceFallback.length === 0) {
2125
+ return primary;
1512
2126
  }
1513
- return params;
1514
- }
1515
- function uniquePatterns(patterns) {
1516
- return [...new Set(patterns)];
2127
+ if (primary.length === 0) {
2128
+ return applyLimit(sourceFallback, opts.limit);
2129
+ }
2130
+ const seen = new Set(primary.map((row) => `${row.symbol}|${row.file}`));
2131
+ const merged = [...primary];
2132
+ for (const row of sourceFallback) {
2133
+ const key = `${row.symbol}|${row.file}`;
2134
+ if (seen.has(key)) continue;
2135
+ seen.add(key);
2136
+ merged.push(row);
2137
+ }
2138
+ return applyLimit(merged, opts.limit);
1517
2139
  }
1518
-
1519
- // src/queries/trace.ts
1520
- function trace(db, symbolPattern) {
1521
- const match = findFirstSymbolMatch(db, symbolPattern);
2140
+ function getCallerRowsForSymbol(db, symbol, opts = {}) {
2141
+ const match = getFullSymbolMatch(db, symbol);
1522
2142
  if (!match) {
1523
- return { definitions: [], referencedBy: [] };
2143
+ return [];
1524
2144
  }
1525
- const defRows = db.all(
1526
- `SELECT d.relative_path, der.start_line, der.end_line,
1527
- REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
1528
- FROM global_symbols gs
1529
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1530
- JOIN documents d ON der.document_id = d.id
1531
- WHERE gs.id = ?
1532
- ORDER BY d.relative_path, der.start_line
1533
- LIMIT 10`,
1534
- match.symbolId
1535
- );
1536
- const definitions = defRows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
1537
- relativePath: r.relative_path,
1538
- startLine: r.start_line,
1539
- endLine: r.end_line,
1540
- signature: cleanSignature(r.sig)
1541
- }));
1542
- const refRows = db.all(
1543
- `SELECT DISTINCT d.relative_path
1544
- FROM mentions m
1545
- JOIN chunks c ON m.chunk_id = c.id
1546
- JOIN documents d ON c.document_id = d.id
1547
- WHERE m.symbol_id = ?
1548
- AND m.role != 1
1549
- ORDER BY d.relative_path`,
1550
- match.symbolId
1551
- );
1552
- const referencedBy = refRows.map((r) => r.relative_path).filter((p) => !db.isIgnored(p));
1553
- return { definitions, referencedBy };
1554
- }
1555
-
1556
- // src/queries/deps.ts
1557
- function deps(db, filePattern) {
1558
- const rows = db.all(
1559
- `SELECT DISTINCT d2.relative_path
1560
- FROM mentions m
1561
- JOIN chunks c ON m.chunk_id = c.id
1562
- JOIN documents d1 ON c.document_id = d1.id
1563
- JOIN global_symbols gs ON m.symbol_id = gs.id
1564
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1565
- JOIN documents d2 ON der.document_id = d2.id
1566
- WHERE d1.relative_path LIKE ?
1567
- AND d2.relative_path <> d1.relative_path
1568
- AND ${db.localSymbolPredicate}
1569
- ORDER BY d2.relative_path`,
1570
- `%${filePattern}%`
1571
- );
1572
- return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({ relativePath: r.relative_path }));
1573
- }
1574
- function rdeps(db, filePattern) {
1575
- const rows = db.all(
1576
- `SELECT DISTINCT d1.relative_path
1577
- FROM mentions m
1578
- JOIN chunks c ON m.chunk_id = c.id
1579
- JOIN documents d1 ON c.document_id = d1.id
1580
- JOIN global_symbols gs ON m.symbol_id = gs.id
1581
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1582
- JOIN documents d2 ON der.document_id = d2.id
1583
- WHERE d2.relative_path LIKE ?
1584
- AND d1.relative_path NOT LIKE ?
1585
- ORDER BY d1.relative_path`,
1586
- `%${filePattern}%`,
1587
- `%${filePattern}%`
1588
- );
1589
- return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({ relativePath: r.relative_path }));
1590
- }
1591
-
1592
- // src/queries/system.ts
1593
- function system(db, modulePattern) {
1594
- const fileRows = db.all(
1595
- `SELECT relative_path FROM documents
1596
- WHERE relative_path LIKE ?
1597
- ORDER BY relative_path`,
1598
- `%${modulePattern}%`
1599
- );
1600
- const files2 = fileRows.map((r) => r.relative_path).filter((p) => !db.isIgnored(p));
1601
- const symbolRows = db.all(
1602
- `SELECT der.start_line, der.end_line, gs.symbol,
1603
- REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
1604
- FROM defn_enclosing_ranges der
1605
- JOIN global_symbols gs ON der.symbol_id = gs.id
1606
- JOIN documents d ON der.document_id = d.id
1607
- WHERE d.relative_path LIKE ?
1608
- AND ${db.localSymbolPredicate}
1609
- ${db.symbolNoise}
1610
- AND gs.documentation IS NOT NULL
1611
- ORDER BY d.relative_path, der.start_line`,
1612
- `%${modulePattern}%`
1613
- );
1614
- const symbols2 = symbolRows.map((r) => ({
1615
- startLine: r.start_line,
1616
- endLine: r.end_line,
1617
- symbol: r.symbol,
1618
- shortName: shortenSymbol(r.symbol),
1619
- signature: cleanSignature(r.sig)
1620
- }));
1621
- const depRows = db.all(
1622
- `SELECT DISTINCT d2.relative_path
1623
- FROM mentions m
1624
- JOIN chunks c ON m.chunk_id = c.id
1625
- JOIN documents d1 ON c.document_id = d1.id
1626
- JOIN global_symbols gs ON m.symbol_id = gs.id
1627
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1628
- JOIN documents d2 ON der.document_id = d2.id
1629
- WHERE d1.relative_path LIKE ?
1630
- AND d2.relative_path NOT LIKE ?
1631
- AND ${db.localSymbolPredicate}
1632
- ORDER BY d2.relative_path`,
1633
- `%${modulePattern}%`,
1634
- `%${modulePattern}%`
1635
- );
1636
- const dependsOn = depRows.map((r) => r.relative_path).filter((p) => !db.isIgnored(p));
1637
- const rdepRows = db.all(
1638
- `SELECT DISTINCT d1.relative_path
1639
- FROM mentions m
1640
- JOIN chunks c ON m.chunk_id = c.id
1641
- JOIN documents d1 ON c.document_id = d1.id
1642
- JOIN global_symbols gs ON m.symbol_id = gs.id
1643
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1644
- JOIN documents d2 ON der.document_id = d2.id
1645
- WHERE d2.relative_path LIKE ?
1646
- AND d1.relative_path NOT LIKE ?
1647
- ORDER BY d1.relative_path`,
1648
- `%${modulePattern}%`,
1649
- `%${modulePattern}%`
1650
- );
1651
- const dependedOnBy = rdepRows.map((r) => r.relative_path).filter((p) => !db.isIgnored(p));
1652
- return { files: files2, symbols: symbols2, dependsOn, dependedOnBy };
1653
- }
1654
-
1655
- // src/queries/surface.ts
1656
- function surface(db, modulePattern) {
1657
- const rows = db.all(
1658
- `SELECT DISTINCT d1.relative_path, gs.symbol
2145
+ const primary = db.all(
2146
+ `SELECT DISTINCT caller_gs.symbol AS caller_symbol, caller_d.relative_path AS caller_file
1659
2147
  FROM mentions m
1660
2148
  JOIN chunks c ON m.chunk_id = c.id
1661
- JOIN documents d1 ON c.document_id = d1.id
1662
- JOIN global_symbols gs ON m.symbol_id = gs.id
1663
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1664
- JOIN documents d2 ON der.document_id = d2.id
1665
- WHERE d2.relative_path LIKE ?
1666
- AND d1.relative_path NOT LIKE ?
1667
- AND ${db.localSymbolPredicate}
2149
+ JOIN documents ref_d ON c.document_id = ref_d.id
2150
+ JOIN defn_enclosing_ranges caller_der
2151
+ ON caller_der.document_id = ref_d.id
2152
+ AND c.start_line >= caller_der.start_line
2153
+ AND c.end_line <= caller_der.end_line
2154
+ JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id
2155
+ JOIN documents caller_d ON caller_der.document_id = caller_d.id
2156
+ WHERE m.symbol_id = ?
1668
2157
  AND m.role != 1
1669
- ORDER BY d1.relative_path`,
1670
- `%${modulePattern}%`,
1671
- `%${modulePattern}%`
1672
- );
1673
- return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
1674
- consumer: r.relative_path,
1675
- symbol: r.symbol,
1676
- shortName: shortenSymbol(r.symbol)
2158
+ AND caller_gs.id != ?
2159
+ ${db.symbolNoiseFor("caller_gs")}
2160
+ ${db.pathExclusionsFor("caller_d")}
2161
+ ORDER BY caller_d.relative_path
2162
+ ${opts.limit ? "LIMIT ?" : ""}`,
2163
+ ...callerQueryParams(match, opts.limit)
2164
+ ).filter((row) => !db.isIgnored(row.caller_file)).map((row) => ({
2165
+ symbol: row.caller_symbol,
2166
+ file: row.caller_file
1677
2167
  }));
1678
- }
1679
-
1680
- // src/entry-surfaces.ts
1681
- var liveBarrelCache = /* @__PURE__ */ new WeakMap();
1682
- function normalizePath(path2) {
1683
- return path2.replace(/\\/g, "/");
1684
- }
1685
- function isBarrelFile(path2) {
1686
- const normalized = normalizePath(path2);
1687
- return normalized === "index.ts" || normalized === "index.js" || normalized.endsWith("/index.ts") || normalized.endsWith("/index.js") || normalized.endsWith("/mod.rs") || normalized.endsWith("/__init__.py");
1688
- }
1689
- function isWorkerEntrySurface(path2) {
1690
- const normalized = normalizePath(path2);
1691
- return /(^|\/)[^/]*worker\.(ts|js|mjs|cjs|rs|py|go)$/.test(normalized);
1692
- }
1693
- function isStructuralEntrySurface(path2) {
1694
- const normalized = normalizePath(path2);
1695
- const segments = normalized.split("/");
1696
- const basename = segments[segments.length - 1] ?? normalized;
1697
- if (basename === "cli.ts" || basename === "cli.js" || basename === "postinstall.ts" || basename === "postinstall.js" || basename === "main.ts" || basename === "main.js" || basename === "main.rs" || basename === "main.go" || basename === "main.py") {
1698
- return true;
2168
+ const sourceFallback = getPythonSourceCallerRows(db, match, opts.limit);
2169
+ if (sourceFallback.length === 0) {
2170
+ return primary;
1699
2171
  }
1700
- if (basename === "index.ts" || basename === "index.js") {
1701
- return segments.length <= 2;
2172
+ const merged = sourceFallback.length > 0 ? [...sourceFallback] : [];
2173
+ const seen = new Set(merged.map((row) => `${row.symbol}|${row.file}`));
2174
+ for (const row of primary) {
2175
+ const key = `${row.symbol}|${row.file}`;
2176
+ if (seen.has(key)) continue;
2177
+ if (isFunctionLikeSymbol(row.symbol) || merged.length === 0) {
2178
+ seen.add(key);
2179
+ merged.push(row);
2180
+ }
1702
2181
  }
1703
- return normalized.endsWith("/mod.rs") || normalized.endsWith("/__init__.py");
1704
- }
1705
- function getIndexedPaths(db) {
1706
- return db.all(
1707
- `SELECT d.relative_path
1708
- FROM documents d
1709
- WHERE 1 = 1
1710
- ${db.pathExclusionsFor("d")}
1711
- ORDER BY d.relative_path`
1712
- ).map((row) => row.relative_path).filter((path2) => !db.isIgnored(path2));
2182
+ return applyLimit(merged, opts.limit);
1713
2183
  }
1714
- function getLiveBarrelPaths(db) {
1715
- const cached = liveBarrelCache.get(db);
1716
- if (cached) {
1717
- return cached;
2184
+ function getSourceReferenceSites(db, symbol) {
2185
+ const match = getFullSymbolMatch(db, symbol);
2186
+ if (!match) {
2187
+ return [];
1718
2188
  }
1719
- const graph = buildFileDepGraph(db);
1720
- const queue = getIndexedPaths(db).filter(
1721
- (path2) => isStructuralEntrySurface(path2) || isWorkerEntrySurface(path2)
2189
+ const identifier = leafName(match.symbol);
2190
+ if (!identifier || !hasUniqueLeafDefinition(db, identifier, match.symbolId)) {
2191
+ return [];
2192
+ }
2193
+ const documents = db.all(
2194
+ `SELECT relative_path
2195
+ FROM documents
2196
+ WHERE 1 = 1
2197
+ ${db.pathExclusionsFor("documents")}
2198
+ ORDER BY relative_path`
1722
2199
  );
1723
- const visited = /* @__PURE__ */ new Set();
1724
- const liveBarrels = /* @__PURE__ */ new Set();
1725
- while (queue.length > 0) {
1726
- const current = queue.shift();
1727
- if (visited.has(current)) {
1728
- continue;
1729
- }
1730
- visited.add(current);
1731
- if (isBarrelFile(current)) {
1732
- liveBarrels.add(current);
1733
- }
1734
- for (const dep of graph.get(current) ?? []) {
1735
- if (!visited.has(dep)) {
1736
- queue.push(dep);
1737
- }
2200
+ const sites = [];
2201
+ const seen = /* @__PURE__ */ new Set();
2202
+ for (const document of documents) {
2203
+ if (db.isIgnored(document.relative_path)) continue;
2204
+ const lines = findIdentifierLines(db, document.relative_path, identifier, document.relative_path === match.relativePath ? { excludeStartLine: match.startLine, excludeEndLine: match.endLine } : {});
2205
+ for (const line of lines) {
2206
+ const enclosing = db.get(
2207
+ `SELECT gs.symbol
2208
+ FROM defn_enclosing_ranges der
2209
+ JOIN global_symbols gs ON der.symbol_id = gs.id
2210
+ JOIN documents d ON der.document_id = d.id
2211
+ WHERE d.relative_path = ?
2212
+ AND der.start_line <= ?
2213
+ AND der.end_line >= ?
2214
+ ORDER BY (der.end_line - der.start_line) ASC
2215
+ LIMIT 1`,
2216
+ document.relative_path,
2217
+ line,
2218
+ line
2219
+ );
2220
+ const key = `${document.relative_path}|${line}|${enclosing?.symbol ?? ""}`;
2221
+ if (seen.has(key)) continue;
2222
+ seen.add(key);
2223
+ sites.push({
2224
+ file: document.relative_path,
2225
+ line,
2226
+ enclosingSymbol: enclosing?.symbol ?? null
2227
+ });
1738
2228
  }
1739
2229
  }
1740
- liveBarrelCache.set(db, liveBarrels);
1741
- return liveBarrels;
2230
+ return sites;
1742
2231
  }
1743
- function isLiveBarrel(db, path2) {
1744
- return getLiveBarrelPaths(db).has(normalizePath(path2));
2232
+ function calleeQueryParams(symbol, limit) {
2233
+ const params = [
2234
+ symbol.documentId,
2235
+ symbol.startLine,
2236
+ symbol.endLine,
2237
+ symbol.symbolId
2238
+ ];
2239
+ if (typeof limit === "number") {
2240
+ params.push(limit);
2241
+ }
2242
+ return params;
1745
2243
  }
1746
- function isEntrySurface(db, path2) {
1747
- return isStructuralEntrySurface(path2) || isWorkerEntrySurface(path2) || isLiveBarrel(db, path2);
2244
+ function callerQueryParams(symbol, limit) {
2245
+ const params = [symbol.symbolId, symbol.symbolId];
2246
+ if (typeof limit === "number") {
2247
+ params.push(limit);
2248
+ }
2249
+ return params;
1748
2250
  }
1749
- function getInactiveBarrelPaths(db) {
1750
- const liveBarrels = getLiveBarrelPaths(db);
1751
- return getIndexedPaths(db).filter((path2) => isBarrelFile(path2)).filter((path2) => !liveBarrels.has(path2));
2251
+ function getPythonSourceCalleeRows(db, symbol, limit) {
2252
+ const match = getFullSymbolMatch(db, symbol);
2253
+ if (!match || !isPythonDocument(db, match.relativePath)) {
2254
+ return [];
2255
+ }
2256
+ const definitions = getDefinitionsForFile(db, match.relativePath);
2257
+ const current = definitions.find((definition) => definition.symbolId === match.symbolId);
2258
+ if (!current) {
2259
+ return [];
2260
+ }
2261
+ const imports2 = getSourceImports(db, match.relativePath);
2262
+ const bindings = new Map(
2263
+ getSourceConstructorBindings(db, match.relativePath, {
2264
+ startLine: match.startLine,
2265
+ endLine: match.endLine
2266
+ }).map((binding) => [binding.localName, binding.typeName])
2267
+ );
2268
+ const rows = [];
2269
+ const seen = /* @__PURE__ */ new Set();
2270
+ for (const call of getSourceCalls(db, match.relativePath, {
2271
+ startLine: match.startLine,
2272
+ endLine: match.endLine
2273
+ })) {
2274
+ const resolved = resolvePythonCallTarget(
2275
+ db,
2276
+ current,
2277
+ definitions,
2278
+ imports2,
2279
+ bindings,
2280
+ call.receiverName,
2281
+ call.calleeName
2282
+ );
2283
+ if (!resolved || resolved.symbolId === match.symbolId || db.isIgnored(resolved.relativePath)) continue;
2284
+ const chunkId = 1e9 + call.line;
2285
+ const key = `${resolved.symbol}|${resolved.relativePath}|${chunkId}`;
2286
+ if (seen.has(key)) continue;
2287
+ seen.add(key);
2288
+ rows.push({
2289
+ symbol: resolved.symbol,
2290
+ file: resolved.relativePath,
2291
+ chunkId
2292
+ });
2293
+ }
2294
+ return applyLimit(rows, limit);
1752
2295
  }
1753
-
1754
- // src/queries/dead.ts
1755
- function dead(db, opts = {}) {
1756
- const {
1757
- scope,
1758
- minLoc = 1,
1759
- includeTests = false,
1760
- skipBarrels = false,
1761
- includeMembers = false
1762
- } = opts;
1763
- const params = [minLoc];
1764
- let testFileExclusions = "";
1765
- let memberExclusion = "";
1766
- let barrelExclusions = "";
1767
- if (scope) {
1768
- params.push(`%${scope}%`);
2296
+ function getJavaScriptSourceCalleeRows(db, symbol, limit) {
2297
+ const match = getFullSymbolMatch(db, symbol);
2298
+ if (!match || !isJavaScriptDocument(db, match.relativePath)) {
2299
+ return [];
1769
2300
  }
1770
- if (!includeTests) {
1771
- testFileExclusions = `
1772
- AND ${testFileExclusionSql("d", TEST_SUPPORT_PATH_PATTERNS)}
1773
- `;
2301
+ const definitions = getDefinitionsForFile(db, match.relativePath);
2302
+ const current = definitions.find((definition) => definition.symbolId === match.symbolId);
2303
+ if (!current) {
2304
+ return [];
1774
2305
  }
1775
- if (!includeMembers) {
1776
- memberExclusion = `AND gs.symbol NOT LIKE '%#%'`;
2306
+ const imports2 = getSourceImports(db, match.relativePath);
2307
+ const bindings = new Map(
2308
+ getSourceConstructorBindings(db, match.relativePath, {
2309
+ startLine: match.startLine,
2310
+ endLine: match.endLine
2311
+ }).map((binding) => [binding.localName, binding.typeName])
2312
+ );
2313
+ const rows = [];
2314
+ const seen = /* @__PURE__ */ new Set();
2315
+ for (const call of getSourceCalls(db, match.relativePath, {
2316
+ startLine: match.startLine,
2317
+ endLine: match.endLine
2318
+ })) {
2319
+ const resolved = resolveJavaScriptCallTarget(
2320
+ db,
2321
+ current,
2322
+ definitions,
2323
+ imports2,
2324
+ bindings,
2325
+ call.receiverName,
2326
+ call.calleeName
2327
+ );
2328
+ if (!resolved || resolved.symbolId === match.symbolId || db.isIgnored(resolved.relativePath)) continue;
2329
+ const chunkId = 1e9 + call.line;
2330
+ const key = `${resolved.symbol}|${resolved.relativePath}|${chunkId}`;
2331
+ if (seen.has(key)) continue;
2332
+ seen.add(key);
2333
+ rows.push({
2334
+ symbol: resolved.symbol,
2335
+ file: resolved.relativePath,
2336
+ chunkId
2337
+ });
1777
2338
  }
1778
- if (skipBarrels) {
1779
- const inactiveBarrelPaths = getInactiveBarrelPaths(db);
1780
- if (inactiveBarrelPaths.length > 0) {
1781
- barrelExclusions = `AND ref_d.relative_path NOT IN (${inactiveBarrelPaths.map(() => "?").join(", ")})`;
1782
- params.push(...inactiveBarrelPaths);
2339
+ return applyLimit(rows, limit);
2340
+ }
2341
+ function getSourceBackedCalleeRows(db, symbol, limit) {
2342
+ const match = getFullSymbolMatch(db, symbol);
2343
+ if (!match) {
2344
+ return [];
2345
+ }
2346
+ if (isPythonDocument(db, match.relativePath)) {
2347
+ return getPythonSourceCalleeRows(db, match, limit);
2348
+ }
2349
+ if (isJavaScriptDocument(db, match.relativePath)) {
2350
+ return getJavaScriptSourceCalleeRows(db, match, limit);
2351
+ }
2352
+ return [];
2353
+ }
2354
+ function getPythonSourceCallerRows(db, target, limit) {
2355
+ if (!isPythonDocument(db, target.relativePath)) {
2356
+ return [];
2357
+ }
2358
+ const rows = [];
2359
+ const seen = /* @__PURE__ */ new Set();
2360
+ for (const candidate of getAllFunctionLikeDefinitions(db)) {
2361
+ if (candidate.symbolId === target.symbolId) continue;
2362
+ const callees = getPythonSourceCalleeRows(db, candidate);
2363
+ if (!callees.some((callee) => callee.symbol === target.symbol)) continue;
2364
+ const key = `${candidate.symbol}|${candidate.relativePath}`;
2365
+ if (seen.has(key) || db.isIgnored(candidate.relativePath)) continue;
2366
+ seen.add(key);
2367
+ rows.push({
2368
+ symbol: candidate.symbol,
2369
+ file: candidate.relativePath
2370
+ });
2371
+ if (typeof limit === "number" && rows.length >= limit) {
2372
+ break;
1783
2373
  }
1784
2374
  }
1785
- const sql = `
1786
- SELECT
1787
- d.relative_path,
1788
- der.start_line,
1789
- der.end_line,
1790
- (der.end_line - der.start_line + 1) AS loc,
1791
- gs.symbol,
1792
- (SELECT COUNT(*) FROM mentions m2
1793
- JOIN chunks c2 ON m2.chunk_id = c2.id
1794
- WHERE m2.symbol_id = gs.id AND m2.role != 1 AND c2.document_id = d.id
1795
- ) AS same_file_refs
1796
- FROM global_symbols gs
1797
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1798
- JOIN documents d ON der.document_id = d.id
1799
- WHERE 1 = 1
1800
- ${db.pathExclusionsFor("d")}
1801
- ${db.symbolNoiseFor("gs")}
1802
- AND (der.end_line - der.start_line + 1) >= ?
1803
- ${scope ? "AND d.relative_path LIKE ?" : ""}
1804
- ${testFileExclusions}
1805
- ${memberExclusion}
1806
- AND NOT EXISTS (
1807
- SELECT 1
1808
- FROM mentions ref_m
1809
- JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
1810
- JOIN documents ref_d ON ref_c.document_id = ref_d.id
1811
- WHERE ref_m.symbol_id = gs.id
1812
- AND ref_m.role != 1
1813
- AND ref_d.id != d.id
1814
- ${barrelExclusions}
1815
- )
1816
- ORDER BY (der.end_line - der.start_line + 1) DESC, d.relative_path, der.start_line
1817
- `;
1818
- const rows = db.all(sql, ...params);
1819
- let deadCodeCount = 0;
1820
- let fileInternalCount = 0;
1821
- let totalLoc = 0;
1822
- const symbols2 = rows.filter((r) => !db.isIgnored(r.relative_path)).filter((r) => !isEntrySurface(db, r.relative_path)).map((r) => {
1823
- const kind = r.same_file_refs === 0 ? "dead-code" : "file-internal";
1824
- if (kind === "dead-code") deadCodeCount++;
1825
- else fileInternalCount++;
1826
- totalLoc += r.loc;
1827
- return {
1828
- relativePath: r.relative_path,
1829
- startLine: r.start_line,
1830
- endLine: r.end_line,
1831
- loc: r.loc,
1832
- symbol: r.symbol,
1833
- shortName: shortenSymbol(r.symbol),
1834
- sameFileRefs: r.same_file_refs,
1835
- kind
1836
- };
1837
- });
1838
- return {
1839
- symbols: symbols2,
1840
- totalCount: symbols2.length,
1841
- deadCodeCount,
1842
- fileInternalCount,
1843
- totalLoc
1844
- };
2375
+ return rows;
1845
2376
  }
1846
-
1847
- // src/queries/hotspots.ts
1848
- function hotspots(db, opts = {}) {
1849
- const { limit = 30, scope } = opts;
1850
- const scopeFilter = scope ? `AND def_d.relative_path LIKE '%${scope}%'` : "";
1851
- const rows = db.all(
1852
- `SELECT
1853
- gs.symbol,
1854
- COUNT(*) AS ref_count,
1855
- COUNT(DISTINCT ref_d.id) AS file_count,
1856
- def_d.relative_path AS defined_in
1857
- FROM mentions m
1858
- JOIN chunks c ON m.chunk_id = c.id
1859
- JOIN documents ref_d ON c.document_id = ref_d.id
1860
- JOIN global_symbols gs ON m.symbol_id = gs.id
1861
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1862
- JOIN documents def_d ON der.document_id = def_d.id
1863
- WHERE m.role != 1
1864
- ${db.pathExclusionsFor("def_d")}
1865
- ${db.symbolNoiseFor("gs")}
1866
- ${scopeFilter}
1867
- GROUP BY gs.id
1868
- ORDER BY ref_count DESC
1869
- LIMIT ?`,
1870
- limit
2377
+ function getFullSymbolMatch(db, symbol) {
2378
+ if ("symbol" in symbol && "relativePath" in symbol) {
2379
+ return symbol;
2380
+ }
2381
+ const row = db.get(
2382
+ `SELECT gs.symbol, d.relative_path
2383
+ FROM global_symbols gs
2384
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2385
+ JOIN documents d ON der.document_id = d.id
2386
+ WHERE gs.id = ?
2387
+ LIMIT 1`,
2388
+ symbol.symbolId
1871
2389
  );
1872
- return rows.filter((r) => !db.isIgnored(r.defined_in)).map((r) => ({
1873
- symbol: r.symbol,
1874
- shortName: shortenSymbol(r.symbol),
1875
- refCount: r.ref_count,
1876
- fileCount: r.file_count,
1877
- definedIn: r.defined_in
1878
- }));
2390
+ if (!row) {
2391
+ return null;
2392
+ }
2393
+ return {
2394
+ ...symbol,
2395
+ symbol: row.symbol,
2396
+ relativePath: row.relative_path
2397
+ };
1879
2398
  }
1880
-
1881
- // src/source-analysis.ts
1882
- import {
1883
- existsSync as existsSync6,
1884
- readFileSync as readFileSync4
1885
- } from "fs";
1886
- import {
1887
- dirname as dirname2,
1888
- extname,
1889
- join as join6,
1890
- relative as relative2,
1891
- resolve as resolve2
1892
- } from "path";
1893
- var SOURCE_IMPORT_CACHE = /* @__PURE__ */ new WeakMap();
1894
- var INDEXED_PATH_CACHE = /* @__PURE__ */ new WeakMap();
1895
- var SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
1896
- var PYTHON_SOURCE_EXTENSIONS = [".py", ".pyi"];
1897
- function getSourceImports(db, relativePath) {
1898
- const cache = getCachedMap(SOURCE_IMPORT_CACHE, db);
1899
- const normalized = normalizePath2(relativePath);
1900
- const cached = cache.get(normalized);
2399
+ function getDefinitionsForFile(db, relativePath) {
2400
+ let cache = FILE_DEFINITION_CACHE.get(db);
2401
+ if (!cache) {
2402
+ cache = /* @__PURE__ */ new Map();
2403
+ FILE_DEFINITION_CACHE.set(db, cache);
2404
+ }
2405
+ const cached = cache.get(relativePath);
1901
2406
  if (cached) {
1902
2407
  return cached;
1903
2408
  }
1904
- const fullPath = join6(db.config.projectRoot, normalized);
1905
- if (!existsSync6(fullPath)) {
1906
- cache.set(normalized, []);
1907
- return [];
1908
- }
1909
- const source = readFileSync4(fullPath, "utf-8");
1910
- const parsed = isPythonSourcePath(normalized) ? parsePythonImports(db, normalized, source) : parseJavaScriptImports(db, normalized, source);
1911
- cache.set(normalized, parsed);
1912
- return parsed;
2409
+ const definitions = db.all(
2410
+ `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
2411
+ FROM global_symbols gs
2412
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2413
+ JOIN documents d ON der.document_id = d.id
2414
+ WHERE d.relative_path = ?
2415
+ ${db.symbolNoiseFor("gs")}
2416
+ ORDER BY der.start_line, der.end_line`,
2417
+ relativePath
2418
+ ).map((row) => ({
2419
+ symbolId: row.id,
2420
+ symbol: row.symbol,
2421
+ documentId: row.document_id,
2422
+ startLine: row.start_line,
2423
+ endLine: row.end_line,
2424
+ relativePath: row.relative_path,
2425
+ leaf: leafName(row.symbol),
2426
+ parentTypeName: parentTypeName(row.symbol),
2427
+ isFunctionLike: isFunctionLikeSymbol(row.symbol),
2428
+ isTypeLike: leafSuffix(row.symbol) === "type"
2429
+ }));
2430
+ cache.set(relativePath, definitions);
2431
+ return definitions;
1913
2432
  }
1914
- function parseJavaScriptImports(db, importerPath, source) {
1915
- return parseJavaScriptImportStatements(source).flatMap((statement) => parseJavaScriptImportStatement(
1916
- db,
1917
- importerPath,
1918
- statement.clause,
1919
- statement.specifier,
1920
- statement.start,
1921
- statement.end,
1922
- source
1923
- ));
2433
+ function getAllFunctionLikeDefinitions(db) {
2434
+ const rows = db.all(
2435
+ `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
2436
+ FROM global_symbols gs
2437
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2438
+ JOIN documents d ON der.document_id = d.id
2439
+ WHERE 1 = 1
2440
+ ${db.pathExclusionsFor("d")}
2441
+ ${db.symbolNoiseFor("gs")}
2442
+ ORDER BY d.relative_path, der.start_line`
2443
+ );
2444
+ return rows.filter((row) => !db.isIgnored(row.relative_path)).filter((row) => isFunctionLikeSymbol(row.symbol)).map((row) => ({
2445
+ symbolId: row.id,
2446
+ symbol: row.symbol,
2447
+ documentId: row.document_id,
2448
+ startLine: row.start_line,
2449
+ endLine: row.end_line,
2450
+ relativePath: row.relative_path,
2451
+ leaf: leafName(row.symbol),
2452
+ parentTypeName: parentTypeName(row.symbol),
2453
+ isFunctionLike: true,
2454
+ isTypeLike: false
2455
+ }));
1924
2456
  }
1925
- function parseJavaScriptImportStatements(source) {
1926
- const statements = [];
1927
- const importFromRegex = /^[ \t]*import\s+([\s\S]*?)\s+from\s+['"]([^'"]+)['"]\s*;?/gm;
1928
- for (const match of source.matchAll(importFromRegex)) {
1929
- const full = match[0];
1930
- const clause = match[1];
1931
- const specifier = match[2];
1932
- if (!full || !specifier || typeof match.index !== "number") continue;
1933
- statements.push({
1934
- clause,
1935
- specifier,
1936
- start: match.index,
1937
- end: match.index + full.length
1938
- });
2457
+ function resolvePythonCallTarget(db, current, currentFileDefinitions, imports2, constructorBindings, receiverName, calleeName) {
2458
+ if (receiverName === "self" || receiverName === "cls") {
2459
+ return findDefinitionByName(currentFileDefinitions, calleeName, current.parentTypeName, ["function"]);
1939
2460
  }
1940
- const sideEffectRegex = /^[ \t]*import\s+['"]([^'"]+)['"]\s*;?/gm;
1941
- for (const match of source.matchAll(sideEffectRegex)) {
1942
- const full = match[0];
1943
- const specifier = match[1];
1944
- if (!full || !specifier || typeof match.index !== "number") continue;
1945
- statements.push({
1946
- clause: null,
1947
- specifier,
1948
- start: match.index,
1949
- end: match.index + full.length
1950
- });
2461
+ if (receiverName) {
2462
+ const inferredType = constructorBindings.get(receiverName);
2463
+ if (inferredType) {
2464
+ const boundMethod = findDefinitionByName(currentFileDefinitions, calleeName, inferredType, ["function"]);
2465
+ if (boundMethod) {
2466
+ return boundMethod;
2467
+ }
2468
+ for (const entry of imports2) {
2469
+ if (entry.localName !== inferredType || !entry.sourcePath) continue;
2470
+ const importedDefinitions = getDefinitionsForFile(db, entry.sourcePath);
2471
+ const importedMethod = findDefinitionByName(importedDefinitions, calleeName, entry.importedName, ["function"]);
2472
+ if (importedMethod) {
2473
+ return importedMethod;
2474
+ }
2475
+ }
2476
+ }
2477
+ const localClassMethod = findDefinitionByName(currentFileDefinitions, calleeName, receiverName, ["function"]);
2478
+ if (localClassMethod) {
2479
+ return localClassMethod;
2480
+ }
2481
+ const namespaceImport = imports2.find((entry) => entry.localName === receiverName && entry.sourcePath);
2482
+ if (namespaceImport?.sourcePath) {
2483
+ const importedDefinitions = getDefinitionsForFile(db, namespaceImport.sourcePath);
2484
+ const importedTypeMethod = namespaceImport.kind === "named" ? findDefinitionByName(importedDefinitions, calleeName, namespaceImport.importedName, ["function"]) : null;
2485
+ if (importedTypeMethod) {
2486
+ return importedTypeMethod;
2487
+ }
2488
+ return findDefinitionByName(importedDefinitions, calleeName, null, ["function", "type"]);
2489
+ }
2490
+ return null;
1951
2491
  }
1952
- return statements.sort((a, b) => a.start - b.start);
2492
+ const importedBinding = imports2.find((entry) => entry.localName === calleeName && entry.sourcePath);
2493
+ if (importedBinding?.sourcePath) {
2494
+ const importedDefinitions = getDefinitionsForFile(db, importedBinding.sourcePath);
2495
+ const importedName = importedBinding.importedName === "*" ? calleeName : importedBinding.importedName;
2496
+ const importedDefinition = findDefinitionByName(importedDefinitions, importedName, null, ["function", "type"]);
2497
+ if (importedDefinition) {
2498
+ return importedDefinition;
2499
+ }
2500
+ }
2501
+ return findDefinitionByName(currentFileDefinitions, calleeName, null, ["function", "type"]);
1953
2502
  }
1954
- function parseJavaScriptImportStatement(db, importerPath, clause, specifier, start, end, source) {
1955
- const resolvedSource = resolveImportPath(db, importerPath, specifier);
1956
- const body = buildUsageBody(source, start, end);
1957
- if (!clause) {
1958
- return [{
1959
- importedName: "*",
1960
- localName: null,
1961
- sourcePath: resolvedSource,
1962
- kind: "side-effect",
1963
- used: true,
1964
- usedMembers: []
1965
- }];
2503
+ function resolveJavaScriptCallTarget(db, current, currentFileDefinitions, imports2, constructorBindings, receiverName, calleeName) {
2504
+ if (receiverName === "this") {
2505
+ return findDefinitionByName(currentFileDefinitions, calleeName, current.parentTypeName, ["function"]);
1966
2506
  }
1967
- const bindings = parseImportClause(clause).map((binding) => ({
1968
- ...binding,
1969
- sourcePath: resolvedSource
1970
- }));
1971
- return bindings.map((binding) => {
1972
- if (binding.kind === "namespace") {
1973
- const usedMembers = collectNamespaceMembers(body, binding.localName);
1974
- return {
1975
- ...binding,
1976
- used: usedMembers.length > 0 || hasIdentifierUsage(body, binding.localName),
1977
- usedMembers
1978
- };
2507
+ if (receiverName) {
2508
+ const inferredType = constructorBindings.get(receiverName);
2509
+ if (inferredType) {
2510
+ const boundMethod = findDefinitionByName(currentFileDefinitions, calleeName, inferredType, ["function"]);
2511
+ if (boundMethod) {
2512
+ return boundMethod;
2513
+ }
2514
+ for (const entry of imports2) {
2515
+ if (entry.localName !== inferredType || !entry.sourcePath) continue;
2516
+ const importedDefinitions = getDefinitionsForFile(db, entry.sourcePath);
2517
+ const importedMethod = findDefinitionByName(importedDefinitions, calleeName, entry.importedName, ["function"]);
2518
+ if (importedMethod) {
2519
+ return importedMethod;
2520
+ }
2521
+ }
1979
2522
  }
1980
- if (binding.kind === "side-effect") {
1981
- return { ...binding, used: true, usedMembers: [] };
2523
+ const localClassMethod = findDefinitionByName(currentFileDefinitions, calleeName, receiverName, ["function"]);
2524
+ if (localClassMethod) {
2525
+ return localClassMethod;
2526
+ }
2527
+ const namespaceImport = imports2.find((entry) => entry.localName === receiverName && entry.sourcePath);
2528
+ if (namespaceImport?.sourcePath) {
2529
+ const importedDefinitions = getDefinitionsForFile(db, namespaceImport.sourcePath);
2530
+ if (namespaceImport.kind === "named" || namespaceImport.kind === "default") {
2531
+ const importedTypeMethod = findDefinitionByName(
2532
+ importedDefinitions,
2533
+ calleeName,
2534
+ namespaceImport.importedName,
2535
+ ["function"]
2536
+ );
2537
+ if (importedTypeMethod) {
2538
+ return importedTypeMethod;
2539
+ }
2540
+ }
2541
+ return findDefinitionByName(importedDefinitions, calleeName, null, ["function", "type"]);
1982
2542
  }
1983
- return {
1984
- ...binding,
1985
- used: binding.localName ? hasIdentifierUsage(body, binding.localName) : false,
1986
- usedMembers: []
1987
- };
1988
- });
2543
+ return null;
2544
+ }
2545
+ const importedBinding = imports2.find((entry) => entry.localName === calleeName && entry.sourcePath && entry.kind !== "namespace");
2546
+ if (importedBinding?.sourcePath) {
2547
+ const importedDefinitions = getDefinitionsForFile(db, importedBinding.sourcePath);
2548
+ const importedName = importedBinding.importedName === "default" ? importedBinding.localName ?? calleeName : importedBinding.importedName;
2549
+ const importedDefinition = findDefinitionByName(importedDefinitions, importedName, null, ["function", "type"]);
2550
+ if (importedDefinition) {
2551
+ return importedDefinition;
2552
+ }
2553
+ }
2554
+ return findDefinitionByName(currentFileDefinitions, calleeName, null, ["function", "type"]);
1989
2555
  }
1990
- function parsePythonImports(db, importerPath, source) {
1991
- return collectPythonImportStatements(source).flatMap(
1992
- (statement) => parsePythonImportStatement(db, importerPath, statement, source)
1993
- );
2556
+ function findDefinitionByName(definitions, leaf, parentType, preference) {
2557
+ const candidates = definitions.filter((definition) => definition.leaf === leaf && definition.parentTypeName === parentType);
2558
+ if (candidates.length === 0) {
2559
+ return null;
2560
+ }
2561
+ for (const preferred of preference) {
2562
+ const match = candidates.find((candidate) => preferred === "function" ? candidate.isFunctionLike : candidate.isTypeLike);
2563
+ if (match) {
2564
+ return match;
2565
+ }
2566
+ }
2567
+ return candidates[0] ?? null;
1994
2568
  }
1995
- function collectPythonImportStatements(source) {
1996
- const lines = source.split("\n");
1997
- const statements = [];
1998
- let offset = 0;
1999
- for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
2000
- const line = lines[lineIndex];
2001
- const trimmed = line.trimStart();
2002
- const lineStart = offset;
2003
- offset += line.length + 1;
2004
- if (!trimmed.startsWith("import ") && !trimmed.startsWith("from ")) {
2005
- continue;
2569
+ function hasUniqueLeafDefinition(db, leaf, symbolId) {
2570
+ const rows = db.all(
2571
+ `SELECT id, symbol
2572
+ FROM global_symbols
2573
+ WHERE symbol LIKE ?
2574
+ LIMIT 50`,
2575
+ `%${leaf}%`
2576
+ );
2577
+ let count = 0;
2578
+ for (const row of rows) {
2579
+ if (leafName(row.symbol) !== leaf) continue;
2580
+ count++;
2581
+ if (count > 1 && row.id !== symbolId) {
2582
+ return false;
2006
2583
  }
2007
- let statement = line;
2008
- let statementEnd = lineStart + line.length;
2009
- let balance = pythonParenBalance(line);
2010
- while (lineIndex + 1 < lines.length && (balance > 0 || statement.trimEnd().endsWith("\\"))) {
2011
- lineIndex++;
2012
- const nextLine = lines[lineIndex];
2013
- statement += `
2014
- ${nextLine}`;
2015
- statementEnd += 1 + nextLine.length;
2016
- balance += pythonParenBalance(nextLine);
2017
- offset += nextLine.length + 1;
2584
+ }
2585
+ return count === 1;
2586
+ }
2587
+ function parentTypeName(rawSymbol) {
2588
+ const parsed = parseSymbol(rawSymbol);
2589
+ if ("kind" in parsed) {
2590
+ return null;
2591
+ }
2592
+ for (let index = parsed.descriptors.length - 2; index >= 0; index--) {
2593
+ const descriptor = parsed.descriptors[index];
2594
+ if (descriptor?.suffix === "type") {
2595
+ return descriptor.name;
2018
2596
  }
2019
- const parsed = parsePythonStatementHeader(statement);
2020
- if (parsed) {
2021
- statements.push({
2022
- ...parsed,
2023
- start: lineStart,
2024
- end: statementEnd
2025
- });
2597
+ }
2598
+ return null;
2599
+ }
2600
+ function isPythonDocument(db, relativePath) {
2601
+ const row = db.get(
2602
+ `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
2603
+ relativePath
2604
+ );
2605
+ return row?.language === "python" || relativePath.endsWith(".py") || relativePath.endsWith(".pyi");
2606
+ }
2607
+ function isJavaScriptDocument(db, relativePath) {
2608
+ const row = db.get(
2609
+ `SELECT language FROM documents WHERE relative_path = ? LIMIT 1`,
2610
+ relativePath
2611
+ );
2612
+ return row?.language === "typescript" || row?.language === "javascript" || /\.(?:[cm]?[jt]sx?)$/.test(relativePath);
2613
+ }
2614
+ function applyLimit(values, limit) {
2615
+ return typeof limit === "number" ? values.slice(0, limit) : values;
2616
+ }
2617
+ function resolveDocumentCandidates(db, filePattern, opts) {
2618
+ const normalizedPattern = normalizeLookupPath(filePattern);
2619
+ if (!normalizedPattern) {
2620
+ return [];
2621
+ }
2622
+ const rows = db.all(
2623
+ `SELECT relative_path
2624
+ FROM documents
2625
+ WHERE 1 = 1
2626
+ ${db.pathExclusionsFor("documents")}
2627
+ ORDER BY relative_path`
2628
+ );
2629
+ const scored = rows.filter((row) => !db.isIgnored(row.relative_path)).map((row) => ({
2630
+ relativePath: row.relative_path,
2631
+ score: scoreDocumentPath(row.relative_path, normalizedPattern)
2632
+ })).filter((row) => row.score > 0).sort((a, b) => b.score - a.score || a.relativePath.localeCompare(b.relativePath));
2633
+ if (scored.length === 0) {
2634
+ return [];
2635
+ }
2636
+ const exactish = scored.filter((row) => row.score >= 800);
2637
+ if (exactish.length > 0) {
2638
+ return opts.allowMultiple ? exactish : [exactish[0]];
2639
+ }
2640
+ return opts.allowMultiple ? scored : [scored[0]];
2641
+ }
2642
+ function scoreDocumentPath(relativePath, rawPattern) {
2643
+ const normalizedPath = normalizeLookupPath(relativePath);
2644
+ const pathBase = basename(normalizedPath);
2645
+ const patternBase = basename(rawPattern);
2646
+ let score = 0;
2647
+ if (normalizedPath === rawPattern) score += 1200;
2648
+ if (normalizedPath.endsWith(`/${rawPattern}`)) score += 1100;
2649
+ if (pathBase === patternBase) score += 900;
2650
+ if (normalizedPath.startsWith(`${rawPattern}/`)) score += 850;
2651
+ if (normalizedPath.includes(rawPattern)) score += 250;
2652
+ return score;
2653
+ }
2654
+ function normalizeLookupPath(filePattern) {
2655
+ return filePattern.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "").replace(/\/+$/, "");
2656
+ }
2657
+ function uniquePatterns(patterns) {
2658
+ return [...new Set(patterns)];
2659
+ }
2660
+
2661
+ // src/queries/clean-signature.ts
2662
+ function cleanSignature(sig) {
2663
+ if (!sig || !sig.trim()) return null;
2664
+ 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;
2665
+ }
2666
+
2667
+ // src/queries/symbols.ts
2668
+ function symbols(db, filePattern) {
2669
+ const resolvedPaths = resolveIndexedPaths(db, filePattern);
2670
+ if (resolvedPaths.length === 0) {
2671
+ return [];
2672
+ }
2673
+ const placeholders = resolvedPaths.map(() => "?").join(", ");
2674
+ const rows = db.all(
2675
+ `SELECT
2676
+ der.start_line,
2677
+ der.end_line,
2678
+ REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig,
2679
+ gs.symbol,
2680
+ d.relative_path
2681
+ FROM defn_enclosing_ranges der
2682
+ JOIN global_symbols gs ON der.symbol_id = gs.id
2683
+ JOIN documents d ON der.document_id = d.id
2684
+ WHERE d.relative_path IN (${placeholders})
2685
+ AND ${db.localSymbolPredicate}
2686
+ ${db.symbolNoise}
2687
+ ORDER BY d.relative_path, der.start_line`,
2688
+ ...resolvedPaths
2689
+ );
2690
+ return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
2691
+ startLine: r.start_line,
2692
+ endLine: r.end_line,
2693
+ symbol: r.symbol,
2694
+ shortName: shortenSymbol(r.symbol),
2695
+ signature: cleanSignature(r.sig)
2696
+ }));
2697
+ }
2698
+
2699
+ // src/queries/methods.ts
2700
+ function methods(db, className) {
2701
+ const rows = db.all(
2702
+ `SELECT der.start_line, der.end_line, gs.symbol
2703
+ FROM global_symbols gs
2704
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2705
+ WHERE gs.symbol LIKE ?
2706
+ AND ${db.localSymbolPredicate}
2707
+ AND gs.symbol LIKE '%().%'
2708
+ ${db.symbolNoise}
2709
+ ORDER BY der.start_line`,
2710
+ `%${className}#%`
2711
+ );
2712
+ return rows.map((r) => ({
2713
+ startLine: r.start_line,
2714
+ endLine: r.end_line,
2715
+ name: leafName(r.symbol)
2716
+ }));
2717
+ }
2718
+
2719
+ // src/queries/refs.ts
2720
+ function refs(db, symbolPattern) {
2721
+ const match = findFirstSymbolMatch(db, symbolPattern);
2722
+ if (match) {
2723
+ const sourceSites = getSourceReferenceSites(db, match).filter((site) => !db.isIgnored(site.file)).map((site) => ({
2724
+ relativePath: site.file,
2725
+ line: site.line
2726
+ }));
2727
+ if (sourceSites.length > 0) {
2728
+ return sourceSites;
2026
2729
  }
2027
2730
  }
2028
- return statements;
2731
+ const rows = db.all(
2732
+ `SELECT DISTINCT d.relative_path, c.start_line
2733
+ FROM mentions m
2734
+ JOIN chunks c ON m.chunk_id = c.id
2735
+ JOIN documents d ON c.document_id = d.id
2736
+ JOIN global_symbols gs ON m.symbol_id = gs.id
2737
+ WHERE m.symbol_id = ?
2738
+ AND ${db.localSymbolPredicate}
2739
+ AND m.role != 1
2740
+ ORDER BY d.relative_path, c.start_line`,
2741
+ match?.symbolId ?? -1
2742
+ );
2743
+ return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
2744
+ relativePath: r.relative_path,
2745
+ line: r.start_line
2746
+ }));
2029
2747
  }
2030
- function parsePythonStatementHeader(statement) {
2031
- const normalized = statement.replace(/\\\s*\n/g, " ").trim();
2032
- if (normalized.startsWith("import ")) {
2033
- return {
2034
- kind: "import",
2035
- module: null,
2036
- clause: normalized.slice("import ".length).trim()
2037
- };
2038
- }
2039
- const fromMatch = normalized.match(/^from\s+([.\w]+)\s+import\s+([\s\S]+)$/);
2040
- if (!fromMatch) {
2041
- return null;
2042
- }
2043
- let clause = fromMatch[2].trim();
2044
- if (clause.startsWith("(") && clause.endsWith(")")) {
2045
- clause = clause.slice(1, -1).trim();
2748
+
2749
+ // src/queries/trace.ts
2750
+ function trace(db, symbolPattern) {
2751
+ const match = findFirstSymbolMatch(db, symbolPattern);
2752
+ if (!match) {
2753
+ return { definitions: [], referencedBy: [] };
2046
2754
  }
2047
- return {
2048
- kind: "from",
2049
- module: fromMatch[1],
2050
- clause
2051
- };
2755
+ const defRows = db.all(
2756
+ `SELECT d.relative_path, der.start_line, der.end_line,
2757
+ gs.display_name,
2758
+ REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
2759
+ FROM global_symbols gs
2760
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2761
+ JOIN documents d ON der.document_id = d.id
2762
+ WHERE gs.id = ?
2763
+ ORDER BY d.relative_path, der.start_line
2764
+ LIMIT 10`,
2765
+ match.symbolId
2766
+ );
2767
+ const definitions = defRows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
2768
+ relativePath: r.relative_path,
2769
+ startLine: r.start_line,
2770
+ endLine: r.end_line,
2771
+ signature: buildTraceSignature(r.sig, r.display_name, match.symbol)
2772
+ }));
2773
+ const sourceSites = getSourceReferenceSites(db, match);
2774
+ const referencedBy = sourceSites.length > 0 ? sourceSites.filter((site) => !db.isIgnored(site.file)).map((site) => ({
2775
+ relativePath: site.file,
2776
+ line: site.line,
2777
+ enclosingSymbol: site.enclosingSymbol,
2778
+ enclosingShort: site.enclosingSymbol ? shortenSymbol(site.enclosingSymbol) : "(top-level)"
2779
+ })) : db.all(
2780
+ `SELECT DISTINCT d.relative_path, c.start_line AS line,
2781
+ (SELECT enc_gs.symbol
2782
+ FROM defn_enclosing_ranges enc_der
2783
+ JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
2784
+ WHERE enc_der.document_id = d.id
2785
+ AND enc_der.start_line <= c.start_line
2786
+ AND enc_der.end_line >= c.end_line
2787
+ ORDER BY (enc_der.end_line - enc_der.start_line) ASC
2788
+ LIMIT 1
2789
+ ) AS enclosing_symbol
2790
+ FROM mentions m
2791
+ JOIN chunks c ON m.chunk_id = c.id
2792
+ JOIN documents d ON c.document_id = d.id
2793
+ WHERE m.symbol_id = ?
2794
+ AND m.role != 1
2795
+ ORDER BY d.relative_path, c.start_line`,
2796
+ match.symbolId
2797
+ ).filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
2798
+ relativePath: r.relative_path,
2799
+ line: r.line,
2800
+ enclosingSymbol: r.enclosing_symbol,
2801
+ enclosingShort: r.enclosing_symbol ? shortenSymbol(r.enclosing_symbol) : "(top-level)"
2802
+ }));
2803
+ return { definitions, referencedBy };
2052
2804
  }
2053
- function parsePythonImportStatement(db, importerPath, statement, source) {
2054
- const body = buildUsageBody(source, statement.start, statement.end);
2055
- const normalizedClause = statement.clause.replace(/\n/g, " ").trim();
2056
- if (statement.kind === "import") {
2057
- return splitTopLevel(normalizedClause).flatMap((entry) => {
2058
- const cleaned = entry.trim().replace(/,$/, "");
2059
- if (!cleaned) return [];
2060
- const [moduleName, alias] = cleaned.split(/\s+as\s+/);
2061
- const importedName = moduleName.trim();
2062
- const localName = (alias ?? importedName.split(".")[0] ?? importedName).trim();
2063
- const sourcePath2 = resolvePythonImportPath(db, importerPath, importedName);
2064
- const usedMembers = collectNamespaceMembers(body, localName);
2065
- return [{
2066
- importedName,
2067
- localName,
2068
- sourcePath: sourcePath2,
2069
- kind: "namespace",
2070
- used: hasIdentifierUsage(body, localName) || usedMembers.length > 0,
2071
- usedMembers
2072
- }];
2073
- });
2805
+ function buildTraceSignature(signature, displayName, rawSymbol) {
2806
+ const cleaned = cleanSignature(signature);
2807
+ if (cleaned && !looksBogusSignature(cleaned)) {
2808
+ return cleaned;
2074
2809
  }
2075
- const sourcePath = statement.module ? resolvePythonImportPath(db, importerPath, statement.module) : null;
2076
- const results = [];
2077
- for (const entry of splitTopLevel(normalizedClause)) {
2078
- const cleaned = entry.trim().replace(/,$/, "");
2079
- if (!cleaned) continue;
2080
- if (cleaned === "*") {
2081
- results.push({
2082
- importedName: "*",
2083
- localName: null,
2084
- sourcePath,
2085
- kind: "side-effect",
2086
- used: true,
2087
- usedMembers: []
2088
- });
2089
- continue;
2090
- }
2091
- const [importedName, alias] = cleaned.split(/\s+as\s+/);
2092
- const localName = (alias ?? importedName).trim();
2093
- results.push({
2094
- importedName: importedName.trim(),
2095
- localName,
2096
- sourcePath,
2097
- kind: "named",
2098
- used: hasIdentifierUsage(body, localName),
2099
- usedMembers: []
2100
- });
2810
+ const fallback = (displayName ?? "").trim();
2811
+ if (fallback) {
2812
+ return isFunctionLikeSymbol(rawSymbol) && !fallback.endsWith("()") ? `${fallback}()` : fallback;
2101
2813
  }
2102
- return results;
2814
+ return shortenSymbol(rawSymbol);
2103
2815
  }
2104
- function parseImportClause(clause) {
2105
- const trimmed = clause.trim().replace(/^type\s+/, "");
2106
- const [first, second] = splitImportClause(trimmed);
2107
- const entries = [];
2108
- if (first) {
2109
- entries.push(...parseImportBinding(first));
2110
- }
2111
- if (second) {
2112
- entries.push(...parseImportBinding(second));
2113
- }
2114
- return entries;
2816
+ function looksBogusSignature(signature) {
2817
+ return signature.startsWith("undefined") || signature.includes("|") || signature.includes("```");
2115
2818
  }
2116
- function parseImportBinding(binding) {
2117
- const trimmed = binding.trim();
2118
- if (!trimmed) return [];
2119
- if (trimmed.startsWith("{")) {
2120
- const inner = trimmed.slice(1, -1).trim();
2121
- if (!inner) return [];
2122
- return splitTopLevel(inner).map((entry) => {
2123
- const cleaned = entry.trim().replace(/^type\s+/, "");
2124
- const [importedName, alias] = cleaned.split(/\s+as\s+/);
2125
- return {
2126
- importedName: importedName.trim(),
2127
- localName: (alias ?? importedName).trim(),
2128
- kind: "named"
2129
- };
2130
- });
2819
+
2820
+ // src/queries/deps.ts
2821
+ function deps(db, filePattern) {
2822
+ const resolvedFile = resolveIndexedFile(db, filePattern);
2823
+ if (!resolvedFile) {
2824
+ return [];
2131
2825
  }
2132
- if (trimmed.startsWith("* as ")) {
2133
- return [{
2134
- importedName: "*",
2135
- localName: trimmed.slice(5).trim(),
2136
- kind: "namespace"
2137
- }];
2826
+ const rows = db.all(
2827
+ `SELECT DISTINCT d2.relative_path
2828
+ FROM mentions m
2829
+ JOIN chunks c ON m.chunk_id = c.id
2830
+ JOIN documents d1 ON c.document_id = d1.id
2831
+ JOIN global_symbols gs ON m.symbol_id = gs.id
2832
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2833
+ JOIN documents d2 ON der.document_id = d2.id
2834
+ WHERE d1.relative_path = ?
2835
+ AND d2.relative_path <> d1.relative_path
2836
+ AND ${db.localSymbolPredicate}
2837
+ ORDER BY d2.relative_path`,
2838
+ resolvedFile
2839
+ );
2840
+ return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({ relativePath: r.relative_path }));
2841
+ }
2842
+ function rdeps(db, filePattern) {
2843
+ const resolvedFile = resolveIndexedFile(db, filePattern);
2844
+ if (!resolvedFile) {
2845
+ return [];
2138
2846
  }
2139
- return [{
2140
- importedName: "default",
2141
- localName: trimmed,
2142
- kind: "default"
2143
- }];
2847
+ const rows = db.all(
2848
+ `SELECT DISTINCT d1.relative_path
2849
+ FROM mentions m
2850
+ JOIN chunks c ON m.chunk_id = c.id
2851
+ JOIN documents d1 ON c.document_id = d1.id
2852
+ JOIN global_symbols gs ON m.symbol_id = gs.id
2853
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2854
+ JOIN documents d2 ON der.document_id = d2.id
2855
+ WHERE d2.relative_path = ?
2856
+ AND d1.relative_path != ?
2857
+ ORDER BY d1.relative_path`,
2858
+ resolvedFile,
2859
+ resolvedFile
2860
+ );
2861
+ return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({ relativePath: r.relative_path }));
2144
2862
  }
2145
- function splitImportClause(clause) {
2146
- let depth = 0;
2147
- for (let i = 0; i < clause.length; i++) {
2148
- const char = clause[i];
2149
- if (char === "{") depth++;
2150
- if (char === "}") depth--;
2151
- if (char === "," && depth === 0) {
2152
- return [clause.slice(0, i).trim(), clause.slice(i + 1).trim()];
2153
- }
2863
+
2864
+ // src/queries/system.ts
2865
+ function system(db, modulePattern) {
2866
+ const matchedPaths = resolveIndexedPaths(db, modulePattern);
2867
+ if (matchedPaths.length === 0) {
2868
+ return { files: [], symbols: [], dependsOn: [], dependedOnBy: [] };
2154
2869
  }
2155
- return [clause.trim(), null];
2870
+ const placeholders = matchedPaths.map(() => "?").join(", ");
2871
+ const fileRows = db.all(
2872
+ `SELECT relative_path FROM documents
2873
+ WHERE relative_path IN (${placeholders})
2874
+ ORDER BY relative_path`,
2875
+ ...matchedPaths
2876
+ );
2877
+ const files2 = fileRows.map((r) => r.relative_path).filter((p) => !db.isIgnored(p));
2878
+ const symbolRows = db.all(
2879
+ `SELECT der.start_line, der.end_line, gs.symbol,
2880
+ REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
2881
+ FROM defn_enclosing_ranges der
2882
+ JOIN global_symbols gs ON der.symbol_id = gs.id
2883
+ JOIN documents d ON der.document_id = d.id
2884
+ WHERE d.relative_path IN (${placeholders})
2885
+ AND ${db.localSymbolPredicate}
2886
+ ${db.symbolNoise}
2887
+ AND gs.documentation IS NOT NULL
2888
+ ORDER BY d.relative_path, der.start_line`,
2889
+ ...matchedPaths
2890
+ );
2891
+ const symbols2 = symbolRows.map((r) => ({
2892
+ startLine: r.start_line,
2893
+ endLine: r.end_line,
2894
+ symbol: r.symbol,
2895
+ shortName: shortenSymbol(r.symbol),
2896
+ signature: cleanSignature(r.sig)
2897
+ }));
2898
+ const depRows = db.all(
2899
+ `SELECT DISTINCT d2.relative_path
2900
+ FROM mentions m
2901
+ JOIN chunks c ON m.chunk_id = c.id
2902
+ JOIN documents d1 ON c.document_id = d1.id
2903
+ JOIN global_symbols gs ON m.symbol_id = gs.id
2904
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2905
+ JOIN documents d2 ON der.document_id = d2.id
2906
+ WHERE d1.relative_path IN (${placeholders})
2907
+ AND d2.relative_path NOT IN (${placeholders})
2908
+ AND ${db.localSymbolPredicate}
2909
+ ORDER BY d2.relative_path`,
2910
+ ...matchedPaths,
2911
+ ...matchedPaths
2912
+ );
2913
+ const dependsOn = depRows.map((r) => r.relative_path).filter((p) => !db.isIgnored(p));
2914
+ const rdepRows = db.all(
2915
+ `SELECT DISTINCT d1.relative_path
2916
+ FROM mentions m
2917
+ JOIN chunks c ON m.chunk_id = c.id
2918
+ JOIN documents d1 ON c.document_id = d1.id
2919
+ JOIN global_symbols gs ON m.symbol_id = gs.id
2920
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2921
+ JOIN documents d2 ON der.document_id = d2.id
2922
+ WHERE d2.relative_path IN (${placeholders})
2923
+ AND d1.relative_path NOT IN (${placeholders})
2924
+ ORDER BY d1.relative_path`,
2925
+ ...matchedPaths,
2926
+ ...matchedPaths
2927
+ );
2928
+ const dependedOnBy = rdepRows.map((r) => r.relative_path).filter((p) => !db.isIgnored(p));
2929
+ return { files: files2, symbols: symbols2, dependsOn, dependedOnBy };
2156
2930
  }
2157
- function splitTopLevel(input) {
2158
- const parts = [];
2159
- let depth = 0;
2160
- let start = 0;
2161
- for (let i = 0; i < input.length; i++) {
2162
- const char = input[i];
2163
- if (char === "{" || char === "[" || char === "(") depth++;
2164
- if (char === "}" || char === "]" || char === ")") depth--;
2165
- if (char === "," && depth === 0) {
2166
- parts.push(input.slice(start, i));
2167
- start = i + 1;
2168
- }
2931
+
2932
+ // src/queries/surface.ts
2933
+ function surface(db, modulePattern) {
2934
+ const matchedPaths = resolveIndexedPaths(db, modulePattern);
2935
+ if (matchedPaths.length === 0) {
2936
+ return [];
2169
2937
  }
2170
- parts.push(input.slice(start));
2171
- return parts;
2938
+ const placeholders = matchedPaths.map(() => "?").join(", ");
2939
+ const rows = db.all(
2940
+ `SELECT DISTINCT d1.relative_path, gs.symbol
2941
+ FROM mentions m
2942
+ JOIN chunks c ON m.chunk_id = c.id
2943
+ JOIN documents d1 ON c.document_id = d1.id
2944
+ JOIN global_symbols gs ON m.symbol_id = gs.id
2945
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2946
+ JOIN documents d2 ON der.document_id = d2.id
2947
+ WHERE d2.relative_path IN (${placeholders})
2948
+ AND d1.relative_path NOT IN (${placeholders})
2949
+ AND ${db.localSymbolPredicate}
2950
+ AND m.role != 1
2951
+ ORDER BY d1.relative_path`,
2952
+ ...matchedPaths,
2953
+ ...matchedPaths
2954
+ );
2955
+ return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
2956
+ consumer: r.relative_path,
2957
+ symbol: r.symbol,
2958
+ shortName: shortenSymbol(r.symbol)
2959
+ }));
2172
2960
  }
2173
- function buildUsageBody(source, start, end) {
2174
- const masked = `${source.slice(0, start)}${" ".repeat(end - start)}${source.slice(end)}`;
2175
- return stripCommentsAndStrings(masked);
2961
+
2962
+ // src/entry-surfaces.ts
2963
+ var liveBarrelCache = /* @__PURE__ */ new WeakMap();
2964
+ function normalizePath2(path2) {
2965
+ return path2.replace(/\\/g, "/");
2176
2966
  }
2177
- function stripCommentsAndStrings(source) {
2178
- return source.replace(/'''[\s\S]*?'''/g, " ").replace(/"""[\s\S]*?"""/g, " ").replace(/#.*$/gm, " ").replace(/\/\/.*$/gm, " ").replace(/\/\*[\s\S]*?\*\//g, " ").replace(/`(?:\\[\s\S]|[^`])*`/g, " ").replace(/'(?:\\.|[^'\\\r\n])*'/g, " ").replace(/"(?:\\.|[^"\\\r\n])*"/g, " ");
2967
+ function isBarrelFile(path2) {
2968
+ const normalized = normalizePath2(path2);
2969
+ return normalized === "index.ts" || normalized === "index.js" || normalized.endsWith("/index.ts") || normalized.endsWith("/index.js") || normalized.endsWith("/mod.rs") || normalized.endsWith("/__init__.py");
2179
2970
  }
2180
- function hasIdentifierUsage(body, identifier) {
2181
- return new RegExp(`\\b${escapeRegex(identifier)}\\b`, "m").test(body);
2971
+ function isWorkerEntrySurface(path2) {
2972
+ const normalized = normalizePath2(path2);
2973
+ return /(^|\/)[^/]*worker\.(ts|js|mjs|cjs|rs|py|go)$/.test(normalized);
2182
2974
  }
2183
- function collectNamespaceMembers(body, namespaceName) {
2184
- const members2 = /* @__PURE__ */ new Set();
2185
- const regex = new RegExp(`\\b${escapeRegex(namespaceName)}\\s*\\.\\s*([A-Za-z_$][\\w$]*)`, "g");
2186
- for (const match of body.matchAll(regex)) {
2187
- const member = match[1];
2188
- if (member) {
2189
- members2.add(member);
2190
- }
2975
+ function isStructuralEntrySurface(path2) {
2976
+ const normalized = normalizePath2(path2);
2977
+ const segments = normalized.split("/");
2978
+ const basename2 = segments[segments.length - 1] ?? normalized;
2979
+ if (basename2 === "cli.ts" || basename2 === "cli.js" || basename2 === "postinstall.ts" || basename2 === "postinstall.js" || basename2 === "main.ts" || basename2 === "main.js" || basename2 === "main.rs" || basename2 === "main.go" || basename2 === "main.py") {
2980
+ return true;
2191
2981
  }
2192
- return [...members2];
2193
- }
2194
- function resolveImportPath(db, importerPath, specifier) {
2195
- if (isPythonSourcePath(importerPath)) {
2196
- return resolvePythonImportPath(db, importerPath, specifier);
2982
+ if (basename2 === "index.ts" || basename2 === "index.js") {
2983
+ return segments.length <= 2;
2197
2984
  }
2198
- return resolveJavaScriptImportPath(db, importerPath, specifier);
2985
+ return normalized.endsWith("/mod.rs") || normalized.endsWith("/__init__.py");
2199
2986
  }
2200
- function resolveJavaScriptImportPath(db, importerPath, specifier) {
2201
- if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
2202
- return null;
2987
+ function getIndexedPaths2(db) {
2988
+ return db.all(
2989
+ `SELECT d.relative_path
2990
+ FROM documents d
2991
+ WHERE 1 = 1
2992
+ ${db.pathExclusionsFor("d")}
2993
+ ORDER BY d.relative_path`
2994
+ ).map((row) => row.relative_path).filter((path2) => !db.isIgnored(path2));
2995
+ }
2996
+ function getLiveBarrelPaths(db) {
2997
+ const cached = liveBarrelCache.get(db);
2998
+ if (cached) {
2999
+ return cached;
2203
3000
  }
2204
- const importerDir = dirname2(join6(db.config.projectRoot, importerPath));
2205
- const absolute = resolve2(importerDir, specifier);
2206
- const indexedPaths = getIndexedPaths2(db);
2207
- for (const candidate of candidateImportPaths(absolute)) {
2208
- const relativeCandidate = normalizePath2(relative2(db.config.projectRoot, candidate));
2209
- if (indexedPaths.has(relativeCandidate) || existsSync6(candidate)) {
2210
- return relativeCandidate;
3001
+ const graph = buildFileDepGraph(db);
3002
+ const queue = getIndexedPaths2(db).filter(
3003
+ (path2) => isStructuralEntrySurface(path2) || isWorkerEntrySurface(path2)
3004
+ );
3005
+ const visited = /* @__PURE__ */ new Set();
3006
+ const liveBarrels = /* @__PURE__ */ new Set();
3007
+ while (queue.length > 0) {
3008
+ const current = queue.shift();
3009
+ if (visited.has(current)) {
3010
+ continue;
2211
3011
  }
2212
- }
2213
- return normalizePath2(relative2(db.config.projectRoot, absolute));
2214
- }
2215
- function resolvePythonImportPath(db, importerPath, specifier) {
2216
- const indexedPaths = getIndexedPaths2(db);
2217
- let basePath;
2218
- if (specifier.startsWith(".")) {
2219
- const match = specifier.match(/^(\.+)(.*)$/);
2220
- if (!match) return null;
2221
- const dots = match[1].length;
2222
- const remainder = match[2].replace(/^\./, "");
2223
- let baseDir = dirname2(join6(db.config.projectRoot, importerPath));
2224
- for (let i = 1; i < dots; i++) {
2225
- baseDir = dirname2(baseDir);
3012
+ visited.add(current);
3013
+ if (isBarrelFile(current)) {
3014
+ liveBarrels.add(current);
2226
3015
  }
2227
- basePath = remainder ? resolve2(baseDir, remainder.replace(/\./g, "/")) : baseDir;
2228
- } else {
2229
- basePath = resolve2(db.config.projectRoot, specifier.replace(/\./g, "/"));
2230
- }
2231
- for (const candidate of pythonCandidateImportPaths(basePath)) {
2232
- const relativeCandidate = normalizePath2(relative2(db.config.projectRoot, candidate));
2233
- if (indexedPaths.has(relativeCandidate) || existsSync6(candidate)) {
2234
- return relativeCandidate;
3016
+ for (const dep of graph.get(current) ?? []) {
3017
+ if (!visited.has(dep)) {
3018
+ queue.push(dep);
3019
+ }
2235
3020
  }
2236
3021
  }
2237
- return null;
3022
+ liveBarrelCache.set(db, liveBarrels);
3023
+ return liveBarrels;
2238
3024
  }
2239
- function pythonCandidateImportPaths(basePath) {
2240
- const ext = extname(basePath);
2241
- if (PYTHON_SOURCE_EXTENSIONS.includes(ext)) {
2242
- return [basePath];
2243
- }
2244
- return [
2245
- `${basePath}.py`,
2246
- `${basePath}.pyi`,
2247
- join6(basePath, "__init__.py"),
2248
- join6(basePath, "__init__.pyi")
2249
- ];
3025
+ function isLiveBarrel(db, path2) {
3026
+ return getLiveBarrelPaths(db).has(normalizePath2(path2));
2250
3027
  }
2251
- function candidateImportPaths(absolute) {
2252
- const ext = extname(absolute);
2253
- const candidates = /* @__PURE__ */ new Set();
2254
- if (ext) {
2255
- candidates.add(absolute);
2256
- for (const sourceExt of SOURCE_EXTENSIONS) {
2257
- candidates.add(absolute.slice(0, -ext.length) + sourceExt);
2258
- }
2259
- } else {
2260
- for (const sourceExt of SOURCE_EXTENSIONS) {
2261
- candidates.add(`${absolute}${sourceExt}`);
2262
- candidates.add(join6(absolute, `index${sourceExt}`));
3028
+ function isEntrySurface(db, path2) {
3029
+ return isStructuralEntrySurface(path2) || isWorkerEntrySurface(path2) || isLiveBarrel(db, path2);
3030
+ }
3031
+ function getInactiveBarrelPaths(db) {
3032
+ const liveBarrels = getLiveBarrelPaths(db);
3033
+ return getIndexedPaths2(db).filter((path2) => isBarrelFile(path2)).filter((path2) => !liveBarrels.has(path2));
3034
+ }
3035
+
3036
+ // src/queries/dead.ts
3037
+ function dead(db, opts = {}) {
3038
+ const {
3039
+ scope,
3040
+ minLoc = 1,
3041
+ includeTests = false,
3042
+ skipBarrels = false,
3043
+ includeMembers = false
3044
+ } = opts;
3045
+ const params = [minLoc];
3046
+ let testFileExclusions = "";
3047
+ let memberExclusion = "";
3048
+ let barrelExclusions = "";
3049
+ if (scope) {
3050
+ params.push(`%${scope}%`);
3051
+ }
3052
+ if (!includeTests) {
3053
+ testFileExclusions = `
3054
+ AND ${testFileExclusionSql("d", TEST_SUPPORT_PATH_PATTERNS)}
3055
+ `;
3056
+ }
3057
+ if (!includeMembers) {
3058
+ memberExclusion = `AND gs.symbol NOT LIKE '%#%'`;
3059
+ }
3060
+ if (skipBarrels) {
3061
+ const inactiveBarrelPaths = getInactiveBarrelPaths(db);
3062
+ if (inactiveBarrelPaths.length > 0) {
3063
+ barrelExclusions = `AND ref_d.relative_path NOT IN (${inactiveBarrelPaths.map(() => "?").join(", ")})`;
3064
+ params.push(...inactiveBarrelPaths);
2263
3065
  }
2264
3066
  }
2265
- return [...candidates];
3067
+ const sql = `
3068
+ SELECT
3069
+ d.relative_path,
3070
+ der.start_line,
3071
+ der.end_line,
3072
+ (der.end_line - der.start_line + 1) AS loc,
3073
+ gs.symbol,
3074
+ (SELECT COUNT(*) FROM mentions m2
3075
+ JOIN chunks c2 ON m2.chunk_id = c2.id
3076
+ WHERE m2.symbol_id = gs.id AND m2.role != 1 AND c2.document_id = d.id
3077
+ ) AS same_file_refs
3078
+ FROM global_symbols gs
3079
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
3080
+ JOIN documents d ON der.document_id = d.id
3081
+ WHERE 1 = 1
3082
+ ${db.pathExclusionsFor("d")}
3083
+ ${db.symbolNoiseFor("gs")}
3084
+ AND (der.end_line - der.start_line + 1) >= ?
3085
+ ${scope ? "AND d.relative_path LIKE ?" : ""}
3086
+ ${testFileExclusions}
3087
+ ${memberExclusion}
3088
+ AND NOT EXISTS (
3089
+ SELECT 1
3090
+ FROM mentions ref_m
3091
+ JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
3092
+ JOIN documents ref_d ON ref_c.document_id = ref_d.id
3093
+ WHERE ref_m.symbol_id = gs.id
3094
+ AND ref_m.role != 1
3095
+ AND ref_d.id != d.id
3096
+ ${barrelExclusions}
3097
+ )
3098
+ ORDER BY (der.end_line - der.start_line + 1) DESC, d.relative_path, der.start_line
3099
+ `;
3100
+ const rows = db.all(sql, ...params);
3101
+ let deadCodeCount = 0;
3102
+ let fileInternalCount = 0;
3103
+ let totalLoc = 0;
3104
+ const symbols2 = rows.filter((r) => !db.isIgnored(r.relative_path)).filter((r) => !isEntrySurface(db, r.relative_path)).map((r) => {
3105
+ const kind = r.same_file_refs === 0 ? "dead-code" : "file-internal";
3106
+ if (kind === "dead-code") deadCodeCount++;
3107
+ else fileInternalCount++;
3108
+ totalLoc += r.loc;
3109
+ return {
3110
+ relativePath: r.relative_path,
3111
+ startLine: r.start_line,
3112
+ endLine: r.end_line,
3113
+ loc: r.loc,
3114
+ symbol: r.symbol,
3115
+ shortName: shortenSymbol(r.symbol),
3116
+ sameFileRefs: r.same_file_refs,
3117
+ kind
3118
+ };
3119
+ });
3120
+ return {
3121
+ symbols: symbols2,
3122
+ totalCount: symbols2.length,
3123
+ deadCodeCount,
3124
+ fileInternalCount,
3125
+ totalLoc
3126
+ };
2266
3127
  }
2267
- function getIndexedPaths2(db) {
2268
- const cached = INDEXED_PATH_CACHE.get(db);
2269
- if (cached) {
2270
- return cached;
2271
- }
2272
- const paths = new Set(
2273
- db.all(
2274
- `SELECT relative_path
2275
- FROM documents
2276
- WHERE 1 = 1
2277
- ${db.pathExclusionsFor("documents")}`
2278
- ).map((row) => normalizePath2(row.relative_path)).filter((relativePath) => !db.isIgnored(relativePath))
3128
+
3129
+ // src/queries/hotspots.ts
3130
+ function hotspots(db, opts = {}) {
3131
+ const { limit = 30, scope } = opts;
3132
+ const scopeFilter = scope ? `AND def_d.relative_path LIKE '%${scope}%'` : "";
3133
+ const rows = db.all(
3134
+ `SELECT
3135
+ gs.symbol,
3136
+ COUNT(*) AS ref_count,
3137
+ COUNT(DISTINCT ref_d.id) AS file_count,
3138
+ def_d.relative_path AS defined_in
3139
+ FROM mentions m
3140
+ JOIN chunks c ON m.chunk_id = c.id
3141
+ JOIN documents ref_d ON c.document_id = ref_d.id
3142
+ JOIN global_symbols gs ON m.symbol_id = gs.id
3143
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
3144
+ JOIN documents def_d ON der.document_id = def_d.id
3145
+ WHERE m.role != 1
3146
+ ${db.pathExclusionsFor("def_d")}
3147
+ ${db.symbolNoiseFor("gs")}
3148
+ ${scopeFilter}
3149
+ GROUP BY gs.id
3150
+ ORDER BY ref_count DESC
3151
+ LIMIT ?`,
3152
+ limit
2279
3153
  );
2280
- INDEXED_PATH_CACHE.set(db, paths);
2281
- return paths;
2282
- }
2283
- function getCachedMap(cache, db) {
2284
- let map = cache.get(db);
2285
- if (!map) {
2286
- map = /* @__PURE__ */ new Map();
2287
- cache.set(db, map);
2288
- }
2289
- return map;
2290
- }
2291
- function normalizePath2(path2) {
2292
- return path2.replace(/\\/g, "/");
2293
- }
2294
- function isPythonSourcePath(relativePath) {
2295
- return PYTHON_SOURCE_EXTENSIONS.includes(extname(relativePath).toLowerCase());
2296
- }
2297
- function pythonParenBalance(value) {
2298
- let balance = 0;
2299
- for (const char of value) {
2300
- if (char === "(") balance++;
2301
- if (char === ")") balance--;
2302
- }
2303
- return balance;
2304
- }
2305
- function escapeRegex(value) {
2306
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3154
+ return rows.filter((r) => !db.isIgnored(r.defined_in)).map((r) => ({
3155
+ symbol: r.symbol,
3156
+ shortName: shortenSymbol(r.symbol),
3157
+ refCount: r.ref_count,
3158
+ fileCount: r.file_count,
3159
+ definedIn: r.defined_in
3160
+ }));
2307
3161
  }
2308
3162
 
2309
3163
  // src/queries/imports.ts
2310
3164
  function imports(db, filePattern) {
3165
+ const importer = resolveIndexedFile(db, filePattern);
3166
+ if (!importer) return [];
2311
3167
  const rows = db.all(
2312
3168
  `SELECT DISTINCT gs.symbol, def_d.relative_path AS from_file, imp_d.relative_path AS importer
2313
3169
  FROM mentions m
@@ -2316,10 +3172,10 @@ function imports(db, filePattern) {
2316
3172
  JOIN global_symbols gs ON m.symbol_id = gs.id
2317
3173
  LEFT JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2318
3174
  LEFT JOIN documents def_d ON der.document_id = def_d.id
2319
- WHERE imp_d.relative_path LIKE ?
3175
+ WHERE imp_d.relative_path = ?
2320
3176
  AND m.role = 2
2321
3177
  ORDER BY def_d.relative_path, gs.symbol`,
2322
- `%${filePattern}%`
3178
+ importer
2323
3179
  );
2324
3180
  const indexedResults = rows.filter((row) => !db.isIgnored(row.importer)).map((r) => ({
2325
3181
  symbol: r.symbol,
@@ -2329,8 +3185,6 @@ function imports(db, filePattern) {
2329
3185
  if (indexedResults.length > 0) {
2330
3186
  return indexedResults;
2331
3187
  }
2332
- const importer = findIndexedFile(db, filePattern);
2333
- if (!importer) return [];
2334
3188
  return getSourceImports(db, importer).map((entry) => ({
2335
3189
  symbol: renderImportSymbol(entry.importedName, entry.localName, entry.kind),
2336
3190
  shortName: renderImportSymbol(entry.importedName, entry.localName, entry.kind),
@@ -2396,13 +3250,15 @@ function importedBy(db, symbolPattern) {
2396
3250
  }));
2397
3251
  }
2398
3252
  function unusedImports(db, filePattern) {
3253
+ const importer = resolveIndexedFile(db, filePattern);
3254
+ if (!importer) return [];
2399
3255
  const rows = db.all(
2400
3256
  `SELECT gs.symbol, d.relative_path AS imported_in, d.relative_path AS importer
2401
3257
  FROM mentions m
2402
3258
  JOIN chunks c ON m.chunk_id = c.id
2403
3259
  JOIN documents d ON c.document_id = d.id
2404
3260
  JOIN global_symbols gs ON m.symbol_id = gs.id
2405
- WHERE d.relative_path LIKE ?
3261
+ WHERE d.relative_path = ?
2406
3262
  AND m.role = 2
2407
3263
  AND NOT EXISTS (
2408
3264
  SELECT 1
@@ -2413,7 +3269,7 @@ function unusedImports(db, filePattern) {
2413
3269
  AND ref_c.document_id = d.id
2414
3270
  )
2415
3271
  ORDER BY d.relative_path, gs.symbol`,
2416
- `%${filePattern}%`
3272
+ importer
2417
3273
  );
2418
3274
  const indexedResults = rows.filter((row) => !db.isIgnored(row.importer)).map((r) => ({
2419
3275
  symbol: r.symbol,
@@ -2423,28 +3279,12 @@ function unusedImports(db, filePattern) {
2423
3279
  if (indexedResults.length > 0) {
2424
3280
  return indexedResults;
2425
3281
  }
2426
- const importer = findIndexedFile(db, filePattern);
2427
- if (!importer) return [];
2428
3282
  return getSourceImports(db, importer).filter((entry) => entry.kind !== "side-effect" && !entry.used).map((entry) => ({
2429
3283
  symbol: renderImportSymbol(entry.importedName, entry.localName, entry.kind),
2430
3284
  shortName: renderImportSymbol(entry.importedName, entry.localName, entry.kind),
2431
3285
  importedIn: importer
2432
3286
  }));
2433
3287
  }
2434
- function findIndexedFile(db, filePattern) {
2435
- const doc = db.get(
2436
- `SELECT relative_path
2437
- FROM documents
2438
- WHERE relative_path LIKE ?
2439
- ${db.pathExclusionsFor("documents")}
2440
- LIMIT 1`,
2441
- `%${filePattern}%`
2442
- );
2443
- if (!doc || db.isIgnored(doc.relative_path)) {
2444
- return null;
2445
- }
2446
- return doc.relative_path;
2447
- }
2448
3288
  function renderImportSymbol(importedName, localName, kind) {
2449
3289
  if (kind === "namespace" && importedName === "*" && localName) {
2450
3290
  return `* as ${localName}`;
@@ -2466,15 +3306,20 @@ function normalizePath3(path2) {
2466
3306
 
2467
3307
  // src/queries/outline.ts
2468
3308
  function outline(db, filePattern) {
3309
+ const resolvedPaths = resolveIndexedPaths(db, filePattern);
3310
+ if (resolvedPaths.length === 0) {
3311
+ return [];
3312
+ }
3313
+ const placeholders = resolvedPaths.map(() => "?").join(", ");
2469
3314
  const rows = db.all(
2470
3315
  `SELECT gs.symbol, gs.enclosing_symbol, der.start_line, der.end_line
2471
3316
  FROM defn_enclosing_ranges der
2472
3317
  JOIN global_symbols gs ON der.symbol_id = gs.id
2473
3318
  JOIN documents d ON der.document_id = d.id
2474
- WHERE d.relative_path LIKE ?
3319
+ WHERE d.relative_path IN (${placeholders})
2475
3320
  ${db.symbolNoise}
2476
- ORDER BY der.start_line`,
2477
- `%${filePattern}%`
3321
+ ORDER BY d.relative_path, der.start_line`,
3322
+ ...resolvedPaths
2478
3323
  );
2479
3324
  const nodeMap = /* @__PURE__ */ new Map();
2480
3325
  const roots = [];
@@ -2542,6 +3387,10 @@ function fanIn(db, symbolPattern) {
2542
3387
  }));
2543
3388
  }
2544
3389
  function fanOut(db, filePattern) {
3390
+ const resolvedFile = resolveIndexedFile(db, filePattern);
3391
+ if (!resolvedFile) {
3392
+ return [];
3393
+ }
2545
3394
  const rows = db.all(
2546
3395
  `SELECT d.relative_path, COUNT(DISTINCT gs.id) AS symbol_count
2547
3396
  FROM mentions m
@@ -2550,12 +3399,12 @@ function fanOut(db, filePattern) {
2550
3399
  JOIN global_symbols gs ON m.symbol_id = gs.id
2551
3400
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2552
3401
  JOIN documents def_d ON der.document_id = def_d.id
2553
- WHERE d.relative_path LIKE ?
3402
+ WHERE d.relative_path = ?
2554
3403
  AND m.role != 1
2555
3404
  AND def_d.id != d.id
2556
3405
  GROUP BY d.id
2557
3406
  ORDER BY symbol_count DESC`,
2558
- `%${filePattern}%`
3407
+ resolvedFile
2559
3408
  );
2560
3409
  return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
2561
3410
  name: r.relative_path,
@@ -2616,6 +3465,8 @@ function topFanOut(db, opts = {}) {
2616
3465
 
2617
3466
  // src/queries/coupling.ts
2618
3467
  function coupling(db, file1, file2) {
3468
+ const resolvedFile1 = resolveIndexedFile(db, file1) ?? file1;
3469
+ const resolvedFile2 = resolveIndexedFile(db, file2) ?? file2;
2619
3470
  const row = db.get(
2620
3471
  `SELECT COUNT(DISTINCT gs.id) AS shared
2621
3472
  FROM global_symbols gs
@@ -2624,36 +3475,36 @@ function coupling(db, file1, file2) {
2624
3475
  EXISTS (
2625
3476
  SELECT 1 FROM defn_enclosing_ranges der
2626
3477
  JOIN documents d ON der.document_id = d.id
2627
- WHERE der.symbol_id = gs.id AND d.relative_path LIKE ?
3478
+ WHERE der.symbol_id = gs.id AND d.relative_path = ?
2628
3479
  )
2629
3480
  AND EXISTS (
2630
3481
  SELECT 1 FROM mentions m
2631
3482
  JOIN chunks c ON m.chunk_id = c.id
2632
3483
  JOIN documents d ON c.document_id = d.id
2633
- WHERE m.symbol_id = gs.id AND m.role != 1 AND d.relative_path LIKE ?
3484
+ WHERE m.symbol_id = gs.id AND m.role != 1 AND d.relative_path = ?
2634
3485
  )
2635
3486
  ) OR (
2636
3487
  -- Defined in file2, referenced in file1
2637
3488
  EXISTS (
2638
3489
  SELECT 1 FROM defn_enclosing_ranges der
2639
3490
  JOIN documents d ON der.document_id = d.id
2640
- WHERE der.symbol_id = gs.id AND d.relative_path LIKE ?
3491
+ WHERE der.symbol_id = gs.id AND d.relative_path = ?
2641
3492
  )
2642
3493
  AND EXISTS (
2643
3494
  SELECT 1 FROM mentions m
2644
3495
  JOIN chunks c ON m.chunk_id = c.id
2645
3496
  JOIN documents d ON c.document_id = d.id
2646
- WHERE m.symbol_id = gs.id AND m.role != 1 AND d.relative_path LIKE ?
3497
+ WHERE m.symbol_id = gs.id AND m.role != 1 AND d.relative_path = ?
2647
3498
  )
2648
3499
  )`,
2649
- `%${file1}%`,
2650
- `%${file2}%`,
2651
- `%${file2}%`,
2652
- `%${file1}%`
3500
+ resolvedFile1,
3501
+ resolvedFile2,
3502
+ resolvedFile2,
3503
+ resolvedFile1
2653
3504
  );
2654
3505
  return {
2655
- file1,
2656
- file2,
3506
+ file1: resolvedFile1,
3507
+ file2: resolvedFile2,
2657
3508
  sharedSymbols: row?.shared ?? 0
2658
3509
  };
2659
3510
  }
@@ -2949,7 +3800,7 @@ function byKind(db, kindQuery, opts = {}) {
2949
3800
  `SELECT COUNT(*) AS c FROM global_symbols WHERE kind IS NOT NULL`
2950
3801
  );
2951
3802
  if (!hasKinds || hasKinds.c === 0) {
2952
- return [];
3803
+ return inferByKind(db, kindNum, scope, limit);
2953
3804
  }
2954
3805
  const rows = db.all(
2955
3806
  `SELECT gs.symbol, gs.kind, d.relative_path, der.start_line, der.end_line
@@ -2989,12 +3840,89 @@ function kindCounts(db, opts = {}) {
2989
3840
  GROUP BY gs.kind
2990
3841
  ORDER BY cnt DESC`
2991
3842
  );
3843
+ if (rows.length === 0) {
3844
+ return inferKindCounts(db, opts.scope);
3845
+ }
2992
3846
  return rows.map((r) => ({
2993
3847
  kind: r.kind,
2994
3848
  kindName: KIND_NAMES[r.kind] ?? "Unknown",
2995
3849
  count: r.cnt
2996
3850
  }));
2997
3851
  }
3852
+ function inferByKind(db, kindNum, scope, limit = 100) {
3853
+ const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
3854
+ const rows = db.all(
3855
+ `SELECT gs.symbol, d.relative_path, der.start_line, der.end_line, gs.documentation, gs.enclosing_symbol
3856
+ FROM global_symbols gs
3857
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
3858
+ JOIN documents d ON der.document_id = d.id
3859
+ WHERE 1 = 1
3860
+ ${db.pathExclusionsFor("d")}
3861
+ ${scopeFilter}
3862
+ ORDER BY d.relative_path, der.start_line`
3863
+ );
3864
+ return rows.filter((row) => !db.isIgnored(row.relative_path)).map((row) => ({
3865
+ row,
3866
+ inferredKind: inferKindNumber(row.symbol, row.documentation, row.enclosing_symbol)
3867
+ })).filter((entry) => entry.inferredKind === kindNum).slice(0, limit).map(({ row, inferredKind }) => ({
3868
+ symbol: row.symbol,
3869
+ shortName: shortenSymbol(row.symbol),
3870
+ kind: inferredKind,
3871
+ kindName: KIND_NAMES[inferredKind] ?? "Unknown",
3872
+ relativePath: row.relative_path,
3873
+ startLine: row.start_line,
3874
+ endLine: row.end_line
3875
+ }));
3876
+ }
3877
+ function inferKindCounts(db, scope) {
3878
+ const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
3879
+ const rows = db.all(
3880
+ `SELECT gs.symbol, gs.documentation, gs.enclosing_symbol, d.relative_path
3881
+ FROM global_symbols gs
3882
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
3883
+ JOIN documents d ON der.document_id = d.id
3884
+ WHERE 1 = 1
3885
+ ${db.pathExclusionsFor("d")}
3886
+ ${scopeFilter}`
3887
+ );
3888
+ const counts = /* @__PURE__ */ new Map();
3889
+ for (const row of rows) {
3890
+ if (db.isIgnored(row.relative_path)) continue;
3891
+ const inferred = inferKindNumber(row.symbol, row.documentation, row.enclosing_symbol);
3892
+ if (inferred === null || inferred === 0) continue;
3893
+ counts.set(inferred, (counts.get(inferred) ?? 0) + 1);
3894
+ }
3895
+ return [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0] - b[0]).map(([kind, count]) => ({
3896
+ kind,
3897
+ kindName: KIND_NAMES[kind] ?? "Unknown",
3898
+ count
3899
+ }));
3900
+ }
3901
+ function inferKindNumber(symbol, documentation, enclosingSymbol) {
3902
+ const parsed = parseSymbol(symbol);
3903
+ if ("kind" in parsed) {
3904
+ return null;
3905
+ }
3906
+ const descriptors = parsed.descriptors;
3907
+ const last = descriptors[descriptors.length - 1] ?? null;
3908
+ const parent = descriptors[descriptors.length - 2] ?? null;
3909
+ const suffix = leafSuffix(symbol);
3910
+ if (suffix === "type") return 9;
3911
+ if (suffix === "method") {
3912
+ return parent?.suffix === "type" ? 33 : 23;
3913
+ }
3914
+ if (suffix === "namespace") return 39;
3915
+ if (suffix !== "term") return null;
3916
+ const signature = (documentation ?? "").toLowerCase();
3917
+ if (signature.includes("async def ") || signature.includes("def ")) {
3918
+ return 23;
3919
+ }
3920
+ const enclosingSuffix = enclosingSymbol ? leafSuffix(enclosingSymbol) : parent?.suffix ?? null;
3921
+ if (enclosingSuffix === "type") {
3922
+ return 21;
3923
+ }
3924
+ return 83;
3925
+ }
2998
3926
 
2999
3927
  // src/queries/doc-coverage.ts
3000
3928
  function docCoverage(db, opts = {}) {
@@ -3158,36 +4086,15 @@ function hierarchy(db, symbolPattern) {
3158
4086
  function callGraph(db, symbolPattern) {
3159
4087
  const target = findFirstSymbolMatch(db, symbolPattern);
3160
4088
  if (!target) return null;
3161
- const callerRows = db.all(
3162
- `SELECT DISTINCT caller_gs.symbol AS caller_symbol, caller_d.relative_path AS caller_file
3163
- FROM mentions m
3164
- JOIN chunks c ON m.chunk_id = c.id
3165
- JOIN documents ref_d ON c.document_id = ref_d.id
3166
- -- Find the enclosing symbol for where the reference appears
3167
- JOIN defn_enclosing_ranges caller_der
3168
- ON caller_der.document_id = ref_d.id
3169
- AND c.start_line >= caller_der.start_line
3170
- AND c.end_line <= caller_der.end_line
3171
- JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id
3172
- JOIN documents caller_d ON caller_der.document_id = caller_d.id
3173
- WHERE m.symbol_id = ?
3174
- AND m.role != 1
3175
- AND caller_gs.id != ?
3176
- ${db.symbolNoiseFor("caller_gs")}
3177
- ${db.pathExclusionsFor("caller_d")}
3178
- ORDER BY caller_d.relative_path
3179
- LIMIT 50`,
3180
- target.symbolId,
3181
- target.symbolId
3182
- );
3183
- const calleeRows = getCalleeRowsForSymbol(db, target, { limit: 50 });
4089
+ const callerRows = getCallerRowsForSymbol(db, target, { limit: 50 });
4090
+ const calleeRows = uniqueRows(getCalleeRowsForSymbol(db, target, { limit: 50 }));
3184
4091
  return {
3185
4092
  symbol: target.symbol,
3186
4093
  shortName: shortenSymbol(target.symbol),
3187
- callers: callerRows.filter((r) => !db.isIgnored(r.caller_file)).map((r) => ({
3188
- symbol: r.caller_symbol,
3189
- shortName: shortenSymbol(r.caller_symbol),
3190
- file: r.caller_file
4094
+ callers: callerRows.map((r) => ({
4095
+ symbol: r.symbol,
4096
+ shortName: shortenSymbol(r.symbol),
4097
+ file: r.file
3191
4098
  })),
3192
4099
  callees: calleeRows.map((r) => ({
3193
4100
  symbol: r.symbol,
@@ -3196,6 +4103,17 @@ function callGraph(db, symbolPattern) {
3196
4103
  }))
3197
4104
  };
3198
4105
  }
4106
+ function uniqueRows(rows) {
4107
+ const seen = /* @__PURE__ */ new Set();
4108
+ const unique = [];
4109
+ for (const row of rows) {
4110
+ const key = `${row.symbol}|${row.file}`;
4111
+ if (seen.has(key)) continue;
4112
+ seen.add(key);
4113
+ unique.push(row);
4114
+ }
4115
+ return unique;
4116
+ }
3199
4117
 
3200
4118
  // src/queries/similar.ts
3201
4119
  function similar(db, symbolPattern, opts = {}) {
@@ -3502,8 +4420,8 @@ function similarChains(db, opts = {}) {
3502
4420
  }
3503
4421
  const structuralNames = ["index.ts", "index.js", "cli.ts", "main.ts", "health.ts", "health.js"];
3504
4422
  for (const node of nodeFreq.keys()) {
3505
- const basename = node.split("/").pop() ?? "";
3506
- if (structuralNames.includes(basename)) infraNodes.add(node);
4423
+ const basename2 = node.split("/").pop() ?? "";
4424
+ if (structuralNames.includes(basename2)) infraNodes.add(node);
3507
4425
  }
3508
4426
  const filteredChains = [];
3509
4427
  for (const chain of rawChains) {
@@ -3690,8 +4608,8 @@ function extractCandidates(db, opts = {}) {
3690
4608
  const results = [];
3691
4609
  for (const sym of symbols2) {
3692
4610
  if (db.isIgnored(sym.relative_path)) continue;
3693
- const basename = sym.relative_path.split("/").pop() ?? "";
3694
- if (basename.includes("types")) continue;
4611
+ const basename2 = sym.relative_path.split("/").pop() ?? "";
4612
+ if (basename2.includes("types")) continue;
3695
4613
  const calleeChunks = getCalleeRowsForSymbol(db, {
3696
4614
  documentId: sym.document_id,
3697
4615
  startLine: sym.start_line,
@@ -3778,63 +4696,119 @@ function affected(db, symbolPattern, opts = {}) {
3778
4696
  const { maxDepth = 5, scope } = opts;
3779
4697
  const target = findFirstSymbolMatch(db, symbolPattern);
3780
4698
  if (!target) return [];
3781
- const scopeFilter = scope ? `AND enc_d.relative_path LIKE '%${scope}%'` : "";
3782
4699
  const results = [];
3783
4700
  const visited = /* @__PURE__ */ new Set([target.symbolId]);
3784
- let frontier = /* @__PURE__ */ new Set([target.symbolId]);
4701
+ const seenResults = /* @__PURE__ */ new Set();
4702
+ let frontier = [target];
3785
4703
  for (let depth = 1; depth <= maxDepth; depth++) {
3786
- if (frontier.size === 0) break;
3787
- const placeholders = [...frontier].map(() => "?").join(",");
3788
- const nextFrontier = /* @__PURE__ */ new Set();
3789
- const rows = db.all(
3790
- `SELECT DISTINCT
3791
- enc_gs.id AS symbol_id,
3792
- enc_gs.symbol AS symbol,
3793
- enc_d.relative_path AS relative_path
3794
- FROM mentions m
3795
- JOIN chunks c ON m.chunk_id = c.id
3796
- JOIN documents ref_d ON c.document_id = ref_d.id
3797
- JOIN defn_enclosing_ranges enc_der
3798
- ON enc_der.document_id = ref_d.id
3799
- AND c.start_line >= enc_der.start_line
3800
- AND c.end_line <= enc_der.end_line
3801
- JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
3802
- JOIN documents enc_d ON enc_der.document_id = enc_d.id
3803
- WHERE m.symbol_id IN (${placeholders})
3804
- AND m.role != 1
3805
- AND enc_gs.id NOT IN (${placeholders})
3806
- ${db.symbolNoiseFor("enc_gs")}
3807
- ${db.pathExclusionsFor("enc_d")}
3808
- ${scopeFilter}`,
3809
- ...[...frontier],
3810
- ...[...frontier]
3811
- );
3812
- for (const row of rows) {
3813
- if (visited.has(row.symbol_id)) continue;
3814
- if (db.isIgnored(row.relative_path)) continue;
3815
- visited.add(row.symbol_id);
3816
- nextFrontier.add(row.symbol_id);
3817
- results.push({
3818
- symbol: row.symbol,
3819
- shortName: shortenSymbol(row.symbol),
3820
- file: row.relative_path,
3821
- depth
3822
- });
4704
+ if (frontier.length === 0) break;
4705
+ const nextFrontier = [];
4706
+ for (const current of frontier) {
4707
+ for (const row of getDirectAffectedRows(db, current, scope)) {
4708
+ const resultKey = `${row.file}|${row.shortName}`;
4709
+ if (row.symbolId !== null) {
4710
+ if (visited.has(row.symbolId)) continue;
4711
+ visited.add(row.symbolId);
4712
+ } else if (seenResults.has(resultKey)) {
4713
+ continue;
4714
+ }
4715
+ seenResults.add(resultKey);
4716
+ results.push({
4717
+ symbol: row.symbol,
4718
+ shortName: row.shortName,
4719
+ file: row.file,
4720
+ depth
4721
+ });
4722
+ if (row.symbolId !== null && row.symbolMatch) {
4723
+ nextFrontier.push(row.symbolMatch);
4724
+ }
4725
+ }
3823
4726
  }
3824
4727
  frontier = nextFrontier;
3825
4728
  }
3826
4729
  results.sort((a, b) => a.depth - b.depth || a.file.localeCompare(b.file));
3827
4730
  return results;
3828
4731
  }
4732
+ function getDirectAffectedRows(db, target, scope) {
4733
+ const sourceSites = getSourceReferenceSites(db, target).filter((site) => !db.isIgnored(site.file)).filter((site) => !scope || site.file.includes(scope));
4734
+ if (sourceSites.length > 0) {
4735
+ const rows2 = [];
4736
+ const seen = /* @__PURE__ */ new Set();
4737
+ for (const site of sourceSites) {
4738
+ if (!site.enclosingSymbol || site.enclosingSymbol === target.symbol) {
4739
+ const key2 = `${site.file}|(top-level)`;
4740
+ if (seen.has(key2)) continue;
4741
+ seen.add(key2);
4742
+ rows2.push({
4743
+ symbolId: null,
4744
+ symbol: site.file,
4745
+ shortName: "(top-level)",
4746
+ file: site.file,
4747
+ symbolMatch: null
4748
+ });
4749
+ continue;
4750
+ }
4751
+ const enclosing = findExactSymbolMatch(db, site.enclosingSymbol);
4752
+ if (!enclosing || enclosing.symbolId === target.symbolId || db.isIgnored(enclosing.relativePath)) {
4753
+ continue;
4754
+ }
4755
+ const key = `${enclosing.symbolId}|${enclosing.relativePath}`;
4756
+ if (seen.has(key)) continue;
4757
+ seen.add(key);
4758
+ rows2.push({
4759
+ symbolId: enclosing.symbolId,
4760
+ symbol: enclosing.symbol,
4761
+ shortName: shortenSymbol(enclosing.symbol),
4762
+ file: enclosing.relativePath,
4763
+ symbolMatch: enclosing
4764
+ });
4765
+ }
4766
+ return rows2;
4767
+ }
4768
+ const rows = db.all(
4769
+ `SELECT DISTINCT
4770
+ enc_gs.id AS symbol_id,
4771
+ enc_gs.symbol AS symbol,
4772
+ enc_d.relative_path AS relative_path
4773
+ FROM mentions m
4774
+ JOIN chunks c ON m.chunk_id = c.id
4775
+ JOIN documents ref_d ON c.document_id = ref_d.id
4776
+ JOIN defn_enclosing_ranges enc_der
4777
+ ON enc_der.document_id = ref_d.id
4778
+ AND c.start_line >= enc_der.start_line
4779
+ AND c.end_line <= enc_der.end_line
4780
+ JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
4781
+ JOIN documents enc_d ON enc_der.document_id = enc_d.id
4782
+ WHERE m.symbol_id = ?
4783
+ AND m.role != 1
4784
+ AND enc_gs.id != ?
4785
+ ${db.symbolNoiseFor("enc_gs")}
4786
+ ${db.pathExclusionsFor("enc_d")}
4787
+ ${scope ? `AND enc_d.relative_path LIKE '%${scope}%'` : ""}
4788
+ ORDER BY enc_d.relative_path
4789
+ LIMIT 1`,
4790
+ target.symbolId,
4791
+ target.symbolId
4792
+ );
4793
+ return rows.filter((row) => !db.isIgnored(row.relative_path)).map((row) => ({
4794
+ symbolId: row.symbol_id,
4795
+ symbol: row.symbol,
4796
+ shortName: shortenSymbol(row.symbol),
4797
+ file: row.relative_path,
4798
+ symbolMatch: findExactSymbolMatch(db, row.symbol)
4799
+ }));
4800
+ }
3829
4801
 
3830
4802
  // src/queries/change-surface.ts
3831
4803
  function changeSurface(db, filePattern) {
4804
+ const resolvedFile = resolveIndexedFile(db, filePattern);
4805
+ if (!resolvedFile) return null;
3832
4806
  const doc = db.get(
3833
4807
  `SELECT id, relative_path FROM documents
3834
- WHERE relative_path LIKE ?
4808
+ WHERE relative_path = ?
3835
4809
  ${db.pathExclusionsFor("documents")}
3836
4810
  LIMIT 1`,
3837
- `%${filePattern}%`
4811
+ resolvedFile
3838
4812
  );
3839
4813
  if (!doc || db.isIgnored(doc.relative_path)) return null;
3840
4814
  const syms = db.all(
@@ -4172,17 +5146,17 @@ function isLikelyTypeOnlyDep(dep) {
4172
5146
  function shouldSkipDriftFile(filePath) {
4173
5147
  return isStructuralRole(path.basename(filePath)) || isTestLikePath(filePath);
4174
5148
  }
4175
- function isStructuralRole(basename) {
4176
- if (basename === "index.ts" || basename === "index.js") return true;
4177
- if (basename === "cli.ts" || basename === "main.ts" || basename === "main.rs") return true;
4178
- if (basename.includes("worker.") || basename.includes("postinstall.")) return true;
4179
- if (basename === "health.ts" || basename === "health.js") return true;
5149
+ function isStructuralRole(basename2) {
5150
+ if (basename2 === "index.ts" || basename2 === "index.js") return true;
5151
+ if (basename2 === "cli.ts" || basename2 === "main.ts" || basename2 === "main.rs") return true;
5152
+ if (basename2.includes("worker.") || basename2.includes("postinstall.")) return true;
5153
+ if (basename2 === "health.ts" || basename2 === "health.js") return true;
4180
5154
  return false;
4181
5155
  }
4182
5156
  function isTestLikePath(filePath) {
4183
5157
  const normalized = filePath.replace(/\\/g, "/");
4184
- const basename = path.basename(normalized);
4185
- return normalized.includes("/__tests__/") || normalized.includes("/tests/") || normalized.includes("/test/") || /\.(test|spec)\.[A-Za-z0-9]+$/.test(basename) || /_(test|spec)\.[A-Za-z0-9]+$/.test(basename) || /^test[_-]/.test(basename) || /^test\./.test(basename);
5158
+ const basename2 = path.basename(normalized);
5159
+ return normalized.includes("/__tests__/") || normalized.includes("/tests/") || normalized.includes("/test/") || /\.(test|spec)\.[A-Za-z0-9]+$/.test(basename2) || /_(test|spec)\.[A-Za-z0-9]+$/.test(basename2) || /^test[_-]/.test(basename2) || /^test\./.test(basename2);
4186
5160
  }
4187
5161
 
4188
5162
  // src/queries/wrapper-candidates.ts
@@ -4368,8 +5342,8 @@ function staleAbstractions(db, opts) {
4368
5342
  limit
4369
5343
  );
4370
5344
  return rows.filter((r) => !db.isIgnored(r.file)).filter((r) => {
4371
- const basename = r.file.split("/").pop() ?? "";
4372
- const isTypeFile = basename.includes("types") || r.file.includes("/types/");
5345
+ const basename2 = r.file.split("/").pop() ?? "";
5346
+ const isTypeFile = basename2.includes("types") || r.file.includes("/types/");
4373
5347
  if (isTypeFile && r.consumers > 0) return false;
4374
5348
  return true;
4375
5349
  }).map((r) => ({
@@ -4773,9 +5747,11 @@ function code(db, symbolPattern, opts = {}) {
4773
5747
  };
4774
5748
  }
4775
5749
  function readFileRange(db, filePath, startLine, endLine, context) {
5750
+ const resolvedPath = resolveIndexedFile(db, filePath);
5751
+ if (!resolvedPath) return null;
4776
5752
  const doc = db.get(
4777
- `SELECT relative_path, language FROM documents WHERE relative_path LIKE ?`,
4778
- `%${filePath}%`
5753
+ `SELECT relative_path, language FROM documents WHERE relative_path = ?`,
5754
+ resolvedPath
4779
5755
  );
4780
5756
  if (!doc) return null;
4781
5757
  const fullPath = join7(db.config.projectRoot, doc.relative_path);
@@ -4902,93 +5878,75 @@ function stripCommentsAndStrings2(source) {
4902
5878
  function dataflow(db, symbolPattern) {
4903
5879
  const match = findFirstSymbolMatch(db, symbolPattern);
4904
5880
  if (!match) return null;
4905
- const defSites = db.all(
4906
- `SELECT d.relative_path AS file, c.start_line AS line
4907
- FROM mentions m
4908
- JOIN chunks c ON m.chunk_id = c.id
4909
- JOIN documents d ON c.document_id = d.id
4910
- WHERE m.symbol_id = ? AND m.role = 1
4911
- ORDER BY d.relative_path, c.start_line`,
4912
- match.symbolId
4913
- );
4914
- const usageSites = db.all(
5881
+ const defSites = [{
5882
+ file: match.relativePath,
5883
+ line: match.startLine
5884
+ }];
5885
+ const sourceUsageSites = getSourceReferenceSites(db, match);
5886
+ const usageSites = sourceUsageSites.length > 0 ? sourceUsageSites.map((site) => ({
5887
+ file: site.file,
5888
+ line: site.line,
5889
+ enclosing_symbol: site.enclosingSymbol
5890
+ })) : db.all(
4915
5891
  `SELECT d.relative_path AS file, c.start_line AS line,
4916
- (SELECT enc_gs.symbol
4917
- FROM defn_enclosing_ranges enc_der
4918
- JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
4919
- WHERE enc_der.document_id = d.id
4920
- AND enc_der.start_line <= c.start_line
4921
- AND enc_der.end_line >= c.end_line
4922
- ORDER BY (enc_der.end_line - enc_der.start_line) ASC
4923
- LIMIT 1
4924
- ) AS enclosing_symbol
4925
- FROM mentions m
4926
- JOIN chunks c ON m.chunk_id = c.id
4927
- JOIN documents d ON c.document_id = d.id
4928
- WHERE m.symbol_id = ? AND m.role != 1
4929
- ${db.pathExclusionsFor("d")}
4930
- ORDER BY d.relative_path, c.start_line`,
4931
- match.symbolId
4932
- );
4933
- const producers = db.all(
4934
- `SELECT DISTINCT other_gs.symbol, other_d.relative_path AS file
4935
- FROM mentions other_m
4936
- JOIN chunks other_c ON other_m.chunk_id = other_c.id
4937
- JOIN global_symbols other_gs ON other_m.symbol_id = other_gs.id
4938
- JOIN defn_enclosing_ranges other_der ON other_gs.id = other_der.symbol_id
4939
- JOIN documents other_d ON other_der.document_id = other_d.id
4940
- WHERE other_c.document_id = ?
4941
- AND other_c.start_line >= ? AND other_c.end_line <= ?
4942
- AND other_m.role != 1
4943
- AND other_gs.id != ?
4944
- ${db.symbolNoiseFor("other_gs")}
4945
- ${db.pathExclusionsFor("other_d")}
4946
- ORDER BY other_d.relative_path
4947
- LIMIT 30`,
4948
- match.documentId,
4949
- match.startLine,
4950
- match.endLine,
5892
+ (SELECT enc_gs.symbol
5893
+ FROM defn_enclosing_ranges enc_der
5894
+ JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
5895
+ WHERE enc_der.document_id = d.id
5896
+ AND enc_der.start_line <= c.start_line
5897
+ AND enc_der.end_line >= c.end_line
5898
+ ORDER BY (enc_der.end_line - enc_der.start_line) ASC
5899
+ LIMIT 1
5900
+ ) AS enclosing_symbol
5901
+ FROM mentions m
5902
+ JOIN chunks c ON m.chunk_id = c.id
5903
+ JOIN documents d ON c.document_id = d.id
5904
+ WHERE m.symbol_id = ? AND m.role != 1
5905
+ ${db.pathExclusionsFor("d")}
5906
+ ORDER BY d.relative_path, c.start_line`,
4951
5907
  match.symbolId
4952
5908
  );
4953
- const consumers = db.all(
4954
- `SELECT DISTINCT consumer_gs.symbol, consumer_d.relative_path AS file
4955
- FROM mentions ref_m
4956
- JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
4957
- JOIN documents ref_d ON ref_c.document_id = ref_d.id
4958
- -- Find the enclosing function at each usage site
4959
- JOIN defn_enclosing_ranges enc_der
4960
- ON enc_der.document_id = ref_d.id
4961
- AND enc_der.start_line <= ref_c.start_line
4962
- AND enc_der.end_line >= ref_c.end_line
4963
- JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
4964
- -- Find other symbols defined by that enclosing function's file
4965
- JOIN mentions consumer_m ON consumer_m.symbol_id = enc_gs.id AND consumer_m.role != 1
4966
- JOIN chunks consumer_c ON consumer_m.chunk_id = consumer_c.id
4967
- JOIN documents consumer_d ON consumer_c.document_id = consumer_d.id
4968
- JOIN global_symbols consumer_gs ON consumer_m.symbol_id = consumer_gs.id
4969
- WHERE ref_m.symbol_id = ? AND ref_m.role != 1
4970
- AND consumer_d.id != ref_d.id
4971
- ${db.symbolNoiseFor("consumer_gs")}
4972
- ${db.pathExclusionsFor("consumer_d")}
4973
- ORDER BY consumer_d.relative_path
4974
- LIMIT 30`,
4975
- match.symbolId
5909
+ const normalizedUsageSites = usageSites.filter((site) => !db.isIgnored(site.file)).map((site) => ({
5910
+ file: site.file,
5911
+ line: site.line,
5912
+ enclosingSymbol: site.enclosing_symbol ?? "(top-level)",
5913
+ enclosingShort: site.enclosing_symbol ? shortenSymbol(site.enclosing_symbol) : "(top-level)"
5914
+ }));
5915
+ const producers = uniqueSymbolRows(getCalleeRowsForSymbol(db, match, { limit: 30 }).map((row) => ({
5916
+ symbol: row.symbol,
5917
+ file: row.file
5918
+ })));
5919
+ const consumers = uniqueSymbolRows(
5920
+ normalizedUsageSites.map((site) => ({
5921
+ symbol: site.enclosingSymbol === "(top-level)" ? site.file : site.enclosingSymbol,
5922
+ file: site.file
5923
+ }))
4976
5924
  );
4977
5925
  return {
4978
5926
  symbol: match.symbol,
4979
5927
  shortName: shortenSymbol(match.symbol),
4980
5928
  relativePath: match.relativePath,
4981
5929
  definitionSites: defSites.filter((s) => !db.isIgnored(s.file)),
4982
- usageSites: usageSites.filter((s) => !db.isIgnored(s.file)).map((s) => ({
4983
- file: s.file,
4984
- line: s.line,
4985
- enclosingSymbol: s.enclosing_symbol ?? "(top-level)",
4986
- enclosingShort: s.enclosing_symbol ? shortenSymbol(s.enclosing_symbol) : "(top-level)"
4987
- })),
5930
+ usageSites: normalizedUsageSites,
4988
5931
  producers: producers.filter((p) => !db.isIgnored(p.file)).map((p) => ({ symbol: p.symbol, shortName: shortenSymbol(p.symbol), file: p.file })),
4989
- consumers: consumers.filter((c) => !db.isIgnored(c.file)).map((c) => ({ symbol: c.symbol, shortName: shortenSymbol(c.symbol), file: c.file }))
5932
+ consumers: consumers.filter((c) => !db.isIgnored(c.file)).map((c) => ({
5933
+ symbol: c.symbol,
5934
+ shortName: c.symbol === c.file ? "(top-level)" : shortenSymbol(c.symbol),
5935
+ file: c.file
5936
+ }))
4990
5937
  };
4991
5938
  }
5939
+ function uniqueSymbolRows(rows) {
5940
+ const seen = /* @__PURE__ */ new Set();
5941
+ const unique = [];
5942
+ for (const row of rows) {
5943
+ const key = `${row.symbol}|${row.file}`;
5944
+ if (seen.has(key)) continue;
5945
+ seen.add(key);
5946
+ unique.push(row);
5947
+ }
5948
+ return unique;
5949
+ }
4992
5950
 
4993
5951
  // src/queries/slice.ts
4994
5952
  function slice(db, symbolPattern, opts = {}) {
@@ -5003,21 +5961,6 @@ function slice(db, symbolPattern, opts = {}) {
5003
5961
  }
5004
5962
  function backwardSlice(db, match) {
5005
5963
  const callees = getCalleeRowsForSymbol(db, match);
5006
- const localPredecessors = db.all(
5007
- `SELECT DISTINCT gs.symbol, d.relative_path AS file
5008
- FROM defn_enclosing_ranges der
5009
- JOIN global_symbols gs ON der.symbol_id = gs.id
5010
- JOIN documents d ON der.document_id = d.id
5011
- WHERE der.document_id = ?
5012
- AND der.end_line < ?
5013
- AND gs.id != ?
5014
- ${db.symbolNoiseFor("gs")}
5015
- ORDER BY der.start_line DESC
5016
- LIMIT 15`,
5017
- match.documentId,
5018
- match.startLine,
5019
- match.symbolId
5020
- );
5021
5964
  const seen = /* @__PURE__ */ new Set();
5022
5965
  const connected = [];
5023
5966
  for (const c of callees) {
@@ -5030,16 +5973,6 @@ function backwardSlice(db, match) {
5030
5973
  relationship: "referenced within definition (callee)"
5031
5974
  });
5032
5975
  }
5033
- for (const p of localPredecessors) {
5034
- if (seen.has(p.symbol) || db.isIgnored(p.file)) continue;
5035
- seen.add(p.symbol);
5036
- connected.push({
5037
- symbol: p.symbol,
5038
- shortName: shortenSymbol(p.symbol),
5039
- file: p.file,
5040
- relationship: "defined before target in same file (local predecessor)"
5041
- });
5042
- }
5043
5976
  return {
5044
5977
  symbol: match.symbol,
5045
5978
  shortName: shortenSymbol(match.symbol),
@@ -5370,6 +6303,15 @@ function runQuery(query, render) {
5370
6303
  render(query(db));
5371
6304
  });
5372
6305
  }
6306
+ function displayLine(line) {
6307
+ return line + 1;
6308
+ }
6309
+ function displayRange(startLine, endLine) {
6310
+ return `${displayLine(startLine)}-${displayLine(endLine)}`;
6311
+ }
6312
+ function displayPathRange(relativePath, startLine, endLine) {
6313
+ return `${relativePath}:${displayRange(startLine, endLine)}`;
6314
+ }
5373
6315
  var queries = {
5374
6316
  stats,
5375
6317
  files,
@@ -5469,7 +6411,7 @@ program.command("symbols <file>").description("List symbols defined in a file (w
5469
6411
  (results) => {
5470
6412
  for (const r of results) {
5471
6413
  const sig = r.signature ? ` \u2014 ${r.signature}` : "";
5472
- console.log(` ${r.startLine}-${r.endLine} ${r.shortName}${sig}`);
6414
+ console.log(` ${displayRange(r.startLine, r.endLine)} ${r.shortName}${sig}`);
5473
6415
  }
5474
6416
  }
5475
6417
  );
@@ -5479,7 +6421,7 @@ program.command("methods <className>").description("List methods of a class (wit
5479
6421
  (db) => queries.methods(db, className),
5480
6422
  (results) => {
5481
6423
  for (const r of results) {
5482
- console.log(` ${r.startLine}-${r.endLine} ${r.name}`);
6424
+ console.log(` ${displayRange(r.startLine, r.endLine)} ${r.name}`);
5483
6425
  }
5484
6426
  }
5485
6427
  );
@@ -5495,7 +6437,7 @@ program.command("refs <symbol>").description("Find all files referencing a symbo
5495
6437
  console.log(r.relativePath);
5496
6438
  prevFile = r.relativePath;
5497
6439
  }
5498
- console.log(` line ${r.line}`);
6440
+ console.log(` line ${displayLine(r.line)}`);
5499
6441
  }
5500
6442
  }
5501
6443
  );
@@ -5507,11 +6449,17 @@ program.command("trace <symbol>").description("Trace a symbol: definition + all
5507
6449
  console.log("\u2550\u2550\u2550 DEFINITION \u2550\u2550\u2550");
5508
6450
  for (const d of result.definitions) {
5509
6451
  const sig = d.signature ? ` \u2014 ${d.signature}` : "";
5510
- console.log(` ${d.relativePath}:${d.startLine}-${d.endLine}${sig}`);
6452
+ console.log(` ${displayPathRange(d.relativePath, d.startLine, d.endLine)}${sig}`);
5511
6453
  }
5512
6454
  console.log("\n\u2550\u2550\u2550 REFERENCED BY \u2550\u2550\u2550");
6455
+ let prevFile = "";
5513
6456
  for (const ref of result.referencedBy) {
5514
- console.log(` ${ref}`);
6457
+ if (ref.relativePath !== prevFile) {
6458
+ if (prevFile) console.log("");
6459
+ console.log(` ${ref.relativePath}`);
6460
+ prevFile = ref.relativePath;
6461
+ }
6462
+ console.log(` line ${displayLine(ref.line)} in ${ref.enclosingShort}`);
5515
6463
  }
5516
6464
  }
5517
6465
  );
@@ -5540,7 +6488,7 @@ program.command("system <module>").description("Full module map: files, symbols,
5540
6488
  for (const f of result.files) console.log(f);
5541
6489
  console.log("\n\u2550\u2550\u2550 EXPORTED SYMBOLS \u2550\u2550\u2550");
5542
6490
  for (const s of result.symbols) {
5543
- console.log(` ${s.startLine}-${s.endLine} ${s.shortName}`);
6491
+ console.log(` ${displayRange(s.startLine, s.endLine)} ${s.shortName}`);
5544
6492
  }
5545
6493
  console.log("\n\u2550\u2550\u2550 DEPENDS ON (internal) \u2550\u2550\u2550");
5546
6494
  for (const d of result.dependsOn) console.log(` ${d}`);
@@ -5581,7 +6529,7 @@ program.command("dead [scope]").description("Find dead code and file-internal sy
5581
6529
  prevFile = s.relativePath;
5582
6530
  }
5583
6531
  const tag = s.kind === "dead-code" ? "[dead code]" : "[file-internal only]";
5584
- console.log(` ${s.startLine}-${s.endLine} (${s.loc} LOC) ${s.shortName} ${tag}`);
6532
+ console.log(` ${displayRange(s.startLine, s.endLine)} (${s.loc} LOC) ${s.shortName} ${tag}`);
5585
6533
  }
5586
6534
  console.log("\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
5587
6535
  console.log(
@@ -5647,7 +6595,7 @@ program.command("outline <file>").description("Tree view of symbols in a file (u
5647
6595
  function printTree(nodes, indent) {
5648
6596
  for (const n of nodes) {
5649
6597
  const prefix = " ".repeat(indent);
5650
- console.log(`${prefix}${n.startLine}-${n.endLine} ${n.shortName}`);
6598
+ console.log(`${prefix}${displayRange(n.startLine, n.endLine)} ${n.shortName}`);
5651
6599
  printTree(n.children, indent + 1);
5652
6600
  }
5653
6601
  }
@@ -5660,7 +6608,7 @@ program.command("members <symbol>").description("All children of a symbol (metho
5660
6608
  (db) => queries.members(db, symbol),
5661
6609
  (results) => {
5662
6610
  for (const r of results) {
5663
- console.log(` ${r.startLine}-${r.endLine} [${r.kind}] ${r.shortName}`);
6611
+ console.log(` ${displayRange(r.startLine, r.endLine)} [${r.kind}] ${r.shortName}`);
5664
6612
  }
5665
6613
  }
5666
6614
  );
@@ -5770,7 +6718,7 @@ program.command("isolated").description("Find completely orphaned symbols (no re
5770
6718
  console.log(r.relativePath);
5771
6719
  prevFile = r.relativePath;
5772
6720
  }
5773
- console.log(` ${r.startLine}-${r.endLine} (${r.loc} LOC) ${r.shortName}`);
6721
+ console.log(` ${displayRange(r.startLine, r.endLine)} (${r.loc} LOC) ${r.shortName}`);
5774
6722
  }
5775
6723
  console.log(`
5776
6724
  ${results.length} isolated symbol(s)`);
@@ -5786,7 +6734,7 @@ program.command("by-kind <kind>").description("Find symbols by SCIP kind (class,
5786
6734
  console.log(`No symbols found for kind "${kind}". Use "kind-counts" to see available kinds.`);
5787
6735
  } else {
5788
6736
  for (const r of results) {
5789
- console.log(` ${r.relativePath}:${r.startLine}-${r.endLine} [${r.kindName}] ${r.shortName}`);
6737
+ console.log(` ${displayPathRange(r.relativePath, r.startLine, r.endLine)} [${r.kindName}] ${r.shortName}`);
5790
6738
  }
5791
6739
  console.log(`
5792
6740
  ${results.length} symbol(s)`);
@@ -5821,7 +6769,7 @@ program.command("doc-coverage").description("Check documentation coverage across
5821
6769
  if (result.undocumentedSymbols.length > 0) {
5822
6770
  console.log("\nUndocumented:");
5823
6771
  for (const s of result.undocumentedSymbols) {
5824
- console.log(` ${s.relativePath}:${s.startLine} ${s.shortName}`);
6772
+ console.log(` ${s.relativePath}:${displayLine(s.startLine)} ${s.shortName}`);
5825
6773
  }
5826
6774
  }
5827
6775
  }
@@ -6003,7 +6951,7 @@ program.command("extract-candidates").description("Find functions with natural e
6003
6951
  } else {
6004
6952
  for (const r of results) {
6005
6953
  console.log(`
6006
- ${r.relativePath}:${r.startLine}-${r.endLine} ${r.shortName} (${r.loc} LOC, ${r.totalCallees} callees)`);
6954
+ ${displayPathRange(r.relativePath, r.startLine, r.endLine)} ${r.shortName} (${r.loc} LOC, ${r.totalCallees} callees)`);
6007
6955
  for (let i = 0; i < r.clusters.length; i++) {
6008
6956
  const c = r.clusters[i];
6009
6957
  console.log(` Cluster ${i + 1} (${Math.round(c.isolation * 100)}% isolated, ${c.callees.length} callees):`);
@@ -6051,7 +6999,7 @@ program.command("change-surface <file>").description("Pre-change briefing: expor
6051
6999
  `);
6052
7000
  for (const s of result.symbols) {
6053
7001
  const risk = s.riskLevel === "high" ? " *** HIGH RISK ***" : s.riskLevel === "medium" ? " * medium risk *" : "";
6054
- console.log(` ${s.startLine}-${s.endLine} ${s.shortName} [${s.externalConsumers} consumers]${risk}`);
7002
+ console.log(` ${displayRange(s.startLine, s.endLine)} ${s.shortName} [${s.externalConsumers} consumers]${risk}`);
6055
7003
  }
6056
7004
  db.close();
6057
7005
  });
@@ -6111,7 +7059,7 @@ program.command("wrapper-candidates").description("Find symbols only called by o
6111
7059
  console.log("No wrapper candidates found.");
6112
7060
  } else {
6113
7061
  for (const r of results) {
6114
- console.log(` ${r.file}:${r.startLine}-${r.endLine} ${r.shortName} (${r.loc} LOC)`);
7062
+ console.log(` ${displayPathRange(r.file, r.startLine, r.endLine)} ${r.shortName} (${r.loc} LOC)`);
6115
7063
  console.log(` Only called by: ${r.singleCallerShort} (fan-in: ${r.callerFanIn})`);
6116
7064
  }
6117
7065
  console.log(`
@@ -6126,7 +7074,7 @@ program.command("passthrough-candidates").description("Find functions that just
6126
7074
  console.log("No passthrough candidates found.");
6127
7075
  } else {
6128
7076
  for (const r of results) {
6129
- console.log(` ${r.file}:${r.startLine}-${r.endLine} ${r.shortName} (${r.loc} LOC)`);
7077
+ console.log(` ${displayPathRange(r.file, r.startLine, r.endLine)} ${r.shortName} (${r.loc} LOC)`);
6130
7078
  console.log(` Forwards to: ${r.forwardsToShort} (${r.forwardsToFile})`);
6131
7079
  }
6132
7080
  console.log(`
@@ -6142,7 +7090,7 @@ program.command("stale-abstractions").description("Find types/interfaces with 0-
6142
7090
  } else {
6143
7091
  for (const r of results) {
6144
7092
  const label = r.consumers === 0 ? "unused" : "1 consumer";
6145
- console.log(` ${r.file}:${r.startLine}-${r.endLine} ${r.shortName} (${r.loc} LOC, ${label})`);
7093
+ console.log(` ${displayPathRange(r.file, r.startLine, r.endLine)} ${r.shortName} (${r.loc} LOC, ${label})`);
6146
7094
  }
6147
7095
  console.log(`
6148
7096
  ${results.length} stale abstraction(s).`);
@@ -6244,11 +7192,11 @@ program.command("code <symbol>").description("Read the source code for a symbol
6244
7192
  db.close();
6245
7193
  return;
6246
7194
  }
6247
- console.log(`${result.relativePath}:${result.startLine}-${result.endLine} ${result.shortName} [${result.language ?? "unknown"}]
7195
+ console.log(`${displayPathRange(result.relativePath, result.startLine, result.endLine)} ${result.shortName} [${result.language ?? "unknown"}]
6248
7196
  `);
6249
7197
  const lines = result.source.split("\n");
6250
7198
  for (let i = 0; i < lines.length; i++) {
6251
- console.log(` ${String(result.startLine + i).padStart(4)} ${lines[i]}`);
7199
+ console.log(` ${String(displayLine(result.startLine + i)).padStart(4)} ${lines[i]}`);
6252
7200
  }
6253
7201
  db.close();
6254
7202
  });
@@ -6260,7 +7208,7 @@ program.command("complexity <symbol>").description("Per-symbol complexity: branc
6260
7208
  db.close();
6261
7209
  return;
6262
7210
  }
6263
- console.log(`${result.relativePath}:${result.startLine}-${result.endLine} ${result.shortName}
7211
+ console.log(`${displayPathRange(result.relativePath, result.startLine, result.endLine)} ${result.shortName}
6264
7212
  `);
6265
7213
  console.log(` LOC: ${result.loc}`);
6266
7214
  console.log(` Branches: ${result.branches}`);
@@ -6283,13 +7231,13 @@ program.command("dataflow <symbol>").description("Reference-level dataflow: defi
6283
7231
  if (result.definitionSites.length > 0) {
6284
7232
  console.log(" \u2550\u2550\u2550 DEFINED AT \u2550\u2550\u2550");
6285
7233
  for (const s of result.definitionSites) {
6286
- console.log(` ${s.file}:${s.line}`);
7234
+ console.log(` ${s.file}:${displayLine(s.line)}`);
6287
7235
  }
6288
7236
  }
6289
7237
  if (result.usageSites.length > 0) {
6290
7238
  console.log("\n \u2550\u2550\u2550 USED AT \u2550\u2550\u2550");
6291
7239
  for (const s of result.usageSites) {
6292
- console.log(` ${s.file}:${s.line} in ${s.enclosingShort}`);
7240
+ console.log(` ${s.file}:${displayLine(s.line)} in ${s.enclosingShort}`);
6293
7241
  }
6294
7242
  }
6295
7243
  if (result.producers.length > 0) {
@@ -6376,7 +7324,7 @@ program.command("similar-signatures").description("Find functions with near-iden
6376
7324
  console.log(`
6377
7325
  Signature: ${g.signature} (${g.functions.length} functions)`);
6378
7326
  for (const f of g.functions) {
6379
- console.log(` ${f.file}:${f.startLine}-${f.endLine} ${f.shortName} (${f.loc} LOC)`);
7327
+ console.log(` ${displayPathRange(f.file, f.startLine, f.endLine)} ${f.shortName} (${f.loc} LOC)`);
6380
7328
  }
6381
7329
  }
6382
7330
  console.log(`
@@ -6445,9 +7393,20 @@ program.command("status").description("Show index status for this project").acti
6445
7393
  });
6446
7394
  }
6447
7395
  });
6448
- if (process.argv[1] && fileURLToPath2(import.meta.url) === process.argv[1]) {
7396
+ if (isCliEntrypoint()) {
6449
7397
  program.parse();
6450
7398
  }
7399
+ function isCliEntrypoint() {
7400
+ if (!process.argv[1]) {
7401
+ return false;
7402
+ }
7403
+ const thisFile = fileURLToPath2(import.meta.url);
7404
+ try {
7405
+ return realpathSync(thisFile) === realpathSync(process.argv[1]);
7406
+ } catch {
7407
+ return thisFile === process.argv[1];
7408
+ }
7409
+ }
6451
7410
  function collect(value, prev) {
6452
7411
  return prev.concat([value]);
6453
7412
  }