@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
@@ -1,14 +1,47 @@
1
1
  import { bfsFromNode } from 'graphology-traversal/bfs.js';
2
+ import type Graph from 'graphology';
2
3
  import { loadGraphFromDb } from './loader.js';
3
4
  import type { MonographDb } from '../storage/db.js';
4
5
 
6
+ // ---------------------------------------------------------------------------
7
+ // Module-level graph cache — avoids reloading the full graph on every call
8
+ // when multiple statistics functions are invoked in the same request cycle.
9
+ // TTL: 5 seconds; keyed by DB file path (better-sqlite3 exposes .name).
10
+ // ---------------------------------------------------------------------------
11
+ const GRAPH_CACHE_TTL_MS = 5_000;
12
+
13
+ interface GraphCacheEntry {
14
+ graph: Graph;
15
+ expiresAt: number;
16
+ }
17
+
18
+ const _graphCache = new Map<string, GraphCacheEntry>();
19
+
20
+ function getGraph(db: MonographDb): Graph {
21
+ const key = (db as unknown as { name: string }).name ?? '__default__';
22
+ const now = Date.now();
23
+ const cached = _graphCache.get(key);
24
+ if (cached && now < cached.expiresAt) return cached.graph;
25
+ const graph = loadGraphFromDb(db);
26
+ _graphCache.set(key, { graph, expiresAt: now + GRAPH_CACHE_TTL_MS });
27
+ return graph;
28
+ }
29
+
30
+ /**
31
+ * Evict the cached graph for a given DB instance (call after writes).
32
+ */
33
+ export function invalidateGraphCache(db: MonographDb): void {
34
+ const key = (db as unknown as { name: string }).name ?? '__default__';
35
+ _graphCache.delete(key);
36
+ }
37
+
5
38
  /**
6
39
  * Graph density: ratio of actual edges to maximum possible directed edges.
7
40
  * For a directed graph with n nodes: max = n * (n - 1).
8
41
  * Ignores self-loops.
9
42
  */
10
43
  export function graphDensity(db: MonographDb): number {
11
- const graph = loadGraphFromDb(db);
44
+ const graph = getGraph(db);
12
45
  const n = graph.order;
13
46
  if (n < 2) return 0;
14
47
  const maxEdges = n * (n - 1);
@@ -28,7 +61,7 @@ export function graphDensity(db: MonographDb): number {
28
61
  * We use the undirected approximation: treat edges as undirected, count triangles.
29
62
  */
30
63
  export function clusteringCoefficient(db: MonographDb): number {
31
- const graph = loadGraphFromDb(db);
64
+ const graph = getGraph(db);
32
65
  if (graph.order === 0) return 0;
33
66
 
34
67
  // Build undirected adjacency sets
@@ -67,67 +100,62 @@ export function clusteringCoefficient(db: MonographDb): number {
67
100
  return totalCoeff / countWithNeighbors;
68
101
  }
69
102
 
103
+ // ---------------------------------------------------------------------------
104
+ // Shared BFS result for path-length metrics
105
+ // ---------------------------------------------------------------------------
106
+
107
+ export interface PathStats {
108
+ /** Average shortest path length across all reachable pairs (i ≠ j). */
109
+ averagePathLength: number;
110
+ /** Maximum shortest path length (diameter) across all reachable pairs. */
111
+ diameter: number;
112
+ }
113
+
70
114
  /**
71
- * Average shortest path length across all reachable node pairs (i, j) where i ≠ j.
72
- * Computed using BFS from each node. Unreachable pairs are excluded from the average.
115
+ * Compute both average path length and graph diameter in a single BFS pass.
116
+ * Unreachable pairs are excluded from averagePathLength.
73
117
  */
74
- export function averagePathLength(db: MonographDb): number {
75
- const graph = loadGraphFromDb(db);
118
+ export function pathStats(db: MonographDb): PathStats {
119
+ const graph = getGraph(db);
76
120
  const nodes = graph.nodes();
77
- if (nodes.length < 2) return 0;
121
+ if (nodes.length < 2) return { averagePathLength: 0, diameter: 0 };
78
122
 
79
123
  let totalLength = 0;
80
124
  let reachablePairs = 0;
125
+ let maxDist = 0;
81
126
 
82
127
  for (const source of nodes) {
83
- const distances = new Map<string, number>();
84
- distances.set(source, 0);
85
-
86
128
  bfsFromNode(graph, source, (node, _attr, depth) => {
87
- if (node !== source) {
88
- distances.set(node, depth);
89
- }
90
- });
91
-
92
- for (const [target, dist] of distances) {
93
- if (target !== source && dist > 0) {
94
- totalLength += dist;
129
+ if (node !== source && depth > 0) {
130
+ totalLength += depth;
95
131
  reachablePairs++;
132
+ if (depth > maxDist) maxDist = depth;
96
133
  }
97
- }
134
+ });
98
135
  }
99
136
 
100
- if (reachablePairs === 0) return 0;
101
- return totalLength / reachablePairs;
137
+ return {
138
+ averagePathLength: reachablePairs === 0 ? 0 : totalLength / reachablePairs,
139
+ diameter: maxDist,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Average shortest path length across all reachable node pairs (i, j) where i ≠ j.
145
+ * Computed using BFS from each node. Unreachable pairs are excluded from the average.
146
+ *
147
+ * @deprecated Prefer `pathStats(db).averagePathLength` to share BFS with `graphDiameter`.
148
+ */
149
+ export function averagePathLength(db: MonographDb): number {
150
+ return pathStats(db).averagePathLength;
102
151
  }
103
152
 
104
153
  /**
105
154
  * Graph diameter: the maximum shortest path length across all reachable node pairs.
106
155
  * Returns 0 for empty or single-node graphs.
156
+ *
157
+ * @deprecated Prefer `pathStats(db).diameter` to share BFS with `averagePathLength`.
107
158
  */
108
159
  export function graphDiameter(db: MonographDb): number {
109
- const graph = loadGraphFromDb(db);
110
- const nodes = graph.nodes();
111
- if (nodes.length < 2) return 0;
112
-
113
- let maxDist = 0;
114
-
115
- for (const source of nodes) {
116
- const distances = new Map<string, number>();
117
- distances.set(source, 0);
118
-
119
- bfsFromNode(graph, source, (node, _attr, depth) => {
120
- if (node !== source) {
121
- distances.set(node, depth);
122
- }
123
- });
124
-
125
- for (const [target, dist] of distances) {
126
- if (target !== source && dist > maxDist) {
127
- maxDist = dist;
128
- }
129
- }
130
- }
131
-
132
- return maxDist;
160
+ return pathStats(db).diameter;
133
161
  }
@@ -1,5 +1,27 @@
1
1
  import type Database from 'better-sqlite3';
2
2
 
3
+ // Per-DB prepared statement cache to avoid re-parsing SQL on repeated calls
4
+ const stmtCache = new WeakMap<Database.Database, Map<string, Database.Statement>>();
5
+
6
+ function stmt(db: Database.Database, sql: string): Database.Statement {
7
+ let dbCache = stmtCache.get(db);
8
+ if (!dbCache) { dbCache = new Map(); stmtCache.set(db, dbCache); }
9
+ let s = dbCache.get(sql);
10
+ if (!s) { s = db.prepare(sql); dbCache.set(sql, s); }
11
+ return s;
12
+ }
13
+
14
+ /** Binary search: returns index of first element > value in a sorted ascending array. */
15
+ function upperBound(sorted: number[], value: number): number {
16
+ let lo = 0, hi = sorted.length;
17
+ while (lo < hi) {
18
+ const mid = (lo + hi) >>> 1;
19
+ if (sorted[mid] <= value) lo = mid + 1;
20
+ else hi = mid;
21
+ }
22
+ return lo;
23
+ }
24
+
3
25
  export type RiskBin = 'low' | 'medium' | 'high' | 'critical';
4
26
 
5
27
  export interface RiskProfile {
@@ -64,23 +86,21 @@ function buildRiskProfile(values: number[]): RiskProfile {
64
86
  * Compute full coupling profile from SQLite.
65
87
  */
66
88
  export function computeCouplingProfile(db: Database.Database): CouplingProfile {
67
- const fanInRows = db.prepare(
68
- 'SELECT target_id, COUNT(*) as c FROM edges GROUP BY target_id'
69
- ).all() as { target_id: string; c: number }[];
89
+ const fanInRows = stmt(db, 'SELECT target_id, COUNT(*) as c FROM edges GROUP BY target_id')
90
+ .all() as { target_id: string; c: number }[];
70
91
 
71
- const fanOutRows = db.prepare(
72
- 'SELECT source_id, COUNT(*) as c FROM edges GROUP BY source_id'
73
- ).all() as { source_id: string; c: number }[];
92
+ const fanOutRows = stmt(db, 'SELECT source_id, COUNT(*) as c FROM edges GROUP BY source_id')
93
+ .all() as { source_id: string; c: number }[];
74
94
 
75
- const totalFiles = (db.prepare(
76
- "SELECT COUNT(*) as n FROM nodes WHERE label = 'File'"
77
- ).get() as { n: number }).n;
95
+ const totalFiles = (stmt(db, "SELECT COUNT(*) as n FROM nodes WHERE label = 'File'")
96
+ .get() as { n: number }).n;
78
97
 
79
98
  const fanInValues = fanInRows.map(r => r.c).sort((a, b) => a - b);
80
99
  const fanOutValues = fanOutRows.map(r => r.c).sort((a, b) => a - b);
81
100
 
82
101
  const p95FanIn = percentile(fanInValues, 95);
83
- const couplingHighCount = fanInValues.filter(v => v > p95FanIn).length;
102
+ // Use binary search instead of O(N) filter to count elements above p95 threshold
103
+ const couplingHighCount = fanInValues.length - upperBound(fanInValues, p95FanIn);
84
104
  const couplingHighPct = fanInValues.length > 0
85
105
  ? Math.round((couplingHighCount / fanInValues.length) * 100)
86
106
  : 0;
@@ -101,14 +121,13 @@ export function computeCouplingProfile(db: Database.Database): CouplingProfile {
101
121
  * Quick stats summary (extends existing stats from monograph_stats MCP tool).
102
122
  */
103
123
  export function computeGraphStats(db: Database.Database): GraphStatsSummary {
104
- const nodeCount = (db.prepare('SELECT COUNT(*) as n FROM nodes').get() as { n: number }).n;
105
- const edgeCount = (db.prepare('SELECT COUNT(*) as n FROM edges').get() as { n: number }).n;
106
- const communityCount = (db.prepare(
124
+ const nodeCount = (stmt(db, 'SELECT COUNT(*) as n FROM nodes').get() as { n: number }).n;
125
+ const edgeCount = (stmt(db, 'SELECT COUNT(*) as n FROM edges').get() as { n: number }).n;
126
+ const communityCount = (stmt(db,
107
127
  "SELECT COUNT(DISTINCT community_id) as n FROM nodes WHERE community_id IS NOT NULL"
108
128
  ).get() as { n: number }).n;
109
- const fileCount = (db.prepare(
110
- "SELECT COUNT(*) as n FROM nodes WHERE label = 'File'"
111
- ).get() as { n: number }).n;
129
+ const fileCount = (stmt(db, "SELECT COUNT(*) as n FROM nodes WHERE label = 'File'")
130
+ .get() as { n: number }).n;
112
131
 
113
132
  const couplingProfile = computeCouplingProfile(db);
114
133
 
@@ -120,3 +139,18 @@ export function computeGraphStats(db: Database.Database): GraphStatsSummary {
120
139
  couplingProfile,
121
140
  };
122
141
  }
142
+
143
+ /** Format a GraphStatsSummary as structured text for LLM consumption. */
144
+ export function formatGraphStats(s: GraphStatsSummary): string {
145
+ const cp = s.couplingProfile;
146
+ const lines: string[] = [
147
+ `Graph Stats`,
148
+ ` Nodes: ${s.nodeCount} Edges: ${s.edgeCount} Communities: ${s.communityCount} Files: ${s.fileCount}`,
149
+ `Coupling Profile (${cp.totalFiles} files)`,
150
+ ` Fan-in p50/p75/p90/p95: ${cp.p50FanIn}/${cp.p75FanIn}/${cp.p90FanIn}/${cp.p95FanIn}`,
151
+ ` High-coupling (>p95): ${cp.couplingHighPct}%`,
152
+ `Fan-in Risk: low=${cp.fanInProfile.low}(${cp.fanInProfile.lowPct}%) med=${cp.fanInProfile.medium}(${cp.fanInProfile.mediumPct}%) high=${cp.fanInProfile.high}(${cp.fanInProfile.highPct}%) crit=${cp.fanInProfile.critical}(${cp.fanInProfile.criticalPct}%)`,
153
+ `Fan-out Risk: low=${cp.fanOutProfile.low}(${cp.fanOutProfile.lowPct}%) med=${cp.fanOutProfile.medium}(${cp.fanOutProfile.mediumPct}%) high=${cp.fanOutProfile.high}(${cp.fanOutProfile.highPct}%) crit=${cp.fanOutProfile.critical}(${cp.fanOutProfile.criticalPct}%)`,
154
+ ];
155
+ return lines.join('\n');
156
+ }
@@ -1,3 +1,4 @@
1
+ import Database from 'better-sqlite3';
1
2
  import type { MonographNode, MonographEdge } from '../types.js';
2
3
  import type { MonographDb } from '../storage/db.js';
3
4
 
@@ -21,46 +22,80 @@ export interface InducedSubgraph {
21
22
  // (source_id IN chunk), then filters target in-memory — full limit available per chunk.
22
23
  const SQLITE_VAR_LIMIT = 32766;
23
24
 
25
+ /** Cache of placeholder strings keyed by count: 1 → "?", 2 → "?,?", etc. */
26
+ const placeholderCache = new Map<number, string>();
27
+ function placeholders(n: number): string {
28
+ let ph = placeholderCache.get(n);
29
+ if (!ph) {
30
+ ph = Array(n).fill('?').join(',');
31
+ placeholderCache.set(n, ph);
32
+ }
33
+ return ph;
34
+ }
35
+
36
+ type RawNode = {
37
+ id: string; label: string; name: string; norm_label?: string | null; file_path: string | null;
38
+ start_line: number | null; end_line: number | null; community_id: number | null;
39
+ is_exported: number; language: string | null; properties: string | null;
40
+ };
41
+ type RawEdge = {
42
+ id: string; source_id: string; target_id: string; relation: string;
43
+ confidence: string; confidence_score: number; reason: string | null;
44
+ // evidence column omitted — not surfaced in InducedSubgraph
45
+ };
46
+
47
+ /**
48
+ * Prepared-statement caches per db instance — stored on the db object to avoid
49
+ * cross-db pollution while ensuring each unique chunk size is prepared at most once.
50
+ */
51
+ function getStmtCaches(db: MonographDb): {
52
+ nodes: Map<number, ReturnType<MonographDb['prepare']>>;
53
+ edges: Map<number, ReturnType<MonographDb['prepare']>>;
54
+ } {
55
+ const key = '__subgraphStmtCache__';
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ const dbAny = db as any;
58
+ if (!dbAny[key]) {
59
+ dbAny[key] = { nodes: new Map(), edges: new Map() };
60
+ }
61
+ return dbAny[key] as { nodes: Map<number, ReturnType<MonographDb['prepare']>>; edges: Map<number, ReturnType<MonographDb['prepare']>> };
62
+ }
63
+
24
64
  export function extractInducedSubgraph(db: MonographDb, nodeIds: string[]): InducedSubgraph {
25
65
  if (nodeIds.length === 0) return { nodes: [], edges: [] };
26
66
 
27
- // Chunk helper to stay within SQLITE_MAX_VARIABLE_NUMBER
28
- function queryChunked<T>(ids: string[], chunkSize: number, query: (ph: string, chunk: string[]) => T[]): T[] {
67
+ const stmts = getStmtCaches(db);
68
+
69
+ // Chunk helper — caches prepared statements by chunk size to avoid re-prepare per call.
70
+ function queryChunked<T>(ids: string[], chunkSize: number, stmtCache: Map<number, Database.Statement>, sql: (ph: string) => string): T[] {
29
71
  const results: T[] = [];
30
72
  for (let i = 0; i < ids.length; i += chunkSize) {
31
73
  const chunk = ids.slice(i, i + chunkSize);
32
- const ph = chunk.map(() => '?').join(',');
33
- results.push(...query(ph, chunk));
74
+ const ph = placeholders(chunk.length);
75
+ let stmt = stmtCache.get(chunk.length);
76
+ if (!stmt) { stmt = db.prepare(sql(ph)); stmtCache.set(chunk.length, stmt); }
77
+ results.push(...(stmt.all(...chunk) as T[]));
34
78
  }
35
79
  return results;
36
80
  }
37
81
 
38
- type RawNode = {
39
- id: string; label: string; name: string; norm_label?: string | null; file_path: string | null;
40
- start_line: number | null; end_line: number | null; community_id: number | null;
41
- is_exported: number; language: string | null; properties: string | null;
42
- };
43
- type RawEdge = {
44
- id: string; source_id: string; target_id: string; relation: string;
45
- confidence: string; confidence_score: number; reason: string | null; evidence: string | null;
46
- };
47
-
48
- const rawNodes = queryChunked<RawNode>(nodeIds, SQLITE_VAR_LIMIT, (ph, chunk) =>
49
- db.prepare(
50
- `SELECT id, label, name, file_path, start_line, end_line, community_id, is_exported, language, properties
51
- FROM nodes WHERE id IN (${ph})`
52
- ).all(...chunk) as RawNode[]
82
+ const rawNodes = queryChunked<RawNode>(
83
+ nodeIds,
84
+ SQLITE_VAR_LIMIT,
85
+ stmts.nodes,
86
+ ph => `SELECT id, label, name, file_path, start_line, end_line, community_id, is_exported, language, properties FROM nodes WHERE id IN (${ph})`,
53
87
  );
54
88
 
55
89
  // For edges: query by source_id chunks (1 bind per row), then filter target in-memory.
56
- // Querying by both source+target would miss cross-chunk edges and requires 2× bind slots.
57
90
  const nodeSet = new Set(nodeIds);
58
- const rawEdges = queryChunked<RawEdge>(nodeIds, SQLITE_VAR_LIMIT, (ph, chunk) =>
59
- (db.prepare(
60
- `SELECT id, source_id, target_id, relation, confidence, confidence_score, reason, evidence
61
- FROM edges WHERE source_id IN (${ph})`
62
- ).all(...chunk) as RawEdge[]).filter(e => nodeSet.has(e.target_id))
91
+ const allSourceEdges = queryChunked<RawEdge>(
92
+ nodeIds,
93
+ SQLITE_VAR_LIMIT,
94
+ stmts.edges,
95
+ // Drop unused 'evidence' column to reduce row data transfer
96
+ ph => `SELECT id, source_id, target_id, relation, confidence, confidence_score, reason FROM edges WHERE source_id IN (${ph})`,
63
97
  );
98
+ const rawEdges = allSourceEdges.filter(e => nodeSet.has(e.target_id));
64
99
 
65
100
  const nodes: MonographNode[] = rawNodes.map(n => ({
66
101
  id: n.id,
@@ -88,3 +123,40 @@ export function extractInducedSubgraph(db: MonographDb, nodeIds: string[]): Indu
88
123
 
89
124
  return { nodes, edges };
90
125
  }
126
+
127
+ /**
128
+ * Format an InducedSubgraph as structured text for LLM consumption.
129
+ * Groups nodes by file path and lists edges with relation + file:line hints.
130
+ */
131
+ export function formatInducedSubgraph(sg: InducedSubgraph): string {
132
+ if (sg.nodes.length === 0) return 'Subgraph is empty.';
133
+
134
+ const lines: string[] = [`Subgraph: ${sg.nodes.length} nodes, ${sg.edges.length} edges`];
135
+
136
+ // Group nodes by file
137
+ const byFile = new Map<string, MonographNode[]>();
138
+ for (const n of sg.nodes) {
139
+ const file = n.filePath ?? '(unknown)';
140
+ let list = byFile.get(file);
141
+ if (!list) { list = []; byFile.set(file, list); }
142
+ list.push(n);
143
+ }
144
+
145
+ lines.push('\nNodes:');
146
+ for (const [file, nodes] of byFile) {
147
+ lines.push(` ${file}`);
148
+ for (const n of nodes) {
149
+ const loc = n.startLine ? `:${n.startLine}` : '';
150
+ lines.push(` ${n.label} ${n.name}${loc}`);
151
+ }
152
+ }
153
+
154
+ if (sg.edges.length > 0) {
155
+ lines.push('\nEdges:');
156
+ for (const e of sg.edges) {
157
+ lines.push(` ${e.sourceId} --[${e.relation}]--> ${e.targetId}`);
158
+ }
159
+ }
160
+
161
+ return lines.join('\n');
162
+ }
@@ -16,35 +16,49 @@ export interface TopoSortResult {
16
16
  * Files with no incoming edges on the reverse graph (i.e., no one imports them)
17
17
  * are leaves and appear in level 0.
18
18
  *
19
+ * The in-degree computation is pushed into SQL (GROUP BY aggregation) to avoid
20
+ * materialising the full edge table into JavaScript memory.
21
+ *
19
22
  * @param db - The MonographDb instance
20
23
  * @returns Object with `levels` (array of independent groups, leaf-first) and `cycleCount`.
21
24
  */
22
25
  export function topologicalLevelSort(db: MonographDb): TopoSortResult {
23
26
  const nodeRows = db.prepare('SELECT id FROM nodes').all() as { id: string }[];
24
- const edgeRows = db.prepare('SELECT source_id, target_id FROM edges').all() as {
25
- source_id: string;
26
- target_id: string;
27
- }[];
28
-
29
27
  if (nodeRows.length === 0) return { levels: [], cycleCount: 0 };
30
28
 
31
29
  const nodes = nodeRows.map(r => r.id);
32
30
 
33
- // Build reverse graph: original edge src→tgt becomes tgt→src in reverse
34
- // In-degree in the reverse graph = number of nodes that tgt imports (out-degree in original)
35
- const reverseAdj = new Map<string, string[]>();
36
- const inDegree = new Map<string, number>();
31
+ // Compute in-degree (on the reverse graph) via SQL aggregation.
32
+ // In the original graph each edge src→tgt means "src imports tgt".
33
+ // In the reverse graph the edge becomes tgt→src, so src gains in-degree +1.
34
+ // We aggregate target_id counts grouped by source_id to get reverse-in-degree per node.
35
+ const inDegreeRows = db
36
+ .prepare(
37
+ `SELECT source_id AS node_id, COUNT(*) AS cnt
38
+ FROM edges
39
+ WHERE source_id != target_id
40
+ GROUP BY source_id`,
41
+ )
42
+ .all() as { node_id: string; cnt: number }[];
37
43
 
38
- for (const n of nodes) {
39
- reverseAdj.set(n, []);
40
- inDegree.set(n, 0);
44
+ const inDegree = new Map<string, number>();
45
+ for (const n of nodes) inDegree.set(n, 0);
46
+ for (const { node_id, cnt } of inDegreeRows) {
47
+ if (inDegree.has(node_id)) inDegree.set(node_id, cnt);
41
48
  }
42
49
 
50
+ // Build reverse adjacency list — still needed for Kahn's BFS relaxation step.
51
+ // Fetch only what Kahn needs: target_id → [source_id, ...] (reverse direction).
52
+ const reverseAdj = new Map<string, string[]>();
53
+ for (const n of nodes) reverseAdj.set(n, []);
54
+
55
+ const edgeRows = db
56
+ .prepare('SELECT source_id, target_id FROM edges WHERE source_id != target_id')
57
+ .all() as { source_id: string; target_id: string }[];
58
+
43
59
  for (const { source_id: src, target_id: tgt } of edgeRows) {
44
- if (src === tgt) continue; // skip self-loops
45
60
  if (!reverseAdj.has(tgt) || !reverseAdj.has(src)) continue;
46
61
  reverseAdj.get(tgt)!.push(src);
47
- inDegree.set(src, (inDegree.get(src) ?? 0) + 1);
48
62
  }
49
63
 
50
64
  // Kahn's BFS on the reverse graph
@@ -57,7 +71,6 @@ export function topologicalLevelSort(db: MonographDb): TopoSortResult {
57
71
  const visited = new Set<string>();
58
72
 
59
73
  while (queue.length > 0) {
60
- // All nodes currently at in-degree 0 form one level
61
74
  const level = [...queue];
62
75
  queue.length = 0;
63
76
  levels.push(level);
@@ -76,7 +89,6 @@ export function topologicalLevelSort(db: MonographDb): TopoSortResult {
76
89
  // Remaining unvisited nodes are in cycles
77
90
  const cycleNodes = nodes.filter(n => !visited.has(n));
78
91
  let cycleCount = 0;
79
-
80
92
  if (cycleNodes.length > 0) {
81
93
  cycleCount = cycleNodes.length;
82
94
  levels.push(cycleNodes);
@@ -84,3 +96,76 @@ export function topologicalLevelSort(db: MonographDb): TopoSortResult {
84
96
 
85
97
  return { levels, cycleCount };
86
98
  }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Structured text formatter for LLM consumption
102
+ // ---------------------------------------------------------------------------
103
+
104
+ /**
105
+ * Format topological sort levels as structured text.
106
+ *
107
+ * Resolves node IDs to names and file paths so LLMs can navigate directly
108
+ * to the source. Cycle nodes (if any) are clearly labelled.
109
+ *
110
+ * @param db - The MonographDb instance (for name resolution)
111
+ * @param result - Result from topologicalLevelSort()
112
+ * @param maxLevels - Max levels to include in output (default: all)
113
+ * @returns Structured text suitable for injection into LLM context
114
+ */
115
+ export function formatTopoSort(
116
+ db: MonographDb,
117
+ result: TopoSortResult,
118
+ maxLevels?: number,
119
+ ): string {
120
+ if (result.levels.length === 0) {
121
+ return 'Topological sort: no nodes found.';
122
+ }
123
+
124
+ // Batch-resolve all node IDs
125
+ const allIds = result.levels.flat();
126
+ const CHUNK = 200;
127
+ const nodeInfo = new Map<string, { name: string; filePath: string | null }>();
128
+
129
+ for (let i = 0; i < allIds.length; i += CHUNK) {
130
+ const chunk = allIds.slice(i, i + CHUNK);
131
+ const ph = chunk.map(() => '?').join(',');
132
+ const rows = db
133
+ .prepare(`SELECT id, name, file_path FROM nodes WHERE id IN (${ph})`)
134
+ .all(...chunk) as { id: string; name: string; file_path: string | null }[];
135
+ for (const row of rows) {
136
+ nodeInfo.set(row.id, { name: row.name ?? row.id, filePath: row.file_path });
137
+ }
138
+ }
139
+
140
+ const isCycleLevelIdx =
141
+ result.cycleCount > 0 && result.levels.length > 0
142
+ ? result.levels.length - 1
143
+ : -1;
144
+
145
+ const displayLevels = maxLevels !== undefined ? result.levels.slice(0, maxLevels) : result.levels;
146
+
147
+ const lines: string[] = [
148
+ `Topological sort: ${result.levels.length - (result.cycleCount > 0 ? 1 : 0)} level${result.levels.length === 1 ? '' : 's'}` +
149
+ (result.cycleCount > 0 ? `, ${result.cycleCount} node${result.cycleCount === 1 ? '' : 's'} in cycles` : ''),
150
+ '',
151
+ ];
152
+
153
+ for (let li = 0; li < displayLevels.length; li++) {
154
+ const level = displayLevels[li];
155
+ const label = li === isCycleLevelIdx ? 'Cycle nodes (unresolvable order)' : `Level ${li}`;
156
+ lines.push(`${label} (${level.length} node${level.length === 1 ? '' : 's'}):`);
157
+ for (const id of level) {
158
+ const info = nodeInfo.get(id);
159
+ const name = info?.name ?? id;
160
+ const fp = info?.filePath;
161
+ lines.push(` ${name}${fp ? ` — ${fp}` : ''}`);
162
+ }
163
+ lines.push('');
164
+ }
165
+
166
+ if (maxLevels !== undefined && result.levels.length > maxLevels) {
167
+ lines.push(`... (${result.levels.length - maxLevels} more level${result.levels.length - maxLevels === 1 ? '' : 's'} omitted)`);
168
+ }
169
+
170
+ return lines.join('\n').trimEnd();
171
+ }