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,213 @@
1
+ import {
2
+ buildFileDepGraph
3
+ } from "./chunk-FUHJCHS4.js";
4
+
5
+ // src/queries/similar-chains.ts
6
+ function similarChains(db, opts = {}) {
7
+ const {
8
+ minSimilarity = 0.5,
9
+ limit = 15,
10
+ scope,
11
+ minChainLength = 3,
12
+ maxChainLength = 8
13
+ } = opts;
14
+ const graph = buildFileDepGraph(db, scope);
15
+ const rawChains = generateChains(graph, minChainLength, maxChainLength);
16
+ if (rawChains.length === 0) return [];
17
+ const nodeFreq = /* @__PURE__ */ new Map();
18
+ const tailFreq = /* @__PURE__ */ new Map();
19
+ for (const chain of rawChains) {
20
+ const seen = /* @__PURE__ */ new Set();
21
+ for (const node of chain) {
22
+ if (!seen.has(node)) {
23
+ nodeFreq.set(node, (nodeFreq.get(node) ?? 0) + 1);
24
+ seen.add(node);
25
+ }
26
+ }
27
+ for (let t = Math.max(0, chain.length - 2); t < chain.length; t++) {
28
+ tailFreq.set(chain[t], (tailFreq.get(chain[t]) ?? 0) + 1);
29
+ }
30
+ }
31
+ const infraThreshold = rawChains.length * 0.4;
32
+ const tailThreshold = rawChains.length * 0.3;
33
+ const infraNodes = /* @__PURE__ */ new Set();
34
+ for (const [node, freq] of nodeFreq) {
35
+ if (freq > infraThreshold) infraNodes.add(node);
36
+ }
37
+ for (const [node, freq] of tailFreq) {
38
+ if (freq > tailThreshold) infraNodes.add(node);
39
+ }
40
+ const structuralNames = ["index.ts", "index.js", "cli.ts", "main.ts", "health.ts", "health.js"];
41
+ for (const node of nodeFreq.keys()) {
42
+ const basename = node.split("/").pop() ?? "";
43
+ if (structuralNames.includes(basename)) infraNodes.add(node);
44
+ }
45
+ const filteredChains = [];
46
+ for (const chain of rawChains) {
47
+ const filtered = chain.filter((n) => !infraNodes.has(n));
48
+ if (filtered.length >= 3) {
49
+ filteredChains.push({ original: chain, filtered });
50
+ }
51
+ }
52
+ if (filteredChains.length < 2) return [];
53
+ const results = [];
54
+ for (let i = 0; i < filteredChains.length; i++) {
55
+ for (let j = i + 1; j < filteredChains.length; j++) {
56
+ const a = filteredChains[i];
57
+ const b = filteredChains[j];
58
+ const setA = new Set(a.filtered);
59
+ let hasShared = false;
60
+ for (const node of b.filtered) {
61
+ if (setA.has(node)) {
62
+ hasShared = true;
63
+ break;
64
+ }
65
+ }
66
+ if (!hasShared) continue;
67
+ const { distance, ops } = editDistance(a.filtered, b.filtered);
68
+ const maxLen = Math.max(a.filtered.length, b.filtered.length);
69
+ if (maxLen === 0) continue;
70
+ const similarity = 1 - distance / maxLen;
71
+ if (similarity < minSimilarity) continue;
72
+ if (distance === 0) continue;
73
+ const divergencePoints = ops.filter((op) => op.type === "substitute").map((op) => ({
74
+ index: op.indexA,
75
+ nodeA: a.filtered[op.indexA],
76
+ nodeB: b.filtered[op.indexB]
77
+ }));
78
+ if (divergencePoints.length === 0) continue;
79
+ const matchCount = ops.filter((op) => op.type === "match").length;
80
+ if (matchCount < 2) continue;
81
+ const commonPrefix = getCommonPrefix(a.original, b.original);
82
+ const commonSuffix = getCommonSuffix(a.original, b.original);
83
+ results.push({
84
+ chainA: a.original,
85
+ chainB: b.original,
86
+ similarity,
87
+ editDistance: distance,
88
+ divergencePoints,
89
+ commonPrefix,
90
+ commonSuffix
91
+ });
92
+ }
93
+ if (results.length > limit * 10) break;
94
+ }
95
+ results.sort((a, b) => {
96
+ if (Math.abs(b.similarity - a.similarity) > 0.01) return b.similarity - a.similarity;
97
+ return a.divergencePoints.length - b.divergencePoints.length;
98
+ });
99
+ const deduped = [];
100
+ for (const r of results) {
101
+ const isDuplicate = deduped.some(
102
+ (existing) => isSubChain(r.chainA, existing.chainA) && isSubChain(r.chainB, existing.chainB)
103
+ );
104
+ if (!isDuplicate) deduped.push(r);
105
+ if (deduped.length >= limit) break;
106
+ }
107
+ return deduped;
108
+ }
109
+ function generateChains(graph, minLen, maxLen) {
110
+ const chains = [];
111
+ const maxChains = 500;
112
+ for (const startNode of graph.keys()) {
113
+ if (chains.length >= maxChains) break;
114
+ dfsChains(graph, startNode, [startNode], /* @__PURE__ */ new Set([startNode]), minLen, maxLen, chains, maxChains);
115
+ }
116
+ return chains;
117
+ }
118
+ function dfsChains(graph, node, path, visited, minLen, maxLen, results, maxResults) {
119
+ if (results.length >= maxResults) return;
120
+ if (path.length >= maxLen) {
121
+ if (path.length >= minLen) results.push([...path]);
122
+ return;
123
+ }
124
+ const neighbors = graph.get(node);
125
+ if (!neighbors || neighbors.size === 0) {
126
+ if (path.length >= minLen) results.push([...path]);
127
+ return;
128
+ }
129
+ let extended = false;
130
+ for (const next of neighbors) {
131
+ if (visited.has(next)) continue;
132
+ visited.add(next);
133
+ path.push(next);
134
+ dfsChains(graph, next, path, visited, minLen, maxLen, results, maxResults);
135
+ path.pop();
136
+ visited.delete(next);
137
+ extended = true;
138
+ if (results.length >= maxResults) return;
139
+ }
140
+ if (!extended && path.length >= minLen) {
141
+ results.push([...path]);
142
+ }
143
+ }
144
+ function editDistance(a, b) {
145
+ const m = a.length;
146
+ const n = b.length;
147
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
148
+ for (let i2 = 0; i2 <= m; i2++) dp[i2][0] = i2;
149
+ for (let j2 = 0; j2 <= n; j2++) dp[0][j2] = j2;
150
+ for (let i2 = 1; i2 <= m; i2++) {
151
+ for (let j2 = 1; j2 <= n; j2++) {
152
+ if (a[i2 - 1] === b[j2 - 1]) {
153
+ dp[i2][j2] = dp[i2 - 1][j2 - 1];
154
+ } else {
155
+ dp[i2][j2] = 1 + Math.min(
156
+ dp[i2 - 1][j2],
157
+ dp[i2][j2 - 1],
158
+ dp[i2 - 1][j2 - 1]
159
+ );
160
+ }
161
+ }
162
+ }
163
+ const ops = [];
164
+ let i = m, j = n;
165
+ while (i > 0 || j > 0) {
166
+ if (i > 0 && j > 0 && a[i - 1] === b[j - 1]) {
167
+ ops.unshift({ type: "match", indexA: i - 1, indexB: j - 1 });
168
+ i--;
169
+ j--;
170
+ } else if (i > 0 && j > 0 && dp[i][j] === dp[i - 1][j - 1] + 1) {
171
+ ops.unshift({ type: "substitute", indexA: i - 1, indexB: j - 1 });
172
+ i--;
173
+ j--;
174
+ } else if (j > 0 && dp[i][j] === dp[i][j - 1] + 1) {
175
+ ops.unshift({ type: "insert", indexA: i, indexB: j - 1 });
176
+ j--;
177
+ } else {
178
+ ops.unshift({ type: "delete", indexA: i - 1, indexB: j });
179
+ i--;
180
+ }
181
+ }
182
+ return { distance: dp[m][n], ops };
183
+ }
184
+ function getCommonPrefix(a, b) {
185
+ const prefix = [];
186
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
187
+ if (a[i] === b[i]) prefix.push(a[i]);
188
+ else break;
189
+ }
190
+ return prefix;
191
+ }
192
+ function getCommonSuffix(a, b) {
193
+ const suffix = [];
194
+ let ai = a.length - 1;
195
+ let bi = b.length - 1;
196
+ while (ai >= 0 && bi >= 0 && a[ai] === b[bi]) {
197
+ suffix.unshift(a[ai]);
198
+ ai--;
199
+ bi--;
200
+ }
201
+ return suffix;
202
+ }
203
+ function isSubChain(sub, full) {
204
+ if (sub.length > full.length) return false;
205
+ const fullStr = full.join("\u2192");
206
+ const subStr = sub.join("\u2192");
207
+ return fullStr.includes(subStr);
208
+ }
209
+
210
+ export {
211
+ similarChains
212
+ };
213
+ //# sourceMappingURL=chunk-JKP5GH6T.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/similar-chains.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { buildFileDepGraph } from '../query-support.js';\nimport type { SimilarChainResult } from '../types.js';\n\n/**\n * Find end-to-end dependency flows that are structurally similar\n * but diverge at a few points — indicating duplicated pipelines\n * that could be consolidated.\n *\n * Uses infrastructure-filtered chain comparison:\n *\n * 1. Build all dependency chains via DFS\n * 2. Compute node frequency across all chains\n * 3. Filter out infrastructure nodes (appearing in >50% of chains)\n * 4. Compare the filtered chains — what's left is the unique pipeline\n * 5. Edit distance on filtered chains finds real structural duplicates\n *\n * Two chains that both pass through db.ts → types.ts is meaningless.\n * Two chains that both pass through userValidation → userRepo → emailService\n * after filtering is a strong consolidation signal.\n */\nexport function similarChains(\n db: ScipDatabase,\n opts: {\n minSimilarity?: number;\n limit?: number;\n scope?: string;\n minChainLength?: number;\n maxChainLength?: number;\n } = {},\n): SimilarChainResult[] {\n const {\n minSimilarity = 0.5,\n limit = 15,\n scope,\n minChainLength = 3,\n maxChainLength = 8,\n } = opts;\n\n const graph = buildFileDepGraph(db, scope);\n const rawChains = generateChains(graph, minChainLength, maxChainLength);\n\n if (rawChains.length === 0) return [];\n\n // Compute node frequency to identify infrastructure.\n // Count both general frequency (appears anywhere in chain) and\n // tail frequency (appears as one of the last 2 nodes). Shared\n // tails like \"→ db.ts → types.ts\" are infrastructure even if\n // they don't appear in >50% of chains overall.\n const nodeFreq = new Map<string, number>();\n const tailFreq = new Map<string, number>();\n for (const chain of rawChains) {\n const seen = new Set<string>();\n for (const node of chain) {\n if (!seen.has(node)) {\n nodeFreq.set(node, (nodeFreq.get(node) ?? 0) + 1);\n seen.add(node);\n }\n }\n // Track tail nodes (last 2)\n for (let t = Math.max(0, chain.length - 2); t < chain.length; t++) {\n tailFreq.set(chain[t]!, (tailFreq.get(chain[t]!) ?? 0) + 1);\n }\n }\n\n // Infrastructure: nodes in >40% of chains OR tail nodes in >30% of chains\n const infraThreshold = rawChains.length * 0.4;\n const tailThreshold = rawChains.length * 0.3;\n const infraNodes = new Set<string>();\n for (const [node, freq] of nodeFreq) {\n if (freq > infraThreshold) infraNodes.add(node);\n }\n for (const [node, freq] of tailFreq) {\n if (freq > tailThreshold) infraNodes.add(node);\n }\n\n // Also treat structural role files as infrastructure — entry points,\n // barrels, and orchestrators are not meaningful pipeline nodes.\n const structuralNames = ['index.ts', 'index.js', 'cli.ts', 'main.ts', 'health.ts', 'health.js'];\n for (const node of nodeFreq.keys()) {\n const basename = node.split('/').pop() ?? '';\n if (structuralNames.includes(basename)) infraNodes.add(node);\n }\n\n // Filter chains: remove infrastructure nodes, keep the \"unique pipeline\"\n const filteredChains: { original: string[]; filtered: string[] }[] = [];\n for (const chain of rawChains) {\n const filtered = chain.filter((n) => !infraNodes.has(n));\n // Only keep chains that have at least 3 non-infrastructure nodes.\n // Chains with 1-2 unique nodes are just \"query → infra\" which is\n // the expected pattern, not a consolidation opportunity.\n if (filtered.length >= 3) {\n filteredChains.push({ original: chain, filtered });\n }\n }\n\n if (filteredChains.length < 2) return [];\n\n // Pairwise comparison on filtered chains\n const results: SimilarChainResult[] = [];\n\n for (let i = 0; i < filteredChains.length; i++) {\n for (let j = i + 1; j < filteredChains.length; j++) {\n const a = filteredChains[i]!;\n const b = filteredChains[j]!;\n\n // Quick reject: filtered chains must share at least one non-infra node\n const setA = new Set(a.filtered);\n let hasShared = false;\n for (const node of b.filtered) {\n if (setA.has(node)) { hasShared = true; break; }\n }\n if (!hasShared) continue;\n\n // Edit distance on the FILTERED chains (infrastructure stripped)\n const { distance, ops } = editDistance(a.filtered, b.filtered);\n const maxLen = Math.max(a.filtered.length, b.filtered.length);\n if (maxLen === 0) continue;\n\n const similarity = 1 - distance / maxLen;\n if (similarity < minSimilarity) continue;\n if (distance === 0) continue; // identical filtered chains = not interesting\n\n // Divergence points from the filtered comparison\n const divergencePoints = ops\n .filter((op) => op.type === 'substitute')\n .map((op) => ({\n index: op.indexA,\n nodeA: a.filtered[op.indexA]!,\n nodeB: b.filtered[op.indexB]!,\n }));\n\n if (divergencePoints.length === 0) continue;\n\n // Require at least 2 matching (non-divergent) filtered nodes.\n // A chain pair with 1 match and 1 divergence is just \"two things\n // that share one dependency\" — not a duplicated pipeline.\n const matchCount = ops.filter((op) => op.type === 'match').length;\n if (matchCount < 2) continue;\n\n // Report using original chains for context, but similarity is from filtered\n const commonPrefix = getCommonPrefix(a.original, b.original);\n const commonSuffix = getCommonSuffix(a.original, b.original);\n\n results.push({\n chainA: a.original,\n chainB: b.original,\n similarity,\n editDistance: distance,\n divergencePoints,\n commonPrefix,\n commonSuffix,\n });\n }\n\n if (results.length > limit * 10) break;\n }\n\n // Sort by similarity desc, then fewest divergence points\n results.sort((a, b) => {\n if (Math.abs(b.similarity - a.similarity) > 0.01) return b.similarity - a.similarity;\n return a.divergencePoints.length - b.divergencePoints.length;\n });\n\n // Deduplicate sub-chains\n const deduped: SimilarChainResult[] = [];\n for (const r of results) {\n const isDuplicate = deduped.some(\n (existing) =>\n isSubChain(r.chainA, existing.chainA) && isSubChain(r.chainB, existing.chainB),\n );\n if (!isDuplicate) deduped.push(r);\n if (deduped.length >= limit) break;\n }\n\n return deduped;\n}\n\n// ── Chain generation ───────────────────────────────────────\n\nfunction generateChains(\n graph: Map<string, Set<string>>,\n minLen: number,\n maxLen: number,\n): string[][] {\n const chains: string[][] = [];\n const maxChains = 500;\n\n for (const startNode of graph.keys()) {\n if (chains.length >= maxChains) break;\n dfsChains(graph, startNode, [startNode], new Set([startNode]), minLen, maxLen, chains, maxChains);\n }\n\n return chains;\n}\n\nfunction dfsChains(\n graph: Map<string, Set<string>>,\n node: string,\n path: string[],\n visited: Set<string>,\n minLen: number,\n maxLen: number,\n results: string[][],\n maxResults: number,\n): void {\n if (results.length >= maxResults) return;\n if (path.length >= maxLen) {\n if (path.length >= minLen) results.push([...path]);\n return;\n }\n\n const neighbors = graph.get(node);\n if (!neighbors || neighbors.size === 0) {\n if (path.length >= minLen) results.push([...path]);\n return;\n }\n\n let extended = false;\n for (const next of neighbors) {\n if (visited.has(next)) continue;\n visited.add(next);\n path.push(next);\n dfsChains(graph, next, path, visited, minLen, maxLen, results, maxResults);\n path.pop();\n visited.delete(next);\n extended = true;\n if (results.length >= maxResults) return;\n }\n\n if (!extended && path.length >= minLen) {\n results.push([...path]);\n }\n}\n\n// ── Edit distance ──────────────────────────────────────────\n\ninterface EditOp {\n type: 'match' | 'substitute' | 'insert' | 'delete';\n indexA: number;\n indexB: number;\n}\n\nfunction editDistance(a: string[], b: string[]): { distance: number; ops: EditOp[] } {\n const m = a.length;\n const n = b.length;\n\n const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));\n for (let i = 0; i <= m; i++) dp[i]![0] = i;\n for (let j = 0; j <= n; j++) dp[0]![j] = j;\n\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n if (a[i - 1] === b[j - 1]) {\n dp[i]![j] = dp[i - 1]![j - 1]!;\n } else {\n dp[i]![j] = 1 + Math.min(\n dp[i - 1]![j]!,\n dp[i]![j - 1]!,\n dp[i - 1]![j - 1]!,\n );\n }\n }\n }\n\n const ops: EditOp[] = [];\n let i = m, j = n;\n while (i > 0 || j > 0) {\n if (i > 0 && j > 0 && a[i - 1] === b[j - 1]) {\n ops.unshift({ type: 'match', indexA: i - 1, indexB: j - 1 });\n i--; j--;\n } else if (i > 0 && j > 0 && dp[i]![j] === dp[i - 1]![j - 1]! + 1) {\n ops.unshift({ type: 'substitute', indexA: i - 1, indexB: j - 1 });\n i--; j--;\n } else if (j > 0 && dp[i]![j] === dp[i]![j - 1]! + 1) {\n ops.unshift({ type: 'insert', indexA: i, indexB: j - 1 });\n j--;\n } else {\n ops.unshift({ type: 'delete', indexA: i - 1, indexB: j });\n i--;\n }\n }\n\n return { distance: dp[m]![n]!, ops };\n}\n\n// ── Utility ────────────────────────────────────────────────\n\nfunction getCommonPrefix(a: string[], b: string[]): string[] {\n const prefix: string[] = [];\n for (let i = 0; i < Math.min(a.length, b.length); i++) {\n if (a[i] === b[i]) prefix.push(a[i]!);\n else break;\n }\n return prefix;\n}\n\nfunction getCommonSuffix(a: string[], b: string[]): string[] {\n const suffix: string[] = [];\n let ai = a.length - 1;\n let bi = b.length - 1;\n while (ai >= 0 && bi >= 0 && a[ai] === b[bi]) {\n suffix.unshift(a[ai]!);\n ai--; bi--;\n }\n return suffix;\n}\n\nfunction isSubChain(sub: string[], full: string[]): boolean {\n if (sub.length > full.length) return false;\n const fullStr = full.join('→');\n const subStr = sub.join('→');\n return fullStr.includes(subStr);\n}\n"],"mappings":"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","names":["i","j"]}
@@ -0,0 +1,38 @@
1
+ import {
2
+ cleanSignature
3
+ } from "./chunk-4TYLS5XX.js";
4
+ import {
5
+ shortenSymbol
6
+ } from "./chunk-QOV2R2WT.js";
7
+
8
+ // src/queries/symbols.ts
9
+ function symbols(db, filePattern) {
10
+ const rows = db.all(
11
+ `SELECT
12
+ der.start_line,
13
+ der.end_line,
14
+ REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig,
15
+ gs.symbol,
16
+ d.relative_path
17
+ FROM defn_enclosing_ranges der
18
+ JOIN global_symbols gs ON der.symbol_id = gs.id
19
+ JOIN documents d ON der.document_id = d.id
20
+ WHERE d.relative_path LIKE ?
21
+ AND ${db.localSymbolPredicate}
22
+ ${db.symbolNoise}
23
+ ORDER BY der.start_line`,
24
+ `%${filePattern}%`
25
+ );
26
+ return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
27
+ startLine: r.start_line,
28
+ endLine: r.end_line,
29
+ symbol: r.symbol,
30
+ shortName: shortenSymbol(r.symbol),
31
+ signature: cleanSignature(r.sig)
32
+ }));
33
+ }
34
+
35
+ export {
36
+ symbols
37
+ };
38
+ //# sourceMappingURL=chunk-KCBMVQL5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/symbols.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { SymbolResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\nimport { cleanSignature } from './clean-signature.js';\n\nexport function symbols(db: ScipDatabase, filePattern: string): SymbolResult[] {\n const rows = db.all<{\n start_line: number;\n end_line: number;\n sig: string | null;\n symbol: string;\n relative_path: string;\n }>(\n `SELECT\n der.start_line,\n der.end_line,\n REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig,\n gs.symbol,\n 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 d.relative_path LIKE ?\n AND ${db.localSymbolPredicate}\n ${db.symbolNoise}\n ORDER BY der.start_line`,\n `%${filePattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n startLine: r.start_line,\n endLine: r.end_line,\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n signature: cleanSignature(r.sig),\n }));\n}\n"],"mappings":";;;;;;;;AAKO,SAAS,QAAQ,IAAkB,aAAqC;AAC7E,QAAM,OAAO,GAAG;AAAA,IAOd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAUQ,GAAG,oBAAoB;AAAA,QAC3B,GAAG,WAAW;AAAA;AAAA,IAElB,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,WAAW,eAAe,EAAE,GAAG;AAAA,EACjC,EAAE;AACN;","names":[]}
@@ -0,0 +1,78 @@
1
+ // src/queries/coupling.ts
2
+ function coupling(db, file1, file2) {
3
+ const row = db.get(
4
+ `SELECT COUNT(DISTINCT gs.id) AS shared
5
+ FROM global_symbols gs
6
+ WHERE (
7
+ -- Defined in file1, referenced in file2
8
+ EXISTS (
9
+ SELECT 1 FROM defn_enclosing_ranges der
10
+ JOIN documents d ON der.document_id = d.id
11
+ WHERE der.symbol_id = gs.id AND d.relative_path LIKE ?
12
+ )
13
+ AND EXISTS (
14
+ SELECT 1 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 = gs.id AND m.role = 0 AND d.relative_path LIKE ?
18
+ )
19
+ ) OR (
20
+ -- Defined in file2, referenced in file1
21
+ EXISTS (
22
+ SELECT 1 FROM defn_enclosing_ranges der
23
+ JOIN documents d ON der.document_id = d.id
24
+ WHERE der.symbol_id = gs.id AND d.relative_path LIKE ?
25
+ )
26
+ AND EXISTS (
27
+ SELECT 1 FROM mentions m
28
+ JOIN chunks c ON m.chunk_id = c.id
29
+ JOIN documents d ON c.document_id = d.id
30
+ WHERE m.symbol_id = gs.id AND m.role = 0 AND d.relative_path LIKE ?
31
+ )
32
+ )`,
33
+ `%${file1}%`,
34
+ `%${file2}%`,
35
+ `%${file2}%`,
36
+ `%${file1}%`
37
+ );
38
+ return {
39
+ file1,
40
+ file2,
41
+ sharedSymbols: row?.shared ?? 0
42
+ };
43
+ }
44
+ function topCoupling(db, opts = {}) {
45
+ const { limit = 20, scope } = opts;
46
+ const scopeFilter = scope ? `AND d1.relative_path LIKE '%${scope}%' AND d2.relative_path LIKE '%${scope}%'` : "";
47
+ const rows = db.all(
48
+ `SELECT
49
+ def_d.relative_path AS file1,
50
+ ref_d.relative_path AS file2,
51
+ COUNT(DISTINCT gs.id) AS shared
52
+ FROM mentions m
53
+ JOIN chunks c ON m.chunk_id = c.id
54
+ JOIN documents ref_d ON c.document_id = ref_d.id
55
+ JOIN global_symbols gs ON m.symbol_id = gs.id
56
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
57
+ JOIN documents def_d ON der.document_id = def_d.id
58
+ WHERE m.role = 0
59
+ AND def_d.id != ref_d.id
60
+ ${db.pathExclusionsFor("def_d", "ref_d")}
61
+ ${scopeFilter}
62
+ GROUP BY def_d.id, ref_d.id
63
+ ORDER BY shared DESC
64
+ LIMIT ?`,
65
+ limit
66
+ );
67
+ return rows.filter((r) => !db.isIgnored(r.file1) && !db.isIgnored(r.file2)).map((r) => ({
68
+ file1: r.file1,
69
+ file2: r.file2,
70
+ sharedSymbols: r.shared
71
+ }));
72
+ }
73
+
74
+ export {
75
+ coupling,
76
+ topCoupling
77
+ };
78
+ //# sourceMappingURL=chunk-KVSW5KYP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/coupling.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { CouplingResult } from '../types.js';\n\n/**\n * Measure coupling between two files: how many symbols do they share\n * (symbols defined in one and referenced in the other, or vice versa).\n */\nexport function coupling(\n db: ScipDatabase,\n file1: string,\n file2: string,\n): CouplingResult {\n const row = db.get<{ shared: number }>(\n `SELECT COUNT(DISTINCT gs.id) AS shared\n FROM global_symbols gs\n WHERE (\n -- Defined in file1, referenced in file2\n EXISTS (\n SELECT 1 FROM defn_enclosing_ranges der\n JOIN documents d ON der.document_id = d.id\n WHERE der.symbol_id = gs.id AND d.relative_path LIKE ?\n )\n AND EXISTS (\n SELECT 1 FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n WHERE m.symbol_id = gs.id AND m.role = 0 AND d.relative_path LIKE ?\n )\n ) OR (\n -- Defined in file2, referenced in file1\n EXISTS (\n SELECT 1 FROM defn_enclosing_ranges der\n JOIN documents d ON der.document_id = d.id\n WHERE der.symbol_id = gs.id AND d.relative_path LIKE ?\n )\n AND EXISTS (\n SELECT 1 FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n WHERE m.symbol_id = gs.id AND m.role = 0 AND d.relative_path LIKE ?\n )\n )`,\n `%${file1}%`, `%${file2}%`,\n `%${file2}%`, `%${file1}%`,\n );\n\n return {\n file1,\n file2,\n sharedSymbols: row?.shared ?? 0,\n };\n}\n\n/**\n * Find the most coupled file pairs in the codebase.\n */\nexport function topCoupling(\n db: ScipDatabase,\n opts: { limit?: number; scope?: string } = {},\n): CouplingResult[] {\n const { limit = 20, scope } = opts;\n const scopeFilter = scope\n ? `AND d1.relative_path LIKE '%${scope}%' AND d2.relative_path LIKE '%${scope}%'`\n : '';\n\n // Find file pairs that share the most symbols (one defines, other references)\n const rows = db.all<{\n file1: string;\n file2: string;\n shared: number;\n }>(\n `SELECT\n def_d.relative_path AS file1,\n ref_d.relative_path AS file2,\n COUNT(DISTINCT gs.id) AS shared\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents def_d ON der.document_id = def_d.id\n WHERE m.role = 0\n AND def_d.id != ref_d.id\n ${db.pathExclusionsFor('def_d', 'ref_d')}\n ${scopeFilter}\n GROUP BY def_d.id, ref_d.id\n ORDER BY shared DESC\n LIMIT ?`,\n limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.file1) && !db.isIgnored(r.file2))\n .map((r) => ({\n file1: r.file1,\n file2: r.file2,\n sharedSymbols: r.shared,\n }));\n}\n"],"mappings":";AAOO,SAAS,SACd,IACA,OACA,OACgB;AAChB,QAAM,MAAM,GAAG;AAAA,IACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IA6BA,IAAI,KAAK;AAAA,IAAK,IAAI,KAAK;AAAA,IACvB,IAAI,KAAK;AAAA,IAAK,IAAI,KAAK;AAAA,EACzB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,eAAe,KAAK,UAAU;AAAA,EAChC;AACF;AAKO,SAAS,YACd,IACA,OAA2C,CAAC,GAC1B;AAClB,QAAM,EAAE,QAAQ,IAAI,MAAM,IAAI;AAC9B,QAAM,cAAc,QAChB,+BAA+B,KAAK,kCAAkC,KAAK,OAC3E;AAGJ,QAAM,OAAO,GAAG;AAAA,IAKd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAYI,GAAG,kBAAkB,SAAS,OAAO,CAAC;AAAA,QACtC,WAAW;AAAA;AAAA;AAAA;AAAA,IAIf;AAAA,EACF;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,KAAK,KAAK,CAAC,GAAG,UAAU,EAAE,KAAK,CAAC,EAC9D,IAAI,CAAC,OAAO;AAAA,IACX,OAAO,EAAE;AAAA,IACT,OAAO,EAAE;AAAA,IACT,eAAe,EAAE;AAAA,EACnB,EAAE;AACN;","names":[]}
@@ -0,0 +1,172 @@
1
+ import {
2
+ shortenSymbol
3
+ } from "./chunk-QOV2R2WT.js";
4
+
5
+ // src/queries/by-kind.ts
6
+ var KIND_NAMES = {
7
+ 0: "UnspecifiedKind",
8
+ 1: "AbstractMethod",
9
+ 2: "Accessor",
10
+ 3: "Array",
11
+ 4: "Assertion",
12
+ 5: "AssociatedType",
13
+ 6: "Attribute",
14
+ 7: "Axiom",
15
+ 8: "Boolean",
16
+ 9: "Class",
17
+ 10: "Constant",
18
+ 11: "Constructor",
19
+ 12: "Contract",
20
+ 13: "DataFamily",
21
+ 14: "DefinitionMacro",
22
+ 15: "Delegate",
23
+ 16: "Enum",
24
+ 17: "EnumMember",
25
+ 18: "Error",
26
+ 19: "Event",
27
+ 20: "Fact",
28
+ 21: "Field",
29
+ 22: "File",
30
+ 23: "Function",
31
+ 24: "Getter",
32
+ 25: "Grammar",
33
+ 26: "Instance",
34
+ 27: "Interface",
35
+ 28: "Key",
36
+ 29: "Lang",
37
+ 30: "Lemma",
38
+ 31: "Library",
39
+ 32: "Macro",
40
+ 33: "Method",
41
+ 34: "MethodAlias",
42
+ 35: "MethodReceiver",
43
+ 36: "MethodSpecification",
44
+ 37: "Message",
45
+ 38: "Modifier",
46
+ 39: "Module",
47
+ 40: "Namespace",
48
+ 41: "Null",
49
+ 42: "Number",
50
+ 43: "Object",
51
+ 44: "Operator",
52
+ 45: "Package",
53
+ 46: "PackageObject",
54
+ 47: "Parameter",
55
+ 48: "ParameterLabel",
56
+ 49: "Pattern",
57
+ 50: "Predicate",
58
+ 51: "Property",
59
+ 52: "Protocol",
60
+ 53: "ProtocolMethod",
61
+ 54: "PureVirtualMethod",
62
+ 55: "Quasiquoter",
63
+ 56: "SelfParameter",
64
+ 57: "Setter",
65
+ 58: "Signature",
66
+ 59: "SingletonClass",
67
+ 60: "SingletonMethod",
68
+ 61: "StaticDataMember",
69
+ 62: "StaticEvent",
70
+ 63: "StaticField",
71
+ 64: "StaticMethod",
72
+ 65: "StaticProperty",
73
+ 66: "StaticVariable",
74
+ 67: "String",
75
+ 68: "Struct",
76
+ 69: "Subscript",
77
+ 70: "Tactic",
78
+ 71: "Theorem",
79
+ 72: "ThisParameter",
80
+ 73: "Trait",
81
+ 74: "TraitMethod",
82
+ 75: "Type",
83
+ 76: "TypeAlias",
84
+ 77: "TypeClass",
85
+ 78: "TypeClassMethod",
86
+ 79: "TypeFamily",
87
+ 80: "TypeParameter",
88
+ 81: "Union",
89
+ 82: "Value",
90
+ 83: "Variable"
91
+ };
92
+ var KIND_BY_NAME = /* @__PURE__ */ new Map();
93
+ for (const [k, v] of Object.entries(KIND_NAMES)) {
94
+ KIND_BY_NAME.set(v.toLowerCase(), Number(k));
95
+ }
96
+ function byKind(db, kindQuery, opts = {}) {
97
+ const { scope, limit = 100 } = opts;
98
+ let kindNum = null;
99
+ const asNum = parseInt(kindQuery, 10);
100
+ if (!isNaN(asNum)) {
101
+ kindNum = asNum;
102
+ } else {
103
+ kindNum = KIND_BY_NAME.get(kindQuery.toLowerCase()) ?? null;
104
+ if (kindNum === null) {
105
+ for (const [name, num] of KIND_BY_NAME) {
106
+ if (name.includes(kindQuery.toLowerCase())) {
107
+ kindNum = num;
108
+ break;
109
+ }
110
+ }
111
+ }
112
+ }
113
+ if (kindNum === null) {
114
+ return [];
115
+ }
116
+ const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
117
+ const hasKinds = db.get(
118
+ `SELECT COUNT(*) AS c FROM global_symbols WHERE kind IS NOT NULL`
119
+ );
120
+ if (!hasKinds || hasKinds.c === 0) {
121
+ return [];
122
+ }
123
+ const rows = db.all(
124
+ `SELECT gs.symbol, gs.kind, d.relative_path, der.start_line, der.end_line
125
+ FROM global_symbols gs
126
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
127
+ JOIN documents d ON der.document_id = d.id
128
+ WHERE gs.kind = ?
129
+ ${db.pathExclusionsFor("d")}
130
+ ${scopeFilter}
131
+ ORDER BY d.relative_path, der.start_line
132
+ LIMIT ?`,
133
+ kindNum,
134
+ limit
135
+ );
136
+ return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
137
+ symbol: r.symbol,
138
+ shortName: shortenSymbol(r.symbol),
139
+ kind: r.kind,
140
+ kindName: KIND_NAMES[r.kind] ?? "Unknown",
141
+ relativePath: r.relative_path,
142
+ startLine: r.start_line,
143
+ endLine: r.end_line
144
+ }));
145
+ }
146
+ function kindCounts(db, opts = {}) {
147
+ const scopeFilter = opts.scope ? `AND d.relative_path LIKE '%${opts.scope}%'` : "";
148
+ const rows = db.all(
149
+ `SELECT gs.kind, COUNT(*) AS cnt
150
+ FROM global_symbols gs
151
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
152
+ JOIN documents d ON der.document_id = d.id
153
+ WHERE 1 = 1
154
+ ${db.pathExclusionsFor("d")}
155
+ AND gs.kind IS NOT NULL
156
+ AND gs.kind != 0
157
+ ${scopeFilter}
158
+ GROUP BY gs.kind
159
+ ORDER BY cnt DESC`
160
+ );
161
+ return rows.map((r) => ({
162
+ kind: r.kind,
163
+ kindName: KIND_NAMES[r.kind] ?? "Unknown",
164
+ count: r.cnt
165
+ }));
166
+ }
167
+
168
+ export {
169
+ byKind,
170
+ kindCounts
171
+ };
172
+ //# sourceMappingURL=chunk-LAWMH22O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/by-kind.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { ByKindResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * SCIP SymbolInformation.Kind enum values.\n * From: https://github.com/sourcegraph/scip/blob/main/scip.proto\n */\nconst KIND_NAMES: Record<number, string> = {\n 0: 'UnspecifiedKind',\n 1: 'AbstractMethod',\n 2: 'Accessor',\n 3: 'Array',\n 4: 'Assertion',\n 5: 'AssociatedType',\n 6: 'Attribute',\n 7: 'Axiom',\n 8: 'Boolean',\n 9: 'Class',\n 10: 'Constant',\n 11: 'Constructor',\n 12: 'Contract',\n 13: 'DataFamily',\n 14: 'DefinitionMacro',\n 15: 'Delegate',\n 16: 'Enum',\n 17: 'EnumMember',\n 18: 'Error',\n 19: 'Event',\n 20: 'Fact',\n 21: 'Field',\n 22: 'File',\n 23: 'Function',\n 24: 'Getter',\n 25: 'Grammar',\n 26: 'Instance',\n 27: 'Interface',\n 28: 'Key',\n 29: 'Lang',\n 30: 'Lemma',\n 31: 'Library',\n 32: 'Macro',\n 33: 'Method',\n 34: 'MethodAlias',\n 35: 'MethodReceiver',\n 36: 'MethodSpecification',\n 37: 'Message',\n 38: 'Modifier',\n 39: 'Module',\n 40: 'Namespace',\n 41: 'Null',\n 42: 'Number',\n 43: 'Object',\n 44: 'Operator',\n 45: 'Package',\n 46: 'PackageObject',\n 47: 'Parameter',\n 48: 'ParameterLabel',\n 49: 'Pattern',\n 50: 'Predicate',\n 51: 'Property',\n 52: 'Protocol',\n 53: 'ProtocolMethod',\n 54: 'PureVirtualMethod',\n 55: 'Quasiquoter',\n 56: 'SelfParameter',\n 57: 'Setter',\n 58: 'Signature',\n 59: 'SingletonClass',\n 60: 'SingletonMethod',\n 61: 'StaticDataMember',\n 62: 'StaticEvent',\n 63: 'StaticField',\n 64: 'StaticMethod',\n 65: 'StaticProperty',\n 66: 'StaticVariable',\n 67: 'String',\n 68: 'Struct',\n 69: 'Subscript',\n 70: 'Tactic',\n 71: 'Theorem',\n 72: 'ThisParameter',\n 73: 'Trait',\n 74: 'TraitMethod',\n 75: 'Type',\n 76: 'TypeAlias',\n 77: 'TypeClass',\n 78: 'TypeClassMethod',\n 79: 'TypeFamily',\n 80: 'TypeParameter',\n 81: 'Union',\n 82: 'Value',\n 83: 'Variable',\n};\n\n/** Reverse lookup: name -> kind number */\nconst KIND_BY_NAME = new Map<string, number>();\nfor (const [k, v] of Object.entries(KIND_NAMES)) {\n KIND_BY_NAME.set(v.toLowerCase(), Number(k));\n}\n\n/**\n * Find symbols by SCIP kind (class, interface, enum, function, etc.)\n */\nexport function byKind(\n db: ScipDatabase,\n kindQuery: string,\n opts: { scope?: string; limit?: number } = {},\n): ByKindResult[] {\n const { scope, limit = 100 } = opts;\n\n // Resolve kind: accept number or name\n let kindNum: number | null = null;\n const asNum = parseInt(kindQuery, 10);\n if (!isNaN(asNum)) {\n kindNum = asNum;\n } else {\n kindNum = KIND_BY_NAME.get(kindQuery.toLowerCase()) ?? null;\n // Fuzzy match: try partial name\n if (kindNum === null) {\n for (const [name, num] of KIND_BY_NAME) {\n if (name.includes(kindQuery.toLowerCase())) {\n kindNum = num;\n break;\n }\n }\n }\n }\n\n if (kindNum === null) {\n return [];\n }\n\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n // Check if the index actually has kind data populated\n const hasKinds = db.get<{ c: number }>(\n `SELECT COUNT(*) AS c FROM global_symbols WHERE kind IS NOT NULL`,\n );\n if (!hasKinds || hasKinds.c === 0) {\n return []; // Indexer doesn't populate kind field\n }\n\n const rows = db.all<{\n symbol: string;\n kind: number;\n relative_path: string;\n start_line: number;\n end_line: number;\n }>(\n `SELECT gs.symbol, gs.kind, d.relative_path, der.start_line, der.end_line\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE gs.kind = ?\n ${db.pathExclusionsFor('d')}\n ${scopeFilter}\n ORDER BY d.relative_path, der.start_line\n LIMIT ?`,\n kindNum, limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n kind: r.kind,\n kindName: KIND_NAMES[r.kind] ?? 'Unknown',\n relativePath: r.relative_path,\n startLine: r.start_line,\n endLine: r.end_line,\n }));\n}\n\n/** List all symbol kinds present in the index with counts */\nexport function kindCounts(\n db: ScipDatabase,\n opts: { scope?: string } = {},\n): Array<{ kind: number; kindName: string; count: number }> {\n const scopeFilter = opts.scope\n ? `AND d.relative_path LIKE '%${opts.scope}%'`\n : '';\n\n const rows = db.all<{ kind: number; cnt: number }>(\n `SELECT gs.kind, COUNT(*) AS cnt\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 gs.kind IS NOT NULL\n AND gs.kind != 0\n ${scopeFilter}\n GROUP BY gs.kind\n ORDER BY cnt DESC`,\n );\n\n return rows.map((r) => ({\n kind: r.kind,\n kindName: KIND_NAMES[r.kind] ?? 'Unknown',\n count: r.cnt,\n }));\n}\n"],"mappings":";;;;;AAQA,IAAM,aAAqC;AAAA,EACzC,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAGA,IAAM,eAAe,oBAAI,IAAoB;AAC7C,WAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC/C,eAAa,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC;AAC7C;AAKO,SAAS,OACd,IACA,WACA,OAA2C,CAAC,GAC5B;AAChB,QAAM,EAAE,OAAO,QAAQ,IAAI,IAAI;AAG/B,MAAI,UAAyB;AAC7B,QAAM,QAAQ,SAAS,WAAW,EAAE;AACpC,MAAI,CAAC,MAAM,KAAK,GAAG;AACjB,cAAU;AAAA,EACZ,OAAO;AACL,cAAU,aAAa,IAAI,UAAU,YAAY,CAAC,KAAK;AAEvD,QAAI,YAAY,MAAM;AACpB,iBAAW,CAAC,MAAM,GAAG,KAAK,cAAc;AACtC,YAAI,KAAK,SAAS,UAAU,YAAY,CAAC,GAAG;AAC1C,oBAAU;AACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,YAAY,MAAM;AACpB,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAGtE,QAAM,WAAW,GAAG;AAAA,IAClB;AAAA,EACF;AACA,MAAI,CAAC,YAAY,SAAS,MAAM,GAAG;AACjC,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,OAAO,GAAG;AAAA,IAOd;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,GAAG,kBAAkB,GAAG,CAAC;AAAA,QACzB,WAAW;AAAA;AAAA;AAAA,IAGf;AAAA,IAAS;AAAA,EACX;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,MAAM,EAAE;AAAA,IACR,UAAU,WAAW,EAAE,IAAI,KAAK;AAAA,IAChC,cAAc,EAAE;AAAA,IAChB,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,EACb,EAAE;AACN;AAGO,SAAS,WACd,IACA,OAA2B,CAAC,GAC8B;AAC1D,QAAM,cAAc,KAAK,QACrB,8BAA8B,KAAK,KAAK,OACxC;AAEJ,QAAM,OAAO,GAAG;AAAA,IACd;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,GAAG,kBAAkB,GAAG,CAAC;AAAA;AAAA;AAAA,QAGzB,WAAW;AAAA;AAAA;AAAA,EAGjB;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,MAAM,EAAE;AAAA,IACR,UAAU,WAAW,EAAE,IAAI,KAAK;AAAA,IAChC,OAAO,EAAE;AAAA,EACX,EAAE;AACJ;","names":[]}
@@ -0,0 +1,72 @@
1
+ import {
2
+ findFirstSymbolMatch,
3
+ getCalleeRowsForSymbol
4
+ } from "./chunk-FUHJCHS4.js";
5
+ import {
6
+ shortenSymbol
7
+ } from "./chunk-QOV2R2WT.js";
8
+
9
+ // src/queries/convergence.ts
10
+ function convergence(db, symbolPatternA, symbolPatternB) {
11
+ const matchA = findFirstSymbolMatch(db, symbolPatternA);
12
+ const matchB = findFirstSymbolMatch(db, symbolPatternB);
13
+ if (!matchA || !matchB) return null;
14
+ const calleesA = new Set(
15
+ getCalleeRowsForSymbol(db, matchA).map((r) => r.symbol)
16
+ );
17
+ const calleesB = new Set(
18
+ getCalleeRowsForSymbol(db, matchB).map((r) => r.symbol)
19
+ );
20
+ const shared = [];
21
+ for (const c of calleesA) {
22
+ if (calleesB.has(c)) shared.push(c);
23
+ }
24
+ const uniqueA = [];
25
+ for (const c of calleesA) {
26
+ if (!calleesB.has(c)) uniqueA.push(c);
27
+ }
28
+ const uniqueB = [];
29
+ for (const c of calleesB) {
30
+ if (!calleesA.has(c)) uniqueB.push(c);
31
+ }
32
+ const union = /* @__PURE__ */ new Set([...calleesA, ...calleesB]);
33
+ const similarity = union.size > 0 ? shared.length / union.size : 0;
34
+ let strategy;
35
+ if (uniqueA.length === 0 && uniqueB.length === 0) {
36
+ strategy = "These functions have identical callee sets. One can replace the other directly.";
37
+ } else if (uniqueA.length === 0) {
38
+ strategy = `A is a subset of B. A can be replaced by calling B (B does everything A does plus more).`;
39
+ } else if (uniqueB.length === 0) {
40
+ strategy = `B is a subset of A. B can be replaced by calling A (A does everything B does plus more).`;
41
+ } else if (uniqueA.length <= 2 && uniqueB.length <= 2) {
42
+ strategy = `Create a shared function with the ${shared.length} common callees. Pass the ${uniqueA.length + uniqueB.length} divergent callees as parameters or strategy callbacks.`;
43
+ } else {
44
+ strategy = `Extract the ${shared.length} shared callees into a common helper. Each function calls the helper plus its own unique logic (${uniqueA.length} callees in A, ${uniqueB.length} in B).`;
45
+ }
46
+ const locA = matchA.endLine - matchA.startLine + 1;
47
+ const locB = matchB.endLine - matchB.startLine + 1;
48
+ return {
49
+ symbolA: {
50
+ symbol: matchA.symbol,
51
+ shortName: shortenSymbol(matchA.symbol),
52
+ file: matchA.relativePath,
53
+ loc: locA
54
+ },
55
+ symbolB: {
56
+ symbol: matchB.symbol,
57
+ shortName: shortenSymbol(matchB.symbol),
58
+ file: matchB.relativePath,
59
+ loc: locB
60
+ },
61
+ similarity,
62
+ sharedCallees: shared.map(shortenSymbol),
63
+ uniqueToA: uniqueA.map(shortenSymbol),
64
+ uniqueToB: uniqueB.map(shortenSymbol),
65
+ consolidationStrategy: strategy
66
+ };
67
+ }
68
+
69
+ export {
70
+ convergence
71
+ };
72
+ //# sourceMappingURL=chunk-LB7OS35Q.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/convergence.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch, getCalleeRowsForSymbol } from '../query-support.js';\nimport type { ConvergenceResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Given two similar symbols, show what a consolidated version would look like.\n * The shared callee set becomes the common body. The unique callees become\n * the parameterization points.\n */\nexport function convergence(\n db: ScipDatabase,\n symbolPatternA: string,\n symbolPatternB: string,\n): ConvergenceResult | null {\n const matchA = findFirstSymbolMatch(db, symbolPatternA);\n const matchB = findFirstSymbolMatch(db, symbolPatternB);\n\n if (!matchA || !matchB) return null;\n\n const calleesA = new Set(\n getCalleeRowsForSymbol(db, matchA).map((r) => r.symbol),\n );\n const calleesB = new Set(\n getCalleeRowsForSymbol(db, matchB).map((r) => r.symbol),\n );\n\n const shared: string[] = [];\n for (const c of calleesA) {\n if (calleesB.has(c)) shared.push(c);\n }\n\n const uniqueA: string[] = [];\n for (const c of calleesA) {\n if (!calleesB.has(c)) uniqueA.push(c);\n }\n\n const uniqueB: string[] = [];\n for (const c of calleesB) {\n if (!calleesA.has(c)) uniqueB.push(c);\n }\n\n const union = new Set([...calleesA, ...calleesB]);\n const similarity = union.size > 0 ? shared.length / union.size : 0;\n\n // Generate a consolidation strategy description\n let strategy: string;\n if (uniqueA.length === 0 && uniqueB.length === 0) {\n strategy = 'These functions have identical callee sets. One can replace the other directly.';\n } else if (uniqueA.length === 0) {\n strategy = `A is a subset of B. A can be replaced by calling B (B does everything A does plus more).`;\n } else if (uniqueB.length === 0) {\n strategy = `B is a subset of A. B can be replaced by calling A (A does everything B does plus more).`;\n } else if (uniqueA.length <= 2 && uniqueB.length <= 2) {\n strategy = `Create a shared function with the ${shared.length} common callees. Pass the ${uniqueA.length + uniqueB.length} divergent callees as parameters or strategy callbacks.`;\n } else {\n strategy = `Extract the ${shared.length} shared callees into a common helper. Each function calls the helper plus its own unique logic (${uniqueA.length} callees in A, ${uniqueB.length} in B).`;\n }\n\n const locA = matchA.endLine - matchA.startLine + 1;\n const locB = matchB.endLine - matchB.startLine + 1;\n\n return {\n symbolA: {\n symbol: matchA.symbol,\n shortName: shortenSymbol(matchA.symbol),\n file: matchA.relativePath,\n loc: locA,\n },\n symbolB: {\n symbol: matchB.symbol,\n shortName: shortenSymbol(matchB.symbol),\n file: matchB.relativePath,\n loc: locB,\n },\n similarity,\n sharedCallees: shared.map(shortenSymbol),\n uniqueToA: uniqueA.map(shortenSymbol),\n uniqueToB: uniqueB.map(shortenSymbol),\n consolidationStrategy: strategy,\n };\n}\n"],"mappings":";;;;;;;;;AAUO,SAAS,YACd,IACA,gBACA,gBAC0B;AAC1B,QAAM,SAAS,qBAAqB,IAAI,cAAc;AACtD,QAAM,SAAS,qBAAqB,IAAI,cAAc;AAEtD,MAAI,CAAC,UAAU,CAAC,OAAQ,QAAO;AAE/B,QAAM,WAAW,IAAI;AAAA,IACnB,uBAAuB,IAAI,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM;AAAA,EACxD;AACA,QAAM,WAAW,IAAI;AAAA,IACnB,uBAAuB,IAAI,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM;AAAA,EACxD;AAEA,QAAM,SAAmB,CAAC;AAC1B,aAAW,KAAK,UAAU;AACxB,QAAI,SAAS,IAAI,CAAC,EAAG,QAAO,KAAK,CAAC;AAAA,EACpC;AAEA,QAAM,UAAoB,CAAC;AAC3B,aAAW,KAAK,UAAU;AACxB,QAAI,CAAC,SAAS,IAAI,CAAC,EAAG,SAAQ,KAAK,CAAC;AAAA,EACtC;AAEA,QAAM,UAAoB,CAAC;AAC3B,aAAW,KAAK,UAAU;AACxB,QAAI,CAAC,SAAS,IAAI,CAAC,EAAG,SAAQ,KAAK,CAAC;AAAA,EACtC;AAEA,QAAM,QAAQ,oBAAI,IAAI,CAAC,GAAG,UAAU,GAAG,QAAQ,CAAC;AAChD,QAAM,aAAa,MAAM,OAAO,IAAI,OAAO,SAAS,MAAM,OAAO;AAGjE,MAAI;AACJ,MAAI,QAAQ,WAAW,KAAK,QAAQ,WAAW,GAAG;AAChD,eAAW;AAAA,EACb,WAAW,QAAQ,WAAW,GAAG;AAC/B,eAAW;AAAA,EACb,WAAW,QAAQ,WAAW,GAAG;AAC/B,eAAW;AAAA,EACb,WAAW,QAAQ,UAAU,KAAK,QAAQ,UAAU,GAAG;AACrD,eAAW,qCAAqC,OAAO,MAAM,6BAA6B,QAAQ,SAAS,QAAQ,MAAM;AAAA,EAC3H,OAAO;AACL,eAAW,eAAe,OAAO,MAAM,mGAAmG,QAAQ,MAAM,kBAAkB,QAAQ,MAAM;AAAA,EAC1L;AAEA,QAAM,OAAO,OAAO,UAAU,OAAO,YAAY;AACjD,QAAM,OAAO,OAAO,UAAU,OAAO,YAAY;AAEjD,SAAO;AAAA,IACL,SAAS;AAAA,MACP,QAAQ,OAAO;AAAA,MACf,WAAW,cAAc,OAAO,MAAM;AAAA,MACtC,MAAM,OAAO;AAAA,MACb,KAAK;AAAA,IACP;AAAA,IACA,SAAS;AAAA,MACP,QAAQ,OAAO;AAAA,MACf,WAAW,cAAc,OAAO,MAAM;AAAA,MACtC,MAAM,OAAO;AAAA,MACb,KAAK;AAAA,IACP;AAAA,IACA;AAAA,IACA,eAAe,OAAO,IAAI,aAAa;AAAA,IACvC,WAAW,QAAQ,IAAI,aAAa;AAAA,IACpC,WAAW,QAAQ,IAAI,aAAa;AAAA,IACpC,uBAAuB;AAAA,EACzB;AACF;","names":[]}