@qwen-code/qwen-code 0.21.5 → 0.21.6

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 (250) hide show
  1. package/bundled/qc-helper/docs/configuration/model-providers.md +8 -8
  2. package/bundled/qc-helper/docs/configuration/settings.md +15 -15
  3. package/bundled/qc-helper/docs/features/channels/github.md +31 -19
  4. package/bundled/qc-helper/docs/features/code-review.md +5 -1
  5. package/bundled/qc-helper/docs/features/headless.md +31 -0
  6. package/bundled/qc-helper/docs/qwen-serve.md +75 -0
  7. package/bundled/review/SKILL.md +72 -60
  8. package/chunks/{MaxSizedBox-2MWU5JYB.js → MaxSizedBox-2HFKP4LI.js} +11 -10
  9. package/chunks/{StandaloneSessionPicker-B6MI5NTB.js → StandaloneSessionPicker-RBV72M5N.js} +26 -25
  10. package/chunks/{acp-startup-profiler-3RICG2YW.js → acp-startup-profiler-ORIQ2CCQ.js} +2 -2
  11. package/chunks/{acpAgent-MRPYN6FH.js → acpAgent-WO6JFCVQ.js} +1232 -139
  12. package/chunks/{agent-NAOXNW4I.js → agent-AHGLYNZ4.js} +11 -10
  13. package/chunks/{agent-headless-L4PPVFKW.js → agent-headless-NTXKKGRO.js} +11 -10
  14. package/chunks/{bridge-LHXGYNCO.js → bridge-RPAU64CA.js} +17 -15
  15. package/chunks/{channel-management-service-UHX56NBO.js → channel-management-service-IGEUOVRT.js} +3 -3
  16. package/chunks/{channel-settings-store-QB54TLYE.js → channel-settings-store-ON64MF7G.js} +18 -17
  17. package/chunks/{channel-worker-group-NDG4WOOZ.js → channel-worker-group-JWQK224X.js} +6 -5
  18. package/chunks/{channel-worker-manager-ZCW6UW34.js → channel-worker-manager-77E3OXRQ.js} +6 -5
  19. package/chunks/{channel-worker-supervisor-ATGFTZRN.js → channel-worker-supervisor-62YWUGP2.js} +4 -3
  20. package/chunks/{chunk-EXHQZBVJ.js → chunk-2POQWYM6.js} +2 -2
  21. package/chunks/{chunk-YMUZQ3MQ.js → chunk-3LZKY4V4.js} +3 -3
  22. package/chunks/{chunk-7MFQLOHZ.js → chunk-3PRJYMXC.js} +1 -1
  23. package/chunks/{chunk-SJMCYCUQ.js → chunk-3UOLYNTF.js} +5 -5
  24. package/chunks/{chunk-Q2V4AOQL.js → chunk-4JH77A2U.js} +1 -1
  25. package/chunks/{chunk-MISUEWB4.js → chunk-4S4OEQ6U.js} +10 -6
  26. package/chunks/{chunk-URJHXOSO.js → chunk-4WTAQEHV.js} +1 -1
  27. package/chunks/{chunk-ZLGPHA34.js → chunk-536SWZTA.js} +5 -5
  28. package/chunks/{chunk-WU5NBSLD.js → chunk-5MJGUQU5.js} +3 -3
  29. package/chunks/{chunk-3P4GP4S2.js → chunk-75QMRMO7.js} +4 -4
  30. package/chunks/{chunk-YLL3SC5S.js → chunk-A4VTILZT.js} +1032 -913
  31. package/chunks/{chunk-B3EBIZUY.js → chunk-ACZRB7AD.js} +8 -3
  32. package/chunks/chunk-BKHCW2BI.js +41 -0
  33. package/chunks/{chunk-XD2PNH3K.js → chunk-BOGOR4HB.js} +5 -5
  34. package/chunks/{chunk-QL4TN4HS.js → chunk-BRVWYMKV.js} +1 -1
  35. package/chunks/{chunk-TCOJPG2P.js → chunk-C4CBJQKY.js} +1 -1
  36. package/chunks/{chunk-4O6HLCOF.js → chunk-CA4UIDFU.js} +1 -1
  37. package/chunks/{chunk-SQTA6PAY.js → chunk-CJ5WLD5Y.js} +6 -2
  38. package/chunks/{chunk-7W5KOBYU.js → chunk-DIT4V3XH.js} +1 -1
  39. package/chunks/{chunk-OIGB253L.js → chunk-DVBEGGLG.js} +8 -8
  40. package/chunks/{chunk-DTAPI453.js → chunk-EMYZIABH.js} +1 -1
  41. package/chunks/{chunk-GBY3ODW7.js → chunk-EXCEQD22.js} +3 -3
  42. package/chunks/{chunk-VJPF6GF2.js → chunk-FZFP7RZI.js} +3 -3
  43. package/chunks/{chunk-3GOCVBYR.js → chunk-G5NNWEYE.js} +5 -5
  44. package/chunks/{chunk-IWYXZAGT.js → chunk-GESMMPAK.js} +33 -33
  45. package/chunks/{chunk-SVIEVA3L.js → chunk-GK2H5GRC.js} +29 -4
  46. package/chunks/{chunk-7M2RLFNX.js → chunk-GWRHVJY2.js} +11 -11
  47. package/chunks/{chunk-BK2UNYWD.js → chunk-HIBNSEJE.js} +12 -0
  48. package/chunks/{chunk-4LLUH3A7.js → chunk-HU5TUALO.js} +95 -8
  49. package/chunks/{chunk-J7I2BZHS.js → chunk-HY25ULVN.js} +23 -6
  50. package/chunks/{chunk-G2UXFO2B.js → chunk-IMOQO6YL.js} +1 -1
  51. package/chunks/{chunk-CAX76R46.js → chunk-JE44IPH6.js} +1 -1
  52. package/chunks/{chunk-IK3RYLAD.js → chunk-K2B3X3E7.js} +3 -3
  53. package/chunks/{chunk-OWAK7LMW.js → chunk-KGJGEEVR.js} +7 -0
  54. package/chunks/{chunk-K64OI7S7.js → chunk-KHUA3NQV.js} +2 -2
  55. package/chunks/{chunk-LEZFUYPM.js → chunk-LAAFLUQV.js} +4 -4
  56. package/chunks/{chunk-FHPKHXHT.js → chunk-MKVZE4ZJ.js} +5 -1
  57. package/chunks/{chunk-4NEBUDWS.js → chunk-N2GJU6KI.js} +9 -9
  58. package/chunks/{chunk-N3RERU5N.js → chunk-NGJOAKJY.js} +3 -3
  59. package/chunks/chunk-NKLP3DCK.js +22 -0
  60. package/chunks/{chunk-K6VEL5KP.js → chunk-NKSF7HYF.js} +1 -1
  61. package/chunks/{chunk-LMNM37OE.js → chunk-O7V7N4G7.js} +1 -1
  62. package/chunks/{chunk-3H5RFLTS.js → chunk-ODPXHGLL.js} +2 -2
  63. package/chunks/{chunk-KH2BIJ4H.js → chunk-OP2OHZ7K.js} +267 -11
  64. package/chunks/{chunk-VOA2UPZM.js → chunk-OWSW7EYP.js} +5 -5
  65. package/chunks/{chunk-B2RBYP7Z.js → chunk-OXX3FG2N.js} +2 -2
  66. package/chunks/{chunk-RM3F46UT.js → chunk-PPUOYL77.js} +71 -6
  67. package/chunks/{chunk-ZPGMDT4S.js → chunk-PX5DNLSA.js} +19 -92
  68. package/chunks/{chunk-FEUXV3DC.js → chunk-Q5RM32DR.js} +1 -1
  69. package/chunks/{chunk-AEWOKOJY.js → chunk-QZKYZCN3.js} +1 -1
  70. package/chunks/{chunk-UKZEXS6K.js → chunk-R574ATRM.js} +11 -0
  71. package/chunks/{chunk-OGAARVN7.js → chunk-R5RBXLNC.js} +3 -3
  72. package/chunks/{chunk-5HWAJ4BK.js → chunk-RFUHDCNF.js} +1 -1
  73. package/chunks/{chunk-LSYVASZS.js → chunk-RHTTKJTP.js} +1 -1
  74. package/chunks/{chunk-LYPXNRBX.js → chunk-RK2EE3T7.js} +1 -1
  75. package/chunks/{chunk-CIUUYB23.js → chunk-S7CV4KO6.js} +3 -3
  76. package/chunks/{chunk-4NNR24EP.js → chunk-SKNE3KQC.js} +3 -3
  77. package/chunks/{chunk-JDNWIXHJ.js → chunk-SLSWFHDF.js} +3 -3
  78. package/chunks/{chunk-UWSXFI6E.js → chunk-SQC6K5HI.js} +6 -2
  79. package/chunks/{chunk-P3SLTSNY.js → chunk-SQOMTWQT.js} +4 -4
  80. package/chunks/{chunk-D7QAZ5DF.js → chunk-SUKEUUAA.js} +1 -1
  81. package/chunks/{chunk-WTM5QONI.js → chunk-T4JSA7KU.js} +2 -2
  82. package/chunks/{chunk-I7JDGNG6.js → chunk-T5U6IM4C.js} +6 -2
  83. package/chunks/{chunk-2JBETW4S.js → chunk-TGGEZINK.js} +5 -5
  84. package/chunks/{chunk-C6ARWPCG.js → chunk-TOGZTG4X.js} +2 -2
  85. package/chunks/{chunk-A6SX5TAH.js → chunk-TSERKWCX.js} +6 -2
  86. package/chunks/{chunk-IWGMDSC3.js → chunk-U4AF3QLU.js} +355 -17
  87. package/chunks/{chunk-PDDYAXL2.js → chunk-UGTWD35F.js} +14 -1
  88. package/chunks/{chunk-BWLUCN35.js → chunk-ULPQQSDN.js} +2 -2
  89. package/chunks/{chunk-GEP54CHX.js → chunk-VGFPFM4U.js} +3 -3
  90. package/chunks/{chunk-NLQIEY6W.js → chunk-VNQCPBC2.js} +3 -3
  91. package/chunks/{chunk-K2ETEWG6.js → chunk-VSALQLT7.js} +1 -1
  92. package/chunks/{chunk-YO4JI7B4.js → chunk-W5DXUTXD.js} +12 -6
  93. package/chunks/{chunk-M744OFVE.js → chunk-W7U4ESW2.js} +1 -1
  94. package/chunks/{chunk-TJ56V3AR.js → chunk-WNU7TZTO.js} +4 -4
  95. package/chunks/{chunk-GTM6IHBB.js → chunk-WO2B5E5B.js} +1 -0
  96. package/chunks/chunk-WOJZWRAZ.js +123 -0
  97. package/chunks/{chunk-5XNO4AW7.js → chunk-XA4TMLFN.js} +11 -11
  98. package/chunks/{chunk-GP4IMBX5.js → chunk-XM2O73NC.js} +1696 -476
  99. package/chunks/{chunk-IZ2CMEYO.js → chunk-YMBOEAF4.js} +1 -1
  100. package/chunks/{chunk-QONSRAEV.js → chunk-YP5KH5Y7.js} +1 -1
  101. package/chunks/{chunk-ZLJQIGCH.js → chunk-YZQTA3ID.js} +28 -11
  102. package/chunks/{computer-use-XDOGP2KA.js → computer-use-5HXJ2P4X.js} +11 -10
  103. package/chunks/{config-utils-E3ZQTHGM.js → config-utils-VXD4SPVR.js} +2 -2
  104. package/chunks/{contextCommand-N3LQDCYJ.js → contextCommand-7BEHRX2H.js} +14 -13
  105. package/chunks/{core-runtime-STAFP45W.js → core-runtime-U4VJ5TIV.js} +11 -10
  106. package/chunks/create-sub-session-QZNQY332.js +772 -0
  107. package/chunks/{daemon-CCIX4BMG.js → daemon-YI5RY62O.js} +115 -3
  108. package/chunks/{daemon-status-provider-PPAH3ATF.js → daemon-status-provider-UJUGTDTL.js} +21 -19
  109. package/chunks/{daemon-trust-policy-X6XL3MB4.js → daemon-trust-policy-ZHDD5VYQ.js} +17 -16
  110. package/chunks/{daemon-trust-policy-monitor-M5LBL2HD.js → daemon-trust-policy-monitor-5SHYBDGA.js} +17 -16
  111. package/chunks/{deferred-core-runtime-O54YPTBU.js → deferred-core-runtime-QWAEBTR5.js} +11 -10
  112. package/chunks/discovery-XLHITE74.js +220 -0
  113. package/chunks/{dist-IKGVITP3.js → dist-KS4O2NKE.js} +99 -6
  114. package/chunks/{earlyInputCapture-CBIT2RGT.js → earlyInputCapture-5BMSGKOI.js} +11 -10
  115. package/chunks/{edit-KW2TOC6A.js → edit-IVQQVYYK.js} +11 -10
  116. package/chunks/{enter-worktree-YL34NEU5.js → enter-worktree-P3DF2N6O.js} +11 -10
  117. package/chunks/{enterPlanMode-JFJTDASQ.js → enterPlanMode-KKTDVLAD.js} +11 -10
  118. package/chunks/{environment-IATU5GXG.js → environment-W5TRDM3G.js} +14 -13
  119. package/chunks/{errors-SAVRDE7Q.js → errors-GJJB36IG.js} +14 -13
  120. package/chunks/{exit-worktree-5M43QPBB.js → exit-worktree-LV5RLOCU.js} +11 -10
  121. package/chunks/{exitPlanMode-W4EICZ4N.js → exitPlanMode-TUGOB2LL.js} +11 -10
  122. package/chunks/external-tool-guard-provider-5HTGOPGU.js +273 -0
  123. package/chunks/{fast-path-YQJPIKMP.js → fast-path-2FU23PAQ.js} +5 -5
  124. package/chunks/{gemini-FUF5SSPR.js → gemini-KDULDQPP.js} +70 -55
  125. package/chunks/{geminiContentGenerator-LLLZBOUG.js → geminiContentGenerator-LOCFHNVA.js} +1 -1
  126. package/chunks/{glob-LJFFMN4D.js → glob-Z4NQFKWS.js} +11 -10
  127. package/chunks/{grep-Q64EAM73.js → grep-Z67Z7EN6.js} +11 -10
  128. package/chunks/{handleAutoUpdate-QBMPRTSQ.js → handleAutoUpdate-DFUZE6I6.js} +16 -15
  129. package/chunks/{i18n-LDUW5DMN.js → i18n-R4FMZ7N2.js} +13 -12
  130. package/chunks/{image-gen-G7RLG3KR.js → image-gen-4CQJPQP5.js} +1 -1
  131. package/chunks/{initializer-74A7DMMD.js → initializer-DK672ZVE.js} +17 -16
  132. package/chunks/{installationInfo-OFDYLDFD.js → installationInfo-PO4Z3ES7.js} +12 -11
  133. package/chunks/{list-JA2EWRHJ.js → list-QXQF6C53.js} +20 -19
  134. package/chunks/{loadedSettingsAdapter-2GNJOQ3L.js → loadedSettingsAdapter-4D7GUGW6.js} +17 -16
  135. package/chunks/{loggingContentGenerator-B6B5EGB3.js → loggingContentGenerator-2P3MNACM.js} +4 -3
  136. package/chunks/{managed-npm-update-4YZHLUER.js → managed-npm-update-LWVJQTWO.js} +24 -14
  137. package/chunks/{mcp-LLK5YJVH.js → mcp-GHETAR6V.js} +17 -16
  138. package/chunks/{monitor-NYJPAWVP.js → monitor-4T7W4M6Q.js} +11 -10
  139. package/chunks/{nonInteractiveCli-HZBBBDJD.js → nonInteractiveCli-P4UKCJFM.js} +47 -45
  140. package/chunks/{notebook-edit-LLM6UXFX.js → notebook-edit-OC2V7U5D.js} +11 -10
  141. package/chunks/{openaiContentGenerator-2AEXDN55.js → openaiContentGenerator-RAJUFWQY.js} +5 -4
  142. package/chunks/{pidfile-3JUWX7G2.js → pidfile-HVPYVRLS.js} +11 -10
  143. package/chunks/{processUtils-JLLRVZHT.js → processUtils-VIQ4JHVX.js} +2 -2
  144. package/chunks/{qwenContentGenerator-2FESCF27.js → qwenContentGenerator-2QLCUFNZ.js} +12 -11
  145. package/chunks/{qwenOAuth2-2KKPYA5J.js → qwenOAuth2-J2X3EN43.js} +1 -1
  146. package/chunks/{read-file-5GVXLY74.js → read-file-ZNAN2B26.js} +3 -2
  147. package/chunks/{record-artifact-L3BAHQFN.js → record-artifact-E6AWQU2Z.js} +1 -1
  148. package/chunks/{resumeHistoryUtils-Z4OFBXPM.js → resumeHistoryUtils-RCY7AL5Y.js} +15 -14
  149. package/chunks/{ripGrep-TKOJTIAB.js → ripGrep-C3KMLZM5.js} +11 -10
  150. package/chunks/{run-qwen-serve-K5W6MM6M.js → run-qwen-serve-3JN75CIQ.js} +518 -75
  151. package/chunks/{runtime-IADXMRCY.js → runtime-VUA4WQYD.js} +20 -19
  152. package/chunks/{scheduler-T7AV37LD.js → scheduler-Z4IBXCOY.js} +11 -10
  153. package/chunks/{serve-ROEGMS3I.js → serve-67T3P7XI.js} +18 -16
  154. package/chunks/{server-NGXMRODI.js → server-46ZO2CLV.js} +7556 -397
  155. package/chunks/{session-RCDLDJA4.js → session-B4IFAFC4.js} +50 -48
  156. package/chunks/{settings-E6H3AURD.js → settings-AI2BTBOZ.js} +16 -15
  157. package/chunks/{shell-RHGGJ6BE.js → shell-BKII3B6E.js} +11 -10
  158. package/chunks/{skill-GQ7PBS25.js → skill-6CFOWVGA.js} +4 -3
  159. package/chunks/{skill-settings-E2F7OCBL.js → skill-settings-6BFQWMW4.js} +16 -15
  160. package/chunks/{spawnChannel-Q2IQBHME.js → spawnChannel-FJXW35U7.js} +14 -12
  161. package/chunks/{standalone-update-NYYZEHLD.js → standalone-update-WVINHRNS.js} +14 -13
  162. package/chunks/{startInteractiveUI-IX6I3Y2Z.js → startInteractiveUI-YE57CJ57.js} +140 -138
  163. package/chunks/{stdioHelpers-HGIYTWAY.js → stdioHelpers-7A2EE64Z.js} +5 -3
  164. package/chunks/{task-create-2X4UP4F3.js → task-create-Q727MXHJ.js} +2 -2
  165. package/chunks/{task-list-WOLJPZ64.js → task-list-G6KPQ5JT.js} +1 -1
  166. package/chunks/{task-update-VDUSN36E.js → task-update-YB7PXO4R.js} +2 -2
  167. package/chunks/{team-create-UTRVUEDA.js → team-create-GNC7NFY6.js} +11 -10
  168. package/chunks/{team-delete-D2VMSDOT.js → team-delete-GDO7H2T4.js} +1 -1
  169. package/chunks/{team-plan-approval-YZNB4U5U.js → team-plan-approval-ECALJLIO.js} +11 -10
  170. package/chunks/{terminal-image-renderer-QRSCS2BE.js → terminal-image-renderer-II74TFVW.js} +11 -10
  171. package/chunks/{theme-manager-EKZBY34I.js → theme-manager-2MGEDD22.js} +11 -10
  172. package/chunks/{tool-search-N2R7LVMC.js → tool-search-S6LRRACA.js} +3 -2
  173. package/chunks/{total-session-admission-HBIYARPO.js → total-session-admission-7WVAWQPU.js} +17 -15
  174. package/chunks/{trustedFolders-ZQLYRCSY.js → trustedFolders-NFS4KHO4.js} +13 -12
  175. package/chunks/{update-relaunch-PJWXMPCR.js → update-relaunch-Q5HLZ6UW.js} +6 -6
  176. package/chunks/{updateCheck-ZXRCGY66.js → updateCheck-UOVZSTP7.js} +15 -14
  177. package/chunks/{useAutoAcceptIndicator-ZYQW5SGJ.js → useAutoAcceptIndicator-PC6MOJCO.js} +19 -18
  178. package/chunks/{validateNonInterActiveAuth-M7J7OTRH.js → validateNonInterActiveAuth-YMQP3ZM7.js} +44 -42
  179. package/chunks/{version-2AOHMPK3.js → version-T4AQFZJH.js} +1 -1
  180. package/chunks/{web-fetch-DS34CJ3R.js → web-fetch-JQCJHEEX.js} +3 -2
  181. package/chunks/{web-search-ITFPD7V4.js → web-search-QGZ2KY4G.js} +4 -3
  182. package/chunks/{workflow-LZQ4PGNS.js → workflow-YIT27YWZ.js} +12 -11
  183. package/chunks/{workspace-providers-status-ZPTLKOBM.js → workspace-providers-status-FIDZQ2PL.js} +20 -19
  184. package/chunks/{workspace-registration-store-OTFKSGG7.js → workspace-registration-store-RCSPE4DU.js} +1 -1
  185. package/chunks/{workspace-registry-YZ3OXPHP.js → workspace-registry-5PEUCY6E.js} +17 -15
  186. package/chunks/{workspace-service-OCZXOOSQ.js → workspace-service-LANDVPL7.js} +21 -20
  187. package/chunks/{workspace-skills-status-K2ALGUCD.js → workspace-skills-status-2IQVJNIN.js} +18 -17
  188. package/chunks/{workspace-trust-reconciler-KMRZISJP.js → workspace-trust-reconciler-CZPZZCZC.js} +26 -24
  189. package/chunks/{write-file-AWBI4GJS.js → write-file-G5VNGCOG.js} +11 -10
  190. package/chunks/{zoom-image-NHKN6F77.js → zoom-image-6RBLD7X6.js} +3 -2
  191. package/cli-entry.js +14 -0
  192. package/cli.js +19 -15
  193. package/package.json +3 -3
  194. package/web-shell/assets/{arc-Dpg66jw7.js → arc-B-Vtkp1W.js} +1 -1
  195. package/web-shell/assets/{architectureDiagram-3BPJPVTR-BJ4FIBxN.js → architectureDiagram-3BPJPVTR-qmTXIj64.js} +1 -1
  196. package/web-shell/assets/{blockDiagram-GPEHLZMM-VXmYdtYT.js → blockDiagram-GPEHLZMM-ZlnXEtdP.js} +1 -1
  197. package/web-shell/assets/{c4Diagram-AAUBKEIU-BNLDb8FE.js → c4Diagram-AAUBKEIU-Wyoirf6Z.js} +1 -1
  198. package/web-shell/assets/channel-DXaHnfjS.js +1 -0
  199. package/web-shell/assets/{chunk-2J33WTMH-DR5yHxr0.js → chunk-2J33WTMH-WQwbq9d8.js} +1 -1
  200. package/web-shell/assets/{chunk-4BX2VUAB-Czkq8UZU.js → chunk-4BX2VUAB-DQKnAXC4.js} +1 -1
  201. package/web-shell/assets/{chunk-55IACEB6-DugUgM6s.js → chunk-55IACEB6-DIdgXNcO.js} +1 -1
  202. package/web-shell/assets/{chunk-727SXJPM-Dw6quA1K.js → chunk-727SXJPM-CUwGOLEd.js} +1 -1
  203. package/web-shell/assets/{chunk-AQP2D5EJ-BoVYOTTz.js → chunk-AQP2D5EJ-CCBGZFZ4.js} +1 -1
  204. package/web-shell/assets/{chunk-FMBD7UC4--Amcse-j.js → chunk-FMBD7UC4-DMGXProk.js} +1 -1
  205. package/web-shell/assets/{chunk-ND2GUHAM-ajaEYAC8.js → chunk-ND2GUHAM-yejcFqsh.js} +1 -1
  206. package/web-shell/assets/{chunk-QZHKN3VN-CUNDZ7im.js → chunk-QZHKN3VN-DxCyqTf7.js} +1 -1
  207. package/web-shell/assets/classDiagram-4FO5ZUOK-BigU82A0.js +1 -0
  208. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-BigU82A0.js +1 -0
  209. package/web-shell/assets/{cose-bilkent-S5V4N54A-BnfkD-m1.js → cose-bilkent-S5V4N54A-DOrF3cg_.js} +1 -1
  210. package/web-shell/assets/{dagre-BM42HDAG-BuUdFk7Z.js → dagre-BM42HDAG-Be6CdYQ_.js} +1 -1
  211. package/web-shell/assets/{diagram-2AECGRRQ-B2ZXxoab.js → diagram-2AECGRRQ-B_Rjsft2.js} +1 -1
  212. package/web-shell/assets/{diagram-5GNKFQAL-BsgOICzM.js → diagram-5GNKFQAL-BvQbKSm1.js} +1 -1
  213. package/web-shell/assets/{diagram-KO2AKTUF-DTTjG6U-.js → diagram-KO2AKTUF-BsdmMdpS.js} +1 -1
  214. package/web-shell/assets/{diagram-LMA3HP47-temLf1x7.js → diagram-LMA3HP47-CrQlBaNr.js} +1 -1
  215. package/web-shell/assets/{diagram-OG6HWLK6-CRFjScs2.js → diagram-OG6HWLK6-aB1MD_DL.js} +1 -1
  216. package/web-shell/assets/{erDiagram-TEJ5UH35-D4oaY-rQ.js → erDiagram-TEJ5UH35-F-eUUn9n.js} +1 -1
  217. package/web-shell/assets/{flowDiagram-I6XJVG4X-BqG8B49b.js → flowDiagram-I6XJVG4X-DY_d8lUK.js} +1 -1
  218. package/web-shell/assets/{ganttDiagram-6RSMTGT7-DffQBdd5.js → ganttDiagram-6RSMTGT7-Dqu7KBPg.js} +1 -1
  219. package/web-shell/assets/{gitGraphDiagram-PVQCEYII-CMDPfseF.js → gitGraphDiagram-PVQCEYII-2IkU_VEj.js} +1 -1
  220. package/web-shell/assets/{index-qTQR_smP.js → index-BuSbqggh.js} +1 -1
  221. package/web-shell/assets/index-DrfC2e9s.js +1768 -0
  222. package/web-shell/assets/index-QQlUCJGg.css +5 -0
  223. package/web-shell/assets/{infoDiagram-5YYISTIA-Cn3T83MA.js → infoDiagram-5YYISTIA-C4orwzPw.js} +1 -1
  224. package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-ijFs7e25.js → ishikawaDiagram-YF4QCWOH-fXUEfef_.js} +1 -1
  225. package/web-shell/assets/{journeyDiagram-JHISSGLW-CINTv8VB.js → journeyDiagram-JHISSGLW-Bs0I1_J5.js} +1 -1
  226. package/web-shell/assets/{kanban-definition-UN3LZRKU-CMGRZCxx.js → kanban-definition-UN3LZRKU-BPRj1dw_.js} +1 -1
  227. package/web-shell/assets/{linear-Co-2_37d.js → linear-DFS6ZDpW.js} +1 -1
  228. package/web-shell/assets/{mermaid.core-DijbyaN5.js → mermaid.core-CZxjWMaO.js} +5 -5
  229. package/web-shell/assets/{mindmap-definition-RKZ34NQL-k_sDYDPZ.js → mindmap-definition-RKZ34NQL-Dsr3PmNs.js} +1 -1
  230. package/web-shell/assets/{pieDiagram-4H26LBE5-CG-oz8V3.js → pieDiagram-4H26LBE5-D5Ngpz5Y.js} +1 -1
  231. package/web-shell/assets/{quadrantDiagram-W4KKPZXB-Baq7HIG2.js → quadrantDiagram-W4KKPZXB-WJK9I25z.js} +1 -1
  232. package/web-shell/assets/{requirementDiagram-4Y6WPE33-CDM3ZvBt.js → requirementDiagram-4Y6WPE33-CmrYcwOH.js} +1 -1
  233. package/web-shell/assets/{sankeyDiagram-5OEKKPKP-C2dJbWKN.js → sankeyDiagram-5OEKKPKP-7SjCc4Mv.js} +1 -1
  234. package/web-shell/assets/{sequenceDiagram-3UESZ5HK-Cf1_h59p.js → sequenceDiagram-3UESZ5HK-Dl9E0IRM.js} +1 -1
  235. package/web-shell/assets/{stateDiagram-AJRCARHV-CI5clUEc.js → stateDiagram-AJRCARHV-CRRio7yV.js} +1 -1
  236. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-1atT7XaU.js +1 -0
  237. package/web-shell/assets/{timeline-definition-PNZ67QCA-B2O8cAZX.js → timeline-definition-PNZ67QCA-DVyXjjfq.js} +1 -1
  238. package/web-shell/assets/{vennDiagram-CIIHVFJN-BLsJ_h9s.js → vennDiagram-CIIHVFJN-Nb2Lckzq.js} +1 -1
  239. package/web-shell/assets/{wardley-L42UT6IY-BlPU4FTA.js → wardley-L42UT6IY-DuL_otIP.js} +1 -1
  240. package/web-shell/assets/{wardleyDiagram-YWT4CUSO-D_U-RdU4.js → wardleyDiagram-YWT4CUSO-DVI9Q4Aa.js} +1 -1
  241. package/web-shell/assets/{xychartDiagram-2RQKCTM6-BynXGSTl.js → xychartDiagram-2RQKCTM6-BNjAm7KD.js} +1 -1
  242. package/web-shell/index.html +2 -2
  243. package/bundled/review/DESIGN.md +0 -724
  244. package/chunks/create-sub-session-2B3JENWD.js +0 -400
  245. package/web-shell/assets/channel-Bd9wOnaA.js +0 -1
  246. package/web-shell/assets/classDiagram-4FO5ZUOK-XhCT8FE3.js +0 -1
  247. package/web-shell/assets/classDiagram-v2-Q7XG4LA2-XhCT8FE3.js +0 -1
  248. package/web-shell/assets/index-2F4RsNzd.js +0 -1756
  249. package/web-shell/assets/index-CaucmEO_.css +0 -5
  250. package/web-shell/assets/stateDiagram-v2-BHNVJYJU-BDc8asKD.js +0 -1
@@ -2,13 +2,13 @@
2
2
  "use strict";
3
3
  import {
4
4
  serveCommand
5
- } from "./chunk-SVIEVA3L.js";
5
+ } from "./chunk-GK2H5GRC.js";
6
6
  import {
7
7
  listCommand
8
- } from "./chunk-ZLGPHA34.js";
8
+ } from "./chunk-536SWZTA.js";
9
9
  import {
10
10
  getErrorMessage as getErrorMessage2
11
- } from "./chunk-VJPF6GF2.js";
11
+ } from "./chunk-FZFP7RZI.js";
12
12
  import {
13
13
  ObservedChannelContactStore
14
14
  } from "./chunk-DYA25Y7U.js";
@@ -24,6 +24,11 @@ import {
24
24
  import {
25
25
  isLoopbackBind
26
26
  } from "./chunk-RXFQM6FQ.js";
27
+ import {
28
+ collectProviderModelsForProtocol,
29
+ getAuthTypeFromEnv,
30
+ resolveCliGenerationConfig
31
+ } from "./chunk-5MJGUQU5.js";
27
32
  import {
28
33
  resolvePath
29
34
  } from "./chunk-JELIWHXD.js";
@@ -46,22 +51,17 @@ import {
46
51
  registerToolCallDispatch,
47
52
  selectFirstModel,
48
53
  sessionsPath
49
- } from "./chunk-VOA2UPZM.js";
54
+ } from "./chunk-OWSW7EYP.js";
50
55
  import {
51
56
  getExtensionManager,
52
57
  requestChoicePluginNonInteractive,
53
58
  requestConsentNonInteractive,
54
59
  requestConsentOrFail,
55
60
  resolveExtensionCommandScope
56
- } from "./chunk-3GOCVBYR.js";
57
- import {
58
- collectProviderModelsForProtocol,
59
- getAuthTypeFromEnv,
60
- resolveCliGenerationConfig
61
- } from "./chunk-WU5NBSLD.js";
61
+ } from "./chunk-G5NNWEYE.js";
62
62
  import {
63
63
  parseChannelConfig
64
- } from "./chunk-LEZFUYPM.js";
64
+ } from "./chunk-LAAFLUQV.js";
65
65
  import {
66
66
  findCliEntryPath
67
67
  } from "./chunk-V6WHFGST.js";
@@ -88,7 +88,7 @@ import {
88
88
  isChannelDeliveryMessage,
89
89
  isChannelStartupReportAckMessage,
90
90
  isChannelWebhookTaskMessage
91
- } from "./chunk-QONSRAEV.js";
91
+ } from "./chunk-YP5KH5Y7.js";
92
92
  import {
93
93
  normalizeWorkerDiagnostic,
94
94
  sanitizeWorkerDiagnostic
@@ -96,6 +96,9 @@ import {
96
96
  import {
97
97
  normalizeServeChannelSelection
98
98
  } from "./chunk-ALMR3E6Q.js";
99
+ import {
100
+ EXTERNAL_TOOL_GUARD_TOKEN_ENV
101
+ } from "./chunk-BKHCW2BI.js";
99
102
  import {
100
103
  redactLogCredentials
101
104
  } from "./chunk-M5GB774H.js";
@@ -128,29 +131,29 @@ import {
128
131
  assembleMcpServers,
129
132
  getPendingGatedMcpServers,
130
133
  mcpCommand
131
- } from "./chunk-SJMCYCUQ.js";
134
+ } from "./chunk-3UOLYNTF.js";
132
135
  import {
133
136
  loadSettings
134
- } from "./chunk-RM3F46UT.js";
137
+ } from "./chunk-PPUOYL77.js";
135
138
  import {
136
139
  detectSystemLanguage,
137
140
  getCurrentLanguage,
138
141
  initializeI18n,
139
142
  resolveLanguageSetting,
140
143
  t
141
- } from "./chunk-K64OI7S7.js";
144
+ } from "./chunk-KHUA3NQV.js";
142
145
  import {
143
146
  loadEnvironment
144
- } from "./chunk-LYPXNRBX.js";
147
+ } from "./chunk-RK2EE3T7.js";
145
148
  import {
146
149
  getCliVersion
147
- } from "./chunk-IZ2CMEYO.js";
150
+ } from "./chunk-YMBOEAF4.js";
148
151
  import {
149
152
  getPackageJson
150
153
  } from "./chunk-VJNBRRZG.js";
151
154
  import {
152
155
  isWorkspaceTrusted
153
- } from "./chunk-B3EBIZUY.js";
156
+ } from "./chunk-ACZRB7AD.js";
154
157
  import {
155
158
  require_strip_json_comments
156
159
  } from "./chunk-ALGGS7UH.js";
@@ -191,29 +194,30 @@ import {
191
194
  unquoteCStylePath,
192
195
  updateChannelMemoryEntry,
193
196
  updateSetting
194
- } from "./chunk-YLL3SC5S.js";
197
+ } from "./chunk-A4VTILZT.js";
195
198
  import {
196
199
  nextFireTime,
197
200
  parseCron
198
201
  } from "./chunk-IRH27ZC2.js";
199
202
  import {
200
203
  require_dist
201
- } from "./chunk-MISUEWB4.js";
204
+ } from "./chunk-4S4OEQ6U.js";
202
205
  import {
203
206
  parseBooleanEnvFlag,
204
207
  resolveTelemetrySettings
205
208
  } from "./chunk-MGSSJT27.js";
206
209
  import {
207
210
  getAllGeminiMdFilenames,
211
+ parseLineTolerant,
208
212
  setGeminiMdFilename
209
- } from "./chunk-ZPGMDT4S.js";
213
+ } from "./chunk-PX5DNLSA.js";
210
214
  import {
211
215
  SchemaValidator,
212
216
  ToolNames
213
217
  } from "./chunk-D7D7RXIJ.js";
214
218
  import {
215
219
  DEFAULT_QWEN_EMBEDDING_MODEL
216
- } from "./chunk-XD2PNH3K.js";
220
+ } from "./chunk-BOGOR4HB.js";
217
221
  import {
218
222
  isTlsVerificationDisabled
219
223
  } from "./chunk-BFHXDAR7.js";
@@ -234,8 +238,9 @@ import {
234
238
  import {
235
239
  writeStderrLine,
236
240
  writeStderrLineSafe,
237
- writeStdoutLine
238
- } from "./chunk-OWAK7LMW.js";
241
+ writeStdoutLine,
242
+ writeStdoutLineSafe
243
+ } from "./chunk-KGJGEEVR.js";
239
244
  import {
240
245
  init_esbuild_shims
241
246
  } from "./chunk-5O2XNYP6.js";
@@ -358,9 +363,9 @@ var require_command_exists = __commonJS({
358
363
  cleanInput = /* @__PURE__ */ __name(function(s) {
359
364
  var isPathName = /[\\]/.test(s);
360
365
  if (isPathName) {
361
- var dirname24 = '"' + path3.dirname(s) + '"';
366
+ var dirname25 = '"' + path3.dirname(s) + '"';
362
367
  var basename6 = '"' + path3.basename(s) + '"';
363
- return dirname24 + ":" + basename6;
368
+ return dirname25 + ":" + basename6;
364
369
  }
365
370
  return '"' + s + '"';
366
371
  }, "cleanInput");
@@ -368,10 +373,10 @@ var require_command_exists = __commonJS({
368
373
  module.exports = /* @__PURE__ */ __name(function commandExists2(commandName, callback) {
369
374
  var cleanedCommandName = cleanInput(commandName);
370
375
  if (!callback && typeof Promise !== "undefined") {
371
- return new Promise(function(resolve24, reject) {
376
+ return new Promise(function(resolve25, reject) {
372
377
  commandExists2(commandName, function(error, output) {
373
378
  if (output) {
374
- resolve24(commandName);
379
+ resolve25(commandName);
375
380
  } else {
376
381
  reject(error);
377
382
  }
@@ -700,6 +705,7 @@ import { join, resolve } from "node:path";
700
705
  var REVIEW_TMP_DIR = join(".qwen", "tmp");
701
706
  var REVIEWS_DIR = join(".qwen", "reviews");
702
707
  var REVIEW_CACHE_DIR = join(".qwen", "review-cache");
708
+ var LEASE_PREFIX = "qwen-review-lease-";
703
709
  var PARSE_ARGS_REPORT = join(
704
710
  REVIEW_TMP_DIR,
705
711
  "qwen-review-parse-args.json"
@@ -735,7 +741,6 @@ function tmpPrefix(target) {
735
741
  __name(tmpPrefix, "tmpPrefix");
736
742
 
737
743
  // packages/cli/src/services/review-worktree-lease.ts
738
- var LEASE_PREFIX = "qwen-review-lease-";
739
744
  var GIT_TIMEOUT_MS = 12e4;
740
745
  var debugLogger = createDebugLogger("REVIEW_WORKTREE_LEASE");
741
746
  function gitOptions(timeout) {
@@ -2255,8 +2260,8 @@ function createBridgeReadinessGate() {
2255
2260
  current: /* @__PURE__ */ __name(() => pending, "current"),
2256
2261
  block: /* @__PURE__ */ __name(() => {
2257
2262
  if (pending) return;
2258
- pending = new Promise((resolve24) => {
2259
- releasePending = resolve24;
2263
+ pending = new Promise((resolve25) => {
2264
+ releasePending = resolve25;
2260
2265
  });
2261
2266
  }, "block"),
2262
2267
  release: /* @__PURE__ */ __name(() => {
@@ -2338,7 +2343,7 @@ function createBridgeRecovery(options) {
2338
2343
  writeStderrLine(
2339
2344
  `[Channel] Bridge crashed (${recentCrashCount}/${MAX_CRASH_RESTARTS} in window). Restarting in ${RESTART_DELAY_MS / 1e3}s...`
2340
2345
  );
2341
- await new Promise((resolve24) => setTimeout(resolve24, RESTART_DELAY_MS));
2346
+ await new Promise((resolve25) => setTimeout(resolve25, RESTART_DELAY_MS));
2342
2347
  const bridge = new AcpBridge(bridgeOpts);
2343
2348
  setBridge(bridge);
2344
2349
  attachDisconnectHandler(bridge);
@@ -2656,7 +2661,7 @@ var stopCommand = {
2656
2661
  if (argv["daemon-url"]) {
2657
2662
  const token = argv.token ?? process.env[QWEN_SERVER_TOKEN_ENV] ?? process.env[QWEN_DAEMON_TOKEN_ENV];
2658
2663
  try {
2659
- const sdk = await import("./daemon-CCIX4BMG.js");
2664
+ const sdk = await import("./daemon-YI5RY62O.js");
2660
2665
  const client = new sdk.DaemonClient({
2661
2666
  baseUrl: argv["daemon-url"],
2662
2667
  ...token ? { token } : {}
@@ -2770,7 +2775,7 @@ function formatChannelStartupFailures(source, fallbackWorkspaceCwd) {
2770
2775
  } catch {
2771
2776
  return [];
2772
2777
  }
2773
- const lines2 = [];
2778
+ const lines = [];
2774
2779
  for (const raw of limitedFailures) {
2775
2780
  try {
2776
2781
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
@@ -2802,16 +2807,16 @@ function formatChannelStartupFailures(source, fallbackWorkspaceCwd) {
2802
2807
  "phase=connect",
2803
2808
  ...code ? [`code=${code}`] : []
2804
2809
  ];
2805
- lines2.push(
2810
+ lines.push(
2806
2811
  `[Channel] Startup failure (${fields.join(", ")}): ${safeMessage}`
2807
2812
  );
2808
2813
  } catch {
2809
2814
  }
2810
2815
  }
2811
2816
  if (rawTruncated === true || rawFailureCount > MAX_CHANNEL_STARTUP_FAILURES) {
2812
- lines2.push("[Channel] Additional startup failures were truncated.");
2817
+ lines.push("[Channel] Additional startup failures were truncated.");
2813
2818
  }
2814
- return lines2;
2819
+ return lines;
2815
2820
  }
2816
2821
  __name(formatChannelStartupFailures, "formatChannelStartupFailures");
2817
2822
 
@@ -2842,7 +2847,7 @@ var statusCommand = {
2842
2847
  if (argv["daemon-url"]) {
2843
2848
  const token = argv.token ?? process.env[QWEN_SERVER_TOKEN_ENV] ?? process.env[QWEN_DAEMON_TOKEN_ENV];
2844
2849
  try {
2845
- const sdk = await import("./daemon-CCIX4BMG.js");
2850
+ const sdk = await import("./daemon-YI5RY62O.js");
2846
2851
  const client = new sdk.DaemonClient({
2847
2852
  baseUrl: argv["daemon-url"],
2848
2853
  ...token ? { token } : {}
@@ -2953,7 +2958,7 @@ var reloadCommand = {
2953
2958
  const token = resolveToken(argv.token);
2954
2959
  let sdk;
2955
2960
  try {
2956
- sdk = await import("./daemon-CCIX4BMG.js");
2961
+ sdk = await import("./daemon-YI5RY62O.js");
2957
2962
  } catch (err) {
2958
2963
  writeStderrLine(
2959
2964
  `[Channel] Failed to load daemon SDK: ${safeChannelCommandErrorMessage(err)}`
@@ -3033,7 +3038,7 @@ var setCommand2 = {
3033
3038
  const selection = names[0] === "all" ? { mode: "all" } : { mode: "names", names };
3034
3039
  let sdk;
3035
3040
  try {
3036
- sdk = await import("./daemon-CCIX4BMG.js");
3041
+ sdk = await import("./daemon-YI5RY62O.js");
3037
3042
  } catch (error) {
3038
3043
  writeStderrLine(
3039
3044
  `[Channel] Failed to load daemon SDK: ${safeChannelCommandErrorMessage(error)}`
@@ -3159,7 +3164,7 @@ function createDaemonChannelBridgeFacade(bridge, opts) {
3159
3164
  }
3160
3165
  __name(createDaemonChannelBridgeFacade, "createDaemonChannelBridgeFacade");
3161
3166
  async function loadDaemonSdk() {
3162
- return await import("./daemon-CCIX4BMG.js");
3167
+ return await import("./daemon-YI5RY62O.js");
3163
3168
  }
3164
3169
  __name(loadDaemonSdk, "loadDaemonSdk");
3165
3170
  function selectedChannelNames(channelsConfig, selection) {
@@ -3206,12 +3211,12 @@ async function abortableStartup(value, signal) {
3206
3211
  const promise = Promise.resolve(value);
3207
3212
  if (!signal) return await promise;
3208
3213
  if (signal.aborted) throw startupAbortError();
3209
- return await new Promise((resolve24, reject) => {
3214
+ return await new Promise((resolve25, reject) => {
3210
3215
  const onAbort = /* @__PURE__ */ __name(() => {
3211
3216
  reject(startupAbortError());
3212
3217
  }, "onAbort");
3213
3218
  signal.addEventListener("abort", onAbort, { once: true });
3214
- promise.then(resolve24, reject).finally(() => {
3219
+ promise.then(resolve25, reject).finally(() => {
3215
3220
  signal.removeEventListener("abort", onAbort);
3216
3221
  });
3217
3222
  });
@@ -3585,6 +3590,7 @@ function scrubDaemonWorkerEnv() {
3585
3590
  delete process.env[QWEN_DAEMON_URL_ENV];
3586
3591
  delete process.env[QWEN_DAEMON_WORKSPACE_ENV];
3587
3592
  delete process.env[QWEN_SERVER_TOKEN_ENV];
3593
+ delete process.env[EXTERNAL_TOOL_GUARD_TOKEN_ENV];
3588
3594
  }
3589
3595
  __name(scrubDaemonWorkerEnv, "scrubDaemonWorkerEnv");
3590
3596
  function readDaemonWorkerEnv() {
@@ -3619,7 +3625,7 @@ function reportStartupToSupervisor(message, signal, subscribeMessage = (listener
3619
3625
  if (!send) {
3620
3626
  return Promise.reject(new Error("Channel worker IPC is unavailable."));
3621
3627
  }
3622
- return new Promise((resolve24, reject) => {
3628
+ return new Promise((resolve25, reject) => {
3623
3629
  let settled = false;
3624
3630
  let unsubscribeMessage = /* @__PURE__ */ __name(() => {
3625
3631
  }, "unsubscribeMessage");
@@ -3633,7 +3639,7 @@ function reportStartupToSupervisor(message, signal, subscribeMessage = (listener
3633
3639
  settled = true;
3634
3640
  cleanup();
3635
3641
  if (error) reject(error);
3636
- else resolve24();
3642
+ else resolve25();
3637
3643
  }, "finish");
3638
3644
  const onMessage = /* @__PURE__ */ __name((value) => {
3639
3645
  if (isChannelStartupReportAckMessage(value)) {
@@ -3881,8 +3887,8 @@ var daemonWorkerCommand = {
3881
3887
  let shuttingDown = false;
3882
3888
  let exitCode = 0;
3883
3889
  let finish;
3884
- const finished = new Promise((resolve24) => {
3885
- finish = resolve24;
3890
+ const finished = new Promise((resolve25) => {
3891
+ finish = resolve25;
3886
3892
  });
3887
3893
  const shutdown = /* @__PURE__ */ __name(async (reason) => {
3888
3894
  if (shuttingDown) {
@@ -3910,8 +3916,8 @@ var daemonWorkerCommand = {
3910
3916
  ...activeChannelDeliveries.values(),
3911
3917
  ...activeWebhookTasks.values()
3912
3918
  ]),
3913
- new Promise((resolve24) => {
3914
- const timer = setTimeout(resolve24, WORKER_SHUTDOWN_DRAIN_MS);
3919
+ new Promise((resolve25) => {
3920
+ const timer = setTimeout(resolve25, WORKER_SHUTDOWN_DRAIN_MS);
3915
3921
  timer.unref();
3916
3922
  })
3917
3923
  ]);
@@ -4547,12 +4553,12 @@ var parseArgsCommand = {
4547
4553
 
4548
4554
  // packages/cli/src/commands/review/compose-review.ts
4549
4555
  init_esbuild_shims();
4550
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
4551
- import { dirname as dirname4, join as join8 } from "node:path";
4556
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
4557
+ import { dirname as dirname4, join as join9 } from "node:path";
4552
4558
 
4553
4559
  // packages/cli/src/commands/review/lib/coverage.ts
4554
4560
  init_esbuild_shims();
4555
- import { readFileSync as readFileSync7, statSync as statSync2 } from "node:fs";
4561
+ import { readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
4556
4562
 
4557
4563
  // packages/cli/src/commands/review/lib/transcripts.ts
4558
4564
  init_esbuild_shims();
@@ -4563,7 +4569,7 @@ var TranscriptsUnavailableError = class extends Error {
4563
4569
  __name(this, "TranscriptsUnavailableError");
4564
4570
  }
4565
4571
  };
4566
- function transcriptDir(env = process.env) {
4572
+ function transcriptPaths(env = process.env) {
4567
4573
  const projectDir = env["QWEN_CODE_PROJECT_DIR"]?.trim();
4568
4574
  const sessionId = env["QWEN_CODE_SESSION_ID"]?.trim();
4569
4575
  if (!projectDir || !sessionId) {
@@ -4571,7 +4577,15 @@ function transcriptDir(env = process.env) {
4571
4577
  "the CLI did not export QWEN_CODE_PROJECT_DIR / QWEN_CODE_SESSION_ID, so this run cannot find the harness's record of what its agents did"
4572
4578
  );
4573
4579
  }
4574
- return join5(projectDir, "subagents", sessionId);
4580
+ return {
4581
+ projectDir,
4582
+ sessionId,
4583
+ dir: join5(projectDir, "subagents", sessionId)
4584
+ };
4585
+ }
4586
+ __name(transcriptPaths, "transcriptPaths");
4587
+ function transcriptDir(env = process.env) {
4588
+ return transcriptPaths(env).dir;
4575
4589
  }
4576
4590
  __name(transcriptDir, "transcriptDir");
4577
4591
  function textOf(rec) {
@@ -4602,8 +4616,8 @@ function parseTranscript(file, diffPath) {
4602
4616
  } catch {
4603
4617
  return null;
4604
4618
  }
4605
- const lines2 = raw.split("\n").filter((l) => l.trim());
4606
- if (lines2.length === 0) return null;
4619
+ const lines = raw.split("\n").filter((l) => l.trim());
4620
+ if (lines.length === 0) return null;
4607
4621
  let agentId = "";
4608
4622
  let agentName = "";
4609
4623
  let launchPrompt = "";
@@ -4614,7 +4628,7 @@ function parseTranscript(file, diffPath) {
4614
4628
  const successfulCallArgs = [];
4615
4629
  const byId = /* @__PURE__ */ new Map();
4616
4630
  const anonymous = [];
4617
- for (const line of lines2) {
4631
+ for (const line of lines) {
4618
4632
  let rec;
4619
4633
  try {
4620
4634
  rec = JSON.parse(line);
@@ -4688,11 +4702,15 @@ function parseTranscript(file, diffPath) {
4688
4702
  };
4689
4703
  }
4690
4704
  __name(parseTranscript, "parseTranscript");
4705
+ function listAgentTranscriptFiles(dir) {
4706
+ return readdirSync2(dir).filter((name) => name.endsWith(".jsonl"));
4707
+ }
4708
+ __name(listAgentTranscriptFiles, "listAgentTranscriptFiles");
4691
4709
  function readTranscripts(since, env = process.env, diffPath) {
4692
4710
  const dir = transcriptDir(env);
4693
4711
  let names;
4694
4712
  try {
4695
- names = readdirSync2(dir);
4713
+ names = listAgentTranscriptFiles(dir);
4696
4714
  } catch (err) {
4697
4715
  throw new TranscriptsUnavailableError(
4698
4716
  `no subagent transcripts at ${dir} (${err.message}). The harness writes one per agent; if there are none, either no agents ran or the harness could not write them.`
@@ -4700,7 +4718,6 @@ function readTranscripts(since, env = process.env, diffPath) {
4700
4718
  }
4701
4719
  const out = [];
4702
4720
  for (const name of names) {
4703
- if (!name.endsWith(".jsonl")) continue;
4704
4721
  const rec = parseTranscript(join5(dir, name), diffPath);
4705
4722
  if (!rec) continue;
4706
4723
  if (since !== void 0 && rec.mtimeMs < since) continue;
@@ -4718,7 +4735,13 @@ __name(wasGivenTheDiff, "wasGivenTheDiff");
4718
4735
 
4719
4736
  // packages/cli/src/commands/review/lib/prompt-record.ts
4720
4737
  init_esbuild_shims();
4721
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, readdirSync as readdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
4738
+ import {
4739
+ mkdirSync as mkdirSync3,
4740
+ readFileSync as readFileSync5,
4741
+ readdirSync as readdirSync3,
4742
+ statSync as statSync2,
4743
+ writeFileSync as writeFileSync3
4744
+ } from "node:fs";
4722
4745
  import { dirname as dirname2, join as join6, basename as basename3, resolve as resolve3 } from "node:path";
4723
4746
  function promptRecordDir(planPath) {
4724
4747
  const p = resolve3(planPath);
@@ -4760,7 +4783,7 @@ function recordPrompt(planPath, key, prompt) {
4760
4783
  }
4761
4784
  }
4762
4785
  __name(recordPrompt, "recordPrompt");
4763
- function readRecordedPrompts(planPath) {
4786
+ function readRecordedPrompts(planPath, sinceMs) {
4764
4787
  const out = /* @__PURE__ */ new Map();
4765
4788
  const dir = promptRecordDir(planPath);
4766
4789
  let names;
@@ -4778,7 +4801,9 @@ function readRecordedPrompts(planPath) {
4778
4801
  } catch {
4779
4802
  continue;
4780
4803
  }
4781
- out.set(key, readFileSync5(join6(dir, name), "utf8"));
4804
+ const file = join6(dir, name);
4805
+ if (sinceMs !== void 0 && statSync2(file).mtimeMs < sinceMs) continue;
4806
+ out.set(key, readFileSync5(file, "utf8"));
4782
4807
  } catch {
4783
4808
  }
4784
4809
  }
@@ -4786,25 +4811,33 @@ function readRecordedPrompts(planPath) {
4786
4811
  }
4787
4812
  __name(readRecordedPrompts, "readRecordedPrompts");
4788
4813
  function wasDeliveredVerbatim(launchPrompt, built) {
4814
+ return deliveredVerbatim(flattenPrompt(launchPrompt), built);
4815
+ }
4816
+ __name(wasDeliveredVerbatim, "wasDeliveredVerbatim");
4817
+ function deliveredVerbatim(flattenedLaunch, built) {
4789
4818
  if (built.trim().length === 0) return false;
4790
- const delivered = flatten(launchPrompt);
4819
+ return deliveredVerbatimLines(flattenedLaunch, promptLines(built));
4820
+ }
4821
+ __name(deliveredVerbatim, "deliveredVerbatim");
4822
+ function deliveredVerbatimLines(flattenedLaunch, builtLines) {
4823
+ if (builtLines.length === 0) return false;
4791
4824
  let at = 0;
4792
- for (const line of lines(built)) {
4793
- const i = delivered.indexOf(line, at);
4825
+ for (const line of builtLines) {
4826
+ const i = flattenedLaunch.indexOf(line, at);
4794
4827
  if (i === -1) return false;
4795
4828
  at = i + line.length;
4796
4829
  }
4797
4830
  return true;
4798
4831
  }
4799
- __name(wasDeliveredVerbatim, "wasDeliveredVerbatim");
4800
- function flatten(s) {
4832
+ __name(deliveredVerbatimLines, "deliveredVerbatimLines");
4833
+ function flattenPrompt(s) {
4801
4834
  return s.replace(/\s+/g, " ").trim();
4802
4835
  }
4803
- __name(flatten, "flatten");
4804
- function lines(built) {
4805
- return built.split("\n").map((l) => flatten(l)).filter((l) => l.length > 0);
4836
+ __name(flattenPrompt, "flattenPrompt");
4837
+ function promptLines(built) {
4838
+ return built.split("\n").map((l) => flattenPrompt(l)).filter((l) => l.length > 0);
4806
4839
  }
4807
- __name(lines, "lines");
4840
+ __name(promptLines, "promptLines");
4808
4841
 
4809
4842
  // packages/cli/src/commands/review/lib/roster.ts
4810
4843
  init_esbuild_shims();
@@ -4908,9 +4941,9 @@ function splitHeaderPaths(rest) {
4908
4941
  }
4909
4942
  __name(splitHeaderPaths, "splitHeaderPaths");
4910
4943
  function parseDiff(diffText) {
4911
- const lines2 = diffText.split("\n");
4912
- if (lines2.length > 0 && lines2[lines2.length - 1] === "") lines2.pop();
4913
- const total = lines2.length;
4944
+ const lines = diffText.split("\n");
4945
+ if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
4946
+ const total = lines.length;
4914
4947
  const files = [];
4915
4948
  let cur = null;
4916
4949
  let curHunk = null;
@@ -4937,7 +4970,7 @@ function parseDiff(diffText) {
4937
4970
  }
4938
4971
  }, "closeFile");
4939
4972
  for (let i = 0; i < total; i++) {
4940
- const line = lines2[i];
4973
+ const line = lines[i];
4941
4974
  const n = i + 1;
4942
4975
  if (line.startsWith("diff --git ")) {
4943
4976
  closeFile(n - 1);
@@ -5016,9 +5049,9 @@ function parseDiff(diffText) {
5016
5049
  }
5017
5050
  __name(parseDiff, "parseDiff");
5018
5051
  var MIN_SPLIT_SEGMENT = 40;
5019
- function isSafeSplitPoint(lines2, n) {
5020
- const cur = lines2[n - 1];
5021
- const prev = lines2[n - 2];
5052
+ function isSafeSplitPoint(lines, n) {
5053
+ const cur = lines[n - 1];
5054
+ const prev = lines[n - 2];
5022
5055
  if (cur === void 0 || prev === void 0) return false;
5023
5056
  const isNewSide = /* @__PURE__ */ __name((l) => l === "" || /^[+ ]/.test(l), "isNewSide");
5024
5057
  if (!isNewSide(cur) || !isNewSide(prev)) return false;
@@ -5028,9 +5061,9 @@ function isSafeSplitPoint(lines2, n) {
5028
5061
  return prev === "" || /^\s*$/.test(prev.slice(1));
5029
5062
  }
5030
5063
  __name(isSafeSplitPoint, "isSafeSplitPoint");
5031
- function charPrefix(lines2) {
5032
- const p = new Array(lines2.length + 1).fill(0);
5033
- for (let i = 0; i < lines2.length; i++) p[i + 1] = p[i] + lines2[i].length + 1;
5064
+ function charPrefix(lines) {
5065
+ const p = new Array(lines.length + 1).fill(0);
5066
+ for (let i = 0; i < lines.length; i++) p[i + 1] = p[i] + lines[i].length + 1;
5034
5067
  return p;
5035
5068
  }
5036
5069
  __name(charPrefix, "charPrefix");
@@ -5038,14 +5071,14 @@ function charsIn(prefix, s, e) {
5038
5071
  return prefix[e] - prefix[s - 1];
5039
5072
  }
5040
5073
  __name(charsIn, "charsIn");
5041
- function splitUnit(unit, lines2, prefix, maxChunkLines, bodyStart) {
5074
+ function splitUnit(unit, lines, prefix, maxChunkLines, bodyStart) {
5042
5075
  const over = /* @__PURE__ */ __name((s, e) => e - s + 1 > maxChunkLines || charsIn(prefix, s, e) > MAX_CHUNK_CHARS, "over");
5043
5076
  const bigEnough = /* @__PURE__ */ __name((s, e) => e - s + 1 >= MIN_SPLIT_SEGMENT || charsIn(prefix, s, e) >= MAX_CHUNK_CHARS / 2, "bigEnough");
5044
5077
  if (!over(unit.start, unit.end)) return [unit];
5045
5078
  const newLineOf = /* @__PURE__ */ new Map();
5046
5079
  let newLine = unit.newStart;
5047
5080
  for (let n = bodyStart; n <= unit.end; n++) {
5048
- const c = lines2[n - 1]?.[0];
5081
+ const c = lines[n - 1]?.[0];
5049
5082
  if (c === " " || c === "+") newLineOf.set(n, newLine++);
5050
5083
  }
5051
5084
  const out = [];
@@ -5054,7 +5087,7 @@ function splitUnit(unit, lines2, prefix, maxChunkLines, bodyStart) {
5054
5087
  const upper = Math.min(unit.end, segStart + maxChunkLines - 1);
5055
5088
  let cut = -1;
5056
5089
  for (let n = upper; n > bodyStart; n--) {
5057
- if (!isSafeSplitPoint(lines2, n)) continue;
5090
+ if (!isSafeSplitPoint(lines, n)) continue;
5058
5091
  if (over(segStart, n - 1)) continue;
5059
5092
  if (!bigEnough(segStart, n - 1)) continue;
5060
5093
  cut = n;
@@ -5062,7 +5095,7 @@ function splitUnit(unit, lines2, prefix, maxChunkLines, bodyStart) {
5062
5095
  }
5063
5096
  if (cut < 0) {
5064
5097
  for (let n = upper + 1; n <= unit.end; n++) {
5065
- if (isSafeSplitPoint(lines2, n) && bigEnough(segStart, n - 1)) {
5098
+ if (isSafeSplitPoint(lines, n) && bigEnough(segStart, n - 1)) {
5066
5099
  cut = n;
5067
5100
  break;
5068
5101
  }
@@ -5088,10 +5121,10 @@ function splitUnit(unit, lines2, prefix, maxChunkLines, bodyStart) {
5088
5121
  return out;
5089
5122
  }
5090
5123
  __name(splitUnit, "splitUnit");
5091
- function planChunks(files, lines2, maxChunkLines = DEFAULT_MAX_CHUNK_LINES) {
5092
- const diffLines = lines2.length;
5124
+ function planChunks(files, lines, maxChunkLines = DEFAULT_MAX_CHUNK_LINES) {
5125
+ const diffLines = lines.length;
5093
5126
  if (diffLines === 0) return [];
5094
- const prefix = charPrefix(lines2);
5127
+ const prefix = charPrefix(lines);
5095
5128
  const units = [];
5096
5129
  for (const f of files) {
5097
5130
  if (f.hunks.length === 0) {
@@ -5115,7 +5148,7 @@ function planChunks(files, lines2, maxChunkLines = DEFAULT_MAX_CHUNK_LINES) {
5115
5148
  newEnd: h.newEnd
5116
5149
  };
5117
5150
  units.push(
5118
- ...splitUnit(unit, lines2, prefix, maxChunkLines, h.diffStart + 1)
5151
+ ...splitUnit(unit, lines, prefix, maxChunkLines, h.diffStart + 1)
5119
5152
  );
5120
5153
  });
5121
5154
  }
@@ -5127,7 +5160,7 @@ function planChunks(files, lines2, maxChunkLines = DEFAULT_MAX_CHUNK_LINES) {
5127
5160
  cur.chars = charsIn(prefix, cur.startLine, cur.endLine);
5128
5161
  let widest = 0;
5129
5162
  for (let n = cur.startLine; n <= cur.endLine; n++) {
5130
- if (lines2[n - 1].length > widest) widest = lines2[n - 1].length;
5163
+ if (lines[n - 1].length > widest) widest = lines[n - 1].length;
5131
5164
  }
5132
5165
  cur.maxLineChars = widest;
5133
5166
  chunks.push(cur);
@@ -5170,10 +5203,10 @@ function planChunks(files, lines2, maxChunkLines = DEFAULT_MAX_CHUNK_LINES) {
5170
5203
  __name(planChunks, "planChunks");
5171
5204
  function buildDiffPlan(diffText, maxChunkLines = DEFAULT_MAX_CHUNK_LINES) {
5172
5205
  const { files, diffLines } = parseDiff(diffText);
5173
- const lines2 = diffText.split("\n");
5174
- if (lines2.length > 0 && lines2[lines2.length - 1] === "") lines2.pop();
5206
+ const lines = diffText.split("\n");
5207
+ if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
5175
5208
  const linesOf = /* @__PURE__ */ __name((kind) => files.filter((f) => f.kind === kind).reduce((n, f) => n + (f.diffEnd - f.diffStart + 1), 0), "linesOf");
5176
- const chunks = planChunks(files, lines2, maxChunkLines);
5209
+ const chunks = planChunks(files, lines, maxChunkLines);
5177
5210
  if (!chunksCoverDiff(chunks, diffLines)) {
5178
5211
  throw new Error(
5179
5212
  `diff-plan: chunks do not tile the diff (${chunks.length} chunks over ${diffLines} lines). Refusing to plan a review with a coverage hole.`
@@ -5692,6 +5725,7 @@ __name(requiredAgents, "requiredAgents");
5692
5725
 
5693
5726
  // packages/cli/src/commands/review/lib/agent-briefs.ts
5694
5727
  init_esbuild_shims();
5728
+ var REVERSE_AUDIT_EXAMPLE_RECEIPT = "No issues found \u2014 re-walked the reconnect state machine and the two changed exports' call sites; every gap I checked was already in the list";
5695
5729
  var BRIEFS = {
5696
5730
  "0": {
5697
5731
  label: "Agent 0: Issue fidelity & root-cause ownership",
@@ -5990,11 +6024,11 @@ You are undirected on purpose. Do not restrict yourself to the list.`
5990
6024
  readsDiff: false,
5991
6025
  brief: `You are **Agent 7: Build & Test Verification**. You do not review the diff \u2014 you run the project's own deterministic checks and report what they say. Your evidence is **the commands you ran and their output**; a return that names no command has not done this job.
5992
6026
 
5993
- **Run \`qwen review build-test\` (the exact command, with its \`--plan\` and \`--worktree\`, is below).** It installs if needed, then builds only the workspaces the diff changes plus everything they compile against, and tests the changed ones \u2014 reading the plan for what changed and the root \`package.json\` for the workspace layout. Do **not** substitute \`npm run build\` / \`npm test\` by hand. The old brief did, with a 120-second deadline, and this repo's cold full build is 125 seconds: measured across the harness's own transcripts, that command timed out **71 times** and verified nothing. \`build-test\` scopes the build, gives it a deadline it can meet, and \u2014 this is the part a hand-run command gets wrong \u2014 reports a timeout as **infrastructure, not a finding**. A build that runs out of time is never a Critical against someone's pull request.
6027
+ **Run \`qwen review build-test\` (the exact command, with its \`--plan\` and \`--worktree\`, is below).** It installs if needed, then builds only the workspaces the diff changes plus everything they compile against, and tests the changed ones plus every workspace that depends on them \u2014 reading the plan for what changed and the root \`package.json\` for the workspace layout. Do **not** substitute \`npm run build\` / \`npm test\` by hand. The old brief did, with a 120-second deadline, and this repo's cold full build is 125 seconds: measured across the harness's own transcripts, that command timed out **71 times** and verified nothing. \`build-test\` scopes the build, gives it a deadline it can meet, and \u2014 this is the part a hand-run command gets wrong \u2014 reports a timeout as **infrastructure, not a finding**. A build that runs out of time is never a Critical against someone's pull request.
5994
6028
 
5995
6029
  Read the JSON it prints:
5996
6030
 
5997
- - \`toolchain: "npm"\` \u2192 use its \`build[]\` / \`test[]\` results. A failure in a file **the diff changed** is a **Critical** (\`Source: [build]\` or \`[test]\`); a failure in a file it did **not** touch is pre-existing \u2014 say so, do not file it against this PR. A non-empty \`timedOut\`, or a failed \`install\`, is environment/infrastructure \u2014 informational, never a Critical. On \`ok: true\`, name the workspaces built and the commands run; a return that names no command is a whiff.
6031
+ - \`toolchain: "npm"\` \u2192 use its \`build[]\` / \`test[]\` results. A failure in a file **the diff changed** is a **Critical** (\`Source: [build]\` or \`[test]\`); a failure in a file it did **not** touch is pre-existing \u2014 say so, do not file it against this PR. A non-empty \`timedOut\`, or a failed \`install\`, is environment/infrastructure \u2014 informational, never a Critical. On \`ok: true\`, name the workspaces built and the commands run; a return that names no command is a whiff. Report the TEST coverage from \`testScope\`, never from assumption. \`testScope.workspaces\` lists exactly the suites that ran \u2014 say "tests scoped to <list> \u2014 the changed workspaces and their declared dependents that define a test script". \`testScope.notRun\`, when present, names suites the whole-call budget stopped before they ran \u2014 say they did not run, never fold them into the coverage. When \`testScope.caveat\` is present, the scope may be incomplete \u2014 quote the caveat and say exactly that. A green run is a claim about those suites only \u2014 do not phrase it as the whole suite passing.
5998
6032
  - **When any \`test[]\` command failed (exit non-zero, not a timeout), MEASURE which failures are the PR's before ruling by path.** The path rule above misclassifies in both directions \u2014 an environment-flaky test in a touched file gets filed as a Critical it did not cause, and a PR that breaks a test in an UNTOUCHED file gets waved through as pre-existing. The measurement is two commands: \`qwen review base-tree --plan <plan> --worktree <worktree> --out <plan dir>/qwen-review-pr-<n>-base-tree.json\` (builds the merge base beside the worktree). **Read \`available\` before using \`path\`** \u2014 a tree that was created but did NOT build populates \`path\` too, and a base that failed to build says nothing whatsoever about the PR, so measuring against it turns an infrastructure failure into a list of Criticals. \`available: false\` (local/lightweight review, no merge base, a base that would not compile) means the path rule stands \u2014 say so and stop here, and \`qwen review test-delta --report <the build-test report you wrote> --baseline <the base-tree report's path field> --pr-worktree <this worktree> --out <plan dir>/qwen-review-pr-<n>-test-delta.json\`. Read its verdict: a file in \`netNew\` fails on the PR side only \u2014 **that is the Critical**, whatever file the diff touches; a file in \`shared\` fails on base too \u2014 **pre-existing by measurement**, never filed, whatever file the diff touches; an \`unparsed\` entry, a timed-out base rerun, a base rerun that FAILED without naming any failing file (it did not measure the base \u2014 an unbuilt tree, a missing install, a workspace absent at base), or a command the whole-command budget could not fit attributes nothing \u2014 the report names each with its own reason; fall back to the path rule for those and say the delta could not rule. Compare failing FILE SETS, never counts: a flaky suite fails different test NAMES on two runs of the same tree, so counts are noise and the set difference is the signal.
5999
6033
  - \`toolchain: "unsupported"\` (build-test could not scope this repo \u2014 no npm package with a build/test script) \u2192 **install dependencies first** (build-test's own install only runs on the npm path, so nothing has installed yet: \`pip install -e .\`, \`mvn -q -DskipTests package\`'s own fetch, \`cargo fetch\`, \`go mod download\`, etc.), then fall back to **one** build and **one** test command by this precedence, each with a deadline it can meet: \`pom.xml\` \u2192 \`{mvn} compile\` / \`{mvn} test -q\`; \`build.gradle\` \u2192 \`{gradle} compileJava\` / \`{gradle} test\`; \`Makefile\` \u2192 \`make build\`; \`Cargo.toml\` \u2192 \`cargo build\` / \`cargo test\`; \`go.mod\` \u2192 \`go build ./...\` / \`go test ./...\`; \`pytest.ini\` or \`pyproject.toml\` \`[tool.pytest]\` \u2192 \`pytest\`. If none match, read the CI config **from the base branch** (\`git show <base>:<path>\`), never the worktree \u2014 the PR branch is untrusted and a modified workflow or Makefile could inject arbitrary commands.
6000
6034
 
@@ -6182,10 +6216,159 @@ The asymmetry cuts both ways: confirming also requires the trace, and a finding
6182
6216
  - **Report only Critical or Suggestion.** Do not report Nice to have.
6183
6217
  - A found gap uses the standard finding format (with \`Source: [review]\`), including its failure scenario \u2014 your findings go through the same verification as any other, so they must carry the evidence a verifier can trace.
6184
6218
 
6185
- If you find no new gap in your scope, say so **and name what you re-examined** \u2014 \`No issues found \u2014 re-walked the reconnect state machine and the two changed exports' call sites; every gap I checked was already in the list\`. A bare "No issues found." is indistinguishable from an agent that did nothing, and it is treated as one: it ends nothing, and it earns your scope a relaunch.`
6219
+ If you find no new gap in your scope, say so **and name what you re-examined** \u2014 \`${REVERSE_AUDIT_EXAMPLE_RECEIPT}\`. A bare "No issues found." is indistinguishable from an agent that did nothing, and it is treated as one: it ends nothing, and it earns your scope a relaunch.`
6186
6220
  }
6187
6221
  };
6188
6222
 
6223
+ // packages/cli/src/commands/review/lib/deadline.ts
6224
+ init_esbuild_shims();
6225
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync5 } from "node:fs";
6226
+ import { join as join8 } from "node:path";
6227
+ var DEADLINE_ENV = "QWEN_REVIEW_DEADLINE_EPOCH";
6228
+ var RESERVE_ENV = "QWEN_REVIEW_DEADLINE_RESERVE_SECONDS";
6229
+ var DEFAULT_RESERVE_SECONDS = 4800;
6230
+ var DEFAULT_ROUND_SECONDS = 1800;
6231
+ var MIN_OBSERVED_ROUND_SECONDS = 600;
6232
+ var STAMPS_FILE = "budget-rounds.json";
6233
+ var STOP_FILE = "budget-stop.json";
6234
+ var RUN_EPOCH_SLACK_MS = 2e3;
6235
+ function runEpochMs(planPath) {
6236
+ try {
6237
+ return statSync3(planPath).mtimeMs - RUN_EPOCH_SLACK_MS;
6238
+ } catch {
6239
+ return Number.NEGATIVE_INFINITY;
6240
+ }
6241
+ }
6242
+ __name(runEpochMs, "runEpochMs");
6243
+ function readRoundStamps(planPath) {
6244
+ try {
6245
+ const raw = readFileSync7(
6246
+ join8(promptRecordDir(planPath), STAMPS_FILE),
6247
+ "utf8"
6248
+ );
6249
+ const parsed = JSON.parse(raw);
6250
+ if (!Array.isArray(parsed)) return [];
6251
+ const epoch = runEpochMs(planPath);
6252
+ return parsed.filter(
6253
+ (e) => typeof e === "object" && e !== null && typeof e.atMs === "number" && e.atMs >= epoch
6254
+ );
6255
+ } catch {
6256
+ return [];
6257
+ }
6258
+ }
6259
+ __name(readRoundStamps, "readRoundStamps");
6260
+ function stampRound(planPath, round, nowMs = Date.now()) {
6261
+ try {
6262
+ const stamps = readRoundStamps(planPath);
6263
+ if (round !== void 0 && stamps.some((s) => s.round === round)) return;
6264
+ stamps.push({ round: round ?? null, atMs: nowMs });
6265
+ const dir = promptRecordDir(planPath);
6266
+ mkdirSync5(dir, { recursive: true });
6267
+ writeFileSync5(join8(dir, STAMPS_FILE), JSON.stringify(stamps));
6268
+ } catch {
6269
+ }
6270
+ }
6271
+ __name(stampRound, "stampRound");
6272
+ function expectedRoundSeconds(planPath, round, nowMs = Date.now()) {
6273
+ const stamps = readRoundStamps(planPath).filter(
6274
+ (s) => round === void 0 || s.round !== round
6275
+ );
6276
+ if (stamps.length === 0) return DEFAULT_ROUND_SECONDS;
6277
+ let maxSeconds = 0;
6278
+ for (let i = 0; i < stamps.length; i++) {
6279
+ const end = i + 1 < stamps.length ? stamps[i + 1].atMs : nowMs;
6280
+ maxSeconds = Math.max(
6281
+ maxSeconds,
6282
+ Math.round((end - stamps[i].atMs) / 1e3)
6283
+ );
6284
+ }
6285
+ return Math.max(MIN_OBSERVED_ROUND_SECONDS, maxSeconds);
6286
+ }
6287
+ __name(expectedRoundSeconds, "expectedRoundSeconds");
6288
+ function reverseAuditBudgetExhausted(env, roundCostSeconds, nowMs = Date.now()) {
6289
+ const raw = env[DEADLINE_ENV];
6290
+ if (raw === void 0 || raw.trim() === "") return null;
6291
+ const deadline = Number(raw);
6292
+ if (!Number.isFinite(deadline) || deadline <= 0) return null;
6293
+ let reserve = DEFAULT_RESERVE_SECONDS;
6294
+ const reserveRaw = env[RESERVE_ENV];
6295
+ if (reserveRaw !== void 0 && reserveRaw.trim() !== "") {
6296
+ const r = Number(reserveRaw);
6297
+ if (Number.isFinite(r) && r >= 0) reserve = r;
6298
+ }
6299
+ const remainingSeconds = Math.floor(deadline - nowMs / 1e3);
6300
+ if (remainingSeconds >= reserve + roundCostSeconds) return null;
6301
+ return {
6302
+ remainingSeconds,
6303
+ reserveSeconds: reserve,
6304
+ expectedRoundSeconds: roundCostSeconds
6305
+ };
6306
+ }
6307
+ __name(reverseAuditBudgetExhausted, "reverseAuditBudgetExhausted");
6308
+ var BUDGET_STOP_PHRASE = "review time budget";
6309
+ function budgetStopDisclosure(round) {
6310
+ const which = round !== void 0 ? `round ${round}` : "the next round";
6311
+ const whichZh = round !== void 0 ? `\u7B2C ${round} \u8F6E` : "\u4E0B\u4E00\u8F6E";
6312
+ return {
6313
+ subject: "reverse audit",
6314
+ reason: `stopped before ${which} by the ${BUDGET_STOP_PHRASE}`,
6315
+ subjectZh: "\u53CD\u5411\u5BA1\u8BA1",
6316
+ reasonZh: `\u8BC4\u5BA1\u65F6\u95F4\u9884\u7B97\u4E0D\u8DB3\uFF0C\u672A\u80FD\u5F00\u59CB${whichZh}`
6317
+ };
6318
+ }
6319
+ __name(budgetStopDisclosure, "budgetStopDisclosure");
6320
+ function budgetStopEntry(round) {
6321
+ const d = budgetStopDisclosure(round);
6322
+ return `${d.subject} \u2014 ${d.reason}`;
6323
+ }
6324
+ __name(budgetStopEntry, "budgetStopEntry");
6325
+ function budgetStopEntryZh(round) {
6326
+ const d = budgetStopDisclosure(round);
6327
+ return `${d.subjectZh}\u2014\u2014${d.reasonZh}`;
6328
+ }
6329
+ __name(budgetStopEntryZh, "budgetStopEntryZh");
6330
+ function writeBudgetStop(planPath, spent, round, nowMs = Date.now()) {
6331
+ try {
6332
+ const dir = promptRecordDir(planPath);
6333
+ mkdirSync5(dir, { recursive: true });
6334
+ const stop = {
6335
+ entry: budgetStopEntry(round),
6336
+ entryZh: budgetStopEntryZh(round),
6337
+ round: round ?? null,
6338
+ remainingSeconds: spent.remainingSeconds,
6339
+ reserveSeconds: spent.reserveSeconds,
6340
+ atMs: nowMs
6341
+ };
6342
+ writeFileSync5(join8(dir, STOP_FILE), JSON.stringify(stop, null, 2));
6343
+ } catch {
6344
+ }
6345
+ }
6346
+ __name(writeBudgetStop, "writeBudgetStop");
6347
+ function readBudgetStop(planPath) {
6348
+ try {
6349
+ const raw = readFileSync7(
6350
+ join8(promptRecordDir(planPath), STOP_FILE),
6351
+ "utf8"
6352
+ );
6353
+ const parsed = JSON.parse(raw);
6354
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.entry !== "string" || typeof parsed.atMs !== "number" || parsed.atMs < runEpochMs(planPath)) {
6355
+ return null;
6356
+ }
6357
+ return parsed;
6358
+ } catch {
6359
+ return null;
6360
+ }
6361
+ }
6362
+ __name(readBudgetStop, "readBudgetStop");
6363
+ function reverseAuditBudgetMessage(spent, round) {
6364
+ const minutesLeft = Math.max(0, Math.floor(spent.remainingSeconds / 60));
6365
+ const reserveMinutes = Math.round(spent.reserveSeconds / 60);
6366
+ const roundMinutes = Math.round(spent.expectedRoundSeconds / 60);
6367
+ const which = round !== void 0 ? `round ${round}` : "the next round";
6368
+ return `BUDGET: ${minutesLeft} minute(s) remain before this review's deadline \u2014 not enough for the ~${roundMinutes}-minute round being asked for plus the ${reserveMinutes}-minute reserve kept for its verification, compose-review and submission \u2014 so no further reverse-audit round will be built. This is the loop's termination rule, not an error: do not rebuild ${which} and do not relaunch auditors. A budget-stop marker has been recorded and compose-review will disclose it and cap the verdict itself; also add exactly this entry to unreviewedDimensions so the terminal report says it too \u2014 \`${budgetStopEntry(round)}\` \u2014 and proceed to Step 6 with the findings already confirmed. Spend what time remains only on verifying findings already in hand, composing, and submitting. A review that stops here still reports everything it proved; a review that runs past its deadline is killed holding all of it.`;
6369
+ }
6370
+ __name(reverseAuditBudgetMessage, "reverseAuditBudgetMessage");
6371
+
6189
6372
  // packages/cli/src/commands/review/lib/shell-quote.ts
6190
6373
  init_esbuild_shims();
6191
6374
  function shellQuotePath(p) {
@@ -6195,7 +6378,7 @@ __name(shellQuotePath, "shellQuotePath");
6195
6378
 
6196
6379
  // packages/cli/src/commands/review/lib/coverage.ts
6197
6380
  function readPlan(path3) {
6198
- const plan = JSON.parse(readFileSync7(path3, "utf8"));
6381
+ const plan = JSON.parse(readFileSync8(path3, "utf8"));
6199
6382
  if (typeof plan?.diffPathAbsolute !== "string" || !plan.diffPathAbsolute) {
6200
6383
  throw new Error(`coverage: ${path3} has no diffPathAbsolute`);
6201
6384
  }
@@ -6206,7 +6389,7 @@ function readPlan(path3) {
6206
6389
  if (problem) {
6207
6390
  throw new Error(`coverage: ${path3} has ${problem}`);
6208
6391
  }
6209
- return { plan, mtimeMs: statSync2(path3).mtimeMs };
6392
+ return { plan, mtimeMs: statSync4(path3).mtimeMs };
6210
6393
  }
6211
6394
  __name(readPlan, "readPlan");
6212
6395
  var CHUNK_RE = /\bchunk\s+(\d+)\s+of\s+\d+\b/i;
@@ -6552,7 +6735,13 @@ function coverageFromTranscripts(planPath, env = process.env) {
6552
6735
  };
6553
6736
  }
6554
6737
  __name(coverageFromTranscripts, "coverageFromTranscripts");
6555
- var rebuildFix = /* @__PURE__ */ __name((role, noun) => `build the prompt with \`"\${QWEN_CODE_CLI:-qwen}" review agent-prompt --plan <plan> --role ${role} --findings <file> [--rules <rules file>] [--round <k>]\` ` + (role === "reverse-audit" ? `(an early round with nothing confirmed passes an empty file; ` : `(pass the shard's findings, never an empty file \u2014 a verifier that sees no findings verifies nothing; `) + `pass --rules whenever the review loaded any, or the rebuilt brief silently drops the project rules) and launch an agent with EXACTLY what it prints \u2014 no hand-added ${noun} number` + // --round bakes in a ROUND number. Verify's noun is "shard", and a
6738
+ var rebuildFix = /* @__PURE__ */ __name((role, noun) => `build the prompt with \`"\${QWEN_CODE_CLI:-qwen}" review agent-prompt --plan <plan> --role ${role} --findings <file> [--rules <rules file>] ` + // --round is MANDATORY for a reverse-audit build (`agent-prompt` refuses a
6739
+ // round-less call — the label keys the record and the budget gate's
6740
+ // accounting), so the paste-and-run repair must not bracket it as optional:
6741
+ // an orchestrator honouring the bracket convention would have its first
6742
+ // repair attempt rejected. Verify genuinely takes it or not (only a repeat
6743
+ // verification round passes one), so its brackets stay.
6744
+ (role === "reverse-audit" ? `--round <k>\` ` : `[--round <k>]\` `) + (role === "reverse-audit" ? `(an early round with nothing confirmed passes an empty file; ` : `(pass the shard's findings, never an empty file \u2014 a verifier that sees no findings verifies nothing; `) + `pass --rules whenever the review loaded any, or the rebuilt brief silently drops the project rules) and launch an agent with EXACTLY what it prints \u2014 no hand-added ${noun} number` + // --round bakes in a ROUND number. Verify's noun is "shard", and a
6556
6745
  // parenthetical claiming --round bakes it in would send the reader to the
6557
6746
  // wrong flag — shards are already told apart by their findings digest.
6558
6747
  (role === "reverse-audit" ? ` (--round bakes it in)` : ``) + `, no summary of your own, no rewording`, "rebuildFix");
@@ -6667,7 +6856,9 @@ function verificationGaps(planPath, opts, env = process.env) {
6667
6856
  (k) => k === "reverse-audit" || k.startsWith("reverse-audit--")
6668
6857
  );
6669
6858
  const reverse = bestDelivery(reverseKeys);
6670
- const reverseGap = !balancedMedium && reverse !== "ok";
6859
+ const budgetStopped = readBudgetStop(planPath) !== null;
6860
+ const reverseByDesign = budgetStopped && reverse === "not-built";
6861
+ const reverseGap = !balancedMedium && !reverseByDesign && reverse !== "ok";
6671
6862
  if (reverseGap) {
6672
6863
  remediation.push(
6673
6864
  `reverse audit: ${REVERSE_AUDIT_GAP[reverse].fix.replace(
@@ -6970,8 +7161,27 @@ function unmarkedComments(comments) {
6970
7161
  }
6971
7162
  __name(unmarkedComments, "unmarkedComments");
6972
7163
 
7164
+ // packages/cli/src/commands/review/lib/review-footer.ts
7165
+ init_esbuild_shims();
7166
+ var FOOTER_MARKER = "via Qwen Code /review";
7167
+ function reviewFooter(modelId, cliVersion) {
7168
+ return `_\u2014 ${modelId} ${FOOTER_MARKER} (v${cliVersion})_`;
7169
+ }
7170
+ __name(reviewFooter, "reviewFooter");
7171
+ var REVIEW_FOOTER_RE = /\s*(?:_— (?:(?! via Qwen Code \/review)[^\n])* via Qwen Code \/review(?: \(v[^\n)]*\))?_?\s*)+$/;
7172
+ function isFooterSafeModelId(modelId) {
7173
+ return !/[\n\r]/.test(modelId) && !modelId.includes(FOOTER_MARKER);
7174
+ }
7175
+ __name(isFooterSafeModelId, "isFooterSafeModelId");
7176
+ var FOOTER_VERSION_RE = /^[A-Za-z0-9._+-]+$/;
7177
+ function footerVersion(stamp) {
7178
+ return stamp !== void 0 && FOOTER_VERSION_RE.test(stamp) ? stamp : void 0;
7179
+ }
7180
+ __name(footerVersion, "footerVersion");
7181
+
6973
7182
  // packages/cli/src/commands/review/compose-review.ts
6974
7183
  var LOW_SIGNAL_SRC_DIFF_LINES = 100;
7184
+ var UNVERIFIED_FINDING_TAG_RE = /—\s*\[unverified\]/gi;
6975
7185
  function withMarker(line) {
6976
7186
  return line.startsWith(CRITICAL_PREFIX) ? line : `${CRITICAL_PREFIX} ${line}`;
6977
7187
  }
@@ -6996,6 +7206,10 @@ function toStringList(value, field) {
6996
7206
  return [...value];
6997
7207
  }
6998
7208
  __name(toStringList, "toStringList");
7209
+ function stripReviewFooter(entry) {
7210
+ return entry.replace(REVIEW_FOOTER_RE, "");
7211
+ }
7212
+ __name(stripReviewFooter, "stripReviewFooter");
6999
7213
  function toBool(value, field) {
7000
7214
  if (value === void 0 || value === null) return false;
7001
7215
  if (typeof value !== "boolean") {
@@ -7017,15 +7231,15 @@ __name(composeReview, "composeReview");
7017
7231
  function ledgerMarkerFor(input) {
7018
7232
  try {
7019
7233
  if (!input.planPath) return null;
7020
- const plan = JSON.parse(readFileSync8(input.planPath, "utf8"));
7234
+ const plan = JSON.parse(readFileSync9(input.planPath, "utf8"));
7021
7235
  const pr = plan?.prNumber;
7022
7236
  const isPr = typeof pr === "number" && Number.isInteger(pr) && pr > 0 || typeof pr === "string" && /^\d+$/.test(pr);
7023
7237
  if (!isPr) return null;
7024
7238
  let prevRound = 0;
7025
7239
  try {
7026
7240
  const prev = JSON.parse(
7027
- readFileSync8(
7028
- join8(
7241
+ readFileSync9(
7242
+ join9(
7029
7243
  dirname4(input.planPath),
7030
7244
  `qwen-review-pr-${pr}-prev-ledger.json`
7031
7245
  ),
@@ -7040,7 +7254,7 @@ function ledgerMarkerFor(input) {
7040
7254
  buildLedger(
7041
7255
  prevRound + 1,
7042
7256
  input.draftedComments ?? [],
7043
- toStringList(input.bodyCriticals, "bodyCriticals")
7257
+ toStringList(input.bodyCriticals, "bodyCriticals").map(stripReviewFooter).filter((entry) => entry.trim() !== "")
7044
7258
  )
7045
7259
  );
7046
7260
  } catch {
@@ -7054,7 +7268,7 @@ function composeReviewBody(input, cliVersion) {
7054
7268
  input.suggestionsInline,
7055
7269
  "suggestionsInline"
7056
7270
  );
7057
- const bodyCriticals = toStringList(input.bodyCriticals, "bodyCriticals");
7271
+ const bodyCriticals = toStringList(input.bodyCriticals, "bodyCriticals").map(stripReviewFooter).filter((entry) => entry.trim() !== "");
7058
7272
  const suggestionsDiscarded = toCount(
7059
7273
  input.suggestionsDiscarded,
7060
7274
  "suggestionsDiscarded"
@@ -7062,7 +7276,7 @@ function composeReviewBody(input, cliVersion) {
7062
7276
  const cannotTell = toStringList(
7063
7277
  input.cannotTellCriticals,
7064
7278
  "cannotTellCriticals"
7065
- );
7279
+ ).map(stripReviewFooter).filter((entry) => entry.trim() !== "");
7066
7280
  const uncoverable = toStringList(
7067
7281
  input.uncoverableChunks,
7068
7282
  "uncoverableChunks"
@@ -7072,6 +7286,19 @@ function composeReviewBody(input, cliVersion) {
7072
7286
  "unreviewedDimensions"
7073
7287
  );
7074
7288
  const coverageEntries = [];
7289
+ let budgetEntry;
7290
+ if (input.planPath) {
7291
+ const stop = readBudgetStop(input.planPath);
7292
+ if (stop !== null) {
7293
+ for (let i = unreviewed.length - 1; i >= 0; i--) {
7294
+ if (unreviewed[i].includes(BUDGET_STOP_PHRASE)) {
7295
+ unreviewed.splice(i, 1);
7296
+ }
7297
+ }
7298
+ budgetEntry = budgetStopDisclosure(stop.round ?? void 0);
7299
+ coverageEntries.push(budgetEntry);
7300
+ }
7301
+ }
7075
7302
  const remediation = [];
7076
7303
  const planRef = input.planPath ? shellQuotePath(input.planPath) : "<plan>";
7077
7304
  const missingReceipts = [];
@@ -7202,6 +7429,33 @@ function composeReviewBody(input, cliVersion) {
7202
7429
  criticalsUnverified = criticalsNeedingVerify >= 1;
7203
7430
  }
7204
7431
  }
7432
+ let findingsUnverifiedAtCompose = false;
7433
+ let findingsFileUnreadable = false;
7434
+ let unverifiedTagCount = 0;
7435
+ const findingsPath = input.findingsPath;
7436
+ if (findingsPath !== void 0 && findingsPath !== null) {
7437
+ if (typeof findingsPath !== "string" || findingsPath.trim() === "") {
7438
+ throw new TypeError(
7439
+ `compose-review: findingsPath must be a non-empty string, got ${JSON.stringify(findingsPath)}`
7440
+ );
7441
+ }
7442
+ try {
7443
+ const findingsContent = readFileSync9(findingsPath, "utf8");
7444
+ unverifiedTagCount = (findingsContent.match(UNVERIFIED_FINDING_TAG_RE) ?? []).length;
7445
+ findingsUnverifiedAtCompose = unverifiedTagCount > 0;
7446
+ if (findingsUnverifiedAtCompose) {
7447
+ remediation.push(
7448
+ "findings still tagged `\u2014 [unverified]`: relaunch the verifier for each tagged entry (Step 4, `--role verify` with that entry), apply its verdict in the cumulative findings file, and run compose-review again with the updated file"
7449
+ );
7450
+ }
7451
+ } catch {
7452
+ findingsFileUnreadable = true;
7453
+ findingsUnverifiedAtCompose = true;
7454
+ remediation.push(
7455
+ "findings file not readable: pass the cumulative reverse-audit findings file \u2014 the one every round's `--findings` received \u2014 as `findingsPath` in the state JSON, and run compose-review again"
7456
+ );
7457
+ }
7458
+ }
7205
7459
  const contextUnavailable = toBool(
7206
7460
  input.contextUnavailable,
7207
7461
  "contextUnavailable"
@@ -7231,6 +7485,11 @@ function composeReviewBody(input, cliVersion) {
7231
7485
  "compose-review: modelId is required (the public footer names the reviewing model)"
7232
7486
  );
7233
7487
  }
7488
+ if (!isFooterSafeModelId(modelId)) {
7489
+ throw new TypeError(
7490
+ "compose-review: modelId is interpolated into the public footer verbatim \u2014 it must be a single line that does not contain the footer marker"
7491
+ );
7492
+ }
7234
7493
  const c = criticalsInline + bodyCriticals.length;
7235
7494
  const s = suggestionsInline + suggestionsDiscarded;
7236
7495
  const baseEvent = c >= 1 ? "REQUEST_CHANGES" : s >= 1 ? "COMMENT" : "APPROVE";
@@ -7243,10 +7502,13 @@ function composeReviewBody(input, cliVersion) {
7243
7502
  }
7244
7503
  if (contextUnavailable) cappedBy.push("context-unavailable");
7245
7504
  if (criticalsUnverified) cappedBy.push("criticals-unverified");
7505
+ if (findingsUnverifiedAtCompose) {
7506
+ cappedBy.push("findings-unverified-at-compose");
7507
+ }
7246
7508
  let event2 = baseEvent;
7247
7509
  if (event2 === "APPROVE" && cappedBy.length > 0) event2 = "COMMENT";
7248
7510
  const deterministicBodyCriticals = bodyCriticals.length - nonDeterministicBodyCriticals;
7249
- if (event2 === "REQUEST_CHANGES" && criticalsUnverified && deterministicBodyCriticals === 0) {
7511
+ if (event2 === "REQUEST_CHANGES" && (criticalsUnverified || findingsUnverifiedAtCompose && criticalsNeedingVerify >= 1) && deterministicBodyCriticals === 0) {
7250
7512
  event2 = "COMMENT";
7251
7513
  }
7252
7514
  let downgraded = false;
@@ -7264,7 +7526,7 @@ function composeReviewBody(input, cliVersion) {
7264
7526
  if (event2 === "APPROVE" && input.planPath) {
7265
7527
  try {
7266
7528
  const plan = JSON.parse(
7267
- readFileSync8(input.planPath, "utf8")
7529
+ readFileSync9(input.planPath, "utf8")
7268
7530
  );
7269
7531
  const src = Number(plan.srcDiffLines ?? 0);
7270
7532
  if (src > LOW_SIGNAL_SRC_DIFF_LINES) {
@@ -7273,7 +7535,7 @@ function composeReviewBody(input, cliVersion) {
7273
7535
  } catch {
7274
7536
  }
7275
7537
  }
7276
- const footer = `_\u2014 ${modelId} via Qwen Code /review (v${cliVersion})_`;
7538
+ const footer = reviewFooter(modelId, cliVersion);
7277
7539
  const bilingual = bilingualFromPlan(input.planPath, input.prBodyFetcher);
7278
7540
  const render = /* @__PURE__ */ __name((parts, sep5) => {
7279
7541
  const en = parts.map((p) => p.en).join(sep5);
@@ -7332,7 +7594,7 @@ ${footer}`;
7332
7594
  if (seenCaller.has(d)) continue;
7333
7595
  seenCaller.add(d);
7334
7596
  const echoesCoverage = covEntries.some(
7335
- (e) => d === e.subject || d.startsWith(`${e.subject} \u2014 `)
7597
+ (e) => d === e.subject || e !== budgetEntry && d.startsWith(`${e.subject} \u2014 `)
7336
7598
  );
7337
7599
  if (!echoesCoverage) callerLeft.push(d);
7338
7600
  }
@@ -7412,11 +7674,23 @@ ${footer}`;
7412
7674
  zh: `Test Plan\uFF08\u975E\u963B\u65AD\uFF09\uFF1A${testPlanNotes.join("; ")}\u3002`
7413
7675
  }
7414
7676
  ] : [];
7677
+ const unverifiedTagsBlock = !findingsUnverifiedAtCompose ? [] : findingsFileUnreadable ? [
7678
+ {
7679
+ en: "\u26A0\uFE0F The reverse-audit findings file could not be read at compose time, so this run cannot show its findings were verified.",
7680
+ zh: "\u26A0\uFE0F \u7EC4\u5408\u8BC4\u5BA1\u65F6\u65E0\u6CD5\u8BFB\u53D6\u53CD\u5411\u5BA1\u8BA1\u53D1\u73B0\u6587\u4EF6\uFF0C\u672C\u6B21\u8FD0\u884C\u65E0\u6CD5\u8BC1\u660E\u5176\u53D1\u73B0\u5DF2\u7ECF\u8FC7\u9A8C\u8BC1\u3002"
7681
+ }
7682
+ ] : [
7683
+ {
7684
+ en: `\u26A0\uFE0F ${unverifiedTagCount} finding(s) still carried the \`\u2014 [unverified]\` tag when the loop ended \u2014 the verifier never ruled on them, and they are not confirmed.`,
7685
+ zh: `\u26A0\uFE0F \u5FAA\u73AF\u7ED3\u675F\u65F6\u4ECD\u6709 ${unverifiedTagCount} \u6761\u53D1\u73B0\u5E26\u7740 \`\u2014 [unverified]\` \u6807\u8BB0\u2014\u2014\u9A8C\u8BC1\u8005\u4ECE\u672A\u5BF9\u5B83\u4EEC\u4F5C\u51FA\u88C1\u51B3\uFF0C\u5B83\u4EEC\u4E0D\u7B97\u5DF2\u786E\u8BA4\u3002`
7686
+ }
7687
+ ];
7415
7688
  if (event2 === "REQUEST_CHANGES") {
7416
7689
  const parts = [
7417
7690
  ...contextUnavailable ? [contextUnavailableClause] : [],
7418
7691
  ...cannotTellBlock,
7419
7692
  ...notReviewedParts,
7693
+ ...unverifiedTagsBlock,
7420
7694
  ...deferredBlock,
7421
7695
  ...testPlanBlock,
7422
7696
  ...bodyCriticalBlock
@@ -7465,8 +7739,9 @@ ${footer}`;
7465
7739
  } else {
7466
7740
  const canCertify = !downgraded && !downgradeApprove && !downgradeRequestChanges && c === 0 && cannotTell.length === 0 && uncoverable.length === 0 && unreviewed.length + coverageEntries.length === 0 && // A missing receipt caps the event but was left out of certification, so a
7467
7741
  // body could open "Reviewed — no blockers." two lines above "nobody read
7468
- // them." Nothing nobody read can be certified blocker-free.
7469
- missingReceipts.length === 0;
7742
+ // them." Nothing nobody read can be certified blocker-free — and neither
7743
+ // can a loop that ended with findings no verifier ever ruled on.
7744
+ missingReceipts.length === 0 && !findingsUnverifiedAtCompose;
7470
7745
  const disclosedChunkIds = /* @__PURE__ */ new Set();
7471
7746
  for (const e of coverageEntries) {
7472
7747
  const m = /^chunk (\d+)$/.exec(e.subject);
@@ -7491,6 +7766,7 @@ ${footer}`;
7491
7766
  }
7492
7767
  clauses.push(...cannotTellBlock);
7493
7768
  clauses.push(...notReviewedParts);
7769
+ clauses.push(...unverifiedTagsBlock);
7494
7770
  clauses.push(...deferredBlock);
7495
7771
  clauses.push(...testPlanBlock);
7496
7772
  if (downgradedFrom === "Request changes" || criticalsUnverified) {
@@ -7556,7 +7832,7 @@ function scriptLintGate(planPath) {
7556
7832
  const disclosed = [];
7557
7833
  let plan;
7558
7834
  try {
7559
- plan = JSON.parse(readFileSync8(planPath, "utf8"));
7835
+ plan = JSON.parse(readFileSync9(planPath, "utf8"));
7560
7836
  } catch {
7561
7837
  unreviewed.push(
7562
7838
  "the executable-script lint \u2014 could not read the plan to check the gate"
@@ -7567,13 +7843,13 @@ function scriptLintGate(planPath) {
7567
7843
  return { criticals, unreviewed, disclosed };
7568
7844
  }
7569
7845
  const owed = hasExecutableScript(plan);
7570
- const reportPath = join8(
7846
+ const reportPath = join9(
7571
7847
  dirname4(planPath),
7572
7848
  scriptLintReportName(plan.prNumber)
7573
7849
  );
7574
7850
  let report;
7575
7851
  try {
7576
- report = JSON.parse(readFileSync8(reportPath, "utf8"));
7852
+ report = JSON.parse(readFileSync9(reportPath, "utf8"));
7577
7853
  } catch {
7578
7854
  if (owed) {
7579
7855
  unreviewed.push(
@@ -7629,7 +7905,7 @@ function testPlanGate(planPath) {
7629
7905
  const notes = [];
7630
7906
  let plan;
7631
7907
  try {
7632
- plan = JSON.parse(readFileSync8(planPath, "utf8"));
7908
+ plan = JSON.parse(readFileSync9(planPath, "utf8"));
7633
7909
  } catch {
7634
7910
  return { notes };
7635
7911
  }
@@ -7639,8 +7915,8 @@ function testPlanGate(planPath) {
7639
7915
  let report;
7640
7916
  try {
7641
7917
  report = JSON.parse(
7642
- readFileSync8(
7643
- join8(dirname4(planPath), `qwen-review-pr-${pr}-test-plan.json`),
7918
+ readFileSync9(
7919
+ join9(dirname4(planPath), `qwen-review-pr-${pr}-test-plan.json`),
7644
7920
  "utf8"
7645
7921
  )
7646
7922
  );
@@ -7673,7 +7949,7 @@ function bilingualFromPlan(planPath, fetchPrBody2 = fetchPrBodyViaGh) {
7673
7949
  if (!planPath) return false;
7674
7950
  let plan;
7675
7951
  try {
7676
- plan = JSON.parse(readFileSync8(planPath, "utf8"));
7952
+ plan = JSON.parse(readFileSync9(planPath, "utf8"));
7677
7953
  } catch {
7678
7954
  return false;
7679
7955
  }
@@ -7693,7 +7969,7 @@ __name(bilingualFromPlan, "bilingualFromPlan");
7693
7969
  function readDraftedComments(path3) {
7694
7970
  let raw;
7695
7971
  try {
7696
- raw = readFileSync8(path3, "utf8");
7972
+ raw = readFileSync9(path3, "utf8");
7697
7973
  } catch (err) {
7698
7974
  throw new Error(
7699
7975
  `compose-review: cannot read the comments file ${path3}: ${err.message}. Pass the drafted inline comments \u2014 the same array the review payload will carry \u2014 or a file containing [] when nothing anchors inline.`
@@ -7747,7 +8023,7 @@ var composeReviewCommand = {
7747
8023
  "compose-review: --comments is required \u2014 the inline counts are counted from the drafted comments file, never typed. Pass a file containing [] when nothing anchors inline."
7748
8024
  );
7749
8025
  }
7750
- const raw = readFileSync8(input ?? 0, "utf8");
8026
+ const raw = readFileSync9(input ?? 0, "utf8");
7751
8027
  const parsed = JSON.parse(raw);
7752
8028
  delete parsed.env;
7753
8029
  delete parsed.prBodyFetcher;
@@ -7764,7 +8040,9 @@ var composeReviewCommand = {
7764
8040
  ...countInlineFindings(drafted),
7765
8041
  draftedComments: drafted
7766
8042
  },
7767
- await getCliVersion()
8043
+ // Same pin as `submit`: the startup stamp, not a version resolved at
8044
+ // compose time — a shared runner can rewrite the install mid-session.
8045
+ footerVersion(process.env["QWEN_CODE_STARTUP_VERSION"]) ?? await getCliVersion()
7768
8046
  );
7769
8047
  const json = JSON.stringify(
7770
8048
  { ...result, verdictLine: verdictLine(result) },
@@ -7772,8 +8050,8 @@ var composeReviewCommand = {
7772
8050
  2
7773
8051
  );
7774
8052
  if (out) {
7775
- mkdirSync5(dirname4(out), { recursive: true });
7776
- writeFileSync5(out, json, "utf8");
8053
+ mkdirSync6(dirname4(out), { recursive: true });
8054
+ writeFileSync6(out, json, "utf8");
7777
8055
  }
7778
8056
  writeStdoutLine(json);
7779
8057
  for (const fix of result.remediation) {
@@ -7851,11 +8129,12 @@ function verdictLine(r) {
7851
8129
  "chunk-nobody-read": "part of the diff was never read",
7852
8130
  "uncoverable-chunk": "part of the diff cannot be read at all",
7853
8131
  "unreviewed-dimension": "a dimension nobody reviewed",
7854
- "context-unavailable": "the PR's existing discussion could not be read"
8132
+ "context-unavailable": "the PR's existing discussion could not be read",
8133
+ "findings-unverified-at-compose": "findings were still unverified when the loop ended"
7855
8134
  };
7856
8135
  let line = `Verdict: ${label2[r.event]}`;
7857
- if (r.baseEvent === "REQUEST_CHANGES" && r.event === "COMMENT" && r.cappedBy.includes("criticals-unverified")) {
7858
- line += " \u2014 a Request changes was NOT available: its blockers were never verified (they are posted, disclosed as unverified)";
8136
+ if (r.baseEvent === "REQUEST_CHANGES" && r.event === "COMMENT" && (r.cappedBy.includes("criticals-unverified") || r.cappedBy.includes("findings-unverified-at-compose"))) {
8137
+ line += " \u2014 a Request changes was NOT available: " + (r.cappedBy.includes("criticals-unverified") ? "its blockers were never verified (they are posted, disclosed as unverified)" : "findings were still unverified when the loop ended (they are posted, disclosed)");
7859
8138
  } else if (r.baseEvent === "APPROVE" && r.event !== "APPROVE") {
7860
8139
  const reasons = r.cappedBy.map((c) => why[c] ?? c);
7861
8140
  if (r.downgraded) reasons.push("a presubmit check failed");
@@ -7874,7 +8153,7 @@ __name(verdictLine, "verdictLine");
7874
8153
 
7875
8154
  // packages/cli/src/commands/review/findings.ts
7876
8155
  init_esbuild_shims();
7877
- import { existsSync as existsSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "node:fs";
8156
+ import { existsSync as existsSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "node:fs";
7878
8157
  import { dirname as dirname5, resolve as resolve5 } from "node:path";
7879
8158
  var SEVERITIES = ["Critical", "Suggestion", "Nice to have"];
7880
8159
  var CONFIDENCES = ["high", "low"];
@@ -8248,7 +8527,7 @@ __name(renderFindings, "renderFindings");
8248
8527
  function readJson(path3, what) {
8249
8528
  let text;
8250
8529
  try {
8251
- text = readFileSync9(resolve5(path3), "utf8");
8530
+ text = readFileSync10(resolve5(path3), "utf8");
8252
8531
  } catch (err) {
8253
8532
  throw new Error(
8254
8533
  `Could not read the ${what} file at ${JSON.stringify(path3)}: ${err instanceof Error ? err.message : String(err)}`
@@ -8319,8 +8598,8 @@ var findingsCommand = {
8319
8598
  }
8320
8599
  const report = buildReport(findings);
8321
8600
  const target = resolve5(out);
8322
- mkdirSync6(dirname5(target), { recursive: true });
8323
- writeFileSync6(target, `${JSON.stringify(report, null, 2)}
8601
+ mkdirSync7(dirname5(target), { recursive: true });
8602
+ writeFileSync7(target, `${JSON.stringify(report, null, 2)}
8324
8603
  `, "utf8");
8325
8604
  const { bySeverity, byConfidence } = report.counts;
8326
8605
  writeStderrLine(
@@ -8351,7 +8630,7 @@ var findingsCommand = {
8351
8630
  // packages/cli/src/commands/review/fetch-pr.ts
8352
8631
  init_esbuild_shims();
8353
8632
  import { execFileSync as execFileSync4 } from "node:child_process";
8354
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "node:fs";
8633
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
8355
8634
  import { dirname as dirname6, resolve as resolve6 } from "node:path";
8356
8635
 
8357
8636
  // packages/cli/src/commands/review/lib/git.ts
@@ -8459,11 +8738,11 @@ var LITERAL_PATHSPECS = "--literal-pathspecs";
8459
8738
 
8460
8739
  // packages/cli/src/commands/review/lib/effort.ts
8461
8740
  init_esbuild_shims();
8462
- import { readFileSync as readFileSync10 } from "node:fs";
8741
+ import { readFileSync as readFileSync11 } from "node:fs";
8463
8742
  function resolveEffort(explicit) {
8464
8743
  if (explicit && EFFORT_LEVELS.has(explicit)) return explicit;
8465
8744
  try {
8466
- const parsed = JSON.parse(readFileSync10(PARSE_ARGS_REPORT, "utf8"));
8745
+ const parsed = JSON.parse(readFileSync11(PARSE_ARGS_REPORT, "utf8"));
8467
8746
  if (typeof parsed.effort === "string" && EFFORT_LEVELS.has(parsed.effort)) {
8468
8747
  return parsed.effort;
8469
8748
  }
@@ -8484,7 +8763,7 @@ __name(planEffortField, "planEffortField");
8484
8763
 
8485
8764
  // packages/cli/src/commands/review/lib/report.ts
8486
8765
  init_esbuild_shims();
8487
- import { statSync as statSync3 } from "node:fs";
8766
+ import { statSync as statSync5 } from "node:fs";
8488
8767
 
8489
8768
  // packages/cli/src/commands/review/lib/heavy.ts
8490
8769
  init_esbuild_shims();
@@ -8587,7 +8866,7 @@ __name(buildPlanReport, "buildPlanReport");
8587
8866
  function warnOnReportSize(path3, cap) {
8588
8867
  let size = 0;
8589
8868
  try {
8590
- size = statSync3(path3).size;
8869
+ size = statSync5(path3).size;
8591
8870
  } catch {
8592
8871
  return;
8593
8872
  }
@@ -8710,7 +8989,7 @@ async function runFetchPr(args) {
8710
8989
  );
8711
8990
  }
8712
8991
  try {
8713
- mkdirSync7(dirname6(wt), { recursive: true });
8992
+ mkdirSync8(dirname6(wt), { recursive: true });
8714
8993
  git("worktree", "add", wt, ref);
8715
8994
  } catch (err) {
8716
8995
  tryRemove(
@@ -8720,7 +8999,7 @@ async function runFetchPr(args) {
8720
8999
  `Failed to create worktree at ${wt}: ${err.message}`
8721
9000
  );
8722
9001
  }
8723
- mkdirSync7(REVIEW_TMP_DIR, { recursive: true });
9002
+ mkdirSync8(REVIEW_TMP_DIR, { recursive: true });
8724
9003
  const { sha: mergeBaseSha, baseFetchFailed } = resolveMergeBase(
8725
9004
  remote,
8726
9005
  meta.baseRefName,
@@ -8744,7 +9023,7 @@ async function runFetchPr(args) {
8744
9023
  ...PINNED_DIFF_FLAGS,
8745
9024
  `${mergeBaseSha}..${fetchedSha}`
8746
9025
  );
8747
- writeFileSync7(diffRel, buf);
9026
+ writeFileSync8(diffRel, buf);
8748
9027
  diffText = buf.toString("utf8");
8749
9028
  diffPath = diffRel;
8750
9029
  diffPathAbsolute = resolve6(diffRel);
@@ -8771,7 +9050,7 @@ async function runFetchPr(args) {
8771
9050
  let auditSince = fetchedAt;
8772
9051
  let prevRaw = null;
8773
9052
  try {
8774
- prevRaw = readFileSync11(out, "utf8");
9053
+ prevRaw = readFileSync12(out, "utf8");
8775
9054
  } catch (err) {
8776
9055
  const code = err.code;
8777
9056
  if (code !== "ENOENT") {
@@ -8854,7 +9133,7 @@ async function runFetchPr(args) {
8854
9133
  ...buildPlanReport(plan, (path3) => fileLineCount(fetchedSha, path3)),
8855
9134
  ...planEffortField(args.effort)
8856
9135
  };
8857
- writeFileSync7(out, stringifyPlanReport(result), "utf8");
9136
+ writeFileSync8(out, stringifyPlanReport(result), "utf8");
8858
9137
  writeStdoutLine(`Wrote fetch-pr report to ${out}`);
8859
9138
  if (diffPath) writeStdoutLine(`Wrote review diff to ${diffPath}`);
8860
9139
  writeStderrLine(
@@ -8938,13 +9217,13 @@ var fetchPrCommand = {
8938
9217
 
8939
9218
  // packages/cli/src/commands/review/capture-local.ts
8940
9219
  init_esbuild_shims();
8941
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "node:fs";
9220
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "node:fs";
8942
9221
  import { dirname as dirname7, resolve as resolve8 } from "node:path";
8943
9222
 
8944
9223
  // packages/cli/src/commands/review/lib/local-diff.ts
8945
9224
  init_esbuild_shims();
8946
- import { lstatSync as lstatSync2, statSync as statSync4, realpathSync as realpathSync2 } from "node:fs";
8947
- import { join as join9, relative as relative2, resolve as resolve7, isAbsolute as isAbsolute2, sep as sep2 } from "node:path";
9225
+ import { lstatSync as lstatSync2, statSync as statSync6, realpathSync as realpathSync2 } from "node:fs";
9226
+ import { join as join10, relative as relative2, resolve as resolve7, isAbsolute as isAbsolute2, sep as sep2 } from "node:path";
8948
9227
  var MAX_UNTRACKED_BYTES = 1e6;
8949
9228
  var MAX_UNTRACKED_FILES = 500;
8950
9229
  var MAX_UNTRACKED_TOTAL_BYTES = 1e7;
@@ -8965,7 +9244,7 @@ function describeUndiffable(abs, st) {
8965
9244
  }
8966
9245
  if (st.isSymbolicLink()) {
8967
9246
  try {
8968
- return statSync4(abs).isDirectory() ? "is a symlink to a directory \u2014 git cannot diff it as a file" : null;
9247
+ return statSync6(abs).isDirectory() ? "is a symlink to a directory \u2014 git cannot diff it as a file" : null;
8969
9248
  } catch {
8970
9249
  return null;
8971
9250
  }
@@ -9070,7 +9349,7 @@ function captureLocalDiff(opts) {
9070
9349
  for (const path3 of candidates) {
9071
9350
  let bytes;
9072
9351
  try {
9073
- const abs = join9(repoRoot, path3);
9352
+ const abs = join10(repoRoot, path3);
9074
9353
  const st = lstatSync2(abs);
9075
9354
  const kind = describeUndiffable(abs, st);
9076
9355
  if (kind) {
@@ -9162,10 +9441,10 @@ function runCaptureLocal(args) {
9162
9441
  includeUntracked: args.untracked
9163
9442
  });
9164
9443
  const diffText = capture.diff.toString("utf8");
9165
- mkdirSync8(REVIEW_TMP_DIR, { recursive: true });
9166
- mkdirSync8(dirname7(resolve8(out)), { recursive: true });
9444
+ mkdirSync9(REVIEW_TMP_DIR, { recursive: true });
9445
+ mkdirSync9(dirname7(resolve8(out)), { recursive: true });
9167
9446
  const diffPath = tmpFile(target, "diff.txt");
9168
- writeFileSync8(diffPath, capture.diff);
9447
+ writeFileSync9(diffPath, capture.diff);
9169
9448
  const plan = buildDiffPlan(diffText);
9170
9449
  const result = {
9171
9450
  diffPath,
@@ -9178,7 +9457,7 @@ function runCaptureLocal(args) {
9178
9457
  skippedFiles: capture.skipped,
9179
9458
  ...planEffortField(args.effort)
9180
9459
  };
9181
- writeFileSync8(out, stringifyPlanReport(result), "utf8");
9460
+ writeFileSync9(out, stringifyPlanReport(result), "utf8");
9182
9461
  writeStdoutLine(`Wrote diff to ${diffPath} and plan to ${out}`);
9183
9462
  if (capture.unbornHead) {
9184
9463
  writeStderrLine(
@@ -9236,13 +9515,13 @@ var captureLocalCommand = {
9236
9515
 
9237
9516
  // packages/cli/src/commands/review/plan-diff.ts
9238
9517
  init_esbuild_shims();
9239
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9 } from "node:fs";
9518
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, mkdirSync as mkdirSync10 } from "node:fs";
9240
9519
  import { resolve as resolve9 } from "node:path";
9241
9520
  function runPlanDiff(args) {
9242
9521
  const { diff_path: diffPath, out } = args;
9243
9522
  let diffText;
9244
9523
  try {
9245
- diffText = readFileSync12(diffPath, "utf8");
9524
+ diffText = readFileSync13(diffPath, "utf8");
9246
9525
  } catch (err) {
9247
9526
  throw new Error(
9248
9527
  `Cannot read diff file ${diffPath}: ${err.message}`
@@ -9269,8 +9548,8 @@ function runPlanDiff(args) {
9269
9548
  ...buildPlanReport(plan, null),
9270
9549
  ...planEffortField(args.effort)
9271
9550
  };
9272
- mkdirSync9(REVIEW_TMP_DIR, { recursive: true });
9273
- writeFileSync9(out, stringifyPlanReport(result), "utf8");
9551
+ mkdirSync10(REVIEW_TMP_DIR, { recursive: true });
9552
+ writeFileSync10(out, stringifyPlanReport(result), "utf8");
9274
9553
  writeStdoutLine(`Wrote diff plan to ${out}`);
9275
9554
  if (plan.diffLines === 0) {
9276
9555
  writeStderrLine(
@@ -9316,8 +9595,8 @@ var planDiffCommand = {
9316
9595
 
9317
9596
  // packages/cli/src/commands/review/pr-context.ts
9318
9597
  init_esbuild_shims();
9319
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "node:fs";
9320
- import { dirname as dirname8, join as join10 } from "node:path";
9598
+ import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "node:fs";
9599
+ import { dirname as dirname8, join as join11 } from "node:path";
9321
9600
  var SUMMARY_MARKER = "<!-- qwen-review-suggestion-summary -->";
9322
9601
  function isLegacySuggestionSummary(body) {
9323
9602
  return (body ?? "").includes(SUMMARY_MARKER);
@@ -9511,9 +9790,9 @@ function blockerSection(roots, issueBlockers, repliesByRoot, ctx) {
9511
9790
  ""
9512
9791
  ];
9513
9792
  let spent = out.join("\n").length;
9514
- const charge = /* @__PURE__ */ __name((lines2) => {
9515
- spent += lines2.join("\n").length;
9516
- return lines2;
9793
+ const charge = /* @__PURE__ */ __name((lines) => {
9794
+ spent += lines.join("\n").length;
9795
+ return lines;
9517
9796
  }, "charge");
9518
9797
  const refsLine = /* @__PURE__ */ __name((body) => {
9519
9798
  const refs = extractCodeRefs(body);
@@ -9795,9 +10074,9 @@ async function runPrContext(args) {
9795
10074
  }
9796
10075
  if (prevLedger) {
9797
10076
  try {
9798
- mkdirSync10(dirname8(out), { recursive: true });
9799
- writeFileSync10(
9800
- join10(dirname8(out), `qwen-review-pr-${prNumber}-prev-ledger.json`),
10077
+ mkdirSync11(dirname8(out), { recursive: true });
10078
+ writeFileSync11(
10079
+ join11(dirname8(out), `qwen-review-pr-${prNumber}-prev-ledger.json`),
9801
10080
  JSON.stringify(prevLedger, null, 2)
9802
10081
  );
9803
10082
  } catch {
@@ -9812,8 +10091,8 @@ async function runPrContext(args) {
9812
10091
  reviews,
9813
10092
  prevLedger
9814
10093
  );
9815
- mkdirSync10(dirname8(out), { recursive: true });
9816
- writeFileSync10(out, md, "utf8");
10094
+ mkdirSync11(dirname8(out), { recursive: true });
10095
+ writeFileSync11(out, md, "utf8");
9817
10096
  const meaningfulReviewCount = reviews.filter(
9818
10097
  (r) => isReviewWorthShowing(stripLedgerMarker(r.body ?? ""))
9819
10098
  ).length;
@@ -9867,7 +10146,7 @@ var prContextCommand = {
9867
10146
 
9868
10147
  // packages/cli/src/commands/review/comment-status.ts
9869
10148
  init_esbuild_shims();
9870
- import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "node:fs";
10149
+ import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync12 } from "node:fs";
9871
10150
  import { dirname as dirname9 } from "node:path";
9872
10151
  var TOUCHED_BY_CAP = 10;
9873
10152
  function buildThreadStatuses(comments, prAuthor, probe) {
@@ -10073,9 +10352,9 @@ async function runCommentStatus(args) {
10073
10352
  summary,
10074
10353
  threads
10075
10354
  };
10076
- mkdirSync11(dirname9(out), { recursive: true });
10355
+ mkdirSync12(dirname9(out), { recursive: true });
10077
10356
  const json = JSON.stringify(report, null, 2) + "\n";
10078
- writeFileSync11(out, json, "utf8");
10357
+ writeFileSync12(out, json, "utf8");
10079
10358
  writeStdoutLine(
10080
10359
  `Wrote comment-status report to ${out} (${comments.length} inline comments in ${summary.threads} threads: ${summary.outdated} outdated, ${summary.blockers} blocker(s), ${summary.changedSinceComment} on files changed since their comment, ${summary.withReplies} with replies, ${summary.authorReplied} answered by the PR author)`
10081
10360
  );
@@ -10103,8 +10382,8 @@ async function runCommentStatus(args) {
10103
10382
  writeStdoutLine(
10104
10383
  `warning: comment-status failed: ${msg}. It is an index, not the evidence \u2014 re-derive thread statuses per-comment if needed.`
10105
10384
  );
10106
- mkdirSync11(dirname9(out), { recursive: true });
10107
- writeFileSync11(
10385
+ mkdirSync12(dirname9(out), { recursive: true });
10386
+ writeFileSync12(
10108
10387
  out,
10109
10388
  JSON.stringify(
10110
10389
  {
@@ -10166,18 +10445,18 @@ var commentStatusCommand = {
10166
10445
 
10167
10446
  // packages/cli/src/commands/review/load-rules.ts
10168
10447
  init_esbuild_shims();
10169
- import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync12 } from "node:fs";
10448
+ import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync13 } from "node:fs";
10170
10449
  import { dirname as dirname10 } from "node:path";
10171
10450
  function showFile(baseRef, path3) {
10172
10451
  return gitOpt("show", `${baseRef}:${path3}`);
10173
10452
  }
10174
10453
  __name(showFile, "showFile");
10175
10454
  function extractCodeReviewSection(content) {
10176
- const lines2 = content.split("\n");
10455
+ const lines = content.split("\n");
10177
10456
  let start = -1;
10178
- let end = lines2.length;
10179
- for (let i = 0; i < lines2.length; i++) {
10180
- const line = lines2[i];
10457
+ let end = lines.length;
10458
+ for (let i = 0; i < lines.length; i++) {
10459
+ const line = lines[i];
10181
10460
  if (start < 0) {
10182
10461
  if (/^## Code Review\s*$/i.test(line)) start = i;
10183
10462
  } else if (/^## /.test(line)) {
@@ -10186,7 +10465,7 @@ function extractCodeReviewSection(content) {
10186
10465
  }
10187
10466
  }
10188
10467
  if (start < 0) return null;
10189
- return lines2.slice(start, end).join("\n").trim();
10468
+ return lines.slice(start, end).join("\n").trim();
10190
10469
  }
10191
10470
  __name(extractCodeReviewSection, "extractCodeReviewSection");
10192
10471
  function loadCombined(baseRef) {
@@ -10247,8 +10526,8 @@ __name(loadCombined, "loadCombined");
10247
10526
  async function runLoadRules(args) {
10248
10527
  const { base_ref: baseRef, out } = args;
10249
10528
  const { combined, loaded } = loadCombined(baseRef);
10250
- mkdirSync12(dirname10(out), { recursive: true });
10251
- writeFileSync12(out, combined, "utf8");
10529
+ mkdirSync13(dirname10(out), { recursive: true });
10530
+ writeFileSync13(out, combined, "utf8");
10252
10531
  if (loaded.length === 0) {
10253
10532
  writeStdoutLine(
10254
10533
  `No review rules found on ${baseRef}; wrote empty file to ${out}`
@@ -10279,7 +10558,7 @@ var loadRulesCommand = {
10279
10558
 
10280
10559
  // packages/cli/src/commands/review/presubmit.ts
10281
10560
  init_esbuild_shims();
10282
- import { writeFileSync as writeFileSync13, readFileSync as readFileSync13 } from "node:fs";
10561
+ import { writeFileSync as writeFileSync14, readFileSync as readFileSync14 } from "node:fs";
10283
10562
  function verdictStamp(run4) {
10284
10563
  return run4.completed_at ?? run4.started_at ?? "";
10285
10564
  }
@@ -10315,7 +10594,7 @@ __name(isCurrentActionsRunCheck, "isCurrentActionsRunCheck");
10315
10594
  function parseFindingsFile(path3) {
10316
10595
  let parsed;
10317
10596
  try {
10318
- parsed = JSON.parse(readFileSync13(path3, "utf8"));
10597
+ parsed = JSON.parse(readFileSync14(path3, "utf8"));
10319
10598
  } catch {
10320
10599
  return null;
10321
10600
  }
@@ -10618,7 +10897,7 @@ async function runPresubmit(args) {
10618
10897
  findingsFileInvalid,
10619
10898
  headDrift
10620
10899
  };
10621
- writeFileSync13(outPath, JSON.stringify(result, null, 2) + "\n", "utf8");
10900
+ writeFileSync14(outPath, JSON.stringify(result, null, 2) + "\n", "utf8");
10622
10901
  writeStdoutLine(`Wrote presubmit report to ${outPath}`);
10623
10902
  }
10624
10903
  __name(runPresubmit, "runPresubmit");
@@ -10657,17 +10936,17 @@ var presubmitCommand = {
10657
10936
  // packages/cli/src/commands/review/publish-assets.ts
10658
10937
  init_esbuild_shims();
10659
10938
  import { createHash as createHash2 } from "node:crypto";
10660
- import { readFileSync as readFileSync15, statSync as statSync5, writeFileSync as writeFileSync14, mkdirSync as mkdirSync14 } from "node:fs";
10939
+ import { readFileSync as readFileSync16, statSync as statSync7, writeFileSync as writeFileSync15, mkdirSync as mkdirSync15 } from "node:fs";
10661
10940
  import { basename as basename5, dirname as dirname11, resolve as resolve10 } from "node:path";
10662
10941
 
10663
10942
  // packages/cli/src/commands/review/lib/authorization.ts
10664
10943
  init_esbuild_shims();
10665
- import { readFileSync as readFileSync14 } from "node:fs";
10944
+ import { readFileSync as readFileSync15 } from "node:fs";
10666
10945
 
10667
10946
  // packages/cli/src/services/skill-args-file.ts
10668
10947
  init_esbuild_shims();
10669
10948
  import {
10670
- mkdirSync as mkdirSync13,
10949
+ mkdirSync as mkdirSync14,
10671
10950
  openSync as openSync2,
10672
10951
  writeSync,
10673
10952
  closeSync as closeSync2,
@@ -10676,9 +10955,9 @@ import {
10676
10955
  existsSync as existsSync5,
10677
10956
  constants
10678
10957
  } from "node:fs";
10679
- import { join as join11 } from "node:path";
10958
+ import { join as join12 } from "node:path";
10680
10959
  var debugLogger3 = createDebugLogger("SKILL_ARGS_FILE");
10681
- var SKILL_ARGS_DIR = join11(".qwen", "tmp");
10960
+ var SKILL_ARGS_DIR = join12(".qwen", "tmp");
10682
10961
  function safe(s) {
10683
10962
  return s.replace(/[^A-Za-z0-9._-]/g, "_");
10684
10963
  }
@@ -10688,11 +10967,11 @@ function currentSessionId() {
10688
10967
  }
10689
10968
  __name(currentSessionId, "currentSessionId");
10690
10969
  function skillArgsDir(sessionId = currentSessionId()) {
10691
- return sessionId ? join11(SKILL_ARGS_DIR, `s-${safe(sessionId)}`) : SKILL_ARGS_DIR;
10970
+ return sessionId ? join12(SKILL_ARGS_DIR, `s-${safe(sessionId)}`) : SKILL_ARGS_DIR;
10692
10971
  }
10693
10972
  __name(skillArgsDir, "skillArgsDir");
10694
10973
  function skillArgsPath(skillName, sessionId = currentSessionId()) {
10695
- return join11(
10974
+ return join12(
10696
10975
  skillArgsDir(sessionId),
10697
10976
  `qwen-skill-args-${safe(skillName)}.txt`
10698
10977
  );
@@ -10702,7 +10981,7 @@ function writeSkillArgs(skillName, args) {
10702
10981
  const path3 = skillArgsPath(skillName);
10703
10982
  try {
10704
10983
  const dir = skillArgsDir();
10705
- mkdirSync13(dir, { recursive: true });
10984
+ mkdirSync14(dir, { recursive: true });
10706
10985
  if (lstatSync3(dir).isSymbolicLink()) {
10707
10986
  debugLogger3.warn(
10708
10987
  `Skill args directory ${dir} is a symlink; refusing to write through it.`
@@ -10785,7 +11064,7 @@ function reviewWriteAuthorization(req) {
10785
11064
  const path3 = currentSessionId() === "" && req.skillArgs ? req.skillArgs : sessionScoped;
10786
11065
  let raw;
10787
11066
  try {
10788
- raw = readFileSync14(path3, "utf8");
11067
+ raw = readFileSync15(path3, "utf8");
10789
11068
  } catch {
10790
11069
  return {
10791
11070
  ok: false,
@@ -10840,7 +11119,64 @@ __name(reviewWriteAuthorization, "reviewWriteAuthorization");
10840
11119
 
10841
11120
  // packages/cli/src/commands/review/lib/assets.ts
10842
11121
  init_esbuild_shims();
10843
- var ASSET_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp"]);
11122
+ var EXTENSION_FORMAT = {
11123
+ png: "png",
11124
+ jpg: "jpeg",
11125
+ jpeg: "jpeg",
11126
+ gif: "gif",
11127
+ webp: "webp"
11128
+ };
11129
+ var ASSET_EXTENSIONS = new Set(Object.keys(EXTENSION_FORMAT));
11130
+ function claimedExtension(basename6) {
11131
+ return basename6.includes(".") ? basename6.slice(basename6.lastIndexOf(".") + 1).toLowerCase() : "";
11132
+ }
11133
+ __name(claimedExtension, "claimedExtension");
11134
+ var ASSET_HEADER_BYTES = 16;
11135
+ function sniffImageFormat(header) {
11136
+ const at = /* @__PURE__ */ __name((i) => header[i] ?? -1, "at");
11137
+ const ascii = /* @__PURE__ */ __name((i, s) => [...s].every((ch, k) => at(i + k) === ch.charCodeAt(0)), "ascii");
11138
+ if (at(0) === 137 && ascii(1, "PNG") && at(4) === 13 && at(5) === 10 && at(6) === 26 && at(7) === 10) {
11139
+ return "png";
11140
+ }
11141
+ if (at(0) === 255 && at(1) === 216 && at(2) === 255) {
11142
+ return "jpeg";
11143
+ }
11144
+ if (ascii(0, "GIF87a") || ascii(0, "GIF89a")) {
11145
+ return "gif";
11146
+ }
11147
+ if (ascii(0, "RIFF") && ascii(8, "WEBP") && // The container claim is not the image: a RIFF box carrying WEBP at
11148
+ // offset 8 could still hold anything. Every spec-conformant WebP's
11149
+ // first chunk is VP8, VP8L or VP8X at byte 12, and the 16-byte read
11150
+ // this gate sniffs always covers it.
11151
+ (ascii(12, "VP8 ") || ascii(12, "VP8L") || ascii(12, "VP8X"))) {
11152
+ return "webp";
11153
+ }
11154
+ return null;
11155
+ }
11156
+ __name(sniffImageFormat, "sniffImageFormat");
11157
+ function refusedExtension(basename6, ext) {
11158
+ return {
11159
+ ok: false,
11160
+ reason: `${basename6}: extension ${JSON.stringify(ext)} is not an allowed evidence image type (${[...ASSET_EXTENSIONS].join(", ")})`
11161
+ };
11162
+ }
11163
+ __name(refusedExtension, "refusedExtension");
11164
+ function validateAssetContent(basename6, header) {
11165
+ const ext = claimedExtension(basename6);
11166
+ const claimed = Object.hasOwn(EXTENSION_FORMAT, ext) ? EXTENSION_FORMAT[ext] : void 0;
11167
+ if (claimed === void 0) {
11168
+ return refusedExtension(basename6, ext);
11169
+ }
11170
+ const actual = sniffImageFormat(header);
11171
+ if (actual !== claimed) {
11172
+ return {
11173
+ ok: false,
11174
+ reason: `content is ${actual ?? "not a recognized image"} but the extension claims ${claimed} \u2014 evidence is admitted by content, not by name`
11175
+ };
11176
+ }
11177
+ return { ok: true };
11178
+ }
11179
+ __name(validateAssetContent, "validateAssetContent");
10844
11180
  var MAX_ASSET_BYTES = 10 * 1024 * 1024;
10845
11181
  var MAX_TOTAL_ASSET_BYTES = 40 * 1024 * 1024;
10846
11182
  function assetsBranch(pr) {
@@ -10875,12 +11211,9 @@ function parseAssetsRepo(value) {
10875
11211
  }
10876
11212
  __name(parseAssetsRepo, "parseAssetsRepo");
10877
11213
  function validateAssetFile(basename6, bytes) {
10878
- const ext = basename6.includes(".") ? basename6.slice(basename6.lastIndexOf(".") + 1).toLowerCase() : "";
11214
+ const ext = claimedExtension(basename6);
10879
11215
  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
- };
11216
+ return refusedExtension(basename6, ext);
10884
11217
  }
10885
11218
  if (bytes <= 0) {
10886
11219
  return { ok: false, reason: `${basename6}: empty file` };
@@ -11051,7 +11384,7 @@ function runPublishAssets(args) {
11051
11384
  const fileList = [...args.files ?? []];
11052
11385
  if (args.findings) {
11053
11386
  try {
11054
- const artifact = JSON.parse(readFileSync15(resolve10(args.findings), "utf8"));
11387
+ const artifact = JSON.parse(readFileSync16(resolve10(args.findings), "utf8"));
11055
11388
  findings = validateFindings(
11056
11389
  Array.isArray(artifact) ? artifact : artifact.findings
11057
11390
  );
@@ -11081,7 +11414,7 @@ function runPublishAssets(args) {
11081
11414
  for (const file of unique) {
11082
11415
  const abs = resolve10(file);
11083
11416
  try {
11084
- const st = statSync5(abs);
11417
+ const st = statSync7(abs);
11085
11418
  if (!st.isFile()) throw new Error("not a regular file");
11086
11419
  stats.push({ file, abs, basename: basename5(abs), bytes: st.size });
11087
11420
  } catch (err) {
@@ -11100,13 +11433,21 @@ function runPublishAssets(args) {
11100
11433
  for (const st of stats) {
11101
11434
  let content;
11102
11435
  try {
11103
- content = readFileSync15(st.abs);
11436
+ content = readFileSync16(st.abs);
11104
11437
  } catch (err) {
11105
11438
  refuse(
11106
11439
  `cannot read ${JSON.stringify(st.file)}: ${err instanceof Error ? err.message : String(err)}`
11107
11440
  );
11108
11441
  return;
11109
11442
  }
11443
+ const contentRuling = validateAssetContent(
11444
+ st.basename,
11445
+ content.subarray(0, ASSET_HEADER_BYTES)
11446
+ );
11447
+ if (!contentRuling.ok) {
11448
+ refuse(`${JSON.stringify(st.file)}: ${contentRuling.reason}`);
11449
+ return;
11450
+ }
11110
11451
  const sha256 = createHash2("sha256").update(content).digest("hex");
11111
11452
  prepared.push({
11112
11453
  file: st.file,
@@ -11147,8 +11488,8 @@ function runPublishAssets(args) {
11147
11488
  published
11148
11489
  };
11149
11490
  const outPath = resolve10(args.out);
11150
- mkdirSync14(dirname11(outPath), { recursive: true });
11151
- writeFileSync14(outPath, `${JSON.stringify(manifest, null, 2)}
11491
+ mkdirSync15(dirname11(outPath), { recursive: true });
11492
+ writeFileSync15(outPath, `${JSON.stringify(manifest, null, 2)}
11152
11493
  `, "utf8");
11153
11494
  if (findings && !args.findingsOut) {
11154
11495
  writeStderrLine(
@@ -11163,8 +11504,8 @@ function runPublishAssets(args) {
11163
11504
  });
11164
11505
  const report = buildReport(updated);
11165
11506
  const fOut = resolve10(args.findingsOut);
11166
- mkdirSync14(dirname11(fOut), { recursive: true });
11167
- writeFileSync14(fOut, `${JSON.stringify(report, null, 2)}
11507
+ mkdirSync15(dirname11(fOut), { recursive: true });
11508
+ writeFileSync15(fOut, `${JSON.stringify(report, null, 2)}
11168
11509
  `, "utf8");
11169
11510
  }
11170
11511
  writeStderrLine(
@@ -11226,19 +11567,19 @@ var publishAssetsCommand = {
11226
11567
 
11227
11568
  // packages/cli/src/commands/review/resolve-anchors.ts
11228
11569
  init_esbuild_shims();
11229
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync15, mkdirSync as mkdirSync15 } from "node:fs";
11570
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync16, mkdirSync as mkdirSync16 } from "node:fs";
11230
11571
  import { dirname as dirname12, resolve as resolve11 } from "node:path";
11231
11572
 
11232
11573
  // packages/cli/src/commands/review/lib/anchors.ts
11233
11574
  init_esbuild_shims();
11234
11575
  function collectNewSideLines(diffText, file) {
11235
- const lines2 = diffText.split("\n");
11576
+ const lines = diffText.split("\n");
11236
11577
  const out = [];
11237
11578
  for (const hunk of file.hunks) {
11238
11579
  if (hunk.newCount === 0) continue;
11239
11580
  let newCursor = hunk.newStart;
11240
11581
  for (let n = hunk.diffStart + 1; n <= hunk.diffEnd; n++) {
11241
- const raw = lines2[n - 1];
11582
+ const raw = lines[n - 1];
11242
11583
  if (raw === void 0) break;
11243
11584
  if (raw.startsWith("+")) {
11244
11585
  out.push({ newLine: newCursor, text: raw.slice(1), added: true });
@@ -11266,20 +11607,20 @@ function normalizeLoose(s) {
11266
11607
  }
11267
11608
  __name(normalizeLoose, "normalizeLoose");
11268
11609
  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() !== "");
11610
+ const lines = anchor.replace(/\r\n/g, "\n").split("\n");
11611
+ while (lines.length > 0 && lines[0].trim() === "") lines.shift();
11612
+ while (lines.length > 0 && lines[lines.length - 1].trim() === "") lines.pop();
11613
+ if (lines.length === 0) return [];
11614
+ const variants = [lines];
11615
+ const meaningful = lines.filter((l) => l.trim() !== "");
11275
11616
  if (meaningful.length > 0 && meaningful.every((l) => /^\+/.test(l))) {
11276
- variants.push(lines2.map((l) => l.startsWith("+") ? l.slice(1) : l));
11617
+ variants.push(lines.map((l) => l.startsWith("+") ? l.slice(1) : l));
11277
11618
  }
11278
11619
  const NO_NEWLINE = /^\$/;
11279
- const marked = lines2.filter((l) => l !== "" && !NO_NEWLINE.test(l));
11620
+ const marked = lines.filter((l) => l !== "" && !NO_NEWLINE.test(l));
11280
11621
  if (marked.length > 0 && marked.every((l) => /^[+\- ]/.test(l)) && marked.some((l) => l.startsWith("+"))) {
11281
11622
  variants.push(
11282
- lines2.filter((l) => !l.startsWith("-") && !NO_NEWLINE.test(l)).map((l) => l.slice(1))
11623
+ lines.filter((l) => !l.startsWith("-") && !NO_NEWLINE.test(l)).map((l) => l.slice(1))
11283
11624
  );
11284
11625
  }
11285
11626
  return variants;
@@ -11451,7 +11792,7 @@ __name(validateRequests, "validateRequests");
11451
11792
  function runResolveAnchors(args) {
11452
11793
  let diffText;
11453
11794
  try {
11454
- diffText = readFileSync16(args.diff, "utf8");
11795
+ diffText = readFileSync17(args.diff, "utf8");
11455
11796
  } catch (err) {
11456
11797
  throw new Error(
11457
11798
  `Cannot read diff file ${args.diff}: ${err.message}`
@@ -11459,7 +11800,7 @@ function runResolveAnchors(args) {
11459
11800
  }
11460
11801
  let rawText;
11461
11802
  try {
11462
- rawText = readFileSync16(args.input, "utf8");
11803
+ rawText = readFileSync17(args.input, "utf8");
11463
11804
  } catch (err) {
11464
11805
  throw new Error(
11465
11806
  `Cannot read findings file ${args.input}: ${err.message}`
@@ -11490,8 +11831,8 @@ function runResolveAnchors(args) {
11490
11831
  loose: resolved.filter((r) => r.tier?.startsWith("loose")).length
11491
11832
  }
11492
11833
  };
11493
- mkdirSync15(dirname12(resolve11(args.out)), { recursive: true });
11494
- writeFileSync15(args.out, JSON.stringify(report, null, 2), "utf8");
11834
+ mkdirSync16(dirname12(resolve11(args.out)), { recursive: true });
11835
+ writeFileSync16(args.out, JSON.stringify(report, null, 2), "utf8");
11495
11836
  writeStdoutLine(`Wrote resolved anchors to ${args.out}`);
11496
11837
  const s = report.stats;
11497
11838
  writeStderrLine(
@@ -11538,7 +11879,7 @@ var resolveAnchorsCommand = {
11538
11879
 
11539
11880
  // packages/cli/src/commands/review/check-coverage.ts
11540
11881
  init_esbuild_shims();
11541
- import { writeFileSync as writeFileSync16, mkdirSync as mkdirSync16 } from "node:fs";
11882
+ import { writeFileSync as writeFileSync17, mkdirSync as mkdirSync17 } from "node:fs";
11542
11883
  import { dirname as dirname13, resolve as resolve12 } from "node:path";
11543
11884
  function runCheckCoverage(args) {
11544
11885
  let report;
@@ -11555,8 +11896,8 @@ This is an environment problem, not a finding about the agents. Coverage cannot
11555
11896
  }
11556
11897
  throw err;
11557
11898
  }
11558
- mkdirSync16(dirname13(resolve12(args.out)), { recursive: true });
11559
- writeFileSync16(args.out, JSON.stringify(report, null, 2));
11899
+ mkdirSync17(dirname13(resolve12(args.out)), { recursive: true });
11900
+ writeFileSync17(args.out, JSON.stringify(report, null, 2));
11560
11901
  writeStdoutLine(`Wrote coverage report to ${args.out}`);
11561
11902
  const totalChunks = report.coveredChunks.length + report.missingChunks.length + report.uncoverableChunks.length;
11562
11903
  const worked = report.agents - report.blindAgents.length - report.idleAgents.length;
@@ -11655,8 +11996,172 @@ var checkCoverageCommand = {
11655
11996
  // packages/cli/src/commands/review/agent-prompt.ts
11656
11997
  init_esbuild_shims();
11657
11998
  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";
11999
+ import { readFileSync as readFileSync18 } from "node:fs";
12000
+ import { dirname as dirname14, join as join13, resolve as resolve13 } from "node:path";
12001
+
12002
+ // packages/cli/src/commands/review/lib/retirement.ts
12003
+ init_esbuild_shims();
12004
+ import { statSync as statSync8 } from "node:fs";
12005
+ var REVERSE_AUDIT_MAX_ROUNDS = 5;
12006
+ var RECORD_KEY_RE = /^reverse-audit--chunk-(\d+)--round-(\d+)--[0-9a-f]+$/;
12007
+ var REVERSE_AUDIT_MARKER = "reverse-audit";
12008
+ function bakedRanges(prompt, diffPath) {
12009
+ const out = [];
12010
+ if (diffPath === void 0) return out;
12011
+ for (const m of prompt.matchAll(
12012
+ /read_file\(\s*file_path="([^"]*)",\s*offset=(\d+),\s*limit=(\d+)/gi
12013
+ )) {
12014
+ if (m[1] !== diffPath) continue;
12015
+ const offset = Number(m[2]);
12016
+ const limit = Number(m[3]);
12017
+ if (limit > 0) out.push([offset + 1, offset + limit]);
12018
+ }
12019
+ return out;
12020
+ }
12021
+ __name(bakedRanges, "bakedRanges");
12022
+ var FILE_LINE_RE = /\*\*File:\*\*\s*([^\n]*)/g;
12023
+ var SEVERITY_LINE_RE = /\*\*Severity:\*\*/;
12024
+ var DRY_RECEIPT_RE = new RegExp(
12025
+ '(?:\\bno (?:new )?(?:issues?|findings?|gaps?)[ \\w()]{0,32}?|\u672A\u53D1\u73B0(?:\u65B0\u7684?)?(?:\u95EE\u9898|\u53D1\u73B0)|\u65E0\u65B0\u7684?(?:\u95EE\u9898|\u53D1\u73B0)|\u6CA1\u6709(?:\u53D1\u73B0)?(?:\u65B0\u7684?)?\u95EE\u9898)\\s*[*_)\\]"\u201D\u2019]*\\s*(?:[\u2014\u2013]+|[:\uFF1A]|--+|-+\\s)\\s*([\\s\\S]*)',
12026
+ "i"
12027
+ );
12028
+ var CJK_RE = /[一-鿿]/g;
12029
+ var EXAMPLE_RECEIPT_CLAUSE = (DRY_RECEIPT_RE.exec(REVERSE_AUDIT_EXAMPLE_RECEIPT)?.[1] ?? "").trim();
12030
+ function substantiveClause(clause) {
12031
+ const c = clause.replace(/\s+/g, " ").trim();
12032
+ if (c.length === 0) return false;
12033
+ if (EXAMPLE_RECEIPT_CLAUSE.length > 0 && c.includes(EXAMPLE_RECEIPT_CLAUSE)) {
12034
+ return false;
12035
+ }
12036
+ if (/`[^`]+`/.test(c)) return true;
12037
+ if (/\w[\w.-]+\/[\w.$-]+\/\w/.test(c)) return true;
12038
+ if (/\w[\w.-]+\/[\w$-]+\.\w+/.test(c)) return true;
12039
+ if ((c.match(CJK_RE) ?? []).length >= 4) return true;
12040
+ return c.length >= 20;
12041
+ }
12042
+ __name(substantiveClause, "substantiveClause");
12043
+ function classifyReturn(rec, territory) {
12044
+ const text = rec.finalText.trim();
12045
+ if (SEVERITY_LINE_RE.test(text)) {
12046
+ for (const m of text.matchAll(FILE_LINE_RE)) {
12047
+ const file = (m[1] ?? "").trim();
12048
+ if (file === "" || /^N\/A\b/i.test(file)) continue;
12049
+ if (rec.launchPrompt.includes(`**File:** ${file}`)) continue;
12050
+ return "yielded";
12051
+ }
12052
+ }
12053
+ const receipt = DRY_RECEIPT_RE.exec(text);
12054
+ if (rec.successfulToolCalls > 0 && rec.diffToolCalls > 0 && openedTheTerritory(rec.diffReads, territory) && receipt !== null && substantiveClause(receipt[1] ?? "")) {
12055
+ return "dry";
12056
+ }
12057
+ return "unknown";
12058
+ }
12059
+ __name(classifyReturn, "classifyReturn");
12060
+ function openedTheTerritory(diffReads, territory) {
12061
+ if (territory.length === 0) return true;
12062
+ return diffReads.some(
12063
+ ([s, e]) => territory.some(([ts, te]) => s <= te && ts <= e)
12064
+ );
12065
+ }
12066
+ __name(openedTheTerritory, "openedTheTerritory");
12067
+ function mergeOutcomes(outcomes) {
12068
+ if (outcomes.includes("yielded")) return "yielded";
12069
+ if (outcomes.includes("dry")) return "dry";
12070
+ return "unknown";
12071
+ }
12072
+ __name(mergeOutcomes, "mergeOutcomes");
12073
+ function scheduleReverseAuditRound(planPath, chunkIds, round, env = process.env, diffPath) {
12074
+ if (round < 3) {
12075
+ return {
12076
+ due: [...chunkIds],
12077
+ coldChecks: [],
12078
+ skipped: [],
12079
+ converged: false
12080
+ };
12081
+ }
12082
+ const since = statSync8(planPath).mtimeMs;
12083
+ const transcripts = readTranscripts(since, env, diffPath);
12084
+ const built = readRecordedPrompts(planPath, since);
12085
+ const records = [];
12086
+ for (const [key, prompt] of built) {
12087
+ const m = RECORD_KEY_RE.exec(key);
12088
+ if (!m) continue;
12089
+ const r = Number(m[2]);
12090
+ if (r >= round) continue;
12091
+ records.push({
12092
+ chunkId: Number(m[1]),
12093
+ round: r,
12094
+ // Flattened ONCE per record, beside the once-per-transcript flatten
12095
+ // below: the pairing walk pays neither half per (record, transcript)
12096
+ // pair.
12097
+ lines: promptLines(prompt),
12098
+ territory: bakedRanges(prompt, diffPath)
12099
+ });
12100
+ }
12101
+ const candidates = transcripts.filter((t2) => t2.launchPrompt.includes(REVERSE_AUDIT_MARKER)).map((t2) => ({ transcript: t2, flat: flattenPrompt(t2.launchPrompt) }));
12102
+ const matchesByRecord = records.map(
12103
+ (rec) => candidates.filter((c) => deliveredVerbatimLines(c.flat, rec.lines)).map((c) => c.transcript)
12104
+ );
12105
+ const recordsPerTranscript = /* @__PURE__ */ new Map();
12106
+ for (const matches of matchesByRecord) {
12107
+ for (const t2 of matches) {
12108
+ recordsPerTranscript.set(t2, (recordsPerTranscript.get(t2) ?? 0) + 1);
12109
+ }
12110
+ }
12111
+ const outcomesByRecord = matchesByRecord.map(
12112
+ (matches, i) => matches.filter((t2) => recordsPerTranscript.get(t2) === 1).map((t2) => classifyReturn(t2, records[i].territory))
12113
+ );
12114
+ const history = /* @__PURE__ */ new Map();
12115
+ records.forEach((rec, i) => {
12116
+ let byRound = history.get(rec.chunkId);
12117
+ if (!byRound) {
12118
+ byRound = /* @__PURE__ */ new Map();
12119
+ history.set(rec.chunkId, byRound);
12120
+ }
12121
+ byRound.set(rec.round, [
12122
+ ...byRound.get(rec.round) ?? [],
12123
+ ...outcomesByRecord[i]
12124
+ ]);
12125
+ });
12126
+ const due = [];
12127
+ const coldChecks = [];
12128
+ const skipped = [];
12129
+ for (const chunkId of chunkIds) {
12130
+ const audits = [...history.get(chunkId)?.entries() ?? []].map(([r, outcomes]) => ({ round: r, outcome: mergeOutcomes(outcomes) })).sort((a, b) => a.round - b.round);
12131
+ const lastTwo = audits.slice(-2);
12132
+ const retired = lastTwo.length === 2 && lastTwo.every((a) => a.outcome === "dry");
12133
+ if (!retired) {
12134
+ due.push(chunkId);
12135
+ continue;
12136
+ }
12137
+ if (round % 2 === 0) {
12138
+ due.push(chunkId);
12139
+ coldChecks.push(chunkId);
12140
+ } else {
12141
+ skipped.push({
12142
+ chunkId,
12143
+ dryRounds: [lastTwo[0].round, lastTwo[1].round],
12144
+ // The next even round — this branch only runs on odd rounds, so
12145
+ // that is always round + 1. Whether the cap allows it is the note
12146
+ // composer's question, not the schedule's (see
12147
+ // REVERSE_AUDIT_MAX_ROUNDS).
12148
+ nextColdCheck: round + 1
12149
+ });
12150
+ }
12151
+ }
12152
+ return {
12153
+ due,
12154
+ coldChecks,
12155
+ skipped,
12156
+ // An empty `chunkIds` empties `due` vacuously — nothing was ever under
12157
+ // audit, so nothing has proven itself cold. `runAllChunks` refuses a
12158
+ // chunkless plan long before scheduling, but this function is exported,
12159
+ // and convergence is an exit-5 termination rule: it must not be
12160
+ // reachable from nothing.
12161
+ converged: chunkIds.length > 0 && due.length === 0
12162
+ };
12163
+ }
12164
+ __name(scheduleReverseAuditRound, "scheduleReverseAuditRound");
11660
12165
 
11661
12166
  // packages/cli/src/commands/review/lib/path-rules.ts
11662
12167
  init_esbuild_shims();
@@ -12108,7 +12613,7 @@ function buildRoleBrief(report, role, opts = {}) {
12108
12613
  "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."
12109
12614
  );
12110
12615
  }
12111
- const ctx = opts.planPath ? join12(dirname14(resolve13(opts.planPath)), `qwen-review-pr-${pr}-context.md`) : null;
12616
+ const ctx = opts.planPath ? join13(dirname14(resolve13(opts.planPath)), `qwen-review-pr-${pr}-context.md`) : null;
12112
12617
  parts.push(
12113
12618
  "",
12114
12619
  `**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.`
@@ -12367,7 +12872,7 @@ The \`description\` parameter of each Agent call is the task name the user watch
12367
12872
  );
12368
12873
  }
12369
12874
  __name(runRoster, "runRoster");
12370
- function runAllChunks(report, planPath, role, findingsContent, rules, round) {
12875
+ function requireAuditableChunks(report) {
12371
12876
  if (!Array.isArray(report.chunks) || report.chunks.length === 0) {
12372
12877
  throw new Error("agent-prompt: the plan has no `chunks[]`.");
12373
12878
  }
@@ -12378,9 +12883,60 @@ function runAllChunks(report, planPath, role, findingsContent, rules, round) {
12378
12883
  `agent-prompt: the plan has ${problem} \u2014 a round built from what remains would look complete while a territory goes unaudited. Re-run the Step 1 capture; do not hand-edit the plan.`
12379
12884
  );
12380
12885
  }
12886
+ return chunks;
12887
+ }
12888
+ __name(requireAuditableChunks, "requireAuditableChunks");
12889
+ function admitReverseAuditRound(planPath, round) {
12890
+ const spent = reverseAuditBudgetExhausted(
12891
+ process.env,
12892
+ expectedRoundSeconds(planPath, round)
12893
+ );
12894
+ if (spent !== null) {
12895
+ writeBudgetStop(planPath, spent, round);
12896
+ writeStderrLine(reverseAuditBudgetMessage(spent, round));
12897
+ process.exitCode = 4;
12898
+ return false;
12899
+ }
12900
+ return true;
12901
+ }
12902
+ __name(admitReverseAuditRound, "admitReverseAuditRound");
12903
+ function refuseConverged() {
12904
+ writeStderrLine(
12905
+ "CONVERGED: every chunk holds two consecutive substantive dry audits; the reverse audit has converged \u2014 stop the loop and proceed to Step 6. This is a clean convergence, not a gap: no unreviewedDimensions entry is owed."
12906
+ );
12907
+ process.exitCode = 5;
12908
+ }
12909
+ __name(refuseConverged, "refuseConverged");
12910
+ function runAllChunks(report, planPath, role, findingsContent, rules, round) {
12911
+ const chunks = requireAuditableChunks(report);
12912
+ let schedule = null;
12913
+ if (role === "reverse-audit" && round !== void 0 && round >= 3) {
12914
+ try {
12915
+ schedule = scheduleReverseAuditRound(
12916
+ planPath,
12917
+ chunks.map((c) => c.id),
12918
+ round,
12919
+ process.env,
12920
+ typeof report.diffPathAbsolute === "string" ? report.diffPathAbsolute : void 0
12921
+ );
12922
+ } catch {
12923
+ schedule = null;
12924
+ }
12925
+ }
12926
+ if (schedule !== null && schedule.converged) {
12927
+ refuseConverged();
12928
+ return;
12929
+ }
12930
+ if (role === "reverse-audit" && !admitReverseAuditRound(planPath, round)) {
12931
+ return;
12932
+ }
12933
+ const dueSet = schedule === null ? null : new Set(schedule.due);
12934
+ const dueChunks = dueSet === null ? chunks : chunks.filter((c) => dueSet.has(c.id));
12935
+ const coldSet = new Set(schedule?.coldChecks ?? []);
12936
+ const skipped = schedule?.skipped ?? [];
12381
12937
  const digest = findingsDigest(findingsContent, rules);
12382
12938
  const roundPart = round !== void 0 ? `--round-${round}` : "";
12383
- const blocks = chunks.map((c, i) => {
12939
+ const blocks = dueChunks.map((c, i) => {
12384
12940
  const key = `${role}--chunk-${c.id}${roundPart}--${digest}`;
12385
12941
  const { prompt } = buildLaunch(
12386
12942
  report,
@@ -12390,17 +12946,29 @@ function runAllChunks(report, planPath, role, findingsContent, rules, round) {
12390
12946
  );
12391
12947
  const printed = foldFindings(role, findingsContent, prompt);
12392
12948
  recordPrompt(planPath, key, printed);
12393
- return `\u2500\u2500\u2500\u2500\u2500 auditor ${i + 1} of ${chunks.length} \u2014 chunk ${c.id} \u2500\u2500\u2500\u2500\u2500
12949
+ const cold = coldSet.has(c.id) ? " (cold check)" : "";
12950
+ return `\u2500\u2500\u2500\u2500\u2500 auditor ${i + 1} of ${dueChunks.length} \u2014 chunk ${c.id}${cold} \u2500\u2500\u2500\u2500\u2500
12394
12951
 
12395
12952
  ` + printed;
12396
12953
  });
12954
+ const scope = skipped.length === 0 ? "one per chunk" : `one per chunk still under audit (${skipped.length} retired chunk(s) skipped; the retirement note after the end-of-round line says which \u2014 relay it to the terminal)`;
12955
+ const retirementNote = skipped.length === 0 ? [] : [
12956
+ `retirement: a chunk whose two most recent audits are substantive dry receipts is cold-checked on alternating rounds instead of audited on every one; a cold check that yields returns it to every-round auditing. Skipped this round:
12957
+ ` + skipped.map(
12958
+ (s) => `chunk ${s.chunkId} \u2014 retired: dry in rounds ${s.dryRounds[0]} and ${s.dryRounds[1]}, ` + (s.nextColdCheck > REVERSE_AUDIT_MAX_ROUNDS ? `certificate final \u2014 the ${REVERSE_AUDIT_MAX_ROUNDS}-round hard cap leaves the loop no round for a cold check` : `next cold check round ${s.nextColdCheck}`)
12959
+ ).join("\n")
12960
+ ];
12397
12961
  writeStdoutLine(
12398
12962
  [
12399
- `${chunks.length} auditors required this round \u2014 one per chunk. Launch one agent per block below, passing its block VERBATIM \u2014 copy, do not retype, and NEVER sample this output (no \`| head\`): the text IS the deliverable, and a launch reconstructed from a sample matches no record. Blocks are numbered \`auditor k of ${chunks.length}\` and the output ends with an end-of-round line \u2014 if either is missing, the output was truncated in transit; rebuild just the missing chunks with --chunk <id>. Write each Agent call's \`description\` (the task name the user watches) in your output language, translating the separator label \u2014 display only; the prompt stays the block VERBATIM.`,
12963
+ `${dueChunks.length} auditors required this round \u2014 ${scope}. Launch one agent per block below, passing its block VERBATIM \u2014 copy, do not retype, and NEVER sample this output (no \`| head\`): the text IS the deliverable, and a launch reconstructed from a sample matches no record. Blocks are numbered \`auditor k of ${dueChunks.length}\`, and the output ends with an end-of-round line \u2014 followed by the retirement note, when there is one. If either the numbering or the end-of-round line is missing, the output was truncated in transit; rebuild just the missing chunks with --chunk <id>. Write each Agent call's \`description\` (the task name the user watches) in your output language, translating the separator label \u2014 display only; the prompt stays the block VERBATIM.`,
12400
12964
  ...blocks,
12401
- `\u2500\u2500\u2500\u2500\u2500 end of round \u2014 ${chunks.length} auditors \u2500\u2500\u2500\u2500\u2500`
12965
+ `\u2500\u2500\u2500\u2500\u2500 end of round \u2014 ${dueChunks.length} auditors \u2500\u2500\u2500\u2500\u2500`,
12966
+ ...retirementNote
12402
12967
  ].join("\n\n")
12403
12968
  );
12969
+ if (role === "reverse-audit") {
12970
+ stampRound(planPath, round);
12971
+ }
12404
12972
  }
12405
12973
  __name(runAllChunks, "runAllChunks");
12406
12974
  function runAgentPrompt(args) {
@@ -12483,6 +13051,11 @@ function runAgentPrompt(args) {
12483
13051
  );
12484
13052
  }
12485
13053
  }
13054
+ if (role === "reverse-audit" && !hasRound) {
13055
+ bad(
13056
+ "--role reverse-audit builds one round of the Step 5 loop and requires --round <k>: the label keys the record and the budget gate's per-round accounting."
13057
+ );
13058
+ }
12486
13059
  } else if (hasFindings) {
12487
13060
  const findingRoles = Object.keys(BRIEFS).filter(
12488
13061
  (r) => BRIEFS[r].acceptsFindings
@@ -12513,7 +13086,7 @@ function runAgentPrompt(args) {
12513
13086
  }
12514
13087
  let report;
12515
13088
  try {
12516
- report = JSON.parse(readFileSync17(args.plan, "utf8"));
13089
+ report = JSON.parse(readFileSync18(args.plan, "utf8"));
12517
13090
  } catch (err) {
12518
13091
  throw new Error(
12519
13092
  `agent-prompt: cannot read the plan ${args.plan}: ${err.message}`
@@ -12522,7 +13095,7 @@ function runAgentPrompt(args) {
12522
13095
  let rules;
12523
13096
  if (args.rules) {
12524
13097
  try {
12525
- rules = readFileSync17(args.rules, "utf8");
13098
+ rules = readFileSync18(args.rules, "utf8");
12526
13099
  } catch (err) {
12527
13100
  throw new Error(
12528
13101
  `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.`
@@ -12537,7 +13110,7 @@ function runAgentPrompt(args) {
12537
13110
  if (hasFindings && args.role) {
12538
13111
  const role = args.role;
12539
13112
  try {
12540
- findingsContent = readFileSync17(args.findings, "utf8");
13113
+ findingsContent = readFileSync18(args.findings, "utf8");
12541
13114
  } catch (err) {
12542
13115
  throw new Error(
12543
13116
  `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).`
@@ -12549,6 +13122,30 @@ function runAgentPrompt(args) {
12549
13122
  );
12550
13123
  }
12551
13124
  }
13125
+ if (args.role === "reverse-audit" && !hasChunk && !args.allChunks && !admitReverseAuditRound(args.plan, args.round)) {
13126
+ return;
13127
+ }
13128
+ if (args.role === "reverse-audit" && hasChunk && !readRoundStamps(args.plan).some((s) => s.round === (args.round ?? null))) {
13129
+ if (args.round !== void 0) {
13130
+ let schedule = null;
13131
+ try {
13132
+ schedule = scheduleReverseAuditRound(
13133
+ args.plan,
13134
+ (Array.isArray(report.chunks) ? report.chunks : []).map((c) => c?.id).filter((id) => typeof id === "number"),
13135
+ args.round,
13136
+ process.env,
13137
+ typeof report.diffPathAbsolute === "string" ? report.diffPathAbsolute : void 0
13138
+ );
13139
+ } catch {
13140
+ schedule = null;
13141
+ }
13142
+ if (schedule !== null && schedule.converged) {
13143
+ refuseConverged();
13144
+ return;
13145
+ }
13146
+ }
13147
+ if (!admitReverseAuditRound(args.plan, args.round)) return;
13148
+ }
12552
13149
  if (args.allChunks && args.role && findingsContent !== void 0) {
12553
13150
  runAllChunks(
12554
13151
  report,
@@ -12588,11 +13185,14 @@ function runAgentPrompt(args) {
12588
13185
  const printed = findingsContent !== void 0 && args.role ? foldFindings(args.role, findingsContent, prompt) : prompt;
12589
13186
  recordPrompt(args.plan, key, printed);
12590
13187
  writeStdoutLine(printed);
13188
+ if (args.role === "reverse-audit") {
13189
+ stampRound(args.plan, args.round);
13190
+ }
12591
13191
  }
12592
13192
  __name(runAgentPrompt, "runAgentPrompt");
12593
13193
  var agentPromptCommand = {
12594
13194
  command: "agent-prompt",
12595
- describe: "Build a review agent's launch prompt from the plan (the diff path, its line ranges and the agent's own brief are welded in, not left to the caller to remember)",
13195
+ describe: "Build a review agent's launch prompt from the plan (the diff path, its line ranges and the agent's own brief are welded in, not left to the caller to remember). Exit codes: 0 built; 4 the review time budget refused another reverse-audit round (a termination rule, not an error \u2014 see the BUDGET line on stderr); 5 the reverse audit CONVERGED \u2014 every chunk holds two consecutive substantive dry audits and none is due a cold check, so stop the loop and proceed to Step 6 (also a termination rule, and a clean one: no disclosure is owed); anything else is a bad call or a broken plan.",
12596
13196
  builder: /* @__PURE__ */ __name((yargs) => yargs.option("plan", {
12597
13197
  type: "string",
12598
13198
  demandOption: true,
@@ -12603,13 +13203,13 @@ var agentPromptCommand = {
12603
13203
  describe: "The dimension this agent owns. Builds its WHOLE prompt \u2014 the diff's line ranges, the brief, the finding format, the severity definitions and the project rules. Pass it to the agent verbatim."
12604
13204
  }).option("chunk", {
12605
13205
  type: "number",
12606
- describe: "Which chunk id this agent owns (a Step 3B territory agent)"
13206
+ describe: "Which chunk id this agent owns (a Step 3B territory agent). With --role reverse-audit --round N it rebuilds ONE auditor of that round: a round already admitted is repaired without gates; a round never admitted answers to the same convergence and budget gates as --all-chunks."
12607
13207
  }).option("file", {
12608
13208
  type: "string",
12609
13209
  describe: "The heavily-rewritten file an invariant agent owns (--role invariant-a|invariant-b|invariant-c)"
12610
13210
  }).option("all-chunks", {
12611
13211
  type: "boolean",
12612
- describe: "With --role reverse-audit --findings: build one block per chunk in one call, labelled and separated (Step 5, 3B). Never sample the output; each block is pasted verbatim to its own agent."
13212
+ describe: "With --role reverse-audit --findings: build one block per chunk in one call, labelled and separated (Step 5, 3B). Never sample the output; each block is pasted verbatim to its own agent. From --round 3 on, a chunk whose two most recent audits were both substantive dry receipts is retired to alternating-round cold checks (the retirement note after the end-of-round line says which); a round where every chunk is retired and none is due exits 5 \u2014 the audit has converged."
12613
13213
  }).option("roster", {
12614
13214
  type: "boolean",
12615
13215
  describe: "Build EVERY prompt the plan requires \u2014 chunk, dimension and invariant agents alike \u2014 in one call, each labelled and separated. The list is the same one check-coverage reads out of the plan."
@@ -12647,17 +13247,17 @@ init_esbuild_shims();
12647
13247
  import { spawnSync as spawnSync2 } from "node:child_process";
12648
13248
  import {
12649
13249
  existsSync as existsSync7,
12650
- readFileSync as readFileSync19,
13250
+ readFileSync as readFileSync20,
12651
13251
  rmSync as rmSync5,
12652
13252
  statfsSync,
12653
- writeFileSync as writeFileSync17
13253
+ writeFileSync as writeFileSync18
12654
13254
  } from "node:fs";
12655
- import { join as join14, resolve as resolve14 } from "node:path";
13255
+ import { join as join15, resolve as resolve14 } from "node:path";
12656
13256
 
12657
13257
  // packages/cli/src/commands/review/lib/workspaces.ts
12658
13258
  init_esbuild_shims();
12659
- import { existsSync as existsSync6, readFileSync as readFileSync18, readdirSync as readdirSync4 } from "node:fs";
12660
- import { join as join13 } from "node:path";
13259
+ import { existsSync as existsSync6, readFileSync as readFileSync19, readdirSync as readdirSync4 } from "node:fs";
13260
+ import { join as join14 } from "node:path";
12661
13261
  function isWorkspaceMember(filePath, workspaceGlobs) {
12662
13262
  return workspaceDirFor(filePath, workspaceGlobs) !== null;
12663
13263
  }
@@ -12665,9 +13265,10 @@ __name(isWorkspaceMember, "isWorkspaceMember");
12665
13265
  function workspaceDirFor(filePath, workspaceGlobs) {
12666
13266
  const norm = filePath.replace(/^\.\//, "");
12667
13267
  let owner = null;
13268
+ const previous = [];
12668
13269
  for (const glob of workspaceGlobs) {
12669
13270
  const negated = glob.startsWith("!");
12670
- const g = glob.replace(/^!/, "").replace(/\/$/, "");
13271
+ const g = glob.replace(/^!/, "").replace(/^\.\//, "").replace(/\/$/, "");
12671
13272
  let dir = null;
12672
13273
  if (g.endsWith("/*")) {
12673
13274
  const base = g.slice(0, -2);
@@ -12679,14 +13280,27 @@ function workspaceDirFor(filePath, workspaceGlobs) {
12679
13280
  dir = g;
12680
13281
  }
12681
13282
  if (dir === null) continue;
12682
- owner = negated ? null : dir;
13283
+ if (!negated) {
13284
+ if (dir !== owner) {
13285
+ previous.push(owner);
13286
+ owner = dir;
13287
+ }
13288
+ } else if (dir === owner) {
13289
+ owner = previous.pop() ?? null;
13290
+ }
12683
13291
  }
12684
13292
  return owner;
12685
13293
  }
12686
13294
  __name(workspaceDirFor, "workspaceDirFor");
13295
+ function isNegationExcluded(filePath, workspaceGlobs) {
13296
+ if (workspaceDirFor(filePath, workspaceGlobs) !== null) return false;
13297
+ const positives = workspaceGlobs.filter((g) => !g.startsWith("!"));
13298
+ return workspaceDirFor(filePath, positives) !== null;
13299
+ }
13300
+ __name(isNegationExcluded, "isNegationExcluded");
12687
13301
  function hasUnmodeledWorkspaceGlob(globs) {
12688
13302
  return globs.some((glob) => {
12689
- const g = glob.replace(/^!/, "");
13303
+ const g = glob.replace(/^!/, "").replace(/^\.\//, "");
12690
13304
  if (!g.includes("*")) return false;
12691
13305
  return !/^[^*]+\/\*$/.test(g);
12692
13306
  });
@@ -12695,7 +13309,7 @@ __name(hasUnmodeledWorkspaceGlob, "hasUnmodeledWorkspaceGlob");
12695
13309
  function readWorkspaceGlobs(root) {
12696
13310
  try {
12697
13311
  const pkg = JSON.parse(
12698
- readFileSync18(join13(root, "package.json"), "utf8")
13312
+ readFileSync19(join14(root, "package.json"), "utf8")
12699
13313
  );
12700
13314
  const ws = pkg.workspaces;
12701
13315
  const globs = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
@@ -12708,31 +13322,51 @@ __name(readWorkspaceGlobs, "readWorkspaceGlobs");
12708
13322
  function readRootPackage(root) {
12709
13323
  let pkg;
12710
13324
  try {
12711
- pkg = JSON.parse(readFileSync18(join13(root, "package.json"), "utf8"));
13325
+ pkg = JSON.parse(readFileSync19(join14(root, "package.json"), "utf8"));
12712
13326
  } catch {
12713
13327
  return null;
12714
13328
  }
13329
+ if (pkg === null) return null;
12715
13330
  const scripts = Object.keys(pkg.scripts ?? {});
12716
13331
  if (!scripts.includes("build") && !scripts.includes("test")) return null;
13332
+ const scriptsText = {};
13333
+ for (const [k, v] of Object.entries(pkg.scripts ?? {})) {
13334
+ if (typeof v === "string") scriptsText[k] = v;
13335
+ }
12717
13336
  return {
12718
13337
  dir: ".",
12719
13338
  name: typeof pkg.name === "string" && pkg.name ? pkg.name : "root",
12720
13339
  scripts,
12721
- deps: []
13340
+ deps: declaredDeps(pkg),
13341
+ scriptsText
12722
13342
  };
12723
13343
  }
12724
13344
  __name(readRootPackage, "readRootPackage");
12725
- function readWorkspacePackages(root) {
12726
- const globs = readWorkspaceGlobs(root);
13345
+ function scriptFansOut(text) {
13346
+ return typeof text === "string" && /(^|\s)(--workspaces(?=\s|$)|--?ws(?=\s|$))/.test(text);
13347
+ }
13348
+ __name(scriptFansOut, "scriptFansOut");
13349
+ function declaredDeps(pkg) {
13350
+ return [
13351
+ ...Object.keys(pkg.dependencies ?? {}),
13352
+ ...Object.keys(pkg.devDependencies ?? {}),
13353
+ ...Object.keys(pkg.peerDependencies ?? {}),
13354
+ ...Object.keys(pkg.optionalDependencies ?? {})
13355
+ ];
13356
+ }
13357
+ __name(declaredDeps, "declaredDeps");
13358
+ function workspaceDirCandidates(root, globs) {
12727
13359
  const dirs = /* @__PURE__ */ new Set();
12728
13360
  for (const glob of globs) {
12729
13361
  if (glob.startsWith("!")) continue;
12730
- const g = glob.replace(/\/$/, "");
13362
+ const g = glob.replace(/^\.\//, "").replace(/\/$/, "");
12731
13363
  if (g.endsWith("/*")) {
12732
13364
  const base = g.slice(0, -2);
12733
13365
  let entries;
12734
13366
  try {
12735
- entries = readdirSync4(join13(root, base), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
13367
+ entries = readdirSync4(join14(root, base), { withFileTypes: true }).filter(
13368
+ (e) => e.isDirectory() || e.isSymbolicLink() && existsSync6(join14(root, base, e.name, "package.json"))
13369
+ ).map((e) => e.name);
12736
13370
  } catch {
12737
13371
  continue;
12738
13372
  }
@@ -12741,30 +13375,43 @@ function readWorkspacePackages(root) {
12741
13375
  dirs.add(g);
12742
13376
  }
12743
13377
  }
12744
- const pkgs = [];
12745
- for (const dir of dirs) {
12746
- if (workspaceDirFor(`${dir}/package.json`, globs) !== dir) continue;
12747
- const manifest = join13(root, dir, "package.json");
13378
+ return [...dirs].sort();
13379
+ }
13380
+ __name(workspaceDirCandidates, "workspaceDirCandidates");
13381
+ function readWorkspacePackages(root) {
13382
+ const globs = readWorkspaceGlobs(root);
13383
+ const packages = [];
13384
+ const skipped = [];
13385
+ for (const dir of workspaceDirCandidates(root, globs)) {
13386
+ const manifest = join14(root, dir, "package.json");
12748
13387
  if (!existsSync6(manifest)) continue;
13388
+ if (workspaceDirFor(`${dir}/package.json`, globs) !== dir) {
13389
+ const positives = globs.filter((g) => !g.startsWith("!"));
13390
+ if (workspaceDirFor(`${dir}/package.json`, positives) === dir) continue;
13391
+ skipped.push(dir);
13392
+ continue;
13393
+ }
12749
13394
  let pkg;
12750
13395
  try {
12751
- pkg = JSON.parse(readFileSync18(manifest, "utf8"));
13396
+ pkg = JSON.parse(readFileSync19(manifest, "utf8"));
12752
13397
  } catch {
13398
+ skipped.push(dir);
13399
+ continue;
13400
+ }
13401
+ if (pkg === null || typeof pkg.name !== "string" || !pkg.name) {
13402
+ skipped.push(dir);
12753
13403
  continue;
12754
13404
  }
12755
- if (typeof pkg.name !== "string" || !pkg.name) continue;
12756
- pkgs.push({
13405
+ packages.push({
12757
13406
  dir,
12758
13407
  name: pkg.name,
12759
13408
  scripts: Object.keys(pkg.scripts ?? {}),
12760
- deps: [
12761
- ...Object.keys(pkg.dependencies ?? {}),
12762
- ...Object.keys(pkg.devDependencies ?? {}),
12763
- ...Object.keys(pkg.peerDependencies ?? {})
12764
- ]
13409
+ deps: declaredDeps(pkg)
12765
13410
  });
12766
13411
  }
12767
- return pkgs.sort((a, b) => a.dir.localeCompare(b.dir));
13412
+ packages.sort((a, b) => a.dir.localeCompare(b.dir));
13413
+ skipped.sort();
13414
+ return { packages, skipped };
12768
13415
  }
12769
13416
  __name(readWorkspacePackages, "readWorkspacePackages");
12770
13417
  function affectedWorkspaces(changedFiles, workspaceGlobs) {
@@ -12776,8 +13423,7 @@ function affectedWorkspaces(changedFiles, workspaceGlobs) {
12776
13423
  return [...dirs].sort();
12777
13424
  }
12778
13425
  __name(affectedWorkspaces, "affectedWorkspaces");
12779
- function buildSetFor(affected, packages, alsoBuild = []) {
12780
- const byDir = new Map(packages.map((p) => [p.dir, p]));
13426
+ function dependencyEdges(packages) {
12781
13427
  const byName = new Map(packages.map((p) => [p.name, p]));
12782
13428
  const dependsOn = /* @__PURE__ */ new Map();
12783
13429
  for (const p of packages) {
@@ -12786,6 +13432,12 @@ function buildSetFor(affected, packages, alsoBuild = []) {
12786
13432
  p.deps.map((d) => byName.get(d)?.dir).filter((d) => !!d && d !== p.dir)
12787
13433
  );
12788
13434
  }
13435
+ return dependsOn;
13436
+ }
13437
+ __name(dependencyEdges, "dependencyEdges");
13438
+ function reverseDependencyClosure(affected, packages) {
13439
+ const byDir = new Map(packages.map((p) => [p.dir, p]));
13440
+ const dependsOn = dependencyEdges(packages);
12789
13441
  const consumers = new Set(affected.filter((a) => byDir.has(a)));
12790
13442
  let grew = true;
12791
13443
  while (grew) {
@@ -12798,6 +13450,13 @@ function buildSetFor(affected, packages, alsoBuild = []) {
12798
13450
  }
12799
13451
  }
12800
13452
  }
13453
+ return [...consumers].sort();
13454
+ }
13455
+ __name(reverseDependencyClosure, "reverseDependencyClosure");
13456
+ function buildSetFor(affected, packages, alsoBuild = []) {
13457
+ const byDir = new Map(packages.map((p) => [p.dir, p]));
13458
+ const dependsOn = dependencyEdges(packages);
13459
+ const consumers = reverseDependencyClosure(affected, packages);
12801
13460
  const wanted = /* @__PURE__ */ new Set();
12802
13461
  const addDeps = /* @__PURE__ */ __name((dir) => {
12803
13462
  if (wanted.has(dir)) return;
@@ -12822,13 +13481,81 @@ function buildSetFor(affected, packages, alsoBuild = []) {
12822
13481
  }
12823
13482
  __name(buildSetFor, "buildSetFor");
12824
13483
 
13484
+ // packages/cli/src/commands/review/lib/workspace-scope.ts
13485
+ init_esbuild_shims();
13486
+ var LICENSE_LIKE_RE = /(^|\/)(LICEN[CS]ES?|COPYING|NOTICES?)(-[^/.]+)?(\.(md|txt|rst))?$/;
13487
+ function isInertLicense(path3) {
13488
+ return LICENSE_LIKE_RE.test(path3);
13489
+ }
13490
+ __name(isInertLicense, "isInertLicense");
13491
+ var INERT_OUTSIDE_RE = /^(\.github\/|CHANGELOG\.md$|\.gitignore$|\.gitattributes$|\.editorconfig$|\.idea\/|\.vscode\/)/;
13492
+ function resolveTestScope(input) {
13493
+ const { changed, globs, packages, skipped } = input;
13494
+ const affected = affectedWorkspaces(changed, globs);
13495
+ const graph = input.rootPackage ? [input.rootPackage, ...packages] : packages;
13496
+ const scriptsOf = new Map(graph.map((p) => [p.dir, p.scripts]));
13497
+ const caveats = [];
13498
+ if (skipped.length > 0) {
13499
+ caveats.push(
13500
+ `the workspace graph could not be fully computed: ${skipped.join(", ")} ${skipped.length === 1 ? "has" : "have"} a package.json that does not parse, has no usable \`name\`, or is shadowed by a later workspace glob, so a reverse dependency may be missing from the scoped set`
13501
+ );
13502
+ }
13503
+ const unmapped = affected.filter(
13504
+ (d) => d !== "." && !scriptsOf.has(d) && !skipped.includes(d)
13505
+ );
13506
+ const outside = changed.filter((f) => workspaceDirFor(f, globs) === null);
13507
+ const excluded = outside.filter((f) => isNegationExcluded(f, globs));
13508
+ const influential = outside.filter(
13509
+ (f) => !isInertLicense(f) && !f.startsWith("docs/") && !INERT_OUTSIDE_RE.test(f) && !isNegationExcluded(f, globs)
13510
+ );
13511
+ if (unmapped.length > 0) {
13512
+ caveats.push(
13513
+ `the workspace globs claim ${unmapped.join(", ")}, but no readable package.json there makes a graph member \u2014 no suite covers a change inside, and a dependent of the diff may be missing from the scoped set`
13514
+ );
13515
+ }
13516
+ if (influential.length > 0) {
13517
+ caveats.push(
13518
+ `${influential.length} changed file(s) sit outside every workspace and are not inert (e.g. ${influential.slice(0, 3).join(", ")}); a root script or config can affect any package's tests, and the scoped set cannot cover them`
13519
+ );
13520
+ }
13521
+ if (excluded.length > 0) {
13522
+ caveats.push(
13523
+ `${excluded.length} changed file(s) sit in negated workspaces (e.g. ${excluded.slice(0, 3).join(", ")}) \u2014 excluded from the npm workspace graph (a separate toolchain and lockfile); their own toolchain's suites were not run`
13524
+ );
13525
+ }
13526
+ const closure = reverseDependencyClosure(affected, graph);
13527
+ let workspaces = closure.filter((d) => scriptsOf.get(d)?.includes("test"));
13528
+ let fanOutCaveat;
13529
+ if (input.rootTestFansOut && workspaces.includes(".")) {
13530
+ workspaces = workspaces.filter((d) => d !== ".");
13531
+ fanOutCaveat = "the root's `test` script fans out over every workspace (`--workspaces`), so the root suite did not run \u2014 it cannot finish inside one command deadline; the scoped member suites are the coverage";
13532
+ }
13533
+ {
13534
+ const rootRuns = input.rootPackage?.scripts.includes("test") && !input.rootTestFansOut ? 1 : 0;
13535
+ const testable = packages.filter((p) => p.scripts.includes("test")).length + rootRuns;
13536
+ const scoped = workspaces.length;
13537
+ if (scoped * 2 > testable) {
13538
+ caveats.push(
13539
+ `the diff's reverse-dependency closure covers ${scoped} of ${testable} testable ${rootRuns ? "suites (including the root)" : "workspaces"} \u2014 more than half, so the scoped set is not a meaningful narrowing`
13540
+ );
13541
+ }
13542
+ }
13543
+ if (fanOutCaveat) caveats.push(fanOutCaveat);
13544
+ return caveats.length > 0 ? { workspaces, caveat: caveats.join("; ") } : { workspaces };
13545
+ }
13546
+ __name(resolveTestScope, "resolveTestScope");
13547
+
12825
13548
  // packages/cli/src/commands/review/build-test.ts
13549
+ function shellArg(dir) {
13550
+ return `"${dir.replace(/[\\"$`]/g, "\\$&")}"`;
13551
+ }
13552
+ __name(shellArg, "shellArg");
12826
13553
  function buildCommand(dir) {
12827
- return dir === "." ? "npm run build" : `npm run build --workspace="${dir}"`;
13554
+ return dir === "." ? "npm run build" : `npm run build --workspace=${shellArg(dir)}`;
12828
13555
  }
12829
13556
  __name(buildCommand, "buildCommand");
12830
13557
  function testCommand(dir) {
12831
- return dir === "." ? "npm test" : `npm test --workspace="${dir}"`;
13558
+ return dir === "." ? "npm test" : `npm test --workspace=${shellArg(dir)}`;
12832
13559
  }
12833
13560
  __name(testCommand, "testCommand");
12834
13561
  var KEEP_HEAD = 2e3;
@@ -12863,6 +13590,7 @@ ${rescued.join("\n")}
12863
13590
  __name(trimOutput, "trimOutput");
12864
13591
  var INSTALL_MIN_FREE_BYTES = 3 * 1024 ** 3;
12865
13592
  var BUILD_MIN_FREE_BYTES = 1024 ** 3;
13593
+ var BUDGET_MIN_ATTEMPT_MS = 15e3;
12866
13594
  function freeDiskBytes(dir) {
12867
13595
  try {
12868
13596
  const s = statfsSync(dir);
@@ -12900,7 +13628,8 @@ function run(command, cwd, timeoutMs) {
12900
13628
  exitCode: r.status,
12901
13629
  seconds: Math.round((Date.now() - started) / 1e3),
12902
13630
  timedOut,
12903
- output: trimOutput(`${r.stdout ?? ""}${r.stderr ?? ""}`)
13631
+ output: trimOutput(`${r.stdout ?? ""}${r.stderr ?? ""}`),
13632
+ deadlineMs: timeoutMs
12904
13633
  };
12905
13634
  }
12906
13635
  __name(run, "run");
@@ -12921,7 +13650,7 @@ __name(unresolvedWorkspaceDeps, "unresolvedWorkspaceDeps");
12921
13650
  function changedFilesFrom(planPath) {
12922
13651
  let parsed;
12923
13652
  try {
12924
- parsed = JSON.parse(readFileSync19(planPath, "utf8"));
13653
+ parsed = JSON.parse(readFileSync20(planPath, "utf8"));
12925
13654
  } catch (err) {
12926
13655
  throw new Error(
12927
13656
  `build-test: cannot read the plan ${planPath}: ${err.message}`
@@ -12940,6 +13669,10 @@ __name(changedFilesFrom, "changedFilesFrom");
12940
13669
  function runBuildTest(args) {
12941
13670
  const root = resolve14(args.worktree);
12942
13671
  const perCommandMs = args.timeout * 1e3;
13672
+ const callBudgetMs = (args.budget ?? Math.max(args.timeout, args.timeout * 2 - 30)) * 1e3;
13673
+ const runStarted = Date.now();
13674
+ const remainingMs = /* @__PURE__ */ __name(() => callBudgetMs - (Date.now() - runStarted), "remainingMs");
13675
+ const deadlineSecs = /* @__PURE__ */ __name((r) => Math.round((r.deadlineMs ?? perCommandMs) / 1e3), "deadlineSecs");
12943
13676
  const exec = args.exec ?? run;
12944
13677
  const changed = changedFilesFrom(args.plan);
12945
13678
  const unsupportedReport = /* @__PURE__ */ __name((note) => ({
@@ -12955,15 +13688,13 @@ function runBuildTest(args) {
12955
13688
  note
12956
13689
  }), "unsupportedReport");
12957
13690
  const globs = readWorkspaceGlobs(root);
12958
- let packages = readWorkspacePackages(root);
13691
+ let { packages, skipped } = readWorkspacePackages(root);
13692
+ const rootPkg = readRootPackage(root);
12959
13693
  let singleRoot = false;
12960
13694
  const unmodeled = globs.length > 0 && hasUnmodeledWorkspaceGlob(globs);
12961
- if (!unmodeled && globs.length === 0) {
12962
- const rootPkg = readRootPackage(root);
12963
- if (rootPkg) {
12964
- packages = [rootPkg];
12965
- singleRoot = true;
12966
- }
13695
+ if (!unmodeled && globs.length === 0 && rootPkg) {
13696
+ packages = [rootPkg];
13697
+ singleRoot = true;
12967
13698
  }
12968
13699
  if (unmodeled || !singleRoot && (globs.length === 0 || packages.length === 0)) {
12969
13700
  return unsupportedReport(
@@ -12971,6 +13702,15 @@ function runBuildTest(args) {
12971
13702
  );
12972
13703
  }
12973
13704
  const affected = singleRoot ? changed.length > 0 ? ["."] : [] : affectedWorkspaces(changed, globs);
13705
+ let testScope = singleRoot || args.buildOnly ? void 0 : resolveTestScope({
13706
+ changed,
13707
+ globs,
13708
+ packages,
13709
+ skipped,
13710
+ rootPackage: rootPkg,
13711
+ rootTestFansOut: rootPkg?.scripts.includes("test") ? scriptFansOut(rootPkg.scriptsText["test"]) : false
13712
+ });
13713
+ const scopeGraph = !singleRoot && rootPkg ? [rootPkg, ...packages] : packages;
12974
13714
  if (affected.length === 0) {
12975
13715
  return {
12976
13716
  toolchain: "npm",
@@ -12980,12 +13720,13 @@ function runBuildTest(args) {
12980
13720
  install: null,
12981
13721
  build: [],
12982
13722
  test: [],
13723
+ ...testScope ? { testScope } : {},
12983
13724
  ok: true,
12984
13725
  timedOut: [],
12985
- note: `The diff changes ${changed.length} file(s), none of them inside a workspace (docs, root config, CI). There is no package to build and no test to run \u2014 this is a complete answer, not a skipped step.`
13726
+ note: args.buildOnly ? `The diff changes ${changed.length} file(s), none of them inside a workspace. There is no package to build, and tests are out of scope for a build-only probe.` : testScope?.caveat ? `The diff changes ${changed.length} file(s), none of them inside a workspace. There is no package to build and no test to run, but the scope decision recorded a caveat: ${testScope.caveat}.` : `The diff changes ${changed.length} file(s), none of them inside a workspace (nothing the workspaces' tests can feel). There is no package to build and no test to run \u2014 this is a complete answer, not a skipped step.`
12986
13727
  };
12987
13728
  }
12988
- const byDir = new Map(packages.map((p) => [p.dir, p]));
13729
+ const byDir = new Map(scopeGraph.map((p) => [p.dir, p]));
12989
13730
  const unmapped = affected.filter((d) => d !== "." && !byDir.has(d));
12990
13731
  if (unmapped.length > 0) {
12991
13732
  return unsupportedReport(
@@ -13004,15 +13745,15 @@ function runBuildTest(args) {
13004
13745
  timedOut: [],
13005
13746
  note: ""
13006
13747
  };
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"))) {
13748
+ const npmLock = existsSync7(join15(root, "package-lock.json"));
13749
+ const installComplete = /* @__PURE__ */ __name(() => existsSync7(join15(root, "node_modules", ".package-lock.json")), "installComplete");
13750
+ if (args.install && !npmLock && !existsSync7(join15(root, "node_modules"))) {
13010
13751
  const altLock = [
13011
13752
  ["yarn.lock", "yarn install --frozen-lockfile"],
13012
13753
  ["pnpm-lock.yaml", "pnpm install --frozen-lockfile"],
13013
13754
  ["bun.lockb", "bun install --frozen-lockfile"],
13014
13755
  ["bun.lock", "bun install --frozen-lockfile"]
13015
- ].find(([f]) => existsSync7(join14(root, f)));
13756
+ ].find(([f]) => existsSync7(join15(root, f)));
13016
13757
  return unsupportedReport(
13017
13758
  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."
13018
13759
  );
@@ -13025,12 +13766,21 @@ function runBuildTest(args) {
13025
13766
  results.note = `Insufficient disk space (${gib(free)}G free, need ~${gib(INSTALL_MIN_FREE_BYTES)}G): skipped \`${installCmd}\`, so nothing could be built or tested. This is an environment issue, not a code finding \u2014 report it as informational.`;
13026
13767
  return results;
13027
13768
  }
13028
- const install = exec(installCmd, root, perCommandMs);
13769
+ if (remainingMs() < BUDGET_MIN_ATTEMPT_MS) {
13770
+ results.ok = false;
13771
+ results.note = `The whole-call budget was spent before the install could start (${args.budget != null ? `--budget ${args.budget}s` : "default budget"}), so nothing could be built or tested. This is an infrastructure result, not a defect in the diff \u2014 report it as informational.`;
13772
+ return results;
13773
+ }
13774
+ const install = exec(
13775
+ installCmd,
13776
+ root,
13777
+ Math.min(perCommandMs, remainingMs())
13778
+ );
13029
13779
  results.install = install;
13030
13780
  if (install.timedOut) results.timedOut.push(install.command);
13031
13781
  if (install.timedOut) {
13032
13782
  try {
13033
- rmSync5(join14(root, "node_modules"), {
13783
+ rmSync5(join15(root, "node_modules"), {
13034
13784
  recursive: true,
13035
13785
  force: true,
13036
13786
  maxRetries: 3
@@ -13040,7 +13790,7 @@ function runBuildTest(args) {
13040
13790
  }
13041
13791
  if (install.timedOut || !installComplete()) {
13042
13792
  results.ok = false;
13043
- results.note = install.timedOut ? `\`${install.command}\` ran out of time (${args.timeout}s) and left an incomplete \`node_modules\`, so nothing could be built or tested against it. This is an infrastructure result, not a defect in the diff \u2014 report it as informational.` : "The install failed and left no usable `node_modules`, so nothing could be built or tested. This is an environment failure, not a defect in the diff \u2014 report it as informational.";
13793
+ results.note = install.timedOut ? `\`${install.command}\` ran out of time (${deadlineSecs(install)}s) and left an incomplete \`node_modules\`, so nothing could be built or tested against it. This is an infrastructure result, not a defect in the diff \u2014 report it as informational.` : "The install failed and left no usable `node_modules`, so nothing could be built or tested. This is an environment failure, not a defect in the diff \u2014 report it as informational.";
13044
13794
  return results;
13045
13795
  }
13046
13796
  }
@@ -13051,9 +13801,12 @@ function runBuildTest(args) {
13051
13801
  return results;
13052
13802
  }
13053
13803
  const alsoBuild = [];
13054
- let set = buildSetFor(affected, packages);
13804
+ let set = buildSetFor(affected, scopeGraph);
13055
13805
  const built = /* @__PURE__ */ new Set();
13056
13806
  const widened = /* @__PURE__ */ new Set();
13807
+ const rootBuildRuns = !!rootPkg?.scripts.includes("build") && !scriptFansOut(rootPkg.scriptsText["build"]);
13808
+ const rootBuildSkipped = !singleRoot && !rootBuildRuns;
13809
+ const notBuilt = [];
13057
13810
  for (let attempt = 0; attempt <= 3; attempt++) {
13058
13811
  let failure = null;
13059
13812
  for (const dir of set) {
@@ -13063,7 +13816,23 @@ function runBuildTest(args) {
13063
13816
  built.add(dir);
13064
13817
  continue;
13065
13818
  }
13066
- const r = exec(buildCommand(dir), root, perCommandMs);
13819
+ if (dir === "." && rootBuildSkipped) {
13820
+ built.add(dir);
13821
+ continue;
13822
+ }
13823
+ if (remainingMs() < BUDGET_MIN_ATTEMPT_MS) {
13824
+ notBuilt.push(
13825
+ ...set.filter(
13826
+ (d) => !built.has(d) && byDir.get(d)?.scripts.includes("build")
13827
+ )
13828
+ );
13829
+ break;
13830
+ }
13831
+ const r = exec(
13832
+ buildCommand(dir),
13833
+ root,
13834
+ Math.min(perCommandMs, remainingMs())
13835
+ );
13067
13836
  results.build.push(r);
13068
13837
  if (r.timedOut) results.timedOut.push(r.command);
13069
13838
  if (r.exitCode !== 0) {
@@ -13079,8 +13848,8 @@ function runBuildTest(args) {
13079
13848
  });
13080
13849
  if (missing.length === 0 || failure.timedOut || attempt === 3) {
13081
13850
  results.ok = false;
13082
- results.note = failure.timedOut ? `\`${failure.command}\` ran out of time (${args.timeout}s). That is an infrastructure result, not a defect in the diff \u2014 report it as informational.` : `\`${failure.command}\` failed. Correlate the errors below with the diff: a compile error in a file the PR changed is a Critical; one in a file it did not touch is a pre-existing failure, and belongs in the terminal, not on the PR.`;
13083
- results.buildSet = set;
13851
+ results.note = failure.timedOut ? `\`${failure.command}\` ran out of time (${deadlineSecs(failure)}s). That is an infrastructure result, not a defect in the diff \u2014 report it as informational.` : `\`${failure.command}\` failed. Correlate the errors below with the diff: a compile error in a file the PR changed is a Critical; one in a file it did not touch is a pre-existing failure, and belongs in the terminal, not on the PR.`;
13852
+ results.buildSet = (rootBuildSkipped ? set.filter((d) => d !== ".") : set).filter((d) => !notBuilt.includes(d));
13084
13853
  results.widenedWith = [...widened];
13085
13854
  return results;
13086
13855
  }
@@ -13090,31 +13859,85 @@ function runBuildTest(args) {
13090
13859
  const dir = packages.find((p) => p.name === name)?.dir;
13091
13860
  if (dir) alsoBuild.push(dir);
13092
13861
  }
13093
- set = buildSetFor(affected, packages, alsoBuild);
13862
+ set = buildSetFor(affected, scopeGraph, alsoBuild);
13094
13863
  }
13095
- results.buildSet = set;
13864
+ results.buildSet = (rootBuildSkipped ? set.filter((d) => d !== ".") : set).filter((d) => !notBuilt.includes(d));
13096
13865
  results.widenedWith = [...widened];
13097
- for (const dir of args.buildOnly ? [] : affected) {
13098
- const pkg = byDir.get(dir);
13099
- if (!pkg?.scripts.includes("test")) continue;
13100
- const r = exec(testCommand(dir), root, perCommandMs);
13866
+ if (notBuilt.length > 0) results.notBuilt = [...notBuilt].sort();
13867
+ const rootHasTest = !!rootPkg?.scripts.includes("test");
13868
+ const testDirs = args.buildOnly ? [] : !testScope ? affected : testScope.workspaces;
13869
+ const runnable = /* @__PURE__ */ __name((dir) => dir === "." ? rootHasTest : !!byDir.get(dir)?.scripts.includes("test"), "runnable");
13870
+ const affectedSet = new Set(affected);
13871
+ const runnableDirs = [
13872
+ ...testDirs.filter((d) => affectedSet.has(d) && runnable(d)),
13873
+ ...testDirs.filter((d) => !affectedSet.has(d) && runnable(d))
13874
+ ];
13875
+ const untestable = notBuilt.length > 0 ? new Set(reverseDependencyClosure(notBuilt, scopeGraph)) : /* @__PURE__ */ new Set();
13876
+ const notRun = [];
13877
+ for (let i = 0; i < runnableDirs.length; i++) {
13878
+ const dir = runnableDirs[i];
13879
+ if (untestable.has(dir)) {
13880
+ notRun.push(dir);
13881
+ continue;
13882
+ }
13883
+ const remaining = remainingMs();
13884
+ if (remaining < BUDGET_MIN_ATTEMPT_MS) {
13885
+ notRun.push(...runnableDirs.slice(i).filter((d) => !untestable.has(d)));
13886
+ break;
13887
+ }
13888
+ const r = exec(testCommand(dir), root, Math.min(perCommandMs, remaining));
13101
13889
  results.test.push(r);
13102
13890
  if (r.timedOut) results.timedOut.push(r.command);
13103
13891
  if (r.exitCode !== 0) results.ok = false;
13104
13892
  }
13893
+ notRun.sort();
13894
+ const partialNote = [
13895
+ notBuilt.length > 0 ? `the build phase reached the whole-call budget \u2014 not built: ` + notBuilt.join(", ") : "",
13896
+ notRun.length > 0 ? `the whole-call budget (${Math.round(callBudgetMs / 1e3)}s) was spent with ${notRun.length} suite(s) still to run \u2014 not run: ` + notRun.join(", ") : ""
13897
+ ].filter(Boolean).join("; ") || void 0;
13898
+ if (testScope && partialNote) {
13899
+ const ran = testScope.workspaces.filter((d) => !notRun.includes(d));
13900
+ testScope = {
13901
+ workspaces: ran,
13902
+ ...notRun.length > 0 ? { notRun } : {},
13903
+ caveat: testScope.caveat ? `${testScope.caveat}; ${partialNote}` : partialNote
13904
+ };
13905
+ }
13906
+ if (testScope) results.testScope = testScope;
13105
13907
  if (!results.note) {
13106
13908
  const failed = [...results.build, ...results.test].filter(
13107
13909
  (r) => r.exitCode !== 0
13108
13910
  );
13109
13911
  const realFailures = failed.filter((r) => !r.timedOut);
13110
13912
  if (results.ok) {
13111
- results.note = `Built ${results.buildSet.length} of ${packages.length} workspaces (the ${affected.length} the diff changes, plus what they compile against${widened.size ? `, plus ${[...widened].join(", ")} the compiler asked for` : ""})${args.buildOnly ? ". Tests were not run (build-only)." : " and ran the tests of the changed ones. Everything passed."}`;
13913
+ let testsClause;
13914
+ if (args.buildOnly) {
13915
+ testsClause = ". Tests were not run (build-only).";
13916
+ } else if (!testScope) {
13917
+ testsClause = results.test.length === 0 ? ", but the package defines no test script, so no tests ran." : " and ran the tests of the changed ones. Everything passed.";
13918
+ } else if (testScope.workspaces.length === 0) {
13919
+ testsClause = testScope.notRun?.length ? ", but the whole-call budget was spent before any suite could run." : ", but no workspace in scope defines a test script, so no tests ran.";
13920
+ } else {
13921
+ testsClause = ` and ran the tests scoped to ${testScope.workspaces.join(", ")} \u2014 the changed workspaces and every workspace declared to depend on them that defines a test script. Everything passed.`;
13922
+ }
13923
+ if (testScope?.caveat) testsClause += ` Caveat: ${testScope.caveat}.`;
13924
+ const builtWorkspaces = results.buildSet.filter(
13925
+ (d) => singleRoot || d !== "."
13926
+ ).length;
13927
+ const rootSuffix = !singleRoot && results.buildSet.includes(".") && !rootBuildSkipped ? " (plus the root package)" : "";
13928
+ results.note = `Built ${builtWorkspaces} of ${packages.length} workspaces${rootSuffix} (the ${affected.length} the diff changes, plus what they compile against${widened.size ? `, plus ${[...widened].join(", ")} the compiler asked for` : ""})${testsClause}`;
13112
13929
  } else if (realFailures.length === 0) {
13113
- results.note = `${failed.length} command(s) ran out of time (${args.timeout}s). A timeout is an infrastructure result, not a defect in the diff \u2014 report it as informational.`;
13930
+ results.note = `${failed.length} command(s) ran out of time (${deadlineSecs(failed[0])}s). A timeout is an infrastructure result, not a defect in the diff \u2014 report it as informational.`;
13114
13931
  } else {
13115
13932
  results.note = `${realFailures.length} command(s) failed. Correlate each error with the diff: a failure in a file the PR changed is a Critical; one in a file it did not touch is pre-existing.` + (failed.length > realFailures.length ? " (Commands that timed out are infrastructure, not findings.)" : "");
13116
13933
  }
13117
13934
  }
13935
+ if (results.testScope?.caveat && !results.note.includes("Caveat:")) {
13936
+ results.note += ` Caveat: ${results.testScope.caveat}.`;
13937
+ }
13938
+ if (partialNote && !results.testScope) {
13939
+ results.note = results.note ? `${results.note} ${partialNote}.` : partialNote;
13940
+ }
13118
13941
  if (results.install && results.install.exitCode !== 0) {
13119
13942
  results.note = `\`${results.install.command}\` exited ${results.install.exitCode} but left a usable \`node_modules\`, so the build and test below ran anyway and their results stand. The install failure is an environment/infrastructure result \u2014 report it as informational, never as a Critical, and never against this PR. ` + results.note;
13120
13943
  }
@@ -13123,7 +13946,7 @@ function runBuildTest(args) {
13123
13946
  __name(runBuildTest, "runBuildTest");
13124
13947
  var buildTestCommand = {
13125
13948
  command: "build-test",
13126
- describe: "Build and test the workspaces the diff changes (and what they compile against), with a deadline the commands can actually meet",
13949
+ describe: "Build the workspaces the diff changes (and what they compile against), test those plus their dependents, with a deadline the commands can actually meet",
13127
13950
  builder: /* @__PURE__ */ __name((yargs) => yargs.option("plan", {
13128
13951
  type: "string",
13129
13952
  demandOption: true,
@@ -13138,7 +13961,10 @@ var buildTestCommand = {
13138
13961
  }).option("timeout", {
13139
13962
  type: "number",
13140
13963
  default: 300,
13141
- describe: "Per-command deadline in seconds. Kept strictly below the 600s (600000ms) tool timeout the agent's brief welds onto the whole call, so a single hung command's own deadline fires \u2014 and build-test reports it as data \u2014 before the outer shell kill would discard the report. (A giant PR whose commands sum past the tool ceiling is a separate, acknowledged follow-up.)"
13964
+ describe: "Per-command deadline in seconds. Kept strictly below the 600s (600000ms) tool timeout the agent's brief welds onto the whole call, so a single hung command's own deadline fires \u2014 and build-test reports it as data \u2014 before the outer shell kill would discard the report. Commands that would SUM past the whole call are stopped and disclosed instead \u2014 see --budget."
13965
+ }).option("budget", {
13966
+ type: "number",
13967
+ describe: "Whole-call wall-clock budget in seconds, measured from the top of the call \u2014 install and build time count against it (default: 2\xD7 --timeout minus 30s of headroom for process startup and the report write). Each suite is attempted with whatever of the budget remains \u2014 a suite killed at the boundary is a timeout, reported as infrastructure \u2014 and only suites never attempted are named notRun. A partial report survives where the outer shell kill would discard the whole one."
13142
13968
  }).option("install", {
13143
13969
  type: "boolean",
13144
13970
  default: true,
@@ -13153,7 +13979,7 @@ var buildTestCommand = {
13153
13979
  try {
13154
13980
  const report = runBuildTest(args);
13155
13981
  if (args.out) {
13156
- writeFileSync17(args.out, JSON.stringify(report, null, 2));
13982
+ writeFileSync18(args.out, JSON.stringify(report, null, 2));
13157
13983
  }
13158
13984
  writeStdoutLine(JSON.stringify(report, null, 2));
13159
13985
  } catch (err) {
@@ -13168,13 +13994,13 @@ init_esbuild_shims();
13168
13994
  import { spawnSync as spawnSync4 } from "node:child_process";
13169
13995
  import {
13170
13996
  existsSync as existsSync8,
13171
- mkdirSync as mkdirSync17,
13172
- readFileSync as readFileSync20,
13997
+ mkdirSync as mkdirSync18,
13998
+ readFileSync as readFileSync21,
13173
13999
  rmSync as rmSync7,
13174
- statSync as statSync6,
13175
- writeFileSync as writeFileSync18
14000
+ statSync as statSync9,
14001
+ writeFileSync as writeFileSync19
13176
14002
  } from "node:fs";
13177
- import { dirname as dirname15, join as join15, resolve as resolve15 } from "node:path";
14003
+ import { dirname as dirname15, join as join16, resolve as resolve15 } from "node:path";
13178
14004
 
13179
14005
  // packages/cli/src/commands/review/lib/worktree.ts
13180
14006
  init_esbuild_shims();
@@ -13221,7 +14047,7 @@ function runBaseTree(args) {
13221
14047
  }), "unavailable");
13222
14048
  let plan;
13223
14049
  try {
13224
- plan = JSON.parse(readFileSync20(args.plan, "utf8"));
14050
+ plan = JSON.parse(readFileSync21(args.plan, "utf8"));
13225
14051
  } catch (err) {
13226
14052
  return unavailable(
13227
14053
  `cannot read the plan ${args.plan}: ${err.message}`
@@ -13243,10 +14069,10 @@ function runBaseTree(args) {
13243
14069
  return unavailable(`the review worktree ${worktree} does not exist`);
13244
14070
  }
13245
14071
  const tree = baseWorktreePath(worktree);
13246
- const marker = /* @__PURE__ */ __name(() => join15(tree, ".qwen-review-base-ok"), "marker");
13247
- const failedMarker = /* @__PURE__ */ __name(() => join15(tree, ".qwen-review-base-failed"), "failedMarker");
14072
+ const marker = /* @__PURE__ */ __name(() => join16(tree, ".qwen-review-base-ok"), "marker");
14073
+ const failedMarker = /* @__PURE__ */ __name(() => join16(tree, ".qwen-review-base-failed"), "failedMarker");
13248
14074
  try {
13249
- if (existsSync8(tree) && readFileSync20(marker(), "utf8").trim() === baseSha && gitOut(tree, "rev-parse", "HEAD") === baseSha) {
14075
+ if (existsSync8(tree) && readFileSync21(marker(), "utf8").trim() === baseSha && gitOut(tree, "rev-parse", "HEAD") === baseSha) {
13250
14076
  return {
13251
14077
  available: true,
13252
14078
  path: tree,
@@ -13258,7 +14084,7 @@ function runBaseTree(args) {
13258
14084
  } catch {
13259
14085
  }
13260
14086
  try {
13261
- if (existsSync8(tree) && readFileSync20(failedMarker(), "utf8").trim() === baseSha) {
14087
+ if (existsSync8(tree) && readFileSync21(failedMarker(), "utf8").trim() === baseSha) {
13262
14088
  return {
13263
14089
  available: false,
13264
14090
  path: tree,
@@ -13271,14 +14097,14 @@ function runBaseTree(args) {
13271
14097
  }
13272
14098
  const lock = `${tree}.lock`;
13273
14099
  try {
13274
- const age = Date.now() - statSync6(lock).mtimeMs;
14100
+ const age = Date.now() - statSync9(lock).mtimeMs;
13275
14101
  if (age > 30 * 60 * 1e3) {
13276
14102
  rmSync7(lock, { recursive: true, force: true });
13277
14103
  }
13278
14104
  } catch {
13279
14105
  }
13280
14106
  try {
13281
- mkdirSync17(lock);
14107
+ mkdirSync18(lock);
13282
14108
  } catch (err) {
13283
14109
  if (err.code !== "EEXIST") {
13284
14110
  return unavailable(
@@ -13314,9 +14140,18 @@ function runBaseTree(args) {
13314
14140
  // tree to run against.
13315
14141
  buildOnly: true
13316
14142
  });
14143
+ if ((build.notBuilt?.length ?? 0) > 0) {
14144
+ return {
14145
+ available: false,
14146
+ path: tree,
14147
+ baseSha: baseSha2,
14148
+ build,
14149
+ note: `the base tree build at ${baseSha2.slice(0, 9)} was cut short by the whole-call budget (${build.notBuilt.join(", ")} not built), so a rerun against it would manufacture pre-existing failures; an A/B is not available for this review (this is an infrastructure result, never a finding against the PR)`
14150
+ };
14151
+ }
13317
14152
  if (!build.ok || build.toolchain !== "npm" || build.build.length === 0) {
13318
14153
  try {
13319
- writeFileSync18(failedMarker(), `${baseSha2}
14154
+ writeFileSync19(failedMarker(), `${baseSha2}
13320
14155
  `);
13321
14156
  } catch {
13322
14157
  }
@@ -13329,7 +14164,7 @@ function runBaseTree(args) {
13329
14164
  };
13330
14165
  }
13331
14166
  try {
13332
- writeFileSync18(marker(), `${baseSha2}
14167
+ writeFileSync19(marker(), `${baseSha2}
13333
14168
  `);
13334
14169
  } catch {
13335
14170
  }
@@ -13369,8 +14204,8 @@ var baseTreeCommand = {
13369
14204
  try {
13370
14205
  const report = runBaseTree(args);
13371
14206
  if (args.out) {
13372
- mkdirSync17(dirname15(resolve15(args.out)), { recursive: true });
13373
- writeFileSync18(resolve15(args.out), JSON.stringify(report, null, 2));
14207
+ mkdirSync18(dirname15(resolve15(args.out)), { recursive: true });
14208
+ writeFileSync19(resolve15(args.out), JSON.stringify(report, null, 2));
13374
14209
  }
13375
14210
  writeStdoutLine(JSON.stringify(report, null, 2));
13376
14211
  writeStderrLine(`base-tree: ${report.note}`);
@@ -13384,7 +14219,7 @@ var baseTreeCommand = {
13384
14219
  // packages/cli/src/commands/review/test-delta.ts
13385
14220
  init_esbuild_shims();
13386
14221
  import { spawnSync as spawnSync5 } from "node:child_process";
13387
- import { existsSync as existsSync9, mkdirSync as mkdirSync18, readFileSync as readFileSync21, writeFileSync as writeFileSync19 } from "node:fs";
14222
+ import { existsSync as existsSync9, mkdirSync as mkdirSync19, readFileSync as readFileSync22, writeFileSync as writeFileSync20 } from "node:fs";
13388
14223
  import { dirname as dirname16, resolve as resolve16 } from "node:path";
13389
14224
  var ANSI_SGR_RE2 = /\x1b\[[0-9;]*m/g;
13390
14225
  var RERUNNABLE_COMMAND_RE = /^npm test(?: --workspace="[\w@./-]+")?$/;
@@ -13458,7 +14293,7 @@ function runTestDelta(args) {
13458
14293
  }), "empty");
13459
14294
  let report;
13460
14295
  try {
13461
- report = JSON.parse(readFileSync21(args.report, "utf8"));
14296
+ report = JSON.parse(readFileSync22(args.report, "utf8"));
13462
14297
  } catch (err) {
13463
14298
  return empty(
13464
14299
  `cannot read the build-test report ${args.report}: ${err.message}`
@@ -13600,8 +14435,8 @@ var testDeltaCommand = {
13600
14435
  const args = argv;
13601
14436
  const report = runTestDelta(args);
13602
14437
  if (args.out) {
13603
- mkdirSync18(dirname16(resolve16(args.out)), { recursive: true });
13604
- writeFileSync19(resolve16(args.out), JSON.stringify(report, null, 2));
14438
+ mkdirSync19(dirname16(resolve16(args.out)), { recursive: true });
14439
+ writeFileSync20(resolve16(args.out), JSON.stringify(report, null, 2));
13605
14440
  }
13606
14441
  writeStdoutLine(JSON.stringify(report, null, 2));
13607
14442
  writeStderrLine(`test-delta: ${report.note}`);
@@ -13612,14 +14447,14 @@ var testDeltaCommand = {
13612
14447
  init_esbuild_shims();
13613
14448
  import { spawnSync as spawnSync6 } from "node:child_process";
13614
14449
  import {
13615
- mkdirSync as mkdirSync19,
13616
- writeFileSync as writeFileSync20,
13617
- readFileSync as readFileSync22,
14450
+ mkdirSync as mkdirSync20,
14451
+ writeFileSync as writeFileSync21,
14452
+ readFileSync as readFileSync23,
13618
14453
  existsSync as existsSync10,
13619
14454
  rmSync as rmSync8,
13620
- statSync as statSync7
14455
+ statSync as statSync10
13621
14456
  } from "node:fs";
13622
- import { dirname as dirname17, join as join16, resolve as resolve17 } from "node:path";
14457
+ import { dirname as dirname17, join as join17, resolve as resolve17 } from "node:path";
13623
14458
  import { tmpdir as tmpdir2 } from "node:os";
13624
14459
  var SERVER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
13625
14460
  function shellQuote(v) {
@@ -13631,7 +14466,7 @@ var LOG_MAX_BYTES = 8 * 1024 * 1024;
13631
14466
  var POLL_MS = 250;
13632
14467
  function logBytes(p) {
13633
14468
  try {
13634
- return statSync7(p).size;
14469
+ return statSync10(p).size;
13635
14470
  } catch {
13636
14471
  return 0;
13637
14472
  }
@@ -13737,13 +14572,13 @@ function runDrive(args) {
13737
14572
  waitMs(POLL_MS);
13738
14573
  }
13739
14574
  }
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");
14575
+ const dir = join17(tmpdir2(), `qwen-review-drive-${server}`);
14576
+ mkdirSync20(dir, { recursive: true });
14577
+ const scriptPath = join17(dir, "drive.sh");
14578
+ const logPath = args.logPath ?? join17(dir, "drive.log");
14579
+ const sentinelPath = join17(dir, "drive.rc");
13745
14580
  rmSync8(sentinelPath, { force: true });
13746
- writeFileSync20(scriptPath, wrapScript(args.script, sentinelPath), "utf8");
14581
+ writeFileSync21(scriptPath, wrapScript(args.script, sentinelPath), "utf8");
13747
14582
  const droveFrom = Date.now();
13748
14583
  let output = "";
13749
14584
  let exitCode = null;
@@ -13773,8 +14608,8 @@ function runDrive(args) {
13773
14608
  }
13774
14609
  const deadline = droveFrom + args.timeout * 1e3;
13775
14610
  for (; ; ) {
13776
- output = existsSync10(logPath) ? readFileSync22(logPath, "utf8") : "";
13777
- exitCode = existsSync10(sentinelPath) ? sentinelExitCode(readFileSync22(sentinelPath, "utf8")) : null;
14611
+ output = existsSync10(logPath) ? readFileSync23(logPath, "utf8") : "";
14612
+ exitCode = existsSync10(sentinelPath) ? sentinelExitCode(readFileSync23(sentinelPath, "utf8")) : null;
13778
14613
  if (exitCode !== null) {
13779
14614
  outcome = "completed";
13780
14615
  break;
@@ -13841,8 +14676,8 @@ var driveCommand = {
13841
14676
  const args = argv;
13842
14677
  const report = runDrive(args);
13843
14678
  if (args.out) {
13844
- mkdirSync19(dirname17(resolve17(args.out)), { recursive: true });
13845
- writeFileSync20(resolve17(args.out), JSON.stringify(report, null, 2));
14679
+ mkdirSync20(dirname17(resolve17(args.out)), { recursive: true });
14680
+ writeFileSync21(resolve17(args.out), JSON.stringify(report, null, 2));
13846
14681
  }
13847
14682
  writeStdoutLine(JSON.stringify(report, null, 2));
13848
14683
  writeStderrLine(`drive: ${report.note}`);
@@ -13859,7 +14694,7 @@ init_esbuild_shims();
13859
14694
  import {
13860
14695
  createServer
13861
14696
  } from "node:http";
13862
- import { appendFileSync, mkdirSync as mkdirSync20, writeFileSync as writeFileSync21 } from "node:fs";
14697
+ import { appendFileSync, mkdirSync as mkdirSync21, writeFileSync as writeFileSync22 } from "node:fs";
13863
14698
  import { dirname as dirname18, resolve as resolve18 } from "node:path";
13864
14699
  import { pathToFileURL } from "node:url";
13865
14700
  function messagesText(body) {
@@ -14116,8 +14951,8 @@ async function startMockProvider(args, respondOverride) {
14116
14951
  respond = fn;
14117
14952
  }
14118
14953
  const logPath = resolve18(args.log);
14119
- mkdirSync20(dirname18(logPath), { recursive: true });
14120
- writeFileSync21(logPath, "");
14954
+ mkdirSync21(dirname18(logPath), { recursive: true });
14955
+ writeFileSync22(logPath, "");
14121
14956
  let n = 0;
14122
14957
  const record = /* @__PURE__ */ __name((entry) => appendFileSync(logPath, `${JSON.stringify(entry)}
14123
14958
  `), "record");
@@ -14279,8 +15114,8 @@ var mockProviderCommand = {
14279
15114
  const args = argv;
14280
15115
  const { report, close } = await startMockProvider(args);
14281
15116
  if (args.out) {
14282
- mkdirSync20(dirname18(resolve18(args.out)), { recursive: true });
14283
- writeFileSync21(resolve18(args.out), JSON.stringify(report, null, 2));
15117
+ mkdirSync21(dirname18(resolve18(args.out)), { recursive: true });
15118
+ writeFileSync22(resolve18(args.out), JSON.stringify(report, null, 2));
14284
15119
  }
14285
15120
  writeStdoutLine(JSON.stringify(report, null, 2));
14286
15121
  writeStderrLine(`mock-provider: ${report.note}`);
@@ -14298,7 +15133,7 @@ var mockProviderCommand = {
14298
15133
  // packages/cli/src/commands/review/extract-step.ts
14299
15134
  init_esbuild_shims();
14300
15135
  var import_yaml = __toESM(require_dist(), 1);
14301
- import { mkdirSync as mkdirSync21, readFileSync as readFileSync23, writeFileSync as writeFileSync22, chmodSync } from "node:fs";
15136
+ import { mkdirSync as mkdirSync22, readFileSync as readFileSync24, writeFileSync as writeFileSync23, chmodSync } from "node:fs";
14302
15137
  import { dirname as dirname19, resolve as resolve19 } from "node:path";
14303
15138
  function expressionsOf(...texts) {
14304
15139
  const seen = /* @__PURE__ */ new Set();
@@ -14546,7 +15381,7 @@ function runExtractStep(args) {
14546
15381
  const wfPath = resolve19(args.workflow);
14547
15382
  let text;
14548
15383
  try {
14549
- text = readFileSync23(wfPath, "utf8");
15384
+ text = readFileSync24(wfPath, "utf8");
14550
15385
  } catch (err) {
14551
15386
  throw new Error(
14552
15387
  `extract-step: cannot read ${args.workflow}: ${err.message}`
@@ -14613,7 +15448,7 @@ function runExtractStep(args) {
14613
15448
  const script = step.run;
14614
15449
  const stepLabel = String(step.name ?? step.id ?? index);
14615
15450
  const outPath = resolve19(args.out);
14616
- mkdirSync21(dirname19(outPath), { recursive: true });
15451
+ mkdirSync22(dirname19(outPath), { recursive: true });
14617
15452
  const header = [
14618
15453
  `#!/usr/bin/env ${shellCommand}`,
14619
15454
  ...commentLines(
@@ -14637,7 +15472,7 @@ function runExtractStep(args) {
14637
15472
  ),
14638
15473
  ""
14639
15474
  ].join("\n");
14640
- writeFileSync22(outPath, header + script + (script.endsWith("\n") ? "" : "\n"));
15475
+ writeFileSync23(outPath, header + script + (script.endsWith("\n") ? "" : "\n"));
14641
15476
  chmodSync(outPath, 493);
14642
15477
  return {
14643
15478
  workflow: args.workflow,
@@ -14698,7 +15533,7 @@ var extractStepCommand = {
14698
15533
 
14699
15534
  // packages/cli/src/commands/review/submit.ts
14700
15535
  init_esbuild_shims();
14701
- import { mkdirSync as mkdirSync22, readFileSync as readFileSync24 } from "node:fs";
15536
+ import { mkdirSync as mkdirSync23, readFileSync as readFileSync25 } from "node:fs";
14702
15537
 
14703
15538
  // packages/cli/src/commands/review/lib/receipt.ts
14704
15539
  init_esbuild_shims();
@@ -14720,7 +15555,7 @@ __name(parseReceiptIds, "parseReceiptIds");
14720
15555
  var EVENTS = /* @__PURE__ */ new Set(["APPROVE", "REQUEST_CHANGES", "COMMENT"]);
14721
15556
  function readReceiptIds(receiptPath) {
14722
15557
  try {
14723
- return parseReceiptIds(readFileSync24(receiptPath, "utf8"));
15558
+ return parseReceiptIds(readFileSync25(receiptPath, "utf8"));
14724
15559
  } catch {
14725
15560
  return [];
14726
15561
  }
@@ -14730,6 +15565,24 @@ function isDiffLine(n) {
14730
15565
  return typeof n === "number" && Number.isSafeInteger(n) && n > 0;
14731
15566
  }
14732
15567
  __name(isDiffLine, "isDiffLine");
15568
+ function normalizeInlineComments(comments, modelId, cliVersion) {
15569
+ if (typeof modelId !== "string" || modelId.trim() === "") return comments;
15570
+ const footer = reviewFooter(modelId, cliVersion);
15571
+ return comments.map(
15572
+ (comment) => (
15573
+ // An empty body stays empty: this runs BEFORE the consistency check, and
15574
+ // a footer pasted onto '' would hide the emptiness from the refusal that
15575
+ // names it ('has no body — an empty comment').
15576
+ typeof comment.body === "string" && comment.body.trim() !== "" ? {
15577
+ ...comment,
15578
+ body: `${comment.body.replace(REVIEW_FOOTER_RE, "")}
15579
+
15580
+ ${footer}`
15581
+ } : comment
15582
+ )
15583
+ );
15584
+ }
15585
+ __name(normalizeInlineComments, "normalizeInlineComments");
14733
15586
  function authorization(args) {
14734
15587
  return reviewWriteAuthorization({
14735
15588
  userAuthorized: args.userAuthorized,
@@ -14775,6 +15628,18 @@ __name(compose, "compose");
14775
15628
  function structuralProblems(payload) {
14776
15629
  const problems = [];
14777
15630
  if (!payload.commit_id) problems.push("`commit_id` is missing");
15631
+ if (payload.comments !== void 0 && !Array.isArray(payload.comments)) {
15632
+ problems.push(
15633
+ "`comments` is not an array \u2014 it is the list of findings this post carries; any other shape is not a list of findings."
15634
+ );
15635
+ }
15636
+ if (Array.isArray(payload.comments) && payload.comments.some(
15637
+ (c) => c === null || typeof c !== "object"
15638
+ )) {
15639
+ problems.push(
15640
+ "`comments` entries must each be an object \u2014 a finding is a path, a line and a body; any other shape is not a finding."
15641
+ );
15642
+ }
14778
15643
  if (payload.event !== void 0 || payload.body !== void 0) {
14779
15644
  problems.push(
14780
15645
  "the payload carries `event`/`body`. Those are computed here, from `state` and from the comments you attached \u2014 they are not inputs. Remove them. (A run that skipped `compose-review` and typed its own Approve is exactly what this refuses.)"
@@ -14855,7 +15720,7 @@ function runSubmit(args, cliVersion = "unknown") {
14855
15720
  }
14856
15721
  let payload;
14857
15722
  try {
14858
- payload = JSON.parse(readFileSync24(args.review, "utf8"));
15723
+ payload = JSON.parse(readFileSync25(args.review, "utf8"));
14859
15724
  } catch (err) {
14860
15725
  throw new Error(
14861
15726
  `Cannot read review JSON ${args.review}: ${err.message}`
@@ -14880,6 +15745,14 @@ Posting is a public, irreversible write, and this run has no authorisation for o
14880
15745
  ` + structural.map((p) => ` - ${p}`).join("\n")
14881
15746
  );
14882
15747
  }
15748
+ payload = {
15749
+ ...payload,
15750
+ comments: normalizeInlineComments(
15751
+ payload.comments ?? [],
15752
+ payload.state?.modelId,
15753
+ cliVersion
15754
+ )
15755
+ };
14883
15756
  let event2;
14884
15757
  let body;
14885
15758
  let cappedBy;
@@ -14931,7 +15804,7 @@ Posting is a public, irreversible write, and this run has no authorisation for o
14931
15804
  const receiptPath = tmpFile(`pr-${args.pr}`, "submit-receipt.json");
14932
15805
  const priorIds = readReceiptIds(receiptPath);
14933
15806
  const reviewIds = [.../* @__PURE__ */ new Set([...priorIds, reviewId])];
14934
- mkdirSync22(REVIEW_TMP_DIR, { recursive: true });
15807
+ mkdirSync23(REVIEW_TMP_DIR, { recursive: true });
14935
15808
  atomicWriteFileSync(
14936
15809
  receiptPath,
14937
15810
  `${JSON.stringify({ reviewIds, event: event2, postedAt: (/* @__PURE__ */ new Date()).toISOString() })}
@@ -14988,7 +15861,8 @@ var submitCommand = {
14988
15861
  describe: "Check authorisation and payload consistency, then stop."
14989
15862
  }), "builder"),
14990
15863
  handler: /* @__PURE__ */ __name(async (argv) => {
14991
- runSubmit(argv, await getCliVersion());
15864
+ const cliVersion = footerVersion(process.env["QWEN_CODE_STARTUP_VERSION"]) ?? await getCliVersion();
15865
+ runSubmit(argv, cliVersion);
14992
15866
  }, "handler")
14993
15867
  };
14994
15868
 
@@ -14996,18 +15870,18 @@ var submitCommand = {
14996
15870
  init_esbuild_shims();
14997
15871
  import { spawnSync as spawnSync7 } from "node:child_process";
14998
15872
  import {
14999
- mkdirSync as mkdirSync23,
15873
+ mkdirSync as mkdirSync24,
15000
15874
  readdirSync as readdirSync5,
15001
- writeFileSync as writeFileSync23,
15002
- readFileSync as readFileSync25,
15875
+ writeFileSync as writeFileSync24,
15876
+ readFileSync as readFileSync26,
15003
15877
  rmSync as rmSync9,
15004
15878
  lstatSync as lstatSync4,
15005
15879
  existsSync as existsSync11,
15006
- statSync as statSync8,
15880
+ statSync as statSync11,
15007
15881
  symlinkSync
15008
15882
  } from "node:fs";
15009
15883
  import { createRequire } from "node:module";
15010
- import { dirname as dirname20, join as join17, isAbsolute as isAbsolute3, sep as sep3 } from "node:path";
15884
+ import { dirname as dirname20, join as join18, isAbsolute as isAbsolute3, sep as sep3 } from "node:path";
15011
15885
  var FIXTURE_DIR_RE = /(^|\/)(__fixtures__|__mocks__|__snapshots__|fixtures)\//;
15012
15886
  function planTestEfficacy(files, workspaceGlobs) {
15013
15887
  const tests = files.filter((f) => f.kind === "test").map((f) => f.path);
@@ -15204,8 +16078,8 @@ function insideClassBody(codeLines, idx) {
15204
16078
  return false;
15205
16079
  }
15206
16080
  __name(insideClassBody, "insideClassBody");
15207
- function isRemovableStatement(lines2, codeLines, idx) {
15208
- const t2 = (lines2[idx] ?? "").trim();
16081
+ function isRemovableStatement(lines, codeLines, idx) {
16082
+ const t2 = (lines[idx] ?? "").trim();
15209
16083
  if (!(codeLines[idx] ?? "").endsWith(";")) return false;
15210
16084
  if ((codeLines[idx] ?? "").slice(0, -1).includes(";")) return false;
15211
16085
  if (!/^(?:await\s+)?[A-Za-z_$]/.test(t2)) return false;
@@ -15274,19 +16148,19 @@ function selectMutants(files, cap = MAX_MUTANTS) {
15274
16148
  const replRest = [];
15275
16149
  const derailed = [];
15276
16150
  for (const f of files) {
15277
- const lines2 = f.content.split("\n");
16151
+ const lines = f.content.split("\n");
15278
16152
  const { inLiteral, codeLines, endState } = scanFileLines(f.content);
15279
16153
  if (endState !== "code") {
15280
16154
  derailed.push(f.file);
15281
16155
  continue;
15282
16156
  }
15283
16157
  for (const n of [...f.addedLines].sort((a, b) => a - b)) {
15284
- const raw = lines2[n - 1];
16158
+ const raw = lines[n - 1];
15285
16159
  if (raw === void 0) continue;
15286
16160
  const t2 = raw.trim();
15287
16161
  if (inLiteral[n - 1]) continue;
15288
16162
  const code = codeLines[n - 1] ?? "";
15289
- if (SAFETY_VERB_RE.test(code) && isRemovableStatement(lines2, codeLines, n - 1)) {
16163
+ if (SAFETY_VERB_RE.test(code) && isRemovableStatement(lines, codeLines, n - 1)) {
15290
16164
  (f.hasNewTests ? preferred : rest).push({
15291
16165
  file: f.file,
15292
16166
  line: n,
@@ -15530,23 +16404,24 @@ function gitOut2(cwd, ...args) {
15530
16404
  return (r.stdout ?? "").trim();
15531
16405
  }
15532
16406
  __name(gitOut2, "gitOut");
15533
- function findVitestBin(worktree) {
15534
- const req = createRequire(join17(worktree, "noop.js"));
16407
+ function findVitestBin(worktree, resolveModule = createRequire(
16408
+ join18(worktree, "noop.js")
16409
+ ).resolve) {
15535
16410
  let pkgPath;
15536
16411
  try {
15537
- pkgPath = req.resolve("vitest/package.json");
16412
+ pkgPath = resolveModule("vitest/package.json");
15538
16413
  } catch (error) {
15539
16414
  if (error.code === "MODULE_NOT_FOUND") {
15540
16415
  throw new Error(`vitest not found searching up from ${worktree}`);
15541
16416
  }
15542
16417
  throw error;
15543
16418
  }
15544
- const pkg = JSON.parse(readFileSync25(pkgPath, "utf8"));
16419
+ const pkg = JSON.parse(readFileSync26(pkgPath, "utf8"));
15545
16420
  const bin = pkg.bin?.vitest;
15546
16421
  if (!bin) {
15547
16422
  throw new Error(`vitest package at ${pkgPath} declares no "vitest" bin`);
15548
16423
  }
15549
- return join17(dirname20(pkgPath), bin);
16424
+ return join18(dirname20(pkgPath), bin);
15550
16425
  }
15551
16426
  __name(findVitestBin, "findVitestBin");
15552
16427
  function gitCapture(cwd, ...args) {
@@ -15572,7 +16447,7 @@ function safeRmWithin(worktree, relPath) {
15572
16447
  const parts = relPath.split(/[/\\]+/).filter((s) => s && s !== ".");
15573
16448
  let cur = worktree;
15574
16449
  for (let i = 0; i < parts.length; i++) {
15575
- cur = join17(cur, parts[i]);
16450
+ cur = join18(cur, parts[i]);
15576
16451
  let st;
15577
16452
  try {
15578
16453
  st = lstatSync4(cur);
@@ -15597,15 +16472,15 @@ __name(probeCleanupFailureDetail, "probeCleanupFailureDetail");
15597
16472
  function exposeDependencies(probeTree, dependencyRoot) {
15598
16473
  const done = { linked: 0, failed: 0 };
15599
16474
  if (probeTree === dependencyRoot) return done;
15600
- const source = join17(dependencyRoot, "node_modules");
15601
- const target = join17(probeTree, "node_modules");
16475
+ const source = join18(dependencyRoot, "node_modules");
16476
+ const target = join18(probeTree, "node_modules");
15602
16477
  if (!existsSync11(source) || existsSync11(target)) return done;
15603
- mkdirSync23(target);
16478
+ mkdirSync24(target);
15604
16479
  const linkType = process.platform === "win32" ? "junction" : "dir";
15605
16480
  for (const entry of readdirSync5(source, { withFileTypes: true })) {
15606
16481
  if (entry.name === ".vite" || entry.name === ".vite-temp") continue;
15607
- const sourceEntry = join17(source, entry.name);
15608
- const targetEntry = join17(target, entry.name);
16482
+ const sourceEntry = join18(source, entry.name);
16483
+ const targetEntry = join18(target, entry.name);
15609
16484
  let sourceStats;
15610
16485
  try {
15611
16486
  sourceStats = lstatSync4(sourceEntry);
@@ -15615,7 +16490,7 @@ function exposeDependencies(probeTree, dependencyRoot) {
15615
16490
  }
15616
16491
  if (sourceStats.isSymbolicLink()) {
15617
16492
  try {
15618
- if (!statSync8(sourceEntry).isDirectory()) continue;
16493
+ if (!statSync11(sourceEntry).isDirectory()) continue;
15619
16494
  } catch {
15620
16495
  continue;
15621
16496
  }
@@ -15625,7 +16500,7 @@ function exposeDependencies(probeTree, dependencyRoot) {
15625
16500
  if (entry.name.startsWith("@")) {
15626
16501
  let pkgs;
15627
16502
  try {
15628
- mkdirSync23(targetEntry);
16503
+ mkdirSync24(targetEntry);
15629
16504
  pkgs = readdirSync5(sourceEntry);
15630
16505
  } catch {
15631
16506
  done.failed++;
@@ -15633,7 +16508,7 @@ function exposeDependencies(probeTree, dependencyRoot) {
15633
16508
  }
15634
16509
  for (const pkg of pkgs) {
15635
16510
  try {
15636
- symlinkSync(join17(sourceEntry, pkg), join17(targetEntry, pkg), linkType);
16511
+ symlinkSync(join18(sourceEntry, pkg), join18(targetEntry, pkg), linkType);
15637
16512
  done.linked++;
15638
16513
  } catch {
15639
16514
  done.failed++;
@@ -15689,34 +16564,34 @@ function runProbeSuite(probeTree, probes, deadlineAt, now = Date.now, dependency
15689
16564
  }
15690
16565
  __name(runProbeSuite, "runProbeSuite");
15691
16566
  function splitDiffIntoHunks(diffText) {
15692
- const lines2 = diffText.split("\n");
15693
- const first = lines2.findIndex((l) => l.startsWith("@@"));
16567
+ const lines = diffText.split("\n");
16568
+ const first = lines.findIndex((l) => l.startsWith("@@"));
15694
16569
  if (first < 0) return [];
15695
16570
  let headerStart = 0;
15696
16571
  for (let k = first - 1; k >= 0; k--) {
15697
- if (lines2[k].startsWith("diff --git ")) {
16572
+ if (lines[k].startsWith("diff --git ")) {
15698
16573
  headerStart = k;
15699
16574
  break;
15700
16575
  }
15701
16576
  }
15702
- let fileHeader = lines2.slice(headerStart, first);
16577
+ let fileHeader = lines.slice(headerStart, first);
15703
16578
  const out = [];
15704
16579
  let i = first;
15705
- while (i < lines2.length) {
15706
- if (lines2[i].startsWith("diff --git ")) {
16580
+ while (i < lines.length) {
16581
+ if (lines[i].startsWith("diff --git ")) {
15707
16582
  const start = i;
15708
- while (i < lines2.length && !lines2[i].startsWith("@@") && !(i > start && lines2[i].startsWith("diff --git ")))
16583
+ while (i < lines.length && !lines[i].startsWith("@@") && !(i > start && lines[i].startsWith("diff --git ")))
15709
16584
  i++;
15710
- fileHeader = lines2.slice(start, i);
16585
+ fileHeader = lines.slice(start, i);
15711
16586
  continue;
15712
16587
  }
15713
- if (!lines2[i].startsWith("@@")) {
16588
+ if (!lines[i].startsWith("@@")) {
15714
16589
  i++;
15715
16590
  continue;
15716
16591
  }
15717
- const header = lines2[i];
16592
+ const header = lines[i];
15718
16593
  let j = i + 1;
15719
- while (j < lines2.length && !lines2[j].startsWith("@@") && !lines2[j].startsWith("diff --git ")) {
16594
+ while (j < lines.length && !lines[j].startsWith("@@") && !lines[j].startsWith("diff --git ")) {
15720
16595
  j++;
15721
16596
  }
15722
16597
  let startLine = Number(
@@ -15724,18 +16599,18 @@ function splitDiffIntoHunks(diffText) {
15724
16599
  );
15725
16600
  let offset = 0;
15726
16601
  for (let k = i + 1; k < j; k++) {
15727
- if (lines2[k].startsWith("+")) {
16602
+ if (lines[k].startsWith("+")) {
15728
16603
  startLine += offset;
15729
16604
  break;
15730
16605
  }
15731
- if (!lines2[k].startsWith("-") && !lines2[k].startsWith("\\")) offset++;
16606
+ if (!lines[k].startsWith("-") && !lines[k].startsWith("\\")) offset++;
15732
16607
  }
15733
16608
  out.push({
15734
16609
  header: header.trim(),
15735
16610
  startLine,
15736
16611
  // `git apply` requires the trailing newline; a patch that ends mid-line is
15737
16612
  // rejected with `corrupt patch`.
15738
- patch: `${[...fileHeader, ...lines2.slice(i, j)].join("\n").replace(/\n+$/, "")}
16613
+ patch: `${[...fileHeader, ...lines.slice(i, j)].join("\n").replace(/\n+$/, "")}
15739
16614
  `
15740
16615
  });
15741
16616
  i = j;
@@ -15779,10 +16654,10 @@ function newSideLength(header) {
15779
16654
  __name(newSideLength, "newSideLength");
15780
16655
  function runOneHunkProbe(probeTree, hunk, probes, deadlineAt, now = Date.now) {
15781
16656
  const { patch: _patch, ...meta } = hunk;
15782
- const abs = join17(probeTree, hunk.file);
16657
+ const abs = join18(probeTree, hunk.file);
15783
16658
  let original;
15784
16659
  try {
15785
- original = readFileSync25(abs, "utf8");
16660
+ original = readFileSync26(abs, "utf8");
15786
16661
  } catch (e) {
15787
16662
  return {
15788
16663
  ...meta,
@@ -15808,21 +16683,21 @@ function runOneHunkProbe(probeTree, hunk, probes, deadlineAt, now = Date.now) {
15808
16683
  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";
15809
16684
  return { ...meta, verdict, detail };
15810
16685
  } finally {
15811
- mkdirSync23(dirname20(abs), { recursive: true });
15812
- writeFileSync23(abs, original, "utf8");
16686
+ mkdirSync24(dirname20(abs), { recursive: true });
16687
+ writeFileSync24(abs, original, "utf8");
15813
16688
  }
15814
16689
  }
15815
16690
  __name(runOneHunkProbe, "runOneHunkProbe");
15816
16691
  function runControlMutant(probeTree, probeFile, deadlineAt, now = Date.now) {
15817
- const abs = join17(probeTree, probeFile);
16692
+ const abs = join18(probeTree, probeFile);
15818
16693
  let original;
15819
16694
  try {
15820
- original = readFileSync25(abs, "utf8");
16695
+ original = readFileSync26(abs, "utf8");
15821
16696
  } catch {
15822
16697
  return null;
15823
16698
  }
15824
16699
  try {
15825
- writeFileSync23(
16700
+ writeFileSync24(
15826
16701
  abs,
15827
16702
  `${original}
15828
16703
  ;import { it as __qcIt, expect as __qcExpect } from 'vitest';
@@ -15835,15 +16710,15 @@ __qcIt('QWEN-REVIEW-POSITIVE-CONTROL', () => {
15835
16710
  const { perFile } = runProbeSuite(probeTree, [probeFile], deadlineAt, now);
15836
16711
  return perFile.some((r) => r.verdict === "gated");
15837
16712
  } finally {
15838
- writeFileSync23(abs, original, "utf8");
16713
+ writeFileSync24(abs, original, "utf8");
15839
16714
  }
15840
16715
  }
15841
16716
  __name(runControlMutant, "runControlMutant");
15842
16717
  function runOneMutant(probeTree, mutant, probes, deadlineAt, now = Date.now, dependencyRoot = probeTree) {
15843
- const abs = join17(probeTree, mutant.file);
15844
- const original = readFileSync25(abs, "utf8");
15845
- const lines2 = original.split("\n");
15846
- if ((lines2[mutant.line - 1] ?? "").trim() !== mutant.statement) {
16718
+ const abs = join18(probeTree, mutant.file);
16719
+ const original = readFileSync26(abs, "utf8");
16720
+ const lines = original.split("\n");
16721
+ if ((lines[mutant.line - 1] ?? "").trim() !== mutant.statement) {
15847
16722
  return {
15848
16723
  ...mutant,
15849
16724
  verdict: "inconclusive",
@@ -15852,12 +16727,12 @@ function runOneMutant(probeTree, mutant, probes, deadlineAt, now = Date.now, dep
15852
16727
  }
15853
16728
  const what = mutant.operator === "coalesce" ? "with its `?? fallback` dropped" : mutant.operator === "guard-true" ? "with its guard condition replaced by `true`" : mutant.operator === "term-drop" ? "with its `+ CONSTANT` term dropped" : "deleted";
15854
16729
  if (mutant.mutated !== void 0) {
15855
- lines2[mutant.line - 1] = mutant.mutated;
16730
+ lines[mutant.line - 1] = mutant.mutated;
15856
16731
  } else {
15857
- lines2.splice(mutant.line - 1, 1);
16732
+ lines.splice(mutant.line - 1, 1);
15858
16733
  }
15859
16734
  try {
15860
- writeFileSync23(abs, lines2.join("\n"), "utf8");
16735
+ writeFileSync24(abs, lines.join("\n"), "utf8");
15861
16736
  const { perFile } = runProbeSuite(
15862
16737
  probeTree,
15863
16738
  probes,
@@ -15869,7 +16744,7 @@ function runOneMutant(probeTree, mutant, probes, deadlineAt, now = Date.now, dep
15869
16744
  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";
15870
16745
  return { ...mutant, verdict, detail };
15871
16746
  } finally {
15872
- writeFileSync23(abs, original, "utf8");
16747
+ writeFileSync24(abs, original, "utf8");
15873
16748
  }
15874
16749
  }
15875
16750
  __name(runOneMutant, "runOneMutant");
@@ -15908,9 +16783,9 @@ async function runTestEfficacy(args) {
15908
16783
  const now = args.now ?? Date.now;
15909
16784
  const startedAt = now();
15910
16785
  const { report, worktree, base, out } = args;
15911
- const plan = JSON.parse(readFileSync25(report, "utf8"));
16786
+ const plan = JSON.parse(readFileSync26(report, "utf8"));
15912
16787
  const rootPkg = JSON.parse(
15913
- readFileSync25(`${worktree}/package.json`, "utf8")
16788
+ readFileSync26(`${worktree}/package.json`, "utf8")
15914
16789
  );
15915
16790
  const globs = rootPkg.workspaces ?? [];
15916
16791
  const { unreachable, probes, revert } = planTestEfficacy(
@@ -15918,8 +16793,8 @@ async function runTestEfficacy(args) {
15918
16793
  globs
15919
16794
  );
15920
16795
  for (const p of revert) {
15921
- const norm = join17(worktree, p);
15922
- const root = join17(worktree, ".");
16796
+ const norm = join18(worktree, p);
16797
+ const root = join18(worktree, ".");
15923
16798
  if (isAbsolute3(p) || norm !== root && !norm.startsWith(root + sep3)) {
15924
16799
  throw new Error(
15925
16800
  `refusing to run: revert path escapes the worktree: ${JSON.stringify(p)}`
@@ -16290,8 +17165,8 @@ async function runTestEfficacy(args) {
16290
17165
  findings,
16291
17166
  cleanupFailure
16292
17167
  };
16293
- mkdirSync23(dirname20(out), { recursive: true });
16294
- writeFileSync23(out, JSON.stringify(result, null, 2), "utf8");
17168
+ mkdirSync24(dirname20(out), { recursive: true });
17169
+ writeFileSync24(out, JSON.stringify(result, null, 2), "utf8");
16295
17170
  writeStdoutLine(
16296
17171
  `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))`
16297
17172
  );
@@ -16368,17 +17243,17 @@ var testEfficacyCommand = {
16368
17243
 
16369
17244
  // packages/cli/src/commands/review/test-plan.ts
16370
17245
  init_esbuild_shims();
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";
17246
+ import { existsSync as existsSync12, mkdirSync as mkdirSync25, readFileSync as readFileSync27, writeFileSync as writeFileSync25 } from "node:fs";
17247
+ import { dirname as dirname21, join as join19, normalize, resolve as resolve20 } from "node:path";
16373
17248
  var PLAN_NAME_RE = /(test\s*plan|\btesting\b|how\s+(?:has\s+this|to)\s+(?:been\s+)?test(?:ed)?|测试计划|测试方案|测试步骤)/i;
16374
17249
  var HEADING_LINE_RE = /^(#{1,6})(?:[ \t](.*))?$/;
16375
17250
  var BOLD_LINE_RE = /^\*\*([^*\n]+)\*\*:?\s*$/;
16376
17251
  function extractTestPlanSection(body) {
16377
- const lines2 = body.split(/\r?\n/);
16378
- const fenced = new Array(lines2.length).fill(false);
17252
+ const lines = body.split(/\r?\n/);
17253
+ const fenced = new Array(lines.length).fill(false);
16379
17254
  let fenceMarker = null;
16380
- for (let i = 0; i < lines2.length; i++) {
16381
- const m = /^\s*(```|~~~)/.exec(lines2[i]);
17255
+ for (let i = 0; i < lines.length; i++) {
17256
+ const m = /^\s*(```|~~~)/.exec(lines[i]);
16382
17257
  if (m) {
16383
17258
  fenced[i] = true;
16384
17259
  if (!fenceMarker) fenceMarker = m[1];
@@ -16387,27 +17262,27 @@ function extractTestPlanSection(body) {
16387
17262
  }
16388
17263
  fenced[i] = fenceMarker !== null;
16389
17264
  }
16390
- for (let i = 0; i < lines2.length; i++) {
17265
+ for (let i = 0; i < lines.length; i++) {
16391
17266
  if (fenced[i]) continue;
16392
- const hash = HEADING_LINE_RE.exec(lines2[i]);
16393
- const bold = BOLD_LINE_RE.exec(lines2[i]);
17267
+ const hash = HEADING_LINE_RE.exec(lines[i]);
17268
+ const bold = BOLD_LINE_RE.exec(lines[i]);
16394
17269
  const name = (hash?.[2] ?? bold?.[1])?.trim();
16395
17270
  if (!name || !PLAN_NAME_RE.test(name)) continue;
16396
17271
  const level = hash ? hash[1].length : Infinity;
16397
17272
  const out = [];
16398
- for (let j = i + 1; j < lines2.length; j++) {
17273
+ for (let j = i + 1; j < lines.length; j++) {
16399
17274
  if (!fenced[j]) {
16400
- const next = HEADING_LINE_RE.exec(lines2[j]);
17275
+ const next = HEADING_LINE_RE.exec(lines[j]);
16401
17276
  if (next && !next[2]?.trim()) {
16402
- out.push(lines2[j]);
17277
+ out.push(lines[j]);
16403
17278
  continue;
16404
17279
  }
16405
17280
  if (next && next[1].length <= level) break;
16406
- if (!hash && (next || BOLD_LINE_RE.test(lines2[j]))) break;
17281
+ if (!hash && (next || BOLD_LINE_RE.test(lines[j]))) break;
16407
17282
  }
16408
- out.push(lines2[j]);
17283
+ out.push(lines[j]);
16409
17284
  }
16410
- return { heading: lines2[i].trim(), content: out.join("\n").trim() };
17285
+ return { heading: lines[i].trim(), content: out.join("\n").trim() };
16411
17286
  }
16412
17287
  return null;
16413
17288
  }
@@ -16572,7 +17447,7 @@ function rulePath(text, worktree, changed) {
16572
17447
  };
16573
17448
  }
16574
17449
  const ignored = isGitIgnored(worktree, path3);
16575
- if (existsSync12(join18(worktree, path3))) {
17450
+ if (existsSync12(join19(worktree, path3))) {
16576
17451
  return {
16577
17452
  kind: "path",
16578
17453
  text,
@@ -16643,15 +17518,27 @@ function ruleCommand(text, worktree, buildTest) {
16643
17518
  let rootScripts = [];
16644
17519
  try {
16645
17520
  const rootPkg = JSON.parse(
16646
- readFileSync26(join18(worktree, "package.json"), "utf8")
17521
+ readFileSync27(join19(worktree, "package.json"), "utf8")
16647
17522
  );
16648
17523
  rootScripts = Object.keys(rootPkg.scripts ?? {});
16649
17524
  } catch {
16650
17525
  }
16651
17526
  const defined = new Set(rootScripts);
16652
- for (const pkg of readWorkspacePackages(worktree)) {
17527
+ const { packages, skipped } = readWorkspacePackages(worktree);
17528
+ for (const pkg of packages) {
16653
17529
  for (const s of pkg.scripts) defined.add(s);
16654
17530
  }
17531
+ const unreadable = [];
17532
+ for (const dir of skipped) {
17533
+ try {
17534
+ const pkg = JSON.parse(
17535
+ readFileSync27(join19(worktree, dir, "package.json"), "utf8")
17536
+ );
17537
+ for (const s of Object.keys(pkg?.scripts ?? {})) defined.add(s);
17538
+ } catch {
17539
+ unreadable.push(dir);
17540
+ }
17541
+ }
16655
17542
  if (defined.size === 0) {
16656
17543
  return {
16657
17544
  kind: "command",
@@ -16660,12 +17547,31 @@ function ruleCommand(text, worktree, buildTest) {
16660
17547
  note: "no package manifest could be read"
16661
17548
  };
16662
17549
  }
16663
- return defined.has(script) ? {
16664
- kind: "command",
16665
- text,
16666
- verdict: "reproduces",
16667
- note: `\`${script}\` is a defined script`
16668
- } : {
17550
+ if (defined.has(script)) {
17551
+ return {
17552
+ kind: "command",
17553
+ text,
17554
+ verdict: "reproduces",
17555
+ note: `\`${script}\` is a defined script`
17556
+ };
17557
+ }
17558
+ if (hasUnmodeledWorkspaceGlob(readWorkspaceGlobs(worktree))) {
17559
+ return {
17560
+ kind: "command",
17561
+ text,
17562
+ verdict: "unchecked",
17563
+ note: "the workspace globs use a shape this check does not model, so the script table may be incomplete"
17564
+ };
17565
+ }
17566
+ if (unreadable.length > 0) {
17567
+ return {
17568
+ kind: "command",
17569
+ text,
17570
+ verdict: "unchecked",
17571
+ note: `${unreadable.join(", ")} ${unreadable.length === 1 ? "has" : "have"} a package.json this check could not read, so the script table may be incomplete`
17572
+ };
17573
+ }
17574
+ return {
16669
17575
  kind: "command",
16670
17576
  text,
16671
17577
  verdict: "contradicted",
@@ -16721,7 +17627,7 @@ __name(fetchPrBody, "fetchPrBody");
16721
17627
  function runTestPlan(args, fetchBody = fetchPrBody) {
16722
17628
  let plan;
16723
17629
  try {
16724
- plan = JSON.parse(readFileSync26(args.plan, "utf8"));
17630
+ plan = JSON.parse(readFileSync27(args.plan, "utf8"));
16725
17631
  } catch (err) {
16726
17632
  throw new Error(
16727
17633
  `test-plan: cannot read the plan ${args.plan}: ${err.message}`
@@ -16756,7 +17662,7 @@ function runTestPlan(args, fetchBody = fetchPrBody) {
16756
17662
  if (args.buildTest) {
16757
17663
  try {
16758
17664
  buildTest = JSON.parse(
16759
- readFileSync26(args.buildTest, "utf8")
17665
+ readFileSync27(args.buildTest, "utf8")
16760
17666
  );
16761
17667
  } catch {
16762
17668
  buildTest = null;
@@ -16813,8 +17719,8 @@ var testPlanCommand = {
16813
17719
  try {
16814
17720
  const report = runTestPlan(args);
16815
17721
  if (args.out) {
16816
- mkdirSync24(dirname21(resolve20(args.out)), { recursive: true });
16817
- writeFileSync24(resolve20(args.out), JSON.stringify(report, null, 2));
17722
+ mkdirSync25(dirname21(resolve20(args.out)), { recursive: true });
17723
+ writeFileSync25(resolve20(args.out), JSON.stringify(report, null, 2));
16818
17724
  }
16819
17725
  writeStdoutLine(JSON.stringify(report, null, 2));
16820
17726
  writeStderrLine(`test-plan: ${report.note}`);
@@ -16828,8 +17734,8 @@ var testPlanCommand = {
16828
17734
  // packages/cli/src/commands/review/cleanup.ts
16829
17735
  init_esbuild_shims();
16830
17736
  import { execFileSync as execFileSync5 } from "node:child_process";
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";
17737
+ import { existsSync as existsSync13, readFileSync as readFileSync28, readdirSync as readdirSync6, rmSync as rmSync10 } from "node:fs";
17738
+ import { join as join20 } from "node:path";
16833
17739
  var AUTOMATION_MARKER = "<!-- qwen-";
16834
17740
  function isAutomationComment(body) {
16835
17741
  return (body ?? "").trimStart().startsWith(AUTOMATION_MARKER);
@@ -16860,7 +17766,7 @@ var OWNER_REPO_RE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
16860
17766
  function readAuditWindow(target, expectedPrNumber) {
16861
17767
  let raw;
16862
17768
  try {
16863
- raw = readFileSync27(tmpFile(target, "fetch.json"), "utf8");
17769
+ raw = readFileSync28(tmpFile(target, "fetch.json"), "utf8");
16864
17770
  } catch (err) {
16865
17771
  const code = err.code;
16866
17772
  return code === "ENOENT" ? { skip: "no fetch report" } : {
@@ -16907,7 +17813,7 @@ function readSubmitReceipt(target) {
16907
17813
  try {
16908
17814
  return new Set(
16909
17815
  parseReceiptIds(
16910
- readFileSync27(tmpFile(target, "submit-receipt.json"), "utf8")
17816
+ readFileSync28(tmpFile(target, "submit-receipt.json"), "utf8")
16911
17817
  )
16912
17818
  );
16913
17819
  } catch {
@@ -17042,7 +17948,7 @@ function runCleanup(target) {
17042
17948
  }
17043
17949
  for (const file of tmpEntries) {
17044
17950
  if (!file.startsWith(prefix)) continue;
17045
- const full = join19(REVIEW_TMP_DIR, file);
17951
+ const full = join20(REVIEW_TMP_DIR, file);
17046
17952
  try {
17047
17953
  rmSync10(full, { recursive: true, force: true });
17048
17954
  writeStdoutLine(`Removed temp file: ${full}`);
@@ -17073,11 +17979,324 @@ var cleanupCommand = {
17073
17979
  }, "handler")
17074
17980
  };
17075
17981
 
17982
+ // packages/cli/src/commands/review/cost-ledger.ts
17983
+ init_esbuild_shims();
17984
+ import { mkdirSync as mkdirSync26, readFileSync as readFileSync29, statSync as statSync12, writeFileSync as writeFileSync26 } from "node:fs";
17985
+ import { dirname as dirname22, join as join21, resolve as resolve21 } from "node:path";
17986
+ function readUsage(file, floorMs) {
17987
+ const raw = readFileSync29(file, "utf8");
17988
+ const events = [];
17989
+ let launch = "";
17990
+ for (const line of raw.split("\n")) {
17991
+ if (line.trim() === "") continue;
17992
+ for (const rec of parseLineTolerant(line, file)) {
17993
+ if (launch === "" && rec["type"] === "user") {
17994
+ launch = textOf(rec);
17995
+ }
17996
+ if (rec["type"] !== "assistant") continue;
17997
+ const usage = rec["usageMetadata"];
17998
+ if (usage === null || typeof usage !== "object") continue;
17999
+ if (Array.isArray(usage)) continue;
18000
+ const u = usage;
18001
+ const ts = rec["timestamp"];
18002
+ if (typeof ts !== "string") continue;
18003
+ const tsMs = Date.parse(ts);
18004
+ if (!Number.isFinite(tsMs) || tsMs < floorMs) continue;
18005
+ const rawCount = /* @__PURE__ */ __name((k) => {
18006
+ const v = u[k];
18007
+ return typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : null;
18008
+ }, "rawCount");
18009
+ const rawPrompt = rawCount("promptTokenCount");
18010
+ const rawTotal = rawCount("totalTokenCount");
18011
+ const prompt = rawPrompt ?? 0;
18012
+ const candidates = rawCount("candidatesTokenCount") ?? 0;
18013
+ events.push({
18014
+ timestampMs: tsMs,
18015
+ timestamp: ts,
18016
+ input: prompt,
18017
+ // Cached is part of the prompt it is reported with; a broken proxy
18018
+ // can report the pair inverted, and the inversion would otherwise
18019
+ // land in BOTH the rendered share and the archived --out JSON.
18020
+ cached: Math.min(rawCount("cachedContentTokenCount") ?? 0, prompt),
18021
+ // `total − prompt` is the output including thinking under BOTH usage
18022
+ // conventions — reasoning inside candidates, and thoughts disjoint
18023
+ // from them — the same derivation tokenEstimation uses. Candidates
18024
+ // alone is the fallback when the provider reported no total; it stays
18025
+ // correct for the providers this CLI converts, which clamp thoughts
18026
+ // inside candidates. Derive it only when BOTH operands survived the
18027
+ // provider intact: a mixed-sign record coerces prompt to 0 while
18028
+ // keeping a positive total, and the subtraction would bill the
18029
+ // call's whole total as output.
18030
+ output: rawPrompt !== null && rawTotal !== null && rawTotal > rawPrompt ? rawTotal - rawPrompt : candidates,
18031
+ thoughts: rawCount("thoughtsTokenCount") ?? 0
18032
+ });
18033
+ }
18034
+ }
18035
+ return { events, launch };
18036
+ }
18037
+ __name(readUsage, "readUsage");
18038
+ var AUDIT_BRIEF_RE = /read_file\(file_path="[^"]*reverse-audit--chunk-(\d+)--round-(\d+)--[0-9a-f][^"]*\.brief\.md"\)/g;
18039
+ function labelOf(launch, fallback) {
18040
+ const nl = launch.indexOf("\n");
18041
+ const identity = nl === -1 ? launch : launch.slice(0, nl);
18042
+ if (!identity.startsWith("You are review agent `")) return fallback;
18043
+ let auditChunk = null;
18044
+ for (const m of launch.matchAll(AUDIT_BRIEF_RE)) auditChunk = m;
18045
+ if (auditChunk) {
18046
+ return `audit chunk ${auditChunk[1]} (round ${auditChunk[2]})`;
18047
+ }
18048
+ const role = /^You are review agent `([^`]+)`/.exec(identity);
18049
+ if (!role) return fallback;
18050
+ const round = /\(round (\d+)\)/.exec(identity);
18051
+ const chunk2 = CHUNK_RE.exec(role[1]);
18052
+ if (chunk2) return `chunk ${chunk2[1]}`;
18053
+ if (round) {
18054
+ return `agent ${role[1]} (round ${round[1]})`;
18055
+ }
18056
+ const file = /Your file: `([^`]+)`/.exec(identity);
18057
+ if (file) return `agent ${role[1]} (${file[1]})`;
18058
+ return `agent ${role[1]}`;
18059
+ }
18060
+ __name(labelOf, "labelOf");
18061
+ function foldEvents(id, label2, events) {
18062
+ const s = {
18063
+ id,
18064
+ label: label2,
18065
+ calls: 0,
18066
+ inputTokens: 0,
18067
+ cachedTokens: 0,
18068
+ outputTokens: 0,
18069
+ thoughtsTokens: 0,
18070
+ firstAt: null,
18071
+ lastAt: null
18072
+ };
18073
+ let firstMs = Number.POSITIVE_INFINITY;
18074
+ let lastMs = Number.NEGATIVE_INFINITY;
18075
+ for (const e of events) {
18076
+ s.calls += 1;
18077
+ s.inputTokens += e.input;
18078
+ s.cachedTokens += e.cached;
18079
+ s.outputTokens += e.output;
18080
+ s.thoughtsTokens += e.thoughts;
18081
+ if (e.timestampMs < firstMs) {
18082
+ firstMs = e.timestampMs;
18083
+ s.firstAt = e.timestamp;
18084
+ }
18085
+ if (e.timestampMs > lastMs) {
18086
+ lastMs = e.timestampMs;
18087
+ s.lastAt = e.timestamp;
18088
+ }
18089
+ }
18090
+ return s;
18091
+ }
18092
+ __name(foldEvents, "foldEvents");
18093
+ function human(n) {
18094
+ if (n >= 9995e5) return `${(n / 1e9).toFixed(1)}B`;
18095
+ if (n >= 999500) return `${(n / 1e6).toFixed(1)}M`;
18096
+ if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
18097
+ return String(n);
18098
+ }
18099
+ __name(human, "human");
18100
+ var plural = /* @__PURE__ */ __name((n, word) => `${n} ${word}${n === 1 ? "" : "s"}`, "plural");
18101
+ function planFloorMs(planPath) {
18102
+ let raw;
18103
+ let floorMs;
18104
+ try {
18105
+ raw = readFileSync29(planPath, "utf8");
18106
+ floorMs = statSync12(planPath).mtimeMs;
18107
+ } catch (err) {
18108
+ throw new Error(
18109
+ `could not read the plan report ${planPath}: ${err.message}`
18110
+ );
18111
+ }
18112
+ let parsed;
18113
+ try {
18114
+ parsed = JSON.parse(raw);
18115
+ } catch {
18116
+ parsed = null;
18117
+ }
18118
+ const plan = parsed !== null && typeof parsed === "object" ? parsed : void 0;
18119
+ if (typeof plan?.["diffLines"] !== "number" || !Array.isArray(plan?.["chunks"])) {
18120
+ throw new Error(`not a review plan report: ${planPath}`);
18121
+ }
18122
+ return floorMs;
18123
+ }
18124
+ __name(planFloorMs, "planFloorMs");
18125
+ function computeLedger(planPath, env = process.env) {
18126
+ const floorMs = planFloorMs(planPath);
18127
+ const { projectDir, sessionId, dir } = transcriptPaths(env);
18128
+ const chatFile = join21(projectDir, "chats", `${sessionId}.jsonl`);
18129
+ let mainEvents;
18130
+ try {
18131
+ mainEvents = readUsage(chatFile, floorMs).events;
18132
+ } catch (err) {
18133
+ throw new Error(
18134
+ `could not read the chat transcript ${chatFile}: ${err.message}`
18135
+ );
18136
+ }
18137
+ const main = mainEvents.length > 0 ? foldEvents("main", "main loop", mainEvents) : null;
18138
+ let files;
18139
+ try {
18140
+ files = listAgentTranscriptFiles(dir);
18141
+ } catch (err) {
18142
+ if (err.code === "ENOENT") {
18143
+ files = [];
18144
+ } else {
18145
+ throw new Error(
18146
+ `could not list the subagent transcripts at ${dir}: ${err.message}`
18147
+ );
18148
+ }
18149
+ }
18150
+ const agents = [];
18151
+ const agentEvents = [];
18152
+ for (const f of files) {
18153
+ const full = join21(dir, f);
18154
+ let mtimeMs;
18155
+ try {
18156
+ mtimeMs = statSync12(full).mtimeMs;
18157
+ } catch {
18158
+ continue;
18159
+ }
18160
+ if (mtimeMs < floorMs) continue;
18161
+ let read;
18162
+ try {
18163
+ read = readUsage(full, floorMs);
18164
+ } catch {
18165
+ continue;
18166
+ }
18167
+ if (read.events.length === 0) continue;
18168
+ const id = f.replace(/^agent-/, "").replace(/\.jsonl$/, "");
18169
+ agents.push(foldEvents(id, labelOf(read.launch, id), read.events));
18170
+ agentEvents.push(...read.events);
18171
+ }
18172
+ agents.sort((a, b) => b.inputTokens - a.inputTokens);
18173
+ if (mainEvents.length === 0) {
18174
+ throw new Error(
18175
+ `could not read the chat transcript ${chatFile}: no main-loop usage records at or after the plan`
18176
+ );
18177
+ }
18178
+ const totals = foldEvents("totals", "totals", [
18179
+ ...mainEvents,
18180
+ ...agentEvents
18181
+ ]);
18182
+ const wallSeconds = totals.firstAt !== null && totals.lastAt !== null ? Math.max(
18183
+ 0,
18184
+ Math.round(
18185
+ (Date.parse(totals.lastAt) - Date.parse(totals.firstAt)) / 1e3
18186
+ )
18187
+ ) : 0;
18188
+ const { id: _i, label: _l, ...totalsRest } = totals;
18189
+ return { totals: { ...totalsRest, wallSeconds }, main, agents };
18190
+ }
18191
+ __name(computeLedger, "computeLedger");
18192
+ function renderLedger(ledger) {
18193
+ const t2 = ledger.totals;
18194
+ const cachedPct = t2.inputTokens > 0 ? Math.round(t2.cachedTokens / t2.inputTokens * 100) : 0;
18195
+ const lines = [];
18196
+ lines.push(
18197
+ `Cost ledger: ${plural(t2.calls, "model call")} \xB7 ${human(t2.inputTokens)} input (${cachedPct}% cached) \xB7 ${human(t2.outputTokens)} output (${human(t2.thoughtsTokens)} thinking) \xB7 ${Math.round(t2.wallSeconds / 60)} min wall`
18198
+ );
18199
+ if (ledger.main !== null) {
18200
+ const m = ledger.main;
18201
+ lines.push(
18202
+ ` main loop: ${plural(m.calls, "call")} \xB7 ${human(m.inputTokens)} in \xB7 ${human(m.outputTokens)} out`
18203
+ );
18204
+ }
18205
+ if (ledger.agents.length > 0) {
18206
+ const rows = [];
18207
+ for (const a of ledger.agents) {
18208
+ const row = rows.find((r) => r.label === a.label);
18209
+ if (row) {
18210
+ row.calls += a.calls;
18211
+ row.inputTokens += a.inputTokens;
18212
+ row.outputTokens += a.outputTokens;
18213
+ row.count += 1;
18214
+ } else {
18215
+ rows.push({
18216
+ label: a.label,
18217
+ calls: a.calls,
18218
+ inputTokens: a.inputTokens,
18219
+ outputTokens: a.outputTokens,
18220
+ count: 1
18221
+ });
18222
+ }
18223
+ }
18224
+ rows.sort((a, b) => b.inputTokens - a.inputTokens);
18225
+ lines.push(` agent runs: ${ledger.agents.length}`);
18226
+ for (const r of rows.slice(0, 8)) {
18227
+ const label2 = r.count > 1 ? `${r.label} (\xD7${r.count})` : r.label;
18228
+ lines.push(
18229
+ ` ${label2}: ${plural(r.calls, "call")} \xB7 ${human(r.inputTokens)} in \xB7 ${human(r.outputTokens)} out`
18230
+ );
18231
+ }
18232
+ if (rows.length > 8) {
18233
+ const rest = rows.slice(8);
18234
+ const restIn = rest.reduce((n, r) => n + r.inputTokens, 0);
18235
+ const restAgents = rest.reduce((n, r) => n + r.count, 0);
18236
+ lines.push(
18237
+ ` \u2026and ${plural(restAgents, "more agent")} \xB7 ${human(restIn)} in combined`
18238
+ );
18239
+ }
18240
+ }
18241
+ return lines.join("\n");
18242
+ }
18243
+ __name(renderLedger, "renderLedger");
18244
+ function runCostLedger(args) {
18245
+ const stdoutErrorHandler = /* @__PURE__ */ __name((err) => {
18246
+ if (err.code === "EPIPE") process.stdout.destroy();
18247
+ }, "stdoutErrorHandler");
18248
+ const stderrErrorHandler = /* @__PURE__ */ __name((err) => {
18249
+ if (err.code === "EPIPE") process.stderr.destroy();
18250
+ }, "stderrErrorHandler");
18251
+ process.stdout.on("error", stdoutErrorHandler);
18252
+ process.stderr.on("error", stderrErrorHandler);
18253
+ try {
18254
+ let ledger;
18255
+ try {
18256
+ ledger = computeLedger(args.plan, process.env);
18257
+ } catch (err) {
18258
+ const why = err instanceof TranscriptsUnavailableError ? err.message : err.message;
18259
+ writeStderrLineSafe(`cost-ledger unavailable \u2014 ${why}`);
18260
+ return;
18261
+ }
18262
+ if (args.out !== void 0 && args.out.length > 0) {
18263
+ try {
18264
+ mkdirSync26(dirname22(resolve21(args.out)), { recursive: true });
18265
+ writeFileSync26(args.out, JSON.stringify(ledger, null, 2));
18266
+ } catch (err) {
18267
+ writeStderrLineSafe(
18268
+ `cost-ledger: could not write ${args.out} \u2014 ${err.message}`
18269
+ );
18270
+ }
18271
+ }
18272
+ writeStdoutLineSafe(renderLedger(ledger));
18273
+ } finally {
18274
+ process.stdout.removeListener("error", stdoutErrorHandler);
18275
+ process.stderr.removeListener("error", stderrErrorHandler);
18276
+ }
18277
+ }
18278
+ __name(runCostLedger, "runCostLedger");
18279
+ var costLedgerCommand = {
18280
+ command: "cost-ledger",
18281
+ describe: "Aggregate this review's model-call cost from the harness's usage records",
18282
+ builder: /* @__PURE__ */ __name((yargs) => yargs.option("plan", {
18283
+ type: "string",
18284
+ demandOption: true,
18285
+ describe: "The plan report from Step 1 \u2014 its mtime marks the review start"
18286
+ }).option("out", {
18287
+ type: "string",
18288
+ describe: "Also write the full ledger as JSON to this path"
18289
+ }), "builder"),
18290
+ handler: /* @__PURE__ */ __name((args) => {
18291
+ runCostLedger(args);
18292
+ }, "handler")
18293
+ };
18294
+
17076
18295
  // packages/cli/src/commands/review/run.ts
17077
18296
  init_esbuild_shims();
17078
18297
  import { spawn, execFileSync as execFileSync6 } from "node:child_process";
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";
18298
+ import { readdirSync as readdirSync7, readFileSync as readFileSync30, realpathSync as realpathSync3, statSync as statSync13 } from "node:fs";
18299
+ import { dirname as dirname23, join as join22, resolve as resolve22 } from "node:path";
17081
18300
  var COMPOSED_PATTERN = /^qwen-review-.*composed\.json$/;
17082
18301
  var COMPOSED_POLL_MS = 250;
17083
18302
  var SIGNAL_EXIT_CODES = {
@@ -17111,10 +18330,10 @@ function newestArtifactSince(dir, pattern, startMs) {
17111
18330
  }
17112
18331
  for (const name of names) {
17113
18332
  if (!pattern.test(name)) continue;
17114
- const path3 = join20(dir, name);
18333
+ const path3 = join22(dir, name);
17115
18334
  let mtime;
17116
18335
  try {
17117
- mtime = statSync9(path3).mtimeMs;
18336
+ mtime = statSync13(path3).mtimeMs;
17118
18337
  } catch {
17119
18338
  continue;
17120
18339
  }
@@ -17132,7 +18351,7 @@ function exitCodeFor(completed, event2, failOn) {
17132
18351
  __name(exitCodeFor, "exitCodeFor");
17133
18352
  function readComposed(path3) {
17134
18353
  try {
17135
- const parsed = JSON.parse(readFileSync28(path3, "utf8"));
18354
+ const parsed = JSON.parse(readFileSync30(path3, "utf8"));
17136
18355
  return typeof parsed.event === "string" ? parsed : null;
17137
18356
  } catch {
17138
18357
  return null;
@@ -17163,7 +18382,7 @@ function childEnv() {
17163
18382
  return env;
17164
18383
  }
17165
18384
  try {
17166
- if (dirname22(realpathSync3(inherited)) === dirname22(realpathSync3(ownEntry))) {
18385
+ if (dirname23(realpathSync3(inherited)) === dirname23(realpathSync3(ownEntry))) {
17167
18386
  return env;
17168
18387
  }
17169
18388
  } catch {
@@ -17285,8 +18504,8 @@ async function runReview(args) {
17285
18504
  downgraded: composed?.downgraded ?? false,
17286
18505
  downgradedFrom: composed?.downgradedFrom ?? null,
17287
18506
  remediation: composed?.remediation ?? [],
17288
- composedPath: composedPath ? resolve21(composedPath) : null,
17289
- reportPath: reportPath ? resolve21(reportPath) : null,
18507
+ composedPath: composedPath ? resolve22(composedPath) : null,
18508
+ reportPath: reportPath ? resolve22(reportPath) : null,
17290
18509
  childExitCode,
17291
18510
  childSignal,
17292
18511
  timedOut,
@@ -17300,7 +18519,7 @@ async function runReview(args) {
17300
18519
  writeStdoutLine(result.verdictLine ?? `Event: ${result.event}`);
17301
18520
  if (result.reportPath) writeStdoutLine(`Report: ${result.reportPath}`);
17302
18521
  } else {
17303
- const detail = composedPath !== null ? `a composed verdict was found at ${resolve21(composedPath)} but could not be parsed` : "no composed verdict was produced";
18522
+ const detail = composedPath !== null ? `a composed verdict was found at ${resolve22(composedPath)} but could not be parsed` : "no composed verdict was produced";
17304
18523
  writeStdoutLine(
17305
18524
  timedOut ? "Review did not complete: timed out." : `Review did not complete: ${detail}${childExitCode !== null ? ` (CLI exit ${childExitCode})` : ""}${childSignal !== null ? ` (killed by ${childSignal})` : ""}.`
17306
18525
  );
@@ -17368,22 +18587,22 @@ init_esbuild_shims();
17368
18587
  import {
17369
18588
  existsSync as existsSync14,
17370
18589
  lstatSync as lstatSync5,
17371
- mkdirSync as mkdirSync25,
17372
- readFileSync as readFileSync29,
17373
- statSync as statSync10
18590
+ mkdirSync as mkdirSync27,
18591
+ readFileSync as readFileSync31,
18592
+ statSync as statSync14
17374
18593
  } from "node:fs";
17375
18594
  import {
17376
- dirname as dirname23,
18595
+ dirname as dirname24,
17377
18596
  isAbsolute as isAbsolute4,
17378
- join as join21,
18597
+ join as join23,
17379
18598
  relative as relative3,
17380
- resolve as resolve22,
18599
+ resolve as resolve23,
17381
18600
  sep as sep4,
17382
18601
  toNamespacedPath
17383
18602
  } from "node:path";
17384
18603
  import { isDeepStrictEqual } from "node:util";
17385
18604
  function workspaceRoot() {
17386
- return resolve22(process.env["QWEN_CODE_PROJECT_DIR"] ?? process.cwd());
18605
+ return resolve23(process.env["QWEN_CODE_PROJECT_DIR"] ?? process.cwd());
17387
18606
  }
17388
18607
  __name(workspaceRoot, "workspaceRoot");
17389
18608
  function isWithin(parent, child) {
@@ -17392,7 +18611,7 @@ function isWithin(parent, child) {
17392
18611
  }
17393
18612
  __name(isWithin, "isWithin");
17394
18613
  function workspacePath(root, value, label2) {
17395
- const absolute = resolve22(root, value);
18614
+ const absolute = resolve23(root, value);
17396
18615
  if (!isWithin(root, absolute)) {
17397
18616
  throw new Error(
17398
18617
  `${label2} must be inside the workspace: ${JSON.stringify(value)}.`
@@ -17405,7 +18624,7 @@ function rejectSymlinkPath(root, path3, label2) {
17405
18624
  const rel = relative3(root, path3);
17406
18625
  let current = root;
17407
18626
  for (const segment of rel.split(sep4)) {
17408
- current = join21(current, segment);
18627
+ current = join23(current, segment);
17409
18628
  if (existsSync14(current) && lstatSync5(current).isSymbolicLink()) {
17410
18629
  throw new Error(`${label2} must not traverse a symbolic link.`);
17411
18630
  }
@@ -17415,14 +18634,14 @@ __name(rejectSymlinkPath, "rejectSymlinkPath");
17415
18634
  function sameFile(left, right) {
17416
18635
  if (left === right) return true;
17417
18636
  if (!existsSync14(left) || !existsSync14(right)) return false;
17418
- const leftStat = statSync10(left);
17419
- const rightStat = statSync10(right);
18637
+ const leftStat = statSync14(left);
18638
+ const rightStat = statSync14(right);
17420
18639
  return leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino;
17421
18640
  }
17422
18641
  __name(sameFile, "sameFile");
17423
18642
  function readText(path3, label2) {
17424
18643
  try {
17425
- return readFileSync29(path3, "utf8");
18644
+ return readFileSync31(path3, "utf8");
17426
18645
  } catch (error) {
17427
18646
  throw new Error(
17428
18647
  `Could not read the ${label2} file at ${JSON.stringify(path3)}: ${error instanceof Error ? error.message : String(error)}`
@@ -17548,7 +18767,7 @@ function saveReviewArtifact(args) {
17548
18767
  const composedPath = workspacePath(root, args.composed, "Composed input");
17549
18768
  const reportPath = workspacePath(root, args.report, "Markdown report");
17550
18769
  const outputPath = workspacePath(root, args.out, "Output");
17551
- const reviewsRoot = resolve22(root, REVIEWS_DIR);
18770
+ const reviewsRoot = resolve23(root, REVIEWS_DIR);
17552
18771
  if (!isWithin(reviewsRoot, reportPath) || reportPath === reviewsRoot) {
17553
18772
  throw new Error(
17554
18773
  `Markdown report must be a file under ${JSON.stringify(REVIEWS_DIR)}: ${JSON.stringify(args.report)}.`
@@ -17589,7 +18808,7 @@ function saveReviewArtifact(args) {
17589
18808
  const verdict = validateVerdict(readJson2(composedPath, "composed verdict"));
17590
18809
  let reportIsFile;
17591
18810
  try {
17592
- reportIsFile = statSync10(reportPath).isFile();
18811
+ reportIsFile = statSync14(reportPath).isFile();
17593
18812
  } catch {
17594
18813
  }
17595
18814
  if (reportIsFile === false) {
@@ -17609,7 +18828,7 @@ function saveReviewArtifact(args) {
17609
18828
  outcomesRecorded: findings.outcomesRecorded,
17610
18829
  markdownReportPath
17611
18830
  };
17612
- mkdirSync25(dirname23(outputPath), { recursive: true });
18831
+ mkdirSync27(dirname24(outputPath), { recursive: true });
17613
18832
  atomicWriteFileSync(
17614
18833
  toNamespacedPath(outputPath),
17615
18834
  `${JSON.stringify(document, null, 2)}
@@ -17662,9 +18881,9 @@ var saveArtifactCommand = {
17662
18881
  var reviewCommand = {
17663
18882
  command: "review",
17664
18883
  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)",
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(
18884
+ 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(costLedgerCommand).command(presubmitCommand).command(testEfficacyCommand).command(testPlanCommand).command(findingsCommand).command(publishAssetsCommand).command(composeReviewCommand).command(saveArtifactCommand).command(submitCommand).command(cleanupCommand).demandCommand(
17666
18885
  1,
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."
18886
+ "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, cost-ledger, presubmit, test-efficacy, test-plan, findings, publish-assets, compose-review, save-artifact, submit, or cleanup."
17668
18887
  ).version(false), "builder"),
17669
18888
  handler: /* @__PURE__ */ __name(() => {
17670
18889
  }, "handler")
@@ -17864,11 +19083,11 @@ var updateCommand3 = {
17864
19083
  stdioHelpers,
17865
19084
  { updateEventEmitter }
17866
19085
  ] = await Promise.all([
17867
- import("./settings-E6H3AURD.js"),
17868
- import("./updateCheck-ZXRCGY66.js"),
17869
- import("./installationInfo-OFDYLDFD.js"),
17870
- import("./standalone-update-NYYZEHLD.js"),
17871
- import("./stdioHelpers-HGIYTWAY.js"),
19086
+ import("./settings-AI2BTBOZ.js"),
19087
+ import("./updateCheck-UOVZSTP7.js"),
19088
+ import("./installationInfo-PO4Z3ES7.js"),
19089
+ import("./standalone-update-WVINHRNS.js"),
19090
+ import("./stdioHelpers-7A2EE64Z.js"),
17872
19091
  import("./updateEventEmitter-5MKC7BLQ.js")
17873
19092
  ]);
17874
19093
  const { formatUpdateInstructions, getInstallationInfo } = installationInfoModule;
@@ -18713,7 +19932,7 @@ var SessionIdConflictError = class extends Error {
18713
19932
  this.sessionId = sessionId;
18714
19933
  }
18715
19934
  };
18716
- async function loadCliConfig(settings, argv, cwd = process.cwd(), overrideExtensions, hooksConfig, disabledSkillNamesProvider, sessionMcpServers, settingsWatcher, throwOnSessionIdConflict = false) {
19935
+ async function loadCliConfig(settings, argv, cwd = process.cwd(), overrideExtensions, hooksConfig, disabledSkillNamesProvider, sessionMcpServers, settingsWatcher, throwOnSessionIdConflict = false, hostPolicy) {
18717
19936
  const debugMode = isDebugMode(argv);
18718
19937
  if (debugMode && process.env["QWEN_DEBUG_LOG_FILE"] === void 0) {
18719
19938
  process.env["QWEN_DEBUG_LOG_FILE"] = "1";
@@ -19034,7 +20253,7 @@ async function loadCliConfig(settings, argv, cwd = process.cwd(), overrideExtens
19034
20253
  disabledSlashCommands: disabledSlashCommands.length > 0 ? disabledSlashCommands : void 0,
19035
20254
  disabledSkillNamesProvider: bareMode || safeMode ? void 0 : disabledSkillNamesProvider,
19036
20255
  terminalImageRenderSupportProvider: interactive ? async () => {
19037
- const { getTerminalImageRenderSupport } = await import("./terminal-image-renderer-QRSCS2BE.js");
20256
+ const { getTerminalImageRenderSupport } = await import("./terminal-image-renderer-II74TFVW.js");
19038
20257
  return getTerminalImageRenderSupport();
19039
20258
  } : void 0,
19040
20259
  disabledSkillLevels: bareMode || safeMode || !Array.isArray(settings.skills?.disabledLevels) ? void 0 : settings.skills.disabledLevels.filter(isSkillLevel),
@@ -19051,6 +20270,7 @@ async function loadCliConfig(settings, argv, cwd = process.cwd(), overrideExtens
19051
20270
  deny: mergedDeny.length > 0 ? mergedDeny : void 0,
19052
20271
  autoMode: bareMode || safeMode ? void 0 : settings.permissions?.autoMode
19053
20272
  },
20273
+ toolInvocationGuard: hostPolicy?.toolInvocationGuard,
19054
20274
  // Permission rule persistence callback (writes to settings files).
19055
20275
  onPersistPermissionRule: /* @__PURE__ */ __name(async (scope, ruleType, rule) => {
19056
20276
  const currentSettings = loadSettings(cwd);