@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
@@ -33,6 +33,176 @@ interface ComplexityRow {
33
33
  halstead_bugs: number;
34
34
  }
35
35
 
36
+ const isValidThreshold = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);
37
+
38
+ /** Build WHERE clause and params for complexity query filtering. */
39
+ function buildComplexityWhere(opts: {
40
+ noTests: boolean;
41
+ target: string | null;
42
+ fileFilter: string | null;
43
+ kindFilter: string | null;
44
+ }): { where: string; params: unknown[] } {
45
+ let where = "WHERE n.kind IN ('function','method')";
46
+ const params: unknown[] = [];
47
+
48
+ if (opts.noTests) {
49
+ where += ` AND n.file NOT LIKE '%.test.%'
50
+ AND n.file NOT LIKE '%.spec.%'
51
+ AND n.file NOT LIKE '%__test__%'
52
+ AND n.file NOT LIKE '%__tests__%'
53
+ AND n.file NOT LIKE '%.stories.%'`;
54
+ }
55
+ if (opts.target) {
56
+ where += ' AND n.name LIKE ?';
57
+ params.push(`%${opts.target}%`);
58
+ }
59
+ {
60
+ const fc = buildFileConditionSQL(opts.fileFilter as string, 'n.file');
61
+ where += fc.sql;
62
+ params.push(...fc.params);
63
+ }
64
+ if (opts.kindFilter) {
65
+ where += ' AND n.kind = ?';
66
+ params.push(opts.kindFilter);
67
+ }
68
+ return { where, params };
69
+ }
70
+
71
+ /** Build HAVING clause for above-threshold filtering. */
72
+ function buildThresholdHaving(thresholds: any): string {
73
+ const conditions: string[] = [];
74
+ if (isValidThreshold(thresholds.cognitive?.warn)) {
75
+ conditions.push(`fc.cognitive >= ${thresholds.cognitive.warn}`);
76
+ }
77
+ if (isValidThreshold(thresholds.cyclomatic?.warn)) {
78
+ conditions.push(`fc.cyclomatic >= ${thresholds.cyclomatic.warn}`);
79
+ }
80
+ if (isValidThreshold(thresholds.maxNesting?.warn)) {
81
+ conditions.push(`fc.max_nesting >= ${thresholds.maxNesting.warn}`);
82
+ }
83
+ if (isValidThreshold(thresholds.maintainabilityIndex?.warn)) {
84
+ conditions.push(
85
+ `fc.maintainability_index > 0 AND fc.maintainability_index <= ${thresholds.maintainabilityIndex.warn}`,
86
+ );
87
+ }
88
+ return conditions.length > 0 ? `AND (${conditions.join(' OR ')})` : '';
89
+ }
90
+
91
+ /** Map a raw DB row to the public complexity result shape. */
92
+ function mapComplexityRow(r: ComplexityRow, thresholds: any): Record<string, unknown> {
93
+ const exceeds: string[] = [];
94
+ if (
95
+ isValidThreshold(thresholds.cognitive?.warn) &&
96
+ r.cognitive >= (thresholds.cognitive?.warn ?? 0)
97
+ )
98
+ exceeds.push('cognitive');
99
+ if (
100
+ isValidThreshold(thresholds.cyclomatic?.warn) &&
101
+ r.cyclomatic >= (thresholds.cyclomatic?.warn ?? 0)
102
+ )
103
+ exceeds.push('cyclomatic');
104
+ if (
105
+ isValidThreshold(thresholds.maxNesting?.warn) &&
106
+ r.max_nesting >= (thresholds.maxNesting?.warn ?? 0)
107
+ )
108
+ exceeds.push('maxNesting');
109
+ if (
110
+ isValidThreshold(thresholds.maintainabilityIndex?.warn) &&
111
+ r.maintainability_index > 0 &&
112
+ r.maintainability_index <= (thresholds.maintainabilityIndex?.warn ?? 0)
113
+ )
114
+ exceeds.push('maintainabilityIndex');
115
+
116
+ return {
117
+ name: r.name,
118
+ kind: r.kind,
119
+ file: r.file,
120
+ line: r.line,
121
+ endLine: r.end_line || null,
122
+ cognitive: r.cognitive,
123
+ cyclomatic: r.cyclomatic,
124
+ maxNesting: r.max_nesting,
125
+ loc: r.loc || 0,
126
+ sloc: r.sloc || 0,
127
+ maintainabilityIndex: r.maintainability_index || 0,
128
+ halstead: {
129
+ volume: r.halstead_volume || 0,
130
+ difficulty: r.halstead_difficulty || 0,
131
+ effort: r.halstead_effort || 0,
132
+ bugs: r.halstead_bugs || 0,
133
+ },
134
+ exceeds: exceeds.length > 0 ? exceeds : undefined,
135
+ };
136
+ }
137
+
138
+ /** Check whether a row exceeds any threshold (for summary counting). */
139
+ function exceedsAnyThreshold(
140
+ r: { cognitive: number; cyclomatic: number; max_nesting: number; maintainability_index: number },
141
+ thresholds: any,
142
+ ): boolean {
143
+ return (
144
+ (isValidThreshold(thresholds.cognitive?.warn) &&
145
+ r.cognitive >= (thresholds.cognitive?.warn ?? 0)) ||
146
+ (isValidThreshold(thresholds.cyclomatic?.warn) &&
147
+ r.cyclomatic >= (thresholds.cyclomatic?.warn ?? 0)) ||
148
+ (isValidThreshold(thresholds.maxNesting?.warn) &&
149
+ r.max_nesting >= (thresholds.maxNesting?.warn ?? 0)) ||
150
+ (isValidThreshold(thresholds.maintainabilityIndex?.warn) &&
151
+ r.maintainability_index > 0 &&
152
+ r.maintainability_index <= (thresholds.maintainabilityIndex?.warn ?? 0))
153
+ );
154
+ }
155
+
156
+ /** Compute summary statistics across all complexity rows. */
157
+ function computeComplexitySummary(
158
+ db: ReturnType<typeof openReadonlyOrFail>,
159
+ noTests: boolean,
160
+ thresholds: any,
161
+ ): Record<string, unknown> | null {
162
+ try {
163
+ const allRows = db
164
+ .prepare<{
165
+ cognitive: number;
166
+ cyclomatic: number;
167
+ max_nesting: number;
168
+ maintainability_index: number;
169
+ }>(
170
+ `SELECT fc.cognitive, fc.cyclomatic, fc.max_nesting, fc.maintainability_index
171
+ FROM function_complexity fc JOIN nodes n ON fc.node_id = n.id
172
+ WHERE n.kind IN ('function','method')
173
+ ${noTests ? `AND n.file NOT LIKE '%.test.%' AND n.file NOT LIKE '%.spec.%' AND n.file NOT LIKE '%__test__%' AND n.file NOT LIKE '%__tests__%' AND n.file NOT LIKE '%.stories.%'` : ''}`,
174
+ )
175
+ .all();
176
+
177
+ if (allRows.length === 0) return null;
178
+
179
+ const miValues = allRows.map((r) => r.maintainability_index || 0);
180
+ return {
181
+ analyzed: allRows.length,
182
+ avgCognitive: +(allRows.reduce((s, r) => s + r.cognitive, 0) / allRows.length).toFixed(1),
183
+ avgCyclomatic: +(allRows.reduce((s, r) => s + r.cyclomatic, 0) / allRows.length).toFixed(1),
184
+ maxCognitive: Math.max(...allRows.map((r) => r.cognitive)),
185
+ maxCyclomatic: Math.max(...allRows.map((r) => r.cyclomatic)),
186
+ avgMI: +(miValues.reduce((s, v) => s + v, 0) / miValues.length).toFixed(1),
187
+ minMI: +Math.min(...miValues).toFixed(1),
188
+ aboveWarn: allRows.filter((r) => exceedsAnyThreshold(r, thresholds)).length,
189
+ };
190
+ } catch (e: unknown) {
191
+ debug(`complexity summary query failed: ${(e as Error).message}`);
192
+ return null;
193
+ }
194
+ }
195
+
196
+ /** Check if graph has nodes (used when complexity table is missing). */
197
+ function checkHasGraph(db: ReturnType<typeof openReadonlyOrFail>): boolean {
198
+ try {
199
+ return (db.prepare<{ c: number }>('SELECT COUNT(*) as c FROM nodes').get()?.c ?? 0) > 0;
200
+ } catch (e: unknown) {
201
+ debug(`nodes table check failed: ${(e as Error).message}`);
202
+ return false;
203
+ }
204
+ }
205
+
36
206
  export function complexityData(
37
207
  customDbPath?: string,
38
208
  opts: {
@@ -52,11 +222,7 @@ export function complexityData(
52
222
  const sort = opts.sort || 'cognitive';
53
223
  const noTests = opts.noTests || false;
54
224
  const aboveThreshold = opts.aboveThreshold || false;
55
- const target = opts.target || null;
56
- const fileFilter = opts.file || null;
57
- const kindFilter = opts.kind || null;
58
225
 
59
- // Load thresholds from config
60
226
  const config = opts.config || loadConfig(process.cwd());
61
227
  const thresholds: any = config.manifesto?.rules || {
62
228
  cognitive: { warn: 15, fail: null },
@@ -65,55 +231,14 @@ export function complexityData(
65
231
  maintainabilityIndex: { warn: 20, fail: null },
66
232
  };
67
233
 
68
- // Build query
69
- let where = "WHERE n.kind IN ('function','method')";
70
- const params: unknown[] = [];
71
-
72
- if (noTests) {
73
- where += ` AND n.file NOT LIKE '%.test.%'
74
- AND n.file NOT LIKE '%.spec.%'
75
- AND n.file NOT LIKE '%__test__%'
76
- AND n.file NOT LIKE '%__tests__%'
77
- AND n.file NOT LIKE '%.stories.%'`;
78
- }
79
- if (target) {
80
- where += ' AND n.name LIKE ?';
81
- params.push(`%${target}%`);
82
- }
83
- {
84
- const fc = buildFileConditionSQL(fileFilter as string, 'n.file');
85
- where += fc.sql;
86
- params.push(...fc.params);
87
- }
88
- if (kindFilter) {
89
- where += ' AND n.kind = ?';
90
- params.push(kindFilter);
91
- }
92
-
93
- const isValidThreshold = (v: unknown): v is number =>
94
- typeof v === 'number' && Number.isFinite(v);
234
+ const { where, params } = buildComplexityWhere({
235
+ noTests,
236
+ target: opts.target || null,
237
+ fileFilter: opts.file || null,
238
+ kindFilter: opts.kind || null,
239
+ });
95
240
 
96
- let having = '';
97
- if (aboveThreshold) {
98
- const conditions: string[] = [];
99
- if (isValidThreshold(thresholds.cognitive?.warn)) {
100
- conditions.push(`fc.cognitive >= ${thresholds.cognitive.warn}`);
101
- }
102
- if (isValidThreshold(thresholds.cyclomatic?.warn)) {
103
- conditions.push(`fc.cyclomatic >= ${thresholds.cyclomatic.warn}`);
104
- }
105
- if (isValidThreshold(thresholds.maxNesting?.warn)) {
106
- conditions.push(`fc.max_nesting >= ${thresholds.maxNesting.warn}`);
107
- }
108
- if (isValidThreshold(thresholds.maintainabilityIndex?.warn)) {
109
- conditions.push(
110
- `fc.maintainability_index > 0 AND fc.maintainability_index <= ${thresholds.maintainabilityIndex.warn}`,
111
- );
112
- }
113
- if (conditions.length > 0) {
114
- having = `AND (${conditions.join(' OR ')})`;
115
- }
116
- }
241
+ const having = aboveThreshold ? buildThresholdHaving(thresholds) : '';
117
242
 
118
243
  const orderMap: Record<string, string> = {
119
244
  cognitive: 'fc.cognitive DESC',
@@ -143,121 +268,14 @@ export function complexityData(
143
268
  .all(...params);
144
269
  } catch (e: unknown) {
145
270
  debug(`complexity query failed (table may not exist): ${(e as Error).message}`);
146
- // Check if graph has nodes even though complexity table is missing/empty
147
- let hasGraph = false;
148
- try {
149
- hasGraph = (db.prepare<{ c: number }>('SELECT COUNT(*) as c FROM nodes').get()?.c ?? 0) > 0;
150
- } catch (e2: unknown) {
151
- debug(`nodes table check failed: ${(e2 as Error).message}`);
152
- }
153
- return { functions: [], summary: null, thresholds, hasGraph };
271
+ return { functions: [], summary: null, thresholds, hasGraph: checkHasGraph(db) };
154
272
  }
155
273
 
156
- // Post-filter test files if needed (belt-and-suspenders for isTestFile)
157
274
  const filtered = noTests ? rows.filter((r) => !isTestFile(r.file)) : rows;
275
+ const functions = filtered.map((r) => mapComplexityRow(r, thresholds));
158
276
 
159
- const functions = filtered.map((r) => {
160
- const exceeds: string[] = [];
161
- if (
162
- isValidThreshold(thresholds.cognitive?.warn) &&
163
- r.cognitive >= (thresholds.cognitive?.warn ?? 0)
164
- )
165
- exceeds.push('cognitive');
166
- if (
167
- isValidThreshold(thresholds.cyclomatic?.warn) &&
168
- r.cyclomatic >= (thresholds.cyclomatic?.warn ?? 0)
169
- )
170
- exceeds.push('cyclomatic');
171
- if (
172
- isValidThreshold(thresholds.maxNesting?.warn) &&
173
- r.max_nesting >= (thresholds.maxNesting?.warn ?? 0)
174
- )
175
- exceeds.push('maxNesting');
176
- if (
177
- isValidThreshold(thresholds.maintainabilityIndex?.warn) &&
178
- r.maintainability_index > 0 &&
179
- r.maintainability_index <= (thresholds.maintainabilityIndex?.warn ?? 0)
180
- )
181
- exceeds.push('maintainabilityIndex');
182
-
183
- return {
184
- name: r.name,
185
- kind: r.kind,
186
- file: r.file,
187
- line: r.line,
188
- endLine: r.end_line || null,
189
- cognitive: r.cognitive,
190
- cyclomatic: r.cyclomatic,
191
- maxNesting: r.max_nesting,
192
- loc: r.loc || 0,
193
- sloc: r.sloc || 0,
194
- maintainabilityIndex: r.maintainability_index || 0,
195
- halstead: {
196
- volume: r.halstead_volume || 0,
197
- difficulty: r.halstead_difficulty || 0,
198
- effort: r.halstead_effort || 0,
199
- bugs: r.halstead_bugs || 0,
200
- },
201
- exceeds: exceeds.length > 0 ? exceeds : undefined,
202
- };
203
- });
204
-
205
- // Summary stats
206
- let summary: Record<string, unknown> | null = null;
207
- try {
208
- const allRows = db
209
- .prepare<{
210
- cognitive: number;
211
- cyclomatic: number;
212
- max_nesting: number;
213
- maintainability_index: number;
214
- }>(
215
- `SELECT fc.cognitive, fc.cyclomatic, fc.max_nesting, fc.maintainability_index
216
- FROM function_complexity fc JOIN nodes n ON fc.node_id = n.id
217
- WHERE n.kind IN ('function','method')
218
- ${noTests ? `AND n.file NOT LIKE '%.test.%' AND n.file NOT LIKE '%.spec.%' AND n.file NOT LIKE '%__test__%' AND n.file NOT LIKE '%__tests__%' AND n.file NOT LIKE '%.stories.%'` : ''}`,
219
- )
220
- .all();
221
-
222
- if (allRows.length > 0) {
223
- const miValues = allRows.map((r) => r.maintainability_index || 0);
224
- summary = {
225
- analyzed: allRows.length,
226
- avgCognitive: +(allRows.reduce((s, r) => s + r.cognitive, 0) / allRows.length).toFixed(1),
227
- avgCyclomatic: +(allRows.reduce((s, r) => s + r.cyclomatic, 0) / allRows.length).toFixed(
228
- 1,
229
- ),
230
- maxCognitive: Math.max(...allRows.map((r) => r.cognitive)),
231
- maxCyclomatic: Math.max(...allRows.map((r) => r.cyclomatic)),
232
- avgMI: +(miValues.reduce((s, v) => s + v, 0) / miValues.length).toFixed(1),
233
- minMI: +Math.min(...miValues).toFixed(1),
234
- aboveWarn: allRows.filter(
235
- (r) =>
236
- (isValidThreshold(thresholds.cognitive?.warn) &&
237
- r.cognitive >= (thresholds.cognitive?.warn ?? 0)) ||
238
- (isValidThreshold(thresholds.cyclomatic?.warn) &&
239
- r.cyclomatic >= (thresholds.cyclomatic?.warn ?? 0)) ||
240
- (isValidThreshold(thresholds.maxNesting?.warn) &&
241
- r.max_nesting >= (thresholds.maxNesting?.warn ?? 0)) ||
242
- (isValidThreshold(thresholds.maintainabilityIndex?.warn) &&
243
- r.maintainability_index > 0 &&
244
- r.maintainability_index <= (thresholds.maintainabilityIndex?.warn ?? 0)),
245
- ).length,
246
- };
247
- }
248
- } catch (e: unknown) {
249
- debug(`complexity summary query failed: ${(e as Error).message}`);
250
- }
251
-
252
- // When summary is null (no complexity rows), check if graph has nodes
253
- let hasGraph = false;
254
- if (summary === null) {
255
- try {
256
- hasGraph = (db.prepare<{ c: number }>('SELECT COUNT(*) as c FROM nodes').get()?.c ?? 0) > 0;
257
- } catch (e: unknown) {
258
- debug(`nodes table check failed: ${(e as Error).message}`);
259
- }
260
- }
277
+ const summary = computeComplexitySummary(db, noTests, thresholds);
278
+ const hasGraph = summary === null ? checkHasGraph(db) : false;
261
279
 
262
280
  const base = { functions, summary, thresholds, hasGraph };
263
281
  return paginateResult(base, 'functions', { limit: opts.limit, offset: opts.offset });
@@ -502,8 +502,71 @@ export async function buildComplexityMetrics(
502
502
  db: BetterSqlite3Database,
503
503
  fileSymbols: Map<string, FileSymbols>,
504
504
  rootDir: string,
505
- _engineOpts?: unknown,
505
+ engineOpts?: {
506
+ nativeDb?: { bulkInsertComplexity?(rows: Array<Record<string, unknown>>): number };
507
+ suspendJsDb?: () => void;
508
+ resumeJsDb?: () => void;
509
+ },
506
510
  ): Promise<void> {
511
+ // ── Native bulk-insert fast path ──────────────────────────────────────
512
+ const nativeDb = engineOpts?.nativeDb;
513
+ if (nativeDb?.bulkInsertComplexity) {
514
+ const rows: Array<Record<string, unknown>> = [];
515
+ let needsJsFallback = false;
516
+
517
+ for (const [relPath, symbols] of fileSymbols) {
518
+ for (const def of symbols.definitions) {
519
+ if (def.kind !== 'function' && def.kind !== 'method') continue;
520
+ if (!def.line) continue;
521
+ if (!def.complexity) {
522
+ needsJsFallback = true;
523
+ break;
524
+ }
525
+ const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
526
+ if (!nodeId) continue;
527
+ const ch = def.complexity.halstead;
528
+ const cl = def.complexity.loc;
529
+ rows.push({
530
+ nodeId,
531
+ cognitive: def.complexity.cognitive ?? 0,
532
+ cyclomatic: def.complexity.cyclomatic ?? 0,
533
+ maxNesting: def.complexity.maxNesting ?? 0,
534
+ loc: cl ? cl.loc : 0,
535
+ sloc: cl ? cl.sloc : 0,
536
+ commentLines: cl ? cl.commentLines : 0,
537
+ halsteadN1: ch ? ch.n1 : 0,
538
+ halsteadN2: ch ? ch.n2 : 0,
539
+ halsteadBigN1: ch ? ch.bigN1 : 0,
540
+ halsteadBigN2: ch ? ch.bigN2 : 0,
541
+ halsteadVocabulary: ch ? ch.vocabulary : 0,
542
+ halsteadLength: ch ? ch.length : 0,
543
+ halsteadVolume: ch ? ch.volume : 0,
544
+ halsteadDifficulty: ch ? ch.difficulty : 0,
545
+ halsteadEffort: ch ? ch.effort : 0,
546
+ halsteadBugs: ch ? ch.bugs : 0,
547
+ maintainabilityIndex: def.complexity.maintainabilityIndex ?? 0,
548
+ });
549
+ }
550
+ if (needsJsFallback) break;
551
+ }
552
+
553
+ if (!needsJsFallback && rows.length > 0) {
554
+ let inserted: number;
555
+ try {
556
+ engineOpts?.suspendJsDb?.();
557
+ inserted = nativeDb.bulkInsertComplexity(rows);
558
+ } finally {
559
+ engineOpts?.resumeJsDb?.();
560
+ }
561
+ if (inserted === rows.length) {
562
+ info(`Complexity (native bulk): ${inserted} functions analyzed`);
563
+ return;
564
+ }
565
+ debug(`Native bulkInsertComplexity partial: ${inserted}/${rows.length} — falling back to JS`);
566
+ }
567
+ }
568
+
569
+ // ── JS fallback path ─────────────────────────────────────────────────
507
570
  const { parsers, extToLang } = await initWasmParsersIfNeeded(fileSymbols);
508
571
  const { getParser } = await import('../domain/parser.js');
509
572