diffing 0.10.1 → 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-7DUbPdeW.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-DRWEgsMs.mjs → doctor-DhuZFVwO.mjs} +1 -1
- package/dist/{github-C27GDU9I.mjs → github-CzxbZnhh.mjs} +32 -1
- package/dist/github-DL7-BqhD.mjs +2 -0
- package/dist/{mcp-D4cwru99.mjs → mcp-BBDdXGOZ.mjs} +212 -28
- package/dist/{server-CRk40DPV.mjs → server-D-gJ7owz.mjs} +938 -11
- 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-8THGZ7Nu.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,6 +3454,91 @@ 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
3543
|
const resolved = resolvedFromSession(session);
|
|
2620
3544
|
let existingReviews = await fetchExistingReviewsViaGh(resolved);
|
|
@@ -2672,7 +3596,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2672
3596
|
const session = await prStore.get();
|
|
2673
3597
|
if (!session) return notInPrMode(c);
|
|
2674
3598
|
try {
|
|
2675
|
-
const { fetchPrChecks } = await import("./github-
|
|
3599
|
+
const { fetchPrChecks } = await import("./github-DL7-BqhD.mjs");
|
|
2676
3600
|
const checks = await fetchPrChecks(resolvedFromSession(session), session.headSha);
|
|
2677
3601
|
const summary = {
|
|
2678
3602
|
total: checks.length,
|
|
@@ -2704,7 +3628,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2704
3628
|
const text = typeof body.body === "string" ? body.body.trim() : "";
|
|
2705
3629
|
if (!text) return c.json({ error: "body is required" }, 400);
|
|
2706
3630
|
try {
|
|
2707
|
-
const { replyToPrComment } = await import("./github-
|
|
3631
|
+
const { replyToPrComment } = await import("./github-DL7-BqhD.mjs");
|
|
2708
3632
|
const result = await replyToPrComment({
|
|
2709
3633
|
resolved: resolvedFromSession(session),
|
|
2710
3634
|
inReplyTo: id,
|
|
@@ -2824,6 +3748,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2824
3748
|
const body = await c.req.json();
|
|
2825
3749
|
const session = await prStore.get();
|
|
2826
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;
|
|
2827
3753
|
const comment = {
|
|
2828
3754
|
id: crypto.randomUUID(),
|
|
2829
3755
|
filePath: body.filePath,
|
|
@@ -2834,7 +3760,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2834
3760
|
body: body.body,
|
|
2835
3761
|
status: "open",
|
|
2836
3762
|
createdAt: Date.now(),
|
|
2837
|
-
replies: []
|
|
3763
|
+
replies: [],
|
|
3764
|
+
...severity ? { severity } : {}
|
|
2838
3765
|
};
|
|
2839
3766
|
const next = {
|
|
2840
3767
|
...session,
|
|
@@ -2913,7 +3840,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2913
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);
|
|
2914
3841
|
const generalBody = typeof body.body === "string" ? body.body : "";
|
|
2915
3842
|
if (body.dryRun === true) {
|
|
2916
|
-
const { buildReviewPayload } = await import("./github-
|
|
3843
|
+
const { buildReviewPayload } = await import("./github-DL7-BqhD.mjs");
|
|
2917
3844
|
const payload = buildReviewPayload({
|
|
2918
3845
|
decision,
|
|
2919
3846
|
body: generalBody,
|