@monoes/monograph 1.2.0 → 1.2.2

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 (469) hide show
  1. package/.monomind/loops/mastermind-review-1782162423447.json +16 -0
  2. package/__tests__/mcp-tools/cypher.test.ts +10 -6
  3. package/__tests__/mcp-tools/detect-changes.test.ts +14 -11
  4. package/__tests__/pipeline/phases/wildcard-synthesis.test.ts +4 -4
  5. package/__tests__/search/embed-batch-config.test.ts +7 -7
  6. package/__tests__/search/hybrid-query.test.ts +3 -1
  7. package/__tests__/web/api.test.ts +1 -1
  8. package/dist/src/analysis/churn-cache.d.ts.map +1 -1
  9. package/dist/src/analysis/churn-cache.js +19 -6
  10. package/dist/src/analysis/churn-cache.js.map +1 -1
  11. package/dist/src/analysis/churn-trend.d.ts.map +1 -1
  12. package/dist/src/analysis/churn-trend.js +50 -6
  13. package/dist/src/analysis/churn-trend.js.map +1 -1
  14. package/dist/src/analysis/cluster-quality.d.ts +3 -0
  15. package/dist/src/analysis/cluster-quality.d.ts.map +1 -1
  16. package/dist/src/analysis/cluster-quality.js +18 -12
  17. package/dist/src/analysis/cluster-quality.js.map +1 -1
  18. package/dist/src/analysis/coverage-gaps.d.ts +3 -0
  19. package/dist/src/analysis/coverage-gaps.d.ts.map +1 -1
  20. package/dist/src/analysis/coverage-gaps.js +40 -1
  21. package/dist/src/analysis/coverage-gaps.js.map +1 -1
  22. package/dist/src/analysis/dep-classification.d.ts +2 -0
  23. package/dist/src/analysis/dep-classification.d.ts.map +1 -1
  24. package/dist/src/analysis/dep-classification.js +30 -0
  25. package/dist/src/analysis/dep-classification.js.map +1 -1
  26. package/dist/src/analysis/dependency-health.d.ts +4 -0
  27. package/dist/src/analysis/dependency-health.d.ts.map +1 -1
  28. package/dist/src/analysis/dependency-health.js +28 -0
  29. package/dist/src/analysis/dependency-health.js.map +1 -1
  30. package/dist/src/analysis/duplicate-exports.d.ts +9 -5
  31. package/dist/src/analysis/duplicate-exports.d.ts.map +1 -1
  32. package/dist/src/analysis/duplicate-exports.js +26 -2
  33. package/dist/src/analysis/duplicate-exports.js.map +1 -1
  34. package/dist/src/analysis/feature-flags.d.ts.map +1 -1
  35. package/dist/src/analysis/feature-flags.js +36 -17
  36. package/dist/src/analysis/feature-flags.js.map +1 -1
  37. package/dist/src/analysis/node-similarity.d.ts +19 -0
  38. package/dist/src/analysis/node-similarity.d.ts.map +1 -1
  39. package/dist/src/analysis/node-similarity.js +34 -3
  40. package/dist/src/analysis/node-similarity.js.map +1 -1
  41. package/dist/src/analysis/private-type-leaks.d.ts +4 -0
  42. package/dist/src/analysis/private-type-leaks.d.ts.map +1 -1
  43. package/dist/src/analysis/private-type-leaks.js +43 -2
  44. package/dist/src/analysis/private-type-leaks.js.map +1 -1
  45. package/dist/src/analysis/project-state.d.ts +2 -0
  46. package/dist/src/analysis/project-state.d.ts.map +1 -1
  47. package/dist/src/analysis/project-state.js +18 -7
  48. package/dist/src/analysis/project-state.js.map +1 -1
  49. package/dist/src/analysis/suppressions.d.ts.map +1 -1
  50. package/dist/src/analysis/suppressions.js +3 -1
  51. package/dist/src/analysis/suppressions.js.map +1 -1
  52. package/dist/src/analysis/trace.d.ts +5 -3
  53. package/dist/src/analysis/trace.d.ts.map +1 -1
  54. package/dist/src/analysis/trace.js +59 -53
  55. package/dist/src/analysis/trace.js.map +1 -1
  56. package/dist/src/analysis/unused-class-members.d.ts.map +1 -1
  57. package/dist/src/analysis/unused-class-members.js +7 -1
  58. package/dist/src/analysis/unused-class-members.js.map +1 -1
  59. package/dist/src/analysis/worker-pool.d.ts +3 -0
  60. package/dist/src/analysis/worker-pool.d.ts.map +1 -1
  61. package/dist/src/analysis/worker-pool.js +30 -8
  62. package/dist/src/analysis/worker-pool.js.map +1 -1
  63. package/dist/src/cli/augment.d.ts.map +1 -1
  64. package/dist/src/cli/augment.js +10 -3
  65. package/dist/src/cli/augment.js.map +1 -1
  66. package/dist/src/cli/skill-gen.js +3 -0
  67. package/dist/src/cli/skill-gen.js.map +1 -1
  68. package/dist/src/export/sarif.js +5 -5
  69. package/dist/src/export/sarif.js.map +1 -1
  70. package/dist/src/graph/analyzer.d.ts.map +1 -1
  71. package/dist/src/graph/analyzer.js +17 -2
  72. package/dist/src/graph/analyzer.js.map +1 -1
  73. package/dist/src/graph/author-analytics.d.ts +2 -0
  74. package/dist/src/graph/author-analytics.d.ts.map +1 -1
  75. package/dist/src/graph/author-analytics.js +39 -5
  76. package/dist/src/graph/author-analytics.js.map +1 -1
  77. package/dist/src/graph/baseline.d.ts +5 -0
  78. package/dist/src/graph/baseline.d.ts.map +1 -1
  79. package/dist/src/graph/baseline.js +65 -18
  80. package/dist/src/graph/baseline.js.map +1 -1
  81. package/dist/src/graph/clone-detector.d.ts.map +1 -1
  82. package/dist/src/graph/clone-detector.js +19 -33
  83. package/dist/src/graph/clone-detector.js.map +1 -1
  84. package/dist/src/graph/clone-families.d.ts +2 -0
  85. package/dist/src/graph/clone-families.d.ts.map +1 -1
  86. package/dist/src/graph/clone-families.js +38 -10
  87. package/dist/src/graph/clone-families.js.map +1 -1
  88. package/dist/src/graph/codeowners-extended.d.ts +17 -0
  89. package/dist/src/graph/codeowners-extended.d.ts.map +1 -1
  90. package/dist/src/graph/codeowners-extended.js +42 -0
  91. package/dist/src/graph/codeowners-extended.js.map +1 -1
  92. package/dist/src/graph/codeowners-gitlab.d.ts +21 -0
  93. package/dist/src/graph/codeowners-gitlab.d.ts.map +1 -1
  94. package/dist/src/graph/codeowners-gitlab.js +37 -0
  95. package/dist/src/graph/codeowners-gitlab.js.map +1 -1
  96. package/dist/src/graph/codeowners.d.ts +10 -0
  97. package/dist/src/graph/codeowners.d.ts.map +1 -1
  98. package/dist/src/graph/codeowners.js +24 -2
  99. package/dist/src/graph/codeowners.js.map +1 -1
  100. package/dist/src/graph/complexity.d.ts +10 -0
  101. package/dist/src/graph/complexity.d.ts.map +1 -1
  102. package/dist/src/graph/complexity.js +93 -43
  103. package/dist/src/graph/complexity.js.map +1 -1
  104. package/dist/src/graph/cross-reference-findings.d.ts.map +1 -1
  105. package/dist/src/graph/cross-reference-findings.js +19 -2
  106. package/dist/src/graph/cross-reference-findings.js.map +1 -1
  107. package/dist/src/graph/cross-reference.d.ts +4 -0
  108. package/dist/src/graph/cross-reference.d.ts.map +1 -1
  109. package/dist/src/graph/cross-reference.js +23 -0
  110. package/dist/src/graph/cross-reference.js.map +1 -1
  111. package/dist/src/graph/cycle-detection.d.ts +2 -0
  112. package/dist/src/graph/cycle-detection.d.ts.map +1 -1
  113. package/dist/src/graph/cycle-detection.js +26 -3
  114. package/dist/src/graph/cycle-detection.js.map +1 -1
  115. package/dist/src/graph/cycles.d.ts +11 -3
  116. package/dist/src/graph/cycles.d.ts.map +1 -1
  117. package/dist/src/graph/cycles.js +121 -83
  118. package/dist/src/graph/cycles.js.map +1 -1
  119. package/dist/src/graph/dead-code.d.ts +23 -0
  120. package/dist/src/graph/dead-code.d.ts.map +1 -1
  121. package/dist/src/graph/dead-code.js +53 -13
  122. package/dist/src/graph/dead-code.js.map +1 -1
  123. package/dist/src/graph/dependency-closure.d.ts.map +1 -1
  124. package/dist/src/graph/dependency-closure.js +20 -4
  125. package/dist/src/graph/dependency-closure.js.map +1 -1
  126. package/dist/src/graph/dfs.d.ts +14 -0
  127. package/dist/src/graph/dfs.d.ts.map +1 -1
  128. package/dist/src/graph/dfs.js +61 -0
  129. package/dist/src/graph/dfs.js.map +1 -1
  130. package/dist/src/graph/duplication-stats.d.ts.map +1 -1
  131. package/dist/src/graph/duplication-stats.js +19 -3
  132. package/dist/src/graph/duplication-stats.js.map +1 -1
  133. package/dist/src/graph/edge-filter.d.ts +2 -0
  134. package/dist/src/graph/edge-filter.d.ts.map +1 -1
  135. package/dist/src/graph/edge-filter.js +26 -12
  136. package/dist/src/graph/edge-filter.js.map +1 -1
  137. package/dist/src/graph/health-score.d.ts +8 -0
  138. package/dist/src/graph/health-score.d.ts.map +1 -1
  139. package/dist/src/graph/health-score.js +48 -17
  140. package/dist/src/graph/health-score.js.map +1 -1
  141. package/dist/src/graph/hotspots.d.ts +7 -0
  142. package/dist/src/graph/hotspots.d.ts.map +1 -1
  143. package/dist/src/graph/hotspots.js +50 -5
  144. package/dist/src/graph/hotspots.js.map +1 -1
  145. package/dist/src/graph/import-chain.d.ts +17 -1
  146. package/dist/src/graph/import-chain.d.ts.map +1 -1
  147. package/dist/src/graph/import-chain.js +68 -19
  148. package/dist/src/graph/import-chain.js.map +1 -1
  149. package/dist/src/graph/loader.d.ts.map +1 -1
  150. package/dist/src/graph/loader.js +9 -10
  151. package/dist/src/graph/loader.js.map +1 -1
  152. package/dist/src/graph/maintainability.d.ts +8 -0
  153. package/dist/src/graph/maintainability.d.ts.map +1 -1
  154. package/dist/src/graph/maintainability.js +65 -10
  155. package/dist/src/graph/maintainability.js.map +1 -1
  156. package/dist/src/graph/mirrored-dirs.d.ts +4 -0
  157. package/dist/src/graph/mirrored-dirs.d.ts.map +1 -1
  158. package/dist/src/graph/mirrored-dirs.js +48 -13
  159. package/dist/src/graph/mirrored-dirs.js.map +1 -1
  160. package/dist/src/graph/narrowing.d.ts +15 -0
  161. package/dist/src/graph/narrowing.d.ts.map +1 -1
  162. package/dist/src/graph/narrowing.js +33 -0
  163. package/dist/src/graph/narrowing.js.map +1 -1
  164. package/dist/src/graph/node-search.d.ts +3 -0
  165. package/dist/src/graph/node-search.d.ts.map +1 -1
  166. package/dist/src/graph/node-search.js +37 -4
  167. package/dist/src/graph/node-search.js.map +1 -1
  168. package/dist/src/graph/normalize-id.d.ts +4 -0
  169. package/dist/src/graph/normalize-id.d.ts.map +1 -1
  170. package/dist/src/graph/normalize-id.js +25 -2
  171. package/dist/src/graph/normalize-id.js.map +1 -1
  172. package/dist/src/graph/pagerank.d.ts +7 -0
  173. package/dist/src/graph/pagerank.d.ts.map +1 -1
  174. package/dist/src/graph/pagerank.js +49 -2
  175. package/dist/src/graph/pagerank.js.map +1 -1
  176. package/dist/src/graph/prune.d.ts +31 -0
  177. package/dist/src/graph/prune.d.ts.map +1 -1
  178. package/dist/src/graph/prune.js +65 -0
  179. package/dist/src/graph/prune.js.map +1 -1
  180. package/dist/src/graph/regex-search.d.ts +10 -2
  181. package/dist/src/graph/regex-search.d.ts.map +1 -1
  182. package/dist/src/graph/regex-search.js +33 -4
  183. package/dist/src/graph/regex-search.js.map +1 -1
  184. package/dist/src/graph/regression.d.ts +5 -0
  185. package/dist/src/graph/regression.d.ts.map +1 -1
  186. package/dist/src/graph/regression.js +45 -23
  187. package/dist/src/graph/regression.js.map +1 -1
  188. package/dist/src/graph/ripple-impact.d.ts +27 -0
  189. package/dist/src/graph/ripple-impact.d.ts.map +1 -1
  190. package/dist/src/graph/ripple-impact.js +55 -7
  191. package/dist/src/graph/ripple-impact.js.map +1 -1
  192. package/dist/src/graph/risk-profile.d.ts +7 -0
  193. package/dist/src/graph/risk-profile.d.ts.map +1 -1
  194. package/dist/src/graph/risk-profile.js +82 -48
  195. package/dist/src/graph/risk-profile.js.map +1 -1
  196. package/dist/src/graph/shingle-filter.d.ts.map +1 -1
  197. package/dist/src/graph/shingle-filter.js +25 -26
  198. package/dist/src/graph/shingle-filter.js.map +1 -1
  199. package/dist/src/graph/statistics.d.ts +19 -0
  200. package/dist/src/graph/statistics.d.ts.map +1 -1
  201. package/dist/src/graph/statistics.js +53 -40
  202. package/dist/src/graph/statistics.js.map +1 -1
  203. package/dist/src/graph/stats.d.ts +2 -0
  204. package/dist/src/graph/stats.d.ts.map +1 -1
  205. package/dist/src/graph/stats.js +54 -8
  206. package/dist/src/graph/stats.js.map +1 -1
  207. package/dist/src/graph/subgraph.d.ts +5 -0
  208. package/dist/src/graph/subgraph.d.ts.map +1 -1
  209. package/dist/src/graph/subgraph.js +73 -9
  210. package/dist/src/graph/subgraph.js.map +1 -1
  211. package/dist/src/graph/topo-sort.d.ts +15 -0
  212. package/dist/src/graph/topo-sort.d.ts.map +1 -1
  213. package/dist/src/graph/topo-sort.js +83 -10
  214. package/dist/src/graph/topo-sort.js.map +1 -1
  215. package/dist/src/graph/wcc.d.ts +29 -0
  216. package/dist/src/graph/wcc.d.ts.map +1 -1
  217. package/dist/src/graph/wcc.js +122 -39
  218. package/dist/src/graph/wcc.js.map +1 -1
  219. package/dist/src/index.js +1 -1
  220. package/dist/src/index.js.map +1 -1
  221. package/dist/src/mcp-tools/api-impact.d.ts.map +1 -1
  222. package/dist/src/mcp-tools/api-impact.js +13 -6
  223. package/dist/src/mcp-tools/api-impact.js.map +1 -1
  224. package/dist/src/mcp-tools/context.d.ts.map +1 -1
  225. package/dist/src/mcp-tools/context.js +19 -29
  226. package/dist/src/mcp-tools/context.js.map +1 -1
  227. package/dist/src/mcp-tools/cypher.d.ts +11 -0
  228. package/dist/src/mcp-tools/cypher.d.ts.map +1 -1
  229. package/dist/src/mcp-tools/cypher.js +13 -1
  230. package/dist/src/mcp-tools/cypher.js.map +1 -1
  231. package/dist/src/mcp-tools/detect-changes.d.ts.map +1 -1
  232. package/dist/src/mcp-tools/detect-changes.js +35 -20
  233. package/dist/src/mcp-tools/detect-changes.js.map +1 -1
  234. package/dist/src/mcp-tools/explain.d.ts.map +1 -1
  235. package/dist/src/mcp-tools/explain.js +4 -1
  236. package/dist/src/mcp-tools/explain.js.map +1 -1
  237. package/dist/src/mcp-tools/graph-query.d.ts +2 -0
  238. package/dist/src/mcp-tools/graph-query.d.ts.map +1 -1
  239. package/dist/src/mcp-tools/graph-query.js +20 -3
  240. package/dist/src/mcp-tools/graph-query.js.map +1 -1
  241. package/dist/src/mcp-tools/group-list.d.ts +1 -0
  242. package/dist/src/mcp-tools/group-list.d.ts.map +1 -1
  243. package/dist/src/mcp-tools/group-list.js +7 -9
  244. package/dist/src/mcp-tools/group-list.js.map +1 -1
  245. package/dist/src/mcp-tools/impact.d.ts.map +1 -1
  246. package/dist/src/mcp-tools/impact.js +20 -59
  247. package/dist/src/mcp-tools/impact.js.map +1 -1
  248. package/dist/src/mcp-tools/neighbors.d.ts.map +1 -1
  249. package/dist/src/mcp-tools/neighbors.js +24 -33
  250. package/dist/src/mcp-tools/neighbors.js.map +1 -1
  251. package/dist/src/mcp-tools/query.d.ts +2 -0
  252. package/dist/src/mcp-tools/query.d.ts.map +1 -1
  253. package/dist/src/mcp-tools/query.js +1 -0
  254. package/dist/src/mcp-tools/query.js.map +1 -1
  255. package/dist/src/mcp-tools/rename.d.ts.map +1 -1
  256. package/dist/src/mcp-tools/rename.js +12 -7
  257. package/dist/src/mcp-tools/rename.js.map +1 -1
  258. package/dist/src/mcp-tools/route-map.d.ts.map +1 -1
  259. package/dist/src/mcp-tools/route-map.js +19 -23
  260. package/dist/src/mcp-tools/route-map.js.map +1 -1
  261. package/dist/src/mcp-tools/shape-check.d.ts.map +1 -1
  262. package/dist/src/mcp-tools/shape-check.js +17 -13
  263. package/dist/src/mcp-tools/shape-check.js.map +1 -1
  264. package/dist/src/parsers/loader.js +6 -5
  265. package/dist/src/parsers/loader.js.map +1 -1
  266. package/dist/src/pipeline/cluster-only.d.ts.map +1 -1
  267. package/dist/src/pipeline/cluster-only.js +8 -4
  268. package/dist/src/pipeline/cluster-only.js.map +1 -1
  269. package/dist/src/pipeline/orchestrator.d.ts +2 -0
  270. package/dist/src/pipeline/orchestrator.d.ts.map +1 -1
  271. package/dist/src/pipeline/orchestrator.js +19 -0
  272. package/dist/src/pipeline/orchestrator.js.map +1 -1
  273. package/dist/src/pipeline/phases/boundary.d.ts +2 -0
  274. package/dist/src/pipeline/phases/boundary.d.ts.map +1 -1
  275. package/dist/src/pipeline/phases/boundary.js +31 -2
  276. package/dist/src/pipeline/phases/boundary.js.map +1 -1
  277. package/dist/src/pipeline/phases/communities.d.ts +14 -0
  278. package/dist/src/pipeline/phases/communities.d.ts.map +1 -1
  279. package/dist/src/pipeline/phases/communities.js +58 -5
  280. package/dist/src/pipeline/phases/communities.js.map +1 -1
  281. package/dist/src/pipeline/phases/contextual-proximity.d.ts.map +1 -1
  282. package/dist/src/pipeline/phases/contextual-proximity.js +21 -14
  283. package/dist/src/pipeline/phases/contextual-proximity.js.map +1 -1
  284. package/dist/src/pipeline/phases/cross-file.d.ts.map +1 -1
  285. package/dist/src/pipeline/phases/cross-file.js +35 -14
  286. package/dist/src/pipeline/phases/cross-file.js.map +1 -1
  287. package/dist/src/pipeline/phases/field-access.d.ts.map +1 -1
  288. package/dist/src/pipeline/phases/field-access.js +9 -4
  289. package/dist/src/pipeline/phases/field-access.js.map +1 -1
  290. package/dist/src/pipeline/phases/god-nodes.d.ts +4 -0
  291. package/dist/src/pipeline/phases/god-nodes.d.ts.map +1 -1
  292. package/dist/src/pipeline/phases/god-nodes.js +43 -9
  293. package/dist/src/pipeline/phases/god-nodes.js.map +1 -1
  294. package/dist/src/pipeline/phases/import-resolver.d.ts +11 -0
  295. package/dist/src/pipeline/phases/import-resolver.d.ts.map +1 -1
  296. package/dist/src/pipeline/phases/import-resolver.js +34 -7
  297. package/dist/src/pipeline/phases/import-resolver.js.map +1 -1
  298. package/dist/src/pipeline/phases/leiden.d.ts.map +1 -1
  299. package/dist/src/pipeline/phases/leiden.js +27 -19
  300. package/dist/src/pipeline/phases/leiden.js.map +1 -1
  301. package/dist/src/pipeline/phases/markdown.d.ts.map +1 -1
  302. package/dist/src/pipeline/phases/markdown.js +10 -5
  303. package/dist/src/pipeline/phases/markdown.js.map +1 -1
  304. package/dist/src/pipeline/phases/pdf-parse.d.ts.map +1 -1
  305. package/dist/src/pipeline/phases/pdf-parse.js +18 -3
  306. package/dist/src/pipeline/phases/pdf-parse.js.map +1 -1
  307. package/dist/src/pipeline/phases/processes.d.ts.map +1 -1
  308. package/dist/src/pipeline/phases/processes.js +26 -9
  309. package/dist/src/pipeline/phases/processes.js.map +1 -1
  310. package/dist/src/pipeline/phases/re-export-propagation.d.ts.map +1 -1
  311. package/dist/src/pipeline/phases/re-export-propagation.js +33 -14
  312. package/dist/src/pipeline/phases/re-export-propagation.js.map +1 -1
  313. package/dist/src/pipeline/phases/reachability.d.ts.map +1 -1
  314. package/dist/src/pipeline/phases/reachability.js +42 -26
  315. package/dist/src/pipeline/phases/reachability.js.map +1 -1
  316. package/dist/src/pipeline/phases/routes.d.ts.map +1 -1
  317. package/dist/src/pipeline/phases/routes.js +30 -21
  318. package/dist/src/pipeline/phases/routes.js.map +1 -1
  319. package/dist/src/pipeline/phases/scope-resolution.d.ts.map +1 -1
  320. package/dist/src/pipeline/phases/scope-resolution.js +93 -63
  321. package/dist/src/pipeline/phases/scope-resolution.js.map +1 -1
  322. package/dist/src/pipeline/runner.d.ts +4 -0
  323. package/dist/src/pipeline/runner.d.ts.map +1 -1
  324. package/dist/src/pipeline/runner.js +30 -11
  325. package/dist/src/pipeline/runner.js.map +1 -1
  326. package/dist/src/query/cypher-parser.d.ts +20 -0
  327. package/dist/src/query/cypher-parser.d.ts.map +1 -1
  328. package/dist/src/query/cypher-parser.js +73 -0
  329. package/dist/src/query/cypher-parser.js.map +1 -1
  330. package/dist/src/search/embed-batch.d.ts.map +1 -1
  331. package/dist/src/search/embed-batch.js +4 -1
  332. package/dist/src/search/embed-batch.js.map +1 -1
  333. package/dist/src/search/hybrid-query.d.ts +4 -0
  334. package/dist/src/search/hybrid-query.d.ts.map +1 -1
  335. package/dist/src/search/hybrid-query.js +5 -1
  336. package/dist/src/search/hybrid-query.js.map +1 -1
  337. package/dist/src/staleness/git-staleness.js +3 -3
  338. package/dist/src/staleness/git-staleness.js.map +1 -1
  339. package/dist/src/storage/db.d.ts.map +1 -1
  340. package/dist/src/storage/db.js +12 -0
  341. package/dist/src/storage/db.js.map +1 -1
  342. package/dist/src/storage/edge-store.d.ts +2 -0
  343. package/dist/src/storage/edge-store.d.ts.map +1 -1
  344. package/dist/src/storage/edge-store.js +26 -1
  345. package/dist/src/storage/edge-store.js.map +1 -1
  346. package/dist/src/storage/embedding-store.d.ts +16 -0
  347. package/dist/src/storage/embedding-store.d.ts.map +1 -1
  348. package/dist/src/storage/embedding-store.js +26 -2
  349. package/dist/src/storage/embedding-store.js.map +1 -1
  350. package/dist/src/storage/file-cache.d.ts +5 -0
  351. package/dist/src/storage/file-cache.d.ts.map +1 -1
  352. package/dist/src/storage/file-cache.js +35 -9
  353. package/dist/src/storage/file-cache.js.map +1 -1
  354. package/dist/src/storage/fts-store.d.ts +4 -0
  355. package/dist/src/storage/fts-store.d.ts.map +1 -1
  356. package/dist/src/storage/fts-store.js +8 -3
  357. package/dist/src/storage/fts-store.js.map +1 -1
  358. package/dist/src/storage/node-store.d.ts +2 -0
  359. package/dist/src/storage/node-store.d.ts.map +1 -1
  360. package/dist/src/storage/node-store.js +28 -1
  361. package/dist/src/storage/node-store.js.map +1 -1
  362. package/dist/src/wiki/wiki-generator.d.ts.map +1 -1
  363. package/dist/src/wiki/wiki-generator.js +5 -1
  364. package/dist/src/wiki/wiki-generator.js.map +1 -1
  365. package/dist/tsconfig.tsbuildinfo +1 -1
  366. package/package.json +1 -1
  367. package/src/__tests__/pipeline/runner.parallel.test.ts +2 -2
  368. package/src/analysis/churn-cache.ts +18 -7
  369. package/src/analysis/churn-trend.ts +35 -6
  370. package/src/analysis/cluster-quality.ts +20 -11
  371. package/src/analysis/coverage-gaps.ts +44 -1
  372. package/src/analysis/dep-classification.ts +34 -0
  373. package/src/analysis/dependency-health.ts +25 -0
  374. package/src/analysis/duplicate-exports.ts +40 -4
  375. package/src/analysis/feature-flags.ts +37 -17
  376. package/src/analysis/node-similarity.ts +45 -4
  377. package/src/analysis/private-type-leaks.ts +49 -2
  378. package/src/analysis/project-state.ts +19 -9
  379. package/src/analysis/suppressions.ts +4 -1
  380. package/src/analysis/trace.ts +73 -54
  381. package/src/analysis/unused-class-members.ts +8 -1
  382. package/src/analysis/worker-pool.ts +40 -8
  383. package/src/cli/augment.ts +10 -3
  384. package/src/cli/skill-gen.ts +3 -0
  385. package/src/export/sarif.ts +5 -5
  386. package/src/graph/analyzer.ts +26 -2
  387. package/src/graph/author-analytics.ts +45 -5
  388. package/src/graph/baseline.ts +78 -24
  389. package/src/graph/clone-detector.ts +22 -36
  390. package/src/graph/clone-families.ts +34 -10
  391. package/src/graph/codeowners-extended.ts +54 -0
  392. package/src/graph/codeowners-gitlab.ts +46 -0
  393. package/src/graph/codeowners.ts +33 -2
  394. package/src/graph/complexity.ts +102 -47
  395. package/src/graph/cross-reference-findings.ts +23 -2
  396. package/src/graph/cross-reference.ts +26 -0
  397. package/src/graph/cycle-detection.ts +22 -3
  398. package/src/graph/cycles.ts +155 -96
  399. package/src/graph/dead-code.ts +76 -17
  400. package/src/graph/dependency-closure.ts +24 -7
  401. package/src/graph/dfs.ts +78 -0
  402. package/src/graph/duplication-stats.ts +17 -6
  403. package/src/graph/edge-filter.ts +26 -12
  404. package/src/graph/health-score.ts +47 -17
  405. package/src/graph/hotspots.ts +57 -8
  406. package/src/graph/import-chain.ts +89 -19
  407. package/src/graph/loader.ts +10 -7
  408. package/src/graph/maintainability.ts +72 -22
  409. package/src/graph/mirrored-dirs.ts +45 -14
  410. package/src/graph/narrowing.ts +44 -0
  411. package/src/graph/node-search.ts +50 -4
  412. package/src/graph/normalize-id.ts +26 -2
  413. package/src/graph/pagerank.ts +70 -2
  414. package/src/graph/prune.ts +76 -0
  415. package/src/graph/regex-search.ts +38 -4
  416. package/src/graph/regression.ts +48 -23
  417. package/src/graph/ripple-impact.ts +71 -11
  418. package/src/graph/risk-profile.ts +98 -53
  419. package/src/graph/shingle-filter.ts +33 -27
  420. package/src/graph/statistics.ts +73 -45
  421. package/src/graph/stats.ts +50 -16
  422. package/src/graph/subgraph.ts +97 -25
  423. package/src/graph/topo-sort.ts +101 -16
  424. package/src/graph/wcc.ts +146 -37
  425. package/src/index.ts +1 -1
  426. package/src/mcp-tools/api-impact.ts +13 -6
  427. package/src/mcp-tools/context.ts +31 -40
  428. package/src/mcp-tools/cypher.ts +14 -1
  429. package/src/mcp-tools/detect-changes.ts +38 -23
  430. package/src/mcp-tools/explain.ts +4 -1
  431. package/src/mcp-tools/graph-query.ts +24 -3
  432. package/src/mcp-tools/group-list.ts +9 -11
  433. package/src/mcp-tools/impact.ts +26 -66
  434. package/src/mcp-tools/neighbors.ts +34 -39
  435. package/src/mcp-tools/query.ts +3 -0
  436. package/src/mcp-tools/rename.ts +9 -8
  437. package/src/mcp-tools/route-map.ts +21 -23
  438. package/src/mcp-tools/shape-check.ts +18 -14
  439. package/src/parsers/loader.ts +5 -5
  440. package/src/pipeline/cluster-only.ts +8 -4
  441. package/src/pipeline/orchestrator.ts +22 -0
  442. package/src/pipeline/phases/boundary.ts +40 -2
  443. package/src/pipeline/phases/communities.ts +66 -5
  444. package/src/pipeline/phases/contextual-proximity.ts +15 -14
  445. package/src/pipeline/phases/cross-file.ts +35 -14
  446. package/src/pipeline/phases/field-access.ts +11 -4
  447. package/src/pipeline/phases/god-nodes.ts +47 -10
  448. package/src/pipeline/phases/import-resolver.ts +37 -5
  449. package/src/pipeline/phases/leiden.ts +25 -18
  450. package/src/pipeline/phases/markdown.ts +12 -7
  451. package/src/pipeline/phases/pdf-parse.ts +19 -2
  452. package/src/pipeline/phases/processes.ts +27 -15
  453. package/src/pipeline/phases/re-export-propagation.ts +40 -18
  454. package/src/pipeline/phases/reachability.ts +39 -24
  455. package/src/pipeline/phases/routes.ts +32 -21
  456. package/src/pipeline/phases/scope-resolution.ts +110 -71
  457. package/src/pipeline/runner.ts +28 -11
  458. package/src/query/cypher-parser.ts +83 -0
  459. package/src/search/embed-batch.ts +4 -1
  460. package/src/search/hybrid-query.ts +11 -1
  461. package/src/staleness/git-staleness.ts +3 -3
  462. package/src/storage/db.ts +11 -0
  463. package/src/storage/edge-store.ts +34 -1
  464. package/src/storage/embedding-store.ts +38 -6
  465. package/src/storage/file-cache.ts +35 -21
  466. package/src/storage/fts-store.ts +12 -3
  467. package/src/storage/node-store.ts +39 -1
  468. package/src/wiki/wiki-generator.ts +5 -1
  469. package/src/coverage/runtime.ts +0 -249
@@ -18,24 +18,27 @@ export function loadGraphFromEdges(edges: MonographEdge[]): Graph {
18
18
 
19
19
  export function loadGraphFromDb(db: import('../storage/db.js').MonographDb): Graph {
20
20
  const nodes = db.prepare('SELECT id FROM nodes').all() as { id: string }[];
21
- const edges = db.prepare('SELECT * FROM edges').all() as {
21
+ // Explicitly select only the columns used here — avoids fetching reason/evidence blobs
22
+ const edges = db.prepare(
23
+ 'SELECT id, source_id, target_id, relation, confidence, confidence_score FROM edges'
24
+ ).all() as {
22
25
  id: string; source_id: string; target_id: string; relation: string;
23
26
  confidence: string; confidence_score: number;
24
27
  }[];
25
28
 
26
29
  const graph = new Graph({ multi: true, type: 'directed' });
27
- for (const n of nodes) {
28
- if (!graph.hasNode(n.id)) graph.addNode(n.id);
29
- }
30
+ // Seed known nodes first; edges below may reference node ids not in the nodes table
31
+ // (defensive: addNode is a no-op guard here, has-check avoided to reduce overhead)
32
+ for (const n of nodes) graph.mergeNode(n.id);
30
33
  for (const e of edges) {
31
- if (!graph.hasNode(e.source_id)) graph.addNode(e.source_id);
32
- if (!graph.hasNode(e.target_id)) graph.addNode(e.target_id);
34
+ graph.mergeNode(e.source_id);
35
+ graph.mergeNode(e.target_id);
33
36
  try {
34
37
  graph.addEdge(e.source_id, e.target_id, {
35
38
  id: e.id, relation: e.relation,
36
39
  confidence: e.confidence, confidenceScore: e.confidence_score,
37
40
  });
38
- } catch { /* skip */ }
41
+ } catch { /* skip duplicate edges */ }
39
42
  }
40
43
  return graph;
41
44
  }
@@ -46,35 +46,36 @@ export function computeMaintainabilityIndex(db: MonographDb): MaintainabilityRep
46
46
  return { results: [], averageMi: 100, lowMiCount: 0, criticalCount: 0 };
47
47
  }
48
48
 
49
+ // ── Batch degree lookup: one query replaces 2×N individual queries ────────
50
+ // Combine in-degree and out-degree via UNION ALL + GROUP BY in a single pass.
51
+ const degreeMap = new Map<string, number>();
52
+ for (const { node_id, deg } of db.prepare(`
53
+ SELECT node_id, SUM(cnt) as deg FROM (
54
+ SELECT target_id AS node_id, COUNT(*) AS cnt FROM edges GROUP BY target_id
55
+ UNION ALL
56
+ SELECT source_id AS node_id, COUNT(*) AS cnt FROM edges GROUP BY source_id
57
+ ) GROUP BY node_id
58
+ `).all() as { node_id: string; deg: number }[]) {
59
+ degreeMap.set(node_id, deg);
60
+ }
61
+
49
62
  const results: MaintainabilityResult[] = [];
63
+ const propUpdates: Array<{ id: string; props: string }> = [];
50
64
 
51
65
  for (const node of nodes) {
52
66
  const loc = Math.max(1, node.end_line - node.start_line + 1);
53
-
54
- // Count in/out degree from edges table
55
- const inDegreeRow = db.prepare(
56
- 'SELECT COUNT(*) as c FROM edges WHERE target_id = ?'
57
- ).get(node.id) as { c: number };
58
- const outDegreeRow = db.prepare(
59
- 'SELECT COUNT(*) as c FROM edges WHERE source_id = ?'
60
- ).get(node.id) as { c: number };
61
-
62
- const degree = inDegreeRow.c + outDegreeRow.c;
67
+ const degree = degreeMap.get(node.id) ?? 0;
63
68
  const hvProxy = degree * Math.log2(Math.max(degree, 2));
64
69
 
65
70
  // Maintainability Index formula
66
71
  const rawMi = 171 - 5.2 * Math.log(hvProxy + 1) - 0.23 * (loc / 10) - 16.2 * Math.log(Math.max(1, loc));
67
72
  const mi = Math.max(0, Math.min(100, rawMi));
68
-
69
73
  const grade = gradeFromMi(mi);
70
74
 
71
- // Store maintainabilityIndex back on the node's properties
72
- const props = node.properties ? JSON.parse(node.properties) : {};
75
+ // Stage property update batched below
76
+ const props: Record<string, unknown> = node.properties ? JSON.parse(node.properties) : {};
73
77
  props.maintainabilityIndex = mi;
74
- db.prepare('UPDATE nodes SET properties = ? WHERE id = ?').run(
75
- JSON.stringify(props),
76
- node.id
77
- );
78
+ propUpdates.push({ id: node.id, props: JSON.stringify(props) });
78
79
 
79
80
  results.push({
80
81
  nodeId: node.id,
@@ -87,6 +88,12 @@ export function computeMaintainabilityIndex(db: MonographDb): MaintainabilityRep
87
88
  });
88
89
  }
89
90
 
91
+ // ── Batch UPDATE all property writes in one transaction ───────────────────
92
+ const updateStmt = db.prepare('UPDATE nodes SET properties = ? WHERE id = ?');
93
+ db.transaction((updates: typeof propUpdates) => {
94
+ for (const { id, props } of updates) updateStmt.run(props, id);
95
+ })(propUpdates);
96
+
90
97
  // Sort by MI ascending (worst first)
91
98
  results.sort((a, b) => a.mi - b.mi);
92
99
 
@@ -94,13 +101,56 @@ export function computeMaintainabilityIndex(db: MonographDb): MaintainabilityRep
94
101
  ? results.reduce((sum, r) => sum + r.mi, 0) / results.length
95
102
  : 100;
96
103
 
97
- const lowMiCount = results.filter(r => r.mi < 65).length;
98
- const criticalCount = results.filter(r => r.mi < 25).length;
99
-
100
104
  return {
101
105
  results,
102
106
  averageMi: Math.round(averageMi * 100) / 100,
103
- lowMiCount,
104
- criticalCount,
107
+ lowMiCount: results.filter(r => r.mi < 65).length,
108
+ criticalCount: results.filter(r => r.mi < 25).length,
105
109
  };
106
110
  }
111
+
112
+ /**
113
+ * Format a MaintainabilityReport as structured text with file:line hints for LLM navigation.
114
+ *
115
+ * @param report - MaintainabilityReport from computeMaintainabilityIndex()
116
+ * @param topN - number of worst files to list (default 10)
117
+ * @returns structured text suitable for LLM consumption
118
+ */
119
+ export function formatMaintainability(report: MaintainabilityReport, topN = 10): string {
120
+ const { results, averageMi, lowMiCount, criticalCount } = report;
121
+
122
+ if (results.length === 0) {
123
+ return 'maintainability: no Function/Method/Symbol nodes with line info found\n';
124
+ }
125
+
126
+ const lines: string[] = [
127
+ `maintainability: ${results.length} nodes analysed`,
128
+ ` avg_mi: ${averageMi} low(mi<65): ${lowMiCount} critical(mi<25): ${criticalCount}`,
129
+ '',
130
+ ];
131
+
132
+ // results already sorted by MI ascending (worst first)
133
+ const worst = results.slice(0, topN);
134
+ if (worst.length > 0) {
135
+ lines.push(`top_${topN}_worst_mi:`);
136
+ for (const r of worst) {
137
+ const loc = r.filePath ? `${r.filePath}:1` : `<unknown>:1`;
138
+ lines.push(` - ${r.name} grade:${r.grade} mi:${r.mi}`);
139
+ lines.push(` file: ${loc}`);
140
+ lines.push(` loc: ${r.linesOfCode} halstead_vol: ${r.halsteadVolume}`);
141
+ }
142
+ lines.push('');
143
+ }
144
+
145
+ const critical = results.filter(r => r.mi < 25);
146
+ if (critical.length > 0) {
147
+ lines.push(`critical_nodes(mi<25): ${critical.length}`);
148
+ for (const r of critical.slice(0, 5)) {
149
+ const loc = r.filePath ? `${r.filePath}:1` : `<unknown>:1`;
150
+ lines.push(` - ${r.name} file: ${loc} mi:${r.mi} grade:${r.grade}`);
151
+ }
152
+ if (critical.length > 5) lines.push(` ... and ${critical.length - 5} more`);
153
+ }
154
+
155
+ return lines.join('\n');
156
+ }
@@ -126,18 +126,30 @@ export function detectMirroredFamilies(
126
126
  const fa = families[i];
127
127
  const fb = families[j];
128
128
 
129
- const dirsA = new Set(fa.files.map(dirOf));
130
- const dirsB = new Set(fb.files.map(dirOf));
131
-
132
- if (dirsA.size !== 1 || dirsB.size !== 1) continue;
133
-
134
- const [dirA] = dirsA;
135
- const [dirB] = dirsB;
129
+ // Single pass: extract dir and basename together to avoid two separate .map() iterations
130
+ let firstDirA: string | undefined;
131
+ let singleDirA = true;
132
+ let firstDirB: string | undefined;
133
+ let singleDirB = true;
134
+ const namesA = new Set<string>();
135
+ const namesB = new Set<string>();
136
+ for (const f of fa.files) {
137
+ const d = dirOf(f);
138
+ if (firstDirA === undefined) firstDirA = d;
139
+ else if (d !== firstDirA) { singleDirA = false; }
140
+ namesA.add(baseName(f));
141
+ }
142
+ for (const f of fb.files) {
143
+ const d = dirOf(f);
144
+ if (firstDirB === undefined) firstDirB = d;
145
+ else if (d !== firstDirB) { singleDirB = false; }
146
+ namesB.add(baseName(f));
147
+ }
136
148
 
137
- if (dirA === dirB) continue;
149
+ if (!singleDirA || !singleDirB) continue;
138
150
 
139
- const namesA = new Set(fa.files.map(baseName));
140
- const namesB = new Set(fb.files.map(baseName));
151
+ const dirA = firstDirA!;
152
+ const dirB = firstDirB!;
141
153
 
142
154
  const shared: string[] = [];
143
155
  for (const n of namesA) {
@@ -164,8 +176,27 @@ export function detectMirroredFamilies(
164
176
 
165
177
  mirrored.sort((a, b) => b.similarity - a.similarity);
166
178
 
167
- return {
168
- mirrored,
169
- remaining: [...remainingIndices].map(i => families[i]),
170
- };
179
+ const remaining: CloneFamily[] = [];
180
+ for (const i of remainingIndices) remaining.push(families[i]);
181
+
182
+ return { mirrored, remaining };
183
+ }
184
+
185
+ /**
186
+ * Format a MirroredDirsReport as structured text for LLM consumption.
187
+ */
188
+ export function formatMirroredDirs(report: MirroredDirsReport): string {
189
+ if (report.pairs.length === 0) {
190
+ return `No mirrored directories found (${report.totalDirsAnalyzed} dirs analyzed).`;
191
+ }
192
+ const lines: string[] = [
193
+ `Mirrored directories: ${report.pairs.length} pair(s) of ${report.totalDirsAnalyzed} analyzed`,
194
+ '',
195
+ ];
196
+ for (const p of report.pairs) {
197
+ lines.push(
198
+ ` ${p.dirA} <-> ${p.dirB} similarity=${(p.similarity * 100).toFixed(0)}% shared=${p.sharedFileNames.length} uniqueA=${p.uniqueToA} uniqueB=${p.uniqueToB}`,
199
+ );
200
+ }
201
+ return lines.join('\n');
171
202
  }
@@ -46,3 +46,47 @@ export function markMemberExportsReferenced(
46
46
  }
47
47
  return result;
48
48
  }
49
+
50
+ export interface NarrowingReport {
51
+ filePath: string;
52
+ totalExports: number;
53
+ referencedExports: string[];
54
+ unusedExports: string[];
55
+ }
56
+
57
+ /**
58
+ * Given a file's exports and the accessed members from all import sites,
59
+ * return a report of which exports are unused.
60
+ */
61
+ export function filterUnusedExports(
62
+ filePath: string,
63
+ exports: string[],
64
+ accessed: AccessedMembers,
65
+ ): NarrowingReport {
66
+ const referenced = markMemberExportsReferenced(exports, accessed);
67
+ const unusedExports: string[] = [];
68
+ for (const exp of exports) {
69
+ if (!referenced.has(exp)) unusedExports.push(exp);
70
+ }
71
+ return {
72
+ filePath,
73
+ totalExports: exports.length,
74
+ referencedExports: Array.from(referenced),
75
+ unusedExports,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Format narrowing reports as structured text for LLM dead-import diagnostics.
81
+ */
82
+ export function formatNarrowingReport(reports: NarrowingReport[]): string {
83
+ const withUnused = reports.filter(r => r.unusedExports.length > 0);
84
+ if (withUnused.length === 0) return 'No unused exports found.';
85
+
86
+ const lines: string[] = [`Unused exports in ${withUnused.length} file(s):`, ''];
87
+ for (const r of withUnused) {
88
+ lines.push(` ${r.filePath} (${r.unusedExports.length}/${r.totalExports} unused)`);
89
+ for (const exp of r.unusedExports) lines.push(` - ${exp}`);
90
+ }
91
+ return lines.join('\n');
92
+ }
@@ -1,3 +1,4 @@
1
+ import Database from 'better-sqlite3';
1
2
  import type { MonographNode, NodeLabel } from '../types.js';
2
3
  import type { MonographDb } from '../storage/db.js';
3
4
 
@@ -50,11 +51,39 @@ function rowToNode(r: {
50
51
  };
51
52
  }
52
53
 
54
+ // ── Prepared statement cache ───────────────────────────────────────────────────
55
+ // Keyed by a fingerprint of which conditions are active (not by param values),
56
+ // so the same query shape reuses the compiled statement across calls.
57
+
58
+ // WeakMap ensures the cache is garbage-collected when the DB object is released.
59
+ const stmtCache = new WeakMap<object, Map<string, Database.Statement>>();
60
+
61
+ function getCachedStmt(
62
+ db: MonographDb,
63
+ key: string,
64
+ buildSql: () => string,
65
+ ): Database.Statement {
66
+ let dbCache = stmtCache.get(db);
67
+ if (!dbCache) {
68
+ dbCache = new Map();
69
+ stmtCache.set(db, dbCache);
70
+ }
71
+ let stmt = dbCache.get(key);
72
+ if (!stmt) {
73
+ stmt = db.prepare(buildSql());
74
+ dbCache.set(key, stmt);
75
+ }
76
+ return stmt;
77
+ }
78
+
53
79
  // ── DB-backed search ───────────────────────────────────────────────────────────
54
80
 
55
81
  /**
56
82
  * Search nodes by structured property criteria.
57
83
  * All supplied criteria are combined with AND.
84
+ *
85
+ * Prepared statements are cached per-DB keyed by the active condition set so
86
+ * repeated calls with the same filter shape reuse the compiled statement.
58
87
  */
59
88
  export function searchNodesByProperty(
60
89
  db: MonographDb,
@@ -62,15 +91,19 @@ export function searchNodesByProperty(
62
91
  ): MonographNode[] {
63
92
  const conditions: string[] = [];
64
93
  const params: unknown[] = [];
94
+ // fingerprint encodes which conditions are active (not their values)
95
+ const fingerprint: string[] = [];
65
96
 
66
97
  if (options.label) {
67
98
  conditions.push('label = ?');
68
99
  params.push(options.label);
100
+ fingerprint.push('lbl');
69
101
  }
70
102
 
71
103
  if (options.language) {
72
104
  conditions.push('LOWER(language) = LOWER(?)');
73
105
  params.push(options.language);
106
+ fingerprint.push('lang');
74
107
  }
75
108
 
76
109
  if (options.fileExtension !== undefined) {
@@ -80,30 +113,43 @@ export function searchNodesByProperty(
80
113
  : `.${options.fileExtension}`;
81
114
  conditions.push("file_path LIKE ?");
82
115
  params.push(`%${ext}`);
116
+ fingerprint.push('ext');
83
117
  }
84
118
 
85
119
  if (options.filePath !== undefined) {
86
120
  conditions.push("LOWER(file_path) LIKE LOWER(?)");
87
121
  params.push(`%${options.filePath}%`);
122
+ fingerprint.push('fp');
88
123
  }
89
124
 
90
125
  if (options.isExported !== undefined) {
91
126
  conditions.push('is_exported = ?');
92
127
  params.push(options.isExported ? 1 : 0);
128
+ fingerprint.push('exp');
93
129
  }
94
130
 
95
131
  if (options.communityId !== undefined) {
96
132
  conditions.push('community_id = ?');
97
133
  params.push(options.communityId);
134
+ fingerprint.push('com');
98
135
  }
99
136
 
137
+ const hasLimit = options.limit !== undefined;
138
+ if (hasLimit) fingerprint.push('lim');
139
+
100
140
  const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
101
- const limitClause = options.limit !== undefined ? `LIMIT ${Number(options.limit)}` : '';
102
- const sql = `SELECT id, label, name, norm_label, file_path, start_line, end_line,
141
+ const limitClause = hasLimit ? `LIMIT ${Number(options.limit)}` : '';
142
+ const cacheKey = fingerprint.join(':') || 'all';
143
+
144
+ const stmt = getCachedStmt(
145
+ db,
146
+ cacheKey,
147
+ () => `SELECT id, label, name, norm_label, file_path, start_line, end_line,
103
148
  community_id, is_exported, language, properties
104
- FROM nodes ${where} ${limitClause}`;
149
+ FROM nodes ${where} ${limitClause}`,
150
+ );
105
151
 
106
- const rows = db.prepare(sql).all(...params) as Parameters<typeof rowToNode>[0][];
152
+ const rows = stmt.all(...params) as Parameters<typeof rowToNode>[0][];
107
153
  return rows.map(rowToNode);
108
154
  }
109
155
 
@@ -87,8 +87,9 @@ export function reconcileEdges(
87
87
  let remapped = false;
88
88
 
89
89
  if (!nodeIds.has(src)) {
90
+ // Cache normalizeId result to avoid calling it twice for the same string
90
91
  const canonical = normToId.get(normalizeId(src));
91
- if (canonical) {
92
+ if (canonical !== undefined) {
92
93
  src = canonical;
93
94
  remapped = true;
94
95
  }
@@ -96,7 +97,7 @@ export function reconcileEdges(
96
97
 
97
98
  if (!nodeIds.has(tgt)) {
98
99
  const canonical = normToId.get(normalizeId(tgt));
99
- if (canonical) {
100
+ if (canonical !== undefined) {
100
101
  tgt = canonical;
101
102
  remapped = true;
102
103
  }
@@ -112,3 +113,26 @@ export function reconcileEdges(
112
113
 
113
114
  return { resolved, dangling, remappedCount };
114
115
  }
116
+
117
+ /**
118
+ * Format a ReconciliationResult as structured text for LLM consumption.
119
+ */
120
+ export function formatReconciliationResult(result: ReconciliationResult): string {
121
+ const total = result.resolved.length + result.dangling.length;
122
+ const lines: string[] = [
123
+ `Edge reconciliation: ${result.resolved.length}/${total} resolved, ${result.remappedCount} remapped, ${result.dangling.length} dangling`,
124
+ ];
125
+ if (result.dangling.length > 0) {
126
+ lines.push('');
127
+ lines.push('Dangling edges (unresolvable endpoints):');
128
+ const limit = Math.min(result.dangling.length, 20);
129
+ for (let i = 0; i < limit; i++) {
130
+ const e = result.dangling[i]!;
131
+ lines.push(` ${e.source} -> ${e.target}`);
132
+ }
133
+ if (result.dangling.length > limit) {
134
+ lines.push(` ... and ${result.dangling.length - limit} more`);
135
+ }
136
+ }
137
+ return lines.join('\n');
138
+ }
@@ -1,3 +1,4 @@
1
+ import Database from 'better-sqlite3';
1
2
  import type { MonographDb } from '../storage/db.js';
2
3
 
3
4
  export interface PageRankOptions {
@@ -9,6 +10,59 @@ export interface PageRankOptions {
9
10
  tolerance?: number;
10
11
  }
11
12
 
13
+ // ---------------------------------------------------------------------------
14
+ // Per-DB statement cache — avoids recompiling SQL on every pageRank() call.
15
+ // ---------------------------------------------------------------------------
16
+ interface StmtCache {
17
+ selectNodes: Database.Statement;
18
+ selectEdges: Database.Statement;
19
+ }
20
+
21
+ const _stmtCache = new Map<string, StmtCache>();
22
+
23
+ function getStmts(db: MonographDb): StmtCache {
24
+ const key = (db as unknown as { name: string }).name ?? '__default__';
25
+ let cache = _stmtCache.get(key);
26
+ if (!cache) {
27
+ cache = {
28
+ selectNodes: db.prepare('SELECT id FROM nodes'),
29
+ selectEdges: db.prepare('SELECT source_id, target_id FROM edges'),
30
+ };
31
+ _stmtCache.set(key, cache);
32
+ }
33
+ return cache;
34
+ }
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Result cache — avoids re-running power iteration when the graph hasn't
38
+ // changed. Keyed by (dbName, nodeCount, edgeCount) with a 5-second TTL.
39
+ // ---------------------------------------------------------------------------
40
+ const PAGERANK_CACHE_TTL_MS = 5_000;
41
+
42
+ interface PageRankCacheEntry {
43
+ result: Map<string, number>;
44
+ expiresAt: number;
45
+ }
46
+
47
+ const _resultCache = new Map<string, PageRankCacheEntry>();
48
+
49
+ function resultCacheKey(db: MonographDb, nodeCount: number, edgeCount: number): string {
50
+ const name = (db as unknown as { name: string }).name ?? '__default__';
51
+ return `${name}:${nodeCount}:${edgeCount}`;
52
+ }
53
+
54
+ /**
55
+ * Evict cached statements and results for a given DB instance (call after writes).
56
+ */
57
+ export function invalidatePageRankCache(db: MonographDb): void {
58
+ const key = (db as unknown as { name: string }).name ?? '__default__';
59
+ _stmtCache.delete(key);
60
+ // Purge all result-cache entries for this DB (they have the name as prefix)
61
+ for (const k of _resultCache.keys()) {
62
+ if (k.startsWith(`${key}:`)) _resultCache.delete(k);
63
+ }
64
+ }
65
+
12
66
  /**
13
67
  * Compute PageRank scores for all nodes using power iteration.
14
68
  *
@@ -16,6 +70,9 @@ export interface PageRankOptions {
16
70
  * After convergence the scores still sum to ~1 (standard normalized PageRank).
17
71
  * Dangling nodes (out-degree 0) distribute their rank equally to all nodes.
18
72
  *
73
+ * Results are cached for 5 seconds when the graph's node+edge counts are
74
+ * unchanged, making repeated calls (e.g. during context preloading) free.
75
+ *
19
76
  * @param db - The MonographDb instance
20
77
  * @param options - Optional tuning parameters
21
78
  * @returns Map of nodeId → PageRank score
@@ -23,14 +80,21 @@ export interface PageRankOptions {
23
80
  export function pageRank(db: MonographDb, options: PageRankOptions = {}): Map<string, number> {
24
81
  const { dampingFactor = 0.85, maxIterations = 100, tolerance = 1e-6 } = options;
25
82
 
26
- const nodeRows = db.prepare('SELECT id FROM nodes').all() as { id: string }[];
27
- const edgeRows = db.prepare('SELECT source_id, target_id FROM edges').all() as {
83
+ const stmts = getStmts(db);
84
+ const nodeRows = stmts.selectNodes.all() as { id: string }[];
85
+ const edgeRows = stmts.selectEdges.all() as {
28
86
  source_id: string;
29
87
  target_id: string;
30
88
  }[];
31
89
 
32
90
  if (nodeRows.length === 0) return new Map();
33
91
 
92
+ // Check result cache before running power iteration
93
+ const cacheKey = resultCacheKey(db, nodeRows.length, edgeRows.length);
94
+ const now = Date.now();
95
+ const cached = _resultCache.get(cacheKey);
96
+ if (cached && now < cached.expiresAt) return cached.result;
97
+
34
98
  const nodes = nodeRows.map(r => r.id);
35
99
  const n = nodes.length;
36
100
  const nodeIndex = new Map<string, number>();
@@ -84,5 +148,9 @@ export function pageRank(db: MonographDb, options: PageRankOptions = {}): Map<st
84
148
  for (let i = 0; i < n; i++) {
85
149
  result.set(nodes[i], scores[i]);
86
150
  }
151
+
152
+ // Store in result cache
153
+ _resultCache.set(cacheKey, { result, expiresAt: now + PAGERANK_CACHE_TTL_MS });
154
+
87
155
  return result;
88
156
  }
@@ -1,5 +1,11 @@
1
1
  import type { MonographDb } from '../storage/db.js';
2
2
 
3
+ export interface PruneResult {
4
+ danglingEdgesRemoved: number;
5
+ orphanNodesRemoved: number;
6
+ staleFileNodesRemoved: number;
7
+ }
8
+
3
9
  /**
4
10
  * Remove edges whose source or target node does not exist in the nodes table.
5
11
  *
@@ -17,3 +23,73 @@ export function pruneDanglingEdges(db: MonographDb): number {
17
23
 
18
24
  return result.changes;
19
25
  }
26
+
27
+ /**
28
+ * Remove non-File nodes that have no incoming or outgoing edges.
29
+ * Leaves File nodes in place so they can still be queried even if isolated.
30
+ *
31
+ * @param db - The MonographDb instance
32
+ * @returns Number of orphan nodes removed
33
+ */
34
+ export function pruneOrphanNodes(db: MonographDb): number {
35
+ const result = db.prepare(`
36
+ DELETE FROM nodes
37
+ WHERE label != 'File'
38
+ AND NOT EXISTS (SELECT 1 FROM edges WHERE source_id = nodes.id OR target_id = nodes.id)
39
+ `).run();
40
+ return result.changes;
41
+ }
42
+
43
+ /**
44
+ * Remove File nodes whose file_path no longer exists on disk.
45
+ * Also cascades to remove any edges connected to those nodes (via pruneDanglingEdges).
46
+ *
47
+ * @param db - The MonographDb instance
48
+ * @param existingPaths - Set of file paths that currently exist on disk
49
+ * @returns Number of stale File nodes removed (call pruneDanglingEdges after to clean edges)
50
+ */
51
+ export function pruneStaleFileNodes(db: MonographDb, existingPaths: Set<string>): number {
52
+ const staleRows = db.prepare(
53
+ `SELECT id, file_path FROM nodes WHERE label = 'File' AND file_path IS NOT NULL`,
54
+ ).all() as { id: string; file_path: string }[];
55
+
56
+ let removed = 0;
57
+ const deleteStmt = db.prepare(`DELETE FROM nodes WHERE id = ?`);
58
+ const tx = db.transaction(() => {
59
+ for (const row of staleRows) {
60
+ if (!existingPaths.has(row.file_path)) {
61
+ deleteStmt.run(row.id);
62
+ removed++;
63
+ }
64
+ }
65
+ });
66
+ tx();
67
+ return removed;
68
+ }
69
+
70
+ /**
71
+ * Run a full prune pass: dangling edges → orphan nodes → stale file nodes.
72
+ * Optionally pass existingPaths to also remove deleted-file nodes.
73
+ */
74
+ export function pruneAll(db: MonographDb, existingPaths?: Set<string>): PruneResult {
75
+ const danglingEdgesRemoved = pruneDanglingEdges(db);
76
+ const orphanNodesRemoved = pruneOrphanNodes(db);
77
+ const staleFileNodesRemoved = existingPaths ? pruneStaleFileNodes(db, existingPaths) : 0;
78
+ // A second dangling-edge pass cleans up any edges left by stale file node removal
79
+ if (staleFileNodesRemoved > 0) pruneDanglingEdges(db);
80
+ return { danglingEdgesRemoved, orphanNodesRemoved, staleFileNodesRemoved };
81
+ }
82
+
83
+ /**
84
+ * Format a PruneResult as structured text for LLM consumption.
85
+ */
86
+ export function formatPruneResult(result: PruneResult): string {
87
+ const total = result.danglingEdgesRemoved + result.orphanNodesRemoved + result.staleFileNodesRemoved;
88
+ if (total === 0) return 'Prune pass: nothing to remove — graph is clean.';
89
+ return [
90
+ `Prune pass removed ${total} artifact(s):`,
91
+ ` dangling edges: ${result.danglingEdgesRemoved}`,
92
+ ` orphan nodes: ${result.orphanNodesRemoved}`,
93
+ ` stale file nodes: ${result.staleFileNodesRemoved}`,
94
+ ].join('\n');
95
+ }