diffing 0.10.0 → 0.10.2
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/{cli-agent-CLrK54k2.mjs → cli-agent-CV0k1pXp.mjs} +10 -8
- package/dist/cli-gh-C2LAp12S.mjs +313 -0
- package/dist/cli.mjs +3 -3
- package/dist/client/assets/{angular-html-DdAR2u-k.js → angular-html-CV0Ak818.js} +1 -1
- package/dist/client/assets/{angular-ts-Dz_vWKSu.js → angular-ts-2e8zQ7to.js} +1 -1
- package/dist/client/assets/{apl-BJ5QXrIk.js → apl-ByVy2dfo.js} +1 -1
- package/dist/client/assets/{arc-D9erSW6x.js → arc--R8kldOB.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-CCQWZLXR.js → architectureDiagram-3BPJPVTR-BWbm1lrV.js} +1 -1
- package/dist/client/assets/{astro-BQAej5a5.js → astro-CE7ySGnq.js} +1 -1
- package/dist/client/assets/{blade-DkAmuwDx.js → blade-PJJaH6RK.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-vwjxNybI.js → blockDiagram-GPEHLZMM-BdfBhnvZ.js} +1 -1
- package/dist/client/assets/{c-DG5p-ChJ.js → c-BhDzEkUq.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-DJ_hVlh4.js → c4Diagram-AAUBKEIU-Bu_tfvPY.js} +1 -1
- package/dist/client/assets/channel-DSEnsd3l.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-CiLowxCo.js → chunk-2J33WTMH-CTGsTtve.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-H3jjMR5Y.js → chunk-3OPIFGDE-BODO_5hx.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-Dk0dk4n5.js → chunk-4BX2VUAB-BTsyFV0Y.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-nbLiUMJS.js → chunk-55IACEB6-DnrCm6LC.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-DZ8RgHg8.js → chunk-5ZQYHXKU-DBjF0sdg.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-DDe5dqJV.js → chunk-727SXJPM-Do27Z5ji.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DmpNclOr.js → chunk-AQP2D5EJ-CTnpneGA.js} +1 -1
- package/dist/client/assets/{chunk-BSJP7CBP-BKQKvtIj.js → chunk-BSJP7CBP-Kr0_rIcG.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-D27t4cyv.js → chunk-CSCIHK7Q-vYCYu98G.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-Bu3LA663.js → chunk-FMBD7UC4-D325zkwE.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-Ed5HNXRW.js → chunk-KSCS5N6A-Byf7HmWv.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-DLjJZgf8.js → chunk-L5ZTLDWV-D6bS5hm3.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-B-cs89SM.js → chunk-LZXEDZCA-BMGYcU-n.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-D_kPSuUA.js → chunk-ND2GUHAM-xfJDzv5I.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-D1u3pjq-.js → chunk-NZK2D7GU-DY1HNoUO.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-Mt0cnPSf.js → chunk-O5CBEL6O-BFgywBQV.js} +1 -1
- package/dist/client/assets/chunk-QZHKN3VN-wL51hu-L.js +1 -0
- package/dist/client/assets/chunk-WU5MYG2G-DcSuLObv.js +1 -0
- package/dist/client/assets/{chunk-XPW4576I-Bt8a8x28.js → chunk-XPW4576I-CCOlY0K5.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-Cd9P7jo-.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-_8XVmWNY.js +1 -0
- package/dist/client/assets/{cobol-Daidj_5Y.js → cobol-D5uvQocP.js} +1 -1
- package/dist/client/assets/{coffee-DUMdB2Cr.js → coffee-DoE1_DMC.js} +1 -1
- package/dist/client/assets/{cose-bilkent-S5V4N54A-DVUDLXie.js → cose-bilkent-S5V4N54A-BffMlb0t.js} +1 -1
- package/dist/client/assets/{cpp-C2FeX1Mc.js → cpp-CxMs3g8q.js} +1 -1
- package/dist/client/assets/{crystal-D4yb9blJ.js → crystal-Ck_Sab8H.js} +1 -1
- package/dist/client/assets/{css-B5cr0U9F.js → css-Bx3zqeyw.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-DdZWb-Ee.js → dagre-BM42HDAG-DuK7JkP6.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-C9DOZt92.js → diagram-2AECGRRQ-DW9W2wY6.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-Bbc07UKr.js → diagram-5GNKFQAL-CfmdY-KT.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-C-lUzY3m.js → diagram-KO2AKTUF-C3W0AVns.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-D-OjEFhp.js → diagram-LMA3HP47-Jxoj1EBH.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-BjtWGvrf.js → diagram-OG6HWLK6-DKZXYFSu.js} +1 -1
- package/dist/client/assets/{dist-BXZygPdX.js → dist-Cxgi_w93.js} +1 -1
- package/dist/client/assets/{edge-RCQWcWNp.js → edge-D7XVJcl7.js} +1 -1
- package/dist/client/assets/{elixir-DwKyVg2X.js → elixir-DLQjwJ9Q.js} +1 -1
- package/dist/client/assets/{elm-DjR33IVs.js → elm-CP5Ox5XO.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-C5T4ywLG.js → erDiagram-TEJ5UH35-BGd46oGi.js} +1 -1
- package/dist/client/assets/{erb-BnhUH1Hk.js → erb-C97aW-va.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-BJmjm7Kx.js → flowDiagram-I6XJVG4X-D6MFCwKy.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-DREPnzu5.js → ganttDiagram-6RSMTGT7-i1RH9KFv.js} +1 -1
- package/dist/client/assets/{git-rebase-DzXoSDXA.js → git-rebase-B4iN-h71.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DIR4G_-_.js → gitGraphDiagram-PVQCEYII-DiLnrGe6.js} +1 -1
- package/dist/client/assets/{glimmer-js-HKnZpCn_.js → glimmer-js-DVkEnHRb.js} +1 -1
- package/dist/client/assets/{glimmer-ts-DedV7rWs.js → glimmer-ts-CyUzWW2D.js} +1 -1
- package/dist/client/assets/{glsl-Bz-0Alp2.js → glsl-5XFOCOCu.js} +1 -1
- package/dist/client/assets/{graphql-DSKkDYCD.js → graphql-IoOZHuWD.js} +1 -1
- package/dist/client/assets/{hack-CnaHZm8k.js → hack-BUQ8LqBu.js} +1 -1
- package/dist/client/assets/{haml-huGFYKlj.js → haml-BIbv0YDp.js} +1 -1
- package/dist/client/assets/{handlebars-CKmgzfWY.js → handlebars-9QihiA8n.js} +1 -1
- package/dist/client/assets/{html-B9Ih_dUU.js → html-5bjcBKb2.js} +1 -1
- package/dist/client/assets/{html-derivative-YabD-M5d.js → html-derivative-DFAt3hvS.js} +1 -1
- package/dist/client/assets/{http-Bdu12JYX.js → http-4_xHs8PR.js} +1 -1
- package/dist/client/assets/{hurl-twZzDTNs.js → hurl-B-wi-Gx2.js} +1 -1
- package/dist/client/assets/{index-BktdZRWB.js → index-CHvT3DR9.js} +10 -10
- package/dist/client/assets/{infoDiagram-5YYISTIA-D2ty2R-i.js → infoDiagram-5YYISTIA-Dy1M2f6k.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-Ccnc9xlE.js → ishikawaDiagram-YF4QCWOH-J9qG2PlR.js} +1 -1
- package/dist/client/assets/{java-_5trO-p9.js → java-D9isH882.js} +1 -1
- package/dist/client/assets/{javascript-BOQn0ikE.js → javascript-Dj8e4yT5.js} +1 -1
- package/dist/client/assets/{jinja-BgY-0IBz.js → jinja-Bkare_Hv.js} +1 -1
- package/dist/client/assets/{jison-BLNTmT1_.js → jison-DDIUNnOW.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-W7eWXlw0.js → journeyDiagram-JHISSGLW-Do4kGeyR.js} +1 -1
- package/dist/client/assets/{json-BLbH8rZf.js → json-Bi84FPdJ.js} +1 -1
- package/dist/client/assets/{jsx-CxteptsJ.js → jsx-DI-NKxp6.js} +1 -1
- package/dist/client/assets/{julia-B__Ncp_z.js → julia-Yhlqb4JO.js} +1 -1
- package/dist/client/assets/{just-DBsHUGW1.js → just-B8KISqsZ.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-Z_AFL-rd.js → kanban-definition-UN3LZRKU-BSeQxfa9.js} +1 -1
- package/dist/client/assets/{latex-DFF_0wxo.js → latex-CkYf4HIK.js} +1 -1
- package/dist/client/assets/{line-aAv8TyOs.js → line-C2Za_tgK.js} +1 -1
- package/dist/client/assets/{linear-BOvEZKH8.js → linear-CRm3pgdR.js} +1 -1
- package/dist/client/assets/{liquid-kDWCO6d7.js → liquid-y_Utf1BC.js} +1 -1
- package/dist/client/assets/{lua-CYTFayq-.js → lua-C4gbVDKd.js} +1 -1
- package/dist/client/assets/{marko-Dpmz3Pdx.js → marko-BlcQZIyX.js} +1 -1
- package/dist/client/assets/{mdc-BsxHyM1i.js → mdc-C6KkSPM_.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-D8z_CWL1.js → mermaid-parser.core-B9mHNH7g.js} +1 -1
- package/dist/client/assets/{mermaid.core-DNKchnly.js → mermaid.core-pkJSRcFi.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-xQKMV4Kw.js → mindmap-definition-RKZ34NQL-BAW_jZIg.js} +1 -1
- package/dist/client/assets/{nginx-CcdTDme-.js → nginx-BGbv5z-H.js} +1 -1
- package/dist/client/assets/{nim-B5wWDskL.js → nim-BtqaozVV.js} +1 -1
- package/dist/client/assets/{perl-Ce2_iKLz.js → perl-BaPVFzDG.js} +1 -1
- package/dist/client/assets/{php-DuLCFERd.js → php-CUSmmFXP.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-CBSCJnUr.js → pieDiagram-4H26LBE5-DHTW5bo3.js} +1 -1
- package/dist/client/assets/{pug-BfQJoQj1.js → pug-ComryLhI.js} +1 -1
- package/dist/client/assets/{qml-DYeovlVl.js → qml-D4-pUDzN.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-uEcgb0Rm.js → quadrantDiagram-W4KKPZXB-oE0LSIIu.js} +1 -1
- package/dist/client/assets/{r-DgHSP56L.js → r-IEiS2KmQ.js} +1 -1
- package/dist/client/assets/{razor-B00x7W-i.js → razor-A3NOQ-6o.js} +1 -1
- package/dist/client/assets/{regexp-CmWVupmd.js → regexp-Dnh3EraB.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-Q4yY_ALe.js → requirementDiagram-4Y6WPE33-CAnOHDxz.js} +1 -1
- package/dist/client/assets/{review-export-pcqDKNqx.js → review-export-D-wScR31.js} +1 -1
- package/dist/client/assets/{rst-B3VARbTX.js → rst-DDcT8Cwj.js} +1 -1
- package/dist/client/assets/{ruby-CaxKSSbT.js → ruby-BqnC7im3.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-B-GLzFGa.js → sankeyDiagram-5OEKKPKP-DARazeui.js} +1 -1
- package/dist/client/assets/{sas-Ue0rHUIk.js → sas-BsVPIslL.js} +1 -1
- package/dist/client/assets/{scss-BJJ7_L6c.js → scss-Cs-F21hy.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DW0em0D_.js → sequenceDiagram-3UESZ5HK-BYAKokia.js} +1 -1
- package/dist/client/assets/{shellscript-C6LkZs1u.js → shellscript-CuqRpwp7.js} +1 -1
- package/dist/client/assets/{shellsession-CkZu1e84.js → shellsession-BT4CTJst.js} +1 -1
- package/dist/client/assets/{soy-CDaZV57J.js → soy-Njc_erP7.js} +1 -1
- package/dist/client/assets/{sql-CLfMGH4x.js → sql-C0M8bIzv.js} +1 -1
- package/dist/client/assets/{src-C27zxuNO.js → src-B8u-CQt1.js} +1 -1
- package/dist/client/assets/{stata-B0kNMHc9.js → stata-DQWLgfGE.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-CrO_D3m4.js → stateDiagram-AJRCARHV-CL6IO6Yx.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CXkQIsOM.js +1 -0
- package/dist/client/assets/{surrealql-D9os_yZO.js → surrealql-CxnsjSMN.js} +1 -1
- package/dist/client/assets/{svelte-2SxVToeO.js → svelte-YiRJWdT-.js} +1 -1
- package/dist/client/assets/{templ-DzmZB67T.js → templ-DVHepm0I.js} +1 -1
- package/dist/client/assets/{tex-BMPk3nHk.js → tex-CbaeWbjN.js} +1 -1
- package/dist/client/assets/{timeline-definition-PNZ67QCA-C9FBevHv.js → timeline-definition-PNZ67QCA-BAKLMRXW.js} +1 -1
- package/dist/client/assets/{ts-tags-CG14Bplc.js → ts-tags-KadJLbnQ.js} +1 -1
- package/dist/client/assets/{tsx-D6NK461A.js → tsx-DgmkmSe3.js} +1 -1
- package/dist/client/assets/{twig-Agh0j5sD.js → twig-BG7aoIMU.js} +1 -1
- package/dist/client/assets/{typescript-_AkeziZ2.js → typescript-C_w_yccd.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-2H9HfhhR.js → vennDiagram-CIIHVFJN-CCRx_JXh.js} +1 -1
- package/dist/client/assets/{vue-C1z3QGG9.js → vue-DXnUyZR0.js} +1 -1
- package/dist/client/assets/{vue-html-C1RMzLZk.js → vue-html-CetUfade.js} +1 -1
- package/dist/client/assets/{vue-vine-oOixA6ge.js → vue-vine-CSW5RjCH.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BJxrQhTf.js → wardleyDiagram-YWT4CUSO-Buoffz1N.js} +1 -1
- package/dist/client/assets/{xml-okuDZ7vq.js → xml-CJgpX9jH.js} +1 -1
- package/dist/client/assets/{xsl-S_n3XrKb.js → xsl-BZk-xhGC.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-BGdee7KG.js → xychartDiagram-2RQKCTM6-CEKRZKfv.js} +1 -1
- package/dist/client/assets/{yaml-BoX-kqX1.js → yaml-DVxl0VLy.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/{completions-ZSKYvW06.mjs → completions-Csvfqctx.mjs} +16 -1
- package/dist/{doctor-CmP0u7-d.mjs → doctor-DhuZFVwO.mjs} +1 -1
- package/dist/{github-oCZx5aim.mjs → github-CzxbZnhh.mjs} +152 -28
- package/dist/github-DL7-BqhD.mjs +2 -0
- package/dist/{mcp-CqxXILYL.mjs → mcp-BBDdXGOZ.mjs} +212 -28
- package/dist/{server-CczuXSWo.mjs → server-D-gJ7owz.mjs} +947 -54
- package/package.json +1 -1
- package/dist/cli-gh-8YC1NzyM.mjs +0 -155
- package/dist/client/assets/channel-DmZgR6r0.js +0 -1
- package/dist/client/assets/chunk-QZHKN3VN-BrYnBs7h.js +0 -1
- package/dist/client/assets/chunk-WU5MYG2G-CdBcsQs_.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-CCpp3t5M.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B1aV1sYb.js +0 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-C9KDGD7R.js +0 -1
- package/dist/github-BjZZcSKV.mjs +0 -2
|
@@ -2,8 +2,9 @@ import { C as isSafePath, S as revertHunk, _ as getUntrackedFilePathsAsync, a as
|
|
|
2
2
|
import { a as searchContent, c as trackSelection, i as searchAll, n as saveSettings, o as searchFiles, r as getSearchStatus, s as searchSymbols, t as loadSettings } from "./settings-BVnzGFL7.mjs";
|
|
3
3
|
import { t as formatComments } from "./comment-format-B5AcGcR-.mjs";
|
|
4
4
|
import { n as formatPlanReview, r as sectionTitleForLine, t as extractPlanLines } from "./plan-format-BQyx--mp.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { D as updatePrReviewComment, E as submitReview, S as refreshPrSession, T as setPrReviewThreadResolved, a as detectCwdRepo, b as parsePrRef, f as fetchPrFileContentViaGh, i as deletePrReviewComment, l as fetchExistingCommentsViaGh, t as buildPrSession, u as fetchExistingReviewsViaGh, w as resolvedFromSession } from "./github-CzxbZnhh.mjs";
|
|
6
6
|
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
import { dirname, extname, join, resolve } from "node:path";
|
|
9
10
|
import { existsSync, mkdirSync, readFileSync, watch } from "node:fs";
|
|
@@ -250,10 +251,10 @@ Subcommands:
|
|
|
250
251
|
progress --message … Live agent progress toast
|
|
251
252
|
url Print the active review base URL
|
|
252
253
|
plan <submit|await|list|show|versions|reply|resolve>
|
|
253
|
-
gh <status|pr-fetch|pr-list-comments|pr-review>
|
|
254
|
+
gh <status|overview|threads|reviews|pr-fetch|pr-list-comments|pr-review>
|
|
254
255
|
mcp [--repo PATH] Stdio MCP server for agents
|
|
255
256
|
inspect <summary|files|hunks|slice|search>
|
|
256
|
-
Bounded reads from a running TUI session
|
|
257
|
+
Bounded reads from a running web, TUI, or PR session
|
|
257
258
|
doctor Environment self-check
|
|
258
259
|
completion <bash|zsh|fish>
|
|
259
260
|
update Check for a newer npm release
|
|
@@ -1977,6 +1978,744 @@ var FilePrSessionStore = class {
|
|
|
1977
1978
|
}
|
|
1978
1979
|
};
|
|
1979
1980
|
//#endregion
|
|
1981
|
+
//#region src/lib/agent-diff-index.ts
|
|
1982
|
+
/**
|
|
1983
|
+
* In-process unified-diff index for token-efficient agent inspection.
|
|
1984
|
+
*
|
|
1985
|
+
* Mirrors the TUI Agent API response shapes (`/api/diff/summary|files|hunks|slice|search`)
|
|
1986
|
+
* so web and gh-pr sessions can share the same inspect CLI / MCP tools without
|
|
1987
|
+
* embedding the Rust sparse spool. Suitable for patches already held in memory
|
|
1988
|
+
* (PR sessions, web `git diff` results).
|
|
1989
|
+
*/
|
|
1990
|
+
const MAX_PAGE_LINES = 1e3;
|
|
1991
|
+
const DEFAULT_SLICE_LINES = 120;
|
|
1992
|
+
const DEFAULT_MAX_BYTES = 256 * 1024;
|
|
1993
|
+
const MAX_BODY_BYTES = 4 * 1024 * 1024;
|
|
1994
|
+
let nextGeneration = 1;
|
|
1995
|
+
function createEmptyIndex(generation = nextGeneration++) {
|
|
1996
|
+
return {
|
|
1997
|
+
generation,
|
|
1998
|
+
complete: true,
|
|
1999
|
+
files: [],
|
|
2000
|
+
totalRows: 0,
|
|
2001
|
+
totalHunks: 0,
|
|
2002
|
+
additions: 0,
|
|
2003
|
+
deletions: 0,
|
|
2004
|
+
patchBytes: 0
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
/**
|
|
2008
|
+
* Parse a unified multi-file patch into an agent-facing index.
|
|
2009
|
+
* Generation is assigned automatically unless provided (for cache control).
|
|
2010
|
+
*/
|
|
2011
|
+
function buildAgentDiffIndex(patch, generation = nextGeneration++) {
|
|
2012
|
+
const patchBytes = Buffer.byteLength(patch, "utf8");
|
|
2013
|
+
if (!patch.trim()) return {
|
|
2014
|
+
...createEmptyIndex(generation),
|
|
2015
|
+
patchBytes
|
|
2016
|
+
};
|
|
2017
|
+
const lines = patch.split(/\r?\n/);
|
|
2018
|
+
const files = [];
|
|
2019
|
+
let i = 0;
|
|
2020
|
+
while (i < lines.length) {
|
|
2021
|
+
const line = lines[i];
|
|
2022
|
+
const gitHeader = parseGitFileHeader(line);
|
|
2023
|
+
if (!gitHeader) {
|
|
2024
|
+
i++;
|
|
2025
|
+
continue;
|
|
2026
|
+
}
|
|
2027
|
+
const [oldPathRaw, newPathRaw] = gitHeader;
|
|
2028
|
+
let oldPath = oldPathRaw === "/dev/null" ? null : oldPathRaw;
|
|
2029
|
+
let newPath = newPathRaw === "/dev/null" ? null : newPathRaw;
|
|
2030
|
+
let isBinary = false;
|
|
2031
|
+
let kind = "modified";
|
|
2032
|
+
i++;
|
|
2033
|
+
while (i < lines.length) {
|
|
2034
|
+
const h = lines[i];
|
|
2035
|
+
if (h.startsWith("diff --git ")) break;
|
|
2036
|
+
if (h.startsWith("@@ ")) break;
|
|
2037
|
+
if (h.startsWith("Binary files ") || h.startsWith("GIT binary patch")) isBinary = true;
|
|
2038
|
+
if (h.startsWith("new file mode")) kind = "added";
|
|
2039
|
+
else if (h.startsWith("deleted file mode")) kind = "deleted";
|
|
2040
|
+
else if (h.startsWith("rename from ")) {
|
|
2041
|
+
kind = "renamed";
|
|
2042
|
+
oldPath = decodeGitPath(h.slice(12));
|
|
2043
|
+
} else if (h.startsWith("rename to ")) {
|
|
2044
|
+
kind = "renamed";
|
|
2045
|
+
newPath = decodeGitPath(h.slice(10));
|
|
2046
|
+
} else if (h.startsWith("--- ")) {
|
|
2047
|
+
const p = decodeGitPath(h.slice(4));
|
|
2048
|
+
oldPath = p === "/dev/null" ? null : stripSidePrefix(p, "a/");
|
|
2049
|
+
} else if (h.startsWith("+++ ")) {
|
|
2050
|
+
const p = decodeGitPath(h.slice(4));
|
|
2051
|
+
newPath = p === "/dev/null" ? null : stripSidePrefix(p, "b/");
|
|
2052
|
+
}
|
|
2053
|
+
i++;
|
|
2054
|
+
}
|
|
2055
|
+
if (isBinary) kind = "binary";
|
|
2056
|
+
else if (!oldPath && newPath) kind = "added";
|
|
2057
|
+
else if (oldPath && !newPath) kind = "deleted";
|
|
2058
|
+
const displayPath = newPath ?? oldPath ?? "";
|
|
2059
|
+
const rows = [];
|
|
2060
|
+
const hunks = [];
|
|
2061
|
+
let additions = 0;
|
|
2062
|
+
let deletions = 0;
|
|
2063
|
+
const fileIndex = files.length;
|
|
2064
|
+
rows.push({
|
|
2065
|
+
type: "fileHeader",
|
|
2066
|
+
fileIndex,
|
|
2067
|
+
path: displayPath,
|
|
2068
|
+
kind,
|
|
2069
|
+
binary: isBinary
|
|
2070
|
+
});
|
|
2071
|
+
if (!isBinary) while (i < lines.length) {
|
|
2072
|
+
const h = lines[i];
|
|
2073
|
+
if (h.startsWith("diff --git ")) break;
|
|
2074
|
+
const hunkMatch = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/.exec(h);
|
|
2075
|
+
if (!hunkMatch) {
|
|
2076
|
+
i++;
|
|
2077
|
+
continue;
|
|
2078
|
+
}
|
|
2079
|
+
const oldStart = Number(hunkMatch[1]);
|
|
2080
|
+
const oldLines = hunkMatch[2] != null ? Number(hunkMatch[2]) : 1;
|
|
2081
|
+
const newStart = Number(hunkMatch[3]);
|
|
2082
|
+
const newLines = hunkMatch[4] != null ? Number(hunkMatch[4]) : 1;
|
|
2083
|
+
const heading = (hunkMatch[5] ?? "").replace(/^\s/, "");
|
|
2084
|
+
const hunkIndex = hunks.length;
|
|
2085
|
+
const rowStart = rows.length;
|
|
2086
|
+
rows.push({
|
|
2087
|
+
type: "hunkHeader",
|
|
2088
|
+
hunkIndex,
|
|
2089
|
+
oldStart,
|
|
2090
|
+
oldLines,
|
|
2091
|
+
newStart,
|
|
2092
|
+
newLines,
|
|
2093
|
+
heading
|
|
2094
|
+
});
|
|
2095
|
+
i++;
|
|
2096
|
+
let oldLineno = oldStart;
|
|
2097
|
+
let newLineno = newStart;
|
|
2098
|
+
let bodyCount = 0;
|
|
2099
|
+
while (i < lines.length) {
|
|
2100
|
+
const body = lines[i];
|
|
2101
|
+
if (body.startsWith("diff --git ") || body.startsWith("@@ ")) break;
|
|
2102
|
+
if (body.startsWith("\")) {
|
|
2103
|
+
rows.push({
|
|
2104
|
+
type: "noNewline",
|
|
2105
|
+
hunkIndex
|
|
2106
|
+
});
|
|
2107
|
+
bodyCount++;
|
|
2108
|
+
i++;
|
|
2109
|
+
continue;
|
|
2110
|
+
}
|
|
2111
|
+
if (body === "" && i === lines.length - 1) {
|
|
2112
|
+
i++;
|
|
2113
|
+
break;
|
|
2114
|
+
}
|
|
2115
|
+
const prefix = body[0];
|
|
2116
|
+
if (prefix === "+") {
|
|
2117
|
+
rows.push({
|
|
2118
|
+
type: "line",
|
|
2119
|
+
hunkIndex,
|
|
2120
|
+
kind: "add",
|
|
2121
|
+
oldLineno: null,
|
|
2122
|
+
newLineno,
|
|
2123
|
+
content: body.slice(1)
|
|
2124
|
+
});
|
|
2125
|
+
newLineno++;
|
|
2126
|
+
additions++;
|
|
2127
|
+
bodyCount++;
|
|
2128
|
+
i++;
|
|
2129
|
+
continue;
|
|
2130
|
+
}
|
|
2131
|
+
if (prefix === "-") {
|
|
2132
|
+
rows.push({
|
|
2133
|
+
type: "line",
|
|
2134
|
+
hunkIndex,
|
|
2135
|
+
kind: "del",
|
|
2136
|
+
oldLineno,
|
|
2137
|
+
newLineno: null,
|
|
2138
|
+
content: body.slice(1)
|
|
2139
|
+
});
|
|
2140
|
+
oldLineno++;
|
|
2141
|
+
deletions++;
|
|
2142
|
+
bodyCount++;
|
|
2143
|
+
i++;
|
|
2144
|
+
continue;
|
|
2145
|
+
}
|
|
2146
|
+
if (prefix === " " || body === "") {
|
|
2147
|
+
const content = prefix === " " ? body.slice(1) : body;
|
|
2148
|
+
rows.push({
|
|
2149
|
+
type: "line",
|
|
2150
|
+
hunkIndex,
|
|
2151
|
+
kind: "context",
|
|
2152
|
+
oldLineno,
|
|
2153
|
+
newLineno,
|
|
2154
|
+
content
|
|
2155
|
+
});
|
|
2156
|
+
oldLineno++;
|
|
2157
|
+
newLineno++;
|
|
2158
|
+
bodyCount++;
|
|
2159
|
+
i++;
|
|
2160
|
+
continue;
|
|
2161
|
+
}
|
|
2162
|
+
break;
|
|
2163
|
+
}
|
|
2164
|
+
hunks.push({
|
|
2165
|
+
oldStart,
|
|
2166
|
+
oldLines,
|
|
2167
|
+
newStart,
|
|
2168
|
+
newLines,
|
|
2169
|
+
heading,
|
|
2170
|
+
rowStart,
|
|
2171
|
+
lineCount: bodyCount
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
else while (i < lines.length && !lines[i].startsWith("diff --git ")) i++;
|
|
2175
|
+
files.push({
|
|
2176
|
+
oldPath,
|
|
2177
|
+
newPath,
|
|
2178
|
+
kind,
|
|
2179
|
+
isBinary,
|
|
2180
|
+
hunks,
|
|
2181
|
+
rowCount: rows.length,
|
|
2182
|
+
additions,
|
|
2183
|
+
deletions,
|
|
2184
|
+
rows
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
let totalRows = 0;
|
|
2188
|
+
let totalHunks = 0;
|
|
2189
|
+
let totalAdd = 0;
|
|
2190
|
+
let totalDel = 0;
|
|
2191
|
+
for (const f of files) {
|
|
2192
|
+
totalRows += f.rowCount;
|
|
2193
|
+
totalHunks += f.hunks.length;
|
|
2194
|
+
totalAdd += f.additions;
|
|
2195
|
+
totalDel += f.deletions;
|
|
2196
|
+
}
|
|
2197
|
+
return {
|
|
2198
|
+
generation,
|
|
2199
|
+
complete: true,
|
|
2200
|
+
files,
|
|
2201
|
+
totalRows,
|
|
2202
|
+
totalHunks,
|
|
2203
|
+
additions: totalAdd,
|
|
2204
|
+
deletions: totalDel,
|
|
2205
|
+
patchBytes
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
function indexSummary(index) {
|
|
2209
|
+
const changes = {};
|
|
2210
|
+
for (const file of index.files) changes[file.kind] = (changes[file.kind] ?? 0) + 1;
|
|
2211
|
+
return {
|
|
2212
|
+
generation: index.generation,
|
|
2213
|
+
complete: index.complete,
|
|
2214
|
+
files: index.files.length,
|
|
2215
|
+
hunks: index.totalHunks,
|
|
2216
|
+
rows: index.totalRows,
|
|
2217
|
+
additions: index.additions,
|
|
2218
|
+
deletions: index.deletions,
|
|
2219
|
+
patchBytes: index.patchBytes,
|
|
2220
|
+
changes,
|
|
2221
|
+
next: [
|
|
2222
|
+
"diff_files",
|
|
2223
|
+
"diff_search",
|
|
2224
|
+
"diff_slice"
|
|
2225
|
+
]
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
function indexFiles(index, cursor = 0, limit = 100) {
|
|
2229
|
+
const safeLimit = clamp(limit, 1, MAX_PAGE_LINES);
|
|
2230
|
+
const start = Math.max(0, cursor);
|
|
2231
|
+
const end = Math.min(index.files.length, start + safeLimit);
|
|
2232
|
+
const files = index.files.slice(start, end).map((file, offset) => ({
|
|
2233
|
+
index: start + offset,
|
|
2234
|
+
path: file.newPath ?? file.oldPath ?? "",
|
|
2235
|
+
oldPath: file.oldPath,
|
|
2236
|
+
newPath: file.newPath,
|
|
2237
|
+
kind: file.kind,
|
|
2238
|
+
binary: file.isBinary,
|
|
2239
|
+
hunks: file.hunks.length,
|
|
2240
|
+
rows: file.rowCount,
|
|
2241
|
+
additions: file.additions,
|
|
2242
|
+
deletions: file.deletions
|
|
2243
|
+
}));
|
|
2244
|
+
return {
|
|
2245
|
+
generation: index.generation,
|
|
2246
|
+
returned: files.length,
|
|
2247
|
+
total: index.files.length,
|
|
2248
|
+
nextCursor: end < index.files.length ? end : null,
|
|
2249
|
+
files
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
function indexHunks(index, fileIndex, cursor = 0, limit = 100, generation) {
|
|
2253
|
+
if (generation !== void 0 && generation !== index.generation) return {
|
|
2254
|
+
error: `stale generation ${generation}; current generation is ${index.generation}`,
|
|
2255
|
+
status: 409
|
|
2256
|
+
};
|
|
2257
|
+
const file = index.files[fileIndex];
|
|
2258
|
+
if (!file) return {
|
|
2259
|
+
error: "file index not found",
|
|
2260
|
+
status: 404
|
|
2261
|
+
};
|
|
2262
|
+
const safeLimit = clamp(limit, 1, MAX_PAGE_LINES);
|
|
2263
|
+
const start = Math.min(Math.max(0, cursor), file.hunks.length);
|
|
2264
|
+
const end = Math.min(file.hunks.length, start + safeLimit);
|
|
2265
|
+
return {
|
|
2266
|
+
generation: index.generation,
|
|
2267
|
+
file: fileIndex,
|
|
2268
|
+
path: file.newPath ?? file.oldPath ?? "",
|
|
2269
|
+
returned: end - start,
|
|
2270
|
+
total: file.hunks.length,
|
|
2271
|
+
nextCursor: end < file.hunks.length ? end : null,
|
|
2272
|
+
hunks: file.hunks.slice(start, end)
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
function indexSlice(index, fileIndex, startRow = 0, maxLines = DEFAULT_SLICE_LINES, maxBytes = DEFAULT_MAX_BYTES, generation) {
|
|
2276
|
+
if (generation !== void 0 && generation !== index.generation) return {
|
|
2277
|
+
error: `stale generation ${generation}; current generation is ${index.generation}`,
|
|
2278
|
+
status: 409
|
|
2279
|
+
};
|
|
2280
|
+
const file = index.files[fileIndex];
|
|
2281
|
+
if (!file) return {
|
|
2282
|
+
generation: index.generation,
|
|
2283
|
+
fileIndex,
|
|
2284
|
+
startRow,
|
|
2285
|
+
nextRow: null,
|
|
2286
|
+
totalRows: 0,
|
|
2287
|
+
truncated: false,
|
|
2288
|
+
estimatedBytes: 0,
|
|
2289
|
+
rows: []
|
|
2290
|
+
};
|
|
2291
|
+
const lineBudget = clamp(maxLines, 1, MAX_PAGE_LINES);
|
|
2292
|
+
const byteBudget = clamp(maxBytes, 1, MAX_BODY_BYTES);
|
|
2293
|
+
const start = Math.min(Math.max(0, startRow), file.rowCount);
|
|
2294
|
+
const rows = [];
|
|
2295
|
+
let estimatedBytes = 0;
|
|
2296
|
+
let truncated = false;
|
|
2297
|
+
let cursor = start;
|
|
2298
|
+
while (cursor < file.rowCount && rows.length < lineBudget) {
|
|
2299
|
+
const row = file.rows[cursor];
|
|
2300
|
+
const cost = viewRowCost(row);
|
|
2301
|
+
if (estimatedBytes + cost > byteBudget && rows.length > 0) {
|
|
2302
|
+
truncated = true;
|
|
2303
|
+
break;
|
|
2304
|
+
}
|
|
2305
|
+
rows.push(row);
|
|
2306
|
+
estimatedBytes += cost;
|
|
2307
|
+
cursor++;
|
|
2308
|
+
}
|
|
2309
|
+
if (cursor < file.rowCount && rows.length >= lineBudget) truncated = true;
|
|
2310
|
+
return {
|
|
2311
|
+
generation: index.generation,
|
|
2312
|
+
fileIndex,
|
|
2313
|
+
startRow: start,
|
|
2314
|
+
nextRow: cursor < file.rowCount ? cursor : null,
|
|
2315
|
+
totalRows: file.rowCount,
|
|
2316
|
+
truncated,
|
|
2317
|
+
estimatedBytes,
|
|
2318
|
+
rows
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
function indexSearch(index, query, fileStart = 0, rowStart = 0, limit = 100, maxBytes = DEFAULT_MAX_BYTES, generation) {
|
|
2322
|
+
if (generation !== void 0 && generation !== index.generation) return {
|
|
2323
|
+
error: `stale generation ${generation}; current generation is ${index.generation}`,
|
|
2324
|
+
status: 409
|
|
2325
|
+
};
|
|
2326
|
+
const q = query.toLowerCase();
|
|
2327
|
+
if (!q) return {
|
|
2328
|
+
generation: index.generation,
|
|
2329
|
+
hits: [],
|
|
2330
|
+
nextFile: null,
|
|
2331
|
+
nextRow: null,
|
|
2332
|
+
truncated: false,
|
|
2333
|
+
estimatedBytes: 0
|
|
2334
|
+
};
|
|
2335
|
+
const hitLimit = clamp(limit, 1, MAX_PAGE_LINES);
|
|
2336
|
+
const byteBudget = clamp(maxBytes, 1, MAX_BODY_BYTES);
|
|
2337
|
+
const hits = [];
|
|
2338
|
+
let estimatedBytes = 0;
|
|
2339
|
+
let truncated = false;
|
|
2340
|
+
let nextFile = null;
|
|
2341
|
+
let nextRow = null;
|
|
2342
|
+
outer: for (let fi = Math.max(0, fileStart); fi < index.files.length; fi++) {
|
|
2343
|
+
const file = index.files[fi];
|
|
2344
|
+
const path = file.newPath ?? file.oldPath ?? "";
|
|
2345
|
+
const pathMatch = path.toLowerCase().includes(q);
|
|
2346
|
+
const rowBegin = fi === fileStart ? Math.max(0, rowStart) : 0;
|
|
2347
|
+
for (let ri = rowBegin; ri < file.rows.length; ri++) {
|
|
2348
|
+
const row = file.rows[ri];
|
|
2349
|
+
let preview = "";
|
|
2350
|
+
let oldLineno = null;
|
|
2351
|
+
let newLineno = null;
|
|
2352
|
+
let matched = false;
|
|
2353
|
+
if (row.type === "fileHeader") {
|
|
2354
|
+
if (pathMatch && ri === 0) {
|
|
2355
|
+
preview = path;
|
|
2356
|
+
matched = true;
|
|
2357
|
+
}
|
|
2358
|
+
} else if (row.type === "hunkHeader") {
|
|
2359
|
+
const text = `@@ -${row.oldStart},${row.oldLines} +${row.newStart},${row.newLines} @@ ${row.heading}`;
|
|
2360
|
+
if (text.toLowerCase().includes(q)) {
|
|
2361
|
+
preview = text;
|
|
2362
|
+
matched = true;
|
|
2363
|
+
}
|
|
2364
|
+
} else if (row.type === "line") {
|
|
2365
|
+
if (row.content.toLowerCase().includes(q)) {
|
|
2366
|
+
preview = row.content;
|
|
2367
|
+
oldLineno = row.oldLineno;
|
|
2368
|
+
newLineno = row.newLineno;
|
|
2369
|
+
matched = true;
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
if (!matched) continue;
|
|
2373
|
+
const cost = preview.length + path.length + 32;
|
|
2374
|
+
if (estimatedBytes + cost > byteBudget && hits.length > 0) {
|
|
2375
|
+
truncated = true;
|
|
2376
|
+
nextFile = fi;
|
|
2377
|
+
nextRow = ri;
|
|
2378
|
+
break outer;
|
|
2379
|
+
}
|
|
2380
|
+
hits.push({
|
|
2381
|
+
fileIndex: fi,
|
|
2382
|
+
path,
|
|
2383
|
+
row: ri,
|
|
2384
|
+
oldLineno,
|
|
2385
|
+
newLineno,
|
|
2386
|
+
preview: preview.slice(0, 400)
|
|
2387
|
+
});
|
|
2388
|
+
estimatedBytes += cost;
|
|
2389
|
+
if (hits.length >= hitLimit) {
|
|
2390
|
+
const nr = ri + 1;
|
|
2391
|
+
if (nr < file.rows.length) {
|
|
2392
|
+
nextFile = fi;
|
|
2393
|
+
nextRow = nr;
|
|
2394
|
+
} else if (fi + 1 < index.files.length) {
|
|
2395
|
+
nextFile = fi + 1;
|
|
2396
|
+
nextRow = 0;
|
|
2397
|
+
}
|
|
2398
|
+
truncated = nextFile != null;
|
|
2399
|
+
break outer;
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
return {
|
|
2404
|
+
generation: index.generation,
|
|
2405
|
+
hits,
|
|
2406
|
+
nextFile,
|
|
2407
|
+
nextRow,
|
|
2408
|
+
truncated,
|
|
2409
|
+
estimatedBytes
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
function viewRowCost(row) {
|
|
2413
|
+
switch (row.type) {
|
|
2414
|
+
case "fileHeader": return row.path.length + 32;
|
|
2415
|
+
case "hunkHeader": return row.heading.length + 48;
|
|
2416
|
+
case "line": return row.content.length + 16;
|
|
2417
|
+
case "noNewline": return 24;
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
function clamp(n, min, max) {
|
|
2421
|
+
if (!Number.isFinite(n)) return min;
|
|
2422
|
+
return Math.min(max, Math.max(min, Math.trunc(n)));
|
|
2423
|
+
}
|
|
2424
|
+
/** Parse Git's file header, including core.quotePath-style C-quoted paths. */
|
|
2425
|
+
function parseGitFileHeader(line) {
|
|
2426
|
+
if (!line.startsWith("diff --git ")) return null;
|
|
2427
|
+
const rest = line.slice(11);
|
|
2428
|
+
if (rest.startsWith("\"")) {
|
|
2429
|
+
const first = consumeGitToken(rest, 0);
|
|
2430
|
+
if (!first) return null;
|
|
2431
|
+
let cursor = first.next;
|
|
2432
|
+
while (cursor < rest.length && /\s/.test(rest[cursor])) cursor++;
|
|
2433
|
+
const second = consumeGitToken(rest, cursor);
|
|
2434
|
+
if (!second) return null;
|
|
2435
|
+
return [stripSidePrefix(decodeGitPath(first.token), "a/"), stripSidePrefix(decodeGitPath(second.token), "b/")];
|
|
2436
|
+
}
|
|
2437
|
+
const separator = rest.lastIndexOf(" b/");
|
|
2438
|
+
if (separator < 0) return null;
|
|
2439
|
+
return [stripSidePrefix(rest.slice(0, separator), "a/"), stripSidePrefix(rest.slice(separator + 1), "b/")];
|
|
2440
|
+
}
|
|
2441
|
+
function consumeGitToken(input, start) {
|
|
2442
|
+
if (input[start] !== "\"") {
|
|
2443
|
+
const end = input.indexOf(" ", start);
|
|
2444
|
+
return {
|
|
2445
|
+
token: input.slice(start, end < 0 ? input.length : end),
|
|
2446
|
+
next: end < 0 ? input.length : end
|
|
2447
|
+
};
|
|
2448
|
+
}
|
|
2449
|
+
let escaped = false;
|
|
2450
|
+
for (let i = start + 1; i < input.length; i++) {
|
|
2451
|
+
const char = input[i];
|
|
2452
|
+
if (!escaped && char === "\"") return {
|
|
2453
|
+
token: input.slice(start, i + 1),
|
|
2454
|
+
next: i + 1
|
|
2455
|
+
};
|
|
2456
|
+
if (!escaped && char === "\\") escaped = true;
|
|
2457
|
+
else escaped = false;
|
|
2458
|
+
}
|
|
2459
|
+
return null;
|
|
2460
|
+
}
|
|
2461
|
+
function stripSidePrefix(path, prefix) {
|
|
2462
|
+
return path.startsWith(prefix) ? path.slice(prefix.length) : path;
|
|
2463
|
+
}
|
|
2464
|
+
/** Decode the byte escapes emitted by Git for quoted pathnames. */
|
|
2465
|
+
function decodeGitPath(raw) {
|
|
2466
|
+
const input = raw.trim();
|
|
2467
|
+
if (!(input.startsWith("\"") && input.endsWith("\""))) return input;
|
|
2468
|
+
const bytes = [];
|
|
2469
|
+
const pushText = (value) => bytes.push(...Buffer.from(value, "utf8"));
|
|
2470
|
+
for (let i = 1; i < input.length - 1; i++) {
|
|
2471
|
+
if (input[i] !== "\\") {
|
|
2472
|
+
const codePoint = input.codePointAt(i);
|
|
2473
|
+
pushText(String.fromCodePoint(codePoint));
|
|
2474
|
+
if (codePoint > 65535) i++;
|
|
2475
|
+
continue;
|
|
2476
|
+
}
|
|
2477
|
+
const escaped = input[++i];
|
|
2478
|
+
if (escaped == null) break;
|
|
2479
|
+
const simple = {
|
|
2480
|
+
a: 7,
|
|
2481
|
+
b: 8,
|
|
2482
|
+
t: 9,
|
|
2483
|
+
n: 10,
|
|
2484
|
+
v: 11,
|
|
2485
|
+
f: 12,
|
|
2486
|
+
r: 13,
|
|
2487
|
+
"\"": 34,
|
|
2488
|
+
"\\": 92
|
|
2489
|
+
};
|
|
2490
|
+
if (escaped in simple) {
|
|
2491
|
+
bytes.push(simple[escaped]);
|
|
2492
|
+
continue;
|
|
2493
|
+
}
|
|
2494
|
+
if (/[0-7]/.test(escaped)) {
|
|
2495
|
+
let octal = escaped;
|
|
2496
|
+
while (octal.length < 3 && i + 1 < input.length - 1 && /[0-7]/.test(input[i + 1])) octal += input[++i];
|
|
2497
|
+
bytes.push(Number.parseInt(octal, 8));
|
|
2498
|
+
continue;
|
|
2499
|
+
}
|
|
2500
|
+
pushText(escaped);
|
|
2501
|
+
}
|
|
2502
|
+
return Buffer.from(bytes).toString("utf8");
|
|
2503
|
+
}
|
|
2504
|
+
/** Cache helper: rebuild only when patch fingerprint changes. */
|
|
2505
|
+
var AgentDiffIndexCache = class {
|
|
2506
|
+
index = null;
|
|
2507
|
+
fingerprint = null;
|
|
2508
|
+
getOrBuild(patch) {
|
|
2509
|
+
const fp = createHash("sha256").update(patch).digest("base64url");
|
|
2510
|
+
if (this.index && this.fingerprint === fp) return this.index;
|
|
2511
|
+
this.index = buildAgentDiffIndex(patch);
|
|
2512
|
+
this.fingerprint = fp;
|
|
2513
|
+
return this.index;
|
|
2514
|
+
}
|
|
2515
|
+
clear() {
|
|
2516
|
+
this.index = null;
|
|
2517
|
+
this.fingerprint = null;
|
|
2518
|
+
}
|
|
2519
|
+
};
|
|
2520
|
+
//#endregion
|
|
2521
|
+
//#region src/lib/pr-agent-format.ts
|
|
2522
|
+
function buildPrOverviewPayload(session) {
|
|
2523
|
+
const threads = session.existingComments ?? [];
|
|
2524
|
+
const reviews = session.existingReviews ?? [];
|
|
2525
|
+
const drafts = session.comments ?? [];
|
|
2526
|
+
let unresolved = 0;
|
|
2527
|
+
let resolved = 0;
|
|
2528
|
+
let outdated = 0;
|
|
2529
|
+
for (const t of threads) {
|
|
2530
|
+
if (t.isOutdated) outdated++;
|
|
2531
|
+
if (t.isResolved) resolved++;
|
|
2532
|
+
else unresolved++;
|
|
2533
|
+
}
|
|
2534
|
+
return {
|
|
2535
|
+
prMode: true,
|
|
2536
|
+
ref: session.ref,
|
|
2537
|
+
owner: session.owner,
|
|
2538
|
+
repo: session.repo,
|
|
2539
|
+
pullNumber: session.pullNumber,
|
|
2540
|
+
title: session.title,
|
|
2541
|
+
url: session.url,
|
|
2542
|
+
author: session.author,
|
|
2543
|
+
baseSha: session.baseSha,
|
|
2544
|
+
headSha: session.headSha,
|
|
2545
|
+
additions: session.additions,
|
|
2546
|
+
deletions: session.deletions,
|
|
2547
|
+
changedFiles: session.changedFiles,
|
|
2548
|
+
host: session.host,
|
|
2549
|
+
counts: {
|
|
2550
|
+
publishedThreads: threads.length,
|
|
2551
|
+
unresolvedThreads: unresolved,
|
|
2552
|
+
resolvedThreads: resolved,
|
|
2553
|
+
outdatedThreads: outdated,
|
|
2554
|
+
reviews: reviews.length,
|
|
2555
|
+
localDrafts: drafts.length,
|
|
2556
|
+
openDrafts: drafts.filter((d) => d.status === "open").length
|
|
2557
|
+
},
|
|
2558
|
+
submittedAt: session.submittedAt ?? null,
|
|
2559
|
+
submittedReviewId: session.submittedReviewId ?? null,
|
|
2560
|
+
submittedReviewUrl: session.submittedReviewUrl ?? null,
|
|
2561
|
+
authSource: session.authSource,
|
|
2562
|
+
patchBytes: Buffer.byteLength(session.diff ?? "", "utf8")
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
function truncateBody(body, maxChars, fullBody) {
|
|
2566
|
+
if (fullBody || maxChars == null) return {
|
|
2567
|
+
text: body,
|
|
2568
|
+
truncated: false
|
|
2569
|
+
};
|
|
2570
|
+
const limit = Math.max(0, maxChars);
|
|
2571
|
+
if (body.length <= limit) return {
|
|
2572
|
+
text: body,
|
|
2573
|
+
truncated: false
|
|
2574
|
+
};
|
|
2575
|
+
return {
|
|
2576
|
+
text: body.slice(0, limit) + "…",
|
|
2577
|
+
truncated: true
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
function paginatePrThreads(session, opts = {}) {
|
|
2581
|
+
const cursor = Math.max(0, opts.cursor ?? 0);
|
|
2582
|
+
const limit = Math.min(200, Math.max(1, opts.limit ?? 50));
|
|
2583
|
+
const bodyMax = Math.min(5e4, Math.max(0, opts.bodyMaxChars ?? 500));
|
|
2584
|
+
const pathFilter = opts.path?.toLowerCase();
|
|
2585
|
+
const authorFilter = opts.author?.toLowerCase();
|
|
2586
|
+
let filtered = session.existingComments ?? [];
|
|
2587
|
+
if (opts.unresolvedOnly) filtered = filtered.filter((t) => !t.isResolved);
|
|
2588
|
+
if (pathFilter) filtered = filtered.filter((t) => (t.path ?? "").toLowerCase().includes(pathFilter));
|
|
2589
|
+
if (authorFilter) filtered = filtered.filter((t) => (t.author?.login ?? "").toLowerCase() === authorFilter);
|
|
2590
|
+
const total = filtered.length;
|
|
2591
|
+
const slice = filtered.slice(cursor, cursor + limit);
|
|
2592
|
+
const threads = slice.map((t) => {
|
|
2593
|
+
const root = truncateBody(t.body ?? "", bodyMax, opts.fullBody);
|
|
2594
|
+
const replies = (t.replies ?? []).map((r) => {
|
|
2595
|
+
const rb = truncateBody(r.body ?? "", bodyMax, opts.fullBody);
|
|
2596
|
+
return {
|
|
2597
|
+
id: r.id,
|
|
2598
|
+
author: r.author?.login ?? null,
|
|
2599
|
+
body: rb.text,
|
|
2600
|
+
bodyTruncated: rb.truncated,
|
|
2601
|
+
createdAt: r.createdAt
|
|
2602
|
+
};
|
|
2603
|
+
});
|
|
2604
|
+
return {
|
|
2605
|
+
id: t.id,
|
|
2606
|
+
threadId: t.threadId,
|
|
2607
|
+
path: t.path,
|
|
2608
|
+
line: t.line,
|
|
2609
|
+
startLine: t.startLine,
|
|
2610
|
+
side: t.side,
|
|
2611
|
+
startSide: t.startSide,
|
|
2612
|
+
author: t.author?.login ?? null,
|
|
2613
|
+
resolved: Boolean(t.isResolved),
|
|
2614
|
+
outdated: Boolean(t.isOutdated),
|
|
2615
|
+
state: t.state,
|
|
2616
|
+
createdAt: t.createdAt,
|
|
2617
|
+
updatedAt: t.updatedAt,
|
|
2618
|
+
body: root.text,
|
|
2619
|
+
bodyTruncated: root.truncated,
|
|
2620
|
+
replyCount: replies.length,
|
|
2621
|
+
replies
|
|
2622
|
+
};
|
|
2623
|
+
});
|
|
2624
|
+
const end = cursor + slice.length;
|
|
2625
|
+
return {
|
|
2626
|
+
returned: threads.length,
|
|
2627
|
+
total,
|
|
2628
|
+
nextCursor: end < total ? end : null,
|
|
2629
|
+
threads
|
|
2630
|
+
};
|
|
2631
|
+
}
|
|
2632
|
+
function paginatePrReviews(session, opts = {}) {
|
|
2633
|
+
const cursor = Math.max(0, opts.cursor ?? 0);
|
|
2634
|
+
const limit = Math.min(100, Math.max(1, opts.limit ?? 50));
|
|
2635
|
+
const bodyMax = Math.min(5e4, Math.max(0, opts.bodyMaxChars ?? 500));
|
|
2636
|
+
let reviews = session.existingReviews ?? [];
|
|
2637
|
+
if (opts.state) {
|
|
2638
|
+
const s = opts.state.toUpperCase();
|
|
2639
|
+
reviews = reviews.filter((r) => r.state === s);
|
|
2640
|
+
}
|
|
2641
|
+
const total = reviews.length;
|
|
2642
|
+
const slice = reviews.slice(cursor, cursor + limit);
|
|
2643
|
+
const mapped = slice.map((r) => {
|
|
2644
|
+
const b = truncateBody(r.body ?? "", bodyMax, opts.fullBody);
|
|
2645
|
+
return {
|
|
2646
|
+
id: r.id,
|
|
2647
|
+
author: r.author?.login ?? null,
|
|
2648
|
+
state: r.state,
|
|
2649
|
+
submittedAt: r.submittedAt,
|
|
2650
|
+
htmlUrl: r.htmlUrl,
|
|
2651
|
+
commitId: r.commitId,
|
|
2652
|
+
body: b.text,
|
|
2653
|
+
bodyTruncated: b.truncated
|
|
2654
|
+
};
|
|
2655
|
+
});
|
|
2656
|
+
const end = cursor + slice.length;
|
|
2657
|
+
return {
|
|
2658
|
+
returned: mapped.length,
|
|
2659
|
+
total,
|
|
2660
|
+
nextCursor: end < total ? end : null,
|
|
2661
|
+
reviews: mapped
|
|
2662
|
+
};
|
|
2663
|
+
}
|
|
2664
|
+
function escapeAttr(value) {
|
|
2665
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
2666
|
+
}
|
|
2667
|
+
function cdata(value) {
|
|
2668
|
+
return value.replace(/]]>/g, "]]]]><![CDATA[>");
|
|
2669
|
+
}
|
|
2670
|
+
/**
|
|
2671
|
+
* Compact agent XML for published GitHub review threads (+ optional reviews).
|
|
2672
|
+
*/
|
|
2673
|
+
function formatPrReviewThreads(session, threads, reviews, page) {
|
|
2674
|
+
const prLabel = `${session.owner}/${session.repo}#${session.pullNumber}`;
|
|
2675
|
+
const lines = [];
|
|
2676
|
+
const pageAttrs = page ? ` returned="${page.returned}" total="${page.total}"${page.nextCursor == null ? "" : ` nextCursor="${page.nextCursor}"`}` : "";
|
|
2677
|
+
lines.push(`<pr-review-threads pr="${escapeAttr(prLabel)}" headSha="${escapeAttr(session.headSha)}" url="${escapeAttr(session.url)}"${pageAttrs}>`);
|
|
2678
|
+
lines.push(" <instructions>");
|
|
2679
|
+
lines.push(" Published GitHub review threads for this pull request (not local drafts).");
|
|
2680
|
+
lines.push(" Prefer addressing feedback via a local checkout + plan/implement cycle unless the user authorized live GitHub replies.");
|
|
2681
|
+
lines.push(" Only treat unresolved threads as actionable. resolved=\"true\" threads are historical.");
|
|
2682
|
+
lines.push(" </instructions>");
|
|
2683
|
+
for (const t of threads) {
|
|
2684
|
+
const lineAttr = t.startLine != null && t.line != null && t.startLine !== t.line ? ` line="${t.startLine}-${t.line}"` : t.line != null ? ` line="${t.line}"` : "";
|
|
2685
|
+
const sideAttr = t.side ? ` side="${t.side}"` : "";
|
|
2686
|
+
const pathAttr = t.path ? ` path="${escapeAttr(t.path)}"` : "";
|
|
2687
|
+
const authorAttr = t.author ? ` author="${escapeAttr(t.author)}"` : "";
|
|
2688
|
+
const threadIdAttr = t.threadId ? ` threadId="${escapeAttr(t.threadId)}"` : "";
|
|
2689
|
+
const stateAttr = t.state ? ` state="${t.state}"` : "";
|
|
2690
|
+
lines.push(` <thread id="${t.id}"${threadIdAttr}${pathAttr}${sideAttr}${lineAttr}${stateAttr} resolved="${t.resolved}" outdated="${t.outdated}" replyCount="${t.replyCount}"${authorAttr}>`);
|
|
2691
|
+
lines.push(` <body${t.bodyTruncated ? " truncated=\"true\"" : ""}><![CDATA[${cdata(t.body)}]]></body>`);
|
|
2692
|
+
if (t.replies.length > 0) {
|
|
2693
|
+
lines.push(" <replies>");
|
|
2694
|
+
for (const r of t.replies) {
|
|
2695
|
+
const ra = r.author ? ` author="${escapeAttr(r.author)}"` : "";
|
|
2696
|
+
lines.push(` <reply id="${r.id}"${ra}${r.bodyTruncated ? " truncated=\"true\"" : ""}><![CDATA[${cdata(r.body)}]]></reply>`);
|
|
2697
|
+
}
|
|
2698
|
+
lines.push(" </replies>");
|
|
2699
|
+
}
|
|
2700
|
+
lines.push(" </thread>");
|
|
2701
|
+
}
|
|
2702
|
+
if (reviews && reviews.length > 0) {
|
|
2703
|
+
lines.push(" <reviews>");
|
|
2704
|
+
for (const r of reviews) {
|
|
2705
|
+
const ra = r.author ? ` author="${escapeAttr(r.author)}"` : "";
|
|
2706
|
+
lines.push(` <review id="${r.id}" state="${r.state}"${ra}${r.submittedAt ? ` submittedAt="${escapeAttr(r.submittedAt)}"` : ""}>`);
|
|
2707
|
+
if (r.body) lines.push(` <body${r.bodyTruncated ? " truncated=\"true\"" : ""}><![CDATA[${cdata(r.body)}]]></body>`);
|
|
2708
|
+
lines.push(" </review>");
|
|
2709
|
+
}
|
|
2710
|
+
lines.push(" </reviews>");
|
|
2711
|
+
}
|
|
2712
|
+
lines.push("</pr-review-threads>");
|
|
2713
|
+
return lines.join("\n");
|
|
2714
|
+
}
|
|
2715
|
+
function formatPrReviews(session, reviews, page) {
|
|
2716
|
+
return formatPrReviewThreads(session, [], reviews, page);
|
|
2717
|
+
}
|
|
2718
|
+
//#endregion
|
|
1980
2719
|
//#region src/server.ts
|
|
1981
2720
|
const MIME_TYPES = {
|
|
1982
2721
|
".html": "text/html",
|
|
@@ -1993,6 +2732,20 @@ const MIME_TYPES = {
|
|
|
1993
2732
|
".bmp": "image/bmp",
|
|
1994
2733
|
".avif": "image/avif"
|
|
1995
2734
|
};
|
|
2735
|
+
const MAX_AVATAR_BYTES = 5 * 1024 * 1024;
|
|
2736
|
+
function collectPrAvatarUrls(session) {
|
|
2737
|
+
const urls = /* @__PURE__ */ new Set();
|
|
2738
|
+
const add = (avatarUrl) => {
|
|
2739
|
+
if (avatarUrl) urls.add(avatarUrl);
|
|
2740
|
+
};
|
|
2741
|
+
add(session.author?.avatarUrl);
|
|
2742
|
+
for (const review of session.existingReviews ?? []) add(review.author?.avatarUrl);
|
|
2743
|
+
for (const comment of session.existingComments) {
|
|
2744
|
+
add(comment.author?.avatarUrl);
|
|
2745
|
+
for (const reply of comment.replies) add(reply.author?.avatarUrl);
|
|
2746
|
+
}
|
|
2747
|
+
return urls;
|
|
2748
|
+
}
|
|
1996
2749
|
function isCustomMode(opts) {
|
|
1997
2750
|
return opts.revisions.length > 0 || opts.pathspecs.length > 0 || opts.showMode;
|
|
1998
2751
|
}
|
|
@@ -2003,6 +2756,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2003
2756
|
const store = commentStore ?? new FileCommentStore();
|
|
2004
2757
|
const plans = planStore ?? new FilePlanStore();
|
|
2005
2758
|
const prStore = prSessionStore ?? new FilePrSessionStore();
|
|
2759
|
+
const agentDiffCache = new AgentDiffIndexCache();
|
|
2006
2760
|
const uiStateStore = new FileUiStateStore();
|
|
2007
2761
|
const viewedFiles = /* @__PURE__ */ new Set();
|
|
2008
2762
|
/** Agent-reported progress events for the human UI (SSE `agent-progress`). */
|
|
@@ -2167,20 +2921,105 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2167
2921
|
overview: result.overview
|
|
2168
2922
|
});
|
|
2169
2923
|
});
|
|
2170
|
-
|
|
2924
|
+
async function resolveAgentPatch() {
|
|
2925
|
+
if (prMode) return (await prStore.get())?.diff ?? "";
|
|
2926
|
+
const stagedQuery = void 0;
|
|
2927
|
+
const untrackedQuery = void 0;
|
|
2928
|
+
const staged = stagedQuery === void 0 ? diffOpts.staged : false;
|
|
2929
|
+
const untracked = untrackedQuery === void 0 ? diffOpts.includeUntracked : false;
|
|
2930
|
+
return (await executeDiffWithMeta(customMode ? diffOpts : {
|
|
2931
|
+
...diffOpts,
|
|
2932
|
+
staged,
|
|
2933
|
+
includeUntracked: untracked
|
|
2934
|
+
})).patch ?? "";
|
|
2935
|
+
}
|
|
2936
|
+
function parseUInt(value, fallback) {
|
|
2937
|
+
if (value == null || value === "") return fallback;
|
|
2938
|
+
const n = Number(value);
|
|
2939
|
+
return Number.isFinite(n) && n >= 0 ? Math.trunc(n) : fallback;
|
|
2940
|
+
}
|
|
2941
|
+
app.get("/api/diff/summary", async (c) => {
|
|
2942
|
+
const patch = await resolveAgentPatch();
|
|
2943
|
+
const summary = indexSummary(agentDiffCache.getOrBuild(patch));
|
|
2944
|
+
if (!prMode) return c.json(summary);
|
|
2945
|
+
const session = await prStore.get();
|
|
2946
|
+
if (!session) return c.json(summary);
|
|
2947
|
+
return c.json({
|
|
2948
|
+
...summary,
|
|
2949
|
+
prMode: true,
|
|
2950
|
+
owner: session.owner,
|
|
2951
|
+
repo: session.repo,
|
|
2952
|
+
pullNumber: session.pullNumber,
|
|
2953
|
+
title: session.title,
|
|
2954
|
+
url: session.url,
|
|
2955
|
+
baseSha: session.baseSha,
|
|
2956
|
+
headSha: session.headSha
|
|
2957
|
+
});
|
|
2958
|
+
});
|
|
2959
|
+
app.get("/api/diff/files", async (c) => {
|
|
2960
|
+
const patch = await resolveAgentPatch();
|
|
2961
|
+
const index = agentDiffCache.getOrBuild(patch);
|
|
2962
|
+
const cursor = parseUInt(c.req.query("cursor"), 0);
|
|
2963
|
+
const limit = parseUInt(c.req.query("limit"), 100);
|
|
2964
|
+
return c.json(indexFiles(index, cursor, limit));
|
|
2965
|
+
});
|
|
2966
|
+
app.get("/api/diff/hunks", async (c) => {
|
|
2967
|
+
const patch = await resolveAgentPatch();
|
|
2968
|
+
const index = agentDiffCache.getOrBuild(patch);
|
|
2969
|
+
const file = parseUInt(c.req.query("file"), 0);
|
|
2970
|
+
const cursor = parseUInt(c.req.query("cursor"), 0);
|
|
2971
|
+
const limit = parseUInt(c.req.query("limit"), 100);
|
|
2972
|
+
const generationRaw = c.req.query("generation");
|
|
2973
|
+
const result = indexHunks(index, file, cursor, limit, generationRaw != null && generationRaw !== "" ? parseUInt(generationRaw, 0) : void 0);
|
|
2974
|
+
if ("status" in result) return c.json({ error: result.error }, result.status);
|
|
2975
|
+
return c.json(result);
|
|
2976
|
+
});
|
|
2977
|
+
app.get("/api/diff/slice", async (c) => {
|
|
2978
|
+
const patch = await resolveAgentPatch();
|
|
2979
|
+
const index = agentDiffCache.getOrBuild(patch);
|
|
2980
|
+
const file = parseUInt(c.req.query("file"), 0);
|
|
2981
|
+
const start = parseUInt(c.req.query("start"), 0);
|
|
2982
|
+
const maxLines = parseUInt(c.req.query("maxLines"), 120);
|
|
2983
|
+
const maxBytes = parseUInt(c.req.query("maxBytes"), 256 * 1024);
|
|
2984
|
+
const generationRaw = c.req.query("generation");
|
|
2985
|
+
const result = indexSlice(index, file, start, maxLines, maxBytes, generationRaw != null && generationRaw !== "" ? parseUInt(generationRaw, 0) : void 0);
|
|
2986
|
+
if ("status" in result) return c.json({ error: result.error }, result.status);
|
|
2987
|
+
return c.json(result);
|
|
2988
|
+
});
|
|
2989
|
+
app.get("/api/diff/search", async (c) => {
|
|
2990
|
+
const patch = await resolveAgentPatch();
|
|
2991
|
+
const index = agentDiffCache.getOrBuild(patch);
|
|
2992
|
+
const q = c.req.query("q") ?? "";
|
|
2993
|
+
const file = parseUInt(c.req.query("file"), 0);
|
|
2994
|
+
const row = parseUInt(c.req.query("row"), 0);
|
|
2995
|
+
const limit = parseUInt(c.req.query("limit"), 100);
|
|
2996
|
+
const maxBytes = parseUInt(c.req.query("maxBytes"), 256 * 1024);
|
|
2997
|
+
const generationRaw = c.req.query("generation");
|
|
2998
|
+
const result = indexSearch(index, q, file, row, limit, maxBytes, generationRaw != null && generationRaw !== "" ? parseUInt(generationRaw, 0) : void 0);
|
|
2999
|
+
if ("status" in result) return c.json({ error: result.error }, result.status);
|
|
3000
|
+
return c.json(result);
|
|
3001
|
+
});
|
|
3002
|
+
const resolveFileVersion = async (path, version) => {
|
|
3003
|
+
if (!prMode) return getFileContent(path, version);
|
|
3004
|
+
const session = await prStore.get();
|
|
3005
|
+
if (!session) return null;
|
|
3006
|
+
const sha = version === "old" ? session.baseSha : session.headSha;
|
|
3007
|
+
return fetchPrFileContentViaGh(resolvedFromSession(session), path, sha);
|
|
3008
|
+
};
|
|
3009
|
+
app.get("/api/file-content", async (c) => {
|
|
2171
3010
|
const path = c.req.query("path");
|
|
2172
3011
|
const version = c.req.query("version");
|
|
2173
3012
|
if (!path || !version) return c.json({ error: "Missing path or version" }, 400);
|
|
2174
|
-
const content =
|
|
3013
|
+
const content = await resolveFileVersion(path, version);
|
|
2175
3014
|
if (!content) return c.json({ error: "File not found" }, 404);
|
|
2176
3015
|
const contentType = MIME_TYPES[extname(path)] || "application/octet-stream";
|
|
2177
3016
|
return new Response(new Uint8Array(content), { headers: { "Content-Type": contentType } });
|
|
2178
3017
|
});
|
|
2179
|
-
app.get("/api/file-text", (c) => {
|
|
3018
|
+
app.get("/api/file-text", async (c) => {
|
|
2180
3019
|
const path = c.req.query("path");
|
|
2181
3020
|
const version = c.req.query("version");
|
|
2182
3021
|
if (!path || !version) return c.json({ error: "Missing path or version" }, 400);
|
|
2183
|
-
const buffer =
|
|
3022
|
+
const buffer = await resolveFileVersion(path, version);
|
|
2184
3023
|
if (!buffer) return c.json({
|
|
2185
3024
|
content: "",
|
|
2186
3025
|
missing: true
|
|
@@ -2615,13 +3454,93 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2615
3454
|
authSource: session.authSource
|
|
2616
3455
|
});
|
|
2617
3456
|
});
|
|
3457
|
+
/**
|
|
3458
|
+
* Serve GitHub avatars from this origin. Privacy extensions commonly block
|
|
3459
|
+
* embedded githubusercontent.com requests even though the same URL works as
|
|
3460
|
+
* a top-level navigation. Only URLs already supplied by the active GitHub
|
|
3461
|
+
* session are accepted, which keeps this endpoint from becoming an SSRF
|
|
3462
|
+
* proxy.
|
|
3463
|
+
*/
|
|
3464
|
+
app.get("/api/gh/avatar", async (c) => {
|
|
3465
|
+
if (!prMode) return notInPrMode(c);
|
|
3466
|
+
const session = await prStore.get();
|
|
3467
|
+
if (!session) return notInPrMode(c);
|
|
3468
|
+
const url = c.req.query("url");
|
|
3469
|
+
if (!url || !collectPrAvatarUrls(session).has(url)) return c.json({ error: "Avatar URL is not part of this PR session" }, 403);
|
|
3470
|
+
let parsed;
|
|
3471
|
+
try {
|
|
3472
|
+
parsed = new URL(url);
|
|
3473
|
+
} catch {
|
|
3474
|
+
return c.json({ error: "Invalid avatar URL" }, 400);
|
|
3475
|
+
}
|
|
3476
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return c.json({ error: "Invalid avatar URL protocol" }, 400);
|
|
3477
|
+
try {
|
|
3478
|
+
const response = await fetch(parsed, { redirect: "error" });
|
|
3479
|
+
if (!response.ok) return c.json({ error: `Avatar request failed with HTTP ${response.status}` }, 502);
|
|
3480
|
+
const contentType = response.headers.get("content-type")?.split(";")[0]?.trim() ?? "";
|
|
3481
|
+
if (!contentType.startsWith("image/")) return c.json({ error: "Avatar response was not an image" }, 502);
|
|
3482
|
+
if (Number(response.headers.get("content-length") ?? "0") > MAX_AVATAR_BYTES) return c.json({ error: "Avatar is too large" }, 413);
|
|
3483
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
3484
|
+
if (bytes.byteLength > MAX_AVATAR_BYTES) return c.json({ error: "Avatar is too large" }, 413);
|
|
3485
|
+
return new Response(bytes, { headers: {
|
|
3486
|
+
"Content-Type": contentType,
|
|
3487
|
+
"Cache-Control": "private, max-age=3600",
|
|
3488
|
+
"X-Content-Type-Options": "nosniff"
|
|
3489
|
+
} });
|
|
3490
|
+
} catch (error) {
|
|
3491
|
+
return c.json({ error: error?.message ?? "Failed to load avatar" }, 502);
|
|
3492
|
+
}
|
|
3493
|
+
});
|
|
3494
|
+
/** Slim PR metadata for agents — no threads, reviews, or patch. */
|
|
3495
|
+
app.get("/api/gh/overview", async (c) => {
|
|
3496
|
+
if (!prMode) return notInPrMode(c);
|
|
3497
|
+
const session = await prStore.get();
|
|
3498
|
+
if (!session) return notInPrMode(c);
|
|
3499
|
+
return c.json(buildPrOverviewPayload(session));
|
|
3500
|
+
});
|
|
3501
|
+
/**
|
|
3502
|
+
* Paged published review threads. Prefer this over /api/gh/session for agents.
|
|
3503
|
+
* Query: unresolvedOnly, path, author, cursor, limit, bodyMaxChars, fullBody, format=xml|json
|
|
3504
|
+
*/
|
|
3505
|
+
app.get("/api/gh/threads", async (c) => {
|
|
3506
|
+
if (!prMode) return notInPrMode(c);
|
|
3507
|
+
const session = await prStore.get();
|
|
3508
|
+
if (!session) return notInPrMode(c);
|
|
3509
|
+
const page = paginatePrThreads(session, {
|
|
3510
|
+
unresolvedOnly: c.req.query("unresolvedOnly") === "true" || c.req.query("unresolved") === "1",
|
|
3511
|
+
path: c.req.query("path") ?? void 0,
|
|
3512
|
+
author: c.req.query("author") ?? void 0,
|
|
3513
|
+
cursor: parseUInt(c.req.query("cursor"), 0),
|
|
3514
|
+
limit: parseUInt(c.req.query("limit"), 50),
|
|
3515
|
+
bodyMaxChars: parseUInt(c.req.query("bodyMaxChars"), 500),
|
|
3516
|
+
fullBody: c.req.query("fullBody") === "true" || c.req.query("fullBody") === "1"
|
|
3517
|
+
});
|
|
3518
|
+
if ((c.req.query("format") ?? "json").toLowerCase() === "xml") {
|
|
3519
|
+
c.header("Content-Type", "application/xml; charset=utf-8");
|
|
3520
|
+
return c.body(formatPrReviewThreads(session, page.threads, void 0, page));
|
|
3521
|
+
}
|
|
3522
|
+
return c.json(page);
|
|
3523
|
+
});
|
|
3524
|
+
/** Paged submitted review verdicts / overall comments. */
|
|
3525
|
+
app.get("/api/gh/reviews", async (c) => {
|
|
3526
|
+
if (!prMode) return notInPrMode(c);
|
|
3527
|
+
const session = await prStore.get();
|
|
3528
|
+
if (!session) return notInPrMode(c);
|
|
3529
|
+
const page = paginatePrReviews(session, {
|
|
3530
|
+
cursor: parseUInt(c.req.query("cursor"), 0),
|
|
3531
|
+
limit: parseUInt(c.req.query("limit"), 50),
|
|
3532
|
+
bodyMaxChars: parseUInt(c.req.query("bodyMaxChars"), 500),
|
|
3533
|
+
fullBody: c.req.query("fullBody") === "true" || c.req.query("fullBody") === "1",
|
|
3534
|
+
state: c.req.query("state") ?? void 0
|
|
3535
|
+
});
|
|
3536
|
+
if ((c.req.query("format") ?? "json").toLowerCase() === "xml") {
|
|
3537
|
+
c.header("Content-Type", "application/xml; charset=utf-8");
|
|
3538
|
+
return c.body(formatPrReviews(session, page.reviews, page));
|
|
3539
|
+
}
|
|
3540
|
+
return c.json(page);
|
|
3541
|
+
});
|
|
2618
3542
|
const syncExistingPrReviewData = async (session) => {
|
|
2619
|
-
const resolved =
|
|
2620
|
-
owner: session.owner,
|
|
2621
|
-
repo: session.repo,
|
|
2622
|
-
pullNumber: session.pullNumber,
|
|
2623
|
-
ref: session.ref
|
|
2624
|
-
};
|
|
3543
|
+
const resolved = resolvedFromSession(session);
|
|
2625
3544
|
let existingReviews = await fetchExistingReviewsViaGh(resolved);
|
|
2626
3545
|
const optimisticReview = session.submittedReviewId == null ? void 0 : session.existingReviews?.find((review) => review.id === session.submittedReviewId);
|
|
2627
3546
|
if (optimisticReview && !existingReviews.some((review) => review.id === optimisticReview.id) && session.submittedAt != null && Date.now() - session.submittedAt < 12e4) existingReviews = [optimisticReview, ...existingReviews];
|
|
@@ -2677,13 +3596,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2677
3596
|
const session = await prStore.get();
|
|
2678
3597
|
if (!session) return notInPrMode(c);
|
|
2679
3598
|
try {
|
|
2680
|
-
const { fetchPrChecks } = await import("./github-
|
|
2681
|
-
const checks = await fetchPrChecks(
|
|
2682
|
-
owner: session.owner,
|
|
2683
|
-
repo: session.repo,
|
|
2684
|
-
pullNumber: session.pullNumber,
|
|
2685
|
-
ref: session.ref
|
|
2686
|
-
}, session.headSha);
|
|
3599
|
+
const { fetchPrChecks } = await import("./github-DL7-BqhD.mjs");
|
|
3600
|
+
const checks = await fetchPrChecks(resolvedFromSession(session), session.headSha);
|
|
2687
3601
|
const summary = {
|
|
2688
3602
|
total: checks.length,
|
|
2689
3603
|
success: checks.filter((x) => x.state === "success").length,
|
|
@@ -2714,14 +3628,9 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2714
3628
|
const text = typeof body.body === "string" ? body.body.trim() : "";
|
|
2715
3629
|
if (!text) return c.json({ error: "body is required" }, 400);
|
|
2716
3630
|
try {
|
|
2717
|
-
const { replyToPrComment } = await import("./github-
|
|
3631
|
+
const { replyToPrComment } = await import("./github-DL7-BqhD.mjs");
|
|
2718
3632
|
const result = await replyToPrComment({
|
|
2719
|
-
resolved:
|
|
2720
|
-
owner: session.owner,
|
|
2721
|
-
repo: session.repo,
|
|
2722
|
-
pullNumber: session.pullNumber,
|
|
2723
|
-
ref: session.ref
|
|
2724
|
-
},
|
|
3633
|
+
resolved: resolvedFromSession(session),
|
|
2725
3634
|
inReplyTo: id,
|
|
2726
3635
|
body: text
|
|
2727
3636
|
});
|
|
@@ -2757,12 +3666,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2757
3666
|
const text = typeof request.body === "string" ? request.body.trim() : "";
|
|
2758
3667
|
if (!text) return c.json({ error: "body is required" }, 400);
|
|
2759
3668
|
const result = await updatePrReviewComment({
|
|
2760
|
-
resolved:
|
|
2761
|
-
owner: session.owner,
|
|
2762
|
-
repo: session.repo,
|
|
2763
|
-
pullNumber: session.pullNumber,
|
|
2764
|
-
ref: session.ref
|
|
2765
|
-
},
|
|
3669
|
+
resolved: resolvedFromSession(session),
|
|
2766
3670
|
commentId: id,
|
|
2767
3671
|
body: text
|
|
2768
3672
|
});
|
|
@@ -2781,12 +3685,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2781
3685
|
const id = Number(c.req.param("id"));
|
|
2782
3686
|
if (!Number.isFinite(id)) return c.json({ error: "Invalid comment id" }, 400);
|
|
2783
3687
|
const result = await deletePrReviewComment({
|
|
2784
|
-
resolved:
|
|
2785
|
-
owner: session.owner,
|
|
2786
|
-
repo: session.repo,
|
|
2787
|
-
pullNumber: session.pullNumber,
|
|
2788
|
-
ref: session.ref
|
|
2789
|
-
},
|
|
3688
|
+
resolved: resolvedFromSession(session),
|
|
2790
3689
|
commentId: id
|
|
2791
3690
|
});
|
|
2792
3691
|
if (!result.ok) return c.json({ error: result.error ?? "Delete failed" }, 502);
|
|
@@ -2806,7 +3705,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2806
3705
|
if (typeof request.resolved !== "boolean") return c.json({ error: "resolved must be a boolean" }, 400);
|
|
2807
3706
|
const result = await setPrReviewThreadResolved({
|
|
2808
3707
|
threadId,
|
|
2809
|
-
resolved: request.resolved
|
|
3708
|
+
resolved: request.resolved,
|
|
3709
|
+
host: session.host
|
|
2810
3710
|
});
|
|
2811
3711
|
if (!result.ok) return c.json({ error: result.error ?? "Thread update failed" }, 502);
|
|
2812
3712
|
const next = await syncExistingPrReviewData(session);
|
|
@@ -2848,6 +3748,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2848
3748
|
const body = await c.req.json();
|
|
2849
3749
|
const session = await prStore.get();
|
|
2850
3750
|
if (!session) return notInPrMode(c);
|
|
3751
|
+
const severityRaw = body.severity;
|
|
3752
|
+
const severity = severityRaw === "blocking" || severityRaw === "nit" || severityRaw === "question" || severityRaw === "praise" || severityRaw === "none" ? severityRaw === "none" ? void 0 : severityRaw : void 0;
|
|
2851
3753
|
const comment = {
|
|
2852
3754
|
id: crypto.randomUUID(),
|
|
2853
3755
|
filePath: body.filePath,
|
|
@@ -2858,7 +3760,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2858
3760
|
body: body.body,
|
|
2859
3761
|
status: "open",
|
|
2860
3762
|
createdAt: Date.now(),
|
|
2861
|
-
replies: []
|
|
3763
|
+
replies: [],
|
|
3764
|
+
...severity ? { severity } : {}
|
|
2862
3765
|
};
|
|
2863
3766
|
const next = {
|
|
2864
3767
|
...session,
|
|
@@ -2937,7 +3840,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2937
3840
|
if (decision !== "approve" && decision !== "comment" && decision !== "request-changes" && decision !== "draft") return c.json({ error: "decision must be one of: approve, comment, request-changes, draft" }, 400);
|
|
2938
3841
|
const generalBody = typeof body.body === "string" ? body.body : "";
|
|
2939
3842
|
if (body.dryRun === true) {
|
|
2940
|
-
const { buildReviewPayload } = await import("./github-
|
|
3843
|
+
const { buildReviewPayload } = await import("./github-DL7-BqhD.mjs");
|
|
2941
3844
|
const payload = buildReviewPayload({
|
|
2942
3845
|
decision,
|
|
2943
3846
|
body: generalBody,
|
|
@@ -2952,12 +3855,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2952
3855
|
});
|
|
2953
3856
|
}
|
|
2954
3857
|
const result = await submitReview({
|
|
2955
|
-
resolved:
|
|
2956
|
-
owner: session.owner,
|
|
2957
|
-
repo: session.repo,
|
|
2958
|
-
pullNumber: session.pullNumber,
|
|
2959
|
-
ref: session.ref
|
|
2960
|
-
},
|
|
3858
|
+
resolved: resolvedFromSession(session),
|
|
2961
3859
|
decision,
|
|
2962
3860
|
body: generalBody,
|
|
2963
3861
|
comments: session.comments ?? []
|
|
@@ -2966,12 +3864,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2966
3864
|
let existingComments = session.existingComments;
|
|
2967
3865
|
let existingReviews = session.existingReviews ?? [];
|
|
2968
3866
|
try {
|
|
2969
|
-
const resolved =
|
|
2970
|
-
owner: session.owner,
|
|
2971
|
-
repo: session.repo,
|
|
2972
|
-
pullNumber: session.pullNumber,
|
|
2973
|
-
ref: session.ref
|
|
2974
|
-
};
|
|
3867
|
+
const resolved = resolvedFromSession(session);
|
|
2975
3868
|
existingReviews = await fetchExistingReviewsViaGh(resolved);
|
|
2976
3869
|
existingComments = await fetchExistingCommentsViaGh(resolved, existingReviews);
|
|
2977
3870
|
} catch {}
|