miko-code 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 (379) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +179 -0
  3. package/bin/miko +10 -0
  4. package/dist/client/antigravity.webp +0 -0
  5. package/dist/client/assets/abap-BdImnpbu.js +1 -0
  6. package/dist/client/assets/actionscript-3-CoDkCxhg.js +1 -0
  7. package/dist/client/assets/ada-bCR0ucgS.js +1 -0
  8. package/dist/client/assets/andromeeda-C4gqWexZ.js +1 -0
  9. package/dist/client/assets/angular-html-CU67Zn6k.js +1 -0
  10. package/dist/client/assets/angular-ts-BwZT4LLn.js +1 -0
  11. package/dist/client/assets/apache-Pmp26Uib.js +1 -0
  12. package/dist/client/assets/apex-D8_7TLub.js +1 -0
  13. package/dist/client/assets/apl-dKokRX4l.js +1 -0
  14. package/dist/client/assets/applescript-Co6uUVPk.js +1 -0
  15. package/dist/client/assets/ara-BRHolxvo.js +1 -0
  16. package/dist/client/assets/asciidoc-Ve4PFQV2.js +1 -0
  17. package/dist/client/assets/asm-D_Q5rh1f.js +1 -0
  18. package/dist/client/assets/astro-CbQHKStN.js +1 -0
  19. package/dist/client/assets/aurora-x-D-2ljcwZ.js +1 -0
  20. package/dist/client/assets/awk-DMzUqQB5.js +1 -0
  21. package/dist/client/assets/ayu-dark-DYE7WIF3.js +1 -0
  22. package/dist/client/assets/ayu-light-BA47KaF1.js +1 -0
  23. package/dist/client/assets/ayu-mirage-32ctXXKs.js +1 -0
  24. package/dist/client/assets/ballerina-BFfxhgS-.js +1 -0
  25. package/dist/client/assets/bat-BkioyH1T.js +1 -0
  26. package/dist/client/assets/beancount-k_qm7-4y.js +1 -0
  27. package/dist/client/assets/berry-uYugtg8r.js +1 -0
  28. package/dist/client/assets/bibtex-CHM0blh-.js +1 -0
  29. package/dist/client/assets/bicep-Bmn6On1c.js +1 -0
  30. package/dist/client/assets/bird2-DPOp833l.js +1 -0
  31. package/dist/client/assets/blade-D4QpJJKB.js +1 -0
  32. package/dist/client/assets/bsl-BO_Y6i37.js +1 -0
  33. package/dist/client/assets/c-BIGW1oBm.js +1 -0
  34. package/dist/client/assets/c3-eo99z4R2.js +1 -0
  35. package/dist/client/assets/cadence-Bv_4Rxtq.js +1 -0
  36. package/dist/client/assets/cairo-KRGpt6FW.js +1 -0
  37. package/dist/client/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  38. package/dist/client/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  39. package/dist/client/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  40. package/dist/client/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  41. package/dist/client/assets/clarity-D53aC0YG.js +1 -0
  42. package/dist/client/assets/clojure-P80f7IUj.js +1 -0
  43. package/dist/client/assets/cmake-D1j8_8rp.js +1 -0
  44. package/dist/client/assets/cobol-nwyudZeR.js +1 -0
  45. package/dist/client/assets/codeowners-Bp6g37R7.js +1 -0
  46. package/dist/client/assets/codeql-DsOJ9woJ.js +1 -0
  47. package/dist/client/assets/coffee-Ch7k5sss.js +1 -0
  48. package/dist/client/assets/common-lisp-Cg-RD9OK.js +1 -0
  49. package/dist/client/assets/coq-DkFqJrB1.js +1 -0
  50. package/dist/client/assets/cpp-CofmeUqb.js +1 -0
  51. package/dist/client/assets/crystal-tKQVLTB8.js +1 -0
  52. package/dist/client/assets/csharp-COcwbKMJ.js +1 -0
  53. package/dist/client/assets/css-DPfMkruS.js +1 -0
  54. package/dist/client/assets/csv-fuZLfV_i.js +1 -0
  55. package/dist/client/assets/cue-D82EKSYY.js +1 -0
  56. package/dist/client/assets/cypher-COkxafJQ.js +1 -0
  57. package/dist/client/assets/d-85-TOEBH.js +1 -0
  58. package/dist/client/assets/dark-plus-C3mMm8J8.js +1 -0
  59. package/dist/client/assets/dart-CF10PKvl.js +1 -0
  60. package/dist/client/assets/dax-CEL-wOlO.js +1 -0
  61. package/dist/client/assets/desktop-BmXAJ9_W.js +1 -0
  62. package/dist/client/assets/diff-D97Zzqfu.js +1 -0
  63. package/dist/client/assets/docker-BcOcwvcX.js +1 -0
  64. package/dist/client/assets/dotenv-Da5cRb03.js +1 -0
  65. package/dist/client/assets/dracula-BzJJZx-M.js +1 -0
  66. package/dist/client/assets/dracula-soft-BXkSAIEj.js +1 -0
  67. package/dist/client/assets/dream-maker-BtqSS_iP.js +1 -0
  68. package/dist/client/assets/edge-BkV0erSs.js +1 -0
  69. package/dist/client/assets/elixir-CDX3lj18.js +1 -0
  70. package/dist/client/assets/elm-DbKCFpqz.js +1 -0
  71. package/dist/client/assets/emacs-lisp-C9XAeP06.js +1 -0
  72. package/dist/client/assets/erb-B12qg9BL.js +1 -0
  73. package/dist/client/assets/erlang-DsQrWhSR.js +1 -0
  74. package/dist/client/assets/everforest-dark-BgDCqdQA.js +1 -0
  75. package/dist/client/assets/everforest-light-C8M2exoo.js +1 -0
  76. package/dist/client/assets/fennel-BYunw83y.js +1 -0
  77. package/dist/client/assets/fish-BvzEVeQv.js +1 -0
  78. package/dist/client/assets/fluent-C4IJs8-o.js +1 -0
  79. package/dist/client/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  80. package/dist/client/assets/fortran-free-form-BxgE0vQu.js +1 -0
  81. package/dist/client/assets/fsharp-CXgrBDvD.js +1 -0
  82. package/dist/client/assets/gdresource-BOOCDP_w.js +1 -0
  83. package/dist/client/assets/gdscript-C5YyOfLZ.js +1 -0
  84. package/dist/client/assets/gdshader-DkwncUOv.js +1 -0
  85. package/dist/client/assets/genie-D0YGMca9.js +1 -0
  86. package/dist/client/assets/gherkin-DyxjwDmM.js +1 -0
  87. package/dist/client/assets/git-commit-F4YmCXRG.js +1 -0
  88. package/dist/client/assets/git-rebase-r7XF79zn.js +1 -0
  89. package/dist/client/assets/github-dark-DHJKELXO.js +1 -0
  90. package/dist/client/assets/github-dark-default-Cuk6v7N8.js +1 -0
  91. package/dist/client/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  92. package/dist/client/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  93. package/dist/client/assets/github-light-DAi9KRSo.js +1 -0
  94. package/dist/client/assets/github-light-default-D7oLnXFd.js +1 -0
  95. package/dist/client/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  96. package/dist/client/assets/gleam-BspZqrRM.js +1 -0
  97. package/dist/client/assets/glimmer-js-Rg0-pVw9.js +1 -0
  98. package/dist/client/assets/glimmer-ts-U6CK756n.js +1 -0
  99. package/dist/client/assets/glsl-DplSGwfg.js +1 -0
  100. package/dist/client/assets/gn-n2N0HUVH.js +1 -0
  101. package/dist/client/assets/gnuplot-DdkO51Og.js +1 -0
  102. package/dist/client/assets/go-CxLEBnE3.js +1 -0
  103. package/dist/client/assets/graphql-ChdNCCLP.js +1 -0
  104. package/dist/client/assets/groovy-gcz8RCvz.js +1 -0
  105. package/dist/client/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  106. package/dist/client/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  107. package/dist/client/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  108. package/dist/client/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  109. package/dist/client/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  110. package/dist/client/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  111. package/dist/client/assets/hack-CaT9iCJl.js +1 -0
  112. package/dist/client/assets/haml-B8DHNrY2.js +1 -0
  113. package/dist/client/assets/handlebars-BL8al0AC.js +1 -0
  114. package/dist/client/assets/haskell-Df6bDoY_.js +1 -0
  115. package/dist/client/assets/haxe-CzTSHFRz.js +1 -0
  116. package/dist/client/assets/hcl-BWvSN4gD.js +1 -0
  117. package/dist/client/assets/hjson-D5-asLiD.js +1 -0
  118. package/dist/client/assets/hlsl-D3lLCCz7.js +1 -0
  119. package/dist/client/assets/horizon-BUw7H-hv.js +1 -0
  120. package/dist/client/assets/horizon-bright-Cn-bp-IR.js +1 -0
  121. package/dist/client/assets/houston-DnULxvSX.js +1 -0
  122. package/dist/client/assets/html-GMplVEZG.js +1 -0
  123. package/dist/client/assets/html-derivative-BFtXZ54Q.js +1 -0
  124. package/dist/client/assets/http-jrhK8wxY.js +1 -0
  125. package/dist/client/assets/hurl-irOxFIW8.js +1 -0
  126. package/dist/client/assets/hxml-Bvhsp5Yf.js +1 -0
  127. package/dist/client/assets/hy-DFXneXwc.js +1 -0
  128. package/dist/client/assets/imba-DGztddWO.js +1 -0
  129. package/dist/client/assets/index-C07zYq_-.css +32 -0
  130. package/dist/client/assets/index-Ce3hNHfL.js +2351 -0
  131. package/dist/client/assets/ini-BEwlwnbL.js +1 -0
  132. package/dist/client/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  133. package/dist/client/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  134. package/dist/client/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  135. package/dist/client/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  136. package/dist/client/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  137. package/dist/client/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  138. package/dist/client/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  139. package/dist/client/assets/java-CylS5w8V.js +1 -0
  140. package/dist/client/assets/javascript-wDzz0qaB.js +1 -0
  141. package/dist/client/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  142. package/dist/client/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  143. package/dist/client/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  144. package/dist/client/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  145. package/dist/client/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  146. package/dist/client/assets/jinja-4LBKfQ-Z.js +1 -0
  147. package/dist/client/assets/jison-wvAkD_A8.js +1 -0
  148. package/dist/client/assets/json-Cp-IABpG.js +1 -0
  149. package/dist/client/assets/json5-C9tS-k6U.js +1 -0
  150. package/dist/client/assets/jsonc-Des-eS-w.js +1 -0
  151. package/dist/client/assets/jsonl-DcaNXYhu.js +1 -0
  152. package/dist/client/assets/jsonnet-DFQXde-d.js +1 -0
  153. package/dist/client/assets/jssm-C2t-YnRu.js +1 -0
  154. package/dist/client/assets/jsx-g9-lgVsj.js +1 -0
  155. package/dist/client/assets/julia-CxzCAyBv.js +1 -0
  156. package/dist/client/assets/just-Cw27pwNe.js +1 -0
  157. package/dist/client/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  158. package/dist/client/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  159. package/dist/client/assets/kanagawa-wave-DWedfzmr.js +1 -0
  160. package/dist/client/assets/kdl-DV7GczEv.js +1 -0
  161. package/dist/client/assets/kotlin-BdnUsdx6.js +1 -0
  162. package/dist/client/assets/kusto-DZf3V79B.js +1 -0
  163. package/dist/client/assets/laserwave-DUszq2jm.js +1 -0
  164. package/dist/client/assets/latex-CWtU0Tv5.js +1 -0
  165. package/dist/client/assets/lean-BZvkOJ9d.js +1 -0
  166. package/dist/client/assets/less-B1dDrJ26.js +1 -0
  167. package/dist/client/assets/light-plus-B7mTdjB0.js +1 -0
  168. package/dist/client/assets/liquid-DYVedYrR.js +1 -0
  169. package/dist/client/assets/llvm-DjAJT7YJ.js +1 -0
  170. package/dist/client/assets/log-2UxHyX5q.js +1 -0
  171. package/dist/client/assets/logo-BtOb2qkB.js +1 -0
  172. package/dist/client/assets/lua-BaeVxFsk.js +1 -0
  173. package/dist/client/assets/luau-C-HG3fhB.js +1 -0
  174. package/dist/client/assets/make-CHLpvVh8.js +1 -0
  175. package/dist/client/assets/markdown-Cvjx9yec.js +1 -0
  176. package/dist/client/assets/marko-CnJfTvn9.js +1 -0
  177. package/dist/client/assets/material-theme-D5KoaKCx.js +1 -0
  178. package/dist/client/assets/material-theme-darker-BfHTSMKl.js +1 -0
  179. package/dist/client/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  180. package/dist/client/assets/material-theme-ocean-CyktbL80.js +1 -0
  181. package/dist/client/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  182. package/dist/client/assets/matlab-D7o27uSR.js +1 -0
  183. package/dist/client/assets/mdc-BMNejdWA.js +1 -0
  184. package/dist/client/assets/mdx-Cmh6b_Ma.js +1 -0
  185. package/dist/client/assets/mermaid-mWjccvbQ.js +1 -0
  186. package/dist/client/assets/min-dark-CafNBF8u.js +1 -0
  187. package/dist/client/assets/min-light-CTRr51gU.js +1 -0
  188. package/dist/client/assets/mipsasm-CKIfxQSi.js +1 -0
  189. package/dist/client/assets/mojo-rZm6bMo-.js +1 -0
  190. package/dist/client/assets/monokai-D4h5O-jR.js +1 -0
  191. package/dist/client/assets/moonbit-_H4v1dQx.js +1 -0
  192. package/dist/client/assets/move-IF9eRakj.js +1 -0
  193. package/dist/client/assets/narrat-DRg8JJMk.js +1 -0
  194. package/dist/client/assets/nextflow-Zz6hmt5N.js +1 -0
  195. package/dist/client/assets/nextflow-groovy-BeH2EWoN.js +1 -0
  196. package/dist/client/assets/nginx-BpAMiNFr.js +1 -0
  197. package/dist/client/assets/night-owl-C39BiMTA.js +1 -0
  198. package/dist/client/assets/night-owl-light-CMTm3GFP.js +1 -0
  199. package/dist/client/assets/nim-CVrawwO9.js +1 -0
  200. package/dist/client/assets/nix-CwoSXNpI.js +1 -0
  201. package/dist/client/assets/nord-Ddv68eIx.js +1 -0
  202. package/dist/client/assets/nushell-Cz2AlsmD.js +1 -0
  203. package/dist/client/assets/objective-c-DXmwc3jG.js +1 -0
  204. package/dist/client/assets/objective-cpp-CLxacb5B.js +1 -0
  205. package/dist/client/assets/ocaml-C0hk2d4L.js +1 -0
  206. package/dist/client/assets/odin-BBf5iR-q.js +1 -0
  207. package/dist/client/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  208. package/dist/client/assets/one-light-C3Wv6jpd.js +1 -0
  209. package/dist/client/assets/openscad-C4EeE6gA.js +1 -0
  210. package/dist/client/assets/pascal-D93ZcfNL.js +1 -0
  211. package/dist/client/assets/perl-C0TMdlhV.js +1 -0
  212. package/dist/client/assets/php-Dhbhpdrm.js +1 -0
  213. package/dist/client/assets/pierre-dark-DF2SEV7i.js +1 -0
  214. package/dist/client/assets/pierre-light-DOlZxES8.js +1 -0
  215. package/dist/client/assets/pkl-u5AG7uiY.js +1 -0
  216. package/dist/client/assets/plastic-3e1v2bzS.js +1 -0
  217. package/dist/client/assets/plsql-ChMvpjG-.js +1 -0
  218. package/dist/client/assets/po-BTJTHyun.js +1 -0
  219. package/dist/client/assets/poimandres-CS3Unz2-.js +1 -0
  220. package/dist/client/assets/polar-C0HS_06l.js +1 -0
  221. package/dist/client/assets/postcss-CXtECtnM.js +1 -0
  222. package/dist/client/assets/powerquery-CEu0bR-o.js +1 -0
  223. package/dist/client/assets/powershell-Dpen1YoG.js +1 -0
  224. package/dist/client/assets/prisma-Dd19v3D-.js +1 -0
  225. package/dist/client/assets/prolog-CbFg5uaA.js +1 -0
  226. package/dist/client/assets/proto-C7zT0LnQ.js +1 -0
  227. package/dist/client/assets/pug-CGlum2m_.js +1 -0
  228. package/dist/client/assets/puppet-BMWR74SV.js +1 -0
  229. package/dist/client/assets/purescript-CklMAg4u.js +1 -0
  230. package/dist/client/assets/python-B6aJPvgy.js +1 -0
  231. package/dist/client/assets/qml-3beO22l8.js +1 -0
  232. package/dist/client/assets/qmldir-C8lEn-DE.js +1 -0
  233. package/dist/client/assets/qss-IeuSbFQv.js +1 -0
  234. package/dist/client/assets/r-Dspwwk_N.js +1 -0
  235. package/dist/client/assets/racket-BqYA7rlc.js +1 -0
  236. package/dist/client/assets/raku-DXvB9xmW.js +1 -0
  237. package/dist/client/assets/razor-Uh8Bk_45.js +1 -0
  238. package/dist/client/assets/red-bN70gL4F.js +1 -0
  239. package/dist/client/assets/reg-C-SQnVFl.js +1 -0
  240. package/dist/client/assets/regexp-CDVJQ6XC.js +1 -0
  241. package/dist/client/assets/rel-C3B-1QV4.js +1 -0
  242. package/dist/client/assets/riscv-BM1_JUlF.js +1 -0
  243. package/dist/client/assets/ron-D8l8udqQ.js +1 -0
  244. package/dist/client/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  245. package/dist/client/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  246. package/dist/client/assets/rose-pine-qdsjHGoJ.js +1 -0
  247. package/dist/client/assets/rosmsg-BJDFO7_C.js +1 -0
  248. package/dist/client/assets/rst-BrH8l1NY.js +1 -0
  249. package/dist/client/assets/ruby-Dw2BHqvy.js +1 -0
  250. package/dist/client/assets/rust-B1yitclQ.js +1 -0
  251. package/dist/client/assets/sas-cz2c8ADy.js +1 -0
  252. package/dist/client/assets/sass-Cj5Yp3dK.js +1 -0
  253. package/dist/client/assets/scala-C151Ov-r.js +1 -0
  254. package/dist/client/assets/scheme-C98Dy4si.js +1 -0
  255. package/dist/client/assets/scratchpad-page-DcMny6uZ.js +1 -0
  256. package/dist/client/assets/scss-OYdSNvt2.js +1 -0
  257. package/dist/client/assets/sdbl-DVxCFoDh.js +1 -0
  258. package/dist/client/assets/shaderlab-Dg9Lc6iA.js +1 -0
  259. package/dist/client/assets/shellscript-Yzrsuije.js +1 -0
  260. package/dist/client/assets/shellsession-BADoaaVG.js +1 -0
  261. package/dist/client/assets/slack-dark-BthQWCQV.js +1 -0
  262. package/dist/client/assets/slack-ochin-DqwNpetd.js +1 -0
  263. package/dist/client/assets/smalltalk-BERRCDM3.js +1 -0
  264. package/dist/client/assets/snazzy-light-Bw305WKR.js +1 -0
  265. package/dist/client/assets/solarized-dark-DXbdFlpD.js +1 -0
  266. package/dist/client/assets/solarized-light-L9t79GZl.js +1 -0
  267. package/dist/client/assets/solidity-rGO070M0.js +1 -0
  268. package/dist/client/assets/soy-Brmx7dQM.js +1 -0
  269. package/dist/client/assets/sparql-rVzFXLq3.js +1 -0
  270. package/dist/client/assets/splunk-BtCnVYZw.js +1 -0
  271. package/dist/client/assets/sql-BLtJtn59.js +1 -0
  272. package/dist/client/assets/ssh-config-_ykCGR6B.js +1 -0
  273. package/dist/client/assets/stata-BH5u7GGu.js +1 -0
  274. package/dist/client/assets/stylus-BEDo0Tqx.js +1 -0
  275. package/dist/client/assets/surrealql-Bq5Q-fJD.js +1 -0
  276. package/dist/client/assets/svelte-C_ipcX3V.js +1 -0
  277. package/dist/client/assets/swift-D82vCrfD.js +1 -0
  278. package/dist/client/assets/synthwave-84-CbfX1IO0.js +1 -0
  279. package/dist/client/assets/system-verilog-CnnmHF94.js +1 -0
  280. package/dist/client/assets/systemd-4A_iFExJ.js +1 -0
  281. package/dist/client/assets/talonscript-CkByrt1z.js +1 -0
  282. package/dist/client/assets/tasl-QIJgUcNo.js +1 -0
  283. package/dist/client/assets/tcl-dwOrl1Do.js +1 -0
  284. package/dist/client/assets/templ-P3uqSqPl.js +1 -0
  285. package/dist/client/assets/terraform-BETggiCN.js +1 -0
  286. package/dist/client/assets/tex-idrVyKtj.js +1 -0
  287. package/dist/client/assets/tokyo-night-hegEt444.js +1 -0
  288. package/dist/client/assets/toml-vGWfd6FD.js +1 -0
  289. package/dist/client/assets/ts-tags-zn1MmPIZ.js +1 -0
  290. package/dist/client/assets/tsv-B_m7g4N7.js +1 -0
  291. package/dist/client/assets/tsx-COt5Ahok.js +1 -0
  292. package/dist/client/assets/turtle-BsS91CYL.js +1 -0
  293. package/dist/client/assets/twig-DNn4PbVi.js +1 -0
  294. package/dist/client/assets/typescript-BPQ3VLAy.js +1 -0
  295. package/dist/client/assets/typespec-BGHnOYBU.js +1 -0
  296. package/dist/client/assets/typst-DHCkPAjA.js +1 -0
  297. package/dist/client/assets/v-BcVCzyr7.js +1 -0
  298. package/dist/client/assets/vala-CsfeWuGM.js +1 -0
  299. package/dist/client/assets/vb-D17OF-Vu.js +1 -0
  300. package/dist/client/assets/verilog-BQ8w6xss.js +1 -0
  301. package/dist/client/assets/vesper-DU1UobuO.js +1 -0
  302. package/dist/client/assets/vhdl-CeAyd5Ju.js +1 -0
  303. package/dist/client/assets/viml-CJc9bBzg.js +1 -0
  304. package/dist/client/assets/vitesse-black-Bkuqu6BP.js +1 -0
  305. package/dist/client/assets/vitesse-dark-D0r3Knsf.js +1 -0
  306. package/dist/client/assets/vitesse-light-CVO1_9PV.js +1 -0
  307. package/dist/client/assets/vue-DN_0RTcg.js +1 -0
  308. package/dist/client/assets/vue-html-AaS7Mt5G.js +1 -0
  309. package/dist/client/assets/vue-vine-CQOfvN7w.js +1 -0
  310. package/dist/client/assets/vyper-CDx5xZoG.js +1 -0
  311. package/dist/client/assets/wasm-CG6Dc4jp.js +1 -0
  312. package/dist/client/assets/wasm-MzD3tlZU.js +1 -0
  313. package/dist/client/assets/wenyan-BV7otONQ.js +1 -0
  314. package/dist/client/assets/wgsl-Dx-B1_4e.js +1 -0
  315. package/dist/client/assets/wikitext-BhOHFoWU.js +1 -0
  316. package/dist/client/assets/wit-5i3qLPDT.js +1 -0
  317. package/dist/client/assets/wolfram-lXgVvXCa.js +1 -0
  318. package/dist/client/assets/xml-sdJ4AIDG.js +1 -0
  319. package/dist/client/assets/xsl-CtQFsRM5.js +1 -0
  320. package/dist/client/assets/yaml-Buea-lGh.js +1 -0
  321. package/dist/client/assets/zenscript-DVFEvuxE.js +1 -0
  322. package/dist/client/assets/zig-VOosw3JB.js +1 -0
  323. package/dist/client/cursor.png +0 -0
  324. package/dist/client/favicon.svg +17 -0
  325. package/dist/client/finder.png +0 -0
  326. package/dist/client/icons/claude.svg +1 -0
  327. package/dist/client/icons/openai.svg +1 -0
  328. package/dist/client/images/github.png +0 -0
  329. package/dist/client/index.html +15 -0
  330. package/dist/client/logo.svg +17 -0
  331. package/dist/client/terminal.png +0 -0
  332. package/dist/client/vscode.png +0 -0
  333. package/dist/client/warp.png +0 -0
  334. package/package.json +107 -0
  335. package/src/server/agent-instruction-attachments.ts +458 -0
  336. package/src/server/agent.ts +1879 -0
  337. package/src/server/cli-runtime.ts +418 -0
  338. package/src/server/cli-supervisor.ts +90 -0
  339. package/src/server/cli.ts +102 -0
  340. package/src/server/codex-app-server-protocol.ts +478 -0
  341. package/src/server/codex-app-server.ts +1645 -0
  342. package/src/server/data-dir-lock.ts +128 -0
  343. package/src/server/diff-store.ts +1587 -0
  344. package/src/server/durable-file.ts +74 -0
  345. package/src/server/event-store.ts +1448 -0
  346. package/src/server/event.ts +249 -0
  347. package/src/server/external-file-access.ts +48 -0
  348. package/src/server/external-open.ts +259 -0
  349. package/src/server/generate-title.ts +75 -0
  350. package/src/server/git-refresh-poller.ts +92 -0
  351. package/src/server/github-rest-client.ts +176 -0
  352. package/src/server/harness-types.ts +24 -0
  353. package/src/server/keybindings.ts +203 -0
  354. package/src/server/machine-name.ts +22 -0
  355. package/src/server/paths.ts +51 -0
  356. package/src/server/pr-manager.ts +1204 -0
  357. package/src/server/pr-refresh-poller.ts +126 -0
  358. package/src/server/process-utils.ts +18 -0
  359. package/src/server/provider-catalog.ts +90 -0
  360. package/src/server/quick-response.ts +274 -0
  361. package/src/server/read-models.ts +311 -0
  362. package/src/server/restart.ts +33 -0
  363. package/src/server/scratchpad-manager.ts +87 -0
  364. package/src/server/server.ts +759 -0
  365. package/src/server/share.ts +126 -0
  366. package/src/server/terminal-manager.ts +371 -0
  367. package/src/server/update-manager.ts +250 -0
  368. package/src/server/uploads.ts +191 -0
  369. package/src/server/workspace-file-search.ts +191 -0
  370. package/src/server/workspace-manager.ts +627 -0
  371. package/src/server/workspace-polling.ts +10 -0
  372. package/src/server/ws-router.ts +1039 -0
  373. package/src/shared/branding.ts +69 -0
  374. package/src/shared/dev-ports.ts +100 -0
  375. package/src/shared/ports.ts +2 -0
  376. package/src/shared/protocol.ts +217 -0
  377. package/src/shared/tools.ts +324 -0
  378. package/src/shared/types.ts +1220 -0
  379. package/tsconfig.json +35 -0
@@ -0,0 +1,1204 @@
1
+ import { createHash } from 'node:crypto';
2
+ import type {
3
+ PullRequestCheckSnapshot,
4
+ PullRequestCommentSnapshot,
5
+ WorkspaceDiffFile,
6
+ WorkspaceGitHubSnapshot,
7
+ } from 'src/shared/types';
8
+ import { runCommand } from './diff-store';
9
+ import type { WorkspaceRecord } from './event';
10
+ import type { EventStore } from './event-store';
11
+ import {
12
+ GitHubRateLimitError,
13
+ GitHubRestClient,
14
+ type GitHubRestResult,
15
+ } from './github-rest-client';
16
+ import { inferWorkspaceFileContentType } from './uploads';
17
+
18
+ type GhResult = Awaited<ReturnType<typeof runCommand>>;
19
+
20
+ interface GitHubApiClient {
21
+ requestJson<T>(cacheKey: string, path: string): Promise<GitHubRestResult<T>>;
22
+ requestJsonPages?<TPage, TItem>(
23
+ cacheKey: string,
24
+ path: string,
25
+ getItems: (page: TPage) => TItem[],
26
+ ): Promise<GitHubRestResult<TItem[]>>;
27
+ }
28
+
29
+ interface PrManagerDeps {
30
+ runGh?: (args: string[]) => Promise<GhResult>;
31
+ github?: GitHubApiClient;
32
+ }
33
+
34
+ interface GitHubPullRequestSearchItem {
35
+ number?: number;
36
+ title?: string;
37
+ url?: string;
38
+ state?: string;
39
+ merged_at?: string | null;
40
+ isDraft?: boolean;
41
+ draft?: boolean;
42
+ headRefName?: string;
43
+ head?: { ref?: string };
44
+ baseRefName?: string;
45
+ base?: { ref?: string };
46
+ createdAt?: string;
47
+ created_at?: string;
48
+ }
49
+
50
+ interface GitHubCommentAuthor {
51
+ login?: string;
52
+ isBot?: boolean;
53
+ }
54
+
55
+ interface GitHubPullRequestComment {
56
+ id?: string;
57
+ author?: GitHubCommentAuthor;
58
+ authorAssociation?: string;
59
+ body?: string;
60
+ url?: string;
61
+ path?: string;
62
+ line?: number;
63
+ createdAt?: string;
64
+ updatedAt?: string;
65
+ }
66
+
67
+ interface GitHubPullRequestCheck {
68
+ name?: string;
69
+ workflowName?: string;
70
+ status?: string;
71
+ conclusion?: string;
72
+ detailsUrl?: string;
73
+ startedAt?: string;
74
+ completedAt?: string;
75
+ }
76
+
77
+ interface GitHubPullRequestView {
78
+ number?: number;
79
+ title?: string;
80
+ body?: string;
81
+ html_url?: string;
82
+ url?: string;
83
+ state?: string;
84
+ merged_at?: string | null;
85
+ mergeable_state?: string;
86
+ mergeStateStatus?: string;
87
+ isDraft?: boolean;
88
+ draft?: boolean;
89
+ headRefName?: string;
90
+ head?: { ref?: string; sha?: string };
91
+ baseRefName?: string;
92
+ base?: { ref?: string };
93
+ createdAt?: string;
94
+ created_at?: string;
95
+ additions?: number;
96
+ deletions?: number;
97
+ comments?: GitHubPullRequestComment[];
98
+ reviews?: Array<{
99
+ id?: string;
100
+ author?: GitHubCommentAuthor;
101
+ authorAssociation?: string;
102
+ body?: string;
103
+ url?: string;
104
+ state?: string;
105
+ submittedAt?: string;
106
+ }>;
107
+ reviewLineComments?: GitHubPullRequestComment[];
108
+ statusCheckRollup?: GitHubPullRequestCheck[];
109
+ files?: GitHubPullRequestFile[];
110
+ }
111
+
112
+ interface GitHubPullRequestFile {
113
+ path?: string;
114
+ filename?: string;
115
+ status?: string;
116
+ additions?: number;
117
+ deletions?: number;
118
+ patch?: string;
119
+ patchDigest?: string;
120
+ previous_filename?: string;
121
+ previousFilename?: string;
122
+ }
123
+
124
+ interface GitHubRestIssueComment {
125
+ id?: number;
126
+ user?: { login?: string; type?: string };
127
+ author_association?: string;
128
+ body?: string;
129
+ html_url?: string;
130
+ path?: string;
131
+ line?: number;
132
+ created_at?: string;
133
+ updated_at?: string;
134
+ }
135
+
136
+ interface GitHubRestReview {
137
+ id?: number;
138
+ user?: { login?: string; type?: string };
139
+ author_association?: string;
140
+ body?: string;
141
+ html_url?: string;
142
+ state?: string;
143
+ submitted_at?: string;
144
+ }
145
+
146
+ interface GitHubRestCheckRun {
147
+ name?: string;
148
+ status?: string;
149
+ conclusion?: string;
150
+ html_url?: string;
151
+ started_at?: string;
152
+ completed_at?: string;
153
+ check_suite?: { app?: { name?: string } };
154
+ }
155
+
156
+ interface GitHubRestCheckRunsResponse {
157
+ check_runs?: GitHubRestCheckRun[];
158
+ }
159
+
160
+ interface GitHubRestCombinedStatus {
161
+ statuses?: Array<{
162
+ context?: string;
163
+ state?: string;
164
+ target_url?: string;
165
+ created_at?: string;
166
+ updated_at?: string;
167
+ }>;
168
+ }
169
+
170
+ type GitHubRestPullRequestFile = GitHubPullRequestFile;
171
+
172
+ function parseJson<T>(value: string): T | null {
173
+ try {
174
+ return JSON.parse(value) as T;
175
+ } catch {
176
+ return null;
177
+ }
178
+ }
179
+
180
+ function normalizePrStatus(
181
+ state: string | undefined,
182
+ mergedAt?: string | null,
183
+ ): WorkspaceGitHubSnapshot['status'] {
184
+ if (mergedAt) return 'merged';
185
+ const normalized = state?.toUpperCase();
186
+ if (normalized === 'OPEN') return 'open';
187
+ if (normalized === 'MERGED') return 'merged';
188
+ if (normalized === 'CLOSED') return 'closed';
189
+ return 'unknown';
190
+ }
191
+
192
+ function parseGitHubTimestamp(value: string | undefined) {
193
+ if (!value) return undefined;
194
+ const timestamp = Date.parse(value);
195
+ return Number.isFinite(timestamp) ? timestamp : undefined;
196
+ }
197
+
198
+ function normalizeCheckStatus(check: GitHubPullRequestCheck): PullRequestCheckSnapshot['status'] {
199
+ const conclusion = check.conclusion?.toUpperCase();
200
+ const status = check.status?.toUpperCase();
201
+
202
+ if (conclusion === 'FAILURE' || conclusion === 'CANCELLED' || conclusion === 'TIMED_OUT')
203
+ return 'failing';
204
+ if (status && status !== 'COMPLETED') return 'pending';
205
+ if (conclusion === 'SUCCESS' || conclusion === 'SKIPPED') return 'passing';
206
+ return 'unknown';
207
+ }
208
+
209
+ function deriveCiStatus(checks: PullRequestCheckSnapshot[]): WorkspaceGitHubSnapshot['ciStatus'] {
210
+ if (checks.length === 0) return 'unknown';
211
+ if (checks.some((check) => check.status === 'failing')) return 'failing';
212
+ if (checks.some((check) => check.status === 'pending')) return 'pending';
213
+ if (checks.every((check) => check.status === 'passing')) return 'passing';
214
+ return 'unknown';
215
+ }
216
+
217
+ function hasMergeConflicts(mergeStateStatus: string | undefined) {
218
+ return mergeStateStatus?.toUpperCase() === 'DIRTY';
219
+ }
220
+
221
+ function sourceIsDraft(source: GitHubPullRequestView | GitHubPullRequestSearchItem) {
222
+ return source.isDraft ?? ('draft' in source ? source.draft : undefined);
223
+ }
224
+
225
+ function dedupeChecks(checks: GitHubPullRequestCheck[]) {
226
+ const seen = new Set<string>();
227
+ return checks.filter((check) => {
228
+ const key = `${check.name ?? ''}:${check.detailsUrl ?? ''}`;
229
+ if (seen.has(key)) return false;
230
+ seen.add(key);
231
+ return true;
232
+ });
233
+ }
234
+
235
+ function mergeStateStatusFromRest(value: string | undefined) {
236
+ return value?.toUpperCase();
237
+ }
238
+
239
+ function sourceUrl(source: GitHubPullRequestView | GitHubPullRequestSearchItem) {
240
+ return 'html_url' in source && source.html_url ? source.html_url : source.url;
241
+ }
242
+
243
+ function sourceHeadRef(source: GitHubPullRequestView | GitHubPullRequestSearchItem) {
244
+ return source.headRefName ?? source.head?.ref;
245
+ }
246
+
247
+ function sourceBaseRef(source: GitHubPullRequestView | GitHubPullRequestSearchItem) {
248
+ return source.baseRefName ?? source.base?.ref;
249
+ }
250
+
251
+ function sourceCreatedAt(source: GitHubPullRequestView | GitHubPullRequestSearchItem) {
252
+ return source.createdAt ?? source.created_at;
253
+ }
254
+
255
+ function mapChecks(pr: GitHubPullRequestView): PullRequestCheckSnapshot[] {
256
+ const checks = pr.statusCheckRollup ?? [];
257
+ return checks.flatMap((check) => {
258
+ if (!check.name) return [];
259
+ const status = normalizeCheckStatus(check);
260
+ return {
261
+ name: check.name,
262
+ workflowName: check.workflowName,
263
+ status,
264
+ conclusion: check.conclusion,
265
+ detailsUrl: check.detailsUrl,
266
+ startedAt: check.startedAt,
267
+ completedAt: check.completedAt,
268
+ canFetchLogs: status === 'failing',
269
+ } satisfies PullRequestCheckSnapshot;
270
+ });
271
+ }
272
+
273
+ function isBotAuthor(author: GitHubCommentAuthor | undefined) {
274
+ return Boolean(author?.isBot || author?.login?.endsWith('[bot]'));
275
+ }
276
+
277
+ function mapIssueComments(pr: GitHubPullRequestView): PullRequestCommentSnapshot[] {
278
+ return (pr.comments ?? []).flatMap((comment, index) => {
279
+ if (!comment.body) return [];
280
+ return {
281
+ id: comment.id ?? `issue-${index}`,
282
+ author: comment.author?.login,
283
+ authorAssociation: comment.authorAssociation,
284
+ body: comment.body,
285
+ url: comment.url,
286
+ path: comment.path,
287
+ line: comment.line,
288
+ isBot: isBotAuthor(comment.author),
289
+ source: 'issue',
290
+ createdAt: comment.createdAt,
291
+ updatedAt: comment.updatedAt,
292
+ } satisfies PullRequestCommentSnapshot;
293
+ });
294
+ }
295
+
296
+ function mapReviewComments(pr: GitHubPullRequestView): PullRequestCommentSnapshot[] {
297
+ return (pr.reviews ?? []).flatMap((review, index) => {
298
+ if (!review.body) return [];
299
+ return {
300
+ id: review.id ?? `review-${index}`,
301
+ author: review.author?.login,
302
+ authorAssociation: review.authorAssociation,
303
+ body: review.body,
304
+ url: review.url,
305
+ isBot: isBotAuthor(review.author),
306
+ source: 'review',
307
+ createdAt: review.submittedAt,
308
+ updatedAt: review.submittedAt,
309
+ } satisfies PullRequestCommentSnapshot;
310
+ });
311
+ }
312
+
313
+ function mapReviewLineComments(pr: GitHubPullRequestView): PullRequestCommentSnapshot[] {
314
+ return (pr.reviewLineComments ?? []).flatMap((comment, index) => {
315
+ if (!comment.body) return [];
316
+ return {
317
+ id: comment.id ?? `thread-${index}`,
318
+ author: comment.author?.login,
319
+ authorAssociation: comment.authorAssociation,
320
+ body: comment.body,
321
+ url: comment.url,
322
+ path: comment.path,
323
+ line: comment.line,
324
+ isBot: isBotAuthor(comment.author),
325
+ source: 'thread',
326
+ createdAt: comment.createdAt,
327
+ updatedAt: comment.updatedAt,
328
+ } satisfies PullRequestCommentSnapshot;
329
+ });
330
+ }
331
+
332
+ function mapComments(pr: GitHubPullRequestView): PullRequestCommentSnapshot[] {
333
+ return [...mapIssueComments(pr), ...mapReviewComments(pr), ...mapReviewLineComments(pr)];
334
+ }
335
+
336
+ function normalizePrFileStatus(status: string | undefined): WorkspaceDiffFile['changeType'] {
337
+ const normalized = status?.toLowerCase();
338
+ if (normalized === 'added') return 'added';
339
+ if (normalized === 'removed' || normalized === 'deleted') return 'deleted';
340
+ if (normalized === 'renamed') return 'renamed';
341
+ return 'modified';
342
+ }
343
+
344
+ function hashPrFile(file: GitHubPullRequestFile, filePath: string) {
345
+ if (file.patchDigest) return file.patchDigest;
346
+ return createHash('sha256')
347
+ .update(
348
+ [
349
+ filePath,
350
+ file.previous_filename ?? file.previousFilename ?? '',
351
+ file.status ?? '',
352
+ String(file.additions ?? 0),
353
+ String(file.deletions ?? 0),
354
+ file.patch ?? '',
355
+ ].join('\0'),
356
+ )
357
+ .digest('hex');
358
+ }
359
+
360
+ function mapPrFiles(files: GitHubPullRequestFile[] | undefined): WorkspaceDiffFile[] {
361
+ return (files ?? [])
362
+ .flatMap((file) => {
363
+ const filePath = file.path ?? file.filename;
364
+ if (!filePath) return [];
365
+ return {
366
+ path: filePath,
367
+ changeType: normalizePrFileStatus(file.status),
368
+ isUntracked: false,
369
+ additions: file.additions ?? 0,
370
+ deletions: file.deletions ?? 0,
371
+ patchDigest: hashPrFile(file, filePath),
372
+ patch: file.patch,
373
+ mimeType:
374
+ normalizePrFileStatus(file.status) === 'deleted'
375
+ ? undefined
376
+ : inferWorkspaceFileContentType(filePath),
377
+ } satisfies WorkspaceDiffFile;
378
+ })
379
+ .sort((left, right) => left.path.localeCompare(right.path));
380
+ }
381
+
382
+ function mapRestIssueComments(comments: GitHubRestIssueComment[]): PullRequestCommentSnapshot[] {
383
+ return comments.flatMap((comment, index) => {
384
+ if (!comment.body) return [];
385
+ const author = comment.user?.login;
386
+ return {
387
+ id: comment.id !== undefined ? `issue-${comment.id}` : `issue-${index}`,
388
+ author,
389
+ authorAssociation: comment.author_association,
390
+ body: comment.body,
391
+ url: comment.html_url,
392
+ path: comment.path,
393
+ line: comment.line,
394
+ isBot: Boolean(comment.user?.type === 'Bot' || author?.endsWith('[bot]')),
395
+ source: 'issue',
396
+ createdAt: comment.created_at,
397
+ updatedAt: comment.updated_at,
398
+ } satisfies PullRequestCommentSnapshot;
399
+ });
400
+ }
401
+
402
+ function mapRestReviews(reviews: GitHubRestReview[]): PullRequestCommentSnapshot[] {
403
+ return reviews.flatMap((review, index) => {
404
+ if (!review.body) return [];
405
+ const author = review.user?.login;
406
+ return {
407
+ id: review.id !== undefined ? `review-${review.id}` : `review-${index}`,
408
+ author,
409
+ authorAssociation: review.author_association,
410
+ body: review.body,
411
+ url: review.html_url,
412
+ isBot: Boolean(review.user?.type === 'Bot' || author?.endsWith('[bot]')),
413
+ source: 'review',
414
+ createdAt: review.submitted_at,
415
+ updatedAt: review.submitted_at,
416
+ } satisfies PullRequestCommentSnapshot;
417
+ });
418
+ }
419
+
420
+ function mapRestReviewLineComments(comments: GitHubRestIssueComment[]): GitHubPullRequestComment[] {
421
+ return comments.flatMap((comment, index) => {
422
+ if (!comment.body) return [];
423
+ return {
424
+ id: comment.id !== undefined ? `thread-${comment.id}` : `thread-${index}`,
425
+ author: {
426
+ login: comment.user?.login,
427
+ isBot: Boolean(comment.user?.type === 'Bot' || comment.user?.login?.endsWith('[bot]')),
428
+ },
429
+ authorAssociation: comment.author_association,
430
+ body: comment.body,
431
+ url: comment.html_url,
432
+ path: comment.path,
433
+ line: comment.line,
434
+ createdAt: comment.created_at,
435
+ updatedAt: comment.updated_at,
436
+ } satisfies GitHubPullRequestComment;
437
+ });
438
+ }
439
+
440
+ function createNoneSnapshot(owner: string, repo: string): WorkspaceGitHubSnapshot {
441
+ return {
442
+ status: 'none',
443
+ owner,
444
+ repo,
445
+ comments: [],
446
+ checks: [],
447
+ lastRefreshedAt: Date.now(),
448
+ };
449
+ }
450
+
451
+ function createKnownPrSnapshot(
452
+ workspace: WorkspaceRecord,
453
+ owner: string,
454
+ repo: string,
455
+ ): WorkspaceGitHubSnapshot | null {
456
+ const pullRequest = workspace.pullRequest;
457
+ if (!pullRequest) return null;
458
+
459
+ return {
460
+ status: pullRequest.status,
461
+ owner,
462
+ repo,
463
+ prNumber: pullRequest.number,
464
+ createdAt: pullRequest.createdAt,
465
+ title: pullRequest.title,
466
+ body: pullRequest.body,
467
+ url: pullRequest.url,
468
+ headRefName: pullRequest.headRefName,
469
+ baseRefName: pullRequest.baseRefName,
470
+ ciStatus: pullRequest.ciStatus ?? 'unknown',
471
+ isDraft: pullRequest.isDraft,
472
+ mergeStateStatus: pullRequest.mergeStateStatus,
473
+ hasMergeConflicts: pullRequest.hasMergeConflicts,
474
+ unresolvedCommentCount: pullRequest.unresolvedCommentCount,
475
+ additions: pullRequest.additions,
476
+ deletions: pullRequest.deletions,
477
+ files: pullRequest.files ?? [],
478
+ comments: pullRequest.comments ?? [],
479
+ checks: pullRequest.checks ?? [],
480
+ lastRefreshedAt: pullRequest.lastObservedAt,
481
+ };
482
+ }
483
+
484
+ function shouldExposeKnownPrSnapshot(workspace: WorkspaceRecord) {
485
+ return workspace.visibilityState === 'active' && Boolean(workspace.pullRequest);
486
+ }
487
+
488
+ export class PrManager {
489
+ private readonly snapshots = new Map<string, WorkspaceGitHubSnapshot>();
490
+ private readonly runGhCommand: (args: string[]) => Promise<GhResult>;
491
+ private readonly github: GitHubApiClient | null;
492
+ private readonly useGhForPrRefresh: boolean;
493
+
494
+ constructor(
495
+ private readonly eventStore: EventStore,
496
+ deps: PrManagerDeps = {},
497
+ ) {
498
+ this.runGhCommand = deps.runGh ?? ((args) => runCommand(['gh', ...args]));
499
+ this.github = deps.github ?? (deps.runGh ? null : new GitHubRestClient());
500
+ this.useGhForPrRefresh = Boolean(deps.runGh && !deps.github);
501
+ }
502
+
503
+ clearWorkspaceGitHubSnapshot(workspaceId: string) {
504
+ this.snapshots.delete(workspaceId);
505
+ }
506
+
507
+ getWorkspaceGitHubSnapshot(workspaceId: string) {
508
+ const snapshot = this.snapshots.get(workspaceId);
509
+ if (snapshot) return snapshot;
510
+
511
+ const workspace = this.eventStore.getWorkspace(workspaceId);
512
+ if (!workspace || !shouldExposeKnownPrSnapshot(workspace)) return null;
513
+
514
+ const directory = this.eventStore.getDirectory(workspace.directoryId);
515
+ if (!directory) return null;
516
+
517
+ const knownSnapshot = createKnownPrSnapshot(
518
+ workspace,
519
+ directory.githubOwner,
520
+ directory.githubRepo,
521
+ );
522
+ if (!knownSnapshot) return null;
523
+
524
+ this.snapshots.set(workspace.id, knownSnapshot);
525
+ return knownSnapshot;
526
+ }
527
+
528
+ private async runGh(args: string[]) {
529
+ return this.runGhCommand(args);
530
+ }
531
+
532
+ private async requestGitHub<T>(cacheKey: string, path: string): Promise<GitHubRestResult<T>> {
533
+ if (!this.github) throw new Error('GitHub REST client is not configured');
534
+ return this.github.requestJson<T>(cacheKey, path);
535
+ }
536
+
537
+ private async requestGitHubPages<TPage, TItem>(
538
+ cacheKey: string,
539
+ path: string,
540
+ getItems: (page: TPage) => TItem[],
541
+ ): Promise<GitHubRestResult<TItem[]>> {
542
+ if (!this.github) throw new Error('GitHub REST client is not configured');
543
+ if (this.github.requestJsonPages) {
544
+ return this.github.requestJsonPages<TPage, TItem>(cacheKey, path, getItems);
545
+ }
546
+
547
+ const result = await this.github.requestJson<TPage>(cacheKey, path);
548
+ return result.status === 'ok' ? { status: 'ok', data: getItems(result.data) } : result;
549
+ }
550
+
551
+ private async findPrForBranch(owner: string, repo: string, branchName: string) {
552
+ if (!this.useGhForPrRefresh) return this.findPrForBranchWithRest(owner, repo, branchName);
553
+
554
+ const result = await this.runGh([
555
+ 'pr',
556
+ 'list',
557
+ '--repo',
558
+ `${owner}/${repo}`,
559
+ '--head',
560
+ branchName,
561
+ '--state',
562
+ 'all',
563
+ '--limit',
564
+ '20',
565
+ '--json',
566
+ 'number,title,url,state,headRefName,baseRefName,isDraft,createdAt',
567
+ ]);
568
+
569
+ if (result.exitCode !== 0) {
570
+ throw new Error(
571
+ [result.stderr.trim(), result.stdout.trim()].filter(Boolean).join('\n') ||
572
+ 'GitHub PR lookup failed',
573
+ );
574
+ }
575
+
576
+ const prs = parseJson<GitHubPullRequestSearchItem[]>(result.stdout) ?? [];
577
+ return (
578
+ prs.find((pr) => pr.state?.toUpperCase() === 'OPEN') ??
579
+ prs.find((pr) => pr.state?.toUpperCase() === 'MERGED') ??
580
+ prs.find((pr) => pr.state?.toUpperCase() === 'CLOSED') ??
581
+ null
582
+ );
583
+ }
584
+
585
+ private async findPrForBranchWithRest(owner: string, repo: string, branchName: string) {
586
+ const path = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls?head=${encodeURIComponent(`${owner}:${branchName}`)}&state=all&per_page=20`;
587
+ const result = await this.requestGitHub<GitHubPullRequestSearchItem[]>(
588
+ `pulls:${owner}/${repo}:${branchName}`,
589
+ path,
590
+ );
591
+ if (result.status === 'not_modified') {
592
+ return this.getWorkspaceSnapshotPrSearchItem(owner, repo, branchName);
593
+ }
594
+
595
+ const prs = result.data ?? [];
596
+ return (
597
+ prs.find((pr) => normalizePrStatus(pr.state, pr.merged_at) === 'open') ??
598
+ prs.find((pr) => normalizePrStatus(pr.state, pr.merged_at) === 'merged') ??
599
+ prs.find((pr) => normalizePrStatus(pr.state, pr.merged_at) === 'closed') ??
600
+ null
601
+ );
602
+ }
603
+
604
+ private getWorkspaceSnapshotPrSearchItem(
605
+ owner: string,
606
+ repo: string,
607
+ branchName: string,
608
+ ): GitHubPullRequestSearchItem | null {
609
+ for (const snapshot of this.snapshots.values()) {
610
+ if (snapshot.owner !== owner || snapshot.repo !== repo) continue;
611
+ if (snapshot.headRefName !== branchName) continue;
612
+ if (!snapshot.prNumber) continue;
613
+ return {
614
+ number: snapshot.prNumber,
615
+ title: snapshot.title,
616
+ url: snapshot.url,
617
+ state:
618
+ snapshot.status === 'merged'
619
+ ? 'MERGED'
620
+ : snapshot.status === 'open'
621
+ ? 'OPEN'
622
+ : snapshot.status === 'closed'
623
+ ? 'CLOSED'
624
+ : undefined,
625
+ headRefName: snapshot.headRefName,
626
+ baseRefName: snapshot.baseRefName,
627
+ createdAt: snapshot.createdAt ? new Date(snapshot.createdAt).toISOString() : undefined,
628
+ };
629
+ }
630
+ return null;
631
+ }
632
+
633
+ private async viewPr(owner: string, repo: string, prNumber: number) {
634
+ if (!this.useGhForPrRefresh) return this.viewPrWithRest(owner, repo, prNumber);
635
+
636
+ const result = await this.runGh([
637
+ 'pr',
638
+ 'view',
639
+ String(prNumber),
640
+ '--repo',
641
+ `${owner}/${repo}`,
642
+ '--json',
643
+ 'number,title,body,url,state,mergeStateStatus,isDraft,headRefName,baseRefName,createdAt,additions,deletions,files,comments,reviews,statusCheckRollup',
644
+ ]);
645
+
646
+ if (result.exitCode !== 0) {
647
+ throw new Error(
648
+ [result.stderr.trim(), result.stdout.trim()].filter(Boolean).join('\n') ||
649
+ 'GitHub PR view failed',
650
+ );
651
+ }
652
+ return parseJson<GitHubPullRequestView>(result.stdout);
653
+ }
654
+
655
+ private async viewPrWithRest(owner: string, repo: string, prNumber: number) {
656
+ const detailPath = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`;
657
+ const detail = await this.requestGitHub<GitHubPullRequestView>(
658
+ `pull:${owner}/${repo}:${prNumber}`,
659
+ detailPath,
660
+ );
661
+ const pr =
662
+ detail.status === 'not_modified'
663
+ ? this.snapshotToPullRequestView(this.findSnapshot(owner, repo, prNumber))
664
+ : detail.data;
665
+ if (!pr) throw new Error('GitHub PR view returned not modified without a cached snapshot');
666
+ const [issueComments, reviewComments, lineComments, checks, files] = await Promise.all([
667
+ this.fetchIssueComments(owner, repo, prNumber),
668
+ this.fetchReviews(owner, repo, prNumber),
669
+ this.fetchReviewLineComments(owner, repo, prNumber),
670
+ this.fetchChecks(owner, repo, prNumber, pr.head?.sha ?? pr.headRefName),
671
+ this.fetchPrFiles(owner, repo, prNumber),
672
+ ]);
673
+
674
+ return {
675
+ ...pr,
676
+ url: pr.html_url ?? pr.url,
677
+ mergeStateStatus: pr.mergeStateStatus ?? mergeStateStatusFromRest(pr.mergeable_state),
678
+ headRefName: sourceHeadRef(pr),
679
+ baseRefName: sourceBaseRef(pr),
680
+ createdAt: sourceCreatedAt(pr),
681
+ comments: issueComments,
682
+ reviews: reviewComments,
683
+ reviewLineComments: lineComments,
684
+ statusCheckRollup: checks,
685
+ files,
686
+ } satisfies GitHubPullRequestView;
687
+ }
688
+
689
+ private findSnapshot(owner: string, repo: string, prNumber: number) {
690
+ for (const snapshot of this.snapshots.values()) {
691
+ if (snapshot.owner === owner && snapshot.repo === repo && snapshot.prNumber === prNumber) {
692
+ return snapshot;
693
+ }
694
+ }
695
+ return null;
696
+ }
697
+
698
+ private snapshotToPullRequestView(
699
+ snapshot: WorkspaceGitHubSnapshot | null,
700
+ ): GitHubPullRequestView | null {
701
+ if (!snapshot?.prNumber) return null;
702
+ return {
703
+ number: snapshot.prNumber,
704
+ title: snapshot.title,
705
+ body: snapshot.body,
706
+ url: snapshot.url,
707
+ state:
708
+ snapshot.status === 'merged'
709
+ ? 'MERGED'
710
+ : snapshot.status === 'open'
711
+ ? 'OPEN'
712
+ : snapshot.status === 'closed'
713
+ ? 'CLOSED'
714
+ : undefined,
715
+ mergeStateStatus: snapshot.mergeStateStatus,
716
+ isDraft: snapshot.isDraft,
717
+ headRefName: snapshot.headRefName,
718
+ baseRefName: snapshot.baseRefName,
719
+ createdAt: snapshot.createdAt ? new Date(snapshot.createdAt).toISOString() : undefined,
720
+ additions: snapshot.additions,
721
+ deletions: snapshot.deletions,
722
+ files: snapshot.files?.map((file) => ({
723
+ filename: file.path,
724
+ status: file.changeType,
725
+ additions: file.additions,
726
+ deletions: file.deletions,
727
+ patchDigest: file.patchDigest,
728
+ patch: file.patch,
729
+ })),
730
+ comments: snapshot.comments
731
+ .filter((comment) => comment.source === 'issue')
732
+ .map((comment) => ({
733
+ id: comment.id,
734
+ author: { login: comment.author, isBot: comment.isBot },
735
+ authorAssociation: comment.authorAssociation,
736
+ body: comment.body,
737
+ url: comment.url,
738
+ path: comment.path,
739
+ line: comment.line,
740
+ createdAt: comment.createdAt,
741
+ updatedAt: comment.updatedAt,
742
+ })),
743
+ reviews: snapshot.comments
744
+ .filter((comment) => comment.source === 'review')
745
+ .map((comment) => ({
746
+ id: comment.id,
747
+ author: { login: comment.author, isBot: comment.isBot },
748
+ authorAssociation: comment.authorAssociation,
749
+ body: comment.body,
750
+ url: comment.url,
751
+ submittedAt: comment.createdAt,
752
+ })),
753
+ reviewLineComments: snapshot.comments
754
+ .filter((comment) => comment.source === 'thread')
755
+ .map((comment) => ({
756
+ id: comment.id,
757
+ author: { login: comment.author, isBot: comment.isBot },
758
+ authorAssociation: comment.authorAssociation,
759
+ body: comment.body,
760
+ url: comment.url,
761
+ path: comment.path,
762
+ line: comment.line,
763
+ createdAt: comment.createdAt,
764
+ updatedAt: comment.updatedAt,
765
+ })),
766
+ statusCheckRollup: snapshot.checks.map((check) => ({
767
+ name: check.name,
768
+ workflowName: check.workflowName,
769
+ status: check.status === 'pending' ? 'IN_PROGRESS' : 'COMPLETED',
770
+ conclusion: check.conclusion,
771
+ detailsUrl: check.detailsUrl,
772
+ startedAt: check.startedAt,
773
+ completedAt: check.completedAt,
774
+ })),
775
+ };
776
+ }
777
+
778
+ private async fetchIssueComments(owner: string, repo: string, prNumber: number) {
779
+ const result = await this.requestGitHubPages<GitHubRestIssueComment[], GitHubRestIssueComment>(
780
+ `pull-comments:${owner}/${repo}:${prNumber}:issue`,
781
+ `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${prNumber}/comments?per_page=100`,
782
+ (page) => page,
783
+ );
784
+ if (result.status === 'not_modified') {
785
+ return (
786
+ this.snapshotToPullRequestView(this.findSnapshot(owner, repo, prNumber))?.comments ?? []
787
+ );
788
+ }
789
+ return mapRestIssueComments(result.data).map((comment) => ({
790
+ id: comment.id,
791
+ author: { login: comment.author, isBot: comment.isBot },
792
+ authorAssociation: comment.authorAssociation,
793
+ body: comment.body,
794
+ url: comment.url,
795
+ path: comment.path,
796
+ line: comment.line,
797
+ createdAt: comment.createdAt,
798
+ updatedAt: comment.updatedAt,
799
+ }));
800
+ }
801
+
802
+ private async fetchReviews(owner: string, repo: string, prNumber: number) {
803
+ const result = await this.requestGitHubPages<GitHubRestReview[], GitHubRestReview>(
804
+ `pull-comments:${owner}/${repo}:${prNumber}:reviews`,
805
+ `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/reviews?per_page=100`,
806
+ (page) => page,
807
+ );
808
+ if (result.status === 'not_modified') {
809
+ return (
810
+ this.snapshotToPullRequestView(this.findSnapshot(owner, repo, prNumber))?.reviews ?? []
811
+ );
812
+ }
813
+ return mapRestReviews(result.data).map((comment) => ({
814
+ id: comment.id,
815
+ author: { login: comment.author, isBot: comment.isBot },
816
+ authorAssociation: comment.authorAssociation,
817
+ body: comment.body,
818
+ url: comment.url,
819
+ submittedAt: comment.createdAt,
820
+ }));
821
+ }
822
+
823
+ private async fetchReviewLineComments(owner: string, repo: string, prNumber: number) {
824
+ const result = await this.requestGitHubPages<GitHubRestIssueComment[], GitHubRestIssueComment>(
825
+ `pull-comments:${owner}/${repo}:${prNumber}:line`,
826
+ `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/comments?per_page=100`,
827
+ (page) => page,
828
+ );
829
+ if (result.status === 'not_modified') {
830
+ return (
831
+ this.snapshotToPullRequestView(this.findSnapshot(owner, repo, prNumber))
832
+ ?.reviewLineComments ?? []
833
+ );
834
+ }
835
+ return mapRestReviewLineComments(result.data);
836
+ }
837
+
838
+ private async fetchPrFiles(owner: string, repo: string, prNumber: number) {
839
+ const result = await this.requestGitHubPages<
840
+ GitHubRestPullRequestFile[],
841
+ GitHubRestPullRequestFile
842
+ >(
843
+ `pull-files:${owner}/${repo}:${prNumber}`,
844
+ `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/files?per_page=100`,
845
+ (page) => page,
846
+ );
847
+ if (result.status === 'not_modified') {
848
+ return this.snapshotToPullRequestView(this.findSnapshot(owner, repo, prNumber))?.files ?? [];
849
+ }
850
+ return result.data;
851
+ }
852
+
853
+ private async fetchChecks(
854
+ owner: string,
855
+ repo: string,
856
+ prNumber: number,
857
+ ref: string | undefined,
858
+ ) {
859
+ if (!ref) return this.findSnapshot(owner, repo, prNumber)?.checks ?? [];
860
+ const [checkRuns, statuses] = await Promise.all([
861
+ this.fetchCheckRuns(owner, repo, prNumber, ref),
862
+ this.fetchCommitStatuses(owner, repo, prNumber, ref),
863
+ ]);
864
+ return dedupeChecks([...checkRuns, ...statuses]);
865
+ }
866
+
867
+ private async fetchCheckRuns(owner: string, repo: string, prNumber: number, ref: string) {
868
+ const result = await this.requestGitHubPages<GitHubRestCheckRunsResponse, GitHubRestCheckRun>(
869
+ `check-runs:${owner}/${repo}:${ref}`,
870
+ `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commits/${encodeURIComponent(ref)}/check-runs?per_page=100`,
871
+ (page) => page.check_runs ?? [],
872
+ );
873
+ if (result.status === 'not_modified')
874
+ return this.snapshotChecksAsGhChecks(owner, repo, prNumber);
875
+ return result.data.flatMap((check) => {
876
+ if (!check.name) return [];
877
+ return {
878
+ name: check.name,
879
+ workflowName: check.check_suite?.app?.name,
880
+ status: check.status,
881
+ conclusion: check.conclusion,
882
+ detailsUrl: check.html_url,
883
+ startedAt: check.started_at,
884
+ completedAt: check.completed_at,
885
+ } satisfies GitHubPullRequestCheck;
886
+ });
887
+ }
888
+
889
+ private async fetchCommitStatuses(owner: string, repo: string, prNumber: number, ref: string) {
890
+ const result = await this.requestGitHubPages<
891
+ GitHubRestCombinedStatus,
892
+ NonNullable<GitHubRestCombinedStatus['statuses']>[number]
893
+ >(
894
+ `commit-status:${owner}/${repo}:${ref}`,
895
+ `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commits/${encodeURIComponent(ref)}/status?per_page=100`,
896
+ (page) => page.statuses ?? [],
897
+ );
898
+ if (result.status === 'not_modified')
899
+ return this.snapshotChecksAsGhChecks(owner, repo, prNumber);
900
+ return result.data.flatMap((status) => {
901
+ if (!status.context) return [];
902
+ return {
903
+ name: status.context,
904
+ status: status.state === 'pending' ? 'IN_PROGRESS' : 'COMPLETED',
905
+ conclusion: status.state === 'success' ? 'success' : status.state,
906
+ detailsUrl: status.target_url,
907
+ startedAt: status.created_at,
908
+ completedAt: status.updated_at,
909
+ } satisfies GitHubPullRequestCheck;
910
+ });
911
+ }
912
+
913
+ private snapshotChecksAsGhChecks(owner: string, repo: string, prNumber: number) {
914
+ return (this.findSnapshot(owner, repo, prNumber)?.checks ?? []).map((check) => ({
915
+ name: check.name,
916
+ workflowName: check.workflowName,
917
+ status: check.status === 'pending' ? 'IN_PROGRESS' : 'COMPLETED',
918
+ conclusion: check.conclusion,
919
+ detailsUrl: check.detailsUrl,
920
+ startedAt: check.startedAt,
921
+ completedAt: check.completedAt,
922
+ }));
923
+ }
924
+
925
+ private async updateReviewState(
926
+ workspace: WorkspaceRecord,
927
+ status: WorkspaceGitHubSnapshot['status'],
928
+ ) {
929
+ if (status === 'open') {
930
+ await this.eventStore.setWorkspaceReviewState(workspace.id, 'in_review');
931
+ } else if (status === 'merged') {
932
+ await this.eventStore.setWorkspaceReviewState(workspace.id, 'done');
933
+ } else if (status === 'closed') {
934
+ await this.eventStore.setWorkspaceReviewState(workspace.id, 'closed');
935
+ }
936
+ }
937
+
938
+ private async setSnapshotFromPr(
939
+ workspace: WorkspaceRecord,
940
+ owner: string,
941
+ repo: string,
942
+ pr: GitHubPullRequestView | GitHubPullRequestSearchItem,
943
+ ) {
944
+ if (typeof pr.number !== 'number') {
945
+ const snapshot = createNoneSnapshot(owner, repo);
946
+ this.snapshots.set(workspace.id, snapshot);
947
+ return snapshot;
948
+ }
949
+
950
+ const detailed =
951
+ 'statusCheckRollup' in pr || 'comments' in pr || 'reviews' in pr || 'body' in pr
952
+ ? (pr as GitHubPullRequestView)
953
+ : await this.viewPr(owner, repo, pr.number);
954
+ const source = detailed ?? pr;
955
+ const status = normalizePrStatus(source.state, source.merged_at);
956
+ const checks = detailed ? mapChecks(detailed) : [];
957
+ const createdAt = parseGitHubTimestamp(sourceCreatedAt(source));
958
+
959
+ const snapshot: WorkspaceGitHubSnapshot = {
960
+ status,
961
+ owner,
962
+ repo,
963
+ prNumber: pr.number,
964
+ title: source.title,
965
+ body: detailed?.body,
966
+ url: sourceUrl(source),
967
+ headRefName: sourceHeadRef(source),
968
+ baseRefName: sourceBaseRef(source),
969
+ ciStatus: deriveCiStatus(checks),
970
+ isDraft: sourceIsDraft(source),
971
+ mergeStateStatus: detailed?.mergeStateStatus,
972
+ hasMergeConflicts: hasMergeConflicts(detailed?.mergeStateStatus),
973
+ unresolvedCommentCount: undefined,
974
+ additions: detailed?.additions,
975
+ deletions: detailed?.deletions,
976
+ files: detailed ? mapPrFiles(detailed.files) : [],
977
+ comments: detailed ? mapComments(detailed) : [],
978
+ checks,
979
+ createdAt,
980
+ lastRefreshedAt: Date.now(),
981
+ };
982
+
983
+ this.snapshots.set(workspace.id, snapshot);
984
+ if (status === 'open' || status === 'merged' || status === 'closed') {
985
+ await this.eventStore.observeWorkspacePullRequest(workspace.id, {
986
+ number: pr.number,
987
+ status,
988
+ title: source.title,
989
+ body: snapshot.body,
990
+ url: sourceUrl(source),
991
+ headRefName: sourceHeadRef(source),
992
+ baseRefName: sourceBaseRef(source),
993
+ ciStatus: snapshot.ciStatus,
994
+ isDraft: snapshot.isDraft,
995
+ mergeStateStatus: snapshot.mergeStateStatus,
996
+ hasMergeConflicts: snapshot.hasMergeConflicts,
997
+ unresolvedCommentCount: snapshot.unresolvedCommentCount,
998
+ additions: snapshot.additions,
999
+ deletions: snapshot.deletions,
1000
+ files: snapshot.files,
1001
+ comments: snapshot.comments,
1002
+ checks: snapshot.checks,
1003
+ createdAt,
1004
+ lastObservedAt: snapshot.lastRefreshedAt ?? Date.now(),
1005
+ });
1006
+ }
1007
+ await this.updateReviewState(workspace, status);
1008
+ return snapshot;
1009
+ }
1010
+
1011
+ async refreshWorkspacePrState(workspaceId: string) {
1012
+ let workspace: WorkspaceRecord;
1013
+ try {
1014
+ workspace = this.eventStore.requireWorkspace(workspaceId);
1015
+ } catch (error) {
1016
+ this.snapshots.delete(workspaceId);
1017
+ throw error;
1018
+ }
1019
+
1020
+ const directory = this.eventStore.requireDirectory(workspace.directoryId);
1021
+ const owner = directory.githubOwner;
1022
+ const repo = directory.githubRepo;
1023
+
1024
+ if (workspace.visibilityState === 'archived') {
1025
+ this.snapshots.delete(workspace.id);
1026
+ return createKnownPrSnapshot(workspace, owner, repo) ?? createNoneSnapshot(owner, repo);
1027
+ }
1028
+
1029
+ if (workspace.reviewState === 'done' || workspace.reviewState === 'closed') {
1030
+ const knownSnapshot = createKnownPrSnapshot(workspace, owner, repo);
1031
+ if (knownSnapshot) {
1032
+ this.snapshots.set(workspace.id, knownSnapshot);
1033
+ return knownSnapshot;
1034
+ }
1035
+ const snapshot = createNoneSnapshot(owner, repo);
1036
+ this.snapshots.set(workspace.id, snapshot);
1037
+ return snapshot;
1038
+ }
1039
+
1040
+ if (workspace.pullRequest?.number !== undefined) {
1041
+ let pr: GitHubPullRequestView | null;
1042
+ try {
1043
+ pr = await this.viewPr(owner, repo, workspace.pullRequest.number);
1044
+ if (!pr) throw new Error('GitHub PR view returned invalid JSON');
1045
+ } catch (error) {
1046
+ if (error instanceof GitHubRateLimitError) throw error;
1047
+ const knownSnapshot = createKnownPrSnapshot(workspace, owner, repo);
1048
+ if (knownSnapshot) {
1049
+ this.snapshots.set(workspace.id, knownSnapshot);
1050
+ return knownSnapshot;
1051
+ }
1052
+ throw new Error('GitHub PR view failed');
1053
+ }
1054
+ return this.setSnapshotFromPr(workspace, owner, repo, pr);
1055
+ }
1056
+
1057
+ const pr = await this.findPrForBranch(owner, repo, workspace.branchName);
1058
+ if (pr) return this.setSnapshotFromPr(workspace, owner, repo, pr);
1059
+
1060
+ const snapshot = createNoneSnapshot(owner, repo);
1061
+ this.snapshots.set(workspace.id, snapshot);
1062
+ return snapshot;
1063
+ }
1064
+
1065
+ async refreshActiveWorkspaces() {
1066
+ const results = new Map<string, WorkspaceGitHubSnapshot>();
1067
+ for (const workspace of this.eventStore.listWorkspaces()) {
1068
+ if (workspace.visibilityState !== 'active') continue;
1069
+ if (workspace.reviewState === 'done' || workspace.reviewState === 'closed') continue;
1070
+ results.set(workspace.id, await this.refreshWorkspacePrState(workspace.id));
1071
+ }
1072
+ return results;
1073
+ }
1074
+
1075
+ async refreshOpenPullRequests() {
1076
+ const results = new Map<string, WorkspaceGitHubSnapshot>();
1077
+ for (const workspace of this.eventStore.listWorkspaces()) {
1078
+ if (workspace.visibilityState !== 'active') continue;
1079
+ if (workspace.reviewState !== 'in_review') continue;
1080
+ results.set(workspace.id, await this.refreshWorkspacePrState(workspace.id));
1081
+ }
1082
+ return results;
1083
+ }
1084
+
1085
+ async fetchFailingCheckLogs(workspaceId: string) {
1086
+ const workspace = this.eventStore.requireWorkspace(workspaceId);
1087
+ const directory = this.eventStore.requireDirectory(workspace.directoryId);
1088
+ const prNumber = workspace.pullRequest?.number;
1089
+ if (prNumber === undefined) throw new Error('Workspace does not have a pull request');
1090
+
1091
+ const result = await this.runGh([
1092
+ 'run',
1093
+ 'list',
1094
+ '--repo',
1095
+ `${directory.githubOwner}/${directory.githubRepo}`,
1096
+ '--branch',
1097
+ workspace.branchName,
1098
+ '--limit',
1099
+ '10',
1100
+ '--json',
1101
+ 'databaseId,conclusion,status,displayTitle,workflowName,url',
1102
+ ]);
1103
+
1104
+ if (result.exitCode !== 0) {
1105
+ throw new Error([result.stderr.trim(), result.stdout.trim()].filter(Boolean).join('\n'));
1106
+ }
1107
+
1108
+ const runs =
1109
+ parseJson<
1110
+ Array<{
1111
+ databaseId?: number;
1112
+ conclusion?: string;
1113
+ status?: string;
1114
+ displayTitle?: string;
1115
+ workflowName?: string;
1116
+ url?: string;
1117
+ }>
1118
+ >(result.stdout) ?? [];
1119
+
1120
+ const failingRuns = runs.filter((run) => {
1121
+ const conclusion = run.conclusion?.toLowerCase();
1122
+ return (
1123
+ (conclusion === 'failure' || conclusion === 'cancelled' || conclusion === 'timed_out') &&
1124
+ run.databaseId
1125
+ );
1126
+ });
1127
+
1128
+ const logs: Array<{
1129
+ runId: number;
1130
+ title?: string;
1131
+ workflowName?: string;
1132
+ url?: string;
1133
+ log: string;
1134
+ }> = [];
1135
+
1136
+ for (const run of failingRuns) {
1137
+ const logResult = await this.runGh([
1138
+ 'run',
1139
+ 'view',
1140
+ String(run.databaseId),
1141
+ '--repo',
1142
+ `${directory.githubOwner}/${directory.githubRepo}`,
1143
+ '--log-failed',
1144
+ ]);
1145
+
1146
+ logs.push({
1147
+ runId: run.databaseId as number,
1148
+ title: run.displayTitle,
1149
+ workflowName: run.workflowName,
1150
+ url: run.url,
1151
+ log: [logResult.stdout.trim(), logResult.stderr.trim()].filter(Boolean).join('\n'),
1152
+ });
1153
+ }
1154
+
1155
+ return logs;
1156
+ }
1157
+
1158
+ async mergeWorkspacePullRequest(workspaceId: string) {
1159
+ const workspace = this.eventStore.requireWorkspace(workspaceId);
1160
+ const directory = this.eventStore.requireDirectory(workspace.directoryId);
1161
+ const prNumber = workspace.pullRequest?.number;
1162
+ if (prNumber === undefined) throw new Error('Workspace does not have a pull request');
1163
+
1164
+ // v1 uses GitHub merge commits only; expose strategy later if repo policy requires it.
1165
+ const result = await this.runGh([
1166
+ 'pr',
1167
+ 'merge',
1168
+ String(prNumber),
1169
+ '--repo',
1170
+ `${directory.githubOwner}/${directory.githubRepo}`,
1171
+ '--merge',
1172
+ ]);
1173
+
1174
+ if (result.exitCode !== 0) {
1175
+ throw new Error([result.stderr.trim(), result.stdout.trim()].filter(Boolean).join('\n'));
1176
+ }
1177
+
1178
+ return this.refreshWorkspacePrState(workspaceId);
1179
+ }
1180
+
1181
+ async markWorkspacePullRequestReady(workspaceId: string) {
1182
+ const workspace = this.eventStore.requireWorkspace(workspaceId);
1183
+ const directory = this.eventStore.requireDirectory(workspace.directoryId);
1184
+ const prNumber = workspace.pullRequest?.number;
1185
+ if (prNumber === undefined) throw new Error('Workspace does not have a pull request');
1186
+ if (workspace.pullRequest?.isDraft !== true) {
1187
+ throw new Error('Workspace pull request is not a draft');
1188
+ }
1189
+
1190
+ const result = await this.runGh([
1191
+ 'pr',
1192
+ 'ready',
1193
+ String(prNumber),
1194
+ '--repo',
1195
+ `${directory.githubOwner}/${directory.githubRepo}`,
1196
+ ]);
1197
+
1198
+ if (result.exitCode !== 0) {
1199
+ throw new Error([result.stderr.trim(), result.stdout.trim()].filter(Boolean).join('\n'));
1200
+ }
1201
+
1202
+ return this.refreshWorkspacePrState(workspaceId);
1203
+ }
1204
+ }