diffing 0.10.1 → 0.10.3
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-RJ5s4n77.mjs} +10 -8
- package/dist/cli-gh-B8HybRWZ.mjs +313 -0
- package/dist/cli.mjs +3 -3
- package/dist/client/assets/{angular-html-DdAR2u-k.js → angular-html-BFXVRkhg.js} +1 -1
- package/dist/client/assets/{angular-ts-Dz_vWKSu.js → angular-ts-bUwtWqya.js} +1 -1
- package/dist/client/assets/{apl-BJ5QXrIk.js → apl-bnOQInIz.js} +1 -1
- package/dist/client/assets/{arc-D9erSW6x.js → arc-CRYHn0oW.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-CCQWZLXR.js → architectureDiagram-3BPJPVTR-BLK33h0-.js} +1 -1
- package/dist/client/assets/{astro-BQAej5a5.js → astro-BpPieWCw.js} +1 -1
- package/dist/client/assets/{blade-DkAmuwDx.js → blade-B804X9BP.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-vwjxNybI.js → blockDiagram-GPEHLZMM-VZc4C656.js} +1 -1
- package/dist/client/assets/{c-DG5p-ChJ.js → c-Cyh0ngfN.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-DJ_hVlh4.js → c4Diagram-AAUBKEIU-9t9UJlU3.js} +1 -1
- package/dist/client/assets/channel-CdyG8Wtt.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-CiLowxCo.js → chunk-2J33WTMH-LudE8z-b.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-H3jjMR5Y.js → chunk-3OPIFGDE-BxaQxo50.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-Dk0dk4n5.js → chunk-4BX2VUAB-BfHcXCJ6.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-nbLiUMJS.js → chunk-55IACEB6-o0ADQlnq.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-DZ8RgHg8.js → chunk-5ZQYHXKU-E1qBkOlk.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-DDe5dqJV.js → chunk-727SXJPM-BitjzJ_b.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DmpNclOr.js → chunk-AQP2D5EJ-B5pw4PfB.js} +1 -1
- package/dist/client/assets/{chunk-BSJP7CBP-BKQKvtIj.js → chunk-BSJP7CBP-CoL841vB.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-D27t4cyv.js → chunk-CSCIHK7Q-Dw4jsICo.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-Bu3LA663.js → chunk-FMBD7UC4-hpHFrNoE.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-Ed5HNXRW.js → chunk-KSCS5N6A-D67-Zs6D.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-DLjJZgf8.js → chunk-L5ZTLDWV-Brr6wA9I.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-B-cs89SM.js → chunk-LZXEDZCA-e3dTHwrC.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-D_kPSuUA.js → chunk-ND2GUHAM-Cj2MlAOf.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-D1u3pjq-.js → chunk-NZK2D7GU-CEyXOVMI.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-Mt0cnPSf.js → chunk-O5CBEL6O-CymJeKCI.js} +1 -1
- package/dist/client/assets/chunk-QZHKN3VN-DycW3jgO.js +1 -0
- package/dist/client/assets/chunk-WU5MYG2G-BXFsAzYU.js +1 -0
- package/dist/client/assets/{chunk-XPW4576I-Bt8a8x28.js → chunk-XPW4576I-DfoZU8hD.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-BxVI8nIr.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DZHTBTt9.js +1 -0
- package/dist/client/assets/{cobol-Daidj_5Y.js → cobol-D0BZ7BzH.js} +1 -1
- package/dist/client/assets/{coffee-DUMdB2Cr.js → coffee-BAPNIyTD.js} +1 -1
- package/dist/client/assets/{cose-bilkent-S5V4N54A-DVUDLXie.js → cose-bilkent-S5V4N54A-wCBDLLgv.js} +1 -1
- package/dist/client/assets/{cpp-C2FeX1Mc.js → cpp-D54qUqoX.js} +1 -1
- package/dist/client/assets/{crystal-D4yb9blJ.js → crystal-BvYuHO8u.js} +1 -1
- package/dist/client/assets/{css-B5cr0U9F.js → css-BwYPZ1qE.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-DdZWb-Ee.js → dagre-BM42HDAG-DSO2rfSO.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-C9DOZt92.js → diagram-2AECGRRQ-OO89Fatz.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-Bbc07UKr.js → diagram-5GNKFQAL-CARudkst.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-C-lUzY3m.js → diagram-KO2AKTUF-MMqM8Xip.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-D-OjEFhp.js → diagram-LMA3HP47-CA7_Ug5b.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-BjtWGvrf.js → diagram-OG6HWLK6-BAb7zakI.js} +1 -1
- package/dist/client/assets/{dist-BXZygPdX.js → dist-CP7b6OkU.js} +1 -1
- package/dist/client/assets/{edge-RCQWcWNp.js → edge-BbeCKm9i.js} +1 -1
- package/dist/client/assets/{elixir-DwKyVg2X.js → elixir-C2M5DZQF.js} +1 -1
- package/dist/client/assets/{elm-DjR33IVs.js → elm-CwqP8u0T.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-C5T4ywLG.js → erDiagram-TEJ5UH35-Jy0Sqs_1.js} +1 -1
- package/dist/client/assets/{erb-BnhUH1Hk.js → erb-C3C66A5u.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-BJmjm7Kx.js → flowDiagram-I6XJVG4X-D9L8qr8V.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-DREPnzu5.js → ganttDiagram-6RSMTGT7-CGaqGarh.js} +1 -1
- package/dist/client/assets/{git-rebase-DzXoSDXA.js → git-rebase-BQxiUc18.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DIR4G_-_.js → gitGraphDiagram-PVQCEYII-RAteUysV.js} +1 -1
- package/dist/client/assets/{glimmer-js-HKnZpCn_.js → glimmer-js-CSiwvg27.js} +1 -1
- package/dist/client/assets/{glimmer-ts-DedV7rWs.js → glimmer-ts-Dpvu35Rk.js} +1 -1
- package/dist/client/assets/{glsl-Bz-0Alp2.js → glsl-DusmoVD0.js} +1 -1
- package/dist/client/assets/{graphql-DSKkDYCD.js → graphql-B4CCj0BK.js} +1 -1
- package/dist/client/assets/{hack-CnaHZm8k.js → hack-BNgw8w6u.js} +1 -1
- package/dist/client/assets/{haml-huGFYKlj.js → haml-BhHHy_kW.js} +1 -1
- package/dist/client/assets/{handlebars-CKmgzfWY.js → handlebars-BBUsDvYG.js} +1 -1
- package/dist/client/assets/{html-B9Ih_dUU.js → html-DMohn6Yq.js} +1 -1
- package/dist/client/assets/{html-derivative-YabD-M5d.js → html-derivative-BY6zEZAg.js} +1 -1
- package/dist/client/assets/{http-Bdu12JYX.js → http-BHAVGGfJ.js} +1 -1
- package/dist/client/assets/{hurl-twZzDTNs.js → hurl-B2q8TNaP.js} +1 -1
- package/dist/client/assets/index-CguUPVUR.css +1 -0
- package/dist/client/assets/{index-BktdZRWB.js → index-CxN77Qol.js} +115 -115
- package/dist/client/assets/{infoDiagram-5YYISTIA-D2ty2R-i.js → infoDiagram-5YYISTIA-DCyKTiIZ.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-Ccnc9xlE.js → ishikawaDiagram-YF4QCWOH-BMTEXPAX.js} +1 -1
- package/dist/client/assets/{java-_5trO-p9.js → java-DHj6gaNP.js} +1 -1
- package/dist/client/assets/{javascript-BOQn0ikE.js → javascript-CbF7u-DE.js} +1 -1
- package/dist/client/assets/{jinja-BgY-0IBz.js → jinja-MOxap5pr.js} +1 -1
- package/dist/client/assets/{jison-BLNTmT1_.js → jison-XUjMP-LK.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-W7eWXlw0.js → journeyDiagram-JHISSGLW-Ckd41c1S.js} +1 -1
- package/dist/client/assets/{json-BLbH8rZf.js → json-7vp-fS_h.js} +1 -1
- package/dist/client/assets/{jsx-CxteptsJ.js → jsx-BgiCPhz9.js} +1 -1
- package/dist/client/assets/{julia-B__Ncp_z.js → julia-DADvZSF3.js} +1 -1
- package/dist/client/assets/{just-DBsHUGW1.js → just-BS3xPvUT.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-Z_AFL-rd.js → kanban-definition-UN3LZRKU-BlLY2sqY.js} +1 -1
- package/dist/client/assets/{latex-DFF_0wxo.js → latex-C_QvolBN.js} +1 -1
- package/dist/client/assets/{line-aAv8TyOs.js → line-Bkj6DIfl.js} +1 -1
- package/dist/client/assets/{linear-BOvEZKH8.js → linear-Cm6J5YrP.js} +1 -1
- package/dist/client/assets/{liquid-kDWCO6d7.js → liquid-CL0UH-ot.js} +1 -1
- package/dist/client/assets/{lua-CYTFayq-.js → lua-DlAyxrVn.js} +1 -1
- package/dist/client/assets/{marko-Dpmz3Pdx.js → marko-6e-PRCyy.js} +1 -1
- package/dist/client/assets/{mdc-BsxHyM1i.js → mdc-BbLfT_IA.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-D8z_CWL1.js → mermaid-parser.core-DwyEEn2f.js} +1 -1
- package/dist/client/assets/{mermaid.core-DNKchnly.js → mermaid.core-K3JZIfb0.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-xQKMV4Kw.js → mindmap-definition-RKZ34NQL-B2pkZuEx.js} +1 -1
- package/dist/client/assets/{nginx-CcdTDme-.js → nginx-CoBWheEf.js} +1 -1
- package/dist/client/assets/{nim-B5wWDskL.js → nim-2_Wea-mV.js} +1 -1
- package/dist/client/assets/{perl-Ce2_iKLz.js → perl-SkTVK0CC.js} +1 -1
- package/dist/client/assets/{php-DuLCFERd.js → php-C2cF2oJ6.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-CBSCJnUr.js → pieDiagram-4H26LBE5-BQeH8sHJ.js} +1 -1
- package/dist/client/assets/{pug-BfQJoQj1.js → pug-C01v9yeE.js} +1 -1
- package/dist/client/assets/{qml-DYeovlVl.js → qml-CCP9ULJs.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-uEcgb0Rm.js → quadrantDiagram-W4KKPZXB-D9CC7zgZ.js} +1 -1
- package/dist/client/assets/{r-DgHSP56L.js → r-DDJJP6PS.js} +1 -1
- package/dist/client/assets/{razor-B00x7W-i.js → razor-DKBWfXRk.js} +1 -1
- package/dist/client/assets/{regexp-CmWVupmd.js → regexp-DOe8oQhc.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-Q4yY_ALe.js → requirementDiagram-4Y6WPE33-DlYO_tzx.js} +1 -1
- package/dist/client/assets/{review-export-pcqDKNqx.js → review-export-CgROw3iz.js} +1 -1
- package/dist/client/assets/{rst-B3VARbTX.js → rst-KHKSB4M-.js} +1 -1
- package/dist/client/assets/{ruby-CaxKSSbT.js → ruby-3gO1sivg.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-B-GLzFGa.js → sankeyDiagram-5OEKKPKP-DvgoVn_C.js} +1 -1
- package/dist/client/assets/{sas-Ue0rHUIk.js → sas-D1u2TkIr.js} +1 -1
- package/dist/client/assets/{scss-BJJ7_L6c.js → scss-C__x0Kwu.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DW0em0D_.js → sequenceDiagram-3UESZ5HK-Dk46IxzZ.js} +1 -1
- package/dist/client/assets/{shellscript-C6LkZs1u.js → shellscript-YrQZ5Lwf.js} +1 -1
- package/dist/client/assets/{shellsession-CkZu1e84.js → shellsession-CoF5Zc6A.js} +1 -1
- package/dist/client/assets/{soy-CDaZV57J.js → soy-BKqc8OYl.js} +1 -1
- package/dist/client/assets/{sql-CLfMGH4x.js → sql-D3MVYjA_.js} +1 -1
- package/dist/client/assets/{src-C27zxuNO.js → src-UnI8qjTK.js} +1 -1
- package/dist/client/assets/{stata-B0kNMHc9.js → stata-BqvSK3te.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-CrO_D3m4.js → stateDiagram-AJRCARHV-BuhZPSPC.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CyVz0INc.js +1 -0
- package/dist/client/assets/{surrealql-D9os_yZO.js → surrealql-BhWLIqpd.js} +1 -1
- package/dist/client/assets/{svelte-2SxVToeO.js → svelte-DNNWxYsj.js} +1 -1
- package/dist/client/assets/{templ-DzmZB67T.js → templ-DdMgSlsy.js} +1 -1
- package/dist/client/assets/{tex-BMPk3nHk.js → tex-B4JFnTVn.js} +1 -1
- package/dist/client/assets/{timeline-definition-PNZ67QCA-C9FBevHv.js → timeline-definition-PNZ67QCA-C1xzVz0K.js} +1 -1
- package/dist/client/assets/{ts-tags-CG14Bplc.js → ts-tags-BQI5G_ZS.js} +1 -1
- package/dist/client/assets/{tsx-D6NK461A.js → tsx-C7qfZi8B.js} +1 -1
- package/dist/client/assets/{twig-Agh0j5sD.js → twig-RWfC7gXX.js} +1 -1
- package/dist/client/assets/{typescript-_AkeziZ2.js → typescript-aLdMd7mo.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-2H9HfhhR.js → vennDiagram-CIIHVFJN-CBkCNH_s.js} +1 -1
- package/dist/client/assets/{vue-C1z3QGG9.js → vue-BHCbeDIL.js} +1 -1
- package/dist/client/assets/{vue-html-C1RMzLZk.js → vue-html-DXTiTIOp.js} +1 -1
- package/dist/client/assets/{vue-vine-oOixA6ge.js → vue-vine-CuhsWKNe.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BJxrQhTf.js → wardleyDiagram-YWT4CUSO-aFrlILUG.js} +1 -1
- package/dist/client/assets/{xml-okuDZ7vq.js → xml-Dnahor-u.js} +1 -1
- package/dist/client/assets/{xsl-S_n3XrKb.js → xsl-DB6zLGtG.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-BGdee7KG.js → xychartDiagram-2RQKCTM6-CpPDrWcg.js} +1 -1
- package/dist/client/assets/{yaml-BoX-kqX1.js → yaml-VItgjNOZ.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/{completions-ZSKYvW06.mjs → completions-Csvfqctx.mjs} +16 -1
- package/dist/{doctor-DRWEgsMs.mjs → doctor-DGZoOg8f.mjs} +1 -1
- package/dist/{github-C27GDU9I.mjs → github-Cf5blBty.mjs} +39 -2
- package/dist/github-DWgPIcKC.mjs +2 -0
- package/dist/{mcp-D4cwru99.mjs → mcp-DOqu6S64.mjs} +212 -28
- package/dist/{server-CRk40DPV.mjs → server-Bjzyb6NG.mjs} +940 -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/index-YlwaPTSG.css +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-Cf5blBty.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,746 @@ 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
|
+
baseRefName: session.baseRefName,
|
|
2546
|
+
headRefName: session.headRefName,
|
|
2547
|
+
additions: session.additions,
|
|
2548
|
+
deletions: session.deletions,
|
|
2549
|
+
changedFiles: session.changedFiles,
|
|
2550
|
+
host: session.host,
|
|
2551
|
+
counts: {
|
|
2552
|
+
publishedThreads: threads.length,
|
|
2553
|
+
unresolvedThreads: unresolved,
|
|
2554
|
+
resolvedThreads: resolved,
|
|
2555
|
+
outdatedThreads: outdated,
|
|
2556
|
+
reviews: reviews.length,
|
|
2557
|
+
localDrafts: drafts.length,
|
|
2558
|
+
openDrafts: drafts.filter((d) => d.status === "open").length
|
|
2559
|
+
},
|
|
2560
|
+
submittedAt: session.submittedAt ?? null,
|
|
2561
|
+
submittedReviewId: session.submittedReviewId ?? null,
|
|
2562
|
+
submittedReviewUrl: session.submittedReviewUrl ?? null,
|
|
2563
|
+
authSource: session.authSource,
|
|
2564
|
+
patchBytes: Buffer.byteLength(session.diff ?? "", "utf8")
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2567
|
+
function truncateBody(body, maxChars, fullBody) {
|
|
2568
|
+
if (fullBody || maxChars == null) return {
|
|
2569
|
+
text: body,
|
|
2570
|
+
truncated: false
|
|
2571
|
+
};
|
|
2572
|
+
const limit = Math.max(0, maxChars);
|
|
2573
|
+
if (body.length <= limit) return {
|
|
2574
|
+
text: body,
|
|
2575
|
+
truncated: false
|
|
2576
|
+
};
|
|
2577
|
+
return {
|
|
2578
|
+
text: body.slice(0, limit) + "…",
|
|
2579
|
+
truncated: true
|
|
2580
|
+
};
|
|
2581
|
+
}
|
|
2582
|
+
function paginatePrThreads(session, opts = {}) {
|
|
2583
|
+
const cursor = Math.max(0, opts.cursor ?? 0);
|
|
2584
|
+
const limit = Math.min(200, Math.max(1, opts.limit ?? 50));
|
|
2585
|
+
const bodyMax = Math.min(5e4, Math.max(0, opts.bodyMaxChars ?? 500));
|
|
2586
|
+
const pathFilter = opts.path?.toLowerCase();
|
|
2587
|
+
const authorFilter = opts.author?.toLowerCase();
|
|
2588
|
+
let filtered = session.existingComments ?? [];
|
|
2589
|
+
if (opts.unresolvedOnly) filtered = filtered.filter((t) => !t.isResolved);
|
|
2590
|
+
if (pathFilter) filtered = filtered.filter((t) => (t.path ?? "").toLowerCase().includes(pathFilter));
|
|
2591
|
+
if (authorFilter) filtered = filtered.filter((t) => (t.author?.login ?? "").toLowerCase() === authorFilter);
|
|
2592
|
+
const total = filtered.length;
|
|
2593
|
+
const slice = filtered.slice(cursor, cursor + limit);
|
|
2594
|
+
const threads = slice.map((t) => {
|
|
2595
|
+
const root = truncateBody(t.body ?? "", bodyMax, opts.fullBody);
|
|
2596
|
+
const replies = (t.replies ?? []).map((r) => {
|
|
2597
|
+
const rb = truncateBody(r.body ?? "", bodyMax, opts.fullBody);
|
|
2598
|
+
return {
|
|
2599
|
+
id: r.id,
|
|
2600
|
+
author: r.author?.login ?? null,
|
|
2601
|
+
body: rb.text,
|
|
2602
|
+
bodyTruncated: rb.truncated,
|
|
2603
|
+
createdAt: r.createdAt
|
|
2604
|
+
};
|
|
2605
|
+
});
|
|
2606
|
+
return {
|
|
2607
|
+
id: t.id,
|
|
2608
|
+
threadId: t.threadId,
|
|
2609
|
+
path: t.path,
|
|
2610
|
+
line: t.line,
|
|
2611
|
+
startLine: t.startLine,
|
|
2612
|
+
side: t.side,
|
|
2613
|
+
startSide: t.startSide,
|
|
2614
|
+
author: t.author?.login ?? null,
|
|
2615
|
+
resolved: Boolean(t.isResolved),
|
|
2616
|
+
outdated: Boolean(t.isOutdated),
|
|
2617
|
+
state: t.state,
|
|
2618
|
+
createdAt: t.createdAt,
|
|
2619
|
+
updatedAt: t.updatedAt,
|
|
2620
|
+
body: root.text,
|
|
2621
|
+
bodyTruncated: root.truncated,
|
|
2622
|
+
replyCount: replies.length,
|
|
2623
|
+
replies
|
|
2624
|
+
};
|
|
2625
|
+
});
|
|
2626
|
+
const end = cursor + slice.length;
|
|
2627
|
+
return {
|
|
2628
|
+
returned: threads.length,
|
|
2629
|
+
total,
|
|
2630
|
+
nextCursor: end < total ? end : null,
|
|
2631
|
+
threads
|
|
2632
|
+
};
|
|
2633
|
+
}
|
|
2634
|
+
function paginatePrReviews(session, opts = {}) {
|
|
2635
|
+
const cursor = Math.max(0, opts.cursor ?? 0);
|
|
2636
|
+
const limit = Math.min(100, Math.max(1, opts.limit ?? 50));
|
|
2637
|
+
const bodyMax = Math.min(5e4, Math.max(0, opts.bodyMaxChars ?? 500));
|
|
2638
|
+
let reviews = session.existingReviews ?? [];
|
|
2639
|
+
if (opts.state) {
|
|
2640
|
+
const s = opts.state.toUpperCase();
|
|
2641
|
+
reviews = reviews.filter((r) => r.state === s);
|
|
2642
|
+
}
|
|
2643
|
+
const total = reviews.length;
|
|
2644
|
+
const slice = reviews.slice(cursor, cursor + limit);
|
|
2645
|
+
const mapped = slice.map((r) => {
|
|
2646
|
+
const b = truncateBody(r.body ?? "", bodyMax, opts.fullBody);
|
|
2647
|
+
return {
|
|
2648
|
+
id: r.id,
|
|
2649
|
+
author: r.author?.login ?? null,
|
|
2650
|
+
state: r.state,
|
|
2651
|
+
submittedAt: r.submittedAt,
|
|
2652
|
+
htmlUrl: r.htmlUrl,
|
|
2653
|
+
commitId: r.commitId,
|
|
2654
|
+
body: b.text,
|
|
2655
|
+
bodyTruncated: b.truncated
|
|
2656
|
+
};
|
|
2657
|
+
});
|
|
2658
|
+
const end = cursor + slice.length;
|
|
2659
|
+
return {
|
|
2660
|
+
returned: mapped.length,
|
|
2661
|
+
total,
|
|
2662
|
+
nextCursor: end < total ? end : null,
|
|
2663
|
+
reviews: mapped
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
function escapeAttr(value) {
|
|
2667
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
2668
|
+
}
|
|
2669
|
+
function cdata(value) {
|
|
2670
|
+
return value.replace(/]]>/g, "]]]]><![CDATA[>");
|
|
2671
|
+
}
|
|
2672
|
+
/**
|
|
2673
|
+
* Compact agent XML for published GitHub review threads (+ optional reviews).
|
|
2674
|
+
*/
|
|
2675
|
+
function formatPrReviewThreads(session, threads, reviews, page) {
|
|
2676
|
+
const prLabel = `${session.owner}/${session.repo}#${session.pullNumber}`;
|
|
2677
|
+
const lines = [];
|
|
2678
|
+
const pageAttrs = page ? ` returned="${page.returned}" total="${page.total}"${page.nextCursor == null ? "" : ` nextCursor="${page.nextCursor}"`}` : "";
|
|
2679
|
+
lines.push(`<pr-review-threads pr="${escapeAttr(prLabel)}" headSha="${escapeAttr(session.headSha)}" url="${escapeAttr(session.url)}"${pageAttrs}>`);
|
|
2680
|
+
lines.push(" <instructions>");
|
|
2681
|
+
lines.push(" Published GitHub review threads for this pull request (not local drafts).");
|
|
2682
|
+
lines.push(" Prefer addressing feedback via a local checkout + plan/implement cycle unless the user authorized live GitHub replies.");
|
|
2683
|
+
lines.push(" Only treat unresolved threads as actionable. resolved=\"true\" threads are historical.");
|
|
2684
|
+
lines.push(" </instructions>");
|
|
2685
|
+
for (const t of threads) {
|
|
2686
|
+
const lineAttr = t.startLine != null && t.line != null && t.startLine !== t.line ? ` line="${t.startLine}-${t.line}"` : t.line != null ? ` line="${t.line}"` : "";
|
|
2687
|
+
const sideAttr = t.side ? ` side="${t.side}"` : "";
|
|
2688
|
+
const pathAttr = t.path ? ` path="${escapeAttr(t.path)}"` : "";
|
|
2689
|
+
const authorAttr = t.author ? ` author="${escapeAttr(t.author)}"` : "";
|
|
2690
|
+
const threadIdAttr = t.threadId ? ` threadId="${escapeAttr(t.threadId)}"` : "";
|
|
2691
|
+
const stateAttr = t.state ? ` state="${t.state}"` : "";
|
|
2692
|
+
lines.push(` <thread id="${t.id}"${threadIdAttr}${pathAttr}${sideAttr}${lineAttr}${stateAttr} resolved="${t.resolved}" outdated="${t.outdated}" replyCount="${t.replyCount}"${authorAttr}>`);
|
|
2693
|
+
lines.push(` <body${t.bodyTruncated ? " truncated=\"true\"" : ""}><![CDATA[${cdata(t.body)}]]></body>`);
|
|
2694
|
+
if (t.replies.length > 0) {
|
|
2695
|
+
lines.push(" <replies>");
|
|
2696
|
+
for (const r of t.replies) {
|
|
2697
|
+
const ra = r.author ? ` author="${escapeAttr(r.author)}"` : "";
|
|
2698
|
+
lines.push(` <reply id="${r.id}"${ra}${r.bodyTruncated ? " truncated=\"true\"" : ""}><![CDATA[${cdata(r.body)}]]></reply>`);
|
|
2699
|
+
}
|
|
2700
|
+
lines.push(" </replies>");
|
|
2701
|
+
}
|
|
2702
|
+
lines.push(" </thread>");
|
|
2703
|
+
}
|
|
2704
|
+
if (reviews && reviews.length > 0) {
|
|
2705
|
+
lines.push(" <reviews>");
|
|
2706
|
+
for (const r of reviews) {
|
|
2707
|
+
const ra = r.author ? ` author="${escapeAttr(r.author)}"` : "";
|
|
2708
|
+
lines.push(` <review id="${r.id}" state="${r.state}"${ra}${r.submittedAt ? ` submittedAt="${escapeAttr(r.submittedAt)}"` : ""}>`);
|
|
2709
|
+
if (r.body) lines.push(` <body${r.bodyTruncated ? " truncated=\"true\"" : ""}><![CDATA[${cdata(r.body)}]]></body>`);
|
|
2710
|
+
lines.push(" </review>");
|
|
2711
|
+
}
|
|
2712
|
+
lines.push(" </reviews>");
|
|
2713
|
+
}
|
|
2714
|
+
lines.push("</pr-review-threads>");
|
|
2715
|
+
return lines.join("\n");
|
|
2716
|
+
}
|
|
2717
|
+
function formatPrReviews(session, reviews, page) {
|
|
2718
|
+
return formatPrReviewThreads(session, [], reviews, page);
|
|
2719
|
+
}
|
|
2720
|
+
//#endregion
|
|
1980
2721
|
//#region src/server.ts
|
|
1981
2722
|
const MIME_TYPES = {
|
|
1982
2723
|
".html": "text/html",
|
|
@@ -1993,6 +2734,20 @@ const MIME_TYPES = {
|
|
|
1993
2734
|
".bmp": "image/bmp",
|
|
1994
2735
|
".avif": "image/avif"
|
|
1995
2736
|
};
|
|
2737
|
+
const MAX_AVATAR_BYTES = 5 * 1024 * 1024;
|
|
2738
|
+
function collectPrAvatarUrls(session) {
|
|
2739
|
+
const urls = /* @__PURE__ */ new Set();
|
|
2740
|
+
const add = (avatarUrl) => {
|
|
2741
|
+
if (avatarUrl) urls.add(avatarUrl);
|
|
2742
|
+
};
|
|
2743
|
+
add(session.author?.avatarUrl);
|
|
2744
|
+
for (const review of session.existingReviews ?? []) add(review.author?.avatarUrl);
|
|
2745
|
+
for (const comment of session.existingComments) {
|
|
2746
|
+
add(comment.author?.avatarUrl);
|
|
2747
|
+
for (const reply of comment.replies) add(reply.author?.avatarUrl);
|
|
2748
|
+
}
|
|
2749
|
+
return urls;
|
|
2750
|
+
}
|
|
1996
2751
|
function isCustomMode(opts) {
|
|
1997
2752
|
return opts.revisions.length > 0 || opts.pathspecs.length > 0 || opts.showMode;
|
|
1998
2753
|
}
|
|
@@ -2003,6 +2758,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2003
2758
|
const store = commentStore ?? new FileCommentStore();
|
|
2004
2759
|
const plans = planStore ?? new FilePlanStore();
|
|
2005
2760
|
const prStore = prSessionStore ?? new FilePrSessionStore();
|
|
2761
|
+
const agentDiffCache = new AgentDiffIndexCache();
|
|
2006
2762
|
const uiStateStore = new FileUiStateStore();
|
|
2007
2763
|
const viewedFiles = /* @__PURE__ */ new Set();
|
|
2008
2764
|
/** Agent-reported progress events for the human UI (SSE `agent-progress`). */
|
|
@@ -2167,20 +2923,105 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2167
2923
|
overview: result.overview
|
|
2168
2924
|
});
|
|
2169
2925
|
});
|
|
2170
|
-
|
|
2926
|
+
async function resolveAgentPatch() {
|
|
2927
|
+
if (prMode) return (await prStore.get())?.diff ?? "";
|
|
2928
|
+
const stagedQuery = void 0;
|
|
2929
|
+
const untrackedQuery = void 0;
|
|
2930
|
+
const staged = stagedQuery === void 0 ? diffOpts.staged : false;
|
|
2931
|
+
const untracked = untrackedQuery === void 0 ? diffOpts.includeUntracked : false;
|
|
2932
|
+
return (await executeDiffWithMeta(customMode ? diffOpts : {
|
|
2933
|
+
...diffOpts,
|
|
2934
|
+
staged,
|
|
2935
|
+
includeUntracked: untracked
|
|
2936
|
+
})).patch ?? "";
|
|
2937
|
+
}
|
|
2938
|
+
function parseUInt(value, fallback) {
|
|
2939
|
+
if (value == null || value === "") return fallback;
|
|
2940
|
+
const n = Number(value);
|
|
2941
|
+
return Number.isFinite(n) && n >= 0 ? Math.trunc(n) : fallback;
|
|
2942
|
+
}
|
|
2943
|
+
app.get("/api/diff/summary", async (c) => {
|
|
2944
|
+
const patch = await resolveAgentPatch();
|
|
2945
|
+
const summary = indexSummary(agentDiffCache.getOrBuild(patch));
|
|
2946
|
+
if (!prMode) return c.json(summary);
|
|
2947
|
+
const session = await prStore.get();
|
|
2948
|
+
if (!session) return c.json(summary);
|
|
2949
|
+
return c.json({
|
|
2950
|
+
...summary,
|
|
2951
|
+
prMode: true,
|
|
2952
|
+
owner: session.owner,
|
|
2953
|
+
repo: session.repo,
|
|
2954
|
+
pullNumber: session.pullNumber,
|
|
2955
|
+
title: session.title,
|
|
2956
|
+
url: session.url,
|
|
2957
|
+
baseSha: session.baseSha,
|
|
2958
|
+
headSha: session.headSha
|
|
2959
|
+
});
|
|
2960
|
+
});
|
|
2961
|
+
app.get("/api/diff/files", async (c) => {
|
|
2962
|
+
const patch = await resolveAgentPatch();
|
|
2963
|
+
const index = agentDiffCache.getOrBuild(patch);
|
|
2964
|
+
const cursor = parseUInt(c.req.query("cursor"), 0);
|
|
2965
|
+
const limit = parseUInt(c.req.query("limit"), 100);
|
|
2966
|
+
return c.json(indexFiles(index, cursor, limit));
|
|
2967
|
+
});
|
|
2968
|
+
app.get("/api/diff/hunks", async (c) => {
|
|
2969
|
+
const patch = await resolveAgentPatch();
|
|
2970
|
+
const index = agentDiffCache.getOrBuild(patch);
|
|
2971
|
+
const file = parseUInt(c.req.query("file"), 0);
|
|
2972
|
+
const cursor = parseUInt(c.req.query("cursor"), 0);
|
|
2973
|
+
const limit = parseUInt(c.req.query("limit"), 100);
|
|
2974
|
+
const generationRaw = c.req.query("generation");
|
|
2975
|
+
const result = indexHunks(index, file, cursor, limit, generationRaw != null && generationRaw !== "" ? parseUInt(generationRaw, 0) : void 0);
|
|
2976
|
+
if ("status" in result) return c.json({ error: result.error }, result.status);
|
|
2977
|
+
return c.json(result);
|
|
2978
|
+
});
|
|
2979
|
+
app.get("/api/diff/slice", async (c) => {
|
|
2980
|
+
const patch = await resolveAgentPatch();
|
|
2981
|
+
const index = agentDiffCache.getOrBuild(patch);
|
|
2982
|
+
const file = parseUInt(c.req.query("file"), 0);
|
|
2983
|
+
const start = parseUInt(c.req.query("start"), 0);
|
|
2984
|
+
const maxLines = parseUInt(c.req.query("maxLines"), 120);
|
|
2985
|
+
const maxBytes = parseUInt(c.req.query("maxBytes"), 256 * 1024);
|
|
2986
|
+
const generationRaw = c.req.query("generation");
|
|
2987
|
+
const result = indexSlice(index, file, start, maxLines, maxBytes, generationRaw != null && generationRaw !== "" ? parseUInt(generationRaw, 0) : void 0);
|
|
2988
|
+
if ("status" in result) return c.json({ error: result.error }, result.status);
|
|
2989
|
+
return c.json(result);
|
|
2990
|
+
});
|
|
2991
|
+
app.get("/api/diff/search", async (c) => {
|
|
2992
|
+
const patch = await resolveAgentPatch();
|
|
2993
|
+
const index = agentDiffCache.getOrBuild(patch);
|
|
2994
|
+
const q = c.req.query("q") ?? "";
|
|
2995
|
+
const file = parseUInt(c.req.query("file"), 0);
|
|
2996
|
+
const row = parseUInt(c.req.query("row"), 0);
|
|
2997
|
+
const limit = parseUInt(c.req.query("limit"), 100);
|
|
2998
|
+
const maxBytes = parseUInt(c.req.query("maxBytes"), 256 * 1024);
|
|
2999
|
+
const generationRaw = c.req.query("generation");
|
|
3000
|
+
const result = indexSearch(index, q, file, row, limit, maxBytes, generationRaw != null && generationRaw !== "" ? parseUInt(generationRaw, 0) : void 0);
|
|
3001
|
+
if ("status" in result) return c.json({ error: result.error }, result.status);
|
|
3002
|
+
return c.json(result);
|
|
3003
|
+
});
|
|
3004
|
+
const resolveFileVersion = async (path, version) => {
|
|
3005
|
+
if (!prMode) return getFileContent(path, version);
|
|
3006
|
+
const session = await prStore.get();
|
|
3007
|
+
if (!session) return null;
|
|
3008
|
+
const sha = version === "old" ? session.baseSha : session.headSha;
|
|
3009
|
+
return fetchPrFileContentViaGh(resolvedFromSession(session), path, sha);
|
|
3010
|
+
};
|
|
3011
|
+
app.get("/api/file-content", async (c) => {
|
|
2171
3012
|
const path = c.req.query("path");
|
|
2172
3013
|
const version = c.req.query("version");
|
|
2173
3014
|
if (!path || !version) return c.json({ error: "Missing path or version" }, 400);
|
|
2174
|
-
const content =
|
|
3015
|
+
const content = await resolveFileVersion(path, version);
|
|
2175
3016
|
if (!content) return c.json({ error: "File not found" }, 404);
|
|
2176
3017
|
const contentType = MIME_TYPES[extname(path)] || "application/octet-stream";
|
|
2177
3018
|
return new Response(new Uint8Array(content), { headers: { "Content-Type": contentType } });
|
|
2178
3019
|
});
|
|
2179
|
-
app.get("/api/file-text", (c) => {
|
|
3020
|
+
app.get("/api/file-text", async (c) => {
|
|
2180
3021
|
const path = c.req.query("path");
|
|
2181
3022
|
const version = c.req.query("version");
|
|
2182
3023
|
if (!path || !version) return c.json({ error: "Missing path or version" }, 400);
|
|
2183
|
-
const buffer =
|
|
3024
|
+
const buffer = await resolveFileVersion(path, version);
|
|
2184
3025
|
if (!buffer) return c.json({
|
|
2185
3026
|
content: "",
|
|
2186
3027
|
missing: true
|
|
@@ -2615,6 +3456,91 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2615
3456
|
authSource: session.authSource
|
|
2616
3457
|
});
|
|
2617
3458
|
});
|
|
3459
|
+
/**
|
|
3460
|
+
* Serve GitHub avatars from this origin. Privacy extensions commonly block
|
|
3461
|
+
* embedded githubusercontent.com requests even though the same URL works as
|
|
3462
|
+
* a top-level navigation. Only URLs already supplied by the active GitHub
|
|
3463
|
+
* session are accepted, which keeps this endpoint from becoming an SSRF
|
|
3464
|
+
* proxy.
|
|
3465
|
+
*/
|
|
3466
|
+
app.get("/api/gh/avatar", async (c) => {
|
|
3467
|
+
if (!prMode) return notInPrMode(c);
|
|
3468
|
+
const session = await prStore.get();
|
|
3469
|
+
if (!session) return notInPrMode(c);
|
|
3470
|
+
const url = c.req.query("url");
|
|
3471
|
+
if (!url || !collectPrAvatarUrls(session).has(url)) return c.json({ error: "Avatar URL is not part of this PR session" }, 403);
|
|
3472
|
+
let parsed;
|
|
3473
|
+
try {
|
|
3474
|
+
parsed = new URL(url);
|
|
3475
|
+
} catch {
|
|
3476
|
+
return c.json({ error: "Invalid avatar URL" }, 400);
|
|
3477
|
+
}
|
|
3478
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return c.json({ error: "Invalid avatar URL protocol" }, 400);
|
|
3479
|
+
try {
|
|
3480
|
+
const response = await fetch(parsed, { redirect: "error" });
|
|
3481
|
+
if (!response.ok) return c.json({ error: `Avatar request failed with HTTP ${response.status}` }, 502);
|
|
3482
|
+
const contentType = response.headers.get("content-type")?.split(";")[0]?.trim() ?? "";
|
|
3483
|
+
if (!contentType.startsWith("image/")) return c.json({ error: "Avatar response was not an image" }, 502);
|
|
3484
|
+
if (Number(response.headers.get("content-length") ?? "0") > MAX_AVATAR_BYTES) return c.json({ error: "Avatar is too large" }, 413);
|
|
3485
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
3486
|
+
if (bytes.byteLength > MAX_AVATAR_BYTES) return c.json({ error: "Avatar is too large" }, 413);
|
|
3487
|
+
return new Response(bytes, { headers: {
|
|
3488
|
+
"Content-Type": contentType,
|
|
3489
|
+
"Cache-Control": "private, max-age=3600",
|
|
3490
|
+
"X-Content-Type-Options": "nosniff"
|
|
3491
|
+
} });
|
|
3492
|
+
} catch (error) {
|
|
3493
|
+
return c.json({ error: error?.message ?? "Failed to load avatar" }, 502);
|
|
3494
|
+
}
|
|
3495
|
+
});
|
|
3496
|
+
/** Slim PR metadata for agents — no threads, reviews, or patch. */
|
|
3497
|
+
app.get("/api/gh/overview", async (c) => {
|
|
3498
|
+
if (!prMode) return notInPrMode(c);
|
|
3499
|
+
const session = await prStore.get();
|
|
3500
|
+
if (!session) return notInPrMode(c);
|
|
3501
|
+
return c.json(buildPrOverviewPayload(session));
|
|
3502
|
+
});
|
|
3503
|
+
/**
|
|
3504
|
+
* Paged published review threads. Prefer this over /api/gh/session for agents.
|
|
3505
|
+
* Query: unresolvedOnly, path, author, cursor, limit, bodyMaxChars, fullBody, format=xml|json
|
|
3506
|
+
*/
|
|
3507
|
+
app.get("/api/gh/threads", async (c) => {
|
|
3508
|
+
if (!prMode) return notInPrMode(c);
|
|
3509
|
+
const session = await prStore.get();
|
|
3510
|
+
if (!session) return notInPrMode(c);
|
|
3511
|
+
const page = paginatePrThreads(session, {
|
|
3512
|
+
unresolvedOnly: c.req.query("unresolvedOnly") === "true" || c.req.query("unresolved") === "1",
|
|
3513
|
+
path: c.req.query("path") ?? void 0,
|
|
3514
|
+
author: c.req.query("author") ?? void 0,
|
|
3515
|
+
cursor: parseUInt(c.req.query("cursor"), 0),
|
|
3516
|
+
limit: parseUInt(c.req.query("limit"), 50),
|
|
3517
|
+
bodyMaxChars: parseUInt(c.req.query("bodyMaxChars"), 500),
|
|
3518
|
+
fullBody: c.req.query("fullBody") === "true" || c.req.query("fullBody") === "1"
|
|
3519
|
+
});
|
|
3520
|
+
if ((c.req.query("format") ?? "json").toLowerCase() === "xml") {
|
|
3521
|
+
c.header("Content-Type", "application/xml; charset=utf-8");
|
|
3522
|
+
return c.body(formatPrReviewThreads(session, page.threads, void 0, page));
|
|
3523
|
+
}
|
|
3524
|
+
return c.json(page);
|
|
3525
|
+
});
|
|
3526
|
+
/** Paged submitted review verdicts / overall comments. */
|
|
3527
|
+
app.get("/api/gh/reviews", async (c) => {
|
|
3528
|
+
if (!prMode) return notInPrMode(c);
|
|
3529
|
+
const session = await prStore.get();
|
|
3530
|
+
if (!session) return notInPrMode(c);
|
|
3531
|
+
const page = paginatePrReviews(session, {
|
|
3532
|
+
cursor: parseUInt(c.req.query("cursor"), 0),
|
|
3533
|
+
limit: parseUInt(c.req.query("limit"), 50),
|
|
3534
|
+
bodyMaxChars: parseUInt(c.req.query("bodyMaxChars"), 500),
|
|
3535
|
+
fullBody: c.req.query("fullBody") === "true" || c.req.query("fullBody") === "1",
|
|
3536
|
+
state: c.req.query("state") ?? void 0
|
|
3537
|
+
});
|
|
3538
|
+
if ((c.req.query("format") ?? "json").toLowerCase() === "xml") {
|
|
3539
|
+
c.header("Content-Type", "application/xml; charset=utf-8");
|
|
3540
|
+
return c.body(formatPrReviews(session, page.reviews, page));
|
|
3541
|
+
}
|
|
3542
|
+
return c.json(page);
|
|
3543
|
+
});
|
|
2618
3544
|
const syncExistingPrReviewData = async (session) => {
|
|
2619
3545
|
const resolved = resolvedFromSession(session);
|
|
2620
3546
|
let existingReviews = await fetchExistingReviewsViaGh(resolved);
|
|
@@ -2672,7 +3598,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2672
3598
|
const session = await prStore.get();
|
|
2673
3599
|
if (!session) return notInPrMode(c);
|
|
2674
3600
|
try {
|
|
2675
|
-
const { fetchPrChecks } = await import("./github-
|
|
3601
|
+
const { fetchPrChecks } = await import("./github-DWgPIcKC.mjs");
|
|
2676
3602
|
const checks = await fetchPrChecks(resolvedFromSession(session), session.headSha);
|
|
2677
3603
|
const summary = {
|
|
2678
3604
|
total: checks.length,
|
|
@@ -2704,7 +3630,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2704
3630
|
const text = typeof body.body === "string" ? body.body.trim() : "";
|
|
2705
3631
|
if (!text) return c.json({ error: "body is required" }, 400);
|
|
2706
3632
|
try {
|
|
2707
|
-
const { replyToPrComment } = await import("./github-
|
|
3633
|
+
const { replyToPrComment } = await import("./github-DWgPIcKC.mjs");
|
|
2708
3634
|
const result = await replyToPrComment({
|
|
2709
3635
|
resolved: resolvedFromSession(session),
|
|
2710
3636
|
inReplyTo: id,
|
|
@@ -2824,6 +3750,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2824
3750
|
const body = await c.req.json();
|
|
2825
3751
|
const session = await prStore.get();
|
|
2826
3752
|
if (!session) return notInPrMode(c);
|
|
3753
|
+
const severityRaw = body.severity;
|
|
3754
|
+
const severity = severityRaw === "blocking" || severityRaw === "nit" || severityRaw === "question" || severityRaw === "praise" || severityRaw === "none" ? severityRaw === "none" ? void 0 : severityRaw : void 0;
|
|
2827
3755
|
const comment = {
|
|
2828
3756
|
id: crypto.randomUUID(),
|
|
2829
3757
|
filePath: body.filePath,
|
|
@@ -2834,7 +3762,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2834
3762
|
body: body.body,
|
|
2835
3763
|
status: "open",
|
|
2836
3764
|
createdAt: Date.now(),
|
|
2837
|
-
replies: []
|
|
3765
|
+
replies: [],
|
|
3766
|
+
...severity ? { severity } : {}
|
|
2838
3767
|
};
|
|
2839
3768
|
const next = {
|
|
2840
3769
|
...session,
|
|
@@ -2913,7 +3842,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
|
|
|
2913
3842
|
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
3843
|
const generalBody = typeof body.body === "string" ? body.body : "";
|
|
2915
3844
|
if (body.dryRun === true) {
|
|
2916
|
-
const { buildReviewPayload } = await import("./github-
|
|
3845
|
+
const { buildReviewPayload } = await import("./github-DWgPIcKC.mjs");
|
|
2917
3846
|
const payload = buildReviewPayload({
|
|
2918
3847
|
decision,
|
|
2919
3848
|
body: generalBody,
|