scip-query 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/README.md +16 -43
  2. package/dist/chunk-2UELLEBI.js +1 -0
  3. package/dist/chunk-34JPTNRN.js +601 -0
  4. package/dist/{chunk-W4ALF422.js → chunk-3566TKJ5.js} +3 -3
  5. package/dist/{chunk-Z6YZJ36C.js → chunk-4ACRRQC4.js} +8 -4
  6. package/dist/{chunk-Z4GHE2HD.js → chunk-4BQFSNFI.js} +6 -2
  7. package/dist/{chunk-4XHWPRAX.js → chunk-6QSHLFSL.js} +3 -3
  8. package/dist/{chunk-4EXL2CUA.js → chunk-6WVR5K46.js} +16 -8
  9. package/dist/{chunk-ZQIIPFD7.js → chunk-75RQSBTK.js} +2 -2
  10. package/dist/{chunk-KPPHZCZJ.js → chunk-7HK5ZLOE.js} +28 -46
  11. package/dist/{chunk-MCUX5LA7.js → chunk-7JFZSOJ7.js} +3 -3
  12. package/dist/{chunk-6SXADWLW.js → chunk-AKMBBKWV.js} +2 -2
  13. package/dist/{chunk-NHBZIL2J.js → chunk-AMNISGYR.js} +3 -3
  14. package/dist/{chunk-2CKGIR6G.js → chunk-BFLULBEU.js} +3 -3
  15. package/dist/{chunk-HPFZLISB.js → chunk-CU62ZDHI.js} +2 -2
  16. package/dist/{chunk-EQYLEQCW.js → chunk-DY4AFG2W.js} +12 -10
  17. package/dist/{chunk-5RMYT5WH.js → chunk-F7XU27LU.js} +2 -2
  18. package/dist/{chunk-KCBMVQL5.js → chunk-GPJVPT3U.js} +2 -2
  19. package/dist/{chunk-NUZ4OMU3.js → chunk-GU2H5QRN.js} +2 -2
  20. package/dist/{chunk-UJQN5N3I.js → chunk-H6WCPKCX.js} +6 -3
  21. package/dist/{chunk-OVPLOMPY.js → chunk-HMYJJ3HY.js} +7 -4
  22. package/dist/chunk-IJKLB2JW.js +69 -0
  23. package/dist/{chunk-DGUPQSOR.js → chunk-IXPHLF6K.js} +2 -2
  24. package/dist/{chunk-7PBOG4YE.js → chunk-KBOQX573.js} +2 -2
  25. package/dist/{chunk-ZOGY2V3N.js → chunk-LLMPAG56.js} +93 -30
  26. package/dist/{chunk-BOVXCR46.js → chunk-LTJC5ZQL.js} +2 -2
  27. package/dist/{chunk-LAWMH22O.js → chunk-M3NPW3FC.js} +2 -2
  28. package/dist/{chunk-NWCE4CIC.js → chunk-M4QGEKKD.js} +5 -27
  29. package/dist/{chunk-63G7IQTD.js → chunk-MVH45PYK.js} +20 -40
  30. package/dist/chunk-N4C3H7LH.js +37 -0
  31. package/dist/chunk-NG5F43OU.js +200 -0
  32. package/dist/{chunk-D567NFIF.js → chunk-NVIIM34O.js} +3 -3
  33. package/dist/{chunk-BNN2RKD2.js → chunk-ORINICIZ.js} +3 -3
  34. package/dist/{chunk-4PDAL6IL.js → chunk-PMJKOXOT.js} +3 -3
  35. package/dist/{chunk-QOV2R2WT.js → chunk-QIXNAB5K.js} +42 -2
  36. package/dist/{chunk-7LLPRPR5.js → chunk-R2I3M5B4.js} +2 -2
  37. package/dist/{chunk-7RLE5EWE.js → chunk-R56FJU3E.js} +34 -13
  38. package/dist/{chunk-H2MDONBU.js → chunk-RFMT7UAZ.js} +3 -3
  39. package/dist/{chunk-SEFSL2GF.js → chunk-TOIEB3LG.js} +2 -2
  40. package/dist/chunk-VO4QI3LS.js +84 -0
  41. package/dist/{chunk-ZK6GXM3J.js → chunk-WVK7AASK.js} +3 -3
  42. package/dist/{chunk-HMLMH7VZ.js → chunk-Y3M323OX.js} +2 -2
  43. package/dist/{chunk-DCKMSTJ4.js → chunk-Y4JFVQ7C.js} +2 -2
  44. package/dist/{chunk-7UCKSQRS.js → chunk-YAFWL3RA.js} +3 -3
  45. package/dist/{chunk-FGXRVW7G.js → chunk-YZ6L7GFO.js} +2 -2
  46. package/dist/cli.js +1355 -671
  47. package/dist/{db-BNVVZSfP.d.ts → db-BHYam4BK.d.ts} +6 -18
  48. package/dist/index.d.ts +15 -15
  49. package/dist/index.js +260 -231
  50. package/dist/postinstall.js +5 -76
  51. package/dist/queries/affected.d.ts +1 -1
  52. package/dist/queries/affected.js +3 -3
  53. package/dist/queries/bottlenecks.d.ts +1 -1
  54. package/dist/queries/bottlenecks.js +2 -2
  55. package/dist/queries/by-kind.d.ts +1 -1
  56. package/dist/queries/by-kind.js +2 -2
  57. package/dist/queries/call-graph.d.ts +1 -1
  58. package/dist/queries/call-graph.js +3 -3
  59. package/dist/queries/change-surface.d.ts +2 -2
  60. package/dist/queries/change-surface.js +2 -3
  61. package/dist/queries/code.d.ts +1 -1
  62. package/dist/queries/code.js +3 -3
  63. package/dist/queries/complexity-hotspots.d.ts +1 -1
  64. package/dist/queries/complexity-hotspots.js +3 -3
  65. package/dist/queries/complexity.d.ts +1 -1
  66. package/dist/queries/complexity.js +3 -3
  67. package/dist/queries/convergence.d.ts +1 -1
  68. package/dist/queries/convergence.js +3 -3
  69. package/dist/queries/coupling.d.ts +1 -1
  70. package/dist/queries/cycles.d.ts +1 -1
  71. package/dist/queries/cycles.js +3 -2
  72. package/dist/queries/dataflow.d.ts +1 -1
  73. package/dist/queries/dataflow.js +3 -3
  74. package/dist/queries/dead.d.ts +1 -1
  75. package/dist/queries/dead.js +4 -3
  76. package/dist/queries/deep-chains.d.ts +1 -1
  77. package/dist/queries/deep-chains.js +3 -2
  78. package/dist/queries/deps.d.ts +1 -1
  79. package/dist/queries/diff-impact.d.ts +2 -2
  80. package/dist/queries/diff-impact.js +2 -3
  81. package/dist/queries/doc-coverage.d.ts +1 -1
  82. package/dist/queries/doc-coverage.js +2 -2
  83. package/dist/queries/drift.d.ts +1 -1
  84. package/dist/queries/drift.js +3 -2
  85. package/dist/queries/extract-candidates.d.ts +1 -1
  86. package/dist/queries/extract-candidates.js +3 -3
  87. package/dist/queries/fan.d.ts +1 -1
  88. package/dist/queries/fan.js +2 -2
  89. package/dist/queries/files.d.ts +1 -1
  90. package/dist/queries/health.d.ts +1 -1
  91. package/dist/queries/health.js +14 -14
  92. package/dist/queries/hierarchy.d.ts +1 -1
  93. package/dist/queries/hierarchy.js +3 -2
  94. package/dist/queries/hotspots.d.ts +1 -1
  95. package/dist/queries/hotspots.js +2 -2
  96. package/dist/queries/imports.d.ts +1 -1
  97. package/dist/queries/imports.js +3 -2
  98. package/dist/queries/index.d.ts +1 -2
  99. package/dist/queries/index.js +43 -48
  100. package/dist/queries/isolated.d.ts +1 -1
  101. package/dist/queries/isolated.js +4 -3
  102. package/dist/queries/members.d.ts +2 -2
  103. package/dist/queries/members.js +3 -2
  104. package/dist/queries/methods.d.ts +1 -1
  105. package/dist/queries/methods.js +2 -2
  106. package/dist/queries/outline.d.ts +1 -1
  107. package/dist/queries/outline.js +2 -2
  108. package/dist/queries/passthrough-candidates.d.ts +1 -1
  109. package/dist/queries/passthrough-candidates.js +3 -3
  110. package/dist/queries/redundant-reexports.d.ts +1 -1
  111. package/dist/queries/redundant-reexports.js +4 -2
  112. package/dist/queries/refs.d.ts +1 -1
  113. package/dist/queries/similar-chains.d.ts +1 -1
  114. package/dist/queries/similar-chains.js +3 -2
  115. package/dist/queries/similar-files.d.ts +1 -1
  116. package/dist/queries/similar-files.js +3 -2
  117. package/dist/queries/similar-signatures.d.ts +1 -1
  118. package/dist/queries/similar-signatures.js +2 -2
  119. package/dist/queries/similar.d.ts +1 -1
  120. package/dist/queries/similar.js +3 -3
  121. package/dist/queries/slice.d.ts +1 -1
  122. package/dist/queries/slice.js +3 -3
  123. package/dist/queries/stale-abstractions.d.ts +1 -1
  124. package/dist/queries/stale-abstractions.js +3 -3
  125. package/dist/queries/stats.d.ts +1 -1
  126. package/dist/queries/surface.d.ts +1 -1
  127. package/dist/queries/surface.js +2 -2
  128. package/dist/queries/symbols.d.ts +1 -1
  129. package/dist/queries/symbols.js +2 -2
  130. package/dist/queries/system.d.ts +1 -1
  131. package/dist/queries/system.js +2 -2
  132. package/dist/queries/trace.d.ts +1 -1
  133. package/dist/queries/trace.js +3 -1
  134. package/dist/queries/wrapper-candidates.d.ts +1 -1
  135. package/dist/queries/wrapper-candidates.js +3 -3
  136. package/dist/reindex-worker.js +24 -12
  137. package/package.json +6 -1
  138. package/IMPROVEMENTS.md +0 -143
  139. package/PLAN.md +0 -320
  140. package/dist/chunk-2CKGIR6G.js.map +0 -1
  141. package/dist/chunk-3UOUTZQT.js +0 -45
  142. package/dist/chunk-3UOUTZQT.js.map +0 -1
  143. package/dist/chunk-4EXL2CUA.js.map +0 -1
  144. package/dist/chunk-4PDAL6IL.js.map +0 -1
  145. package/dist/chunk-4TYLS5XX.js.map +0 -1
  146. package/dist/chunk-4XHWPRAX.js.map +0 -1
  147. package/dist/chunk-5RMYT5WH.js.map +0 -1
  148. package/dist/chunk-63G7IQTD.js.map +0 -1
  149. package/dist/chunk-6SXADWLW.js.map +0 -1
  150. package/dist/chunk-74RFWB5T.js.map +0 -1
  151. package/dist/chunk-7LLPRPR5.js.map +0 -1
  152. package/dist/chunk-7PBOG4YE.js.map +0 -1
  153. package/dist/chunk-7RLE5EWE.js.map +0 -1
  154. package/dist/chunk-7UCKSQRS.js.map +0 -1
  155. package/dist/chunk-BNN2RKD2.js.map +0 -1
  156. package/dist/chunk-BOVXCR46.js.map +0 -1
  157. package/dist/chunk-D567NFIF.js.map +0 -1
  158. package/dist/chunk-DCKMSTJ4.js.map +0 -1
  159. package/dist/chunk-DEZKCZXD.js +0 -40
  160. package/dist/chunk-DEZKCZXD.js.map +0 -1
  161. package/dist/chunk-DGUPQSOR.js.map +0 -1
  162. package/dist/chunk-DVWGWHFW.js +0 -99
  163. package/dist/chunk-DVWGWHFW.js.map +0 -1
  164. package/dist/chunk-EQYLEQCW.js.map +0 -1
  165. package/dist/chunk-FGXRVW7G.js.map +0 -1
  166. package/dist/chunk-H2MDONBU.js.map +0 -1
  167. package/dist/chunk-HB7MRLLL.js +0 -76
  168. package/dist/chunk-HB7MRLLL.js.map +0 -1
  169. package/dist/chunk-HDSRORNV.js.map +0 -1
  170. package/dist/chunk-HMLMH7VZ.js.map +0 -1
  171. package/dist/chunk-HPFZLISB.js.map +0 -1
  172. package/dist/chunk-HZBC7PPD.js +0 -88
  173. package/dist/chunk-HZBC7PPD.js.map +0 -1
  174. package/dist/chunk-ITZ3DDOG.js.map +0 -1
  175. package/dist/chunk-JJP7KQND.js +0 -1
  176. package/dist/chunk-JJP7KQND.js.map +0 -1
  177. package/dist/chunk-KCBMVQL5.js.map +0 -1
  178. package/dist/chunk-KPPHZCZJ.js.map +0 -1
  179. package/dist/chunk-LAWMH22O.js.map +0 -1
  180. package/dist/chunk-MCUX5LA7.js.map +0 -1
  181. package/dist/chunk-MGNMHKX3.js.map +0 -1
  182. package/dist/chunk-N5KEREIA.js.map +0 -1
  183. package/dist/chunk-NHBZIL2J.js.map +0 -1
  184. package/dist/chunk-NUZ4OMU3.js.map +0 -1
  185. package/dist/chunk-NWCE4CIC.js.map +0 -1
  186. package/dist/chunk-OVPLOMPY.js.map +0 -1
  187. package/dist/chunk-QOV2R2WT.js.map +0 -1
  188. package/dist/chunk-SEFSL2GF.js.map +0 -1
  189. package/dist/chunk-UJQN5N3I.js.map +0 -1
  190. package/dist/chunk-W4ALF422.js.map +0 -1
  191. package/dist/chunk-Z4GHE2HD.js.map +0 -1
  192. package/dist/chunk-Z6YZJ36C.js.map +0 -1
  193. package/dist/chunk-ZK6GXM3J.js.map +0 -1
  194. package/dist/chunk-ZOGY2V3N.js.map +0 -1
  195. package/dist/chunk-ZQIIPFD7.js.map +0 -1
  196. package/dist/cli.js.map +0 -1
  197. package/dist/index.js.map +0 -1
  198. package/dist/postinstall.js.map +0 -1
  199. package/dist/queries/affected.js.map +0 -1
  200. package/dist/queries/bottlenecks.js.map +0 -1
  201. package/dist/queries/by-kind.js.map +0 -1
  202. package/dist/queries/call-graph.js.map +0 -1
  203. package/dist/queries/change-surface.js.map +0 -1
  204. package/dist/queries/clean-signature.js.map +0 -1
  205. package/dist/queries/code.js.map +0 -1
  206. package/dist/queries/complexity-hotspots.js.map +0 -1
  207. package/dist/queries/complexity.js.map +0 -1
  208. package/dist/queries/convergence.js.map +0 -1
  209. package/dist/queries/coupling.js.map +0 -1
  210. package/dist/queries/cycles.js.map +0 -1
  211. package/dist/queries/dataflow.js.map +0 -1
  212. package/dist/queries/dead.js.map +0 -1
  213. package/dist/queries/deep-chains.js.map +0 -1
  214. package/dist/queries/deps.js.map +0 -1
  215. package/dist/queries/diff-impact.js.map +0 -1
  216. package/dist/queries/doc-coverage.js.map +0 -1
  217. package/dist/queries/drift.js.map +0 -1
  218. package/dist/queries/extract-candidates.js.map +0 -1
  219. package/dist/queries/fan.js.map +0 -1
  220. package/dist/queries/files.js.map +0 -1
  221. package/dist/queries/health.js.map +0 -1
  222. package/dist/queries/hierarchy.js.map +0 -1
  223. package/dist/queries/hotspots.js.map +0 -1
  224. package/dist/queries/imports.js.map +0 -1
  225. package/dist/queries/index.js.map +0 -1
  226. package/dist/queries/isolated.js.map +0 -1
  227. package/dist/queries/members.js.map +0 -1
  228. package/dist/queries/methods.js.map +0 -1
  229. package/dist/queries/outline.js.map +0 -1
  230. package/dist/queries/passthrough-candidates.js.map +0 -1
  231. package/dist/queries/redundant-reexports.js.map +0 -1
  232. package/dist/queries/refs.js.map +0 -1
  233. package/dist/queries/similar-chains.js.map +0 -1
  234. package/dist/queries/similar-files.js.map +0 -1
  235. package/dist/queries/similar-signatures.js.map +0 -1
  236. package/dist/queries/similar.js.map +0 -1
  237. package/dist/queries/slice.js.map +0 -1
  238. package/dist/queries/stale-abstractions.js.map +0 -1
  239. package/dist/queries/stats.js.map +0 -1
  240. package/dist/queries/surface.js.map +0 -1
  241. package/dist/queries/symbols.js.map +0 -1
  242. package/dist/queries/system.js.map +0 -1
  243. package/dist/queries/test-coverage.d.ts +0 -22
  244. package/dist/queries/test-coverage.js +0 -11
  245. package/dist/queries/test-coverage.js.map +0 -1
  246. package/dist/queries/trace.js.map +0 -1
  247. package/dist/queries/wrapper-candidates.js.map +0 -1
  248. package/dist/reindex-worker.js.map +0 -1
  249. package/docs/AGENT_GUIDE.md +0 -359
  250. package/reports/debloat/2026-04-10-scip-query-self-audit.md +0 -161
  251. package/src/cli.ts +0 -1480
  252. package/src/config.ts +0 -117
  253. package/src/db.ts +0 -127
  254. package/src/gitignore-filter.ts +0 -143
  255. package/src/index.ts +0 -11
  256. package/src/postinstall.ts +0 -8
  257. package/src/queries/affected.ts +0 -86
  258. package/src/queries/bottlenecks.ts +0 -67
  259. package/src/queries/by-kind.ts +0 -204
  260. package/src/queries/call-graph.ts +0 -66
  261. package/src/queries/change-surface.ts +0 -110
  262. package/src/queries/clean-signature.ts +0 -22
  263. package/src/queries/code.ts +0 -101
  264. package/src/queries/complexity-hotspots.ts +0 -119
  265. package/src/queries/complexity.ts +0 -152
  266. package/src/queries/convergence.ts +0 -82
  267. package/src/queries/coupling.ts +0 -99
  268. package/src/queries/cycles.ts +0 -78
  269. package/src/queries/dataflow.ts +0 -128
  270. package/src/queries/dead.ts +0 -122
  271. package/src/queries/deep-chains.ts +0 -59
  272. package/src/queries/deps.ts +0 -46
  273. package/src/queries/diff-impact.ts +0 -204
  274. package/src/queries/doc-coverage.ts +0 -86
  275. package/src/queries/drift.ts +0 -224
  276. package/src/queries/extract-candidates.ts +0 -167
  277. package/src/queries/fan.ts +0 -148
  278. package/src/queries/files.ts +0 -16
  279. package/src/queries/health.ts +0 -324
  280. package/src/queries/hierarchy.ts +0 -49
  281. package/src/queries/hotspots.ts +0 -53
  282. package/src/queries/imports.ts +0 -95
  283. package/src/queries/index.ts +0 -45
  284. package/src/queries/isolated.ts +0 -67
  285. package/src/queries/members.ts +0 -54
  286. package/src/queries/methods.ts +0 -27
  287. package/src/queries/outline.ts +0 -52
  288. package/src/queries/passthrough-candidates.ts +0 -94
  289. package/src/queries/redundant-reexports.ts +0 -170
  290. package/src/queries/refs.ts +0 -27
  291. package/src/queries/similar-chains.ts +0 -314
  292. package/src/queries/similar-files.ts +0 -140
  293. package/src/queries/similar-signatures.ts +0 -151
  294. package/src/queries/similar.ts +0 -305
  295. package/src/queries/slice.ts +0 -154
  296. package/src/queries/stale-abstractions.ts +0 -82
  297. package/src/queries/stats.ts +0 -22
  298. package/src/queries/surface.ts +0 -34
  299. package/src/queries/symbols.ts +0 -39
  300. package/src/queries/system.ts +0 -86
  301. package/src/queries/test-coverage.ts +0 -106
  302. package/src/queries/trace.ts +0 -55
  303. package/src/queries/wrapper-candidates.ts +0 -112
  304. package/src/query-support.ts +0 -226
  305. package/src/reindex/detect.ts +0 -58
  306. package/src/reindex/index.ts +0 -153
  307. package/src/reindex/indexers.ts +0 -220
  308. package/src/reindex/install.ts +0 -125
  309. package/src/reindex-worker.ts +0 -35
  310. package/src/setup.ts +0 -202
  311. package/src/symbol-parser.ts +0 -278
  312. package/src/types.ts +0 -654
  313. package/src/watch.ts +0 -274
  314. package/tests/gitignore-filter.test.ts +0 -48
  315. package/tests/queries.test.ts +0 -300
  316. package/tests/symbol-parser.test.ts +0 -157
  317. package/tsconfig.json +0 -20
  318. package/tsup.config.ts +0 -40
  319. package/vitest.config.ts +0 -7
@@ -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 +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 +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 +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":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/code.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch } from '../query-support.js';\nimport type { CodeResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Read the source code for a symbol, bounded to its definition range.\n * Language-agnostic: just reads the file and extracts the relevant lines.\n *\n * Accepts:\n * - Symbol name pattern: \"processVegaMention\"\n * - Full short name: \"src:modules:chat:processVegaMention\"\n * - File:line-line syntax: \"src/chat/service.ts:100-200\"\n */\nexport function code(\n db: ScipDatabase,\n symbolPattern: string,\n opts: { context?: number } = {},\n): CodeResult | null {\n const { context = 0 } = opts;\n\n // Handle direct file:line-line syntax (bypass symbol lookup)\n const fileLineMatch = symbolPattern.match(/^(.+\\.\\w+):(\\d+)-(\\d+)$/);\n if (fileLineMatch) {\n return readFileRange(db, fileLineMatch[1]!, parseInt(fileLineMatch[2]!, 10), parseInt(fileLineMatch[3]!, 10), context);\n }\n\n const match = findFirstSymbolMatch(db, symbolPattern);\n if (!match) return null;\n\n // Get the language from the documents table\n const doc = db.get<{ language: string | null }>(\n `SELECT language FROM documents WHERE relative_path = ?`,\n match.relativePath,\n );\n\n // Read the file\n const filePath = join(db.config.projectRoot, match.relativePath);\n let fileContent: string;\n try {\n fileContent = readFileSync(filePath, 'utf-8');\n } catch {\n return null;\n }\n\n const lines = fileContent.split('\\n');\n const startLine = Math.max(0, match.startLine - context);\n const endLine = Math.min(lines.length - 1, match.endLine + context);\n const source = lines.slice(startLine, endLine + 1).join('\\n');\n\n return {\n symbol: match.symbol,\n shortName: shortenSymbol(match.symbol),\n relativePath: match.relativePath,\n startLine: startLine + 1, // 1-indexed for display\n endLine: endLine + 1,\n language: doc?.language ?? null,\n source,\n };\n}\n\n/** Read source by file path and line range directly (no symbol lookup) */\nfunction readFileRange(\n db: ScipDatabase,\n filePath: string,\n startLine: number,\n endLine: number,\n context: number,\n): CodeResult | null {\n // Find the file in the index\n const doc = db.get<{ relative_path: string; language: string | null }>(\n `SELECT relative_path, language FROM documents WHERE relative_path LIKE ?`,\n `%${filePath}%`,\n );\n if (!doc) return null;\n\n const fullPath = join(db.config.projectRoot, doc.relative_path);\n let fileContent: string;\n try {\n fileContent = readFileSync(fullPath, 'utf-8');\n } catch {\n return null;\n }\n\n const lines = fileContent.split('\\n');\n const start = Math.max(0, startLine - 1 - context); // convert to 0-indexed\n const end = Math.min(lines.length - 1, endLine - 1 + context);\n const source = lines.slice(start, end + 1).join('\\n');\n\n return {\n symbol: `${doc.relative_path}:${startLine}-${endLine}`,\n shortName: `${doc.relative_path}:${startLine}-${endLine}`,\n relativePath: doc.relative_path,\n startLine: start + 1,\n endLine: end + 1,\n language: doc.language,\n source,\n };\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,oBAAoB;AAC7B,SAAS,YAAY;AAed,SAAS,KACd,IACA,eACA,OAA6B,CAAC,GACX;AACnB,QAAM,EAAE,UAAU,EAAE,IAAI;AAGxB,QAAM,gBAAgB,cAAc,MAAM,yBAAyB;AACnE,MAAI,eAAe;AACjB,WAAO,cAAc,IAAI,cAAc,CAAC,GAAI,SAAS,cAAc,CAAC,GAAI,EAAE,GAAG,SAAS,cAAc,CAAC,GAAI,EAAE,GAAG,OAAO;AAAA,EACvH;AAEA,QAAM,QAAQ,qBAAqB,IAAI,aAAa;AACpD,MAAI,CAAC,MAAO,QAAO;AAGnB,QAAM,MAAM,GAAG;AAAA,IACb;AAAA,IACA,MAAM;AAAA,EACR;AAGA,QAAM,WAAW,KAAK,GAAG,OAAO,aAAa,MAAM,YAAY;AAC/D,MAAI;AACJ,MAAI;AACF,kBAAc,aAAa,UAAU,OAAO;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,YAAY,MAAM,IAAI;AACpC,QAAM,YAAY,KAAK,IAAI,GAAG,MAAM,YAAY,OAAO;AACvD,QAAM,UAAU,KAAK,IAAI,MAAM,SAAS,GAAG,MAAM,UAAU,OAAO;AAClE,QAAM,SAAS,MAAM,MAAM,WAAW,UAAU,CAAC,EAAE,KAAK,IAAI;AAE5D,SAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd,WAAW,cAAc,MAAM,MAAM;AAAA,IACrC,cAAc,MAAM;AAAA,IACpB,WAAW,YAAY;AAAA;AAAA,IACvB,SAAS,UAAU;AAAA,IACnB,UAAU,KAAK,YAAY;AAAA,IAC3B;AAAA,EACF;AACF;AAGA,SAAS,cACP,IACA,UACA,WACA,SACA,SACmB;AAEnB,QAAM,MAAM,GAAG;AAAA,IACb;AAAA,IACA,IAAI,QAAQ;AAAA,EACd;AACA,MAAI,CAAC,IAAK,QAAO;AAEjB,QAAM,WAAW,KAAK,GAAG,OAAO,aAAa,IAAI,aAAa;AAC9D,MAAI;AACJ,MAAI;AACF,kBAAc,aAAa,UAAU,OAAO;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,YAAY,MAAM,IAAI;AACpC,QAAM,QAAQ,KAAK,IAAI,GAAG,YAAY,IAAI,OAAO;AACjD,QAAM,MAAM,KAAK,IAAI,MAAM,SAAS,GAAG,UAAU,IAAI,OAAO;AAC5D,QAAM,SAAS,MAAM,MAAM,OAAO,MAAM,CAAC,EAAE,KAAK,IAAI;AAEpD,SAAO;AAAA,IACL,QAAQ,GAAG,IAAI,aAAa,IAAI,SAAS,IAAI,OAAO;AAAA,IACpD,WAAW,GAAG,IAAI,aAAa,IAAI,SAAS,IAAI,OAAO;AAAA,IACvD,cAAc,IAAI;AAAA,IAClB,WAAW,QAAQ;AAAA,IACnB,SAAS,MAAM;AAAA,IACf,UAAU,IAAI;AAAA,IACd;AAAA,EACF;AACF;","names":[]}
@@ -1,76 +0,0 @@
1
- import {
2
- shortenSymbol
3
- } from "./chunk-QOV2R2WT.js";
4
-
5
- // src/queries/imports.ts
6
- function imports(db, filePattern) {
7
- const rows = db.all(
8
- `SELECT DISTINCT gs.symbol, def_d.relative_path AS from_file
9
- FROM mentions m
10
- JOIN chunks c ON m.chunk_id = c.id
11
- JOIN documents imp_d ON c.document_id = imp_d.id
12
- JOIN global_symbols gs ON m.symbol_id = gs.id
13
- LEFT JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
14
- LEFT JOIN documents def_d ON der.document_id = def_d.id
15
- WHERE imp_d.relative_path LIKE ?
16
- AND m.role = 2
17
- ORDER BY def_d.relative_path, gs.symbol`,
18
- `%${filePattern}%`
19
- );
20
- return rows.map((r) => ({
21
- symbol: r.symbol,
22
- shortName: shortenSymbol(r.symbol),
23
- fromFile: r.from_file ?? "(external)"
24
- }));
25
- }
26
- function importedBy(db, symbolPattern) {
27
- const rows = db.all(
28
- `SELECT DISTINCT gs.symbol, d.relative_path AS importer
29
- FROM mentions m
30
- JOIN chunks c ON m.chunk_id = c.id
31
- JOIN documents d ON c.document_id = d.id
32
- JOIN global_symbols gs ON m.symbol_id = gs.id
33
- WHERE gs.symbol LIKE ?
34
- AND m.role = 2
35
- ORDER BY d.relative_path`,
36
- `%${symbolPattern}%`
37
- );
38
- return rows.filter((r) => !db.isIgnored(r.importer)).map((r) => ({
39
- symbol: r.symbol,
40
- shortName: shortenSymbol(r.symbol),
41
- fromFile: r.importer
42
- }));
43
- }
44
- function unusedImports(db, filePattern) {
45
- const rows = db.all(
46
- `SELECT gs.symbol, d.relative_path AS imported_in
47
- FROM mentions m
48
- JOIN chunks c ON m.chunk_id = c.id
49
- JOIN documents d ON c.document_id = d.id
50
- JOIN global_symbols gs ON m.symbol_id = gs.id
51
- WHERE d.relative_path LIKE ?
52
- AND m.role = 2
53
- AND NOT EXISTS (
54
- SELECT 1
55
- FROM mentions ref_m
56
- JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
57
- WHERE ref_m.symbol_id = gs.id
58
- AND ref_m.role != 1
59
- AND ref_c.document_id = d.id
60
- )
61
- ORDER BY d.relative_path, gs.symbol`,
62
- `%${filePattern}%`
63
- );
64
- return rows.map((r) => ({
65
- symbol: r.symbol,
66
- shortName: shortenSymbol(r.symbol),
67
- importedIn: r.imported_in
68
- }));
69
- }
70
-
71
- export {
72
- imports,
73
- importedBy,
74
- unusedImports
75
- };
76
- //# sourceMappingURL=chunk-HB7MRLLL.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/imports.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { ImportResult, UnusedImportResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * What symbols does this file import?\n * Uses role=2 (import) from the SCIP mentions table.\n */\nexport function imports(db: ScipDatabase, filePattern: string): ImportResult[] {\n const rows = db.all<{\n symbol: string;\n from_file: string;\n }>(\n `SELECT DISTINCT gs.symbol, def_d.relative_path AS from_file\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents imp_d ON c.document_id = imp_d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n LEFT JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n LEFT JOIN documents def_d ON der.document_id = def_d.id\n WHERE imp_d.relative_path LIKE ?\n AND m.role = 2\n ORDER BY def_d.relative_path, gs.symbol`,\n `%${filePattern}%`,\n );\n\n return rows.map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n fromFile: r.from_file ?? '(external)',\n }));\n}\n\n/**\n * Which files import this symbol?\n */\nexport function importedBy(db: ScipDatabase, symbolPattern: string): ImportResult[] {\n const rows = db.all<{\n symbol: string;\n importer: string;\n }>(\n `SELECT DISTINCT gs.symbol, d.relative_path AS importer\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 m.role = 2\n ORDER BY d.relative_path`,\n `%${symbolPattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.importer))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n fromFile: r.importer,\n }));\n}\n\n/**\n * Find imports in a file that are never referenced (role=0) in the same file.\n * These are likely unused imports.\n */\nexport function unusedImports(db: ScipDatabase, filePattern: string): UnusedImportResult[] {\n const rows = db.all<{\n symbol: string;\n imported_in: string;\n }>(\n `SELECT gs.symbol, d.relative_path AS imported_in\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 d.relative_path LIKE ?\n AND m.role = 2\n AND NOT EXISTS (\n SELECT 1\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 = d.id\n )\n ORDER BY d.relative_path, gs.symbol`,\n `%${filePattern}%`,\n );\n\n return rows.map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n importedIn: r.imported_in,\n }));\n}\n"],"mappings":";;;;;AAQO,SAAS,QAAQ,IAAkB,aAAqC;AAC7E,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUA,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,UAAU,EAAE,aAAa;AAAA,EAC3B,EAAE;AACJ;AAKO,SAAS,WAAW,IAAkB,eAAuC;AAClF,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,IAAI,aAAa;AAAA,EACnB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,QAAQ,CAAC,EACvC,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,UAAU,EAAE;AAAA,EACd,EAAE;AACN;AAMO,SAAS,cAAc,IAAkB,aAA2C;AACzF,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAgBA,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,YAAY,EAAE;AAAA,EAChB,EAAE;AACJ;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/coupling.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { CouplingResult } from '../types.js';\n\n/**\n * Measure coupling between two files: how many symbols do they share\n * (symbols defined in one and referenced in the other, or vice versa).\n */\nexport function coupling(\n db: ScipDatabase,\n file1: string,\n file2: string,\n): CouplingResult {\n const row = db.get<{ shared: number }>(\n `SELECT COUNT(DISTINCT gs.id) AS shared\n FROM global_symbols gs\n WHERE (\n -- Defined in file1, referenced in file2\n EXISTS (\n SELECT 1 FROM defn_enclosing_ranges der\n JOIN documents d ON der.document_id = d.id\n WHERE der.symbol_id = gs.id AND d.relative_path LIKE ?\n )\n AND EXISTS (\n SELECT 1 FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n WHERE m.symbol_id = gs.id AND m.role != 1 AND d.relative_path LIKE ?\n )\n ) OR (\n -- Defined in file2, referenced in file1\n EXISTS (\n SELECT 1 FROM defn_enclosing_ranges der\n JOIN documents d ON der.document_id = d.id\n WHERE der.symbol_id = gs.id AND d.relative_path LIKE ?\n )\n AND EXISTS (\n SELECT 1 FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n WHERE m.symbol_id = gs.id AND m.role != 1 AND d.relative_path LIKE ?\n )\n )`,\n `%${file1}%`, `%${file2}%`,\n `%${file2}%`, `%${file1}%`,\n );\n\n return {\n file1,\n file2,\n sharedSymbols: row?.shared ?? 0,\n };\n}\n\n/**\n * Find the most coupled file pairs in the codebase.\n */\nexport function topCoupling(\n db: ScipDatabase,\n opts: { limit?: number; scope?: string } = {},\n): CouplingResult[] {\n const { limit = 20, scope } = opts;\n const scopeFilter = scope\n ? `AND d1.relative_path LIKE '%${scope}%' AND d2.relative_path LIKE '%${scope}%'`\n : '';\n\n // Find file pairs that share the most symbols (one defines, other references)\n const rows = db.all<{\n file1: string;\n file2: string;\n shared: number;\n }>(\n `SELECT\n def_d.relative_path AS file1,\n ref_d.relative_path AS file2,\n COUNT(DISTINCT gs.id) AS shared\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 AND def_d.id != ref_d.id\n ${db.pathExclusionsFor('def_d', 'ref_d')}\n ${scopeFilter}\n GROUP BY def_d.id, ref_d.id\n ORDER BY shared DESC\n LIMIT ?`,\n limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.file1) && !db.isIgnored(r.file2))\n .map((r) => ({\n file1: r.file1,\n file2: r.file2,\n sharedSymbols: r.shared,\n }));\n}\n"],"mappings":";AAOO,SAAS,SACd,IACA,OACA,OACgB;AAChB,QAAM,MAAM,GAAG;AAAA,IACb;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,IA6BA,IAAI,KAAK;AAAA,IAAK,IAAI,KAAK;AAAA,IACvB,IAAI,KAAK;AAAA,IAAK,IAAI,KAAK;AAAA,EACzB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,eAAe,KAAK,UAAU;AAAA,EAChC;AACF;AAKO,SAAS,YACd,IACA,OAA2C,CAAC,GAC1B;AAClB,QAAM,EAAE,QAAQ,IAAI,MAAM,IAAI;AAC9B,QAAM,cAAc,QAChB,+BAA+B,KAAK,kCAAkC,KAAK,OAC3E;AAGJ,QAAM,OAAO,GAAG;AAAA,IAKd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAYI,GAAG,kBAAkB,SAAS,OAAO,CAAC;AAAA,QACtC,WAAW;AAAA;AAAA;AAAA;AAAA,IAIf;AAAA,EACF;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,KAAK,KAAK,CAAC,GAAG,UAAU,EAAE,KAAK,CAAC,EAC9D,IAAI,CAAC,OAAO;AAAA,IACX,OAAO,EAAE;AAAA,IACT,OAAO,EAAE;AAAA,IACT,eAAe,EAAE;AAAA,EACnB,EAAE;AACN;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/cycles.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { buildFileDepGraph } from '../query-support.js';\nimport type { CycleResult } from '../types.js';\n\n/**\n * Detect circular dependency chains between files.\n * A cycle exists when file A depends on B, B depends on C, and C depends on A.\n *\n * Uses the same dependency edges as the `deps` command (symbol definitions\n * referenced across files), then runs DFS cycle detection.\n */\nexport function cycles(\n db: ScipDatabase,\n opts: { scope?: string; maxDepth?: number } = {},\n): CycleResult[] {\n const { scope, maxDepth = 10 } = opts;\n const graph = buildFileDepGraph(db, scope);\n\n // DFS cycle detection\n const allCycles: CycleResult[] = [];\n const visited = new Set<string>();\n const inStack = new Set<string>();\n const stack: string[] = [];\n\n function dfs(node: string, depth: number): void {\n if (depth > maxDepth) return;\n if (inStack.has(node)) {\n // Found a cycle — extract it from the stack\n const cycleStart = stack.indexOf(node);\n if (cycleStart !== -1) {\n const cyclePath = stack.slice(cycleStart).concat(node);\n // Normalize: start from the lexicographically smallest file\n const minIdx = cyclePath.indexOf(\n cyclePath.reduce((a, b) => (a < b ? a : b)),\n );\n const normalized = [\n ...cyclePath.slice(minIdx, -1),\n ...cyclePath.slice(0, minIdx),\n cyclePath[minIdx]!,\n ];\n // Deduplicate\n const key = normalized.join(' -> ');\n if (!seenCycles.has(key)) {\n seenCycles.add(key);\n allCycles.push({ path: normalized });\n }\n }\n return;\n }\n if (visited.has(node)) return;\n\n visited.add(node);\n inStack.add(node);\n stack.push(node);\n\n const neighbors = graph.get(node);\n if (neighbors) {\n for (const neighbor of neighbors) {\n dfs(neighbor, depth + 1);\n }\n }\n\n stack.pop();\n inStack.delete(node);\n }\n\n const seenCycles = new Set<string>();\n for (const node of graph.keys()) {\n if (!visited.has(node)) {\n dfs(node, 0);\n }\n }\n\n // Sort by cycle length (shorter cycles are more actionable)\n allCycles.sort((a, b) => a.path.length - b.path.length);\n\n return allCycles;\n}\n"],"mappings":";;;;;AAWO,SAAS,OACd,IACA,OAA8C,CAAC,GAChC;AACf,QAAM,EAAE,OAAO,WAAW,GAAG,IAAI;AACjC,QAAM,QAAQ,kBAAkB,IAAI,KAAK;AAGzC,QAAM,YAA2B,CAAC;AAClC,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,QAAkB,CAAC;AAEzB,WAAS,IAAI,MAAc,OAAqB;AAC9C,QAAI,QAAQ,SAAU;AACtB,QAAI,QAAQ,IAAI,IAAI,GAAG;AAErB,YAAM,aAAa,MAAM,QAAQ,IAAI;AACrC,UAAI,eAAe,IAAI;AACrB,cAAM,YAAY,MAAM,MAAM,UAAU,EAAE,OAAO,IAAI;AAErD,cAAM,SAAS,UAAU;AAAA,UACvB,UAAU,OAAO,CAAC,GAAG,MAAO,IAAI,IAAI,IAAI,CAAE;AAAA,QAC5C;AACA,cAAM,aAAa;AAAA,UACjB,GAAG,UAAU,MAAM,QAAQ,EAAE;AAAA,UAC7B,GAAG,UAAU,MAAM,GAAG,MAAM;AAAA,UAC5B,UAAU,MAAM;AAAA,QAClB;AAEA,cAAM,MAAM,WAAW,KAAK,MAAM;AAClC,YAAI,CAAC,WAAW,IAAI,GAAG,GAAG;AACxB,qBAAW,IAAI,GAAG;AAClB,oBAAU,KAAK,EAAE,MAAM,WAAW,CAAC;AAAA,QACrC;AAAA,MACF;AACA;AAAA,IACF;AACA,QAAI,QAAQ,IAAI,IAAI,EAAG;AAEvB,YAAQ,IAAI,IAAI;AAChB,YAAQ,IAAI,IAAI;AAChB,UAAM,KAAK,IAAI;AAEf,UAAM,YAAY,MAAM,IAAI,IAAI;AAChC,QAAI,WAAW;AACb,iBAAW,YAAY,WAAW;AAChC,YAAI,UAAU,QAAQ,CAAC;AAAA,MACzB;AAAA,IACF;AAEA,UAAM,IAAI;AACV,YAAQ,OAAO,IAAI;AAAA,EACrB;AAEA,QAAM,aAAa,oBAAI,IAAY;AACnC,aAAW,QAAQ,MAAM,KAAK,GAAG;AAC/B,QAAI,CAAC,QAAQ,IAAI,IAAI,GAAG;AACtB,UAAI,MAAM,CAAC;AAAA,IACb;AAAA,EACF;AAGA,YAAU,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,SAAS,EAAE,KAAK,MAAM;AAEtD,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queries/similar-files.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { buildFileDepGraph } from '../query-support.js';\nimport type { SimilarFileResult } from '../types.js';\n\n/**\n * Find files with similar dependency profiles.\n *\n * Two files that depend on (import from) the same set of other files\n * are structurally doing similar work. High Jaccard similarity between\n * their dependency sets = likely copy-paste variants or consolidation candidates.\n */\nexport function similarFiles(\n db: ScipDatabase,\n opts: {\n minSimilarity?: number;\n limit?: number;\n scope?: string;\n minDeps?: number;\n filePattern?: string;\n } = {},\n): SimilarFileResult[] {\n const { minSimilarity = 0.5, limit = 20, scope, minDeps = 3, filePattern } = opts;\n\n // Build dependency profile for each file\n const profiles = buildFileProfiles(db, { scope, minDeps });\n\n const results: SimilarFileResult[] = [];\n\n if (filePattern) {\n // Compare one file against all others\n const target = profiles.find((p) => p.file.includes(filePattern));\n if (!target) return [];\n\n for (const candidate of profiles) {\n if (candidate.file === target.file) continue;\n const result = compareProfiles(target, candidate, minSimilarity);\n if (result) results.push(result);\n }\n } else {\n // Pairwise comparison across all files\n for (let i = 0; i < profiles.length; i++) {\n for (let j = i + 1; j < profiles.length; j++) {\n const result = compareProfiles(profiles[i]!, profiles[j]!, minSimilarity);\n if (result) results.push(result);\n }\n if (results.length > limit * 5) break;\n }\n }\n\n results.sort((a, b) => b.similarity - a.similarity);\n return results.slice(0, limit);\n}\n\n// ── Internal ───────────────────────────────────────────────\n\ninterface FileProfile {\n file: string;\n deps: Set<string>;\n}\n\nfunction buildFileProfiles(\n db: ScipDatabase,\n opts: { scope?: string; minDeps: number },\n): FileProfile[] {\n const { scope, minDeps } = opts;\n const depMap = buildFileDepGraph(db, scope);\n const universalDeps = findUniversalDependencies(depMap);\n\n // Filter to files with enough deps\n const profiles: FileProfile[] = [];\n for (const [file, deps] of depMap) {\n if (deps.size >= minDeps) {\n profiles.push({\n file,\n deps: new Set([...deps].filter((dep) => !universalDeps.has(dep))),\n });\n }\n }\n\n return profiles;\n}\n\nfunction findUniversalDependencies(\n depMap: Map<string, Set<string>>,\n): Set<string> {\n const universalDeps = new Set<string>();\n const fileCount = depMap.size;\n if (fileCount === 0) return universalDeps;\n\n const depCounts = new Map<string, number>();\n for (const deps of depMap.values()) {\n for (const dep of deps) {\n depCounts.set(dep, (depCounts.get(dep) ?? 0) + 1);\n }\n }\n\n for (const [dep, count] of depCounts) {\n if (count / fileCount > 0.5) {\n universalDeps.add(dep);\n }\n }\n\n return universalDeps;\n}\n\nfunction compareProfiles(\n a: FileProfile,\n b: FileProfile,\n minSimilarity: number,\n): SimilarFileResult | null {\n const shared = new Set<string>();\n for (const dep of a.deps) {\n if (b.deps.has(dep)) shared.add(dep);\n }\n\n if (shared.size === 0) return null;\n\n const unionSize = new Set([...a.deps, ...b.deps]).size;\n const similarity = shared.size / unionSize;\n\n if (similarity < minSimilarity) return null;\n\n const uniqueA: string[] = [];\n for (const dep of a.deps) {\n if (!b.deps.has(dep)) uniqueA.push(dep);\n }\n const uniqueB: string[] = [];\n for (const dep of b.deps) {\n if (!a.deps.has(dep)) uniqueB.push(dep);\n }\n\n return {\n fileA: a.file,\n fileB: b.file,\n similarity,\n sharedDeps: [...shared],\n uniqueToA: uniqueA,\n uniqueToB: uniqueB,\n };\n}\n"],"mappings":"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","names":[]}