@qwen-code/qwen-code 0.21.3 → 0.21.4-nightly.20260804.d6f55a1c9

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 (274) hide show
  1. package/bundled/qc-helper/docs/configuration/model-providers.md +8 -7
  2. package/bundled/qc-helper/docs/configuration/settings.md +14 -1
  3. package/bundled/qc-helper/docs/features/channels/github.md +12 -8
  4. package/bundled/qc-helper/docs/features/code-review.md +16 -1
  5. package/bundled/qc-helper/docs/features/commands.md +46 -40
  6. package/bundled/qc-helper/docs/features/mcp.md +11 -3
  7. package/bundled/qc-helper/docs/qwen-serve.md +27 -26
  8. package/bundled/qc-helper/docs/reference/keyboard-shortcuts.md +50 -19
  9. package/bundled/review/SKILL.md +99 -23
  10. package/chunks/{MaxSizedBox-OJR636GP.js → MaxSizedBox-6J7WTW2Q.js} +16 -15
  11. package/chunks/{StandaloneSessionPicker-AMMBF5UY.js → StandaloneSessionPicker-J3HNBNWF.js} +30 -29
  12. package/chunks/{acpAgent-M57LVIQA.js → acpAgent-SGIEWC4W.js} +681 -291
  13. package/chunks/{agent-PE37ARLM.js → agent-W6IG55Q7.js} +16 -15
  14. package/chunks/{agent-headless-45ASME4R.js → agent-headless-EAPKUL6K.js} +16 -15
  15. package/chunks/{anthropicContentGenerator-WCBYB6JG.js → anthropicContentGenerator-3CAHEZBM.js} +4 -2
  16. package/chunks/{artifact-tool-TBOYEKTY.js → artifact-tool-RROCCJDA.js} +1 -1
  17. package/chunks/{askUserQuestion-23Y5WZFJ.js → askUserQuestion-2EPPBPUZ.js} +1 -1
  18. package/chunks/{bridge-K3ZRPO4D.js → bridge-SNQAVLI3.js} +17 -16
  19. package/chunks/{ca-4OJN75WT.js → ca-NYKNH222.js} +4 -0
  20. package/chunks/{channel-management-service-IXJ2MZTS.js → channel-management-service-UHX56NBO.js} +1 -1
  21. package/chunks/{channel-settings-store-PEAR3IME.js → channel-settings-store-TR7UMQVQ.js} +22 -21
  22. package/chunks/{chunk-L7FTDKCD.js → chunk-23DFTW2G.js} +21 -3
  23. package/chunks/{chunk-PWCQRLM5.js → chunk-2AGOQ7QO.js} +1 -1
  24. package/chunks/{chunk-HUIQ4FYC.js → chunk-2UYGQRYZ.js} +3 -3
  25. package/chunks/{chunk-4K3RV3OA.js → chunk-36Q4BOUL.js} +243 -108
  26. package/chunks/{chunk-2MI7ZTRT.js → chunk-3KTHFE53.js} +5 -5
  27. package/chunks/{chunk-QDWLUXKP.js → chunk-3X3CRYEU.js} +1 -1
  28. package/chunks/{chunk-YXEVDA66.js → chunk-4LLUH3A7.js} +19 -18
  29. package/chunks/{chunk-QPSYIV2O.js → chunk-4RNMAX6B.js} +2492 -690
  30. package/chunks/{chunk-O2ZYHG33.js → chunk-5L6CQ4ZU.js} +131 -67
  31. package/chunks/{chunk-HEBW74ZW.js → chunk-5WFVHBQR.js} +3 -3
  32. package/chunks/{chunk-LZV2BZPL.js → chunk-6BBIRJ6Z.js} +2 -2
  33. package/chunks/{chunk-TZTBZ25J.js → chunk-6WBWIPBF.js} +2 -2
  34. package/chunks/{chunk-QWDVZH24.js → chunk-7BUWEQD3.js} +3 -3
  35. package/chunks/{chunk-EGYJRIDF.js → chunk-7DKCE4VO.js} +2 -2
  36. package/chunks/{chunk-UQDFEU47.js → chunk-7E5BCHGR.js} +1165 -385
  37. package/chunks/{chunk-X6QNZOTC.js → chunk-7MFQLOHZ.js} +1 -1
  38. package/chunks/{chunk-MCXMWLFT.js → chunk-7VZZVBW7.js} +5 -5
  39. package/chunks/{chunk-FF75UUB2.js → chunk-7WE7B63C.js} +1 -1
  40. package/chunks/{chunk-M5LIY7Y6.js → chunk-AUM6IYIZ.js} +1 -1
  41. package/chunks/{chunk-K2J5JZ6B.js → chunk-B452JFK7.js} +1 -1
  42. package/chunks/{chunk-6NE5NTHJ.js → chunk-BDUPVCHA.js} +1 -1
  43. package/chunks/{chunk-SKSF3LGQ.js → chunk-BH4HRGTW.js} +4 -4
  44. package/chunks/{chunk-3XF4WT3B.js → chunk-BKLXFYJJ.js} +1 -1
  45. package/chunks/{chunk-RL6J3BPE.js → chunk-BQUXJJ72.js} +3 -3
  46. package/chunks/{chunk-H6FIBBXK.js → chunk-C4EK5K64.js} +13 -13
  47. package/chunks/{chunk-VUNRNRN6.js → chunk-CE424L46.js} +5 -5
  48. package/chunks/{chunk-DSGJLHM3.js → chunk-CJGLCNDE.js} +3 -3
  49. package/chunks/{chunk-FXVHVCNA.js → chunk-CTRZ7IDH.js} +1 -1
  50. package/chunks/{chunk-CFDR3GNL.js → chunk-D7D7RXIJ.js} +11 -0
  51. package/chunks/{chunk-ZGSUFIL5.js → chunk-DMI5SX2V.js} +1 -1
  52. package/chunks/{chunk-X6WUYMOV.js → chunk-DTAPI453.js} +2 -2
  53. package/chunks/{chunk-NW57GHNL.js → chunk-EF2FDVAL.js} +5 -5
  54. package/chunks/{chunk-BSKUTHLO.js → chunk-EVSV25NF.js} +16 -3
  55. package/chunks/{chunk-3MWNROHB.js → chunk-FQL6PVUI.js} +3 -3
  56. package/chunks/{chunk-IF6K42YJ.js → chunk-FSHOUNV4.js} +1 -1
  57. package/chunks/chunk-HGCQ5SE7.js +136 -0
  58. package/chunks/{chunk-GMSBXEH4.js → chunk-HTCXG5RY.js} +6 -6
  59. package/chunks/{chunk-FG63DZQA.js → chunk-I4PRUCND.js} +10 -10
  60. package/chunks/{chunk-YWMDTMAU.js → chunk-J2QPGVAE.js} +2 -2
  61. package/chunks/{chunk-RCXCGZG6.js → chunk-KCHQQBHO.js} +4 -4
  62. package/chunks/{chunk-LGT3YMLN.js → chunk-LEZFUYPM.js} +1 -1
  63. package/chunks/{chunk-G7XXKQYS.js → chunk-LKRMNRSN.js} +27 -1
  64. package/chunks/{chunk-WY2ENRDN.js → chunk-LZPIUB47.js} +1 -1
  65. package/chunks/{chunk-HFVW54NL.js → chunk-MBEMTXQT.js} +9 -2
  66. package/chunks/{chunk-RPYYTL7E.js → chunk-MGSSJT27.js} +2 -2
  67. package/chunks/{chunk-H35Q5CEG.js → chunk-MMWH7ERH.js} +6 -6
  68. package/chunks/{chunk-MUEJ4R3E.js → chunk-MP2XAFAD.js} +3 -3
  69. package/chunks/{chunk-R7XNRHYD.js → chunk-N64L3CUV.js} +1 -1
  70. package/chunks/{chunk-EIMKVOSF.js → chunk-NJ4ANA44.js} +1 -1
  71. package/chunks/{chunk-ZJES4C3I.js → chunk-O6GB7LVR.js} +2 -2
  72. package/chunks/{chunk-QWW6I2UO.js → chunk-PFQWATDS.js} +410 -39
  73. package/chunks/{chunk-XUMAD5IT.js → chunk-PMMWMPWV.js} +1 -1
  74. package/chunks/{chunk-H6ZYP74A.js → chunk-PUQDGXJV.js} +1 -1
  75. package/chunks/{chunk-XMJO6C44.js → chunk-PYXZLKUJ.js} +3 -3
  76. package/chunks/{chunk-KXWERR6P.js → chunk-QIS6FQFI.js} +10 -10
  77. package/chunks/{chunk-22YFMJBW.js → chunk-QK6BFCAL.js} +59 -7
  78. package/chunks/{chunk-HIXVWF7I.js → chunk-R2JZCL4S.js} +3 -3
  79. package/chunks/{chunk-N42C2FU3.js → chunk-RJ2KM6NF.js} +1 -1
  80. package/chunks/{chunk-2LZCLQIS.js → chunk-S5VUT4K2.js} +5 -5
  81. package/chunks/{chunk-4AGGSHCH.js → chunk-SVR6JPEL.js} +197 -53
  82. package/chunks/{chunk-SQ3YD5MI.js → chunk-T4LUW2CP.js} +4 -3
  83. package/chunks/{chunk-BKGA75MU.js → chunk-T7AALAF5.js} +1 -1
  84. package/chunks/{chunk-YU6TBRTA.js → chunk-URJHXOSO.js} +2 -2
  85. package/chunks/{chunk-MTR4JWQJ.js → chunk-UZNJUCOI.js} +4 -4
  86. package/chunks/{chunk-D2GPKXCQ.js → chunk-VQHIIPVE.js} +4 -4
  87. package/chunks/{chunk-IWGTGH4G.js → chunk-W2W6PMZX.js} +2 -2
  88. package/chunks/{chunk-SRY26X4H.js → chunk-WVAESIM6.js} +12 -3
  89. package/chunks/{chunk-5VYFXHY2.js → chunk-XKX3PNZR.js} +3 -3
  90. package/chunks/{chunk-BKXTHMOZ.js → chunk-XLD2R34T.js} +1 -1
  91. package/chunks/{chunk-5JPVOYWA.js → chunk-YT2BQ4JH.js} +5 -5
  92. package/chunks/{chunk-YPUHMQXV.js → chunk-Z5ZLRJFS.js} +1 -1
  93. package/chunks/{chunk-VA5NLVPT.js → chunk-Z7JILXGX.js} +1 -1
  94. package/chunks/{chunk-RR224BUP.js → chunk-ZLJQIGCH.js} +45 -1
  95. package/chunks/{chunk-TXABHVZD.js → chunk-ZNCRSKRP.js} +1 -1
  96. package/chunks/{chunk-UEJESCS7.js → chunk-ZPPP6VCL.js} +1 -1
  97. package/chunks/{chunk-Q3G5L5KF.js → chunk-ZQPHKE4K.js} +1 -1
  98. package/chunks/{computer-use-UJMK6JQU.js → computer-use-NFBEZ73C.js} +16 -15
  99. package/chunks/{config-utils-7COTJMWK.js → config-utils-E3ZQTHGM.js} +2 -2
  100. package/chunks/{contextCommand-4YABGF4S.js → contextCommand-HJN4W5PC.js} +18 -17
  101. package/chunks/{core-runtime-7M4VM3DA.js → core-runtime-Z4IALQ37.js} +16 -15
  102. package/chunks/{create-sub-session-J32WC64U.js → create-sub-session-6EWLAZD5.js} +1 -1
  103. package/chunks/{create-sub-session-HOFDW4N7.js → create-sub-session-MJKTT6R4.js} +28 -21
  104. package/chunks/{cron-create-HMFSHIAR.js → cron-create-CCRIULJE.js} +1 -1
  105. package/chunks/{cron-delete-ZJFHOX57.js → cron-delete-FCFW6BGP.js} +1 -1
  106. package/chunks/{cron-list-ZEX2NBMN.js → cron-list-QDBVCHRF.js} +1 -1
  107. package/chunks/{daemon-status-provider-W2U5Q3J5.js → daemon-status-provider-GR32VNM6.js} +21 -20
  108. package/chunks/{daemon-trust-policy-IFICWPXP.js → daemon-trust-policy-O4DGWLR5.js} +21 -20
  109. package/chunks/{daemon-trust-policy-monitor-GX7EO6FW.js → daemon-trust-policy-monitor-5RHH3YSK.js} +21 -20
  110. package/chunks/{de-A6TI4LBB.js → de-VL76CPLV.js} +4 -0
  111. package/chunks/{deferred-core-runtime-U36OOOKE.js → deferred-core-runtime-A5CKPGQZ.js} +16 -15
  112. package/chunks/{display-image-VS3TXYQH.js → display-image-GUJA352B.js} +2 -2
  113. package/chunks/{dist-NEBRI7WO.js → dist-C5U4LJKW.js} +95 -0
  114. package/chunks/{dist-4KAX7CCL.js → dist-IKGVITP3.js} +401 -24
  115. package/chunks/{earlyInputCapture-5ZWDH3SB.js → earlyInputCapture-ZB4EHBSD.js} +16 -15
  116. package/chunks/{edit-J7SW6UP6.js → edit-QSTJ353U.js} +17 -16
  117. package/chunks/{en-L4UQDLWW.js → en-IRJ2WN5T.js} +4 -0
  118. package/chunks/{enter-worktree-NRLOHRB6.js → enter-worktree-R73VWVFF.js} +16 -15
  119. package/chunks/{enterPlanMode-GDSTEFSM.js → enterPlanMode-OWFAGX5J.js} +16 -15
  120. package/chunks/{environment-KHYEKG5W.js → environment-57MAPDBT.js} +18 -17
  121. package/chunks/{errors-VNLAJV3O.js → errors-5BKZ3KMI.js} +18 -17
  122. package/chunks/{exit-worktree-ARVID7DF.js → exit-worktree-6O2324LA.js} +16 -15
  123. package/chunks/{exitPlanMode-4ZFY7KUH.js → exitPlanMode-GCRUFTNX.js} +16 -15
  124. package/chunks/{fast-path-KA65VREJ.js → fast-path-VN3BV4WN.js} +11 -2
  125. package/chunks/{fr-QF7RI5ZE.js → fr-GK3HQYVB.js} +4 -0
  126. package/chunks/{gemini-5PMCNPF7.js → gemini-K43YNDW7.js} +50 -48
  127. package/chunks/{geminiContentGenerator-NFSYJ4HA.js → geminiContentGenerator-BTPPSMCF.js} +1 -1
  128. package/chunks/{glob-F3EAUDAX.js → glob-4MLY2HOS.js} +16 -15
  129. package/chunks/{goal-tools-SPQ5M6D3.js → goal-tools-3AZKX5WQ.js} +2 -18
  130. package/chunks/{grep-UJMPVZER.js → grep-XFBSAUJW.js} +16 -15
  131. package/chunks/{handleAutoUpdate-MG4FRZPN.js → handleAutoUpdate-HWVO6M65.js} +20 -19
  132. package/chunks/{i18n-EVHV4ZF3.js → i18n-MASWUB3I.js} +17 -16
  133. package/chunks/{image-gen-UC7XVHI7.js → image-gen-XLFJRDFA.js} +2 -2
  134. package/chunks/{initializer-O5B6HUBI.js → initializer-M6JYLRYA.js} +21 -20
  135. package/chunks/{installationInfo-2LIKCNVX.js → installationInfo-TEJOXP5W.js} +17 -16
  136. package/chunks/{ja-URQESVGW.js → ja-3FA46IKD.js} +4 -0
  137. package/chunks/{list-BPKIE3Z4.js → list-UABKY6YY.js} +24 -23
  138. package/chunks/{list-agents-TWEGRJWO.js → list-agents-GC4BRET7.js} +1 -1
  139. package/chunks/{loadedSettingsAdapter-NGLFF4V4.js → loadedSettingsAdapter-L7PIVGJX.js} +21 -20
  140. package/chunks/{loggingContentGenerator-7TFESRG5.js → loggingContentGenerator-W7QO23AD.js} +8 -8
  141. package/chunks/{loop-wakeup-6LLEOQYV.js → loop-wakeup-SQQSBMEY.js} +2 -2
  142. package/chunks/{ls-H5WX22VK.js → ls-63CC3NNX.js} +1 -1
  143. package/chunks/{lsp-G6FXYEIS.js → lsp-P6NVJVNT.js} +1 -1
  144. package/chunks/{managed-npm-update-CLZGGV7F.js → managed-npm-update-WQ2J42H7.js} +17 -16
  145. package/chunks/{mcp-X5OZOOXX.js → mcp-A652OREB.js} +21 -20
  146. package/chunks/{monitor-TGSVSAZZ.js → monitor-Y7YAHCA6.js} +16 -15
  147. package/chunks/{nonInteractiveCli-GUMJ5JLG.js → nonInteractiveCli-EKLP5WCN.js} +48 -46
  148. package/chunks/{notebook-edit-EZISE7IC.js → notebook-edit-MALKVMST.js} +17 -16
  149. package/chunks/{openaiContentGenerator-ZAKS5A6P.js → openaiContentGenerator-WM2TUU7E.js} +8 -8
  150. package/chunks/{pidfile-PCMBX4LR.js → pidfile-QBNVWD7M.js} +16 -15
  151. package/chunks/{processUtils-2CSVE5BW.js → processUtils-4L46PM2I.js} +2 -2
  152. package/chunks/{pt-EEEQXRUI.js → pt-3AOBKPIJ.js} +4 -0
  153. package/chunks/{qwenContentGenerator-S3H6PIC4.js → qwenContentGenerator-GG6SY4YP.js} +17 -17
  154. package/chunks/{qwenOAuth2-MNU425VE.js → qwenOAuth2-PE27GJRG.js} +1 -1
  155. package/chunks/{read-file-HXJXHBI6.js → read-file-IHXKC6D3.js} +6 -6
  156. package/chunks/{read-mcp-resource-DRZSI3HE.js → read-mcp-resource-RTSSEER5.js} +1 -1
  157. package/chunks/{record-artifact-IPIRWMFY.js → record-artifact-L3BAHQFN.js} +2 -2
  158. package/chunks/{resumeHistoryUtils-4QAMC2S6.js → resumeHistoryUtils-BWVAHKRZ.js} +19 -18
  159. package/chunks/{ripGrep-MCETPF2E.js → ripGrep-7ASLNF5O.js} +16 -15
  160. package/chunks/{ru-WGZDYQAB.js → ru-VJRAMC4W.js} +4 -0
  161. package/chunks/{run-qwen-serve-3NMODTXV.js → run-qwen-serve-JLFJHJSU.js} +82 -35
  162. package/chunks/{runtime-AQOV7DVP.js → runtime-DVQFRHKX.js} +24 -23
  163. package/chunks/{scheduler-DN7SZ6ZE.js → scheduler-WMFCKI7V.js} +16 -15
  164. package/chunks/{sdk-exporters-http-IKNWQJNV.js → sdk-exporters-http-IL2346MJ.js} +6 -2
  165. package/chunks/{sdk-impl-7CUUMQDB.js → sdk-impl-SYKGVWOZ.js} +2 -2
  166. package/chunks/{send-message-HGCVVDTZ.js → send-message-TL6FIMMN.js} +3 -3
  167. package/chunks/{serve-WZB45HVT.js → serve-NYWUO6YF.js} +22 -20
  168. package/chunks/{server-GOTZFEEZ.js → server-WHLRWZCG.js} +44 -42
  169. package/chunks/{session-23LOFLWN.js → session-EN34VEJM.js} +49 -47
  170. package/chunks/{settings-LI2ORVXC.js → settings-FZH5YZLH.js} +20 -19
  171. package/chunks/{shell-GLC6OP3C.js → shell-FQLEHJKT.js} +16 -15
  172. package/chunks/{skill-C7W6M4GO.js → skill-QAXTGG6F.js} +7 -7
  173. package/chunks/{skill-settings-W25NCK5I.js → skill-settings-SRUMUJIE.js} +20 -19
  174. package/chunks/{spawnChannel-OHJLEBLD.js → spawnChannel-VYQCFI7T.js} +16 -15
  175. package/chunks/{standalone-update-E4V5V7WT.js → standalone-update-LEPP5BOB.js} +18 -17
  176. package/chunks/{startInteractiveUI-FKILLW7Q.js → startInteractiveUI-IQIG2MKN.js} +77 -62
  177. package/chunks/{syntheticOutput-EYL7DWO7.js → syntheticOutput-BN7E626U.js} +2 -2
  178. package/chunks/{task-create-CSCEG4SM.js → task-create-2X4UP4F3.js} +4 -4
  179. package/chunks/{task-list-6CVQPORX.js → task-list-WOLJPZ64.js} +1 -1
  180. package/chunks/{task-stop-Y5ZTFVRX.js → task-stop-TNXRNAL2.js} +1 -1
  181. package/chunks/{task-update-C52LNFU5.js → task-update-VDUSN36E.js} +4 -4
  182. package/chunks/{team-create-HBIJ7NHT.js → team-create-7B5NKWA2.js} +16 -15
  183. package/chunks/{team-delete-CE7DC53R.js → team-delete-D2VMSDOT.js} +1 -1
  184. package/chunks/{team-plan-approval-IBJOKS4J.js → team-plan-approval-J6TURTZC.js} +16 -15
  185. package/chunks/{terminal-image-renderer-BOQJPVLB.js → terminal-image-renderer-KZMAF5E5.js} +17 -16
  186. package/chunks/{theme-manager-NSS7VLUD.js → theme-manager-NPISCKAX.js} +16 -15
  187. package/chunks/{todoWrite-YKAA57W2.js → todoWrite-MHWHO5UO.js} +3 -2
  188. package/chunks/{tool-search-DEAS54VG.js → tool-search-MFTNRTFA.js} +8 -8
  189. package/chunks/{total-session-admission-4LWTX3WD.js → total-session-admission-42THE4X2.js} +17 -16
  190. package/chunks/{trustedFolders-NF3J2TA7.js → trustedFolders-OT7IORFI.js} +17 -16
  191. package/chunks/{update-relaunch-WUHRH5V3.js → update-relaunch-2NJAMGWQ.js} +5 -5
  192. package/chunks/{updateCheck-7EG4BLBH.js → updateCheck-XSIBJXYA.js} +19 -18
  193. package/chunks/{useAutoAcceptIndicator-ZRV56GVP.js → useAutoAcceptIndicator-VH4DU3JR.js} +22 -21
  194. package/chunks/{validateNonInterActiveAuth-WC2FPE3I.js → validateNonInterActiveAuth-JD3VEJ2O.js} +45 -43
  195. package/chunks/{version-6SS7KCUP.js → version-F75ALUI3.js} +1 -1
  196. package/chunks/{web-fetch-Y5QQMPKA.js → web-fetch-Y6VEJMFN.js} +6 -6
  197. package/chunks/{web-search-QKKEU723.js → web-search-BTKVYWOR.js} +4 -4
  198. package/chunks/{workflow-PWIU6Q2M.js → workflow-LWFOBFN3.js} +17 -16
  199. package/chunks/{workspace-providers-status-B2DM6FNK.js → workspace-providers-status-VFEB7C5S.js} +23 -22
  200. package/chunks/{workspace-registration-store-7Y33TTHJ.js → workspace-registration-store-MB24E7PA.js} +1 -1
  201. package/chunks/{workspace-registry-TSNJKLPC.js → workspace-registry-FHFAPD74.js} +17 -16
  202. package/chunks/{workspace-service-4I2QHDX4.js → workspace-service-A6EFCR5H.js} +24 -23
  203. package/chunks/{workspace-skills-status-HVXNWVGR.js → workspace-skills-status-SDX2JT7P.js} +21 -20
  204. package/chunks/{workspace-trust-reconciler-RDBBGMWP.js → workspace-trust-reconciler-5NOCATAW.js} +22 -21
  205. package/chunks/{write-file-RQ4DG5RY.js → write-file-OHTP7KKV.js} +16 -15
  206. package/chunks/{zh-72XPUG5P.js → zh-ARWDLDLR.js} +4 -0
  207. package/chunks/{zh-TW-LTB4SRH3.js → zh-TW-Y4RGI3P7.js} +4 -0
  208. package/chunks/{zoom-image-O5YCB7WN.js → zoom-image-WLSVNK2C.js} +6 -6
  209. package/cli.js +12 -12
  210. package/locales/ca.js +8 -0
  211. package/locales/de.js +8 -0
  212. package/locales/en.js +8 -0
  213. package/locales/fr.js +8 -0
  214. package/locales/ja.js +8 -0
  215. package/locales/pt.js +8 -0
  216. package/locales/ru.js +8 -0
  217. package/locales/zh-TW.js +7 -0
  218. package/locales/zh.js +7 -0
  219. package/package.json +3 -3
  220. package/web-shell/assets/{arc-Bd7d781i.js → arc-Dpg66jw7.js} +1 -1
  221. package/web-shell/assets/{architectureDiagram-3BPJPVTR-CpnOJqNE.js → architectureDiagram-3BPJPVTR-BJ4FIBxN.js} +1 -1
  222. package/web-shell/assets/{blockDiagram-GPEHLZMM-NxVTiWnk.js → blockDiagram-GPEHLZMM-VXmYdtYT.js} +1 -1
  223. package/web-shell/assets/{c4Diagram-AAUBKEIU-BWT75l82.js → c4Diagram-AAUBKEIU-BNLDb8FE.js} +1 -1
  224. package/web-shell/assets/channel-Bd9wOnaA.js +1 -0
  225. package/web-shell/assets/{chunk-2J33WTMH-Dzz03hk6.js → chunk-2J33WTMH-DR5yHxr0.js} +1 -1
  226. package/web-shell/assets/{chunk-4BX2VUAB-DRF-egfm.js → chunk-4BX2VUAB-Czkq8UZU.js} +1 -1
  227. package/web-shell/assets/{chunk-55IACEB6-Co15vq5x.js → chunk-55IACEB6-DugUgM6s.js} +1 -1
  228. package/web-shell/assets/{chunk-727SXJPM-DHSUk1J7.js → chunk-727SXJPM-Dw6quA1K.js} +1 -1
  229. package/web-shell/assets/{chunk-AQP2D5EJ-Br8u_E2R.js → chunk-AQP2D5EJ-BoVYOTTz.js} +1 -1
  230. package/web-shell/assets/{chunk-FMBD7UC4-DXbPTnqF.js → chunk-FMBD7UC4--Amcse-j.js} +1 -1
  231. package/web-shell/assets/{chunk-ND2GUHAM-BleQdi4d.js → chunk-ND2GUHAM-ajaEYAC8.js} +1 -1
  232. package/web-shell/assets/{chunk-QZHKN3VN-CqgBf-95.js → chunk-QZHKN3VN-CUNDZ7im.js} +1 -1
  233. package/web-shell/assets/classDiagram-4FO5ZUOK-XhCT8FE3.js +1 -0
  234. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-XhCT8FE3.js +1 -0
  235. package/web-shell/assets/{cose-bilkent-S5V4N54A-WS7b13Yg.js → cose-bilkent-S5V4N54A-BnfkD-m1.js} +1 -1
  236. package/web-shell/assets/{dagre-BM42HDAG-v_55ki8o.js → dagre-BM42HDAG-BuUdFk7Z.js} +1 -1
  237. package/web-shell/assets/{diagram-2AECGRRQ-DaYAjbdK.js → diagram-2AECGRRQ-B2ZXxoab.js} +1 -1
  238. package/web-shell/assets/{diagram-5GNKFQAL-D_Hy-L64.js → diagram-5GNKFQAL-BsgOICzM.js} +1 -1
  239. package/web-shell/assets/{diagram-KO2AKTUF-dB06cZyD.js → diagram-KO2AKTUF-DTTjG6U-.js} +1 -1
  240. package/web-shell/assets/{diagram-LMA3HP47-DzSluD4F.js → diagram-LMA3HP47-temLf1x7.js} +1 -1
  241. package/web-shell/assets/{diagram-OG6HWLK6-Cvb6CeXQ.js → diagram-OG6HWLK6-CRFjScs2.js} +1 -1
  242. package/web-shell/assets/{erDiagram-TEJ5UH35-7eB2Hmov.js → erDiagram-TEJ5UH35-D4oaY-rQ.js} +1 -1
  243. package/web-shell/assets/{flowDiagram-I6XJVG4X-B08iGCw_.js → flowDiagram-I6XJVG4X-BqG8B49b.js} +1 -1
  244. package/web-shell/assets/{ganttDiagram-6RSMTGT7-BgsrCHq-.js → ganttDiagram-6RSMTGT7-DffQBdd5.js} +1 -1
  245. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-DQLurQ2i.js → gitGraphDiagram-PVQCEYII-CMDPfseF.js} +1 -1
  246. package/web-shell/assets/index-2F4RsNzd.js +1756 -0
  247. package/web-shell/assets/index-CaucmEO_.css +5 -0
  248. package/web-shell/assets/{index-B_1Z0Mgr.js → index-qTQR_smP.js} +1 -1
  249. package/web-shell/assets/{infoDiagram-5YYISTIA-DXZGO8qB.js → infoDiagram-5YYISTIA-Cn3T83MA.js} +1 -1
  250. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-g7gbuH2w.js → ishikawaDiagram-YF4QCWOH-ijFs7e25.js} +1 -1
  251. package/web-shell/assets/{journeyDiagram-JHISSGLW-euefICGn.js → journeyDiagram-JHISSGLW-CINTv8VB.js} +1 -1
  252. package/web-shell/assets/{kanban-definition-UN3LZRKU-Dws8-mjG.js → kanban-definition-UN3LZRKU-CMGRZCxx.js} +1 -1
  253. package/web-shell/assets/{linear-C63F0tyh.js → linear-Co-2_37d.js} +1 -1
  254. package/web-shell/assets/{mermaid.core-fLrhp_4-.js → mermaid.core-DijbyaN5.js} +5 -5
  255. package/web-shell/assets/{mindmap-definition-RKZ34NQL-qRkydfT-.js → mindmap-definition-RKZ34NQL-k_sDYDPZ.js} +1 -1
  256. package/web-shell/assets/{pieDiagram-4H26LBE5-BZ0wQaKN.js → pieDiagram-4H26LBE5-CG-oz8V3.js} +1 -1
  257. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-Fjq6GbSZ.js → quadrantDiagram-W4KKPZXB-Baq7HIG2.js} +1 -1
  258. package/web-shell/assets/{requirementDiagram-4Y6WPE33-CqWvjLHU.js → requirementDiagram-4Y6WPE33-CDM3ZvBt.js} +1 -1
  259. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-VcXX6PVZ.js → sankeyDiagram-5OEKKPKP-C2dJbWKN.js} +1 -1
  260. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-BJSRtxFr.js → sequenceDiagram-3UESZ5HK-Cf1_h59p.js} +1 -1
  261. package/web-shell/assets/{stateDiagram-AJRCARHV-Dt9zUwmZ.js → stateDiagram-AJRCARHV-CI5clUEc.js} +1 -1
  262. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BDc8asKD.js +1 -0
  263. package/web-shell/assets/{timeline-definition-PNZ67QCA-DQwlNRws.js → timeline-definition-PNZ67QCA-B2O8cAZX.js} +1 -1
  264. package/web-shell/assets/{vennDiagram-CIIHVFJN-DwxjUEtQ.js → vennDiagram-CIIHVFJN-BLsJ_h9s.js} +1 -1
  265. package/web-shell/assets/{wardley-L42UT6IY-D3QdMSpJ.js → wardley-L42UT6IY-BlPU4FTA.js} +1 -1
  266. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-BddN8oK8.js → wardleyDiagram-YWT4CUSO-D_U-RdU4.js} +1 -1
  267. package/web-shell/assets/{xychartDiagram-2RQKCTM6-BUNuBrRJ.js → xychartDiagram-2RQKCTM6-BynXGSTl.js} +1 -1
  268. package/web-shell/index.html +2 -2
  269. package/web-shell/assets/channel-A434sWK5.js +0 -1
  270. package/web-shell/assets/classDiagram-4FO5ZUOK-CwPuhldB.js +0 -1
  271. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-CwPuhldB.js +0 -1
  272. package/web-shell/assets/index-Bi1dP2mU.css +0 -5
  273. package/web-shell/assets/index-DJ-z1Oba.js +0 -1756
  274. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-CCxQt56w.js +0 -1
@@ -2,13 +2,13 @@
2
2
  "use strict";
3
3
  import {
4
4
  serveCommand
5
- } from "./chunk-BSKUTHLO.js";
5
+ } from "./chunk-EVSV25NF.js";
6
6
  import {
7
7
  listCommand
8
- } from "./chunk-MTR4JWQJ.js";
8
+ } from "./chunk-UZNJUCOI.js";
9
9
  import {
10
10
  getErrorMessage as getErrorMessage2
11
- } from "./chunk-EGYJRIDF.js";
11
+ } from "./chunk-7DKCE4VO.js";
12
12
  import {
13
13
  ObservedChannelContactStore
14
14
  } from "./chunk-DYA25Y7U.js";
@@ -46,22 +46,22 @@ import {
46
46
  registerToolCallDispatch,
47
47
  selectFirstModel,
48
48
  sessionsPath
49
- } from "./chunk-2LZCLQIS.js";
49
+ } from "./chunk-S5VUT4K2.js";
50
50
  import {
51
51
  getExtensionManager,
52
52
  requestChoicePluginNonInteractive,
53
53
  requestConsentNonInteractive,
54
54
  requestConsentOrFail,
55
55
  resolveExtensionCommandScope
56
- } from "./chunk-D2GPKXCQ.js";
56
+ } from "./chunk-VQHIIPVE.js";
57
57
  import {
58
58
  collectProviderModelsForProtocol,
59
59
  getAuthTypeFromEnv,
60
60
  resolveCliGenerationConfig
61
- } from "./chunk-5VYFXHY2.js";
61
+ } from "./chunk-XKX3PNZR.js";
62
62
  import {
63
63
  parseChannelConfig
64
- } from "./chunk-LGT3YMLN.js";
64
+ } from "./chunk-LEZFUYPM.js";
65
65
  import {
66
66
  findCliEntryPath
67
67
  } from "./chunk-V6WHFGST.js";
@@ -128,29 +128,29 @@ import {
128
128
  assembleMcpServers,
129
129
  getPendingGatedMcpServers,
130
130
  mcpCommand
131
- } from "./chunk-2MI7ZTRT.js";
131
+ } from "./chunk-3KTHFE53.js";
132
132
  import {
133
133
  loadSettings
134
- } from "./chunk-22YFMJBW.js";
134
+ } from "./chunk-QK6BFCAL.js";
135
135
  import {
136
136
  detectSystemLanguage,
137
137
  getCurrentLanguage,
138
138
  initializeI18n,
139
139
  resolveLanguageSetting,
140
140
  t
141
- } from "./chunk-KXWERR6P.js";
141
+ } from "./chunk-QIS6FQFI.js";
142
142
  import {
143
143
  loadEnvironment
144
- } from "./chunk-6NE5NTHJ.js";
144
+ } from "./chunk-BDUPVCHA.js";
145
145
  import {
146
146
  getCliVersion
147
- } from "./chunk-BKXTHMOZ.js";
147
+ } from "./chunk-XLD2R34T.js";
148
148
  import {
149
149
  getPackageJson
150
150
  } from "./chunk-VJNBRRZG.js";
151
151
  import {
152
152
  isWorkspaceTrusted
153
- } from "./chunk-M5LIY7Y6.js";
153
+ } from "./chunk-AUM6IYIZ.js";
154
154
  import {
155
155
  require_strip_json_comments
156
156
  } from "./chunk-ALGGS7UH.js";
@@ -191,7 +191,7 @@ import {
191
191
  unquoteCStylePath,
192
192
  updateChannelMemoryEntry,
193
193
  updateSetting
194
- } from "./chunk-UQDFEU47.js";
194
+ } from "./chunk-7E5BCHGR.js";
195
195
  import {
196
196
  nextFireTime,
197
197
  parseCron
@@ -202,18 +202,18 @@ import {
202
202
  import {
203
203
  parseBooleanEnvFlag,
204
204
  resolveTelemetrySettings
205
- } from "./chunk-RPYYTL7E.js";
205
+ } from "./chunk-MGSSJT27.js";
206
206
  import {
207
207
  getAllGeminiMdFilenames,
208
208
  setGeminiMdFilename
209
- } from "./chunk-4K3RV3OA.js";
209
+ } from "./chunk-36Q4BOUL.js";
210
210
  import {
211
211
  SchemaValidator,
212
212
  ToolNames
213
- } from "./chunk-CFDR3GNL.js";
213
+ } from "./chunk-D7D7RXIJ.js";
214
214
  import {
215
215
  DEFAULT_QWEN_EMBEDDING_MODEL
216
- } from "./chunk-H35Q5CEG.js";
216
+ } from "./chunk-MMWH7ERH.js";
217
217
  import {
218
218
  isTlsVerificationDisabled
219
219
  } from "./chunk-BFHXDAR7.js";
@@ -222,7 +222,7 @@ import {
222
222
  } from "./chunk-UH7Z7Y45.js";
223
223
  import {
224
224
  recordChannelMemoryRecallMetrics
225
- } from "./chunk-O2ZYHG33.js";
225
+ } from "./chunk-5L6CQ4ZU.js";
226
226
  import {
227
227
  FatalConfigError,
228
228
  FatalSandboxError,
@@ -358,9 +358,9 @@ var require_command_exists = __commonJS({
358
358
  cleanInput = /* @__PURE__ */ __name(function(s) {
359
359
  var isPathName = /[\\]/.test(s);
360
360
  if (isPathName) {
361
- var dirname19 = '"' + path3.dirname(s) + '"';
362
- var basename5 = '"' + path3.basename(s) + '"';
363
- return dirname19 + ":" + basename5;
361
+ var dirname24 = '"' + path3.dirname(s) + '"';
362
+ var basename6 = '"' + path3.basename(s) + '"';
363
+ return dirname24 + ":" + basename6;
364
364
  }
365
365
  return '"' + s + '"';
366
366
  }, "cleanInput");
@@ -368,10 +368,10 @@ var require_command_exists = __commonJS({
368
368
  module.exports = /* @__PURE__ */ __name(function commandExists2(commandName, callback) {
369
369
  var cleanedCommandName = cleanInput(commandName);
370
370
  if (!callback && typeof Promise !== "undefined") {
371
- return new Promise(function(resolve20, reject) {
371
+ return new Promise(function(resolve24, reject) {
372
372
  commandExists2(commandName, function(error, output) {
373
373
  if (output) {
374
- resolve20(commandName);
374
+ resolve24(commandName);
375
375
  } else {
376
376
  reject(error);
377
377
  }
@@ -2255,8 +2255,8 @@ function createBridgeReadinessGate() {
2255
2255
  current: /* @__PURE__ */ __name(() => pending, "current"),
2256
2256
  block: /* @__PURE__ */ __name(() => {
2257
2257
  if (pending) return;
2258
- pending = new Promise((resolve20) => {
2259
- releasePending = resolve20;
2258
+ pending = new Promise((resolve24) => {
2259
+ releasePending = resolve24;
2260
2260
  });
2261
2261
  }, "block"),
2262
2262
  release: /* @__PURE__ */ __name(() => {
@@ -2338,7 +2338,7 @@ function createBridgeRecovery(options) {
2338
2338
  writeStderrLine(
2339
2339
  `[Channel] Bridge crashed (${recentCrashCount}/${MAX_CRASH_RESTARTS} in window). Restarting in ${RESTART_DELAY_MS / 1e3}s...`
2340
2340
  );
2341
- await new Promise((resolve20) => setTimeout(resolve20, RESTART_DELAY_MS));
2341
+ await new Promise((resolve24) => setTimeout(resolve24, RESTART_DELAY_MS));
2342
2342
  const bridge = new AcpBridge(bridgeOpts);
2343
2343
  setBridge(bridge);
2344
2344
  attachDisconnectHandler(bridge);
@@ -3206,12 +3206,12 @@ async function abortableStartup(value, signal) {
3206
3206
  const promise = Promise.resolve(value);
3207
3207
  if (!signal) return await promise;
3208
3208
  if (signal.aborted) throw startupAbortError();
3209
- return await new Promise((resolve20, reject) => {
3209
+ return await new Promise((resolve24, reject) => {
3210
3210
  const onAbort = /* @__PURE__ */ __name(() => {
3211
3211
  reject(startupAbortError());
3212
3212
  }, "onAbort");
3213
3213
  signal.addEventListener("abort", onAbort, { once: true });
3214
- promise.then(resolve20, reject).finally(() => {
3214
+ promise.then(resolve24, reject).finally(() => {
3215
3215
  signal.removeEventListener("abort", onAbort);
3216
3216
  });
3217
3217
  });
@@ -3619,7 +3619,7 @@ function reportStartupToSupervisor(message, signal, subscribeMessage = (listener
3619
3619
  if (!send) {
3620
3620
  return Promise.reject(new Error("Channel worker IPC is unavailable."));
3621
3621
  }
3622
- return new Promise((resolve20, reject) => {
3622
+ return new Promise((resolve24, reject) => {
3623
3623
  let settled = false;
3624
3624
  let unsubscribeMessage = /* @__PURE__ */ __name(() => {
3625
3625
  }, "unsubscribeMessage");
@@ -3633,7 +3633,7 @@ function reportStartupToSupervisor(message, signal, subscribeMessage = (listener
3633
3633
  settled = true;
3634
3634
  cleanup();
3635
3635
  if (error) reject(error);
3636
- else resolve20();
3636
+ else resolve24();
3637
3637
  }, "finish");
3638
3638
  const onMessage = /* @__PURE__ */ __name((value) => {
3639
3639
  if (isChannelStartupReportAckMessage(value)) {
@@ -3881,8 +3881,8 @@ var daemonWorkerCommand = {
3881
3881
  let shuttingDown = false;
3882
3882
  let exitCode = 0;
3883
3883
  let finish;
3884
- const finished = new Promise((resolve20) => {
3885
- finish = resolve20;
3884
+ const finished = new Promise((resolve24) => {
3885
+ finish = resolve24;
3886
3886
  });
3887
3887
  const shutdown = /* @__PURE__ */ __name(async (reason) => {
3888
3888
  if (shuttingDown) {
@@ -3910,8 +3910,8 @@ var daemonWorkerCommand = {
3910
3910
  ...activeChannelDeliveries.values(),
3911
3911
  ...activeWebhookTasks.values()
3912
3912
  ]),
3913
- new Promise((resolve20) => {
3914
- const timer = setTimeout(resolve20, WORKER_SHUTDOWN_DRAIN_MS);
3913
+ new Promise((resolve24) => {
3914
+ const timer = setTimeout(resolve24, WORKER_SHUTDOWN_DRAIN_MS);
3915
3915
  timer.unref();
3916
3916
  })
3917
3917
  ]);
@@ -6127,6 +6127,22 @@ A finding an A/B settled carries \`Source: [probe]\` like any other run-produced
6127
6127
 
6128
6128
  It writes the \`run:\` script **verbatim** as an executable and reports what the runner would have supplied: the effective \`env:\` with all three levels merged and each key's level named, every \`\${{ \u2026 }}\` site listed **unevaluated** \u2014 that list is precisely what you have to stub, because the command refuses to invent values for it \u2014 the resolved \`shell\` and \`working-directory\`, and the commands the script invokes. Stubbing and input are yours: shim \`gh\`/\`curl\` onto \`PATH\`, export the env, run it, observe. **Combined with \`base-tree\`, a workflow A/B is two invocations** \u2014 extract the same step from both trees, feed both the same input, diff what each would have done. That is how the strongest workflow finding in this pipeline's history was produced: the real composer step from both arms, a stubbed \`gh\`, and a byte-for-byte comparison against a comment the workflow had actually posted. Three limits worth knowing before you spend the step: a \`uses:\` step has no \`run:\` and is refused rather than simulated; a step NAME that two steps in the job share is refused as ambiguous rather than resolved to the first, so pass the index (which is what an A/B wants anyway \u2014 the two trees must select the same step, and a name that moved between them is exactly how they stop doing that); and the \`invokes\` list is a labelled heuristic \u2014 the verbatim script beside it is the authority.
6129
6129
 
6130
+ **When the claim is about what the product DOES at runtime, drive it \u2014 two commands make that mechanical.** A finding about behaviour ("this hangs when the provider 429s", "the retry never fires", "the daemon answers before it is ready") is settled by running the built product and watching, and the two halves that used to be hand-written every time are now commands.
6131
+
6132
+ \`\`\`bash
6133
+ "\${QWEN_CODE_CLI:-qwen}" review mock-provider --responder <a module you write> \\
6134
+ --log <plan dir>/mock.jsonl --ttl 600 --out <plan dir>/mock.json &
6135
+ until [ -s <plan dir>/mock.json ]; do sleep 0.1; done # its port is in that report
6136
+ "\${QWEN_CODE_CLI:-qwen}" review drive --cwd <the worktree> --script <what to run> \\
6137
+ --ready <a command polled until it exits 0> --timeout 300 --out <plan dir>/drive.json
6138
+ \`\`\`
6139
+
6140
+ \`mock-provider\` serves \`/v1/chat/completions\` (OpenAI) and \`/v1/messages\` (Anthropic) on an OS-assigned port it reports back, and appends every request to a JSONL log; your responder module exports \`respond(req)\` returning \`{text}\`, \`{tool, args}\` or \`{status, body}\`, and never has to get SSE framing right. **It serves for the whole \`--ttl\` and returns only when that expires** \u2014 so background it and wait, as above; run sequentially it is already shut down by the time the next line starts. Its report is written once the port is bound, which is what makes the file's appearance a readiness signal rather than a guess, and the TTL is the only thing that ends it \u2014 set it to bound the drive, not to match it. **The log is the A/B evidence** \u2014 drive the same script against the PR worktree and the \`base-tree\` path, then diff the two request sequences; a difference is evidence, a reading is not.
6141
+
6142
+ \`drive\` owns the three things that used to be guesswork, and its \`outcome\` is what you rule on, never the captured text alone: \`completed\` carries the script's own \`exitCode\` and is the only value that licenses a behavioural claim; \`not-ready\` means the readiness probe never passed, so **nothing was driven and nothing observed is evidence either way**; \`timed-out\` and \`overflowed\` mean the capture is PARTIAL \u2014 a partial capture is not evidence that the run produced nothing; \`unavailable\` (no tmux) is an environment gap and explicitly not a finding. Pass \`--ready\` for anything that binds a port: without it the drive starts immediately, and an empty capture reads as "the feature does not work" when it means "the daemon had not finished starting".
6143
+
6144
+ For anything that is not one of those two wires \u2014 the project's own HTTP service, an MCP server, an OAuth endpoint \u2014 stand it up yourself and let \`drive\` own the lifecycle.
6145
+
6130
6146
  **When the claim is about GITHUB's behaviour, neither tree can settle it \u2014 only GitHub can.** A claim like "this encoding renders identically and can never ping", "GitHub strips this tag", "this markdown shape closes the fold" is about the comment pipeline's parser, sanitizer allowlist and notification path, none of which exist in this environment \u2014 a local markdown library is a model of GitHub, and judging a sanitizer claim against a model of the authority is exactly the parser-divergence failure under review. Measured live: an \`@\` \u2192 \`&#64;\` defusal read as sound in every local trace, and GitHub's real renderer registered the mention and fired the notification. So:
6131
6147
 
6132
6148
  - **If the environment variable \`QWEN_REVIEW_SCRATCH_REPO\` is set** (an \`owner/repo\` the user designated for disposable test posts), you may adjudicate on the real renderer: post the payload as an issue comment there \u2014 \`gh api repos/$QWEN_REVIEW_SCRATCH_REPO/issues/<n>/comments -f body=@<file>\` against an issue you created there for this purpose \u2014 read it back with \`-H "Accept: application/vnd.github.html+json"\`, and rule on the returned HTML (and, for mention claims, the timeline events). The observation is the verdict; quote it. This is the ONLY write destination other than \`submit\`'s that any part of this review may touch, it is user-designated, and nothing about the PR under review, its code, or its authors may appear in what you post there \u2014 post the minimal payload shape, not the report.
@@ -6142,6 +6158,13 @@ Return, for each finding, one verdict:
6142
6158
 
6143
6159
  **For anything non-Critical, when uncertain, downgrade to low confidence rather than rejecting.** Reserve outright rejection for a finding that clearly does not match the code (it describes behaviour the code does not have) or matches an Exclusion Criterion. Low confidence is for "likely real, needs human judgement", not for "I have no idea" \u2014 a vague suspicion with no concrete evidence in the code can still be rejected.
6144
6160
 
6161
+ **Your job is to falsify, not to fail-to-verify \u2014 and the two feel identical from inside a trace that went nowhere.** Rejection is a claim that you hold **direct counter-evidence**: the quoted contradicting code, the observed probe output, the matched Exclusion Criterion (the one exception stays as stated above: a finding that names nothing checkable at all is rejectable for that reason). Two states reliably masquerade as grounds to reject, and neither is (the split is measured practice from a reflection filter that ran over production reviews at millions-of-comments scale, whose single operating rule was this asymmetry):
6162
+
6163
+ - **"I could not verify it."** A trace that fails to confirm a claim is information about your trace, not about the claim \u2014 the trigger may need state you did not construct, a platform you are not on, a timing window your read-through cannot open. If the trace neither confirms nor contradicts, the verdict is \`confirmed (low confidence)\` with what-would-settle-it named. "Could not reproduce the reasoning" is already listed above as insufficient to reject a Critical; it is equally insufficient for a Suggestion whose evidence is merely out of your reach.
6164
+ - **"Its evidence is somewhere I did not look."** A finding may rest on evidence the finder gathered and you have not \u2014 a caller grepped in a file the diff never touches, issue evidence fetched from GitHub, a behaviour observed in a run. The evidence being absent from *your* view is not evidence of absence: you have the same tools the finder had, so **go read the claimed source first** \u2014 the named file, the quoted issue text in the launch message, the cited output. Reject only if what you find there contradicts the claim. If the source is genuinely unreachable from this environment (a lightweight diff-only review, an external service), the floor is the low-confidence downgrade, never rejection.
6165
+
6166
+ The asymmetry cuts both ways: confirming also requires the trace, and a finding that merely *sounds* right confirms nothing. What it forbids is only the shortcut in the rejecting direction, because that direction is the irreversible one.
6167
+
6145
6168
  **Do not reject an issue-fidelity / root-cause-ownership finding merely because the code compiles, runs, or has a passing test.** A working sanitizer with a green "malformed-shape" test does not disprove an issue-grounded claim that the root cause belongs upstream. Verify such a finding against the issue evidence quoted in the message that launched you; if that evidence is absent or genuinely inconclusive, downgrade rather than reject.`
6146
6169
  },
6147
6170
  "reverse-audit": {
@@ -6245,8 +6268,8 @@ function publicRoleLabelZh(req) {
6245
6268
  return req.file ? `${base}\uFF08${req.file}\uFF09` : base;
6246
6269
  }
6247
6270
  __name(publicRoleLabelZh, "publicRoleLabelZh");
6248
- function label(rec, chunk) {
6249
- if (chunk !== null) return `chunk ${chunk}`;
6271
+ function label(rec, chunk2) {
6272
+ if (chunk2 !== null) return `chunk ${chunk2}`;
6250
6273
  const first = rec.launchPrompt.split("\n")[0]?.trim() ?? "";
6251
6274
  if (first) return first.length > 60 ? `${first.slice(0, 57)}...` : first;
6252
6275
  return rec.agentName || rec.agentId;
@@ -6260,6 +6283,11 @@ function coverageFromTranscripts(planPath, env = process.env) {
6260
6283
  const idleAgents = [];
6261
6284
  const unopenedAgents = [];
6262
6285
  const rewrittenPrompts = [];
6286
+ const driftedLaunches = [];
6287
+ const openedBriefOf = /* @__PURE__ */ __name((rec, key) => {
6288
+ const needle = JSON.stringify(briefPath(planPath, key));
6289
+ return rec.successfulCallArgs.some((a) => a.includes(needle));
6290
+ }, "openedBriefOf");
6263
6291
  const disclosures = [];
6264
6292
  const disclose = /* @__PURE__ */ __name((subject, reason, pub) => {
6265
6293
  disclosures.push({
@@ -6301,26 +6329,26 @@ function coverageFromTranscripts(planPath, env = process.env) {
6301
6329
  }
6302
6330
  return false;
6303
6331
  }, "keySatisfied");
6304
- const superseded = /* @__PURE__ */ __name((rec, chunk) => chunk !== null ? chunkSatisfied(chunk, rec) : keySatisfied(rec), "superseded");
6332
+ const superseded = /* @__PURE__ */ __name((rec, chunk2) => chunk2 !== null ? chunkSatisfied(chunk2, rec) : keySatisfied(rec), "superseded");
6305
6333
  for (const rec of records) {
6306
- const chunk = assignedChunk(rec);
6307
- const name = label(rec, chunk);
6334
+ const chunk2 = assignedChunk(rec);
6335
+ const name = label(rec, chunk2);
6308
6336
  const given = wasGivenTheDiff(rec, plan.diffPathAbsolute);
6309
- if (chunk !== null && !given) {
6310
- if (!superseded(rec, chunk)) blindAgents.push(name);
6337
+ if (chunk2 !== null && !given) {
6338
+ if (!superseded(rec, chunk2)) blindAgents.push(name);
6311
6339
  continue;
6312
6340
  }
6313
6341
  if (rec.successfulToolCalls === 0) {
6314
- if (!superseded(rec, chunk)) idleAgents.push(name);
6342
+ if (!superseded(rec, chunk2)) idleAgents.push(name);
6315
6343
  continue;
6316
6344
  }
6317
6345
  if (!given) continue;
6318
6346
  let rewrittenThisRecord = false;
6319
- if (chunk !== null) {
6320
- const b = builtOf(`chunk-${chunk}`);
6347
+ if (chunk2 !== null) {
6348
+ const b = builtOf(`chunk-${chunk2}`);
6321
6349
  if (b === void 0) {
6322
6350
  rewrittenThisRecord = true;
6323
- if (!nothingBuiltAtAll && !superseded(rec, chunk)) {
6351
+ if (!nothingBuiltAtAll && !superseded(rec, chunk2)) {
6324
6352
  rewrittenPrompts.push(
6325
6353
  disclose(
6326
6354
  name,
@@ -6332,21 +6360,29 @@ function coverageFromTranscripts(planPath, env = process.env) {
6332
6360
  );
6333
6361
  }
6334
6362
  } else if (!wasDeliveredVerbatim(rec.launchPrompt, b)) {
6335
- rewrittenThisRecord = true;
6336
- if (!superseded(rec, chunk)) {
6337
- rewrittenPrompts.push(
6338
- disclose(
6339
- name,
6340
- "launched with a prompt that is not the one the CLI built",
6341
- { reasonZh: "\u542F\u52A8\u65F6\u4F7F\u7528\u7684 prompt \u4E0D\u662F CLI \u6784\u5EFA\u7684\u90A3\u4E00\u4EFD" }
6342
- )
6343
- );
6363
+ if (openedBriefOf(rec, `chunk-${chunk2}`) && rec.diffToolCalls > 0) {
6364
+ if (!superseded(rec, chunk2)) {
6365
+ driftedLaunches.push(
6366
+ `${name} \u2014 launched with a near-verbatim prompt; its brief was opened and the diff was read, so the delivery stands`
6367
+ );
6368
+ }
6369
+ } else {
6370
+ rewrittenThisRecord = true;
6371
+ if (!superseded(rec, chunk2)) {
6372
+ rewrittenPrompts.push(
6373
+ disclose(
6374
+ name,
6375
+ "launched with a prompt that is not the one the CLI built",
6376
+ { reasonZh: "\u542F\u52A8\u65F6\u4F7F\u7528\u7684 prompt \u4E0D\u662F CLI \u6784\u5EFA\u7684\u90A3\u4E00\u4EFD" }
6377
+ )
6378
+ );
6379
+ }
6344
6380
  }
6345
6381
  }
6346
6382
  }
6347
6383
  const told = pointedAt(rec.launchPrompt, plan);
6348
6384
  if (told.length > 0 && rec.diffToolCalls === 0) {
6349
- if (!rewrittenThisRecord && !superseded(rec, chunk)) {
6385
+ if (!rewrittenThisRecord && !superseded(rec, chunk2)) {
6350
6386
  unopenedAgents.push(name);
6351
6387
  }
6352
6388
  continue;
@@ -6354,8 +6390,8 @@ function coverageFromTranscripts(planPath, env = process.env) {
6354
6390
  const ranges = merge([...told, ...rec.diffReads]);
6355
6391
  if (ranges.length === 0) continue;
6356
6392
  const u = UNCOVERABLE_RE.exec(rec.finalText);
6357
- if (u && chunk !== null && Number(u[1]) === chunk) {
6358
- uncoverable.add(chunk);
6393
+ if (u && chunk2 !== null && Number(u[1]) === chunk2) {
6394
+ uncoverable.add(chunk2);
6359
6395
  continue;
6360
6396
  }
6361
6397
  for (const c of plan.chunks) {
@@ -6384,16 +6420,12 @@ function coverageFromTranscripts(planPath, env = process.env) {
6384
6420
  );
6385
6421
  }
6386
6422
  const buildable = roster.filter((r) => builtOf(r.key) !== void 0);
6387
- const openedBrief = /* @__PURE__ */ __name((rec, key) => {
6388
- const needle = JSON.stringify(briefPath(planPath, key));
6389
- return rec.successfulCallArgs.some((a) => a.includes(needle));
6390
- }, "openedBrief");
6391
6423
  const candidatesOf = buildable.map((req) => {
6392
6424
  const b = builtOf(req.key);
6393
6425
  return records.filter((r) => wasDeliveredVerbatim(r.launchPrompt, b));
6394
6426
  });
6395
6427
  const openedOfReq = buildable.map(
6396
- (req, i) => candidatesOf[i].filter((r) => openedBrief(r, req.key))
6428
+ (req, i) => candidatesOf[i].filter((r) => openedBriefOf(r, req.key))
6397
6429
  );
6398
6430
  const matchedRec = /* @__PURE__ */ new Map();
6399
6431
  const augment = /* @__PURE__ */ __name((i, edges, seen) => {
@@ -6418,6 +6450,7 @@ function coverageFromTranscripts(planPath, env = process.env) {
6418
6450
  }
6419
6451
  const assignment = /* @__PURE__ */ new Map();
6420
6452
  for (const [rec, i] of matchedRec) assignment.set(i, rec);
6453
+ const rescued = /* @__PURE__ */ new Set();
6421
6454
  let buildableIdx = -1;
6422
6455
  for (const req of roster) {
6423
6456
  const b = builtOf(req.key);
@@ -6442,6 +6475,21 @@ function coverageFromTranscripts(planPath, env = process.env) {
6442
6475
  const pick2 = assignment.get(buildableIdx);
6443
6476
  if (pick2 === void 0) {
6444
6477
  const anyMatch = candidatesOf[buildableIdx].length > 0;
6478
+ if (!anyMatch) {
6479
+ const needsDiff = req.role === "chunk" || BRIEFS[req.role].readsDiff;
6480
+ const rescue = records.find(
6481
+ (r) => !matchedRec.has(r) && !rescued.has(r) && r.successfulToolCalls > 0 && (!needsDiff || r.diffToolCalls > 0) && openedBriefOf(r, req.key)
6482
+ );
6483
+ if (rescue !== void 0) {
6484
+ rescued.add(rescue);
6485
+ if (req.role !== "chunk") {
6486
+ driftedLaunches.push(
6487
+ `${roleLabel(req)} \u2014 no launch matched its block verbatim, but an agent opened this role's brief and did the work, so the delivery stands`
6488
+ );
6489
+ }
6490
+ continue;
6491
+ }
6492
+ }
6445
6493
  missingRoles.push(
6446
6494
  disclose(
6447
6495
  roleLabel(req),
@@ -6489,6 +6537,7 @@ function coverageFromTranscripts(planPath, env = process.env) {
6489
6537
  idleAgents,
6490
6538
  unopenedAgents,
6491
6539
  rewrittenPrompts,
6540
+ driftedLaunches,
6492
6541
  missingRoles,
6493
6542
  missingRoleSelectors,
6494
6543
  disclosures,
@@ -6751,6 +6800,10 @@ function gh(...args) {
6751
6800
  return execGhWithRetry(args, {});
6752
6801
  }
6753
6802
  __name(gh, "gh");
6803
+ function ghWithInputRetried(input, ...args) {
6804
+ return execGhWithRetry(args, { input });
6805
+ }
6806
+ __name(ghWithInputRetried, "ghWithInputRetried");
6754
6807
  function ghWithInput(input, ...args) {
6755
6808
  return execFileSync2("gh", args, {
6756
6809
  encoding: "utf8",
@@ -7190,25 +7243,25 @@ function composeReviewBody(input, cliVersion) {
7190
7243
  }
7191
7244
  if (contextUnavailable) cappedBy.push("context-unavailable");
7192
7245
  if (criticalsUnverified) cappedBy.push("criticals-unverified");
7193
- let event = baseEvent;
7194
- if (event === "APPROVE" && cappedBy.length > 0) event = "COMMENT";
7246
+ let event2 = baseEvent;
7247
+ if (event2 === "APPROVE" && cappedBy.length > 0) event2 = "COMMENT";
7195
7248
  const deterministicBodyCriticals = bodyCriticals.length - nonDeterministicBodyCriticals;
7196
- if (event === "REQUEST_CHANGES" && criticalsUnverified && deterministicBodyCriticals === 0) {
7197
- event = "COMMENT";
7249
+ if (event2 === "REQUEST_CHANGES" && criticalsUnverified && deterministicBodyCriticals === 0) {
7250
+ event2 = "COMMENT";
7198
7251
  }
7199
7252
  let downgraded = false;
7200
7253
  let downgradedFrom = null;
7201
- if (event === "APPROVE" && downgradeApprove) {
7202
- event = "COMMENT";
7254
+ if (event2 === "APPROVE" && downgradeApprove) {
7255
+ event2 = "COMMENT";
7203
7256
  downgraded = true;
7204
7257
  downgradedFrom = "Approve";
7205
- } else if ((event === "REQUEST_CHANGES" || baseEvent === "REQUEST_CHANGES" && criticalsUnverified) && downgradeRequestChanges) {
7206
- event = "COMMENT";
7258
+ } else if ((event2 === "REQUEST_CHANGES" || baseEvent === "REQUEST_CHANGES" && criticalsUnverified) && downgradeRequestChanges) {
7259
+ event2 = "COMMENT";
7207
7260
  downgraded = true;
7208
7261
  downgradedFrom = "Request changes";
7209
7262
  }
7210
7263
  let lowSignal = null;
7211
- if (event === "APPROVE" && input.planPath) {
7264
+ if (event2 === "APPROVE" && input.planPath) {
7212
7265
  try {
7213
7266
  const plan = JSON.parse(
7214
7267
  readFileSync8(input.planPath, "utf8")
@@ -7222,10 +7275,10 @@ function composeReviewBody(input, cliVersion) {
7222
7275
  }
7223
7276
  const footer = `_\u2014 ${modelId} via Qwen Code /review (v${cliVersion})_`;
7224
7277
  const bilingual = bilingualFromPlan(input.planPath, input.prBodyFetcher);
7225
- const render = /* @__PURE__ */ __name((parts, sep4) => {
7226
- const en = parts.map((p) => p.en).join(sep4);
7278
+ const render = /* @__PURE__ */ __name((parts, sep5) => {
7279
+ const en = parts.map((p) => p.en).join(sep5);
7227
7280
  if (en === "") return "";
7228
- const zh = parts.map((p) => p.zh).join(sep4);
7281
+ const zh = parts.map((p) => p.zh).join(sep5);
7229
7282
  const text = bilingual && zh !== en ? `${en}
7230
7283
 
7231
7284
  <details>
@@ -7359,7 +7412,7 @@ ${footer}`;
7359
7412
  zh: `Test Plan\uFF08\u975E\u963B\u65AD\uFF09\uFF1A${testPlanNotes.join("; ")}\u3002`
7360
7413
  }
7361
7414
  ] : [];
7362
- if (event === "REQUEST_CHANGES") {
7415
+ if (event2 === "REQUEST_CHANGES") {
7363
7416
  const parts = [
7364
7417
  ...contextUnavailable ? [contextUnavailableClause] : [],
7365
7418
  ...cannotTellBlock,
@@ -7369,7 +7422,7 @@ ${footer}`;
7369
7422
  ...bodyCriticalBlock
7370
7423
  ];
7371
7424
  return {
7372
- event,
7425
+ event: event2,
7373
7426
  body: render(parts, "\n\n"),
7374
7427
  baseEvent,
7375
7428
  cappedBy,
@@ -7379,9 +7432,9 @@ ${footer}`;
7379
7432
  lowSignal
7380
7433
  };
7381
7434
  }
7382
- if (event === "APPROVE") {
7435
+ if (event2 === "APPROVE") {
7383
7436
  return {
7384
- event,
7437
+ event: event2,
7385
7438
  body: render(
7386
7439
  [
7387
7440
  { en: "No issues found. LGTM! \u2705", zh: "\u672A\u53D1\u73B0\u95EE\u9898\u3002LGTM\uFF01\u2705" },
@@ -7444,7 +7497,7 @@ ${footer}`;
7444
7497
  clauses.push(...bodyCriticalBlock);
7445
7498
  }
7446
7499
  return {
7447
- event,
7500
+ event: event2,
7448
7501
  body: render(clauses, " "),
7449
7502
  baseEvent,
7450
7503
  cappedBy,
@@ -7821,7 +7874,7 @@ __name(verdictLine, "verdictLine");
7821
7874
 
7822
7875
  // packages/cli/src/commands/review/findings.ts
7823
7876
  init_esbuild_shims();
7824
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "node:fs";
7877
+ import { existsSync as existsSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "node:fs";
7825
7878
  import { dirname as dirname5, resolve as resolve5 } from "node:path";
7826
7879
  var SEVERITIES = ["Critical", "Suggestion", "Nice to have"];
7827
7880
  var CONFIDENCES = ["high", "low"];
@@ -7845,6 +7898,18 @@ function asString(o, key) {
7845
7898
  return typeof v === "string" && v.trim() !== "" ? v : void 0;
7846
7899
  }
7847
7900
  __name(asString, "asString");
7901
+ function stringArray(o, key, index) {
7902
+ const v = o[key];
7903
+ if (v == null) return void 0;
7904
+ if (!Array.isArray(v) || v.some((x) => typeof x !== "string" || x.trim() === "")) {
7905
+ fail(
7906
+ index,
7907
+ `"${key}" must be an array of non-empty strings, got ${JSON.stringify(v)}.`
7908
+ );
7909
+ }
7910
+ return v.length > 0 ? v : void 0;
7911
+ }
7912
+ __name(stringArray, "stringArray");
7848
7913
  function oneOf(value, allowed) {
7849
7914
  return typeof value === "string" && allowed.includes(value) ? value : void 0;
7850
7915
  }
@@ -7862,8 +7927,8 @@ function parseLocations(o, index) {
7862
7927
  const lo = l;
7863
7928
  const file2 = asString(lo, "file");
7864
7929
  if (!file2) fail(index, `location ${j} is missing a non-empty "file".`);
7865
- if (lo["line"] !== void 0 && typeof lo["line"] !== "number") {
7866
- fail(index, `location ${j} has a non-numeric "line".`);
7930
+ if (lo["line"] !== void 0 && (typeof lo["line"] !== "number" || !Number.isSafeInteger(lo["line"]) || lo["line"] <= 0)) {
7931
+ fail(index, `location ${j} has an invalid "line".`);
7867
7932
  }
7868
7933
  return {
7869
7934
  file: file2,
@@ -7879,8 +7944,8 @@ function parseLocations(o, index) {
7879
7944
  'needs a non-empty "file" (or a "locations" array for a pattern aggregate).'
7880
7945
  );
7881
7946
  }
7882
- if (o["line"] !== void 0 && typeof o["line"] !== "number") {
7883
- fail(index, 'has a non-numeric "line".');
7947
+ if (o["line"] !== void 0 && (typeof o["line"] !== "number" || !Number.isSafeInteger(o["line"]) || o["line"] <= 0)) {
7948
+ fail(index, 'has an invalid "line".');
7884
7949
  }
7885
7950
  return [
7886
7951
  {
@@ -7939,7 +8004,11 @@ function validateFindings(raw) {
7939
8004
  `has outcome ${JSON.stringify(o["outcome"])}; expected one of ${OUTCOMES.map((s) => JSON.stringify(s)).join(", ")}.`
7940
8005
  );
7941
8006
  }
8007
+ const assetFiles = stringArray(o, "assetFiles", i) ?? stringArray(o, "asset_files", i);
8008
+ const assets = stringArray(o, "assets", i);
7942
8009
  const outcomeNote = asString(o, "outcomeNote") ?? asString(o, "outcome_note");
8010
+ const heldRaw = o.heldByMeasurement;
8011
+ const heldFile = heldRaw && typeof heldRaw === "object" ? asString(heldRaw, "file") : void 0;
7943
8012
  const shortSummary = asString(o, "shortSummary") ?? asString(o, "short_summary");
7944
8013
  return {
7945
8014
  id,
@@ -7954,6 +8023,9 @@ function validateFindings(raw) {
7954
8023
  } : {},
7955
8024
  ...asString(o, "category") ? { category: asString(o, "category") } : {},
7956
8025
  locations: parseLocations(o, i),
8026
+ ...assetFiles ? { assetFiles } : {},
8027
+ ...assets ? { assets } : {},
8028
+ ...heldFile ? { heldByMeasurement: { file: heldFile } } : {},
7957
8029
  ...outcome ? { outcome } : {},
7958
8030
  ...outcome && outcomeNote ? { outcomeNote } : {}
7959
8031
  };
@@ -7970,6 +8042,89 @@ function validateFindings(raw) {
7970
8042
  return findings;
7971
8043
  }
7972
8044
  __name(validateFindings, "validateFindings");
8045
+ function namesPath(text, probe) {
8046
+ const isNameChar = /* @__PURE__ */ __name((c) => c !== void 0 && /[A-Za-z0-9._-]/.test(c), "isNameChar");
8047
+ const isBoundary = /* @__PURE__ */ __name((c) => c === void 0 || !isNameChar(c) && c !== "/", "isBoundary");
8048
+ let from = 0;
8049
+ for (; ; ) {
8050
+ const at = text.indexOf(probe, from);
8051
+ if (at < 0) return false;
8052
+ const after = text[at + probe.length];
8053
+ const extendsName = after !== void 0 && (/[A-Za-z0-9_-]/.test(after) || after === "." && /[A-Za-z0-9]/.test(text[at + probe.length + 1] ?? ""));
8054
+ if (isBoundary(at === 0 ? void 0 : text[at - 1]) && !extendsName) {
8055
+ return true;
8056
+ }
8057
+ from = at + 1;
8058
+ }
8059
+ }
8060
+ __name(namesPath, "namesPath");
8061
+ function holdCriticalsFailingOnBase(findings, sharedFailingFiles) {
8062
+ const held = [];
8063
+ const readjudicated = [];
8064
+ const out = findings.map((f) => {
8065
+ if (f.severity !== "Critical") return f;
8066
+ const haystack = [f.summary, f.failureScenario].join("\n");
8067
+ const hit = sharedFailingFiles.find((p) => p && namesPath(haystack, p));
8068
+ if (!hit) return f;
8069
+ if (f.heldByMeasurement?.file === hit) {
8070
+ readjudicated.push({ id: f.id, file: hit });
8071
+ return f;
8072
+ }
8073
+ held.push({ id: f.id, file: hit });
8074
+ return {
8075
+ ...f,
8076
+ severity: "Suggestion",
8077
+ heldByMeasurement: { file: hit },
8078
+ failureScenario: `${f.failureScenario}
8079
+
8080
+ Held back from Critical by measurement: \`test-delta\` reran the failing test command on the merge base and ${hit} failed there too, so this is not a passing test the PR turns red. If the PR makes an already-red test fail for a NEW reason, say which test, quote both sides, and file it at Critical again: a finding that already carries this measurement and is raised anyway is left where you put it.`
8081
+ };
8082
+ });
8083
+ return { findings: out, held, readjudicated };
8084
+ }
8085
+ __name(holdCriticalsFailingOnBase, "holdCriticalsFailingOnBase");
8086
+ var WORKSPACE_IN_COMMAND_RE = /--workspace="([^"]+)"/;
8087
+ function repoRelative(path3, workspace) {
8088
+ const at = path3.indexOf("::");
8089
+ const bare = at < 0 ? path3 : path3.slice(at + 2);
8090
+ if (at >= 0 && !workspace) return void 0;
8091
+ if (!workspace || bare.startsWith(`${workspace}/`)) return bare;
8092
+ return `${workspace}/${bare}`;
8093
+ }
8094
+ __name(repoRelative, "repoRelative");
8095
+ function sharedFailingFilesOf(raw) {
8096
+ if (!raw || typeof raw !== "object")
8097
+ return { shared: [], unidentifiable: [] };
8098
+ const shared = /* @__PURE__ */ new Set();
8099
+ const netNew = /* @__PURE__ */ new Set();
8100
+ const unidentifiable = /* @__PURE__ */ new Set();
8101
+ const take = /* @__PURE__ */ __name((into, v, workspace) => {
8102
+ if (!Array.isArray(v)) return;
8103
+ for (const e of v) {
8104
+ if (typeof e !== "string" || !e) continue;
8105
+ const qualified = repoRelative(e, workspace);
8106
+ if (qualified === void 0) {
8107
+ if (into === shared) unidentifiable.add(e);
8108
+ } else into.add(qualified);
8109
+ }
8110
+ }, "take");
8111
+ const entries = raw.entries;
8112
+ const rows = Array.isArray(entries) ? entries : [];
8113
+ if (rows.length > 0) {
8114
+ for (const e of rows) {
8115
+ if (!e || typeof e !== "object") continue;
8116
+ const command = e.command;
8117
+ const workspace = typeof command === "string" ? WORKSPACE_IN_COMMAND_RE.exec(command)?.[1] ?? void 0 : void 0;
8118
+ take(shared, e.shared, workspace);
8119
+ take(netNew, e.netNew, workspace);
8120
+ }
8121
+ }
8122
+ return {
8123
+ shared: [...shared].filter((f) => !netNew.has(f)),
8124
+ unidentifiable: [...unidentifiable]
8125
+ };
8126
+ }
8127
+ __name(sharedFailingFilesOf, "sharedFailingFilesOf");
7973
8128
  function sortFindings(findings) {
7974
8129
  return [...findings].sort((a, b) => {
7975
8130
  const bySeverity = SEVERITIES.indexOf(a.severity) - SEVERITIES.indexOf(b.severity);
@@ -8072,6 +8227,7 @@ function buildReport(findings) {
8072
8227
  total: sorted.length,
8073
8228
  bySeverity,
8074
8229
  byConfidence,
8230
+ held: sorted.filter((f) => f.heldByMeasurement !== void 0).length,
8075
8231
  ...byOutcome ? { byOutcome } : {}
8076
8232
  },
8077
8233
  outcomesRecorded
@@ -8121,12 +8277,15 @@ var findingsCommand = {
8121
8277
  }).option("outcomes", {
8122
8278
  type: "string",
8123
8279
  describe: "JSON array of {id, outcome, note?} recording what --fix did to each finding. Must cover every finding."
8280
+ }).option("test-delta", {
8281
+ type: "string",
8282
+ describe: "The test-delta artifact. A Critical naming a test file that also failed on the merge base is held back to Suggestion, carrying the measurement that demoted it."
8124
8283
  }).option("print", {
8125
8284
  type: "boolean",
8126
8285
  describe: "Also print one line per finding to stdout"
8127
8286
  }), "builder"),
8128
8287
  handler: /* @__PURE__ */ __name((argv) => {
8129
- const { input, out, outcomes, print } = argv;
8288
+ const { input, out, outcomes, print, testDelta } = argv;
8130
8289
  let findings = validateFindings(readJson(input, "findings"));
8131
8290
  if (outcomes !== void 0) {
8132
8291
  findings = applyOutcomes(
@@ -8134,6 +8293,30 @@ var findingsCommand = {
8134
8293
  validateOutcomes(readJson(outcomes, "outcomes"))
8135
8294
  );
8136
8295
  }
8296
+ let held = [];
8297
+ let readjudicated = [];
8298
+ if (testDelta !== void 0) {
8299
+ let measurement;
8300
+ if (existsSync3(resolve5(testDelta))) {
8301
+ try {
8302
+ measurement = readJson(testDelta, "test-delta");
8303
+ } catch (err) {
8304
+ writeStderrLine(
8305
+ `findings: no holds applied \u2014 ${err instanceof Error ? err.message : String(err)}`
8306
+ );
8307
+ }
8308
+ }
8309
+ const { shared, unidentifiable } = sharedFailingFilesOf(measurement);
8310
+ for (const f of unidentifiable) {
8311
+ writeStderrLine(
8312
+ `findings: ignored the measured file ${f} \u2014 it carries a vitest project key and no workspace to resolve it against, so which project it names cannot be established`
8313
+ );
8314
+ }
8315
+ ({ findings, held, readjudicated } = holdCriticalsFailingOnBase(
8316
+ findings,
8317
+ shared
8318
+ ));
8319
+ }
8137
8320
  const report = buildReport(findings);
8138
8321
  const target = resolve5(out);
8139
8322
  mkdirSync6(dirname5(target), { recursive: true });
@@ -8143,6 +8326,16 @@ var findingsCommand = {
8143
8326
  writeStderrLine(
8144
8327
  `findings: ${report.counts.total} total \u2014 ${bySeverity["Critical"]} Critical, ${bySeverity["Suggestion"]} Suggestion, ${bySeverity["Nice to have"]} Nice to have; ${byConfidence["low"]} low-confidence. Wrote ${target}`
8145
8328
  );
8329
+ for (const h of held) {
8330
+ writeStderrLine(
8331
+ `findings: ${h.id} held back from Critical \u2014 test-delta measured ${h.file} as failing on the merge base too`
8332
+ );
8333
+ }
8334
+ for (const r of readjudicated) {
8335
+ writeStderrLine(
8336
+ `findings: ${r.id} left at Critical \u2014 it already carries the ${r.file} measurement and was raised again anyway`
8337
+ );
8338
+ }
8146
8339
  if (report.counts.byOutcome) {
8147
8340
  const o = report.counts.byOutcome;
8148
8341
  writeStderrLine(
@@ -8164,7 +8357,7 @@ import { dirname as dirname6, resolve as resolve6 } from "node:path";
8164
8357
  // packages/cli/src/commands/review/lib/git.ts
8165
8358
  init_esbuild_shims();
8166
8359
  import { execFileSync as execFileSync3 } from "node:child_process";
8167
- import { existsSync as existsSync3, rmSync as rmSync3 } from "node:fs";
8360
+ import { existsSync as existsSync4, rmSync as rmSync3 } from "node:fs";
8168
8361
  var GIT_TIMEOUT_MS2 = 12e4;
8169
8362
  function gitOpts() {
8170
8363
  return {
@@ -8210,7 +8403,7 @@ function worktreeReleaseResult(existed, stillThere, removeError) {
8210
8403
  }
8211
8404
  __name(worktreeReleaseResult, "worktreeReleaseResult");
8212
8405
  function releaseWorktree(worktreePath2) {
8213
- const existed = existsSync3(worktreePath2);
8406
+ const existed = existsSync4(worktreePath2);
8214
8407
  let removeError;
8215
8408
  if (existed) {
8216
8409
  gitOpt("worktree", "remove", worktreePath2, "--force");
@@ -8221,7 +8414,7 @@ function releaseWorktree(worktreePath2) {
8221
8414
  }
8222
8415
  }
8223
8416
  gitOpt("worktree", "prune");
8224
- return worktreeReleaseResult(existed, existsSync3(worktreePath2), removeError);
8417
+ return worktreeReleaseResult(existed, existsSync4(worktreePath2), removeError);
8225
8418
  }
8226
8419
  __name(releaseWorktree, "releaseWorktree");
8227
8420
  function gitRaw(...args) {
@@ -10087,8 +10280,8 @@ var loadRulesCommand = {
10087
10280
  // packages/cli/src/commands/review/presubmit.ts
10088
10281
  init_esbuild_shims();
10089
10282
  import { writeFileSync as writeFileSync13, readFileSync as readFileSync13 } from "node:fs";
10090
- function verdictStamp(run3) {
10091
- return run3.completed_at ?? run3.started_at ?? "";
10283
+ function verdictStamp(run4) {
10284
+ return run4.completed_at ?? run4.started_at ?? "";
10092
10285
  }
10093
10286
  __name(verdictStamp, "verdictStamp");
10094
10287
  var FAIL_CONCLUSIONS = /* @__PURE__ */ new Set([
@@ -10110,11 +10303,11 @@ var PENDING_STATES = /* @__PURE__ */ new Set([
10110
10303
  "requested"
10111
10304
  ]);
10112
10305
  var NOT_RUN_CONCLUSIONS = /* @__PURE__ */ new Set(["skipped", "neutral", "stale"]);
10113
- function isCurrentActionsRunCheck(run3) {
10306
+ function isCurrentActionsRunCheck(run4) {
10114
10307
  const runId = process.env["GITHUB_RUN_ID"];
10115
10308
  if (!runId) return false;
10116
10309
  const runUrlMarker = `/actions/runs/${runId}/`;
10117
- return [run3.details_url, run3.html_url].some(
10310
+ return [run4.details_url, run4.html_url].some(
10118
10311
  (url) => typeof url === "string" && url.includes(runUrlMarker)
10119
10312
  );
10120
10313
  }
@@ -10179,33 +10372,33 @@ function classifyCi(checkRuns, statuses) {
10179
10372
  const failedCheckNames = [];
10180
10373
  let hasPending = false;
10181
10374
  const relevantCheckRuns = checkRuns.filter(
10182
- (run3) => !isCurrentActionsRunCheck(run3)
10375
+ (run4) => !isCurrentActionsRunCheck(run4)
10183
10376
  );
10184
10377
  const executedNames = /* @__PURE__ */ new Set();
10185
10378
  const notRunNames = /* @__PURE__ */ new Set();
10186
- for (const run3 of relevantCheckRuns) {
10187
- if (run3.status !== "completed") continue;
10188
- if (!run3.conclusion || NOT_RUN_CONCLUSIONS.has(run3.conclusion)) {
10189
- notRunNames.add(run3.name);
10379
+ for (const run4 of relevantCheckRuns) {
10380
+ if (run4.status !== "completed") continue;
10381
+ if (!run4.conclusion || NOT_RUN_CONCLUSIONS.has(run4.conclusion)) {
10382
+ notRunNames.add(run4.name);
10190
10383
  } else {
10191
- executedNames.add(run3.name);
10384
+ executedNames.add(run4.name);
10192
10385
  }
10193
10386
  }
10194
10387
  const skippedCheckNames = [...notRunNames].filter((n) => !executedNames.has(n)).sort();
10195
10388
  const latestVerdicts = /* @__PURE__ */ new Map();
10196
- for (const run3 of relevantCheckRuns) {
10197
- if (run3.status !== "completed") {
10198
- if (PENDING_STATES.has(run3.status)) hasPending = true;
10389
+ for (const run4 of relevantCheckRuns) {
10390
+ if (run4.status !== "completed") {
10391
+ if (PENDING_STATES.has(run4.status)) hasPending = true;
10199
10392
  continue;
10200
10393
  }
10201
- if (!run3.conclusion || NOT_RUN_CONCLUSIONS.has(run3.conclusion)) continue;
10202
- const prev = latestVerdicts.get(run3.name);
10203
- if (!prev || verdictStamp(run3) > verdictStamp(prev)) {
10204
- latestVerdicts.set(run3.name, run3);
10394
+ if (!run4.conclusion || NOT_RUN_CONCLUSIONS.has(run4.conclusion)) continue;
10395
+ const prev = latestVerdicts.get(run4.name);
10396
+ if (!prev || verdictStamp(run4) > verdictStamp(prev)) {
10397
+ latestVerdicts.set(run4.name, run4);
10205
10398
  }
10206
10399
  }
10207
- for (const [name, run3] of latestVerdicts) {
10208
- if (FAIL_CONCLUSIONS.has(run3.conclusion)) {
10400
+ for (const [name, run4] of latestVerdicts) {
10401
+ if (FAIL_CONCLUSIONS.has(run4.conclusion)) {
10209
10402
  failedCheckNames.push(name);
10210
10403
  }
10211
10404
  }
@@ -10461,136 +10654,706 @@ var presubmitCommand = {
10461
10654
  }, "handler")
10462
10655
  };
10463
10656
 
10464
- // packages/cli/src/commands/review/resolve-anchors.ts
10657
+ // packages/cli/src/commands/review/publish-assets.ts
10465
10658
  init_esbuild_shims();
10466
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync14, mkdirSync as mkdirSync13 } from "node:fs";
10467
- import { dirname as dirname11, resolve as resolve10 } from "node:path";
10659
+ import { createHash as createHash2 } from "node:crypto";
10660
+ import { readFileSync as readFileSync15, statSync as statSync5, writeFileSync as writeFileSync14, mkdirSync as mkdirSync14 } from "node:fs";
10661
+ import { basename as basename5, dirname as dirname11, resolve as resolve10 } from "node:path";
10468
10662
 
10469
- // packages/cli/src/commands/review/lib/anchors.ts
10663
+ // packages/cli/src/commands/review/lib/authorization.ts
10470
10664
  init_esbuild_shims();
10471
- function collectNewSideLines(diffText, file) {
10472
- const lines2 = diffText.split("\n");
10473
- const out = [];
10474
- for (const hunk of file.hunks) {
10475
- if (hunk.newCount === 0) continue;
10476
- let newCursor = hunk.newStart;
10477
- for (let n = hunk.diffStart + 1; n <= hunk.diffEnd; n++) {
10478
- const raw = lines2[n - 1];
10479
- if (raw === void 0) break;
10480
- if (raw.startsWith("+")) {
10481
- out.push({ newLine: newCursor, text: raw.slice(1), added: true });
10482
- newCursor++;
10483
- } else if (raw.startsWith("-")) {
10484
- } else if (raw.startsWith(" ") || raw === "") {
10485
- out.push({
10486
- newLine: newCursor,
10487
- text: raw.startsWith(" ") ? raw.slice(1) : "",
10488
- added: false
10489
- });
10490
- newCursor++;
10491
- }
10492
- }
10493
- }
10494
- return out;
10665
+ import { readFileSync as readFileSync14 } from "node:fs";
10666
+
10667
+ // packages/cli/src/services/skill-args-file.ts
10668
+ init_esbuild_shims();
10669
+ import {
10670
+ mkdirSync as mkdirSync13,
10671
+ openSync as openSync2,
10672
+ writeSync,
10673
+ closeSync as closeSync2,
10674
+ rmSync as rmSync4,
10675
+ lstatSync as lstatSync3,
10676
+ existsSync as existsSync5,
10677
+ constants
10678
+ } from "node:fs";
10679
+ import { join as join11 } from "node:path";
10680
+ var debugLogger3 = createDebugLogger("SKILL_ARGS_FILE");
10681
+ var SKILL_ARGS_DIR = join11(".qwen", "tmp");
10682
+ function safe(s) {
10683
+ return s.replace(/[^A-Za-z0-9._-]/g, "_");
10495
10684
  }
10496
- __name(collectNewSideLines, "collectNewSideLines");
10497
- function normalizeExact(s) {
10498
- return s.replace(/\s+$/, "");
10685
+ __name(safe, "safe");
10686
+ function currentSessionId() {
10687
+ return sessionIdContext.getStore()?.trim() || process.env["QWEN_CODE_SESSION_ID"]?.trim() || "";
10499
10688
  }
10500
- __name(normalizeExact, "normalizeExact");
10501
- function normalizeLoose(s) {
10502
- return s.trim();
10689
+ __name(currentSessionId, "currentSessionId");
10690
+ function skillArgsDir(sessionId = currentSessionId()) {
10691
+ return sessionId ? join11(SKILL_ARGS_DIR, `s-${safe(sessionId)}`) : SKILL_ARGS_DIR;
10503
10692
  }
10504
- __name(normalizeLoose, "normalizeLoose");
10505
- function anchorVariants(anchor) {
10506
- const lines2 = anchor.replace(/\r\n/g, "\n").split("\n");
10507
- while (lines2.length > 0 && lines2[0].trim() === "") lines2.shift();
10508
- while (lines2.length > 0 && lines2[lines2.length - 1].trim() === "") lines2.pop();
10509
- if (lines2.length === 0) return [];
10510
- const variants = [lines2];
10511
- const meaningful = lines2.filter((l) => l.trim() !== "");
10512
- if (meaningful.length > 0 && meaningful.every((l) => /^\+/.test(l))) {
10513
- variants.push(lines2.map((l) => l.startsWith("+") ? l.slice(1) : l));
10514
- }
10515
- const NO_NEWLINE = /^\$/;
10516
- const marked = lines2.filter((l) => l !== "" && !NO_NEWLINE.test(l));
10517
- if (marked.length > 0 && marked.every((l) => /^[+\- ]/.test(l)) && marked.some((l) => l.startsWith("+"))) {
10518
- variants.push(
10519
- lines2.filter((l) => !l.startsWith("-") && !NO_NEWLINE.test(l)).map((l) => l.slice(1))
10520
- );
10521
- }
10522
- return variants;
10693
+ __name(skillArgsDir, "skillArgsDir");
10694
+ function skillArgsPath(skillName, sessionId = currentSessionId()) {
10695
+ return join11(
10696
+ skillArgsDir(sessionId),
10697
+ `qwen-skill-args-${safe(skillName)}.txt`
10698
+ );
10523
10699
  }
10524
- __name(anchorVariants, "anchorVariants");
10525
- function matchRuns(hay, needle, norm) {
10526
- const starts = [];
10527
- if (needle.length === 0 || hay.length < needle.length) return starts;
10528
- const normNeedle = needle.map(norm);
10529
- for (let i = 0; i + normNeedle.length <= hay.length; i++) {
10530
- let ok = true;
10531
- for (let j = 0; j < normNeedle.length; j++) {
10532
- const cell = hay[i + j];
10533
- if (norm(cell.text) !== normNeedle[j]) {
10534
- ok = false;
10535
- break;
10536
- }
10537
- if (j > 0 && cell.newLine !== hay[i + j - 1].newLine + 1) {
10538
- ok = false;
10539
- break;
10700
+ __name(skillArgsPath, "skillArgsPath");
10701
+ function writeSkillArgs(skillName, args) {
10702
+ const path3 = skillArgsPath(skillName);
10703
+ try {
10704
+ const dir = skillArgsDir();
10705
+ mkdirSync13(dir, { recursive: true });
10706
+ if (lstatSync3(dir).isSymbolicLink()) {
10707
+ debugLogger3.warn(
10708
+ `Skill args directory ${dir} is a symlink; refusing to write through it.`
10709
+ );
10710
+ return null;
10711
+ }
10712
+ if (lstatSync3(path3, { throwIfNoEntry: false })?.isSymbolicLink()) {
10713
+ debugLogger3.warn(
10714
+ `Skill args file ${path3} is a symlink; refusing to write through it.`
10715
+ );
10716
+ return null;
10717
+ }
10718
+ const fd = openSync2(
10719
+ path3,
10720
+ constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW,
10721
+ 384
10722
+ );
10723
+ try {
10724
+ const buf = Buffer.from(args, "utf8");
10725
+ let off = 0;
10726
+ while (off < buf.length) {
10727
+ off += writeSync(fd, buf, off, buf.length - off);
10540
10728
  }
10729
+ } finally {
10730
+ closeSync2(fd);
10541
10731
  }
10542
- if (ok) starts.push(i);
10732
+ return path3;
10733
+ } catch (err) {
10734
+ debugLogger3.warn(
10735
+ `Could not write skill args to ${path3}: ${err.message}`
10736
+ );
10737
+ return null;
10543
10738
  }
10544
- return starts;
10545
- }
10546
- __name(matchRuns, "matchRuns");
10547
- function candidatesFor(hay, needle, norm) {
10548
- return matchRuns(hay, needle, norm).map((i) => {
10549
- const run3 = hay.slice(i, i + needle.length);
10550
- return {
10551
- startLine: run3[0].newLine,
10552
- line: run3[run3.length - 1].newLine,
10553
- added: run3.some((l) => l.added)
10554
- };
10555
- });
10556
10739
  }
10557
- __name(candidatesFor, "candidatesFor");
10558
- function pick(cands, claimedLine) {
10559
- if (cands.length === 1) return cands[0];
10560
- if (claimedLine !== void 0) {
10561
- const dist = /* @__PURE__ */ __name((c) => Math.abs(c.startLine - claimedLine), "dist");
10562
- let best = Infinity;
10563
- for (const c of cands) best = Math.min(best, dist(c));
10564
- const nearest = cands.filter((c) => dist(c) === best);
10565
- return nearest.length === 1 ? nearest[0] : null;
10740
+ __name(writeSkillArgs, "writeSkillArgs");
10741
+ function clearSkillArgs(skillName) {
10742
+ const path3 = skillArgsPath(skillName);
10743
+ try {
10744
+ rmSync4(path3, { force: true });
10745
+ } catch (err) {
10746
+ debugLogger3.warn(
10747
+ `Could not clear skill args for ${skillName}: ${err.message}`
10748
+ );
10749
+ return false;
10566
10750
  }
10567
- const added = cands.filter((c) => c.added);
10568
- return added.length === 1 ? added[0] : null;
10569
- }
10570
- __name(pick, "pick");
10571
- function resolveAnchor(newSideLines, anchor, claimedLine) {
10572
- const variants = anchorVariants(anchor);
10573
- if (variants.length === 0) {
10574
- return { status: "unmatched", reason: "anchor is empty" };
10751
+ if (existsSync5(path3)) {
10752
+ debugLogger3.warn(`Skill args for ${skillName} survived removal at ${path3}`);
10753
+ return false;
10575
10754
  }
10576
- for (const [vi, needle] of variants.entries()) {
10577
- const norms = vi === 0 ? [
10578
- [true, normalizeExact],
10579
- [false, normalizeLoose]
10580
- ] : [[true, normalizeExact]];
10581
- for (const [exact, norm] of norms) {
10582
- const cands = candidatesFor(newSideLines, needle, norm);
10583
- if (cands.length === 0) continue;
10584
- if (!exact && cands.length > 1) {
10585
- return {
10586
- status: "unmatched",
10587
- reason: "the snippet matched in more than one place only after its indentation was normalised \u2014 and in an indentation-significant language the nesting level IS the semantics, so choosing between them would be choosing which block the finding is about. Quote it verbatim."
10588
- };
10589
- }
10590
- const best = pick(cands, claimedLine);
10591
- if (!best) {
10592
- return {
10593
- status: "unmatched",
10755
+ return true;
10756
+ }
10757
+ __name(clearSkillArgs, "clearSkillArgs");
10758
+ function staleArgsWarning() {
10759
+ return `
10760
+
10761
+ <skill-args-stale>A previous invocation's argument record could not be removed, so it is still on disk and still names whatever it named. This invocation supplied NO arguments. Do not treat that stale record as this run's authorisation: this run has none, and must not post.</skill-args-stale>
10762
+ `;
10763
+ }
10764
+ __name(staleArgsWarning, "staleArgsWarning");
10765
+ function skillArgsNote(path3, args) {
10766
+ return `
10767
+
10768
+ Your invocation arguments have been written verbatim to a session-private file. Its exact path is below \u2014 use it wherever these instructions say to read the args file (e.g. \`< '${path3}'\`), and do not retype the arguments, which is how they get mistyped.
10769
+ <skill-args-file>${path3}</skill-args-file>
10770
+ <skill-args>${args}</skill-args>
10771
+ `;
10772
+ }
10773
+ __name(skillArgsNote, "skillArgsNote");
10774
+
10775
+ // packages/cli/src/commands/review/lib/authorization.ts
10776
+ function defaultSkillArgsPath() {
10777
+ return skillArgsPath("review");
10778
+ }
10779
+ __name(defaultSkillArgsPath, "defaultSkillArgsPath");
10780
+ function reviewWriteAuthorization(req) {
10781
+ if (req.userAuthorized) {
10782
+ return { ok: true, why: "the user asked for this review to be published" };
10783
+ }
10784
+ const sessionScoped = defaultSkillArgsPath();
10785
+ const path3 = currentSessionId() === "" && req.skillArgs ? req.skillArgs : sessionScoped;
10786
+ let raw;
10787
+ try {
10788
+ raw = readFileSync14(path3, "utf8");
10789
+ } catch {
10790
+ return {
10791
+ ok: false,
10792
+ why: `no review arguments were recorded at ${path3}, so this run cannot show that \`--comment\` was requested`
10793
+ };
10794
+ }
10795
+ const verdict = parseReviewArgs(raw);
10796
+ if (!verdict.comment.effective) {
10797
+ return {
10798
+ ok: false,
10799
+ why: `\`--comment\` was not in the review arguments (${JSON.stringify(raw.trim())})`
10800
+ };
10801
+ }
10802
+ const t2 = verdict.target;
10803
+ const authorisedPr = t2.type === "pr-number" || t2.type === "pr-url" ? t2.number : void 0;
10804
+ if (authorisedPr === void 0) {
10805
+ return {
10806
+ ok: false,
10807
+ why: `the review arguments (${JSON.stringify(raw.trim())}) do not name a pull request, so they cannot authorise posting to one`
10808
+ };
10809
+ }
10810
+ if (authorisedPr !== req.pr) {
10811
+ return {
10812
+ ok: false,
10813
+ why: `the review arguments authorise pull request #${authorisedPr}, but this submission targets #${req.pr}`
10814
+ };
10815
+ }
10816
+ if (t2.type === "pr-url") {
10817
+ if (req.repo !== void 0) {
10818
+ const authorisedRepo = `${t2.owner}/${t2.repo}`;
10819
+ if (authorisedRepo.toLowerCase() !== req.repo.toLowerCase()) {
10820
+ return {
10821
+ ok: false,
10822
+ why: `the review arguments authorise ${authorisedRepo}, but this submission targets ${req.repo}`
10823
+ };
10824
+ }
10825
+ }
10826
+ const writeHost = (req.host ?? "github.com").toLowerCase();
10827
+ if (t2.host.toLowerCase() !== writeHost) {
10828
+ return {
10829
+ ok: false,
10830
+ why: `the review arguments authorise ${t2.host}, but this submission targets ${req.host ?? "github.com"}`
10831
+ };
10832
+ }
10833
+ }
10834
+ return {
10835
+ ok: true,
10836
+ why: `\`--comment\` was in the review arguments for #${authorisedPr}`
10837
+ };
10838
+ }
10839
+ __name(reviewWriteAuthorization, "reviewWriteAuthorization");
10840
+
10841
+ // packages/cli/src/commands/review/lib/assets.ts
10842
+ init_esbuild_shims();
10843
+ var ASSET_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp"]);
10844
+ var MAX_ASSET_BYTES = 10 * 1024 * 1024;
10845
+ var MAX_TOTAL_ASSET_BYTES = 40 * 1024 * 1024;
10846
+ function assetsBranch(pr) {
10847
+ return `pr-assets/${pr}-review`;
10848
+ }
10849
+ __name(assetsBranch, "assetsBranch");
10850
+ function remoteAssetPath(pr, basename6, sha256Hex) {
10851
+ const safe2 = basename6.replace(/[^A-Za-z0-9._-]/g, "_");
10852
+ return `${pr}-review/${sha256Hex.slice(0, 12)}-${safe2}`;
10853
+ }
10854
+ __name(remoteAssetPath, "remoteAssetPath");
10855
+ function rawAssetUrl(opts) {
10856
+ const host = opts.host && opts.host.trim() !== "" ? opts.host : "github.com";
10857
+ return `https://${host}/${opts.repo}/raw/${opts.commitSha}/${opts.remotePath}`;
10858
+ }
10859
+ __name(rawAssetUrl, "rawAssetUrl");
10860
+ function parseAssetsRepo(value) {
10861
+ const v = (value ?? "").trim();
10862
+ if (v === "") {
10863
+ return {
10864
+ error: "QWEN_REVIEW_ASSETS_REPO is not set. Designate an assets repository (owner/repo you can push to \u2014 the repo under review for maintainers, a fork or scratch repo otherwise) to publish review evidence images."
10865
+ };
10866
+ }
10867
+ const parts = v.split("/");
10868
+ const segmentOk = /* @__PURE__ */ __name((p) => /^[A-Za-z0-9_.-]+$/.test(p) && p !== "." && p !== "..", "segmentOk");
10869
+ if (parts.length !== 2 || !parts.every(segmentOk)) {
10870
+ return {
10871
+ error: `QWEN_REVIEW_ASSETS_REPO must be owner/repo, got ${JSON.stringify(v)}.`
10872
+ };
10873
+ }
10874
+ return { repo: v };
10875
+ }
10876
+ __name(parseAssetsRepo, "parseAssetsRepo");
10877
+ function validateAssetFile(basename6, bytes) {
10878
+ const ext = basename6.includes(".") ? basename6.slice(basename6.lastIndexOf(".") + 1).toLowerCase() : "";
10879
+ if (!ASSET_EXTENSIONS.has(ext)) {
10880
+ return {
10881
+ ok: false,
10882
+ reason: `${basename6}: extension ${JSON.stringify(ext)} is not an allowed evidence image type (${[...ASSET_EXTENSIONS].join(", ")})`
10883
+ };
10884
+ }
10885
+ if (bytes <= 0) {
10886
+ return { ok: false, reason: `${basename6}: empty file` };
10887
+ }
10888
+ if (bytes > MAX_ASSET_BYTES) {
10889
+ return {
10890
+ ok: false,
10891
+ reason: `${basename6}: ${bytes} bytes exceeds the per-file cap of ${MAX_ASSET_BYTES} bytes`
10892
+ };
10893
+ }
10894
+ return { ok: true };
10895
+ }
10896
+ __name(validateAssetFile, "validateAssetFile");
10897
+ function validateAssetBatch(files) {
10898
+ let total = 0;
10899
+ for (const f of files) {
10900
+ const one = validateAssetFile(f.basename, f.bytes);
10901
+ if (!one.ok) return one;
10902
+ total += f.bytes;
10903
+ if (total > MAX_TOTAL_ASSET_BYTES) {
10904
+ return {
10905
+ ok: false,
10906
+ reason: `total size exceeds ${MAX_TOTAL_ASSET_BYTES} bytes`
10907
+ };
10908
+ }
10909
+ }
10910
+ return { ok: true };
10911
+ }
10912
+ __name(validateAssetBatch, "validateAssetBatch");
10913
+
10914
+ // packages/cli/src/commands/review/publish-assets.ts
10915
+ function putContent(repo, branch, remotePath, contentBase64, pr) {
10916
+ const api = `repos/${repo}/contents/${remotePath}`;
10917
+ const message = `review evidence for PR #${pr}`;
10918
+ const payload = /* @__PURE__ */ __name((sha) => JSON.stringify({
10919
+ message,
10920
+ content: contentBase64,
10921
+ branch,
10922
+ ...sha ? { sha } : {}
10923
+ }), "payload");
10924
+ try {
10925
+ ghWithInputRetried(payload(), "api", "-X", "PUT", api, "--input", "-");
10926
+ } catch (err) {
10927
+ const msg = err instanceof Error ? err.message : String(err);
10928
+ if (!/HTTP 422|"sha"|sha wasn't supplied|already exists/i.test(msg)) {
10929
+ throw err;
10930
+ }
10931
+ let existing;
10932
+ try {
10933
+ existing = gh(
10934
+ "api",
10935
+ `${api}?ref=${encodeURIComponent(branch)}`,
10936
+ "--jq",
10937
+ ".sha"
10938
+ ).trim();
10939
+ } catch {
10940
+ throw err;
10941
+ }
10942
+ if (existing === "" || existing === "null") {
10943
+ throw err;
10944
+ }
10945
+ ghWithInputRetried(
10946
+ payload(existing),
10947
+ "api",
10948
+ "-X",
10949
+ "PUT",
10950
+ api,
10951
+ "--input",
10952
+ "-"
10953
+ );
10954
+ }
10955
+ }
10956
+ __name(putContent, "putContent");
10957
+ function ensureBranch(repo, branch) {
10958
+ try {
10959
+ gh("api", `repos/${repo}/git/ref/heads/${branch}`);
10960
+ return;
10961
+ } catch (err) {
10962
+ const msg = err instanceof Error ? err.message : String(err);
10963
+ if (!/HTTP 404|Not Found/i.test(msg)) throw err;
10964
+ }
10965
+ const defaultBranch = gh("api", `repos/${repo}`, "--jq", ".default_branch");
10966
+ let baseSha;
10967
+ try {
10968
+ baseSha = gh(
10969
+ "api",
10970
+ `repos/${repo}/git/ref/heads/${defaultBranch.trim()}`,
10971
+ "--jq",
10972
+ ".object.sha"
10973
+ );
10974
+ } catch (err) {
10975
+ const msg = err instanceof Error ? err.message : String(err);
10976
+ if (/HTTP 404|Not Found/i.test(msg)) {
10977
+ throw new Error(
10978
+ `assets repository ${repo} appears to be empty (its default branch ${defaultBranch.trim()} has no commits). Push an initial commit to it once; publish-assets creates and reuses its own branch from there.`
10979
+ );
10980
+ }
10981
+ throw err;
10982
+ }
10983
+ const base = baseSha.trim();
10984
+ if (base === "" || base === "null") {
10985
+ throw new Error(
10986
+ `could not resolve the head of ${repo}@${defaultBranch.trim()} \u2014 the ref lookup returned nothing to branch from.`
10987
+ );
10988
+ }
10989
+ try {
10990
+ ghWithInputRetried(
10991
+ JSON.stringify({ ref: `refs/heads/${branch}`, sha: base }),
10992
+ "api",
10993
+ "-X",
10994
+ "POST",
10995
+ `repos/${repo}/git/refs`,
10996
+ "--input",
10997
+ "-"
10998
+ );
10999
+ } catch (err) {
11000
+ const msg = err instanceof Error ? err.message : String(err);
11001
+ if (!/already exists/i.test(msg)) throw err;
11002
+ }
11003
+ }
11004
+ __name(ensureBranch, "ensureBranch");
11005
+ function runPublishAssets(args) {
11006
+ const refuse = /* @__PURE__ */ __name((reason) => {
11007
+ writeStderrLine(`publish-assets: refused \u2014 ${reason}`);
11008
+ writeStdoutLine(JSON.stringify({ published: false }));
11009
+ process.exitCode = 3;
11010
+ }, "refuse");
11011
+ if (!Number.isInteger(args.pr) || args.pr <= 0) {
11012
+ refuse(`--pr must be a positive integer, got ${String(args.pr)}.`);
11013
+ return;
11014
+ }
11015
+ const repoResult = parseAssetsRepo(process.env["QWEN_REVIEW_ASSETS_REPO"]);
11016
+ if ("error" in repoResult) {
11017
+ refuse(repoResult.error);
11018
+ return;
11019
+ }
11020
+ const repo = repoResult.repo;
11021
+ const effectiveHost = args.host ?? (process.env["GH_HOST"]?.trim() || void 0);
11022
+ const auth = reviewWriteAuthorization({
11023
+ userAuthorized: args.userAuthorized,
11024
+ skillArgs: args.skillArgs,
11025
+ pr: args.pr,
11026
+ // Bind the REVIEWED repo when the caller names it, never the assets repo:
11027
+ // the designation itself is the consent for the destination, and binding a
11028
+ // URL-shaped authorisation against a fork-hosted assets repo refused
11029
+ // legitimately authorised runs. Without --reviewed-repo the gate binds the
11030
+ // PR number (and host) alone.
11031
+ repo: args.reviewedRepo,
11032
+ host: effectiveHost
11033
+ });
11034
+ if (!auth.ok) {
11035
+ refuse(
11036
+ `not authorised \u2014 ${auth.why}. Evidence images are published only for a run that is authorised to post the review itself; the findings and their local file paths remain in the terminal output and the saved report.`
11037
+ );
11038
+ return;
11039
+ }
11040
+ if (effectiveHost) {
11041
+ try {
11042
+ setGhHost(effectiveHost);
11043
+ } catch (err) {
11044
+ refuse(
11045
+ `${err instanceof Error ? err.message : String(err)} (from ${args.host !== void 0 ? "--host" : "the GH_HOST environment variable"})`
11046
+ );
11047
+ return;
11048
+ }
11049
+ }
11050
+ let findings;
11051
+ const fileList = [...args.files ?? []];
11052
+ if (args.findings) {
11053
+ try {
11054
+ const artifact = JSON.parse(readFileSync15(resolve10(args.findings), "utf8"));
11055
+ findings = validateFindings(
11056
+ Array.isArray(artifact) ? artifact : artifact.findings
11057
+ );
11058
+ } catch (err) {
11059
+ refuse(
11060
+ `cannot use the findings artifact at ${JSON.stringify(args.findings)}: ${err instanceof Error ? err.message : String(err)}`
11061
+ );
11062
+ return;
11063
+ }
11064
+ for (const f of findings) {
11065
+ for (const a of f.assetFiles ?? []) fileList.push(a);
11066
+ }
11067
+ }
11068
+ const unique = [...new Set(fileList)];
11069
+ if (unique.length === 0) {
11070
+ if (args.findings) {
11071
+ writeStderrLine(
11072
+ "publish-assets: no finding carries assetFiles \u2014 nothing to publish."
11073
+ );
11074
+ writeStdoutLine(JSON.stringify({ published: false, count: 0 }));
11075
+ return;
11076
+ }
11077
+ refuse("no files to publish.");
11078
+ return;
11079
+ }
11080
+ const stats = [];
11081
+ for (const file of unique) {
11082
+ const abs = resolve10(file);
11083
+ try {
11084
+ const st = statSync5(abs);
11085
+ if (!st.isFile()) throw new Error("not a regular file");
11086
+ stats.push({ file, abs, basename: basename5(abs), bytes: st.size });
11087
+ } catch (err) {
11088
+ refuse(
11089
+ `cannot read ${JSON.stringify(file)}: ${err instanceof Error ? err.message : String(err)}`
11090
+ );
11091
+ return;
11092
+ }
11093
+ }
11094
+ const batch = validateAssetBatch(stats);
11095
+ if (!batch.ok) {
11096
+ refuse(batch.reason);
11097
+ return;
11098
+ }
11099
+ const prepared = [];
11100
+ for (const st of stats) {
11101
+ let content;
11102
+ try {
11103
+ content = readFileSync15(st.abs);
11104
+ } catch (err) {
11105
+ refuse(
11106
+ `cannot read ${JSON.stringify(st.file)}: ${err instanceof Error ? err.message : String(err)}`
11107
+ );
11108
+ return;
11109
+ }
11110
+ const sha256 = createHash2("sha256").update(content).digest("hex");
11111
+ prepared.push({
11112
+ file: st.file,
11113
+ bytes: st.bytes,
11114
+ sha256,
11115
+ remotePath: remoteAssetPath(args.pr, st.basename, sha256),
11116
+ contentBase64: content.toString("base64")
11117
+ });
11118
+ }
11119
+ const branch = assetsBranch(args.pr);
11120
+ ensureBranch(repo, branch);
11121
+ for (const p of prepared) {
11122
+ putContent(repo, branch, p.remotePath, p.contentBase64, args.pr);
11123
+ }
11124
+ const headSha = gh(
11125
+ "api",
11126
+ `repos/${repo}/git/ref/heads/${branch}`,
11127
+ "--jq",
11128
+ ".object.sha"
11129
+ ).trim();
11130
+ const published = prepared.map((p) => ({
11131
+ file: p.file,
11132
+ remotePath: p.remotePath,
11133
+ url: rawAssetUrl({
11134
+ host: effectiveHost,
11135
+ repo,
11136
+ commitSha: headSha,
11137
+ remotePath: p.remotePath
11138
+ }),
11139
+ bytes: p.bytes,
11140
+ sha256: p.sha256
11141
+ }));
11142
+ const manifest = {
11143
+ repo,
11144
+ branch,
11145
+ commitSha: headSha,
11146
+ pr: args.pr,
11147
+ published
11148
+ };
11149
+ const outPath = resolve10(args.out);
11150
+ mkdirSync14(dirname11(outPath), { recursive: true });
11151
+ writeFileSync14(outPath, `${JSON.stringify(manifest, null, 2)}
11152
+ `, "utf8");
11153
+ if (findings && !args.findingsOut) {
11154
+ writeStderrLine(
11155
+ "publish-assets: --findings given without --findings-out \u2014 the URLs are in the manifest only; the artifact keeps local paths."
11156
+ );
11157
+ }
11158
+ if (findings && args.findingsOut) {
11159
+ const urlByFile = new Map(published.map((p) => [p.file, p.url]));
11160
+ const updated = findings.map((f) => {
11161
+ const urls = (f.assetFiles ?? []).map((a) => urlByFile.get(a)).filter((u) => u !== void 0);
11162
+ return urls.length > 0 ? { ...f, assets: urls } : f;
11163
+ });
11164
+ const report = buildReport(updated);
11165
+ const fOut = resolve10(args.findingsOut);
11166
+ mkdirSync14(dirname11(fOut), { recursive: true });
11167
+ writeFileSync14(fOut, `${JSON.stringify(report, null, 2)}
11168
+ `, "utf8");
11169
+ }
11170
+ writeStderrLine(
11171
+ `publish-assets: ${published.length} file(s) \u2192 ${repo}@${branch} (commit ${headSha.slice(0, 9)})`
11172
+ );
11173
+ writeStdoutLine(JSON.stringify({ published: true, count: published.length }));
11174
+ }
11175
+ __name(runPublishAssets, "runPublishAssets");
11176
+ var publishAssetsCommand = {
11177
+ command: "publish-assets",
11178
+ describe: "Publish review evidence images to the user-designated assets repository (QWEN_REVIEW_ASSETS_REPO) and emit commit-pinned URLs \u2014 gated on the same authorisation as submit",
11179
+ builder: /* @__PURE__ */ __name((yargs) => yargs.option("pr", {
11180
+ type: "number",
11181
+ demandOption: true,
11182
+ describe: "The pull request this evidence belongs to"
11183
+ }).option("reviewed-repo", {
11184
+ type: "string",
11185
+ describe: "The owner/repo the reviewed PR lives in \u2014 strengthens the authorisation binding for URL-shaped review arguments; omit to bind by PR number alone"
11186
+ }).option("files", {
11187
+ type: "string",
11188
+ array: true,
11189
+ describe: "Evidence image files to publish"
11190
+ }).option("findings", {
11191
+ type: "string",
11192
+ describe: "Findings artifact whose per-finding assetFiles should be published"
11193
+ }).option("findings-out", {
11194
+ type: "string",
11195
+ implies: "findings",
11196
+ describe: "Where to write the findings artifact with published URLs woven into each finding (requires --findings)"
11197
+ }).option("out", {
11198
+ type: "string",
11199
+ demandOption: true,
11200
+ describe: "Where to write the publish manifest"
11201
+ }).option("host", {
11202
+ type: "string",
11203
+ describe: "GitHub Enterprise host (defaults to github.com)"
11204
+ }).option("user-authorized", {
11205
+ type: "boolean",
11206
+ default: false,
11207
+ describe: "Pass ONLY when the user asked, in a message they typed this session, for this review to be published"
11208
+ }).option("skill-args", {
11209
+ type: "string",
11210
+ describe: "Test seam: path to the recorded review arguments (ignored when a session id is present)"
11211
+ }), "builder"),
11212
+ handler: /* @__PURE__ */ __name((argv) => {
11213
+ runPublishAssets({
11214
+ pr: argv["pr"],
11215
+ reviewedRepo: argv["reviewed-repo"],
11216
+ files: argv["files"],
11217
+ findings: argv["findings"],
11218
+ findingsOut: argv["findings-out"],
11219
+ out: argv["out"],
11220
+ host: argv["host"],
11221
+ userAuthorized: Boolean(argv["user-authorized"]),
11222
+ skillArgs: argv["skill-args"]
11223
+ });
11224
+ }, "handler")
11225
+ };
11226
+
11227
+ // packages/cli/src/commands/review/resolve-anchors.ts
11228
+ init_esbuild_shims();
11229
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync15, mkdirSync as mkdirSync15 } from "node:fs";
11230
+ import { dirname as dirname12, resolve as resolve11 } from "node:path";
11231
+
11232
+ // packages/cli/src/commands/review/lib/anchors.ts
11233
+ init_esbuild_shims();
11234
+ function collectNewSideLines(diffText, file) {
11235
+ const lines2 = diffText.split("\n");
11236
+ const out = [];
11237
+ for (const hunk of file.hunks) {
11238
+ if (hunk.newCount === 0) continue;
11239
+ let newCursor = hunk.newStart;
11240
+ for (let n = hunk.diffStart + 1; n <= hunk.diffEnd; n++) {
11241
+ const raw = lines2[n - 1];
11242
+ if (raw === void 0) break;
11243
+ if (raw.startsWith("+")) {
11244
+ out.push({ newLine: newCursor, text: raw.slice(1), added: true });
11245
+ newCursor++;
11246
+ } else if (raw.startsWith("-")) {
11247
+ } else if (raw.startsWith(" ") || raw === "") {
11248
+ out.push({
11249
+ newLine: newCursor,
11250
+ text: raw.startsWith(" ") ? raw.slice(1) : "",
11251
+ added: false
11252
+ });
11253
+ newCursor++;
11254
+ }
11255
+ }
11256
+ }
11257
+ return out;
11258
+ }
11259
+ __name(collectNewSideLines, "collectNewSideLines");
11260
+ function normalizeExact(s) {
11261
+ return s.replace(/\s+$/, "");
11262
+ }
11263
+ __name(normalizeExact, "normalizeExact");
11264
+ function normalizeLoose(s) {
11265
+ return s.trim();
11266
+ }
11267
+ __name(normalizeLoose, "normalizeLoose");
11268
+ function anchorVariants(anchor) {
11269
+ const lines2 = anchor.replace(/\r\n/g, "\n").split("\n");
11270
+ while (lines2.length > 0 && lines2[0].trim() === "") lines2.shift();
11271
+ while (lines2.length > 0 && lines2[lines2.length - 1].trim() === "") lines2.pop();
11272
+ if (lines2.length === 0) return [];
11273
+ const variants = [lines2];
11274
+ const meaningful = lines2.filter((l) => l.trim() !== "");
11275
+ if (meaningful.length > 0 && meaningful.every((l) => /^\+/.test(l))) {
11276
+ variants.push(lines2.map((l) => l.startsWith("+") ? l.slice(1) : l));
11277
+ }
11278
+ const NO_NEWLINE = /^\$/;
11279
+ const marked = lines2.filter((l) => l !== "" && !NO_NEWLINE.test(l));
11280
+ if (marked.length > 0 && marked.every((l) => /^[+\- ]/.test(l)) && marked.some((l) => l.startsWith("+"))) {
11281
+ variants.push(
11282
+ lines2.filter((l) => !l.startsWith("-") && !NO_NEWLINE.test(l)).map((l) => l.slice(1))
11283
+ );
11284
+ }
11285
+ return variants;
11286
+ }
11287
+ __name(anchorVariants, "anchorVariants");
11288
+ function matchRuns(hay, needle, norm) {
11289
+ const starts = [];
11290
+ if (needle.length === 0 || hay.length < needle.length) return starts;
11291
+ const normNeedle = needle.map(norm);
11292
+ for (let i = 0; i + normNeedle.length <= hay.length; i++) {
11293
+ let ok = true;
11294
+ for (let j = 0; j < normNeedle.length; j++) {
11295
+ const cell = hay[i + j];
11296
+ if (norm(cell.text) !== normNeedle[j]) {
11297
+ ok = false;
11298
+ break;
11299
+ }
11300
+ if (j > 0 && cell.newLine !== hay[i + j - 1].newLine + 1) {
11301
+ ok = false;
11302
+ break;
11303
+ }
11304
+ }
11305
+ if (ok) starts.push(i);
11306
+ }
11307
+ return starts;
11308
+ }
11309
+ __name(matchRuns, "matchRuns");
11310
+ function candidatesFor(hay, needle, norm) {
11311
+ return matchRuns(hay, needle, norm).map((i) => {
11312
+ const run4 = hay.slice(i, i + needle.length);
11313
+ return {
11314
+ startLine: run4[0].newLine,
11315
+ line: run4[run4.length - 1].newLine,
11316
+ added: run4.some((l) => l.added)
11317
+ };
11318
+ });
11319
+ }
11320
+ __name(candidatesFor, "candidatesFor");
11321
+ function pick(cands, claimedLine) {
11322
+ if (cands.length === 1) return cands[0];
11323
+ if (claimedLine !== void 0) {
11324
+ const dist = /* @__PURE__ */ __name((c) => Math.abs(c.startLine - claimedLine), "dist");
11325
+ let best = Infinity;
11326
+ for (const c of cands) best = Math.min(best, dist(c));
11327
+ const nearest = cands.filter((c) => dist(c) === best);
11328
+ return nearest.length === 1 ? nearest[0] : null;
11329
+ }
11330
+ const added = cands.filter((c) => c.added);
11331
+ return added.length === 1 ? added[0] : null;
11332
+ }
11333
+ __name(pick, "pick");
11334
+ function resolveAnchor(newSideLines, anchor, claimedLine) {
11335
+ const variants = anchorVariants(anchor);
11336
+ if (variants.length === 0) {
11337
+ return { status: "unmatched", reason: "anchor is empty" };
11338
+ }
11339
+ for (const [vi, needle] of variants.entries()) {
11340
+ const norms = vi === 0 ? [
11341
+ [true, normalizeExact],
11342
+ [false, normalizeLoose]
11343
+ ] : [[true, normalizeExact]];
11344
+ for (const [exact, norm] of norms) {
11345
+ const cands = candidatesFor(newSideLines, needle, norm);
11346
+ if (cands.length === 0) continue;
11347
+ if (!exact && cands.length > 1) {
11348
+ return {
11349
+ status: "unmatched",
11350
+ reason: "the snippet matched in more than one place only after its indentation was normalised \u2014 and in an indentation-significant language the nesting level IS the semantics, so choosing between them would be choosing which block the finding is about. Quote it verbatim."
11351
+ };
11352
+ }
11353
+ const best = pick(cands, claimedLine);
11354
+ if (!best) {
11355
+ return {
11356
+ status: "unmatched",
10594
11357
  reason: "the snippet appears in more than one place and nothing distinguishes them \u2014 quote more lines so it is unique, or give the line number you mean so the nearest match can be chosen"
10595
11358
  };
10596
11359
  }
@@ -10661,8 +11424,8 @@ function validateRequests(raw) {
10661
11424
  );
10662
11425
  }
10663
11426
  }
10664
- if (o["line"] !== void 0 && typeof o["line"] !== "number") {
10665
- throw new Error(`Finding "${o["id"]}" has a non-numeric "line".`);
11427
+ if (o["line"] !== void 0 && (typeof o["line"] !== "number" || !Number.isSafeInteger(o["line"]) || o["line"] <= 0)) {
11428
+ throw new Error(`Finding "${o["id"]}" has an invalid "line".`);
10666
11429
  }
10667
11430
  return {
10668
11431
  id: o["id"],
@@ -10688,7 +11451,7 @@ __name(validateRequests, "validateRequests");
10688
11451
  function runResolveAnchors(args) {
10689
11452
  let diffText;
10690
11453
  try {
10691
- diffText = readFileSync14(args.diff, "utf8");
11454
+ diffText = readFileSync16(args.diff, "utf8");
10692
11455
  } catch (err) {
10693
11456
  throw new Error(
10694
11457
  `Cannot read diff file ${args.diff}: ${err.message}`
@@ -10696,7 +11459,7 @@ function runResolveAnchors(args) {
10696
11459
  }
10697
11460
  let rawText;
10698
11461
  try {
10699
- rawText = readFileSync14(args.input, "utf8");
11462
+ rawText = readFileSync16(args.input, "utf8");
10700
11463
  } catch (err) {
10701
11464
  throw new Error(
10702
11465
  `Cannot read findings file ${args.input}: ${err.message}`
@@ -10727,8 +11490,8 @@ function runResolveAnchors(args) {
10727
11490
  loose: resolved.filter((r) => r.tier?.startsWith("loose")).length
10728
11491
  }
10729
11492
  };
10730
- mkdirSync13(dirname11(resolve10(args.out)), { recursive: true });
10731
- writeFileSync14(args.out, JSON.stringify(report, null, 2), "utf8");
11493
+ mkdirSync15(dirname12(resolve11(args.out)), { recursive: true });
11494
+ writeFileSync15(args.out, JSON.stringify(report, null, 2), "utf8");
10732
11495
  writeStdoutLine(`Wrote resolved anchors to ${args.out}`);
10733
11496
  const s = report.stats;
10734
11497
  writeStderrLine(
@@ -10775,8 +11538,8 @@ var resolveAnchorsCommand = {
10775
11538
 
10776
11539
  // packages/cli/src/commands/review/check-coverage.ts
10777
11540
  init_esbuild_shims();
10778
- import { writeFileSync as writeFileSync15, mkdirSync as mkdirSync14 } from "node:fs";
10779
- import { dirname as dirname12, resolve as resolve11 } from "node:path";
11541
+ import { writeFileSync as writeFileSync16, mkdirSync as mkdirSync16 } from "node:fs";
11542
+ import { dirname as dirname13, resolve as resolve12 } from "node:path";
10780
11543
  function runCheckCoverage(args) {
10781
11544
  let report;
10782
11545
  try {
@@ -10792,8 +11555,8 @@ This is an environment problem, not a finding about the agents. Coverage cannot
10792
11555
  }
10793
11556
  throw err;
10794
11557
  }
10795
- mkdirSync14(dirname12(resolve11(args.out)), { recursive: true });
10796
- writeFileSync15(args.out, JSON.stringify(report, null, 2));
11558
+ mkdirSync16(dirname13(resolve12(args.out)), { recursive: true });
11559
+ writeFileSync16(args.out, JSON.stringify(report, null, 2));
10797
11560
  writeStdoutLine(`Wrote coverage report to ${args.out}`);
10798
11561
  const totalChunks = report.coveredChunks.length + report.missingChunks.length + report.uncoverableChunks.length;
10799
11562
  const worked = report.agents - report.blindAgents.length - report.idleAgents.length;
@@ -10810,6 +11573,11 @@ This is an environment problem, not a finding about the agents. Coverage cannot
10810
11573
  `ERROR: ${report.rewrittenPrompts.length} agent(s) were not launched with the prompt this CLI built for them \u2014 ${report.rewrittenPrompts.join("; ")}. \`agent-prompt\` prints a prompt to be passed VERBATIM; a summary of it is not it. The last run to paraphrase one dropped the rule against reciting a stock sentence and replaced the project's review rules with three sentences of its own. Re-run \`"\${QWEN_CODE_CLI:-qwen}" review agent-prompt\` and pass its output unedited \u2014 copy it, do not retype it.`
10811
11574
  );
10812
11575
  }
11576
+ if (report.driftedLaunches.length > 0) {
11577
+ writeStderrLine(
11578
+ `NOTE: ${report.driftedLaunches.length} agent(s) launched with a near-verbatim prompt \u2014 ${report.driftedLaunches.join("; ")}. Their briefs were opened and the work is on record, so the delivery stands and NO relaunch is owed. Copy future blocks exactly \u2014 one edited word is what turns a clean pass into this note.`
11579
+ );
11580
+ }
10813
11581
  if (report.missingRoles.length > 0) {
10814
11582
  writeStderrLine(
10815
11583
  // No count: when no role was briefed at all, `missingRoles` collapses to one
@@ -10886,9 +11654,9 @@ var checkCoverageCommand = {
10886
11654
 
10887
11655
  // packages/cli/src/commands/review/agent-prompt.ts
10888
11656
  init_esbuild_shims();
10889
- import { createHash as createHash2 } from "node:crypto";
10890
- import { readFileSync as readFileSync15 } from "node:fs";
10891
- import { dirname as dirname13, join as join11, resolve as resolve12 } from "node:path";
11657
+ import { createHash as createHash3 } from "node:crypto";
11658
+ import { readFileSync as readFileSync17 } from "node:fs";
11659
+ import { dirname as dirname14, join as join12, resolve as resolve13 } from "node:path";
10892
11660
 
10893
11661
  // packages/cli/src/commands/review/lib/path-rules.ts
10894
11662
  init_esbuild_shims();
@@ -10918,14 +11686,85 @@ var GITHUB_ACTIONS = {
10918
11686
 
10919
11687
  **Favour precision over recall here.** A false alarm on a workflow costs more reviewer trust than a missed minor nit, because a YAML finding is the easiest kind for an author to dismiss. Every finding needs the concrete trigger and the concrete outcome, like any other.`
10920
11688
  };
10921
- var PATH_RULES = [GITHUB_ACTIONS];
11689
+ var JAVA = {
11690
+ title: "Java / JVM performance",
11691
+ matches: /* @__PURE__ */ __name((p) => /\.java$/i.test(p), "matches"),
11692
+ checklist: `A Java change's most expensive regressions are decided by the JVM, not by anything a reader can see in the source: HotSpot chooses what to inline, what to scalar-replace, and what to compile at all \u2014 and a one-line change can flip each of those on a hot path. The general performance lens (N+1, repeated work, data structures) sees none of it.
11693
+
11694
+ **You are reviewing this diff, not auditing this file.** A JVM-cost weakness the code already had, on a line this change does not touch, is out of scope \u2014 the same rule as everywhere else. What is in scope: a line this diff **adds or changes**, and a cheap path this diff **makes hot** (a new caller in a request loop, a call moved inside a loop). Test sources (\`src/test/**\`), \`package-info\`/\`module-info\`, and generated sources are out of scope for the hot-path items \u2014 nothing there is hot.
11695
+
11696
+ **Correctness traps dressed as concurrency or performance code (Critical):**
11697
+
11698
+ - **A \`SimpleDateFormat\` shared across threads.** It is not thread-safe: concurrent \`parse\`/\`format\` corrupts its calendar state and returns silently wrong values or throws. If the diff adds a shared (static or instance field) \`SimpleDateFormat\`, or makes an existing one reachable from more than one thread, that is wrong, not slow. The fix is \`java.time.format.DateTimeFormatter\` (immutable) or a \`ThreadLocal\`.
11699
+ - **A compound action on \`ConcurrentHashMap\` built from two calls.** \`get\`-then-\`put\`, \`containsKey\`-then-\`put\`, check-then-act of any shape: the map's thread safety covers each call, not the pair, and the race loses an update or runs the guarded work twice. Say what the race corrupts \u2014 that is the finding. Fix with \`putIfAbsent\`, \`computeIfAbsent\`, or \`merge\`.
11700
+ - **Double-checked locking without \`volatile\` on the field.** The reading thread can observe a partially constructed object. If the diff adds or touches the idiom, the field must be \`volatile\` (or replaced with the holder idiom).
11701
+
11702
+ **JVM-cost defects provable from the source (Suggestion \u2014 a cost, not a wrongness):**
11703
+
11704
+ - **A regex compiled per call.** \`Pattern.compile(...)\` in a method body or loop, and the \`String\` conveniences that hide it \u2014 \`matches\`, \`replaceAll\`, \`replaceFirst\`, and \`split\` with a real regex (a one- or two-character \`split\` argument takes a fast path only when it is not a regex construct \u2014 \`split(".")\` does not, since \`.\` is a metacharacter; the escaped two-character form \`split("\\\\.")\` does) \u2014 recompile the pattern on every invocation. On a per-request or per-record path that is real CPU. Fix: \`private static final Pattern\`.
11705
+ - **\`+=\` on a \`String\` inside a loop.** Each iteration rebuilds and copies the whole accumulated prefix; the loop is O(n\xB2) in the final length. Fix: one \`StringBuilder\` outside the loop.
11706
+ - **Boxing on a hot path.** A \`Long\`/\`Integer\` loop accumulator, a \`Map<Long, \u2026>\` over dense \`int\` keys, \`.boxed()\` in a counted stream: every box past the small-integer cache is an allocation plus a pointer chase per iteration. Fix: primitives, or the primitive streams/collections.
11707
+ - **A capturing lambda or method reference inside a hot loop.** A non-capturing lambda is a singleton; one that closes over a local allocates per iteration. If the diff moves a capturing lambda into a loop, hoist it or restructure it to capture nothing.
11708
+ - **A log message built whether or not it is logged.** \`log.debug("result: " + value)\` pays the concatenation with the level off, and even \`log.debug("{}", expensive())\` still pays \`expensive()\`. Fix: parameterized messages, and guard expensive arguments with \`isDebugEnabled()\` or a supplier-based logging API.
11709
+ - **A collection sized after the fact.** A \`HashMap\`/\`ArrayList\` the code then fills with a known N pays repeated rehash/grow-copy chains; presize it (a \`HashMap\` needs \`N/0.75 + 1\` to avoid rehashing at all \u2014 \`HashMap.newHashMap(n)\`, JDK 19+, does that arithmetic for you).
11710
+ - **Legacy synchronized types in new code.** \`Vector\`, \`Hashtable\`, \`StringBuffer\` put a monitor on every call; the unsynchronized equivalents are the default for a reason.
11711
+ - **An exception used as control flow.** Validating by catching \`NumberFormatException\`, looping until \`EOFException\`: \`fillInStackTrace\` costs more than the work being guarded. Validate instead.
11712
+ - **Reflection rediscovered per call.** \`getMethod\`/\`getField\` on every invocation of a hot path; cache the \`Method\`/\`Field\` \u2014 or a \`MethodHandle\` \u2014 once.
11713
+
11714
+ **JIT inlining \u2014 the regression no dimension can see, and you cannot prove from source:**
11715
+
11716
+ HotSpot's C2 compiler gates inlining on the **bytecode size of the callee** among other criteria. These are the size caps in its normal policy (a project's own \`-XX:\` flags, or a different JVM, change them \u2014 check before citing):
11717
+
11718
+ | callee bytecode size | C2 normal-policy size cap |
11719
+ | --- | --- |
11720
+ | \u2264 6 (\`MaxTrivialSize\`) | under the trivial cap \u2014 size alone does not block it |
11721
+ | \u2264 35 (\`MaxInlineSize\`) | under the normal cap \u2014 size alone does not block it |
11722
+ | \u2264 325 (\`FreqInlineSize\`) | under the hot cap \u2014 size blocks it unless the call site is hot |
11723
+ | > 325 | over the hot cap \u2014 size blocks it at every call site |
11724
+ | > 8000 (\`HugeMethodLimit\`, a develop flag gated by the product \`DontCompileHugeMethods\`) | over the huge-method limit \u2014 size blocks even JIT compilation |
11725
+
11726
+ A size cap is a gate, not an outcome: passing one is necessary but not sufficient. Independent of size, C2 also declines a call site that was **never executed**, has **low call site frequency**, exceeds the inlining **depth or recursion limits**, or hits the **node-count** cutoff \u2014 so a method under a cap can still be left un-inlined, and a static size measurement proves only that a threshold was crossed, not that the method *was* inlined before and *is not* now.
11727
+
11728
+ Two more ways a diff can flip it: an already-compiled callee whose **native** size passes \`InlineSmallCode\` (x86_64 default: 2500 on JDK 17+, 2000 on JDK 8\u201316; 1000 only with \`-XX:-TieredCompilation\`) stops being inlined to protect the code cache, and a call site that gains a **third** receiver implementation goes megamorphic \u2014 vtable dispatch, and no inlining **unless one receiver still dominates the profile** (\`TypeProfileMajorReceiverPercent\`, 90% by default), in which case C2 inlines that receiver behind a guard with an uncommon trap for the rest. So the diff-shapes that matter: a small method on a hot path the change **grows** (added validation, logging, a branch), a new layer in a hot call chain, a new implementation registered for an interface invoked in a hot loop. A cold method over 325 bytes is **not** a finding \u2014 inlining only matters where the call is hot.
11729
+
11730
+ **Measure it; do not estimate bytecode from source.** Two tiers, in order of cost:
11731
+
11732
+ 1. **Static \u2014 available when the class compiles, which for a leaf class is always and for a connected one needs its dependencies.** Allocate a private scratch dir first \u2014 \`SCRATCH=$(mktemp -d)\`, never a fixed path like \`/tmp/scratch\`: other agents are compiling concurrently, and two of them writing different revisions of the same class into one directory read each other's \`.class\` files and measure the wrong bytecode (on Windows, \`mkdir %TEMP%\\review-%RANDOM%\` gives the same uniqueness). Then \`javac -proc:none -nowarn -d "$SCRATCH" X.java\` \u2014 \`-proc:none\` is not optional: annotation processors on the classpath (Lombok, Dagger, one the PR itself added) run at compile time with your privileges, so a \`javac\` without it is itself an untrusted-code execution. It is also a fidelity flag: on a project whose processor contributes code to the measured class (Lombok's generated members, Dagger's injected fields), the compiled class is missing those members and \`javap\` reports a size that is simply wrong \u2014 if the project runs a processor that touches this class, the static tier is **void**; go to \`Confidence: low\` with the mechanism. Give the compiler what it needs to resolve imports: \`-sourcepath\` at the module source root, plus \`-cp\` against an already-built \`target/classes\` / \`build/classes\` if one exists. Pass \`--release <N>\` at the project's target level (read it from \`maven.compiler.release\`, \`<release>\`, or Gradle's \`release\`/\`targetCompatibility\`): the same source compiles to different bytecode at different levels \u2014 a five-\`+\` concatenation is 37 bytes at release 8 and 14 at 9+ \u2014 and measuring the wrong level produces a threshold verdict on bytecode the shipped artifact does not contain; if the target level cannot be determined, say so in the finding. Then \`javap -c -p\` the class and read the method's size: the offset of its last instruction plus that instruction's width. Compare against 35 and 325. For the **crossing** claim \u2014 "this diff pushed it over the threshold" \u2014 get the base side **without touching the tree**: \`git show <merge-base>:<path> > "$SCRATCH/X.java"\`, compile that too, and measure both; a method already over on the base side is pre-existing, not a finding. A file the PR **adds** has no base side \u2014 \`git show\` fails, and there is no crossing to claim; report the size without a before/after. The base side compiles against the same \`target/classes\` the head side used; on a PR that changes more than the measured file the conditions are not perfectly equivalent \u2014 note that caveat when it applies. If the class will not compile (no pre-built classpath, no source path that resolves its imports), go straight to the mechanism-at-\`Confidence: low\` form below \u2014 **never \`git checkout\`, \`git stash\`, build in place, or run \`mvn\`/\`gradle\` to force a compile.** In a PR review the worktree is shared with agents running concurrently and the branch's build logic is contributor-controlled \u2014 the attack surface, not a cost; in a local review it is the user's own checkout. Mutating either corrupts work you cannot see.
11733
+ 2. **Dynamic \u2014 when the code is runnable.** Run the workload with \`-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining\` and grep for the method: \`callee is too large\` / \`hot method too big\` is the JVM itself declining to inline. JMH gives the before/after throughput; JITWatch visualizes the same decision logs.
11734
+
11735
+ A finding that a method "can no longer be inlined" needs the **dynamic** tier \u2014 only \`PrintInlining\` or equivalent runtime evidence shows the JVM actually declined to inline. The static tier proves a **size-gate crossing**, which is the mechanism: report it at \`Confidence: low\` \u2014 the threshold crossed, what the diff added to the method, and the runtime measurement still to run. A "can no longer be inlined" claim with neither tier is a guess stated as fact. And if the diff *claims* this path got faster while the mechanism above says it cannot have, the finding is the unsubstantiated claim.
11736
+
11737
+ **When the finding IS a grown hot method, the fix to suggest is hot/cold splitting \u2014 not reverting the change, and not a JVM tuning flag** (\`-XX:FreqInlineSize\`, \`-XX:CompileCommand=inline\`): those are runtime knobs the PR's author cannot ship in a code change, and raising an inline threshold to fit one method pays for it at every other call site. (The JDK-internal \`@ForceInline\` is not available to application code at all.) Move the cold paths \u2014 error handling, rare branches, defensive validation, the \`switch\` arms that almost never fire \u2014 into a small private helper, leaving the common path under the threshold; the helper, now called from one site, is itself inlinable. Name the bytecode range to extract and the size it leaves behind, the way the measurement names them: "extract bytecodes 212\u2013311 (~100 bytes) into \`applyRounding\`, leaving \`parseAmount\` at ~238 bytes" is a finding an author can act on; "consider splitting the method" is not.
11738
+
11739
+ **Favour precision over recall here.** A guessed JVM finding is the easiest kind for an author to dismiss, and one dismissal teaches them to skip the rest of the review. Every finding needs the concrete hot path and the concrete cost, like any other. Performance findings are **Suggestions** \u2014 slow is a cost, not incorrect behaviour \u2014 **except where the cost is itself the wrongness**: unbounded allocation, quadratic work, or unbounded cache growth on attacker-reachable input is a denial-of-service hole, which the severity ladder grades Critical, not a Suggestion. Name the reachable input that triggers it; "this loop is slow" with no attacker-reachable trigger stays a Suggestion.`
11740
+ };
11741
+ var PATH_RULES = [GITHUB_ACTIONS, JAVA];
11742
+ function isOutOfScope(p) {
11743
+ return /src\/(test|integrationTest|integTest|androidTest|testFixtures)\//i.test(
11744
+ p
11745
+ ) || /(?:^|\/)(?:package-info|module-info)\.java$/i.test(p) || /(?:^|\/)(?:target|build)\/(?:generated-sources|generated-test-sources|generated)\//i.test(
11746
+ p
11747
+ );
11748
+ }
11749
+ __name(isOutOfScope, "isOutOfScope");
11750
+ function describePaths(which) {
11751
+ const CAP = 10;
11752
+ const prod = which.filter((p) => !isOutOfScope(p));
11753
+ const rest = which.filter((p) => isOutOfScope(p));
11754
+ const ordered = [...prod, ...rest];
11755
+ if (ordered.length <= CAP) {
11756
+ return ordered.join(", ");
11757
+ }
11758
+ return `${ordered.slice(0, CAP).join(", ")}, \u2026and ${ordered.length - CAP} more`;
11759
+ }
11760
+ __name(describePaths, "describePaths");
10922
11761
  function pathRulesFor(paths) {
10923
11762
  const hit = PATH_RULES.filter((r) => paths.some((p) => r.matches(p)));
10924
11763
  if (hit.length === 0) return "";
10925
11764
  const parts = ["## Rules for the files in front of you", ""];
10926
11765
  for (const r of hit) {
10927
11766
  const which = paths.filter((p) => r.matches(p));
10928
- parts.push(`### ${r.title} \u2014 ${which.join(", ")}`, "", r.checklist, "");
11767
+ parts.push(`### ${r.title} \u2014 ${describePaths(which)}`, "", r.checklist, "");
10929
11768
  }
10930
11769
  return parts.join("\n").trimEnd();
10931
11770
  }
@@ -10990,32 +11829,32 @@ function chunkFrom(report, id) {
10990
11829
  throw new Error("agent-prompt: the plan has no `chunks[]`.");
10991
11830
  }
10992
11831
  const chunks = report.chunks;
10993
- const chunk = chunks.find((c) => c?.id === id);
10994
- if (!chunk) {
11832
+ const chunk2 = chunks.find((c) => c?.id === id);
11833
+ if (!chunk2) {
10995
11834
  throw new Error(
10996
11835
  `agent-prompt: the plan has no chunk ${id} (it has ${chunks.length}: ${chunks.map((c) => c?.id).join(", ")}).`
10997
11836
  );
10998
11837
  }
10999
- if (!Number.isSafeInteger(chunk.startLine) || !Number.isSafeInteger(chunk.endLine) || chunk.startLine < 1 || chunk.endLine < chunk.startLine) {
11838
+ if (!Number.isSafeInteger(chunk2.startLine) || !Number.isSafeInteger(chunk2.endLine) || chunk2.startLine < 1 || chunk2.endLine < chunk2.startLine) {
11000
11839
  throw new Error(
11001
- `agent-prompt: chunk ${id} has no usable line range (startLine=${chunk.startLine}, endLine=${chunk.endLine}).`
11840
+ `agent-prompt: chunk ${id} has no usable line range (startLine=${chunk2.startLine}, endLine=${chunk2.endLine}).`
11002
11841
  );
11003
11842
  }
11004
- return { diffPath, chunk, total: chunks.length };
11843
+ return { diffPath, chunk: chunk2, total: chunks.length };
11005
11844
  }
11006
11845
  __name(chunkFrom, "chunkFrom");
11007
11846
  function buildChunkAgentPrompt(report, id, rules) {
11008
- const { chunk, total } = chunkFrom(report, id);
11009
- const files = (Array.isArray(chunk.files) ? chunk.files : []).filter(
11847
+ const { chunk: chunk2, total } = chunkFrom(report, id);
11848
+ const files = (Array.isArray(chunk2.files) ? chunk2.files : []).filter(
11010
11849
  (f) => !!f && typeof f.path === "string" && f.path.length > 0
11011
11850
  ).map(
11012
11851
  (f) => `- ${inertPath(f.path)} (new-side lines ${f.newStart}-${f.newEnd})`
11013
11852
  ).join("\n");
11014
- const unreachable = chunk.maxLineChars > READ_FILE_CHAR_CAP;
11853
+ const unreachable = chunk2.maxLineChars > READ_FILE_CHAR_CAP;
11015
11854
  const parts = [
11016
- `You are reviewing chunk ${chunk.id} of ${total} of a code diff.`,
11855
+ `You are reviewing chunk ${chunk2.id} of ${total} of a code diff.`,
11017
11856
  "",
11018
- `Your territory: lines ${chunk.startLine}-${chunk.endLine} of the diff (${chunk.lines} lines, ${chunk.chars} characters). The surrounding chunks belong to other agents \u2014 do not review them.`,
11857
+ `Your territory: lines ${chunk2.startLine}-${chunk2.endLine} of the diff (${chunk2.lines} lines, ${chunk2.chars} characters). The surrounding chunks belong to other agents \u2014 do not review them.`,
11019
11858
  "",
11020
11859
  "It covers these source files:",
11021
11860
  files || "- (none recorded)",
@@ -11025,11 +11864,11 @@ function buildChunkAgentPrompt(report, id, rules) {
11025
11864
  if (unreachable) {
11026
11865
  parts.push(
11027
11866
  "",
11028
- `**This chunk contains a single line of ${chunk.maxLineChars} characters** \u2014 longer than one read returns, and paging cannot reach its tail (every page starts at a line boundary). Do not claim to have reviewed it. Return exactly:`,
11867
+ `**This chunk contains a single line of ${chunk2.maxLineChars} characters** \u2014 longer than one read returns, and paging cannot reach its tail (every page starts at a line boundary). Do not claim to have reviewed it. Return exactly:`,
11029
11868
  "",
11030
- ` Uncoverable: chunk ${chunk.id} \u2014 line exceeds the read limit`
11869
+ ` Uncoverable: chunk ${chunk2.id} \u2014 line exceeds the read limit`
11031
11870
  );
11032
- } else if (chunk.oversized) {
11871
+ } else if (chunk2.oversized) {
11033
11872
  parts.push(
11034
11873
  "",
11035
11874
  "**This chunk is oversized** \u2014 it is a single hunk with no safe place to cut, and it may exceed one read. Expect to page."
@@ -11049,7 +11888,7 @@ function buildChunkAgentPrompt(report, id, rules) {
11049
11888
  "",
11050
11889
  EXCLUSIONS
11051
11890
  );
11052
- const chunkPaths = (Array.isArray(chunk.files) ? chunk.files : []).map((f) => f?.path).filter((p) => typeof p === "string");
11891
+ const chunkPaths = (Array.isArray(chunk2.files) ? chunk2.files : []).map((f) => f?.path).filter((p) => typeof p === "string");
11053
11892
  const pathRules = pathRulesFor(chunkPaths);
11054
11893
  if (pathRules) parts.push("", pathRules);
11055
11894
  if (rules && rules.trim()) {
@@ -11064,7 +11903,7 @@ function buildChunkAgentPrompt(report, id, rules) {
11064
11903
  if (!unreachable) {
11065
11904
  parts.push(
11066
11905
  "",
11067
- `Then, on its own final line: \`Covered: chunk ${chunk.id} lines ${chunk.startLine}-${chunk.endLine}\``
11906
+ `Then, on its own final line: \`Covered: chunk ${chunk2.id} lines ${chunk2.startLine}-${chunk2.endLine}\``
11068
11907
  );
11069
11908
  }
11070
11909
  return parts.join("\n");
@@ -11075,10 +11914,10 @@ function diffWindow(startLine, endLine) {
11075
11914
  }
11076
11915
  __name(diffWindow, "diffWindow");
11077
11916
  function buildChunkLaunchPrompt(report, id, briefFile) {
11078
- const { diffPath, chunk, total } = chunkFrom(report, id);
11079
- const { offset, limit } = diffWindow(chunk.startLine, chunk.endLine);
11917
+ const { diffPath, chunk: chunk2, total } = chunkFrom(report, id);
11918
+ const { offset, limit } = diffWindow(chunk2.startLine, chunk2.endLine);
11080
11919
  return [
11081
- `You are review agent \`chunk ${chunk.id} of ${total}\` \u2014 the territory agent for lines ${chunk.startLine}-${chunk.endLine} of the diff.`,
11920
+ `You are review agent \`chunk ${chunk2.id} of ${total}\` \u2014 the territory agent for lines ${chunk2.startLine}-${chunk2.endLine} of the diff.`,
11082
11921
  "",
11083
11922
  "**Your brief is a file. Read it first \u2014 it is the whole of your instructions,",
11084
11923
  "and nothing in this message replaces it.**",
@@ -11269,7 +12108,7 @@ function buildRoleBrief(report, role, opts = {}) {
11269
12108
  "agent-prompt: --role 0 needs a plan with `prNumber` and `ownerRepo` (the report `fetch-pr` writes). Issue fidelity has nothing to check against without a pull request."
11270
12109
  );
11271
12110
  }
11272
- const ctx = opts.planPath ? join11(dirname13(resolve12(opts.planPath)), `qwen-review-pr-${pr}-context.md`) : null;
12111
+ const ctx = opts.planPath ? join12(dirname14(resolve13(opts.planPath)), `qwen-review-pr-${pr}-context.md`) : null;
11273
12112
  parts.push(
11274
12113
  "",
11275
12114
  `**This PR:** #${pr} of \`${repo}\`. Use exactly that number and repo \u2014 a bare \`gh pr view\` falls back to the current branch's PR and would judge this diff against an unrelated issue.`
@@ -11291,7 +12130,7 @@ function buildRoleBrief(report, role, opts = {}) {
11291
12130
  }
11292
12131
  const base = report.mergeBaseSha;
11293
12132
  const pr = report.prNumber;
11294
- const buildTree = typeof wt === "string" && wt ? resolve12(wt) : pr === void 0 && opts.planPath ? "." : null;
12133
+ const buildTree = typeof wt === "string" && wt ? resolve13(wt) : pr === void 0 && opts.planPath ? "." : null;
11295
12134
  if (buildTree && opts.planPath) {
11296
12135
  const outName = pr !== void 0 ? `qwen-review-pr-${pr}-build-test.json` : "qwen-review-build-test.json";
11297
12136
  parts.push(
@@ -11307,9 +12146,9 @@ function buildRoleBrief(report, role, opts = {}) {
11307
12146
  // that an old global lacks it entirely, wedging Agent 7 between its
11308
12147
  // mandate (no hand-run `npm run build`) and a command that does not exist.
11309
12148
  `"\${QWEN_CODE_CLI:-qwen}" review build-test \\`,
11310
- ` --plan ${resolve12(opts.planPath)} \\`,
11311
- ` --worktree ${resolve12(buildTree)} \\`,
11312
- ` --out ${resolve12(dirname13(opts.planPath), outName)}`,
12149
+ ` --plan ${resolve13(opts.planPath)} \\`,
12150
+ ` --worktree ${resolve13(buildTree)} \\`,
12151
+ ` --out ${resolve13(dirname14(opts.planPath), outName)}`,
11313
12152
  "```"
11314
12153
  );
11315
12154
  }
@@ -11319,10 +12158,10 @@ function buildRoleBrief(report, role, opts = {}) {
11319
12158
  "**Then run the test-efficacy probe.** A green suite says the tests pass. It does not say they would have failed had the change been wrong, and those are different claims. Give this call `timeout: 600000` too \u2014 besides the revert probe it runs up to 8 single-statement deletion mutants and up to 6 per-hunk reverse-apply probes, each a suite run, and it budgets itself to finish inside that ceiling:",
11320
12159
  "",
11321
12160
  "```bash",
11322
- `"\${QWEN_CODE_CLI:-qwen}" review test-efficacy ${resolve12(opts.planPath)} \\`,
11323
- ` --worktree ${typeof wt === "string" ? resolve12(wt) : "<worktree>"} \\`,
12161
+ `"\${QWEN_CODE_CLI:-qwen}" review test-efficacy ${resolve13(opts.planPath)} \\`,
12162
+ ` --worktree ${typeof wt === "string" ? resolve13(wt) : "<worktree>"} \\`,
11324
12163
  ` --base ${base} \\`,
11325
- ` --out ${resolve12(dirname13(opts.planPath), `qwen-review-pr-${pr}-efficacy.json`)}`,
12164
+ ` --out ${resolve13(dirname14(opts.planPath), `qwen-review-pr-${pr}-efficacy.json`)}`,
11326
12165
  "```",
11327
12166
  "",
11328
12167
  'Read its `findings[]`. `kind: "unreachable"` is a test the project\'s test command never collects \u2014 it did not run here and it does not run in CI. `kind: "inert"` is a test that **still passed with the change reverted**: it is green whether or not the feature exists, so it cannot catch a regression in it. `kind: "mutant-survived"` is a single safety statement the diff added (a `.clear()`, an `.abort(\u2026)`, a reset-to-empty) that was **deleted and every affected test stayed green** \u2014 no test in the diff fails when it is removed, which the whole-file revert cannot see when the file\'s other, tested behaviours mask it. `kind: "hunk-survived"` is one HUNK reverted on its own with every affected test still green \u2014 that specific change ships with nothing gating it. Report each of the four as a **Suggestion** with `Source: [test]`, saying plainly which behaviour has no test in this diff that would catch its removal. `harnessValidated` has THREE values and they are not two: `false` means the positive control failed and every would-be survivor was re-classed \u2014 say the harness could not be validated instead of implying clean coverage; `null` means the control never ran, so the run is neither validated nor refuted and any survivor below is unconfirmed by a control; only `true` licenses reading a survivor as a coverage gap. **`inconclusive` is not a finding** \u2014 for probes, mutants and hunks alike, reverting or mutating the source often breaks the test\'s own compile, and that is not the test catching anything. Entries counted in `mutants.skipped*` or `hunks.skipped*` never ran \u2014 not findings either, and `skippedForControl` in particular means the control stopped the run, NOT that the window ran out. `mutants.note`, when present, explains why no mutants ran at all. Note them and move on.'
@@ -11468,7 +12307,7 @@ function buildLaunch(report, planPath, spec, rules) {
11468
12307
  }
11469
12308
  __name(buildLaunch, "buildLaunch");
11470
12309
  function findingsDigest(content, rules) {
11471
- return createHash2("sha256").update(JSON.stringify([content, rules ?? null])).digest("hex").slice(0, 12);
12310
+ return createHash3("sha256").update(JSON.stringify([content, rules ?? null])).digest("hex").slice(0, 12);
11472
12311
  }
11473
12312
  __name(findingsDigest, "findingsDigest");
11474
12313
  function foldFindings(role, content, prompt) {
@@ -11674,7 +12513,7 @@ function runAgentPrompt(args) {
11674
12513
  }
11675
12514
  let report;
11676
12515
  try {
11677
- report = JSON.parse(readFileSync15(args.plan, "utf8"));
12516
+ report = JSON.parse(readFileSync17(args.plan, "utf8"));
11678
12517
  } catch (err) {
11679
12518
  throw new Error(
11680
12519
  `agent-prompt: cannot read the plan ${args.plan}: ${err.message}`
@@ -11683,7 +12522,7 @@ function runAgentPrompt(args) {
11683
12522
  let rules;
11684
12523
  if (args.rules) {
11685
12524
  try {
11686
- rules = readFileSync15(args.rules, "utf8");
12525
+ rules = readFileSync17(args.rules, "utf8");
11687
12526
  } catch (err) {
11688
12527
  throw new Error(
11689
12528
  `agent-prompt: cannot read the rules ${args.rules}: ${err.message}. Omit --rules if this review has none; passing a path that does not resolve would silently review without the project rules it was told to enforce.`
@@ -11698,7 +12537,7 @@ function runAgentPrompt(args) {
11698
12537
  if (hasFindings && args.role) {
11699
12538
  const role = args.role;
11700
12539
  try {
11701
- findingsContent = readFileSync15(args.findings, "utf8");
12540
+ findingsContent = readFileSync17(args.findings, "utf8");
11702
12541
  } catch (err) {
11703
12542
  throw new Error(
11704
12543
  `agent-prompt: cannot read the findings ${args.findings}: ${err.message}. Pass a path that resolves \u2014 --findings is required for this role, so omitting it only fails one guard earlier. An early reverse-audit round with nothing confirmed passes an empty file (create it first).`
@@ -11807,18 +12646,18 @@ var agentPromptCommand = {
11807
12646
  init_esbuild_shims();
11808
12647
  import { spawnSync as spawnSync2 } from "node:child_process";
11809
12648
  import {
11810
- existsSync as existsSync5,
11811
- readFileSync as readFileSync17,
11812
- rmSync as rmSync4,
12649
+ existsSync as existsSync7,
12650
+ readFileSync as readFileSync19,
12651
+ rmSync as rmSync5,
11813
12652
  statfsSync,
11814
- writeFileSync as writeFileSync16
12653
+ writeFileSync as writeFileSync17
11815
12654
  } from "node:fs";
11816
- import { join as join13, resolve as resolve13 } from "node:path";
12655
+ import { join as join14, resolve as resolve14 } from "node:path";
11817
12656
 
11818
12657
  // packages/cli/src/commands/review/lib/workspaces.ts
11819
12658
  init_esbuild_shims();
11820
- import { existsSync as existsSync4, readFileSync as readFileSync16, readdirSync as readdirSync4 } from "node:fs";
11821
- import { join as join12 } from "node:path";
12659
+ import { existsSync as existsSync6, readFileSync as readFileSync18, readdirSync as readdirSync4 } from "node:fs";
12660
+ import { join as join13 } from "node:path";
11822
12661
  function isWorkspaceMember(filePath, workspaceGlobs) {
11823
12662
  return workspaceDirFor(filePath, workspaceGlobs) !== null;
11824
12663
  }
@@ -11856,7 +12695,7 @@ __name(hasUnmodeledWorkspaceGlob, "hasUnmodeledWorkspaceGlob");
11856
12695
  function readWorkspaceGlobs(root) {
11857
12696
  try {
11858
12697
  const pkg = JSON.parse(
11859
- readFileSync16(join12(root, "package.json"), "utf8")
12698
+ readFileSync18(join13(root, "package.json"), "utf8")
11860
12699
  );
11861
12700
  const ws = pkg.workspaces;
11862
12701
  const globs = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
@@ -11869,7 +12708,7 @@ __name(readWorkspaceGlobs, "readWorkspaceGlobs");
11869
12708
  function readRootPackage(root) {
11870
12709
  let pkg;
11871
12710
  try {
11872
- pkg = JSON.parse(readFileSync16(join12(root, "package.json"), "utf8"));
12711
+ pkg = JSON.parse(readFileSync18(join13(root, "package.json"), "utf8"));
11873
12712
  } catch {
11874
12713
  return null;
11875
12714
  }
@@ -11893,7 +12732,7 @@ function readWorkspacePackages(root) {
11893
12732
  const base = g.slice(0, -2);
11894
12733
  let entries;
11895
12734
  try {
11896
- entries = readdirSync4(join12(root, base), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
12735
+ entries = readdirSync4(join13(root, base), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
11897
12736
  } catch {
11898
12737
  continue;
11899
12738
  }
@@ -11905,11 +12744,11 @@ function readWorkspacePackages(root) {
11905
12744
  const pkgs = [];
11906
12745
  for (const dir of dirs) {
11907
12746
  if (workspaceDirFor(`${dir}/package.json`, globs) !== dir) continue;
11908
- const manifest = join12(root, dir, "package.json");
11909
- if (!existsSync4(manifest)) continue;
12747
+ const manifest = join13(root, dir, "package.json");
12748
+ if (!existsSync6(manifest)) continue;
11910
12749
  let pkg;
11911
12750
  try {
11912
- pkg = JSON.parse(readFileSync16(manifest, "utf8"));
12751
+ pkg = JSON.parse(readFileSync18(manifest, "utf8"));
11913
12752
  } catch {
11914
12753
  continue;
11915
12754
  }
@@ -12082,7 +12921,7 @@ __name(unresolvedWorkspaceDeps, "unresolvedWorkspaceDeps");
12082
12921
  function changedFilesFrom(planPath) {
12083
12922
  let parsed;
12084
12923
  try {
12085
- parsed = JSON.parse(readFileSync17(planPath, "utf8"));
12924
+ parsed = JSON.parse(readFileSync19(planPath, "utf8"));
12086
12925
  } catch (err) {
12087
12926
  throw new Error(
12088
12927
  `build-test: cannot read the plan ${planPath}: ${err.message}`
@@ -12099,7 +12938,7 @@ function changedFilesFrom(planPath) {
12099
12938
  }
12100
12939
  __name(changedFilesFrom, "changedFilesFrom");
12101
12940
  function runBuildTest(args) {
12102
- const root = resolve13(args.worktree);
12941
+ const root = resolve14(args.worktree);
12103
12942
  const perCommandMs = args.timeout * 1e3;
12104
12943
  const exec = args.exec ?? run;
12105
12944
  const changed = changedFilesFrom(args.plan);
@@ -12165,15 +13004,15 @@ function runBuildTest(args) {
12165
13004
  timedOut: [],
12166
13005
  note: ""
12167
13006
  };
12168
- const npmLock = existsSync5(join13(root, "package-lock.json"));
12169
- const installComplete = /* @__PURE__ */ __name(() => existsSync5(join13(root, "node_modules", ".package-lock.json")), "installComplete");
12170
- if (args.install && !npmLock && !existsSync5(join13(root, "node_modules"))) {
13007
+ const npmLock = existsSync7(join14(root, "package-lock.json"));
13008
+ const installComplete = /* @__PURE__ */ __name(() => existsSync7(join14(root, "node_modules", ".package-lock.json")), "installComplete");
13009
+ if (args.install && !npmLock && !existsSync7(join14(root, "node_modules"))) {
12171
13010
  const altLock = [
12172
13011
  ["yarn.lock", "yarn install --frozen-lockfile"],
12173
13012
  ["pnpm-lock.yaml", "pnpm install --frozen-lockfile"],
12174
13013
  ["bun.lockb", "bun install --frozen-lockfile"],
12175
13014
  ["bun.lock", "bun install --frozen-lockfile"]
12176
- ].find(([f]) => existsSync5(join13(root, f)));
13015
+ ].find(([f]) => existsSync7(join14(root, f)));
12177
13016
  return unsupportedReport(
12178
13017
  altLock ? `This is a ${altLock[0]} repo with no installed \`node_modules\`, so \`npm ci\` cannot install it. Run \`${altLock[1]}\` first, then fall back to the build/test precedence in your brief, each command with a deadline it can meet.` : "There is no lockfile and no `node_modules` here, so nothing can be installed deterministically. Install dependencies first, then fall back to the build/test precedence in your brief."
12179
13018
  );
@@ -12191,7 +13030,7 @@ function runBuildTest(args) {
12191
13030
  if (install.timedOut) results.timedOut.push(install.command);
12192
13031
  if (install.timedOut) {
12193
13032
  try {
12194
- rmSync4(join13(root, "node_modules"), {
13033
+ rmSync5(join14(root, "node_modules"), {
12195
13034
  recursive: true,
12196
13035
  force: true,
12197
13036
  maxRetries: 3
@@ -12314,7 +13153,7 @@ var buildTestCommand = {
12314
13153
  try {
12315
13154
  const report = runBuildTest(args);
12316
13155
  if (args.out) {
12317
- writeFileSync16(args.out, JSON.stringify(report, null, 2));
13156
+ writeFileSync17(args.out, JSON.stringify(report, null, 2));
12318
13157
  }
12319
13158
  writeStdoutLine(JSON.stringify(report, null, 2));
12320
13159
  } catch (err) {
@@ -12328,25 +13167,25 @@ var buildTestCommand = {
12328
13167
  init_esbuild_shims();
12329
13168
  import { spawnSync as spawnSync4 } from "node:child_process";
12330
13169
  import {
12331
- existsSync as existsSync6,
12332
- mkdirSync as mkdirSync15,
12333
- readFileSync as readFileSync18,
12334
- rmSync as rmSync6,
12335
- statSync as statSync5,
12336
- writeFileSync as writeFileSync17
13170
+ existsSync as existsSync8,
13171
+ mkdirSync as mkdirSync17,
13172
+ readFileSync as readFileSync20,
13173
+ rmSync as rmSync7,
13174
+ statSync as statSync6,
13175
+ writeFileSync as writeFileSync18
12337
13176
  } from "node:fs";
12338
- import { dirname as dirname14, join as join14, resolve as resolve14 } from "node:path";
13177
+ import { dirname as dirname15, join as join15, resolve as resolve15 } from "node:path";
12339
13178
 
12340
13179
  // packages/cli/src/commands/review/lib/worktree.ts
12341
13180
  init_esbuild_shims();
12342
13181
  import { spawnSync as spawnSync3 } from "node:child_process";
12343
- import { rmSync as rmSync5 } from "node:fs";
13182
+ import { rmSync as rmSync6 } from "node:fs";
12344
13183
  function discardWorktree(cwd, tree) {
12345
13184
  const sweep = spawnSync3("git", ["worktree", "remove", "--force", tree], {
12346
13185
  cwd,
12347
13186
  encoding: "utf8"
12348
13187
  });
12349
- rmSync5(tree, { recursive: true, force: true });
13188
+ rmSync6(tree, { recursive: true, force: true });
12350
13189
  return sweep;
12351
13190
  }
12352
13191
  __name(discardWorktree, "discardWorktree");
@@ -12382,7 +13221,7 @@ function runBaseTree(args) {
12382
13221
  }), "unavailable");
12383
13222
  let plan;
12384
13223
  try {
12385
- plan = JSON.parse(readFileSync18(args.plan, "utf8"));
13224
+ plan = JSON.parse(readFileSync20(args.plan, "utf8"));
12386
13225
  } catch (err) {
12387
13226
  return unavailable(
12388
13227
  `cannot read the plan ${args.plan}: ${err.message}`
@@ -12399,15 +13238,15 @@ function runBaseTree(args) {
12399
13238
  "the base branch could not be fetched, so mergeBaseSha may be stale; an A/B against it would attribute the base branch's own commits to this PR"
12400
13239
  );
12401
13240
  }
12402
- const worktree = resolve14(args.worktree);
12403
- if (!existsSync6(worktree)) {
13241
+ const worktree = resolve15(args.worktree);
13242
+ if (!existsSync8(worktree)) {
12404
13243
  return unavailable(`the review worktree ${worktree} does not exist`);
12405
13244
  }
12406
13245
  const tree = baseWorktreePath(worktree);
12407
- const marker = /* @__PURE__ */ __name(() => join14(tree, ".qwen-review-base-ok"), "marker");
12408
- const failedMarker = /* @__PURE__ */ __name(() => join14(tree, ".qwen-review-base-failed"), "failedMarker");
13246
+ const marker = /* @__PURE__ */ __name(() => join15(tree, ".qwen-review-base-ok"), "marker");
13247
+ const failedMarker = /* @__PURE__ */ __name(() => join15(tree, ".qwen-review-base-failed"), "failedMarker");
12409
13248
  try {
12410
- if (existsSync6(tree) && readFileSync18(marker(), "utf8").trim() === baseSha && gitOut(tree, "rev-parse", "HEAD") === baseSha) {
13249
+ if (existsSync8(tree) && readFileSync20(marker(), "utf8").trim() === baseSha && gitOut(tree, "rev-parse", "HEAD") === baseSha) {
12411
13250
  return {
12412
13251
  available: true,
12413
13252
  path: tree,
@@ -12419,7 +13258,7 @@ function runBaseTree(args) {
12419
13258
  } catch {
12420
13259
  }
12421
13260
  try {
12422
- if (existsSync6(tree) && readFileSync18(failedMarker(), "utf8").trim() === baseSha) {
13261
+ if (existsSync8(tree) && readFileSync20(failedMarker(), "utf8").trim() === baseSha) {
12423
13262
  return {
12424
13263
  available: false,
12425
13264
  path: tree,
@@ -12432,14 +13271,14 @@ function runBaseTree(args) {
12432
13271
  }
12433
13272
  const lock = `${tree}.lock`;
12434
13273
  try {
12435
- const age = Date.now() - statSync5(lock).mtimeMs;
13274
+ const age = Date.now() - statSync6(lock).mtimeMs;
12436
13275
  if (age > 30 * 60 * 1e3) {
12437
- rmSync6(lock, { recursive: true, force: true });
13276
+ rmSync7(lock, { recursive: true, force: true });
12438
13277
  }
12439
13278
  } catch {
12440
13279
  }
12441
13280
  try {
12442
- mkdirSync15(lock);
13281
+ mkdirSync17(lock);
12443
13282
  } catch (err) {
12444
13283
  if (err.code !== "EEXIST") {
12445
13284
  return unavailable(
@@ -12453,7 +13292,7 @@ function runBaseTree(args) {
12453
13292
  try {
12454
13293
  return buildBaseTree(baseSha);
12455
13294
  } finally {
12456
- rmSync6(lock, { recursive: true, force: true });
13295
+ rmSync7(lock, { recursive: true, force: true });
12457
13296
  }
12458
13297
  function buildBaseTree(baseSha2) {
12459
13298
  let sweep;
@@ -12477,7 +13316,7 @@ function runBaseTree(args) {
12477
13316
  });
12478
13317
  if (!build.ok || build.toolchain !== "npm" || build.build.length === 0) {
12479
13318
  try {
12480
- writeFileSync17(failedMarker(), `${baseSha2}
13319
+ writeFileSync18(failedMarker(), `${baseSha2}
12481
13320
  `);
12482
13321
  } catch {
12483
13322
  }
@@ -12490,7 +13329,7 @@ function runBaseTree(args) {
12490
13329
  };
12491
13330
  }
12492
13331
  try {
12493
- writeFileSync17(marker(), `${baseSha2}
13332
+ writeFileSync18(marker(), `${baseSha2}
12494
13333
  `);
12495
13334
  } catch {
12496
13335
  }
@@ -12530,8 +13369,8 @@ var baseTreeCommand = {
12530
13369
  try {
12531
13370
  const report = runBaseTree(args);
12532
13371
  if (args.out) {
12533
- mkdirSync15(dirname14(resolve14(args.out)), { recursive: true });
12534
- writeFileSync17(resolve14(args.out), JSON.stringify(report, null, 2));
13372
+ mkdirSync17(dirname15(resolve15(args.out)), { recursive: true });
13373
+ writeFileSync18(resolve15(args.out), JSON.stringify(report, null, 2));
12535
13374
  }
12536
13375
  writeStdoutLine(JSON.stringify(report, null, 2));
12537
13376
  writeStderrLine(`base-tree: ${report.note}`);
@@ -12545,8 +13384,8 @@ var baseTreeCommand = {
12545
13384
  // packages/cli/src/commands/review/test-delta.ts
12546
13385
  init_esbuild_shims();
12547
13386
  import { spawnSync as spawnSync5 } from "node:child_process";
12548
- import { existsSync as existsSync7, mkdirSync as mkdirSync16, readFileSync as readFileSync19, writeFileSync as writeFileSync18 } from "node:fs";
12549
- import { dirname as dirname15, resolve as resolve15 } from "node:path";
13387
+ import { existsSync as existsSync9, mkdirSync as mkdirSync18, readFileSync as readFileSync21, writeFileSync as writeFileSync19 } from "node:fs";
13388
+ import { dirname as dirname16, resolve as resolve16 } from "node:path";
12550
13389
  var ANSI_SGR_RE2 = /\x1b\[[0-9;]*m/g;
12551
13390
  var RERUNNABLE_COMMAND_RE = /^npm test(?: --workspace="[\w@./-]+")?$/;
12552
13391
  var TRIM_MARKER_RE = /\.\.\. \[\d+ characters omitted/;
@@ -12609,7 +13448,7 @@ var TOTAL_BUDGET_MS = 54e4;
12609
13448
  var DEFAULT_TIMEOUT_S = 300;
12610
13449
  function runTestDelta(args) {
12611
13450
  const exec = args.exec ?? run2;
12612
- const baseline = resolve15(args.baseline);
13451
+ const baseline = resolve16(args.baseline);
12613
13452
  const empty = /* @__PURE__ */ __name((note) => ({
12614
13453
  entries: [],
12615
13454
  netNew: [],
@@ -12619,13 +13458,13 @@ function runTestDelta(args) {
12619
13458
  }), "empty");
12620
13459
  let report;
12621
13460
  try {
12622
- report = JSON.parse(readFileSync19(args.report, "utf8"));
13461
+ report = JSON.parse(readFileSync21(args.report, "utf8"));
12623
13462
  } catch (err) {
12624
13463
  return empty(
12625
13464
  `cannot read the build-test report ${args.report}: ${err.message}`
12626
13465
  );
12627
13466
  }
12628
- if (!existsSync7(baseline)) {
13467
+ if (!existsSync9(baseline)) {
12629
13468
  return empty(
12630
13469
  `the base tree ${baseline} does not exist \u2014 run \`qwen review base-tree\` first`
12631
13470
  );
@@ -12724,56 +13563,743 @@ function runTestDelta(args) {
12724
13563
  `${timedOut} base-side rerun(s) timed out \u2014 infrastructure, not evidence` + (budgetClamped.length ? ` (${budgetClamped.length} of them on a deadline the whole-command budget shortened, not their own: ${budgetClamped.join(", ")} \u2014 a rerun with budget to spare may still measure them)` : "")
12725
13564
  );
12726
13565
  }
12727
- if (skippedForBudget.length) {
12728
- parts.push(
12729
- `${skippedForBudget.length} failed command(s) not rerun \u2014 the whole-command budget was exhausted (${skippedForBudget.join(", ")}); their failures stay unattributed, judge them by the diff`
12730
- );
13566
+ if (skippedForBudget.length) {
13567
+ parts.push(
13568
+ `${skippedForBudget.length} failed command(s) not rerun \u2014 the whole-command budget was exhausted (${skippedForBudget.join(", ")}); their failures stay unattributed, judge them by the diff`
13569
+ );
13570
+ }
13571
+ return {
13572
+ entries,
13573
+ netNew,
13574
+ shared,
13575
+ skippedForBudget,
13576
+ note: parts.join(". ") || "nothing to report"
13577
+ };
13578
+ }
13579
+ __name(runTestDelta, "runTestDelta");
13580
+ var testDeltaCommand = {
13581
+ command: "test-delta",
13582
+ describe: "Rerun the PR side's failed test commands on the base tree and report which failing files are the PR's own (net-new) vs pre-existing (shared)",
13583
+ builder: /* @__PURE__ */ __name((yargs) => yargs.option("report", {
13584
+ type: "string",
13585
+ demandOption: true,
13586
+ describe: "Agent 7's build-test report (its failed commands and outputs)"
13587
+ }).option("baseline", {
13588
+ type: "string",
13589
+ demandOption: true,
13590
+ describe: "The BUILT base tree from `qwen review base-tree`"
13591
+ }).option("pr-worktree", {
13592
+ type: "string",
13593
+ describe: "The PR worktree the report's failures were produced in \u2014 its root is stripped so both sides compare as repo-relative paths"
13594
+ }).option("out", { type: "string", describe: "Write the JSON report here" }).option("timeout", {
13595
+ type: "number",
13596
+ default: 300,
13597
+ describe: "Per-command deadline in seconds, as build-test"
13598
+ }), "builder"),
13599
+ handler: /* @__PURE__ */ __name((argv) => {
13600
+ const args = argv;
13601
+ const report = runTestDelta(args);
13602
+ if (args.out) {
13603
+ mkdirSync18(dirname16(resolve16(args.out)), { recursive: true });
13604
+ writeFileSync19(resolve16(args.out), JSON.stringify(report, null, 2));
13605
+ }
13606
+ writeStdoutLine(JSON.stringify(report, null, 2));
13607
+ writeStderrLine(`test-delta: ${report.note}`);
13608
+ }, "handler")
13609
+ };
13610
+
13611
+ // packages/cli/src/commands/review/drive.ts
13612
+ init_esbuild_shims();
13613
+ import { spawnSync as spawnSync6 } from "node:child_process";
13614
+ import {
13615
+ mkdirSync as mkdirSync19,
13616
+ writeFileSync as writeFileSync20,
13617
+ readFileSync as readFileSync22,
13618
+ existsSync as existsSync10,
13619
+ rmSync as rmSync8,
13620
+ statSync as statSync7
13621
+ } from "node:fs";
13622
+ import { dirname as dirname17, join as join16, resolve as resolve17 } from "node:path";
13623
+ import { tmpdir as tmpdir2 } from "node:os";
13624
+ var SERVER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
13625
+ function shellQuote(v) {
13626
+ return `'${v.split("'").join(`'\\''`)}'`;
13627
+ }
13628
+ __name(shellQuote, "shellQuote");
13629
+ var CAPTURE_MAX = 2e5;
13630
+ var LOG_MAX_BYTES = 8 * 1024 * 1024;
13631
+ var POLL_MS = 250;
13632
+ function logBytes(p) {
13633
+ try {
13634
+ return statSync7(p).size;
13635
+ } catch {
13636
+ return 0;
13637
+ }
13638
+ }
13639
+ __name(logBytes, "logBytes");
13640
+ function waitMs(ms) {
13641
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
13642
+ }
13643
+ __name(waitMs, "waitMs");
13644
+ function run3(cmd, args, input) {
13645
+ const r = spawnSync6(cmd, args, {
13646
+ encoding: "utf8",
13647
+ input,
13648
+ timeout: 3e4,
13649
+ maxBuffer: 64 * 1024 * 1024,
13650
+ stdio: ["pipe", "pipe", "pipe"]
13651
+ });
13652
+ return {
13653
+ status: r.status ?? null,
13654
+ stdout: r.stdout ?? "",
13655
+ stderr: r.stderr ?? ""
13656
+ };
13657
+ }
13658
+ __name(run3, "run");
13659
+ var DRIVE_SENTINEL = "__QWEN_REVIEW_DRIVE_DONE__";
13660
+ function wrapScript(script, sentinelPath, sentinel = DRIVE_SENTINEL) {
13661
+ return `trap '__qwen_rc=$?; echo "${sentinel} rc=\${__qwen_rc}" > ${shellQuote(sentinelPath)}' EXIT
13662
+ set +e
13663
+ ${script}
13664
+ `;
13665
+ }
13666
+ __name(wrapScript, "wrapScript");
13667
+ function sentinelExitCode(capture, sentinel = DRIVE_SENTINEL) {
13668
+ const re = new RegExp(`${sentinel} rc=(\\d+)`, "g");
13669
+ let last = null;
13670
+ for (let m = re.exec(capture); m; m = re.exec(capture)) last = m;
13671
+ return last ? Number(last[1]) : null;
13672
+ }
13673
+ __name(sentinelExitCode, "sentinelExitCode");
13674
+ function trimCapture(s) {
13675
+ if (s.length <= CAPTURE_MAX) return { text: s, truncated: false };
13676
+ return {
13677
+ text: `... [${s.length - CAPTURE_MAX} characters omitted from the head] ...
13678
+ ${s.slice(-CAPTURE_MAX)}`,
13679
+ truncated: true
13680
+ };
13681
+ }
13682
+ __name(trimCapture, "trimCapture");
13683
+ function runDrive(args) {
13684
+ const exec = args.exec ?? run3;
13685
+ const server = args.server;
13686
+ if (!SERVER_NAME_RE.test(server)) {
13687
+ return {
13688
+ outcome: "unavailable",
13689
+ observed: false,
13690
+ exitCode: null,
13691
+ readyAfterMs: null,
13692
+ droveForMs: 0,
13693
+ output: "",
13694
+ truncated: false,
13695
+ killedStale: false,
13696
+ note: `--server ${JSON.stringify(server)} is not a name this command will own: it becomes both a path under the temp dir and a word in the shell line tmux runs, so it is restricted to letters, digits, dot, dash and underscore (max 64). Nothing was started.`
13697
+ };
13698
+ }
13699
+ const tmux = /* @__PURE__ */ __name((...a) => exec("tmux", ["-L", server, ...a]), "tmux");
13700
+ if (exec("tmux", ["-V"]).status !== 0) {
13701
+ return {
13702
+ outcome: "unavailable",
13703
+ observed: false,
13704
+ exitCode: null,
13705
+ readyAfterMs: null,
13706
+ droveForMs: 0,
13707
+ output: "",
13708
+ truncated: false,
13709
+ killedStale: false,
13710
+ note: "tmux is not available, so nothing could be driven \u2014 an environment gap, not a finding about the diff"
13711
+ };
13712
+ }
13713
+ const killedStale = tmux("kill-server").status === 0;
13714
+ const started = Date.now();
13715
+ let readyAfterMs = null;
13716
+ if (args.ready) {
13717
+ const deadline = started + args.readyTimeout * 1e3;
13718
+ for (; ; ) {
13719
+ if (exec("bash", ["-lc", args.ready]).status === 0) {
13720
+ readyAfterMs = Date.now() - started;
13721
+ break;
13722
+ }
13723
+ if (Date.now() >= deadline) {
13724
+ tmux("kill-server");
13725
+ return {
13726
+ outcome: "not-ready",
13727
+ observed: false,
13728
+ exitCode: null,
13729
+ readyAfterMs: null,
13730
+ droveForMs: 0,
13731
+ output: "",
13732
+ truncated: false,
13733
+ killedStale,
13734
+ note: `readiness probe never succeeded within ${args.readyTimeout}s (\`${args.ready}\`) \u2014 nothing was driven, so nothing here is evidence about the diff. A slower machine needs a larger --ready-timeout; a probe that can never pass needs a different probe.`
13735
+ };
13736
+ }
13737
+ waitMs(POLL_MS);
13738
+ }
13739
+ }
13740
+ const dir = join16(tmpdir2(), `qwen-review-drive-${server}`);
13741
+ mkdirSync19(dir, { recursive: true });
13742
+ const scriptPath = join16(dir, "drive.sh");
13743
+ const logPath = args.logPath ?? join16(dir, "drive.log");
13744
+ const sentinelPath = join16(dir, "drive.rc");
13745
+ rmSync8(sentinelPath, { force: true });
13746
+ writeFileSync20(scriptPath, wrapScript(args.script, sentinelPath), "utf8");
13747
+ const droveFrom = Date.now();
13748
+ let output = "";
13749
+ let exitCode = null;
13750
+ let outcome = "timed-out";
13751
+ try {
13752
+ const create = tmux(
13753
+ "new-session",
13754
+ "-d",
13755
+ "-c",
13756
+ args.cwd,
13757
+ "bash",
13758
+ "-lc",
13759
+ `bash ${shellQuote(scriptPath)} > ${shellQuote(logPath)} 2>&1`
13760
+ );
13761
+ if (create.status !== 0) {
13762
+ return {
13763
+ outcome: "unavailable",
13764
+ observed: false,
13765
+ exitCode: null,
13766
+ readyAfterMs,
13767
+ droveForMs: 0,
13768
+ output: "",
13769
+ truncated: false,
13770
+ killedStale,
13771
+ note: `tmux could not start a session: ${create.stderr.trim() || "no error text"} \u2014 an environment gap, not a finding`
13772
+ };
13773
+ }
13774
+ const deadline = droveFrom + args.timeout * 1e3;
13775
+ for (; ; ) {
13776
+ output = existsSync10(logPath) ? readFileSync22(logPath, "utf8") : "";
13777
+ exitCode = existsSync10(sentinelPath) ? sentinelExitCode(readFileSync22(sentinelPath, "utf8")) : null;
13778
+ if (exitCode !== null) {
13779
+ outcome = "completed";
13780
+ break;
13781
+ }
13782
+ if (logBytes(logPath) > LOG_MAX_BYTES) {
13783
+ outcome = "overflowed";
13784
+ break;
13785
+ }
13786
+ if (Date.now() >= deadline) break;
13787
+ waitMs(POLL_MS);
13788
+ }
13789
+ } finally {
13790
+ tmux("kill-server");
13791
+ if (!args.logPath) rmSync8(dir, { recursive: true, force: true });
13792
+ }
13793
+ const { text, truncated } = trimCapture(output);
13794
+ const droveForMs = Date.now() - droveFrom;
13795
+ const note = outcome === "overflowed" ? `the drive wrote more than ${Math.round(LOG_MAX_BYTES / 1024 / 1024)} MiB and was stopped \u2014 no exit code is reported because it never gave one, and a run this command had to stop is not evidence about the diff either way. Quieten the script, or have it manage its own output file.` : outcome === "completed" ? `drove for ${Math.round(droveForMs / 1e3)}s and reached its sentinel with exit ${exitCode}${readyAfterMs === null ? "" : ` (ready after ${Math.round(readyAfterMs / 1e3)}s)`}${truncated ? "; the capture was trimmed at the head, so early output is missing" : ""}` : `the drive did not finish within ${args.timeout}s \u2014 the capture below is PARTIAL, and a partial capture is not evidence that the run produced nothing. Raise --timeout, or give the script a smaller job.`;
13796
+ return {
13797
+ outcome,
13798
+ observed: outcome === "completed",
13799
+ exitCode,
13800
+ readyAfterMs,
13801
+ droveForMs,
13802
+ output: text,
13803
+ truncated,
13804
+ killedStale,
13805
+ note
13806
+ };
13807
+ }
13808
+ __name(runDrive, "runDrive");
13809
+ var driveCommand = {
13810
+ command: "drive",
13811
+ describe: "Start something, wait until it is really up, drive it, and capture what it did \u2014 readiness polled rather than slept on, completion proven by a sentinel, cleanup guaranteed",
13812
+ builder: /* @__PURE__ */ __name((yargs) => yargs.option("script", {
13813
+ type: "string",
13814
+ demandOption: true,
13815
+ describe: "Shell script to drive (its exit code is captured)"
13816
+ }).option("cwd", {
13817
+ type: "string",
13818
+ demandOption: true,
13819
+ describe: "Working directory \u2014 usually the PR or base worktree"
13820
+ }).option("ready", {
13821
+ type: "string",
13822
+ describe: "Command polled until it exits 0 before driving (omit only when nothing needs to come up first)"
13823
+ }).option("ready-timeout", {
13824
+ type: "number",
13825
+ default: 60,
13826
+ describe: "Seconds to wait for readiness"
13827
+ }).option("timeout", {
13828
+ type: "number",
13829
+ default: 300,
13830
+ describe: "Seconds to wait for the script to reach its sentinel"
13831
+ }).option("server", {
13832
+ type: "string",
13833
+ default: `qr-${process.pid}`,
13834
+ describe: "tmux server name \u2014 namespaced so runs cannot capture each other"
13835
+ }).option("out", {
13836
+ type: "string",
13837
+ describe: "Write the JSON report here"
13838
+ }), "builder"),
13839
+ handler: /* @__PURE__ */ __name((argv) => {
13840
+ try {
13841
+ const args = argv;
13842
+ const report = runDrive(args);
13843
+ if (args.out) {
13844
+ mkdirSync19(dirname17(resolve17(args.out)), { recursive: true });
13845
+ writeFileSync20(resolve17(args.out), JSON.stringify(report, null, 2));
13846
+ }
13847
+ writeStdoutLine(JSON.stringify(report, null, 2));
13848
+ writeStderrLine(`drive: ${report.note}`);
13849
+ if (!report.observed) process.exitCode = 1;
13850
+ } catch (err) {
13851
+ writeStderrLine(err.message);
13852
+ process.exitCode = 1;
13853
+ }
13854
+ }, "handler")
13855
+ };
13856
+
13857
+ // packages/cli/src/commands/review/mock-provider.ts
13858
+ init_esbuild_shims();
13859
+ import {
13860
+ createServer
13861
+ } from "node:http";
13862
+ import { appendFileSync, mkdirSync as mkdirSync20, writeFileSync as writeFileSync21 } from "node:fs";
13863
+ import { dirname as dirname18, resolve as resolve18 } from "node:path";
13864
+ import { pathToFileURL } from "node:url";
13865
+ function messagesText(body) {
13866
+ const msgs = Array.isArray(body?.["messages"]) ? body["messages"] : [];
13867
+ const one = /* @__PURE__ */ __name((m) => {
13868
+ const c = m?.content;
13869
+ if (typeof c === "string") return c;
13870
+ if (Array.isArray(c))
13871
+ return c.map((p) => String(p?.text ?? "")).join("");
13872
+ return "";
13873
+ }, "one");
13874
+ return msgs.map(one).join("\n");
13875
+ }
13876
+ __name(messagesText, "messagesText");
13877
+ var enc = /* @__PURE__ */ __name((o) => `data: ${JSON.stringify(o)}
13878
+
13879
+ `, "enc");
13880
+ var BODY_MAX_BYTES = 4 * 1024 * 1024;
13881
+ var RECORD_TEXT_MAX = 8 * 1024;
13882
+ function replyProblem(r) {
13883
+ if (r === null || typeof r !== "object")
13884
+ return `responder returned ${r === void 0 ? "undefined" : JSON.stringify(r)}; it must return {text} | {tool, args?} | {status, body?}`;
13885
+ const o = r;
13886
+ if ("status" in o) {
13887
+ const st = o["status"];
13888
+ if (!(typeof st === "number" && Number.isInteger(st) && st >= 100 && st <= 599))
13889
+ return `responder returned a non-HTTP status ${JSON.stringify(st)}`;
13890
+ try {
13891
+ JSON.stringify(o["body"] ?? {});
13892
+ } catch {
13893
+ return "responder returned a status body that cannot be serialised (a circular reference?)";
13894
+ }
13895
+ return null;
13896
+ }
13897
+ if ("tool" in o) {
13898
+ if (typeof o["tool"] !== "string" || o["tool"] === "")
13899
+ return `responder returned an empty or non-string tool name ${JSON.stringify(o["tool"])}`;
13900
+ try {
13901
+ JSON.stringify(o["args"] ?? {});
13902
+ } catch {
13903
+ return "responder returned tool args that cannot be serialised (a circular reference?)";
13904
+ }
13905
+ return null;
13906
+ }
13907
+ if ("text" in o)
13908
+ return typeof o["text"] === "string" ? null : `responder returned a non-string text ${JSON.stringify(o["text"])}`;
13909
+ return `responder returned an object with none of text/tool/status: ${JSON.stringify(o).slice(0, 80)}`;
13910
+ }
13911
+ __name(replyProblem, "replyProblem");
13912
+ function forRecord(v) {
13913
+ return v.length <= RECORD_TEXT_MAX ? v : `${v.slice(0, RECORD_TEXT_MAX)}\u2026[${v.length - RECORD_TEXT_MAX} more characters]`;
13914
+ }
13915
+ __name(forRecord, "forRecord");
13916
+ function forRecordReq(r) {
13917
+ return {
13918
+ method: r.method,
13919
+ path: r.path,
13920
+ wire: r.wire,
13921
+ n: r.n,
13922
+ stream: r.stream,
13923
+ text: forRecord(r.text),
13924
+ bodyKeys: r.body ? Object.keys(r.body) : []
13925
+ };
13926
+ }
13927
+ __name(forRecordReq, "forRecordReq");
13928
+ function chunk(delta, finish = null) {
13929
+ return {
13930
+ id: "chatcmpl-qwen-review-mock",
13931
+ object: "chat.completion.chunk",
13932
+ created: Math.floor(Date.now() / 1e3),
13933
+ model: "qwen-review-mock",
13934
+ choices: [{ index: 0, delta, finish_reason: finish }]
13935
+ };
13936
+ }
13937
+ __name(chunk, "chunk");
13938
+ function chunksFor(reply) {
13939
+ const out = [
13940
+ chunk({ role: "assistant", content: "" })
13941
+ ];
13942
+ if ("tool" in reply) {
13943
+ out.push(
13944
+ chunk({
13945
+ tool_calls: [
13946
+ {
13947
+ index: 0,
13948
+ id: `call_${reply.tool}_1`,
13949
+ type: "function",
13950
+ function: {
13951
+ name: reply.tool,
13952
+ arguments: JSON.stringify(reply.args ?? {})
13953
+ }
13954
+ }
13955
+ ]
13956
+ }),
13957
+ chunk({}, "tool_calls")
13958
+ );
13959
+ return out;
13960
+ }
13961
+ if ("text" in reply) {
13962
+ for (const piece of reply.text.match(/[\s\S]{1,40}/g) ?? [])
13963
+ out.push(chunk({ content: piece }));
12731
13964
  }
13965
+ out.push(chunk({}, "stop"));
13966
+ return out;
13967
+ }
13968
+ __name(chunksFor, "chunksFor");
13969
+ function completionFor(reply) {
13970
+ const message = "tool" in reply ? {
13971
+ role: "assistant",
13972
+ content: null,
13973
+ tool_calls: [
13974
+ {
13975
+ index: 0,
13976
+ id: `call_${reply.tool}_1`,
13977
+ type: "function",
13978
+ function: {
13979
+ name: reply.tool,
13980
+ arguments: JSON.stringify(reply.args ?? {})
13981
+ }
13982
+ }
13983
+ ]
13984
+ } : { role: "assistant", content: "text" in reply ? reply.text : "" };
12732
13985
  return {
12733
- entries,
12734
- netNew,
12735
- shared,
12736
- skippedForBudget,
12737
- note: parts.join(". ") || "nothing to report"
13986
+ id: "chatcmpl-qwen-review-mock",
13987
+ object: "chat.completion",
13988
+ created: Math.floor(Date.now() / 1e3),
13989
+ model: "qwen-review-mock",
13990
+ choices: [
13991
+ {
13992
+ index: 0,
13993
+ message,
13994
+ finish_reason: "tool" in reply ? "tool_calls" : "stop"
13995
+ }
13996
+ ],
13997
+ // 69% of the corpus carried one, and a client that reads token counts
13998
+ // behaves differently without it — an absent usage block is a behaviour
13999
+ // difference the mock introduced, not one the diff did.
14000
+ usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
12738
14001
  };
12739
14002
  }
12740
- __name(runTestDelta, "runTestDelta");
12741
- var testDeltaCommand = {
12742
- command: "test-delta",
12743
- describe: "Rerun the PR side's failed test commands on the base tree and report which failing files are the PR's own (net-new) vs pre-existing (shared)",
12744
- builder: /* @__PURE__ */ __name((yargs) => yargs.option("report", {
14003
+ __name(completionFor, "completionFor");
14004
+ function sseEvent(type, data) {
14005
+ return `event: ${type}
14006
+ data: ${JSON.stringify({ type, ...data })}
14007
+
14008
+ `;
14009
+ }
14010
+ __name(sseEvent, "sseEvent");
14011
+ function anthropicStream(reply) {
14012
+ const usage = {
14013
+ input_tokens: 0,
14014
+ output_tokens: 0,
14015
+ cache_creation_input_tokens: 0,
14016
+ cache_read_input_tokens: 0
14017
+ };
14018
+ let out = sseEvent("message_start", {
14019
+ message: {
14020
+ id: "msg_qwen_review_mock",
14021
+ type: "message",
14022
+ role: "assistant",
14023
+ model: "qwen-review-mock",
14024
+ content: [],
14025
+ stop_reason: null,
14026
+ usage
14027
+ }
14028
+ });
14029
+ if ("tool" in reply) {
14030
+ out += sseEvent("content_block_start", {
14031
+ index: 0,
14032
+ content_block: {
14033
+ type: "tool_use",
14034
+ id: `toolu_${reply.tool}_1`,
14035
+ name: reply.tool,
14036
+ input: {}
14037
+ }
14038
+ });
14039
+ out += sseEvent("content_block_delta", {
14040
+ index: 0,
14041
+ delta: {
14042
+ type: "input_json_delta",
14043
+ partial_json: JSON.stringify(reply.args ?? {})
14044
+ }
14045
+ });
14046
+ out += sseEvent("content_block_stop", { index: 0 });
14047
+ out += sseEvent("message_delta", {
14048
+ delta: { stop_reason: "tool_use", stop_sequence: null },
14049
+ usage: { output_tokens: 0 }
14050
+ });
14051
+ } else {
14052
+ const text = "text" in reply ? reply.text : "";
14053
+ out += sseEvent("content_block_start", {
14054
+ index: 0,
14055
+ content_block: { type: "text", text: "" }
14056
+ });
14057
+ for (const piece of text.match(/[\s\S]{1,40}/g) ?? [])
14058
+ out += sseEvent("content_block_delta", {
14059
+ index: 0,
14060
+ delta: { type: "text_delta", text: piece }
14061
+ });
14062
+ out += sseEvent("content_block_stop", { index: 0 });
14063
+ out += sseEvent("message_delta", {
14064
+ delta: { stop_reason: "end_turn", stop_sequence: null },
14065
+ usage: { output_tokens: 0 }
14066
+ });
14067
+ }
14068
+ return out + sseEvent("message_stop", {});
14069
+ }
14070
+ __name(anthropicStream, "anthropicStream");
14071
+ function anthropicMessage(reply) {
14072
+ const content = "tool" in reply ? [
14073
+ {
14074
+ type: "tool_use",
14075
+ id: `toolu_${reply.tool}_1`,
14076
+ name: reply.tool,
14077
+ input: reply.args ?? {}
14078
+ }
14079
+ ] : [{ type: "text", text: "text" in reply ? reply.text : "" }];
14080
+ return {
14081
+ id: "msg_qwen_review_mock",
14082
+ type: "message",
14083
+ role: "assistant",
14084
+ model: "qwen-review-mock",
14085
+ content,
14086
+ stop_reason: "tool" in reply ? "tool_use" : "end_turn",
14087
+ stop_sequence: null,
14088
+ usage: {
14089
+ input_tokens: 0,
14090
+ output_tokens: 0,
14091
+ cache_creation_input_tokens: 0,
14092
+ cache_read_input_tokens: 0
14093
+ }
14094
+ };
14095
+ }
14096
+ __name(anthropicMessage, "anthropicMessage");
14097
+ function anthropicText(body) {
14098
+ const sys = body?.["system"];
14099
+ const sysText = typeof sys === "string" ? sys : Array.isArray(sys) ? sys.map((b) => String(b?.text ?? "")).join("") : "";
14100
+ const rest = messagesText(body);
14101
+ return sysText && rest ? `${sysText}
14102
+ ${rest}` : sysText || rest;
14103
+ }
14104
+ __name(anthropicText, "anthropicText");
14105
+ var DEFAULT_REPLY = { text: "ok" };
14106
+ async function startMockProvider(args, respondOverride) {
14107
+ let respond = respondOverride ?? (() => DEFAULT_REPLY);
14108
+ if (!respondOverride && args.responder) {
14109
+ const mod = await import(pathToFileURL(resolve18(args.responder)).href);
14110
+ const fn = mod.respond ?? mod.default;
14111
+ if (typeof fn !== "function") {
14112
+ throw new Error(
14113
+ `mock-provider: ${args.responder} exports no \`respond\` function \u2014 a responder module must export \`respond(req)\` (or a default export of the same shape)`
14114
+ );
14115
+ }
14116
+ respond = fn;
14117
+ }
14118
+ const logPath = resolve18(args.log);
14119
+ mkdirSync20(dirname18(logPath), { recursive: true });
14120
+ writeFileSync21(logPath, "");
14121
+ let n = 0;
14122
+ const record = /* @__PURE__ */ __name((entry) => appendFileSync(logPath, `${JSON.stringify(entry)}
14123
+ `), "record");
14124
+ const server = createServer((req, res) => {
14125
+ const parts = [];
14126
+ let size = 0;
14127
+ let tooBig = false;
14128
+ req.on("data", (d) => {
14129
+ size += d.length;
14130
+ if (size > BODY_MAX_BYTES) {
14131
+ tooBig = true;
14132
+ parts.length = 0;
14133
+ return;
14134
+ }
14135
+ parts.push(d);
14136
+ });
14137
+ req.on("end", () => {
14138
+ if (tooBig) {
14139
+ appendFileSync(
14140
+ logPath,
14141
+ `${JSON.stringify({ t: Date.now(), method: req.method, path: req.url, refused: "body-too-large", bytes: size })}
14142
+ `
14143
+ );
14144
+ res.writeHead(413, { "Content-Type": "application/json" });
14145
+ res.end(
14146
+ JSON.stringify({
14147
+ error: `request body exceeded ${BODY_MAX_BYTES} bytes; refused rather than truncated, because a truncated body is a different request`
14148
+ })
14149
+ );
14150
+ return;
14151
+ }
14152
+ void (async () => {
14153
+ const raw = Buffer.concat(parts).toString("utf8");
14154
+ let body = null;
14155
+ try {
14156
+ body = raw ? JSON.parse(raw) : null;
14157
+ } catch {
14158
+ body = null;
14159
+ }
14160
+ const path3 = req.url ?? "/";
14161
+ const isModels = path3.startsWith("/v1/models");
14162
+ const chat = path3.includes("/chat/completions") || path3.includes("/v1/messages");
14163
+ const willAsk = !isModels && chat && (req.method ?? "GET") === "POST" && body !== null;
14164
+ const wire = path3.includes("/v1/messages") ? "anthropic" : "openai";
14165
+ if (willAsk) n += 1;
14166
+ const mreq = {
14167
+ method: req.method ?? "GET",
14168
+ path: path3,
14169
+ body,
14170
+ wire,
14171
+ text: wire === "anthropic" ? anthropicText(body) : messagesText(body),
14172
+ stream: body?.["stream"] === true,
14173
+ // Null for anything the responder was never asked about, so the
14174
+ // record cannot be read as "the responder saw this".
14175
+ n: willAsk ? n : null
14176
+ };
14177
+ if (!isModels) {
14178
+ const why = !chat ? `this mock serves /v1/chat/completions and /v1/messages only; ${path3} is not one of them` : mreq.method !== "POST" ? `${mreq.method} is not how either chat endpoint is called; use POST` : body === null ? "the request body was empty or not JSON, so there is nothing to answer" : null;
14179
+ if (why) {
14180
+ record({ t: Date.now(), ...forRecordReq(mreq), refused: why });
14181
+ res.writeHead(400, { "Content-Type": "application/json" });
14182
+ res.end(JSON.stringify({ error: why }));
14183
+ return;
14184
+ }
14185
+ }
14186
+ if (isModels) {
14187
+ record({ t: Date.now(), ...forRecordReq(mreq), reply: "models" });
14188
+ res.writeHead(200, { "Content-Type": "application/json" });
14189
+ res.end(
14190
+ JSON.stringify({
14191
+ object: "list",
14192
+ data: [{ id: "qwen-review-mock", object: "model" }]
14193
+ })
14194
+ );
14195
+ return;
14196
+ }
14197
+ let reply;
14198
+ try {
14199
+ reply = await respond(mreq);
14200
+ const problem = replyProblem(reply);
14201
+ if (problem) throw new Error(problem);
14202
+ } catch (err) {
14203
+ reply = {
14204
+ status: 500,
14205
+ body: { error: String(err.message) }
14206
+ };
14207
+ }
14208
+ record({ t: Date.now(), ...forRecordReq(mreq), reply });
14209
+ if ("status" in reply) {
14210
+ res.writeHead(reply.status, { "Content-Type": "application/json" });
14211
+ res.end(JSON.stringify(reply.body ?? {}));
14212
+ return;
14213
+ }
14214
+ if (!mreq.stream) {
14215
+ res.writeHead(200, { "Content-Type": "application/json" });
14216
+ res.end(
14217
+ JSON.stringify(
14218
+ wire === "anthropic" ? anthropicMessage(reply) : completionFor(reply)
14219
+ )
14220
+ );
14221
+ return;
14222
+ }
14223
+ res.writeHead(200, {
14224
+ "Content-Type": "text/event-stream",
14225
+ "Cache-Control": "no-cache",
14226
+ Connection: "keep-alive"
14227
+ });
14228
+ if (wire === "anthropic") {
14229
+ res.write(anthropicStream(reply));
14230
+ } else {
14231
+ for (const c of chunksFor(reply)) res.write(enc(c));
14232
+ res.write("data: [DONE]\n\n");
14233
+ }
14234
+ res.end();
14235
+ })();
14236
+ });
14237
+ });
14238
+ await new Promise((ok) => server.listen(0, "127.0.0.1", ok));
14239
+ const addr = server.address();
14240
+ const port = typeof addr === "object" && addr ? addr.port : 0;
14241
+ return {
14242
+ report: {
14243
+ port,
14244
+ baseUrl: `http://127.0.0.1:${port}/v1`,
14245
+ logPath,
14246
+ note: `mock provider on port ${port}; every request is appended to ${logPath} as JSONL \u2014 diff those between two trees and the difference is evidence`
14247
+ },
14248
+ close: /* @__PURE__ */ __name(() => new Promise((ok) => {
14249
+ server.close(() => ok());
14250
+ }), "close")
14251
+ };
14252
+ }
14253
+ __name(startMockProvider, "startMockProvider");
14254
+ var mockProviderCommand = {
14255
+ command: "mock-provider",
14256
+ describe: "Serve OpenAI (/v1/chat/completions) and Anthropic (/v1/messages) endpoints for the review to drive the real product against, recording every request as JSONL \u2014 the protocol is provided, the answers are yours; for any other kind of upstream, start it yourself and use `drive`",
14257
+ builder: /* @__PURE__ */ __name((yargs) => yargs.option("responder", {
12745
14258
  type: "string",
12746
- demandOption: true,
12747
- describe: "Agent 7's build-test report (its failed commands and outputs)"
12748
- }).option("baseline", {
14259
+ describe: "Module exporting `respond(req)` returning {text} | {tool,args} | {status,body}"
14260
+ }).option("log", {
12749
14261
  type: "string",
12750
14262
  demandOption: true,
12751
- describe: "The BUILT base tree from `qwen review base-tree`"
12752
- }).option("pr-worktree", {
12753
- type: "string",
12754
- describe: "The PR worktree the report's failures were produced in \u2014 its root is stripped so both sides compare as repo-relative paths"
12755
- }).option("out", { type: "string", describe: "Write the JSON report here" }).option("timeout", {
14263
+ describe: "Where to append the JSONL request record"
14264
+ }).option("ttl", {
12756
14265
  type: "number",
12757
- default: 300,
12758
- describe: "Per-command deadline in seconds, as build-test"
14266
+ default: 600,
14267
+ describe: "Seconds to serve before shutting down on its own"
14268
+ }).option("out", {
14269
+ type: "string",
14270
+ describe: "Write the JSON report here"
12759
14271
  }), "builder"),
12760
- handler: /* @__PURE__ */ __name((argv) => {
12761
- const args = argv;
12762
- const report = runTestDelta(args);
12763
- if (args.out) {
12764
- mkdirSync16(dirname15(resolve15(args.out)), { recursive: true });
12765
- writeFileSync18(resolve15(args.out), JSON.stringify(report, null, 2));
14272
+ // `async`, and the returned promise is what keeps the process alive. A
14273
+ // fire-and-forget `void (async () => …)()` here produced a command that
14274
+ // printed nothing at all and exited: yargs returned, node found no pending
14275
+ // work it could see, and the server it had just bound went with it. Measured
14276
+ // — both streams empty, exit 0, a mock that was never reachable.
14277
+ handler: /* @__PURE__ */ __name(async (argv) => {
14278
+ try {
14279
+ const args = argv;
14280
+ const { report, close } = await startMockProvider(args);
14281
+ if (args.out) {
14282
+ mkdirSync20(dirname18(resolve18(args.out)), { recursive: true });
14283
+ writeFileSync21(resolve18(args.out), JSON.stringify(report, null, 2));
14284
+ }
14285
+ writeStdoutLine(JSON.stringify(report, null, 2));
14286
+ writeStderrLine(`mock-provider: ${report.note}`);
14287
+ await new Promise((done) => {
14288
+ setTimeout(done, Math.max(1, args.ttl) * 1e3);
14289
+ });
14290
+ await close();
14291
+ } catch (err) {
14292
+ writeStderrLine(err.message);
14293
+ process.exitCode = 1;
12766
14294
  }
12767
- writeStdoutLine(JSON.stringify(report, null, 2));
12768
- writeStderrLine(`test-delta: ${report.note}`);
12769
14295
  }, "handler")
12770
14296
  };
12771
14297
 
12772
14298
  // packages/cli/src/commands/review/extract-step.ts
12773
14299
  init_esbuild_shims();
12774
14300
  var import_yaml = __toESM(require_dist(), 1);
12775
- import { mkdirSync as mkdirSync17, readFileSync as readFileSync20, writeFileSync as writeFileSync19, chmodSync } from "node:fs";
12776
- import { dirname as dirname16, resolve as resolve16 } from "node:path";
14301
+ import { mkdirSync as mkdirSync21, readFileSync as readFileSync23, writeFileSync as writeFileSync22, chmodSync } from "node:fs";
14302
+ import { dirname as dirname19, resolve as resolve19 } from "node:path";
12777
14303
  function expressionsOf(...texts) {
12778
14304
  const seen = /* @__PURE__ */ new Set();
12779
14305
  for (const t2 of texts) {
@@ -12986,8 +14512,8 @@ function nearestScoped(...values) {
12986
14512
  __name(nearestScoped, "nearestScoped");
12987
14513
  function runDefaultsOf(container) {
12988
14514
  const defaults = container?.defaults;
12989
- const run3 = defaults && typeof defaults === "object" ? defaults.run : void 0;
12990
- return run3 && typeof run3 === "object" ? run3 : {};
14515
+ const run4 = defaults && typeof defaults === "object" ? defaults.run : void 0;
14516
+ return run4 && typeof run4 === "object" ? run4 : {};
12991
14517
  }
12992
14518
  __name(runDefaultsOf, "runDefaultsOf");
12993
14519
  function envValue(v) {
@@ -13017,10 +14543,10 @@ function nearestFirst(env, sources) {
13017
14543
  }
13018
14544
  __name(nearestFirst, "nearestFirst");
13019
14545
  function runExtractStep(args) {
13020
- const wfPath = resolve16(args.workflow);
14546
+ const wfPath = resolve19(args.workflow);
13021
14547
  let text;
13022
14548
  try {
13023
- text = readFileSync20(wfPath, "utf8");
14549
+ text = readFileSync23(wfPath, "utf8");
13024
14550
  } catch (err) {
13025
14551
  throw new Error(
13026
14552
  `extract-step: cannot read ${args.workflow}: ${err.message}`
@@ -13086,8 +14612,8 @@ function runExtractStep(args) {
13086
14612
  const workingDirectory = workingDir?.value;
13087
14613
  const script = step.run;
13088
14614
  const stepLabel = String(step.name ?? step.id ?? index);
13089
- const outPath = resolve16(args.out);
13090
- mkdirSync17(dirname16(outPath), { recursive: true });
14615
+ const outPath = resolve19(args.out);
14616
+ mkdirSync21(dirname19(outPath), { recursive: true });
13091
14617
  const header = [
13092
14618
  `#!/usr/bin/env ${shellCommand}`,
13093
14619
  ...commentLines(
@@ -13111,7 +14637,7 @@ function runExtractStep(args) {
13111
14637
  ),
13112
14638
  ""
13113
14639
  ].join("\n");
13114
- writeFileSync19(outPath, header + script + (script.endsWith("\n") ? "" : "\n"));
14640
+ writeFileSync22(outPath, header + script + (script.endsWith("\n") ? "" : "\n"));
13115
14641
  chmodSync(outPath, 493);
13116
14642
  return {
13117
14643
  workflow: args.workflow,
@@ -13136,177 +14662,65 @@ function runExtractStep(args) {
13136
14662
  scriptPath: outPath
13137
14663
  };
13138
14664
  }
13139
- __name(runExtractStep, "runExtractStep");
13140
- var extractStepCommand = {
13141
- command: "extract-step",
13142
- describe: "Extract one workflow step's run: script, verbatim, into an executable \u2014 with its env and ${{ }} stub list as metadata",
13143
- builder: /* @__PURE__ */ __name((yargs) => yargs.option("workflow", {
13144
- type: "string",
13145
- demandOption: true,
13146
- describe: "Path to the workflow YAML (in the tree being reviewed)"
13147
- }).option("job", { type: "string", demandOption: true, describe: "Job id" }).option("step", {
13148
- type: "string",
13149
- demandOption: true,
13150
- describe: "Step name, id, or 0-based index within the job (an all-digit value is always read as an index)"
13151
- }).option("out", {
13152
- type: "string",
13153
- demandOption: true,
13154
- describe: "Where to write the executable script"
13155
- }), "builder"),
13156
- handler: /* @__PURE__ */ __name((argv) => {
13157
- try {
13158
- const meta = runExtractStep(argv);
13159
- writeStdoutLine(JSON.stringify(meta, null, 2));
13160
- const inherited = Object.values(meta.envSources).filter(
13161
- (scope) => scope !== "step"
13162
- ).length;
13163
- writeStderrLine(
13164
- `extract-step: wrote ${meta.scriptPath} (${meta.expressions.length} \${{ }} site(s) to stub, ${Object.keys(meta.env).length} env var(s), ${inherited} inherited from job/workflow, invokes: ${meta.invokes.join(", ") || "(none detected)"})`
13165
- );
13166
- } catch (err) {
13167
- writeStderrLine(err.message);
13168
- process.exitCode = 1;
13169
- }
13170
- }, "handler")
13171
- };
13172
-
13173
- // packages/cli/src/commands/review/submit.ts
13174
- init_esbuild_shims();
13175
- import { mkdirSync as mkdirSync19, readFileSync as readFileSync21 } from "node:fs";
13176
-
13177
- // packages/cli/src/commands/review/lib/receipt.ts
13178
- init_esbuild_shims();
13179
- function parseReceiptIds(raw) {
13180
- let value;
13181
- try {
13182
- value = JSON.parse(raw);
13183
- } catch {
13184
- return [];
13185
- }
13186
- if (value === null || typeof value !== "object") return [];
13187
- const parsed = value;
13188
- const ids = Array.isArray(parsed.reviewIds) ? parsed.reviewIds : typeof parsed.reviewId === "number" ? [parsed.reviewId] : [];
13189
- return ids.filter((n) => typeof n === "number");
13190
- }
13191
- __name(parseReceiptIds, "parseReceiptIds");
13192
-
13193
- // packages/cli/src/services/skill-args-file.ts
13194
- init_esbuild_shims();
13195
- import {
13196
- mkdirSync as mkdirSync18,
13197
- openSync as openSync2,
13198
- writeSync,
13199
- closeSync as closeSync2,
13200
- rmSync as rmSync7,
13201
- lstatSync as lstatSync3,
13202
- existsSync as existsSync8,
13203
- constants
13204
- } from "node:fs";
13205
- import { join as join15 } from "node:path";
13206
- var debugLogger3 = createDebugLogger("SKILL_ARGS_FILE");
13207
- var SKILL_ARGS_DIR = join15(".qwen", "tmp");
13208
- function safe(s) {
13209
- return s.replace(/[^A-Za-z0-9._-]/g, "_");
13210
- }
13211
- __name(safe, "safe");
13212
- function currentSessionId() {
13213
- return sessionIdContext.getStore()?.trim() || process.env["QWEN_CODE_SESSION_ID"]?.trim() || "";
13214
- }
13215
- __name(currentSessionId, "currentSessionId");
13216
- function skillArgsDir(sessionId = currentSessionId()) {
13217
- return sessionId ? join15(SKILL_ARGS_DIR, `s-${safe(sessionId)}`) : SKILL_ARGS_DIR;
13218
- }
13219
- __name(skillArgsDir, "skillArgsDir");
13220
- function skillArgsPath(skillName, sessionId = currentSessionId()) {
13221
- return join15(
13222
- skillArgsDir(sessionId),
13223
- `qwen-skill-args-${safe(skillName)}.txt`
13224
- );
13225
- }
13226
- __name(skillArgsPath, "skillArgsPath");
13227
- function writeSkillArgs(skillName, args) {
13228
- const path3 = skillArgsPath(skillName);
13229
- try {
13230
- const dir = skillArgsDir();
13231
- mkdirSync18(dir, { recursive: true });
13232
- if (lstatSync3(dir).isSymbolicLink()) {
13233
- debugLogger3.warn(
13234
- `Skill args directory ${dir} is a symlink; refusing to write through it.`
13235
- );
13236
- return null;
13237
- }
13238
- if (lstatSync3(path3, { throwIfNoEntry: false })?.isSymbolicLink()) {
13239
- debugLogger3.warn(
13240
- `Skill args file ${path3} is a symlink; refusing to write through it.`
13241
- );
13242
- return null;
13243
- }
13244
- const fd = openSync2(
13245
- path3,
13246
- constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW,
13247
- 384
13248
- );
13249
- try {
13250
- const buf = Buffer.from(args, "utf8");
13251
- let off = 0;
13252
- while (off < buf.length) {
13253
- off += writeSync(fd, buf, off, buf.length - off);
13254
- }
13255
- } finally {
13256
- closeSync2(fd);
13257
- }
13258
- return path3;
13259
- } catch (err) {
13260
- debugLogger3.warn(
13261
- `Could not write skill args to ${path3}: ${err.message}`
13262
- );
13263
- return null;
13264
- }
13265
- }
13266
- __name(writeSkillArgs, "writeSkillArgs");
13267
- function clearSkillArgs(skillName) {
13268
- const path3 = skillArgsPath(skillName);
13269
- try {
13270
- rmSync7(path3, { force: true });
13271
- } catch (err) {
13272
- debugLogger3.warn(
13273
- `Could not clear skill args for ${skillName}: ${err.message}`
13274
- );
13275
- return false;
13276
- }
13277
- if (existsSync8(path3)) {
13278
- debugLogger3.warn(`Skill args for ${skillName} survived removal at ${path3}`);
13279
- return false;
13280
- }
13281
- return true;
13282
- }
13283
- __name(clearSkillArgs, "clearSkillArgs");
13284
- function staleArgsWarning() {
13285
- return `
14665
+ __name(runExtractStep, "runExtractStep");
14666
+ var extractStepCommand = {
14667
+ command: "extract-step",
14668
+ describe: "Extract one workflow step's run: script, verbatim, into an executable \u2014 with its env and ${{ }} stub list as metadata",
14669
+ builder: /* @__PURE__ */ __name((yargs) => yargs.option("workflow", {
14670
+ type: "string",
14671
+ demandOption: true,
14672
+ describe: "Path to the workflow YAML (in the tree being reviewed)"
14673
+ }).option("job", { type: "string", demandOption: true, describe: "Job id" }).option("step", {
14674
+ type: "string",
14675
+ demandOption: true,
14676
+ describe: "Step name, id, or 0-based index within the job (an all-digit value is always read as an index)"
14677
+ }).option("out", {
14678
+ type: "string",
14679
+ demandOption: true,
14680
+ describe: "Where to write the executable script"
14681
+ }), "builder"),
14682
+ handler: /* @__PURE__ */ __name((argv) => {
14683
+ try {
14684
+ const meta = runExtractStep(argv);
14685
+ writeStdoutLine(JSON.stringify(meta, null, 2));
14686
+ const inherited = Object.values(meta.envSources).filter(
14687
+ (scope) => scope !== "step"
14688
+ ).length;
14689
+ writeStderrLine(
14690
+ `extract-step: wrote ${meta.scriptPath} (${meta.expressions.length} \${{ }} site(s) to stub, ${Object.keys(meta.env).length} env var(s), ${inherited} inherited from job/workflow, invokes: ${meta.invokes.join(", ") || "(none detected)"})`
14691
+ );
14692
+ } catch (err) {
14693
+ writeStderrLine(err.message);
14694
+ process.exitCode = 1;
14695
+ }
14696
+ }, "handler")
14697
+ };
13286
14698
 
13287
- <skill-args-stale>A previous invocation's argument record could not be removed, so it is still on disk and still names whatever it named. This invocation supplied NO arguments. Do not treat that stale record as this run's authorisation: this run has none, and must not post.</skill-args-stale>
13288
- `;
13289
- }
13290
- __name(staleArgsWarning, "staleArgsWarning");
13291
- function skillArgsNote(path3, args) {
13292
- return `
14699
+ // packages/cli/src/commands/review/submit.ts
14700
+ init_esbuild_shims();
14701
+ import { mkdirSync as mkdirSync22, readFileSync as readFileSync24 } from "node:fs";
13293
14702
 
13294
- Your invocation arguments have been written verbatim to a session-private file. Its exact path is below \u2014 use it wherever these instructions say to read the args file (e.g. \`< '${path3}'\`), and do not retype the arguments, which is how they get mistyped.
13295
- <skill-args-file>${path3}</skill-args-file>
13296
- <skill-args>${args}</skill-args>
13297
- `;
14703
+ // packages/cli/src/commands/review/lib/receipt.ts
14704
+ init_esbuild_shims();
14705
+ function parseReceiptIds(raw) {
14706
+ let value;
14707
+ try {
14708
+ value = JSON.parse(raw);
14709
+ } catch {
14710
+ return [];
14711
+ }
14712
+ if (value === null || typeof value !== "object") return [];
14713
+ const parsed = value;
14714
+ const ids = Array.isArray(parsed.reviewIds) ? parsed.reviewIds : typeof parsed.reviewId === "number" ? [parsed.reviewId] : [];
14715
+ return ids.filter((n) => typeof n === "number");
13298
14716
  }
13299
- __name(skillArgsNote, "skillArgsNote");
14717
+ __name(parseReceiptIds, "parseReceiptIds");
13300
14718
 
13301
14719
  // packages/cli/src/commands/review/submit.ts
13302
- function defaultSkillArgsPath() {
13303
- return skillArgsPath("review");
13304
- }
13305
- __name(defaultSkillArgsPath, "defaultSkillArgsPath");
13306
14720
  var EVENTS = /* @__PURE__ */ new Set(["APPROVE", "REQUEST_CHANGES", "COMMENT"]);
13307
14721
  function readReceiptIds(receiptPath) {
13308
14722
  try {
13309
- return parseReceiptIds(readFileSync21(receiptPath, "utf8"));
14723
+ return parseReceiptIds(readFileSync24(receiptPath, "utf8"));
13310
14724
  } catch {
13311
14725
  return [];
13312
14726
  }
@@ -13317,60 +14731,20 @@ function isDiffLine(n) {
13317
14731
  }
13318
14732
  __name(isDiffLine, "isDiffLine");
13319
14733
  function authorization(args) {
13320
- if (args.userAuthorized) {
13321
- return { ok: true, why: "the user asked for this review to be published" };
13322
- }
13323
- const sessionScoped = defaultSkillArgsPath();
13324
- const path3 = currentSessionId() === "" && args.skillArgs ? args.skillArgs : sessionScoped;
13325
- let raw;
13326
- try {
13327
- raw = readFileSync21(path3, "utf8");
13328
- } catch {
13329
- return {
13330
- ok: false,
13331
- why: `no review arguments were recorded at ${path3}, so this run cannot show that \`--comment\` was requested`
13332
- };
13333
- }
13334
- const verdict = parseReviewArgs(raw);
13335
- if (!verdict.comment.effective) {
13336
- return {
13337
- ok: false,
13338
- why: `\`--comment\` was not in the review arguments (${JSON.stringify(raw.trim())})`
13339
- };
13340
- }
13341
- const t2 = verdict.target;
13342
- const authorisedPr = t2.type === "pr-number" || t2.type === "pr-url" ? t2.number : void 0;
13343
- if (authorisedPr === void 0) {
13344
- return {
13345
- ok: false,
13346
- why: `the review arguments (${JSON.stringify(raw.trim())}) do not name a pull request, so they cannot authorise posting to one`
13347
- };
13348
- }
13349
- if (authorisedPr !== args.pr) {
13350
- return {
13351
- ok: false,
13352
- why: `the review arguments authorise pull request #${authorisedPr}, but this submission targets #${args.pr}`
13353
- };
13354
- }
13355
- if (t2.type === "pr-url") {
13356
- const authorisedRepo = `${t2.owner}/${t2.repo}`;
13357
- if (authorisedRepo.toLowerCase() !== args.repo.toLowerCase()) {
13358
- return {
13359
- ok: false,
13360
- why: `the review arguments authorise ${authorisedRepo}, but this submission targets ${args.repo}`
13361
- };
13362
- }
13363
- if (args.host && t2.host.toLowerCase() !== args.host.toLowerCase()) {
13364
- return {
13365
- ok: false,
13366
- why: `the review arguments authorise ${t2.host}, but this submission targets ${args.host}`
13367
- };
13368
- }
13369
- }
13370
- return {
13371
- ok: true,
13372
- why: `\`--comment\` was in the review arguments for #${authorisedPr}`
13373
- };
14734
+ return reviewWriteAuthorization({
14735
+ userAuthorized: args.userAuthorized,
14736
+ skillArgs: args.skillArgs,
14737
+ pr: args.pr,
14738
+ repo: args.repo,
14739
+ // The EFFECTIVE host, not merely the flag: with --host absent the gh
14740
+ // child inherits an operator-exported GH_HOST, so that is where this
14741
+ // write would route — and what the gate must bind. Same resolution as
14742
+ // publish-assets.
14743
+ // `|| undefined`, not `??`: an exported-but-empty GH_HOST ("" survives
14744
+ // `??`, being non-nullish) must read as "no host", not as a host named
14745
+ // "" that fails every comparison.
14746
+ host: args.host ?? (process.env["GH_HOST"]?.trim() || void 0)
14747
+ });
13374
14748
  }
13375
14749
  __name(authorization, "authorization");
13376
14750
  function compose(payload, cliVersion) {
@@ -13419,12 +14793,12 @@ function structuralProblems(payload) {
13419
14793
  return problems;
13420
14794
  }
13421
14795
  __name(structuralProblems, "structuralProblems");
13422
- function inconsistencies(payload, event) {
14796
+ function inconsistencies(payload, event2) {
13423
14797
  const problems = [];
13424
14798
  const comments = payload.comments ?? [];
13425
- if (!EVENTS.has(event)) {
14799
+ if (!EVENTS.has(event2)) {
13426
14800
  problems.push(
13427
- `computed \`event\` is ${JSON.stringify(event)}; GitHub accepts only ${[...EVENTS].join(", ")}`
14801
+ `computed \`event\` is ${JSON.stringify(event2)}; GitHub accepts only ${[...EVENTS].join(", ")}`
13428
14802
  );
13429
14803
  }
13430
14804
  comments.forEach((c, i) => {
@@ -13481,7 +14855,7 @@ function runSubmit(args, cliVersion = "unknown") {
13481
14855
  }
13482
14856
  let payload;
13483
14857
  try {
13484
- payload = JSON.parse(readFileSync21(args.review, "utf8"));
14858
+ payload = JSON.parse(readFileSync24(args.review, "utf8"));
13485
14859
  } catch (err) {
13486
14860
  throw new Error(
13487
14861
  `Cannot read review JSON ${args.review}: ${err.message}`
@@ -13506,18 +14880,18 @@ Posting is a public, irreversible write, and this run has no authorisation for o
13506
14880
  ` + structural.map((p) => ` - ${p}`).join("\n")
13507
14881
  );
13508
14882
  }
13509
- let event;
14883
+ let event2;
13510
14884
  let body;
13511
14885
  let cappedBy;
13512
14886
  try {
13513
- ({ event, body, cappedBy } = compose(payload, cliVersion));
14887
+ ({ event: event2, body, cappedBy } = compose(payload, cliVersion));
13514
14888
  } catch (err) {
13515
14889
  throw new Error(
13516
14890
  `The review state does not compose into a verdict; refusing to post:
13517
14891
  - ${err.message}`
13518
14892
  );
13519
14893
  }
13520
- const problems = inconsistencies(payload, event);
14894
+ const problems = inconsistencies(payload, event2);
13521
14895
  if (problems.length > 0) {
13522
14896
  throw new Error(
13523
14897
  `The review payload contradicts itself; refusing to post it:
@@ -13526,7 +14900,7 @@ Posting is a public, irreversible write, and this run has no authorisation for o
13526
14900
  }
13527
14901
  const post = {
13528
14902
  commit_id: payload.commit_id,
13529
- event,
14903
+ event: event2,
13530
14904
  body,
13531
14905
  comments: payload.comments ?? []
13532
14906
  };
@@ -13537,7 +14911,7 @@ Posting is a public, irreversible write, and this run has no authorisation for o
13537
14911
  );
13538
14912
  writeStdoutLine(
13539
14913
  JSON.stringify(
13540
- { posted: false, wouldPost: true, target, event, cappedBy },
14914
+ { posted: false, wouldPost: true, target, event: event2, cappedBy },
13541
14915
  null,
13542
14916
  2
13543
14917
  )
@@ -13557,23 +14931,23 @@ Posting is a public, irreversible write, and this run has no authorisation for o
13557
14931
  const receiptPath = tmpFile(`pr-${args.pr}`, "submit-receipt.json");
13558
14932
  const priorIds = readReceiptIds(receiptPath);
13559
14933
  const reviewIds = [.../* @__PURE__ */ new Set([...priorIds, reviewId])];
13560
- mkdirSync19(REVIEW_TMP_DIR, { recursive: true });
14934
+ mkdirSync22(REVIEW_TMP_DIR, { recursive: true });
13561
14935
  atomicWriteFileSync(
13562
14936
  receiptPath,
13563
- `${JSON.stringify({ reviewIds, event, postedAt: (/* @__PURE__ */ new Date()).toISOString() })}
14937
+ `${JSON.stringify({ reviewIds, event: event2, postedAt: (/* @__PURE__ */ new Date()).toISOString() })}
13564
14938
  `
13565
14939
  );
13566
14940
  }
13567
14941
  } catch {
13568
14942
  }
13569
14943
  writeStderrLine(
13570
- `Posted ${event} to ${args.repo}#${args.pr} \u2014 ${auth.why}` + (cappedBy.length ? ` (capped by ${cappedBy.join(", ")})` : "") + "."
14944
+ `Posted ${event2} to ${args.repo}#${args.pr} \u2014 ${auth.why}` + (cappedBy.length ? ` (capped by ${cappedBy.join(", ")})` : "") + "."
13571
14945
  );
13572
14946
  writeStdoutLine(
13573
14947
  JSON.stringify(
13574
14948
  {
13575
14949
  posted: true,
13576
- event,
14950
+ event: event2,
13577
14951
  cappedBy,
13578
14952
  inlineComments: post.comments.length
13579
14953
  },
@@ -13620,20 +14994,20 @@ var submitCommand = {
13620
14994
 
13621
14995
  // packages/cli/src/commands/review/test-efficacy.ts
13622
14996
  init_esbuild_shims();
13623
- import { spawnSync as spawnSync6 } from "node:child_process";
14997
+ import { spawnSync as spawnSync7 } from "node:child_process";
13624
14998
  import {
13625
- mkdirSync as mkdirSync20,
14999
+ mkdirSync as mkdirSync23,
13626
15000
  readdirSync as readdirSync5,
13627
- writeFileSync as writeFileSync20,
13628
- readFileSync as readFileSync22,
13629
- rmSync as rmSync8,
15001
+ writeFileSync as writeFileSync23,
15002
+ readFileSync as readFileSync25,
15003
+ rmSync as rmSync9,
13630
15004
  lstatSync as lstatSync4,
13631
- existsSync as existsSync9,
13632
- statSync as statSync6,
15005
+ existsSync as existsSync11,
15006
+ statSync as statSync8,
13633
15007
  symlinkSync
13634
15008
  } from "node:fs";
13635
15009
  import { createRequire } from "node:module";
13636
- import { dirname as dirname17, join as join16, isAbsolute as isAbsolute3, sep as sep3 } from "node:path";
15010
+ import { dirname as dirname20, join as join17, isAbsolute as isAbsolute3, sep as sep3 } from "node:path";
13637
15011
  var FIXTURE_DIR_RE = /(^|\/)(__fixtures__|__mocks__|__snapshots__|fixtures)\//;
13638
15012
  function planTestEfficacy(files, workspaceGlobs) {
13639
15013
  const tests = files.filter((f) => f.kind === "test").map((f) => f.path);
@@ -13988,6 +15362,73 @@ function collocatedProbe(file, testPaths) {
13988
15362
  });
13989
15363
  }
13990
15364
  __name(collocatedProbe, "collocatedProbe");
15365
+ function heldForRedCollocatedTest(kind, file, probes, greenProbes, baselinePerFile) {
15366
+ const own = collocatedProbe(file, probes);
15367
+ if (!own || greenProbes.includes(own)) return void 0;
15368
+ return collocatedNotGreenDetail(kind, own, baselinePerFile);
15369
+ }
15370
+ __name(heldForRedCollocatedTest, "heldForRedCollocatedTest");
15371
+ function runnerFailureReason(r) {
15372
+ return r.signal || r.error?.code === "ETIMEDOUT" ? "runner-died" : "not-run";
15373
+ }
15374
+ __name(runnerFailureReason, "runnerFailureReason");
15375
+ var ProbeRunFailure = class extends Error {
15376
+ constructor(message, reason) {
15377
+ super(message);
15378
+ this.reason = reason;
15379
+ this.name = "ProbeRunFailure";
15380
+ }
15381
+ static {
15382
+ __name(this, "ProbeRunFailure");
15383
+ }
15384
+ };
15385
+ var BASELINE_REASON = /* @__PURE__ */ new Set([
15386
+ "no-output",
15387
+ "not-in-results",
15388
+ "no-tests",
15389
+ "all-skipped"
15390
+ ]);
15391
+ var REASON_PHRASE = {
15392
+ // The suite ran and read green, but the control proved the runner could not
15393
+ // have reported otherwise.
15394
+ "control-failed": "it read green there, but the positive control failed, so nothing could have turned that run red",
15395
+ // Nothing ran: no worktree, the checkout failed, or the runner could not be
15396
+ // started at all — a spawn that fails with ENOENT never gets to a test.
15397
+ "not-run": "no probe suite ran for it at all there",
15398
+ // Started and did not survive. How much of it ran before that is unknown.
15399
+ "runner-died": "the probe suite was started there and did not survive (killed at the deadline, or by a signal)",
15400
+ // Nothing is known about collection here — the runner never produced output
15401
+ // to read. Saying "collected no tests" would be the same invented cause this
15402
+ // function exists to stop, one layer down: a reader sent after a compile
15403
+ // error that is not there, while the runner itself is what fell over.
15404
+ "no-output": "the runner produced no parseable output there, so nothing at all is known about it",
15405
+ // The run answered, and this file was not in the answer. A compile or import
15406
+ // error looks like this — and so does a path that failed to match, which the
15407
+ // boundary and case rules in `classifyProbeRun` exist because of. Naming one
15408
+ // would be picking between two live possibilities.
15409
+ "not-in-results": "the run produced results there but none for it \u2014 which a compile or import error looks like, and so does a path that did not match",
15410
+ "no-tests": "it collected no tests there \u2014 the shape a compile or import error in the probe tree takes",
15411
+ "all-skipped": "it collected tests there but executed none of them"
15412
+ };
15413
+ var NOT_REPORTED = "the baseline did not report it";
15414
+ function collocatedNotGreenDetail(kind, probe, baselinePerFile) {
15415
+ const entry = baselinePerFile.find((p) => p.file === probe);
15416
+ const reason = entry === void 0 ? NOT_REPORTED : entry.verdict === "inconclusive" ? (
15417
+ // Three reasons are set on the run-level `results` array and never by
15418
+ // `classifyProbeRun`, so a baseline entry cannot carry them. Reaching
15419
+ // one means the caller passed something other than the baseline —
15420
+ // say that, rather than emit "did not run green … it read green".
15421
+ BASELINE_REASON.has(entry.reason) ? REASON_PHRASE[entry.reason] : `the baseline did not classify it (${REASON_PHRASE[entry.reason]}), so this explanation does not apply to it`
15422
+ ) : entry.verdict === "gated" ? "it was RED there" : (
15423
+ // `inert` IS green, so the sentence this function builds does not
15424
+ // apply to it. Say that, rather than produce a fluent claim that
15425
+ // contradicts the measurement — the caller is what is wrong here.
15426
+ "the baseline reported it GREEN, so this explanation does not apply to it"
15427
+ );
15428
+ const what = kind === "mutant" ? "the statement" : "the hunk";
15429
+ return `this ${kind}'s collocated test ${probe} did not run green in the unmutated baseline \u2014 ${reason}, so the remaining probes passing cannot show ${what} is uncovered`;
15430
+ }
15431
+ __name(collocatedNotGreenDetail, "collocatedNotGreenDetail");
13991
15432
  function hasCollocatedNewTest(file, testPaths) {
13992
15433
  return collocatedProbe(file, testPaths) !== void 0;
13993
15434
  }
@@ -14018,6 +15459,7 @@ function classifyProbeRun(exitCode, stdout, probes, stderr = "") {
14018
15459
  return probes.map((file) => ({
14019
15460
  file,
14020
15461
  verdict: "inconclusive",
15462
+ reason: "no-output",
14021
15463
  detail: `runner produced no parseable JSON (exit ${exitCode})${why ? `: ${why}` : ""}`
14022
15464
  }));
14023
15465
  }
@@ -14032,10 +15474,19 @@ function classifyProbeRun(exitCode, stdout, probes, stderr = "") {
14032
15474
  const assertions = result?.assertionResults ?? [];
14033
15475
  const failed = assertions.filter((a) => a.status === "failed").length;
14034
15476
  const passed = assertions.filter((a) => a.status === "passed").length;
14035
- if (!result || assertions.length === 0) {
15477
+ if (!result) {
15478
+ return {
15479
+ file,
15480
+ verdict: "inconclusive",
15481
+ reason: "not-in-results",
15482
+ detail: `the run produced results but none for this file (run exit ${exitCode}) \u2014 a compile or import error looks like this, and so does a path that did not match; not evidence either way`
15483
+ };
15484
+ }
15485
+ if (assertions.length === 0) {
14036
15486
  return {
14037
15487
  file,
14038
15488
  verdict: "inconclusive",
15489
+ reason: "no-tests",
14039
15490
  detail: `collected no tests with the source reverted (run exit ${exitCode}) \u2014 likely a compile or import error, which is not evidence either way`
14040
15491
  };
14041
15492
  }
@@ -14050,6 +15501,7 @@ function classifyProbeRun(exitCode, stdout, probes, stderr = "") {
14050
15501
  return {
14051
15502
  file,
14052
15503
  verdict: "inconclusive",
15504
+ reason: "all-skipped",
14053
15505
  detail: `${assertions.length} test(s) collected but none executed with the source reverted (all skipped) \u2014 not evidence either way`
14054
15506
  };
14055
15507
  }
@@ -14062,7 +15514,7 @@ function classifyProbeRun(exitCode, stdout, probes, stderr = "") {
14062
15514
  }
14063
15515
  __name(classifyProbeRun, "classifyProbeRun");
14064
15516
  function git3(cwd, ...args) {
14065
- const r = spawnSync6("git", args, { cwd, encoding: "utf8" });
15517
+ const r = spawnSync7("git", args, { cwd, encoding: "utf8" });
14066
15518
  if (r.error) throw r.error;
14067
15519
  if (r.status !== 0) {
14068
15520
  throw new Error(`git ${args.join(" ")} failed: ${r.stderr ?? ""}`);
@@ -14070,7 +15522,7 @@ function git3(cwd, ...args) {
14070
15522
  }
14071
15523
  __name(git3, "git");
14072
15524
  function gitOut2(cwd, ...args) {
14073
- const r = spawnSync6("git", args, { cwd, encoding: "utf8" });
15525
+ const r = spawnSync7("git", args, { cwd, encoding: "utf8" });
14074
15526
  if (r.error) throw r.error;
14075
15527
  if (r.status !== 0) {
14076
15528
  throw new Error(`git ${args.join(" ")} failed: ${r.stderr ?? ""}`);
@@ -14079,7 +15531,7 @@ function gitOut2(cwd, ...args) {
14079
15531
  }
14080
15532
  __name(gitOut2, "gitOut");
14081
15533
  function findVitestBin(worktree) {
14082
- const req = createRequire(join16(worktree, "noop.js"));
15534
+ const req = createRequire(join17(worktree, "noop.js"));
14083
15535
  let pkgPath;
14084
15536
  try {
14085
15537
  pkgPath = req.resolve("vitest/package.json");
@@ -14089,16 +15541,16 @@ function findVitestBin(worktree) {
14089
15541
  }
14090
15542
  throw error;
14091
15543
  }
14092
- const pkg = JSON.parse(readFileSync22(pkgPath, "utf8"));
15544
+ const pkg = JSON.parse(readFileSync25(pkgPath, "utf8"));
14093
15545
  const bin = pkg.bin?.vitest;
14094
15546
  if (!bin) {
14095
15547
  throw new Error(`vitest package at ${pkgPath} declares no "vitest" bin`);
14096
15548
  }
14097
- return join16(dirname17(pkgPath), bin);
15549
+ return join17(dirname20(pkgPath), bin);
14098
15550
  }
14099
15551
  __name(findVitestBin, "findVitestBin");
14100
15552
  function gitCapture(cwd, ...args) {
14101
- const r = spawnSync6("git", args, {
15553
+ const r = spawnSync7("git", args, {
14102
15554
  cwd,
14103
15555
  encoding: "utf8",
14104
15556
  maxBuffer: 64 * 1024 * 1024
@@ -14111,7 +15563,7 @@ function gitCapture(cwd, ...args) {
14111
15563
  }
14112
15564
  __name(gitCapture, "gitCapture");
14113
15565
  function existsAtRev(cwd, rev, path3) {
14114
- const r = spawnSync6("git", ["cat-file", "-e", `${rev}:${path3}`], { cwd });
15566
+ const r = spawnSync7("git", ["cat-file", "-e", `${rev}:${path3}`], { cwd });
14115
15567
  if (r.error) throw r.error;
14116
15568
  return r.status === 0;
14117
15569
  }
@@ -14120,7 +15572,7 @@ function safeRmWithin(worktree, relPath) {
14120
15572
  const parts = relPath.split(/[/\\]+/).filter((s) => s && s !== ".");
14121
15573
  let cur = worktree;
14122
15574
  for (let i = 0; i < parts.length; i++) {
14123
- cur = join16(cur, parts[i]);
15575
+ cur = join17(cur, parts[i]);
14124
15576
  let st;
14125
15577
  try {
14126
15578
  st = lstatSync4(cur);
@@ -14133,7 +15585,7 @@ function safeRmWithin(worktree, relPath) {
14133
15585
  );
14134
15586
  }
14135
15587
  }
14136
- rmSync8(cur, { force: true });
15588
+ rmSync9(cur, { force: true });
14137
15589
  }
14138
15590
  __name(safeRmWithin, "safeRmWithin");
14139
15591
  var existsAtBase = /* @__PURE__ */ __name((cwd, base, path3) => existsAtRev(cwd, base, path3), "existsAtBase");
@@ -14145,15 +15597,15 @@ __name(probeCleanupFailureDetail, "probeCleanupFailureDetail");
14145
15597
  function exposeDependencies(probeTree, dependencyRoot) {
14146
15598
  const done = { linked: 0, failed: 0 };
14147
15599
  if (probeTree === dependencyRoot) return done;
14148
- const source = join16(dependencyRoot, "node_modules");
14149
- const target = join16(probeTree, "node_modules");
14150
- if (!existsSync9(source) || existsSync9(target)) return done;
14151
- mkdirSync20(target);
15600
+ const source = join17(dependencyRoot, "node_modules");
15601
+ const target = join17(probeTree, "node_modules");
15602
+ if (!existsSync11(source) || existsSync11(target)) return done;
15603
+ mkdirSync23(target);
14152
15604
  const linkType = process.platform === "win32" ? "junction" : "dir";
14153
15605
  for (const entry of readdirSync5(source, { withFileTypes: true })) {
14154
15606
  if (entry.name === ".vite" || entry.name === ".vite-temp") continue;
14155
- const sourceEntry = join16(source, entry.name);
14156
- const targetEntry = join16(target, entry.name);
15607
+ const sourceEntry = join17(source, entry.name);
15608
+ const targetEntry = join17(target, entry.name);
14157
15609
  let sourceStats;
14158
15610
  try {
14159
15611
  sourceStats = lstatSync4(sourceEntry);
@@ -14163,7 +15615,7 @@ function exposeDependencies(probeTree, dependencyRoot) {
14163
15615
  }
14164
15616
  if (sourceStats.isSymbolicLink()) {
14165
15617
  try {
14166
- if (!statSync6(sourceEntry).isDirectory()) continue;
15618
+ if (!statSync8(sourceEntry).isDirectory()) continue;
14167
15619
  } catch {
14168
15620
  continue;
14169
15621
  }
@@ -14173,7 +15625,7 @@ function exposeDependencies(probeTree, dependencyRoot) {
14173
15625
  if (entry.name.startsWith("@")) {
14174
15626
  let pkgs;
14175
15627
  try {
14176
- mkdirSync20(targetEntry);
15628
+ mkdirSync23(targetEntry);
14177
15629
  pkgs = readdirSync5(sourceEntry);
14178
15630
  } catch {
14179
15631
  done.failed++;
@@ -14181,7 +15633,7 @@ function exposeDependencies(probeTree, dependencyRoot) {
14181
15633
  }
14182
15634
  for (const pkg of pkgs) {
14183
15635
  try {
14184
- symlinkSync(join16(sourceEntry, pkg), join16(targetEntry, pkg), linkType);
15636
+ symlinkSync(join17(sourceEntry, pkg), join17(targetEntry, pkg), linkType);
14185
15637
  done.linked++;
14186
15638
  } catch {
14187
15639
  done.failed++;
@@ -14203,7 +15655,7 @@ function runProbeSuite(probeTree, probes, deadlineAt, now = Date.now, dependency
14203
15655
  const started = now();
14204
15656
  const timeout = deadlineAt !== void 0 ? Math.max(1, Math.min(PROBE_RUN_TIMEOUT_MS, deadlineAt - started)) : PROBE_RUN_TIMEOUT_MS;
14205
15657
  const exposed = exposeDependencies(probeTree, dependencyRoot);
14206
- const r = spawnSync6(
15658
+ const r = spawnSync7(
14207
15659
  process.execPath,
14208
15660
  [findVitestBin(dependencyRoot), "run", "--reporter=json", ...probes],
14209
15661
  {
@@ -14216,10 +15668,12 @@ function runProbeSuite(probeTree, probes, deadlineAt, now = Date.now, dependency
14216
15668
  maxBuffer: 64 * 1024 * 1024
14217
15669
  }
14218
15670
  );
14219
- if (r.error) throw r.error;
15671
+ if (r.error)
15672
+ throw new ProbeRunFailure(r.error.message, runnerFailureReason(r));
14220
15673
  if (r.signal) {
14221
- throw new Error(
14222
- `runner killed by ${r.signal}${r.signal === "SIGTERM" ? ` (probe timed out after ${Math.round(timeout / 1e3)}s)` : ""}`
15674
+ throw new ProbeRunFailure(
15675
+ `runner killed by ${r.signal}${r.signal === "SIGTERM" ? ` (probe timed out after ${Math.round(timeout / 1e3)}s)` : ""}`,
15676
+ runnerFailureReason(r)
14223
15677
  );
14224
15678
  }
14225
15679
  return {
@@ -14325,10 +15779,10 @@ function newSideLength(header) {
14325
15779
  __name(newSideLength, "newSideLength");
14326
15780
  function runOneHunkProbe(probeTree, hunk, probes, deadlineAt, now = Date.now) {
14327
15781
  const { patch: _patch, ...meta } = hunk;
14328
- const abs = join16(probeTree, hunk.file);
15782
+ const abs = join17(probeTree, hunk.file);
14329
15783
  let original;
14330
15784
  try {
14331
- original = readFileSync22(abs, "utf8");
15785
+ original = readFileSync25(abs, "utf8");
14332
15786
  } catch (e) {
14333
15787
  return {
14334
15788
  ...meta,
@@ -14336,7 +15790,7 @@ function runOneHunkProbe(probeTree, hunk, probes, deadlineAt, now = Date.now) {
14336
15790
  detail: `the probe tree does not hold ${hunk.file}: ${e instanceof Error ? e.message : String(e)}`
14337
15791
  };
14338
15792
  }
14339
- const applied = spawnSync6("git", ["apply", "--reverse", "-"], {
15793
+ const applied = spawnSync7("git", ["apply", "--reverse", "-"], {
14340
15794
  cwd: probeTree,
14341
15795
  input: hunk.patch,
14342
15796
  encoding: "utf8"
@@ -14354,21 +15808,21 @@ function runOneHunkProbe(probeTree, hunk, probes, deadlineAt, now = Date.now) {
14354
15808
  const detail = verdict === "killed" ? "the suite went red with this hunk reverted \u2014 a test covers this change" : verdict === "survived" ? "every affected test still PASSED with this hunk reverted \u2014 no test in this diff fails when the change is undone" : "the tree with this hunk reverted produced no clean verdict (likely a compile or import error) \u2014 not evidence either way";
14355
15809
  return { ...meta, verdict, detail };
14356
15810
  } finally {
14357
- mkdirSync20(dirname17(abs), { recursive: true });
14358
- writeFileSync20(abs, original, "utf8");
15811
+ mkdirSync23(dirname20(abs), { recursive: true });
15812
+ writeFileSync23(abs, original, "utf8");
14359
15813
  }
14360
15814
  }
14361
15815
  __name(runOneHunkProbe, "runOneHunkProbe");
14362
15816
  function runControlMutant(probeTree, probeFile, deadlineAt, now = Date.now) {
14363
- const abs = join16(probeTree, probeFile);
15817
+ const abs = join17(probeTree, probeFile);
14364
15818
  let original;
14365
15819
  try {
14366
- original = readFileSync22(abs, "utf8");
15820
+ original = readFileSync25(abs, "utf8");
14367
15821
  } catch {
14368
15822
  return null;
14369
15823
  }
14370
15824
  try {
14371
- writeFileSync20(
15825
+ writeFileSync23(
14372
15826
  abs,
14373
15827
  `${original}
14374
15828
  ;import { it as __qcIt, expect as __qcExpect } from 'vitest';
@@ -14381,13 +15835,13 @@ __qcIt('QWEN-REVIEW-POSITIVE-CONTROL', () => {
14381
15835
  const { perFile } = runProbeSuite(probeTree, [probeFile], deadlineAt, now);
14382
15836
  return perFile.some((r) => r.verdict === "gated");
14383
15837
  } finally {
14384
- writeFileSync20(abs, original, "utf8");
15838
+ writeFileSync23(abs, original, "utf8");
14385
15839
  }
14386
15840
  }
14387
15841
  __name(runControlMutant, "runControlMutant");
14388
15842
  function runOneMutant(probeTree, mutant, probes, deadlineAt, now = Date.now, dependencyRoot = probeTree) {
14389
- const abs = join16(probeTree, mutant.file);
14390
- const original = readFileSync22(abs, "utf8");
15843
+ const abs = join17(probeTree, mutant.file);
15844
+ const original = readFileSync25(abs, "utf8");
14391
15845
  const lines2 = original.split("\n");
14392
15846
  if ((lines2[mutant.line - 1] ?? "").trim() !== mutant.statement) {
14393
15847
  return {
@@ -14403,7 +15857,7 @@ function runOneMutant(probeTree, mutant, probes, deadlineAt, now = Date.now, dep
14403
15857
  lines2.splice(mutant.line - 1, 1);
14404
15858
  }
14405
15859
  try {
14406
- writeFileSync20(abs, lines2.join("\n"), "utf8");
15860
+ writeFileSync23(abs, lines2.join("\n"), "utf8");
14407
15861
  const { perFile } = runProbeSuite(
14408
15862
  probeTree,
14409
15863
  probes,
@@ -14415,7 +15869,7 @@ function runOneMutant(probeTree, mutant, probes, deadlineAt, now = Date.now, dep
14415
15869
  const detail = verdict === "killed" ? `the suite went red with this statement ${what} \u2014 a test catches it` : verdict === "survived" ? `every affected test still PASSED with this statement ${what} \u2014 no test fails ${mutant.mutated === void 0 ? "when it is removed" : "when it changes"}` : "the mutated tree produced no clean verdict (likely a compile or import error) \u2014 not evidence either way";
14416
15870
  return { ...mutant, verdict, detail };
14417
15871
  } finally {
14418
- writeFileSync20(abs, original, "utf8");
15872
+ writeFileSync23(abs, original, "utf8");
14419
15873
  }
14420
15874
  }
14421
15875
  __name(runOneMutant, "runOneMutant");
@@ -14454,9 +15908,9 @@ async function runTestEfficacy(args) {
14454
15908
  const now = args.now ?? Date.now;
14455
15909
  const startedAt = now();
14456
15910
  const { report, worktree, base, out } = args;
14457
- const plan = JSON.parse(readFileSync22(report, "utf8"));
15911
+ const plan = JSON.parse(readFileSync25(report, "utf8"));
14458
15912
  const rootPkg = JSON.parse(
14459
- readFileSync22(`${worktree}/package.json`, "utf8")
15913
+ readFileSync25(`${worktree}/package.json`, "utf8")
14460
15914
  );
14461
15915
  const globs = rootPkg.workspaces ?? [];
14462
15916
  const { unreachable, probes, revert } = planTestEfficacy(
@@ -14464,8 +15918,8 @@ async function runTestEfficacy(args) {
14464
15918
  globs
14465
15919
  );
14466
15920
  for (const p of revert) {
14467
- const norm = join16(worktree, p);
14468
- const root = join16(worktree, ".");
15921
+ const norm = join17(worktree, p);
15922
+ const root = join17(worktree, ".");
14469
15923
  if (isAbsolute3(p) || norm !== root && !norm.startsWith(root + sep3)) {
14470
15924
  throw new Error(
14471
15925
  `refusing to run: revert path escapes the worktree: ${JSON.stringify(p)}`
@@ -14568,7 +16022,12 @@ async function runTestEfficacy(args) {
14568
16022
  String(sweep?.stderr ?? "")
14569
16023
  );
14570
16024
  for (const file of probes) {
14571
- results.push({ file, verdict: "inconclusive", detail });
16025
+ results.push({
16026
+ file,
16027
+ verdict: "inconclusive",
16028
+ reason: "not-run",
16029
+ detail
16030
+ });
14572
16031
  }
14573
16032
  for (const c of candidates) {
14574
16033
  mutantResults.push({ ...c, verdict: "inconclusive", detail });
@@ -14618,6 +16077,21 @@ async function runTestEfficacy(args) {
14618
16077
  );
14619
16078
  }
14620
16079
  for (const c of harnessValidated === false ? [] : candidates) {
16080
+ const heldDetail = heldForRedCollocatedTest(
16081
+ "mutant",
16082
+ c.file,
16083
+ probes,
16084
+ greenProbes,
16085
+ baseline.perFile
16086
+ );
16087
+ if (heldDetail) {
16088
+ mutantResults.push({
16089
+ ...c,
16090
+ verdict: "inconclusive",
16091
+ detail: heldDetail
16092
+ });
16093
+ continue;
16094
+ }
14621
16095
  const remaining = mutantDeadline - now();
14622
16096
  if (!fitsAnotherMutantRun(remaining, estimatedRunMs)) {
14623
16097
  mutantsSkippedForBudget = candidates.length - mutantResults.length;
@@ -14635,13 +16109,19 @@ async function runTestEfficacy(args) {
14635
16109
  );
14636
16110
  }
14637
16111
  for (const h of harnessValidated === false ? [] : hunkCandidates) {
14638
- const own = collocatedProbe(h.file, probes);
14639
- if (own && !greenProbes.includes(own)) {
16112
+ const heldDetail = heldForRedCollocatedTest(
16113
+ "hunk",
16114
+ h.file,
16115
+ probes,
16116
+ greenProbes,
16117
+ baseline.perFile
16118
+ );
16119
+ if (heldDetail) {
14640
16120
  const { patch: _patch, ...meta } = h;
14641
16121
  hunkResults.push({
14642
16122
  ...meta,
14643
16123
  verdict: "inconclusive",
14644
- detail: `this hunk's collocated test ${own} did not run green in the unmutated baseline (likely a compile or import error in the probe tree), so the remaining probes passing cannot show the hunk is uncovered`
16124
+ detail: heldDetail
14645
16125
  });
14646
16126
  continue;
14647
16127
  }
@@ -14692,11 +16172,14 @@ async function runTestEfficacy(args) {
14692
16172
  noteDependencyFarm(revertRun.exposed);
14693
16173
  results.push(...revertRun.perFile);
14694
16174
  } catch (e) {
14695
- const detail = `probe could not run: ${e instanceof Error ? e.message : String(e)}`;
16175
+ const message = e instanceof Error ? e.message : String(e);
16176
+ const detail = `probe could not run: ${message}`;
16177
+ const reason = e instanceof ProbeRunFailure ? e.reason : "not-run";
14696
16178
  results.push(
14697
16179
  ...probes.map((file) => ({
14698
16180
  file,
14699
16181
  verdict: "inconclusive",
16182
+ reason,
14700
16183
  detail
14701
16184
  }))
14702
16185
  );
@@ -14708,7 +16191,7 @@ async function runTestEfficacy(args) {
14708
16191
  } catch (e) {
14709
16192
  removeError = e;
14710
16193
  }
14711
- if (existsSync9(probeTree)) {
16194
+ if (existsSync11(probeTree)) {
14712
16195
  cleanupFailure = probeCleanupFailureDetail(
14713
16196
  probeTree,
14714
16197
  removeError,
@@ -14732,10 +16215,15 @@ async function runTestEfficacy(args) {
14732
16215
  h.detail = detail;
14733
16216
  }
14734
16217
  }
14735
- for (const r of results) {
16218
+ for (let i = 0; i < results.length; i++) {
16219
+ const r = results[i];
14736
16220
  if (r.verdict === "inert") {
14737
- r.verdict = "inconclusive";
14738
- r.detail = detail;
16221
+ results[i] = {
16222
+ file: r.file,
16223
+ verdict: "inconclusive",
16224
+ reason: "control-failed",
16225
+ detail
16226
+ };
14739
16227
  }
14740
16228
  }
14741
16229
  }
@@ -14802,8 +16290,8 @@ async function runTestEfficacy(args) {
14802
16290
  findings,
14803
16291
  cleanupFailure
14804
16292
  };
14805
- mkdirSync20(dirname17(out), { recursive: true });
14806
- writeFileSync20(out, JSON.stringify(result, null, 2), "utf8");
16293
+ mkdirSync23(dirname20(out), { recursive: true });
16294
+ writeFileSync23(out, JSON.stringify(result, null, 2), "utf8");
14807
16295
  writeStdoutLine(
14808
16296
  `Wrote test-efficacy report to ${out} (${unreachable.length} unreachable, ${results.length} probed, ${mutantResults.length} mutant(s), ${hunkResults.length} hunk probe(s), ${findings.length} finding(s))`
14809
16297
  );
@@ -14880,8 +16368,8 @@ var testEfficacyCommand = {
14880
16368
 
14881
16369
  // packages/cli/src/commands/review/test-plan.ts
14882
16370
  init_esbuild_shims();
14883
- import { existsSync as existsSync10, mkdirSync as mkdirSync21, readFileSync as readFileSync23, writeFileSync as writeFileSync21 } from "node:fs";
14884
- import { dirname as dirname18, join as join17, normalize, resolve as resolve17 } from "node:path";
16371
+ import { existsSync as existsSync12, mkdirSync as mkdirSync24, readFileSync as readFileSync26, writeFileSync as writeFileSync24 } from "node:fs";
16372
+ import { dirname as dirname21, join as join18, normalize, resolve as resolve20 } from "node:path";
14885
16373
  var PLAN_NAME_RE = /(test\s*plan|\btesting\b|how\s+(?:has\s+this|to)\s+(?:been\s+)?test(?:ed)?|测试计划|测试方案|测试步骤)/i;
14886
16374
  var HEADING_LINE_RE = /^(#{1,6})(?:[ \t](.*))?$/;
14887
16375
  var BOLD_LINE_RE = /^\*\*([^*\n]+)\*\*:?\s*$/;
@@ -15084,7 +16572,7 @@ function rulePath(text, worktree, changed) {
15084
16572
  };
15085
16573
  }
15086
16574
  const ignored = isGitIgnored(worktree, path3);
15087
- if (existsSync10(join17(worktree, path3))) {
16575
+ if (existsSync12(join18(worktree, path3))) {
15088
16576
  return {
15089
16577
  kind: "path",
15090
16578
  text,
@@ -15155,7 +16643,7 @@ function ruleCommand(text, worktree, buildTest) {
15155
16643
  let rootScripts = [];
15156
16644
  try {
15157
16645
  const rootPkg = JSON.parse(
15158
- readFileSync23(join17(worktree, "package.json"), "utf8")
16646
+ readFileSync26(join18(worktree, "package.json"), "utf8")
15159
16647
  );
15160
16648
  rootScripts = Object.keys(rootPkg.scripts ?? {});
15161
16649
  } catch {
@@ -15233,7 +16721,7 @@ __name(fetchPrBody, "fetchPrBody");
15233
16721
  function runTestPlan(args, fetchBody = fetchPrBody) {
15234
16722
  let plan;
15235
16723
  try {
15236
- plan = JSON.parse(readFileSync23(args.plan, "utf8"));
16724
+ plan = JSON.parse(readFileSync26(args.plan, "utf8"));
15237
16725
  } catch (err) {
15238
16726
  throw new Error(
15239
16727
  `test-plan: cannot read the plan ${args.plan}: ${err.message}`
@@ -15260,7 +16748,7 @@ function runTestPlan(args, fetchBody = fetchPrBody) {
15260
16748
  note: "the PR description has no Test Plan section"
15261
16749
  };
15262
16750
  }
15263
- const worktree = resolve17(args.worktree);
16751
+ const worktree = resolve20(args.worktree);
15264
16752
  const changed = new Set(
15265
16753
  (plan.files ?? []).map((f) => normalize(String(f.path)))
15266
16754
  );
@@ -15268,7 +16756,7 @@ function runTestPlan(args, fetchBody = fetchPrBody) {
15268
16756
  if (args.buildTest) {
15269
16757
  try {
15270
16758
  buildTest = JSON.parse(
15271
- readFileSync23(args.buildTest, "utf8")
16759
+ readFileSync26(args.buildTest, "utf8")
15272
16760
  );
15273
16761
  } catch {
15274
16762
  buildTest = null;
@@ -15325,8 +16813,8 @@ var testPlanCommand = {
15325
16813
  try {
15326
16814
  const report = runTestPlan(args);
15327
16815
  if (args.out) {
15328
- mkdirSync21(dirname18(resolve17(args.out)), { recursive: true });
15329
- writeFileSync21(resolve17(args.out), JSON.stringify(report, null, 2));
16816
+ mkdirSync24(dirname21(resolve20(args.out)), { recursive: true });
16817
+ writeFileSync24(resolve20(args.out), JSON.stringify(report, null, 2));
15330
16818
  }
15331
16819
  writeStdoutLine(JSON.stringify(report, null, 2));
15332
16820
  writeStderrLine(`test-plan: ${report.note}`);
@@ -15340,8 +16828,8 @@ var testPlanCommand = {
15340
16828
  // packages/cli/src/commands/review/cleanup.ts
15341
16829
  init_esbuild_shims();
15342
16830
  import { execFileSync as execFileSync5 } from "node:child_process";
15343
- import { existsSync as existsSync11, readFileSync as readFileSync24, readdirSync as readdirSync6, rmSync as rmSync9 } from "node:fs";
15344
- import { join as join18 } from "node:path";
16831
+ import { existsSync as existsSync13, readFileSync as readFileSync27, readdirSync as readdirSync6, rmSync as rmSync10 } from "node:fs";
16832
+ import { join as join19 } from "node:path";
15345
16833
  var AUTOMATION_MARKER = "<!-- qwen-";
15346
16834
  function isAutomationComment(body) {
15347
16835
  return (body ?? "").trimStart().startsWith(AUTOMATION_MARKER);
@@ -15372,7 +16860,7 @@ var OWNER_REPO_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
15372
16860
  function readAuditWindow(target, expectedPrNumber) {
15373
16861
  let raw;
15374
16862
  try {
15375
- raw = readFileSync24(tmpFile(target, "fetch.json"), "utf8");
16863
+ raw = readFileSync27(tmpFile(target, "fetch.json"), "utf8");
15376
16864
  } catch (err) {
15377
16865
  const code = err.code;
15378
16866
  return code === "ENOENT" ? { skip: "no fetch report" } : {
@@ -15419,7 +16907,7 @@ function readSubmitReceipt(target) {
15419
16907
  try {
15420
16908
  return new Set(
15421
16909
  parseReceiptIds(
15422
- readFileSync24(tmpFile(target, "submit-receipt.json"), "utf8")
16910
+ readFileSync27(tmpFile(target, "submit-receipt.json"), "utf8")
15423
16911
  )
15424
16912
  );
15425
16913
  } catch {
@@ -15523,7 +17011,7 @@ function runCleanup(target) {
15523
17011
  report("probe worktree", probeWorktreePath(wt));
15524
17012
  report("base worktree", baseWorktreePath(wt));
15525
17013
  try {
15526
- rmSync9(`${baseWorktreePath(wt)}.lock`, { recursive: true, force: true });
17014
+ rmSync10(`${baseWorktreePath(wt)}.lock`, { recursive: true, force: true });
15527
17015
  } catch (err) {
15528
17016
  writeStderrLine(
15529
17017
  `note: could not remove base lock ${baseWorktreePath(wt)}.lock: ${err.message}`
@@ -15546,7 +17034,7 @@ function runCleanup(target) {
15546
17034
  const prefix = tmpPrefix(target);
15547
17035
  let tmpEntries = [];
15548
17036
  try {
15549
- tmpEntries = existsSync11(REVIEW_TMP_DIR) ? readdirSync6(REVIEW_TMP_DIR) : [];
17037
+ tmpEntries = existsSync13(REVIEW_TMP_DIR) ? readdirSync6(REVIEW_TMP_DIR) : [];
15550
17038
  } catch (err) {
15551
17039
  writeStderrLine(
15552
17040
  `Failed to read ${REVIEW_TMP_DIR}: ${err.message}`
@@ -15554,9 +17042,9 @@ function runCleanup(target) {
15554
17042
  }
15555
17043
  for (const file of tmpEntries) {
15556
17044
  if (!file.startsWith(prefix)) continue;
15557
- const full = join18(REVIEW_TMP_DIR, file);
17045
+ const full = join19(REVIEW_TMP_DIR, file);
15558
17046
  try {
15559
- rmSync9(full, { recursive: true, force: true });
17047
+ rmSync10(full, { recursive: true, force: true });
15560
17048
  writeStdoutLine(`Removed temp file: ${full}`);
15561
17049
  removedAny = true;
15562
17050
  } catch (err) {
@@ -15588,8 +17076,8 @@ var cleanupCommand = {
15588
17076
  // packages/cli/src/commands/review/run.ts
15589
17077
  init_esbuild_shims();
15590
17078
  import { spawn, execFileSync as execFileSync6 } from "node:child_process";
15591
- import { readdirSync as readdirSync7, readFileSync as readFileSync25, statSync as statSync7 } from "node:fs";
15592
- import { join as join19, resolve as resolve18 } from "node:path";
17079
+ import { readdirSync as readdirSync7, readFileSync as readFileSync28, realpathSync as realpathSync3, statSync as statSync9 } from "node:fs";
17080
+ import { dirname as dirname22, join as join20, resolve as resolve21 } from "node:path";
15593
17081
  var COMPOSED_PATTERN = /^qwen-review-.*composed\.json$/;
15594
17082
  var COMPOSED_POLL_MS = 250;
15595
17083
  var SIGNAL_EXIT_CODES = {
@@ -15623,10 +17111,10 @@ function newestArtifactSince(dir, pattern, startMs) {
15623
17111
  }
15624
17112
  for (const name of names) {
15625
17113
  if (!pattern.test(name)) continue;
15626
- const path3 = join19(dir, name);
17114
+ const path3 = join20(dir, name);
15627
17115
  let mtime;
15628
17116
  try {
15629
- mtime = statSync7(path3).mtimeMs;
17117
+ mtime = statSync9(path3).mtimeMs;
15630
17118
  } catch {
15631
17119
  continue;
15632
17120
  }
@@ -15636,15 +17124,15 @@ function newestArtifactSince(dir, pattern, startMs) {
15636
17124
  return best ? best.path : null;
15637
17125
  }
15638
17126
  __name(newestArtifactSince, "newestArtifactSince");
15639
- function exitCodeFor(completed, event, failOn) {
17127
+ function exitCodeFor(completed, event2, failOn) {
15640
17128
  if (!completed) return 1;
15641
- if (failOn === "request-changes" && event === "REQUEST_CHANGES") return 3;
17129
+ if (failOn === "request-changes" && event2 === "REQUEST_CHANGES") return 3;
15642
17130
  return 0;
15643
17131
  }
15644
17132
  __name(exitCodeFor, "exitCodeFor");
15645
17133
  function readComposed(path3) {
15646
17134
  try {
15647
- const parsed = JSON.parse(readFileSync25(path3, "utf8"));
17135
+ const parsed = JSON.parse(readFileSync28(path3, "utf8"));
15648
17136
  return typeof parsed.event === "string" ? parsed : null;
15649
17137
  } catch {
15650
17138
  return null;
@@ -15667,6 +17155,23 @@ function killProcessGroup(pid, signal) {
15667
17155
  }
15668
17156
  }
15669
17157
  __name(killProcessGroup, "killProcessGroup");
17158
+ function childEnv() {
17159
+ const env = { ...process.env };
17160
+ const inherited = env["QWEN_CODE_CLI"];
17161
+ const ownEntry = process.argv[1];
17162
+ if (!inherited || !ownEntry) {
17163
+ return env;
17164
+ }
17165
+ try {
17166
+ if (dirname22(realpathSync3(inherited)) === dirname22(realpathSync3(ownEntry))) {
17167
+ return env;
17168
+ }
17169
+ } catch {
17170
+ }
17171
+ env["QWEN_CODE_CLI"] = "";
17172
+ return env;
17173
+ }
17174
+ __name(childEnv, "childEnv");
15670
17175
  async function runReview(args) {
15671
17176
  const startMs = Date.now();
15672
17177
  const prompt = buildReviewPrompt(args);
@@ -15682,6 +17187,7 @@ async function runReview(args) {
15682
17187
  args.approvalMode
15683
17188
  ],
15684
17189
  {
17190
+ env: childEnv(),
15685
17191
  // stdin CLOSED, not inherited: piped input would be prepended to the
15686
17192
  // prompt and the leading `/` would no longer be the first character —
15687
17193
  // the slash command would reach the model as plain text.
@@ -15693,9 +17199,9 @@ async function runReview(args) {
15693
17199
  }
15694
17200
  );
15695
17201
  if (!args.quiet) {
15696
- const writeProgress = /* @__PURE__ */ __name((chunk) => {
17202
+ const writeProgress = /* @__PURE__ */ __name((chunk2) => {
15697
17203
  try {
15698
- process.stderr.write(chunk);
17204
+ process.stderr.write(chunk2);
15699
17205
  } catch {
15700
17206
  }
15701
17207
  }, "writeProgress");
@@ -15779,8 +17285,8 @@ async function runReview(args) {
15779
17285
  downgraded: composed?.downgraded ?? false,
15780
17286
  downgradedFrom: composed?.downgradedFrom ?? null,
15781
17287
  remediation: composed?.remediation ?? [],
15782
- composedPath: composedPath ? resolve18(composedPath) : null,
15783
- reportPath: reportPath ? resolve18(reportPath) : null,
17288
+ composedPath: composedPath ? resolve21(composedPath) : null,
17289
+ reportPath: reportPath ? resolve21(reportPath) : null,
15784
17290
  childExitCode,
15785
17291
  childSignal,
15786
17292
  timedOut,
@@ -15794,7 +17300,7 @@ async function runReview(args) {
15794
17300
  writeStdoutLine(result.verdictLine ?? `Event: ${result.event}`);
15795
17301
  if (result.reportPath) writeStdoutLine(`Report: ${result.reportPath}`);
15796
17302
  } else {
15797
- const detail = composedPath !== null ? `a composed verdict was found at ${resolve18(composedPath)} but could not be parsed` : "no composed verdict was produced";
17303
+ const detail = composedPath !== null ? `a composed verdict was found at ${resolve21(composedPath)} but could not be parsed` : "no composed verdict was produced";
15798
17304
  writeStdoutLine(
15799
17305
  timedOut ? "Review did not complete: timed out." : `Review did not complete: ${detail}${childExitCode !== null ? ` (CLI exit ${childExitCode})` : ""}${childSignal !== null ? ` (killed by ${childSignal})` : ""}.`
15800
17306
  );
@@ -15857,13 +17363,308 @@ var runCommand = {
15857
17363
  }, "handler")
15858
17364
  };
15859
17365
 
17366
+ // packages/cli/src/commands/review/save-artifact.ts
17367
+ init_esbuild_shims();
17368
+ import {
17369
+ existsSync as existsSync14,
17370
+ lstatSync as lstatSync5,
17371
+ mkdirSync as mkdirSync25,
17372
+ readFileSync as readFileSync29,
17373
+ statSync as statSync10
17374
+ } from "node:fs";
17375
+ import {
17376
+ dirname as dirname23,
17377
+ isAbsolute as isAbsolute4,
17378
+ join as join21,
17379
+ relative as relative3,
17380
+ resolve as resolve22,
17381
+ sep as sep4,
17382
+ toNamespacedPath
17383
+ } from "node:path";
17384
+ import { isDeepStrictEqual } from "node:util";
17385
+ function workspaceRoot() {
17386
+ return resolve22(process.env["QWEN_CODE_PROJECT_DIR"] ?? process.cwd());
17387
+ }
17388
+ __name(workspaceRoot, "workspaceRoot");
17389
+ function isWithin(parent, child) {
17390
+ const rel = relative3(parent, child);
17391
+ return rel === "" || !isAbsolute4(rel) && rel !== ".." && !rel.startsWith(`..${sep4}`);
17392
+ }
17393
+ __name(isWithin, "isWithin");
17394
+ function workspacePath(root, value, label2) {
17395
+ const absolute = resolve22(root, value);
17396
+ if (!isWithin(root, absolute)) {
17397
+ throw new Error(
17398
+ `${label2} must be inside the workspace: ${JSON.stringify(value)}.`
17399
+ );
17400
+ }
17401
+ return absolute;
17402
+ }
17403
+ __name(workspacePath, "workspacePath");
17404
+ function rejectSymlinkPath(root, path3, label2) {
17405
+ const rel = relative3(root, path3);
17406
+ let current = root;
17407
+ for (const segment of rel.split(sep4)) {
17408
+ current = join21(current, segment);
17409
+ if (existsSync14(current) && lstatSync5(current).isSymbolicLink()) {
17410
+ throw new Error(`${label2} must not traverse a symbolic link.`);
17411
+ }
17412
+ }
17413
+ }
17414
+ __name(rejectSymlinkPath, "rejectSymlinkPath");
17415
+ function sameFile(left, right) {
17416
+ if (left === right) return true;
17417
+ if (!existsSync14(left) || !existsSync14(right)) return false;
17418
+ const leftStat = statSync10(left);
17419
+ const rightStat = statSync10(right);
17420
+ return leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino;
17421
+ }
17422
+ __name(sameFile, "sameFile");
17423
+ function readText(path3, label2) {
17424
+ try {
17425
+ return readFileSync29(path3, "utf8");
17426
+ } catch (error) {
17427
+ throw new Error(
17428
+ `Could not read the ${label2} file at ${JSON.stringify(path3)}: ${error instanceof Error ? error.message : String(error)}`
17429
+ );
17430
+ }
17431
+ }
17432
+ __name(readText, "readText");
17433
+ function readJson2(path3, label2) {
17434
+ const text = readText(path3, label2);
17435
+ try {
17436
+ return JSON.parse(text);
17437
+ } catch (error) {
17438
+ throw new Error(
17439
+ `The ${label2} file at ${JSON.stringify(path3)} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
17440
+ );
17441
+ }
17442
+ }
17443
+ __name(readJson2, "readJson");
17444
+ function object(value, label2) {
17445
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
17446
+ throw new Error(`${label2} must be an object.`);
17447
+ }
17448
+ return value;
17449
+ }
17450
+ __name(object, "object");
17451
+ function string(value, label2) {
17452
+ if (typeof value !== "string") {
17453
+ throw new Error(`${label2} must be a string.`);
17454
+ }
17455
+ return value;
17456
+ }
17457
+ __name(string, "string");
17458
+ function nonEmptyString(value, label2) {
17459
+ const text = string(value, label2);
17460
+ if (text.trim() === "") {
17461
+ throw new Error(`${label2} must be a non-empty string.`);
17462
+ }
17463
+ return text;
17464
+ }
17465
+ __name(nonEmptyString, "nonEmptyString");
17466
+ function stringArray2(value, label2) {
17467
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
17468
+ throw new Error(`${label2} must be an array of strings.`);
17469
+ }
17470
+ return value;
17471
+ }
17472
+ __name(stringArray2, "stringArray");
17473
+ function event(value, label2) {
17474
+ if (value !== "APPROVE" && value !== "COMMENT" && value !== "REQUEST_CHANGES") {
17475
+ throw new Error(`${label2} has an unsupported value.`);
17476
+ }
17477
+ return value;
17478
+ }
17479
+ __name(event, "event");
17480
+ function validateVerdict(value) {
17481
+ const verdict = object(value, "Composed verdict");
17482
+ const downgradedFrom = verdict["downgradedFrom"];
17483
+ if (downgradedFrom !== null && downgradedFrom !== "Approve" && downgradedFrom !== "Request changes") {
17484
+ throw new Error(
17485
+ "Composed verdict.downgradedFrom has an unsupported value."
17486
+ );
17487
+ }
17488
+ if (typeof verdict["downgraded"] !== "boolean") {
17489
+ throw new Error("Composed verdict.downgraded must be a boolean.");
17490
+ }
17491
+ const lowSignal = verdict["lowSignal"];
17492
+ if (lowSignal !== null) {
17493
+ const signal = object(lowSignal, "Composed verdict.lowSignal");
17494
+ for (const key of ["agents", "srcDiffLines"]) {
17495
+ if (typeof signal[key] !== "number" || !Number.isInteger(signal[key]) || signal[key] < 0) {
17496
+ throw new Error(
17497
+ `Composed verdict.lowSignal.${key} must be a non-negative integer.`
17498
+ );
17499
+ }
17500
+ }
17501
+ }
17502
+ return {
17503
+ event: event(verdict["event"], "Composed verdict.event"),
17504
+ body: string(verdict["body"], "Composed verdict.body"),
17505
+ baseEvent: event(verdict["baseEvent"], "Composed verdict.baseEvent"),
17506
+ cappedBy: stringArray2(verdict["cappedBy"], "Composed verdict.cappedBy"),
17507
+ downgraded: verdict["downgraded"],
17508
+ downgradedFrom,
17509
+ remediation: stringArray2(
17510
+ verdict["remediation"],
17511
+ "Composed verdict.remediation"
17512
+ ),
17513
+ lowSignal: lowSignal === null ? null : {
17514
+ agents: lowSignal["agents"],
17515
+ srcDiffLines: lowSignal["srcDiffLines"]
17516
+ },
17517
+ verdictLine: nonEmptyString(
17518
+ verdict["verdictLine"],
17519
+ "Composed verdict.verdictLine"
17520
+ )
17521
+ };
17522
+ }
17523
+ __name(validateVerdict, "validateVerdict");
17524
+ function validateFindingsReport(value) {
17525
+ const report = object(value, "Canonical findings report");
17526
+ if (typeof report["outcomesRecorded"] !== "boolean") {
17527
+ throw new Error(
17528
+ "Canonical findings report.outcomesRecorded must be a boolean."
17529
+ );
17530
+ }
17531
+ const canonical = buildReport(validateFindings(report["findings"]));
17532
+ const supplied = {
17533
+ findings: report["findings"],
17534
+ counts: report["counts"],
17535
+ outcomesRecorded: report["outcomesRecorded"]
17536
+ };
17537
+ if (!isDeepStrictEqual(supplied, canonical)) {
17538
+ throw new Error(
17539
+ "Canonical findings report is malformed or inconsistent with its findings."
17540
+ );
17541
+ }
17542
+ return supplied;
17543
+ }
17544
+ __name(validateFindingsReport, "validateFindingsReport");
17545
+ function saveReviewArtifact(args) {
17546
+ const root = workspaceRoot();
17547
+ const findingsPath = workspacePath(root, args.findings, "Findings input");
17548
+ const composedPath = workspacePath(root, args.composed, "Composed input");
17549
+ const reportPath = workspacePath(root, args.report, "Markdown report");
17550
+ const outputPath = workspacePath(root, args.out, "Output");
17551
+ const reviewsRoot = resolve22(root, REVIEWS_DIR);
17552
+ if (!isWithin(reviewsRoot, reportPath) || reportPath === reviewsRoot) {
17553
+ throw new Error(
17554
+ `Markdown report must be a file under ${JSON.stringify(REVIEWS_DIR)}: ${JSON.stringify(args.report)}.`
17555
+ );
17556
+ }
17557
+ if (!isWithin(reviewsRoot, outputPath) || outputPath === reviewsRoot) {
17558
+ throw new Error(
17559
+ `Output must be a file under ${JSON.stringify(REVIEWS_DIR)}: ${JSON.stringify(args.out)}.`
17560
+ );
17561
+ }
17562
+ for (const [label2, inputPath] of [
17563
+ ["findings input", findingsPath],
17564
+ ["composed input", composedPath],
17565
+ ["Markdown report", reportPath]
17566
+ ]) {
17567
+ if (sameFile(outputPath, inputPath)) {
17568
+ throw new Error(`Output must not overwrite the ${label2}.`);
17569
+ }
17570
+ }
17571
+ rejectSymlinkPath(root, findingsPath, "Findings input");
17572
+ rejectSymlinkPath(root, composedPath, "Composed input");
17573
+ rejectSymlinkPath(root, reportPath, "Markdown report");
17574
+ rejectSymlinkPath(root, outputPath, "Output");
17575
+ if (!EFFORT_LEVELS.has(args.effort)) {
17576
+ throw new Error(
17577
+ `Unsupported review effort: ${JSON.stringify(args.effort)}.`
17578
+ );
17579
+ }
17580
+ if (args.effort === "low") {
17581
+ throw new Error(
17582
+ "save-artifact does not support low-effort reviews: low has no canonical composed verdict to persist."
17583
+ );
17584
+ }
17585
+ nonEmptyString(args.target, "Target");
17586
+ const findings = validateFindingsReport(
17587
+ readJson2(findingsPath, "canonical findings")
17588
+ );
17589
+ const verdict = validateVerdict(readJson2(composedPath, "composed verdict"));
17590
+ let reportIsFile;
17591
+ try {
17592
+ reportIsFile = statSync10(reportPath).isFile();
17593
+ } catch {
17594
+ }
17595
+ if (reportIsFile === false) {
17596
+ throw new Error(
17597
+ `Markdown report is not a file: ${JSON.stringify(args.report)}.`
17598
+ );
17599
+ }
17600
+ readText(reportPath, "Markdown report");
17601
+ const markdownReportPath = relative3(root, reportPath).split(sep4).join("/");
17602
+ const document = {
17603
+ schemaVersion: 1,
17604
+ target: args.target,
17605
+ effort: args.effort,
17606
+ verdict,
17607
+ findings: findings.findings,
17608
+ counts: findings.counts,
17609
+ outcomesRecorded: findings.outcomesRecorded,
17610
+ markdownReportPath
17611
+ };
17612
+ mkdirSync25(dirname23(outputPath), { recursive: true });
17613
+ atomicWriteFileSync(
17614
+ toNamespacedPath(outputPath),
17615
+ `${JSON.stringify(document, null, 2)}
17616
+ `,
17617
+ { encoding: "utf8", noFollow: true }
17618
+ );
17619
+ return {
17620
+ path: outputPath,
17621
+ workspacePath: relative3(root, outputPath).split(sep4).join("/")
17622
+ };
17623
+ }
17624
+ __name(saveReviewArtifact, "saveReviewArtifact");
17625
+ var saveArtifactCommand = {
17626
+ command: "save-artifact",
17627
+ describe: "Create a durable versioned code-review JSON document from canonical review artifacts",
17628
+ builder: /* @__PURE__ */ __name((yargs) => yargs.option("findings", {
17629
+ type: "string",
17630
+ demandOption: true,
17631
+ describe: "Canonical findings JSON"
17632
+ }).option("composed", {
17633
+ type: "string",
17634
+ demandOption: true,
17635
+ describe: "Composed verdict JSON"
17636
+ }).option("report", {
17637
+ type: "string",
17638
+ demandOption: true,
17639
+ describe: "Existing durable Markdown report"
17640
+ }).option("target", {
17641
+ type: "string",
17642
+ demandOption: true,
17643
+ describe: "Review target label"
17644
+ }).option("effort", {
17645
+ type: "string",
17646
+ choices: [...EFFORT_LEVELS].filter((effort) => effort !== "low"),
17647
+ demandOption: true,
17648
+ describe: "Resolved review effort"
17649
+ }).option("out", {
17650
+ type: "string",
17651
+ demandOption: true,
17652
+ describe: "Output path under .qwen/reviews/"
17653
+ }), "builder"),
17654
+ handler: /* @__PURE__ */ __name((argv) => {
17655
+ const saved = saveReviewArtifact(argv);
17656
+ writeStdoutLine(JSON.stringify(saved));
17657
+ writeStderrLine(`save-artifact: wrote ${saved.path}`);
17658
+ }, "handler")
17659
+ };
17660
+
15860
17661
  // packages/cli/src/commands/review.ts
15861
17662
  var reviewCommand = {
15862
17663
  command: "review",
15863
17664
  describe: "Run a review non-interactively (`run`), plus the internal helpers used by the /review skill (PR worktree setup, context fetch, rules loading, presubmit checks, cleanup)",
15864
- builder: /* @__PURE__ */ __name((yargs) => yargs.command(runCommand).command(parseArgsCommand).command(fetchPrCommand).command(captureLocalCommand).command(planDiffCommand).command(prContextCommand).command(commentStatusCommand).command(loadRulesCommand).command(agentPromptCommand).command(buildTestCommand).command(baseTreeCommand).command(testDeltaCommand).command(extractStepCommand).command(scriptLintCommand).command(resolveAnchorsCommand).command(checkCoverageCommand).command(presubmitCommand).command(testEfficacyCommand).command(testPlanCommand).command(findingsCommand).command(composeReviewCommand).command(submitCommand).command(cleanupCommand).demandCommand(
17665
+ builder: /* @__PURE__ */ __name((yargs) => yargs.command(runCommand).command(parseArgsCommand).command(fetchPrCommand).command(captureLocalCommand).command(planDiffCommand).command(prContextCommand).command(commentStatusCommand).command(loadRulesCommand).command(agentPromptCommand).command(buildTestCommand).command(baseTreeCommand).command(testDeltaCommand).command(driveCommand).command(mockProviderCommand).command(extractStepCommand).command(scriptLintCommand).command(resolveAnchorsCommand).command(checkCoverageCommand).command(presubmitCommand).command(testEfficacyCommand).command(testPlanCommand).command(findingsCommand).command(publishAssetsCommand).command(composeReviewCommand).command(saveArtifactCommand).command(submitCommand).command(cleanupCommand).demandCommand(
15865
17666
  1,
15866
- "Specify a subcommand: run, parse-args, fetch-pr, capture-local, plan-diff, pr-context, comment-status, load-rules, agent-prompt, build-test, base-tree, test-delta, extract-step, script-lint, resolve-anchors, check-coverage, presubmit, test-efficacy, test-plan, findings, compose-review, submit, or cleanup."
17667
+ "Specify a subcommand: run, parse-args, fetch-pr, capture-local, plan-diff, pr-context, comment-status, load-rules, agent-prompt, build-test, base-tree, test-delta, drive, mock-provider, extract-step, script-lint, resolve-anchors, check-coverage, presubmit, test-efficacy, test-plan, findings, publish-assets, compose-review, save-artifact, submit, or cleanup."
15867
17668
  ).version(false), "builder"),
15868
17669
  handler: /* @__PURE__ */ __name(() => {
15869
17670
  }, "handler")
@@ -16063,10 +17864,10 @@ var updateCommand3 = {
16063
17864
  stdioHelpers,
16064
17865
  { updateEventEmitter }
16065
17866
  ] = await Promise.all([
16066
- import("./settings-LI2ORVXC.js"),
16067
- import("./updateCheck-7EG4BLBH.js"),
16068
- import("./installationInfo-2LIKCNVX.js"),
16069
- import("./standalone-update-E4V5V7WT.js"),
17867
+ import("./settings-FZH5YZLH.js"),
17868
+ import("./updateCheck-XSIBJXYA.js"),
17869
+ import("./installationInfo-TEJOXP5W.js"),
17870
+ import("./standalone-update-LEPP5BOB.js"),
16070
17871
  import("./stdioHelpers-HGIYTWAY.js"),
16071
17872
  import("./updateEventEmitter-5MKC7BLQ.js")
16072
17873
  ]);
@@ -17233,7 +19034,7 @@ async function loadCliConfig(settings, argv, cwd = process.cwd(), overrideExtens
17233
19034
  disabledSlashCommands: disabledSlashCommands.length > 0 ? disabledSlashCommands : void 0,
17234
19035
  disabledSkillNamesProvider: bareMode || safeMode ? void 0 : disabledSkillNamesProvider,
17235
19036
  terminalImageRenderSupportProvider: interactive ? async () => {
17236
- const { getTerminalImageRenderSupport } = await import("./terminal-image-renderer-BOQJPVLB.js");
19037
+ const { getTerminalImageRenderSupport } = await import("./terminal-image-renderer-KZMAF5E5.js");
17237
19038
  return getTerminalImageRenderSupport();
17238
19039
  } : void 0,
17239
19040
  disabledSkillLevels: bareMode || safeMode || !Array.isArray(settings.skills?.disabledLevels) ? void 0 : settings.skills.disabledLevels.filter(isSkillLevel),
@@ -17387,6 +19188,7 @@ async function loadCliConfig(settings, argv, cwd = process.cwd(), overrideExtens
17387
19188
  enableAutoSkill: bareMode || safeMode ? false : settings.memory?.enableAutoSkill ?? false,
17388
19189
  autoSkillConfirm: bareMode || safeMode ? false : settings.memory?.autoSkillConfirm ?? true,
17389
19190
  memoryAgentTimeoutMinutes: settings.memory?.agentTimeoutMinutes,
19191
+ memoryAgentMaxTurns: settings.memory?.agentMaxTurns,
17390
19192
  fastModel: settings.fastModel || void 0,
17391
19193
  webSearch: bareMode || safeMode ? void 0 : resolveWebSearchSettings(settings),
17392
19194
  visionModel: settings.visionModel || void 0,
@@ -17508,11 +19310,11 @@ var ExtensionRefreshState = class {
17508
19310
  contentChangedDuringReload = false;
17509
19311
  suppressionDepth = 0;
17510
19312
  suppressUntil = 0;
17511
- on(event, listener) {
17512
- this.events.on(event, listener);
19313
+ on(event2, listener) {
19314
+ this.events.on(event2, listener);
17513
19315
  }
17514
- off(event, listener) {
17515
- this.events.off(event, listener);
19316
+ off(event2, listener) {
19317
+ this.events.off(event2, listener);
17516
19318
  }
17517
19319
  markExtensionsChanged(reason) {
17518
19320
  if (this.isSuppressed()) {