farming-code 2.2.29 → 2.2.32
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 +25 -66
- package/README.zh_cn.md +15 -66
- package/THIRD_PARTY_NOTICES.md +7 -0
- package/backend/acp/{client-services.js → client-services.cjs} +132 -134
- package/backend/acp/packaged-claude-acp.cjs +37 -0
- package/backend/acp/packaged-codex-acp.cjs +37 -0
- package/backend/acp/patch-decisions.cjs +182 -0
- package/backend/acp/permission-security.cjs +131 -0
- package/backend/{acp-checkpoint-store.js → acp-checkpoint-store.cjs} +90 -59
- package/backend/{acp-runtime.js → acp-runtime.cjs} +1219 -1125
- package/backend/acp-session-state.cjs +622 -0
- package/backend/acp-transcript.cjs +525 -0
- package/backend/agent-env.cjs +257 -0
- package/backend/agent-extension-discovery.cjs +452 -0
- package/backend/agent-extension-inventory.cjs +116 -0
- package/backend/agent-json-stream.cjs +283 -0
- package/backend/agent-lifecycle-journal.cjs +185 -0
- package/backend/{agent-manager.js → agent-manager.cjs} +3116 -4275
- package/backend/agent-order-transaction.cjs +85 -0
- package/backend/agent-order.cjs +159 -0
- package/backend/agent-provider-session.cjs +98 -0
- package/backend/agent-runtime-binding.cjs +179 -0
- package/backend/agent-session-history.cjs +1022 -0
- package/backend/agent-session-inventory.cjs +291 -0
- package/backend/async-cache.cjs +104 -0
- package/backend/atomic-json-store.cjs +67 -0
- package/backend/auth.cjs +261 -0
- package/backend/authoritative-inventory-cache.cjs +383 -0
- package/backend/business-health.cjs +68 -0
- package/backend/chat-runtime.cjs +50 -0
- package/backend/claude-settings.cjs +132 -0
- package/backend/cli-agents.cjs +435 -0
- package/backend/codex-context-window.cjs +196 -0
- package/backend/codex-models.cjs +228 -0
- package/backend/codex-session-archive.cjs +82 -0
- package/backend/codex-session-history.cjs +590 -0
- package/backend/{codex-terminal-profile.js → codex-terminal-profile.cjs} +149 -155
- package/backend/codex-transcript-sanitizer.cjs +130 -0
- package/backend/codex-transcript.cjs +2107 -0
- package/backend/command-runner-child.cjs +105 -0
- package/backend/config-manager.cjs +1263 -0
- package/backend/control-api.cjs +439 -0
- package/backend/data/LICENSE.cua +21 -0
- package/backend/executable-discovery.cjs +204 -0
- package/backend/farming-agent-bootstrap.cjs +81 -0
- package/backend/farming-agent-bootstrap.zh_cn.md +6 -2
- package/backend/{farming-app-cli.js → farming-app-cli.cjs} +441 -493
- package/backend/farming-cli.cjs +414 -0
- package/backend/farming-net-pass.cjs +319 -0
- package/backend/farming-net-registry.cjs +123 -0
- package/backend/farming-net-server.cjs +309 -0
- package/backend/farming-session-store.cjs +651 -0
- package/backend/git-worktree-info.cjs +181 -0
- package/backend/haiku-token.cjs +828 -0
- package/backend/index-html.cjs +96 -0
- package/backend/input-parts.cjs +68 -0
- package/backend/input-routing.cjs +40 -0
- package/backend/{local-session-engine.js → local-session-engine.cjs} +276 -282
- package/backend/main-agent-skills.cjs +309 -0
- package/backend/main-page-session.cjs +132 -0
- package/backend/native-pty-controller-generation.cjs +139 -0
- package/backend/{native-pty-host-client.js → native-pty-host-client.cjs} +338 -346
- package/backend/native-pty-host-identity.cjs +125 -0
- package/backend/native-pty-host-path.cjs +68 -0
- package/backend/{native-pty-host.js → native-pty-host.cjs} +480 -569
- package/backend/native-session-engine.cjs +296 -0
- package/backend/network.cjs +57 -0
- package/backend/npm-update-helper.cjs +399 -0
- package/backend/packaged-node-pty.cjs +120 -0
- package/backend/preview-session-manager.cjs +89 -0
- package/backend/provider-adapters.cjs +492 -0
- package/backend/provider-home-configuration.cjs +211 -0
- package/backend/provider-session-id.cjs +79 -0
- package/backend/provider-session-service.cjs +333 -0
- package/backend/qr-share-tickets.cjs +119 -0
- package/backend/resource-broadcast-protocol.cjs +50 -0
- package/backend/review-diff-router.cjs +202 -0
- package/backend/review-diff-service.cjs +1267 -0
- package/backend/review-session-router.cjs +93 -0
- package/backend/review-session-service.cjs +470 -0
- package/backend/review-session-store.cjs +161 -0
- package/backend/review-state-router.cjs +132 -0
- package/backend/review-state-store.cjs +299 -0
- package/backend/run-history-store.cjs +97 -0
- package/backend/runtime-dependency-manager.cjs +893 -0
- package/backend/runtime-executable-invocation.cjs +37 -0
- package/backend/runtime-observation.cjs +96 -0
- package/backend/server-process-identity.cjs +70 -0
- package/backend/server.cjs +3356 -0
- package/backend/session-engine-bridge.cjs +144 -0
- package/backend/session-engine-router.cjs +105 -0
- package/backend/session-engine.cjs +64 -0
- package/backend/session-stream-protocol.cjs +181 -0
- package/backend/shell-busy-integration.cjs +442 -0
- package/backend/slash-command-discovery.cjs +423 -0
- package/backend/storage-layout.cjs +263 -0
- package/backend/system-monitor.cjs +96 -0
- package/backend/terminal-attach-checkpoint.cjs +73 -0
- package/backend/terminal-exit-quiescence.cjs +57 -0
- package/backend/{terminal-reducer-flow-control.js → terminal-reducer-flow-control.cjs} +45 -22
- package/backend/terminal-runtime-cleanup.cjs +99 -0
- package/backend/{terminal-screen-state.js → terminal-screen-state.cjs} +100 -108
- package/backend/{terminal-screen-worker-pool.js → terminal-screen-worker-pool.cjs} +89 -79
- package/backend/{terminal-screen-worker-thread.js → terminal-screen-worker-thread.cjs} +64 -65
- package/backend/terminal-screen-worker.cjs +352 -0
- package/backend/terminal-state-serialization.cjs +147 -0
- package/backend/{terminal-status.js → terminal-status.cjs} +137 -187
- package/backend/theme-manager.cjs +185 -0
- package/backend/update-service.cjs +675 -0
- package/backend/usage-forecast.cjs +95 -0
- package/backend/{usage-history-client.js → usage-history-client.cjs} +144 -94
- package/backend/usage-history-smoke.cjs +85 -0
- package/backend/usage-history-worker.cjs +60 -0
- package/backend/{usage-monitor.js → usage-monitor.cjs} +363 -390
- package/backend/workspace-directory.cjs +239 -0
- package/backend/workspace-discovery.cjs +271 -0
- package/backend/{workspace-file-router.js → workspace-file-router.cjs} +213 -282
- package/backend/{workspace-file-service.js → workspace-file-service.cjs} +1139 -1318
- package/backend/workspace-root-registry.cjs +192 -0
- package/bin/farming +1 -1
- package/config/farming.deploy.env.example +1 -1
- package/config/farming.install.env.example +1 -1
- package/dist/acp/codex-acp-1.1.4.mjs +405 -8
- package/dist/assets/App-BCkdpWB6.css +1 -0
- package/dist/assets/App-CRjKpaGp.js +697 -0
- package/dist/assets/CodeSelect-Ci8sKZPU.js +1 -0
- package/dist/assets/CodeSelect-T2eNzrOc.css +1 -0
- package/dist/assets/{FileEditorMarkdownPreview-Cs9cLv8a.js → FileEditorMarkdownPreview-V06QHlAg.js} +55 -55
- package/dist/assets/FileEditorPane-BX-Wjg9U.js +2 -0
- package/dist/assets/IconGlyphs-YhV7tqd4.js +1 -0
- package/dist/assets/ProjectFilesSection-BDT2_r0o.js +12 -0
- package/dist/assets/ReviewPage-BqAs5sG1.js +3 -0
- package/dist/assets/code-dark-B7eM_9G9.css +1 -0
- package/dist/assets/{cssMode-Bdg5RpcJ.js → cssMode-ADdAlxTO.js} +1 -1
- package/dist/assets/{editor.api2-D74ufkbr.js → editor.api2-BFLAr0YF.js} +162 -162
- package/dist/assets/file-icons-BpeqWkIB.js +1 -0
- package/dist/assets/{freemarker2-DfMDJfnE.js → freemarker2-C90q5jrp.js} +1 -1
- package/dist/assets/{handlebars-BEdPtzMT.js → handlebars-BwUQcEDq.js} +1 -1
- package/dist/assets/{html-B1fPb1QD.js → html-1s3uPKfh.js} +1 -1
- package/dist/assets/{htmlMode-CY2ZjHxH.js → htmlMode-xtKD99de.js} +1 -1
- package/dist/assets/{index-CQNaZc3l.js → index-C8MchCem.js} +3 -3
- package/dist/assets/{javascript-BV2OsWA0.js → javascript-Ph7ijyny.js} +1 -1
- package/dist/assets/{jsonMode-zd07aeuX.js → jsonMode-D-jGAnFH.js} +1 -1
- package/dist/assets/linkComputer-CbGyI_qx.js +1 -0
- package/dist/assets/{liquid-BParI24X.js → liquid-0IamE_cI.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-BzredkoV.js → lspLanguageFeatures-BxnQunWF.js} +1 -1
- package/dist/assets/main-CLKXa6hf.css +1 -0
- package/dist/assets/{mdx-vGzUJN98.js → mdx-DdNmfivn.js} +1 -1
- package/dist/assets/{monaco.contribution-BmaeBQEc.js → monaco.contribution-B43jsUs1.js} +2 -2
- package/dist/assets/{python-CUwdLgsI.js → python-IAt4X5TS.js} +1 -1
- package/dist/assets/{razor-BScebUxU.js → razor-DRkFYW0S.js} +1 -1
- package/dist/assets/review-C8-oPKXk.css +1 -0
- package/dist/assets/{tsMode-DzUAibXK.js → tsMode-_d_4U2gf.js} +1 -1
- package/dist/assets/{typescript-D9EOeM7N.js → typescript-C7zcMq9l.js} +1 -1
- package/dist/assets/{workers-DAiomYZM.js → workers-D0nOyJzf.js} +1 -1
- package/dist/assets/workspace-editor-model-DND_Y-Bj.js +1 -0
- package/dist/assets/workspace-editor-monaco-BPWhgcIB.js +4 -0
- package/dist/assets/workspace-editor-monaco-N641qPjX.js +1 -0
- package/dist/assets/workspace-view-state-k1bTlJ_Z.js +13 -0
- package/dist/assets/{xml-wWl1dhkQ.js → xml-BR-HvxT8.js} +1 -1
- package/dist/assets/{yaml-Dc2WDaOZ.js → yaml-YtFQwh4q.js} +1 -1
- package/dist/index.html +2 -1
- package/extensions/browser/backend/agent-browser-runtime.cjs +1040 -0
- package/extensions/browser/backend/agent-capability.cjs +85 -0
- package/extensions/browser/backend/browser-mcp-server.cjs +655 -0
- package/extensions/browser/backend/browser-resource-manager.cjs +1486 -0
- package/extensions/browser/backend/browser-resource-store.cjs +228 -0
- package/extensions/browser/backend/browser-router.cjs +203 -0
- package/extensions/browser/backend/executable-discovery.cjs +248 -0
- package/extensions/browser/backend/farming-browser-client.cjs +116 -0
- package/extensions/browser/backend/index.cjs +32 -0
- package/extensions/browser/backend/managed-chromium-installer.cjs +833 -0
- package/extensions/browser/bin/farming-browser +16 -7
- package/extensions/computer/backend/agent-capability.cjs +72 -0
- package/extensions/computer/backend/computer-constants.cjs +59 -0
- package/extensions/computer/backend/computer-mcp-server.cjs +182 -0
- package/extensions/computer/backend/computer-resource-manager.cjs +1219 -0
- package/extensions/computer/backend/computer-resource-store.cjs +190 -0
- package/extensions/computer/backend/computer-router.cjs +257 -0
- package/extensions/computer/backend/cua-tools.json +3067 -0
- package/extensions/computer/backend/farming-computer-client.cjs +84 -0
- package/extensions/computer/backend/index.cjs +35 -0
- package/extensions/computer/backend/isolated-browser-provider.cjs +157 -0
- package/extensions/computer/bin/farming-computer +131 -0
- package/frontend/farming-net/app.js +255 -262
- package/frontend/ghostty-loader.js +24 -20
- package/frontend/reading-anchor.js +210 -193
- package/frontend/runtime-paths.js +24 -26
- package/frontend/session-bridge.js +90 -111
- package/frontend/session-modal-bridge.js +184 -302
- package/frontend/skin-bridge.js +20 -23
- package/frontend/skins/crt/app.js +7686 -7901
- package/frontend/skins/crt/effects.js +7 -6
- package/frontend/skins/crt/index.html +6 -6
- package/frontend/skins/crt/styles/markdown.css +4 -4
- package/frontend/terminal-bridge.js +174 -181
- package/frontend/terminal-replay.js +320 -345
- package/frontend/theme-loader.js +55 -71
- package/index.html +1 -0
- package/package.json +64 -37
- package/shared/backend-connection-status.js +38 -69
- package/shared/browser-protocol.js +235 -110
- package/shared/websocket-liveness.js +40 -0
- package/backend/acp/packaged-claude-acp.js +0 -13
- package/backend/acp/packaged-codex-acp.js +0 -13
- package/backend/acp/patch-decisions.js +0 -153
- package/backend/acp/permission-security.js +0 -108
- package/backend/acp-session-state.js +0 -644
- package/backend/acp-transcript.js +0 -512
- package/backend/agent-env.js +0 -250
- package/backend/agent-json-stream.js +0 -268
- package/backend/agent-lifecycle-journal.js +0 -133
- package/backend/agent-memory-report.js +0 -583
- package/backend/agent-order.js +0 -166
- package/backend/agent-provider-session.js +0 -76
- package/backend/agent-runtime-binding.js +0 -174
- package/backend/agent-session-history.js +0 -953
- package/backend/async-cache.js +0 -94
- package/backend/atomic-json-store.js +0 -37
- package/backend/auth.js +0 -251
- package/backend/bundle-update-helper.js +0 -113
- package/backend/chat-runtime.js +0 -28
- package/backend/claude-settings.js +0 -121
- package/backend/cli-agents.js +0 -486
- package/backend/codex-context-window.js +0 -189
- package/backend/codex-models.js +0 -226
- package/backend/codex-rollout-follower.js +0 -326
- package/backend/codex-session-archive.js +0 -65
- package/backend/codex-session-history.js +0 -647
- package/backend/codex-transcript-sanitizer.js +0 -115
- package/backend/codex-transcript.js +0 -2564
- package/backend/command-runner-child.js +0 -67
- package/backend/config-manager.js +0 -1133
- package/backend/control-api.js +0 -455
- package/backend/executable-discovery.js +0 -216
- package/backend/farming-agent-bootstrap.js +0 -49
- package/backend/farming-cli.js +0 -457
- package/backend/farming-net-pass.js +0 -285
- package/backend/farming-net-registry.js +0 -112
- package/backend/farming-net-server.js +0 -298
- package/backend/farming-session-store.js +0 -677
- package/backend/git-worktree-info.js +0 -181
- package/backend/haiku-token.js +0 -772
- package/backend/index-html.js +0 -62
- package/backend/input-parts.js +0 -54
- package/backend/input-routing.js +0 -24
- package/backend/json-cli-runtime.js +0 -275
- package/backend/main-agent-skills.js +0 -305
- package/backend/main-page-session.js +0 -122
- package/backend/native-pty-controller-generation.js +0 -106
- package/backend/native-pty-host-identity.js +0 -86
- package/backend/native-pty-host-path.js +0 -42
- package/backend/native-session-engine.js +0 -268
- package/backend/network.js +0 -23
- package/backend/npm-update-helper.js +0 -374
- package/backend/packaged-node-pty.js +0 -99
- package/backend/preview-session-manager.js +0 -68
- package/backend/provider-adapters.js +0 -399
- package/backend/provider-session-id.js +0 -47
- package/backend/provider-session-service.js +0 -345
- package/backend/qr-share-tickets.js +0 -100
- package/backend/review-diff-router.js +0 -180
- package/backend/review-diff-service.js +0 -1270
- package/backend/review-session-router.js +0 -74
- package/backend/review-session-service.js +0 -446
- package/backend/review-session-store.js +0 -151
- package/backend/review-state-router.js +0 -116
- package/backend/review-state-store.js +0 -286
- package/backend/run-history-store.js +0 -67
- package/backend/runtime-dependency-manager.js +0 -759
- package/backend/runtime-executable-invocation.js +0 -20
- package/backend/runtime-observation.js +0 -81
- package/backend/server-process-identity.js +0 -47
- package/backend/server.js +0 -3267
- package/backend/session-engine-bridge.js +0 -129
- package/backend/session-engine-router.js +0 -66
- package/backend/session-engine.js +0 -39
- package/backend/session-stream-protocol.js +0 -185
- package/backend/shell-busy-integration.js +0 -472
- package/backend/slash-command-discovery.js +0 -433
- package/backend/storage-layout.js +0 -190
- package/backend/system-monitor.js +0 -77
- package/backend/terminal-attach-checkpoint.js +0 -74
- package/backend/terminal-exit-quiescence.js +0 -39
- package/backend/terminal-runtime-cleanup.js +0 -80
- package/backend/terminal-screen-worker.js +0 -337
- package/backend/terminal-state-serialization.js +0 -127
- package/backend/theme-manager.js +0 -163
- package/backend/update-service.js +0 -1545
- package/backend/usage-forecast.js +0 -86
- package/backend/usage-history-scanner.ts +0 -2718
- package/backend/usage-history-smoke.js +0 -64
- package/backend/usage-history-worker.js +0 -34
- package/backend/workspace-directory.js +0 -232
- package/backend/workspace-discovery.js +0 -311
- package/backend/workspace-root-registry.js +0 -166
- package/dist/assets/App-BKNUILuL.css +0 -1
- package/dist/assets/App-Ex2oVVXO.js +0 -693
- package/dist/assets/FileEditorPane-CNtuGjQ3.js +0 -2
- package/dist/assets/IconGlyphs-BClgag6E.js +0 -1
- package/dist/assets/ProjectFilesSection-Cf2BEcAN.js +0 -12
- package/dist/assets/ReviewPage-BRuxFaH5.js +0 -3
- package/dist/assets/code-dark-BEZTQwYZ.css +0 -1
- package/dist/assets/file-icons-BOlcJ0ZF.js +0 -1
- package/dist/assets/main-DK9Sy7-c.css +0 -1
- package/dist/assets/review-BcnwQtJv.css +0 -1
- package/dist/assets/workspace-editor-model-CSGCl21x.js +0 -1
- package/dist/assets/workspace-editor-monaco-CJNwbpCy.js +0 -4
- package/dist/assets/workspace-editor-monaco-CO9w8YV0.js +0 -1
- package/dist/assets/workspace-view-state-BjKPYp0E.js +0 -7
- package/extensions/browser/backend/agent-browser-runtime.js +0 -1041
- package/extensions/browser/backend/agent-capability.js +0 -55
- package/extensions/browser/backend/browser-mcp-server.js +0 -621
- package/extensions/browser/backend/browser-resource-manager.js +0 -1309
- package/extensions/browser/backend/browser-resource-store.js +0 -190
- package/extensions/browser/backend/browser-router.js +0 -133
- package/extensions/browser/backend/executable-discovery.js +0 -224
- package/extensions/browser/backend/farming-browser-client.js +0 -83
- package/extensions/browser/backend/index.js +0 -7
- package/extensions/browser/frontend/BrowserSidebarPortals.tsx +0 -360
- package/extensions/browser/frontend/BrowserViewer.tsx +0 -500
- package/extensions/browser/frontend/browser-resource-state.ts +0 -60
- package/extensions/browser/frontend/browser.css +0 -567
- package/extensions/browser/frontend/types.ts +0 -31
- package/extensions/browser/frontend/useBrowserResources.ts +0 -158
- package/frontend/vendor/ghostty-web/index.d.ts +0 -1842
- package/shared/backend-connection-status.d.ts +0 -37
- package/shared/browser-protocol.d.ts +0 -5
- /package/dist/assets/{api-w2x5QfxB.js → api-ByCV9MEr.js} +0 -0
- /package/dist/assets/{core-vPkV8P_m.js → core-Czwdx12k.js} +0 -0
- /package/dist/assets/{fsharp-DNZNZyEE.js → fsharp-BjPpkoJe.js} +0 -0
- /package/dist/assets/{go-_kPJjNc_.js → go-BkIDmSUP.js} +0 -0
- /package/dist/assets/{graphql-4HCNVt5g.js → graphql-CCJTpqi1.js} +0 -0
- /package/dist/assets/{hcl-C87ymY0o.js → hcl-s4z8lUFg.js} +0 -0
- /package/dist/assets/{ini-BYrrGNTc.js → ini-CyUW1I7Z.js} +0 -0
- /package/dist/assets/{java-B211rJbk.js → java-CFCcGRiM.js} +0 -0
- /package/dist/assets/{julia-DVPsRtBm.js → julia-DQyH_EYb.js} +0 -0
- /package/dist/assets/{kotlin-BYyNerjp.js → kotlin-B8hA5O51.js} +0 -0
- /package/dist/assets/{less-EC9UblGE.js → less-DaVdaas0.js} +0 -0
- /package/dist/assets/{lexon-B1yuUdqW.js → lexon-DLBhPd3y.js} +0 -0
- /package/dist/assets/{lua-Bo6GFzxT.js → lua-gB1c_cif.js} +0 -0
- /package/dist/assets/{m3-C-Y9cBVI.js → m3-C2T3z3gB.js} +0 -0
- /package/dist/assets/{markdown-BHu-wLbe.js → markdown-CKq-Euj1.js} +0 -0
- /package/dist/assets/{mips-Dkds26rX.js → mips-Bcs5qIDX.js} +0 -0
- /package/dist/assets/{msdax-PU4g-8_g.js → msdax-CBP48FHF.js} +0 -0
- /package/dist/assets/{mysql-fPpuGpS7.js → mysql-c7tiPcFj.js} +0 -0
- /package/dist/assets/{objective-c-Wy1e5JUf.js → objective-c-D4BZkeEV.js} +0 -0
- /package/dist/assets/{pascal-BIXwvrqU.js → pascal-CJDZg0MN.js} +0 -0
- /package/dist/assets/{pascaligo-DwR0AtL_.js → pascaligo-Bygi8Ynl.js} +0 -0
- /package/dist/assets/{perl-CGHAl8my.js → perl-aS8bkf7E.js} +0 -0
- /package/dist/assets/{pgsql-B91M73E2.js → pgsql-CzNAYFYl.js} +0 -0
- /package/dist/assets/{php-C70ttrCx.js → php-DOtoEFTT.js} +0 -0
- /package/dist/assets/{pla-BNrXaRh0.js → pla-DugRnao1.js} +0 -0
- /package/dist/assets/{postiats-Dv2IAZR9.js → postiats-DsXwusKh.js} +0 -0
- /package/dist/assets/{powerquery-CkY5Wq7J.js → powerquery-DL3o72oD.js} +0 -0
- /package/dist/assets/{powershell-uGIYLU3C.js → powershell-C1qHekys.js} +0 -0
- /package/dist/assets/{protobuf-CavfmJu-.js → protobuf-DHgsc-cH.js} +0 -0
- /package/dist/assets/{pug-Drqnyitx.js → pug-FZXlE_Iu.js} +0 -0
- /package/dist/assets/{qrcode-7uGsTEmi.js → qrcode-DJDTdjL7.js} +0 -0
- /package/dist/assets/{qsharp-C7yF-Dti.js → qsharp-bLyhI3DT.js} +0 -0
- /package/dist/assets/{r-HXLGP0_-.js → r-DjFGXbAa.js} +0 -0
- /package/dist/assets/{redis-C4y6mf2p.js → redis-DSoZ_V6u.js} +0 -0
- /package/dist/assets/{redshift-DdQrFPrv.js → redshift-CzkdCsrg.js} +0 -0
- /package/dist/assets/{restructuredtext-CBrf2PNj.js → restructuredtext-B_XPrGCQ.js} +0 -0
- /package/dist/assets/{ruby-CVtMoX2B.js → ruby-Cry2RneJ.js} +0 -0
- /package/dist/assets/{rust-Ckq4sbMP.js → rust-CWzdbFq1.js} +0 -0
- /package/dist/assets/{sb-er66OpE3.js → sb-DmofU59h.js} +0 -0
- /package/dist/assets/{scala-BdWZz-hH.js → scala-BUC2l-vN.js} +0 -0
- /package/dist/assets/{scheme-kTV_s2kP.js → scheme-CNIB_72f.js} +0 -0
- /package/dist/assets/{scss-Bymy2VJQ.js → scss-DcL4-NbX.js} +0 -0
- /package/dist/assets/{shell-bwEP1LQ1.js → shell-CgAGvSSA.js} +0 -0
- /package/dist/assets/{solidity-DCddeBgL.js → solidity-DSkt6O_P.js} +0 -0
- /package/dist/assets/{sophia-BMN3AQig.js → sophia--5ytcjDN.js} +0 -0
- /package/dist/assets/{sparql-CSXOLjzi.js → sparql-C1Srr7cD.js} +0 -0
- /package/dist/assets/{sql-COg8ewmK.js → sql-2k_O5fj7.js} +0 -0
- /package/dist/assets/{st-CIaZtBS_.js → st-DCCCPqfz.js} +0 -0
- /package/dist/assets/{swift-DXiOhUHu.js → swift-Dd8mAfOt.js} +0 -0
- /package/dist/assets/{systemverilog-BTDE6Dr2.js → systemverilog-B0-aZYXu.js} +0 -0
- /package/dist/assets/{tcl-B0zy9NvV.js → tcl-CZAZwPVX.js} +0 -0
- /package/dist/assets/{twig-D594l4Z1.js → twig-D-Ag0tdS.js} +0 -0
- /package/dist/assets/{typespec-XKVjaPqo.js → typespec-B7kTFp0Z.js} +0 -0
- /package/dist/assets/{useWorkspaceMenuKeyboard-Bpy_s69Z.js → useWorkspaceMenuKeyboard-uYZpDxhU.js} +0 -0
- /package/dist/assets/{vb-B2wYoKRV.js → vb-20w1JPum.js} +0 -0
- /package/dist/assets/{wgsl-D0Yf2LbK.js → wgsl-CLJfqFt0.js} +0 -0
- /package/dist/assets/{workspace-working-copy-C3FSR3N6.js → workspace-working-copy-Cfz07cOd.js} +0 -0
|
@@ -0,0 +1,1267 @@
|
|
|
1
|
+
// Generated by scripts/build-backend-runtime.ts. Do not edit.
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
var review_diff_service_exports = {};
|
|
31
|
+
__export(review_diff_service_exports, {
|
|
32
|
+
ReviewDiffService: () => ReviewDiffService,
|
|
33
|
+
fileFromPatch: () => fileFromPatch,
|
|
34
|
+
filterWorkingCopyChangeItems: () => filterWorkingCopyChangeItems,
|
|
35
|
+
gitContextArgs: () => gitContextArgs,
|
|
36
|
+
gitDiffPathspecArgs: () => gitDiffPathspecArgs,
|
|
37
|
+
gitRangeReviewId: () => gitRangeReviewId,
|
|
38
|
+
gitWhitespaceArgs: () => gitWhitespaceArgs,
|
|
39
|
+
metadataFile: () => metadataFile,
|
|
40
|
+
normalizeDiffContext: () => normalizeDiffContext,
|
|
41
|
+
normalizeIgnoreWhitespace: () => normalizeIgnoreWhitespace,
|
|
42
|
+
normalizeModifiedWithinDays: () => normalizeModifiedWithinDays,
|
|
43
|
+
normalizeReviewLimit: () => normalizeReviewLimit,
|
|
44
|
+
normalizeWorkingCopyScope: () => normalizeWorkingCopyScope,
|
|
45
|
+
parseComparisonBranches: () => parseComparisonBranches,
|
|
46
|
+
parseComparisonCommits: () => parseComparisonCommits,
|
|
47
|
+
parseNameStatus: () => parseNameStatus,
|
|
48
|
+
parseNumstat: () => parseNumstat,
|
|
49
|
+
parseRawDiffMetadata: () => parseRawDiffMetadata,
|
|
50
|
+
patchDiffHeader: () => patchDiffHeader,
|
|
51
|
+
patchMetadata: () => patchMetadata,
|
|
52
|
+
untrackedHunks: () => untrackedHunks,
|
|
53
|
+
untrackedPatch: () => untrackedPatch,
|
|
54
|
+
workingCopyPatchset: () => workingCopyPatchset,
|
|
55
|
+
workingCopyReviewId: () => workingCopyReviewId
|
|
56
|
+
});
|
|
57
|
+
module.exports = __toCommonJS(review_diff_service_exports);
|
|
58
|
+
var crypto = __toESM(require("crypto"));
|
|
59
|
+
var fs = __toESM(require("fs"));
|
|
60
|
+
var path = __toESM(require("path"));
|
|
61
|
+
var import_workspace_file_service = require("./workspace-file-service.cjs");
|
|
62
|
+
const MAX_REVIEW_FILES = 200;
|
|
63
|
+
const MAX_WORKING_COPY_SCAN_FILES = 2e3;
|
|
64
|
+
const MAX_UNTRACKED_LINES = 500;
|
|
65
|
+
const MAX_REVIEW_CONTEXT_RANGE_LINES = 1e4;
|
|
66
|
+
const DIFF_CONCURRENCY = 4;
|
|
67
|
+
function errorField(error, field) {
|
|
68
|
+
return error && typeof error === "object" && field in error ? error[field] : void 0;
|
|
69
|
+
}
|
|
70
|
+
function errorString(error, field) {
|
|
71
|
+
const value = errorField(error, field);
|
|
72
|
+
return value ? String(value) : "";
|
|
73
|
+
}
|
|
74
|
+
function reviewKind(gitStatus) {
|
|
75
|
+
if (gitStatus === "added" || gitStatus === "untracked") return "added";
|
|
76
|
+
if (gitStatus === "copied") return "copied";
|
|
77
|
+
if (gitStatus === "deleted") return "deleted";
|
|
78
|
+
if (gitStatus === "renamed") return "renamed";
|
|
79
|
+
if (gitStatus === "rewritten") return "rewritten";
|
|
80
|
+
return "modified";
|
|
81
|
+
}
|
|
82
|
+
function reviewStatus(kind) {
|
|
83
|
+
if (kind === "added") return "A";
|
|
84
|
+
if (kind === "copied") return "C";
|
|
85
|
+
if (kind === "deleted") return "D";
|
|
86
|
+
if (kind === "renamed") return "R";
|
|
87
|
+
if (kind === "rewritten") return "W";
|
|
88
|
+
if (kind === "unmodified") return "U";
|
|
89
|
+
if (kind === "reverted") return "X";
|
|
90
|
+
return "M";
|
|
91
|
+
}
|
|
92
|
+
function countRows(hunks) {
|
|
93
|
+
return hunks.reduce((total, hunk) => hunk.rows.reduce((count, row) => ({
|
|
94
|
+
added: count.added + (row.kind === "added" || row.kind === "changed" ? 1 : 0),
|
|
95
|
+
removed: count.removed + (row.kind === "deleted" || row.kind === "changed" ? 1 : 0)
|
|
96
|
+
}), total), { added: 0, removed: 0 });
|
|
97
|
+
}
|
|
98
|
+
function metadataOnlyOption(value) {
|
|
99
|
+
return value === true || value === "1" || value === "true";
|
|
100
|
+
}
|
|
101
|
+
function normalizeIgnoreWhitespace(value) {
|
|
102
|
+
if (value === "ALL" || value === "IGNORE_ALL") return "ALL";
|
|
103
|
+
if (value === "TRAILING" || value === "IGNORE_TRAILING") return "TRAILING";
|
|
104
|
+
if (value === "LEADING_AND_TRAILING" || value === "IGNORE_LEADING_AND_TRAILING") return "LEADING_AND_TRAILING";
|
|
105
|
+
return "NONE";
|
|
106
|
+
}
|
|
107
|
+
function normalizeDiffContext(value) {
|
|
108
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
109
|
+
const context = Number(value);
|
|
110
|
+
if (!Number.isInteger(context) || context < 0) return void 0;
|
|
111
|
+
return Math.min(context, 1e4);
|
|
112
|
+
}
|
|
113
|
+
function normalizeReviewLimit(value) {
|
|
114
|
+
const limit = Number(value);
|
|
115
|
+
if (!Number.isInteger(limit) || limit <= 0) return MAX_REVIEW_FILES;
|
|
116
|
+
return Math.min(MAX_REVIEW_FILES, limit);
|
|
117
|
+
}
|
|
118
|
+
function normalizeContextRangeInteger(value, name, { allowZero = false } = {}) {
|
|
119
|
+
const number = Number(value);
|
|
120
|
+
const minimum = allowZero ? 0 : 1;
|
|
121
|
+
if (!Number.isInteger(number) || number < minimum) {
|
|
122
|
+
throw new import_workspace_file_service.WorkspaceFileError(`${name} must be an integer greater than or equal to ${minimum}`, 400);
|
|
123
|
+
}
|
|
124
|
+
return number;
|
|
125
|
+
}
|
|
126
|
+
function textLines(content) {
|
|
127
|
+
if (content === void 0 || content === null || content === "") return [];
|
|
128
|
+
const lines = String(content).split("\n");
|
|
129
|
+
if (lines.at(-1) === "") lines.pop();
|
|
130
|
+
return lines;
|
|
131
|
+
}
|
|
132
|
+
function reviewTextFileMeta(name, lines) {
|
|
133
|
+
return {
|
|
134
|
+
contentType: "text/plain",
|
|
135
|
+
lines: lines.length,
|
|
136
|
+
name
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function contextRowsFromSources(sources, options = {}) {
|
|
140
|
+
const oldStart = normalizeContextRangeInteger(options.oldStart, "oldStart");
|
|
141
|
+
const newStart = normalizeContextRangeInteger(options.newStart, "newStart");
|
|
142
|
+
const requestedLines = normalizeContextRangeInteger(options.lines, "lines");
|
|
143
|
+
if (requestedLines > MAX_REVIEW_CONTEXT_RANGE_LINES) {
|
|
144
|
+
throw new import_workspace_file_service.WorkspaceFileError(`lines must not exceed ${MAX_REVIEW_CONTEXT_RANGE_LINES}`, 400);
|
|
145
|
+
}
|
|
146
|
+
const left = sources.leftLines.slice(oldStart - 1, oldStart - 1 + requestedLines);
|
|
147
|
+
const right = sources.rightLines.slice(newStart - 1, newStart - 1 + requestedLines);
|
|
148
|
+
if (left.length !== requestedLines || right.length !== requestedLines) {
|
|
149
|
+
throw new import_workspace_file_service.WorkspaceFileError("review context range is outside the file", 416);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
leftLines: sources.leftLines.length,
|
|
153
|
+
rightLines: sources.rightLines.length,
|
|
154
|
+
rows: Array.from({ length: requestedLines }, (_, index) => ({
|
|
155
|
+
kind: "context",
|
|
156
|
+
left: { line: oldStart + index, text: left[index] },
|
|
157
|
+
right: { line: newStart + index, text: right[index] }
|
|
158
|
+
}))
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function normalizeWorkingCopyScope(value) {
|
|
162
|
+
return value === "tracked" || value === "untracked" ? value : void 0;
|
|
163
|
+
}
|
|
164
|
+
function normalizeModifiedWithinDays(value) {
|
|
165
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
166
|
+
const days = Number(value);
|
|
167
|
+
return Number.isInteger(days) && days >= 1 && days <= 3650 ? days : void 0;
|
|
168
|
+
}
|
|
169
|
+
function filterWorkingCopyChangeItems(root, items, options = {}, now = Date.now()) {
|
|
170
|
+
const scope = normalizeWorkingCopyScope(options.scope);
|
|
171
|
+
let filtered = items;
|
|
172
|
+
if (scope === "tracked") {
|
|
173
|
+
filtered = items.filter((change) => change.gitStatus !== "untracked");
|
|
174
|
+
} else if (scope === "untracked") {
|
|
175
|
+
filtered = items.filter((change) => change.gitStatus === "untracked");
|
|
176
|
+
const modifiedWithinDays = normalizeModifiedWithinDays(options.modifiedWithinDays);
|
|
177
|
+
if (modifiedWithinDays !== void 0) {
|
|
178
|
+
const modifiedSince = now - modifiedWithinDays * 24 * 60 * 60 * 1e3;
|
|
179
|
+
filtered = filtered.filter((change) => {
|
|
180
|
+
if (!isSafeReviewPath(change.path)) return false;
|
|
181
|
+
try {
|
|
182
|
+
return fs.statSync(path.join(root, change.path)).mtimeMs >= modifiedSince;
|
|
183
|
+
} catch {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return filtered;
|
|
190
|
+
}
|
|
191
|
+
function gitWhitespaceArgs(value) {
|
|
192
|
+
const normalized = normalizeIgnoreWhitespace(value);
|
|
193
|
+
if (normalized === "ALL") return ["--ignore-all-space"];
|
|
194
|
+
if (normalized === "TRAILING") return ["--ignore-space-at-eol"];
|
|
195
|
+
if (normalized === "LEADING_AND_TRAILING") return ["--ignore-space-change"];
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
function gitContextArgs(value) {
|
|
199
|
+
const context = normalizeDiffContext(value);
|
|
200
|
+
return context === void 0 ? [] : [`--unified=${context}`];
|
|
201
|
+
}
|
|
202
|
+
function diffContentOptions(options = {}) {
|
|
203
|
+
const ignoreWhitespace = normalizeIgnoreWhitespace(options.ignoreWhitespace);
|
|
204
|
+
const context = normalizeDiffContext(options.context);
|
|
205
|
+
return {
|
|
206
|
+
...ignoreWhitespace !== "NONE" ? { ignoreWhitespace } : {},
|
|
207
|
+
...context !== void 0 ? { context } : {}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function hasOptions(value) {
|
|
211
|
+
return Object.keys(value).length > 0;
|
|
212
|
+
}
|
|
213
|
+
function reviewFileIdentity(file) {
|
|
214
|
+
return {
|
|
215
|
+
added: file.added,
|
|
216
|
+
binary: file.binary === true,
|
|
217
|
+
kind: file.kind,
|
|
218
|
+
newMode: file.newMode || "",
|
|
219
|
+
newSha: file.newSha || "",
|
|
220
|
+
oldMode: file.oldMode || "",
|
|
221
|
+
oldSha: file.oldSha || "",
|
|
222
|
+
path: file.path,
|
|
223
|
+
previousPath: file.previousPath || "",
|
|
224
|
+
removed: file.removed,
|
|
225
|
+
size: Number.isInteger(file.size) ? file.size : null,
|
|
226
|
+
sizeDelta: Number.isInteger(file.sizeDelta) ? file.sizeDelta : null,
|
|
227
|
+
status: file.status || reviewStatus(file.kind)
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function metadataFile(change, options = {}) {
|
|
231
|
+
const kind = change.kind || reviewKind(change.gitStatus);
|
|
232
|
+
return {
|
|
233
|
+
added: typeof options.added === "number" && Number.isInteger(options.added) && options.added >= 0 ? options.added : 0,
|
|
234
|
+
...options.binary === true ? { binary: true } : {},
|
|
235
|
+
diff: { hunks: [], ...options.truncated === true ? { truncated: true } : {} },
|
|
236
|
+
diffLoaded: false,
|
|
237
|
+
...options.diffTooExpensive === true ? { diffTooExpensive: true } : {},
|
|
238
|
+
kind,
|
|
239
|
+
...typeof options.newMode === "string" ? { newMode: options.newMode } : {},
|
|
240
|
+
...typeof options.newSha === "string" ? { newSha: options.newSha } : {},
|
|
241
|
+
...typeof options.oldMode === "string" ? { oldMode: options.oldMode } : {},
|
|
242
|
+
...typeof options.oldSha === "string" ? { oldSha: options.oldSha } : {},
|
|
243
|
+
path: change.path,
|
|
244
|
+
...change.previousPath ? { previousPath: change.previousPath } : {},
|
|
245
|
+
removed: typeof options.removed === "number" && Number.isInteger(options.removed) && options.removed >= 0 ? options.removed : 0,
|
|
246
|
+
...Number.isInteger(options.size) ? { size: options.size } : {},
|
|
247
|
+
...Number.isInteger(options.sizeDelta) ? { sizeDelta: options.sizeDelta } : {},
|
|
248
|
+
status: change.status || reviewStatus(kind)
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function assertUniqueReviewPaths(files) {
|
|
252
|
+
const seen = /* @__PURE__ */ new Set();
|
|
253
|
+
for (const file of files) {
|
|
254
|
+
if (seen.has(file.path)) throw new import_workspace_file_service.WorkspaceFileError("review snapshot contains duplicate file paths", 500);
|
|
255
|
+
seen.add(file.path);
|
|
256
|
+
}
|
|
257
|
+
return files;
|
|
258
|
+
}
|
|
259
|
+
function untrackedLines(content) {
|
|
260
|
+
const lines = String(content || "").split("\n");
|
|
261
|
+
if (lines.at(-1) === "") lines.pop();
|
|
262
|
+
return lines;
|
|
263
|
+
}
|
|
264
|
+
function untrackedHunks(content) {
|
|
265
|
+
const lines = untrackedLines(content);
|
|
266
|
+
const visibleLines = lines.slice(0, MAX_UNTRACKED_LINES);
|
|
267
|
+
return [{
|
|
268
|
+
header: `@@ -0,0 +1,${visibleLines.length} @@`,
|
|
269
|
+
oldStart: 0,
|
|
270
|
+
oldLines: 0,
|
|
271
|
+
newStart: 1,
|
|
272
|
+
newLines: visibleLines.length,
|
|
273
|
+
rows: visibleLines.map((text, index) => ({ kind: "added", right: { line: index + 1, text } }))
|
|
274
|
+
}];
|
|
275
|
+
}
|
|
276
|
+
function untrackedPatch(filePath, content) {
|
|
277
|
+
const lines = untrackedLines(content);
|
|
278
|
+
const visibleLines = lines.slice(0, MAX_UNTRACKED_LINES);
|
|
279
|
+
const suffix = visibleLines.length ? `${visibleLines.map((line) => `+${line}`).join("\n")}
|
|
280
|
+
` : "";
|
|
281
|
+
return [
|
|
282
|
+
`diff --git a/${filePath} b/${filePath}`,
|
|
283
|
+
"new file mode 100644",
|
|
284
|
+
"--- /dev/null",
|
|
285
|
+
`+++ b/${filePath}`,
|
|
286
|
+
`@@ -0,0 +1,${visibleLines.length} @@`,
|
|
287
|
+
suffix
|
|
288
|
+
].join("\n");
|
|
289
|
+
}
|
|
290
|
+
function untrackedContentTooLarge(content) {
|
|
291
|
+
return untrackedLines(content).length > MAX_UNTRACKED_LINES;
|
|
292
|
+
}
|
|
293
|
+
async function mapWithConcurrency(values, mapper) {
|
|
294
|
+
const result = new Array(values.length);
|
|
295
|
+
let next = 0;
|
|
296
|
+
await Promise.all(Array.from({ length: Math.min(DIFF_CONCURRENCY, values.length) }, async () => {
|
|
297
|
+
while (next < values.length) {
|
|
298
|
+
const index = next++;
|
|
299
|
+
result[index] = await mapper(values[index]);
|
|
300
|
+
}
|
|
301
|
+
}));
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
function stableHash(value) {
|
|
305
|
+
return crypto.createHash("sha256").update(String(value)).digest("hex");
|
|
306
|
+
}
|
|
307
|
+
function reviewRootIdentity(root) {
|
|
308
|
+
try {
|
|
309
|
+
return fs.realpathSync.native(root);
|
|
310
|
+
} catch {
|
|
311
|
+
return root;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function workingCopyReviewId(root, options = {}) {
|
|
315
|
+
const scope = normalizeWorkingCopyScope(options.scope);
|
|
316
|
+
if (!scope) return `working-copy-${stableHash(reviewRootIdentity(root)).slice(0, 24)}`;
|
|
317
|
+
const modifiedWithinDays = scope === "untracked" ? normalizeModifiedWithinDays(options.modifiedWithinDays) : void 0;
|
|
318
|
+
const identity = [reviewRootIdentity(root), scope, modifiedWithinDays || "all"].join("\n");
|
|
319
|
+
return `working-copy-${stableHash(identity).slice(0, 24)}`;
|
|
320
|
+
}
|
|
321
|
+
function workingCopyPatchset(files) {
|
|
322
|
+
return `Working copy ${stableHash(JSON.stringify(files.map(reviewFileIdentity))).slice(0, 12)}`;
|
|
323
|
+
}
|
|
324
|
+
function gitRangeReviewId(root, base, head) {
|
|
325
|
+
return `git-range-${stableHash(`${reviewRootIdentity(root)}
|
|
326
|
+
${base}
|
|
327
|
+
${head}`).slice(0, 24)}`;
|
|
328
|
+
}
|
|
329
|
+
function parseComparisonCommits(value) {
|
|
330
|
+
return String(value || "").split("").map((record) => record.trim()).filter(Boolean).map((record) => {
|
|
331
|
+
const [id = "", parents = "", shortId = "", ...subjectParts] = record.split("");
|
|
332
|
+
const parent = parents.trim().split(/\s+/).filter(Boolean)[0];
|
|
333
|
+
const subject = subjectParts.join("").trim();
|
|
334
|
+
if (!/^[a-f0-9]{40,64}$/i.test(id) || !/^[a-f0-9]{40,64}$/i.test(parent || "")) return null;
|
|
335
|
+
return {
|
|
336
|
+
base: parent,
|
|
337
|
+
head: id,
|
|
338
|
+
id: `commit:${id}`,
|
|
339
|
+
label: `${shortId || id.slice(0, 8)} ${subject || "Commit"}`.trim()
|
|
340
|
+
};
|
|
341
|
+
}).filter((commit) => commit !== null);
|
|
342
|
+
}
|
|
343
|
+
function parseComparisonBranches(value, currentBranch) {
|
|
344
|
+
return String(value || "").split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
345
|
+
const [name = "", id = ""] = line.split("\0");
|
|
346
|
+
if (!name || name === currentBranch || name.endsWith("/HEAD") || !/^[a-f0-9]{40,64}$/i.test(id)) return null;
|
|
347
|
+
return { id, name };
|
|
348
|
+
}).filter((branch) => branch !== null);
|
|
349
|
+
}
|
|
350
|
+
function isSafeGitRevision(value) {
|
|
351
|
+
return typeof value === "string" && value.length > 0 && value.length <= 200 && !value.startsWith("-") && !/[\\\0\r\n\t ]/.test(value);
|
|
352
|
+
}
|
|
353
|
+
function isReviewHead(value) {
|
|
354
|
+
return value === "now" || isSafeGitRevision(value);
|
|
355
|
+
}
|
|
356
|
+
function gitRangeRevisionArgs(base, head) {
|
|
357
|
+
return head === "now" ? [base] : [base, head];
|
|
358
|
+
}
|
|
359
|
+
function isSafeReviewPath(value) {
|
|
360
|
+
return typeof value === "string" && value.length > 0 && value.length <= 4096 && !value.includes("\0") && !value.startsWith("/") && !value.startsWith("\\") && value.split(/[\\/]/).every((segment) => segment && segment !== "." && segment !== "..");
|
|
361
|
+
}
|
|
362
|
+
function nulFields(stdout) {
|
|
363
|
+
const fields = String(stdout || "").split("\0");
|
|
364
|
+
if (fields.at(-1) === "") fields.pop();
|
|
365
|
+
return fields;
|
|
366
|
+
}
|
|
367
|
+
function changeFromStatus(status, path2, previousPath) {
|
|
368
|
+
const filePath = path2 || "";
|
|
369
|
+
if (status.startsWith("R")) return { kind: "renamed", path: filePath, previousPath, status: "R" };
|
|
370
|
+
if (status.startsWith("C")) return { kind: "copied", path: filePath, previousPath, status: "C" };
|
|
371
|
+
if (status === "A") return { kind: "added", path: filePath, status: "A" };
|
|
372
|
+
if (status === "D") return { kind: "deleted", path: filePath, status: "D" };
|
|
373
|
+
if (status === "W") return { kind: "rewritten", path: filePath, status: "W" };
|
|
374
|
+
return { kind: "modified", path: filePath, status: "M" };
|
|
375
|
+
}
|
|
376
|
+
function parseNameStatus(stdout) {
|
|
377
|
+
if (String(stdout || "").includes("\0")) {
|
|
378
|
+
const fields = nulFields(stdout);
|
|
379
|
+
const changes = [];
|
|
380
|
+
for (let index = 0; index < fields.length; ) {
|
|
381
|
+
const status = fields[index++] || "";
|
|
382
|
+
if (status.startsWith("R") || status.startsWith("C")) {
|
|
383
|
+
const previousPath = fields[index++];
|
|
384
|
+
const filePath2 = fields[index++];
|
|
385
|
+
if (filePath2) changes.push(changeFromStatus(status, filePath2, previousPath));
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const filePath = fields[index++];
|
|
389
|
+
if (filePath) changes.push(changeFromStatus(status, filePath));
|
|
390
|
+
}
|
|
391
|
+
return changes;
|
|
392
|
+
}
|
|
393
|
+
return String(stdout || "").split("\n").filter(Boolean).map((line) => {
|
|
394
|
+
const fields = line.split(" ");
|
|
395
|
+
const status = fields[0] || "";
|
|
396
|
+
return changeFromStatus(status, status.startsWith("R") || status.startsWith("C") ? fields[2] : fields[1], fields[1]);
|
|
397
|
+
}).filter((file) => file.path);
|
|
398
|
+
}
|
|
399
|
+
function normalizedNumstatPath(pathField) {
|
|
400
|
+
const value = String(pathField || "");
|
|
401
|
+
const braceRename = value.match(/^(.*)\{.* => (.*)\}(.*)$/);
|
|
402
|
+
if (braceRename) return `${braceRename[1]}${braceRename[2]}${braceRename[3]}`;
|
|
403
|
+
return value;
|
|
404
|
+
}
|
|
405
|
+
function numstatPathForChanges(pathField, changes = []) {
|
|
406
|
+
const value = String(pathField || "");
|
|
407
|
+
if (!value) return "";
|
|
408
|
+
if (changes.some((change) => change.path === value)) return value;
|
|
409
|
+
const bracePath = normalizedNumstatPath(value);
|
|
410
|
+
const renamed = changes.find((change) => {
|
|
411
|
+
if (!change.previousPath) return false;
|
|
412
|
+
if (bracePath && change.path === bracePath) return true;
|
|
413
|
+
if (value === `${change.previousPath} => ${change.path}`) return true;
|
|
414
|
+
return false;
|
|
415
|
+
});
|
|
416
|
+
if (renamed) return renamed.path;
|
|
417
|
+
return value;
|
|
418
|
+
}
|
|
419
|
+
function splitNumstatEntry(value) {
|
|
420
|
+
const text = String(value || "");
|
|
421
|
+
const first = text.indexOf(" ");
|
|
422
|
+
const second = first === -1 ? -1 : text.indexOf(" ", first + 1);
|
|
423
|
+
if (first === -1 || second === -1) return ["", "", ""];
|
|
424
|
+
return [text.slice(0, first), text.slice(first + 1, second), text.slice(second + 1)];
|
|
425
|
+
}
|
|
426
|
+
function parseNumstat(stdout, changes = []) {
|
|
427
|
+
const stats = /* @__PURE__ */ new Map();
|
|
428
|
+
if (String(stdout || "").includes("\0")) {
|
|
429
|
+
const fields = nulFields(stdout);
|
|
430
|
+
for (let index = 0; index < fields.length; ) {
|
|
431
|
+
const counts = fields[index++] || "";
|
|
432
|
+
const [addedText, removedText, inlinePath] = splitNumstatEntry(counts);
|
|
433
|
+
const renameLike = inlinePath === "";
|
|
434
|
+
const pathField = renameLike ? `${fields[index++] || ""} => ${fields[index++] || ""}` : inlinePath;
|
|
435
|
+
const added = Number(addedText);
|
|
436
|
+
const removed = Number(removedText);
|
|
437
|
+
const path2 = numstatPathForChanges(pathField, changes);
|
|
438
|
+
if (!path2) continue;
|
|
439
|
+
stats.set(path2, {
|
|
440
|
+
added: Number.isInteger(added) && added >= 0 ? added : 0,
|
|
441
|
+
binary: addedText === "-" || removedText === "-",
|
|
442
|
+
removed: Number.isInteger(removed) && removed >= 0 ? removed : 0
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
return stats;
|
|
446
|
+
}
|
|
447
|
+
for (const line of String(stdout || "").split("\n")) {
|
|
448
|
+
if (!line.trim()) continue;
|
|
449
|
+
const [addedText, removedText, pathText] = splitNumstatEntry(line);
|
|
450
|
+
if (!pathText) continue;
|
|
451
|
+
const added = Number(addedText);
|
|
452
|
+
const removed = Number(removedText);
|
|
453
|
+
const path2 = numstatPathForChanges(pathText, changes);
|
|
454
|
+
if (!path2) continue;
|
|
455
|
+
stats.set(path2, {
|
|
456
|
+
added: Number.isInteger(added) && added >= 0 ? added : 0,
|
|
457
|
+
binary: addedText === "-" || removedText === "-",
|
|
458
|
+
removed: Number.isInteger(removed) && removed >= 0 ? removed : 0
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
return stats;
|
|
462
|
+
}
|
|
463
|
+
function parseRawDiffMetadata(stdout) {
|
|
464
|
+
const metadata = /* @__PURE__ */ new Map();
|
|
465
|
+
if (String(stdout || "").includes("\0")) {
|
|
466
|
+
const fields = nulFields(stdout);
|
|
467
|
+
for (let index = 0; index < fields.length; ) {
|
|
468
|
+
const header = fields[index++] || "";
|
|
469
|
+
const match = header.match(/^:([0-7]{6}) ([0-7]{6}) ([0-9a-f]+) ([0-9a-f]+) ([A-Z][0-9]*)$/i);
|
|
470
|
+
if (!match) continue;
|
|
471
|
+
const status = match[5] || "";
|
|
472
|
+
const oldPath = fields[index++];
|
|
473
|
+
const filePath = status.startsWith("R") || status.startsWith("C") ? fields[index++] : oldPath;
|
|
474
|
+
if (!filePath) continue;
|
|
475
|
+
const fileMetadata = {};
|
|
476
|
+
if (match[1] !== "000000") fileMetadata.oldMode = match[1];
|
|
477
|
+
if (match[2] !== "000000") fileMetadata.newMode = match[2];
|
|
478
|
+
if (!isZeroObjectId(match[3])) fileMetadata.oldSha = match[3];
|
|
479
|
+
if (!isZeroObjectId(match[4])) fileMetadata.newSha = match[4];
|
|
480
|
+
metadata.set(filePath, fileMetadata);
|
|
481
|
+
}
|
|
482
|
+
return metadata;
|
|
483
|
+
}
|
|
484
|
+
for (const line of String(stdout || "").split("\n")) {
|
|
485
|
+
if (!line.trim()) continue;
|
|
486
|
+
const fields = line.split(" ");
|
|
487
|
+
const header = fields[0] || "";
|
|
488
|
+
const match = header.match(/^:([0-7]{6}) ([0-7]{6}) ([0-9a-f]+) ([0-9a-f]+) ([A-Z][0-9]*)$/i);
|
|
489
|
+
if (!match) continue;
|
|
490
|
+
const status = match[5] || "";
|
|
491
|
+
const path2 = status.startsWith("R") || status.startsWith("C") ? fields[2] : fields[1];
|
|
492
|
+
if (!path2) continue;
|
|
493
|
+
const fileMetadata = {};
|
|
494
|
+
if (match[1] !== "000000") fileMetadata.oldMode = match[1];
|
|
495
|
+
if (match[2] !== "000000") fileMetadata.newMode = match[2];
|
|
496
|
+
if (!isZeroObjectId(match[3])) fileMetadata.oldSha = match[3];
|
|
497
|
+
if (!isZeroObjectId(match[4])) fileMetadata.newSha = match[4];
|
|
498
|
+
metadata.set(path2, fileMetadata);
|
|
499
|
+
}
|
|
500
|
+
return metadata;
|
|
501
|
+
}
|
|
502
|
+
function patchDiffHeader(patch) {
|
|
503
|
+
const lines = String(patch || "").split("\n");
|
|
504
|
+
const header = [];
|
|
505
|
+
for (const line of lines) {
|
|
506
|
+
if (line.startsWith("@@ ")) break;
|
|
507
|
+
if (!line) continue;
|
|
508
|
+
header.push(line);
|
|
509
|
+
if (line === "GIT binary patch") break;
|
|
510
|
+
}
|
|
511
|
+
return header;
|
|
512
|
+
}
|
|
513
|
+
function isZeroObjectId(value) {
|
|
514
|
+
return /^0+$/.test(String(value || ""));
|
|
515
|
+
}
|
|
516
|
+
function patchMetadata(patchOrHeader) {
|
|
517
|
+
const header = Array.isArray(patchOrHeader) ? patchOrHeader : patchDiffHeader(patchOrHeader);
|
|
518
|
+
const metadata = {};
|
|
519
|
+
for (const line of header) {
|
|
520
|
+
const index = line.match(/^index ([0-9a-f]+)\.\.([0-9a-f]+)(?: ([0-7]{6}))?$/i);
|
|
521
|
+
if (index) {
|
|
522
|
+
if (!isZeroObjectId(index[1])) metadata.oldSha = index[1];
|
|
523
|
+
if (!isZeroObjectId(index[2])) metadata.newSha = index[2];
|
|
524
|
+
if (index[3]) {
|
|
525
|
+
metadata.oldMode = metadata.oldMode || index[3];
|
|
526
|
+
metadata.newMode = metadata.newMode || index[3];
|
|
527
|
+
}
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
const newFileMode = line.match(/^new file mode ([0-7]{6})$/);
|
|
531
|
+
if (newFileMode) {
|
|
532
|
+
metadata.newMode = newFileMode[1];
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
const deletedFileMode = line.match(/^deleted file mode ([0-7]{6})$/);
|
|
536
|
+
if (deletedFileMode) {
|
|
537
|
+
metadata.oldMode = deletedFileMode[1];
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
const oldMode = line.match(/^old mode ([0-7]{6})$/);
|
|
541
|
+
if (oldMode) {
|
|
542
|
+
metadata.oldMode = oldMode[1];
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
const newMode = line.match(/^new mode ([0-7]{6})$/);
|
|
546
|
+
if (newMode) metadata.newMode = newMode[1];
|
|
547
|
+
}
|
|
548
|
+
return metadata;
|
|
549
|
+
}
|
|
550
|
+
function fileFromPatch(change, patch) {
|
|
551
|
+
const hunks = (0, import_workspace_file_service.parseUnifiedDiffRows)(patch);
|
|
552
|
+
const totals = countRows(hunks);
|
|
553
|
+
const binary = /(^|\n)(Binary files? |GIT binary patch(?:\n|$))/.test(String(patch || ""));
|
|
554
|
+
const diffHeader = patchDiffHeader(patch);
|
|
555
|
+
const metadata = patchMetadata(diffHeader);
|
|
556
|
+
return {
|
|
557
|
+
added: totals.added,
|
|
558
|
+
...binary ? { binary: true } : {},
|
|
559
|
+
diff: {
|
|
560
|
+
...diffHeader.length ? { diffHeader } : {},
|
|
561
|
+
hunks
|
|
562
|
+
},
|
|
563
|
+
kind: change.kind,
|
|
564
|
+
...metadata,
|
|
565
|
+
path: change.path,
|
|
566
|
+
...change.previousPath ? { previousPath: change.previousPath } : {},
|
|
567
|
+
removed: totals.removed,
|
|
568
|
+
status: change.status || reviewStatus(change.kind)
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
function fileWithStats(file, stat) {
|
|
572
|
+
if (!stat) return file;
|
|
573
|
+
return {
|
|
574
|
+
...file,
|
|
575
|
+
added: stat.added,
|
|
576
|
+
...stat.binary === true ? { binary: true } : {},
|
|
577
|
+
removed: stat.removed
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
function gitDiffPathArgs(change) {
|
|
581
|
+
return change.previousPath ? [change.previousPath, change.path] : [change.path];
|
|
582
|
+
}
|
|
583
|
+
function gitDiffPathspecArgs(changes = []) {
|
|
584
|
+
const seen = /* @__PURE__ */ new Set();
|
|
585
|
+
const paths = [];
|
|
586
|
+
for (const change of changes) {
|
|
587
|
+
for (const filePath of gitDiffPathArgs(change)) {
|
|
588
|
+
if (!filePath || seen.has(filePath)) continue;
|
|
589
|
+
seen.add(filePath);
|
|
590
|
+
paths.push(filePath);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return paths;
|
|
594
|
+
}
|
|
595
|
+
class ReviewDiffService {
|
|
596
|
+
agentManager;
|
|
597
|
+
fileService;
|
|
598
|
+
constructor(agentManager, fileService) {
|
|
599
|
+
this.agentManager = agentManager;
|
|
600
|
+
this.fileService = fileService;
|
|
601
|
+
}
|
|
602
|
+
resolveWorkspace(agentId, requestedRoot) {
|
|
603
|
+
if (requestedRoot !== void 0) {
|
|
604
|
+
if (typeof requestedRoot !== "string" || !requestedRoot.trim() || typeof agentId === "string" && agentId.trim()) {
|
|
605
|
+
throw new import_workspace_file_service.WorkspaceFileError("exactly one review workspace target is required", 400);
|
|
606
|
+
}
|
|
607
|
+
let root2;
|
|
608
|
+
try {
|
|
609
|
+
root2 = fs.realpathSync.native(path.resolve(requestedRoot));
|
|
610
|
+
} catch {
|
|
611
|
+
throw new import_workspace_file_service.WorkspaceFileError("review workspace does not exist", 404);
|
|
612
|
+
}
|
|
613
|
+
if (!fs.statSync(root2).isDirectory()) throw new import_workspace_file_service.WorkspaceFileError("review workspace must be a directory", 400);
|
|
614
|
+
return root2;
|
|
615
|
+
}
|
|
616
|
+
if (typeof agentId !== "string" || !agentId.trim()) {
|
|
617
|
+
throw new import_workspace_file_service.WorkspaceFileError("review workspace target is required", 400);
|
|
618
|
+
}
|
|
619
|
+
const root = this.agentManager?.getAgentWorkspaceRoot?.(agentId);
|
|
620
|
+
if (!root) throw new import_workspace_file_service.WorkspaceFileError("agent not found", 404);
|
|
621
|
+
return root;
|
|
622
|
+
}
|
|
623
|
+
async git(root, args, options = {}) {
|
|
624
|
+
try {
|
|
625
|
+
return await this.fileService.execFile(this.fileService.gitPath, ["-C", root, ...args], {
|
|
626
|
+
cwd: root,
|
|
627
|
+
timeout: this.fileService.diffTimeoutMs,
|
|
628
|
+
maxBuffer: this.fileService.diffMaxBuffer,
|
|
629
|
+
...options
|
|
630
|
+
});
|
|
631
|
+
} catch (error) {
|
|
632
|
+
if (errorString(error, "code") === "ETIMEDOUT") {
|
|
633
|
+
throw new import_workspace_file_service.WorkspaceFileError("git comparison source request timed out", 504);
|
|
634
|
+
}
|
|
635
|
+
throw new import_workspace_file_service.WorkspaceFileError(
|
|
636
|
+
errorString(error, "stderr") || errorString(error, "message") || "git comparison sources could not be loaded",
|
|
637
|
+
500
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
async getComparisonSources(agentId, options = {}) {
|
|
642
|
+
const root = this.resolveWorkspace(agentId, options.root);
|
|
643
|
+
const [{ stdout: headOutput }, { stdout: indexOutput }, branchResult, logResult, statusResult] = await Promise.all([
|
|
644
|
+
this.git(root, ["rev-parse", "--verify", "HEAD"]),
|
|
645
|
+
this.git(root, ["write-tree"]),
|
|
646
|
+
this.git(root, ["symbolic-ref", "--quiet", "--short", "HEAD"]).catch(() => ({ stdout: "" })),
|
|
647
|
+
this.git(root, ["log", "-n", "12", "--format=%H%x1f%P%x1f%h%x1f%s%x1e", "HEAD"]),
|
|
648
|
+
Promise.all([
|
|
649
|
+
this.git(root, ["diff", "--name-only", "-z", "--"]),
|
|
650
|
+
this.git(root, ["diff", "--cached", "--name-only", "-z", "--"]),
|
|
651
|
+
this.git(root, ["ls-files", "--others", "--exclude-standard", "-z"])
|
|
652
|
+
])
|
|
653
|
+
]);
|
|
654
|
+
const head = String(headOutput || "").trim();
|
|
655
|
+
const indexTree = String(indexOutput || "").trim();
|
|
656
|
+
if (!/^[a-f0-9]{40,64}$/i.test(head) || !/^[a-f0-9]{40,64}$/i.test(indexTree)) {
|
|
657
|
+
throw new import_workspace_file_service.WorkspaceFileError("git comparison sources are invalid", 500);
|
|
658
|
+
}
|
|
659
|
+
const currentBranch = String(branchResult.stdout || "").trim();
|
|
660
|
+
const [unstagedResult, stagedResult, untrackedResult] = statusResult;
|
|
661
|
+
const refsResult = await this.git(root, [
|
|
662
|
+
"for-each-ref",
|
|
663
|
+
"--count=20",
|
|
664
|
+
"--sort=-committerdate",
|
|
665
|
+
"--format=%(refname:short)%00%(objectname)",
|
|
666
|
+
"refs/heads",
|
|
667
|
+
"refs/remotes"
|
|
668
|
+
]);
|
|
669
|
+
const branchRefs = parseComparisonBranches(refsResult.stdout, currentBranch);
|
|
670
|
+
const branches = [];
|
|
671
|
+
for (const branch of branchRefs) {
|
|
672
|
+
try {
|
|
673
|
+
const { stdout } = await this.git(root, ["merge-base", "HEAD", branch.id]);
|
|
674
|
+
const base = String(stdout || "").trim();
|
|
675
|
+
if (!/^[a-f0-9]{40,64}$/i.test(base) || base === head) continue;
|
|
676
|
+
branches.push({
|
|
677
|
+
base,
|
|
678
|
+
head,
|
|
679
|
+
id: `branch:${branch.name}`,
|
|
680
|
+
label: branch.name
|
|
681
|
+
});
|
|
682
|
+
} catch {
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return {
|
|
686
|
+
branches,
|
|
687
|
+
commits: parseComparisonCommits(logResult.stdout),
|
|
688
|
+
currentBranch: currentBranch || "Detached HEAD",
|
|
689
|
+
root,
|
|
690
|
+
staged: {
|
|
691
|
+
available: Boolean(String(stagedResult.stdout || "")),
|
|
692
|
+
base: head,
|
|
693
|
+
head: indexTree,
|
|
694
|
+
id: "staged",
|
|
695
|
+
label: "Staged"
|
|
696
|
+
},
|
|
697
|
+
unstaged: {
|
|
698
|
+
available: Boolean(String(unstagedResult.stdout || "") || String(untrackedResult.stdout || "")),
|
|
699
|
+
base: indexTree,
|
|
700
|
+
head: "now",
|
|
701
|
+
id: "unstaged",
|
|
702
|
+
label: "Unstaged"
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
async getWorkingCopyChanges(root, options, limit) {
|
|
707
|
+
const scope = normalizeWorkingCopyScope(options.scope);
|
|
708
|
+
const scanLimit = scope ? MAX_WORKING_COPY_SCAN_FILES : limit;
|
|
709
|
+
const changes = await this.fileService.changes(root, { limit: scanLimit });
|
|
710
|
+
const items = filterWorkingCopyChangeItems(root, assertUniqueReviewPaths(changes.items), options);
|
|
711
|
+
return {
|
|
712
|
+
items: items.slice(0, limit),
|
|
713
|
+
truncated: changes.truncated === true && (scope !== "tracked" || changes.items.at(-1)?.gitStatus !== "untracked") || items.length > limit
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
async getGitRangeChanges(root, base, head) {
|
|
717
|
+
try {
|
|
718
|
+
const { stdout } = await this.fileService.execFile(this.fileService.gitPath, [
|
|
719
|
+
"-C",
|
|
720
|
+
root,
|
|
721
|
+
"diff",
|
|
722
|
+
"--name-status",
|
|
723
|
+
"-z",
|
|
724
|
+
"--find-renames",
|
|
725
|
+
...gitRangeRevisionArgs(base, head),
|
|
726
|
+
"--"
|
|
727
|
+
], { cwd: root, timeout: this.fileService.diffTimeoutMs, maxBuffer: this.fileService.diffMaxBuffer });
|
|
728
|
+
const changes = parseNameStatus(stdout);
|
|
729
|
+
if (head !== "now") return changes;
|
|
730
|
+
const untracked = await this.fileService.execFile(this.fileService.gitPath, [
|
|
731
|
+
"-C",
|
|
732
|
+
root,
|
|
733
|
+
"ls-files",
|
|
734
|
+
"--others",
|
|
735
|
+
"--exclude-standard",
|
|
736
|
+
"-z"
|
|
737
|
+
], { cwd: root, timeout: this.fileService.diffTimeoutMs, maxBuffer: this.fileService.diffMaxBuffer });
|
|
738
|
+
return [...changes, ...nulFields(untracked.stdout).map((path2) => ({ gitStatus: "untracked", kind: "added", path: path2, status: "A" }))];
|
|
739
|
+
} catch (error) {
|
|
740
|
+
if (errorString(error, "code") === "ETIMEDOUT") {
|
|
741
|
+
throw new import_workspace_file_service.WorkspaceFileError("git diff timed out", 504);
|
|
742
|
+
}
|
|
743
|
+
throw new import_workspace_file_service.WorkspaceFileError(
|
|
744
|
+
errorString(error, "stderr") || errorString(error, "message") || "git diff failed",
|
|
745
|
+
500
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
async getCommitSummary(root, revision) {
|
|
750
|
+
if (!isSafeGitRevision(revision) || revision === "now") return null;
|
|
751
|
+
try {
|
|
752
|
+
const { stdout } = await this.fileService.execFile(this.fileService.gitPath, [
|
|
753
|
+
"-C",
|
|
754
|
+
root,
|
|
755
|
+
"show",
|
|
756
|
+
"-s",
|
|
757
|
+
"--format=%H%x1f%an%x1f%ae%x1f%aI%x1f%B",
|
|
758
|
+
revision
|
|
759
|
+
], { cwd: root, timeout: this.fileService.diffTimeoutMs, maxBuffer: this.fileService.diffMaxBuffer });
|
|
760
|
+
const [id, authorName, authorEmail, authoredAt, ...messageParts] = String(stdout || "").split("");
|
|
761
|
+
const message = messageParts.join("").trim();
|
|
762
|
+
if (!/^[a-f0-9]{40,64}$/i.test(id || "") || !message) return null;
|
|
763
|
+
return { authoredAt: String(authoredAt || "").trim(), authorEmail: String(authorEmail || "").trim(), authorName: String(authorName || "").trim(), id, message };
|
|
764
|
+
} catch {
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
async getComparison(root, base, head) {
|
|
769
|
+
const [baseCommit, headCommit] = await Promise.all([
|
|
770
|
+
this.getCommitSummary(root, base),
|
|
771
|
+
head === "now" ? Promise.resolve(null) : this.getCommitSummary(root, head)
|
|
772
|
+
]);
|
|
773
|
+
if (!baseCommit && !headCommit) return null;
|
|
774
|
+
return {
|
|
775
|
+
...baseCommit ? { base: baseCommit } : {},
|
|
776
|
+
...headCommit ? { head: headCommit } : {},
|
|
777
|
+
workingTree: head === "now"
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
async readGitTextFile(root, revision, filePath) {
|
|
781
|
+
try {
|
|
782
|
+
const { stdout } = await this.fileService.execFile(this.fileService.gitPath, [
|
|
783
|
+
"-C",
|
|
784
|
+
root,
|
|
785
|
+
"show",
|
|
786
|
+
`${revision}:${filePath}`
|
|
787
|
+
], { cwd: root, encoding: "buffer", timeout: this.fileService.diffTimeoutMs, maxBuffer: this.fileService.diffMaxBuffer });
|
|
788
|
+
const buffer = Buffer.isBuffer(stdout) ? stdout : Buffer.from(String(stdout || ""));
|
|
789
|
+
if (buffer.includes(0)) throw new import_workspace_file_service.WorkspaceFileError("binary files do not have expandable text context", 415);
|
|
790
|
+
return textLines(buffer.toString("utf8"));
|
|
791
|
+
} catch (error) {
|
|
792
|
+
if (error instanceof import_workspace_file_service.WorkspaceFileError) throw error;
|
|
793
|
+
if (errorString(error, "code") === "ETIMEDOUT") {
|
|
794
|
+
throw new import_workspace_file_service.WorkspaceFileError("git show timed out", 504);
|
|
795
|
+
}
|
|
796
|
+
throw new import_workspace_file_service.WorkspaceFileError(
|
|
797
|
+
errorString(error, "stderr") || errorString(error, "message") || "git file content could not be loaded",
|
|
798
|
+
500
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
async readWorkingTreeTextFile(root, filePath) {
|
|
803
|
+
try {
|
|
804
|
+
const resolved = await this.fileService.resolvePath(root, filePath);
|
|
805
|
+
const buffer = await fs.promises.readFile(resolved.target);
|
|
806
|
+
if (buffer.includes(0)) throw new import_workspace_file_service.WorkspaceFileError("binary files do not have expandable text context", 415);
|
|
807
|
+
return textLines(buffer.toString("utf8"));
|
|
808
|
+
} catch (error) {
|
|
809
|
+
if (error instanceof import_workspace_file_service.WorkspaceFileError) throw error;
|
|
810
|
+
throw new import_workspace_file_service.WorkspaceFileError(
|
|
811
|
+
errorString(error, "message") || "working tree file content could not be loaded",
|
|
812
|
+
500
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
async getWorkingCopyTextSources(root, change, source) {
|
|
817
|
+
const kind = reviewKind(change.gitStatus);
|
|
818
|
+
const leftLines = kind === "added" ? [] : textLines(source.originalContent);
|
|
819
|
+
const rightLines = kind === "deleted" ? [] : textLines(source.modifiedContent);
|
|
820
|
+
return {
|
|
821
|
+
leftLines,
|
|
822
|
+
leftName: change.previousPath || change.path,
|
|
823
|
+
rightLines,
|
|
824
|
+
rightName: change.path
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
async getGitRangeTextSources(root, base, head, change) {
|
|
828
|
+
const kind = change.kind || reviewKind(change.gitStatus);
|
|
829
|
+
const leftName = change.previousPath || change.path;
|
|
830
|
+
const [leftLines, rightLines] = await Promise.all([
|
|
831
|
+
kind === "added" ? Promise.resolve([]) : this.readGitTextFile(root, base, leftName),
|
|
832
|
+
kind === "deleted" ? Promise.resolve([]) : head === "now" ? this.readWorkingTreeTextFile(root, change.path) : this.readGitTextFile(root, head, change.path)
|
|
833
|
+
]);
|
|
834
|
+
return { leftLines, leftName, rightLines, rightName: change.path };
|
|
835
|
+
}
|
|
836
|
+
async getWorkingCopyFileContext(agentId, filePath, options = {}) {
|
|
837
|
+
const root = this.resolveWorkspace(agentId, options.root);
|
|
838
|
+
if (!isSafeReviewPath(filePath)) throw new import_workspace_file_service.WorkspaceFileError("file path is required", 400);
|
|
839
|
+
const changes = await this.getWorkingCopyChanges(root, options, MAX_REVIEW_FILES);
|
|
840
|
+
const change = changes.items.find((item) => item.path === filePath);
|
|
841
|
+
if (!change) throw new import_workspace_file_service.WorkspaceFileError("review file not found", 404);
|
|
842
|
+
if (change.gitStatus === "untracked") throw new import_workspace_file_service.WorkspaceFileError("untracked files do not have common diff context", 400);
|
|
843
|
+
const source = await this.fileService.diff(root, change.path);
|
|
844
|
+
if (source.binary === true) throw new import_workspace_file_service.WorkspaceFileError("binary files do not have expandable text context", 415);
|
|
845
|
+
return contextRowsFromSources(await this.getWorkingCopyTextSources(root, change, source), options);
|
|
846
|
+
}
|
|
847
|
+
async getGitRangeFileContext(agentId, options = {}) {
|
|
848
|
+
const root = this.resolveWorkspace(agentId, options.root);
|
|
849
|
+
const base = String(options.base || "").trim();
|
|
850
|
+
const head = String(options.head || "").trim();
|
|
851
|
+
const filePath = typeof options.path === "string" ? options.path : "";
|
|
852
|
+
if (!isSafeGitRevision(base) || !isReviewHead(head)) {
|
|
853
|
+
throw new import_workspace_file_service.WorkspaceFileError("base and head revisions are required", 400);
|
|
854
|
+
}
|
|
855
|
+
if (!isSafeReviewPath(filePath)) throw new import_workspace_file_service.WorkspaceFileError("file path is required", 400);
|
|
856
|
+
const changes = assertUniqueReviewPaths(await this.getGitRangeChanges(root, base, head));
|
|
857
|
+
const change = changes.find((item) => item.path === filePath);
|
|
858
|
+
if (!change) throw new import_workspace_file_service.WorkspaceFileError("review file not found", 404);
|
|
859
|
+
if (change.gitStatus === "untracked") throw new import_workspace_file_service.WorkspaceFileError("untracked files do not have common diff context", 400);
|
|
860
|
+
return contextRowsFromSources(await this.getGitRangeTextSources(root, base, head, change), options);
|
|
861
|
+
}
|
|
862
|
+
async getGitRangeUntrackedFile(root, change, metadataOnly) {
|
|
863
|
+
const source = await this.fileService.diff(root, change.path);
|
|
864
|
+
const hunks = untrackedHunks(source.modifiedContent);
|
|
865
|
+
const totals = countRows(hunks);
|
|
866
|
+
const truncated = source.truncated === true || untrackedContentTooLarge(source.modifiedContent);
|
|
867
|
+
const metadata = patchMetadata(patchDiffHeader(untrackedPatch(change.path, source.modifiedContent)));
|
|
868
|
+
if (metadataOnly) {
|
|
869
|
+
return metadataFile(change, {
|
|
870
|
+
added: totals.added,
|
|
871
|
+
binary: source.binary === true,
|
|
872
|
+
diffTooExpensive: truncated,
|
|
873
|
+
...metadata,
|
|
874
|
+
removed: totals.removed,
|
|
875
|
+
size: source.size,
|
|
876
|
+
sizeDelta: source.sizeDelta,
|
|
877
|
+
truncated
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
return {
|
|
881
|
+
added: totals.added,
|
|
882
|
+
...source.binary === true ? { binary: true } : {},
|
|
883
|
+
diff: {
|
|
884
|
+
diffHeader: patchDiffHeader(untrackedPatch(change.path, source.modifiedContent)),
|
|
885
|
+
hunks,
|
|
886
|
+
truncated
|
|
887
|
+
},
|
|
888
|
+
...truncated ? { diffTooExpensive: true } : {},
|
|
889
|
+
kind: "added",
|
|
890
|
+
...metadata,
|
|
891
|
+
path: change.path,
|
|
892
|
+
removed: totals.removed,
|
|
893
|
+
...Number.isInteger(source.size) ? { size: source.size } : {},
|
|
894
|
+
...Number.isInteger(source.sizeDelta) ? { sizeDelta: source.sizeDelta } : {},
|
|
895
|
+
status: "A"
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
async getWorkingCopy(agentId, options = {}) {
|
|
899
|
+
const root = this.resolveWorkspace(agentId, options.root);
|
|
900
|
+
const limit = normalizeReviewLimit(options.limit);
|
|
901
|
+
const metadataOnly = metadataOnlyOption(options.metadataOnly);
|
|
902
|
+
const contentOptions = diffContentOptions(options);
|
|
903
|
+
const changes = await this.getWorkingCopyChanges(root, options, limit);
|
|
904
|
+
const changeItems = changes.items;
|
|
905
|
+
const files = await mapWithConcurrency(changeItems, async (change) => {
|
|
906
|
+
const source = await this.fileService.diff(root, change.path);
|
|
907
|
+
const diffSource = !metadataOnly && hasOptions(contentOptions) ? await this.fileService.diff(root, change.path, contentOptions) : source;
|
|
908
|
+
const isUntracked = change.gitStatus === "untracked";
|
|
909
|
+
const metadataHunks = isUntracked ? untrackedHunks(source.modifiedContent) : (0, import_workspace_file_service.parseUnifiedDiffRows)(source.patch);
|
|
910
|
+
const hunks = isUntracked ? metadataHunks : (0, import_workspace_file_service.parseUnifiedDiffRows)(diffSource.patch);
|
|
911
|
+
const totals = countRows(metadataHunks);
|
|
912
|
+
const kind = reviewKind(change.gitStatus);
|
|
913
|
+
const diffHeader = patchDiffHeader(isUntracked ? untrackedPatch(change.path, source.modifiedContent) : diffSource.patch || source.patch);
|
|
914
|
+
const metadata = patchMetadata(diffHeader);
|
|
915
|
+
const untrackedTooLarge = isUntracked && untrackedContentTooLarge(source.modifiedContent);
|
|
916
|
+
if (metadataOnly) {
|
|
917
|
+
return metadataFile({ ...change, kind }, {
|
|
918
|
+
added: totals.added,
|
|
919
|
+
binary: source.binary === true,
|
|
920
|
+
diffTooExpensive: source.truncated === true || untrackedTooLarge,
|
|
921
|
+
...metadata,
|
|
922
|
+
removed: totals.removed,
|
|
923
|
+
size: source.size,
|
|
924
|
+
sizeDelta: source.sizeDelta,
|
|
925
|
+
truncated: source.truncated === true || untrackedTooLarge
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
return {
|
|
929
|
+
added: totals.added,
|
|
930
|
+
...source.binary === true ? { binary: true } : {},
|
|
931
|
+
diff: {
|
|
932
|
+
...diffHeader.length ? { diffHeader } : {},
|
|
933
|
+
hunks,
|
|
934
|
+
truncated: source.truncated === true || diffSource.truncated === true || untrackedTooLarge
|
|
935
|
+
},
|
|
936
|
+
...source.truncated === true || diffSource.truncated === true || untrackedTooLarge ? { diffTooExpensive: true } : {},
|
|
937
|
+
kind,
|
|
938
|
+
...metadata,
|
|
939
|
+
path: change.path,
|
|
940
|
+
...change.previousPath ? { previousPath: change.previousPath } : {},
|
|
941
|
+
removed: totals.removed,
|
|
942
|
+
...Number.isInteger(source.size) ? { size: source.size } : {},
|
|
943
|
+
...Number.isInteger(source.sizeDelta) ? { sizeDelta: source.sizeDelta } : {},
|
|
944
|
+
status: reviewStatus(kind)
|
|
945
|
+
};
|
|
946
|
+
});
|
|
947
|
+
const comparison = await this.getComparison(root, "HEAD", "now");
|
|
948
|
+
return {
|
|
949
|
+
basePatchset: "HEAD",
|
|
950
|
+
...comparison ? { comparison } : {},
|
|
951
|
+
files,
|
|
952
|
+
isGitRepo: true,
|
|
953
|
+
patchset: workingCopyPatchset(files),
|
|
954
|
+
reviewId: workingCopyReviewId(root, options),
|
|
955
|
+
root,
|
|
956
|
+
source: "working-copy",
|
|
957
|
+
truncated: changes.truncated
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
async getWorkingCopyFile(agentId, filePath, options = {}) {
|
|
961
|
+
const root = this.resolveWorkspace(agentId, options.root);
|
|
962
|
+
const contentOptions = diffContentOptions(options);
|
|
963
|
+
if (!isSafeReviewPath(filePath)) throw new import_workspace_file_service.WorkspaceFileError("file path is required", 400);
|
|
964
|
+
const changes = await this.getWorkingCopyChanges(root, options, MAX_REVIEW_FILES);
|
|
965
|
+
const changeItems = changes.items;
|
|
966
|
+
const change = changeItems.find((item) => item.path === filePath);
|
|
967
|
+
if (!change) throw new import_workspace_file_service.WorkspaceFileError("review file not found", 404);
|
|
968
|
+
const source = await this.fileService.diff(root, change.path);
|
|
969
|
+
const diffSource = hasOptions(contentOptions) ? await this.fileService.diff(root, change.path, contentOptions) : source;
|
|
970
|
+
const isUntracked = change.gitStatus === "untracked";
|
|
971
|
+
const metadataHunks = isUntracked ? untrackedHunks(source.modifiedContent) : (0, import_workspace_file_service.parseUnifiedDiffRows)(source.patch);
|
|
972
|
+
const hunks = isUntracked ? metadataHunks : (0, import_workspace_file_service.parseUnifiedDiffRows)(diffSource.patch);
|
|
973
|
+
const totals = countRows(metadataHunks);
|
|
974
|
+
const kind = reviewKind(change.gitStatus);
|
|
975
|
+
const diffHeader = patchDiffHeader(isUntracked ? untrackedPatch(change.path, source.modifiedContent) : diffSource.patch || source.patch);
|
|
976
|
+
const metadata = patchMetadata(diffHeader);
|
|
977
|
+
const untrackedTooLarge = isUntracked && untrackedContentTooLarge(source.modifiedContent);
|
|
978
|
+
const textSources = options.fileMeta === true && !isUntracked && source.binary !== true && typeof source.originalContent === "string" && typeof source.modifiedContent === "string" ? await this.getWorkingCopyTextSources(root, change, source) : null;
|
|
979
|
+
return {
|
|
980
|
+
added: totals.added,
|
|
981
|
+
...source.binary === true ? { binary: true } : {},
|
|
982
|
+
diff: {
|
|
983
|
+
...diffHeader.length ? { diffHeader } : {},
|
|
984
|
+
hunks,
|
|
985
|
+
...textSources ? { leftMeta: reviewTextFileMeta(textSources.leftName, textSources.leftLines) } : {},
|
|
986
|
+
...textSources ? { rightMeta: reviewTextFileMeta(textSources.rightName, textSources.rightLines) } : {},
|
|
987
|
+
truncated: source.truncated === true || diffSource.truncated === true || untrackedTooLarge
|
|
988
|
+
},
|
|
989
|
+
...source.truncated === true || diffSource.truncated === true || untrackedTooLarge ? { diffTooExpensive: true } : {},
|
|
990
|
+
kind,
|
|
991
|
+
...metadata,
|
|
992
|
+
path: change.path,
|
|
993
|
+
...change.previousPath ? { previousPath: change.previousPath } : {},
|
|
994
|
+
removed: totals.removed,
|
|
995
|
+
...Number.isInteger(source.size) ? { size: source.size } : {},
|
|
996
|
+
...Number.isInteger(source.sizeDelta) ? { sizeDelta: source.sizeDelta } : {},
|
|
997
|
+
status: reviewStatus(kind)
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
async getWorkingCopyPatch(agentId, options = {}) {
|
|
1001
|
+
const root = this.resolveWorkspace(agentId, options.root);
|
|
1002
|
+
const limit = normalizeReviewLimit(options.limit);
|
|
1003
|
+
const contentOptions = diffContentOptions(options);
|
|
1004
|
+
const changes = await this.getWorkingCopyChanges(root, options, limit);
|
|
1005
|
+
const changeItems = changes.items;
|
|
1006
|
+
const patchResults = await mapWithConcurrency(changeItems, async (change) => {
|
|
1007
|
+
const source = await this.fileService.diff(root, change.path, contentOptions);
|
|
1008
|
+
const untrackedTooLarge = change.gitStatus === "untracked" && untrackedContentTooLarge(source.modifiedContent);
|
|
1009
|
+
return {
|
|
1010
|
+
patch: change.gitStatus === "untracked" ? untrackedPatch(change.path, source.modifiedContent) : String(source.patch || ""),
|
|
1011
|
+
truncated: source.truncated === true || untrackedTooLarge
|
|
1012
|
+
};
|
|
1013
|
+
});
|
|
1014
|
+
return {
|
|
1015
|
+
patch: patchResults.map((result) => result.patch).filter(Boolean).join("\n"),
|
|
1016
|
+
truncated: changes.truncated || patchResults.some((result) => result.truncated)
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
async getGitRange(agentId, options = {}) {
|
|
1020
|
+
const root = this.resolveWorkspace(agentId, options.root);
|
|
1021
|
+
const base = String(options.base || "").trim();
|
|
1022
|
+
const head = String(options.head || "").trim();
|
|
1023
|
+
if (!isSafeGitRevision(base) || !isReviewHead(head)) {
|
|
1024
|
+
throw new import_workspace_file_service.WorkspaceFileError("base and head revisions are required", 400);
|
|
1025
|
+
}
|
|
1026
|
+
const limit = normalizeReviewLimit(options.limit);
|
|
1027
|
+
const ignoreWhitespace = normalizeIgnoreWhitespace(options.ignoreWhitespace);
|
|
1028
|
+
const context = normalizeDiffContext(options.context);
|
|
1029
|
+
const changes = await this.getGitRangeChanges(root, base, head);
|
|
1030
|
+
const comparisonPromise = this.getComparison(root, base, head);
|
|
1031
|
+
const selected = assertUniqueReviewPaths(changes.slice(0, limit));
|
|
1032
|
+
const lineStats = ignoreWhitespace !== "NONE" && !metadataOnlyOption(options.metadataOnly) ? await this.getGitRangeNumstat(root, base, head, ignoreWhitespace, selected) : null;
|
|
1033
|
+
if (metadataOnlyOption(options.metadataOnly)) {
|
|
1034
|
+
const stats = await this.getGitRangeNumstat(root, base, head, "NONE", selected);
|
|
1035
|
+
const rawMetadata = await this.getGitRangeRawMetadata(root, base, head, selected);
|
|
1036
|
+
const files2 = await mapWithConcurrency(selected, async (change) => {
|
|
1037
|
+
if (change.gitStatus === "untracked") return this.getGitRangeUntrackedFile(root, change, true);
|
|
1038
|
+
return metadataFile(change, {
|
|
1039
|
+
...stats.get(change.path),
|
|
1040
|
+
...rawMetadata.get(change.path)
|
|
1041
|
+
});
|
|
1042
|
+
});
|
|
1043
|
+
const comparison2 = await comparisonPromise;
|
|
1044
|
+
return {
|
|
1045
|
+
basePatchset: base,
|
|
1046
|
+
...comparison2 ? { comparison: comparison2 } : {},
|
|
1047
|
+
files: files2,
|
|
1048
|
+
isGitRepo: true,
|
|
1049
|
+
patchset: head,
|
|
1050
|
+
reviewId: options.reviewId || gitRangeReviewId(root, base, head),
|
|
1051
|
+
root,
|
|
1052
|
+
source: "git-range",
|
|
1053
|
+
truncated: changes.length > limit
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
const files = await mapWithConcurrency(selected, async (change) => {
|
|
1057
|
+
if (change.gitStatus === "untracked") return this.getGitRangeUntrackedFile(root, change, false);
|
|
1058
|
+
try {
|
|
1059
|
+
const { stdout } = await this.fileService.execFile(this.fileService.gitPath, [
|
|
1060
|
+
"-C",
|
|
1061
|
+
root,
|
|
1062
|
+
"diff",
|
|
1063
|
+
"--find-renames",
|
|
1064
|
+
...gitWhitespaceArgs(ignoreWhitespace),
|
|
1065
|
+
...gitContextArgs(context),
|
|
1066
|
+
...gitRangeRevisionArgs(base, head),
|
|
1067
|
+
"--",
|
|
1068
|
+
...gitDiffPathArgs(change)
|
|
1069
|
+
], { cwd: root, timeout: this.fileService.diffTimeoutMs, maxBuffer: this.fileService.diffMaxBuffer });
|
|
1070
|
+
const file = fileFromPatch(change, stdout);
|
|
1071
|
+
const stat = lineStats?.get(change.path);
|
|
1072
|
+
return fileWithStats(file, stat);
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
if (errorString(error, "code") === "ETIMEDOUT") {
|
|
1075
|
+
throw new import_workspace_file_service.WorkspaceFileError("git diff timed out", 504);
|
|
1076
|
+
}
|
|
1077
|
+
throw new import_workspace_file_service.WorkspaceFileError(
|
|
1078
|
+
errorString(error, "stderr") || errorString(error, "message") || "git diff failed",
|
|
1079
|
+
500
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
const comparison = await comparisonPromise;
|
|
1084
|
+
return {
|
|
1085
|
+
basePatchset: base,
|
|
1086
|
+
...comparison ? { comparison } : {},
|
|
1087
|
+
files,
|
|
1088
|
+
isGitRepo: true,
|
|
1089
|
+
patchset: head,
|
|
1090
|
+
reviewId: options.reviewId || gitRangeReviewId(root, base, head),
|
|
1091
|
+
root,
|
|
1092
|
+
source: "git-range",
|
|
1093
|
+
truncated: changes.length > limit
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
async getGitRangeFile(agentId, options = {}) {
|
|
1097
|
+
const root = this.resolveWorkspace(agentId, options.root);
|
|
1098
|
+
const base = String(options.base || "").trim();
|
|
1099
|
+
const head = String(options.head || "").trim();
|
|
1100
|
+
const path2 = typeof options.path === "string" ? options.path : "";
|
|
1101
|
+
const ignoreWhitespace = normalizeIgnoreWhitespace(options.ignoreWhitespace);
|
|
1102
|
+
const context = normalizeDiffContext(options.context);
|
|
1103
|
+
if (!isSafeGitRevision(base) || !isReviewHead(head)) {
|
|
1104
|
+
throw new import_workspace_file_service.WorkspaceFileError("base and head revisions are required", 400);
|
|
1105
|
+
}
|
|
1106
|
+
if (!isSafeReviewPath(path2)) throw new import_workspace_file_service.WorkspaceFileError("file path is required", 400);
|
|
1107
|
+
const changes = await this.getGitRangeChanges(root, base, head);
|
|
1108
|
+
const uniqueChanges = assertUniqueReviewPaths(changes);
|
|
1109
|
+
const change = uniqueChanges.find((item) => item.path === path2);
|
|
1110
|
+
if (!change) throw new import_workspace_file_service.WorkspaceFileError("review file not found", 404);
|
|
1111
|
+
if (change.gitStatus === "untracked") return this.getGitRangeUntrackedFile(root, change, false);
|
|
1112
|
+
const stats = ignoreWhitespace !== "NONE" ? await this.getGitRangeNumstat(root, base, head, ignoreWhitespace, [change]) : null;
|
|
1113
|
+
try {
|
|
1114
|
+
const { stdout } = await this.fileService.execFile(this.fileService.gitPath, [
|
|
1115
|
+
"-C",
|
|
1116
|
+
root,
|
|
1117
|
+
"diff",
|
|
1118
|
+
"--find-renames",
|
|
1119
|
+
...gitWhitespaceArgs(ignoreWhitespace),
|
|
1120
|
+
...gitContextArgs(context),
|
|
1121
|
+
...gitRangeRevisionArgs(base, head),
|
|
1122
|
+
"--",
|
|
1123
|
+
...gitDiffPathArgs(change)
|
|
1124
|
+
], { cwd: root, timeout: this.fileService.diffTimeoutMs, maxBuffer: this.fileService.diffMaxBuffer });
|
|
1125
|
+
const file = fileFromPatch(change, stdout);
|
|
1126
|
+
if (options.fileMeta === true && file.binary !== true) {
|
|
1127
|
+
const textSources = await this.getGitRangeTextSources(root, base, head, change);
|
|
1128
|
+
file.diff = {
|
|
1129
|
+
...file.diff,
|
|
1130
|
+
leftMeta: reviewTextFileMeta(textSources.leftName, textSources.leftLines),
|
|
1131
|
+
rightMeta: reviewTextFileMeta(textSources.rightName, textSources.rightLines)
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
const stat = stats?.get(change.path);
|
|
1135
|
+
return fileWithStats(file, stat);
|
|
1136
|
+
} catch (error) {
|
|
1137
|
+
if (errorString(error, "code") === "ETIMEDOUT") {
|
|
1138
|
+
throw new import_workspace_file_service.WorkspaceFileError("git diff timed out", 504);
|
|
1139
|
+
}
|
|
1140
|
+
throw new import_workspace_file_service.WorkspaceFileError(
|
|
1141
|
+
errorString(error, "stderr") || errorString(error, "message") || "git diff failed",
|
|
1142
|
+
500
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
async getGitRangePatch(agentId, options = {}) {
|
|
1147
|
+
const root = this.resolveWorkspace(agentId, options.root);
|
|
1148
|
+
const base = String(options.base || "").trim();
|
|
1149
|
+
const head = String(options.head || "").trim();
|
|
1150
|
+
if (!isSafeGitRevision(base) || !isReviewHead(head)) {
|
|
1151
|
+
throw new import_workspace_file_service.WorkspaceFileError("base and head revisions are required", 400);
|
|
1152
|
+
}
|
|
1153
|
+
const limit = normalizeReviewLimit(options.limit);
|
|
1154
|
+
const ignoreWhitespace = normalizeIgnoreWhitespace(options.ignoreWhitespace);
|
|
1155
|
+
const context = normalizeDiffContext(options.context);
|
|
1156
|
+
const changes = await this.getGitRangeChanges(root, base, head);
|
|
1157
|
+
const selected = assertUniqueReviewPaths(changes.slice(0, limit));
|
|
1158
|
+
const patches = await mapWithConcurrency(selected, async (change) => {
|
|
1159
|
+
if (change.gitStatus === "untracked") {
|
|
1160
|
+
const source = await this.fileService.diff(root, change.path);
|
|
1161
|
+
return untrackedPatch(change.path, source.modifiedContent);
|
|
1162
|
+
}
|
|
1163
|
+
try {
|
|
1164
|
+
const { stdout } = await this.fileService.execFile(this.fileService.gitPath, [
|
|
1165
|
+
"-C",
|
|
1166
|
+
root,
|
|
1167
|
+
"diff",
|
|
1168
|
+
"--find-renames",
|
|
1169
|
+
...gitWhitespaceArgs(ignoreWhitespace),
|
|
1170
|
+
...gitContextArgs(context),
|
|
1171
|
+
...gitRangeRevisionArgs(base, head),
|
|
1172
|
+
"--",
|
|
1173
|
+
...gitDiffPathArgs(change)
|
|
1174
|
+
], { cwd: root, timeout: this.fileService.diffTimeoutMs, maxBuffer: this.fileService.diffMaxBuffer });
|
|
1175
|
+
return stdout;
|
|
1176
|
+
} catch (error) {
|
|
1177
|
+
if (errorString(error, "code") === "ETIMEDOUT") {
|
|
1178
|
+
throw new import_workspace_file_service.WorkspaceFileError("git diff timed out", 504);
|
|
1179
|
+
}
|
|
1180
|
+
throw new import_workspace_file_service.WorkspaceFileError(
|
|
1181
|
+
errorString(error, "stderr") || errorString(error, "message") || "git diff failed",
|
|
1182
|
+
500
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
return {
|
|
1187
|
+
patch: patches.filter(Boolean).join("\n"),
|
|
1188
|
+
truncated: changes.length > limit
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
async getGitRangeNumstat(root, base, head, ignoreWhitespace = "NONE", changes = []) {
|
|
1192
|
+
try {
|
|
1193
|
+
const { stdout } = await this.fileService.execFile(this.fileService.gitPath, [
|
|
1194
|
+
"-C",
|
|
1195
|
+
root,
|
|
1196
|
+
"diff",
|
|
1197
|
+
"--numstat",
|
|
1198
|
+
"-z",
|
|
1199
|
+
"--find-renames",
|
|
1200
|
+
...gitWhitespaceArgs(ignoreWhitespace),
|
|
1201
|
+
...gitRangeRevisionArgs(base, head),
|
|
1202
|
+
"--",
|
|
1203
|
+
...gitDiffPathspecArgs(changes)
|
|
1204
|
+
], { cwd: root, timeout: this.fileService.diffTimeoutMs, maxBuffer: this.fileService.diffMaxBuffer });
|
|
1205
|
+
return parseNumstat(stdout, changes);
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
if (errorString(error, "code") === "ETIMEDOUT") {
|
|
1208
|
+
throw new import_workspace_file_service.WorkspaceFileError("git diff timed out", 504);
|
|
1209
|
+
}
|
|
1210
|
+
throw new import_workspace_file_service.WorkspaceFileError(
|
|
1211
|
+
errorString(error, "stderr") || errorString(error, "message") || "git diff failed",
|
|
1212
|
+
500
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
async getGitRangeRawMetadata(root, base, head, changes = []) {
|
|
1217
|
+
try {
|
|
1218
|
+
const { stdout } = await this.fileService.execFile(this.fileService.gitPath, [
|
|
1219
|
+
"-C",
|
|
1220
|
+
root,
|
|
1221
|
+
"diff",
|
|
1222
|
+
"--raw",
|
|
1223
|
+
"-z",
|
|
1224
|
+
"--find-renames",
|
|
1225
|
+
...gitRangeRevisionArgs(base, head),
|
|
1226
|
+
"--",
|
|
1227
|
+
...gitDiffPathspecArgs(changes)
|
|
1228
|
+
], { cwd: root, timeout: this.fileService.diffTimeoutMs, maxBuffer: this.fileService.diffMaxBuffer });
|
|
1229
|
+
return parseRawDiffMetadata(stdout);
|
|
1230
|
+
} catch (error) {
|
|
1231
|
+
if (errorString(error, "code") === "ETIMEDOUT") {
|
|
1232
|
+
throw new import_workspace_file_service.WorkspaceFileError("git diff timed out", 504);
|
|
1233
|
+
}
|
|
1234
|
+
throw new import_workspace_file_service.WorkspaceFileError(
|
|
1235
|
+
errorString(error, "stderr") || errorString(error, "message") || "git diff failed",
|
|
1236
|
+
500
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1242
|
+
0 && (module.exports = {
|
|
1243
|
+
ReviewDiffService,
|
|
1244
|
+
fileFromPatch,
|
|
1245
|
+
filterWorkingCopyChangeItems,
|
|
1246
|
+
gitContextArgs,
|
|
1247
|
+
gitDiffPathspecArgs,
|
|
1248
|
+
gitRangeReviewId,
|
|
1249
|
+
gitWhitespaceArgs,
|
|
1250
|
+
metadataFile,
|
|
1251
|
+
normalizeDiffContext,
|
|
1252
|
+
normalizeIgnoreWhitespace,
|
|
1253
|
+
normalizeModifiedWithinDays,
|
|
1254
|
+
normalizeReviewLimit,
|
|
1255
|
+
normalizeWorkingCopyScope,
|
|
1256
|
+
parseComparisonBranches,
|
|
1257
|
+
parseComparisonCommits,
|
|
1258
|
+
parseNameStatus,
|
|
1259
|
+
parseNumstat,
|
|
1260
|
+
parseRawDiffMetadata,
|
|
1261
|
+
patchDiffHeader,
|
|
1262
|
+
patchMetadata,
|
|
1263
|
+
untrackedHunks,
|
|
1264
|
+
untrackedPatch,
|
|
1265
|
+
workingCopyPatchset,
|
|
1266
|
+
workingCopyReviewId
|
|
1267
|
+
});
|