trail-code 0.0.0-stage → 0.28.0-semantic.3

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 (593) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +567 -2
  3. package/dist/ai/crux.d.ts +88 -0
  4. package/dist/ai/crux.d.ts.map +1 -0
  5. package/dist/ai/crux.js +219 -0
  6. package/dist/ai/crux.js.map +1 -0
  7. package/dist/ai/failure.d.ts +60 -0
  8. package/dist/ai/failure.d.ts.map +1 -0
  9. package/dist/ai/failure.js +86 -0
  10. package/dist/ai/failure.js.map +1 -0
  11. package/dist/ai/llm/anthropic.d.ts +37 -0
  12. package/dist/ai/llm/anthropic.d.ts.map +1 -0
  13. package/dist/ai/llm/anthropic.js +144 -0
  14. package/dist/ai/llm/anthropic.js.map +1 -0
  15. package/dist/ai/llm/factory.d.ts +28 -0
  16. package/dist/ai/llm/factory.d.ts.map +1 -0
  17. package/dist/ai/llm/factory.js +36 -0
  18. package/dist/ai/llm/factory.js.map +1 -0
  19. package/dist/ai/llm/litellm.d.ts +34 -0
  20. package/dist/ai/llm/litellm.d.ts.map +1 -0
  21. package/dist/ai/llm/litellm.js +45 -0
  22. package/dist/ai/llm/litellm.js.map +1 -0
  23. package/dist/ai/llm/openai.d.ts +145 -0
  24. package/dist/ai/llm/openai.d.ts.map +1 -0
  25. package/dist/ai/llm/openai.js +629 -0
  26. package/dist/ai/llm/openai.js.map +1 -0
  27. package/dist/ai/llm/orcarouter.d.ts +34 -0
  28. package/dist/ai/llm/orcarouter.d.ts.map +1 -0
  29. package/dist/ai/llm/orcarouter.js +45 -0
  30. package/dist/ai/llm/orcarouter.js.map +1 -0
  31. package/dist/ai/llm/recover-tool.d.ts +21 -0
  32. package/dist/ai/llm/recover-tool.d.ts.map +1 -0
  33. package/dist/ai/llm/recover-tool.js +109 -0
  34. package/dist/ai/llm/recover-tool.js.map +1 -0
  35. package/dist/ai/llm/types.d.ts +122 -0
  36. package/dist/ai/llm/types.d.ts.map +1 -0
  37. package/dist/ai/llm/types.js +33 -0
  38. package/dist/ai/llm/types.js.map +1 -0
  39. package/dist/ai/providers.d.ts +59 -0
  40. package/dist/ai/providers.d.ts.map +1 -0
  41. package/dist/ai/providers.js +54 -0
  42. package/dist/ai/providers.js.map +1 -0
  43. package/dist/ai/summarize.d.ts +25 -0
  44. package/dist/ai/summarize.d.ts.map +1 -0
  45. package/dist/ai/summarize.js +34 -0
  46. package/dist/ai/summarize.js.map +1 -0
  47. package/dist/ai/synthesize.d.ts +40 -0
  48. package/dist/ai/synthesize.d.ts.map +1 -0
  49. package/dist/ai/synthesize.js +230 -0
  50. package/dist/ai/synthesize.js.map +1 -0
  51. package/dist/ask/ask.d.ts +199 -0
  52. package/dist/ask/ask.d.ts.map +1 -0
  53. package/dist/ask/ask.js +1291 -0
  54. package/dist/ask/ask.js.map +1 -0
  55. package/dist/ask/file-rank.d.ts +72 -0
  56. package/dist/ask/file-rank.d.ts.map +1 -0
  57. package/dist/ask/file-rank.js +160 -0
  58. package/dist/ask/file-rank.js.map +1 -0
  59. package/dist/ask/file-selection.d.ts +22 -0
  60. package/dist/ask/file-selection.d.ts.map +1 -0
  61. package/dist/ask/file-selection.js +66 -0
  62. package/dist/ask/file-selection.js.map +1 -0
  63. package/dist/ask/fuse.d.ts +195 -0
  64. package/dist/ask/fuse.d.ts.map +1 -0
  65. package/dist/ask/fuse.js +344 -0
  66. package/dist/ask/fuse.js.map +1 -0
  67. package/dist/ask/graphrank.d.ts +61 -0
  68. package/dist/ask/graphrank.d.ts.map +1 -0
  69. package/dist/ask/graphrank.js +130 -0
  70. package/dist/ask/graphrank.js.map +1 -0
  71. package/dist/ask/index-file.d.ts +44 -0
  72. package/dist/ask/index-file.d.ts.map +1 -0
  73. package/dist/ask/index-file.js +128 -0
  74. package/dist/ask/index-file.js.map +1 -0
  75. package/dist/ask/semantic.d.ts +42 -0
  76. package/dist/ask/semantic.d.ts.map +1 -0
  77. package/dist/ask/semantic.js +293 -0
  78. package/dist/ask/semantic.js.map +1 -0
  79. package/dist/bin/graft.d.ts +3 -0
  80. package/dist/bin/graft.d.ts.map +1 -0
  81. package/dist/bin/graft.js +14 -0
  82. package/dist/bin/graft.js.map +1 -0
  83. package/dist/bin/trail.d.ts +3 -0
  84. package/dist/bin/trail.d.ts.map +1 -0
  85. package/dist/bin/trail.js +13 -0
  86. package/dist/bin/trail.js.map +1 -0
  87. package/dist/blast/blast-cli.d.ts +35 -0
  88. package/dist/blast/blast-cli.d.ts.map +1 -0
  89. package/dist/blast/blast-cli.js +155 -0
  90. package/dist/blast/blast-cli.js.map +1 -0
  91. package/dist/blast/blast.d.ts +128 -0
  92. package/dist/blast/blast.d.ts.map +1 -0
  93. package/dist/blast/blast.js +383 -0
  94. package/dist/blast/blast.js.map +1 -0
  95. package/dist/blast/diff.d.ts +53 -0
  96. package/dist/blast/diff.d.ts.map +1 -0
  97. package/dist/blast/diff.js +193 -0
  98. package/dist/blast/diff.js.map +1 -0
  99. package/dist/blast/evidence.d.ts +51 -0
  100. package/dist/blast/evidence.d.ts.map +1 -0
  101. package/dist/blast/evidence.js +162 -0
  102. package/dist/blast/evidence.js.map +1 -0
  103. package/dist/blast/modules.d.ts +33 -0
  104. package/dist/blast/modules.d.ts.map +1 -0
  105. package/dist/blast/modules.js +83 -0
  106. package/dist/blast/modules.js.map +1 -0
  107. package/dist/blast/name.d.ts +74 -0
  108. package/dist/blast/name.d.ts.map +1 -0
  109. package/dist/blast/name.js +269 -0
  110. package/dist/blast/name.js.map +1 -0
  111. package/dist/blast/owners.d.ts +85 -0
  112. package/dist/blast/owners.d.ts.map +1 -0
  113. package/dist/blast/owners.js +272 -0
  114. package/dist/blast/owners.js.map +1 -0
  115. package/dist/blast/render.d.ts +46 -0
  116. package/dist/blast/render.d.ts.map +1 -0
  117. package/dist/blast/render.js +422 -0
  118. package/dist/blast/render.js.map +1 -0
  119. package/dist/blast/viz.d.ts +37 -0
  120. package/dist/blast/viz.d.ts.map +1 -0
  121. package/dist/blast/viz.js +180 -0
  122. package/dist/blast/viz.js.map +1 -0
  123. package/dist/brand.d.ts +111 -0
  124. package/dist/brand.d.ts.map +1 -0
  125. package/dist/brand.js +282 -0
  126. package/dist/brand.js.map +1 -0
  127. package/dist/claude/build-progress.d.ts +30 -0
  128. package/dist/claude/build-progress.d.ts.map +1 -0
  129. package/dist/claude/build-progress.js +81 -0
  130. package/dist/claude/build-progress.js.map +1 -0
  131. package/dist/claude/hermes-skill-template.d.ts +16 -0
  132. package/dist/claude/hermes-skill-template.d.ts.map +1 -0
  133. package/dist/claude/hermes-skill-template.js +154 -0
  134. package/dist/claude/hermes-skill-template.js.map +1 -0
  135. package/dist/claude/init.d.ts +65 -0
  136. package/dist/claude/init.d.ts.map +1 -0
  137. package/dist/claude/init.js +258 -0
  138. package/dist/claude/init.js.map +1 -0
  139. package/dist/claude/paths.d.ts +9 -0
  140. package/dist/claude/paths.d.ts.map +1 -0
  141. package/dist/claude/paths.js +22 -0
  142. package/dist/claude/paths.js.map +1 -0
  143. package/dist/claude/session-metrics.d.ts +73 -0
  144. package/dist/claude/session-metrics.d.ts.map +1 -0
  145. package/dist/claude/session-metrics.js +209 -0
  146. package/dist/claude/session-metrics.js.map +1 -0
  147. package/dist/claude/settings-merge.d.ts +76 -0
  148. package/dist/claude/settings-merge.d.ts.map +1 -0
  149. package/dist/claude/settings-merge.js +283 -0
  150. package/dist/claude/settings-merge.js.map +1 -0
  151. package/dist/claude/skill-template.d.ts +13 -0
  152. package/dist/claude/skill-template.d.ts.map +1 -0
  153. package/dist/claude/skill-template.js +290 -0
  154. package/dist/claude/skill-template.js.map +1 -0
  155. package/dist/claude/state.d.ts +85 -0
  156. package/dist/claude/state.d.ts.map +1 -0
  157. package/dist/claude/state.js +88 -0
  158. package/dist/claude/state.js.map +1 -0
  159. package/dist/claude/stats.d.ts +5 -0
  160. package/dist/claude/stats.d.ts.map +1 -0
  161. package/dist/claude/stats.js +24 -0
  162. package/dist/claude/stats.js.map +1 -0
  163. package/dist/cli-epilogue.d.ts +12 -0
  164. package/dist/cli-epilogue.d.ts.map +1 -0
  165. package/dist/cli-epilogue.js +72 -0
  166. package/dist/cli-epilogue.js.map +1 -0
  167. package/dist/cli-meta.d.ts +82 -0
  168. package/dist/cli-meta.d.ts.map +1 -0
  169. package/dist/cli-meta.js +238 -0
  170. package/dist/cli-meta.js.map +1 -0
  171. package/dist/cli-picker.d.ts +139 -0
  172. package/dist/cli-picker.d.ts.map +1 -0
  173. package/dist/cli-picker.js +439 -0
  174. package/dist/cli-picker.js.map +1 -0
  175. package/dist/cli.d.ts +3 -0
  176. package/dist/cli.d.ts.map +1 -0
  177. package/dist/cli.js +1638 -0
  178. package/dist/cli.js.map +1 -0
  179. package/dist/context/batches.d.ts +29 -0
  180. package/dist/context/batches.d.ts.map +1 -0
  181. package/dist/context/batches.js +130 -0
  182. package/dist/context/batches.js.map +1 -0
  183. package/dist/context/build.d.ts +57 -0
  184. package/dist/context/build.d.ts.map +1 -0
  185. package/dist/context/build.js +396 -0
  186. package/dist/context/build.js.map +1 -0
  187. package/dist/context/check.d.ts +42 -0
  188. package/dist/context/check.d.ts.map +1 -0
  189. package/dist/context/check.js +162 -0
  190. package/dist/context/check.js.map +1 -0
  191. package/dist/context/map-dir.d.ts +13 -0
  192. package/dist/context/map-dir.d.ts.map +1 -0
  193. package/dist/context/map-dir.js +81 -0
  194. package/dist/context/map-dir.js.map +1 -0
  195. package/dist/context/node-file.d.ts +109 -0
  196. package/dist/context/node-file.d.ts.map +1 -0
  197. package/dist/context/node-file.js +390 -0
  198. package/dist/context/node-file.js.map +1 -0
  199. package/dist/context/price.d.ts +49 -0
  200. package/dist/context/price.d.ts.map +1 -0
  201. package/dist/context/price.js +83 -0
  202. package/dist/context/price.js.map +1 -0
  203. package/dist/context/savings.d.ts +67 -0
  204. package/dist/context/savings.d.ts.map +1 -0
  205. package/dist/context/savings.js +125 -0
  206. package/dist/context/savings.js.map +1 -0
  207. package/dist/engine.d.ts +85 -0
  208. package/dist/engine.d.ts.map +1 -0
  209. package/dist/engine.js +122 -0
  210. package/dist/engine.js.map +1 -0
  211. package/dist/graph/bindings.d.ts +64 -0
  212. package/dist/graph/bindings.d.ts.map +1 -0
  213. package/dist/graph/bindings.js +713 -0
  214. package/dist/graph/bindings.js.map +1 -0
  215. package/dist/graph/build-run.d.ts +25 -0
  216. package/dist/graph/build-run.d.ts.map +1 -0
  217. package/dist/graph/build-run.js +121 -0
  218. package/dist/graph/build-run.js.map +1 -0
  219. package/dist/graph/build.d.ts +88 -0
  220. package/dist/graph/build.d.ts.map +1 -0
  221. package/dist/graph/build.js +386 -0
  222. package/dist/graph/build.js.map +1 -0
  223. package/dist/graph/cards.d.ts +46 -0
  224. package/dist/graph/cards.d.ts.map +1 -0
  225. package/dist/graph/cards.js +280 -0
  226. package/dist/graph/cards.js.map +1 -0
  227. package/dist/graph/check.d.ts +25 -0
  228. package/dist/graph/check.d.ts.map +1 -0
  229. package/dist/graph/check.js +192 -0
  230. package/dist/graph/check.js.map +1 -0
  231. package/dist/graph/container.d.ts +63 -0
  232. package/dist/graph/container.d.ts.map +1 -0
  233. package/dist/graph/container.js +191 -0
  234. package/dist/graph/container.js.map +1 -0
  235. package/dist/graph/depth-parser.d.ts +61 -0
  236. package/dist/graph/depth-parser.d.ts.map +1 -0
  237. package/dist/graph/depth-parser.js +153 -0
  238. package/dist/graph/depth-parser.js.map +1 -0
  239. package/dist/graph/enrich.d.ts +62 -0
  240. package/dist/graph/enrich.d.ts.map +1 -0
  241. package/dist/graph/enrich.js +258 -0
  242. package/dist/graph/enrich.js.map +1 -0
  243. package/dist/graph/extract-cache.d.ts +101 -0
  244. package/dist/graph/extract-cache.d.ts.map +1 -0
  245. package/dist/graph/extract-cache.js +199 -0
  246. package/dist/graph/extract-cache.js.map +1 -0
  247. package/dist/graph/extract.d.ts +85 -0
  248. package/dist/graph/extract.d.ts.map +1 -0
  249. package/dist/graph/extract.js +2377 -0
  250. package/dist/graph/extract.js.map +1 -0
  251. package/dist/graph/fingerprint.d.ts +76 -0
  252. package/dist/graph/fingerprint.d.ts.map +1 -0
  253. package/dist/graph/fingerprint.js +160 -0
  254. package/dist/graph/fingerprint.js.map +1 -0
  255. package/dist/graph/generic.d.ts +74 -0
  256. package/dist/graph/generic.d.ts.map +1 -0
  257. package/dist/graph/generic.js +773 -0
  258. package/dist/graph/generic.js.map +1 -0
  259. package/dist/graph/invariants.d.ts +33 -0
  260. package/dist/graph/invariants.d.ts.map +1 -0
  261. package/dist/graph/invariants.js +52 -0
  262. package/dist/graph/invariants.js.map +1 -0
  263. package/dist/graph/load.d.ts +29 -0
  264. package/dist/graph/load.d.ts.map +1 -0
  265. package/dist/graph/load.js +97 -0
  266. package/dist/graph/load.js.map +1 -0
  267. package/dist/graph/lsp/client.d.ts +61 -0
  268. package/dist/graph/lsp/client.d.ts.map +1 -0
  269. package/dist/graph/lsp/client.js +217 -0
  270. package/dist/graph/lsp/client.js.map +1 -0
  271. package/dist/graph/lsp/enrich.d.ts +11 -0
  272. package/dist/graph/lsp/enrich.d.ts.map +1 -0
  273. package/dist/graph/lsp/enrich.js +163 -0
  274. package/dist/graph/lsp/enrich.js.map +1 -0
  275. package/dist/graph/lsp/registry.d.ts +22 -0
  276. package/dist/graph/lsp/registry.d.ts.map +1 -0
  277. package/dist/graph/lsp/registry.js +71 -0
  278. package/dist/graph/lsp/registry.js.map +1 -0
  279. package/dist/graph/map.d.ts +107 -0
  280. package/dist/graph/map.d.ts.map +1 -0
  281. package/dist/graph/map.js +239 -0
  282. package/dist/graph/map.js.map +1 -0
  283. package/dist/graph/markdown.d.ts +22 -0
  284. package/dist/graph/markdown.d.ts.map +1 -0
  285. package/dist/graph/markdown.js +143 -0
  286. package/dist/graph/markdown.js.map +1 -0
  287. package/dist/graph/native.d.ts +46 -0
  288. package/dist/graph/native.d.ts.map +1 -0
  289. package/dist/graph/native.js +169 -0
  290. package/dist/graph/native.js.map +1 -0
  291. package/dist/graph/queries/c.scm +13 -0
  292. package/dist/graph/queries/c_sharp.scm +19 -0
  293. package/dist/graph/queries/clojure.scm +58 -0
  294. package/dist/graph/queries/cpp.scm +23 -0
  295. package/dist/graph/queries/dart.scm +42 -0
  296. package/dist/graph/queries/elixir.scm +54 -0
  297. package/dist/graph/queries/gleam.scm +51 -0
  298. package/dist/graph/queries/java.scm +20 -0
  299. package/dist/graph/queries/kotlin.scm +43 -0
  300. package/dist/graph/queries/lua.scm +36 -0
  301. package/dist/graph/queries/nix.scm +32 -0
  302. package/dist/graph/queries/php.scm +40 -0
  303. package/dist/graph/queries/ruby.scm +65 -0
  304. package/dist/graph/queries/rust.scm +41 -0
  305. package/dist/graph/queries/scala.scm +66 -0
  306. package/dist/graph/queries/solidity.scm +43 -0
  307. package/dist/graph/queries/swift.scm +51 -0
  308. package/dist/graph/refresh.d.ts +106 -0
  309. package/dist/graph/refresh.d.ts.map +1 -0
  310. package/dist/graph/refresh.js +430 -0
  311. package/dist/graph/refresh.js.map +1 -0
  312. package/dist/graph/relations.d.ts +14 -0
  313. package/dist/graph/relations.d.ts.map +1 -0
  314. package/dist/graph/relations.js +8 -0
  315. package/dist/graph/relations.js.map +1 -0
  316. package/dist/graph/resolve.d.ts +17 -0
  317. package/dist/graph/resolve.d.ts.map +1 -0
  318. package/dist/graph/resolve.js +716 -0
  319. package/dist/graph/resolve.js.map +1 -0
  320. package/dist/graph/root.d.ts +11 -0
  321. package/dist/graph/root.d.ts.map +1 -0
  322. package/dist/graph/root.js +40 -0
  323. package/dist/graph/root.js.map +1 -0
  324. package/dist/graph/scopes.d.ts +40 -0
  325. package/dist/graph/scopes.d.ts.map +1 -0
  326. package/dist/graph/scopes.js +383 -0
  327. package/dist/graph/scopes.js.map +1 -0
  328. package/dist/graph/seed.d.ts +38 -0
  329. package/dist/graph/seed.d.ts.map +1 -0
  330. package/dist/graph/seed.js +229 -0
  331. package/dist/graph/seed.js.map +1 -0
  332. package/dist/graph/source-files.d.ts +46 -0
  333. package/dist/graph/source-files.d.ts.map +1 -0
  334. package/dist/graph/source-files.js +118 -0
  335. package/dist/graph/source-files.js.map +1 -0
  336. package/dist/graph/traverse-cli.d.ts +55 -0
  337. package/dist/graph/traverse-cli.d.ts.map +1 -0
  338. package/dist/graph/traverse-cli.js +189 -0
  339. package/dist/graph/traverse-cli.js.map +1 -0
  340. package/dist/graph/traverse.d.ts +112 -0
  341. package/dist/graph/traverse.d.ts.map +1 -0
  342. package/dist/graph/traverse.js +192 -0
  343. package/dist/graph/traverse.js.map +1 -0
  344. package/dist/graph/types.d.ts +78 -0
  345. package/dist/graph/types.d.ts.map +1 -0
  346. package/dist/graph/types.js +14 -0
  347. package/dist/graph/types.js.map +1 -0
  348. package/dist/graph/wasm-runtime.d.ts +19 -0
  349. package/dist/graph/wasm-runtime.d.ts.map +1 -0
  350. package/dist/graph/wasm-runtime.js +38 -0
  351. package/dist/graph/wasm-runtime.js.map +1 -0
  352. package/dist/graph/workspace-cli.d.ts +56 -0
  353. package/dist/graph/workspace-cli.d.ts.map +1 -0
  354. package/dist/graph/workspace-cli.js +111 -0
  355. package/dist/graph/workspace-cli.js.map +1 -0
  356. package/dist/graph/workspace.d.ts +172 -0
  357. package/dist/graph/workspace.d.ts.map +1 -0
  358. package/dist/graph/workspace.js +714 -0
  359. package/dist/graph/workspace.js.map +1 -0
  360. package/dist/graph/write.d.ts +13 -0
  361. package/dist/graph/write.d.ts.map +1 -0
  362. package/dist/graph/write.js +86 -0
  363. package/dist/graph/write.js.map +1 -0
  364. package/dist/hosts/antigravity.d.ts +14 -0
  365. package/dist/hosts/antigravity.d.ts.map +1 -0
  366. package/dist/hosts/antigravity.js +59 -0
  367. package/dist/hosts/antigravity.js.map +1 -0
  368. package/dist/hosts/auto.d.ts +37 -0
  369. package/dist/hosts/auto.d.ts.map +1 -0
  370. package/dist/hosts/auto.js +141 -0
  371. package/dist/hosts/auto.js.map +1 -0
  372. package/dist/hosts/claude-global.d.ts +72 -0
  373. package/dist/hosts/claude-global.d.ts.map +1 -0
  374. package/dist/hosts/claude-global.js +230 -0
  375. package/dist/hosts/claude-global.js.map +1 -0
  376. package/dist/hosts/config-write.d.ts +36 -0
  377. package/dist/hosts/config-write.d.ts.map +1 -0
  378. package/dist/hosts/config-write.js +64 -0
  379. package/dist/hosts/config-write.js.map +1 -0
  380. package/dist/hosts/init.d.ts +28 -0
  381. package/dist/hosts/init.d.ts.map +1 -0
  382. package/dist/hosts/init.js +89 -0
  383. package/dist/hosts/init.js.map +1 -0
  384. package/dist/hosts/instructions.d.ts +14 -0
  385. package/dist/hosts/instructions.d.ts.map +1 -0
  386. package/dist/hosts/instructions.js +95 -0
  387. package/dist/hosts/instructions.js.map +1 -0
  388. package/dist/hosts/mcp-config.d.ts +78 -0
  389. package/dist/hosts/mcp-config.d.ts.map +1 -0
  390. package/dist/hosts/mcp-config.js +251 -0
  391. package/dist/hosts/mcp-config.js.map +1 -0
  392. package/dist/hosts/plan.d.ts +32 -0
  393. package/dist/hosts/plan.d.ts.map +1 -0
  394. package/dist/hosts/plan.js +73 -0
  395. package/dist/hosts/plan.js.map +1 -0
  396. package/dist/hosts/registry.d.ts +41 -0
  397. package/dist/hosts/registry.d.ts.map +1 -0
  398. package/dist/hosts/registry.js +164 -0
  399. package/dist/hosts/registry.js.map +1 -0
  400. package/dist/hosts/retract.d.ts +47 -0
  401. package/dist/hosts/retract.d.ts.map +1 -0
  402. package/dist/hosts/retract.js +520 -0
  403. package/dist/hosts/retract.js.map +1 -0
  404. package/dist/hosts/sections.d.ts +47 -0
  405. package/dist/hosts/sections.d.ts.map +1 -0
  406. package/dist/hosts/sections.js +127 -0
  407. package/dist/hosts/sections.js.map +1 -0
  408. package/dist/index.d.ts +41 -0
  409. package/dist/index.d.ts.map +1 -0
  410. package/dist/index.js +32 -0
  411. package/dist/index.js.map +1 -0
  412. package/dist/ingest/fs.d.ts +79 -0
  413. package/dist/ingest/fs.d.ts.map +1 -0
  414. package/dist/ingest/fs.js +306 -0
  415. package/dist/ingest/fs.js.map +1 -0
  416. package/dist/init-guard.d.ts +16 -0
  417. package/dist/init-guard.d.ts.map +1 -0
  418. package/dist/init-guard.js +27 -0
  419. package/dist/init-guard.js.map +1 -0
  420. package/dist/mcp/instructions.d.ts +5 -0
  421. package/dist/mcp/instructions.d.ts.map +1 -0
  422. package/dist/mcp/instructions.js +64 -0
  423. package/dist/mcp/instructions.js.map +1 -0
  424. package/dist/mcp/kept-listing.d.ts +27 -0
  425. package/dist/mcp/kept-listing.d.ts.map +1 -0
  426. package/dist/mcp/kept-listing.js +63 -0
  427. package/dist/mcp/kept-listing.js.map +1 -0
  428. package/dist/mcp/server.d.ts +4 -0
  429. package/dist/mcp/server.d.ts.map +1 -0
  430. package/dist/mcp/server.js +147 -0
  431. package/dist/mcp/server.js.map +1 -0
  432. package/dist/mcp/tool-names.d.ts +27 -0
  433. package/dist/mcp/tool-names.d.ts.map +1 -0
  434. package/dist/mcp/tool-names.js +47 -0
  435. package/dist/mcp/tool-names.js.map +1 -0
  436. package/dist/mcp/tools.d.ts +22 -0
  437. package/dist/mcp/tools.d.ts.map +1 -0
  438. package/dist/mcp/tools.js +440 -0
  439. package/dist/mcp/tools.js.map +1 -0
  440. package/dist/node-version.d.ts +15 -0
  441. package/dist/node-version.d.ts.map +1 -0
  442. package/dist/node-version.js +23 -0
  443. package/dist/node-version.js.map +1 -0
  444. package/dist/search/grep-cli.d.ts +35 -0
  445. package/dist/search/grep-cli.d.ts.map +1 -0
  446. package/dist/search/grep-cli.js +115 -0
  447. package/dist/search/grep-cli.js.map +1 -0
  448. package/dist/search/grep.d.ts +60 -0
  449. package/dist/search/grep.d.ts.map +1 -0
  450. package/dist/search/grep.js +143 -0
  451. package/dist/search/grep.js.map +1 -0
  452. package/dist/search/ignored-code.d.ts +9 -0
  453. package/dist/search/ignored-code.d.ts.map +1 -0
  454. package/dist/search/ignored-code.js +73 -0
  455. package/dist/search/ignored-code.js.map +1 -0
  456. package/dist/search/user-regexp.d.ts +26 -0
  457. package/dist/search/user-regexp.d.ts.map +1 -0
  458. package/dist/search/user-regexp.js +150 -0
  459. package/dist/search/user-regexp.js.map +1 -0
  460. package/dist/telemetry/contract.d.ts +117 -0
  461. package/dist/telemetry/contract.d.ts.map +1 -0
  462. package/dist/telemetry/contract.js +341 -0
  463. package/dist/telemetry/contract.js.map +1 -0
  464. package/dist/telemetry/flush.d.ts +21 -0
  465. package/dist/telemetry/flush.d.ts.map +1 -0
  466. package/dist/telemetry/flush.js +85 -0
  467. package/dist/telemetry/flush.js.map +1 -0
  468. package/dist/telemetry/gate.d.ts +30 -0
  469. package/dist/telemetry/gate.d.ts.map +1 -0
  470. package/dist/telemetry/gate.js +81 -0
  471. package/dist/telemetry/gate.js.map +1 -0
  472. package/dist/telemetry/identity.d.ts +40 -0
  473. package/dist/telemetry/identity.d.ts.map +1 -0
  474. package/dist/telemetry/identity.js +80 -0
  475. package/dist/telemetry/identity.js.map +1 -0
  476. package/dist/telemetry/index.d.ts +18 -0
  477. package/dist/telemetry/index.d.ts.map +1 -0
  478. package/dist/telemetry/index.js +16 -0
  479. package/dist/telemetry/index.js.map +1 -0
  480. package/dist/telemetry/key.d.ts +21 -0
  481. package/dist/telemetry/key.d.ts.map +1 -0
  482. package/dist/telemetry/key.js +49 -0
  483. package/dist/telemetry/key.js.map +1 -0
  484. package/dist/telemetry/notice.d.ts +19 -0
  485. package/dist/telemetry/notice.d.ts.map +1 -0
  486. package/dist/telemetry/notice.js +93 -0
  487. package/dist/telemetry/notice.js.map +1 -0
  488. package/dist/telemetry/queue.d.ts +35 -0
  489. package/dist/telemetry/queue.d.ts.map +1 -0
  490. package/dist/telemetry/queue.js +162 -0
  491. package/dist/telemetry/queue.js.map +1 -0
  492. package/dist/telemetry/send.d.ts +19 -0
  493. package/dist/telemetry/send.d.ts.map +1 -0
  494. package/dist/telemetry/send.js +84 -0
  495. package/dist/telemetry/send.js.map +1 -0
  496. package/dist/telemetry/sessions.d.ts +25 -0
  497. package/dist/telemetry/sessions.d.ts.map +1 -0
  498. package/dist/telemetry/sessions.js +104 -0
  499. package/dist/telemetry/sessions.js.map +1 -0
  500. package/dist/telemetry/track.d.ts +62 -0
  501. package/dist/telemetry/track.d.ts.map +1 -0
  502. package/dist/telemetry/track.js +143 -0
  503. package/dist/telemetry/track.js.map +1 -0
  504. package/dist/upkeep-run.d.ts +22 -0
  505. package/dist/upkeep-run.d.ts.map +1 -0
  506. package/dist/upkeep-run.js +85 -0
  507. package/dist/upkeep-run.js.map +1 -0
  508. package/dist/upkeep.d.ts +127 -0
  509. package/dist/upkeep.d.ts.map +1 -0
  510. package/dist/upkeep.js +252 -0
  511. package/dist/upkeep.js.map +1 -0
  512. package/dist/util/actions-folder.d.ts +2 -0
  513. package/dist/util/actions-folder.d.ts.map +1 -0
  514. package/dist/util/actions-folder.js +9 -0
  515. package/dist/util/actions-folder.js.map +1 -0
  516. package/dist/util/browser.d.ts +3 -0
  517. package/dist/util/browser.d.ts.map +1 -0
  518. package/dist/util/browser.js +14 -0
  519. package/dist/util/browser.js.map +1 -0
  520. package/dist/util/child-process.d.ts +15 -0
  521. package/dist/util/child-process.d.ts.map +1 -0
  522. package/dist/util/child-process.js +15 -0
  523. package/dist/util/child-process.js.map +1 -0
  524. package/dist/util/front-matter.d.ts +46 -0
  525. package/dist/util/front-matter.d.ts.map +1 -0
  526. package/dist/util/front-matter.js +113 -0
  527. package/dist/util/front-matter.js.map +1 -0
  528. package/dist/util/id.d.ts +7 -0
  529. package/dist/util/id.d.ts.map +1 -0
  530. package/dist/util/id.js +14 -0
  531. package/dist/util/id.js.map +1 -0
  532. package/dist/util/paths.d.ts +29 -0
  533. package/dist/util/paths.d.ts.map +1 -0
  534. package/dist/util/paths.js +71 -0
  535. package/dist/util/paths.js.map +1 -0
  536. package/dist/util/private-file.d.ts +14 -0
  537. package/dist/util/private-file.d.ts.map +1 -0
  538. package/dist/util/private-file.js +82 -0
  539. package/dist/util/private-file.js.map +1 -0
  540. package/dist/util/source.d.ts +4 -0
  541. package/dist/util/source.d.ts.map +1 -0
  542. package/dist/util/source.js +12 -0
  543. package/dist/util/source.js.map +1 -0
  544. package/dist/util/spinner.d.ts +15 -0
  545. package/dist/util/spinner.d.ts.map +1 -0
  546. package/dist/util/spinner.js +52 -0
  547. package/dist/util/spinner.js.map +1 -0
  548. package/dist/util/state.d.ts +120 -0
  549. package/dist/util/state.d.ts.map +1 -0
  550. package/dist/util/state.js +264 -0
  551. package/dist/util/state.js.map +1 -0
  552. package/dist/vendor/grammars/tree-sitter-go.LICENSE +21 -0
  553. package/dist/vendor/grammars/tree-sitter-go.wasm +0 -0
  554. package/dist/vendor/grammars/tree-sitter-java.LICENSE +21 -0
  555. package/dist/vendor/grammars/tree-sitter-java.wasm +0 -0
  556. package/dist/vendor/grammars/tree-sitter-kotlin.LICENSE +9 -0
  557. package/dist/vendor/grammars/tree-sitter-kotlin.wasm +0 -0
  558. package/dist/vendor/grammars/tree-sitter-php.LICENSE +22 -0
  559. package/dist/vendor/grammars/tree-sitter-php.wasm +0 -0
  560. package/dist/vendor/grammars/tree-sitter-python.LICENSE +21 -0
  561. package/dist/vendor/grammars/tree-sitter-python.wasm +0 -0
  562. package/dist/vendor/grammars/tree-sitter-r.LICENSE +9 -0
  563. package/dist/vendor/grammars/tree-sitter-r.wasm +0 -0
  564. package/dist/vendor/grammars/tree-sitter-swift.LICENSE +21 -0
  565. package/dist/vendor/grammars/tree-sitter-swift.wasm +0 -0
  566. package/dist/vendor/grammars/tree-sitter-tsx.wasm +0 -0
  567. package/dist/vendor/grammars/tree-sitter-typescript.LICENSE +21 -0
  568. package/dist/vendor/grammars/tree-sitter-typescript.wasm +0 -0
  569. package/dist/viewer/app.js +2 -0
  570. package/dist/viewer/index.html +49 -0
  571. package/dist/viewer/style.css +154 -0
  572. package/dist/viz/assemble.d.ts +83 -0
  573. package/dist/viz/assemble.d.ts.map +1 -0
  574. package/dist/viz/assemble.js +101 -0
  575. package/dist/viz/assemble.js.map +1 -0
  576. package/dist/viz/export.d.ts +39 -0
  577. package/dist/viz/export.d.ts.map +1 -0
  578. package/dist/viz/export.js +98 -0
  579. package/dist/viz/export.js.map +1 -0
  580. package/dist/viz/serve.d.ts +13 -0
  581. package/dist/viz/serve.d.ts.map +1 -0
  582. package/dist/viz/serve.js +130 -0
  583. package/dist/viz/serve.js.map +1 -0
  584. package/package.json +115 -4
  585. package/scripts/build-grammar-wasm.mjs +112 -0
  586. package/scripts/build-viewer.mjs +44 -0
  587. package/scripts/graph-quality.mjs +117 -0
  588. package/scripts/pack-trail.mjs +53 -0
  589. package/scripts/postinstall.mjs +65 -0
  590. package/scripts/run-tests.mjs +72 -0
  591. package/scripts/stamp-telemetry-key.mjs +56 -0
  592. package/scripts/tally-audit.mjs +140 -0
  593. package/scripts/vendor-grammars.mjs +53 -0
@@ -0,0 +1,2377 @@
1
+ /**
2
+ * Tier-1 extraction: source file → {@link NodeV1}[] + raw edges, via tree-sitter.
3
+ *
4
+ * Deterministic and dependency-only (no LLM, no network). Emits one node per
5
+ * definition (file, class, function, method, interface, type, enum, and TS
6
+ * arrow-function consts) plus unresolved edge intents. Edge *targets* are
7
+ * resolved against the whole-repo node index later, in build.ts.
8
+ */
9
+ import { basename } from "node:path";
10
+ import { contentHash } from "../util/id.js";
11
+ import { collectBindings, goReceiverVarOf, resolveRecvType } from "./bindings.js";
12
+ import { parseDepth } from "./depth-parser.js";
13
+ /**
14
+ * Extension → the tree-sitter grammar that parses it, and the label a human expects
15
+ * to see for it.
16
+ *
17
+ * The two are not the same, and conflating them under-reported coverage: `.mjs` is
18
+ * parsed by the typescript grammar, so a JS repo's build banner read `[typescript]`
19
+ * and a `.jsx` one read `[tsx]`. Both are true about the *parser* and misleading
20
+ * about the repo — people went looking for why their JavaScript hadn't been indexed
21
+ * when it had, and could not tell a language that was merely unlabelled from one
22
+ * that really was skipped (see issue #36).
23
+ *
24
+ * One table, both readings derived from it, so adding an extension cannot fix
25
+ * extraction and forget the label. Ordered longest-suffix-first: `.tsx` has to be
26
+ * tested before `.ts` would match it.
27
+ */
28
+ const EXTENSIONS = [
29
+ { ext: ".tsx", grammar: "tsx", label: "tsx" },
30
+ { ext: ".jsx", grammar: "tsx", label: "jsx" },
31
+ { ext: ".mts", grammar: "typescript", label: "typescript" },
32
+ { ext: ".cts", grammar: "typescript", label: "typescript" },
33
+ { ext: ".ts", grammar: "typescript", label: "typescript" },
34
+ { ext: ".mjs", grammar: "typescript", label: "javascript" },
35
+ { ext: ".cjs", grammar: "typescript", label: "javascript" },
36
+ { ext: ".js", grammar: "typescript", label: "javascript" },
37
+ { ext: ".pyi", grammar: "python", label: "python" },
38
+ { ext: ".py", grammar: "python", label: "python" },
39
+ { ext: ".go", grammar: "go", label: "go" },
40
+ { ext: ".java", grammar: "java", label: "java" },
41
+ { ext: ".kt", grammar: "kotlin", label: "kotlin" },
42
+ { ext: ".kts", grammar: "kotlin", label: "kotlin" },
43
+ { ext: ".swift", grammar: "swift", label: "swift" },
44
+ { ext: ".php", grammar: "php", label: "php" },
45
+ // `entryFor` lower-cases the path before matching, so this one entry covers
46
+ // both `.R` (the conventional case in real R codebases) and `.r`.
47
+ { ext: ".r", grammar: "r", label: "r" },
48
+ ];
49
+ function entryFor(path) {
50
+ const p = path.toLowerCase();
51
+ return EXTENSIONS.find((e) => p.endsWith(e.ext));
52
+ }
53
+ /** Every file extension a depth-tier (hand-written) extractor claims. */
54
+ export function depthExtensions() {
55
+ return EXTENSIONS.map((e) => e.ext);
56
+ }
57
+ /** Map a file path to a supported language, or null if unsupported. */
58
+ export function languageOf(path) {
59
+ return entryFor(path)?.grammar ?? null;
60
+ }
61
+ /**
62
+ * What to *call* the language of this file, for a banner or a repo map — or null when
63
+ * the file isn't indexed at all, which is the distinction {@link languageOf} shares
64
+ * and the one that matters to a reader checking coverage.
65
+ */
66
+ export function languageLabelOf(path) {
67
+ return entryFor(path)?.label ?? null;
68
+ }
69
+ /** Max chars of normalized body stored per symbol for search. Large enough that
70
+ * essentially every real definition is stored whole — only a rare giant function
71
+ * is clipped — while bounding how much the committed graph can grow. */
72
+ const MAX_BODY_CHARS = 5000;
73
+ /** Cap for a file node's module-level residual (imports, constants, module
74
+ * docstring — everything not inside a symbol). Higher than the per-symbol cap
75
+ * because a data-heavy module (constant tables, big config dicts) is legitimate
76
+ * residual, and it's the recall play — but still bounded. */
77
+ const MAX_FILE_BODY_CHARS = 16000;
78
+ /** The searchable body of a definition: its source text, whitespace-collapsed
79
+ * so every identifier becomes a token, capped at `max`. Search-only — the agent
80
+ * still reads verbatim source via `ask --source`, which slices the file from
81
+ * disk, so nothing here reaches the agent's context. */
82
+ function searchBody(text, max = MAX_BODY_CHARS) {
83
+ const norm = text.replace(/\s+/g, " ").trim();
84
+ return norm.length > max ? norm.slice(0, max) : norm;
85
+ }
86
+ /** A file's module-level residual: the lines NOT covered by any symbol span.
87
+ * Symbol bodies are already indexed on their own nodes, so this captures only
88
+ * what they miss — top-of-file imports, module constants, module docstrings —
89
+ * making a file findable by a term that lives outside every function/class.
90
+ * `symbols` are the file's emitted nodes (with `Lx-Ly` spans); `source` is the
91
+ * whole file. Far leaner than storing full-file bodies (no symbol duplication). */
92
+ function fileResidual(source, symbols) {
93
+ const lines = source.split("\n");
94
+ const covered = new Uint8Array(lines.length + 2);
95
+ for (const s of symbols) {
96
+ const m = s.span.match(/^L(\d+)-L(\d+)$/);
97
+ if (!m)
98
+ continue;
99
+ for (let r = Number(m[1]); r <= Number(m[2]) && r < covered.length; r++)
100
+ covered[r] = 1;
101
+ }
102
+ const kept = [];
103
+ for (let i = 0; i < lines.length; i++)
104
+ if (!covered[i + 1])
105
+ kept.push(lines[i]);
106
+ return searchBody(kept.join(" "), MAX_FILE_BODY_CHARS);
107
+ }
108
+ const TS_KINDS = {
109
+ class_declaration: "class",
110
+ abstract_class_declaration: "class",
111
+ function_declaration: "function",
112
+ generator_function_declaration: "function",
113
+ method_definition: "method",
114
+ interface_declaration: "interface",
115
+ type_alias_declaration: "type",
116
+ enum_declaration: "enum",
117
+ };
118
+ const PY_KINDS = {
119
+ class_definition: "class",
120
+ function_definition: "function", // → "method" inside a class (resolved in the walk)
121
+ };
122
+ // Go: `type_spec` is intentionally absent — its kind (struct/interface/type) depends on
123
+ // the named type's shape, so it's resolved dynamically in describe().
124
+ const GO_KINDS = {
125
+ function_declaration: "function",
126
+ method_declaration: "method",
127
+ };
128
+ // R: `function_definition` carries no name field at all (unlike every other
129
+ // supported language) — its identifier always comes from context (an
130
+ // assignment's other side), resolved dynamically in describeR(). Empty, like
131
+ // Go's own table — never consulted, kept only to satisfy KINDS_BY_LANG's type.
132
+ const R_KINDS = {};
133
+ // Java: a record is a nominal data carrier, so it takes "struct" — the same role
134
+ // Go's struct plays — rather than "class", which would make a service and a DTO
135
+ // indistinguishable in a repo where DTOs are most of the type surface.
136
+ const JAVA_KINDS = {
137
+ class_declaration: "class",
138
+ interface_declaration: "interface",
139
+ enum_declaration: "enum",
140
+ record_declaration: "struct",
141
+ annotation_type_declaration: "interface",
142
+ annotation_type_element_declaration: "method",
143
+ method_declaration: "method",
144
+ constructor_declaration: "method",
145
+ };
146
+ /** Java type declarations: they set `enclosingClass` for the methods nested in them,
147
+ * which "class"-only logic would miss for a record's or interface's members. */
148
+ const JAVA_TYPE_KINDS = new Set(["class", "interface", "enum", "struct"]);
149
+ const KOTLIN_KINDS = {
150
+ class_declaration: "class", // → "interface" / "enum" / "interface" (annotation) in describeKotlin
151
+ object_declaration: "class", // a singleton object is class-like (companion objects included)
152
+ function_declaration: "function", // → "method" inside a type (resolved in the walk)
153
+ secondary_constructor: "method", // the class's own secondary constructor
154
+ type_alias: "type",
155
+ property_declaration: "variable", // top-level `val`/`var` only (fields resolved in the walk)
156
+ };
157
+ /** Kotlin type declarations: they set `enclosingClass` for the members nested in them.
158
+ * "class" also covers object_declaration (it maps to "class"); interface/enum are the
159
+ * same class_declaration node rekinded in describeKotlin, so all three land in the set. */
160
+ const KOTLIN_TYPE_KINDS = new Set(["class", "interface", "enum"]);
161
+ const SWIFT_KINDS = {
162
+ class_declaration: "class", // → "struct" / "enum" in describeSwift (one node type covers all five keywords)
163
+ protocol_declaration: "interface",
164
+ function_declaration: "function", // → "method" inside a type (resolved in the walk)
165
+ protocol_function_declaration: "method", // a protocol requirement is always a member
166
+ init_declaration: "method", // the type's own initializer (named after it, like a Java constructor)
167
+ typealias_declaration: "type",
168
+ property_declaration: "variable", // top-level `let`/`var` only (fields resolved in the walk)
169
+ };
170
+ /** Swift type declarations: they set `enclosingClass` for the members nested in them.
171
+ * class/struct/enum are the same class_declaration node rekinded in describeSwift
172
+ * (an actor takes "class"); protocols are "interface". "module" is an extension
173
+ * body — a member-contributing scope named after the extended type, deliberately
174
+ * NOT a type kind in the graph so it can never make the real declaration's name
175
+ * ambiguous (see describeSwift's extension branch) — but its members still
176
+ * promote to methods owned by that type, which is why it belongs in this set. */
177
+ const SWIFT_TYPE_KINDS = new Set([
178
+ "class",
179
+ "struct",
180
+ "enum",
181
+ "interface",
182
+ "module",
183
+ ]);
184
+ // PHP: definition node types are all distinct (no py-style function→method
185
+ // promotion needed — a class body uses `method_declaration`, not
186
+ // `function_definition`). `trait_declaration` maps to the PHP-only `trait` kind.
187
+ const PHP_KINDS = {
188
+ function_definition: "function",
189
+ method_declaration: "method",
190
+ class_declaration: "class",
191
+ interface_declaration: "interface",
192
+ trait_declaration: "trait",
193
+ enum_declaration: "enum",
194
+ };
195
+ const KINDS_BY_LANG = {
196
+ typescript: TS_KINDS,
197
+ tsx: TS_KINDS,
198
+ python: PY_KINDS,
199
+ go: GO_KINDS,
200
+ r: R_KINDS,
201
+ java: JAVA_KINDS,
202
+ kotlin: KOTLIN_KINDS,
203
+ swift: SWIFT_KINDS,
204
+ php: PHP_KINDS,
205
+ };
206
+ /**
207
+ * The node type(s) that constitute a call site, per language.
208
+ *
209
+ * Java is the reason this is a set rather than a string: `method_invocation` and
210
+ * `object_creation_expression` (`new Foo()`) are separate node types, and a Java
211
+ * codebase's constructor calls are a large share of its real edges. PHP is
212
+ * likewise multi-shape: a call is a function / member / nullsafe-member / scoped
213
+ * call, never a single `call_expression`.
214
+ */
215
+ const CALL_TYPES = {
216
+ typescript: new Set(["call_expression"]),
217
+ tsx: new Set(["call_expression"]),
218
+ python: new Set(["call"]),
219
+ go: new Set(["call_expression"]),
220
+ java: new Set(["method_invocation", "object_creation_expression"]),
221
+ kotlin: new Set(["call_expression"]),
222
+ swift: new Set(["call_expression"]),
223
+ php: new Set([
224
+ "function_call_expression",
225
+ "member_call_expression",
226
+ "nullsafe_member_call_expression",
227
+ "scoped_call_expression",
228
+ ]),
229
+ r: new Set(["call"]),
230
+ };
231
+ const FUNCTION_VALUE_TYPES = new Set([
232
+ "arrow_function",
233
+ "function",
234
+ "function_expression",
235
+ "generator_function",
236
+ ]);
237
+ const EMPTY_SET = new Set();
238
+ /** Throws when `lang`'s grammar is unavailable: callers check `depthReady`
239
+ * (depth-parser.ts) first and hand such a file to its breadth-tier stand-in instead. */
240
+ export function extractFile(rel, source, lang) {
241
+ return parseDepth(lang, source, (root) => extractTree(rel, source, lang, root));
242
+ }
243
+ /** {@link extractFile} over an already-parsed tree. */
244
+ function extractTree(rel, source, lang, root) {
245
+ const bindings = collectBindings(root, lang);
246
+ const importedSymbols = collectImportedSymbols(root, lang);
247
+ const rGenerics = lang === "r" ? collectRGenerics(root) : EMPTY_SET;
248
+ const nodes = [
249
+ {
250
+ id: rel,
251
+ name: basename(rel),
252
+ kind: "file",
253
+ path: rel,
254
+ span: `L1-L${root.endPosition.row + 1}`,
255
+ signature: null,
256
+ exported: true,
257
+ origin: "ast",
258
+ body_hash: contentHash(source),
259
+ chars: source.length,
260
+ summary_state: "pending",
261
+ summary: null,
262
+ crux: null,
263
+ },
264
+ ];
265
+ const rawEdges = [];
266
+ const ctx = {
267
+ rel,
268
+ source,
269
+ lang,
270
+ kinds: KINDS_BY_LANG[lang],
271
+ scope: [],
272
+ enclosingKind: null,
273
+ parentId: rel,
274
+ bindings,
275
+ enclosingClass: null,
276
+ goReceiverVar: null,
277
+ importedSymbols,
278
+ rR6Access: null,
279
+ rGenerics,
280
+ rSuperClass: null,
281
+ };
282
+ // Every id minted this file, seeded with the file node's own id (`rel`) so a
283
+ // top-level definition can never collide with it. Threaded as its own
284
+ // parameter rather than living on WalkCtx — WalkCtx is spread into every
285
+ // childCtx, so a by-ref Set there would read as ordinary inherited context
286
+ // when it's actually accidental shared mutable state across the whole walk.
287
+ const minted = new Set([rel]);
288
+ walkNamedChildren(root.namedChildren, ctx, nodes, rawEdges, minted);
289
+ // nodes[0] is the file node; the rest are its symbols. Index the module-level
290
+ // residual on the file node so a term outside every symbol still surfaces it.
291
+ nodes[0].body_text = fileResidual(source, nodes.slice(1));
292
+ return { nodes, rawEdges };
293
+ }
294
+ /** Mint-time uniqueness: a document-order duplicate (same name reopened, or two
295
+ * sibling defs that happen to collide) gets `~2`, `~3`, ... instead of silently
296
+ * shadowing the first. The while-loop (not a single `~2` guess) is what makes
297
+ * this collision-proof: a source name that itself ends in ~N would collide
298
+ * with a single-guess suffix, so this keeps incrementing until it finds a
299
+ * truly free id rather than trusting one candidate suffix is unused. */
300
+ export function mintId(base, minted) {
301
+ let id = base;
302
+ let k = 2;
303
+ while (minted.has(id))
304
+ id = `${base}~${k++}`;
305
+ minted.add(id);
306
+ return id;
307
+ }
308
+ /**
309
+ * tree-sitter-php 0.23.x cannot parse a `const` inside an enum body (#145). An
310
+ * array initializer collapses the whole `enum_declaration` into ERROR; the
311
+ * method is recovered as a sibling `function_definition`. 0.24.2 parses this
312
+ * natively but is ABI 15 and cannot load on Graft's tree-sitter 0.21.1.
313
+ *
314
+ * Bound: only an ERROR that already contains `enum_case` + `name` is treated as
315
+ * a collapsed enum. Only `const_declaration` / `function_definition` /
316
+ * `method_declaration` / ERROR siblings are absorbed, stopping at a `}` ERROR.
317
+ * Unknown ERROR nodes are still walked, never mapped to a type. Clean
318
+ * class/enum trees are `class_declaration` / `enum_declaration` and skip this.
319
+ */
320
+ function phpCollapsedEnumName(node) {
321
+ if (node.type !== "ERROR")
322
+ return null;
323
+ if (!node.namedChildren.some((c) => c.type === "enum_case"))
324
+ return null;
325
+ return node.namedChildren.find((c) => c.type === "name")?.text ?? null;
326
+ }
327
+ function phpCollapsedEnumHold(node) {
328
+ return (node.type === "const_declaration" ||
329
+ node.type === "function_definition" ||
330
+ node.type === "method_declaration" ||
331
+ node.type === "ERROR");
332
+ }
333
+ function phpCollapsedEnumClose(node) {
334
+ return node.type === "ERROR" && node.text.trim() === "}";
335
+ }
336
+ function walkNamedChildren(children, ctx, out, edges, minted) {
337
+ if (ctx.lang !== "php") {
338
+ for (const child of children)
339
+ walk(child, ctx, out, edges, minted);
340
+ return;
341
+ }
342
+ for (let i = 0; i < children.length;) {
343
+ const n = children[i];
344
+ const enumName = phpCollapsedEnumName(n);
345
+ if (enumName) {
346
+ const group = [n];
347
+ let j = i + 1;
348
+ while (j < children.length && phpCollapsedEnumHold(children[j])) {
349
+ const next = children[j];
350
+ group.push(next);
351
+ j++;
352
+ if (phpCollapsedEnumClose(next))
353
+ break;
354
+ }
355
+ emitPhpCollapsedEnum(enumName, n, group, ctx, out, edges, minted);
356
+ i = j;
357
+ continue;
358
+ }
359
+ walk(n, ctx, out, edges, minted);
360
+ i++;
361
+ }
362
+ }
363
+ function emitPhpCollapsedEnum(name, errorNode, group, ctx, out, edges, minted) {
364
+ const last = group[group.length - 1];
365
+ const id = mintId(`${ctx.rel}#${[...ctx.scope, name].join(".")}`, minted);
366
+ const body = ctx.source.slice(errorNode.startIndex, last.endIndex);
367
+ out.push({
368
+ id,
369
+ name,
370
+ kind: "enum",
371
+ path: ctx.rel,
372
+ span: `L${errorNode.startPosition.row + 1}-L${last.endPosition.row + 1}`,
373
+ signature: `enum ${name}`,
374
+ exported: true,
375
+ origin: "ast",
376
+ body_hash: contentHash(body),
377
+ body_text: searchBody(body),
378
+ summary_state: "pending",
379
+ summary: null,
380
+ crux: null,
381
+ });
382
+ edges.push({ source: ctx.parentId, relation: "contains", targetId: id, file: ctx.rel });
383
+ const childCtx = {
384
+ ...ctx,
385
+ scope: [...ctx.scope, name],
386
+ enclosingKind: "enum",
387
+ parentId: id,
388
+ };
389
+ for (const g of group) {
390
+ if (phpCollapsedEnumClose(g))
391
+ continue;
392
+ if (phpCollapsedEnumName(g)) {
393
+ walkNamedChildren(g.namedChildren, childCtx, out, edges, minted);
394
+ continue;
395
+ }
396
+ walk(g, childCtx, out, edges, minted);
397
+ }
398
+ }
399
+ function walk(node, ctx, out, edges, minted) {
400
+ const desc = describe(node, ctx);
401
+ if (desc) {
402
+ // `idName` scopes the id (e.g. a Go method under its receiver: `#DB.Count`) while
403
+ // `name` stays the bare symbol name so member-call resolution matches it.
404
+ const idPart = desc.idName ?? desc.name;
405
+ const base = `${ctx.rel}#${[...ctx.scope, idPart].join(".")}`;
406
+ const id = mintId(base, minted);
407
+ const isGoMethod = ctx.lang === "go" && node.type === "method_declaration";
408
+ // The bare name of this node's OWN immediate enclosing class/receiver — for a
409
+ // Go method that's its receiver type (methods aren't nested, so ctx.enclosingClass
410
+ // wouldn't see it); for an R S3/S4 method it's the qualifier/class describeR
411
+ // already resolved (desc.owner — these don't lexically nest inside their class
412
+ // either); for every other method it's simply what the nearest ancestor class
413
+ // already set as ctx.enclosingClass. Only method nodes carry it — resolve.ts's
414
+ // ownerMethod index is the sole consumer (see NodeV1.owner's doc comment).
415
+ const owner = desc.kind === "method"
416
+ ? (isGoMethod ? (goReceiverType(node) ?? undefined) : (desc.owner ?? ctx.enclosingClass ?? undefined))
417
+ : undefined;
418
+ out.push({
419
+ id,
420
+ name: desc.name,
421
+ kind: desc.kind,
422
+ path: ctx.rel,
423
+ span: `L${desc.hashNode.startPosition.row + 1}-L${desc.hashNode.endPosition.row + 1}`,
424
+ signature: clean(ctx.source.slice(desc.hashNode.startIndex, desc.headerEnd)),
425
+ exported: ctx.lang === "python"
426
+ ? !desc.name.startsWith("_")
427
+ : ctx.lang === "go"
428
+ ? goExported(desc.name)
429
+ : ctx.lang === "r"
430
+ ? rExported(desc.name, ctx, node)
431
+ : ctx.lang === "java"
432
+ ? javaExported(node)
433
+ : ctx.lang === "kotlin"
434
+ ? kotlinExported(node)
435
+ : ctx.lang === "swift"
436
+ ? swiftExported(node)
437
+ : ctx.lang === "php"
438
+ ? phpExported(node)
439
+ : tsExported(node),
440
+ origin: "ast",
441
+ body_hash: contentHash(desc.hashNode.text),
442
+ body_text: searchBody(desc.hashNode.text),
443
+ summary_state: "pending",
444
+ summary: null,
445
+ crux: null,
446
+ ...(owner !== undefined ? { owner } : {}),
447
+ ...(desc.arity !== undefined ? { arity: desc.arity } : {}),
448
+ ...(desc.variadic ? { variadic: true } : {}),
449
+ });
450
+ // structural containment
451
+ edges.push({ source: ctx.parentId, relation: "contains", targetId: id, file: ctx.rel });
452
+ // class heritage — in Java an interface may also `extends`, and a record/enum
453
+ // may `implements`, so every type declaration is a heritage site, not just a class.
454
+ const javaTypeDecl = ctx.lang === "java" && JAVA_TYPE_KINDS.has(desc.kind);
455
+ const kotlinTypeDecl = ctx.lang === "kotlin" && KOTLIN_TYPE_KINDS.has(desc.kind);
456
+ const swiftTypeDecl = ctx.lang === "swift" && SWIFT_TYPE_KINDS.has(desc.kind);
457
+ if (desc.kind === "class" || javaTypeDecl || kotlinTypeDecl || swiftTypeDecl)
458
+ edges.push(...heritageEdges(node, id, ctx));
459
+ if (ctx.lang === "php")
460
+ edges.push(...phpAttributeReferenceEdges(node, id, ctx));
461
+ if (ctx.lang === "java")
462
+ edges.push(...javaAnnotationReferenceEdges(node, id, ctx));
463
+ const enclosingClass = desc.kind === "class" || javaTypeDecl || kotlinTypeDecl || swiftTypeDecl
464
+ ? desc.name
465
+ : isGoMethod
466
+ ? goReceiverType(node)
467
+ : (desc.owner ?? ctx.enclosingClass);
468
+ const childCtx = {
469
+ ...ctx,
470
+ scope: [...ctx.scope, idPart],
471
+ enclosingKind: desc.kind,
472
+ parentId: id,
473
+ enclosingClass,
474
+ goReceiverVar: isGoMethod ? goReceiverVarOf(node) : ctx.goReceiverVar,
475
+ importedSymbols: desc.kind === "function" || desc.kind === "method"
476
+ ? withoutShadowedImports(ctx.importedSymbols, node)
477
+ : ctx.importedSymbols,
478
+ // Reset on every new definition — this is a purely local marker for "we're
479
+ // still inside THIS class-defining call's own public=/private=/active=
480
+ // argument chain," not something that should leak into a nested definition
481
+ // (a method's own body, or — vanishingly rare but possible — another class
482
+ // defined inside one).
483
+ rR6Access: null,
484
+ // Unlike rR6Access, only reset when entering a genuinely new class (so it
485
+ // stays live through a method's whole body, where super$ / super. calls
486
+ // actually happen) — inherited unchanged for every other definition kind.
487
+ // Swift reads it off the declaration's own `:` clause, so `super.ping()`
488
+ // resolves against the PARENT type, not the overriding current one.
489
+ rSuperClass: desc.kind === "class"
490
+ ? ctx.lang === "r"
491
+ ? rR6ParentClass(node)
492
+ : ctx.lang === "swift"
493
+ ? swiftSuperClassName(node)
494
+ : null
495
+ : ctx.rSuperClass,
496
+ };
497
+ walkNamedChildren(node.namedChildren, childCtx, out, edges, minted);
498
+ return;
499
+ }
500
+ // R6 (Phase 2): `public =`/`private =`/`active =` inside an R6Class(...) call's
501
+ // own arguments is a `list(...)` call whose named entries become methods —
502
+ // this is R's version of a class body, but structurally it's several levels of
503
+ // ordinary call/argument nodes rather than a dedicated grammar construct, so it
504
+ // needs its own interception (mirrors how every other stateful/pattern-matched
505
+ // R construct in this walk needs one). `ctx.enclosingKind === "class"` scopes
506
+ // this to the class-defining call's own direct structure — once we're inside
507
+ // an actual method's body, enclosingKind has moved on to "method" and an
508
+ // unrelated nested `list(public = list(fn = function() {}))` elsewhere won't
509
+ // be misread as another class body.
510
+ if (ctx.lang === "r" &&
511
+ ctx.enclosingKind === "class" &&
512
+ ctx.rR6Access === null &&
513
+ node.type === "argument") {
514
+ const argName = node.childForFieldName("name");
515
+ const value = node.childForFieldName("value");
516
+ if (argName?.type === "identifier" &&
517
+ (argName.text === "public" || argName.text === "private" || argName.text === "active") &&
518
+ value?.type === "call" &&
519
+ rCalleeName(value) === "list") {
520
+ const access = argName.text;
521
+ for (const entry of rCallArgs(value)) {
522
+ walk(entry, { ...ctx, rR6Access: access }, out, edges, minted);
523
+ }
524
+ return;
525
+ }
526
+ }
527
+ // not a definition — capture calls/imports/references, then descend with the same context
528
+ // R's `call` node is also its ONLY vehicle for library()/require()/source() —
529
+ // there's no separate import-statement grammar construct to key off, so isImport
530
+ // must be checked before the generic calls path or every import call would be
531
+ // captured as a (harmlessly unresolvable, but wrong) `calls` edge instead.
532
+ const callTypes = CALL_TYPES[ctx.lang];
533
+ if (isImport(node, ctx.lang)) {
534
+ const spec = importSpecifier(node, ctx.lang);
535
+ if (spec)
536
+ edges.push({ source: ctx.rel, relation: "imports", specifier: spec, file: ctx.rel });
537
+ // Imported identifiers are declarations, not uses. The import-binding pass
538
+ // above already recorded them, so do not descend and emit false references.
539
+ return;
540
+ }
541
+ else if (callTypes.has(node.type)) {
542
+ // R6Class(...) / a Phase-5 mixin list(...) is already consumed by its
543
+ // enclosing binary_operator as the class definition (see describeR) — the
544
+ // walk still reaches this SAME call node again, recursing generically to
545
+ // find its public=/private=/active= arguments (there's no other path to
546
+ // them), and it must not ALSO be treated as an ordinary call to a
547
+ // function literally named "R6Class"/"list".
548
+ const consumedCallee = ctx.lang === "r" && node.type === "call" ? rCalleeName(node) : null;
549
+ const isConsumedRClassCall = consumedCallee === "R6Class" || (consumedCallee === "list" && rIsMixinContainer(node));
550
+ const callee = isConsumedRClassCall ? null : calleeName(node, ctx.lang);
551
+ if (callee) {
552
+ const callEdge = {
553
+ source: ctx.parentId,
554
+ relation: "calls",
555
+ name: callee.name,
556
+ viaMember: callee.viaMember,
557
+ file: ctx.rel,
558
+ ...(callee.kinds ? { kinds: callee.kinds } : {}),
559
+ };
560
+ // Overloading languages: the call site's argument count, to pick the right
561
+ // overload (see RawEdge.argCount).
562
+ const argCount = ctx.lang === "java" ? javaArgCount(node) : ctx.lang === "swift" ? swiftArgCount(node) : undefined;
563
+ if (argCount !== undefined)
564
+ callEdge.argCount = argCount;
565
+ // Swift: a bare lowercase call inside a type body may be an implicit-`self`
566
+ // member call (`walk()` for `self.walk()`), syntactically indistinguishable
567
+ // from a free-function call — and Swift's own lookup is member-FIRST (inner
568
+ // scope wins). So the edge is emitted as the member reading, typed to the
569
+ // enclosing class — resolved through the owner-qualified method index and
570
+ // the class's in-repo ancestor chain (`clearLogs()` in a test subclass
571
+ // finds the base class's method) — with `implicitSelf` letting resolve.ts
572
+ // fall back to the free-function reading only when no member exists on the
573
+ // chain. One edge, both readings, language-order precedence. This is
574
+ // deliberately NOT a bare-name kind widening: dogfooding on
575
+ // swift-composable-architecture, a global unique-name match bound
576
+ // `contains(element)` inside `extension Set` — a stdlib call — to an
577
+ // unrelated type's only in-repo `contains`. And not for an UpperCamelCase
578
+ // callee: that is an initializer call (`Text("hi")`), which takes
579
+ // resolve.ts's class/struct/enum fallback instead — extension nodes (kind
580
+ // "module") can never false-match it.
581
+ const swiftImplicitSelf = ctx.lang === "swift" &&
582
+ !callee.viaMember &&
583
+ !callee.kinds &&
584
+ ctx.enclosingClass &&
585
+ !/^[A-Z]/.test(callee.name);
586
+ if (swiftImplicitSelf) {
587
+ edges.push({
588
+ ...callEdge,
589
+ viaMember: true,
590
+ recvType: ctx.enclosingClass,
591
+ implicitSelf: true,
592
+ });
593
+ }
594
+ else {
595
+ const recvType = resolveRecvType(callee.receiver, ctx);
596
+ edges.push(recvType ? { ...callEdge, recvType } : callEdge);
597
+ }
598
+ }
599
+ }
600
+ else if (ctx.lang === "php" && node.type === "use_declaration") {
601
+ // Trait composition inside a class body (`use HasFactory, Notifiable;`).
602
+ // Modelled as `implements`: like an interface, a trait is a contract of
603
+ // behaviour the class mixes in (Graft's Relation set has no `uses`).
604
+ for (const t of node.namedChildren) {
605
+ if (t.type === "name" || t.type === "qualified_name") {
606
+ edges.push({ source: ctx.parentId, relation: "implements", name: t.text.replace(/^.*\\/, ""), file: ctx.rel });
607
+ }
608
+ }
609
+ return;
610
+ }
611
+ else if (node.type === "identifier" &&
612
+ !isDirectCallee(node, callTypes) &&
613
+ !isDeclarationName(node)) {
614
+ const imported = ctx.importedSymbols.get(node.text);
615
+ if (imported) {
616
+ edges.push({
617
+ source: ctx.parentId,
618
+ relation: "references",
619
+ name: imported.name,
620
+ specifier: imported.specifier,
621
+ file: ctx.rel,
622
+ });
623
+ }
624
+ }
625
+ // Java anonymous class (`new Type() { … }`): tree-sitter-java has no
626
+ // `anonymous_class` node (unlike PHP) — the body is an optional `class_body`
627
+ // on `object_creation_expression`. Mint `{anonymous}` (mirroring PHP #144 /
628
+ // `{closure}`) so nested methods take that owner instead of the enclosing
629
+ // type's, which otherwise pollutes `ownerMethod` and steals real call edges
630
+ // (#161). The constructor call edge above still fires for `new Type()`.
631
+ if (ctx.lang === "java" && node.type === "object_creation_expression") {
632
+ const body = node.namedChildren.find((c) => c.type === "class_body");
633
+ if (body) {
634
+ const idPart = "{anonymous}";
635
+ const base = `${ctx.rel}#${[...ctx.scope, idPart].join(".")}`;
636
+ const id = mintId(base, minted);
637
+ out.push({
638
+ id,
639
+ name: "{anonymous}",
640
+ kind: "class",
641
+ path: ctx.rel,
642
+ span: `L${node.startPosition.row + 1}-L${node.endPosition.row + 1}`,
643
+ signature: clean(ctx.source.slice(node.startIndex, body.startIndex)),
644
+ exported: false,
645
+ origin: "ast",
646
+ body_hash: contentHash(node.text),
647
+ body_text: searchBody(node.text),
648
+ summary_state: "pending",
649
+ summary: null,
650
+ crux: null,
651
+ });
652
+ edges.push({ source: ctx.parentId, relation: "contains", targetId: id, file: ctx.rel });
653
+ // Single supertype from `new Type()`: emit `implements` so an interface
654
+ // target resolves (adapters are the common case; a class target drops
655
+ // under resolve's implements kind filter — drop-not-guess).
656
+ const superName = javaConstructedTypeName(node.childForFieldName("type"));
657
+ if (superName) {
658
+ edges.push({ source: id, relation: "implements", name: superName, file: ctx.rel });
659
+ }
660
+ const anonCtx = {
661
+ ...ctx,
662
+ scope: [...ctx.scope, idPart],
663
+ enclosingKind: "class",
664
+ parentId: id,
665
+ enclosingClass: "{anonymous}",
666
+ };
667
+ for (const child of node.namedChildren) {
668
+ walk(child, child.type === "class_body" ? anonCtx : ctx, out, edges, minted);
669
+ }
670
+ return;
671
+ }
672
+ }
673
+ for (const child of node.namedChildren)
674
+ walk(child, ctx, out, edges, minted);
675
+ }
676
+ /**
677
+ * Named imports whose local binding can be recognized later as a symbol use.
678
+ * Namespace/default imports are intentionally excluded: they do not tell us
679
+ * the exported symbol name, so wiring them would require guessing.
680
+ */
681
+ function collectImportedSymbols(root, lang) {
682
+ if (lang === "typescript" || lang === "tsx") {
683
+ const out = new Map();
684
+ const visit = (node) => {
685
+ if (node.type === "import_statement") {
686
+ const specifier = importSpecifier(node, lang);
687
+ if (!specifier)
688
+ return;
689
+ collectTsImportBindings(node, specifier, out);
690
+ return;
691
+ }
692
+ for (const child of node.namedChildren)
693
+ visit(child);
694
+ };
695
+ visit(root);
696
+ return out;
697
+ }
698
+ if (lang === "php")
699
+ return collectPhpImportedSymbols(root);
700
+ return new Map();
701
+ }
702
+ /** PHP `use` bindings: local alias → { exported name, FQN specifier }. */
703
+ function collectPhpImportedSymbols(root) {
704
+ const out = new Map();
705
+ const visit = (node) => {
706
+ if (node.type === "namespace_use_declaration") {
707
+ collectPhpUseDeclaration(node, out);
708
+ return;
709
+ }
710
+ for (const child of node.namedChildren)
711
+ visit(child);
712
+ };
713
+ visit(root);
714
+ return out;
715
+ }
716
+ function collectPhpUseDeclaration(decl, out) {
717
+ const prefix = decl.namedChildren.find((c) => c.type === "namespace_name")?.text.replace(/\\$/, "") ?? "";
718
+ const clauses = [];
719
+ for (const child of decl.namedChildren) {
720
+ if (child.type === "namespace_use_clause")
721
+ clauses.push(child);
722
+ if (child.type === "namespace_use_group") {
723
+ for (const c of child.namedChildren) {
724
+ if (c.type === "namespace_use_clause")
725
+ clauses.push(c);
726
+ }
727
+ }
728
+ }
729
+ for (const clause of clauses) {
730
+ const binding = phpUseClauseBinding(clause, prefix);
731
+ if (binding)
732
+ out.set(binding.local, { name: binding.name, specifier: binding.specifier });
733
+ }
734
+ }
735
+ function phpUseClauseBinding(clause, prefix) {
736
+ const names = clause.namedChildren.filter((c) => c.type === "name");
737
+ const qualified = clause.namedChildren.find((c) => c.type === "qualified_name");
738
+ let fqn;
739
+ let importedName;
740
+ if (qualified) {
741
+ fqn = qualified.text.replace(/^\\/, "");
742
+ importedName = fqn.replace(/^.*\\/, "");
743
+ }
744
+ else if (names[0]) {
745
+ importedName = names[0].text;
746
+ fqn = prefix ? `${prefix}\\${importedName}` : importedName;
747
+ }
748
+ else {
749
+ return null;
750
+ }
751
+ const alias = qualified && names.length >= 1
752
+ ? names[names.length - 1].text
753
+ : names.length >= 2
754
+ ? names[1].text
755
+ : undefined;
756
+ const local = alias ?? importedName;
757
+ return { local, name: importedName, specifier: fqn };
758
+ }
759
+ /** PHP 8 attributes on a definition → `references` edges to the attribute class. */
760
+ function phpAttributeReferenceEdges(node, sourceId, ctx) {
761
+ const edges = [];
762
+ for (const child of node.namedChildren) {
763
+ if (child.type !== "attribute_list")
764
+ continue;
765
+ for (const group of child.namedChildren) {
766
+ if (group.type !== "attribute_group")
767
+ continue;
768
+ for (const attr of group.namedChildren) {
769
+ if (attr.type !== "attribute")
770
+ continue;
771
+ const ref = phpAttributeClassRef(attr, ctx);
772
+ if (ref) {
773
+ edges.push({
774
+ source: sourceId,
775
+ relation: "references",
776
+ name: ref.name,
777
+ ...(ref.specifier ? { specifier: ref.specifier } : {}),
778
+ file: ctx.rel,
779
+ });
780
+ }
781
+ }
782
+ }
783
+ }
784
+ return edges;
785
+ }
786
+ function phpAttributeClassRef(attr, ctx) {
787
+ const nameNode = attr.childForFieldName("name") ??
788
+ attr.namedChildren.find((c) => c.type === "name" || c.type === "qualified_name");
789
+ if (!nameNode)
790
+ return null;
791
+ if (nameNode.type === "qualified_name") {
792
+ const fqn = nameNode.text.replace(/^\\/, "");
793
+ return { name: fqn.replace(/^.*\\/, ""), specifier: fqn };
794
+ }
795
+ const bare = nameNode.text;
796
+ const imported = ctx.importedSymbols.get(bare);
797
+ if (imported)
798
+ return { name: imported.name, specifier: imported.specifier };
799
+ return { name: bare };
800
+ }
801
+ /** Java annotations on a definition → `references` edges to the annotation type. */
802
+ function javaAnnotationReferenceEdges(node, sourceId, ctx) {
803
+ const edges = [];
804
+ const mods = node.namedChildren.find((c) => c.type === "modifiers");
805
+ if (!mods)
806
+ return edges;
807
+ for (const child of mods.namedChildren) {
808
+ if (child.type !== "marker_annotation" && child.type !== "annotation")
809
+ continue;
810
+ const name = javaAnnotationTypeName(child);
811
+ if (name) {
812
+ edges.push({
813
+ source: sourceId,
814
+ relation: "references",
815
+ name,
816
+ file: ctx.rel,
817
+ });
818
+ }
819
+ }
820
+ return edges;
821
+ }
822
+ /** The type named by `@Foo` / `@a.b.Foo(...)`. Arguments are ignored (issue #89).
823
+ * A scoped name is kept whole, matching heritage: a bare last segment would
824
+ * false-match an unrelated in-repo type (#103). */
825
+ function javaAnnotationTypeName(anno) {
826
+ const nameNode = anno.childForFieldName("name");
827
+ if (!nameNode)
828
+ return null;
829
+ if (nameNode.type === "identifier" || nameNode.type === "scoped_identifier")
830
+ return nameNode.text;
831
+ return null;
832
+ }
833
+ function collectTsImportBindings(node, specifier, out) {
834
+ if (node.type === "import_specifier") {
835
+ const name = node.childForFieldName("name")?.text;
836
+ const local = node.childForFieldName("alias")?.text ?? name;
837
+ if (name && local)
838
+ out.set(local, { name, specifier });
839
+ return;
840
+ }
841
+ for (const child of node.namedChildren)
842
+ collectTsImportBindings(child, specifier, out);
843
+ }
844
+ /**
845
+ * Do these two wrappers stand for the same syntax node? `===` does not answer that:
846
+ * node-tree-sitter materializes `SyntaxNode` objects on demand and caches them
847
+ * weakly, so reaching one node twice can return two different JS objects. Comparing
848
+ * wrappers makes a purely syntactic question depend on collector timing — two cold
849
+ * builds of unchanged source then disagree on `references` edges (#116).
850
+ *
851
+ * `id` is the stable identity, unique within one tree, so the tree is compared too.
852
+ * A `Tree` is one object per parse (unlike its nodes), so `===` is right for it.
853
+ */
854
+ function sameSyntaxNode(a, b) {
855
+ return !!a && !!b && a.tree === b.tree && a.id === b.id;
856
+ }
857
+ /**
858
+ * A parameter or local declaration wins over an import inside that function.
859
+ * Drop that imported binding for the whole function rather than create a false
860
+ * dependency. Nested functions are separate scopes and filter themselves.
861
+ */
862
+ function withoutShadowedImports(imports, definition) {
863
+ if (imports.size === 0)
864
+ return imports;
865
+ const shadowed = new Set();
866
+ const definitionValue = definition.childForFieldName("value");
867
+ const visit = (node) => {
868
+ if (!sameSyntaxNode(node, definition) && !sameSyntaxNode(node, definitionValue) && isFunctionBoundary(node)) {
869
+ const name = node.childForFieldName("name");
870
+ if (name?.type === "identifier")
871
+ shadowed.add(name.text);
872
+ return;
873
+ }
874
+ if (node.type === "variable_declarator") {
875
+ const name = node.childForFieldName("name");
876
+ if (name?.type === "identifier")
877
+ shadowed.add(name.text);
878
+ }
879
+ else if (node.type === "required_parameter" || node.type === "optional_parameter") {
880
+ const pattern = node.childForFieldName("pattern");
881
+ if (pattern?.type === "identifier")
882
+ shadowed.add(pattern.text);
883
+ }
884
+ else if (node.type === "identifier" && node.parent?.type === "formal_parameters") {
885
+ shadowed.add(node.text);
886
+ }
887
+ for (const child of node.namedChildren)
888
+ visit(child);
889
+ };
890
+ visit(definition);
891
+ if (![...shadowed].some((name) => imports.has(name)))
892
+ return imports;
893
+ return new Map([...imports].filter(([local]) => !shadowed.has(local)));
894
+ }
895
+ function isFunctionBoundary(node) {
896
+ return (node.type === "function_declaration" ||
897
+ node.type === "generator_function_declaration" ||
898
+ node.type === "method_definition" ||
899
+ node.type === "arrow_function" ||
900
+ node.type === "function_expression" ||
901
+ node.type === "function");
902
+ }
903
+ /** A direct invocation already emits a stronger `calls` edge. Java names the callee
904
+ * in a `name` field (there is no `function` field on `method_invocation`), so both
905
+ * spellings count. */
906
+ function isDirectCallee(node, callTypes) {
907
+ const parent = node.parent;
908
+ if (!parent || !callTypes.has(parent.type))
909
+ return false;
910
+ return (sameSyntaxNode(parent.childForFieldName("function"), node) ||
911
+ sameSyntaxNode(parent.childForFieldName("name"), node));
912
+ }
913
+ /** Definition/declaration identifiers name a new binding; they do not use one. */
914
+ function isDeclarationName(node) {
915
+ const parent = node.parent;
916
+ return sameSyntaxNode(parent?.childForFieldName("name"), node);
917
+ }
918
+ /** Recognize the definition shapes: mapped node types, Go's type/method forms, and
919
+ * TS arrow-consts. */
920
+ function describe(node, ctx) {
921
+ if (ctx.lang === "go")
922
+ return describeGo(node, ctx);
923
+ if (ctx.lang === "r")
924
+ return describeR(node, ctx);
925
+ if (ctx.lang === "java")
926
+ return describeJava(node, ctx);
927
+ if (ctx.lang === "kotlin")
928
+ return describeKotlin(node, ctx);
929
+ if (ctx.lang === "swift")
930
+ return describeSwift(node, ctx);
931
+ // PHP closures: `$h = function () {…}` / `fn() => …`, and bare callbacks
932
+ // (`$routes->get('/x', function () {…})`). Captured as function nodes so a
933
+ // closure-only file (a routing table, a DI container) keeps its structure
934
+ // and the calls inside attribute to the closure, not the file.
935
+ if (ctx.lang === "php" && (node.type === "anonymous_function" || node.type === "arrow_function")) {
936
+ const body = node.childForFieldName("body");
937
+ return {
938
+ name: phpClosureName(node),
939
+ kind: "function",
940
+ headerEnd: body ? body.startIndex : node.endIndex,
941
+ hashNode: node,
942
+ };
943
+ }
944
+ // PHP anonymous classes (`new class implements I {…}`): minted as a class
945
+ // node named `{anonymous}` (mirroring `{closure}`, deduplicated per file by
946
+ // mintId). Without this the type vanished — no node, no heritage edge — and
947
+ // its methods mis-attributed to the enclosing function (issue #144). The
948
+ // class kind makes the walk emit heritageEdges (base_clause /
949
+ // class_interface_clause are direct children) and own the nested methods.
950
+ if (ctx.lang === "php" && node.type === "anonymous_class") {
951
+ const body = node.childForFieldName("body");
952
+ return {
953
+ name: "{anonymous}",
954
+ kind: "class",
955
+ headerEnd: body ? body.startIndex : node.endIndex,
956
+ hashNode: node,
957
+ };
958
+ }
959
+ const mapped = ctx.kinds[node.type];
960
+ if (mapped) {
961
+ const name = node.childForFieldName("name")?.text;
962
+ if (!name)
963
+ return null;
964
+ let kind = mapped;
965
+ if (ctx.lang === "python" && mapped === "function" && ctx.enclosingKind === "class") {
966
+ kind = "method";
967
+ }
968
+ // tree-sitter-php 0.23.x recovers a collapsed enum method as function_definition
969
+ // at program scope; walkNamedChildren reparents it under the enum, and this
970
+ // promotion is what keeps the kind `method` rather than a leaked `function`.
971
+ if (ctx.lang === "php" && mapped === "function" && ctx.enclosingKind === "enum") {
972
+ kind = "method";
973
+ }
974
+ const body = node.childForFieldName("body");
975
+ return { name, kind, headerEnd: body ? body.startIndex : node.endIndex, hashNode: node };
976
+ }
977
+ // TS: `const foo = (…) => …` / `const foo = function () {}`
978
+ if ((ctx.lang === "typescript" || ctx.lang === "tsx") && node.type === "variable_declarator") {
979
+ const value = node.childForFieldName("value");
980
+ if (value && FUNCTION_VALUE_TYPES.has(value.type)) {
981
+ const name = node.childForFieldName("name")?.text;
982
+ if (!name)
983
+ return null;
984
+ const vbody = value.childForFieldName("body");
985
+ return {
986
+ name,
987
+ kind: "function",
988
+ headerEnd: vbody ? vbody.startIndex : node.endIndex,
989
+ hashNode: node,
990
+ };
991
+ }
992
+ }
993
+ return null;
994
+ }
995
+ /** Go definition shapes: top-level funcs, receiver methods, and named types
996
+ * (struct / interface / type alias). Methods carry no nesting — they're qualified
997
+ * by their receiver type (`User.Save`) so calls can resolve and cards read clearly. */
998
+ function describeGo(node, _ctx) {
999
+ if (node.type === "function_declaration") {
1000
+ const name = node.childForFieldName("name")?.text;
1001
+ if (!name)
1002
+ return null;
1003
+ const body = node.childForFieldName("body");
1004
+ return { name, kind: "function", headerEnd: body ? body.startIndex : node.endIndex, hashNode: node };
1005
+ }
1006
+ if (node.type === "method_declaration") {
1007
+ const name = node.childForFieldName("name")?.text;
1008
+ if (!name)
1009
+ return null;
1010
+ const recv = goReceiverType(node);
1011
+ const body = node.childForFieldName("body");
1012
+ // Bare `name` (so `recv.Method()` calls resolve); receiver-qualified `idName`
1013
+ // (so the id is `file.go#Receiver.Method` and stays unique per receiver).
1014
+ return {
1015
+ name,
1016
+ idName: recv ? `${recv}.${name}` : name,
1017
+ kind: "method",
1018
+ headerEnd: body ? body.startIndex : node.endIndex,
1019
+ hashNode: node,
1020
+ };
1021
+ }
1022
+ // `type Name <shape>` — one type_spec per name (grouped `type ( … )` yields several).
1023
+ if (node.type === "type_spec") {
1024
+ const name = node.childForFieldName("name")?.text;
1025
+ if (!name)
1026
+ return null;
1027
+ const type = node.childForFieldName("type");
1028
+ const kind = type?.type === "struct_type" ? "struct" : type?.type === "interface_type" ? "interface" : "type";
1029
+ // Header ends where the body opens (`{`) for struct/interface, else the whole node
1030
+ // (a one-line alias like `type ID int`).
1031
+ const headerEnd = type && (kind === "struct" || kind === "interface") ? type.startIndex : node.endIndex;
1032
+ return { name, kind, headerEnd, hashNode: node };
1033
+ }
1034
+ return null;
1035
+ }
1036
+ /**
1037
+ * R definition shapes. `function_definition` carries no name field at all, so
1038
+ * unlike every other supported language the name always comes from an
1039
+ * enclosing assignment, detected here. The plain-function assignment check
1040
+ * (op-filtering `binary_operator`, the right-assign body-swap) is duplicated
1041
+ * in bindings.ts's own `rDefName` rather than imported — same reasoning as
1042
+ * this file's Go receiver helpers: bindings.ts can't take a value import back
1043
+ * on extract.ts. bindings.ts doesn't need the S3/S4/R6 half of this at all
1044
+ * (no handleR binding collector exists — see bindings.ts's own doc comment).
1045
+ *
1046
+ * Phase 1 (flat extraction — every named function is a plain `function` node)
1047
+ * plus Phase 2 (S3/S4/R6 class awareness, R's class systems being library
1048
+ * *convention* rather than grammar syntax, unlike every other language graft
1049
+ * supports):
1050
+ * - left-assign (`<-`/`<<-`/`=`) / right-assign (`->`/`->>`) function
1051
+ * assignment — Phase 1's shape, see the two `binary_operator`/
1052
+ * `function_definition` branches below. Right-assign's AST shape does NOT
1053
+ * mirror left-assign's the way it looks like it should (confirmed
1054
+ * empirically, not assumed — R's `->` has low enough precedence that it's
1055
+ * absorbed into the function's own `body` field as a `binary_operator`
1056
+ * instead of the function sitting inside an outer wrapper); only an
1057
+ * explicitly parenthesized `(function() {}) -> foo` produces the
1058
+ * "expected" outer-wrapping shape, which isn't specially handled (falls
1059
+ * through as an anonymous function).
1060
+ * - `name.Class <- function() {}` — an S3 method, IF `name` is a known
1061
+ * generic (registered locally via `UseMethod()` in this file, or one of a
1062
+ * curated set of common base-R generics — see `rS3Split`'s doc comment
1063
+ * for the false-positive risk this guards against).
1064
+ * - `Foo <- R6::R6Class("Foo", public = list(...), private = list(...))` —
1065
+ * an R6 class; its `public =`/`private =`/`active =` list entries become
1066
+ * methods, handled by walk()'s own `argument`-node interception (this
1067
+ * function only recognizes the class itself; the "a call defines a
1068
+ * symbol" list-walking lives in walk() since it needs to mint several
1069
+ * nodes, not describe a single one).
1070
+ * - `Foo <- list(public = list(...), private = list(...))` (Phase 5) — a
1071
+ * plain-list "mixin"/"extension" bundle, NOT wrapped in `R6::R6Class(...)`
1072
+ * at all: a real, deliberate convention found dogfooding against a real
1073
+ * R6-heavy corpus (25 files, 11 of them entirely invisible to the graph
1074
+ * without this) for sharing method bundles across classes via splicing
1075
+ * (`public = c(Foo$public, list(...))`) rather than `inherit =`. Only
1076
+ * recognized when the list actually has a `public =`/`private =` entry
1077
+ * (see `rIsMixinContainer`) — an ordinary data/config list never matches.
1078
+ * Reuses kind "class" (nothing better-fitting exists, and everything
1079
+ * downstream — the method-list walking, visibility — only cares that
1080
+ * ctx.enclosingKind is "class", not how the container was spelled); no
1081
+ * heritage edge, since splicing isn't `inherit =`-based inheritance.
1082
+ * - `setClass("Foo", ...)` / `setMethod("generic", "Foo", function() {})`
1083
+ * — S4 class/method calls, recognized as bare top-level `call` nodes
1084
+ * (setClass/setMethod have side effects registering with the S4 system;
1085
+ * they're essentially never assigned to a variable). `setGeneric()` is
1086
+ * NOT specially extracted — it doesn't naturally map to a class or method
1087
+ * kind, and the plan flags it as a case not worth the design risk.
1088
+ */
1089
+ function describeR(node, ctx) {
1090
+ if (node.type === "binary_operator") {
1091
+ const op = node.childForFieldName("operator")?.text;
1092
+ if (!op || !R_ASSIGN_OPS.has(op))
1093
+ return null;
1094
+ const lhs = node.childForFieldName("lhs");
1095
+ const rhs = node.childForFieldName("rhs");
1096
+ if (lhs?.type !== "identifier")
1097
+ return null;
1098
+ if (rhs?.type === "function_definition") {
1099
+ return rFunctionDescriptor(lhs.text, rhs, rhs.childForFieldName("body"), ctx);
1100
+ }
1101
+ if (rhs?.type === "call" && rCalleeName(rhs) === "R6Class") {
1102
+ // The class node itself; its public=/private=/active= method lists are
1103
+ // handled by walk()'s own `argument`-node interception, not here.
1104
+ return { name: lhs.text, kind: "class", headerEnd: rhs.endIndex, hashNode: rhs };
1105
+ }
1106
+ if (rhs?.type === "call" && rCalleeName(rhs) === "list" && rIsMixinContainer(rhs)) {
1107
+ // Phase 5: a plain-list mixin/extension bundle — same treatment as R6Class.
1108
+ return { name: lhs.text, kind: "class", headerEnd: rhs.endIndex, hashNode: rhs };
1109
+ }
1110
+ return null;
1111
+ }
1112
+ if (node.type === "function_definition") {
1113
+ // Right-assign (`function() {} -> foo`): see this function's own doc
1114
+ // comment for why this doesn't mirror the binary_operator branch above.
1115
+ const body = node.childForFieldName("body");
1116
+ if (body?.type !== "binary_operator")
1117
+ return null;
1118
+ const op = body.childForFieldName("operator")?.text;
1119
+ if (!op || !R_RIGHT_ASSIGN_OPS.has(op))
1120
+ return null;
1121
+ const rhs = body.childForFieldName("rhs");
1122
+ if (rhs?.type !== "identifier")
1123
+ return null;
1124
+ return rFunctionDescriptor(rhs.text, node, body.childForFieldName("lhs"), ctx);
1125
+ }
1126
+ if (node.type === "call") {
1127
+ return describeRTopLevelCall(node);
1128
+ }
1129
+ // R6 (Phase 2): reached via walk()'s own `argument`-node interception for a
1130
+ // `public =`/`private =`/`active =` list entry — see the special case there
1131
+ // for why this can't just be a flat kind-table/node-type check like every
1132
+ // other definition shape.
1133
+ if (node.type === "argument" && ctx.rR6Access !== null) {
1134
+ const argName = node.childForFieldName("name");
1135
+ const value = node.childForFieldName("value");
1136
+ if (argName?.type !== "identifier" || value?.type !== "function_definition")
1137
+ return null;
1138
+ const body = value.childForFieldName("body");
1139
+ return {
1140
+ name: argName.text,
1141
+ kind: "method",
1142
+ headerEnd: body ? body.startIndex : value.endIndex,
1143
+ hashNode: value,
1144
+ // owner deliberately unset — R6 methods DO lexically nest inside the
1145
+ // class-defining call, so ctx.enclosingClass already has it.
1146
+ };
1147
+ }
1148
+ return null;
1149
+ }
1150
+ const R_ASSIGN_OPS = new Set(["<-", "<<-", "="]);
1151
+ const R_RIGHT_ASSIGN_OPS = new Set(["->", "->>"]);
1152
+ /** A plain function assignment (left- or right-assign), OR — if `name` matches
1153
+ * a known S3 generic's `generic.Class` pattern — an S3 method instead. `body`
1154
+ * is the function's REAL content node (already resolved by the caller for
1155
+ * either assignment direction), used only for `headerEnd`; `hashNode` is
1156
+ * always the `function_definition` itself. */
1157
+ function rFunctionDescriptor(name, hashNode, body, ctx) {
1158
+ const headerEnd = body ? body.startIndex : hashNode.endIndex;
1159
+ const s3 = rS3Split(name, ctx.rGenerics);
1160
+ if (s3) {
1161
+ return {
1162
+ name: s3.generic,
1163
+ idName: `${s3.className}.${s3.generic}`,
1164
+ kind: "method",
1165
+ headerEnd,
1166
+ hashNode,
1167
+ owner: s3.className,
1168
+ };
1169
+ }
1170
+ return { name, kind: "function", headerEnd, hashNode };
1171
+ }
1172
+ /**
1173
+ * S3 dispatch detection: does `name` split as `generic.Class` for some KNOWN
1174
+ * generic? Tries the longest possible generic prefix first (so a dotted
1175
+ * generic itself, like `as.character`, is found before a shorter false match)
1176
+ * and only ever matches a generic that's either registered locally via
1177
+ * `UseMethod()` in this file (see `collectRGenerics`) or in the small curated
1178
+ * `R_BASE_GENERICS` set below.
1179
+ *
1180
+ * This is the genuinely ambiguous part of R support the plan calls out:
1181
+ * `read.csv`, `data.frame`, and `as.character` used as an ordinary helper
1182
+ * name are NOT S3 dispatch, and nothing in the grammar distinguishes them
1183
+ * from `print.MyClass`. Erring toward the curated set staying small — a
1184
+ * missed S3 method (false negative, falls back to an ordinary `function`
1185
+ * node) is a much smaller problem than a false positive misfiling an
1186
+ * unrelated dotted-name function as some other class's method.
1187
+ */
1188
+ function rS3Split(name, generics) {
1189
+ const parts = name.split(".");
1190
+ if (parts.length < 2)
1191
+ return null;
1192
+ for (let i = parts.length - 1; i >= 1; i--) {
1193
+ const generic = parts.slice(0, i).join(".");
1194
+ if (generics.has(generic) || R_BASE_GENERICS.has(generic)) {
1195
+ return { generic, className: parts.slice(i).join(".") };
1196
+ }
1197
+ }
1198
+ return null;
1199
+ }
1200
+ /** Common base-R S3 generics worth assuming even without local evidence —
1201
+ * print.Foo/format.Foo etc. are the single most common real-world S3
1202
+ * pattern, and a local `UseMethod()` call will never exist for them (they
1203
+ * ship in base/methods/stats, not the user's own repo). Deliberately small
1204
+ * and unsurprising rather than exhaustive — see `rS3Split`'s doc comment. */
1205
+ const R_BASE_GENERICS = new Set([
1206
+ "print",
1207
+ "format",
1208
+ "summary",
1209
+ "plot",
1210
+ "str",
1211
+ "toString",
1212
+ "as.character",
1213
+ "as.list",
1214
+ "as.data.frame",
1215
+ "as.vector",
1216
+ "as.numeric",
1217
+ "as.matrix",
1218
+ "length",
1219
+ "dim",
1220
+ "names",
1221
+ "rev",
1222
+ "sort",
1223
+ "unique",
1224
+ "predict",
1225
+ "coef",
1226
+ "residuals",
1227
+ "fitted",
1228
+ "update",
1229
+ "merge",
1230
+ "all.equal",
1231
+ "anova",
1232
+ "confint",
1233
+ "vcov",
1234
+ "logLik",
1235
+ ]);
1236
+ /** Every S3 generic THIS file registers via a local `UseMethod()` call, so
1237
+ * `rS3Split` can recognize `generic.Class` methods for a repo's own generics,
1238
+ * not just the base-R ones. Runs once per file, ahead of the main walk (same
1239
+ * pre-pass shape as `collectImportedSymbols`). Cross-file generics — a
1240
+ * generic defined in one file, dispatched on in another — aren't found this
1241
+ * way; that would need a whole-repo pass extractFile has no visibility into,
1242
+ * the same limitation Go/C++'s per-file bindings already accept. */
1243
+ function collectRGenerics(root) {
1244
+ const generics = new Set();
1245
+ const visit = (node) => {
1246
+ let fnDef = null;
1247
+ let ownName = null;
1248
+ if (node.type === "binary_operator") {
1249
+ const op = node.childForFieldName("operator")?.text;
1250
+ const lhs = node.childForFieldName("lhs");
1251
+ const rhs = node.childForFieldName("rhs");
1252
+ if (op && R_ASSIGN_OPS.has(op) && lhs?.type === "identifier" && rhs?.type === "function_definition") {
1253
+ fnDef = rhs;
1254
+ ownName = lhs.text;
1255
+ }
1256
+ }
1257
+ else if (node.type === "function_definition") {
1258
+ const body = node.childForFieldName("body");
1259
+ if (body?.type === "binary_operator") {
1260
+ const op = body.childForFieldName("operator")?.text;
1261
+ const rhs = body.childForFieldName("rhs");
1262
+ if (op && R_RIGHT_ASSIGN_OPS.has(op) && rhs?.type === "identifier") {
1263
+ fnDef = node;
1264
+ ownName = rhs.text;
1265
+ }
1266
+ }
1267
+ }
1268
+ if (fnDef && ownName) {
1269
+ const arg = findUseMethodArg(fnDef.childForFieldName("body"));
1270
+ if (arg !== undefined)
1271
+ generics.add(arg || ownName); // "" means UseMethod() with no args
1272
+ }
1273
+ for (const child of node.namedChildren)
1274
+ visit(child);
1275
+ };
1276
+ visit(root);
1277
+ return generics;
1278
+ }
1279
+ /** Searches a function body for a `UseMethod(...)` call and returns its
1280
+ * string-literal generic-name argument, `""` if called with no arguments
1281
+ * (defaults to the enclosing function's own name), or `undefined` if no
1282
+ * `UseMethod` call is found at all. */
1283
+ function findUseMethodArg(node) {
1284
+ if (!node)
1285
+ return undefined;
1286
+ if (node.type === "call" && rCalleeName(node) === "UseMethod") {
1287
+ const first = rCallArgs(node)[0]?.childForFieldName("value");
1288
+ return first?.type === "string" ? (rStringContent(first) ?? "") : "";
1289
+ }
1290
+ for (const child of node.namedChildren) {
1291
+ const found = findUseMethodArg(child);
1292
+ if (found !== undefined)
1293
+ return found;
1294
+ }
1295
+ return undefined;
1296
+ }
1297
+ /** S4: `setClass("Foo", ...)` → a class; `setMethod("generic", "Foo",
1298
+ * function() {})` → a method owned by "Foo". Both are ordinary top-level
1299
+ * `call` nodes — S4's registration functions have side effects and are
1300
+ * essentially never assigned to a variable, unlike R6Class. */
1301
+ function describeRTopLevelCall(node) {
1302
+ const callee = rCalleeName(node);
1303
+ if (callee === "setClass") {
1304
+ const first = rCallArgs(node)[0]?.childForFieldName("value");
1305
+ const name = first?.type === "string" ? rStringContent(first) : null;
1306
+ if (!name)
1307
+ return null;
1308
+ return { name, kind: "class", headerEnd: node.endIndex, hashNode: node };
1309
+ }
1310
+ if (callee === "setMethod") {
1311
+ const args = rCallArgs(node);
1312
+ const generic = args[0] ? rStringContent(args[0].childForFieldName("value") ?? null) : null;
1313
+ const className = args[1] ? rStringContent(args[1].childForFieldName("value") ?? null) : null;
1314
+ const defArg = args.find((a) => a.childForFieldName("name")?.text === "definition") ?? args[2];
1315
+ const fnDef = defArg?.childForFieldName("value");
1316
+ if (!generic || !className || fnDef?.type !== "function_definition")
1317
+ return null;
1318
+ const body = fnDef.childForFieldName("body");
1319
+ return {
1320
+ name: generic,
1321
+ idName: `${className}.${generic}`,
1322
+ kind: "method",
1323
+ headerEnd: body ? body.startIndex : fnDef.endIndex,
1324
+ hashNode: fnDef,
1325
+ owner: className,
1326
+ };
1327
+ }
1328
+ return null;
1329
+ }
1330
+ /** A call's callee name, whether bare (`R6Class(...)`) or namespace-qualified
1331
+ * (`R6::R6Class(...)`). Null if the callee isn't a simple name (e.g. itself a
1332
+ * call, or a `$`-based access). */
1333
+ function rCalleeName(node) {
1334
+ const fn = node.childForFieldName("function");
1335
+ if (fn?.type === "identifier")
1336
+ return fn.text;
1337
+ if (fn?.type === "namespace_operator") {
1338
+ const rhs = fn.childForFieldName("rhs");
1339
+ return rhs?.type === "identifier" ? rhs.text : null;
1340
+ }
1341
+ return null;
1342
+ }
1343
+ /** A call's positional/named `argument` children (skipping the `,`/`(`/`)`
1344
+ * punctuation tokens that share the `arguments` node's child list). */
1345
+ function rCallArgs(node) {
1346
+ return node.childForFieldName("arguments")?.namedChildren.filter((c) => c.type === "argument") ?? [];
1347
+ }
1348
+ /** An R `string` node's unquoted text, or null if `node` isn't a string. */
1349
+ function rStringContent(node) {
1350
+ if (node?.type !== "string")
1351
+ return null;
1352
+ const content = node.namedChildren.find((c) => c.type === "string_content");
1353
+ return content?.text ?? null;
1354
+ }
1355
+ /** The value of a call's named argument (`setClass("Foo", contains = "Base")`
1356
+ * → the `contains` argument), or null if absent. */
1357
+ function rNamedArg(node, argName) {
1358
+ const arg = rCallArgs(node).find((a) => a.childForFieldName("name")?.text === argName);
1359
+ return arg?.childForFieldName("value") ?? null;
1360
+ }
1361
+ /** An R6 class-defining node's `inherit =` parent class name (a bare
1362
+ * identifier — the parent's own generator variable, not a string), for
1363
+ * `super$` call resolution (Phase 3). `node` is whatever describeR matched: a
1364
+ * binary_operator for R6 (`Foo <- R6::R6Class(...)`) or, for any other kind of
1365
+ * class (S4's setClass, which has no `super`), the call itself — always null
1366
+ * there since `rCalleeName(call) !== "R6Class"`. */
1367
+ function rR6ParentClass(node) {
1368
+ const call = node.type === "binary_operator" ? node.childForFieldName("rhs") : node;
1369
+ if (call?.type !== "call" || rCalleeName(call) !== "R6Class")
1370
+ return null;
1371
+ const value = rNamedArg(call, "inherit");
1372
+ return value?.type === "identifier" ? value.text : null;
1373
+ }
1374
+ /** Does this `list(...)` call look like a Phase-5 mixin/extension bundle —
1375
+ * i.e. does it have a `public =` or `private =` entry whose own value is
1376
+ * itself a `list(...)` call? This is the one check standing between "class-
1377
+ * like container" and an ordinary data/config list (`list(a = 1, b = 2)`,
1378
+ * or even one that happens to have a field named "public" holding something
1379
+ * else) — real code never coincidentally shapes plain data this way, so it's
1380
+ * a safe, precise signal without needing a naming-convention heuristic. */
1381
+ function rIsMixinContainer(node) {
1382
+ return rCallArgs(node).some((a) => {
1383
+ const name = a.childForFieldName("name")?.text;
1384
+ if (name !== "public" && name !== "private")
1385
+ return false;
1386
+ const value = a.childForFieldName("value");
1387
+ return value?.type === "call" && rCalleeName(value) === "list";
1388
+ });
1389
+ }
1390
+ /** A base-class name list from either a bare string (`contains = "Base"`) or
1391
+ * a `c(...)` call of strings (`contains = c("Base1", "Base2")`) — S4's
1392
+ * multiple-inheritance form. */
1393
+ function rStringOrCVector(value) {
1394
+ if (!value)
1395
+ return [];
1396
+ const single = rStringContent(value);
1397
+ if (single)
1398
+ return [single];
1399
+ if (value.type === "call" && rCalleeName(value) === "c") {
1400
+ return rCallArgs(value)
1401
+ .map((a) => rStringContent(a.childForFieldName("value")))
1402
+ .filter((s) => !!s);
1403
+ }
1404
+ return [];
1405
+ }
1406
+ /** R6 visibility follows the `public =`/`private =`/`active =` section a
1407
+ * method was declared in (see walk()'s `argument`-node interception) —
1408
+ * `ctx.rR6Access` is only set for that direct span, so a plain function or an
1409
+ * S3/S4 method (neither of which has a real visibility concept) checks for a
1410
+ * roxygen `@export` tag next (Phase 3), falling back to the leading-dot naming
1411
+ * convention only when there's no roxygen evidence to go on at all. `node` is
1412
+ * the exact node describeR matched — see `rRoxygenExported`'s doc comment for
1413
+ * why that's always the right one to check for a preceding comment block. */
1414
+ function rExported(name, ctx, node) {
1415
+ if (ctx.rR6Access !== null)
1416
+ return ctx.rR6Access !== "private";
1417
+ const roxygen = rRoxygenExported(node);
1418
+ if (roxygen !== null)
1419
+ return roxygen;
1420
+ return !name.startsWith(".");
1421
+ }
1422
+ /**
1423
+ * Roxygen `@export` detection (Phase 3): does `node` — a top-level definition
1424
+ * statement (a `binary_operator` assignment, a right-assigned
1425
+ * `function_definition`, or an S4 `setClass`/`setMethod` call) — have a
1426
+ * roxygen doc block immediately preceding it, and if so, is it tagged
1427
+ * `@export`?
1428
+ *
1429
+ * `comment` is a grammar EXTRA in this grammar (floats loosely between
1430
+ * sibling nodes rather than attaching to "the next statement" via a field),
1431
+ * so this walks backward through `previousNamedSibling` collecting a
1432
+ * contiguous run of `comment` nodes — the run ends at the first non-comment
1433
+ * sibling, or at the first comment that isn't itself a roxygen (`#'`) line,
1434
+ * either of which is roxygen's own "this block documents the next statement"
1435
+ * boundary.
1436
+ *
1437
+ * Returns:
1438
+ * - `true` — a roxygen block was found and it contains `@export`.
1439
+ * - `false` — a roxygen block was found but it does NOT contain `@export`.
1440
+ * This is deliberately a confident "not exported," not "unknown": roxygen
1441
+ * generates a package's NAMESPACE from exactly its `@export`-tagged items,
1442
+ * so a documented-but-untagged function is an explicit "internal, for
1443
+ * maintainers only" signal, not an absence of evidence.
1444
+ * - `null` — no roxygen block at all, so the caller should fall back to the
1445
+ * leading-dot naming convention instead of guessing.
1446
+ */
1447
+ function rRoxygenExported(node) {
1448
+ let sib = node.previousNamedSibling;
1449
+ let sawRoxygen = false;
1450
+ let exported = false;
1451
+ while (sib?.type === "comment") {
1452
+ const text = sib.text.trim();
1453
+ if (!text.startsWith("#'"))
1454
+ break; // an ordinary # comment ends the roxygen block
1455
+ sawRoxygen = true;
1456
+ if (/^#'\s*@export\b/.test(text))
1457
+ exported = true;
1458
+ sib = sib.previousNamedSibling;
1459
+ }
1460
+ return sawRoxygen ? exported : null;
1461
+ }
1462
+ /** Java definition shapes. Uniform in a way Go's are not: every declaration carries
1463
+ * a `name` field and (for types and most members) a `body`, so one mapped lookup
1464
+ * covers classes, interfaces, enums, records, methods, and constructors. Methods are
1465
+ * lexically nested in their type, so — unlike Go — they need no receiver qualification. */
1466
+ function describeJava(node, ctx) {
1467
+ const mapped = ctx.kinds[node.type];
1468
+ if (!mapped)
1469
+ return null;
1470
+ const name = node.childForFieldName("name")?.text;
1471
+ if (!name)
1472
+ return null;
1473
+ const body = node.childForFieldName("body");
1474
+ const desc = {
1475
+ name,
1476
+ kind: mapped,
1477
+ headerEnd: body ? body.startIndex : node.endIndex,
1478
+ hashNode: node,
1479
+ };
1480
+ // Only callables carry arity. A record declaration also has a `parameters` node,
1481
+ // but its components are not an overload set and must never be filtered against.
1482
+ if (node.type === "method_declaration" || node.type === "constructor_declaration") {
1483
+ const params = node.childForFieldName("parameters");
1484
+ if (params) {
1485
+ const declared = params.namedChildren.filter((c) => c.type === "formal_parameter" || c.type === "spread_parameter");
1486
+ desc.arity = declared.length;
1487
+ if (declared.some((c) => c.type === "spread_parameter"))
1488
+ desc.variadic = true;
1489
+ }
1490
+ }
1491
+ return desc;
1492
+ }
1493
+ /** Kotlin definition shapes. Unlike Java's, tree-sitter-kotlin exposes no `name`
1494
+ * or `body` fields: a definition's name is an unnamed `simple_identifier` (functions)
1495
+ * or `type_identifier` (types) child, and its body is a `class_body` / `function_body`
1496
+ * / `statements` child. `class_declaration` also folds classes, interfaces, and enum
1497
+ * classes into one node type — the kind is read off the declaration's own keywords. */
1498
+ function describeKotlin(node, ctx) {
1499
+ // The first direct `type_identifier` is the declared name (type parameters, primary
1500
+ // constructor parameters and delegation specifiers are all nested beneath it).
1501
+ const typeName = () => node.namedChildren.find((c) => c.type === "type_identifier")?.text ?? null;
1502
+ // The first direct `simple_identifier` is the function name (receiver type, params
1503
+ // and type parameters are all nested beneath other child nodes).
1504
+ const funcName = () => node.namedChildren.find((c) => c.type === "simple_identifier")?.text ?? null;
1505
+ // `class X : A, B()` heritage lives in `delegation_specifier` children; a nested
1506
+ // type parameter's identifier is one of the same node type, so only direct children
1507
+ // count as the declared name.
1508
+ const headEnd = (type) => {
1509
+ const body = node.namedChildren.find((c) => c.type === type);
1510
+ return body ? body.startIndex : node.endIndex;
1511
+ };
1512
+ if (node.type === "class_declaration") {
1513
+ const name = typeName();
1514
+ if (!name)
1515
+ return null;
1516
+ let kind = "class";
1517
+ if (node.namedChildren.some((c) => c.type === "enum_class_body"))
1518
+ kind = "enum";
1519
+ else if (node.children.some((c) => c.type === "interface"))
1520
+ kind = "interface";
1521
+ else {
1522
+ const mods = node.namedChildren.find((c) => c.type === "modifiers");
1523
+ // `annotation class` → the interface role Java's annotation_type_declaration plays.
1524
+ if (mods?.namedChildren.some((c) => c.type === "class_modifier" && c.text === "annotation"))
1525
+ kind = "interface";
1526
+ }
1527
+ const body = node.namedChildren.find((c) => c.type === "class_body" || c.type === "enum_class_body");
1528
+ return { name, kind, headerEnd: body ? body.startIndex : node.endIndex, hashNode: node };
1529
+ }
1530
+ if (node.type === "object_declaration") {
1531
+ const name = typeName();
1532
+ if (!name)
1533
+ return null;
1534
+ return { name, kind: "class", headerEnd: headEnd("class_body"), hashNode: node };
1535
+ }
1536
+ if (node.type === "function_declaration") {
1537
+ const name = funcName();
1538
+ if (!name)
1539
+ return null;
1540
+ const kind = KOTLIN_TYPE_KINDS.has(ctx.enclosingKind ?? "file") ? "method" : "function";
1541
+ return { name, kind, headerEnd: headEnd("function_body"), hashNode: node };
1542
+ }
1543
+ if (node.type === "secondary_constructor") {
1544
+ // Constructors carry no name of their own — they are the class's own, so scope the
1545
+ // node under the enclosing class the same way Java's constructor_declaration does.
1546
+ if (!ctx.enclosingClass)
1547
+ return null;
1548
+ return {
1549
+ name: ctx.enclosingClass,
1550
+ kind: "method",
1551
+ headerEnd: headEnd("statements"),
1552
+ hashNode: node,
1553
+ };
1554
+ }
1555
+ if (node.type === "type_alias") {
1556
+ const name = typeName();
1557
+ if (!name)
1558
+ return null;
1559
+ return { name, kind: "type", headerEnd: node.endIndex, hashNode: node };
1560
+ }
1561
+ if (node.type === "property_declaration") {
1562
+ // Top-level `val`/`var` only — a class property is a field, not a definition node
1563
+ // (no depth tier emits fields), so it must not become one.
1564
+ if (ctx.enclosingKind !== null)
1565
+ return null;
1566
+ const decl = node.namedChildren.find((c) => c.type === "variable_declaration");
1567
+ const name = decl?.namedChildren.find((c) => c.type === "simple_identifier")?.text;
1568
+ if (!name)
1569
+ return null;
1570
+ return { name, kind: "variable", headerEnd: node.endIndex, hashNode: node };
1571
+ }
1572
+ return null;
1573
+ }
1574
+ /** Swift definition shapes. Like Kotlin's, tree-sitter-swift exposes no `name` or
1575
+ * `body` fields: a definition's name is a direct `type_identifier` (types) or
1576
+ * `simple_identifier` (functions) child, and its body is a `class_body` /
1577
+ * `enum_class_body` / `protocol_body` / `function_body` child. One
1578
+ * `class_declaration` node type covers `class`, `struct`, `enum`, `actor` AND
1579
+ * `extension` — the declaration's own keyword token tells them apart. */
1580
+ function describeSwift(node, ctx) {
1581
+ // The first direct `type_identifier` is the declared name (generic parameters and
1582
+ // inheritance specifiers are all nested beneath other child nodes).
1583
+ const typeName = () => node.namedChildren.find((c) => c.type === "type_identifier")?.text ?? null;
1584
+ // The first direct `simple_identifier` is the function name (parameters and
1585
+ // generic parameters are all nested beneath other child nodes).
1586
+ const funcName = () => node.namedChildren.find((c) => c.type === "simple_identifier")?.text ?? null;
1587
+ const headEnd = (...types) => {
1588
+ const body = node.namedChildren.find((c) => types.includes(c.type));
1589
+ return body ? body.startIndex : node.endIndex;
1590
+ };
1591
+ if (node.type === "class_declaration") {
1592
+ const kw = node.children.find((c) => c.type === "class" ||
1593
+ c.type === "struct" ||
1594
+ c.type === "enum" ||
1595
+ c.type === "actor" ||
1596
+ c.type === "extension")?.type;
1597
+ let name;
1598
+ if (kw === "extension") {
1599
+ // `extension Point { … }` has no name of its own — the extended type's IS its
1600
+ // identity, so the node takes that name: members mint as `Point.method`,
1601
+ // `enclosingClass` becomes `Point`, and a member call on a Point receiver
1602
+ // resolves to them exactly as if they were declared on the type. A qualified
1603
+ // target (`extension Swift.Array`) reduces to its last component, matching
1604
+ // how the extended type is itself named in the graph.
1605
+ //
1606
+ // Its KIND is "module", not "class": the type usually already has a real
1607
+ // declaration, and a second same-named "class" node would make the name
1608
+ // AMBIGUOUS to resolveName — every `Point(...)` initializer call and every
1609
+ // `: Point` heritage target would then drop instead of resolving (resolve
1610
+ // never guesses between same-named candidates). "module" keeps the node out
1611
+ // of type-name resolution entirely while SWIFT_TYPE_KINDS still makes it
1612
+ // own its members; typed member calls are untouched either way, since they
1613
+ // go through the owner-qualified method index, not the type's own node.
1614
+ const ut = node.namedChildren.find((c) => c.type === "user_type");
1615
+ const ids = ut?.namedChildren.filter((c) => c.type === "type_identifier") ?? [];
1616
+ name = ids.length ? ids[ids.length - 1].text : null;
1617
+ }
1618
+ else {
1619
+ name = typeName();
1620
+ }
1621
+ if (!name)
1622
+ return null;
1623
+ // An actor is class-like (reference semantics, methods) and there is no
1624
+ // dedicated actor kind, so it takes "class".
1625
+ const kind = kw === "extension" ? "module" : kw === "struct" ? "struct" : kw === "enum" ? "enum" : "class";
1626
+ return { name, kind, headerEnd: headEnd("class_body", "enum_class_body"), hashNode: node };
1627
+ }
1628
+ if (node.type === "protocol_declaration") {
1629
+ const name = typeName();
1630
+ if (!name)
1631
+ return null;
1632
+ return { name, kind: "interface", headerEnd: headEnd("protocol_body"), hashNode: node };
1633
+ }
1634
+ // A protocol requirement (`protocol_function_declaration`) has no body and is
1635
+ // always a member; an ordinary `function_declaration` is a method exactly when
1636
+ // it is nested in a type (or an extension of one).
1637
+ if (node.type === "function_declaration" || node.type === "protocol_function_declaration") {
1638
+ const name = funcName();
1639
+ if (!name)
1640
+ return null;
1641
+ const kind = node.type === "protocol_function_declaration" ||
1642
+ SWIFT_TYPE_KINDS.has(ctx.enclosingKind ?? "file")
1643
+ ? "method"
1644
+ : "function";
1645
+ return {
1646
+ name,
1647
+ kind,
1648
+ headerEnd: headEnd("function_body"),
1649
+ hashNode: node,
1650
+ ...swiftArity(node),
1651
+ };
1652
+ }
1653
+ if (node.type === "init_declaration") {
1654
+ // Initializers carry no name of their own — they are the type's own, so scope
1655
+ // the node under the enclosing type the same way Java's constructor_declaration
1656
+ // does. (A protocol's `init` requirement lands here too, owned by the protocol.)
1657
+ if (!ctx.enclosingClass)
1658
+ return null;
1659
+ return {
1660
+ name: ctx.enclosingClass,
1661
+ kind: "method",
1662
+ headerEnd: headEnd("function_body"),
1663
+ hashNode: node,
1664
+ ...swiftArity(node),
1665
+ };
1666
+ }
1667
+ if (node.type === "typealias_declaration") {
1668
+ const name = typeName();
1669
+ if (!name)
1670
+ return null;
1671
+ return { name, kind: "type", headerEnd: node.endIndex, hashNode: node };
1672
+ }
1673
+ if (node.type === "property_declaration") {
1674
+ // Top-level `let`/`var` only — a stored/computed property inside a type is a
1675
+ // field, not a definition node (no depth tier emits fields), so it must not
1676
+ // become one. `deinit` and `subscript` are likewise skipped: neither is ever
1677
+ // the target of a resolvable call edge, and neither carries a usable name.
1678
+ if (ctx.enclosingKind !== null)
1679
+ return null;
1680
+ const name = node.namedChildren
1681
+ .find((c) => c.type === "pattern")
1682
+ ?.namedChildren.find((c) => c.type === "simple_identifier")?.text;
1683
+ if (!name)
1684
+ return null;
1685
+ return { name, kind: "variable", headerEnd: node.endIndex, hashNode: node };
1686
+ }
1687
+ return null;
1688
+ }
1689
+ /** Java visibility: `public` (or `protected`) on the declaration's own modifier list.
1690
+ * A package-private or private member is not part of the API surface. Read off the
1691
+ * `modifiers` child's tokens, ignoring annotations, which live in the same node. */
1692
+ function javaExported(node) {
1693
+ const mods = node.namedChildren.find((c) => c.type === "modifiers");
1694
+ if (!mods)
1695
+ return false;
1696
+ return mods.children.some((c) => c.type === "public" || c.type === "protected");
1697
+ }
1698
+ /** Kotlin visibility: exported by default (`public` is implicit); only an explicit
1699
+ * `internal` / `private` / `protected` visibility modifier hides a definition. */
1700
+ function kotlinExported(node) {
1701
+ const mods = node.namedChildren.find((c) => c.type === "modifiers");
1702
+ if (!mods)
1703
+ return true;
1704
+ const vis = mods.namedChildren.find((c) => c.type === "visibility_modifier");
1705
+ return !vis || vis.text === "public";
1706
+ }
1707
+ /** Declared parameter count for a Swift callable, for overload disambiguation
1708
+ * (the same role Java's `arity`/`argCount` pair plays). `parameter` nodes are
1709
+ * direct children of the declaration; a default value's `=` sits as a SIBLING
1710
+ * token after its parameter, and a variadic `...` sits inside its parameter.
1711
+ * `arity` is the REQUIRED minimum (parameters minus defaults) and `variadic`
1712
+ * marks any default or variadic parameter, so `narrowByArity`'s at-least
1713
+ * semantics keeps every overload a call of that shape could reach. */
1714
+ function swiftArity(node) {
1715
+ const params = node.children.filter((c) => c.type === "parameter");
1716
+ const defaults = node.children.filter((c) => c.type === "=").length;
1717
+ const hasVariadic = params.some((p) => p.children.some((c) => c.type === "..."));
1718
+ const arity = Math.max(0, params.length - defaults);
1719
+ return hasVariadic || defaults > 0 ? { arity, variadic: true } : { arity };
1720
+ }
1721
+ /** Argument count at a Swift call site: the `value_argument`s plus one for a
1722
+ * trailing closure (`run(x) { … }` calls a two-parameter function). */
1723
+ function swiftArgCount(node) {
1724
+ const suffix = node.namedChildren.find((c) => c.type === "call_suffix");
1725
+ if (!suffix)
1726
+ return undefined;
1727
+ const args = suffix.namedChildren
1728
+ .find((c) => c.type === "value_arguments")
1729
+ ?.namedChildren.filter((c) => c.type === "value_argument").length ?? 0;
1730
+ const trailing = suffix.namedChildren.some((c) => c.type === "lambda_literal") ? 1 : 0;
1731
+ return args + trailing;
1732
+ }
1733
+ /** Swift visibility: the default (`internal`) is module-wide, and a repo is
1734
+ * typically one module — so `public` / `open` / `package` / `internal` all count
1735
+ * as API surface, and only an explicit `private` / `fileprivate` hides a
1736
+ * definition. A setter-only restriction (`private(set)`) leaves the getter
1737
+ * visible, so it does not hide the symbol either. */
1738
+ function swiftExported(node) {
1739
+ const mods = node.namedChildren.find((c) => c.type === "modifiers");
1740
+ const vis = mods?.namedChildren.find((c) => c.type === "visibility_modifier");
1741
+ return !vis || (vis.text !== "private" && vis.text !== "fileprivate");
1742
+ }
1743
+ /** The superclass a Swift class declaration names: its FIRST inheritance
1744
+ * specifier — Swift's grammar requires the superclass to precede any protocol
1745
+ * in the `:` list, so when a superclass exists it is always this entry. A
1746
+ * class conforming only to protocols yields that protocol's name instead, but
1747
+ * `super` is illegal in such a class, so no call site ever consults it. Null
1748
+ * for a bare `class Foo` (and for an extension, whose declaration carries no
1749
+ * heritage for the original type — `super` inside one stays unresolved). */
1750
+ function swiftSuperClassName(node) {
1751
+ const spec = node.namedChildren.find((c) => c.type === "inheritance_specifier");
1752
+ const ids = spec?.namedChildren
1753
+ .find((c) => c.type === "user_type")
1754
+ ?.namedChildren.filter((c) => c.type === "type_identifier");
1755
+ return ids?.length ? ids[ids.length - 1].text : null;
1756
+ }
1757
+ /** The receiver's base type name for a Go method, unwrapping a pointer receiver
1758
+ * (`func (u *User) …` → `User`). Null if it can't be read. */
1759
+ function goReceiverType(node) {
1760
+ const recv = node.childForFieldName("receiver"); // parameter_list
1761
+ const param = recv?.namedChildren.find((c) => c.type === "parameter_declaration");
1762
+ let type = param?.childForFieldName("type");
1763
+ if (type?.type === "pointer_type")
1764
+ type = type.namedChildren.at(-1) ?? null;
1765
+ return type?.type === "type_identifier" ? type.text : null;
1766
+ }
1767
+ /** Go visibility: a symbol is exported iff its own name starts with an uppercase
1768
+ * letter. For a receiver-qualified method name, the own name is the part after the dot. */
1769
+ function goExported(name) {
1770
+ const own = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1) : name;
1771
+ const first = own[0] ?? "";
1772
+ return first !== first.toLowerCase() && first === first.toUpperCase();
1773
+ }
1774
+ /** PHP visibility: a class member is "exported" unless it is `private`/`protected`.
1775
+ * Top-level functions/classes carry no visibility modifier and are always visible. */
1776
+ function phpExported(node) {
1777
+ const vis = node.namedChildren.find((c) => c.type === "visibility_modifier");
1778
+ return vis ? vis.text === "public" : true;
1779
+ }
1780
+ /** Name for a PHP closure / arrow-fn: the variable it's assigned to
1781
+ * (`$handler = fn(...)` -> `handler`, mirroring how TS names arrow-consts),
1782
+ * else the anonymous `{closure}` (deduplicated per file by mintId).
1783
+ *
1784
+ * The "is this the assignment's right-hand side" check compares tree-sitter node
1785
+ * `.id` (a stable per-tree node identity) rather than `===` on the wrapper
1786
+ * objects: the binding does not guarantee that two traversals to the same
1787
+ * underlying node hand back the same JS wrapper, so `right === node` can be false
1788
+ * even when they are the same node — producing a stray `{closure}` name that
1789
+ * makes `graft check` report the graph STALE against its own stored output. */
1790
+ function phpClosureName(node) {
1791
+ const parent = node.parent;
1792
+ if (parent?.type === "assignment_expression" && parent.childForFieldName("right")?.id === node.id) {
1793
+ const left = parent.childForFieldName("left");
1794
+ if (left?.type === "variable_name")
1795
+ return left.text.replace(/^\$/, "");
1796
+ }
1797
+ return "{closure}";
1798
+ }
1799
+ function heritageEdges(node, classId, ctx) {
1800
+ const edges = [];
1801
+ if (ctx.lang === "java") {
1802
+ // `superclass` holds `extends X`; `super_interfaces` holds `implements A, B`
1803
+ // (and, on an interface declaration, `extends A, B` — which tree-sitter-java
1804
+ // still spells `extends_interfaces`).
1805
+ const typeParams = javaTypeParameterNames(node);
1806
+ for (const child of node.namedChildren) {
1807
+ const relation = child.type === "superclass"
1808
+ ? "extends"
1809
+ : child.type === "super_interfaces" || child.type === "extends_interfaces"
1810
+ ? "implements"
1811
+ : null;
1812
+ if (!relation)
1813
+ continue;
1814
+ for (const entry of javaSupertypeEntries(child)) {
1815
+ const name = javaSupertypeName(entry);
1816
+ // Belt-and-braces. A type VARIABLE is never a supertype, and erasing the
1817
+ // arguments already removes every case measured on gson and spring-petclinic
1818
+ // (identical output with this filter removed) — Java cannot extend or implement
1819
+ // a type variable, so a surviving `T` would have to come from a shape neither
1820
+ // repo contains. Kept because a wrong supertype is not a cosmetic edge: it
1821
+ // feeds `classParents` and from there call resolution.
1822
+ if (!name || typeParams.has(name))
1823
+ continue;
1824
+ edges.push({ source: classId, relation, name, file: ctx.rel });
1825
+ }
1826
+ }
1827
+ return edges;
1828
+ }
1829
+ if (ctx.lang === "kotlin") {
1830
+ // The `:` clause is a list of `delegation_specifier`s — a superclass construction
1831
+ // (`class A : B()`), an interface, or `by` delegation. The first `type_identifier`
1832
+ // under each names the type; everything else (type args, delegation target) is not
1833
+ // the heritage target, so only the head type counts.
1834
+ for (const child of node.namedChildren) {
1835
+ if (child.type !== "delegation_specifier")
1836
+ continue;
1837
+ const t = child.namedChildren.find((c) => c.type === "user_type")?.namedChildren.find((c) => c.type === "type_identifier");
1838
+ if (t)
1839
+ edges.push({ source: classId, relation: "extends", name: t.text, file: ctx.rel });
1840
+ }
1841
+ return edges;
1842
+ }
1843
+ if (ctx.lang === "swift") {
1844
+ // `class A: B, C` — each `inheritance_specifier` (a direct child of the
1845
+ // declaration; protocols and extensions carry them too) wraps a `user_type`
1846
+ // whose LAST direct `type_identifier` is the bare supertype name: a
1847
+ // module-qualified `Foundation.NSObject` reduces to `NSObject`, and generic
1848
+ // arguments live in nested nodes so they never leak in. Swift cannot say
1849
+ // syntactically whether a specifier is the superclass or a protocol
1850
+ // conformance (that needs the target's kind), so every edge is `extends` —
1851
+ // the same collapse Kotlin's delegation specifiers make.
1852
+ for (const child of node.namedChildren) {
1853
+ if (child.type !== "inheritance_specifier")
1854
+ continue;
1855
+ const ids = child.namedChildren
1856
+ .find((c) => c.type === "user_type")
1857
+ ?.namedChildren.filter((c) => c.type === "type_identifier");
1858
+ const t = ids?.length ? ids[ids.length - 1] : undefined;
1859
+ if (t)
1860
+ edges.push({ source: classId, relation: "extends", name: t.text, file: ctx.rel });
1861
+ }
1862
+ return edges;
1863
+ }
1864
+ if (ctx.lang === "python") {
1865
+ const supers = node.childForFieldName("superclasses"); // argument_list
1866
+ for (const c of supers?.namedChildren ?? []) {
1867
+ if (c.type === "identifier") {
1868
+ edges.push({ source: classId, relation: "extends", name: c.text, file: ctx.rel });
1869
+ }
1870
+ }
1871
+ return edges;
1872
+ }
1873
+ if (ctx.lang === "r") {
1874
+ // `node` is whatever describeR matched: a binary_operator for R6
1875
+ // (`Foo <- R6::R6Class(...)`) or the call itself for S4 (`setClass(...)`
1876
+ // is a bare top-level statement, essentially never assigned).
1877
+ const call = node.type === "binary_operator" ? node.childForFieldName("rhs") : node;
1878
+ if (call?.type !== "call")
1879
+ return edges;
1880
+ const callee = rCalleeName(call);
1881
+ if (callee === "R6Class") {
1882
+ // `inherit = ParentClass` — a bare identifier (the parent's own
1883
+ // generator variable), not a string; R6 supports single inheritance only.
1884
+ const value = rNamedArg(call, "inherit");
1885
+ if (value?.type === "identifier") {
1886
+ edges.push({ source: classId, relation: "extends", name: value.text, file: ctx.rel });
1887
+ }
1888
+ }
1889
+ else if (callee === "setClass") {
1890
+ // `contains = "Base"` or `contains = c("Base1", "Base2")` — S4 supports
1891
+ // multiple inheritance.
1892
+ for (const name of rStringOrCVector(rNamedArg(call, "contains"))) {
1893
+ edges.push({ source: classId, relation: "extends", name, file: ctx.rel });
1894
+ }
1895
+ }
1896
+ return edges;
1897
+ }
1898
+ if (ctx.lang === "php") {
1899
+ // `class C extends B implements I, J` → base_clause (extends) +
1900
+ // class_interface_clause (implements); names may be namespace-qualified.
1901
+ for (const clause of node.namedChildren) {
1902
+ const relation = clause.type === "base_clause" ? "extends" : clause.type === "class_interface_clause" ? "implements" : null;
1903
+ if (!relation)
1904
+ continue;
1905
+ for (const t of clause.namedChildren) {
1906
+ if (t.type === "name" || t.type === "qualified_name") {
1907
+ edges.push({ source: classId, relation, name: t.text.replace(/^.*\\/, ""), file: ctx.rel });
1908
+ }
1909
+ }
1910
+ }
1911
+ return edges;
1912
+ }
1913
+ const heritage = node.namedChildren.find((c) => c.type === "class_heritage");
1914
+ for (const clause of heritage?.namedChildren ?? []) {
1915
+ const relation = clause.type === "implements_clause"
1916
+ ? "implements"
1917
+ : clause.type === "extends_clause"
1918
+ ? "extends"
1919
+ : null;
1920
+ if (!relation)
1921
+ continue;
1922
+ for (const t of clause.namedChildren) {
1923
+ if (t.type === "identifier" || t.type === "type_identifier") {
1924
+ edges.push({ source: classId, relation, name: t.text, file: ctx.rel });
1925
+ }
1926
+ }
1927
+ }
1928
+ return edges;
1929
+ }
1930
+ /**
1931
+ * The supertypes a heritage clause names, one node each — NOT every `type_identifier`
1932
+ * beneath it.
1933
+ *
1934
+ * `superclass` wraps a single type; `super_interfaces`/`extends_interfaces` wrap a
1935
+ * `type_list` of them. Descending blindly instead walked into `type_arguments`, so
1936
+ * `implements Comparable<Item>` reported `Item` as a supertype too.
1937
+ */
1938
+ function javaSupertypeEntries(clause) {
1939
+ const list = clause.namedChildren.find((c) => c.type === "type_list");
1940
+ return list ? [...list.namedChildren] : [...clause.namedChildren];
1941
+ }
1942
+ /**
1943
+ * What a supertype entry is CALLED, or null when this pass cannot say.
1944
+ *
1945
+ * Type arguments are erased, because they are not part of the supertype's identity:
1946
+ *
1947
+ * Base | Base<Item> -> Base
1948
+ *
1949
+ * A qualified name is kept WHOLE rather than reduced to its final segment:
1950
+ *
1951
+ * Outer.Inner | Outer.Inner<K> -> Outer.Inner
1952
+ *
1953
+ * Heritage keeps an unresolved base as the edge target by design ("usually an
1954
+ * external/imported type — keep the name"), so the full string is both truthful and
1955
+ * unable to false-match a node id, where a bare `Inner` could collide with an
1956
+ * unrelated in-repo type. That differs from construction (#103), which drops a
1957
+ * qualified name instead — construction has no keep-the-name contract to fall back on.
1958
+ */
1959
+ function javaSupertypeName(node) {
1960
+ if (!node)
1961
+ return null;
1962
+ if (node.type === "generic_type")
1963
+ return javaSupertypeName(node.namedChildren[0]);
1964
+ if (node.type === "scoped_type_identifier")
1965
+ return node.text;
1966
+ return node.type === "type_identifier" ? node.text : null;
1967
+ }
1968
+ /** The names a declaration binds as its own type parameters (`class C<T, U>` → T, U),
1969
+ * so they can never be mistaken for supertypes. */
1970
+ function javaTypeParameterNames(decl) {
1971
+ const params = decl.childForFieldName("type_parameters");
1972
+ if (!params)
1973
+ return new Set();
1974
+ const out = new Set();
1975
+ const visit = (n) => {
1976
+ if (n.type === "type_identifier")
1977
+ out.add(n.text);
1978
+ for (const c of n.namedChildren)
1979
+ visit(c);
1980
+ };
1981
+ visit(params);
1982
+ return out;
1983
+ }
1984
+ function calleeName(node, lang) {
1985
+ // Java first: `method_invocation` has NO `function` field (it splits the callee
1986
+ // into `object` + `name`), so the shared lookup below would return null for every
1987
+ // Java call site and the language would extract nodes with no call edges at all.
1988
+ if (lang === "java") {
1989
+ if (node.type === "object_creation_expression") {
1990
+ // `new Foo()` — the constructed type is the call target, named as the graph
1991
+ // names it.
1992
+ const name = javaConstructedTypeName(node.childForFieldName("type"));
1993
+ return name ? { name, viaMember: false } : null;
1994
+ }
1995
+ const nameNode = node.childForFieldName("name");
1996
+ if (!nameNode)
1997
+ return null;
1998
+ const obj = node.childForFieldName("object");
1999
+ // No `object` means an implicit-`this` call (`decorate(name)`), which in Java is a
2000
+ // method call, not a free function — Java has none. Reporting it as a plain call
2001
+ // would send it to the function-only resolver and drop it, losing the most common
2002
+ // intra-class edge there is. Spelling it as a `this` member call routes it through
2003
+ // owner-qualified resolution, which also walks the superclass chain and stays
2004
+ // conservative: an unmatched name (e.g. a static import) resolves to nothing.
2005
+ if (!obj)
2006
+ return { name: nameNode.text, viaMember: true, receiver: "this" };
2007
+ return { name: nameNode.text, viaMember: true, receiver: javaReceiver(obj) };
2008
+ }
2009
+ if (lang === "kotlin") {
2010
+ // `call_expression` = callee expression + `call_suffix`. A bare `foo()` names a
2011
+ // plain call; `obj.foo()` is a `navigation_expression` whose trailing
2012
+ // `navigation_suffix` holds the method name and whose object is the receiver.
2013
+ const target = node.namedChildren[0];
2014
+ if (target?.type === "simple_identifier")
2015
+ return { name: target.text, viaMember: false };
2016
+ if (target?.type === "navigation_expression") {
2017
+ const suffix = target.namedChildren.find((c) => c.type === "navigation_suffix");
2018
+ const name = suffix?.namedChildren.find((c) => c.type === "simple_identifier");
2019
+ const receiver = target.namedChildren[0];
2020
+ if (!name)
2021
+ return null;
2022
+ if (receiver?.type === "simple_identifier")
2023
+ return { name: name.text, viaMember: true, receiver: receiver.text };
2024
+ if (receiver?.type === "this_expression" || receiver?.type === "super_expression")
2025
+ return { name: name.text, viaMember: true, receiver: receiver.type === "this_expression" ? "this" : "super" };
2026
+ }
2027
+ return null;
2028
+ }
2029
+ if (lang === "swift") {
2030
+ // Same shape as Kotlin's: `call_expression` = callee expression + `call_suffix`.
2031
+ // A bare `foo()` names a plain call (this also covers `Animal()` initializer
2032
+ // calls, which have no distinguishing syntax); `obj.foo()` is a
2033
+ // `navigation_expression` whose trailing `navigation_suffix` holds the member
2034
+ // name and whose head is the receiver.
2035
+ const target = node.namedChildren[0];
2036
+ if (target?.type === "simple_identifier")
2037
+ return { name: target.text, viaMember: false };
2038
+ if (target?.type === "navigation_expression") {
2039
+ const suffix = target.namedChildren.find((c) => c.type === "navigation_suffix");
2040
+ const name = suffix?.namedChildren.find((c) => c.type === "simple_identifier");
2041
+ const receiver = target.namedChildren[0];
2042
+ if (!name)
2043
+ return null;
2044
+ if (receiver?.type === "simple_identifier")
2045
+ return { name: name.text, viaMember: true, receiver: receiver.text };
2046
+ if (receiver?.type === "self_expression" || receiver?.type === "super_expression")
2047
+ return {
2048
+ name: name.text,
2049
+ viaMember: true,
2050
+ receiver: receiver.type === "self_expression" ? "self" : "super",
2051
+ };
2052
+ if (receiver?.type === "navigation_expression") {
2053
+ // `self.repo.save()` — one hop off self is a field access and binds like
2054
+ // TS's `this.x`. Deeper chains and call-result receivers carry no
2055
+ // confident local clue, so those fall through with no receiver.
2056
+ const head = receiver.namedChildren[0];
2057
+ const field = receiver.namedChildren
2058
+ .find((c) => c.type === "navigation_suffix")
2059
+ ?.namedChildren.find((c) => c.type === "simple_identifier");
2060
+ if (head?.type === "self_expression" && field)
2061
+ return { name: name.text, viaMember: true, receiver: `self.${field.text}` };
2062
+ }
2063
+ // Still a member call even with an unknowable receiver (a chained call, a
2064
+ // literal, a subscript): recvType stays unset and resolve drops it rather
2065
+ // than guessing — same contract as Java's and TS's unknown receivers.
2066
+ return { name: name.text, viaMember: true };
2067
+ }
2068
+ return null;
2069
+ }
2070
+ if (lang === "php")
2071
+ return phpCallee(node);
2072
+ const fn = node.childForFieldName("function");
2073
+ if (!fn)
2074
+ return null;
2075
+ if (fn.type === "identifier")
2076
+ return { name: fn.text, viaMember: false };
2077
+ if (lang === "python" && fn.type === "attribute") {
2078
+ const a = fn.childForFieldName("attribute") ?? fn.namedChildren.at(-1);
2079
+ return a ? { name: a.text, viaMember: true, receiver: pyReceiver(fn) } : null;
2080
+ }
2081
+ if (lang === "go" && fn.type === "selector_expression") {
2082
+ // `pkg.Fn()` / `recv.Method()` — the called name is the trailing field.
2083
+ const p = fn.childForFieldName("field") ?? fn.namedChildren.at(-1);
2084
+ const operand = fn.childForFieldName("operand");
2085
+ const receiver = operand?.type === "identifier" ? operand.text : undefined;
2086
+ return p ? { name: p.text, viaMember: true, receiver } : null;
2087
+ }
2088
+ if ((lang === "typescript" || lang === "tsx") && fn.type === "member_expression") {
2089
+ const p = fn.childForFieldName("property") ?? fn.namedChildren.at(-1);
2090
+ return p ? { name: p.text, viaMember: true, receiver: tsReceiver(fn) } : null;
2091
+ }
2092
+ if (lang === "r" && (fn.type === "extract_operator" || fn.type === "namespace_operator")) {
2093
+ const rhs = fn.childForFieldName("rhs");
2094
+ if (rhs?.type !== "identifier")
2095
+ return null;
2096
+ if (fn.type === "extract_operator") {
2097
+ const lhs = fn.childForFieldName("lhs");
2098
+ if (lhs?.type === "identifier" && (lhs.text === "self" || lhs.text === "private")) {
2099
+ // R6 (Phase 2): `self$method()` / `private$method()` — resolves directly to
2100
+ // the enclosing class via ctx.enclosingClass, same mechanism (and same
2101
+ // magic receiver string) as Python/TS's self/cls/this — see
2102
+ // resolveRecvType, which already special-cases "self" generically.
2103
+ return { name: rhs.text, viaMember: true, receiver: "self" };
2104
+ }
2105
+ if (lhs?.type === "identifier" && lhs.text === "super") {
2106
+ // R6 (Phase 3): `super$method()` — R6's inheritance-dispatch keyword,
2107
+ // resolves directly to the PARENT class via ctx.rSuperClass (NOT
2108
+ // ctx.enclosingClass — that would wrongly find the current class's own
2109
+ // same-named override instead of climbing to the parent).
2110
+ return { name: rhs.text, viaMember: true, receiver: "super" };
2111
+ }
2112
+ // Any other `obj$method()` (Phase 4): still a PLAIN name match, not a
2113
+ // typed member call — there's no general field-type-binding table for
2114
+ // R6 composition (`private$other_obj$method()`), and a real codebase's
2115
+ // dominant field-assignment shape (constructor-parameter pass-through,
2116
+ // `do.call(class_var$new, ...)` dynamic dispatch) turned out to defeat
2117
+ // the simple "field <- SomeClass$new()" pattern every other language's
2118
+ // binding table relies on anyway — see plan_r_language_support.md's
2119
+ // Phase 2 "known gaps" and the follow-up investigation against a real
2120
+ // R6-heavy corpus. What DOES help: bare-name resolution must be allowed
2121
+ // to match a "method" node here, not just "function" — R6 methods are
2122
+ // always kind "method", so without `kinds` below, EVERY untyped `$`
2123
+ // call would be unconditionally unresolvable rather than just
2124
+ // occasionally ambiguous (resolve.ts already drops a genuinely
2125
+ // ambiguous bare-name match rather than guessing, so this only adds
2126
+ // resolutions for uniquely-named methods, never a wrong-class guess).
2127
+ return { name: rhs.text, viaMember: false, kinds: ["function", "method"] };
2128
+ }
2129
+ // `pkg::fun()` (qualified call) — always a real function/exported symbol,
2130
+ // never an R6 method (those are only ever reached via `$` on an instance),
2131
+ // so no need to widen the match kinds here.
2132
+ return { name: rhs.text, viaMember: false };
2133
+ }
2134
+ return null;
2135
+ }
2136
+ /** The number of arguments at a Java call site (`method_invocation` or
2137
+ * `object_creation_expression`), read off the `arguments` list. Undefined when the
2138
+ * list is absent, which keeps resolution at its previous name-only behavior rather
2139
+ * than filtering on a count we never established. */
2140
+ function javaArgCount(node) {
2141
+ const args = node.childForFieldName("arguments");
2142
+ return args ? args.namedChildren.length : undefined;
2143
+ }
2144
+ /**
2145
+ * The name a `new` CONSTRUCTS, as the graph names it — or null when this pass cannot
2146
+ * say, in which case the construction resolves to nothing.
2147
+ *
2148
+ * Erasing type arguments is the only transformation here, because it is the only one
2149
+ * that provably does not change which type is being named:
2150
+ *
2151
+ * Box -> Box
2152
+ * Box<String> -> Box (the node is `Box`; the arguments are not part of it)
2153
+ * Box<> -> Box
2154
+ *
2155
+ * A QUALIFIED name is deliberately dropped rather than reduced to its final segment:
2156
+ *
2157
+ * java.io.File -> null (not the repo's own `File`)
2158
+ * Beta.Builder -> null (not `Alpha.Builder` in the same file)
2159
+ *
2160
+ * Collapsing those was the first attempt at this fix, and it traded lost edges for
2161
+ * WRONG ones — `new java.io.File(…)` resolved to an unrelated in-repo `File`, and a
2162
+ * nested `Beta.Builder` bound to a sibling `Alpha.Builder` at `extracted` confidence,
2163
+ * because the same-file tiebreak takes the first candidate. Dropping keeps this pass
2164
+ * on the resolver's own rule: resolve precisely, or not at all.
2165
+ *
2166
+ * Deliberately NOT shared with bindings.ts's `javaTypeName`. That one answers "what
2167
+ * type does this variable HOLD", where reducing `java.util.List` to `List` is a local
2168
+ * heuristic with different stakes; this one answers "what type is being constructed",
2169
+ * and the two questions do not have the same safe answer. Supporting qualified
2170
+ * construction properly needs an import-aware type index, not a longer helper.
2171
+ */
2172
+ function javaConstructedTypeName(node) {
2173
+ if (!node)
2174
+ return null;
2175
+ if (node.type === "generic_type")
2176
+ return javaConstructedTypeName(node.namedChildren[0]);
2177
+ return node.type === "type_identifier" ? node.text : null;
2178
+ }
2179
+ /** A Java call's receiver text: a bare identifier (`repo.save()`), `this`, or
2180
+ * `this.x` for a field access (`this.repo.save()`). A chained call or a qualified
2181
+ * static reference yields none — there is no confident local clue to bind. */
2182
+ function javaReceiver(obj) {
2183
+ if (!obj)
2184
+ return undefined;
2185
+ if (obj.type === "identifier")
2186
+ return obj.text;
2187
+ if (obj.type === "this")
2188
+ return "this";
2189
+ if (obj.type === "field_access") {
2190
+ const inner = obj.childForFieldName("object");
2191
+ const field = obj.childForFieldName("field");
2192
+ if (inner?.type === "this" && field)
2193
+ return `this.${field.text}`;
2194
+ }
2195
+ return undefined;
2196
+ }
2197
+ /** py `attribute` node's receiver text: bare identifier, or `self.x` for a
2198
+ * chained `self.x.y()`. Anything else (e.g. a chained call `f().g()`) → none. */
2199
+ function pyReceiver(fn) {
2200
+ const obj = fn.childForFieldName("object");
2201
+ if (obj?.type === "identifier")
2202
+ return obj.text;
2203
+ if (obj?.type === "attribute") {
2204
+ const innerObj = obj.childForFieldName("object");
2205
+ const innerAttr = obj.childForFieldName("attribute");
2206
+ if (innerObj?.type === "identifier" && innerObj.text === "self" && innerAttr)
2207
+ return `self.${innerAttr.text}`;
2208
+ }
2209
+ return undefined;
2210
+ }
2211
+ /**
2212
+ * PHP call shapes: `foo()` (function_call_expression), `$obj->m()` /
2213
+ * `$obj?->m()` (member/nullsafe_member_call_expression), and `Cls::m()`
2214
+ * (scoped_call_expression). The called name is the trailing `name`; the
2215
+ * receiver, when locally knowable (`$this`, `self`/`static`/`parent`), feeds
2216
+ * receiver-typed resolution the same way Python's `self` and Go's receiver do.
2217
+ */
2218
+ function phpCallee(node) {
2219
+ if (node.type === "function_call_expression") {
2220
+ const fn = node.childForFieldName("function");
2221
+ const name = fn ? phpName(fn) : null;
2222
+ return name ? { name, viaMember: false } : null;
2223
+ }
2224
+ const nameNode = node.childForFieldName("name");
2225
+ if (!nameNode)
2226
+ return null;
2227
+ if (node.type === "scoped_call_expression") {
2228
+ return { name: nameNode.text, viaMember: true, receiver: phpScopeReceiver(node.childForFieldName("scope")) };
2229
+ }
2230
+ // member_call_expression / nullsafe_member_call_expression
2231
+ return { name: nameNode.text, viaMember: true, receiver: phpObjReceiver(node.childForFieldName("object")) };
2232
+ }
2233
+ /** A PHP callee identifier: bare `name`, or the trailing segment of a
2234
+ * `qualified_name` (`\App\helpers\slug` → `slug`). Dynamic calls (`$fn()`) → null. */
2235
+ function phpName(node) {
2236
+ if (node.type === "name")
2237
+ return node.text;
2238
+ if (node.type === "qualified_name")
2239
+ return node.text.replace(/^.*\\/, "") || null;
2240
+ return null;
2241
+ }
2242
+ /** `$obj->m()` receiver: `$this` normalizes to `this` (→ enclosing class); any
2243
+ * other variable is returned verbatim for a bindings lookup. */
2244
+ function phpObjReceiver(obj) {
2245
+ if (obj?.type !== "variable_name")
2246
+ return undefined;
2247
+ return obj.text === "$this" ? "this" : obj.text;
2248
+ }
2249
+ /** `Cls::m()` receiver: `self`/`static`/`parent` normalize to `self` (→ enclosing
2250
+ * class); an explicit class name is the trailing segment of its qualified path. */
2251
+ function phpScopeReceiver(scope) {
2252
+ if (!scope)
2253
+ return undefined;
2254
+ const text = scope.text;
2255
+ if (scope.type === "relative_scope" || text === "self" || text === "static" || text === "parent")
2256
+ return "self";
2257
+ if (scope.type === "name")
2258
+ return text;
2259
+ if (scope.type === "qualified_name")
2260
+ return text.replace(/^.*\\/, "");
2261
+ return undefined;
2262
+ }
2263
+ /** ts `member_expression` node's receiver text: `this`, `this.x`, or a bare identifier. */
2264
+ function tsReceiver(fn) {
2265
+ const obj = fn.childForFieldName("object");
2266
+ if (obj?.type === "this")
2267
+ return "this";
2268
+ if (obj?.type === "identifier")
2269
+ return obj.text;
2270
+ if (obj?.type === "member_expression") {
2271
+ const innerObj = obj.childForFieldName("object");
2272
+ const innerProp = obj.childForFieldName("property");
2273
+ if (innerObj?.type === "this" && innerProp)
2274
+ return `this.${innerProp.text}`;
2275
+ }
2276
+ return undefined;
2277
+ }
2278
+ /** R has no import statement at the grammar level — `library(x)`, `require(x)`,
2279
+ * and `source("f.R")` are ordinary `call` nodes, indistinguishable from any other
2280
+ * call except by their callee name. This is call-SITE pattern matching, a first
2281
+ * for this function's normal node-type switch — every other language's import
2282
+ * shape is a dedicated grammar construct. */
2283
+ const R_IMPORT_CALLS = new Set(["library", "require", "source"]);
2284
+ function isImport(node, lang) {
2285
+ // Go: match the per-import leaf, so single (`import "fmt"`) and grouped
2286
+ // (`import ( … )`) forms each yield one edge as the walk recurses into the list.
2287
+ if (lang === "go")
2288
+ return node.type === "import_spec";
2289
+ if (lang === "r") {
2290
+ if (node.type !== "call")
2291
+ return false;
2292
+ const fn = node.childForFieldName("function");
2293
+ return fn?.type === "identifier" && R_IMPORT_CALLS.has(fn.text);
2294
+ }
2295
+ if (lang === "java")
2296
+ return node.type === "import_declaration";
2297
+ if (lang === "kotlin")
2298
+ return node.type === "import_header";
2299
+ if (lang === "swift")
2300
+ return node.type === "import_declaration";
2301
+ // PHP: one edge per imported symbol — the clause leaf inside a (possibly
2302
+ // grouped) `use A\B, C\D;` / `use A\{B, C};` declaration.
2303
+ if (lang === "php")
2304
+ return node.type === "namespace_use_clause";
2305
+ return node.type === "import_statement" || node.type === "import_from_statement";
2306
+ }
2307
+ function importSpecifier(node, lang) {
2308
+ if (lang === "php") {
2309
+ // namespace_use_clause → its `qualified_name`/`name`, e.g. `App\Models\Animal`.
2310
+ const q = node.namedChildren.find((c) => c.type === "qualified_name" || c.type === "name");
2311
+ return q ? q.text.replace(/^\\/, "") : null;
2312
+ }
2313
+ if (lang === "python") {
2314
+ const m = node.childForFieldName("module_name") ??
2315
+ node.namedChildren.find((c) => c.type === "dotted_name" || c.type === "relative_import");
2316
+ return m?.text ?? null;
2317
+ }
2318
+ if (lang === "go") {
2319
+ // import_spec's `path` is an interpreted_string_literal, e.g. `"mymod/pkg/util"`.
2320
+ const path = node.childForFieldName("path") ?? node.namedChildren.at(-1);
2321
+ return path ? path.text.replace(/^["`]|["`]$/g, "") : null;
2322
+ }
2323
+ if (lang === "r") {
2324
+ // library(pkg) / library("pkg") / require(pkg) / source("f.R") — the target is
2325
+ // always the first (and normally only) positional argument, bare symbol or string.
2326
+ const value = rCallArgs(node)[0]?.childForFieldName("value") ?? null;
2327
+ if (value?.type === "identifier")
2328
+ return value.text;
2329
+ return rStringContent(value);
2330
+ }
2331
+ if (lang === "java") {
2332
+ // `import a.b.C;` / `import static a.b.C.d;` / `import a.b.*;` — the fully
2333
+ // qualified name is the scoped_identifier; a wildcard `*` is a separate token
2334
+ // and is dropped, leaving the package as the import target.
2335
+ const id = node.namedChildren.find((c) => c.type === "scoped_identifier" || c.type === "identifier");
2336
+ return id?.text ?? null;
2337
+ }
2338
+ if (lang === "kotlin") {
2339
+ // `import com.example.Foo` — the dotted path is the `identifier` child. A
2340
+ // wildcard (`import a.b.*`) and an `as` alias are separate children, so the
2341
+ // identifier text is already the module path (wildcards dropped, like Java).
2342
+ return node.namedChildren.find((c) => c.type === "identifier")?.text ?? null;
2343
+ }
2344
+ if (lang === "swift") {
2345
+ // `import UIKit` / `import struct Foundation.Date` — the dotted path is the
2346
+ // `identifier` child (an import-kind keyword like `struct` is a separate
2347
+ // token). Swift imports name MODULES, not files, so the specifier resolves
2348
+ // to a repo file only when a same-named module target exists; external
2349
+ // frameworks stay as unresolved (but truthful) import intents.
2350
+ return node.namedChildren.find((c) => c.type === "identifier")?.text ?? null;
2351
+ }
2352
+ const str = node.namedChildren.find((c) => c.type === "string");
2353
+ if (!str)
2354
+ return null;
2355
+ const frag = str.namedChildren.find((c) => c.type === "string_fragment");
2356
+ return frag?.text ?? str.text.replace(/^['"]|['"]$/g, "");
2357
+ }
2358
+ /** Signature = the definition header, whitespace-collapsed, trailing punctuation stripped. */
2359
+ function clean(raw) {
2360
+ const sig = raw
2361
+ .replace(/\s+/g, " ")
2362
+ .trim()
2363
+ .replace(/(=>|[{:=])\s*$/, "")
2364
+ .trim();
2365
+ return sig || null;
2366
+ }
2367
+ /** TS: a definition is exported if any ancestor is an `export` statement. */
2368
+ function tsExported(node) {
2369
+ let p = node.parent;
2370
+ while (p) {
2371
+ if (p.type === "export_statement")
2372
+ return true;
2373
+ p = p.parent;
2374
+ }
2375
+ return false;
2376
+ }
2377
+ //# sourceMappingURL=extract.js.map