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.
- package/AGENT-GUIDE.md +468 -0
- package/CHANGELOG.md +67 -0
- package/LICENSE +21 -0
- package/README.md +126 -2
- package/dist/demo/lib/gitworld.js +224 -0
- package/dist/demo/world/api.js +2436 -0
- package/dist/demo/world/infra.js +189 -0
- package/dist/demo/world/people.js +16 -0
- package/dist/demo/world/review.js +578 -0
- package/dist/demo/world/sdk.js +376 -0
- package/dist/demo/world/web.js +657 -0
- package/dist/server/anchors.js +207 -0
- package/dist/server/assets.js +147 -0
- package/dist/server/backups.js +212 -0
- package/dist/server/branches.js +113 -0
- package/dist/server/cli.js +283 -0
- package/dist/server/commits.js +393 -0
- package/dist/server/config.js +62 -0
- package/dist/server/context.js +68 -0
- package/dist/server/events.js +87 -0
- package/dist/server/examples.js +204 -0
- package/dist/server/git.js +103 -0
- package/dist/server/index.js +581 -0
- package/dist/server/landed.js +71 -0
- package/dist/server/plan.js +260 -0
- package/dist/server/port.js +3 -0
- package/dist/server/projectEdit.js +117 -0
- package/dist/server/projects.js +321 -0
- package/dist/server/prs.js +314 -0
- package/dist/server/reviewer.js +33 -0
- package/dist/server/reviews.js +586 -0
- package/dist/server/safefs.js +92 -0
- package/dist/server/security.js +38 -0
- package/dist/server/stackviz.js +446 -0
- package/dist/shared/assets.js +54 -0
- package/dist/shared/paths.js +13 -0
- package/dist/shared/prText.js +22 -0
- package/dist/shared/readiness.js +37 -0
- package/dist/shared/repoLabel.js +13 -0
- package/dist/shared/totals.js +75 -0
- package/dist/web/assets/abap-CLvhMVsD.js +1 -0
- package/dist/web/assets/actionscript-3-Cs6JJXT5.js +1 -0
- package/dist/web/assets/ada-X3Y2mlq2.js +1 -0
- package/dist/web/assets/ahk-DwADkxyd.js +1 -0
- package/dist/web/assets/ahk2-_6rzW9BS.js +1 -0
- package/dist/web/assets/andromeeda-vGVdxbeo.js +1 -0
- package/dist/web/assets/angular-html-qFVZP7wG.js +1 -0
- package/dist/web/assets/angular-ts-DzOxtaC9.js +1 -0
- package/dist/web/assets/apache-U0d_L8uA.js +1 -0
- package/dist/web/assets/apex-KFGFyKwG.js +1 -0
- package/dist/web/assets/apl-0AThSj8r.js +1 -0
- package/dist/web/assets/apple-touch-icon-BNrSfFwW.png +0 -0
- package/dist/web/assets/applescript-CCn79oCD.js +1 -0
- package/dist/web/assets/ara-4CJ0cIlV.js +1 -0
- package/dist/web/assets/asciidoc-SCjQUq34.js +1 -0
- package/dist/web/assets/asm-Cmm7eHzH.js +1 -0
- package/dist/web/assets/astro-By53bk_I.js +1 -0
- package/dist/web/assets/aurora-x-CDeNXAV0.js +1 -0
- package/dist/web/assets/awk-BWXHIvNe.js +1 -0
- package/dist/web/assets/ayu-dark-DluEY0Gj.js +1 -0
- package/dist/web/assets/ayu-light-C3h-C4tm.js +1 -0
- package/dist/web/assets/ayu-mirage-Bqwy1Gya.js +1 -0
- package/dist/web/assets/ballerina-B7ZEbQpA.js +1 -0
- package/dist/web/assets/bat-qdxAdBWQ.js +1 -0
- package/dist/web/assets/beancount-C2IN4Vk8.js +1 -0
- package/dist/web/assets/berry-DKpUyyne.js +1 -0
- package/dist/web/assets/bibtex-Ci_nEsc7.js +1 -0
- package/dist/web/assets/bicep-CUHmPFLl.js +1 -0
- package/dist/web/assets/bird2-CYIUgYqB.js +1 -0
- package/dist/web/assets/blade-Dw_Nglq9.js +1 -0
- package/dist/web/assets/bsl-_VjlB9mv.js +1 -0
- package/dist/web/assets/c-CQT-cQHo.js +1 -0
- package/dist/web/assets/c3-D0apG41V.js +1 -0
- package/dist/web/assets/cadence-CQ2zXKGN.js +1 -0
- package/dist/web/assets/cairo-DLTphjLi.js +1 -0
- package/dist/web/assets/catppuccin-frappe-C9Q5zVZY.js +1 -0
- package/dist/web/assets/catppuccin-latte-CtsyrtPq.js +1 -0
- package/dist/web/assets/catppuccin-macchiato-R8nAf1vR.js +1 -0
- package/dist/web/assets/catppuccin-mocha-DjuaAJKi.js +1 -0
- package/dist/web/assets/chapel-64o3Hib1.js +1 -0
- package/dist/web/assets/clarity-CtfBBspg.js +1 -0
- package/dist/web/assets/clojure-DqKBuwfJ.js +1 -0
- package/dist/web/assets/cmake-Bj61d0ZC.js +1 -0
- package/dist/web/assets/cobol-COyB9oJ9.js +1 -0
- package/dist/web/assets/codeowners-C8r90Shi.js +1 -0
- package/dist/web/assets/codeql-oeQT6MSM.js +1 -0
- package/dist/web/assets/coffee-BGIfDQTv.js +1 -0
- package/dist/web/assets/common-lisp-Cv5bFMCO.js +1 -0
- package/dist/web/assets/coq-CGaSfhwR.js +1 -0
- package/dist/web/assets/cpp-wi7ixCRa.js +1 -0
- package/dist/web/assets/crystal-ADRCp2kP.js +1 -0
- package/dist/web/assets/csharp-dmMxLVLW.js +1 -0
- package/dist/web/assets/css-BH9tz7qt.js +1 -0
- package/dist/web/assets/csv-Dx-8-gkx.js +1 -0
- package/dist/web/assets/cue-CE9AQfxI.js +1 -0
- package/dist/web/assets/cypher-ClKdZ_lG.js +1 -0
- package/dist/web/assets/d-qD-0Kul2.js +1 -0
- package/dist/web/assets/dark-plus-Cs2F2srj.js +1 -0
- package/dist/web/assets/dart-CHZY9Xhz.js +1 -0
- package/dist/web/assets/dax-BkyTk9wS.js +1 -0
- package/dist/web/assets/desktop-Dlh5hvp9.js +1 -0
- package/dist/web/assets/diff-woXpYk--.js +1 -0
- package/dist/web/assets/docker-IyjqRm3v.js +1 -0
- package/dist/web/assets/dotenv-CuH5my32.js +1 -0
- package/dist/web/assets/dracula-BHWKrbxM.js +1 -0
- package/dist/web/assets/dracula-soft-5eyTD99u.js +1 -0
- package/dist/web/assets/dream-maker-DW3nJb8Q.js +1 -0
- package/dist/web/assets/edge-DFzphiQm.js +1 -0
- package/dist/web/assets/elixir-BUj3AoF_.js +1 -0
- package/dist/web/assets/elm-Bqgz9ryu.js +1 -0
- package/dist/web/assets/emacs-lisp-DR2vtWaj.js +1 -0
- package/dist/web/assets/erb-DHHWfqsN.js +1 -0
- package/dist/web/assets/erlang-Cphh6RMH.js +1 -0
- package/dist/web/assets/everforest-dark-sB-x3p7T.js +1 -0
- package/dist/web/assets/everforest-light-Df2xbC6M.js +1 -0
- package/dist/web/assets/favicon-Bld6z4jR.svg +13 -0
- package/dist/web/assets/favicon-CDTyigy2.ico +0 -0
- package/dist/web/assets/fennel-DQxkIbk2.js +1 -0
- package/dist/web/assets/fish-BJitypiv.js +1 -0
- package/dist/web/assets/fluent-C03EYrpw.js +1 -0
- package/dist/web/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
- package/dist/web/assets/fortran-free-form-CYNrtFtB.js +1 -0
- package/dist/web/assets/fsharp-D13ZGOAj.js +1 -0
- package/dist/web/assets/gdresource-xMC7jr7x.js +1 -0
- package/dist/web/assets/gdscript-DYvRD3hg.js +1 -0
- package/dist/web/assets/gdshader-CBce3t8t.js +1 -0
- package/dist/web/assets/genie-CV2tkWYe.js +1 -0
- package/dist/web/assets/gherkin-DExj1W_8.js +1 -0
- package/dist/web/assets/git-commit-BSykSTBG.js +1 -0
- package/dist/web/assets/git-rebase-C7iLD81B.js +1 -0
- package/dist/web/assets/github-dark-C-LZuMrd.js +1 -0
- package/dist/web/assets/github-dark-default-DXG-b-1a.js +1 -0
- package/dist/web/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
- package/dist/web/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
- package/dist/web/assets/github-light-EUqPIrTm.js +1 -0
- package/dist/web/assets/github-light-default-BXViO-2h.js +1 -0
- package/dist/web/assets/github-light-high-contrast-B68TUdTA.js +1 -0
- package/dist/web/assets/gleam-CSRkHgEL.js +1 -0
- package/dist/web/assets/glimmer-js-BT_UjuD4.js +1 -0
- package/dist/web/assets/glimmer-ts-BpeqXZCf.js +1 -0
- package/dist/web/assets/glsl-mPoRKPQX.js +1 -0
- package/dist/web/assets/gn-CAXYAgV-.js +1 -0
- package/dist/web/assets/gnuplot-7GGW24-e.js +1 -0
- package/dist/web/assets/go-rLFTqkRN.js +1 -0
- package/dist/web/assets/graphql-D5e-luGt.js +1 -0
- package/dist/web/assets/groovy-CacY0gHj.js +1 -0
- package/dist/web/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
- package/dist/web/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
- package/dist/web/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
- package/dist/web/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
- package/dist/web/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
- package/dist/web/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
- package/dist/web/assets/hack-Yj_0xAq9.js +1 -0
- package/dist/web/assets/haml-rCEvyqvs.js +1 -0
- package/dist/web/assets/handlebars-CZRXN7yP.js +1 -0
- package/dist/web/assets/haskell-D8IpX4py.js +1 -0
- package/dist/web/assets/haxe-C8oCnwJJ.js +1 -0
- package/dist/web/assets/hcl-Dh228itO.js +1 -0
- package/dist/web/assets/hjson-CxZEssPk.js +1 -0
- package/dist/web/assets/hlsl-Cvrh5tZx.js +1 -0
- package/dist/web/assets/horizon-CE9ld1lL.js +1 -0
- package/dist/web/assets/horizon-bright-DSNQnXHK.js +1 -0
- package/dist/web/assets/houston-CsvMBhTu.js +1 -0
- package/dist/web/assets/html-XqiqNW99.js +1 -0
- package/dist/web/assets/html-derivative-nWC0aMV7.js +1 -0
- package/dist/web/assets/http-C4fFBmNU.js +1 -0
- package/dist/web/assets/hurl-DjZ_NyWj.js +1 -0
- package/dist/web/assets/hxml-Dcm88srW.js +1 -0
- package/dist/web/assets/hy-CZbG8q4J.js +1 -0
- package/dist/web/assets/imba-DsUTQ-LC.js +1 -0
- package/dist/web/assets/index-BPfnOrNh.js +451 -0
- package/dist/web/assets/index-BplAAg-1.css +1 -0
- package/dist/web/assets/ini-B5eOa1yu.js +1 -0
- package/dist/web/assets/java-B0zZG6CJ.js +1 -0
- package/dist/web/assets/javascript-CFawPmAZ.js +1 -0
- package/dist/web/assets/jinja-qu2nA6S7.js +1 -0
- package/dist/web/assets/jison-DviWniCr.js +1 -0
- package/dist/web/assets/json-C0vF7y3r.js +1 -0
- package/dist/web/assets/json5-BR5RXkoi.js +1 -0
- package/dist/web/assets/jsonc-CYpm1nAK.js +1 -0
- package/dist/web/assets/jsonl-CmCQp5Yx.js +1 -0
- package/dist/web/assets/jsonnet-CJTPZ8u_.js +1 -0
- package/dist/web/assets/jssm-DXw9l8Rf.js +1 -0
- package/dist/web/assets/jsx-DQYIrdZV.js +1 -0
- package/dist/web/assets/julia-C_b0WNzR.js +1 -0
- package/dist/web/assets/just-NwM_1ruS.js +1 -0
- package/dist/web/assets/kanagawa-dragon-Bozd7klA.js +1 -0
- package/dist/web/assets/kanagawa-lotus-BmuOG9ZW.js +1 -0
- package/dist/web/assets/kanagawa-wave-BML5mF2J.js +1 -0
- package/dist/web/assets/kdl-CsD5j6eV.js +1 -0
- package/dist/web/assets/kotlin-DhhofPvG.js +1 -0
- package/dist/web/assets/kusto-C7mF5XQf.js +1 -0
- package/dist/web/assets/laserwave-C_8bwKvT.js +1 -0
- package/dist/web/assets/latex-As2zLFN8.js +1 -0
- package/dist/web/assets/lean-CewbzKMR.js +1 -0
- package/dist/web/assets/less-DVTAwKKz.js +1 -0
- package/dist/web/assets/light-plus-DVQuIRkW.js +1 -0
- package/dist/web/assets/liquid-By8qNhnm.js +1 -0
- package/dist/web/assets/llvm-DqHOMS-s.js +1 -0
- package/dist/web/assets/log-BNLmms1o.js +1 -0
- package/dist/web/assets/logo-Cluzi2Zq.js +1 -0
- package/dist/web/assets/lua-Dairqw_c.js +1 -0
- package/dist/web/assets/luau-VGWVLoqX.js +1 -0
- package/dist/web/assets/make-Dixweg8N.js +1 -0
- package/dist/web/assets/markdown-BYOwaDjH.js +1 -0
- package/dist/web/assets/marko-C2UPN-L_.js +1 -0
- package/dist/web/assets/material-theme-Bm3Qr25_.js +1 -0
- package/dist/web/assets/material-theme-darker-2IIEA8gg.js +1 -0
- package/dist/web/assets/material-theme-lighter-uhdI0v04.js +1 -0
- package/dist/web/assets/material-theme-ocean-CHQ94UKr.js +1 -0
- package/dist/web/assets/material-theme-palenight-B5W6OYN7.js +1 -0
- package/dist/web/assets/matlab-D7qyCx1q.js +1 -0
- package/dist/web/assets/mdc-Eijzzwck.js +1 -0
- package/dist/web/assets/mdx-DQZ5AkYe.js +1 -0
- package/dist/web/assets/mermaid-BxpfTIzt.js +1 -0
- package/dist/web/assets/min-dark-BSWPekZh.js +1 -0
- package/dist/web/assets/min-light-DDpmG2fV.js +1 -0
- package/dist/web/assets/mipsasm-BMqwQI7S.js +1 -0
- package/dist/web/assets/mojo-fg40_ntQ.js +1 -0
- package/dist/web/assets/monokai-BscAjiQZ.js +1 -0
- package/dist/web/assets/moonbit-D9W784E_.js +1 -0
- package/dist/web/assets/move-BYnGhZoM.js +1 -0
- package/dist/web/assets/narrat-_X_XdTYD.js +1 -0
- package/dist/web/assets/nextflow-CSgcYZcx.js +1 -0
- package/dist/web/assets/nextflow-groovy-D7IRt6Pp.js +1 -0
- package/dist/web/assets/nginx-BF8eLz9n.js +1 -0
- package/dist/web/assets/night-owl-DhmEMT88.js +1 -0
- package/dist/web/assets/night-owl-light-eJ-hLW7d.js +1 -0
- package/dist/web/assets/nim-BlPfC-Lu.js +1 -0
- package/dist/web/assets/nix-IvuFDN5E.js +1 -0
- package/dist/web/assets/nord-Cb4Vim4T.js +1 -0
- package/dist/web/assets/nsis-CjNLYCLg.js +1 -0
- package/dist/web/assets/nushell--0fT3PI1.js +1 -0
- package/dist/web/assets/objective-c-D1A_Heim.js +1 -0
- package/dist/web/assets/objective-cpp-BsSzOQcm.js +1 -0
- package/dist/web/assets/ocaml-O90oeIOV.js +1 -0
- package/dist/web/assets/odin-B1RWQWA5.js +1 -0
- package/dist/web/assets/one-dark-pro-C6YyR8OD.js +1 -0
- package/dist/web/assets/one-light-D7Lr4KcI.js +1 -0
- package/dist/web/assets/openscad-BUDT5pXO.js +1 -0
- package/dist/web/assets/org-jZgCqYxI.js +1 -0
- package/dist/web/assets/pascal-4ZHwLPI5.js +1 -0
- package/dist/web/assets/perl-BIZHUlMC.js +1 -0
- package/dist/web/assets/php-Bu0o-4mH.js +1 -0
- package/dist/web/assets/pierre-dark-CpLgRqie.js +1 -0
- package/dist/web/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
- package/dist/web/assets/pierre-dark-soft-kZQmAZld.js +1 -0
- package/dist/web/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
- package/dist/web/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
- package/dist/web/assets/pierre-light-CoaEpmwp.js +1 -0
- package/dist/web/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
- package/dist/web/assets/pierre-light-soft-lWLdNTOI.js +1 -0
- package/dist/web/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
- package/dist/web/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
- package/dist/web/assets/pkl-ot-7Btpt.js +1 -0
- package/dist/web/assets/plastic-DQwYfKfQ.js +1 -0
- package/dist/web/assets/plsql-DGHpHOYJ.js +1 -0
- package/dist/web/assets/po-BiJDBrnU.js +1 -0
- package/dist/web/assets/poimandres-DRFjx7u4.js +1 -0
- package/dist/web/assets/polar-C7UOKdEL.js +1 -0
- package/dist/web/assets/postcss-BXeXVLqQ.js +1 -0
- package/dist/web/assets/powerquery-DNMTfnFr.js +1 -0
- package/dist/web/assets/powershell-QtSnmXcu.js +1 -0
- package/dist/web/assets/prisma-DMgNhnYS.js +1 -0
- package/dist/web/assets/prolog-iXnhIJG7.js +1 -0
- package/dist/web/assets/proto-DB4EqR-F.js +1 -0
- package/dist/web/assets/pug-Benj2kck.js +1 -0
- package/dist/web/assets/puppet-CDv2pdJW.js +1 -0
- package/dist/web/assets/purescript-9MfHhQsQ.js +1 -0
- package/dist/web/assets/python-gzcpVVnB.js +1 -0
- package/dist/web/assets/qml-BHSeoMrW.js +1 -0
- package/dist/web/assets/qmldir-DCQb3MpD.js +1 -0
- package/dist/web/assets/qss-Fe1Jh2GI.js +1 -0
- package/dist/web/assets/r-CUvEVkuI.js +1 -0
- package/dist/web/assets/racket-DcIDlBhZ.js +1 -0
- package/dist/web/assets/raku-B3gFvitq.js +1 -0
- package/dist/web/assets/razor-BwPBMIxs.js +1 -0
- package/dist/web/assets/rbs-C0MpOrj4.js +1 -0
- package/dist/web/assets/red-CYlAq2b0.js +1 -0
- package/dist/web/assets/reg-CRGYupPL.js +1 -0
- package/dist/web/assets/regexp-_ifpjSOj.js +1 -0
- package/dist/web/assets/rel-BtDbiS_P.js +1 -0
- package/dist/web/assets/riscv-Ckw8ddFX.js +1 -0
- package/dist/web/assets/ron-VUp2lXgN.js +1 -0
- package/dist/web/assets/rose-pine-aMsaMD1D.js +1 -0
- package/dist/web/assets/rose-pine-dawn-8PwUyIlP.js +1 -0
- package/dist/web/assets/rose-pine-moon-DO-qXg-I.js +1 -0
- package/dist/web/assets/rosmsg-CAekHB0j.js +1 -0
- package/dist/web/assets/rst-Es4SE5IJ.js +1 -0
- package/dist/web/assets/ruby-76YJjt1j.js +1 -0
- package/dist/web/assets/rust-DpiIf5JO.js +1 -0
- package/dist/web/assets/sas-BEWDsPnW.js +1 -0
- package/dist/web/assets/sass-DXrisJhu.js +1 -0
- package/dist/web/assets/scala-BiFt_7mu.js +1 -0
- package/dist/web/assets/scheme-DQCgrYNe.js +1 -0
- package/dist/web/assets/scss-DgcF5VZK.js +1 -0
- package/dist/web/assets/sdbl-bTVj8UrX.js +1 -0
- package/dist/web/assets/shaderlab-TOUzSsQk.js +1 -0
- package/dist/web/assets/shellscript-Cty74sqM.js +1 -0
- package/dist/web/assets/shellsession-BCfSy2gp.js +1 -0
- package/dist/web/assets/slack-dark-DnToyrRv.js +1 -0
- package/dist/web/assets/slack-ochin-B2OO5cIa.js +1 -0
- package/dist/web/assets/smalltalk-BHpQb5nM.js +1 -0
- package/dist/web/assets/smithy-Dgaz7aur.js +1 -0
- package/dist/web/assets/snazzy-light-4G7pJPwS.js +1 -0
- package/dist/web/assets/solarized-dark-b97czJOk.js +1 -0
- package/dist/web/assets/solarized-light-DFPxpIYQ.js +1 -0
- package/dist/web/assets/solidity-Bj7E1mRn.js +1 -0
- package/dist/web/assets/soy-dM_k5IdG.js +1 -0
- package/dist/web/assets/sparql-D_iOobhT.js +1 -0
- package/dist/web/assets/splunk-BC2Px7Mm.js +1 -0
- package/dist/web/assets/sql-Dr4UDgPO.js +1 -0
- package/dist/web/assets/ssh-config-BgfXC-Er.js +1 -0
- package/dist/web/assets/stata-COCyw4rB.js +1 -0
- package/dist/web/assets/stylus-B6D30XZt.js +1 -0
- package/dist/web/assets/surrealql-Cc-YKbr5.js +1 -0
- package/dist/web/assets/svelte-BBNgvYF5.js +1 -0
- package/dist/web/assets/swift-CyEgAFGc.js +1 -0
- package/dist/web/assets/synthwave-84-nFMaYfgc.js +1 -0
- package/dist/web/assets/system-verilog-Dh5ihWbf.js +1 -0
- package/dist/web/assets/systemd-BxMlprV5.js +1 -0
- package/dist/web/assets/talonscript-CohzipZa.js +1 -0
- package/dist/web/assets/tasl-DMoTqEGO.js +1 -0
- package/dist/web/assets/tcl-CZd0xW_V.js +1 -0
- package/dist/web/assets/templ-D8WXh8mG.js +1 -0
- package/dist/web/assets/terraform-DswuEJGm.js +1 -0
- package/dist/web/assets/tex-BNsfM6-w.js +1 -0
- package/dist/web/assets/tokyo-night-oM2G3aXe.js +1 -0
- package/dist/web/assets/toml-CcmNWLt0.js +1 -0
- package/dist/web/assets/ts-tags-C5cp5d5e.js +1 -0
- package/dist/web/assets/tsv-sltzmVWM.js +1 -0
- package/dist/web/assets/tsx-D0nBgoOJ.js +1 -0
- package/dist/web/assets/turtle-ByJddavk.js +1 -0
- package/dist/web/assets/twig-Clef_vTO.js +1 -0
- package/dist/web/assets/typescript-84qFVDd8.js +1 -0
- package/dist/web/assets/typespec-BGhR2SqG.js +1 -0
- package/dist/web/assets/typst-kHrpsAej.js +1 -0
- package/dist/web/assets/v-DBj4K9d_.js +1 -0
- package/dist/web/assets/vala-zf12oZj6.js +1 -0
- package/dist/web/assets/vb-DOwzaHZ0.js +1 -0
- package/dist/web/assets/verilog-B2gwz-sJ.js +1 -0
- package/dist/web/assets/vesper-DdrHHSXu.js +1 -0
- package/dist/web/assets/vhdl-BroJfC0k.js +1 -0
- package/dist/web/assets/viml-DvXPmvsu.js +1 -0
- package/dist/web/assets/vitesse-black-fwtXNY1n.js +1 -0
- package/dist/web/assets/vitesse-dark-BZCL-v6S.js +1 -0
- package/dist/web/assets/vitesse-light-VbXTXTou.js +1 -0
- package/dist/web/assets/vue-D_k3LQNp.js +1 -0
- package/dist/web/assets/vue-html-Bclb5q-s.js +1 -0
- package/dist/web/assets/vue-vine-BvY0ORZK.js +1 -0
- package/dist/web/assets/vyper-CgoNMtux.js +1 -0
- package/dist/web/assets/wasm-BnjxR4X6.js +1 -0
- package/dist/web/assets/wasm-ByWQv1Qj.js +1 -0
- package/dist/web/assets/wenyan-C8pVoKbM.js +1 -0
- package/dist/web/assets/wgsl-BsKzXJz4.js +1 -0
- package/dist/web/assets/wikitext-ClFFjSW2.js +1 -0
- package/dist/web/assets/wit-DdvCle-K.js +1 -0
- package/dist/web/assets/wolfram-DLL8P-h_.js +1 -0
- package/dist/web/assets/xml-BgfQ1YbS.js +1 -0
- package/dist/web/assets/xsl-DEqH3rje.js +1 -0
- package/dist/web/assets/yaml-BiWwl5JE.js +1 -0
- package/dist/web/assets/zenscript-BnlCZFoB.js +1 -0
- package/dist/web/assets/zig-CMLA9XwU.js +1 -0
- package/dist/web/index.html +16 -0
- package/examples/project.example.yaml +40 -0
- package/package.json +90 -3
- package/server/fonts/Inter-OFL.txt +93 -0
- package/server/fonts/Inter_400Regular.ttf +0 -0
- package/server/fonts/Inter_600SemiBold.ttf +0 -0
- package/server/fonts/Inter_700Bold.ttf +0 -0
- package/server/fonts/JetBrainsMono-OFL.txt +93 -0
- package/server/fonts/JetBrainsMono_400Regular.ttf +0 -0
package/README.md
CHANGED
|
@@ -1,3 +1,127 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/edspencer/local-review/main/brand/lockup-dark.svg">
|
|
4
|
+
<img alt="Local Review" src="https://raw.githubusercontent.com/edspencer/local-review/main/brand/lockup-light.svg" width="380">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
2
7
|
|
|
3
|
-
|
|
8
|
+
<p align="center"><strong>Review your PR stacks before you create them.</strong></p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="https://github.com/edspencer/local-review/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/edspencer/local-review/actions/workflows/ci.yml/badge.svg?branch=main"></a>
|
|
12
|
+
<a href="https://www.npmjs.com/package/local-review"><img alt="npm" src="https://img.shields.io/npm/v/local-review?color=4a1942"></a>
|
|
13
|
+
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue"></a>
|
|
14
|
+
<a href="https://local-review.edspencer.net"><img alt="Docs" src="https://img.shields.io/badge/docs-local--review.edspencer.net-4a1942"></a>
|
|
15
|
+
<img alt="Node.js 22 or later" src="https://img.shields.io/badge/node-%E2%89%A522-339933">
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img alt="Local Review showing a PR: the plum "not on GitHub" header, a sidebar of colour-coded PR stacks with readiness badges, and the PR's title, readiness toggle and Conversation tab" src="https://raw.githubusercontent.com/edspencer/local-review/main/docs/media/readme-hero.png" width="800">
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
Local Review is a GitHub-style pull-request UI for the **stack of commits on your machine**, before any of it is
|
|
23
|
+
pushed. Every commit is shown as its own PR, grouped into the stacks you mean to open. You review them like PRs, your
|
|
24
|
+
coding agent reads your comments and replies in the same place, and when a stack is right, the agent publishes it as
|
|
25
|
+
GitHub stacked PRs.
|
|
26
|
+
|
|
27
|
+
- **Every commit is a PR**, with a Conversation tab, a Files changed tab and a readiness badge, grouped into
|
|
28
|
+
colour-coded [PR stacks](https://local-review.edspencer.net/concepts/pr-stacks/), across one repo or several.
|
|
29
|
+
- **Your agent answers in the review.** Comments are plain YAML files that any coding agent can read and reply to; the
|
|
30
|
+
replies show up in your browser live, and threads follow their code through amends and rebases.
|
|
31
|
+
- **Publish when it's ready.** `local-review plan` prints each PR's branch, base, title and body, and your agent opens
|
|
32
|
+
the stack as [GitHub native stacked PRs](https://local-review.edspencer.net/agents/publishing-stacked-prs/). It
|
|
33
|
+
works in front of any [stacking tool](https://local-review.edspencer.net/concepts/stacking-tools/).
|
|
34
|
+
- **Safe to point at your work.** It never modifies your repos, never talks to GitHub, never deletes your review, and
|
|
35
|
+
only listens on localhost.
|
|
36
|
+
|
|
37
|
+
<p align="center">
|
|
38
|
+
<img alt="A review round trip: the reviewer comments on a range of lines, the agent's reply appears in the thread live, the reviewer resolves the thread and marks the PR ready, and the sidebar's stack tally goes from 1/3 to 2/3 ready" src="https://raw.githubusercontent.com/edspencer/local-review/main/docs/media/demo.gif" width="800">
|
|
39
|
+
<br><sub>One review round trip, on the fictional demo project the docs use.</sub>
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
## Quickstart
|
|
43
|
+
|
|
44
|
+
Needs Node.js 22 or later and git, on macOS or Linux.
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
npx local-review init my-feature # create ~/.local-review/projects/my-feature/project.yaml
|
|
48
|
+
npx local-review # serve the UI on http://localhost:5622
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Edit `project.yaml` to point at your repo, branch and stacks:
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
title: My feature
|
|
55
|
+
repos:
|
|
56
|
+
- path: ~/code/my-service
|
|
57
|
+
branch: me/my-feature # the range ends at this branch's head
|
|
58
|
+
base: origin/main # and starts at its merge-base with this
|
|
59
|
+
stacks:
|
|
60
|
+
- title: Groundwork
|
|
61
|
+
- title: The feature itself
|
|
62
|
+
starts_at: "Add the feature flag" # the subject of the commit the stack starts at
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then open **http://localhost:5622**. Edits are picked up live. The full walk-through is the
|
|
66
|
+
[Quickstart](https://local-review.edspencer.net/getting-started/quickstart/).
|
|
67
|
+
|
|
68
|
+
> **Upgrading from before 0.2.0?** The default port was 5000; it's now 5622 (macOS uses 5000 for AirPlay). Update
|
|
69
|
+
> bookmarks and agent prompts, or run `npx local-review --port 5000`.
|
|
70
|
+
|
|
71
|
+
## For agents
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
npx local-review guide
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
prints the guide for coding agents driving Local Review from the repo they work on: the data model, how to answer
|
|
78
|
+
review threads, fix a commit mid-stack, tag branches, publish stacked PRs and record merges. It's
|
|
79
|
+
[AGENT-GUIDE.md](AGENT-GUIDE.md) in this repo, and [on the docs site](https://local-review.edspencer.net/agents/agent-guide/).
|
|
80
|
+
To hand a reviewed project to your agent, paste the
|
|
81
|
+
[hand-off prompt](https://local-review.edspencer.net/agents/hand-off-prompt/).
|
|
82
|
+
|
|
83
|
+
## Documentation
|
|
84
|
+
|
|
85
|
+
**https://local-review.edspencer.net**
|
|
86
|
+
|
|
87
|
+
- **Getting started:** [Quickstart](https://local-review.edspencer.net/getting-started/quickstart/) ·
|
|
88
|
+
[Installation](https://local-review.edspencer.net/getting-started/installation/)
|
|
89
|
+
- **Concepts:** [Projects & repos](https://local-review.edspencer.net/concepts/projects-and-repos/) ·
|
|
90
|
+
[PR stacks](https://local-review.edspencer.net/concepts/pr-stacks/) ·
|
|
91
|
+
[Readiness](https://local-review.edspencer.net/concepts/readiness/) ·
|
|
92
|
+
[Landed PRs](https://local-review.edspencer.net/concepts/landed-prs/) ·
|
|
93
|
+
[Following rebases](https://local-review.edspencer.net/concepts/following-rebases/)
|
|
94
|
+
- **Using it:** [A tour of the UI](https://local-review.edspencer.net/using/ui-tour/) ·
|
|
95
|
+
[Reviewing & comments](https://local-review.edspencer.net/using/reviewing-and-comments/) ·
|
|
96
|
+
[Stack pictures](https://local-review.edspencer.net/using/stack-pictures/)
|
|
97
|
+
- **Reference:** [CLI](https://local-review.edspencer.net/reference/cli/) ·
|
|
98
|
+
[project.yaml](https://local-review.edspencer.net/reference/project-yaml/) ·
|
|
99
|
+
[Comment files](https://local-review.edspencer.net/reference/comment-files/) ·
|
|
100
|
+
[HTTP API](https://local-review.edspencer.net/reference/http-api/) ·
|
|
101
|
+
[Where your data lives](https://local-review.edspencer.net/reference/data-directory/)
|
|
102
|
+
|
|
103
|
+
## Guarantees
|
|
104
|
+
|
|
105
|
+
- **Read-only:** it never fetches, checks out, commits or rebases; all git access goes through one read-only
|
|
106
|
+
allow-list. [Read-only guarantee](https://local-review.edspencer.net/guarantees/read-only/)
|
|
107
|
+
- **Never deletes:** your review lives in `~/.local-review`, never in your repos; writes are atomic, nothing is
|
|
108
|
+
unlinked, and it's snapshotted hourly. [Never-delete guarantee](https://local-review.edspencer.net/guarantees/never-delete/)
|
|
109
|
+
- **Localhost only:** it binds 127.0.0.1 and rejects non-localhost `Host` and `Origin` headers.
|
|
110
|
+
[Localhost security](https://local-review.edspencer.net/guarantees/localhost-security/)
|
|
111
|
+
|
|
112
|
+
And so you can't mistake it for github.com, its header is deep plum with an amber underline and a "not on GitHub" pill.
|
|
113
|
+
|
|
114
|
+
## Contributing
|
|
115
|
+
|
|
116
|
+
Contributions are welcome: see [CONTRIBUTING.md](CONTRIBUTING.md), and [AGENTS.md](AGENTS.md) for coding agents
|
|
117
|
+
working on this repo. Report vulnerabilities privately as described in [SECURITY.md](SECURITY.md).
|
|
118
|
+
How AI was used to build it: [AI-DECLARATION.md](AI-DECLARATION.md).
|
|
119
|
+
Releases: [RELEASING.md](RELEASING.md).
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT, see [LICENSE](LICENSE).
|
|
124
|
+
|
|
125
|
+
The fonts bundled in `server/fonts/` for the stack picture are [Inter](https://github.com/rsms/inter) and
|
|
126
|
+
[JetBrains Mono](https://github.com/JetBrains/JetBrainsMono), both under the SIL Open Font License 1.1
|
|
127
|
+
(`server/fonts/*-OFL.txt`). npm dependencies keep their own licenses.
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// A tiny deterministic git history builder for the demo test bed. Commits are built in memory (each one a full
|
|
2
|
+
// snapshot of the tree) and written with one `git fast-import` per repo, with fixed authors and dates and git's
|
|
3
|
+
// config isolated from the machine's, so every run on every machine produces the same shas.
|
|
4
|
+
import { execFile, execFileSync } from 'node:child_process';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
/** The environment every git command here runs with: nothing from the machine's git config, no signing, no hooks. */
|
|
8
|
+
export function gitEnv() {
|
|
9
|
+
const env = {};
|
|
10
|
+
for (const [k, v] of Object.entries(process.env))
|
|
11
|
+
if (!k.startsWith('GIT_'))
|
|
12
|
+
env[k] = v;
|
|
13
|
+
return { ...env, GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_NOSYSTEM: '1', GIT_TERMINAL_PROMPT: '0', LC_ALL: 'C', TZ: 'UTC' };
|
|
14
|
+
}
|
|
15
|
+
export function git(cwd, args, input) {
|
|
16
|
+
return execFileSync('git', args, { cwd, env: gitEnv(), input, encoding: 'utf8', maxBuffer: 64 << 20, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
17
|
+
}
|
|
18
|
+
/** `git` without blocking: the app builds the example projects with this while it keeps serving. */
|
|
19
|
+
export function gitAsync(cwd, args, input) {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const child = execFile('git', args, { cwd, env: gitEnv(), encoding: 'utf8', maxBuffer: 64 << 20 }, (err, stdout, stderr) => err ? reject(new Error(`git ${args[0]} failed in ${cwd}: ${stderr || err.message}`)) : resolve(stdout));
|
|
22
|
+
// commands that don't read stdin may exit before it's written (EPIPE): their exit status is what counts
|
|
23
|
+
child.stdin?.on('error', () => { });
|
|
24
|
+
child.stdin?.end(input ?? '');
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** Replace exactly one occurrence of `from` (fails loudly if it isn't there, so content edits can't silently drift). */
|
|
28
|
+
export const replace = (from, to) => (old) => {
|
|
29
|
+
const i = old.indexOf(from);
|
|
30
|
+
if (i < 0)
|
|
31
|
+
throw new Error(`replace: text not found:\n${from}`);
|
|
32
|
+
if (old.indexOf(from, i + 1) >= 0)
|
|
33
|
+
throw new Error(`replace: text found more than once:\n${from}`);
|
|
34
|
+
return old.slice(0, i) + to + old.slice(i + from.length);
|
|
35
|
+
};
|
|
36
|
+
/** Several `replace`s in order. */
|
|
37
|
+
export const edits = (...pairs) => (old) => pairs.reduce((s, [a, b]) => replace(a, b)(s), old);
|
|
38
|
+
/** Insert `text` right after the (unique) `anchor`. */
|
|
39
|
+
export const after = (anchor, text) => replace(anchor, anchor + text);
|
|
40
|
+
/** Strip the common indentation of a template literal and its leading newline, keep one trailing newline. */
|
|
41
|
+
export function code(strings, ...vals) {
|
|
42
|
+
let s = strings.reduce((a, str, i) => a + str + (i < vals.length ? String(vals[i]) : ''), '');
|
|
43
|
+
s = s.replace(/^\n/, '').replace(/[ \t]+$/, '');
|
|
44
|
+
const lines = s.split('\n');
|
|
45
|
+
const indent = Math.min(...lines.filter((l) => l.trim()).map((l) => /^ */.exec(l)[0].length));
|
|
46
|
+
return lines.map((l) => l.slice(indent)).join('\n').replace(/\n*$/, '\n');
|
|
47
|
+
}
|
|
48
|
+
/** Go files and Makefiles are indented with tabs on disk; the content scripts write them with 4 spaces. */
|
|
49
|
+
export function onDisk(file, content) {
|
|
50
|
+
if (!/\.go$|(^|\/)Makefile$/.test(file))
|
|
51
|
+
return content;
|
|
52
|
+
return content.replace(/^((?: {4})+)/gm, (m) => '\t'.repeat(m.length / 4));
|
|
53
|
+
}
|
|
54
|
+
const unix = (iso) => Math.floor(new Date(iso).getTime() / 1000);
|
|
55
|
+
export class RepoBuilder {
|
|
56
|
+
name;
|
|
57
|
+
remoteUrl;
|
|
58
|
+
nextMark = 1;
|
|
59
|
+
commits = [];
|
|
60
|
+
byKey = new Map();
|
|
61
|
+
refs = new Map();
|
|
62
|
+
constructor(name, remoteUrl) {
|
|
63
|
+
this.name = name;
|
|
64
|
+
this.remoteUrl = remoteUrl;
|
|
65
|
+
}
|
|
66
|
+
/** A commit on `parent` (null: a root commit). `key` defaults to the subject. */
|
|
67
|
+
commit(parent, message, changes, o) {
|
|
68
|
+
const files = new Map(parent?.files ?? []);
|
|
69
|
+
if (o.merge)
|
|
70
|
+
for (const [p, c] of o.merge.files)
|
|
71
|
+
if (!files.has(p))
|
|
72
|
+
files.set(p, c);
|
|
73
|
+
for (const [p, ch] of Object.entries(changes)) {
|
|
74
|
+
if (ch === null) {
|
|
75
|
+
if (!files.delete(p))
|
|
76
|
+
throw new Error(`${this.name}: delete of missing file ${p}`);
|
|
77
|
+
}
|
|
78
|
+
else if (typeof ch === 'function') {
|
|
79
|
+
const old = files.get(p);
|
|
80
|
+
if (old === undefined)
|
|
81
|
+
throw new Error(`${this.name}: edit of missing file ${p} in "${message.split('\n')[0]}"`);
|
|
82
|
+
files.set(p, ch(old));
|
|
83
|
+
}
|
|
84
|
+
else
|
|
85
|
+
files.set(p, ch);
|
|
86
|
+
}
|
|
87
|
+
const subject = message.split('\n')[0];
|
|
88
|
+
const key = o.key ?? subject;
|
|
89
|
+
if (this.byKey.has(key))
|
|
90
|
+
throw new Error(`${this.name}: duplicate commit key ${key}`);
|
|
91
|
+
const c = {
|
|
92
|
+
mark: this.nextMark++,
|
|
93
|
+
key,
|
|
94
|
+
message: message.replace(/\n*$/, '\n'),
|
|
95
|
+
subject,
|
|
96
|
+
author: o.author,
|
|
97
|
+
authorAt: o.at,
|
|
98
|
+
committer: o.committer ?? o.author,
|
|
99
|
+
committedAt: o.committedAt ?? o.at,
|
|
100
|
+
parent,
|
|
101
|
+
merge: o.merge ?? null,
|
|
102
|
+
changes,
|
|
103
|
+
files,
|
|
104
|
+
};
|
|
105
|
+
this.commits.push(c);
|
|
106
|
+
this.byKey.set(key, c);
|
|
107
|
+
return c;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Replay commits onto `onto`, as `git rebase` would: same message, author and author date, a new committer
|
|
111
|
+
* date. Each copy takes over its original's key; the original stays reachable as `<key>@<tag>`.
|
|
112
|
+
*/
|
|
113
|
+
replay(list, onto, o) {
|
|
114
|
+
let base = onto;
|
|
115
|
+
return list.map((c) => {
|
|
116
|
+
const key = c.key;
|
|
117
|
+
this.rekey(c, `${key}@${o.tag}`);
|
|
118
|
+
// `override`: the copy's changes, when the rebase had to adapt the commit (a fixup squashed into it)
|
|
119
|
+
base = this.commit(base, c.message, o.override?.[key] ?? c.changes, { author: c.author, at: c.authorAt, committer: o.committer ?? c.committer, committedAt: o.at, key });
|
|
120
|
+
return base;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
rekey(c, key) {
|
|
124
|
+
if (this.byKey.has(key))
|
|
125
|
+
throw new Error(`${this.name}: duplicate commit key ${key}`);
|
|
126
|
+
this.byKey.delete(c.key);
|
|
127
|
+
c.key = key;
|
|
128
|
+
this.byKey.set(key, c);
|
|
129
|
+
}
|
|
130
|
+
get(key) {
|
|
131
|
+
const c = this.byKey.get(key);
|
|
132
|
+
if (!c)
|
|
133
|
+
throw new Error(`${this.name}: no commit ${key}`);
|
|
134
|
+
return c;
|
|
135
|
+
}
|
|
136
|
+
sha(key) {
|
|
137
|
+
const s = this.get(key).sha;
|
|
138
|
+
if (!s)
|
|
139
|
+
throw new Error(`${this.name}: not built yet`);
|
|
140
|
+
return s;
|
|
141
|
+
}
|
|
142
|
+
/** A file at a commit, as it is on disk. */
|
|
143
|
+
file(key, file) {
|
|
144
|
+
const s = this.get(key).files.get(file);
|
|
145
|
+
if (s === undefined)
|
|
146
|
+
throw new Error(`${this.name}: no ${file} at ${key}`);
|
|
147
|
+
return onDisk(file, s);
|
|
148
|
+
}
|
|
149
|
+
ref(name, c) {
|
|
150
|
+
this.refs.set(name.startsWith('refs/') ? name : `refs/heads/${name}`, c);
|
|
151
|
+
}
|
|
152
|
+
/** `git init`, one fast-import, then check out `checkout` (the work tree a developer would have). */
|
|
153
|
+
build(dir, checkout) {
|
|
154
|
+
const { steps, marks } = this.plan(dir, checkout);
|
|
155
|
+
for (const [args, input] of steps)
|
|
156
|
+
git(dir, args, input);
|
|
157
|
+
this.readMarks(marks);
|
|
158
|
+
}
|
|
159
|
+
/** The same as `build`, without blocking the event loop. */
|
|
160
|
+
async buildAsync(dir, checkout) {
|
|
161
|
+
const { steps, marks } = this.plan(dir, checkout);
|
|
162
|
+
for (const [args, input] of steps)
|
|
163
|
+
await gitAsync(dir, args, input);
|
|
164
|
+
this.readMarks(marks);
|
|
165
|
+
}
|
|
166
|
+
/** Create `dir` (it mustn't exist: never builds over anything) and list the git commands that fill it. */
|
|
167
|
+
plan(dir, checkout) {
|
|
168
|
+
if (fs.existsSync(dir))
|
|
169
|
+
throw new Error(`${dir} already exists`);
|
|
170
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
171
|
+
const chunks = [];
|
|
172
|
+
const w = (s) => chunks.push(Buffer.from(s, 'utf8'));
|
|
173
|
+
const data = (s) => {
|
|
174
|
+
w(`data ${Buffer.byteLength(s, 'utf8')}\n`);
|
|
175
|
+
w(s);
|
|
176
|
+
w('\n');
|
|
177
|
+
};
|
|
178
|
+
for (const c of this.commits) {
|
|
179
|
+
// every commit goes through one scratch ref, reset first so its parent is exactly what it says (none for a root)
|
|
180
|
+
w(`reset refs/lr-demo/build\ncommit refs/lr-demo/build\nmark :${c.mark}\n`);
|
|
181
|
+
w(`author ${c.author.name} <${c.author.email}> ${unix(c.authorAt)} +0000\n`);
|
|
182
|
+
w(`committer ${c.committer.name} <${c.committer.email}> ${unix(c.committedAt)} +0000\n`);
|
|
183
|
+
data(c.message);
|
|
184
|
+
if (c.parent)
|
|
185
|
+
w(`from :${c.parent.mark}\n`);
|
|
186
|
+
if (c.merge)
|
|
187
|
+
w(`merge :${c.merge.mark}\n`);
|
|
188
|
+
w('deleteall\n');
|
|
189
|
+
for (const p of [...c.files.keys()].sort()) {
|
|
190
|
+
w(`M 100644 inline ${p}\n`);
|
|
191
|
+
data(onDisk(p, c.files.get(p)));
|
|
192
|
+
}
|
|
193
|
+
w('\n');
|
|
194
|
+
}
|
|
195
|
+
for (const [name, c] of [...this.refs].sort(([a], [b]) => a.localeCompare(b)))
|
|
196
|
+
w(`reset ${name}\nfrom :${c.mark}\n\n`);
|
|
197
|
+
const marks = path.join(dir, '.git', 'lr-demo-marks');
|
|
198
|
+
return {
|
|
199
|
+
marks,
|
|
200
|
+
steps: [
|
|
201
|
+
[['init', '-q', '-b', 'main']],
|
|
202
|
+
[['config', 'core.hooksPath', '/dev/null']],
|
|
203
|
+
[['config', 'commit.gpgSign', 'false']],
|
|
204
|
+
[['config', 'gc.auto', '0']], // keep rewritten-away commits (anchors and moved branches point at them)
|
|
205
|
+
[['config', 'rebase.updateRefs', 'true']], // AGENTS.md "Branch tagging"
|
|
206
|
+
[['remote', 'add', 'origin', this.remoteUrl]],
|
|
207
|
+
[['fast-import', '--quiet', '--date-format=raw', `--export-marks=${marks}`], Buffer.concat(chunks).toString('utf8')],
|
|
208
|
+
[['update-ref', '-d', 'refs/lr-demo/build']],
|
|
209
|
+
[['checkout', '-q', '-f', checkout]],
|
|
210
|
+
],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/** Every commit's sha, from fast-import's marks. The file stays in .git: nothing reads it, and this code never
|
|
214
|
+
* deletes a file (the app runs it to create the example projects, so test/fsguard.test.ts scans it like the server). */
|
|
215
|
+
readMarks(marks) {
|
|
216
|
+
const markShas = new Map();
|
|
217
|
+
for (const line of fs.readFileSync(marks, 'utf8').split('\n').filter(Boolean)) {
|
|
218
|
+
const [m, sha] = line.split(' ');
|
|
219
|
+
markShas.set(Number(m.slice(1)), sha);
|
|
220
|
+
}
|
|
221
|
+
for (const c of this.commits)
|
|
222
|
+
c.sha = markShas.get(c.mark);
|
|
223
|
+
}
|
|
224
|
+
}
|