@monoes/monograph 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/.monomind/loops/mastermind-review-1782162423447.json +16 -0
  2. package/__tests__/mcp-tools/cypher.test.ts +10 -6
  3. package/__tests__/mcp-tools/detect-changes.test.ts +14 -11
  4. package/__tests__/pipeline/phases/wildcard-synthesis.test.ts +4 -4
  5. package/__tests__/search/embed-batch-config.test.ts +7 -7
  6. package/__tests__/search/hybrid-query.test.ts +3 -1
  7. package/__tests__/web/api.test.ts +1 -1
  8. package/dist/src/analysis/churn-cache.d.ts.map +1 -1
  9. package/dist/src/analysis/churn-cache.js +19 -6
  10. package/dist/src/analysis/churn-cache.js.map +1 -1
  11. package/dist/src/analysis/churn-trend.d.ts.map +1 -1
  12. package/dist/src/analysis/churn-trend.js +50 -6
  13. package/dist/src/analysis/churn-trend.js.map +1 -1
  14. package/dist/src/analysis/cluster-quality.d.ts +3 -0
  15. package/dist/src/analysis/cluster-quality.d.ts.map +1 -1
  16. package/dist/src/analysis/cluster-quality.js +18 -12
  17. package/dist/src/analysis/cluster-quality.js.map +1 -1
  18. package/dist/src/analysis/coverage-gaps.d.ts +3 -0
  19. package/dist/src/analysis/coverage-gaps.d.ts.map +1 -1
  20. package/dist/src/analysis/coverage-gaps.js +40 -1
  21. package/dist/src/analysis/coverage-gaps.js.map +1 -1
  22. package/dist/src/analysis/dep-classification.d.ts +2 -0
  23. package/dist/src/analysis/dep-classification.d.ts.map +1 -1
  24. package/dist/src/analysis/dep-classification.js +30 -0
  25. package/dist/src/analysis/dep-classification.js.map +1 -1
  26. package/dist/src/analysis/dependency-health.d.ts +4 -0
  27. package/dist/src/analysis/dependency-health.d.ts.map +1 -1
  28. package/dist/src/analysis/dependency-health.js +28 -0
  29. package/dist/src/analysis/dependency-health.js.map +1 -1
  30. package/dist/src/analysis/duplicate-exports.d.ts +9 -5
  31. package/dist/src/analysis/duplicate-exports.d.ts.map +1 -1
  32. package/dist/src/analysis/duplicate-exports.js +26 -2
  33. package/dist/src/analysis/duplicate-exports.js.map +1 -1
  34. package/dist/src/analysis/feature-flags.d.ts.map +1 -1
  35. package/dist/src/analysis/feature-flags.js +36 -17
  36. package/dist/src/analysis/feature-flags.js.map +1 -1
  37. package/dist/src/analysis/node-similarity.d.ts +19 -0
  38. package/dist/src/analysis/node-similarity.d.ts.map +1 -1
  39. package/dist/src/analysis/node-similarity.js +34 -3
  40. package/dist/src/analysis/node-similarity.js.map +1 -1
  41. package/dist/src/analysis/private-type-leaks.d.ts +4 -0
  42. package/dist/src/analysis/private-type-leaks.d.ts.map +1 -1
  43. package/dist/src/analysis/private-type-leaks.js +43 -2
  44. package/dist/src/analysis/private-type-leaks.js.map +1 -1
  45. package/dist/src/analysis/project-state.d.ts +2 -0
  46. package/dist/src/analysis/project-state.d.ts.map +1 -1
  47. package/dist/src/analysis/project-state.js +18 -7
  48. package/dist/src/analysis/project-state.js.map +1 -1
  49. package/dist/src/analysis/suppressions.d.ts.map +1 -1
  50. package/dist/src/analysis/suppressions.js +3 -1
  51. package/dist/src/analysis/suppressions.js.map +1 -1
  52. package/dist/src/analysis/trace.d.ts +5 -3
  53. package/dist/src/analysis/trace.d.ts.map +1 -1
  54. package/dist/src/analysis/trace.js +59 -53
  55. package/dist/src/analysis/trace.js.map +1 -1
  56. package/dist/src/analysis/unused-class-members.d.ts.map +1 -1
  57. package/dist/src/analysis/unused-class-members.js +7 -1
  58. package/dist/src/analysis/unused-class-members.js.map +1 -1
  59. package/dist/src/analysis/worker-pool.d.ts +3 -0
  60. package/dist/src/analysis/worker-pool.d.ts.map +1 -1
  61. package/dist/src/analysis/worker-pool.js +30 -8
  62. package/dist/src/analysis/worker-pool.js.map +1 -1
  63. package/dist/src/cli/augment.d.ts.map +1 -1
  64. package/dist/src/cli/augment.js +10 -3
  65. package/dist/src/cli/augment.js.map +1 -1
  66. package/dist/src/cli/skill-gen.js +3 -0
  67. package/dist/src/cli/skill-gen.js.map +1 -1
  68. package/dist/src/export/sarif.js +5 -5
  69. package/dist/src/export/sarif.js.map +1 -1
  70. package/dist/src/graph/analyzer.d.ts.map +1 -1
  71. package/dist/src/graph/analyzer.js +17 -2
  72. package/dist/src/graph/analyzer.js.map +1 -1
  73. package/dist/src/graph/author-analytics.d.ts +2 -0
  74. package/dist/src/graph/author-analytics.d.ts.map +1 -1
  75. package/dist/src/graph/author-analytics.js +39 -5
  76. package/dist/src/graph/author-analytics.js.map +1 -1
  77. package/dist/src/graph/baseline.d.ts +5 -0
  78. package/dist/src/graph/baseline.d.ts.map +1 -1
  79. package/dist/src/graph/baseline.js +65 -18
  80. package/dist/src/graph/baseline.js.map +1 -1
  81. package/dist/src/graph/clone-detector.d.ts.map +1 -1
  82. package/dist/src/graph/clone-detector.js +19 -33
  83. package/dist/src/graph/clone-detector.js.map +1 -1
  84. package/dist/src/graph/clone-families.d.ts +2 -0
  85. package/dist/src/graph/clone-families.d.ts.map +1 -1
  86. package/dist/src/graph/clone-families.js +38 -10
  87. package/dist/src/graph/clone-families.js.map +1 -1
  88. package/dist/src/graph/codeowners-extended.d.ts +17 -0
  89. package/dist/src/graph/codeowners-extended.d.ts.map +1 -1
  90. package/dist/src/graph/codeowners-extended.js +42 -0
  91. package/dist/src/graph/codeowners-extended.js.map +1 -1
  92. package/dist/src/graph/codeowners-gitlab.d.ts +21 -0
  93. package/dist/src/graph/codeowners-gitlab.d.ts.map +1 -1
  94. package/dist/src/graph/codeowners-gitlab.js +37 -0
  95. package/dist/src/graph/codeowners-gitlab.js.map +1 -1
  96. package/dist/src/graph/codeowners.d.ts +10 -0
  97. package/dist/src/graph/codeowners.d.ts.map +1 -1
  98. package/dist/src/graph/codeowners.js +24 -2
  99. package/dist/src/graph/codeowners.js.map +1 -1
  100. package/dist/src/graph/complexity.d.ts +10 -0
  101. package/dist/src/graph/complexity.d.ts.map +1 -1
  102. package/dist/src/graph/complexity.js +93 -43
  103. package/dist/src/graph/complexity.js.map +1 -1
  104. package/dist/src/graph/cross-reference-findings.d.ts.map +1 -1
  105. package/dist/src/graph/cross-reference-findings.js +19 -2
  106. package/dist/src/graph/cross-reference-findings.js.map +1 -1
  107. package/dist/src/graph/cross-reference.d.ts +4 -0
  108. package/dist/src/graph/cross-reference.d.ts.map +1 -1
  109. package/dist/src/graph/cross-reference.js +23 -0
  110. package/dist/src/graph/cross-reference.js.map +1 -1
  111. package/dist/src/graph/cycle-detection.d.ts +2 -0
  112. package/dist/src/graph/cycle-detection.d.ts.map +1 -1
  113. package/dist/src/graph/cycle-detection.js +26 -3
  114. package/dist/src/graph/cycle-detection.js.map +1 -1
  115. package/dist/src/graph/cycles.d.ts +11 -3
  116. package/dist/src/graph/cycles.d.ts.map +1 -1
  117. package/dist/src/graph/cycles.js +121 -83
  118. package/dist/src/graph/cycles.js.map +1 -1
  119. package/dist/src/graph/dead-code.d.ts +23 -0
  120. package/dist/src/graph/dead-code.d.ts.map +1 -1
  121. package/dist/src/graph/dead-code.js +53 -13
  122. package/dist/src/graph/dead-code.js.map +1 -1
  123. package/dist/src/graph/dependency-closure.d.ts.map +1 -1
  124. package/dist/src/graph/dependency-closure.js +20 -4
  125. package/dist/src/graph/dependency-closure.js.map +1 -1
  126. package/dist/src/graph/dfs.d.ts +14 -0
  127. package/dist/src/graph/dfs.d.ts.map +1 -1
  128. package/dist/src/graph/dfs.js +61 -0
  129. package/dist/src/graph/dfs.js.map +1 -1
  130. package/dist/src/graph/duplication-stats.d.ts.map +1 -1
  131. package/dist/src/graph/duplication-stats.js +19 -3
  132. package/dist/src/graph/duplication-stats.js.map +1 -1
  133. package/dist/src/graph/edge-filter.d.ts +2 -0
  134. package/dist/src/graph/edge-filter.d.ts.map +1 -1
  135. package/dist/src/graph/edge-filter.js +26 -12
  136. package/dist/src/graph/edge-filter.js.map +1 -1
  137. package/dist/src/graph/health-score.d.ts +8 -0
  138. package/dist/src/graph/health-score.d.ts.map +1 -1
  139. package/dist/src/graph/health-score.js +48 -17
  140. package/dist/src/graph/health-score.js.map +1 -1
  141. package/dist/src/graph/hotspots.d.ts +7 -0
  142. package/dist/src/graph/hotspots.d.ts.map +1 -1
  143. package/dist/src/graph/hotspots.js +50 -5
  144. package/dist/src/graph/hotspots.js.map +1 -1
  145. package/dist/src/graph/import-chain.d.ts +17 -1
  146. package/dist/src/graph/import-chain.d.ts.map +1 -1
  147. package/dist/src/graph/import-chain.js +68 -19
  148. package/dist/src/graph/import-chain.js.map +1 -1
  149. package/dist/src/graph/loader.d.ts.map +1 -1
  150. package/dist/src/graph/loader.js +9 -10
  151. package/dist/src/graph/loader.js.map +1 -1
  152. package/dist/src/graph/maintainability.d.ts +8 -0
  153. package/dist/src/graph/maintainability.d.ts.map +1 -1
  154. package/dist/src/graph/maintainability.js +65 -10
  155. package/dist/src/graph/maintainability.js.map +1 -1
  156. package/dist/src/graph/mirrored-dirs.d.ts +4 -0
  157. package/dist/src/graph/mirrored-dirs.d.ts.map +1 -1
  158. package/dist/src/graph/mirrored-dirs.js +48 -13
  159. package/dist/src/graph/mirrored-dirs.js.map +1 -1
  160. package/dist/src/graph/narrowing.d.ts +15 -0
  161. package/dist/src/graph/narrowing.d.ts.map +1 -1
  162. package/dist/src/graph/narrowing.js +33 -0
  163. package/dist/src/graph/narrowing.js.map +1 -1
  164. package/dist/src/graph/node-search.d.ts +3 -0
  165. package/dist/src/graph/node-search.d.ts.map +1 -1
  166. package/dist/src/graph/node-search.js +37 -4
  167. package/dist/src/graph/node-search.js.map +1 -1
  168. package/dist/src/graph/normalize-id.d.ts +4 -0
  169. package/dist/src/graph/normalize-id.d.ts.map +1 -1
  170. package/dist/src/graph/normalize-id.js +25 -2
  171. package/dist/src/graph/normalize-id.js.map +1 -1
  172. package/dist/src/graph/pagerank.d.ts +7 -0
  173. package/dist/src/graph/pagerank.d.ts.map +1 -1
  174. package/dist/src/graph/pagerank.js +49 -2
  175. package/dist/src/graph/pagerank.js.map +1 -1
  176. package/dist/src/graph/prune.d.ts +31 -0
  177. package/dist/src/graph/prune.d.ts.map +1 -1
  178. package/dist/src/graph/prune.js +65 -0
  179. package/dist/src/graph/prune.js.map +1 -1
  180. package/dist/src/graph/regex-search.d.ts +10 -2
  181. package/dist/src/graph/regex-search.d.ts.map +1 -1
  182. package/dist/src/graph/regex-search.js +33 -4
  183. package/dist/src/graph/regex-search.js.map +1 -1
  184. package/dist/src/graph/regression.d.ts +5 -0
  185. package/dist/src/graph/regression.d.ts.map +1 -1
  186. package/dist/src/graph/regression.js +45 -23
  187. package/dist/src/graph/regression.js.map +1 -1
  188. package/dist/src/graph/ripple-impact.d.ts +27 -0
  189. package/dist/src/graph/ripple-impact.d.ts.map +1 -1
  190. package/dist/src/graph/ripple-impact.js +55 -7
  191. package/dist/src/graph/ripple-impact.js.map +1 -1
  192. package/dist/src/graph/risk-profile.d.ts +7 -0
  193. package/dist/src/graph/risk-profile.d.ts.map +1 -1
  194. package/dist/src/graph/risk-profile.js +82 -48
  195. package/dist/src/graph/risk-profile.js.map +1 -1
  196. package/dist/src/graph/shingle-filter.d.ts.map +1 -1
  197. package/dist/src/graph/shingle-filter.js +25 -26
  198. package/dist/src/graph/shingle-filter.js.map +1 -1
  199. package/dist/src/graph/statistics.d.ts +19 -0
  200. package/dist/src/graph/statistics.d.ts.map +1 -1
  201. package/dist/src/graph/statistics.js +53 -40
  202. package/dist/src/graph/statistics.js.map +1 -1
  203. package/dist/src/graph/stats.d.ts +2 -0
  204. package/dist/src/graph/stats.d.ts.map +1 -1
  205. package/dist/src/graph/stats.js +54 -8
  206. package/dist/src/graph/stats.js.map +1 -1
  207. package/dist/src/graph/subgraph.d.ts +5 -0
  208. package/dist/src/graph/subgraph.d.ts.map +1 -1
  209. package/dist/src/graph/subgraph.js +73 -9
  210. package/dist/src/graph/subgraph.js.map +1 -1
  211. package/dist/src/graph/topo-sort.d.ts +15 -0
  212. package/dist/src/graph/topo-sort.d.ts.map +1 -1
  213. package/dist/src/graph/topo-sort.js +83 -10
  214. package/dist/src/graph/topo-sort.js.map +1 -1
  215. package/dist/src/graph/wcc.d.ts +29 -0
  216. package/dist/src/graph/wcc.d.ts.map +1 -1
  217. package/dist/src/graph/wcc.js +122 -39
  218. package/dist/src/graph/wcc.js.map +1 -1
  219. package/dist/src/index.js +1 -1
  220. package/dist/src/index.js.map +1 -1
  221. package/dist/src/mcp-tools/api-impact.d.ts.map +1 -1
  222. package/dist/src/mcp-tools/api-impact.js +13 -6
  223. package/dist/src/mcp-tools/api-impact.js.map +1 -1
  224. package/dist/src/mcp-tools/context.d.ts.map +1 -1
  225. package/dist/src/mcp-tools/context.js +19 -29
  226. package/dist/src/mcp-tools/context.js.map +1 -1
  227. package/dist/src/mcp-tools/cypher.d.ts +11 -0
  228. package/dist/src/mcp-tools/cypher.d.ts.map +1 -1
  229. package/dist/src/mcp-tools/cypher.js +13 -1
  230. package/dist/src/mcp-tools/cypher.js.map +1 -1
  231. package/dist/src/mcp-tools/detect-changes.d.ts.map +1 -1
  232. package/dist/src/mcp-tools/detect-changes.js +35 -20
  233. package/dist/src/mcp-tools/detect-changes.js.map +1 -1
  234. package/dist/src/mcp-tools/explain.d.ts.map +1 -1
  235. package/dist/src/mcp-tools/explain.js +4 -1
  236. package/dist/src/mcp-tools/explain.js.map +1 -1
  237. package/dist/src/mcp-tools/graph-query.d.ts +2 -0
  238. package/dist/src/mcp-tools/graph-query.d.ts.map +1 -1
  239. package/dist/src/mcp-tools/graph-query.js +20 -3
  240. package/dist/src/mcp-tools/graph-query.js.map +1 -1
  241. package/dist/src/mcp-tools/group-list.d.ts +1 -0
  242. package/dist/src/mcp-tools/group-list.d.ts.map +1 -1
  243. package/dist/src/mcp-tools/group-list.js +7 -9
  244. package/dist/src/mcp-tools/group-list.js.map +1 -1
  245. package/dist/src/mcp-tools/impact.d.ts.map +1 -1
  246. package/dist/src/mcp-tools/impact.js +20 -59
  247. package/dist/src/mcp-tools/impact.js.map +1 -1
  248. package/dist/src/mcp-tools/neighbors.d.ts.map +1 -1
  249. package/dist/src/mcp-tools/neighbors.js +24 -33
  250. package/dist/src/mcp-tools/neighbors.js.map +1 -1
  251. package/dist/src/mcp-tools/query.d.ts +2 -0
  252. package/dist/src/mcp-tools/query.d.ts.map +1 -1
  253. package/dist/src/mcp-tools/query.js +1 -0
  254. package/dist/src/mcp-tools/query.js.map +1 -1
  255. package/dist/src/mcp-tools/rename.d.ts.map +1 -1
  256. package/dist/src/mcp-tools/rename.js +12 -7
  257. package/dist/src/mcp-tools/rename.js.map +1 -1
  258. package/dist/src/mcp-tools/route-map.d.ts.map +1 -1
  259. package/dist/src/mcp-tools/route-map.js +19 -23
  260. package/dist/src/mcp-tools/route-map.js.map +1 -1
  261. package/dist/src/mcp-tools/shape-check.d.ts.map +1 -1
  262. package/dist/src/mcp-tools/shape-check.js +17 -13
  263. package/dist/src/mcp-tools/shape-check.js.map +1 -1
  264. package/dist/src/parsers/loader.js +6 -5
  265. package/dist/src/parsers/loader.js.map +1 -1
  266. package/dist/src/pipeline/cluster-only.d.ts.map +1 -1
  267. package/dist/src/pipeline/cluster-only.js +8 -4
  268. package/dist/src/pipeline/cluster-only.js.map +1 -1
  269. package/dist/src/pipeline/orchestrator.d.ts +2 -0
  270. package/dist/src/pipeline/orchestrator.d.ts.map +1 -1
  271. package/dist/src/pipeline/orchestrator.js +19 -0
  272. package/dist/src/pipeline/orchestrator.js.map +1 -1
  273. package/dist/src/pipeline/phases/boundary.d.ts +2 -0
  274. package/dist/src/pipeline/phases/boundary.d.ts.map +1 -1
  275. package/dist/src/pipeline/phases/boundary.js +31 -2
  276. package/dist/src/pipeline/phases/boundary.js.map +1 -1
  277. package/dist/src/pipeline/phases/communities.d.ts +14 -0
  278. package/dist/src/pipeline/phases/communities.d.ts.map +1 -1
  279. package/dist/src/pipeline/phases/communities.js +58 -5
  280. package/dist/src/pipeline/phases/communities.js.map +1 -1
  281. package/dist/src/pipeline/phases/contextual-proximity.d.ts.map +1 -1
  282. package/dist/src/pipeline/phases/contextual-proximity.js +21 -14
  283. package/dist/src/pipeline/phases/contextual-proximity.js.map +1 -1
  284. package/dist/src/pipeline/phases/cross-file.d.ts.map +1 -1
  285. package/dist/src/pipeline/phases/cross-file.js +35 -14
  286. package/dist/src/pipeline/phases/cross-file.js.map +1 -1
  287. package/dist/src/pipeline/phases/field-access.d.ts.map +1 -1
  288. package/dist/src/pipeline/phases/field-access.js +9 -4
  289. package/dist/src/pipeline/phases/field-access.js.map +1 -1
  290. package/dist/src/pipeline/phases/god-nodes.d.ts +4 -0
  291. package/dist/src/pipeline/phases/god-nodes.d.ts.map +1 -1
  292. package/dist/src/pipeline/phases/god-nodes.js +43 -9
  293. package/dist/src/pipeline/phases/god-nodes.js.map +1 -1
  294. package/dist/src/pipeline/phases/import-resolver.d.ts +11 -0
  295. package/dist/src/pipeline/phases/import-resolver.d.ts.map +1 -1
  296. package/dist/src/pipeline/phases/import-resolver.js +34 -7
  297. package/dist/src/pipeline/phases/import-resolver.js.map +1 -1
  298. package/dist/src/pipeline/phases/leiden.d.ts.map +1 -1
  299. package/dist/src/pipeline/phases/leiden.js +27 -19
  300. package/dist/src/pipeline/phases/leiden.js.map +1 -1
  301. package/dist/src/pipeline/phases/markdown.d.ts.map +1 -1
  302. package/dist/src/pipeline/phases/markdown.js +10 -5
  303. package/dist/src/pipeline/phases/markdown.js.map +1 -1
  304. package/dist/src/pipeline/phases/pdf-parse.d.ts.map +1 -1
  305. package/dist/src/pipeline/phases/pdf-parse.js +18 -3
  306. package/dist/src/pipeline/phases/pdf-parse.js.map +1 -1
  307. package/dist/src/pipeline/phases/processes.d.ts.map +1 -1
  308. package/dist/src/pipeline/phases/processes.js +26 -9
  309. package/dist/src/pipeline/phases/processes.js.map +1 -1
  310. package/dist/src/pipeline/phases/re-export-propagation.d.ts.map +1 -1
  311. package/dist/src/pipeline/phases/re-export-propagation.js +33 -14
  312. package/dist/src/pipeline/phases/re-export-propagation.js.map +1 -1
  313. package/dist/src/pipeline/phases/reachability.d.ts.map +1 -1
  314. package/dist/src/pipeline/phases/reachability.js +42 -26
  315. package/dist/src/pipeline/phases/reachability.js.map +1 -1
  316. package/dist/src/pipeline/phases/routes.d.ts.map +1 -1
  317. package/dist/src/pipeline/phases/routes.js +30 -21
  318. package/dist/src/pipeline/phases/routes.js.map +1 -1
  319. package/dist/src/pipeline/phases/scope-resolution.d.ts.map +1 -1
  320. package/dist/src/pipeline/phases/scope-resolution.js +93 -63
  321. package/dist/src/pipeline/phases/scope-resolution.js.map +1 -1
  322. package/dist/src/pipeline/runner.d.ts +4 -0
  323. package/dist/src/pipeline/runner.d.ts.map +1 -1
  324. package/dist/src/pipeline/runner.js +30 -11
  325. package/dist/src/pipeline/runner.js.map +1 -1
  326. package/dist/src/query/cypher-parser.d.ts +20 -0
  327. package/dist/src/query/cypher-parser.d.ts.map +1 -1
  328. package/dist/src/query/cypher-parser.js +73 -0
  329. package/dist/src/query/cypher-parser.js.map +1 -1
  330. package/dist/src/search/embed-batch.d.ts.map +1 -1
  331. package/dist/src/search/embed-batch.js +4 -1
  332. package/dist/src/search/embed-batch.js.map +1 -1
  333. package/dist/src/search/hybrid-query.d.ts +4 -0
  334. package/dist/src/search/hybrid-query.d.ts.map +1 -1
  335. package/dist/src/search/hybrid-query.js +5 -1
  336. package/dist/src/search/hybrid-query.js.map +1 -1
  337. package/dist/src/staleness/git-staleness.js +3 -3
  338. package/dist/src/staleness/git-staleness.js.map +1 -1
  339. package/dist/src/storage/db.d.ts.map +1 -1
  340. package/dist/src/storage/db.js +12 -0
  341. package/dist/src/storage/db.js.map +1 -1
  342. package/dist/src/storage/edge-store.d.ts +2 -0
  343. package/dist/src/storage/edge-store.d.ts.map +1 -1
  344. package/dist/src/storage/edge-store.js +26 -1
  345. package/dist/src/storage/edge-store.js.map +1 -1
  346. package/dist/src/storage/embedding-store.d.ts +16 -0
  347. package/dist/src/storage/embedding-store.d.ts.map +1 -1
  348. package/dist/src/storage/embedding-store.js +26 -2
  349. package/dist/src/storage/embedding-store.js.map +1 -1
  350. package/dist/src/storage/file-cache.d.ts +5 -0
  351. package/dist/src/storage/file-cache.d.ts.map +1 -1
  352. package/dist/src/storage/file-cache.js +35 -9
  353. package/dist/src/storage/file-cache.js.map +1 -1
  354. package/dist/src/storage/fts-store.d.ts +4 -0
  355. package/dist/src/storage/fts-store.d.ts.map +1 -1
  356. package/dist/src/storage/fts-store.js +8 -3
  357. package/dist/src/storage/fts-store.js.map +1 -1
  358. package/dist/src/storage/node-store.d.ts +2 -0
  359. package/dist/src/storage/node-store.d.ts.map +1 -1
  360. package/dist/src/storage/node-store.js +28 -1
  361. package/dist/src/storage/node-store.js.map +1 -1
  362. package/dist/src/wiki/wiki-generator.d.ts.map +1 -1
  363. package/dist/src/wiki/wiki-generator.js +5 -1
  364. package/dist/src/wiki/wiki-generator.js.map +1 -1
  365. package/dist/tsconfig.tsbuildinfo +1 -1
  366. package/package.json +1 -1
  367. package/src/__tests__/pipeline/runner.parallel.test.ts +2 -2
  368. package/src/analysis/churn-cache.ts +18 -7
  369. package/src/analysis/churn-trend.ts +35 -6
  370. package/src/analysis/cluster-quality.ts +20 -11
  371. package/src/analysis/coverage-gaps.ts +44 -1
  372. package/src/analysis/dep-classification.ts +34 -0
  373. package/src/analysis/dependency-health.ts +25 -0
  374. package/src/analysis/duplicate-exports.ts +40 -4
  375. package/src/analysis/feature-flags.ts +37 -17
  376. package/src/analysis/node-similarity.ts +45 -4
  377. package/src/analysis/private-type-leaks.ts +49 -2
  378. package/src/analysis/project-state.ts +19 -9
  379. package/src/analysis/suppressions.ts +4 -1
  380. package/src/analysis/trace.ts +73 -54
  381. package/src/analysis/unused-class-members.ts +8 -1
  382. package/src/analysis/worker-pool.ts +40 -8
  383. package/src/cli/augment.ts +10 -3
  384. package/src/cli/skill-gen.ts +3 -0
  385. package/src/export/sarif.ts +5 -5
  386. package/src/graph/analyzer.ts +26 -2
  387. package/src/graph/author-analytics.ts +45 -5
  388. package/src/graph/baseline.ts +78 -24
  389. package/src/graph/clone-detector.ts +22 -36
  390. package/src/graph/clone-families.ts +34 -10
  391. package/src/graph/codeowners-extended.ts +54 -0
  392. package/src/graph/codeowners-gitlab.ts +46 -0
  393. package/src/graph/codeowners.ts +33 -2
  394. package/src/graph/complexity.ts +102 -47
  395. package/src/graph/cross-reference-findings.ts +23 -2
  396. package/src/graph/cross-reference.ts +26 -0
  397. package/src/graph/cycle-detection.ts +22 -3
  398. package/src/graph/cycles.ts +155 -96
  399. package/src/graph/dead-code.ts +76 -17
  400. package/src/graph/dependency-closure.ts +24 -7
  401. package/src/graph/dfs.ts +78 -0
  402. package/src/graph/duplication-stats.ts +17 -6
  403. package/src/graph/edge-filter.ts +26 -12
  404. package/src/graph/health-score.ts +47 -17
  405. package/src/graph/hotspots.ts +57 -8
  406. package/src/graph/import-chain.ts +89 -19
  407. package/src/graph/loader.ts +10 -7
  408. package/src/graph/maintainability.ts +72 -22
  409. package/src/graph/mirrored-dirs.ts +45 -14
  410. package/src/graph/narrowing.ts +44 -0
  411. package/src/graph/node-search.ts +50 -4
  412. package/src/graph/normalize-id.ts +26 -2
  413. package/src/graph/pagerank.ts +70 -2
  414. package/src/graph/prune.ts +76 -0
  415. package/src/graph/regex-search.ts +38 -4
  416. package/src/graph/regression.ts +48 -23
  417. package/src/graph/ripple-impact.ts +71 -11
  418. package/src/graph/risk-profile.ts +98 -53
  419. package/src/graph/shingle-filter.ts +33 -27
  420. package/src/graph/statistics.ts +73 -45
  421. package/src/graph/stats.ts +50 -16
  422. package/src/graph/subgraph.ts +97 -25
  423. package/src/graph/topo-sort.ts +101 -16
  424. package/src/graph/wcc.ts +146 -37
  425. package/src/index.ts +1 -1
  426. package/src/mcp-tools/api-impact.ts +13 -6
  427. package/src/mcp-tools/context.ts +31 -40
  428. package/src/mcp-tools/cypher.ts +14 -1
  429. package/src/mcp-tools/detect-changes.ts +38 -23
  430. package/src/mcp-tools/explain.ts +4 -1
  431. package/src/mcp-tools/graph-query.ts +24 -3
  432. package/src/mcp-tools/group-list.ts +9 -11
  433. package/src/mcp-tools/impact.ts +26 -66
  434. package/src/mcp-tools/neighbors.ts +34 -39
  435. package/src/mcp-tools/query.ts +3 -0
  436. package/src/mcp-tools/rename.ts +9 -8
  437. package/src/mcp-tools/route-map.ts +21 -23
  438. package/src/mcp-tools/shape-check.ts +18 -14
  439. package/src/parsers/loader.ts +5 -5
  440. package/src/pipeline/cluster-only.ts +8 -4
  441. package/src/pipeline/orchestrator.ts +22 -0
  442. package/src/pipeline/phases/boundary.ts +40 -2
  443. package/src/pipeline/phases/communities.ts +66 -5
  444. package/src/pipeline/phases/contextual-proximity.ts +15 -14
  445. package/src/pipeline/phases/cross-file.ts +35 -14
  446. package/src/pipeline/phases/field-access.ts +11 -4
  447. package/src/pipeline/phases/god-nodes.ts +47 -10
  448. package/src/pipeline/phases/import-resolver.ts +37 -5
  449. package/src/pipeline/phases/leiden.ts +25 -18
  450. package/src/pipeline/phases/markdown.ts +12 -7
  451. package/src/pipeline/phases/pdf-parse.ts +19 -2
  452. package/src/pipeline/phases/processes.ts +27 -15
  453. package/src/pipeline/phases/re-export-propagation.ts +40 -18
  454. package/src/pipeline/phases/reachability.ts +39 -24
  455. package/src/pipeline/phases/routes.ts +32 -21
  456. package/src/pipeline/phases/scope-resolution.ts +110 -71
  457. package/src/pipeline/runner.ts +28 -11
  458. package/src/query/cypher-parser.ts +83 -0
  459. package/src/search/embed-batch.ts +4 -1
  460. package/src/search/hybrid-query.ts +11 -1
  461. package/src/staleness/git-staleness.ts +3 -3
  462. package/src/storage/db.ts +11 -0
  463. package/src/storage/edge-store.ts +34 -1
  464. package/src/storage/embedding-store.ts +38 -6
  465. package/src/storage/file-cache.ts +35 -21
  466. package/src/storage/fts-store.ts +12 -3
  467. package/src/storage/node-store.ts +39 -1
  468. package/src/wiki/wiki-generator.ts +5 -1
  469. package/src/coverage/runtime.ts +0 -249
@@ -22,19 +22,26 @@ export const contextualProximityPhase: PipelinePhase<ContextualProximityOutput>
22
22
 
23
23
  if (rows.length === 0) return { coOccursEdges: 0, conceptsScored: 0 };
24
24
 
25
- // Group concepts by section
25
+ // Group concepts by section using ??= to avoid has+get pattern
26
26
  const sectionConcepts = new Map<string, string[]>();
27
27
  for (const { source_id, target_id } of rows) {
28
- if (!sectionConcepts.has(source_id)) sectionConcepts.set(source_id, []);
29
- sectionConcepts.get(source_id)!.push(target_id);
28
+ let list = sectionConcepts.get(source_id);
29
+ if (!list) { list = []; sectionConcepts.set(source_id, list); }
30
+ list.push(target_id);
30
31
  }
31
32
 
32
- // Count concept co-occurrences across all sections
33
+ // Count concept co-occurrences across all sections.
34
+ // Accumulate conceptDegree in the same pass to avoid a second loop over sectionConcepts.
35
+ // Use a ternary comparison instead of [a,b].sort() to avoid per-pair array allocation.
33
36
  const coOccur = new Map<string, number>();
37
+ const conceptDegree = new Map<string, number>();
34
38
  for (const concepts of sectionConcepts.values()) {
35
39
  for (let i = 0; i < concepts.length; i++) {
40
+ conceptDegree.set(concepts[i], (conceptDegree.get(concepts[i]) ?? 0) + 1);
36
41
  for (let j = i + 1; j < concepts.length; j++) {
37
- const [a, b] = [concepts[i], concepts[j]].sort();
42
+ // Avoid allocating a 2-element sort array — ternary comparison is equivalent
43
+ const a = concepts[i] <= concepts[j] ? concepts[i] : concepts[j];
44
+ const b = concepts[i] <= concepts[j] ? concepts[j] : concepts[i];
38
45
  const key = `${a}::${b}`;
39
46
  coOccur.set(key, (coOccur.get(key) ?? 0) + 1);
40
47
  }
@@ -58,16 +65,10 @@ export const contextualProximityPhase: PipelinePhase<ContextualProximityOutput>
58
65
  });
59
66
  }
60
67
 
61
- // Score concept importance 1-5 based on normalized degree (sections it appears in)
62
- const conceptDegree = new Map<string, number>();
63
- for (const concepts of sectionConcepts.values()) {
64
- for (const cid of concepts) {
65
- conceptDegree.set(cid, (conceptDegree.get(cid) ?? 0) + 1);
66
- }
67
- }
68
-
69
68
  if (conceptDegree.size > 0) {
70
- const maxDeg = [...conceptDegree.values()].reduce((a, b) => Math.max(a, b), 0);
69
+ // Compute maxDeg with a loop to avoid spread array allocation from [...values()]
70
+ let maxDeg = 0;
71
+ for (const deg of conceptDegree.values()) { if (deg > maxDeg) maxDeg = deg; }
71
72
  const update = ctx.db.prepare(
72
73
  `UPDATE nodes SET properties = json_set(COALESCE(properties, '{}'), '$.importance', ?) WHERE id = ?`,
73
74
  );
@@ -16,27 +16,49 @@ export const crossFilePhase: PipelinePhase<CrossFileOutput> = {
16
16
  const { allEdges, symbolNodes } = deps.get('parse') as ParseOutput;
17
17
  const { fileNodes } = deps.get('structure') as StructureOutput;
18
18
 
19
- // Symbol name symbol node ID
19
+ /** Extract the last path segment of a '/'-separated string (faster than split+pop). */
20
+ function lastSegment(s: string): string {
21
+ const idx = s.lastIndexOf('/');
22
+ return idx === -1 ? s : s.slice(idx + 1);
23
+ }
24
+
25
+ /** Strip leading `import_` prefix, then return the last path segment. */
26
+ function targetBasename(rawId: string): string {
27
+ const stripped = rawId.startsWith('import_') ? rawId.slice(7) : rawId;
28
+ return lastSegment(stripped);
29
+ }
30
+
31
+ // Symbol name → symbol node ID.
32
+ // Pre-key lowercase variants so IMPORTS resolution is a single Map.get() instead of two.
20
33
  const nameIndex = new Map<string, string>();
21
34
  for (const node of symbolNodes) {
22
35
  nameIndex.set(node.name, node.id);
23
- if (node.normLabel) nameIndex.set(node.normLabel, node.id);
36
+ const lower = node.name.toLowerCase();
37
+ if (lower !== node.name) nameIndex.set(lower, node.id);
38
+ if (node.normLabel) {
39
+ nameIndex.set(node.normLabel, node.id);
40
+ const normLower = node.normLabel.toLowerCase();
41
+ if (normLower !== node.normLabel) nameIndex.set(normLower, node.id);
42
+ }
24
43
  }
25
44
 
26
- // Basename (without extension) → File node ID, for resolving RE_EXPORTS
45
+ // Basename (without extension) → File node ID, for resolving RE_EXPORTS.
46
+ // Use lastIndexOf to avoid allocating a full split array per file path.
27
47
  const fileBasenameIndex = new Map<string, string>();
28
48
  for (const fileNode of fileNodes) {
29
- const basename = (fileNode.filePath ?? '').split('/').pop() ?? '';
30
- const noExt = basename.replace(/\.[^.]+$/, '');
31
- fileBasenameIndex.set(noExt.toLowerCase(), fileNode.id);
32
- fileBasenameIndex.set(basename.toLowerCase(), fileNode.id);
49
+ const basename = lastSegment(fileNode.filePath ?? '').toLowerCase();
50
+ const dotIdx = basename.lastIndexOf('.');
51
+ const noExt = dotIdx === -1 ? basename : basename.slice(0, dotIdx);
52
+ fileBasenameIndex.set(basename, fileNode.id);
53
+ if (noExt !== basename) fileBasenameIndex.set(noExt, fileNode.id);
33
54
  }
34
55
 
35
56
  const resolvedEdges: MonographEdge[] = [];
36
57
 
37
58
  for (const edge of allEdges) {
38
59
  if (edge.relation === 'IMPORTS') {
39
- const targetName = edge.targetId.replace(/^import_/, '').split('/').pop() ?? '';
60
+ const targetName = targetBasename(edge.targetId);
61
+ // Single lookup — lowercase variants are pre-keyed in nameIndex
40
62
  const resolvedId = nameIndex.get(targetName) ?? nameIndex.get(targetName.toLowerCase());
41
63
 
42
64
  if (resolvedId && resolvedId !== edge.targetId) {
@@ -49,12 +71,11 @@ export const crossFilePhase: PipelinePhase<CrossFileOutput> = {
49
71
  });
50
72
  }
51
73
  } else if (edge.relation === 'RE_EXPORTS') {
52
- // Resolve to a File node ID
53
- const rawTarget = edge.targetId.replace(/^import_/, '');
54
- const basename = rawTarget.split('/').pop() ?? '';
55
- const noExt = basename.replace(/\.[^.]+$/, '');
56
- const resolvedId = fileBasenameIndex.get(basename.toLowerCase())
57
- ?? fileBasenameIndex.get(noExt.toLowerCase());
74
+ // Resolve to a File node ID using the pre-built basename index
75
+ const basename = targetBasename(edge.targetId).toLowerCase();
76
+ const dotIdx = basename.lastIndexOf('.');
77
+ const noExt = dotIdx === -1 ? basename : basename.slice(0, dotIdx);
78
+ const resolvedId = fileBasenameIndex.get(basename) ?? fileBasenameIndex.get(noExt);
58
79
 
59
80
  if (resolvedId && resolvedId !== edge.targetId) {
60
81
  resolvedEdges.push({
@@ -16,12 +16,19 @@ export function extractFieldAccesses(source: string, varName: string, filePath:
16
16
  const lines = source.split('\n');
17
17
  const escapedName = escapeRegexChars(varName);
18
18
 
19
+ // Hoist regex construction outside the loop — both patterns are fixed for the
20
+ // entire call, so creating them once avoids O(lines) RegExp allocations.
21
+ // Write: varName.field = or varName.field +=/-=/*=
22
+ const writeRe = new RegExp(`\\b${escapedName}\\.(\\w+)\\s*(?:\\+|-|\\*|\\/|%)?=(?!=)`, 'g');
23
+ // Read: varName.field NOT followed by assignment operator
24
+ const readRe = new RegExp(`\\b${escapedName}\\.(\\w+)`, 'g');
25
+
19
26
  for (let i = 0; i < lines.length; i++) {
20
27
  const line = lines[i];
21
- // Write: varName.field = or varName.field +=/-=/*=
22
- const writeRe = new RegExp(`\\b${escapedName}\\.(\\w+)\\s*(?:\\+|-|\\*|\\/|%)?=(?!=)`, 'g');
23
- // Read: varName.field NOT followed by assignment operator
24
- const readRe = new RegExp(`\\b${escapedName}\\.(\\w+)`, 'g');
28
+
29
+ // Reset stateful lastIndex before each line (required for reused /g regexes).
30
+ writeRe.lastIndex = 0;
31
+ readRe.lastIndex = 0;
25
32
 
26
33
  const writeFields = new Set<string>();
27
34
  let m: RegExpExecArray | null;
@@ -53,16 +53,17 @@ export const godNodesPhase: PipelinePhase<GodNodesOutput> = {
53
53
  inDeg.set(e.targetId, (inDeg.get(e.targetId) ?? 0) + 1);
54
54
  }
55
55
 
56
- // Compute degree distributions from all symbol nodes
57
- const allFanIn = symbolNodes
58
- .filter(n => !EXCLUDED_LABELS.has(n.label))
59
- .map(n => inDeg.get(n.id) ?? 0)
60
- .sort((a, b) => a - b);
61
-
62
- const allFanOut = symbolNodes
63
- .filter(n => !EXCLUDED_LABELS.has(n.label))
64
- .map(n => outDeg.get(n.id) ?? 0)
65
- .sort((a, b) => a - b);
56
+ // Compute degree distributions from all symbol nodes in a single pass
57
+ // (avoids two separate filter + map passes over potentially large arrays)
58
+ const allFanIn: number[] = [];
59
+ const allFanOut: number[] = [];
60
+ for (const n of symbolNodes) {
61
+ if (EXCLUDED_LABELS.has(n.label)) continue;
62
+ allFanIn.push(inDeg.get(n.id) ?? 0);
63
+ allFanOut.push(outDeg.get(n.id) ?? 0);
64
+ }
65
+ allFanIn.sort((a, b) => a - b);
66
+ allFanOut.sort((a, b) => a - b);
66
67
 
67
68
  const thresholds: GodNodesThresholds = {
68
69
  p75FanIn: percentile(allFanIn, 75),
@@ -116,3 +117,39 @@ export const godNodesPhase: PipelinePhase<GodNodesOutput> = {
116
117
  return { godNodes, thresholds };
117
118
  },
118
119
  };
120
+
121
+ /**
122
+ * Format god-node results as structured text with file:line hints for LLM navigation.
123
+ */
124
+ export function formatGodNodes(output: GodNodesOutput): string {
125
+ const { godNodes, thresholds } = output;
126
+ if (godNodes.length === 0) {
127
+ return 'god-nodes: none\nstatus: no high-centrality nodes detected\n';
128
+ }
129
+
130
+ const lines: string[] = [
131
+ `god-nodes: ${godNodes.length} high-centrality node(s) detected`,
132
+ `thresholds: fanIn p75=${thresholds.p75FanIn} p90=${thresholds.p90FanIn} p95=${thresholds.p95FanIn}; fanOut p75=${thresholds.p75FanOut} p90=${thresholds.p90FanOut}`,
133
+ '',
134
+ ];
135
+
136
+ for (let i = 0; i < godNodes.length; i++) {
137
+ const n = godNodes[i];
138
+ const loc = n.filePath
139
+ ? `${n.filePath}${n.startLine !== undefined ? `:${n.startLine}` : ''}`
140
+ : n.id;
141
+ lines.push(`[${i + 1}] ${n.name} (${n.label}) — ${n.category}`);
142
+ lines.push(` file: ${loc}`);
143
+ lines.push(` degree: ${n.degree} (fanIn=${n.inDegree}, fanOut=${n.outDegree})`);
144
+ for (const f of n.contributingFactors) {
145
+ lines.push(` factor: ${f.metric}=${f.value} (threshold=${f.threshold})`);
146
+ }
147
+ lines.push('');
148
+ }
149
+
150
+ const bridgeCount = godNodes.filter(n => n.category === 'BRIDGE_NODE').length;
151
+ const highCentralityCount = godNodes.length - bridgeCount;
152
+ lines.push(`summary: ${highCentralityCount} HIGH_CENTRALITY, ${bridgeCount} BRIDGE_NODE`);
153
+
154
+ return lines.join('\n');
155
+ }
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync, readdirSync } from 'fs';
1
+ import { readFileSync, readdirSync } from 'fs';
2
2
  import { join, resolve } from 'path';
3
3
  import type { PipelinePhase, PipelineContext } from '../types.js';
4
4
 
@@ -18,7 +18,7 @@ function globWorkspacePattern(repoPath: string, pattern: string): string[] {
18
18
  }
19
19
  const [base] = pattern.split('/*');
20
20
  const baseDir = join(repoPath, base ?? '');
21
- if (!existsSync(baseDir)) return [];
21
+ // Skip existsSync readdirSync will throw ENOENT which the catch already handles.
22
22
  try {
23
23
  return readdirSync(baseDir, { withFileTypes: true })
24
24
  .filter(d => d.isDirectory())
@@ -30,8 +30,6 @@ function globWorkspacePattern(repoPath: string, pattern: string): string[] {
30
30
 
31
31
  export function detectWorkspacePackages(repoPath: string): WorkspacePackage[] {
32
32
  const pkgPath = join(repoPath, 'package.json');
33
- if (!existsSync(pkgPath)) return [];
34
-
35
33
  let workspaceGlobs: string[] = [];
36
34
  try {
37
35
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
@@ -47,8 +45,8 @@ export function detectWorkspacePackages(repoPath: string): WorkspacePackage[] {
47
45
  const dirs = globWorkspacePattern(repoPath, glob);
48
46
  for (const dir of dirs) {
49
47
  const subPkgPath = join(dir, 'package.json');
50
- if (!existsSync(subPkgPath)) continue;
51
48
  try {
49
+ // existsSync guard removed — readFileSync throws ENOENT which the catch handles.
52
50
  const subPkg = JSON.parse(readFileSync(subPkgPath, 'utf8'));
53
51
  if (subPkg.name) {
54
52
  result.push({ name: subPkg.name, path: resolve(dir) });
@@ -72,6 +70,40 @@ export function resolveWorkspaceImport(
72
70
  return null;
73
71
  }
74
72
 
73
+ /**
74
+ * Build a Map<packageName, packagePath> index from a WorkspacePackage array.
75
+ * Use this when resolveWorkspaceImport will be called many times (e.g. per import
76
+ * statement across a whole repo) to reduce resolution cost from O(N*I) to O(N+I).
77
+ */
78
+ export function buildPackageIndex(packages: WorkspacePackage[]): Map<string, string> {
79
+ const index = new Map<string, string>();
80
+ for (const pkg of packages) {
81
+ index.set(pkg.name, pkg.path);
82
+ }
83
+ return index;
84
+ }
85
+
86
+ /**
87
+ * O(1) workspace import resolution using a pre-built package index.
88
+ * Falls back to prefix scan only for sub-path imports (e.g. `pkg/subpath`).
89
+ */
90
+ export function resolveWorkspaceImportFromIndex(
91
+ importSpecifier: string,
92
+ index: Map<string, string>,
93
+ ): string | null {
94
+ // Exact match: O(1)
95
+ const exact = index.get(importSpecifier);
96
+ if (exact !== undefined) return exact;
97
+ // Sub-path import: find the matching package name prefix
98
+ const slash = importSpecifier.indexOf('/', importSpecifier.startsWith('@') ? importSpecifier.indexOf('/') + 1 : 0);
99
+ if (slash !== -1) {
100
+ const pkgName = importSpecifier.slice(0, slash);
101
+ const path = index.get(pkgName);
102
+ if (path !== undefined) return path;
103
+ }
104
+ return null;
105
+ }
106
+
75
107
  export const importResolverPhase: PipelinePhase<ImportResolverOutput> = {
76
108
  name: 'import-resolver',
77
109
  deps: ['cross-file'],
@@ -26,11 +26,18 @@ export function leiden(graph: Graph, _options: { seed?: number } = {}): Record<s
26
26
  // Phase 1: Louvain with deterministic settings
27
27
  const communities: Record<string, number> = louvain(graph, { randomWalk: false });
28
28
 
29
- // Phase 2: Refinement — merge singletons into their largest neighbor's community
29
+ // Phase 2: Refinement — merge singletons into their largest neighbor's community.
30
+ // Single pass over Object.entries to build both communitySizes and the singletonNodes Set,
31
+ // avoiding a separate Object.values scan. The singletonNodes Set enables O(1) skip in the
32
+ // refinement loop below instead of a map.get(commId) <= 1 test per node.
30
33
  const communitySizes = new Map<number, number>();
31
34
  for (const comm of Object.values(communities)) {
32
35
  communitySizes.set(comm, (communitySizes.get(comm) ?? 0) + 1);
33
36
  }
37
+ const singletonNodes = new Set<string>();
38
+ for (const [nodeId, commId] of Object.entries(communities)) {
39
+ if ((communitySizes.get(commId) ?? 0) <= 1) singletonNodes.add(nodeId);
40
+ }
34
41
 
35
42
  // Refinement: merge singletons into their largest neighbor's community.
36
43
  // Pass 1: update communitySizes eagerly (so adjacent singletons see each other's moves
@@ -38,26 +45,26 @@ export function leiden(graph: Graph, _options: { seed?: number } = {}): Record<s
38
45
  // Pass 2: apply community writes — keeps the community map consistent at all times.
39
46
  const pending = new Map<string, number>();
40
47
  for (const [nodeId, commId] of Object.entries(communities)) {
41
- if ((communitySizes.get(commId) ?? 0) <= 1) {
42
- let bestComm = commId;
43
- let bestSize = 0;
48
+ if (!singletonNodes.has(nodeId)) continue; // O(1) skip for non-singletons
44
49
 
45
- graph.forEachNeighbor(nodeId, (neighbor: string) => {
46
- const neighborComm = communities[neighbor];
47
- if (neighborComm === undefined) return;
48
- const size = communitySizes.get(neighborComm) ?? 0;
49
- if (size > bestSize) {
50
- bestSize = size;
51
- bestComm = neighborComm;
52
- }
53
- });
50
+ let bestComm = commId;
51
+ let bestSize = 0;
54
52
 
55
- if (bestComm !== commId) {
56
- // Update sizes eagerly so subsequent singletons see the correct community sizes
57
- communitySizes.set(commId, (communitySizes.get(commId) ?? 1) - 1);
58
- communitySizes.set(bestComm, (communitySizes.get(bestComm) ?? 0) + 1);
59
- pending.set(nodeId, bestComm);
53
+ graph.forEachNeighbor(nodeId, (neighbor: string) => {
54
+ const neighborComm = communities[neighbor];
55
+ if (neighborComm === undefined) return;
56
+ const size = communitySizes.get(neighborComm) ?? 0;
57
+ if (size > bestSize) {
58
+ bestSize = size;
59
+ bestComm = neighborComm;
60
60
  }
61
+ });
62
+
63
+ if (bestComm !== commId) {
64
+ // Update sizes eagerly so subsequent singletons see the correct community sizes
65
+ communitySizes.set(commId, (communitySizes.get(commId) ?? 1) - 1);
66
+ communitySizes.set(bestComm, (communitySizes.get(bestComm) ?? 0) + 1);
67
+ pending.set(nodeId, bestComm);
61
68
  }
62
69
  }
63
70
 
@@ -10,6 +10,8 @@ import type { StructureOutput } from './structure.js';
10
10
  const MARKDOWN_EXTENSIONS = new Set(['.md', '.mdx']);
11
11
  // Inline code span: `identifier`
12
12
  const INLINE_CODE_RE = /`([^`]+)`/g;
13
+ // Module-level constant: compiled once per process, not per isIdentifier() call.
14
+ const IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$.-]*$/;
13
15
 
14
16
  export interface MarkdownOutput {
15
17
  documentNodes: MonographNode[];
@@ -23,6 +25,7 @@ export const markdownPhase: PipelinePhase<MarkdownOutput> = {
23
25
  // Runs before communities so REFERENCES edges are visible to graph analyses.
24
26
  deps: ['parse', 'structure'],
25
27
  async execute(ctx, deps) {
28
+ if (ctx.options.codeOnly) return { documentNodes: [], referencesEdges: [] };
26
29
  const { fileNodes } = deps.get('structure') as StructureOutput;
27
30
  const documentNodes: MonographNode[] = [];
28
31
  const referencesEdges: MonographEdge[] = [];
@@ -62,17 +65,19 @@ export const markdownPhase: PipelinePhase<MarkdownOutput> = {
62
65
  // Extract inline code spans and match against symbol names in the DB
63
66
  if (ctx.db) {
64
67
  const spans = extractCodeSpans(source);
68
+ // Hoist prepared statement outside the span loop — avoids re-compiling the
69
+ // SQL query for every identifier in every markdown file (N+1 preparation).
70
+ const symbolLookup = ctx.db.prepare(
71
+ `SELECT id FROM nodes WHERE name = ?
72
+ AND label NOT IN ('File', 'Folder', 'Document')`,
73
+ );
74
+
65
75
  for (const span of spans) {
66
76
  // Only match single-word identifiers (not shell commands, paths, etc.)
67
77
  if (!isIdentifier(span)) continue;
68
78
 
69
79
  // Exact name match, excluding structural nodes to avoid matching file names
70
- const rows = ctx.db
71
- .prepare(
72
- `SELECT id FROM nodes WHERE name = ?
73
- AND label NOT IN ('File', 'Folder', 'Document')`,
74
- )
75
- .all(span) as { id: string }[];
80
+ const rows = symbolLookup.all(span) as { id: string }[];
76
81
 
77
82
  if (rows.length === 1) {
78
83
  const edgeId = makeId(docId, rows[0].id, 'references');
@@ -112,5 +117,5 @@ function extractCodeSpans(source: string): string[] {
112
117
 
113
118
  /** Accept only simple identifiers — letters, digits, underscores, hyphens, dots (for namespaced names). */
114
119
  function isIdentifier(span: string): boolean {
115
- return /^[A-Za-z_$][A-Za-z0-9_$.-]*$/.test(span);
120
+ return IDENTIFIER_RE.test(span);
116
121
  }
@@ -107,8 +107,25 @@ export const pdfParsePhase: PipelinePhase<PdfParseOutput> = {
107
107
  }
108
108
  }
109
109
 
110
- if (sectionNodes.length > 0) insertNodes(ctx.db, sectionNodes);
111
- if (allEdges.length > 0) insertEdges(ctx.db, allEdges);
110
+ if (sectionNodes.length > 0) {
111
+ // File nodes are not persisted by structure/parse phases for non-code files —
112
+ // insert them here before edges to satisfy the FK constraint.
113
+ const seenFileIds = new Set<string>();
114
+ const fileNodes: MonographNode[] = [];
115
+ for (const edge of allEdges) {
116
+ if (!seenFileIds.has(edge.sourceId)) {
117
+ seenFileIds.add(edge.sourceId);
118
+ const relPath = sectionNodes.find(n => n.id === edge.targetId)?.filePath ?? '';
119
+ fileNodes.push({
120
+ id: edge.sourceId, label: 'File',
121
+ name: basename(relPath), normLabel: toNormLabel(basename(relPath)),
122
+ filePath: relPath, isExported: false,
123
+ });
124
+ }
125
+ }
126
+ insertNodes(ctx.db, [...fileNodes, ...sectionNodes]);
127
+ insertEdges(ctx.db, allEdges);
128
+ }
112
129
 
113
130
  ctx.onProgress?.({ phase: 'pdf-parse', message: `Parsed ${pdfPaths.length} PDF files → ${sectionNodes.length} chunks` });
114
131
  return { sectionNodes, pdfFiles: pdfPaths.length };
@@ -79,31 +79,43 @@ function scoreEntryPoints(db: Database.Database): ScoredNode[] {
79
79
  language: string | null;
80
80
  }>;
81
81
 
82
- const incomingCallsStmt = db.prepare(
83
- `SELECT COUNT(*) as cnt FROM edges WHERE target_id = ? AND relation = 'CALLS'`,
84
- );
85
- const isRouteHandlerStmt = db.prepare(
86
- `SELECT COUNT(*) as cnt FROM edges WHERE target_id = ? AND relation = 'HANDLES_ROUTE'`,
87
- );
88
- const isToolHandlerStmt = db.prepare(
89
- `SELECT COUNT(*) as cnt FROM edges WHERE target_id = ? AND relation = 'HANDLES_TOOL'`,
90
- );
82
+ // Preload all three edge-count signals in a single pass instead of 3 per-row queries.
83
+ // LEFT JOIN with FILTER avoids N*3 prepared-stmt round-trips.
84
+ const edgeCounts = db
85
+ .prepare(
86
+ `SELECT
87
+ n.id,
88
+ COUNT(CASE WHEN e.relation = 'CALLS' THEN 1 END) AS incoming_calls,
89
+ COUNT(CASE WHEN e.relation = 'HANDLES_ROUTE' THEN 1 END) AS route_count,
90
+ COUNT(CASE WHEN e.relation = 'HANDLES_TOOL' THEN 1 END) AS tool_count
91
+ FROM nodes n
92
+ LEFT JOIN edges e ON e.target_id = n.id AND e.relation IN ('CALLS','HANDLES_ROUTE','HANDLES_TOOL')
93
+ WHERE n.label IN ('Function','Method','Class')
94
+ GROUP BY n.id`,
95
+ )
96
+ .all() as Array<{ id: string; incoming_calls: number; route_count: number; tool_count: number }>;
97
+
98
+ const callsMap = new Map<string, number>();
99
+ const routeSet = new Set<string>();
100
+ const toolSet = new Set<string>();
101
+ for (const r of edgeCounts) {
102
+ callsMap.set(r.id, r.incoming_calls);
103
+ if (r.route_count > 0) routeSet.add(r.id);
104
+ if (r.tool_count > 0) toolSet.add(r.id);
105
+ }
91
106
 
92
107
  const scored: ScoredNode[] = [];
93
108
 
94
109
  for (const row of symbolRows) {
95
110
  let score = 0;
96
111
 
97
- const incomingCalls = (incomingCallsStmt.get(row.id) as { cnt: number }).cnt;
98
- if (incomingCalls === 0) score += 3;
112
+ if ((callsMap.get(row.id) ?? 0) === 0) score += 3;
99
113
 
100
114
  if (row.is_exported === 1) score += 2;
101
115
 
102
- const isRouteHandler = (isRouteHandlerStmt.get(row.id) as { cnt: number }).cnt > 0;
103
- if (isRouteHandler) score += 4;
116
+ if (routeSet.has(row.id)) score += 4;
104
117
 
105
- const isToolHandler = (isToolHandlerStmt.get(row.id) as { cnt: number }).cnt > 0;
106
- if (isToolHandler) score += 4;
118
+ if (toolSet.has(row.id)) score += 4;
107
119
 
108
120
  const filePath = row.file_path ?? '';
109
121
  const filename = basename(filePath, extname(filePath)).toLowerCase();
@@ -74,6 +74,36 @@ export function propagateReExports(db: Database.Database): ReExportPropagationOu
74
74
  },
75
75
  );
76
76
 
77
+ // ── Preload: avoid per-iteration DB queries inside the BFS while-loop ─────────
78
+
79
+ // nodeId → file_path for all nodes (used to resolve importer file paths)
80
+ const nodeIdToFilePath = new Map<string, string>(
81
+ (db.prepare(`SELECT id, file_path FROM nodes WHERE file_path IS NOT NULL`)
82
+ .all() as { id: string; file_path: string }[]).map(r => [r.id, r.file_path]),
83
+ );
84
+
85
+ // barrelFilePath → Set<importerNodeId>: who IMPORTS any node from each barrel file
86
+ // Load all IMPORTS edges once and group by target file_path
87
+ const allImportEdges = db.prepare(`
88
+ SELECT e.source_id, n.file_path AS target_file_path
89
+ FROM edges e
90
+ JOIN nodes n ON n.id = e.target_id
91
+ WHERE e.relation = 'IMPORTS'
92
+ AND n.file_path IS NOT NULL
93
+ `).all() as { source_id: string; target_file_path: string }[];
94
+
95
+ const barrelImporters = new Map<string, Set<string>>();
96
+ for (const row of allImportEdges) {
97
+ let s = barrelImporters.get(row.target_file_path);
98
+ if (!s) {
99
+ s = new Set();
100
+ barrelImporters.set(row.target_file_path, s);
101
+ }
102
+ s.add(row.source_id);
103
+ }
104
+
105
+ // ── BFS fixed-point (all lookups now O(1) from preloaded maps) ────────────────
106
+
77
107
  let totalPropagated = 0;
78
108
  let changed = true;
79
109
 
@@ -85,24 +115,16 @@ export function propagateReExports(db: Database.Database): ReExportPropagationOu
85
115
  const barrelFilePath = fileIdToPath.get(barrelFileId);
86
116
  if (!barrelFilePath || !reachedFilePaths.has(barrelFilePath)) continue;
87
117
 
88
- // Find who imports this barrel (via its File node or via its symbols)
89
- const importers = db.prepare(`
90
- SELECT DISTINCT e.source_id
91
- FROM edges e
92
- WHERE e.relation = 'IMPORTS'
93
- AND e.target_id IN (
94
- SELECT id FROM nodes
95
- WHERE file_path = ?
96
- )
97
- `).all(barrelFilePath) as { source_id: string }[];
98
-
99
- for (const { source_id: importerNodeId } of importers) {
100
- // Walk up to the importing File node
101
- const importerFilePath = (db.prepare(
102
- `SELECT file_path FROM nodes WHERE id = ? AND file_path IS NOT NULL LIMIT 1`,
103
- ).get(importerNodeId) as { file_path: string } | null)?.file_path;
104
-
105
- const importerFileId = importerFilePath ? filePathToId.get(importerFilePath) : undefined;
118
+ // O(1) lookup instead of per-barrel DB query
119
+ const importerNodeIds = barrelImporters.get(barrelFilePath);
120
+ if (!importerNodeIds) continue;
121
+
122
+ for (const importerNodeId of importerNodeIds) {
123
+ // O(1) lookup instead of per-importer DB query
124
+ const importerFilePath = nodeIdToFilePath.get(importerNodeId);
125
+ if (!importerFilePath) continue;
126
+
127
+ const importerFileId = filePathToId.get(importerFilePath);
106
128
  if (!importerFileId) continue;
107
129
 
108
130
  for (const targetFileId of exportedFileIds) {