@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
@@ -16,26 +16,38 @@ import type {
16
16
 
17
17
  // Re-export all extractors for backward compatibility
18
18
  export {
19
+ extractBashSymbols,
20
+ extractCppSymbols,
19
21
  extractCSharpSymbols,
22
+ extractCSymbols,
20
23
  extractGoSymbols,
21
24
  extractHCLSymbols,
22
25
  extractJavaSymbols,
26
+ extractKotlinSymbols,
23
27
  extractPHPSymbols,
24
28
  extractPythonSymbols,
25
29
  extractRubySymbols,
26
30
  extractRustSymbols,
31
+ extractScalaSymbols,
32
+ extractSwiftSymbols,
27
33
  extractSymbols,
28
34
  } from '../extractors/index.js';
29
35
 
30
36
  import {
37
+ extractBashSymbols,
38
+ extractCppSymbols,
31
39
  extractCSharpSymbols,
40
+ extractCSymbols,
32
41
  extractGoSymbols,
33
42
  extractHCLSymbols,
34
43
  extractJavaSymbols,
44
+ extractKotlinSymbols,
35
45
  extractPHPSymbols,
36
46
  extractPythonSymbols,
37
47
  extractRubySymbols,
38
48
  extractRustSymbols,
49
+ extractScalaSymbols,
50
+ extractSwiftSymbols,
39
51
  extractSymbols,
40
52
  } from '../extractors/index.js';
41
53
 
@@ -85,6 +97,7 @@ const COMMON_QUERY_PATTERNS: string[] = [
85
97
  '(variable_declarator name: (identifier) @varfn_name value: (arrow_function) @varfn_value)',
86
98
  '(variable_declarator name: (identifier) @varfn_name value: (function_expression) @varfn_value)',
87
99
  '(method_definition name: (property_identifier) @meth_name) @meth_node',
100
+ '(method_definition name: (private_property_identifier) @meth_name) @meth_node',
88
101
  '(import_statement source: (string) @imp_source) @imp_node',
89
102
  '(export_statement) @exp_node',
90
103
  '(call_expression function: (identifier) @callfn_name) @callfn_node',
@@ -272,37 +285,38 @@ function resolveEngine(opts: ParseEngineOpts = {}): ResolvedEngine {
272
285
  * - Backward compat for older native binaries missing js_name annotations
273
286
  * - dataflow argFlows/mutations bindingType -> binding wrapper
274
287
  */
275
- function patchNativeResult(r: any): ExtractorOutput {
276
- // lineCount: napi(js_name) emits "lineCount"; older binaries may emit "line_count"
277
- r.lineCount = r.lineCount ?? r.line_count ?? null;
278
- r._lineCount = r.lineCount;
279
-
280
- // Backward compat for older binaries missing js_name annotations
281
- if (r.definitions) {
282
- for (const d of r.definitions) {
283
- if (d.endLine === undefined && d.end_line !== undefined) {
284
- d.endLine = d.end_line;
285
- }
288
+ /** Patch definition fields for backward compat with older native binaries. */
289
+ function patchDefinitions(definitions: any[]): void {
290
+ for (const d of definitions) {
291
+ if (d.endLine === undefined && d.end_line !== undefined) {
292
+ d.endLine = d.end_line;
286
293
  }
287
294
  }
288
- if (r.imports) {
289
- for (const i of r.imports) {
290
- if (i.typeOnly === undefined) i.typeOnly = i.type_only;
291
- if (i.wildcardReexport === undefined) i.wildcardReexport = i.wildcard_reexport;
292
- if (i.pythonImport === undefined) i.pythonImport = i.python_import;
293
- if (i.goImport === undefined) i.goImport = i.go_import;
294
- if (i.rustUse === undefined) i.rustUse = i.rust_use;
295
- if (i.javaImport === undefined) i.javaImport = i.java_import;
296
- if (i.csharpUsing === undefined) i.csharpUsing = i.csharp_using;
297
- if (i.rubyRequire === undefined) i.rubyRequire = i.ruby_require;
298
- if (i.phpUse === undefined) i.phpUse = i.php_use;
299
- if (i.dynamicImport === undefined) i.dynamicImport = i.dynamic_import;
300
- }
295
+ }
296
+
297
+ /** Patch import fields for backward compat with older native binaries. */
298
+ function patchImports(imports: any[]): void {
299
+ for (const i of imports) {
300
+ if (i.typeOnly === undefined) i.typeOnly = i.type_only;
301
+ if (i.wildcardReexport === undefined) i.wildcardReexport = i.wildcard_reexport;
302
+ if (i.pythonImport === undefined) i.pythonImport = i.python_import;
303
+ if (i.goImport === undefined) i.goImport = i.go_import;
304
+ if (i.rustUse === undefined) i.rustUse = i.rust_use;
305
+ if (i.javaImport === undefined) i.javaImport = i.java_import;
306
+ if (i.csharpUsing === undefined) i.csharpUsing = i.csharp_using;
307
+ if (i.rubyRequire === undefined) i.rubyRequire = i.ruby_require;
308
+ if (i.phpUse === undefined) i.phpUse = i.php_use;
309
+ if (i.cInclude === undefined) i.cInclude = i.c_include;
310
+ if (i.kotlinImport === undefined) i.kotlinImport = i.kotlin_import;
311
+ if (i.swiftImport === undefined) i.swiftImport = i.swift_import;
312
+ if (i.scalaImport === undefined) i.scalaImport = i.scala_import;
313
+ if (i.bashSource === undefined) i.bashSource = i.bash_source;
314
+ if (i.dynamicImport === undefined) i.dynamicImport = i.dynamic_import;
301
315
  }
316
+ }
302
317
 
303
- // typeMap: native returns an array of {name, typeName}; normalize to Map.
304
- // Non-TS languages may omit typeMap entirely — default to empty Map so
305
- // callers can safely access .entries()/.size without null checks.
318
+ /** Normalize native typeMap array to a Map instance. */
319
+ function patchTypeMap(r: any): void {
306
320
  if (!r.typeMap) {
307
321
  r.typeMap = new Map();
308
322
  } else if (!(r.typeMap instanceof Map)) {
@@ -313,20 +327,31 @@ function patchNativeResult(r: any): ExtractorOutput {
313
327
  ]),
314
328
  );
315
329
  }
330
+ }
316
331
 
317
- // dataflow: wrap bindingType into binding object for argFlows and mutations
318
- if (r.dataflow) {
319
- if (r.dataflow.argFlows) {
320
- for (const f of r.dataflow.argFlows) {
321
- f.binding = f.bindingType ? { type: f.bindingType } : null;
322
- }
332
+ /** Wrap bindingType into binding object for dataflow argFlows and mutations. */
333
+ function patchDataflow(dataflow: any): void {
334
+ if (dataflow.argFlows) {
335
+ for (const f of dataflow.argFlows) {
336
+ f.binding = f.bindingType ? { type: f.bindingType } : null;
323
337
  }
324
- if (r.dataflow.mutations) {
325
- for (const m of r.dataflow.mutations) {
326
- m.binding = m.bindingType ? { type: m.bindingType } : null;
327
- }
338
+ }
339
+ if (dataflow.mutations) {
340
+ for (const m of dataflow.mutations) {
341
+ m.binding = m.bindingType ? { type: m.bindingType } : null;
328
342
  }
329
343
  }
344
+ }
345
+
346
+ function patchNativeResult(r: any): ExtractorOutput {
347
+ // lineCount: napi(js_name) emits "lineCount"; older binaries may emit "line_count"
348
+ r.lineCount = r.lineCount ?? r.line_count ?? null;
349
+ r._lineCount = r.lineCount;
350
+
351
+ if (r.definitions) patchDefinitions(r.definitions);
352
+ if (r.imports) patchImports(r.imports);
353
+ patchTypeMap(r);
354
+ if (r.dataflow) patchDataflow(r.dataflow);
330
355
 
331
356
  return r;
332
357
  }
@@ -413,6 +438,48 @@ export const LANGUAGE_REGISTRY: LanguageRegistryEntry[] = [
413
438
  extractor: extractPHPSymbols,
414
439
  required: false,
415
440
  },
441
+ {
442
+ id: 'c',
443
+ extensions: ['.c', '.h'],
444
+ grammarFile: 'tree-sitter-c.wasm',
445
+ extractor: extractCSymbols,
446
+ required: false,
447
+ },
448
+ {
449
+ id: 'cpp',
450
+ extensions: ['.cpp', '.cc', '.cxx', '.hpp'],
451
+ grammarFile: 'tree-sitter-cpp.wasm',
452
+ extractor: extractCppSymbols,
453
+ required: false,
454
+ },
455
+ {
456
+ id: 'kotlin',
457
+ extensions: ['.kt', '.kts'],
458
+ grammarFile: 'tree-sitter-kotlin.wasm',
459
+ extractor: extractKotlinSymbols,
460
+ required: false,
461
+ },
462
+ {
463
+ id: 'swift',
464
+ extensions: ['.swift'],
465
+ grammarFile: 'tree-sitter-swift.wasm',
466
+ extractor: extractSwiftSymbols,
467
+ required: false,
468
+ },
469
+ {
470
+ id: 'scala',
471
+ extensions: ['.scala'],
472
+ grammarFile: 'tree-sitter-scala.wasm',
473
+ extractor: extractScalaSymbols,
474
+ required: false,
475
+ },
476
+ {
477
+ id: 'bash',
478
+ extensions: ['.sh', '.bash'],
479
+ grammarFile: 'tree-sitter-bash.wasm',
480
+ extractor: extractBashSymbols,
481
+ required: false,
482
+ },
416
483
  ];
417
484
 
418
485
  const _extToLang: Map<string, LanguageRegistryEntry> = new Map();
@@ -521,73 +588,48 @@ export async function parseFileAuto(
521
588
  return extracted ? extracted.symbols : null;
522
589
  }
523
590
 
524
- /**
525
- * Parse multiple files in bulk and return a Map<relPath, symbols>.
526
- */
527
- export async function parseFilesAuto(
528
- filePaths: string[],
529
- rootDir: string,
530
- opts: ParseEngineOpts = {},
531
- ): Promise<Map<string, ExtractorOutput>> {
532
- const { native } = resolveEngine(opts);
533
- const result = new Map<string, ExtractorOutput>();
591
+ /** Backfill typeMap via WASM for files missing type-map data from native engine. */
592
+ async function backfillTypeMapBatch(
593
+ needsTypeMap: { filePath: string; relPath: string }[],
594
+ result: Map<string, ExtractorOutput>,
595
+ ): Promise<void> {
596
+ const tsFiles = needsTypeMap.filter(({ filePath }) =>
597
+ TS_BACKFILL_EXTS.has(path.extname(filePath)),
598
+ );
599
+ if (tsFiles.length === 0) return;
534
600
 
535
- if (native) {
536
- const nativeResults = native.parseFiles(
537
- filePaths,
538
- rootDir,
539
- !!opts.dataflow,
540
- opts.ast !== false,
541
- );
542
- const needsTypeMap: { filePath: string; relPath: string }[] = [];
543
- for (const r of nativeResults) {
544
- if (!r) continue;
545
- const patched = patchNativeResult(r);
546
- const relPath = path.relative(rootDir, r.file).split(path.sep).join('/');
547
- result.set(relPath, patched);
548
- if (patched.typeMap.size === 0) {
549
- needsTypeMap.push({ filePath: r.file, relPath });
601
+ const parsers = await createParsers();
602
+ for (const { filePath, relPath } of tsFiles) {
603
+ let extracted: WasmExtractResult | null | undefined;
604
+ try {
605
+ const code = fs.readFileSync(filePath, 'utf-8');
606
+ extracted = wasmExtractSymbols(parsers, filePath, code);
607
+ if (extracted?.symbols && extracted.symbols.typeMap.size > 0) {
608
+ const symbols = result.get(relPath);
609
+ if (!symbols) continue;
610
+ symbols.typeMap = extracted.symbols.typeMap;
611
+ symbols._typeMapBackfilled = true;
550
612
  }
551
- }
552
- // Backfill typeMap via WASM for native binaries that predate the type-map feature
553
- if (needsTypeMap.length > 0) {
554
- // Only backfill for languages where WASM extraction can produce typeMap
555
- // (TS/TSX have type annotations; JS only has `new Expr()` which native already handles)
556
- const tsFiles = needsTypeMap.filter(({ filePath }) =>
557
- TS_BACKFILL_EXTS.has(path.extname(filePath)),
558
- );
559
- if (tsFiles.length > 0) {
560
- const parsers = await createParsers();
561
- for (const { filePath, relPath } of tsFiles) {
562
- let extracted: WasmExtractResult | null | undefined;
563
- try {
564
- const code = fs.readFileSync(filePath, 'utf-8');
565
- extracted = wasmExtractSymbols(parsers, filePath, code);
566
- if (extracted?.symbols && extracted.symbols.typeMap.size > 0) {
567
- const symbols = result.get(relPath);
568
- if (!symbols) continue;
569
- symbols.typeMap = extracted.symbols.typeMap;
570
- symbols._typeMapBackfilled = true;
571
- }
572
- } catch (e) {
573
- debug(`batchExtract: typeMap backfill failed: ${toErrorMessage(e)}`);
574
- } finally {
575
- // Free the WASM tree to prevent memory accumulation across repeated builds
576
- if (extracted?.tree && typeof extracted.tree.delete === 'function') {
577
- try {
578
- extracted.tree.delete();
579
- } catch (e) {
580
- debug(`batchExtract: WASM tree cleanup failed: ${toErrorMessage(e)}`);
581
- }
582
- }
583
- }
613
+ } catch (e) {
614
+ debug(`batchExtract: typeMap backfill failed: ${toErrorMessage(e)}`);
615
+ } finally {
616
+ if (extracted?.tree && typeof extracted.tree.delete === 'function') {
617
+ try {
618
+ extracted.tree.delete();
619
+ } catch (e) {
620
+ debug(`batchExtract: WASM tree cleanup failed: ${toErrorMessage(e)}`);
584
621
  }
585
622
  }
586
623
  }
587
- return result;
588
624
  }
625
+ }
589
626
 
590
- // WASM path
627
+ /** Parse files via WASM engine, returning a Map<relPath, symbols>. */
628
+ async function parseFilesWasm(
629
+ filePaths: string[],
630
+ rootDir: string,
631
+ ): Promise<Map<string, ExtractorOutput>> {
632
+ const result = new Map<string, ExtractorOutput>();
591
633
  const parsers = await createParsers();
592
634
  for (const filePath of filePaths) {
593
635
  let code: string;
@@ -609,6 +651,36 @@ export async function parseFilesAuto(
609
651
  return result;
610
652
  }
611
653
 
654
+ /**
655
+ * Parse multiple files in bulk and return a Map<relPath, symbols>.
656
+ */
657
+ export async function parseFilesAuto(
658
+ filePaths: string[],
659
+ rootDir: string,
660
+ opts: ParseEngineOpts = {},
661
+ ): Promise<Map<string, ExtractorOutput>> {
662
+ const { native } = resolveEngine(opts);
663
+
664
+ if (!native) return parseFilesWasm(filePaths, rootDir);
665
+
666
+ const result = new Map<string, ExtractorOutput>();
667
+ const nativeResults = native.parseFiles(filePaths, rootDir, !!opts.dataflow, opts.ast !== false);
668
+ const needsTypeMap: { filePath: string; relPath: string }[] = [];
669
+ for (const r of nativeResults) {
670
+ if (!r) continue;
671
+ const patched = patchNativeResult(r);
672
+ const relPath = path.relative(rootDir, r.file).split(path.sep).join('/');
673
+ result.set(relPath, patched);
674
+ if (patched.typeMap.size === 0) {
675
+ needsTypeMap.push({ filePath: r.file, relPath });
676
+ }
677
+ }
678
+ if (needsTypeMap.length > 0) {
679
+ await backfillTypeMapBatch(needsTypeMap, result);
680
+ }
681
+ return result;
682
+ }
683
+
612
684
  /**
613
685
  * Report which engine is active.
614
686
  */
@@ -11,113 +11,98 @@ interface SearchOpts extends SemanticSearchOpts {
11
11
  offset?: number;
12
12
  }
13
13
 
14
- export async function search(
15
- query: string,
16
- customDbPath: string | undefined,
17
- opts: SearchOpts = {},
18
- ): Promise<void> {
19
- const mode = opts.mode || 'hybrid';
14
+ const kindIcon = (kind: string): string =>
15
+ kind === 'function' ? 'f' : kind === 'class' ? '*' : 'o';
20
16
 
21
- const queries = query
22
- .split(';')
23
- .map((q) => q.trim())
24
- .filter((q) => q.length > 0);
25
-
26
- const kindIcon = (kind: string): string =>
27
- kind === 'function' ? 'f' : kind === 'class' ? '*' : 'o';
28
-
29
- // Keyword-only mode
30
- if (mode === 'keyword') {
31
- const singleQuery = queries.length === 1 ? queries[0]! : query;
32
- const data = ftsSearchData(singleQuery, customDbPath, opts);
33
- if (!data) {
34
- console.log('No FTS5 index found. Run `codegraph embed` to build the keyword index.');
35
- return;
36
- }
37
- if (opts.json) {
38
- console.log(JSON.stringify(data, null, 2));
39
- return;
40
- }
41
- console.log(`\nKeyword search: "${singleQuery}" (BM25)\n`);
42
- if (data.results.length === 0) {
43
- console.log(' No results found.');
44
- } else {
45
- for (const r of data.results) {
46
- console.log(
47
- ` BM25 ${r.bm25Score.toFixed(2)} ${kindIcon(r.kind)} ${r.name} -- ${r.file}:${r.line}`,
48
- );
49
- }
50
- }
51
- console.log(`\n ${data.results.length} results shown\n`);
17
+ function formatKeywordResults(
18
+ singleQuery: string,
19
+ customDbPath: string | undefined,
20
+ opts: SearchOpts,
21
+ ): void {
22
+ const data = ftsSearchData(singleQuery, customDbPath, opts);
23
+ if (!data) {
24
+ console.log('No FTS5 index found. Run `codegraph embed` to build the keyword index.');
52
25
  return;
53
26
  }
54
-
55
- // Semantic-only mode
56
- if (mode === 'semantic') {
57
- if (queries.length <= 1) {
58
- const singleQuery = queries[0] || query;
59
- const data = await searchData(singleQuery, customDbPath, opts);
60
- if (!data) return;
61
- if (opts.json) {
62
- console.log(JSON.stringify(data, null, 2));
63
- return;
64
- }
65
- console.log(`\nSemantic search: "${singleQuery}"\n`);
66
- if (data.results.length === 0) {
67
- console.log(' No results above threshold.');
68
- } else {
69
- for (const r of data.results) {
70
- const bar = '#'.repeat(Math.round(r.similarity * 20));
71
- console.log(` ${(r.similarity * 100).toFixed(1)}% ${bar}`);
72
- console.log(` ${kindIcon(r.kind)} ${r.name} -- ${r.file}:${r.line}`);
73
- }
74
- }
75
- console.log(`\n ${data.results.length} results shown\n`);
76
- } else {
77
- const data = await multiSearchData(queries, customDbPath, opts);
78
- if (!data) return;
79
- if (opts.json) {
80
- console.log(JSON.stringify(data, null, 2));
81
- return;
82
- }
83
- console.log(`\nMulti-query semantic search (RRF, k=${opts.rrfK || 60}):`);
84
- for (let i = 0; i < queries.length; i++) console.log(` [${i + 1}] "${queries[i]}"`);
85
- console.log();
86
- if (data.results.length === 0) {
87
- console.log(' No results above threshold.');
88
- } else {
89
- for (const r of data.results) {
90
- console.log(
91
- ` RRF ${r.rrf.toFixed(4)} ${kindIcon(r.kind)} ${r.name} -- ${r.file}:${r.line}`,
92
- );
93
- for (const qs of r.queryScores) {
94
- const bar = '#'.repeat(Math.round(qs.similarity * 20));
95
- console.log(
96
- ` [${queries.indexOf(qs.query) + 1}] ${(qs.similarity * 100).toFixed(1)}% ${bar} (rank ${qs.rank})`,
97
- );
98
- }
99
- }
100
- }
101
- console.log(`\n ${data.results.length} results shown\n`);
102
- }
27
+ if (opts.json) {
28
+ console.log(JSON.stringify(data, null, 2));
103
29
  return;
104
30
  }
31
+ console.log(`\nKeyword search: "${singleQuery}" (BM25)\n`);
32
+ if (data.results.length === 0) {
33
+ console.log(' No results found.');
34
+ } else {
35
+ for (const r of data.results) {
36
+ console.log(
37
+ ` BM25 ${r.bm25Score.toFixed(2)} ${kindIcon(r.kind)} ${r.name} -- ${r.file}:${r.line}`,
38
+ );
39
+ }
40
+ }
41
+ console.log(`\n ${data.results.length} results shown\n`);
42
+ }
105
43
 
106
- // Hybrid mode (default)
107
- const data = await hybridSearchData(query, customDbPath, opts);
108
-
109
- if (!data) {
110
- warn(
111
- 'FTS5 index not found using semantic search only. Re-run `codegraph embed` to enable hybrid mode.',
112
- );
113
- return search(query, customDbPath, { ...opts, mode: 'semantic' });
44
+ async function formatSemanticSingle(
45
+ singleQuery: string,
46
+ customDbPath: string | undefined,
47
+ opts: SearchOpts,
48
+ ): Promise<void> {
49
+ const data = await searchData(singleQuery, customDbPath, opts);
50
+ if (!data) return;
51
+ if (opts.json) {
52
+ console.log(JSON.stringify(data, null, 2));
53
+ return;
54
+ }
55
+ console.log(`\nSemantic search: "${singleQuery}"\n`);
56
+ if (data.results.length === 0) {
57
+ console.log(' No results above threshold.');
58
+ } else {
59
+ for (const r of data.results) {
60
+ const bar = '#'.repeat(Math.round(r.similarity * 20));
61
+ console.log(` ${(r.similarity * 100).toFixed(1)}% ${bar}`);
62
+ console.log(` ${kindIcon(r.kind)} ${r.name} -- ${r.file}:${r.line}`);
63
+ }
114
64
  }
65
+ console.log(`\n ${data.results.length} results shown\n`);
66
+ }
115
67
 
68
+ async function formatSemanticMulti(
69
+ queries: string[],
70
+ customDbPath: string | undefined,
71
+ opts: SearchOpts,
72
+ ): Promise<void> {
73
+ const data = await multiSearchData(queries, customDbPath, opts);
74
+ if (!data) return;
116
75
  if (opts.json) {
117
76
  console.log(JSON.stringify(data, null, 2));
118
77
  return;
119
78
  }
79
+ console.log(`\nMulti-query semantic search (RRF, k=${opts.rrfK || 60}):`);
80
+ for (let i = 0; i < queries.length; i++) console.log(` [${i + 1}] "${queries[i]}"`);
81
+ console.log();
82
+ if (data.results.length === 0) {
83
+ console.log(' No results above threshold.');
84
+ } else {
85
+ for (const r of data.results) {
86
+ console.log(
87
+ ` RRF ${r.rrf.toFixed(4)} ${kindIcon(r.kind)} ${r.name} -- ${r.file}:${r.line}`,
88
+ );
89
+ for (const qs of r.queryScores) {
90
+ const bar = '#'.repeat(Math.round(qs.similarity * 20));
91
+ console.log(
92
+ ` [${queries.indexOf(qs.query) + 1}] ${(qs.similarity * 100).toFixed(1)}% ${bar} (rank ${qs.rank})`,
93
+ );
94
+ }
95
+ }
96
+ }
97
+ console.log(`\n ${data.results.length} results shown\n`);
98
+ }
120
99
 
100
+ function formatHybridResults(
101
+ queries: string[],
102
+ query: string,
103
+ data: { results: any[] },
104
+ opts: SearchOpts,
105
+ ): void {
121
106
  const rrfK = opts.rrfK || 60;
122
107
  if (queries.length <= 1) {
123
108
  const singleQuery = queries[0] || query;
@@ -150,3 +135,45 @@ export async function search(
150
135
 
151
136
  console.log(`\n ${data.results.length} results shown\n`);
152
137
  }
138
+
139
+ export async function search(
140
+ query: string,
141
+ customDbPath: string | undefined,
142
+ opts: SearchOpts = {},
143
+ ): Promise<void> {
144
+ const mode = opts.mode || 'hybrid';
145
+
146
+ const queries = query
147
+ .split(';')
148
+ .map((q) => q.trim())
149
+ .filter((q) => q.length > 0);
150
+
151
+ if (mode === 'keyword') {
152
+ const singleQuery = queries.length === 1 ? queries[0]! : query;
153
+ return formatKeywordResults(singleQuery, customDbPath, opts);
154
+ }
155
+
156
+ if (mode === 'semantic') {
157
+ if (queries.length <= 1) {
158
+ return formatSemanticSingle(queries[0] || query, customDbPath, opts);
159
+ }
160
+ return formatSemanticMulti(queries, customDbPath, opts);
161
+ }
162
+
163
+ // Hybrid mode (default)
164
+ const data = await hybridSearchData(query, customDbPath, opts);
165
+
166
+ if (!data) {
167
+ warn(
168
+ 'FTS5 index not found — using semantic search only. Re-run `codegraph embed` to enable hybrid mode.',
169
+ );
170
+ return search(query, customDbPath, { ...opts, mode: 'semantic' });
171
+ }
172
+
173
+ if (opts.json) {
174
+ console.log(JSON.stringify(data, null, 2));
175
+ return;
176
+ }
177
+
178
+ formatHybridResults(queries, query, data, opts);
179
+ }
@@ -0,0 +1,97 @@
1
+ import type { Call, ExtractorOutput, TreeSitterNode, TreeSitterTree } from '../types.js';
2
+ import { nodeEndLine } from './helpers.js';
3
+
4
+ /**
5
+ * Extract symbols from Bash/Shell files.
6
+ */
7
+ export function extractBashSymbols(tree: TreeSitterTree, _filePath: string): ExtractorOutput {
8
+ const ctx: ExtractorOutput = {
9
+ definitions: [],
10
+ calls: [],
11
+ imports: [],
12
+ classes: [],
13
+ exports: [],
14
+ typeMap: new Map(),
15
+ };
16
+
17
+ walkBashNode(tree.rootNode, ctx);
18
+ return ctx;
19
+ }
20
+
21
+ function walkBashNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
22
+ switch (node.type) {
23
+ case 'function_definition':
24
+ handleBashFunctionDef(node, ctx);
25
+ break;
26
+ case 'command':
27
+ handleBashCommand(node, ctx);
28
+ break;
29
+ }
30
+
31
+ for (let i = 0; i < node.childCount; i++) {
32
+ const child = node.child(i);
33
+ if (child) walkBashNode(child, ctx);
34
+ }
35
+ }
36
+
37
+ // ── Walk-path per-node-type handlers ────────────────────────────────────────
38
+
39
+ function handleBashFunctionDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
40
+ const nameNode = node.childForFieldName('name');
41
+ if (!nameNode) return;
42
+ ctx.definitions.push({
43
+ name: nameNode.text,
44
+ kind: 'function',
45
+ line: node.startPosition.row + 1,
46
+ endLine: nodeEndLine(node),
47
+ });
48
+ }
49
+
50
+ function handleBashCommand(node: TreeSitterNode, ctx: ExtractorOutput): void {
51
+ // First child is command_name
52
+ let commandNameNode: TreeSitterNode | null = null;
53
+ for (let i = 0; i < node.childCount; i++) {
54
+ const child = node.child(i);
55
+ if (child && child.type === 'command_name') {
56
+ commandNameNode = child;
57
+ break;
58
+ }
59
+ }
60
+ if (!commandNameNode) return;
61
+
62
+ const cmdText = commandNameNode.text;
63
+
64
+ // "source" or "." commands are imports
65
+ if (cmdText === 'source' || cmdText === '.') {
66
+ // Second argument is the source path
67
+ let argNode: TreeSitterNode | null = null;
68
+ let foundCmd = false;
69
+ for (let i = 0; i < node.childCount; i++) {
70
+ const child = node.child(i);
71
+ if (!child) continue;
72
+ if (child.type === 'command_name') {
73
+ foundCmd = true;
74
+ continue;
75
+ }
76
+ if (foundCmd && child.type !== 'command_name') {
77
+ argNode = child;
78
+ break;
79
+ }
80
+ }
81
+ if (argNode) {
82
+ const source = argNode.text;
83
+ const lastName = source.split('/').pop() ?? source;
84
+ ctx.imports.push({
85
+ source,
86
+ names: [lastName],
87
+ line: node.startPosition.row + 1,
88
+ bashSource: true,
89
+ });
90
+ }
91
+ return;
92
+ }
93
+
94
+ // Regular command call
95
+ const call: Call = { name: cmdText, line: node.startPosition.row + 1 };
96
+ ctx.calls.push(call);
97
+ }