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.
Files changed (152) hide show
  1. package/dist/{cli-agent-7DUbPdeW.mjs → cli-agent-CV0k1pXp.mjs} +10 -8
  2. package/dist/cli-gh-C2LAp12S.mjs +313 -0
  3. package/dist/cli.mjs +3 -3
  4. package/dist/client/assets/{angular-html-DdAR2u-k.js → angular-html-CV0Ak818.js} +1 -1
  5. package/dist/client/assets/{angular-ts-Dz_vWKSu.js → angular-ts-2e8zQ7to.js} +1 -1
  6. package/dist/client/assets/{apl-BJ5QXrIk.js → apl-ByVy2dfo.js} +1 -1
  7. package/dist/client/assets/{arc-D9erSW6x.js → arc--R8kldOB.js} +1 -1
  8. package/dist/client/assets/{architectureDiagram-3BPJPVTR-CCQWZLXR.js → architectureDiagram-3BPJPVTR-BWbm1lrV.js} +1 -1
  9. package/dist/client/assets/{astro-BQAej5a5.js → astro-CE7ySGnq.js} +1 -1
  10. package/dist/client/assets/{blade-DkAmuwDx.js → blade-PJJaH6RK.js} +1 -1
  11. package/dist/client/assets/{blockDiagram-GPEHLZMM-vwjxNybI.js → blockDiagram-GPEHLZMM-BdfBhnvZ.js} +1 -1
  12. package/dist/client/assets/{c-DG5p-ChJ.js → c-BhDzEkUq.js} +1 -1
  13. package/dist/client/assets/{c4Diagram-AAUBKEIU-DJ_hVlh4.js → c4Diagram-AAUBKEIU-Bu_tfvPY.js} +1 -1
  14. package/dist/client/assets/channel-DSEnsd3l.js +1 -0
  15. package/dist/client/assets/{chunk-2J33WTMH-CiLowxCo.js → chunk-2J33WTMH-CTGsTtve.js} +1 -1
  16. package/dist/client/assets/{chunk-3OPIFGDE-H3jjMR5Y.js → chunk-3OPIFGDE-BODO_5hx.js} +1 -1
  17. package/dist/client/assets/{chunk-4BX2VUAB-Dk0dk4n5.js → chunk-4BX2VUAB-BTsyFV0Y.js} +1 -1
  18. package/dist/client/assets/{chunk-55IACEB6-nbLiUMJS.js → chunk-55IACEB6-DnrCm6LC.js} +1 -1
  19. package/dist/client/assets/{chunk-5ZQYHXKU-DZ8RgHg8.js → chunk-5ZQYHXKU-DBjF0sdg.js} +1 -1
  20. package/dist/client/assets/{chunk-727SXJPM-DDe5dqJV.js → chunk-727SXJPM-Do27Z5ji.js} +1 -1
  21. package/dist/client/assets/{chunk-AQP2D5EJ-DmpNclOr.js → chunk-AQP2D5EJ-CTnpneGA.js} +1 -1
  22. package/dist/client/assets/{chunk-BSJP7CBP-BKQKvtIj.js → chunk-BSJP7CBP-Kr0_rIcG.js} +1 -1
  23. package/dist/client/assets/{chunk-CSCIHK7Q-D27t4cyv.js → chunk-CSCIHK7Q-vYCYu98G.js} +1 -1
  24. package/dist/client/assets/{chunk-FMBD7UC4-Bu3LA663.js → chunk-FMBD7UC4-D325zkwE.js} +1 -1
  25. package/dist/client/assets/{chunk-KSCS5N6A-Ed5HNXRW.js → chunk-KSCS5N6A-Byf7HmWv.js} +1 -1
  26. package/dist/client/assets/{chunk-L5ZTLDWV-DLjJZgf8.js → chunk-L5ZTLDWV-D6bS5hm3.js} +1 -1
  27. package/dist/client/assets/{chunk-LZXEDZCA-B-cs89SM.js → chunk-LZXEDZCA-BMGYcU-n.js} +2 -2
  28. package/dist/client/assets/{chunk-ND2GUHAM-D_kPSuUA.js → chunk-ND2GUHAM-xfJDzv5I.js} +1 -1
  29. package/dist/client/assets/{chunk-NZK2D7GU-D1u3pjq-.js → chunk-NZK2D7GU-DY1HNoUO.js} +1 -1
  30. package/dist/client/assets/{chunk-O5CBEL6O-Mt0cnPSf.js → chunk-O5CBEL6O-BFgywBQV.js} +1 -1
  31. package/dist/client/assets/chunk-QZHKN3VN-wL51hu-L.js +1 -0
  32. package/dist/client/assets/chunk-WU5MYG2G-DcSuLObv.js +1 -0
  33. package/dist/client/assets/{chunk-XPW4576I-Bt8a8x28.js → chunk-XPW4576I-CCOlY0K5.js} +1 -1
  34. package/dist/client/assets/classDiagram-4FO5ZUOK-Cd9P7jo-.js +1 -0
  35. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-_8XVmWNY.js +1 -0
  36. package/dist/client/assets/{cobol-Daidj_5Y.js → cobol-D5uvQocP.js} +1 -1
  37. package/dist/client/assets/{coffee-DUMdB2Cr.js → coffee-DoE1_DMC.js} +1 -1
  38. package/dist/client/assets/{cose-bilkent-S5V4N54A-DVUDLXie.js → cose-bilkent-S5V4N54A-BffMlb0t.js} +1 -1
  39. package/dist/client/assets/{cpp-C2FeX1Mc.js → cpp-CxMs3g8q.js} +1 -1
  40. package/dist/client/assets/{crystal-D4yb9blJ.js → crystal-Ck_Sab8H.js} +1 -1
  41. package/dist/client/assets/{css-B5cr0U9F.js → css-Bx3zqeyw.js} +1 -1
  42. package/dist/client/assets/{dagre-BM42HDAG-DdZWb-Ee.js → dagre-BM42HDAG-DuK7JkP6.js} +1 -1
  43. package/dist/client/assets/{diagram-2AECGRRQ-C9DOZt92.js → diagram-2AECGRRQ-DW9W2wY6.js} +1 -1
  44. package/dist/client/assets/{diagram-5GNKFQAL-Bbc07UKr.js → diagram-5GNKFQAL-CfmdY-KT.js} +1 -1
  45. package/dist/client/assets/{diagram-KO2AKTUF-C-lUzY3m.js → diagram-KO2AKTUF-C3W0AVns.js} +1 -1
  46. package/dist/client/assets/{diagram-LMA3HP47-D-OjEFhp.js → diagram-LMA3HP47-Jxoj1EBH.js} +1 -1
  47. package/dist/client/assets/{diagram-OG6HWLK6-BjtWGvrf.js → diagram-OG6HWLK6-DKZXYFSu.js} +1 -1
  48. package/dist/client/assets/{dist-BXZygPdX.js → dist-Cxgi_w93.js} +1 -1
  49. package/dist/client/assets/{edge-RCQWcWNp.js → edge-D7XVJcl7.js} +1 -1
  50. package/dist/client/assets/{elixir-DwKyVg2X.js → elixir-DLQjwJ9Q.js} +1 -1
  51. package/dist/client/assets/{elm-DjR33IVs.js → elm-CP5Ox5XO.js} +1 -1
  52. package/dist/client/assets/{erDiagram-TEJ5UH35-C5T4ywLG.js → erDiagram-TEJ5UH35-BGd46oGi.js} +1 -1
  53. package/dist/client/assets/{erb-BnhUH1Hk.js → erb-C97aW-va.js} +1 -1
  54. package/dist/client/assets/{flowDiagram-I6XJVG4X-BJmjm7Kx.js → flowDiagram-I6XJVG4X-D6MFCwKy.js} +1 -1
  55. package/dist/client/assets/{ganttDiagram-6RSMTGT7-DREPnzu5.js → ganttDiagram-6RSMTGT7-i1RH9KFv.js} +1 -1
  56. package/dist/client/assets/{git-rebase-DzXoSDXA.js → git-rebase-B4iN-h71.js} +1 -1
  57. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DIR4G_-_.js → gitGraphDiagram-PVQCEYII-DiLnrGe6.js} +1 -1
  58. package/dist/client/assets/{glimmer-js-HKnZpCn_.js → glimmer-js-DVkEnHRb.js} +1 -1
  59. package/dist/client/assets/{glimmer-ts-DedV7rWs.js → glimmer-ts-CyUzWW2D.js} +1 -1
  60. package/dist/client/assets/{glsl-Bz-0Alp2.js → glsl-5XFOCOCu.js} +1 -1
  61. package/dist/client/assets/{graphql-DSKkDYCD.js → graphql-IoOZHuWD.js} +1 -1
  62. package/dist/client/assets/{hack-CnaHZm8k.js → hack-BUQ8LqBu.js} +1 -1
  63. package/dist/client/assets/{haml-huGFYKlj.js → haml-BIbv0YDp.js} +1 -1
  64. package/dist/client/assets/{handlebars-CKmgzfWY.js → handlebars-9QihiA8n.js} +1 -1
  65. package/dist/client/assets/{html-B9Ih_dUU.js → html-5bjcBKb2.js} +1 -1
  66. package/dist/client/assets/{html-derivative-YabD-M5d.js → html-derivative-DFAt3hvS.js} +1 -1
  67. package/dist/client/assets/{http-Bdu12JYX.js → http-4_xHs8PR.js} +1 -1
  68. package/dist/client/assets/{hurl-twZzDTNs.js → hurl-B-wi-Gx2.js} +1 -1
  69. package/dist/client/assets/{index-BktdZRWB.js → index-CHvT3DR9.js} +10 -10
  70. package/dist/client/assets/{infoDiagram-5YYISTIA-D2ty2R-i.js → infoDiagram-5YYISTIA-Dy1M2f6k.js} +1 -1
  71. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-Ccnc9xlE.js → ishikawaDiagram-YF4QCWOH-J9qG2PlR.js} +1 -1
  72. package/dist/client/assets/{java-_5trO-p9.js → java-D9isH882.js} +1 -1
  73. package/dist/client/assets/{javascript-BOQn0ikE.js → javascript-Dj8e4yT5.js} +1 -1
  74. package/dist/client/assets/{jinja-BgY-0IBz.js → jinja-Bkare_Hv.js} +1 -1
  75. package/dist/client/assets/{jison-BLNTmT1_.js → jison-DDIUNnOW.js} +1 -1
  76. package/dist/client/assets/{journeyDiagram-JHISSGLW-W7eWXlw0.js → journeyDiagram-JHISSGLW-Do4kGeyR.js} +1 -1
  77. package/dist/client/assets/{json-BLbH8rZf.js → json-Bi84FPdJ.js} +1 -1
  78. package/dist/client/assets/{jsx-CxteptsJ.js → jsx-DI-NKxp6.js} +1 -1
  79. package/dist/client/assets/{julia-B__Ncp_z.js → julia-Yhlqb4JO.js} +1 -1
  80. package/dist/client/assets/{just-DBsHUGW1.js → just-B8KISqsZ.js} +1 -1
  81. package/dist/client/assets/{kanban-definition-UN3LZRKU-Z_AFL-rd.js → kanban-definition-UN3LZRKU-BSeQxfa9.js} +1 -1
  82. package/dist/client/assets/{latex-DFF_0wxo.js → latex-CkYf4HIK.js} +1 -1
  83. package/dist/client/assets/{line-aAv8TyOs.js → line-C2Za_tgK.js} +1 -1
  84. package/dist/client/assets/{linear-BOvEZKH8.js → linear-CRm3pgdR.js} +1 -1
  85. package/dist/client/assets/{liquid-kDWCO6d7.js → liquid-y_Utf1BC.js} +1 -1
  86. package/dist/client/assets/{lua-CYTFayq-.js → lua-C4gbVDKd.js} +1 -1
  87. package/dist/client/assets/{marko-Dpmz3Pdx.js → marko-BlcQZIyX.js} +1 -1
  88. package/dist/client/assets/{mdc-BsxHyM1i.js → mdc-C6KkSPM_.js} +1 -1
  89. package/dist/client/assets/{mermaid-parser.core-D8z_CWL1.js → mermaid-parser.core-B9mHNH7g.js} +1 -1
  90. package/dist/client/assets/{mermaid.core-DNKchnly.js → mermaid.core-pkJSRcFi.js} +3 -3
  91. package/dist/client/assets/{mindmap-definition-RKZ34NQL-xQKMV4Kw.js → mindmap-definition-RKZ34NQL-BAW_jZIg.js} +1 -1
  92. package/dist/client/assets/{nginx-CcdTDme-.js → nginx-BGbv5z-H.js} +1 -1
  93. package/dist/client/assets/{nim-B5wWDskL.js → nim-BtqaozVV.js} +1 -1
  94. package/dist/client/assets/{perl-Ce2_iKLz.js → perl-BaPVFzDG.js} +1 -1
  95. package/dist/client/assets/{php-DuLCFERd.js → php-CUSmmFXP.js} +1 -1
  96. package/dist/client/assets/{pieDiagram-4H26LBE5-CBSCJnUr.js → pieDiagram-4H26LBE5-DHTW5bo3.js} +1 -1
  97. package/dist/client/assets/{pug-BfQJoQj1.js → pug-ComryLhI.js} +1 -1
  98. package/dist/client/assets/{qml-DYeovlVl.js → qml-D4-pUDzN.js} +1 -1
  99. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-uEcgb0Rm.js → quadrantDiagram-W4KKPZXB-oE0LSIIu.js} +1 -1
  100. package/dist/client/assets/{r-DgHSP56L.js → r-IEiS2KmQ.js} +1 -1
  101. package/dist/client/assets/{razor-B00x7W-i.js → razor-A3NOQ-6o.js} +1 -1
  102. package/dist/client/assets/{regexp-CmWVupmd.js → regexp-Dnh3EraB.js} +1 -1
  103. package/dist/client/assets/{requirementDiagram-4Y6WPE33-Q4yY_ALe.js → requirementDiagram-4Y6WPE33-CAnOHDxz.js} +1 -1
  104. package/dist/client/assets/{review-export-pcqDKNqx.js → review-export-D-wScR31.js} +1 -1
  105. package/dist/client/assets/{rst-B3VARbTX.js → rst-DDcT8Cwj.js} +1 -1
  106. package/dist/client/assets/{ruby-CaxKSSbT.js → ruby-BqnC7im3.js} +1 -1
  107. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-B-GLzFGa.js → sankeyDiagram-5OEKKPKP-DARazeui.js} +1 -1
  108. package/dist/client/assets/{sas-Ue0rHUIk.js → sas-BsVPIslL.js} +1 -1
  109. package/dist/client/assets/{scss-BJJ7_L6c.js → scss-Cs-F21hy.js} +1 -1
  110. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DW0em0D_.js → sequenceDiagram-3UESZ5HK-BYAKokia.js} +1 -1
  111. package/dist/client/assets/{shellscript-C6LkZs1u.js → shellscript-CuqRpwp7.js} +1 -1
  112. package/dist/client/assets/{shellsession-CkZu1e84.js → shellsession-BT4CTJst.js} +1 -1
  113. package/dist/client/assets/{soy-CDaZV57J.js → soy-Njc_erP7.js} +1 -1
  114. package/dist/client/assets/{sql-CLfMGH4x.js → sql-C0M8bIzv.js} +1 -1
  115. package/dist/client/assets/{src-C27zxuNO.js → src-B8u-CQt1.js} +1 -1
  116. package/dist/client/assets/{stata-B0kNMHc9.js → stata-DQWLgfGE.js} +1 -1
  117. package/dist/client/assets/{stateDiagram-AJRCARHV-CrO_D3m4.js → stateDiagram-AJRCARHV-CL6IO6Yx.js} +1 -1
  118. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CXkQIsOM.js +1 -0
  119. package/dist/client/assets/{surrealql-D9os_yZO.js → surrealql-CxnsjSMN.js} +1 -1
  120. package/dist/client/assets/{svelte-2SxVToeO.js → svelte-YiRJWdT-.js} +1 -1
  121. package/dist/client/assets/{templ-DzmZB67T.js → templ-DVHepm0I.js} +1 -1
  122. package/dist/client/assets/{tex-BMPk3nHk.js → tex-CbaeWbjN.js} +1 -1
  123. package/dist/client/assets/{timeline-definition-PNZ67QCA-C9FBevHv.js → timeline-definition-PNZ67QCA-BAKLMRXW.js} +1 -1
  124. package/dist/client/assets/{ts-tags-CG14Bplc.js → ts-tags-KadJLbnQ.js} +1 -1
  125. package/dist/client/assets/{tsx-D6NK461A.js → tsx-DgmkmSe3.js} +1 -1
  126. package/dist/client/assets/{twig-Agh0j5sD.js → twig-BG7aoIMU.js} +1 -1
  127. package/dist/client/assets/{typescript-_AkeziZ2.js → typescript-C_w_yccd.js} +1 -1
  128. package/dist/client/assets/{vennDiagram-CIIHVFJN-2H9HfhhR.js → vennDiagram-CIIHVFJN-CCRx_JXh.js} +1 -1
  129. package/dist/client/assets/{vue-C1z3QGG9.js → vue-DXnUyZR0.js} +1 -1
  130. package/dist/client/assets/{vue-html-C1RMzLZk.js → vue-html-CetUfade.js} +1 -1
  131. package/dist/client/assets/{vue-vine-oOixA6ge.js → vue-vine-CSW5RjCH.js} +1 -1
  132. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BJxrQhTf.js → wardleyDiagram-YWT4CUSO-Buoffz1N.js} +1 -1
  133. package/dist/client/assets/{xml-okuDZ7vq.js → xml-CJgpX9jH.js} +1 -1
  134. package/dist/client/assets/{xsl-S_n3XrKb.js → xsl-BZk-xhGC.js} +1 -1
  135. package/dist/client/assets/{xychartDiagram-2RQKCTM6-BGdee7KG.js → xychartDiagram-2RQKCTM6-CEKRZKfv.js} +1 -1
  136. package/dist/client/assets/{yaml-BoX-kqX1.js → yaml-DVxl0VLy.js} +1 -1
  137. package/dist/client/index.html +1 -1
  138. package/dist/{completions-ZSKYvW06.mjs → completions-Csvfqctx.mjs} +16 -1
  139. package/dist/{doctor-DRWEgsMs.mjs → doctor-DhuZFVwO.mjs} +1 -1
  140. package/dist/{github-C27GDU9I.mjs → github-CzxbZnhh.mjs} +32 -1
  141. package/dist/github-DL7-BqhD.mjs +2 -0
  142. package/dist/{mcp-D4cwru99.mjs → mcp-BBDdXGOZ.mjs} +212 -28
  143. package/dist/{server-CRk40DPV.mjs → server-D-gJ7owz.mjs} +938 -11
  144. package/package.json +1 -1
  145. package/dist/cli-gh-8YC1NzyM.mjs +0 -155
  146. package/dist/client/assets/channel-DmZgR6r0.js +0 -1
  147. package/dist/client/assets/chunk-QZHKN3VN-BrYnBs7h.js +0 -1
  148. package/dist/client/assets/chunk-WU5MYG2G-CdBcsQs_.js +0 -1
  149. package/dist/client/assets/classDiagram-4FO5ZUOK-CCpp3t5M.js +0 -1
  150. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B1aV1sYb.js +0 -1
  151. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-C9KDGD7R.js +0 -1
  152. 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 { C as resolvedFromSession, E as updatePrReviewComment, T as submitReview, a as detectCwdRepo, i as deletePrReviewComment, l as fetchExistingCommentsViaGh, t as buildPrSession, u as fetchExistingReviewsViaGh, w as setPrReviewThreadResolved, x as refreshPrSession, y as parsePrRef } from "./github-C27GDU9I.mjs";
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, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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
- app.get("/api/file-content", (c) => {
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 = getFileContent(path, version);
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 = getFileContent(path, version);
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-8THGZ7Nu.mjs");
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-8THGZ7Nu.mjs");
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-8THGZ7Nu.mjs");
3843
+ const { buildReviewPayload } = await import("./github-DL7-BqhD.mjs");
2917
3844
  const payload = buildReviewPayload({
2918
3845
  decision,
2919
3846
  body: generalBody,