diffing 0.2.1 → 0.4.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/README.md +169 -3
- package/dist/{cli-agent-BNwY5lZE.mjs → cli-agent-B4hU6Xay.mjs} +58 -5
- package/dist/{cli-agent-CbzXDSSF.mjs → cli-agent-B_PHd635.mjs} +58 -5
- package/dist/{cli-agent-ZAkixf4c.mjs → cli-agent-KxNAFIPf.mjs} +6 -2
- package/dist/cli-gh-CDGSpndZ.mjs +147 -0
- package/dist/cli-gh-abs-IJrz.mjs +147 -0
- package/dist/cli.mjs +1214 -56
- package/dist/client/assets/{angular-html-DsssUIMZ.js → angular-html-BVFBJbbm.js} +1 -1
- package/dist/client/assets/{angular-ts-dTg-sJ77.js → angular-ts-CbJB4bNJ.js} +1 -1
- package/dist/client/assets/{apl-C1yUKbqz.js → apl-MzqTwOQT.js} +1 -1
- package/dist/client/assets/arc-DkYz3FVO.js +1 -0
- package/dist/client/assets/architecture-7EHR7CIX-BM5HxebS.js +1 -0
- package/dist/client/assets/architectureDiagram-3BPJPVTR-CU3k1-_g.js +36 -0
- package/dist/client/assets/array-CkF66D10.js +1 -0
- package/dist/client/assets/{astro-BI07ha2y.js → astro-DDblGxmh.js} +1 -1
- package/dist/client/assets/{blade-DkzUGffS.js → blade-DlgT3iuV.js} +1 -1
- package/dist/client/assets/blockDiagram-GPEHLZMM-1lwkHRY_.js +132 -0
- package/dist/client/assets/{bsl-Dx5tFn3i.js → bsl-BrHd4RA2.js} +1 -1
- package/dist/client/assets/{c-KkuqqOvW.js → c-BL61MF4M.js} +1 -1
- package/dist/client/assets/c4Diagram-AAUBKEIU-BPBjnNM8.js +10 -0
- package/dist/client/assets/{cairo-wCCQTdA9.js → cairo-BDZ6TRZS.js} +1 -1
- package/dist/client/assets/channel-BX6tZjG6.js +1 -0
- package/dist/client/assets/chunk-2J33WTMH-C_SASPYW.js +1 -0
- package/dist/client/assets/chunk-3OPIFGDE-CTcbOBts.js +62 -0
- package/dist/client/assets/chunk-4BX2VUAB-BipHnfY4.js +1 -0
- package/dist/client/assets/chunk-4EGX6M5U-C70lfA2p.js +1 -0
- package/dist/client/assets/chunk-55IACEB6-BzJYLlnc.js +1 -0
- package/dist/client/assets/chunk-5DO6E6H7-PprNUwlI.js +1 -0
- package/dist/client/assets/chunk-5ZQYHXKU-CorupEGf.js +2 -0
- package/dist/client/assets/chunk-727SXJPM-Cqjzb4FZ.js +206 -0
- package/dist/client/assets/chunk-AQP2D5EJ-DcaAmtSe.js +231 -0
- package/dist/client/assets/chunk-BR22UD5L-D8XCYMy9.js +1 -0
- package/dist/client/assets/chunk-BSJP7CBP-CiBjSxwv.js +1 -0
- package/dist/client/assets/chunk-CSCIHK7Q-BTbf6qN3.js +124 -0
- package/dist/client/assets/chunk-FHYWG6QK-CcHPrHH1.js +1 -0
- package/dist/client/assets/chunk-FMBD7UC4-BA8_ZS2e.js +15 -0
- package/dist/client/assets/chunk-KSCS5N6A-ChcT2GOp.js +10 -0
- package/dist/client/assets/chunk-L5ZTLDWV-BjzpE0xm.js +1 -0
- package/dist/client/assets/chunk-LZXEDZCA-Bu0azrf_.js +2 -0
- package/dist/client/assets/chunk-MPE355IW-Bf9yHfDS.js +1 -0
- package/dist/client/assets/chunk-MZUSXYTE-CnUIUKI4.js +1 -0
- package/dist/client/assets/chunk-N66VUXT2-ahAf9GvV.js +1 -0
- package/dist/client/assets/chunk-ND2GUHAM-Co55RNsd.js +1 -0
- package/dist/client/assets/chunk-NNHCCRGN-DHF3V75i.js +159 -0
- package/dist/client/assets/chunk-NZK2D7GU-CtPYeO8_.js +1 -0
- package/dist/client/assets/chunk-O5CBEL6O-CzRKb_Hn.js +70 -0
- package/dist/client/assets/chunk-PUPMXCY4-D0KKIhut.js +1 -0
- package/dist/client/assets/chunk-QZHKN3VN-BCSN705V.js +1 -0
- package/dist/client/assets/chunk-UIBZB4QT-TYsZ_c8u.js +1 -0
- package/dist/client/assets/chunk-WCWK7LTN-Dj8SfnMU.js +1 -0
- package/dist/client/assets/chunk-WU5MYG2G-xPWDnrji.js +1 -0
- package/dist/client/assets/chunk-XPW4576I-CYH9wUok.js +32 -0
- package/dist/client/assets/classDiagram-4FO5ZUOK-7xfBUC5E.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CFM5yYso.js +1 -0
- package/dist/client/assets/{cobol-DRh7-jLi.js → cobol-D37UDAkO.js} +1 -1
- package/dist/client/assets/{coffee-wR_ZAh9a.js → coffee-NCjhDwWZ.js} +1 -1
- package/dist/client/assets/cose-bilkent-S5V4N54A-B8RnQ-yF.js +1 -0
- package/dist/client/assets/{cpp-OD_qyywn.js → cpp-CQUNgUz4.js} +1 -1
- package/dist/client/assets/{crystal-E4ahAddE.js → crystal-DGHLJtZ_.js} +1 -1
- package/dist/client/assets/{css-2NgE_uxz.js → css-xCgEbp_3.js} +1 -1
- package/dist/client/assets/cytoscape.esm-B7MQoI9k.js +321 -0
- package/dist/client/assets/dagre-BM42HDAG-CvmQMXPT.js +4 -0
- package/dist/client/assets/dagre-DAhKikc0.js +1 -0
- package/dist/client/assets/defaultLocale-B_TxGd0k.js +1 -0
- package/dist/client/assets/diagram-2AECGRRQ-wIyWTlXo.js +43 -0
- package/dist/client/assets/diagram-5GNKFQAL-BLMMWtHj.js +10 -0
- package/dist/client/assets/diagram-KO2AKTUF-Duz_hHky.js +3 -0
- package/dist/client/assets/diagram-LMA3HP47-CftQIrJ1.js +24 -0
- package/dist/client/assets/diagram-OG6HWLK6-7odjt2qL.js +24 -0
- package/dist/client/assets/dist-PmPhNea3.js +1 -0
- package/dist/client/assets/{edge-8mbscxxD.js → edge-C6_UFojU.js} +1 -1
- package/dist/client/assets/{elixir-CJv1b3Q6.js → elixir-B12MoaTY.js} +1 -1
- package/dist/client/assets/{elm-DUPzqZ9H.js → elm-C80KxC_R.js} +1 -1
- package/dist/client/assets/erDiagram-TEJ5UH35-DOeyf617.js +85 -0
- package/dist/client/assets/{erb-ssvEF26z.js → erb-DqelkF7l.js} +1 -1
- package/dist/client/assets/{erlang-CtK0kQ6z.js → erlang-cuhwpmsu.js} +1 -1
- package/dist/client/assets/eventmodeling-FCH6USID-hlXE2E34.js +1 -0
- package/dist/client/assets/flowDiagram-I6XJVG4X-BJX7W8r0.js +162 -0
- package/dist/client/assets/{fortran-fixed-form-DBM4e0_k.js → fortran-fixed-form-DWr0EnJn.js} +1 -1
- package/dist/client/assets/{fsharp-CmzzGQLe.js → fsharp-CQ5iYSPA.js} +1 -1
- package/dist/client/assets/ganttDiagram-6RSMTGT7-CFWnCca0.js +292 -0
- package/dist/client/assets/{gdresource-B_wbPqHd.js → gdresource-CN-lsNu5.js} +1 -1
- package/dist/client/assets/{git-commit-1z4D6NzL.js → git-commit-6e79eN7Q.js} +1 -1
- package/dist/client/assets/{git-rebase-DiWIK1Oa.js → git-rebase-7L4r_tbx.js} +1 -1
- package/dist/client/assets/gitGraph-WXDBUCRP-D_3ZhTam.js +1 -0
- package/dist/client/assets/gitGraphDiagram-PVQCEYII-oNErRHPR.js +106 -0
- package/dist/client/assets/{glimmer-js-DrHg6AuA.js → glimmer-js-8OQAbTGs.js} +1 -1
- package/dist/client/assets/{glimmer-ts-wQSGOa8A.js → glimmer-ts-3IyQiCHC.js} +1 -1
- package/dist/client/assets/{glsl-OY27QGY0.js → glsl-Bi_bzGJK.js} +1 -1
- package/dist/client/assets/graphlib-DBdw4opr.js +1 -0
- package/dist/client/assets/{graphql-CSWSqe6f.js → graphql-YcGxsBfT.js} +1 -1
- package/dist/client/assets/{hack-Bi1v624Q.js → hack-C9IpUqi6.js} +1 -1
- package/dist/client/assets/{haml-CX94zV9m.js → haml-YWDwVGeX.js} +1 -1
- package/dist/client/assets/{handlebars-C1j6wnjG.js → handlebars-zyG7ocdI.js} +1 -1
- package/dist/client/assets/{html-BcAjMlB7.js → html-CHYhpzfH.js} +1 -1
- package/dist/client/assets/{html-derivative-DcEpOzdx.js → html-derivative-BhgSILdB.js} +1 -1
- package/dist/client/assets/{http-BBxxSc6S.js → http-BPv6bUpq.js} +1 -1
- package/dist/client/assets/{hurl-CpKRNTz4.js → hurl-CpohSten.js} +1 -1
- package/dist/client/assets/{hxml-DC4KwKt2.js → hxml-nwJci6H5.js} +1 -1
- package/dist/client/assets/index-BIrpcT6-.js +3922 -0
- package/dist/client/assets/index-CMyunQt6.css +1 -0
- package/dist/client/assets/info-J43DQDTF-CAllC7mK.js +1 -0
- package/dist/client/assets/infoDiagram-5YYISTIA-D1xM4RZP.js +2 -0
- package/dist/client/assets/init-Ce66tIvf.js +1 -0
- package/dist/client/assets/ishikawaDiagram-YF4QCWOH-7DsxXa1L.js +70 -0
- package/dist/client/assets/{java-CFUlLvAA.js → java-Bt4sAY2m.js} +1 -1
- package/dist/client/assets/{javascript-C-HnT3sE.js → javascript-DZjI42QZ.js} +1 -1
- package/dist/client/assets/{jinja-CPU8RwNP.js → jinja-D2zTW07t.js} +1 -1
- package/dist/client/assets/{jison-CW0TBQOs.js → jison-Dz-ndIM5.js} +1 -1
- package/dist/client/assets/journeyDiagram-JHISSGLW-C_A1TmoL.js +139 -0
- package/dist/client/assets/{json-qUjlNuEJ.js → json-Ykn4LhOH.js} +1 -1
- package/dist/client/assets/{jsx-KWeo60kf.js → jsx-Ce8SKNSt.js} +1 -1
- package/dist/client/assets/{julia-CnJB8y2e.js → julia-DlfibiGU.js} +1 -1
- package/dist/client/assets/{just-Do22d9PP.js → just-CT2PIeY2.js} +1 -1
- package/dist/client/assets/kanban-definition-UN3LZRKU-ccKLE8Df.js +89 -0
- package/dist/client/assets/katex-eRFNlMh0.js +257 -0
- package/dist/client/assets/{latex-CCPgzvKn.js → latex-Dkfo8_p4.js} +1 -1
- package/dist/client/assets/line-BbqJI7bZ.js +1 -0
- package/dist/client/assets/linear-B7c0dSOq.js +1 -0
- package/dist/client/assets/{liquid-B9btdZoI.js → liquid-dWmg5egV.js} +1 -1
- package/dist/client/assets/{lua-IFRifurW.js → lua-6hNhLpj8.js} +1 -1
- package/dist/client/assets/{marko-CCaVBFq1.js → marko-DTVuQVoJ.js} +1 -1
- package/dist/client/assets/{mdc-BtbnOLUc.js → mdc-LbJVW6cj.js} +1 -1
- package/dist/client/assets/mermaid-parser.core-f-eKVdX9.js +4 -0
- package/dist/client/assets/mermaid.core-ClMOD6O3.js +9 -0
- package/dist/client/assets/mindmap-definition-RKZ34NQL-vaLTR09L.js +96 -0
- package/dist/client/assets/{nextflow-CApkvBGW.js → nextflow-DSfydIxt.js} +1 -1
- package/dist/client/assets/{nginx-BkbFZHVt.js → nginx-BvMTgdwH.js} +1 -1
- package/dist/client/assets/{nim-2uCd5j65.js → nim-BdGYhiTu.js} +1 -1
- package/dist/client/assets/ordinal-5ziJS8mo.js +1 -0
- package/dist/client/assets/packet-YPE3B663-Dy58IAiO.js +1 -0
- package/dist/client/assets/path-g3G46IZB.js +1 -0
- package/dist/client/assets/{perl-C4ixqSNT.js → perl-DamEOAxn.js} +1 -1
- package/dist/client/assets/{php-DJ8WOtcI.js → php-DtubpAAy.js} +1 -1
- package/dist/client/assets/pie-LRSECV5Y-FXFFfdz2.js +1 -0
- package/dist/client/assets/pieDiagram-4H26LBE5-yuTkGKaM.js +30 -0
- package/dist/client/assets/{pug-BOaafhT5.js → pug-CMYtl1BN.js} +1 -1
- package/dist/client/assets/{qml-CURE-1y4.js → qml-D76DCV4U.js} +1 -1
- package/dist/client/assets/quadrantDiagram-W4KKPZXB-Db00tlGp.js +7 -0
- package/dist/client/assets/{r-CPWmgzo3.js → r-gMTLVmYP.js} +1 -1
- package/dist/client/assets/radar-GUYGQ44K-BKTzSQ68.js +1 -0
- package/dist/client/assets/{razor-DH83VP_d.js → razor-1oOg77PO.js} +1 -1
- package/dist/client/assets/{regexp-BXVWi-t_.js → regexp-BJzbycG8.js} +1 -1
- package/dist/client/assets/requirementDiagram-4Y6WPE33-CwCGFaVe.js +84 -0
- package/dist/client/assets/rough.esm-BBrzuQ0N.js +1 -0
- package/dist/client/assets/{rst-Bbxxs6E2.js → rst-Be7RtxcU.js} +1 -1
- package/dist/client/assets/{ruby-C-TuWES4.js → ruby-oyNd3WUZ.js} +1 -1
- package/dist/client/assets/sankeyDiagram-5OEKKPKP-D78nH-eA.js +40 -0
- package/dist/client/assets/{sas-njzNYNOq.js → sas-Bx8dCxcK.js} +1 -1
- package/dist/client/assets/{scss-D2i6rHB8.js → scss-DzZEPh1d.js} +1 -1
- package/dist/client/assets/sequenceDiagram-3UESZ5HK-CEy9mjHg.js +162 -0
- package/dist/client/assets/{shaderlab-DXAhMdPo.js → shaderlab-CbbQwRPF.js} +1 -1
- package/dist/client/assets/{shellscript-D6Bxi40C.js → shellscript-t9s0AoBc.js} +1 -1
- package/dist/client/assets/{shellsession-Defxq-I6.js → shellsession-NczJO6Io.js} +1 -1
- package/dist/client/assets/{soy-D0GKXrtT.js → soy-Dl6InSac.js} +1 -1
- package/dist/client/assets/{sparql-BdcL8cbr.js → sparql-DEoxFJ5d.js} +1 -1
- package/dist/client/assets/{sql-BRW_ADkU.js → sql-DXkZEc28.js} +1 -1
- package/dist/client/assets/src-DCIMHd9M.js +1 -0
- package/dist/client/assets/{stata-w7w59F48.js → stata-DmpUDP7M.js} +1 -1
- package/dist/client/assets/stateDiagram-AJRCARHV-Cz0qtN4R.js +1 -0
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Dye5cILT.js +1 -0
- package/dist/client/assets/{surrealql-O-lLuqz7.js → surrealql-BAZp4tA6.js} +1 -1
- package/dist/client/assets/{svelte-DN3DR5-t.js → svelte-DXBMAgu7.js} +1 -1
- package/dist/client/assets/{templ-CJ2JFoia.js → templ-CF-yfRM7.js} +1 -1
- package/dist/client/assets/{tex-CpWrf5g9.js → tex-D5tGX-j9.js} +1 -1
- package/dist/client/assets/timeline-definition-PNZ67QCA-B6_u5aFI.js +120 -0
- package/dist/client/assets/treeView-BLDUP644-DtjHwb6x.js +1 -0
- package/dist/client/assets/treemap-LRROVOQU-GqTXzMFU.js +1 -0
- package/dist/client/assets/{ts-tags-CZ6Af6Pe.js → ts-tags-DWMVxoO1.js} +1 -1
- package/dist/client/assets/{tsx-gIUSpRih.js → tsx-C6QVlWvX.js} +1 -1
- package/dist/client/assets/{twig-DwZINhmw.js → twig-DKFN0Lvm.js} +1 -1
- package/dist/client/assets/{typescript-Dj2aMECR.js → typescript-DVfrnfSW.js} +1 -1
- package/dist/client/assets/vennDiagram-CIIHVFJN-C_-TQFos.js +34 -0
- package/dist/client/assets/{vue-B5K0aWrP.js → vue-BCLQ-g1T.js} +1 -1
- package/dist/client/assets/{vue-html-DKGgrypd.js → vue-html-Ddr0JbE-.js} +1 -1
- package/dist/client/assets/{vue-vine-B5iDYTCh.js → vue-vine-CX36RUs5.js} +1 -1
- package/dist/client/assets/wardley-L42UT6IY-pROVAAhW.js +1 -0
- package/dist/client/assets/wardleyDiagram-YWT4CUSO-BJQg7Pb-.js +78 -0
- package/dist/client/assets/{xml-CbTzxfc-.js → xml-FnMA1W8C.js} +1 -1
- package/dist/client/assets/{xsl-Dj-Be6nx.js → xsl-CLduN3zs.js} +1 -1
- package/dist/client/assets/xychartDiagram-2RQKCTM6-1Ux8tle4.js +7 -0
- package/dist/client/assets/{yaml-rTK8uEjh.js → yaml-B6hNfHm7.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/{mcp-CE77qfjx.mjs → mcp-BWeWR4yd.mjs} +2 -1
- package/dist/{mcp-CFeE13n-.mjs → mcp-DNZQvKKF.mjs} +45 -1
- package/dist/{mcp-SGCrfYGo.mjs → mcp-DO4zfNZX.mjs} +45 -1
- package/dist/plan-format-DVnF8xrq.mjs +126 -0
- package/dist/plan-format-f62JWFI-.mjs +117 -0
- package/dist/plan-format-mSDtsPcC.mjs +132 -0
- package/dist/{server-lock-B4crz9-_.mjs → server-lock-D73NKKbn.mjs} +267 -133
- package/dist/{server-lock-DpCrvL-Z.mjs → server-lock-Dj9fG9md.mjs} +246 -122
- package/package.json +26 -14
- package/dist/cli-agent-BLPX3oOP.mjs +0 -443
- package/dist/cli-agent-Cfuy9fLC.mjs +0 -179
- package/dist/cli-agent-CtNivjQW.mjs +0 -179
- package/dist/cli-agent-DZBvZoXZ.mjs +0 -179
- package/dist/cli-agent-DsqbS3n7.mjs +0 -443
- package/dist/cli-agent-Vbs-ePHv.mjs +0 -443
- package/dist/cli-agent-WzJPMH52.mjs +0 -174
- package/dist/cli-agent-uYUoCA4X.mjs +0 -179
- package/dist/client/assets/index-Bwv7BWUp.js +0 -3959
- package/dist/client/assets/index-CoZyJYkw.css +0 -1
- package/dist/mcp-BQmH_By-.mjs +0 -119
- package/dist/mcp-CLgzC8_a.mjs +0 -119
- package/dist/mcp-D9E2HQZ9.mjs +0 -119
- package/dist/mcp-DKlThflu.mjs +0 -253
- package/dist/mcp-LVbLV17O.mjs +0 -119
- package/dist/mcp-nMwTAut3.mjs +0 -119
- package/dist/server-lock-BbiF7u3F.mjs +0 -493
- package/dist/server-lock-Bx9ixiF6.mjs +0 -575
- package/dist/server-lock-CAdqFSfl.mjs +0 -691
- package/dist/server-lock-DIkImokU.mjs +0 -487
- package/dist/server-lock-DbxpfF3j.mjs +0 -575
- package/dist/update-check-DJuFE9w6.mjs +0 -128
- package/dist/update-check-DSC02MWQ.mjs +0 -129
- package/dist/client/assets/{abap-CkQ0tsz-.js → abap-cWYhO34H.js} +0 -0
- package/dist/client/assets/{actionscript-3-DHm0ZfF4.js → actionscript-3-B80ndJsD.js} +0 -0
- package/dist/client/assets/{ada-DK_ua92Z.js → ada-C0C0SoYB.js} +0 -0
- package/dist/client/assets/{andromeeda-D2ccw8v4.js → andromeeda-Ch2kWS7s.js} +0 -0
- package/dist/client/assets/{apache-DXtpFu7o.js → apache-DBwBm4bB.js} +0 -0
- package/dist/client/assets/{apex-DlnRRWpl.js → apex-FcV7UxQi.js} +0 -0
- package/dist/client/assets/{applescript-CShk62ad.js → applescript-hQs8VNpS.js} +0 -0
- package/dist/client/assets/{ara-CWN-FDkm.js → ara-BOadBOZg.js} +0 -0
- package/dist/client/assets/{asciidoc-Bg-PM9Vn.js → asciidoc-BsfqiBck.js} +0 -0
- package/dist/client/assets/{asm-DKOhR24S.js → asm-C2vUqzqz.js} +0 -0
- package/dist/client/assets/{aurora-x-DpaEirL8.js → aurora-x-BHLqzt7j.js} +0 -0
- package/dist/client/assets/{awk-3ibKHffv.js → awk-D4e-eeZT.js} +0 -0
- package/dist/client/assets/{ayu-dark-ursiSH5k.js → ayu-dark-DCotN_ZT.js} +0 -0
- package/dist/client/assets/{ayu-light-DMjlrKmx.js → ayu-light-CKB6o_im.js} +0 -0
- package/dist/client/assets/{ayu-mirage-DZEMb75h.js → ayu-mirage-B38lW-C5.js} +0 -0
- package/dist/client/assets/{ballerina-xx7VNGxs.js → ballerina-BzvFqbha.js} +0 -0
- package/dist/client/assets/{bat-DGYsZ5wR.js → bat-CFyw2ERn.js} +0 -0
- package/dist/client/assets/{beancount-D_ATR1UV.js → beancount-a6Gsu5WX.js} +0 -0
- package/dist/client/assets/{berry-BqYMORfH.js → berry-C2jyz4Ah.js} +0 -0
- package/dist/client/assets/{bibtex-CQTBDajW.js → bibtex-Dyet-B83.js} +0 -0
- package/dist/client/assets/{bicep-CfNtxPrK.js → bicep-CghvUIvy.js} +0 -0
- package/dist/client/assets/{bird2-C0dUPe3I.js → bird2-nHigvs-R.js} +0 -0
- package/dist/client/assets/{c3-BUwZ5e3k.js → c3-9ZrcmAmr.js} +0 -0
- package/dist/client/assets/{cadence-C3OKDYrr.js → cadence-BPDgYNWy.js} +0 -0
- package/dist/client/assets/{catppuccin-frappe-CMnRXVZP.js → catppuccin-frappe-BY_2ofjk.js} +0 -0
- package/dist/client/assets/{catppuccin-latte-CqhXSjCC.js → catppuccin-latte-D6GXPBmA.js} +0 -0
- package/dist/client/assets/{catppuccin-macchiato-B6kneE0C.js → catppuccin-macchiato-BuW8_cyo.js} +0 -0
- package/dist/client/assets/{catppuccin-mocha-CP8YZpvj.js → catppuccin-mocha-znex9DPe.js} +0 -0
- package/dist/client/assets/{clarity-Crxe8gSr.js → clarity-Cmao5KSK.js} +0 -0
- package/dist/client/assets/{clojure-BuzOUPMr.js → clojure-D2dPuTkk.js} +0 -0
- package/dist/client/assets/{cmake-CCpZLdLI.js → cmake-DaqQuj4A.js} +0 -0
- package/dist/client/assets/{codeowners-Bo1VYpHz.js → codeowners-B748jmvc.js} +0 -0
- package/dist/client/assets/{codeql-Db01hzHE.js → codeql-CXb_8gHR.js} +0 -0
- package/dist/client/assets/{common-lisp-CMlJIc6Y.js → common-lisp-UZn9nc_5.js} +0 -0
- package/dist/client/assets/{coq-BlzBYp3f.js → coq-DtO6yBIf.js} +0 -0
- package/dist/client/assets/{csharp-kfNshAa7.js → csharp-C3YlYcLV.js} +0 -0
- package/dist/client/assets/{csv-Dr8ri5Nd.js → csv-BBwiVr0X.js} +0 -0
- package/dist/client/assets/{cue-jXfhyOwY.js → cue-ScBFXY5f.js} +0 -0
- package/dist/client/assets/{cypher-CayLeiaR.js → cypher-CGStaC1X.js} +0 -0
- package/dist/client/assets/{d-BMZQ0mAy.js → d-CuTCIvnX.js} +0 -0
- package/dist/client/assets/{dark-plus-B__q47Nr.js → dark-plus-BvePH-r3.js} +0 -0
- package/dist/client/assets/{dart-gMOEMyuz.js → dart-CqEodbm2.js} +0 -0
- package/dist/client/assets/{dax-C3t5LadU.js → dax-UdE5S0Dh.js} +0 -0
- package/dist/client/assets/{desktop-DmhXLwLR.js → desktop-DfmsfHJA.js} +0 -0
- package/dist/client/assets/{diff-togOXQX6.js → diff-CW0RWGd9.js} +0 -0
- package/dist/client/assets/{docker--6N01olR.js → docker-B1qtW4ha.js} +0 -0
- package/dist/client/assets/{dotenv-Bq930Yx8.js → dotenv-BUzOh1k8.js} +0 -0
- package/dist/client/assets/{dracula-CBkSghcw.js → dracula-kJQjC_Gy.js} +0 -0
- package/dist/client/assets/{dracula-soft-CCSYeoxL.js → dracula-soft-B3XhEjCl.js} +0 -0
- package/dist/client/assets/{dream-maker-BiMf3DHl.js → dream-maker-CBvk18nW.js} +0 -0
- package/dist/client/assets/{emacs-lisp-Dhht7he2.js → emacs-lisp-DvSgLhDc.js} +0 -0
- package/dist/client/assets/{everforest-dark-NqSz4oHS.js → everforest-dark-u0zLn75q.js} +0 -0
- package/dist/client/assets/{everforest-light-a3Yzs_lT.js → everforest-light-D3syIoAR.js} +0 -0
- package/dist/client/assets/{fennel-BExc9TZf.js → fennel-C-1kWlGf.js} +0 -0
- package/dist/client/assets/{fish-C2p-AJmZ.js → fish-CCpdtwqS.js} +0 -0
- package/dist/client/assets/{fluent-CeYzs8Qy.js → fluent-BLg032oD.js} +0 -0
- package/dist/client/assets/{fortran-free-form-BIQEK2Jw.js → fortran-free-form-Br4jRtSc.js} +0 -0
- package/dist/client/assets/{gdscript-B5JQPh_-.js → gdscript-hSAZM1eh.js} +0 -0
- package/dist/client/assets/{gdshader-vVjJ_Qr8.js → gdshader-D4XNAEs9.js} +0 -0
- package/dist/client/assets/{genie-DGl5rqC8.js → genie-DmN-ICAN.js} +0 -0
- package/dist/client/assets/{gherkin-CY_VLUnD.js → gherkin-cuHHmRcX.js} +0 -0
- package/dist/client/assets/{github-dark-ClO4AStc.js → github-dark-CEBLh0--.js} +0 -0
- package/dist/client/assets/{github-dark-default-CM0dPco8.js → github-dark-default-Dq0Z4Xai.js} +0 -0
- package/dist/client/assets/{github-dark-dimmed-Cb6xxpXY.js → github-dark-dimmed-D90YmS4w.js} +0 -0
- package/dist/client/assets/{github-dark-high-contrast-BpYtx2-M.js → github-dark-high-contrast-B6BWv1iC.js} +0 -0
- package/dist/client/assets/{github-light-default-BmnCWL6x.js → github-light-default-DsPWmj4v.js} +0 -0
- package/dist/client/assets/{github-light-high-contrast-rpagNF5O.js → github-light-high-contrast-C7aLb3kp.js} +0 -0
- package/dist/client/assets/{github-light-CeOVFgf_.js → github-light-lXSVSq5T.js} +0 -0
- package/dist/client/assets/{gleam-BarIG9kQ.js → gleam-D8VhqPDN.js} +0 -0
- package/dist/client/assets/{gn-DhYsTPYc.js → gn-BjGRlX27.js} +0 -0
- package/dist/client/assets/{gnuplot-CucLlTBc.js → gnuplot-BZCXuE4q.js} +0 -0
- package/dist/client/assets/{go-toi-uWvg.js → go-BJpWXZEs.js} +0 -0
- package/dist/client/assets/{groovy-Ds4dis_k.js → groovy-D9w3Dn-z.js} +0 -0
- package/dist/client/assets/{gruvbox-dark-hard-aPiW8pQh.js → gruvbox-dark-hard-_deksD0U.js} +0 -0
- package/dist/client/assets/{gruvbox-dark-medium-2HJCvzsS.js → gruvbox-dark-medium-CT31284j.js} +0 -0
- package/dist/client/assets/{gruvbox-dark-soft-Da97ODX9.js → gruvbox-dark-soft-BZuEUCbj.js} +0 -0
- package/dist/client/assets/{gruvbox-light-hard-BXlEipFe.js → gruvbox-light-hard-dxv4n_ZS.js} +0 -0
- package/dist/client/assets/{gruvbox-light-medium-Cun-GjGk.js → gruvbox-light-medium-BsW_fVfR.js} +0 -0
- package/dist/client/assets/{gruvbox-light-soft-Bd3qiiSw.js → gruvbox-light-soft-BiGy99gD.js} +0 -0
- package/dist/client/assets/{haskell-B_jvz5Po.js → haskell-DqDdKEgk.js} +0 -0
- package/dist/client/assets/{haxe-BRbuo5UO.js → haxe-DMg0jS3a.js} +0 -0
- package/dist/client/assets/{hcl-BzNUCtsK.js → hcl-SZIZUK_X.js} +0 -0
- package/dist/client/assets/{hjson-BxVo3sS2.js → hjson-DF1VspiE.js} +0 -0
- package/dist/client/assets/{hlsl-C8Wes1nf.js → hlsl-DPe9L0Dt.js} +0 -0
- package/dist/client/assets/{horizon-D54PjLAY.js → horizon-Ngud75Kn.js} +0 -0
- package/dist/client/assets/{horizon-bright-D9RccdIr.js → horizon-bright-Cnljd0XD.js} +0 -0
- package/dist/client/assets/{houston-CMu1N_4K.js → houston-BenZxlMr.js} +0 -0
- package/dist/client/assets/{hy-dMrJKKU_.js → hy-B0f-B1mH.js} +0 -0
- package/dist/client/assets/{imba-DG_oO8tg.js → imba-Bq3j1cW5.js} +0 -0
- package/dist/client/assets/{ini-geHw79Aj.js → ini-BayloP2U.js} +0 -0
- package/dist/client/assets/{json5-CDUsNqz6.js → json5-CRSfdIrY.js} +0 -0
- package/dist/client/assets/{jsonc-D48xcwyD.js → jsonc-y4g9DQPx.js} +0 -0
- package/dist/client/assets/{jsonl-cpIYDXEe.js → jsonl-D2paVn9r.js} +0 -0
- package/dist/client/assets/{jsonnet-Da1lhszN.js → jsonnet-CLH27mtz.js} +0 -0
- package/dist/client/assets/{jssm-B8BLrEGL.js → jssm-CPPYaqX-.js} +0 -0
- package/dist/client/assets/{kanagawa-dragon-2Sk8Rx83.js → kanagawa-dragon-Bt5mhLu1.js} +0 -0
- package/dist/client/assets/{kanagawa-lotus-CQVSWfJp.js → kanagawa-lotus-FGA3pRJk.js} +0 -0
- package/dist/client/assets/{kanagawa-wave-BKWNioLX.js → kanagawa-wave-DnLVZPAD.js} +0 -0
- package/dist/client/assets/{kdl-B-2tSRmp.js → kdl-D1BQjjnw.js} +0 -0
- package/dist/client/assets/{kotlin-DepsEzh3.js → kotlin-ndCaDnf0.js} +0 -0
- package/dist/client/assets/{kusto-BcCtXIb8.js → kusto-D-4nbkD3.js} +0 -0
- package/dist/client/assets/{laserwave-BUqkO46F.js → laserwave-D2qAZkaG.js} +0 -0
- package/dist/client/assets/{lean-CNO9ffpX.js → lean-YmtAsB0p.js} +0 -0
- package/dist/client/assets/{less-Dj0T9pWC.js → less-DyHub3ow.js} +0 -0
- package/dist/client/assets/{light-plus-C9Hnuc1T.js → light-plus-CP13QUvm.js} +0 -0
- package/dist/client/assets/{llvm-DbV9b66l.js → llvm-DCcqrY0D.js} +0 -0
- package/dist/client/assets/{log-BMblmPyH.js → log-DWAAMNbe.js} +0 -0
- package/dist/client/assets/{logo-B78qMxws.js → logo-ClQMGkNL.js} +0 -0
- package/dist/client/assets/{luau-BUd8xsdE.js → luau-CmcXFvLt.js} +0 -0
- package/dist/client/assets/{make-Ev-6cJrd.js → make-DcfJyUzm.js} +0 -0
- package/dist/client/assets/{markdown-CS4LI6sN.js → markdown-DXxotAxn.js} +0 -0
- package/dist/client/assets/{material-theme-5rA1zn0D.js → material-theme-JFoPTBSr.js} +0 -0
- package/dist/client/assets/{material-theme-darker-DGOEiL65.js → material-theme-darker-BHaqZ19v.js} +0 -0
- package/dist/client/assets/{material-theme-lighter-CULfFRX0.js → material-theme-lighter-CGnnS2bs.js} +0 -0
- package/dist/client/assets/{material-theme-ocean-LQZ9CleP.js → material-theme-ocean-Drhewjqf.js} +0 -0
- package/dist/client/assets/{material-theme-palenight-Di_f5oks.js → material-theme-palenight-DbUypmpb.js} +0 -0
- package/dist/client/assets/{matlab-C0qEtAx9.js → matlab-BlfbTznR.js} +0 -0
- package/dist/client/assets/{mdx-CzvnbLXb.js → mdx-H8fyPQd8.js} +0 -0
- package/dist/client/assets/{mermaid-BPquZaKt.js → mermaid-CWAV2dmV.js} +0 -0
- package/dist/client/assets/{min-dark-rkuIbtJV.js → min-dark-BEelvXUB.js} +0 -0
- package/dist/client/assets/{min-light-Dj_HOGsr.js → min-light-CTPBOACR.js} +0 -0
- package/dist/client/assets/{mipsasm-k5DQoS6j.js → mipsasm-BWYFAm2s.js} +0 -0
- package/dist/client/assets/{mojo-sTTDuNnR.js → mojo-DZcPJEIN.js} +0 -0
- package/dist/client/assets/{monokai-D5p-u2ij.js → monokai-DQlZXbkg.js} +0 -0
- package/dist/client/assets/{moonbit-j5ChFtBC.js → moonbit-6eslNlJL.js} +0 -0
- package/dist/client/assets/{move-Dj0Gmn6R.js → move-DxFc9-QU.js} +0 -0
- package/dist/client/assets/{narrat-DJgMz2oW.js → narrat-gv38dJOT.js} +0 -0
- package/dist/client/assets/{nextflow-groovy-eDLuZ_oi.js → nextflow-groovy-DV4H3hok.js} +0 -0
- package/dist/client/assets/{night-owl-CEdtPl3A.js → night-owl-BLcrYhaC.js} +0 -0
- package/dist/client/assets/{night-owl-light-GdSldewm.js → night-owl-light-BG-tBJOz.js} +0 -0
- package/dist/client/assets/{nix-CxIPS3pb.js → nix-NlOz3HGM.js} +0 -0
- package/dist/client/assets/{nord-3WFAz5H9.js → nord-Dm2F5_QB.js} +0 -0
- package/dist/client/assets/{nushell-GDfBmoBU.js → nushell-D86Z8aFF.js} +0 -0
- package/dist/client/assets/{objective-c-MFQ_MGUa.js → objective-c-iuJvUjCv.js} +0 -0
- package/dist/client/assets/{objective-cpp-Y83LcBE7.js → objective-cpp-C62JvfMT.js} +0 -0
- package/dist/client/assets/{ocaml-FxYfaJxu.js → ocaml-Rbryo9lg.js} +0 -0
- package/dist/client/assets/{odin-CprihY3z.js → odin-CfaOVNP_.js} +0 -0
- package/dist/client/assets/{one-dark-pro-BB6mpmmA.js → one-dark-pro-DC9SHdX0.js} +0 -0
- package/dist/client/assets/{one-light-BYLoxNtg.js → one-light-Doo39p0H.js} +0 -0
- package/dist/client/assets/{openscad-Bc1FbpOi.js → openscad-DCRhgheK.js} +0 -0
- package/dist/client/assets/{pascal-Bb4d8cUL.js → pascal-CTesTHtV.js} +0 -0
- package/dist/client/assets/{pierre-dark-cF19s-RM.js → pierre-dark-Gtc9M_gS.js} +0 -0
- package/dist/client/assets/{pierre-light-BdpX8OpD.js → pierre-light-EDxMHmhf.js} +0 -0
- package/dist/client/assets/{pkl-tM25Sq0w.js → pkl-BjUMjQkt.js} +0 -0
- package/dist/client/assets/{plastic-D3k2Xp_5.js → plastic-B3CcSpon.js} +0 -0
- package/dist/client/assets/{plsql-CLEfFhSj.js → plsql-BgKEZSSo.js} +0 -0
- package/dist/client/assets/{po-CaAzdrrh.js → po-C5s_CVt8.js} +0 -0
- package/dist/client/assets/{poimandres-bPTvT4Da.js → poimandres-BsK70ma5.js} +0 -0
- package/dist/client/assets/{polar-TG7aTGdK.js → polar-y4hX8qM0.js} +0 -0
- package/dist/client/assets/{postcss-8kqfa9f0.js → postcss-BHCm7Qae.js} +0 -0
- package/dist/client/assets/{powerquery-CtSu1Nfd.js → powerquery-C2vMt8aZ.js} +0 -0
- package/dist/client/assets/{powershell-87lLBHMy.js → powershell-DEcjmg1c.js} +0 -0
- package/dist/client/assets/{prisma-Dnf694DZ.js → prisma-Ba-Tcxy5.js} +0 -0
- package/dist/client/assets/{prolog-C0XbHTWa.js → prolog-B4o261Jv.js} +0 -0
- package/dist/client/assets/{proto-BZP-HLIb.js → proto-o3IZKiMR.js} +0 -0
- package/dist/client/assets/{puppet-CJYWgYC5.js → puppet-BC0dqZ31.js} +0 -0
- package/dist/client/assets/{purescript-B95Q6BAd.js → purescript-CLDw1y5G.js} +0 -0
- package/dist/client/assets/{python-CqTCsi7P.js → python-D1NFioGW.js} +0 -0
- package/dist/client/assets/{qmldir-DlIL4hls.js → qmldir-B7i9e9Vn.js} +0 -0
- package/dist/client/assets/{qss-BW72E0q_.js → qss-DIe6oKX8.js} +0 -0
- package/dist/client/assets/{racket-CCAimEsC.js → racket-CAOC_yyk.js} +0 -0
- package/dist/client/assets/{raku-mSuqElkM.js → raku-Ccua3vW6.js} +0 -0
- package/dist/client/assets/{red-C-DA4yBI.js → red-DtDH206x.js} +0 -0
- package/dist/client/assets/{reg-CGwMBJ2c.js → reg-CAYZT9kE.js} +0 -0
- package/dist/client/assets/{rel-JwrbYgX4.js → rel-cvB6F5nO.js} +0 -0
- package/dist/client/assets/{riscv-CoYyE75b.js → riscv-SYGtE4EN.js} +0 -0
- package/dist/client/assets/{ron-C5wrhnmJ.js → ron-CRt869SB.js} +0 -0
- package/dist/client/assets/{rose-pine-DlrMPYYd.js → rose-pine-CjBfSJ5b.js} +0 -0
- package/dist/client/assets/{rose-pine-dawn-DBHFtZD2.js → rose-pine-dawn-C0xwy7iJ.js} +0 -0
- package/dist/client/assets/{rose-pine-moon-BBfrpe5q.js → rose-pine-moon-BSNy9Ty0.js} +0 -0
- package/dist/client/assets/{rosmsg-DlimTR9t.js → rosmsg-BwrfJ66Z.js} +0 -0
- package/dist/client/assets/{rust-Cxnkxx3h.js → rust-DCKTTPxN.js} +0 -0
- package/dist/client/assets/{sass-DEyqUIpU.js → sass-CorcwySt.js} +0 -0
- package/dist/client/assets/{scala-i3RN3yAN.js → scala-DlI-OmYj.js} +0 -0
- package/dist/client/assets/{scheme-BcKdjYkO.js → scheme-CcNEGkZH.js} +0 -0
- package/dist/client/assets/{sdbl-Be_XWc3n.js → sdbl-AL_m-RO_.js} +0 -0
- package/dist/client/assets/{slack-dark-3PDtm74m.js → slack-dark-rMhrfPm6.js} +0 -0
- package/dist/client/assets/{slack-ochin-CwiuFRJD.js → slack-ochin-9w8X74hU.js} +0 -0
- package/dist/client/assets/{smalltalk-BdBlL6ks.js → smalltalk-Y2ogrfbk.js} +0 -0
- package/dist/client/assets/{snazzy-light-Dyqc8Is2.js → snazzy-light-DHr9ygpP.js} +0 -0
- package/dist/client/assets/{solarized-dark-DZiTscIX.js → solarized-dark-UPcKev9Q.js} +0 -0
- package/dist/client/assets/{solarized-light-DBcfxrfG.js → solarized-light-DUsjROvl.js} +0 -0
- package/dist/client/assets/{solidity-DH-yUSWc.js → solidity-Bfbm9QZQ.js} +0 -0
- package/dist/client/assets/{splunk-CIjc4kxs.js → splunk-6DJA2XFa.js} +0 -0
- package/dist/client/assets/{ssh-config-BKVd4drG.js → ssh-config-CbQtdF-u.js} +0 -0
- package/dist/client/assets/{stylus-tqAsmugv.js → stylus-B_iq45Sj.js} +0 -0
- package/dist/client/assets/{swift-CdDBQ3_Z.js → swift-C7KIgN-p.js} +0 -0
- package/dist/client/assets/{synthwave-84-CbbRs73K.js → synthwave-84-lf_Wt0K_.js} +0 -0
- package/dist/client/assets/{system-verilog-CBqEY-9v.js → system-verilog-C3G9wERd.js} +0 -0
- package/dist/client/assets/{systemd-khRP5zwK.js → systemd-CwxGrojj.js} +0 -0
- package/dist/client/assets/{talonscript-D_lneH-6.js → talonscript-DqnSo1EO.js} +0 -0
- package/dist/client/assets/{tasl-CLymawGe.js → tasl-T8ethp86.js} +0 -0
- package/dist/client/assets/{tcl-BRY1S87U.js → tcl-Ccl366o0.js} +0 -0
- package/dist/client/assets/{terraform-BqohMo2f.js → terraform-BBf6ZI_0.js} +0 -0
- package/dist/client/assets/{tokyo-night-BWUONxad.js → tokyo-night-BF5ata25.js} +0 -0
- package/dist/client/assets/{toml-C4c85Lwg.js → toml-C5Z_BR4r.js} +0 -0
- package/dist/client/assets/{tsv-DsZq9HBQ.js → tsv-rX8sUJs7.js} +0 -0
- package/dist/client/assets/{turtle-Dpr8zLlw.js → turtle-BM-MG8WY.js} +0 -0
- package/dist/client/assets/{typespec-DcqgATB-.js → typespec-C5gKydvR.js} +0 -0
- package/dist/client/assets/{typst-CeVxmuPg.js → typst--_5VchrY.js} +0 -0
- package/dist/client/assets/{v-C2Qsh2-1.js → v-C6PwpmTC.js} +0 -0
- package/dist/client/assets/{vala-CvZ3yL7c.js → vala-4pTaceSj.js} +0 -0
- package/dist/client/assets/{vb-B_p4qDw4.js → vb-DhoR5A6S.js} +0 -0
- package/dist/client/assets/{verilog-D8MGjxD7.js → verilog-B9f_1lPn.js} +0 -0
- package/dist/client/assets/{vesper-DossXiE1.js → vesper-CmBi_iCT.js} +0 -0
- package/dist/client/assets/{vhdl-MLyYh_Gz.js → vhdl-Cy4RAmOe.js} +0 -0
- package/dist/client/assets/{viml-BuYIArcz.js → viml-CFQW-CcD.js} +0 -0
- package/dist/client/assets/{vitesse-black-C3kc1227.js → vitesse-black-CE8RoBJP.js} +0 -0
- package/dist/client/assets/{vitesse-dark-kWOu5V36.js → vitesse-dark-BpF142p_.js} +0 -0
- package/dist/client/assets/{vitesse-light-DMtiL8bK.js → vitesse-light-CMwxPBrg.js} +0 -0
- package/dist/client/assets/{vyper-CNqG09-_.js → vyper-BW5zj9bE.js} +0 -0
- package/dist/client/assets/{wasm-BsQDV7Y0.js → wasm-BigxSLnm.js} +0 -0
- package/dist/client/assets/{wasm-DFVlQlgd.js → wasm-CSccDuAq.js} +0 -0
- package/dist/client/assets/{wenyan-BmOQge13.js → wenyan-BZzM1SQT.js} +0 -0
- package/dist/client/assets/{wgsl-CgWrmvIg.js → wgsl-xBPHbLlq.js} +0 -0
- package/dist/client/assets/{wikitext-DkwckXCk.js → wikitext-BXJWUNJK.js} +0 -0
- package/dist/client/assets/{wit-CtyVFGe7.js → wit-2GnUs67P.js} +0 -0
- package/dist/client/assets/{wolfram-CXE4FrKz.js → wolfram-DIFDmw-g.js} +0 -0
- package/dist/client/assets/{zenscript-DH15wJt0.js → zenscript-DQaUUjbx.js} +0 -0
- package/dist/client/assets/{zig-CqXN9brD.js → zig-BDepRvBi.js} +0 -0
- package/dist/{update-check-KL-2sGxE.mjs → update-check-DAPFCqpF.mjs} +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as
|
|
2
|
+
import { C as toSafeRelativePath, S as isSafePath, _ as getUntrackedFilePathsAsync, a as formatComments, b as listRepoFiles, c as getFileContent, d as getMergeStatus, f as getProjectStorageDir, g as getTabSizeForFiles, h as getShowDiff, i as writeServerLock, l as getGitDiffAsync, m as getRepoRoot, o as getBranchName, p as getRepoName, r as removeServerLock, s as getCustomGitDiffAsync, u as getHunkHistory, v as gitAddFile, x as revertHunk, y as isGitRepo } from "./server-lock-D73NKKbn.mjs";
|
|
3
|
+
import { n as formatPlanReview, r as sectionTitleForLine, t as extractPlanLines } from "./plan-format-mSDtsPcC.mjs";
|
|
4
|
+
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
3
5
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { dirname, extname, join, resolve } from "node:path";
|
|
6
|
+
import { dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
5
7
|
import { existsSync, mkdirSync, readFileSync, watch, writeFileSync } from "node:fs";
|
|
6
8
|
import getPort from "get-port";
|
|
7
|
-
import { parseArgs } from "node:util";
|
|
8
|
-
import { execFileSync } from "node:child_process";
|
|
9
|
+
import { parseArgs, promisify } from "node:util";
|
|
9
10
|
import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
10
11
|
import { homedir } from "node:os";
|
|
11
12
|
import { Hono } from "hono";
|
|
@@ -48,7 +49,11 @@ const DEFAULTS$1 = {
|
|
|
48
49
|
help: false,
|
|
49
50
|
version: false,
|
|
50
51
|
includeUnstaged: true,
|
|
51
|
-
includeUntracked: true
|
|
52
|
+
includeUntracked: true,
|
|
53
|
+
tui: false,
|
|
54
|
+
gpu: false,
|
|
55
|
+
showMode: false,
|
|
56
|
+
showRevspecs: []
|
|
52
57
|
};
|
|
53
58
|
/**
|
|
54
59
|
* All git-diff option definitions for `node:util.parseArgs`.
|
|
@@ -173,6 +178,15 @@ const DIFFING_OPTIONS = {
|
|
|
173
178
|
type: "boolean",
|
|
174
179
|
default: false
|
|
175
180
|
},
|
|
181
|
+
tui: {
|
|
182
|
+
type: "boolean",
|
|
183
|
+
default: false
|
|
184
|
+
},
|
|
185
|
+
gpu: {
|
|
186
|
+
type: "boolean",
|
|
187
|
+
default: false
|
|
188
|
+
},
|
|
189
|
+
"gh-pr": { type: "string" },
|
|
176
190
|
help: {
|
|
177
191
|
type: "boolean",
|
|
178
192
|
short: "h"
|
|
@@ -209,18 +223,37 @@ Output Modes:
|
|
|
209
223
|
- Terminal mode: when output is a pipe, redirect, or non-TTY
|
|
210
224
|
- Web mode: when output is a TTY (interactive terminal)
|
|
211
225
|
|
|
212
|
-
Force a mode with --web or --
|
|
226
|
+
Force a mode with --web, --terminal, or --tui flags.
|
|
227
|
+
--tui is experimental and opt-in. It opens a native-Rust terminal
|
|
228
|
+
interface that mirrors the web dashboard. Falls back to git diff
|
|
229
|
+
output if the environment cannot support a TUI (piped stdin, CI,
|
|
230
|
+
missing binary).
|
|
231
|
+
|
|
232
|
+
Subcommands:
|
|
233
|
+
show <revspec>... Drop-in for 'git show'. Renders commit metadata
|
|
234
|
+
(subject, author, date, message body) above the
|
|
235
|
+
diff in the web UI. Accepts single commits,
|
|
236
|
+
ranges (a..b, a...b), tags, and multiple SHAs.
|
|
237
|
+
Use '--' to separate revspecs from pathspecs.
|
|
213
238
|
|
|
214
239
|
Examples:
|
|
215
240
|
diffing Review uncommitted changes (web UI)
|
|
216
241
|
diffing --staged Review staged changes
|
|
217
242
|
diffing HEAD~3 Review last 3 commits
|
|
218
243
|
diffing main..feature Compare branches
|
|
244
|
+
diffing show HEAD Review a single commit's metadata + diff
|
|
245
|
+
diffing show HEAD~3..HEAD Review the last 3 commits as a series
|
|
246
|
+
diffing show HEAD -- src/ Limit a commit review to a directory
|
|
219
247
|
diffing --diff-algorithm=patience src/ Diff with patience algorithm
|
|
220
248
|
diffing -U10 Diff with 10 context lines
|
|
221
249
|
diffing --word-diff=color Word-level diff in color
|
|
222
250
|
diffing -b -w Ignore whitespace changes
|
|
223
|
-
diffing --host 0.0.0.0 Allow other machines on the LAN to review
|
|
251
|
+
diffing --host 0.0.0.0 Allow other machines on the LAN to review
|
|
252
|
+
diffing --tui Open the experimental TUI (native Rust)
|
|
253
|
+
|
|
254
|
+
Note: 'diffing <sha>' runs 'git diff <sha>' (working tree vs <sha>); it
|
|
255
|
+
is not the same as 'diffing show <sha>'. Use 'show' when you want to
|
|
256
|
+
review a commit's changes.`);
|
|
224
257
|
}
|
|
225
258
|
function printGitDiffHelp() {
|
|
226
259
|
console.log(`
|
|
@@ -464,7 +497,8 @@ function parseDiffOptions(rawArgs) {
|
|
|
464
497
|
}
|
|
465
498
|
for (let i = dashDashIdx + 1; i < rawArgs.length; i++) opts.pathspecs.push(rawArgs[i]);
|
|
466
499
|
} else for (const p of positionals) opts.revisions.push(p);
|
|
467
|
-
if (values.
|
|
500
|
+
if (values.tui) opts.outputMode = "tui";
|
|
501
|
+
else if (values.web) opts.outputMode = "web";
|
|
468
502
|
else if (values.terminal) opts.outputMode = "terminal";
|
|
469
503
|
if (process.stdout.isTTY && opts.outputMode === "auto") opts.outputMode = "web";
|
|
470
504
|
else if (!process.stdout.isTTY && opts.outputMode === "auto") opts.outputMode = "terminal";
|
|
@@ -545,6 +579,21 @@ function buildGitDiffArgs(opts) {
|
|
|
545
579
|
}
|
|
546
580
|
return args;
|
|
547
581
|
}
|
|
582
|
+
/**
|
|
583
|
+
* Transform a `parseDiffOptions(...)` result into a "show mode" variant:
|
|
584
|
+
* moves any parsed revisions into `showRevspecs`, clears `revisions` (so the
|
|
585
|
+
* existing diff-engine path does not try to `git diff` them), and sets the
|
|
586
|
+
* `showMode` flag. Pathspecs and every other flag survive unchanged.
|
|
587
|
+
*
|
|
588
|
+
* Returns the same object for callsite ergonomics — the mutation is in-place
|
|
589
|
+
* because `parseDiffOptions` always returns a fresh object.
|
|
590
|
+
*/
|
|
591
|
+
function intoShowMode(opts) {
|
|
592
|
+
opts.showRevspecs = opts.revisions;
|
|
593
|
+
opts.revisions = [];
|
|
594
|
+
opts.showMode = true;
|
|
595
|
+
return opts;
|
|
596
|
+
}
|
|
548
597
|
//#endregion
|
|
549
598
|
//#region src/lib/diff-engine.ts
|
|
550
599
|
const MAX_BUFFER = 50 * 1024 * 1024;
|
|
@@ -553,10 +602,11 @@ const MAX_BUFFER = 50 * 1024 * 1024;
|
|
|
553
602
|
* or a "custom" mode (specific revisions / pathspecs).
|
|
554
603
|
*
|
|
555
604
|
* Custom mode is used when the user provides explicit revisions or pathspecs
|
|
556
|
-
* beyond just toggling staged/untracked.
|
|
605
|
+
* beyond just toggling staged/untracked. `showMode` is *also* a custom mode
|
|
606
|
+
* — the "Show staged / Show untracked" toggles don't apply to a commit view.
|
|
557
607
|
*/
|
|
558
608
|
function isCustomMode$1(opts) {
|
|
559
|
-
return opts.revisions.length > 0 || opts.pathspecs.length > 0;
|
|
609
|
+
return opts.revisions.length > 0 || opts.pathspecs.length > 0 || opts.showMode;
|
|
560
610
|
}
|
|
561
611
|
function parseFilePaths(patch) {
|
|
562
612
|
const paths = /* @__PURE__ */ new Set();
|
|
@@ -603,8 +653,24 @@ function parseBinaryFiles(patch, untrackedFiles) {
|
|
|
603
653
|
}
|
|
604
654
|
/**
|
|
605
655
|
* Execute a diff asynchronously — used by the server.
|
|
656
|
+
*
|
|
657
|
+
* Show mode bypasses the normal `git diff` path entirely: it resolves
|
|
658
|
+
* revspecs to a commit list and fetches per-commit metadata + patches in
|
|
659
|
+
* one `git log` call. The concatenated patch is returned in the same shape
|
|
660
|
+
* the existing UI pipeline expects (so file tree, binary detection, and
|
|
661
|
+
* tab-size detection all keep working unchanged); the per-commit data
|
|
662
|
+
* rides along on `commits` for the metadata banners.
|
|
606
663
|
*/
|
|
607
664
|
async function executeDiffAsync(opts) {
|
|
665
|
+
if (opts.showMode) {
|
|
666
|
+
const { commits, patch, truncated } = await getShowDiff(opts.showRevspecs, opts.pathspecs);
|
|
667
|
+
return {
|
|
668
|
+
patch,
|
|
669
|
+
args: [],
|
|
670
|
+
commits,
|
|
671
|
+
truncated
|
|
672
|
+
};
|
|
673
|
+
}
|
|
608
674
|
if (isCustomMode$1(opts)) {
|
|
609
675
|
const args = buildGitDiffArgs(opts);
|
|
610
676
|
return {
|
|
@@ -624,10 +690,10 @@ async function executeDiffAsync(opts) {
|
|
|
624
690
|
* Execute a diff and produce the full enriched result used by the web API.
|
|
625
691
|
*/
|
|
626
692
|
async function executeDiffWithMeta(opts) {
|
|
627
|
-
const { patch } = await executeDiffAsync(opts);
|
|
693
|
+
const { patch, commits, truncated } = await executeDiffAsync(opts);
|
|
628
694
|
const repoName = getRepoName();
|
|
629
695
|
const branch = getBranchName();
|
|
630
|
-
const untrackedFiles = opts.includeUntracked ? await getUntrackedFilePathsAsync() : [];
|
|
696
|
+
const untrackedFiles = opts.includeUntracked && !opts.showMode ? await getUntrackedFilePathsAsync() : [];
|
|
631
697
|
const binaryFiles = parseBinaryFiles(patch, new Set(untrackedFiles));
|
|
632
698
|
const filePaths = parseFilePaths(patch);
|
|
633
699
|
return {
|
|
@@ -637,14 +703,19 @@ async function executeDiffWithMeta(opts) {
|
|
|
637
703
|
tabSizeMap: getTabSizeForFiles(filePaths),
|
|
638
704
|
untrackedFiles,
|
|
639
705
|
repoName,
|
|
640
|
-
branch
|
|
706
|
+
branch,
|
|
707
|
+
...commits ? { commits } : {},
|
|
708
|
+
...truncated ? { truncated } : {}
|
|
641
709
|
};
|
|
642
710
|
}
|
|
643
711
|
/**
|
|
644
712
|
* Run the diff in terminal mode: forward to git diff with full flags,
|
|
645
|
-
* output to stdout. Behaves identically to `git diff`.
|
|
713
|
+
* output to stdout. Behaves identically to `git diff`. In show mode this
|
|
714
|
+
* delegates to `git show` instead so `diffing show <rev>... --terminal`
|
|
715
|
+
* is a drop-in for `git show <rev>...`.
|
|
646
716
|
*/
|
|
647
717
|
function runTerminalDiff(opts) {
|
|
718
|
+
if (opts.showMode) return runTerminalShow(opts);
|
|
648
719
|
const args = buildGitDiffArgs(opts);
|
|
649
720
|
const enforceDefaults = !opts.outputFormat && !opts.quiet;
|
|
650
721
|
const finalArgs = [];
|
|
@@ -665,6 +736,30 @@ function runTerminalDiff(opts) {
|
|
|
665
736
|
}
|
|
666
737
|
}
|
|
667
738
|
/**
|
|
739
|
+
* Terminal mode for `diffing show`: spawn `git show` with the user's revspecs
|
|
740
|
+
* and pathspecs, inherit stdio, propagate exit code. We pass `--no-ext-diff`
|
|
741
|
+
* so the output is a clean unified diff regardless of the user's git config
|
|
742
|
+
* (matches the web-mode pipeline), but otherwise hand everything to git.
|
|
743
|
+
*/
|
|
744
|
+
function runTerminalShow(opts) {
|
|
745
|
+
const args = [
|
|
746
|
+
"show",
|
|
747
|
+
"--no-ext-diff",
|
|
748
|
+
...opts.showRevspecs
|
|
749
|
+
];
|
|
750
|
+
if (opts.pathspecs.length > 0) args.push("--", ...opts.pathspecs);
|
|
751
|
+
try {
|
|
752
|
+
execFileSync("git", args, {
|
|
753
|
+
encoding: "utf-8",
|
|
754
|
+
maxBuffer: MAX_BUFFER,
|
|
755
|
+
stdio: "inherit"
|
|
756
|
+
});
|
|
757
|
+
return 0;
|
|
758
|
+
} catch (err) {
|
|
759
|
+
return err.status ?? 1;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
668
763
|
* Validate the environment: check that we're in a git repo.
|
|
669
764
|
* Returns an error message or null.
|
|
670
765
|
*/
|
|
@@ -1236,6 +1331,42 @@ var FileCommentStore = class {
|
|
|
1236
1331
|
function newId() {
|
|
1237
1332
|
return crypto.randomUUID();
|
|
1238
1333
|
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Backfill / repair a plan loaded from disk so it matches the current schema.
|
|
1336
|
+
* Older persisted plans may be missing `versions` and `createdAtPlanVersion`;
|
|
1337
|
+
* we synthesize sensible values so callers can always rely on the new fields.
|
|
1338
|
+
*/
|
|
1339
|
+
function backfillPlan(plan) {
|
|
1340
|
+
const currentVersion = plan.version ?? 1;
|
|
1341
|
+
const have = Array.isArray(plan.versions) ? plan.versions.length : 0;
|
|
1342
|
+
if (!plan.versions || have === 0 || have < currentVersion) {
|
|
1343
|
+
const createdAt = plan.updatedAt ?? plan.createdAt ?? Date.now();
|
|
1344
|
+
const haveEntries = Array.isArray(plan.versions) ? plan.versions : [];
|
|
1345
|
+
const present = new Set(haveEntries.map((v) => v.version));
|
|
1346
|
+
const next = [];
|
|
1347
|
+
for (let i = 1; i <= currentVersion; i++) {
|
|
1348
|
+
const existing = haveEntries.find((v) => v.version === i);
|
|
1349
|
+
if (existing) next.push(existing);
|
|
1350
|
+
else next.push({
|
|
1351
|
+
version: i,
|
|
1352
|
+
body: plan.body,
|
|
1353
|
+
title: plan.title,
|
|
1354
|
+
source: plan.source,
|
|
1355
|
+
model: plan.model,
|
|
1356
|
+
createdAt
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
plan.versions = next.filter((v) => v.version >= 1 && v.version <= currentVersion);
|
|
1360
|
+
if (have < currentVersion || !present.has(currentVersion)) markBackfilled(plan);
|
|
1361
|
+
}
|
|
1362
|
+
if (plan.comments) {
|
|
1363
|
+
for (const c of plan.comments) if (typeof c.createdAtPlanVersion !== "number") c.createdAtPlanVersion = plan.version ?? 1;
|
|
1364
|
+
}
|
|
1365
|
+
if (!plan.comments) plan.comments = [];
|
|
1366
|
+
}
|
|
1367
|
+
function markBackfilled(plan) {
|
|
1368
|
+
plan.__backfilledVersions = true;
|
|
1369
|
+
}
|
|
1239
1370
|
/** Shared mutation helpers so both stores behave identically. */
|
|
1240
1371
|
function applyUpsert(plans, input, now) {
|
|
1241
1372
|
if (input.id) {
|
|
@@ -1247,6 +1378,15 @@ function applyUpsert(plans, input, now) {
|
|
|
1247
1378
|
if (input.model !== void 0) existing.model = input.model;
|
|
1248
1379
|
existing.version += 1;
|
|
1249
1380
|
existing.updatedAt = now;
|
|
1381
|
+
if (!existing.versions) existing.versions = [];
|
|
1382
|
+
existing.versions.push({
|
|
1383
|
+
version: existing.version,
|
|
1384
|
+
body: existing.body,
|
|
1385
|
+
title: existing.title,
|
|
1386
|
+
source: existing.source,
|
|
1387
|
+
model: existing.model,
|
|
1388
|
+
createdAt: now
|
|
1389
|
+
});
|
|
1250
1390
|
existing.decision = "pending";
|
|
1251
1391
|
existing.decisionComment = void 0;
|
|
1252
1392
|
existing.decidedAt = void 0;
|
|
@@ -1263,11 +1403,43 @@ function applyUpsert(plans, input, now) {
|
|
|
1263
1403
|
updatedAt: now,
|
|
1264
1404
|
version: 1,
|
|
1265
1405
|
decision: "pending",
|
|
1266
|
-
comments: []
|
|
1406
|
+
comments: [],
|
|
1407
|
+
versions: [{
|
|
1408
|
+
version: 1,
|
|
1409
|
+
body: input.body,
|
|
1410
|
+
title: input.title,
|
|
1411
|
+
source: input.source,
|
|
1412
|
+
model: input.model,
|
|
1413
|
+
createdAt: now
|
|
1414
|
+
}]
|
|
1267
1415
|
};
|
|
1268
1416
|
plans.push(plan);
|
|
1269
1417
|
return plan;
|
|
1270
1418
|
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Keep the most-recent `PlanVersion` snapshot in sync with the current `Plan`
|
|
1421
|
+
* fields after a non-resubmit edit (PUT). Doesn't add a new entry — that only
|
|
1422
|
+
* happens via `upsert` (the resubmit flow).
|
|
1423
|
+
*/
|
|
1424
|
+
function syncCurrentVersion(plan) {
|
|
1425
|
+
if (!plan.versions || plan.versions.length === 0) plan.versions = [];
|
|
1426
|
+
if (plan.versions.length === 0) {
|
|
1427
|
+
plan.versions.push({
|
|
1428
|
+
version: plan.version,
|
|
1429
|
+
body: plan.body,
|
|
1430
|
+
title: plan.title,
|
|
1431
|
+
source: plan.source,
|
|
1432
|
+
model: plan.model,
|
|
1433
|
+
createdAt: plan.updatedAt
|
|
1434
|
+
});
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
const last = plan.versions[plan.versions.length - 1];
|
|
1438
|
+
last.body = plan.body;
|
|
1439
|
+
last.title = plan.title;
|
|
1440
|
+
last.source = plan.source;
|
|
1441
|
+
last.model = plan.model;
|
|
1442
|
+
}
|
|
1271
1443
|
var FilePlanStore = class {
|
|
1272
1444
|
dirPath;
|
|
1273
1445
|
filePath;
|
|
@@ -1285,7 +1457,19 @@ var FilePlanStore = class {
|
|
|
1285
1457
|
async getAll() {
|
|
1286
1458
|
try {
|
|
1287
1459
|
const data = await readFile(this.filePath, "utf-8");
|
|
1288
|
-
|
|
1460
|
+
const plans = JSON.parse(data);
|
|
1461
|
+
let anyBackfilled = false;
|
|
1462
|
+
for (const p of plans) {
|
|
1463
|
+
const before = Array.isArray(p.versions) ? p.versions.length : 0;
|
|
1464
|
+
const beforeValid = Array.isArray(p.versions) && p.versions.length > 0;
|
|
1465
|
+
backfillPlan(p);
|
|
1466
|
+
const after = Array.isArray(p.versions) ? p.versions.length : 0;
|
|
1467
|
+
if ((!beforeValid || before < (p.version ?? 1)) && after > before) anyBackfilled = true;
|
|
1468
|
+
}
|
|
1469
|
+
if (anyBackfilled) try {
|
|
1470
|
+
await this.save(plans);
|
|
1471
|
+
} catch {}
|
|
1472
|
+
return plans;
|
|
1289
1473
|
} catch {
|
|
1290
1474
|
return [];
|
|
1291
1475
|
}
|
|
@@ -1293,6 +1477,11 @@ var FilePlanStore = class {
|
|
|
1293
1477
|
async get(id) {
|
|
1294
1478
|
return (await this.getAll()).find((p) => p.id === id) ?? null;
|
|
1295
1479
|
}
|
|
1480
|
+
async getVersion(id, version) {
|
|
1481
|
+
const plan = await this.get(id);
|
|
1482
|
+
if (!plan) return null;
|
|
1483
|
+
return plan.versions.find((v) => v.version === version) ?? null;
|
|
1484
|
+
}
|
|
1296
1485
|
async save(plans) {
|
|
1297
1486
|
try {
|
|
1298
1487
|
await mkdir(this.dirPath, { recursive: true });
|
|
@@ -1300,7 +1489,12 @@ var FilePlanStore = class {
|
|
|
1300
1489
|
const repoRoot = getRepoRoot();
|
|
1301
1490
|
await writeFile(join(this.dirPath, "repo_path.txt"), repoRoot, "utf-8");
|
|
1302
1491
|
} catch {}
|
|
1303
|
-
|
|
1492
|
+
const clean = plans.map((p) => {
|
|
1493
|
+
const copy = { ...p };
|
|
1494
|
+
delete copy.__backfilledVersions;
|
|
1495
|
+
return copy;
|
|
1496
|
+
});
|
|
1497
|
+
await writeFile(this.filePath, JSON.stringify(clean, null, 2), "utf-8");
|
|
1304
1498
|
} catch (err) {
|
|
1305
1499
|
console.error("Failed to save plans to file:", err);
|
|
1306
1500
|
}
|
|
@@ -1308,6 +1502,7 @@ var FilePlanStore = class {
|
|
|
1308
1502
|
async upsert(input) {
|
|
1309
1503
|
const plans = await this.getAll();
|
|
1310
1504
|
const plan = applyUpsert(plans, input, Date.now());
|
|
1505
|
+
backfillPlan(plan);
|
|
1311
1506
|
await this.save(plans);
|
|
1312
1507
|
return plan;
|
|
1313
1508
|
}
|
|
@@ -1320,6 +1515,8 @@ var FilePlanStore = class {
|
|
|
1320
1515
|
if (fields.source !== void 0) plan.source = fields.source;
|
|
1321
1516
|
if (fields.model !== void 0) plan.model = fields.model;
|
|
1322
1517
|
plan.updatedAt = Date.now();
|
|
1518
|
+
syncCurrentVersion(plan);
|
|
1519
|
+
backfillPlan(plan);
|
|
1323
1520
|
await this.save(plans);
|
|
1324
1521
|
return plan;
|
|
1325
1522
|
}
|
|
@@ -1348,11 +1545,13 @@ var FilePlanStore = class {
|
|
|
1348
1545
|
if (!plan) return null;
|
|
1349
1546
|
if (!plan.comments) plan.comments = [];
|
|
1350
1547
|
if (!fn(plan)) return null;
|
|
1548
|
+
backfillPlan(plan);
|
|
1351
1549
|
await this.save(plans);
|
|
1352
1550
|
return plan;
|
|
1353
1551
|
}
|
|
1354
1552
|
async addComment(planId, comment) {
|
|
1355
1553
|
return this.mutate(planId, (plan) => {
|
|
1554
|
+
if (typeof comment.createdAtPlanVersion !== "number") comment.createdAtPlanVersion = plan.version ?? 1;
|
|
1356
1555
|
plan.comments.push(comment);
|
|
1357
1556
|
return true;
|
|
1358
1557
|
});
|
|
@@ -1378,6 +1577,7 @@ var FilePlanStore = class {
|
|
|
1378
1577
|
return this.mutate(planId, (plan) => {
|
|
1379
1578
|
const c = plan.comments.find((x) => x.id === commentId);
|
|
1380
1579
|
if (!c) return false;
|
|
1580
|
+
if (typeof c.createdAtPlanVersion === "number" && typeof reply.createdAtPlanVersion !== "number") reply.createdAtPlanVersion = c.createdAtPlanVersion;
|
|
1381
1581
|
if (!c.replies) c.replies = [];
|
|
1382
1582
|
c.replies.push(reply);
|
|
1383
1583
|
return true;
|
|
@@ -1438,6 +1638,62 @@ var FileUiStateStore = class {
|
|
|
1438
1638
|
}
|
|
1439
1639
|
};
|
|
1440
1640
|
//#endregion
|
|
1641
|
+
//#region src/lib/editor-launcher.ts
|
|
1642
|
+
function resolveEditorCommand(editor, absolutePath, platform = process.platform, env = process.env) {
|
|
1643
|
+
switch (editor) {
|
|
1644
|
+
case "vscode": return guiEditorCommand("code", absolutePath, platform);
|
|
1645
|
+
case "zed": return guiEditorCommand("zed", absolutePath, platform);
|
|
1646
|
+
case "vim": return terminalEditorCommand("vim", absolutePath, platform, env);
|
|
1647
|
+
case "neovim": return terminalEditorCommand("nvim", absolutePath, platform, env);
|
|
1648
|
+
default: return null;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
function guiEditorCommand(bin, absolutePath, platform) {
|
|
1652
|
+
if (platform === "win32") return {
|
|
1653
|
+
cmd: `${bin}.cmd`,
|
|
1654
|
+
args: [absolutePath]
|
|
1655
|
+
};
|
|
1656
|
+
return {
|
|
1657
|
+
cmd: bin,
|
|
1658
|
+
args: [absolutePath]
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
function terminalEditorCommand(bin, absolutePath, platform, env) {
|
|
1662
|
+
if (platform === "darwin") return {
|
|
1663
|
+
cmd: "osascript",
|
|
1664
|
+
args: ["-e", `tell application "Terminal" to do script "${bin} \\"${absolutePath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}\\""`]
|
|
1665
|
+
};
|
|
1666
|
+
if (platform === "win32") return {
|
|
1667
|
+
cmd: "cmd.exe",
|
|
1668
|
+
args: [
|
|
1669
|
+
"/c",
|
|
1670
|
+
"start",
|
|
1671
|
+
"\"\"",
|
|
1672
|
+
"cmd.exe",
|
|
1673
|
+
"/k",
|
|
1674
|
+
bin,
|
|
1675
|
+
absolutePath
|
|
1676
|
+
]
|
|
1677
|
+
};
|
|
1678
|
+
const term = env.TERMINAL?.trim();
|
|
1679
|
+
if (term) return {
|
|
1680
|
+
cmd: term,
|
|
1681
|
+
args: [
|
|
1682
|
+
"-e",
|
|
1683
|
+
bin,
|
|
1684
|
+
absolutePath
|
|
1685
|
+
]
|
|
1686
|
+
};
|
|
1687
|
+
return {
|
|
1688
|
+
cmd: "x-terminal-emulator",
|
|
1689
|
+
args: [
|
|
1690
|
+
"-e",
|
|
1691
|
+
bin,
|
|
1692
|
+
absolutePath
|
|
1693
|
+
]
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
//#endregion
|
|
1441
1697
|
//#region src/lib/review-session.ts
|
|
1442
1698
|
var ReviewSession = class {
|
|
1443
1699
|
round = 0;
|
|
@@ -1502,7 +1758,8 @@ var ReviewSession = class {
|
|
|
1502
1758
|
commentXml: input.commentXml,
|
|
1503
1759
|
openCount: input.openCount,
|
|
1504
1760
|
comments: input.comments,
|
|
1505
|
-
decision: input.decision
|
|
1761
|
+
decision: input.decision,
|
|
1762
|
+
mode: input.mode ?? "standard"
|
|
1506
1763
|
};
|
|
1507
1764
|
this.lastPayload = payload;
|
|
1508
1765
|
const waiters = [...this.waiters];
|
|
@@ -1596,7 +1853,8 @@ var PlanReviewSession = class {
|
|
|
1596
1853
|
decisionComment: input.decisionComment,
|
|
1597
1854
|
reviewXml: input.reviewXml,
|
|
1598
1855
|
openCommentCount: input.openCommentCount,
|
|
1599
|
-
plan: input.plan
|
|
1856
|
+
plan: input.plan,
|
|
1857
|
+
mode: input.mode ?? "standard"
|
|
1600
1858
|
};
|
|
1601
1859
|
this.lastPayload = payload;
|
|
1602
1860
|
const waiters = [...this.waiters];
|
|
@@ -1630,6 +1888,483 @@ var PlanReviewSession = class {
|
|
|
1630
1888
|
}
|
|
1631
1889
|
};
|
|
1632
1890
|
//#endregion
|
|
1891
|
+
//#region src/lib/pr-session.ts
|
|
1892
|
+
var FilePrSessionStore = class {
|
|
1893
|
+
dirPath;
|
|
1894
|
+
filePath;
|
|
1895
|
+
constructor(storageDir) {
|
|
1896
|
+
this.dirPath = storageDir ?? getProjectStorageDir();
|
|
1897
|
+
this.filePath = join(this.dirPath, "pr-session.json");
|
|
1898
|
+
}
|
|
1899
|
+
async get() {
|
|
1900
|
+
try {
|
|
1901
|
+
const data = await readFile(this.filePath, "utf-8");
|
|
1902
|
+
return JSON.parse(data);
|
|
1903
|
+
} catch {
|
|
1904
|
+
return null;
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
async save(session) {
|
|
1908
|
+
try {
|
|
1909
|
+
await mkdir(this.dirPath, { recursive: true });
|
|
1910
|
+
await writeFile(this.filePath, JSON.stringify(session, null, 2), "utf-8");
|
|
1911
|
+
} catch (err) {
|
|
1912
|
+
console.error("Failed to save pr-session to file:", err);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
async set(session) {
|
|
1916
|
+
await this.save(session);
|
|
1917
|
+
}
|
|
1918
|
+
async update(fields) {
|
|
1919
|
+
const current = await this.get();
|
|
1920
|
+
if (!current) return null;
|
|
1921
|
+
const next = {
|
|
1922
|
+
...current,
|
|
1923
|
+
...fields
|
|
1924
|
+
};
|
|
1925
|
+
await this.save(next);
|
|
1926
|
+
return next;
|
|
1927
|
+
}
|
|
1928
|
+
async clear() {
|
|
1929
|
+
try {
|
|
1930
|
+
const { unlink } = await import("node:fs/promises");
|
|
1931
|
+
await unlink(this.filePath);
|
|
1932
|
+
} catch {}
|
|
1933
|
+
}
|
|
1934
|
+
};
|
|
1935
|
+
//#endregion
|
|
1936
|
+
//#region src/lib/github.ts
|
|
1937
|
+
const execFileAsync = promisify(execFile);
|
|
1938
|
+
/**
|
|
1939
|
+
* Probe the `gh` CLI: is it on `$PATH`, and is the user authenticated?
|
|
1940
|
+
* Never throws — returns a permissive `available: false` on any failure.
|
|
1941
|
+
*/
|
|
1942
|
+
async function detectGhCli() {
|
|
1943
|
+
try {
|
|
1944
|
+
const { stdout } = await execFileAsync("gh", ["--version"], { encoding: "utf-8" });
|
|
1945
|
+
const version = stdout.split("\n")[0]?.trim() || void 0;
|
|
1946
|
+
try {
|
|
1947
|
+
const result = await execFileAsync("gh", ["auth", "status"], { encoding: "utf-8" });
|
|
1948
|
+
return {
|
|
1949
|
+
available: true,
|
|
1950
|
+
authenticated: true,
|
|
1951
|
+
version,
|
|
1952
|
+
user: parseGhAuthStatusUser(`${result.stdout ?? ""}\n${result.stderr ?? ""}`)
|
|
1953
|
+
};
|
|
1954
|
+
} catch {
|
|
1955
|
+
return {
|
|
1956
|
+
available: true,
|
|
1957
|
+
authenticated: false,
|
|
1958
|
+
version
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
} catch {
|
|
1962
|
+
return {
|
|
1963
|
+
available: false,
|
|
1964
|
+
authenticated: false
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Extract the GitHub username from `gh auth status` output. Exported for
|
|
1970
|
+
* unit tests — see `__tests__/github-auth.test.ts`.
|
|
1971
|
+
*
|
|
1972
|
+
* Real-world samples:
|
|
1973
|
+
* `✓ Logged in to github.com account octocat (keyring)` (gh 2.40+)
|
|
1974
|
+
* `Logged in to github.com as octocat (oauth_token)` (older gh)
|
|
1975
|
+
* ` ✓ Logged in to github.com as octocat (~/.config/gh/hosts.yml)`
|
|
1976
|
+
*/
|
|
1977
|
+
function parseGhAuthStatusUser(output) {
|
|
1978
|
+
const account = /Logged in to [^\s]+ account\s+([^\s(]+)/.exec(output);
|
|
1979
|
+
if (account) return account[1];
|
|
1980
|
+
return /Logged in to [^\s]+ as\s+([^\s(]+)/.exec(output)?.[1];
|
|
1981
|
+
}
|
|
1982
|
+
/** Read the most-preferred GitHub token from the env. */
|
|
1983
|
+
function readGithubToken() {
|
|
1984
|
+
const env = process.env;
|
|
1985
|
+
return env.GH_TOKEN || env.GITHUB_TOKEN || env.GITHUB_API_TOKEN || null;
|
|
1986
|
+
}
|
|
1987
|
+
function parsePrRef(input, cwdRepo) {
|
|
1988
|
+
const trimmed = input.trim();
|
|
1989
|
+
const urlMatch = /^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/i.exec(trimmed);
|
|
1990
|
+
if (urlMatch) return {
|
|
1991
|
+
owner: urlMatch[1],
|
|
1992
|
+
repo: urlMatch[2],
|
|
1993
|
+
pullNumber: Number(urlMatch[3]),
|
|
1994
|
+
ref: trimmed
|
|
1995
|
+
};
|
|
1996
|
+
const shorthand = /^([^/\s]+)\/([^#\s]+)#(\d+)$/.exec(trimmed);
|
|
1997
|
+
if (shorthand) return {
|
|
1998
|
+
owner: shorthand[1],
|
|
1999
|
+
repo: shorthand[2],
|
|
2000
|
+
pullNumber: Number(shorthand[3]),
|
|
2001
|
+
ref: trimmed
|
|
2002
|
+
};
|
|
2003
|
+
const bare = /^#?(\d+)$/.exec(trimmed);
|
|
2004
|
+
if (bare && cwdRepo) return {
|
|
2005
|
+
owner: cwdRepo.owner,
|
|
2006
|
+
repo: cwdRepo.repo,
|
|
2007
|
+
pullNumber: Number(bare[1]),
|
|
2008
|
+
ref: trimmed
|
|
2009
|
+
};
|
|
2010
|
+
if (bare) throw new Error(`Cannot resolve bare PR number "${trimmed}" — run from inside the target repo, or pass a full URL or \`owner/repo#1234\`.`);
|
|
2011
|
+
throw new Error(`Unrecognised PR ref: ${trimmed}`);
|
|
2012
|
+
}
|
|
2013
|
+
/**
|
|
2014
|
+
* Best-effort: derive `{ owner, repo }` for the current working directory
|
|
2015
|
+
* from `git remote get-url origin`. Falls back to throwing.
|
|
2016
|
+
*/
|
|
2017
|
+
async function detectCwdRepo() {
|
|
2018
|
+
try {
|
|
2019
|
+
const { stdout } = await execFileAsync("git", [
|
|
2020
|
+
"remote",
|
|
2021
|
+
"get-url",
|
|
2022
|
+
"origin"
|
|
2023
|
+
], { encoding: "utf-8" });
|
|
2024
|
+
const url = stdout.trim();
|
|
2025
|
+
const m = /github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\.git)?\s*$/.exec(url);
|
|
2026
|
+
if (m) return {
|
|
2027
|
+
owner: m[1],
|
|
2028
|
+
repo: m[2]
|
|
2029
|
+
};
|
|
2030
|
+
} catch {}
|
|
2031
|
+
return null;
|
|
2032
|
+
}
|
|
2033
|
+
/**
|
|
2034
|
+
* Fetch PR metadata + diff + existing review comments using `gh`.
|
|
2035
|
+
* Throws with a user-readable message on failure.
|
|
2036
|
+
*/
|
|
2037
|
+
async function fetchPrMetadataViaGh(resolved) {
|
|
2038
|
+
const args = ["-R", `${resolved.owner}/${resolved.repo}`];
|
|
2039
|
+
let metaJson;
|
|
2040
|
+
try {
|
|
2041
|
+
const { stdout } = await execFileAsync("gh", [
|
|
2042
|
+
"pr",
|
|
2043
|
+
"view",
|
|
2044
|
+
String(resolved.pullNumber),
|
|
2045
|
+
...args,
|
|
2046
|
+
"--json",
|
|
2047
|
+
"number,title,url,author,baseRefOid,headRefOid,additions,deletions,changedFiles,headRepositoryOwner,headRepository"
|
|
2048
|
+
], {
|
|
2049
|
+
encoding: "utf-8",
|
|
2050
|
+
maxBuffer: 20 * 1024 * 1024
|
|
2051
|
+
});
|
|
2052
|
+
metaJson = JSON.parse(stdout);
|
|
2053
|
+
} catch (err) {
|
|
2054
|
+
const stderr = err?.stderr || err?.message || "unknown error";
|
|
2055
|
+
throw new Error(`gh pr view failed: ${stderr.trim()}`);
|
|
2056
|
+
}
|
|
2057
|
+
let diff = "";
|
|
2058
|
+
try {
|
|
2059
|
+
const { stdout } = await execFileAsync("gh", [
|
|
2060
|
+
"pr",
|
|
2061
|
+
"diff",
|
|
2062
|
+
String(resolved.pullNumber),
|
|
2063
|
+
...args
|
|
2064
|
+
], {
|
|
2065
|
+
encoding: "utf-8",
|
|
2066
|
+
maxBuffer: 50 * 1024 * 1024
|
|
2067
|
+
});
|
|
2068
|
+
diff = stdout;
|
|
2069
|
+
} catch (err) {
|
|
2070
|
+
const stderr = err?.stderr || err?.message || "unknown error";
|
|
2071
|
+
throw new Error(`gh pr diff failed: ${stderr.trim()}`);
|
|
2072
|
+
}
|
|
2073
|
+
const headOwner = metaJson.headRepositoryOwner?.login || metaJson.headRepository?.owner?.login || resolved.owner;
|
|
2074
|
+
const repoName = metaJson.headRepository?.name || resolved.repo;
|
|
2075
|
+
const author = metaJson.author ? {
|
|
2076
|
+
login: metaJson.author.login,
|
|
2077
|
+
avatarUrl: metaJson.author.avatarUrl
|
|
2078
|
+
} : null;
|
|
2079
|
+
const existingComments = await fetchExistingCommentsViaGh(resolved);
|
|
2080
|
+
return {
|
|
2081
|
+
number: metaJson.number,
|
|
2082
|
+
title: metaJson.title,
|
|
2083
|
+
url: metaJson.url,
|
|
2084
|
+
author,
|
|
2085
|
+
baseSha: metaJson.baseRefOid,
|
|
2086
|
+
headSha: metaJson.headRefOid,
|
|
2087
|
+
additions: metaJson.additions,
|
|
2088
|
+
deletions: metaJson.deletions,
|
|
2089
|
+
changedFiles: metaJson.changedFiles,
|
|
2090
|
+
owner: headOwner,
|
|
2091
|
+
repo: repoName,
|
|
2092
|
+
diff,
|
|
2093
|
+
existingComments
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
async function fetchExistingCommentsViaGh(resolved) {
|
|
2097
|
+
`${resolved.owner}${resolved.repo}`;
|
|
2098
|
+
const endpoint = `repos/${resolved.owner}/${resolved.repo}/pulls/${resolved.pullNumber}/comments`;
|
|
2099
|
+
const reviewsEndpoint = `repos/${resolved.owner}/${resolved.repo}/pulls/${resolved.pullNumber}/reviews`;
|
|
2100
|
+
let reviews = [];
|
|
2101
|
+
try {
|
|
2102
|
+
const { stdout } = await execFileAsync("gh", [
|
|
2103
|
+
"api",
|
|
2104
|
+
reviewsEndpoint,
|
|
2105
|
+
"--paginate"
|
|
2106
|
+
], {
|
|
2107
|
+
encoding: "utf-8",
|
|
2108
|
+
maxBuffer: 20 * 1024 * 1024
|
|
2109
|
+
});
|
|
2110
|
+
const all = JSON.parse(stdout);
|
|
2111
|
+
if (Array.isArray(all)) reviews = all;
|
|
2112
|
+
} catch {}
|
|
2113
|
+
reviews.sort((a, b) => a.submitted_at < b.submitted_at ? 1 : -1);
|
|
2114
|
+
const recentReviews = reviews.slice(0, 50);
|
|
2115
|
+
const reviewStateById = /* @__PURE__ */ new Map();
|
|
2116
|
+
for (const r of recentReviews) reviewStateById.set(r.id, r.state);
|
|
2117
|
+
let commentsRaw = [];
|
|
2118
|
+
try {
|
|
2119
|
+
const { stdout } = await execFileAsync("gh", [
|
|
2120
|
+
"api",
|
|
2121
|
+
`${endpoint}?per_page=100`,
|
|
2122
|
+
"--paginate"
|
|
2123
|
+
], {
|
|
2124
|
+
encoding: "utf-8",
|
|
2125
|
+
maxBuffer: 20 * 1024 * 1024
|
|
2126
|
+
});
|
|
2127
|
+
const parsed = JSON.parse(stdout);
|
|
2128
|
+
if (Array.isArray(parsed)) commentsRaw = parsed;
|
|
2129
|
+
} catch {
|
|
2130
|
+
return [];
|
|
2131
|
+
}
|
|
2132
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2133
|
+
for (const c of commentsRaw) byId.set(c.id, c);
|
|
2134
|
+
const replyByParent = /* @__PURE__ */ new Map();
|
|
2135
|
+
for (const c of commentsRaw) if (c.in_reply_to_id && byId.has(c.in_reply_to_id)) {
|
|
2136
|
+
const list = replyByParent.get(c.in_reply_to_id) ?? [];
|
|
2137
|
+
list.push(c);
|
|
2138
|
+
replyByParent.set(c.in_reply_to_id, list);
|
|
2139
|
+
}
|
|
2140
|
+
const tops = [];
|
|
2141
|
+
for (const c of commentsRaw) {
|
|
2142
|
+
if (c.in_reply_to_id) continue;
|
|
2143
|
+
const state = c.pull_request_review_id ? reviewStateById.get(c.pull_request_review_id) ?? null : null;
|
|
2144
|
+
tops.push({
|
|
2145
|
+
id: c.id,
|
|
2146
|
+
author: c.user ? {
|
|
2147
|
+
login: c.user.login,
|
|
2148
|
+
avatarUrl: c.user.avatar_url
|
|
2149
|
+
} : null,
|
|
2150
|
+
body: c.body ?? "",
|
|
2151
|
+
path: c.path,
|
|
2152
|
+
line: typeof c.line === "number" ? c.line : null,
|
|
2153
|
+
side: c.side === "LEFT" || c.side === "RIGHT" ? c.side : null,
|
|
2154
|
+
createdAt: c.created_at,
|
|
2155
|
+
updatedAt: c.updated_at,
|
|
2156
|
+
state,
|
|
2157
|
+
replies: (replyByParent.get(c.id) ?? []).map((r) => ({
|
|
2158
|
+
id: r.id,
|
|
2159
|
+
author: r.user ? {
|
|
2160
|
+
login: r.user.login,
|
|
2161
|
+
avatarUrl: r.user.avatar_url
|
|
2162
|
+
} : null,
|
|
2163
|
+
body: r.body ?? "",
|
|
2164
|
+
createdAt: r.created_at,
|
|
2165
|
+
updatedAt: r.updated_at
|
|
2166
|
+
})),
|
|
2167
|
+
isOutdated: typeof c.position === "number" && c.position === null ? false : false
|
|
2168
|
+
});
|
|
2169
|
+
}
|
|
2170
|
+
tops.sort((a, b) => {
|
|
2171
|
+
if (a.path !== b.path) return a.path.localeCompare(b.path);
|
|
2172
|
+
return (a.line ?? 0) - (b.line ?? 0);
|
|
2173
|
+
});
|
|
2174
|
+
return tops;
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* POST the review to GitHub. Prefers `gh api` (uses the user's existing
|
|
2178
|
+
* `gh auth`); falls back to a direct HTTP call with `$GITHUB_TOKEN` when
|
|
2179
|
+
* `gh` is unavailable.
|
|
2180
|
+
*/
|
|
2181
|
+
async function submitReview(input) {
|
|
2182
|
+
const gh = await detectGhCli();
|
|
2183
|
+
if (gh.available && gh.authenticated) return submitViaGh(input);
|
|
2184
|
+
const token = readGithubToken();
|
|
2185
|
+
if (token) return submitViaToken(input, token);
|
|
2186
|
+
return {
|
|
2187
|
+
ok: false,
|
|
2188
|
+
authSource: "none",
|
|
2189
|
+
error: "Cannot submit: no `gh` CLI on $PATH (or not authenticated) and no $GITHUB_TOKEN env var. Run `gh auth login` or set $GITHUB_TOKEN."
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
/**
|
|
2193
|
+
* Map our `PrDecision` to the GitHub REST event string. Exported so the
|
|
2194
|
+
* test suite can pin the mapping (including the `rejected → REQUEST_CHANGES`
|
|
2195
|
+
* quirk, which exists because GitHub has no REJECT event).
|
|
2196
|
+
*/
|
|
2197
|
+
function decisionToEvent(decision) {
|
|
2198
|
+
if (decision === "approve") return "APPROVE";
|
|
2199
|
+
if (decision === "request-changes") return "REQUEST_CHANGES";
|
|
2200
|
+
return "COMMENT";
|
|
2201
|
+
}
|
|
2202
|
+
/**
|
|
2203
|
+
* Expand multi-line `ReviewComment`s into N single-line `gh`-shaped entries
|
|
2204
|
+
* with `[part N/M]` prefixes. Exported for the test suite — the body of the
|
|
2205
|
+
* caller (`buildReviewPayload`) is otherwise private.
|
|
2206
|
+
*
|
|
2207
|
+
* Skips: non-`open` comments, file-level comments (`lineNumber === 0`).
|
|
2208
|
+
* Strips the `a/` / `b/` prefix from the file path (GitHub expects PR-relative
|
|
2209
|
+
* paths in the `path` field).
|
|
2210
|
+
*/
|
|
2211
|
+
function expandMultiLineComments(comments) {
|
|
2212
|
+
const flat = [];
|
|
2213
|
+
for (const c of comments) {
|
|
2214
|
+
if (c.status !== "open") continue;
|
|
2215
|
+
if (c.lineNumber === 0) continue;
|
|
2216
|
+
const path = stripBPrefix(c.filePath);
|
|
2217
|
+
const start = c.startLineNumber && c.startLineNumber < c.lineNumber ? c.startLineNumber : c.lineNumber;
|
|
2218
|
+
const end = c.lineNumber;
|
|
2219
|
+
const span = end - start + 1;
|
|
2220
|
+
if (span > 1) for (let i = 0; i < span; i++) flat.push({
|
|
2221
|
+
path,
|
|
2222
|
+
line: start + i,
|
|
2223
|
+
side: "RIGHT",
|
|
2224
|
+
body: `[part ${i + 1}/${span}]\n${c.body}`
|
|
2225
|
+
});
|
|
2226
|
+
else flat.push({
|
|
2227
|
+
path,
|
|
2228
|
+
line: end,
|
|
2229
|
+
side: "RIGHT",
|
|
2230
|
+
body: c.body
|
|
2231
|
+
});
|
|
2232
|
+
}
|
|
2233
|
+
return flat;
|
|
2234
|
+
}
|
|
2235
|
+
function buildReviewPayload(input) {
|
|
2236
|
+
return {
|
|
2237
|
+
body: input.body,
|
|
2238
|
+
event: decisionToEvent(input.decision ?? "comment"),
|
|
2239
|
+
comments: expandMultiLineComments(input.comments ?? [])
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
function stripBPrefix(path) {
|
|
2243
|
+
if (path.startsWith("b/")) return path.slice(2);
|
|
2244
|
+
if (path.startsWith("a/")) return path.slice(2);
|
|
2245
|
+
return path;
|
|
2246
|
+
}
|
|
2247
|
+
async function submitViaGh(input) {
|
|
2248
|
+
const payload = buildReviewPayload(input);
|
|
2249
|
+
`${input.resolved.owner}${input.resolved.repo}`;
|
|
2250
|
+
const args = [
|
|
2251
|
+
"api",
|
|
2252
|
+
"--method",
|
|
2253
|
+
"POST",
|
|
2254
|
+
`repos/${input.resolved.owner}/${input.resolved.repo}/pulls/${input.resolved.pullNumber}/reviews`,
|
|
2255
|
+
"-H",
|
|
2256
|
+
"Accept: application/vnd.github+json",
|
|
2257
|
+
"--input",
|
|
2258
|
+
"-"
|
|
2259
|
+
];
|
|
2260
|
+
try {
|
|
2261
|
+
const { stdout } = await execFileAsync("gh", args, {
|
|
2262
|
+
encoding: "utf-8",
|
|
2263
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
2264
|
+
input: JSON.stringify(payload)
|
|
2265
|
+
});
|
|
2266
|
+
const result = JSON.parse(stdout);
|
|
2267
|
+
return {
|
|
2268
|
+
ok: true,
|
|
2269
|
+
authSource: "gh",
|
|
2270
|
+
reviewId: result.id,
|
|
2271
|
+
reviewUrl: result.html_url
|
|
2272
|
+
};
|
|
2273
|
+
} catch (err) {
|
|
2274
|
+
const stdout = err?.stdout || "";
|
|
2275
|
+
const stderr = err?.stderr || err?.message || "unknown error";
|
|
2276
|
+
let parsed = null;
|
|
2277
|
+
try {
|
|
2278
|
+
parsed = JSON.parse(stdout);
|
|
2279
|
+
} catch {}
|
|
2280
|
+
return {
|
|
2281
|
+
ok: false,
|
|
2282
|
+
authSource: "gh",
|
|
2283
|
+
error: parsed?.message || stderr.trim()
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
async function submitViaToken(input, token) {
|
|
2288
|
+
const payload = buildReviewPayload(input);
|
|
2289
|
+
const url = `https://api.github.com/repos/${input.resolved.owner}/${input.resolved.repo}/pulls/${input.resolved.pullNumber}/reviews`;
|
|
2290
|
+
try {
|
|
2291
|
+
const res = await fetch(url, {
|
|
2292
|
+
method: "POST",
|
|
2293
|
+
headers: {
|
|
2294
|
+
Authorization: `Bearer ${token}`,
|
|
2295
|
+
Accept: "application/vnd.github+json",
|
|
2296
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
2297
|
+
"Content-Type": "application/json",
|
|
2298
|
+
"User-Agent": "diffing-cli"
|
|
2299
|
+
},
|
|
2300
|
+
body: JSON.stringify(payload)
|
|
2301
|
+
});
|
|
2302
|
+
if (!res.ok) {
|
|
2303
|
+
const errBody = await res.text().catch(() => "");
|
|
2304
|
+
return {
|
|
2305
|
+
ok: false,
|
|
2306
|
+
authSource: "token",
|
|
2307
|
+
error: `HTTP ${res.status}: ${errBody.slice(0, 500)}`
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
const result = await res.json();
|
|
2311
|
+
return {
|
|
2312
|
+
ok: true,
|
|
2313
|
+
authSource: "token",
|
|
2314
|
+
reviewId: result.id,
|
|
2315
|
+
reviewUrl: result.html_url
|
|
2316
|
+
};
|
|
2317
|
+
} catch (err) {
|
|
2318
|
+
return {
|
|
2319
|
+
ok: false,
|
|
2320
|
+
authSource: "token",
|
|
2321
|
+
error: err?.message || "Network error"
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
async function buildPrSession(ref) {
|
|
2326
|
+
const meta = await fetchPrMetadataViaGh(parsePrRef(ref, await detectCwdRepo() ?? void 0));
|
|
2327
|
+
return {
|
|
2328
|
+
ref,
|
|
2329
|
+
owner: meta.owner,
|
|
2330
|
+
repo: meta.repo,
|
|
2331
|
+
pullNumber: meta.number,
|
|
2332
|
+
baseSha: meta.baseSha,
|
|
2333
|
+
headSha: meta.headSha,
|
|
2334
|
+
title: meta.title,
|
|
2335
|
+
url: meta.url,
|
|
2336
|
+
author: meta.author,
|
|
2337
|
+
additions: meta.additions,
|
|
2338
|
+
deletions: meta.deletions,
|
|
2339
|
+
changedFiles: meta.changedFiles,
|
|
2340
|
+
diff: meta.diff,
|
|
2341
|
+
comments: [],
|
|
2342
|
+
existingComments: meta.existingComments
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
/** Refresh `diff` + `existingComments` + head SHA in an existing session. */
|
|
2346
|
+
async function refreshPrSession(session) {
|
|
2347
|
+
const meta = await fetchPrMetadataViaGh({
|
|
2348
|
+
owner: session.owner,
|
|
2349
|
+
repo: session.repo,
|
|
2350
|
+
pullNumber: session.pullNumber,
|
|
2351
|
+
ref: session.ref
|
|
2352
|
+
});
|
|
2353
|
+
return {
|
|
2354
|
+
...session,
|
|
2355
|
+
baseSha: meta.baseSha,
|
|
2356
|
+
headSha: meta.headSha,
|
|
2357
|
+
title: meta.title,
|
|
2358
|
+
url: meta.url,
|
|
2359
|
+
author: meta.author,
|
|
2360
|
+
additions: meta.additions,
|
|
2361
|
+
deletions: meta.deletions,
|
|
2362
|
+
changedFiles: meta.changedFiles,
|
|
2363
|
+
diff: meta.diff,
|
|
2364
|
+
existingComments: meta.existingComments
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
//#endregion
|
|
1633
2368
|
//#region src/server.ts
|
|
1634
2369
|
const MIME_TYPES = {
|
|
1635
2370
|
".html": "text/html",
|
|
@@ -1647,13 +2382,14 @@ const MIME_TYPES = {
|
|
|
1647
2382
|
".avif": "image/avif"
|
|
1648
2383
|
};
|
|
1649
2384
|
function isCustomMode(opts) {
|
|
1650
|
-
return opts.revisions.length > 0 || opts.pathspecs.length > 0;
|
|
2385
|
+
return opts.revisions.length > 0 || opts.pathspecs.length > 0 || opts.showMode;
|
|
1651
2386
|
}
|
|
1652
|
-
function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
2387
|
+
function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore, prSessionStore) {
|
|
1653
2388
|
const app = new Hono();
|
|
1654
2389
|
const customMode = isCustomMode(diffOpts);
|
|
1655
2390
|
const store = commentStore ?? new FileCommentStore();
|
|
1656
2391
|
const plans = planStore ?? new FilePlanStore();
|
|
2392
|
+
const prStore = prSessionStore ?? new FilePrSessionStore();
|
|
1657
2393
|
const uiStateStore = new FileUiStateStore();
|
|
1658
2394
|
const viewedFiles = /* @__PURE__ */ new Set();
|
|
1659
2395
|
const activeClients = /* @__PURE__ */ new Set();
|
|
@@ -1675,6 +2411,7 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
1675
2411
|
mkdirSync(storageDir, { recursive: true });
|
|
1676
2412
|
let commentsDebounce = null;
|
|
1677
2413
|
let plansDebounce = null;
|
|
2414
|
+
let prSessionDebounce = null;
|
|
1678
2415
|
watch(storageDir, (_eventType, filename) => {
|
|
1679
2416
|
if (!filename) return;
|
|
1680
2417
|
if (filename.startsWith("comments.json")) {
|
|
@@ -1683,6 +2420,9 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
1683
2420
|
} else if (filename.startsWith("plans.json")) {
|
|
1684
2421
|
if (plansDebounce) clearTimeout(plansDebounce);
|
|
1685
2422
|
plansDebounce = setTimeout(() => broadcast("plans", Date.now().toString()), 120);
|
|
2423
|
+
} else if (filename.startsWith("pr-session.json")) {
|
|
2424
|
+
if (prSessionDebounce) clearTimeout(prSessionDebounce);
|
|
2425
|
+
prSessionDebounce = setTimeout(() => broadcast("pr-session", Date.now().toString()), 120);
|
|
1686
2426
|
}
|
|
1687
2427
|
}).unref();
|
|
1688
2428
|
} catch (err) {
|
|
@@ -1743,11 +2483,40 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
1743
2483
|
app.get("/api/diff", async (c) => {
|
|
1744
2484
|
const staged = c.req.query("staged") === "true";
|
|
1745
2485
|
const untracked = c.req.query("untracked") === "true";
|
|
1746
|
-
const
|
|
2486
|
+
const prSession = await prStore.get();
|
|
2487
|
+
if (prSession) {
|
|
2488
|
+
const binaryFiles = [];
|
|
2489
|
+
const filePaths = [];
|
|
2490
|
+
for (const line of prSession.diff.split("\n")) {
|
|
2491
|
+
const m = /^diff --git a\/.+ b\/(.+)$/.exec(line);
|
|
2492
|
+
if (m) filePaths.push(m[1]);
|
|
2493
|
+
}
|
|
2494
|
+
return c.json({
|
|
2495
|
+
patch: prSession.diff,
|
|
2496
|
+
repoName: prSession.repo,
|
|
2497
|
+
branch: `#${prSession.pullNumber}`,
|
|
2498
|
+
customMode: true,
|
|
2499
|
+
binaryFiles,
|
|
2500
|
+
tabSizeMap: {},
|
|
2501
|
+
untrackedFiles: [],
|
|
2502
|
+
prMode: true,
|
|
2503
|
+
prRef: prSession.ref,
|
|
2504
|
+
prOwner: prSession.owner,
|
|
2505
|
+
prRepo: prSession.repo,
|
|
2506
|
+
prPullNumber: prSession.pullNumber,
|
|
2507
|
+
prUrl: prSession.url,
|
|
2508
|
+
prTitle: prSession.title,
|
|
2509
|
+
prAuthor: prSession.author,
|
|
2510
|
+
prHeadSha: prSession.headSha,
|
|
2511
|
+
prBaseSha: prSession.baseSha
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
const optsForDiff = customMode ? diffOpts : {
|
|
1747
2515
|
...diffOpts,
|
|
1748
2516
|
staged,
|
|
1749
2517
|
includeUntracked: untracked
|
|
1750
|
-
}
|
|
2518
|
+
};
|
|
2519
|
+
const result = await executeDiffWithMeta(optsForDiff);
|
|
1751
2520
|
return c.json({
|
|
1752
2521
|
patch: result.patch,
|
|
1753
2522
|
repoName: result.repoName,
|
|
@@ -1755,7 +2524,10 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
1755
2524
|
customMode,
|
|
1756
2525
|
binaryFiles: result.binaryFiles,
|
|
1757
2526
|
tabSizeMap: result.tabSizeMap,
|
|
1758
|
-
untrackedFiles: result.untrackedFiles
|
|
2527
|
+
untrackedFiles: result.untrackedFiles,
|
|
2528
|
+
showMode: optsForDiff.showMode || void 0,
|
|
2529
|
+
commits: result.commits,
|
|
2530
|
+
truncated: result.truncated
|
|
1759
2531
|
});
|
|
1760
2532
|
});
|
|
1761
2533
|
app.get("/api/file-content", (c) => {
|
|
@@ -1791,12 +2563,17 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
1791
2563
|
const relPath = toSafeRelativePath(filePath, root);
|
|
1792
2564
|
if (!relPath) return c.json({ error: "Forbidden file path" }, 403);
|
|
1793
2565
|
const absolutePath = resolve(root, relPath);
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
2566
|
+
if (editor && editor !== "default") {
|
|
2567
|
+
const command = resolveEditorCommand(editor, absolutePath);
|
|
2568
|
+
if (command) {
|
|
2569
|
+
const { execFile } = await import("node:child_process");
|
|
2570
|
+
execFile(command.cmd, command.args, (err) => {
|
|
2571
|
+
if (err) console.error(`Failed to launch ${editor} for ${absolutePath}: ${err.message}`);
|
|
2572
|
+
});
|
|
2573
|
+
return c.json({ ok: true });
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
await (await import("open")).default(absolutePath);
|
|
1800
2577
|
return c.json({ ok: true });
|
|
1801
2578
|
} catch (err) {
|
|
1802
2579
|
return c.json({ error: `Failed to open file: ${err.message}` }, 500);
|
|
@@ -1881,7 +2658,7 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
1881
2658
|
try {
|
|
1882
2659
|
const relPath = toSafeRelativePath(filePath, getRepoRoot());
|
|
1883
2660
|
if (!relPath) return c.json({ error: "Forbidden file path" }, 403);
|
|
1884
|
-
const history = getHunkHistory(relPath, deletionStart, deletionCount, diffOpts.revisions[0] || "HEAD");
|
|
2661
|
+
const history = getHunkHistory(relPath, deletionStart, deletionCount, diffOpts.showMode ? diffOpts.showRevspecs[diffOpts.showRevspecs.length - 1] || "HEAD" : diffOpts.revisions[0] || "HEAD");
|
|
1885
2662
|
return c.json(history);
|
|
1886
2663
|
} catch (err) {
|
|
1887
2664
|
return c.json({ error: err.message || "Failed to fetch hunk history" }, 500);
|
|
@@ -2027,24 +2804,214 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
2027
2804
|
if (!await store.remove(id)) return c.json({ error: "Comment not found" }, 404);
|
|
2028
2805
|
return c.json({ ok: true });
|
|
2029
2806
|
});
|
|
2807
|
+
/** Shared helper: 404 with a stable shape so the client knows "not in PR mode". */
|
|
2808
|
+
const notInPrMode = (c) => c.json({
|
|
2809
|
+
error: "Not in PR review mode",
|
|
2810
|
+
prMode: false
|
|
2811
|
+
}, 404);
|
|
2812
|
+
app.get("/api/gh/session", async (c) => {
|
|
2813
|
+
const session = await prStore.get();
|
|
2814
|
+
if (!session) return notInPrMode(c);
|
|
2815
|
+
return c.json({
|
|
2816
|
+
prMode: true,
|
|
2817
|
+
ref: session.ref,
|
|
2818
|
+
owner: session.owner,
|
|
2819
|
+
repo: session.repo,
|
|
2820
|
+
pullNumber: session.pullNumber,
|
|
2821
|
+
baseSha: session.baseSha,
|
|
2822
|
+
headSha: session.headSha,
|
|
2823
|
+
title: session.title,
|
|
2824
|
+
url: session.url,
|
|
2825
|
+
author: session.author,
|
|
2826
|
+
additions: session.additions,
|
|
2827
|
+
deletions: session.deletions,
|
|
2828
|
+
changedFiles: session.changedFiles,
|
|
2829
|
+
existingComments: session.existingComments,
|
|
2830
|
+
submittedAt: session.submittedAt,
|
|
2831
|
+
submittedReviewId: session.submittedReviewId,
|
|
2832
|
+
submittedReviewUrl: session.submittedReviewUrl,
|
|
2833
|
+
authSource: session.authSource
|
|
2834
|
+
});
|
|
2835
|
+
});
|
|
2836
|
+
/** Re-fetch PR metadata (head SHA, diff, existing comments) and persist. */
|
|
2837
|
+
app.post("/api/gh/pr/refresh", async (c) => {
|
|
2838
|
+
const session = await prStore.get();
|
|
2839
|
+
if (!session) return notInPrMode(c);
|
|
2840
|
+
try {
|
|
2841
|
+
const refreshed = await refreshPrSession(session);
|
|
2842
|
+
await prStore.set(refreshed);
|
|
2843
|
+
return c.json({
|
|
2844
|
+
ok: true,
|
|
2845
|
+
headSha: refreshed.headSha
|
|
2846
|
+
});
|
|
2847
|
+
} catch (err) {
|
|
2848
|
+
return c.json({ error: err?.message ?? "Refresh failed" }, 500);
|
|
2849
|
+
}
|
|
2850
|
+
});
|
|
2851
|
+
/** Initialize a PR session from a ref like `1234`, `o/r#42`, or a GitHub URL. */
|
|
2852
|
+
app.post("/api/gh/pr/init", async (c) => {
|
|
2853
|
+
const body = await c.req.json().catch(() => ({}));
|
|
2854
|
+
const ref = typeof body?.ref === "string" ? body.ref : "";
|
|
2855
|
+
if (!ref.trim()) return c.json({ error: "ref is required" }, 400);
|
|
2856
|
+
try {
|
|
2857
|
+
parsePrRef(ref, await detectCwdRepo() ?? void 0);
|
|
2858
|
+
const session = await buildPrSession(ref);
|
|
2859
|
+
await prStore.set(session);
|
|
2860
|
+
return c.json({
|
|
2861
|
+
ok: true,
|
|
2862
|
+
ref: session.ref,
|
|
2863
|
+
owner: session.owner,
|
|
2864
|
+
repo: session.repo,
|
|
2865
|
+
pullNumber: session.pullNumber,
|
|
2866
|
+
url: session.url
|
|
2867
|
+
});
|
|
2868
|
+
} catch (err) {
|
|
2869
|
+
return c.json({ error: err?.message ?? "Failed to initialise PR session" }, 500);
|
|
2870
|
+
}
|
|
2871
|
+
});
|
|
2872
|
+
app.get("/api/gh/pr-session/comments", async (c) => {
|
|
2873
|
+
const session = await prStore.get();
|
|
2874
|
+
if (!session) return notInPrMode(c);
|
|
2875
|
+
return c.json(session.comments ?? []);
|
|
2876
|
+
});
|
|
2877
|
+
app.post("/api/gh/pr-session/comments", async (c) => {
|
|
2878
|
+
const body = await c.req.json();
|
|
2879
|
+
const session = await prStore.get();
|
|
2880
|
+
if (!session) return notInPrMode(c);
|
|
2881
|
+
const comment = {
|
|
2882
|
+
id: crypto.randomUUID(),
|
|
2883
|
+
filePath: body.filePath,
|
|
2884
|
+
side: body.side,
|
|
2885
|
+
lineNumber: body.lineNumber,
|
|
2886
|
+
startLineNumber: body.startLineNumber,
|
|
2887
|
+
lineContent: body.lineContent,
|
|
2888
|
+
body: body.body,
|
|
2889
|
+
status: "open",
|
|
2890
|
+
createdAt: Date.now(),
|
|
2891
|
+
replies: []
|
|
2892
|
+
};
|
|
2893
|
+
const next = {
|
|
2894
|
+
...session,
|
|
2895
|
+
comments: [...session.comments ?? [], comment]
|
|
2896
|
+
};
|
|
2897
|
+
await prStore.set(next);
|
|
2898
|
+
return c.json(comment, 201);
|
|
2899
|
+
});
|
|
2900
|
+
app.put("/api/gh/pr-session/comments/:id", async (c) => {
|
|
2901
|
+
const id = c.req.param("id");
|
|
2902
|
+
const { body, status } = await c.req.json();
|
|
2903
|
+
const session = await prStore.get();
|
|
2904
|
+
if (!session) return notInPrMode(c);
|
|
2905
|
+
const comments = (session.comments ?? []).map((cm) => cm.id === id ? {
|
|
2906
|
+
...cm,
|
|
2907
|
+
body: body ?? cm.body,
|
|
2908
|
+
status: status ?? cm.status
|
|
2909
|
+
} : cm);
|
|
2910
|
+
await prStore.set({
|
|
2911
|
+
...session,
|
|
2912
|
+
comments
|
|
2913
|
+
});
|
|
2914
|
+
const updated = comments.find((cm) => cm.id === id) ?? null;
|
|
2915
|
+
if (!updated) return c.json({ error: "Comment not found" }, 404);
|
|
2916
|
+
return c.json(updated);
|
|
2917
|
+
});
|
|
2918
|
+
app.delete("/api/gh/pr-session/comments/:id", async (c) => {
|
|
2919
|
+
const id = c.req.param("id");
|
|
2920
|
+
const session = await prStore.get();
|
|
2921
|
+
if (!session) return notInPrMode(c);
|
|
2922
|
+
const comments = (session.comments ?? []).filter((cm) => cm.id !== id);
|
|
2923
|
+
await prStore.set({
|
|
2924
|
+
...session,
|
|
2925
|
+
comments
|
|
2926
|
+
});
|
|
2927
|
+
return c.json({ ok: true });
|
|
2928
|
+
});
|
|
2929
|
+
app.post("/api/gh/pr-session/comments/:id/replies", async (c) => {
|
|
2930
|
+
const id = c.req.param("id");
|
|
2931
|
+
const { body, role, model } = await c.req.json();
|
|
2932
|
+
const session = await prStore.get();
|
|
2933
|
+
if (!session) return notInPrMode(c);
|
|
2934
|
+
const comments = (session.comments ?? []).map((cm) => {
|
|
2935
|
+
if (cm.id !== id) return cm;
|
|
2936
|
+
const reply = {
|
|
2937
|
+
id: crypto.randomUUID(),
|
|
2938
|
+
body,
|
|
2939
|
+
createdAt: Date.now(),
|
|
2940
|
+
role: role || (model ? "agent" : "user"),
|
|
2941
|
+
model: model || void 0
|
|
2942
|
+
};
|
|
2943
|
+
return {
|
|
2944
|
+
...cm,
|
|
2945
|
+
replies: [...cm.replies ?? [], reply]
|
|
2946
|
+
};
|
|
2947
|
+
});
|
|
2948
|
+
await prStore.set({
|
|
2949
|
+
...session,
|
|
2950
|
+
comments
|
|
2951
|
+
});
|
|
2952
|
+
return c.json(comments.find((cm) => cm.id === id));
|
|
2953
|
+
});
|
|
2954
|
+
/** Submit the current PR session's review (new comments + verdict + body) to GitHub. */
|
|
2955
|
+
app.post("/api/gh/submit", async (c) => {
|
|
2956
|
+
const session = await prStore.get();
|
|
2957
|
+
if (!session) return notInPrMode(c);
|
|
2958
|
+
const body = await c.req.json().catch(() => ({}));
|
|
2959
|
+
const decision = body.decision;
|
|
2960
|
+
if (decision !== "approve" && decision !== "comment" && decision !== "request-changes") return c.json({ error: "decision must be one of: approve, comment, request-changes" }, 400);
|
|
2961
|
+
const generalBody = typeof body.body === "string" ? body.body : "";
|
|
2962
|
+
const dryRun = body.dryRun === true;
|
|
2963
|
+
const result = await submitReview({
|
|
2964
|
+
resolved: {
|
|
2965
|
+
owner: session.owner,
|
|
2966
|
+
repo: session.repo,
|
|
2967
|
+
pullNumber: session.pullNumber,
|
|
2968
|
+
ref: session.ref
|
|
2969
|
+
},
|
|
2970
|
+
decision,
|
|
2971
|
+
body: generalBody,
|
|
2972
|
+
comments: session.comments ?? []
|
|
2973
|
+
});
|
|
2974
|
+
if (result.ok && !dryRun) {
|
|
2975
|
+
const next = {
|
|
2976
|
+
...session,
|
|
2977
|
+
submittedAt: Date.now(),
|
|
2978
|
+
submittedReviewId: result.reviewId,
|
|
2979
|
+
submittedReviewUrl: result.reviewUrl,
|
|
2980
|
+
authSource: result.authSource
|
|
2981
|
+
};
|
|
2982
|
+
await prStore.set(next);
|
|
2983
|
+
}
|
|
2984
|
+
return c.json({
|
|
2985
|
+
ok: result.ok,
|
|
2986
|
+
reviewId: result.reviewId,
|
|
2987
|
+
reviewUrl: result.reviewUrl,
|
|
2988
|
+
failedComments: result.failedComments ?? 0,
|
|
2989
|
+
authSource: result.authSource,
|
|
2990
|
+
error: result.error,
|
|
2991
|
+
dryRun
|
|
2992
|
+
});
|
|
2993
|
+
});
|
|
2030
2994
|
app.post("/api/review/send", async (c) => {
|
|
2031
2995
|
const body = await c.req.json().catch(() => ({}));
|
|
2032
2996
|
const generalComment = typeof body?.generalComment === "string" ? body.generalComment : void 0;
|
|
2033
|
-
const decision = body?.decision === "approved" || body?.decision === "changes-requested" || body?.decision === "rejected" ? body.decision : void 0;
|
|
2997
|
+
const decision = body?.decision === "approved" || body?.decision === "changes-requested" || body?.decision === "rejected" || body?.decision === "comment-only" ? body.decision : void 0;
|
|
2998
|
+
const mode = body?.mode === "comment-only" || body?.mode === "standard" ? body.mode : "standard";
|
|
2034
2999
|
const all = await store.getAll();
|
|
2035
3000
|
const openCount = all.filter((x) => x.status === "open").length;
|
|
2036
3001
|
const payload = reviewSession.send({
|
|
2037
3002
|
sentAt: Date.now(),
|
|
2038
|
-
commentXml: formatComments(all, generalComment, decision),
|
|
3003
|
+
commentXml: formatComments(all, generalComment, decision, mode),
|
|
2039
3004
|
openCount,
|
|
2040
3005
|
comments: all,
|
|
2041
|
-
decision
|
|
3006
|
+
decision,
|
|
3007
|
+
mode
|
|
2042
3008
|
});
|
|
2043
3009
|
return c.json({
|
|
2044
3010
|
ok: true,
|
|
2045
3011
|
round: payload.round,
|
|
2046
3012
|
openCount: payload.openCount,
|
|
2047
3013
|
decision: payload.decision,
|
|
3014
|
+
mode: payload.mode,
|
|
2048
3015
|
waiters: reviewSession.snapshot().waiters
|
|
2049
3016
|
});
|
|
2050
3017
|
});
|
|
@@ -2071,6 +3038,29 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
2071
3038
|
if (!plan) return c.json({ error: "Plan not found" }, 404);
|
|
2072
3039
|
return c.json(plan);
|
|
2073
3040
|
});
|
|
3041
|
+
app.get("/api/plans/:id/versions", async (c) => {
|
|
3042
|
+
const plan = await plans.get(c.req.param("id"));
|
|
3043
|
+
if (!plan) return c.json({ error: "Plan not found" }, 404);
|
|
3044
|
+
return c.json(plan.versions ?? []);
|
|
3045
|
+
});
|
|
3046
|
+
app.get("/api/plans/:id/versions/:n", async (c) => {
|
|
3047
|
+
const id = c.req.param("id");
|
|
3048
|
+
const n = Number(c.req.param("n"));
|
|
3049
|
+
if (!Number.isFinite(n) || n < 1) return c.json({ error: "version must be a positive integer" }, 400);
|
|
3050
|
+
const plan = await plans.get(id);
|
|
3051
|
+
if (!plan) return c.json({ error: "Plan not found" }, 404);
|
|
3052
|
+
const version = (plan.versions ?? []).find((v) => v.version === n);
|
|
3053
|
+
if (!version) return c.json({ error: `Version ${n} not found` }, 404);
|
|
3054
|
+
return c.json({
|
|
3055
|
+
version,
|
|
3056
|
+
plan: {
|
|
3057
|
+
id: plan.id,
|
|
3058
|
+
title: plan.title,
|
|
3059
|
+
decision: plan.decision,
|
|
3060
|
+
currentVersion: plan.version
|
|
3061
|
+
}
|
|
3062
|
+
});
|
|
3063
|
+
});
|
|
2074
3064
|
app.post("/api/plans", async (c) => {
|
|
2075
3065
|
const body = await c.req.json().catch(() => ({}));
|
|
2076
3066
|
if (typeof body.body !== "string" || !body.body.trim()) return c.json({ error: "A plan body (markdown) is required" }, 400);
|
|
@@ -2110,6 +3100,7 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
2110
3100
|
const anchorStart = startLineNumber ?? lineNumber;
|
|
2111
3101
|
const lineContent = typeof body.lineContent === "string" && body.lineContent ? body.lineContent : lineNumber > 0 ? extractPlanLines(plan.body, anchorStart, lineNumber) : "";
|
|
2112
3102
|
const sectionTitle = typeof body.sectionTitle === "string" && body.sectionTitle ? body.sectionTitle : lineNumber > 0 ? sectionTitleForLine(plan.body, anchorStart) : void 0;
|
|
3103
|
+
const createdAtPlanVersion = Number.isFinite(body.createdAtPlanVersion) ? Number(body.createdAtPlanVersion) : plan.version;
|
|
2113
3104
|
const comment = {
|
|
2114
3105
|
id: crypto.randomUUID(),
|
|
2115
3106
|
lineNumber,
|
|
@@ -2119,6 +3110,7 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
2119
3110
|
body: body.body,
|
|
2120
3111
|
status: "open",
|
|
2121
3112
|
createdAt: Date.now(),
|
|
3113
|
+
createdAtPlanVersion,
|
|
2122
3114
|
replies: []
|
|
2123
3115
|
};
|
|
2124
3116
|
const updated = await plans.addComment(planId, comment);
|
|
@@ -2168,8 +3160,9 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
2168
3160
|
const planId = c.req.param("id");
|
|
2169
3161
|
const body = await c.req.json().catch(() => ({}));
|
|
2170
3162
|
const decision = body.decision;
|
|
2171
|
-
if (decision !== "approved" && decision !== "rejected" && decision !== "changes-requested") return c.json({ error: "decision must be one of: approved, rejected, changes-requested" }, 400);
|
|
3163
|
+
if (decision !== "approved" && decision !== "rejected" && decision !== "changes-requested" && decision !== "comment-only") return c.json({ error: "decision must be one of: approved, rejected, changes-requested, comment-only" }, 400);
|
|
2172
3164
|
const decisionComment = typeof body.decisionComment === "string" ? body.decisionComment : void 0;
|
|
3165
|
+
const mode = body?.mode === "comment-only" || body?.mode === "standard" ? body.mode : "standard";
|
|
2173
3166
|
const plan = await plans.setDecision(planId, decision, decisionComment);
|
|
2174
3167
|
if (!plan) return c.json({ error: "Plan not found" }, 404);
|
|
2175
3168
|
const openCommentCount = (plan.comments ?? []).filter((x) => x.status === "open").length;
|
|
@@ -2178,14 +3171,16 @@ function createApp(clientDir, diffOpts = DEFAULTS$1, commentStore, planStore) {
|
|
|
2178
3171
|
planId,
|
|
2179
3172
|
decision,
|
|
2180
3173
|
decisionComment: plan.decisionComment,
|
|
2181
|
-
reviewXml: formatPlanReview(plan),
|
|
3174
|
+
reviewXml: formatPlanReview(plan, { mode }),
|
|
2182
3175
|
openCommentCount,
|
|
2183
|
-
plan
|
|
3176
|
+
plan,
|
|
3177
|
+
mode
|
|
2184
3178
|
});
|
|
2185
3179
|
return c.json({
|
|
2186
3180
|
ok: true,
|
|
2187
3181
|
round: payload.round,
|
|
2188
3182
|
decision,
|
|
3183
|
+
mode: payload.mode,
|
|
2189
3184
|
openCommentCount,
|
|
2190
3185
|
waiters: planReviewSession.snapshot().waiters
|
|
2191
3186
|
});
|
|
@@ -2269,7 +3264,11 @@ async function newestActivityMs(projectDir) {
|
|
|
2269
3264
|
return null;
|
|
2270
3265
|
}
|
|
2271
3266
|
};
|
|
2272
|
-
for (const name of [
|
|
3267
|
+
for (const name of [
|
|
3268
|
+
"comments.json",
|
|
3269
|
+
"plans.json",
|
|
3270
|
+
"pr-session.json"
|
|
3271
|
+
]) {
|
|
2273
3272
|
const file = join(projectDir, name);
|
|
2274
3273
|
if (!existsSync(file)) continue;
|
|
2275
3274
|
const m = await safeStat(file);
|
|
@@ -2320,14 +3319,35 @@ function startServer(options) {
|
|
|
2320
3319
|
cleanupStaleProjects().catch((err) => {
|
|
2321
3320
|
console.error("Failed to clean up stale projects:", err);
|
|
2322
3321
|
});
|
|
3322
|
+
let prMode = false;
|
|
3323
|
+
const prReady = (async () => {
|
|
3324
|
+
if (!options.prRef) return;
|
|
3325
|
+
console.error(`Building PR session for ${options.prRef}...`);
|
|
3326
|
+
try {
|
|
3327
|
+
const store = new FilePrSessionStore();
|
|
3328
|
+
const existing = await store.get();
|
|
3329
|
+
if (!existing || existing.ref !== options.prRef) {
|
|
3330
|
+
const session = await buildPrSession(options.prRef);
|
|
3331
|
+
await store.set(session);
|
|
3332
|
+
}
|
|
3333
|
+
prMode = true;
|
|
3334
|
+
} catch (err) {
|
|
3335
|
+
console.error(`[pr-session] failed to build session for ${options.prRef}: ${err?.message ?? err}`);
|
|
3336
|
+
}
|
|
3337
|
+
})();
|
|
2323
3338
|
const app = createApp(options.clientDir, options.diffOpts);
|
|
2324
3339
|
return new Promise((resolve) => {
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
3340
|
+
prReady.then(() => {
|
|
3341
|
+
serve({
|
|
3342
|
+
fetch: app.fetch,
|
|
3343
|
+
port: options.port,
|
|
3344
|
+
hostname: options.host
|
|
3345
|
+
}, (info) => {
|
|
3346
|
+
resolve({
|
|
3347
|
+
port: info.port,
|
|
3348
|
+
prMode
|
|
3349
|
+
});
|
|
3350
|
+
});
|
|
2331
3351
|
});
|
|
2332
3352
|
});
|
|
2333
3353
|
}
|
|
@@ -2464,7 +3484,7 @@ const QUOTES = [
|
|
|
2464
3484
|
author: "Unknown"
|
|
2465
3485
|
},
|
|
2466
3486
|
{
|
|
2467
|
-
text: "A QA engineer walks into a
|
|
3487
|
+
text: "A QA engineer walks into a coffie shop. Orders 0 cups. Orders 999999 cups. Orders NULL cups. Walks in through the window.",
|
|
2468
3488
|
author: "Unknown"
|
|
2469
3489
|
},
|
|
2470
3490
|
{
|
|
@@ -2715,8 +3735,68 @@ function playStartupDisplay() {
|
|
|
2715
3735
|
})();
|
|
2716
3736
|
}
|
|
2717
3737
|
//#endregion
|
|
3738
|
+
//#region src/lib/find-tui-binary.ts
|
|
3739
|
+
/**
|
|
3740
|
+
* Locate the `diffing-tui` native binary. Looks, in order:
|
|
3741
|
+
* 1. Sibling of the calling module (`dist/diffing-tui[.exe]` after build).
|
|
3742
|
+
* 2. `bin/diffing-tui[.exe]` next to the package root.
|
|
3743
|
+
* 3. `target/release/diffing-tui[.exe]` next to the package root
|
|
3744
|
+
* (cargo release build).
|
|
3745
|
+
* 4. `target/debug/diffing-tui[.exe]` next to the package root
|
|
3746
|
+
* (cargo debug build — the common case during development, especially
|
|
3747
|
+
* on Windows where release builds are slow).
|
|
3748
|
+
* 5. `$PATH` lookup via `which` / `where`.
|
|
3749
|
+
*
|
|
3750
|
+
* Returns the absolute path of the first match, or `null` if none are found.
|
|
3751
|
+
*
|
|
3752
|
+
* `callerUrl` is the `import.meta.url` of the caller — pass `import.meta.url`
|
|
3753
|
+
* from `cli.ts`. Exposed as a parameter so unit tests can pin the search
|
|
3754
|
+
* root to a known location instead of depending on the test runner's CWD.
|
|
3755
|
+
*/
|
|
3756
|
+
function findTuiBinary$1(callerUrl) {
|
|
3757
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
3758
|
+
const here = dirname(fileURLToPath(callerUrl));
|
|
3759
|
+
const candidates = [
|
|
3760
|
+
resolve(here, `diffing-tui${ext}`),
|
|
3761
|
+
resolve(here, "..", "bin", `diffing-tui${ext}`),
|
|
3762
|
+
resolve(here, "..", "target", "release", `diffing-tui${ext}`),
|
|
3763
|
+
resolve(here, "..", "..", "target", "release", `diffing-tui${ext}`),
|
|
3764
|
+
resolve(here, "..", "target", "debug", `diffing-tui${ext}`),
|
|
3765
|
+
resolve(here, "..", "..", "target", "debug", `diffing-tui${ext}`)
|
|
3766
|
+
];
|
|
3767
|
+
for (const c of candidates) if (existsSync(c)) return c;
|
|
3768
|
+
try {
|
|
3769
|
+
const out = execFileSync(process.platform === "win32" ? "where" : "which", ["diffing-tui"], {
|
|
3770
|
+
encoding: "utf-8",
|
|
3771
|
+
stdio: [
|
|
3772
|
+
"ignore",
|
|
3773
|
+
"pipe",
|
|
3774
|
+
"ignore"
|
|
3775
|
+
]
|
|
3776
|
+
}).trim();
|
|
3777
|
+
if (out) {
|
|
3778
|
+
const first = out.split(/\r?\n/)[0]?.trim();
|
|
3779
|
+
if (first && isAbsolute(first)) return first;
|
|
3780
|
+
}
|
|
3781
|
+
} catch {}
|
|
3782
|
+
return null;
|
|
3783
|
+
}
|
|
3784
|
+
//#endregion
|
|
2718
3785
|
//#region src/cli.ts
|
|
2719
3786
|
const args = process.argv.slice(2);
|
|
3787
|
+
let prRef = null;
|
|
3788
|
+
let ghPrConsumed = 0;
|
|
3789
|
+
if (args[0]?.startsWith("gh pr ") === true) {
|
|
3790
|
+
const rest = args[0].slice(6).trim();
|
|
3791
|
+
if (rest) {
|
|
3792
|
+
prRef = rest;
|
|
3793
|
+
ghPrConsumed = 1;
|
|
3794
|
+
}
|
|
3795
|
+
} else if (args[0] === "gh" && args[1] === "pr" && args[2] !== void 0) {
|
|
3796
|
+
prRef = args[2];
|
|
3797
|
+
ghPrConsumed = 3;
|
|
3798
|
+
}
|
|
3799
|
+
if (ghPrConsumed > 0) args.splice(0, ghPrConsumed);
|
|
2720
3800
|
if (new Set([
|
|
2721
3801
|
"await-review",
|
|
2722
3802
|
"reply",
|
|
@@ -2725,17 +3805,30 @@ if (new Set([
|
|
|
2725
3805
|
"url",
|
|
2726
3806
|
"mcp",
|
|
2727
3807
|
"plan",
|
|
2728
|
-
"update"
|
|
3808
|
+
"update",
|
|
3809
|
+
"gh"
|
|
2729
3810
|
]).has(args[0])) {
|
|
2730
3811
|
if (args[0] === "mcp") {
|
|
2731
|
-
const { startMcpServer } = await import("./mcp-
|
|
3812
|
+
const { startMcpServer } = await import("./mcp-DNZQvKKF.mjs");
|
|
2732
3813
|
await startMcpServer();
|
|
2733
3814
|
await new Promise(() => {});
|
|
2734
3815
|
}
|
|
2735
|
-
const { runSubcommand } = await import("./cli-agent-
|
|
3816
|
+
const { runSubcommand } = await import("./cli-agent-B4hU6Xay.mjs");
|
|
2736
3817
|
process.exit(await runSubcommand(args[0], args.slice(1)));
|
|
2737
3818
|
}
|
|
3819
|
+
let showSubcommand = false;
|
|
3820
|
+
if (args[0] === "show") {
|
|
3821
|
+
showSubcommand = true;
|
|
3822
|
+
args.shift();
|
|
3823
|
+
}
|
|
2738
3824
|
const opts = parseDiffOptions(args);
|
|
3825
|
+
if (showSubcommand) {
|
|
3826
|
+
if (opts.revisions.length === 0 && !opts.help && !opts.version) {
|
|
3827
|
+
console.error("Usage: diffing show <revspec>... [-- <pathspec>...]");
|
|
3828
|
+
process.exit(5);
|
|
3829
|
+
}
|
|
3830
|
+
intoShowMode(opts);
|
|
3831
|
+
}
|
|
2739
3832
|
if (opts.help) {
|
|
2740
3833
|
printHelp();
|
|
2741
3834
|
process.exit(0);
|
|
@@ -2751,6 +3844,10 @@ if (envErr) {
|
|
|
2751
3844
|
console.error(envErr);
|
|
2752
3845
|
process.exit(1);
|
|
2753
3846
|
}
|
|
3847
|
+
if (opts.outputMode === "tui") {
|
|
3848
|
+
const tuiResult = await launchTui(args);
|
|
3849
|
+
process.exit(tuiResult);
|
|
3850
|
+
}
|
|
2754
3851
|
if (opts.outputMode === "terminal") {
|
|
2755
3852
|
const exitCode = runTerminalDiff(opts);
|
|
2756
3853
|
process.exit(exitCode);
|
|
@@ -2759,7 +3856,7 @@ const __pkgDir = dirname(fileURLToPath(import.meta.url));
|
|
|
2759
3856
|
const currentVersion = JSON.parse(readFileSync(resolve(__pkgDir, "..", "package.json"), "utf-8")).version;
|
|
2760
3857
|
const updateCheckPromise = (async () => {
|
|
2761
3858
|
try {
|
|
2762
|
-
const { checkForUpdates } = await import("./update-check-
|
|
3859
|
+
const { checkForUpdates } = await import("./update-check-DAPFCqpF.mjs");
|
|
2763
3860
|
return await checkForUpdates(currentVersion);
|
|
2764
3861
|
} catch {
|
|
2765
3862
|
return null;
|
|
@@ -2768,12 +3865,12 @@ const updateCheckPromise = (async () => {
|
|
|
2768
3865
|
const port = await getPort(opts.port ? { port: opts.port } : void 0);
|
|
2769
3866
|
const host = opts.host;
|
|
2770
3867
|
const clientDir = resolve(__pkgDir, "client");
|
|
2771
|
-
const {
|
|
2772
|
-
const { port: actualPort } = await startServer({
|
|
3868
|
+
const { port: actualPort, prMode } = await startServer({
|
|
2773
3869
|
port,
|
|
2774
3870
|
host,
|
|
2775
|
-
clientDir: existsSync
|
|
2776
|
-
diffOpts: opts
|
|
3871
|
+
clientDir: existsSync(clientDir) ? clientDir : resolve(process.cwd(), "dist/client"),
|
|
3872
|
+
diffOpts: opts,
|
|
3873
|
+
prRef: prRef ?? void 0
|
|
2777
3874
|
});
|
|
2778
3875
|
const localUrl = `http://${host}:${actualPort}`;
|
|
2779
3876
|
try {
|
|
@@ -2789,7 +3886,9 @@ try {
|
|
|
2789
3886
|
pid: process.pid,
|
|
2790
3887
|
repoRoot,
|
|
2791
3888
|
startedAt: Date.now(),
|
|
2792
|
-
version: currentVersion
|
|
3889
|
+
version: currentVersion,
|
|
3890
|
+
mode: prMode ? "gh-pr" : "web",
|
|
3891
|
+
prRef: prMode ? prRef ?? void 0 : void 0
|
|
2793
3892
|
});
|
|
2794
3893
|
} catch {}
|
|
2795
3894
|
console.log(`diffing server running at ${localUrl}`);
|
|
@@ -2797,7 +3896,7 @@ await playStartupDisplay();
|
|
|
2797
3896
|
try {
|
|
2798
3897
|
const updateInfo = await updateCheckPromise;
|
|
2799
3898
|
if (updateInfo?.hasUpdate) {
|
|
2800
|
-
const { printUpdateDisclaimer } = await import("./update-check-
|
|
3899
|
+
const { printUpdateDisclaimer } = await import("./update-check-DAPFCqpF.mjs");
|
|
2801
3900
|
printUpdateDisclaimer(currentVersion, updateInfo.latestVersion);
|
|
2802
3901
|
}
|
|
2803
3902
|
} catch {}
|
|
@@ -2817,5 +3916,64 @@ const shutdown = () => {
|
|
|
2817
3916
|
};
|
|
2818
3917
|
process.on("SIGINT", shutdown);
|
|
2819
3918
|
process.on("SIGTERM", shutdown);
|
|
3919
|
+
/**
|
|
3920
|
+
* Wrapper around `findTuiBinary` that passes this script's `import.meta.url`
|
|
3921
|
+
* so the search paths anchor to the bundled CLI's directory. The real
|
|
3922
|
+
* implementation lives in `lib/find-tui-binary.ts` and is unit-tested there.
|
|
3923
|
+
*/
|
|
3924
|
+
function findTuiBinary() {
|
|
3925
|
+
return findTuiBinary$1(import.meta.url);
|
|
3926
|
+
}
|
|
3927
|
+
/**
|
|
3928
|
+
* Fall back to default `git diff` output when the TUI cannot run.
|
|
3929
|
+
* Re-parses `args` so the terminal output exactly matches `diffing` (no flag)
|
|
3930
|
+
* in a non-TTY context.
|
|
3931
|
+
*/
|
|
3932
|
+
function runTerminalFallback(args) {
|
|
3933
|
+
const terminalOpts = parseDiffOptions(args.filter((a) => a !== "--tui"));
|
|
3934
|
+
terminalOpts.outputMode = "terminal";
|
|
3935
|
+
terminalOpts.tui = false;
|
|
3936
|
+
return runTerminalDiff(terminalOpts);
|
|
3937
|
+
}
|
|
3938
|
+
/**
|
|
3939
|
+
* Launch the native-Rust TUI binary as a child process. Returns the process
|
|
3940
|
+
* exit code. If the TUI cannot start (no TTY, missing binary), prints a
|
|
3941
|
+
* single stderr line and falls back to the default `git diff` output.
|
|
3942
|
+
*/
|
|
3943
|
+
async function launchTui(args) {
|
|
3944
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
3945
|
+
console.error("diffing --tui requires a TTY; falling back to git diff");
|
|
3946
|
+
return runTerminalFallback(args);
|
|
3947
|
+
}
|
|
3948
|
+
const bin = findTuiBinary();
|
|
3949
|
+
if (!bin) {
|
|
3950
|
+
console.error("diffing-tui binary not found; build it with `pnpm build:tui`; falling back to git diff");
|
|
3951
|
+
return runTerminalFallback(args);
|
|
3952
|
+
}
|
|
3953
|
+
const forwarded = args.filter((a) => a !== "--tui");
|
|
3954
|
+
let repoRoot;
|
|
3955
|
+
try {
|
|
3956
|
+
repoRoot = getRepoRoot();
|
|
3957
|
+
} catch {
|
|
3958
|
+
repoRoot = process.cwd();
|
|
3959
|
+
}
|
|
3960
|
+
console.log(`diffing TUI starting (binary: ${bin})`);
|
|
3961
|
+
await playStartupDisplay();
|
|
3962
|
+
return new Promise((resolveP) => {
|
|
3963
|
+
const child = spawn(bin, [
|
|
3964
|
+
"--repo",
|
|
3965
|
+
repoRoot,
|
|
3966
|
+
...forwarded
|
|
3967
|
+
], {
|
|
3968
|
+
stdio: "inherit",
|
|
3969
|
+
env: process.env
|
|
3970
|
+
});
|
|
3971
|
+
child.on("exit", (code) => resolveP(code ?? 0));
|
|
3972
|
+
child.on("error", (err) => {
|
|
3973
|
+
console.error(`diffing-tui failed to start: ${err.message}; falling back to git diff`);
|
|
3974
|
+
resolveP(runTerminalFallback(args));
|
|
3975
|
+
});
|
|
3976
|
+
});
|
|
3977
|
+
}
|
|
2820
3978
|
//#endregion
|
|
2821
|
-
export {};
|
|
3979
|
+
export { findTuiBinary };
|