scip-query 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/README.md +16 -43
  2. package/dist/chunk-2UELLEBI.js +1 -0
  3. package/dist/chunk-34JPTNRN.js +601 -0
  4. package/dist/{chunk-NDSQYIWT.js → chunk-3566TKJ5.js} +3 -3
  5. package/dist/{chunk-LB7OS35Q.js → chunk-4ACRRQC4.js} +8 -4
  6. package/dist/{chunk-3E2X7RIE.js → chunk-4BQFSNFI.js} +10 -6
  7. package/dist/{chunk-BP2ATLK2.js → chunk-6QSHLFSL.js} +4 -4
  8. package/dist/{chunk-5FGUEU7N.js → chunk-6WVR5K46.js} +18 -10
  9. package/dist/{chunk-XFXDXEUN.js → chunk-74RFWB5T.js} +2 -2
  10. package/dist/{chunk-MBVNHJVN.js → chunk-75RQSBTK.js} +2 -2
  11. package/dist/{chunk-YZAA4LYG.js → chunk-7HK5ZLOE.js} +30 -48
  12. package/dist/{chunk-T6ARFSBZ.js → chunk-7JFZSOJ7.js} +7 -7
  13. package/dist/{chunk-6SXADWLW.js → chunk-AKMBBKWV.js} +2 -2
  14. package/dist/{chunk-ZJRYBOEE.js → chunk-AMNISGYR.js} +5 -5
  15. package/dist/{chunk-CM454WL3.js → chunk-BFLULBEU.js} +3 -3
  16. package/dist/{chunk-Z73NYSBZ.js → chunk-CU62ZDHI.js} +2 -2
  17. package/dist/{chunk-TBP6BICL.js → chunk-DY4AFG2W.js} +13 -11
  18. package/dist/{chunk-2QZ23IBN.js → chunk-F7XU27LU.js} +4 -4
  19. package/dist/{chunk-KCBMVQL5.js → chunk-GPJVPT3U.js} +2 -2
  20. package/dist/{chunk-NUZ4OMU3.js → chunk-GU2H5QRN.js} +2 -2
  21. package/dist/{chunk-TSPZOMHC.js → chunk-H6WCPKCX.js} +6 -3
  22. package/dist/{chunk-KVSW5KYP.js → chunk-HDSRORNV.js} +4 -4
  23. package/dist/{chunk-LUSIFBXO.js → chunk-HMYJJ3HY.js} +9 -6
  24. package/dist/chunk-IJKLB2JW.js +69 -0
  25. package/dist/{chunk-6NBLIDF4.js → chunk-ITZ3DDOG.js} +2 -2
  26. package/dist/{chunk-GTILYBH6.js → chunk-IXPHLF6K.js} +6 -6
  27. package/dist/{chunk-BFSCMC22.js → chunk-KBOQX573.js} +3 -3
  28. package/dist/{chunk-FUHJCHS4.js → chunk-LLMPAG56.js} +95 -32
  29. package/dist/{chunk-FFSWWE5O.js → chunk-LTJC5ZQL.js} +3 -3
  30. package/dist/{chunk-LAWMH22O.js → chunk-M3NPW3FC.js} +2 -2
  31. package/dist/{chunk-VRUJH4BO.js → chunk-M4QGEKKD.js} +6 -28
  32. package/dist/{chunk-7OZPA5OO.js → chunk-MVH45PYK.js} +21 -41
  33. package/dist/chunk-N4C3H7LH.js +37 -0
  34. package/dist/chunk-NG5F43OU.js +200 -0
  35. package/dist/{chunk-6VJ6Q7IE.js → chunk-NVIIM34O.js} +4 -4
  36. package/dist/{chunk-GJFURBEW.js → chunk-ORINICIZ.js} +4 -4
  37. package/dist/{chunk-TDNNOR6D.js → chunk-PMJKOXOT.js} +7 -7
  38. package/dist/{chunk-QOV2R2WT.js → chunk-QIXNAB5K.js} +42 -2
  39. package/dist/{chunk-JKP5GH6T.js → chunk-R2I3M5B4.js} +2 -2
  40. package/dist/{chunk-36OMT7ZJ.js → chunk-R56FJU3E.js} +35 -14
  41. package/dist/{chunk-VZ7AMAFL.js → chunk-RFMT7UAZ.js} +3 -3
  42. package/dist/{chunk-SEFSL2GF.js → chunk-TOIEB3LG.js} +2 -2
  43. package/dist/chunk-VO4QI3LS.js +84 -0
  44. package/dist/{chunk-EMDQWNYR.js → chunk-WVK7AASK.js} +8 -8
  45. package/dist/{chunk-5WTJAXY2.js → chunk-Y3M323OX.js} +2 -2
  46. package/dist/{chunk-DCKMSTJ4.js → chunk-Y4JFVQ7C.js} +2 -2
  47. package/dist/{chunk-UNTPVD36.js → chunk-YAFWL3RA.js} +4 -4
  48. package/dist/{chunk-FGXRVW7G.js → chunk-YZ6L7GFO.js} +2 -2
  49. package/dist/cli.js +1401 -717
  50. package/dist/{db-BxaevAyc.d.ts → db-BHYam4BK.d.ts} +7 -19
  51. package/dist/index.d.ts +15 -15
  52. package/dist/index.js +263 -234
  53. package/dist/postinstall.js +5 -76
  54. package/dist/queries/affected.d.ts +1 -1
  55. package/dist/queries/affected.js +3 -3
  56. package/dist/queries/bottlenecks.d.ts +1 -1
  57. package/dist/queries/bottlenecks.js +2 -2
  58. package/dist/queries/by-kind.d.ts +1 -1
  59. package/dist/queries/by-kind.js +2 -2
  60. package/dist/queries/call-graph.d.ts +1 -1
  61. package/dist/queries/call-graph.js +3 -3
  62. package/dist/queries/change-surface.d.ts +2 -2
  63. package/dist/queries/change-surface.js +2 -3
  64. package/dist/queries/code.d.ts +1 -1
  65. package/dist/queries/code.js +3 -3
  66. package/dist/queries/complexity-hotspots.d.ts +1 -1
  67. package/dist/queries/complexity-hotspots.js +3 -3
  68. package/dist/queries/complexity.d.ts +1 -1
  69. package/dist/queries/complexity.js +3 -3
  70. package/dist/queries/convergence.d.ts +1 -1
  71. package/dist/queries/convergence.js +3 -3
  72. package/dist/queries/coupling.d.ts +1 -1
  73. package/dist/queries/coupling.js +1 -1
  74. package/dist/queries/cycles.d.ts +1 -1
  75. package/dist/queries/cycles.js +3 -2
  76. package/dist/queries/dataflow.d.ts +1 -1
  77. package/dist/queries/dataflow.js +3 -3
  78. package/dist/queries/dead.d.ts +1 -1
  79. package/dist/queries/dead.js +4 -3
  80. package/dist/queries/deep-chains.d.ts +1 -1
  81. package/dist/queries/deep-chains.js +3 -2
  82. package/dist/queries/deps.d.ts +1 -1
  83. package/dist/queries/diff-impact.d.ts +2 -2
  84. package/dist/queries/diff-impact.js +2 -3
  85. package/dist/queries/doc-coverage.d.ts +1 -1
  86. package/dist/queries/doc-coverage.js +2 -2
  87. package/dist/queries/drift.d.ts +1 -1
  88. package/dist/queries/drift.js +3 -2
  89. package/dist/queries/extract-candidates.d.ts +1 -1
  90. package/dist/queries/extract-candidates.js +3 -3
  91. package/dist/queries/fan.d.ts +1 -1
  92. package/dist/queries/fan.js +2 -2
  93. package/dist/queries/files.d.ts +1 -1
  94. package/dist/queries/health.d.ts +1 -1
  95. package/dist/queries/health.js +15 -15
  96. package/dist/queries/hierarchy.d.ts +1 -1
  97. package/dist/queries/hierarchy.js +3 -2
  98. package/dist/queries/hotspots.d.ts +1 -1
  99. package/dist/queries/hotspots.js +2 -2
  100. package/dist/queries/imports.d.ts +1 -1
  101. package/dist/queries/imports.js +3 -2
  102. package/dist/queries/index.d.ts +1 -2
  103. package/dist/queries/index.js +46 -51
  104. package/dist/queries/isolated.d.ts +1 -1
  105. package/dist/queries/isolated.js +4 -3
  106. package/dist/queries/members.d.ts +2 -2
  107. package/dist/queries/members.js +3 -2
  108. package/dist/queries/methods.d.ts +1 -1
  109. package/dist/queries/methods.js +2 -2
  110. package/dist/queries/outline.d.ts +1 -1
  111. package/dist/queries/outline.js +2 -2
  112. package/dist/queries/passthrough-candidates.d.ts +1 -1
  113. package/dist/queries/passthrough-candidates.js +3 -3
  114. package/dist/queries/redundant-reexports.d.ts +1 -1
  115. package/dist/queries/redundant-reexports.js +4 -2
  116. package/dist/queries/refs.d.ts +1 -1
  117. package/dist/queries/refs.js +1 -1
  118. package/dist/queries/similar-chains.d.ts +1 -1
  119. package/dist/queries/similar-chains.js +3 -2
  120. package/dist/queries/similar-files.d.ts +1 -1
  121. package/dist/queries/similar-files.js +3 -2
  122. package/dist/queries/similar-signatures.d.ts +1 -1
  123. package/dist/queries/similar-signatures.js +2 -2
  124. package/dist/queries/similar.d.ts +1 -1
  125. package/dist/queries/similar.js +3 -3
  126. package/dist/queries/slice.d.ts +1 -1
  127. package/dist/queries/slice.js +3 -3
  128. package/dist/queries/stale-abstractions.d.ts +1 -1
  129. package/dist/queries/stale-abstractions.js +3 -3
  130. package/dist/queries/stats.d.ts +1 -1
  131. package/dist/queries/stats.js +1 -1
  132. package/dist/queries/surface.d.ts +1 -1
  133. package/dist/queries/surface.js +2 -2
  134. package/dist/queries/symbols.d.ts +1 -1
  135. package/dist/queries/symbols.js +2 -2
  136. package/dist/queries/system.d.ts +1 -1
  137. package/dist/queries/system.js +2 -2
  138. package/dist/queries/trace.d.ts +1 -1
  139. package/dist/queries/trace.js +3 -1
  140. package/dist/queries/wrapper-candidates.d.ts +1 -1
  141. package/dist/queries/wrapper-candidates.js +3 -3
  142. package/dist/reindex-worker.js +24 -12
  143. package/package.json +6 -1
  144. package/IMPROVEMENTS.md +0 -143
  145. package/PLAN.md +0 -320
  146. package/dist/chunk-2QZ23IBN.js.map +0 -1
  147. package/dist/chunk-36OMT7ZJ.js.map +0 -1
  148. package/dist/chunk-3E2X7RIE.js.map +0 -1
  149. package/dist/chunk-3UOUTZQT.js +0 -45
  150. package/dist/chunk-3UOUTZQT.js.map +0 -1
  151. package/dist/chunk-3ZZJVBIO.js +0 -88
  152. package/dist/chunk-3ZZJVBIO.js.map +0 -1
  153. package/dist/chunk-4TYLS5XX.js.map +0 -1
  154. package/dist/chunk-5FGUEU7N.js.map +0 -1
  155. package/dist/chunk-5WTJAXY2.js.map +0 -1
  156. package/dist/chunk-6NBLIDF4.js.map +0 -1
  157. package/dist/chunk-6SXADWLW.js.map +0 -1
  158. package/dist/chunk-6VJ6Q7IE.js.map +0 -1
  159. package/dist/chunk-7OZPA5OO.js.map +0 -1
  160. package/dist/chunk-BEPIEVLR.js +0 -76
  161. package/dist/chunk-BEPIEVLR.js.map +0 -1
  162. package/dist/chunk-BFSCMC22.js.map +0 -1
  163. package/dist/chunk-BP2ATLK2.js.map +0 -1
  164. package/dist/chunk-CM454WL3.js.map +0 -1
  165. package/dist/chunk-DCKMSTJ4.js.map +0 -1
  166. package/dist/chunk-DEZKCZXD.js +0 -40
  167. package/dist/chunk-DEZKCZXD.js.map +0 -1
  168. package/dist/chunk-DVWGWHFW.js +0 -99
  169. package/dist/chunk-DVWGWHFW.js.map +0 -1
  170. package/dist/chunk-EMDQWNYR.js.map +0 -1
  171. package/dist/chunk-FFSWWE5O.js.map +0 -1
  172. package/dist/chunk-FGXRVW7G.js.map +0 -1
  173. package/dist/chunk-FUHJCHS4.js.map +0 -1
  174. package/dist/chunk-GJFURBEW.js.map +0 -1
  175. package/dist/chunk-GTILYBH6.js.map +0 -1
  176. package/dist/chunk-JJP7KQND.js +0 -1
  177. package/dist/chunk-JJP7KQND.js.map +0 -1
  178. package/dist/chunk-JKP5GH6T.js.map +0 -1
  179. package/dist/chunk-KCBMVQL5.js.map +0 -1
  180. package/dist/chunk-KVSW5KYP.js.map +0 -1
  181. package/dist/chunk-LAWMH22O.js.map +0 -1
  182. package/dist/chunk-LB7OS35Q.js.map +0 -1
  183. package/dist/chunk-LUSIFBXO.js.map +0 -1
  184. package/dist/chunk-MBVNHJVN.js.map +0 -1
  185. package/dist/chunk-MGNMHKX3.js.map +0 -1
  186. package/dist/chunk-N5KEREIA.js.map +0 -1
  187. package/dist/chunk-NDSQYIWT.js.map +0 -1
  188. package/dist/chunk-NUZ4OMU3.js.map +0 -1
  189. package/dist/chunk-QOV2R2WT.js.map +0 -1
  190. package/dist/chunk-SEFSL2GF.js.map +0 -1
  191. package/dist/chunk-T6ARFSBZ.js.map +0 -1
  192. package/dist/chunk-TBP6BICL.js.map +0 -1
  193. package/dist/chunk-TDNNOR6D.js.map +0 -1
  194. package/dist/chunk-TSPZOMHC.js.map +0 -1
  195. package/dist/chunk-UNTPVD36.js.map +0 -1
  196. package/dist/chunk-VRUJH4BO.js.map +0 -1
  197. package/dist/chunk-VZ7AMAFL.js.map +0 -1
  198. package/dist/chunk-XFXDXEUN.js.map +0 -1
  199. package/dist/chunk-YZAA4LYG.js.map +0 -1
  200. package/dist/chunk-Z73NYSBZ.js.map +0 -1
  201. package/dist/chunk-ZJRYBOEE.js.map +0 -1
  202. package/dist/cli.js.map +0 -1
  203. package/dist/index.js.map +0 -1
  204. package/dist/postinstall.js.map +0 -1
  205. package/dist/queries/affected.js.map +0 -1
  206. package/dist/queries/bottlenecks.js.map +0 -1
  207. package/dist/queries/by-kind.js.map +0 -1
  208. package/dist/queries/call-graph.js.map +0 -1
  209. package/dist/queries/change-surface.js.map +0 -1
  210. package/dist/queries/clean-signature.js.map +0 -1
  211. package/dist/queries/code.js.map +0 -1
  212. package/dist/queries/complexity-hotspots.js.map +0 -1
  213. package/dist/queries/complexity.js.map +0 -1
  214. package/dist/queries/convergence.js.map +0 -1
  215. package/dist/queries/coupling.js.map +0 -1
  216. package/dist/queries/cycles.js.map +0 -1
  217. package/dist/queries/dataflow.js.map +0 -1
  218. package/dist/queries/dead.js.map +0 -1
  219. package/dist/queries/deep-chains.js.map +0 -1
  220. package/dist/queries/deps.js.map +0 -1
  221. package/dist/queries/diff-impact.js.map +0 -1
  222. package/dist/queries/doc-coverage.js.map +0 -1
  223. package/dist/queries/drift.js.map +0 -1
  224. package/dist/queries/extract-candidates.js.map +0 -1
  225. package/dist/queries/fan.js.map +0 -1
  226. package/dist/queries/files.js.map +0 -1
  227. package/dist/queries/health.js.map +0 -1
  228. package/dist/queries/hierarchy.js.map +0 -1
  229. package/dist/queries/hotspots.js.map +0 -1
  230. package/dist/queries/imports.js.map +0 -1
  231. package/dist/queries/index.js.map +0 -1
  232. package/dist/queries/isolated.js.map +0 -1
  233. package/dist/queries/members.js.map +0 -1
  234. package/dist/queries/methods.js.map +0 -1
  235. package/dist/queries/outline.js.map +0 -1
  236. package/dist/queries/passthrough-candidates.js.map +0 -1
  237. package/dist/queries/redundant-reexports.js.map +0 -1
  238. package/dist/queries/refs.js.map +0 -1
  239. package/dist/queries/similar-chains.js.map +0 -1
  240. package/dist/queries/similar-files.js.map +0 -1
  241. package/dist/queries/similar-signatures.js.map +0 -1
  242. package/dist/queries/similar.js.map +0 -1
  243. package/dist/queries/slice.js.map +0 -1
  244. package/dist/queries/stale-abstractions.js.map +0 -1
  245. package/dist/queries/stats.js.map +0 -1
  246. package/dist/queries/surface.js.map +0 -1
  247. package/dist/queries/symbols.js.map +0 -1
  248. package/dist/queries/system.js.map +0 -1
  249. package/dist/queries/test-coverage.d.ts +0 -22
  250. package/dist/queries/test-coverage.js +0 -11
  251. package/dist/queries/test-coverage.js.map +0 -1
  252. package/dist/queries/trace.js.map +0 -1
  253. package/dist/queries/wrapper-candidates.js.map +0 -1
  254. package/dist/reindex-worker.js.map +0 -1
  255. package/docs/AGENT_GUIDE.md +0 -359
  256. package/reports/debloat/2026-04-10-scip-query-self-audit.md +0 -161
  257. package/src/cli.ts +0 -1480
  258. package/src/config.ts +0 -117
  259. package/src/db.ts +0 -127
  260. package/src/gitignore-filter.ts +0 -143
  261. package/src/index.ts +0 -11
  262. package/src/postinstall.ts +0 -8
  263. package/src/queries/affected.ts +0 -86
  264. package/src/queries/bottlenecks.ts +0 -67
  265. package/src/queries/by-kind.ts +0 -204
  266. package/src/queries/call-graph.ts +0 -66
  267. package/src/queries/change-surface.ts +0 -110
  268. package/src/queries/clean-signature.ts +0 -22
  269. package/src/queries/code.ts +0 -101
  270. package/src/queries/complexity-hotspots.ts +0 -119
  271. package/src/queries/complexity.ts +0 -152
  272. package/src/queries/convergence.ts +0 -82
  273. package/src/queries/coupling.ts +0 -99
  274. package/src/queries/cycles.ts +0 -78
  275. package/src/queries/dataflow.ts +0 -128
  276. package/src/queries/dead.ts +0 -122
  277. package/src/queries/deep-chains.ts +0 -59
  278. package/src/queries/deps.ts +0 -46
  279. package/src/queries/diff-impact.ts +0 -204
  280. package/src/queries/doc-coverage.ts +0 -86
  281. package/src/queries/drift.ts +0 -224
  282. package/src/queries/extract-candidates.ts +0 -167
  283. package/src/queries/fan.ts +0 -148
  284. package/src/queries/files.ts +0 -16
  285. package/src/queries/health.ts +0 -324
  286. package/src/queries/hierarchy.ts +0 -49
  287. package/src/queries/hotspots.ts +0 -53
  288. package/src/queries/imports.ts +0 -95
  289. package/src/queries/index.ts +0 -45
  290. package/src/queries/isolated.ts +0 -67
  291. package/src/queries/members.ts +0 -54
  292. package/src/queries/methods.ts +0 -27
  293. package/src/queries/outline.ts +0 -52
  294. package/src/queries/passthrough-candidates.ts +0 -94
  295. package/src/queries/redundant-reexports.ts +0 -170
  296. package/src/queries/refs.ts +0 -27
  297. package/src/queries/similar-chains.ts +0 -314
  298. package/src/queries/similar-files.ts +0 -140
  299. package/src/queries/similar-signatures.ts +0 -151
  300. package/src/queries/similar.ts +0 -305
  301. package/src/queries/slice.ts +0 -154
  302. package/src/queries/stale-abstractions.ts +0 -82
  303. package/src/queries/stats.ts +0 -22
  304. package/src/queries/surface.ts +0 -34
  305. package/src/queries/symbols.ts +0 -39
  306. package/src/queries/system.ts +0 -86
  307. package/src/queries/test-coverage.ts +0 -106
  308. package/src/queries/trace.ts +0 -55
  309. package/src/queries/wrapper-candidates.ts +0 -112
  310. package/src/query-support.ts +0 -226
  311. package/src/reindex/detect.ts +0 -58
  312. package/src/reindex/index.ts +0 -153
  313. package/src/reindex/indexers.ts +0 -220
  314. package/src/reindex/install.ts +0 -125
  315. package/src/reindex-worker.ts +0 -35
  316. package/src/setup.ts +0 -202
  317. package/src/symbol-parser.ts +0 -278
  318. package/src/types.ts +0 -654
  319. package/src/watch.ts +0 -274
  320. package/tests/gitignore-filter.test.ts +0 -48
  321. package/tests/queries.test.ts +0 -300
  322. package/tests/symbol-parser.test.ts +0 -157
  323. package/tsconfig.json +0 -20
  324. package/tsup.config.ts +0 -40
  325. package/vitest.config.ts +0 -7
package/dist/cli.js CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { program } from "commander";
5
- import { existsSync as existsSync7 } from "fs";
6
- import { join as join9 } from "path";
5
+ import { existsSync as existsSync8 } from "fs";
6
+ import { join as join10 } from "path";
7
+ import { fileURLToPath as fileURLToPath2 } from "url";
7
8
 
8
9
  // src/db.ts
9
10
  import Database from "better-sqlite3";
@@ -272,10 +273,192 @@ function ensureDir(dir) {
272
273
  }
273
274
 
274
275
  // src/reindex/index.ts
275
- import { execFileSync as execFileSync2 } from "child_process";
276
+ import { execFileSync as execFileSync3 } from "child_process";
276
277
  import { existsSync as existsSync4 } from "fs";
277
278
  import { join as join4 } from "path";
278
279
 
280
+ // src/scip-cli.ts
281
+ import { execFileSync as execFileSync2 } from "child_process";
282
+ import { platform as platform2, arch } from "os";
283
+
284
+ // src/reindex/install.ts
285
+ import { execFileSync } from "child_process";
286
+ import { platform } from "os";
287
+ var IS_WINDOWS = platform() === "win32";
288
+ function isBinaryAvailable(name) {
289
+ const cmd = IS_WINDOWS ? "where" : "which";
290
+ try {
291
+ execFileSync(cmd, [name], { stdio: "pipe" });
292
+ return true;
293
+ } catch {
294
+ return false;
295
+ }
296
+ }
297
+ function getBinaryCandidates(config) {
298
+ return [config.indexerBinary, ...config.binaryAliases ?? []];
299
+ }
300
+ function describeIndexerBinary(config) {
301
+ const candidates = getBinaryCandidates(config);
302
+ return candidates.length === 1 ? candidates[0] : candidates.join(" or ");
303
+ }
304
+ function resolveIndexerBinary(config) {
305
+ for (const candidate of getBinaryCandidates(config)) {
306
+ if (isBinaryAvailable(candidate)) {
307
+ return candidate;
308
+ }
309
+ }
310
+ return null;
311
+ }
312
+ function isIndexerInstalled(config) {
313
+ return resolveIndexerBinary(config) !== null;
314
+ }
315
+ function tryInstallIndexer(config, onStatus) {
316
+ const methods2 = config.installMethods;
317
+ const binaryLabel = describeIndexerBinary(config);
318
+ if (!methods2?.length) {
319
+ onStatus(`No auto-install method available for ${binaryLabel}.`);
320
+ if (config.installUrl) {
321
+ onStatus(`Install manually from: ${config.installUrl}`);
322
+ }
323
+ return false;
324
+ }
325
+ for (const method of methods2) {
326
+ if (!isBinaryAvailable(method.prerequisite)) {
327
+ continue;
328
+ }
329
+ onStatus(`Installing ${binaryLabel} via ${method.label}...`);
330
+ try {
331
+ execFileSync(method.binary, method.args, {
332
+ stdio: "inherit",
333
+ timeout: 3e5,
334
+ env: process.env
335
+ });
336
+ const resolvedBinary = resolveIndexerBinary(config);
337
+ if (resolvedBinary) {
338
+ const resolutionNote = resolvedBinary === config.indexerBinary ? "" : ` (using ${resolvedBinary})`;
339
+ onStatus(`Successfully installed ${binaryLabel} via ${method.label}${resolutionNote}`);
340
+ return true;
341
+ }
342
+ onStatus(`${method.label} command completed but ${binaryLabel} was not found on PATH`);
343
+ } catch (err) {
344
+ const msg = err instanceof Error ? err.message : String(err);
345
+ onStatus(`${method.label} install failed: ${msg}`);
346
+ }
347
+ }
348
+ onStatus(`Could not auto-install ${binaryLabel}.`);
349
+ if (config.installUrl) {
350
+ onStatus(`Install manually from: ${config.installUrl}`);
351
+ }
352
+ return false;
353
+ }
354
+
355
+ // src/scip-cli.ts
356
+ var IS_WINDOWS2 = platform2() === "win32";
357
+ var SCIP_VERSION = "v0.7.0";
358
+ function isScipInstalled() {
359
+ try {
360
+ const cmd = IS_WINDOWS2 ? "where" : "which";
361
+ execFileSync2(cmd, ["scip"], { stdio: "pipe" });
362
+ return true;
363
+ } catch {
364
+ return false;
365
+ }
366
+ }
367
+ function getScipDownloadUrl() {
368
+ const os = platform2();
369
+ const cpu = arch();
370
+ let osName;
371
+ let archName;
372
+ let ext;
373
+ switch (os) {
374
+ case "darwin":
375
+ osName = "darwin";
376
+ ext = "tar.gz";
377
+ break;
378
+ case "linux":
379
+ osName = "linux";
380
+ ext = "tar.gz";
381
+ break;
382
+ case "win32":
383
+ osName = "windows";
384
+ ext = "zip";
385
+ break;
386
+ default:
387
+ return null;
388
+ }
389
+ switch (cpu) {
390
+ case "arm64":
391
+ archName = "arm64";
392
+ break;
393
+ case "x64":
394
+ archName = "amd64";
395
+ break;
396
+ default:
397
+ return null;
398
+ }
399
+ const filename = `scip-${osName}-${archName}.${ext}`;
400
+ const url = `https://github.com/sourcegraph/scip/releases/download/${SCIP_VERSION}/${filename}`;
401
+ return { url, filename };
402
+ }
403
+ function printScipInstallInstructions() {
404
+ const download = getScipDownloadUrl();
405
+ console.log("\nThe `scip` CLI is required but not found on PATH.\n");
406
+ if (platform2() === "darwin") {
407
+ console.log("Install via Homebrew:");
408
+ console.log(" brew install sourcegraph/scip/scip\n");
409
+ console.log("Or download manually:");
410
+ } else {
411
+ console.log("Download from:");
412
+ }
413
+ if (download) {
414
+ console.log(` ${download.url}
415
+ `);
416
+ } else {
417
+ console.log(` https://github.com/sourcegraph/scip/releases/tag/${SCIP_VERSION}
418
+ `);
419
+ }
420
+ console.log("After installing, ensure `scip` is on your PATH and run `scip-query reindex`.");
421
+ }
422
+ function tryInstallScipCli(onStatus) {
423
+ if (platform2() === "darwin" && isBinaryAvailable("brew")) {
424
+ onStatus("Installing scip CLI via Homebrew...");
425
+ try {
426
+ execFileSync2("brew", ["install", "sourcegraph/scip/scip"], {
427
+ stdio: "inherit",
428
+ timeout: 3e5,
429
+ env: process.env
430
+ });
431
+ if (isBinaryAvailable("scip")) {
432
+ onStatus("Successfully installed scip CLI via Homebrew");
433
+ return true;
434
+ }
435
+ } catch (err) {
436
+ const msg = err instanceof Error ? err.message : String(err);
437
+ onStatus(`Homebrew install failed: ${msg}`);
438
+ }
439
+ }
440
+ if (isBinaryAvailable("go")) {
441
+ onStatus("Installing scip CLI via go install...");
442
+ try {
443
+ execFileSync2("go", ["install", "github.com/sourcegraph/scip/cmd/scip@latest"], {
444
+ stdio: "inherit",
445
+ timeout: 3e5,
446
+ env: process.env
447
+ });
448
+ if (isBinaryAvailable("scip")) {
449
+ onStatus("Successfully installed scip CLI via go install");
450
+ return true;
451
+ }
452
+ } catch (err) {
453
+ const msg = err instanceof Error ? err.message : String(err);
454
+ onStatus(`go install failed: ${msg}`);
455
+ }
456
+ }
457
+ onStatus("Could not auto-install scip CLI.");
458
+ onStatus("Install manually from: https://github.com/sourcegraph/scip/releases");
459
+ return false;
460
+ }
461
+
279
462
  // src/reindex/detect.ts
280
463
  import { existsSync as existsSync3 } from "fs";
281
464
  import { join as join3 } from "path";
@@ -406,9 +589,10 @@ var INDEXER_CONFIGS = {
406
589
  python: {
407
590
  language: "python",
408
591
  indexerBinary: "scip-python",
592
+ binaryAliases: ["scip-python-plus"],
409
593
  checkCommand: "scip-python --version",
410
- indexArgs: ({ outputPath }) => ({
411
- binary: "scip-python",
594
+ indexArgs: ({ outputPath, indexerBinary }) => ({
595
+ binary: indexerBinary,
412
596
  args: ["index", "--output", outputPath, "--project-name", "project"]
413
597
  }),
414
598
  markerFiles: ["pyproject.toml", "setup.py"],
@@ -514,98 +698,6 @@ function getIndexerConfig(language) {
514
698
  return INDEXER_CONFIGS[language];
515
699
  }
516
700
 
517
- // src/reindex/install.ts
518
- import { execFileSync } from "child_process";
519
- import { platform } from "os";
520
- var IS_WINDOWS = platform() === "win32";
521
- function isBinaryAvailable(name) {
522
- const cmd = IS_WINDOWS ? "where" : "which";
523
- try {
524
- execFileSync(cmd, [name], { stdio: "pipe" });
525
- return true;
526
- } catch {
527
- return false;
528
- }
529
- }
530
- function isIndexerInstalled(config) {
531
- return isBinaryAvailable(config.indexerBinary);
532
- }
533
- function tryInstallIndexer(config, onStatus) {
534
- const methods2 = config.installMethods;
535
- if (!methods2?.length) {
536
- onStatus(`No auto-install method available for ${config.indexerBinary}.`);
537
- if (config.installUrl) {
538
- onStatus(`Install manually from: ${config.installUrl}`);
539
- }
540
- return false;
541
- }
542
- for (const method of methods2) {
543
- if (!isBinaryAvailable(method.prerequisite)) {
544
- continue;
545
- }
546
- onStatus(`Installing ${config.indexerBinary} via ${method.label}...`);
547
- try {
548
- execFileSync(method.binary, method.args, {
549
- stdio: "inherit",
550
- timeout: 3e5,
551
- env: process.env
552
- });
553
- if (isIndexerInstalled(config)) {
554
- onStatus(`Successfully installed ${config.indexerBinary} via ${method.label}`);
555
- return true;
556
- }
557
- onStatus(`${method.label} command completed but ${config.indexerBinary} not found on PATH`);
558
- } catch (err) {
559
- const msg = err instanceof Error ? err.message : String(err);
560
- onStatus(`${method.label} install failed: ${msg}`);
561
- }
562
- }
563
- onStatus(`Could not auto-install ${config.indexerBinary}.`);
564
- if (config.installUrl) {
565
- onStatus(`Install manually from: ${config.installUrl}`);
566
- }
567
- return false;
568
- }
569
- function tryInstallScipCli(onStatus) {
570
- if (platform() === "darwin" && isBinaryAvailable("brew")) {
571
- onStatus("Installing scip CLI via Homebrew...");
572
- try {
573
- execFileSync("brew", ["install", "sourcegraph/scip/scip"], {
574
- stdio: "inherit",
575
- timeout: 3e5,
576
- env: process.env
577
- });
578
- if (isBinaryAvailable("scip")) {
579
- onStatus("Successfully installed scip CLI via Homebrew");
580
- return true;
581
- }
582
- } catch (err) {
583
- const msg = err instanceof Error ? err.message : String(err);
584
- onStatus(`Homebrew install failed: ${msg}`);
585
- }
586
- }
587
- if (isBinaryAvailable("go")) {
588
- onStatus("Installing scip CLI via go install...");
589
- try {
590
- execFileSync("go", ["install", "github.com/sourcegraph/scip/cmd/scip@latest"], {
591
- stdio: "inherit",
592
- timeout: 3e5,
593
- env: process.env
594
- });
595
- if (isBinaryAvailable("scip")) {
596
- onStatus("Successfully installed scip CLI via go install");
597
- return true;
598
- }
599
- } catch (err) {
600
- const msg = err instanceof Error ? err.message : String(err);
601
- onStatus(`go install failed: ${msg}`);
602
- }
603
- }
604
- onStatus("Could not auto-install scip CLI.");
605
- onStatus("Install manually from: https://github.com/sourcegraph/scip/releases");
606
- return false;
607
- }
608
-
609
701
  // src/reindex/index.ts
610
702
  async function reindex(opts) {
611
703
  const {
@@ -643,29 +735,38 @@ async function reindex(opts) {
643
735
  };
644
736
  for (const lang of languages) {
645
737
  const config = getIndexerConfig(lang);
738
+ const binaryLabel = describeIndexerBinary(config);
646
739
  if (!isIndexerInstalled(config)) {
647
740
  if (skipAutoInstall) {
648
741
  throw new Error(
649
- `${config.indexerBinary} is required to index ${lang} but not found on PATH.
650
- ` + (config.installUrl ? `Install from: ${config.installUrl}` : `Make sure ${config.indexerBinary} is installed and available on PATH.`)
742
+ `${binaryLabel} is required to index ${lang} but not found on PATH.
743
+ ` + (config.installUrl ? `Install from: ${config.installUrl}` : `Make sure ${binaryLabel} is installed and available on PATH.`)
651
744
  );
652
745
  }
653
- onStatus(`${config.indexerBinary} not found. Attempting auto-install...`);
746
+ onStatus(`${binaryLabel} not found. Attempting auto-install...`);
654
747
  if (!tryInstallIndexer(config, onStatus)) {
655
748
  throw new Error(
656
- `${config.indexerBinary} is required to index ${lang} but could not be installed.
657
- ` + (config.installUrl ? `Install manually from: ${config.installUrl}` : `Make sure ${config.indexerBinary} is installed and available on PATH.`)
749
+ `${binaryLabel} is required to index ${lang} but could not be installed.
750
+ ` + (config.installUrl ? `Install manually from: ${config.installUrl}` : `Make sure ${binaryLabel} is installed and available on PATH.`)
658
751
  );
659
752
  }
660
753
  }
661
- onStatus(`Indexing ${lang} with ${config.indexerBinary}...`);
754
+ const resolvedBinary = resolveIndexerBinary(config);
755
+ if (!resolvedBinary) {
756
+ throw new Error(
757
+ `${binaryLabel} is required to index ${lang} but was not found on PATH after installation checks.
758
+ ` + (config.installUrl ? `Install manually from: ${config.installUrl}` : `Make sure ${binaryLabel} is installed and available on PATH.`)
759
+ );
760
+ }
761
+ onStatus(`Indexing ${lang} with ${resolvedBinary}...`);
662
762
  const { binary, args } = config.indexArgs({
663
763
  projectRoot,
664
764
  outputPath: outputScip,
665
- pnpmWorkspaces: opts.pnpmWorkspaces
765
+ pnpmWorkspaces: opts.pnpmWorkspaces,
766
+ indexerBinary: resolvedBinary
666
767
  });
667
768
  try {
668
- execFileSync2(binary, args, {
769
+ execFileSync3(binary, args, {
669
770
  cwd: projectRoot,
670
771
  env,
671
772
  stdio: "pipe",
@@ -674,8 +775,8 @@ async function reindex(opts) {
674
775
  } catch (err) {
675
776
  const msg = err instanceof Error ? err.message : String(err);
676
777
  throw new Error(
677
- `Failed to index ${lang} with ${config.indexerBinary}: ${msg}
678
- Make sure ${config.indexerBinary} is installed and available on PATH.`
778
+ `Failed to index ${lang} with ${resolvedBinary}: ${msg}
779
+ Make sure ${binaryLabel} is installed and available on PATH.`
679
780
  );
680
781
  }
681
782
  }
@@ -684,7 +785,7 @@ Make sure ${config.indexerBinary} is installed and available on PATH.`
684
785
  throw new Error(`SCIP index not found at ${outputScip} after indexing`);
685
786
  }
686
787
  try {
687
- execFileSync2("scip", ["expt-convert", "--output", outputDb, outputScip], {
788
+ execFileSync3("scip", ["expt-convert", "--output", outputDb, outputScip], {
688
789
  env,
689
790
  stdio: "pipe",
690
791
  maxBuffer: 50 * 1024 * 1024
@@ -860,7 +961,7 @@ var Watcher = class {
860
961
  * Writes to index.db.tmp, then atomically renames to index.db.
861
962
  */
862
963
  runReindex() {
863
- return new Promise((resolve3, reject) => {
964
+ return new Promise((resolve4, reject) => {
864
965
  const start = Date.now();
865
966
  const tmpDb = this.indexPaths.dbPath + ".tmp";
866
967
  const tmpScip = this.indexPaths.indexPath + ".tmp";
@@ -888,7 +989,7 @@ var Watcher = class {
888
989
  if (existsSync5(tmpScip)) {
889
990
  renameSync(tmpScip, this.indexPaths.indexPath);
890
991
  }
891
- resolve3(Date.now() - start);
992
+ resolve4(Date.now() - start);
892
993
  } catch (err) {
893
994
  reject(new Error(`Atomic swap failed: ${err}`));
894
995
  }
@@ -913,7 +1014,7 @@ function stats(db) {
913
1014
  "SELECT COUNT(*) as c FROM mentions WHERE role = 1"
914
1015
  ).c;
915
1016
  const references = db.get(
916
- "SELECT COUNT(*) as c FROM mentions WHERE role = 0"
1017
+ "SELECT COUNT(*) as c FROM mentions WHERE role != 1"
917
1018
  ).c;
918
1019
  return {
919
1020
  documents,
@@ -1099,11 +1200,47 @@ function leafName(raw) {
1099
1200
  const last = sym.descriptors[sym.descriptors.length - 1];
1100
1201
  return last.name;
1101
1202
  }
1102
-
1103
- // src/queries/clean-signature.ts
1104
- function cleanSignature(sig) {
1105
- if (!sig || !sig.trim()) return null;
1106
- 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;
1203
+ function leafSuffix(raw) {
1204
+ const parsed = parseSymbol(raw);
1205
+ if ("kind" in parsed && parsed.kind === "local") {
1206
+ return null;
1207
+ }
1208
+ const sym = parsed;
1209
+ const last = sym.descriptors[sym.descriptors.length - 1];
1210
+ return last?.suffix ?? null;
1211
+ }
1212
+ function isFunctionLikeSymbol(raw) {
1213
+ const suffix = leafSuffix(raw);
1214
+ return suffix === "method" || suffix === "term";
1215
+ }
1216
+ function isModuleLikeSymbol(raw) {
1217
+ return leafSuffix(raw) === "namespace";
1218
+ }
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;
1224
+ }
1225
+ const parentDescriptors = parent.descriptors;
1226
+ const candidateDescriptors = candidate.descriptors;
1227
+ if (candidateDescriptors.length !== parentDescriptors.length + 1) {
1228
+ return false;
1229
+ }
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;
1235
+ }
1236
+ }
1237
+ return true;
1238
+ }
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;
1107
1244
  }
1108
1245
 
1109
1246
  // src/queries/symbols.ts
@@ -1163,7 +1300,7 @@ function refs(db, symbolPattern) {
1163
1300
  JOIN global_symbols gs ON m.symbol_id = gs.id
1164
1301
  WHERE gs.symbol LIKE ?
1165
1302
  AND ${db.localSymbolPredicate}
1166
- AND m.role = 0
1303
+ AND m.role != 1
1167
1304
  ORDER BY d.relative_path, c.start_line`,
1168
1305
  `%${symbolPattern}%`
1169
1306
  );
@@ -1173,20 +1310,228 @@ function refs(db, symbolPattern) {
1173
1310
  }));
1174
1311
  }
1175
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 ");
1331
+ }
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);
1354
+ }
1355
+ return graph;
1356
+ }
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
+ };
1384
+ }
1385
+ }
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 };
1396
+ }
1397
+ }
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
+ };
1407
+ }
1408
+ return null;
1409
+ }
1410
+ function normalizeLookupPattern(symbolPattern) {
1411
+ return symbolPattern.trim().replace(/\(\)$/, "").replace(/\(.*$/, "");
1412
+ }
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];
1417
+ }
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
1435
+ );
1436
+ }
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;
1465
+ }
1466
+ if (isFunctionLikeSymbol(row.symbol) && leaf === noParens) {
1467
+ score += 60;
1468
+ }
1469
+ if (!looksPathLike && isModuleLikeSymbol(row.symbol)) {
1470
+ score -= 160;
1471
+ }
1472
+ score -= Math.min(50, Math.max(0, row.end_line - row.start_line));
1473
+ return score;
1474
+ }
1475
+ function getCalleeRowsForSymbol(db, symbol, opts = {}) {
1476
+ const rows = db.all(
1477
+ `SELECT DISTINCT
1478
+ callee_gs.symbol AS symbol,
1479
+ callee_d.relative_path AS file,
1480
+ c.id AS chunk_id
1481
+ FROM mentions m
1482
+ JOIN chunks c ON m.chunk_id = c.id
1483
+ JOIN global_symbols callee_gs ON m.symbol_id = callee_gs.id
1484
+ JOIN defn_enclosing_ranges callee_der ON callee_gs.id = callee_der.symbol_id
1485
+ JOIN documents callee_d ON callee_der.document_id = callee_d.id
1486
+ WHERE c.document_id = ?
1487
+ AND c.start_line >= ?
1488
+ AND c.end_line <= ?
1489
+ AND m.role != 1
1490
+ AND callee_gs.id != ?
1491
+ ${db.symbolNoiseFor("callee_gs")}
1492
+ ${db.pathExclusionsFor("callee_d")}
1493
+ ORDER BY callee_d.relative_path
1494
+ ${opts.limit ? "LIMIT ?" : ""}`,
1495
+ ...calleeQueryParams(symbol, opts.limit)
1496
+ );
1497
+ return rows.filter((row) => !db.isIgnored(row.file)).map((row) => ({
1498
+ symbol: row.symbol,
1499
+ file: row.file,
1500
+ chunkId: row.chunk_id
1501
+ }));
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);
1512
+ }
1513
+ return params;
1514
+ }
1515
+ function uniquePatterns(patterns) {
1516
+ return [...new Set(patterns)];
1517
+ }
1518
+
1176
1519
  // src/queries/trace.ts
1177
1520
  function trace(db, symbolPattern) {
1521
+ const match = findFirstSymbolMatch(db, symbolPattern);
1522
+ if (!match) {
1523
+ return { definitions: [], referencedBy: [] };
1524
+ }
1178
1525
  const defRows = db.all(
1179
1526
  `SELECT d.relative_path, der.start_line, der.end_line,
1180
1527
  REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
1181
1528
  FROM global_symbols gs
1182
1529
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1183
1530
  JOIN documents d ON der.document_id = d.id
1184
- WHERE gs.symbol LIKE ?
1185
- AND ${db.localSymbolPredicate}
1186
- ${db.symbolNoise}
1531
+ WHERE gs.id = ?
1187
1532
  ORDER BY d.relative_path, der.start_line
1188
1533
  LIMIT 10`,
1189
- `%${symbolPattern}%`
1534
+ match.symbolId
1190
1535
  );
1191
1536
  const definitions = defRows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
1192
1537
  relativePath: r.relative_path,
@@ -1199,13 +1544,10 @@ function trace(db, symbolPattern) {
1199
1544
  FROM mentions m
1200
1545
  JOIN chunks c ON m.chunk_id = c.id
1201
1546
  JOIN documents d ON c.document_id = d.id
1202
- JOIN global_symbols gs ON m.symbol_id = gs.id
1203
- WHERE gs.symbol LIKE ?
1204
- AND ${db.localSymbolPredicate}
1205
- ${db.symbolNoise}
1206
- AND m.role = 0
1547
+ WHERE m.symbol_id = ?
1548
+ AND m.role != 1
1207
1549
  ORDER BY d.relative_path`,
1208
- `%${symbolPattern}%`
1550
+ match.symbolId
1209
1551
  );
1210
1552
  const referencedBy = refRows.map((r) => r.relative_path).filter((p) => !db.isIgnored(p));
1211
1553
  return { definitions, referencedBy };
@@ -1323,7 +1665,7 @@ function surface(db, modulePattern) {
1323
1665
  WHERE d2.relative_path LIKE ?
1324
1666
  AND d1.relative_path NOT LIKE ?
1325
1667
  AND ${db.localSymbolPredicate}
1326
- AND m.role = 0
1668
+ AND m.role != 1
1327
1669
  ORDER BY d1.relative_path`,
1328
1670
  `%${modulePattern}%`,
1329
1671
  `%${modulePattern}%`
@@ -1335,152 +1677,78 @@ function surface(db, modulePattern) {
1335
1677
  }));
1336
1678
  }
1337
1679
 
1338
- // src/query-support.ts
1339
- var TEST_FILE_PATTERNS = [
1340
- "%/__tests__/%",
1341
- "%.test.%",
1342
- "%.spec.%",
1343
- "%/test/%",
1344
- "%/tests/%",
1345
- "%_test.%",
1346
- "%_spec.%",
1347
- "%/test_%.%",
1348
- "%/spec_%.%"
1349
- ];
1350
- var TEST_SUPPORT_PATH_PATTERNS = [
1351
- "%/test-utils/%"
1352
- ];
1353
- function testFileMatchSql(alias, patterns = TEST_FILE_PATTERNS) {
1354
- return `(${patterns.map((pattern) => `${alias}.relative_path LIKE '${pattern}'`).join(" OR ")})`;
1680
+ // src/entry-surfaces.ts
1681
+ var liveBarrelCache = /* @__PURE__ */ new WeakMap();
1682
+ function normalizePath(path2) {
1683
+ return path2.replace(/\\/g, "/");
1355
1684
  }
1356
- function testFileExclusionSql(alias, extraPatterns = []) {
1357
- const patterns = uniquePatterns([...TEST_FILE_PATTERNS, ...extraPatterns]);
1358
- return patterns.map((pattern) => `${alias}.relative_path NOT LIKE '${pattern}'`).join("\n AND ");
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");
1359
1688
  }
1360
- function buildFileDepGraph(db, scope) {
1361
- const scopeFilter = scope ? `AND d1.relative_path LIKE '%${scope}%'` : "";
1362
- const edges = db.all(
1363
- `SELECT DISTINCT
1364
- d1.relative_path AS from_file,
1365
- d2.relative_path AS to_file
1366
- FROM mentions m
1367
- JOIN chunks c ON m.chunk_id = c.id
1368
- JOIN documents d1 ON c.document_id = d1.id
1369
- JOIN global_symbols gs ON m.symbol_id = gs.id
1370
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1371
- JOIN documents d2 ON der.document_id = d2.id
1372
- WHERE d1.id != d2.id
1373
- AND m.role = 0
1374
- ${db.pathExclusionsFor("d1", "d2")}
1375
- ${scopeFilter}`
1376
- );
1377
- const graph = /* @__PURE__ */ new Map();
1378
- for (const edge of edges) {
1379
- if (db.isIgnored(edge.from_file) || db.isIgnored(edge.to_file)) continue;
1380
- if (!graph.has(edge.from_file)) graph.set(edge.from_file, /* @__PURE__ */ new Set());
1381
- graph.get(edge.from_file).add(edge.to_file);
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;
1382
1699
  }
1383
- return graph;
1700
+ if (basename === "index.ts" || basename === "index.js") {
1701
+ return segments.length <= 2;
1702
+ }
1703
+ return normalized.endsWith("/mod.rs") || normalized.endsWith("/__init__.py");
1384
1704
  }
1385
- function findFirstSymbolMatch(db, symbolPattern) {
1386
- const fileLineMatch = symbolPattern.match(/^(.+):(\d+)-(\d+)$/);
1387
- if (fileLineMatch) {
1388
- const [, filePath, startStr, endStr] = fileLineMatch;
1389
- const row = db.get(
1390
- `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
1391
- FROM global_symbols gs
1392
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1393
- JOIN documents d ON der.document_id = d.id
1394
- WHERE d.relative_path LIKE ?
1395
- AND der.start_line <= ? AND der.end_line >= ?
1396
- ${db.pathExclusionsFor("d")}
1397
- ORDER BY (der.end_line - der.start_line) ASC
1398
- LIMIT 1`,
1399
- `%${filePath}%`,
1400
- parseInt(startStr, 10),
1401
- parseInt(endStr, 10)
1402
- );
1403
- if (row && !db.isIgnored(row.relative_path)) {
1404
- return {
1405
- symbolId: row.id,
1406
- symbol: row.symbol,
1407
- documentId: row.document_id,
1408
- startLine: row.start_line,
1409
- endLine: row.end_line,
1410
- relativePath: row.relative_path
1411
- };
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));
1713
+ }
1714
+ function getLiveBarrelPaths(db) {
1715
+ const cached = liveBarrelCache.get(db);
1716
+ if (cached) {
1717
+ return cached;
1718
+ }
1719
+ const graph = buildFileDepGraph(db);
1720
+ const queue = getIndexedPaths(db).filter(
1721
+ (path2) => isStructuralEntrySurface(path2) || isWorkerEntrySurface(path2)
1722
+ );
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;
1412
1729
  }
1413
- }
1414
- const cleaned = symbolPattern.replace(/\(\)$/, "").replace(/\(.*$/, "");
1415
- for (const useNoiseFilter of [true, false]) {
1416
- const noiseClause = useNoiseFilter ? db.symbolNoiseFor("gs") : "";
1417
- const row = db.get(
1418
- `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
1419
- FROM global_symbols gs
1420
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1421
- JOIN documents d ON der.document_id = d.id
1422
- WHERE gs.symbol LIKE ?
1423
- ${db.pathExclusionsFor("d")}
1424
- ${noiseClause}
1425
- ORDER BY (der.end_line - der.start_line) DESC
1426
- LIMIT 1`,
1427
- `%${cleaned}%`
1428
- );
1429
- if (row && !db.isIgnored(row.relative_path)) {
1430
- return {
1431
- symbolId: row.id,
1432
- symbol: row.symbol,
1433
- documentId: row.document_id,
1434
- startLine: row.start_line,
1435
- endLine: row.end_line,
1436
- relativePath: row.relative_path
1437
- };
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
+ }
1438
1738
  }
1439
1739
  }
1440
- return null;
1740
+ liveBarrelCache.set(db, liveBarrels);
1741
+ return liveBarrels;
1441
1742
  }
1442
- function getCalleeRowsForSymbol(db, symbol, opts = {}) {
1443
- const rows = db.all(
1444
- `SELECT DISTINCT
1445
- callee_gs.symbol AS symbol,
1446
- callee_d.relative_path AS file,
1447
- c.id AS chunk_id
1448
- FROM mentions m
1449
- JOIN chunks c ON m.chunk_id = c.id
1450
- JOIN global_symbols callee_gs ON m.symbol_id = callee_gs.id
1451
- JOIN defn_enclosing_ranges callee_der ON callee_gs.id = callee_der.symbol_id
1452
- JOIN documents callee_d ON callee_der.document_id = callee_d.id
1453
- WHERE c.document_id = ?
1454
- AND c.start_line >= ?
1455
- AND c.end_line <= ?
1456
- AND m.role = 0
1457
- AND callee_gs.id != ?
1458
- ${db.symbolNoiseFor("callee_gs")}
1459
- ${db.pathExclusionsFor("callee_d")}
1460
- ORDER BY callee_d.relative_path
1461
- ${opts.limit ? "LIMIT ?" : ""}`,
1462
- ...calleeQueryParams(symbol, opts.limit)
1463
- );
1464
- return rows.filter((row) => !db.isIgnored(row.file)).map((row) => ({
1465
- symbol: row.symbol,
1466
- file: row.file,
1467
- chunkId: row.chunk_id
1468
- }));
1743
+ function isLiveBarrel(db, path2) {
1744
+ return getLiveBarrelPaths(db).has(normalizePath(path2));
1469
1745
  }
1470
- function calleeQueryParams(symbol, limit) {
1471
- const params = [
1472
- symbol.documentId,
1473
- symbol.startLine,
1474
- symbol.endLine,
1475
- symbol.symbolId
1476
- ];
1477
- if (typeof limit === "number") {
1478
- params.push(limit);
1479
- }
1480
- return params;
1746
+ function isEntrySurface(db, path2) {
1747
+ return isStructuralEntrySurface(path2) || isWorkerEntrySurface(path2) || isLiveBarrel(db, path2);
1481
1748
  }
1482
- function uniquePatterns(patterns) {
1483
- return [...new Set(patterns)];
1749
+ function getInactiveBarrelPaths(db) {
1750
+ const liveBarrels = getLiveBarrelPaths(db);
1751
+ return getIndexedPaths(db).filter((path2) => isBarrelFile(path2)).filter((path2) => !liveBarrels.has(path2));
1484
1752
  }
1485
1753
 
1486
1754
  // src/queries/dead.ts
@@ -1495,6 +1763,7 @@ function dead(db, opts = {}) {
1495
1763
  const params = [minLoc];
1496
1764
  let testFileExclusions = "";
1497
1765
  let memberExclusion = "";
1766
+ let barrelExclusions = "";
1498
1767
  if (scope) {
1499
1768
  params.push(`%${scope}%`);
1500
1769
  }
@@ -1506,10 +1775,13 @@ function dead(db, opts = {}) {
1506
1775
  if (!includeMembers) {
1507
1776
  memberExclusion = `AND gs.symbol NOT LIKE '%#%'`;
1508
1777
  }
1509
- const barrelExclusions = skipBarrels ? `AND ref_d.relative_path NOT LIKE '%/index.ts'
1510
- AND ref_d.relative_path NOT LIKE '%/index.js'
1511
- AND ref_d.relative_path NOT LIKE '%/mod.rs'
1512
- AND ref_d.relative_path NOT LIKE '%/__init__.py'` : "";
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);
1783
+ }
1784
+ }
1513
1785
  const sql = `
1514
1786
  SELECT
1515
1787
  d.relative_path,
@@ -1519,7 +1791,7 @@ function dead(db, opts = {}) {
1519
1791
  gs.symbol,
1520
1792
  (SELECT COUNT(*) FROM mentions m2
1521
1793
  JOIN chunks c2 ON m2.chunk_id = c2.id
1522
- WHERE m2.symbol_id = gs.id AND m2.role = 0 AND c2.document_id = d.id
1794
+ WHERE m2.symbol_id = gs.id AND m2.role != 1 AND c2.document_id = d.id
1523
1795
  ) AS same_file_refs
1524
1796
  FROM global_symbols gs
1525
1797
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
@@ -1537,7 +1809,7 @@ function dead(db, opts = {}) {
1537
1809
  JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
1538
1810
  JOIN documents ref_d ON ref_c.document_id = ref_d.id
1539
1811
  WHERE ref_m.symbol_id = gs.id
1540
- AND ref_m.role = 0
1812
+ AND ref_m.role != 1
1541
1813
  AND ref_d.id != d.id
1542
1814
  ${barrelExclusions}
1543
1815
  )
@@ -1547,7 +1819,7 @@ function dead(db, opts = {}) {
1547
1819
  let deadCodeCount = 0;
1548
1820
  let fileInternalCount = 0;
1549
1821
  let totalLoc = 0;
1550
- const symbols2 = rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => {
1822
+ const symbols2 = rows.filter((r) => !db.isIgnored(r.relative_path)).filter((r) => !isEntrySurface(db, r.relative_path)).map((r) => {
1551
1823
  const kind = r.same_file_refs === 0 ? "dead-code" : "file-internal";
1552
1824
  if (kind === "dead-code") deadCodeCount++;
1553
1825
  else fileInternalCount++;
@@ -1588,7 +1860,7 @@ function hotspots(db, opts = {}) {
1588
1860
  JOIN global_symbols gs ON m.symbol_id = gs.id
1589
1861
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1590
1862
  JOIN documents def_d ON der.document_id = def_d.id
1591
- WHERE m.role = 0
1863
+ WHERE m.role != 1
1592
1864
  ${db.pathExclusionsFor("def_d")}
1593
1865
  ${db.symbolNoiseFor("gs")}
1594
1866
  ${scopeFilter}
@@ -1597,19 +1869,447 @@ function hotspots(db, opts = {}) {
1597
1869
  LIMIT ?`,
1598
1870
  limit
1599
1871
  );
1600
- return rows.filter((r) => !db.isIgnored(r.defined_in)).map((r) => ({
1601
- symbol: r.symbol,
1602
- shortName: shortenSymbol(r.symbol),
1603
- refCount: r.ref_count,
1604
- fileCount: r.file_count,
1605
- definedIn: r.defined_in
1606
- }));
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
+ }));
1879
+ }
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);
1901
+ if (cached) {
1902
+ return cached;
1903
+ }
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;
1913
+ }
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
+ ));
1924
+ }
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
+ });
1939
+ }
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
+ });
1951
+ }
1952
+ return statements.sort((a, b) => a.start - b.start);
1953
+ }
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
+ }];
1966
+ }
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
+ };
1979
+ }
1980
+ if (binding.kind === "side-effect") {
1981
+ return { ...binding, used: true, usedMembers: [] };
1982
+ }
1983
+ return {
1984
+ ...binding,
1985
+ used: binding.localName ? hasIdentifierUsage(body, binding.localName) : false,
1986
+ usedMembers: []
1987
+ };
1988
+ });
1989
+ }
1990
+ function parsePythonImports(db, importerPath, source) {
1991
+ return collectPythonImportStatements(source).flatMap(
1992
+ (statement) => parsePythonImportStatement(db, importerPath, statement, source)
1993
+ );
1994
+ }
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;
2006
+ }
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;
2018
+ }
2019
+ const parsed = parsePythonStatementHeader(statement);
2020
+ if (parsed) {
2021
+ statements.push({
2022
+ ...parsed,
2023
+ start: lineStart,
2024
+ end: statementEnd
2025
+ });
2026
+ }
2027
+ }
2028
+ return statements;
2029
+ }
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();
2046
+ }
2047
+ return {
2048
+ kind: "from",
2049
+ module: fromMatch[1],
2050
+ clause
2051
+ };
2052
+ }
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
+ });
2074
+ }
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
+ });
2101
+ }
2102
+ return results;
2103
+ }
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;
2115
+ }
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
+ });
2131
+ }
2132
+ if (trimmed.startsWith("* as ")) {
2133
+ return [{
2134
+ importedName: "*",
2135
+ localName: trimmed.slice(5).trim(),
2136
+ kind: "namespace"
2137
+ }];
2138
+ }
2139
+ return [{
2140
+ importedName: "default",
2141
+ localName: trimmed,
2142
+ kind: "default"
2143
+ }];
2144
+ }
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
+ }
2154
+ }
2155
+ return [clause.trim(), null];
2156
+ }
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
+ }
2169
+ }
2170
+ parts.push(input.slice(start));
2171
+ return parts;
2172
+ }
2173
+ function buildUsageBody(source, start, end) {
2174
+ const masked = `${source.slice(0, start)}${" ".repeat(end - start)}${source.slice(end)}`;
2175
+ return stripCommentsAndStrings(masked);
2176
+ }
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, " ");
2179
+ }
2180
+ function hasIdentifierUsage(body, identifier) {
2181
+ return new RegExp(`\\b${escapeRegex(identifier)}\\b`, "m").test(body);
2182
+ }
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
+ }
2191
+ }
2192
+ return [...members2];
2193
+ }
2194
+ function resolveImportPath(db, importerPath, specifier) {
2195
+ if (isPythonSourcePath(importerPath)) {
2196
+ return resolvePythonImportPath(db, importerPath, specifier);
2197
+ }
2198
+ return resolveJavaScriptImportPath(db, importerPath, specifier);
2199
+ }
2200
+ function resolveJavaScriptImportPath(db, importerPath, specifier) {
2201
+ if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
2202
+ return null;
2203
+ }
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;
2211
+ }
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);
2226
+ }
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;
2235
+ }
2236
+ }
2237
+ return null;
2238
+ }
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
+ ];
2250
+ }
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}`));
2263
+ }
2264
+ }
2265
+ return [...candidates];
2266
+ }
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))
2279
+ );
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, "\\$&");
1607
2307
  }
1608
2308
 
1609
2309
  // src/queries/imports.ts
1610
2310
  function imports(db, filePattern) {
1611
2311
  const rows = db.all(
1612
- `SELECT DISTINCT gs.symbol, def_d.relative_path AS from_file
2312
+ `SELECT DISTINCT gs.symbol, def_d.relative_path AS from_file, imp_d.relative_path AS importer
1613
2313
  FROM mentions m
1614
2314
  JOIN chunks c ON m.chunk_id = c.id
1615
2315
  JOIN documents imp_d ON c.document_id = imp_d.id
@@ -1621,11 +2321,21 @@ function imports(db, filePattern) {
1621
2321
  ORDER BY def_d.relative_path, gs.symbol`,
1622
2322
  `%${filePattern}%`
1623
2323
  );
1624
- return rows.map((r) => ({
2324
+ const indexedResults = rows.filter((row) => !db.isIgnored(row.importer)).map((r) => ({
1625
2325
  symbol: r.symbol,
1626
2326
  shortName: shortenSymbol(r.symbol),
1627
2327
  fromFile: r.from_file ?? "(external)"
1628
2328
  }));
2329
+ if (indexedResults.length > 0) {
2330
+ return indexedResults;
2331
+ }
2332
+ const importer = findIndexedFile(db, filePattern);
2333
+ if (!importer) return [];
2334
+ return getSourceImports(db, importer).map((entry) => ({
2335
+ symbol: renderImportSymbol(entry.importedName, entry.localName, entry.kind),
2336
+ shortName: renderImportSymbol(entry.importedName, entry.localName, entry.kind),
2337
+ fromFile: entry.sourcePath ?? "(external)"
2338
+ }));
1629
2339
  }
1630
2340
  function importedBy(db, symbolPattern) {
1631
2341
  const rows = db.all(
@@ -1639,15 +2349,55 @@ function importedBy(db, symbolPattern) {
1639
2349
  ORDER BY d.relative_path`,
1640
2350
  `%${symbolPattern}%`
1641
2351
  );
1642
- return rows.filter((r) => !db.isIgnored(r.importer)).map((r) => ({
2352
+ const indexedResults = rows.filter((r) => !db.isIgnored(r.importer)).map((r) => ({
1643
2353
  symbol: r.symbol,
1644
2354
  shortName: shortenSymbol(r.symbol),
1645
2355
  fromFile: r.importer
1646
2356
  }));
2357
+ if (indexedResults.length > 0) {
2358
+ return indexedResults;
2359
+ }
2360
+ const target = findFirstSymbolMatch(db, symbolPattern);
2361
+ const targetFile = target?.relativePath ?? null;
2362
+ const targetLeaf = target ? leafName(target.symbol) : symbolPattern.replace(/\(\)$/, "");
2363
+ const targetIsModule = target ? isModuleLikeSymbol(target.symbol) : false;
2364
+ const files2 = db.all(
2365
+ `SELECT relative_path
2366
+ FROM documents
2367
+ WHERE 1 = 1
2368
+ ${db.pathExclusionsFor("documents")}
2369
+ ORDER BY relative_path`
2370
+ );
2371
+ const importers = /* @__PURE__ */ new Set();
2372
+ for (const row of files2) {
2373
+ if (db.isIgnored(row.relative_path)) continue;
2374
+ for (const entry of getSourceImports(db, row.relative_path)) {
2375
+ if (!entry.sourcePath) continue;
2376
+ if (targetFile && normalizePath3(entry.sourcePath) !== normalizePath3(targetFile)) {
2377
+ continue;
2378
+ }
2379
+ if (targetIsModule) {
2380
+ importers.add(row.relative_path);
2381
+ continue;
2382
+ }
2383
+ if (entry.kind === "named" && entry.importedName === targetLeaf) {
2384
+ importers.add(row.relative_path);
2385
+ continue;
2386
+ }
2387
+ if (entry.kind === "namespace" && entry.usedMembers.includes(targetLeaf)) {
2388
+ importers.add(row.relative_path);
2389
+ }
2390
+ }
2391
+ }
2392
+ return [...importers].sort().map((importer) => ({
2393
+ symbol: target?.symbol ?? targetLeaf,
2394
+ shortName: target ? shortenSymbol(target.symbol) : targetLeaf,
2395
+ fromFile: importer
2396
+ }));
1647
2397
  }
1648
2398
  function unusedImports(db, filePattern) {
1649
2399
  const rows = db.all(
1650
- `SELECT gs.symbol, d.relative_path AS imported_in
2400
+ `SELECT gs.symbol, d.relative_path AS imported_in, d.relative_path AS importer
1651
2401
  FROM mentions m
1652
2402
  JOIN chunks c ON m.chunk_id = c.id
1653
2403
  JOIN documents d ON c.document_id = d.id
@@ -1659,17 +2409,59 @@ function unusedImports(db, filePattern) {
1659
2409
  FROM mentions ref_m
1660
2410
  JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
1661
2411
  WHERE ref_m.symbol_id = gs.id
1662
- AND ref_m.role = 0
2412
+ AND ref_m.role != 1
1663
2413
  AND ref_c.document_id = d.id
1664
2414
  )
1665
2415
  ORDER BY d.relative_path, gs.symbol`,
1666
2416
  `%${filePattern}%`
1667
2417
  );
1668
- return rows.map((r) => ({
2418
+ const indexedResults = rows.filter((row) => !db.isIgnored(row.importer)).map((r) => ({
1669
2419
  symbol: r.symbol,
1670
2420
  shortName: shortenSymbol(r.symbol),
1671
2421
  importedIn: r.imported_in
1672
2422
  }));
2423
+ if (indexedResults.length > 0) {
2424
+ return indexedResults;
2425
+ }
2426
+ const importer = findIndexedFile(db, filePattern);
2427
+ if (!importer) return [];
2428
+ return getSourceImports(db, importer).filter((entry) => entry.kind !== "side-effect" && !entry.used).map((entry) => ({
2429
+ symbol: renderImportSymbol(entry.importedName, entry.localName, entry.kind),
2430
+ shortName: renderImportSymbol(entry.importedName, entry.localName, entry.kind),
2431
+ importedIn: importer
2432
+ }));
2433
+ }
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
+ function renderImportSymbol(importedName, localName, kind) {
2449
+ if (kind === "namespace" && importedName === "*" && localName) {
2450
+ return `* as ${localName}`;
2451
+ }
2452
+ if (kind === "default" && localName) {
2453
+ return `default as ${localName}`;
2454
+ }
2455
+ if (kind === "side-effect") {
2456
+ return "(side effect import)";
2457
+ }
2458
+ if (localName && localName !== importedName) {
2459
+ return `${importedName} as ${localName}`;
2460
+ }
2461
+ return importedName;
2462
+ }
2463
+ function normalizePath3(path2) {
2464
+ return path2.replace(/\\/g, "/");
1673
2465
  }
1674
2466
 
1675
2467
  // src/queries/outline.ts
@@ -1709,38 +2501,26 @@ function outline(db, filePattern) {
1709
2501
 
1710
2502
  // src/queries/members.ts
1711
2503
  function members(db, symbolPattern) {
1712
- const parents = db.all(
1713
- `SELECT symbol FROM global_symbols WHERE symbol LIKE ?`,
1714
- `%${symbolPattern}%`
1715
- );
1716
- if (parents.length === 0) return [];
1717
- const placeholders = parents.map(() => "?").join(",");
1718
- const parentSymbols = parents.map((p) => p.symbol);
2504
+ const parent = findFirstSymbolMatch(db, symbolPattern);
2505
+ if (!parent) return [];
1719
2506
  const rows = db.all(
1720
2507
  `SELECT gs.symbol, der.start_line, der.end_line
1721
- FROM global_symbols gs
1722
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1723
- WHERE gs.enclosing_symbol IN (${placeholders})
1724
- ${db.symbolNoise}
1725
- ORDER BY der.start_line`,
1726
- ...parentSymbols
2508
+ FROM global_symbols gs
2509
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2510
+ WHERE der.document_id = ?
2511
+ AND gs.symbol != ?
2512
+ ${db.symbolNoiseFor("gs")}
2513
+ ORDER BY der.start_line`,
2514
+ parent.documentId,
2515
+ parent.symbol
1727
2516
  );
1728
- return rows.map((r) => {
1729
- const parsed = parseSymbol(r.symbol);
1730
- let kind = "unknown";
1731
- if (!("kind" in parsed)) {
1732
- const sym = parsed;
1733
- const last = sym.descriptors[sym.descriptors.length - 1];
1734
- if (last) kind = last.suffix;
1735
- }
1736
- return {
1737
- symbol: r.symbol,
1738
- shortName: shortenSymbol(r.symbol),
1739
- startLine: r.start_line,
1740
- endLine: r.end_line,
1741
- kind
1742
- };
1743
- });
2517
+ return rows.filter((row) => isDirectChildSymbol(parent.symbol, row.symbol)).map((row) => ({
2518
+ symbol: row.symbol,
2519
+ shortName: shortenSymbol(row.symbol),
2520
+ startLine: row.start_line,
2521
+ endLine: row.end_line,
2522
+ kind: leafSuffix(row.symbol) ?? "unknown"
2523
+ }));
1744
2524
  }
1745
2525
 
1746
2526
  // src/queries/fan.ts
@@ -1751,7 +2531,7 @@ function fanIn(db, symbolPattern) {
1751
2531
  JOIN chunks c ON m.chunk_id = c.id
1752
2532
  JOIN global_symbols gs ON m.symbol_id = gs.id
1753
2533
  WHERE gs.symbol LIKE ?
1754
- AND m.role = 0
2534
+ AND m.role != 1
1755
2535
  GROUP BY gs.id
1756
2536
  ORDER BY file_count DESC`,
1757
2537
  `%${symbolPattern}%`
@@ -1771,7 +2551,7 @@ function fanOut(db, filePattern) {
1771
2551
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1772
2552
  JOIN documents def_d ON der.document_id = def_d.id
1773
2553
  WHERE d.relative_path LIKE ?
1774
- AND m.role = 0
2554
+ AND m.role != 1
1775
2555
  AND def_d.id != d.id
1776
2556
  GROUP BY d.id
1777
2557
  ORDER BY symbol_count DESC`,
@@ -1792,7 +2572,7 @@ function topFanIn(db, opts = {}) {
1792
2572
  JOIN global_symbols gs ON m.symbol_id = gs.id
1793
2573
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1794
2574
  JOIN documents def_d ON der.document_id = def_d.id
1795
- WHERE m.role = 0
2575
+ WHERE m.role != 1
1796
2576
  ${db.pathExclusionsFor("def_d")}
1797
2577
  ${db.symbolNoiseFor("gs")}
1798
2578
  ${scopeFilter}
@@ -1818,7 +2598,7 @@ function topFanOut(db, opts = {}) {
1818
2598
  JOIN global_symbols gs ON m.symbol_id = gs.id
1819
2599
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1820
2600
  JOIN documents def_d ON der.document_id = def_d.id
1821
- WHERE m.role = 0
2601
+ WHERE m.role != 1
1822
2602
  AND def_d.id != d.id
1823
2603
  ${db.pathExclusionsFor("d")}
1824
2604
  ${db.symbolNoiseFor("gs")}
@@ -1850,7 +2630,7 @@ function coupling(db, file1, file2) {
1850
2630
  SELECT 1 FROM mentions m
1851
2631
  JOIN chunks c ON m.chunk_id = c.id
1852
2632
  JOIN documents d ON c.document_id = d.id
1853
- WHERE m.symbol_id = gs.id AND m.role = 0 AND d.relative_path LIKE ?
2633
+ WHERE m.symbol_id = gs.id AND m.role != 1 AND d.relative_path LIKE ?
1854
2634
  )
1855
2635
  ) OR (
1856
2636
  -- Defined in file2, referenced in file1
@@ -1863,7 +2643,7 @@ function coupling(db, file1, file2) {
1863
2643
  SELECT 1 FROM mentions m
1864
2644
  JOIN chunks c ON m.chunk_id = c.id
1865
2645
  JOIN documents d ON c.document_id = d.id
1866
- WHERE m.symbol_id = gs.id AND m.role = 0 AND d.relative_path LIKE ?
2646
+ WHERE m.symbol_id = gs.id AND m.role != 1 AND d.relative_path LIKE ?
1867
2647
  )
1868
2648
  )`,
1869
2649
  `%${file1}%`,
@@ -1891,7 +2671,7 @@ function topCoupling(db, opts = {}) {
1891
2671
  JOIN global_symbols gs ON m.symbol_id = gs.id
1892
2672
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
1893
2673
  JOIN documents def_d ON der.document_id = def_d.id
1894
- WHERE m.role = 0
2674
+ WHERE m.role != 1
1895
2675
  AND def_d.id != ref_d.id
1896
2676
  ${db.pathExclusionsFor("def_d", "ref_d")}
1897
2677
  ${scopeFilter}
@@ -1972,7 +2752,7 @@ function bottlenecks(db, opts = {}) {
1972
2752
  (SELECT COUNT(DISTINCT ref_c.document_id)
1973
2753
  FROM mentions ref_m
1974
2754
  JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
1975
- WHERE ref_m.symbol_id = gs.id AND ref_m.role = 0
2755
+ WHERE ref_m.symbol_id = gs.id AND ref_m.role != 1
1976
2756
  ) AS fan_in,
1977
2757
  (SELECT COUNT(DISTINCT ref_gs.id)
1978
2758
  FROM mentions ref_m
@@ -1980,7 +2760,7 @@ function bottlenecks(db, opts = {}) {
1980
2760
  JOIN global_symbols ref_gs ON ref_m.symbol_id = ref_gs.id
1981
2761
  JOIN defn_enclosing_ranges ref_der ON ref_gs.id = ref_der.symbol_id
1982
2762
  WHERE ref_c.document_id = def_d.id
1983
- AND ref_m.role = 0
2763
+ AND ref_m.role != 1
1984
2764
  AND ref_der.document_id != def_d.id
1985
2765
  ) AS fan_out
1986
2766
  FROM global_symbols gs
@@ -2032,18 +2812,18 @@ function isolated(db, opts = {}) {
2032
2812
  AND NOT EXISTS (
2033
2813
  SELECT 1 FROM mentions m
2034
2814
  JOIN chunks c ON m.chunk_id = c.id
2035
- WHERE m.symbol_id = gs.id AND m.role = 0 AND c.document_id != d.id
2815
+ WHERE m.symbol_id = gs.id AND m.role != 1 AND c.document_id != d.id
2036
2816
  )
2037
2817
  -- No same-file references either
2038
2818
  AND NOT EXISTS (
2039
2819
  SELECT 1 FROM mentions m
2040
2820
  JOIN chunks c ON m.chunk_id = c.id
2041
- WHERE m.symbol_id = gs.id AND m.role = 0 AND c.document_id = d.id
2821
+ WHERE m.symbol_id = gs.id AND m.role != 1 AND c.document_id = d.id
2042
2822
  )
2043
2823
  ORDER BY loc DESC, d.relative_path`,
2044
2824
  minLoc
2045
2825
  );
2046
- return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
2826
+ return rows.filter((r) => !db.isIgnored(r.relative_path)).filter((r) => !isEntrySurface(db, r.relative_path)).map((r) => ({
2047
2827
  symbol: r.symbol,
2048
2828
  shortName: shortenSymbol(r.symbol),
2049
2829
  relativePath: r.relative_path,
@@ -2216,80 +2996,6 @@ function kindCounts(db, opts = {}) {
2216
2996
  }));
2217
2997
  }
2218
2998
 
2219
- // src/queries/test-coverage.ts
2220
- function testCoverage(db, symbolPattern) {
2221
- const syms = db.all(
2222
- `SELECT gs.id, gs.symbol, d.relative_path
2223
- FROM global_symbols gs
2224
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2225
- JOIN documents d ON der.document_id = d.id
2226
- WHERE gs.symbol LIKE ?
2227
- ${db.pathExclusionsFor("d")}
2228
- ${db.symbolNoiseFor("gs")}
2229
- ORDER BY d.relative_path`,
2230
- `%${symbolPattern}%`
2231
- );
2232
- const testPatternSql = testFileMatchSql("ref_d", TEST_FILE_PATTERNS);
2233
- return syms.filter((s) => !db.isIgnored(s.relative_path)).map((s) => {
2234
- const testFiles = db.all(
2235
- `SELECT DISTINCT ref_d.relative_path
2236
- FROM mentions m
2237
- JOIN chunks c ON m.chunk_id = c.id
2238
- JOIN documents ref_d ON c.document_id = ref_d.id
2239
- WHERE m.symbol_id = ?
2240
- AND m.role = 0
2241
- AND (${testPatternSql})
2242
- ORDER BY ref_d.relative_path`,
2243
- s.id
2244
- ).map((r) => r.relative_path);
2245
- return {
2246
- symbol: s.symbol,
2247
- shortName: shortenSymbol(s.symbol),
2248
- definedIn: s.relative_path,
2249
- testFiles,
2250
- covered: testFiles.length > 0
2251
- };
2252
- });
2253
- }
2254
- function testCoverageSummary(db, opts = {}) {
2255
- const { scope, minLoc = 3 } = opts;
2256
- const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
2257
- const testPatternSql = testFileExclusionSql("d");
2258
- const symbols2 = db.all(
2259
- `SELECT gs.id
2260
- FROM global_symbols gs
2261
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
2262
- JOIN documents d ON der.document_id = d.id
2263
- WHERE 1 = 1
2264
- ${db.pathExclusionsFor("d")}
2265
- AND ${testPatternSql}
2266
- ${db.symbolNoiseFor("gs")}
2267
- AND gs.symbol NOT LIKE '%#%'
2268
- AND (der.end_line - der.start_line + 1) >= ?
2269
- ${scopeFilter}`,
2270
- minLoc
2271
- );
2272
- const testRefSql = testFileMatchSql("ref_d", TEST_FILE_PATTERNS);
2273
- let covered = 0;
2274
- for (const s of symbols2) {
2275
- const hasTest = db.get(
2276
- `SELECT COUNT(*) AS c FROM mentions m
2277
- JOIN chunks c ON m.chunk_id = c.id
2278
- JOIN documents ref_d ON c.document_id = ref_d.id
2279
- WHERE m.symbol_id = ? AND m.role = 0 AND (${testRefSql})`,
2280
- s.id
2281
- );
2282
- if (hasTest && hasTest.c > 0) covered++;
2283
- }
2284
- const total = symbols2.length;
2285
- return {
2286
- total,
2287
- covered,
2288
- uncovered: total - covered,
2289
- percent: total > 0 ? Math.round(covered / total * 100) : 0
2290
- };
2291
- }
2292
-
2293
2999
  // src/queries/doc-coverage.ts
2294
3000
  function docCoverage(db, opts = {}) {
2295
3001
  const { scope, minLoc = 3, limit = 50 } = opts;
@@ -2393,10 +3099,12 @@ function deepChains(db, opts = {}) {
2393
3099
 
2394
3100
  // src/queries/hierarchy.ts
2395
3101
  function hierarchy(db, symbolPattern) {
3102
+ const match = findFirstSymbolMatch(db, symbolPattern);
3103
+ if (!match) return [];
2396
3104
  const sym = db.get(
2397
3105
  `SELECT symbol, enclosing_symbol FROM global_symbols
2398
- WHERE symbol LIKE ? LIMIT 1`,
2399
- `%${symbolPattern}%`
3106
+ WHERE id = ? LIMIT 1`,
3107
+ match.symbolId
2400
3108
  );
2401
3109
  if (!sym) return [];
2402
3110
  const chain = [
@@ -2420,7 +3128,30 @@ function hierarchy(db, symbolPattern) {
2420
3128
  current = parent.enclosing_symbol;
2421
3129
  depth++;
2422
3130
  }
2423
- return chain;
3131
+ if (chain.length > 1) {
3132
+ return chain;
3133
+ }
3134
+ const parsed = parseSymbol(sym.symbol);
3135
+ if ("kind" in parsed) {
3136
+ return chain;
3137
+ }
3138
+ const descriptors = parsed.descriptors;
3139
+ if (descriptors.length <= 1) {
3140
+ return chain;
3141
+ }
3142
+ const syntheticChain = [chain[0]];
3143
+ for (let i = descriptors.length - 2, syntheticDepth = 1; i >= 0; i--, syntheticDepth++) {
3144
+ const partial = descriptors.slice(0, i + 1);
3145
+ const shortName = partial.map(
3146
+ (descriptor) => descriptor.suffix === "method" ? `${descriptor.name}()` : descriptor.name.replace(/\.(ts|tsx|js|jsx|mjs|cjs|py|pyi|rs|java|scala|kt|kts|rb|go|cs|vb|dart|php|c|cc|cpp|cxx|h|hpp)$/, "")
3147
+ ).join(":");
3148
+ syntheticChain.push({
3149
+ symbol: shortName,
3150
+ shortName,
3151
+ depth: syntheticDepth
3152
+ });
3153
+ }
3154
+ return syntheticChain;
2424
3155
  }
2425
3156
 
2426
3157
  // src/queries/call-graph.ts
@@ -2440,7 +3171,7 @@ function callGraph(db, symbolPattern) {
2440
3171
  JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id
2441
3172
  JOIN documents caller_d ON caller_der.document_id = caller_d.id
2442
3173
  WHERE m.symbol_id = ?
2443
- AND m.role = 0
3174
+ AND m.role != 1
2444
3175
  AND caller_gs.id != ?
2445
3176
  ${db.symbolNoiseFor("caller_gs")}
2446
3177
  ${db.pathExclusionsFor("caller_d")}
@@ -2471,6 +3202,7 @@ function similar(db, symbolPattern, opts = {}) {
2471
3202
  const { minSimilarity = 0.4, limit = 20 } = opts;
2472
3203
  const target = findCallees(db, symbolPattern);
2473
3204
  if (!target || target.callees.size === 0) return [];
3205
+ if (!isFunctionLikeSymbol(target.symbol)) return [];
2474
3206
  const candidates = getAllCalleeFingerprints(db, {
2475
3207
  minCallees: 3,
2476
3208
  excludeSymbol: target.symbol
@@ -2620,6 +3352,7 @@ function getAllCalleeFingerprints(db, opts) {
2620
3352
  const fingerprints = [];
2621
3353
  for (const sym of symbols2) {
2622
3354
  if (db.isIgnored(sym.relative_path)) continue;
3355
+ if (!isFunctionLikeSymbol(sym.symbol)) continue;
2623
3356
  const calleeRows = getCalleeRowsForSymbol(db, {
2624
3357
  documentId: sym.document_id,
2625
3358
  startLine: sym.start_line,
@@ -3068,7 +3801,7 @@ function affected(db, symbolPattern, opts = {}) {
3068
3801
  JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
3069
3802
  JOIN documents enc_d ON enc_der.document_id = enc_d.id
3070
3803
  WHERE m.symbol_id IN (${placeholders})
3071
- AND m.role = 0
3804
+ AND m.role != 1
3072
3805
  AND enc_gs.id NOT IN (${placeholders})
3073
3806
  ${db.symbolNoiseFor("enc_gs")}
3074
3807
  ${db.pathExclusionsFor("enc_d")}
@@ -3113,39 +3846,24 @@ function changeSurface(db, filePattern) {
3113
3846
  ORDER BY der.start_line`,
3114
3847
  doc.id
3115
3848
  );
3116
- const testPatternSql = testFileMatchSql("ref_d", TEST_FILE_PATTERNS);
3117
3849
  const symbols2 = [];
3118
3850
  let totalExternalConsumers = 0;
3119
- let coveredCount = 0;
3120
3851
  for (const sym of syms) {
3121
3852
  const consumerRow = db.get(
3122
3853
  `SELECT COUNT(DISTINCT c.document_id) AS consumer_count
3123
3854
  FROM mentions m
3124
3855
  JOIN chunks c ON m.chunk_id = c.id
3125
3856
  WHERE m.symbol_id = ?
3126
- AND m.role = 0
3857
+ AND m.role != 1
3127
3858
  AND c.document_id != ?`,
3128
3859
  sym.symbol_id,
3129
3860
  doc.id
3130
3861
  );
3131
3862
  const externalConsumers = consumerRow?.consumer_count ?? 0;
3132
- const testFiles = db.all(
3133
- `SELECT DISTINCT ref_d.relative_path
3134
- FROM mentions m
3135
- JOIN chunks c ON m.chunk_id = c.id
3136
- JOIN documents ref_d ON c.document_id = ref_d.id
3137
- WHERE m.symbol_id = ?
3138
- AND m.role = 0
3139
- AND (${testPatternSql})
3140
- ORDER BY ref_d.relative_path`,
3141
- sym.symbol_id
3142
- ).map((r) => r.relative_path);
3143
- const hasTests = testFiles.length > 0;
3144
- if (hasTests) coveredCount++;
3145
3863
  let riskLevel;
3146
- if (externalConsumers > 10 && !hasTests) {
3864
+ if (externalConsumers > 10) {
3147
3865
  riskLevel = "high";
3148
- } else if (externalConsumers > 5 || !hasTests) {
3866
+ } else if (externalConsumers > 0) {
3149
3867
  riskLevel = "medium";
3150
3868
  } else {
3151
3869
  riskLevel = "low";
@@ -3157,42 +3875,33 @@ function changeSurface(db, filePattern) {
3157
3875
  startLine: sym.start_line,
3158
3876
  endLine: sym.end_line,
3159
3877
  externalConsumers,
3160
- testFiles,
3161
3878
  riskLevel
3162
3879
  });
3163
3880
  }
3164
- const testCoveragePercent = symbols2.length > 0 ? Math.round(coveredCount / symbols2.length * 100) : 0;
3165
3881
  return {
3166
3882
  file: doc.relative_path,
3167
3883
  symbols: symbols2,
3168
- totalExternalConsumers,
3169
- testCoveragePercent
3884
+ totalExternalConsumers
3170
3885
  };
3171
3886
  }
3172
3887
 
3173
3888
  // src/queries/diff-impact.ts
3174
- import { execFileSync as execFileSync3 } from "child_process";
3889
+ import { execFileSync as execFileSync4 } from "child_process";
3175
3890
  function diffImpact(db, opts = {}) {
3176
3891
  const { base = "HEAD" } = opts;
3177
3892
  let changedFileLines;
3178
3893
  try {
3179
- const stdout = execFileSync3("git", ["diff", "--name-only", base], {
3180
- encoding: "utf-8",
3181
- cwd: db.config.projectRoot,
3182
- timeout: 1e4
3183
- });
3184
- changedFileLines = stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
3894
+ changedFileLines = getChangedFiles(db.config.projectRoot, base);
3185
3895
  } catch {
3186
3896
  return {
3187
3897
  changedFiles: [],
3188
3898
  changedSymbols: [],
3189
3899
  affectedConsumers: [],
3190
- uncoveredSymbols: [],
3191
3900
  summary: {
3192
3901
  totalChangedFiles: 0,
3193
3902
  totalChangedSymbols: 0,
3194
3903
  totalAffectedFiles: 0,
3195
- testCoveragePercent: 0
3904
+ note: "Unable to compute git diff."
3196
3905
  }
3197
3906
  };
3198
3907
  }
@@ -3201,12 +3910,11 @@ function diffImpact(db, opts = {}) {
3201
3910
  changedFiles: [],
3202
3911
  changedSymbols: [],
3203
3912
  affectedConsumers: [],
3204
- uncoveredSymbols: [],
3205
3913
  summary: {
3206
3914
  totalChangedFiles: 0,
3207
3915
  totalChangedSymbols: 0,
3208
3916
  totalAffectedFiles: 0,
3209
- testCoveragePercent: 0
3917
+ note: "No changed files found."
3210
3918
  }
3211
3919
  };
3212
3920
  }
@@ -3229,12 +3937,11 @@ function diffImpact(db, opts = {}) {
3229
3937
  changedFiles: changedFileLines,
3230
3938
  changedSymbols: [],
3231
3939
  affectedConsumers: [],
3232
- uncoveredSymbols: [],
3233
3940
  summary: {
3234
3941
  totalChangedFiles: changedFileLines.length,
3235
3942
  totalChangedSymbols: 0,
3236
3943
  totalAffectedFiles: 0,
3237
- testCoveragePercent: 0
3944
+ note: "Changed files are not present in the current SCIP index."
3238
3945
  }
3239
3946
  };
3240
3947
  }
@@ -3249,18 +3956,15 @@ function diffImpact(db, opts = {}) {
3249
3956
  ORDER BY d.relative_path`,
3250
3957
  ...changedDocIds
3251
3958
  );
3252
- const testPatternSql = testFileMatchSql("ref_d", TEST_FILE_PATTERNS);
3253
3959
  const changedSymbols = [];
3254
3960
  const consumerMap = /* @__PURE__ */ new Map();
3255
- const uncoveredSymbols = [];
3256
- let coveredCount = 0;
3257
3961
  for (const sym of syms) {
3258
3962
  const fanInRow = db.get(
3259
3963
  `SELECT COUNT(DISTINCT c.document_id) AS fan_in
3260
3964
  FROM mentions m
3261
3965
  JOIN chunks c ON m.chunk_id = c.id
3262
3966
  WHERE m.symbol_id = ?
3263
- AND m.role = 0`,
3967
+ AND m.role != 1`,
3264
3968
  sym.symbol_id
3265
3969
  );
3266
3970
  const fanIn2 = fanInRow?.fan_in ?? 0;
@@ -3277,7 +3981,7 @@ function diffImpact(db, opts = {}) {
3277
3981
  JOIN chunks c ON m.chunk_id = c.id
3278
3982
  JOIN documents ref_d ON c.document_id = ref_d.id
3279
3983
  WHERE m.symbol_id = ?
3280
- AND m.role = 0
3984
+ AND m.role != 1
3281
3985
  AND ref_d.relative_path NOT IN (${changedFiles.map(() => "?").join(",")})
3282
3986
  ${db.pathExclusionsFor("ref_d")}`,
3283
3987
  sym.symbol_id,
@@ -3290,42 +3994,39 @@ function diffImpact(db, opts = {}) {
3290
3994
  }
3291
3995
  consumerMap.get(consumer.relative_path).add(shortName);
3292
3996
  }
3293
- const hasTest = db.get(
3294
- `SELECT COUNT(*) AS c
3295
- FROM mentions m
3296
- JOIN chunks c ON m.chunk_id = c.id
3297
- JOIN documents ref_d ON c.document_id = ref_d.id
3298
- WHERE m.symbol_id = ?
3299
- AND m.role = 0
3300
- AND (${testPatternSql})`,
3301
- sym.symbol_id
3302
- );
3303
- if (hasTest && hasTest.c > 0) {
3304
- coveredCount++;
3305
- } else {
3306
- uncoveredSymbols.push({
3307
- symbol: sym.symbol,
3308
- shortName,
3309
- file: sym.relative_path
3310
- });
3311
- }
3312
3997
  }
3313
3998
  const affectedConsumers = [...consumerMap.entries()].map(([file, symbols2]) => ({ file, consumedSymbols: symbols2.size })).sort((a, b) => b.consumedSymbols - a.consumedSymbols);
3314
- const totalSymbols = changedSymbols.length;
3315
- const testCoveragePercent = totalSymbols > 0 ? Math.round(coveredCount / totalSymbols * 100) : 0;
3316
3999
  return {
3317
4000
  changedFiles,
3318
4001
  changedSymbols,
3319
4002
  affectedConsumers,
3320
- uncoveredSymbols,
3321
4003
  summary: {
3322
4004
  totalChangedFiles: changedFiles.length,
3323
- totalChangedSymbols: totalSymbols,
3324
- totalAffectedFiles: affectedConsumers.length,
3325
- testCoveragePercent
4005
+ totalChangedSymbols: changedSymbols.length,
4006
+ totalAffectedFiles: affectedConsumers.length
3326
4007
  }
3327
4008
  };
3328
4009
  }
4010
+ function getChangedFiles(projectRoot, base) {
4011
+ const diff = execFileSync4("git", ["diff", "--name-only", base], {
4012
+ encoding: "utf-8",
4013
+ cwd: projectRoot,
4014
+ timeout: 1e4
4015
+ });
4016
+ const staged = execFileSync4("git", ["diff", "--name-only", "--cached", base], {
4017
+ encoding: "utf-8",
4018
+ cwd: projectRoot,
4019
+ timeout: 1e4
4020
+ });
4021
+ const untracked = execFileSync4("git", ["ls-files", "--others", "--exclude-standard"], {
4022
+ encoding: "utf-8",
4023
+ cwd: projectRoot,
4024
+ timeout: 1e4
4025
+ });
4026
+ return [...new Set(
4027
+ [diff, staged, untracked].flatMap((chunk) => chunk.split("\n")).map((line) => line.trim()).filter((line) => line.length > 0)
4028
+ )];
4029
+ }
3329
4030
 
3330
4031
  // src/queries/drift.ts
3331
4032
  import path from "path";
@@ -3335,9 +4036,10 @@ function drift(db, opts) {
3335
4036
  const symbolRefs = buildSymbolRefGraph(db, scope);
3336
4037
  const results = [];
3337
4038
  for (const [file, deps2] of depGraph) {
3338
- if (isStructuralRole(path.basename(file))) continue;
4039
+ if (shouldSkipDriftFile(file)) continue;
3339
4040
  const referencedFiles = symbolRefs.get(file) ?? /* @__PURE__ */ new Set();
3340
4041
  for (const dep of deps2) {
4042
+ if (shouldSkipDriftFile(dep)) continue;
3341
4043
  if (!referencedFiles.has(dep)) {
3342
4044
  if (isLikelyTypeOnlyDep(dep)) continue;
3343
4045
  results.push({
@@ -3351,10 +4053,11 @@ function drift(db, opts) {
3351
4053
  }
3352
4054
  const layerRules = inferLayerRules(depGraph);
3353
4055
  for (const [file, deps2] of depGraph) {
3354
- if (isStructuralRole(path.basename(file))) continue;
3355
- const fileLayer = getTopDir(file);
4056
+ if (shouldSkipDriftFile(file)) continue;
4057
+ const fileLayer = getArchitecturalLayer(file);
3356
4058
  for (const dep of deps2) {
3357
- const depLayer = getTopDir(dep);
4059
+ if (shouldSkipDriftFile(dep)) continue;
4060
+ const depLayer = getArchitecturalLayer(dep);
3358
4061
  if (fileLayer === depLayer) continue;
3359
4062
  const violation = layerRules.get(`${fileLayer}->${depLayer}`);
3360
4063
  if (violation === "violation") {
@@ -3378,14 +4081,16 @@ function drift(db, opts) {
3378
4081
  if (files2.length < 3) continue;
3379
4082
  const depFreq = /* @__PURE__ */ new Map();
3380
4083
  for (const file of files2) {
3381
- if (isStructuralRole(path.basename(file))) continue;
4084
+ if (shouldSkipDriftFile(file)) continue;
3382
4085
  for (const dep of depGraph.get(file) ?? []) {
4086
+ if (shouldSkipDriftFile(dep)) continue;
3383
4087
  depFreq.set(dep, (depFreq.get(dep) ?? 0) + 1);
3384
4088
  }
3385
4089
  }
3386
4090
  for (const file of files2) {
3387
- if (isStructuralRole(path.basename(file))) continue;
4091
+ if (shouldSkipDriftFile(file)) continue;
3388
4092
  for (const dep of depGraph.get(file) ?? []) {
4093
+ if (shouldSkipDriftFile(dep)) continue;
3389
4094
  if ((depFreq.get(dep) ?? 0) === 1) {
3390
4095
  if (path.dirname(dep) === dir) continue;
3391
4096
  results.push({
@@ -3416,7 +4121,7 @@ function buildSymbolRefGraph(db, scope) {
3416
4121
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
3417
4122
  JOIN documents d2 ON der.document_id = d2.id
3418
4123
  WHERE d1.id != d2.id
3419
- AND m.role = 0
4124
+ AND m.role != 1
3420
4125
  ${db.pathExclusionsFor("d1", "d2")}
3421
4126
  ${scopeFilter}`
3422
4127
  );
@@ -3432,10 +4137,12 @@ function inferLayerRules(depGraph) {
3432
4137
  const layerEdges = /* @__PURE__ */ new Map();
3433
4138
  const layerSet = /* @__PURE__ */ new Set();
3434
4139
  for (const [file, deps2] of depGraph) {
3435
- const fromLayer = getTopDir(file);
4140
+ if (shouldSkipDriftFile(file)) continue;
4141
+ const fromLayer = getArchitecturalLayer(file);
3436
4142
  layerSet.add(fromLayer);
3437
4143
  for (const dep of deps2) {
3438
- const toLayer = getTopDir(dep);
4144
+ if (shouldSkipDriftFile(dep)) continue;
4145
+ const toLayer = getArchitecturalLayer(dep);
3439
4146
  if (fromLayer === toLayer) continue;
3440
4147
  layerSet.add(toLayer);
3441
4148
  const key = `${fromLayer}->${toLayer}`;
@@ -3448,13 +4155,23 @@ function inferLayerRules(depGraph) {
3448
4155
  }
3449
4156
  return rules;
3450
4157
  }
3451
- function getTopDir(filePath) {
3452
- const parts = filePath.split("/");
3453
- return parts[0] ?? filePath;
4158
+ function getArchitecturalLayer(filePath) {
4159
+ const normalized = filePath.replace(/\\/g, "/");
4160
+ const parts = normalized.split("/").filter(Boolean);
4161
+ if (parts.length <= 1) {
4162
+ return "(root)";
4163
+ }
4164
+ if (parts.length >= 3 && ["src", "lib", "app", "server", "client"].includes(parts[0])) {
4165
+ return `${parts[0]}/${parts[1]}`;
4166
+ }
4167
+ return parts[0];
3454
4168
  }
3455
4169
  function isLikelyTypeOnlyDep(dep) {
3456
4170
  return dep.includes("types") || dep.endsWith(".d.ts");
3457
4171
  }
4172
+ function shouldSkipDriftFile(filePath) {
4173
+ return isStructuralRole(path.basename(filePath)) || isTestLikePath(filePath);
4174
+ }
3458
4175
  function isStructuralRole(basename) {
3459
4176
  if (basename === "index.ts" || basename === "index.js") return true;
3460
4177
  if (basename === "cli.ts" || basename === "main.ts" || basename === "main.rs") return true;
@@ -3462,6 +4179,11 @@ function isStructuralRole(basename) {
3462
4179
  if (basename === "health.ts" || basename === "health.js") return true;
3463
4180
  return false;
3464
4181
  }
4182
+ function isTestLikePath(filePath) {
4183
+ 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);
4186
+ }
3465
4187
 
3466
4188
  // src/queries/wrapper-candidates.ts
3467
4189
  function wrapperCandidates(db, opts) {
@@ -3486,7 +4208,7 @@ function wrapperCandidates(db, opts) {
3486
4208
  AND ref_c.end_line <= caller_der.end_line
3487
4209
  JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id
3488
4210
  WHERE ref_m.symbol_id = gs.id
3489
- AND ref_m.role = 0
4211
+ AND ref_m.role != 1
3490
4212
  AND ref_c.document_id != der.document_id
3491
4213
  LIMIT 1
3492
4214
  ) AS caller_symbol,
@@ -3503,11 +4225,11 @@ function wrapperCandidates(db, opts) {
3503
4225
  AND ref_c2.start_line >= caller_der2.start_line
3504
4226
  AND ref_c2.end_line <= caller_der2.end_line
3505
4227
  WHERE ref_m2.symbol_id = gs.id
3506
- AND ref_m2.role = 0
4228
+ AND ref_m2.role != 1
3507
4229
  AND ref_c2.document_id != der.document_id
3508
4230
  LIMIT 1
3509
4231
  )
3510
- AND caller_ref_m.role = 0
4232
+ AND caller_ref_m.role != 1
3511
4233
  ) AS caller_fan_in
3512
4234
  FROM global_symbols gs
3513
4235
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
@@ -3527,7 +4249,7 @@ function wrapperCandidates(db, opts) {
3527
4249
  FROM mentions ref_m
3528
4250
  JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
3529
4251
  WHERE ref_m.symbol_id = gs.id
3530
- AND ref_m.role = 0
4252
+ AND ref_m.role != 1
3531
4253
  AND ref_c.document_id != der.document_id
3532
4254
  ) = 1
3533
4255
  ) WHERE caller_symbol IS NOT NULL AND caller_fan_in > 3
@@ -3624,7 +4346,7 @@ function staleAbstractions(db, opts) {
3624
4346
  FROM mentions ref_m
3625
4347
  JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
3626
4348
  WHERE ref_m.symbol_id = gs.id
3627
- AND ref_m.role = 0
4349
+ AND ref_m.role != 1
3628
4350
  AND ref_c.document_id != der.document_id
3629
4351
  ) AS consumers
3630
4352
  FROM global_symbols gs
@@ -3676,7 +4398,7 @@ function complexityHotspots(db, opts) {
3676
4398
  (SELECT COUNT(DISTINCT ref_c.document_id)
3677
4399
  FROM mentions ref_m
3678
4400
  JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
3679
- WHERE ref_m.symbol_id = gs.id AND ref_m.role = 0
4401
+ WHERE ref_m.symbol_id = gs.id AND ref_m.role != 1
3680
4402
  ) AS fan_in,
3681
4403
  -- fanOut: distinct symbols referenced within this definition range
3682
4404
  -- that are defined in different files
@@ -3688,7 +4410,7 @@ function complexityHotspots(db, opts) {
3688
4410
  WHERE out_c.document_id = der.document_id
3689
4411
  AND out_c.start_line >= der.start_line
3690
4412
  AND out_c.end_line <= der.end_line
3691
- AND out_m.role = 0
4413
+ AND out_m.role != 1
3692
4414
  AND out_gs.id != gs.id
3693
4415
  AND out_der.document_id != der.document_id
3694
4416
  ) AS fan_out,
@@ -3700,7 +4422,7 @@ function complexityHotspots(db, opts) {
3700
4422
  WHERE callee_c.document_id = der.document_id
3701
4423
  AND callee_c.start_line >= der.start_line
3702
4424
  AND callee_c.end_line <= der.end_line
3703
- AND callee_m.role = 0
4425
+ AND callee_m.role != 1
3704
4426
  AND callee_gs.id != gs.id
3705
4427
  ) AS callee_count
3706
4428
  FROM global_symbols gs
@@ -3717,7 +4439,7 @@ function complexityHotspots(db, opts) {
3717
4439
  * CAST((SELECT COUNT(DISTINCT ref_c2.document_id)
3718
4440
  FROM mentions ref_m2
3719
4441
  JOIN chunks ref_c2 ON ref_m2.chunk_id = ref_c2.id
3720
- WHERE ref_m2.symbol_id = gs.id AND ref_m2.role = 0
4442
+ WHERE ref_m2.symbol_id = gs.id AND ref_m2.role != 1
3721
4443
  ) AS REAL) / 5.0
3722
4444
  * MAX(CAST((SELECT COUNT(DISTINCT out_gs2.id)
3723
4445
  FROM mentions out_m2
@@ -3727,7 +4449,7 @@ function complexityHotspots(db, opts) {
3727
4449
  WHERE out_c2.document_id = der.document_id
3728
4450
  AND out_c2.start_line >= der.start_line
3729
4451
  AND out_c2.end_line <= der.end_line
3730
- AND out_m2.role = 0
4452
+ AND out_m2.role != 1
3731
4453
  AND out_gs2.id != gs.id
3732
4454
  AND out_der2.document_id != der.document_id
3733
4455
  ) AS REAL) / 5.0, 1.0)
@@ -3756,7 +4478,7 @@ function complexityHotspots(db, opts) {
3756
4478
  function health(db, opts = {}) {
3757
4479
  const { scope } = opts;
3758
4480
  const s = stats(db);
3759
- const deadResult = dead(db, { scope, minLoc: 3, skipBarrels: false });
4481
+ const deadResult = dead(db, { scope, minLoc: 3, skipBarrels: true });
3760
4482
  const isolatedResult = isolated(db, { scope, minLoc: 3 });
3761
4483
  const cycleResult = cycles(db, { scope });
3762
4484
  const similarResult = similarAll(db, { scope, minSimilarity: 0.6, limit: 50, minCallees: 4 });
@@ -3766,20 +4488,13 @@ function health(db, opts = {}) {
3766
4488
  const staleResult = staleAbstractions(db, { scope, minLoc: 3, limit: 50 });
3767
4489
  const driftResult = drift(db, { scope });
3768
4490
  const complexResult = complexityHotspots(db, { scope, minLoc: 10, limit: 10 });
3769
- const testResult = testCoverageSummary(db, { scope, minLoc: 3 });
3770
- const isolatedLoc = isolatedResult.reduce((sum, r) => sum + r.loc, 0);
3771
- const entryPointPatterns = ["/index.ts", "/index.js", "cli.ts", "worker.ts", "postinstall.ts", "/mod.rs", "__init__.py", "main.ts", "main.rs", "main.go", "main.py"];
3772
- const isEntryPoint = (path2) => entryPointPatterns.some((p) => path2.endsWith(p));
3773
4491
  const trueDeadSymbols = deadResult.symbols.filter(
3774
- (s2) => !isEntryPoint(s2.relativePath) && s2.kind === "dead-code"
4492
+ (s2) => !isEntrySurface(db, s2.relativePath) && s2.kind === "dead-code"
3775
4493
  );
3776
4494
  const trueDeadCount = trueDeadSymbols.length;
3777
4495
  const trueDeadLoc = trueDeadSymbols.reduce((sum, s2) => sum + s2.loc, 0);
3778
- const fileInternalCount = deadResult.symbols.filter(
3779
- (s2) => !isEntryPoint(s2.relativePath) && s2.kind === "file-internal"
3780
- ).length;
3781
4496
  const trueIsolatedCount = isolatedResult.filter(
3782
- (s2) => !isEntryPoint(s2.relativePath)
4497
+ (s2) => !isEntrySurface(db, s2.relativePath)
3783
4498
  ).length;
3784
4499
  const filesWithFunctions = new Set(
3785
4500
  db.all(
@@ -3808,16 +4523,6 @@ function health(db, opts = {}) {
3808
4523
  locRecoverable: trueDeadLoc
3809
4524
  });
3810
4525
  }
3811
- if (testResult.percent < 50) {
3812
- actions.push({
3813
- category: "Test coverage",
3814
- description: `${testResult.percent}% of symbols referenced by tests (${testResult.uncovered} uncovered)`,
3815
- effort: "high",
3816
- impact: "high",
3817
- count: testResult.uncovered,
3818
- locRecoverable: 0
3819
- });
3820
- }
3821
4526
  if (trueIsolatedCount > 0) {
3822
4527
  actions.push({
3823
4528
  category: "Isolated symbols",
@@ -3825,7 +4530,7 @@ function health(db, opts = {}) {
3825
4530
  effort: "low",
3826
4531
  impact: "medium",
3827
4532
  count: trueIsolatedCount,
3828
- locRecoverable: isolatedResult.filter((s2) => !isEntryPoint(s2.relativePath)).reduce((sum, s2) => sum + s2.loc, 0)
4533
+ locRecoverable: isolatedResult.filter((s2) => !isEntrySurface(db, s2.relativePath)).reduce((sum, s2) => sum + s2.loc, 0)
3829
4534
  });
3830
4535
  }
3831
4536
  if (cycleResult.length > 0) {
@@ -3936,8 +4641,6 @@ function health(db, opts = {}) {
3936
4641
  score -= Math.min(5, Math.round(driftPercent * 50));
3937
4642
  const extremeComplexity = complexResult.filter((r) => r.score > 50).length;
3938
4643
  score -= Math.min(5, extremeComplexity * 2);
3939
- const coverageDeduction = Math.round(15 * (1 - testResult.percent / 100));
3940
- score -= coverageDeduction;
3941
4644
  score = Math.max(0, Math.min(100, score));
3942
4645
  return {
3943
4646
  score,
@@ -3950,7 +4653,7 @@ function health(db, opts = {}) {
3950
4653
  deadSymbols: trueDeadCount,
3951
4654
  deadLoc: trueDeadLoc,
3952
4655
  isolatedSymbols: trueIsolatedCount,
3953
- isolatedLoc: isolatedResult.filter((s2) => !isEntryPoint(s2.relativePath)).reduce((sum, s2) => sum + s2.loc, 0),
4656
+ isolatedLoc: isolatedResult.filter((s2) => !isEntrySurface(db, s2.relativePath)).reduce((sum, s2) => sum + s2.loc, 0),
3954
4657
  cycles: cycleResult.length,
3955
4658
  similarPairs: trueSimilarCount,
3956
4659
  extractionCandidates: extractResult.length,
@@ -3958,8 +4661,7 @@ function health(db, opts = {}) {
3958
4661
  passthroughs: passthroughResult.length,
3959
4662
  staleTypes: trueStaleCount,
3960
4663
  driftedFiles: trueDriftCount,
3961
- complexityHotspotCount: complexResult.length,
3962
- testCoveragePercent: testResult.percent
4664
+ complexityHotspotCount: complexResult.length
3963
4665
  },
3964
4666
  actions,
3965
4667
  topComplexity: complexResult.slice(0, 5).map((r) => ({
@@ -3995,7 +4697,11 @@ function convergence(db, symbolPatternA, symbolPatternB) {
3995
4697
  const union = /* @__PURE__ */ new Set([...calleesA, ...calleesB]);
3996
4698
  const similarity = union.size > 0 ? shared.length / union.size : 0;
3997
4699
  let strategy;
3998
- if (uniqueA.length === 0 && uniqueB.length === 0) {
4700
+ if (union.size === 0) {
4701
+ strategy = "Neither function calls other tracked symbols. There is no callee-pattern evidence for consolidation; inspect the source bodies directly.";
4702
+ } else if (shared.length === 0) {
4703
+ strategy = "These functions do not share any callees. They are not a callee-based consolidation candidate.";
4704
+ } else if (uniqueA.length === 0 && uniqueB.length === 0) {
3999
4705
  strategy = "These functions have identical callee sets. One can replace the other directly.";
4000
4706
  } else if (uniqueA.length === 0) {
4001
4707
  strategy = `A is a subset of B. A can be replaced by calling B (B does everything A does plus more).`;
@@ -4030,8 +4736,8 @@ function convergence(db, symbolPatternA, symbolPatternB) {
4030
4736
  }
4031
4737
 
4032
4738
  // src/queries/code.ts
4033
- import { readFileSync as readFileSync4 } from "fs";
4034
- import { join as join6 } from "path";
4739
+ import { readFileSync as readFileSync5 } from "fs";
4740
+ import { join as join7 } from "path";
4035
4741
  function code(db, symbolPattern, opts = {}) {
4036
4742
  const { context = 0 } = opts;
4037
4743
  const fileLineMatch = symbolPattern.match(/^(.+\.\w+):(\d+)-(\d+)$/);
@@ -4044,10 +4750,10 @@ function code(db, symbolPattern, opts = {}) {
4044
4750
  `SELECT language FROM documents WHERE relative_path = ?`,
4045
4751
  match.relativePath
4046
4752
  );
4047
- const filePath = join6(db.config.projectRoot, match.relativePath);
4753
+ const filePath = join7(db.config.projectRoot, match.relativePath);
4048
4754
  let fileContent;
4049
4755
  try {
4050
- fileContent = readFileSync4(filePath, "utf-8");
4756
+ fileContent = readFileSync5(filePath, "utf-8");
4051
4757
  } catch {
4052
4758
  return null;
4053
4759
  }
@@ -4072,10 +4778,10 @@ function readFileRange(db, filePath, startLine, endLine, context) {
4072
4778
  `%${filePath}%`
4073
4779
  );
4074
4780
  if (!doc) return null;
4075
- const fullPath = join6(db.config.projectRoot, doc.relative_path);
4781
+ const fullPath = join7(db.config.projectRoot, doc.relative_path);
4076
4782
  let fileContent;
4077
4783
  try {
4078
- fileContent = readFileSync4(fullPath, "utf-8");
4784
+ fileContent = readFileSync5(fullPath, "utf-8");
4079
4785
  } catch {
4080
4786
  return null;
4081
4787
  }
@@ -4095,8 +4801,8 @@ function readFileRange(db, filePath, startLine, endLine, context) {
4095
4801
  }
4096
4802
 
4097
4803
  // src/queries/complexity.ts
4098
- import { readFileSync as readFileSync5 } from "fs";
4099
- import { join as join7 } from "path";
4804
+ import { readFileSync as readFileSync6 } from "fs";
4805
+ import { join as join8 } from "path";
4100
4806
  function complexity(db, symbolPattern) {
4101
4807
  const match = findFirstSymbolMatch(db, symbolPattern);
4102
4808
  if (!match) return null;
@@ -4105,10 +4811,10 @@ function complexity(db, symbolPattern) {
4105
4811
  match.relativePath
4106
4812
  );
4107
4813
  const language = doc?.language ?? "unknown";
4108
- const filePath = join7(db.config.projectRoot, match.relativePath);
4814
+ const filePath = join8(db.config.projectRoot, match.relativePath);
4109
4815
  let source = "";
4110
4816
  try {
4111
- const lines = readFileSync5(filePath, "utf-8").split("\n");
4817
+ const lines = readFileSync6(filePath, "utf-8").split("\n");
4112
4818
  source = lines.slice(match.startLine, match.endLine + 1).join("\n");
4113
4819
  } catch {
4114
4820
  }
@@ -4120,7 +4826,7 @@ function complexity(db, symbolPattern) {
4120
4826
  `SELECT COUNT(DISTINCT c.document_id) AS c
4121
4827
  FROM mentions m
4122
4828
  JOIN chunks c ON m.chunk_id = c.id
4123
- WHERE m.symbol_id = ? AND m.role = 0`,
4829
+ WHERE m.symbol_id = ? AND m.role != 1`,
4124
4830
  match.symbolId
4125
4831
  );
4126
4832
  const fanOut2 = new Set(
@@ -4141,7 +4847,7 @@ function complexity(db, symbolPattern) {
4141
4847
  };
4142
4848
  }
4143
4849
  function countBranches(source, language) {
4144
- const stripped = stripCommentsAndStrings(source);
4850
+ const stripped = stripCommentsAndStrings2(source);
4145
4851
  let count = 0;
4146
4852
  const universalPatterns = [
4147
4853
  /\bif\b/g,
@@ -4188,7 +4894,7 @@ function countBranches(source, language) {
4188
4894
  }
4189
4895
  return count;
4190
4896
  }
4191
- function stripCommentsAndStrings(source) {
4897
+ function stripCommentsAndStrings2(source) {
4192
4898
  return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*/g, "").replace(/#.*/g, "").replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''").replace(/`(?:[^`\\]|\\.)*`/g, "``");
4193
4899
  }
4194
4900
 
@@ -4219,7 +4925,7 @@ function dataflow(db, symbolPattern) {
4219
4925
  FROM mentions m
4220
4926
  JOIN chunks c ON m.chunk_id = c.id
4221
4927
  JOIN documents d ON c.document_id = d.id
4222
- WHERE m.symbol_id = ? AND m.role = 0
4928
+ WHERE m.symbol_id = ? AND m.role != 1
4223
4929
  ${db.pathExclusionsFor("d")}
4224
4930
  ORDER BY d.relative_path, c.start_line`,
4225
4931
  match.symbolId
@@ -4233,7 +4939,7 @@ function dataflow(db, symbolPattern) {
4233
4939
  JOIN documents other_d ON other_der.document_id = other_d.id
4234
4940
  WHERE other_c.document_id = ?
4235
4941
  AND other_c.start_line >= ? AND other_c.end_line <= ?
4236
- AND other_m.role = 0
4942
+ AND other_m.role != 1
4237
4943
  AND other_gs.id != ?
4238
4944
  ${db.symbolNoiseFor("other_gs")}
4239
4945
  ${db.pathExclusionsFor("other_d")}
@@ -4256,11 +4962,11 @@ function dataflow(db, symbolPattern) {
4256
4962
  AND enc_der.end_line >= ref_c.end_line
4257
4963
  JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
4258
4964
  -- Find other symbols defined by that enclosing function's file
4259
- JOIN mentions consumer_m ON consumer_m.symbol_id = enc_gs.id AND consumer_m.role = 0
4965
+ JOIN mentions consumer_m ON consumer_m.symbol_id = enc_gs.id AND consumer_m.role != 1
4260
4966
  JOIN chunks consumer_c ON consumer_m.chunk_id = consumer_c.id
4261
4967
  JOIN documents consumer_d ON consumer_c.document_id = consumer_d.id
4262
4968
  JOIN global_symbols consumer_gs ON consumer_m.symbol_id = consumer_gs.id
4263
- WHERE ref_m.symbol_id = ? AND ref_m.role = 0
4969
+ WHERE ref_m.symbol_id = ? AND ref_m.role != 1
4264
4970
  AND consumer_d.id != ref_d.id
4265
4971
  ${db.symbolNoiseFor("consumer_gs")}
4266
4972
  ${db.pathExclusionsFor("consumer_d")}
@@ -4359,7 +5065,7 @@ function forwardSlice(db, match) {
4359
5065
  JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
4360
5066
  JOIN documents enc_d ON enc_der.document_id = enc_d.id
4361
5067
  -- Find other symbols referenced within that enclosing function
4362
- JOIN mentions out_m ON out_m.role = 0
5068
+ JOIN mentions out_m ON out_m.role != 1
4363
5069
  JOIN chunks out_c ON out_m.chunk_id = out_c.id
4364
5070
  AND out_c.document_id = enc_der.document_id
4365
5071
  AND out_c.start_line >= enc_der.start_line
@@ -4367,7 +5073,7 @@ function forwardSlice(db, match) {
4367
5073
  JOIN global_symbols out_gs ON out_m.symbol_id = out_gs.id
4368
5074
  JOIN defn_enclosing_ranges out_der ON out_gs.id = out_der.symbol_id
4369
5075
  JOIN documents out_d ON out_der.document_id = out_d.id
4370
- WHERE ref_m.symbol_id = ? AND ref_m.role = 0
5076
+ WHERE ref_m.symbol_id = ? AND ref_m.role != 1
4371
5077
  AND out_gs.id != ? AND out_gs.id != enc_gs.id
4372
5078
  AND out_d.id != ref_d.id
4373
5079
  ${db.symbolNoiseFor("out_gs")}
@@ -4415,7 +5121,7 @@ function redundantReexports(db, opts = {}) {
4415
5121
  JOIN global_symbols gs ON m.symbol_id = gs.id
4416
5122
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
4417
5123
  JOIN documents orig_d ON der.document_id = orig_d.id
4418
- WHERE m.role = 0
5124
+ WHERE m.role != 1
4419
5125
  AND (barrel_d.relative_path LIKE '%/index.ts'
4420
5126
  OR barrel_d.relative_path LIKE '%/index.js'
4421
5127
  OR barrel_d.relative_path = 'index.ts'
@@ -4431,6 +5137,7 @@ function redundantReexports(db, opts = {}) {
4431
5137
  const results = [];
4432
5138
  for (const row of reexportRows) {
4433
5139
  if (db.isIgnored(row.barrel_path) || db.isIgnored(row.original_path)) continue;
5140
+ if (isLiveBarrel(db, row.barrel_path)) continue;
4434
5141
  const consumerCounts = db.get(
4435
5142
  `SELECT
4436
5143
  SUM(CASE WHEN uses_barrel = 1 THEN 1 ELSE 0 END) AS barrel_consumers,
@@ -4443,20 +5150,20 @@ function redundantReexports(db, opts = {}) {
4443
5150
  FROM mentions barrel_m
4444
5151
  JOIN chunks barrel_c ON barrel_m.chunk_id = barrel_c.id
4445
5152
  WHERE barrel_c.document_id = consumer_d.id
4446
- AND barrel_m.role = 0
5153
+ AND barrel_m.role != 1
4447
5154
  AND barrel_m.symbol_id IN (
4448
5155
  SELECT m2.symbol_id
4449
5156
  FROM mentions m2
4450
5157
  JOIN chunks c2 ON m2.chunk_id = c2.id
4451
5158
  WHERE c2.document_id = ?
4452
- AND m2.role = 0
5159
+ AND m2.role != 1
4453
5160
  )
4454
5161
  ) THEN 1 ELSE 0 END) AS uses_barrel
4455
5162
  FROM mentions ref_m
4456
5163
  JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
4457
5164
  JOIN documents consumer_d ON ref_c.document_id = consumer_d.id
4458
5165
  WHERE ref_m.symbol_id = ?
4459
- AND ref_m.role = 0
5166
+ AND ref_m.role != 1
4460
5167
  AND consumer_d.id != ?
4461
5168
  AND consumer_d.id != ?
4462
5169
  ${db.pathExclusionsFor("consumer_d")}
@@ -4562,27 +5269,25 @@ function normalizeSignature(raw) {
4562
5269
 
4563
5270
  // src/setup.ts
4564
5271
  import {
4565
- existsSync as existsSync6,
5272
+ existsSync as existsSync7,
4566
5273
  mkdirSync as mkdirSync2,
4567
5274
  symlinkSync,
4568
5275
  readlinkSync,
4569
5276
  unlinkSync
4570
5277
  } from "fs";
4571
- import { join as join8, dirname as dirname2, resolve as resolve2 } from "path";
4572
- import { homedir as homedir2, platform as platform2, arch } from "os";
4573
- import { execFileSync as execFileSync4 } from "child_process";
5278
+ import { join as join9, dirname as dirname3, resolve as resolve3 } from "path";
5279
+ import { homedir as homedir2, platform as platform3 } from "os";
4574
5280
  import { fileURLToPath } from "url";
4575
- var IS_WINDOWS2 = platform2() === "win32";
5281
+ var IS_WINDOWS3 = platform3() === "win32";
4576
5282
  var SKILLS = ["concrete-plan", "scip-explore", "scip-debloat", "scip-verify"];
4577
- var SCIP_VERSION = "v0.7.0";
4578
5283
  function installSkills(opts = {}) {
4579
5284
  const log = opts.quiet ? () => {
4580
5285
  } : console.log;
4581
5286
  const thisFile = fileURLToPath(import.meta.url);
4582
- const skillsSource = resolve2(dirname2(thisFile), "..", "skills");
5287
+ const skillsSource = resolve3(dirname3(thisFile), "..", "skills");
4583
5288
  const targets = [
4584
- join8(homedir2(), ".claude", "skills"),
4585
- join8(homedir2(), ".codex", "skills")
5289
+ join9(homedir2(), ".claude", "skills"),
5290
+ join9(homedir2(), ".codex", "skills")
4586
5291
  ];
4587
5292
  const result = {
4588
5293
  installed: [],
@@ -4590,23 +5295,23 @@ function installSkills(opts = {}) {
4590
5295
  alreadyLinked: []
4591
5296
  };
4592
5297
  for (const targetDir of targets) {
4593
- const parentDir = dirname2(targetDir);
4594
- if (!existsSync6(parentDir)) {
5298
+ const parentDir = dirname3(targetDir);
5299
+ if (!existsSync7(parentDir)) {
4595
5300
  continue;
4596
5301
  }
4597
5302
  mkdirSync2(targetDir, { recursive: true });
4598
5303
  const toolName = targetDir.includes(".codex") ? "Codex" : "Claude";
4599
5304
  for (const skill of SKILLS) {
4600
- const source = join8(skillsSource, skill);
4601
- const target = join8(targetDir, skill);
4602
- if (!existsSync6(source)) {
5305
+ const source = join9(skillsSource, skill);
5306
+ const target = join9(targetDir, skill);
5307
+ if (!existsSync7(source)) {
4603
5308
  result.skipped.push(`${toolName}/${skill}`);
4604
5309
  continue;
4605
5310
  }
4606
- if (existsSync6(target)) {
5311
+ if (existsSync7(target)) {
4607
5312
  try {
4608
5313
  const existing = readlinkSync(target);
4609
- if (resolve2(existing) === resolve2(source)) {
5314
+ if (resolve3(existing) === resolve3(source)) {
4610
5315
  result.alreadyLinked.push(`${toolName}/${skill}`);
4611
5316
  log(` ok: ${skill} \u2192 ${toolName} (already linked)`);
4612
5317
  continue;
@@ -4618,88 +5323,29 @@ function installSkills(opts = {}) {
4618
5323
  }
4619
5324
  unlinkSync(target);
4620
5325
  }
4621
- symlinkSync(source, target, IS_WINDOWS2 ? "junction" : "dir");
5326
+ symlinkSync(source, target, IS_WINDOWS3 ? "junction" : "dir");
4622
5327
  result.installed.push(`${toolName}/${skill}`);
4623
5328
  log(` done: ${skill} \u2192 ${toolName}`);
4624
5329
  }
4625
5330
  }
4626
5331
  return result;
4627
5332
  }
4628
- function isScipInstalled() {
4629
- try {
4630
- const cmd = IS_WINDOWS2 ? "where" : "which";
4631
- execFileSync4(cmd, ["scip"], { stdio: "pipe" });
4632
- return true;
4633
- } catch {
4634
- return false;
4635
- }
4636
- }
4637
- function getScipDownloadUrl() {
4638
- const os = platform2();
4639
- const cpu = arch();
4640
- let osName;
4641
- let archName;
4642
- let ext;
4643
- switch (os) {
4644
- case "darwin":
4645
- osName = "darwin";
4646
- ext = "tar.gz";
4647
- break;
4648
- case "linux":
4649
- osName = "linux";
4650
- ext = "tar.gz";
4651
- break;
4652
- case "win32":
4653
- osName = "windows";
4654
- ext = "zip";
4655
- break;
4656
- default:
4657
- return null;
4658
- }
4659
- switch (cpu) {
4660
- case "arm64":
4661
- archName = "arm64";
4662
- break;
4663
- case "x64":
4664
- archName = "amd64";
4665
- break;
4666
- default:
4667
- return null;
4668
- }
4669
- const filename = `scip-${osName}-${archName}.${ext}`;
4670
- const url = `https://github.com/sourcegraph/scip/releases/download/${SCIP_VERSION}/${filename}`;
4671
- return { url, filename };
4672
- }
4673
- function printScipInstallInstructions() {
4674
- const download = getScipDownloadUrl();
4675
- console.log("\nThe `scip` CLI is required but not found on PATH.\n");
4676
- if (platform2() === "darwin") {
4677
- console.log("Install via Homebrew:");
4678
- console.log(" brew install sourcegraph/scip/scip\n");
4679
- console.log("Or download manually:");
4680
- } else {
4681
- console.log("Download from:");
4682
- }
4683
- if (download) {
4684
- console.log(` ${download.url}
4685
- `);
4686
- } else {
4687
- console.log(` https://github.com/sourcegraph/scip/releases/tag/${SCIP_VERSION}
4688
- `);
4689
- }
4690
- console.log("After installing, ensure `scip` is on your PATH and run `scip-query reindex`.");
4691
- }
4692
5333
 
4693
5334
  // src/cli.ts
4694
5335
  function resolveProjectRoot() {
4695
5336
  return process.env["SCIP_QUERY_PROJECT_ROOT"] ?? process.cwd();
4696
5337
  }
5338
+ function resolveActiveDbPath(projectRoot) {
5339
+ const config = loadProjectConfig(projectRoot);
5340
+ const paths = resolveIndexPaths(projectRoot, config);
5341
+ return process.env["SCIP_QUERY_INDEX_DB"] ?? (existsSync8(paths.dbPath) ? paths.dbPath : join10(projectRoot, "index.db"));
5342
+ }
4697
5343
  function openDb() {
4698
5344
  const projectRoot = resolveProjectRoot();
4699
5345
  const config = loadProjectConfig(projectRoot);
4700
5346
  const paths = resolveIndexPaths(projectRoot, config);
4701
- const dbPath = process.env["SCIP_QUERY_INDEX_DB"] ?? (existsSync7(paths.dbPath) ? paths.dbPath : join9(projectRoot, "index.db"));
4702
- if (!existsSync7(dbPath)) {
5347
+ const dbPath = resolveActiveDbPath(projectRoot);
5348
+ if (!existsSync8(dbPath)) {
4703
5349
  console.error(`error: No index.db found. Run: scip-query reindex`);
4704
5350
  process.exit(1);
4705
5351
  }
@@ -4724,6 +5370,61 @@ function runQuery(query, render) {
4724
5370
  render(query(db));
4725
5371
  });
4726
5372
  }
5373
+ var queries = {
5374
+ stats,
5375
+ files,
5376
+ symbols,
5377
+ methods,
5378
+ refs,
5379
+ trace,
5380
+ deps,
5381
+ rdeps,
5382
+ system,
5383
+ surface,
5384
+ dead,
5385
+ hotspots,
5386
+ imports,
5387
+ importedBy,
5388
+ unusedImports,
5389
+ outline,
5390
+ members,
5391
+ fanIn,
5392
+ fanOut,
5393
+ topFanIn,
5394
+ topFanOut,
5395
+ coupling,
5396
+ topCoupling,
5397
+ cycles,
5398
+ bottlenecks,
5399
+ isolated,
5400
+ byKind,
5401
+ kindCounts,
5402
+ docCoverage,
5403
+ deepChains,
5404
+ hierarchy,
5405
+ callGraph,
5406
+ similar,
5407
+ similarAll,
5408
+ similarFiles,
5409
+ similarChains,
5410
+ extractCandidates,
5411
+ affected,
5412
+ changeSurface,
5413
+ diffImpact,
5414
+ drift,
5415
+ wrapperCandidates,
5416
+ passthroughCandidates,
5417
+ staleAbstractions,
5418
+ complexityHotspots,
5419
+ health,
5420
+ convergence,
5421
+ code,
5422
+ complexity,
5423
+ dataflow,
5424
+ slice,
5425
+ redundantReexports,
5426
+ similarSignatures
5427
+ };
4727
5428
  program.name("scip-query").description("Language-agnostic code intelligence CLI powered by SCIP indexes").version("0.1.0");
4728
5429
  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) => {
4729
5430
  const projectRoot = resolveProjectRoot();
@@ -4741,7 +5442,7 @@ program.command("reindex").description("Index the codebase and convert to SQLite
4741
5442
  });
4742
5443
  program.command("stats").description("Show index statistics").action(() => {
4743
5444
  runQuery(
4744
- (db) => stats(db),
5445
+ (db) => queries.stats(db),
4745
5446
  (s) => {
4746
5447
  console.log(`Documents: ${s.documents}`);
4747
5448
  console.log(`Symbols: ${s.symbols}`);
@@ -4756,7 +5457,7 @@ program.command("stats").description("Show index statistics").action(() => {
4756
5457
  });
4757
5458
  program.command("files <pattern>").description("Find files matching a pattern").action((pattern) => {
4758
5459
  runQuery(
4759
- (db) => files(db, pattern),
5460
+ (db) => queries.files(db, pattern),
4760
5461
  (results) => {
4761
5462
  for (const r of results) console.log(r.relativePath);
4762
5463
  }
@@ -4764,7 +5465,7 @@ program.command("files <pattern>").description("Find files matching a pattern").
4764
5465
  });
4765
5466
  program.command("symbols <file>").description("List symbols defined in a file (with line ranges + signatures)").action((file) => {
4766
5467
  runQuery(
4767
- (db) => symbols(db, file),
5468
+ (db) => queries.symbols(db, file),
4768
5469
  (results) => {
4769
5470
  for (const r of results) {
4770
5471
  const sig = r.signature ? ` \u2014 ${r.signature}` : "";
@@ -4775,7 +5476,7 @@ program.command("symbols <file>").description("List symbols defined in a file (w
4775
5476
  });
4776
5477
  program.command("methods <className>").description("List methods of a class (with line ranges)").action((className) => {
4777
5478
  runQuery(
4778
- (db) => methods(db, className),
5479
+ (db) => queries.methods(db, className),
4779
5480
  (results) => {
4780
5481
  for (const r of results) {
4781
5482
  console.log(` ${r.startLine}-${r.endLine} ${r.name}`);
@@ -4785,7 +5486,7 @@ program.command("methods <className>").description("List methods of a class (wit
4785
5486
  });
4786
5487
  program.command("refs <symbol>").description("Find all files referencing a symbol").action((symbol) => {
4787
5488
  runQuery(
4788
- (db) => refs(db, symbol),
5489
+ (db) => queries.refs(db, symbol),
4789
5490
  (results) => {
4790
5491
  let prevFile = "";
4791
5492
  for (const r of results) {
@@ -4801,7 +5502,7 @@ program.command("refs <symbol>").description("Find all files referencing a symbo
4801
5502
  });
4802
5503
  program.command("trace <symbol>").description("Trace a symbol: definition + all references").action((symbol) => {
4803
5504
  runQuery(
4804
- (db) => trace(db, symbol),
5505
+ (db) => queries.trace(db, symbol),
4805
5506
  (result) => {
4806
5507
  console.log("\u2550\u2550\u2550 DEFINITION \u2550\u2550\u2550");
4807
5508
  for (const d of result.definitions) {
@@ -4817,7 +5518,7 @@ program.command("trace <symbol>").description("Trace a symbol: definition + all
4817
5518
  });
4818
5519
  program.command("deps <file>").description("Files this file depends on (internal)").action((file) => {
4819
5520
  runQuery(
4820
- (db) => deps(db, file),
5521
+ (db) => queries.deps(db, file),
4821
5522
  (results) => {
4822
5523
  for (const r of results) console.log(r.relativePath);
4823
5524
  }
@@ -4825,7 +5526,7 @@ program.command("deps <file>").description("Files this file depends on (internal
4825
5526
  });
4826
5527
  program.command("rdeps <file>").description("Files that depend on this file/module").action((file) => {
4827
5528
  runQuery(
4828
- (db) => rdeps(db, file),
5529
+ (db) => queries.rdeps(db, file),
4829
5530
  (results) => {
4830
5531
  for (const r of results) console.log(r.relativePath);
4831
5532
  }
@@ -4833,7 +5534,7 @@ program.command("rdeps <file>").description("Files that depend on this file/modu
4833
5534
  });
4834
5535
  program.command("system <module>").description("Full module map: files, symbols, deps in/out").action((module) => {
4835
5536
  runQuery(
4836
- (db) => system(db, module),
5537
+ (db) => queries.system(db, module),
4837
5538
  (result) => {
4838
5539
  console.log("\u2550\u2550\u2550 FILES \u2550\u2550\u2550");
4839
5540
  for (const f of result.files) console.log(f);
@@ -4850,7 +5551,7 @@ program.command("system <module>").description("Full module map: files, symbols,
4850
5551
  });
4851
5552
  program.command("surface <module>").description("What symbols consumers actually use from this module").action((module) => {
4852
5553
  runQuery(
4853
- (db) => surface(db, module),
5554
+ (db) => queries.surface(db, module),
4854
5555
  (results) => {
4855
5556
  for (const r of results) {
4856
5557
  console.log(` ${r.consumer} \u2192 ${r.shortName}`);
@@ -4867,7 +5568,7 @@ program.command("dead [scope]").description("Find dead code and file-internal sy
4867
5568
  skipBarrels: opts.skipBarrels,
4868
5569
  includeMembers: opts.includeMembers
4869
5570
  };
4870
- const result = dead(db, deadOpts);
5571
+ const result = queries.dead(db, deadOpts);
4871
5572
  if (result.symbols.length === 0) {
4872
5573
  console.log("No dead code found.");
4873
5574
  return;
@@ -4890,7 +5591,7 @@ program.command("dead [scope]").description("Find dead code and file-internal sy
4890
5591
  });
4891
5592
  program.command("hotspots").description("Most-referenced symbols in the codebase (choke points)").option("-n, --limit <n>", "Number of results", parseIntSafe, 30).option("-s, --scope <path>", "Limit to files matching path").action((opts) => {
4892
5593
  runQuery(
4893
- (db) => hotspots(db, { limit: opts.limit, scope: opts.scope }),
5594
+ (db) => queries.hotspots(db, { limit: opts.limit, scope: opts.scope }),
4894
5595
  (results) => {
4895
5596
  console.log(" refs files symbol");
4896
5597
  console.log(" \u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500");
@@ -4902,7 +5603,7 @@ program.command("hotspots").description("Most-referenced symbols in the codebase
4902
5603
  });
4903
5604
  program.command("imports <file>").description("What symbols does this file import?").action((file) => {
4904
5605
  runQuery(
4905
- (db) => imports(db, file),
5606
+ (db) => queries.imports(db, file),
4906
5607
  (results) => {
4907
5608
  if (results.length === 0) {
4908
5609
  console.log("No imports found (indexer may not emit role=2 for this language).");
@@ -4915,7 +5616,7 @@ program.command("imports <file>").description("What symbols does this file impor
4915
5616
  });
4916
5617
  program.command("imported-by <symbol>").description("Which files import this symbol?").action((symbol) => {
4917
5618
  runQuery(
4918
- (db) => importedBy(db, symbol),
5619
+ (db) => queries.importedBy(db, symbol),
4919
5620
  (results) => {
4920
5621
  for (const r of results) {
4921
5622
  console.log(` ${r.fromFile}`);
@@ -4925,7 +5626,7 @@ program.command("imported-by <symbol>").description("Which files import this sym
4925
5626
  });
4926
5627
  program.command("unused-imports <file>").description("Find imports not referenced in the same file").action((file) => {
4927
5628
  runQuery(
4928
- (db) => unusedImports(db, file),
5629
+ (db) => queries.unusedImports(db, file),
4929
5630
  (results) => {
4930
5631
  if (results.length === 0) {
4931
5632
  console.log("No unused imports found.");
@@ -4941,7 +5642,7 @@ ${results.length} unused import(s)`);
4941
5642
  });
4942
5643
  program.command("outline <file>").description("Tree view of symbols in a file (using nesting hierarchy)").action((file) => {
4943
5644
  runQuery(
4944
- (db) => outline(db, file),
5645
+ (db) => queries.outline(db, file),
4945
5646
  (roots) => {
4946
5647
  function printTree(nodes, indent) {
4947
5648
  for (const n of nodes) {
@@ -4956,7 +5657,7 @@ program.command("outline <file>").description("Tree view of symbols in a file (u
4956
5657
  });
4957
5658
  program.command("members <symbol>").description("All children of a symbol (methods, fields, nested types)").action((symbol) => {
4958
5659
  runQuery(
4959
- (db) => members(db, symbol),
5660
+ (db) => queries.members(db, symbol),
4960
5661
  (results) => {
4961
5662
  for (const r of results) {
4962
5663
  console.log(` ${r.startLine}-${r.endLine} [${r.kind}] ${r.shortName}`);
@@ -4967,12 +5668,12 @@ program.command("members <symbol>").description("All children of a symbol (metho
4967
5668
  program.command("fan-in [symbol]").description("How many files reference a symbol (or top fan-in across codebase)").option("-n, --limit <n>", "Number of results for top mode", parseIntSafe, 30).option("-s, --scope <path>", "Limit to files matching path").action((symbol, opts) => {
4968
5669
  withDb((db) => {
4969
5670
  if (symbol) {
4970
- const results = fanIn(db, symbol);
5671
+ const results = queries.fanIn(db, symbol);
4971
5672
  for (const r of results) {
4972
5673
  console.log(` ${String(r.count).padStart(4)} files ${r.name}`);
4973
5674
  }
4974
5675
  } else {
4975
- const results = topFanIn(db, { limit: opts.limit, scope: opts.scope });
5676
+ const results = queries.topFanIn(db, { limit: opts.limit, scope: opts.scope });
4976
5677
  console.log(" files symbol");
4977
5678
  console.log(" \u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500");
4978
5679
  for (const r of results) {
@@ -4984,12 +5685,12 @@ program.command("fan-in [symbol]").description("How many files reference a symbo
4984
5685
  program.command("fan-out [file]").description("How many external symbols a file uses (or top fan-out across codebase)").option("-n, --limit <n>", "Number of results for top mode", parseIntSafe, 30).option("-s, --scope <path>", "Limit to files matching path").action((file, opts) => {
4985
5686
  withDb((db) => {
4986
5687
  if (file) {
4987
- const results = fanOut(db, file);
5688
+ const results = queries.fanOut(db, file);
4988
5689
  for (const r of results) {
4989
5690
  console.log(` ${String(r.count).padStart(4)} symbols ${r.name}`);
4990
5691
  }
4991
5692
  } else {
4992
- const results = topFanOut(db, { limit: opts.limit, scope: opts.scope });
5693
+ const results = queries.topFanOut(db, { limit: opts.limit, scope: opts.scope });
4993
5694
  console.log(" symbols file");
4994
5695
  console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500");
4995
5696
  for (const r of results) {
@@ -5001,10 +5702,10 @@ program.command("fan-out [file]").description("How many external symbols a file
5001
5702
  program.command("coupling [file1] [file2]").description("Coupling between two files, or top coupled pairs in codebase").option("-n, --limit <n>", "Number of results for top mode", parseIntSafe, 20).option("-s, --scope <path>", "Limit to files matching path").action((file1, file2, opts) => {
5002
5703
  withDb((db) => {
5003
5704
  if (file1 && file2) {
5004
- const result = coupling(db, file1, file2);
5705
+ const result = queries.coupling(db, file1, file2);
5005
5706
  console.log(`${result.file1} \u2194 ${result.file2}: ${result.sharedSymbols} shared symbols`);
5006
5707
  } else {
5007
- const results = topCoupling(db, { limit: opts.limit, scope: opts.scope });
5708
+ const results = queries.topCoupling(db, { limit: opts.limit, scope: opts.scope });
5008
5709
  console.log(" shared file1 \u2192 file2");
5009
5710
  console.log(" \u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
5010
5711
  for (const r of results) {
@@ -5015,7 +5716,7 @@ program.command("coupling [file1] [file2]").description("Coupling between two fi
5015
5716
  });
5016
5717
  program.command("cycles").description("Detect circular dependency chains between files").option("-s, --scope <path>", "Limit to files matching path").option("--max-depth <n>", "Maximum cycle depth", parseIntSafe, 10).action((opts) => {
5017
5718
  runQuery(
5018
- (db) => cycles(db, { scope: opts.scope, maxDepth: opts.maxDepth }),
5719
+ (db) => queries.cycles(db, { scope: opts.scope, maxDepth: opts.maxDepth }),
5019
5720
  (results) => {
5020
5721
  if (results.length === 0) {
5021
5722
  console.log("No circular dependencies found.");
@@ -5036,7 +5737,7 @@ ${results.length} cycle(s) found.`);
5036
5737
  });
5037
5738
  program.command("bottlenecks").description("Find coupling hubs: high fan-in AND high fan-out").option("-n, --limit <n>", "Number of results", parseIntSafe, 20).option("-s, --scope <path>", "Limit to files matching path").option("--min-fan-in <n>", "Minimum fan-in", parseIntSafe, 2).option("--min-fan-out <n>", "Minimum fan-out", parseIntSafe, 2).action((opts) => {
5038
5739
  runQuery(
5039
- (db) => bottlenecks(db, {
5740
+ (db) => queries.bottlenecks(db, {
5040
5741
  limit: opts.limit,
5041
5742
  scope: opts.scope,
5042
5743
  minFanIn: opts.minFanIn,
@@ -5057,7 +5758,7 @@ program.command("bottlenecks").description("Find coupling hubs: high fan-in AND
5057
5758
  });
5058
5759
  program.command("isolated").description("Find completely orphaned symbols (no references at all)").option("-s, --scope <path>", "Limit to files matching path").option("--min-loc <n>", "Minimum lines of code", parseIntSafe, 3).action((opts) => {
5059
5760
  runQuery(
5060
- (db) => isolated(db, { scope: opts.scope, minLoc: opts.minLoc }),
5761
+ (db) => queries.isolated(db, { scope: opts.scope, minLoc: opts.minLoc }),
5061
5762
  (results) => {
5062
5763
  if (results.length === 0) {
5063
5764
  console.log("No isolated symbols found.");
@@ -5079,7 +5780,7 @@ ${results.length} isolated symbol(s)`);
5079
5780
  });
5080
5781
  program.command("by-kind <kind>").description("Find symbols by SCIP kind (class, interface, enum, function, etc.)").option("-s, --scope <path>", "Limit to files matching path").option("-n, --limit <n>", "Number of results", parseIntSafe, 100).action((kind, opts) => {
5081
5782
  runQuery(
5082
- (db) => byKind(db, kind, { scope: opts.scope, limit: opts.limit }),
5783
+ (db) => queries.byKind(db, kind, { scope: opts.scope, limit: opts.limit }),
5083
5784
  (results) => {
5084
5785
  if (results.length === 0) {
5085
5786
  console.log(`No symbols found for kind "${kind}". Use "kind-counts" to see available kinds.`);
@@ -5095,7 +5796,7 @@ ${results.length} symbol(s)`);
5095
5796
  });
5096
5797
  program.command("kind-counts").description("Histogram of symbol kinds in the codebase").option("-s, --scope <path>", "Limit to files matching path").action((opts) => {
5097
5798
  runQuery(
5098
- (db) => kindCounts(db, { scope: opts.scope }),
5799
+ (db) => queries.kindCounts(db, { scope: opts.scope }),
5099
5800
  (results) => {
5100
5801
  console.log(" count kind");
5101
5802
  console.log(" \u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500");
@@ -5105,29 +5806,9 @@ program.command("kind-counts").description("Histogram of symbol kinds in the cod
5105
5806
  }
5106
5807
  );
5107
5808
  });
5108
- program.command("test-coverage [symbol]").description("Check if symbols are referenced by test files").option("-s, --scope <path>", "Limit to files matching path").option("--min-loc <n>", "Minimum LOC for summary mode", parseIntSafe, 3).action((symbol, opts) => {
5109
- withDb((db) => {
5110
- if (symbol) {
5111
- const results = testCoverage(db, symbol);
5112
- for (const r of results) {
5113
- const status = r.covered ? "covered" : "NOT COVERED";
5114
- console.log(` [${status}] ${r.shortName} (${r.definedIn})`);
5115
- for (const tf of r.testFiles) {
5116
- console.log(` \u2190 ${tf}`);
5117
- }
5118
- }
5119
- } else {
5120
- const summary = testCoverageSummary(db, { scope: opts.scope, minLoc: opts.minLoc });
5121
- console.log(`Test coverage: ${summary.percent}%`);
5122
- console.log(` Total symbols: ${summary.total}`);
5123
- console.log(` Covered: ${summary.covered}`);
5124
- console.log(` Not covered: ${summary.uncovered}`);
5125
- }
5126
- });
5127
- });
5128
5809
  program.command("doc-coverage").description("Check documentation coverage across symbols").option("-s, --scope <path>", "Limit to files matching path").option("--min-loc <n>", "Minimum LOC to consider", parseIntSafe, 3).option("-n, --limit <n>", "Max undocumented symbols to show", parseIntSafe, 50).action((opts) => {
5129
5810
  runQuery(
5130
- (db) => docCoverage(db, {
5811
+ (db) => queries.docCoverage(db, {
5131
5812
  scope: opts.scope,
5132
5813
  minLoc: opts.minLoc,
5133
5814
  limit: opts.limit
@@ -5148,7 +5829,7 @@ program.command("doc-coverage").description("Check documentation coverage across
5148
5829
  });
5149
5830
  program.command("deep-chains").description("Find the longest transitive dependency chains").option("-n, --limit <n>", "Number of chains to show", parseIntSafe, 10).option("-s, --scope <path>", "Limit to files matching path").option("--min-depth <n>", "Minimum chain depth", parseIntSafe, 3).action((opts) => {
5150
5831
  runQuery(
5151
- (db) => deepChains(db, {
5832
+ (db) => queries.deepChains(db, {
5152
5833
  limit: opts.limit,
5153
5834
  scope: opts.scope,
5154
5835
  minDepth: opts.minDepth
@@ -5170,7 +5851,7 @@ Chain ${i + 1} (depth ${results[i].depth}):`);
5170
5851
  });
5171
5852
  program.command("hierarchy <symbol>").description("Show a symbol's ancestry chain (method \u2192 class \u2192 module)").action((symbol) => {
5172
5853
  runQuery(
5173
- (db) => hierarchy(db, symbol),
5854
+ (db) => queries.hierarchy(db, symbol),
5174
5855
  (chain) => {
5175
5856
  if (chain.length === 0) {
5176
5857
  console.log("Symbol not found.");
@@ -5185,7 +5866,7 @@ program.command("hierarchy <symbol>").description("Show a symbol's ancestry chai
5185
5866
  });
5186
5867
  program.command("call-graph <symbol>").description("Show incoming callers and outgoing callees for a symbol").action((symbol) => {
5187
5868
  runQuery(
5188
- (db) => callGraph(db, symbol),
5869
+ (db) => queries.callGraph(db, symbol),
5189
5870
  (result) => {
5190
5871
  if (!result) {
5191
5872
  console.log("Symbol not found.");
@@ -5208,7 +5889,7 @@ program.command("call-graph <symbol>").description("Show incoming callers and ou
5208
5889
  program.command("similar [symbol]").description("Find functions with similar callee fingerprints (consolidation candidates)").option("--min-similarity <n>", "Minimum Jaccard similarity (0-1)", parseFloat, 0.4).option("-n, --limit <n>", "Number of results", parseIntSafe, 20).option("-s, --scope <path>", "Limit to files matching path").option("--min-callees <n>", "Minimum callees to consider", parseIntSafe, 4).action((symbol, opts) => {
5209
5890
  withDb((db) => {
5210
5891
  if (symbol) {
5211
- const results = similar(db, symbol, {
5892
+ const results = queries.similar(db, symbol, {
5212
5893
  minSimilarity: opts.minSimilarity,
5213
5894
  limit: opts.limit
5214
5895
  });
@@ -5226,7 +5907,7 @@ ${Math.round(r.similarity * 100)}% similar:`);
5226
5907
  }
5227
5908
  }
5228
5909
  } else {
5229
- const results = similarAll(db, {
5910
+ const results = queries.similarAll(db, {
5230
5911
  minSimilarity: opts.minSimilarity,
5231
5912
  limit: opts.limit,
5232
5913
  scope: opts.scope,
@@ -5250,7 +5931,7 @@ ${results.length} similar pair(s) found.`);
5250
5931
  });
5251
5932
  program.command("similar-files [file]").description("Find files with similar dependency profiles").option("--min-similarity <n>", "Minimum Jaccard similarity (0-1)", parseFloat, 0.5).option("-n, --limit <n>", "Number of results", parseIntSafe, 20).option("-s, --scope <path>", "Limit to files matching path").option("--min-deps <n>", "Minimum dependencies to consider", parseIntSafe, 3).action((file, opts) => {
5252
5933
  runQuery(
5253
- (db) => similarFiles(db, {
5934
+ (db) => queries.similarFiles(db, {
5254
5935
  minSimilarity: opts.minSimilarity,
5255
5936
  limit: opts.limit,
5256
5937
  scope: opts.scope,
@@ -5278,7 +5959,7 @@ ${results.length} similar pair(s) found.`);
5278
5959
  });
5279
5960
  program.command("similar-chains").description("Find end-to-end dependency flows that diverge at few points").option("--min-similarity <n>", "Minimum chain similarity (0-1)", parseFloat, 0.5).option("-n, --limit <n>", "Number of results", parseIntSafe, 15).option("-s, --scope <path>", "Limit to files matching path").option("--min-length <n>", "Minimum chain length", parseIntSafe, 3).option("--max-length <n>", "Maximum chain length", parseIntSafe, 8).action((opts) => {
5280
5961
  runQuery(
5281
- (db) => similarChains(db, {
5962
+ (db) => queries.similarChains(db, {
5282
5963
  minSimilarity: opts.minSimilarity,
5283
5964
  limit: opts.limit,
5284
5965
  scope: opts.scope,
@@ -5310,7 +5991,7 @@ ${results.length} similar chain pair(s) found.`);
5310
5991
  });
5311
5992
  program.command("extract-candidates").description("Find functions with natural extraction seams (isolated callee clusters)").option("-s, --scope <path>", "Limit to files matching path").option("--min-loc <n>", "Minimum function LOC", parseIntSafe, 10).option("--min-callees <n>", "Minimum callees to analyze", parseIntSafe, 6).option("-n, --limit <n>", "Number of results", parseIntSafe, 20).action((opts) => {
5312
5993
  runQuery(
5313
- (db) => extractCandidates(db, {
5994
+ (db) => queries.extractCandidates(db, {
5314
5995
  scope: opts.scope,
5315
5996
  minLoc: opts.minLoc,
5316
5997
  minCallees: opts.minCallees,
@@ -5339,7 +6020,7 @@ ${results.length} extraction candidate(s) found.`);
5339
6020
  });
5340
6021
  program.command("affected <symbol>").description("Transitive closure of symbols that could break if this symbol changes").option("--max-depth <n>", "Maximum traversal depth", parseIntSafe, 5).option("-s, --scope <path>", "Limit to files matching path").action((symbol, opts) => {
5341
6022
  const db = openDb();
5342
- const results = affected(db, symbol, { maxDepth: opts.maxDepth, scope: opts.scope });
6023
+ const results = queries.affected(db, symbol, { maxDepth: opts.maxDepth, scope: opts.scope });
5343
6024
  if (results.length === 0) {
5344
6025
  console.log("No affected symbols found.");
5345
6026
  } else {
@@ -5357,44 +6038,39 @@ ${results.length} affected symbol(s) across ${new Set(results.map((r) => r.file)
5357
6038
  }
5358
6039
  db.close();
5359
6040
  });
5360
- program.command("change-surface <file>").description("Pre-change briefing: exports, consumers, test coverage, risk").action((file) => {
6041
+ program.command("change-surface <file>").description("Pre-change briefing: exports, consumers, and blast-radius risk").action((file) => {
5361
6042
  const db = openDb();
5362
- const result = changeSurface(db, file);
6043
+ const result = queries.changeSurface(db, file);
5363
6044
  if (!result) {
5364
6045
  console.log("File not found in index.");
5365
6046
  db.close();
5366
6047
  return;
5367
6048
  }
5368
6049
  console.log(`File: ${result.file}`);
5369
- console.log(`Test coverage: ${result.testCoveragePercent}% | External consumers: ${result.totalExternalConsumers}
6050
+ console.log(`External consumers: ${result.totalExternalConsumers}
5370
6051
  `);
5371
6052
  for (const s of result.symbols) {
5372
6053
  const risk = s.riskLevel === "high" ? " *** HIGH RISK ***" : s.riskLevel === "medium" ? " * medium risk *" : "";
5373
- const tests = s.testFiles.length > 0 ? ` (${s.testFiles.length} test file(s))` : " (no tests)";
5374
- console.log(` ${s.startLine}-${s.endLine} ${s.shortName} [${s.externalConsumers} consumers]${tests}${risk}`);
6054
+ console.log(` ${s.startLine}-${s.endLine} ${s.shortName} [${s.externalConsumers} consumers]${risk}`);
5375
6055
  }
5376
6056
  db.close();
5377
6057
  });
5378
- program.command("diff-impact").description("Compute affected symbols from current git diff").option("--base <ref>", "Git ref to diff against (default: HEAD)").action((opts) => {
6058
+ program.command("diff-impact").description("Compute changed symbols and downstream consumers from current git diff").option("--base <ref>", "Git ref to diff against (default: HEAD)").action((opts) => {
5379
6059
  const db = openDb();
5380
- const result = diffImpact(db, { base: opts.base });
6060
+ const result = queries.diffImpact(db, { base: opts.base });
5381
6061
  console.log(`Changed files: ${result.summary.totalChangedFiles}`);
5382
6062
  console.log(`Changed symbols: ${result.summary.totalChangedSymbols}`);
5383
6063
  console.log(`Affected consumer files: ${result.summary.totalAffectedFiles}`);
5384
- console.log(`Test coverage: ${result.summary.testCoveragePercent}%
5385
- `);
6064
+ if (result.summary.note) {
6065
+ console.log(`Note: ${result.summary.note}`);
6066
+ }
6067
+ console.log("");
5386
6068
  if (result.changedSymbols.length > 0) {
5387
6069
  console.log("Changed symbols:");
5388
6070
  for (const s of result.changedSymbols) {
5389
6071
  console.log(` ${s.file} ${s.shortName} (fan-in: ${s.fanIn})`);
5390
6072
  }
5391
6073
  }
5392
- if (result.uncoveredSymbols.length > 0) {
5393
- console.log("\nUncovered (no test references):");
5394
- for (const s of result.uncoveredSymbols) {
5395
- console.log(` ${s.file} ${s.shortName}`);
5396
- }
5397
- }
5398
6074
  if (result.affectedConsumers.length > 0) {
5399
6075
  console.log("\nAffected consumer files:");
5400
6076
  for (const c of result.affectedConsumers) {
@@ -5405,7 +6081,7 @@ program.command("diff-impact").description("Compute affected symbols from curren
5405
6081
  });
5406
6082
  program.command("drift [module]").description("Detect unused imports, layer violations, and pattern deviations").action((module) => {
5407
6083
  const db = openDb();
5408
- const summary = drift(db, { scope: module });
6084
+ const summary = queries.drift(db, { scope: module });
5409
6085
  if (summary.results.length === 0) {
5410
6086
  console.log("No drift detected.");
5411
6087
  } else {
@@ -5430,7 +6106,7 @@ ${summary.unusedImports} unused import(s), ${summary.layerViolations} layer viol
5430
6106
  });
5431
6107
  program.command("wrapper-candidates").description("Find symbols only called by one consumer (premature abstractions)").option("-s, --scope <path>", "Limit to files matching path").option("--max-loc <n>", "Maximum LOC for candidates", parseIntSafe, 15).option("-n, --limit <n>", "Number of results", parseIntSafe, 30).action((opts) => {
5432
6108
  const db = openDb();
5433
- const results = wrapperCandidates(db, { scope: opts.scope, maxLoc: opts.maxLoc, limit: opts.limit });
6109
+ const results = queries.wrapperCandidates(db, { scope: opts.scope, maxLoc: opts.maxLoc, limit: opts.limit });
5434
6110
  if (results.length === 0) {
5435
6111
  console.log("No wrapper candidates found.");
5436
6112
  } else {
@@ -5445,7 +6121,7 @@ ${results.length} wrapper candidate(s).`);
5445
6121
  });
5446
6122
  program.command("passthrough-candidates").description("Find functions that just forward to one other function").option("-s, --scope <path>", "Limit to files matching path").option("--max-loc <n>", "Maximum LOC for candidates", parseIntSafe, 15).option("-n, --limit <n>", "Number of results", parseIntSafe, 30).action((opts) => {
5447
6123
  const db = openDb();
5448
- const results = passthroughCandidates(db, { scope: opts.scope, maxLoc: opts.maxLoc, limit: opts.limit });
6124
+ const results = queries.passthroughCandidates(db, { scope: opts.scope, maxLoc: opts.maxLoc, limit: opts.limit });
5449
6125
  if (results.length === 0) {
5450
6126
  console.log("No passthrough candidates found.");
5451
6127
  } else {
@@ -5460,7 +6136,7 @@ ${results.length} passthrough candidate(s).`);
5460
6136
  });
5461
6137
  program.command("stale-abstractions").description("Find types/interfaces with 0-1 consumers (premature abstractions)").option("-s, --scope <path>", "Limit to files matching path").option("--min-loc <n>", "Minimum LOC", parseIntSafe, 3).option("-n, --limit <n>", "Number of results", parseIntSafe, 30).action((opts) => {
5462
6138
  const db = openDb();
5463
- const results = staleAbstractions(db, { scope: opts.scope, minLoc: opts.minLoc, limit: opts.limit });
6139
+ const results = queries.staleAbstractions(db, { scope: opts.scope, minLoc: opts.minLoc, limit: opts.limit });
5464
6140
  if (results.length === 0) {
5465
6141
  console.log("No stale abstractions found.");
5466
6142
  } else {
@@ -5475,7 +6151,7 @@ ${results.length} stale abstraction(s).`);
5475
6151
  });
5476
6152
  program.command("complexity-hotspots").description("Composite complexity score: LOC x fan-in x fan-out").option("-s, --scope <path>", "Limit to files matching path").option("--min-loc <n>", "Minimum LOC", parseIntSafe, 10).option("-n, --limit <n>", "Number of results", parseIntSafe, 20).action((opts) => {
5477
6153
  const db = openDb();
5478
- const results = complexityHotspots(db, { scope: opts.scope, minLoc: opts.minLoc, limit: opts.limit });
6154
+ const results = queries.complexityHotspots(db, { scope: opts.scope, minLoc: opts.minLoc, limit: opts.limit });
5479
6155
  if (results.length === 0) {
5480
6156
  console.log("No complexity hotspots found.");
5481
6157
  } else {
@@ -5489,7 +6165,7 @@ program.command("complexity-hotspots").description("Composite complexity score:
5489
6165
  });
5490
6166
  program.command("health").description("Composite codebase health report with prioritized action list").option("-s, --scope <path>", "Limit to files matching path").option("--json", "Output as JSON for programmatic consumption").action((opts) => {
5491
6167
  const db = openDb();
5492
- const report = health(db, { scope: opts.scope });
6168
+ const report = queries.health(db, { scope: opts.scope });
5493
6169
  if (opts.json) {
5494
6170
  console.log(JSON.stringify(report, null, 2));
5495
6171
  } else {
@@ -5510,7 +6186,6 @@ program.command("health").description("Composite codebase health report with pri
5510
6186
  if (f.staleTypes > 0) console.log(` Stale abstractions: ${f.staleTypes}`);
5511
6187
  if (f.driftedFiles > 0) console.log(` Pattern drift: ${f.driftedFiles} files`);
5512
6188
  if (f.complexityHotspotCount > 0) console.log(` Complexity hotspots: ${f.complexityHotspotCount}`);
5513
- console.log(` Test coverage: ${f.testCoveragePercent}%`);
5514
6189
  if (report.actions.length > 0) {
5515
6190
  console.log("\n Prioritized Actions (highest impact + lowest effort first):");
5516
6191
  for (let i = 0; i < report.actions.length; i++) {
@@ -5533,7 +6208,7 @@ program.command("health").description("Composite codebase health report with pri
5533
6208
  });
5534
6209
  program.command("convergence <symbol1> <symbol2>").description("Show what a consolidated version of two similar functions would look like").action((symbol1, symbol2) => {
5535
6210
  const db = openDb();
5536
- const result = convergence(db, symbol1, symbol2);
6211
+ const result = queries.convergence(db, symbol1, symbol2);
5537
6212
  if (!result) {
5538
6213
  console.log("One or both symbols not found.");
5539
6214
  db.close();
@@ -5563,7 +6238,7 @@ ${Math.round(result.similarity * 100)}% callee overlap
5563
6238
  });
5564
6239
  program.command("code <symbol>").description("Read the source code for a symbol (bounded to its definition range)").option("-C, --context <n>", "Extra lines of context above/below", parseIntSafe, 0).action((symbol, opts) => {
5565
6240
  const db = openDb();
5566
- const result = code(db, symbol, { context: opts.context });
6241
+ const result = queries.code(db, symbol, { context: opts.context });
5567
6242
  if (!result) {
5568
6243
  console.log("Symbol not found or file unreadable.");
5569
6244
  db.close();
@@ -5579,7 +6254,7 @@ program.command("code <symbol>").description("Read the source code for a symbol
5579
6254
  });
5580
6255
  program.command("complexity <symbol>").description("Per-symbol complexity: branches, cyclomatic estimate, fan-in/out, callees").action((symbol) => {
5581
6256
  const db = openDb();
5582
- const result = complexity(db, symbol);
6257
+ const result = queries.complexity(db, symbol);
5583
6258
  if (!result) {
5584
6259
  console.log("Symbol not found.");
5585
6260
  db.close();
@@ -5597,7 +6272,7 @@ program.command("complexity <symbol>").description("Per-symbol complexity: branc
5597
6272
  });
5598
6273
  program.command("dataflow <symbol>").description("Reference-level dataflow: definition sites, usage sites, producers, consumers").action((symbol) => {
5599
6274
  const db = openDb();
5600
- const result = dataflow(db, symbol);
6275
+ const result = queries.dataflow(db, symbol);
5601
6276
  if (!result) {
5602
6277
  console.log("Symbol not found.");
5603
6278
  db.close();
@@ -5634,7 +6309,7 @@ program.command("dataflow <symbol>").description("Reference-level dataflow: defi
5634
6309
  program.command("slice <symbol>").description("Reference-level program slice: what affects this (backward) or what this affects (forward)").option("--forward", "Forward slice (what does this affect). Default is backward.").action((symbol, opts) => {
5635
6310
  const db = openDb();
5636
6311
  const direction = opts.forward ? "forward" : "backward";
5637
- const result = slice(db, symbol, { direction });
6312
+ const result = queries.slice(db, symbol, { direction });
5638
6313
  if (!result) {
5639
6314
  console.log("Symbol not found.");
5640
6315
  db.close();
@@ -5672,7 +6347,7 @@ program.command("check-deps").description("Check if required dependencies (scip
5672
6347
  });
5673
6348
  program.command("redundant-reexports").description("Find barrel re-exports that nobody imports through").option("-s, --scope <path>", "Limit to files matching path").option("-n, --limit <n>", "Number of results", parseIntSafe, 30).action((opts) => {
5674
6349
  const db = openDb();
5675
- const results = redundantReexports(db, { scope: opts.scope, limit: opts.limit });
6350
+ const results = queries.redundantReexports(db, { scope: opts.scope, limit: opts.limit });
5676
6351
  if (results.length === 0) {
5677
6352
  console.log("No redundant re-exports found.");
5678
6353
  } else {
@@ -5693,7 +6368,7 @@ ${results.length} redundant re-export(s).`);
5693
6368
  });
5694
6369
  program.command("similar-signatures").description("Find functions with near-identical type signatures (same shape)").option("-s, --scope <path>", "Limit to files matching path").option("--min-loc <n>", "Minimum LOC per function", parseIntSafe, 3).option("-n, --limit <n>", "Number of groups", parseIntSafe, 20).action((opts) => {
5695
6370
  const db = openDb();
5696
- const groups = similarSignatures(db, { scope: opts.scope, minLoc: opts.minLoc, limit: opts.limit });
6371
+ const groups = queries.similarSignatures(db, { scope: opts.scope, minLoc: opts.minLoc, limit: opts.limit });
5697
6372
  if (groups.length === 0) {
5698
6373
  console.log("No same-shape function groups found.");
5699
6374
  } else {
@@ -5750,12 +6425,16 @@ program.command("status").description("Show index status for this project").acti
5750
6425
  const projectRoot = resolveProjectRoot();
5751
6426
  const config = loadProjectConfig(projectRoot);
5752
6427
  const paths = resolveIndexPaths(projectRoot, config);
6428
+ const dbPath = resolveActiveDbPath(projectRoot);
5753
6429
  console.log(`Project: ${projectRoot}`);
5754
- console.log(`DB path: ${paths.dbPath}`);
5755
- console.log(`Exists: ${existsSync7(paths.dbPath) ? "yes" : "no"}`);
5756
- if (existsSync7(paths.dbPath)) {
6430
+ console.log(`DB path: ${dbPath}`);
6431
+ if (dbPath !== paths.dbPath) {
6432
+ console.log(`Config: ${paths.dbPath} (fallback to project root index.db)`);
6433
+ }
6434
+ console.log(`Exists: ${existsSync8(dbPath) ? "yes" : "no"}`);
6435
+ if (existsSync8(dbPath)) {
5757
6436
  withDb((db) => {
5758
- const s = stats(db);
6437
+ const s = queries.stats(db);
5759
6438
  console.log(`Symbols: ${s.symbols}`);
5760
6439
  console.log(`Files: ${s.documents}`);
5761
6440
  console.log(`Size: ${formatBytes(s.indexSizeBytes)}`);
@@ -5766,7 +6445,9 @@ program.command("status").description("Show index status for this project").acti
5766
6445
  });
5767
6446
  }
5768
6447
  });
5769
- program.parse();
6448
+ if (process.argv[1] && fileURLToPath2(import.meta.url) === process.argv[1]) {
6449
+ program.parse();
6450
+ }
5770
6451
  function collect(value, prev) {
5771
6452
  return prev.concat([value]);
5772
6453
  }
@@ -5795,4 +6476,7 @@ function formatStatus(status) {
5795
6476
  }
5796
6477
  }
5797
6478
  }
6479
+ export {
6480
+ program
6481
+ };
5798
6482
  //# sourceMappingURL=cli.js.map