@optave/codegraph 3.4.1 → 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 (349) hide show
  1. package/README.md +50 -28
  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/rules/javascript.d.ts.map +1 -1
  6. package/dist/ast-analysis/rules/javascript.js +1 -0
  7. package/dist/ast-analysis/rules/javascript.js.map +1 -1
  8. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  9. package/dist/ast-analysis/visitors/ast-store-visitor.js +116 -35
  10. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  11. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  12. package/dist/ast-analysis/visitors/complexity-visitor.js +11 -13
  13. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  14. package/dist/db/better-sqlite3.d.ts +3 -0
  15. package/dist/db/better-sqlite3.d.ts.map +1 -0
  16. package/dist/db/better-sqlite3.js +19 -0
  17. package/dist/db/better-sqlite3.js.map +1 -0
  18. package/dist/db/connection.d.ts +25 -4
  19. package/dist/db/connection.d.ts.map +1 -1
  20. package/dist/db/connection.js +125 -23
  21. package/dist/db/connection.js.map +1 -1
  22. package/dist/db/index.d.ts +2 -2
  23. package/dist/db/index.d.ts.map +1 -1
  24. package/dist/db/index.js +1 -1
  25. package/dist/db/index.js.map +1 -1
  26. package/dist/db/migrations.d.ts.map +1 -1
  27. package/dist/db/migrations.js +40 -32
  28. package/dist/db/migrations.js.map +1 -1
  29. package/dist/db/query-builder.d.ts +5 -5
  30. package/dist/db/query-builder.d.ts.map +1 -1
  31. package/dist/db/query-builder.js +20 -4
  32. package/dist/db/query-builder.js.map +1 -1
  33. package/dist/db/repository/index.d.ts +1 -0
  34. package/dist/db/repository/index.d.ts.map +1 -1
  35. package/dist/db/repository/index.js +1 -0
  36. package/dist/db/repository/index.js.map +1 -1
  37. package/dist/db/repository/native-repository.d.ts +58 -0
  38. package/dist/db/repository/native-repository.d.ts.map +1 -0
  39. package/dist/db/repository/native-repository.js +261 -0
  40. package/dist/db/repository/native-repository.js.map +1 -0
  41. package/dist/db/repository/nodes.d.ts +4 -4
  42. package/dist/db/repository/nodes.d.ts.map +1 -1
  43. package/dist/db/repository/nodes.js +6 -6
  44. package/dist/db/repository/nodes.js.map +1 -1
  45. package/dist/domain/analysis/context.d.ts.map +1 -1
  46. package/dist/domain/analysis/context.js +51 -66
  47. package/dist/domain/analysis/context.js.map +1 -1
  48. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  49. package/dist/domain/analysis/dependencies.js +62 -70
  50. package/dist/domain/analysis/dependencies.js.map +1 -1
  51. package/dist/domain/analysis/diff-impact.d.ts +9 -7
  52. package/dist/domain/analysis/diff-impact.d.ts.map +1 -1
  53. package/dist/domain/analysis/exports.d.ts.map +1 -1
  54. package/dist/domain/analysis/exports.js +29 -33
  55. package/dist/domain/analysis/exports.js.map +1 -1
  56. package/dist/domain/analysis/fn-impact.d.ts +15 -17
  57. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  58. package/dist/domain/analysis/fn-impact.js +35 -65
  59. package/dist/domain/analysis/fn-impact.js.map +1 -1
  60. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  61. package/dist/domain/analysis/module-map.js +91 -6
  62. package/dist/domain/analysis/module-map.js.map +1 -1
  63. package/dist/domain/analysis/query-helpers.d.ts +20 -0
  64. package/dist/domain/analysis/query-helpers.d.ts.map +1 -0
  65. package/dist/domain/analysis/query-helpers.js +27 -0
  66. package/dist/domain/analysis/query-helpers.js.map +1 -0
  67. package/dist/domain/graph/builder/context.d.ts +2 -1
  68. package/dist/domain/graph/builder/context.d.ts.map +1 -1
  69. package/dist/domain/graph/builder/context.js +1 -0
  70. package/dist/domain/graph/builder/context.js.map +1 -1
  71. package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
  72. package/dist/domain/graph/builder/helpers.js +15 -9
  73. package/dist/domain/graph/builder/helpers.js.map +1 -1
  74. package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
  75. package/dist/domain/graph/builder/incremental.js +3 -2
  76. package/dist/domain/graph/builder/incremental.js.map +1 -1
  77. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  78. package/dist/domain/graph/builder/pipeline.js +95 -7
  79. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  80. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  81. package/dist/domain/graph/builder/stages/build-edges.js +101 -57
  82. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  83. package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
  84. package/dist/domain/graph/builder/stages/build-structure.js +33 -3
  85. package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
  86. package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
  87. package/dist/domain/graph/builder/stages/collect-files.js +70 -6
  88. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  89. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  90. package/dist/domain/graph/builder/stages/detect-changes.js +36 -14
  91. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  92. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  93. package/dist/domain/graph/builder/stages/finalize.js +130 -88
  94. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  95. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  96. package/dist/domain/graph/builder/stages/insert-nodes.js +124 -16
  97. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  98. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  99. package/dist/domain/graph/builder/stages/resolve-imports.js +3 -2
  100. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  101. package/dist/domain/graph/resolve.d.ts +0 -4
  102. package/dist/domain/graph/resolve.d.ts.map +1 -1
  103. package/dist/domain/graph/resolve.js +32 -48
  104. package/dist/domain/graph/resolve.js.map +1 -1
  105. package/dist/domain/graph/watcher.d.ts.map +1 -1
  106. package/dist/domain/graph/watcher.js +12 -12
  107. package/dist/domain/graph/watcher.js.map +1 -1
  108. package/dist/domain/parser.d.ts +1 -1
  109. package/dist/domain/parser.d.ts.map +1 -1
  110. package/dist/domain/parser.js +165 -101
  111. package/dist/domain/parser.js.map +1 -1
  112. package/dist/domain/search/search/cli-formatter.d.ts.map +1 -1
  113. package/dist/domain/search/search/cli-formatter.js +88 -83
  114. package/dist/domain/search/search/cli-formatter.js.map +1 -1
  115. package/dist/extractors/bash.d.ts +6 -0
  116. package/dist/extractors/bash.d.ts.map +1 -0
  117. package/dist/extractors/bash.js +91 -0
  118. package/dist/extractors/bash.js.map +1 -0
  119. package/dist/extractors/c.d.ts +6 -0
  120. package/dist/extractors/c.d.ts.map +1 -0
  121. package/dist/extractors/c.js +204 -0
  122. package/dist/extractors/c.js.map +1 -0
  123. package/dist/extractors/cpp.d.ts +6 -0
  124. package/dist/extractors/cpp.d.ts.map +1 -0
  125. package/dist/extractors/cpp.js +283 -0
  126. package/dist/extractors/cpp.js.map +1 -0
  127. package/dist/extractors/csharp.d.ts.map +1 -1
  128. package/dist/extractors/csharp.js +42 -54
  129. package/dist/extractors/csharp.js.map +1 -1
  130. package/dist/extractors/go.d.ts.map +1 -1
  131. package/dist/extractors/go.js +126 -130
  132. package/dist/extractors/go.js.map +1 -1
  133. package/dist/extractors/hcl.js +6 -6
  134. package/dist/extractors/hcl.js.map +1 -1
  135. package/dist/extractors/helpers.d.ts +32 -1
  136. package/dist/extractors/helpers.d.ts.map +1 -1
  137. package/dist/extractors/helpers.js +74 -0
  138. package/dist/extractors/helpers.js.map +1 -1
  139. package/dist/extractors/index.d.ts +6 -0
  140. package/dist/extractors/index.d.ts.map +1 -1
  141. package/dist/extractors/index.js +6 -0
  142. package/dist/extractors/index.js.map +1 -1
  143. package/dist/extractors/java.d.ts.map +1 -1
  144. package/dist/extractors/java.js +32 -47
  145. package/dist/extractors/java.js.map +1 -1
  146. package/dist/extractors/javascript.d.ts.map +1 -1
  147. package/dist/extractors/javascript.js +359 -330
  148. package/dist/extractors/javascript.js.map +1 -1
  149. package/dist/extractors/kotlin.d.ts +6 -0
  150. package/dist/extractors/kotlin.d.ts.map +1 -0
  151. package/dist/extractors/kotlin.js +275 -0
  152. package/dist/extractors/kotlin.js.map +1 -0
  153. package/dist/extractors/php.d.ts.map +1 -1
  154. package/dist/extractors/php.js +39 -44
  155. package/dist/extractors/php.js.map +1 -1
  156. package/dist/extractors/python.d.ts.map +1 -1
  157. package/dist/extractors/python.js +75 -93
  158. package/dist/extractors/python.js.map +1 -1
  159. package/dist/extractors/ruby.js +6 -13
  160. package/dist/extractors/ruby.js.map +1 -1
  161. package/dist/extractors/rust.d.ts.map +1 -1
  162. package/dist/extractors/rust.js +58 -82
  163. package/dist/extractors/rust.js.map +1 -1
  164. package/dist/extractors/scala.d.ts +6 -0
  165. package/dist/extractors/scala.d.ts.map +1 -0
  166. package/dist/extractors/scala.js +269 -0
  167. package/dist/extractors/scala.js.map +1 -0
  168. package/dist/extractors/swift.d.ts +6 -0
  169. package/dist/extractors/swift.d.ts.map +1 -0
  170. package/dist/extractors/swift.js +275 -0
  171. package/dist/extractors/swift.js.map +1 -0
  172. package/dist/features/ast.d.ts +16 -1
  173. package/dist/features/ast.d.ts.map +1 -1
  174. package/dist/features/ast.js +45 -23
  175. package/dist/features/ast.js.map +1 -1
  176. package/dist/features/audit.d.ts.map +1 -1
  177. package/dist/features/audit.js +17 -21
  178. package/dist/features/audit.js.map +1 -1
  179. package/dist/features/branch-compare.d.ts.map +1 -1
  180. package/dist/features/branch-compare.js +50 -4
  181. package/dist/features/branch-compare.js.map +1 -1
  182. package/dist/features/cfg.d.ts +7 -1
  183. package/dist/features/cfg.d.ts.map +1 -1
  184. package/dist/features/cfg.js +118 -62
  185. package/dist/features/cfg.js.map +1 -1
  186. package/dist/features/check.d.ts.map +1 -1
  187. package/dist/features/check.js +79 -62
  188. package/dist/features/check.js.map +1 -1
  189. package/dist/features/complexity-query.d.ts.map +1 -1
  190. package/dist/features/complexity-query.js +142 -137
  191. package/dist/features/complexity-query.js.map +1 -1
  192. package/dist/features/complexity.d.ts +7 -1
  193. package/dist/features/complexity.d.ts.map +1 -1
  194. package/dist/features/complexity.js +62 -1
  195. package/dist/features/complexity.js.map +1 -1
  196. package/dist/features/dataflow.d.ts +7 -1
  197. package/dist/features/dataflow.d.ts.map +1 -1
  198. package/dist/features/dataflow.js +356 -188
  199. package/dist/features/dataflow.js.map +1 -1
  200. package/dist/features/graph-enrichment.d.ts.map +1 -1
  201. package/dist/features/graph-enrichment.js +117 -104
  202. package/dist/features/graph-enrichment.js.map +1 -1
  203. package/dist/features/sequence.d.ts.map +1 -1
  204. package/dist/features/sequence.js +25 -4
  205. package/dist/features/sequence.js.map +1 -1
  206. package/dist/features/snapshot.d.ts.map +1 -1
  207. package/dist/features/snapshot.js +2 -1
  208. package/dist/features/snapshot.js.map +1 -1
  209. package/dist/features/structure-query.d.ts.map +1 -1
  210. package/dist/features/structure-query.js +29 -4
  211. package/dist/features/structure-query.js.map +1 -1
  212. package/dist/features/structure.d.ts.map +1 -1
  213. package/dist/features/structure.js +35 -15
  214. package/dist/features/structure.js.map +1 -1
  215. package/dist/graph/algorithms/leiden/adapter.d.ts.map +1 -1
  216. package/dist/graph/algorithms/leiden/adapter.js +88 -73
  217. package/dist/graph/algorithms/leiden/adapter.js.map +1 -1
  218. package/dist/graph/algorithms/leiden/index.js +43 -28
  219. package/dist/graph/algorithms/leiden/index.js.map +1 -1
  220. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  221. package/dist/graph/algorithms/leiden/optimiser.js +90 -104
  222. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  223. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  224. package/dist/graph/algorithms/leiden/partition.js +89 -106
  225. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  226. package/dist/graph/model.d.ts +2 -0
  227. package/dist/graph/model.d.ts.map +1 -1
  228. package/dist/graph/model.js +20 -8
  229. package/dist/graph/model.js.map +1 -1
  230. package/dist/infrastructure/config.d.ts +0 -8
  231. package/dist/infrastructure/config.d.ts.map +1 -1
  232. package/dist/infrastructure/config.js +73 -62
  233. package/dist/infrastructure/config.js.map +1 -1
  234. package/dist/infrastructure/registry.d.ts +0 -8
  235. package/dist/infrastructure/registry.d.ts.map +1 -1
  236. package/dist/infrastructure/registry.js +12 -14
  237. package/dist/infrastructure/registry.js.map +1 -1
  238. package/dist/mcp/server.d.ts.map +1 -1
  239. package/dist/mcp/server.js +47 -45
  240. package/dist/mcp/server.js.map +1 -1
  241. package/dist/presentation/audit.d.ts.map +1 -1
  242. package/dist/presentation/audit.js +61 -57
  243. package/dist/presentation/audit.js.map +1 -1
  244. package/dist/presentation/branch-compare.d.ts.map +1 -1
  245. package/dist/presentation/branch-compare.js +56 -38
  246. package/dist/presentation/branch-compare.js.map +1 -1
  247. package/dist/presentation/check.d.ts.map +1 -1
  248. package/dist/presentation/check.js +30 -32
  249. package/dist/presentation/check.js.map +1 -1
  250. package/dist/presentation/colors.d.ts.map +1 -1
  251. package/dist/presentation/colors.js +2 -0
  252. package/dist/presentation/colors.js.map +1 -1
  253. package/dist/presentation/complexity.d.ts.map +1 -1
  254. package/dist/presentation/complexity.js +25 -19
  255. package/dist/presentation/complexity.js.map +1 -1
  256. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  257. package/dist/presentation/queries-cli/exports.js +15 -15
  258. package/dist/presentation/queries-cli/exports.js.map +1 -1
  259. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  260. package/dist/presentation/queries-cli/impact.js +29 -19
  261. package/dist/presentation/queries-cli/impact.js.map +1 -1
  262. package/dist/types.d.ts +406 -3
  263. package/dist/types.d.ts.map +1 -1
  264. package/grammars/tree-sitter-bash.wasm +0 -0
  265. package/grammars/tree-sitter-c.wasm +0 -0
  266. package/grammars/tree-sitter-cpp.wasm +0 -0
  267. package/grammars/tree-sitter-kotlin.wasm +0 -0
  268. package/grammars/tree-sitter-scala.wasm +0 -0
  269. package/grammars/tree-sitter-swift.wasm +0 -0
  270. package/package.json +67 -11
  271. package/src/ast-analysis/engine.ts +147 -138
  272. package/src/ast-analysis/rules/javascript.ts +1 -0
  273. package/src/ast-analysis/visitors/ast-store-visitor.ts +116 -34
  274. package/src/ast-analysis/visitors/complexity-visitor.ts +11 -11
  275. package/src/db/better-sqlite3.ts +20 -0
  276. package/src/db/connection.ts +148 -26
  277. package/src/db/index.ts +4 -1
  278. package/src/db/migrations.ts +38 -32
  279. package/src/db/query-builder.ts +30 -5
  280. package/src/db/repository/index.ts +1 -0
  281. package/src/db/repository/native-repository.ts +361 -0
  282. package/src/db/repository/nodes.ts +7 -3
  283. package/src/domain/analysis/context.ts +73 -75
  284. package/src/domain/analysis/dependencies.ts +78 -68
  285. package/src/domain/analysis/exports.ts +45 -34
  286. package/src/domain/analysis/fn-impact.ts +67 -64
  287. package/src/domain/analysis/module-map.ts +103 -8
  288. package/src/domain/analysis/query-helpers.ts +35 -0
  289. package/src/domain/graph/builder/context.ts +2 -0
  290. package/src/domain/graph/builder/helpers.ts +12 -6
  291. package/src/domain/graph/builder/incremental.ts +3 -2
  292. package/src/domain/graph/builder/pipeline.ts +98 -6
  293. package/src/domain/graph/builder/stages/build-edges.ts +116 -83
  294. package/src/domain/graph/builder/stages/build-structure.ts +46 -8
  295. package/src/domain/graph/builder/stages/collect-files.ts +83 -6
  296. package/src/domain/graph/builder/stages/detect-changes.ts +44 -21
  297. package/src/domain/graph/builder/stages/finalize.ts +172 -109
  298. package/src/domain/graph/builder/stages/insert-nodes.ts +147 -17
  299. package/src/domain/graph/builder/stages/resolve-imports.ts +3 -2
  300. package/src/domain/graph/resolve.ts +34 -46
  301. package/src/domain/graph/watcher.ts +12 -14
  302. package/src/domain/parser.ts +169 -97
  303. package/src/domain/search/search/cli-formatter.ts +121 -94
  304. package/src/extractors/bash.ts +97 -0
  305. package/src/extractors/c.ts +212 -0
  306. package/src/extractors/cpp.ts +298 -0
  307. package/src/extractors/csharp.ts +53 -56
  308. package/src/extractors/go.ts +152 -134
  309. package/src/extractors/hcl.ts +6 -6
  310. package/src/extractors/helpers.ts +93 -1
  311. package/src/extractors/index.ts +6 -0
  312. package/src/extractors/java.ts +43 -48
  313. package/src/extractors/javascript.ts +382 -317
  314. package/src/extractors/kotlin.ts +293 -0
  315. package/src/extractors/php.ts +46 -40
  316. package/src/extractors/python.ts +81 -104
  317. package/src/extractors/ruby.ts +6 -13
  318. package/src/extractors/rust.ts +65 -84
  319. package/src/extractors/scala.ts +285 -0
  320. package/src/extractors/swift.ts +293 -0
  321. package/src/features/ast.ts +74 -24
  322. package/src/features/audit.ts +24 -20
  323. package/src/features/branch-compare.ts +54 -5
  324. package/src/features/cfg.ts +158 -65
  325. package/src/features/check.ts +90 -74
  326. package/src/features/complexity-query.ts +181 -163
  327. package/src/features/complexity.ts +64 -1
  328. package/src/features/dataflow.ts +462 -217
  329. package/src/features/graph-enrichment.ts +161 -117
  330. package/src/features/sequence.ts +27 -4
  331. package/src/features/snapshot.ts +2 -1
  332. package/src/features/structure-query.ts +43 -4
  333. package/src/features/structure.ts +50 -22
  334. package/src/graph/algorithms/leiden/adapter.ts +126 -71
  335. package/src/graph/algorithms/leiden/index.ts +67 -28
  336. package/src/graph/algorithms/leiden/optimiser.ts +114 -105
  337. package/src/graph/algorithms/leiden/partition.ts +131 -98
  338. package/src/graph/model.ts +19 -7
  339. package/src/infrastructure/config.ts +60 -58
  340. package/src/infrastructure/registry.ts +17 -14
  341. package/src/mcp/server.ts +48 -47
  342. package/src/presentation/audit.ts +72 -67
  343. package/src/presentation/branch-compare.ts +54 -50
  344. package/src/presentation/check.ts +34 -34
  345. package/src/presentation/colors.ts +2 -0
  346. package/src/presentation/complexity.ts +39 -33
  347. package/src/presentation/queries-cli/exports.ts +17 -17
  348. package/src/presentation/queries-cli/impact.ts +30 -22
  349. package/src/types.ts +458 -3
@@ -0,0 +1,293 @@
1
+ import type {
2
+ Call,
3
+ ExtractorOutput,
4
+ SubDeclaration,
5
+ TreeSitterNode,
6
+ TreeSitterTree,
7
+ } from '../types.js';
8
+ import { extractModifierVisibility, findChild, nodeEndLine } from './helpers.js';
9
+
10
+ /**
11
+ * Extract symbols from Swift files.
12
+ */
13
+ export function extractSwiftSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
14
+ const ctx: ExtractorOutput = {
15
+ definitions: [],
16
+ calls: [],
17
+ imports: [],
18
+ classes: [],
19
+ exports: [],
20
+ typeMap: new Map(),
21
+ };
22
+
23
+ walkSwiftNode(tree.rootNode, ctx);
24
+ return ctx;
25
+ }
26
+
27
+ function walkSwiftNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
28
+ switch (node.type) {
29
+ case 'class_declaration':
30
+ handleSwiftClassDecl(node, ctx);
31
+ break;
32
+ case 'protocol_declaration':
33
+ handleSwiftProtocolDecl(node, ctx);
34
+ break;
35
+ case 'function_declaration':
36
+ handleSwiftFunctionDecl(node, ctx);
37
+ break;
38
+ case 'import_declaration':
39
+ handleSwiftImportDecl(node, ctx);
40
+ break;
41
+ case 'call_expression':
42
+ handleSwiftCallExpression(node, ctx);
43
+ break;
44
+ case 'property_declaration':
45
+ handleSwiftPropertyDecl(node, ctx);
46
+ break;
47
+ }
48
+
49
+ for (let i = 0; i < node.childCount; i++) {
50
+ const child = node.child(i);
51
+ if (child) walkSwiftNode(child, ctx);
52
+ }
53
+ }
54
+
55
+ // ── Walk-path per-node-type handlers ────────────────────────────────────────
56
+
57
+ function hasKeywordChild(node: TreeSitterNode, keyword: string): boolean {
58
+ for (let i = 0; i < node.childCount; i++) {
59
+ const child = node.child(i);
60
+ if (child && child.text === keyword) return true;
61
+ }
62
+ return false;
63
+ }
64
+
65
+ function handleSwiftClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
66
+ const isStruct = hasKeywordChild(node, 'struct');
67
+ const isEnum = hasKeywordChild(node, 'enum');
68
+
69
+ // Name is a type_identifier direct child
70
+ const nameNode = findChild(node, 'type_identifier');
71
+ if (!nameNode) return;
72
+ const name = nameNode.text;
73
+
74
+ const kind = isEnum ? 'enum' : isStruct ? 'struct' : 'class';
75
+
76
+ const children: SubDeclaration[] = [];
77
+
78
+ if (isEnum) {
79
+ // Enum cases: enum_entry > simple_identifier, inside enum_class_body
80
+ const body = findChild(node, 'enum_class_body');
81
+ if (body) {
82
+ for (let i = 0; i < body.childCount; i++) {
83
+ const child = body.child(i);
84
+ if (child && child.type === 'enum_entry') {
85
+ const entryName = findChild(child, 'simple_identifier');
86
+ if (entryName) {
87
+ children.push({
88
+ name: entryName.text,
89
+ kind: 'constant',
90
+ line: child.startPosition.row + 1,
91
+ });
92
+ }
93
+ }
94
+ }
95
+ }
96
+ } else {
97
+ // Extract properties from class_body
98
+ const body = findChild(node, 'class_body');
99
+ if (body) {
100
+ for (let i = 0; i < body.childCount; i++) {
101
+ const child = body.child(i);
102
+ if (child && child.type === 'property_declaration') {
103
+ const pattern = findChild(child, 'pattern');
104
+ if (pattern) {
105
+ const propName = findChild(pattern, 'simple_identifier');
106
+ if (propName) {
107
+ children.push({
108
+ name: propName.text,
109
+ kind: 'property',
110
+ line: child.startPosition.row + 1,
111
+ visibility: extractModifierVisibility(child),
112
+ });
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+ ctx.definitions.push({
121
+ name,
122
+ kind,
123
+ line: node.startPosition.row + 1,
124
+ endLine: nodeEndLine(node),
125
+ children: children.length > 0 ? children : undefined,
126
+ });
127
+
128
+ // Methods inside class_body or enum_class_body
129
+ const body = findChild(node, 'class_body') || findChild(node, 'enum_class_body');
130
+ if (body) {
131
+ for (let i = 0; i < body.childCount; i++) {
132
+ const child = body.child(i);
133
+ if (child && child.type === 'function_declaration') {
134
+ const methName = findChild(child, 'simple_identifier');
135
+ if (methName) {
136
+ ctx.definitions.push({
137
+ name: `${name}.${methName.text}`,
138
+ kind: 'method',
139
+ line: child.startPosition.row + 1,
140
+ endLine: child.endPosition.row + 1,
141
+ visibility: extractModifierVisibility(child),
142
+ });
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ // Inheritance: inheritance_specifier nodes are DIRECT children of class_declaration
149
+ // First specifier is the superclass (extends), rest are protocol conformances (implements)
150
+ let first = true;
151
+ for (let i = 0; i < node.childCount; i++) {
152
+ const child = node.child(i);
153
+ if (!child || child.type !== 'inheritance_specifier') continue;
154
+ // inheritance_specifier > user_type > type_identifier
155
+ const userType = findChild(child, 'user_type');
156
+ if (userType) {
157
+ const typeId = findChild(userType, 'type_identifier');
158
+ if (typeId) {
159
+ if (first) {
160
+ ctx.classes.push({
161
+ name,
162
+ extends: typeId.text,
163
+ line: node.startPosition.row + 1,
164
+ });
165
+ first = false;
166
+ } else {
167
+ ctx.classes.push({
168
+ name,
169
+ implements: typeId.text,
170
+ line: node.startPosition.row + 1,
171
+ });
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+
178
+ function handleSwiftProtocolDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
179
+ const nameNode = findChild(node, 'type_identifier');
180
+ if (!nameNode) return;
181
+ const name = nameNode.text;
182
+
183
+ ctx.definitions.push({
184
+ name,
185
+ kind: 'interface',
186
+ line: node.startPosition.row + 1,
187
+ endLine: nodeEndLine(node),
188
+ });
189
+
190
+ // Methods inside protocol_body or class_body
191
+ const body = findChild(node, 'protocol_body') || findChild(node, 'class_body');
192
+ if (body) {
193
+ for (let i = 0; i < body.childCount; i++) {
194
+ const child = body.child(i);
195
+ if (child && child.type === 'function_declaration') {
196
+ const methName = findChild(child, 'simple_identifier');
197
+ if (methName) {
198
+ ctx.definitions.push({
199
+ name: `${name}.${methName.text}`,
200
+ kind: 'method',
201
+ line: child.startPosition.row + 1,
202
+ endLine: child.endPosition.row + 1,
203
+ });
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+
210
+ function handleSwiftFunctionDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
211
+ // Skip methods already emitted by class/protocol handlers
212
+ if (
213
+ node.parent?.type === 'class_body' ||
214
+ node.parent?.type === 'protocol_body' ||
215
+ node.parent?.type === 'enum_class_body'
216
+ ) {
217
+ if (
218
+ node.parent.parent?.type === 'class_declaration' ||
219
+ node.parent.parent?.type === 'protocol_declaration'
220
+ ) {
221
+ return;
222
+ }
223
+ }
224
+ const nameNode = findChild(node, 'simple_identifier');
225
+ if (!nameNode) return;
226
+ ctx.definitions.push({
227
+ name: nameNode.text,
228
+ kind: 'function',
229
+ line: node.startPosition.row + 1,
230
+ endLine: nodeEndLine(node),
231
+ visibility: extractModifierVisibility(node),
232
+ });
233
+ }
234
+
235
+ function handleSwiftImportDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
236
+ const identNode = findChild(node, 'identifier');
237
+ if (!identNode) return;
238
+ const source = identNode.text;
239
+ ctx.imports.push({
240
+ source,
241
+ names: [source],
242
+ line: node.startPosition.row + 1,
243
+ swiftImport: true,
244
+ });
245
+ }
246
+
247
+ function handleSwiftCallExpression(node: TreeSitterNode, ctx: ExtractorOutput): void {
248
+ const funcNode = node.child(0);
249
+ if (!funcNode) return;
250
+ const call: Call = { name: '', line: node.startPosition.row + 1 };
251
+ if (funcNode.type === 'navigation_expression') {
252
+ // obj.method(...)
253
+ const lastChild = funcNode.child(funcNode.childCount - 1);
254
+ const firstChild = funcNode.child(0);
255
+ if (lastChild && lastChild.type === 'simple_identifier' && firstChild) {
256
+ call.name = lastChild.text;
257
+ call.receiver = firstChild.text;
258
+ }
259
+ } else if (funcNode.type === 'simple_identifier') {
260
+ call.name = funcNode.text;
261
+ } else {
262
+ call.name = funcNode.text;
263
+ }
264
+ if (call.name) ctx.calls.push(call);
265
+ }
266
+
267
+ function handleSwiftPropertyDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
268
+ // Only handle top-level properties (class properties are handled inline)
269
+ if (
270
+ node.parent?.type === 'class_body' ||
271
+ node.parent?.type === 'protocol_body' ||
272
+ node.parent?.type === 'enum_class_body'
273
+ ) {
274
+ return;
275
+ }
276
+ // Skip function-local let/var bindings
277
+ if (node.parent?.type === 'statements' || node.parent?.type === 'function_body') {
278
+ return;
279
+ }
280
+ const pattern = findChild(node, 'pattern');
281
+ if (!pattern) return;
282
+ const nameNode = findChild(pattern, 'simple_identifier');
283
+ if (!nameNode) return;
284
+ // let → constant, var → variable
285
+ const isLet = hasKeywordChild(node, 'let');
286
+ const kind = isLet ? 'constant' : 'variable';
287
+ ctx.definitions.push({
288
+ name: nameNode.text,
289
+ kind,
290
+ line: node.startPosition.row + 1,
291
+ endLine: nodeEndLine(node),
292
+ });
293
+ }
@@ -65,8 +65,81 @@ export async function buildAstNodes(
65
65
  db: BetterSqlite3Database,
66
66
  fileSymbols: Map<string, FileSymbols>,
67
67
  _rootDir: string,
68
- _engineOpts?: unknown,
68
+ engineOpts?: {
69
+ nativeDb?: {
70
+ bulkInsertAstNodes(
71
+ batches: Array<{
72
+ file: string;
73
+ nodes: Array<{
74
+ line: number;
75
+ kind: string;
76
+ name: string;
77
+ text?: string | null;
78
+ receiver?: string | null;
79
+ }>;
80
+ }>,
81
+ ): number;
82
+ };
83
+ suspendJsDb?: () => void;
84
+ resumeJsDb?: () => void;
85
+ },
69
86
  ): Promise<void> {
87
+ // ── Native bulk-insert fast path ──────────────────────────────────────
88
+ // Uses NativeDatabase persistent connection (Phase 6.15+).
89
+ // Standalone napi functions were removed in 6.17.
90
+ const nativeDb = engineOpts?.nativeDb;
91
+ if (nativeDb?.bulkInsertAstNodes) {
92
+ let needsJsFallback = false;
93
+ const batches: Array<{
94
+ file: string;
95
+ nodes: Array<{
96
+ line: number;
97
+ kind: string;
98
+ name: string;
99
+ text?: string | null;
100
+ receiver?: string | null;
101
+ }>;
102
+ }> = [];
103
+
104
+ for (const [relPath, symbols] of fileSymbols) {
105
+ if (Array.isArray(symbols.astNodes)) {
106
+ batches.push({
107
+ file: relPath,
108
+ nodes: symbols.astNodes.map((n) => ({
109
+ line: n.line,
110
+ kind: n.kind,
111
+ name: n.name,
112
+ text: n.text,
113
+ receiver: n.receiver ?? '',
114
+ })),
115
+ });
116
+ } else if (symbols.calls || symbols._tree) {
117
+ needsJsFallback = true;
118
+ break;
119
+ }
120
+ }
121
+
122
+ if (!needsJsFallback) {
123
+ const expectedNodes = batches.reduce((s, b) => s + b.nodes.length, 0);
124
+ let inserted: number;
125
+ try {
126
+ engineOpts?.suspendJsDb?.();
127
+ inserted = nativeDb.bulkInsertAstNodes(batches);
128
+ } finally {
129
+ engineOpts?.resumeJsDb?.();
130
+ }
131
+ if (inserted === expectedNodes) {
132
+ debug(`AST extraction (native bulk): ${inserted} nodes stored`);
133
+ return;
134
+ }
135
+ debug(
136
+ `AST extraction (native bulk): expected ${expectedNodes}, got ${inserted} — falling back to JS`,
137
+ );
138
+ // fall through to JS path
139
+ }
140
+ }
141
+
142
+ // ── JS fallback path ──────────────────────────────────────────────────
70
143
  let insertStmt: ReturnType<BetterSqlite3Database['prepare']>;
71
144
  try {
72
145
  insertStmt = db.prepare(
@@ -93,29 +166,6 @@ export async function buildAstNodes(
93
166
  nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
94
167
  }
95
168
 
96
- // When native astNodes includes call entries, skip separate symbols.calls processing
97
- // to avoid duplication. Fall back to symbols.calls for WASM or older native binaries.
98
- const nativeProvidedAstNodes = Array.isArray(symbols.astNodes);
99
- if (symbols.calls && !nativeProvidedAstNodes) {
100
- for (const call of symbols.calls) {
101
- const parentDef = findParentDef(defs, call.line);
102
- let parentNodeId: number | null = null;
103
- if (parentDef) {
104
- parentNodeId =
105
- nodeIdMap.get(`${parentDef.name}|${parentDef.kind}|${parentDef.line}`) || null;
106
- }
107
- allRows.push({
108
- file: relPath,
109
- line: call.line,
110
- kind: 'call',
111
- name: call.name,
112
- text: call.dynamic ? `[dynamic] ${call.name}` : null,
113
- receiver: call.receiver || null,
114
- parentNodeId,
115
- });
116
- }
117
- }
118
-
119
169
  if (Array.isArray(symbols.astNodes)) {
120
170
  // Native engine provided AST nodes (may be empty for files with no AST content)
121
171
  for (const n of symbols.astNodes) {
@@ -183,27 +183,8 @@ export function auditData(
183
183
  let functions: unknown[];
184
184
  try {
185
185
  if (explained.kind === 'file') {
186
- // File target: explainData returns file-level info with publicApi + internal
187
- // We need to enrich each symbol
188
- functions = [];
189
- for (const fileResult of results) {
190
- const allSymbols = [
191
- ...(fileResult.publicApi || []),
192
- ...(fileResult.internal || []),
193
- ] as FileSymbol[];
194
- if (kind) {
195
- const filtered = allSymbols.filter((s) => s.kind === kind);
196
- for (const sym of filtered) {
197
- functions.push(enrichSymbol(db, sym, fileResult.file, noTests, maxDepth, thresholds));
198
- }
199
- } else {
200
- for (const sym of allSymbols) {
201
- functions.push(enrichSymbol(db, sym, fileResult.file, noTests, maxDepth, thresholds));
202
- }
203
- }
204
- }
186
+ functions = enrichFileResults(db, results, kind, noTests, maxDepth, thresholds);
205
187
  } else {
206
- // Function target: explainData returns per-function results
207
188
  functions = results.map((r: ExplainResult) =>
208
189
  enrichFunction(db, r, noTests, maxDepth, thresholds),
209
190
  );
@@ -232,6 +213,29 @@ interface ExplainResult {
232
213
  relatedTests?: { file: string }[];
233
214
  }
234
215
 
216
+ /** Enrich all symbols from file-target results. */
217
+ function enrichFileResults(
218
+ db: BetterSqlite3Database,
219
+ results: any[],
220
+ kind: string | undefined,
221
+ noTests: boolean,
222
+ maxDepth: number,
223
+ thresholds: Record<string, ThresholdEntry>,
224
+ ): unknown[] {
225
+ const functions: unknown[] = [];
226
+ for (const fileResult of results) {
227
+ let allSymbols = [
228
+ ...(fileResult.publicApi || []),
229
+ ...(fileResult.internal || []),
230
+ ] as FileSymbol[];
231
+ if (kind) allSymbols = allSymbols.filter((s) => s.kind === kind);
232
+ for (const sym of allSymbols) {
233
+ functions.push(enrichSymbol(db, sym, fileResult.file, noTests, maxDepth, thresholds));
234
+ }
235
+ }
236
+ return functions;
237
+ }
238
+
235
239
  function enrichFunction(
236
240
  db: BetterSqlite3Database,
237
241
  r: ExplainResult,
@@ -2,11 +2,13 @@ import { execFileSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
- import Database from 'better-sqlite3';
5
+ import { getDatabase } from '../db/better-sqlite3.js';
6
6
  import { buildGraph } from '../domain/graph/builder.js';
7
7
  import { kindIcon } from '../domain/queries.js';
8
+ import { debug } from '../infrastructure/logger.js';
9
+ import { getNative, isNativeAvailable } from '../infrastructure/native.js';
8
10
  import { isTestFile } from '../infrastructure/test-filter.js';
9
- import type { EngineMode } from '../types.js';
11
+ import type { EngineMode, NativeDatabase } from '../types.js';
10
12
 
11
13
  // ─── Git Helpers ────────────────────────────────────────────────────────
12
14
 
@@ -105,7 +107,20 @@ function loadSymbolsFromDb(
105
107
  changedFiles: string[],
106
108
  noTests: boolean,
107
109
  ): Map<string, SymbolInfo> {
110
+ const Database = getDatabase();
108
111
  const db = new Database(dbPath, { readonly: true });
112
+
113
+ // Try opening a NativeDatabase for batched fan metrics
114
+ let nativeDb: NativeDatabase | undefined;
115
+ if (isNativeAvailable()) {
116
+ try {
117
+ const native = getNative();
118
+ nativeDb = native.NativeDatabase.openReadonly(dbPath);
119
+ } catch (e) {
120
+ debug(`loadSymbolsFromDb: native path failed: ${(e as Error).message}`);
121
+ }
122
+ }
123
+
109
124
  try {
110
125
  const symbols = new Map<string, SymbolInfo>();
111
126
 
@@ -131,6 +146,34 @@ function loadSymbolsFromDb(
131
146
  end_line: number | null;
132
147
  }>;
133
148
 
149
+ // Filter first, then batch fan metrics for all surviving rows
150
+ const filtered = noTests ? rows.filter((r) => !isTestFile(r.file)) : rows;
151
+
152
+ // ── Native fast path: batch all fan-in/fan-out in one napi call ──
153
+ if (nativeDb?.batchFanMetrics && filtered.length > 0) {
154
+ const nodeIds = filtered.map((r) => r.id);
155
+ const metrics = nativeDb.batchFanMetrics(nodeIds);
156
+ const metricsMap = new Map(metrics.map((m) => [m.nodeId, m]));
157
+
158
+ for (const row of filtered) {
159
+ const lineCount = row.end_line ? row.end_line - row.line + 1 : 0;
160
+ const m = metricsMap.get(row.id);
161
+ const key = makeSymbolKey(row.kind, row.file, row.name);
162
+ symbols.set(key, {
163
+ id: row.id,
164
+ name: row.name,
165
+ kind: row.kind,
166
+ file: row.file,
167
+ line: row.line,
168
+ lineCount,
169
+ fanIn: m?.fanIn ?? 0,
170
+ fanOut: m?.fanOut ?? 0,
171
+ });
172
+ }
173
+ return symbols;
174
+ }
175
+
176
+ // ── JS fallback ───────────────────────────────────────────────────
134
177
  const fanInStmt = db.prepare(
135
178
  `SELECT COUNT(*) AS cnt FROM edges WHERE target_id = ? AND kind = 'calls'`,
136
179
  );
@@ -138,9 +181,7 @@ function loadSymbolsFromDb(
138
181
  `SELECT COUNT(*) AS cnt FROM edges WHERE source_id = ? AND kind = 'calls'`,
139
182
  );
140
183
 
141
- for (const row of rows) {
142
- if (noTests && isTestFile(row.file)) continue;
143
-
184
+ for (const row of filtered) {
144
185
  const lineCount = row.end_line ? row.end_line - row.line + 1 : 0;
145
186
  const fanIn = (fanInStmt.get(row.id) as { cnt: number }).cnt;
146
187
  const fanOut = (fanOutStmt.get(row.id) as { cnt: number }).cnt;
@@ -161,6 +202,13 @@ function loadSymbolsFromDb(
161
202
  return symbols;
162
203
  } finally {
163
204
  db.close();
205
+ if (nativeDb) {
206
+ try {
207
+ nativeDb.close();
208
+ } catch {
209
+ /* already closed */
210
+ }
211
+ }
164
212
  }
165
213
  }
166
214
 
@@ -174,6 +222,7 @@ function loadCallersFromDb(
174
222
  ): CallerInfo[] {
175
223
  if (nodeIds.length === 0) return [];
176
224
 
225
+ const Database = getDatabase();
177
226
  const db = new Database(dbPath, { readonly: true });
178
227
  try {
179
228
  const allCallers = new Set<string>();