diffing 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/{cli-agent-CLrK54k2.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-CmP0u7-d.mjs → doctor-DhuZFVwO.mjs} +1 -1
  140. package/dist/{github-oCZx5aim.mjs → github-CzxbZnhh.mjs} +152 -28
  141. package/dist/github-DL7-BqhD.mjs +2 -0
  142. package/dist/{mcp-CqxXILYL.mjs → mcp-BBDdXGOZ.mjs} +212 -28
  143. package/dist/{server-CczuXSWo.mjs → server-D-gJ7owz.mjs} +947 -54
  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-BjZZcSKV.mjs +0 -2
@@ -2,8 +2,9 @@ import { C as isSafePath, S as revertHunk, _ as getUntrackedFilePathsAsync, a as
2
2
  import { a as searchContent, c as trackSelection, i as searchAll, n as saveSettings, o as searchFiles, r as getSearchStatus, s as searchSymbols, t as loadSettings } from "./settings-BVnzGFL7.mjs";
3
3
  import { t as formatComments } from "./comment-format-B5AcGcR-.mjs";
4
4
  import { n as formatPlanReview, r as sectionTitleForLine, t as extractPlanLines } from "./plan-format-BQyx--mp.mjs";
5
- import { a as detectCwdRepo, b as updatePrReviewComment, g as refreshPrSession, i as deletePrReviewComment, l as fetchExistingCommentsViaGh, m as parsePrRef, t as buildPrSession, u as fetchExistingReviewsViaGh, v as setPrReviewThreadResolved, y as submitReview } from "./github-oCZx5aim.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,13 +3454,93 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2615
3454
  authSource: session.authSource
2616
3455
  });
2617
3456
  });
3457
+ /**
3458
+ * Serve GitHub avatars from this origin. Privacy extensions commonly block
3459
+ * embedded githubusercontent.com requests even though the same URL works as
3460
+ * a top-level navigation. Only URLs already supplied by the active GitHub
3461
+ * session are accepted, which keeps this endpoint from becoming an SSRF
3462
+ * proxy.
3463
+ */
3464
+ app.get("/api/gh/avatar", async (c) => {
3465
+ if (!prMode) return notInPrMode(c);
3466
+ const session = await prStore.get();
3467
+ if (!session) return notInPrMode(c);
3468
+ const url = c.req.query("url");
3469
+ if (!url || !collectPrAvatarUrls(session).has(url)) return c.json({ error: "Avatar URL is not part of this PR session" }, 403);
3470
+ let parsed;
3471
+ try {
3472
+ parsed = new URL(url);
3473
+ } catch {
3474
+ return c.json({ error: "Invalid avatar URL" }, 400);
3475
+ }
3476
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return c.json({ error: "Invalid avatar URL protocol" }, 400);
3477
+ try {
3478
+ const response = await fetch(parsed, { redirect: "error" });
3479
+ if (!response.ok) return c.json({ error: `Avatar request failed with HTTP ${response.status}` }, 502);
3480
+ const contentType = response.headers.get("content-type")?.split(";")[0]?.trim() ?? "";
3481
+ if (!contentType.startsWith("image/")) return c.json({ error: "Avatar response was not an image" }, 502);
3482
+ if (Number(response.headers.get("content-length") ?? "0") > MAX_AVATAR_BYTES) return c.json({ error: "Avatar is too large" }, 413);
3483
+ const bytes = new Uint8Array(await response.arrayBuffer());
3484
+ if (bytes.byteLength > MAX_AVATAR_BYTES) return c.json({ error: "Avatar is too large" }, 413);
3485
+ return new Response(bytes, { headers: {
3486
+ "Content-Type": contentType,
3487
+ "Cache-Control": "private, max-age=3600",
3488
+ "X-Content-Type-Options": "nosniff"
3489
+ } });
3490
+ } catch (error) {
3491
+ return c.json({ error: error?.message ?? "Failed to load avatar" }, 502);
3492
+ }
3493
+ });
3494
+ /** Slim PR metadata for agents — no threads, reviews, or patch. */
3495
+ app.get("/api/gh/overview", async (c) => {
3496
+ if (!prMode) return notInPrMode(c);
3497
+ const session = await prStore.get();
3498
+ if (!session) return notInPrMode(c);
3499
+ return c.json(buildPrOverviewPayload(session));
3500
+ });
3501
+ /**
3502
+ * Paged published review threads. Prefer this over /api/gh/session for agents.
3503
+ * Query: unresolvedOnly, path, author, cursor, limit, bodyMaxChars, fullBody, format=xml|json
3504
+ */
3505
+ app.get("/api/gh/threads", async (c) => {
3506
+ if (!prMode) return notInPrMode(c);
3507
+ const session = await prStore.get();
3508
+ if (!session) return notInPrMode(c);
3509
+ const page = paginatePrThreads(session, {
3510
+ unresolvedOnly: c.req.query("unresolvedOnly") === "true" || c.req.query("unresolved") === "1",
3511
+ path: c.req.query("path") ?? void 0,
3512
+ author: c.req.query("author") ?? void 0,
3513
+ cursor: parseUInt(c.req.query("cursor"), 0),
3514
+ limit: parseUInt(c.req.query("limit"), 50),
3515
+ bodyMaxChars: parseUInt(c.req.query("bodyMaxChars"), 500),
3516
+ fullBody: c.req.query("fullBody") === "true" || c.req.query("fullBody") === "1"
3517
+ });
3518
+ if ((c.req.query("format") ?? "json").toLowerCase() === "xml") {
3519
+ c.header("Content-Type", "application/xml; charset=utf-8");
3520
+ return c.body(formatPrReviewThreads(session, page.threads, void 0, page));
3521
+ }
3522
+ return c.json(page);
3523
+ });
3524
+ /** Paged submitted review verdicts / overall comments. */
3525
+ app.get("/api/gh/reviews", async (c) => {
3526
+ if (!prMode) return notInPrMode(c);
3527
+ const session = await prStore.get();
3528
+ if (!session) return notInPrMode(c);
3529
+ const page = paginatePrReviews(session, {
3530
+ cursor: parseUInt(c.req.query("cursor"), 0),
3531
+ limit: parseUInt(c.req.query("limit"), 50),
3532
+ bodyMaxChars: parseUInt(c.req.query("bodyMaxChars"), 500),
3533
+ fullBody: c.req.query("fullBody") === "true" || c.req.query("fullBody") === "1",
3534
+ state: c.req.query("state") ?? void 0
3535
+ });
3536
+ if ((c.req.query("format") ?? "json").toLowerCase() === "xml") {
3537
+ c.header("Content-Type", "application/xml; charset=utf-8");
3538
+ return c.body(formatPrReviews(session, page.reviews, page));
3539
+ }
3540
+ return c.json(page);
3541
+ });
2618
3542
  const syncExistingPrReviewData = async (session) => {
2619
- const resolved = {
2620
- owner: session.owner,
2621
- repo: session.repo,
2622
- pullNumber: session.pullNumber,
2623
- ref: session.ref
2624
- };
3543
+ const resolved = resolvedFromSession(session);
2625
3544
  let existingReviews = await fetchExistingReviewsViaGh(resolved);
2626
3545
  const optimisticReview = session.submittedReviewId == null ? void 0 : session.existingReviews?.find((review) => review.id === session.submittedReviewId);
2627
3546
  if (optimisticReview && !existingReviews.some((review) => review.id === optimisticReview.id) && session.submittedAt != null && Date.now() - session.submittedAt < 12e4) existingReviews = [optimisticReview, ...existingReviews];
@@ -2677,13 +3596,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2677
3596
  const session = await prStore.get();
2678
3597
  if (!session) return notInPrMode(c);
2679
3598
  try {
2680
- const { fetchPrChecks } = await import("./github-BjZZcSKV.mjs");
2681
- const checks = await fetchPrChecks({
2682
- owner: session.owner,
2683
- repo: session.repo,
2684
- pullNumber: session.pullNumber,
2685
- ref: session.ref
2686
- }, session.headSha);
3599
+ const { fetchPrChecks } = await import("./github-DL7-BqhD.mjs");
3600
+ const checks = await fetchPrChecks(resolvedFromSession(session), session.headSha);
2687
3601
  const summary = {
2688
3602
  total: checks.length,
2689
3603
  success: checks.filter((x) => x.state === "success").length,
@@ -2714,14 +3628,9 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2714
3628
  const text = typeof body.body === "string" ? body.body.trim() : "";
2715
3629
  if (!text) return c.json({ error: "body is required" }, 400);
2716
3630
  try {
2717
- const { replyToPrComment } = await import("./github-BjZZcSKV.mjs");
3631
+ const { replyToPrComment } = await import("./github-DL7-BqhD.mjs");
2718
3632
  const result = await replyToPrComment({
2719
- resolved: {
2720
- owner: session.owner,
2721
- repo: session.repo,
2722
- pullNumber: session.pullNumber,
2723
- ref: session.ref
2724
- },
3633
+ resolved: resolvedFromSession(session),
2725
3634
  inReplyTo: id,
2726
3635
  body: text
2727
3636
  });
@@ -2757,12 +3666,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2757
3666
  const text = typeof request.body === "string" ? request.body.trim() : "";
2758
3667
  if (!text) return c.json({ error: "body is required" }, 400);
2759
3668
  const result = await updatePrReviewComment({
2760
- resolved: {
2761
- owner: session.owner,
2762
- repo: session.repo,
2763
- pullNumber: session.pullNumber,
2764
- ref: session.ref
2765
- },
3669
+ resolved: resolvedFromSession(session),
2766
3670
  commentId: id,
2767
3671
  body: text
2768
3672
  });
@@ -2781,12 +3685,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2781
3685
  const id = Number(c.req.param("id"));
2782
3686
  if (!Number.isFinite(id)) return c.json({ error: "Invalid comment id" }, 400);
2783
3687
  const result = await deletePrReviewComment({
2784
- resolved: {
2785
- owner: session.owner,
2786
- repo: session.repo,
2787
- pullNumber: session.pullNumber,
2788
- ref: session.ref
2789
- },
3688
+ resolved: resolvedFromSession(session),
2790
3689
  commentId: id
2791
3690
  });
2792
3691
  if (!result.ok) return c.json({ error: result.error ?? "Delete failed" }, 502);
@@ -2806,7 +3705,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2806
3705
  if (typeof request.resolved !== "boolean") return c.json({ error: "resolved must be a boolean" }, 400);
2807
3706
  const result = await setPrReviewThreadResolved({
2808
3707
  threadId,
2809
- resolved: request.resolved
3708
+ resolved: request.resolved,
3709
+ host: session.host
2810
3710
  });
2811
3711
  if (!result.ok) return c.json({ error: result.error ?? "Thread update failed" }, 502);
2812
3712
  const next = await syncExistingPrReviewData(session);
@@ -2848,6 +3748,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2848
3748
  const body = await c.req.json();
2849
3749
  const session = await prStore.get();
2850
3750
  if (!session) return notInPrMode(c);
3751
+ const severityRaw = body.severity;
3752
+ const severity = severityRaw === "blocking" || severityRaw === "nit" || severityRaw === "question" || severityRaw === "praise" || severityRaw === "none" ? severityRaw === "none" ? void 0 : severityRaw : void 0;
2851
3753
  const comment = {
2852
3754
  id: crypto.randomUUID(),
2853
3755
  filePath: body.filePath,
@@ -2858,7 +3760,8 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2858
3760
  body: body.body,
2859
3761
  status: "open",
2860
3762
  createdAt: Date.now(),
2861
- replies: []
3763
+ replies: [],
3764
+ ...severity ? { severity } : {}
2862
3765
  };
2863
3766
  const next = {
2864
3767
  ...session,
@@ -2937,7 +3840,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2937
3840
  if (decision !== "approve" && decision !== "comment" && decision !== "request-changes" && decision !== "draft") return c.json({ error: "decision must be one of: approve, comment, request-changes, draft" }, 400);
2938
3841
  const generalBody = typeof body.body === "string" ? body.body : "";
2939
3842
  if (body.dryRun === true) {
2940
- const { buildReviewPayload } = await import("./github-BjZZcSKV.mjs");
3843
+ const { buildReviewPayload } = await import("./github-DL7-BqhD.mjs");
2941
3844
  const payload = buildReviewPayload({
2942
3845
  decision,
2943
3846
  body: generalBody,
@@ -2952,12 +3855,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2952
3855
  });
2953
3856
  }
2954
3857
  const result = await submitReview({
2955
- resolved: {
2956
- owner: session.owner,
2957
- repo: session.repo,
2958
- pullNumber: session.pullNumber,
2959
- ref: session.ref
2960
- },
3858
+ resolved: resolvedFromSession(session),
2961
3859
  decision,
2962
3860
  body: generalBody,
2963
3861
  comments: session.comments ?? []
@@ -2966,12 +3864,7 @@ function createApp(clientDir, diffOptsInput = DEFAULTS, commentStore, planStore,
2966
3864
  let existingComments = session.existingComments;
2967
3865
  let existingReviews = session.existingReviews ?? [];
2968
3866
  try {
2969
- const resolved = {
2970
- owner: session.owner,
2971
- repo: session.repo,
2972
- pullNumber: session.pullNumber,
2973
- ref: session.ref
2974
- };
3867
+ const resolved = resolvedFromSession(session);
2975
3868
  existingReviews = await fetchExistingReviewsViaGh(resolved);
2976
3869
  existingComments = await fetchExistingCommentsViaGh(resolved, existingReviews);
2977
3870
  } catch {}