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,49 @@
1
+ import type { ScipDatabase } from '../db.js';
2
+ import type { HierarchyNode } from '../types.js';
3
+ import { shortenSymbol } from '../symbol-parser.js';
4
+
5
+ /**
6
+ * Walk the enclosing_symbol chain upward to show a symbol's ancestry.
7
+ * e.g., method → class → module → file
8
+ *
9
+ * Falls back to parsing the SCIP symbol descriptor chain when
10
+ * enclosing_symbol is not populated by the indexer.
11
+ */
12
+ export function hierarchy(db: ScipDatabase, symbolPattern: string): HierarchyNode[] {
13
+ // Find the symbol
14
+ const sym = db.get<{ symbol: string; enclosing_symbol: string | null }>(
15
+ `SELECT symbol, enclosing_symbol FROM global_symbols
16
+ WHERE symbol LIKE ? LIMIT 1`,
17
+ `%${symbolPattern}%`,
18
+ );
19
+
20
+ if (!sym) return [];
21
+
22
+ const chain: HierarchyNode[] = [
23
+ { symbol: sym.symbol, shortName: shortenSymbol(sym.symbol), depth: 0 },
24
+ ];
25
+
26
+ // Walk enclosing_symbol chain if available
27
+ let current = sym.enclosing_symbol;
28
+ let depth = 1;
29
+ const seen = new Set<string>([sym.symbol]);
30
+
31
+ while (current && !seen.has(current) && depth < 20) {
32
+ seen.add(current);
33
+ const parent = db.get<{ symbol: string; enclosing_symbol: string | null }>(
34
+ `SELECT symbol, enclosing_symbol FROM global_symbols WHERE symbol = ?`,
35
+ current,
36
+ );
37
+ if (!parent) break;
38
+
39
+ chain.push({
40
+ symbol: parent.symbol,
41
+ shortName: shortenSymbol(parent.symbol),
42
+ depth,
43
+ });
44
+ current = parent.enclosing_symbol;
45
+ depth++;
46
+ }
47
+
48
+ return chain;
49
+ }
@@ -0,0 +1,53 @@
1
+ import type { ScipDatabase } from '../db.js';
2
+ import type { HotspotResult } from '../types.js';
3
+ import { shortenSymbol } from '../symbol-parser.js';
4
+
5
+ /**
6
+ * Find the most-referenced symbols in the codebase — the choke points
7
+ * where changes have the widest blast radius.
8
+ */
9
+ export function hotspots(
10
+ db: ScipDatabase,
11
+ opts: { limit?: number; scope?: string } = {},
12
+ ): HotspotResult[] {
13
+ const { limit = 30, scope } = opts;
14
+
15
+ const scopeFilter = scope ? `AND def_d.relative_path LIKE '%${scope}%'` : '';
16
+
17
+ const rows = db.all<{
18
+ symbol: string;
19
+ ref_count: number;
20
+ file_count: number;
21
+ defined_in: string;
22
+ }>(
23
+ `SELECT
24
+ gs.symbol,
25
+ COUNT(*) AS ref_count,
26
+ COUNT(DISTINCT ref_d.id) AS file_count,
27
+ def_d.relative_path AS defined_in
28
+ FROM mentions m
29
+ JOIN chunks c ON m.chunk_id = c.id
30
+ JOIN documents ref_d ON c.document_id = ref_d.id
31
+ JOIN global_symbols gs ON m.symbol_id = gs.id
32
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
33
+ JOIN documents def_d ON der.document_id = def_d.id
34
+ WHERE m.role = 0
35
+ ${db.pathExclusionsFor('def_d')}
36
+ ${db.symbolNoiseFor('gs')}
37
+ ${scopeFilter}
38
+ GROUP BY gs.id
39
+ ORDER BY ref_count DESC
40
+ LIMIT ?`,
41
+ limit,
42
+ );
43
+
44
+ return rows
45
+ .filter((r) => !db.isIgnored(r.defined_in))
46
+ .map((r) => ({
47
+ symbol: r.symbol,
48
+ shortName: shortenSymbol(r.symbol),
49
+ refCount: r.ref_count,
50
+ fileCount: r.file_count,
51
+ definedIn: r.defined_in,
52
+ }));
53
+ }
@@ -0,0 +1,95 @@
1
+ import type { ScipDatabase } from '../db.js';
2
+ import type { ImportResult, UnusedImportResult } from '../types.js';
3
+ import { shortenSymbol } from '../symbol-parser.js';
4
+
5
+ /**
6
+ * What symbols does this file import?
7
+ * Uses role=2 (import) from the SCIP mentions table.
8
+ */
9
+ export function imports(db: ScipDatabase, filePattern: string): ImportResult[] {
10
+ const rows = db.all<{
11
+ symbol: string;
12
+ from_file: string;
13
+ }>(
14
+ `SELECT DISTINCT gs.symbol, def_d.relative_path AS from_file
15
+ FROM mentions m
16
+ JOIN chunks c ON m.chunk_id = c.id
17
+ JOIN documents imp_d ON c.document_id = imp_d.id
18
+ JOIN global_symbols gs ON m.symbol_id = gs.id
19
+ LEFT JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
20
+ LEFT JOIN documents def_d ON der.document_id = def_d.id
21
+ WHERE imp_d.relative_path LIKE ?
22
+ AND m.role = 2
23
+ ORDER BY def_d.relative_path, gs.symbol`,
24
+ `%${filePattern}%`,
25
+ );
26
+
27
+ return rows.map((r) => ({
28
+ symbol: r.symbol,
29
+ shortName: shortenSymbol(r.symbol),
30
+ fromFile: r.from_file ?? '(external)',
31
+ }));
32
+ }
33
+
34
+ /**
35
+ * Which files import this symbol?
36
+ */
37
+ export function importedBy(db: ScipDatabase, symbolPattern: string): ImportResult[] {
38
+ const rows = db.all<{
39
+ symbol: string;
40
+ importer: string;
41
+ }>(
42
+ `SELECT DISTINCT gs.symbol, d.relative_path AS importer
43
+ FROM mentions m
44
+ JOIN chunks c ON m.chunk_id = c.id
45
+ JOIN documents d ON c.document_id = d.id
46
+ JOIN global_symbols gs ON m.symbol_id = gs.id
47
+ WHERE gs.symbol LIKE ?
48
+ AND m.role = 2
49
+ ORDER BY d.relative_path`,
50
+ `%${symbolPattern}%`,
51
+ );
52
+
53
+ return rows
54
+ .filter((r) => !db.isIgnored(r.importer))
55
+ .map((r) => ({
56
+ symbol: r.symbol,
57
+ shortName: shortenSymbol(r.symbol),
58
+ fromFile: r.importer,
59
+ }));
60
+ }
61
+
62
+ /**
63
+ * Find imports in a file that are never referenced (role=0) in the same file.
64
+ * These are likely unused imports.
65
+ */
66
+ export function unusedImports(db: ScipDatabase, filePattern: string): UnusedImportResult[] {
67
+ const rows = db.all<{
68
+ symbol: string;
69
+ imported_in: string;
70
+ }>(
71
+ `SELECT gs.symbol, d.relative_path AS imported_in
72
+ FROM mentions m
73
+ JOIN chunks c ON m.chunk_id = c.id
74
+ JOIN documents d ON c.document_id = d.id
75
+ JOIN global_symbols gs ON m.symbol_id = gs.id
76
+ WHERE d.relative_path LIKE ?
77
+ AND m.role = 2
78
+ AND NOT EXISTS (
79
+ SELECT 1
80
+ FROM mentions ref_m
81
+ JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
82
+ WHERE ref_m.symbol_id = gs.id
83
+ AND ref_m.role = 0
84
+ AND ref_c.document_id = d.id
85
+ )
86
+ ORDER BY d.relative_path, gs.symbol`,
87
+ `%${filePattern}%`,
88
+ );
89
+
90
+ return rows.map((r) => ({
91
+ symbol: r.symbol,
92
+ shortName: shortenSymbol(r.symbol),
93
+ importedIn: r.imported_in,
94
+ }));
95
+ }
@@ -0,0 +1,45 @@
1
+ export { stats } from './stats.js';
2
+ export { files } from './files.js';
3
+ export { symbols } from './symbols.js';
4
+ export { methods } from './methods.js';
5
+ export { refs } from './refs.js';
6
+ export { trace } from './trace.js';
7
+ export { deps, rdeps } from './deps.js';
8
+ export { system } from './system.js';
9
+ export { surface } from './surface.js';
10
+ export { dead } from './dead.js';
11
+ export { hotspots } from './hotspots.js';
12
+ export { imports, importedBy, unusedImports } from './imports.js';
13
+ export { outline } from './outline.js';
14
+ export { members } from './members.js';
15
+ export { fanIn, fanOut, topFanIn, topFanOut } from './fan.js';
16
+ export { coupling, topCoupling } from './coupling.js';
17
+ export { cycles } from './cycles.js';
18
+ export { bottlenecks } from './bottlenecks.js';
19
+ export { isolated } from './isolated.js';
20
+ export { byKind, kindCounts } from './by-kind.js';
21
+ export { testCoverage, testCoverageSummary } from './test-coverage.js';
22
+ export { docCoverage } from './doc-coverage.js';
23
+ export { deepChains } from './deep-chains.js';
24
+ export { hierarchy } from './hierarchy.js';
25
+ export { callGraph } from './call-graph.js';
26
+ export { similar, similarAll } from './similar.js';
27
+ export { similarFiles } from './similar-files.js';
28
+ export { similarChains } from './similar-chains.js';
29
+ export { extractCandidates } from './extract-candidates.js';
30
+ export { affected } from './affected.js';
31
+ export { changeSurface } from './change-surface.js';
32
+ export { diffImpact } from './diff-impact.js';
33
+ export { drift } from './drift.js';
34
+ export { wrapperCandidates } from './wrapper-candidates.js';
35
+ export { passthroughCandidates } from './passthrough-candidates.js';
36
+ export { staleAbstractions } from './stale-abstractions.js';
37
+ export { complexityHotspots } from './complexity-hotspots.js';
38
+ export { health } from './health.js';
39
+ export { convergence } from './convergence.js';
40
+ export { code } from './code.js';
41
+ export { complexity } from './complexity.js';
42
+ export { dataflow } from './dataflow.js';
43
+ export { slice } from './slice.js';
44
+ export { redundantReexports } from './redundant-reexports.js';
45
+ export { similarSignatures } from './similar-signatures.js';
@@ -0,0 +1,67 @@
1
+ import type { ScipDatabase } from '../db.js';
2
+ import { testFileExclusionSql } from '../query-support.js';
3
+ import type { IsolatedResult } from '../types.js';
4
+ import { shortenSymbol } from '../symbol-parser.js';
5
+
6
+ /**
7
+ * Find isolated symbols: defined locally, referenced by nothing,
8
+ * and referencing nothing external. These are truly orphaned code —
9
+ * not just unused exports, but completely disconnected from the graph.
10
+ */
11
+ export function isolated(
12
+ db: ScipDatabase,
13
+ opts: { scope?: string; minLoc?: number } = {},
14
+ ): IsolatedResult[] {
15
+ const { scope, minLoc = 3 } = opts;
16
+ const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';
17
+
18
+ const rows = db.all<{
19
+ symbol: string;
20
+ relative_path: string;
21
+ start_line: number;
22
+ end_line: number;
23
+ loc: number;
24
+ }>(
25
+ `SELECT
26
+ gs.symbol,
27
+ d.relative_path,
28
+ der.start_line,
29
+ der.end_line,
30
+ (der.end_line - der.start_line + 1) AS loc
31
+ FROM global_symbols gs
32
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
33
+ JOIN documents d ON der.document_id = d.id
34
+ WHERE 1 = 1
35
+ ${db.pathExclusionsFor('d')}
36
+ AND ${testFileExclusionSql('d')}
37
+ ${db.symbolNoiseFor('gs')}
38
+ AND gs.symbol NOT LIKE '%#%'
39
+ AND (der.end_line - der.start_line + 1) >= ?
40
+ ${scopeFilter}
41
+ -- No cross-file references TO this symbol
42
+ AND NOT EXISTS (
43
+ SELECT 1 FROM mentions m
44
+ JOIN chunks c ON m.chunk_id = c.id
45
+ WHERE m.symbol_id = gs.id AND m.role = 0 AND c.document_id != d.id
46
+ )
47
+ -- No same-file references either
48
+ AND NOT EXISTS (
49
+ SELECT 1 FROM mentions m
50
+ JOIN chunks c ON m.chunk_id = c.id
51
+ WHERE m.symbol_id = gs.id AND m.role = 0 AND c.document_id = d.id
52
+ )
53
+ ORDER BY loc DESC, d.relative_path`,
54
+ minLoc,
55
+ );
56
+
57
+ return rows
58
+ .filter((r) => !db.isIgnored(r.relative_path))
59
+ .map((r) => ({
60
+ symbol: r.symbol,
61
+ shortName: shortenSymbol(r.symbol),
62
+ relativePath: r.relative_path,
63
+ startLine: r.start_line,
64
+ endLine: r.end_line,
65
+ loc: r.loc,
66
+ }));
67
+ }
@@ -0,0 +1,54 @@
1
+ import type { ScipDatabase } from '../db.js';
2
+ import type { MemberResult } from '../types.js';
3
+ import { shortenSymbol, parseSymbol } from '../symbol-parser.js';
4
+ import type { ScipSymbol } from '../types.js';
5
+
6
+ /**
7
+ * Find all direct children of a symbol (methods, fields, nested types).
8
+ * Uses the enclosing_symbol relationship in global_symbols.
9
+ */
10
+ export function members(db: ScipDatabase, symbolPattern: string): MemberResult[] {
11
+ // First find the parent symbol(s)
12
+ const parents = db.all<{ symbol: string }>(
13
+ `SELECT symbol FROM global_symbols WHERE symbol LIKE ?`,
14
+ `%${symbolPattern}%`,
15
+ );
16
+
17
+ if (parents.length === 0) return [];
18
+
19
+ // Find children whose enclosing_symbol matches any parent
20
+ const placeholders = parents.map(() => '?').join(',');
21
+ const parentSymbols = parents.map((p) => p.symbol);
22
+
23
+ const rows = db.all<{
24
+ symbol: string;
25
+ start_line: number;
26
+ end_line: number;
27
+ }>(
28
+ `SELECT gs.symbol, der.start_line, der.end_line
29
+ FROM global_symbols gs
30
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
31
+ WHERE gs.enclosing_symbol IN (${placeholders})
32
+ ${db.symbolNoise}
33
+ ORDER BY der.start_line`,
34
+ ...parentSymbols,
35
+ );
36
+
37
+ return rows.map((r) => {
38
+ const parsed = parseSymbol(r.symbol);
39
+ let kind = 'unknown';
40
+ if (!('kind' in parsed)) {
41
+ const sym = parsed as ScipSymbol;
42
+ const last = sym.descriptors[sym.descriptors.length - 1];
43
+ if (last) kind = last.suffix;
44
+ }
45
+
46
+ return {
47
+ symbol: r.symbol,
48
+ shortName: shortenSymbol(r.symbol),
49
+ startLine: r.start_line,
50
+ endLine: r.end_line,
51
+ kind,
52
+ };
53
+ });
54
+ }
@@ -0,0 +1,27 @@
1
+ import type { ScipDatabase } from '../db.js';
2
+ import type { MethodResult } from '../types.js';
3
+ import { leafName } from '../symbol-parser.js';
4
+
5
+ export function methods(db: ScipDatabase, className: string): MethodResult[] {
6
+ const rows = db.all<{
7
+ start_line: number;
8
+ end_line: number;
9
+ symbol: string;
10
+ }>(
11
+ `SELECT der.start_line, der.end_line, gs.symbol
12
+ FROM global_symbols gs
13
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
14
+ WHERE gs.symbol LIKE ?
15
+ AND ${db.localSymbolPredicate}
16
+ AND gs.symbol LIKE '%().%'
17
+ ${db.symbolNoise}
18
+ ORDER BY der.start_line`,
19
+ `%${className}#%`,
20
+ );
21
+
22
+ return rows.map((r) => ({
23
+ startLine: r.start_line,
24
+ endLine: r.end_line,
25
+ name: leafName(r.symbol),
26
+ }));
27
+ }
@@ -0,0 +1,52 @@
1
+ import type { ScipDatabase } from '../db.js';
2
+ import type { OutlineNode } from '../types.js';
3
+ import { shortenSymbol } from '../symbol-parser.js';
4
+
5
+ /**
6
+ * Build a tree-structured outline of symbols in a file,
7
+ * using the enclosing_symbol field to establish parent-child relationships.
8
+ */
9
+ export function outline(db: ScipDatabase, filePattern: string): OutlineNode[] {
10
+ const rows = db.all<{
11
+ symbol: string;
12
+ enclosing_symbol: string | null;
13
+ start_line: number;
14
+ end_line: number;
15
+ }>(
16
+ `SELECT gs.symbol, gs.enclosing_symbol, der.start_line, der.end_line
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
+ ${db.symbolNoise}
22
+ ORDER BY der.start_line`,
23
+ `%${filePattern}%`,
24
+ );
25
+
26
+ // Build a map of symbol -> node
27
+ const nodeMap = new Map<string, OutlineNode>();
28
+ const roots: OutlineNode[] = [];
29
+
30
+ for (const r of rows) {
31
+ const node: OutlineNode = {
32
+ symbol: r.symbol,
33
+ shortName: shortenSymbol(r.symbol),
34
+ startLine: r.start_line,
35
+ endLine: r.end_line,
36
+ children: [],
37
+ };
38
+ nodeMap.set(r.symbol, node);
39
+ }
40
+
41
+ // Wire up parent-child via enclosing_symbol
42
+ for (const r of rows) {
43
+ const node = nodeMap.get(r.symbol)!;
44
+ if (r.enclosing_symbol && nodeMap.has(r.enclosing_symbol)) {
45
+ nodeMap.get(r.enclosing_symbol)!.children.push(node);
46
+ } else {
47
+ roots.push(node);
48
+ }
49
+ }
50
+
51
+ return roots;
52
+ }
@@ -0,0 +1,94 @@
1
+ import type { ScipDatabase } from '../db.js';
2
+ import { getCalleeRowsForSymbol, testFileExclusionSql } from '../query-support.js';
3
+ import type { PassthroughCandidate } from '../types.js';
4
+ import { shortenSymbol } from '../symbol-parser.js';
5
+
6
+ /**
7
+ * Find passthrough candidates: functions that just forward to one
8
+ * other function.
9
+ *
10
+ * A function with exactly 1 callee and small LOC is likely a thin
11
+ * wrapper that adds no value — it just passes arguments through to
12
+ * the real implementation.
13
+ */
14
+ export function passthroughCandidates(
15
+ db: ScipDatabase,
16
+ opts?: { scope?: string; maxLoc?: number; limit?: number },
17
+ ): PassthroughCandidate[] {
18
+ const { scope, maxLoc = 15, limit = 30 } = opts ?? {};
19
+ const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';
20
+
21
+ // 1. Find all non-trivial symbols with definition ranges (>= 3 LOC)
22
+ const symbols = db.all<{
23
+ id: number;
24
+ symbol: string;
25
+ document_id: number;
26
+ start_line: number;
27
+ end_line: number;
28
+ loc: number;
29
+ relative_path: string;
30
+ }>(
31
+ `SELECT
32
+ gs.id,
33
+ gs.symbol,
34
+ der.document_id,
35
+ der.start_line,
36
+ der.end_line,
37
+ (der.end_line - der.start_line + 1) AS loc,
38
+ d.relative_path
39
+ FROM global_symbols gs
40
+ JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
41
+ JOIN documents d ON der.document_id = d.id
42
+ WHERE 1 = 1
43
+ ${db.pathExclusionsFor('d')}
44
+ AND ${testFileExclusionSql('d')}
45
+ ${db.symbolNoiseFor('gs')}
46
+ AND (der.end_line - der.start_line + 1) >= 3
47
+ AND (der.end_line - der.start_line + 1) <= ?
48
+ ${scopeFilter}
49
+ ORDER BY d.relative_path`,
50
+ maxLoc,
51
+ );
52
+
53
+ const results: PassthroughCandidate[] = [];
54
+
55
+ for (const sym of symbols) {
56
+ if (db.isIgnored(sym.relative_path)) continue;
57
+
58
+ // 2. Count callees for this symbol
59
+ const callees = getCalleeRowsForSymbol(db, {
60
+ documentId: sym.document_id,
61
+ startLine: sym.start_line,
62
+ endLine: sym.end_line,
63
+ symbolId: sym.id,
64
+ });
65
+
66
+ // Deduplicate by symbol (same callee may appear in multiple chunks)
67
+ const uniqueCallees = new Map<string, { symbol: string; file: string }>();
68
+ for (const c of callees) {
69
+ if (!uniqueCallees.has(c.symbol)) {
70
+ uniqueCallees.set(c.symbol, { symbol: c.symbol, file: c.file });
71
+ }
72
+ }
73
+
74
+ // 3. Passthrough = exactly 1 unique callee
75
+ if (uniqueCallees.size !== 1) continue;
76
+
77
+ const [, callee] = [...uniqueCallees.entries()][0]!;
78
+
79
+ results.push({
80
+ symbol: sym.symbol,
81
+ shortName: shortenSymbol(sym.symbol),
82
+ file: sym.relative_path,
83
+ startLine: sym.start_line,
84
+ endLine: sym.end_line,
85
+ loc: sym.loc,
86
+ forwardsTo: callee.symbol,
87
+ forwardsToShort: shortenSymbol(callee.symbol),
88
+ forwardsToFile: callee.file,
89
+ });
90
+ }
91
+
92
+ results.sort((a, b) => a.loc - b.loc || a.file.localeCompare(b.file));
93
+ return results.slice(0, limit);
94
+ }