local-review 0.0.1 → 0.3.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 (372) hide show
  1. package/AGENT-GUIDE.md +468 -0
  2. package/CHANGELOG.md +67 -0
  3. package/LICENSE +21 -0
  4. package/README.md +126 -2
  5. package/dist/demo/lib/gitworld.js +224 -0
  6. package/dist/demo/world/api.js +2436 -0
  7. package/dist/demo/world/infra.js +189 -0
  8. package/dist/demo/world/people.js +16 -0
  9. package/dist/demo/world/review.js +578 -0
  10. package/dist/demo/world/sdk.js +376 -0
  11. package/dist/demo/world/web.js +657 -0
  12. package/dist/server/anchors.js +207 -0
  13. package/dist/server/assets.js +147 -0
  14. package/dist/server/backups.js +212 -0
  15. package/dist/server/branches.js +113 -0
  16. package/dist/server/cli.js +283 -0
  17. package/dist/server/commits.js +393 -0
  18. package/dist/server/config.js +62 -0
  19. package/dist/server/context.js +68 -0
  20. package/dist/server/events.js +87 -0
  21. package/dist/server/examples.js +204 -0
  22. package/dist/server/git.js +103 -0
  23. package/dist/server/index.js +581 -0
  24. package/dist/server/landed.js +71 -0
  25. package/dist/server/plan.js +260 -0
  26. package/dist/server/port.js +3 -0
  27. package/dist/server/projectEdit.js +117 -0
  28. package/dist/server/projects.js +321 -0
  29. package/dist/server/prs.js +314 -0
  30. package/dist/server/reviewer.js +33 -0
  31. package/dist/server/reviews.js +586 -0
  32. package/dist/server/safefs.js +92 -0
  33. package/dist/server/security.js +38 -0
  34. package/dist/server/stackviz.js +446 -0
  35. package/dist/shared/assets.js +54 -0
  36. package/dist/shared/paths.js +13 -0
  37. package/dist/shared/prText.js +22 -0
  38. package/dist/shared/readiness.js +37 -0
  39. package/dist/shared/repoLabel.js +13 -0
  40. package/dist/shared/totals.js +75 -0
  41. package/dist/web/assets/abap-CLvhMVsD.js +1 -0
  42. package/dist/web/assets/actionscript-3-Cs6JJXT5.js +1 -0
  43. package/dist/web/assets/ada-X3Y2mlq2.js +1 -0
  44. package/dist/web/assets/ahk-DwADkxyd.js +1 -0
  45. package/dist/web/assets/ahk2-_6rzW9BS.js +1 -0
  46. package/dist/web/assets/andromeeda-vGVdxbeo.js +1 -0
  47. package/dist/web/assets/angular-html-qFVZP7wG.js +1 -0
  48. package/dist/web/assets/angular-ts-DzOxtaC9.js +1 -0
  49. package/dist/web/assets/apache-U0d_L8uA.js +1 -0
  50. package/dist/web/assets/apex-KFGFyKwG.js +1 -0
  51. package/dist/web/assets/apl-0AThSj8r.js +1 -0
  52. package/dist/web/assets/apple-touch-icon-BNrSfFwW.png +0 -0
  53. package/dist/web/assets/applescript-CCn79oCD.js +1 -0
  54. package/dist/web/assets/ara-4CJ0cIlV.js +1 -0
  55. package/dist/web/assets/asciidoc-SCjQUq34.js +1 -0
  56. package/dist/web/assets/asm-Cmm7eHzH.js +1 -0
  57. package/dist/web/assets/astro-By53bk_I.js +1 -0
  58. package/dist/web/assets/aurora-x-CDeNXAV0.js +1 -0
  59. package/dist/web/assets/awk-BWXHIvNe.js +1 -0
  60. package/dist/web/assets/ayu-dark-DluEY0Gj.js +1 -0
  61. package/dist/web/assets/ayu-light-C3h-C4tm.js +1 -0
  62. package/dist/web/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  63. package/dist/web/assets/ballerina-B7ZEbQpA.js +1 -0
  64. package/dist/web/assets/bat-qdxAdBWQ.js +1 -0
  65. package/dist/web/assets/beancount-C2IN4Vk8.js +1 -0
  66. package/dist/web/assets/berry-DKpUyyne.js +1 -0
  67. package/dist/web/assets/bibtex-Ci_nEsc7.js +1 -0
  68. package/dist/web/assets/bicep-CUHmPFLl.js +1 -0
  69. package/dist/web/assets/bird2-CYIUgYqB.js +1 -0
  70. package/dist/web/assets/blade-Dw_Nglq9.js +1 -0
  71. package/dist/web/assets/bsl-_VjlB9mv.js +1 -0
  72. package/dist/web/assets/c-CQT-cQHo.js +1 -0
  73. package/dist/web/assets/c3-D0apG41V.js +1 -0
  74. package/dist/web/assets/cadence-CQ2zXKGN.js +1 -0
  75. package/dist/web/assets/cairo-DLTphjLi.js +1 -0
  76. package/dist/web/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
  77. package/dist/web/assets/catppuccin-latte-CtsyrtPq.js +1 -0
  78. package/dist/web/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
  79. package/dist/web/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
  80. package/dist/web/assets/chapel-64o3Hib1.js +1 -0
  81. package/dist/web/assets/clarity-CtfBBspg.js +1 -0
  82. package/dist/web/assets/clojure-DqKBuwfJ.js +1 -0
  83. package/dist/web/assets/cmake-Bj61d0ZC.js +1 -0
  84. package/dist/web/assets/cobol-COyB9oJ9.js +1 -0
  85. package/dist/web/assets/codeowners-C8r90Shi.js +1 -0
  86. package/dist/web/assets/codeql-oeQT6MSM.js +1 -0
  87. package/dist/web/assets/coffee-BGIfDQTv.js +1 -0
  88. package/dist/web/assets/common-lisp-Cv5bFMCO.js +1 -0
  89. package/dist/web/assets/coq-CGaSfhwR.js +1 -0
  90. package/dist/web/assets/cpp-wi7ixCRa.js +1 -0
  91. package/dist/web/assets/crystal-ADRCp2kP.js +1 -0
  92. package/dist/web/assets/csharp-dmMxLVLW.js +1 -0
  93. package/dist/web/assets/css-BH9tz7qt.js +1 -0
  94. package/dist/web/assets/csv-Dx-8-gkx.js +1 -0
  95. package/dist/web/assets/cue-CE9AQfxI.js +1 -0
  96. package/dist/web/assets/cypher-ClKdZ_lG.js +1 -0
  97. package/dist/web/assets/d-qD-0Kul2.js +1 -0
  98. package/dist/web/assets/dark-plus-Cs2F2srj.js +1 -0
  99. package/dist/web/assets/dart-CHZY9Xhz.js +1 -0
  100. package/dist/web/assets/dax-BkyTk9wS.js +1 -0
  101. package/dist/web/assets/desktop-Dlh5hvp9.js +1 -0
  102. package/dist/web/assets/diff-woXpYk--.js +1 -0
  103. package/dist/web/assets/docker-IyjqRm3v.js +1 -0
  104. package/dist/web/assets/dotenv-CuH5my32.js +1 -0
  105. package/dist/web/assets/dracula-BHWKrbxM.js +1 -0
  106. package/dist/web/assets/dracula-soft-5eyTD99u.js +1 -0
  107. package/dist/web/assets/dream-maker-DW3nJb8Q.js +1 -0
  108. package/dist/web/assets/edge-DFzphiQm.js +1 -0
  109. package/dist/web/assets/elixir-BUj3AoF_.js +1 -0
  110. package/dist/web/assets/elm-Bqgz9ryu.js +1 -0
  111. package/dist/web/assets/emacs-lisp-DR2vtWaj.js +1 -0
  112. package/dist/web/assets/erb-DHHWfqsN.js +1 -0
  113. package/dist/web/assets/erlang-Cphh6RMH.js +1 -0
  114. package/dist/web/assets/everforest-dark-sB-x3p7T.js +1 -0
  115. package/dist/web/assets/everforest-light-Df2xbC6M.js +1 -0
  116. package/dist/web/assets/favicon-Bld6z4jR.svg +13 -0
  117. package/dist/web/assets/favicon-CDTyigy2.ico +0 -0
  118. package/dist/web/assets/fennel-DQxkIbk2.js +1 -0
  119. package/dist/web/assets/fish-BJitypiv.js +1 -0
  120. package/dist/web/assets/fluent-C03EYrpw.js +1 -0
  121. package/dist/web/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  122. package/dist/web/assets/fortran-free-form-CYNrtFtB.js +1 -0
  123. package/dist/web/assets/fsharp-D13ZGOAj.js +1 -0
  124. package/dist/web/assets/gdresource-xMC7jr7x.js +1 -0
  125. package/dist/web/assets/gdscript-DYvRD3hg.js +1 -0
  126. package/dist/web/assets/gdshader-CBce3t8t.js +1 -0
  127. package/dist/web/assets/genie-CV2tkWYe.js +1 -0
  128. package/dist/web/assets/gherkin-DExj1W_8.js +1 -0
  129. package/dist/web/assets/git-commit-BSykSTBG.js +1 -0
  130. package/dist/web/assets/git-rebase-C7iLD81B.js +1 -0
  131. package/dist/web/assets/github-dark-C-LZuMrd.js +1 -0
  132. package/dist/web/assets/github-dark-default-DXG-b-1a.js +1 -0
  133. package/dist/web/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  134. package/dist/web/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  135. package/dist/web/assets/github-light-EUqPIrTm.js +1 -0
  136. package/dist/web/assets/github-light-default-BXViO-2h.js +1 -0
  137. package/dist/web/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  138. package/dist/web/assets/gleam-CSRkHgEL.js +1 -0
  139. package/dist/web/assets/glimmer-js-BT_UjuD4.js +1 -0
  140. package/dist/web/assets/glimmer-ts-BpeqXZCf.js +1 -0
  141. package/dist/web/assets/glsl-mPoRKPQX.js +1 -0
  142. package/dist/web/assets/gn-CAXYAgV-.js +1 -0
  143. package/dist/web/assets/gnuplot-7GGW24-e.js +1 -0
  144. package/dist/web/assets/go-rLFTqkRN.js +1 -0
  145. package/dist/web/assets/graphql-D5e-luGt.js +1 -0
  146. package/dist/web/assets/groovy-CacY0gHj.js +1 -0
  147. package/dist/web/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  148. package/dist/web/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  149. package/dist/web/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  150. package/dist/web/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  151. package/dist/web/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  152. package/dist/web/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  153. package/dist/web/assets/hack-Yj_0xAq9.js +1 -0
  154. package/dist/web/assets/haml-rCEvyqvs.js +1 -0
  155. package/dist/web/assets/handlebars-CZRXN7yP.js +1 -0
  156. package/dist/web/assets/haskell-D8IpX4py.js +1 -0
  157. package/dist/web/assets/haxe-C8oCnwJJ.js +1 -0
  158. package/dist/web/assets/hcl-Dh228itO.js +1 -0
  159. package/dist/web/assets/hjson-CxZEssPk.js +1 -0
  160. package/dist/web/assets/hlsl-Cvrh5tZx.js +1 -0
  161. package/dist/web/assets/horizon-CE9ld1lL.js +1 -0
  162. package/dist/web/assets/horizon-bright-DSNQnXHK.js +1 -0
  163. package/dist/web/assets/houston-CsvMBhTu.js +1 -0
  164. package/dist/web/assets/html-XqiqNW99.js +1 -0
  165. package/dist/web/assets/html-derivative-nWC0aMV7.js +1 -0
  166. package/dist/web/assets/http-C4fFBmNU.js +1 -0
  167. package/dist/web/assets/hurl-DjZ_NyWj.js +1 -0
  168. package/dist/web/assets/hxml-Dcm88srW.js +1 -0
  169. package/dist/web/assets/hy-CZbG8q4J.js +1 -0
  170. package/dist/web/assets/imba-DsUTQ-LC.js +1 -0
  171. package/dist/web/assets/index-BPfnOrNh.js +451 -0
  172. package/dist/web/assets/index-BplAAg-1.css +1 -0
  173. package/dist/web/assets/ini-B5eOa1yu.js +1 -0
  174. package/dist/web/assets/java-B0zZG6CJ.js +1 -0
  175. package/dist/web/assets/javascript-CFawPmAZ.js +1 -0
  176. package/dist/web/assets/jinja-qu2nA6S7.js +1 -0
  177. package/dist/web/assets/jison-DviWniCr.js +1 -0
  178. package/dist/web/assets/json-C0vF7y3r.js +1 -0
  179. package/dist/web/assets/json5-BR5RXkoi.js +1 -0
  180. package/dist/web/assets/jsonc-CYpm1nAK.js +1 -0
  181. package/dist/web/assets/jsonl-CmCQp5Yx.js +1 -0
  182. package/dist/web/assets/jsonnet-CJTPZ8u_.js +1 -0
  183. package/dist/web/assets/jssm-DXw9l8Rf.js +1 -0
  184. package/dist/web/assets/jsx-DQYIrdZV.js +1 -0
  185. package/dist/web/assets/julia-C_b0WNzR.js +1 -0
  186. package/dist/web/assets/just-NwM_1ruS.js +1 -0
  187. package/dist/web/assets/kanagawa-dragon-Bozd7klA.js +1 -0
  188. package/dist/web/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
  189. package/dist/web/assets/kanagawa-wave-BML5mF2J.js +1 -0
  190. package/dist/web/assets/kdl-CsD5j6eV.js +1 -0
  191. package/dist/web/assets/kotlin-DhhofPvG.js +1 -0
  192. package/dist/web/assets/kusto-C7mF5XQf.js +1 -0
  193. package/dist/web/assets/laserwave-C_8bwKvT.js +1 -0
  194. package/dist/web/assets/latex-As2zLFN8.js +1 -0
  195. package/dist/web/assets/lean-CewbzKMR.js +1 -0
  196. package/dist/web/assets/less-DVTAwKKz.js +1 -0
  197. package/dist/web/assets/light-plus-DVQuIRkW.js +1 -0
  198. package/dist/web/assets/liquid-By8qNhnm.js +1 -0
  199. package/dist/web/assets/llvm-DqHOMS-s.js +1 -0
  200. package/dist/web/assets/log-BNLmms1o.js +1 -0
  201. package/dist/web/assets/logo-Cluzi2Zq.js +1 -0
  202. package/dist/web/assets/lua-Dairqw_c.js +1 -0
  203. package/dist/web/assets/luau-VGWVLoqX.js +1 -0
  204. package/dist/web/assets/make-Dixweg8N.js +1 -0
  205. package/dist/web/assets/markdown-BYOwaDjH.js +1 -0
  206. package/dist/web/assets/marko-C2UPN-L_.js +1 -0
  207. package/dist/web/assets/material-theme-Bm3Qr25_.js +1 -0
  208. package/dist/web/assets/material-theme-darker-2IIEA8gg.js +1 -0
  209. package/dist/web/assets/material-theme-lighter-uhdI0v04.js +1 -0
  210. package/dist/web/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  211. package/dist/web/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  212. package/dist/web/assets/matlab-D7qyCx1q.js +1 -0
  213. package/dist/web/assets/mdc-Eijzzwck.js +1 -0
  214. package/dist/web/assets/mdx-DQZ5AkYe.js +1 -0
  215. package/dist/web/assets/mermaid-BxpfTIzt.js +1 -0
  216. package/dist/web/assets/min-dark-BSWPekZh.js +1 -0
  217. package/dist/web/assets/min-light-DDpmG2fV.js +1 -0
  218. package/dist/web/assets/mipsasm-BMqwQI7S.js +1 -0
  219. package/dist/web/assets/mojo-fg40_ntQ.js +1 -0
  220. package/dist/web/assets/monokai-BscAjiQZ.js +1 -0
  221. package/dist/web/assets/moonbit-D9W784E_.js +1 -0
  222. package/dist/web/assets/move-BYnGhZoM.js +1 -0
  223. package/dist/web/assets/narrat-_X_XdTYD.js +1 -0
  224. package/dist/web/assets/nextflow-CSgcYZcx.js +1 -0
  225. package/dist/web/assets/nextflow-groovy-D7IRt6Pp.js +1 -0
  226. package/dist/web/assets/nginx-BF8eLz9n.js +1 -0
  227. package/dist/web/assets/night-owl-DhmEMT88.js +1 -0
  228. package/dist/web/assets/night-owl-light-eJ-hLW7d.js +1 -0
  229. package/dist/web/assets/nim-BlPfC-Lu.js +1 -0
  230. package/dist/web/assets/nix-IvuFDN5E.js +1 -0
  231. package/dist/web/assets/nord-Cb4Vim4T.js +1 -0
  232. package/dist/web/assets/nsis-CjNLYCLg.js +1 -0
  233. package/dist/web/assets/nushell--0fT3PI1.js +1 -0
  234. package/dist/web/assets/objective-c-D1A_Heim.js +1 -0
  235. package/dist/web/assets/objective-cpp-BsSzOQcm.js +1 -0
  236. package/dist/web/assets/ocaml-O90oeIOV.js +1 -0
  237. package/dist/web/assets/odin-B1RWQWA5.js +1 -0
  238. package/dist/web/assets/one-dark-pro-C6YyR8OD.js +1 -0
  239. package/dist/web/assets/one-light-D7Lr4KcI.js +1 -0
  240. package/dist/web/assets/openscad-BUDT5pXO.js +1 -0
  241. package/dist/web/assets/org-jZgCqYxI.js +1 -0
  242. package/dist/web/assets/pascal-4ZHwLPI5.js +1 -0
  243. package/dist/web/assets/perl-BIZHUlMC.js +1 -0
  244. package/dist/web/assets/php-Bu0o-4mH.js +1 -0
  245. package/dist/web/assets/pierre-dark-CpLgRqie.js +1 -0
  246. package/dist/web/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  247. package/dist/web/assets/pierre-dark-soft-kZQmAZld.js +1 -0
  248. package/dist/web/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  249. package/dist/web/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
  250. package/dist/web/assets/pierre-light-CoaEpmwp.js +1 -0
  251. package/dist/web/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  252. package/dist/web/assets/pierre-light-soft-lWLdNTOI.js +1 -0
  253. package/dist/web/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  254. package/dist/web/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
  255. package/dist/web/assets/pkl-ot-7Btpt.js +1 -0
  256. package/dist/web/assets/plastic-DQwYfKfQ.js +1 -0
  257. package/dist/web/assets/plsql-DGHpHOYJ.js +1 -0
  258. package/dist/web/assets/po-BiJDBrnU.js +1 -0
  259. package/dist/web/assets/poimandres-DRFjx7u4.js +1 -0
  260. package/dist/web/assets/polar-C7UOKdEL.js +1 -0
  261. package/dist/web/assets/postcss-BXeXVLqQ.js +1 -0
  262. package/dist/web/assets/powerquery-DNMTfnFr.js +1 -0
  263. package/dist/web/assets/powershell-QtSnmXcu.js +1 -0
  264. package/dist/web/assets/prisma-DMgNhnYS.js +1 -0
  265. package/dist/web/assets/prolog-iXnhIJG7.js +1 -0
  266. package/dist/web/assets/proto-DB4EqR-F.js +1 -0
  267. package/dist/web/assets/pug-Benj2kck.js +1 -0
  268. package/dist/web/assets/puppet-CDv2pdJW.js +1 -0
  269. package/dist/web/assets/purescript-9MfHhQsQ.js +1 -0
  270. package/dist/web/assets/python-gzcpVVnB.js +1 -0
  271. package/dist/web/assets/qml-BHSeoMrW.js +1 -0
  272. package/dist/web/assets/qmldir-DCQb3MpD.js +1 -0
  273. package/dist/web/assets/qss-Fe1Jh2GI.js +1 -0
  274. package/dist/web/assets/r-CUvEVkuI.js +1 -0
  275. package/dist/web/assets/racket-DcIDlBhZ.js +1 -0
  276. package/dist/web/assets/raku-B3gFvitq.js +1 -0
  277. package/dist/web/assets/razor-BwPBMIxs.js +1 -0
  278. package/dist/web/assets/rbs-C0MpOrj4.js +1 -0
  279. package/dist/web/assets/red-CYlAq2b0.js +1 -0
  280. package/dist/web/assets/reg-CRGYupPL.js +1 -0
  281. package/dist/web/assets/regexp-_ifpjSOj.js +1 -0
  282. package/dist/web/assets/rel-BtDbiS_P.js +1 -0
  283. package/dist/web/assets/riscv-Ckw8ddFX.js +1 -0
  284. package/dist/web/assets/ron-VUp2lXgN.js +1 -0
  285. package/dist/web/assets/rose-pine-aMsaMD1D.js +1 -0
  286. package/dist/web/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
  287. package/dist/web/assets/rose-pine-moon-DO-qXg-I.js +1 -0
  288. package/dist/web/assets/rosmsg-CAekHB0j.js +1 -0
  289. package/dist/web/assets/rst-Es4SE5IJ.js +1 -0
  290. package/dist/web/assets/ruby-76YJjt1j.js +1 -0
  291. package/dist/web/assets/rust-DpiIf5JO.js +1 -0
  292. package/dist/web/assets/sas-BEWDsPnW.js +1 -0
  293. package/dist/web/assets/sass-DXrisJhu.js +1 -0
  294. package/dist/web/assets/scala-BiFt_7mu.js +1 -0
  295. package/dist/web/assets/scheme-DQCgrYNe.js +1 -0
  296. package/dist/web/assets/scss-DgcF5VZK.js +1 -0
  297. package/dist/web/assets/sdbl-bTVj8UrX.js +1 -0
  298. package/dist/web/assets/shaderlab-TOUzSsQk.js +1 -0
  299. package/dist/web/assets/shellscript-Cty74sqM.js +1 -0
  300. package/dist/web/assets/shellsession-BCfSy2gp.js +1 -0
  301. package/dist/web/assets/slack-dark-DnToyrRv.js +1 -0
  302. package/dist/web/assets/slack-ochin-B2OO5cIa.js +1 -0
  303. package/dist/web/assets/smalltalk-BHpQb5nM.js +1 -0
  304. package/dist/web/assets/smithy-Dgaz7aur.js +1 -0
  305. package/dist/web/assets/snazzy-light-4G7pJPwS.js +1 -0
  306. package/dist/web/assets/solarized-dark-b97czJOk.js +1 -0
  307. package/dist/web/assets/solarized-light-DFPxpIYQ.js +1 -0
  308. package/dist/web/assets/solidity-Bj7E1mRn.js +1 -0
  309. package/dist/web/assets/soy-dM_k5IdG.js +1 -0
  310. package/dist/web/assets/sparql-D_iOobhT.js +1 -0
  311. package/dist/web/assets/splunk-BC2Px7Mm.js +1 -0
  312. package/dist/web/assets/sql-Dr4UDgPO.js +1 -0
  313. package/dist/web/assets/ssh-config-BgfXC-Er.js +1 -0
  314. package/dist/web/assets/stata-COCyw4rB.js +1 -0
  315. package/dist/web/assets/stylus-B6D30XZt.js +1 -0
  316. package/dist/web/assets/surrealql-Cc-YKbr5.js +1 -0
  317. package/dist/web/assets/svelte-BBNgvYF5.js +1 -0
  318. package/dist/web/assets/swift-CyEgAFGc.js +1 -0
  319. package/dist/web/assets/synthwave-84-nFMaYfgc.js +1 -0
  320. package/dist/web/assets/system-verilog-Dh5ihWbf.js +1 -0
  321. package/dist/web/assets/systemd-BxMlprV5.js +1 -0
  322. package/dist/web/assets/talonscript-CohzipZa.js +1 -0
  323. package/dist/web/assets/tasl-DMoTqEGO.js +1 -0
  324. package/dist/web/assets/tcl-CZd0xW_V.js +1 -0
  325. package/dist/web/assets/templ-D8WXh8mG.js +1 -0
  326. package/dist/web/assets/terraform-DswuEJGm.js +1 -0
  327. package/dist/web/assets/tex-BNsfM6-w.js +1 -0
  328. package/dist/web/assets/tokyo-night-oM2G3aXe.js +1 -0
  329. package/dist/web/assets/toml-CcmNWLt0.js +1 -0
  330. package/dist/web/assets/ts-tags-C5cp5d5e.js +1 -0
  331. package/dist/web/assets/tsv-sltzmVWM.js +1 -0
  332. package/dist/web/assets/tsx-D0nBgoOJ.js +1 -0
  333. package/dist/web/assets/turtle-ByJddavk.js +1 -0
  334. package/dist/web/assets/twig-Clef_vTO.js +1 -0
  335. package/dist/web/assets/typescript-84qFVDd8.js +1 -0
  336. package/dist/web/assets/typespec-BGhR2SqG.js +1 -0
  337. package/dist/web/assets/typst-kHrpsAej.js +1 -0
  338. package/dist/web/assets/v-DBj4K9d_.js +1 -0
  339. package/dist/web/assets/vala-zf12oZj6.js +1 -0
  340. package/dist/web/assets/vb-DOwzaHZ0.js +1 -0
  341. package/dist/web/assets/verilog-B2gwz-sJ.js +1 -0
  342. package/dist/web/assets/vesper-DdrHHSXu.js +1 -0
  343. package/dist/web/assets/vhdl-BroJfC0k.js +1 -0
  344. package/dist/web/assets/viml-DvXPmvsu.js +1 -0
  345. package/dist/web/assets/vitesse-black-fwtXNY1n.js +1 -0
  346. package/dist/web/assets/vitesse-dark-BZCL-v6S.js +1 -0
  347. package/dist/web/assets/vitesse-light-VbXTXTou.js +1 -0
  348. package/dist/web/assets/vue-D_k3LQNp.js +1 -0
  349. package/dist/web/assets/vue-html-Bclb5q-s.js +1 -0
  350. package/dist/web/assets/vue-vine-BvY0ORZK.js +1 -0
  351. package/dist/web/assets/vyper-CgoNMtux.js +1 -0
  352. package/dist/web/assets/wasm-BnjxR4X6.js +1 -0
  353. package/dist/web/assets/wasm-ByWQv1Qj.js +1 -0
  354. package/dist/web/assets/wenyan-C8pVoKbM.js +1 -0
  355. package/dist/web/assets/wgsl-BsKzXJz4.js +1 -0
  356. package/dist/web/assets/wikitext-ClFFjSW2.js +1 -0
  357. package/dist/web/assets/wit-DdvCle-K.js +1 -0
  358. package/dist/web/assets/wolfram-DLL8P-h_.js +1 -0
  359. package/dist/web/assets/xml-BgfQ1YbS.js +1 -0
  360. package/dist/web/assets/xsl-DEqH3rje.js +1 -0
  361. package/dist/web/assets/yaml-BiWwl5JE.js +1 -0
  362. package/dist/web/assets/zenscript-BnlCZFoB.js +1 -0
  363. package/dist/web/assets/zig-CMLA9XwU.js +1 -0
  364. package/dist/web/index.html +16 -0
  365. package/examples/project.example.yaml +40 -0
  366. package/package.json +90 -3
  367. package/server/fonts/Inter-OFL.txt +93 -0
  368. package/server/fonts/Inter_400Regular.ttf +0 -0
  369. package/server/fonts/Inter_600SemiBold.ttf +0 -0
  370. package/server/fonts/Inter_700Bold.ttf +0 -0
  371. package/server/fonts/JetBrainsMono-OFL.txt +93 -0
  372. package/server/fonts/JetBrainsMono_400Regular.ttf +0 -0
package/AGENT-GUIDE.md ADDED
@@ -0,0 +1,468 @@
1
+ # Driving Local Review: a guide for agents
2
+
3
+ **This is for agents working on OTHER repos** (e.g. `~/code/my-service`) that use Local Review to get a stack of
4
+ commits reviewed and then published as GitHub stacked PRs. It is not about developing Local Review itself (for that,
5
+ see [AGENTS.md](https://github.com/edspencer/local-review/blob/main/AGENTS.md) in its repo). `npx local-review guide`
6
+ prints this guide.
7
+
8
+ Commands below are `local-review …`: run them as `npx local-review …` unless it's installed globally
9
+ (`npm i -g local-review`). The URLs assume the default port, 5622; if the reviewer runs it with `--port`, use
10
+ theirs. The reference docs (file schemas, the HTTP API, every edge case) are at
11
+ https://local-review.edspencer.net; this guide links the pages it relies on.
12
+
13
+ ## What Local Review is
14
+
15
+ A local, GitHub-PR-style review UI for stacks of commits. Each commit is one candidate PR. The reviewer (the human you're working for) reviews them at
16
+ **http://localhost:5622/&lt;project&gt;/&lt;repo&gt;/&lt;sha&gt;** (short or full sha; `/<project>` is the project homepage).
17
+ Local Review never talks to GitHub and never modifies the repos it shows. You do the git and GitHub work, and record
18
+ the results in its files.
19
+
20
+ Everything lives in plain YAML under `~/.local-review/projects/<slug>/` (or `$LOCAL_REVIEW_HOME/projects/<slug>/`):
21
+
22
+ ```
23
+ project.yaml repos, commit ranges, PR stacks, intro and stack notes
24
+ reviews/<repo>/<full-sha>.yaml the reviewer's threads and your replies (schema: the docs site's "Comment files schema")
25
+ prs/<repo>/<full-sha>.yaml PR title/body overrides, status (draft / ready), branch, github, landed
26
+ assets/<hash>.<ext> images shown in descriptions, stack notes and the intro (`local-review asset add`)
27
+ ```
28
+
29
+ `local-review init <slug>` creates a new project's `project.yaml` from a commented example (it never replaces one).
30
+ The server re-reads these files on every request and pushes changes to the browser, so edits show up live. When the
31
+ server is running, prefer its API (below): its writes are atomic and are merged with concurrent edits.
32
+
33
+ ## The data model
34
+
35
+ **`project.yaml`.** It lists `repos`, each with a `path`, a `branch` (the range end) and a `base` (the range starts
36
+ at `merge-base(base, branch)`). Each repo has `stacks`, consecutive groups of commits, each with a `title`, a
37
+ `starts_at` selector and an optional `description` (the stack note). The top-level `description` is the project
38
+ intro. `starts_at` is a commit **subject** (or a sha prefix): a stack runs from that commit up to the next stack's
39
+ start. A stack may have `follow_on: true`: parked work for later (below). After merges, a repo also has
40
+ `landed:`, the keys of its merged PRs' prs files, bottom to top ("After merges"), and its `branch` becomes optional.
41
+ The full schema: [project.yaml](https://local-review.edspencer.net/reference/project-yaml/).
42
+
43
+ **Follow-on stacks** are parked work: PRs to get to later, not part of the stacks going up now. They're
44
+ reviewed like any other, keep their PR numbers, and sit at the end of the repo's `stacks:`. **Don't publish or
45
+ materialize them as part of a stack barrage unless the reviewer asks.** There's nothing else to do with them: no promote
46
+ step. When the reviewer wants one to go up, they (or you, when asked) remove the `follow_on:` line. They're always **yellow**
47
+ (any `color:` on them is ignored), and yellow is reserved for them: regular stacks use blue, green, purple, orange,
48
+ pink, teal, red or a `#hex` (`color: yellow` on a regular stack is a warning). Totals everywhere (stack picture,
49
+ homepage, sidebar, project list) leave follow-on stacks out and mention them beside the numbers
50
+ (`+1 follow-on stack (2 PRs)`); the API's `GET /api/projects` gives `commitCount` without them and `followOnCount`.
51
+
52
+ **`reviews/<repo>/<sha>.yaml`.** Its `threads[]` each have an `id`, `resolved`, optional `path`/`side`/`line`, and
53
+ `comments[]` (`author`, `body`).
54
+
55
+ **`prs/<repo>/<sha>.yaml`.** All fields are optional. Hand-written files need only the fields you set:
56
+
57
+ ```yaml
58
+ title: Short PR title # default: the commit subject
59
+ body: | # default: the commit body
60
+ What and why, briefly.
61
+ status: ready # draft (default, when absent) or ready: the reviewer's call, see below
62
+ branch: me/feature-flag # the plain git branch for this PR (you choose the name)
63
+ github: # once the PR exists on GitHub
64
+ number: 123
65
+ url: https://github.com/<org>/my-service/pull/123
66
+ branch: me/feature-flag
67
+ base: me/config-loader
68
+ stack: 12 # optional: the GitHub stack number
69
+ stacked_on: me/config-loader # optional: the branch it was stacked on before GitHub retargeted it
70
+ landed: # once it has merged, written by you before any fetch ("After merges")
71
+ head: <full sha> # headRefOid at merge
72
+ base: <full sha> # baseRefOid (pull.base.sha) at merge
73
+ commit: <full sha> # mergeCommit.oid
74
+ method: merge # merge, squash or rebase
75
+ at: 2026-10-09T12:00:00Z # mergedAt
76
+ ```
77
+
78
+ **Readiness: draft → ready → published → merged.** Every PR starts as a draft. The reviewer marks it **ready** in the UI when they're happy
79
+ with it (that writes `status: ready`). It's **published** once it has a `github:` record, whatever `status:` says,
80
+ and **merged** once it has a `landed:` record. The API gives each commit `status` (`draft` / `ready` / `published` /
81
+ `merged`) and `progress` (threads resolved, files viewed).
82
+ **Never set `status: ready` yourself (or `"status":"ready"` through the API) unless the reviewer asks you to: readiness is
83
+ their call.** Don't clear it either. Recording `github:` after publishing is what makes a PR published.
84
+
85
+ **Local Review comments and GitHub comments are entirely separate.** Review threads here are between the reviewer
86
+ and you, locally. Never copy them to GitHub, and never import GitHub review threads or comments into Local Review
87
+ files, not even for merged or historical PRs.
88
+
89
+ **Files follow rebases.** A file named after an old sha is re-attached to the new commit by patch-id, then by
90
+ Change-Id, then by **subject**. Landed PRs' files never move and are never taken by another commit. A fixup or amend changes the patch, so the subject is what carries the comments
91
+ across. **Keep subjects stable**: don't reword a reviewed commit's subject, and give every commit a unique one. The
92
+ stack selectors (`starts_at`) depend on subjects too.
93
+
94
+ ## Building the local representation (before anything goes to GitHub)
95
+
96
+ 1. **Shape the commits into PRs.** One commit is one PR. Each should be reviewable on its own, with a clear
97
+ subject. Use `git rebase -i` to split, squash and reorder.
98
+ 2. **Group them into stacks** by editing `starts_at` in `project.yaml`, one stack per coherent chunk of work.
99
+ 3. **Titles and descriptions:** the commit message is the default. To change one without touching the commit,
100
+ write `title:`/`body:` into the prs file, or call `PUT …/pr` (below).
101
+ 4. **Keep descriptions short.** Reviewers dislike verbose, AI-sounding descriptions: no headings, no restating the diff,
102
+ no "This PR…" boilerplate. Say what changed and why, in a few lines. Only write the project intro and stack
103
+ notes (`description:` in `project.yaml`) when asked.
104
+ 5. **Images** (a diagram, a before/after screenshot), only where a picture says it better: add the file with
105
+ `local-review asset add <project> <file> [--alt <text>]`, which copies it into the project's `assets/` folder and
106
+ prints the markdown to paste into a `body:` or `description:`, e.g. `![Overview](assets/3f2a9c1e0b7d4a65.png)`.
107
+ Never write `data:` URIs or copy files into `assets/` yourself: names are content hashes, and the command checks
108
+ the type and the 5 MB limit. GitHub can't show these until they're published ("Images" under "Materializing").
109
+
110
+ Then tell the reviewer it's ready, with the URL.
111
+
112
+ ## API (http://localhost:5622)
113
+
114
+ Use a short or full sha.
115
+
116
+ ```sh
117
+ P=my-feature; R=my-service; SHA=0a1b2c3d
118
+ BASE=http://localhost:5622/api/projects/$P/repos/$R/commits/$SHA
119
+ J='Content-Type: application/json'
120
+
121
+ # all commits: position, stack, title, status, unresolved, branch, github
122
+ curl -s http://localhost:5622/api/projects/$P | jq '.repos[] | {name, commits: [.commits[] | {position, short, title, status, unresolved, branch, github}]}'
123
+
124
+ curl -s $BASE/review | jq '.threads[] | select(.resolved != true)' # open threads
125
+ curl -s -X POST -H "$J" $BASE/threads/<tid>/comments -d '{"author":"claude","body":"…"}' # reply
126
+ curl -s -X PATCH -H "$J" $BASE/threads/<tid> -d '{"resolved":true}' # resolve
127
+ curl -s -X PUT -H "$J" $BASE/pr -d '{"title":"…","body":"…"}' # null clears a field
128
+ curl -s -X PUT -H "$J" $BASE/pr -d '{"branch":"me/x"}' # record a branch
129
+ ```
130
+
131
+ With the server down, edit the YAML files directly instead: see
132
+ [Comment files](https://local-review.edspencer.net/agents/comment-files/) for the recipe and
133
+ [the schema](https://local-review.edspencer.net/reference/comment-files/).
134
+
135
+ ## Handling review threads
136
+
137
+ To find every open thread in a project:
138
+
139
+ ```sh
140
+ for f in ~/.local-review/projects/$P/reviews/*/*.yaml; do
141
+ npx yaml --json --single < "$f" | jq -r --arg f "${f##*/reviews/}" '.threads[]? | select(.resolved != true) | "\($f) \(.id) \(.path // "general"):\(.line // "") \(.comments[-1].body | split("\n")[0])"'
142
+ done
143
+ ```
144
+
145
+ For each thread:
146
+
147
+ - **Always reply first**, as `author: claude`. Say what you changed and in which commit, e.g. "Renamed to
148
+ `parseFlag` in 0a1b2c3d (Add the feature flag)." If you changed nothing, say why.
149
+ - **Resolve it yourself** (`resolved: true`) only when the comment was a clear instruction that you carried out
150
+ exactly ("delete this", "rename this to X", "use this wording").
151
+ - **Leave it open** when it was a question, needed a judgement call, or you did something different from what was
152
+ asked. The reviewer resolves those.
153
+ - **Never delete threads or comments, and never change `id`s.** Don't edit the reviewer's comments.
154
+
155
+ ## Fixing a reviewed commit in a stack
156
+
157
+ ```sh
158
+ cd ~/code/my-service # on the stack's tip branch, clean work tree
159
+ # … edit …
160
+ git commit --fixup=<sha-of-the-reviewed-commit>
161
+ MB=$(git merge-base origin/main me/my-feature) # <base> and <branch> from project.yaml
162
+ GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash --update-refs "$MB"
163
+ ```
164
+
165
+ - **Rebase onto the stack's current merge-base, not `origin/main`.** The merge-base is the commit the stack sits on
166
+ now, so the rebase rewrites only the stack's own commits. If `origin/main` has moved since (it usually has after a
167
+ fetch), rebasing onto it silently moves the whole stack onto the new main. Every commit then changes. The diffs
168
+ the reviewer reviewed shift under them. Conflicts appear that have nothing to do with the fix, and the branches no longer
169
+ match what was reviewed. Moving to a new main is a separate, deliberate step (see "After merges").
170
+ - **Keep subjects unchanged.** The fixup is squashed into the target commit, keeping its subject, so its review
171
+ file follows it.
172
+ - **`--update-refs`** moves every branch that points at a rewritten commit (see "Branch tagging"). Without it, they
173
+ stay on the old commits and show as `moved`.
174
+ - **Afterwards,** check that the threads re-attached: open the PR in the UI, or run `curl -s $BASE/review | jq
175
+ '{carried_over, threads: [.threads[] | {id, outdated, line}]}'` with the new sha. Then reply in each thread you
176
+ addressed. `outdated: true` means the commented lines are gone. That's expected when you deleted them; say so in
177
+ the reply.
178
+
179
+ ## Branch tagging
180
+
181
+ Each candidate PR gets a plain git branch pointing at its commit. GitHub needs one head branch per PR, so this is
182
+ what lets the stack become real PRs later, and it lets the reviewer see in the sidebar what each PR's branch will be. **You
183
+ choose the name**: Local Review has no naming scheme. Pick short, descriptive names in the repo's usual style (e.g.
184
+ `me/feature-flag`). Tagging is local only: create and record the branches, but **don't push them** until the
185
+ reviewer approves materializing a stack (below). Don't move or rename the stack's own branch.
186
+
187
+ ```sh
188
+ git config rebase.updateRefs true # once per repo: every rebase moves these branches
189
+ git branch me/feature-flag 0a1b2c3d # one per commit, bottom to top
190
+ curl -s -X PUT -H "$J" $BASE/pr -d '{"branch":"me/feature-flag"}' # or write branch: into the prs file
191
+ ```
192
+
193
+ The top commit already has the stack's own branch (e.g. `me/my-feature`). Local Review shows it as
194
+ `discovered`. Give the top commit its own name too, unless that branch is meant to be the top PR's head.
195
+
196
+ The API and the UI show each commit's branch with a status:
197
+
198
+ | status | meaning | fix |
199
+ | --- | --- | --- |
200
+ | `ok` | the recorded branch points at this commit | nothing to do |
201
+ | `moved` | it points elsewhere (`points_at`), e.g. after a rebase without `--update-refs` | `git branch -f <name> <sha>` |
202
+ | `missing` | it's recorded but not in git | `git branch <name> <sha>`, or record the right name |
203
+ | `discovered` | nothing is recorded; a branch in git points here | record it, if it's the one |
204
+
205
+ ## The plan command
206
+
207
+ The plan command is read-only: it never writes, pushes or calls GitHub. Run it from anywhere:
208
+
209
+ ```sh
210
+ local-review plan <project> [--repo <name>] [--stack <n>] [--include-follow-on] [--format md|json|yaml]
211
+ local-review plan my-feature --repo my-service --stack 1
212
+ ```
213
+
214
+ It lists each PR in scope, in order, with:
215
+
216
+ - the commit, its readiness (`Status: draft|ready|published`), and the branch with its status;
217
+ - the base: the previous PR's branch, or the repo's base (remote prefix stripped, e.g. `main`) for the first PR;
218
+ - the title, and the **exact full body**: project intro, stack note, `---`, PR description, the same as the UI's
219
+ "Full body" button;
220
+ - any recorded `github:` info;
221
+ - the images the body shows (`images[]` in JSON: `name`, `file`, `url`).
222
+
223
+ It warns about anything that needs fixing first: unresolved `starts_at` selectors, PRs without a branch, `missing`
224
+ and `moved` branches, and stacks that still contain draft PRs (only the reviewer can clear that one, by marking them ready). `--format json` is for scripts, e.g. `jq -r '.prs[0].body'`.
225
+ It also warns when a live PR's recorded `github.base` differs from the plan's base (retarget the PR, or check the
226
+ order), and when a prs file with a `github:` record and no `landed:` has a commit that's now in the repo base: it
227
+ landed upstream but wasn't frozen (do that now; "After merges"). And for each image (`assets/<name>`) in a body with
228
+ no published URL, since GitHub can't show a file on this machine ("Images" under "Materializing"). Where a URL is
229
+ recorded, the body already has it in place of `assets/<name>`.
230
+
231
+ Landed (merged) PRs are never planned, with no warnings of their own; the first live PR's base is the repo base. A
232
+ note lists them ("Skipped 9 landed (merged) PRs …"); in JSON, `excluded_landed[]` (`repo`, `prs`).
233
+
234
+ Follow-on stacks are left out by default, with none of their warnings, and a note says so ("Excluded 1 follow-on
235
+ stack (2 PRs: …); use --include-follow-on"). `--include-follow-on` plans them too (the first one's base is the
236
+ previous PR's branch, as for any stack). `--stack <n>` naming a follow-on stack plans it, with a note that it's a
237
+ follow-on. In JSON: `scope.include_follow_on`, `excluded_follow_on[]` (`repo`, `stack`, `title`, `prs`), `notes[]`,
238
+ and `stack.follow_on` on each PR.
239
+
240
+ ## Materializing with GitHub native stacked PRs
241
+
242
+ **Only when the reviewer asks, one stack at a time:**
243
+
244
+ 1. Fix every warning, run the plan for **one** stack, and **show it to the reviewer**. Skip follow-on stacks unless
245
+ the reviewer names one. If it still has draft PRs, say so:
246
+ the reviewer marks them ready (or tells you to go ahead anyway).
247
+ 2. **Wait for an explicit go-ahead.**
248
+ 3. Publish that one stack (below).
249
+ 4. Record what you created, and **report back** with the PR URLs.
250
+ 5. Stop. The next stack waits for the next go-ahead.
251
+
252
+ This uses the [`gh stack` extension](https://github.com/github/gh-stack) (`gh extension install github/gh-stack`).
253
+ See [About stacked PRs](https://docs.github.com/en/pull-requests/get-started/about-stacked-prs), the
254
+ [CLI reference](https://docs.github.com/en/pull-requests/reference/stacked-prs-cli-commands) and the extension's
255
+ [docs](https://github.com/github/gh-stack/tree/main/docs/src/content/docs) (FAQ, REST API). `gh stack` has **no
256
+ title or body flags**: `submit` opens an interactive editor, and `--auto` uses generated text. So create each PR
257
+ with `gh pr create`, which takes the exact title and body, then link the PRs into a stack with `gh stack link`,
258
+ which is meant for branches managed by other tools.
259
+
260
+ Each repo gets **one GitHub stack**, grown one Local Review stack at a time: the first go-ahead creates it, and each
261
+ later one appends to its top by stack number. (Stack numbers share the repo's numberspace with PRs and issues,
262
+ so they never equal a PR number.)
263
+
264
+ ```sh
265
+ cd ~/code/my-service
266
+ local-review plan $P --repo $R --stack 1 --format json > /tmp/plan.json
267
+
268
+ # 0. stacked PRs available? Any 200 (even `[]`) means yes; 404 means not enabled, or no access to the repo
269
+ gh api "repos/{owner}/{repo}/stacks?per_page=1" >/dev/null
270
+
271
+ # 1. push the stack's branches (bottom to top)
272
+ git push --force-with-lease origin $(jq -r '.prs[].branch.name' /tmp/plan.json)
273
+
274
+ # 2. one PR per branch, bottom to top, with the plan's base, title and full body
275
+ jq -c '.prs[]' /tmp/plan.json | while read -r pr; do
276
+ jq -r .body <<<"$pr" > /tmp/pr-body.md
277
+ gh pr create --draft --head "$(jq -r .branch.name <<<"$pr")" --base "$(jq -r .base <<<"$pr")" \
278
+ --title "$(jq -r .title <<<"$pr")" --body-file /tmp/pr-body.md
279
+ done
280
+
281
+ # 3a. first stack in this repo: create the GitHub stack, bottom to top. Always pass --base: without it, link
282
+ # retargets the bottom PR to the repo's default branch.
283
+ gh stack link --remote origin --base "$(jq -r '.prs[0].base' /tmp/plan.json)" $(jq -r '.prs[].branch.name' /tmp/plan.json)
284
+ # 3b. later stack: append only the new PRs to the existing GitHub stack, by its number (--base is ignored here)
285
+ N=$(gh api "repos/{owner}/{repo}/stacks?pull_request=<any earlier PR number>" --jq '.[0].number')
286
+ gh stack link --remote origin "$N" $(jq -r '.prs[].branch.name' /tmp/plan.json)
287
+
288
+ # check: the stack, bottom to top (gh stack view can't see it; see below)
289
+ gh api "repos/{owner}/{repo}/stacks?pull_request=$(gh pr view "$(jq -r '.prs[0].branch.name' /tmp/plan.json)" --json number --jq .number)" \
290
+ --jq '.[0] | "stack #\(.number) on \(.base.ref): \([.pull_requests[] | "#\(.number) \(.head.ref)"] | join(" <- "))"'
291
+
292
+ # 4. record each PR in its prs file (this also records the branch)
293
+ for b in $(jq -r '.prs[].branch.name' /tmp/plan.json); do
294
+ sha=$(git rev-parse "$b")
295
+ gh pr view "$b" --json number,url,headRefName,baseRefName \
296
+ | jq '{github: {number, url, branch: .headRefName, base: .baseRefName}}' \
297
+ | curl -s -X PUT -H "$J" -d @- http://localhost:5622/api/projects/$P/repos/$R/commits/$sha/pr >/dev/null
298
+ done
299
+ ```
300
+
301
+ Checked against the docs and the extension's source (October 2026):
302
+
303
+ - **No setup:** stacked PRs are GA on all github.com plans (6 Oct 2026), with no repo or org setting. Step 0 is
304
+ the check. `gh stack` exits `9` for "not enabled for this repository" (a 404 from the Stacks API).
305
+ - `link` keeps **no local state**, so `gh stack view`, `push`, `rebase` and `sync` don't know about the stack
306
+ (exit `2`, "not part of a stack"). Read it with `gh api` instead, as above.
307
+ - `link` pushes branch arguments **without force**, so push rebased branches with `--force-with-lease` first
308
+ (step 1). It reuses open PRs for those branches and retargets any whose base breaks the chain. It **only adds**,
309
+ and only to the top. A PR can be in only one stack, and a stack holds 2–100 PRs, all in the same repo (no forks).
310
+ Don't pass `--open`: it marks every PR in the command ready for review. Removing or reordering means
311
+ `gh stack unstack <n>` and relinking, which is the reviewer's call.
312
+ - A **fully merged** stack can't be extended. The next Local Review stack then becomes a new GitHub stack (3a,
313
+ with the plan's base, which is `main` by then).
314
+ - Force-pushing rebased branches keeps the stack: membership belongs to the PRs, not the commits. Merging needs a
315
+ linear history, so each branch must contain the one below it, which `--update-refs` keeps true.
316
+ - CI and branch protection treat **every** PR as if it targeted the stack's base, so workflows on `pull_request`
317
+ to `main` run for each PR in the stack. To merge a PR, every PR below it must pass its checks and reviews too.
318
+
319
+ Not verified (needs a live test):
320
+
321
+ - splitting one repo's chain into several GitHub stacks, each based on the previous one's top branch. GitHub says
322
+ stacks can branch off one another, but we haven't tried `link --base <previous top branch>`. Use one stack per
323
+ repo;
324
+ - whether approvals survive a local force-push. GitHub says its own **Rebase stack** button keeps them, but
325
+ after a local force-push the repo's "dismiss stale approvals" rule probably applies.
326
+
327
+ Check `gh stack link --help` before you run it, and if anything differs from the above, tell the reviewer rather than guess.
328
+
329
+ ### Images
330
+
331
+ GitHub has no API for attaching an image to a PR body: its upload exists only in the web UI. So an `assets/<name>`
332
+ reference in a plan body would show as a broken image on GitHub. For each image the plan warns about, either:
333
+
334
+ - **record a URL the PR's readers can reach**, with `local-review asset url <project> <name> <url>` (for example an
335
+ image committed to a repo they can read, or one already uploaded to an earlier PR or issue), then re-run the plan:
336
+ the body now has that URL; or
337
+ - **leave it to the reviewer**: publish as usual, and tell them which PRs have images to drag in by hand (the plan's
338
+ `images[].file` is where each one is). Dragging an image into the PR on GitHub uploads it; they then replace the
339
+ `assets/<name>` reference with the link GitHub inserts.
340
+
341
+ Don't upload images anywhere public on your own initiative, and ask before committing one to a repo.
342
+
343
+ ## After merges
344
+
345
+ - Stacks merge **bottom-up**, in the web UI or with `gh stack merge <pr> --yes` (`--squash`/`--rebase`/`--merge`).
346
+ This lands that PR and every unmerged PR below it, all or nothing. **`gh pr merge` can't merge a stacked PR.**
347
+ Squash gives one commit per PR. Merging is the reviewer's call.
348
+ - GitHub then retargets the lowest unmerged PR to the stack's base and **rebases the rest on the server**,
349
+ force-pushing their branches. The remote branches have moved, so fetch before pushing anything, but **freeze the
350
+ merged PRs first** (below).
351
+
352
+ ### Freezing merged PRs (before any fetch or rebase)
353
+
354
+ Once merged, a PR's commits leave the Local Review range when you rebase onto the new main, and the stack would empty
355
+ out and renumber. Freezing keeps it as a **landed PR**: same number, same stack, purple **Merged** badge, GitHub's diff
356
+ of it. **This is your job, and it comes first**, while the prs files still sit under the shas the reviewer reviewed.
357
+ Local Review only reads the result and warns (`local-review plan`: "landed upstream but not frozen"); it never calls GitHub.
358
+
359
+ For each merged PR, bottom to top (`N` its number, `SHA` the full sha of its prs file, i.e. the commit it was reviewed as):
360
+
361
+ ```sh
362
+ P=my-feature; R=my-service; N=123; SHA=<full sha>
363
+ F=~/.local-review/projects/$P/prs/$R/$SHA.yaml
364
+
365
+ # 1. what landed (read-only GETs)
366
+ gh pr view $N --json headRefOid,baseRefOid,mergeCommit,mergedAt > /tmp/merged.json
367
+ PARENTS=$(gh api "repos/{owner}/{repo}/commits/$(jq -r .mergeCommit.oid /tmp/merged.json)" --jq '.parents | length')
368
+ METHOD=$([ "$PARENTS" = 2 ] && echo merge || echo squash) # one parent: squash, or rebase if that's how it merged
369
+ # optional, for github.stacked_on: the branch it was stacked on before GitHub retargeted it
370
+ gh api graphql -F n=$N -f query='query($n:Int!){repository(owner:"<owner>",name:"<repo>"){pullRequest(number:$n){
371
+ timelineItems(first:5,itemTypes:[AUTOMATIC_BASE_CHANGE_SUCCEEDED_EVENT]){nodes{... on AutomaticBaseChangeSucceededEvent{oldBase newBase}}}}}}' \
372
+ --jq '.data.repository.pullRequest.timelineItems.nodes[0].oldBase'
373
+
374
+ # 2. write the landed: record into its prs file (append it; keep everything else)
375
+ grep -q '^landed:' "$F" 2>/dev/null || jq -r --arg m "$METHOD" \
376
+ '"landed:\n head: \(.headRefOid)\n base: \(.baseRefOid)\n commit: \(.mergeCommit.oid)\n method: \($m)\n at: \(.mergedAt)"' \
377
+ /tmp/merged.json >> "$F"
378
+ ```
379
+
380
+ 3. **Append its key to the repo's `landed:` list** in `project.yaml` (bottom to top; a quoted sha prefix is fine), by
381
+ hand, keeping the rest of the file:
382
+
383
+ ```yaml
384
+ repos:
385
+ - path: ~/code/my-service
386
+ branch: me/my-feature
387
+ base: origin/main
388
+ landed: ["0a1b2c3d", "4e5f6a7b"] # the merged PRs, bottom to top
389
+ ```
390
+
391
+ Make sure the prs file has `subject:` (the commit's subject; the server writes it whenever it saves the file):
392
+ a stack whose `starts_at` is that subject keeps matching the landed PR.
393
+ 4. **Then fetch and rebase** what's left onto the new main. This is the one time to rebase onto `origin/main`:
394
+
395
+ ```sh
396
+ git fetch origin
397
+ git rebase --update-refs origin/main # on the tip branch
398
+ ```
399
+
400
+ Git skips commits whose patch is already upstream (a squashed single-commit PR has the same patch). If a merged
401
+ commit was changed during the merge and isn't skipped, use
402
+ `git rebase --update-refs --onto origin/main <merged-commit-sha>`.
403
+
404
+ - Then push only the published branches whose content differs from GitHub's rebase (normally none):
405
+ `git diff --quiet origin/$b $b || git push --force-with-lease origin $b`. `gh stack push`, `rebase` and `sync`
406
+ would do all this, but only for stacks tracked locally (`gh stack init <branches…>`, which also turns on
407
+ `rerere`). `link` doesn't set that up, so don't use them.
408
+ - The landed PRs stay first in the repo, in their stacks, with their numbers; their review and prs files stay where
409
+ they are. Don't remove or regroup the stacks. Once everything has landed, the branch can go (`branch:` is optional
410
+ with `landed:`) and the project is archived automatically. Review threads on landed PRs work as normal, and stay
411
+ in Local Review: don't carry them to GitHub, and don't import GitHub's.
412
+ - A PR merged without being frozen (you fetched first) can still be frozen: its prs file is where it was, and `gh pr
413
+ view` still has the data. `local-review plan` lists the ones whose commits it can see in the base.
414
+
415
+ ### Importing a historical, already-merged stack (for reference or demos)
416
+
417
+ Only when the reviewer asks. Everything comes from read-only `gh` GETs, and **no review threads, ever** (no
418
+ `reviews/` files; GitHub's comments stay on GitHub).
419
+
420
+ 1. Get the members in order from the GitHub stack API: `gh api repos/{owner}/{repo}/stacks/<n>` lists
421
+ `pull_requests[]` with each one's `number`, `head` (`ref`, `sha`) and `base` (`ref`, `sha` at merge) and
422
+ `merged_at`; `gh api repos/{owner}/{repo}/pulls/<N> --jq .stack.position` gives a PR's position, if you need to
423
+ check the order. Then per PR: `gh pr view <N> --json title,body,headRefName,baseRefName,headRefOid,baseRefOid,mergeCommit,mergedAt`.
424
+ 2. Write one prs file per PR, named after its head sha, `prs/<repo>/<headRefOid>.yaml`, with `commit:`, `title:`,
425
+ `body:`, `branch:`, `github:` (`number`, `url`, `branch`, `base`, `stack: <n>`, optionally `stacked_on`) and
426
+ `landed:` (as above).
427
+ 3. Write `project.yaml`: the repo's `path` and `base`, no `branch`, `github_stack: <n>` (informational), `landed:`
428
+ with the head shas bottom to top, and the stacks (one per GitHub stack is simplest, `starts_at` the first PR's
429
+ title). A neutral project intro if the reviewer wants one.
430
+ 4. The diffs need the heads and bases in the local clone (fetching is your call, in your repo; Local Review never
431
+ fetches). Without the head or base, a PR shows its merge commit's diff; without that either, no diff and a
432
+ warning. Check the files / + / − against GitHub's numbers.
433
+
434
+ Stacking tools leave merged stacks looking different on GitHub. Check which kind you have before writing `landed:`:
435
+
436
+ - **ghstack (e.g. PyTorch): "Closed", not merged.** A merge bot lands each PR on `main` as one commit and closes the
437
+ PR, so `mergedAt` and `mergeCommit` are null. Find the landed commit by its `Pull Request resolved: …/pull/<N>`
438
+ trailer (`gh api search/commits -f q='repo:<owner>/<repo> "pull/<N>"'`), and use it as `landed.commit`, with
439
+ `method: rebase` and its commit date as `at`. Each PR's base is its own synthetic `gh/<user>/<n>/base` branch, not
440
+ the PR below; `headRefOid`/`baseRefOid` still give the PR's own diff.
441
+ - **Meta's exported repos (e.g. pytorch/executorch): "Merged", but into a synthetic base.** The PR merged into its
442
+ own `gh/…/base` branch, and that merge commit is not on `main`. The real commit on `main` comes from Meta's export
443
+ (a `Differential Revision:` trailer): use that as `landed.commit`, not `mergeCommit`.
444
+ - **GitHub native stacks merged in one go: a stale `baseRefName`.** The upper PRs keep the branch below as their
445
+ `baseRefName` although everything landed on the stack's base. That's expected: record it as is, since `baseRefOid`
446
+ (the head of the PR below) is what gives each PR its own diff.
447
+ - **Graphite and other auto-retargeted stacks: interleaved squashes.** After each merge GitHub retargets the next PR
448
+ to `main`, so its `baseRefName` is `main` and the squash commits sit between unrelated commits on `main`. The
449
+ head/base pairs still give each PR's own diff; the branch it was stacked on is only in the timeline
450
+ (`github.stacked_on`, as in "Freezing merged PRs").
451
+ - **Sapling: cumulative PRs.** Each PR targets `main` and contains every PR below it, so its head/base diff is the
452
+ whole stack so far, and GitHub's numbers are cumulative too. Set each PR's `landed.base` to the `headRefOid` of the
453
+ PR below it (the bottom PR keeps its own base), so each one shows just its own change.
454
+
455
+ ## Hand-off prompt template
456
+
457
+ To start a chat that addresses the reviewer's comments, paste this (fill in the `<…>`s):
458
+
459
+ ```
460
+ I've reviewed <repo> in Local Review (project <project>, http://localhost:5622/<project>).
461
+ Address my open review threads. First run `npx local-review guide` and follow that guide exactly:
462
+ - fix each reviewed commit with --fixup + autosquash onto the stack's current merge-base, with
463
+ --update-refs, keeping subjects unchanged;
464
+ - reply in every thread (author: claude) saying what changed and in which commit;
465
+ - resolve only threads that were clear instructions carried out exactly, and leave the rest open;
466
+ - never delete threads or comments; don't push or touch GitHub.
467
+ Then check that the threads re-attached, run the plan command and report what's left.
468
+ ```
package/CHANGELOG.md ADDED
@@ -0,0 +1,67 @@
1
+ # local-review
2
+
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#23](https://github.com/edspencer/local-review/pull/23) [`6af921a`](https://github.com/edspencer/local-review/commit/6af921a47a32e7d30324e2149730e007a4200d4e) Thanks [@edspencer](https://github.com/edspencer)! - A first-run welcome page, and one-click example projects:
8
+
9
+ - With no projects, the UI now shows a welcome page instead of a bare "No projects yet": what Local Review is, how to
10
+ create your first project (or a prompt to hand that to your coding agent), and links to the docs.
11
+ - **Add the example projects** (on that page, at the bottom of the project switcher, or `local-review examples`)
12
+ creates "Orchard", a made-up app reviewed across four repos, in about a second and with no network: stacked PRs,
13
+ review threads with an agent's replies, and a fully merged stack to click around. They're marked **Example**.
14
+ - **Remove examples** hides them again and deletes nothing: each example's `project.yaml` is renamed to a `.bak`, and
15
+ adding them again brings them back as you left them. A project of your own is never touched.
16
+
17
+ ## 0.2.0
18
+
19
+ ### Minor Changes
20
+
21
+ - [#20](https://github.com/edspencer/local-review/pull/20) [`205a9a4`](https://github.com/edspencer/local-review/commit/205a9a4573831fdb866c5a7086356c0a410609d5) Thanks [@edspencer](https://github.com/edspencer)! - Images in PR descriptions, stack notes and the project intro. Paste, drop or attach an image in any of their editors,
22
+ or add one with `local-review asset add <project> <file>`, which prints the markdown (`![alt](assets/<hash>.png)`).
23
+ Images live in the project's `assets/` folder, named by their content, and are never replaced or deleted; snapshots
24
+ hard-link them instead of copying them. PNG, JPEG, GIF, WebP and SVG up to 5 MB, checked by their contents, and served
25
+ with `nosniff` and a Content Security Policy that stops an SVG running script. GitHub can't show a local image, so
26
+ `local-review plan` warns about each one, and puts its URL in the body instead once you record where it's published
27
+ with `local-review asset url <project> <name> <url>`.
28
+
29
+ - [#10](https://github.com/edspencer/local-review/pull/10) [`89ddc0b`](https://github.com/edspencer/local-review/commit/89ddc0be153ffb5675545bdcb945a45cbb4a7544) Thanks [@edspencer](https://github.com/edspencer)! - **Breaking: the default port is now 5622, not 5000.** macOS uses port 5000 for AirPlay, so Local Review now serves on
30
+ http://localhost:5622. If you have bookmarks, scripts or agent prompts that say `localhost:5000`, update them, or keep
31
+ the old port with `local-review --port 5000` (or `PORT=5000`).
32
+
33
+ Local Review is now an installable npm package. Run it with `npx local-review`, or install it once with
34
+ `npm i -g local-review` and run `local-review`. It has four commands:
35
+
36
+ - `local-review [serve] [--port <n>]` serves the UI (the default command).
37
+ - `local-review init <project>` creates a project from a commented example.
38
+ - `local-review plan <project>` prints the read-only materialization plan.
39
+ - `local-review guide` prints the guide for coding agents.
40
+
41
+ The package ships compiled JavaScript and a prebuilt UI, so it no longer needs `tsx` or a build step at run time, and
42
+ it installs only its runtime dependencies. It needs Node.js 22 or later.
43
+
44
+ The guide for agents using Local Review on your repos moved from `AGENTS.md` to `AGENT-GUIDE.md`, and
45
+ `local-review guide` prints it, so an agent can read it without a checkout. `AGENTS.md` now holds the conventions
46
+ for contributing to Local Review itself.
47
+
48
+ There's a new logo and favicon.
49
+
50
+ ### Patch Changes
51
+
52
+ - [#18](https://github.com/edspencer/local-review/pull/18) [`eab708f`](https://github.com/edspencer/local-review/commit/eab708f7808b1a49313de9b5b5fc0d4c3eea64d4) Thanks [@edspencer](https://github.com/edspencer)! - Display fixes:
53
+
54
+ - A PR title or description written into a prs file that matches the commit message (ignoring whitespace) no longer
55
+ shows as "edited", and `local-review plan` no longer says "(edited)" for it. The file itself isn't changed.
56
+ - Tabs in code in the Conversation tab (code blocks in comments and descriptions, and the code under inline
57
+ threads) are now as wide as in the Files changed diff, instead of 8 columns.
58
+ - A repo whose PRs have all merged is labelled **merged** on the project homepage and in the stack picture, as it
59
+ already was in the sidebar, instead of with its base branch (for example `origin/main`).
60
+ - Paths under your home directory are shown as `~/…`, for example `~/.local-review/projects/…`. The API still returns
61
+ absolute paths, and now always does: they used to be relative to Local Review's own directory when your data was
62
+ inside it. `GET /api/projects` also returns `home`, the home directory the UI shortens paths against.
63
+
64
+ - [#19](https://github.com/edspencer/local-review/pull/19) [`45e5b6f`](https://github.com/edspencer/local-review/commit/45e5b6f214f5f917e17ea44a9f3563d563bdd969) Thanks [@edspencer](https://github.com/edspencer)! - The documentation now lives at https://local-review.edspencer.net, and the README is a short introduction that links
65
+ to it. The "No projects yet" page links to the docs' Quickstart and `project.yaml` reference, and the example
66
+ `project.yaml` and `local-review guide` point at the docs too. The agent guide's "Importing a historical stack" section
67
+ now covers how stacks merged with ghstack, Sapling, Graphite and GitHub's own stacks look on GitHub.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ed Spencer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.