scip-query 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/IMPROVEMENTS.md +143 -0
  2. package/PLAN.md +320 -0
  3. package/README.md +1213 -0
  4. package/dist/chunk-2QZ23IBN.js +55 -0
  5. package/dist/chunk-2QZ23IBN.js.map +1 -0
  6. package/dist/chunk-36OMT7ZJ.js +144 -0
  7. package/dist/chunk-36OMT7ZJ.js.map +1 -0
  8. package/dist/chunk-3E2X7RIE.js +101 -0
  9. package/dist/chunk-3E2X7RIE.js.map +1 -0
  10. package/dist/chunk-3UOUTZQT.js +45 -0
  11. package/dist/chunk-3UOUTZQT.js.map +1 -0
  12. package/dist/chunk-3ZZJVBIO.js +88 -0
  13. package/dist/chunk-3ZZJVBIO.js.map +1 -0
  14. package/dist/chunk-4TYLS5XX.js +10 -0
  15. package/dist/chunk-4TYLS5XX.js.map +1 -0
  16. package/dist/chunk-5FGUEU7N.js +101 -0
  17. package/dist/chunk-5FGUEU7N.js.map +1 -0
  18. package/dist/chunk-5WTJAXY2.js +61 -0
  19. package/dist/chunk-5WTJAXY2.js.map +1 -0
  20. package/dist/chunk-6NBLIDF4.js +24 -0
  21. package/dist/chunk-6NBLIDF4.js.map +1 -0
  22. package/dist/chunk-6SXADWLW.js +43 -0
  23. package/dist/chunk-6SXADWLW.js.map +1 -0
  24. package/dist/chunk-6VJ6Q7IE.js +65 -0
  25. package/dist/chunk-6VJ6Q7IE.js.map +1 -0
  26. package/dist/chunk-7OZPA5OO.js +258 -0
  27. package/dist/chunk-7OZPA5OO.js.map +1 -0
  28. package/dist/chunk-BEPIEVLR.js +76 -0
  29. package/dist/chunk-BEPIEVLR.js.map +1 -0
  30. package/dist/chunk-BFSCMC22.js +42 -0
  31. package/dist/chunk-BFSCMC22.js.map +1 -0
  32. package/dist/chunk-BP2ATLK2.js +110 -0
  33. package/dist/chunk-BP2ATLK2.js.map +1 -0
  34. package/dist/chunk-CM454WL3.js +114 -0
  35. package/dist/chunk-CM454WL3.js.map +1 -0
  36. package/dist/chunk-DCKMSTJ4.js +74 -0
  37. package/dist/chunk-DCKMSTJ4.js.map +1 -0
  38. package/dist/chunk-DEZKCZXD.js +40 -0
  39. package/dist/chunk-DEZKCZXD.js.map +1 -0
  40. package/dist/chunk-DVWGWHFW.js +99 -0
  41. package/dist/chunk-DVWGWHFW.js.map +1 -0
  42. package/dist/chunk-EMDQWNYR.js +102 -0
  43. package/dist/chunk-EMDQWNYR.js.map +1 -0
  44. package/dist/chunk-FFSWWE5O.js +33 -0
  45. package/dist/chunk-FFSWWE5O.js.map +1 -0
  46. package/dist/chunk-FGXRVW7G.js +73 -0
  47. package/dist/chunk-FGXRVW7G.js.map +1 -0
  48. package/dist/chunk-FUHJCHS4.js +158 -0
  49. package/dist/chunk-FUHJCHS4.js.map +1 -0
  50. package/dist/chunk-GJFURBEW.js +64 -0
  51. package/dist/chunk-GJFURBEW.js.map +1 -0
  52. package/dist/chunk-GTILYBH6.js +102 -0
  53. package/dist/chunk-GTILYBH6.js.map +1 -0
  54. package/dist/chunk-JJP7KQND.js +1 -0
  55. package/dist/chunk-JJP7KQND.js.map +1 -0
  56. package/dist/chunk-JKP5GH6T.js +213 -0
  57. package/dist/chunk-JKP5GH6T.js.map +1 -0
  58. package/dist/chunk-KCBMVQL5.js +38 -0
  59. package/dist/chunk-KCBMVQL5.js.map +1 -0
  60. package/dist/chunk-KVSW5KYP.js +78 -0
  61. package/dist/chunk-KVSW5KYP.js.map +1 -0
  62. package/dist/chunk-LAWMH22O.js +172 -0
  63. package/dist/chunk-LAWMH22O.js.map +1 -0
  64. package/dist/chunk-LB7OS35Q.js +72 -0
  65. package/dist/chunk-LB7OS35Q.js.map +1 -0
  66. package/dist/chunk-LUSIFBXO.js +57 -0
  67. package/dist/chunk-LUSIFBXO.js.map +1 -0
  68. package/dist/chunk-MBVNHJVN.js +44 -0
  69. package/dist/chunk-MBVNHJVN.js.map +1 -0
  70. package/dist/chunk-MGNMHKX3.js +15 -0
  71. package/dist/chunk-MGNMHKX3.js.map +1 -0
  72. package/dist/chunk-N5KEREIA.js +41 -0
  73. package/dist/chunk-N5KEREIA.js.map +1 -0
  74. package/dist/chunk-NDSQYIWT.js +71 -0
  75. package/dist/chunk-NDSQYIWT.js.map +1 -0
  76. package/dist/chunk-NUZ4OMU3.js +28 -0
  77. package/dist/chunk-NUZ4OMU3.js.map +1 -0
  78. package/dist/chunk-QOV2R2WT.js +170 -0
  79. package/dist/chunk-QOV2R2WT.js.map +1 -0
  80. package/dist/chunk-SEFSL2GF.js +78 -0
  81. package/dist/chunk-SEFSL2GF.js.map +1 -0
  82. package/dist/chunk-T6ARFSBZ.js +103 -0
  83. package/dist/chunk-T6ARFSBZ.js.map +1 -0
  84. package/dist/chunk-TBP6BICL.js +46 -0
  85. package/dist/chunk-TBP6BICL.js.map +1 -0
  86. package/dist/chunk-TDNNOR6D.js +97 -0
  87. package/dist/chunk-TDNNOR6D.js.map +1 -0
  88. package/dist/chunk-TSPZOMHC.js +195 -0
  89. package/dist/chunk-TSPZOMHC.js.map +1 -0
  90. package/dist/chunk-UNTPVD36.js +55 -0
  91. package/dist/chunk-UNTPVD36.js.map +1 -0
  92. package/dist/chunk-VRUJH4BO.js +88 -0
  93. package/dist/chunk-VRUJH4BO.js.map +1 -0
  94. package/dist/chunk-VZ7AMAFL.js +76 -0
  95. package/dist/chunk-VZ7AMAFL.js.map +1 -0
  96. package/dist/chunk-XFXDXEUN.js +24 -0
  97. package/dist/chunk-XFXDXEUN.js.map +1 -0
  98. package/dist/chunk-YZAA4LYG.js +169 -0
  99. package/dist/chunk-YZAA4LYG.js.map +1 -0
  100. package/dist/chunk-Z73NYSBZ.js +92 -0
  101. package/dist/chunk-Z73NYSBZ.js.map +1 -0
  102. package/dist/chunk-ZJRYBOEE.js +125 -0
  103. package/dist/chunk-ZJRYBOEE.js.map +1 -0
  104. package/dist/cli.js +5798 -0
  105. package/dist/cli.js.map +1 -0
  106. package/dist/db-BxaevAyc.d.ts +683 -0
  107. package/dist/index.d.ts +254 -0
  108. package/dist/index.js +1271 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/postinstall.js +167 -0
  111. package/dist/postinstall.js.map +1 -0
  112. package/dist/queries/affected.d.ts +14 -0
  113. package/dist/queries/affected.js +9 -0
  114. package/dist/queries/affected.js.map +1 -0
  115. package/dist/queries/bottlenecks.d.ts +18 -0
  116. package/dist/queries/bottlenecks.js +8 -0
  117. package/dist/queries/bottlenecks.js.map +1 -0
  118. package/dist/queries/by-kind.d.ts +20 -0
  119. package/dist/queries/by-kind.js +10 -0
  120. package/dist/queries/by-kind.js.map +1 -0
  121. package/dist/queries/call-graph.d.ts +13 -0
  122. package/dist/queries/call-graph.js +9 -0
  123. package/dist/queries/call-graph.js.map +1 -0
  124. package/dist/queries/change-surface.d.ts +10 -0
  125. package/dist/queries/change-surface.js +9 -0
  126. package/dist/queries/change-surface.js.map +1 -0
  127. package/dist/queries/clean-signature.d.ts +9 -0
  128. package/dist/queries/clean-signature.js +7 -0
  129. package/dist/queries/clean-signature.js.map +1 -0
  130. package/dist/queries/code.d.ts +17 -0
  131. package/dist/queries/code.js +9 -0
  132. package/dist/queries/code.js.map +1 -0
  133. package/dist/queries/complexity-hotspots.d.ts +19 -0
  134. package/dist/queries/complexity-hotspots.js +9 -0
  135. package/dist/queries/complexity-hotspots.js.map +1 -0
  136. package/dist/queries/complexity.d.ts +13 -0
  137. package/dist/queries/complexity.js +9 -0
  138. package/dist/queries/complexity.js.map +1 -0
  139. package/dist/queries/convergence.d.ts +11 -0
  140. package/dist/queries/convergence.js +9 -0
  141. package/dist/queries/convergence.js.map +1 -0
  142. package/dist/queries/coupling.d.ts +17 -0
  143. package/dist/queries/coupling.js +9 -0
  144. package/dist/queries/coupling.js.map +1 -0
  145. package/dist/queries/cycles.d.ts +16 -0
  146. package/dist/queries/cycles.js +8 -0
  147. package/dist/queries/cycles.js.map +1 -0
  148. package/dist/queries/dataflow.d.ts +19 -0
  149. package/dist/queries/dataflow.js +9 -0
  150. package/dist/queries/dataflow.js.map +1 -0
  151. package/dist/queries/dead.d.ts +10 -0
  152. package/dist/queries/dead.js +9 -0
  153. package/dist/queries/dead.js.map +1 -0
  154. package/dist/queries/deep-chains.d.ts +16 -0
  155. package/dist/queries/deep-chains.js +8 -0
  156. package/dist/queries/deep-chains.js.map +1 -0
  157. package/dist/queries/deps.d.ts +9 -0
  158. package/dist/queries/deps.js +9 -0
  159. package/dist/queries/deps.js.map +1 -0
  160. package/dist/queries/diff-impact.d.ts +13 -0
  161. package/dist/queries/diff-impact.js +9 -0
  162. package/dist/queries/diff-impact.js.map +1 -0
  163. package/dist/queries/doc-coverage.d.ts +14 -0
  164. package/dist/queries/doc-coverage.js +8 -0
  165. package/dist/queries/doc-coverage.js.map +1 -0
  166. package/dist/queries/drift.d.ts +25 -0
  167. package/dist/queries/drift.js +8 -0
  168. package/dist/queries/drift.js.map +1 -0
  169. package/dist/queries/extract-candidates.d.ts +25 -0
  170. package/dist/queries/extract-candidates.js +9 -0
  171. package/dist/queries/extract-candidates.js.map +1 -0
  172. package/dist/queries/fan.d.ts +29 -0
  173. package/dist/queries/fan.js +14 -0
  174. package/dist/queries/fan.js.map +1 -0
  175. package/dist/queries/files.d.ts +6 -0
  176. package/dist/queries/files.js +7 -0
  177. package/dist/queries/files.js.map +1 -0
  178. package/dist/queries/health.d.ts +18 -0
  179. package/dist/queries/health.js +21 -0
  180. package/dist/queries/health.js.map +1 -0
  181. package/dist/queries/hierarchy.d.ts +13 -0
  182. package/dist/queries/hierarchy.js +8 -0
  183. package/dist/queries/hierarchy.js.map +1 -0
  184. package/dist/queries/hotspots.d.ts +13 -0
  185. package/dist/queries/hotspots.js +8 -0
  186. package/dist/queries/hotspots.js.map +1 -0
  187. package/dist/queries/imports.d.ts +19 -0
  188. package/dist/queries/imports.js +12 -0
  189. package/dist/queries/imports.js.map +1 -0
  190. package/dist/queries/index.d.ts +47 -0
  191. package/dist/queries/index.js +207 -0
  192. package/dist/queries/index.js.map +1 -0
  193. package/dist/queries/isolated.d.ts +14 -0
  194. package/dist/queries/isolated.js +9 -0
  195. package/dist/queries/isolated.js.map +1 -0
  196. package/dist/queries/members.d.ts +10 -0
  197. package/dist/queries/members.js +8 -0
  198. package/dist/queries/members.js.map +1 -0
  199. package/dist/queries/methods.d.ts +6 -0
  200. package/dist/queries/methods.js +8 -0
  201. package/dist/queries/methods.js.map +1 -0
  202. package/dist/queries/outline.d.ts +10 -0
  203. package/dist/queries/outline.js +8 -0
  204. package/dist/queries/outline.js.map +1 -0
  205. package/dist/queries/passthrough-candidates.d.ts +18 -0
  206. package/dist/queries/passthrough-candidates.js +9 -0
  207. package/dist/queries/passthrough-candidates.js.map +1 -0
  208. package/dist/queries/redundant-reexports.d.ts +22 -0
  209. package/dist/queries/redundant-reexports.js +8 -0
  210. package/dist/queries/redundant-reexports.js.map +1 -0
  211. package/dist/queries/refs.d.ts +6 -0
  212. package/dist/queries/refs.js +7 -0
  213. package/dist/queries/refs.js.map +1 -0
  214. package/dist/queries/similar-chains.d.ts +29 -0
  215. package/dist/queries/similar-chains.js +8 -0
  216. package/dist/queries/similar-chains.js.map +1 -0
  217. package/dist/queries/similar-files.d.ts +19 -0
  218. package/dist/queries/similar-files.js +8 -0
  219. package/dist/queries/similar-files.js.map +1 -0
  220. package/dist/queries/similar-signatures.d.ts +21 -0
  221. package/dist/queries/similar-signatures.js +8 -0
  222. package/dist/queries/similar-signatures.js.map +1 -0
  223. package/dist/queries/similar.d.ts +34 -0
  224. package/dist/queries/similar.js +11 -0
  225. package/dist/queries/similar.js.map +1 -0
  226. package/dist/queries/slice.d.ts +21 -0
  227. package/dist/queries/slice.js +9 -0
  228. package/dist/queries/slice.js.map +1 -0
  229. package/dist/queries/stale-abstractions.d.ts +18 -0
  230. package/dist/queries/stale-abstractions.js +9 -0
  231. package/dist/queries/stale-abstractions.js.map +1 -0
  232. package/dist/queries/stats.d.ts +6 -0
  233. package/dist/queries/stats.js +7 -0
  234. package/dist/queries/stats.js.map +1 -0
  235. package/dist/queries/surface.d.ts +7 -0
  236. package/dist/queries/surface.js +8 -0
  237. package/dist/queries/surface.js.map +1 -0
  238. package/dist/queries/symbols.d.ts +6 -0
  239. package/dist/queries/symbols.js +9 -0
  240. package/dist/queries/symbols.js.map +1 -0
  241. package/dist/queries/system.d.ts +7 -0
  242. package/dist/queries/system.js +9 -0
  243. package/dist/queries/system.js.map +1 -0
  244. package/dist/queries/test-coverage.d.ts +22 -0
  245. package/dist/queries/test-coverage.js +11 -0
  246. package/dist/queries/test-coverage.js.map +1 -0
  247. package/dist/queries/trace.d.ts +6 -0
  248. package/dist/queries/trace.js +8 -0
  249. package/dist/queries/trace.js.map +1 -0
  250. package/dist/queries/wrapper-candidates.d.ts +17 -0
  251. package/dist/queries/wrapper-candidates.js +9 -0
  252. package/dist/queries/wrapper-candidates.js.map +1 -0
  253. package/dist/reindex-worker.js +368 -0
  254. package/dist/reindex-worker.js.map +1 -0
  255. package/docs/AGENT_GUIDE.md +359 -0
  256. package/package.json +70 -0
  257. package/reports/debloat/2026-04-10-scip-query-self-audit.md +161 -0
  258. package/skills/concrete-plan/SKILL.md +318 -0
  259. package/skills/scip-debloat/SKILL.md +413 -0
  260. package/skills/scip-explore/SKILL.md +235 -0
  261. package/skills/scip-verify/SKILL.md +323 -0
  262. package/src/cli.ts +1480 -0
  263. package/src/config.ts +117 -0
  264. package/src/db.ts +127 -0
  265. package/src/gitignore-filter.ts +143 -0
  266. package/src/index.ts +11 -0
  267. package/src/postinstall.ts +8 -0
  268. package/src/queries/affected.ts +86 -0
  269. package/src/queries/bottlenecks.ts +67 -0
  270. package/src/queries/by-kind.ts +204 -0
  271. package/src/queries/call-graph.ts +66 -0
  272. package/src/queries/change-surface.ts +110 -0
  273. package/src/queries/clean-signature.ts +22 -0
  274. package/src/queries/code.ts +101 -0
  275. package/src/queries/complexity-hotspots.ts +119 -0
  276. package/src/queries/complexity.ts +152 -0
  277. package/src/queries/convergence.ts +82 -0
  278. package/src/queries/coupling.ts +99 -0
  279. package/src/queries/cycles.ts +78 -0
  280. package/src/queries/dataflow.ts +128 -0
  281. package/src/queries/dead.ts +122 -0
  282. package/src/queries/deep-chains.ts +59 -0
  283. package/src/queries/deps.ts +46 -0
  284. package/src/queries/diff-impact.ts +204 -0
  285. package/src/queries/doc-coverage.ts +86 -0
  286. package/src/queries/drift.ts +224 -0
  287. package/src/queries/extract-candidates.ts +167 -0
  288. package/src/queries/fan.ts +148 -0
  289. package/src/queries/files.ts +16 -0
  290. package/src/queries/health.ts +324 -0
  291. package/src/queries/hierarchy.ts +49 -0
  292. package/src/queries/hotspots.ts +53 -0
  293. package/src/queries/imports.ts +95 -0
  294. package/src/queries/index.ts +45 -0
  295. package/src/queries/isolated.ts +67 -0
  296. package/src/queries/members.ts +54 -0
  297. package/src/queries/methods.ts +27 -0
  298. package/src/queries/outline.ts +52 -0
  299. package/src/queries/passthrough-candidates.ts +94 -0
  300. package/src/queries/redundant-reexports.ts +170 -0
  301. package/src/queries/refs.ts +27 -0
  302. package/src/queries/similar-chains.ts +314 -0
  303. package/src/queries/similar-files.ts +140 -0
  304. package/src/queries/similar-signatures.ts +151 -0
  305. package/src/queries/similar.ts +305 -0
  306. package/src/queries/slice.ts +154 -0
  307. package/src/queries/stale-abstractions.ts +82 -0
  308. package/src/queries/stats.ts +22 -0
  309. package/src/queries/surface.ts +34 -0
  310. package/src/queries/symbols.ts +39 -0
  311. package/src/queries/system.ts +86 -0
  312. package/src/queries/test-coverage.ts +106 -0
  313. package/src/queries/trace.ts +55 -0
  314. package/src/queries/wrapper-candidates.ts +112 -0
  315. package/src/query-support.ts +226 -0
  316. package/src/reindex/detect.ts +58 -0
  317. package/src/reindex/index.ts +153 -0
  318. package/src/reindex/indexers.ts +220 -0
  319. package/src/reindex/install.ts +125 -0
  320. package/src/reindex-worker.ts +35 -0
  321. package/src/setup.ts +202 -0
  322. package/src/symbol-parser.ts +278 -0
  323. package/src/types.ts +654 -0
  324. package/src/watch.ts +274 -0
  325. package/tests/gitignore-filter.test.ts +48 -0
  326. package/tests/queries.test.ts +300 -0
  327. package/tests/symbol-parser.test.ts +157 -0
  328. package/tsconfig.json +20 -0
  329. package/tsup.config.ts +40 -0
  330. package/vitest.config.ts +7 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/change-surface.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { TEST_FILE_PATTERNS, testFileMatchSql } from '../query-support.js';\nimport type { ChangeSurfaceEntry, ChangeSurfaceResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Pre-change briefing for a file. For each symbol defined in the file,\n * reports external consumer count, test coverage, and risk level.\n */\nexport function changeSurface(\n db: ScipDatabase,\n filePattern: string,\n): ChangeSurfaceResult | null {\n // Find the file\n const doc = db.get<{ id: number; relative_path: string }>(\n `SELECT id, relative_path FROM documents\n WHERE relative_path LIKE ?\n ${db.pathExclusionsFor('documents')}\n LIMIT 1`,\n `%${filePattern}%`,\n );\n\n if (!doc || db.isIgnored(doc.relative_path)) return null;\n\n // Get all symbols defined in this file, excluding typeLiterals\n const syms = db.all<{\n symbol_id: number;\n symbol: string;\n start_line: number;\n end_line: number;\n }>(\n `SELECT DISTINCT gs.id AS symbol_id, gs.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 WHERE der.document_id = ?\n ${db.symbolNoiseFor('gs')}\n ORDER BY der.start_line`,\n doc.id,\n );\n\n const testPatternSql = testFileMatchSql('ref_d', TEST_FILE_PATTERNS);\n\n const symbols: ChangeSurfaceEntry[] = [];\n let totalExternalConsumers = 0;\n let coveredCount = 0;\n\n for (const sym of syms) {\n // Count external consumers: mentions with role=0 from different documents\n const consumerRow = db.get<{ consumer_count: number }>(\n `SELECT COUNT(DISTINCT c.document_id) AS consumer_count\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n WHERE m.symbol_id = ?\n AND m.role = 0\n AND c.document_id != ?`,\n sym.symbol_id,\n doc.id,\n );\n\n const externalConsumers = consumerRow?.consumer_count ?? 0;\n\n // Find test files that reference this symbol\n const testFiles = db.all<{ relative_path: string }>(\n `SELECT DISTINCT ref_d.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 WHERE m.symbol_id = ?\n AND m.role = 0\n AND (${testPatternSql})\n ORDER BY ref_d.relative_path`,\n sym.symbol_id,\n ).map((r) => r.relative_path);\n\n const hasTests = testFiles.length > 0;\n if (hasTests) coveredCount++;\n\n // Risk level determination\n let riskLevel: 'low' | 'medium' | 'high';\n if (externalConsumers > 10 && !hasTests) {\n riskLevel = 'high';\n } else if (externalConsumers > 5 || !hasTests) {\n riskLevel = 'medium';\n } else {\n riskLevel = 'low';\n }\n\n totalExternalConsumers += externalConsumers;\n\n symbols.push({\n symbol: sym.symbol,\n shortName: shortenSymbol(sym.symbol),\n startLine: sym.start_line,\n endLine: sym.end_line,\n externalConsumers,\n testFiles,\n riskLevel,\n });\n }\n\n const testCoveragePercent =\n symbols.length > 0 ? Math.round((coveredCount / symbols.length) * 100) : 0;\n\n return {\n file: doc.relative_path,\n symbols,\n totalExternalConsumers,\n testCoveragePercent,\n };\n}\n"],"mappings":";;;;;;;;;AASO,SAAS,cACd,IACA,aAC4B;AAE5B,QAAM,MAAM,GAAG;AAAA,IACb;AAAA;AAAA,SAEK,GAAG,kBAAkB,WAAW,CAAC;AAAA;AAAA,IAEtC,IAAI,WAAW;AAAA,EACjB;AAEA,MAAI,CAAC,OAAO,GAAG,UAAU,IAAI,aAAa,EAAG,QAAO;AAGpD,QAAM,OAAO,GAAG;AAAA,IAMd;AAAA;AAAA;AAAA;AAAA,QAII,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA,IAE3B,IAAI;AAAA,EACN;AAEA,QAAM,iBAAiB,iBAAiB,SAAS,kBAAkB;AAEnE,QAAM,UAAgC,CAAC;AACvC,MAAI,yBAAyB;AAC7B,MAAI,eAAe;AAEnB,aAAW,OAAO,MAAM;AAEtB,UAAM,cAAc,GAAG;AAAA,MACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,IAAI;AAAA,MACJ,IAAI;AAAA,IACN;AAEA,UAAM,oBAAoB,aAAa,kBAAkB;AAGzD,UAAM,YAAY,GAAG;AAAA,MACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAMS,cAAc;AAAA;AAAA,MAEvB,IAAI;AAAA,IACN,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa;AAE5B,UAAM,WAAW,UAAU,SAAS;AACpC,QAAI,SAAU;AAGd,QAAI;AACJ,QAAI,oBAAoB,MAAM,CAAC,UAAU;AACvC,kBAAY;AAAA,IACd,WAAW,oBAAoB,KAAK,CAAC,UAAU;AAC7C,kBAAY;AAAA,IACd,OAAO;AACL,kBAAY;AAAA,IACd;AAEA,8BAA0B;AAE1B,YAAQ,KAAK;AAAA,MACX,QAAQ,IAAI;AAAA,MACZ,WAAW,cAAc,IAAI,MAAM;AAAA,MACnC,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,sBACJ,QAAQ,SAAS,IAAI,KAAK,MAAO,eAAe,QAAQ,SAAU,GAAG,IAAI;AAE3E,SAAO;AAAA,IACL,MAAM,IAAI;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,76 @@
1
+ import {
2
+ findFirstSymbolMatch
3
+ } from "./chunk-FUHJCHS4.js";
4
+ import {
5
+ shortenSymbol
6
+ } from "./chunk-QOV2R2WT.js";
7
+
8
+ // src/queries/code.ts
9
+ import { readFileSync } from "fs";
10
+ import { join } from "path";
11
+ function code(db, symbolPattern, opts = {}) {
12
+ const { context = 0 } = opts;
13
+ const fileLineMatch = symbolPattern.match(/^(.+\.\w+):(\d+)-(\d+)$/);
14
+ if (fileLineMatch) {
15
+ return readFileRange(db, fileLineMatch[1], parseInt(fileLineMatch[2], 10), parseInt(fileLineMatch[3], 10), context);
16
+ }
17
+ const match = findFirstSymbolMatch(db, symbolPattern);
18
+ if (!match) return null;
19
+ const doc = db.get(
20
+ `SELECT language FROM documents WHERE relative_path = ?`,
21
+ match.relativePath
22
+ );
23
+ const filePath = join(db.config.projectRoot, match.relativePath);
24
+ let fileContent;
25
+ try {
26
+ fileContent = readFileSync(filePath, "utf-8");
27
+ } catch {
28
+ return null;
29
+ }
30
+ const lines = fileContent.split("\n");
31
+ const startLine = Math.max(0, match.startLine - context);
32
+ const endLine = Math.min(lines.length - 1, match.endLine + context);
33
+ const source = lines.slice(startLine, endLine + 1).join("\n");
34
+ return {
35
+ symbol: match.symbol,
36
+ shortName: shortenSymbol(match.symbol),
37
+ relativePath: match.relativePath,
38
+ startLine: startLine + 1,
39
+ // 1-indexed for display
40
+ endLine: endLine + 1,
41
+ language: doc?.language ?? null,
42
+ source
43
+ };
44
+ }
45
+ function readFileRange(db, filePath, startLine, endLine, context) {
46
+ const doc = db.get(
47
+ `SELECT relative_path, language FROM documents WHERE relative_path LIKE ?`,
48
+ `%${filePath}%`
49
+ );
50
+ if (!doc) return null;
51
+ const fullPath = join(db.config.projectRoot, doc.relative_path);
52
+ let fileContent;
53
+ try {
54
+ fileContent = readFileSync(fullPath, "utf-8");
55
+ } catch {
56
+ return null;
57
+ }
58
+ const lines = fileContent.split("\n");
59
+ const start = Math.max(0, startLine - 1 - context);
60
+ const end = Math.min(lines.length - 1, endLine - 1 + context);
61
+ const source = lines.slice(start, end + 1).join("\n");
62
+ return {
63
+ symbol: `${doc.relative_path}:${startLine}-${endLine}`,
64
+ shortName: `${doc.relative_path}:${startLine}-${endLine}`,
65
+ relativePath: doc.relative_path,
66
+ startLine: start + 1,
67
+ endLine: end + 1,
68
+ language: doc.language,
69
+ source
70
+ };
71
+ }
72
+
73
+ export {
74
+ code
75
+ };
76
+ //# sourceMappingURL=chunk-VZ7AMAFL.js.map
@@ -0,0 +1 @@
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":[]}
@@ -0,0 +1,24 @@
1
+ // src/queries/stats.ts
2
+ function stats(db) {
3
+ const documents = db.get("SELECT COUNT(*) as c FROM documents").c;
4
+ const symbols = db.get("SELECT COUNT(*) as c FROM global_symbols").c;
5
+ const definitions = db.get(
6
+ "SELECT COUNT(*) as c FROM mentions WHERE role = 1"
7
+ ).c;
8
+ const references = db.get(
9
+ "SELECT COUNT(*) as c FROM mentions WHERE role = 0"
10
+ ).c;
11
+ return {
12
+ documents,
13
+ symbols,
14
+ definitions,
15
+ references,
16
+ indexSizeBytes: db.sizeBytes(),
17
+ lastBuilt: db.lastModified()
18
+ };
19
+ }
20
+
21
+ export {
22
+ stats
23
+ };
24
+ //# sourceMappingURL=chunk-XFXDXEUN.js.map
@@ -0,0 +1 @@
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 = 0',\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":[]}
@@ -0,0 +1,169 @@
1
+ import {
2
+ TEST_FILE_PATTERNS,
3
+ testFileMatchSql
4
+ } from "./chunk-FUHJCHS4.js";
5
+ import {
6
+ shortenSymbol
7
+ } from "./chunk-QOV2R2WT.js";
8
+
9
+ // src/queries/diff-impact.ts
10
+ import { execFileSync } from "child_process";
11
+ function diffImpact(db, opts = {}) {
12
+ const { base = "HEAD" } = opts;
13
+ let changedFileLines;
14
+ try {
15
+ const stdout = execFileSync("git", ["diff", "--name-only", base], {
16
+ encoding: "utf-8",
17
+ cwd: db.config.projectRoot,
18
+ timeout: 1e4
19
+ });
20
+ changedFileLines = stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
21
+ } catch {
22
+ return {
23
+ changedFiles: [],
24
+ changedSymbols: [],
25
+ affectedConsumers: [],
26
+ uncoveredSymbols: [],
27
+ summary: {
28
+ totalChangedFiles: 0,
29
+ totalChangedSymbols: 0,
30
+ totalAffectedFiles: 0,
31
+ testCoveragePercent: 0
32
+ }
33
+ };
34
+ }
35
+ if (changedFileLines.length === 0) {
36
+ return {
37
+ changedFiles: [],
38
+ changedSymbols: [],
39
+ affectedConsumers: [],
40
+ uncoveredSymbols: [],
41
+ summary: {
42
+ totalChangedFiles: 0,
43
+ totalChangedSymbols: 0,
44
+ totalAffectedFiles: 0,
45
+ testCoveragePercent: 0
46
+ }
47
+ };
48
+ }
49
+ const changedFiles = [];
50
+ const changedDocIds = [];
51
+ for (const file of changedFileLines) {
52
+ const doc = db.get(
53
+ `SELECT id, relative_path FROM documents
54
+ WHERE relative_path LIKE ?
55
+ LIMIT 1`,
56
+ `%${file}`
57
+ );
58
+ if (doc && !db.isIgnored(doc.relative_path)) {
59
+ changedFiles.push(doc.relative_path);
60
+ changedDocIds.push(doc.id);
61
+ }
62
+ }
63
+ if (changedDocIds.length === 0) {
64
+ return {
65
+ changedFiles: changedFileLines,
66
+ changedSymbols: [],
67
+ affectedConsumers: [],
68
+ uncoveredSymbols: [],
69
+ summary: {
70
+ totalChangedFiles: changedFileLines.length,
71
+ totalChangedSymbols: 0,
72
+ totalAffectedFiles: 0,
73
+ testCoveragePercent: 0
74
+ }
75
+ };
76
+ }
77
+ const docPlaceholders = changedDocIds.map(() => "?").join(",");
78
+ const syms = db.all(
79
+ `SELECT DISTINCT gs.id AS symbol_id, gs.symbol, d.relative_path
80
+ FROM defn_enclosing_ranges der
81
+ JOIN global_symbols gs ON der.symbol_id = gs.id
82
+ JOIN documents d ON der.document_id = d.id
83
+ WHERE der.document_id IN (${docPlaceholders})
84
+ ${db.symbolNoiseFor("gs")}
85
+ ORDER BY d.relative_path`,
86
+ ...changedDocIds
87
+ );
88
+ const testPatternSql = testFileMatchSql("ref_d", TEST_FILE_PATTERNS);
89
+ const changedSymbols = [];
90
+ const consumerMap = /* @__PURE__ */ new Map();
91
+ const uncoveredSymbols = [];
92
+ let coveredCount = 0;
93
+ for (const sym of syms) {
94
+ const fanInRow = db.get(
95
+ `SELECT COUNT(DISTINCT c.document_id) AS fan_in
96
+ FROM mentions m
97
+ JOIN chunks c ON m.chunk_id = c.id
98
+ WHERE m.symbol_id = ?
99
+ AND m.role = 0`,
100
+ sym.symbol_id
101
+ );
102
+ const fanIn = fanInRow?.fan_in ?? 0;
103
+ const shortName = shortenSymbol(sym.symbol);
104
+ changedSymbols.push({
105
+ symbol: sym.symbol,
106
+ shortName,
107
+ file: sym.relative_path,
108
+ fanIn
109
+ });
110
+ const consumers = db.all(
111
+ `SELECT DISTINCT ref_d.relative_path
112
+ FROM mentions m
113
+ JOIN chunks c ON m.chunk_id = c.id
114
+ JOIN documents ref_d ON c.document_id = ref_d.id
115
+ WHERE m.symbol_id = ?
116
+ AND m.role = 0
117
+ AND ref_d.relative_path NOT IN (${changedFiles.map(() => "?").join(",")})
118
+ ${db.pathExclusionsFor("ref_d")}`,
119
+ sym.symbol_id,
120
+ ...changedFiles
121
+ );
122
+ for (const consumer of consumers) {
123
+ if (db.isIgnored(consumer.relative_path)) continue;
124
+ if (!consumerMap.has(consumer.relative_path)) {
125
+ consumerMap.set(consumer.relative_path, /* @__PURE__ */ new Set());
126
+ }
127
+ consumerMap.get(consumer.relative_path).add(shortName);
128
+ }
129
+ const hasTest = db.get(
130
+ `SELECT COUNT(*) AS c
131
+ FROM mentions m
132
+ JOIN chunks c ON m.chunk_id = c.id
133
+ JOIN documents ref_d ON c.document_id = ref_d.id
134
+ WHERE m.symbol_id = ?
135
+ AND m.role = 0
136
+ AND (${testPatternSql})`,
137
+ sym.symbol_id
138
+ );
139
+ if (hasTest && hasTest.c > 0) {
140
+ coveredCount++;
141
+ } else {
142
+ uncoveredSymbols.push({
143
+ symbol: sym.symbol,
144
+ shortName,
145
+ file: sym.relative_path
146
+ });
147
+ }
148
+ }
149
+ const affectedConsumers = [...consumerMap.entries()].map(([file, symbols]) => ({ file, consumedSymbols: symbols.size })).sort((a, b) => b.consumedSymbols - a.consumedSymbols);
150
+ const totalSymbols = changedSymbols.length;
151
+ const testCoveragePercent = totalSymbols > 0 ? Math.round(coveredCount / totalSymbols * 100) : 0;
152
+ return {
153
+ changedFiles,
154
+ changedSymbols,
155
+ affectedConsumers,
156
+ uncoveredSymbols,
157
+ summary: {
158
+ totalChangedFiles: changedFiles.length,
159
+ totalChangedSymbols: totalSymbols,
160
+ totalAffectedFiles: affectedConsumers.length,
161
+ testCoveragePercent
162
+ }
163
+ };
164
+ }
165
+
166
+ export {
167
+ diffImpact
168
+ };
169
+ //# sourceMappingURL=chunk-YZAA4LYG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/diff-impact.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process';\nimport type { ScipDatabase } from '../db.js';\nimport { TEST_FILE_PATTERNS, testFileMatchSql } from '../query-support.js';\nimport type { DiffImpactResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Given a git diff, compute the affected symbol set.\n * Finds all symbols defined in changed files, their fan-in,\n * the files that consume them, and test coverage gaps.\n */\nexport function diffImpact(\n db: ScipDatabase,\n opts: { base?: string } = {},\n): DiffImpactResult {\n const { base = 'HEAD' } = opts;\n\n // Get changed files from git\n let changedFileLines: string[];\n try {\n const stdout = execFileSync('git', ['diff', '--name-only', base], {\n encoding: 'utf-8',\n cwd: db.config.projectRoot,\n timeout: 10_000,\n });\n changedFileLines = stdout\n .split('\\n')\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n } catch {\n // Not in a git repo or git not available — return empty result\n return {\n changedFiles: [],\n changedSymbols: [],\n affectedConsumers: [],\n uncoveredSymbols: [],\n summary: {\n totalChangedFiles: 0,\n totalChangedSymbols: 0,\n totalAffectedFiles: 0,\n testCoveragePercent: 0,\n },\n };\n }\n\n if (changedFileLines.length === 0) {\n return {\n changedFiles: [],\n changedSymbols: [],\n affectedConsumers: [],\n uncoveredSymbols: [],\n summary: {\n totalChangedFiles: 0,\n totalChangedSymbols: 0,\n totalAffectedFiles: 0,\n testCoveragePercent: 0,\n },\n };\n }\n\n // Match changed files against the index\n const changedFiles: string[] = [];\n const changedDocIds: number[] = [];\n\n for (const file of changedFileLines) {\n const doc = db.get<{ id: number; relative_path: string }>(\n `SELECT id, relative_path FROM documents\n WHERE relative_path LIKE ?\n LIMIT 1`,\n `%${file}`,\n );\n if (doc && !db.isIgnored(doc.relative_path)) {\n changedFiles.push(doc.relative_path);\n changedDocIds.push(doc.id);\n }\n }\n\n if (changedDocIds.length === 0) {\n return {\n changedFiles: changedFileLines,\n changedSymbols: [],\n affectedConsumers: [],\n uncoveredSymbols: [],\n summary: {\n totalChangedFiles: changedFileLines.length,\n totalChangedSymbols: 0,\n totalAffectedFiles: 0,\n testCoveragePercent: 0,\n },\n };\n }\n\n // Get all symbols defined in changed files\n const docPlaceholders = changedDocIds.map(() => '?').join(',');\n const syms = db.all<{\n symbol_id: number;\n symbol: string;\n relative_path: string;\n }>(\n `SELECT DISTINCT gs.id AS symbol_id, gs.symbol, d.relative_path\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 der.document_id IN (${docPlaceholders})\n ${db.symbolNoiseFor('gs')}\n ORDER BY d.relative_path`,\n ...changedDocIds,\n );\n\n // For each symbol, compute fan-in (distinct referencing documents)\n const testPatternSql = testFileMatchSql('ref_d', TEST_FILE_PATTERNS);\n const changedSymbols: DiffImpactResult['changedSymbols'] = [];\n const consumerMap = new Map<string, Set<string>>(); // file -> set of consumed symbol shortNames\n const uncoveredSymbols: DiffImpactResult['uncoveredSymbols'] = [];\n let coveredCount = 0;\n\n for (const sym of syms) {\n // Fan-in: distinct files that reference this symbol\n const fanInRow = db.get<{ fan_in: number }>(\n `SELECT COUNT(DISTINCT c.document_id) AS fan_in\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n WHERE m.symbol_id = ?\n AND m.role = 0`,\n sym.symbol_id,\n );\n\n const fanIn = fanInRow?.fan_in ?? 0;\n const shortName = shortenSymbol(sym.symbol);\n\n changedSymbols.push({\n symbol: sym.symbol,\n shortName,\n file: sym.relative_path,\n fanIn,\n });\n\n // Collect consumer files (excluding the changed files themselves)\n const consumers = db.all<{ relative_path: string }>(\n `SELECT DISTINCT ref_d.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 WHERE m.symbol_id = ?\n AND m.role = 0\n AND ref_d.relative_path NOT IN (${changedFiles.map(() => '?').join(',')})\n ${db.pathExclusionsFor('ref_d')}`,\n sym.symbol_id,\n ...changedFiles,\n );\n\n for (const consumer of consumers) {\n if (db.isIgnored(consumer.relative_path)) continue;\n if (!consumerMap.has(consumer.relative_path)) {\n consumerMap.set(consumer.relative_path, new Set());\n }\n consumerMap.get(consumer.relative_path)!.add(shortName);\n }\n\n // Check test coverage\n const hasTest = db.get<{ c: number }>(\n `SELECT COUNT(*) AS c\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 WHERE m.symbol_id = ?\n AND m.role = 0\n AND (${testPatternSql})`,\n sym.symbol_id,\n );\n\n if (hasTest && hasTest.c > 0) {\n coveredCount++;\n } else {\n uncoveredSymbols.push({\n symbol: sym.symbol,\n shortName,\n file: sym.relative_path,\n });\n }\n }\n\n // Build affected consumers list\n const affectedConsumers = [...consumerMap.entries()]\n .map(([file, symbols]) => ({ file, consumedSymbols: symbols.size }))\n .sort((a, b) => b.consumedSymbols - a.consumedSymbols);\n\n const totalSymbols = changedSymbols.length;\n const testCoveragePercent =\n totalSymbols > 0 ? Math.round((coveredCount / totalSymbols) * 100) : 0;\n\n return {\n changedFiles,\n changedSymbols,\n affectedConsumers,\n uncoveredSymbols,\n summary: {\n totalChangedFiles: changedFiles.length,\n totalChangedSymbols: totalSymbols,\n totalAffectedFiles: affectedConsumers.length,\n testCoveragePercent,\n },\n };\n}\n"],"mappings":";;;;;;;;;AAAA,SAAS,oBAAoB;AAWtB,SAAS,WACd,IACA,OAA0B,CAAC,GACT;AAClB,QAAM,EAAE,OAAO,OAAO,IAAI;AAG1B,MAAI;AACJ,MAAI;AACF,UAAM,SAAS,aAAa,OAAO,CAAC,QAAQ,eAAe,IAAI,GAAG;AAAA,MAChE,UAAU;AAAA,MACV,KAAK,GAAG,OAAO;AAAA,MACf,SAAS;AAAA,IACX,CAAC;AACD,uBAAmB,OAChB,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAAA,EAC/B,QAAQ;AAEN,WAAO;AAAA,MACL,cAAc,CAAC;AAAA,MACf,gBAAgB,CAAC;AAAA,MACjB,mBAAmB,CAAC;AAAA,MACpB,kBAAkB,CAAC;AAAA,MACnB,SAAS;AAAA,QACP,mBAAmB;AAAA,QACnB,qBAAqB;AAAA,QACrB,oBAAoB;AAAA,QACpB,qBAAqB;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,iBAAiB,WAAW,GAAG;AACjC,WAAO;AAAA,MACL,cAAc,CAAC;AAAA,MACf,gBAAgB,CAAC;AAAA,MACjB,mBAAmB,CAAC;AAAA,MACpB,kBAAkB,CAAC;AAAA,MACnB,SAAS;AAAA,QACP,mBAAmB;AAAA,QACnB,qBAAqB;AAAA,QACrB,oBAAoB;AAAA,QACpB,qBAAqB;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,eAAyB,CAAC;AAChC,QAAM,gBAA0B,CAAC;AAEjC,aAAW,QAAQ,kBAAkB;AACnC,UAAM,MAAM,GAAG;AAAA,MACb;AAAA;AAAA;AAAA,MAGA,IAAI,IAAI;AAAA,IACV;AACA,QAAI,OAAO,CAAC,GAAG,UAAU,IAAI,aAAa,GAAG;AAC3C,mBAAa,KAAK,IAAI,aAAa;AACnC,oBAAc,KAAK,IAAI,EAAE;AAAA,IAC3B;AAAA,EACF;AAEA,MAAI,cAAc,WAAW,GAAG;AAC9B,WAAO;AAAA,MACL,cAAc;AAAA,MACd,gBAAgB,CAAC;AAAA,MACjB,mBAAmB,CAAC;AAAA,MACpB,kBAAkB,CAAC;AAAA,MACnB,SAAS;AAAA,QACP,mBAAmB,iBAAiB;AAAA,QACpC,qBAAqB;AAAA,QACrB,oBAAoB;AAAA,QACpB,qBAAqB;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,kBAAkB,cAAc,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AAC7D,QAAM,OAAO,GAAG;AAAA,IAKd;AAAA;AAAA;AAAA;AAAA,gCAI4B,eAAe;AAAA,QACvC,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA,IAE3B,GAAG;AAAA,EACL;AAGA,QAAM,iBAAiB,iBAAiB,SAAS,kBAAkB;AACnE,QAAM,iBAAqD,CAAC;AAC5D,QAAM,cAAc,oBAAI,IAAyB;AACjD,QAAM,mBAAyD,CAAC;AAChE,MAAI,eAAe;AAEnB,aAAW,OAAO,MAAM;AAEtB,UAAM,WAAW,GAAG;AAAA,MAClB;AAAA;AAAA;AAAA;AAAA;AAAA,MAKA,IAAI;AAAA,IACN;AAEA,UAAM,QAAQ,UAAU,UAAU;AAClC,UAAM,YAAY,cAAc,IAAI,MAAM;AAE1C,mBAAe,KAAK;AAAA,MAClB,QAAQ,IAAI;AAAA,MACZ;AAAA,MACA,MAAM,IAAI;AAAA,MACV;AAAA,IACF,CAAC;AAGD,UAAM,YAAY,GAAG;AAAA,MACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0CAMoC,aAAa,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC;AAAA,UACrE,GAAG,kBAAkB,OAAO,CAAC;AAAA,MACjC,IAAI;AAAA,MACJ,GAAG;AAAA,IACL;AAEA,eAAW,YAAY,WAAW;AAChC,UAAI,GAAG,UAAU,SAAS,aAAa,EAAG;AAC1C,UAAI,CAAC,YAAY,IAAI,SAAS,aAAa,GAAG;AAC5C,oBAAY,IAAI,SAAS,eAAe,oBAAI,IAAI,CAAC;AAAA,MACnD;AACA,kBAAY,IAAI,SAAS,aAAa,EAAG,IAAI,SAAS;AAAA,IACxD;AAGA,UAAM,UAAU,GAAG;AAAA,MACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAMS,cAAc;AAAA,MACvB,IAAI;AAAA,IACN;AAEA,QAAI,WAAW,QAAQ,IAAI,GAAG;AAC5B;AAAA,IACF,OAAO;AACL,uBAAiB,KAAK;AAAA,QACpB,QAAQ,IAAI;AAAA,QACZ;AAAA,QACA,MAAM,IAAI;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF;AAGA,QAAM,oBAAoB,CAAC,GAAG,YAAY,QAAQ,CAAC,EAChD,IAAI,CAAC,CAAC,MAAM,OAAO,OAAO,EAAE,MAAM,iBAAiB,QAAQ,KAAK,EAAE,EAClE,KAAK,CAAC,GAAG,MAAM,EAAE,kBAAkB,EAAE,eAAe;AAEvD,QAAM,eAAe,eAAe;AACpC,QAAM,sBACJ,eAAe,IAAI,KAAK,MAAO,eAAe,eAAgB,GAAG,IAAI;AAEvE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,MACP,mBAAmB,aAAa;AAAA,MAChC,qBAAqB;AAAA,MACrB,oBAAoB,kBAAkB;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,92 @@
1
+ import {
2
+ buildFileDepGraph
3
+ } from "./chunk-FUHJCHS4.js";
4
+
5
+ // src/queries/similar-files.ts
6
+ function similarFiles(db, opts = {}) {
7
+ const { minSimilarity = 0.5, limit = 20, scope, minDeps = 3, filePattern } = opts;
8
+ const profiles = buildFileProfiles(db, { scope, minDeps });
9
+ const results = [];
10
+ if (filePattern) {
11
+ const target = profiles.find((p) => p.file.includes(filePattern));
12
+ if (!target) return [];
13
+ for (const candidate of profiles) {
14
+ if (candidate.file === target.file) continue;
15
+ const result = compareProfiles(target, candidate, minSimilarity);
16
+ if (result) results.push(result);
17
+ }
18
+ } else {
19
+ for (let i = 0; i < profiles.length; i++) {
20
+ for (let j = i + 1; j < profiles.length; j++) {
21
+ const result = compareProfiles(profiles[i], profiles[j], minSimilarity);
22
+ if (result) results.push(result);
23
+ }
24
+ if (results.length > limit * 5) break;
25
+ }
26
+ }
27
+ results.sort((a, b) => b.similarity - a.similarity);
28
+ return results.slice(0, limit);
29
+ }
30
+ function buildFileProfiles(db, opts) {
31
+ const { scope, minDeps } = opts;
32
+ const depMap = buildFileDepGraph(db, scope);
33
+ const universalDeps = findUniversalDependencies(depMap);
34
+ const profiles = [];
35
+ for (const [file, deps] of depMap) {
36
+ if (deps.size >= minDeps) {
37
+ profiles.push({
38
+ file,
39
+ deps: new Set([...deps].filter((dep) => !universalDeps.has(dep)))
40
+ });
41
+ }
42
+ }
43
+ return profiles;
44
+ }
45
+ function findUniversalDependencies(depMap) {
46
+ const universalDeps = /* @__PURE__ */ new Set();
47
+ const fileCount = depMap.size;
48
+ if (fileCount === 0) return universalDeps;
49
+ const depCounts = /* @__PURE__ */ new Map();
50
+ for (const deps of depMap.values()) {
51
+ for (const dep of deps) {
52
+ depCounts.set(dep, (depCounts.get(dep) ?? 0) + 1);
53
+ }
54
+ }
55
+ for (const [dep, count] of depCounts) {
56
+ if (count / fileCount > 0.5) {
57
+ universalDeps.add(dep);
58
+ }
59
+ }
60
+ return universalDeps;
61
+ }
62
+ function compareProfiles(a, b, minSimilarity) {
63
+ const shared = /* @__PURE__ */ new Set();
64
+ for (const dep of a.deps) {
65
+ if (b.deps.has(dep)) shared.add(dep);
66
+ }
67
+ if (shared.size === 0) return null;
68
+ const unionSize = (/* @__PURE__ */ new Set([...a.deps, ...b.deps])).size;
69
+ const similarity = shared.size / unionSize;
70
+ if (similarity < minSimilarity) return null;
71
+ const uniqueA = [];
72
+ for (const dep of a.deps) {
73
+ if (!b.deps.has(dep)) uniqueA.push(dep);
74
+ }
75
+ const uniqueB = [];
76
+ for (const dep of b.deps) {
77
+ if (!a.deps.has(dep)) uniqueB.push(dep);
78
+ }
79
+ return {
80
+ fileA: a.file,
81
+ fileB: b.file,
82
+ similarity,
83
+ sharedDeps: [...shared],
84
+ uniqueToA: uniqueA,
85
+ uniqueToB: uniqueB
86
+ };
87
+ }
88
+
89
+ export {
90
+ similarFiles
91
+ };
92
+ //# sourceMappingURL=chunk-Z73NYSBZ.js.map
@@ -0,0 +1 @@
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":[]}
@@ -0,0 +1,125 @@
1
+ import {
2
+ findFirstSymbolMatch,
3
+ getCalleeRowsForSymbol
4
+ } from "./chunk-FUHJCHS4.js";
5
+ import {
6
+ shortenSymbol
7
+ } from "./chunk-QOV2R2WT.js";
8
+
9
+ // src/queries/slice.ts
10
+ function slice(db, symbolPattern, opts = {}) {
11
+ const { direction = "backward" } = opts;
12
+ const match = findFirstSymbolMatch(db, symbolPattern);
13
+ if (!match) return null;
14
+ if (direction === "backward") {
15
+ return backwardSlice(db, match);
16
+ } else {
17
+ return forwardSlice(db, match);
18
+ }
19
+ }
20
+ function backwardSlice(db, match) {
21
+ const callees = getCalleeRowsForSymbol(db, match);
22
+ const localPredecessors = db.all(
23
+ `SELECT DISTINCT gs.symbol, d.relative_path AS file
24
+ FROM defn_enclosing_ranges der
25
+ JOIN global_symbols gs ON der.symbol_id = gs.id
26
+ JOIN documents d ON der.document_id = d.id
27
+ WHERE der.document_id = ?
28
+ AND der.end_line < ?
29
+ AND gs.id != ?
30
+ ${db.symbolNoiseFor("gs")}
31
+ ORDER BY der.start_line DESC
32
+ LIMIT 15`,
33
+ match.documentId,
34
+ match.startLine,
35
+ match.symbolId
36
+ );
37
+ const seen = /* @__PURE__ */ new Set();
38
+ const connected = [];
39
+ for (const c of callees) {
40
+ if (seen.has(c.symbol)) continue;
41
+ seen.add(c.symbol);
42
+ connected.push({
43
+ symbol: c.symbol,
44
+ shortName: shortenSymbol(c.symbol),
45
+ file: c.file,
46
+ relationship: "referenced within definition (callee)"
47
+ });
48
+ }
49
+ for (const p of localPredecessors) {
50
+ if (seen.has(p.symbol) || db.isIgnored(p.file)) continue;
51
+ seen.add(p.symbol);
52
+ connected.push({
53
+ symbol: p.symbol,
54
+ shortName: shortenSymbol(p.symbol),
55
+ file: p.file,
56
+ relationship: "defined before target in same file (local predecessor)"
57
+ });
58
+ }
59
+ return {
60
+ symbol: match.symbol,
61
+ shortName: shortenSymbol(match.symbol),
62
+ direction: "backward",
63
+ connectedSymbols: connected
64
+ };
65
+ }
66
+ function forwardSlice(db, match) {
67
+ const rows = db.all(
68
+ `SELECT DISTINCT
69
+ enc_gs.symbol AS enclosing_symbol,
70
+ enc_d.relative_path AS enclosing_file,
71
+ out_gs.symbol AS output_symbol,
72
+ out_d.relative_path AS output_file
73
+ FROM mentions ref_m
74
+ JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
75
+ JOIN documents ref_d ON ref_c.document_id = ref_d.id
76
+ -- Find enclosing function at each reference site
77
+ JOIN defn_enclosing_ranges enc_der
78
+ ON enc_der.document_id = ref_d.id
79
+ AND enc_der.start_line <= ref_c.start_line
80
+ AND enc_der.end_line >= ref_c.end_line
81
+ JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
82
+ JOIN documents enc_d ON enc_der.document_id = enc_d.id
83
+ -- Find other symbols referenced within that enclosing function
84
+ JOIN mentions out_m ON out_m.role = 0
85
+ JOIN chunks out_c ON out_m.chunk_id = out_c.id
86
+ AND out_c.document_id = enc_der.document_id
87
+ AND out_c.start_line >= enc_der.start_line
88
+ AND out_c.end_line <= enc_der.end_line
89
+ JOIN global_symbols out_gs ON out_m.symbol_id = out_gs.id
90
+ JOIN defn_enclosing_ranges out_der ON out_gs.id = out_der.symbol_id
91
+ JOIN documents out_d ON out_der.document_id = out_d.id
92
+ WHERE ref_m.symbol_id = ? AND ref_m.role = 0
93
+ AND out_gs.id != ? AND out_gs.id != enc_gs.id
94
+ AND out_d.id != ref_d.id
95
+ ${db.symbolNoiseFor("out_gs")}
96
+ ${db.pathExclusionsFor("out_d")}
97
+ ORDER BY out_d.relative_path
98
+ LIMIT 30`,
99
+ match.symbolId,
100
+ match.symbolId
101
+ );
102
+ const seen = /* @__PURE__ */ new Set();
103
+ const connected = [];
104
+ for (const r of rows) {
105
+ if (seen.has(r.output_symbol) || db.isIgnored(r.output_file)) continue;
106
+ seen.add(r.output_symbol);
107
+ connected.push({
108
+ symbol: r.output_symbol,
109
+ shortName: shortenSymbol(r.output_symbol),
110
+ file: r.output_file,
111
+ relationship: `used alongside target in ${shortenSymbol(r.enclosing_symbol)}`
112
+ });
113
+ }
114
+ return {
115
+ symbol: match.symbol,
116
+ shortName: shortenSymbol(match.symbol),
117
+ direction: "forward",
118
+ connectedSymbols: connected
119
+ };
120
+ }
121
+
122
+ export {
123
+ slice
124
+ };
125
+ //# sourceMappingURL=chunk-ZJRYBOEE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/slice.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch, getCalleeRowsForSymbol, type SymbolMatch } from '../query-support.js';\nimport type { SliceResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Reference-level program slicing: track what affects a symbol (backward)\n * or what a symbol affects (forward).\n *\n * Backward slice: \"What feeds into this?\" — symbols referenced in the same\n * function that defines the target. These are the inputs/dependencies.\n *\n * Forward slice: \"What does this feed into?\" — at each site where the target\n * is referenced, find the enclosing function, then find what that function\n * exports/defines. These are the outputs/consumers.\n *\n * Language-agnostic: works with any SCIP index.\n */\nexport function slice(\n db: ScipDatabase,\n symbolPattern: string,\n opts: { direction?: 'backward' | 'forward' } = {},\n): SliceResult | null {\n const { direction = 'backward' } = opts;\n\n const match = findFirstSymbolMatch(db, symbolPattern);\n if (!match) return null;\n\n if (direction === 'backward') {\n return backwardSlice(db, match);\n } else {\n return forwardSlice(db, match);\n }\n}\n\n\nfunction backwardSlice(db: ScipDatabase, match: SymbolMatch): SliceResult {\n // Find all symbols referenced within the definition range of the target.\n // These are what \"feeds into\" the target — the inputs.\n const callees = getCalleeRowsForSymbol(db, match);\n\n // Also find symbols whose definitions are in the same file and whose\n // ranges overlap or precede the target — local variables, parameters, etc.\n const localPredecessors = db.all<{ symbol: string; file: string }>(\n `SELECT DISTINCT gs.symbol, d.relative_path AS file\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 der.document_id = ?\n AND der.end_line < ?\n AND gs.id != ?\n ${db.symbolNoiseFor('gs')}\n ORDER BY der.start_line DESC\n LIMIT 15`,\n match.documentId, match.startLine, match.symbolId,\n );\n\n const seen = new Set<string>();\n const connected: SliceResult['connectedSymbols'] = [];\n\n for (const c of callees) {\n if (seen.has(c.symbol)) continue;\n seen.add(c.symbol);\n connected.push({\n symbol: c.symbol,\n shortName: shortenSymbol(c.symbol),\n file: c.file,\n relationship: 'referenced within definition (callee)',\n });\n }\n\n for (const p of localPredecessors) {\n if (seen.has(p.symbol) || db.isIgnored(p.file)) continue;\n seen.add(p.symbol);\n connected.push({\n symbol: p.symbol,\n shortName: shortenSymbol(p.symbol),\n file: p.file,\n relationship: 'defined before target in same file (local predecessor)',\n });\n }\n\n return {\n symbol: match.symbol,\n shortName: shortenSymbol(match.symbol),\n direction: 'backward',\n connectedSymbols: connected,\n };\n}\n\nfunction forwardSlice(db: ScipDatabase, match: SymbolMatch): SliceResult {\n // Find where the target is referenced, then at each reference site,\n // find what else the enclosing function defines/exports.\n const rows = db.all<{\n enclosing_symbol: string;\n enclosing_file: string;\n output_symbol: string;\n output_file: string;\n }>(\n `SELECT DISTINCT\n enc_gs.symbol AS enclosing_symbol,\n enc_d.relative_path AS enclosing_file,\n out_gs.symbol AS output_symbol,\n out_d.relative_path AS output_file\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n JOIN documents ref_d ON ref_c.document_id = ref_d.id\n -- Find enclosing function at each reference site\n JOIN defn_enclosing_ranges enc_der\n ON enc_der.document_id = ref_d.id\n AND enc_der.start_line <= ref_c.start_line\n AND enc_der.end_line >= ref_c.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 -- Find other symbols referenced within that enclosing function\n JOIN mentions out_m ON out_m.role = 0\n JOIN chunks out_c ON out_m.chunk_id = out_c.id\n AND out_c.document_id = enc_der.document_id\n AND out_c.start_line >= enc_der.start_line\n AND out_c.end_line <= enc_der.end_line\n JOIN global_symbols out_gs ON out_m.symbol_id = out_gs.id\n JOIN defn_enclosing_ranges out_der ON out_gs.id = out_der.symbol_id\n JOIN documents out_d ON out_der.document_id = out_d.id\n WHERE ref_m.symbol_id = ? AND ref_m.role = 0\n AND out_gs.id != ? AND out_gs.id != enc_gs.id\n AND out_d.id != ref_d.id\n ${db.symbolNoiseFor('out_gs')}\n ${db.pathExclusionsFor('out_d')}\n ORDER BY out_d.relative_path\n LIMIT 30`,\n match.symbolId, match.symbolId,\n );\n\n const seen = new Set<string>();\n const connected: SliceResult['connectedSymbols'] = [];\n\n for (const r of rows) {\n if (seen.has(r.output_symbol) || db.isIgnored(r.output_file)) continue;\n seen.add(r.output_symbol);\n connected.push({\n symbol: r.output_symbol,\n shortName: shortenSymbol(r.output_symbol),\n file: r.output_file,\n relationship: `used alongside target in ${shortenSymbol(r.enclosing_symbol)}`,\n });\n }\n\n return {\n symbol: match.symbol,\n shortName: shortenSymbol(match.symbol),\n direction: 'forward',\n connectedSymbols: connected,\n };\n}\n"],"mappings":";;;;;;;;;AAkBO,SAAS,MACd,IACA,eACA,OAA+C,CAAC,GAC5B;AACpB,QAAM,EAAE,YAAY,WAAW,IAAI;AAEnC,QAAM,QAAQ,qBAAqB,IAAI,aAAa;AACpD,MAAI,CAAC,MAAO,QAAO;AAEnB,MAAI,cAAc,YAAY;AAC5B,WAAO,cAAc,IAAI,KAAK;AAAA,EAChC,OAAO;AACL,WAAO,aAAa,IAAI,KAAK;AAAA,EAC/B;AACF;AAGA,SAAS,cAAc,IAAkB,OAAiC;AAGxE,QAAM,UAAU,uBAAuB,IAAI,KAAK;AAIhD,QAAM,oBAAoB,GAAG;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOI,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA,IAG3B,MAAM;AAAA,IAAY,MAAM;AAAA,IAAW,MAAM;AAAA,EAC3C;AAEA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,YAA6C,CAAC;AAEpD,aAAW,KAAK,SAAS;AACvB,QAAI,KAAK,IAAI,EAAE,MAAM,EAAG;AACxB,SAAK,IAAI,EAAE,MAAM;AACjB,cAAU,KAAK;AAAA,MACb,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,MAAM,EAAE;AAAA,MACR,cAAc;AAAA,IAChB,CAAC;AAAA,EACH;AAEA,aAAW,KAAK,mBAAmB;AACjC,QAAI,KAAK,IAAI,EAAE,MAAM,KAAK,GAAG,UAAU,EAAE,IAAI,EAAG;AAChD,SAAK,IAAI,EAAE,MAAM;AACjB,cAAU,KAAK;AAAA,MACb,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,MAAM,EAAE;AAAA,MACR,cAAc;AAAA,IAChB,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd,WAAW,cAAc,MAAM,MAAM;AAAA,IACrC,WAAW;AAAA,IACX,kBAAkB;AAAA,EACpB;AACF;AAEA,SAAS,aAAa,IAAkB,OAAiC;AAGvE,QAAM,OAAO,GAAG;AAAA,IAMd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QA2BI,GAAG,eAAe,QAAQ,CAAC;AAAA,QAC3B,GAAG,kBAAkB,OAAO,CAAC;AAAA;AAAA;AAAA,IAGjC,MAAM;AAAA,IAAU,MAAM;AAAA,EACxB;AAEA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,YAA6C,CAAC;AAEpD,aAAW,KAAK,MAAM;AACpB,QAAI,KAAK,IAAI,EAAE,aAAa,KAAK,GAAG,UAAU,EAAE,WAAW,EAAG;AAC9D,SAAK,IAAI,EAAE,aAAa;AACxB,cAAU,KAAK;AAAA,MACb,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,aAAa;AAAA,MACxC,MAAM,EAAE;AAAA,MACR,cAAc,4BAA4B,cAAc,EAAE,gBAAgB,CAAC;AAAA,IAC7E,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd,WAAW,cAAc,MAAM,MAAM;AAAA,IACrC,WAAW;AAAA,IACX,kBAAkB;AAAA,EACpB;AACF;","names":[]}