@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
@@ -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
+ }
@@ -80,6 +80,8 @@ export async function buildAstNodes(
80
80
  }>,
81
81
  ): number;
82
82
  };
83
+ suspendJsDb?: () => void;
84
+ resumeJsDb?: () => void;
83
85
  },
84
86
  ): Promise<void> {
85
87
  // ── Native bulk-insert fast path ──────────────────────────────────────
@@ -108,7 +110,7 @@ export async function buildAstNodes(
108
110
  kind: n.kind,
109
111
  name: n.name,
110
112
  text: n.text,
111
- receiver: n.receiver,
113
+ receiver: n.receiver ?? '',
112
114
  })),
113
115
  });
114
116
  } else if (symbols.calls || symbols._tree) {
@@ -119,7 +121,13 @@ export async function buildAstNodes(
119
121
 
120
122
  if (!needsJsFallback) {
121
123
  const expectedNodes = batches.reduce((s, b) => s + b.nodes.length, 0);
122
- const inserted = nativeDb.bulkInsertAstNodes(batches);
124
+ let inserted: number;
125
+ try {
126
+ engineOpts?.suspendJsDb?.();
127
+ inserted = nativeDb.bulkInsertAstNodes(batches);
128
+ } finally {
129
+ engineOpts?.resumeJsDb?.();
130
+ }
123
131
  if (inserted === expectedNodes) {
124
132
  debug(`AST extraction (native bulk): ${inserted} nodes stored`);
125
133
  return;
@@ -158,29 +166,6 @@ export async function buildAstNodes(
158
166
  nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
159
167
  }
160
168
 
161
- // When native astNodes includes call entries, skip separate symbols.calls processing
162
- // to avoid duplication. Fall back to symbols.calls for WASM or older native binaries.
163
- const nativeProvidedAstNodes = Array.isArray(symbols.astNodes);
164
- if (symbols.calls && !nativeProvidedAstNodes) {
165
- for (const call of symbols.calls) {
166
- const parentDef = findParentDef(defs, call.line);
167
- let parentNodeId: number | null = null;
168
- if (parentDef) {
169
- parentNodeId =
170
- nodeIdMap.get(`${parentDef.name}|${parentDef.kind}|${parentDef.line}`) || null;
171
- }
172
- allRows.push({
173
- file: relPath,
174
- line: call.line,
175
- kind: 'call',
176
- name: call.name,
177
- text: call.dynamic ? `[dynamic] ${call.name}` : null,
178
- receiver: call.receiver || null,
179
- parentNodeId,
180
- });
181
- }
182
- }
183
-
184
169
  if (Array.isArray(symbols.astNodes)) {
185
170
  // Native engine provided AST nodes (may be empty for files with no AST content)
186
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,
@@ -5,8 +5,10 @@ import path from 'node:path';
5
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
 
@@ -107,6 +109,18 @@ function loadSymbolsFromDb(
107
109
  ): Map<string, SymbolInfo> {
108
110
  const Database = getDatabase();
109
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
+
110
124
  try {
111
125
  const symbols = new Map<string, SymbolInfo>();
112
126
 
@@ -132,6 +146,34 @@ function loadSymbolsFromDb(
132
146
  end_line: number | null;
133
147
  }>;
134
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 ───────────────────────────────────────────────────
135
177
  const fanInStmt = db.prepare(
136
178
  `SELECT COUNT(*) AS cnt FROM edges WHERE target_id = ? AND kind = 'calls'`,
137
179
  );
@@ -139,9 +181,7 @@ function loadSymbolsFromDb(
139
181
  `SELECT COUNT(*) AS cnt FROM edges WHERE source_id = ? AND kind = 'calls'`,
140
182
  );
141
183
 
142
- for (const row of rows) {
143
- if (noTests && isTestFile(row.file)) continue;
144
-
184
+ for (const row of filtered) {
145
185
  const lineCount = row.end_line ? row.end_line - row.line + 1 : 0;
146
186
  const fanIn = (fanInStmt.get(row.id) as { cnt: number }).cnt;
147
187
  const fanOut = (fanOutStmt.get(row.id) as { cnt: number }).cnt;
@@ -162,6 +202,13 @@ function loadSymbolsFromDb(
162
202
  return symbols;
163
203
  } finally {
164
204
  db.close();
205
+ if (nativeDb) {
206
+ try {
207
+ nativeDb.close();
208
+ } catch {
209
+ /* already closed */
210
+ }
211
+ }
165
212
  }
166
213
  }
167
214