@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,543 @@
1
+ // `downstream`: the agent's handle on a review. Every write goes through the
2
+ // same HTTP actions the browser uses; reads can also go straight to SQLite
3
+ // through a read-only connection.
4
+ import { spawn, execFileSync } from "node:child_process";
5
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, openSync, readdirSync, statSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import path from "node:path";
8
+ import net from "node:net";
9
+ import { codeRoot, install, packageRoot, serverEntry } from "../server/install.js";
10
+ import { severityLabel } from "../domain/model.js";
11
+ const skillDir = packageRoot;
12
+ const argv = process.argv.slice(2);
13
+ const cmd = argv[0] ?? "help";
14
+ // Under the 300s headers timeout of Node's fetch.
15
+ const WAIT_CHUNK_SECONDS = 240;
16
+ function flags(list) {
17
+ const pos = [];
18
+ const opts = {};
19
+ for (let i = 0; i < list.length; i++) {
20
+ const a = list[i];
21
+ if (a.startsWith("--")) {
22
+ const [k, v] = a.slice(2).split("=", 2);
23
+ if (v !== undefined)
24
+ opts[k] = v;
25
+ else if (list[i + 1] !== undefined && !list[i + 1].startsWith("--"))
26
+ opts[k] = list[++i];
27
+ else
28
+ opts[k] = true;
29
+ }
30
+ else
31
+ pos.push(a);
32
+ }
33
+ return { pos, opts };
34
+ }
35
+ const { pos, opts } = flags(argv.slice(1));
36
+ const str = (k) => (typeof opts[k] === "string" ? opts[k] : undefined);
37
+ function repoRoot() {
38
+ try {
39
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ }
45
+ const insideRepo = () => repoRoot() !== null;
46
+ function fail(msg) {
47
+ console.error(`downstream: ${msg}`);
48
+ process.exit(1);
49
+ }
50
+ /** The one server for this machine, shared by every repository (and the home page). */
51
+ const homeDir = process.env.DOWNSTREAM_HOME ?? path.join(homedir(), ".downstream");
52
+ const serverFile = path.join(homeDir, "server.json");
53
+ // Repository commands need a repo; `home`, `stop` and `url` don't.
54
+ let rootCache = null;
55
+ const root = () => (rootCache ??= repoRoot() ?? fail("Not inside a git repository."));
56
+ const dir = () => path.join(root(), ".downstream");
57
+ const cursorFile = () => path.join(dir(), "agent-cursor");
58
+ async function health() {
59
+ if (!existsSync(serverFile))
60
+ return null;
61
+ const { url } = JSON.parse(readFileSync(serverFile, "utf8"));
62
+ try {
63
+ const r = await fetch(`${url}/api/health`, { signal: AbortSignal.timeout(1500) });
64
+ const h = (await r.json());
65
+ return { url, pid: h.pid, startedAt: h.startedAt ?? 0 };
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ }
71
+ async function serverUrl() {
72
+ return (await health())?.url ?? null;
73
+ }
74
+ let repoBase = null;
75
+ /** This repository's API on the shared server: `<server>/r/<repo id>`. */
76
+ async function base() {
77
+ if (repoBase)
78
+ return repoBase;
79
+ const url = (await serverUrl()) ?? fail("no server running. Start with `downstream open`.");
80
+ const r = await fetch(`${url}/api/repos`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ root: root() }) });
81
+ const body = (await r.json());
82
+ if (!r.ok || !body.id)
83
+ fail(body.error ?? "couldn't register this repository with the server");
84
+ return (repoBase = `${url}/r/${body.id}`);
85
+ }
86
+ function newest(dir) {
87
+ let t = 0;
88
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
89
+ const p = path.join(dir, e.name);
90
+ t = Math.max(t, e.isDirectory() ? newest(p) : statSync(p).mtimeMs);
91
+ }
92
+ return t;
93
+ }
94
+ /** Newest change to the server's own code, so an updated skill restarts a stale server. */
95
+ function codeVersion() {
96
+ return Math.max(newest(path.join(codeRoot, "server")), newest(path.join(codeRoot, "domain")));
97
+ }
98
+ function freePort(start) {
99
+ return new Promise((resolve) => {
100
+ const s = net.createServer();
101
+ s.once("error", () => resolve(freePort(start + 1)));
102
+ s.listen(start, "127.0.0.1", () => s.close(() => resolve(start)));
103
+ });
104
+ }
105
+ function ensureBuilt() {
106
+ // A published copy ships its dependencies and built UI; it has no sources to build from.
107
+ if (install === "published")
108
+ return;
109
+ if (!existsSync(path.join(skillDir, "node_modules", "hono"))) {
110
+ console.error("downstream: installing dependencies (first run)…");
111
+ execFileSync("npm", ["install", "--no-audit", "--no-fund"], { cwd: skillDir, stdio: "inherit", shell: process.platform === "win32" });
112
+ }
113
+ const built = path.join(skillDir, "web", "dist", "index.html");
114
+ if (!existsSync(built) || newest(path.join(skillDir, "web", "src")) > statSync(built).mtimeMs) {
115
+ console.error("downstream: building the review UI…");
116
+ execFileSync("npm", ["run", "build"], { cwd: skillDir, stdio: "inherit", shell: process.platform === "win32" });
117
+ }
118
+ }
119
+ async function ensureServer() {
120
+ const running = await health();
121
+ if (running && running.startedAt >= codeVersion())
122
+ return running.url;
123
+ if (running) {
124
+ console.error("downstream: the skill was updated; restarting the server…");
125
+ try {
126
+ process.kill(running.pid);
127
+ }
128
+ catch {
129
+ /* already gone */
130
+ }
131
+ await new Promise((r) => setTimeout(r, 400));
132
+ }
133
+ ensureBuilt();
134
+ mkdirSync(homeDir, { recursive: true });
135
+ const port = await freePort(Number(str("port") ?? 4317));
136
+ const log = openSync(path.join(homeDir, "server.log"), "a");
137
+ const child = spawn(process.execPath, ["--no-warnings=ExperimentalWarning", serverEntry, `--port=${port}`], { cwd: skillDir, detached: true, stdio: ["ignore", log, log], windowsHide: true });
138
+ child.unref();
139
+ for (let i = 0; i < 100; i++) {
140
+ await new Promise((r) => setTimeout(r, 150));
141
+ const url = await serverUrl();
142
+ if (url)
143
+ return url;
144
+ }
145
+ fail(`server did not start; see ${path.join(homeDir, "server.log")}`);
146
+ }
147
+ async function call(name, input = {}) {
148
+ const r = await fetch(`${await base()}/api/actions/${name}`, {
149
+ method: "POST",
150
+ headers: { "content-type": "application/json", "x-downstream-actor": "agent" },
151
+ body: JSON.stringify({ input }),
152
+ });
153
+ const body = (await r.json());
154
+ if (!r.ok)
155
+ fail(`${name}: ${body.error ?? r.statusText}${body.issues ? "\n" + JSON.stringify(body.issues, null, 2) : ""}`);
156
+ return body;
157
+ }
158
+ async function get(p) {
159
+ const r = await fetch(`${await base()}${p.replace(/^\/api/, "/api")}`, { headers: { "x-downstream-actor": "agent" } });
160
+ const body = (await r.json());
161
+ if (!r.ok)
162
+ fail(body.error ?? r.statusText);
163
+ return body;
164
+ }
165
+ function openBrowser(url) {
166
+ if (opts["no-browser"])
167
+ return;
168
+ const [bin, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
169
+ try {
170
+ spawn(bin, args, { detached: true, stdio: "ignore" }).unref();
171
+ }
172
+ catch {
173
+ /* printing the URL is enough */
174
+ }
175
+ }
176
+ const range = (v) => {
177
+ if (!v)
178
+ return null;
179
+ const [a, b] = v.split(/[-:]/).map(Number);
180
+ return { start: a, end: b ?? a };
181
+ };
182
+ const glyph = { added: "+", modified: "~", removed: "-", context: " " };
183
+ function outline(state) {
184
+ const { review, symbols, edges, notes, steps, files } = state;
185
+ const byId = new Map(symbols.map((s) => [s.id, s]));
186
+ const out = [];
187
+ out.push(`${review.title} [${review.mode}${review.base ? `, ${review.base}…${review.head === "WORKTREE" ? "working tree" : review.head}` : ""}, ${review.status}]`);
188
+ if (files.length) {
189
+ const add = files.reduce((n, f) => n + f.additions, 0);
190
+ const del = files.reduce((n, f) => n + f.deletions, 0);
191
+ out.push(`${files.length} files +${add} -${del}`);
192
+ }
193
+ out.push(review.summary ? `\nSummary: ${review.summary.split("\n")[0]}` : "\nSummary: (none yet: `downstream apply` or review.update)");
194
+ const entries = symbols.filter((s) => s.entry);
195
+ out.push(`\nEntry points (${entries.length})`);
196
+ for (const s of entries)
197
+ out.push(` ${s.id}`);
198
+ const order = ["added", "modified", "removed", "context"];
199
+ out.push(`\nSymbols (+ added ~ modified - removed · context)`);
200
+ for (const status of order) {
201
+ for (const s of symbols.filter((x) => x.status === status).sort((a, b) => a.file.localeCompare(b.file))) {
202
+ const lines = s.range ? `:${s.range.start}-${s.range.end}` : s.oldRange ? ` (was :${s.oldRange.start}-${s.oldRange.end})` : "";
203
+ const tag = s.status === "context" ? "·" : glyph[s.status];
204
+ out.push(` ${tag} ${s.id}${lines} ${s.kind}${s.entry ? " ENTRY" : ""}${s.summary ? `\n ${s.summary}` : ""}`);
205
+ }
206
+ }
207
+ out.push(`\nFlow (${edges.length} edges; * = new in this change, x = removed)`);
208
+ for (const e of edges.sort((a, b) => a.from.localeCompare(b.from))) {
209
+ const mark = e.change === "added" ? "*" : e.change === "removed" ? "x" : " ";
210
+ out.push(` ${mark} ${short(byId.get(e.from), e.from)} ─${e.kind}→ ${short(byId.get(e.to), e.to)}${e.line ? ` (line ${e.line})` : ""}${e.label ? ` "${e.label}"` : ""}`);
211
+ }
212
+ out.push(`\nWalkthrough (${steps.length} steps)`);
213
+ for (const s of steps)
214
+ out.push(` ${s.order}. ${s.title}${s.symbolId ? ` → ${s.symbolId}` : ""}`);
215
+ const open = notes.filter((n) => n.status === "open");
216
+ out.push(`\nThreads (${open.length} open of ${notes.length})`);
217
+ for (const n of notes)
218
+ out.push(` ${noteLine(n)}`);
219
+ return out.join("\n");
220
+ }
221
+ const short = (s, id) => (s ? `${s.container ? s.container + "." : ""}${s.name}` : id);
222
+ function noteLine(n) {
223
+ const sev = n.severity ? `/${severityLabel[n.severity]}` : "";
224
+ const out = n.outgoing ? (n.outgoing.include ? " [goes to PR]" : " [private]") : "";
225
+ const last = n.replies.at(-1);
226
+ const waiting = last ? (last.author === "user" ? " ← user replied" : "") : n.author === "user" ? " ← from user" : "";
227
+ return `#${n.number} [${n.id}] ${n.status === "open" ? "" : `(${n.status}) `}${n.kind}${sev} by ${n.author}: ${n.title}${n.symbolId ? ` @ ${n.symbolId}` : ""}${n.lines ? `:${n.lines.start}-${n.lines.end}` : ""} (${n.replies.length} replies)${waiting}${out}`;
228
+ }
229
+ function printFrame(patch) {
230
+ const lines = patch.split("\n");
231
+ const header = lines.find((l) => l.startsWith("@@"));
232
+ const m = header.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
233
+ let oldN = Number(m[1]);
234
+ let newN = Number(m[2]);
235
+ const out = [];
236
+ for (const l of lines.slice(lines.indexOf(header) + 1)) {
237
+ if (!l && out.length && lines.at(-1) === l)
238
+ continue;
239
+ const t = l[0];
240
+ if (t === "+")
241
+ out.push(`${String(newN++).padStart(5)} + ${l.slice(1)}`);
242
+ else if (t === "-")
243
+ out.push(`${String(oldN++).padStart(5)} - ${l.slice(1)}`);
244
+ else {
245
+ out.push(`${String(newN++).padStart(5)} ${l.slice(1)}`);
246
+ oldN++;
247
+ }
248
+ }
249
+ console.log(out.join("\n"));
250
+ }
251
+ function describe(e, state) {
252
+ const p = e.payload;
253
+ const note = state?.notes.find((n) => n.id === (p.noteId ?? p.id));
254
+ switch (e.type) {
255
+ case "ask":
256
+ return `The user asked [${p.id}]${p.symbolId ? ` about ${p.symbolId}` : ""}${p.lines ? ` lines ${p.lines.start}-${p.lines.end}` : ""}:\n "${p.body}"\n → answer: downstream reply ${p.id} "…"`;
257
+ case "note.replied":
258
+ return `The user replied in [${p.noteId}] "${p.title}":\n "${p.body}"\n → downstream reply ${p.noteId} "…" (or resolve it: downstream resolve ${p.noteId} "why")`;
259
+ case "note.added":
260
+ return `The user opened a ${String(p.kind)} [${p.id}]: "${p.title}"${note?.body ? `\n ${note.body}` : ""}\n → downstream reply ${p.id} "…"`;
261
+ case "note.status":
262
+ return `The user marked [${p.noteId}] "${p.title}" as ${p.status}${p.reason ? `: "${p.reason}"` : ""}`;
263
+ case "review.verdict":
264
+ return `The user's verdict: ${p.value}${p.body ? ` ("${p.body}")` : ""}`;
265
+ case "review.done":
266
+ return "The user ended the review session.";
267
+ case "note.updated":
268
+ return `The user edited #${p.number} "${p.title}" (${p.fields.join(", ")}). Run downstream draft to see what would be sent.`;
269
+ case "review.published":
270
+ return `The user sent the review to the pull request (${p.event}, ${p.comments} inline comments): ${p.url}`;
271
+ case "symbol.annotated":
272
+ return `The user changed ${p.id}${p.entry !== null ? ` (entry=${p.entry})` : ""}`;
273
+ default:
274
+ return `${e.type} ${JSON.stringify(p)}`;
275
+ }
276
+ }
277
+ const HELP = `downstream: a review surface you and the user share.
278
+
279
+ Start
280
+ home open the home page: every PR that needs you, one next action each
281
+ open [--base <ref>] [--head <ref>] [--pr <n>] [--title <t>] review a change (default: working tree vs main)
282
+ open --teach <path...> explain existing code instead of a diff
283
+ url | status | stop
284
+
285
+ Read
286
+ show [--json] the map: entry points, symbols, flow edges, steps, threads
287
+ code <symbol> a symbol's code with line numbers and +/- marks
288
+ screen what the user is looking at and has selected
289
+ sql "<select …>" read-only SQL (views: v_symbols, v_edges, v_notes, v_replies)
290
+ actions every action with its input schema
291
+
292
+ Write (the browser uses the same actions)
293
+ apply <file.json | -> author the walkthrough in one batch (see SKILL.md for the shape)
294
+ note <kind> [symbol] --title "…" [--body "…"] [--severity blocker|concern|nit] [--lines a-b]
295
+ kind: why | decision | finding | question | comment
296
+ reply <thread> "…" resolve <thread> ["why"] dismiss <thread> ["why"] reopen <thread>
297
+ ask "…" [--symbol s] [--lines a-b]
298
+ go <symbol> [--lines a-b] | go step <n> | go thread <id> | go map | go overview
299
+ verdict approve|changes|comment ["…"]
300
+ outgoing <thread> "…" [--exclude] word a thread for the PR author, or keep it private
301
+ draft the review exactly as it would be sent
302
+ send [--event comment] [--force] send it to the PR, only when the user has said to (--force: even though the PR changed since you opened it)
303
+ reanalyze re-read the code after edits
304
+ do <action> '<json>' call any action directly
305
+
306
+ Converse
307
+ wait [--timeout 600] block until the user replies, asks, resolves or ends the session`;
308
+ async function main() {
309
+ switch (cmd) {
310
+ case "open": {
311
+ await ensureServer();
312
+ const teach = opts.teach !== undefined;
313
+ const paths = teach ? [...(typeof opts.teach === "string" ? [opts.teach] : []), ...pos] : [];
314
+ const res = await call("review.open", {
315
+ mode: teach ? "teach" : "diff",
316
+ base: str("base"),
317
+ head: str("head"),
318
+ pr: str("pr") ? Number(str("pr")) : undefined,
319
+ paths,
320
+ title: str("title"),
321
+ });
322
+ writeFileSync(cursorFile(), "0");
323
+ const state = await get("/api/state");
324
+ writeFileSync(cursorFile(), String(state.cursor));
325
+ const page = await base();
326
+ console.log(`Opened "${res.review.title}": ${res.files} files, ${res.symbols} symbols, ${res.edges} edges.`);
327
+ console.log(`UI: ${page}`);
328
+ console.log(`Next: \`downstream show\`, read the code, then \`downstream apply plan.json\`. See SKILL.md.`);
329
+ // Started from the home page, the user's browser is already on its way to this review.
330
+ if (!process.env.DOWNSTREAM_HOSTED)
331
+ openBrowser(page);
332
+ return;
333
+ }
334
+ case "home": {
335
+ const url = await ensureServer();
336
+ console.log(`Home: ${url}`);
337
+ openBrowser(url);
338
+ return;
339
+ }
340
+ case "url":
341
+ {
342
+ const url = await serverUrl();
343
+ if (!url)
344
+ return console.log("not running");
345
+ console.log(insideRepo() ? await base() : url);
346
+ return;
347
+ }
348
+ case "status": {
349
+ const url = await serverUrl();
350
+ if (!url)
351
+ return console.log("not running");
352
+ const state = await get("/api/state");
353
+ console.log(`${await base()} · ${state.review.title} · ${state.notes.filter((n) => n.status === "open").length} open threads · agent ${state.presence.agent}`);
354
+ return;
355
+ }
356
+ case "stop": {
357
+ if (!existsSync(serverFile))
358
+ return console.log("not running");
359
+ const { pid } = JSON.parse(readFileSync(serverFile, "utf8"));
360
+ try {
361
+ process.kill(pid);
362
+ }
363
+ catch {
364
+ /* already gone */
365
+ }
366
+ console.log("stopped");
367
+ return;
368
+ }
369
+ case "show": {
370
+ const state = await get("/api/state");
371
+ console.log(opts.json ? JSON.stringify(state, null, 2) : outline(state));
372
+ return;
373
+ }
374
+ case "code": {
375
+ const state = await get("/api/state");
376
+ const q = pos[0] ?? fail("which symbol?");
377
+ const s = state.symbols.find((x) => x.id === q) ?? state.symbols.find((x) => x.id.endsWith(`#${q}`) || x.name === q) ?? fail(`no symbol ${q}`);
378
+ const frame = await get(`/api/frame?symbol=${encodeURIComponent(s.id)}`);
379
+ console.log(`${s.id} [${s.status}] ${s.file}${s.range ? `:${s.range.start}-${s.range.end}` : ""}`);
380
+ if (s.signature)
381
+ console.log(s.signature);
382
+ const into = state.edges.filter((e) => e.to === s.id).map((e) => `${e.from}${e.change === "added" ? " *" : ""}`);
383
+ const out = state.edges.filter((e) => e.from === s.id).map((e) => `${e.to}${e.change === "added" ? " *" : e.change === "removed" ? " x" : ""}`);
384
+ if (into.length)
385
+ console.log(`called by: ${into.join(", ")}`);
386
+ if (out.length)
387
+ console.log(`calls: ${out.join(", ")}`);
388
+ console.log("");
389
+ printFrame(frame.patch);
390
+ return;
391
+ }
392
+ case "screen":
393
+ console.log(JSON.stringify(await call("screen"), null, 2));
394
+ return;
395
+ case "sql": {
396
+ const { readOnly } = await import("../server/store.js");
397
+ const db = readOnly(path.join(dir(), "review.db"));
398
+ const rows = db.prepare(pos.join(" ")).all();
399
+ console.log(rows.length ? JSON.stringify(rows, null, 2) : "(no rows)");
400
+ return;
401
+ }
402
+ case "actions": {
403
+ const list = await get("/api/actions");
404
+ for (const a of list)
405
+ console.log(`${a.name}\n ${a.description}\n input: ${JSON.stringify(a.input)}\n`);
406
+ return;
407
+ }
408
+ case "apply": {
409
+ const src = pos[0] ?? "-";
410
+ const text = src === "-" ? readFileSync(0, "utf8") : readFileSync(path.resolve(src), "utf8");
411
+ const res = await call("apply", JSON.parse(text));
412
+ console.log(`applied: ${res.applied.join(", ") || "nothing"}`);
413
+ return;
414
+ }
415
+ case "note": {
416
+ const kind = pos[0] ?? fail("note <why|decision|finding|question|comment> [symbol] --title …");
417
+ const note = await call("note.add", {
418
+ kind,
419
+ symbolId: pos[1] ?? str("symbol") ?? null,
420
+ title: str("title") ?? fail("--title is required"),
421
+ body: str("body") ?? "",
422
+ severity: str("severity") ?? null,
423
+ category: str("category") ?? null,
424
+ fix: str("fix") ?? "",
425
+ impact: str("impact") ?? "",
426
+ lines: range(str("lines")),
427
+ side: str("side") ?? "new",
428
+ });
429
+ console.log(`#${note.number} [${note.id}] ${note.kind}: ${note.title}`);
430
+ return;
431
+ }
432
+ case "reply": {
433
+ const [noteId, ...rest] = pos;
434
+ await call("note.reply", { noteId, body: rest.join(" ") || str("body") || fail("reply <thread> \"text\"") });
435
+ console.log("replied");
436
+ return;
437
+ }
438
+ case "resolve":
439
+ case "dismiss":
440
+ case "reopen": {
441
+ const [noteId, ...rest] = pos;
442
+ const status = cmd === "resolve" ? "resolved" : cmd === "dismiss" ? "dismissed" : "open";
443
+ await call("note.status", { noteId, status, reason: rest.join(" ") || undefined });
444
+ console.log(status);
445
+ return;
446
+ }
447
+ case "ask": {
448
+ const note = await call("ask", { body: pos.join(" "), symbolId: str("symbol") ?? null, lines: range(str("lines")) });
449
+ console.log(`[${note.id}] asked. \`downstream wait\` to hear back.`);
450
+ return;
451
+ }
452
+ case "go": {
453
+ const [what, arg] = pos;
454
+ let input;
455
+ if (what === "map" || what === "overview" || what === "trail")
456
+ input = { view: what };
457
+ else if (what === "step") {
458
+ const state = await get("/api/state");
459
+ const step = state.steps.find((s) => s.order === Number(arg) || s.id === arg) ?? fail(`no step ${arg}`);
460
+ input = { stepId: step.id };
461
+ }
462
+ else if (what === "thread" || what === "note")
463
+ input = { noteId: arg };
464
+ else
465
+ input = { symbolId: what ?? fail("go <symbol>"), lines: range(str("lines")) };
466
+ await call("navigate", input);
467
+ console.log("moved the user's view");
468
+ return;
469
+ }
470
+ case "outgoing": {
471
+ // downstream outgoing <thread> "comment for the PR author" | --exclude keeps it private
472
+ const [noteId, ...rest] = pos;
473
+ const include = !opts.exclude;
474
+ const body = rest.join(" ") || str("body") || "";
475
+ await call("note.update", { noteId, outgoing: { include, body } });
476
+ console.log(include ? `${noteId} goes to the PR${body ? " in your wording" : ""}` : `${noteId} stays private`);
477
+ return;
478
+ }
479
+ case "draft": {
480
+ const d = await call("review.draft", {});
481
+ const target = d.target ? `${d.target.repo ?? "?"} #${d.target.pr}` : "none: not a PR review, copy the markdown";
482
+ console.log(`Event: ${d.event} Target: ${target} Inline comments: ${d.comments.filter((c) => c.inline).length}\n`);
483
+ console.log(d.markdown);
484
+ return;
485
+ }
486
+ case "send": {
487
+ const raw = str("event")?.toLowerCase();
488
+ const event = raw === "approve" ? "APPROVE" : raw === "changes" || raw === "request_changes" ? "REQUEST_CHANGES" : raw === "comment" ? "COMMENT" : undefined;
489
+ const res = await call("review.publish", { ...(event ? { event } : {}), ...(opts.force ? { force: true } : {}) });
490
+ console.log(`Sent: ${res.url} (${res.comments} inline comments)`);
491
+ return;
492
+ }
493
+ case "verdict": {
494
+ const [value, ...rest] = pos;
495
+ await call("review.verdict", { value, body: rest.join(" ") });
496
+ console.log(`verdict: ${value}`);
497
+ return;
498
+ }
499
+ case "reanalyze": {
500
+ const res = await call("review.reanalyze");
501
+ console.log(`re-read: ${res.symbols} symbols, ${res.edges} edges`);
502
+ return;
503
+ }
504
+ case "do": {
505
+ const [name, json] = pos;
506
+ console.log(JSON.stringify(await call(name ?? fail("do <action> '<json>'"), json ? JSON.parse(json) : {}), null, 2));
507
+ return;
508
+ }
509
+ case "wait": {
510
+ const after = existsSync(cursorFile()) ? Number(readFileSync(cursorFile(), "utf8")) || 0 : 0;
511
+ const timeout = Number(str("timeout") ?? 600);
512
+ // Node's fetch gives up on a response whose headers take longer than 300s, so a
513
+ // long wait is a series of shorter ones from the same cursor.
514
+ const deadline = Date.now() + timeout * 1000;
515
+ let res;
516
+ do {
517
+ const chunk = Math.max(1, Math.min(WAIT_CHUNK_SECONDS, Math.ceil((deadline - Date.now()) / 1000)));
518
+ res = await get(`/api/wait?after=${after}&timeout=${chunk}`);
519
+ } while (res.timedOut && Date.now() < deadline);
520
+ writeFileSync(cursorFile(), String(res.cursor));
521
+ if (res.timedOut)
522
+ return console.log(`(no activity in ${timeout}s; the user may be reading. Run \`downstream wait\` again.)`);
523
+ const state = await get("/api/state");
524
+ for (const e of res.events)
525
+ console.log(describe(e, state) + "\n");
526
+ const screen = await call("screen");
527
+ if (screen.selection?.code && res.events.some((e) => e.type === "ask")) {
528
+ console.log(`User's selection, ${screen.selection.file}:${screen.selection.lines.start}-${screen.selection.lines.end}:\n${screen.selection.code}\n`);
529
+ }
530
+ if (!res.events.some((e) => e.type === "review.done"))
531
+ console.log("When you've responded, run `downstream wait` again.");
532
+ return;
533
+ }
534
+ case "help":
535
+ case "--help":
536
+ case "-h":
537
+ console.log(HELP);
538
+ return;
539
+ default:
540
+ fail(`unknown command "${cmd}".\n\n${HELP}`);
541
+ }
542
+ }
543
+ await main();
@@ -0,0 +1,3 @@
1
+ // An agent session Downstream runs for you, as the browser sees it.
2
+ /** `owner/name#42`: how sessions, reviews and inbox rows find each other. */
3
+ export const prKey = (repo, pr) => `${repo.toLowerCase()}#${pr}`;