scip-query 0.1.0 → 0.2.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 (198) hide show
  1. package/dist/{chunk-CM454WL3.js → chunk-2CKGIR6G.js} +2 -2
  2. package/dist/{chunk-5FGUEU7N.js → chunk-4EXL2CUA.js} +4 -4
  3. package/dist/chunk-4EXL2CUA.js.map +1 -0
  4. package/dist/{chunk-TDNNOR6D.js → chunk-4PDAL6IL.js} +6 -6
  5. package/dist/chunk-4PDAL6IL.js.map +1 -0
  6. package/dist/{chunk-BP2ATLK2.js → chunk-4XHWPRAX.js} +3 -3
  7. package/dist/chunk-4XHWPRAX.js.map +1 -0
  8. package/dist/{chunk-2QZ23IBN.js → chunk-5RMYT5WH.js} +3 -3
  9. package/dist/chunk-5RMYT5WH.js.map +1 -0
  10. package/dist/{chunk-7OZPA5OO.js → chunk-63G7IQTD.js} +13 -13
  11. package/dist/{chunk-XFXDXEUN.js → chunk-74RFWB5T.js} +2 -2
  12. package/dist/{chunk-XFXDXEUN.js.map → chunk-74RFWB5T.js.map} +1 -1
  13. package/dist/{chunk-JKP5GH6T.js → chunk-7LLPRPR5.js} +2 -2
  14. package/dist/{chunk-BFSCMC22.js → chunk-7PBOG4YE.js} +2 -2
  15. package/dist/{chunk-BFSCMC22.js.map → chunk-7PBOG4YE.js.map} +1 -1
  16. package/dist/{chunk-36OMT7ZJ.js → chunk-7RLE5EWE.js} +3 -3
  17. package/dist/{chunk-36OMT7ZJ.js.map → chunk-7RLE5EWE.js.map} +1 -1
  18. package/dist/{chunk-UNTPVD36.js → chunk-7UCKSQRS.js} +3 -3
  19. package/dist/{chunk-UNTPVD36.js.map → chunk-7UCKSQRS.js.map} +1 -1
  20. package/dist/{chunk-GJFURBEW.js → chunk-BNN2RKD2.js} +3 -3
  21. package/dist/chunk-BNN2RKD2.js.map +1 -0
  22. package/dist/{chunk-FFSWWE5O.js → chunk-BOVXCR46.js} +2 -2
  23. package/dist/{chunk-FFSWWE5O.js.map → chunk-BOVXCR46.js.map} +1 -1
  24. package/dist/{chunk-6VJ6Q7IE.js → chunk-D567NFIF.js} +3 -3
  25. package/dist/{chunk-6VJ6Q7IE.js.map → chunk-D567NFIF.js.map} +1 -1
  26. package/dist/{chunk-GTILYBH6.js → chunk-DGUPQSOR.js} +5 -5
  27. package/dist/chunk-DGUPQSOR.js.map +1 -0
  28. package/dist/{chunk-TBP6BICL.js → chunk-EQYLEQCW.js} +2 -2
  29. package/dist/{chunk-TBP6BICL.js.map → chunk-EQYLEQCW.js.map} +1 -1
  30. package/dist/{chunk-VZ7AMAFL.js → chunk-H2MDONBU.js} +2 -2
  31. package/dist/{chunk-BEPIEVLR.js → chunk-HB7MRLLL.js} +2 -2
  32. package/dist/{chunk-BEPIEVLR.js.map → chunk-HB7MRLLL.js.map} +1 -1
  33. package/dist/{chunk-KVSW5KYP.js → chunk-HDSRORNV.js} +4 -4
  34. package/dist/chunk-HDSRORNV.js.map +1 -0
  35. package/dist/{chunk-5WTJAXY2.js → chunk-HMLMH7VZ.js} +2 -2
  36. package/dist/{chunk-Z73NYSBZ.js → chunk-HPFZLISB.js} +2 -2
  37. package/dist/{chunk-3ZZJVBIO.js → chunk-HZBC7PPD.js} +4 -4
  38. package/dist/chunk-HZBC7PPD.js.map +1 -0
  39. package/dist/{chunk-6NBLIDF4.js → chunk-ITZ3DDOG.js} +2 -2
  40. package/dist/chunk-ITZ3DDOG.js.map +1 -0
  41. package/dist/{chunk-YZAA4LYG.js → chunk-KPPHZCZJ.js} +5 -5
  42. package/dist/chunk-KPPHZCZJ.js.map +1 -0
  43. package/dist/{chunk-T6ARFSBZ.js → chunk-MCUX5LA7.js} +6 -6
  44. package/dist/chunk-MCUX5LA7.js.map +1 -0
  45. package/dist/{chunk-ZJRYBOEE.js → chunk-NHBZIL2J.js} +4 -4
  46. package/dist/{chunk-ZJRYBOEE.js.map → chunk-NHBZIL2J.js.map} +1 -1
  47. package/dist/{chunk-VRUJH4BO.js → chunk-NWCE4CIC.js} +4 -4
  48. package/dist/chunk-NWCE4CIC.js.map +1 -0
  49. package/dist/{chunk-LUSIFBXO.js → chunk-OVPLOMPY.js} +4 -4
  50. package/dist/{chunk-LUSIFBXO.js.map → chunk-OVPLOMPY.js.map} +1 -1
  51. package/dist/{chunk-TSPZOMHC.js → chunk-UJQN5N3I.js} +2 -2
  52. package/dist/{chunk-NDSQYIWT.js → chunk-W4ALF422.js} +2 -2
  53. package/dist/{chunk-3E2X7RIE.js → chunk-Z4GHE2HD.js} +5 -5
  54. package/dist/chunk-Z4GHE2HD.js.map +1 -0
  55. package/dist/{chunk-LB7OS35Q.js → chunk-Z6YZJ36C.js} +2 -2
  56. package/dist/{chunk-EMDQWNYR.js → chunk-ZK6GXM3J.js} +7 -7
  57. package/dist/chunk-ZK6GXM3J.js.map +1 -0
  58. package/dist/{chunk-FUHJCHS4.js → chunk-ZOGY2V3N.js} +3 -3
  59. package/dist/chunk-ZOGY2V3N.js.map +1 -0
  60. package/dist/{chunk-MBVNHJVN.js → chunk-ZQIIPFD7.js} +2 -2
  61. package/dist/cli.js +52 -52
  62. package/dist/cli.js.map +1 -1
  63. package/dist/{db-BxaevAyc.d.ts → db-BNVVZSfP.d.ts} +1 -1
  64. package/dist/index.d.ts +2 -2
  65. package/dist/index.js +35 -35
  66. package/dist/queries/affected.d.ts +1 -1
  67. package/dist/queries/affected.js +2 -2
  68. package/dist/queries/bottlenecks.d.ts +1 -1
  69. package/dist/queries/bottlenecks.js +1 -1
  70. package/dist/queries/by-kind.d.ts +1 -1
  71. package/dist/queries/call-graph.d.ts +1 -1
  72. package/dist/queries/call-graph.js +2 -2
  73. package/dist/queries/change-surface.d.ts +1 -1
  74. package/dist/queries/change-surface.js +2 -2
  75. package/dist/queries/code.d.ts +1 -1
  76. package/dist/queries/code.js +2 -2
  77. package/dist/queries/complexity-hotspots.d.ts +1 -1
  78. package/dist/queries/complexity-hotspots.js +2 -2
  79. package/dist/queries/complexity.d.ts +1 -1
  80. package/dist/queries/complexity.js +2 -2
  81. package/dist/queries/convergence.d.ts +1 -1
  82. package/dist/queries/convergence.js +2 -2
  83. package/dist/queries/coupling.d.ts +1 -1
  84. package/dist/queries/coupling.js +1 -1
  85. package/dist/queries/cycles.d.ts +1 -1
  86. package/dist/queries/cycles.js +2 -2
  87. package/dist/queries/dataflow.d.ts +1 -1
  88. package/dist/queries/dataflow.js +2 -2
  89. package/dist/queries/dead.d.ts +1 -1
  90. package/dist/queries/dead.js +2 -2
  91. package/dist/queries/deep-chains.d.ts +1 -1
  92. package/dist/queries/deep-chains.js +2 -2
  93. package/dist/queries/deps.d.ts +1 -1
  94. package/dist/queries/diff-impact.d.ts +1 -1
  95. package/dist/queries/diff-impact.js +2 -2
  96. package/dist/queries/doc-coverage.d.ts +1 -1
  97. package/dist/queries/drift.d.ts +1 -1
  98. package/dist/queries/drift.js +2 -2
  99. package/dist/queries/extract-candidates.d.ts +1 -1
  100. package/dist/queries/extract-candidates.js +2 -2
  101. package/dist/queries/fan.d.ts +1 -1
  102. package/dist/queries/fan.js +1 -1
  103. package/dist/queries/files.d.ts +1 -1
  104. package/dist/queries/health.d.ts +1 -1
  105. package/dist/queries/health.js +14 -14
  106. package/dist/queries/hierarchy.d.ts +1 -1
  107. package/dist/queries/hotspots.d.ts +1 -1
  108. package/dist/queries/hotspots.js +1 -1
  109. package/dist/queries/imports.d.ts +1 -1
  110. package/dist/queries/imports.js +1 -1
  111. package/dist/queries/index.d.ts +1 -1
  112. package/dist/queries/index.js +35 -35
  113. package/dist/queries/isolated.d.ts +1 -1
  114. package/dist/queries/isolated.js +2 -2
  115. package/dist/queries/members.d.ts +1 -1
  116. package/dist/queries/methods.d.ts +1 -1
  117. package/dist/queries/outline.d.ts +1 -1
  118. package/dist/queries/passthrough-candidates.d.ts +1 -1
  119. package/dist/queries/passthrough-candidates.js +2 -2
  120. package/dist/queries/redundant-reexports.d.ts +1 -1
  121. package/dist/queries/redundant-reexports.js +1 -1
  122. package/dist/queries/refs.d.ts +1 -1
  123. package/dist/queries/refs.js +1 -1
  124. package/dist/queries/similar-chains.d.ts +1 -1
  125. package/dist/queries/similar-chains.js +2 -2
  126. package/dist/queries/similar-files.d.ts +1 -1
  127. package/dist/queries/similar-files.js +2 -2
  128. package/dist/queries/similar-signatures.d.ts +1 -1
  129. package/dist/queries/similar.d.ts +1 -1
  130. package/dist/queries/similar.js +2 -2
  131. package/dist/queries/slice.d.ts +1 -1
  132. package/dist/queries/slice.js +2 -2
  133. package/dist/queries/stale-abstractions.d.ts +1 -1
  134. package/dist/queries/stale-abstractions.js +2 -2
  135. package/dist/queries/stats.d.ts +1 -1
  136. package/dist/queries/stats.js +1 -1
  137. package/dist/queries/surface.d.ts +1 -1
  138. package/dist/queries/surface.js +1 -1
  139. package/dist/queries/symbols.d.ts +1 -1
  140. package/dist/queries/system.d.ts +1 -1
  141. package/dist/queries/test-coverage.d.ts +1 -1
  142. package/dist/queries/test-coverage.js +2 -2
  143. package/dist/queries/trace.d.ts +1 -1
  144. package/dist/queries/trace.js +1 -1
  145. package/dist/queries/wrapper-candidates.d.ts +1 -1
  146. package/dist/queries/wrapper-candidates.js +2 -2
  147. package/package.json +1 -1
  148. package/src/queries/affected.ts +1 -1
  149. package/src/queries/bottlenecks.ts +2 -2
  150. package/src/queries/call-graph.ts +1 -1
  151. package/src/queries/change-surface.ts +2 -2
  152. package/src/queries/complexity-hotspots.ts +5 -5
  153. package/src/queries/complexity.ts +1 -1
  154. package/src/queries/coupling.ts +3 -3
  155. package/src/queries/dataflow.ts +4 -4
  156. package/src/queries/dead.ts +2 -2
  157. package/src/queries/diff-impact.ts +3 -3
  158. package/src/queries/drift.ts +1 -1
  159. package/src/queries/fan.ts +4 -4
  160. package/src/queries/hotspots.ts +1 -1
  161. package/src/queries/imports.ts +1 -1
  162. package/src/queries/isolated.ts +2 -2
  163. package/src/queries/redundant-reexports.ts +4 -4
  164. package/src/queries/refs.ts +1 -1
  165. package/src/queries/slice.ts +2 -2
  166. package/src/queries/stale-abstractions.ts +1 -1
  167. package/src/queries/stats.ts +1 -1
  168. package/src/queries/surface.ts +1 -1
  169. package/src/queries/test-coverage.ts +2 -2
  170. package/src/queries/trace.ts +1 -1
  171. package/src/queries/wrapper-candidates.ts +4 -4
  172. package/src/query-support.ts +2 -2
  173. package/src/types.ts +1 -1
  174. package/dist/chunk-2QZ23IBN.js.map +0 -1
  175. package/dist/chunk-3E2X7RIE.js.map +0 -1
  176. package/dist/chunk-3ZZJVBIO.js.map +0 -1
  177. package/dist/chunk-5FGUEU7N.js.map +0 -1
  178. package/dist/chunk-6NBLIDF4.js.map +0 -1
  179. package/dist/chunk-BP2ATLK2.js.map +0 -1
  180. package/dist/chunk-EMDQWNYR.js.map +0 -1
  181. package/dist/chunk-FUHJCHS4.js.map +0 -1
  182. package/dist/chunk-GJFURBEW.js.map +0 -1
  183. package/dist/chunk-GTILYBH6.js.map +0 -1
  184. package/dist/chunk-KVSW5KYP.js.map +0 -1
  185. package/dist/chunk-T6ARFSBZ.js.map +0 -1
  186. package/dist/chunk-TDNNOR6D.js.map +0 -1
  187. package/dist/chunk-VRUJH4BO.js.map +0 -1
  188. package/dist/chunk-YZAA4LYG.js.map +0 -1
  189. /package/dist/{chunk-CM454WL3.js.map → chunk-2CKGIR6G.js.map} +0 -0
  190. /package/dist/{chunk-7OZPA5OO.js.map → chunk-63G7IQTD.js.map} +0 -0
  191. /package/dist/{chunk-JKP5GH6T.js.map → chunk-7LLPRPR5.js.map} +0 -0
  192. /package/dist/{chunk-VZ7AMAFL.js.map → chunk-H2MDONBU.js.map} +0 -0
  193. /package/dist/{chunk-5WTJAXY2.js.map → chunk-HMLMH7VZ.js.map} +0 -0
  194. /package/dist/{chunk-Z73NYSBZ.js.map → chunk-HPFZLISB.js.map} +0 -0
  195. /package/dist/{chunk-TSPZOMHC.js.map → chunk-UJQN5N3I.js.map} +0 -0
  196. /package/dist/{chunk-NDSQYIWT.js.map → chunk-W4ALF422.js.map} +0 -0
  197. /package/dist/{chunk-LB7OS35Q.js.map → chunk-Z6YZJ36C.js.map} +0 -0
  198. /package/dist/{chunk-MBVNHJVN.js.map → chunk-ZQIIPFD7.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getCalleeRowsForSymbol
3
- } from "./chunk-FUHJCHS4.js";
3
+ } from "./chunk-ZOGY2V3N.js";
4
4
  import {
5
5
  shortenSymbol
6
6
  } from "./chunk-QOV2R2WT.js";
@@ -111,4 +111,4 @@ function extractCandidates(db, opts = {}) {
111
111
  export {
112
112
  extractCandidates
113
113
  };
114
- //# sourceMappingURL=chunk-CM454WL3.js.map
114
+ //# sourceMappingURL=chunk-2CKGIR6G.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  TEST_SUPPORT_PATH_PATTERNS,
3
3
  testFileExclusionSql
4
- } from "./chunk-FUHJCHS4.js";
4
+ } from "./chunk-ZOGY2V3N.js";
5
5
  import {
6
6
  shortenSymbol
7
7
  } from "./chunk-QOV2R2WT.js";
@@ -42,7 +42,7 @@ function dead(db, opts = {}) {
42
42
  gs.symbol,
43
43
  (SELECT COUNT(*) FROM mentions m2
44
44
  JOIN chunks c2 ON m2.chunk_id = c2.id
45
- WHERE m2.symbol_id = gs.id AND m2.role = 0 AND c2.document_id = d.id
45
+ WHERE m2.symbol_id = gs.id AND m2.role != 1 AND c2.document_id = d.id
46
46
  ) AS same_file_refs
47
47
  FROM global_symbols gs
48
48
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
@@ -60,7 +60,7 @@ function dead(db, opts = {}) {
60
60
  JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
61
61
  JOIN documents ref_d ON ref_c.document_id = ref_d.id
62
62
  WHERE ref_m.symbol_id = gs.id
63
- AND ref_m.role = 0
63
+ AND ref_m.role != 1
64
64
  AND ref_d.id != d.id
65
65
  ${barrelExclusions}
66
66
  )
@@ -98,4 +98,4 @@ function dead(db, opts = {}) {
98
98
  export {
99
99
  dead
100
100
  };
101
- //# sourceMappingURL=chunk-5FGUEU7N.js.map
101
+ //# sourceMappingURL=chunk-4EXL2CUA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/dead.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { TEST_SUPPORT_PATH_PATTERNS, testFileExclusionSql } from '../query-support.js';\nimport type { DeadOptions, DeadSymbolResult, DeadSummary } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find dead exports: symbols defined locally with no cross-file references.\n * Language-agnostic — works with any SCIP index.\n */\nexport function dead(db: ScipDatabase, opts: DeadOptions = {}): DeadSummary {\n const {\n scope,\n minLoc = 1,\n includeTests = false,\n skipBarrels = false,\n includeMembers = false,\n } = opts;\n\n const params: unknown[] = [minLoc];\n let testFileExclusions = '';\n let memberExclusion = '';\n\n if (scope) {\n params.push(`%${scope}%`);\n }\n\n if (!includeTests) {\n testFileExclusions = `\n AND ${testFileExclusionSql('d', TEST_SUPPORT_PATH_PATTERNS)}\n `;\n }\n\n if (!includeMembers) {\n memberExclusion = `AND gs.symbol NOT LIKE '%#%'`;\n }\n\n // Barrel file exclusion for the NOT EXISTS subquery\n const barrelExclusions = skipBarrels\n ? `AND ref_d.relative_path NOT LIKE '%/index.ts'\n AND ref_d.relative_path NOT LIKE '%/index.js'\n AND ref_d.relative_path NOT LIKE '%/mod.rs'\n AND ref_d.relative_path NOT LIKE '%/__init__.py'`\n : '';\n\n const sql = `\n SELECT\n d.relative_path,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc,\n gs.symbol,\n (SELECT COUNT(*) FROM mentions m2\n JOIN chunks c2 ON m2.chunk_id = c2.id\n WHERE m2.symbol_id = gs.id AND m2.role != 1 AND c2.document_id = d.id\n ) AS same_file_refs\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n ${db.symbolNoiseFor('gs')}\n AND (der.end_line - der.start_line + 1) >= ?\n ${scope ? 'AND d.relative_path LIKE ?' : ''}\n ${testFileExclusions}\n ${memberExclusion}\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 JOIN documents ref_d ON ref_c.document_id = ref_d.id\n WHERE ref_m.symbol_id = gs.id\n AND ref_m.role != 1\n AND ref_d.id != d.id\n ${barrelExclusions}\n )\n ORDER BY (der.end_line - der.start_line + 1) DESC, d.relative_path, der.start_line\n `;\n\n const rows = db.all<{\n relative_path: string;\n start_line: number;\n end_line: number;\n loc: number;\n symbol: string;\n same_file_refs: number;\n }>(sql, ...params);\n\n let deadCodeCount = 0;\n let fileInternalCount = 0;\n let totalLoc = 0;\n\n const symbols: DeadSymbolResult[] = rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => {\n // dead-code: zero references anywhere (not even in same file) — safe to delete\n // file-internal: referenced within same file but never cross-file —\n // may be a private helper (fine) or a forgotten export (needs review)\n const kind = r.same_file_refs === 0 ? 'dead-code' : 'file-internal';\n if (kind === 'dead-code') deadCodeCount++;\n else fileInternalCount++;\n totalLoc += r.loc;\n\n return {\n relativePath: r.relative_path,\n startLine: r.start_line,\n endLine: r.end_line,\n loc: r.loc,\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n sameFileRefs: r.same_file_refs,\n kind,\n };\n });\n\n return {\n symbols,\n totalCount: symbols.length,\n deadCodeCount,\n fileInternalCount,\n totalLoc,\n };\n}\n"],"mappings":";;;;;;;;;AASO,SAAS,KAAK,IAAkB,OAAoB,CAAC,GAAgB;AAC1E,QAAM;AAAA,IACJ;AAAA,IACA,SAAS;AAAA,IACT,eAAe;AAAA,IACf,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB,IAAI;AAEJ,QAAM,SAAoB,CAAC,MAAM;AACjC,MAAI,qBAAqB;AACzB,MAAI,kBAAkB;AAEtB,MAAI,OAAO;AACT,WAAO,KAAK,IAAI,KAAK,GAAG;AAAA,EAC1B;AAEA,MAAI,CAAC,cAAc;AACjB,yBAAqB;AAAA,YACb,qBAAqB,KAAK,0BAA0B,CAAC;AAAA;AAAA,EAE/D;AAEA,MAAI,CAAC,gBAAgB;AACnB,sBAAkB;AAAA,EACpB;AAGA,QAAM,mBAAmB,cACrB;AAAA;AAAA;AAAA,2DAIA;AAEJ,QAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAeN,GAAG,kBAAkB,GAAG,CAAC;AAAA,QACzB,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA,QAEvB,QAAQ,+BAA+B,EAAE;AAAA,QACzC,kBAAkB;AAAA,QAClB,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YASX,gBAAgB;AAAA;AAAA;AAAA;AAK1B,QAAM,OAAO,GAAG,IAOb,KAAK,GAAG,MAAM;AAEjB,MAAI,gBAAgB;AACpB,MAAI,oBAAoB;AACxB,MAAI,WAAW;AAEf,QAAM,UAA8B,KACjC,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,MAAM;AAIV,UAAM,OAAO,EAAE,mBAAmB,IAAI,cAAc;AACpD,QAAI,SAAS,YAAa;AAAA,QACrB;AACL,gBAAY,EAAE;AAEd,WAAO;AAAA,MACL,cAAc,EAAE;AAAA,MAChB,WAAW,EAAE;AAAA,MACb,SAAS,EAAE;AAAA,MACX,KAAK,EAAE;AAAA,MACP,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,cAAc,EAAE;AAAA,MAChB;AAAA,IACF;AAAA,EACF,CAAC;AAEH,SAAO;AAAA,IACL;AAAA,IACA,YAAY,QAAQ;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  testFileExclusionSql
3
- } from "./chunk-FUHJCHS4.js";
3
+ } from "./chunk-ZOGY2V3N.js";
4
4
  import {
5
5
  shortenSymbol
6
6
  } from "./chunk-QOV2R2WT.js";
@@ -28,7 +28,7 @@ function wrapperCandidates(db, opts) {
28
28
  AND ref_c.end_line <= caller_der.end_line
29
29
  JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id
30
30
  WHERE ref_m.symbol_id = gs.id
31
- AND ref_m.role = 0
31
+ AND ref_m.role != 1
32
32
  AND ref_c.document_id != der.document_id
33
33
  LIMIT 1
34
34
  ) AS caller_symbol,
@@ -45,11 +45,11 @@ function wrapperCandidates(db, opts) {
45
45
  AND ref_c2.start_line >= caller_der2.start_line
46
46
  AND ref_c2.end_line <= caller_der2.end_line
47
47
  WHERE ref_m2.symbol_id = gs.id
48
- AND ref_m2.role = 0
48
+ AND ref_m2.role != 1
49
49
  AND ref_c2.document_id != der.document_id
50
50
  LIMIT 1
51
51
  )
52
- AND caller_ref_m.role = 0
52
+ AND caller_ref_m.role != 1
53
53
  ) AS caller_fan_in
54
54
  FROM global_symbols gs
55
55
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
@@ -69,7 +69,7 @@ function wrapperCandidates(db, opts) {
69
69
  FROM mentions ref_m
70
70
  JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
71
71
  WHERE ref_m.symbol_id = gs.id
72
- AND ref_m.role = 0
72
+ AND ref_m.role != 1
73
73
  AND ref_c.document_id != der.document_id
74
74
  ) = 1
75
75
  ) WHERE caller_symbol IS NOT NULL AND caller_fan_in > 3
@@ -94,4 +94,4 @@ function wrapperCandidates(db, opts) {
94
94
  export {
95
95
  wrapperCandidates
96
96
  };
97
- //# sourceMappingURL=chunk-TDNNOR6D.js.map
97
+ //# sourceMappingURL=chunk-4PDAL6IL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/wrapper-candidates.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { testFileExclusionSql } from '../query-support.js';\nimport type { WrapperCandidate } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find wrapper candidates: symbols called by only one other symbol.\n *\n * These are premature abstractions that add indirection without\n * providing reuse. A function with fan-in = 1 whose sole caller\n * is widely used is a strong signal of unnecessary wrapping.\n */\nexport function wrapperCandidates(\n db: ScipDatabase,\n opts?: { scope?: string; maxLoc?: number; limit?: number },\n): WrapperCandidate[] {\n const { scope, maxLoc = 15, limit = 30 } = opts ?? {};\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n // Find all symbols with exactly 1 cross-file consumer (fan-in = 1),\n // along with who that single caller is and the caller's own fan-in.\n const rows = db.all<{\n symbol: string;\n file: string;\n start_line: number;\n end_line: number;\n loc: number;\n caller_symbol: string;\n caller_fan_in: number;\n }>(\n `SELECT * FROM (\n SELECT\n gs.symbol,\n d.relative_path AS file,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc,\n -- The single caller: the symbol whose definition range contains\n -- the chunk that references our target\n (SELECT caller_gs.symbol\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n JOIN defn_enclosing_ranges caller_der\n ON caller_der.document_id = ref_c.document_id\n AND ref_c.start_line >= caller_der.start_line\n AND ref_c.end_line <= caller_der.end_line\n JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id\n WHERE ref_m.symbol_id = gs.id\n AND ref_m.role != 1\n AND ref_c.document_id != der.document_id\n LIMIT 1\n ) AS caller_symbol,\n -- Fan-in of that single caller\n (SELECT COUNT(DISTINCT caller_ref_c.document_id)\n FROM mentions caller_ref_m\n JOIN chunks caller_ref_c ON caller_ref_m.chunk_id = caller_ref_c.id\n WHERE caller_ref_m.symbol_id = (\n SELECT caller_der2.symbol_id\n FROM mentions ref_m2\n JOIN chunks ref_c2 ON ref_m2.chunk_id = ref_c2.id\n JOIN defn_enclosing_ranges caller_der2\n ON caller_der2.document_id = ref_c2.document_id\n AND ref_c2.start_line >= caller_der2.start_line\n AND ref_c2.end_line <= caller_der2.end_line\n WHERE ref_m2.symbol_id = gs.id\n AND ref_m2.role != 1\n AND ref_c2.document_id != der.document_id\n LIMIT 1\n )\n AND caller_ref_m.role != 1\n ) AS caller_fan_in\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND ${testFileExclusionSql('d')}\n ${db.symbolNoiseFor('gs')}\n -- Only functions/terms, not type definitions (types with # are not wrappers)\n AND gs.symbol NOT LIKE '%#'\n AND (der.end_line - der.start_line + 1) <= ?\n AND (der.end_line - der.start_line + 1) >= 2\n ${scopeFilter}\n -- Exactly 1 cross-file consumer\n AND (\n SELECT COUNT(DISTINCT ref_c.document_id)\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n WHERE ref_m.symbol_id = gs.id\n AND ref_m.role != 1\n AND ref_c.document_id != der.document_id\n ) = 1\n ) WHERE caller_symbol IS NOT NULL AND caller_fan_in > 3\n ORDER BY caller_fan_in DESC, loc DESC\n LIMIT ?`,\n maxLoc, limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.file))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n file: r.file,\n startLine: r.start_line,\n endLine: r.end_line,\n loc: r.loc,\n singleCaller: r.caller_symbol,\n singleCallerShort: shortenSymbol(r.caller_symbol),\n callerFanIn: r.caller_fan_in,\n }));\n}\n"],"mappings":";;;;;;;;AAYO,SAAS,kBACd,IACA,MACoB;AACpB,QAAM,EAAE,OAAO,SAAS,IAAI,QAAQ,GAAG,IAAI,QAAQ,CAAC;AACpD,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAItE,QAAM,OAAO,GAAG;AAAA,IASd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UA6CM,GAAG,kBAAkB,GAAG,CAAC;AAAA,cACrB,qBAAqB,GAAG,CAAC;AAAA,UAC7B,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,UAKvB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAajB;AAAA,IAAQ;AAAA,EACV;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EACnC,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,MAAM,EAAE;AAAA,IACR,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,KAAK,EAAE;AAAA,IACP,cAAc,EAAE;AAAA,IAChB,mBAAmB,cAAc,EAAE,aAAa;AAAA,IAChD,aAAa,EAAE;AAAA,EACjB,EAAE;AACN;","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  findFirstSymbolMatch,
3
3
  getCalleeRowsForSymbol
4
- } from "./chunk-FUHJCHS4.js";
4
+ } from "./chunk-ZOGY2V3N.js";
5
5
  import {
6
6
  shortenSymbol
7
7
  } from "./chunk-QOV2R2WT.js";
@@ -32,7 +32,7 @@ function complexity(db, symbolPattern) {
32
32
  `SELECT COUNT(DISTINCT c.document_id) AS c
33
33
  FROM mentions m
34
34
  JOIN chunks c ON m.chunk_id = c.id
35
- WHERE m.symbol_id = ? AND m.role = 0`,
35
+ WHERE m.symbol_id = ? AND m.role != 1`,
36
36
  match.symbolId
37
37
  );
38
38
  const fanOut = new Set(
@@ -107,4 +107,4 @@ function stripCommentsAndStrings(source) {
107
107
  export {
108
108
  complexity
109
109
  };
110
- //# sourceMappingURL=chunk-BP2ATLK2.js.map
110
+ //# sourceMappingURL=chunk-4XHWPRAX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/complexity.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch, getCalleeRowsForSymbol } from '../query-support.js';\nimport type { ComplexityResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Per-symbol complexity analysis combining source-level branch counting\n * with index-level metrics (fan-in, fan-out, callee count).\n *\n * Branch counting uses language-aware regex. The language is read from\n * the SCIP documents table, so it works for any indexed language.\n */\nexport function complexity(\n db: ScipDatabase,\n symbolPattern: string,\n): ComplexityResult | null {\n const match = findFirstSymbolMatch(db, symbolPattern);\n if (!match) return null;\n\n // Get language\n const doc = db.get<{ language: string | null }>(\n `SELECT language FROM documents WHERE relative_path = ?`,\n match.relativePath,\n );\n const language = doc?.language ?? 'unknown';\n\n // Read source for branch counting\n const filePath = join(db.config.projectRoot, match.relativePath);\n let source = '';\n try {\n const lines = readFileSync(filePath, 'utf-8').split('\\n');\n source = lines.slice(match.startLine, match.endLine + 1).join('\\n');\n } catch {\n // If we can't read the file, just skip branch counting\n }\n\n const branches = countBranches(source, language);\n const loc = match.endLine - match.startLine + 1;\n\n // Callee count\n const callees = getCalleeRowsForSymbol(db, match);\n const uniqueCallees = new Set(callees.map((c) => c.symbol));\n\n // Fan-in\n const fanInRow = db.get<{ c: number }>(\n `SELECT COUNT(DISTINCT c.document_id) AS c\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n WHERE m.symbol_id = ? AND m.role != 1`,\n match.symbolId,\n );\n\n // Fan-out (callees in other files)\n const fanOut = new Set(\n callees.filter((c) => c.file !== match.relativePath).map((c) => c.symbol),\n ).size;\n\n return {\n symbol: match.symbol,\n shortName: shortenSymbol(match.symbol),\n relativePath: match.relativePath,\n startLine: match.startLine,\n endLine: match.endLine,\n loc,\n branches,\n cyclomaticEstimate: branches + 1,\n calleeCount: uniqueCallees.size,\n fanIn: fanInRow?.c ?? 0,\n fanOut,\n };\n}\n\n/**\n * Count branch points in source code using language-aware regex.\n * Works across all SCIP-supported languages.\n */\nfunction countBranches(source: string, language: string): number {\n // Strip comments and strings to avoid false positives\n const stripped = stripCommentsAndStrings(source);\n let count = 0;\n\n // Universal branch keywords (work across most C-family languages)\n const universalPatterns = [\n /\\bif\\b/g,\n /\\belse\\s+if\\b/g,\n /\\belse\\b/g,\n /\\bfor\\b/g,\n /\\bwhile\\b/g,\n /\\bswitch\\b/g,\n /\\bcase\\b/g,\n /\\bcatch\\b/g,\n /\\?\\s*[^?]/g, // ternary (but not ??)\n /&&/g,\n /\\|\\|/g,\n ];\n\n for (const pattern of universalPatterns) {\n const matches = stripped.match(pattern);\n if (matches) count += matches.length;\n }\n\n // Language-specific patterns\n if (language === 'python') {\n const pyPatterns = [/\\belif\\b/g, /\\bexcept\\b/g, /\\bfinally\\b/g];\n for (const p of pyPatterns) {\n const m = stripped.match(p);\n if (m) count += m.length;\n }\n } else if (language === 'rust') {\n const rustPatterns = [/\\bmatch\\b/g, /=>/g, /\\bloop\\b/g];\n for (const p of rustPatterns) {\n const m = stripped.match(p);\n if (m) count += m.length;\n }\n } else if (language === 'ruby') {\n const rubyPatterns = [/\\belsif\\b/g, /\\bunless\\b/g, /\\brescue\\b/g, /\\bwhen\\b/g];\n for (const p of rubyPatterns) {\n const m = stripped.match(p);\n if (m) count += m.length;\n }\n } else if (language === 'go') {\n const goPatterns = [/\\bselect\\b/g, /\\bdefer\\b/g];\n for (const p of goPatterns) {\n const m = stripped.match(p);\n if (m) count += m.length;\n }\n }\n\n return count;\n}\n\n/**\n * Rough strip of comments and string literals to reduce false positives\n * in branch counting. Not perfect but good enough for estimation.\n */\nfunction stripCommentsAndStrings(source: string): string {\n return source\n // Block comments\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n // Line comments\n .replace(/\\/\\/.*/g, '')\n // Python/Ruby line comments\n .replace(/#.*/g, '')\n // Double-quoted strings\n .replace(/\"(?:[^\"\\\\]|\\\\.)*\"/g, '\"\"')\n // Single-quoted strings\n .replace(/'(?:[^'\\\\]|\\\\.)*'/g, \"''\")\n // Template literals\n .replace(/`(?:[^`\\\\]|\\\\.)*`/g, '``');\n}\n"],"mappings":"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","names":[]}
@@ -14,7 +14,7 @@ function bottlenecks(db, opts = {}) {
14
14
  (SELECT COUNT(DISTINCT ref_c.document_id)
15
15
  FROM mentions ref_m
16
16
  JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
17
- WHERE ref_m.symbol_id = gs.id AND ref_m.role = 0
17
+ WHERE ref_m.symbol_id = gs.id AND ref_m.role != 1
18
18
  ) AS fan_in,
19
19
  (SELECT COUNT(DISTINCT ref_gs.id)
20
20
  FROM mentions ref_m
@@ -22,7 +22,7 @@ function bottlenecks(db, opts = {}) {
22
22
  JOIN global_symbols ref_gs ON ref_m.symbol_id = ref_gs.id
23
23
  JOIN defn_enclosing_ranges ref_der ON ref_gs.id = ref_der.symbol_id
24
24
  WHERE ref_c.document_id = def_d.id
25
- AND ref_m.role = 0
25
+ AND ref_m.role != 1
26
26
  AND ref_der.document_id != def_d.id
27
27
  ) AS fan_out
28
28
  FROM global_symbols gs
@@ -52,4 +52,4 @@ function bottlenecks(db, opts = {}) {
52
52
  export {
53
53
  bottlenecks
54
54
  };
55
- //# sourceMappingURL=chunk-2QZ23IBN.js.map
55
+ //# sourceMappingURL=chunk-5RMYT5WH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/bottlenecks.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { BottleneckResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find coupling hubs: symbols with both high fan-in (many consumers)\n * AND high fan-out (references many other symbols).\n *\n * These are the most dangerous symbols to change — they sit at the\n * intersection of many dependency paths. Score = fanIn * fanOut.\n */\nexport function bottlenecks(\n db: ScipDatabase,\n opts: { limit?: number; scope?: string; minFanIn?: number; minFanOut?: number } = {},\n): BottleneckResult[] {\n const { limit = 20, scope, minFanIn = 2, minFanOut = 2 } = opts;\n const scopeFilter = scope ? `AND def_d.relative_path LIKE '%${scope}%'` : '';\n\n // Use a wrapping query to filter on computed columns\n const rows = db.all<{\n symbol: string;\n defined_in: string;\n fan_in: number;\n fan_out: number;\n }>(\n `SELECT * FROM (\n SELECT\n gs.symbol,\n def_d.relative_path AS defined_in,\n (SELECT COUNT(DISTINCT ref_c.document_id)\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n WHERE ref_m.symbol_id = gs.id AND ref_m.role != 1\n ) AS fan_in,\n (SELECT COUNT(DISTINCT ref_gs.id)\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n JOIN global_symbols ref_gs ON ref_m.symbol_id = ref_gs.id\n JOIN defn_enclosing_ranges ref_der ON ref_gs.id = ref_der.symbol_id\n WHERE ref_c.document_id = def_d.id\n AND ref_m.role != 1\n AND ref_der.document_id != def_d.id\n ) AS fan_out\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents def_d ON der.document_id = def_d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('def_d')}\n ${db.symbolNoiseFor('gs')}\n ${scopeFilter}\n ) WHERE fan_in >= ? AND fan_out >= ?\n ORDER BY (fan_in * fan_out) DESC\n LIMIT ?`,\n minFanIn, minFanOut, limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.defined_in))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n fanIn: r.fan_in,\n fanOut: r.fan_out,\n score: r.fan_in * r.fan_out,\n definedIn: r.defined_in,\n }));\n}\n"],"mappings":";;;;;AAWO,SAAS,YACd,IACA,OAAkF,CAAC,GAC/D;AACpB,QAAM,EAAE,QAAQ,IAAI,OAAO,WAAW,GAAG,YAAY,EAAE,IAAI;AAC3D,QAAM,cAAc,QAAQ,kCAAkC,KAAK,OAAO;AAG1E,QAAM,OAAO,GAAG;AAAA,IAMd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAsBM,GAAG,kBAAkB,OAAO,CAAC;AAAA,UAC7B,GAAG,eAAe,IAAI,CAAC;AAAA,UACvB,WAAW;AAAA;AAAA;AAAA;AAAA,IAIjB;AAAA,IAAU;AAAA,IAAW;AAAA,EACvB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,UAAU,CAAC,EACzC,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,OAAO,EAAE;AAAA,IACT,QAAQ,EAAE;AAAA,IACV,OAAO,EAAE,SAAS,EAAE;AAAA,IACpB,WAAW,EAAE;AAAA,EACf,EAAE;AACN;","names":[]}
@@ -1,39 +1,39 @@
1
1
  import {
2
2
  staleAbstractions
3
- } from "./chunk-GJFURBEW.js";
3
+ } from "./chunk-BNN2RKD2.js";
4
4
  import {
5
5
  stats
6
- } from "./chunk-XFXDXEUN.js";
6
+ } from "./chunk-74RFWB5T.js";
7
7
  import {
8
8
  testCoverageSummary
9
- } from "./chunk-3ZZJVBIO.js";
9
+ } from "./chunk-HZBC7PPD.js";
10
10
  import {
11
11
  wrapperCandidates
12
- } from "./chunk-TDNNOR6D.js";
12
+ } from "./chunk-4PDAL6IL.js";
13
13
  import {
14
14
  passthroughCandidates
15
- } from "./chunk-NDSQYIWT.js";
15
+ } from "./chunk-W4ALF422.js";
16
16
  import {
17
17
  similarAll
18
- } from "./chunk-TSPZOMHC.js";
18
+ } from "./chunk-UJQN5N3I.js";
19
19
  import {
20
20
  isolated
21
- } from "./chunk-LUSIFBXO.js";
21
+ } from "./chunk-OVPLOMPY.js";
22
22
  import {
23
23
  drift
24
- } from "./chunk-36OMT7ZJ.js";
24
+ } from "./chunk-7RLE5EWE.js";
25
25
  import {
26
26
  extractCandidates
27
- } from "./chunk-CM454WL3.js";
27
+ } from "./chunk-2CKGIR6G.js";
28
28
  import {
29
29
  complexityHotspots
30
- } from "./chunk-EMDQWNYR.js";
30
+ } from "./chunk-ZK6GXM3J.js";
31
31
  import {
32
32
  cycles
33
- } from "./chunk-5WTJAXY2.js";
33
+ } from "./chunk-HMLMH7VZ.js";
34
34
  import {
35
35
  dead
36
- } from "./chunk-5FGUEU7N.js";
36
+ } from "./chunk-4EXL2CUA.js";
37
37
 
38
38
  // src/queries/health.ts
39
39
  function health(db, opts = {}) {
@@ -255,4 +255,4 @@ function health(db, opts = {}) {
255
255
  export {
256
256
  health
257
257
  };
258
- //# sourceMappingURL=chunk-7OZPA5OO.js.map
258
+ //# sourceMappingURL=chunk-63G7IQTD.js.map
@@ -6,7 +6,7 @@ function stats(db) {
6
6
  "SELECT COUNT(*) as c FROM mentions WHERE role = 1"
7
7
  ).c;
8
8
  const references = db.get(
9
- "SELECT COUNT(*) as c FROM mentions WHERE role = 0"
9
+ "SELECT COUNT(*) as c FROM mentions WHERE role != 1"
10
10
  ).c;
11
11
  return {
12
12
  documents,
@@ -21,4 +21,4 @@ function stats(db) {
21
21
  export {
22
22
  stats
23
23
  };
24
- //# sourceMappingURL=chunk-XFXDXEUN.js.map
24
+ //# sourceMappingURL=chunk-74RFWB5T.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/queries/stats.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { StatsResult } from '../types.js';\n\nexport function stats(db: ScipDatabase): StatsResult {\n const documents = db.get<{ c: number }>('SELECT COUNT(*) as c FROM documents')!.c;\n const symbols = db.get<{ c: number }>('SELECT COUNT(*) as c FROM global_symbols')!.c;\n const definitions = db.get<{ c: number }>(\n 'SELECT COUNT(*) as c FROM mentions WHERE role = 1',\n )!.c;\n const references = db.get<{ c: number }>(\n 'SELECT COUNT(*) as c FROM mentions WHERE role = 0',\n )!.c;\n\n return {\n documents,\n symbols,\n definitions,\n references,\n indexSizeBytes: db.sizeBytes(),\n lastBuilt: db.lastModified(),\n };\n}\n"],"mappings":";AAGO,SAAS,MAAM,IAA+B;AACnD,QAAM,YAAY,GAAG,IAAmB,qCAAqC,EAAG;AAChF,QAAM,UAAU,GAAG,IAAmB,0CAA0C,EAAG;AACnF,QAAM,cAAc,GAAG;AAAA,IACrB;AAAA,EACF,EAAG;AACH,QAAM,aAAa,GAAG;AAAA,IACpB;AAAA,EACF,EAAG;AAEH,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB,GAAG,UAAU;AAAA,IAC7B,WAAW,GAAG,aAAa;AAAA,EAC7B;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/queries/stats.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { StatsResult } from '../types.js';\n\nexport function stats(db: ScipDatabase): StatsResult {\n const documents = db.get<{ c: number }>('SELECT COUNT(*) as c FROM documents')!.c;\n const symbols = db.get<{ c: number }>('SELECT COUNT(*) as c FROM global_symbols')!.c;\n const definitions = db.get<{ c: number }>(\n 'SELECT COUNT(*) as c FROM mentions WHERE role = 1',\n )!.c;\n const references = db.get<{ c: number }>(\n 'SELECT COUNT(*) as c FROM mentions WHERE role != 1',\n )!.c;\n\n return {\n documents,\n symbols,\n definitions,\n references,\n indexSizeBytes: db.sizeBytes(),\n lastBuilt: db.lastModified(),\n };\n}\n"],"mappings":";AAGO,SAAS,MAAM,IAA+B;AACnD,QAAM,YAAY,GAAG,IAAmB,qCAAqC,EAAG;AAChF,QAAM,UAAU,GAAG,IAAmB,0CAA0C,EAAG;AACnF,QAAM,cAAc,GAAG;AAAA,IACrB;AAAA,EACF,EAAG;AACH,QAAM,aAAa,GAAG;AAAA,IACpB;AAAA,EACF,EAAG;AAEH,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB,GAAG,UAAU;AAAA,IAC7B,WAAW,GAAG,aAAa;AAAA,EAC7B;AACF;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  buildFileDepGraph
3
- } from "./chunk-FUHJCHS4.js";
3
+ } from "./chunk-ZOGY2V3N.js";
4
4
 
5
5
  // src/queries/similar-chains.ts
6
6
  function similarChains(db, opts = {}) {
@@ -210,4 +210,4 @@ function isSubChain(sub, full) {
210
210
  export {
211
211
  similarChains
212
212
  };
213
- //# sourceMappingURL=chunk-JKP5GH6T.js.map
213
+ //# sourceMappingURL=chunk-7LLPRPR5.js.map
@@ -18,7 +18,7 @@ function hotspots(db, opts = {}) {
18
18
  JOIN global_symbols gs ON m.symbol_id = gs.id
19
19
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
20
20
  JOIN documents def_d ON der.document_id = def_d.id
21
- WHERE m.role = 0
21
+ WHERE m.role != 1
22
22
  ${db.pathExclusionsFor("def_d")}
23
23
  ${db.symbolNoiseFor("gs")}
24
24
  ${scopeFilter}
@@ -39,4 +39,4 @@ function hotspots(db, opts = {}) {
39
39
  export {
40
40
  hotspots
41
41
  };
42
- //# sourceMappingURL=chunk-BFSCMC22.js.map
42
+ //# sourceMappingURL=chunk-7PBOG4YE.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/queries/hotspots.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { HotspotResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find the most-referenced symbols in the codebase — the choke points\n * where changes have the widest blast radius.\n */\nexport function hotspots(\n db: ScipDatabase,\n opts: { limit?: number; scope?: string } = {},\n): HotspotResult[] {\n const { limit = 30, scope } = opts;\n\n const scopeFilter = scope ? `AND def_d.relative_path LIKE '%${scope}%'` : '';\n\n const rows = db.all<{\n symbol: string;\n ref_count: number;\n file_count: number;\n defined_in: string;\n }>(\n `SELECT\n gs.symbol,\n COUNT(*) AS ref_count,\n COUNT(DISTINCT ref_d.id) AS file_count,\n def_d.relative_path AS defined_in\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents def_d ON der.document_id = def_d.id\n WHERE m.role = 0\n ${db.pathExclusionsFor('def_d')}\n ${db.symbolNoiseFor('gs')}\n ${scopeFilter}\n GROUP BY gs.id\n ORDER BY ref_count DESC\n LIMIT ?`,\n limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.defined_in))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n refCount: r.ref_count,\n fileCount: r.file_count,\n definedIn: r.defined_in,\n }));\n}\n"],"mappings":";;;;;AAQO,SAAS,SACd,IACA,OAA2C,CAAC,GAC3B;AACjB,QAAM,EAAE,QAAQ,IAAI,MAAM,IAAI;AAE9B,QAAM,cAAc,QAAQ,kCAAkC,KAAK,OAAO;AAE1E,QAAM,OAAO,GAAG;AAAA,IAMd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAYI,GAAG,kBAAkB,OAAO,CAAC;AAAA,QAC7B,GAAG,eAAe,IAAI,CAAC;AAAA,QACvB,WAAW;AAAA;AAAA;AAAA;AAAA,IAIf;AAAA,EACF;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,UAAU,CAAC,EACzC,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,UAAU,EAAE;AAAA,IACZ,WAAW,EAAE;AAAA,IACb,WAAW,EAAE;AAAA,EACf,EAAE;AACN;","names":[]}
1
+ {"version":3,"sources":["../src/queries/hotspots.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { HotspotResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find the most-referenced symbols in the codebase — the choke points\n * where changes have the widest blast radius.\n */\nexport function hotspots(\n db: ScipDatabase,\n opts: { limit?: number; scope?: string } = {},\n): HotspotResult[] {\n const { limit = 30, scope } = opts;\n\n const scopeFilter = scope ? `AND def_d.relative_path LIKE '%${scope}%'` : '';\n\n const rows = db.all<{\n symbol: string;\n ref_count: number;\n file_count: number;\n defined_in: string;\n }>(\n `SELECT\n gs.symbol,\n COUNT(*) AS ref_count,\n COUNT(DISTINCT ref_d.id) AS file_count,\n def_d.relative_path AS defined_in\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents def_d ON der.document_id = def_d.id\n WHERE m.role != 1\n ${db.pathExclusionsFor('def_d')}\n ${db.symbolNoiseFor('gs')}\n ${scopeFilter}\n GROUP BY gs.id\n ORDER BY ref_count DESC\n LIMIT ?`,\n limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.defined_in))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n refCount: r.ref_count,\n fileCount: r.file_count,\n definedIn: r.defined_in,\n }));\n}\n"],"mappings":";;;;;AAQO,SAAS,SACd,IACA,OAA2C,CAAC,GAC3B;AACjB,QAAM,EAAE,QAAQ,IAAI,MAAM,IAAI;AAE9B,QAAM,cAAc,QAAQ,kCAAkC,KAAK,OAAO;AAE1E,QAAM,OAAO,GAAG;AAAA,IAMd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAYI,GAAG,kBAAkB,OAAO,CAAC;AAAA,QAC7B,GAAG,eAAe,IAAI,CAAC;AAAA,QACvB,WAAW;AAAA;AAAA;AAAA;AAAA,IAIf;AAAA,EACF;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,UAAU,CAAC,EACzC,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,UAAU,EAAE;AAAA,IACZ,WAAW,EAAE;AAAA,IACb,WAAW,EAAE;AAAA,EACf,EAAE;AACN;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  buildFileDepGraph
3
- } from "./chunk-FUHJCHS4.js";
3
+ } from "./chunk-ZOGY2V3N.js";
4
4
 
5
5
  // src/queries/drift.ts
6
6
  import path from "path";
@@ -91,7 +91,7 @@ function buildSymbolRefGraph(db, scope) {
91
91
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
92
92
  JOIN documents d2 ON der.document_id = d2.id
93
93
  WHERE d1.id != d2.id
94
- AND m.role = 0
94
+ AND m.role != 1
95
95
  ${db.pathExclusionsFor("d1", "d2")}
96
96
  ${scopeFilter}`
97
97
  );
@@ -141,4 +141,4 @@ function isStructuralRole(basename) {
141
141
  export {
142
142
  drift
143
143
  };
144
- //# sourceMappingURL=chunk-36OMT7ZJ.js.map
144
+ //# sourceMappingURL=chunk-7RLE5EWE.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/queries/drift.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ScipDatabase } from '../db.js';\nimport { buildFileDepGraph } from '../query-support.js';\nimport type { DriftResult, DriftSummary } from '../types.js';\n\n/**\n * Detect structural drift using the reference graph, not just import patterns.\n *\n * Three types of drift, each detecting a real problem:\n *\n * 1. **Unused imports** — file depends on a module but never references\n * any of its symbols. Dead dependency, safe to remove.\n *\n * 2. **Layer violations** — file imports from a directory it shouldn't\n * based on the project's directory structure (e.g., a query importing\n * from reindex, a helper importing from CLI). Architectural decay.\n *\n * 3. **Pattern deviations** — file imports something no sibling does,\n * suggesting it's reaching outside its expected scope. Only flagged\n * when the file is the ONLY one in its directory with that dep.\n */\nexport function drift(\n db: ScipDatabase,\n opts?: { scope?: string; minDeviation?: number },\n): DriftSummary {\n const { scope } = opts ?? {};\n\n // Build file dep graph (which files depend on which)\n const depGraph = buildFileDepGraph(db, scope);\n\n // Build symbol-level reference graph: for each file, which other files'\n // symbols does it actually reference?\n const symbolRefs = buildSymbolRefGraph(db, scope);\n\n const results: DriftResult[] = [];\n\n // ── Angle 1: Unused imports ──────────────────────────────\n // File depends on module B (via dep graph) but never references\n // any symbol defined in B (via symbol ref graph).\n for (const [file, deps] of depGraph) {\n if (isStructuralRole(path.basename(file))) continue;\n\n const referencedFiles = symbolRefs.get(file) ?? new Set<string>();\n\n for (const dep of deps) {\n if (!referencedFiles.has(dep)) {\n // This file \"depends on\" dep but never references its symbols.\n // This can happen when the dep is imported for types only\n // (which don't appear in the mention graph). Skip type-heavy deps.\n if (isLikelyTypeOnlyDep(dep)) continue;\n\n results.push({\n file,\n kind: 'unused-import',\n description: `Depends on ${dep} but references none of its symbols`,\n dep,\n });\n }\n }\n }\n\n // ── Angle 2: Layer violations ────────────────────────────\n // Detect when a file imports from a directory that represents\n // a different architectural layer. We infer layers from the\n // directory structure: files in the same top-level dir are peers,\n // files in different top-level dirs crossing inward is a violation.\n const layerRules = inferLayerRules(depGraph);\n\n for (const [file, deps] of depGraph) {\n if (isStructuralRole(path.basename(file))) continue;\n\n const fileLayer = getTopDir(file);\n for (const dep of deps) {\n const depLayer = getTopDir(dep);\n if (fileLayer === depLayer) continue; // same layer, fine\n\n const violation = layerRules.get(`${fileLayer}->${depLayer}`);\n if (violation === 'violation') {\n results.push({\n file,\n kind: 'layer-violation',\n description: `Imports from ${depLayer}/ (${dep}) — may cross architectural boundary`,\n dep,\n detail: `${fileLayer}/ should not depend on ${depLayer}/`,\n });\n }\n }\n }\n\n // ── Angle 3: Unique deps (pattern deviation) ─────────────\n // If a file is the ONLY one in its directory that depends on a\n // particular module, that dependency is unusual and worth flagging.\n const dirToFiles = new Map<string, string[]>();\n for (const file of depGraph.keys()) {\n const dir = path.dirname(file);\n if (!dirToFiles.has(dir)) dirToFiles.set(dir, []);\n dirToFiles.get(dir)!.push(file);\n }\n\n for (const [dir, files] of dirToFiles) {\n if (files.length < 3) continue;\n\n // Count dep frequency across siblings\n const depFreq = new Map<string, number>();\n for (const file of files) {\n if (isStructuralRole(path.basename(file))) continue;\n for (const dep of depGraph.get(file) ?? []) {\n depFreq.set(dep, (depFreq.get(dep) ?? 0) + 1);\n }\n }\n\n for (const file of files) {\n if (isStructuralRole(path.basename(file))) continue;\n for (const dep of depGraph.get(file) ?? []) {\n if ((depFreq.get(dep) ?? 0) === 1) {\n // This file is the only one in its dir that depends on this module\n // Skip if dep is in the same directory (sibling imports are normal)\n if (path.dirname(dep) === dir) continue;\n\n results.push({\n file,\n kind: 'pattern-deviation',\n description: `Only file in ${dir}/ that depends on ${dep}`,\n dep,\n });\n }\n }\n }\n }\n\n return {\n results,\n unusedImports: results.filter((r) => r.kind === 'unused-import').length,\n layerViolations: results.filter((r) => r.kind === 'layer-violation').length,\n patternDeviations: results.filter((r) => r.kind === 'pattern-deviation').length,\n };\n}\n\n// ── Helpers ────────────────────────────────────────────────\n\n/**\n * Build a map of file → set of files whose symbols it references.\n * This is more precise than the dep graph because it uses actual\n * symbol mentions, not just import statements.\n */\nfunction buildSymbolRefGraph(\n db: ScipDatabase,\n scope?: string,\n): Map<string, Set<string>> {\n const scopeFilter = scope ? `AND d1.relative_path LIKE '%${scope}%'` : '';\n\n const rows = db.all<{ from_file: string; to_file: string }>(\n `SELECT DISTINCT d1.relative_path AS from_file, d2.relative_path AS to_file\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d1 ON c.document_id = d1.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d2 ON der.document_id = d2.id\n WHERE d1.id != d2.id\n AND m.role = 0\n ${db.pathExclusionsFor('d1', 'd2')}\n ${scopeFilter}`,\n );\n\n const graph = new Map<string, Set<string>>();\n for (const r of rows) {\n if (db.isIgnored(r.from_file) || db.isIgnored(r.to_file)) continue;\n if (!graph.has(r.from_file)) graph.set(r.from_file, new Set());\n graph.get(r.from_file)!.add(r.to_file);\n }\n return graph;\n}\n\n/**\n * Infer layer boundary rules from the dependency graph.\n * If directory A never depends on directory B across the entire codebase,\n * then a new A→B dependency is a violation.\n */\nfunction inferLayerRules(\n depGraph: Map<string, Set<string>>,\n): Map<string, 'ok' | 'violation'> {\n const layerEdges = new Map<string, number>();\n const layerSet = new Set<string>();\n\n for (const [file, deps] of depGraph) {\n const fromLayer = getTopDir(file);\n layerSet.add(fromLayer);\n for (const dep of deps) {\n const toLayer = getTopDir(dep);\n if (fromLayer === toLayer) continue;\n layerSet.add(toLayer);\n const key = `${fromLayer}->${toLayer}`;\n layerEdges.set(key, (layerEdges.get(key) ?? 0) + 1);\n }\n }\n\n // An edge that appears only 1-2 times across the whole codebase\n // is likely a violation (anomalous cross-layer dep).\n // Edges that appear many times are established patterns.\n const rules = new Map<string, 'ok' | 'violation'>();\n for (const [edge, count] of layerEdges) {\n rules.set(edge, count <= 2 ? 'violation' : 'ok');\n }\n\n return rules;\n}\n\nfunction getTopDir(filePath: string): string {\n const parts = filePath.split('/');\n return parts[0] ?? filePath;\n}\n\nfunction isLikelyTypeOnlyDep(dep: string): boolean {\n return dep.includes('types') || dep.endsWith('.d.ts');\n}\n\nfunction isStructuralRole(basename: string): boolean {\n if (basename === 'index.ts' || basename === 'index.js') return true;\n if (basename === 'cli.ts' || basename === 'main.ts' || basename === 'main.rs') return true;\n if (basename.includes('worker.') || basename.includes('postinstall.')) return true;\n if (basename === 'health.ts' || basename === 'health.js') return true;\n return false;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/queries/drift.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ScipDatabase } from '../db.js';\nimport { buildFileDepGraph } from '../query-support.js';\nimport type { DriftResult, DriftSummary } from '../types.js';\n\n/**\n * Detect structural drift using the reference graph, not just import patterns.\n *\n * Three types of drift, each detecting a real problem:\n *\n * 1. **Unused imports** — file depends on a module but never references\n * any of its symbols. Dead dependency, safe to remove.\n *\n * 2. **Layer violations** — file imports from a directory it shouldn't\n * based on the project's directory structure (e.g., a query importing\n * from reindex, a helper importing from CLI). Architectural decay.\n *\n * 3. **Pattern deviations** — file imports something no sibling does,\n * suggesting it's reaching outside its expected scope. Only flagged\n * when the file is the ONLY one in its directory with that dep.\n */\nexport function drift(\n db: ScipDatabase,\n opts?: { scope?: string; minDeviation?: number },\n): DriftSummary {\n const { scope } = opts ?? {};\n\n // Build file dep graph (which files depend on which)\n const depGraph = buildFileDepGraph(db, scope);\n\n // Build symbol-level reference graph: for each file, which other files'\n // symbols does it actually reference?\n const symbolRefs = buildSymbolRefGraph(db, scope);\n\n const results: DriftResult[] = [];\n\n // ── Angle 1: Unused imports ──────────────────────────────\n // File depends on module B (via dep graph) but never references\n // any symbol defined in B (via symbol ref graph).\n for (const [file, deps] of depGraph) {\n if (isStructuralRole(path.basename(file))) continue;\n\n const referencedFiles = symbolRefs.get(file) ?? new Set<string>();\n\n for (const dep of deps) {\n if (!referencedFiles.has(dep)) {\n // This file \"depends on\" dep but never references its symbols.\n // This can happen when the dep is imported for types only\n // (which don't appear in the mention graph). Skip type-heavy deps.\n if (isLikelyTypeOnlyDep(dep)) continue;\n\n results.push({\n file,\n kind: 'unused-import',\n description: `Depends on ${dep} but references none of its symbols`,\n dep,\n });\n }\n }\n }\n\n // ── Angle 2: Layer violations ────────────────────────────\n // Detect when a file imports from a directory that represents\n // a different architectural layer. We infer layers from the\n // directory structure: files in the same top-level dir are peers,\n // files in different top-level dirs crossing inward is a violation.\n const layerRules = inferLayerRules(depGraph);\n\n for (const [file, deps] of depGraph) {\n if (isStructuralRole(path.basename(file))) continue;\n\n const fileLayer = getTopDir(file);\n for (const dep of deps) {\n const depLayer = getTopDir(dep);\n if (fileLayer === depLayer) continue; // same layer, fine\n\n const violation = layerRules.get(`${fileLayer}->${depLayer}`);\n if (violation === 'violation') {\n results.push({\n file,\n kind: 'layer-violation',\n description: `Imports from ${depLayer}/ (${dep}) — may cross architectural boundary`,\n dep,\n detail: `${fileLayer}/ should not depend on ${depLayer}/`,\n });\n }\n }\n }\n\n // ── Angle 3: Unique deps (pattern deviation) ─────────────\n // If a file is the ONLY one in its directory that depends on a\n // particular module, that dependency is unusual and worth flagging.\n const dirToFiles = new Map<string, string[]>();\n for (const file of depGraph.keys()) {\n const dir = path.dirname(file);\n if (!dirToFiles.has(dir)) dirToFiles.set(dir, []);\n dirToFiles.get(dir)!.push(file);\n }\n\n for (const [dir, files] of dirToFiles) {\n if (files.length < 3) continue;\n\n // Count dep frequency across siblings\n const depFreq = new Map<string, number>();\n for (const file of files) {\n if (isStructuralRole(path.basename(file))) continue;\n for (const dep of depGraph.get(file) ?? []) {\n depFreq.set(dep, (depFreq.get(dep) ?? 0) + 1);\n }\n }\n\n for (const file of files) {\n if (isStructuralRole(path.basename(file))) continue;\n for (const dep of depGraph.get(file) ?? []) {\n if ((depFreq.get(dep) ?? 0) === 1) {\n // This file is the only one in its dir that depends on this module\n // Skip if dep is in the same directory (sibling imports are normal)\n if (path.dirname(dep) === dir) continue;\n\n results.push({\n file,\n kind: 'pattern-deviation',\n description: `Only file in ${dir}/ that depends on ${dep}`,\n dep,\n });\n }\n }\n }\n }\n\n return {\n results,\n unusedImports: results.filter((r) => r.kind === 'unused-import').length,\n layerViolations: results.filter((r) => r.kind === 'layer-violation').length,\n patternDeviations: results.filter((r) => r.kind === 'pattern-deviation').length,\n };\n}\n\n// ── Helpers ────────────────────────────────────────────────\n\n/**\n * Build a map of file → set of files whose symbols it references.\n * This is more precise than the dep graph because it uses actual\n * symbol mentions, not just import statements.\n */\nfunction buildSymbolRefGraph(\n db: ScipDatabase,\n scope?: string,\n): Map<string, Set<string>> {\n const scopeFilter = scope ? `AND d1.relative_path LIKE '%${scope}%'` : '';\n\n const rows = db.all<{ from_file: string; to_file: string }>(\n `SELECT DISTINCT d1.relative_path AS from_file, d2.relative_path AS to_file\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d1 ON c.document_id = d1.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d2 ON der.document_id = d2.id\n WHERE d1.id != d2.id\n AND m.role != 1\n ${db.pathExclusionsFor('d1', 'd2')}\n ${scopeFilter}`,\n );\n\n const graph = new Map<string, Set<string>>();\n for (const r of rows) {\n if (db.isIgnored(r.from_file) || db.isIgnored(r.to_file)) continue;\n if (!graph.has(r.from_file)) graph.set(r.from_file, new Set());\n graph.get(r.from_file)!.add(r.to_file);\n }\n return graph;\n}\n\n/**\n * Infer layer boundary rules from the dependency graph.\n * If directory A never depends on directory B across the entire codebase,\n * then a new A→B dependency is a violation.\n */\nfunction inferLayerRules(\n depGraph: Map<string, Set<string>>,\n): Map<string, 'ok' | 'violation'> {\n const layerEdges = new Map<string, number>();\n const layerSet = new Set<string>();\n\n for (const [file, deps] of depGraph) {\n const fromLayer = getTopDir(file);\n layerSet.add(fromLayer);\n for (const dep of deps) {\n const toLayer = getTopDir(dep);\n if (fromLayer === toLayer) continue;\n layerSet.add(toLayer);\n const key = `${fromLayer}->${toLayer}`;\n layerEdges.set(key, (layerEdges.get(key) ?? 0) + 1);\n }\n }\n\n // An edge that appears only 1-2 times across the whole codebase\n // is likely a violation (anomalous cross-layer dep).\n // Edges that appear many times are established patterns.\n const rules = new Map<string, 'ok' | 'violation'>();\n for (const [edge, count] of layerEdges) {\n rules.set(edge, count <= 2 ? 'violation' : 'ok');\n }\n\n return rules;\n}\n\nfunction getTopDir(filePath: string): string {\n const parts = filePath.split('/');\n return parts[0] ?? filePath;\n}\n\nfunction isLikelyTypeOnlyDep(dep: string): boolean {\n return dep.includes('types') || dep.endsWith('.d.ts');\n}\n\nfunction isStructuralRole(basename: string): boolean {\n if (basename === 'index.ts' || basename === 'index.js') return true;\n if (basename === 'cli.ts' || basename === 'main.ts' || basename === 'main.rs') return true;\n if (basename.includes('worker.') || basename.includes('postinstall.')) return true;\n if (basename === 'health.ts' || basename === 'health.js') return true;\n return false;\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  findFirstSymbolMatch,
3
3
  getCalleeRowsForSymbol
4
- } from "./chunk-FUHJCHS4.js";
4
+ } from "./chunk-ZOGY2V3N.js";
5
5
  import {
6
6
  shortenSymbol
7
7
  } from "./chunk-QOV2R2WT.js";
@@ -23,7 +23,7 @@ function callGraph(db, symbolPattern) {
23
23
  JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id
24
24
  JOIN documents caller_d ON caller_der.document_id = caller_d.id
25
25
  WHERE m.symbol_id = ?
26
- AND m.role = 0
26
+ AND m.role != 1
27
27
  AND caller_gs.id != ?
28
28
  ${db.symbolNoiseFor("caller_gs")}
29
29
  ${db.pathExclusionsFor("caller_d")}
@@ -52,4 +52,4 @@ function callGraph(db, symbolPattern) {
52
52
  export {
53
53
  callGraph
54
54
  };
55
- //# sourceMappingURL=chunk-UNTPVD36.js.map
55
+ //# sourceMappingURL=chunk-7UCKSQRS.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/queries/call-graph.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch, getCalleeRowsForSymbol } from '../query-support.js';\nimport type { CallGraphResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Build a call graph for a symbol: who calls it (incoming) and\n * what it calls (outgoing).\n *\n * Incoming: other symbols whose definition ranges contain a reference to this symbol.\n * Outgoing: symbols referenced within this symbol's definition range.\n */\nexport function callGraph(db: ScipDatabase, symbolPattern: string): CallGraphResult | null {\n // Find the target symbol and its definition range\n const target = findFirstSymbolMatch(db, symbolPattern);\n\n if (!target) return null;\n\n // CALLERS: symbols whose definition ranges contain a reference to our target.\n // Find chunks that reference our symbol, then find which symbol's definition encloses that chunk.\n const callerRows = db.all<{\n caller_symbol: string;\n caller_file: string;\n }>(\n `SELECT DISTINCT caller_gs.symbol AS caller_symbol, caller_d.relative_path AS caller_file\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n -- Find the enclosing symbol for where the reference appears\n JOIN defn_enclosing_ranges caller_der\n ON caller_der.document_id = ref_d.id\n AND c.start_line >= caller_der.start_line\n AND c.end_line <= caller_der.end_line\n JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id\n JOIN documents caller_d ON caller_der.document_id = caller_d.id\n WHERE m.symbol_id = ?\n AND m.role = 0\n AND caller_gs.id != ?\n ${db.symbolNoiseFor('caller_gs')}\n ${db.pathExclusionsFor('caller_d')}\n ORDER BY caller_d.relative_path\n LIMIT 50`,\n target.symbolId, target.symbolId,\n );\n\n // CALLEES: symbols referenced within our target's definition range.\n const calleeRows = getCalleeRowsForSymbol(db, target, { limit: 50 });\n\n return {\n symbol: target.symbol,\n shortName: shortenSymbol(target.symbol),\n callers: callerRows\n .filter((r) => !db.isIgnored(r.caller_file))\n .map((r) => ({\n symbol: r.caller_symbol,\n shortName: shortenSymbol(r.caller_symbol),\n file: r.caller_file,\n })),\n callees: calleeRows\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n file: r.file,\n })),\n };\n}\n"],"mappings":";;;;;;;;;AAYO,SAAS,UAAU,IAAkB,eAA+C;AAEzF,QAAM,SAAS,qBAAqB,IAAI,aAAa;AAErD,MAAI,CAAC,OAAQ,QAAO;AAIpB,QAAM,aAAa,GAAG;AAAA,IAIpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAcI,GAAG,eAAe,WAAW,CAAC;AAAA,QAC9B,GAAG,kBAAkB,UAAU,CAAC;AAAA;AAAA;AAAA,IAGpC,OAAO;AAAA,IAAU,OAAO;AAAA,EAC1B;AAGA,QAAM,aAAa,uBAAuB,IAAI,QAAQ,EAAE,OAAO,GAAG,CAAC;AAEnE,SAAO;AAAA,IACL,QAAQ,OAAO;AAAA,IACf,WAAW,cAAc,OAAO,MAAM;AAAA,IACtC,SAAS,WACN,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,WAAW,CAAC,EAC1C,IAAI,CAAC,OAAO;AAAA,MACX,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,aAAa;AAAA,MACxC,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,IACJ,SAAS,WACN,IAAI,CAAC,OAAO;AAAA,MACX,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,EACN;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/queries/call-graph.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch, getCalleeRowsForSymbol } from '../query-support.js';\nimport type { CallGraphResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Build a call graph for a symbol: who calls it (incoming) and\n * what it calls (outgoing).\n *\n * Incoming: other symbols whose definition ranges contain a reference to this symbol.\n * Outgoing: symbols referenced within this symbol's definition range.\n */\nexport function callGraph(db: ScipDatabase, symbolPattern: string): CallGraphResult | null {\n // Find the target symbol and its definition range\n const target = findFirstSymbolMatch(db, symbolPattern);\n\n if (!target) return null;\n\n // CALLERS: symbols whose definition ranges contain a reference to our target.\n // Find chunks that reference our symbol, then find which symbol's definition encloses that chunk.\n const callerRows = db.all<{\n caller_symbol: string;\n caller_file: string;\n }>(\n `SELECT DISTINCT caller_gs.symbol AS caller_symbol, caller_d.relative_path AS caller_file\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n -- Find the enclosing symbol for where the reference appears\n JOIN defn_enclosing_ranges caller_der\n ON caller_der.document_id = ref_d.id\n AND c.start_line >= caller_der.start_line\n AND c.end_line <= caller_der.end_line\n JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id\n JOIN documents caller_d ON caller_der.document_id = caller_d.id\n WHERE m.symbol_id = ?\n AND m.role != 1\n AND caller_gs.id != ?\n ${db.symbolNoiseFor('caller_gs')}\n ${db.pathExclusionsFor('caller_d')}\n ORDER BY caller_d.relative_path\n LIMIT 50`,\n target.symbolId, target.symbolId,\n );\n\n // CALLEES: symbols referenced within our target's definition range.\n const calleeRows = getCalleeRowsForSymbol(db, target, { limit: 50 });\n\n return {\n symbol: target.symbol,\n shortName: shortenSymbol(target.symbol),\n callers: callerRows\n .filter((r) => !db.isIgnored(r.caller_file))\n .map((r) => ({\n symbol: r.caller_symbol,\n shortName: shortenSymbol(r.caller_symbol),\n file: r.caller_file,\n })),\n callees: calleeRows\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n file: r.file,\n })),\n };\n}\n"],"mappings":";;;;;;;;;AAYO,SAAS,UAAU,IAAkB,eAA+C;AAEzF,QAAM,SAAS,qBAAqB,IAAI,aAAa;AAErD,MAAI,CAAC,OAAQ,QAAO;AAIpB,QAAM,aAAa,GAAG;AAAA,IAIpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAcI,GAAG,eAAe,WAAW,CAAC;AAAA,QAC9B,GAAG,kBAAkB,UAAU,CAAC;AAAA;AAAA;AAAA,IAGpC,OAAO;AAAA,IAAU,OAAO;AAAA,EAC1B;AAGA,QAAM,aAAa,uBAAuB,IAAI,QAAQ,EAAE,OAAO,GAAG,CAAC;AAEnE,SAAO;AAAA,IACL,QAAQ,OAAO;AAAA,IACf,WAAW,cAAc,OAAO,MAAM;AAAA,IACtC,SAAS,WACN,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,WAAW,CAAC,EAC1C,IAAI,CAAC,OAAO;AAAA,MACX,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,aAAa;AAAA,MACxC,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,IACJ,SAAS,WACN,IAAI,CAAC,OAAO;AAAA,MACX,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,EACN;AACF;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  testFileExclusionSql
3
- } from "./chunk-FUHJCHS4.js";
3
+ } from "./chunk-ZOGY2V3N.js";
4
4
  import {
5
5
  shortenSymbol
6
6
  } from "./chunk-QOV2R2WT.js";
@@ -21,7 +21,7 @@ function staleAbstractions(db, opts) {
21
21
  FROM mentions ref_m
22
22
  JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
23
23
  WHERE ref_m.symbol_id = gs.id
24
- AND ref_m.role = 0
24
+ AND ref_m.role != 1
25
25
  AND ref_c.document_id != der.document_id
26
26
  ) AS consumers
27
27
  FROM global_symbols gs
@@ -61,4 +61,4 @@ function staleAbstractions(db, opts) {
61
61
  export {
62
62
  staleAbstractions
63
63
  };
64
- //# sourceMappingURL=chunk-GJFURBEW.js.map
64
+ //# sourceMappingURL=chunk-BNN2RKD2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/stale-abstractions.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { testFileExclusionSql } from '../query-support.js';\nimport type { StaleAbstraction } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find stale abstractions: type-level symbols (classes, interfaces, type\n * aliases) that have 0 or 1 cross-file consumers.\n *\n * A type that only one file uses is over-abstracted — it was designed\n * for reuse that never materialized. Large single-consumer types are\n * the strongest signal of wasted abstraction.\n */\nexport function staleAbstractions(\n db: ScipDatabase,\n opts?: { scope?: string; minLoc?: number; limit?: number },\n): StaleAbstraction[] {\n const { scope, minLoc = 3, limit = 30 } = opts ?? {};\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n const rows = db.all<{\n symbol: string;\n file: string;\n start_line: number;\n end_line: number;\n loc: number;\n consumers: number;\n }>(\n `SELECT * FROM (\n SELECT\n gs.symbol,\n d.relative_path AS file,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc,\n (SELECT COUNT(DISTINCT ref_c.document_id)\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n WHERE ref_m.symbol_id = gs.id\n AND ref_m.role != 1\n AND ref_c.document_id != der.document_id\n ) AS consumers\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND ${testFileExclusionSql('d')}\n ${db.symbolNoiseFor('gs')}\n -- Top-level type symbols: ends with # but does not contain nested #\n AND gs.symbol LIKE '%#'\n AND gs.symbol NOT LIKE '%#%#%'\n AND (der.end_line - der.start_line + 1) >= ?\n ${scopeFilter}\n ) WHERE consumers <= 1\n ORDER BY loc DESC\n LIMIT ?`,\n minLoc, limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.file))\n // Exclude types defined in dedicated type files (types.ts, types/, etc.)\n // These are intentional public API types, not premature abstractions.\n .filter((r) => {\n const basename = r.file.split('/').pop() ?? '';\n const isTypeFile = basename.includes('types') || r.file.includes('/types/');\n // Types in type files with 1 consumer are normal API types — skip them.\n // Types in type files with 0 consumers are genuinely unused — keep them.\n if (isTypeFile && r.consumers > 0) return false;\n return true;\n })\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n file: r.file,\n startLine: r.start_line,\n endLine: r.end_line,\n loc: r.loc,\n consumers: r.consumers,\n }));\n}\n"],"mappings":";;;;;;;;AAaO,SAAS,kBACd,IACA,MACoB;AACpB,QAAM,EAAE,OAAO,SAAS,GAAG,QAAQ,GAAG,IAAI,QAAQ,CAAC;AACnD,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAEtE,QAAM,OAAO,GAAG;AAAA,IAQd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAkBM,GAAG,kBAAkB,GAAG,CAAC;AAAA,cACrB,qBAAqB,GAAG,CAAC;AAAA,UAC7B,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,UAKvB,WAAW;AAAA;AAAA;AAAA;AAAA,IAIjB;AAAA,IAAQ;AAAA,EACV;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EAGnC,OAAO,CAAC,MAAM;AACb,UAAM,WAAW,EAAE,KAAK,MAAM,GAAG,EAAE,IAAI,KAAK;AAC5C,UAAM,aAAa,SAAS,SAAS,OAAO,KAAK,EAAE,KAAK,SAAS,SAAS;AAG1E,QAAI,cAAc,EAAE,YAAY,EAAG,QAAO;AAC1C,WAAO;AAAA,EACT,CAAC,EACA,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,MAAM,EAAE;AAAA,IACR,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,KAAK,EAAE;AAAA,IACP,WAAW,EAAE;AAAA,EACf,EAAE;AACN;","names":[]}
@@ -15,7 +15,7 @@ function surface(db, modulePattern) {
15
15
  WHERE d2.relative_path LIKE ?
16
16
  AND d1.relative_path NOT LIKE ?
17
17
  AND ${db.localSymbolPredicate}
18
- AND m.role = 0
18
+ AND m.role != 1
19
19
  ORDER BY d1.relative_path`,
20
20
  `%${modulePattern}%`,
21
21
  `%${modulePattern}%`
@@ -30,4 +30,4 @@ function surface(db, modulePattern) {
30
30
  export {
31
31
  surface
32
32
  };
33
- //# sourceMappingURL=chunk-FFSWWE5O.js.map
33
+ //# sourceMappingURL=chunk-BOVXCR46.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/queries/surface.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { SurfaceResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/** Public API surface: what symbols do external consumers actually use from this module? */\nexport function surface(db: ScipDatabase, modulePattern: string): SurfaceResult[] {\n const rows = db.all<{\n relative_path: string;\n symbol: string;\n }>(\n `SELECT DISTINCT d1.relative_path, gs.symbol\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d1 ON c.document_id = d1.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d2 ON der.document_id = d2.id\n WHERE d2.relative_path LIKE ?\n AND d1.relative_path NOT LIKE ?\n AND ${db.localSymbolPredicate}\n AND m.role = 0\n ORDER BY d1.relative_path`,\n `%${modulePattern}%`,\n `%${modulePattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n consumer: r.relative_path,\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n }));\n}\n"],"mappings":";;;;;AAKO,SAAS,QAAQ,IAAkB,eAAwC;AAChF,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YASQ,GAAG,oBAAoB;AAAA;AAAA;AAAA,IAG/B,IAAI,aAAa;AAAA,IACjB,IAAI,aAAa;AAAA,EACnB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,UAAU,EAAE;AAAA,IACZ,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,EACnC,EAAE;AACN;","names":[]}
1
+ {"version":3,"sources":["../src/queries/surface.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { SurfaceResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/** Public API surface: what symbols do external consumers actually use from this module? */\nexport function surface(db: ScipDatabase, modulePattern: string): SurfaceResult[] {\n const rows = db.all<{\n relative_path: string;\n symbol: string;\n }>(\n `SELECT DISTINCT d1.relative_path, gs.symbol\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d1 ON c.document_id = d1.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d2 ON der.document_id = d2.id\n WHERE d2.relative_path LIKE ?\n AND d1.relative_path NOT LIKE ?\n AND ${db.localSymbolPredicate}\n AND m.role != 1\n ORDER BY d1.relative_path`,\n `%${modulePattern}%`,\n `%${modulePattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n consumer: r.relative_path,\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n }));\n}\n"],"mappings":";;;;;AAKO,SAAS,QAAQ,IAAkB,eAAwC;AAChF,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YASQ,GAAG,oBAAoB;AAAA;AAAA;AAAA,IAG/B,IAAI,aAAa;AAAA,IACjB,IAAI,aAAa;AAAA,EACnB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,UAAU,EAAE;AAAA,IACZ,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,EACnC,EAAE;AACN;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  findFirstSymbolMatch
3
- } from "./chunk-FUHJCHS4.js";
3
+ } from "./chunk-ZOGY2V3N.js";
4
4
  import {
5
5
  shortenSymbol
6
6
  } from "./chunk-QOV2R2WT.js";
@@ -33,7 +33,7 @@ function affected(db, symbolPattern, opts = {}) {
33
33
  JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
34
34
  JOIN documents enc_d ON enc_der.document_id = enc_d.id
35
35
  WHERE m.symbol_id IN (${placeholders})
36
- AND m.role = 0
36
+ AND m.role != 1
37
37
  AND enc_gs.id NOT IN (${placeholders})
38
38
  ${db.symbolNoiseFor("enc_gs")}
39
39
  ${db.pathExclusionsFor("enc_d")}
@@ -62,4 +62,4 @@ function affected(db, symbolPattern, opts = {}) {
62
62
  export {
63
63
  affected
64
64
  };
65
- //# sourceMappingURL=chunk-6VJ6Q7IE.js.map
65
+ //# sourceMappingURL=chunk-D567NFIF.js.map
@@ -1 +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":[]}
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 != 1\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":[]}
@@ -10,7 +10,7 @@ function fanIn(db, symbolPattern) {
10
10
  JOIN chunks c ON m.chunk_id = c.id
11
11
  JOIN global_symbols gs ON m.symbol_id = gs.id
12
12
  WHERE gs.symbol LIKE ?
13
- AND m.role = 0
13
+ AND m.role != 1
14
14
  GROUP BY gs.id
15
15
  ORDER BY file_count DESC`,
16
16
  `%${symbolPattern}%`
@@ -30,7 +30,7 @@ function fanOut(db, filePattern) {
30
30
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
31
31
  JOIN documents def_d ON der.document_id = def_d.id
32
32
  WHERE d.relative_path LIKE ?
33
- AND m.role = 0
33
+ AND m.role != 1
34
34
  AND def_d.id != d.id
35
35
  GROUP BY d.id
36
36
  ORDER BY symbol_count DESC`,
@@ -51,7 +51,7 @@ function topFanIn(db, opts = {}) {
51
51
  JOIN global_symbols gs ON m.symbol_id = gs.id
52
52
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
53
53
  JOIN documents def_d ON der.document_id = def_d.id
54
- WHERE m.role = 0
54
+ WHERE m.role != 1
55
55
  ${db.pathExclusionsFor("def_d")}
56
56
  ${db.symbolNoiseFor("gs")}
57
57
  ${scopeFilter}
@@ -77,7 +77,7 @@ function topFanOut(db, opts = {}) {
77
77
  JOIN global_symbols gs ON m.symbol_id = gs.id
78
78
  JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
79
79
  JOIN documents def_d ON der.document_id = def_d.id
80
- WHERE m.role = 0
80
+ WHERE m.role != 1
81
81
  AND def_d.id != d.id
82
82
  ${db.pathExclusionsFor("d")}
83
83
  ${db.symbolNoiseFor("gs")}
@@ -99,4 +99,4 @@ export {
99
99
  topFanIn,
100
100
  topFanOut
101
101
  };
102
- //# sourceMappingURL=chunk-GTILYBH6.js.map
102
+ //# sourceMappingURL=chunk-DGUPQSOR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/queries/fan.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { FanResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Fan-in: how many distinct files reference this symbol.\n * High fan-in = widely depended upon = high blast radius for changes.\n */\nexport function fanIn(\n db: ScipDatabase,\n symbolPattern: string,\n): FanResult[] {\n const rows = db.all<{\n symbol: string;\n file_count: number;\n }>(\n `SELECT gs.symbol, COUNT(DISTINCT c.document_id) AS file_count\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n WHERE gs.symbol LIKE ?\n AND m.role != 1\n GROUP BY gs.id\n ORDER BY file_count DESC`,\n `%${symbolPattern}%`,\n );\n\n return rows.map((r) => ({\n name: shortenSymbol(r.symbol),\n count: r.file_count,\n }));\n}\n\n/**\n * Fan-out: how many external symbols does this file reference.\n * High fan-out = depends on many things = fragile to upstream changes.\n */\nexport function fanOut(\n db: ScipDatabase,\n filePattern: string,\n): FanResult[] {\n const rows = db.all<{\n relative_path: string;\n symbol_count: number;\n }>(\n `SELECT d.relative_path, COUNT(DISTINCT gs.id) AS symbol_count\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 JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents def_d ON der.document_id = def_d.id\n WHERE d.relative_path LIKE ?\n AND m.role != 1\n AND def_d.id != d.id\n GROUP BY d.id\n ORDER BY symbol_count DESC`,\n `%${filePattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n name: r.relative_path,\n count: r.symbol_count,\n }));\n}\n\n/**\n * Top fan-in across the whole codebase — the most depended-on symbols.\n */\nexport function topFanIn(\n db: ScipDatabase,\n opts: { limit?: number; scope?: string } = {},\n): FanResult[] {\n const { limit = 30, scope } = opts;\n const scopeFilter = scope\n ? `AND def_d.relative_path LIKE '%${scope}%'`\n : '';\n\n const rows = db.all<{\n symbol: string;\n file_count: number;\n }>(\n `SELECT gs.symbol, COUNT(DISTINCT c.document_id) AS file_count\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents def_d ON der.document_id = def_d.id\n WHERE m.role != 1\n ${db.pathExclusionsFor('def_d')}\n ${db.symbolNoiseFor('gs')}\n ${scopeFilter}\n GROUP BY gs.id\n HAVING file_count > 1\n ORDER BY file_count DESC\n LIMIT ?`,\n limit,\n );\n\n return rows.map((r) => ({\n name: shortenSymbol(r.symbol),\n count: r.file_count,\n }));\n}\n\n/**\n * Top fan-out across the whole codebase — files that depend on the most external symbols.\n */\nexport function topFanOut(\n db: ScipDatabase,\n opts: { limit?: number; scope?: string } = {},\n): FanResult[] {\n const { limit = 30, scope } = opts;\n const scopeFilter = scope\n ? `AND d.relative_path LIKE '%${scope}%'`\n : '';\n\n const rows = db.all<{\n relative_path: string;\n symbol_count: number;\n }>(\n `SELECT d.relative_path, COUNT(DISTINCT gs.id) AS symbol_count\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 JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents def_d ON der.document_id = def_d.id\n WHERE m.role != 1\n AND def_d.id != d.id\n ${db.pathExclusionsFor('d')}\n ${db.symbolNoiseFor('gs')}\n ${scopeFilter}\n GROUP BY d.id\n ORDER BY symbol_count DESC\n LIMIT ?`,\n limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n name: r.relative_path,\n count: r.symbol_count,\n }));\n}\n"],"mappings":";;;;;AAQO,SAAS,MACd,IACA,eACa;AACb,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,IAAI,aAAa;AAAA,EACnB;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,MAAM,cAAc,EAAE,MAAM;AAAA,IAC5B,OAAO,EAAE;AAAA,EACX,EAAE;AACJ;AAMO,SAAS,OACd,IACA,aACa;AACb,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAYA,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,MAAM,EAAE;AAAA,IACR,OAAO,EAAE;AAAA,EACX,EAAE;AACN;AAKO,SAAS,SACd,IACA,OAA2C,CAAC,GAC/B;AACb,QAAM,EAAE,QAAQ,IAAI,MAAM,IAAI;AAC9B,QAAM,cAAc,QAChB,kCAAkC,KAAK,OACvC;AAEJ,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOI,GAAG,kBAAkB,OAAO,CAAC;AAAA,QAC7B,GAAG,eAAe,IAAI,CAAC;AAAA,QACvB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,IAKf;AAAA,EACF;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,MAAM,cAAc,EAAE,MAAM;AAAA,IAC5B,OAAO,EAAE;AAAA,EACX,EAAE;AACJ;AAKO,SAAS,UACd,IACA,OAA2C,CAAC,GAC/B;AACb,QAAM,EAAE,QAAQ,IAAI,MAAM,IAAI;AAC9B,QAAM,cAAc,QAChB,8BAA8B,KAAK,OACnC;AAEJ,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QASI,GAAG,kBAAkB,GAAG,CAAC;AAAA,QACzB,GAAG,eAAe,IAAI,CAAC;AAAA,QACvB,WAAW;AAAA;AAAA;AAAA;AAAA,IAIf;AAAA,EACF;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,MAAM,EAAE;AAAA,IACR,OAAO,EAAE;AAAA,EACX,EAAE;AACN;","names":[]}