@armanckeser/downstream 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +133 -2
  3. package/SKILL.md +150 -0
  4. package/bin/downstream.mjs +19 -0
  5. package/dist/cli/downstream.js +543 -0
  6. package/dist/domain/agent.js +3 -0
  7. package/dist/domain/approval.js +190 -0
  8. package/dist/domain/delivery.js +21 -0
  9. package/dist/domain/home.js +1 -0
  10. package/dist/domain/inbox.js +245 -0
  11. package/dist/domain/model.js +180 -0
  12. package/dist/domain/next-move.js +34 -0
  13. package/dist/domain/pr-detail.js +45 -0
  14. package/dist/server/actions.js +564 -0
  15. package/dist/server/agent-host.js +230 -0
  16. package/dist/server/analyze/git.js +161 -0
  17. package/dist/server/analyze/graph.js +403 -0
  18. package/dist/server/analyze/py-graph.js +141 -0
  19. package/dist/server/analyze/py.js +346 -0
  20. package/dist/server/analyze/ts-decls.js +299 -0
  21. package/dist/server/analyze/ts-project.js +212 -0
  22. package/dist/server/gh.js +51 -0
  23. package/dist/server/github.js +264 -0
  24. package/dist/server/home-store.js +127 -0
  25. package/dist/server/home.js +325 -0
  26. package/dist/server/install.js +12 -0
  27. package/dist/server/main.js +228 -0
  28. package/dist/server/prompts.js +32 -0
  29. package/dist/server/publish.js +136 -0
  30. package/dist/server/repos.js +164 -0
  31. package/dist/server/stale.js +50 -0
  32. package/dist/server/store.js +269 -0
  33. package/dist/server/workspace.js +251 -0
  34. package/package.json +63 -4
  35. package/web/dist/assets/abap-CLvhMVsD.js +1 -0
  36. package/web/dist/assets/actionscript-3-Cs6JJXT5.js +1 -0
  37. package/web/dist/assets/ada-X3Y2mlq2.js +1 -0
  38. package/web/dist/assets/ahk-DwADkxyd.js +1 -0
  39. package/web/dist/assets/ahk2-_6rzW9BS.js +1 -0
  40. package/web/dist/assets/andromeeda-vGVdxbeo.js +1 -0
  41. package/web/dist/assets/angular-html-iITFzleb.js +1 -0
  42. package/web/dist/assets/angular-ts-qNGaWUZr.js +1 -0
  43. package/web/dist/assets/apache-U0d_L8uA.js +1 -0
  44. package/web/dist/assets/apex-KFGFyKwG.js +1 -0
  45. package/web/dist/assets/apl-DAcxbtpZ.js +1 -0
  46. package/web/dist/assets/applescript-CCn79oCD.js +1 -0
  47. package/web/dist/assets/ara-4CJ0cIlV.js +1 -0
  48. package/web/dist/assets/asciidoc-SCjQUq34.js +1 -0
  49. package/web/dist/assets/asm-Cmm7eHzH.js +1 -0
  50. package/web/dist/assets/astro-CxPQu5s8.js +1 -0
  51. package/web/dist/assets/aurora-x-CDeNXAV0.js +1 -0
  52. package/web/dist/assets/awk-BWXHIvNe.js +1 -0
  53. package/web/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
  54. package/web/dist/assets/ayu-light-C3h-C4tm.js +1 -0
  55. package/web/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  56. package/web/dist/assets/ballerina-B7ZEbQpA.js +1 -0
  57. package/web/dist/assets/bat-qdxAdBWQ.js +1 -0
  58. package/web/dist/assets/beancount-C2IN4Vk8.js +1 -0
  59. package/web/dist/assets/berry-DKpUyyne.js +1 -0
  60. package/web/dist/assets/bibtex-Ci_nEsc7.js +1 -0
  61. package/web/dist/assets/bicep-CUHmPFLl.js +1 -0
  62. package/web/dist/assets/bird2-CYIUgYqB.js +1 -0
  63. package/web/dist/assets/blade-DJrnOqL4.js +1 -0
  64. package/web/dist/assets/bsl-_VjlB9mv.js +1 -0
  65. package/web/dist/assets/c-DJLzn8qa.js +1 -0
  66. package/web/dist/assets/c3-D0apG41V.js +1 -0
  67. package/web/dist/assets/cadence-CQ2zXKGN.js +1 -0
  68. package/web/dist/assets/cairo-DLTphjLi.js +1 -0
  69. package/web/dist/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
  70. package/web/dist/assets/catppuccin-latte-CtsyrtPq.js +1 -0
  71. package/web/dist/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
  72. package/web/dist/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
  73. package/web/dist/assets/chapel-BHYX99F3.js +1 -0
  74. package/web/dist/assets/clarity-CtfBBspg.js +1 -0
  75. package/web/dist/assets/clojure-DqKBuwfJ.js +1 -0
  76. package/web/dist/assets/cmake-Bj61d0ZC.js +1 -0
  77. package/web/dist/assets/cobol-C_LDam8n.js +1 -0
  78. package/web/dist/assets/codeowners-C8r90Shi.js +1 -0
  79. package/web/dist/assets/codeql-oeQT6MSM.js +1 -0
  80. package/web/dist/assets/coffee-CcuifkxZ.js +1 -0
  81. package/web/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
  82. package/web/dist/assets/coq-CGaSfhwR.js +1 -0
  83. package/web/dist/assets/cpp-E7p80IG9.js +1 -0
  84. package/web/dist/assets/crystal-iYHODIir.js +1 -0
  85. package/web/dist/assets/csharp-dmMxLVLW.js +1 -0
  86. package/web/dist/assets/css-Bk6njqO4.js +1 -0
  87. package/web/dist/assets/csv-Dx-8-gkx.js +1 -0
  88. package/web/dist/assets/cue-CE9AQfxI.js +1 -0
  89. package/web/dist/assets/cypher-ClKdZ_lG.js +1 -0
  90. package/web/dist/assets/d-qD-0Kul2.js +1 -0
  91. package/web/dist/assets/dark-plus-Cs2F2srj.js +1 -0
  92. package/web/dist/assets/dart-CHZY9Xhz.js +1 -0
  93. package/web/dist/assets/dax-BkyTk9wS.js +1 -0
  94. package/web/dist/assets/desktop-Dlh5hvp9.js +1 -0
  95. package/web/dist/assets/diff-woXpYk--.js +1 -0
  96. package/web/dist/assets/docker-IyjqRm3v.js +1 -0
  97. package/web/dist/assets/dotenv-CuH5my32.js +1 -0
  98. package/web/dist/assets/dracula-BHWKrbxM.js +1 -0
  99. package/web/dist/assets/dracula-soft-5eyTD99u.js +1 -0
  100. package/web/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
  101. package/web/dist/assets/edge-Ct7_SkpN.js +1 -0
  102. package/web/dist/assets/elixir-YTzSQL2h.js +1 -0
  103. package/web/dist/assets/elm-bNkwlHr8.js +1 -0
  104. package/web/dist/assets/emacs-lisp-DR2vtWaj.js +1 -0
  105. package/web/dist/assets/erb-uFBspDdG.js +1 -0
  106. package/web/dist/assets/erlang-Cphh6RMH.js +1 -0
  107. package/web/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
  108. package/web/dist/assets/everforest-light-Df2xbC6M.js +1 -0
  109. package/web/dist/assets/fennel-DQxkIbk2.js +1 -0
  110. package/web/dist/assets/fish-BJitypiv.js +1 -0
  111. package/web/dist/assets/fluent-C03EYrpw.js +1 -0
  112. package/web/dist/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  113. package/web/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
  114. package/web/dist/assets/fsharp-D13ZGOAj.js +1 -0
  115. package/web/dist/assets/gdresource-xMC7jr7x.js +1 -0
  116. package/web/dist/assets/gdscript-DYvRD3hg.js +1 -0
  117. package/web/dist/assets/gdshader-CBce3t8t.js +1 -0
  118. package/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  119. package/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  120. package/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  121. package/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  122. package/web/dist/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  123. package/web/dist/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  124. package/web/dist/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  125. package/web/dist/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  126. package/web/dist/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  127. package/web/dist/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  128. package/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  129. package/web/dist/assets/genie-CV2tkWYe.js +1 -0
  130. package/web/dist/assets/gherkin-DExj1W_8.js +1 -0
  131. package/web/dist/assets/git-commit-BSykSTBG.js +1 -0
  132. package/web/dist/assets/git-rebase-CSzaMu4k.js +1 -0
  133. package/web/dist/assets/github-dark-C-LZuMrd.js +1 -0
  134. package/web/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
  135. package/web/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  136. package/web/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  137. package/web/dist/assets/github-light-EUqPIrTm.js +1 -0
  138. package/web/dist/assets/github-light-default-BXViO-2h.js +1 -0
  139. package/web/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  140. package/web/dist/assets/gleam-CSRkHgEL.js +1 -0
  141. package/web/dist/assets/glimmer-js-WrpQIag3.js +1 -0
  142. package/web/dist/assets/glimmer-ts-BkR7s81M.js +1 -0
  143. package/web/dist/assets/glsl-r6ahr8UN.js +1 -0
  144. package/web/dist/assets/gn-CAXYAgV-.js +1 -0
  145. package/web/dist/assets/gnuplot-7GGW24-e.js +1 -0
  146. package/web/dist/assets/go-rLFTqkRN.js +1 -0
  147. package/web/dist/assets/graphql-vcFzEvUM.js +1 -0
  148. package/web/dist/assets/groovy-CacY0gHj.js +1 -0
  149. package/web/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  150. package/web/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  151. package/web/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  152. package/web/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  153. package/web/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  154. package/web/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  155. package/web/dist/assets/hack-DV1TStQR.js +1 -0
  156. package/web/dist/assets/haml-BzEpEFnM.js +1 -0
  157. package/web/dist/assets/handlebars-Bw2ai2tn.js +1 -0
  158. package/web/dist/assets/haskell-D8IpX4py.js +1 -0
  159. package/web/dist/assets/haxe-C8oCnwJJ.js +1 -0
  160. package/web/dist/assets/hcl-Dh228itO.js +1 -0
  161. package/web/dist/assets/hjson-CxZEssPk.js +1 -0
  162. package/web/dist/assets/hlsl-Cvrh5tZx.js +1 -0
  163. package/web/dist/assets/horizon-CE9ld1lL.js +1 -0
  164. package/web/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
  165. package/web/dist/assets/houston-CsvMBhTu.js +1 -0
  166. package/web/dist/assets/html-CVWJgEI4.js +1 -0
  167. package/web/dist/assets/html-derivative-kTFEcoss.js +1 -0
  168. package/web/dist/assets/http-DXC6PGgt.js +1 -0
  169. package/web/dist/assets/hurl-Bpd8P_ef.js +1 -0
  170. package/web/dist/assets/hxml-Dcm88srW.js +1 -0
  171. package/web/dist/assets/hy-CZbG8q4J.js +1 -0
  172. package/web/dist/assets/imba-DsUTQ-LC.js +1 -0
  173. package/web/dist/assets/index-DpYMsPxu.js +1673 -0
  174. package/web/dist/assets/index-Zh-Bd2u6.css +2 -0
  175. package/web/dist/assets/ini-B5eOa1yu.js +1 -0
  176. package/web/dist/assets/java-Bsmh9nXE.js +1 -0
  177. package/web/dist/assets/javascript-CeyaFN_P.js +1 -0
  178. package/web/dist/assets/jinja-BuqTDofy.js +1 -0
  179. package/web/dist/assets/jison-BYfmmaBZ.js +1 -0
  180. package/web/dist/assets/json-Bhl6cDM_.js +1 -0
  181. package/web/dist/assets/json5-BR5RXkoi.js +1 -0
  182. package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
  183. package/web/dist/assets/jsonl-CmCQp5Yx.js +1 -0
  184. package/web/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
  185. package/web/dist/assets/jssm-DXw9l8Rf.js +1 -0
  186. package/web/dist/assets/jsx-BSUHjwDX.js +1 -0
  187. package/web/dist/assets/julia-CWrNF0wf.js +1 -0
  188. package/web/dist/assets/just-wLw7gWsD.js +1 -0
  189. package/web/dist/assets/kanagawa-dragon-Bozd7klA.js +1 -0
  190. package/web/dist/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
  191. package/web/dist/assets/kanagawa-wave-BML5mF2J.js +1 -0
  192. package/web/dist/assets/kdl-CsD5j6eV.js +1 -0
  193. package/web/dist/assets/kotlin-DhhofPvG.js +1 -0
  194. package/web/dist/assets/kusto-C7mF5XQf.js +1 -0
  195. package/web/dist/assets/laserwave-C_8bwKvT.js +1 -0
  196. package/web/dist/assets/latex-DyWaeH0P.js +1 -0
  197. package/web/dist/assets/lean-CewbzKMR.js +1 -0
  198. package/web/dist/assets/less-DVTAwKKz.js +1 -0
  199. package/web/dist/assets/light-plus-DVQuIRkW.js +1 -0
  200. package/web/dist/assets/liquid-DeCc5Rg2.js +1 -0
  201. package/web/dist/assets/llvm-DqHOMS-s.js +1 -0
  202. package/web/dist/assets/log-BNLmms1o.js +1 -0
  203. package/web/dist/assets/logo-Cluzi2Zq.js +1 -0
  204. package/web/dist/assets/lua-Ba-NltBp.js +1 -0
  205. package/web/dist/assets/luau-VGWVLoqX.js +1 -0
  206. package/web/dist/assets/make-Dixweg8N.js +1 -0
  207. package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
  208. package/web/dist/assets/marko-DGb2HMVj.js +1 -0
  209. package/web/dist/assets/material-theme-Bm3Qr25_.js +1 -0
  210. package/web/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
  211. package/web/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
  212. package/web/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  213. package/web/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  214. package/web/dist/assets/matlab-D7qyCx1q.js +1 -0
  215. package/web/dist/assets/mdc-CL7bhRQ-.js +1 -0
  216. package/web/dist/assets/mdx-DQZ5AkYe.js +1 -0
  217. package/web/dist/assets/mermaid-BxpfTIzt.js +1 -0
  218. package/web/dist/assets/min-dark-BSWPekZh.js +1 -0
  219. package/web/dist/assets/min-light-DDpmG2fV.js +1 -0
  220. package/web/dist/assets/mipsasm-BMqwQI7S.js +1 -0
  221. package/web/dist/assets/mojo-fg40_ntQ.js +1 -0
  222. package/web/dist/assets/monokai-BscAjiQZ.js +1 -0
  223. package/web/dist/assets/moonbit-D9W784E_.js +1 -0
  224. package/web/dist/assets/move-BYnGhZoM.js +1 -0
  225. package/web/dist/assets/narrat-_X_XdTYD.js +1 -0
  226. package/web/dist/assets/nextflow-CSgcYZcx.js +1 -0
  227. package/web/dist/assets/nextflow-groovy-D7IRt6Pp.js +1 -0
  228. package/web/dist/assets/nginx-CbyuJibN.js +1 -0
  229. package/web/dist/assets/night-owl-DhmEMT88.js +1 -0
  230. package/web/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
  231. package/web/dist/assets/nim-C7LemgDR.js +1 -0
  232. package/web/dist/assets/nix-IvuFDN5E.js +1 -0
  233. package/web/dist/assets/nord-Cb4Vim4T.js +1 -0
  234. package/web/dist/assets/nsis-CjNLYCLg.js +1 -0
  235. package/web/dist/assets/nushell--0fT3PI1.js +1 -0
  236. package/web/dist/assets/objective-c-D1A_Heim.js +1 -0
  237. package/web/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
  238. package/web/dist/assets/ocaml-O90oeIOV.js +1 -0
  239. package/web/dist/assets/odin-B1RWQWA5.js +1 -0
  240. package/web/dist/assets/one-dark-pro-C6YyR8OD.js +1 -0
  241. package/web/dist/assets/one-light-D7Lr4KcI.js +1 -0
  242. package/web/dist/assets/openscad-BUDT5pXO.js +1 -0
  243. package/web/dist/assets/org-wrbLazmP.js +1 -0
  244. package/web/dist/assets/pascal-4ZHwLPI5.js +1 -0
  245. package/web/dist/assets/perl-DarHXyEu.js +1 -0
  246. package/web/dist/assets/php--54M0-pj.js +1 -0
  247. package/web/dist/assets/pierre-dark-CpLgRqie.js +1 -0
  248. package/web/dist/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  249. package/web/dist/assets/pierre-dark-soft-kZQmAZld.js +1 -0
  250. package/web/dist/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  251. package/web/dist/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
  252. package/web/dist/assets/pierre-light-CoaEpmwp.js +1 -0
  253. package/web/dist/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  254. package/web/dist/assets/pierre-light-soft-lWLdNTOI.js +1 -0
  255. package/web/dist/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  256. package/web/dist/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
  257. package/web/dist/assets/pkl-ot-7Btpt.js +1 -0
  258. package/web/dist/assets/plastic-DQwYfKfQ.js +1 -0
  259. package/web/dist/assets/plsql-DGHpHOYJ.js +1 -0
  260. package/web/dist/assets/po-BiJDBrnU.js +1 -0
  261. package/web/dist/assets/poimandres-DRFjx7u4.js +1 -0
  262. package/web/dist/assets/polar-C7UOKdEL.js +1 -0
  263. package/web/dist/assets/postcss-BXeXVLqQ.js +1 -0
  264. package/web/dist/assets/powerquery-DNMTfnFr.js +1 -0
  265. package/web/dist/assets/powershell-QtSnmXcu.js +1 -0
  266. package/web/dist/assets/prisma-DMgNhnYS.js +1 -0
  267. package/web/dist/assets/prolog-iXnhIJG7.js +1 -0
  268. package/web/dist/assets/proto-DB4EqR-F.js +1 -0
  269. package/web/dist/assets/pug-CFrC1rDC.js +1 -0
  270. package/web/dist/assets/puppet-CDv2pdJW.js +1 -0
  271. package/web/dist/assets/purescript-9MfHhQsQ.js +1 -0
  272. package/web/dist/assets/python-gzcpVVnB.js +1 -0
  273. package/web/dist/assets/qml-Go4B46qX.js +1 -0
  274. package/web/dist/assets/qmldir-DCQb3MpD.js +1 -0
  275. package/web/dist/assets/qss-Fe1Jh2GI.js +1 -0
  276. package/web/dist/assets/r-Bpu17F3f.js +1 -0
  277. package/web/dist/assets/racket-DcIDlBhZ.js +1 -0
  278. package/web/dist/assets/raku-B3gFvitq.js +1 -0
  279. package/web/dist/assets/razor-CeZTEIIg.js +1 -0
  280. package/web/dist/assets/rbs-C0MpOrj4.js +1 -0
  281. package/web/dist/assets/red-CYlAq2b0.js +1 -0
  282. package/web/dist/assets/reg-CRGYupPL.js +1 -0
  283. package/web/dist/assets/regexp-D4K6CiTG.js +1 -0
  284. package/web/dist/assets/rel-BtDbiS_P.js +1 -0
  285. package/web/dist/assets/riscv-Ckw8ddFX.js +1 -0
  286. package/web/dist/assets/ron-VUp2lXgN.js +1 -0
  287. package/web/dist/assets/rose-pine-aMsaMD1D.js +1 -0
  288. package/web/dist/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
  289. package/web/dist/assets/rose-pine-moon-DO-qXg-I.js +1 -0
  290. package/web/dist/assets/rosmsg-CAekHB0j.js +1 -0
  291. package/web/dist/assets/rst-Dy2Bl1Fg.js +1 -0
  292. package/web/dist/assets/ruby-CNqr2Wuz.js +1 -0
  293. package/web/dist/assets/rust-DpiIf5JO.js +1 -0
  294. package/web/dist/assets/sas-B6MsvXFb.js +1 -0
  295. package/web/dist/assets/sass-DXrisJhu.js +1 -0
  296. package/web/dist/assets/scala-BiFt_7mu.js +1 -0
  297. package/web/dist/assets/scheme-DQCgrYNe.js +1 -0
  298. package/web/dist/assets/scss-CHuFWGi8.js +1 -0
  299. package/web/dist/assets/sdbl-bTVj8UrX.js +1 -0
  300. package/web/dist/assets/shaderlab-TOUzSsQk.js +1 -0
  301. package/web/dist/assets/shellscript-EEdMjEbs.js +1 -0
  302. package/web/dist/assets/shellsession-U_I5Q2cJ.js +1 -0
  303. package/web/dist/assets/slack-dark-DnToyrRv.js +1 -0
  304. package/web/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
  305. package/web/dist/assets/smalltalk-BHpQb5nM.js +1 -0
  306. package/web/dist/assets/smithy-Dgaz7aur.js +1 -0
  307. package/web/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
  308. package/web/dist/assets/solarized-dark-b97czJOk.js +1 -0
  309. package/web/dist/assets/solarized-light-DFPxpIYQ.js +1 -0
  310. package/web/dist/assets/solidity-Bj7E1mRn.js +1 -0
  311. package/web/dist/assets/soy-BnCBZkG3.js +1 -0
  312. package/web/dist/assets/sparql-D_iOobhT.js +1 -0
  313. package/web/dist/assets/splunk-BC2Px7Mm.js +1 -0
  314. package/web/dist/assets/sql-5dStcFSw.js +1 -0
  315. package/web/dist/assets/ssh-config-BgfXC-Er.js +1 -0
  316. package/web/dist/assets/stata-B5iSQdtS.js +1 -0
  317. package/web/dist/assets/stylus-B6D30XZt.js +1 -0
  318. package/web/dist/assets/surrealql-D4K5msa4.js +1 -0
  319. package/web/dist/assets/svelte-Dn-_7BgE.js +1 -0
  320. package/web/dist/assets/swift-CyEgAFGc.js +1 -0
  321. package/web/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
  322. package/web/dist/assets/system-verilog-Dh5ihWbf.js +1 -0
  323. package/web/dist/assets/systemd-BxMlprV5.js +1 -0
  324. package/web/dist/assets/talonscript-CohzipZa.js +1 -0
  325. package/web/dist/assets/tasl-DMoTqEGO.js +1 -0
  326. package/web/dist/assets/tcl-CZd0xW_V.js +1 -0
  327. package/web/dist/assets/templ-BTe8yLW8.js +1 -0
  328. package/web/dist/assets/terraform-DswuEJGm.js +1 -0
  329. package/web/dist/assets/tex-BBG77enc.js +1 -0
  330. package/web/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
  331. package/web/dist/assets/toml-CcmNWLt0.js +1 -0
  332. package/web/dist/assets/ts-tags-BzJXXnMD.js +1 -0
  333. package/web/dist/assets/tsv-sltzmVWM.js +1 -0
  334. package/web/dist/assets/tsx-Cj2inrAP.js +1 -0
  335. package/web/dist/assets/turtle-ByJddavk.js +1 -0
  336. package/web/dist/assets/twig-BH3HU2q4.js +1 -0
  337. package/web/dist/assets/typescript-DpDozqSz.js +1 -0
  338. package/web/dist/assets/typespec-BGhR2SqG.js +1 -0
  339. package/web/dist/assets/typst-GHzvrUad.js +1 -0
  340. package/web/dist/assets/v-DBj4K9d_.js +1 -0
  341. package/web/dist/assets/vala-zf12oZj6.js +1 -0
  342. package/web/dist/assets/vb-DOwzaHZ0.js +1 -0
  343. package/web/dist/assets/verilog-B2gwz-sJ.js +1 -0
  344. package/web/dist/assets/vesper-DdrHHSXu.js +1 -0
  345. package/web/dist/assets/vhdl-BroJfC0k.js +1 -0
  346. package/web/dist/assets/viml-DvXPmvsu.js +1 -0
  347. package/web/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
  348. package/web/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
  349. package/web/dist/assets/vitesse-light-VbXTXTou.js +1 -0
  350. package/web/dist/assets/vue-CqkncxvB.js +1 -0
  351. package/web/dist/assets/vue-html-DrD64fqb.js +1 -0
  352. package/web/dist/assets/vue-vine-BJy37wv2.js +1 -0
  353. package/web/dist/assets/vyper-CgoNMtux.js +1 -0
  354. package/web/dist/assets/wasm-BnjxR4X6.js +1 -0
  355. package/web/dist/assets/wasm-ByWQv1Qj.js +1 -0
  356. package/web/dist/assets/wenyan-C8pVoKbM.js +1 -0
  357. package/web/dist/assets/wgsl-BsKzXJz4.js +1 -0
  358. package/web/dist/assets/wikitext-ClFFjSW2.js +1 -0
  359. package/web/dist/assets/wit-DdvCle-K.js +1 -0
  360. package/web/dist/assets/wolfram-DLL8P-h_.js +1 -0
  361. package/web/dist/assets/xml-CtXDvb3g.js +1 -0
  362. package/web/dist/assets/xsl-9J2XPEqv.js +1 -0
  363. package/web/dist/assets/yaml-CKsrpaXo.js +1 -0
  364. package/web/dist/assets/zenscript-BnlCZFoB.js +1 -0
  365. package/web/dist/assets/zig-CMLA9XwU.js +1 -0
  366. package/web/dist/favicon.svg +1 -0
  367. package/web/dist/index.html +15 -0
@@ -0,0 +1,346 @@
1
+ // Python without a type checker: declarations from indentation, calls resolved
2
+ // through each file's imports. Good enough to draw the flow and answer
3
+ // "what is this?" on hover; the agent fills in what static reading can't see.
4
+ import path from "node:path";
5
+ export const isPy = (file) => file.endsWith(".py") || file.endsWith(".pyi");
6
+ const ROUTE = /^\s*@\s*[\w.]+\.(get|post|put|patch|delete|head|options|route|api_route|websocket)\s*\(\s*(?:path\s*=\s*)?["']([^"']+)["']/;
7
+ const COMMAND = /^\s*@\s*[\w.]*\b(command|group|task|shared_task|receiver|on_event|listener|handler)\b/;
8
+ const NOT_CALLS = new Set([
9
+ "if", "elif", "while", "for", "return", "yield", "not", "and", "or", "in", "is", "lambda", "with", "assert", "del", "await", "print", "len", "range",
10
+ "str", "int", "float", "bool", "dict", "list", "set", "tuple", "frozenset", "bytes", "isinstance", "issubclass", "super", "getattr", "setattr", "hasattr",
11
+ "type", "object", "enumerate", "zip", "map", "filter", "sorted", "reversed", "min", "max", "sum", "any", "all", "abs", "round", "open", "repr", "iter", "next",
12
+ "id", "hash", "vars", "dir", "callable", "except", "raise", "def", "class", "import", "from", "as", "pass", "else", "try", "finally", "case", "match",
13
+ ]);
14
+ /** Blank out strings and comments, keeping every character's position, so regexes see only code. */
15
+ export function stripPy(text) {
16
+ let out = "";
17
+ let i = 0;
18
+ while (i < text.length) {
19
+ const c = text[i];
20
+ if (c === "#") {
21
+ while (i < text.length && text[i] !== "\n")
22
+ (out += " "), i++;
23
+ continue;
24
+ }
25
+ if (c === '"' || c === "'") {
26
+ // Prefixes like f"", rb'' were already emitted as identifiers; that's fine.
27
+ const triple = text.startsWith(c.repeat(3), i);
28
+ const quote = triple ? c.repeat(3) : c;
29
+ out += quote.replace(/./g, " ");
30
+ i += quote.length;
31
+ while (i < text.length && !text.startsWith(quote, i)) {
32
+ if (text[i] === "\\")
33
+ (out += " "), (i += 2);
34
+ else if (!triple && text[i] === "\n")
35
+ break;
36
+ else
37
+ (out += text[i] === "\n" ? "\n" : " "), i++;
38
+ }
39
+ if (text.startsWith(quote, i))
40
+ (out += quote.replace(/./g, " ")), (i += quote.length);
41
+ continue;
42
+ }
43
+ out += c;
44
+ i++;
45
+ }
46
+ return out;
47
+ }
48
+ const indentOf = (line) => line.length - line.trimStart().length;
49
+ export function parsePy(file, text) {
50
+ const raw = text.split("\n");
51
+ const code = stripPy(text).split("\n");
52
+ const decls = [];
53
+ const stack = [];
54
+ for (let i = 0; i < code.length; i++) {
55
+ const line = code[i];
56
+ const m = line.match(/^(\s*)(async\s+def|def|class)\s+([A-Za-z_]\w*)/);
57
+ if (!m)
58
+ continue;
59
+ const indent = m[1].length;
60
+ while (stack.length && stack.at(-1).indent >= indent)
61
+ stack.pop();
62
+ const parent = stack.at(-1) ?? null;
63
+ // Header: until the colon that closes the signature.
64
+ let depth = 0;
65
+ let headerEnd = i;
66
+ for (let j = i; j < code.length; j++) {
67
+ for (const ch of code[j])
68
+ depth += ch === "(" || ch === "[" ? 1 : ch === ")" || ch === "]" ? -1 : 0;
69
+ headerEnd = j;
70
+ if (depth <= 0 && /:\s*$/.test(code[j].trimEnd()))
71
+ break;
72
+ }
73
+ // Body: every following line indented deeper (blank and comment lines don't end it).
74
+ let end = headerEnd;
75
+ for (let j = headerEnd + 1; j < code.length; j++) {
76
+ if (!code[j].trim())
77
+ continue;
78
+ if (indentOf(code[j]) > indent)
79
+ end = j;
80
+ else
81
+ break;
82
+ }
83
+ // Decorators sit directly above.
84
+ let start = i;
85
+ while (start > 0 && /^\s*@/.test(code[start - 1]) && indentOf(code[start - 1]) === indent)
86
+ start--;
87
+ const isClass = m[2] === "class";
88
+ const name = m[3];
89
+ const decorators = raw.slice(start, i).join("\n");
90
+ const routeMatch = raw.slice(start, i).map((d) => d.match(ROUTE)).find(Boolean);
91
+ const command = raw.slice(start, i).some((d) => COMMAND.test(d));
92
+ const kind = isClass ? "class" : routeMatch || command ? "route" : parent?.isClass ? "method" : "function";
93
+ const signature = (decorators ? decorators + "\n" : "") + raw.slice(i, headerEnd + 1).join("\n").trimEnd();
94
+ const decl = {
95
+ key: parent ? `${parent.key}.${name}` : name,
96
+ name,
97
+ container: parent ? parent.key : null,
98
+ kind,
99
+ range: { start: start + 1, end: end + 1 },
100
+ defLine: i + 1,
101
+ signature: routeMatch ? `${routeMatch[1].toUpperCase()} ${routeMatch[2]}\n${signature}` : signature,
102
+ doc: docstring(raw, headerEnd + 1),
103
+ exported: !name.startsWith("_"),
104
+ memberRanges: [],
105
+ indent,
106
+ isClass,
107
+ kept: false,
108
+ };
109
+ // Nested functions inside functions are part of their parent, unless the parent is a big container.
110
+ // Helpers defined inside a test belong to that test.
111
+ const keep = !parent || (parent.kept && (parent.isClass || (parent.range.end - parent.range.start >= 40 && !parent.name.startsWith("test"))));
112
+ decl.kept = keep;
113
+ if (keep) {
114
+ if (parent)
115
+ parent.memberRanges.push(decl.range);
116
+ decls.push(decl);
117
+ }
118
+ stack.push(decl);
119
+ }
120
+ const imports = new Map();
121
+ const joined = code.join("\n");
122
+ for (const m of joined.matchAll(/^[ \t]*from\s+(\.*[\w.]*)\s+import\s+(\([^)]*\)|[^\n]+)/gm)) {
123
+ for (const part of m[2].replace(/[()\\]/g, " ").split(",")) {
124
+ const [name, alias] = part.trim().split(/\s+as\s+/);
125
+ if (name && name !== "*")
126
+ imports.set((alias ?? name).trim(), { module: m[1], name: name.trim() });
127
+ }
128
+ }
129
+ for (const m of joined.matchAll(/^[ \t]*import\s+([^\n]+)/gm)) {
130
+ for (const part of m[1].split(",")) {
131
+ const [mod, alias] = part.trim().split(/\s+as\s+/);
132
+ if (!mod)
133
+ continue;
134
+ if (alias)
135
+ imports.set(alias.trim(), { module: mod.trim(), name: null });
136
+ else
137
+ imports.set(mod.trim().split(".")[0], { module: mod.trim().split(".")[0], name: null });
138
+ }
139
+ }
140
+ const calls = [];
141
+ code.forEach((line, idx) => {
142
+ if (/^\s*(async\s+def|def|class)\s/.test(line))
143
+ return;
144
+ for (const m of line.matchAll(/(?<![\w.])([A-Za-z_][\w]*(?:\s*\.\s*[A-Za-z_]\w*)*)\s*\(/g)) {
145
+ const chain = m[1].replace(/\s+/g, "");
146
+ if (NOT_CALLS.has(chain))
147
+ continue;
148
+ calls.push({ chain, line: idx + 1, col: m.index });
149
+ }
150
+ // Call on a fresh instance: GrantService().create(1) reads as GrantService.create.
151
+ for (const m of line.matchAll(/(?<![\w.])([A-Za-z_][\w.]*)\s*\([^()]*\)\s*\.\s*([A-Za-z_]\w*)\s*\(/g)) {
152
+ calls.push({ chain: `${m[1]}.${m[2]}`, line: idx + 1, col: m.index + m[0].lastIndexOf(m[2]) });
153
+ }
154
+ });
155
+ return { file, decls, imports, calls, code };
156
+ }
157
+ function docstring(raw, from) {
158
+ for (let j = from; j < raw.length && j < from + 3; j++) {
159
+ const t = raw[j].trim();
160
+ if (!t)
161
+ continue;
162
+ const q = t.startsWith('"""') ? '"""' : t.startsWith("'''") ? "'''" : null;
163
+ if (!q)
164
+ return "";
165
+ const rest = t.slice(3);
166
+ if (rest.includes(q))
167
+ return rest.slice(0, rest.indexOf(q)).trim();
168
+ const lines = [rest];
169
+ for (let k = j + 1; k < raw.length && k < j + 40; k++) {
170
+ const l = raw[k];
171
+ if (l.includes(q)) {
172
+ lines.push(l.slice(0, l.indexOf(q)));
173
+ break;
174
+ }
175
+ lines.push(l);
176
+ }
177
+ return dedent(lines).trim();
178
+ }
179
+ return "";
180
+ }
181
+ function dedent(lines) {
182
+ const ind = Math.min(...lines.slice(1).filter((l) => l.trim()).map(indentOf), 1e9);
183
+ return [lines[0], ...lines.slice(1).map((l) => l.slice(ind === 1e9 ? 0 : ind))].join("\n");
184
+ }
185
+ export function enclosingPy(decls, line) {
186
+ let best = null;
187
+ for (const d of decls) {
188
+ if (line < d.range.start || line > d.range.end)
189
+ continue;
190
+ if (!best || d.range.end - d.range.start < best.range.end - best.range.start)
191
+ best = d;
192
+ }
193
+ return best;
194
+ }
195
+ /** Every Python file in the repo, parsed on demand, with module-path resolution. */
196
+ export class PyIndex {
197
+ parsed = new Map();
198
+ byModuleSuffix;
199
+ files;
200
+ read;
201
+ constructor(files, read) {
202
+ this.files = files;
203
+ this.read = read;
204
+ this.byModuleSuffix = new Map();
205
+ for (const f of files.filter(isPy)) {
206
+ const mod = f.replace(/\.pyi?$/, "").replace(/\/__init__$/, "").split("/");
207
+ // Index every suffix so `app.models` finds `src/app/models.py`.
208
+ for (let i = 0; i < mod.length; i++) {
209
+ const key = mod.slice(i).join(".");
210
+ (this.byModuleSuffix.get(key) ?? this.byModuleSuffix.set(key, []).get(key)).push(f);
211
+ }
212
+ }
213
+ }
214
+ all() {
215
+ return this.files.filter(isPy);
216
+ }
217
+ get(file) {
218
+ if (!this.parsed.has(file)) {
219
+ const text = this.read(file);
220
+ this.parsed.set(file, text === null ? null : parsePy(file, text));
221
+ }
222
+ return this.parsed.get(file);
223
+ }
224
+ moduleFile(fromFile, module) {
225
+ if (module.startsWith(".")) {
226
+ const dots = module.match(/^\.+/)[0].length;
227
+ let dir = path.posix.dirname(fromFile);
228
+ for (let i = 1; i < dots; i++)
229
+ dir = path.posix.dirname(dir);
230
+ const rest = module.slice(dots).replace(/\./g, "/");
231
+ const base = rest ? `${dir}/${rest}` : dir;
232
+ for (const cand of [`${base}.py`, `${base}/__init__.py`, `${base}.pyi`])
233
+ if (this.files.includes(cand.replace(/^\.\//, "")))
234
+ return cand.replace(/^\.\//, "");
235
+ return null;
236
+ }
237
+ const hits = this.byModuleSuffix.get(module);
238
+ if (!hits?.length)
239
+ return null;
240
+ return [...hits].sort((a, b) => a.length - b.length)[0];
241
+ }
242
+ /** What a call chain in a file refers to, as (file, decl). */
243
+ resolve(file, chain, line) {
244
+ const pf = this.get(file);
245
+ if (!pf)
246
+ return null;
247
+ const parts = chain.split(".");
248
+ const find = (f, key) => {
249
+ const target = this.get(f);
250
+ const d = target?.decls.find((x) => x.key === key) ?? null;
251
+ // Calling a class runs its __init__ when it has one.
252
+ if (d?.kind === "class")
253
+ return target?.decls.find((x) => x.key === `${key}.__init__`) ?? d;
254
+ return d;
255
+ };
256
+ if ((parts[0] === "self" || parts[0] === "cls") && parts.length === 2) {
257
+ const here = enclosingPy(pf.decls, line);
258
+ const cls = here?.container ?? (here?.kind === "class" ? here.key : null);
259
+ if (cls) {
260
+ const d = find(file, `${cls}.${parts[1]}`);
261
+ if (d)
262
+ return { file, decl: d };
263
+ }
264
+ return null;
265
+ }
266
+ if (parts.length === 1) {
267
+ const local = find(file, parts[0]);
268
+ if (local && !local.container)
269
+ return { file, decl: local };
270
+ }
271
+ const imp = pf.imports.get(parts[0]);
272
+ if (imp) {
273
+ if (imp.name) {
274
+ // from mod import name → name.rest...
275
+ const target = this.moduleFile(file, imp.module);
276
+ if (target) {
277
+ const d = find(target, [imp.name, ...parts.slice(1)].join("."));
278
+ if (d)
279
+ return { file: target, decl: d };
280
+ }
281
+ // from pkg import submodule → submodule.func
282
+ const sub = this.moduleFile(file, `${imp.module}${imp.module.endsWith(".") ? "" : "."}${imp.name}`);
283
+ if (sub && parts.length > 1) {
284
+ const d = find(sub, parts.slice(1).join("."));
285
+ if (d)
286
+ return { file: sub, decl: d };
287
+ }
288
+ }
289
+ else {
290
+ // import a.b as x → x.func, or import a → a.b.func
291
+ for (let cut = parts.length - 1; cut >= 1; cut--) {
292
+ const modPath = imp.module.split(".").length > 1 && parts.length - cut === 1 ? imp.module : [imp.module, ...parts.slice(1, cut)].join(".");
293
+ const target = this.moduleFile(file, modPath);
294
+ if (target) {
295
+ const d = find(target, parts.slice(cut).join("."));
296
+ if (d)
297
+ return { file: target, decl: d };
298
+ }
299
+ }
300
+ }
301
+ }
302
+ // obj.method(): if exactly one method by that name exists anywhere, it's almost surely that one.
303
+ if (parts.length >= 2) {
304
+ const method = parts.at(-1);
305
+ if (method.startsWith("__"))
306
+ return null;
307
+ const hits = [];
308
+ for (const f of this.all()) {
309
+ for (const d of this.get(f)?.decls ?? [])
310
+ if (d.name === method && d.container)
311
+ hits.push({ file: f, decl: d });
312
+ if (hits.length > 1)
313
+ return null;
314
+ }
315
+ return hits[0] ?? null;
316
+ }
317
+ return null;
318
+ }
319
+ /** Callee names inside a range, for diffing calls across sides. */
320
+ static calleeNames(pf, range) {
321
+ const names = new Map();
322
+ for (const c of pf.calls) {
323
+ if (c.line < range.start || c.line > range.end)
324
+ continue;
325
+ const name = c.chain.split(".").at(-1);
326
+ if (!names.has(name))
327
+ names.set(name, c.line);
328
+ }
329
+ return names;
330
+ }
331
+ /** The identifier chain under a cursor, for hover and go-to-definition. */
332
+ chainAt(file, line, col) {
333
+ const pf = this.get(file);
334
+ const text = pf?.code[line - 1];
335
+ if (!text)
336
+ return null;
337
+ let s = col;
338
+ let e = col;
339
+ while (s > 0 && /[\w.]/.test(text[s - 1]))
340
+ s--;
341
+ while (e < text.length && /\w/.test(text[e]))
342
+ e++;
343
+ const chain = text.slice(s, e).replace(/^\.+|\.+$/g, "");
344
+ return chain || null;
345
+ }
346
+ }
@@ -0,0 +1,299 @@
1
+ // Syntax-only declaration extraction. No type checker needed, so it runs on
2
+ // both sides of a change (the old side has no program to check against).
3
+ import ts from "typescript";
4
+ const TS_EXT = /\.(m|c)?(t|j)sx?$/;
5
+ export const isTsLike = (file) => TS_EXT.test(file) && !file.endsWith(".d.ts");
6
+ export function scriptKind(file) {
7
+ if (file.endsWith(".tsx"))
8
+ return ts.ScriptKind.TSX;
9
+ if (file.endsWith(".jsx"))
10
+ return ts.ScriptKind.JSX;
11
+ if (/\.(m|c)?js$/.test(file))
12
+ return ts.ScriptKind.JS;
13
+ return ts.ScriptKind.TS;
14
+ }
15
+ export function parse(file, text) {
16
+ return ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, scriptKind(file));
17
+ }
18
+ const HTTP = new Set(["get", "post", "put", "patch", "delete", "all", "options", "head"]);
19
+ /** Where a declaration really starts: its JSDoc belongs to it, and reviewers read docs too. */
20
+ function docStart(sf, node) {
21
+ const holder = ts.isVariableDeclaration(node) && ts.isVariableDeclarationList(node.parent) && node.parent.declarations.length === 1 ? node.parent.parent : node;
22
+ const docs = holder.jsDoc;
23
+ return docs?.length ? Math.min(docs[0].getStart(sf), node.getStart(sf)) : node.getStart(sf);
24
+ }
25
+ function lines(sf, node) {
26
+ const start = sf.getLineAndCharacterOfPosition(docStart(sf, node)).line + 1;
27
+ const end = sf.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
28
+ return { start, end };
29
+ }
30
+ function isExported(node) {
31
+ const mods = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
32
+ return !!mods?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
33
+ }
34
+ const squash = (s) => s.replace(/\s+/g, " ").trim();
35
+ function headText(sf, from, body) {
36
+ const start = from.getStart(sf);
37
+ const end = body ? body.getStart(sf) : from.getEnd();
38
+ const text = squash(sf.text.slice(start, end)).replace(/\s*(=>|\{)?\s*$/, "");
39
+ return text.length > 240 ? text.slice(0, 237) + "…" : text;
40
+ }
41
+ function clip(text, maxLines = 14) {
42
+ const ls = text.split("\n");
43
+ return ls.length > maxLines ? ls.slice(0, maxLines).join("\n") + "\n …" : text;
44
+ }
45
+ function blockText(sf, node, maxLines = 14) {
46
+ const text = sf.text.slice(node.getStart(sf), node.getEnd());
47
+ const ls = text.split("\n");
48
+ return ls.length > maxLines ? ls.slice(0, maxLines).join("\n") + "\n …" : text;
49
+ }
50
+ function isFunctionLike(init) {
51
+ return !!init && (ts.isArrowFunction(init) || ts.isFunctionExpression(init));
52
+ }
53
+ /** `memo(() => …)`, `forwardRef(function X() …)`, `defineAction({ run() … })`. */
54
+ function wrapsFunction(init) {
55
+ if (!init || !ts.isCallExpression(init))
56
+ return false;
57
+ return init.arguments.some((a) => isFunctionLike(a) || ts.isObjectLiteralExpression(a) || wrapsFunction(a));
58
+ }
59
+ export function extractDecls(sf) {
60
+ const out = [];
61
+ const jsx = sf.fileName.endsWith("x");
62
+ const fnKind = (name) => (jsx && /^[A-Z]/.test(name) ? "component" : "function");
63
+ const pushMembers = (container, members, exported) => {
64
+ const ranges = [];
65
+ for (const m of members) {
66
+ let name = null;
67
+ let body;
68
+ if (ts.isConstructorDeclaration(m)) {
69
+ name = "constructor";
70
+ body = m.body;
71
+ }
72
+ else if ((ts.isMethodDeclaration(m) || ts.isGetAccessor(m) || ts.isSetAccessor(m)) && m.name) {
73
+ name = m.name.getText(sf);
74
+ body = m.body;
75
+ }
76
+ else if ((ts.isPropertyDeclaration(m) || ts.isPropertyAssignment(m)) && isFunctionLike(m.initializer)) {
77
+ name = m.name.getText(sf);
78
+ body = m.initializer.body;
79
+ }
80
+ if (!name)
81
+ continue;
82
+ const range = lines(sf, m);
83
+ ranges.push(range);
84
+ out.push({
85
+ key: `${container}.${name}`,
86
+ name,
87
+ container,
88
+ kind: "method",
89
+ node: m,
90
+ nameNode: m.name ?? m.getFirstToken(sf) ?? null,
91
+ range,
92
+ exported,
93
+ signature: headText(sf, m, body),
94
+ memberRanges: [],
95
+ });
96
+ }
97
+ return ranges;
98
+ };
99
+ for (const st of sf.statements) {
100
+ const exported = isExported(st);
101
+ if (ts.isFunctionDeclaration(st)) {
102
+ const name = st.name?.text ?? "default";
103
+ out.push({
104
+ key: name,
105
+ name,
106
+ container: null,
107
+ kind: fnKind(name),
108
+ node: st,
109
+ nameNode: st.name ?? null,
110
+ range: lines(sf, st),
111
+ exported,
112
+ signature: headText(sf, st, st.body),
113
+ memberRanges: [],
114
+ });
115
+ }
116
+ else if (ts.isClassDeclaration(st)) {
117
+ const name = st.name?.text ?? "default";
118
+ const decl = {
119
+ key: name,
120
+ name,
121
+ container: null,
122
+ kind: "class",
123
+ node: st,
124
+ nameNode: st.name ?? null,
125
+ range: lines(sf, st),
126
+ exported,
127
+ signature: clip(sf.text.slice(st.getStart(sf), st.members.pos).replace(/\s*\{\s*$/, "")),
128
+ memberRanges: [],
129
+ };
130
+ out.push(decl);
131
+ decl.memberRanges = pushMembers(name, st.members, exported);
132
+ }
133
+ else if (ts.isInterfaceDeclaration(st) || ts.isTypeAliasDeclaration(st) || ts.isEnumDeclaration(st)) {
134
+ out.push({
135
+ key: st.name.text,
136
+ name: st.name.text,
137
+ container: null,
138
+ kind: ts.isInterfaceDeclaration(st) ? "interface" : "type",
139
+ node: st,
140
+ nameNode: st.name,
141
+ range: lines(sf, st),
142
+ exported,
143
+ signature: blockText(sf, st),
144
+ memberRanges: [],
145
+ });
146
+ }
147
+ else if (ts.isVariableStatement(st)) {
148
+ for (const d of st.declarationList.declarations) {
149
+ if (!ts.isIdentifier(d.name))
150
+ continue;
151
+ const name = d.name.text;
152
+ const init = d.initializer;
153
+ const kind = isFunctionLike(init)
154
+ ? fnKind(name)
155
+ : init && ts.isClassExpression(init)
156
+ ? "class"
157
+ : wrapsFunction(init) && jsx && /^[A-Z]/.test(name)
158
+ ? "component"
159
+ : "const";
160
+ const range = lines(sf, st.declarationList.declarations.length === 1 ? st : d);
161
+ const decl = {
162
+ key: name,
163
+ name,
164
+ container: null,
165
+ kind,
166
+ node: d,
167
+ nameNode: d.name,
168
+ range,
169
+ exported,
170
+ signature: isFunctionLike(init)
171
+ ? `${exported ? "export " : ""}const ${headText(sf, d, init.body)}`
172
+ : squash(blockText(sf, d, 1)).slice(0, 160),
173
+ memberRanges: [],
174
+ };
175
+ out.push(decl);
176
+ if (init && ts.isObjectLiteralExpression(init)) {
177
+ decl.memberRanges = pushMembers(name, init.properties.filter((p) => ts.isMethodDeclaration(p) || ts.isPropertyAssignment(p)), exported);
178
+ }
179
+ else if (init && ts.isClassExpression(init)) {
180
+ decl.memberRanges = pushMembers(name, init.members, exported);
181
+ }
182
+ }
183
+ }
184
+ else if (ts.isExportAssignment(st) && isFunctionLike(st.expression)) {
185
+ out.push({
186
+ key: "default",
187
+ name: "default",
188
+ container: null,
189
+ kind: fnKind("Default"),
190
+ node: st,
191
+ nameNode: null,
192
+ range: lines(sf, st),
193
+ exported: true,
194
+ signature: headText(sf, st, st.expression.body),
195
+ memberRanges: [],
196
+ });
197
+ }
198
+ }
199
+ // Big containers (Effect service layers, app builders, factories) define their real units as inner
200
+ // named functions: `const patchGrant = Effect.fn(...)(function* ...)`. Lift those out as members so a
201
+ // reviewer can open the unit that changed. Components are left whole; their handlers read best in place.
202
+ const NESTED_MIN_LINES = 40;
203
+ for (const d of [...out]) {
204
+ if (d.kind === "component" || d.range.end - d.range.start < NESTED_MIN_LINES)
205
+ continue;
206
+ const owned = (n) => d.memberRanges.some((r) => lines(sf, n).start >= r.start && lines(sf, n).end <= r.end);
207
+ const visit = (n) => {
208
+ let name = null;
209
+ let body;
210
+ if (ts.isVariableDeclaration(n) && ts.isIdentifier(n.name) && (isFunctionLike(n.initializer) || wrapsFunction(n.initializer))) {
211
+ name = n.name.text;
212
+ body = isFunctionLike(n.initializer) ? n.initializer.body : undefined;
213
+ }
214
+ else if (ts.isFunctionDeclaration(n) && n.name) {
215
+ name = n.name.text;
216
+ body = n.body;
217
+ }
218
+ if (name && !owned(n)) {
219
+ const range = lines(sf, n);
220
+ if (range.end > range.start) {
221
+ d.memberRanges.push(range);
222
+ out.push({
223
+ key: `${d.name}.${name}`,
224
+ name,
225
+ container: d.name,
226
+ kind: "method",
227
+ node: n,
228
+ nameNode: n.name ?? null,
229
+ range,
230
+ exported: d.exported,
231
+ signature: body ? headText(sf, n, body) : squash(blockText(sf, n, 1)).slice(0, 200),
232
+ memberRanges: [],
233
+ });
234
+ return;
235
+ }
236
+ }
237
+ ts.forEachChild(n, visit);
238
+ };
239
+ ts.forEachChild(d.node, visit);
240
+ }
241
+ // Route registrations anywhere in the file: app.get("/x", handler).
242
+ const visit = (node) => {
243
+ if (ts.isCallExpression(node) &&
244
+ ts.isPropertyAccessExpression(node.expression) &&
245
+ HTTP.has(node.expression.name.text) &&
246
+ node.arguments.length >= 2 &&
247
+ ts.isStringLiteralLike(node.arguments[0]) &&
248
+ node.arguments[0].text.startsWith("/")) {
249
+ const method = node.expression.name.text.toUpperCase();
250
+ const route = node.arguments[0].text;
251
+ const name = `${method} ${route}`;
252
+ out.push({
253
+ key: `route:${name}`,
254
+ name,
255
+ container: null,
256
+ kind: "route",
257
+ node,
258
+ nameNode: node.expression.name,
259
+ range: lines(sf, node),
260
+ exported: true,
261
+ signature: `${node.expression.expression.getText(sf)}.${node.expression.name.text}("${route}", …)`,
262
+ memberRanges: [],
263
+ });
264
+ }
265
+ ts.forEachChild(node, visit);
266
+ };
267
+ visit(sf);
268
+ return out;
269
+ }
270
+ /** Innermost declaration containing a position. Routes and methods win over their enclosing function. */
271
+ export function enclosing(decls, sf, pos) {
272
+ let best = null;
273
+ for (const d of decls) {
274
+ if (pos < d.node.getStart(sf) || pos > d.node.getEnd())
275
+ continue;
276
+ if (!best || d.node.getEnd() - d.node.getStart(sf) < best.node.getEnd() - best.node.getStart(sf))
277
+ best = d;
278
+ }
279
+ return best;
280
+ }
281
+ /** Names called inside a node. Used to diff call sets across sides without a checker. */
282
+ export function calleeNames(sf, node) {
283
+ const names = new Map();
284
+ const visit = (n) => {
285
+ let target;
286
+ if (ts.isCallExpression(n) || ts.isNewExpression(n))
287
+ target = n.expression;
288
+ else if (ts.isJsxOpeningElement(n) || ts.isJsxSelfClosingElement(n))
289
+ target = n.tagName;
290
+ if (target) {
291
+ const name = ts.isPropertyAccessExpression(target) ? target.name.text : ts.isIdentifier(target) ? target.text : null;
292
+ if (name && !names.has(name))
293
+ names.set(name, sf.getLineAndCharacterOfPosition(n.getStart(sf)).line + 1);
294
+ }
295
+ ts.forEachChild(n, visit);
296
+ };
297
+ ts.forEachChild(node, visit);
298
+ return names;
299
+ }