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,103 @@
1
+ import {
2
+ findFirstSymbolMatch
3
+ } from "./chunk-FUHJCHS4.js";
4
+ import {
5
+ shortenSymbol
6
+ } from "./chunk-QOV2R2WT.js";
7
+
8
+ // src/queries/dataflow.ts
9
+ function dataflow(db, symbolPattern) {
10
+ const match = findFirstSymbolMatch(db, symbolPattern);
11
+ if (!match) return null;
12
+ const defSites = db.all(
13
+ `SELECT d.relative_path AS file, c.start_line AS line
14
+ FROM mentions m
15
+ JOIN chunks c ON m.chunk_id = c.id
16
+ JOIN documents d ON c.document_id = d.id
17
+ WHERE m.symbol_id = ? AND m.role = 1
18
+ ORDER BY d.relative_path, c.start_line`,
19
+ match.symbolId
20
+ );
21
+ const usageSites = db.all(
22
+ `SELECT d.relative_path AS file, c.start_line AS line,
23
+ (SELECT enc_gs.symbol
24
+ FROM defn_enclosing_ranges enc_der
25
+ JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
26
+ WHERE enc_der.document_id = d.id
27
+ AND enc_der.start_line <= c.start_line
28
+ AND enc_der.end_line >= c.end_line
29
+ ORDER BY (enc_der.end_line - enc_der.start_line) ASC
30
+ LIMIT 1
31
+ ) AS enclosing_symbol
32
+ FROM mentions m
33
+ JOIN chunks c ON m.chunk_id = c.id
34
+ JOIN documents d ON c.document_id = d.id
35
+ WHERE m.symbol_id = ? AND m.role = 0
36
+ ${db.pathExclusionsFor("d")}
37
+ ORDER BY d.relative_path, c.start_line`,
38
+ match.symbolId
39
+ );
40
+ const producers = db.all(
41
+ `SELECT DISTINCT other_gs.symbol, other_d.relative_path AS file
42
+ FROM mentions other_m
43
+ JOIN chunks other_c ON other_m.chunk_id = other_c.id
44
+ JOIN global_symbols other_gs ON other_m.symbol_id = other_gs.id
45
+ JOIN defn_enclosing_ranges other_der ON other_gs.id = other_der.symbol_id
46
+ JOIN documents other_d ON other_der.document_id = other_d.id
47
+ WHERE other_c.document_id = ?
48
+ AND other_c.start_line >= ? AND other_c.end_line <= ?
49
+ AND other_m.role = 0
50
+ AND other_gs.id != ?
51
+ ${db.symbolNoiseFor("other_gs")}
52
+ ${db.pathExclusionsFor("other_d")}
53
+ ORDER BY other_d.relative_path
54
+ LIMIT 30`,
55
+ match.documentId,
56
+ match.startLine,
57
+ match.endLine,
58
+ match.symbolId
59
+ );
60
+ const consumers = db.all(
61
+ `SELECT DISTINCT consumer_gs.symbol, consumer_d.relative_path AS file
62
+ FROM mentions ref_m
63
+ JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
64
+ JOIN documents ref_d ON ref_c.document_id = ref_d.id
65
+ -- Find the enclosing function at each usage site
66
+ JOIN defn_enclosing_ranges enc_der
67
+ ON enc_der.document_id = ref_d.id
68
+ AND enc_der.start_line <= ref_c.start_line
69
+ AND enc_der.end_line >= ref_c.end_line
70
+ JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
71
+ -- Find other symbols defined by that enclosing function's file
72
+ JOIN mentions consumer_m ON consumer_m.symbol_id = enc_gs.id AND consumer_m.role = 0
73
+ JOIN chunks consumer_c ON consumer_m.chunk_id = consumer_c.id
74
+ JOIN documents consumer_d ON consumer_c.document_id = consumer_d.id
75
+ JOIN global_symbols consumer_gs ON consumer_m.symbol_id = consumer_gs.id
76
+ WHERE ref_m.symbol_id = ? AND ref_m.role = 0
77
+ AND consumer_d.id != ref_d.id
78
+ ${db.symbolNoiseFor("consumer_gs")}
79
+ ${db.pathExclusionsFor("consumer_d")}
80
+ ORDER BY consumer_d.relative_path
81
+ LIMIT 30`,
82
+ match.symbolId
83
+ );
84
+ return {
85
+ symbol: match.symbol,
86
+ shortName: shortenSymbol(match.symbol),
87
+ relativePath: match.relativePath,
88
+ definitionSites: defSites.filter((s) => !db.isIgnored(s.file)),
89
+ usageSites: usageSites.filter((s) => !db.isIgnored(s.file)).map((s) => ({
90
+ file: s.file,
91
+ line: s.line,
92
+ enclosingSymbol: s.enclosing_symbol ?? "(top-level)",
93
+ enclosingShort: s.enclosing_symbol ? shortenSymbol(s.enclosing_symbol) : "(top-level)"
94
+ })),
95
+ producers: producers.filter((p) => !db.isIgnored(p.file)).map((p) => ({ symbol: p.symbol, shortName: shortenSymbol(p.symbol), file: p.file })),
96
+ consumers: consumers.filter((c) => !db.isIgnored(c.file)).map((c) => ({ symbol: c.symbol, shortName: shortenSymbol(c.symbol), file: c.file }))
97
+ };
98
+ }
99
+
100
+ export {
101
+ dataflow
102
+ };
103
+ //# sourceMappingURL=chunk-T6ARFSBZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/dataflow.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch } from '../query-support.js';\nimport type { DataflowResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Reference-level dataflow analysis: where does data around this symbol\n * come from and where does it go?\n *\n * This is not value-level dataflow (we can't trace x = foo(); bar(x);\n * as a chain). Instead it shows:\n * - Where the symbol is defined and used\n * - What other symbols appear in the same enclosing scope (co-occurring data)\n * - What feeds into the function that defines it (producers)\n * - What consumes the function that uses it (consumers)\n *\n * Language-agnostic: works with any SCIP index.\n */\nexport function dataflow(\n db: ScipDatabase,\n symbolPattern: string,\n): DataflowResult | null {\n const match = findFirstSymbolMatch(db, symbolPattern);\n if (!match) return null;\n\n // Definition sites (role=1)\n const defSites = db.all<{ file: string; line: number }>(\n `SELECT d.relative_path AS file, c.start_line AS line\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 WHERE m.symbol_id = ? AND m.role = 1\n ORDER BY d.relative_path, c.start_line`,\n match.symbolId,\n );\n\n // Usage sites (role=0) with enclosing symbol\n const usageSites = db.all<{\n file: string;\n line: number;\n enclosing_symbol: string | null;\n }>(\n `SELECT d.relative_path AS file, c.start_line AS line,\n (SELECT enc_gs.symbol\n FROM defn_enclosing_ranges enc_der\n JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id\n WHERE enc_der.document_id = d.id\n AND enc_der.start_line <= c.start_line\n AND enc_der.end_line >= c.end_line\n ORDER BY (enc_der.end_line - enc_der.start_line) ASC\n LIMIT 1\n ) AS enclosing_symbol\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 WHERE m.symbol_id = ? AND m.role = 0\n ${db.pathExclusionsFor('d')}\n ORDER BY d.relative_path, c.start_line`,\n match.symbolId,\n );\n\n // Producers: other symbols referenced within the same function that defines our target\n const producers = db.all<{ symbol: string; file: string }>(\n `SELECT DISTINCT other_gs.symbol, other_d.relative_path AS file\n FROM mentions other_m\n JOIN chunks other_c ON other_m.chunk_id = other_c.id\n JOIN global_symbols other_gs ON other_m.symbol_id = other_gs.id\n JOIN defn_enclosing_ranges other_der ON other_gs.id = other_der.symbol_id\n JOIN documents other_d ON other_der.document_id = other_d.id\n WHERE other_c.document_id = ?\n AND other_c.start_line >= ? AND other_c.end_line <= ?\n AND other_m.role = 0\n AND other_gs.id != ?\n ${db.symbolNoiseFor('other_gs')}\n ${db.pathExclusionsFor('other_d')}\n ORDER BY other_d.relative_path\n LIMIT 30`,\n match.documentId, match.startLine, match.endLine, match.symbolId,\n );\n\n // Consumers: symbols exported/defined by functions that reference our target\n // (what does the data flow into after being used)\n const consumers = db.all<{ symbol: string; file: string }>(\n `SELECT DISTINCT consumer_gs.symbol, consumer_d.relative_path AS 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 the enclosing function at each usage 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 -- Find other symbols defined by that enclosing function's file\n JOIN mentions consumer_m ON consumer_m.symbol_id = enc_gs.id AND consumer_m.role = 0\n JOIN chunks consumer_c ON consumer_m.chunk_id = consumer_c.id\n JOIN documents consumer_d ON consumer_c.document_id = consumer_d.id\n JOIN global_symbols consumer_gs ON consumer_m.symbol_id = consumer_gs.id\n WHERE ref_m.symbol_id = ? AND ref_m.role = 0\n AND consumer_d.id != ref_d.id\n ${db.symbolNoiseFor('consumer_gs')}\n ${db.pathExclusionsFor('consumer_d')}\n ORDER BY consumer_d.relative_path\n LIMIT 30`,\n match.symbolId,\n );\n\n return {\n symbol: match.symbol,\n shortName: shortenSymbol(match.symbol),\n relativePath: match.relativePath,\n definitionSites: defSites.filter((s) => !db.isIgnored(s.file)),\n usageSites: usageSites\n .filter((s) => !db.isIgnored(s.file))\n .map((s) => ({\n file: s.file,\n line: s.line,\n enclosingSymbol: s.enclosing_symbol ?? '(top-level)',\n enclosingShort: s.enclosing_symbol ? shortenSymbol(s.enclosing_symbol) : '(top-level)',\n })),\n producers: producers\n .filter((p) => !db.isIgnored(p.file))\n .map((p) => ({ symbol: p.symbol, shortName: shortenSymbol(p.symbol), file: p.file })),\n consumers: consumers\n .filter((c) => !db.isIgnored(c.file))\n .map((c) => ({ symbol: c.symbol, shortName: shortenSymbol(c.symbol), file: c.file })),\n };\n}\n"],"mappings":";;;;;;;;AAkBO,SAAS,SACd,IACA,eACuB;AACvB,QAAM,QAAQ,qBAAqB,IAAI,aAAa;AACpD,MAAI,CAAC,MAAO,QAAO;AAGnB,QAAM,WAAW,GAAG;AAAA,IAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,MAAM;AAAA,EACR;AAGA,QAAM,aAAa,GAAG;AAAA,IAKpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAcI,GAAG,kBAAkB,GAAG,CAAC;AAAA;AAAA,IAE7B,MAAM;AAAA,EACR;AAGA,QAAM,YAAY,GAAG;AAAA,IACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAUI,GAAG,eAAe,UAAU,CAAC;AAAA,QAC7B,GAAG,kBAAkB,SAAS,CAAC;AAAA;AAAA;AAAA,IAGnC,MAAM;AAAA,IAAY,MAAM;AAAA,IAAW,MAAM;AAAA,IAAS,MAAM;AAAA,EAC1D;AAIA,QAAM,YAAY,GAAG;AAAA,IACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAiBI,GAAG,eAAe,aAAa,CAAC;AAAA,QAChC,GAAG,kBAAkB,YAAY,CAAC;AAAA;AAAA;AAAA,IAGtC,MAAM;AAAA,EACR;AAEA,SAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd,WAAW,cAAc,MAAM,MAAM;AAAA,IACrC,cAAc,MAAM;AAAA,IACpB,iBAAiB,SAAS,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC;AAAA,IAC7D,YAAY,WACT,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EACnC,IAAI,CAAC,OAAO;AAAA,MACX,MAAM,EAAE;AAAA,MACR,MAAM,EAAE;AAAA,MACR,iBAAiB,EAAE,oBAAoB;AAAA,MACvC,gBAAgB,EAAE,mBAAmB,cAAc,EAAE,gBAAgB,IAAI;AAAA,IAC3E,EAAE;AAAA,IACJ,WAAW,UACR,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EACnC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,WAAW,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE;AAAA,IACtF,WAAW,UACR,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EACnC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,WAAW,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE;AAAA,EACxF;AACF;","names":[]}
@@ -0,0 +1,46 @@
1
+ import {
2
+ cleanSignature
3
+ } from "./chunk-4TYLS5XX.js";
4
+
5
+ // src/queries/trace.ts
6
+ function trace(db, symbolPattern) {
7
+ const defRows = db.all(
8
+ `SELECT d.relative_path, der.start_line, der.end_line,
9
+ REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
10
+ FROM global_symbols gs
11
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
12
+ JOIN documents d ON der.document_id = d.id
13
+ WHERE gs.symbol LIKE ?
14
+ AND ${db.localSymbolPredicate}
15
+ ${db.symbolNoise}
16
+ ORDER BY d.relative_path, der.start_line
17
+ LIMIT 10`,
18
+ `%${symbolPattern}%`
19
+ );
20
+ const definitions = defRows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
21
+ relativePath: r.relative_path,
22
+ startLine: r.start_line,
23
+ endLine: r.end_line,
24
+ signature: cleanSignature(r.sig)
25
+ }));
26
+ const refRows = db.all(
27
+ `SELECT DISTINCT d.relative_path
28
+ FROM mentions m
29
+ JOIN chunks c ON m.chunk_id = c.id
30
+ JOIN documents d ON c.document_id = d.id
31
+ JOIN global_symbols gs ON m.symbol_id = gs.id
32
+ WHERE gs.symbol LIKE ?
33
+ AND ${db.localSymbolPredicate}
34
+ ${db.symbolNoise}
35
+ AND m.role = 0
36
+ ORDER BY d.relative_path`,
37
+ `%${symbolPattern}%`
38
+ );
39
+ const referencedBy = refRows.map((r) => r.relative_path).filter((p) => !db.isIgnored(p));
40
+ return { definitions, referencedBy };
41
+ }
42
+
43
+ export {
44
+ trace
45
+ };
46
+ //# sourceMappingURL=chunk-TBP6BICL.js.map
@@ -0,0 +1 @@
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 = 0\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":[]}
@@ -0,0 +1,97 @@
1
+ import {
2
+ testFileExclusionSql
3
+ } from "./chunk-FUHJCHS4.js";
4
+ import {
5
+ shortenSymbol
6
+ } from "./chunk-QOV2R2WT.js";
7
+
8
+ // src/queries/wrapper-candidates.ts
9
+ function wrapperCandidates(db, opts) {
10
+ const { scope, maxLoc = 15, limit = 30 } = opts ?? {};
11
+ const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
12
+ const rows = db.all(
13
+ `SELECT * FROM (
14
+ SELECT
15
+ gs.symbol,
16
+ d.relative_path AS file,
17
+ der.start_line,
18
+ der.end_line,
19
+ (der.end_line - der.start_line + 1) AS loc,
20
+ -- The single caller: the symbol whose definition range contains
21
+ -- the chunk that references our target
22
+ (SELECT caller_gs.symbol
23
+ FROM mentions ref_m
24
+ JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
25
+ JOIN defn_enclosing_ranges caller_der
26
+ ON caller_der.document_id = ref_c.document_id
27
+ AND ref_c.start_line >= caller_der.start_line
28
+ AND ref_c.end_line <= caller_der.end_line
29
+ JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id
30
+ WHERE ref_m.symbol_id = gs.id
31
+ AND ref_m.role = 0
32
+ AND ref_c.document_id != der.document_id
33
+ LIMIT 1
34
+ ) AS caller_symbol,
35
+ -- Fan-in of that single caller
36
+ (SELECT COUNT(DISTINCT caller_ref_c.document_id)
37
+ FROM mentions caller_ref_m
38
+ JOIN chunks caller_ref_c ON caller_ref_m.chunk_id = caller_ref_c.id
39
+ WHERE caller_ref_m.symbol_id = (
40
+ SELECT caller_der2.symbol_id
41
+ FROM mentions ref_m2
42
+ JOIN chunks ref_c2 ON ref_m2.chunk_id = ref_c2.id
43
+ JOIN defn_enclosing_ranges caller_der2
44
+ ON caller_der2.document_id = ref_c2.document_id
45
+ AND ref_c2.start_line >= caller_der2.start_line
46
+ AND ref_c2.end_line <= caller_der2.end_line
47
+ WHERE ref_m2.symbol_id = gs.id
48
+ AND ref_m2.role = 0
49
+ AND ref_c2.document_id != der.document_id
50
+ LIMIT 1
51
+ )
52
+ AND caller_ref_m.role = 0
53
+ ) AS caller_fan_in
54
+ FROM global_symbols gs
55
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
56
+ JOIN documents d ON der.document_id = d.id
57
+ WHERE 1 = 1
58
+ ${db.pathExclusionsFor("d")}
59
+ AND ${testFileExclusionSql("d")}
60
+ ${db.symbolNoiseFor("gs")}
61
+ -- Only functions/terms, not type definitions (types with # are not wrappers)
62
+ AND gs.symbol NOT LIKE '%#'
63
+ AND (der.end_line - der.start_line + 1) <= ?
64
+ AND (der.end_line - der.start_line + 1) >= 2
65
+ ${scopeFilter}
66
+ -- Exactly 1 cross-file consumer
67
+ AND (
68
+ SELECT COUNT(DISTINCT ref_c.document_id)
69
+ FROM mentions ref_m
70
+ JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
71
+ WHERE ref_m.symbol_id = gs.id
72
+ AND ref_m.role = 0
73
+ AND ref_c.document_id != der.document_id
74
+ ) = 1
75
+ ) WHERE caller_symbol IS NOT NULL AND caller_fan_in > 3
76
+ ORDER BY caller_fan_in DESC, loc DESC
77
+ LIMIT ?`,
78
+ maxLoc,
79
+ limit
80
+ );
81
+ return rows.filter((r) => !db.isIgnored(r.file)).map((r) => ({
82
+ symbol: r.symbol,
83
+ shortName: shortenSymbol(r.symbol),
84
+ file: r.file,
85
+ startLine: r.start_line,
86
+ endLine: r.end_line,
87
+ loc: r.loc,
88
+ singleCaller: r.caller_symbol,
89
+ singleCallerShort: shortenSymbol(r.caller_symbol),
90
+ callerFanIn: r.caller_fan_in
91
+ }));
92
+ }
93
+
94
+ export {
95
+ wrapperCandidates
96
+ };
97
+ //# sourceMappingURL=chunk-TDNNOR6D.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/wrapper-candidates.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { testFileExclusionSql } from '../query-support.js';\nimport type { WrapperCandidate } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find wrapper candidates: symbols called by only one other symbol.\n *\n * These are premature abstractions that add indirection without\n * providing reuse. A function with fan-in = 1 whose sole caller\n * is widely used is a strong signal of unnecessary wrapping.\n */\nexport function wrapperCandidates(\n db: ScipDatabase,\n opts?: { scope?: string; maxLoc?: number; limit?: number },\n): WrapperCandidate[] {\n const { scope, maxLoc = 15, limit = 30 } = opts ?? {};\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n // Find all symbols with exactly 1 cross-file consumer (fan-in = 1),\n // along with who that single caller is and the caller's own fan-in.\n const rows = db.all<{\n symbol: string;\n file: string;\n start_line: number;\n end_line: number;\n loc: number;\n caller_symbol: string;\n caller_fan_in: number;\n }>(\n `SELECT * FROM (\n SELECT\n gs.symbol,\n d.relative_path AS file,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc,\n -- The single caller: the symbol whose definition range contains\n -- the chunk that references our target\n (SELECT caller_gs.symbol\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n JOIN defn_enclosing_ranges caller_der\n ON caller_der.document_id = ref_c.document_id\n AND ref_c.start_line >= caller_der.start_line\n AND ref_c.end_line <= caller_der.end_line\n JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id\n WHERE ref_m.symbol_id = gs.id\n AND ref_m.role = 0\n AND ref_c.document_id != der.document_id\n LIMIT 1\n ) AS caller_symbol,\n -- Fan-in of that single caller\n (SELECT COUNT(DISTINCT caller_ref_c.document_id)\n FROM mentions caller_ref_m\n JOIN chunks caller_ref_c ON caller_ref_m.chunk_id = caller_ref_c.id\n WHERE caller_ref_m.symbol_id = (\n SELECT caller_der2.symbol_id\n FROM mentions ref_m2\n JOIN chunks ref_c2 ON ref_m2.chunk_id = ref_c2.id\n JOIN defn_enclosing_ranges caller_der2\n ON caller_der2.document_id = ref_c2.document_id\n AND ref_c2.start_line >= caller_der2.start_line\n AND ref_c2.end_line <= caller_der2.end_line\n WHERE ref_m2.symbol_id = gs.id\n AND ref_m2.role = 0\n AND ref_c2.document_id != der.document_id\n LIMIT 1\n )\n AND caller_ref_m.role = 0\n ) AS caller_fan_in\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND ${testFileExclusionSql('d')}\n ${db.symbolNoiseFor('gs')}\n -- Only functions/terms, not type definitions (types with # are not wrappers)\n AND gs.symbol NOT LIKE '%#'\n AND (der.end_line - der.start_line + 1) <= ?\n AND (der.end_line - der.start_line + 1) >= 2\n ${scopeFilter}\n -- Exactly 1 cross-file consumer\n AND (\n SELECT COUNT(DISTINCT ref_c.document_id)\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n WHERE ref_m.symbol_id = gs.id\n AND ref_m.role = 0\n AND ref_c.document_id != der.document_id\n ) = 1\n ) WHERE caller_symbol IS NOT NULL AND caller_fan_in > 3\n ORDER BY caller_fan_in DESC, loc DESC\n LIMIT ?`,\n maxLoc, limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.file))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n file: r.file,\n startLine: r.start_line,\n endLine: r.end_line,\n loc: r.loc,\n singleCaller: r.caller_symbol,\n singleCallerShort: shortenSymbol(r.caller_symbol),\n callerFanIn: r.caller_fan_in,\n }));\n}\n"],"mappings":";;;;;;;;AAYO,SAAS,kBACd,IACA,MACoB;AACpB,QAAM,EAAE,OAAO,SAAS,IAAI,QAAQ,GAAG,IAAI,QAAQ,CAAC;AACpD,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAItE,QAAM,OAAO,GAAG;AAAA,IASd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UA6CM,GAAG,kBAAkB,GAAG,CAAC;AAAA,cACrB,qBAAqB,GAAG,CAAC;AAAA,UAC7B,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,UAKvB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAajB;AAAA,IAAQ;AAAA,EACV;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EACnC,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,MAAM,EAAE;AAAA,IACR,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,KAAK,EAAE;AAAA,IACP,cAAc,EAAE;AAAA,IAChB,mBAAmB,cAAc,EAAE,aAAa;AAAA,IAChD,aAAa,EAAE;AAAA,EACjB,EAAE;AACN;","names":[]}
@@ -0,0 +1,195 @@
1
+ import {
2
+ findFirstSymbolMatch,
3
+ getCalleeRowsForSymbol
4
+ } from "./chunk-FUHJCHS4.js";
5
+ import {
6
+ shortenSymbol
7
+ } from "./chunk-QOV2R2WT.js";
8
+
9
+ // src/queries/similar.ts
10
+ function similar(db, symbolPattern, opts = {}) {
11
+ const { minSimilarity = 0.4, limit = 20 } = opts;
12
+ const target = findCallees(db, symbolPattern);
13
+ if (!target || target.callees.size === 0) return [];
14
+ const candidates = getAllCalleeFingerprints(db, {
15
+ minCallees: 3,
16
+ excludeSymbol: target.symbol
17
+ });
18
+ const allFingerprints = [target, ...candidates];
19
+ const idfWeights = computeIdf(allFingerprints);
20
+ const results = [];
21
+ for (const candidate of candidates) {
22
+ if (candidate.callees.size < 3) continue;
23
+ const { similarity, significantShared, trivialShared } = weightedSimilarity(
24
+ target.callees,
25
+ candidate.callees,
26
+ idfWeights
27
+ );
28
+ if (similarity < minSimilarity) continue;
29
+ if (significantShared.length < 1) continue;
30
+ results.push({
31
+ symbolA: target.symbol,
32
+ shortNameA: shortenSymbol(target.symbol),
33
+ fileA: target.file,
34
+ symbolB: candidate.symbol,
35
+ shortNameB: shortenSymbol(candidate.symbol),
36
+ fileB: candidate.file,
37
+ similarity,
38
+ sharedCallees: significantShared.map(shortenSymbol),
39
+ uniqueToA: [...difference(target.callees, candidate.callees)].map(shortenSymbol),
40
+ uniqueToB: [...difference(candidate.callees, target.callees)].map(shortenSymbol)
41
+ });
42
+ }
43
+ results.sort((a, b) => b.similarity - a.similarity);
44
+ return results.slice(0, limit);
45
+ }
46
+ function similarAll(db, opts = {}) {
47
+ const { minSimilarity = 0.5, limit = 20, scope, minCallees = 4 } = opts;
48
+ const all = getAllCalleeFingerprints(db, { minCallees, scope });
49
+ const idfWeights = computeIdf(all);
50
+ const results = [];
51
+ for (let i = 0; i < all.length; i++) {
52
+ for (let j = i + 1; j < all.length; j++) {
53
+ const a = all[i];
54
+ const b = all[j];
55
+ if (a.file === b.file) continue;
56
+ const { similarity, significantShared } = weightedSimilarity(
57
+ a.callees,
58
+ b.callees,
59
+ idfWeights
60
+ );
61
+ if (similarity < minSimilarity) continue;
62
+ if (significantShared.length < 2) continue;
63
+ results.push({
64
+ symbolA: a.symbol,
65
+ shortNameA: shortenSymbol(a.symbol),
66
+ fileA: a.file,
67
+ symbolB: b.symbol,
68
+ shortNameB: shortenSymbol(b.symbol),
69
+ fileB: b.file,
70
+ similarity,
71
+ sharedCallees: significantShared.map(shortenSymbol),
72
+ uniqueToA: [...difference(a.callees, b.callees)].map(shortenSymbol),
73
+ uniqueToB: [...difference(b.callees, a.callees)].map(shortenSymbol)
74
+ });
75
+ }
76
+ if (results.length > limit * 5) break;
77
+ }
78
+ results.sort((a, b) => b.similarity - a.similarity);
79
+ return results.slice(0, limit);
80
+ }
81
+ function computeIdf(fingerprints) {
82
+ const n = fingerprints.length;
83
+ if (n === 0) return /* @__PURE__ */ new Map();
84
+ const docFreq = /* @__PURE__ */ new Map();
85
+ for (const fp of fingerprints) {
86
+ for (const callee of fp.callees) {
87
+ docFreq.set(callee, (docFreq.get(callee) ?? 0) + 1);
88
+ }
89
+ }
90
+ const idf = /* @__PURE__ */ new Map();
91
+ for (const [callee, df] of docFreq) {
92
+ idf.set(callee, Math.log(n / df));
93
+ }
94
+ return idf;
95
+ }
96
+ function weightedSimilarity(a, b, idf) {
97
+ const shared = intersection(a, b);
98
+ if (shared.size === 0) return { similarity: 0, significantShared: [], trivialShared: [] };
99
+ let dotProduct = 0;
100
+ let magA = 0;
101
+ let magB = 0;
102
+ const allCallees = /* @__PURE__ */ new Set([...a, ...b]);
103
+ for (const callee of allCallees) {
104
+ const weight = idf.get(callee) ?? 0;
105
+ const inA = a.has(callee) ? weight : 0;
106
+ const inB = b.has(callee) ? weight : 0;
107
+ dotProduct += inA * inB;
108
+ magA += inA * inA;
109
+ magB += inB * inB;
110
+ }
111
+ const magnitude = Math.sqrt(magA) * Math.sqrt(magB);
112
+ const similarity = magnitude > 0 ? dotProduct / magnitude : 0;
113
+ const medianIdf = getMedianIdf(idf);
114
+ const significantShared = [];
115
+ const trivialShared = [];
116
+ for (const callee of shared) {
117
+ const weight = idf.get(callee) ?? 0;
118
+ if (weight >= medianIdf) {
119
+ significantShared.push(callee);
120
+ } else {
121
+ trivialShared.push(callee);
122
+ }
123
+ }
124
+ significantShared.sort((x, y) => (idf.get(y) ?? 0) - (idf.get(x) ?? 0));
125
+ return { similarity, significantShared, trivialShared };
126
+ }
127
+ function getMedianIdf(idf) {
128
+ const values = [...idf.values()].sort((a, b) => a - b);
129
+ if (values.length === 0) return 0;
130
+ const mid = Math.floor(values.length / 2);
131
+ return values.length % 2 === 0 ? (values[mid - 1] + values[mid]) / 2 : values[mid];
132
+ }
133
+ function findCallees(db, symbolPattern) {
134
+ const target = findFirstSymbolMatch(db, symbolPattern);
135
+ if (!target) return null;
136
+ const calleeRows = getCalleeRowsForSymbol(db, target);
137
+ return {
138
+ symbol: target.symbol,
139
+ file: target.relativePath,
140
+ callees: new Set(calleeRows.map((r) => r.symbol))
141
+ };
142
+ }
143
+ function getAllCalleeFingerprints(db, opts) {
144
+ const { minCallees, scope, excludeSymbol } = opts;
145
+ const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
146
+ const excludeFilter = excludeSymbol ? `AND gs.symbol != '${excludeSymbol.replace(/'/g, "''")}'` : "";
147
+ const symbols = db.all(
148
+ `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
149
+ FROM global_symbols gs
150
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
151
+ JOIN documents d ON der.document_id = d.id
152
+ WHERE 1 = 1
153
+ ${db.pathExclusionsFor("d")}
154
+ ${db.symbolNoiseFor("gs")}
155
+ AND (der.end_line - der.start_line + 1) >= 5
156
+ ${scopeFilter}
157
+ ${excludeFilter}
158
+ ORDER BY d.relative_path`
159
+ );
160
+ const fingerprints = [];
161
+ for (const sym of symbols) {
162
+ if (db.isIgnored(sym.relative_path)) continue;
163
+ const calleeRows = getCalleeRowsForSymbol(db, {
164
+ documentId: sym.document_id,
165
+ startLine: sym.start_line,
166
+ endLine: sym.end_line,
167
+ symbolId: sym.id
168
+ });
169
+ const callees = new Set(calleeRows.map((r) => r.symbol));
170
+ if (callees.size >= minCallees) {
171
+ fingerprints.push({ symbol: sym.symbol, file: sym.relative_path, callees });
172
+ }
173
+ }
174
+ return fingerprints;
175
+ }
176
+ function intersection(a, b) {
177
+ const result = /* @__PURE__ */ new Set();
178
+ for (const item of a) {
179
+ if (b.has(item)) result.add(item);
180
+ }
181
+ return result;
182
+ }
183
+ function difference(a, b) {
184
+ const result = /* @__PURE__ */ new Set();
185
+ for (const item of a) {
186
+ if (!b.has(item)) result.add(item);
187
+ }
188
+ return result;
189
+ }
190
+
191
+ export {
192
+ similar,
193
+ similarAll
194
+ };
195
+ //# sourceMappingURL=chunk-TSPZOMHC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/similar.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch, getCalleeRowsForSymbol } from '../query-support.js';\nimport type { SimilarSymbolResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find functions with similar callee fingerprints using TF-IDF weighted\n * cosine similarity.\n *\n * Plain Jaccard similarity inflates scores when functions share ubiquitous\n * infrastructure imports (db, types, shortenSymbol). TF-IDF fixes this by\n * weighting each shared callee by how rare it is:\n *\n * - A callee used by 2 functions (rare) gets high weight → strong signal\n * - A callee used by 30 functions (common) gets low weight → noise\n *\n * This means two functions sharing `sendWelcomeEmail()` (rare) score\n * much higher than two functions sharing `db.all()` (ubiquitous), even\n * though both are \"shared callees.\"\n */\nexport function similar(\n db: ScipDatabase,\n symbolPattern: string,\n opts: { minSimilarity?: number; limit?: number } = {},\n): SimilarSymbolResult[] {\n const { minSimilarity = 0.4, limit = 20 } = opts;\n\n const target = findCallees(db, symbolPattern);\n if (!target || target.callees.size === 0) return [];\n\n const candidates = getAllCalleeFingerprints(db, {\n minCallees: 3,\n excludeSymbol: target.symbol,\n });\n\n // Compute IDF weights across all fingerprints + target\n const allFingerprints = [target, ...candidates];\n const idfWeights = computeIdf(allFingerprints);\n\n const results: SimilarSymbolResult[] = [];\n\n for (const candidate of candidates) {\n if (candidate.callees.size < 3) continue;\n\n const { similarity, significantShared, trivialShared } = weightedSimilarity(\n target.callees, candidate.callees, idfWeights,\n );\n\n if (similarity < minSimilarity) continue;\n if (significantShared.length < 1) continue; // no real overlap\n\n results.push({\n symbolA: target.symbol,\n shortNameA: shortenSymbol(target.symbol),\n fileA: target.file,\n symbolB: candidate.symbol,\n shortNameB: shortenSymbol(candidate.symbol),\n fileB: candidate.file,\n similarity,\n sharedCallees: significantShared.map(shortenSymbol),\n uniqueToA: [...difference(target.callees, candidate.callees)].map(shortenSymbol),\n uniqueToB: [...difference(candidate.callees, target.callees)].map(shortenSymbol),\n });\n }\n\n results.sort((a, b) => b.similarity - a.similarity);\n return results.slice(0, limit);\n}\n\n/**\n * Find similar symbols across the entire codebase.\n * Uses TF-IDF weighted similarity to filter out infrastructure noise.\n */\nexport function similarAll(\n db: ScipDatabase,\n opts: { minSimilarity?: number; limit?: number; scope?: string; minCallees?: number } = {},\n): SimilarSymbolResult[] {\n const { minSimilarity = 0.5, limit = 20, scope, minCallees = 4 } = opts;\n\n const all = getAllCalleeFingerprints(db, { minCallees, scope });\n const idfWeights = computeIdf(all);\n\n const results: SimilarSymbolResult[] = [];\n\n for (let i = 0; i < all.length; i++) {\n for (let j = i + 1; j < all.length; j++) {\n const a = all[i]!;\n const b = all[j]!;\n\n if (a.file === b.file) continue;\n\n const { similarity, significantShared } = weightedSimilarity(\n a.callees, b.callees, idfWeights,\n );\n\n if (similarity < minSimilarity) continue;\n if (significantShared.length < 2) continue;\n\n results.push({\n symbolA: a.symbol,\n shortNameA: shortenSymbol(a.symbol),\n fileA: a.file,\n symbolB: b.symbol,\n shortNameB: shortenSymbol(b.symbol),\n fileB: b.file,\n similarity,\n sharedCallees: significantShared.map(shortenSymbol),\n uniqueToA: [...difference(a.callees, b.callees)].map(shortenSymbol),\n uniqueToB: [...difference(b.callees, a.callees)].map(shortenSymbol),\n });\n }\n\n if (results.length > limit * 5) break;\n }\n\n results.sort((a, b) => b.similarity - a.similarity);\n return results.slice(0, limit);\n}\n\n// ── TF-IDF Engine ──────────────────────────────────────────\n\n/**\n * Compute inverse document frequency for each callee.\n * IDF(callee) = log(N / df(callee)) where N is total functions\n * and df is how many functions reference that callee.\n *\n * High IDF = rare callee = strong similarity signal.\n * Low IDF = ubiquitous callee = noise.\n */\nfunction computeIdf(fingerprints: SymbolFingerprint[]): Map<string, number> {\n const n = fingerprints.length;\n if (n === 0) return new Map();\n\n // Count how many functions reference each callee\n const docFreq = new Map<string, number>();\n for (const fp of fingerprints) {\n for (const callee of fp.callees) {\n docFreq.set(callee, (docFreq.get(callee) ?? 0) + 1);\n }\n }\n\n // Compute IDF\n const idf = new Map<string, number>();\n for (const [callee, df] of docFreq) {\n idf.set(callee, Math.log(n / df));\n }\n\n return idf;\n}\n\n/**\n * Compute TF-IDF weighted cosine similarity between two callee sets.\n *\n * Each callee is a dimension. Its weight is its IDF score.\n * Cosine similarity of the weighted vectors gives a similarity\n * that ignores ubiquitous callees and emphasizes rare shared ones.\n *\n * Also returns which shared callees are \"significant\" (above-median IDF)\n * vs \"trivial\" (below-median IDF, i.e., infrastructure).\n */\nfunction weightedSimilarity(\n a: Set<string>,\n b: Set<string>,\n idf: Map<string, number>,\n): { similarity: number; significantShared: string[]; trivialShared: string[] } {\n const shared = intersection(a, b);\n if (shared.size === 0) return { similarity: 0, significantShared: [], trivialShared: [] };\n\n // Compute weighted dot product and magnitudes\n let dotProduct = 0;\n let magA = 0;\n let magB = 0;\n\n const allCallees = new Set([...a, ...b]);\n for (const callee of allCallees) {\n const weight = idf.get(callee) ?? 0;\n const inA = a.has(callee) ? weight : 0;\n const inB = b.has(callee) ? weight : 0;\n dotProduct += inA * inB;\n magA += inA * inA;\n magB += inB * inB;\n }\n\n const magnitude = Math.sqrt(magA) * Math.sqrt(magB);\n const similarity = magnitude > 0 ? dotProduct / magnitude : 0;\n\n // Split shared callees into significant (high IDF) and trivial (low IDF)\n const medianIdf = getMedianIdf(idf);\n const significantShared: string[] = [];\n const trivialShared: string[] = [];\n\n for (const callee of shared) {\n const weight = idf.get(callee) ?? 0;\n if (weight >= medianIdf) {\n significantShared.push(callee);\n } else {\n trivialShared.push(callee);\n }\n }\n\n // Sort significant callees by IDF descending (most distinctive first)\n significantShared.sort((x, y) => (idf.get(y) ?? 0) - (idf.get(x) ?? 0));\n\n return { similarity, significantShared, trivialShared };\n}\n\nfunction getMedianIdf(idf: Map<string, number>): number {\n const values = [...idf.values()].sort((a, b) => a - b);\n if (values.length === 0) return 0;\n const mid = Math.floor(values.length / 2);\n return values.length % 2 === 0\n ? (values[mid - 1]! + values[mid]!) / 2\n : values[mid]!;\n}\n\n// ── Internal helpers ───────────────────────────────────────\n\ninterface SymbolFingerprint {\n symbol: string;\n file: string;\n callees: Set<string>;\n}\n\nfunction findCallees(\n db: ScipDatabase,\n symbolPattern: string,\n): SymbolFingerprint | null {\n const target = findFirstSymbolMatch(db, symbolPattern);\n\n if (!target) return null;\n\n const calleeRows = getCalleeRowsForSymbol(db, target);\n\n return {\n symbol: target.symbol,\n file: target.relativePath,\n callees: new Set(calleeRows.map((r) => r.symbol)),\n };\n}\n\nfunction getAllCalleeFingerprints(\n db: ScipDatabase,\n opts: { minCallees: number; scope?: string; excludeSymbol?: string },\n): SymbolFingerprint[] {\n const { minCallees, scope, excludeSymbol } = opts;\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n const excludeFilter = excludeSymbol ? `AND gs.symbol != '${excludeSymbol.replace(/'/g, \"''\")}'` : '';\n\n const symbols = db.all<{\n id: number;\n symbol: string;\n document_id: number;\n start_line: number;\n end_line: number;\n relative_path: string;\n }>(\n `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, 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 1 = 1\n ${db.pathExclusionsFor('d')}\n ${db.symbolNoiseFor('gs')}\n AND (der.end_line - der.start_line + 1) >= 5\n ${scopeFilter}\n ${excludeFilter}\n ORDER BY d.relative_path`,\n );\n\n const fingerprints: SymbolFingerprint[] = [];\n\n for (const sym of symbols) {\n if (db.isIgnored(sym.relative_path)) continue;\n\n const calleeRows = getCalleeRowsForSymbol(db, {\n documentId: sym.document_id,\n startLine: sym.start_line,\n endLine: sym.end_line,\n symbolId: sym.id,\n });\n\n const callees = new Set(calleeRows.map((r) => r.symbol));\n if (callees.size >= minCallees) {\n fingerprints.push({ symbol: sym.symbol, file: sym.relative_path, callees });\n }\n }\n\n return fingerprints;\n}\n\nfunction intersection<T>(a: Set<T>, b: Set<T>): Set<T> {\n const result = new Set<T>();\n for (const item of a) {\n if (b.has(item)) result.add(item);\n }\n return result;\n}\n\nfunction difference<T>(a: Set<T>, b: Set<T>): Set<T> {\n const result = new Set<T>();\n for (const item of a) {\n if (!b.has(item)) result.add(item);\n }\n return result;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,55 @@
1
+ import {
2
+ findFirstSymbolMatch,
3
+ getCalleeRowsForSymbol
4
+ } from "./chunk-FUHJCHS4.js";
5
+ import {
6
+ shortenSymbol
7
+ } from "./chunk-QOV2R2WT.js";
8
+
9
+ // src/queries/call-graph.ts
10
+ function callGraph(db, symbolPattern) {
11
+ const target = findFirstSymbolMatch(db, symbolPattern);
12
+ if (!target) return null;
13
+ const callerRows = db.all(
14
+ `SELECT DISTINCT caller_gs.symbol AS caller_symbol, caller_d.relative_path AS caller_file
15
+ FROM mentions m
16
+ JOIN chunks c ON m.chunk_id = c.id
17
+ JOIN documents ref_d ON c.document_id = ref_d.id
18
+ -- Find the enclosing symbol for where the reference appears
19
+ JOIN defn_enclosing_ranges caller_der
20
+ ON caller_der.document_id = ref_d.id
21
+ AND c.start_line >= caller_der.start_line
22
+ AND c.end_line <= caller_der.end_line
23
+ JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id
24
+ JOIN documents caller_d ON caller_der.document_id = caller_d.id
25
+ WHERE m.symbol_id = ?
26
+ AND m.role = 0
27
+ AND caller_gs.id != ?
28
+ ${db.symbolNoiseFor("caller_gs")}
29
+ ${db.pathExclusionsFor("caller_d")}
30
+ ORDER BY caller_d.relative_path
31
+ LIMIT 50`,
32
+ target.symbolId,
33
+ target.symbolId
34
+ );
35
+ const calleeRows = getCalleeRowsForSymbol(db, target, { limit: 50 });
36
+ return {
37
+ symbol: target.symbol,
38
+ shortName: shortenSymbol(target.symbol),
39
+ callers: callerRows.filter((r) => !db.isIgnored(r.caller_file)).map((r) => ({
40
+ symbol: r.caller_symbol,
41
+ shortName: shortenSymbol(r.caller_symbol),
42
+ file: r.caller_file
43
+ })),
44
+ callees: calleeRows.map((r) => ({
45
+ symbol: r.symbol,
46
+ shortName: shortenSymbol(r.symbol),
47
+ file: r.file
48
+ }))
49
+ };
50
+ }
51
+
52
+ export {
53
+ callGraph
54
+ };
55
+ //# sourceMappingURL=chunk-UNTPVD36.js.map
@@ -0,0 +1 @@
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 = 0\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":[]}
@@ -0,0 +1,88 @@
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/change-surface.ts
10
+ function changeSurface(db, filePattern) {
11
+ const doc = db.get(
12
+ `SELECT id, relative_path FROM documents
13
+ WHERE relative_path LIKE ?
14
+ ${db.pathExclusionsFor("documents")}
15
+ LIMIT 1`,
16
+ `%${filePattern}%`
17
+ );
18
+ if (!doc || db.isIgnored(doc.relative_path)) return null;
19
+ const syms = db.all(
20
+ `SELECT DISTINCT gs.id AS symbol_id, gs.symbol, der.start_line, der.end_line
21
+ FROM defn_enclosing_ranges der
22
+ JOIN global_symbols gs ON der.symbol_id = gs.id
23
+ WHERE der.document_id = ?
24
+ ${db.symbolNoiseFor("gs")}
25
+ ORDER BY der.start_line`,
26
+ doc.id
27
+ );
28
+ const testPatternSql = testFileMatchSql("ref_d", TEST_FILE_PATTERNS);
29
+ const symbols = [];
30
+ let totalExternalConsumers = 0;
31
+ let coveredCount = 0;
32
+ for (const sym of syms) {
33
+ const consumerRow = db.get(
34
+ `SELECT COUNT(DISTINCT c.document_id) AS consumer_count
35
+ FROM mentions m
36
+ JOIN chunks c ON m.chunk_id = c.id
37
+ WHERE m.symbol_id = ?
38
+ AND m.role = 0
39
+ AND c.document_id != ?`,
40
+ sym.symbol_id,
41
+ doc.id
42
+ );
43
+ const externalConsumers = consumerRow?.consumer_count ?? 0;
44
+ const testFiles = db.all(
45
+ `SELECT DISTINCT ref_d.relative_path
46
+ FROM mentions m
47
+ JOIN chunks c ON m.chunk_id = c.id
48
+ JOIN documents ref_d ON c.document_id = ref_d.id
49
+ WHERE m.symbol_id = ?
50
+ AND m.role = 0
51
+ AND (${testPatternSql})
52
+ ORDER BY ref_d.relative_path`,
53
+ sym.symbol_id
54
+ ).map((r) => r.relative_path);
55
+ const hasTests = testFiles.length > 0;
56
+ if (hasTests) coveredCount++;
57
+ let riskLevel;
58
+ if (externalConsumers > 10 && !hasTests) {
59
+ riskLevel = "high";
60
+ } else if (externalConsumers > 5 || !hasTests) {
61
+ riskLevel = "medium";
62
+ } else {
63
+ riskLevel = "low";
64
+ }
65
+ totalExternalConsumers += externalConsumers;
66
+ symbols.push({
67
+ symbol: sym.symbol,
68
+ shortName: shortenSymbol(sym.symbol),
69
+ startLine: sym.start_line,
70
+ endLine: sym.end_line,
71
+ externalConsumers,
72
+ testFiles,
73
+ riskLevel
74
+ });
75
+ }
76
+ const testCoveragePercent = symbols.length > 0 ? Math.round(coveredCount / symbols.length * 100) : 0;
77
+ return {
78
+ file: doc.relative_path,
79
+ symbols,
80
+ totalExternalConsumers,
81
+ testCoveragePercent
82
+ };
83
+ }
84
+
85
+ export {
86
+ changeSurface
87
+ };
88
+ //# sourceMappingURL=chunk-VRUJH4BO.js.map