homegraph 1.2.0 → 1.5.0

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 (560) hide show
  1. package/README.md +48 -11
  2. package/dist/bin/homegraph.js +298 -72
  3. package/dist/bin/homegraph.js.map +1 -1
  4. package/dist/context/index.d.ts.map +1 -1
  5. package/dist/context/index.js +117 -11
  6. package/dist/context/index.js.map +1 -1
  7. package/dist/db/index.d.ts +207 -6
  8. package/dist/db/index.d.ts.map +1 -1
  9. package/dist/db/index.js +461 -20
  10. package/dist/db/index.js.map +1 -1
  11. package/dist/db/migrations.d.ts +1 -1
  12. package/dist/db/migrations.d.ts.map +1 -1
  13. package/dist/db/migrations.js +19 -1
  14. package/dist/db/migrations.js.map +1 -1
  15. package/dist/db/queries.d.ts +157 -3
  16. package/dist/db/queries.d.ts.map +1 -1
  17. package/dist/db/queries.js +426 -9
  18. package/dist/db/queries.js.map +1 -1
  19. package/dist/db/schema.sql +32 -1
  20. package/dist/db/sqlite-adapter.d.ts +24 -17
  21. package/dist/db/sqlite-adapter.d.ts.map +1 -1
  22. package/dist/db/sqlite-adapter.js +292 -36
  23. package/dist/db/sqlite-adapter.js.map +1 -1
  24. package/dist/db/wal-valve.d.ts +119 -0
  25. package/dist/db/wal-valve.d.ts.map +1 -0
  26. package/dist/db/wal-valve.js +290 -0
  27. package/dist/db/wal-valve.js.map +1 -0
  28. package/dist/directory.d.ts +9 -5
  29. package/dist/directory.d.ts.map +1 -1
  30. package/dist/directory.js +210 -19
  31. package/dist/directory.js.map +1 -1
  32. package/dist/extraction/arkts-batch-worker.js +3 -2
  33. package/dist/extraction/arkts-batch-worker.js.map +1 -1
  34. package/dist/extraction/cfml-extractor.d.ts +107 -0
  35. package/dist/extraction/cfml-extractor.d.ts.map +1 -0
  36. package/dist/extraction/cfml-extractor.js +494 -0
  37. package/dist/extraction/cfml-extractor.js.map +1 -0
  38. package/dist/extraction/default-ignore.d.ts +39 -0
  39. package/dist/extraction/default-ignore.d.ts.map +1 -0
  40. package/dist/extraction/default-ignore.js +221 -0
  41. package/dist/extraction/default-ignore.js.map +1 -0
  42. package/dist/extraction/grammars.d.ts +25 -1
  43. package/dist/extraction/grammars.d.ts.map +1 -1
  44. package/dist/extraction/grammars.js +177 -24
  45. package/dist/extraction/grammars.js.map +1 -1
  46. package/dist/extraction/index.d.ts +52 -14
  47. package/dist/extraction/index.d.ts.map +1 -1
  48. package/dist/extraction/index.js +521 -314
  49. package/dist/extraction/index.js.map +1 -1
  50. package/dist/extraction/languages/arkts.d.ts +90 -6
  51. package/dist/extraction/languages/arkts.d.ts.map +1 -1
  52. package/dist/extraction/languages/arkts.js +1579 -128
  53. package/dist/extraction/languages/arkts.js.map +1 -1
  54. package/dist/extraction/languages/c-cpp.d.ts +42 -0
  55. package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
  56. package/dist/extraction/languages/c-cpp.js +369 -4
  57. package/dist/extraction/languages/c-cpp.js.map +1 -1
  58. package/dist/extraction/languages/cfquery.d.ts +12 -0
  59. package/dist/extraction/languages/cfquery.d.ts.map +1 -0
  60. package/dist/extraction/languages/cfquery.js +28 -0
  61. package/dist/extraction/languages/cfquery.js.map +1 -0
  62. package/dist/extraction/languages/cfscript.d.ts +3 -0
  63. package/dist/extraction/languages/cfscript.d.ts.map +1 -0
  64. package/dist/extraction/languages/cfscript.js +73 -0
  65. package/dist/extraction/languages/cfscript.js.map +1 -0
  66. package/dist/extraction/languages/cobol.d.ts +33 -0
  67. package/dist/extraction/languages/cobol.d.ts.map +1 -0
  68. package/dist/extraction/languages/cobol.js +499 -0
  69. package/dist/extraction/languages/cobol.js.map +1 -0
  70. package/dist/extraction/languages/erlang.d.ts +3 -0
  71. package/dist/extraction/languages/erlang.d.ts.map +1 -0
  72. package/dist/extraction/languages/erlang.js +350 -0
  73. package/dist/extraction/languages/erlang.js.map +1 -0
  74. package/dist/extraction/languages/index.d.ts.map +1 -1
  75. package/dist/extraction/languages/index.js +16 -0
  76. package/dist/extraction/languages/index.js.map +1 -1
  77. package/dist/extraction/languages/nix.d.ts +3 -0
  78. package/dist/extraction/languages/nix.d.ts.map +1 -0
  79. package/dist/extraction/languages/nix.js +294 -0
  80. package/dist/extraction/languages/nix.js.map +1 -0
  81. package/dist/extraction/languages/solidity.d.ts +3 -0
  82. package/dist/extraction/languages/solidity.d.ts.map +1 -0
  83. package/dist/extraction/languages/solidity.js +293 -0
  84. package/dist/extraction/languages/solidity.js.map +1 -0
  85. package/dist/extraction/languages/terraform.d.ts +3 -0
  86. package/dist/extraction/languages/terraform.d.ts.map +1 -0
  87. package/dist/extraction/languages/terraform.js +641 -0
  88. package/dist/extraction/languages/terraform.js.map +1 -0
  89. package/dist/extraction/languages/vbnet.d.ts +11 -0
  90. package/dist/extraction/languages/vbnet.d.ts.map +1 -0
  91. package/dist/extraction/languages/vbnet.js +141 -0
  92. package/dist/extraction/languages/vbnet.js.map +1 -0
  93. package/dist/extraction/mybatis-extractor.d.ts +30 -10
  94. package/dist/extraction/mybatis-extractor.d.ts.map +1 -1
  95. package/dist/extraction/mybatis-extractor.js +140 -38
  96. package/dist/extraction/mybatis-extractor.js.map +1 -1
  97. package/dist/extraction/parse-pool.d.ts +27 -1
  98. package/dist/extraction/parse-pool.d.ts.map +1 -1
  99. package/dist/extraction/parse-pool.js +75 -7
  100. package/dist/extraction/parse-pool.js.map +1 -1
  101. package/dist/extraction/parse-worker.js +9 -2
  102. package/dist/extraction/parse-worker.js.map +1 -1
  103. package/dist/extraction/store-worker.d.ts +20 -0
  104. package/dist/extraction/store-worker.d.ts.map +1 -0
  105. package/dist/extraction/store-worker.js +102 -0
  106. package/dist/extraction/store-worker.js.map +1 -0
  107. package/dist/extraction/store-writer.d.ts +48 -0
  108. package/dist/extraction/store-writer.d.ts.map +1 -0
  109. package/dist/extraction/store-writer.js +167 -0
  110. package/dist/extraction/store-writer.js.map +1 -0
  111. package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
  112. package/dist/extraction/tree-sitter-helpers.js +1 -0
  113. package/dist/extraction/tree-sitter-helpers.js.map +1 -1
  114. package/dist/extraction/tree-sitter-types.d.ts +3 -1
  115. package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
  116. package/dist/extraction/tree-sitter.d.ts +38 -0
  117. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  118. package/dist/extraction/tree-sitter.js +801 -10
  119. package/dist/extraction/tree-sitter.js.map +1 -1
  120. package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
  121. package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
  122. package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
  123. package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
  124. package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
  125. package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
  126. package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
  127. package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
  128. package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
  129. package/dist/extraction/wasm-runtime-flags.d.ts +14 -4
  130. package/dist/extraction/wasm-runtime-flags.d.ts.map +1 -1
  131. package/dist/extraction/wasm-runtime-flags.js +55 -7
  132. package/dist/extraction/wasm-runtime-flags.js.map +1 -1
  133. package/dist/graph/traversal.d.ts.map +1 -1
  134. package/dist/graph/traversal.js +1 -1
  135. package/dist/graph/traversal.js.map +1 -1
  136. package/dist/index.d.ts +74 -5
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +436 -55
  139. package/dist/index.js.map +1 -1
  140. package/dist/installer/index.d.ts +42 -0
  141. package/dist/installer/index.d.ts.map +1 -1
  142. package/dist/installer/index.js +97 -2
  143. package/dist/installer/index.js.map +1 -1
  144. package/dist/mcp/daemon.d.ts +25 -3
  145. package/dist/mcp/daemon.d.ts.map +1 -1
  146. package/dist/mcp/daemon.js +62 -8
  147. package/dist/mcp/daemon.js.map +1 -1
  148. package/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
  149. package/dist/mcp/dynamic-boundaries.js +2 -1
  150. package/dist/mcp/dynamic-boundaries.js.map +1 -1
  151. package/dist/mcp/early-ppid.d.ts +26 -0
  152. package/dist/mcp/early-ppid.d.ts.map +1 -0
  153. package/dist/mcp/early-ppid.js +29 -0
  154. package/dist/mcp/early-ppid.js.map +1 -0
  155. package/dist/mcp/engine.d.ts +4 -5
  156. package/dist/mcp/engine.d.ts.map +1 -1
  157. package/dist/mcp/engine.js +27 -1
  158. package/dist/mcp/engine.js.map +1 -1
  159. package/dist/mcp/index.d.ts.map +1 -1
  160. package/dist/mcp/index.js +41 -6
  161. package/dist/mcp/index.js.map +1 -1
  162. package/dist/mcp/liveness-watchdog.d.ts +18 -1
  163. package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
  164. package/dist/mcp/liveness-watchdog.js +73 -13
  165. package/dist/mcp/liveness-watchdog.js.map +1 -1
  166. package/dist/mcp/memory-budget.d.ts +30 -0
  167. package/dist/mcp/memory-budget.d.ts.map +1 -0
  168. package/dist/mcp/memory-budget.js +123 -0
  169. package/dist/mcp/memory-budget.js.map +1 -0
  170. package/dist/mcp/proxy.d.ts.map +1 -1
  171. package/dist/mcp/proxy.js +88 -4
  172. package/dist/mcp/proxy.js.map +1 -1
  173. package/dist/mcp/query-cache.d.ts +35 -4
  174. package/dist/mcp/query-cache.d.ts.map +1 -1
  175. package/dist/mcp/query-cache.js +89 -19
  176. package/dist/mcp/query-cache.js.map +1 -1
  177. package/dist/mcp/query-pool.d.ts +46 -12
  178. package/dist/mcp/query-pool.d.ts.map +1 -1
  179. package/dist/mcp/query-pool.js +100 -23
  180. package/dist/mcp/query-pool.js.map +1 -1
  181. package/dist/mcp/query-worker.d.ts +2 -2
  182. package/dist/mcp/query-worker.js +2 -2
  183. package/dist/mcp/server-instructions.d.ts +4 -27
  184. package/dist/mcp/server-instructions.d.ts.map +1 -1
  185. package/dist/mcp/server-instructions.js +39 -84
  186. package/dist/mcp/server-instructions.js.map +1 -1
  187. package/dist/mcp/session.d.ts +14 -0
  188. package/dist/mcp/session.d.ts.map +1 -1
  189. package/dist/mcp/session.js +28 -1
  190. package/dist/mcp/session.js.map +1 -1
  191. package/dist/mcp/startup-handshake.d.ts +44 -0
  192. package/dist/mcp/startup-handshake.d.ts.map +1 -0
  193. package/dist/mcp/startup-handshake.js +73 -0
  194. package/dist/mcp/startup-handshake.js.map +1 -0
  195. package/dist/mcp/tools.d.ts +104 -4
  196. package/dist/mcp/tools.d.ts.map +1 -1
  197. package/dist/mcp/tools.js +2432 -217
  198. package/dist/mcp/tools.js.map +1 -1
  199. package/dist/mcp/transport.d.ts.map +1 -1
  200. package/dist/mcp/transport.js +18 -0
  201. package/dist/mcp/transport.js.map +1 -1
  202. package/dist/project-config.d.ts +38 -0
  203. package/dist/project-config.d.ts.map +1 -1
  204. package/dist/project-config.js +101 -2
  205. package/dist/project-config.js.map +1 -1
  206. package/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
  207. package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
  208. package/dist/resolution/c-fnptr-synthesizer.js +178 -146
  209. package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
  210. package/dist/resolution/callback-synthesizer.d.ts +54 -10
  211. package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  212. package/dist/resolution/callback-synthesizer.js +1299 -257
  213. package/dist/resolution/callback-synthesizer.js.map +1 -1
  214. package/dist/resolution/cooperative-yield.d.ts +10 -2
  215. package/dist/resolution/cooperative-yield.d.ts.map +1 -1
  216. package/dist/resolution/cooperative-yield.js +6 -4
  217. package/dist/resolution/cooperative-yield.js.map +1 -1
  218. package/dist/resolution/frameworks/cics.d.ts +20 -0
  219. package/dist/resolution/frameworks/cics.d.ts.map +1 -0
  220. package/dist/resolution/frameworks/cics.js +90 -0
  221. package/dist/resolution/frameworks/cics.js.map +1 -0
  222. package/dist/resolution/frameworks/go.d.ts.map +1 -1
  223. package/dist/resolution/frameworks/go.js +29 -6
  224. package/dist/resolution/frameworks/go.js.map +1 -1
  225. package/dist/resolution/frameworks/index.d.ts.map +1 -1
  226. package/dist/resolution/frameworks/index.js +3 -0
  227. package/dist/resolution/frameworks/index.js.map +1 -1
  228. package/dist/resolution/frameworks/java.d.ts.map +1 -1
  229. package/dist/resolution/frameworks/java.js +14 -6
  230. package/dist/resolution/frameworks/java.js.map +1 -1
  231. package/dist/resolution/frameworks/terraform.d.ts +38 -0
  232. package/dist/resolution/frameworks/terraform.d.ts.map +1 -0
  233. package/dist/resolution/frameworks/terraform.js +277 -0
  234. package/dist/resolution/frameworks/terraform.js.map +1 -0
  235. package/dist/resolution/goframe-synthesizer.d.ts +2 -1
  236. package/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
  237. package/dist/resolution/goframe-synthesizer.js +8 -3
  238. package/dist/resolution/goframe-synthesizer.js.map +1 -1
  239. package/dist/resolution/import-resolver.d.ts +9 -3
  240. package/dist/resolution/import-resolver.d.ts.map +1 -1
  241. package/dist/resolution/import-resolver.js +295 -11
  242. package/dist/resolution/import-resolver.js.map +1 -1
  243. package/dist/resolution/index.d.ts +146 -8
  244. package/dist/resolution/index.d.ts.map +1 -1
  245. package/dist/resolution/index.js +1089 -131
  246. package/dist/resolution/index.js.map +1 -1
  247. package/dist/resolution/memory-budget.d.ts +48 -0
  248. package/dist/resolution/memory-budget.d.ts.map +1 -0
  249. package/dist/resolution/memory-budget.js +162 -0
  250. package/dist/resolution/memory-budget.js.map +1 -0
  251. package/dist/resolution/name-matcher.d.ts +12 -4
  252. package/dist/resolution/name-matcher.d.ts.map +1 -1
  253. package/dist/resolution/name-matcher.js +793 -130
  254. package/dist/resolution/name-matcher.js.map +1 -1
  255. package/dist/resolution/resolver-pool.d.ts +106 -0
  256. package/dist/resolution/resolver-pool.d.ts.map +1 -0
  257. package/dist/resolution/resolver-pool.js +344 -0
  258. package/dist/resolution/resolver-pool.js.map +1 -0
  259. package/dist/resolution/resolver-worker.d.ts +17 -0
  260. package/dist/resolution/resolver-worker.d.ts.map +1 -0
  261. package/dist/resolution/resolver-worker.js +141 -0
  262. package/dist/resolution/resolver-worker.js.map +1 -0
  263. package/dist/resolution/strip-comments.d.ts +1 -1
  264. package/dist/resolution/strip-comments.d.ts.map +1 -1
  265. package/dist/resolution/strip-comments.js +49 -0
  266. package/dist/resolution/strip-comments.js.map +1 -1
  267. package/dist/resolution/types.d.ts +32 -0
  268. package/dist/resolution/types.d.ts.map +1 -1
  269. package/dist/resolution/workspace-packages.d.ts +10 -0
  270. package/dist/resolution/workspace-packages.d.ts.map +1 -1
  271. package/dist/resolution/workspace-packages.js +142 -4
  272. package/dist/resolution/workspace-packages.js.map +1 -1
  273. package/dist/search/identifier-segments.d.ts +60 -0
  274. package/dist/search/identifier-segments.d.ts.map +1 -0
  275. package/dist/search/identifier-segments.js +176 -0
  276. package/dist/search/identifier-segments.js.map +1 -0
  277. package/dist/search/query-utils.d.ts +201 -0
  278. package/dist/search/query-utils.d.ts.map +1 -1
  279. package/dist/search/query-utils.js +919 -2
  280. package/dist/search/query-utils.js.map +1 -1
  281. package/dist/spec/build/diff-parser.d.ts +55 -0
  282. package/dist/spec/build/diff-parser.d.ts.map +1 -0
  283. package/dist/spec/build/diff-parser.js +205 -0
  284. package/dist/spec/build/diff-parser.js.map +1 -0
  285. package/dist/spec/build/git-scanner.d.ts +93 -0
  286. package/dist/spec/build/git-scanner.d.ts.map +1 -0
  287. package/dist/spec/build/git-scanner.js +254 -0
  288. package/dist/spec/build/git-scanner.js.map +1 -0
  289. package/dist/spec/build/pipeline.d.ts +54 -0
  290. package/dist/spec/build/pipeline.d.ts.map +1 -0
  291. package/dist/spec/build/pipeline.js +170 -0
  292. package/dist/spec/build/pipeline.js.map +1 -0
  293. package/dist/spec/build/scan.d.ts +48 -0
  294. package/dist/spec/build/scan.d.ts.map +1 -0
  295. package/dist/spec/build/scan.js +89 -0
  296. package/dist/spec/build/scan.js.map +1 -0
  297. package/dist/spec/build/scope-resolver.d.ts +50 -0
  298. package/dist/spec/build/scope-resolver.d.ts.map +1 -0
  299. package/dist/spec/build/scope-resolver.js +105 -0
  300. package/dist/spec/build/scope-resolver.js.map +1 -0
  301. package/dist/spec/build/spec-extractor.d.ts +74 -0
  302. package/dist/spec/build/spec-extractor.d.ts.map +1 -0
  303. package/dist/spec/build/spec-extractor.js +344 -0
  304. package/dist/spec/build/spec-extractor.js.map +1 -0
  305. package/dist/spec/config.d.ts +35 -1
  306. package/dist/spec/config.d.ts.map +1 -1
  307. package/dist/spec/config.js +30 -5
  308. package/dist/spec/config.js.map +1 -1
  309. package/dist/spec/db/fts.d.ts.map +1 -1
  310. package/dist/spec/db/fts.js +19 -50
  311. package/dist/spec/db/fts.js.map +1 -1
  312. package/dist/spec/db/index.d.ts +5 -3
  313. package/dist/spec/db/index.d.ts.map +1 -1
  314. package/dist/spec/db/index.js +16 -1
  315. package/dist/spec/db/index.js.map +1 -1
  316. package/dist/spec/db/persist.d.ts +45 -0
  317. package/dist/spec/db/persist.d.ts.map +1 -0
  318. package/dist/spec/db/persist.js +66 -0
  319. package/dist/spec/db/persist.js.map +1 -0
  320. package/dist/spec/db/relations.d.ts +80 -1
  321. package/dist/spec/db/relations.d.ts.map +1 -1
  322. package/dist/spec/db/relations.js +175 -4
  323. package/dist/spec/db/relations.js.map +1 -1
  324. package/dist/spec/db/schema.d.ts +4 -0
  325. package/dist/spec/db/schema.d.ts.map +1 -1
  326. package/dist/spec/db/schema.js +4 -0
  327. package/dist/spec/db/schema.js.map +1 -1
  328. package/dist/spec/db/schema.sql +5 -1
  329. package/dist/spec/db/spec-node.d.ts +5 -0
  330. package/dist/spec/db/spec-node.d.ts.map +1 -1
  331. package/dist/spec/db/spec-node.js +11 -5
  332. package/dist/spec/db/spec-node.js.map +1 -1
  333. package/dist/spec/db/sql-utils.d.ts +11 -0
  334. package/dist/spec/db/sql-utils.d.ts.map +1 -0
  335. package/dist/spec/db/sql-utils.js +19 -0
  336. package/dist/spec/db/sql-utils.js.map +1 -0
  337. package/dist/spec/evolve/cluster-context.d.ts +30 -0
  338. package/dist/spec/evolve/cluster-context.d.ts.map +1 -0
  339. package/dist/spec/evolve/cluster-context.js +78 -0
  340. package/dist/spec/evolve/cluster-context.js.map +1 -0
  341. package/dist/spec/evolve/commit-spec-analyzer.d.ts +51 -0
  342. package/dist/spec/evolve/commit-spec-analyzer.d.ts.map +1 -0
  343. package/dist/spec/evolve/commit-spec-analyzer.js +88 -0
  344. package/dist/spec/evolve/commit-spec-analyzer.js.map +1 -0
  345. package/dist/spec/evolve/commit-spec-persister.d.ts +53 -0
  346. package/dist/spec/evolve/commit-spec-persister.d.ts.map +1 -0
  347. package/dist/spec/evolve/commit-spec-persister.js +93 -0
  348. package/dist/spec/evolve/commit-spec-persister.js.map +1 -0
  349. package/dist/spec/evolve/impact-locator.d.ts +23 -7
  350. package/dist/spec/evolve/impact-locator.d.ts.map +1 -1
  351. package/dist/spec/evolve/impact-locator.js +58 -14
  352. package/dist/spec/evolve/impact-locator.js.map +1 -1
  353. package/dist/spec/evolve/pipeline.d.ts +62 -25
  354. package/dist/spec/evolve/pipeline.d.ts.map +1 -1
  355. package/dist/spec/evolve/pipeline.js +436 -457
  356. package/dist/spec/evolve/pipeline.js.map +1 -1
  357. package/dist/spec/evolve/spec-rewriter.d.ts +13 -13
  358. package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -1
  359. package/dist/spec/evolve/spec-rewriter.js +45 -45
  360. package/dist/spec/evolve/spec-rewriter.js.map +1 -1
  361. package/dist/spec/git/commits.d.ts +85 -0
  362. package/dist/spec/git/commits.d.ts.map +1 -0
  363. package/dist/spec/git/commits.js +218 -0
  364. package/dist/spec/git/commits.js.map +1 -0
  365. package/dist/spec/git/exec.d.ts +13 -0
  366. package/dist/spec/git/exec.d.ts.map +1 -0
  367. package/dist/spec/git/exec.js +19 -0
  368. package/dist/spec/git/exec.js.map +1 -0
  369. package/dist/spec/git/index.d.ts +9 -0
  370. package/dist/spec/git/index.d.ts.map +1 -0
  371. package/dist/spec/git/index.js +22 -0
  372. package/dist/spec/git/index.js.map +1 -0
  373. package/dist/spec/git-utils.d.ts +8 -0
  374. package/dist/spec/git-utils.d.ts.map +1 -0
  375. package/dist/spec/git-utils.js +14 -0
  376. package/dist/spec/git-utils.js.map +1 -0
  377. package/dist/spec/graph/index.d.ts +8 -0
  378. package/dist/spec/graph/index.d.ts.map +1 -0
  379. package/dist/spec/graph/index.js +17 -0
  380. package/dist/spec/graph/index.js.map +1 -0
  381. package/dist/spec/graph/queries.d.ts +6 -35
  382. package/dist/spec/graph/queries.d.ts.map +1 -1
  383. package/dist/spec/graph/queries.js +71 -248
  384. package/dist/spec/graph/queries.js.map +1 -1
  385. package/dist/spec/llm/agent-client.d.ts +59 -0
  386. package/dist/spec/llm/agent-client.d.ts.map +1 -0
  387. package/dist/spec/llm/agent-client.js +213 -0
  388. package/dist/spec/llm/agent-client.js.map +1 -0
  389. package/dist/spec/llm/agents/claude-code.d.ts +32 -0
  390. package/dist/spec/llm/agents/claude-code.d.ts.map +1 -0
  391. package/dist/spec/llm/agents/claude-code.js +125 -0
  392. package/dist/spec/llm/agents/claude-code.js.map +1 -0
  393. package/dist/spec/llm/agents/codex.d.ts +32 -0
  394. package/dist/spec/llm/agents/codex.d.ts.map +1 -0
  395. package/dist/spec/llm/agents/codex.js +145 -0
  396. package/dist/spec/llm/agents/codex.js.map +1 -0
  397. package/dist/spec/llm/agents/detect-utils.d.ts +28 -0
  398. package/dist/spec/llm/agents/detect-utils.d.ts.map +1 -0
  399. package/dist/spec/llm/agents/detect-utils.js +91 -0
  400. package/dist/spec/llm/agents/detect-utils.js.map +1 -0
  401. package/dist/spec/llm/agents/deveco-code.d.ts +33 -0
  402. package/dist/spec/llm/agents/deveco-code.d.ts.map +1 -0
  403. package/dist/spec/llm/agents/deveco-code.js +165 -0
  404. package/dist/spec/llm/agents/deveco-code.js.map +1 -0
  405. package/dist/spec/llm/agents/index.d.ts +31 -0
  406. package/dist/spec/llm/agents/index.d.ts.map +1 -0
  407. package/dist/spec/llm/agents/index.js +65 -0
  408. package/dist/spec/llm/agents/index.js.map +1 -0
  409. package/dist/spec/llm/agents/types.d.ts +76 -0
  410. package/dist/spec/llm/agents/types.d.ts.map +1 -0
  411. package/dist/spec/llm/agents/types.js +15 -0
  412. package/dist/spec/llm/agents/types.js.map +1 -0
  413. package/dist/spec/llm/client.d.ts +31 -11
  414. package/dist/spec/llm/client.d.ts.map +1 -1
  415. package/dist/spec/llm/client.js +143 -81
  416. package/dist/spec/llm/client.js.map +1 -1
  417. package/dist/spec/llm/factory.d.ts +41 -0
  418. package/dist/spec/llm/factory.d.ts.map +1 -0
  419. package/dist/spec/llm/factory.js +80 -0
  420. package/dist/spec/llm/factory.js.map +1 -0
  421. package/dist/spec/llm/prompts.d.ts +24 -8
  422. package/dist/spec/llm/prompts.d.ts.map +1 -1
  423. package/dist/spec/llm/prompts.js +100 -50
  424. package/dist/spec/llm/prompts.js.map +1 -1
  425. package/dist/spec/llm/retry.d.ts +53 -0
  426. package/dist/spec/llm/retry.d.ts.map +1 -0
  427. package/dist/spec/llm/retry.js +153 -0
  428. package/dist/spec/llm/retry.js.map +1 -0
  429. package/dist/spec/mine/clusterer.d.ts +63 -0
  430. package/dist/spec/mine/clusterer.d.ts.map +1 -0
  431. package/dist/spec/mine/clusterer.js +904 -0
  432. package/dist/spec/mine/clusterer.js.map +1 -0
  433. package/dist/spec/mine/clustering/features.d.ts +38 -0
  434. package/dist/spec/mine/clustering/features.d.ts.map +1 -0
  435. package/dist/spec/mine/clustering/features.js +108 -0
  436. package/dist/spec/mine/clustering/features.js.map +1 -0
  437. package/dist/spec/mine/clustering/index.d.ts +62 -0
  438. package/dist/spec/mine/clustering/index.d.ts.map +1 -0
  439. package/dist/spec/mine/clustering/index.js +259 -0
  440. package/dist/spec/mine/clustering/index.js.map +1 -0
  441. package/dist/spec/mine/clustering/leiden.d.ts +61 -0
  442. package/dist/spec/mine/clustering/leiden.d.ts.map +1 -0
  443. package/dist/spec/mine/clustering/leiden.js +489 -0
  444. package/dist/spec/mine/clustering/leiden.js.map +1 -0
  445. package/dist/spec/mine/clustering/text-similarity.d.ts +22 -0
  446. package/dist/spec/mine/clustering/text-similarity.d.ts.map +1 -0
  447. package/dist/spec/mine/clustering/text-similarity.js +98 -0
  448. package/dist/spec/mine/clustering/text-similarity.js.map +1 -0
  449. package/dist/spec/mine/generator.d.ts +54 -0
  450. package/dist/spec/mine/generator.d.ts.map +1 -0
  451. package/dist/spec/mine/generator.js +267 -0
  452. package/dist/spec/mine/generator.js.map +1 -0
  453. package/dist/spec/mine/persist.d.ts +37 -0
  454. package/dist/spec/mine/persist.d.ts.map +1 -0
  455. package/dist/spec/mine/persist.js +170 -0
  456. package/dist/spec/mine/persist.js.map +1 -0
  457. package/dist/spec/mine/pipeline.d.ts +41 -0
  458. package/dist/spec/mine/pipeline.d.ts.map +1 -0
  459. package/dist/spec/mine/pipeline.js +236 -0
  460. package/dist/spec/mine/pipeline.js.map +1 -0
  461. package/dist/spec/mine/progress-handler.d.ts +22 -0
  462. package/dist/spec/mine/progress-handler.d.ts.map +1 -0
  463. package/dist/spec/mine/progress-handler.js +108 -0
  464. package/dist/spec/mine/progress-handler.js.map +1 -0
  465. package/dist/spec/mine/progress.d.ts +23 -0
  466. package/dist/spec/mine/progress.d.ts.map +1 -0
  467. package/dist/spec/mine/progress.js +12 -0
  468. package/dist/spec/mine/progress.js.map +1 -0
  469. package/dist/spec/mine/scanner.d.ts +59 -0
  470. package/dist/spec/mine/scanner.d.ts.map +1 -0
  471. package/dist/spec/mine/scanner.js +383 -0
  472. package/dist/spec/mine/scanner.js.map +1 -0
  473. package/dist/spec/types.d.ts +32 -0
  474. package/dist/spec/types.d.ts.map +1 -1
  475. package/dist/spec/ui/index.d.ts +9 -0
  476. package/dist/spec/ui/index.d.ts.map +1 -0
  477. package/dist/spec/ui/index.js +16 -0
  478. package/dist/spec/ui/index.js.map +1 -0
  479. package/dist/spec/ui/progress-handler.d.ts +33 -0
  480. package/dist/spec/ui/progress-handler.d.ts.map +1 -0
  481. package/dist/spec/ui/progress-handler.js +132 -0
  482. package/dist/spec/ui/progress-handler.js.map +1 -0
  483. package/dist/spec/ui/progress.d.ts +24 -0
  484. package/dist/spec/ui/progress.d.ts.map +1 -0
  485. package/dist/spec/ui/progress.js +13 -0
  486. package/dist/spec/ui/progress.js.map +1 -0
  487. package/dist/spec/utils/fs.d.ts +45 -0
  488. package/dist/spec/utils/fs.d.ts.map +1 -0
  489. package/dist/spec/utils/fs.js +163 -0
  490. package/dist/spec/utils/fs.js.map +1 -0
  491. package/dist/spec/utils/index.d.ts +12 -0
  492. package/dist/spec/utils/index.d.ts.map +1 -0
  493. package/dist/spec/utils/index.js +26 -0
  494. package/dist/spec/utils/index.js.map +1 -0
  495. package/dist/spec/utils/meta.d.ts +32 -0
  496. package/dist/spec/utils/meta.d.ts.map +1 -0
  497. package/dist/spec/utils/meta.js +122 -0
  498. package/dist/spec/utils/meta.js.map +1 -0
  499. package/dist/spec/utils/truncate.d.ts +86 -0
  500. package/dist/spec/utils/truncate.d.ts.map +1 -0
  501. package/dist/spec/utils/truncate.js +172 -0
  502. package/dist/spec/utils/truncate.js.map +1 -0
  503. package/dist/spec/utils.d.ts +4 -16
  504. package/dist/spec/utils.d.ts.map +1 -1
  505. package/dist/spec/utils.js +6 -58
  506. package/dist/spec/utils.js.map +1 -1
  507. package/dist/sync/git-hooks.d.ts.map +1 -1
  508. package/dist/sync/git-hooks.js +2 -0
  509. package/dist/sync/git-hooks.js.map +1 -1
  510. package/dist/sync/watcher.d.ts +35 -6
  511. package/dist/sync/watcher.d.ts.map +1 -1
  512. package/dist/sync/watcher.js +127 -17
  513. package/dist/sync/watcher.js.map +1 -1
  514. package/dist/sync/worktree.d.ts.map +1 -1
  515. package/dist/sync/worktree.js +5 -0
  516. package/dist/sync/worktree.js.map +1 -1
  517. package/dist/telemetry/index.js +3 -3
  518. package/dist/telemetry/index.js.map +1 -1
  519. package/dist/types.d.ts +22 -1
  520. package/dist/types.d.ts.map +1 -1
  521. package/dist/types.js +10 -0
  522. package/dist/types.js.map +1 -1
  523. package/dist/upgrade/index.d.ts +32 -0
  524. package/dist/upgrade/index.d.ts.map +1 -1
  525. package/dist/upgrade/index.js +162 -14
  526. package/dist/upgrade/index.js.map +1 -1
  527. package/dist/upgrade/remove-binary.d.ts +87 -0
  528. package/dist/upgrade/remove-binary.d.ts.map +1 -0
  529. package/dist/upgrade/remove-binary.js +289 -0
  530. package/dist/upgrade/remove-binary.js.map +1 -0
  531. package/dist/upgrade/update-check.d.ts +92 -0
  532. package/dist/upgrade/update-check.d.ts.map +1 -0
  533. package/dist/upgrade/update-check.js +258 -0
  534. package/dist/upgrade/update-check.js.map +1 -0
  535. package/package.json +6 -2
  536. package/scripts/agent-eval/run-all.sh +6 -0
  537. package/scripts/build-bundle.sh +5 -0
  538. package/scripts/npm-shim.js +8 -1
  539. package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
  540. package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
  541. package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-310.pyc +0 -0
  542. package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
  543. package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
  544. package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
  545. package/scripts/exp_boundary_eval/data/agents.json +0 -109
  546. package/scripts/exp_boundary_eval/data/experiments.json +0 -140
  547. package/scripts/qa_eval/README.md +0 -407
  548. package/scripts/qa_eval/_test_deveco_probe.py +0 -41
  549. package/scripts/qa_eval/agent_runner.py +0 -526
  550. package/scripts/qa_eval/data/.gitignore +0 -4
  551. package/scripts/qa_eval/data/test-set.jsonl +0 -2
  552. package/scripts/qa_eval/eval_metrics.py +0 -274
  553. package/scripts/qa_eval/external_agent.py +0 -976
  554. package/scripts/qa_eval/llm_config.py +0 -92
  555. package/scripts/qa_eval/memory_monitor.py +0 -132
  556. package/scripts/qa_eval/my_answer_accuracy.py +0 -187
  557. package/scripts/qa_eval/requirements.txt +0 -2
  558. package/scripts/qa_eval/run_pipeline.py +0 -804
  559. package/scripts/qa_eval/stats_efficiency.py +0 -279
  560. package/scripts/qa_eval/stats_scores.py +0 -207
@@ -0,0 +1,904 @@
1
+ "use strict";
2
+ /**
3
+ * Commit Clusterer — multi-signal graph clustering for the `spec mine` pipeline.
4
+ *
5
+ * Groups commits by structural (AST symbol overlap), file-path, temporal,
6
+ * and commit-message similarity. Pure TypeScript implementation — no native
7
+ * dependencies required.
8
+ *
9
+ * Algorithm: Leiden community detection on a weighted similarity graph.
10
+ * Replaces Louvain and connected-components (single-linkage) to avoid chaining
11
+ * all commits into one giant cluster and to guarantee well-connected communities
12
+ * via the refinement phase. A resolution parameter γ is auto-tuned via
13
+ * multiplicative search so the output community count stays close to maxClusters.
14
+ *
15
+ * @module spec/mine/clusterer
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.clusterCommits = clusterCommits;
19
+ const errors_1 = require("../../errors");
20
+ const query_utils_1 = require("../../search/query-utils");
21
+ // ---------------------------------------------------------------------------
22
+ // Helpers
23
+ // ---------------------------------------------------------------------------
24
+ /** Tokenize a string into lowercase words (3+ chars). */
25
+ function tokenize(text) {
26
+ return text
27
+ .toLowerCase()
28
+ .split(/[^a-z0-9]+/)
29
+ .filter((t) => t.length >= 3);
30
+ }
31
+ /** Jaccard similarity between two sets of strings. */
32
+ function jaccard(a, b) {
33
+ if (a.length === 0 && b.length === 0)
34
+ return 0;
35
+ const setA = new Set(a);
36
+ const setB = new Set(b);
37
+ let intersection = 0;
38
+ for (const x of setA) {
39
+ if (setB.has(x))
40
+ intersection++;
41
+ }
42
+ const union = setA.size + setB.size - intersection;
43
+ return union === 0 ? 0 : intersection / union;
44
+ }
45
+ /** Cosine similarity between two equal-length numeric vectors. */
46
+ function cosineSimilarity(a, b) {
47
+ if (a.length !== b.length || a.length === 0)
48
+ return 0;
49
+ let dot = 0;
50
+ let magA = 0;
51
+ let magB = 0;
52
+ for (let i = 0; i < a.length; i++) {
53
+ dot += a[i] * b[i];
54
+ magA += a[i] * a[i];
55
+ magB += b[i] * b[i];
56
+ }
57
+ const denom = Math.sqrt(magA) * Math.sqrt(magB);
58
+ return denom === 0 ? 0 : dot / denom;
59
+ }
60
+ /** Collect all symbol names across all file changes in a commit. */
61
+ function collectSymbolNames(change) {
62
+ const names = new Set();
63
+ for (const fc of change.fileChanges) {
64
+ for (const s of fc.addedSymbols)
65
+ names.add(s.name);
66
+ for (const s of fc.removedSymbols)
67
+ names.add(s.name);
68
+ for (const m of fc.modifiedSymbols) {
69
+ names.add(m.old.name);
70
+ names.add(m.new.name);
71
+ }
72
+ }
73
+ return Array.from(names);
74
+ }
75
+ /** Collect all file paths changed in a commit. */
76
+ function collectFilePaths(change) {
77
+ const paths = new Set();
78
+ for (const fc of change.fileChanges) {
79
+ paths.add(fc.filePath);
80
+ }
81
+ return Array.from(paths);
82
+ }
83
+ /** Collect directory prefixes (first 2 path segments) for module proximity.
84
+ * For single-segment paths (root-level files) the filename itself is used as
85
+ * the key — this prevents unrelated root-level files from falsely sharing an
86
+ * empty "directory" and being grouped together. */
87
+ function collectDirectoryPrefixes(change) {
88
+ const dirs = new Set();
89
+ for (const fc of change.fileChanges) {
90
+ const parts = fc.filePath.split('/');
91
+ if (parts.length === 1) {
92
+ dirs.add(parts[0]);
93
+ }
94
+ else {
95
+ dirs.add(parts.slice(0, 2).join('/'));
96
+ }
97
+ }
98
+ return Array.from(dirs);
99
+ }
100
+ /** Minimum number of changed symbols (added + removed + modified) required
101
+ * for a solo commit to be promoted into its own cluster. Commits below
102
+ * this threshold go to `unclustered` — they are too small to warrant a
103
+ * standalone spec document. */
104
+ const MIN_SYMBOLS_FOR_SOLO_CLUSTER = 2;
105
+ /** Count total changed symbols across all file changes in a commit. */
106
+ function countChangedSymbols(change) {
107
+ let count = 0;
108
+ for (const fc of change.fileChanges) {
109
+ count += fc.addedSymbols.length;
110
+ count += fc.removedSymbols.length;
111
+ count += fc.modifiedSymbols.length;
112
+ }
113
+ return count;
114
+ }
115
+ /** Compute the cohesion ratio for a commit — symbols changed per file touched.
116
+ * Low cohesion (< 1.0) suggests a mechanical refactor across many files;
117
+ * high cohesion (≥ 2.0) suggests concentrated feature work. */
118
+ function computeCohesion(change) {
119
+ const totalSymbols = countChangedSymbols(change);
120
+ const filesTouched = change.fileChanges.length || 1;
121
+ return totalSymbols / filesTouched;
122
+ }
123
+ /** Extract ticket references (e.g., PROJ-123, #456) from a commit message. */
124
+ function extractTicketRefs(message) {
125
+ const refs = [];
126
+ const jiraRe = /[A-Z]+-\d+/g;
127
+ const ghRe = /#\d+/g;
128
+ let m;
129
+ while ((m = jiraRe.exec(message)) !== null)
130
+ refs.push(m[0]);
131
+ while ((m = ghRe.exec(message)) !== null)
132
+ refs.push(m[0]);
133
+ return Array.from(new Set(refs));
134
+ }
135
+ // ---------------------------------------------------------------------------
136
+ // TF-IDF Vectorizer (message-level)
137
+ // ---------------------------------------------------------------------------
138
+ class TfidfVectorizer {
139
+ vocabulary = new Map();
140
+ idf = [];
141
+ /** Build vocabulary and compute IDF from document corpus. */
142
+ fit(documents) {
143
+ const docFreq = new Map();
144
+ const N = documents.length;
145
+ for (const doc of documents) {
146
+ const seen = new Set();
147
+ for (const term of doc) {
148
+ if (!seen.has(term)) {
149
+ seen.add(term);
150
+ docFreq.set(term, (docFreq.get(term) || 0) + 1);
151
+ }
152
+ }
153
+ }
154
+ // Build vocabulary sorted for deterministic output
155
+ const sorted = Array.from(docFreq.keys()).sort();
156
+ this.vocabulary.clear();
157
+ this.idf = [];
158
+ for (let i = 0; i < sorted.length; i++) {
159
+ const term = sorted[i];
160
+ this.vocabulary.set(term, i);
161
+ // IDF = log((N + 1) / (df + 1)) + 1 (smooth)
162
+ const df = docFreq.get(term);
163
+ this.idf.push(Math.log((N + 1) / (df + 1)) + 1);
164
+ }
165
+ }
166
+ /** Transform a document into a TF-IDF vector. */
167
+ transform(document) {
168
+ const vec = new Array(this.vocabulary.size).fill(0);
169
+ const tf = new Map();
170
+ for (const term of document) {
171
+ tf.set(term, (tf.get(term) || 0) + 1);
172
+ }
173
+ for (const [term, count] of tf) {
174
+ const idx = this.vocabulary.get(term);
175
+ if (idx !== undefined) {
176
+ vec[idx] = (count / document.length) * this.idf[idx];
177
+ }
178
+ }
179
+ return vec;
180
+ }
181
+ }
182
+ // ---------------------------------------------------------------------------
183
+ // Similarity Computation
184
+ // ---------------------------------------------------------------------------
185
+ /**
186
+ * Compute multi-signal similarity between two commits.
187
+ *
188
+ * Weights:
189
+ * - Symbol overlap (Jaccard): 0.40
190
+ * - Spatial proximity (Jaccard): 0.15 (max of file-path and dir-prefix)
191
+ * - Message TF-IDF cosine: 0.25
192
+ * - Ticket reference overlap: 0.10
193
+ * - Temporal proximity: 0.10 (half-life 3 days)
194
+ *
195
+ * A cohesion penalty is applied as a multiplier when one commit has low
196
+ * symbol-per-file density (< 2.0), which is characteristic of mechanical
197
+ * refactors that should not bridge unrelated feature clusters.
198
+ */
199
+ function computeSimilarity(a, b, aSymbols, bSymbols, aFiles, bFiles, aDirs, bDirs, aTickets, bTickets, msgTfidfMatrix, aIdx, bIdx) {
200
+ // Symbol overlap — highest weight because AST-level diff is precise
201
+ const symbolSim = jaccard(aSymbols, bSymbols);
202
+ // Spatial proximity — max of exact file-path and module-level dir-prefix
203
+ // overlap, so same-file edits get full credit and same-module edits also
204
+ // connect, without double-counting the same spatial dimension.
205
+ const spatialSim = Math.max(jaccard(aFiles, bFiles), jaccard(aDirs, bDirs));
206
+ // Message TF-IDF cosine
207
+ const msgSim = cosineSimilarity(msgTfidfMatrix[aIdx], msgTfidfMatrix[bIdx]);
208
+ // Ticket reference overlap
209
+ const ticketSim = jaccard(aTickets, bTickets);
210
+ // Temporal proximity — half-life 3 days (259200 seconds)
211
+ const halfLife = 3 * 24 * 60 * 60 * 1000;
212
+ const maxTimeSpan = Math.abs(a.timestamp - b.timestamp);
213
+ const timeSim = Math.exp(-maxTimeSpan / halfLife);
214
+ // Cohesion penalty: low cohesion (many files, few symbols) = refactor-like.
215
+ // Penalty multiplier: 0.6 at cohesion 0 → 1.0 at cohesion ≥ 2.0
216
+ const minCohesion = Math.min(computeCohesion(a), computeCohesion(b));
217
+ const cohesionPenalty = minCohesion >= 2.0
218
+ ? 1.0
219
+ : 0.6 + 0.4 * (minCohesion / 2.0);
220
+ const rawScore = 0.40 * symbolSim +
221
+ 0.15 * spatialSim +
222
+ 0.25 * msgSim +
223
+ 0.10 * ticketSim +
224
+ 0.10 * timeSim;
225
+ return rawScore * cohesionPenalty;
226
+ }
227
+ /** Compute weighted degree for every node. */
228
+ function computeDegrees(graph) {
229
+ const n = graph.length;
230
+ const degrees = new Array(n).fill(0);
231
+ for (let i = 0; i < n; i++) {
232
+ for (let j = 0; j < n; j++) {
233
+ degrees[i] += graph[i][j];
234
+ }
235
+ }
236
+ return degrees;
237
+ }
238
+ /**
239
+ * Shuffle an array in place (Fisher-Yates). Returns the same array.
240
+ */
241
+ function shuffle(arr) {
242
+ for (let i = arr.length - 1; i > 0; i--) {
243
+ const j = Math.floor(Math.random() * (i + 1));
244
+ [arr[i], arr[j]] = [arr[j], arr[i]];
245
+ }
246
+ return arr;
247
+ }
248
+ /**
249
+ * Create an array [0, 1, ..., n-1].
250
+ */
251
+ function range(n) {
252
+ return Array.from({ length: n }, (_, i) => i);
253
+ }
254
+ /**
255
+ * Modularity gain from moving node i into community C.
256
+ *
257
+ * Assumes node i has already been *removed* from its current community.
258
+ *
259
+ * @param k_i_in - Sum of edge weights from node i to community C.
260
+ * @param k_i - Weighted degree of node i.
261
+ * @param sigmaTotC - Total weighted degree of community C BEFORE adding i.
262
+ * @param m2 - Twice the total edge weight of the graph (2m).
263
+ * @param gamma - Resolution parameter (higher = more communities).
264
+ */
265
+ function modularityGain(k_i_in, k_i, sigmaTotC, m2, gamma) {
266
+ if (m2 === 0)
267
+ return 0;
268
+ return k_i_in - gamma * sigmaTotC * k_i / m2;
269
+ }
270
+ /**
271
+ * Refinement phase of the Leiden algorithm.
272
+ *
273
+ * For each Phase-1 community, runs a restricted local-moving pass that starts
274
+ * from singleton sub-communities and only allows moves within the same community.
275
+ * This guarantees that sub-communities are internally well-connected before they
276
+ * are aggregated — the key advantage of Leiden over Louvain.
277
+ *
278
+ * @param graph - Full weighted graph.
279
+ * @param communities - Phase-1 partition (list of node-index arrays).
280
+ * @param degrees - Pre-computed weighted degrees for every node.
281
+ * @param m2 - Twice the total edge weight of the full graph.
282
+ * @param gamma - Resolution parameter.
283
+ * @returns Refined partition — flat list of sub-community node-index arrays.
284
+ */
285
+ function refine(graph, communities, degrees, m2, gamma) {
286
+ const refined = [];
287
+ for (const community of communities) {
288
+ if (community.length <= 1) {
289
+ refined.push([...community]);
290
+ continue;
291
+ }
292
+ const nodes = community;
293
+ const nodeCount = nodes.length;
294
+ // Each node starts in its own singleton sub-community.
295
+ const nodeToSub = range(nodeCount);
296
+ const subSigmaTot = nodes.map((n) => degrees[n]);
297
+ // Local moving restricted to within this community only.
298
+ const MAX_PASSES = 20;
299
+ for (let pass = 0; pass < MAX_PASSES; pass++) {
300
+ let moved = false;
301
+ const order = shuffle(range(nodeCount));
302
+ for (const localIdx of order) {
303
+ const globalNode = nodes[localIdx];
304
+ const currentSub = nodeToSub[localIdx];
305
+ const k_i = degrees[globalNode];
306
+ // Collect edge weights to neighbor sub-communities (within this community only).
307
+ const neighborSubs = new Map();
308
+ for (let j = 0; j < nodeCount; j++) {
309
+ if (j === localIdx)
310
+ continue;
311
+ const w = graph[globalNode][nodes[j]];
312
+ if (w > 0) {
313
+ const subJ = nodeToSub[j];
314
+ neighborSubs.set(subJ, (neighborSubs.get(subJ) || 0) + w);
315
+ }
316
+ }
317
+ // ΔQ for removing from current sub-community.
318
+ const k_i_in_current = neighborSubs.get(currentSub) || 0;
319
+ const sigmaTotCurrent = subSigmaTot[currentSub];
320
+ const sigmaTotCurrentWithout = sigmaTotCurrent - k_i;
321
+ const deltaQRemove = k_i_in_current > 0 || sigmaTotCurrentWithout > 0
322
+ ? -(k_i_in_current - gamma * sigmaTotCurrentWithout * k_i / m2)
323
+ : 0;
324
+ let bestSub = currentSub;
325
+ let bestDeltaQ = 0;
326
+ for (const [subId, k_i_in] of neighborSubs) {
327
+ if (subId === currentSub)
328
+ continue;
329
+ const sigmaTotTarget = subSigmaTot[subId];
330
+ if (sigmaTotTarget === 0)
331
+ continue;
332
+ const deltaQAdd = modularityGain(k_i_in, k_i, sigmaTotTarget, m2, gamma);
333
+ const totalDelta = deltaQRemove + deltaQAdd;
334
+ if (totalDelta > bestDeltaQ) {
335
+ bestDeltaQ = totalDelta;
336
+ bestSub = subId;
337
+ }
338
+ }
339
+ if (bestSub !== currentSub) {
340
+ nodeToSub[localIdx] = bestSub;
341
+ subSigmaTot[currentSub] -= k_i;
342
+ subSigmaTot[bestSub] += k_i;
343
+ moved = true;
344
+ }
345
+ }
346
+ if (!moved)
347
+ break;
348
+ }
349
+ // Collect non-empty sub-communities from this community.
350
+ const subCommToNodes = new Map();
351
+ for (let i = 0; i < nodeCount; i++) {
352
+ const subId = nodeToSub[i];
353
+ if (!subCommToNodes.has(subId)) {
354
+ subCommToNodes.set(subId, []);
355
+ }
356
+ subCommToNodes.get(subId).push(nodes[i]);
357
+ }
358
+ for (const [, members] of subCommToNodes) {
359
+ refined.push(members);
360
+ }
361
+ }
362
+ return refined;
363
+ }
364
+ /**
365
+ * Core Leiden algorithm: iterative local-moving + refinement + aggregation.
366
+ *
367
+ * Unlike Louvain (which feeds the Phase-1 partition directly into aggregation),
368
+ * Leiden inserts a refinement step that splits each Phase-1 community into
369
+ * well-connected sub-communities. Only the refined partition is aggregated.
370
+ * This guarantees internally-connected communities and avoids the
371
+ * "disconnected community" defect of Louvain.
372
+ *
373
+ * @param graph - Weighted undirected similarity graph.
374
+ * @param gamma - Resolution parameter (default 1.0).
375
+ * @param depth - Aggregation depth guard (internal use).
376
+ */
377
+ function leiden(graph, gamma, depth = 0) {
378
+ const n = graph.length;
379
+ if (n <= 1) {
380
+ return n === 0 ? [] : [[0]];
381
+ }
382
+ const degrees = computeDegrees(graph);
383
+ const totalWeight = degrees.reduce((a, b) => a + b, 0);
384
+ const m2 = totalWeight; // 2m (sum of all degrees = 2 × sum of edge weights)
385
+ // Each node starts in its own community
386
+ const nodeToComm = range(n);
387
+ // Per-community stats
388
+ // sigmaTot[c] = sum of degrees of all nodes in community c
389
+ const sigmaTot = [...degrees];
390
+ // Phase 1: local moving
391
+ const MAX_PASSES = 20;
392
+ for (let pass = 0; pass < MAX_PASSES; pass++) {
393
+ let moved = false;
394
+ const order = shuffle(range(n));
395
+ for (const node of order) {
396
+ const currentComm = nodeToComm[node];
397
+ const k_i = degrees[node];
398
+ // Collect edge weights to each neighbor community
399
+ const neighborComms = new Map(); // commId → k_i_in
400
+ for (let j = 0; j < n; j++) {
401
+ const w = graph[node][j];
402
+ if (w > 0) {
403
+ const cj = nodeToComm[j];
404
+ neighborComms.set(cj, (neighborComms.get(cj) || 0) + w);
405
+ }
406
+ }
407
+ // Compute ΔQ for removing node from its current community.
408
+ // k_i_in_current = edges from node to nodes in current community.
409
+ const k_i_in_current = neighborComms.get(currentComm) || 0;
410
+ const sigmaTotCurrent = sigmaTot[currentComm];
411
+ // ΔQ of removal (negative of the gain formula applied to current community
412
+ // with sigmaTot excluding self)
413
+ const sigmaTotCurrentWithout = sigmaTotCurrent - k_i;
414
+ const deltaQRemove = k_i_in_current > 0 || sigmaTotCurrentWithout > 0
415
+ ? -(k_i_in_current - gamma * sigmaTotCurrentWithout * k_i / m2)
416
+ : 0;
417
+ // Find best community to move to
418
+ let bestComm = currentComm;
419
+ let bestDeltaQ = 0;
420
+ for (const [commId, k_i_in] of neighborComms) {
421
+ if (commId === currentComm)
422
+ continue;
423
+ const sigmaTotTarget = sigmaTot[commId];
424
+ if (sigmaTotTarget === 0)
425
+ continue;
426
+ const deltaQAdd = modularityGain(k_i_in, k_i, sigmaTotTarget, m2, gamma);
427
+ const totalDelta = deltaQRemove + deltaQAdd;
428
+ if (totalDelta > bestDeltaQ) {
429
+ bestDeltaQ = totalDelta;
430
+ bestComm = commId;
431
+ }
432
+ }
433
+ if (bestComm !== currentComm) {
434
+ // Move node from currentComm to bestComm
435
+ nodeToComm[node] = bestComm;
436
+ sigmaTot[currentComm] -= k_i;
437
+ sigmaTot[bestComm] += k_i;
438
+ moved = true;
439
+ }
440
+ }
441
+ if (!moved)
442
+ break;
443
+ }
444
+ // Collect non-empty communities from Phase 1
445
+ const commIdToMembers = new Map();
446
+ for (let i = 0; i < n; i++) {
447
+ const cid = nodeToComm[i];
448
+ if (!commIdToMembers.has(cid)) {
449
+ commIdToMembers.set(cid, []);
450
+ }
451
+ commIdToMembers.get(cid).push(i);
452
+ }
453
+ const communities = Array.from(commIdToMembers.values());
454
+ // Refinement: split each Phase-1 community into well-connected sub-communities.
455
+ const refinedCommunities = refine(graph, communities, degrees, m2, gamma);
456
+ // Phase 2: Aggregation using refined partition (Leiden key difference from Louvain).
457
+ const k = refinedCommunities.length;
458
+ if (k < n && k > 1 && depth < 5) {
459
+ const aggGraph = aggregateGraph(graph, refinedCommunities);
460
+ const subCommunities = leiden(aggGraph, gamma, depth + 1);
461
+ return mapBackCommunities(subCommunities, refinedCommunities);
462
+ }
463
+ return refinedCommunities;
464
+ }
465
+ /**
466
+ * Build an aggregated graph where each node represents a community from the
467
+ * previous level. Edge weights are summed across community boundaries.
468
+ */
469
+ function aggregateGraph(graph, communities) {
470
+ const k = communities.length;
471
+ const agg = Array.from({ length: k }, () => new Array(k).fill(0));
472
+ // Build reverse map: original node → community index
473
+ const nodeToCommIdx = new Map();
474
+ for (let ci = 0; ci < k; ci++) {
475
+ for (const node of communities[ci]) {
476
+ nodeToCommIdx.set(node, ci);
477
+ }
478
+ }
479
+ // Aggregate edge weights
480
+ for (let i = 0; i < graph.length; i++) {
481
+ const ci = nodeToCommIdx.get(i);
482
+ for (let j = i + 1; j < graph.length; j++) {
483
+ const w = graph[i][j];
484
+ if (w > 0) {
485
+ const cj = nodeToCommIdx.get(j);
486
+ if (ci === cj) {
487
+ // Self-loop: double weight to preserve total degree
488
+ // (each internal edge contributes to degrees of both endpoints).
489
+ agg[ci][ci] += 2 * w;
490
+ }
491
+ else {
492
+ agg[ci][cj] += w;
493
+ agg[cj][ci] += w;
494
+ }
495
+ }
496
+ }
497
+ }
498
+ return agg;
499
+ }
500
+ /**
501
+ * Map aggregated-level community indices back to original node indices.
502
+ */
503
+ function mapBackCommunities(subCommunities, originalCommunities) {
504
+ return subCommunities.map((aggComm) => {
505
+ const members = [];
506
+ for (const aggIdx of aggComm) {
507
+ for (const node of originalCommunities[aggIdx]) {
508
+ members.push(node);
509
+ }
510
+ }
511
+ return members;
512
+ });
513
+ }
514
+ /**
515
+ * Extract a sub-graph containing only the specified nodes and edges between them.
516
+ *
517
+ * @param graph - Full weighted graph.
518
+ * @param indices - Node indices to include in the sub-graph.
519
+ * @returns New WeightedGraph with local indexing; subGraph[i][j] == graph[indices[i]][indices[j]].
520
+ */
521
+ function extractSubGraph(graph, indices) {
522
+ const m = indices.length;
523
+ const subGraph = Array.from({ length: m }, () => new Array(m).fill(0));
524
+ for (let i = 0; i < m; i++) {
525
+ for (let j = i + 1; j < m; j++) {
526
+ const w = graph[indices[i]][indices[j]];
527
+ subGraph[i][j] = w;
528
+ subGraph[j][i] = w;
529
+ }
530
+ }
531
+ return subGraph;
532
+ }
533
+ // ---------------------------------------------------------------------------
534
+ // Resolution Auto-Tuning
535
+ // ---------------------------------------------------------------------------
536
+ /** Maximum iterations for resolution parameter search. */
537
+ const MAX_TUNING_ITER = 15;
538
+ /** Minimum cluster size to consider for recursive sub-splitting. */
539
+ const MIN_SUBSPLIT_SIZE = 4;
540
+ /** Maximum recursion depth for sub-splitting. */
541
+ const MAX_SUBSPLIT_DEPTH = 3;
542
+ /**
543
+ * Recursively split large clusters by running Leiden on their sub-graphs.
544
+ *
545
+ * For each cluster with size > MIN_SUBSPLIT_SIZE, extracts its sub-graph,
546
+ * runs Leiden with auto-tuned γ (targeting up to min(5, floor(size/2))),
547
+ * and recursively splits further if sub-communities still have structure.
548
+ *
549
+ * Stop conditions:
550
+ * - Cluster size ≤ MIN_SUBSPLIT_SIZE
551
+ * - Sub-graph Leiden returns 1 community (no further structure)
552
+ * - Max recursion depth reached (MAX_SUBSPLIT_DEPTH)
553
+ *
554
+ * @param graph - Full original weighted graph.
555
+ * @param communities - Current partition (list of node-index arrays).
556
+ * @param depth - Current recursion depth (internal).
557
+ * @returns Flat list of leaf-community node-index arrays.
558
+ */
559
+ function recursiveSubSplit(graph, communities, depth = 0) {
560
+ if (depth >= MAX_SUBSPLIT_DEPTH)
561
+ return communities;
562
+ const result = [];
563
+ for (const community of communities) {
564
+ if (community.length <= MIN_SUBSPLIT_SIZE) {
565
+ result.push(community);
566
+ continue;
567
+ }
568
+ // Extract sub-graph and run Leiden with independent γ tuning.
569
+ const subGraph = extractSubGraph(graph, community);
570
+ const targetClusters = Math.min(5, Math.floor(community.length / 2));
571
+ const subComms = autoTuneResolution(subGraph, targetClusters);
572
+ if (subComms.length <= 1) {
573
+ // No further structure found — keep as-is.
574
+ result.push(community);
575
+ continue;
576
+ }
577
+ // Map sub-community local indices back to original graph indices.
578
+ const mappedSubComms = subComms.map((subComm) => subComm.map((localIdx) => community[localIdx]));
579
+ // Recurse into sub-communities.
580
+ const splitResult = recursiveSubSplit(graph, mappedSubComms, depth + 1);
581
+ result.push(...splitResult);
582
+ }
583
+ return result;
584
+ }
585
+ /**
586
+ * Adjust the resolution parameter γ so that Leiden produces approximately
587
+ * `targetClusters` communities.
588
+ *
589
+ * Uses multiplicative search: higher γ → more communities, lower γ → fewer.
590
+ */
591
+ function autoTuneResolution(graph, maxClusters) {
592
+ const n = graph.length;
593
+ if (n <= maxClusters) {
594
+ // Can't have more communities than nodes. Run once with default gamma
595
+ // and let post-processing handle it.
596
+ return leiden(graph, 1.0);
597
+ }
598
+ let gamma = 1.0;
599
+ let communities = leiden(graph, gamma);
600
+ let count = communities.length;
601
+ // If already in range (±30% tolerance with upper cap at maxClusters), accept.
602
+ if (count >= Math.ceil(maxClusters * 0.5) && count <= maxClusters) {
603
+ return communities;
604
+ }
605
+ // Multiplicative search: keep adjusting gamma until we bracket the target.
606
+ let lowGamma = 0.05;
607
+ let highGamma = 50.0;
608
+ let lowCommunities = null;
609
+ let highCommunities = null;
610
+ for (let iter = 0; iter < MAX_TUNING_ITER; iter++) {
611
+ // Run Leiden at current gamma
612
+ communities = leiden(graph, gamma);
613
+ count = communities.length;
614
+ (0, errors_1.logDebug)('Clusterer: resolution tuning', {
615
+ iteration: iter,
616
+ gamma: Math.round(gamma * 1000) / 1000,
617
+ communities: count,
618
+ target: maxClusters,
619
+ });
620
+ if (count >= Math.ceil(maxClusters * 0.5) && count <= maxClusters) {
621
+ // In range — accept.
622
+ return communities;
623
+ }
624
+ if (count > maxClusters) {
625
+ // Too many communities → need lower gamma
626
+ highGamma = gamma;
627
+ highCommunities = communities;
628
+ gamma = Math.max(lowGamma, gamma * 0.65);
629
+ }
630
+ else {
631
+ // Too few communities → need higher gamma
632
+ lowGamma = gamma;
633
+ lowCommunities = communities;
634
+ gamma = Math.min(highGamma, gamma * 1.55);
635
+ }
636
+ // If search range collapsed, exit
637
+ if (highGamma - lowGamma < 0.01)
638
+ break;
639
+ }
640
+ // Prefer the result with count closest to (but not exceeding) maxClusters.
641
+ // If both exceed, use the one with fewer communities.
642
+ if (lowCommunities && highCommunities) {
643
+ const lowCount = lowCommunities.length;
644
+ const highCount = highCommunities.length;
645
+ if (lowCount <= maxClusters && highCount > maxClusters) {
646
+ return lowCommunities;
647
+ }
648
+ if (lowCount > maxClusters && highCount > maxClusters) {
649
+ return lowCount <= highCount ? lowCommunities : highCommunities;
650
+ }
651
+ // Both <= maxClusters: prefer the one closer to target
652
+ const lowDist = maxClusters - lowCount;
653
+ const highDist = maxClusters - highCount;
654
+ return lowDist <= highDist ? lowCommunities : highCommunities;
655
+ }
656
+ return communities;
657
+ }
658
+ // ---------------------------------------------------------------------------
659
+ // Greedy Merge (fallback when too many communities)
660
+ // ---------------------------------------------------------------------------
661
+ /**
662
+ * Greedily merge communities until count ≤ maxClusters, by repeatedly
663
+ * merging the pair with the highest average inter-community similarity.
664
+ */
665
+ function greedyMerge(graph, communities, maxClusters) {
666
+ if (communities.length <= maxClusters)
667
+ return communities;
668
+ let current = communities.map((c) => [...c]);
669
+ while (current.length > maxClusters) {
670
+ let bestPair = null;
671
+ let bestSim = -Infinity;
672
+ for (let i = 0; i < current.length; i++) {
673
+ for (let j = i + 1; j < current.length; j++) {
674
+ let sum = 0;
675
+ let count = 0;
676
+ for (const a of current[i]) {
677
+ for (const b of current[j]) {
678
+ sum += graph[a][b];
679
+ count++;
680
+ }
681
+ }
682
+ const avgSim = count > 0 ? sum / count : 0;
683
+ if (avgSim > bestSim) {
684
+ bestSim = avgSim;
685
+ bestPair = [i, j];
686
+ }
687
+ }
688
+ }
689
+ if (!bestPair)
690
+ break;
691
+ const [a, b] = bestPair;
692
+ current[a] = [...current[a], ...current[b]];
693
+ current.splice(b, 1);
694
+ }
695
+ return current;
696
+ }
697
+ // ---------------------------------------------------------------------------
698
+ // Public API
699
+ // ---------------------------------------------------------------------------
700
+ /**
701
+ * Fallback quality gate: true when a commit has at least one new file that
702
+ * is not a test file. Used when the symbol-level change count is below the
703
+ * solo-cluster threshold — a commit that adds non-test source files carries
704
+ * structural intent even when the extractor could not produce symbols
705
+ * (e.g. ArkTS files without an active extraction context).
706
+ */
707
+ function hasNewNonTestFiles(change) {
708
+ for (const fc of change.fileChanges) {
709
+ if (fc.isNewFile && !(0, query_utils_1.isTestFile)(fc.filePath)) {
710
+ return true;
711
+ }
712
+ }
713
+ return false;
714
+ }
715
+ /**
716
+ * Cluster commits using Leiden community detection on a multi-signal
717
+ * similarity graph, with recursive sub-splitting for large clusters.
718
+ *
719
+ * The pipeline:
720
+ * 1. Build a weighted similarity graph from AST symbol, file-path,
721
+ * commit-message TF-IDF, ticket-reference, and temporal signals.
722
+ * 2. Run Leiden community detection with auto-tuned resolution γ.
723
+ * 3. Greedy-merge if still too many communities.
724
+ * 4. Recursively split large clusters (≥ 4 commits) by running
725
+ * Leiden on their sub-graphs.
726
+ *
727
+ * Replaces the previous Louvain (no refinement) and connected-components
728
+ * (single-linkage chaining) approaches.
729
+ *
730
+ * @param changes - AST change data for each commit.
731
+ * @param threshold - Minimum similarity (0-1) for considering an edge.
732
+ * @param maxClusters - Desired maximum number of top-level clusters.
733
+ * The resolution parameter γ is auto-tuned to approach
734
+ * this target. Recursive splitting may produce more
735
+ * clusters at finer granularities.
736
+ * @returns Clustered result with stats.
737
+ */
738
+ function clusterCommits(changes, threshold, maxClusters) {
739
+ const n = changes.length;
740
+ // Edge cases: 0 or 1 commit — avoid Leiden overhead.
741
+ if (n === 0) {
742
+ return {
743
+ clusters: [],
744
+ unclustered: [],
745
+ stats: { totalCommits: 0, clusteredCommits: 0, clusterCount: 0 },
746
+ };
747
+ }
748
+ if (n === 1) {
749
+ const c = changes[0];
750
+ const totalSymbols = countChangedSymbols(c);
751
+ if (totalSymbols < MIN_SYMBOLS_FOR_SOLO_CLUSTER && !hasNewNonTestFiles(c)) {
752
+ (0, errors_1.logDebug)('Clusterer: single commit below quality threshold', {
753
+ hash: c.commitHash.slice(0, 7),
754
+ changedSymbols: totalSymbols,
755
+ minRequired: MIN_SYMBOLS_FOR_SOLO_CLUSTER,
756
+ });
757
+ return {
758
+ clusters: [],
759
+ unclustered: [c],
760
+ stats: { totalCommits: 1, clusteredCommits: 0, clusterCount: 0 },
761
+ };
762
+ }
763
+ const syms = collectSymbolNames(c);
764
+ const files = collectFilePaths(c);
765
+ return {
766
+ clusters: [
767
+ {
768
+ id: 0,
769
+ commits: [c],
770
+ primaryFiles: files,
771
+ primarySymbols: syms,
772
+ summary: `1 commit: ${c.commitMessage}`,
773
+ timeRange: { start: c.timestamp, end: c.timestamp },
774
+ },
775
+ ],
776
+ unclustered: [],
777
+ stats: { totalCommits: 1, clusteredCommits: 1, clusterCount: 1 },
778
+ };
779
+ }
780
+ // n >= 2: full pipeline from here.
781
+ // --- Pre-compute per-commit feature vectors ---
782
+ const messages = [];
783
+ const allSymbols = [];
784
+ const allFiles = [];
785
+ const allDirs = [];
786
+ const allTickets = [];
787
+ for (const change of changes) {
788
+ messages.push(tokenize(change.commitMessage));
789
+ allSymbols.push(collectSymbolNames(change));
790
+ allFiles.push(collectFilePaths(change));
791
+ allDirs.push(collectDirectoryPrefixes(change));
792
+ allTickets.push(extractTicketRefs(change.commitMessage));
793
+ }
794
+ // --- Build TF-IDF vectors for commit messages ---
795
+ const tfidf = new TfidfVectorizer();
796
+ tfidf.fit(messages);
797
+ const msgTfidfMatrix = messages.map((msg) => tfidf.transform(msg));
798
+ // --- Build weighted similarity graph ---
799
+ const graph = Array.from({ length: n }, () => new Array(n).fill(0));
800
+ for (let i = 0; i < n; i++) {
801
+ for (let j = i + 1; j < n; j++) {
802
+ const sim = computeSimilarity(changes[i], changes[j], allSymbols[i], allSymbols[j], allFiles[i], allFiles[j], allDirs[i], allDirs[j], allTickets[i], allTickets[j], msgTfidfMatrix, i, j);
803
+ if (sim >= threshold) {
804
+ graph[i][j] = sim;
805
+ graph[j][i] = sim;
806
+ }
807
+ }
808
+ }
809
+ // --- Leiden community detection with auto-tuned resolution ---
810
+ let components = autoTuneResolution(graph, maxClusters);
811
+ // --- Greedy merge if still too many communities ---
812
+ if (components.length > maxClusters) {
813
+ (0, errors_1.logDebug)('Clusterer: post-Leiden greedy merge', {
814
+ beforeMerge: components.length,
815
+ maxClusters,
816
+ });
817
+ components = greedyMerge(graph, components, maxClusters);
818
+ }
819
+ // --- Recursive sub-splitting for large clusters ---
820
+ components = recursiveSubSplit(graph, components);
821
+ // --- Sort by size descending for deterministic output ---
822
+ components.sort((a, b) => b.length - a.length);
823
+ // --- Filter: size-1 communities go to unclustered if < MIN_SYMBOLS ---
824
+ const validComponents = [];
825
+ const unclusteredIndices = [];
826
+ for (const comp of components) {
827
+ if (comp.length === 1) {
828
+ const idx = comp[0];
829
+ const c = changes[idx];
830
+ if (countChangedSymbols(c) < MIN_SYMBOLS_FOR_SOLO_CLUSTER && !hasNewNonTestFiles(c)) {
831
+ unclusteredIndices.push(idx);
832
+ continue;
833
+ }
834
+ }
835
+ validComponents.push(comp);
836
+ }
837
+ // --- Build cluster objects ---
838
+ const clustered = new Set();
839
+ const clusters = [];
840
+ for (let i = 0; i < validComponents.length; i++) {
841
+ const comp = validComponents[i];
842
+ const clusterCommits = comp.map((idx) => {
843
+ clustered.add(idx);
844
+ return changes[idx];
845
+ });
846
+ // Compute primary files (appear in >50% of cluster commits)
847
+ const fileCounts = new Map();
848
+ const symbolCounts = new Map();
849
+ const allTimes = clusterCommits
850
+ .map((c) => c.timestamp)
851
+ .filter((t) => t > 0);
852
+ for (const c of clusterCommits) {
853
+ for (const fp of collectFilePaths(c)) {
854
+ fileCounts.set(fp, (fileCounts.get(fp) || 0) + 1);
855
+ }
856
+ for (const s of collectSymbolNames(c)) {
857
+ symbolCounts.set(s, (symbolCounts.get(s) || 0) + 1);
858
+ }
859
+ }
860
+ const half = clusterCommits.length / 2;
861
+ const primaryFiles = Array.from(fileCounts.entries())
862
+ .filter(([, count]) => count >= half)
863
+ .sort((a, b) => b[1] - a[1])
864
+ .map(([fp]) => fp);
865
+ const primarySymbols = Array.from(symbolCounts.entries())
866
+ .sort((a, b) => b[1] - a[1])
867
+ .slice(0, 10)
868
+ .map(([s]) => s);
869
+ // Build summary
870
+ const fileSummary = primaryFiles.length > 0
871
+ ? `changing ${primaryFiles.slice(0, 3).join(', ')}`
872
+ : '';
873
+ const summary = `${clusterCommits.length} commits ${fileSummary}`.trim();
874
+ clusters.push({
875
+ id: i,
876
+ commits: clusterCommits,
877
+ primaryFiles,
878
+ primarySymbols,
879
+ summary,
880
+ timeRange: {
881
+ start: allTimes.length > 0 ? Math.min(...allTimes) : 0,
882
+ end: allTimes.length > 0 ? Math.max(...allTimes) : 0,
883
+ },
884
+ });
885
+ }
886
+ // --- Unclustered commits ---
887
+ const unclustered = changes.filter((_, i) => !clustered.has(i));
888
+ (0, errors_1.logDebug)('Clusterer result', {
889
+ totalCommits: n,
890
+ clusterCount: clusters.length,
891
+ clusteredCommits: n - unclustered.length,
892
+ unclustered: unclustered.length,
893
+ });
894
+ return {
895
+ clusters,
896
+ unclustered,
897
+ stats: {
898
+ totalCommits: n,
899
+ clusteredCommits: n - unclustered.length,
900
+ clusterCount: clusters.length,
901
+ },
902
+ };
903
+ }
904
+ //# sourceMappingURL=clusterer.js.map