@optave/codegraph 3.8.0 → 3.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/README.md +9 -8
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +95 -87
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/metrics.d.ts +0 -3
  6. package/dist/ast-analysis/metrics.d.ts.map +1 -1
  7. package/dist/ast-analysis/metrics.js +30 -13
  8. package/dist/ast-analysis/metrics.js.map +1 -1
  9. package/dist/ast-analysis/shared.d.ts.map +1 -1
  10. package/dist/ast-analysis/shared.js +24 -19
  11. package/dist/ast-analysis/shared.js.map +1 -1
  12. package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
  13. package/dist/ast-analysis/visitor-utils.js +55 -39
  14. package/dist/ast-analysis/visitor-utils.js.map +1 -1
  15. package/dist/ast-analysis/visitor.d.ts.map +1 -1
  16. package/dist/ast-analysis/visitor.js +91 -70
  17. package/dist/ast-analysis/visitor.js.map +1 -1
  18. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  19. package/dist/ast-analysis/visitors/ast-store-visitor.js +54 -58
  20. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  21. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  22. package/dist/ast-analysis/visitors/complexity-visitor.js +32 -39
  23. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  24. package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
  25. package/dist/ast-analysis/visitors/dataflow-visitor.js +57 -38
  26. package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
  27. package/dist/cli/commands/watch.d.ts.map +1 -1
  28. package/dist/cli/commands/watch.js +16 -2
  29. package/dist/cli/commands/watch.js.map +1 -1
  30. package/dist/db/connection.d.ts.map +1 -1
  31. package/dist/db/connection.js +29 -26
  32. package/dist/db/connection.js.map +1 -1
  33. package/dist/db/query-builder.d.ts.map +1 -1
  34. package/dist/db/query-builder.js +16 -5
  35. package/dist/db/query-builder.js.map +1 -1
  36. package/dist/db/repository/base.d.ts +10 -0
  37. package/dist/db/repository/base.d.ts.map +1 -1
  38. package/dist/db/repository/base.js +17 -0
  39. package/dist/db/repository/base.js.map +1 -1
  40. package/dist/db/repository/native-repository.d.ts +6 -1
  41. package/dist/db/repository/native-repository.d.ts.map +1 -1
  42. package/dist/db/repository/native-repository.js +77 -1
  43. package/dist/db/repository/native-repository.js.map +1 -1
  44. package/dist/db/repository/nodes.d.ts.map +1 -1
  45. package/dist/db/repository/nodes.js +8 -4
  46. package/dist/db/repository/nodes.js.map +1 -1
  47. package/dist/db/repository/sqlite-repository.d.ts +3 -0
  48. package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
  49. package/dist/db/repository/sqlite-repository.js +26 -0
  50. package/dist/db/repository/sqlite-repository.js.map +1 -1
  51. package/dist/domain/analysis/brief.d.ts.map +1 -1
  52. package/dist/domain/analysis/brief.js +13 -17
  53. package/dist/domain/analysis/brief.js.map +1 -1
  54. package/dist/domain/analysis/context.d.ts.map +1 -1
  55. package/dist/domain/analysis/context.js +14 -11
  56. package/dist/domain/analysis/context.js.map +1 -1
  57. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  58. package/dist/domain/analysis/dependencies.js +53 -52
  59. package/dist/domain/analysis/dependencies.js.map +1 -1
  60. package/dist/domain/analysis/fn-impact.d.ts +2 -7
  61. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  62. package/dist/domain/analysis/fn-impact.js +33 -31
  63. package/dist/domain/analysis/fn-impact.js.map +1 -1
  64. package/dist/domain/analysis/implementations.d.ts.map +1 -1
  65. package/dist/domain/analysis/implementations.js +11 -19
  66. package/dist/domain/analysis/implementations.js.map +1 -1
  67. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  68. package/dist/domain/analysis/module-map.js +55 -76
  69. package/dist/domain/analysis/module-map.js.map +1 -1
  70. package/dist/domain/analysis/query-helpers.d.ts +7 -0
  71. package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
  72. package/dist/domain/analysis/query-helpers.js +15 -1
  73. package/dist/domain/analysis/query-helpers.js.map +1 -1
  74. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  75. package/dist/domain/graph/builder/pipeline.js +255 -105
  76. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  77. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  78. package/dist/domain/graph/builder/stages/build-edges.js +22 -17
  79. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  80. package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
  81. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  82. package/dist/domain/graph/builder/stages/finalize.js +2 -2
  83. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  84. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  85. package/dist/domain/graph/builder/stages/insert-nodes.js +32 -21
  86. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  87. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  88. package/dist/domain/graph/builder/stages/resolve-imports.js +95 -84
  89. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  90. package/dist/domain/graph/cycles.d.ts +6 -0
  91. package/dist/domain/graph/cycles.d.ts.map +1 -1
  92. package/dist/domain/graph/cycles.js +114 -22
  93. package/dist/domain/graph/cycles.js.map +1 -1
  94. package/dist/domain/graph/resolve.js +1 -1
  95. package/dist/domain/graph/resolve.js.map +1 -1
  96. package/dist/domain/graph/watcher.d.ts +2 -0
  97. package/dist/domain/graph/watcher.d.ts.map +1 -1
  98. package/dist/domain/graph/watcher.js +170 -75
  99. package/dist/domain/graph/watcher.js.map +1 -1
  100. package/dist/domain/parser.d.ts +0 -5
  101. package/dist/domain/parser.d.ts.map +1 -1
  102. package/dist/domain/parser.js +13 -28
  103. package/dist/domain/parser.js.map +1 -1
  104. package/dist/domain/search/generator.js +1 -1
  105. package/dist/domain/search/generator.js.map +1 -1
  106. package/dist/domain/search/models.d.ts +4 -3
  107. package/dist/domain/search/models.d.ts.map +1 -1
  108. package/dist/domain/search/models.js +18 -5
  109. package/dist/domain/search/models.js.map +1 -1
  110. package/dist/domain/search/search/hybrid.d.ts.map +1 -1
  111. package/dist/domain/search/search/hybrid.js +29 -18
  112. package/dist/domain/search/search/hybrid.js.map +1 -1
  113. package/dist/extractors/go.js +36 -33
  114. package/dist/extractors/go.js.map +1 -1
  115. package/dist/extractors/helpers.d.ts.map +1 -1
  116. package/dist/extractors/helpers.js +40 -29
  117. package/dist/extractors/helpers.js.map +1 -1
  118. package/dist/extractors/java.js +58 -46
  119. package/dist/extractors/java.js.map +1 -1
  120. package/dist/extractors/javascript.js +46 -45
  121. package/dist/extractors/javascript.js.map +1 -1
  122. package/dist/extractors/kotlin.js +84 -78
  123. package/dist/extractors/kotlin.js.map +1 -1
  124. package/dist/extractors/python.js +29 -24
  125. package/dist/extractors/python.js.map +1 -1
  126. package/dist/extractors/rust.js +41 -32
  127. package/dist/extractors/rust.js.map +1 -1
  128. package/dist/extractors/solidity.js +58 -67
  129. package/dist/extractors/solidity.js.map +1 -1
  130. package/dist/extractors/swift.js +83 -81
  131. package/dist/extractors/swift.js.map +1 -1
  132. package/dist/extractors/zig.js +58 -60
  133. package/dist/extractors/zig.js.map +1 -1
  134. package/dist/features/ast.d.ts +16 -14
  135. package/dist/features/ast.d.ts.map +1 -1
  136. package/dist/features/ast.js +83 -81
  137. package/dist/features/ast.js.map +1 -1
  138. package/dist/features/audit.d.ts.map +1 -1
  139. package/dist/features/audit.js +8 -6
  140. package/dist/features/audit.js.map +1 -1
  141. package/dist/features/branch-compare.d.ts.map +1 -1
  142. package/dist/features/branch-compare.js +69 -72
  143. package/dist/features/branch-compare.js.map +1 -1
  144. package/dist/features/communities.d.ts.map +1 -1
  145. package/dist/features/communities.js +19 -7
  146. package/dist/features/communities.js.map +1 -1
  147. package/dist/features/complexity.d.ts.map +1 -1
  148. package/dist/features/complexity.js +120 -125
  149. package/dist/features/complexity.js.map +1 -1
  150. package/dist/features/dataflow.d.ts.map +1 -1
  151. package/dist/features/dataflow.js +136 -137
  152. package/dist/features/dataflow.js.map +1 -1
  153. package/dist/features/flow.d.ts.map +1 -1
  154. package/dist/features/flow.js +84 -79
  155. package/dist/features/flow.js.map +1 -1
  156. package/dist/features/structure-query.d.ts.map +1 -1
  157. package/dist/features/structure-query.js +69 -65
  158. package/dist/features/structure-query.js.map +1 -1
  159. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  160. package/dist/graph/algorithms/leiden/optimiser.js +70 -55
  161. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  162. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  163. package/dist/graph/algorithms/leiden/partition.js +288 -266
  164. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  165. package/dist/graph/model.d.ts.map +1 -1
  166. package/dist/graph/model.js +5 -1
  167. package/dist/graph/model.js.map +1 -1
  168. package/dist/infrastructure/config.d.ts.map +1 -1
  169. package/dist/infrastructure/config.js +6 -4
  170. package/dist/infrastructure/config.js.map +1 -1
  171. package/dist/infrastructure/suppress.d.ts +25 -0
  172. package/dist/infrastructure/suppress.d.ts.map +1 -0
  173. package/dist/infrastructure/suppress.js +43 -0
  174. package/dist/infrastructure/suppress.js.map +1 -0
  175. package/dist/mcp/server.d.ts.map +1 -1
  176. package/dist/mcp/server.js +29 -24
  177. package/dist/mcp/server.js.map +1 -1
  178. package/dist/presentation/dataflow.d.ts.map +1 -1
  179. package/dist/presentation/dataflow.js +47 -38
  180. package/dist/presentation/dataflow.js.map +1 -1
  181. package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
  182. package/dist/presentation/diff-impact-mermaid.js +60 -51
  183. package/dist/presentation/diff-impact-mermaid.js.map +1 -1
  184. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  185. package/dist/presentation/queries-cli/exports.js +20 -14
  186. package/dist/presentation/queries-cli/exports.js.map +1 -1
  187. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  188. package/dist/presentation/queries-cli/impact.js +15 -13
  189. package/dist/presentation/queries-cli/impact.js.map +1 -1
  190. package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
  191. package/dist/presentation/queries-cli/inspect.js +101 -79
  192. package/dist/presentation/queries-cli/inspect.js.map +1 -1
  193. package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
  194. package/dist/presentation/queries-cli/overview.js +25 -16
  195. package/dist/presentation/queries-cli/overview.js.map +1 -1
  196. package/dist/presentation/queries-cli/path.js +26 -20
  197. package/dist/presentation/queries-cli/path.js.map +1 -1
  198. package/dist/presentation/result-formatter.d.ts +10 -0
  199. package/dist/presentation/result-formatter.d.ts.map +1 -1
  200. package/dist/presentation/result-formatter.js +16 -1
  201. package/dist/presentation/result-formatter.js.map +1 -1
  202. package/dist/presentation/viewer.d.ts.map +1 -1
  203. package/dist/presentation/viewer.js +18 -12
  204. package/dist/presentation/viewer.js.map +1 -1
  205. package/dist/shared/errors.d.ts +5 -0
  206. package/dist/shared/errors.d.ts.map +1 -1
  207. package/dist/shared/errors.js +5 -0
  208. package/dist/shared/errors.js.map +1 -1
  209. package/dist/shared/hierarchy.d.ts +8 -2
  210. package/dist/shared/hierarchy.d.ts.map +1 -1
  211. package/dist/shared/hierarchy.js +42 -1
  212. package/dist/shared/hierarchy.js.map +1 -1
  213. package/dist/shared/normalize.d.ts +6 -1
  214. package/dist/shared/normalize.d.ts.map +1 -1
  215. package/dist/shared/normalize.js +20 -12
  216. package/dist/shared/normalize.js.map +1 -1
  217. package/dist/shared/paginate.d.ts +0 -9
  218. package/dist/shared/paginate.d.ts.map +1 -1
  219. package/dist/shared/paginate.js +0 -15
  220. package/dist/shared/paginate.js.map +1 -1
  221. package/dist/types.d.ts +10 -4
  222. package/dist/types.d.ts.map +1 -1
  223. package/package.json +7 -7
  224. package/src/ast-analysis/engine.ts +126 -105
  225. package/src/ast-analysis/metrics.ts +33 -11
  226. package/src/ast-analysis/shared.ts +33 -24
  227. package/src/ast-analysis/visitor-utils.ts +52 -32
  228. package/src/ast-analysis/visitor.ts +132 -71
  229. package/src/ast-analysis/visitors/ast-store-visitor.ts +53 -50
  230. package/src/ast-analysis/visitors/complexity-visitor.ts +35 -40
  231. package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
  232. package/src/cli/commands/watch.ts +16 -2
  233. package/src/db/connection.ts +29 -28
  234. package/src/db/query-builder.ts +15 -3
  235. package/src/db/repository/base.ts +20 -0
  236. package/src/db/repository/native-repository.ts +79 -1
  237. package/src/db/repository/nodes.ts +13 -8
  238. package/src/db/repository/sqlite-repository.ts +29 -0
  239. package/src/domain/analysis/brief.ts +15 -25
  240. package/src/domain/analysis/context.ts +17 -10
  241. package/src/domain/analysis/dependencies.ts +67 -76
  242. package/src/domain/analysis/fn-impact.ts +36 -43
  243. package/src/domain/analysis/implementations.ts +11 -17
  244. package/src/domain/analysis/module-map.ts +58 -92
  245. package/src/domain/analysis/query-helpers.ts +18 -1
  246. package/src/domain/graph/builder/pipeline.ts +286 -97
  247. package/src/domain/graph/builder/stages/build-edges.ts +22 -18
  248. package/src/domain/graph/builder/stages/detect-changes.ts +2 -2
  249. package/src/domain/graph/builder/stages/finalize.ts +2 -2
  250. package/src/domain/graph/builder/stages/insert-nodes.ts +59 -34
  251. package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
  252. package/src/domain/graph/cycles.ts +110 -23
  253. package/src/domain/graph/resolve.ts +1 -1
  254. package/src/domain/graph/watcher.ts +202 -96
  255. package/src/domain/parser.ts +14 -26
  256. package/src/domain/search/generator.ts +1 -1
  257. package/src/domain/search/models.ts +17 -4
  258. package/src/domain/search/search/hybrid.ts +69 -51
  259. package/src/extractors/go.ts +43 -33
  260. package/src/extractors/helpers.ts +37 -23
  261. package/src/extractors/java.ts +66 -47
  262. package/src/extractors/javascript.ts +45 -46
  263. package/src/extractors/kotlin.ts +84 -77
  264. package/src/extractors/python.ts +31 -25
  265. package/src/extractors/rust.ts +37 -29
  266. package/src/extractors/solidity.ts +57 -61
  267. package/src/extractors/swift.ts +81 -80
  268. package/src/extractors/zig.ts +58 -61
  269. package/src/features/ast.ts +130 -110
  270. package/src/features/audit.ts +8 -6
  271. package/src/features/branch-compare.ts +105 -79
  272. package/src/features/communities.ts +25 -10
  273. package/src/features/complexity.ts +171 -134
  274. package/src/features/dataflow.ts +165 -175
  275. package/src/features/flow.ts +129 -92
  276. package/src/features/structure-query.ts +79 -64
  277. package/src/graph/algorithms/leiden/optimiser.ts +99 -55
  278. package/src/graph/algorithms/leiden/partition.ts +359 -294
  279. package/src/graph/model.ts +6 -1
  280. package/src/infrastructure/config.ts +6 -4
  281. package/src/infrastructure/suppress.ts +47 -0
  282. package/src/mcp/server.ts +53 -37
  283. package/src/presentation/dataflow.ts +50 -44
  284. package/src/presentation/diff-impact-mermaid.ts +104 -62
  285. package/src/presentation/queries-cli/exports.ts +21 -13
  286. package/src/presentation/queries-cli/impact.ts +15 -13
  287. package/src/presentation/queries-cli/inspect.ts +100 -81
  288. package/src/presentation/queries-cli/overview.ts +26 -16
  289. package/src/presentation/queries-cli/path.ts +33 -25
  290. package/src/presentation/result-formatter.ts +19 -1
  291. package/src/presentation/viewer.ts +42 -14
  292. package/src/shared/errors.ts +6 -0
  293. package/src/shared/hierarchy.ts +50 -2
  294. package/src/shared/normalize.ts +31 -12
  295. package/src/shared/paginate.ts +0 -17
  296. package/src/types.ts +24 -4
@@ -80,21 +80,9 @@ function handleGoFuncDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
80
80
 
81
81
  function handleGoMethodDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
82
82
  const nameNode = node.childForFieldName('name');
83
- const receiver = node.childForFieldName('receiver');
84
83
  if (!nameNode) return;
85
- let receiverType: string | null = null;
86
- if (receiver) {
87
- for (let i = 0; i < receiver.childCount; i++) {
88
- const param = receiver.child(i);
89
- if (!param) continue;
90
- const typeNode = param.childForFieldName('type');
91
- if (typeNode) {
92
- receiverType =
93
- typeNode.type === 'pointer_type' ? typeNode.text.replace(/^\*/, '') : typeNode.text;
94
- break;
95
- }
96
- }
97
- }
84
+ const receiver = node.childForFieldName('receiver');
85
+ const receiverType = receiver ? extractGoReceiverType(receiver) : null;
98
86
  const fullName = receiverType ? `${receiverType}.${nameNode.text}` : nameNode.text;
99
87
  const params = extractGoParameters(node.childForFieldName('parameters'));
100
88
  ctx.definitions.push({
@@ -107,6 +95,19 @@ function handleGoMethodDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
107
95
  });
108
96
  }
109
97
 
98
+ /** Extract the receiver type name from a method receiver parameter list. */
99
+ function extractGoReceiverType(receiver: TreeSitterNode): string | null {
100
+ for (let i = 0; i < receiver.childCount; i++) {
101
+ const param = receiver.child(i);
102
+ if (!param) continue;
103
+ const typeNode = param.childForFieldName('type');
104
+ if (typeNode) {
105
+ return typeNode.type === 'pointer_type' ? typeNode.text.replace(/^\*/, '') : typeNode.text;
106
+ }
107
+ }
108
+ return null;
109
+ }
110
+
110
111
  function handleGoTypeDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
111
112
  for (let i = 0; i < node.childCount; i++) {
112
113
  const spec = node.child(i);
@@ -403,13 +404,38 @@ function extractGoParameters(paramListNode: TreeSitterNode | null): SubDeclarati
403
404
  * This performs file-local matching (cross-file matching requires build-edges).
404
405
  */
405
406
  function matchGoStructuralInterfaces(ctx: ExtractorOutput): void {
407
+ const { interfaceMethods, structMethods, structLines } = collectGoMethodSets(ctx);
408
+
409
+ // Match: struct satisfies interface if it has all interface methods (name-only;
410
+ // signatures are not verified — treat as candidate match, not definitive).
411
+ // NOTE: embedded interfaces (type_elem nodes) are not resolved — composite
412
+ // interfaces like `type ReadWriter interface { Reader; Writer }` will have an
413
+ // empty method set and be silently excluded from matching.
414
+ for (const [structName, methods] of structMethods) {
415
+ for (const [ifaceName, ifaceMethods] of interfaceMethods) {
416
+ if (ifaceMethods.size > 0 && [...ifaceMethods].every((m) => methods.has(m))) {
417
+ ctx.classes.push({
418
+ name: structName,
419
+ implements: ifaceName,
420
+ line: structLines.get(structName) || 1,
421
+ });
422
+ }
423
+ }
424
+ }
425
+ }
426
+
427
+ /** Collect interface and struct method sets from definitions for structural matching. */
428
+ function collectGoMethodSets(ctx: ExtractorOutput): {
429
+ interfaceMethods: Map<string, Set<string>>;
430
+ structMethods: Map<string, Set<string>>;
431
+ structLines: Map<string, number>;
432
+ } {
406
433
  const interfaceMethods = new Map<string, Set<string>>();
407
434
  const structMethods = new Map<string, Set<string>>();
408
435
  const structLines = new Map<string, number>();
409
-
410
- // Collect interface and struct definitions
411
436
  const interfaceNames = new Set<string>();
412
437
  const structNames = new Set<string>();
438
+
413
439
  for (const def of ctx.definitions) {
414
440
  if (def.kind === 'interface') interfaceNames.add(def.name);
415
441
  if (def.kind === 'struct') {
@@ -418,7 +444,6 @@ function matchGoStructuralInterfaces(ctx: ExtractorOutput): void {
418
444
  }
419
445
  }
420
446
 
421
- // Collect methods grouped by receiver type
422
447
  for (const def of ctx.definitions) {
423
448
  if (def.kind !== 'method' || !def.name.includes('.')) continue;
424
449
  const dotIdx = def.name.indexOf('.');
@@ -435,22 +460,7 @@ function matchGoStructuralInterfaces(ctx: ExtractorOutput): void {
435
460
  }
436
461
  }
437
462
 
438
- // Match: struct satisfies interface if it has all interface methods (name-only;
439
- // signatures are not verified — treat as candidate match, not definitive).
440
- // NOTE: embedded interfaces (type_elem nodes) are not resolved — composite
441
- // interfaces like `type ReadWriter interface { Reader; Writer }` will have an
442
- // empty method set and be silently excluded from matching.
443
- for (const [structName, methods] of structMethods) {
444
- for (const [ifaceName, ifaceMethods] of interfaceMethods) {
445
- if (ifaceMethods.size > 0 && [...ifaceMethods].every((m) => methods.has(m))) {
446
- ctx.classes.push({
447
- name: structName,
448
- implements: ifaceName,
449
- line: structLines.get(structName) || 1,
450
- });
451
- }
452
- }
453
- }
463
+ return { interfaceMethods, structMethods, structLines };
454
464
  }
455
465
 
456
466
  function extractStructFields(structTypeNode: TreeSitterNode): SubDeclaration[] {
@@ -110,6 +110,20 @@ export function findParentNode(
110
110
  return null;
111
111
  }
112
112
 
113
+ /**
114
+ * Resolve a container's body node by trying each field name in order.
115
+ */
116
+ function resolveBodyNode(
117
+ containerNode: TreeSitterNode,
118
+ bodyFields: readonly string[],
119
+ ): TreeSitterNode | null {
120
+ for (const field of bodyFields) {
121
+ const body = containerNode.childForFieldName(field) || findChild(containerNode, field);
122
+ if (body) return body;
123
+ }
124
+ return null;
125
+ }
126
+
113
127
  /**
114
128
  * Extract child declarations from a container node's body.
115
129
  * Finds the body via `bodyFields` (tries childForFieldName then findChild for each),
@@ -126,22 +140,17 @@ export function extractBodyMembers(
126
140
  nameField: string = 'name',
127
141
  visibility?: (member: TreeSitterNode) => SubDeclaration['visibility'],
128
142
  ): SubDeclaration[] {
143
+ const body = resolveBodyNode(containerNode, bodyFields);
144
+ if (!body) return [];
129
145
  const members: SubDeclaration[] = [];
130
- let body: TreeSitterNode | null = null;
131
- for (const field of bodyFields) {
132
- body = containerNode.childForFieldName(field) || findChild(containerNode, field);
133
- if (body) break;
134
- }
135
- if (!body) return members;
136
146
  for (let i = 0; i < body.childCount; i++) {
137
147
  const member = body.child(i);
138
148
  if (!member || member.type !== memberType) continue;
139
149
  const nn = member.childForFieldName(nameField);
140
- if (nn) {
141
- const entry: SubDeclaration = { name: nn.text, kind, line: member.startPosition.row + 1 };
142
- if (visibility) entry.visibility = visibility(member);
143
- members.push(entry);
144
- }
150
+ if (!nn) continue;
151
+ const entry: SubDeclaration = { name: nn.text, kind, line: member.startPosition.row + 1 };
152
+ if (visibility) entry.visibility = visibility(member);
153
+ members.push(entry);
145
154
  }
146
155
  return members;
147
156
  }
@@ -162,24 +171,29 @@ export function lastPathSegment(path: string, separator: string = '/'): string {
162
171
  return path.split(separator).pop() ?? path;
163
172
  }
164
173
 
174
+ /**
175
+ * Parse visibility from a modifier node's text content.
176
+ */
177
+ function parseModifierText(text: string): 'public' | 'private' | 'protected' | undefined {
178
+ if (VISIBILITY_KEYWORDS.has(text)) return text as 'public' | 'private' | 'protected';
179
+ // C# 'private protected' — accessible to derived types in same assembly → protected
180
+ if (text === 'private protected') return 'protected';
181
+ // Compound modifiers node (Java: "public static") — scan its text for a keyword
182
+ for (const kw of VISIBILITY_KEYWORDS) {
183
+ if (text.includes(kw)) return kw as 'public' | 'private' | 'protected';
184
+ }
185
+ return undefined;
186
+ }
187
+
165
188
  export function extractModifierVisibility(
166
189
  node: TreeSitterNode,
167
190
  modifierTypes: Set<string> = DEFAULT_MODIFIER_TYPES,
168
191
  ): 'public' | 'private' | 'protected' | undefined {
169
192
  for (let i = 0; i < node.childCount; i++) {
170
193
  const child = node.child(i);
171
- if (!child) continue;
172
- // Direct keyword match (e.g., PHP visibility_modifier = "public")
173
- if (modifierTypes.has(child.type)) {
174
- const text = child.text;
175
- if (VISIBILITY_KEYWORDS.has(text)) return text as 'public' | 'private' | 'protected';
176
- // C# 'private protected' — accessible to derived types in same assembly → protected
177
- if (text === 'private protected') return 'protected';
178
- // Compound modifiers node (Java: "public static") — scan its text for a keyword
179
- for (const kw of VISIBILITY_KEYWORDS) {
180
- if (text.includes(kw)) return kw as 'public' | 'private' | 'protected';
181
- }
182
- }
194
+ if (!child || !modifierTypes.has(child.type)) continue;
195
+ const result = parseModifierText(child.text);
196
+ if (result) return result;
183
197
  }
184
198
  return undefined;
185
199
  }
@@ -83,27 +83,7 @@ function handleJavaClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
83
83
  children: classChildren.length > 0 ? classChildren : undefined,
84
84
  });
85
85
 
86
- const superclass = node.childForFieldName('superclass');
87
- if (superclass) {
88
- for (let i = 0; i < superclass.childCount; i++) {
89
- const child = superclass.child(i);
90
- if (
91
- child &&
92
- (child.type === 'type_identifier' ||
93
- child.type === 'identifier' ||
94
- child.type === 'generic_type')
95
- ) {
96
- const superName = child.type === 'generic_type' ? child.child(0)?.text : child.text;
97
- if (superName)
98
- ctx.classes.push({
99
- name: nameNode.text,
100
- extends: superName,
101
- line: node.startPosition.row + 1,
102
- });
103
- break;
104
- }
105
- }
106
- }
86
+ extractJavaSuperclass(node, nameNode.text, ctx);
107
87
 
108
88
  const interfaces = node.childForFieldName('interfaces');
109
89
  if (interfaces) {
@@ -111,6 +91,32 @@ function handleJavaClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
111
91
  }
112
92
  }
113
93
 
94
+ /** Extract the superclass (extends) relationship from a Java class declaration. */
95
+ function extractJavaSuperclass(
96
+ node: TreeSitterNode,
97
+ className: string,
98
+ ctx: ExtractorOutput,
99
+ ): void {
100
+ const superclass = node.childForFieldName('superclass');
101
+ if (!superclass) return;
102
+ const superName = findJavaSuperTypeName(superclass);
103
+ if (superName) {
104
+ ctx.classes.push({ name: className, extends: superName, line: node.startPosition.row + 1 });
105
+ }
106
+ }
107
+
108
+ /** Find the type name from a superclass node (handles generic_type unwrapping). */
109
+ function findJavaSuperTypeName(superclass: TreeSitterNode): string | undefined {
110
+ for (let i = 0; i < superclass.childCount; i++) {
111
+ const child = superclass.child(i);
112
+ if (!child) continue;
113
+ if (JAVA_TYPE_NODE_TYPES.has(child.type)) {
114
+ return resolveJavaIfaceName(child);
115
+ }
116
+ }
117
+ return undefined;
118
+ }
119
+
114
120
  const JAVA_TYPE_NODE_TYPES = new Set(['type_identifier', 'identifier', 'generic_type']);
115
121
 
116
122
  /** Resolve interface name from a type node (handles generic_type unwrapping). */
@@ -161,19 +167,26 @@ function handleJavaInterfaceDecl(node: TreeSitterNode, ctx: ExtractorOutput): vo
161
167
  endLine: nodeEndLine(node),
162
168
  });
163
169
  const body = node.childForFieldName('body');
164
- if (body) {
165
- for (let i = 0; i < body.childCount; i++) {
166
- const child = body.child(i);
167
- if (child && child.type === 'method_declaration') {
168
- const methName = child.childForFieldName('name');
169
- if (methName) {
170
- ctx.definitions.push({
171
- name: `${nameNode.text}.${methName.text}`,
172
- kind: 'method',
173
- line: child.startPosition.row + 1,
174
- endLine: child.endPosition.row + 1,
175
- });
176
- }
170
+ if (body) extractJavaInterfaceMethods(body, nameNode.text, ctx);
171
+ }
172
+
173
+ /** Extract method declarations from a Java interface body. */
174
+ function extractJavaInterfaceMethods(
175
+ body: TreeSitterNode,
176
+ ifaceName: string,
177
+ ctx: ExtractorOutput,
178
+ ): void {
179
+ for (let i = 0; i < body.childCount; i++) {
180
+ const child = body.child(i);
181
+ if (child && child.type === 'method_declaration') {
182
+ const methName = child.childForFieldName('name');
183
+ if (methName) {
184
+ ctx.definitions.push({
185
+ name: `${ifaceName}.${methName.text}`,
186
+ kind: 'method',
187
+ line: child.startPosition.row + 1,
188
+ endLine: child.endPosition.row + 1,
189
+ });
177
190
  }
178
191
  }
179
192
  }
@@ -321,23 +334,29 @@ function extractClassFields(classNode: TreeSitterNode): SubDeclaration[] {
321
334
  for (let i = 0; i < body.childCount; i++) {
322
335
  const member = body.child(i);
323
336
  if (!member || member.type !== 'field_declaration') continue;
324
- for (let j = 0; j < member.childCount; j++) {
325
- const child = member.child(j);
326
- if (!child || child.type !== 'variable_declarator') continue;
327
- const nameNode = child.childForFieldName('name');
328
- if (nameNode) {
329
- fields.push({
330
- name: nameNode.text,
331
- kind: 'property',
332
- line: member.startPosition.row + 1,
333
- visibility: extractModifierVisibility(member),
334
- });
335
- }
336
- }
337
+ extractFieldDeclarators(member, fields);
337
338
  }
338
339
  return fields;
339
340
  }
340
341
 
342
+ /** Extract variable_declarator names from a field_declaration node. */
343
+ function extractFieldDeclarators(member: TreeSitterNode, fields: SubDeclaration[]): void {
344
+ const vis = extractModifierVisibility(member);
345
+ for (let j = 0; j < member.childCount; j++) {
346
+ const child = member.child(j);
347
+ if (!child || child.type !== 'variable_declarator') continue;
348
+ const nameNode = child.childForFieldName('name');
349
+ if (nameNode) {
350
+ fields.push({
351
+ name: nameNode.text,
352
+ kind: 'property',
353
+ line: member.startPosition.row + 1,
354
+ visibility: vis,
355
+ });
356
+ }
357
+ }
358
+ }
359
+
341
360
  function extractEnumConstants(enumNode: TreeSitterNode): SubDeclaration[] {
342
361
  return extractBodyMembers(enumNode, ['body', 'enum_body'], 'enum_constant', 'constant');
343
362
  }
@@ -328,34 +328,7 @@ function extractConstantsWalk(node: TreeSitterNode, definitions: Definition[]):
328
328
  if (inner) declNode = inner;
329
329
  }
330
330
 
331
- const t = declNode.type;
332
- if (t === 'lexical_declaration' || t === 'variable_declaration') {
333
- if (declNode.text.startsWith('const ')) {
334
- for (let j = 0; j < declNode.childCount; j++) {
335
- const declarator = declNode.child(j);
336
- if (!declarator || declarator.type !== 'variable_declarator') continue;
337
- const nameN = declarator.childForFieldName('name');
338
- const valueN = declarator.childForFieldName('value');
339
- if (!nameN || nameN.type !== 'identifier' || !valueN) continue;
340
- // Skip functions — already captured by query patterns
341
- const valType = valueN.type;
342
- if (
343
- valType === 'arrow_function' ||
344
- valType === 'function_expression' ||
345
- valType === 'function'
346
- )
347
- continue;
348
- if (isConstantValue(valueN)) {
349
- definitions.push({
350
- name: nameN.text,
351
- kind: 'constant',
352
- line: declNode.startPosition.row + 1,
353
- endLine: nodeEndLine(declNode),
354
- });
355
- }
356
- }
357
- }
358
- }
331
+ extractConstDeclarators(declNode, definitions);
359
332
 
360
333
  // Recurse into non-function, non-export-statement children (blocks, if-statements, etc.)
361
334
  if (child.type !== 'export_statement') {
@@ -364,6 +337,33 @@ function extractConstantsWalk(node: TreeSitterNode, definitions: Definition[]):
364
337
  }
365
338
  }
366
339
 
340
+ /** Extract constant definitions from a `const` declaration node. */
341
+ function extractConstDeclarators(declNode: TreeSitterNode, definitions: Definition[]): void {
342
+ const t = declNode.type;
343
+ if (t !== 'lexical_declaration' && t !== 'variable_declaration') return;
344
+ if (!declNode.text.startsWith('const ')) return;
345
+
346
+ for (let j = 0; j < declNode.childCount; j++) {
347
+ const declarator = declNode.child(j);
348
+ if (!declarator || declarator.type !== 'variable_declarator') continue;
349
+ const nameN = declarator.childForFieldName('name');
350
+ const valueN = declarator.childForFieldName('value');
351
+ if (!nameN || nameN.type !== 'identifier' || !valueN) continue;
352
+ // Skip functions — already captured by query patterns
353
+ const valType = valueN.type;
354
+ if (valType === 'arrow_function' || valType === 'function_expression' || valType === 'function')
355
+ continue;
356
+ if (isConstantValue(valueN)) {
357
+ definitions.push({
358
+ name: nameN.text,
359
+ kind: 'constant',
360
+ line: declNode.startPosition.row + 1,
361
+ endLine: nodeEndLine(declNode),
362
+ });
363
+ }
364
+ }
365
+ }
366
+
367
367
  /**
368
368
  * Recursive walk to find dynamic import() calls.
369
369
  * Query patterns match call_expression with identifier/member_expression/subscript_expression
@@ -678,24 +678,7 @@ function handleCallExpr(node: TreeSitterNode, ctx: ExtractorOutput): void {
678
678
  const fn = node.childForFieldName('function');
679
679
  if (!fn) return;
680
680
  if (fn.type === 'import') {
681
- const args = node.childForFieldName('arguments') || findChild(node, 'arguments');
682
- if (args) {
683
- const strArg = findChild(args, 'string');
684
- if (strArg) {
685
- const modPath = strArg.text.replace(/['"]/g, '');
686
- const names = extractDynamicImportNames(node);
687
- ctx.imports.push({
688
- source: modPath,
689
- names,
690
- line: node.startPosition.row + 1,
691
- dynamicImport: true,
692
- });
693
- } else {
694
- debug(
695
- `Skipping non-static dynamic import() at line ${node.startPosition.row + 1} (template literal or variable)`,
696
- );
697
- }
698
- }
681
+ handleDynamicImportCall(node, ctx.imports);
699
682
  } else {
700
683
  const callInfo = extractCallInfo(fn, node);
701
684
  if (callInfo) ctx.calls.push(callInfo);
@@ -706,6 +689,22 @@ function handleCallExpr(node: TreeSitterNode, ctx: ExtractorOutput): void {
706
689
  }
707
690
  }
708
691
 
692
+ /** Handle a dynamic import() call expression and add to imports if static. */
693
+ function handleDynamicImportCall(node: TreeSitterNode, imports: Import[]): void {
694
+ const args = node.childForFieldName('arguments') || findChild(node, 'arguments');
695
+ if (!args) return;
696
+ const strArg = findChild(args, 'string');
697
+ if (strArg) {
698
+ const modPath = strArg.text.replace(/['"]/g, '');
699
+ const names = extractDynamicImportNames(node);
700
+ imports.push({ source: modPath, names, line: node.startPosition.row + 1, dynamicImport: true });
701
+ } else {
702
+ debug(
703
+ `Skipping non-static dynamic import() at line ${node.startPosition.row + 1} (template literal or variable)`,
704
+ );
705
+ }
706
+ }
707
+
709
708
  function handleImportStmt(node: TreeSitterNode, ctx: ExtractorOutput): void {
710
709
  const isTypeOnly = node.text.startsWith('import type');
711
710
  const source = node.childForFieldName('source') || findChild(node, 'string');
@@ -81,48 +81,7 @@ function handleKotlinClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void
81
81
 
82
82
  const kind = isInterface ? 'interface' : isEnum ? 'enum' : 'class';
83
83
 
84
- const children: SubDeclaration[] = [];
85
- if (isEnum) {
86
- // Enum entries are inside class_body
87
- const body = findChild(node, 'class_body');
88
- if (body) {
89
- for (let i = 0; i < body.childCount; i++) {
90
- const child = body.child(i);
91
- if (child && child.type === 'enum_entry') {
92
- const entryName = findChild(child, 'simple_identifier');
93
- if (entryName) {
94
- children.push({
95
- name: entryName.text,
96
- kind: 'constant',
97
- line: child.startPosition.row + 1,
98
- });
99
- }
100
- }
101
- }
102
- }
103
- } else {
104
- // Extract properties from class_body
105
- const body = findChild(node, 'class_body');
106
- if (body) {
107
- for (let i = 0; i < body.childCount; i++) {
108
- const child = body.child(i);
109
- if (child && child.type === 'property_declaration') {
110
- const propName = findChild(child, 'variable_declaration');
111
- if (propName) {
112
- const id = findChild(propName, 'simple_identifier');
113
- if (id) {
114
- children.push({
115
- name: id.text,
116
- kind: 'property',
117
- line: child.startPosition.row + 1,
118
- visibility: extractModifierVisibility(child),
119
- });
120
- }
121
- }
122
- }
123
- }
124
- }
125
- }
84
+ const children = isEnum ? collectKotlinEnumEntries(node) : collectKotlinProperties(node);
126
85
 
127
86
  ctx.definitions.push({
128
87
  name,
@@ -132,27 +91,79 @@ function handleKotlinClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void
132
91
  children: children.length > 0 ? children : undefined,
133
92
  });
134
93
 
135
- // Methods inside class_body
94
+ collectKotlinMethods(node, name, ctx);
95
+ collectKotlinInheritance(node, name, ctx);
96
+ }
97
+
98
+ /** Collect enum constant entries from a class_body. */
99
+ function collectKotlinEnumEntries(node: TreeSitterNode): SubDeclaration[] {
100
+ const entries: SubDeclaration[] = [];
136
101
  const body = findChild(node, 'class_body');
137
- if (body) {
138
- for (let i = 0; i < body.childCount; i++) {
139
- const child = body.child(i);
140
- if (child && child.type === 'function_declaration') {
141
- const methName = findChild(child, 'simple_identifier');
142
- if (methName) {
143
- ctx.definitions.push({
144
- name: `${name}.${methName.text}`,
145
- kind: 'method',
146
- line: child.startPosition.row + 1,
147
- endLine: child.endPosition.row + 1,
148
- visibility: extractModifierVisibility(child),
149
- });
150
- }
151
- }
102
+ if (!body) return entries;
103
+ for (let i = 0; i < body.childCount; i++) {
104
+ const child = body.child(i);
105
+ if (!child || child.type !== 'enum_entry') continue;
106
+ const entryName = findChild(child, 'simple_identifier');
107
+ if (entryName) {
108
+ entries.push({
109
+ name: entryName.text,
110
+ kind: 'constant',
111
+ line: child.startPosition.row + 1,
112
+ });
152
113
  }
153
114
  }
115
+ return entries;
116
+ }
117
+
118
+ /** Collect property declarations from a class_body. */
119
+ function collectKotlinProperties(node: TreeSitterNode): SubDeclaration[] {
120
+ const props: SubDeclaration[] = [];
121
+ const body = findChild(node, 'class_body');
122
+ if (!body) return props;
123
+ for (let i = 0; i < body.childCount; i++) {
124
+ const child = body.child(i);
125
+ if (!child || child.type !== 'property_declaration') continue;
126
+ const varDecl = findChild(child, 'variable_declaration');
127
+ if (!varDecl) continue;
128
+ const id = findChild(varDecl, 'simple_identifier');
129
+ if (id) {
130
+ props.push({
131
+ name: id.text,
132
+ kind: 'property',
133
+ line: child.startPosition.row + 1,
134
+ visibility: extractModifierVisibility(child),
135
+ });
136
+ }
137
+ }
138
+ return props;
139
+ }
140
+
141
+ /** Collect method declarations from a class_body. */
142
+ function collectKotlinMethods(node: TreeSitterNode, className: string, ctx: ExtractorOutput): void {
143
+ const body = findChild(node, 'class_body');
144
+ if (!body) return;
145
+ for (let i = 0; i < body.childCount; i++) {
146
+ const child = body.child(i);
147
+ if (!child || child.type !== 'function_declaration') continue;
148
+ const methName = findChild(child, 'simple_identifier');
149
+ if (methName) {
150
+ ctx.definitions.push({
151
+ name: `${className}.${methName.text}`,
152
+ kind: 'method',
153
+ line: child.startPosition.row + 1,
154
+ endLine: child.endPosition.row + 1,
155
+ visibility: extractModifierVisibility(child),
156
+ });
157
+ }
158
+ }
159
+ }
154
160
 
155
- // Inheritance: delegation_specifier nodes are DIRECT children
161
+ /** Collect inheritance relationships from delegation_specifier children. */
162
+ function collectKotlinInheritance(
163
+ node: TreeSitterNode,
164
+ className: string,
165
+ ctx: ExtractorOutput,
166
+ ): void {
156
167
  for (let i = 0; i < node.childCount; i++) {
157
168
  const child = node.child(i);
158
169
  if (!child || child.type !== 'delegation_specifier') continue;
@@ -161,30 +172,26 @@ function handleKotlinClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void
161
172
  const ctorInvocation = findChild(child, 'constructor_invocation');
162
173
  if (ctorInvocation) {
163
174
  const userType = findChild(ctorInvocation, 'user_type');
164
- if (userType) {
165
- const typeId = findChild(userType, 'type_identifier');
166
- if (typeId) {
167
- ctx.classes.push({
168
- name,
169
- extends: typeId.text,
170
- line: node.startPosition.row + 1,
171
- });
172
- }
175
+ const typeId = userType ? findChild(userType, 'type_identifier') : null;
176
+ if (typeId) {
177
+ ctx.classes.push({
178
+ name: className,
179
+ extends: typeId.text,
180
+ line: node.startPosition.row + 1,
181
+ });
173
182
  }
174
183
  continue;
175
184
  }
176
185
 
177
186
  // user_type > type_identifier (implements)
178
187
  const userType = findChild(child, 'user_type');
179
- if (userType) {
180
- const typeId = findChild(userType, 'type_identifier');
181
- if (typeId) {
182
- ctx.classes.push({
183
- name,
184
- implements: typeId.text,
185
- line: node.startPosition.row + 1,
186
- });
187
- }
188
+ const typeId = userType ? findChild(userType, 'type_identifier') : null;
189
+ if (typeId) {
190
+ ctx.classes.push({
191
+ name: className,
192
+ implements: typeId.text,
193
+ line: node.startPosition.row + 1,
194
+ });
188
195
  }
189
196
  }
190
197
  }