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,61 @@
1
+ import {
2
+ buildFileDepGraph
3
+ } from "./chunk-FUHJCHS4.js";
4
+
5
+ // src/queries/cycles.ts
6
+ function cycles(db, opts = {}) {
7
+ const { scope, maxDepth = 10 } = opts;
8
+ const graph = buildFileDepGraph(db, scope);
9
+ const allCycles = [];
10
+ const visited = /* @__PURE__ */ new Set();
11
+ const inStack = /* @__PURE__ */ new Set();
12
+ const stack = [];
13
+ function dfs(node, depth) {
14
+ if (depth > maxDepth) return;
15
+ if (inStack.has(node)) {
16
+ const cycleStart = stack.indexOf(node);
17
+ if (cycleStart !== -1) {
18
+ const cyclePath = stack.slice(cycleStart).concat(node);
19
+ const minIdx = cyclePath.indexOf(
20
+ cyclePath.reduce((a, b) => a < b ? a : b)
21
+ );
22
+ const normalized = [
23
+ ...cyclePath.slice(minIdx, -1),
24
+ ...cyclePath.slice(0, minIdx),
25
+ cyclePath[minIdx]
26
+ ];
27
+ const key = normalized.join(" -> ");
28
+ if (!seenCycles.has(key)) {
29
+ seenCycles.add(key);
30
+ allCycles.push({ path: normalized });
31
+ }
32
+ }
33
+ return;
34
+ }
35
+ if (visited.has(node)) return;
36
+ visited.add(node);
37
+ inStack.add(node);
38
+ stack.push(node);
39
+ const neighbors = graph.get(node);
40
+ if (neighbors) {
41
+ for (const neighbor of neighbors) {
42
+ dfs(neighbor, depth + 1);
43
+ }
44
+ }
45
+ stack.pop();
46
+ inStack.delete(node);
47
+ }
48
+ const seenCycles = /* @__PURE__ */ new Set();
49
+ for (const node of graph.keys()) {
50
+ if (!visited.has(node)) {
51
+ dfs(node, 0);
52
+ }
53
+ }
54
+ allCycles.sort((a, b) => a.path.length - b.path.length);
55
+ return allCycles;
56
+ }
57
+
58
+ export {
59
+ cycles
60
+ };
61
+ //# sourceMappingURL=chunk-5WTJAXY2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/cycles.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { buildFileDepGraph } from '../query-support.js';\nimport type { CycleResult } from '../types.js';\n\n/**\n * Detect circular dependency chains between files.\n * A cycle exists when file A depends on B, B depends on C, and C depends on A.\n *\n * Uses the same dependency edges as the `deps` command (symbol definitions\n * referenced across files), then runs DFS cycle detection.\n */\nexport function cycles(\n db: ScipDatabase,\n opts: { scope?: string; maxDepth?: number } = {},\n): CycleResult[] {\n const { scope, maxDepth = 10 } = opts;\n const graph = buildFileDepGraph(db, scope);\n\n // DFS cycle detection\n const allCycles: CycleResult[] = [];\n const visited = new Set<string>();\n const inStack = new Set<string>();\n const stack: string[] = [];\n\n function dfs(node: string, depth: number): void {\n if (depth > maxDepth) return;\n if (inStack.has(node)) {\n // Found a cycle — extract it from the stack\n const cycleStart = stack.indexOf(node);\n if (cycleStart !== -1) {\n const cyclePath = stack.slice(cycleStart).concat(node);\n // Normalize: start from the lexicographically smallest file\n const minIdx = cyclePath.indexOf(\n cyclePath.reduce((a, b) => (a < b ? a : b)),\n );\n const normalized = [\n ...cyclePath.slice(minIdx, -1),\n ...cyclePath.slice(0, minIdx),\n cyclePath[minIdx]!,\n ];\n // Deduplicate\n const key = normalized.join(' -> ');\n if (!seenCycles.has(key)) {\n seenCycles.add(key);\n allCycles.push({ path: normalized });\n }\n }\n return;\n }\n if (visited.has(node)) return;\n\n visited.add(node);\n inStack.add(node);\n stack.push(node);\n\n const neighbors = graph.get(node);\n if (neighbors) {\n for (const neighbor of neighbors) {\n dfs(neighbor, depth + 1);\n }\n }\n\n stack.pop();\n inStack.delete(node);\n }\n\n const seenCycles = new Set<string>();\n for (const node of graph.keys()) {\n if (!visited.has(node)) {\n dfs(node, 0);\n }\n }\n\n // Sort by cycle length (shorter cycles are more actionable)\n allCycles.sort((a, b) => a.path.length - b.path.length);\n\n return allCycles;\n}\n"],"mappings":";;;;;AAWO,SAAS,OACd,IACA,OAA8C,CAAC,GAChC;AACf,QAAM,EAAE,OAAO,WAAW,GAAG,IAAI;AACjC,QAAM,QAAQ,kBAAkB,IAAI,KAAK;AAGzC,QAAM,YAA2B,CAAC;AAClC,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,QAAkB,CAAC;AAEzB,WAAS,IAAI,MAAc,OAAqB;AAC9C,QAAI,QAAQ,SAAU;AACtB,QAAI,QAAQ,IAAI,IAAI,GAAG;AAErB,YAAM,aAAa,MAAM,QAAQ,IAAI;AACrC,UAAI,eAAe,IAAI;AACrB,cAAM,YAAY,MAAM,MAAM,UAAU,EAAE,OAAO,IAAI;AAErD,cAAM,SAAS,UAAU;AAAA,UACvB,UAAU,OAAO,CAAC,GAAG,MAAO,IAAI,IAAI,IAAI,CAAE;AAAA,QAC5C;AACA,cAAM,aAAa;AAAA,UACjB,GAAG,UAAU,MAAM,QAAQ,EAAE;AAAA,UAC7B,GAAG,UAAU,MAAM,GAAG,MAAM;AAAA,UAC5B,UAAU,MAAM;AAAA,QAClB;AAEA,cAAM,MAAM,WAAW,KAAK,MAAM;AAClC,YAAI,CAAC,WAAW,IAAI,GAAG,GAAG;AACxB,qBAAW,IAAI,GAAG;AAClB,oBAAU,KAAK,EAAE,MAAM,WAAW,CAAC;AAAA,QACrC;AAAA,MACF;AACA;AAAA,IACF;AACA,QAAI,QAAQ,IAAI,IAAI,EAAG;AAEvB,YAAQ,IAAI,IAAI;AAChB,YAAQ,IAAI,IAAI;AAChB,UAAM,KAAK,IAAI;AAEf,UAAM,YAAY,MAAM,IAAI,IAAI;AAChC,QAAI,WAAW;AACb,iBAAW,YAAY,WAAW;AAChC,YAAI,UAAU,QAAQ,CAAC;AAAA,MACzB;AAAA,IACF;AAEA,UAAM,IAAI;AACV,YAAQ,OAAO,IAAI;AAAA,EACrB;AAEA,QAAM,aAAa,oBAAI,IAAY;AACnC,aAAW,QAAQ,MAAM,KAAK,GAAG;AAC/B,QAAI,CAAC,QAAQ,IAAI,IAAI,GAAG;AACtB,UAAI,MAAM,CAAC;AAAA,IACb;AAAA,EACF;AAGA,YAAU,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,SAAS,EAAE,KAAK,MAAM;AAEtD,SAAO;AACT;","names":[]}
@@ -0,0 +1,24 @@
1
+ // src/queries/refs.ts
2
+ function refs(db, symbolPattern) {
3
+ const rows = db.all(
4
+ `SELECT DISTINCT d.relative_path, c.start_line
5
+ FROM mentions m
6
+ JOIN chunks c ON m.chunk_id = c.id
7
+ JOIN documents d ON c.document_id = d.id
8
+ JOIN global_symbols gs ON m.symbol_id = gs.id
9
+ WHERE gs.symbol LIKE ?
10
+ AND ${db.localSymbolPredicate}
11
+ AND m.role = 0
12
+ ORDER BY d.relative_path, c.start_line`,
13
+ `%${symbolPattern}%`
14
+ );
15
+ return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
16
+ relativePath: r.relative_path,
17
+ line: r.start_line
18
+ }));
19
+ }
20
+
21
+ export {
22
+ refs
23
+ };
24
+ //# sourceMappingURL=chunk-6NBLIDF4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/refs.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { RefResult } from '../types.js';\n\nexport function refs(db: ScipDatabase, symbolPattern: string): RefResult[] {\n const rows = db.all<{\n relative_path: string;\n start_line: number;\n }>(\n `SELECT DISTINCT d.relative_path, c.start_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 JOIN global_symbols gs ON m.symbol_id = gs.id\n WHERE gs.symbol LIKE ?\n AND ${db.localSymbolPredicate}\n AND m.role = 0\n ORDER BY d.relative_path, c.start_line`,\n `%${symbolPattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n relativePath: r.relative_path,\n line: r.start_line,\n }));\n}\n"],"mappings":";AAGO,SAAS,KAAK,IAAkB,eAAoC;AACzE,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAMQ,GAAG,oBAAoB;AAAA;AAAA;AAAA,IAG/B,IAAI,aAAa;AAAA,EACnB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,cAAc,EAAE;AAAA,IAChB,MAAM,EAAE;AAAA,EACV,EAAE;AACN;","names":[]}
@@ -0,0 +1,43 @@
1
+ import {
2
+ shortenSymbol
3
+ } from "./chunk-QOV2R2WT.js";
4
+
5
+ // src/queries/outline.ts
6
+ function outline(db, filePattern) {
7
+ const rows = db.all(
8
+ `SELECT gs.symbol, gs.enclosing_symbol, der.start_line, der.end_line
9
+ FROM defn_enclosing_ranges der
10
+ JOIN global_symbols gs ON der.symbol_id = gs.id
11
+ JOIN documents d ON der.document_id = d.id
12
+ WHERE d.relative_path LIKE ?
13
+ ${db.symbolNoise}
14
+ ORDER BY der.start_line`,
15
+ `%${filePattern}%`
16
+ );
17
+ const nodeMap = /* @__PURE__ */ new Map();
18
+ const roots = [];
19
+ for (const r of rows) {
20
+ const node = {
21
+ symbol: r.symbol,
22
+ shortName: shortenSymbol(r.symbol),
23
+ startLine: r.start_line,
24
+ endLine: r.end_line,
25
+ children: []
26
+ };
27
+ nodeMap.set(r.symbol, node);
28
+ }
29
+ for (const r of rows) {
30
+ const node = nodeMap.get(r.symbol);
31
+ if (r.enclosing_symbol && nodeMap.has(r.enclosing_symbol)) {
32
+ nodeMap.get(r.enclosing_symbol).children.push(node);
33
+ } else {
34
+ roots.push(node);
35
+ }
36
+ }
37
+ return roots;
38
+ }
39
+
40
+ export {
41
+ outline
42
+ };
43
+ //# sourceMappingURL=chunk-6SXADWLW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/outline.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { OutlineNode } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Build a tree-structured outline of symbols in a file,\n * using the enclosing_symbol field to establish parent-child relationships.\n */\nexport function outline(db: ScipDatabase, filePattern: string): OutlineNode[] {\n const rows = db.all<{\n symbol: string;\n enclosing_symbol: string | null;\n start_line: number;\n end_line: number;\n }>(\n `SELECT gs.symbol, gs.enclosing_symbol, der.start_line, der.end_line\n FROM defn_enclosing_ranges der\n JOIN global_symbols gs ON der.symbol_id = gs.id\n JOIN documents d ON der.document_id = d.id\n WHERE d.relative_path LIKE ?\n ${db.symbolNoise}\n ORDER BY der.start_line`,\n `%${filePattern}%`,\n );\n\n // Build a map of symbol -> node\n const nodeMap = new Map<string, OutlineNode>();\n const roots: OutlineNode[] = [];\n\n for (const r of rows) {\n const node: OutlineNode = {\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n startLine: r.start_line,\n endLine: r.end_line,\n children: [],\n };\n nodeMap.set(r.symbol, node);\n }\n\n // Wire up parent-child via enclosing_symbol\n for (const r of rows) {\n const node = nodeMap.get(r.symbol)!;\n if (r.enclosing_symbol && nodeMap.has(r.enclosing_symbol)) {\n nodeMap.get(r.enclosing_symbol)!.children.push(node);\n } else {\n roots.push(node);\n }\n }\n\n return roots;\n}\n"],"mappings":";;;;;AAQO,SAAS,QAAQ,IAAkB,aAAoC;AAC5E,QAAM,OAAO,GAAG;AAAA,IAMd;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,GAAG,WAAW;AAAA;AAAA,IAElB,IAAI,WAAW;AAAA,EACjB;AAGA,QAAM,UAAU,oBAAI,IAAyB;AAC7C,QAAM,QAAuB,CAAC;AAE9B,aAAW,KAAK,MAAM;AACpB,UAAM,OAAoB;AAAA,MACxB,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,WAAW,EAAE;AAAA,MACb,SAAS,EAAE;AAAA,MACX,UAAU,CAAC;AAAA,IACb;AACA,YAAQ,IAAI,EAAE,QAAQ,IAAI;AAAA,EAC5B;AAGA,aAAW,KAAK,MAAM;AACpB,UAAM,OAAO,QAAQ,IAAI,EAAE,MAAM;AACjC,QAAI,EAAE,oBAAoB,QAAQ,IAAI,EAAE,gBAAgB,GAAG;AACzD,cAAQ,IAAI,EAAE,gBAAgB,EAAG,SAAS,KAAK,IAAI;AAAA,IACrD,OAAO;AACL,YAAM,KAAK,IAAI;AAAA,IACjB;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -0,0 +1,65 @@
1
+ import {
2
+ findFirstSymbolMatch
3
+ } from "./chunk-FUHJCHS4.js";
4
+ import {
5
+ shortenSymbol
6
+ } from "./chunk-QOV2R2WT.js";
7
+
8
+ // src/queries/affected.ts
9
+ function affected(db, symbolPattern, opts = {}) {
10
+ const { maxDepth = 5, scope } = opts;
11
+ const target = findFirstSymbolMatch(db, symbolPattern);
12
+ if (!target) return [];
13
+ const scopeFilter = scope ? `AND enc_d.relative_path LIKE '%${scope}%'` : "";
14
+ const results = [];
15
+ const visited = /* @__PURE__ */ new Set([target.symbolId]);
16
+ let frontier = /* @__PURE__ */ new Set([target.symbolId]);
17
+ for (let depth = 1; depth <= maxDepth; depth++) {
18
+ if (frontier.size === 0) break;
19
+ const placeholders = [...frontier].map(() => "?").join(",");
20
+ const nextFrontier = /* @__PURE__ */ new Set();
21
+ const rows = db.all(
22
+ `SELECT DISTINCT
23
+ enc_gs.id AS symbol_id,
24
+ enc_gs.symbol AS symbol,
25
+ enc_d.relative_path AS relative_path
26
+ FROM mentions m
27
+ JOIN chunks c ON m.chunk_id = c.id
28
+ JOIN documents ref_d ON c.document_id = ref_d.id
29
+ JOIN defn_enclosing_ranges enc_der
30
+ ON enc_der.document_id = ref_d.id
31
+ AND c.start_line >= enc_der.start_line
32
+ AND c.end_line <= enc_der.end_line
33
+ JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
34
+ JOIN documents enc_d ON enc_der.document_id = enc_d.id
35
+ WHERE m.symbol_id IN (${placeholders})
36
+ AND m.role = 0
37
+ AND enc_gs.id NOT IN (${placeholders})
38
+ ${db.symbolNoiseFor("enc_gs")}
39
+ ${db.pathExclusionsFor("enc_d")}
40
+ ${scopeFilter}`,
41
+ ...[...frontier],
42
+ ...[...frontier]
43
+ );
44
+ for (const row of rows) {
45
+ if (visited.has(row.symbol_id)) continue;
46
+ if (db.isIgnored(row.relative_path)) continue;
47
+ visited.add(row.symbol_id);
48
+ nextFrontier.add(row.symbol_id);
49
+ results.push({
50
+ symbol: row.symbol,
51
+ shortName: shortenSymbol(row.symbol),
52
+ file: row.relative_path,
53
+ depth
54
+ });
55
+ }
56
+ frontier = nextFrontier;
57
+ }
58
+ results.sort((a, b) => a.depth - b.depth || a.file.localeCompare(b.file));
59
+ return results;
60
+ }
61
+
62
+ export {
63
+ affected
64
+ };
65
+ //# sourceMappingURL=chunk-6VJ6Q7IE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/affected.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch } from '../query-support.js';\nimport type { AffectedResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Full transitive closure of symbols that could break if a given symbol changes.\n * BFS from the target through the mention graph: depth 1 = direct consumers,\n * depth 2 = consumers of consumers, etc.\n */\nexport function affected(\n db: ScipDatabase,\n symbolPattern: string,\n opts: { maxDepth?: number; scope?: string } = {},\n): AffectedResult[] {\n const { maxDepth = 5, scope } = opts;\n\n const target = findFirstSymbolMatch(db, symbolPattern);\n if (!target) return [];\n\n const scopeFilter = scope\n ? `AND enc_d.relative_path LIKE '%${scope}%'`\n : '';\n\n const results: AffectedResult[] = [];\n const visited = new Set<number>([target.symbolId]);\n let frontier = new Set<number>([target.symbolId]);\n\n for (let depth = 1; depth <= maxDepth; depth++) {\n if (frontier.size === 0) break;\n\n const placeholders = [...frontier].map(() => '?').join(',');\n const nextFrontier = new Set<number>();\n\n // For each symbol in the frontier, find enclosing symbols whose\n // definition ranges contain a reference (role=0) to that frontier symbol.\n const rows = db.all<{\n symbol_id: number;\n symbol: string;\n relative_path: string;\n }>(\n `SELECT DISTINCT\n enc_gs.id AS symbol_id,\n enc_gs.symbol AS symbol,\n enc_d.relative_path AS relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n JOIN defn_enclosing_ranges enc_der\n ON enc_der.document_id = ref_d.id\n AND c.start_line >= enc_der.start_line\n AND c.end_line <= enc_der.end_line\n JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id\n JOIN documents enc_d ON enc_der.document_id = enc_d.id\n WHERE m.symbol_id IN (${placeholders})\n AND m.role = 0\n AND enc_gs.id NOT IN (${placeholders})\n ${db.symbolNoiseFor('enc_gs')}\n ${db.pathExclusionsFor('enc_d')}\n ${scopeFilter}`,\n ...[...frontier],\n ...[...frontier],\n );\n\n for (const row of rows) {\n if (visited.has(row.symbol_id)) continue;\n if (db.isIgnored(row.relative_path)) continue;\n\n visited.add(row.symbol_id);\n nextFrontier.add(row.symbol_id);\n\n results.push({\n symbol: row.symbol,\n shortName: shortenSymbol(row.symbol),\n file: row.relative_path,\n depth,\n });\n }\n\n frontier = nextFrontier;\n }\n\n // Sort by depth then file path\n results.sort((a, b) => a.depth - b.depth || a.file.localeCompare(b.file));\n return results;\n}\n"],"mappings":";;;;;;;;AAUO,SAAS,SACd,IACA,eACA,OAA8C,CAAC,GAC7B;AAClB,QAAM,EAAE,WAAW,GAAG,MAAM,IAAI;AAEhC,QAAM,SAAS,qBAAqB,IAAI,aAAa;AACrD,MAAI,CAAC,OAAQ,QAAO,CAAC;AAErB,QAAM,cAAc,QAChB,kCAAkC,KAAK,OACvC;AAEJ,QAAM,UAA4B,CAAC;AACnC,QAAM,UAAU,oBAAI,IAAY,CAAC,OAAO,QAAQ,CAAC;AACjD,MAAI,WAAW,oBAAI,IAAY,CAAC,OAAO,QAAQ,CAAC;AAEhD,WAAS,QAAQ,GAAG,SAAS,UAAU,SAAS;AAC9C,QAAI,SAAS,SAAS,EAAG;AAEzB,UAAM,eAAe,CAAC,GAAG,QAAQ,EAAE,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AAC1D,UAAM,eAAe,oBAAI,IAAY;AAIrC,UAAM,OAAO,GAAG;AAAA,MAKd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BAawB,YAAY;AAAA;AAAA,gCAEV,YAAY;AAAA,UAClC,GAAG,eAAe,QAAQ,CAAC;AAAA,UAC3B,GAAG,kBAAkB,OAAO,CAAC;AAAA,UAC7B,WAAW;AAAA,MACf,GAAG,CAAC,GAAG,QAAQ;AAAA,MACf,GAAG,CAAC,GAAG,QAAQ;AAAA,IACjB;AAEA,eAAW,OAAO,MAAM;AACtB,UAAI,QAAQ,IAAI,IAAI,SAAS,EAAG;AAChC,UAAI,GAAG,UAAU,IAAI,aAAa,EAAG;AAErC,cAAQ,IAAI,IAAI,SAAS;AACzB,mBAAa,IAAI,IAAI,SAAS;AAE9B,cAAQ,KAAK;AAAA,QACX,QAAQ,IAAI;AAAA,QACZ,WAAW,cAAc,IAAI,MAAM;AAAA,QACnC,MAAM,IAAI;AAAA,QACV;AAAA,MACF,CAAC;AAAA,IACH;AAEA,eAAW;AAAA,EACb;AAGA,UAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACxE,SAAO;AACT;","names":[]}
@@ -0,0 +1,258 @@
1
+ import {
2
+ staleAbstractions
3
+ } from "./chunk-GJFURBEW.js";
4
+ import {
5
+ stats
6
+ } from "./chunk-XFXDXEUN.js";
7
+ import {
8
+ testCoverageSummary
9
+ } from "./chunk-3ZZJVBIO.js";
10
+ import {
11
+ wrapperCandidates
12
+ } from "./chunk-TDNNOR6D.js";
13
+ import {
14
+ passthroughCandidates
15
+ } from "./chunk-NDSQYIWT.js";
16
+ import {
17
+ similarAll
18
+ } from "./chunk-TSPZOMHC.js";
19
+ import {
20
+ isolated
21
+ } from "./chunk-LUSIFBXO.js";
22
+ import {
23
+ drift
24
+ } from "./chunk-36OMT7ZJ.js";
25
+ import {
26
+ extractCandidates
27
+ } from "./chunk-CM454WL3.js";
28
+ import {
29
+ complexityHotspots
30
+ } from "./chunk-EMDQWNYR.js";
31
+ import {
32
+ cycles
33
+ } from "./chunk-5WTJAXY2.js";
34
+ import {
35
+ dead
36
+ } from "./chunk-5FGUEU7N.js";
37
+
38
+ // src/queries/health.ts
39
+ function health(db, opts = {}) {
40
+ const { scope } = opts;
41
+ const s = stats(db);
42
+ const deadResult = dead(db, { scope, minLoc: 3, skipBarrels: false });
43
+ const isolatedResult = isolated(db, { scope, minLoc: 3 });
44
+ const cycleResult = cycles(db, { scope });
45
+ const similarResult = similarAll(db, { scope, minSimilarity: 0.6, limit: 50, minCallees: 4 });
46
+ const extractResult = extractCandidates(db, { scope, minLoc: 15, minCallees: 5, limit: 50 });
47
+ const wrapperResult = wrapperCandidates(db, { scope, maxLoc: 15, limit: 50 });
48
+ const passthroughResult = passthroughCandidates(db, { scope, maxLoc: 15, limit: 50 });
49
+ const staleResult = staleAbstractions(db, { scope, minLoc: 3, limit: 50 });
50
+ const driftResult = drift(db, { scope });
51
+ const complexResult = complexityHotspots(db, { scope, minLoc: 10, limit: 10 });
52
+ const testResult = testCoverageSummary(db, { scope, minLoc: 3 });
53
+ const isolatedLoc = isolatedResult.reduce((sum, r) => sum + r.loc, 0);
54
+ const entryPointPatterns = ["/index.ts", "/index.js", "cli.ts", "worker.ts", "postinstall.ts", "/mod.rs", "__init__.py", "main.ts", "main.rs", "main.go", "main.py"];
55
+ const isEntryPoint = (path) => entryPointPatterns.some((p) => path.endsWith(p));
56
+ const trueDeadSymbols = deadResult.symbols.filter(
57
+ (s2) => !isEntryPoint(s2.relativePath) && s2.kind === "dead-code"
58
+ );
59
+ const trueDeadCount = trueDeadSymbols.length;
60
+ const trueDeadLoc = trueDeadSymbols.reduce((sum, s2) => sum + s2.loc, 0);
61
+ const fileInternalCount = deadResult.symbols.filter(
62
+ (s2) => !isEntryPoint(s2.relativePath) && s2.kind === "file-internal"
63
+ ).length;
64
+ const trueIsolatedCount = isolatedResult.filter(
65
+ (s2) => !isEntryPoint(s2.relativePath)
66
+ ).length;
67
+ const filesWithFunctions = new Set(
68
+ db.all(
69
+ `SELECT DISTINCT d.relative_path
70
+ FROM global_symbols gs
71
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
72
+ JOIN documents d ON der.document_id = d.id
73
+ WHERE gs.symbol LIKE '%().'
74
+ ${db.pathExclusionsFor("d")}`
75
+ ).map((r) => r.relative_path)
76
+ );
77
+ const trueStaleCount = staleResult.filter((s2) => {
78
+ if (s2.consumers === 0 && filesWithFunctions.has(s2.file)) return false;
79
+ return true;
80
+ }).length;
81
+ const trueDriftCount = driftResult.results.length;
82
+ const trueSimilarCount = similarResult.length;
83
+ const actions = [];
84
+ if (trueDeadCount > 0) {
85
+ actions.push({
86
+ category: "Dead code",
87
+ description: `${trueDeadCount} symbols with zero references anywhere \u2014 safe to delete`,
88
+ effort: "low",
89
+ impact: "high",
90
+ count: trueDeadCount,
91
+ locRecoverable: trueDeadLoc
92
+ });
93
+ }
94
+ if (testResult.percent < 50) {
95
+ actions.push({
96
+ category: "Test coverage",
97
+ description: `${testResult.percent}% of symbols referenced by tests (${testResult.uncovered} uncovered)`,
98
+ effort: "high",
99
+ impact: "high",
100
+ count: testResult.uncovered,
101
+ locRecoverable: 0
102
+ });
103
+ }
104
+ if (trueIsolatedCount > 0) {
105
+ actions.push({
106
+ category: "Isolated symbols",
107
+ description: `${trueIsolatedCount} symbols completely disconnected from the codebase graph`,
108
+ effort: "low",
109
+ impact: "medium",
110
+ count: trueIsolatedCount,
111
+ locRecoverable: isolatedResult.filter((s2) => !isEntryPoint(s2.relativePath)).reduce((sum, s2) => sum + s2.loc, 0)
112
+ });
113
+ }
114
+ if (cycleResult.length > 0) {
115
+ actions.push({
116
+ category: "Circular dependencies",
117
+ description: `${cycleResult.length} cycle(s) \u2014 break with dependency inversion or module restructuring`,
118
+ effort: "medium",
119
+ impact: "high",
120
+ count: cycleResult.length,
121
+ locRecoverable: 0
122
+ });
123
+ }
124
+ if (trueSimilarCount > 0) {
125
+ actions.push({
126
+ category: "Similar functions",
127
+ description: `${trueSimilarCount} pairs with real logic overlap (beyond shared imports) \u2014 consolidation candidates`,
128
+ effort: "medium",
129
+ impact: "medium",
130
+ count: trueSimilarCount,
131
+ locRecoverable: 0
132
+ });
133
+ }
134
+ if (extractResult.length > 0) {
135
+ actions.push({
136
+ category: "Extraction candidates",
137
+ description: `${extractResult.length} large functions with isolated callee clusters \u2014 extract method opportunities`,
138
+ effort: "medium",
139
+ impact: "medium",
140
+ count: extractResult.length,
141
+ locRecoverable: 0
142
+ });
143
+ }
144
+ if (wrapperResult.length > 0) {
145
+ actions.push({
146
+ category: "Wrapper functions",
147
+ description: `${wrapperResult.length} single-consumer symbols that could be inlined`,
148
+ effort: "low",
149
+ impact: "low",
150
+ count: wrapperResult.length,
151
+ locRecoverable: wrapperResult.reduce((sum, r) => sum + r.loc, 0)
152
+ });
153
+ }
154
+ if (passthroughResult.length > 0) {
155
+ actions.push({
156
+ category: "Passthrough functions",
157
+ description: `${passthroughResult.length} functions that just forward to one callee \u2014 unnecessary indirection`,
158
+ effort: "low",
159
+ impact: "low",
160
+ count: passthroughResult.length,
161
+ locRecoverable: passthroughResult.reduce((sum, r) => sum + r.loc, 0)
162
+ });
163
+ }
164
+ if (trueStaleCount > 0) {
165
+ const trueStaleSymbols = staleResult.filter((s2) => {
166
+ if (s2.consumers === 0 && filesWithFunctions.has(s2.file)) return false;
167
+ return true;
168
+ });
169
+ const unused = trueStaleSymbols.filter((s2) => s2.consumers === 0).length;
170
+ const singleUse = trueStaleCount - unused;
171
+ const parts = [];
172
+ if (unused > 0) parts.push(`${unused} unused`);
173
+ if (singleUse > 0) parts.push(`${singleUse} single-consumer (not in types file)`);
174
+ actions.push({
175
+ category: "Stale abstractions",
176
+ description: `${parts.join(", ")} \u2014 premature abstraction`,
177
+ effort: "low",
178
+ impact: "medium",
179
+ count: trueStaleCount,
180
+ locRecoverable: staleResult.filter((s2) => s2.consumers === 0 || !s2.file.includes("types")).reduce((sum, r) => sum + r.loc, 0)
181
+ });
182
+ }
183
+ if (trueDriftCount > 0) {
184
+ const parts = [];
185
+ if (driftResult.unusedImports > 0) parts.push(`${driftResult.unusedImports} unused imports`);
186
+ if (driftResult.layerViolations > 0) parts.push(`${driftResult.layerViolations} layer violations`);
187
+ if (driftResult.patternDeviations > 0) parts.push(`${driftResult.patternDeviations} unique deps`);
188
+ actions.push({
189
+ category: "Structural drift",
190
+ description: parts.join(", "),
191
+ effort: driftResult.layerViolations > 0 ? "medium" : "low",
192
+ impact: driftResult.layerViolations > 0 ? "medium" : "low",
193
+ count: trueDriftCount,
194
+ locRecoverable: 0
195
+ });
196
+ }
197
+ const impactWeight = { high: 3, medium: 2, low: 1 };
198
+ const effortWeight = { low: 3, medium: 2, high: 1 };
199
+ actions.sort((a, b) => {
200
+ const scoreA = impactWeight[a.impact] * effortWeight[a.effort];
201
+ const scoreB = impactWeight[b.impact] * effortWeight[b.effort];
202
+ return scoreB - scoreA;
203
+ });
204
+ const fileCount = Math.max(s.documents, 1);
205
+ const symbolCount = Math.max(s.symbols, 1);
206
+ let score = 100;
207
+ const deadPercent = trueDeadCount / symbolCount;
208
+ score -= Math.min(20, Math.round(deadPercent * 200));
209
+ const isolatedPercent = trueIsolatedCount / symbolCount;
210
+ score -= Math.min(10, Math.round(isolatedPercent * 200));
211
+ score -= Math.min(15, cycleResult.length * 5);
212
+ score -= Math.min(10, trueSimilarCount * 2);
213
+ score -= Math.min(5, extractResult.length * 2);
214
+ score -= Math.min(3, wrapperResult.length);
215
+ score -= Math.min(3, passthroughResult.length);
216
+ const stalePercent = trueStaleCount / Math.max(symbolCount * 0.1, 1);
217
+ score -= Math.min(8, Math.round(stalePercent * 10));
218
+ const driftPercent = trueDriftCount / fileCount;
219
+ score -= Math.min(5, Math.round(driftPercent * 50));
220
+ const extremeComplexity = complexResult.filter((r) => r.score > 50).length;
221
+ score -= Math.min(5, extremeComplexity * 2);
222
+ const coverageDeduction = Math.round(15 * (1 - testResult.percent / 100));
223
+ score -= coverageDeduction;
224
+ score = Math.max(0, Math.min(100, score));
225
+ return {
226
+ score,
227
+ overview: {
228
+ documents: s.documents,
229
+ symbols: s.symbols,
230
+ indexSizeBytes: s.indexSizeBytes
231
+ },
232
+ findings: {
233
+ deadSymbols: trueDeadCount,
234
+ deadLoc: trueDeadLoc,
235
+ isolatedSymbols: trueIsolatedCount,
236
+ isolatedLoc: isolatedResult.filter((s2) => !isEntryPoint(s2.relativePath)).reduce((sum, s2) => sum + s2.loc, 0),
237
+ cycles: cycleResult.length,
238
+ similarPairs: trueSimilarCount,
239
+ extractionCandidates: extractResult.length,
240
+ wrappers: wrapperResult.length,
241
+ passthroughs: passthroughResult.length,
242
+ staleTypes: trueStaleCount,
243
+ driftedFiles: trueDriftCount,
244
+ complexityHotspotCount: complexResult.length,
245
+ testCoveragePercent: testResult.percent
246
+ },
247
+ actions,
248
+ topComplexity: complexResult.slice(0, 5).map((r) => ({
249
+ symbol: r.shortName,
250
+ score: r.score
251
+ }))
252
+ };
253
+ }
254
+
255
+ export {
256
+ health
257
+ };
258
+ //# sourceMappingURL=chunk-7OZPA5OO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/health.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { dead } from './dead.js';\nimport { isolated } from './isolated.js';\nimport { cycles } from './cycles.js';\nimport { similarAll } from './similar.js';\nimport { extractCandidates } from './extract-candidates.js';\nimport { wrapperCandidates } from './wrapper-candidates.js';\nimport { passthroughCandidates } from './passthrough-candidates.js';\nimport { staleAbstractions } from './stale-abstractions.js';\nimport { drift } from './drift.js';\nimport { complexityHotspots } from './complexity-hotspots.js';\nimport { testCoverageSummary } from './test-coverage.js';\nimport { stats } from './stats.js';\nimport type { HealthAction, HealthReport } from '../types.js';\n\n/**\n * Single composite health report that runs all de-bloat analyses\n * and produces a prioritized action list.\n *\n * The scoring formula accounts for common false positives:\n * - Entry points (CLI, workers, barrels) appearing as \"dead code\"\n * - Typed result interfaces with 1 consumer (normal for APIs)\n * - Consistent import patterns across sibling modules (not duplication)\n * - Barrel and orchestrator files deviating from sibling patterns (expected)\n */\nexport function health(\n db: ScipDatabase,\n opts: { scope?: string } = {},\n): HealthReport {\n const { scope } = opts;\n\n // Run all analyses\n const s = stats(db);\n const deadResult = dead(db, { scope, minLoc: 3, skipBarrels: false });\n const isolatedResult = isolated(db, { scope, minLoc: 3 });\n const cycleResult = cycles(db, { scope });\n const similarResult = similarAll(db, { scope, minSimilarity: 0.6, limit: 50, minCallees: 4 });\n const extractResult = extractCandidates(db, { scope, minLoc: 15, minCallees: 5, limit: 50 });\n const wrapperResult = wrapperCandidates(db, { scope, maxLoc: 15, limit: 50 });\n const passthroughResult = passthroughCandidates(db, { scope, maxLoc: 15, limit: 50 });\n const staleResult = staleAbstractions(db, { scope, minLoc: 3, limit: 50 });\n const driftResult = drift(db, { scope });\n const complexResult = complexityHotspots(db, { scope, minLoc: 10, limit: 10 });\n const testResult = testCoverageSummary(db, { scope, minLoc: 3 });\n\n const isolatedLoc = isolatedResult.reduce((sum, r) => sum + r.loc, 0);\n\n // ── False-positive filtering ─────────────────────────────\n\n // Entry points and barrels appear as dead/isolated because nothing imports them.\n // Filter them out of the scoring (but still report them with a note).\n const entryPointPatterns = ['/index.ts', '/index.js', 'cli.ts', 'worker.ts', 'postinstall.ts', '/mod.rs', '__init__.py', 'main.ts', 'main.rs', 'main.go', 'main.py'];\n const isEntryPoint = (path: string) => entryPointPatterns.some((p) => path.endsWith(p));\n\n // Dead code: only count truly dead symbols (zero refs anywhere),\n // excluding entry points AND file-internal helpers (which are fine).\n const trueDeadSymbols = deadResult.symbols.filter(\n (s) => !isEntryPoint(s.relativePath) && s.kind === 'dead-code',\n );\n const trueDeadCount = trueDeadSymbols.length;\n const trueDeadLoc = trueDeadSymbols.reduce((sum, s) => sum + s.loc, 0);\n const fileInternalCount = deadResult.symbols.filter(\n (s) => !isEntryPoint(s.relativePath) && s.kind === 'file-internal',\n ).length;\n\n // Isolated: same entry-point filtering\n const trueIsolatedCount = isolatedResult.filter(\n (s) => !isEntryPoint(s.relativePath),\n ).length;\n\n // Stale abstractions: the command filters out types.ts single-consumer types.\n // Also filter out 0-consumer types in files that export functions — these are\n // likely parameter/return types consumed through function signatures, which\n // the SCIP index can't track as direct mentions.\n const filesWithFunctions = new Set(\n db.all<{ relative_path: string }>(\n `SELECT DISTINCT d.relative_path\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE gs.symbol LIKE '%().'\n ${db.pathExclusionsFor('d')}`,\n ).map((r) => r.relative_path),\n );\n const trueStaleCount = staleResult.filter((s) => {\n // 0-consumer types in files with functions are likely param/return types\n if (s.consumers === 0 && filesWithFunctions.has(s.file)) return false;\n return true;\n }).length;\n\n // Drift: now uses usage-based detection (unused imports, layer violations, pattern deviations)\n // The drift command already filters structural roles internally.\n const trueDriftCount = driftResult.results.length;\n\n // Similar pairs: the similar command now uses TF-IDF weighted cosine\n // similarity which automatically discounts infrastructure callees.\n // The sharedCallees list only contains significant (above-median IDF) callees.\n // We can trust the count directly.\n const trueSimilarCount = similarResult.length;\n\n // ── Build prioritized action list ────────────────────────\n\n const actions: HealthAction[] = [];\n\n if (trueDeadCount > 0) {\n actions.push({\n category: 'Dead code',\n description: `${trueDeadCount} symbols with zero references anywhere — safe to delete`,\n effort: 'low',\n impact: 'high',\n count: trueDeadCount,\n locRecoverable: trueDeadLoc,\n });\n }\n\n if (testResult.percent < 50) {\n actions.push({\n category: 'Test coverage',\n description: `${testResult.percent}% of symbols referenced by tests (${testResult.uncovered} uncovered)`,\n effort: 'high',\n impact: 'high',\n count: testResult.uncovered,\n locRecoverable: 0,\n });\n }\n\n if (trueIsolatedCount > 0) {\n actions.push({\n category: 'Isolated symbols',\n description: `${trueIsolatedCount} symbols completely disconnected from the codebase graph`,\n effort: 'low',\n impact: 'medium',\n count: trueIsolatedCount,\n locRecoverable: isolatedResult\n .filter((s) => !isEntryPoint(s.relativePath))\n .reduce((sum, s) => sum + s.loc, 0),\n });\n }\n\n if (cycleResult.length > 0) {\n actions.push({\n category: 'Circular dependencies',\n description: `${cycleResult.length} cycle(s) — break with dependency inversion or module restructuring`,\n effort: 'medium',\n impact: 'high',\n count: cycleResult.length,\n locRecoverable: 0,\n });\n }\n\n if (trueSimilarCount > 0) {\n actions.push({\n category: 'Similar functions',\n description: `${trueSimilarCount} pairs with real logic overlap (beyond shared imports) — consolidation candidates`,\n effort: 'medium',\n impact: 'medium',\n count: trueSimilarCount,\n locRecoverable: 0,\n });\n }\n\n if (extractResult.length > 0) {\n actions.push({\n category: 'Extraction candidates',\n description: `${extractResult.length} large functions with isolated callee clusters — extract method opportunities`,\n effort: 'medium',\n impact: 'medium',\n count: extractResult.length,\n locRecoverable: 0,\n });\n }\n\n if (wrapperResult.length > 0) {\n actions.push({\n category: 'Wrapper functions',\n description: `${wrapperResult.length} single-consumer symbols that could be inlined`,\n effort: 'low',\n impact: 'low',\n count: wrapperResult.length,\n locRecoverable: wrapperResult.reduce((sum, r) => sum + r.loc, 0),\n });\n }\n\n if (passthroughResult.length > 0) {\n actions.push({\n category: 'Passthrough functions',\n description: `${passthroughResult.length} functions that just forward to one callee — unnecessary indirection`,\n effort: 'low',\n impact: 'low',\n count: passthroughResult.length,\n locRecoverable: passthroughResult.reduce((sum, r) => sum + r.loc, 0),\n });\n }\n\n if (trueStaleCount > 0) {\n // Count from the filtered set, not the raw result\n const trueStaleSymbols = staleResult.filter((s) => {\n if (s.consumers === 0 && filesWithFunctions.has(s.file)) return false;\n return true;\n });\n const unused = trueStaleSymbols.filter((s) => s.consumers === 0).length;\n const singleUse = trueStaleCount - unused;\n const parts: string[] = [];\n if (unused > 0) parts.push(`${unused} unused`);\n if (singleUse > 0) parts.push(`${singleUse} single-consumer (not in types file)`);\n actions.push({\n category: 'Stale abstractions',\n description: `${parts.join(', ')} — premature abstraction`,\n effort: 'low',\n impact: 'medium',\n count: trueStaleCount,\n locRecoverable: staleResult\n .filter((s) => s.consumers === 0 || !s.file.includes('types'))\n .reduce((sum, r) => sum + r.loc, 0),\n });\n }\n\n if (trueDriftCount > 0) {\n const parts: string[] = [];\n if (driftResult.unusedImports > 0) parts.push(`${driftResult.unusedImports} unused imports`);\n if (driftResult.layerViolations > 0) parts.push(`${driftResult.layerViolations} layer violations`);\n if (driftResult.patternDeviations > 0) parts.push(`${driftResult.patternDeviations} unique deps`);\n actions.push({\n category: 'Structural drift',\n description: parts.join(', '),\n effort: driftResult.layerViolations > 0 ? 'medium' : 'low',\n impact: driftResult.layerViolations > 0 ? 'medium' : 'low',\n count: trueDriftCount,\n locRecoverable: 0,\n });\n }\n\n // Sort: high impact + low effort first\n const impactWeight = { high: 3, medium: 2, low: 1 };\n const effortWeight = { low: 3, medium: 2, high: 1 };\n actions.sort((a, b) => {\n const scoreA = impactWeight[a.impact] * effortWeight[a.effort];\n const scoreB = impactWeight[b.impact] * effortWeight[b.effort];\n return scoreB - scoreA;\n });\n\n // ── Compute health score (0-100) ─────────────────────────\n //\n // Uses filtered counts (false positives removed).\n // Deductions scale with codebase size so a 10-file project\n // and a 1000-file project aren't penalized the same way.\n const fileCount = Math.max(s.documents, 1);\n const symbolCount = Math.max(s.symbols, 1);\n\n let score = 100;\n\n // Dead code: deduct based on % of symbols that are dead, not raw count\n const deadPercent = trueDeadCount / symbolCount;\n score -= Math.min(20, Math.round(deadPercent * 200));\n\n // Isolated: same percentage-based\n const isolatedPercent = trueIsolatedCount / symbolCount;\n score -= Math.min(10, Math.round(isolatedPercent * 200));\n\n // Cycles: these are always bad, flat penalty\n score -= Math.min(15, cycleResult.length * 5);\n\n // Similar pairs: only count true logic overlap, not boilerplate\n score -= Math.min(10, trueSimilarCount * 2);\n\n // Extract candidates: mild penalty\n score -= Math.min(5, extractResult.length * 2);\n\n // Wrappers: mild\n score -= Math.min(3, wrapperResult.length);\n\n // Passthroughs: mild\n score -= Math.min(3, passthroughResult.length);\n\n // Stale abstractions: percentage-based with filtered count\n const stalePercent = trueStaleCount / Math.max(symbolCount * 0.1, 1);\n score -= Math.min(8, Math.round(stalePercent * 10));\n\n // Drift: percentage of files that deviate\n const driftPercent = trueDriftCount / fileCount;\n score -= Math.min(5, Math.round(driftPercent * 50));\n\n // Complexity: only penalize extreme outliers\n const extremeComplexity = complexResult.filter((r) => r.score > 50).length;\n score -= Math.min(5, extremeComplexity * 2);\n\n // Test coverage: significant penalty for low coverage\n // 0% = -15, 25% = -11, 50% = -7, 75% = -4, 100% = 0\n const coverageDeduction = Math.round(15 * (1 - testResult.percent / 100));\n score -= coverageDeduction;\n\n score = Math.max(0, Math.min(100, score));\n\n return {\n score,\n overview: {\n documents: s.documents,\n symbols: s.symbols,\n indexSizeBytes: s.indexSizeBytes,\n },\n findings: {\n deadSymbols: trueDeadCount,\n deadLoc: trueDeadLoc,\n isolatedSymbols: trueIsolatedCount,\n isolatedLoc: isolatedResult\n .filter((s) => !isEntryPoint(s.relativePath))\n .reduce((sum, s) => sum + s.loc, 0),\n cycles: cycleResult.length,\n similarPairs: trueSimilarCount,\n extractionCandidates: extractResult.length,\n wrappers: wrapperResult.length,\n passthroughs: passthroughResult.length,\n staleTypes: trueStaleCount,\n driftedFiles: trueDriftCount,\n complexityHotspotCount: complexResult.length,\n testCoveragePercent: testResult.percent,\n },\n actions,\n topComplexity: complexResult.slice(0, 5).map((r) => ({\n symbol: r.shortName,\n score: r.score,\n })),\n };\n}\n"],"mappings":"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","names":["s"]}
@@ -0,0 +1,76 @@
1
+ import {
2
+ shortenSymbol
3
+ } from "./chunk-QOV2R2WT.js";
4
+
5
+ // src/queries/imports.ts
6
+ function imports(db, filePattern) {
7
+ const rows = db.all(
8
+ `SELECT DISTINCT gs.symbol, def_d.relative_path AS from_file
9
+ FROM mentions m
10
+ JOIN chunks c ON m.chunk_id = c.id
11
+ JOIN documents imp_d ON c.document_id = imp_d.id
12
+ JOIN global_symbols gs ON m.symbol_id = gs.id
13
+ LEFT JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
14
+ LEFT JOIN documents def_d ON der.document_id = def_d.id
15
+ WHERE imp_d.relative_path LIKE ?
16
+ AND m.role = 2
17
+ ORDER BY def_d.relative_path, gs.symbol`,
18
+ `%${filePattern}%`
19
+ );
20
+ return rows.map((r) => ({
21
+ symbol: r.symbol,
22
+ shortName: shortenSymbol(r.symbol),
23
+ fromFile: r.from_file ?? "(external)"
24
+ }));
25
+ }
26
+ function importedBy(db, symbolPattern) {
27
+ const rows = db.all(
28
+ `SELECT DISTINCT gs.symbol, d.relative_path AS importer
29
+ FROM mentions m
30
+ JOIN chunks c ON m.chunk_id = c.id
31
+ JOIN documents d ON c.document_id = d.id
32
+ JOIN global_symbols gs ON m.symbol_id = gs.id
33
+ WHERE gs.symbol LIKE ?
34
+ AND m.role = 2
35
+ ORDER BY d.relative_path`,
36
+ `%${symbolPattern}%`
37
+ );
38
+ return rows.filter((r) => !db.isIgnored(r.importer)).map((r) => ({
39
+ symbol: r.symbol,
40
+ shortName: shortenSymbol(r.symbol),
41
+ fromFile: r.importer
42
+ }));
43
+ }
44
+ function unusedImports(db, filePattern) {
45
+ const rows = db.all(
46
+ `SELECT gs.symbol, d.relative_path AS imported_in
47
+ FROM mentions m
48
+ JOIN chunks c ON m.chunk_id = c.id
49
+ JOIN documents d ON c.document_id = d.id
50
+ JOIN global_symbols gs ON m.symbol_id = gs.id
51
+ WHERE d.relative_path LIKE ?
52
+ AND m.role = 2
53
+ AND NOT EXISTS (
54
+ SELECT 1
55
+ FROM mentions ref_m
56
+ JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
57
+ WHERE ref_m.symbol_id = gs.id
58
+ AND ref_m.role = 0
59
+ AND ref_c.document_id = d.id
60
+ )
61
+ ORDER BY d.relative_path, gs.symbol`,
62
+ `%${filePattern}%`
63
+ );
64
+ return rows.map((r) => ({
65
+ symbol: r.symbol,
66
+ shortName: shortenSymbol(r.symbol),
67
+ importedIn: r.imported_in
68
+ }));
69
+ }
70
+
71
+ export {
72
+ imports,
73
+ importedBy,
74
+ unusedImports
75
+ };
76
+ //# sourceMappingURL=chunk-BEPIEVLR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/imports.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { ImportResult, UnusedImportResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * What symbols does this file import?\n * Uses role=2 (import) from the SCIP mentions table.\n */\nexport function imports(db: ScipDatabase, filePattern: string): ImportResult[] {\n const rows = db.all<{\n symbol: string;\n from_file: string;\n }>(\n `SELECT DISTINCT gs.symbol, def_d.relative_path AS from_file\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents imp_d ON c.document_id = imp_d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n LEFT JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n LEFT JOIN documents def_d ON der.document_id = def_d.id\n WHERE imp_d.relative_path LIKE ?\n AND m.role = 2\n ORDER BY def_d.relative_path, gs.symbol`,\n `%${filePattern}%`,\n );\n\n return rows.map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n fromFile: r.from_file ?? '(external)',\n }));\n}\n\n/**\n * Which files import this symbol?\n */\nexport function importedBy(db: ScipDatabase, symbolPattern: string): ImportResult[] {\n const rows = db.all<{\n symbol: string;\n importer: string;\n }>(\n `SELECT DISTINCT gs.symbol, d.relative_path AS importer\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n WHERE gs.symbol LIKE ?\n AND m.role = 2\n ORDER BY d.relative_path`,\n `%${symbolPattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.importer))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n fromFile: r.importer,\n }));\n}\n\n/**\n * Find imports in a file that are never referenced (role=0) in the same file.\n * These are likely unused imports.\n */\nexport function unusedImports(db: ScipDatabase, filePattern: string): UnusedImportResult[] {\n const rows = db.all<{\n symbol: string;\n imported_in: string;\n }>(\n `SELECT gs.symbol, d.relative_path AS imported_in\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n WHERE d.relative_path LIKE ?\n AND m.role = 2\n AND NOT EXISTS (\n SELECT 1\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n WHERE ref_m.symbol_id = gs.id\n AND ref_m.role = 0\n AND ref_c.document_id = d.id\n )\n ORDER BY d.relative_path, gs.symbol`,\n `%${filePattern}%`,\n );\n\n return rows.map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n importedIn: r.imported_in,\n }));\n}\n"],"mappings":";;;;;AAQO,SAAS,QAAQ,IAAkB,aAAqC;AAC7E,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUA,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,UAAU,EAAE,aAAa;AAAA,EAC3B,EAAE;AACJ;AAKO,SAAS,WAAW,IAAkB,eAAuC;AAClF,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,IAAI,aAAa;AAAA,EACnB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,QAAQ,CAAC,EACvC,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,UAAU,EAAE;AAAA,EACd,EAAE;AACN;AAMO,SAAS,cAAc,IAAkB,aAA2C;AACzF,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAgBA,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,YAAY,EAAE;AAAA,EAChB,EAAE;AACJ;","names":[]}
@@ -0,0 +1,42 @@
1
+ import {
2
+ shortenSymbol
3
+ } from "./chunk-QOV2R2WT.js";
4
+
5
+ // src/queries/hotspots.ts
6
+ function hotspots(db, opts = {}) {
7
+ const { limit = 30, scope } = opts;
8
+ const scopeFilter = scope ? `AND def_d.relative_path LIKE '%${scope}%'` : "";
9
+ const rows = db.all(
10
+ `SELECT
11
+ gs.symbol,
12
+ COUNT(*) AS ref_count,
13
+ COUNT(DISTINCT ref_d.id) AS file_count,
14
+ def_d.relative_path AS defined_in
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
+ JOIN global_symbols gs ON m.symbol_id = gs.id
19
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
20
+ JOIN documents def_d ON der.document_id = def_d.id
21
+ WHERE m.role = 0
22
+ ${db.pathExclusionsFor("def_d")}
23
+ ${db.symbolNoiseFor("gs")}
24
+ ${scopeFilter}
25
+ GROUP BY gs.id
26
+ ORDER BY ref_count DESC
27
+ LIMIT ?`,
28
+ limit
29
+ );
30
+ return rows.filter((r) => !db.isIgnored(r.defined_in)).map((r) => ({
31
+ symbol: r.symbol,
32
+ shortName: shortenSymbol(r.symbol),
33
+ refCount: r.ref_count,
34
+ fileCount: r.file_count,
35
+ definedIn: r.defined_in
36
+ }));
37
+ }
38
+
39
+ export {
40
+ hotspots
41
+ };
42
+ //# sourceMappingURL=chunk-BFSCMC22.js.map