@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,34 @@
1
+ /** The agent spoke last in an open thread that asks something of you. */
2
+ export function awaitsYou(n) {
3
+ if (n.status !== "open")
4
+ return false;
5
+ const last = n.replies.at(-1)?.author ?? n.author;
6
+ return last === "agent" && (n.kind === "question" || n.replies.length > 0);
7
+ }
8
+ /** An open thread the agent raised that nobody answered yet: yours to settle. */
9
+ export function awaitsDecision(n) {
10
+ return n.status === "open" && n.author === "agent" && n.replies.length === 0;
11
+ }
12
+ const first = (notes) => [...notes].sort((a, b) => a.number - b.number)[0];
13
+ /**
14
+ * Order matters: someone blocked on you comes before reading on, and reading
15
+ * comes before settling what's left open, which comes before sending. `visited` holds the steps you've opened.
16
+ */
17
+ export function nextMove(state, visited) {
18
+ const { review, steps, notes } = state;
19
+ if (review.published)
20
+ return { kind: "sent", url: review.published.url };
21
+ const blocked = first(notes.filter(awaitsYou));
22
+ if (blocked)
23
+ return { kind: "answer", noteId: blocked.id, number: blocked.number };
24
+ const nothingWritten = !review.summary && steps.length === 0 && notes.length === 0;
25
+ if (nothingWritten && review.status === "drafting")
26
+ return { kind: "reading" };
27
+ const unseen = steps.find((s) => !visited.has(s.id));
28
+ if (unseen)
29
+ return { kind: "walk", stepId: unseen.id, order: unseen.order, total: steps.length };
30
+ const open = first(notes.filter(awaitsDecision));
31
+ if (open)
32
+ return { kind: "decide", noteId: open.id, number: open.number };
33
+ return review.mode === "teach" ? { kind: "finish" } : { kind: "send" };
34
+ }
@@ -0,0 +1,45 @@
1
+ // What the preview shows before you decide to start: what the PR says it does,
2
+ // how big it is, and where its checks and reviews stand.
3
+ const GENERATED = [
4
+ /(^|\/)(package-lock\.json|npm-shrinkwrap\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb?|uv\.lock|poetry\.lock|Pipfile\.lock|pdm\.lock|Cargo\.lock|go\.sum|Gemfile\.lock|composer\.lock|flake\.lock|mix\.lock|pubspec\.lock|Podfile\.lock|packages\.lock\.json)$/,
5
+ /\.(min\.(js|css)|map|snap)$/,
6
+ /(^|\/)(__snapshots__|dist|build|vendor|node_modules|\.yarn)\//,
7
+ /(_pb2(_grpc)?\.pyi?|\.pb\.go|\.pb\.(cc|h)|\.g\.dart|\.generated\.[a-z]+)$/,
8
+ ];
9
+ const TEST = [
10
+ /(^|\/)(tests?|__tests__|spec|testdata|fixtures|__fixtures__|__mocks__)\//,
11
+ /\.(test|spec)\.[cm]?[jt]sx?$/,
12
+ /(^|\/)(test_[^/]+|[^/]+_test)\.py$/,
13
+ /(^|\/)conftest\.py$/,
14
+ /_test\.go$/,
15
+ /Tests?\.(cs|java|kt|swift)$/,
16
+ ];
17
+ const DOCS = [/\.(md|mdx|rst|txt|adoc)$/i, /(^|\/)docs?\//, /(^|\/)(LICENSE|CHANGELOG|AUTHORS)[^/]*$/i];
18
+ export function fileRole(path) {
19
+ if (GENERATED.some((r) => r.test(path)))
20
+ return "generated";
21
+ if (TEST.some((r) => r.test(path)))
22
+ return "test";
23
+ if (DOCS.some((r) => r.test(path)))
24
+ return "docs";
25
+ return "code";
26
+ }
27
+ export function reviewable(files) {
28
+ const others = { test: 0, docs: 0, generated: 0 };
29
+ const code = [];
30
+ let additions = 0;
31
+ let deletions = 0;
32
+ for (const f of files) {
33
+ const role = fileRole(f.path);
34
+ if (role !== "generated") {
35
+ additions += f.additions;
36
+ deletions += f.deletions;
37
+ }
38
+ if (role === "code")
39
+ code.push(f);
40
+ else
41
+ others[role]++;
42
+ }
43
+ code.sort((a, b) => b.additions + b.deletions - (a.additions + a.deletions));
44
+ return { code, others, additions, deletions };
45
+ }
@@ -0,0 +1,564 @@
1
+ // Every way to change a review is an action defined once here. The browser,
2
+ // the agent's CLI and plain HTTP all call the same code, so the user can do
3
+ // anything the agent can and the other way round.
4
+ import { z } from "zod";
5
+ import { Alternative, Author, CodeSymbol, EdgeKind, NavigateCommand, Navigation, Note, NoteKind, NoteStatus, Range, Selection, Severity, Step, Suggestion, SymbolKind, FindingCategory, Outgoing, SymbolStatus, } from "../domain/model.js";
6
+ import { newId, now } from "./store.js";
7
+ import { resolveTarget, Workspace } from "./workspace.js";
8
+ import { tryGit } from "./analyze/git.js";
9
+ import { isTestFile } from "./analyze/graph.js";
10
+ import { draft, HeadMoved, publish } from "./publish.js";
11
+ import { HEAD_MOVED } from "../domain/model.js";
12
+ export const actions = new Map();
13
+ function defineAction(def) {
14
+ actions.set(def.name, def);
15
+ return def;
16
+ }
17
+ export class ActionError extends Error {
18
+ status;
19
+ constructor(message, status = 400) {
20
+ super(message);
21
+ this.status = status;
22
+ }
23
+ }
24
+ function requireSymbol(ctx, id) {
25
+ const s = ctx.store.symbol(ctx.reviewId, id);
26
+ if (s)
27
+ return s;
28
+ // Be forgiving with ids: accept a bare name if it is unique.
29
+ const byName = ctx.store.symbols(ctx.reviewId).filter((x) => x.name === id || `${x.container}.${x.name}` === id || x.id.endsWith(`#${id}`));
30
+ if (byName.length === 1)
31
+ return byName[0];
32
+ if (byName.length > 1)
33
+ throw new ActionError(`"${id}" is ambiguous: ${byName.map((x) => x.id).join(", ")}`);
34
+ throw new ActionError(`No symbol "${id}". Run \`downstream show\` for ids.`, 404);
35
+ }
36
+ /** Threads are addressed by id or by their number ("3", "#3"), the way people talk about them. */
37
+ function requireNote(ctx, id) {
38
+ const byNumber = id.match(/^#?(\d+)$/);
39
+ const n = byNumber ? (ctx.store.notes(ctx.reviewId).find((x) => x.number === Number(byNumber[1])) ?? null) : ctx.store.note(ctx.reviewId, id);
40
+ if (!n)
41
+ throw new ActionError(`No thread "${id}".`, 404);
42
+ return n;
43
+ }
44
+ // --- review lifecycle ----------------------------------------------------------
45
+ export const openReview = defineAction({
46
+ name: "review.open",
47
+ description: "Analyze a change (or a set of paths, in teach mode) and open it as the current review.",
48
+ input: z.object({
49
+ mode: z.enum(["diff", "teach"]).default("diff"),
50
+ base: z.string().optional(),
51
+ head: z.string().optional(),
52
+ pr: z.number().int().optional(),
53
+ paths: z.array(z.string()).default([]),
54
+ title: z.string().optional(),
55
+ }),
56
+ run(ctx, input) {
57
+ const id = newId("rv");
58
+ let review;
59
+ if (input.mode === "teach") {
60
+ review = {
61
+ id,
62
+ title: input.title ?? `Tour of ${input.paths.join(", ") || "the repository"}`,
63
+ mode: "teach",
64
+ base: null,
65
+ baseSha: null,
66
+ head: "WORKTREE",
67
+ paths: input.paths,
68
+ pr: null,
69
+ repo: ctx.nameWithOwner,
70
+ account: null,
71
+ summary: "",
72
+ status: "drafting",
73
+ createdAt: now(),
74
+ verdicts: [],
75
+ outgoingBody: "",
76
+ published: null,
77
+ };
78
+ }
79
+ else {
80
+ const account = input.pr ? ctx.accountFor(input.pr) : null;
81
+ const t = resolveTarget(ctx.root, { ...input, repo: ctx.nameWithOwner, account });
82
+ review = {
83
+ id,
84
+ title: input.title ?? t.title ?? defaultTitle(ctx.root, t.head, t.base, input.pr),
85
+ mode: "diff",
86
+ base: t.base,
87
+ baseSha: t.baseSha,
88
+ head: t.head,
89
+ paths: [],
90
+ pr: input.pr ?? null,
91
+ repo: ctx.nameWithOwner,
92
+ account,
93
+ summary: "",
94
+ status: "drafting",
95
+ createdAt: now(),
96
+ verdicts: [],
97
+ outgoingBody: "",
98
+ published: null,
99
+ };
100
+ }
101
+ ctx.store.saveReview(review, ctx.root);
102
+ const { files, graph } = ctx.workspace(id).analyze();
103
+ ctx.store.replaceAnalysis(id, files, graph.symbols, graph.edges);
104
+ ctx.store.setCurrent(id);
105
+ ctx.store.emit(id, "review.opened", ctx.actor, { id });
106
+ return { review, files: files.length, symbols: graph.symbols.length, edges: graph.edges.length };
107
+ },
108
+ });
109
+ defineAction({
110
+ name: "review.reanalyze",
111
+ description: "Re-read the code after it changed (e.g. a finding was fixed). Keeps summaries, threads and the walkthrough.",
112
+ input: z.object({}),
113
+ run(ctx) {
114
+ ctx.dropWorkspace(ctx.reviewId);
115
+ const { files, graph } = ctx.workspace(ctx.reviewId).analyze();
116
+ ctx.store.replaceAnalysis(ctx.reviewId, files, graph.symbols, graph.edges);
117
+ ctx.store.emit(ctx.reviewId, "review.reanalyzed", ctx.actor, { symbols: graph.symbols.length });
118
+ return { symbols: graph.symbols.length, edges: graph.edges.length };
119
+ },
120
+ });
121
+ defineAction({
122
+ name: "review.update",
123
+ description: "Set the title, the summary (markdown), or the status (drafting → ready when the walkthrough is written).",
124
+ input: z.object({
125
+ title: z.string().optional(),
126
+ summary: z.string().optional(),
127
+ status: z.enum(["drafting", "ready", "done"]).optional(),
128
+ /** The review body that goes to the pull request (markdown). */
129
+ outgoingBody: z.string().optional(),
130
+ }),
131
+ run(ctx, input) {
132
+ const review = ctx.store.review(ctx.reviewId);
133
+ const next = { ...review, ...Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined)) };
134
+ ctx.store.saveReview(next);
135
+ ctx.store.emit(ctx.reviewId, "review.updated", ctx.actor, input);
136
+ return next;
137
+ },
138
+ });
139
+ defineAction({
140
+ name: "review.verdict",
141
+ description: "Record a verdict: approve, changes (request changes), or comment.",
142
+ input: z.object({ value: z.enum(["approve", "changes", "comment"]), body: z.string().default("") }),
143
+ run(ctx, input) {
144
+ const review = ctx.store.review(ctx.reviewId);
145
+ const verdict = { by: ctx.actor, value: input.value, body: input.body, at: now() };
146
+ const verdicts = [...review.verdicts.filter((v) => v.by !== ctx.actor), verdict];
147
+ ctx.store.saveReview({ ...review, verdicts });
148
+ ctx.store.emit(ctx.reviewId, "review.verdict", ctx.actor, verdict);
149
+ return verdict;
150
+ },
151
+ });
152
+ defineAction({
153
+ name: "review.done",
154
+ description: "End the session. A waiting agent is released with a `review.done` event.",
155
+ input: z.object({}),
156
+ run(ctx) {
157
+ const review = ctx.store.review(ctx.reviewId);
158
+ ctx.store.saveReview({ ...review, status: "done" });
159
+ ctx.store.emit(ctx.reviewId, "review.done", ctx.actor, {});
160
+ return { ok: true };
161
+ },
162
+ });
163
+ // --- the map --------------------------------------------------------------------
164
+ const SymbolPatch = z.object({ id: z.string(), summary: z.string().nullable().optional(), entry: z.boolean().optional() });
165
+ defineAction({
166
+ name: "symbol.annotate",
167
+ description: "Give a symbol a one-line purpose, or mark/unmark it as an entry point.",
168
+ input: SymbolPatch,
169
+ run(ctx, input) {
170
+ const s = requireSymbol(ctx, input.id);
171
+ const next = { ...s, summary: input.summary !== undefined ? input.summary : s.summary, entry: input.entry ?? s.entry };
172
+ ctx.store.putSymbol(ctx.reviewId, next);
173
+ ctx.store.emit(ctx.reviewId, "symbol.annotated", ctx.actor, { id: s.id, entry: input.entry ?? null });
174
+ return next;
175
+ },
176
+ });
177
+ const NewSymbol = z.object({
178
+ file: z.string(),
179
+ name: z.string(),
180
+ lines: Range.optional(),
181
+ /** Alternatively point at one line and let the analyzer find the enclosing declaration (TS/JS). */
182
+ line: z.number().int().optional(),
183
+ kind: SymbolKind.default("function"),
184
+ status: SymbolStatus.default("context"),
185
+ summary: z.string().nullable().default(null),
186
+ entry: z.boolean().default(false),
187
+ signature: z.string().nullable().default(null),
188
+ });
189
+ defineAction({
190
+ name: "symbol.add",
191
+ description: "Put a symbol on the map by hand (other languages, or code the analyzer missed).",
192
+ input: NewSymbol,
193
+ run(ctx, input) {
194
+ let s = null;
195
+ if (!input.lines && input.line)
196
+ s = ctx.workspace(ctx.reviewId).symbolAt(input.file, input.line);
197
+ if (!s) {
198
+ if (!input.lines)
199
+ throw new ActionError("Give `lines: {start, end}` (or `line` inside a TS/JS declaration).");
200
+ s = {
201
+ id: `${input.file}#${input.name}`,
202
+ name: input.name,
203
+ container: null,
204
+ kind: input.kind,
205
+ file: input.file,
206
+ range: input.lines,
207
+ oldFile: null,
208
+ oldRange: null,
209
+ status: input.status,
210
+ signature: input.signature,
211
+ exported: false,
212
+ entry: input.entry,
213
+ summary: input.summary,
214
+ changedLines: 0,
215
+ test: isTestFile(input.file),
216
+ };
217
+ }
218
+ else {
219
+ s = { ...s, summary: input.summary, entry: input.entry, status: input.status };
220
+ }
221
+ ctx.store.putSymbol(ctx.reviewId, s, true);
222
+ ctx.store.emit(ctx.reviewId, "symbol.added", ctx.actor, { id: s.id });
223
+ return s;
224
+ },
225
+ });
226
+ defineAction({
227
+ name: "symbol.remove",
228
+ description: "Take a symbol (and its edges) off the map.",
229
+ input: z.object({ id: z.string() }),
230
+ run(ctx, input) {
231
+ const s = requireSymbol(ctx, input.id);
232
+ ctx.store.deleteSymbol(ctx.reviewId, s.id);
233
+ ctx.store.emit(ctx.reviewId, "symbol.removed", ctx.actor, { id: s.id });
234
+ return { ok: true };
235
+ },
236
+ });
237
+ const NewEdge = z.object({ from: z.string(), to: z.string(), kind: EdgeKind.default("calls"), label: z.string().nullable().default(null), change: z.enum(["added", "removed", "same"]).default("same") });
238
+ defineAction({
239
+ name: "edge.add",
240
+ description: "Draw a relationship the analyzer can't see (events, queues, HTTP between services, dynamic dispatch). Label it.",
241
+ input: NewEdge,
242
+ run(ctx, input) {
243
+ const from = requireSymbol(ctx, input.from);
244
+ const to = requireSymbol(ctx, input.to);
245
+ const edge = { id: `${from.id}->${to.id}:${input.kind}`, from: from.id, to: to.id, kind: input.kind, change: input.change, line: null, label: input.label };
246
+ ctx.store.putEdge(ctx.reviewId, edge, true);
247
+ ctx.store.emit(ctx.reviewId, "edge.added", ctx.actor, { id: edge.id });
248
+ return edge;
249
+ },
250
+ });
251
+ defineAction({
252
+ name: "edge.remove",
253
+ description: "Remove a relationship that is noise.",
254
+ input: z.object({ id: z.string() }),
255
+ run(ctx, input) {
256
+ ctx.store.deleteEdge(ctx.reviewId, input.id);
257
+ ctx.store.emit(ctx.reviewId, "edge.removed", ctx.actor, input);
258
+ return { ok: true };
259
+ },
260
+ });
261
+ // --- walkthrough ---------------------------------------------------------------------
262
+ const StepInput = z.object({ title: z.string(), body: z.string().default(""), symbolId: z.string().nullable().default(null), lines: Range.nullable().default(null) });
263
+ function toSteps(ctx, steps) {
264
+ return steps.map((s, i) => ({
265
+ id: newId("st"),
266
+ order: i + 1,
267
+ title: s.title,
268
+ body: s.body,
269
+ symbolId: s.symbolId ? requireSymbol(ctx, s.symbolId).id : null,
270
+ lines: s.lines,
271
+ }));
272
+ }
273
+ defineAction({
274
+ name: "steps.set",
275
+ description: "Replace the walkthrough: an ordered path through the change, entry point first, each step anchored to a symbol.",
276
+ input: z.object({ steps: z.array(StepInput) }),
277
+ run(ctx, input) {
278
+ const steps = toSteps(ctx, input.steps);
279
+ ctx.store.replaceSteps(ctx.reviewId, steps);
280
+ ctx.store.emit(ctx.reviewId, "steps.set", ctx.actor, { count: steps.length });
281
+ return steps;
282
+ },
283
+ });
284
+ // --- threads ------------------------------------------------------------------------------
285
+ const NoteInput = z.object({
286
+ kind: NoteKind,
287
+ title: z.string(),
288
+ body: z.string().default(""),
289
+ severity: Severity.nullable().default(null),
290
+ category: FindingCategory.nullable().default(null),
291
+ fix: z.string().default(""),
292
+ impact: z.string().default(""),
293
+ symbolId: z.string().nullable().default(null),
294
+ file: z.string().nullable().default(null),
295
+ lines: Range.nullable().default(null),
296
+ side: z.enum(["new", "old"]).default("new"),
297
+ alternatives: z.array(Alternative).default([]),
298
+ suggestion: Suggestion.nullable().default(null),
299
+ outgoing: Outgoing.nullable().default(null),
300
+ });
301
+ function makeNote(ctx, input) {
302
+ const symbol = input.symbolId ? requireSymbol(ctx, input.symbolId) : null;
303
+ if (input.kind === "finding" && !input.severity)
304
+ input.severity = "concern";
305
+ return {
306
+ id: newId("n"),
307
+ number: ctx.store.nextNoteNumber(ctx.reviewId),
308
+ kind: input.kind,
309
+ severity: input.kind === "finding" ? input.severity : null,
310
+ category: input.kind === "finding" ? input.category : null,
311
+ title: input.title,
312
+ body: input.body,
313
+ fix: input.fix,
314
+ impact: input.impact,
315
+ outgoing: input.outgoing,
316
+ symbolId: symbol?.id ?? null,
317
+ file: input.file ?? symbol?.file ?? null,
318
+ lines: input.lines,
319
+ side: input.side,
320
+ alternatives: input.alternatives,
321
+ suggestion: input.suggestion,
322
+ status: "open",
323
+ author: ctx.actor,
324
+ createdAt: now(),
325
+ replies: [],
326
+ };
327
+ }
328
+ defineAction({
329
+ name: "note.add",
330
+ description: "Say something about the code. kind: why (rationale), decision (with alternatives that lost), finding (problem; severity blocker|concern|nit), question, comment. Anchor with symbolId and optional lines.",
331
+ input: NoteInput,
332
+ run(ctx, input) {
333
+ const note = makeNote(ctx, input);
334
+ ctx.store.putNote(ctx.reviewId, note);
335
+ ctx.store.emit(ctx.reviewId, "note.added", ctx.actor, { id: note.id, kind: note.kind, title: note.title, symbolId: note.symbolId });
336
+ return note;
337
+ },
338
+ });
339
+ defineAction({
340
+ name: "note.reply",
341
+ description: "Reply in a thread.",
342
+ input: z.object({ noteId: z.string(), body: z.string().min(1) }),
343
+ run(ctx, input) {
344
+ const note = requireNote(ctx, input.noteId);
345
+ const reply = { id: newId("r"), noteId: note.id, author: ctx.actor, body: input.body, createdAt: now() };
346
+ ctx.store.addReply(ctx.reviewId, reply);
347
+ ctx.store.emit(ctx.reviewId, "note.replied", ctx.actor, { noteId: note.id, title: note.title, body: input.body, replyId: reply.id });
348
+ return reply;
349
+ },
350
+ });
351
+ defineAction({
352
+ name: "note.status",
353
+ description: "Resolve, dismiss, or reopen a thread.",
354
+ input: z.object({ noteId: z.string(), status: NoteStatus, reason: z.string().optional() }),
355
+ run(ctx, input) {
356
+ const note = requireNote(ctx, input.noteId);
357
+ ctx.store.putNote(ctx.reviewId, { ...note, status: input.status });
358
+ if (input.reason) {
359
+ ctx.store.addReply(ctx.reviewId, { id: newId("r"), noteId: note.id, author: ctx.actor, body: input.reason, createdAt: now() });
360
+ }
361
+ ctx.store.emit(ctx.reviewId, "note.status", ctx.actor, { noteId: note.id, title: note.title, status: input.status, reason: input.reason ?? null });
362
+ return { ok: true };
363
+ },
364
+ });
365
+ defineAction({
366
+ name: "ask",
367
+ description: "Ask the other side a question about a symbol or a line range. Opens a question thread.",
368
+ input: z.object({ body: z.string().min(1), symbolId: z.string().nullable().default(null), file: z.string().nullable().default(null), lines: Range.nullable().default(null), side: z.enum(["new", "old"]).default("new") }),
369
+ run(ctx, input) {
370
+ const title = input.body.split("\n")[0].slice(0, 120);
371
+ const note = makeNote(ctx, NoteInput.parse({ kind: "question", title, body: input.body.length > title.length ? input.body : "", symbolId: input.symbolId, file: input.file, lines: input.lines, side: input.side }));
372
+ ctx.store.putNote(ctx.reviewId, note);
373
+ ctx.store.emit(ctx.reviewId, "ask", ctx.actor, { id: note.id, body: input.body, symbolId: note.symbolId, file: note.file, lines: note.lines });
374
+ return note;
375
+ },
376
+ });
377
+ // --- batch authoring -------------------------------------------------------------------------
378
+ defineAction({
379
+ name: "apply",
380
+ description: "Author the whole walkthrough in one call: summary, symbol purposes and entry flags, extra symbols and edges, steps, notes.",
381
+ input: z.object({
382
+ title: z.string().optional(),
383
+ summary: z.string().optional(),
384
+ status: z.enum(["drafting", "ready"]).optional(),
385
+ symbols: z.array(SymbolPatch).default([]),
386
+ addSymbols: z.array(NewSymbol).default([]),
387
+ edges: z.array(NewEdge).default([]),
388
+ removeEdges: z.array(z.string()).default([]),
389
+ steps: z.array(StepInput).optional(),
390
+ notes: z.array(NoteInput).default([]),
391
+ /** Edits to existing threads by id or number, e.g. drafting their outgoing comments. */
392
+ updates: z.array(z.object({ noteId: z.string() }).passthrough()).default([]),
393
+ /** The review body for the pull request. */
394
+ outgoingBody: z.string().optional(),
395
+ }),
396
+ async run(ctx, input) {
397
+ // Validate every part before writing any, so a bad update can't leave the notes
398
+ // above it applied and a retry duplicating them.
399
+ const planned = [];
400
+ const plan = (name, payload) => {
401
+ const parsed = actions.get(name).input.safeParse(payload);
402
+ if (!parsed.success)
403
+ throw new ActionError(`Nothing applied. ${name} is invalid:\n${z.prettifyError(parsed.error)}`);
404
+ planned.push({ name, payload: parsed.data });
405
+ };
406
+ const results = [];
407
+ if (input.title || input.summary || input.status || input.outgoingBody !== undefined) {
408
+ plan("review.update", { title: input.title, summary: input.summary, status: input.status, outgoingBody: input.outgoingBody });
409
+ results.push("review updated");
410
+ }
411
+ for (const s of input.addSymbols)
412
+ plan("symbol.add", s);
413
+ if (input.addSymbols.length)
414
+ results.push(`${input.addSymbols.length} symbols added`);
415
+ for (const s of input.symbols)
416
+ plan("symbol.annotate", s);
417
+ if (input.symbols.length)
418
+ results.push(`${input.symbols.length} symbols annotated`);
419
+ for (const e of input.edges)
420
+ plan("edge.add", e);
421
+ for (const id of input.removeEdges)
422
+ plan("edge.remove", { id });
423
+ if (input.steps) {
424
+ plan("steps.set", { steps: input.steps });
425
+ results.push(`${input.steps.length} steps`);
426
+ }
427
+ for (const n of input.notes)
428
+ plan("note.add", n);
429
+ if (input.notes.length)
430
+ results.push(`${input.notes.length} notes`);
431
+ for (const u of input.updates) {
432
+ requireNote(ctx, u.noteId);
433
+ plan("note.update", u);
434
+ }
435
+ if (input.updates.length)
436
+ results.push(`${input.updates.length} threads updated`);
437
+ for (const { name, payload } of planned)
438
+ await actions.get(name).run(ctx, payload);
439
+ return { ok: true, applied: results };
440
+ },
441
+ });
442
+ // --- shared application state ----------------------------------------------------------------
443
+ defineAction({
444
+ name: "state.set",
445
+ description: "The UI reports what the user is looking at (navigation), what they selected (selection), and which steps they've opened (visited).",
446
+ input: z.discriminatedUnion("key", [
447
+ z.object({ key: z.literal("navigation"), value: Navigation }),
448
+ z.object({ key: z.literal("selection"), value: Selection.nullable() }),
449
+ /** Walkthrough steps the user has opened, so the next move (here and on the home page) knows what's left. */
450
+ z.object({ key: z.literal("visited"), value: z.array(z.string()) }),
451
+ ]),
452
+ run(ctx, input) {
453
+ ctx.store.setAppState(ctx.reviewId, input.key, input.value);
454
+ return { ok: true };
455
+ },
456
+ });
457
+ defineAction({
458
+ name: "navigate",
459
+ description: "Move the user's view: open a symbol (optionally highlighting lines), a step, a thread, or switch view.",
460
+ input: NavigateCommand,
461
+ run(ctx, input) {
462
+ if (input.symbolId)
463
+ input.symbolId = requireSymbol(ctx, input.symbolId).id;
464
+ if (input.noteId)
465
+ input.noteId = requireNote(ctx, input.noteId).id;
466
+ const cmd = { ...input, nonce: newId("nav") };
467
+ ctx.store.emit(ctx.reviewId, "navigate", ctx.actor, cmd);
468
+ return cmd;
469
+ },
470
+ });
471
+ defineAction({
472
+ name: "screen",
473
+ readOnly: true,
474
+ description: "What the user is looking at right now: their view, the trail of open symbols, and their selected code.",
475
+ input: z.object({}),
476
+ run(ctx) {
477
+ const nav = ctx.store.getAppState(ctx.reviewId, "navigation");
478
+ const sel = ctx.store.getAppState(ctx.reviewId, "selection");
479
+ const ws = ctx.workspace(ctx.reviewId);
480
+ let selectedCode = null;
481
+ if (sel) {
482
+ const text = ws.read(sel.file, sel.side);
483
+ selectedCode = text?.split("\n").slice(sel.lines.start - 1, sel.lines.end).join("\n") ?? null;
484
+ }
485
+ const symbols = new Map(ctx.store.symbols(ctx.reviewId).map((s) => [s.id, s]));
486
+ return {
487
+ view: nav?.view ?? "overview",
488
+ trail: (nav?.trail ?? []).map((id) => {
489
+ const s = symbols.get(id);
490
+ return s ? { id, name: s.name, file: s.file, lines: s.range } : { id };
491
+ }),
492
+ step: nav?.stepId ? (ctx.store.steps(ctx.reviewId).find((s) => s.id === nav.stepId) ?? null) : null,
493
+ thread: nav?.noteId ? ctx.store.note(ctx.reviewId, nav.noteId) : null,
494
+ selection: sel ? { ...sel, code: selectedCode } : null,
495
+ presence: ctx.store.presence(),
496
+ };
497
+ },
498
+ });
499
+ function defaultTitle(root, head, base, pr) {
500
+ if (pr)
501
+ return `PR #${pr}`;
502
+ if (head !== "WORKTREE")
503
+ return tryGit(root, ["log", "-1", "--format=%s", head])?.trim() || `${head} against ${base}`;
504
+ const branch = tryGit(root, ["branch", "--show-current"])?.trim();
505
+ return branch && branch !== "main" && branch !== "master" ? `${branch}, uncommitted` : "Uncommitted work";
506
+ }
507
+ // --- what goes to the pull request ---------------------------------------------------------------
508
+ defineAction({
509
+ name: "note.update",
510
+ description: "Edit a thread: its wording, severity, fix or impact, or its outgoing comment (`outgoing: {include, body}` decides whether and how it goes to the PR author).",
511
+ input: z.object({
512
+ noteId: z.string(),
513
+ title: z.string().optional(),
514
+ body: z.string().optional(),
515
+ fix: z.string().optional(),
516
+ impact: z.string().optional(),
517
+ severity: Severity.nullable().optional(),
518
+ category: FindingCategory.nullable().optional(),
519
+ outgoing: Outgoing.nullable().optional(),
520
+ }),
521
+ run(ctx, input) {
522
+ const note = requireNote(ctx, input.noteId);
523
+ const { noteId: _id, ...patch } = input;
524
+ const next = { ...note, ...Object.fromEntries(Object.entries(patch).filter(([, v]) => v !== undefined)) };
525
+ ctx.store.putNote(ctx.reviewId, next);
526
+ ctx.store.emit(ctx.reviewId, "note.updated", ctx.actor, { noteId: note.id, number: note.number, title: next.title, fields: Object.keys(patch).filter((k) => patch[k] !== undefined) });
527
+ return next;
528
+ },
529
+ });
530
+ defineAction({
531
+ name: "review.draft",
532
+ readOnly: true,
533
+ description: "The review as it would be sent: event, body, and each inline comment (or the ones that fall outside the diff and go into the body).",
534
+ input: z.object({ event: z.enum(["APPROVE", "REQUEST_CHANGES", "COMMENT"]).optional(), body: z.string().optional() }),
535
+ run(ctx, input) {
536
+ return draft(ctx.root, ctx.store.state(ctx.reviewId), input);
537
+ },
538
+ });
539
+ defineAction({
540
+ name: "review.publish",
541
+ description: "Send the review to the pull request on GitHub. Outward-facing and permanent: the agent only calls this when the user has explicitly said to send it. Refuses with 409 when the PR changed since the review was opened; `force` sends anyway, only when the user says so.",
542
+ input: z.object({ event: z.enum(["APPROVE", "REQUEST_CHANGES", "COMMENT"]).optional(), body: z.string().optional(), force: z.boolean().optional() }),
543
+ run(ctx, { force, ...input }) {
544
+ const state = ctx.store.state(ctx.reviewId);
545
+ const d = draft(ctx.root, state, input);
546
+ let sent;
547
+ try {
548
+ sent = publish(state, d, { force });
549
+ }
550
+ catch (e) {
551
+ if (e instanceof HeadMoved)
552
+ throw new ActionError(e.message, HEAD_MOVED);
553
+ const err = e;
554
+ const detail = err.stderr?.toString().trim() || err.message;
555
+ if (/own pull request/i.test(detail))
556
+ throw new ActionError("GitHub doesn't allow approving or requesting changes on your own pull request. Send it as Comment.", 422);
557
+ throw new ActionError(`GitHub refused the review: ${detail.split("\n").slice(0, 3).join(" ")}`, 502);
558
+ }
559
+ const published = { url: sent.url, at: now(), event: d.event, comments: sent.comments };
560
+ ctx.store.saveReview({ ...state.review, published, outgoingBody: d.body });
561
+ ctx.store.emit(ctx.reviewId, "review.published", ctx.actor, published);
562
+ return published;
563
+ },
564
+ });