@monoes/monograph 1.2.0 → 1.2.1

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 (460) hide show
  1. package/__tests__/mcp-tools/cypher.test.ts +10 -6
  2. package/__tests__/mcp-tools/detect-changes.test.ts +14 -11
  3. package/__tests__/pipeline/phases/wildcard-synthesis.test.ts +4 -4
  4. package/__tests__/search/embed-batch-config.test.ts +7 -7
  5. package/__tests__/search/hybrid-query.test.ts +3 -1
  6. package/__tests__/web/api.test.ts +1 -1
  7. package/dist/src/analysis/churn-cache.d.ts.map +1 -1
  8. package/dist/src/analysis/churn-cache.js +19 -6
  9. package/dist/src/analysis/churn-cache.js.map +1 -1
  10. package/dist/src/analysis/churn-trend.d.ts.map +1 -1
  11. package/dist/src/analysis/churn-trend.js +50 -6
  12. package/dist/src/analysis/churn-trend.js.map +1 -1
  13. package/dist/src/analysis/cluster-quality.d.ts +3 -0
  14. package/dist/src/analysis/cluster-quality.d.ts.map +1 -1
  15. package/dist/src/analysis/cluster-quality.js +18 -12
  16. package/dist/src/analysis/cluster-quality.js.map +1 -1
  17. package/dist/src/analysis/coverage-gaps.d.ts +3 -0
  18. package/dist/src/analysis/coverage-gaps.d.ts.map +1 -1
  19. package/dist/src/analysis/coverage-gaps.js +40 -1
  20. package/dist/src/analysis/coverage-gaps.js.map +1 -1
  21. package/dist/src/analysis/dep-classification.d.ts +2 -0
  22. package/dist/src/analysis/dep-classification.d.ts.map +1 -1
  23. package/dist/src/analysis/dep-classification.js +30 -0
  24. package/dist/src/analysis/dep-classification.js.map +1 -1
  25. package/dist/src/analysis/dependency-health.d.ts +4 -0
  26. package/dist/src/analysis/dependency-health.d.ts.map +1 -1
  27. package/dist/src/analysis/dependency-health.js +28 -0
  28. package/dist/src/analysis/dependency-health.js.map +1 -1
  29. package/dist/src/analysis/duplicate-exports.d.ts +9 -5
  30. package/dist/src/analysis/duplicate-exports.d.ts.map +1 -1
  31. package/dist/src/analysis/duplicate-exports.js +26 -2
  32. package/dist/src/analysis/duplicate-exports.js.map +1 -1
  33. package/dist/src/analysis/feature-flags.d.ts.map +1 -1
  34. package/dist/src/analysis/feature-flags.js +36 -17
  35. package/dist/src/analysis/feature-flags.js.map +1 -1
  36. package/dist/src/analysis/node-similarity.d.ts +19 -0
  37. package/dist/src/analysis/node-similarity.d.ts.map +1 -1
  38. package/dist/src/analysis/node-similarity.js +34 -3
  39. package/dist/src/analysis/node-similarity.js.map +1 -1
  40. package/dist/src/analysis/private-type-leaks.d.ts +4 -0
  41. package/dist/src/analysis/private-type-leaks.d.ts.map +1 -1
  42. package/dist/src/analysis/private-type-leaks.js +43 -2
  43. package/dist/src/analysis/private-type-leaks.js.map +1 -1
  44. package/dist/src/analysis/project-state.d.ts +2 -0
  45. package/dist/src/analysis/project-state.d.ts.map +1 -1
  46. package/dist/src/analysis/project-state.js +18 -7
  47. package/dist/src/analysis/project-state.js.map +1 -1
  48. package/dist/src/analysis/suppressions.d.ts.map +1 -1
  49. package/dist/src/analysis/suppressions.js +3 -1
  50. package/dist/src/analysis/suppressions.js.map +1 -1
  51. package/dist/src/analysis/trace.d.ts +5 -3
  52. package/dist/src/analysis/trace.d.ts.map +1 -1
  53. package/dist/src/analysis/trace.js +59 -53
  54. package/dist/src/analysis/trace.js.map +1 -1
  55. package/dist/src/analysis/unused-class-members.d.ts.map +1 -1
  56. package/dist/src/analysis/unused-class-members.js +7 -1
  57. package/dist/src/analysis/unused-class-members.js.map +1 -1
  58. package/dist/src/analysis/worker-pool.d.ts +3 -0
  59. package/dist/src/analysis/worker-pool.d.ts.map +1 -1
  60. package/dist/src/analysis/worker-pool.js +30 -8
  61. package/dist/src/analysis/worker-pool.js.map +1 -1
  62. package/dist/src/cli/augment.d.ts.map +1 -1
  63. package/dist/src/cli/augment.js +10 -3
  64. package/dist/src/cli/augment.js.map +1 -1
  65. package/dist/src/export/sarif.js +5 -5
  66. package/dist/src/export/sarif.js.map +1 -1
  67. package/dist/src/graph/analyzer.d.ts.map +1 -1
  68. package/dist/src/graph/analyzer.js +17 -2
  69. package/dist/src/graph/analyzer.js.map +1 -1
  70. package/dist/src/graph/author-analytics.d.ts +2 -0
  71. package/dist/src/graph/author-analytics.d.ts.map +1 -1
  72. package/dist/src/graph/author-analytics.js +39 -5
  73. package/dist/src/graph/author-analytics.js.map +1 -1
  74. package/dist/src/graph/baseline.d.ts +5 -0
  75. package/dist/src/graph/baseline.d.ts.map +1 -1
  76. package/dist/src/graph/baseline.js +65 -18
  77. package/dist/src/graph/baseline.js.map +1 -1
  78. package/dist/src/graph/clone-detector.d.ts.map +1 -1
  79. package/dist/src/graph/clone-detector.js +19 -33
  80. package/dist/src/graph/clone-detector.js.map +1 -1
  81. package/dist/src/graph/clone-families.d.ts +2 -0
  82. package/dist/src/graph/clone-families.d.ts.map +1 -1
  83. package/dist/src/graph/clone-families.js +38 -10
  84. package/dist/src/graph/clone-families.js.map +1 -1
  85. package/dist/src/graph/codeowners-extended.d.ts +17 -0
  86. package/dist/src/graph/codeowners-extended.d.ts.map +1 -1
  87. package/dist/src/graph/codeowners-extended.js +42 -0
  88. package/dist/src/graph/codeowners-extended.js.map +1 -1
  89. package/dist/src/graph/codeowners-gitlab.d.ts +21 -0
  90. package/dist/src/graph/codeowners-gitlab.d.ts.map +1 -1
  91. package/dist/src/graph/codeowners-gitlab.js +37 -0
  92. package/dist/src/graph/codeowners-gitlab.js.map +1 -1
  93. package/dist/src/graph/codeowners.d.ts +10 -0
  94. package/dist/src/graph/codeowners.d.ts.map +1 -1
  95. package/dist/src/graph/codeowners.js +24 -2
  96. package/dist/src/graph/codeowners.js.map +1 -1
  97. package/dist/src/graph/complexity.d.ts +10 -0
  98. package/dist/src/graph/complexity.d.ts.map +1 -1
  99. package/dist/src/graph/complexity.js +93 -43
  100. package/dist/src/graph/complexity.js.map +1 -1
  101. package/dist/src/graph/cross-reference-findings.d.ts.map +1 -1
  102. package/dist/src/graph/cross-reference-findings.js +19 -2
  103. package/dist/src/graph/cross-reference-findings.js.map +1 -1
  104. package/dist/src/graph/cross-reference.d.ts +4 -0
  105. package/dist/src/graph/cross-reference.d.ts.map +1 -1
  106. package/dist/src/graph/cross-reference.js +23 -0
  107. package/dist/src/graph/cross-reference.js.map +1 -1
  108. package/dist/src/graph/cycle-detection.d.ts +2 -0
  109. package/dist/src/graph/cycle-detection.d.ts.map +1 -1
  110. package/dist/src/graph/cycle-detection.js +26 -3
  111. package/dist/src/graph/cycle-detection.js.map +1 -1
  112. package/dist/src/graph/cycles.d.ts +11 -3
  113. package/dist/src/graph/cycles.d.ts.map +1 -1
  114. package/dist/src/graph/cycles.js +121 -83
  115. package/dist/src/graph/cycles.js.map +1 -1
  116. package/dist/src/graph/dead-code.d.ts +23 -0
  117. package/dist/src/graph/dead-code.d.ts.map +1 -1
  118. package/dist/src/graph/dead-code.js +53 -13
  119. package/dist/src/graph/dead-code.js.map +1 -1
  120. package/dist/src/graph/dependency-closure.d.ts.map +1 -1
  121. package/dist/src/graph/dependency-closure.js +20 -4
  122. package/dist/src/graph/dependency-closure.js.map +1 -1
  123. package/dist/src/graph/dfs.d.ts +14 -0
  124. package/dist/src/graph/dfs.d.ts.map +1 -1
  125. package/dist/src/graph/dfs.js +61 -0
  126. package/dist/src/graph/dfs.js.map +1 -1
  127. package/dist/src/graph/duplication-stats.d.ts.map +1 -1
  128. package/dist/src/graph/duplication-stats.js +19 -3
  129. package/dist/src/graph/duplication-stats.js.map +1 -1
  130. package/dist/src/graph/edge-filter.d.ts +2 -0
  131. package/dist/src/graph/edge-filter.d.ts.map +1 -1
  132. package/dist/src/graph/edge-filter.js +26 -12
  133. package/dist/src/graph/edge-filter.js.map +1 -1
  134. package/dist/src/graph/health-score.d.ts +8 -0
  135. package/dist/src/graph/health-score.d.ts.map +1 -1
  136. package/dist/src/graph/health-score.js +48 -17
  137. package/dist/src/graph/health-score.js.map +1 -1
  138. package/dist/src/graph/hotspots.d.ts +7 -0
  139. package/dist/src/graph/hotspots.d.ts.map +1 -1
  140. package/dist/src/graph/hotspots.js +50 -5
  141. package/dist/src/graph/hotspots.js.map +1 -1
  142. package/dist/src/graph/import-chain.d.ts +17 -1
  143. package/dist/src/graph/import-chain.d.ts.map +1 -1
  144. package/dist/src/graph/import-chain.js +68 -19
  145. package/dist/src/graph/import-chain.js.map +1 -1
  146. package/dist/src/graph/loader.d.ts.map +1 -1
  147. package/dist/src/graph/loader.js +9 -10
  148. package/dist/src/graph/loader.js.map +1 -1
  149. package/dist/src/graph/maintainability.d.ts +8 -0
  150. package/dist/src/graph/maintainability.d.ts.map +1 -1
  151. package/dist/src/graph/maintainability.js +65 -10
  152. package/dist/src/graph/maintainability.js.map +1 -1
  153. package/dist/src/graph/mirrored-dirs.d.ts +4 -0
  154. package/dist/src/graph/mirrored-dirs.d.ts.map +1 -1
  155. package/dist/src/graph/mirrored-dirs.js +48 -13
  156. package/dist/src/graph/mirrored-dirs.js.map +1 -1
  157. package/dist/src/graph/narrowing.d.ts +15 -0
  158. package/dist/src/graph/narrowing.d.ts.map +1 -1
  159. package/dist/src/graph/narrowing.js +33 -0
  160. package/dist/src/graph/narrowing.js.map +1 -1
  161. package/dist/src/graph/node-search.d.ts +3 -0
  162. package/dist/src/graph/node-search.d.ts.map +1 -1
  163. package/dist/src/graph/node-search.js +37 -4
  164. package/dist/src/graph/node-search.js.map +1 -1
  165. package/dist/src/graph/normalize-id.d.ts +4 -0
  166. package/dist/src/graph/normalize-id.d.ts.map +1 -1
  167. package/dist/src/graph/normalize-id.js +25 -2
  168. package/dist/src/graph/normalize-id.js.map +1 -1
  169. package/dist/src/graph/pagerank.d.ts +7 -0
  170. package/dist/src/graph/pagerank.d.ts.map +1 -1
  171. package/dist/src/graph/pagerank.js +49 -2
  172. package/dist/src/graph/pagerank.js.map +1 -1
  173. package/dist/src/graph/prune.d.ts +31 -0
  174. package/dist/src/graph/prune.d.ts.map +1 -1
  175. package/dist/src/graph/prune.js +65 -0
  176. package/dist/src/graph/prune.js.map +1 -1
  177. package/dist/src/graph/regex-search.d.ts +10 -2
  178. package/dist/src/graph/regex-search.d.ts.map +1 -1
  179. package/dist/src/graph/regex-search.js +33 -4
  180. package/dist/src/graph/regex-search.js.map +1 -1
  181. package/dist/src/graph/regression.d.ts +5 -0
  182. package/dist/src/graph/regression.d.ts.map +1 -1
  183. package/dist/src/graph/regression.js +45 -23
  184. package/dist/src/graph/regression.js.map +1 -1
  185. package/dist/src/graph/ripple-impact.d.ts +27 -0
  186. package/dist/src/graph/ripple-impact.d.ts.map +1 -1
  187. package/dist/src/graph/ripple-impact.js +55 -7
  188. package/dist/src/graph/ripple-impact.js.map +1 -1
  189. package/dist/src/graph/risk-profile.d.ts +7 -0
  190. package/dist/src/graph/risk-profile.d.ts.map +1 -1
  191. package/dist/src/graph/risk-profile.js +82 -48
  192. package/dist/src/graph/risk-profile.js.map +1 -1
  193. package/dist/src/graph/shingle-filter.d.ts.map +1 -1
  194. package/dist/src/graph/shingle-filter.js +25 -26
  195. package/dist/src/graph/shingle-filter.js.map +1 -1
  196. package/dist/src/graph/statistics.d.ts +19 -0
  197. package/dist/src/graph/statistics.d.ts.map +1 -1
  198. package/dist/src/graph/statistics.js +53 -40
  199. package/dist/src/graph/statistics.js.map +1 -1
  200. package/dist/src/graph/stats.d.ts +2 -0
  201. package/dist/src/graph/stats.d.ts.map +1 -1
  202. package/dist/src/graph/stats.js +54 -8
  203. package/dist/src/graph/stats.js.map +1 -1
  204. package/dist/src/graph/subgraph.d.ts +5 -0
  205. package/dist/src/graph/subgraph.d.ts.map +1 -1
  206. package/dist/src/graph/subgraph.js +73 -9
  207. package/dist/src/graph/subgraph.js.map +1 -1
  208. package/dist/src/graph/topo-sort.d.ts +15 -0
  209. package/dist/src/graph/topo-sort.d.ts.map +1 -1
  210. package/dist/src/graph/topo-sort.js +83 -10
  211. package/dist/src/graph/topo-sort.js.map +1 -1
  212. package/dist/src/graph/wcc.d.ts +29 -0
  213. package/dist/src/graph/wcc.d.ts.map +1 -1
  214. package/dist/src/graph/wcc.js +122 -39
  215. package/dist/src/graph/wcc.js.map +1 -1
  216. package/dist/src/index.js +1 -1
  217. package/dist/src/index.js.map +1 -1
  218. package/dist/src/mcp-tools/api-impact.d.ts.map +1 -1
  219. package/dist/src/mcp-tools/api-impact.js +13 -6
  220. package/dist/src/mcp-tools/api-impact.js.map +1 -1
  221. package/dist/src/mcp-tools/context.d.ts.map +1 -1
  222. package/dist/src/mcp-tools/context.js +19 -29
  223. package/dist/src/mcp-tools/context.js.map +1 -1
  224. package/dist/src/mcp-tools/cypher.d.ts +11 -0
  225. package/dist/src/mcp-tools/cypher.d.ts.map +1 -1
  226. package/dist/src/mcp-tools/cypher.js +13 -1
  227. package/dist/src/mcp-tools/cypher.js.map +1 -1
  228. package/dist/src/mcp-tools/detect-changes.d.ts.map +1 -1
  229. package/dist/src/mcp-tools/detect-changes.js +35 -20
  230. package/dist/src/mcp-tools/detect-changes.js.map +1 -1
  231. package/dist/src/mcp-tools/explain.d.ts.map +1 -1
  232. package/dist/src/mcp-tools/explain.js +4 -1
  233. package/dist/src/mcp-tools/explain.js.map +1 -1
  234. package/dist/src/mcp-tools/graph-query.d.ts +2 -0
  235. package/dist/src/mcp-tools/graph-query.d.ts.map +1 -1
  236. package/dist/src/mcp-tools/graph-query.js +20 -3
  237. package/dist/src/mcp-tools/graph-query.js.map +1 -1
  238. package/dist/src/mcp-tools/group-list.d.ts +1 -0
  239. package/dist/src/mcp-tools/group-list.d.ts.map +1 -1
  240. package/dist/src/mcp-tools/group-list.js +7 -9
  241. package/dist/src/mcp-tools/group-list.js.map +1 -1
  242. package/dist/src/mcp-tools/impact.d.ts.map +1 -1
  243. package/dist/src/mcp-tools/impact.js +20 -59
  244. package/dist/src/mcp-tools/impact.js.map +1 -1
  245. package/dist/src/mcp-tools/neighbors.d.ts.map +1 -1
  246. package/dist/src/mcp-tools/neighbors.js +24 -33
  247. package/dist/src/mcp-tools/neighbors.js.map +1 -1
  248. package/dist/src/mcp-tools/query.d.ts +2 -0
  249. package/dist/src/mcp-tools/query.d.ts.map +1 -1
  250. package/dist/src/mcp-tools/query.js +1 -0
  251. package/dist/src/mcp-tools/query.js.map +1 -1
  252. package/dist/src/mcp-tools/rename.d.ts.map +1 -1
  253. package/dist/src/mcp-tools/rename.js +12 -7
  254. package/dist/src/mcp-tools/rename.js.map +1 -1
  255. package/dist/src/mcp-tools/route-map.d.ts.map +1 -1
  256. package/dist/src/mcp-tools/route-map.js +19 -23
  257. package/dist/src/mcp-tools/route-map.js.map +1 -1
  258. package/dist/src/mcp-tools/shape-check.d.ts.map +1 -1
  259. package/dist/src/mcp-tools/shape-check.js +17 -13
  260. package/dist/src/mcp-tools/shape-check.js.map +1 -1
  261. package/dist/src/parsers/loader.js +6 -5
  262. package/dist/src/parsers/loader.js.map +1 -1
  263. package/dist/src/pipeline/cluster-only.d.ts.map +1 -1
  264. package/dist/src/pipeline/cluster-only.js +8 -4
  265. package/dist/src/pipeline/cluster-only.js.map +1 -1
  266. package/dist/src/pipeline/orchestrator.d.ts.map +1 -1
  267. package/dist/src/pipeline/orchestrator.js +19 -0
  268. package/dist/src/pipeline/orchestrator.js.map +1 -1
  269. package/dist/src/pipeline/phases/boundary.d.ts +2 -0
  270. package/dist/src/pipeline/phases/boundary.d.ts.map +1 -1
  271. package/dist/src/pipeline/phases/boundary.js +31 -2
  272. package/dist/src/pipeline/phases/boundary.js.map +1 -1
  273. package/dist/src/pipeline/phases/communities.d.ts +14 -0
  274. package/dist/src/pipeline/phases/communities.d.ts.map +1 -1
  275. package/dist/src/pipeline/phases/communities.js +58 -5
  276. package/dist/src/pipeline/phases/communities.js.map +1 -1
  277. package/dist/src/pipeline/phases/contextual-proximity.d.ts.map +1 -1
  278. package/dist/src/pipeline/phases/contextual-proximity.js +21 -14
  279. package/dist/src/pipeline/phases/contextual-proximity.js.map +1 -1
  280. package/dist/src/pipeline/phases/cross-file.d.ts.map +1 -1
  281. package/dist/src/pipeline/phases/cross-file.js +35 -14
  282. package/dist/src/pipeline/phases/cross-file.js.map +1 -1
  283. package/dist/src/pipeline/phases/field-access.d.ts.map +1 -1
  284. package/dist/src/pipeline/phases/field-access.js +9 -4
  285. package/dist/src/pipeline/phases/field-access.js.map +1 -1
  286. package/dist/src/pipeline/phases/god-nodes.d.ts +4 -0
  287. package/dist/src/pipeline/phases/god-nodes.d.ts.map +1 -1
  288. package/dist/src/pipeline/phases/god-nodes.js +43 -9
  289. package/dist/src/pipeline/phases/god-nodes.js.map +1 -1
  290. package/dist/src/pipeline/phases/import-resolver.d.ts +11 -0
  291. package/dist/src/pipeline/phases/import-resolver.d.ts.map +1 -1
  292. package/dist/src/pipeline/phases/import-resolver.js +34 -7
  293. package/dist/src/pipeline/phases/import-resolver.js.map +1 -1
  294. package/dist/src/pipeline/phases/leiden.d.ts.map +1 -1
  295. package/dist/src/pipeline/phases/leiden.js +27 -19
  296. package/dist/src/pipeline/phases/leiden.js.map +1 -1
  297. package/dist/src/pipeline/phases/markdown.d.ts.map +1 -1
  298. package/dist/src/pipeline/phases/markdown.js +10 -5
  299. package/dist/src/pipeline/phases/markdown.js.map +1 -1
  300. package/dist/src/pipeline/phases/pdf-parse.d.ts.map +1 -1
  301. package/dist/src/pipeline/phases/pdf-parse.js +18 -3
  302. package/dist/src/pipeline/phases/pdf-parse.js.map +1 -1
  303. package/dist/src/pipeline/phases/processes.d.ts.map +1 -1
  304. package/dist/src/pipeline/phases/processes.js +26 -9
  305. package/dist/src/pipeline/phases/processes.js.map +1 -1
  306. package/dist/src/pipeline/phases/re-export-propagation.d.ts.map +1 -1
  307. package/dist/src/pipeline/phases/re-export-propagation.js +33 -14
  308. package/dist/src/pipeline/phases/re-export-propagation.js.map +1 -1
  309. package/dist/src/pipeline/phases/reachability.d.ts.map +1 -1
  310. package/dist/src/pipeline/phases/reachability.js +42 -26
  311. package/dist/src/pipeline/phases/reachability.js.map +1 -1
  312. package/dist/src/pipeline/phases/routes.d.ts.map +1 -1
  313. package/dist/src/pipeline/phases/routes.js +30 -21
  314. package/dist/src/pipeline/phases/routes.js.map +1 -1
  315. package/dist/src/pipeline/phases/scope-resolution.d.ts.map +1 -1
  316. package/dist/src/pipeline/phases/scope-resolution.js +93 -63
  317. package/dist/src/pipeline/phases/scope-resolution.js.map +1 -1
  318. package/dist/src/pipeline/runner.d.ts +4 -0
  319. package/dist/src/pipeline/runner.d.ts.map +1 -1
  320. package/dist/src/pipeline/runner.js +30 -11
  321. package/dist/src/pipeline/runner.js.map +1 -1
  322. package/dist/src/query/cypher-parser.d.ts +20 -0
  323. package/dist/src/query/cypher-parser.d.ts.map +1 -1
  324. package/dist/src/query/cypher-parser.js +73 -0
  325. package/dist/src/query/cypher-parser.js.map +1 -1
  326. package/dist/src/search/embed-batch.d.ts.map +1 -1
  327. package/dist/src/search/embed-batch.js +4 -1
  328. package/dist/src/search/embed-batch.js.map +1 -1
  329. package/dist/src/search/hybrid-query.d.ts +4 -0
  330. package/dist/src/search/hybrid-query.d.ts.map +1 -1
  331. package/dist/src/search/hybrid-query.js +5 -1
  332. package/dist/src/search/hybrid-query.js.map +1 -1
  333. package/dist/src/staleness/git-staleness.js +3 -3
  334. package/dist/src/staleness/git-staleness.js.map +1 -1
  335. package/dist/src/storage/db.d.ts.map +1 -1
  336. package/dist/src/storage/db.js +12 -0
  337. package/dist/src/storage/db.js.map +1 -1
  338. package/dist/src/storage/edge-store.d.ts +2 -0
  339. package/dist/src/storage/edge-store.d.ts.map +1 -1
  340. package/dist/src/storage/edge-store.js +26 -1
  341. package/dist/src/storage/edge-store.js.map +1 -1
  342. package/dist/src/storage/embedding-store.d.ts +16 -0
  343. package/dist/src/storage/embedding-store.d.ts.map +1 -1
  344. package/dist/src/storage/embedding-store.js +26 -2
  345. package/dist/src/storage/embedding-store.js.map +1 -1
  346. package/dist/src/storage/file-cache.d.ts +5 -0
  347. package/dist/src/storage/file-cache.d.ts.map +1 -1
  348. package/dist/src/storage/file-cache.js +35 -9
  349. package/dist/src/storage/file-cache.js.map +1 -1
  350. package/dist/src/storage/fts-store.d.ts +4 -0
  351. package/dist/src/storage/fts-store.d.ts.map +1 -1
  352. package/dist/src/storage/fts-store.js +8 -3
  353. package/dist/src/storage/fts-store.js.map +1 -1
  354. package/dist/src/storage/node-store.d.ts +2 -0
  355. package/dist/src/storage/node-store.d.ts.map +1 -1
  356. package/dist/src/storage/node-store.js +28 -1
  357. package/dist/src/storage/node-store.js.map +1 -1
  358. package/dist/tsconfig.tsbuildinfo +1 -1
  359. package/package.json +1 -1
  360. package/src/__tests__/pipeline/runner.parallel.test.ts +2 -2
  361. package/src/analysis/churn-cache.ts +18 -7
  362. package/src/analysis/churn-trend.ts +35 -6
  363. package/src/analysis/cluster-quality.ts +20 -11
  364. package/src/analysis/coverage-gaps.ts +44 -1
  365. package/src/analysis/dep-classification.ts +34 -0
  366. package/src/analysis/dependency-health.ts +25 -0
  367. package/src/analysis/duplicate-exports.ts +40 -4
  368. package/src/analysis/feature-flags.ts +37 -17
  369. package/src/analysis/node-similarity.ts +45 -4
  370. package/src/analysis/private-type-leaks.ts +49 -2
  371. package/src/analysis/project-state.ts +19 -9
  372. package/src/analysis/suppressions.ts +4 -1
  373. package/src/analysis/trace.ts +73 -54
  374. package/src/analysis/unused-class-members.ts +8 -1
  375. package/src/analysis/worker-pool.ts +40 -8
  376. package/src/cli/augment.ts +10 -3
  377. package/src/export/sarif.ts +5 -5
  378. package/src/graph/analyzer.ts +26 -2
  379. package/src/graph/author-analytics.ts +45 -5
  380. package/src/graph/baseline.ts +78 -24
  381. package/src/graph/clone-detector.ts +22 -36
  382. package/src/graph/clone-families.ts +34 -10
  383. package/src/graph/codeowners-extended.ts +54 -0
  384. package/src/graph/codeowners-gitlab.ts +46 -0
  385. package/src/graph/codeowners.ts +33 -2
  386. package/src/graph/complexity.ts +102 -47
  387. package/src/graph/cross-reference-findings.ts +23 -2
  388. package/src/graph/cross-reference.ts +26 -0
  389. package/src/graph/cycle-detection.ts +22 -3
  390. package/src/graph/cycles.ts +155 -96
  391. package/src/graph/dead-code.ts +76 -17
  392. package/src/graph/dependency-closure.ts +24 -7
  393. package/src/graph/dfs.ts +78 -0
  394. package/src/graph/duplication-stats.ts +17 -6
  395. package/src/graph/edge-filter.ts +26 -12
  396. package/src/graph/health-score.ts +47 -17
  397. package/src/graph/hotspots.ts +57 -8
  398. package/src/graph/import-chain.ts +89 -19
  399. package/src/graph/loader.ts +10 -7
  400. package/src/graph/maintainability.ts +72 -22
  401. package/src/graph/mirrored-dirs.ts +45 -14
  402. package/src/graph/narrowing.ts +44 -0
  403. package/src/graph/node-search.ts +49 -4
  404. package/src/graph/normalize-id.ts +26 -2
  405. package/src/graph/pagerank.ts +69 -2
  406. package/src/graph/prune.ts +76 -0
  407. package/src/graph/regex-search.ts +38 -4
  408. package/src/graph/regression.ts +48 -23
  409. package/src/graph/ripple-impact.ts +71 -11
  410. package/src/graph/risk-profile.ts +98 -53
  411. package/src/graph/shingle-filter.ts +33 -27
  412. package/src/graph/statistics.ts +73 -45
  413. package/src/graph/stats.ts +50 -16
  414. package/src/graph/subgraph.ts +96 -25
  415. package/src/graph/topo-sort.ts +101 -16
  416. package/src/graph/wcc.ts +146 -37
  417. package/src/index.ts +1 -1
  418. package/src/mcp-tools/api-impact.ts +13 -6
  419. package/src/mcp-tools/context.ts +31 -40
  420. package/src/mcp-tools/cypher.ts +14 -1
  421. package/src/mcp-tools/detect-changes.ts +38 -23
  422. package/src/mcp-tools/explain.ts +4 -1
  423. package/src/mcp-tools/graph-query.ts +24 -3
  424. package/src/mcp-tools/group-list.ts +9 -11
  425. package/src/mcp-tools/impact.ts +26 -66
  426. package/src/mcp-tools/neighbors.ts +34 -39
  427. package/src/mcp-tools/query.ts +3 -0
  428. package/src/mcp-tools/rename.ts +9 -8
  429. package/src/mcp-tools/route-map.ts +21 -23
  430. package/src/mcp-tools/shape-check.ts +18 -14
  431. package/src/parsers/loader.ts +5 -5
  432. package/src/pipeline/cluster-only.ts +8 -4
  433. package/src/pipeline/orchestrator.ts +22 -0
  434. package/src/pipeline/phases/boundary.ts +40 -2
  435. package/src/pipeline/phases/communities.ts +66 -5
  436. package/src/pipeline/phases/contextual-proximity.ts +15 -14
  437. package/src/pipeline/phases/cross-file.ts +35 -14
  438. package/src/pipeline/phases/field-access.ts +11 -4
  439. package/src/pipeline/phases/god-nodes.ts +47 -10
  440. package/src/pipeline/phases/import-resolver.ts +37 -5
  441. package/src/pipeline/phases/leiden.ts +25 -18
  442. package/src/pipeline/phases/markdown.ts +12 -7
  443. package/src/pipeline/phases/pdf-parse.ts +19 -2
  444. package/src/pipeline/phases/processes.ts +27 -15
  445. package/src/pipeline/phases/re-export-propagation.ts +40 -18
  446. package/src/pipeline/phases/reachability.ts +39 -24
  447. package/src/pipeline/phases/routes.ts +32 -21
  448. package/src/pipeline/phases/scope-resolution.ts +110 -71
  449. package/src/pipeline/runner.ts +28 -11
  450. package/src/query/cypher-parser.ts +83 -0
  451. package/src/search/embed-batch.ts +4 -1
  452. package/src/search/hybrid-query.ts +11 -1
  453. package/src/staleness/git-staleness.ts +3 -3
  454. package/src/storage/db.ts +11 -0
  455. package/src/storage/edge-store.ts +34 -1
  456. package/src/storage/embedding-store.ts +38 -6
  457. package/src/storage/file-cache.ts +35 -21
  458. package/src/storage/fts-store.ts +12 -3
  459. package/src/storage/node-store.ts +39 -1
  460. package/src/coverage/runtime.ts +0 -249
@@ -31,6 +31,10 @@ export interface HybridResult extends RankedResult {
31
31
  filePath: string | null;
32
32
  label: string;
33
33
  score: number;
34
+ /** First line of the symbol in its source file (1-based, null if unknown). */
35
+ startLine?: number | null;
36
+ /** Last line of the symbol in its source file (1-based, null if unknown). */
37
+ endLine?: number | null;
34
38
  }
35
39
 
36
40
  /**
@@ -61,6 +65,8 @@ export async function hybridQuery(
61
65
  filePath: r.filePath,
62
66
  label: r.label,
63
67
  score: r.rank,
68
+ startLine: r.startLine,
69
+ endLine: r.endLine,
64
70
  }));
65
71
 
66
72
  // ── Decide whether to add vector results ──────────────────────────────────
@@ -121,7 +127,7 @@ export async function hybridQuery(
121
127
  const placeholders = unknownIds.map(() => '?').join(',');
122
128
  const rows = db
123
129
  .prepare(
124
- `SELECT id, name, norm_label, file_path, label FROM nodes WHERE id IN (${placeholders})`,
130
+ `SELECT id, name, norm_label, file_path, label, start_line, end_line FROM nodes WHERE id IN (${placeholders})`,
125
131
  )
126
132
  .all(...unknownIds) as {
127
133
  id: string;
@@ -129,6 +135,8 @@ export async function hybridQuery(
129
135
  norm_label: string;
130
136
  file_path: string | null;
131
137
  label: string;
138
+ start_line: number | null;
139
+ end_line: number | null;
132
140
  }[];
133
141
 
134
142
  const rowMap = new Map(rows.map((r) => [r.id, r]));
@@ -140,6 +148,8 @@ export async function hybridQuery(
140
148
  item.normLabel = row.norm_label;
141
149
  item.filePath = row.file_path;
142
150
  item.label = row.label;
151
+ (item as HybridResult).startLine = row.start_line;
152
+ (item as HybridResult).endLine = row.end_line;
143
153
  }
144
154
  }
145
155
  }
@@ -24,12 +24,12 @@ export function checkStaleness(db: MonographDb, repoPath: string): StalenessRepo
24
24
  return { isStale: false, indexedAt: null, indexedCommit: null, currentCommit: null, changedSince: [], staleSince: null };
25
25
  }
26
26
 
27
- // Display short SHA (7 chars) for the interface field; compare full SHAs for correctness
27
+ // Display short SHA (7 chars) for the interface field; compare short SHAs for staleness
28
28
  const indexedCommitShort = indexedCommitFull ? indexedCommitFull.slice(0, 7) : null;
29
29
  const currentCommitShort = currentCommit ? currentCommit.slice(0, 7) : null;
30
30
 
31
- // 3. If no stored commit or full SHAs match, not stale
32
- if (!indexedCommitFull || indexedCommitFull === currentCommit) {
31
+ // 3. If no stored commit or short SHAs match, not stale
32
+ if (!indexedCommitFull || indexedCommitShort === currentCommitShort) {
33
33
  return { isStale: false, indexedAt: null, indexedCommit: indexedCommitShort, currentCommit: currentCommitShort, changedSince: [], staleSince: null };
34
34
  }
35
35
 
package/src/storage/db.ts CHANGED
@@ -37,6 +37,17 @@ function applyMigrations(db: MonographDb): void {
37
37
  db.exec(FTS_SYNC_TRIGGERS);
38
38
  db.exec(CREATE_EMBEDDINGS);
39
39
  db.exec(CREATE_WIKI_PAGES);
40
+
41
+ // Schema version table — tracks incremental column additions.
42
+ db.exec(`CREATE TABLE IF NOT EXISTS schema_version (version INTEGER PRIMARY KEY)`);
43
+ const row = db.prepare('SELECT MAX(version) AS v FROM schema_version').get() as { v: number | null };
44
+ const current = row?.v ?? 0;
45
+
46
+ if (current < 1) {
47
+ // v1: added `reason TEXT` to edges
48
+ try { db.exec('ALTER TABLE edges ADD COLUMN reason TEXT'); } catch { /* already present */ }
49
+ db.prepare('INSERT OR REPLACE INTO schema_version VALUES (1)').run();
50
+ }
40
51
  }
41
52
 
42
53
  /** Write to a .tmp file then rename for atomic replacement. */
@@ -17,15 +17,48 @@ export function insertEdge(db: Database.Database, edge: MonographEdge): void {
17
17
  );
18
18
  }
19
19
 
20
+ const INSERT_EDGE_SQL = `
21
+ INSERT OR REPLACE INTO edges (id, source_id, target_id, relation, confidence, confidence_score, reason, evidence)
22
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
23
+ `;
24
+
20
25
  export function insertEdges(db: Database.Database, edges: MonographEdge[]): void {
26
+ if (edges.length === 0) return;
27
+ // Prepare once and reuse across all rows — avoids N redundant prepare calls
28
+ const stmt = db.prepare(INSERT_EDGE_SQL);
21
29
  const insertMany = db.transaction((rows: MonographEdge[]) => {
22
30
  for (const e of rows) {
23
- insertEdge(db, e);
31
+ stmt.run(
32
+ e.id,
33
+ e.sourceId,
34
+ e.targetId,
35
+ e.relation,
36
+ e.confidence,
37
+ e.confidenceScore,
38
+ e.reason ?? null,
39
+ e.evidence != null ? JSON.stringify(e.evidence) : null,
40
+ );
24
41
  }
25
42
  });
26
43
  insertMany(edges);
27
44
  }
28
45
 
46
+ /** Batch-fetch edges for multiple source IDs using a single SQL IN query. Batches at 50 IDs. */
47
+ export function getEdgesForSources(db: Database.Database, sourceIds: string[]): MonographEdge[] {
48
+ if (sourceIds.length === 0) return [];
49
+ const BATCH = 50;
50
+ const results: MonographEdge[] = [];
51
+ for (let i = 0; i < sourceIds.length; i += BATCH) {
52
+ const chunk = sourceIds.slice(i, i + BATCH);
53
+ const placeholders = chunk.map(() => '?').join(',');
54
+ const rows = db
55
+ .prepare(`SELECT * FROM edges WHERE source_id IN (${placeholders})`)
56
+ .all(...chunk) as Record<string, unknown>[];
57
+ for (const row of rows) results.push(rowToEdge(row));
58
+ }
59
+ return results;
60
+ }
61
+
29
62
  export function getEdgesForSource(db: Database.Database, sourceId: string): MonographEdge[] {
30
63
  const rows = db
31
64
  .prepare('SELECT * FROM edges WHERE source_id = ?')
@@ -6,18 +6,25 @@ import type Database from 'better-sqlite3';
6
6
  * Vectors are stored as BLOBs (raw Float32Array bytes) and reconstructed on read.
7
7
  */
8
8
 
9
+ /**
10
+ * Ensure the embeddings table has the content_hash column introduced in a later
11
+ * schema version. Call this ONCE before a batch of upserts rather than inside
12
+ * upsertEmbedding itself to avoid running ALTER TABLE on every row write.
13
+ */
14
+ export function ensureEmbeddingSchema(db: Database.Database): void {
15
+ try {
16
+ db.exec('ALTER TABLE embeddings ADD COLUMN content_hash TEXT');
17
+ } catch {
18
+ // Column already exists — ignore.
19
+ }
20
+ }
21
+
9
22
  export function upsertEmbedding(
10
23
  db: Database.Database,
11
24
  nodeId: string,
12
25
  vector: Float32Array,
13
26
  contentHash?: string,
14
27
  ): void {
15
- // Migrate existing DBs that may not have the content_hash column yet.
16
- try {
17
- db.exec('ALTER TABLE embeddings ADD COLUMN content_hash TEXT');
18
- } catch {
19
- // Column already exists — ignore.
20
- }
21
28
  const buf = Buffer.from(vector.buffer, vector.byteOffset, vector.byteLength);
22
29
  db
23
30
  .prepare(
@@ -26,6 +33,31 @@ export function upsertEmbedding(
26
33
  .run(nodeId, buf, contentHash ?? null);
27
34
  }
28
35
 
36
+ /**
37
+ * Bulk-upsert multiple embeddings in a single transaction.
38
+ * Calls ensureEmbeddingSchema once before writing, then wraps all inserts in
39
+ * a transaction for 10-100x faster throughput vs per-row upsertEmbedding calls.
40
+ */
41
+ export function batchUpsertEmbeddings(
42
+ db: Database.Database,
43
+ entries: Array<{ nodeId: string; vector: Float32Array; contentHash?: string }>,
44
+ ): void {
45
+ if (entries.length === 0) return;
46
+ ensureEmbeddingSchema(db);
47
+ const stmt = db.prepare(
48
+ 'INSERT OR REPLACE INTO embeddings (node_id, vector, content_hash) VALUES (?, ?, ?)',
49
+ );
50
+ const insertMany = db.transaction(
51
+ (rows: Array<{ nodeId: string; vector: Float32Array; contentHash?: string }>) => {
52
+ for (const e of rows) {
53
+ const buf = Buffer.from(e.vector.buffer, e.vector.byteOffset, e.vector.byteLength);
54
+ stmt.run(e.nodeId, buf, e.contentHash ?? null);
55
+ }
56
+ },
57
+ );
58
+ insertMany(entries);
59
+ }
60
+
29
61
  export function getEmbeddingContentHash(db: Database.Database, nodeId: string): string | null {
30
62
  const row = db
31
63
  .prepare('SELECT content_hash FROM embeddings WHERE node_id = ?')
@@ -27,34 +27,48 @@ export function updateFileCache(db: MonographDb, entry: FileCacheEntry): void {
27
27
  `).run(entry.filePath, entry.contentHash, entry.lastParsed, entry.nodeCount, entry.edgeCount);
28
28
  }
29
29
 
30
+ /**
31
+ * Bulk-upsert multiple file cache entries in a single transaction.
32
+ * Prefer this over calling updateFileCache in a loop for pipeline batch writes.
33
+ */
34
+ export function batchUpdateFileCache(db: MonographDb, entries: FileCacheEntry[]): void {
35
+ if (entries.length === 0) return;
36
+ const stmt = db.prepare(`
37
+ INSERT OR REPLACE INTO file_cache (file_path, content_hash, last_parsed, node_count, edge_count)
38
+ VALUES (?, ?, ?, ?, ?)
39
+ `);
40
+ const insertMany = db.transaction((rows: FileCacheEntry[]) => {
41
+ for (const e of rows) {
42
+ stmt.run(e.filePath, e.contentHash, e.lastParsed, e.nodeCount, e.edgeCount);
43
+ }
44
+ });
45
+ insertMany(entries);
46
+ }
47
+
30
48
  export function getFileCacheStats(db: MonographDb): {
31
49
  totalCached: number;
32
50
  hitRate: number;
33
51
  stalePaths: string[];
34
52
  } {
35
- const totalCached = (db.prepare('SELECT COUNT(*) as c FROM file_cache').get() as { c: number }).c;
36
-
37
- const totalNodes = (db.prepare(
38
- "SELECT COUNT(DISTINCT file_path) as c FROM nodes WHERE file_path IS NOT NULL"
39
- ).get() as { c: number }).c;
40
-
41
- const hitRate = totalNodes > 0 ? totalCached / totalNodes : 0;
42
-
43
- // Find stale paths: cached entries whose file no longer exists in nodes table
44
- const cachedPaths = db.prepare('SELECT file_path, content_hash FROM file_cache').all() as {
45
- file_path: string;
46
- content_hash: string;
47
- }[];
53
+ // Single query: count cached entries and distinct indexed file paths in one pass.
54
+ const counts = db.prepare(`
55
+ SELECT
56
+ (SELECT COUNT(*) FROM file_cache) AS total_cached,
57
+ (SELECT COUNT(DISTINCT file_path) FROM nodes WHERE file_path IS NOT NULL) AS total_indexed
58
+ `).get() as { total_cached: number; total_indexed: number };
48
59
 
49
- const indexedPaths = new Set<string>(
50
- (db.prepare("SELECT DISTINCT file_path FROM nodes WHERE file_path IS NOT NULL").all() as {
51
- file_path: string;
52
- }[]).map(r => r.file_path)
53
- );
60
+ const totalCached = counts.total_cached;
61
+ const hitRate = counts.total_indexed > 0 ? totalCached / counts.total_indexed : 0;
54
62
 
55
- const stalePaths = cachedPaths
56
- .filter(r => !indexedPaths.has(r.file_path))
57
- .map(r => r.file_path);
63
+ // Find stale paths via SQL LEFT JOIN — avoids loading all rows into JS memory.
64
+ const stalePaths = (db.prepare(`
65
+ SELECT fc.file_path
66
+ FROM file_cache fc
67
+ LEFT JOIN (
68
+ SELECT DISTINCT file_path FROM nodes WHERE file_path IS NOT NULL
69
+ ) n ON fc.file_path = n.file_path
70
+ WHERE n.file_path IS NULL
71
+ `).all() as { file_path: string }[]).map(r => r.file_path);
58
72
 
59
73
  return { totalCached, hitRate, stalePaths };
60
74
  }
@@ -7,6 +7,10 @@ export interface FtsResult {
7
7
  filePath: string | null;
8
8
  label: string;
9
9
  rank: number;
10
+ /** First line of the symbol in its source file (1-based, null if unknown). */
11
+ startLine: number | null;
12
+ /** Last line of the symbol in its source file (1-based, null if unknown). */
13
+ endLine: number | null;
10
14
  }
11
15
 
12
16
  export function ftsSearch(
@@ -25,7 +29,7 @@ export function ftsSearch(
25
29
 
26
30
  let matchSql = `
27
31
  SELECT n.id, n.name, n.norm_label, n.file_path, n.label,
28
- nodes_fts.rank
32
+ n.start_line, n.end_line, nodes_fts.rank
29
33
  FROM nodes_fts
30
34
  JOIN nodes n ON n.rowid = nodes_fts.rowid
31
35
  WHERE nodes_fts MATCH ?
@@ -55,7 +59,7 @@ export function ftsSearch(
55
59
  const likePattern = `%${escapedQuery}%`;
56
60
  let likeSql = `
57
61
  SELECT n.id, n.name, n.norm_label, n.file_path, n.label,
58
- 0 AS rank
62
+ n.start_line, n.end_line, 0 AS rank
59
63
  FROM nodes n
60
64
  WHERE (n.name LIKE ? OR n.norm_label LIKE ? OR n.file_path LIKE ?)
61
65
  `;
@@ -89,6 +93,8 @@ export function ftsSearch(
89
93
  filePath: (r.file_path as string | null) ?? null,
90
94
  label: r.label as string,
91
95
  rank: r.rank as number,
96
+ startLine: (r.start_line as number | null) ?? null,
97
+ endLine: (r.end_line as number | null) ?? null,
92
98
  }));
93
99
  }
94
100
 
@@ -183,7 +189,8 @@ export function hybridSearch(
183
189
  const escapedSafeQuery = safeQuery.replace(/%/g, '\\%').replace(/_/g, '\\_');
184
190
  const likePattern = `%${escapedSafeQuery}%`;
185
191
  let likeSql = `
186
- SELECT n.id, n.name, n.norm_label, n.file_path, n.label
192
+ SELECT n.id, n.name, n.norm_label, n.file_path, n.label,
193
+ n.start_line, n.end_line
187
194
  FROM nodes n
188
195
  WHERE (n.name LIKE ? OR n.norm_label LIKE ?)
189
196
  `;
@@ -211,6 +218,8 @@ export function hybridSearch(
211
218
  filePath: (r.file_path as string | null) ?? null,
212
219
  label: lbl,
213
220
  rank: 0,
221
+ startLine: (r.start_line as number | null) ?? null,
222
+ endLine: (r.end_line as number | null) ?? null,
214
223
  combinedScore: combined,
215
224
  matchStrategy: 'like',
216
225
  });
@@ -23,15 +23,53 @@ export function insertNode(db: Database.Database, node: MonographNode): void {
23
23
  );
24
24
  }
25
25
 
26
+ const INSERT_SQL = `
27
+ INSERT OR REPLACE INTO nodes
28
+ (id, label, name, norm_label, file_path, start_line, end_line,
29
+ community_id, is_exported, language, properties)
30
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
31
+ `;
32
+
26
33
  export function insertNodes(db: Database.Database, nodes: MonographNode[]): void {
34
+ if (nodes.length === 0) return;
35
+ // Prepare once and reuse across all rows — avoids N redundant prepare calls
36
+ const stmt = db.prepare(INSERT_SQL);
27
37
  const insertMany = db.transaction((rows: MonographNode[]) => {
28
38
  for (const n of rows) {
29
- insertNode(db, n);
39
+ stmt.run(
40
+ n.id,
41
+ n.label,
42
+ n.name,
43
+ n.normLabel ?? toNormLabel(n.name),
44
+ n.filePath ?? null,
45
+ n.startLine ?? null,
46
+ n.endLine ?? null,
47
+ n.communityId ?? null,
48
+ n.isExported ? 1 : 0,
49
+ n.language ?? null,
50
+ n.properties ? JSON.stringify(n.properties) : null,
51
+ );
30
52
  }
31
53
  });
32
54
  insertMany(nodes);
33
55
  }
34
56
 
57
+ /** Batch-fetch multiple nodes by ID using a single SQL IN query. Batches at 50 IDs. */
58
+ export function getNodesByIds(db: Database.Database, ids: string[]): MonographNode[] {
59
+ if (ids.length === 0) return [];
60
+ const BATCH = 50;
61
+ const results: MonographNode[] = [];
62
+ for (let i = 0; i < ids.length; i += BATCH) {
63
+ const chunk = ids.slice(i, i + BATCH);
64
+ const placeholders = chunk.map(() => '?').join(',');
65
+ const rows = db
66
+ .prepare(`SELECT * FROM nodes WHERE id IN (${placeholders})`)
67
+ .all(...chunk) as Record<string, unknown>[];
68
+ for (const row of rows) results.push(rowToNode(row));
69
+ }
70
+ return results;
71
+ }
72
+
35
73
  export function getNode(db: Database.Database, id: string): MonographNode | undefined {
36
74
  const row = db
37
75
  .prepare('SELECT * FROM nodes WHERE id = ?')
@@ -1,249 +0,0 @@
1
- import type { MonographDb } from '../storage/db.js';
2
-
3
- export type RuntimeClassification =
4
- | 'SafeToDelete' // graph-dead AND not covered at runtime
5
- | 'ReviewRequired' // graph-dead BUT has runtime coverage
6
- | 'LowTraffic' // covered but <5% call rate
7
- | 'Active' // actively covered
8
- | 'CoverageUnavailable'; // in graph but no coverage data for this file
9
-
10
- export interface FunctionCoverageEntry {
11
- nodeId: string;
12
- name: string;
13
- filePath: string | null;
14
- startLine: number | null;
15
- covered: boolean;
16
- callCount: number;
17
- classification: RuntimeClassification;
18
- }
19
-
20
- export interface RuntimeCoverageReport {
21
- entries: FunctionCoverageEntry[];
22
- safeToDelete: number;
23
- reviewRequired: number;
24
- active: number;
25
- coverageUnavailable: number;
26
- blastRadius: string[]; // active entry-point file paths (covered + reachable)
27
- }
28
-
29
- // V8 coverage format (subset of what Node/Chrome produces)
30
- export interface V8CoverageFunction {
31
- functionName: string;
32
- url: string; // file URL like 'file:///path/to/file.js'
33
- ranges: Array<{ startOffset: number; endOffset: number; count: number }>;
34
- }
35
-
36
- export interface V8Coverage {
37
- result: Array<{
38
- url: string;
39
- functions: V8CoverageFunction[];
40
- }>;
41
- }
42
-
43
- interface CoverageMapEntry {
44
- covered: boolean;
45
- callCount: number;
46
- }
47
-
48
- interface SymbolNodeRow {
49
- id: string;
50
- name: string;
51
- file_path: string | null;
52
- start_line: number | null;
53
- properties: string | null;
54
- }
55
-
56
- export function analyzeRuntimeCoverage(
57
- db: MonographDb,
58
- coverage: V8Coverage,
59
- repoRoot?: string
60
- ): RuntimeCoverageReport {
61
- // Build a map: filePath -> Map<functionName, CoverageMapEntry>
62
- const fileCoverageMap = new Map<string, Map<string, CoverageMapEntry>>();
63
-
64
- for (const scriptCov of coverage.result) {
65
- let filePath = scriptCov.url;
66
-
67
- // Strip file:// prefix
68
- if (filePath.startsWith('file://')) {
69
- filePath = filePath.slice('file://'.length);
70
- }
71
-
72
- // Resolve relative to repoRoot if provided and path is relative
73
- if (repoRoot && !filePath.startsWith('/')) {
74
- filePath = repoRoot.replace(/\/$/, '') + '/' + filePath;
75
- }
76
-
77
- const fnMap = fileCoverageMap.get(filePath) ?? new Map<string, CoverageMapEntry>();
78
-
79
- for (const fn of scriptCov.functions) {
80
- const covered = fn.ranges.some(r => r.count > 0);
81
- const callCount = fn.ranges.reduce((max, r) => Math.max(max, r.count), 0);
82
- const existing = fnMap.get(fn.functionName);
83
- if (!existing || callCount > existing.callCount) {
84
- fnMap.set(fn.functionName, { covered, callCount });
85
- }
86
- }
87
-
88
- fileCoverageMap.set(filePath, fnMap);
89
- }
90
-
91
- // Helper: normalize a file path for lookup
92
- function normalizeForLookup(fp: string): string {
93
- return fp.replace(/\\/g, '/');
94
- }
95
-
96
- // Build alternate lookup: basename -> filePath list
97
- const filePathIndex = new Map<string, string[]>();
98
- for (const fp of fileCoverageMap.keys()) {
99
- const norm = normalizeForLookup(fp);
100
- filePathIndex.set(norm, (filePathIndex.get(norm) ?? []).concat(fp));
101
- // Also store by just the filename for relative matching
102
- const parts = norm.split('/');
103
- const base = parts[parts.length - 1];
104
- if (base && base !== norm) {
105
- filePathIndex.set(base, (filePathIndex.get(base) ?? []).concat(fp));
106
- }
107
- }
108
-
109
- // Compute total call count across all functions for LowTraffic threshold
110
- let totalCallCount = 0;
111
- for (const fnMap of fileCoverageMap.values()) {
112
- for (const entry of fnMap.values()) {
113
- totalCallCount += entry.callCount;
114
- }
115
- }
116
- const lowTrafficThreshold = totalCallCount * 0.05;
117
-
118
- // Query all Symbol nodes that are Functions/Methods
119
- const rows = db.prepare(`
120
- SELECT id, name, file_path, start_line, properties
121
- FROM nodes
122
- WHERE label IN ('Function', 'Method')
123
- `).all() as SymbolNodeRow[];
124
-
125
- const entries: FunctionCoverageEntry[] = [];
126
-
127
- for (const row of rows) {
128
- const props = row.properties ? JSON.parse(row.properties) : {};
129
- const reachabilityRole: string = props.reachabilityRole ?? '';
130
-
131
- let fnEntry: CoverageMapEntry | null = null;
132
-
133
- if (row.file_path) {
134
- const normFilePath = normalizeForLookup(row.file_path);
135
-
136
- // Try exact match first
137
- const exactFnMap = fileCoverageMap.get(normFilePath) ?? fileCoverageMap.get(row.file_path);
138
- if (exactFnMap) {
139
- const match = exactFnMap.get(row.name);
140
- if (match) fnEntry = match;
141
- // If no exact function name match, aggregate all functions for this file
142
- if (!fnEntry) {
143
- // Best-effort: file is covered if any function is covered
144
- let fileCovered = false;
145
- let fileMaxCount = 0;
146
- for (const e of exactFnMap.values()) {
147
- if (e.covered) fileCovered = true;
148
- fileMaxCount = Math.max(fileMaxCount, e.callCount);
149
- }
150
- if (exactFnMap.size > 0) {
151
- fnEntry = { covered: fileCovered, callCount: fileMaxCount };
152
- }
153
- }
154
- } else {
155
- // Try relative path matching
156
- for (const [covPath, fnMap] of fileCoverageMap.entries()) {
157
- if (covPath.endsWith(normFilePath) || normFilePath.endsWith(covPath.replace(/.*\//, ''))) {
158
- const match = fnMap.get(row.name);
159
- if (match) { fnEntry = match; break; }
160
- // Aggregate file-level coverage
161
- let fileCovered = false;
162
- let fileMaxCount = 0;
163
- for (const e of fnMap.values()) {
164
- if (e.covered) fileCovered = true;
165
- fileMaxCount = Math.max(fileMaxCount, e.callCount);
166
- }
167
- if (fnMap.size > 0) {
168
- fnEntry = { covered: fileCovered, callCount: fileMaxCount };
169
- break;
170
- }
171
- }
172
- }
173
- }
174
- }
175
-
176
- let classification: RuntimeClassification;
177
- let covered = false;
178
- let callCount = 0;
179
-
180
- if (!fnEntry) {
181
- classification = 'CoverageUnavailable';
182
- } else {
183
- covered = fnEntry.covered;
184
- callCount = fnEntry.callCount;
185
-
186
- if (!covered && reachabilityRole === 'unreachable') {
187
- classification = 'SafeToDelete';
188
- } else if (covered && reachabilityRole === 'unreachable') {
189
- classification = 'ReviewRequired';
190
- } else if (covered && callCount < lowTrafficThreshold) {
191
- classification = 'LowTraffic';
192
- } else if (covered) {
193
- classification = 'Active';
194
- } else {
195
- // not covered, not unreachable — treat as unavailable
196
- classification = 'CoverageUnavailable';
197
- }
198
- }
199
-
200
- entries.push({
201
- nodeId: row.id,
202
- name: row.name,
203
- filePath: row.file_path,
204
- startLine: row.start_line,
205
- covered,
206
- callCount,
207
- classification,
208
- });
209
- }
210
-
211
- // Aggregate counts
212
- let safeToDelete = 0;
213
- let reviewRequired = 0;
214
- let active = 0;
215
- let coverageUnavailable = 0;
216
-
217
- const blastRadiusSet = new Set<string>();
218
-
219
- for (const entry of entries) {
220
- switch (entry.classification) {
221
- case 'SafeToDelete': safeToDelete++; break;
222
- case 'ReviewRequired': reviewRequired++; break;
223
- case 'Active': active++; break;
224
- case 'CoverageUnavailable': coverageUnavailable++; break;
225
- }
226
-
227
- // BlastRadius: Active nodes with reachabilityRole === 'runtime'
228
- if (entry.classification === 'Active' && entry.filePath) {
229
- const nodeProps = entries.find(e => e.nodeId === entry.nodeId);
230
- // Re-query to get reachabilityRole for blast radius check
231
- const nodeRow = db.prepare(`SELECT properties FROM nodes WHERE id = ?`).get(entry.nodeId) as { properties: string | null } | undefined;
232
- if (nodeRow) {
233
- const p = nodeRow.properties ? JSON.parse(nodeRow.properties) : {};
234
- if (p.reachabilityRole === 'runtime') {
235
- blastRadiusSet.add(entry.filePath);
236
- }
237
- }
238
- }
239
- }
240
-
241
- return {
242
- entries,
243
- safeToDelete,
244
- reviewRequired,
245
- active,
246
- coverageUnavailable,
247
- blastRadius: Array.from(blastRadiusSet),
248
- };
249
- }