scip-query 0.2.1 → 0.3.2

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