@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
@@ -73,17 +73,30 @@ export function classifyReachability(
73
73
  }
74
74
  }
75
75
 
76
- // ── BFS from entry points forward direction (walk what files import) ───────
76
+ // ── Preload forward adjacency map for BFS (one query vs N per BFS node) ────────
77
+ const forwardEdges = new Map<string, string[]>();
78
+ const fwdRows = db.prepare(
79
+ `SELECT source_id, target_id FROM edges WHERE relation IN ('IMPORTS', 'RE_EXPORTS')`,
80
+ ).all() as { source_id: string; target_id: string }[];
81
+ for (const row of fwdRows) {
82
+ let targets = forwardEdges.get(row.source_id);
83
+ if (!targets) {
84
+ targets = [];
85
+ forwardEdges.set(row.source_id, targets);
86
+ }
87
+ targets.push(row.target_id);
88
+ }
89
+
90
+ // ── BFS from entry points — uses preloaded adjacency (no per-node DB query) ──
77
91
  const bfs = (startIds: Set<string>, role: ReachabilityRole) => {
78
92
  const queue = Array.from(startIds);
79
93
  const visited = new Set(startIds);
80
94
  while (queue.length > 0) {
81
95
  const nodeId = queue.shift()!;
82
- // Follow forward edges: find files that this file imports
83
- const imported = db.prepare(
84
- `SELECT DISTINCT target_id FROM edges WHERE source_id = ? AND relation IN ('IMPORTS', 'RE_EXPORTS')`,
85
- ).all(nodeId) as { target_id: string }[];
86
- for (const { target_id } of imported) {
96
+ // O(1) adjacency lookup instead of per-node DB query
97
+ const targets = forwardEdges.get(nodeId);
98
+ if (!targets) continue;
99
+ for (const target_id of targets) {
87
100
  if (!visited.has(target_id)) {
88
101
  visited.add(target_id);
89
102
  // Set role only if not already set to a higher-priority role
@@ -102,24 +115,26 @@ export function classifyReachability(
102
115
  bfs(testEntryIds, 'test');
103
116
  bfs(runtimeEntryIds, 'runtime');
104
117
 
105
- // ── Persist roles and count ───────────────────────────────────────────────────
106
- let runtime = 0, test = 0, support = 0, unreachable = 0;
107
- for (const node of allFileNodes) {
108
- const role = roleMap.get(node.id) ?? 'unreachable';
109
- let props: Record<string, unknown> = {};
110
- try {
111
- if (node.properties) props = JSON.parse(node.properties);
112
- } catch { /* ignore parse errors */ }
113
- props.reachabilityRole = role;
114
- db.prepare(`UPDATE nodes SET properties = ? WHERE id = ?`)
115
- .run(JSON.stringify(props), node.id);
116
- if (role === 'runtime') runtime++;
117
- else if (role === 'test') test++;
118
- else if (role === 'support') support++;
119
- else unreachable++;
120
- }
121
-
122
- return { runtime, test, support, unreachable };
118
+ // ── Persist roles and count — batch UPDATE in transaction ────────────────────
119
+ const updateStmt = db.prepare(`UPDATE nodes SET properties = ? WHERE id = ?`);
120
+ const persistAll = db.transaction(() => {
121
+ let runtime = 0, test = 0, support = 0, unreachable = 0;
122
+ for (const node of allFileNodes) {
123
+ const role = roleMap.get(node.id) ?? 'unreachable';
124
+ let props: Record<string, unknown> = {};
125
+ try {
126
+ if (node.properties) props = JSON.parse(node.properties);
127
+ } catch { /* ignore parse errors */ }
128
+ props.reachabilityRole = role;
129
+ updateStmt.run(JSON.stringify(props), node.id);
130
+ if (role === 'runtime') runtime++;
131
+ else if (role === 'test') test++;
132
+ else if (role === 'support') support++;
133
+ else unreachable++;
134
+ }
135
+ return { runtime, test, support, unreachable };
136
+ });
137
+ return persistAll();
123
138
  }
124
139
 
125
140
  /**
@@ -46,6 +46,23 @@ export const routesPhase: PipelinePhase<RoutesOutput> = {
46
46
  const routeNodes: MonographNode[] = [];
47
47
  const handlesEdges: MonographEdge[] = [];
48
48
 
49
+ // ── Preload handler index: name:filePath → nodeId (one DB query vs N per route) ──
50
+ // Keyed as `${name}\0${filePath}` for O(1) lookup during route extraction.
51
+ const handlerIndex = new Map<string, string>();
52
+ if (ctx.db) {
53
+ const handlerRows = ctx.db
54
+ .prepare(`SELECT id, name, file_path FROM nodes WHERE file_path IS NOT NULL`)
55
+ .all() as { id: string; name: string; file_path: string }[];
56
+ for (const row of handlerRows) {
57
+ handlerIndex.set(`${row.name}\0${row.file_path}`, row.id);
58
+ }
59
+ }
60
+
61
+ /** O(1) handler lookup using the preloaded index */
62
+ function lookupHandler(handlerName: string, filePath: string): string | undefined {
63
+ return handlerIndex.get(`${handlerName}\0${filePath}`);
64
+ }
65
+
49
66
  for (const fileNode of fileNodes) {
50
67
  const relPath = fileNode.filePath ?? '';
51
68
  const ext = extname(relPath).toLowerCase();
@@ -84,20 +101,18 @@ export const routesPhase: PipelinePhase<RoutesOutput> = {
84
101
  if (source) {
85
102
  const handlerName = extractDefaultExportName(source);
86
103
  if (handlerName) {
87
- const row = ctx.db
88
- .prepare(`SELECT id FROM nodes WHERE name = ? AND file_path = ?`)
89
- .get(handlerName, relPath) as { id: string } | undefined;
90
- if (row) {
91
- const edgeId = makeId(routeNodeId, row.id, 'handles_route');
104
+ const targetId = lookupHandler(handlerName, relPath);
105
+ if (targetId) {
106
+ const edgeId = makeId(routeNodeId, targetId, 'handles_route');
92
107
  handlesEdges.push({
93
108
  id: edgeId,
94
109
  sourceId: routeNodeId,
95
- targetId: row.id,
110
+ targetId,
96
111
  relation: 'HANDLES_ROUTE',
97
112
  confidence: 'EXTRACTED',
98
113
  confidenceScore: 0.9,
99
114
  });
100
- entry.handlerNodeId = row.id;
115
+ entry.handlerNodeId = targetId;
101
116
  }
102
117
  }
103
118
  }
@@ -131,20 +146,18 @@ export const routesPhase: PipelinePhase<RoutesOutput> = {
131
146
  const entry: RouteEntry = { method: e.method, path: e.path, filePath: relPath, routeNodeId, middlewareChain: [] };
132
147
 
133
148
  if (ctx.db && e.handlerName) {
134
- const row = ctx.db
135
- .prepare(`SELECT id FROM nodes WHERE name = ? AND file_path = ?`)
136
- .get(e.handlerName, relPath) as { id: string } | undefined;
137
- if (row) {
138
- const edgeId = makeId(routeNodeId, row.id, 'handles_route');
149
+ const targetId = lookupHandler(e.handlerName, relPath);
150
+ if (targetId) {
151
+ const edgeId = makeId(routeNodeId, targetId, 'handles_route');
139
152
  handlesEdges.push({
140
153
  id: edgeId,
141
154
  sourceId: routeNodeId,
142
- targetId: row.id,
155
+ targetId,
143
156
  relation: 'HANDLES_ROUTE',
144
157
  confidence: 'EXTRACTED',
145
158
  confidenceScore: 0.9,
146
159
  });
147
- entry.handlerNodeId = row.id;
160
+ entry.handlerNodeId = targetId;
148
161
  }
149
162
  }
150
163
 
@@ -172,20 +185,18 @@ export const routesPhase: PipelinePhase<RoutesOutput> = {
172
185
  const entry: RouteEntry = { method: e.method, path: e.path, filePath: relPath, routeNodeId, middlewareChain: [] };
173
186
 
174
187
  if (ctx.db && e.handlerName) {
175
- const row = ctx.db
176
- .prepare(`SELECT id FROM nodes WHERE name = ? AND file_path = ?`)
177
- .get(e.handlerName, relPath) as { id: string } | undefined;
178
- if (row) {
179
- const edgeId = makeId(routeNodeId, row.id, 'handles_route');
188
+ const targetId = lookupHandler(e.handlerName, relPath);
189
+ if (targetId) {
190
+ const edgeId = makeId(routeNodeId, targetId, 'handles_route');
180
191
  handlesEdges.push({
181
192
  id: edgeId,
182
193
  sourceId: routeNodeId,
183
- targetId: row.id,
194
+ targetId,
184
195
  relation: 'HANDLES_ROUTE',
185
196
  confidence: 'EXTRACTED',
186
197
  confidenceScore: 0.9,
187
198
  });
188
- entry.handlerNodeId = row.id;
199
+ entry.handlerNodeId = targetId;
189
200
  }
190
201
  }
191
202
 
@@ -210,68 +210,119 @@ function extractCallSites(
210
210
  return sites;
211
211
  }
212
212
 
213
- // ── Stage 3: Build import map from DB ─────────────────────────────────────────
213
+ // ── Stage 3: Build import maps from DB (batch) ────────────────────────────────
214
214
 
215
- /** Returns a map of imported name → source file path for a given file node */
216
- function buildImportMap(
215
+ /**
216
+ * Load all IMPORTS edges once and return a map of fileNodeId → (name → filePath).
217
+ * Eliminates the N+1 pattern of querying per-file inside the main loop.
218
+ */
219
+ function buildAllImportMaps(
217
220
  ctx: PipelineContext,
218
- fileNodeId: string,
219
221
  symbolNodes: MonographNode[],
220
- ): Map<string, string> {
221
- const importMap = new Map<string, string>();
222
-
223
- if (!ctx.db) return importMap;
222
+ ): Map<string, Map<string, string>> {
223
+ const result = new Map<string, Map<string, string>>();
224
+ if (!ctx.db) return result;
225
+
226
+ // Build a lookup: fileNodeId → filePath from symbolNodes
227
+ const idToFilePath = new Map<string, string>();
228
+ for (const n of symbolNodes) {
229
+ if (n.label === 'File' && n.filePath) {
230
+ idToFilePath.set(n.id, n.filePath);
231
+ // Also register the alternate makeId form
232
+ const altId = makeId(n.filePath.replace(/\//g, '_'), 'file');
233
+ if (altId !== n.id) idToFilePath.set(altId, n.filePath);
234
+ }
235
+ }
224
236
 
225
- // Get IMPORTS edges for this file to find which modules it imports
237
+ // Load ALL IMPORTS edges in one query
226
238
  const importsRows = ctx.db
227
- .prepare(`SELECT target_id FROM edges WHERE source_id = ? AND relation = 'IMPORTS'`)
228
- .all(fileNodeId) as { target_id: string }[];
239
+ .prepare(`SELECT source_id, target_id FROM edges WHERE relation = 'IMPORTS'`)
240
+ .all() as { source_id: string; target_id: string }[];
229
241
 
230
242
  for (const row of importsRows) {
231
- const targetId = row.target_id;
232
- // Find a File node that matches the resolved target
233
- const fileNode = symbolNodes.find(
234
- n => n.label === 'File' && (
235
- n.id === targetId ||
236
- makeId(n.filePath?.replace(/\//g, '_') ?? '', 'file') === targetId
237
- ),
238
- );
239
- if (fileNode?.filePath) {
240
- // Extract the module name from the last path segment without extension
241
- const parts = fileNode.filePath.split('/');
242
- const baseName = parts[parts.length - 1].replace(/\.\w+$/, '');
243
- importMap.set(baseName, fileNode.filePath);
244
- importMap.set(fileNode.filePath, fileNode.filePath);
243
+ const targetPath = idToFilePath.get(row.target_id);
244
+ if (!targetPath) continue;
245
+ let fileMap = result.get(row.source_id);
246
+ if (!fileMap) {
247
+ fileMap = new Map();
248
+ result.set(row.source_id, fileMap);
245
249
  }
250
+ const parts = targetPath.split('/');
251
+ const baseName = parts[parts.length - 1].replace(/\.\w+$/, '');
252
+ fileMap.set(baseName, targetPath);
253
+ fileMap.set(targetPath, targetPath);
246
254
  }
247
255
 
248
- return importMap;
256
+ return result;
249
257
  }
250
258
 
251
- // ── Stage 4 + 5: Resolve target node ─────────────────────────────────────────
259
+ /** Returns the pre-built import map for a given file node (empty map if not found). */
260
+ function getImportMap(
261
+ allImportMaps: Map<string, Map<string, string>>,
262
+ fileNodeId: string,
263
+ ): Map<string, string> {
264
+ return allImportMaps.get(fileNodeId) ?? new Map();
265
+ }
266
+
267
+ // ── Stage 4 helpers: preloaded function/method index ──────────────────────────
268
+
269
+ /**
270
+ * Load all Function/Method/Constructor nodes once.
271
+ * Returns two indices:
272
+ * - byFilePath: filePath → name → id[] (for resolveTarget candidate lookup)
273
+ * - nameCounts: name → total occurrences across all files (for ambiguity check)
274
+ */
275
+ function buildFunctionIndex(ctx: PipelineContext): {
276
+ byFilePath: Map<string, Map<string, string[]>>;
277
+ nameCounts: Map<string, number>;
278
+ } {
279
+ const byFilePath = new Map<string, Map<string, string[]>>();
280
+ const nameCounts = new Map<string, number>();
281
+
282
+ if (!ctx.db) return { byFilePath, nameCounts };
283
+
284
+ const rows = ctx.db
285
+ .prepare(`SELECT id, name, file_path FROM nodes WHERE label IN ('Function', 'Method', 'Constructor') AND file_path IS NOT NULL`)
286
+ .all() as { id: string; name: string; file_path: string }[];
287
+
288
+ for (const row of rows) {
289
+ // byFilePath index
290
+ let fileMap = byFilePath.get(row.file_path);
291
+ if (!fileMap) {
292
+ fileMap = new Map();
293
+ byFilePath.set(row.file_path, fileMap);
294
+ }
295
+ let ids = fileMap.get(row.name);
296
+ if (!ids) {
297
+ ids = [];
298
+ fileMap.set(row.name, ids);
299
+ }
300
+ ids.push(row.id);
301
+
302
+ // global count for ambiguity detection
303
+ nameCounts.set(row.name, (nameCounts.get(row.name) ?? 0) + 1);
304
+ }
305
+
306
+ return { byFilePath, nameCounts };
307
+ }
308
+
309
+ // ── Stage 4 + 5: Resolve target node (index-based, no per-call DB query) ──────
252
310
 
253
311
  function resolveTarget(
254
- ctx: PipelineContext,
255
312
  site: CallSite,
256
313
  callerFilePath: string,
257
314
  importMap: Map<string, string>,
315
+ fnIndex: Map<string, Map<string, string[]>>,
258
316
  ): { targetId: string } | null {
259
- if (!ctx.db) return null;
260
-
261
317
  const methodName = site.methodName;
262
318
  if (!methodName) return null;
263
319
 
264
- let candidateFilePaths: string[] = [];
320
+ let candidateFilePaths: string[];
265
321
 
266
322
  if (site.form === 'method' && site.receiverName) {
267
323
  // Stage 3: Infer receiver type — look up receiver in import map
268
324
  const receiverPath = importMap.get(site.receiverName);
269
- if (receiverPath) {
270
- candidateFilePaths = [receiverPath];
271
- } else {
272
- // Receiver not imported — look in same file only
273
- candidateFilePaths = [callerFilePath];
274
- }
325
+ candidateFilePaths = receiverPath ? [receiverPath] : [callerFilePath];
275
326
  } else if (site.form === 'direct') {
276
327
  // Direct call: same file first, then all imported files
277
328
  candidateFilePaths = [callerFilePath, ...importMap.values()];
@@ -279,22 +330,13 @@ function resolveTarget(
279
330
  return null;
280
331
  }
281
332
 
282
- // Stage 5: Query nodes table for matching function/method/constructor
333
+ // Stage 5: O(1) lookup in preloaded index instead of per-call DB query
283
334
  for (const fp of candidateFilePaths) {
284
- const rows = ctx.db
285
- .prepare(`
286
- SELECT id FROM nodes
287
- WHERE name = ?
288
- AND label IN ('Function', 'Method', 'Constructor')
289
- AND file_path = ?
290
- LIMIT 2
291
- `)
292
- .all(methodName, fp) as { id: string }[];
293
-
294
- if (rows.length === 1) {
295
- return { targetId: rows[0].id };
335
+ const ids = fnIndex.get(fp)?.get(methodName);
336
+ if (ids && ids.length === 1) {
337
+ return { targetId: ids[0] };
296
338
  }
297
- // If > 1 match, it's ambiguous — skip this candidate file
339
+ // ids.length > 1 ambiguous within this file — skip to next candidate
298
340
  }
299
341
 
300
342
  return null;
@@ -361,6 +403,8 @@ export const scopeResolutionPhase: PipelinePhase<ScopeResolutionOutput> = {
361
403
  let skippedDynamic = 0;
362
404
  let ambiguous = 0;
363
405
 
406
+ // ── Batch preload: one query per table instead of one per file/call-site ──
407
+
364
408
  // Build a map from file_path → file node id using the parse output
365
409
  const fileNodesByPath = new Map<string, MonographNode>();
366
410
  for (const node of symbolNodes) {
@@ -388,6 +432,12 @@ export const scopeResolutionPhase: PipelinePhase<ScopeResolutionOutput> = {
388
432
  }
389
433
  }
390
434
 
435
+ // Preload all IMPORTS edges → per-file import maps (one DB query total)
436
+ const allImportMaps = buildAllImportMaps(ctx, symbolNodes);
437
+
438
+ // Preload all Function/Method/Constructor nodes into indices (one DB query total)
439
+ const { byFilePath: fnIndex, nameCounts } = buildFunctionIndex(ctx);
440
+
391
441
  for (const [filePath, fileNode] of fileNodesByPath) {
392
442
  const ext = extname(filePath).toLowerCase();
393
443
  if (!isSupportedExt(ext)) continue;
@@ -403,8 +453,8 @@ export const scopeResolutionPhase: PipelinePhase<ScopeResolutionOutput> = {
403
453
  // Stage 1+2: Extract call sites
404
454
  const callSites = extractCallSites(source, filePath, fileNode.id, ext);
405
455
 
406
- // Stage 3: Build import map for this file
407
- const importMap = buildImportMap(ctx, fileNode.id, symbolNodes);
456
+ // Stage 3: Get pre-built import map for this file (O(1))
457
+ const importMap = getImportMap(allImportMaps, fileNode.id);
408
458
 
409
459
  for (const site of callSites) {
410
460
  if (site.form === 'dynamic') {
@@ -412,29 +462,18 @@ export const scopeResolutionPhase: PipelinePhase<ScopeResolutionOutput> = {
412
462
  continue;
413
463
  }
414
464
 
415
- // Stage 4+5: Resolve target
416
- const resolved = resolveTarget(ctx, site, filePath, importMap);
465
+ // Stage 4+5: Resolve target using preloaded indices (no DB query)
466
+ const resolved = resolveTarget(site, filePath, importMap, fnIndex);
417
467
 
418
468
  if (!resolved) {
419
- // Could not resolve — treat as ambiguous if we had candidates
420
- // (method calls with unresolvable receivers are simply skipped)
469
+ // Could not resolve — method calls with unresolvable receivers are simply skipped
421
470
  continue;
422
471
  }
423
472
 
424
- // Check if the resolved target is actually in multiple places (ambiguity check)
425
- if (ctx.db && site.methodName) {
426
- const totalMatches = ctx.db
427
- .prepare(`
428
- SELECT COUNT(*) as cnt FROM nodes
429
- WHERE name = ?
430
- AND label IN ('Function', 'Method', 'Constructor')
431
- `)
432
- .get(site.methodName) as { cnt: number };
433
- if (totalMatches.cnt > 1) {
434
- ambiguous++;
435
- // Still emit if we found exactly one in the candidate file — this is per-file ambiguity
436
- // The resolve already picked one candidate, so proceed
437
- }
473
+ // Ambiguity check using preloaded name counts (no DB query)
474
+ if (site.methodName && (nameCounts.get(site.methodName) ?? 0) > 1) {
475
+ ambiguous++;
476
+ // Still emit — we found exactly one candidate in the caller's context
438
477
  }
439
478
 
440
479
  // Stage 6: Emit edge (source is the file node of the caller)
@@ -3,36 +3,48 @@ import { MonographError } from '../types.js';
3
3
 
4
4
  export class PipelineRunner {
5
5
  private readonly phases: PipelinePhase<unknown>[];
6
+ /** Topo-sorted phase names, computed once in constructor for O(1) phase map lookups. */
7
+ private readonly sortedNames: string[];
8
+ /** O(1) phase lookup by name. */
9
+ private readonly phaseMap: Map<string, PipelinePhase<unknown>>;
6
10
 
7
11
  constructor(phases: PipelinePhase<unknown>[]) {
8
12
  this.phases = phases;
9
- // Validate: detect cycles and unknown deps
10
- topoSort(phases);
13
+ this.sortedNames = topoSort(phases);
14
+ this.phaseMap = new Map(phases.map(p => [p.name, p]));
11
15
  }
12
16
 
13
17
  async run(ctx: PipelineContext): Promise<Map<string, unknown>> {
14
18
  const outputs = new Map<string, unknown>();
15
- const phaseMap = new Map(this.phases.map(p => [p.name, p]));
16
19
 
17
20
  // Lazily-created promise per phase — ensures dep promises exist before they are awaited
18
21
  const promises = new Map<string, Promise<void>>();
19
22
 
20
23
  const getOrCreatePromise = (name: string): Promise<void> => {
21
24
  if (promises.has(name)) return promises.get(name)!;
22
- const phase = phaseMap.get(name)!;
25
+ const phase = this.phaseMap.get(name)!;
23
26
  const p = (async () => {
24
27
  // Wait for all dep phases to finish
25
28
  await Promise.all(phase.deps.map(dep => getOrCreatePromise(dep)));
26
29
  ctx.onProgress?.({ phase: phase.name });
27
- const output = await phase.execute(ctx, outputs);
28
- outputs.set(phase.name, output);
30
+ try {
31
+ const output = await phase.execute(ctx, outputs);
32
+ outputs.set(phase.name, output);
33
+ } catch (err) {
34
+ const msg = err instanceof Error ? err.message : String(err);
35
+ throw new MonographError(`Phase '${phase.name}' failed: ${msg}`);
36
+ }
29
37
  })();
30
38
  promises.set(name, p);
31
39
  return p;
32
40
  };
33
41
 
34
- // Kick off all phases — each self-manages its dep wait via lazy promise creation
35
- await Promise.all(this.phases.map(phase => getOrCreatePromise(phase.name)));
42
+ // Use allSettled so every in-flight phase completes before we return.
43
+ // This ensures the DB is not closed while phases are still writing to it,
44
+ // which would otherwise cause unhandled rejections that hang the process.
45
+ const results = await Promise.allSettled(this.sortedNames.map(name => getOrCreatePromise(name)));
46
+ const failed = results.find(r => r.status === 'rejected') as PromiseRejectedResult | undefined;
47
+ if (failed) throw failed.reason;
36
48
  return outputs;
37
49
  }
38
50
  }
@@ -94,14 +106,19 @@ export async function runIncrementalAst(
94
106
  if (preserveInferred) {
95
107
  if (changedFiles.length > 0) {
96
108
  const placeholders = changedFiles.map(() => '?').join(',');
109
+ // Use a CTE to resolve file_path→id once, then delete edges referencing those ids.
110
+ // This avoids passing changedFiles twice (N*2 params) and lets SQLite reuse the scan.
97
111
  db.prepare(`
112
+ WITH changed_ids AS (
113
+ SELECT id FROM nodes WHERE file_path IN (${placeholders})
114
+ )
98
115
  DELETE FROM edges
99
116
  WHERE confidence = 'EXTRACTED'
100
117
  AND (
101
- source_id IN (SELECT id FROM nodes WHERE file_path IN (${placeholders}))
102
- OR target_id IN (SELECT id FROM nodes WHERE file_path IN (${placeholders}))
118
+ source_id IN (SELECT id FROM changed_ids)
119
+ OR target_id IN (SELECT id FROM changed_ids)
103
120
  )
104
- `).run(...changedFiles, ...changedFiles);
121
+ `).run(...changedFiles);
105
122
  } else {
106
123
  db.prepare(`DELETE FROM edges WHERE confidence = 'EXTRACTED'`).run();
107
124
  }
@@ -365,3 +365,86 @@ export function executeCypherQuery(db: Database.Database, query: string): Cypher
365
365
  };
366
366
  }
367
367
  }
368
+
369
+ // ── Structured text formatter ──────────────────────────────────────────────────
370
+
371
+ /**
372
+ * Format a CypherResult as structured text for LLM consumption.
373
+ *
374
+ * Rows that contain *_filePath and *_startLine fields are rendered with
375
+ * "file:line" navigation hints so LLMs can jump directly to the symbol.
376
+ * Plain key=value pairs are rendered for all other fields.
377
+ *
378
+ * @example
379
+ * ```
380
+ * monograph_cypher result (3 rows, 2ms)
381
+ *
382
+ * Row 1:
383
+ * n.name = buildAsync
384
+ * n.filePath = src/server.ts:42
385
+ *
386
+ * Row 2:
387
+ * ...
388
+ * ```
389
+ */
390
+ export function formatCypherResult(result: CypherResult): string {
391
+ if (result.error) {
392
+ return `monograph_cypher error: ${result.error}`;
393
+ }
394
+
395
+ if (result.rows.length === 0) {
396
+ return `monograph_cypher result (0 rows, ${result.queryTime}ms)\n\nNo results found.`;
397
+ }
398
+
399
+ const lines: string[] = [
400
+ `monograph_cypher result (${result.rows.length} row${result.rows.length === 1 ? '' : 's'}, ${result.queryTime}ms)`,
401
+ '',
402
+ ];
403
+
404
+ for (let i = 0; i < result.rows.length; i++) {
405
+ lines.push(`Row ${i + 1}:`);
406
+ const row = result.rows[i];
407
+
408
+ // Group fields by alias prefix (e.g., "n_name", "b_filePath")
409
+ // and reconstruct "alias.field = value" display, with file:line merging
410
+ const seen = new Set<string>();
411
+
412
+ for (const key of Object.keys(row)) {
413
+ if (seen.has(key)) continue;
414
+ seen.add(key);
415
+
416
+ // key format is aliasName_fieldName (e.g. n_filePath, n_startLine)
417
+ const sepIdx = key.indexOf('_');
418
+ if (sepIdx === -1) {
419
+ lines.push(` ${key} = ${row[key] ?? 'null'}`);
420
+ continue;
421
+ }
422
+
423
+ const alias = key.slice(0, sepIdx);
424
+ const field = key.slice(sepIdx + 1);
425
+
426
+ // Merge filePath + startLine into a single file:line hint
427
+ if (field === 'filePath') {
428
+ const startLineKey = `${alias}_startLine`;
429
+ const startLine = row[startLineKey];
430
+ seen.add(startLineKey);
431
+ const filePath = row[key];
432
+ if (filePath != null && startLine != null) {
433
+ lines.push(` ${alias}.filePath = ${filePath}:${startLine}`);
434
+ } else if (filePath != null) {
435
+ lines.push(` ${alias}.filePath = ${filePath}`);
436
+ }
437
+ continue;
438
+ }
439
+
440
+ // Skip startLine — already consumed by filePath branch above
441
+ if (field === 'startLine') continue;
442
+
443
+ lines.push(` ${alias}.${field} = ${row[key] ?? 'null'}`);
444
+ }
445
+
446
+ if (i < result.rows.length - 1) lines.push('');
447
+ }
448
+
449
+ return lines.join('\n');
450
+ }
@@ -6,7 +6,7 @@ import type Database from 'better-sqlite3';
6
6
  import type { EmbedderFn } from './embedder.js';
7
7
  import type { EmbedDeviceConfig } from './device-config.js';
8
8
  import type { HttpEmbedderConfig } from './http-embedder.js';
9
- import { upsertEmbedding, countEmbeddings } from '../storage/embedding-store.js';
9
+ import { upsertEmbedding, ensureEmbeddingSchema, countEmbeddings } from '../storage/embedding-store.js';
10
10
  import { embedText } from './embedder.js';
11
11
 
12
12
  const BATCH_SIZE = 32;
@@ -110,6 +110,9 @@ export async function embedAll(
110
110
  const toEmbed = filteredRows.filter((r) => !existingIds.has(r.id));
111
111
  skipped = filteredRows.length - toEmbed.length;
112
112
 
113
+ // Ensure schema migration runs once before any upsert — not per-row.
114
+ if (toEmbed.length > 0) ensureEmbeddingSchema(db);
115
+
113
116
  // Process in batches
114
117
  for (let i = 0; i < toEmbed.length; i += BATCH_SIZE) {
115
118
  const batch = toEmbed.slice(i, i + BATCH_SIZE);