@optave/codegraph 3.5.0 → 3.6.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 (310) hide show
  1. package/README.md +35 -14
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +119 -127
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  6. package/dist/ast-analysis/visitors/ast-store-visitor.js +14 -1
  7. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  8. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  9. package/dist/ast-analysis/visitors/complexity-visitor.js +11 -13
  10. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  11. package/dist/db/connection.d.ts +12 -2
  12. package/dist/db/connection.d.ts.map +1 -1
  13. package/dist/db/connection.js +81 -53
  14. package/dist/db/connection.js.map +1 -1
  15. package/dist/db/index.d.ts +1 -1
  16. package/dist/db/index.d.ts.map +1 -1
  17. package/dist/db/index.js +1 -1
  18. package/dist/db/index.js.map +1 -1
  19. package/dist/db/migrations.d.ts.map +1 -1
  20. package/dist/db/migrations.js +38 -32
  21. package/dist/db/migrations.js.map +1 -1
  22. package/dist/domain/analysis/context.d.ts.map +1 -1
  23. package/dist/domain/analysis/context.js +51 -66
  24. package/dist/domain/analysis/context.js.map +1 -1
  25. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  26. package/dist/domain/analysis/dependencies.js +62 -70
  27. package/dist/domain/analysis/dependencies.js.map +1 -1
  28. package/dist/domain/analysis/diff-impact.d.ts +9 -7
  29. package/dist/domain/analysis/diff-impact.d.ts.map +1 -1
  30. package/dist/domain/analysis/exports.d.ts.map +1 -1
  31. package/dist/domain/analysis/exports.js +29 -33
  32. package/dist/domain/analysis/exports.js.map +1 -1
  33. package/dist/domain/analysis/fn-impact.d.ts +15 -17
  34. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  35. package/dist/domain/analysis/fn-impact.js +35 -65
  36. package/dist/domain/analysis/fn-impact.js.map +1 -1
  37. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  38. package/dist/domain/analysis/module-map.js +91 -6
  39. package/dist/domain/analysis/module-map.js.map +1 -1
  40. package/dist/domain/analysis/query-helpers.d.ts +20 -0
  41. package/dist/domain/analysis/query-helpers.d.ts.map +1 -0
  42. package/dist/domain/analysis/query-helpers.js +27 -0
  43. package/dist/domain/analysis/query-helpers.js.map +1 -0
  44. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  45. package/dist/domain/graph/builder/helpers.js +15 -9
  46. package/dist/domain/graph/builder/helpers.js.map +1 -1
  47. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  48. package/dist/domain/graph/builder/incremental.js +3 -2
  49. package/dist/domain/graph/builder/incremental.js.map +1 -1
  50. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  51. package/dist/domain/graph/builder/pipeline.js +69 -3
  52. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  53. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  54. package/dist/domain/graph/builder/stages/build-edges.js +7 -51
  55. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  56. package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
  57. package/dist/domain/graph/builder/stages/build-structure.js +7 -5
  58. package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
  59. package/dist/domain/graph/builder/stages/collect-files.js +2 -2
  60. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  61. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  62. package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
  63. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  64. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  65. package/dist/domain/graph/builder/stages/finalize.js +124 -105
  66. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  67. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  68. package/dist/domain/graph/builder/stages/insert-nodes.js +28 -15
  69. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  70. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  71. package/dist/domain/graph/builder/stages/resolve-imports.js +3 -2
  72. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  73. package/dist/domain/graph/resolve.d.ts +0 -4
  74. package/dist/domain/graph/resolve.d.ts.map +1 -1
  75. package/dist/domain/graph/resolve.js +32 -48
  76. package/dist/domain/graph/resolve.js.map +1 -1
  77. package/dist/domain/graph/watcher.d.ts.map +1 -1
  78. package/dist/domain/graph/watcher.js +12 -12
  79. package/dist/domain/graph/watcher.js.map +1 -1
  80. package/dist/domain/parser.d.ts +1 -1
  81. package/dist/domain/parser.d.ts.map +1 -1
  82. package/dist/domain/parser.js +164 -101
  83. package/dist/domain/parser.js.map +1 -1
  84. package/dist/domain/search/search/cli-formatter.d.ts.map +1 -1
  85. package/dist/domain/search/search/cli-formatter.js +88 -83
  86. package/dist/domain/search/search/cli-formatter.js.map +1 -1
  87. package/dist/extractors/bash.d.ts +6 -0
  88. package/dist/extractors/bash.d.ts.map +1 -0
  89. package/dist/extractors/bash.js +91 -0
  90. package/dist/extractors/bash.js.map +1 -0
  91. package/dist/extractors/c.d.ts +6 -0
  92. package/dist/extractors/c.d.ts.map +1 -0
  93. package/dist/extractors/c.js +204 -0
  94. package/dist/extractors/c.js.map +1 -0
  95. package/dist/extractors/cpp.d.ts +6 -0
  96. package/dist/extractors/cpp.d.ts.map +1 -0
  97. package/dist/extractors/cpp.js +283 -0
  98. package/dist/extractors/cpp.js.map +1 -0
  99. package/dist/extractors/csharp.d.ts.map +1 -1
  100. package/dist/extractors/csharp.js +42 -54
  101. package/dist/extractors/csharp.js.map +1 -1
  102. package/dist/extractors/go.d.ts.map +1 -1
  103. package/dist/extractors/go.js +126 -130
  104. package/dist/extractors/go.js.map +1 -1
  105. package/dist/extractors/hcl.js +6 -6
  106. package/dist/extractors/hcl.js.map +1 -1
  107. package/dist/extractors/helpers.d.ts +32 -1
  108. package/dist/extractors/helpers.d.ts.map +1 -1
  109. package/dist/extractors/helpers.js +74 -0
  110. package/dist/extractors/helpers.js.map +1 -1
  111. package/dist/extractors/index.d.ts +6 -0
  112. package/dist/extractors/index.d.ts.map +1 -1
  113. package/dist/extractors/index.js +6 -0
  114. package/dist/extractors/index.js.map +1 -1
  115. package/dist/extractors/java.d.ts.map +1 -1
  116. package/dist/extractors/java.js +32 -47
  117. package/dist/extractors/java.js.map +1 -1
  118. package/dist/extractors/javascript.d.ts.map +1 -1
  119. package/dist/extractors/javascript.js +306 -292
  120. package/dist/extractors/javascript.js.map +1 -1
  121. package/dist/extractors/kotlin.d.ts +6 -0
  122. package/dist/extractors/kotlin.d.ts.map +1 -0
  123. package/dist/extractors/kotlin.js +275 -0
  124. package/dist/extractors/kotlin.js.map +1 -0
  125. package/dist/extractors/php.d.ts.map +1 -1
  126. package/dist/extractors/php.js +39 -44
  127. package/dist/extractors/php.js.map +1 -1
  128. package/dist/extractors/python.d.ts.map +1 -1
  129. package/dist/extractors/python.js +75 -93
  130. package/dist/extractors/python.js.map +1 -1
  131. package/dist/extractors/ruby.js +6 -13
  132. package/dist/extractors/ruby.js.map +1 -1
  133. package/dist/extractors/rust.d.ts.map +1 -1
  134. package/dist/extractors/rust.js +58 -83
  135. package/dist/extractors/rust.js.map +1 -1
  136. package/dist/extractors/scala.d.ts +6 -0
  137. package/dist/extractors/scala.d.ts.map +1 -0
  138. package/dist/extractors/scala.js +269 -0
  139. package/dist/extractors/scala.js.map +1 -0
  140. package/dist/extractors/swift.d.ts +6 -0
  141. package/dist/extractors/swift.d.ts.map +1 -0
  142. package/dist/extractors/swift.js +275 -0
  143. package/dist/extractors/swift.js.map +1 -0
  144. package/dist/features/ast.d.ts +2 -0
  145. package/dist/features/ast.d.ts.map +1 -1
  146. package/dist/features/ast.js +9 -24
  147. package/dist/features/ast.js.map +1 -1
  148. package/dist/features/audit.d.ts.map +1 -1
  149. package/dist/features/audit.js +17 -21
  150. package/dist/features/audit.js.map +1 -1
  151. package/dist/features/branch-compare.d.ts.map +1 -1
  152. package/dist/features/branch-compare.js +47 -3
  153. package/dist/features/branch-compare.js.map +1 -1
  154. package/dist/features/cfg.d.ts +7 -1
  155. package/dist/features/cfg.d.ts.map +1 -1
  156. package/dist/features/cfg.js +118 -62
  157. package/dist/features/cfg.js.map +1 -1
  158. package/dist/features/check.d.ts.map +1 -1
  159. package/dist/features/check.js +79 -62
  160. package/dist/features/check.js.map +1 -1
  161. package/dist/features/complexity-query.d.ts.map +1 -1
  162. package/dist/features/complexity-query.js +142 -137
  163. package/dist/features/complexity-query.js.map +1 -1
  164. package/dist/features/complexity.d.ts +7 -1
  165. package/dist/features/complexity.d.ts.map +1 -1
  166. package/dist/features/complexity.js +62 -1
  167. package/dist/features/complexity.js.map +1 -1
  168. package/dist/features/dataflow.d.ts +7 -1
  169. package/dist/features/dataflow.d.ts.map +1 -1
  170. package/dist/features/dataflow.js +356 -188
  171. package/dist/features/dataflow.js.map +1 -1
  172. package/dist/features/graph-enrichment.d.ts.map +1 -1
  173. package/dist/features/graph-enrichment.js +117 -104
  174. package/dist/features/graph-enrichment.js.map +1 -1
  175. package/dist/features/sequence.d.ts.map +1 -1
  176. package/dist/features/sequence.js +25 -4
  177. package/dist/features/sequence.js.map +1 -1
  178. package/dist/features/structure-query.d.ts.map +1 -1
  179. package/dist/features/structure-query.js +29 -4
  180. package/dist/features/structure-query.js.map +1 -1
  181. package/dist/features/structure.d.ts.map +1 -1
  182. package/dist/features/structure.js +35 -15
  183. package/dist/features/structure.js.map +1 -1
  184. package/dist/graph/algorithms/leiden/adapter.d.ts.map +1 -1
  185. package/dist/graph/algorithms/leiden/adapter.js +88 -73
  186. package/dist/graph/algorithms/leiden/adapter.js.map +1 -1
  187. package/dist/graph/algorithms/leiden/index.js +43 -28
  188. package/dist/graph/algorithms/leiden/index.js.map +1 -1
  189. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  190. package/dist/graph/algorithms/leiden/optimiser.js +90 -104
  191. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  192. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  193. package/dist/graph/algorithms/leiden/partition.js +89 -106
  194. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  195. package/dist/graph/model.d.ts +2 -0
  196. package/dist/graph/model.d.ts.map +1 -1
  197. package/dist/graph/model.js +20 -8
  198. package/dist/graph/model.js.map +1 -1
  199. package/dist/infrastructure/config.d.ts +0 -8
  200. package/dist/infrastructure/config.d.ts.map +1 -1
  201. package/dist/infrastructure/config.js +73 -62
  202. package/dist/infrastructure/config.js.map +1 -1
  203. package/dist/infrastructure/registry.d.ts +0 -8
  204. package/dist/infrastructure/registry.d.ts.map +1 -1
  205. package/dist/infrastructure/registry.js +12 -14
  206. package/dist/infrastructure/registry.js.map +1 -1
  207. package/dist/mcp/server.d.ts.map +1 -1
  208. package/dist/mcp/server.js +45 -36
  209. package/dist/mcp/server.js.map +1 -1
  210. package/dist/presentation/audit.d.ts.map +1 -1
  211. package/dist/presentation/audit.js +61 -57
  212. package/dist/presentation/audit.js.map +1 -1
  213. package/dist/presentation/branch-compare.d.ts.map +1 -1
  214. package/dist/presentation/branch-compare.js +56 -38
  215. package/dist/presentation/branch-compare.js.map +1 -1
  216. package/dist/presentation/check.d.ts.map +1 -1
  217. package/dist/presentation/check.js +30 -32
  218. package/dist/presentation/check.js.map +1 -1
  219. package/dist/presentation/colors.d.ts.map +1 -1
  220. package/dist/presentation/colors.js +2 -0
  221. package/dist/presentation/colors.js.map +1 -1
  222. package/dist/presentation/complexity.d.ts.map +1 -1
  223. package/dist/presentation/complexity.js +25 -19
  224. package/dist/presentation/complexity.js.map +1 -1
  225. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  226. package/dist/presentation/queries-cli/exports.js +15 -15
  227. package/dist/presentation/queries-cli/exports.js.map +1 -1
  228. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  229. package/dist/presentation/queries-cli/impact.js +29 -19
  230. package/dist/presentation/queries-cli/impact.js.map +1 -1
  231. package/dist/types.d.ts +182 -7
  232. package/dist/types.d.ts.map +1 -1
  233. package/grammars/tree-sitter-bash.wasm +0 -0
  234. package/grammars/tree-sitter-c.wasm +0 -0
  235. package/grammars/tree-sitter-cpp.wasm +0 -0
  236. package/grammars/tree-sitter-kotlin.wasm +0 -0
  237. package/grammars/tree-sitter-scala.wasm +0 -0
  238. package/grammars/tree-sitter-swift.wasm +0 -0
  239. package/package.json +13 -7
  240. package/src/ast-analysis/engine.ts +147 -138
  241. package/src/ast-analysis/visitors/ast-store-visitor.ts +15 -2
  242. package/src/ast-analysis/visitors/complexity-visitor.ts +11 -11
  243. package/src/db/connection.ts +90 -59
  244. package/src/db/index.ts +1 -0
  245. package/src/db/migrations.ts +36 -32
  246. package/src/domain/analysis/context.ts +73 -75
  247. package/src/domain/analysis/dependencies.ts +78 -68
  248. package/src/domain/analysis/exports.ts +45 -34
  249. package/src/domain/analysis/fn-impact.ts +67 -64
  250. package/src/domain/analysis/module-map.ts +103 -8
  251. package/src/domain/analysis/query-helpers.ts +35 -0
  252. package/src/domain/graph/builder/helpers.ts +12 -6
  253. package/src/domain/graph/builder/incremental.ts +3 -2
  254. package/src/domain/graph/builder/pipeline.ts +71 -3
  255. package/src/domain/graph/builder/stages/build-edges.ts +10 -75
  256. package/src/domain/graph/builder/stages/build-structure.ts +9 -7
  257. package/src/domain/graph/builder/stages/collect-files.ts +2 -2
  258. package/src/domain/graph/builder/stages/detect-changes.ts +7 -2
  259. package/src/domain/graph/builder/stages/finalize.ts +159 -125
  260. package/src/domain/graph/builder/stages/insert-nodes.ts +32 -21
  261. package/src/domain/graph/builder/stages/resolve-imports.ts +3 -2
  262. package/src/domain/graph/resolve.ts +34 -46
  263. package/src/domain/graph/watcher.ts +12 -14
  264. package/src/domain/parser.ts +168 -97
  265. package/src/domain/search/search/cli-formatter.ts +121 -94
  266. package/src/extractors/bash.ts +97 -0
  267. package/src/extractors/c.ts +212 -0
  268. package/src/extractors/cpp.ts +298 -0
  269. package/src/extractors/csharp.ts +53 -56
  270. package/src/extractors/go.ts +152 -134
  271. package/src/extractors/hcl.ts +6 -6
  272. package/src/extractors/helpers.ts +93 -1
  273. package/src/extractors/index.ts +6 -0
  274. package/src/extractors/java.ts +43 -48
  275. package/src/extractors/javascript.ts +328 -281
  276. package/src/extractors/kotlin.ts +293 -0
  277. package/src/extractors/php.ts +46 -40
  278. package/src/extractors/python.ts +81 -104
  279. package/src/extractors/ruby.ts +6 -13
  280. package/src/extractors/rust.ts +65 -85
  281. package/src/extractors/scala.ts +285 -0
  282. package/src/extractors/swift.ts +293 -0
  283. package/src/features/ast.ts +10 -25
  284. package/src/features/audit.ts +24 -20
  285. package/src/features/branch-compare.ts +51 -4
  286. package/src/features/cfg.ts +158 -65
  287. package/src/features/check.ts +90 -74
  288. package/src/features/complexity-query.ts +181 -163
  289. package/src/features/complexity.ts +64 -1
  290. package/src/features/dataflow.ts +462 -217
  291. package/src/features/graph-enrichment.ts +161 -117
  292. package/src/features/sequence.ts +27 -4
  293. package/src/features/structure-query.ts +43 -4
  294. package/src/features/structure.ts +50 -22
  295. package/src/graph/algorithms/leiden/adapter.ts +126 -71
  296. package/src/graph/algorithms/leiden/index.ts +67 -28
  297. package/src/graph/algorithms/leiden/optimiser.ts +114 -105
  298. package/src/graph/algorithms/leiden/partition.ts +131 -98
  299. package/src/graph/model.ts +19 -7
  300. package/src/infrastructure/config.ts +60 -58
  301. package/src/infrastructure/registry.ts +17 -14
  302. package/src/mcp/server.ts +46 -37
  303. package/src/presentation/audit.ts +72 -67
  304. package/src/presentation/branch-compare.ts +54 -50
  305. package/src/presentation/check.ts +34 -34
  306. package/src/presentation/colors.ts +2 -0
  307. package/src/presentation/complexity.ts +39 -33
  308. package/src/presentation/queries-cli/exports.ts +17 -17
  309. package/src/presentation/queries-cli/impact.ts +30 -22
  310. package/src/types.ts +189 -7
@@ -56,6 +56,109 @@ function u8get(a: Uint8Array, i: number): number {
56
56
  return a[i] as number;
57
57
  }
58
58
 
59
+ /**
60
+ * Accumulate per-community node-level totals (size, count, strength) into the
61
+ * provided aggregate arrays. Both `initializeAggregates` and `compactCommunityIds`
62
+ * share this logic — extracting it eliminates the duplication.
63
+ */
64
+ function accumulateNodeAggregates(
65
+ graph: GraphAdapter,
66
+ nodeCommunity: Int32Array,
67
+ n: number,
68
+ totalSize: Float64Array,
69
+ nodeCount: Int32Array,
70
+ internalEdgeWeight: Float64Array,
71
+ totalStrength: Float64Array,
72
+ totalOutStrength: Float64Array,
73
+ totalInStrength: Float64Array,
74
+ ): void {
75
+ for (let i = 0; i < n; i++) {
76
+ const c: number = iget(nodeCommunity, i);
77
+ totalSize[c] = fget(totalSize, c) + fget(graph.size, i);
78
+ nodeCount[c] = iget(nodeCount, c) + 1;
79
+ if (graph.directed) {
80
+ totalOutStrength[c] = fget(totalOutStrength, c) + fget(graph.strengthOut, i);
81
+ totalInStrength[c] = fget(totalInStrength, c) + fget(graph.strengthIn, i);
82
+ } else {
83
+ totalStrength[c] = fget(totalStrength, c) + fget(graph.strengthOut, i);
84
+ }
85
+ if (fget(graph.selfLoop, i) !== 0)
86
+ internalEdgeWeight[c] = fget(internalEdgeWeight, c) + fget(graph.selfLoop, i);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Accumulate intra-community edge weights. For directed graphs, counts all
92
+ * intra-community non-self edges. For undirected, counts each edge once (j > i).
93
+ */
94
+ function accumulateInternalEdgeWeights(
95
+ graph: GraphAdapter,
96
+ nodeCommunity: Int32Array,
97
+ n: number,
98
+ internalEdgeWeight: Float64Array,
99
+ ): void {
100
+ if (graph.directed) {
101
+ for (let i = 0; i < n; i++) {
102
+ const ci: number = iget(nodeCommunity, i);
103
+ const neighbors = graph.outEdges[i]!;
104
+ for (let k = 0; k < neighbors.length; k++) {
105
+ const { to: j, w } = neighbors[k]!;
106
+ if (i === j) continue; // self-loop already counted via graph.selfLoop[i]
107
+ if (ci === iget(nodeCommunity, j))
108
+ internalEdgeWeight[ci] = fget(internalEdgeWeight, ci) + w;
109
+ }
110
+ }
111
+ } else {
112
+ for (let i = 0; i < n; i++) {
113
+ const ci: number = iget(nodeCommunity, i);
114
+ const neighbors = graph.outEdges[i]!;
115
+ for (let k = 0; k < neighbors.length; k++) {
116
+ const { to: j, w } = neighbors[k]!;
117
+ if (j <= i) continue;
118
+ if (ci === iget(nodeCommunity, j))
119
+ internalEdgeWeight[ci] = fget(internalEdgeWeight, ci) + w;
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Sort community IDs according to the compaction options: preserve original
127
+ * order, respect a user-provided label map, or sort by descending size.
128
+ * Returns the sorted list of non-empty community IDs.
129
+ */
130
+ function buildSortedCommunityIds(
131
+ ids: number[],
132
+ opts: CompactOptions,
133
+ communityTotalSize: Float64Array,
134
+ communityNodeCount: Int32Array,
135
+ ): void {
136
+ if (opts.keepOldOrder) {
137
+ ids.sort((a, b) => a - b);
138
+ } else if (opts.preserveMap instanceof Map) {
139
+ const preserveMap = opts.preserveMap;
140
+ ids.sort((a, b) => {
141
+ const pa = preserveMap.get(a);
142
+ const pb = preserveMap.get(b);
143
+ if (pa != null && pb != null && pa !== pb) return pa - pb;
144
+ if (pa != null && pb == null) return -1;
145
+ if (pb != null && pa == null) return 1;
146
+ return (
147
+ fget(communityTotalSize, b) - fget(communityTotalSize, a) ||
148
+ iget(communityNodeCount, b) - iget(communityNodeCount, a) ||
149
+ a - b
150
+ );
151
+ });
152
+ } else {
153
+ ids.sort(
154
+ (a, b) =>
155
+ fget(communityTotalSize, b) - fget(communityTotalSize, a) ||
156
+ iget(communityNodeCount, b) - iget(communityNodeCount, a) ||
157
+ a - b,
158
+ );
159
+ }
160
+ }
161
+
59
162
  export function makePartition(graph: GraphAdapter): Partition {
60
163
  const n: number = graph.n;
61
164
  const nodeCommunity = new Int32Array(n);
@@ -94,44 +197,18 @@ export function makePartition(graph: GraphAdapter): Partition {
94
197
  communityTotalStrength.fill(0);
95
198
  communityTotalOutStrength.fill(0);
96
199
  communityTotalInStrength.fill(0);
97
- for (let i = 0; i < n; i++) {
98
- const c: number = iget(nodeCommunity, i);
99
- communityTotalSize[c] = fget(communityTotalSize, c) + fget(graph.size, i);
100
- communityNodeCount[c] = iget(communityNodeCount, c) + 1;
101
- if (graph.directed) {
102
- communityTotalOutStrength[c] =
103
- fget(communityTotalOutStrength, c) + fget(graph.strengthOut, i);
104
- communityTotalInStrength[c] = fget(communityTotalInStrength, c) + fget(graph.strengthIn, i);
105
- } else {
106
- communityTotalStrength[c] = fget(communityTotalStrength, c) + fget(graph.strengthOut, i);
107
- }
108
- if (fget(graph.selfLoop, i) !== 0)
109
- communityInternalEdgeWeight[c] =
110
- fget(communityInternalEdgeWeight, c) + fget(graph.selfLoop, i);
111
- }
112
- if (graph.directed) {
113
- for (let i = 0; i < n; i++) {
114
- const ci: number = iget(nodeCommunity, i);
115
- const neighbors = graph.outEdges[i]!;
116
- for (let k = 0; k < neighbors.length; k++) {
117
- const { to: j, w } = neighbors[k]!;
118
- if (i === j) continue; // self-loop already counted via graph.selfLoop[i]
119
- if (ci === iget(nodeCommunity, j))
120
- communityInternalEdgeWeight[ci] = fget(communityInternalEdgeWeight, ci) + w;
121
- }
122
- }
123
- } else {
124
- for (let i = 0; i < n; i++) {
125
- const ci: number = iget(nodeCommunity, i);
126
- const neighbors = graph.outEdges[i]!;
127
- for (let k = 0; k < neighbors.length; k++) {
128
- const { to: j, w } = neighbors[k]!;
129
- if (j <= i) continue;
130
- if (ci === iget(nodeCommunity, j))
131
- communityInternalEdgeWeight[ci] = fget(communityInternalEdgeWeight, ci) + w;
132
- }
133
- }
134
- }
200
+ accumulateNodeAggregates(
201
+ graph,
202
+ nodeCommunity,
203
+ n,
204
+ communityTotalSize,
205
+ communityNodeCount,
206
+ communityInternalEdgeWeight,
207
+ communityTotalStrength,
208
+ communityTotalOutStrength,
209
+ communityTotalInStrength,
210
+ );
211
+ accumulateInternalEdgeWeights(graph, nodeCommunity, n, communityInternalEdgeWeight);
135
212
  }
136
213
 
137
214
  function resetScratch(): void {
@@ -323,36 +400,15 @@ export function makePartition(graph: GraphAdapter): Partition {
323
400
  function compactCommunityIds(opts: CompactOptions = {}): void {
324
401
  const ids: number[] = [];
325
402
  for (let c = 0; c < communityCount; c++) if (iget(communityNodeCount, c) > 0) ids.push(c);
326
- if (opts.keepOldOrder) {
327
- ids.sort((a, b) => a - b);
328
- } else if (opts.preserveMap instanceof Map) {
329
- const preserveMap = opts.preserveMap;
330
- ids.sort((a, b) => {
331
- const pa = preserveMap.get(a);
332
- const pb = preserveMap.get(b);
333
- if (pa != null && pb != null && pa !== pb) return pa - pb;
334
- if (pa != null && pb == null) return -1;
335
- if (pb != null && pa == null) return 1;
336
- return (
337
- fget(communityTotalSize, b) - fget(communityTotalSize, a) ||
338
- iget(communityNodeCount, b) - iget(communityNodeCount, a) ||
339
- a - b
340
- );
341
- });
342
- } else {
343
- ids.sort(
344
- (a, b) =>
345
- fget(communityTotalSize, b) - fget(communityTotalSize, a) ||
346
- iget(communityNodeCount, b) - iget(communityNodeCount, a) ||
347
- a - b,
348
- );
349
- }
403
+ buildSortedCommunityIds(ids, opts, communityTotalSize, communityNodeCount);
404
+
350
405
  const newId = new Int32Array(communityCount).fill(-1);
351
406
  ids.forEach((c, i) => {
352
407
  newId[c] = i;
353
408
  });
354
409
  for (let i = 0; i < nodeCommunity.length; i++)
355
410
  nodeCommunity[i] = iget(newId, iget(nodeCommunity, i));
411
+
356
412
  const remappedCount: number = ids.length;
357
413
  const newTotalSize = new Float64Array(remappedCount);
358
414
  const newNodeCount = new Int32Array(remappedCount);
@@ -360,42 +416,19 @@ export function makePartition(graph: GraphAdapter): Partition {
360
416
  const newTotalStrength = new Float64Array(remappedCount);
361
417
  const newTotalOutStrength = new Float64Array(remappedCount);
362
418
  const newTotalInStrength = new Float64Array(remappedCount);
363
- for (let i = 0; i < n; i++) {
364
- const c: number = iget(nodeCommunity, i);
365
- newTotalSize[c] = fget(newTotalSize, c) + fget(graph.size, i);
366
- newNodeCount[c] = iget(newNodeCount, c) + 1;
367
- if (graph.directed) {
368
- newTotalOutStrength[c] = fget(newTotalOutStrength, c) + fget(graph.strengthOut, i);
369
- newTotalInStrength[c] = fget(newTotalInStrength, c) + fget(graph.strengthIn, i);
370
- } else {
371
- newTotalStrength[c] = fget(newTotalStrength, c) + fget(graph.strengthOut, i);
372
- }
373
- if (fget(graph.selfLoop, i) !== 0)
374
- newInternalEdgeWeight[c] = fget(newInternalEdgeWeight, c) + fget(graph.selfLoop, i);
375
- }
376
- if (graph.directed) {
377
- for (let i = 0; i < n; i++) {
378
- const ci: number = iget(nodeCommunity, i);
379
- const list = graph.outEdges[i]!;
380
- for (let k = 0; k < list.length; k++) {
381
- const { to: j, w } = list[k]!;
382
- if (i === j) continue; // self-loop already counted via graph.selfLoop[i]
383
- if (ci === iget(nodeCommunity, j))
384
- newInternalEdgeWeight[ci] = fget(newInternalEdgeWeight, ci) + w;
385
- }
386
- }
387
- } else {
388
- for (let i = 0; i < n; i++) {
389
- const ci: number = iget(nodeCommunity, i);
390
- const list = graph.outEdges[i]!;
391
- for (let k = 0; k < list.length; k++) {
392
- const { to: j, w } = list[k]!;
393
- if (j <= i) continue;
394
- if (ci === iget(nodeCommunity, j))
395
- newInternalEdgeWeight[ci] = fget(newInternalEdgeWeight, ci) + w;
396
- }
397
- }
398
- }
419
+ accumulateNodeAggregates(
420
+ graph,
421
+ nodeCommunity,
422
+ n,
423
+ newTotalSize,
424
+ newNodeCount,
425
+ newInternalEdgeWeight,
426
+ newTotalStrength,
427
+ newTotalOutStrength,
428
+ newTotalInStrength,
429
+ );
430
+ accumulateInternalEdgeWeights(graph, nodeCommunity, n, newInternalEdgeWeight);
431
+
399
432
  communityCount = remappedCount;
400
433
  communityTotalSize = newTotalSize;
401
434
  communityNodeCount = newNodeCount;
@@ -103,15 +103,27 @@ export class CodeGraph {
103
103
  }
104
104
 
105
105
  *edges(): Generator<[string, string, EdgeAttrs]> {
106
- const seen = this._directed ? null : new Set<string>();
106
+ if (this._directed) {
107
+ yield* this._directedEdges();
108
+ } else {
109
+ yield* this._undirectedEdges();
110
+ }
111
+ }
112
+
113
+ private *_directedEdges(): Generator<[string, string, EdgeAttrs]> {
114
+ for (const [src, targets] of this._successors) {
115
+ for (const [tgt, attrs] of targets) yield [src, tgt, attrs];
116
+ }
117
+ }
118
+
119
+ private *_undirectedEdges(): Generator<[string, string, EdgeAttrs]> {
120
+ // \0 is safe as separator — node IDs are file paths/symbols, never contain null bytes
121
+ const seen = new Set<string>();
107
122
  for (const [src, targets] of this._successors) {
108
123
  for (const [tgt, attrs] of targets) {
109
- if (!this._directed) {
110
- // \0 is safe as separator — node IDs are file paths/symbols, never contain null bytes
111
- const key = src < tgt ? `${src}\0${tgt}` : `${tgt}\0${src}`;
112
- if (seen!.has(key)) continue;
113
- seen!.add(key);
114
- }
124
+ const key = src < tgt ? `${src}\0${tgt}` : `${tgt}\0${src}`;
125
+ if (seen.has(key)) continue;
126
+ seen.add(key);
115
127
  yield [src, tgt, attrs];
116
128
  }
117
129
  }
@@ -323,79 +323,70 @@ function resolveWorkspaceEntry(pkgDir: string): string | null {
323
323
  * 2. package.json — `workspaces` field (npm/yarn)
324
324
  * 3. lerna.json — `packages` array
325
325
  */
326
- export function detectWorkspaces(rootDir: string): Map<string, WorkspaceEntry> {
327
- const workspaces = new Map<string, WorkspaceEntry>();
328
- const patterns: string[] = [];
329
-
330
- // 1. pnpm-workspace.yaml
326
+ /** Read pnpm-workspace.yaml and return workspace glob patterns. */
327
+ function readPnpmWorkspacePatterns(rootDir: string): string[] {
331
328
  const pnpmPath = path.join(rootDir, 'pnpm-workspace.yaml');
332
- if (fs.existsSync(pnpmPath)) {
333
- try {
334
- const raw = fs.readFileSync(pnpmPath, 'utf-8');
335
- // Simple YAML parse for `packages:` array — no dependency needed
336
- const packagesMatch = raw.match(/^packages:\s*\n((?:\s+-\s+.+\n?)*)/m);
337
- if (packagesMatch) {
338
- const lines = packagesMatch[1]!.match(/^\s+-\s+['"]?([^'"#\n]+)['"]?\s*$/gm);
339
- if (lines) {
340
- for (const line of lines) {
341
- const m = line.match(/^\s+-\s+['"]?([^'"#\n]+?)['"]?\s*$/);
342
- if (m) patterns.push(m[1]!.trim());
343
- }
344
- }
345
- }
346
- } catch (e) {
347
- debug(`detectWorkspaces: failed to parse pnpm-workspace.yaml: ${toErrorMessage(e)}`);
329
+ if (!fs.existsSync(pnpmPath)) return [];
330
+ try {
331
+ const raw = fs.readFileSync(pnpmPath, 'utf-8');
332
+ const packagesMatch = raw.match(/^packages:\s*\n((?:\s+-\s+.+\n?)*)/m);
333
+ if (!packagesMatch) return [];
334
+ const lines = packagesMatch[1]!.match(/^\s+-\s+['"]?([^'"#\n]+)['"]?\s*$/gm);
335
+ if (!lines) return [];
336
+ const patterns: string[] = [];
337
+ for (const line of lines) {
338
+ const m = line.match(/^\s+-\s+['"]?([^'"#\n]+?)['"]?\s*$/);
339
+ if (m) patterns.push(m[1]!.trim());
348
340
  }
341
+ return patterns;
342
+ } catch (e) {
343
+ debug(`detectWorkspaces: failed to parse pnpm-workspace.yaml: ${toErrorMessage(e)}`);
344
+ return [];
349
345
  }
346
+ }
350
347
 
351
- // 2. package.json workspaces (npm/yarn)
352
- if (patterns.length === 0) {
353
- const rootPkgPath = path.join(rootDir, 'package.json');
354
- if (fs.existsSync(rootPkgPath)) {
355
- try {
356
- const raw = fs.readFileSync(rootPkgPath, 'utf-8');
357
- const pkg = JSON.parse(raw);
358
- const ws = pkg.workspaces;
359
- if (Array.isArray(ws)) {
360
- patterns.push(...ws);
361
- } else if (ws && Array.isArray(ws.packages)) {
362
- // Yarn classic format: { packages: [...], nohoist: [...] }
363
- patterns.push(...ws.packages);
364
- }
365
- } catch (e) {
366
- debug(`detectWorkspaces: failed to parse package.json workspaces: ${toErrorMessage(e)}`);
367
- }
368
- }
348
+ /** Read package.json workspaces field (npm/yarn) and return glob patterns. */
349
+ function readNpmWorkspacePatterns(rootDir: string): string[] {
350
+ const rootPkgPath = path.join(rootDir, 'package.json');
351
+ if (!fs.existsSync(rootPkgPath)) return [];
352
+ try {
353
+ const raw = fs.readFileSync(rootPkgPath, 'utf-8');
354
+ const pkg = JSON.parse(raw);
355
+ const ws = pkg.workspaces;
356
+ if (Array.isArray(ws)) return ws;
357
+ if (ws && Array.isArray(ws.packages)) return ws.packages;
358
+ return [];
359
+ } catch (e) {
360
+ debug(`detectWorkspaces: failed to parse package.json workspaces: ${toErrorMessage(e)}`);
361
+ return [];
369
362
  }
363
+ }
370
364
 
371
- // 3. lerna.json
372
- if (patterns.length === 0) {
373
- const lernaPath = path.join(rootDir, 'lerna.json');
374
- if (fs.existsSync(lernaPath)) {
375
- try {
376
- const raw = fs.readFileSync(lernaPath, 'utf-8');
377
- const lerna = JSON.parse(raw);
378
- if (Array.isArray(lerna.packages)) {
379
- patterns.push(...lerna.packages);
380
- }
381
- } catch (e) {
382
- debug(`detectWorkspaces: failed to parse lerna.json: ${toErrorMessage(e)}`);
383
- }
384
- }
365
+ /** Read lerna.json packages field and return glob patterns. */
366
+ function readLernaPatterns(rootDir: string): string[] {
367
+ const lernaPath = path.join(rootDir, 'lerna.json');
368
+ if (!fs.existsSync(lernaPath)) return [];
369
+ try {
370
+ const raw = fs.readFileSync(lernaPath, 'utf-8');
371
+ const lerna = JSON.parse(raw);
372
+ if (Array.isArray(lerna.packages)) return lerna.packages;
373
+ return [];
374
+ } catch (e) {
375
+ debug(`detectWorkspaces: failed to parse lerna.json: ${toErrorMessage(e)}`);
376
+ return [];
385
377
  }
378
+ }
386
379
 
387
- if (patterns.length === 0) return workspaces;
388
-
389
- // Expand glob patterns and collect packages
380
+ /** Expand workspace patterns into concrete package entries. */
381
+ function expandWorkspacePatterns(patterns: string[], rootDir: string): Map<string, WorkspaceEntry> {
382
+ const workspaces = new Map<string, WorkspaceEntry>();
390
383
  for (const pattern of patterns) {
391
- // Check if pattern is a direct path (no glob) or a glob
392
384
  if (pattern.includes('*')) {
393
385
  for (const dir of expandWorkspaceGlob(pattern, rootDir)) {
394
386
  const name = readPackageName(dir);
395
387
  if (name) workspaces.set(name, { dir, entry: resolveWorkspaceEntry(dir) });
396
388
  }
397
389
  } else {
398
- // Direct path like "packages/core"
399
390
  const dir = path.resolve(rootDir, pattern);
400
391
  if (fs.existsSync(path.join(dir, 'package.json'))) {
401
392
  const name = readPackageName(dir);
@@ -403,6 +394,17 @@ export function detectWorkspaces(rootDir: string): Map<string, WorkspaceEntry> {
403
394
  }
404
395
  }
405
396
  }
397
+ return workspaces;
398
+ }
399
+
400
+ export function detectWorkspaces(rootDir: string): Map<string, WorkspaceEntry> {
401
+ // Try each package manager in priority order — first match wins
402
+ let patterns = readPnpmWorkspacePatterns(rootDir);
403
+ if (patterns.length === 0) patterns = readNpmWorkspacePatterns(rootDir);
404
+ if (patterns.length === 0) patterns = readLernaPatterns(rootDir);
405
+ if (patterns.length === 0) return new Map();
406
+
407
+ const workspaces = expandWorkspacePatterns(patterns, rootDir);
406
408
 
407
409
  if (workspaces.size > 0) {
408
410
  debug(`Detected ${workspaces.size} workspace packages: ${[...workspaces.keys()].join(', ')}`);
@@ -56,6 +56,22 @@ export function saveRegistry(registry: Registry, registryPath: string = REGISTRY
56
56
  * pointing to a different path, auto-suffixes (`api` → `api-2`, `api-3`, …).
57
57
  * Re-registering the same path updates in place. Explicit names always overwrite.
58
58
  */
59
+
60
+ /** Find a unique suffixed name when the base name collides with a different path. */
61
+ function findAvailableName(
62
+ baseName: string,
63
+ absRoot: string,
64
+ repos: Record<string, RegistryEntry>,
65
+ ): string {
66
+ let suffix = 2;
67
+ while (repos[`${baseName}-${suffix}`]) {
68
+ const entry = repos[`${baseName}-${suffix}`]!;
69
+ if (path.resolve(entry.path) === absRoot) return `${baseName}-${suffix}`;
70
+ suffix++;
71
+ }
72
+ return `${baseName}-${suffix}`;
73
+ }
74
+
59
75
  export function registerRepo(
60
76
  rootDir: string,
61
77
  name?: string,
@@ -71,20 +87,7 @@ export function registerRepo(
71
87
  if (!name) {
72
88
  const existing = registry.repos[baseName];
73
89
  if (existing && path.resolve(existing.path) !== absRoot) {
74
- // Basename collision with a different path — find next available suffix
75
- let suffix = 2;
76
- while (registry.repos[`${baseName}-${suffix}`]) {
77
- const entry = registry.repos[`${baseName}-${suffix}`]!;
78
- if (path.resolve(entry.path) === absRoot) {
79
- // Already registered under this suffixed name — update in place
80
- repoName = `${baseName}-${suffix}`;
81
- break;
82
- }
83
- suffix++;
84
- }
85
- if (repoName === baseName) {
86
- repoName = `${baseName}-${suffix}`;
87
- }
90
+ repoName = findAvailableName(baseName, absRoot, registry.repos);
88
91
  }
89
92
  }
90
93
 
package/src/mcp/server.ts CHANGED
@@ -109,6 +109,51 @@ function validateMultiRepoAccess(multiRepo: boolean, name: string, args: { repo?
109
109
  }
110
110
  }
111
111
 
112
+ /**
113
+ * Register process-level shutdown and error handlers once per process.
114
+ * Ensures graceful cleanup when the MCP client disconnects or the transport
115
+ * encounters broken-pipe errors. Uses a globalThis flag to survive
116
+ * vi.resetModules() in tests.
117
+ */
118
+ function registerShutdownHandlers(): void {
119
+ const g = globalThis as Record<string, unknown>;
120
+ if (g.__codegraph_shutdown_installed) return;
121
+ g.__codegraph_shutdown_installed = true;
122
+
123
+ const shutdown = async () => {
124
+ try {
125
+ await _activeServer?.close();
126
+ } catch (_shutdownErr: unknown) {
127
+ // Ignore close errors during shutdown — the transport may already be gone.
128
+ }
129
+ process.exit(0);
130
+ };
131
+ const silentExit = (err: Error & { code?: string }) => {
132
+ // Only suppress broken-pipe errors from closed stdio transport;
133
+ // let real bugs surface with a non-zero exit code.
134
+ if (err.code === 'EPIPE' || err.code === 'ERR_STREAM_DESTROYED') {
135
+ process.exit(0);
136
+ }
137
+ process.stderr.write(`Uncaught exception: ${err.stack ?? err.message}\n`);
138
+ process.exit(1);
139
+ };
140
+ const silentReject = (reason: unknown) => {
141
+ const err = reason instanceof Error ? reason : new Error(String(reason));
142
+ const code = (err as Error & { code?: string }).code;
143
+ if (code === 'EPIPE' || code === 'ERR_STREAM_DESTROYED') {
144
+ process.exit(0);
145
+ }
146
+ process.stderr.write(`Unhandled rejection: ${err.stack ?? err.message}\n`);
147
+ process.exit(1);
148
+ };
149
+
150
+ process.on('SIGINT', shutdown);
151
+ process.on('SIGTERM', shutdown);
152
+ process.on('SIGHUP', shutdown);
153
+ process.on('uncaughtException', silentExit);
154
+ process.on('unhandledRejection', silentReject);
155
+ }
156
+
112
157
  export async function startMCPServer(
113
158
  customDbPath?: string,
114
159
  options: MCPServerOptionsInternal = {},
@@ -180,43 +225,7 @@ export async function startMCPServer(
180
225
  // the latest instance (matters when tests call startMCPServer repeatedly).
181
226
  _activeServer = server;
182
227
 
183
- // Register handlers once per process to avoid listener accumulation.
184
- // Use a process-level flag so it survives vi.resetModules() in tests.
185
- const g = globalThis as Record<string, unknown>;
186
- if (!g.__codegraph_shutdown_installed) {
187
- g.__codegraph_shutdown_installed = true;
188
-
189
- const shutdown = async () => {
190
- try {
191
- await _activeServer?.close();
192
- } catch {}
193
- process.exit(0);
194
- };
195
- const silentExit = (err: Error & { code?: string }) => {
196
- // Only suppress broken-pipe errors from closed stdio transport;
197
- // let real bugs surface with a non-zero exit code.
198
- if (err.code === 'EPIPE' || err.code === 'ERR_STREAM_DESTROYED') {
199
- process.exit(0);
200
- }
201
- process.stderr.write(`Uncaught exception: ${err.stack ?? err.message}\n`);
202
- process.exit(1);
203
- };
204
- const silentReject = (reason: unknown) => {
205
- const err = reason instanceof Error ? reason : new Error(String(reason));
206
- const code = (err as Error & { code?: string }).code;
207
- if (code === 'EPIPE' || code === 'ERR_STREAM_DESTROYED') {
208
- process.exit(0);
209
- }
210
- process.stderr.write(`Unhandled rejection: ${err.stack ?? err.message}\n`);
211
- process.exit(1);
212
- };
213
-
214
- process.on('SIGINT', shutdown);
215
- process.on('SIGTERM', shutdown);
216
- process.on('SIGHUP', shutdown);
217
- process.on('uncaughtException', silentExit);
218
- process.on('unhandledRejection', silentReject);
219
- }
228
+ registerShutdownHandlers();
220
229
 
221
230
  try {
222
231
  await server.connect(transport);