@monoes/monograph 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (460) hide show
  1. package/__tests__/mcp-tools/cypher.test.ts +10 -6
  2. package/__tests__/mcp-tools/detect-changes.test.ts +14 -11
  3. package/__tests__/pipeline/phases/wildcard-synthesis.test.ts +4 -4
  4. package/__tests__/search/embed-batch-config.test.ts +7 -7
  5. package/__tests__/search/hybrid-query.test.ts +3 -1
  6. package/__tests__/web/api.test.ts +1 -1
  7. package/dist/src/analysis/churn-cache.d.ts.map +1 -1
  8. package/dist/src/analysis/churn-cache.js +19 -6
  9. package/dist/src/analysis/churn-cache.js.map +1 -1
  10. package/dist/src/analysis/churn-trend.d.ts.map +1 -1
  11. package/dist/src/analysis/churn-trend.js +50 -6
  12. package/dist/src/analysis/churn-trend.js.map +1 -1
  13. package/dist/src/analysis/cluster-quality.d.ts +3 -0
  14. package/dist/src/analysis/cluster-quality.d.ts.map +1 -1
  15. package/dist/src/analysis/cluster-quality.js +18 -12
  16. package/dist/src/analysis/cluster-quality.js.map +1 -1
  17. package/dist/src/analysis/coverage-gaps.d.ts +3 -0
  18. package/dist/src/analysis/coverage-gaps.d.ts.map +1 -1
  19. package/dist/src/analysis/coverage-gaps.js +40 -1
  20. package/dist/src/analysis/coverage-gaps.js.map +1 -1
  21. package/dist/src/analysis/dep-classification.d.ts +2 -0
  22. package/dist/src/analysis/dep-classification.d.ts.map +1 -1
  23. package/dist/src/analysis/dep-classification.js +30 -0
  24. package/dist/src/analysis/dep-classification.js.map +1 -1
  25. package/dist/src/analysis/dependency-health.d.ts +4 -0
  26. package/dist/src/analysis/dependency-health.d.ts.map +1 -1
  27. package/dist/src/analysis/dependency-health.js +28 -0
  28. package/dist/src/analysis/dependency-health.js.map +1 -1
  29. package/dist/src/analysis/duplicate-exports.d.ts +9 -5
  30. package/dist/src/analysis/duplicate-exports.d.ts.map +1 -1
  31. package/dist/src/analysis/duplicate-exports.js +26 -2
  32. package/dist/src/analysis/duplicate-exports.js.map +1 -1
  33. package/dist/src/analysis/feature-flags.d.ts.map +1 -1
  34. package/dist/src/analysis/feature-flags.js +36 -17
  35. package/dist/src/analysis/feature-flags.js.map +1 -1
  36. package/dist/src/analysis/node-similarity.d.ts +19 -0
  37. package/dist/src/analysis/node-similarity.d.ts.map +1 -1
  38. package/dist/src/analysis/node-similarity.js +34 -3
  39. package/dist/src/analysis/node-similarity.js.map +1 -1
  40. package/dist/src/analysis/private-type-leaks.d.ts +4 -0
  41. package/dist/src/analysis/private-type-leaks.d.ts.map +1 -1
  42. package/dist/src/analysis/private-type-leaks.js +43 -2
  43. package/dist/src/analysis/private-type-leaks.js.map +1 -1
  44. package/dist/src/analysis/project-state.d.ts +2 -0
  45. package/dist/src/analysis/project-state.d.ts.map +1 -1
  46. package/dist/src/analysis/project-state.js +18 -7
  47. package/dist/src/analysis/project-state.js.map +1 -1
  48. package/dist/src/analysis/suppressions.d.ts.map +1 -1
  49. package/dist/src/analysis/suppressions.js +3 -1
  50. package/dist/src/analysis/suppressions.js.map +1 -1
  51. package/dist/src/analysis/trace.d.ts +5 -3
  52. package/dist/src/analysis/trace.d.ts.map +1 -1
  53. package/dist/src/analysis/trace.js +59 -53
  54. package/dist/src/analysis/trace.js.map +1 -1
  55. package/dist/src/analysis/unused-class-members.d.ts.map +1 -1
  56. package/dist/src/analysis/unused-class-members.js +7 -1
  57. package/dist/src/analysis/unused-class-members.js.map +1 -1
  58. package/dist/src/analysis/worker-pool.d.ts +3 -0
  59. package/dist/src/analysis/worker-pool.d.ts.map +1 -1
  60. package/dist/src/analysis/worker-pool.js +30 -8
  61. package/dist/src/analysis/worker-pool.js.map +1 -1
  62. package/dist/src/cli/augment.d.ts.map +1 -1
  63. package/dist/src/cli/augment.js +10 -3
  64. package/dist/src/cli/augment.js.map +1 -1
  65. package/dist/src/export/sarif.js +5 -5
  66. package/dist/src/export/sarif.js.map +1 -1
  67. package/dist/src/graph/analyzer.d.ts.map +1 -1
  68. package/dist/src/graph/analyzer.js +17 -2
  69. package/dist/src/graph/analyzer.js.map +1 -1
  70. package/dist/src/graph/author-analytics.d.ts +2 -0
  71. package/dist/src/graph/author-analytics.d.ts.map +1 -1
  72. package/dist/src/graph/author-analytics.js +39 -5
  73. package/dist/src/graph/author-analytics.js.map +1 -1
  74. package/dist/src/graph/baseline.d.ts +5 -0
  75. package/dist/src/graph/baseline.d.ts.map +1 -1
  76. package/dist/src/graph/baseline.js +65 -18
  77. package/dist/src/graph/baseline.js.map +1 -1
  78. package/dist/src/graph/clone-detector.d.ts.map +1 -1
  79. package/dist/src/graph/clone-detector.js +19 -33
  80. package/dist/src/graph/clone-detector.js.map +1 -1
  81. package/dist/src/graph/clone-families.d.ts +2 -0
  82. package/dist/src/graph/clone-families.d.ts.map +1 -1
  83. package/dist/src/graph/clone-families.js +38 -10
  84. package/dist/src/graph/clone-families.js.map +1 -1
  85. package/dist/src/graph/codeowners-extended.d.ts +17 -0
  86. package/dist/src/graph/codeowners-extended.d.ts.map +1 -1
  87. package/dist/src/graph/codeowners-extended.js +42 -0
  88. package/dist/src/graph/codeowners-extended.js.map +1 -1
  89. package/dist/src/graph/codeowners-gitlab.d.ts +21 -0
  90. package/dist/src/graph/codeowners-gitlab.d.ts.map +1 -1
  91. package/dist/src/graph/codeowners-gitlab.js +37 -0
  92. package/dist/src/graph/codeowners-gitlab.js.map +1 -1
  93. package/dist/src/graph/codeowners.d.ts +10 -0
  94. package/dist/src/graph/codeowners.d.ts.map +1 -1
  95. package/dist/src/graph/codeowners.js +24 -2
  96. package/dist/src/graph/codeowners.js.map +1 -1
  97. package/dist/src/graph/complexity.d.ts +10 -0
  98. package/dist/src/graph/complexity.d.ts.map +1 -1
  99. package/dist/src/graph/complexity.js +93 -43
  100. package/dist/src/graph/complexity.js.map +1 -1
  101. package/dist/src/graph/cross-reference-findings.d.ts.map +1 -1
  102. package/dist/src/graph/cross-reference-findings.js +19 -2
  103. package/dist/src/graph/cross-reference-findings.js.map +1 -1
  104. package/dist/src/graph/cross-reference.d.ts +4 -0
  105. package/dist/src/graph/cross-reference.d.ts.map +1 -1
  106. package/dist/src/graph/cross-reference.js +23 -0
  107. package/dist/src/graph/cross-reference.js.map +1 -1
  108. package/dist/src/graph/cycle-detection.d.ts +2 -0
  109. package/dist/src/graph/cycle-detection.d.ts.map +1 -1
  110. package/dist/src/graph/cycle-detection.js +26 -3
  111. package/dist/src/graph/cycle-detection.js.map +1 -1
  112. package/dist/src/graph/cycles.d.ts +11 -3
  113. package/dist/src/graph/cycles.d.ts.map +1 -1
  114. package/dist/src/graph/cycles.js +121 -83
  115. package/dist/src/graph/cycles.js.map +1 -1
  116. package/dist/src/graph/dead-code.d.ts +23 -0
  117. package/dist/src/graph/dead-code.d.ts.map +1 -1
  118. package/dist/src/graph/dead-code.js +53 -13
  119. package/dist/src/graph/dead-code.js.map +1 -1
  120. package/dist/src/graph/dependency-closure.d.ts.map +1 -1
  121. package/dist/src/graph/dependency-closure.js +20 -4
  122. package/dist/src/graph/dependency-closure.js.map +1 -1
  123. package/dist/src/graph/dfs.d.ts +14 -0
  124. package/dist/src/graph/dfs.d.ts.map +1 -1
  125. package/dist/src/graph/dfs.js +61 -0
  126. package/dist/src/graph/dfs.js.map +1 -1
  127. package/dist/src/graph/duplication-stats.d.ts.map +1 -1
  128. package/dist/src/graph/duplication-stats.js +19 -3
  129. package/dist/src/graph/duplication-stats.js.map +1 -1
  130. package/dist/src/graph/edge-filter.d.ts +2 -0
  131. package/dist/src/graph/edge-filter.d.ts.map +1 -1
  132. package/dist/src/graph/edge-filter.js +26 -12
  133. package/dist/src/graph/edge-filter.js.map +1 -1
  134. package/dist/src/graph/health-score.d.ts +8 -0
  135. package/dist/src/graph/health-score.d.ts.map +1 -1
  136. package/dist/src/graph/health-score.js +48 -17
  137. package/dist/src/graph/health-score.js.map +1 -1
  138. package/dist/src/graph/hotspots.d.ts +7 -0
  139. package/dist/src/graph/hotspots.d.ts.map +1 -1
  140. package/dist/src/graph/hotspots.js +50 -5
  141. package/dist/src/graph/hotspots.js.map +1 -1
  142. package/dist/src/graph/import-chain.d.ts +17 -1
  143. package/dist/src/graph/import-chain.d.ts.map +1 -1
  144. package/dist/src/graph/import-chain.js +68 -19
  145. package/dist/src/graph/import-chain.js.map +1 -1
  146. package/dist/src/graph/loader.d.ts.map +1 -1
  147. package/dist/src/graph/loader.js +9 -10
  148. package/dist/src/graph/loader.js.map +1 -1
  149. package/dist/src/graph/maintainability.d.ts +8 -0
  150. package/dist/src/graph/maintainability.d.ts.map +1 -1
  151. package/dist/src/graph/maintainability.js +65 -10
  152. package/dist/src/graph/maintainability.js.map +1 -1
  153. package/dist/src/graph/mirrored-dirs.d.ts +4 -0
  154. package/dist/src/graph/mirrored-dirs.d.ts.map +1 -1
  155. package/dist/src/graph/mirrored-dirs.js +48 -13
  156. package/dist/src/graph/mirrored-dirs.js.map +1 -1
  157. package/dist/src/graph/narrowing.d.ts +15 -0
  158. package/dist/src/graph/narrowing.d.ts.map +1 -1
  159. package/dist/src/graph/narrowing.js +33 -0
  160. package/dist/src/graph/narrowing.js.map +1 -1
  161. package/dist/src/graph/node-search.d.ts +3 -0
  162. package/dist/src/graph/node-search.d.ts.map +1 -1
  163. package/dist/src/graph/node-search.js +37 -4
  164. package/dist/src/graph/node-search.js.map +1 -1
  165. package/dist/src/graph/normalize-id.d.ts +4 -0
  166. package/dist/src/graph/normalize-id.d.ts.map +1 -1
  167. package/dist/src/graph/normalize-id.js +25 -2
  168. package/dist/src/graph/normalize-id.js.map +1 -1
  169. package/dist/src/graph/pagerank.d.ts +7 -0
  170. package/dist/src/graph/pagerank.d.ts.map +1 -1
  171. package/dist/src/graph/pagerank.js +49 -2
  172. package/dist/src/graph/pagerank.js.map +1 -1
  173. package/dist/src/graph/prune.d.ts +31 -0
  174. package/dist/src/graph/prune.d.ts.map +1 -1
  175. package/dist/src/graph/prune.js +65 -0
  176. package/dist/src/graph/prune.js.map +1 -1
  177. package/dist/src/graph/regex-search.d.ts +10 -2
  178. package/dist/src/graph/regex-search.d.ts.map +1 -1
  179. package/dist/src/graph/regex-search.js +33 -4
  180. package/dist/src/graph/regex-search.js.map +1 -1
  181. package/dist/src/graph/regression.d.ts +5 -0
  182. package/dist/src/graph/regression.d.ts.map +1 -1
  183. package/dist/src/graph/regression.js +45 -23
  184. package/dist/src/graph/regression.js.map +1 -1
  185. package/dist/src/graph/ripple-impact.d.ts +27 -0
  186. package/dist/src/graph/ripple-impact.d.ts.map +1 -1
  187. package/dist/src/graph/ripple-impact.js +55 -7
  188. package/dist/src/graph/ripple-impact.js.map +1 -1
  189. package/dist/src/graph/risk-profile.d.ts +7 -0
  190. package/dist/src/graph/risk-profile.d.ts.map +1 -1
  191. package/dist/src/graph/risk-profile.js +82 -48
  192. package/dist/src/graph/risk-profile.js.map +1 -1
  193. package/dist/src/graph/shingle-filter.d.ts.map +1 -1
  194. package/dist/src/graph/shingle-filter.js +25 -26
  195. package/dist/src/graph/shingle-filter.js.map +1 -1
  196. package/dist/src/graph/statistics.d.ts +19 -0
  197. package/dist/src/graph/statistics.d.ts.map +1 -1
  198. package/dist/src/graph/statistics.js +53 -40
  199. package/dist/src/graph/statistics.js.map +1 -1
  200. package/dist/src/graph/stats.d.ts +2 -0
  201. package/dist/src/graph/stats.d.ts.map +1 -1
  202. package/dist/src/graph/stats.js +54 -8
  203. package/dist/src/graph/stats.js.map +1 -1
  204. package/dist/src/graph/subgraph.d.ts +5 -0
  205. package/dist/src/graph/subgraph.d.ts.map +1 -1
  206. package/dist/src/graph/subgraph.js +73 -9
  207. package/dist/src/graph/subgraph.js.map +1 -1
  208. package/dist/src/graph/topo-sort.d.ts +15 -0
  209. package/dist/src/graph/topo-sort.d.ts.map +1 -1
  210. package/dist/src/graph/topo-sort.js +83 -10
  211. package/dist/src/graph/topo-sort.js.map +1 -1
  212. package/dist/src/graph/wcc.d.ts +29 -0
  213. package/dist/src/graph/wcc.d.ts.map +1 -1
  214. package/dist/src/graph/wcc.js +122 -39
  215. package/dist/src/graph/wcc.js.map +1 -1
  216. package/dist/src/index.js +1 -1
  217. package/dist/src/index.js.map +1 -1
  218. package/dist/src/mcp-tools/api-impact.d.ts.map +1 -1
  219. package/dist/src/mcp-tools/api-impact.js +13 -6
  220. package/dist/src/mcp-tools/api-impact.js.map +1 -1
  221. package/dist/src/mcp-tools/context.d.ts.map +1 -1
  222. package/dist/src/mcp-tools/context.js +19 -29
  223. package/dist/src/mcp-tools/context.js.map +1 -1
  224. package/dist/src/mcp-tools/cypher.d.ts +11 -0
  225. package/dist/src/mcp-tools/cypher.d.ts.map +1 -1
  226. package/dist/src/mcp-tools/cypher.js +13 -1
  227. package/dist/src/mcp-tools/cypher.js.map +1 -1
  228. package/dist/src/mcp-tools/detect-changes.d.ts.map +1 -1
  229. package/dist/src/mcp-tools/detect-changes.js +35 -20
  230. package/dist/src/mcp-tools/detect-changes.js.map +1 -1
  231. package/dist/src/mcp-tools/explain.d.ts.map +1 -1
  232. package/dist/src/mcp-tools/explain.js +4 -1
  233. package/dist/src/mcp-tools/explain.js.map +1 -1
  234. package/dist/src/mcp-tools/graph-query.d.ts +2 -0
  235. package/dist/src/mcp-tools/graph-query.d.ts.map +1 -1
  236. package/dist/src/mcp-tools/graph-query.js +20 -3
  237. package/dist/src/mcp-tools/graph-query.js.map +1 -1
  238. package/dist/src/mcp-tools/group-list.d.ts +1 -0
  239. package/dist/src/mcp-tools/group-list.d.ts.map +1 -1
  240. package/dist/src/mcp-tools/group-list.js +7 -9
  241. package/dist/src/mcp-tools/group-list.js.map +1 -1
  242. package/dist/src/mcp-tools/impact.d.ts.map +1 -1
  243. package/dist/src/mcp-tools/impact.js +20 -59
  244. package/dist/src/mcp-tools/impact.js.map +1 -1
  245. package/dist/src/mcp-tools/neighbors.d.ts.map +1 -1
  246. package/dist/src/mcp-tools/neighbors.js +24 -33
  247. package/dist/src/mcp-tools/neighbors.js.map +1 -1
  248. package/dist/src/mcp-tools/query.d.ts +2 -0
  249. package/dist/src/mcp-tools/query.d.ts.map +1 -1
  250. package/dist/src/mcp-tools/query.js +1 -0
  251. package/dist/src/mcp-tools/query.js.map +1 -1
  252. package/dist/src/mcp-tools/rename.d.ts.map +1 -1
  253. package/dist/src/mcp-tools/rename.js +12 -7
  254. package/dist/src/mcp-tools/rename.js.map +1 -1
  255. package/dist/src/mcp-tools/route-map.d.ts.map +1 -1
  256. package/dist/src/mcp-tools/route-map.js +19 -23
  257. package/dist/src/mcp-tools/route-map.js.map +1 -1
  258. package/dist/src/mcp-tools/shape-check.d.ts.map +1 -1
  259. package/dist/src/mcp-tools/shape-check.js +17 -13
  260. package/dist/src/mcp-tools/shape-check.js.map +1 -1
  261. package/dist/src/parsers/loader.js +6 -5
  262. package/dist/src/parsers/loader.js.map +1 -1
  263. package/dist/src/pipeline/cluster-only.d.ts.map +1 -1
  264. package/dist/src/pipeline/cluster-only.js +8 -4
  265. package/dist/src/pipeline/cluster-only.js.map +1 -1
  266. package/dist/src/pipeline/orchestrator.d.ts.map +1 -1
  267. package/dist/src/pipeline/orchestrator.js +19 -0
  268. package/dist/src/pipeline/orchestrator.js.map +1 -1
  269. package/dist/src/pipeline/phases/boundary.d.ts +2 -0
  270. package/dist/src/pipeline/phases/boundary.d.ts.map +1 -1
  271. package/dist/src/pipeline/phases/boundary.js +31 -2
  272. package/dist/src/pipeline/phases/boundary.js.map +1 -1
  273. package/dist/src/pipeline/phases/communities.d.ts +14 -0
  274. package/dist/src/pipeline/phases/communities.d.ts.map +1 -1
  275. package/dist/src/pipeline/phases/communities.js +58 -5
  276. package/dist/src/pipeline/phases/communities.js.map +1 -1
  277. package/dist/src/pipeline/phases/contextual-proximity.d.ts.map +1 -1
  278. package/dist/src/pipeline/phases/contextual-proximity.js +21 -14
  279. package/dist/src/pipeline/phases/contextual-proximity.js.map +1 -1
  280. package/dist/src/pipeline/phases/cross-file.d.ts.map +1 -1
  281. package/dist/src/pipeline/phases/cross-file.js +35 -14
  282. package/dist/src/pipeline/phases/cross-file.js.map +1 -1
  283. package/dist/src/pipeline/phases/field-access.d.ts.map +1 -1
  284. package/dist/src/pipeline/phases/field-access.js +9 -4
  285. package/dist/src/pipeline/phases/field-access.js.map +1 -1
  286. package/dist/src/pipeline/phases/god-nodes.d.ts +4 -0
  287. package/dist/src/pipeline/phases/god-nodes.d.ts.map +1 -1
  288. package/dist/src/pipeline/phases/god-nodes.js +43 -9
  289. package/dist/src/pipeline/phases/god-nodes.js.map +1 -1
  290. package/dist/src/pipeline/phases/import-resolver.d.ts +11 -0
  291. package/dist/src/pipeline/phases/import-resolver.d.ts.map +1 -1
  292. package/dist/src/pipeline/phases/import-resolver.js +34 -7
  293. package/dist/src/pipeline/phases/import-resolver.js.map +1 -1
  294. package/dist/src/pipeline/phases/leiden.d.ts.map +1 -1
  295. package/dist/src/pipeline/phases/leiden.js +27 -19
  296. package/dist/src/pipeline/phases/leiden.js.map +1 -1
  297. package/dist/src/pipeline/phases/markdown.d.ts.map +1 -1
  298. package/dist/src/pipeline/phases/markdown.js +10 -5
  299. package/dist/src/pipeline/phases/markdown.js.map +1 -1
  300. package/dist/src/pipeline/phases/pdf-parse.d.ts.map +1 -1
  301. package/dist/src/pipeline/phases/pdf-parse.js +18 -3
  302. package/dist/src/pipeline/phases/pdf-parse.js.map +1 -1
  303. package/dist/src/pipeline/phases/processes.d.ts.map +1 -1
  304. package/dist/src/pipeline/phases/processes.js +26 -9
  305. package/dist/src/pipeline/phases/processes.js.map +1 -1
  306. package/dist/src/pipeline/phases/re-export-propagation.d.ts.map +1 -1
  307. package/dist/src/pipeline/phases/re-export-propagation.js +33 -14
  308. package/dist/src/pipeline/phases/re-export-propagation.js.map +1 -1
  309. package/dist/src/pipeline/phases/reachability.d.ts.map +1 -1
  310. package/dist/src/pipeline/phases/reachability.js +42 -26
  311. package/dist/src/pipeline/phases/reachability.js.map +1 -1
  312. package/dist/src/pipeline/phases/routes.d.ts.map +1 -1
  313. package/dist/src/pipeline/phases/routes.js +30 -21
  314. package/dist/src/pipeline/phases/routes.js.map +1 -1
  315. package/dist/src/pipeline/phases/scope-resolution.d.ts.map +1 -1
  316. package/dist/src/pipeline/phases/scope-resolution.js +93 -63
  317. package/dist/src/pipeline/phases/scope-resolution.js.map +1 -1
  318. package/dist/src/pipeline/runner.d.ts +4 -0
  319. package/dist/src/pipeline/runner.d.ts.map +1 -1
  320. package/dist/src/pipeline/runner.js +30 -11
  321. package/dist/src/pipeline/runner.js.map +1 -1
  322. package/dist/src/query/cypher-parser.d.ts +20 -0
  323. package/dist/src/query/cypher-parser.d.ts.map +1 -1
  324. package/dist/src/query/cypher-parser.js +73 -0
  325. package/dist/src/query/cypher-parser.js.map +1 -1
  326. package/dist/src/search/embed-batch.d.ts.map +1 -1
  327. package/dist/src/search/embed-batch.js +4 -1
  328. package/dist/src/search/embed-batch.js.map +1 -1
  329. package/dist/src/search/hybrid-query.d.ts +4 -0
  330. package/dist/src/search/hybrid-query.d.ts.map +1 -1
  331. package/dist/src/search/hybrid-query.js +5 -1
  332. package/dist/src/search/hybrid-query.js.map +1 -1
  333. package/dist/src/staleness/git-staleness.js +3 -3
  334. package/dist/src/staleness/git-staleness.js.map +1 -1
  335. package/dist/src/storage/db.d.ts.map +1 -1
  336. package/dist/src/storage/db.js +12 -0
  337. package/dist/src/storage/db.js.map +1 -1
  338. package/dist/src/storage/edge-store.d.ts +2 -0
  339. package/dist/src/storage/edge-store.d.ts.map +1 -1
  340. package/dist/src/storage/edge-store.js +26 -1
  341. package/dist/src/storage/edge-store.js.map +1 -1
  342. package/dist/src/storage/embedding-store.d.ts +16 -0
  343. package/dist/src/storage/embedding-store.d.ts.map +1 -1
  344. package/dist/src/storage/embedding-store.js +26 -2
  345. package/dist/src/storage/embedding-store.js.map +1 -1
  346. package/dist/src/storage/file-cache.d.ts +5 -0
  347. package/dist/src/storage/file-cache.d.ts.map +1 -1
  348. package/dist/src/storage/file-cache.js +35 -9
  349. package/dist/src/storage/file-cache.js.map +1 -1
  350. package/dist/src/storage/fts-store.d.ts +4 -0
  351. package/dist/src/storage/fts-store.d.ts.map +1 -1
  352. package/dist/src/storage/fts-store.js +8 -3
  353. package/dist/src/storage/fts-store.js.map +1 -1
  354. package/dist/src/storage/node-store.d.ts +2 -0
  355. package/dist/src/storage/node-store.d.ts.map +1 -1
  356. package/dist/src/storage/node-store.js +28 -1
  357. package/dist/src/storage/node-store.js.map +1 -1
  358. package/dist/tsconfig.tsbuildinfo +1 -1
  359. package/package.json +1 -1
  360. package/src/__tests__/pipeline/runner.parallel.test.ts +2 -2
  361. package/src/analysis/churn-cache.ts +18 -7
  362. package/src/analysis/churn-trend.ts +35 -6
  363. package/src/analysis/cluster-quality.ts +20 -11
  364. package/src/analysis/coverage-gaps.ts +44 -1
  365. package/src/analysis/dep-classification.ts +34 -0
  366. package/src/analysis/dependency-health.ts +25 -0
  367. package/src/analysis/duplicate-exports.ts +40 -4
  368. package/src/analysis/feature-flags.ts +37 -17
  369. package/src/analysis/node-similarity.ts +45 -4
  370. package/src/analysis/private-type-leaks.ts +49 -2
  371. package/src/analysis/project-state.ts +19 -9
  372. package/src/analysis/suppressions.ts +4 -1
  373. package/src/analysis/trace.ts +73 -54
  374. package/src/analysis/unused-class-members.ts +8 -1
  375. package/src/analysis/worker-pool.ts +40 -8
  376. package/src/cli/augment.ts +10 -3
  377. package/src/export/sarif.ts +5 -5
  378. package/src/graph/analyzer.ts +26 -2
  379. package/src/graph/author-analytics.ts +45 -5
  380. package/src/graph/baseline.ts +78 -24
  381. package/src/graph/clone-detector.ts +22 -36
  382. package/src/graph/clone-families.ts +34 -10
  383. package/src/graph/codeowners-extended.ts +54 -0
  384. package/src/graph/codeowners-gitlab.ts +46 -0
  385. package/src/graph/codeowners.ts +33 -2
  386. package/src/graph/complexity.ts +102 -47
  387. package/src/graph/cross-reference-findings.ts +23 -2
  388. package/src/graph/cross-reference.ts +26 -0
  389. package/src/graph/cycle-detection.ts +22 -3
  390. package/src/graph/cycles.ts +155 -96
  391. package/src/graph/dead-code.ts +76 -17
  392. package/src/graph/dependency-closure.ts +24 -7
  393. package/src/graph/dfs.ts +78 -0
  394. package/src/graph/duplication-stats.ts +17 -6
  395. package/src/graph/edge-filter.ts +26 -12
  396. package/src/graph/health-score.ts +47 -17
  397. package/src/graph/hotspots.ts +57 -8
  398. package/src/graph/import-chain.ts +89 -19
  399. package/src/graph/loader.ts +10 -7
  400. package/src/graph/maintainability.ts +72 -22
  401. package/src/graph/mirrored-dirs.ts +45 -14
  402. package/src/graph/narrowing.ts +44 -0
  403. package/src/graph/node-search.ts +49 -4
  404. package/src/graph/normalize-id.ts +26 -2
  405. package/src/graph/pagerank.ts +69 -2
  406. package/src/graph/prune.ts +76 -0
  407. package/src/graph/regex-search.ts +38 -4
  408. package/src/graph/regression.ts +48 -23
  409. package/src/graph/ripple-impact.ts +71 -11
  410. package/src/graph/risk-profile.ts +98 -53
  411. package/src/graph/shingle-filter.ts +33 -27
  412. package/src/graph/statistics.ts +73 -45
  413. package/src/graph/stats.ts +50 -16
  414. package/src/graph/subgraph.ts +96 -25
  415. package/src/graph/topo-sort.ts +101 -16
  416. package/src/graph/wcc.ts +146 -37
  417. package/src/index.ts +1 -1
  418. package/src/mcp-tools/api-impact.ts +13 -6
  419. package/src/mcp-tools/context.ts +31 -40
  420. package/src/mcp-tools/cypher.ts +14 -1
  421. package/src/mcp-tools/detect-changes.ts +38 -23
  422. package/src/mcp-tools/explain.ts +4 -1
  423. package/src/mcp-tools/graph-query.ts +24 -3
  424. package/src/mcp-tools/group-list.ts +9 -11
  425. package/src/mcp-tools/impact.ts +26 -66
  426. package/src/mcp-tools/neighbors.ts +34 -39
  427. package/src/mcp-tools/query.ts +3 -0
  428. package/src/mcp-tools/rename.ts +9 -8
  429. package/src/mcp-tools/route-map.ts +21 -23
  430. package/src/mcp-tools/shape-check.ts +18 -14
  431. package/src/parsers/loader.ts +5 -5
  432. package/src/pipeline/cluster-only.ts +8 -4
  433. package/src/pipeline/orchestrator.ts +22 -0
  434. package/src/pipeline/phases/boundary.ts +40 -2
  435. package/src/pipeline/phases/communities.ts +66 -5
  436. package/src/pipeline/phases/contextual-proximity.ts +15 -14
  437. package/src/pipeline/phases/cross-file.ts +35 -14
  438. package/src/pipeline/phases/field-access.ts +11 -4
  439. package/src/pipeline/phases/god-nodes.ts +47 -10
  440. package/src/pipeline/phases/import-resolver.ts +37 -5
  441. package/src/pipeline/phases/leiden.ts +25 -18
  442. package/src/pipeline/phases/markdown.ts +12 -7
  443. package/src/pipeline/phases/pdf-parse.ts +19 -2
  444. package/src/pipeline/phases/processes.ts +27 -15
  445. package/src/pipeline/phases/re-export-propagation.ts +40 -18
  446. package/src/pipeline/phases/reachability.ts +39 -24
  447. package/src/pipeline/phases/routes.ts +32 -21
  448. package/src/pipeline/phases/scope-resolution.ts +110 -71
  449. package/src/pipeline/runner.ts +28 -11
  450. package/src/query/cypher-parser.ts +83 -0
  451. package/src/search/embed-batch.ts +4 -1
  452. package/src/search/hybrid-query.ts +11 -1
  453. package/src/staleness/git-staleness.ts +3 -3
  454. package/src/storage/db.ts +11 -0
  455. package/src/storage/edge-store.ts +34 -1
  456. package/src/storage/embedding-store.ts +38 -6
  457. package/src/storage/file-cache.ts +35 -21
  458. package/src/storage/fts-store.ts +12 -3
  459. package/src/storage/node-store.ts +39 -1
  460. package/src/coverage/runtime.ts +0 -249
@@ -118,13 +118,32 @@ export function getMonographImpact(
118
118
 
119
119
  // Reverse BFS to find all callers (depth 0 = start node)
120
120
  const visited = reverseBfs(nodeId, db, maxDepth, {});
121
+ return { node, ...extractCallerResult(db, nodeId, visited) };
122
+ }
123
+
124
+ // ── Shared helper: fetch nodes by IDs in a single query ───────────────────────
125
+
126
+ function getNodesByIds(db: Database.Database, ids: string[]): MonographNode[] {
127
+ if (ids.length === 0) return [];
128
+ const placeholders = ids.map(() => '?').join(',');
129
+ const rows = db
130
+ .prepare(`SELECT * FROM nodes WHERE id IN (${placeholders})`)
131
+ .all(...ids) as Record<string, unknown>[];
132
+ return rows.map(rowToNode);
133
+ }
134
+
135
+ // ── Shared helper: turn a visited map into structured caller lists ─────────────
121
136
 
122
- // Separate direct callers (depth 1) from transitive (depth 2+)
137
+ function extractCallerResult(
138
+ db: Database.Database,
139
+ startNodeId: string,
140
+ visited: Map<string, number>,
141
+ ): Omit<MonographImpactResult, 'node'> {
123
142
  const directCallerIds: string[] = [];
124
143
  const byDepth = new Map<number, string[]>();
125
144
 
126
145
  for (const [id, depth] of visited.entries()) {
127
- if (id === nodeId) continue;
146
+ if (id === startNodeId) continue;
128
147
  if (depth === 1) {
129
148
  directCallerIds.push(id);
130
149
  } else {
@@ -134,39 +153,24 @@ export function getMonographImpact(
134
153
  }
135
154
  }
136
155
 
137
- // Fetch node details for direct callers
138
- const getNodesByIds = (ids: string[]): MonographNode[] => {
139
- if (ids.length === 0) return [];
140
- const placeholders = ids.map(() => '?').join(',');
141
- const rows = db
142
- .prepare(`SELECT * FROM nodes WHERE id IN (${placeholders})`)
143
- .all(...ids) as Record<string, unknown>[];
144
- return rows.map(rowToNode);
145
- };
146
-
147
- const directCallers = getNodesByIds(directCallerIds);
156
+ const directCallers = getNodesByIds(db, directCallerIds);
148
157
 
149
158
  const transitiveCallers: Array<{ depth: number; nodes: MonographNode[] }> = [];
150
159
  const sortedDepths = Array.from(byDepth.keys()).sort((a, b) => a - b);
151
160
  for (const depth of sortedDepths) {
152
- const ids = byDepth.get(depth)!;
153
- transitiveCallers.push({ depth, nodes: getNodesByIds(ids) });
161
+ transitiveCallers.push({ depth, nodes: getNodesByIds(db, byDepth.get(depth)!) });
154
162
  }
155
163
 
156
- // Collect unique affected file paths (excluding start node)
157
164
  const allAffectedNodes = [...directCallers, ...transitiveCallers.flatMap(t => t.nodes)];
158
165
  const affectedFiles = [...new Set(
159
- allAffectedNodes
160
- .map(n => n.filePath)
161
- .filter((p): p is string => p != null),
166
+ allAffectedNodes.map(n => n.filePath).filter((p): p is string => p != null),
162
167
  )];
163
168
 
164
- // Risk score: log2(totalCallerCount + 1) normalized to [0, 1] (max log2(11) ≈ 3.46, capped at 10, /10)
165
169
  const totalCallerCount = visited.size - 1; // exclude start node
166
170
  const rawScore = Math.min(Math.log2(totalCallerCount + 1), 10);
167
171
  const riskScore = rawScore / 10;
168
172
 
169
- return { node, directCallers, transitiveCallers, affectedFiles, riskScore, riskLevel: computeRiskLevel(riskScore) };
173
+ return { directCallers, transitiveCallers, affectedFiles, riskScore, riskLevel: computeRiskLevel(riskScore) };
170
174
  }
171
175
 
172
176
  // ── id-based impact with filtering options ────────────────────────────────────
@@ -188,49 +192,5 @@ export async function monographImpact(
188
192
 
189
193
  const node = rowToNode(nodeRow);
190
194
  const visited = reverseBfs(nodeId, db, maxDepth, options);
191
-
192
- const directCallerIds: string[] = [];
193
- const byDepth = new Map<number, string[]>();
194
-
195
- for (const [id, depth] of visited.entries()) {
196
- if (id === nodeId) continue;
197
- if (depth === 1) {
198
- directCallerIds.push(id);
199
- } else {
200
- const existing = byDepth.get(depth) ?? [];
201
- existing.push(id);
202
- byDepth.set(depth, existing);
203
- }
204
- }
205
-
206
- const getNodesByIds = (ids: string[]): MonographNode[] => {
207
- if (ids.length === 0) return [];
208
- const placeholders = ids.map(() => '?').join(',');
209
- const rows = db
210
- .prepare(`SELECT * FROM nodes WHERE id IN (${placeholders})`)
211
- .all(...ids) as Record<string, unknown>[];
212
- return rows.map(rowToNode);
213
- };
214
-
215
- const directCallers = getNodesByIds(directCallerIds);
216
-
217
- const transitiveCallers: Array<{ depth: number; nodes: MonographNode[] }> = [];
218
- const sortedDepths = Array.from(byDepth.keys()).sort((a, b) => a - b);
219
- for (const depth of sortedDepths) {
220
- const ids = byDepth.get(depth)!;
221
- transitiveCallers.push({ depth, nodes: getNodesByIds(ids) });
222
- }
223
-
224
- const allAffectedNodes = [...directCallers, ...transitiveCallers.flatMap(t => t.nodes)];
225
- const affectedFiles = [...new Set(
226
- allAffectedNodes
227
- .map(n => n.filePath)
228
- .filter((p): p is string => p != null),
229
- )];
230
-
231
- const totalCallerCount = visited.size - 1;
232
- const rawScore = Math.min(Math.log2(totalCallerCount + 1), 10);
233
- const riskScore = rawScore / 10;
234
-
235
- return { node, directCallers, transitiveCallers, affectedFiles, riskScore, riskLevel: computeRiskLevel(riskScore) };
195
+ return { node, ...extractCallerResult(db, nodeId, visited) };
236
196
  }
@@ -30,6 +30,36 @@ export interface MonographNeighborsResult {
30
30
  neighbors: NeighborEntry[];
31
31
  }
32
32
 
33
+ // ── Shared edge query helper ──────────────────────────────────────────────────
34
+
35
+ function queryEdges(
36
+ db: Database.Database,
37
+ nodeId: string,
38
+ direction: 'outbound' | 'inbound',
39
+ relationFilter?: string,
40
+ ): NeighborEntry[] {
41
+ // outbound: source_id = nodeId → join target_id
42
+ // inbound: target_id = nodeId → join source_id
43
+ const [idCol, joinCol] = direction === 'outbound'
44
+ ? ['source_id', 'target_id']
45
+ : ['target_id', 'source_id'];
46
+
47
+ const sql = relationFilter
48
+ ? `SELECT n.*, e.relation, e.confidence, e.confidence_score FROM nodes n JOIN edges e ON n.id = e.${joinCol} WHERE e.${idCol} = ? AND e.relation = ? LIMIT 50`
49
+ : `SELECT n.*, e.relation, e.confidence, e.confidence_score FROM nodes n JOIN edges e ON n.id = e.${joinCol} WHERE e.${idCol} = ? LIMIT 50`;
50
+
51
+ const params = relationFilter ? [nodeId, relationFilter] : [nodeId];
52
+ const rows = db.prepare(sql).all(...params) as Record<string, unknown>[];
53
+
54
+ return rows.map(row => ({
55
+ node: rowToNode(row),
56
+ relation: row['relation'] as string,
57
+ confidence: row['confidence'] as string,
58
+ confidenceScore: (row['confidence_score'] as number) ?? 1,
59
+ direction,
60
+ }));
61
+ }
62
+
33
63
  export function getMonographNeighbors(
34
64
  db: Database.Database,
35
65
  input: { name: string; relationFilter?: string; includeInbound?: boolean },
@@ -41,45 +71,10 @@ export function getMonographNeighbors(
41
71
  if (!nodeRow) return { node: null, neighbors: [] };
42
72
 
43
73
  const node = rowToNode(nodeRow);
44
- const neighbors: NeighborEntry[] = [];
45
-
46
- // Outbound edges
47
- const outboundSql = input.relationFilter
48
- ? `SELECT n.*, e.relation, e.confidence, e.confidence_score FROM nodes n JOIN edges e ON n.id = e.target_id WHERE e.source_id = ? AND e.relation = ? LIMIT 50`
49
- : `SELECT n.*, e.relation, e.confidence, e.confidence_score FROM nodes n JOIN edges e ON n.id = e.target_id WHERE e.source_id = ? LIMIT 50`;
50
-
51
- const outboundParams = input.relationFilter ? [node.id, input.relationFilter] : [node.id];
52
- const outboundRows = db.prepare(outboundSql).all(...outboundParams) as Record<string, unknown>[];
53
-
54
- for (const row of outboundRows) {
55
- neighbors.push({
56
- node: rowToNode(row),
57
- relation: row['relation'] as string,
58
- confidence: row['confidence'] as string,
59
- confidenceScore: (row['confidence_score'] as number) ?? 1,
60
- direction: 'outbound',
61
- });
62
- }
63
-
64
- // Inbound edges
65
- if (input.includeInbound) {
66
- const inboundSql = input.relationFilter
67
- ? `SELECT n.*, e.relation, e.confidence, e.confidence_score FROM nodes n JOIN edges e ON n.id = e.source_id WHERE e.target_id = ? AND e.relation = ? LIMIT 50`
68
- : `SELECT n.*, e.relation, e.confidence, e.confidence_score FROM nodes n JOIN edges e ON n.id = e.source_id WHERE e.target_id = ? LIMIT 50`;
69
-
70
- const inboundParams = input.relationFilter ? [node.id, input.relationFilter] : [node.id];
71
- const inboundRows = db.prepare(inboundSql).all(...inboundParams) as Record<string, unknown>[];
72
-
73
- for (const row of inboundRows) {
74
- neighbors.push({
75
- node: rowToNode(row),
76
- relation: row['relation'] as string,
77
- confidence: row['confidence'] as string,
78
- confidenceScore: (row['confidence_score'] as number) ?? 1,
79
- direction: 'inbound',
80
- });
81
- }
82
- }
74
+ const neighbors: NeighborEntry[] = [
75
+ ...queryEdges(db, node.id, 'outbound', input.relationFilter),
76
+ ...(input.includeInbound ? queryEdges(db, node.id, 'inbound', input.relationFilter) : []),
77
+ ];
83
78
 
84
79
  return { node, neighbors };
85
80
  }
@@ -8,6 +8,8 @@ export interface QueryResult {
8
8
  label: string;
9
9
  name: string;
10
10
  filePath?: string;
11
+ /** Line number where the symbol is defined — enables direct file:line navigation. */
12
+ startLine?: number | null;
11
13
  score: number;
12
14
  isProcess: boolean;
13
15
  }
@@ -66,6 +68,7 @@ export const monographQueryTool = {
66
68
  label: h.label ?? 'Symbol',
67
69
  name: h.name ?? h.id,
68
70
  filePath: h.filePath ?? undefined,
71
+ startLine: h.startLine ?? null,
69
72
  score: h.score,
70
73
  isProcess: h.label === 'Process',
71
74
  }));
@@ -1,4 +1,4 @@
1
- import { readFileSync } from 'fs';
1
+ import { readFileSync, statSync } from 'fs';
2
2
  import type Database from 'better-sqlite3';
3
3
  import type { MonographNode } from '../types.js';
4
4
 
@@ -68,7 +68,10 @@ export function getMonographRename(
68
68
 
69
69
  // Build changes list by reading source files
70
70
  const changes: Array<{ file: string; line: number; before: string; after: string }> = [];
71
- const nameRegex = new RegExp(`\\b${escapeRegExp(input.oldName)}\\b`, 'g');
71
+ // Two separate regexes: testRe has no `g` flag (safe for repeated test()), replaceRe has `g`
72
+ const testRe = new RegExp(`\\b${escapeRegExp(input.oldName)}\\b`);
73
+ const replaceRe = new RegExp(`\\b${escapeRegExp(input.oldName)}\\b`, 'g');
74
+ const MAX_FILE_BYTES = 1_048_576; // 1 MiB guard
72
75
 
73
76
  // File line cache to avoid re-reading the same file multiple times
74
77
  const fileLineCache = new Map<string, string[]>();
@@ -76,6 +79,8 @@ export function getMonographRename(
76
79
  const getLines = (filePath: string): string[] => {
77
80
  if (fileLineCache.has(filePath)) return fileLineCache.get(filePath)!;
78
81
  try {
82
+ const st = statSync(filePath);
83
+ if (st.size > MAX_FILE_BYTES) { fileLineCache.set(filePath, []); return []; }
79
84
  const content = readFileSync(filePath, 'utf-8');
80
85
  const lines = content.split('\n');
81
86
  fileLineCache.set(filePath, lines);
@@ -94,13 +99,9 @@ export function getMonographRename(
94
99
  if (lineIdx < 0 || lineIdx >= lines.length) continue;
95
100
 
96
101
  const originalLine = lines[lineIdx];
97
- if (!nameRegex.test(originalLine)) {
98
- nameRegex.lastIndex = 0;
99
- continue;
100
- }
101
- nameRegex.lastIndex = 0;
102
+ if (!testRe.test(originalLine)) continue;
102
103
 
103
- const updatedLine = originalLine.replace(nameRegex, input.newName);
104
+ const updatedLine = originalLine.replace(replaceRe, input.newName);
104
105
  changes.push({
105
106
  file: row.file_path,
106
107
  line: row.start_line,
@@ -1,4 +1,4 @@
1
- import { readFileSync } from 'fs';
1
+ import { readFileSync, statSync } from 'fs';
2
2
  import type Database from 'better-sqlite3';
3
3
  import { extractMiddlewareChain } from '../pipeline/phases/middleware-extractor.js';
4
4
 
@@ -19,39 +19,34 @@ export interface MonographRouteMapResult {
19
19
  total: number;
20
20
  }
21
21
 
22
+ const MAX_FILE_BYTES = 1_048_576; // 1 MiB guard for middleware source reads
23
+
22
24
  // ── Implementation ─────────────────────────────────────────────────────────────
23
25
 
24
26
  export function getMonographRouteMap(
25
27
  db: Database.Database,
26
28
  input: { prefix?: string; method?: string; includeMiddleware?: boolean; repoPath?: string },
27
29
  ): MonographRouteMapResult {
28
- // 1. Query all Route nodes
29
- let routeRows = db
30
- .prepare("SELECT * FROM nodes WHERE label = 'Route'")
31
- .all() as Record<string, unknown>[];
30
+ // 1. Build SQL-level filters to avoid loading the whole Route table into JS
31
+ const conditions: string[] = ["label = 'Route'"];
32
+ const params: unknown[] = [];
32
33
 
33
- // 2. Apply prefix filter
34
34
  if (input.prefix) {
35
- const prefix = input.prefix;
36
- routeRows = routeRows.filter((row) => {
37
- const name = row.name as string;
38
- // name is like "GET /api/users" — find the path part
39
- const spaceIdx = name.indexOf(' ');
40
- const path = spaceIdx >= 0 ? name.slice(spaceIdx + 1) : name;
41
- return path.startsWith(prefix);
42
- });
35
+ // Route name format: "METHOD /path" — prefix applies to path portion after first space
36
+ conditions.push("name LIKE ?");
37
+ params.push(`% ${input.prefix}%`);
43
38
  }
44
39
 
45
- // 3. Apply method filter
46
40
  if (input.method) {
47
41
  const methodUpper = input.method.toUpperCase();
48
- routeRows = routeRows.filter((row) => {
49
- const name = row.name as string;
50
- return name.startsWith(methodUpper + ' ') || name.startsWith('ANY ');
51
- });
42
+ conditions.push("(name LIKE ? OR name LIKE ?)");
43
+ params.push(`${methodUpper} %`, 'ANY %');
52
44
  }
53
45
 
54
- // 4. For each route, find handler via HANDLES_ROUTE edge
46
+ const sql = `SELECT * FROM nodes WHERE ${conditions.join(' AND ')}`;
47
+ const routeRows = db.prepare(sql).all(...params) as Record<string, unknown>[];
48
+
49
+ // 2. For each route, find handler via HANDLES_ROUTE edge (prepared once)
55
50
  const handlerStmt = db.prepare(
56
51
  `SELECT n.name, n.file_path, n.start_line FROM nodes n
57
52
  JOIN edges e ON n.id = e.target_id
@@ -78,10 +73,13 @@ export function getMonographRouteMap(
78
73
  if (input.includeMiddleware && input.repoPath && handlerRow?.name && handlerRow?.file_path) {
79
74
  try {
80
75
  const absPath = `${input.repoPath}/${handlerRow.file_path}`;
81
- const source = readFileSync(absPath, 'utf-8');
82
- middlewareChain = extractMiddlewareChain(source, handlerRow.name).middlewareNames;
76
+ const st = statSync(absPath);
77
+ if (st.size <= MAX_FILE_BYTES) {
78
+ const source = readFileSync(absPath, 'utf-8');
79
+ middlewareChain = extractMiddlewareChain(source, handlerRow.name).middlewareNames;
80
+ }
83
81
  } catch {
84
- // File not found or unreadable — leave middlewareChain as []
82
+ // File not found, unreadable, or too large — leave middlewareChain as []
85
83
  }
86
84
  }
87
85
 
@@ -1,4 +1,4 @@
1
- import { readFileSync } from 'fs';
1
+ import { readFileSync, statSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import type Database from 'better-sqlite3';
4
4
  import {
@@ -24,8 +24,12 @@ export interface ShapeCheckResult {
24
24
 
25
25
  // ── Internal helpers ───────────────────────────────────────────────────────────
26
26
 
27
+ const MAX_FILE_BYTES = 1_048_576; // 1 MiB guard
28
+
27
29
  function safeReadFile(absPath: string): string {
28
30
  try {
31
+ const st = statSync(absPath);
32
+ if (st.size > MAX_FILE_BYTES) return '';
29
33
  return readFileSync(absPath, 'utf-8');
30
34
  } catch {
31
35
  return '';
@@ -64,19 +68,19 @@ export function getShapeCheck(
64
68
  .prepare("SELECT * FROM nodes WHERE label = 'Route' AND file_path = ? LIMIT 1")
65
69
  .get(options.file) as Record<string, unknown> | undefined;
66
70
  } else if (options.route) {
67
- // Search by name substring (name is like "GET /api/users")
68
- // We match against path part of the name to avoid matching the method token
69
- const allRoutes = db
70
- .prepare("SELECT * FROM nodes WHERE label = 'Route'")
71
- .all() as Record<string, unknown>[];
72
-
73
- const searchTerm = options.route.toLowerCase();
74
- routeRow = allRoutes.find((row) => {
75
- const name = (row.name as string).toLowerCase();
76
- const spaceIdx = name.indexOf(' ');
77
- const path = spaceIdx >= 0 ? name.slice(spaceIdx + 1) : name;
78
- return path.includes(searchTerm);
79
- });
71
+ // Search by name substring (name is like "GET /api/users") using SQL LIKE to avoid full scan
72
+ // The route path follows the first space, so we match anywhere after it via a LIKE pattern.
73
+ const likeTerm = `% ${options.route.toLowerCase()}%`;
74
+ routeRow = db
75
+ .prepare("SELECT * FROM nodes WHERE label = 'Route' AND lower(name) LIKE ? LIMIT 1")
76
+ .get(likeTerm) as Record<string, unknown> | undefined;
77
+
78
+ // Fallback: match anywhere in the name (e.g. no space prefix)
79
+ if (!routeRow) {
80
+ routeRow = db
81
+ .prepare("SELECT * FROM nodes WHERE label = 'Route' AND lower(name) LIKE ? LIMIT 1")
82
+ .get(`%${options.route.toLowerCase()}%`) as Record<string, unknown> | undefined;
83
+ }
80
84
  }
81
85
 
82
86
  if (!routeRow) {
@@ -70,14 +70,14 @@ export async function getParser(ext: string): Promise<{ parser: Parser; config:
70
70
  }
71
71
 
72
72
  try {
73
+ const lang = config.getLanguage();
74
+ if (!lang) throw new Error('getLanguage() returned undefined');
73
75
  const parser = new Parser();
74
- parser.setLanguage(config.getLanguage());
76
+ parser.setLanguage(lang);
75
77
  parserCache.set(ext, parser);
76
78
  return { parser, config };
77
- } catch (err) {
78
- // Grammar unavailable at runtime (ABI mismatch, native build failure, etc.).
79
- // Log a warning and skip the language gracefully.
80
- console.warn(`[monograph] warning: grammar for "${config.name}" (${ext}) could not be loaded: ${err}`);
79
+ } catch {
80
+ // Grammar unavailable at runtime (ABI mismatch, native build failure, etc.) — skip silently.
81
81
  return null;
82
82
  }
83
83
  }
@@ -43,18 +43,22 @@ export async function runClusterOnly(db: Database.Database): Promise<ClusterOnly
43
43
  }
44
44
  }
45
45
 
46
- // Write community assignments back to DB
46
+ // Write community assignments back to DB and count in a single pass
47
47
  const updateStmt = db.prepare('UPDATE nodes SET community_id = ? WHERE id = ?');
48
+ const communityIds = new Set<number>();
49
+ let nodeCount = 0;
48
50
  const updateAll = db.transaction(() => {
49
- for (const [nodeId, commId] of Object.entries(communities)) {
51
+ for (const nodeId in communities) {
52
+ const commId = communities[nodeId];
50
53
  updateStmt.run(commId, nodeId);
54
+ communityIds.add(commId);
55
+ nodeCount++;
51
56
  }
52
57
  });
53
58
  updateAll();
54
59
 
55
- const communityIds = new Set(Object.values(communities));
56
60
  return {
57
61
  communityCount: communityIds.size,
58
- nodeCount: Object.keys(communities).length,
62
+ nodeCount,
59
63
  };
60
64
  }
@@ -38,11 +38,33 @@ function getCurrentCommitHash(repoPath: string): string | null {
38
38
  export interface BuildOptions extends Partial<PipelineOptions> {
39
39
  onProgress?: (p: PipelineProgress) => void;
40
40
  force?: boolean;
41
+ /** When true, skip the full rebuild if the index is already fresh (matches HEAD). Default false. */
42
+ incremental?: boolean;
41
43
  }
42
44
 
43
45
  export async function buildAsync(repoPath: string, options: BuildOptions = {}): Promise<void> {
44
46
  const dbPath = resolve(join(repoPath, '.monomind', 'monograph.db'));
45
47
  const fullOptions: PipelineOptions = { ...DEFAULT_OPTIONS, ...options };
48
+
49
+ // Incremental guard: if the caller requested skip-when-fresh and force is
50
+ // not set, check staleness before opening the DB for a full write cycle.
51
+ if (options.incremental && !options.force) {
52
+ const { existsSync: _existsSync } = await import('fs');
53
+ if (_existsSync(dbPath)) {
54
+ const { checkStaleness } = await import('../staleness/git-staleness.js');
55
+ const tmpDb = openDb(dbPath);
56
+ try {
57
+ const report = checkStaleness(tmpDb, resolve(repoPath));
58
+ if (!report.isStale && report.currentCommit !== null) {
59
+ options.onProgress?.({ phase: 'skip', message: 'Index is fresh — skipping rebuild' });
60
+ return; // Already up-to-date
61
+ }
62
+ } finally {
63
+ closeDb(tmpDb);
64
+ }
65
+ }
66
+ }
67
+
46
68
  const db = openDb(dbPath);
47
69
 
48
70
  try {
@@ -34,6 +34,28 @@ function globToRegex(glob: string): RegExp {
34
34
  return new RegExp('^' + pattern + '$');
35
35
  }
36
36
 
37
+ /** Zone with its regex precompiled — built once per detectBoundaryViolations call. */
38
+ interface CompiledZone {
39
+ name: string;
40
+ re: RegExp;
41
+ }
42
+
43
+ /** Compile a ZoneConfig[] into CompiledZone[] once to avoid reconstructing RegExps per path. */
44
+ function compileZones(zones: ZoneConfig[]): CompiledZone[] {
45
+ return zones.map(z => ({ name: z.name, re: globToRegex(z.glob) }));
46
+ }
47
+
48
+ /**
49
+ * Classify a file path into a zone name using precompiled regexes.
50
+ * Returns null if no zone matches.
51
+ */
52
+ function classifyZoneCompiled(filePath: string, compiled: CompiledZone[]): string | null {
53
+ for (const z of compiled) {
54
+ if (z.re.test(filePath)) return z.name;
55
+ }
56
+ return null;
57
+ }
58
+
37
59
  /**
38
60
  * Load .monographrc.json from repoRoot. Returns empty config if not found or invalid.
39
61
  */
@@ -50,6 +72,8 @@ export function loadMonographConfig(repoRoot: string): MonographConfig {
50
72
 
51
73
  /**
52
74
  * Classify a file path into a zone name. Returns null if no zone matches.
75
+ * Compiles the glob patterns on every call — use classifyZoneCompiled with
76
+ * precompiled zones for hot paths.
53
77
  */
54
78
  export function classifyZone(filePath: string, zones: ZoneConfig[]): string | null {
55
79
  for (const zone of zones) {
@@ -78,6 +102,9 @@ export function detectBoundaryViolations(
78
102
  (config.allowedImports ?? []).map(([from, to]) => `${from}→${to}`),
79
103
  );
80
104
 
105
+ // Precompile zone regexes once — avoids re-constructing RegExp objects per file path.
106
+ const compiledZones = compileZones(zones);
107
+
81
108
  const rows = db.prepare(`
82
109
  SELECT e.id, e.relation,
83
110
  ns.file_path AS src_path,
@@ -88,11 +115,22 @@ export function detectBoundaryViolations(
88
115
  WHERE ns.file_path IS NOT NULL AND nt.file_path IS NOT NULL
89
116
  `).all() as { id: string; relation: string; src_path: string; tgt_path: string }[];
90
117
 
118
+ // Cache per-path zone classification to avoid O(rows * zones) repeated regex tests.
119
+ const pathZoneCache = new Map<string, string | null>();
120
+ const classifyPath = (p: string): string | null => {
121
+ let zone = pathZoneCache.get(p);
122
+ if (zone === undefined) {
123
+ zone = classifyZoneCompiled(p, compiledZones);
124
+ pathZoneCache.set(p, zone);
125
+ }
126
+ return zone;
127
+ };
128
+
91
129
  const violations: BoundaryViolation[] = [];
92
130
 
93
131
  for (const row of rows) {
94
- const fromZone = classifyZone(row.src_path, zones);
95
- const toZone = classifyZone(row.tgt_path, zones);
132
+ const fromZone = classifyPath(row.src_path);
133
+ const toZone = classifyPath(row.tgt_path);
96
134
 
97
135
  if (fromZone === null || toZone === null) continue;
98
136
  if (fromZone === toZone) continue;
@@ -40,6 +40,70 @@ export function computeCohesion(
40
40
  return internalCount / maxEdges;
41
41
  }
42
42
 
43
+ /**
44
+ * Compute cohesion scores for all communities in a single O(N+E) pass.
45
+ *
46
+ * This replaces calling `computeCohesion` inside a loop, which was O(K*(N+E))
47
+ * because each call re-scanned all memberships (O(N)) and all edges (O(E)).
48
+ *
49
+ * @param memberships - nodeId → communityId map from the clustering step
50
+ * @param edges - all edges used for the graph (IMPORTS + resolved)
51
+ * @returns Map of communityId → cohesion score ∈ [0, 1]
52
+ */
53
+ export function computeAllCohesionScores(
54
+ memberships: Map<string, number>,
55
+ edges: Array<{ sourceId: string; targetId: string }>,
56
+ ): Map<number, number> {
57
+ // Single pass over memberships: build communityId → Set<nodeId>
58
+ const memberSets = new Map<number, Set<string>>();
59
+ for (const [nodeId, commId] of memberships) {
60
+ let s = memberSets.get(commId);
61
+ if (!s) {
62
+ s = new Set();
63
+ memberSets.set(commId, s);
64
+ }
65
+ s.add(nodeId);
66
+ }
67
+
68
+ // Single pass over edges: count unique undirected internal edges per community
69
+ const internalEdgeCounts = new Map<number, number>();
70
+ const seenEdgeKeys = new Map<number, Set<string>>();
71
+
72
+ for (const e of edges) {
73
+ const commSrc = memberships.get(e.sourceId);
74
+ const commTgt = memberships.get(e.targetId);
75
+ if (commSrc === undefined || commTgt === undefined || commSrc !== commTgt) continue;
76
+
77
+ let seen = seenEdgeKeys.get(commSrc);
78
+ if (!seen) {
79
+ seen = new Set();
80
+ seenEdgeKeys.set(commSrc, seen);
81
+ }
82
+ const key = e.sourceId < e.targetId
83
+ ? `${e.sourceId}\0${e.targetId}`
84
+ : `${e.targetId}\0${e.sourceId}`;
85
+ if (!seen.has(key)) {
86
+ seen.add(key);
87
+ internalEdgeCounts.set(commSrc, (internalEdgeCounts.get(commSrc) ?? 0) + 1);
88
+ }
89
+ }
90
+
91
+ // Compute final scores
92
+ const scores = new Map<number, number>();
93
+ for (const [commId, members] of memberSets) {
94
+ const n = members.size;
95
+ if (n <= 1) {
96
+ scores.set(commId, 1);
97
+ continue;
98
+ }
99
+ const maxEdges = (n * (n - 1)) / 2;
100
+ const internalCount = internalEdgeCounts.get(commId) ?? 0;
101
+ scores.set(commId, maxEdges > 0 ? internalCount / maxEdges : 1);
102
+ }
103
+
104
+ return scores;
105
+ }
106
+
43
107
  export const communitiesPhase: PipelinePhase<CommunitiesOutput> = {
44
108
  name: 'communities',
45
109
  deps: ['parse', 'cross-file', 'mro'],
@@ -75,11 +139,8 @@ export const communitiesPhase: PipelinePhase<CommunitiesOutput> = {
75
139
  communityLabels.set(commId, `community-${commId}(${topNode.slice(0, 20)})`);
76
140
  }
77
141
 
78
- const communityIds = new Set([...memberships.values()]);
79
- const cohesionScores = new Map<number, number>();
80
- for (const cid of communityIds) {
81
- cohesionScores.set(cid, computeCohesion(cid, memberships, allUsedEdges));
82
- }
142
+ // Compute all cohesion scores in one O(N+E) pass instead of O(K*(N+E))
143
+ const cohesionScores = computeAllCohesionScores(memberships, allUsedEdges);
83
144
 
84
145
  return { memberships, communityLabels, cohesionScores };
85
146
  },