@optave/codegraph 3.8.0 → 3.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/README.md +9 -8
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +95 -87
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/metrics.d.ts +0 -3
  6. package/dist/ast-analysis/metrics.d.ts.map +1 -1
  7. package/dist/ast-analysis/metrics.js +30 -13
  8. package/dist/ast-analysis/metrics.js.map +1 -1
  9. package/dist/ast-analysis/shared.d.ts.map +1 -1
  10. package/dist/ast-analysis/shared.js +24 -19
  11. package/dist/ast-analysis/shared.js.map +1 -1
  12. package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
  13. package/dist/ast-analysis/visitor-utils.js +55 -39
  14. package/dist/ast-analysis/visitor-utils.js.map +1 -1
  15. package/dist/ast-analysis/visitor.d.ts.map +1 -1
  16. package/dist/ast-analysis/visitor.js +91 -70
  17. package/dist/ast-analysis/visitor.js.map +1 -1
  18. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  19. package/dist/ast-analysis/visitors/ast-store-visitor.js +54 -58
  20. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  21. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  22. package/dist/ast-analysis/visitors/complexity-visitor.js +32 -39
  23. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  24. package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
  25. package/dist/ast-analysis/visitors/dataflow-visitor.js +57 -38
  26. package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
  27. package/dist/cli/commands/watch.d.ts.map +1 -1
  28. package/dist/cli/commands/watch.js +16 -2
  29. package/dist/cli/commands/watch.js.map +1 -1
  30. package/dist/db/connection.d.ts.map +1 -1
  31. package/dist/db/connection.js +29 -26
  32. package/dist/db/connection.js.map +1 -1
  33. package/dist/db/query-builder.d.ts.map +1 -1
  34. package/dist/db/query-builder.js +16 -5
  35. package/dist/db/query-builder.js.map +1 -1
  36. package/dist/db/repository/base.d.ts +10 -0
  37. package/dist/db/repository/base.d.ts.map +1 -1
  38. package/dist/db/repository/base.js +17 -0
  39. package/dist/db/repository/base.js.map +1 -1
  40. package/dist/db/repository/native-repository.d.ts +6 -1
  41. package/dist/db/repository/native-repository.d.ts.map +1 -1
  42. package/dist/db/repository/native-repository.js +77 -1
  43. package/dist/db/repository/native-repository.js.map +1 -1
  44. package/dist/db/repository/nodes.d.ts.map +1 -1
  45. package/dist/db/repository/nodes.js +8 -4
  46. package/dist/db/repository/nodes.js.map +1 -1
  47. package/dist/db/repository/sqlite-repository.d.ts +3 -0
  48. package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
  49. package/dist/db/repository/sqlite-repository.js +26 -0
  50. package/dist/db/repository/sqlite-repository.js.map +1 -1
  51. package/dist/domain/analysis/brief.d.ts.map +1 -1
  52. package/dist/domain/analysis/brief.js +13 -17
  53. package/dist/domain/analysis/brief.js.map +1 -1
  54. package/dist/domain/analysis/context.d.ts.map +1 -1
  55. package/dist/domain/analysis/context.js +14 -11
  56. package/dist/domain/analysis/context.js.map +1 -1
  57. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  58. package/dist/domain/analysis/dependencies.js +53 -52
  59. package/dist/domain/analysis/dependencies.js.map +1 -1
  60. package/dist/domain/analysis/fn-impact.d.ts +2 -7
  61. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  62. package/dist/domain/analysis/fn-impact.js +33 -31
  63. package/dist/domain/analysis/fn-impact.js.map +1 -1
  64. package/dist/domain/analysis/implementations.d.ts.map +1 -1
  65. package/dist/domain/analysis/implementations.js +11 -19
  66. package/dist/domain/analysis/implementations.js.map +1 -1
  67. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  68. package/dist/domain/analysis/module-map.js +55 -76
  69. package/dist/domain/analysis/module-map.js.map +1 -1
  70. package/dist/domain/analysis/query-helpers.d.ts +7 -0
  71. package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
  72. package/dist/domain/analysis/query-helpers.js +15 -1
  73. package/dist/domain/analysis/query-helpers.js.map +1 -1
  74. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  75. package/dist/domain/graph/builder/pipeline.js +255 -105
  76. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  77. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  78. package/dist/domain/graph/builder/stages/build-edges.js +22 -17
  79. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  80. package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
  81. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  82. package/dist/domain/graph/builder/stages/finalize.js +2 -2
  83. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  84. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  85. package/dist/domain/graph/builder/stages/insert-nodes.js +32 -21
  86. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  87. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  88. package/dist/domain/graph/builder/stages/resolve-imports.js +95 -84
  89. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  90. package/dist/domain/graph/cycles.d.ts +6 -0
  91. package/dist/domain/graph/cycles.d.ts.map +1 -1
  92. package/dist/domain/graph/cycles.js +114 -22
  93. package/dist/domain/graph/cycles.js.map +1 -1
  94. package/dist/domain/graph/resolve.js +1 -1
  95. package/dist/domain/graph/resolve.js.map +1 -1
  96. package/dist/domain/graph/watcher.d.ts +2 -0
  97. package/dist/domain/graph/watcher.d.ts.map +1 -1
  98. package/dist/domain/graph/watcher.js +170 -75
  99. package/dist/domain/graph/watcher.js.map +1 -1
  100. package/dist/domain/parser.d.ts +0 -5
  101. package/dist/domain/parser.d.ts.map +1 -1
  102. package/dist/domain/parser.js +13 -28
  103. package/dist/domain/parser.js.map +1 -1
  104. package/dist/domain/search/generator.js +1 -1
  105. package/dist/domain/search/generator.js.map +1 -1
  106. package/dist/domain/search/models.d.ts +4 -3
  107. package/dist/domain/search/models.d.ts.map +1 -1
  108. package/dist/domain/search/models.js +18 -5
  109. package/dist/domain/search/models.js.map +1 -1
  110. package/dist/domain/search/search/hybrid.d.ts.map +1 -1
  111. package/dist/domain/search/search/hybrid.js +29 -18
  112. package/dist/domain/search/search/hybrid.js.map +1 -1
  113. package/dist/extractors/go.js +36 -33
  114. package/dist/extractors/go.js.map +1 -1
  115. package/dist/extractors/helpers.d.ts.map +1 -1
  116. package/dist/extractors/helpers.js +40 -29
  117. package/dist/extractors/helpers.js.map +1 -1
  118. package/dist/extractors/java.js +58 -46
  119. package/dist/extractors/java.js.map +1 -1
  120. package/dist/extractors/javascript.js +46 -45
  121. package/dist/extractors/javascript.js.map +1 -1
  122. package/dist/extractors/kotlin.js +84 -78
  123. package/dist/extractors/kotlin.js.map +1 -1
  124. package/dist/extractors/python.js +29 -24
  125. package/dist/extractors/python.js.map +1 -1
  126. package/dist/extractors/rust.js +41 -32
  127. package/dist/extractors/rust.js.map +1 -1
  128. package/dist/extractors/solidity.js +58 -67
  129. package/dist/extractors/solidity.js.map +1 -1
  130. package/dist/extractors/swift.js +83 -81
  131. package/dist/extractors/swift.js.map +1 -1
  132. package/dist/extractors/zig.js +58 -60
  133. package/dist/extractors/zig.js.map +1 -1
  134. package/dist/features/ast.d.ts +16 -14
  135. package/dist/features/ast.d.ts.map +1 -1
  136. package/dist/features/ast.js +83 -81
  137. package/dist/features/ast.js.map +1 -1
  138. package/dist/features/audit.d.ts.map +1 -1
  139. package/dist/features/audit.js +8 -6
  140. package/dist/features/audit.js.map +1 -1
  141. package/dist/features/branch-compare.d.ts.map +1 -1
  142. package/dist/features/branch-compare.js +69 -72
  143. package/dist/features/branch-compare.js.map +1 -1
  144. package/dist/features/communities.d.ts.map +1 -1
  145. package/dist/features/communities.js +19 -7
  146. package/dist/features/communities.js.map +1 -1
  147. package/dist/features/complexity.d.ts.map +1 -1
  148. package/dist/features/complexity.js +120 -125
  149. package/dist/features/complexity.js.map +1 -1
  150. package/dist/features/dataflow.d.ts.map +1 -1
  151. package/dist/features/dataflow.js +136 -137
  152. package/dist/features/dataflow.js.map +1 -1
  153. package/dist/features/flow.d.ts.map +1 -1
  154. package/dist/features/flow.js +84 -79
  155. package/dist/features/flow.js.map +1 -1
  156. package/dist/features/structure-query.d.ts.map +1 -1
  157. package/dist/features/structure-query.js +69 -65
  158. package/dist/features/structure-query.js.map +1 -1
  159. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  160. package/dist/graph/algorithms/leiden/optimiser.js +70 -55
  161. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  162. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  163. package/dist/graph/algorithms/leiden/partition.js +288 -266
  164. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  165. package/dist/graph/model.d.ts.map +1 -1
  166. package/dist/graph/model.js +5 -1
  167. package/dist/graph/model.js.map +1 -1
  168. package/dist/infrastructure/config.d.ts.map +1 -1
  169. package/dist/infrastructure/config.js +6 -4
  170. package/dist/infrastructure/config.js.map +1 -1
  171. package/dist/infrastructure/suppress.d.ts +25 -0
  172. package/dist/infrastructure/suppress.d.ts.map +1 -0
  173. package/dist/infrastructure/suppress.js +43 -0
  174. package/dist/infrastructure/suppress.js.map +1 -0
  175. package/dist/mcp/server.d.ts.map +1 -1
  176. package/dist/mcp/server.js +29 -24
  177. package/dist/mcp/server.js.map +1 -1
  178. package/dist/presentation/dataflow.d.ts.map +1 -1
  179. package/dist/presentation/dataflow.js +47 -38
  180. package/dist/presentation/dataflow.js.map +1 -1
  181. package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
  182. package/dist/presentation/diff-impact-mermaid.js +60 -51
  183. package/dist/presentation/diff-impact-mermaid.js.map +1 -1
  184. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  185. package/dist/presentation/queries-cli/exports.js +20 -14
  186. package/dist/presentation/queries-cli/exports.js.map +1 -1
  187. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  188. package/dist/presentation/queries-cli/impact.js +15 -13
  189. package/dist/presentation/queries-cli/impact.js.map +1 -1
  190. package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
  191. package/dist/presentation/queries-cli/inspect.js +101 -79
  192. package/dist/presentation/queries-cli/inspect.js.map +1 -1
  193. package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
  194. package/dist/presentation/queries-cli/overview.js +25 -16
  195. package/dist/presentation/queries-cli/overview.js.map +1 -1
  196. package/dist/presentation/queries-cli/path.js +26 -20
  197. package/dist/presentation/queries-cli/path.js.map +1 -1
  198. package/dist/presentation/result-formatter.d.ts +10 -0
  199. package/dist/presentation/result-formatter.d.ts.map +1 -1
  200. package/dist/presentation/result-formatter.js +16 -1
  201. package/dist/presentation/result-formatter.js.map +1 -1
  202. package/dist/presentation/viewer.d.ts.map +1 -1
  203. package/dist/presentation/viewer.js +18 -12
  204. package/dist/presentation/viewer.js.map +1 -1
  205. package/dist/shared/errors.d.ts +5 -0
  206. package/dist/shared/errors.d.ts.map +1 -1
  207. package/dist/shared/errors.js +5 -0
  208. package/dist/shared/errors.js.map +1 -1
  209. package/dist/shared/hierarchy.d.ts +8 -2
  210. package/dist/shared/hierarchy.d.ts.map +1 -1
  211. package/dist/shared/hierarchy.js +42 -1
  212. package/dist/shared/hierarchy.js.map +1 -1
  213. package/dist/shared/normalize.d.ts +6 -1
  214. package/dist/shared/normalize.d.ts.map +1 -1
  215. package/dist/shared/normalize.js +20 -12
  216. package/dist/shared/normalize.js.map +1 -1
  217. package/dist/shared/paginate.d.ts +0 -9
  218. package/dist/shared/paginate.d.ts.map +1 -1
  219. package/dist/shared/paginate.js +0 -15
  220. package/dist/shared/paginate.js.map +1 -1
  221. package/dist/types.d.ts +10 -4
  222. package/dist/types.d.ts.map +1 -1
  223. package/package.json +7 -7
  224. package/src/ast-analysis/engine.ts +126 -105
  225. package/src/ast-analysis/metrics.ts +33 -11
  226. package/src/ast-analysis/shared.ts +33 -24
  227. package/src/ast-analysis/visitor-utils.ts +52 -32
  228. package/src/ast-analysis/visitor.ts +132 -71
  229. package/src/ast-analysis/visitors/ast-store-visitor.ts +53 -50
  230. package/src/ast-analysis/visitors/complexity-visitor.ts +35 -40
  231. package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
  232. package/src/cli/commands/watch.ts +16 -2
  233. package/src/db/connection.ts +29 -28
  234. package/src/db/query-builder.ts +15 -3
  235. package/src/db/repository/base.ts +20 -0
  236. package/src/db/repository/native-repository.ts +79 -1
  237. package/src/db/repository/nodes.ts +13 -8
  238. package/src/db/repository/sqlite-repository.ts +29 -0
  239. package/src/domain/analysis/brief.ts +15 -25
  240. package/src/domain/analysis/context.ts +17 -10
  241. package/src/domain/analysis/dependencies.ts +67 -76
  242. package/src/domain/analysis/fn-impact.ts +36 -43
  243. package/src/domain/analysis/implementations.ts +11 -17
  244. package/src/domain/analysis/module-map.ts +58 -92
  245. package/src/domain/analysis/query-helpers.ts +18 -1
  246. package/src/domain/graph/builder/pipeline.ts +286 -97
  247. package/src/domain/graph/builder/stages/build-edges.ts +22 -18
  248. package/src/domain/graph/builder/stages/detect-changes.ts +2 -2
  249. package/src/domain/graph/builder/stages/finalize.ts +2 -2
  250. package/src/domain/graph/builder/stages/insert-nodes.ts +59 -34
  251. package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
  252. package/src/domain/graph/cycles.ts +110 -23
  253. package/src/domain/graph/resolve.ts +1 -1
  254. package/src/domain/graph/watcher.ts +202 -96
  255. package/src/domain/parser.ts +14 -26
  256. package/src/domain/search/generator.ts +1 -1
  257. package/src/domain/search/models.ts +17 -4
  258. package/src/domain/search/search/hybrid.ts +69 -51
  259. package/src/extractors/go.ts +43 -33
  260. package/src/extractors/helpers.ts +37 -23
  261. package/src/extractors/java.ts +66 -47
  262. package/src/extractors/javascript.ts +45 -46
  263. package/src/extractors/kotlin.ts +84 -77
  264. package/src/extractors/python.ts +31 -25
  265. package/src/extractors/rust.ts +37 -29
  266. package/src/extractors/solidity.ts +57 -61
  267. package/src/extractors/swift.ts +81 -80
  268. package/src/extractors/zig.ts +58 -61
  269. package/src/features/ast.ts +130 -110
  270. package/src/features/audit.ts +8 -6
  271. package/src/features/branch-compare.ts +105 -79
  272. package/src/features/communities.ts +25 -10
  273. package/src/features/complexity.ts +171 -134
  274. package/src/features/dataflow.ts +165 -175
  275. package/src/features/flow.ts +129 -92
  276. package/src/features/structure-query.ts +79 -64
  277. package/src/graph/algorithms/leiden/optimiser.ts +99 -55
  278. package/src/graph/algorithms/leiden/partition.ts +359 -294
  279. package/src/graph/model.ts +6 -1
  280. package/src/infrastructure/config.ts +6 -4
  281. package/src/infrastructure/suppress.ts +47 -0
  282. package/src/mcp/server.ts +53 -37
  283. package/src/presentation/dataflow.ts +50 -44
  284. package/src/presentation/diff-impact-mermaid.ts +104 -62
  285. package/src/presentation/queries-cli/exports.ts +21 -13
  286. package/src/presentation/queries-cli/impact.ts +15 -13
  287. package/src/presentation/queries-cli/inspect.ts +100 -81
  288. package/src/presentation/queries-cli/overview.ts +26 -16
  289. package/src/presentation/queries-cli/path.ts +33 -25
  290. package/src/presentation/result-formatter.ts +19 -1
  291. package/src/presentation/viewer.ts +42 -14
  292. package/src/shared/errors.ts +6 -0
  293. package/src/shared/hierarchy.ts +50 -2
  294. package/src/shared/normalize.ts +31 -12
  295. package/src/shared/paginate.ts +0 -17
  296. package/src/types.ts +24 -4
@@ -68,6 +68,67 @@ interface DirectoryEntry {
68
68
  subdirectories: string[];
69
69
  }
70
70
 
71
+ function buildDirectoryEntry(
72
+ d: DirRow,
73
+ filesStmt: { all(...params: unknown[]): unknown[] },
74
+ subdirsStmt: { all(...params: unknown[]): unknown[] },
75
+ noTests: boolean,
76
+ ): DirectoryEntry {
77
+ let files = filesStmt.all(d.id) as FileMetricRow[];
78
+ if (noTests) files = files.filter((f) => !isTestFile(f.name));
79
+
80
+ const subdirs = subdirsStmt.all(d.id) as { name: string }[];
81
+
82
+ const fileCount = noTests ? files.length : d.file_count || 0;
83
+ return {
84
+ directory: d.name,
85
+ fileCount,
86
+ symbolCount: d.symbol_count || 0,
87
+ fanIn: d.fan_in || 0,
88
+ fanOut: d.fan_out || 0,
89
+ cohesion: d.cohesion,
90
+ density: fileCount > 0 ? (d.symbol_count || 0) / fileCount : 0,
91
+ files: files.map((f) => ({
92
+ file: f.name,
93
+ lineCount: f.line_count || 0,
94
+ symbolCount: f.symbol_count || 0,
95
+ importCount: f.import_count || 0,
96
+ exportCount: f.export_count || 0,
97
+ fanIn: f.fan_in || 0,
98
+ fanOut: f.fan_out || 0,
99
+ })),
100
+ subdirectories: subdirs.map((s) => s.name),
101
+ };
102
+ }
103
+
104
+ function applyFileLimit(
105
+ result: DirectoryEntry[],
106
+ fileLimit: number,
107
+ ): { directories: DirectoryEntry[]; count: number; suppressed: number; warning: string } | null {
108
+ const totalFiles = result.reduce((sum, d) => sum + d.files.length, 0);
109
+ if (totalFiles <= fileLimit) return null;
110
+
111
+ let shown = 0;
112
+ for (const d of result) {
113
+ const remaining = fileLimit - shown;
114
+ if (remaining <= 0) {
115
+ d.files = [];
116
+ } else if (d.files.length > remaining) {
117
+ d.files = d.files.slice(0, remaining);
118
+ shown = fileLimit;
119
+ } else {
120
+ shown += d.files.length;
121
+ }
122
+ }
123
+ const suppressed = totalFiles - fileLimit;
124
+ return {
125
+ directories: result,
126
+ count: result.length,
127
+ suppressed,
128
+ warning: `${suppressed} files omitted (showing ${fileLimit}/${totalFiles}). Use --full to show all files, or narrow with --directory.`,
129
+ };
130
+ }
131
+
71
132
  export function structureData(
72
133
  customDbPath?: string,
73
134
  opts: StructureDataOpts = {},
@@ -115,73 +176,27 @@ export function structureData(
115
176
  dirs.sort(sortFn);
116
177
 
117
178
  // Get file metrics for each directory
118
- const result: DirectoryEntry[] = dirs.map((d) => {
119
- let files = db
120
- .prepare(`
121
- SELECT n.name, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count, nm.fan_in, nm.fan_out
122
- FROM edges e
123
- JOIN nodes n ON e.target_id = n.id
124
- LEFT JOIN node_metrics nm ON n.id = nm.node_id
125
- WHERE e.source_id = ? AND e.kind = 'contains' AND n.kind = 'file'
126
- `)
127
- .all(d.id) as FileMetricRow[];
128
- if (noTests) files = files.filter((f) => !isTestFile(f.name));
129
-
130
- const subdirs = db
131
- .prepare(`
132
- SELECT n.name
133
- FROM edges e
134
- JOIN nodes n ON e.target_id = n.id
135
- WHERE e.source_id = ? AND e.kind = 'contains' AND n.kind = 'directory'
136
- `)
137
- .all(d.id) as { name: string }[];
138
-
139
- const fileCount = noTests ? files.length : d.file_count || 0;
140
- return {
141
- directory: d.name,
142
- fileCount,
143
- symbolCount: d.symbol_count || 0,
144
- fanIn: d.fan_in || 0,
145
- fanOut: d.fan_out || 0,
146
- cohesion: d.cohesion,
147
- density: fileCount > 0 ? (d.symbol_count || 0) / fileCount : 0,
148
- files: files.map((f) => ({
149
- file: f.name,
150
- lineCount: f.line_count || 0,
151
- symbolCount: f.symbol_count || 0,
152
- importCount: f.import_count || 0,
153
- exportCount: f.export_count || 0,
154
- fanIn: f.fan_in || 0,
155
- fanOut: f.fan_out || 0,
156
- })),
157
- subdirectories: subdirs.map((s) => s.name),
158
- };
159
- });
179
+ const filesStmt = db.prepare(`
180
+ SELECT n.name, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count, nm.fan_in, nm.fan_out
181
+ FROM edges e
182
+ JOIN nodes n ON e.target_id = n.id
183
+ LEFT JOIN node_metrics nm ON n.id = nm.node_id
184
+ WHERE e.source_id = ? AND e.kind = 'contains' AND n.kind = 'file'
185
+ `);
186
+ const subdirsStmt = db.prepare(`
187
+ SELECT n.name
188
+ FROM edges e
189
+ JOIN nodes n ON e.target_id = n.id
190
+ WHERE e.source_id = ? AND e.kind = 'contains' AND n.kind = 'directory'
191
+ `);
192
+ const result: DirectoryEntry[] = dirs.map((d) =>
193
+ buildDirectoryEntry(d, filesStmt, subdirsStmt, noTests),
194
+ );
160
195
 
161
196
  // Apply global file limit unless full mode
162
197
  if (!full) {
163
- const totalFiles = result.reduce((sum, d) => sum + d.files.length, 0);
164
- if (totalFiles > fileLimit) {
165
- let shown = 0;
166
- for (const d of result) {
167
- const remaining = fileLimit - shown;
168
- if (remaining <= 0) {
169
- d.files = [];
170
- } else if (d.files.length > remaining) {
171
- d.files = d.files.slice(0, remaining);
172
- shown = fileLimit;
173
- } else {
174
- shown += d.files.length;
175
- }
176
- }
177
- const suppressed = totalFiles - fileLimit;
178
- return {
179
- directories: result,
180
- count: result.length,
181
- suppressed,
182
- warning: `${suppressed} files omitted (showing ${fileLimit}/${totalFiles}). Use --full to show all files, or narrow with --directory.`,
183
- };
184
- }
198
+ const limited = applyFileLimit(result, fileLimit);
199
+ if (limited) return limited;
185
200
  }
186
201
 
187
202
  const base = { directories: result, count: result.length };
@@ -18,6 +18,13 @@ const DEFAULT_MAX_LEVELS: number = 50;
18
18
  const DEFAULT_MAX_LOCAL_PASSES: number = 20;
19
19
  const GAIN_EPSILON: number = 1e-12;
20
20
 
21
+ /** Pre-allocated scratch buffers for refinement candidate collection. */
22
+ interface RefinementScratch {
23
+ candC: Int32Array;
24
+ candGain: Float64Array;
25
+ candWeight: Float64Array;
26
+ }
27
+
21
28
  const CandidateStrategy = {
22
29
  Neighbors: 0,
23
30
  All: 1,
@@ -335,6 +342,79 @@ function buildCoarseGraph(g: GraphAdapter, p: Partition): CodeGraph {
335
342
  return coarse;
336
343
  }
337
344
 
345
+ /**
346
+ * Collect eligible candidate communities for node `v` during refinement.
347
+ * A candidate must: (a) be in the same macro-community, (b) respect the size
348
+ * limit, and (c) produce a positive quality gain above GAIN_EPSILON.
349
+ * Returns the number of collected candidates written into `scratch`.
350
+ */
351
+ function collectRefinementCandidates(
352
+ p: Partition,
353
+ g: GraphAdapter,
354
+ v: number,
355
+ touchedCount: number,
356
+ macroV: number,
357
+ commMacro: Int32Array,
358
+ maxSize: number,
359
+ opts: NormalizedOptions,
360
+ scratch: RefinementScratch,
361
+ ): number {
362
+ let candLen: number = 0;
363
+ for (let t = 0; t < touchedCount; t++) {
364
+ const c: number = p.getCandidateCommunityAt(t);
365
+ if (c === p.nodeCommunity[v]!) continue;
366
+ if (commMacro[c]! !== macroV) continue;
367
+ if (maxSize < Infinity) {
368
+ const nextSize: number = p.getCommunityTotalSize(c) + g.size[v]!;
369
+ if (nextSize > maxSize) continue;
370
+ }
371
+ const gain: number = computeQualityGain(p, v, c, opts);
372
+ if (gain > GAIN_EPSILON) {
373
+ scratch.candC[candLen] = c;
374
+ scratch.candGain[candLen] = gain;
375
+ candLen++;
376
+ }
377
+ }
378
+ return candLen;
379
+ }
380
+
381
+ /**
382
+ * Boltzmann probabilistic selection from collected candidates (Algorithm 3).
383
+ * Returns the chosen community ID, or -1 if the node should stay as singleton.
384
+ *
385
+ * p(v, C) is proportional to exp(deltaH / theta), with the "stay as singleton"
386
+ * option (deltaH = 0) included. For numerical stability, the max gain is
387
+ * subtracted before exponentiation.
388
+ */
389
+ function boltzmannSelectCandidate(
390
+ candLen: number,
391
+ theta: number,
392
+ rng: () => number,
393
+ scratch: RefinementScratch,
394
+ ): number {
395
+ let maxGain: number = 0;
396
+ for (let i = 0; i < candLen; i++) {
397
+ if (scratch.candGain[i]! > maxGain) maxGain = scratch.candGain[i]!;
398
+ }
399
+ // "Stay as singleton" weight: exp((0 - maxGain) / theta)
400
+ const stayWeight: number = Math.exp((0 - maxGain) / theta);
401
+ let totalWeight: number = stayWeight;
402
+ for (let i = 0; i < candLen; i++) {
403
+ scratch.candWeight[i] = Math.exp((scratch.candGain[i]! - maxGain) / theta);
404
+ totalWeight += scratch.candWeight[i]!;
405
+ }
406
+
407
+ const r: number = rng() * totalWeight;
408
+ if (r < stayWeight) return -1; // node stays as singleton
409
+
410
+ let cumulative: number = stayWeight;
411
+ for (let i = 0; i < candLen; i++) {
412
+ cumulative += scratch.candWeight[i]!;
413
+ if (r < cumulative) return scratch.candC[i]!;
414
+ }
415
+ return scratch.candC[candLen - 1]!; // fallback
416
+ }
417
+
338
418
  /**
339
419
  * True Leiden refinement phase (Algorithm 3, Traag et al. 2019).
340
420
  *
@@ -380,10 +460,12 @@ function refineWithinCoarseCommunities(
380
460
  shuffleArrayInPlace(order, rng);
381
461
 
382
462
  // Pre-allocate flat arrays for candidate collection to avoid per-node GC pressure.
383
- // Maximum possible candidates per node is bounded by g.n (community count).
384
- const candC = new Int32Array(g.n);
385
- const candGain = new Float64Array(g.n);
386
- const candWeight = new Float64Array(g.n);
463
+ const scratch: RefinementScratch = {
464
+ candC: new Int32Array(g.n),
465
+ candGain: new Float64Array(g.n),
466
+ candWeight: new Float64Array(g.n),
467
+ };
468
+ const maxSize: number = Number.isFinite(opts.maxCommunitySize) ? opts.maxCommunitySize : Infinity;
387
469
 
388
470
  for (let idx = 0; idx < order.length; idx++) {
389
471
  const v: number = order[idx]!;
@@ -394,59 +476,21 @@ function refineWithinCoarseCommunities(
394
476
 
395
477
  const macroV: number = macro[v]!;
396
478
  const touchedCount: number = p.accumulateNeighborCommunityEdgeWeights(v);
397
- const maxSize: number = Number.isFinite(opts.maxCommunitySize)
398
- ? opts.maxCommunitySize
399
- : Infinity;
400
-
401
- // Collect eligible communities and their quality gains.
402
- let candLen: number = 0;
403
- for (let t = 0; t < touchedCount; t++) {
404
- const c: number = p.getCandidateCommunityAt(t);
405
- if (c === p.nodeCommunity[v]!) continue;
406
- if (commMacro[c]! !== macroV) continue;
407
- if (maxSize < Infinity) {
408
- const nextSize: number = p.getCommunityTotalSize(c) + g.size[v]!;
409
- if (nextSize > maxSize) continue;
410
- }
411
- const gain: number = computeQualityGain(p, v, c, opts);
412
- if (gain > GAIN_EPSILON) {
413
- candC[candLen] = c;
414
- candGain[candLen] = gain;
415
- candLen++;
416
- }
417
- }
418
-
479
+ const candLen: number = collectRefinementCandidates(
480
+ p,
481
+ g,
482
+ v,
483
+ touchedCount,
484
+ macroV,
485
+ commMacro,
486
+ maxSize,
487
+ opts,
488
+ scratch,
489
+ );
419
490
  if (candLen === 0) continue;
420
491
 
421
- // Probabilistic selection: p(v, C) proportional to exp(deltaH / theta),
422
- // with the "stay" option (deltaH = 0) included per Algorithm 3.
423
- // For numerical stability, subtract the max gain before exponentiation.
424
- let maxGain: number = 0;
425
- for (let i = 0; i < candLen; i++) {
426
- if (candGain[i]! > maxGain) maxGain = candGain[i]!;
427
- }
428
- // "Stay as singleton" weight: exp((0 - maxGain) / theta)
429
- const stayWeight: number = Math.exp((0 - maxGain) / theta);
430
- let totalWeight: number = stayWeight;
431
- for (let i = 0; i < candLen; i++) {
432
- candWeight[i] = Math.exp((candGain[i]! - maxGain) / theta);
433
- totalWeight += candWeight[i]!;
434
- }
435
-
436
- const r: number = rng() * totalWeight;
437
- if (r < stayWeight) continue; // node stays as singleton
438
-
439
- let cumulative: number = stayWeight;
440
- let chosenC: number = candC[candLen - 1]!; // fallback
441
- for (let i = 0; i < candLen; i++) {
442
- cumulative += candWeight[i]!;
443
- if (r < cumulative) {
444
- chosenC = candC[i]!;
445
- break;
446
- }
447
- }
448
-
449
- p.moveNodeToCommunity(v, chosenC);
492
+ const chosenC: number = boltzmannSelectCandidate(candLen, theta, rng, scratch);
493
+ if (chosenC >= 0) p.moveNodeToCommunity(v, chosenC);
450
494
  }
451
495
  return p;
452
496
  }