openclaw-multi-auto 1.0.9 → 1.1.1

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 (490) hide show
  1. package/dist/{accounts-C_lW3Ag9.js → accounts-BOzyfwW4.js} +2 -2
  2. package/dist/{accounts-BVgYdU9W.js → accounts-BtEMxtPK.js} +1 -1
  3. package/dist/{accounts-BU-CeDai.js → accounts-CUYZBSKh.js} +1 -1
  4. package/dist/{accounts-C9HcPI9h.js → accounts-L9ByEpnP.js} +2 -2
  5. package/dist/{accounts-DJaYqD2E.js → accounts-khXX75l1.js} +7 -7
  6. package/dist/{accounts-Tgelvk0C.js → accounts-yfBeCZtS.js} +17 -17
  7. package/dist/{acp-cli-oWFHnS7i.js → acp-cli-CA2oCCEA.js} +8 -8
  8. package/dist/{active-listener-BEdprTkn.js → active-listener-D1yqT1cw.js} +2 -2
  9. package/dist/{agent-scope-BRElciAf.js → agent-scope-NQ9CtXYN.js} +17 -17
  10. package/dist/{agents-CujsWz9d.js → agents-BWVlofyv.js} +14 -14
  11. package/dist/{agents.config-C42STger.js → agents.config-QVfqc4C-.js} +2 -2
  12. package/dist/{api-key-rotation-BJpKWXy0.js → api-key-rotation-DtsNS2Nb.js} +2 -2
  13. package/dist/{api-key-rotation-BbuLHl0_.js → api-key-rotation-Vix80AUw.js} +1 -1
  14. package/dist/{audio-preflight-Yg1vzPE1.js → audio-preflight-DUtdCXjJ.js} +34 -34
  15. package/dist/{audio-preflight-C58LeYZM.js → audio-preflight-DpxQCpsA.js} +32 -32
  16. package/dist/{audio-transcription-runner-B8jbozH5.js → audio-transcription-runner-28fcRNNi.js} +12 -12
  17. package/dist/{audio-transcription-runner-D1cvrZ6s.js → audio-transcription-runner-DYKvqK54.js} +23 -23
  18. package/dist/{audit-membership-runtime-Dntemq07.js → audit-membership-runtime-DWyHWAHM.js} +4 -4
  19. package/dist/{audit-DtlAv66L.js → audit-oC--RZy0.js} +29 -29
  20. package/dist/{auth-Mj21c_GN.js → auth-YB6m93-M.js} +1 -1
  21. package/dist/{auth-choice-CEycltU4.js → auth-choice-CsSQLEkP.js} +13 -13
  22. package/dist/{auth-choice-CGHVedXa.js → auth-choice-gp-h1aBd.js} +11 -11
  23. package/dist/{auth-choice.apply-helpers-D4YQXYfc.js → auth-choice.apply-helpers-CPpuynsT.js} +1 -1
  24. package/dist/{auth-profiles-CdLTlJLc.js → auth-profiles-15pq9j9V.js} +16 -16
  25. package/dist/{auth-token-7kzDLVhb.js → auth-token-Bwn8N6KA.js} +1 -1
  26. package/dist/{banner-CpzKVd4-.js → banner-hcZ0XNXv.js} +2 -2
  27. package/dist/{bonjour-discovery-BuS9wF_p.js → bonjour-discovery-2btw06uD.js} +1 -1
  28. package/dist/{browser-cli-BVtbvQ3Z.js → browser-cli-C4PdLYHy.js} +12 -12
  29. package/dist/build-info.json +3 -3
  30. package/dist/bundled/boot-md/handler.js +51 -51
  31. package/dist/bundled/bootstrap-extra-files/handler.js +6 -6
  32. package/dist/bundled/command-logger/handler.js +2 -2
  33. package/dist/bundled/session-memory/handler.js +51 -51
  34. package/dist/{call-Dp9SDkqK.js → call-CZgRbVYm.js} +10 -10
  35. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  36. package/dist/{channel-account-context-ESLVGdUf.js → channel-account-context-BYiOuxnf.js} +5 -5
  37. package/dist/{channel-activity-LfmEkdDN.js → channel-activity-D9K6yeu8.js} +1 -1
  38. package/dist/{channel-activity-BDnjYF7B.js → channel-activity-xHOMiarp.js} +3 -3
  39. package/dist/{channel-options-CsYaFB_4.js → channel-options-DJkf_8wi.js} +3 -3
  40. package/dist/{channel-selection-DFJsYFPo.js → channel-selection-BpIsCVV5.js} +1 -1
  41. package/dist/{channel-web-DzRsJEE4.js → channel-web-wb1My1Yi.js} +17 -17
  42. package/dist/{channels-cli-CpfeOXtJ.js → channels-cli-DlQONtY9.js} +93 -93
  43. package/dist/{channels-status-issues-6V9ktlZD.js → channels-status-issues-Hj-GOSVm.js} +1 -1
  44. package/dist/{chrome-CRBG2YP_.js → chrome-DwizpzOC.js} +26 -26
  45. package/dist/{chrome-CyM61Cn2.js → chrome-L_icBVLq.js} +4 -4
  46. package/dist/{clawbot-cli-COtnakIJ.js → clawbot-cli-CyTZUVIJ.js} +11 -11
  47. package/dist/cli/daemon-cli.js +1 -1
  48. package/dist/{cli-jnT1hYPO.js → cli-CNTrB-ni.js} +73 -73
  49. package/dist/{client-C-wA75vx.js → client-g0366Z73.js} +2 -2
  50. package/dist/{command-registry-g5tDqdAU.js → command-registry-VOmumlmD.js} +11 -11
  51. package/dist/{command-secret-targets-u3eWn-W3.js → command-secret-targets-CIMuIdbL.js} +4 -4
  52. package/dist/{commands-C8pWcFs_.js → commands-DU7At3rY.js} +1 -1
  53. package/dist/{commands-registry-J6nVL3BQ.js → commands-registry-B_WUa9KB.js} +3 -3
  54. package/dist/{commands-registry-D5qXbFJn.js → commands-registry-V1zZ5pPC.js} +4 -4
  55. package/dist/{completion-cli-DQsyli3B.js → completion-cli-pqdmKVTo.js} +13 -13
  56. package/dist/{config-cli-DUycVHoC.js → config-cli-BqznmzlM.js} +7 -7
  57. package/dist/{config-guard--Obk2MGi.js → config-guard-D8jiLjr7.js} +3 -3
  58. package/dist/{config-validation-iOxszdEd.js → config-validation-BngLiGoq.js} +3 -3
  59. package/dist/{configure-Al_pz_5z.js → configure-B--Umf_O.js} +17 -17
  60. package/dist/{control-ui-assets-DpjfNcTC.js → control-ui-assets-C9ewbI8z.js} +1 -1
  61. package/dist/{cron-cli-CQ5EZw_b.js → cron-cli-DCRPpI1m.js} +11 -11
  62. package/dist/{daemon-cli-Bb80Mzv5.js → daemon-cli-DtBzKM_d.js} +15 -15
  63. package/dist/{daemon-install-B09ifLcJ.js → daemon-install-ckPBUAjJ.js} +4 -4
  64. package/dist/{daemon-install-helpers-CYpECiEx.js → daemon-install-helpers-BWf-PrgA.js} +11 -11
  65. package/dist/{deliver-ChbFOwjw.js → deliver-D4o6VIur.js} +21 -21
  66. package/dist/{deliver-CxF9h9PE.js → deliver-b93aBgie.js} +7 -7
  67. package/dist/deliver-runtime-CdWd-JQI.js +61 -0
  68. package/dist/deliver-runtime-P-G3bPjW.js +36 -0
  69. package/dist/deps-send-discord.runtime-BltWu9GW.js +36 -0
  70. package/dist/deps-send-discord.runtime-DnbhTFX9.js +26 -0
  71. package/dist/deps-send-imessage.runtime-BOiQ6mDx.js +25 -0
  72. package/dist/deps-send-imessage.runtime-CdCif3t7.js +35 -0
  73. package/dist/deps-send-signal.runtime-CTcl388M.js +24 -0
  74. package/dist/deps-send-signal.runtime-fHqkSmMW.js +34 -0
  75. package/dist/deps-send-slack.runtime-CCqBz4Kg.js +22 -0
  76. package/dist/deps-send-slack.runtime-CkyZO7ln.js +32 -0
  77. package/dist/{deps-send-telegram.runtime-B6wp3kx8.js → deps-send-telegram.runtime-C--B69CN.js} +16 -16
  78. package/dist/deps-send-telegram.runtime-DGSKTCpH.js +27 -0
  79. package/dist/deps-send-whatsapp.runtime-BkoMLlCM.js +119 -0
  80. package/dist/deps-send-whatsapp.runtime-CJkTHkah.js +60 -0
  81. package/dist/{devices-cli-C0dUbeL4.js → devices-cli-D6V059Dh.js} +8 -8
  82. package/dist/{diagnostic-Co6Kghr-.js → diagnostic-Bn4PZjMZ.js} +2 -2
  83. package/dist/{diagnostic-BfiudAAN.js → diagnostic-BpP9UBCE.js} +1 -1
  84. package/dist/{diagnostics-DCWM_8Ur.js → diagnostics---0c2_jo.js} +5 -5
  85. package/dist/{directory-cli-Dlws13Di.js → directory-cli-RYC34-EU.js} +7 -7
  86. package/dist/{dns-cli-But7QdAw.js → dns-cli-CElNoV5S.js} +5 -5
  87. package/dist/{dock-BbUkruOF.js → dock-nA2DVPCV.js} +4 -4
  88. package/dist/{docs-cli-Ck2IKAtw.js → docs-cli-Bv7ltPvi.js} +4 -4
  89. package/dist/{doctor-completion-Nat1HXE1.js → doctor-completion-CQIiN7rY.js} +2 -2
  90. package/dist/{doctor-config-flow-WCCoUXeB.js → doctor-config-flow-BOAryh5P.js} +15 -15
  91. package/dist/{enable-C3H8BtN4.js → enable-688HYBNS.js} +1 -1
  92. package/dist/entry.js +2 -2
  93. package/dist/{errors-xt401nuk.js → errors-CCLeFWAg.js} +1 -1
  94. package/dist/{exec-approvals-allowlist-ySf2Yo5n.js → exec-approvals-allowlist-C0Eya3rT.js} +1 -1
  95. package/dist/{exec-approvals-cli-DhLLOys6.js → exec-approvals-cli-BYYerKp8.js} +16 -16
  96. package/dist/{exec-safe-bin-runtime-policy-DnXViOlF.js → exec-safe-bin-runtime-policy-BmJmfDRo.js} +2 -2
  97. package/dist/{fetch-D-SiVqBm.js → fetch-BhAHVdwx.js} +3 -3
  98. package/dist/{fetch-DuraYswo.js → fetch-BlJWzEP6.js} +5 -5
  99. package/dist/{fetch-guard-CKYBfJ66.js → fetch-guard-BbBdhWz_.js} +1 -1
  100. package/dist/{fetch-guard-DWr0d00H.js → fetch-guard-ChYBwfiy.js} +2 -2
  101. package/dist/{frontmatter-BkTfEZ93.js → frontmatter-CvaMP376.js} +3 -3
  102. package/dist/{fs-safe-CTYUrIgQ.js → fs-safe-0jAo_Whb.js} +4 -4
  103. package/dist/{fs-safe-jMDpsYew.js → fs-safe-CFLs-j60.js} +24 -24
  104. package/dist/{gateway-cli--atF6LYo.js → gateway-cli-DOYWSRqa.js} +153 -153
  105. package/dist/{gateway-rpc-DYyQQ1z9.js → gateway-rpc-w6t8Eq_1.js} +1 -1
  106. package/dist/{github-copilot-token-BDioPmd6.js → github-copilot-token-D13V9YBz.js} +7 -7
  107. package/dist/{health-BDJ72U4Z.js → health-C1EstYHd.js} +11 -11
  108. package/dist/{hooks-cli-r0UX8B9a.js → hooks-cli-BFMve5sG.js} +81 -81
  109. package/dist/{hooks-status-CxBdpBry.js → hooks-status-DBnEWOEP.js} +1 -1
  110. package/dist/{image-C7RmnYxa.js → image-Bbn53mzj.js} +6 -6
  111. package/dist/{image-CyHTO86Q.js → image-Bw71y73Q.js} +5 -5
  112. package/dist/{image-ops-pjs5W0CZ.js → image-ops-AJL9tN3_.js} +10 -10
  113. package/dist/{image-ops-BuUnEOE0.js → image-ops-CehkHxmW.js} +2 -2
  114. package/dist/image-runtime-6jhrqcle.js +55 -0
  115. package/dist/image-runtime-CVv2ra9J.js +29 -0
  116. package/dist/{inspect-BeU4yMp2.js → inspect-CUxeDA8c.js} +4 -4
  117. package/dist/{install-safe-path-BiL8OJvK.js → install-safe-path-BX58wFBl.js} +25 -25
  118. package/dist/{installs-BwKmG0Uy.js → installs-B-xr0Fzq.js} +9 -9
  119. package/dist/{ipv4-BoGwfnEw.js → ipv4-C4Yt-xid.js} +1 -1
  120. package/dist/{ir-B83looB-.js → ir-DAP-B-Xw.js} +8 -8
  121. package/dist/{ir-Da7Ahsqc.js → ir-otKVkb4a.js} +8 -8
  122. package/dist/{issue-format-CJ89_-9v.js → issue-format-DSksfKiV.js} +1 -1
  123. package/dist/{json-files-CuJjdF_0.js → json-files-rR19q30D.js} +8 -8
  124. package/dist/{legacy-names-DOC03BkU.js → legacy-names-TyzbVqa_.js} +1 -1
  125. package/dist/{lifecycle-core-BzuWBBm8.js → lifecycle-core-DVwen0ks.js} +5 -5
  126. package/dist/llm-slug-generator.js +51 -51
  127. package/dist/{logger-BfjWMCSD.js → logger-DMZQQtxK.js} +7 -7
  128. package/dist/{login-VkQ9MW3d.js → login-BcRaGQb6.js} +3 -3
  129. package/dist/{login-CrIwcrVI.js → login-DiCctRo1.js} +5 -5
  130. package/dist/{login-qr-BVMQ-xQz.js → login-qr-C9D47WEZ.js} +6 -6
  131. package/dist/{login-qr-BpPDZdl_.js → login-qr-MUbXgjtd.js} +10 -10
  132. package/dist/{logs-cli-DWHhSnWs.js → logs-cli-B3TG2lp8.js} +9 -9
  133. package/dist/{manager-1bvuGrNR.js → manager-BW_NSIMl.js} +13 -13
  134. package/dist/{manager-BUBwl6M6.js → manager-iwcRf3Xc.js} +14 -14
  135. package/dist/manager-runtime-BN6VevdC.js +18 -0
  136. package/dist/{manager-runtime-H9iQnw64.js → manager-runtime-D3KqpRT7.js} +9 -9
  137. package/dist/{manifest-registry-DVviqWVY.js → manifest-registry-CJb8odRF.js} +1 -1
  138. package/dist/{memory-cli-DarWCbU6.js → memory-cli-DGsJx7YM.js} +12 -12
  139. package/dist/{model-BLV8y_N-.js → model-B792l3Cn.js} +2 -2
  140. package/dist/{model-catalog-DTcBkUlX.js → model-catalog-DZ6kl2Ko.js} +3 -3
  141. package/dist/{model-picker-D-fTOOzb.js → model-picker-6fHys0wK.js} +4 -4
  142. package/dist/{model-selection-Dna0Gz1k.js → model-selection-idoqPmw0.js} +43 -43
  143. package/dist/{models-CmjutMmh.js → models-DerrMmwF.js} +17 -17
  144. package/dist/{models-cli-BZFQX22p.js → models-cli-BkTZg_4v.js} +78 -78
  145. package/dist/{models-config-DNcDbV_G.js → models-config-Dabs9Kdv.js} +6 -6
  146. package/dist/{net-D5fSREu4.js → net-BAIqYNz0.js} +2 -2
  147. package/dist/{node-cli-Cnq-PJTp.js → node-cli-DoEq4zeG.js} +33 -33
  148. package/dist/{node-command-policy-B5BMBBJP.js → node-command-policy-DRhSc90G.js} +1 -1
  149. package/dist/{node-service-BpYZAvpl.js → node-service-CcgtNIeT.js} +1 -1
  150. package/dist/{nodes-cli-CkrDOWpv.js → nodes-cli--d9RiCnK.js} +16 -16
  151. package/dist/{nodes-screen-DfsQohWd.js → nodes-screen-BTND5VDq.js} +7 -7
  152. package/dist/{npm-pack-install-E-mkrZ55.js → npm-pack-install-CGUDOSKz.js} +18 -18
  153. package/dist/{npm-resolution-BUUmg5ON.js → npm-resolution-CAoULCWM.js} +4 -4
  154. package/dist/{onboard-DINGSS9R.js → onboard-CMZRkPVc.js} +6 -6
  155. package/dist/{onboard-channels-Di4MjWDY.js → onboard-channels-SlJu6XWJ.js} +21 -21
  156. package/dist/{onboard-custom-DITBQuWQ.js → onboard-custom-EzfL89yz.js} +4 -4
  157. package/dist/{onboard-helpers-DO7u2ZOy.js → onboard-helpers-lGpaVp03.js} +10 -10
  158. package/dist/{onboard-hooks-K02_ZWDq.js → onboard-hooks-B7TMiJoY.js} +4 -4
  159. package/dist/{onboard-remote-5-ugiSN2.js → onboard-remote-nvWS7on7.js} +4 -4
  160. package/dist/{onboard-skills-CSzbC2KZ.js → onboard-skills-hz91W03u.js} +4 -4
  161. package/dist/{onboarding-BM4dvUK6.js → onboarding-CTPuMVQI.js} +14 -14
  162. package/dist/{onboarding.finalize-p_RDh7ET.js → onboarding.finalize-DLzl5ZOg.js} +87 -87
  163. package/dist/{onboarding.gateway-config-DldlRfms.js → onboarding.gateway-config-C4MoTr60.js} +18 -18
  164. package/dist/{onboarding.secret-input-BIRIJiCU.js → onboarding.secret-input-BFD0OW4X.js} +1 -1
  165. package/dist/{openai-model-default-CY2Nk4cn.js → openai-model-default-h4LbSR7f.js} +2 -2
  166. package/dist/{openclaw-root-BFfBQ6FD.js → openclaw-root-BU3lu8pM.js} +8 -8
  167. package/dist/{outbound-ChDjtuD6.js → outbound-C2kanETZ.js} +6 -6
  168. package/dist/{outbound-attachment-DfWsfe2N.js → outbound-attachment-CAJBGcna.js} +2 -2
  169. package/dist/{outbound-attachment-DqHlD21U.js → outbound-attachment-DBrYWX8h.js} +2 -2
  170. package/dist/{outbound-DPdJe7e1.js → outbound-kbHYt1JW.js} +3 -3
  171. package/dist/{pairing-cli-D2VZxWg3.js → pairing-cli-CXbmkz7Z.js} +8 -8
  172. package/dist/{pairing-labels-D4rnJ5pJ.js → pairing-labels-CaPLIhlh.js} +1 -1
  173. package/dist/{pairing-store-BXArq4hn.js → pairing-store-Brs9aNn_.js} +3 -3
  174. package/dist/{path-alias-guards-DbNvNQar.js → path-alias-guards-DhIwq92y.js} +3 -3
  175. package/dist/{path-alias-guards-BzvdLvTI.js → path-alias-guards-DqXRZmsL.js} +1 -1
  176. package/dist/{path-safety-ClQO4BB6.js → path-safety-BjIM4N4t.js} +1 -1
  177. package/dist/{paths-Cvc9EM8Y.js → paths-C6TxBCvO.js} +5 -5
  178. package/dist/{paths-Bkr-BCxW.js → paths-CCxysrzL.js} +4 -4
  179. package/dist/{paths-BB_1ZWOj.js → paths-Cv63xST_.js} +9 -9
  180. package/dist/{pi-embedded-BC_GWGuw.js → pi-embedded-BaGj07T0.js} +167 -167
  181. package/dist/{pi-embedded-helpers-oXDyXTD8.js → pi-embedded-helpers-DC2OtKrl.js} +6 -6
  182. package/dist/{pi-embedded-helpers-CbIShbOM.js → pi-embedded-helpers-wy0DZvx1.js} +52 -52
  183. package/dist/{pi-model-discovery-Dymwdjt0.js → pi-model-discovery-BGgOlX8N.js} +7 -7
  184. package/dist/{pi-model-discovery-CECkJMCt.js → pi-model-discovery-DOb5RTev.js} +1 -1
  185. package/dist/pi-model-discovery-runtime-Bwmi4Ev8.js +11 -0
  186. package/dist/{pi-model-discovery-runtime-g7UP-SFR.js → pi-model-discovery-runtime-DShjmiiF.js} +5 -5
  187. package/dist/{pi-tools.before-tool-call.runtime-Df7B-ggW.js → pi-tools.before-tool-call.runtime-B079pVah.js} +5 -5
  188. package/dist/{pi-tools.before-tool-call.runtime-Cwab_5W1.js → pi-tools.before-tool-call.runtime-BuLxSyx9.js} +9 -9
  189. package/dist/{pi-tools.policy-CydUEzFi.js → pi-tools.policy-DZ-X86Va.js} +5 -5
  190. package/dist/{plugin-auto-enable-BE4ZVjAL.js → plugin-auto-enable-BCMqEDjQ.js} +3 -3
  191. package/dist/{plugin-registry-DXW3eyib.js → plugin-registry-BPlCWMur.js} +3 -3
  192. package/dist/plugin-sdk/discord.js +6 -6
  193. package/dist/{plugins-4Rj4OjLY.js → plugins-CWkRQYDj.js} +11 -11
  194. package/dist/{plugins-VAZrrfgw.js → plugins-DCxT-37x.js} +2 -2
  195. package/dist/{plugins-cli-BQJOOVMx.js → plugins-cli-D9ILEfyb.js} +83 -83
  196. package/dist/{ports-DMkRSlnH.js → ports-BlCLhwbc.js} +1 -1
  197. package/dist/{ports-DogAV7pa.js → ports-CER5YPnN.js} +2 -2
  198. package/dist/{program-BA11qFx1.js → program-CV56xn7w.js} +81 -81
  199. package/dist/{prompt-select-styled-B0GS28ia.js → prompt-select-styled-BOa5I_PU.js} +40 -40
  200. package/dist/{provider-auth-helpers-CrGL-jik.js → provider-auth-helpers-c6mNCUXO.js} +5 -5
  201. package/dist/{proxy-env-wKO3g8Yv.js → proxy-env-BMrSVckF.js} +1 -1
  202. package/dist/{proxy-env-DlmzDx8x.js → proxy-env-Cq5gdrbj.js} +1 -1
  203. package/dist/{proxy-fetch-B2pEfjbR.js → proxy-fetch-CCjEYbFm.js} +1 -1
  204. package/dist/{push-apns-BEwBjZ0a.js → push-apns-FOkPD05E.js} +5 -5
  205. package/dist/{pw-ai-D2wlMJtN.js → pw-ai-Cl1Lc7RC.js} +14 -14
  206. package/dist/{pw-ai-CnbxziFP.js → pw-ai-CxBU3aK5.js} +18 -18
  207. package/dist/{qmd-manager-BtIKUaO9.js → qmd-manager-BsYsO9Ii.js} +10 -10
  208. package/dist/{qmd-manager-Dp6PJ8zQ.js → qmd-manager-DEJMqoGd.js} +20 -20
  209. package/dist/{qr-cli-CFz9kS5X.js → qr-cli-BD2jK4fg.js} +2 -2
  210. package/dist/{query-expansion-BrSWVbaE.js → query-expansion-1UTIWjP6.js} +12 -12
  211. package/dist/{query-expansion-CX-1fS52.js → query-expansion-DtLc3wjL.js} +6 -6
  212. package/dist/{redact-COik8ET1.js → redact-ClbcYG1J.js} +1 -1
  213. package/dist/{redact-snapshot-Bs4goggz.js → redact-snapshot-D_qQD4A-.js} +1 -1
  214. package/dist/{register.agent-D641ju8B.js → register.agent-tbPA5YAy.js} +94 -94
  215. package/dist/register.configure-DuwRrXc2.js +165 -0
  216. package/dist/{register.maintenance-BFkk8MEH.js → register.maintenance-DrqDlV5b.js} +95 -95
  217. package/dist/{register.message-D5uE_Hop.js → register.message-CoKXNaU0.js} +74 -74
  218. package/dist/{register.onboard-zHFvSwFr.js → register.onboard-CSWOSL9O.js} +18 -18
  219. package/dist/{register.setup-uTbv3_P1.js → register.setup-DBx5JX6h.js} +21 -21
  220. package/dist/{register.status-health-sessions-CB7t-JQx.js → register.status-health-sessions-D1bPtfep.js} +88 -88
  221. package/dist/{register.subclis-MEiNmuy5.js → register.subclis-QGJNmjss.js} +31 -31
  222. package/dist/{rpc-DVfuVmy9.js → rpc-DkMrTUww.js} +1 -1
  223. package/dist/{run-main-mrvunzuy.js → run-main-CPftxqTe.js} +92 -92
  224. package/dist/{run-with-concurrency-BgYfgkXT.js → run-with-concurrency-D_ZpbgEG.js} +4 -4
  225. package/dist/{runtime-C87FQrrv.js → runtime-MAH2Oph4.js} +3 -3
  226. package/dist/{runtime-config-collectors-BQaC477D.js → runtime-config-collectors-BJMV6Mt1.js} +1 -1
  227. package/dist/{runtime-whatsapp-login.runtime-DjdgScUI.js → runtime-whatsapp-login.runtime-CGYWl9eB.js} +7 -7
  228. package/dist/runtime-whatsapp-login.runtime-IeylZEl4.js +13 -0
  229. package/dist/runtime-whatsapp-outbound.runtime-ClBRuLsq.js +22 -0
  230. package/dist/{runtime-whatsapp-outbound.runtime-B74K7opl.js → runtime-whatsapp-outbound.runtime-hZEfYaRM.js} +15 -15
  231. package/dist/{sandbox-Ct-_lzi1.js → sandbox-0TbzPJaS.js} +18 -18
  232. package/dist/{sandbox-cli-C3RoXFRE.js → sandbox-cli-NLaxffXl.js} +25 -25
  233. package/dist/{secrets-cli-B3hqCxvs.js → secrets-cli-KLGbYet6.js} +11 -11
  234. package/dist/{security-cli-DcHIyXr7.js → security-cli-EIEkcYVb.js} +42 -42
  235. package/dist/{send-dfu6_rgf.js → send-BQERFNyo.js} +5 -5
  236. package/dist/{send-CnRP4P-G.js → send-BW-ZtYG3.js} +5 -5
  237. package/dist/{send-L7gRiwyd.js → send-Bj776ESJ.js} +7 -7
  238. package/dist/{send-2zKwf9NW.js → send-BtZAqquW.js} +11 -11
  239. package/dist/{send-DAQAKa9Z.js → send-CxgWxXZc.js} +6 -6
  240. package/dist/{send-5o2p_xjn.js → send-DAMtu9kK.js} +4 -4
  241. package/dist/{send-PE6cwoTe.js → send-DcxmcFi_.js} +8 -8
  242. package/dist/{send-6lz6rNVP.js → send-Dx2RkUOZ.js} +6 -6
  243. package/dist/{send-u1Bo4CSn.js → send-sC6ka831.js} +8 -8
  244. package/dist/{send-BHTiZcH3.js → send-vmONuVgL.js} +26 -26
  245. package/dist/{server-context-8pDe2iyd.js → server-context-HJVwPQYn.js} +12 -12
  246. package/dist/{server-Ci4xtuR9.js → server-kUElNhlY.js} +20 -20
  247. package/dist/{server-lifecycle-k5daSrde.js → server-lifecycle-BE32unpZ.js} +2 -2
  248. package/dist/{server-middleware-DMiFT9xU.js → server-middleware-AS2VOYkK.js} +1 -1
  249. package/dist/{server-node-events-BmnPjNXE.js → server-node-events-DkbZzI6P.js} +74 -74
  250. package/dist/{service-BhOFtHSw.js → service-CyStNr3d.js} +15 -15
  251. package/dist/{session-D8ImowSs.js → session-A4QhBRvH.js} +8 -8
  252. package/dist/{session-DkOjpX3_.js → session-CaCx4rPH.js} +1 -1
  253. package/dist/{session-utils-DKRmXD2l.js → session-utils-CGqb1oeq.js} +6 -6
  254. package/dist/{sessions-svLGrv0Z.js → sessions-BV6HNW4h.js} +4 -4
  255. package/dist/{sessions-BOWPuhe5.js → sessions-BmVDW-7q.js} +15 -15
  256. package/dist/{shared-CjuadLFV.js → shared-DUQavBtY.js} +3 -3
  257. package/dist/{shared-CbAkLNrg.js → shared-JW74idb0.js} +1 -1
  258. package/dist/{skill-commands-DNqJ-kwn.js → skill-commands-CMzBZKG2.js} +9 -9
  259. package/dist/{skill-commands-TpUsdjev.js → skill-commands-WtIJG0CI.js} +5 -5
  260. package/dist/{skill-scanner-BZvvItef.js → skill-scanner-DIFsGcqE.js} +6 -6
  261. package/dist/{skills-DR-vacol.js → skills-7T9PwwL6.js} +3 -3
  262. package/dist/{skills-7ODkHQYp.js → skills-CE_iqvM5.js} +22 -22
  263. package/dist/{skills-cli-oay0tY8Z.js → skills-cli-DPavvthL.js} +5 -5
  264. package/dist/{skills-install-Vmi7xYfa.js → skills-install-BoLfaoWv.js} +6 -6
  265. package/dist/{skills-status-BmN697ff.js → skills-status-CK5Gnf6i.js} +1 -1
  266. package/dist/{slash-commands.runtime-BfaheruW.js → slash-commands.runtime-CMGx2xHy.js} +11 -11
  267. package/dist/slash-commands.runtime-Cpn2tYW4.js +16 -0
  268. package/dist/slash-dispatch.runtime-BAeJXa56.js +114 -0
  269. package/dist/slash-dispatch.runtime-DoBAQBU5.js +56 -0
  270. package/dist/{slash-skill-commands.runtime-D34BKAN-.js → slash-skill-commands.runtime-ChU2tck2.js} +15 -15
  271. package/dist/slash-skill-commands.runtime-DKMvvdDW.js +20 -0
  272. package/dist/{status-BcQchPaC.js → status-Bp-K1BEf.js} +27 -27
  273. package/dist/{status.update-B20UBTDq.js → status.update-ZYUSggzS.js} +2 -2
  274. package/dist/{store-D89wDcz9.js → store--eR1R_UX.js} +2 -2
  275. package/dist/{store-DDkqo1sO.js → store-DeASfYEV.js} +5 -5
  276. package/dist/{subagent-registry-CPxHbyN5.js → subagent-registry-8qHIVhRq.js} +149 -149
  277. package/dist/subagent-registry-runtime-DVomlbm6.js +114 -0
  278. package/dist/subagent-registry-runtime-ppWS3tVu.js +56 -0
  279. package/dist/{subsystem-B45WV3qB.js → subsystem-Di1z8l0Z.js} +14 -14
  280. package/dist/{system-cli-D2yIJoKU.js → system-cli-C2xNfuQM.js} +9 -9
  281. package/dist/{system-run-command-BmhbnLTE.js → system-run-command-BPWZk7KI.js} +1 -1
  282. package/dist/{systemd-DjWVSbAG.js → systemd-Cf-0XKYu.js} +9 -9
  283. package/dist/{systemd-hints-Do-aQ9jw.js → systemd-hints-CYllYKO0.js} +6 -6
  284. package/dist/{systemd-linger-DpmnYgKU.js → systemd-linger-zllO90bD.js} +1 -1
  285. package/dist/{tables-BxyIF0w4.js → tables-CV7Afb0h.js} +1 -1
  286. package/dist/{tables-mE4cJBN2.js → tables-d739Y1xW.js} +1 -1
  287. package/dist/{tailnet-0_FsdHP-.js → tailnet-DJFUq7_R.js} +1 -1
  288. package/dist/{target-errors-KOHiT_JA.js → target-errors-CBI2Ga0y.js} +2 -2
  289. package/dist/{target-errors-mnlwhAjP.js → target-errors-iVxliVqA.js} +4 -4
  290. package/dist/{thinking-BeGmb5k6.js → thinking-DXYisHiZ.js} +7 -7
  291. package/dist/{tokens-q32vI39c.js → tokens-DxnY9ui_.js} +1 -1
  292. package/dist/{tool-images-RZdHiZcG.js → tool-images-2cBx1W8h.js} +2 -2
  293. package/dist/{tool-images-CNPfeCmU.js → tool-images-Bn6dB14u.js} +1 -1
  294. package/dist/{tui-DL6NZZEa.js → tui-DTVy-YhN.js} +6 -6
  295. package/dist/{tui-cli-BNAYhvpu.js → tui-cli-CexRLJ3a.js} +32 -32
  296. package/dist/{update-DlS-d52F.js → update-DijPxK0g.js} +3 -3
  297. package/dist/{update-cli-D8-DqIs2.js → update-cli-9NslG4yR.js} +104 -104
  298. package/dist/{update-runner-CzTQ7BJT.js → update-runner-B6_UqreW.js} +16 -16
  299. package/dist/{web-igmw_EhT.js → web-CzWRVmFt.js} +55 -55
  300. package/dist/web-DGoa03ue.js +118 -0
  301. package/dist/{webhooks-cli-QdaQhvbT.js → webhooks-cli-B4ZcXTtw.js} +6 -6
  302. package/dist/{whatsapp-actions-Bp8F0cOF.js → whatsapp-actions-Jm4VW1Ve.js} +17 -17
  303. package/dist/{whatsapp-actions-BHbJJyqw.js → whatsapp-actions-iEArE_Ez.js} +21 -21
  304. package/dist/{with-timeout-BMMWHlH3.js → with-timeout-C8-tY12i.js} +3 -3
  305. package/dist/{workspace-v76gFdZu.js → workspace-CIGzK2_w.js} +1 -1
  306. package/dist/{workspace-U-DyR64O.js → workspace-CUVC6GX1.js} +20 -20
  307. package/dist/{workspace-dirs-Cz_Zgtg2.js → workspace-dirs-D4SMysgC.js} +1 -1
  308. package/dist/{wsl-CvQfS6aU.js → wsl-CsGe5QCP.js} +2 -2
  309. package/package.json +7 -9
  310. package/scripts/auth-monitor.sh +89 -0
  311. package/scripts/bench-cli-startup.ts +200 -0
  312. package/scripts/bench-model.ts +146 -0
  313. package/scripts/build-and-run-mac.sh +18 -0
  314. package/scripts/build-docs-list.mjs +14 -0
  315. package/scripts/build_icon.sh +59 -0
  316. package/scripts/bundle-a2ui.sh +95 -0
  317. package/scripts/canvas-a2ui-copy.ts +40 -0
  318. package/scripts/changelog-to-html.sh +91 -0
  319. package/scripts/check-channel-agnostic-boundaries.mjs +343 -0
  320. package/scripts/check-composite-action-input-interpolation.py +81 -0
  321. package/scripts/check-ingress-agent-owner-context.mjs +45 -0
  322. package/scripts/check-no-monolithic-plugin-sdk-entry-imports.ts +103 -0
  323. package/scripts/check-no-pairing-store-group-auth.mjs +180 -0
  324. package/scripts/check-no-random-messaging-tmp.mjs +89 -0
  325. package/scripts/check-no-raw-channel-fetch.mjs +107 -0
  326. package/scripts/check-no-raw-window-open.mjs +86 -0
  327. package/scripts/check-no-register-http-handler.mjs +38 -0
  328. package/scripts/check-pairing-account-scope.mjs +100 -0
  329. package/scripts/check-plugin-sdk-exports.mjs +157 -0
  330. package/scripts/check-ts-max-loc.ts +80 -0
  331. package/scripts/check-webhook-auth-body-order.mjs +55 -0
  332. package/scripts/ci-changed-scope.d.mts +9 -0
  333. package/scripts/ci-changed-scope.mjs +141 -0
  334. package/scripts/claude-auth-status.sh +280 -0
  335. package/scripts/clawlog.sh +321 -0
  336. package/scripts/clawtributors-map.json +40 -0
  337. package/scripts/codesign-mac-app.sh +289 -0
  338. package/scripts/codespell-dictionary.txt +3 -0
  339. package/scripts/codespell-ignore.txt +9 -0
  340. package/scripts/committer +117 -0
  341. package/scripts/copy-export-html-templates.ts +59 -0
  342. package/scripts/copy-hook-metadata.ts +55 -0
  343. package/scripts/copy-plugin-sdk-root-alias.mjs +10 -0
  344. package/scripts/create-dmg.sh +176 -0
  345. package/scripts/create-instance.sh +4 -1
  346. package/scripts/cron_usage_report.ts +273 -0
  347. package/scripts/debug-claude-usage.ts +391 -0
  348. package/scripts/dev/discord-acp-plain-language-smoke.ts +868 -0
  349. package/scripts/dev/gateway-smoke.ts +75 -0
  350. package/scripts/dev/gateway-ws-client.ts +132 -0
  351. package/scripts/dev/ios-node-e2e.ts +283 -0
  352. package/scripts/dev/ios-pull-gateway-log.sh +17 -0
  353. package/scripts/dev/test-device-pair-telegram.ts +62 -0
  354. package/scripts/docker/cleanup-smoke/Dockerfile +19 -0
  355. package/scripts/docker/cleanup-smoke/run.sh +35 -0
  356. package/scripts/docker/install-sh-common/cli-verify.sh +47 -0
  357. package/scripts/docker/install-sh-e2e/Dockerfile +17 -0
  358. package/scripts/docker/install-sh-e2e/run.sh +535 -0
  359. package/scripts/docker/install-sh-nonroot/Dockerfile +33 -0
  360. package/scripts/docker/install-sh-nonroot/run.sh +36 -0
  361. package/scripts/docker/install-sh-smoke/Dockerfile +25 -0
  362. package/scripts/docker/install-sh-smoke/run.sh +63 -0
  363. package/scripts/docs-i18n/doc_mode.go +272 -0
  364. package/scripts/docs-i18n/glossary.go +29 -0
  365. package/scripts/docs-i18n/go.mod +10 -0
  366. package/scripts/docs-i18n/go.sum +10 -0
  367. package/scripts/docs-i18n/html_translate.go +160 -0
  368. package/scripts/docs-i18n/main.go +273 -0
  369. package/scripts/docs-i18n/markdown_segments.go +131 -0
  370. package/scripts/docs-i18n/masking.go +89 -0
  371. package/scripts/docs-i18n/order.go +37 -0
  372. package/scripts/docs-i18n/placeholders.go +30 -0
  373. package/scripts/docs-i18n/process.go +202 -0
  374. package/scripts/docs-i18n/prompt.go +146 -0
  375. package/scripts/docs-i18n/segment.go +11 -0
  376. package/scripts/docs-i18n/tm.go +132 -0
  377. package/scripts/docs-i18n/translator.go +247 -0
  378. package/scripts/docs-i18n/util.go +81 -0
  379. package/scripts/docs-link-audit.mjs +233 -0
  380. package/scripts/docs-list.js +173 -0
  381. package/scripts/docs-spellcheck.sh +44 -0
  382. package/scripts/e2e/Dockerfile +30 -0
  383. package/scripts/e2e/Dockerfile.qr-import +13 -0
  384. package/scripts/e2e/doctor-install-switch-docker.sh +160 -0
  385. package/scripts/e2e/gateway-network-docker.sh +145 -0
  386. package/scripts/e2e/onboard-docker.sh +570 -0
  387. package/scripts/e2e/plugins-docker.sh +224 -0
  388. package/scripts/e2e/qr-import-docker.sh +11 -0
  389. package/scripts/firecrawl-compare.ts +139 -0
  390. package/scripts/generate-host-env-security-policy-swift.mjs +74 -0
  391. package/scripts/generate-secretref-credential-matrix.ts +14 -0
  392. package/scripts/ghsa-patch.mjs +168 -0
  393. package/scripts/install-maca.sh +173 -0
  394. package/scripts/install.ps1 +329 -0
  395. package/scripts/ios-configure-signing.sh +100 -0
  396. package/scripts/ios-team-id.sh +207 -0
  397. package/scripts/label-open-issues.ts +893 -0
  398. package/scripts/lib/callsite-guard.mjs +45 -0
  399. package/scripts/lib/pairing-guard-context.mjs +13 -0
  400. package/scripts/lib/ts-guard-utils.mjs +157 -0
  401. package/scripts/make_appcast.sh +70 -0
  402. package/scripts/mobile-reauth.sh +84 -0
  403. package/scripts/notarize-mac-artifact.sh +65 -0
  404. package/scripts/npm_publish.sh +48 -0
  405. package/scripts/package-mac-app.sh +280 -0
  406. package/scripts/package-mac-dist.sh +93 -0
  407. package/scripts/podman/openclaw.container.in +28 -0
  408. package/scripts/pr +1708 -0
  409. package/scripts/pr-merge +44 -0
  410. package/scripts/pr-prepare +40 -0
  411. package/scripts/pr-review +13 -0
  412. package/scripts/pre-commit/filter-staged-files.mjs +39 -0
  413. package/scripts/pre-commit/run-node-tool.sh +31 -0
  414. package/scripts/protocol-gen-swift.ts +247 -0
  415. package/scripts/protocol-gen.ts +51 -0
  416. package/scripts/readability-basic-compare.ts +66 -0
  417. package/scripts/recover-orphaned-processes.sh +191 -0
  418. package/scripts/release-check.ts +362 -0
  419. package/scripts/repro/tsx-name-repro.ts +3 -0
  420. package/scripts/restart-mac.sh +269 -0
  421. package/scripts/run-node.d.mts +22 -0
  422. package/scripts/run-node.mjs +263 -0
  423. package/scripts/run-openclaw-podman.sh +213 -0
  424. package/scripts/sandbox-browser-entrypoint.sh +127 -0
  425. package/scripts/sandbox-browser-setup.sh +7 -0
  426. package/scripts/sandbox-common-setup.sh +40 -0
  427. package/scripts/sandbox-setup.sh +7 -0
  428. package/scripts/setup-auth-system.sh +119 -0
  429. package/scripts/shell-helpers/README.md +226 -0
  430. package/scripts/shell-helpers/clawdock-helpers.sh +417 -0
  431. package/scripts/sparkle-build.ts +76 -0
  432. package/scripts/sqlite-vec-smoke.mjs +38 -0
  433. package/scripts/sync-dist.sh +171 -0
  434. package/scripts/sync-labels.ts +100 -0
  435. package/scripts/sync-moonshot-docs.ts +125 -0
  436. package/scripts/sync-plugin-versions.ts +108 -0
  437. package/scripts/systemd/openclaw-auth-monitor.service +14 -0
  438. package/scripts/systemd/openclaw-auth-monitor.timer +10 -0
  439. package/scripts/termux-auth-widget.sh +81 -0
  440. package/scripts/termux-quick-auth.sh +30 -0
  441. package/scripts/termux-sync-widget.sh +25 -0
  442. package/scripts/test-cleanup-docker.sh +14 -0
  443. package/scripts/test-force.ts +59 -0
  444. package/scripts/test-hotspots.mjs +83 -0
  445. package/scripts/test-install-sh-docker.sh +72 -0
  446. package/scripts/test-install-sh-e2e-docker.sh +29 -0
  447. package/scripts/test-live-gateway-models-docker.sh +33 -0
  448. package/scripts/test-live-models-docker.sh +34 -0
  449. package/scripts/test-parallel.mjs +468 -0
  450. package/scripts/test-perf-budget.mjs +127 -0
  451. package/scripts/test-shell-completion.ts +223 -0
  452. package/scripts/ui.js +203 -0
  453. package/scripts/update-clawtributors.ts +573 -0
  454. package/scripts/update-clawtributors.types.ts +36 -0
  455. package/scripts/update-package-with-mirror.sh +67 -0
  456. package/scripts/watch-node.d.mts +14 -0
  457. package/scripts/watch-node.mjs +92 -0
  458. package/scripts/write-build-info.ts +47 -0
  459. package/scripts/write-cli-compat.ts +74 -0
  460. package/scripts/write-cli-startup-metadata.ts +93 -0
  461. package/scripts/write-plugin-sdk-entry-dts.ts +60 -0
  462. package/scripts/zai-fallback-repro.ts +168 -0
  463. package/dist/deliver-runtime-B80mXYrL.js +0 -36
  464. package/dist/deliver-runtime-DixuU_uB.js +0 -61
  465. package/dist/deps-send-discord.runtime-CDcLcDSt.js +0 -36
  466. package/dist/deps-send-discord.runtime-DZUccI6Z.js +0 -26
  467. package/dist/deps-send-imessage.runtime-BGPUVMIE.js +0 -35
  468. package/dist/deps-send-imessage.runtime-CF3OpoqY.js +0 -25
  469. package/dist/deps-send-signal.runtime-Cw4-ozeO.js +0 -24
  470. package/dist/deps-send-signal.runtime-DQDD44_O.js +0 -34
  471. package/dist/deps-send-slack.runtime-BDsDhS1P.js +0 -22
  472. package/dist/deps-send-slack.runtime-Cpy77gBG.js +0 -32
  473. package/dist/deps-send-telegram.runtime-D_4xVasO.js +0 -27
  474. package/dist/deps-send-whatsapp.runtime-D2sVAclS.js +0 -119
  475. package/dist/deps-send-whatsapp.runtime-UBgpoWg9.js +0 -60
  476. package/dist/image-runtime-B2qh5seQ.js +0 -55
  477. package/dist/image-runtime-DtMbm7AR.js +0 -29
  478. package/dist/manager-runtime-FO1Sx3W8.js +0 -18
  479. package/dist/pi-model-discovery-runtime-BeY4EUPp.js +0 -11
  480. package/dist/register.configure-BrDOSLIq.js +0 -165
  481. package/dist/runtime-whatsapp-login.runtime-DUb55byQ.js +0 -13
  482. package/dist/runtime-whatsapp-outbound.runtime-Bii_xSfI.js +0 -22
  483. package/dist/slash-commands.runtime-CVw6566g.js +0 -16
  484. package/dist/slash-dispatch.runtime-CMK_EPYG.js +0 -56
  485. package/dist/slash-dispatch.runtime-D0xInkf3.js +0 -114
  486. package/dist/slash-skill-commands.runtime-DxZ4z5h6.js +0 -20
  487. package/dist/subagent-registry-runtime-DdGrQBnQ.js +0 -56
  488. package/dist/subagent-registry-runtime-SkB2tTaE.js +0 -114
  489. package/dist/web-DWiOofzq.js +0 -118
  490. package/install.sh +0 -293
package/scripts/pr ADDED
@@ -0,0 +1,1708 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ # If invoked from a linked worktree copy of this script, re-exec the canonical
6
+ # script from the repository root so behavior stays consistent across worktrees.
7
+ script_self="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
8
+ script_parent_dir="$(dirname "$script_self")"
9
+ if common_git_dir=$(git -C "$script_parent_dir" rev-parse --path-format=absolute --git-common-dir 2>/dev/null); then
10
+ canonical_repo_root="$(dirname "$common_git_dir")"
11
+ canonical_self="$canonical_repo_root/scripts/$(basename "${BASH_SOURCE[0]}")"
12
+ if [ "$script_self" != "$canonical_self" ] && [ -x "$canonical_self" ]; then
13
+ exec "$canonical_self" "$@"
14
+ fi
15
+ fi
16
+
17
+ usage() {
18
+ cat <<USAGE
19
+ Usage:
20
+ scripts/pr review-init <PR>
21
+ scripts/pr review-checkout-main <PR>
22
+ scripts/pr review-checkout-pr <PR>
23
+ scripts/pr review-guard <PR>
24
+ scripts/pr review-artifacts-init <PR>
25
+ scripts/pr review-validate-artifacts <PR>
26
+ scripts/pr review-tests <PR> <test-file> [<test-file> ...]
27
+ scripts/pr prepare-init <PR>
28
+ scripts/pr prepare-validate-commit <PR>
29
+ scripts/pr prepare-gates <PR>
30
+ scripts/pr prepare-push <PR>
31
+ scripts/pr prepare-sync-head <PR>
32
+ scripts/pr prepare-run <PR>
33
+ scripts/pr merge-verify <PR>
34
+ scripts/pr merge-run <PR>
35
+ USAGE
36
+ }
37
+
38
+ require_cmds() {
39
+ local missing=()
40
+ local cmd
41
+ for cmd in git gh jq rg pnpm node; do
42
+ if ! command -v "$cmd" >/dev/null 2>&1; then
43
+ missing+=("$cmd")
44
+ fi
45
+ done
46
+
47
+ if [ "${#missing[@]}" -gt 0 ]; then
48
+ echo "Missing required command(s): ${missing[*]}"
49
+ exit 1
50
+ fi
51
+ }
52
+
53
+ repo_root() {
54
+ # Resolve canonical repository root from git common-dir so wrappers work
55
+ # the same from main checkout or any linked worktree.
56
+ local script_dir
57
+ local common_git_dir
58
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
59
+
60
+ if common_git_dir=$(git -C "$script_dir" rev-parse --path-format=absolute --git-common-dir 2>/dev/null); then
61
+ (cd "$(dirname "$common_git_dir")" && pwd)
62
+ return
63
+ fi
64
+
65
+ # Fallback for environments where git common-dir is unavailable.
66
+ (cd "$script_dir/.." && pwd)
67
+ }
68
+
69
+ enter_worktree() {
70
+ local pr="$1"
71
+ local reset_to_main="${2:-false}"
72
+ local invoke_cwd
73
+ invoke_cwd="$PWD"
74
+ local root
75
+ root=$(repo_root)
76
+
77
+ if [ "$invoke_cwd" != "$root" ]; then
78
+ echo "Detected non-root invocation cwd=$invoke_cwd, using canonical root $root"
79
+ fi
80
+
81
+ cd "$root"
82
+ gh auth status >/dev/null
83
+ git fetch origin main
84
+
85
+ local dir=".worktrees/pr-$pr"
86
+ if [ -d "$dir" ]; then
87
+ cd "$dir"
88
+ git fetch origin main
89
+ if [ "$reset_to_main" = "true" ]; then
90
+ git checkout -B "temp/pr-$pr" origin/main
91
+ fi
92
+ else
93
+ git worktree add "$dir" -b "temp/pr-$pr" origin/main
94
+ cd "$dir"
95
+ fi
96
+
97
+ mkdir -p .local
98
+ }
99
+
100
+ pr_meta_json() {
101
+ local pr="$1"
102
+ gh pr view "$pr" --json number,title,state,isDraft,author,baseRefName,headRefName,headRefOid,headRepository,headRepositoryOwner,url,body,labels,assignees,reviewRequests,files,additions,deletions,statusCheckRollup
103
+ }
104
+
105
+ write_pr_meta_files() {
106
+ local json="$1"
107
+
108
+ printf '%s\n' "$json" > .local/pr-meta.json
109
+
110
+ cat > .local/pr-meta.env <<EOF_ENV
111
+ PR_NUMBER=$(printf '%s\n' "$json" | jq -r .number)
112
+ PR_URL=$(printf '%s\n' "$json" | jq -r .url)
113
+ PR_AUTHOR=$(printf '%s\n' "$json" | jq -r .author.login)
114
+ PR_BASE=$(printf '%s\n' "$json" | jq -r .baseRefName)
115
+ PR_HEAD=$(printf '%s\n' "$json" | jq -r .headRefName)
116
+ PR_HEAD_SHA=$(printf '%s\n' "$json" | jq -r .headRefOid)
117
+ PR_HEAD_REPO=$(printf '%s\n' "$json" | jq -r .headRepository.nameWithOwner)
118
+ PR_HEAD_REPO_URL=$(printf '%s\n' "$json" | jq -r '.headRepository.url // ""')
119
+ PR_HEAD_OWNER=$(printf '%s\n' "$json" | jq -r '.headRepositoryOwner.login // ""')
120
+ PR_HEAD_REPO_NAME=$(printf '%s\n' "$json" | jq -r '.headRepository.name // ""')
121
+ EOF_ENV
122
+ }
123
+
124
+ require_artifact() {
125
+ local path="$1"
126
+ if [ ! -s "$path" ]; then
127
+ echo "Missing required artifact: $path"
128
+ exit 1
129
+ fi
130
+ }
131
+
132
+ print_relevant_log_excerpt() {
133
+ local log_file="$1"
134
+ if [ ! -s "$log_file" ]; then
135
+ echo "(no output captured)"
136
+ return 0
137
+ fi
138
+
139
+ local filtered_log
140
+ filtered_log=$(mktemp)
141
+ if rg -n -i 'error|err|failed|fail|fatal|panic|exception|TypeError|ReferenceError|SyntaxError|ELIFECYCLE|ERR_' "$log_file" >"$filtered_log"; then
142
+ echo "Relevant log lines:"
143
+ tail -n 120 "$filtered_log"
144
+ else
145
+ echo "No focused error markers found; showing last 120 lines:"
146
+ tail -n 120 "$log_file"
147
+ fi
148
+ rm -f "$filtered_log"
149
+ }
150
+
151
+ run_quiet_logged() {
152
+ local label="$1"
153
+ local log_file="$2"
154
+ shift 2
155
+
156
+ mkdir -p .local
157
+ if "$@" >"$log_file" 2>&1; then
158
+ echo "$label passed"
159
+ return 0
160
+ fi
161
+
162
+ echo "$label failed (log: $log_file)"
163
+ print_relevant_log_excerpt "$log_file"
164
+ return 1
165
+ }
166
+
167
+ bootstrap_deps_if_needed() {
168
+ if [ ! -x node_modules/.bin/vitest ]; then
169
+ run_quiet_logged "pnpm install --frozen-lockfile" ".local/bootstrap-install.log" pnpm install --frozen-lockfile
170
+ fi
171
+ }
172
+
173
+ wait_for_pr_head_sha() {
174
+ local pr="$1"
175
+ local expected_sha="$2"
176
+ local max_attempts="${3:-6}"
177
+ local sleep_seconds="${4:-2}"
178
+
179
+ local attempt
180
+ for attempt in $(seq 1 "$max_attempts"); do
181
+ local observed_sha
182
+ observed_sha=$(gh pr view "$pr" --json headRefOid --jq .headRefOid)
183
+ if [ "$observed_sha" = "$expected_sha" ]; then
184
+ return 0
185
+ fi
186
+
187
+ if [ "$attempt" -lt "$max_attempts" ]; then
188
+ sleep "$sleep_seconds"
189
+ fi
190
+ done
191
+
192
+ return 1
193
+ }
194
+
195
+ is_author_email_merge_error() {
196
+ local msg="$1"
197
+ printf '%s\n' "$msg" | rg -qi 'author.?email|email.*associated|associated.*email|invalid.*email'
198
+ }
199
+
200
+ merge_author_email_candidates() {
201
+ local reviewer="$1"
202
+ local reviewer_id="$2"
203
+
204
+ local gh_email
205
+ gh_email=$(gh api user --jq '.email // ""' 2>/dev/null || true)
206
+ local git_email
207
+ git_email=$(git config user.email 2>/dev/null || true)
208
+
209
+ printf '%s\n' \
210
+ "$gh_email" \
211
+ "$git_email" \
212
+ "${reviewer_id}+${reviewer}@users.noreply.github.com" \
213
+ "${reviewer}@users.noreply.github.com" | awk 'NF && !seen[$0]++'
214
+ }
215
+
216
+ checkout_prep_branch() {
217
+ local pr="$1"
218
+ require_artifact .local/prep-context.env
219
+ # shellcheck disable=SC1091
220
+ source .local/prep-context.env
221
+
222
+ local prep_branch="${PREP_BRANCH:-pr-$pr-prep}"
223
+ if ! git show-ref --verify --quiet "refs/heads/$prep_branch"; then
224
+ echo "Expected prep branch $prep_branch not found. Run prepare-init first."
225
+ exit 1
226
+ fi
227
+
228
+ git checkout "$prep_branch"
229
+ }
230
+
231
+ resolve_head_push_url() {
232
+ # shellcheck disable=SC1091
233
+ source .local/pr-meta.env
234
+
235
+ if [ -n "${PR_HEAD_OWNER:-}" ] && [ -n "${PR_HEAD_REPO_NAME:-}" ]; then
236
+ printf 'git@github.com:%s/%s.git\n' "$PR_HEAD_OWNER" "$PR_HEAD_REPO_NAME"
237
+ return 0
238
+ fi
239
+
240
+ if [ -n "${PR_HEAD_REPO_URL:-}" ] && [ "$PR_HEAD_REPO_URL" != "null" ]; then
241
+ case "$PR_HEAD_REPO_URL" in
242
+ *.git) printf '%s\n' "$PR_HEAD_REPO_URL" ;;
243
+ *) printf '%s.git\n' "$PR_HEAD_REPO_URL" ;;
244
+ esac
245
+ return 0
246
+ fi
247
+
248
+ return 1
249
+ }
250
+
251
+ # Push to a fork PR branch via GitHub GraphQL createCommitOnBranch.
252
+ # This uses the same permission model as the GitHub web editor, bypassing
253
+ # the git-protocol 403 that occurs even when maintainer_can_modify is true.
254
+ # Usage: graphql_push_to_fork <owner/repo> <branch> <expected_head_oid>
255
+ # Pushes the diff between expected_head_oid and local HEAD as file additions/deletions.
256
+ # File bytes are read from git objects (not the working tree) to avoid
257
+ # symlink/special-file dereference risks from untrusted fork content.
258
+ graphql_push_to_fork() {
259
+ local repo_nwo="$1" # e.g. Oncomatic/openclaw
260
+ local branch="$2" # e.g. fix/memory-flush-not-executing
261
+ local expected_oid="$3"
262
+ local max_blob_bytes=$((5 * 1024 * 1024))
263
+
264
+ # Build file changes JSON from the diff between expected_oid and HEAD.
265
+ local additions="[]"
266
+ local deletions="[]"
267
+
268
+ # Collect added/modified files
269
+ local added_files
270
+ added_files=$(git diff --no-renames --name-only --diff-filter=AM "$expected_oid" HEAD)
271
+ if [ -n "$added_files" ]; then
272
+ additions="["
273
+ local first=true
274
+ while IFS= read -r fpath; do
275
+ [ -n "$fpath" ] || continue
276
+
277
+ local tree_entry
278
+ tree_entry=$(git ls-tree HEAD -- "$fpath")
279
+ if [ -z "$tree_entry" ]; then
280
+ echo "GraphQL push could not resolve path in HEAD tree: $fpath" >&2
281
+ return 1
282
+ fi
283
+
284
+ local file_mode
285
+ file_mode=$(printf '%s\n' "$tree_entry" | awk '{print $1}')
286
+ local file_type
287
+ file_type=$(printf '%s\n' "$tree_entry" | awk '{print $2}')
288
+ local file_oid
289
+ file_oid=$(printf '%s\n' "$tree_entry" | awk '{print $3}')
290
+
291
+ if [ "$file_type" != "blob" ] || [ "$file_mode" = "160000" ]; then
292
+ echo "GraphQL push only supports blob files; refusing $fpath (mode=$file_mode type=$file_type)" >&2
293
+ return 1
294
+ fi
295
+
296
+ local blob_size
297
+ blob_size=$(git cat-file -s "$file_oid")
298
+ if [ "$blob_size" -gt "$max_blob_bytes" ]; then
299
+ echo "GraphQL push refused large file $fpath (${blob_size} bytes > ${max_blob_bytes})" >&2
300
+ return 1
301
+ fi
302
+
303
+ local b64
304
+ b64=$(git cat-file -p "$file_oid" | base64 | tr -d '\n')
305
+ if [ "$first" = true ]; then first=false; else additions+=","; fi
306
+ additions+="{\"path\":$(printf '%s' "$fpath" | jq -Rs .),\"contents\":$(printf '%s' "$b64" | jq -Rs .)}"
307
+ done <<< "$added_files"
308
+ additions+="]"
309
+ fi
310
+
311
+ # Collect deleted files
312
+ local deleted_files
313
+ deleted_files=$(git diff --no-renames --name-only --diff-filter=D "$expected_oid" HEAD)
314
+ if [ -n "$deleted_files" ]; then
315
+ deletions="["
316
+ local first=true
317
+ while IFS= read -r fpath; do
318
+ [ -n "$fpath" ] || continue
319
+ if [ "$first" = true ]; then first=false; else deletions+=","; fi
320
+ deletions+="{\"path\":$(printf '%s' "$fpath" | jq -Rs .)}"
321
+ done <<< "$deleted_files"
322
+ deletions+="]"
323
+ fi
324
+
325
+ local commit_headline
326
+ commit_headline=$(git log -1 --format=%s HEAD)
327
+
328
+ local query
329
+ query=$(cat <<'GRAPHQL'
330
+ mutation($input: CreateCommitOnBranchInput!) {
331
+ createCommitOnBranch(input: $input) {
332
+ commit { oid url }
333
+ }
334
+ }
335
+ GRAPHQL
336
+ )
337
+
338
+ local variables
339
+ variables=$(jq -n \
340
+ --arg nwo "$repo_nwo" \
341
+ --arg branch "$branch" \
342
+ --arg oid "$expected_oid" \
343
+ --arg headline "$commit_headline" \
344
+ --argjson additions "$additions" \
345
+ --argjson deletions "$deletions" \
346
+ '{input: {
347
+ branch: { repositoryNameWithOwner: $nwo, branchName: $branch },
348
+ message: { headline: $headline },
349
+ fileChanges: { additions: $additions, deletions: $deletions },
350
+ expectedHeadOid: $oid
351
+ }}')
352
+
353
+ local result
354
+ result=$(gh api graphql -f query="$query" --input - <<< "$variables" 2>&1) || {
355
+ echo "GraphQL push failed: $result" >&2
356
+ return 1
357
+ }
358
+
359
+ local new_oid
360
+ new_oid=$(printf '%s' "$result" | jq -r '.data.createCommitOnBranch.commit.oid // empty')
361
+ if [ -z "$new_oid" ]; then
362
+ echo "GraphQL push returned no commit OID: $result" >&2
363
+ return 1
364
+ fi
365
+
366
+ echo "GraphQL push succeeded: $new_oid" >&2
367
+ printf '%s\n' "$new_oid"
368
+ }
369
+
370
+ # Resolve HTTPS fallback URL for prhead push (used if SSH fails).
371
+ resolve_head_push_url_https() {
372
+ # shellcheck disable=SC1091
373
+ source .local/pr-meta.env
374
+
375
+ if [ -n "${PR_HEAD_OWNER:-}" ] && [ -n "${PR_HEAD_REPO_NAME:-}" ]; then
376
+ printf 'https://github.com/%s/%s.git\n' "$PR_HEAD_OWNER" "$PR_HEAD_REPO_NAME"
377
+ return 0
378
+ fi
379
+
380
+ if [ -n "${PR_HEAD_REPO_URL:-}" ] && [ "$PR_HEAD_REPO_URL" != "null" ]; then
381
+ case "$PR_HEAD_REPO_URL" in
382
+ *.git) printf '%s\n' "$PR_HEAD_REPO_URL" ;;
383
+ *) printf '%s.git\n' "$PR_HEAD_REPO_URL" ;;
384
+ esac
385
+ return 0
386
+ fi
387
+
388
+ return 1
389
+ }
390
+
391
+ set_review_mode() {
392
+ local mode="$1"
393
+ cat > .local/review-mode.env <<EOF_ENV
394
+ REVIEW_MODE=$mode
395
+ REVIEW_MODE_SET_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
396
+ EOF_ENV
397
+ }
398
+
399
+ review_checkout_main() {
400
+ local pr="$1"
401
+ enter_worktree "$pr" false
402
+ git fetch origin main
403
+ git checkout --detach origin/main
404
+ set_review_mode main
405
+
406
+ echo "review mode set to main baseline"
407
+ echo "branch=$(git branch --show-current)"
408
+ echo "head=$(git rev-parse --short HEAD)"
409
+ }
410
+
411
+ review_checkout_pr() {
412
+ local pr="$1"
413
+ enter_worktree "$pr" false
414
+ git fetch origin "pull/$pr/head:pr-$pr" --force
415
+ git checkout --detach "pr-$pr"
416
+ set_review_mode pr
417
+
418
+ echo "review mode set to PR head"
419
+ echo "branch=$(git branch --show-current)"
420
+ echo "head=$(git rev-parse --short HEAD)"
421
+ }
422
+
423
+ review_guard() {
424
+ local pr="$1"
425
+ enter_worktree "$pr" false
426
+ require_artifact .local/review-mode.env
427
+ require_artifact .local/pr-meta.env
428
+ # shellcheck disable=SC1091
429
+ source .local/review-mode.env
430
+ # shellcheck disable=SC1091
431
+ source .local/pr-meta.env
432
+
433
+ local branch
434
+ branch=$(git branch --show-current)
435
+ local head_sha
436
+ head_sha=$(git rev-parse HEAD)
437
+
438
+ case "${REVIEW_MODE:-}" in
439
+ main)
440
+ local expected_main_sha
441
+ expected_main_sha=$(git rev-parse origin/main)
442
+ if [ "$head_sha" != "$expected_main_sha" ]; then
443
+ echo "Review guard failed: expected HEAD at origin/main ($expected_main_sha) for main baseline mode, got $head_sha"
444
+ exit 1
445
+ fi
446
+ ;;
447
+ pr)
448
+ if [ -z "${PR_HEAD_SHA:-}" ]; then
449
+ echo "Review guard failed: missing PR_HEAD_SHA in .local/pr-meta.env"
450
+ exit 1
451
+ fi
452
+ if [ "$head_sha" != "$PR_HEAD_SHA" ]; then
453
+ echo "Review guard failed: expected HEAD at PR_HEAD_SHA ($PR_HEAD_SHA), got $head_sha"
454
+ exit 1
455
+ fi
456
+ ;;
457
+ *)
458
+ echo "Review guard failed: unknown review mode '${REVIEW_MODE:-}'"
459
+ exit 1
460
+ ;;
461
+ esac
462
+
463
+ echo "review guard passed"
464
+ echo "mode=$REVIEW_MODE"
465
+ echo "branch=$branch"
466
+ echo "head=$head_sha"
467
+ }
468
+
469
+ review_artifacts_init() {
470
+ local pr="$1"
471
+ enter_worktree "$pr" false
472
+ require_artifact .local/pr-meta.env
473
+
474
+ if [ ! -f .local/review.md ]; then
475
+ cat > .local/review.md <<'EOF_MD'
476
+ A) TL;DR recommendation
477
+
478
+ B) What changed and what is good?
479
+
480
+ C) Security findings
481
+
482
+ D) What is the PR intent? Is this the most optimal implementation?
483
+
484
+ E) Concerns or questions (actionable)
485
+
486
+ F) Tests
487
+
488
+ G) Docs status
489
+
490
+ H) Changelog
491
+
492
+ I) Follow ups (optional)
493
+
494
+ J) Suggested PR comment (optional)
495
+ EOF_MD
496
+ fi
497
+
498
+ if [ ! -f .local/review.json ]; then
499
+ cat > .local/review.json <<'EOF_JSON'
500
+ {
501
+ "recommendation": "READY FOR /prepare-pr",
502
+ "findings": [],
503
+ "nitSweep": {
504
+ "performed": true,
505
+ "status": "none",
506
+ "summary": "No optional nits identified."
507
+ },
508
+ "issueValidation": {
509
+ "performed": true,
510
+ "source": "pr_body",
511
+ "status": "valid",
512
+ "summary": "PR description clearly states a valid problem."
513
+ },
514
+ "tests": {
515
+ "ran": [],
516
+ "gaps": [],
517
+ "result": "pass"
518
+ },
519
+ "docs": "not_applicable",
520
+ "changelog": "required"
521
+ }
522
+ EOF_JSON
523
+ fi
524
+
525
+ echo "review artifact templates are ready"
526
+ echo "files=.local/review.md .local/review.json"
527
+ }
528
+
529
+ review_validate_artifacts() {
530
+ local pr="$1"
531
+ enter_worktree "$pr" false
532
+ require_artifact .local/review.md
533
+ require_artifact .local/review.json
534
+ require_artifact .local/pr-meta.env
535
+
536
+ review_guard "$pr"
537
+
538
+ jq . .local/review.json >/dev/null
539
+
540
+ local section
541
+ for section in "A)" "B)" "C)" "D)" "E)" "F)" "G)" "H)" "I)" "J)"; do
542
+ awk -v s="$section" 'index($0, s) == 1 { found=1; exit } END { exit(found ? 0 : 1) }' .local/review.md || {
543
+ echo "Missing section header in .local/review.md: $section"
544
+ exit 1
545
+ }
546
+ done
547
+
548
+ local recommendation
549
+ recommendation=$(jq -r '.recommendation // ""' .local/review.json)
550
+ case "$recommendation" in
551
+ "READY FOR /prepare-pr"|"NEEDS WORK"|"NEEDS DISCUSSION"|"NOT USEFUL (CLOSE)")
552
+ ;;
553
+ *)
554
+ echo "Invalid recommendation in .local/review.json: $recommendation"
555
+ exit 1
556
+ ;;
557
+ esac
558
+
559
+ local invalid_severity_count
560
+ invalid_severity_count=$(jq '[.findings[]? | select((.severity // "") != "BLOCKER" and (.severity // "") != "IMPORTANT" and (.severity // "") != "NIT")] | length' .local/review.json)
561
+ if [ "$invalid_severity_count" -gt 0 ]; then
562
+ echo "Invalid finding severity in .local/review.json"
563
+ exit 1
564
+ fi
565
+
566
+ local invalid_findings_count
567
+ invalid_findings_count=$(jq '[.findings[]? | select((.id|type)!="string" or (.title|type)!="string" or (.area|type)!="string" or (.fix|type)!="string")] | length' .local/review.json)
568
+ if [ "$invalid_findings_count" -gt 0 ]; then
569
+ echo "Invalid finding shape in .local/review.json (id/title/area/fix must be strings)"
570
+ exit 1
571
+ fi
572
+
573
+ local nit_findings_count
574
+ nit_findings_count=$(jq '[.findings[]? | select((.severity // "") == "NIT")] | length' .local/review.json)
575
+
576
+ local nit_sweep_performed
577
+ nit_sweep_performed=$(jq -r '.nitSweep.performed // empty' .local/review.json)
578
+ if [ "$nit_sweep_performed" != "true" ]; then
579
+ echo "Invalid nit sweep in .local/review.json: nitSweep.performed must be true"
580
+ exit 1
581
+ fi
582
+
583
+ local nit_sweep_status
584
+ nit_sweep_status=$(jq -r '.nitSweep.status // ""' .local/review.json)
585
+ case "$nit_sweep_status" in
586
+ "none")
587
+ if [ "$nit_findings_count" -gt 0 ]; then
588
+ echo "Invalid nit sweep in .local/review.json: nitSweep.status is none but NIT findings exist"
589
+ exit 1
590
+ fi
591
+ ;;
592
+ "has_nits")
593
+ if [ "$nit_findings_count" -lt 1 ]; then
594
+ echo "Invalid nit sweep in .local/review.json: nitSweep.status is has_nits but no NIT findings exist"
595
+ exit 1
596
+ fi
597
+ ;;
598
+ *)
599
+ echo "Invalid nit sweep status in .local/review.json: $nit_sweep_status"
600
+ exit 1
601
+ ;;
602
+ esac
603
+
604
+ local invalid_nit_summary_count
605
+ invalid_nit_summary_count=$(jq '[.nitSweep.summary | select((type != "string") or (gsub("^\\s+|\\s+$";"") | length == 0))] | length' .local/review.json)
606
+ if [ "$invalid_nit_summary_count" -gt 0 ]; then
607
+ echo "Invalid nit sweep summary in .local/review.json: nitSweep.summary must be a non-empty string"
608
+ exit 1
609
+ fi
610
+
611
+ local issue_validation_performed
612
+ issue_validation_performed=$(jq -r '.issueValidation.performed // empty' .local/review.json)
613
+ if [ "$issue_validation_performed" != "true" ]; then
614
+ echo "Invalid issue validation in .local/review.json: issueValidation.performed must be true"
615
+ exit 1
616
+ fi
617
+
618
+ local issue_validation_source
619
+ issue_validation_source=$(jq -r '.issueValidation.source // ""' .local/review.json)
620
+ case "$issue_validation_source" in
621
+ "linked_issue"|"pr_body"|"both")
622
+ ;;
623
+ *)
624
+ echo "Invalid issue validation source in .local/review.json: $issue_validation_source"
625
+ exit 1
626
+ ;;
627
+ esac
628
+
629
+ local issue_validation_status
630
+ issue_validation_status=$(jq -r '.issueValidation.status // ""' .local/review.json)
631
+ case "$issue_validation_status" in
632
+ "valid"|"unclear"|"invalid"|"already_fixed_on_main")
633
+ ;;
634
+ *)
635
+ echo "Invalid issue validation status in .local/review.json: $issue_validation_status"
636
+ exit 1
637
+ ;;
638
+ esac
639
+
640
+ local invalid_issue_summary_count
641
+ invalid_issue_summary_count=$(jq '[.issueValidation.summary | select((type != "string") or (gsub("^\\s+|\\s+$";"") | length == 0))] | length' .local/review.json)
642
+ if [ "$invalid_issue_summary_count" -gt 0 ]; then
643
+ echo "Invalid issue validation summary in .local/review.json: issueValidation.summary must be a non-empty string"
644
+ exit 1
645
+ fi
646
+
647
+ if [ "$recommendation" = "READY FOR /prepare-pr" ] && [ "$issue_validation_status" != "valid" ]; then
648
+ echo "Invalid recommendation in .local/review.json: READY FOR /prepare-pr requires issueValidation.status=valid"
649
+ exit 1
650
+ fi
651
+
652
+ local docs_status
653
+ docs_status=$(jq -r '.docs // ""' .local/review.json)
654
+ case "$docs_status" in
655
+ "up_to_date"|"missing"|"not_applicable")
656
+ ;;
657
+ *)
658
+ echo "Invalid docs status in .local/review.json: $docs_status"
659
+ exit 1
660
+ ;;
661
+ esac
662
+
663
+ local changelog_status
664
+ changelog_status=$(jq -r '.changelog // ""' .local/review.json)
665
+ case "$changelog_status" in
666
+ "required")
667
+ ;;
668
+ *)
669
+ echo "Invalid changelog status in .local/review.json: $changelog_status (must be \"required\")"
670
+ exit 1
671
+ ;;
672
+ esac
673
+
674
+ echo "review artifacts validated"
675
+ }
676
+
677
+ review_tests() {
678
+ local pr="$1"
679
+ shift
680
+ if [ "$#" -lt 1 ]; then
681
+ echo "Usage: scripts/pr review-tests <PR> <test-file> [<test-file> ...]"
682
+ exit 2
683
+ fi
684
+
685
+ enter_worktree "$pr" false
686
+ review_guard "$pr"
687
+
688
+ local target
689
+ for target in "$@"; do
690
+ if [ ! -f "$target" ]; then
691
+ echo "Missing test target file: $target"
692
+ exit 1
693
+ fi
694
+ done
695
+
696
+ bootstrap_deps_if_needed
697
+
698
+ local list_log=".local/review-tests-list.log"
699
+ run_quiet_logged "pnpm vitest list" "$list_log" pnpm vitest list "$@"
700
+
701
+ local missing_list=()
702
+ for target in "$@"; do
703
+ local base
704
+ base=$(basename "$target")
705
+ if ! rg -F -q "$target" "$list_log" && ! rg -F -q "$base" "$list_log"; then
706
+ missing_list+=("$target")
707
+ fi
708
+ done
709
+
710
+ if [ "${#missing_list[@]}" -gt 0 ]; then
711
+ echo "These requested targets were not selected by vitest list:"
712
+ printf ' - %s\n' "${missing_list[@]}"
713
+ exit 1
714
+ fi
715
+
716
+ local run_log=".local/review-tests-run.log"
717
+ run_quiet_logged "pnpm vitest run" "$run_log" pnpm vitest run "$@"
718
+
719
+ local missing_run=()
720
+ for target in "$@"; do
721
+ local base
722
+ base=$(basename "$target")
723
+ if ! rg -F -q "$target" "$run_log" && ! rg -F -q "$base" "$run_log"; then
724
+ missing_run+=("$target")
725
+ fi
726
+ done
727
+
728
+ if [ "${#missing_run[@]}" -gt 0 ]; then
729
+ echo "These requested targets were not observed in vitest run output:"
730
+ printf ' - %s\n' "${missing_run[@]}"
731
+ exit 1
732
+ fi
733
+
734
+ {
735
+ echo "REVIEW_TESTS_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
736
+ echo "REVIEW_TEST_TARGET_COUNT=$#"
737
+ } > .local/review-tests.env
738
+
739
+ echo "review tests passed and were observed in output"
740
+ }
741
+
742
+ review_init() {
743
+ local pr="$1"
744
+ enter_worktree "$pr" true
745
+
746
+ local json
747
+ json=$(pr_meta_json "$pr")
748
+ write_pr_meta_files "$json"
749
+
750
+ git fetch origin "pull/$pr/head:pr-$pr" --force
751
+ local mb
752
+ mb=$(git merge-base origin/main "pr-$pr")
753
+
754
+ cat > .local/review-context.env <<EOF_ENV
755
+ PR_NUMBER=$pr
756
+ MERGE_BASE=$mb
757
+ REVIEW_STARTED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
758
+ EOF_ENV
759
+ set_review_mode main
760
+
761
+ printf '%s\n' "$json" | jq '{number,title,url,state,isDraft,author:.author.login,base:.baseRefName,head:.headRefName,headSha:.headRefOid,headRepo:.headRepository.nameWithOwner,additions,deletions,files:(.files|length)}'
762
+ echo "worktree=$PWD"
763
+ echo "merge_base=$mb"
764
+ echo "branch=$(git branch --show-current)"
765
+ echo "wrote=.local/pr-meta.json .local/pr-meta.env .local/review-context.env .local/review-mode.env"
766
+ cat <<EOF_GUIDE
767
+ Review guidance:
768
+ - Inspect main baseline: scripts/pr review-checkout-main $pr
769
+ - Inspect PR head: scripts/pr review-checkout-pr $pr
770
+ - Guard before writeout: scripts/pr review-guard $pr
771
+ EOF_GUIDE
772
+ }
773
+
774
+ prepare_init() {
775
+ local pr="$1"
776
+ enter_worktree "$pr" true
777
+
778
+ require_artifact .local/pr-meta.env
779
+ require_artifact .local/review.md
780
+
781
+ if [ ! -s .local/review.json ]; then
782
+ echo "WARNING: .local/review.json is missing; structured findings are expected."
783
+ fi
784
+
785
+ # shellcheck disable=SC1091
786
+ source .local/pr-meta.env
787
+
788
+ local json
789
+ json=$(pr_meta_json "$pr")
790
+
791
+ local head
792
+ head=$(printf '%s\n' "$json" | jq -r .headRefName)
793
+ local pr_head_sha_before
794
+ pr_head_sha_before=$(printf '%s\n' "$json" | jq -r .headRefOid)
795
+
796
+ if [ -n "${PR_HEAD:-}" ] && [ "$head" != "$PR_HEAD" ]; then
797
+ echo "PR head branch changed from $PR_HEAD to $head. Re-run review-pr."
798
+ exit 1
799
+ fi
800
+
801
+ git fetch origin "pull/$pr/head:pr-$pr" --force
802
+ git checkout -B "pr-$pr-prep" "pr-$pr"
803
+ git fetch origin main
804
+ git rebase origin/main
805
+
806
+ cat > .local/prep-context.env <<EOF_ENV
807
+ PR_NUMBER=$pr
808
+ PR_HEAD=$head
809
+ PR_HEAD_SHA_BEFORE=$pr_head_sha_before
810
+ PREP_BRANCH=pr-$pr-prep
811
+ PREP_STARTED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
812
+ EOF_ENV
813
+
814
+ if [ ! -f .local/prep.md ]; then
815
+ cat > .local/prep.md <<EOF_PREP
816
+ # PR $pr prepare log
817
+
818
+ - Initialized prepare context and rebased prep branch on origin/main.
819
+ EOF_PREP
820
+ fi
821
+
822
+ echo "worktree=$PWD"
823
+ echo "branch=$(git branch --show-current)"
824
+ echo "wrote=.local/prep-context.env .local/prep.md"
825
+ }
826
+
827
+ prepare_validate_commit() {
828
+ local pr="$1"
829
+ enter_worktree "$pr" false
830
+ require_artifact .local/pr-meta.env
831
+
832
+ checkout_prep_branch "$pr"
833
+
834
+ # shellcheck disable=SC1091
835
+ source .local/pr-meta.env
836
+ local contrib="${PR_AUTHOR:-}"
837
+ local pr_number="${PR_NUMBER:-$pr}"
838
+
839
+ if [ -z "$contrib" ]; then
840
+ contrib=$(gh pr view "$pr" --json author --jq .author.login)
841
+ fi
842
+
843
+ local subject
844
+ subject=$(git log -1 --pretty=%s)
845
+
846
+ echo "$subject" | rg -q "openclaw#$pr_number" || {
847
+ echo "ERROR: commit subject missing openclaw#$pr_number"
848
+ exit 1
849
+ }
850
+
851
+ echo "$subject" | rg -q "thanks @$contrib" || {
852
+ echo "ERROR: commit subject missing thanks @$contrib"
853
+ exit 1
854
+ }
855
+
856
+ echo "commit subject validated: $subject"
857
+ }
858
+
859
+ validate_changelog_entry_for_pr() {
860
+ local pr="$1"
861
+ local contrib="$2"
862
+
863
+ local added_lines
864
+ added_lines=$(git diff --unified=0 origin/main...HEAD -- CHANGELOG.md | awk '
865
+ /^\+\+\+/ { next }
866
+ /^\+/ { print substr($0, 2) }
867
+ ')
868
+
869
+ if [ -z "$added_lines" ]; then
870
+ echo "CHANGELOG.md is in diff but no added lines were detected."
871
+ exit 1
872
+ fi
873
+
874
+ local pr_pattern
875
+ pr_pattern="(#$pr|openclaw#$pr)"
876
+
877
+ local with_pr
878
+ with_pr=$(printf '%s\n' "$added_lines" | rg -in "$pr_pattern" || true)
879
+ if [ -z "$with_pr" ]; then
880
+ echo "CHANGELOG.md update must reference PR #$pr (for example, (#$pr))."
881
+ exit 1
882
+ fi
883
+
884
+ if [ -n "$contrib" ] && [ "$contrib" != "null" ]; then
885
+ local with_pr_and_thanks
886
+ with_pr_and_thanks=$(printf '%s\n' "$added_lines" | rg -in "$pr_pattern" | rg -i "thanks @$contrib" || true)
887
+ if [ -z "$with_pr_and_thanks" ]; then
888
+ echo "CHANGELOG.md update must include both PR #$pr and thanks @$contrib on the changelog entry line."
889
+ exit 1
890
+ fi
891
+ echo "changelog validated: found PR #$pr + thanks @$contrib"
892
+ return 0
893
+ fi
894
+
895
+ echo "changelog validated: found PR #$pr (contributor handle unavailable, skipping thanks check)"
896
+ }
897
+
898
+ validate_changelog_merge_hygiene() {
899
+ local diff
900
+ diff=$(git diff --unified=0 origin/main...HEAD -- CHANGELOG.md)
901
+
902
+ local removed_lines
903
+ removed_lines=$(printf '%s\n' "$diff" | awk '
904
+ /^---/ { next }
905
+ /^-/ { print substr($0, 2) }
906
+ ')
907
+ if [ -z "$removed_lines" ]; then
908
+ return 0
909
+ fi
910
+
911
+ local removed_refs
912
+ removed_refs=$(printf '%s\n' "$removed_lines" | rg -o '#[0-9]+' | sort -u || true)
913
+ if [ -z "$removed_refs" ]; then
914
+ return 0
915
+ fi
916
+
917
+ local added_lines
918
+ added_lines=$(printf '%s\n' "$diff" | awk '
919
+ /^\+\+\+/ { next }
920
+ /^\+/ { print substr($0, 2) }
921
+ ')
922
+
923
+ local ref
924
+ while IFS= read -r ref; do
925
+ [ -z "$ref" ] && continue
926
+ if ! printf '%s\n' "$added_lines" | rg -q -F "$ref"; then
927
+ echo "CHANGELOG.md drops existing entry reference $ref without re-adding it."
928
+ echo "Likely merge conflict loss; restore the dropped entry (or keep the same PR ref in rewritten text)."
929
+ exit 1
930
+ fi
931
+ done <<<"$removed_refs"
932
+
933
+ echo "changelog merge hygiene validated: no dropped PR references"
934
+ }
935
+
936
+ changed_changelog_fragment_files() {
937
+ git diff --name-only origin/main...HEAD -- changelog/fragments | rg '^changelog/fragments/.*\.md$' || true
938
+ }
939
+
940
+ validate_changelog_fragments_for_pr() {
941
+ local pr="$1"
942
+ local contrib="$2"
943
+ shift 2
944
+
945
+ if [ "$#" -lt 1 ]; then
946
+ echo "No changelog fragments provided for validation."
947
+ exit 1
948
+ fi
949
+
950
+ local pr_pattern
951
+ pr_pattern="(#$pr|openclaw#$pr)"
952
+
953
+ local added_lines
954
+ local file
955
+ local all_added_lines=""
956
+ for file in "$@"; do
957
+ added_lines=$(git diff --unified=0 origin/main...HEAD -- "$file" | awk '
958
+ /^\+\+\+/ { next }
959
+ /^\+/ { print substr($0, 2) }
960
+ ')
961
+
962
+ if [ -z "$added_lines" ]; then
963
+ echo "$file is in diff but no added lines were detected."
964
+ exit 1
965
+ fi
966
+
967
+ all_added_lines=$(printf '%s\n%s\n' "$all_added_lines" "$added_lines")
968
+ done
969
+
970
+ local with_pr
971
+ with_pr=$(printf '%s\n' "$all_added_lines" | rg -in "$pr_pattern" || true)
972
+ if [ -z "$with_pr" ]; then
973
+ echo "Changelog fragment update must reference PR #$pr (for example, (#$pr))."
974
+ exit 1
975
+ fi
976
+
977
+ if [ -n "$contrib" ] && [ "$contrib" != "null" ]; then
978
+ local with_pr_and_thanks
979
+ with_pr_and_thanks=$(printf '%s\n' "$all_added_lines" | rg -in "$pr_pattern" | rg -i "thanks @$contrib" || true)
980
+ if [ -z "$with_pr_and_thanks" ]; then
981
+ echo "Changelog fragment update must include both PR #$pr and thanks @$contrib on the entry line."
982
+ exit 1
983
+ fi
984
+ echo "changelog fragments validated: found PR #$pr + thanks @$contrib"
985
+ return 0
986
+ fi
987
+
988
+ echo "changelog fragments validated: found PR #$pr (contributor handle unavailable, skipping thanks check)"
989
+ }
990
+
991
+ prepare_gates() {
992
+ local pr="$1"
993
+ enter_worktree "$pr" false
994
+
995
+ checkout_prep_branch "$pr"
996
+ bootstrap_deps_if_needed
997
+ require_artifact .local/pr-meta.env
998
+ # shellcheck disable=SC1091
999
+ source .local/pr-meta.env
1000
+
1001
+ local changed_files
1002
+ changed_files=$(git diff --name-only origin/main...HEAD)
1003
+ local non_docs
1004
+ non_docs=$(printf '%s\n' "$changed_files" | grep -Ev '^(docs/|README.*\.md$|CHANGELOG\.md$|.*\.md$|.*\.mdx$|mintlify\.json$|docs\.json$)' || true)
1005
+
1006
+ local docs_only=false
1007
+ if [ -n "$changed_files" ] && [ -z "$non_docs" ]; then
1008
+ docs_only=true
1009
+ fi
1010
+
1011
+ local has_changelog_update=false
1012
+ if printf '%s\n' "$changed_files" | rg -q '^CHANGELOG\.md$'; then
1013
+ has_changelog_update=true
1014
+ fi
1015
+ local fragment_files
1016
+ fragment_files=$(changed_changelog_fragment_files)
1017
+ local has_fragment_update=false
1018
+ if [ -n "$fragment_files" ]; then
1019
+ has_fragment_update=true
1020
+ fi
1021
+ # Enforce workflow policy: every prepared PR must include either CHANGELOG.md
1022
+ # or one or more changelog fragments.
1023
+ if [ "$has_changelog_update" = "false" ] && [ "$has_fragment_update" = "false" ]; then
1024
+ echo "Missing changelog update. Add CHANGELOG.md changes or changelog/fragments/*.md entry."
1025
+ exit 1
1026
+ fi
1027
+ local contrib="${PR_AUTHOR:-}"
1028
+ if [ "$has_changelog_update" = "true" ]; then
1029
+ validate_changelog_merge_hygiene
1030
+ validate_changelog_entry_for_pr "$pr" "$contrib"
1031
+ fi
1032
+ if [ "$has_fragment_update" = "true" ]; then
1033
+ mapfile -t fragment_file_list <<<"$fragment_files"
1034
+ validate_changelog_fragments_for_pr "$pr" "$contrib" "${fragment_file_list[@]}"
1035
+ fi
1036
+
1037
+ run_quiet_logged "pnpm build" ".local/gates-build.log" pnpm build
1038
+ run_quiet_logged "pnpm check" ".local/gates-check.log" pnpm check
1039
+
1040
+ if [ "$docs_only" = "true" ]; then
1041
+ echo "Docs-only change detected with high confidence; skipping pnpm test."
1042
+ else
1043
+ run_quiet_logged "pnpm test" ".local/gates-test.log" pnpm test
1044
+ fi
1045
+
1046
+ cat > .local/gates.env <<EOF_ENV
1047
+ PR_NUMBER=$pr
1048
+ DOCS_ONLY=$docs_only
1049
+ GATES_PASSED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
1050
+ EOF_ENV
1051
+
1052
+ echo "docs_only=$docs_only"
1053
+ echo "wrote=.local/gates.env"
1054
+ }
1055
+
1056
+ prepare_push() {
1057
+ local pr="$1"
1058
+ enter_worktree "$pr" false
1059
+
1060
+ require_artifact .local/pr-meta.env
1061
+ require_artifact .local/prep-context.env
1062
+ require_artifact .local/gates.env
1063
+
1064
+ checkout_prep_branch "$pr"
1065
+
1066
+ # shellcheck disable=SC1091
1067
+ source .local/pr-meta.env
1068
+ # shellcheck disable=SC1091
1069
+ source .local/prep-context.env
1070
+ # shellcheck disable=SC1091
1071
+ source .local/gates.env
1072
+
1073
+ local prep_head_sha
1074
+ prep_head_sha=$(git rev-parse HEAD)
1075
+
1076
+ local current_head
1077
+ current_head=$(gh pr view "$pr" --json headRefName --jq .headRefName)
1078
+ local lease_sha
1079
+ lease_sha=$(gh pr view "$pr" --json headRefOid --jq .headRefOid)
1080
+
1081
+ if [ "$current_head" != "$PR_HEAD" ]; then
1082
+ echo "PR head branch changed from $PR_HEAD to $current_head. Re-run prepare-init."
1083
+ exit 1
1084
+ fi
1085
+
1086
+ local push_url
1087
+ push_url=$(resolve_head_push_url) || {
1088
+ echo "Unable to resolve PR head repo push URL."
1089
+ exit 1
1090
+ }
1091
+
1092
+ # Always set prhead to the correct fork URL for this PR.
1093
+ # The remote is repo-level (shared across worktrees), so a previous
1094
+ # prepare-pr run for a different fork PR can leave a stale URL.
1095
+ git remote remove prhead 2>/dev/null || true
1096
+ git remote add prhead "$push_url"
1097
+
1098
+ local remote_sha
1099
+ remote_sha=$(git ls-remote prhead "refs/heads/$PR_HEAD" 2>/dev/null | awk '{print $1}' || true)
1100
+ if [ -z "$remote_sha" ]; then
1101
+ local https_url
1102
+ https_url=$(resolve_head_push_url_https 2>/dev/null) || true
1103
+ if [ -n "$https_url" ] && [ "$https_url" != "$push_url" ]; then
1104
+ echo "SSH remote failed; falling back to HTTPS..."
1105
+ git remote set-url prhead "$https_url"
1106
+ git remote set-url --push prhead "$https_url"
1107
+ push_url="$https_url"
1108
+ remote_sha=$(git ls-remote prhead "refs/heads/$PR_HEAD" 2>/dev/null | awk '{print $1}' || true)
1109
+ fi
1110
+ if [ -z "$remote_sha" ]; then
1111
+ echo "Remote branch refs/heads/$PR_HEAD not found on prhead"
1112
+ exit 1
1113
+ fi
1114
+ fi
1115
+
1116
+ local pushed_from_sha="$remote_sha"
1117
+ if [ "$remote_sha" = "$prep_head_sha" ]; then
1118
+ echo "Remote branch already at local prep HEAD; skipping push."
1119
+ else
1120
+ if [ "$remote_sha" != "$lease_sha" ]; then
1121
+ echo "Remote SHA $remote_sha differs from PR head SHA $lease_sha. Refreshing lease SHA from remote."
1122
+ lease_sha="$remote_sha"
1123
+ fi
1124
+ pushed_from_sha="$lease_sha"
1125
+ local push_output
1126
+ if ! push_output=$(git push --force-with-lease=refs/heads/$PR_HEAD:$lease_sha prhead HEAD:$PR_HEAD 2>&1); then
1127
+ echo "Push failed: $push_output"
1128
+
1129
+ # Check if this is a permission error (fork PR) vs a lease conflict.
1130
+ # Permission errors go straight to GraphQL; lease conflicts retry with rebase.
1131
+ if printf '%s' "$push_output" | grep -qiE '(permission|denied|403|forbidden)'; then
1132
+ echo "Permission denied on git push; trying GraphQL createCommitOnBranch fallback..."
1133
+ if [ -n "${PR_HEAD_OWNER:-}" ] && [ -n "${PR_HEAD_REPO_NAME:-}" ]; then
1134
+ local graphql_oid
1135
+ graphql_oid=$(graphql_push_to_fork "${PR_HEAD_OWNER}/${PR_HEAD_REPO_NAME}" "$PR_HEAD" "$lease_sha")
1136
+ prep_head_sha="$graphql_oid"
1137
+ else
1138
+ echo "Git push permission denied and no fork owner/repo info for GraphQL fallback."
1139
+ exit 1
1140
+ fi
1141
+ else
1142
+ echo "Lease push failed, retrying once with fresh PR head..."
1143
+
1144
+ lease_sha=$(gh pr view "$pr" --json headRefOid --jq .headRefOid)
1145
+ pushed_from_sha="$lease_sha"
1146
+
1147
+ git fetch origin "pull/$pr/head:pr-$pr-latest" --force
1148
+ git rebase "pr-$pr-latest"
1149
+ prep_head_sha=$(git rev-parse HEAD)
1150
+
1151
+ bootstrap_deps_if_needed
1152
+ run_quiet_logged "pnpm build (lease-retry)" ".local/lease-retry-build.log" pnpm build
1153
+ run_quiet_logged "pnpm check (lease-retry)" ".local/lease-retry-check.log" pnpm check
1154
+ if [ "${DOCS_ONLY:-false}" != "true" ]; then
1155
+ run_quiet_logged "pnpm test (lease-retry)" ".local/lease-retry-test.log" pnpm test
1156
+ fi
1157
+
1158
+ if ! git push --force-with-lease=refs/heads/$PR_HEAD:$lease_sha prhead HEAD:$PR_HEAD; then
1159
+ # Retry also failed — try GraphQL as last resort.
1160
+ if [ -n "${PR_HEAD_OWNER:-}" ] && [ -n "${PR_HEAD_REPO_NAME:-}" ]; then
1161
+ echo "Git push retry failed; trying GraphQL createCommitOnBranch fallback..."
1162
+ local graphql_oid
1163
+ graphql_oid=$(graphql_push_to_fork "${PR_HEAD_OWNER}/${PR_HEAD_REPO_NAME}" "$PR_HEAD" "$lease_sha")
1164
+ prep_head_sha="$graphql_oid"
1165
+ else
1166
+ echo "Git push failed and no fork owner/repo info for GraphQL fallback."
1167
+ exit 1
1168
+ fi
1169
+ fi
1170
+ fi
1171
+ fi
1172
+ fi
1173
+
1174
+ if ! wait_for_pr_head_sha "$pr" "$prep_head_sha" 8 3; then
1175
+ local observed_sha
1176
+ observed_sha=$(gh pr view "$pr" --json headRefOid --jq .headRefOid)
1177
+ echo "Pushed head SHA propagation timed out. expected=$prep_head_sha observed=$observed_sha"
1178
+ exit 1
1179
+ fi
1180
+
1181
+ local pr_head_sha_after
1182
+ pr_head_sha_after=$(gh pr view "$pr" --json headRefOid --jq .headRefOid)
1183
+
1184
+ git fetch origin main
1185
+ git fetch origin "pull/$pr/head:pr-$pr-verify" --force
1186
+ git merge-base --is-ancestor origin/main "pr-$pr-verify" || {
1187
+ echo "PR branch is behind main after push."
1188
+ exit 1
1189
+ }
1190
+ git branch -D "pr-$pr-verify" 2>/dev/null || true
1191
+
1192
+ local contrib="${PR_AUTHOR:-}"
1193
+ if [ -z "$contrib" ]; then
1194
+ contrib=$(gh pr view "$pr" --json author --jq .author.login)
1195
+ fi
1196
+ local contrib_id
1197
+ contrib_id=$(gh api "users/$contrib" --jq .id)
1198
+ local coauthor_email="${contrib_id}+${contrib}@users.noreply.github.com"
1199
+
1200
+ cat >> .local/prep.md <<EOF_PREP
1201
+ - Gates passed and push succeeded to branch $PR_HEAD.
1202
+ - Verified PR head SHA matches local prep HEAD.
1203
+ - Verified PR head contains origin/main.
1204
+ EOF_PREP
1205
+
1206
+ cat > .local/prep.env <<EOF_ENV
1207
+ PR_NUMBER=$PR_NUMBER
1208
+ PR_AUTHOR=$contrib
1209
+ PR_HEAD=$PR_HEAD
1210
+ PR_HEAD_SHA_BEFORE=$pushed_from_sha
1211
+ PREP_HEAD_SHA=$prep_head_sha
1212
+ COAUTHOR_EMAIL=$coauthor_email
1213
+ EOF_ENV
1214
+
1215
+ ls -la .local/prep.md .local/prep.env >/dev/null
1216
+
1217
+ echo "prepare-push complete"
1218
+ echo "prep_branch=$(git branch --show-current)"
1219
+ echo "prep_head_sha=$prep_head_sha"
1220
+ echo "pr_head_sha=$pr_head_sha_after"
1221
+ echo "artifacts=.local/prep.md .local/prep.env"
1222
+ }
1223
+
1224
+ prepare_sync_head() {
1225
+ local pr="$1"
1226
+ enter_worktree "$pr" false
1227
+
1228
+ require_artifact .local/pr-meta.env
1229
+ require_artifact .local/prep-context.env
1230
+
1231
+ checkout_prep_branch "$pr"
1232
+
1233
+ # shellcheck disable=SC1091
1234
+ source .local/pr-meta.env
1235
+ # shellcheck disable=SC1091
1236
+ source .local/prep-context.env
1237
+
1238
+ local prep_head_sha
1239
+ prep_head_sha=$(git rev-parse HEAD)
1240
+
1241
+ local current_head
1242
+ current_head=$(gh pr view "$pr" --json headRefName --jq .headRefName)
1243
+ local lease_sha
1244
+ lease_sha=$(gh pr view "$pr" --json headRefOid --jq .headRefOid)
1245
+
1246
+ if [ "$current_head" != "$PR_HEAD" ]; then
1247
+ echo "PR head branch changed from $PR_HEAD to $current_head. Re-run prepare-init."
1248
+ exit 1
1249
+ fi
1250
+
1251
+ local push_url
1252
+ push_url=$(resolve_head_push_url) || {
1253
+ echo "Unable to resolve PR head repo push URL."
1254
+ exit 1
1255
+ }
1256
+
1257
+ # Always set prhead to the correct fork URL for this PR.
1258
+ # The remote is repo-level (shared across worktrees), so a previous
1259
+ # run for a different fork PR can leave a stale URL.
1260
+ git remote remove prhead 2>/dev/null || true
1261
+ git remote add prhead "$push_url"
1262
+
1263
+ local remote_sha
1264
+ remote_sha=$(git ls-remote prhead "refs/heads/$PR_HEAD" 2>/dev/null | awk '{print $1}' || true)
1265
+ if [ -z "$remote_sha" ]; then
1266
+ local https_url
1267
+ https_url=$(resolve_head_push_url_https 2>/dev/null) || true
1268
+ if [ -n "$https_url" ] && [ "$https_url" != "$push_url" ]; then
1269
+ echo "SSH remote failed; falling back to HTTPS..."
1270
+ git remote set-url prhead "$https_url"
1271
+ git remote set-url --push prhead "$https_url"
1272
+ push_url="$https_url"
1273
+ remote_sha=$(git ls-remote prhead "refs/heads/$PR_HEAD" 2>/dev/null | awk '{print $1}' || true)
1274
+ fi
1275
+ if [ -z "$remote_sha" ]; then
1276
+ echo "Remote branch refs/heads/$PR_HEAD not found on prhead"
1277
+ exit 1
1278
+ fi
1279
+ fi
1280
+
1281
+ local pushed_from_sha="$remote_sha"
1282
+ if [ "$remote_sha" = "$prep_head_sha" ]; then
1283
+ echo "Remote branch already at local prep HEAD; skipping push."
1284
+ else
1285
+ if [ "$remote_sha" != "$lease_sha" ]; then
1286
+ echo "Remote SHA $remote_sha differs from PR head SHA $lease_sha. Refreshing lease SHA from remote."
1287
+ lease_sha="$remote_sha"
1288
+ fi
1289
+ pushed_from_sha="$lease_sha"
1290
+ local push_output
1291
+ if ! push_output=$(git push --force-with-lease=refs/heads/$PR_HEAD:$lease_sha prhead HEAD:$PR_HEAD 2>&1); then
1292
+ echo "Push failed: $push_output"
1293
+
1294
+ if printf '%s' "$push_output" | grep -qiE '(permission|denied|403|forbidden)'; then
1295
+ echo "Permission denied on git push; trying GraphQL createCommitOnBranch fallback..."
1296
+ if [ -n "${PR_HEAD_OWNER:-}" ] && [ -n "${PR_HEAD_REPO_NAME:-}" ]; then
1297
+ local graphql_oid
1298
+ graphql_oid=$(graphql_push_to_fork "${PR_HEAD_OWNER}/${PR_HEAD_REPO_NAME}" "$PR_HEAD" "$lease_sha")
1299
+ prep_head_sha="$graphql_oid"
1300
+ else
1301
+ echo "Git push permission denied and no fork owner/repo info for GraphQL fallback."
1302
+ exit 1
1303
+ fi
1304
+ else
1305
+ echo "Lease push failed, retrying once with fresh PR head lease..."
1306
+ lease_sha=$(gh pr view "$pr" --json headRefOid --jq .headRefOid)
1307
+ pushed_from_sha="$lease_sha"
1308
+
1309
+ if ! push_output=$(git push --force-with-lease=refs/heads/$PR_HEAD:$lease_sha prhead HEAD:$PR_HEAD 2>&1); then
1310
+ echo "Retry push failed: $push_output"
1311
+ if [ -n "${PR_HEAD_OWNER:-}" ] && [ -n "${PR_HEAD_REPO_NAME:-}" ]; then
1312
+ echo "Retry failed; trying GraphQL createCommitOnBranch fallback..."
1313
+ local graphql_oid
1314
+ graphql_oid=$(graphql_push_to_fork "${PR_HEAD_OWNER}/${PR_HEAD_REPO_NAME}" "$PR_HEAD" "$lease_sha")
1315
+ prep_head_sha="$graphql_oid"
1316
+ else
1317
+ echo "Git push failed and no fork owner/repo info for GraphQL fallback."
1318
+ exit 1
1319
+ fi
1320
+ fi
1321
+ fi
1322
+ fi
1323
+ fi
1324
+
1325
+ if ! wait_for_pr_head_sha "$pr" "$prep_head_sha" 8 3; then
1326
+ local observed_sha
1327
+ observed_sha=$(gh pr view "$pr" --json headRefOid --jq .headRefOid)
1328
+ echo "Pushed head SHA propagation timed out. expected=$prep_head_sha observed=$observed_sha"
1329
+ exit 1
1330
+ fi
1331
+
1332
+ local pr_head_sha_after
1333
+ pr_head_sha_after=$(gh pr view "$pr" --json headRefOid --jq .headRefOid)
1334
+
1335
+ git fetch origin main
1336
+ git fetch origin "pull/$pr/head:pr-$pr-verify" --force
1337
+ git merge-base --is-ancestor origin/main "pr-$pr-verify" || {
1338
+ echo "PR branch is behind main after push."
1339
+ exit 1
1340
+ }
1341
+ git branch -D "pr-$pr-verify" 2>/dev/null || true
1342
+
1343
+ local contrib="${PR_AUTHOR:-}"
1344
+ if [ -z "$contrib" ]; then
1345
+ contrib=$(gh pr view "$pr" --json author --jq .author.login)
1346
+ fi
1347
+ local contrib_id
1348
+ contrib_id=$(gh api "users/$contrib" --jq .id)
1349
+ local coauthor_email="${contrib_id}+${contrib}@users.noreply.github.com"
1350
+
1351
+ cat >> .local/prep.md <<EOF_PREP
1352
+ - Prep head sync completed to branch $PR_HEAD.
1353
+ - Verified PR head SHA matches local prep HEAD.
1354
+ - Verified PR head contains origin/main.
1355
+ - Note: prep sync flow does not re-run prepare gates.
1356
+ EOF_PREP
1357
+
1358
+ cat > .local/prep.env <<EOF_ENV
1359
+ PR_NUMBER=$PR_NUMBER
1360
+ PR_AUTHOR=$contrib
1361
+ PR_HEAD=$PR_HEAD
1362
+ PR_HEAD_SHA_BEFORE=$pushed_from_sha
1363
+ PREP_HEAD_SHA=$prep_head_sha
1364
+ COAUTHOR_EMAIL=$coauthor_email
1365
+ EOF_ENV
1366
+
1367
+ ls -la .local/prep.md .local/prep.env >/dev/null
1368
+
1369
+ echo "prepare-sync-head complete"
1370
+ echo "prep_branch=$(git branch --show-current)"
1371
+ echo "prep_head_sha=$prep_head_sha"
1372
+ echo "pr_head_sha=$pr_head_sha_after"
1373
+ echo "artifacts=.local/prep.md .local/prep.env"
1374
+ }
1375
+
1376
+ prepare_run() {
1377
+ local pr="$1"
1378
+ prepare_init "$pr"
1379
+ prepare_validate_commit "$pr"
1380
+ prepare_gates "$pr"
1381
+ prepare_push "$pr"
1382
+ echo "prepare-run complete for PR #$pr"
1383
+ }
1384
+
1385
+ merge_verify() {
1386
+ local pr="$1"
1387
+ enter_worktree "$pr" false
1388
+
1389
+ require_artifact .local/prep.env
1390
+ # shellcheck disable=SC1091
1391
+ source .local/prep.env
1392
+
1393
+ local json
1394
+ json=$(pr_meta_json "$pr")
1395
+ local is_draft
1396
+ is_draft=$(printf '%s\n' "$json" | jq -r .isDraft)
1397
+ if [ "$is_draft" = "true" ]; then
1398
+ echo "PR is draft."
1399
+ exit 1
1400
+ fi
1401
+ local pr_head_sha
1402
+ pr_head_sha=$(printf '%s\n' "$json" | jq -r .headRefOid)
1403
+
1404
+ if [ "$pr_head_sha" != "$PREP_HEAD_SHA" ]; then
1405
+ echo "PR head changed after prepare (expected $PREP_HEAD_SHA, got $pr_head_sha)."
1406
+ echo "Re-run prepare to refresh prep artifacts and gates: scripts/pr-prepare run $pr"
1407
+
1408
+ # Best-effort delta summary to show exactly what changed since PREP_HEAD_SHA.
1409
+ git fetch origin "pull/$pr/head" >/dev/null 2>&1 || true
1410
+ if git cat-file -e "${PREP_HEAD_SHA}^{commit}" 2>/dev/null && git cat-file -e "${pr_head_sha}^{commit}" 2>/dev/null; then
1411
+ echo "HEAD delta (expected...current):"
1412
+ git log --oneline --left-right "${PREP_HEAD_SHA}...${pr_head_sha}" | sed 's/^/ /' || true
1413
+ else
1414
+ echo "HEAD delta unavailable locally (could not resolve one of the SHAs)."
1415
+ fi
1416
+ exit 1
1417
+ fi
1418
+
1419
+ gh pr checks "$pr" --required --watch --fail-fast >.local/merge-checks-watch.log 2>&1 || true
1420
+ local checks_json
1421
+ local checks_err_file
1422
+ checks_err_file=$(mktemp)
1423
+ checks_json=$(gh pr checks "$pr" --required --json name,bucket,state 2>"$checks_err_file" || true)
1424
+ rm -f "$checks_err_file"
1425
+ if [ -z "$checks_json" ]; then
1426
+ checks_json='[]'
1427
+ fi
1428
+ local required_count
1429
+ required_count=$(printf '%s\n' "$checks_json" | jq 'length')
1430
+ if [ "$required_count" -eq 0 ]; then
1431
+ echo "No required checks configured for this PR."
1432
+ fi
1433
+ printf '%s\n' "$checks_json" | jq -r '.[] | "\(.bucket)\t\(.name)\t\(.state)"'
1434
+
1435
+ local failed_required
1436
+ failed_required=$(printf '%s\n' "$checks_json" | jq '[.[] | select(.bucket=="fail")] | length')
1437
+ local pending_required
1438
+ pending_required=$(printf '%s\n' "$checks_json" | jq '[.[] | select(.bucket=="pending")] | length')
1439
+
1440
+ if [ "$failed_required" -gt 0 ]; then
1441
+ echo "Required checks are failing."
1442
+ exit 1
1443
+ fi
1444
+
1445
+ if [ "$pending_required" -gt 0 ]; then
1446
+ echo "Required checks are still pending."
1447
+ exit 1
1448
+ fi
1449
+
1450
+ git fetch origin main
1451
+ git fetch origin "pull/$pr/head:pr-$pr" --force
1452
+ git merge-base --is-ancestor origin/main "pr-$pr" || {
1453
+ echo "PR branch is behind main."
1454
+ exit 1
1455
+ }
1456
+
1457
+ echo "merge-verify passed for PR #$pr"
1458
+ }
1459
+
1460
+ merge_run() {
1461
+ local pr="$1"
1462
+ enter_worktree "$pr" false
1463
+
1464
+ local required
1465
+ for required in .local/review.md .local/review.json .local/prep.md .local/prep.env; do
1466
+ require_artifact "$required"
1467
+ done
1468
+
1469
+ merge_verify "$pr"
1470
+ # shellcheck disable=SC1091
1471
+ source .local/prep.env
1472
+
1473
+ local pr_meta_json
1474
+ pr_meta_json=$(gh pr view "$pr" --json number,title,state,isDraft,author)
1475
+ local pr_title
1476
+ pr_title=$(printf '%s\n' "$pr_meta_json" | jq -r .title)
1477
+ local pr_number
1478
+ pr_number=$(printf '%s\n' "$pr_meta_json" | jq -r .number)
1479
+ local contrib
1480
+ contrib=$(printf '%s\n' "$pr_meta_json" | jq -r .author.login)
1481
+ local is_draft
1482
+ is_draft=$(printf '%s\n' "$pr_meta_json" | jq -r .isDraft)
1483
+ if [ "$is_draft" = "true" ]; then
1484
+ echo "PR is draft; stop."
1485
+ exit 1
1486
+ fi
1487
+
1488
+ local reviewer
1489
+ reviewer=$(gh api user --jq .login)
1490
+ local reviewer_id
1491
+ reviewer_id=$(gh api user --jq .id)
1492
+
1493
+ local contrib_coauthor_email="${COAUTHOR_EMAIL:-}"
1494
+ if [ -z "$contrib_coauthor_email" ] || [ "$contrib_coauthor_email" = "null" ]; then
1495
+ local contrib_id
1496
+ contrib_id=$(gh api "users/$contrib" --jq .id)
1497
+ contrib_coauthor_email="${contrib_id}+${contrib}@users.noreply.github.com"
1498
+ fi
1499
+
1500
+ local reviewer_email_candidates=()
1501
+ local reviewer_email_candidate
1502
+ while IFS= read -r reviewer_email_candidate; do
1503
+ [ -n "$reviewer_email_candidate" ] || continue
1504
+ reviewer_email_candidates+=("$reviewer_email_candidate")
1505
+ done < <(merge_author_email_candidates "$reviewer" "$reviewer_id")
1506
+ if [ "${#reviewer_email_candidates[@]}" -eq 0 ]; then
1507
+ echo "Unable to resolve a candidate merge author email for reviewer $reviewer"
1508
+ exit 1
1509
+ fi
1510
+
1511
+ local reviewer_email="${reviewer_email_candidates[0]}"
1512
+ local reviewer_coauthor_email="${reviewer_id}+${reviewer}@users.noreply.github.com"
1513
+
1514
+ cat > .local/merge-body.txt <<EOF_BODY
1515
+ Merged via squash.
1516
+
1517
+ Prepared head SHA: $PREP_HEAD_SHA
1518
+ Co-authored-by: $contrib <$contrib_coauthor_email>
1519
+ Co-authored-by: $reviewer <$reviewer_coauthor_email>
1520
+ Reviewed-by: @$reviewer
1521
+ EOF_BODY
1522
+
1523
+ run_merge_with_email() {
1524
+ local email="$1"
1525
+ local merge_output_file
1526
+ merge_output_file=$(mktemp)
1527
+ if gh pr merge "$pr" \
1528
+ --squash \
1529
+ --delete-branch \
1530
+ --match-head-commit "$PREP_HEAD_SHA" \
1531
+ --author-email "$email" \
1532
+ --subject "$pr_title (#$pr_number)" \
1533
+ --body-file .local/merge-body.txt \
1534
+ >"$merge_output_file" 2>&1
1535
+ then
1536
+ rm -f "$merge_output_file"
1537
+ return 0
1538
+ fi
1539
+
1540
+ MERGE_ERR_MSG=$(cat "$merge_output_file")
1541
+ print_relevant_log_excerpt "$merge_output_file"
1542
+ rm -f "$merge_output_file"
1543
+ return 1
1544
+ }
1545
+
1546
+ local MERGE_ERR_MSG=""
1547
+ local selected_merge_author_email="$reviewer_email"
1548
+ if ! run_merge_with_email "$selected_merge_author_email"; then
1549
+ if is_author_email_merge_error "$MERGE_ERR_MSG" && [ "${#reviewer_email_candidates[@]}" -ge 2 ]; then
1550
+ selected_merge_author_email="${reviewer_email_candidates[1]}"
1551
+ echo "Retrying merge once with fallback author email: $selected_merge_author_email"
1552
+ run_merge_with_email "$selected_merge_author_email" || {
1553
+ echo "Merge failed after fallback retry."
1554
+ exit 1
1555
+ }
1556
+ else
1557
+ echo "Merge failed."
1558
+ exit 1
1559
+ fi
1560
+ fi
1561
+
1562
+ local state
1563
+ state=$(gh pr view "$pr" --json state --jq .state)
1564
+ if [ "$state" != "MERGED" ]; then
1565
+ echo "Merge not finalized yet (state=$state), waiting up to 15 minutes..."
1566
+ local i
1567
+ for i in $(seq 1 90); do
1568
+ sleep 10
1569
+ state=$(gh pr view "$pr" --json state --jq .state)
1570
+ if [ "$state" = "MERGED" ]; then
1571
+ break
1572
+ fi
1573
+ done
1574
+ fi
1575
+
1576
+ if [ "$state" != "MERGED" ]; then
1577
+ echo "PR state is $state after waiting."
1578
+ exit 1
1579
+ fi
1580
+
1581
+ local merge_sha
1582
+ merge_sha=$(gh pr view "$pr" --json mergeCommit --jq '.mergeCommit.oid')
1583
+ if [ -z "$merge_sha" ] || [ "$merge_sha" = "null" ]; then
1584
+ echo "Merge commit SHA missing."
1585
+ exit 1
1586
+ fi
1587
+
1588
+ local commit_body
1589
+ commit_body=$(gh api repos/:owner/:repo/commits/"$merge_sha" --jq .commit.message)
1590
+ printf '%s\n' "$commit_body" | rg -q "^Co-authored-by: $contrib <" || { echo "Missing PR author co-author trailer"; exit 1; }
1591
+ printf '%s\n' "$commit_body" | rg -q "^Co-authored-by: $reviewer <" || { echo "Missing reviewer co-author trailer"; exit 1; }
1592
+
1593
+ local ok=0
1594
+ local comment_output=""
1595
+ local attempt
1596
+ for attempt in 1 2 3; do
1597
+ if comment_output=$(gh pr comment "$pr" -F - 2>&1 <<EOF_COMMENT
1598
+ Merged via squash.
1599
+
1600
+ - Prepared head SHA: $PREP_HEAD_SHA
1601
+ - Merge commit: $merge_sha
1602
+
1603
+ Thanks @$contrib!
1604
+ EOF_COMMENT
1605
+ ); then
1606
+ ok=1
1607
+ break
1608
+ fi
1609
+ sleep 2
1610
+ done
1611
+ [ "$ok" -eq 1 ] || { echo "Failed to post PR comment after retries"; exit 1; }
1612
+
1613
+ local comment_url=""
1614
+ comment_url=$(printf '%s\n' "$comment_output" | rg -o 'https://github.com/[^ ]+/pull/[0-9]+#issuecomment-[0-9]+' -m1 || true)
1615
+ if [ -z "$comment_url" ]; then
1616
+ comment_url="unresolved"
1617
+ fi
1618
+
1619
+ local root
1620
+ root=$(repo_root)
1621
+ cd "$root"
1622
+ git worktree remove ".worktrees/pr-$pr" --force
1623
+ git branch -D "temp/pr-$pr" 2>/dev/null || true
1624
+ git branch -D "pr-$pr" 2>/dev/null || true
1625
+ git branch -D "pr-$pr-prep" 2>/dev/null || true
1626
+
1627
+ local pr_url
1628
+ pr_url=$(gh pr view "$pr" --json url --jq .url)
1629
+
1630
+ echo "merge-run complete for PR #$pr"
1631
+ echo "merge commit: $merge_sha"
1632
+ echo "merge author email: $selected_merge_author_email"
1633
+ echo "completion comment: $comment_url"
1634
+ echo "$pr_url"
1635
+ }
1636
+
1637
+ main() {
1638
+ if [ "$#" -lt 2 ]; then
1639
+ usage
1640
+ exit 2
1641
+ fi
1642
+
1643
+ require_cmds
1644
+
1645
+ local cmd="${1-}"
1646
+ shift || true
1647
+ local pr="${1-}"
1648
+ shift || true
1649
+
1650
+ if [ -z "$cmd" ] || [ -z "$pr" ]; then
1651
+ usage
1652
+ exit 2
1653
+ fi
1654
+
1655
+ case "$cmd" in
1656
+ review-init)
1657
+ review_init "$pr"
1658
+ ;;
1659
+ review-checkout-main)
1660
+ review_checkout_main "$pr"
1661
+ ;;
1662
+ review-checkout-pr)
1663
+ review_checkout_pr "$pr"
1664
+ ;;
1665
+ review-guard)
1666
+ review_guard "$pr"
1667
+ ;;
1668
+ review-artifacts-init)
1669
+ review_artifacts_init "$pr"
1670
+ ;;
1671
+ review-validate-artifacts)
1672
+ review_validate_artifacts "$pr"
1673
+ ;;
1674
+ review-tests)
1675
+ review_tests "$pr" "$@"
1676
+ ;;
1677
+ prepare-init)
1678
+ prepare_init "$pr"
1679
+ ;;
1680
+ prepare-validate-commit)
1681
+ prepare_validate_commit "$pr"
1682
+ ;;
1683
+ prepare-gates)
1684
+ prepare_gates "$pr"
1685
+ ;;
1686
+ prepare-push)
1687
+ prepare_push "$pr"
1688
+ ;;
1689
+ prepare-sync-head)
1690
+ prepare_sync_head "$pr"
1691
+ ;;
1692
+ prepare-run)
1693
+ prepare_run "$pr"
1694
+ ;;
1695
+ merge-verify)
1696
+ merge_verify "$pr"
1697
+ ;;
1698
+ merge-run)
1699
+ merge_run "$pr"
1700
+ ;;
1701
+ *)
1702
+ usage
1703
+ exit 2
1704
+ ;;
1705
+ esac
1706
+ }
1707
+
1708
+ main "$@"