scip-query 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/README.md +16 -43
  2. package/dist/chunk-26DOJ63W.js +161 -0
  3. package/dist/chunk-2UELLEBI.js +1 -0
  4. package/dist/{chunk-4PDAL6IL.js → chunk-4JCSOF2O.js} +3 -3
  5. package/dist/{chunk-6SXADWLW.js → chunk-5OMVSV6E.js} +13 -5
  6. package/dist/{chunk-KPPHZCZJ.js → chunk-7HK5ZLOE.js} +28 -46
  7. package/dist/{chunk-7RLE5EWE.js → chunk-7KIMF5PV.js} +34 -13
  8. package/dist/chunk-AXQKUYKF.js +1442 -0
  9. package/dist/chunk-C7H5WBTJ.js +46 -0
  10. package/dist/{chunk-NHBZIL2J.js → chunk-CHDJXYBG.js} +3 -28
  11. package/dist/{chunk-KCBMVQL5.js → chunk-CPVAQJEC.js} +13 -5
  12. package/dist/{chunk-ZQIIPFD7.js → chunk-DH7G3DDV.js} +2 -2
  13. package/dist/{chunk-BOVXCR46.js → chunk-EOROMIFO.js} +14 -6
  14. package/dist/{chunk-2CKGIR6G.js → chunk-EPWLXXBL.js} +3 -3
  15. package/dist/{chunk-5RMYT5WH.js → chunk-F7XU27LU.js} +2 -2
  16. package/dist/{chunk-63G7IQTD.js → chunk-FYYOWQXK.js} +20 -40
  17. package/dist/chunk-GEXE2T6I.js +87 -0
  18. package/dist/{chunk-DGUPQSOR.js → chunk-GJDHTTR2.js} +11 -4
  19. package/dist/chunk-GSH2FPKV.js +87 -0
  20. package/dist/{chunk-NUZ4OMU3.js → chunk-GU2H5QRN.js} +2 -2
  21. package/dist/{chunk-Z6YZJ36C.js → chunk-HJZUSUPU.js} +8 -4
  22. package/dist/{chunk-LAWMH22O.js → chunk-HLKAFWWJ.js} +82 -3
  23. package/dist/{chunk-HPFZLISB.js → chunk-HLUS2HEB.js} +2 -2
  24. package/dist/{chunk-H2MDONBU.js → chunk-J3JSOSUO.js} +9 -6
  25. package/dist/{chunk-7PBOG4YE.js → chunk-KBOQX573.js} +2 -2
  26. package/dist/{chunk-HDSRORNV.js → chunk-KKCHYLVI.js} +17 -11
  27. package/dist/{chunk-HMLMH7VZ.js → chunk-LFJQVJYJ.js} +2 -2
  28. package/dist/{chunk-4EXL2CUA.js → chunk-LQJUPXQY.js} +16 -8
  29. package/dist/{chunk-ITZ3DDOG.js → chunk-MPGIHELS.js} +18 -3
  30. package/dist/chunk-NFS5W3PP.js +37 -0
  31. package/dist/chunk-NG5F43OU.js +200 -0
  32. package/dist/{chunk-DCKMSTJ4.js → chunk-O7Q7FDUJ.js} +22 -14
  33. package/dist/{chunk-UJQN5N3I.js → chunk-OIDHN6GD.js} +6 -3
  34. package/dist/{chunk-Z4GHE2HD.js → chunk-P3E6L7KW.js} +6 -2
  35. package/dist/{chunk-BNN2RKD2.js → chunk-P4WO3BBW.js} +3 -3
  36. package/dist/{chunk-QOV2R2WT.js → chunk-QIXNAB5K.js} +42 -2
  37. package/dist/{chunk-NWCE4CIC.js → chunk-SMDCNPMK.js} +11 -28
  38. package/dist/{chunk-SEFSL2GF.js → chunk-TOIEB3LG.js} +2 -2
  39. package/dist/{chunk-4XHWPRAX.js → chunk-UGQKAVCD.js} +3 -3
  40. package/dist/{chunk-OVPLOMPY.js → chunk-UQEQ6AHX.js} +7 -4
  41. package/dist/{chunk-ZK6GXM3J.js → chunk-VIYSWZCO.js} +3 -3
  42. package/dist/chunk-VJJKSGIX.js +121 -0
  43. package/dist/{chunk-N5KEREIA.js → chunk-VT4JBH6L.js} +19 -7
  44. package/dist/{chunk-7LLPRPR5.js → chunk-WGAD3GNR.js} +2 -2
  45. package/dist/chunk-YDBXNPYU.js +69 -0
  46. package/dist/chunk-YY4QGUQ5.js +84 -0
  47. package/dist/{chunk-FGXRVW7G.js → chunk-YZ6L7GFO.js} +2 -2
  48. package/dist/{chunk-W4ALF422.js → chunk-ZEUCXQBN.js} +3 -3
  49. package/dist/cli.js +2697 -1054
  50. package/dist/{db-BNVVZSfP.d.ts → db-ShvwGDKf.d.ts} +12 -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 +3 -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 +3 -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/deps.js +3 -1
  84. package/dist/queries/diff-impact.d.ts +2 -2
  85. package/dist/queries/diff-impact.js +2 -3
  86. package/dist/queries/doc-coverage.d.ts +1 -1
  87. package/dist/queries/doc-coverage.js +2 -2
  88. package/dist/queries/drift.d.ts +1 -1
  89. package/dist/queries/drift.js +3 -2
  90. package/dist/queries/extract-candidates.d.ts +1 -1
  91. package/dist/queries/extract-candidates.js +3 -3
  92. package/dist/queries/fan.d.ts +1 -1
  93. package/dist/queries/fan.js +3 -2
  94. package/dist/queries/files.d.ts +1 -1
  95. package/dist/queries/health.d.ts +1 -1
  96. package/dist/queries/health.js +14 -14
  97. package/dist/queries/hierarchy.d.ts +1 -1
  98. package/dist/queries/hierarchy.js +3 -2
  99. package/dist/queries/hotspots.d.ts +1 -1
  100. package/dist/queries/hotspots.js +2 -2
  101. package/dist/queries/imports.d.ts +1 -1
  102. package/dist/queries/imports.js +3 -2
  103. package/dist/queries/index.d.ts +1 -2
  104. package/dist/queries/index.js +46 -51
  105. package/dist/queries/isolated.d.ts +1 -1
  106. package/dist/queries/isolated.js +4 -3
  107. package/dist/queries/members.d.ts +2 -2
  108. package/dist/queries/members.js +3 -2
  109. package/dist/queries/methods.d.ts +1 -1
  110. package/dist/queries/methods.js +2 -2
  111. package/dist/queries/outline.d.ts +1 -1
  112. package/dist/queries/outline.js +3 -2
  113. package/dist/queries/passthrough-candidates.d.ts +1 -1
  114. package/dist/queries/passthrough-candidates.js +3 -3
  115. package/dist/queries/redundant-reexports.d.ts +1 -1
  116. package/dist/queries/redundant-reexports.js +4 -2
  117. package/dist/queries/refs.d.ts +1 -1
  118. package/dist/queries/refs.js +3 -1
  119. package/dist/queries/similar-chains.d.ts +1 -1
  120. package/dist/queries/similar-chains.js +3 -2
  121. package/dist/queries/similar-files.d.ts +1 -1
  122. package/dist/queries/similar-files.js +3 -2
  123. package/dist/queries/similar-signatures.d.ts +1 -1
  124. package/dist/queries/similar-signatures.js +2 -2
  125. package/dist/queries/similar.d.ts +1 -1
  126. package/dist/queries/similar.js +3 -3
  127. package/dist/queries/slice.d.ts +2 -2
  128. package/dist/queries/slice.js +3 -3
  129. package/dist/queries/stale-abstractions.d.ts +1 -1
  130. package/dist/queries/stale-abstractions.js +3 -3
  131. package/dist/queries/stats.d.ts +1 -1
  132. package/dist/queries/surface.d.ts +1 -1
  133. package/dist/queries/surface.js +3 -2
  134. package/dist/queries/symbols.d.ts +1 -1
  135. package/dist/queries/symbols.js +3 -2
  136. package/dist/queries/system.d.ts +1 -1
  137. package/dist/queries/system.js +3 -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-2CKGIR6G.js.map +0 -1
  147. package/dist/chunk-3UOUTZQT.js +0 -45
  148. package/dist/chunk-3UOUTZQT.js.map +0 -1
  149. package/dist/chunk-4EXL2CUA.js.map +0 -1
  150. package/dist/chunk-4PDAL6IL.js.map +0 -1
  151. package/dist/chunk-4TYLS5XX.js.map +0 -1
  152. package/dist/chunk-4XHWPRAX.js.map +0 -1
  153. package/dist/chunk-5RMYT5WH.js.map +0 -1
  154. package/dist/chunk-63G7IQTD.js.map +0 -1
  155. package/dist/chunk-6SXADWLW.js.map +0 -1
  156. package/dist/chunk-74RFWB5T.js.map +0 -1
  157. package/dist/chunk-7LLPRPR5.js.map +0 -1
  158. package/dist/chunk-7PBOG4YE.js.map +0 -1
  159. package/dist/chunk-7RLE5EWE.js.map +0 -1
  160. package/dist/chunk-7UCKSQRS.js +0 -55
  161. package/dist/chunk-7UCKSQRS.js.map +0 -1
  162. package/dist/chunk-BNN2RKD2.js.map +0 -1
  163. package/dist/chunk-BOVXCR46.js.map +0 -1
  164. package/dist/chunk-D567NFIF.js +0 -65
  165. package/dist/chunk-D567NFIF.js.map +0 -1
  166. package/dist/chunk-DCKMSTJ4.js.map +0 -1
  167. package/dist/chunk-DEZKCZXD.js +0 -40
  168. package/dist/chunk-DEZKCZXD.js.map +0 -1
  169. package/dist/chunk-DGUPQSOR.js.map +0 -1
  170. package/dist/chunk-DVWGWHFW.js +0 -99
  171. package/dist/chunk-DVWGWHFW.js.map +0 -1
  172. package/dist/chunk-EQYLEQCW.js +0 -46
  173. package/dist/chunk-EQYLEQCW.js.map +0 -1
  174. package/dist/chunk-FGXRVW7G.js.map +0 -1
  175. package/dist/chunk-H2MDONBU.js.map +0 -1
  176. package/dist/chunk-HB7MRLLL.js +0 -76
  177. package/dist/chunk-HB7MRLLL.js.map +0 -1
  178. package/dist/chunk-HDSRORNV.js.map +0 -1
  179. package/dist/chunk-HMLMH7VZ.js.map +0 -1
  180. package/dist/chunk-HPFZLISB.js.map +0 -1
  181. package/dist/chunk-HZBC7PPD.js +0 -88
  182. package/dist/chunk-HZBC7PPD.js.map +0 -1
  183. package/dist/chunk-ITZ3DDOG.js.map +0 -1
  184. package/dist/chunk-JJP7KQND.js +0 -1
  185. package/dist/chunk-JJP7KQND.js.map +0 -1
  186. package/dist/chunk-KCBMVQL5.js.map +0 -1
  187. package/dist/chunk-KPPHZCZJ.js.map +0 -1
  188. package/dist/chunk-LAWMH22O.js.map +0 -1
  189. package/dist/chunk-MCUX5LA7.js +0 -103
  190. package/dist/chunk-MCUX5LA7.js.map +0 -1
  191. package/dist/chunk-MGNMHKX3.js.map +0 -1
  192. package/dist/chunk-N5KEREIA.js.map +0 -1
  193. package/dist/chunk-NHBZIL2J.js.map +0 -1
  194. package/dist/chunk-NUZ4OMU3.js.map +0 -1
  195. package/dist/chunk-NWCE4CIC.js.map +0 -1
  196. package/dist/chunk-OVPLOMPY.js.map +0 -1
  197. package/dist/chunk-QOV2R2WT.js.map +0 -1
  198. package/dist/chunk-SEFSL2GF.js.map +0 -1
  199. package/dist/chunk-UJQN5N3I.js.map +0 -1
  200. package/dist/chunk-W4ALF422.js.map +0 -1
  201. package/dist/chunk-Z4GHE2HD.js.map +0 -1
  202. package/dist/chunk-Z6YZJ36C.js.map +0 -1
  203. package/dist/chunk-ZK6GXM3J.js.map +0 -1
  204. package/dist/chunk-ZOGY2V3N.js +0 -158
  205. package/dist/chunk-ZOGY2V3N.js.map +0 -1
  206. package/dist/chunk-ZQIIPFD7.js.map +0 -1
  207. package/dist/cli.js.map +0 -1
  208. package/dist/index.js.map +0 -1
  209. package/dist/postinstall.js.map +0 -1
  210. package/dist/queries/affected.js.map +0 -1
  211. package/dist/queries/bottlenecks.js.map +0 -1
  212. package/dist/queries/by-kind.js.map +0 -1
  213. package/dist/queries/call-graph.js.map +0 -1
  214. package/dist/queries/change-surface.js.map +0 -1
  215. package/dist/queries/clean-signature.js.map +0 -1
  216. package/dist/queries/code.js.map +0 -1
  217. package/dist/queries/complexity-hotspots.js.map +0 -1
  218. package/dist/queries/complexity.js.map +0 -1
  219. package/dist/queries/convergence.js.map +0 -1
  220. package/dist/queries/coupling.js.map +0 -1
  221. package/dist/queries/cycles.js.map +0 -1
  222. package/dist/queries/dataflow.js.map +0 -1
  223. package/dist/queries/dead.js.map +0 -1
  224. package/dist/queries/deep-chains.js.map +0 -1
  225. package/dist/queries/deps.js.map +0 -1
  226. package/dist/queries/diff-impact.js.map +0 -1
  227. package/dist/queries/doc-coverage.js.map +0 -1
  228. package/dist/queries/drift.js.map +0 -1
  229. package/dist/queries/extract-candidates.js.map +0 -1
  230. package/dist/queries/fan.js.map +0 -1
  231. package/dist/queries/files.js.map +0 -1
  232. package/dist/queries/health.js.map +0 -1
  233. package/dist/queries/hierarchy.js.map +0 -1
  234. package/dist/queries/hotspots.js.map +0 -1
  235. package/dist/queries/imports.js.map +0 -1
  236. package/dist/queries/index.js.map +0 -1
  237. package/dist/queries/isolated.js.map +0 -1
  238. package/dist/queries/members.js.map +0 -1
  239. package/dist/queries/methods.js.map +0 -1
  240. package/dist/queries/outline.js.map +0 -1
  241. package/dist/queries/passthrough-candidates.js.map +0 -1
  242. package/dist/queries/redundant-reexports.js.map +0 -1
  243. package/dist/queries/refs.js.map +0 -1
  244. package/dist/queries/similar-chains.js.map +0 -1
  245. package/dist/queries/similar-files.js.map +0 -1
  246. package/dist/queries/similar-signatures.js.map +0 -1
  247. package/dist/queries/similar.js.map +0 -1
  248. package/dist/queries/slice.js.map +0 -1
  249. package/dist/queries/stale-abstractions.js.map +0 -1
  250. package/dist/queries/stats.js.map +0 -1
  251. package/dist/queries/surface.js.map +0 -1
  252. package/dist/queries/symbols.js.map +0 -1
  253. package/dist/queries/system.js.map +0 -1
  254. package/dist/queries/test-coverage.d.ts +0 -22
  255. package/dist/queries/test-coverage.js +0 -11
  256. package/dist/queries/test-coverage.js.map +0 -1
  257. package/dist/queries/trace.js.map +0 -1
  258. package/dist/queries/wrapper-candidates.js.map +0 -1
  259. package/dist/reindex-worker.js.map +0 -1
  260. package/docs/AGENT_GUIDE.md +0 -359
  261. package/reports/debloat/2026-04-10-scip-query-self-audit.md +0 -161
  262. package/src/cli.ts +0 -1480
  263. package/src/config.ts +0 -117
  264. package/src/db.ts +0 -127
  265. package/src/gitignore-filter.ts +0 -143
  266. package/src/index.ts +0 -11
  267. package/src/postinstall.ts +0 -8
  268. package/src/queries/affected.ts +0 -86
  269. package/src/queries/bottlenecks.ts +0 -67
  270. package/src/queries/by-kind.ts +0 -204
  271. package/src/queries/call-graph.ts +0 -66
  272. package/src/queries/change-surface.ts +0 -110
  273. package/src/queries/clean-signature.ts +0 -22
  274. package/src/queries/code.ts +0 -101
  275. package/src/queries/complexity-hotspots.ts +0 -119
  276. package/src/queries/complexity.ts +0 -152
  277. package/src/queries/convergence.ts +0 -82
  278. package/src/queries/coupling.ts +0 -99
  279. package/src/queries/cycles.ts +0 -78
  280. package/src/queries/dataflow.ts +0 -128
  281. package/src/queries/dead.ts +0 -122
  282. package/src/queries/deep-chains.ts +0 -59
  283. package/src/queries/deps.ts +0 -46
  284. package/src/queries/diff-impact.ts +0 -204
  285. package/src/queries/doc-coverage.ts +0 -86
  286. package/src/queries/drift.ts +0 -224
  287. package/src/queries/extract-candidates.ts +0 -167
  288. package/src/queries/fan.ts +0 -148
  289. package/src/queries/files.ts +0 -16
  290. package/src/queries/health.ts +0 -324
  291. package/src/queries/hierarchy.ts +0 -49
  292. package/src/queries/hotspots.ts +0 -53
  293. package/src/queries/imports.ts +0 -95
  294. package/src/queries/index.ts +0 -45
  295. package/src/queries/isolated.ts +0 -67
  296. package/src/queries/members.ts +0 -54
  297. package/src/queries/methods.ts +0 -27
  298. package/src/queries/outline.ts +0 -52
  299. package/src/queries/passthrough-candidates.ts +0 -94
  300. package/src/queries/redundant-reexports.ts +0 -170
  301. package/src/queries/refs.ts +0 -27
  302. package/src/queries/similar-chains.ts +0 -314
  303. package/src/queries/similar-files.ts +0 -140
  304. package/src/queries/similar-signatures.ts +0 -151
  305. package/src/queries/similar.ts +0 -305
  306. package/src/queries/slice.ts +0 -154
  307. package/src/queries/stale-abstractions.ts +0 -82
  308. package/src/queries/stats.ts +0 -22
  309. package/src/queries/surface.ts +0 -34
  310. package/src/queries/symbols.ts +0 -39
  311. package/src/queries/system.ts +0 -86
  312. package/src/queries/test-coverage.ts +0 -106
  313. package/src/queries/trace.ts +0 -55
  314. package/src/queries/wrapper-candidates.ts +0 -112
  315. package/src/query-support.ts +0 -226
  316. package/src/reindex/detect.ts +0 -58
  317. package/src/reindex/index.ts +0 -153
  318. package/src/reindex/indexers.ts +0 -220
  319. package/src/reindex/install.ts +0 -125
  320. package/src/reindex-worker.ts +0 -35
  321. package/src/setup.ts +0 -202
  322. package/src/symbol-parser.ts +0 -278
  323. package/src/types.ts +0 -654
  324. package/src/watch.ts +0 -274
  325. package/tests/gitignore-filter.test.ts +0 -48
  326. package/tests/queries.test.ts +0 -300
  327. package/tests/symbol-parser.test.ts +0 -157
  328. package/tsconfig.json +0 -20
  329. package/tsup.config.ts +0 -40
  330. package/vitest.config.ts +0 -7
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/wrapper-candidates.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { testFileExclusionSql } from '../query-support.js';\nimport type { WrapperCandidate } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find wrapper candidates: symbols called by only one other symbol.\n *\n * These are premature abstractions that add indirection without\n * providing reuse. A function with fan-in = 1 whose sole caller\n * is widely used is a strong signal of unnecessary wrapping.\n */\nexport function wrapperCandidates(\n db: ScipDatabase,\n opts?: { scope?: string; maxLoc?: number; limit?: number },\n): WrapperCandidate[] {\n const { scope, maxLoc = 15, limit = 30 } = opts ?? {};\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n // Find all symbols with exactly 1 cross-file consumer (fan-in = 1),\n // along with who that single caller is and the caller's own fan-in.\n const rows = db.all<{\n symbol: string;\n file: string;\n start_line: number;\n end_line: number;\n loc: number;\n caller_symbol: string;\n caller_fan_in: number;\n }>(\n `SELECT * FROM (\n SELECT\n gs.symbol,\n d.relative_path AS file,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc,\n -- The single caller: the symbol whose definition range contains\n -- the chunk that references our target\n (SELECT caller_gs.symbol\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n JOIN defn_enclosing_ranges caller_der\n ON caller_der.document_id = ref_c.document_id\n AND ref_c.start_line >= caller_der.start_line\n AND ref_c.end_line <= caller_der.end_line\n JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id\n WHERE ref_m.symbol_id = gs.id\n AND ref_m.role != 1\n AND ref_c.document_id != der.document_id\n LIMIT 1\n ) AS caller_symbol,\n -- Fan-in of that single caller\n (SELECT COUNT(DISTINCT caller_ref_c.document_id)\n FROM mentions caller_ref_m\n JOIN chunks caller_ref_c ON caller_ref_m.chunk_id = caller_ref_c.id\n WHERE caller_ref_m.symbol_id = (\n SELECT caller_der2.symbol_id\n FROM mentions ref_m2\n JOIN chunks ref_c2 ON ref_m2.chunk_id = ref_c2.id\n JOIN defn_enclosing_ranges caller_der2\n ON caller_der2.document_id = ref_c2.document_id\n AND ref_c2.start_line >= caller_der2.start_line\n AND ref_c2.end_line <= caller_der2.end_line\n WHERE ref_m2.symbol_id = gs.id\n AND ref_m2.role != 1\n AND ref_c2.document_id != der.document_id\n LIMIT 1\n )\n AND caller_ref_m.role != 1\n ) AS caller_fan_in\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND ${testFileExclusionSql('d')}\n ${db.symbolNoiseFor('gs')}\n -- Only functions/terms, not type definitions (types with # are not wrappers)\n AND gs.symbol NOT LIKE '%#'\n AND (der.end_line - der.start_line + 1) <= ?\n AND (der.end_line - der.start_line + 1) >= 2\n ${scopeFilter}\n -- Exactly 1 cross-file consumer\n AND (\n SELECT COUNT(DISTINCT ref_c.document_id)\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n WHERE ref_m.symbol_id = gs.id\n AND ref_m.role != 1\n AND ref_c.document_id != der.document_id\n ) = 1\n ) WHERE caller_symbol IS NOT NULL AND caller_fan_in > 3\n ORDER BY caller_fan_in DESC, loc DESC\n LIMIT ?`,\n maxLoc, limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.file))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n file: r.file,\n startLine: r.start_line,\n endLine: r.end_line,\n loc: r.loc,\n singleCaller: r.caller_symbol,\n singleCallerShort: shortenSymbol(r.caller_symbol),\n callerFanIn: r.caller_fan_in,\n }));\n}\n"],"mappings":";;;;;;;;AAYO,SAAS,kBACd,IACA,MACoB;AACpB,QAAM,EAAE,OAAO,SAAS,IAAI,QAAQ,GAAG,IAAI,QAAQ,CAAC;AACpD,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAItE,QAAM,OAAO,GAAG;AAAA,IASd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UA6CM,GAAG,kBAAkB,GAAG,CAAC;AAAA,cACrB,qBAAqB,GAAG,CAAC;AAAA,UAC7B,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,UAKvB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAajB;AAAA,IAAQ;AAAA,EACV;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EACnC,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,MAAM,EAAE;AAAA,IACR,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,KAAK,EAAE;AAAA,IACP,cAAc,EAAE;AAAA,IAChB,mBAAmB,cAAc,EAAE,aAAa;AAAA,IAChD,aAAa,EAAE;AAAA,EACjB,EAAE;AACN;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/clean-signature.ts"],"sourcesContent":["/**\n * Clean up the raw doc/signature string from the SCIP index.\n *\n * Shared across symbols, trace, and system queries.\n * Previously duplicated as cleanSig/cleanSignature in three files.\n */\nexport function cleanSignature(sig: string | null): string | null {\n if (!sig || !sig.trim()) return null;\n return sig\n .replace(/^```\\w*\\s*/, '')\n .replace(/\\s*```$/, '')\n .replace(/^\\(method\\)\\s*/, '')\n .replace(/^\\(property\\)\\s*/, '')\n .replace(/^\\(function\\)\\s*/, '')\n .replace(/^\\(class\\)\\s*/, '')\n .replace(/^\\(interface\\)\\s*/, '')\n .replace(/^\\(enum\\)\\s*/, '')\n .replace(/^\\(type alias\\)\\s*/, '')\n .replace(/^\\(const\\)\\s*/, '')\n .replace(/^\\(var\\)\\s*/, '')\n .trim() || null;\n}\n"],"mappings":";AAMO,SAAS,eAAe,KAAmC;AAChE,MAAI,CAAC,OAAO,CAAC,IAAI,KAAK,EAAG,QAAO;AAChC,SAAO,IACJ,QAAQ,cAAc,EAAE,EACxB,QAAQ,WAAW,EAAE,EACrB,QAAQ,kBAAkB,EAAE,EAC5B,QAAQ,oBAAoB,EAAE,EAC9B,QAAQ,oBAAoB,EAAE,EAC9B,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,qBAAqB,EAAE,EAC/B,QAAQ,gBAAgB,EAAE,EAC1B,QAAQ,sBAAsB,EAAE,EAChC,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,eAAe,EAAE,EACzB,KAAK,KAAK;AACf;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/complexity.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch, getCalleeRowsForSymbol } from '../query-support.js';\nimport type { ComplexityResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Per-symbol complexity analysis combining source-level branch counting\n * with index-level metrics (fan-in, fan-out, callee count).\n *\n * Branch counting uses language-aware regex. The language is read from\n * the SCIP documents table, so it works for any indexed language.\n */\nexport function complexity(\n db: ScipDatabase,\n symbolPattern: string,\n): ComplexityResult | null {\n const match = findFirstSymbolMatch(db, symbolPattern);\n if (!match) return null;\n\n // Get language\n const doc = db.get<{ language: string | null }>(\n `SELECT language FROM documents WHERE relative_path = ?`,\n match.relativePath,\n );\n const language = doc?.language ?? 'unknown';\n\n // Read source for branch counting\n const filePath = join(db.config.projectRoot, match.relativePath);\n let source = '';\n try {\n const lines = readFileSync(filePath, 'utf-8').split('\\n');\n source = lines.slice(match.startLine, match.endLine + 1).join('\\n');\n } catch {\n // If we can't read the file, just skip branch counting\n }\n\n const branches = countBranches(source, language);\n const loc = match.endLine - match.startLine + 1;\n\n // Callee count\n const callees = getCalleeRowsForSymbol(db, match);\n const uniqueCallees = new Set(callees.map((c) => c.symbol));\n\n // Fan-in\n const fanInRow = db.get<{ c: number }>(\n `SELECT COUNT(DISTINCT c.document_id) AS c\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n WHERE m.symbol_id = ? AND m.role != 1`,\n match.symbolId,\n );\n\n // Fan-out (callees in other files)\n const fanOut = new Set(\n callees.filter((c) => c.file !== match.relativePath).map((c) => c.symbol),\n ).size;\n\n return {\n symbol: match.symbol,\n shortName: shortenSymbol(match.symbol),\n relativePath: match.relativePath,\n startLine: match.startLine,\n endLine: match.endLine,\n loc,\n branches,\n cyclomaticEstimate: branches + 1,\n calleeCount: uniqueCallees.size,\n fanIn: fanInRow?.c ?? 0,\n fanOut,\n };\n}\n\n/**\n * Count branch points in source code using language-aware regex.\n * Works across all SCIP-supported languages.\n */\nfunction countBranches(source: string, language: string): number {\n // Strip comments and strings to avoid false positives\n const stripped = stripCommentsAndStrings(source);\n let count = 0;\n\n // Universal branch keywords (work across most C-family languages)\n const universalPatterns = [\n /\\bif\\b/g,\n /\\belse\\s+if\\b/g,\n /\\belse\\b/g,\n /\\bfor\\b/g,\n /\\bwhile\\b/g,\n /\\bswitch\\b/g,\n /\\bcase\\b/g,\n /\\bcatch\\b/g,\n /\\?\\s*[^?]/g, // ternary (but not ??)\n /&&/g,\n /\\|\\|/g,\n ];\n\n for (const pattern of universalPatterns) {\n const matches = stripped.match(pattern);\n if (matches) count += matches.length;\n }\n\n // Language-specific patterns\n if (language === 'python') {\n const pyPatterns = [/\\belif\\b/g, /\\bexcept\\b/g, /\\bfinally\\b/g];\n for (const p of pyPatterns) {\n const m = stripped.match(p);\n if (m) count += m.length;\n }\n } else if (language === 'rust') {\n const rustPatterns = [/\\bmatch\\b/g, /=>/g, /\\bloop\\b/g];\n for (const p of rustPatterns) {\n const m = stripped.match(p);\n if (m) count += m.length;\n }\n } else if (language === 'ruby') {\n const rubyPatterns = [/\\belsif\\b/g, /\\bunless\\b/g, /\\brescue\\b/g, /\\bwhen\\b/g];\n for (const p of rubyPatterns) {\n const m = stripped.match(p);\n if (m) count += m.length;\n }\n } else if (language === 'go') {\n const goPatterns = [/\\bselect\\b/g, /\\bdefer\\b/g];\n for (const p of goPatterns) {\n const m = stripped.match(p);\n if (m) count += m.length;\n }\n }\n\n return count;\n}\n\n/**\n * Rough strip of comments and string literals to reduce false positives\n * in branch counting. Not perfect but good enough for estimation.\n */\nfunction stripCommentsAndStrings(source: string): string {\n return source\n // Block comments\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n // Line comments\n .replace(/\\/\\/.*/g, '')\n // Python/Ruby line comments\n .replace(/#.*/g, '')\n // Double-quoted strings\n .replace(/\"(?:[^\"\\\\]|\\\\.)*\"/g, '\"\"')\n // Single-quoted strings\n .replace(/'(?:[^'\\\\]|\\\\.)*'/g, \"''\")\n // Template literals\n .replace(/`(?:[^`\\\\]|\\\\.)*`/g, '``');\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/bottlenecks.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { BottleneckResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find coupling hubs: symbols with both high fan-in (many consumers)\n * AND high fan-out (references many other symbols).\n *\n * These are the most dangerous symbols to change — they sit at the\n * intersection of many dependency paths. Score = fanIn * fanOut.\n */\nexport function bottlenecks(\n db: ScipDatabase,\n opts: { limit?: number; scope?: string; minFanIn?: number; minFanOut?: number } = {},\n): BottleneckResult[] {\n const { limit = 20, scope, minFanIn = 2, minFanOut = 2 } = opts;\n const scopeFilter = scope ? `AND def_d.relative_path LIKE '%${scope}%'` : '';\n\n // Use a wrapping query to filter on computed columns\n const rows = db.all<{\n symbol: string;\n defined_in: string;\n fan_in: number;\n fan_out: number;\n }>(\n `SELECT * FROM (\n SELECT\n gs.symbol,\n def_d.relative_path AS defined_in,\n (SELECT COUNT(DISTINCT ref_c.document_id)\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n WHERE ref_m.symbol_id = gs.id AND ref_m.role != 1\n ) AS fan_in,\n (SELECT COUNT(DISTINCT ref_gs.id)\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n JOIN global_symbols ref_gs ON ref_m.symbol_id = ref_gs.id\n JOIN defn_enclosing_ranges ref_der ON ref_gs.id = ref_der.symbol_id\n WHERE ref_c.document_id = def_d.id\n AND ref_m.role != 1\n AND ref_der.document_id != def_d.id\n ) AS fan_out\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents def_d ON der.document_id = def_d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('def_d')}\n ${db.symbolNoiseFor('gs')}\n ${scopeFilter}\n ) WHERE fan_in >= ? AND fan_out >= ?\n ORDER BY (fan_in * fan_out) DESC\n LIMIT ?`,\n minFanIn, minFanOut, limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.defined_in))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n fanIn: r.fan_in,\n fanOut: r.fan_out,\n score: r.fan_in * r.fan_out,\n definedIn: r.defined_in,\n }));\n}\n"],"mappings":";;;;;AAWO,SAAS,YACd,IACA,OAAkF,CAAC,GAC/D;AACpB,QAAM,EAAE,QAAQ,IAAI,OAAO,WAAW,GAAG,YAAY,EAAE,IAAI;AAC3D,QAAM,cAAc,QAAQ,kCAAkC,KAAK,OAAO;AAG1E,QAAM,OAAO,GAAG;AAAA,IAMd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAsBM,GAAG,kBAAkB,OAAO,CAAC;AAAA,UAC7B,GAAG,eAAe,IAAI,CAAC;AAAA,UACvB,WAAW;AAAA;AAAA;AAAA;AAAA,IAIjB;AAAA,IAAU;AAAA,IAAW;AAAA,EACvB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,UAAU,CAAC,EACzC,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,OAAO,EAAE;AAAA,IACT,QAAQ,EAAE;AAAA,IACV,OAAO,EAAE,SAAS,EAAE;AAAA,IACpB,WAAW,EAAE;AAAA,EACf,EAAE;AACN;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/health.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { dead } from './dead.js';\nimport { isolated } from './isolated.js';\nimport { cycles } from './cycles.js';\nimport { similarAll } from './similar.js';\nimport { extractCandidates } from './extract-candidates.js';\nimport { wrapperCandidates } from './wrapper-candidates.js';\nimport { passthroughCandidates } from './passthrough-candidates.js';\nimport { staleAbstractions } from './stale-abstractions.js';\nimport { drift } from './drift.js';\nimport { complexityHotspots } from './complexity-hotspots.js';\nimport { testCoverageSummary } from './test-coverage.js';\nimport { stats } from './stats.js';\nimport type { HealthAction, HealthReport } from '../types.js';\n\n/**\n * Single composite health report that runs all de-bloat analyses\n * and produces a prioritized action list.\n *\n * The scoring formula accounts for common false positives:\n * - Entry points (CLI, workers, barrels) appearing as \"dead code\"\n * - Typed result interfaces with 1 consumer (normal for APIs)\n * - Consistent import patterns across sibling modules (not duplication)\n * - Barrel and orchestrator files deviating from sibling patterns (expected)\n */\nexport function health(\n db: ScipDatabase,\n opts: { scope?: string } = {},\n): HealthReport {\n const { scope } = opts;\n\n // Run all analyses\n const s = stats(db);\n const deadResult = dead(db, { scope, minLoc: 3, skipBarrels: false });\n const isolatedResult = isolated(db, { scope, minLoc: 3 });\n const cycleResult = cycles(db, { scope });\n const similarResult = similarAll(db, { scope, minSimilarity: 0.6, limit: 50, minCallees: 4 });\n const extractResult = extractCandidates(db, { scope, minLoc: 15, minCallees: 5, limit: 50 });\n const wrapperResult = wrapperCandidates(db, { scope, maxLoc: 15, limit: 50 });\n const passthroughResult = passthroughCandidates(db, { scope, maxLoc: 15, limit: 50 });\n const staleResult = staleAbstractions(db, { scope, minLoc: 3, limit: 50 });\n const driftResult = drift(db, { scope });\n const complexResult = complexityHotspots(db, { scope, minLoc: 10, limit: 10 });\n const testResult = testCoverageSummary(db, { scope, minLoc: 3 });\n\n const isolatedLoc = isolatedResult.reduce((sum, r) => sum + r.loc, 0);\n\n // ── False-positive filtering ─────────────────────────────\n\n // Entry points and barrels appear as dead/isolated because nothing imports them.\n // Filter them out of the scoring (but still report them with a note).\n const entryPointPatterns = ['/index.ts', '/index.js', 'cli.ts', 'worker.ts', 'postinstall.ts', '/mod.rs', '__init__.py', 'main.ts', 'main.rs', 'main.go', 'main.py'];\n const isEntryPoint = (path: string) => entryPointPatterns.some((p) => path.endsWith(p));\n\n // Dead code: only count truly dead symbols (zero refs anywhere),\n // excluding entry points AND file-internal helpers (which are fine).\n const trueDeadSymbols = deadResult.symbols.filter(\n (s) => !isEntryPoint(s.relativePath) && s.kind === 'dead-code',\n );\n const trueDeadCount = trueDeadSymbols.length;\n const trueDeadLoc = trueDeadSymbols.reduce((sum, s) => sum + s.loc, 0);\n const fileInternalCount = deadResult.symbols.filter(\n (s) => !isEntryPoint(s.relativePath) && s.kind === 'file-internal',\n ).length;\n\n // Isolated: same entry-point filtering\n const trueIsolatedCount = isolatedResult.filter(\n (s) => !isEntryPoint(s.relativePath),\n ).length;\n\n // Stale abstractions: the command filters out types.ts single-consumer types.\n // Also filter out 0-consumer types in files that export functions — these are\n // likely parameter/return types consumed through function signatures, which\n // the SCIP index can't track as direct mentions.\n const filesWithFunctions = new Set(\n db.all<{ relative_path: string }>(\n `SELECT DISTINCT d.relative_path\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE gs.symbol LIKE '%().'\n ${db.pathExclusionsFor('d')}`,\n ).map((r) => r.relative_path),\n );\n const trueStaleCount = staleResult.filter((s) => {\n // 0-consumer types in files with functions are likely param/return types\n if (s.consumers === 0 && filesWithFunctions.has(s.file)) return false;\n return true;\n }).length;\n\n // Drift: now uses usage-based detection (unused imports, layer violations, pattern deviations)\n // The drift command already filters structural roles internally.\n const trueDriftCount = driftResult.results.length;\n\n // Similar pairs: the similar command now uses TF-IDF weighted cosine\n // similarity which automatically discounts infrastructure callees.\n // The sharedCallees list only contains significant (above-median IDF) callees.\n // We can trust the count directly.\n const trueSimilarCount = similarResult.length;\n\n // ── Build prioritized action list ────────────────────────\n\n const actions: HealthAction[] = [];\n\n if (trueDeadCount > 0) {\n actions.push({\n category: 'Dead code',\n description: `${trueDeadCount} symbols with zero references anywhere — safe to delete`,\n effort: 'low',\n impact: 'high',\n count: trueDeadCount,\n locRecoverable: trueDeadLoc,\n });\n }\n\n if (testResult.percent < 50) {\n actions.push({\n category: 'Test coverage',\n description: `${testResult.percent}% of symbols referenced by tests (${testResult.uncovered} uncovered)`,\n effort: 'high',\n impact: 'high',\n count: testResult.uncovered,\n locRecoverable: 0,\n });\n }\n\n if (trueIsolatedCount > 0) {\n actions.push({\n category: 'Isolated symbols',\n description: `${trueIsolatedCount} symbols completely disconnected from the codebase graph`,\n effort: 'low',\n impact: 'medium',\n count: trueIsolatedCount,\n locRecoverable: isolatedResult\n .filter((s) => !isEntryPoint(s.relativePath))\n .reduce((sum, s) => sum + s.loc, 0),\n });\n }\n\n if (cycleResult.length > 0) {\n actions.push({\n category: 'Circular dependencies',\n description: `${cycleResult.length} cycle(s) — break with dependency inversion or module restructuring`,\n effort: 'medium',\n impact: 'high',\n count: cycleResult.length,\n locRecoverable: 0,\n });\n }\n\n if (trueSimilarCount > 0) {\n actions.push({\n category: 'Similar functions',\n description: `${trueSimilarCount} pairs with real logic overlap (beyond shared imports) — consolidation candidates`,\n effort: 'medium',\n impact: 'medium',\n count: trueSimilarCount,\n locRecoverable: 0,\n });\n }\n\n if (extractResult.length > 0) {\n actions.push({\n category: 'Extraction candidates',\n description: `${extractResult.length} large functions with isolated callee clusters — extract method opportunities`,\n effort: 'medium',\n impact: 'medium',\n count: extractResult.length,\n locRecoverable: 0,\n });\n }\n\n if (wrapperResult.length > 0) {\n actions.push({\n category: 'Wrapper functions',\n description: `${wrapperResult.length} single-consumer symbols that could be inlined`,\n effort: 'low',\n impact: 'low',\n count: wrapperResult.length,\n locRecoverable: wrapperResult.reduce((sum, r) => sum + r.loc, 0),\n });\n }\n\n if (passthroughResult.length > 0) {\n actions.push({\n category: 'Passthrough functions',\n description: `${passthroughResult.length} functions that just forward to one callee — unnecessary indirection`,\n effort: 'low',\n impact: 'low',\n count: passthroughResult.length,\n locRecoverable: passthroughResult.reduce((sum, r) => sum + r.loc, 0),\n });\n }\n\n if (trueStaleCount > 0) {\n // Count from the filtered set, not the raw result\n const trueStaleSymbols = staleResult.filter((s) => {\n if (s.consumers === 0 && filesWithFunctions.has(s.file)) return false;\n return true;\n });\n const unused = trueStaleSymbols.filter((s) => s.consumers === 0).length;\n const singleUse = trueStaleCount - unused;\n const parts: string[] = [];\n if (unused > 0) parts.push(`${unused} unused`);\n if (singleUse > 0) parts.push(`${singleUse} single-consumer (not in types file)`);\n actions.push({\n category: 'Stale abstractions',\n description: `${parts.join(', ')} — premature abstraction`,\n effort: 'low',\n impact: 'medium',\n count: trueStaleCount,\n locRecoverable: staleResult\n .filter((s) => s.consumers === 0 || !s.file.includes('types'))\n .reduce((sum, r) => sum + r.loc, 0),\n });\n }\n\n if (trueDriftCount > 0) {\n const parts: string[] = [];\n if (driftResult.unusedImports > 0) parts.push(`${driftResult.unusedImports} unused imports`);\n if (driftResult.layerViolations > 0) parts.push(`${driftResult.layerViolations} layer violations`);\n if (driftResult.patternDeviations > 0) parts.push(`${driftResult.patternDeviations} unique deps`);\n actions.push({\n category: 'Structural drift',\n description: parts.join(', '),\n effort: driftResult.layerViolations > 0 ? 'medium' : 'low',\n impact: driftResult.layerViolations > 0 ? 'medium' : 'low',\n count: trueDriftCount,\n locRecoverable: 0,\n });\n }\n\n // Sort: high impact + low effort first\n const impactWeight = { high: 3, medium: 2, low: 1 };\n const effortWeight = { low: 3, medium: 2, high: 1 };\n actions.sort((a, b) => {\n const scoreA = impactWeight[a.impact] * effortWeight[a.effort];\n const scoreB = impactWeight[b.impact] * effortWeight[b.effort];\n return scoreB - scoreA;\n });\n\n // ── Compute health score (0-100) ─────────────────────────\n //\n // Uses filtered counts (false positives removed).\n // Deductions scale with codebase size so a 10-file project\n // and a 1000-file project aren't penalized the same way.\n const fileCount = Math.max(s.documents, 1);\n const symbolCount = Math.max(s.symbols, 1);\n\n let score = 100;\n\n // Dead code: deduct based on % of symbols that are dead, not raw count\n const deadPercent = trueDeadCount / symbolCount;\n score -= Math.min(20, Math.round(deadPercent * 200));\n\n // Isolated: same percentage-based\n const isolatedPercent = trueIsolatedCount / symbolCount;\n score -= Math.min(10, Math.round(isolatedPercent * 200));\n\n // Cycles: these are always bad, flat penalty\n score -= Math.min(15, cycleResult.length * 5);\n\n // Similar pairs: only count true logic overlap, not boilerplate\n score -= Math.min(10, trueSimilarCount * 2);\n\n // Extract candidates: mild penalty\n score -= Math.min(5, extractResult.length * 2);\n\n // Wrappers: mild\n score -= Math.min(3, wrapperResult.length);\n\n // Passthroughs: mild\n score -= Math.min(3, passthroughResult.length);\n\n // Stale abstractions: percentage-based with filtered count\n const stalePercent = trueStaleCount / Math.max(symbolCount * 0.1, 1);\n score -= Math.min(8, Math.round(stalePercent * 10));\n\n // Drift: percentage of files that deviate\n const driftPercent = trueDriftCount / fileCount;\n score -= Math.min(5, Math.round(driftPercent * 50));\n\n // Complexity: only penalize extreme outliers\n const extremeComplexity = complexResult.filter((r) => r.score > 50).length;\n score -= Math.min(5, extremeComplexity * 2);\n\n // Test coverage: significant penalty for low coverage\n // 0% = -15, 25% = -11, 50% = -7, 75% = -4, 100% = 0\n const coverageDeduction = Math.round(15 * (1 - testResult.percent / 100));\n score -= coverageDeduction;\n\n score = Math.max(0, Math.min(100, score));\n\n return {\n score,\n overview: {\n documents: s.documents,\n symbols: s.symbols,\n indexSizeBytes: s.indexSizeBytes,\n },\n findings: {\n deadSymbols: trueDeadCount,\n deadLoc: trueDeadLoc,\n isolatedSymbols: trueIsolatedCount,\n isolatedLoc: isolatedResult\n .filter((s) => !isEntryPoint(s.relativePath))\n .reduce((sum, s) => sum + s.loc, 0),\n cycles: cycleResult.length,\n similarPairs: trueSimilarCount,\n extractionCandidates: extractResult.length,\n wrappers: wrapperResult.length,\n passthroughs: passthroughResult.length,\n staleTypes: trueStaleCount,\n driftedFiles: trueDriftCount,\n complexityHotspotCount: complexResult.length,\n testCoveragePercent: testResult.percent,\n },\n actions,\n topComplexity: complexResult.slice(0, 5).map((r) => ({\n symbol: r.shortName,\n score: r.score,\n })),\n };\n}\n"],"mappings":"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","names":["s"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/outline.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { OutlineNode } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Build a tree-structured outline of symbols in a file,\n * using the enclosing_symbol field to establish parent-child relationships.\n */\nexport function outline(db: ScipDatabase, filePattern: string): OutlineNode[] {\n const rows = db.all<{\n symbol: string;\n enclosing_symbol: string | null;\n start_line: number;\n end_line: number;\n }>(\n `SELECT gs.symbol, gs.enclosing_symbol, der.start_line, der.end_line\n FROM defn_enclosing_ranges der\n JOIN global_symbols gs ON der.symbol_id = gs.id\n JOIN documents d ON der.document_id = d.id\n WHERE d.relative_path LIKE ?\n ${db.symbolNoise}\n ORDER BY der.start_line`,\n `%${filePattern}%`,\n );\n\n // Build a map of symbol -> node\n const nodeMap = new Map<string, OutlineNode>();\n const roots: OutlineNode[] = [];\n\n for (const r of rows) {\n const node: OutlineNode = {\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n startLine: r.start_line,\n endLine: r.end_line,\n children: [],\n };\n nodeMap.set(r.symbol, node);\n }\n\n // Wire up parent-child via enclosing_symbol\n for (const r of rows) {\n const node = nodeMap.get(r.symbol)!;\n if (r.enclosing_symbol && nodeMap.has(r.enclosing_symbol)) {\n nodeMap.get(r.enclosing_symbol)!.children.push(node);\n } else {\n roots.push(node);\n }\n }\n\n return roots;\n}\n"],"mappings":";;;;;AAQO,SAAS,QAAQ,IAAkB,aAAoC;AAC5E,QAAM,OAAO,GAAG;AAAA,IAMd;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,GAAG,WAAW;AAAA;AAAA,IAElB,IAAI,WAAW;AAAA,EACjB;AAGA,QAAM,UAAU,oBAAI,IAAyB;AAC7C,QAAM,QAAuB,CAAC;AAE9B,aAAW,KAAK,MAAM;AACpB,UAAM,OAAoB;AAAA,MACxB,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,WAAW,EAAE;AAAA,MACb,SAAS,EAAE;AAAA,MACX,UAAU,CAAC;AAAA,IACb;AACA,YAAQ,IAAI,EAAE,QAAQ,IAAI;AAAA,EAC5B;AAGA,aAAW,KAAK,MAAM;AACpB,UAAM,OAAO,QAAQ,IAAI,EAAE,MAAM;AACjC,QAAI,EAAE,oBAAoB,QAAQ,IAAI,EAAE,gBAAgB,GAAG;AACzD,cAAQ,IAAI,EAAE,gBAAgB,EAAG,SAAS,KAAK,IAAI;AAAA,IACrD,OAAO;AACL,YAAM,KAAK,IAAI;AAAA,IACjB;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/stats.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { StatsResult } from '../types.js';\n\nexport function stats(db: ScipDatabase): StatsResult {\n const documents = db.get<{ c: number }>('SELECT COUNT(*) as c FROM documents')!.c;\n const symbols = db.get<{ c: number }>('SELECT COUNT(*) as c FROM global_symbols')!.c;\n const definitions = db.get<{ c: number }>(\n 'SELECT COUNT(*) as c FROM mentions WHERE role = 1',\n )!.c;\n const references = db.get<{ c: number }>(\n 'SELECT COUNT(*) as c FROM mentions WHERE role != 1',\n )!.c;\n\n return {\n documents,\n symbols,\n definitions,\n references,\n indexSizeBytes: db.sizeBytes(),\n lastBuilt: db.lastModified(),\n };\n}\n"],"mappings":";AAGO,SAAS,MAAM,IAA+B;AACnD,QAAM,YAAY,GAAG,IAAmB,qCAAqC,EAAG;AAChF,QAAM,UAAU,GAAG,IAAmB,0CAA0C,EAAG;AACnF,QAAM,cAAc,GAAG;AAAA,IACrB;AAAA,EACF,EAAG;AACH,QAAM,aAAa,GAAG;AAAA,IACpB;AAAA,EACF,EAAG;AAEH,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB,GAAG,UAAU;AAAA,IAC7B,WAAW,GAAG,aAAa;AAAA,EAC7B;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/similar-chains.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { buildFileDepGraph } from '../query-support.js';\nimport type { SimilarChainResult } from '../types.js';\n\n/**\n * Find end-to-end dependency flows that are structurally similar\n * but diverge at a few points — indicating duplicated pipelines\n * that could be consolidated.\n *\n * Uses infrastructure-filtered chain comparison:\n *\n * 1. Build all dependency chains via DFS\n * 2. Compute node frequency across all chains\n * 3. Filter out infrastructure nodes (appearing in >50% of chains)\n * 4. Compare the filtered chains — what's left is the unique pipeline\n * 5. Edit distance on filtered chains finds real structural duplicates\n *\n * Two chains that both pass through db.ts → types.ts is meaningless.\n * Two chains that both pass through userValidation → userRepo → emailService\n * after filtering is a strong consolidation signal.\n */\nexport function similarChains(\n db: ScipDatabase,\n opts: {\n minSimilarity?: number;\n limit?: number;\n scope?: string;\n minChainLength?: number;\n maxChainLength?: number;\n } = {},\n): SimilarChainResult[] {\n const {\n minSimilarity = 0.5,\n limit = 15,\n scope,\n minChainLength = 3,\n maxChainLength = 8,\n } = opts;\n\n const graph = buildFileDepGraph(db, scope);\n const rawChains = generateChains(graph, minChainLength, maxChainLength);\n\n if (rawChains.length === 0) return [];\n\n // Compute node frequency to identify infrastructure.\n // Count both general frequency (appears anywhere in chain) and\n // tail frequency (appears as one of the last 2 nodes). Shared\n // tails like \"→ db.ts → types.ts\" are infrastructure even if\n // they don't appear in >50% of chains overall.\n const nodeFreq = new Map<string, number>();\n const tailFreq = new Map<string, number>();\n for (const chain of rawChains) {\n const seen = new Set<string>();\n for (const node of chain) {\n if (!seen.has(node)) {\n nodeFreq.set(node, (nodeFreq.get(node) ?? 0) + 1);\n seen.add(node);\n }\n }\n // Track tail nodes (last 2)\n for (let t = Math.max(0, chain.length - 2); t < chain.length; t++) {\n tailFreq.set(chain[t]!, (tailFreq.get(chain[t]!) ?? 0) + 1);\n }\n }\n\n // Infrastructure: nodes in >40% of chains OR tail nodes in >30% of chains\n const infraThreshold = rawChains.length * 0.4;\n const tailThreshold = rawChains.length * 0.3;\n const infraNodes = new Set<string>();\n for (const [node, freq] of nodeFreq) {\n if (freq > infraThreshold) infraNodes.add(node);\n }\n for (const [node, freq] of tailFreq) {\n if (freq > tailThreshold) infraNodes.add(node);\n }\n\n // Also treat structural role files as infrastructure — entry points,\n // barrels, and orchestrators are not meaningful pipeline nodes.\n const structuralNames = ['index.ts', 'index.js', 'cli.ts', 'main.ts', 'health.ts', 'health.js'];\n for (const node of nodeFreq.keys()) {\n const basename = node.split('/').pop() ?? '';\n if (structuralNames.includes(basename)) infraNodes.add(node);\n }\n\n // Filter chains: remove infrastructure nodes, keep the \"unique pipeline\"\n const filteredChains: { original: string[]; filtered: string[] }[] = [];\n for (const chain of rawChains) {\n const filtered = chain.filter((n) => !infraNodes.has(n));\n // Only keep chains that have at least 3 non-infrastructure nodes.\n // Chains with 1-2 unique nodes are just \"query → infra\" which is\n // the expected pattern, not a consolidation opportunity.\n if (filtered.length >= 3) {\n filteredChains.push({ original: chain, filtered });\n }\n }\n\n if (filteredChains.length < 2) return [];\n\n // Pairwise comparison on filtered chains\n const results: SimilarChainResult[] = [];\n\n for (let i = 0; i < filteredChains.length; i++) {\n for (let j = i + 1; j < filteredChains.length; j++) {\n const a = filteredChains[i]!;\n const b = filteredChains[j]!;\n\n // Quick reject: filtered chains must share at least one non-infra node\n const setA = new Set(a.filtered);\n let hasShared = false;\n for (const node of b.filtered) {\n if (setA.has(node)) { hasShared = true; break; }\n }\n if (!hasShared) continue;\n\n // Edit distance on the FILTERED chains (infrastructure stripped)\n const { distance, ops } = editDistance(a.filtered, b.filtered);\n const maxLen = Math.max(a.filtered.length, b.filtered.length);\n if (maxLen === 0) continue;\n\n const similarity = 1 - distance / maxLen;\n if (similarity < minSimilarity) continue;\n if (distance === 0) continue; // identical filtered chains = not interesting\n\n // Divergence points from the filtered comparison\n const divergencePoints = ops\n .filter((op) => op.type === 'substitute')\n .map((op) => ({\n index: op.indexA,\n nodeA: a.filtered[op.indexA]!,\n nodeB: b.filtered[op.indexB]!,\n }));\n\n if (divergencePoints.length === 0) continue;\n\n // Require at least 2 matching (non-divergent) filtered nodes.\n // A chain pair with 1 match and 1 divergence is just \"two things\n // that share one dependency\" — not a duplicated pipeline.\n const matchCount = ops.filter((op) => op.type === 'match').length;\n if (matchCount < 2) continue;\n\n // Report using original chains for context, but similarity is from filtered\n const commonPrefix = getCommonPrefix(a.original, b.original);\n const commonSuffix = getCommonSuffix(a.original, b.original);\n\n results.push({\n chainA: a.original,\n chainB: b.original,\n similarity,\n editDistance: distance,\n divergencePoints,\n commonPrefix,\n commonSuffix,\n });\n }\n\n if (results.length > limit * 10) break;\n }\n\n // Sort by similarity desc, then fewest divergence points\n results.sort((a, b) => {\n if (Math.abs(b.similarity - a.similarity) > 0.01) return b.similarity - a.similarity;\n return a.divergencePoints.length - b.divergencePoints.length;\n });\n\n // Deduplicate sub-chains\n const deduped: SimilarChainResult[] = [];\n for (const r of results) {\n const isDuplicate = deduped.some(\n (existing) =>\n isSubChain(r.chainA, existing.chainA) && isSubChain(r.chainB, existing.chainB),\n );\n if (!isDuplicate) deduped.push(r);\n if (deduped.length >= limit) break;\n }\n\n return deduped;\n}\n\n// ── Chain generation ───────────────────────────────────────\n\nfunction generateChains(\n graph: Map<string, Set<string>>,\n minLen: number,\n maxLen: number,\n): string[][] {\n const chains: string[][] = [];\n const maxChains = 500;\n\n for (const startNode of graph.keys()) {\n if (chains.length >= maxChains) break;\n dfsChains(graph, startNode, [startNode], new Set([startNode]), minLen, maxLen, chains, maxChains);\n }\n\n return chains;\n}\n\nfunction dfsChains(\n graph: Map<string, Set<string>>,\n node: string,\n path: string[],\n visited: Set<string>,\n minLen: number,\n maxLen: number,\n results: string[][],\n maxResults: number,\n): void {\n if (results.length >= maxResults) return;\n if (path.length >= maxLen) {\n if (path.length >= minLen) results.push([...path]);\n return;\n }\n\n const neighbors = graph.get(node);\n if (!neighbors || neighbors.size === 0) {\n if (path.length >= minLen) results.push([...path]);\n return;\n }\n\n let extended = false;\n for (const next of neighbors) {\n if (visited.has(next)) continue;\n visited.add(next);\n path.push(next);\n dfsChains(graph, next, path, visited, minLen, maxLen, results, maxResults);\n path.pop();\n visited.delete(next);\n extended = true;\n if (results.length >= maxResults) return;\n }\n\n if (!extended && path.length >= minLen) {\n results.push([...path]);\n }\n}\n\n// ── Edit distance ──────────────────────────────────────────\n\ninterface EditOp {\n type: 'match' | 'substitute' | 'insert' | 'delete';\n indexA: number;\n indexB: number;\n}\n\nfunction editDistance(a: string[], b: string[]): { distance: number; ops: EditOp[] } {\n const m = a.length;\n const n = b.length;\n\n const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));\n for (let i = 0; i <= m; i++) dp[i]![0] = i;\n for (let j = 0; j <= n; j++) dp[0]![j] = j;\n\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n if (a[i - 1] === b[j - 1]) {\n dp[i]![j] = dp[i - 1]![j - 1]!;\n } else {\n dp[i]![j] = 1 + Math.min(\n dp[i - 1]![j]!,\n dp[i]![j - 1]!,\n dp[i - 1]![j - 1]!,\n );\n }\n }\n }\n\n const ops: EditOp[] = [];\n let i = m, j = n;\n while (i > 0 || j > 0) {\n if (i > 0 && j > 0 && a[i - 1] === b[j - 1]) {\n ops.unshift({ type: 'match', indexA: i - 1, indexB: j - 1 });\n i--; j--;\n } else if (i > 0 && j > 0 && dp[i]![j] === dp[i - 1]![j - 1]! + 1) {\n ops.unshift({ type: 'substitute', indexA: i - 1, indexB: j - 1 });\n i--; j--;\n } else if (j > 0 && dp[i]![j] === dp[i]![j - 1]! + 1) {\n ops.unshift({ type: 'insert', indexA: i, indexB: j - 1 });\n j--;\n } else {\n ops.unshift({ type: 'delete', indexA: i - 1, indexB: j });\n i--;\n }\n }\n\n return { distance: dp[m]![n]!, ops };\n}\n\n// ── Utility ────────────────────────────────────────────────\n\nfunction getCommonPrefix(a: string[], b: string[]): string[] {\n const prefix: string[] = [];\n for (let i = 0; i < Math.min(a.length, b.length); i++) {\n if (a[i] === b[i]) prefix.push(a[i]!);\n else break;\n }\n return prefix;\n}\n\nfunction getCommonSuffix(a: string[], b: string[]): string[] {\n const suffix: string[] = [];\n let ai = a.length - 1;\n let bi = b.length - 1;\n while (ai >= 0 && bi >= 0 && a[ai] === b[bi]) {\n suffix.unshift(a[ai]!);\n ai--; bi--;\n }\n return suffix;\n}\n\nfunction isSubChain(sub: string[], full: string[]): boolean {\n if (sub.length > full.length) return false;\n const fullStr = full.join('→');\n const subStr = sub.join('→');\n return fullStr.includes(subStr);\n}\n"],"mappings":"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","names":["i","j"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/hotspots.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { HotspotResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find the most-referenced symbols in the codebase — the choke points\n * where changes have the widest blast radius.\n */\nexport function hotspots(\n db: ScipDatabase,\n opts: { limit?: number; scope?: string } = {},\n): HotspotResult[] {\n const { limit = 30, scope } = opts;\n\n const scopeFilter = scope ? `AND def_d.relative_path LIKE '%${scope}%'` : '';\n\n const rows = db.all<{\n symbol: string;\n ref_count: number;\n file_count: number;\n defined_in: string;\n }>(\n `SELECT\n gs.symbol,\n COUNT(*) AS ref_count,\n COUNT(DISTINCT ref_d.id) AS file_count,\n def_d.relative_path AS defined_in\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents def_d ON der.document_id = def_d.id\n WHERE m.role != 1\n ${db.pathExclusionsFor('def_d')}\n ${db.symbolNoiseFor('gs')}\n ${scopeFilter}\n GROUP BY gs.id\n ORDER BY ref_count DESC\n LIMIT ?`,\n limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.defined_in))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n refCount: r.ref_count,\n fileCount: r.file_count,\n definedIn: r.defined_in,\n }));\n}\n"],"mappings":";;;;;AAQO,SAAS,SACd,IACA,OAA2C,CAAC,GAC3B;AACjB,QAAM,EAAE,QAAQ,IAAI,MAAM,IAAI;AAE9B,QAAM,cAAc,QAAQ,kCAAkC,KAAK,OAAO;AAE1E,QAAM,OAAO,GAAG;AAAA,IAMd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAYI,GAAG,kBAAkB,OAAO,CAAC;AAAA,QAC7B,GAAG,eAAe,IAAI,CAAC;AAAA,QACvB,WAAW;AAAA;AAAA;AAAA;AAAA,IAIf;AAAA,EACF;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,UAAU,CAAC,EACzC,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,UAAU,EAAE;AAAA,IACZ,WAAW,EAAE;AAAA,IACb,WAAW,EAAE;AAAA,EACf,EAAE;AACN;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/drift.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ScipDatabase } from '../db.js';\nimport { buildFileDepGraph } from '../query-support.js';\nimport type { DriftResult, DriftSummary } from '../types.js';\n\n/**\n * Detect structural drift using the reference graph, not just import patterns.\n *\n * Three types of drift, each detecting a real problem:\n *\n * 1. **Unused imports** — file depends on a module but never references\n * any of its symbols. Dead dependency, safe to remove.\n *\n * 2. **Layer violations** — file imports from a directory it shouldn't\n * based on the project's directory structure (e.g., a query importing\n * from reindex, a helper importing from CLI). Architectural decay.\n *\n * 3. **Pattern deviations** — file imports something no sibling does,\n * suggesting it's reaching outside its expected scope. Only flagged\n * when the file is the ONLY one in its directory with that dep.\n */\nexport function drift(\n db: ScipDatabase,\n opts?: { scope?: string; minDeviation?: number },\n): DriftSummary {\n const { scope } = opts ?? {};\n\n // Build file dep graph (which files depend on which)\n const depGraph = buildFileDepGraph(db, scope);\n\n // Build symbol-level reference graph: for each file, which other files'\n // symbols does it actually reference?\n const symbolRefs = buildSymbolRefGraph(db, scope);\n\n const results: DriftResult[] = [];\n\n // ── Angle 1: Unused imports ──────────────────────────────\n // File depends on module B (via dep graph) but never references\n // any symbol defined in B (via symbol ref graph).\n for (const [file, deps] of depGraph) {\n if (isStructuralRole(path.basename(file))) continue;\n\n const referencedFiles = symbolRefs.get(file) ?? new Set<string>();\n\n for (const dep of deps) {\n if (!referencedFiles.has(dep)) {\n // This file \"depends on\" dep but never references its symbols.\n // This can happen when the dep is imported for types only\n // (which don't appear in the mention graph). Skip type-heavy deps.\n if (isLikelyTypeOnlyDep(dep)) continue;\n\n results.push({\n file,\n kind: 'unused-import',\n description: `Depends on ${dep} but references none of its symbols`,\n dep,\n });\n }\n }\n }\n\n // ── Angle 2: Layer violations ────────────────────────────\n // Detect when a file imports from a directory that represents\n // a different architectural layer. We infer layers from the\n // directory structure: files in the same top-level dir are peers,\n // files in different top-level dirs crossing inward is a violation.\n const layerRules = inferLayerRules(depGraph);\n\n for (const [file, deps] of depGraph) {\n if (isStructuralRole(path.basename(file))) continue;\n\n const fileLayer = getTopDir(file);\n for (const dep of deps) {\n const depLayer = getTopDir(dep);\n if (fileLayer === depLayer) continue; // same layer, fine\n\n const violation = layerRules.get(`${fileLayer}->${depLayer}`);\n if (violation === 'violation') {\n results.push({\n file,\n kind: 'layer-violation',\n description: `Imports from ${depLayer}/ (${dep}) — may cross architectural boundary`,\n dep,\n detail: `${fileLayer}/ should not depend on ${depLayer}/`,\n });\n }\n }\n }\n\n // ── Angle 3: Unique deps (pattern deviation) ─────────────\n // If a file is the ONLY one in its directory that depends on a\n // particular module, that dependency is unusual and worth flagging.\n const dirToFiles = new Map<string, string[]>();\n for (const file of depGraph.keys()) {\n const dir = path.dirname(file);\n if (!dirToFiles.has(dir)) dirToFiles.set(dir, []);\n dirToFiles.get(dir)!.push(file);\n }\n\n for (const [dir, files] of dirToFiles) {\n if (files.length < 3) continue;\n\n // Count dep frequency across siblings\n const depFreq = new Map<string, number>();\n for (const file of files) {\n if (isStructuralRole(path.basename(file))) continue;\n for (const dep of depGraph.get(file) ?? []) {\n depFreq.set(dep, (depFreq.get(dep) ?? 0) + 1);\n }\n }\n\n for (const file of files) {\n if (isStructuralRole(path.basename(file))) continue;\n for (const dep of depGraph.get(file) ?? []) {\n if ((depFreq.get(dep) ?? 0) === 1) {\n // This file is the only one in its dir that depends on this module\n // Skip if dep is in the same directory (sibling imports are normal)\n if (path.dirname(dep) === dir) continue;\n\n results.push({\n file,\n kind: 'pattern-deviation',\n description: `Only file in ${dir}/ that depends on ${dep}`,\n dep,\n });\n }\n }\n }\n }\n\n return {\n results,\n unusedImports: results.filter((r) => r.kind === 'unused-import').length,\n layerViolations: results.filter((r) => r.kind === 'layer-violation').length,\n patternDeviations: results.filter((r) => r.kind === 'pattern-deviation').length,\n };\n}\n\n// ── Helpers ────────────────────────────────────────────────\n\n/**\n * Build a map of file → set of files whose symbols it references.\n * This is more precise than the dep graph because it uses actual\n * symbol mentions, not just import statements.\n */\nfunction buildSymbolRefGraph(\n db: ScipDatabase,\n scope?: string,\n): Map<string, Set<string>> {\n const scopeFilter = scope ? `AND d1.relative_path LIKE '%${scope}%'` : '';\n\n const rows = db.all<{ from_file: string; to_file: string }>(\n `SELECT DISTINCT d1.relative_path AS from_file, d2.relative_path AS to_file\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d1 ON c.document_id = d1.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d2 ON der.document_id = d2.id\n WHERE d1.id != d2.id\n AND m.role != 1\n ${db.pathExclusionsFor('d1', 'd2')}\n ${scopeFilter}`,\n );\n\n const graph = new Map<string, Set<string>>();\n for (const r of rows) {\n if (db.isIgnored(r.from_file) || db.isIgnored(r.to_file)) continue;\n if (!graph.has(r.from_file)) graph.set(r.from_file, new Set());\n graph.get(r.from_file)!.add(r.to_file);\n }\n return graph;\n}\n\n/**\n * Infer layer boundary rules from the dependency graph.\n * If directory A never depends on directory B across the entire codebase,\n * then a new A→B dependency is a violation.\n */\nfunction inferLayerRules(\n depGraph: Map<string, Set<string>>,\n): Map<string, 'ok' | 'violation'> {\n const layerEdges = new Map<string, number>();\n const layerSet = new Set<string>();\n\n for (const [file, deps] of depGraph) {\n const fromLayer = getTopDir(file);\n layerSet.add(fromLayer);\n for (const dep of deps) {\n const toLayer = getTopDir(dep);\n if (fromLayer === toLayer) continue;\n layerSet.add(toLayer);\n const key = `${fromLayer}->${toLayer}`;\n layerEdges.set(key, (layerEdges.get(key) ?? 0) + 1);\n }\n }\n\n // An edge that appears only 1-2 times across the whole codebase\n // is likely a violation (anomalous cross-layer dep).\n // Edges that appear many times are established patterns.\n const rules = new Map<string, 'ok' | 'violation'>();\n for (const [edge, count] of layerEdges) {\n rules.set(edge, count <= 2 ? 'violation' : 'ok');\n }\n\n return rules;\n}\n\nfunction getTopDir(filePath: string): string {\n const parts = filePath.split('/');\n return parts[0] ?? filePath;\n}\n\nfunction isLikelyTypeOnlyDep(dep: string): boolean {\n return dep.includes('types') || dep.endsWith('.d.ts');\n}\n\nfunction isStructuralRole(basename: string): boolean {\n if (basename === 'index.ts' || basename === 'index.js') return true;\n if (basename === 'cli.ts' || basename === 'main.ts' || basename === 'main.rs') return true;\n if (basename.includes('worker.') || basename.includes('postinstall.')) return true;\n if (basename === 'health.ts' || basename === 'health.js') return true;\n return false;\n}\n"],"mappings":"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","names":[]}
@@ -1,55 +0,0 @@
1
- import {
2
- findFirstSymbolMatch,
3
- getCalleeRowsForSymbol
4
- } from "./chunk-ZOGY2V3N.js";
5
- import {
6
- shortenSymbol
7
- } from "./chunk-QOV2R2WT.js";
8
-
9
- // src/queries/call-graph.ts
10
- function callGraph(db, symbolPattern) {
11
- const target = findFirstSymbolMatch(db, symbolPattern);
12
- if (!target) return null;
13
- const callerRows = db.all(
14
- `SELECT DISTINCT caller_gs.symbol AS caller_symbol, caller_d.relative_path AS caller_file
15
- FROM mentions m
16
- JOIN chunks c ON m.chunk_id = c.id
17
- JOIN documents ref_d ON c.document_id = ref_d.id
18
- -- Find the enclosing symbol for where the reference appears
19
- JOIN defn_enclosing_ranges caller_der
20
- ON caller_der.document_id = ref_d.id
21
- AND c.start_line >= caller_der.start_line
22
- AND c.end_line <= caller_der.end_line
23
- JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id
24
- JOIN documents caller_d ON caller_der.document_id = caller_d.id
25
- WHERE m.symbol_id = ?
26
- AND m.role != 1
27
- AND caller_gs.id != ?
28
- ${db.symbolNoiseFor("caller_gs")}
29
- ${db.pathExclusionsFor("caller_d")}
30
- ORDER BY caller_d.relative_path
31
- LIMIT 50`,
32
- target.symbolId,
33
- target.symbolId
34
- );
35
- const calleeRows = getCalleeRowsForSymbol(db, target, { limit: 50 });
36
- return {
37
- symbol: target.symbol,
38
- shortName: shortenSymbol(target.symbol),
39
- callers: callerRows.filter((r) => !db.isIgnored(r.caller_file)).map((r) => ({
40
- symbol: r.caller_symbol,
41
- shortName: shortenSymbol(r.caller_symbol),
42
- file: r.caller_file
43
- })),
44
- callees: calleeRows.map((r) => ({
45
- symbol: r.symbol,
46
- shortName: shortenSymbol(r.symbol),
47
- file: r.file
48
- }))
49
- };
50
- }
51
-
52
- export {
53
- callGraph
54
- };
55
- //# sourceMappingURL=chunk-7UCKSQRS.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/call-graph.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch, getCalleeRowsForSymbol } from '../query-support.js';\nimport type { CallGraphResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Build a call graph for a symbol: who calls it (incoming) and\n * what it calls (outgoing).\n *\n * Incoming: other symbols whose definition ranges contain a reference to this symbol.\n * Outgoing: symbols referenced within this symbol's definition range.\n */\nexport function callGraph(db: ScipDatabase, symbolPattern: string): CallGraphResult | null {\n // Find the target symbol and its definition range\n const target = findFirstSymbolMatch(db, symbolPattern);\n\n if (!target) return null;\n\n // CALLERS: symbols whose definition ranges contain a reference to our target.\n // Find chunks that reference our symbol, then find which symbol's definition encloses that chunk.\n const callerRows = db.all<{\n caller_symbol: string;\n caller_file: string;\n }>(\n `SELECT DISTINCT caller_gs.symbol AS caller_symbol, caller_d.relative_path AS caller_file\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n -- Find the enclosing symbol for where the reference appears\n JOIN defn_enclosing_ranges caller_der\n ON caller_der.document_id = ref_d.id\n AND c.start_line >= caller_der.start_line\n AND c.end_line <= caller_der.end_line\n JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id\n JOIN documents caller_d ON caller_der.document_id = caller_d.id\n WHERE m.symbol_id = ?\n AND m.role != 1\n AND caller_gs.id != ?\n ${db.symbolNoiseFor('caller_gs')}\n ${db.pathExclusionsFor('caller_d')}\n ORDER BY caller_d.relative_path\n LIMIT 50`,\n target.symbolId, target.symbolId,\n );\n\n // CALLEES: symbols referenced within our target's definition range.\n const calleeRows = getCalleeRowsForSymbol(db, target, { limit: 50 });\n\n return {\n symbol: target.symbol,\n shortName: shortenSymbol(target.symbol),\n callers: callerRows\n .filter((r) => !db.isIgnored(r.caller_file))\n .map((r) => ({\n symbol: r.caller_symbol,\n shortName: shortenSymbol(r.caller_symbol),\n file: r.caller_file,\n })),\n callees: calleeRows\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n file: r.file,\n })),\n };\n}\n"],"mappings":";;;;;;;;;AAYO,SAAS,UAAU,IAAkB,eAA+C;AAEzF,QAAM,SAAS,qBAAqB,IAAI,aAAa;AAErD,MAAI,CAAC,OAAQ,QAAO;AAIpB,QAAM,aAAa,GAAG;AAAA,IAIpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAcI,GAAG,eAAe,WAAW,CAAC;AAAA,QAC9B,GAAG,kBAAkB,UAAU,CAAC;AAAA;AAAA;AAAA,IAGpC,OAAO;AAAA,IAAU,OAAO;AAAA,EAC1B;AAGA,QAAM,aAAa,uBAAuB,IAAI,QAAQ,EAAE,OAAO,GAAG,CAAC;AAEnE,SAAO;AAAA,IACL,QAAQ,OAAO;AAAA,IACf,WAAW,cAAc,OAAO,MAAM;AAAA,IACtC,SAAS,WACN,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,WAAW,CAAC,EAC1C,IAAI,CAAC,OAAO;AAAA,MACX,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,aAAa;AAAA,MACxC,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,IACJ,SAAS,WACN,IAAI,CAAC,OAAO;AAAA,MACX,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,EACN;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/stale-abstractions.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { testFileExclusionSql } from '../query-support.js';\nimport type { StaleAbstraction } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find stale abstractions: type-level symbols (classes, interfaces, type\n * aliases) that have 0 or 1 cross-file consumers.\n *\n * A type that only one file uses is over-abstracted — it was designed\n * for reuse that never materialized. Large single-consumer types are\n * the strongest signal of wasted abstraction.\n */\nexport function staleAbstractions(\n db: ScipDatabase,\n opts?: { scope?: string; minLoc?: number; limit?: number },\n): StaleAbstraction[] {\n const { scope, minLoc = 3, limit = 30 } = opts ?? {};\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n const rows = db.all<{\n symbol: string;\n file: string;\n start_line: number;\n end_line: number;\n loc: number;\n consumers: number;\n }>(\n `SELECT * FROM (\n SELECT\n gs.symbol,\n d.relative_path AS file,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc,\n (SELECT COUNT(DISTINCT ref_c.document_id)\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n WHERE ref_m.symbol_id = gs.id\n AND ref_m.role != 1\n AND ref_c.document_id != der.document_id\n ) AS consumers\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND ${testFileExclusionSql('d')}\n ${db.symbolNoiseFor('gs')}\n -- Top-level type symbols: ends with # but does not contain nested #\n AND gs.symbol LIKE '%#'\n AND gs.symbol NOT LIKE '%#%#%'\n AND (der.end_line - der.start_line + 1) >= ?\n ${scopeFilter}\n ) WHERE consumers <= 1\n ORDER BY loc DESC\n LIMIT ?`,\n minLoc, limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.file))\n // Exclude types defined in dedicated type files (types.ts, types/, etc.)\n // These are intentional public API types, not premature abstractions.\n .filter((r) => {\n const basename = r.file.split('/').pop() ?? '';\n const isTypeFile = basename.includes('types') || r.file.includes('/types/');\n // Types in type files with 1 consumer are normal API types — skip them.\n // Types in type files with 0 consumers are genuinely unused — keep them.\n if (isTypeFile && r.consumers > 0) return false;\n return true;\n })\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n file: r.file,\n startLine: r.start_line,\n endLine: r.end_line,\n loc: r.loc,\n consumers: r.consumers,\n }));\n}\n"],"mappings":";;;;;;;;AAaO,SAAS,kBACd,IACA,MACoB;AACpB,QAAM,EAAE,OAAO,SAAS,GAAG,QAAQ,GAAG,IAAI,QAAQ,CAAC;AACnD,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAEtE,QAAM,OAAO,GAAG;AAAA,IAQd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAkBM,GAAG,kBAAkB,GAAG,CAAC;AAAA,cACrB,qBAAqB,GAAG,CAAC;AAAA,UAC7B,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,UAKvB,WAAW;AAAA;AAAA;AAAA;AAAA,IAIjB;AAAA,IAAQ;AAAA,EACV;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EAGnC,OAAO,CAAC,MAAM;AACb,UAAM,WAAW,EAAE,KAAK,MAAM,GAAG,EAAE,IAAI,KAAK;AAC5C,UAAM,aAAa,SAAS,SAAS,OAAO,KAAK,EAAE,KAAK,SAAS,SAAS;AAG1E,QAAI,cAAc,EAAE,YAAY,EAAG,QAAO;AAC1C,WAAO;AAAA,EACT,CAAC,EACA,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,MAAM,EAAE;AAAA,IACR,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,KAAK,EAAE;AAAA,IACP,WAAW,EAAE;AAAA,EACf,EAAE;AACN;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/surface.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { SurfaceResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/** Public API surface: what symbols do external consumers actually use from this module? */\nexport function surface(db: ScipDatabase, modulePattern: string): SurfaceResult[] {\n const rows = db.all<{\n relative_path: string;\n symbol: string;\n }>(\n `SELECT DISTINCT d1.relative_path, gs.symbol\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d1 ON c.document_id = d1.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d2 ON der.document_id = d2.id\n WHERE d2.relative_path LIKE ?\n AND d1.relative_path NOT LIKE ?\n AND ${db.localSymbolPredicate}\n AND m.role != 1\n ORDER BY d1.relative_path`,\n `%${modulePattern}%`,\n `%${modulePattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n consumer: r.relative_path,\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n }));\n}\n"],"mappings":";;;;;AAKO,SAAS,QAAQ,IAAkB,eAAwC;AAChF,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YASQ,GAAG,oBAAoB;AAAA;AAAA;AAAA,IAG/B,IAAI,aAAa;AAAA,IACjB,IAAI,aAAa;AAAA,EACnB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,UAAU,EAAE;AAAA,IACZ,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,EACnC,EAAE;AACN;","names":[]}
@@ -1,65 +0,0 @@
1
- import {
2
- findFirstSymbolMatch
3
- } from "./chunk-ZOGY2V3N.js";
4
- import {
5
- shortenSymbol
6
- } from "./chunk-QOV2R2WT.js";
7
-
8
- // src/queries/affected.ts
9
- function affected(db, symbolPattern, opts = {}) {
10
- const { maxDepth = 5, scope } = opts;
11
- const target = findFirstSymbolMatch(db, symbolPattern);
12
- if (!target) return [];
13
- const scopeFilter = scope ? `AND enc_d.relative_path LIKE '%${scope}%'` : "";
14
- const results = [];
15
- const visited = /* @__PURE__ */ new Set([target.symbolId]);
16
- let frontier = /* @__PURE__ */ new Set([target.symbolId]);
17
- for (let depth = 1; depth <= maxDepth; depth++) {
18
- if (frontier.size === 0) break;
19
- const placeholders = [...frontier].map(() => "?").join(",");
20
- const nextFrontier = /* @__PURE__ */ new Set();
21
- const rows = db.all(
22
- `SELECT DISTINCT
23
- enc_gs.id AS symbol_id,
24
- enc_gs.symbol AS symbol,
25
- enc_d.relative_path AS relative_path
26
- FROM mentions m
27
- JOIN chunks c ON m.chunk_id = c.id
28
- JOIN documents ref_d ON c.document_id = ref_d.id
29
- JOIN defn_enclosing_ranges enc_der
30
- ON enc_der.document_id = ref_d.id
31
- AND c.start_line >= enc_der.start_line
32
- AND c.end_line <= enc_der.end_line
33
- JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
34
- JOIN documents enc_d ON enc_der.document_id = enc_d.id
35
- WHERE m.symbol_id IN (${placeholders})
36
- AND m.role != 1
37
- AND enc_gs.id NOT IN (${placeholders})
38
- ${db.symbolNoiseFor("enc_gs")}
39
- ${db.pathExclusionsFor("enc_d")}
40
- ${scopeFilter}`,
41
- ...[...frontier],
42
- ...[...frontier]
43
- );
44
- for (const row of rows) {
45
- if (visited.has(row.symbol_id)) continue;
46
- if (db.isIgnored(row.relative_path)) continue;
47
- visited.add(row.symbol_id);
48
- nextFrontier.add(row.symbol_id);
49
- results.push({
50
- symbol: row.symbol,
51
- shortName: shortenSymbol(row.symbol),
52
- file: row.relative_path,
53
- depth
54
- });
55
- }
56
- frontier = nextFrontier;
57
- }
58
- results.sort((a, b) => a.depth - b.depth || a.file.localeCompare(b.file));
59
- return results;
60
- }
61
-
62
- export {
63
- affected
64
- };
65
- //# sourceMappingURL=chunk-D567NFIF.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/affected.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch } from '../query-support.js';\nimport type { AffectedResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Full transitive closure of symbols that could break if a given symbol changes.\n * BFS from the target through the mention graph: depth 1 = direct consumers,\n * depth 2 = consumers of consumers, etc.\n */\nexport function affected(\n db: ScipDatabase,\n symbolPattern: string,\n opts: { maxDepth?: number; scope?: string } = {},\n): AffectedResult[] {\n const { maxDepth = 5, scope } = opts;\n\n const target = findFirstSymbolMatch(db, symbolPattern);\n if (!target) return [];\n\n const scopeFilter = scope\n ? `AND enc_d.relative_path LIKE '%${scope}%'`\n : '';\n\n const results: AffectedResult[] = [];\n const visited = new Set<number>([target.symbolId]);\n let frontier = new Set<number>([target.symbolId]);\n\n for (let depth = 1; depth <= maxDepth; depth++) {\n if (frontier.size === 0) break;\n\n const placeholders = [...frontier].map(() => '?').join(',');\n const nextFrontier = new Set<number>();\n\n // For each symbol in the frontier, find enclosing symbols whose\n // definition ranges contain a reference (role=0) to that frontier symbol.\n const rows = db.all<{\n symbol_id: number;\n symbol: string;\n relative_path: string;\n }>(\n `SELECT DISTINCT\n enc_gs.id AS symbol_id,\n enc_gs.symbol AS symbol,\n enc_d.relative_path AS relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n JOIN defn_enclosing_ranges enc_der\n ON enc_der.document_id = ref_d.id\n AND c.start_line >= enc_der.start_line\n AND c.end_line <= enc_der.end_line\n JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id\n JOIN documents enc_d ON enc_der.document_id = enc_d.id\n WHERE m.symbol_id IN (${placeholders})\n AND m.role != 1\n AND enc_gs.id NOT IN (${placeholders})\n ${db.symbolNoiseFor('enc_gs')}\n ${db.pathExclusionsFor('enc_d')}\n ${scopeFilter}`,\n ...[...frontier],\n ...[...frontier],\n );\n\n for (const row of rows) {\n if (visited.has(row.symbol_id)) continue;\n if (db.isIgnored(row.relative_path)) continue;\n\n visited.add(row.symbol_id);\n nextFrontier.add(row.symbol_id);\n\n results.push({\n symbol: row.symbol,\n shortName: shortenSymbol(row.symbol),\n file: row.relative_path,\n depth,\n });\n }\n\n frontier = nextFrontier;\n }\n\n // Sort by depth then file path\n results.sort((a, b) => a.depth - b.depth || a.file.localeCompare(b.file));\n return results;\n}\n"],"mappings":";;;;;;;;AAUO,SAAS,SACd,IACA,eACA,OAA8C,CAAC,GAC7B;AAClB,QAAM,EAAE,WAAW,GAAG,MAAM,IAAI;AAEhC,QAAM,SAAS,qBAAqB,IAAI,aAAa;AACrD,MAAI,CAAC,OAAQ,QAAO,CAAC;AAErB,QAAM,cAAc,QAChB,kCAAkC,KAAK,OACvC;AAEJ,QAAM,UAA4B,CAAC;AACnC,QAAM,UAAU,oBAAI,IAAY,CAAC,OAAO,QAAQ,CAAC;AACjD,MAAI,WAAW,oBAAI,IAAY,CAAC,OAAO,QAAQ,CAAC;AAEhD,WAAS,QAAQ,GAAG,SAAS,UAAU,SAAS;AAC9C,QAAI,SAAS,SAAS,EAAG;AAEzB,UAAM,eAAe,CAAC,GAAG,QAAQ,EAAE,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AAC1D,UAAM,eAAe,oBAAI,IAAY;AAIrC,UAAM,OAAO,GAAG;AAAA,MAKd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BAawB,YAAY;AAAA;AAAA,gCAEV,YAAY;AAAA,UAClC,GAAG,eAAe,QAAQ,CAAC;AAAA,UAC3B,GAAG,kBAAkB,OAAO,CAAC;AAAA,UAC7B,WAAW;AAAA,MACf,GAAG,CAAC,GAAG,QAAQ;AAAA,MACf,GAAG,CAAC,GAAG,QAAQ;AAAA,IACjB;AAEA,eAAW,OAAO,MAAM;AACtB,UAAI,QAAQ,IAAI,IAAI,SAAS,EAAG;AAChC,UAAI,GAAG,UAAU,IAAI,aAAa,EAAG;AAErC,cAAQ,IAAI,IAAI,SAAS;AACzB,mBAAa,IAAI,IAAI,SAAS;AAE9B,cAAQ,KAAK;AAAA,QACX,QAAQ,IAAI;AAAA,QACZ,WAAW,cAAc,IAAI,MAAM;AAAA,QACnC,MAAM,IAAI;AAAA,QACV;AAAA,MACF,CAAC;AAAA,IACH;AAEA,eAAW;AAAA,EACb;AAGA,UAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACxE,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/system.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { SystemResult, SymbolResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\nimport { cleanSignature } from './clean-signature.js';\n\n/** Full system map for a module path: files, symbols, deps in/out */\nexport function system(db: ScipDatabase, modulePattern: string): SystemResult {\n // Files in this module\n const fileRows = db.all<{ relative_path: string }>(\n `SELECT relative_path FROM documents\n WHERE relative_path LIKE ?\n ORDER BY relative_path`,\n `%${modulePattern}%`,\n );\n const files = fileRows\n .map((r) => r.relative_path)\n .filter((p) => !db.isIgnored(p));\n\n // Exported symbols\n const symbolRows = db.all<{\n start_line: number;\n end_line: number;\n symbol: string;\n sig: string | null;\n }>(\n `SELECT der.start_line, der.end_line, gs.symbol,\n REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig\n FROM defn_enclosing_ranges der\n JOIN global_symbols gs ON der.symbol_id = gs.id\n JOIN documents d ON der.document_id = d.id\n WHERE d.relative_path LIKE ?\n AND ${db.localSymbolPredicate}\n ${db.symbolNoise}\n AND gs.documentation IS NOT NULL\n ORDER BY d.relative_path, der.start_line`,\n `%${modulePattern}%`,\n );\n const symbols: SymbolResult[] = symbolRows.map((r) => ({\n startLine: r.start_line,\n endLine: r.end_line,\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n signature: cleanSignature(r.sig),\n }));\n\n // Internal dependencies (what this module depends on)\n const depRows = db.all<{ relative_path: string }>(\n `SELECT DISTINCT d2.relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d1 ON c.document_id = d1.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d2 ON der.document_id = d2.id\n WHERE d1.relative_path LIKE ?\n AND d2.relative_path NOT LIKE ?\n AND ${db.localSymbolPredicate}\n ORDER BY d2.relative_path`,\n `%${modulePattern}%`,\n `%${modulePattern}%`,\n );\n const dependsOn = depRows\n .map((r) => r.relative_path)\n .filter((p) => !db.isIgnored(p));\n\n // Reverse dependencies (who depends on this module)\n const rdepRows = db.all<{ relative_path: string }>(\n `SELECT DISTINCT d1.relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d1 ON c.document_id = d1.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d2 ON der.document_id = d2.id\n WHERE d2.relative_path LIKE ?\n AND d1.relative_path NOT LIKE ?\n ORDER BY d1.relative_path`,\n `%${modulePattern}%`,\n `%${modulePattern}%`,\n );\n const dependedOnBy = rdepRows\n .map((r) => r.relative_path)\n .filter((p) => !db.isIgnored(p));\n\n return { files, symbols, dependsOn, dependedOnBy };\n}\n"],"mappings":";;;;;;;;AAMO,SAAS,OAAO,IAAkB,eAAqC;AAE5E,QAAM,WAAW,GAAG;AAAA,IAClB;AAAA;AAAA;AAAA,IAGA,IAAI,aAAa;AAAA,EACnB;AACA,QAAM,QAAQ,SACX,IAAI,CAAC,MAAM,EAAE,aAAa,EAC1B,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC;AAGjC,QAAM,aAAa,GAAG;AAAA,IAMpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAMQ,GAAG,oBAAoB;AAAA,QAC3B,GAAG,WAAW;AAAA;AAAA;AAAA,IAGlB,IAAI,aAAa;AAAA,EACnB;AACA,QAAM,UAA0B,WAAW,IAAI,CAAC,OAAO;AAAA,IACrD,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,WAAW,eAAe,EAAE,GAAG;AAAA,EACjC,EAAE;AAGF,QAAM,UAAU,GAAG;AAAA,IACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YASQ,GAAG,oBAAoB;AAAA;AAAA,IAE/B,IAAI,aAAa;AAAA,IACjB,IAAI,aAAa;AAAA,EACnB;AACA,QAAM,YAAY,QACf,IAAI,CAAC,MAAM,EAAE,aAAa,EAC1B,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC;AAGjC,QAAM,WAAW,GAAG;AAAA,IAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUA,IAAI,aAAa;AAAA,IACjB,IAAI,aAAa;AAAA,EACnB;AACA,QAAM,eAAe,SAClB,IAAI,CAAC,MAAM,EAAE,aAAa,EAC1B,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC;AAEjC,SAAO,EAAE,OAAO,SAAS,WAAW,aAAa;AACnD;","names":[]}
@@ -1,40 +0,0 @@
1
- import {
2
- shortenSymbol
3
- } from "./chunk-QOV2R2WT.js";
4
-
5
- // src/queries/hierarchy.ts
6
- function hierarchy(db, symbolPattern) {
7
- const sym = db.get(
8
- `SELECT symbol, enclosing_symbol FROM global_symbols
9
- WHERE symbol LIKE ? LIMIT 1`,
10
- `%${symbolPattern}%`
11
- );
12
- if (!sym) return [];
13
- const chain = [
14
- { symbol: sym.symbol, shortName: shortenSymbol(sym.symbol), depth: 0 }
15
- ];
16
- let current = sym.enclosing_symbol;
17
- let depth = 1;
18
- const seen = /* @__PURE__ */ new Set([sym.symbol]);
19
- while (current && !seen.has(current) && depth < 20) {
20
- seen.add(current);
21
- const parent = db.get(
22
- `SELECT symbol, enclosing_symbol FROM global_symbols WHERE symbol = ?`,
23
- current
24
- );
25
- if (!parent) break;
26
- chain.push({
27
- symbol: parent.symbol,
28
- shortName: shortenSymbol(parent.symbol),
29
- depth
30
- });
31
- current = parent.enclosing_symbol;
32
- depth++;
33
- }
34
- return chain;
35
- }
36
-
37
- export {
38
- hierarchy
39
- };
40
- //# sourceMappingURL=chunk-DEZKCZXD.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/hierarchy.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { HierarchyNode } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Walk the enclosing_symbol chain upward to show a symbol's ancestry.\n * e.g., method → class → module → file\n *\n * Falls back to parsing the SCIP symbol descriptor chain when\n * enclosing_symbol is not populated by the indexer.\n */\nexport function hierarchy(db: ScipDatabase, symbolPattern: string): HierarchyNode[] {\n // Find the symbol\n const sym = db.get<{ symbol: string; enclosing_symbol: string | null }>(\n `SELECT symbol, enclosing_symbol FROM global_symbols\n WHERE symbol LIKE ? LIMIT 1`,\n `%${symbolPattern}%`,\n );\n\n if (!sym) return [];\n\n const chain: HierarchyNode[] = [\n { symbol: sym.symbol, shortName: shortenSymbol(sym.symbol), depth: 0 },\n ];\n\n // Walk enclosing_symbol chain if available\n let current = sym.enclosing_symbol;\n let depth = 1;\n const seen = new Set<string>([sym.symbol]);\n\n while (current && !seen.has(current) && depth < 20) {\n seen.add(current);\n const parent = db.get<{ symbol: string; enclosing_symbol: string | null }>(\n `SELECT symbol, enclosing_symbol FROM global_symbols WHERE symbol = ?`,\n current,\n );\n if (!parent) break;\n\n chain.push({\n symbol: parent.symbol,\n shortName: shortenSymbol(parent.symbol),\n depth,\n });\n current = parent.enclosing_symbol;\n depth++;\n }\n\n return chain;\n}\n"],"mappings":";;;;;AAWO,SAAS,UAAU,IAAkB,eAAwC;AAElF,QAAM,MAAM,GAAG;AAAA,IACb;AAAA;AAAA,IAEA,IAAI,aAAa;AAAA,EACnB;AAEA,MAAI,CAAC,IAAK,QAAO,CAAC;AAElB,QAAM,QAAyB;AAAA,IAC7B,EAAE,QAAQ,IAAI,QAAQ,WAAW,cAAc,IAAI,MAAM,GAAG,OAAO,EAAE;AAAA,EACvE;AAGA,MAAI,UAAU,IAAI;AAClB,MAAI,QAAQ;AACZ,QAAM,OAAO,oBAAI,IAAY,CAAC,IAAI,MAAM,CAAC;AAEzC,SAAO,WAAW,CAAC,KAAK,IAAI,OAAO,KAAK,QAAQ,IAAI;AAClD,SAAK,IAAI,OAAO;AAChB,UAAM,SAAS,GAAG;AAAA,MAChB;AAAA,MACA;AAAA,IACF;AACA,QAAI,CAAC,OAAQ;AAEb,UAAM,KAAK;AAAA,MACT,QAAQ,OAAO;AAAA,MACf,WAAW,cAAc,OAAO,MAAM;AAAA,MACtC;AAAA,IACF,CAAC;AACD,cAAU,OAAO;AACjB;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/fan.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { FanResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Fan-in: how many distinct files reference this symbol.\n * High fan-in = widely depended upon = high blast radius for changes.\n */\nexport function fanIn(\n db: ScipDatabase,\n symbolPattern: string,\n): FanResult[] {\n const rows = db.all<{\n symbol: string;\n file_count: number;\n }>(\n `SELECT gs.symbol, COUNT(DISTINCT c.document_id) AS file_count\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n WHERE gs.symbol LIKE ?\n AND m.role != 1\n GROUP BY gs.id\n ORDER BY file_count DESC`,\n `%${symbolPattern}%`,\n );\n\n return rows.map((r) => ({\n name: shortenSymbol(r.symbol),\n count: r.file_count,\n }));\n}\n\n/**\n * Fan-out: how many external symbols does this file reference.\n * High fan-out = depends on many things = fragile to upstream changes.\n */\nexport function fanOut(\n db: ScipDatabase,\n filePattern: string,\n): FanResult[] {\n const rows = db.all<{\n relative_path: string;\n symbol_count: number;\n }>(\n `SELECT d.relative_path, COUNT(DISTINCT gs.id) AS symbol_count\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents def_d ON der.document_id = def_d.id\n WHERE d.relative_path LIKE ?\n AND m.role != 1\n AND def_d.id != d.id\n GROUP BY d.id\n ORDER BY symbol_count DESC`,\n `%${filePattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n name: r.relative_path,\n count: r.symbol_count,\n }));\n}\n\n/**\n * Top fan-in across the whole codebase — the most depended-on symbols.\n */\nexport function topFanIn(\n db: ScipDatabase,\n opts: { limit?: number; scope?: string } = {},\n): FanResult[] {\n const { limit = 30, scope } = opts;\n const scopeFilter = scope\n ? `AND def_d.relative_path LIKE '%${scope}%'`\n : '';\n\n const rows = db.all<{\n symbol: string;\n file_count: number;\n }>(\n `SELECT gs.symbol, COUNT(DISTINCT c.document_id) AS file_count\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents def_d ON der.document_id = def_d.id\n WHERE m.role != 1\n ${db.pathExclusionsFor('def_d')}\n ${db.symbolNoiseFor('gs')}\n ${scopeFilter}\n GROUP BY gs.id\n HAVING file_count > 1\n ORDER BY file_count DESC\n LIMIT ?`,\n limit,\n );\n\n return rows.map((r) => ({\n name: shortenSymbol(r.symbol),\n count: r.file_count,\n }));\n}\n\n/**\n * Top fan-out across the whole codebase — files that depend on the most external symbols.\n */\nexport function topFanOut(\n db: ScipDatabase,\n opts: { limit?: number; scope?: string } = {},\n): FanResult[] {\n const { limit = 30, scope } = opts;\n const scopeFilter = scope\n ? `AND d.relative_path LIKE '%${scope}%'`\n : '';\n\n const rows = db.all<{\n relative_path: string;\n symbol_count: number;\n }>(\n `SELECT d.relative_path, COUNT(DISTINCT gs.id) AS symbol_count\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents def_d ON der.document_id = def_d.id\n WHERE m.role != 1\n AND def_d.id != d.id\n ${db.pathExclusionsFor('d')}\n ${db.symbolNoiseFor('gs')}\n ${scopeFilter}\n GROUP BY d.id\n ORDER BY symbol_count DESC\n LIMIT ?`,\n limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n name: r.relative_path,\n count: r.symbol_count,\n }));\n}\n"],"mappings":";;;;;AAQO,SAAS,MACd,IACA,eACa;AACb,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,IAAI,aAAa;AAAA,EACnB;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,MAAM,cAAc,EAAE,MAAM;AAAA,IAC5B,OAAO,EAAE;AAAA,EACX,EAAE;AACJ;AAMO,SAAS,OACd,IACA,aACa;AACb,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAYA,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,MAAM,EAAE;AAAA,IACR,OAAO,EAAE;AAAA,EACX,EAAE;AACN;AAKO,SAAS,SACd,IACA,OAA2C,CAAC,GAC/B;AACb,QAAM,EAAE,QAAQ,IAAI,MAAM,IAAI;AAC9B,QAAM,cAAc,QAChB,kCAAkC,KAAK,OACvC;AAEJ,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOI,GAAG,kBAAkB,OAAO,CAAC;AAAA,QAC7B,GAAG,eAAe,IAAI,CAAC;AAAA,QACvB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,IAKf;AAAA,EACF;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,MAAM,cAAc,EAAE,MAAM;AAAA,IAC5B,OAAO,EAAE;AAAA,EACX,EAAE;AACJ;AAKO,SAAS,UACd,IACA,OAA2C,CAAC,GAC/B;AACb,QAAM,EAAE,QAAQ,IAAI,MAAM,IAAI;AAC9B,QAAM,cAAc,QAChB,8BAA8B,KAAK,OACnC;AAEJ,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QASI,GAAG,kBAAkB,GAAG,CAAC;AAAA,QACzB,GAAG,eAAe,IAAI,CAAC;AAAA,QACvB,WAAW;AAAA;AAAA;AAAA;AAAA,IAIf;AAAA,EACF;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,MAAM,EAAE;AAAA,IACR,OAAO,EAAE;AAAA,EACX,EAAE;AACN;","names":[]}
@@ -1,99 +0,0 @@
1
- // src/reindex/install.ts
2
- import { execFileSync } from "child_process";
3
- import { platform } from "os";
4
- var IS_WINDOWS = platform() === "win32";
5
- function isBinaryAvailable(name) {
6
- const cmd = IS_WINDOWS ? "where" : "which";
7
- try {
8
- execFileSync(cmd, [name], { stdio: "pipe" });
9
- return true;
10
- } catch {
11
- return false;
12
- }
13
- }
14
- function isIndexerInstalled(config) {
15
- return isBinaryAvailable(config.indexerBinary);
16
- }
17
- function tryInstallIndexer(config, onStatus) {
18
- const methods = config.installMethods;
19
- if (!methods?.length) {
20
- onStatus(`No auto-install method available for ${config.indexerBinary}.`);
21
- if (config.installUrl) {
22
- onStatus(`Install manually from: ${config.installUrl}`);
23
- }
24
- return false;
25
- }
26
- for (const method of methods) {
27
- if (!isBinaryAvailable(method.prerequisite)) {
28
- continue;
29
- }
30
- onStatus(`Installing ${config.indexerBinary} via ${method.label}...`);
31
- try {
32
- execFileSync(method.binary, method.args, {
33
- stdio: "inherit",
34
- timeout: 3e5,
35
- env: process.env
36
- });
37
- if (isIndexerInstalled(config)) {
38
- onStatus(`Successfully installed ${config.indexerBinary} via ${method.label}`);
39
- return true;
40
- }
41
- onStatus(`${method.label} command completed but ${config.indexerBinary} not found on PATH`);
42
- } catch (err) {
43
- const msg = err instanceof Error ? err.message : String(err);
44
- onStatus(`${method.label} install failed: ${msg}`);
45
- }
46
- }
47
- onStatus(`Could not auto-install ${config.indexerBinary}.`);
48
- if (config.installUrl) {
49
- onStatus(`Install manually from: ${config.installUrl}`);
50
- }
51
- return false;
52
- }
53
- function tryInstallScipCli(onStatus) {
54
- if (platform() === "darwin" && isBinaryAvailable("brew")) {
55
- onStatus("Installing scip CLI via Homebrew...");
56
- try {
57
- execFileSync("brew", ["install", "sourcegraph/scip/scip"], {
58
- stdio: "inherit",
59
- timeout: 3e5,
60
- env: process.env
61
- });
62
- if (isBinaryAvailable("scip")) {
63
- onStatus("Successfully installed scip CLI via Homebrew");
64
- return true;
65
- }
66
- } catch (err) {
67
- const msg = err instanceof Error ? err.message : String(err);
68
- onStatus(`Homebrew install failed: ${msg}`);
69
- }
70
- }
71
- if (isBinaryAvailable("go")) {
72
- onStatus("Installing scip CLI via go install...");
73
- try {
74
- execFileSync("go", ["install", "github.com/sourcegraph/scip/cmd/scip@latest"], {
75
- stdio: "inherit",
76
- timeout: 3e5,
77
- env: process.env
78
- });
79
- if (isBinaryAvailable("scip")) {
80
- onStatus("Successfully installed scip CLI via go install");
81
- return true;
82
- }
83
- } catch (err) {
84
- const msg = err instanceof Error ? err.message : String(err);
85
- onStatus(`go install failed: ${msg}`);
86
- }
87
- }
88
- onStatus("Could not auto-install scip CLI.");
89
- onStatus("Install manually from: https://github.com/sourcegraph/scip/releases");
90
- return false;
91
- }
92
-
93
- export {
94
- isBinaryAvailable,
95
- isIndexerInstalled,
96
- tryInstallIndexer,
97
- tryInstallScipCli
98
- };
99
- //# sourceMappingURL=chunk-DVWGWHFW.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/reindex/install.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process';\nimport { platform } from 'node:os';\nimport type { IndexerConfig } from '../types.js';\n\nconst IS_WINDOWS = platform() === 'win32';\n\n/**\n * Check if a binary is available on PATH.\n */\nexport function isBinaryAvailable(name: string): boolean {\n const cmd = IS_WINDOWS ? 'where' : 'which';\n try {\n execFileSync(cmd, [name], { stdio: 'pipe' });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Check if an indexer's binary is available on PATH.\n */\nexport function isIndexerInstalled(config: IndexerConfig): boolean {\n return isBinaryAvailable(config.indexerBinary);\n}\n\n/**\n * Attempt to auto-install an indexer using its configured install methods.\n * Tries each method in order, checking prerequisites first.\n * Returns true if installation succeeded.\n */\nexport function tryInstallIndexer(\n config: IndexerConfig,\n onStatus: (msg: string) => void,\n): boolean {\n const methods = config.installMethods;\n if (!methods?.length) {\n onStatus(`No auto-install method available for ${config.indexerBinary}.`);\n if (config.installUrl) {\n onStatus(`Install manually from: ${config.installUrl}`);\n }\n return false;\n }\n\n for (const method of methods) {\n if (!isBinaryAvailable(method.prerequisite)) {\n continue;\n }\n\n onStatus(`Installing ${config.indexerBinary} via ${method.label}...`);\n try {\n execFileSync(method.binary, method.args, {\n stdio: 'inherit',\n timeout: 300_000,\n env: process.env,\n });\n\n if (isIndexerInstalled(config)) {\n onStatus(`Successfully installed ${config.indexerBinary} via ${method.label}`);\n return true;\n }\n onStatus(`${method.label} command completed but ${config.indexerBinary} not found on PATH`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n onStatus(`${method.label} install failed: ${msg}`);\n }\n }\n\n onStatus(`Could not auto-install ${config.indexerBinary}.`);\n if (config.installUrl) {\n onStatus(`Install manually from: ${config.installUrl}`);\n }\n return false;\n}\n\n/**\n * Attempt to auto-install the `scip` CLI binary.\n * Tries brew (macOS), then go install, then prints manual instructions.\n * Returns true if installation succeeded.\n */\nexport function tryInstallScipCli(\n onStatus: (msg: string) => void,\n): boolean {\n // macOS: try Homebrew first\n if (platform() === 'darwin' && isBinaryAvailable('brew')) {\n onStatus('Installing scip CLI via Homebrew...');\n try {\n execFileSync('brew', ['install', 'sourcegraph/scip/scip'], {\n stdio: 'inherit',\n timeout: 300_000,\n env: process.env,\n });\n if (isBinaryAvailable('scip')) {\n onStatus('Successfully installed scip CLI via Homebrew');\n return true;\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n onStatus(`Homebrew install failed: ${msg}`);\n }\n }\n\n // Any platform: try go install\n if (isBinaryAvailable('go')) {\n onStatus('Installing scip CLI via go install...');\n try {\n execFileSync('go', ['install', 'github.com/sourcegraph/scip/cmd/scip@latest'], {\n stdio: 'inherit',\n timeout: 300_000,\n env: process.env,\n });\n if (isBinaryAvailable('scip')) {\n onStatus('Successfully installed scip CLI via go install');\n return true;\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n onStatus(`go install failed: ${msg}`);\n }\n }\n\n onStatus('Could not auto-install scip CLI.');\n onStatus('Install manually from: https://github.com/sourcegraph/scip/releases');\n return false;\n}\n"],"mappings":";AAAA,SAAS,oBAAoB;AAC7B,SAAS,gBAAgB;AAGzB,IAAM,aAAa,SAAS,MAAM;AAK3B,SAAS,kBAAkB,MAAuB;AACvD,QAAM,MAAM,aAAa,UAAU;AACnC,MAAI;AACF,iBAAa,KAAK,CAAC,IAAI,GAAG,EAAE,OAAO,OAAO,CAAC;AAC3C,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,mBAAmB,QAAgC;AACjE,SAAO,kBAAkB,OAAO,aAAa;AAC/C;AAOO,SAAS,kBACd,QACA,UACS;AACT,QAAM,UAAU,OAAO;AACvB,MAAI,CAAC,SAAS,QAAQ;AACpB,aAAS,wCAAwC,OAAO,aAAa,GAAG;AACxE,QAAI,OAAO,YAAY;AACrB,eAAS,0BAA0B,OAAO,UAAU,EAAE;AAAA,IACxD;AACA,WAAO;AAAA,EACT;AAEA,aAAW,UAAU,SAAS;AAC5B,QAAI,CAAC,kBAAkB,OAAO,YAAY,GAAG;AAC3C;AAAA,IACF;AAEA,aAAS,cAAc,OAAO,aAAa,QAAQ,OAAO,KAAK,KAAK;AACpE,QAAI;AACF,mBAAa,OAAO,QAAQ,OAAO,MAAM;AAAA,QACvC,OAAO;AAAA,QACP,SAAS;AAAA,QACT,KAAK,QAAQ;AAAA,MACf,CAAC;AAED,UAAI,mBAAmB,MAAM,GAAG;AAC9B,iBAAS,0BAA0B,OAAO,aAAa,QAAQ,OAAO,KAAK,EAAE;AAC7E,eAAO;AAAA,MACT;AACA,eAAS,GAAG,OAAO,KAAK,0BAA0B,OAAO,aAAa,oBAAoB;AAAA,IAC5F,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,eAAS,GAAG,OAAO,KAAK,oBAAoB,GAAG,EAAE;AAAA,IACnD;AAAA,EACF;AAEA,WAAS,0BAA0B,OAAO,aAAa,GAAG;AAC1D,MAAI,OAAO,YAAY;AACrB,aAAS,0BAA0B,OAAO,UAAU,EAAE;AAAA,EACxD;AACA,SAAO;AACT;AAOO,SAAS,kBACd,UACS;AAET,MAAI,SAAS,MAAM,YAAY,kBAAkB,MAAM,GAAG;AACxD,aAAS,qCAAqC;AAC9C,QAAI;AACF,mBAAa,QAAQ,CAAC,WAAW,uBAAuB,GAAG;AAAA,QACzD,OAAO;AAAA,QACP,SAAS;AAAA,QACT,KAAK,QAAQ;AAAA,MACf,CAAC;AACD,UAAI,kBAAkB,MAAM,GAAG;AAC7B,iBAAS,8CAA8C;AACvD,eAAO;AAAA,MACT;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,eAAS,4BAA4B,GAAG,EAAE;AAAA,IAC5C;AAAA,EACF;AAGA,MAAI,kBAAkB,IAAI,GAAG;AAC3B,aAAS,uCAAuC;AAChD,QAAI;AACF,mBAAa,MAAM,CAAC,WAAW,6CAA6C,GAAG;AAAA,QAC7E,OAAO;AAAA,QACP,SAAS;AAAA,QACT,KAAK,QAAQ;AAAA,MACf,CAAC;AACD,UAAI,kBAAkB,MAAM,GAAG;AAC7B,iBAAS,gDAAgD;AACzD,eAAO;AAAA,MACT;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,eAAS,sBAAsB,GAAG,EAAE;AAAA,IACtC;AAAA,EACF;AAEA,WAAS,kCAAkC;AAC3C,WAAS,qEAAqE;AAC9E,SAAO;AACT;","names":[]}
@@ -1,46 +0,0 @@
1
- import {
2
- cleanSignature
3
- } from "./chunk-4TYLS5XX.js";
4
-
5
- // src/queries/trace.ts
6
- function trace(db, symbolPattern) {
7
- const defRows = db.all(
8
- `SELECT d.relative_path, der.start_line, der.end_line,
9
- REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
10
- FROM global_symbols gs
11
- JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
12
- JOIN documents d ON der.document_id = d.id
13
- WHERE gs.symbol LIKE ?
14
- AND ${db.localSymbolPredicate}
15
- ${db.symbolNoise}
16
- ORDER BY d.relative_path, der.start_line
17
- LIMIT 10`,
18
- `%${symbolPattern}%`
19
- );
20
- const definitions = defRows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
21
- relativePath: r.relative_path,
22
- startLine: r.start_line,
23
- endLine: r.end_line,
24
- signature: cleanSignature(r.sig)
25
- }));
26
- const refRows = db.all(
27
- `SELECT DISTINCT d.relative_path
28
- FROM mentions m
29
- JOIN chunks c ON m.chunk_id = c.id
30
- JOIN documents d ON c.document_id = d.id
31
- JOIN global_symbols gs ON m.symbol_id = gs.id
32
- WHERE gs.symbol LIKE ?
33
- AND ${db.localSymbolPredicate}
34
- ${db.symbolNoise}
35
- AND m.role != 1
36
- ORDER BY d.relative_path`,
37
- `%${symbolPattern}%`
38
- );
39
- const referencedBy = refRows.map((r) => r.relative_path).filter((p) => !db.isIgnored(p));
40
- return { definitions, referencedBy };
41
- }
42
-
43
- export {
44
- trace
45
- };
46
- //# sourceMappingURL=chunk-EQYLEQCW.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/trace.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { TraceResult } from '../types.js';\nimport { cleanSignature } from './clean-signature.js';\n\nexport function trace(db: ScipDatabase, symbolPattern: string): TraceResult {\n // Definitions\n const defRows = db.all<{\n relative_path: string;\n start_line: number;\n end_line: number;\n sig: string | null;\n }>(\n `SELECT d.relative_path, der.start_line, der.end_line,\n REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE gs.symbol LIKE ?\n AND ${db.localSymbolPredicate}\n ${db.symbolNoise}\n ORDER BY d.relative_path, der.start_line\n LIMIT 10`,\n `%${symbolPattern}%`,\n );\n\n const definitions = defRows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n relativePath: r.relative_path,\n startLine: r.start_line,\n endLine: r.end_line,\n signature: cleanSignature(r.sig),\n }));\n\n // References\n const refRows = db.all<{ relative_path: string }>(\n `SELECT DISTINCT d.relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n WHERE gs.symbol LIKE ?\n AND ${db.localSymbolPredicate}\n ${db.symbolNoise}\n AND m.role != 1\n ORDER BY d.relative_path`,\n `%${symbolPattern}%`,\n );\n\n const referencedBy = refRows\n .map((r) => r.relative_path)\n .filter((p) => !db.isIgnored(p));\n\n return { definitions, referencedBy };\n}\n"],"mappings":";;;;;AAIO,SAAS,MAAM,IAAkB,eAAoC;AAE1E,QAAM,UAAU,GAAG;AAAA,IAMjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAMQ,GAAG,oBAAoB;AAAA,QAC3B,GAAG,WAAW;AAAA;AAAA;AAAA,IAGlB,IAAI,aAAa;AAAA,EACnB;AAEA,QAAM,cAAc,QACjB,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,cAAc,EAAE;AAAA,IAChB,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,WAAW,eAAe,EAAE,GAAG;AAAA,EACjC,EAAE;AAGJ,QAAM,UAAU,GAAG;AAAA,IACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAMQ,GAAG,oBAAoB;AAAA,QAC3B,GAAG,WAAW;AAAA;AAAA;AAAA,IAGlB,IAAI,aAAa;AAAA,EACnB;AAEA,QAAM,eAAe,QAClB,IAAI,CAAC,MAAM,EAAE,aAAa,EAC1B,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC;AAEjC,SAAO,EAAE,aAAa,aAAa;AACrC;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/doc-coverage.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { DocCoverageResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Check documentation coverage: what percentage of symbols have doc strings?\n * Reports overall stats and lists undocumented symbols.\n */\nexport function docCoverage(\n db: ScipDatabase,\n opts: { scope?: string; minLoc?: number; limit?: number } = {},\n): DocCoverageResult {\n const { scope, minLoc = 3, limit = 50 } = opts;\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n // Count all local symbols meeting the threshold\n const totalRow = db.get<{ c: number }>(\n `SELECT COUNT(*) AS c\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n ${db.symbolNoiseFor('gs')}\n AND gs.symbol NOT LIKE '%#%'\n AND (der.end_line - der.start_line + 1) >= ?\n ${scopeFilter}`,\n minLoc,\n );\n\n const docRow = db.get<{ c: number }>(\n `SELECT COUNT(*) AS c\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n ${db.symbolNoiseFor('gs')}\n AND gs.symbol NOT LIKE '%#%'\n AND (der.end_line - der.start_line + 1) >= ?\n AND gs.documentation IS NOT NULL\n AND gs.documentation != ''\n ${scopeFilter}`,\n minLoc,\n );\n\n const total = totalRow?.c ?? 0;\n const documented = docRow?.c ?? 0;\n\n // Get undocumented symbols\n const undocRows = db.all<{\n symbol: string;\n relative_path: string;\n start_line: number;\n }>(\n `SELECT gs.symbol, d.relative_path, der.start_line\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n ${db.symbolNoiseFor('gs')}\n AND gs.symbol NOT LIKE '%#%'\n AND (der.end_line - der.start_line + 1) >= ?\n AND (gs.documentation IS NULL OR gs.documentation = '')\n ${scopeFilter}\n ORDER BY d.relative_path, der.start_line\n LIMIT ?`,\n minLoc, limit,\n );\n\n return {\n totalSymbols: total,\n documented,\n undocumented: total - documented,\n coveragePercent: total > 0 ? Math.round((documented / total) * 100) : 0,\n undocumentedSymbols: undocRows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n relativePath: r.relative_path,\n startLine: r.start_line,\n })),\n };\n}\n"],"mappings":";;;;;AAQO,SAAS,YACd,IACA,OAA4D,CAAC,GAC1C;AACnB,QAAM,EAAE,OAAO,SAAS,GAAG,QAAQ,GAAG,IAAI;AAC1C,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAGtE,QAAM,WAAW,GAAG;AAAA,IAClB;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,GAAG,kBAAkB,GAAG,CAAC;AAAA,QACzB,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA,QAGvB,WAAW;AAAA,IACf;AAAA,EACF;AAEA,QAAM,SAAS,GAAG;AAAA,IAChB;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,GAAG,kBAAkB,GAAG,CAAC;AAAA,QACzB,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,QAKvB,WAAW;AAAA,IACf;AAAA,EACF;AAEA,QAAM,QAAQ,UAAU,KAAK;AAC7B,QAAM,aAAa,QAAQ,KAAK;AAGhC,QAAM,YAAY,GAAG;AAAA,IAKnB;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,GAAG,kBAAkB,GAAG,CAAC;AAAA,QACzB,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA,QAIvB,WAAW;AAAA;AAAA;AAAA,IAGf;AAAA,IAAQ;AAAA,EACV;AAEA,SAAO;AAAA,IACL,cAAc;AAAA,IACd;AAAA,IACA,cAAc,QAAQ;AAAA,IACtB,iBAAiB,QAAQ,IAAI,KAAK,MAAO,aAAa,QAAS,GAAG,IAAI;AAAA,IACtE,qBAAqB,UAClB,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,MACX,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,cAAc,EAAE;AAAA,MAChB,WAAW,EAAE;AAAA,IACf,EAAE;AAAA,EACN;AACF;","names":[]}