diffity 0.1.0 → 0.1.1
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/dist/index.js +1081 -0
- package/dist/ui/assets/abap-BdImnpbu.js +1 -0
- package/dist/ui/assets/actionscript-3-CoDkCxhg.js +1 -0
- package/dist/ui/assets/ada-bCR0ucgS.js +1 -0
- package/dist/ui/assets/andromeeda-C4gqWexZ.js +1 -0
- package/dist/ui/assets/angular-html-DA-rfuFy.js +1 -0
- package/dist/ui/assets/angular-ts-BrjP3tb8.js +1 -0
- package/dist/ui/assets/apache-Pmp26Uib.js +1 -0
- package/dist/ui/assets/apex-D8_7TLub.js +1 -0
- package/dist/ui/assets/apl-CORt7UWP.js +1 -0
- package/dist/ui/assets/applescript-Co6uUVPk.js +1 -0
- package/dist/ui/assets/ara-BRHolxvo.js +1 -0
- package/dist/ui/assets/asciidoc-Ve4PFQV2.js +1 -0
- package/dist/ui/assets/asm-D_Q5rh1f.js +1 -0
- package/dist/ui/assets/astro-HNnZUWAn.js +1 -0
- package/dist/ui/assets/aurora-x-D-2ljcwZ.js +1 -0
- package/dist/ui/assets/awk-DMzUqQB5.js +1 -0
- package/dist/ui/assets/ayu-dark-DYE7WIF3.js +1 -0
- package/dist/ui/assets/ayu-light-BA47KaF1.js +1 -0
- package/dist/ui/assets/ayu-mirage-32ctXXKs.js +1 -0
- package/dist/ui/assets/ballerina-BFfxhgS-.js +1 -0
- package/dist/ui/assets/bat-BkioyH1T.js +1 -0
- package/dist/ui/assets/beancount-k_qm7-4y.js +1 -0
- package/dist/ui/assets/berry-uYugtg8r.js +1 -0
- package/dist/ui/assets/bibtex-CHM0blh-.js +1 -0
- package/dist/ui/assets/bicep-Bmn6On1c.js +1 -0
- package/dist/ui/assets/bird2-BIv1doCn.js +1 -0
- package/dist/ui/assets/blade-BjGOyj-B.js +1 -0
- package/dist/ui/assets/bsl-BO_Y6i37.js +1 -0
- package/dist/ui/assets/c-BIGW1oBm.js +1 -0
- package/dist/ui/assets/c3-eo99z4R2.js +1 -0
- package/dist/ui/assets/cadence-Bv_4Rxtq.js +1 -0
- package/dist/ui/assets/cairo-KRGpt6FW.js +1 -0
- package/dist/ui/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- package/dist/ui/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- package/dist/ui/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- package/dist/ui/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- package/dist/ui/assets/clarity-D53aC0YG.js +1 -0
- package/dist/ui/assets/clojure-P80f7IUj.js +1 -0
- package/dist/ui/assets/cmake-D1j8_8rp.js +1 -0
- package/dist/ui/assets/cobol-nBiQ_Alo.js +1 -0
- package/dist/ui/assets/codeowners-Bp6g37R7.js +1 -0
- package/dist/ui/assets/codeql-DsOJ9woJ.js +1 -0
- package/dist/ui/assets/coffee-Ch7k5sss.js +1 -0
- package/dist/ui/assets/common-lisp-Cg-RD9OK.js +1 -0
- package/dist/ui/assets/coq-DkFqJrB1.js +1 -0
- package/dist/ui/assets/cpp-CofmeUqb.js +1 -0
- package/dist/ui/assets/crystal-DNxU26gB.js +1 -0
- package/dist/ui/assets/csharp-COcwbKMJ.js +1 -0
- package/dist/ui/assets/css-CLj8gQPS.js +1 -0
- package/dist/ui/assets/csv-fuZLfV_i.js +1 -0
- package/dist/ui/assets/cue-D82EKSYY.js +1 -0
- package/dist/ui/assets/cypher-COkxafJQ.js +1 -0
- package/dist/ui/assets/d-85-TOEBH.js +1 -0
- package/dist/ui/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/ui/assets/dart-bE4Kk8sk.js +1 -0
- package/dist/ui/assets/dax-CEL-wOlO.js +1 -0
- package/dist/ui/assets/desktop-BmXAJ9_W.js +1 -0
- package/dist/ui/assets/diff-D97Zzqfu.js +1 -0
- package/dist/ui/assets/docker-BcOcwvcX.js +1 -0
- package/dist/ui/assets/dotenv-Da5cRb03.js +1 -0
- package/dist/ui/assets/dracula-BzJJZx-M.js +1 -0
- package/dist/ui/assets/dracula-soft-BXkSAIEj.js +1 -0
- package/dist/ui/assets/dream-maker-BtqSS_iP.js +1 -0
- package/dist/ui/assets/edge-FbVlp4U3.js +1 -0
- package/dist/ui/assets/elixir-CkH2-t6x.js +1 -0
- package/dist/ui/assets/elm-DbKCFpqz.js +1 -0
- package/dist/ui/assets/emacs-lisp-CXvaQtF9.js +1 -0
- package/dist/ui/assets/erb-BYCe7drp.js +1 -0
- package/dist/ui/assets/erlang-DsQrWhSR.js +1 -0
- package/dist/ui/assets/everforest-dark-BgDCqdQA.js +1 -0
- package/dist/ui/assets/everforest-light-C8M2exoo.js +1 -0
- package/dist/ui/assets/fennel-BYunw83y.js +1 -0
- package/dist/ui/assets/fish-BvzEVeQv.js +1 -0
- package/dist/ui/assets/fluent-C4IJs8-o.js +1 -0
- package/dist/ui/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- package/dist/ui/assets/fortran-free-form-BxgE0vQu.js +1 -0
- package/dist/ui/assets/fsharp-CXgrBDvD.js +1 -0
- package/dist/ui/assets/gdresource-BOOCDP_w.js +1 -0
- package/dist/ui/assets/gdscript-C5YyOfLZ.js +1 -0
- package/dist/ui/assets/gdshader-DkwncUOv.js +1 -0
- package/dist/ui/assets/genie-D0YGMca9.js +1 -0
- package/dist/ui/assets/gherkin-DyxjwDmM.js +1 -0
- package/dist/ui/assets/git-commit-F4YmCXRG.js +1 -0
- package/dist/ui/assets/git-rebase-r7XF79zn.js +1 -0
- package/dist/ui/assets/github-dark-DHJKELXO.js +1 -0
- package/dist/ui/assets/github-dark-default-Cuk6v7N8.js +1 -0
- package/dist/ui/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- package/dist/ui/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- package/dist/ui/assets/github-light-DAi9KRSo.js +1 -0
- package/dist/ui/assets/github-light-default-D7oLnXFd.js +1 -0
- package/dist/ui/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- package/dist/ui/assets/gleam-BspZqrRM.js +1 -0
- package/dist/ui/assets/glimmer-js-ByusRIyA.js +1 -0
- package/dist/ui/assets/glimmer-ts-BfAWNZQY.js +1 -0
- package/dist/ui/assets/glsl-DplSGwfg.js +1 -0
- package/dist/ui/assets/gn-n2N0HUVH.js +1 -0
- package/dist/ui/assets/gnuplot-DdkO51Og.js +1 -0
- package/dist/ui/assets/go-C27-OAKa.js +1 -0
- package/dist/ui/assets/graphql-ChdNCCLP.js +1 -0
- package/dist/ui/assets/groovy-gcz8RCvz.js +1 -0
- package/dist/ui/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- package/dist/ui/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- package/dist/ui/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- package/dist/ui/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- package/dist/ui/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- package/dist/ui/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- package/dist/ui/assets/hack-i7_Ulhet.js +1 -0
- package/dist/ui/assets/haml-D5jkg6IW.js +1 -0
- package/dist/ui/assets/handlebars-BpdQsYii.js +1 -0
- package/dist/ui/assets/haskell-Df6bDoY_.js +1 -0
- package/dist/ui/assets/haxe-CzTSHFRz.js +1 -0
- package/dist/ui/assets/hcl-BWvSN4gD.js +1 -0
- package/dist/ui/assets/hjson-D5-asLiD.js +1 -0
- package/dist/ui/assets/hlsl-D3lLCCz7.js +1 -0
- package/dist/ui/assets/horizon-BUw7H-hv.js +1 -0
- package/dist/ui/assets/horizon-bright-CUuTKBJd.js +1 -0
- package/dist/ui/assets/houston-DnULxvSX.js +1 -0
- package/dist/ui/assets/html-derivative-DlHx6ybY.js +1 -0
- package/dist/ui/assets/html-pp8916En.js +1 -0
- package/dist/ui/assets/http-jrhK8wxY.js +1 -0
- package/dist/ui/assets/hurl-irOxFIW8.js +1 -0
- package/dist/ui/assets/hxml-Bvhsp5Yf.js +1 -0
- package/dist/ui/assets/hy-DFXneXwc.js +1 -0
- package/dist/ui/assets/imba-DGztddWO.js +1 -0
- package/dist/ui/assets/index-B2RHRT_g.css +1 -0
- package/dist/ui/assets/index-BUq0B-nJ.js +198 -0
- package/dist/ui/assets/ini-BEwlwnbL.js +1 -0
- package/dist/ui/assets/java-CylS5w8V.js +1 -0
- package/dist/ui/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/ui/assets/jinja-f2NsQr07.js +1 -0
- package/dist/ui/assets/jison-wvAkD_A8.js +1 -0
- package/dist/ui/assets/json-Cp-IABpG.js +1 -0
- package/dist/ui/assets/json5-C9tS-k6U.js +1 -0
- package/dist/ui/assets/jsonc-Des-eS-w.js +1 -0
- package/dist/ui/assets/jsonl-DcaNXYhu.js +1 -0
- package/dist/ui/assets/jsonnet-DFQXde-d.js +1 -0
- package/dist/ui/assets/jssm-C2t-YnRu.js +1 -0
- package/dist/ui/assets/jsx-g9-lgVsj.js +1 -0
- package/dist/ui/assets/julia-CxzCAyBv.js +1 -0
- package/dist/ui/assets/just-VxiPbLrw.js +1 -0
- package/dist/ui/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- package/dist/ui/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- package/dist/ui/assets/kanagawa-wave-DWedfzmr.js +1 -0
- package/dist/ui/assets/kdl-DV7GczEv.js +1 -0
- package/dist/ui/assets/kotlin-BdnUsdx6.js +1 -0
- package/dist/ui/assets/kusto-wEQ09or8.js +1 -0
- package/dist/ui/assets/laserwave-DUszq2jm.js +1 -0
- package/dist/ui/assets/latex-CWtU0Tv5.js +1 -0
- package/dist/ui/assets/lean-BZvkOJ9d.js +1 -0
- package/dist/ui/assets/less-B1dDrJ26.js +1 -0
- package/dist/ui/assets/light-plus-B7mTdjB0.js +1 -0
- package/dist/ui/assets/liquid-C0sCDyMI.js +1 -0
- package/dist/ui/assets/llvm-DjAJT7YJ.js +1 -0
- package/dist/ui/assets/log-2UxHyX5q.js +1 -0
- package/dist/ui/assets/logo-BtOb2qkB.js +1 -0
- package/dist/ui/assets/lua-BaeVxFsk.js +1 -0
- package/dist/ui/assets/luau-C-HG3fhB.js +1 -0
- package/dist/ui/assets/make-CHLpvVh8.js +1 -0
- package/dist/ui/assets/markdown-Cvjx9yec.js +1 -0
- package/dist/ui/assets/marko-DjSrsDqO.js +1 -0
- package/dist/ui/assets/material-theme-D5KoaKCx.js +1 -0
- package/dist/ui/assets/material-theme-darker-BfHTSMKl.js +1 -0
- package/dist/ui/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- package/dist/ui/assets/material-theme-ocean-CyktbL80.js +1 -0
- package/dist/ui/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- package/dist/ui/assets/matlab-D7o27uSR.js +1 -0
- package/dist/ui/assets/mdc-DTYItulj.js +1 -0
- package/dist/ui/assets/mdx-Cmh6b_Ma.js +1 -0
- package/dist/ui/assets/mermaid-mWjccvbQ.js +1 -0
- package/dist/ui/assets/min-dark-CafNBF8u.js +1 -0
- package/dist/ui/assets/min-light-CTRr51gU.js +1 -0
- package/dist/ui/assets/mipsasm-CKIfxQSi.js +1 -0
- package/dist/ui/assets/mojo-rZm6bMo-.js +1 -0
- package/dist/ui/assets/monokai-D4h5O-jR.js +1 -0
- package/dist/ui/assets/moonbit-_H4v1dQx.js +1 -0
- package/dist/ui/assets/move-IF9eRakj.js +1 -0
- package/dist/ui/assets/narrat-DRg8JJMk.js +1 -0
- package/dist/ui/assets/nextflow-C-mBbutL.js +1 -0
- package/dist/ui/assets/nextflow-groovy-vE_lwT2v.js +1 -0
- package/dist/ui/assets/nginx-BpAMiNFr.js +1 -0
- package/dist/ui/assets/night-owl-C39BiMTA.js +1 -0
- package/dist/ui/assets/night-owl-light-CMTm3GFP.js +1 -0
- package/dist/ui/assets/nim-BIad80T-.js +1 -0
- package/dist/ui/assets/nix-CwoSXNpI.js +1 -0
- package/dist/ui/assets/nord-Ddv68eIx.js +1 -0
- package/dist/ui/assets/nushell-Cz2AlsmD.js +1 -0
- package/dist/ui/assets/objective-c-DXmwc3jG.js +1 -0
- package/dist/ui/assets/objective-cpp-CLxacb5B.js +1 -0
- package/dist/ui/assets/ocaml-C0hk2d4L.js +1 -0
- package/dist/ui/assets/odin-BBf5iR-q.js +1 -0
- package/dist/ui/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- package/dist/ui/assets/one-light-C3Wv6jpd.js +1 -0
- package/dist/ui/assets/openscad-C4EeE6gA.js +1 -0
- package/dist/ui/assets/pascal-D93ZcfNL.js +1 -0
- package/dist/ui/assets/perl-NvoQZIq0.js +1 -0
- package/dist/ui/assets/php-R6g_5hLQ.js +1 -0
- package/dist/ui/assets/pkl-u5AG7uiY.js +1 -0
- package/dist/ui/assets/plastic-3e1v2bzS.js +1 -0
- package/dist/ui/assets/plsql-ChMvpjG-.js +1 -0
- package/dist/ui/assets/po-BTJTHyun.js +1 -0
- package/dist/ui/assets/poimandres-CS3Unz2-.js +1 -0
- package/dist/ui/assets/polar-C0HS_06l.js +1 -0
- package/dist/ui/assets/postcss-CXtECtnM.js +1 -0
- package/dist/ui/assets/powerquery-CEu0bR-o.js +1 -0
- package/dist/ui/assets/powershell-Dpen1YoG.js +1 -0
- package/dist/ui/assets/prisma-Dd19v3D-.js +1 -0
- package/dist/ui/assets/prolog-CbFg5uaA.js +1 -0
- package/dist/ui/assets/proto-C7zT0LnQ.js +1 -0
- package/dist/ui/assets/pug-DKIMFp6K.js +1 -0
- package/dist/ui/assets/puppet-BMWR74SV.js +1 -0
- package/dist/ui/assets/purescript-CklMAg4u.js +1 -0
- package/dist/ui/assets/python-B6aJPvgy.js +1 -0
- package/dist/ui/assets/qml-3beO22l8.js +1 -0
- package/dist/ui/assets/qmldir-C8lEn-DE.js +1 -0
- package/dist/ui/assets/qss-IeuSbFQv.js +1 -0
- package/dist/ui/assets/r-Dspwwk_N.js +1 -0
- package/dist/ui/assets/racket-BqYA7rlc.js +1 -0
- package/dist/ui/assets/raku-DXvB9xmW.js +1 -0
- package/dist/ui/assets/razor-BDqjjVU7.js +1 -0
- package/dist/ui/assets/red-bN70gL4F.js +1 -0
- package/dist/ui/assets/reg-C-SQnVFl.js +1 -0
- package/dist/ui/assets/regexp-CDVJQ6XC.js +1 -0
- package/dist/ui/assets/rel-C3B-1QV4.js +1 -0
- package/dist/ui/assets/riscv-BM1_JUlF.js +1 -0
- package/dist/ui/assets/ron-D8l8udqQ.js +1 -0
- package/dist/ui/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- package/dist/ui/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- package/dist/ui/assets/rose-pine-qdsjHGoJ.js +1 -0
- package/dist/ui/assets/rosmsg-BJDFO7_C.js +1 -0
- package/dist/ui/assets/rst-CRjBmOyv.js +1 -0
- package/dist/ui/assets/ruby-Wjq7vjNf.js +1 -0
- package/dist/ui/assets/rust-B1yitclQ.js +1 -0
- package/dist/ui/assets/sas-cz2c8ADy.js +1 -0
- package/dist/ui/assets/sass-Cj5Yp3dK.js +1 -0
- package/dist/ui/assets/scala-C151Ov-r.js +1 -0
- package/dist/ui/assets/scheme-C98Dy4si.js +1 -0
- package/dist/ui/assets/scss-D5BDwBP9.js +1 -0
- package/dist/ui/assets/sdbl-DVxCFoDh.js +1 -0
- package/dist/ui/assets/shaderlab-Dg9Lc6iA.js +1 -0
- package/dist/ui/assets/shellscript-Yzrsuije.js +1 -0
- package/dist/ui/assets/shellsession-BADoaaVG.js +1 -0
- package/dist/ui/assets/slack-dark-BthQWCQV.js +1 -0
- package/dist/ui/assets/slack-ochin-DqwNpetd.js +1 -0
- package/dist/ui/assets/smalltalk-BERRCDM3.js +1 -0
- package/dist/ui/assets/snazzy-light-Bw305WKR.js +1 -0
- package/dist/ui/assets/solarized-dark-DXbdFlpD.js +1 -0
- package/dist/ui/assets/solarized-light-L9t79GZl.js +1 -0
- package/dist/ui/assets/solidity-rGO070M0.js +1 -0
- package/dist/ui/assets/soy-8wufbnw4.js +1 -0
- package/dist/ui/assets/sparql-rVzFXLq3.js +1 -0
- package/dist/ui/assets/splunk-BtCnVYZw.js +1 -0
- package/dist/ui/assets/sql-BLtJtn59.js +1 -0
- package/dist/ui/assets/ssh-config-_ykCGR6B.js +1 -0
- package/dist/ui/assets/stata-BH5u7GGu.js +1 -0
- package/dist/ui/assets/stylus-BEDo0Tqx.js +1 -0
- package/dist/ui/assets/surrealql-Bq5Q-fJD.js +1 -0
- package/dist/ui/assets/svelte-Cy7k_4gC.js +1 -0
- package/dist/ui/assets/swift-D82vCrfD.js +1 -0
- package/dist/ui/assets/synthwave-84-CbfX1IO0.js +1 -0
- package/dist/ui/assets/system-verilog-CnnmHF94.js +1 -0
- package/dist/ui/assets/systemd-4A_iFExJ.js +1 -0
- package/dist/ui/assets/talonscript-CkByrt1z.js +1 -0
- package/dist/ui/assets/tasl-QIJgUcNo.js +1 -0
- package/dist/ui/assets/tcl-dwOrl1Do.js +1 -0
- package/dist/ui/assets/templ-DhtptRzy.js +1 -0
- package/dist/ui/assets/terraform-BETggiCN.js +1 -0
- package/dist/ui/assets/tex-idrVyKtj.js +1 -0
- package/dist/ui/assets/tokyo-night-hegEt444.js +1 -0
- package/dist/ui/assets/toml-vGWfd6FD.js +1 -0
- package/dist/ui/assets/ts-tags-DQrlYJgV.js +1 -0
- package/dist/ui/assets/tsv-B_m7g4N7.js +1 -0
- package/dist/ui/assets/tsx-COt5Ahok.js +1 -0
- package/dist/ui/assets/turtle-BsS91CYL.js +1 -0
- package/dist/ui/assets/twig-xg9kU7Mw.js +1 -0
- package/dist/ui/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/ui/assets/typespec-CAFt9gP4.js +1 -0
- package/dist/ui/assets/typst-DHCkPAjA.js +1 -0
- package/dist/ui/assets/v-BcVCzyr7.js +1 -0
- package/dist/ui/assets/vala-CsfeWuGM.js +1 -0
- package/dist/ui/assets/vb-D17OF-Vu.js +1 -0
- package/dist/ui/assets/verilog-BQ8w6xss.js +1 -0
- package/dist/ui/assets/vesper-DU1UobuO.js +1 -0
- package/dist/ui/assets/vhdl-CeAyd5Ju.js +1 -0
- package/dist/ui/assets/viml-CJc9bBzg.js +1 -0
- package/dist/ui/assets/vitesse-black-Bkuqu6BP.js +1 -0
- package/dist/ui/assets/vitesse-dark-D0r3Knsf.js +1 -0
- package/dist/ui/assets/vitesse-light-CVO1_9PV.js +1 -0
- package/dist/ui/assets/vue-D2xRrEX4.js +1 -0
- package/dist/ui/assets/vue-html-AaS7Mt5G.js +1 -0
- package/dist/ui/assets/vue-vine-BoDAl6tE.js +1 -0
- package/dist/ui/assets/vyper-CDx5xZoG.js +1 -0
- package/dist/ui/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/ui/assets/wasm-MzD3tlZU.js +1 -0
- package/dist/ui/assets/wenyan-BV7otONQ.js +1 -0
- package/dist/ui/assets/wgsl-Dx-B1_4e.js +1 -0
- package/dist/ui/assets/wikitext-BhOHFoWU.js +1 -0
- package/dist/ui/assets/wit-5i3qLPDT.js +1 -0
- package/dist/ui/assets/wolfram-lXgVvXCa.js +1 -0
- package/dist/ui/assets/xml-sdJ4AIDG.js +1 -0
- package/dist/ui/assets/xsl-CtQFsRM5.js +1 -0
- package/dist/ui/assets/yaml-Buea-lGh.js +1 -0
- package/dist/ui/assets/zenscript-DVFEvuxE.js +1 -0
- package/dist/ui/assets/zig-VOosw3JB.js +1 -0
- package/{packages → dist}/ui/index.html +2 -1
- package/package.json +39 -20
- package/.claude/settings.local.json +0 -11
- package/LICENSE +0 -21
- package/README.md +0 -71
- package/development.md +0 -156
- package/packages/cli/build.js +0 -38
- package/packages/cli/package.json +0 -51
- package/packages/cli/src/agent.ts +0 -187
- package/packages/cli/src/db.ts +0 -58
- package/packages/cli/src/index.ts +0 -196
- package/packages/cli/src/review-routes.ts +0 -150
- package/packages/cli/src/server.ts +0 -370
- package/packages/cli/src/session.ts +0 -48
- package/packages/cli/src/threads.ts +0 -238
- package/packages/cli/tsconfig.json +0 -13
- package/packages/git/package.json +0 -24
- package/packages/git/src/commits.ts +0 -28
- package/packages/git/src/diff.ts +0 -97
- package/packages/git/src/exec.ts +0 -35
- package/packages/git/src/index.ts +0 -5
- package/packages/git/src/repo.ts +0 -63
- package/packages/git/src/status.ts +0 -9
- package/packages/git/src/types.ts +0 -12
- package/packages/git/tsconfig.json +0 -9
- package/packages/parser/package.json +0 -26
- package/packages/parser/src/index.ts +0 -12
- package/packages/parser/src/parse.ts +0 -299
- package/packages/parser/src/types.ts +0 -52
- package/packages/parser/src/word-diff.ts +0 -155
- package/packages/parser/tests/fixtures/binary-deleted.diff +0 -4
- package/packages/parser/tests/fixtures/binary-file.diff +0 -4
- package/packages/parser/tests/fixtures/binary-modified.diff +0 -3
- package/packages/parser/tests/fixtures/copied-file.diff +0 -12
- package/packages/parser/tests/fixtures/deleted-file.diff +0 -9
- package/packages/parser/tests/fixtures/empty.diff +0 -0
- package/packages/parser/tests/fixtures/hunk-with-context.diff +0 -12
- package/packages/parser/tests/fixtures/mode-change-with-content.diff +0 -10
- package/packages/parser/tests/fixtures/mode-change.diff +0 -3
- package/packages/parser/tests/fixtures/multi-file.diff +0 -22
- package/packages/parser/tests/fixtures/new-file.diff +0 -9
- package/packages/parser/tests/fixtures/no-newline.diff +0 -10
- package/packages/parser/tests/fixtures/renamed-file.diff +0 -12
- package/packages/parser/tests/fixtures/single-file-additions.diff +0 -11
- package/packages/parser/tests/fixtures/single-file-deletions.diff +0 -11
- package/packages/parser/tests/fixtures/single-file-mixed.diff +0 -15
- package/packages/parser/tests/fixtures/single-file-multi-hunk.diff +0 -22
- package/packages/parser/tests/fixtures/spaces-in-path.diff +0 -9
- package/packages/parser/tests/fixtures/submodule.diff +0 -7
- package/packages/parser/tests/fixtures/unicode-content.diff +0 -11
- package/packages/parser/tests/parse.test.ts +0 -312
- package/packages/parser/tests/word-diff-integration.test.ts +0 -52
- package/packages/parser/tests/word-diff.test.ts +0 -121
- package/packages/parser/tsconfig.json +0 -10
- package/packages/skills/diffity-resolve/SKILL.md +0 -55
- package/packages/skills/diffity-review/SKILL.md +0 -74
- package/packages/skills/diffity-start/SKILL.md +0 -25
- package/packages/ui/package.json +0 -35
- package/packages/ui/src/app.tsx +0 -14
- package/packages/ui/src/components/comment-bubble.tsx +0 -78
- package/packages/ui/src/components/comment-form-row.tsx +0 -58
- package/packages/ui/src/components/comment-form.tsx +0 -78
- package/packages/ui/src/components/comment-line-number.tsx +0 -60
- package/packages/ui/src/components/comment-thread.tsx +0 -209
- package/packages/ui/src/components/commit-list.tsx +0 -100
- package/packages/ui/src/components/dashboard.tsx +0 -84
- package/packages/ui/src/components/diff-line.tsx +0 -90
- package/packages/ui/src/components/diff-page.tsx +0 -332
- package/packages/ui/src/components/diff-stats.tsx +0 -20
- package/packages/ui/src/components/diff-view.tsx +0 -278
- package/packages/ui/src/components/expand-row.tsx +0 -45
- package/packages/ui/src/components/file-block.tsx +0 -536
- package/packages/ui/src/components/file-tree-item.tsx +0 -84
- package/packages/ui/src/components/file-tree.tsx +0 -72
- package/packages/ui/src/components/general-comments.tsx +0 -174
- package/packages/ui/src/components/hunk-block-split.tsx +0 -357
- package/packages/ui/src/components/hunk-block.tsx +0 -161
- package/packages/ui/src/components/hunk-header.tsx +0 -144
- package/packages/ui/src/components/hunk-with-gap.tsx +0 -113
- package/packages/ui/src/components/icons/arrow-down-icon.tsx +0 -7
- package/packages/ui/src/components/icons/arrow-up-icon.tsx +0 -7
- package/packages/ui/src/components/icons/check-circle-icon.tsx +0 -8
- package/packages/ui/src/components/icons/check-icon.tsx +0 -9
- package/packages/ui/src/components/icons/chevron-down-icon.tsx +0 -11
- package/packages/ui/src/components/icons/chevron-icon.tsx +0 -20
- package/packages/ui/src/components/icons/chevron-up-down-icon.tsx +0 -7
- package/packages/ui/src/components/icons/chevron-up-icon.tsx +0 -11
- package/packages/ui/src/components/icons/comment-icon.tsx +0 -9
- package/packages/ui/src/components/icons/copy-icon.tsx +0 -10
- package/packages/ui/src/components/icons/eye-icon.tsx +0 -10
- package/packages/ui/src/components/icons/eye-off-icon.tsx +0 -12
- package/packages/ui/src/components/icons/file-icon.tsx +0 -7
- package/packages/ui/src/components/icons/folder-icon.tsx +0 -19
- package/packages/ui/src/components/icons/git-branch-icon.tsx +0 -13
- package/packages/ui/src/components/icons/keyboard-icon.tsx +0 -13
- package/packages/ui/src/components/icons/moon-icon.tsx +0 -9
- package/packages/ui/src/components/icons/plus-icon.tsx +0 -9
- package/packages/ui/src/components/icons/search-icon.tsx +0 -10
- package/packages/ui/src/components/icons/sidebar-icon.tsx +0 -10
- package/packages/ui/src/components/icons/spinner.tsx +0 -7
- package/packages/ui/src/components/icons/split-view-icon.tsx +0 -10
- package/packages/ui/src/components/icons/sun-icon.tsx +0 -17
- package/packages/ui/src/components/icons/trash-icon.tsx +0 -11
- package/packages/ui/src/components/icons/undo-icon.tsx +0 -9
- package/packages/ui/src/components/icons/unified-view-icon.tsx +0 -12
- package/packages/ui/src/components/icons/x-icon.tsx +0 -10
- package/packages/ui/src/components/line-number-cell.tsx +0 -18
- package/packages/ui/src/components/markdown-content.tsx +0 -139
- package/packages/ui/src/components/orphaned-threads.tsx +0 -80
- package/packages/ui/src/components/overview-file-list.tsx +0 -57
- package/packages/ui/src/components/render-expansion-rows.tsx +0 -47
- package/packages/ui/src/components/shortcut-modal.tsx +0 -93
- package/packages/ui/src/components/sidebar.tsx +0 -80
- package/packages/ui/src/components/skeleton.tsx +0 -9
- package/packages/ui/src/components/stale-diff-banner.tsx +0 -21
- package/packages/ui/src/components/summary-bar.tsx +0 -39
- package/packages/ui/src/components/toolbar.tsx +0 -246
- package/packages/ui/src/components/ui/badge.tsx +0 -17
- package/packages/ui/src/components/ui/confirm-dialog.tsx +0 -52
- package/packages/ui/src/components/ui/icon-button.tsx +0 -23
- package/packages/ui/src/components/ui/status-badge.tsx +0 -57
- package/packages/ui/src/components/ui/thread-badge.tsx +0 -35
- package/packages/ui/src/components/word-diff.tsx +0 -126
- package/packages/ui/src/hooks/use-comment-actions.ts +0 -97
- package/packages/ui/src/hooks/use-commits.ts +0 -12
- package/packages/ui/src/hooks/use-copy.ts +0 -18
- package/packages/ui/src/hooks/use-diff-staleness.ts +0 -58
- package/packages/ui/src/hooks/use-diff.ts +0 -12
- package/packages/ui/src/hooks/use-highlighter.ts +0 -190
- package/packages/ui/src/hooks/use-info.ts +0 -12
- package/packages/ui/src/hooks/use-keyboard.ts +0 -55
- package/packages/ui/src/hooks/use-line-selection.ts +0 -157
- package/packages/ui/src/hooks/use-overview.ts +0 -12
- package/packages/ui/src/hooks/use-review-threads.ts +0 -12
- package/packages/ui/src/hooks/use-search-params.ts +0 -26
- package/packages/ui/src/hooks/use-theme.ts +0 -34
- package/packages/ui/src/hooks/use-thread-navigation.ts +0 -43
- package/packages/ui/src/lib/api.ts +0 -232
- package/packages/ui/src/lib/cn.ts +0 -6
- package/packages/ui/src/lib/context-expansion.ts +0 -122
- package/packages/ui/src/lib/diff-utils.ts +0 -268
- package/packages/ui/src/lib/dom-utils.ts +0 -13
- package/packages/ui/src/lib/file-tree.ts +0 -122
- package/packages/ui/src/lib/query-client.ts +0 -10
- package/packages/ui/src/lib/render-content.tsx +0 -23
- package/packages/ui/src/lib/syntax-token.ts +0 -4
- package/packages/ui/src/main.tsx +0 -14
- package/packages/ui/src/queries/commits.ts +0 -9
- package/packages/ui/src/queries/diff.ts +0 -9
- package/packages/ui/src/queries/file.ts +0 -10
- package/packages/ui/src/queries/info.ts +0 -9
- package/packages/ui/src/queries/overview.ts +0 -9
- package/packages/ui/src/styles/app.css +0 -178
- package/packages/ui/src/types/comment.ts +0 -61
- package/packages/ui/src/vite-env.d.ts +0 -1
- package/packages/ui/tests/context-expansion.test.ts +0 -279
- package/packages/ui/tests/diff-utils.test.ts +0 -409
- package/packages/ui/tsconfig.json +0 -14
- package/packages/ui/vite.config.ts +0 -23
- package/scripts/build-skills.ts +0 -26
- package/scripts/build.ts +0 -15
- package/scripts/dev.ts +0 -32
- package/scripts/lib/transformers/claude-code.ts +0 -11
- package/scripts/lib/transformers/codex.ts +0 -17
- package/scripts/lib/transformers/cursor.ts +0 -17
- package/scripts/lib/transformers/index.ts +0 -3
- package/scripts/lib/utils.ts +0 -70
- package/scripts/link-dev.ts +0 -54
- package/skills/diffity-resolve/SKILL.md +0 -55
- package/skills/diffity-review/SKILL.md +0 -74
- package/skills/diffity-start/SKILL.md +0 -27
- package/tsconfig.json +0 -22
- /package/{packages/ui/public → dist/ui}/brand.svg +0 -0
- /package/{packages/ui/public → dist/ui}/favicon.svg +0 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1081 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { execSync as execSync3 } from "node:child_process";
|
|
6
|
+
import { rmSync, existsSync as existsSync2 } from "node:fs";
|
|
7
|
+
import { join as join5 } from "node:path";
|
|
8
|
+
import { homedir as homedir2 } from "node:os";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
import open from "open";
|
|
11
|
+
import pc2 from "picocolors";
|
|
12
|
+
|
|
13
|
+
// ../git/dist/repo.js
|
|
14
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { mkdirSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { homedir } from "node:os";
|
|
19
|
+
|
|
20
|
+
// ../git/dist/exec.js
|
|
21
|
+
import { execSync } from "node:child_process";
|
|
22
|
+
var STDIO = ["pipe", "pipe", "pipe"];
|
|
23
|
+
function execWithStdin(cmd, input) {
|
|
24
|
+
return execSync(cmd, {
|
|
25
|
+
encoding: "utf-8",
|
|
26
|
+
stdio: STDIO,
|
|
27
|
+
input,
|
|
28
|
+
maxBuffer: 50 * 1024 * 1024
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function exec(cmd) {
|
|
32
|
+
return execSync(cmd, {
|
|
33
|
+
encoding: "utf-8",
|
|
34
|
+
stdio: STDIO
|
|
35
|
+
}).trim();
|
|
36
|
+
}
|
|
37
|
+
function execLarge(cmd) {
|
|
38
|
+
return execSync(cmd, {
|
|
39
|
+
encoding: "utf-8",
|
|
40
|
+
stdio: STDIO,
|
|
41
|
+
maxBuffer: 50 * 1024 * 1024
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function execLines(cmd) {
|
|
45
|
+
let output = exec(cmd);
|
|
46
|
+
return output ? output.split(`
|
|
47
|
+
`) : [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ../git/dist/repo.js
|
|
51
|
+
function isGitRepo() {
|
|
52
|
+
try {
|
|
53
|
+
return execSync2("git rev-parse --is-inside-work-tree", { stdio: "pipe" }), !0;
|
|
54
|
+
} catch {
|
|
55
|
+
return !1;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function getRepoRoot() {
|
|
59
|
+
return exec("git rev-parse --show-toplevel");
|
|
60
|
+
}
|
|
61
|
+
function getRepoName() {
|
|
62
|
+
let root = getRepoRoot();
|
|
63
|
+
return root.split("/").pop() || root;
|
|
64
|
+
}
|
|
65
|
+
function getCurrentBranch() {
|
|
66
|
+
try {
|
|
67
|
+
return exec("git rev-parse --abbrev-ref HEAD");
|
|
68
|
+
} catch {
|
|
69
|
+
return "HEAD";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function getRepoInfo() {
|
|
73
|
+
return {
|
|
74
|
+
name: getRepoName(),
|
|
75
|
+
branch: getCurrentBranch(),
|
|
76
|
+
root: getRepoRoot()
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function getHeadHash() {
|
|
80
|
+
return exec("git rev-parse HEAD");
|
|
81
|
+
}
|
|
82
|
+
function getDiffityDirPath() {
|
|
83
|
+
let repoRoot = getRepoRoot(), hash = createHash("sha256").update(repoRoot).digest("hex").slice(0, 12);
|
|
84
|
+
return join(homedir(), ".diffity", hash);
|
|
85
|
+
}
|
|
86
|
+
function getDiffityDir() {
|
|
87
|
+
let dir = getDiffityDirPath();
|
|
88
|
+
return mkdirSync(dir, { recursive: !0 }), dir;
|
|
89
|
+
}
|
|
90
|
+
var ACTIONABLE_REFS = /* @__PURE__ */ new Set(["work", "staged", "unstaged", "working", "untracked"]);
|
|
91
|
+
function isActionableRef(ref) {
|
|
92
|
+
return !!ref && ACTIONABLE_REFS.has(ref);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ../git/dist/diff.js
|
|
96
|
+
function getDiff(args = []) {
|
|
97
|
+
let cmd = ["git", "diff", ...args].join(" ");
|
|
98
|
+
return execLarge(cmd);
|
|
99
|
+
}
|
|
100
|
+
function getUntrackedFiles() {
|
|
101
|
+
return execLines("git ls-files --others --exclude-standard");
|
|
102
|
+
}
|
|
103
|
+
function getUntrackedDiff(files) {
|
|
104
|
+
let diffs = [];
|
|
105
|
+
for (let file of files)
|
|
106
|
+
try {
|
|
107
|
+
execLarge(`git diff --no-index -- /dev/null "${file}"`);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
let error = err;
|
|
110
|
+
error.status === 1 && error.stdout && diffs.push(error.stdout);
|
|
111
|
+
}
|
|
112
|
+
return diffs.join(`
|
|
113
|
+
`);
|
|
114
|
+
}
|
|
115
|
+
function resolveRef(ref, extraArgs = []) {
|
|
116
|
+
switch (ref) {
|
|
117
|
+
case "staged":
|
|
118
|
+
return getDiff(["--staged", ...extraArgs]);
|
|
119
|
+
case "unstaged":
|
|
120
|
+
return getDiff(extraArgs);
|
|
121
|
+
case "working":
|
|
122
|
+
return getDiff(["HEAD", ...extraArgs]);
|
|
123
|
+
case "untracked": {
|
|
124
|
+
let files = getUntrackedFiles();
|
|
125
|
+
return files.length === 0 ? "" : getUntrackedDiff(files);
|
|
126
|
+
}
|
|
127
|
+
case "work": {
|
|
128
|
+
let raw = getDiff(["HEAD", ...extraArgs]), untrackedFiles = getUntrackedFiles();
|
|
129
|
+
return untrackedFiles.length > 0 && (raw += `
|
|
130
|
+
` + getUntrackedDiff(untrackedFiles)), raw;
|
|
131
|
+
}
|
|
132
|
+
default:
|
|
133
|
+
return getDiff([ref, ...extraArgs]);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function revertFile(filePath, isUntracked) {
|
|
137
|
+
isUntracked ? exec(`rm "${filePath}"`) : exec(`git checkout HEAD -- "${filePath}"`);
|
|
138
|
+
}
|
|
139
|
+
function revertHunk(patch) {
|
|
140
|
+
execWithStdin("git apply --reverse --unidiff-zero", patch);
|
|
141
|
+
}
|
|
142
|
+
function getDiffStat(args = []) {
|
|
143
|
+
let cmd = ["git", "diff", "--stat", ...args].join(" ");
|
|
144
|
+
try {
|
|
145
|
+
return execLarge(cmd);
|
|
146
|
+
} catch {
|
|
147
|
+
return "";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function getMergeBase(a, b) {
|
|
151
|
+
return exec(`git merge-base ${a} ${b}`);
|
|
152
|
+
}
|
|
153
|
+
function getFileContent(path, ref = "HEAD") {
|
|
154
|
+
return exec(`git show ${ref}:${path}`);
|
|
155
|
+
}
|
|
156
|
+
function getFileLineCount(path, ref = "HEAD") {
|
|
157
|
+
try {
|
|
158
|
+
return exec(`git show ${ref}:${path}`).split(`
|
|
159
|
+
`).length;
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ../git/dist/status.js
|
|
166
|
+
function getStagedFiles() {
|
|
167
|
+
return execLines("git diff --staged --name-only");
|
|
168
|
+
}
|
|
169
|
+
function getUnstagedFiles() {
|
|
170
|
+
return execLines("git diff --name-only");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ../git/dist/commits.js
|
|
174
|
+
function getRecentCommits(query) {
|
|
175
|
+
let { count, skip = 0, search } = query, args = [`-n ${count}`, `--skip=${skip}`, '--format="%H|%h|%s|%cr"'];
|
|
176
|
+
search && args.push(`--grep=${search}`, "-i");
|
|
177
|
+
let output = exec(`git log ${args.join(" ")}`);
|
|
178
|
+
return output ? output.split(`
|
|
179
|
+
`).map((line) => {
|
|
180
|
+
let [hash, shortHash, message, relativeDate] = line.split("|");
|
|
181
|
+
return { hash, shortHash, message, relativeDate };
|
|
182
|
+
}) : [];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/server.ts
|
|
186
|
+
import { createServer } from "node:http";
|
|
187
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
188
|
+
import { readFileSync as readFileSync2, existsSync } from "node:fs";
|
|
189
|
+
import { join as join4, extname } from "node:path";
|
|
190
|
+
import { fileURLToPath } from "node:url";
|
|
191
|
+
import { dirname } from "node:path";
|
|
192
|
+
|
|
193
|
+
// ../parser/dist/word-diff.js
|
|
194
|
+
function tokenize(text) {
|
|
195
|
+
let tokens = [], current = "";
|
|
196
|
+
for (let i = 0; i < text.length; i++) {
|
|
197
|
+
let char = text[i], isWordChar = /\w/.test(char);
|
|
198
|
+
if (current.length === 0) {
|
|
199
|
+
current = char;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
let prevIsWordChar = /\w/.test(current[current.length - 1]);
|
|
203
|
+
isWordChar === prevIsWordChar ? current += char : (tokens.push(current), current = char);
|
|
204
|
+
}
|
|
205
|
+
return current.length > 0 && tokens.push(current), tokens;
|
|
206
|
+
}
|
|
207
|
+
function lcs(a, b) {
|
|
208
|
+
let m = a.length, n = b.length, dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
209
|
+
for (let i2 = 1; i2 <= m; i2++)
|
|
210
|
+
for (let j2 = 1; j2 <= n; j2++)
|
|
211
|
+
a[i2 - 1] === b[j2 - 1] ? dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1 : dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
|
|
212
|
+
let result = [], i = m, j = n;
|
|
213
|
+
for (; i > 0 && j > 0; )
|
|
214
|
+
a[i - 1] === b[j - 1] ? (result.unshift([a[i - 1], "equal"]), i--, j--) : dp[i - 1][j] > dp[i][j - 1] ? i-- : j--;
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
function computeWordDiff(oldLine, newLine) {
|
|
218
|
+
if (oldLine === newLine)
|
|
219
|
+
return [{ text: oldLine, type: "equal" }];
|
|
220
|
+
if (oldLine.length === 0)
|
|
221
|
+
return [{ text: newLine, type: "insert" }];
|
|
222
|
+
if (newLine.length === 0)
|
|
223
|
+
return [{ text: oldLine, type: "delete" }];
|
|
224
|
+
let oldTokens = tokenize(oldLine), newTokens = tokenize(newLine), common = lcs(oldTokens, newTokens), segments = [], oi = 0, ni = 0, ci = 0;
|
|
225
|
+
for (; ci < common.length; ) {
|
|
226
|
+
let commonToken = common[ci][0], deleteText = "";
|
|
227
|
+
for (; oi < oldTokens.length && oldTokens[oi] !== commonToken; )
|
|
228
|
+
deleteText += oldTokens[oi], oi++;
|
|
229
|
+
let insertText = "";
|
|
230
|
+
for (; ni < newTokens.length && newTokens[ni] !== commonToken; )
|
|
231
|
+
insertText += newTokens[ni], ni++;
|
|
232
|
+
deleteText && segments.push({ text: deleteText, type: "delete" }), insertText && segments.push({ text: insertText, type: "insert" });
|
|
233
|
+
let equalText = "";
|
|
234
|
+
for (; oi < oldTokens.length && ni < newTokens.length && ci < common.length && oldTokens[oi] === common[ci][0] && newTokens[ni] === common[ci][0]; )
|
|
235
|
+
equalText += oldTokens[oi], oi++, ni++, ci++;
|
|
236
|
+
equalText && segments.push({ text: equalText, type: "equal" });
|
|
237
|
+
}
|
|
238
|
+
let trailingDelete = "";
|
|
239
|
+
for (; oi < oldTokens.length; )
|
|
240
|
+
trailingDelete += oldTokens[oi], oi++;
|
|
241
|
+
let trailingInsert = "";
|
|
242
|
+
for (; ni < newTokens.length; )
|
|
243
|
+
trailingInsert += newTokens[ni], ni++;
|
|
244
|
+
return trailingDelete && segments.push({ text: trailingDelete, type: "delete" }), trailingInsert && segments.push({ text: trailingInsert, type: "insert" }), segments;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ../parser/dist/parse.js
|
|
248
|
+
var DIFF_HEADER_RE = /^diff --git a\/(.*) b\/(.*)$/, HUNK_HEADER_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/, OLD_FILE_RE = /^--- (.+)$/, NEW_FILE_RE = /^\+\+\+ (.+)$/, SIMILARITY_RE = /^similarity index (\d+)%$/, RENAME_FROM_RE = /^rename from (.+)$/, RENAME_TO_RE = /^rename to (.+)$/, COPY_FROM_RE = /^copy from (.+)$/, COPY_TO_RE = /^copy to (.+)$/, OLD_MODE_RE = /^old mode (\d+)$/, NEW_MODE_RE = /^new mode (\d+)$/, NEW_FILE_MODE_RE = /^new file mode (\d+)$/, DELETED_FILE_MODE_RE = /^deleted file mode (\d+)$/, BINARY_RE = /^Binary files (.+) and (.+) differ$/, NO_NEWLINE_RE = /^\$/;
|
|
249
|
+
function stripPrefix(path) {
|
|
250
|
+
return path === "/dev/null" ? path : path.startsWith("a/") || path.startsWith("b/") ? path.slice(2) : path;
|
|
251
|
+
}
|
|
252
|
+
function attachWordDiffs(hunk) {
|
|
253
|
+
let lines = hunk.lines, i = 0;
|
|
254
|
+
for (; i < lines.length; )
|
|
255
|
+
if (lines[i].type === "delete") {
|
|
256
|
+
let deleteStart = i;
|
|
257
|
+
for (; i < lines.length && lines[i].type === "delete"; )
|
|
258
|
+
i++;
|
|
259
|
+
let deleteEnd = i, addStart = i;
|
|
260
|
+
for (; i < lines.length && lines[i].type === "add"; )
|
|
261
|
+
i++;
|
|
262
|
+
let addEnd = i, deleteCount = deleteEnd - deleteStart, addCount = addEnd - addStart;
|
|
263
|
+
if (deleteCount > 0 && addCount > 0) {
|
|
264
|
+
let pairCount = Math.min(deleteCount, addCount);
|
|
265
|
+
for (let p = 0; p < pairCount; p++) {
|
|
266
|
+
let delLine = lines[deleteStart + p], addLine = lines[addStart + p], segments = computeWordDiff(delLine.content, addLine.content);
|
|
267
|
+
delLine.wordDiff = segments, addLine.wordDiff = segments;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} else
|
|
271
|
+
i++;
|
|
272
|
+
}
|
|
273
|
+
function parseDiff(raw) {
|
|
274
|
+
let lines = raw.split(`
|
|
275
|
+
`), files = [], currentFile = null, currentHunk = null, oldLineNum = 0, newLineNum = 0, i = 0;
|
|
276
|
+
for (; i < lines.length; ) {
|
|
277
|
+
let line = lines[i], diffMatch = line.match(DIFF_HEADER_RE);
|
|
278
|
+
if (diffMatch) {
|
|
279
|
+
currentFile && files.push(currentFile), currentFile = {
|
|
280
|
+
oldPath: diffMatch[1],
|
|
281
|
+
newPath: diffMatch[2],
|
|
282
|
+
status: "modified",
|
|
283
|
+
hunks: [],
|
|
284
|
+
additions: 0,
|
|
285
|
+
deletions: 0,
|
|
286
|
+
isBinary: !1
|
|
287
|
+
}, currentHunk = null, i++;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (!currentFile) {
|
|
291
|
+
i++;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
let oldModeMatch = line.match(OLD_MODE_RE);
|
|
295
|
+
if (oldModeMatch) {
|
|
296
|
+
currentFile.oldMode = oldModeMatch[1], i++;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
let newModeMatch = line.match(NEW_MODE_RE);
|
|
300
|
+
if (newModeMatch) {
|
|
301
|
+
currentFile.newMode = newModeMatch[1], i++;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
let newFileModeMatch = line.match(NEW_FILE_MODE_RE);
|
|
305
|
+
if (newFileModeMatch) {
|
|
306
|
+
currentFile.newMode = newFileModeMatch[1], currentFile.status = "added", i++;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
let deletedFileModeMatch = line.match(DELETED_FILE_MODE_RE);
|
|
310
|
+
if (deletedFileModeMatch) {
|
|
311
|
+
currentFile.oldMode = deletedFileModeMatch[1], currentFile.status = "deleted", i++;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
let similarityMatch = line.match(SIMILARITY_RE);
|
|
315
|
+
if (similarityMatch) {
|
|
316
|
+
currentFile.similarityIndex = parseInt(similarityMatch[1], 10), i++;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
let renameFromMatch = line.match(RENAME_FROM_RE);
|
|
320
|
+
if (renameFromMatch) {
|
|
321
|
+
currentFile.oldPath = renameFromMatch[1], currentFile.status = "renamed", i++;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
let renameToMatch = line.match(RENAME_TO_RE);
|
|
325
|
+
if (renameToMatch) {
|
|
326
|
+
currentFile.newPath = renameToMatch[1], currentFile.status = "renamed", i++;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
let copyFromMatch = line.match(COPY_FROM_RE);
|
|
330
|
+
if (copyFromMatch) {
|
|
331
|
+
currentFile.oldPath = copyFromMatch[1], currentFile.status = "copied", i++;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
let copyToMatch = line.match(COPY_TO_RE);
|
|
335
|
+
if (copyToMatch) {
|
|
336
|
+
currentFile.newPath = copyToMatch[1], currentFile.status = "copied", i++;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
let oldFileMatch = line.match(OLD_FILE_RE);
|
|
340
|
+
if (oldFileMatch) {
|
|
341
|
+
let path = stripPrefix(oldFileMatch[1]);
|
|
342
|
+
currentFile.oldPath = path, path === "/dev/null" && (currentFile.status = "added"), i++;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
let newFileMatch = line.match(NEW_FILE_RE);
|
|
346
|
+
if (newFileMatch) {
|
|
347
|
+
let path = stripPrefix(newFileMatch[1]);
|
|
348
|
+
currentFile.newPath = path, path === "/dev/null" && (currentFile.status = "deleted"), i++;
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (line.match(BINARY_RE)) {
|
|
352
|
+
currentFile.isBinary = !0, i++;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
let hunkMatch = line.match(HUNK_HEADER_RE);
|
|
356
|
+
if (hunkMatch) {
|
|
357
|
+
currentHunk = {
|
|
358
|
+
header: line,
|
|
359
|
+
oldStart: parseInt(hunkMatch[1], 10),
|
|
360
|
+
oldCount: hunkMatch[2] !== void 0 ? parseInt(hunkMatch[2], 10) : 1,
|
|
361
|
+
newStart: parseInt(hunkMatch[3], 10),
|
|
362
|
+
newCount: hunkMatch[4] !== void 0 ? parseInt(hunkMatch[4], 10) : 1,
|
|
363
|
+
context: hunkMatch[5]?.trim() || void 0,
|
|
364
|
+
lines: []
|
|
365
|
+
}, currentFile.hunks.push(currentHunk), oldLineNum = currentHunk.oldStart, newLineNum = currentHunk.newStart, i++;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (NO_NEWLINE_RE.test(line)) {
|
|
369
|
+
currentHunk && currentHunk.lines.length > 0 && (currentHunk.lines[currentHunk.lines.length - 1].noNewline = !0), i++;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (currentHunk) {
|
|
373
|
+
let prefix = line[0], content = line.slice(1);
|
|
374
|
+
if (prefix === "+") {
|
|
375
|
+
let diffLine = {
|
|
376
|
+
type: "add",
|
|
377
|
+
content,
|
|
378
|
+
oldLineNumber: null,
|
|
379
|
+
newLineNumber: newLineNum
|
|
380
|
+
};
|
|
381
|
+
currentHunk.lines.push(diffLine), currentFile.additions++, newLineNum++;
|
|
382
|
+
} else if (prefix === "-") {
|
|
383
|
+
let diffLine = {
|
|
384
|
+
type: "delete",
|
|
385
|
+
content,
|
|
386
|
+
oldLineNumber: oldLineNum,
|
|
387
|
+
newLineNumber: null
|
|
388
|
+
};
|
|
389
|
+
currentHunk.lines.push(diffLine), currentFile.deletions++, oldLineNum++;
|
|
390
|
+
} else if (prefix === " ") {
|
|
391
|
+
let diffLine = {
|
|
392
|
+
type: "context",
|
|
393
|
+
content: content || "",
|
|
394
|
+
oldLineNumber: oldLineNum,
|
|
395
|
+
newLineNumber: newLineNum
|
|
396
|
+
};
|
|
397
|
+
currentHunk.lines.push(diffLine), oldLineNum++, newLineNum++;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
i++;
|
|
401
|
+
}
|
|
402
|
+
currentFile && files.push(currentFile);
|
|
403
|
+
for (let file of files)
|
|
404
|
+
for (let hunk of file.hunks)
|
|
405
|
+
attachWordDiffs(hunk);
|
|
406
|
+
let totalAdditions = 0, totalDeletions = 0;
|
|
407
|
+
for (let file of files)
|
|
408
|
+
totalAdditions += file.additions, totalDeletions += file.deletions;
|
|
409
|
+
return {
|
|
410
|
+
files,
|
|
411
|
+
stats: {
|
|
412
|
+
totalAdditions,
|
|
413
|
+
totalDeletions,
|
|
414
|
+
filesChanged: files.length
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/session.ts
|
|
420
|
+
import { randomUUID } from "node:crypto";
|
|
421
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
422
|
+
import { join as join3 } from "node:path";
|
|
423
|
+
|
|
424
|
+
// src/db.ts
|
|
425
|
+
import Database from "better-sqlite3";
|
|
426
|
+
import { join as join2 } from "node:path";
|
|
427
|
+
var db = null;
|
|
428
|
+
function getDb() {
|
|
429
|
+
if (db)
|
|
430
|
+
return db;
|
|
431
|
+
let dbPath = join2(getDiffityDir(), "reviews.db");
|
|
432
|
+
return db = new Database(dbPath), db.pragma("journal_mode = WAL"), db.pragma("foreign_keys = ON"), migrateDb(db), db;
|
|
433
|
+
}
|
|
434
|
+
function migrateDb(db2) {
|
|
435
|
+
db2.exec(`
|
|
436
|
+
CREATE TABLE IF NOT EXISTS review_sessions (
|
|
437
|
+
id TEXT PRIMARY KEY,
|
|
438
|
+
ref TEXT NOT NULL,
|
|
439
|
+
head_hash TEXT NOT NULL,
|
|
440
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
CREATE TABLE IF NOT EXISTS comment_threads (
|
|
444
|
+
id TEXT PRIMARY KEY,
|
|
445
|
+
session_id TEXT NOT NULL REFERENCES review_sessions(id),
|
|
446
|
+
file_path TEXT NOT NULL,
|
|
447
|
+
side TEXT NOT NULL,
|
|
448
|
+
start_line INTEGER NOT NULL,
|
|
449
|
+
end_line INTEGER NOT NULL,
|
|
450
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
451
|
+
anchor_content TEXT,
|
|
452
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
453
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
CREATE TABLE IF NOT EXISTS comments (
|
|
457
|
+
id TEXT PRIMARY KEY,
|
|
458
|
+
thread_id TEXT NOT NULL REFERENCES comment_threads(id) ON DELETE CASCADE,
|
|
459
|
+
author_name TEXT NOT NULL,
|
|
460
|
+
author_type TEXT NOT NULL,
|
|
461
|
+
body TEXT NOT NULL,
|
|
462
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
463
|
+
);
|
|
464
|
+
`);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/session.ts
|
|
468
|
+
function sessionFilePath() {
|
|
469
|
+
return join3(getDiffityDir(), "current-session");
|
|
470
|
+
}
|
|
471
|
+
function findOrCreateSession(ref) {
|
|
472
|
+
let db2 = getDb(), headHash = getHeadHash(), existing = db2.prepare(
|
|
473
|
+
"SELECT id, ref, head_hash FROM review_sessions WHERE ref = ? AND head_hash = ?"
|
|
474
|
+
).get(ref, headHash);
|
|
475
|
+
if (existing) {
|
|
476
|
+
let session2 = { id: existing.id, ref: existing.ref, headHash: existing.head_hash };
|
|
477
|
+
return writeFileSync(sessionFilePath(), JSON.stringify(session2)), session2;
|
|
478
|
+
}
|
|
479
|
+
let id = randomUUID();
|
|
480
|
+
db2.prepare(
|
|
481
|
+
"INSERT INTO review_sessions (id, ref, head_hash) VALUES (?, ?, ?)"
|
|
482
|
+
).run(id, ref, headHash);
|
|
483
|
+
let session = { id, ref, headHash };
|
|
484
|
+
return writeFileSync(sessionFilePath(), JSON.stringify(session)), session;
|
|
485
|
+
}
|
|
486
|
+
function getCurrentSession() {
|
|
487
|
+
try {
|
|
488
|
+
let raw = readFileSync(sessionFilePath(), "utf-8");
|
|
489
|
+
return JSON.parse(raw);
|
|
490
|
+
} catch {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/threads.ts
|
|
496
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
497
|
+
function rowToThread(row, comments) {
|
|
498
|
+
return {
|
|
499
|
+
id: row.id,
|
|
500
|
+
sessionId: row.session_id,
|
|
501
|
+
filePath: row.file_path,
|
|
502
|
+
side: row.side,
|
|
503
|
+
startLine: row.start_line,
|
|
504
|
+
endLine: row.end_line,
|
|
505
|
+
status: row.status,
|
|
506
|
+
anchorContent: row.anchor_content,
|
|
507
|
+
createdAt: row.created_at,
|
|
508
|
+
updatedAt: row.updated_at,
|
|
509
|
+
comments
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
function rowToComment(row) {
|
|
513
|
+
return {
|
|
514
|
+
id: row.id,
|
|
515
|
+
author: { name: row.author_name, type: row.author_type },
|
|
516
|
+
body: row.body,
|
|
517
|
+
createdAt: row.created_at
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function getCommentsForThreads(threadIds) {
|
|
521
|
+
if (threadIds.length === 0)
|
|
522
|
+
return /* @__PURE__ */ new Map();
|
|
523
|
+
let db2 = getDb(), placeholders = threadIds.map(() => "?").join(", "), rows = db2.prepare(
|
|
524
|
+
`SELECT * FROM comments WHERE thread_id IN (${placeholders}) ORDER BY created_at ASC`
|
|
525
|
+
).all(...threadIds), map = /* @__PURE__ */ new Map();
|
|
526
|
+
for (let row of rows) {
|
|
527
|
+
let comments = map.get(row.thread_id) ?? [];
|
|
528
|
+
comments.push(rowToComment(row)), map.set(row.thread_id, comments);
|
|
529
|
+
}
|
|
530
|
+
return map;
|
|
531
|
+
}
|
|
532
|
+
function getCommentsForThread(threadId) {
|
|
533
|
+
return getCommentsForThreads([threadId]).get(threadId) ?? [];
|
|
534
|
+
}
|
|
535
|
+
function createThread(sessionId, filePath, side, startLine, endLine, body, author, anchorContent) {
|
|
536
|
+
let db2 = getDb(), threadId = randomUUID2(), commentId = randomUUID2(), now = (/* @__PURE__ */ new Date()).toISOString();
|
|
537
|
+
return db2.prepare(
|
|
538
|
+
"INSERT INTO comment_threads (id, session_id, file_path, side, start_line, end_line, anchor_content, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
539
|
+
).run(threadId, sessionId, filePath, side, startLine, endLine, anchorContent ?? null, now, now), db2.prepare(
|
|
540
|
+
"INSERT INTO comments (id, thread_id, author_name, author_type, body, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
541
|
+
).run(commentId, threadId, author.name, author.type, body, now), {
|
|
542
|
+
id: threadId,
|
|
543
|
+
sessionId,
|
|
544
|
+
filePath,
|
|
545
|
+
side,
|
|
546
|
+
startLine,
|
|
547
|
+
endLine,
|
|
548
|
+
status: "open",
|
|
549
|
+
anchorContent: anchorContent ?? null,
|
|
550
|
+
createdAt: now,
|
|
551
|
+
updatedAt: now,
|
|
552
|
+
comments: [{
|
|
553
|
+
id: commentId,
|
|
554
|
+
author,
|
|
555
|
+
body,
|
|
556
|
+
createdAt: now
|
|
557
|
+
}]
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
function getThreadsForSession(sessionId, status) {
|
|
561
|
+
let db2 = getDb(), rows;
|
|
562
|
+
status ? rows = db2.prepare(
|
|
563
|
+
"SELECT * FROM comment_threads WHERE session_id = ? AND status = ? ORDER BY created_at ASC"
|
|
564
|
+
).all(sessionId, status) : rows = db2.prepare(
|
|
565
|
+
"SELECT * FROM comment_threads WHERE session_id = ? ORDER BY created_at ASC"
|
|
566
|
+
).all(sessionId);
|
|
567
|
+
let commentsByThread = getCommentsForThreads(rows.map((r) => r.id));
|
|
568
|
+
return rows.map((row) => rowToThread(row, commentsByThread.get(row.id) ?? []));
|
|
569
|
+
}
|
|
570
|
+
function getThread(idOrPrefix) {
|
|
571
|
+
let db2 = getDb(), row = db2.prepare("SELECT * FROM comment_threads WHERE id = ?").get(idOrPrefix);
|
|
572
|
+
return !row && idOrPrefix.length >= 8 && (row = db2.prepare("SELECT * FROM comment_threads WHERE id LIKE ?").get(idOrPrefix + "%")), row ? rowToThread(row, getCommentsForThread(row.id)) : null;
|
|
573
|
+
}
|
|
574
|
+
function addReply(threadId, body, author) {
|
|
575
|
+
let db2 = getDb(), commentId = randomUUID2(), now = (/* @__PURE__ */ new Date()).toISOString();
|
|
576
|
+
return db2.prepare(
|
|
577
|
+
"INSERT INTO comments (id, thread_id, author_name, author_type, body, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
578
|
+
).run(commentId, threadId, author.name, author.type, body, now), db2.prepare(
|
|
579
|
+
"UPDATE comment_threads SET updated_at = ? WHERE id = ?"
|
|
580
|
+
).run(now, threadId), {
|
|
581
|
+
id: commentId,
|
|
582
|
+
author,
|
|
583
|
+
body,
|
|
584
|
+
createdAt: now
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
function updateThreadStatus(threadId, status, summaryBody, summaryAuthor) {
|
|
588
|
+
let db2 = getDb(), now = (/* @__PURE__ */ new Date()).toISOString();
|
|
589
|
+
if (db2.prepare(
|
|
590
|
+
"UPDATE comment_threads SET status = ?, updated_at = ? WHERE id = ?"
|
|
591
|
+
).run(status, now, threadId), summaryBody && summaryAuthor) {
|
|
592
|
+
let commentId = randomUUID2();
|
|
593
|
+
db2.prepare(
|
|
594
|
+
"INSERT INTO comments (id, thread_id, author_name, author_type, body, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
595
|
+
).run(commentId, threadId, summaryAuthor.name, summaryAuthor.type, summaryBody, now);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function deleteThread(threadId) {
|
|
599
|
+
getDb().prepare("DELETE FROM comment_threads WHERE id = ?").run(threadId);
|
|
600
|
+
}
|
|
601
|
+
function deleteAllThreadsForSession(sessionId) {
|
|
602
|
+
getDb().prepare("DELETE FROM comment_threads WHERE session_id = ?").run(sessionId);
|
|
603
|
+
}
|
|
604
|
+
function deleteComment(commentId) {
|
|
605
|
+
let db2 = getDb(), comment = db2.prepare("SELECT thread_id FROM comments WHERE id = ?").get(commentId);
|
|
606
|
+
if (!comment)
|
|
607
|
+
return;
|
|
608
|
+
db2.prepare("DELETE FROM comments WHERE id = ?").run(commentId), db2.prepare("SELECT COUNT(*) as count FROM comments WHERE thread_id = ?").get(comment.thread_id).count === 0 && db2.prepare("DELETE FROM comment_threads WHERE id = ?").run(comment.thread_id);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/review-routes.ts
|
|
612
|
+
function sendJson(res, data) {
|
|
613
|
+
res.writeHead(200, { "Content-Type": "application/json" }), res.end(JSON.stringify(data));
|
|
614
|
+
}
|
|
615
|
+
function sendError(res, status, message) {
|
|
616
|
+
res.writeHead(status, { "Content-Type": "application/json" }), res.end(JSON.stringify({ error: message }));
|
|
617
|
+
}
|
|
618
|
+
function readBody(req) {
|
|
619
|
+
return new Promise((resolve, reject) => {
|
|
620
|
+
let chunks = [];
|
|
621
|
+
req.on("data", (chunk) => chunks.push(chunk)), req.on("end", () => resolve(Buffer.concat(chunks).toString())), req.on("error", reject);
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
function handleReviewRoute(req, res, pathname, url) {
|
|
625
|
+
if (pathname === "/api/sessions/current" && req.method === "GET") {
|
|
626
|
+
let session = getCurrentSession();
|
|
627
|
+
return sendJson(res, session), !0;
|
|
628
|
+
}
|
|
629
|
+
if (pathname === "/api/threads" && req.method === "GET") {
|
|
630
|
+
let sid = url.searchParams.get("session");
|
|
631
|
+
if (!sid)
|
|
632
|
+
return sendError(res, 400, "Missing session parameter"), !0;
|
|
633
|
+
let status = url.searchParams.get("status"), threads = getThreadsForSession(sid, status || void 0);
|
|
634
|
+
return sendJson(res, threads), !0;
|
|
635
|
+
}
|
|
636
|
+
if (pathname === "/api/threads" && req.method === "DELETE")
|
|
637
|
+
return readBody(req).then((raw) => {
|
|
638
|
+
try {
|
|
639
|
+
let body = JSON.parse(raw), { sessionId: sid } = body;
|
|
640
|
+
if (!sid) {
|
|
641
|
+
sendError(res, 400, "Missing sessionId");
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
deleteAllThreadsForSession(sid), sendJson(res, { ok: !0 });
|
|
645
|
+
} catch (err) {
|
|
646
|
+
sendError(res, 500, `Failed to delete all threads: ${err}`);
|
|
647
|
+
}
|
|
648
|
+
}), !0;
|
|
649
|
+
if (pathname === "/api/threads" && req.method === "POST")
|
|
650
|
+
return readBody(req).then((raw) => {
|
|
651
|
+
try {
|
|
652
|
+
let body = JSON.parse(raw), { sessionId: sid, filePath, side, startLine, endLine, body: commentBody, author, anchorContent } = body;
|
|
653
|
+
if (!sid || !filePath || !side || typeof startLine != "number" || typeof endLine != "number" || !commentBody || !author) {
|
|
654
|
+
sendError(res, 400, "Missing required fields");
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
let thread = createThread(sid, filePath, side, startLine, endLine, commentBody, author, anchorContent);
|
|
658
|
+
sendJson(res, thread);
|
|
659
|
+
} catch (err) {
|
|
660
|
+
sendError(res, 500, `Failed to create thread: ${err}`);
|
|
661
|
+
}
|
|
662
|
+
}), !0;
|
|
663
|
+
let threadReplyMatch = pathname.match(/^\/api\/threads\/([^/]+)\/reply$/);
|
|
664
|
+
if (threadReplyMatch && req.method === "POST")
|
|
665
|
+
return readBody(req).then((raw) => {
|
|
666
|
+
try {
|
|
667
|
+
let body = JSON.parse(raw), { body: commentBody, author } = body;
|
|
668
|
+
if (!commentBody || !author) {
|
|
669
|
+
sendError(res, 400, "Missing body or author");
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
let comment = addReply(threadReplyMatch[1], commentBody, author);
|
|
673
|
+
sendJson(res, comment);
|
|
674
|
+
} catch (err) {
|
|
675
|
+
sendError(res, 500, `Failed to add reply: ${err}`);
|
|
676
|
+
}
|
|
677
|
+
}), !0;
|
|
678
|
+
let threadStatusMatch = pathname.match(/^\/api\/threads\/([^/]+)\/status$/);
|
|
679
|
+
if (threadStatusMatch && req.method === "PATCH")
|
|
680
|
+
return readBody(req).then((raw) => {
|
|
681
|
+
try {
|
|
682
|
+
let body = JSON.parse(raw), { status, summary } = body;
|
|
683
|
+
if (!status) {
|
|
684
|
+
sendError(res, 400, "Missing status");
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
let summaryAuthor = summary ? { name: "System", type: "user" } : void 0;
|
|
688
|
+
updateThreadStatus(threadStatusMatch[1], status, summary, summaryAuthor), sendJson(res, { ok: !0 });
|
|
689
|
+
} catch (err) {
|
|
690
|
+
sendError(res, 500, `Failed to update thread status: ${err}`);
|
|
691
|
+
}
|
|
692
|
+
}), !0;
|
|
693
|
+
let threadDeleteMatch = pathname.match(/^\/api\/threads\/([^/]+)$/);
|
|
694
|
+
if (threadDeleteMatch && req.method === "DELETE") {
|
|
695
|
+
try {
|
|
696
|
+
deleteThread(threadDeleteMatch[1]), sendJson(res, { ok: !0 });
|
|
697
|
+
} catch (err) {
|
|
698
|
+
sendError(res, 500, `Failed to delete thread: ${err}`);
|
|
699
|
+
}
|
|
700
|
+
return !0;
|
|
701
|
+
}
|
|
702
|
+
let commentDeleteMatch = pathname.match(/^\/api\/comments\/([^/]+)$/);
|
|
703
|
+
if (commentDeleteMatch && req.method === "DELETE") {
|
|
704
|
+
try {
|
|
705
|
+
deleteComment(commentDeleteMatch[1]), sendJson(res, { ok: !0 });
|
|
706
|
+
} catch (err) {
|
|
707
|
+
sendError(res, 500, `Failed to delete comment: ${err}`);
|
|
708
|
+
}
|
|
709
|
+
return !0;
|
|
710
|
+
}
|
|
711
|
+
return !1;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// src/server.ts
|
|
715
|
+
var __dirname = dirname(fileURLToPath(import.meta.url)), MIME_TYPES = {
|
|
716
|
+
".html": "text/html",
|
|
717
|
+
".js": "application/javascript",
|
|
718
|
+
".css": "text/css",
|
|
719
|
+
".json": "application/json",
|
|
720
|
+
".svg": "image/svg+xml",
|
|
721
|
+
".png": "image/png",
|
|
722
|
+
".ico": "image/x-icon"
|
|
723
|
+
};
|
|
724
|
+
function sendJson2(res, data) {
|
|
725
|
+
res.writeHead(200, { "Content-Type": "application/json" }), res.end(JSON.stringify(data));
|
|
726
|
+
}
|
|
727
|
+
function sendError2(res, status, message) {
|
|
728
|
+
res.writeHead(status, { "Content-Type": "application/json" }), res.end(JSON.stringify({ error: message }));
|
|
729
|
+
}
|
|
730
|
+
function serveStatic(res, filePath) {
|
|
731
|
+
if (!existsSync(filePath)) {
|
|
732
|
+
sendError2(res, 404, "Not found");
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
let ext = extname(filePath), mime = MIME_TYPES[ext] || "application/octet-stream", content = readFileSync2(filePath);
|
|
736
|
+
res.writeHead(200, { "Content-Type": mime }), res.end(content);
|
|
737
|
+
}
|
|
738
|
+
function descriptionForRef(ref) {
|
|
739
|
+
switch (ref) {
|
|
740
|
+
case "staged":
|
|
741
|
+
return "Staged changes";
|
|
742
|
+
case "unstaged":
|
|
743
|
+
return "Unstaged changes";
|
|
744
|
+
case "working":
|
|
745
|
+
return "Working tree changes";
|
|
746
|
+
case "untracked":
|
|
747
|
+
return "Untracked files";
|
|
748
|
+
case "work":
|
|
749
|
+
return "All changes";
|
|
750
|
+
default:
|
|
751
|
+
return ref.includes("..") ? ref : `Changes from ${ref}`;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
function resolveBaseRef(ref) {
|
|
755
|
+
if (["staged", "working", "work"].includes(ref) || ref === "unstaged" || ref === "untracked")
|
|
756
|
+
return "HEAD";
|
|
757
|
+
let threeDotsIdx = ref.indexOf("...");
|
|
758
|
+
if (threeDotsIdx !== -1) {
|
|
759
|
+
let left = ref.slice(0, threeDotsIdx), right = ref.slice(threeDotsIdx + 3);
|
|
760
|
+
return getMergeBase(left, right);
|
|
761
|
+
}
|
|
762
|
+
let twoDotsIdx = ref.indexOf("..");
|
|
763
|
+
return twoDotsIdx !== -1 ? ref.slice(0, twoDotsIdx) : ref;
|
|
764
|
+
}
|
|
765
|
+
function readBody2(req) {
|
|
766
|
+
return new Promise((resolve, reject) => {
|
|
767
|
+
let chunks = [];
|
|
768
|
+
req.on("data", (chunk) => chunks.push(chunk)), req.on("end", () => resolve(Buffer.concat(chunks).toString())), req.on("error", reject);
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
function startServer(options) {
|
|
772
|
+
let { port, diffArgs, description, effectiveRef } = options, sessionId = null, reviewsEnabled = isActionableRef(effectiveRef);
|
|
773
|
+
reviewsEnabled && effectiveRef && (sessionId = findOrCreateSession(effectiveRef).id);
|
|
774
|
+
let includeUntracked = diffArgs.length === 0;
|
|
775
|
+
function enrichWithLineCounts(diff, baseRef) {
|
|
776
|
+
for (let file of diff.files) {
|
|
777
|
+
if (file.status === "added" || file.isBinary)
|
|
778
|
+
continue;
|
|
779
|
+
let path = file.oldPath || file.newPath, count = getFileLineCount(path, baseRef);
|
|
780
|
+
count !== null && (file.oldFileLineCount = count);
|
|
781
|
+
}
|
|
782
|
+
return diff;
|
|
783
|
+
}
|
|
784
|
+
function getFullDiff(args) {
|
|
785
|
+
let raw = getDiff(args);
|
|
786
|
+
if (includeUntracked) {
|
|
787
|
+
let untrackedFiles = getUntrackedFiles();
|
|
788
|
+
untrackedFiles.length > 0 && (raw += `
|
|
789
|
+
` + getUntrackedDiff(untrackedFiles));
|
|
790
|
+
}
|
|
791
|
+
return raw;
|
|
792
|
+
}
|
|
793
|
+
let uiDir = join4(__dirname, "ui"), server = createServer(async (req, res) => {
|
|
794
|
+
let url = new URL(req.url || "/", `http://localhost:${port}`), pathname = url.pathname;
|
|
795
|
+
if (res.setHeader("Access-Control-Allow-Origin", "*"), res.setHeader("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS"), res.setHeader("Access-Control-Allow-Headers", "Content-Type"), req.method === "OPTIONS") {
|
|
796
|
+
res.writeHead(204), res.end();
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (pathname === "/api/revert-file" && req.method === "POST") {
|
|
800
|
+
try {
|
|
801
|
+
let body = JSON.parse(await readBody2(req)), { filePath: path, isUntracked } = body;
|
|
802
|
+
if (!path || typeof path != "string") {
|
|
803
|
+
sendError2(res, 400, "Missing filePath");
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
revertFile(path, !!isUntracked), sendJson2(res, { ok: !0 });
|
|
807
|
+
} catch (err) {
|
|
808
|
+
sendError2(res, 500, `Failed to revert file: ${err}`);
|
|
809
|
+
}
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
if (pathname === "/api/revert-hunk" && req.method === "POST") {
|
|
813
|
+
try {
|
|
814
|
+
let body = JSON.parse(await readBody2(req)), { patch } = body;
|
|
815
|
+
if (!patch || typeof patch != "string") {
|
|
816
|
+
sendError2(res, 400, "Missing patch");
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
revertHunk(patch), sendJson2(res, { ok: !0 });
|
|
820
|
+
} catch (err) {
|
|
821
|
+
sendError2(res, 500, `Failed to revert hunk: ${err}`);
|
|
822
|
+
}
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (pathname === "/api/overview") {
|
|
826
|
+
try {
|
|
827
|
+
let staged = getStagedFiles(), unstaged = getUnstagedFiles(), untracked = getUntrackedFiles(), fileMap = /* @__PURE__ */ new Map();
|
|
828
|
+
for (let f of staged)
|
|
829
|
+
fileMap.set(f, "staged");
|
|
830
|
+
for (let f of unstaged)
|
|
831
|
+
fileMap.set(f, "modified");
|
|
832
|
+
for (let f of untracked)
|
|
833
|
+
fileMap.set(f, "added");
|
|
834
|
+
let files = Array.from(fileMap.entries()).map(([path, status]) => ({ path, status }));
|
|
835
|
+
sendJson2(res, { files });
|
|
836
|
+
} catch (err) {
|
|
837
|
+
sendError2(res, 500, `Failed to get overview: ${err}`);
|
|
838
|
+
}
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
if (pathname === "/api/commits") {
|
|
842
|
+
let count = parseInt(url.searchParams.get("count") || "10", 10), skip = parseInt(url.searchParams.get("skip") || "0", 10), search = url.searchParams.get("search") || void 0;
|
|
843
|
+
try {
|
|
844
|
+
let commits = getRecentCommits({ count, skip, search });
|
|
845
|
+
sendJson2(res, { commits, hasMore: commits.length === count });
|
|
846
|
+
} catch (err) {
|
|
847
|
+
sendError2(res, 500, `Failed to get commits: ${err}`);
|
|
848
|
+
}
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
if (pathname === "/api/diff-fingerprint") {
|
|
852
|
+
let ref = url.searchParams.get("ref"), stat;
|
|
853
|
+
if (ref)
|
|
854
|
+
switch (ref) {
|
|
855
|
+
case "work":
|
|
856
|
+
case "working":
|
|
857
|
+
stat = getDiffStat(["HEAD"]) + `
|
|
858
|
+
` + getUntrackedFiles().join(`
|
|
859
|
+
`);
|
|
860
|
+
break;
|
|
861
|
+
case "staged":
|
|
862
|
+
stat = getDiffStat(["--staged"]);
|
|
863
|
+
break;
|
|
864
|
+
case "unstaged":
|
|
865
|
+
stat = getDiffStat([]);
|
|
866
|
+
break;
|
|
867
|
+
case "untracked":
|
|
868
|
+
stat = getUntrackedFiles().join(`
|
|
869
|
+
`);
|
|
870
|
+
break;
|
|
871
|
+
default:
|
|
872
|
+
stat = getDiffStat([ref]);
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
else
|
|
876
|
+
stat = getDiffStat(diffArgs), includeUntracked && (stat += `
|
|
877
|
+
` + getUntrackedFiles().join(`
|
|
878
|
+
`));
|
|
879
|
+
let hash = createHash2("sha1").update(stat).digest("hex").slice(0, 12);
|
|
880
|
+
sendJson2(res, { fingerprint: hash });
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
if (pathname === "/api/diff") {
|
|
884
|
+
let ref = url.searchParams.get("ref"), whitespace = url.searchParams.get("whitespace"), extraArgs = whitespace === "hide" ? ["-w"] : [], baseRef = ref ? resolveBaseRef(ref) : "HEAD";
|
|
885
|
+
if (ref) {
|
|
886
|
+
sendJson2(res, enrichWithLineCounts(parseDiff(resolveRef(ref, extraArgs)), baseRef));
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (whitespace === "hide") {
|
|
890
|
+
sendJson2(res, enrichWithLineCounts(parseDiff(getFullDiff([...diffArgs, "-w"])), baseRef));
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
sendJson2(res, enrichWithLineCounts(parseDiff(getFullDiff(diffArgs)), baseRef));
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
if (pathname.startsWith("/api/file/")) {
|
|
897
|
+
let filePath2 = decodeURIComponent(pathname.slice(10)), ref = url.searchParams.get("ref") || void 0, baseRef = ref ? resolveBaseRef(ref) : "HEAD";
|
|
898
|
+
try {
|
|
899
|
+
let content = getFileContent(filePath2, baseRef);
|
|
900
|
+
sendJson2(res, { path: filePath2, content: content.split(`
|
|
901
|
+
`) });
|
|
902
|
+
} catch {
|
|
903
|
+
sendError2(res, 404, `File not found: ${filePath2}`);
|
|
904
|
+
}
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
if (pathname === "/api/info") {
|
|
908
|
+
let ref = url.searchParams.get("ref"), info = getRepoInfo(), refDescription = description || diffArgs.join(" ") || "Unstaged changes";
|
|
909
|
+
ref && (refDescription = descriptionForRef(ref)), sendJson2(res, {
|
|
910
|
+
...info,
|
|
911
|
+
description: refDescription,
|
|
912
|
+
capabilities: { reviews: reviewsEnabled },
|
|
913
|
+
sessionId
|
|
914
|
+
});
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
if (handleReviewRoute(req, res, pathname, url))
|
|
918
|
+
return;
|
|
919
|
+
let filePath = join4(uiDir, pathname === "/" ? "index.html" : pathname);
|
|
920
|
+
existsSync(filePath) || (filePath = join4(uiDir, "index.html")), serveStatic(res, filePath);
|
|
921
|
+
}), closeFn = () => server.close();
|
|
922
|
+
return new Promise((resolve, reject) => {
|
|
923
|
+
let retries = 0, maxRetries = 30, onError = (err) => {
|
|
924
|
+
err.code === "EADDRINUSE" && retries < maxRetries ? (retries++, server.close(), setTimeout(() => server.listen(port), 500)) : reject(err);
|
|
925
|
+
};
|
|
926
|
+
server.on("error", onError), server.on("listening", () => {
|
|
927
|
+
let addr = server.address();
|
|
928
|
+
addr && typeof addr != "string" && resolve({ port: addr.port, close: closeFn });
|
|
929
|
+
}), server.listen(port);
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// src/agent.ts
|
|
934
|
+
import pc from "picocolors";
|
|
935
|
+
function requireSession() {
|
|
936
|
+
isGitRepo() || (console.error(pc.red("Error: Not a git repository")), process.exit(1));
|
|
937
|
+
let session = getCurrentSession();
|
|
938
|
+
return session || (console.error(pc.red("Error: No active review session.")), console.error(pc.dim("Start diffity first to create a session.")), process.exit(1)), session;
|
|
939
|
+
}
|
|
940
|
+
function resolveThreadId(shortId, sessionId) {
|
|
941
|
+
let thread = getThread(shortId);
|
|
942
|
+
return thread || (console.error(pc.red(`Error: Thread not found: ${shortId}`)), process.exit(1)), thread.sessionId !== sessionId && (console.error(pc.red(`Error: Thread ${shortId} does not belong to current session`)), process.exit(1)), thread;
|
|
943
|
+
}
|
|
944
|
+
function formatThreadLine(thread) {
|
|
945
|
+
let shortId = thread.id.slice(0, 8), isGeneral = thread.filePath === "__general__", statusLabel = (thread.status === "open" ? pc.yellow : thread.status === "resolved" ? pc.green : thread.status === "dismissed" ? pc.dim : pc.cyan)(`[${thread.status}]`), firstComment = thread.comments[0]?.body || "", truncated = firstComment.length > 80 ? firstComment.slice(0, 77) + "..." : firstComment;
|
|
946
|
+
if (isGeneral)
|
|
947
|
+
return `${statusLabel.padEnd(22)} ${pc.dim(shortId)} ${pc.bold("General comment")}
|
|
948
|
+
${"".padEnd(15)}${pc.dim('"')}${truncated}${pc.dim('"')}`;
|
|
949
|
+
let lineRange = thread.startLine === thread.endLine ? `${thread.startLine}` : `${thread.startLine}-${thread.endLine}`, location = `${thread.filePath}:${lineRange}`, sideLabel = thread.side === "old" ? "(old)" : "(new)";
|
|
950
|
+
return `${statusLabel.padEnd(22)} ${pc.dim(shortId)} ${location} ${pc.dim(sideLabel)}
|
|
951
|
+
${"".padEnd(15)}${pc.dim('"')}${truncated}${pc.dim('"')}`;
|
|
952
|
+
}
|
|
953
|
+
function registerAgentCommands(program2) {
|
|
954
|
+
let agent = program2.command("agent").description("Agent commands for interacting with review comments").addHelpText("after", `
|
|
955
|
+
Examples:
|
|
956
|
+
$ diffity agent list --status open --json
|
|
957
|
+
$ diffity agent comment --file src/app.ts --line 42 --body "Missing null check"
|
|
958
|
+
$ diffity agent resolve abc123 --summary "Added null check"
|
|
959
|
+
$ diffity agent reply abc123 --body "Good catch, fixed"
|
|
960
|
+
$ diffity agent general-comment --body "Overall this looks good, just a few nits"`);
|
|
961
|
+
agent.command("list").description("List comment threads in the current session (use --json for full details)").option("--status <status>", "Filter by status (open, resolved, dismissed)").option("--json", "Output as JSON").action((opts) => {
|
|
962
|
+
let validStatuses = ["open", "resolved", "dismissed"];
|
|
963
|
+
opts.status && !validStatuses.includes(opts.status) && (console.error(pc.red(`Error: Invalid status "${opts.status}". Must be one of: ${validStatuses.join(", ")}`)), process.exit(1));
|
|
964
|
+
let session = requireSession(), threads = getThreadsForSession(session.id, opts.status);
|
|
965
|
+
if (opts.json) {
|
|
966
|
+
console.log(JSON.stringify(threads, null, 2));
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
if (threads.length === 0) {
|
|
970
|
+
console.log(pc.dim("No threads found."));
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
for (let thread of threads)
|
|
974
|
+
console.log(formatThreadLine(thread));
|
|
975
|
+
}), agent.command("comment").description("Create a new comment thread").requiredOption("--file <path>", "File path (relative to repo root)").requiredOption("--line <n>", "Line number (1-indexed)", parseInt).option("--end-line <n>", "End line for multi-line comments (1-indexed)", parseInt).option("--side <side>", "Which side of the diff (new or old)", "new").requiredOption("--body <text>", "Comment body").action((opts) => {
|
|
976
|
+
opts.side !== "new" && opts.side !== "old" && (console.error(pc.red(`Error: Invalid side "${opts.side}". Must be "new" or "old"`)), process.exit(1));
|
|
977
|
+
let session = requireSession(), endLine = opts.endLine ?? opts.line, thread = createThread(
|
|
978
|
+
session.id,
|
|
979
|
+
opts.file,
|
|
980
|
+
opts.side,
|
|
981
|
+
opts.line,
|
|
982
|
+
endLine,
|
|
983
|
+
opts.body,
|
|
984
|
+
{ name: "Agent", type: "agent" }
|
|
985
|
+
);
|
|
986
|
+
console.log(pc.green(`Created thread ${thread.id.slice(0, 8)}`));
|
|
987
|
+
}), agent.command("resolve").description("Resolve a thread (marks as fixed)").argument("<thread-id>", "Thread ID (or 8-char prefix)").option("--summary <text>", "What was done to resolve it").action((id, opts) => {
|
|
988
|
+
let session = requireSession(), thread = resolveThreadId(id, session.id), author = opts.summary ? { name: "Agent", type: "agent" } : void 0;
|
|
989
|
+
updateThreadStatus(thread.id, "resolved", opts.summary, author), console.log(pc.green(`Resolved thread ${thread.id.slice(0, 8)}`));
|
|
990
|
+
}), agent.command("dismiss").description("Dismiss a thread (marks as won't fix)").argument("<thread-id>", "Thread ID (or 8-char prefix)").option("--reason <text>", "Why the thread is being dismissed").action((id, opts) => {
|
|
991
|
+
let session = requireSession(), thread = resolveThreadId(id, session.id), author = opts.reason ? { name: "Agent", type: "agent" } : void 0;
|
|
992
|
+
updateThreadStatus(thread.id, "dismissed", opts.reason, author), console.log(pc.green(`Dismissed thread ${thread.id.slice(0, 8)}`));
|
|
993
|
+
}), agent.command("reply").description("Reply to a comment thread").argument("<thread-id>", "Thread ID (or 8-char prefix)").requiredOption("--body <text>", "Reply body").action((id, opts) => {
|
|
994
|
+
let session = requireSession(), thread = resolveThreadId(id, session.id);
|
|
995
|
+
addReply(thread.id, opts.body, { name: "Agent", type: "agent" }), console.log(pc.green(`Replied to thread ${thread.id.slice(0, 8)}`));
|
|
996
|
+
}), agent.command("general-comment").description("Create a general comment on the entire diff (not tied to a specific file or line)").requiredOption("--body <text>", "Comment body").action((opts) => {
|
|
997
|
+
let session = requireSession(), thread = createThread(
|
|
998
|
+
session.id,
|
|
999
|
+
"__general__",
|
|
1000
|
+
"new",
|
|
1001
|
+
0,
|
|
1002
|
+
0,
|
|
1003
|
+
opts.body,
|
|
1004
|
+
{ name: "Agent", type: "agent" }
|
|
1005
|
+
);
|
|
1006
|
+
console.log(pc.green(`Created general comment ${thread.id.slice(0, 8)}`));
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// src/index.ts
|
|
1011
|
+
var require2 = createRequire(import.meta.url), pkg = require2("../package.json"), program = new Command();
|
|
1012
|
+
program.name("diffity").description("GitHub-style git diff viewer in the browser").version(pkg.version).argument("[refs...]", "Git refs to diff (e.g. HEAD~3, main, main..feature)").option("--port <port>", "Port to use", "5391").option("--no-open", "Do not open browser automatically").option("--quiet", "Minimal terminal output").option("--dark", "Open in dark mode (default: light)").option("--unified", "Open in unified view (default: split)").addHelpText("after", `
|
|
1013
|
+
Examples:
|
|
1014
|
+
$ diffity Working tree changes
|
|
1015
|
+
$ diffity HEAD~1 Last commit vs working tree
|
|
1016
|
+
$ diffity HEAD~3 Last 3 commits vs working tree
|
|
1017
|
+
$ diffity abc1234 Changes since a specific commit
|
|
1018
|
+
$ diffity main..feature Compare branches
|
|
1019
|
+
$ diffity main feature Same as main..feature
|
|
1020
|
+
$ diffity v1.0.0..v2.0.0 Compare tags`).action(async (refs, opts) => {
|
|
1021
|
+
isGitRepo() || (console.error(pc2.red("Error: Not a git repository")), process.exit(1));
|
|
1022
|
+
let diffArgs = [], description = "";
|
|
1023
|
+
if (refs.length === 1) {
|
|
1024
|
+
let ref = refs[0];
|
|
1025
|
+
ref.includes("..") ? (diffArgs.push(ref), description = ref) : (diffArgs.push(ref), description = `Changes from ${ref}`);
|
|
1026
|
+
} else refs.length === 2 ? (diffArgs.push(`${refs[0]}..${refs[1]}`), description = `${refs[0]}..${refs[1]}`) : description = "Unstaged changes";
|
|
1027
|
+
let port = parseInt(opts.port, 10), effectiveRef;
|
|
1028
|
+
refs.length > 0 ? effectiveRef = refs.length === 2 ? `${refs[0]}..${refs[1]}` : refs[0] : effectiveRef = "work";
|
|
1029
|
+
try {
|
|
1030
|
+
let { port: actualPort, close } = await startServer({ port, diffArgs, description, effectiveRef }), urlParams = new URLSearchParams({ ref: effectiveRef });
|
|
1031
|
+
opts.dark && urlParams.set("theme", "dark"), opts.unified && urlParams.set("view", "unified");
|
|
1032
|
+
let url = `http://localhost:${actualPort}/?${urlParams.toString()}`;
|
|
1033
|
+
opts.quiet || (console.log(""), console.log(pc2.bold(" diffity")), console.log(` ${pc2.dim(description)}`), console.log(""), console.log(` ${pc2.green("\u2192")} ${pc2.cyan(url)}`), console.log(` ${pc2.dim("Press Ctrl+C to stop")}`), console.log("")), process.on("SIGINT", () => {
|
|
1034
|
+
opts.quiet || console.log(pc2.dim(`
|
|
1035
|
+
Shutting down...`)), close(), process.exit(0);
|
|
1036
|
+
}), process.on("SIGTERM", () => {
|
|
1037
|
+
close(), process.exit(0);
|
|
1038
|
+
}), opts.open !== !1 && await open(url);
|
|
1039
|
+
} catch (err) {
|
|
1040
|
+
console.error(pc2.red(`Failed to start server: ${err}`)), process.exit(1);
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
program.command("prune").description("Remove all diffity data (database, sessions) for all repos").action(() => {
|
|
1044
|
+
let dir = join5(homedir2(), ".diffity");
|
|
1045
|
+
if (!existsSync2(dir)) {
|
|
1046
|
+
console.log(pc2.dim("Nothing to prune."));
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
rmSync(dir, { recursive: !0, force: !0 }), console.log(pc2.green("Pruned all diffity data (~/.diffity)."));
|
|
1050
|
+
});
|
|
1051
|
+
program.command("update").description("Update diffity to the latest version").action(() => {
|
|
1052
|
+
try {
|
|
1053
|
+
let registry = execSync3("npm view diffity version", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
1054
|
+
if (registry === pkg.version) {
|
|
1055
|
+
console.log(pc2.green(`Already on the latest version (${pkg.version}).`));
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
console.log(`${pc2.dim(`Current: ${pkg.version}`)} \u2192 ${pc2.bold(registry)}`), console.log(pc2.dim("Updating...")), execSync3("npm install -g diffity@latest", { stdio: "inherit" }), console.log(pc2.green(`Updated to ${registry}.`));
|
|
1059
|
+
} catch {
|
|
1060
|
+
console.error(pc2.red("Failed to update. Try running: npm install -g diffity@latest")), process.exit(1);
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
program.command("doctor").description("Check that diffity can run correctly").action(() => {
|
|
1064
|
+
let ok = !0;
|
|
1065
|
+
process.stdout.write(" git ");
|
|
1066
|
+
try {
|
|
1067
|
+
let gitVersion = execSync3("git --version", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
1068
|
+
console.log(pc2.green(`\u2713 ${gitVersion}`));
|
|
1069
|
+
} catch {
|
|
1070
|
+
console.log(pc2.red("\u2717 git not found")), ok = !1;
|
|
1071
|
+
}
|
|
1072
|
+
process.stdout.write(" git repo "), isGitRepo() ? console.log(pc2.green("\u2713 inside a git repository")) : console.log(pc2.yellow("- not inside a git repository")), process.stdout.write(" node "), console.log(pc2.green(`\u2713 ${process.version}`)), process.stdout.write(" sqlite ");
|
|
1073
|
+
try {
|
|
1074
|
+
require2("better-sqlite3"), console.log(pc2.green("\u2713 better-sqlite3 loaded"));
|
|
1075
|
+
} catch {
|
|
1076
|
+
console.log(pc2.red("\u2717 better-sqlite3 failed to load (native module issue)")), ok = !1;
|
|
1077
|
+
}
|
|
1078
|
+
process.stdout.write(" version "), console.log(pc2.green(`\u2713 diffity ${pkg.version}`)), console.log(""), ok ? console.log(pc2.green(" All checks passed.")) : (console.log(pc2.red(" Some checks failed. Fix the issues above and try again.")), process.exit(1));
|
|
1079
|
+
});
|
|
1080
|
+
registerAgentCommands(program);
|
|
1081
|
+
program.parse();
|