@ebrindley/build 0.0.0-stage → 1.42.1-rc28

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 (878) hide show
  1. package/.build-project/providers/catalog.json +13951 -0
  2. package/.build-project/providers/pricing.json +1978 -0
  3. package/.build-project/providers/registry.json +4103 -0
  4. package/.github/SECURITY.md +75 -0
  5. package/.nvmrc +1 -0
  6. package/CHANGELOG.md +661 -0
  7. package/CODE_OF_CONDUCT.md +44 -0
  8. package/CONTRIBUTING.md +24 -0
  9. package/INSTALL.md +623 -0
  10. package/LICENSE +21 -0
  11. package/README.md +118 -2
  12. package/THIRD_PARTY_NOTICES.txt +26 -0
  13. package/dist/ai-judge-UE5UOYOO.js +191 -0
  14. package/dist/allowlist-grounding-KPLN6NC5.js +16 -0
  15. package/dist/allowlist-utils-MZZKKXRD.js +31 -0
  16. package/dist/apply-completion-mode-EM37UZHB.js +110 -0
  17. package/dist/artifact-migrator-WPACNSEP.js +276 -0
  18. package/dist/ask-2OZ4JFQR.js +176 -0
  19. package/dist/auth-6UKBTJUQ.js +185 -0
  20. package/dist/auth-H5KJL6HL.js +165 -0
  21. package/dist/auth-LVJRJDMU.js +12 -0
  22. package/dist/auth-NHZOCNRR.js +94 -0
  23. package/dist/auth-PBXHWXGW.js +95 -0
  24. package/dist/auth-T7Y5DCAH.js +91 -0
  25. package/dist/auth-TVVKCHQW.js +13 -0
  26. package/dist/auth-UHB2UCSS.js +97 -0
  27. package/dist/auth-VACA3VUL.js +98 -0
  28. package/dist/auth-VVLC77S3.js +44 -0
  29. package/dist/auth-liveness-XO4AGDDD.js +53 -0
  30. package/dist/autoloop-cleanup-SI3VGAAR.js +106 -0
  31. package/dist/autoloop-evidence-gates-J5GG5YQB.js +39 -0
  32. package/dist/autoloop-gate-policy-SUNAEP6Q.js +169 -0
  33. package/dist/autoloop-helpers-VQGQNTT6.js +52 -0
  34. package/dist/autoloop-ledger-P6TIDP7C.js +96 -0
  35. package/dist/autoloop-ledger-subscriber-HFAGOZXH.js +161 -0
  36. package/dist/autoloop-run-defaults-GWGZBNT7.js +27 -0
  37. package/dist/autoloop-run-lock-GBWRM5A2.js +197 -0
  38. package/dist/autoloop-success-C32QVWOP.js +93 -0
  39. package/dist/autoloop-termination-summary-GI6IBQWD.js +58 -0
  40. package/dist/autoloop-trajectory-AMKSQISD.js +70 -0
  41. package/dist/autoloop-wall-clock-T7ZVIQOC.js +12 -0
  42. package/dist/automatic-branch-cleanup-R62X2C5O.js +49 -0
  43. package/dist/autonomous-loop-controller-2WL363LW.js +4386 -0
  44. package/dist/backend-YUTFDZXZ.js +30 -0
  45. package/dist/background-executor-Z3ODTMD7.js +260 -0
  46. package/dist/backlog-75QPEAMW.js +403 -0
  47. package/dist/backlog-execution-intent-GWUV5TUK.js +19 -0
  48. package/dist/backup-active-store-XHWLN34M.js +16 -0
  49. package/dist/backup-machine-stores-UI4LTJOY.js +251 -0
  50. package/dist/backup-telemetry-db-Z4ICYCNW.js +26 -0
  51. package/dist/branch-archive-manager-FMSQ7YJF.js +18 -0
  52. package/dist/branch-cleanup-manager-ST6FJGND.js +35 -0
  53. package/dist/build-gate-7NKWPY62.js +68 -0
  54. package/dist/build-root-S7DINODY.js +18 -0
  55. package/dist/change-summary-R6CB6VXJ.js +50 -0
  56. package/dist/chunk-24MNKKOM.js +173 -0
  57. package/dist/chunk-2H6VZFIO.js +235 -0
  58. package/dist/chunk-2JIX3EKV.js +39 -0
  59. package/dist/chunk-2KO7HVZM.js +106 -0
  60. package/dist/chunk-2L36U25O.js +100 -0
  61. package/dist/chunk-2NMNFUQA.js +475 -0
  62. package/dist/chunk-2OUAEH67.js +83 -0
  63. package/dist/chunk-2VLJUXJU.js +115 -0
  64. package/dist/chunk-2VUEDIUM.js +77 -0
  65. package/dist/chunk-2Y2IIHPI.js +430 -0
  66. package/dist/chunk-2YRG3LHZ.js +220 -0
  67. package/dist/chunk-2Z5Q3SUY.js +65 -0
  68. package/dist/chunk-32IYXIE5.js +873 -0
  69. package/dist/chunk-33IGPPUI.js +292 -0
  70. package/dist/chunk-352PCJLK.js +283 -0
  71. package/dist/chunk-35J4NE6S.js +226 -0
  72. package/dist/chunk-3C7WBIVK.js +1016 -0
  73. package/dist/chunk-3H3EY6U7.js +1225 -0
  74. package/dist/chunk-3H4MWYIC.js +198 -0
  75. package/dist/chunk-3HHWQR3N.js +2479 -0
  76. package/dist/chunk-3JKNRYEE.js +14 -0
  77. package/dist/chunk-3LECCYZY.js +128 -0
  78. package/dist/chunk-3LOY6YZI.js +852 -0
  79. package/dist/chunk-3M57DADF.js +44 -0
  80. package/dist/chunk-3NOTLVLG.js +3803 -0
  81. package/dist/chunk-3OPP2OZB.js +1480 -0
  82. package/dist/chunk-3P5IN5E7.js +9 -0
  83. package/dist/chunk-3PL22IYZ.js +20 -0
  84. package/dist/chunk-3REDAJUJ.js +14 -0
  85. package/dist/chunk-3S2R4XJI.js +203 -0
  86. package/dist/chunk-3UBZI5DW.js +61 -0
  87. package/dist/chunk-3VV5OBGG.js +308 -0
  88. package/dist/chunk-3WY5TU2O.js +823 -0
  89. package/dist/chunk-43OKHTW4.js +82 -0
  90. package/dist/chunk-45KUJBDI.js +112 -0
  91. package/dist/chunk-47X5LM7B.js +224 -0
  92. package/dist/chunk-4B6FQJ77.js +268 -0
  93. package/dist/chunk-4BTDUT2S.js +42 -0
  94. package/dist/chunk-4DJL2VXA.js +24 -0
  95. package/dist/chunk-4EBSXZ73.js +48 -0
  96. package/dist/chunk-4QKP6I4C.js +294 -0
  97. package/dist/chunk-4U6XK2DG.js +300 -0
  98. package/dist/chunk-4UMRUFBD.js +132 -0
  99. package/dist/chunk-4US4SYJ4.js +569 -0
  100. package/dist/chunk-4ZPLWEZO.js +154 -0
  101. package/dist/chunk-56WPYDJH.js +624 -0
  102. package/dist/chunk-57WRCJJW.js +50 -0
  103. package/dist/chunk-5AW4RMNG.js +24 -0
  104. package/dist/chunk-5EQN56OZ.js +155 -0
  105. package/dist/chunk-5F43TUIN.js +38 -0
  106. package/dist/chunk-5FKHIKTS.js +115 -0
  107. package/dist/chunk-5HDP7XZG.js +91 -0
  108. package/dist/chunk-5KQETTNS.js +152 -0
  109. package/dist/chunk-5PDVN2KO.js +498 -0
  110. package/dist/chunk-5UBIFVRD.js +509 -0
  111. package/dist/chunk-5YDE3Q62.js +46 -0
  112. package/dist/chunk-626AJVMU.js +302 -0
  113. package/dist/chunk-633XZ7UF.js +3583 -0
  114. package/dist/chunk-63DHNHGA.js +236 -0
  115. package/dist/chunk-666656AR.js +568 -0
  116. package/dist/chunk-67YRJPJO.js +19 -0
  117. package/dist/chunk-6CAU7VBA.js +20 -0
  118. package/dist/chunk-6CB3KJRY.js +21 -0
  119. package/dist/chunk-6DBMALXL.js +29 -0
  120. package/dist/chunk-6E25C4IA.js +269 -0
  121. package/dist/chunk-6FIBNIQU.js +73 -0
  122. package/dist/chunk-6G57FYBV.js +189 -0
  123. package/dist/chunk-6KZKRTG3.js +4885 -0
  124. package/dist/chunk-6LAH2UV2.js +525 -0
  125. package/dist/chunk-6PZY2TTU.js +921 -0
  126. package/dist/chunk-6T6NHXK4.js +230 -0
  127. package/dist/chunk-6TBEX4R4.js +432 -0
  128. package/dist/chunk-6TRD7TEE.js +911 -0
  129. package/dist/chunk-6XCP2RAI.js +47 -0
  130. package/dist/chunk-726G6WDL.js +351 -0
  131. package/dist/chunk-75EMT7BF.js +616 -0
  132. package/dist/chunk-762ACDZJ.js +4696 -0
  133. package/dist/chunk-77I6G4CO.js +1 -0
  134. package/dist/chunk-7DJ6SQH3.js +510 -0
  135. package/dist/chunk-7HPCIOMA.js +1383 -0
  136. package/dist/chunk-7HYBSRPZ.js +17503 -0
  137. package/dist/chunk-7KBEOPVB.js +73 -0
  138. package/dist/chunk-7LHJDJB3.js +2222 -0
  139. package/dist/chunk-7NVJMH52.js +306 -0
  140. package/dist/chunk-7RMARDVH.js +33 -0
  141. package/dist/chunk-7SEWU4QG.js +36 -0
  142. package/dist/chunk-7TUTZTI2.js +911 -0
  143. package/dist/chunk-7VBDFPUM.js +340 -0
  144. package/dist/chunk-7VRJHTGZ.js +6406 -0
  145. package/dist/chunk-7YAOUUKK.js +493 -0
  146. package/dist/chunk-AAAODV76.js +860 -0
  147. package/dist/chunk-AB4HUJVQ.js +339 -0
  148. package/dist/chunk-AEQWV746.js +611 -0
  149. package/dist/chunk-AHU3FKPA.js +11639 -0
  150. package/dist/chunk-AISTV5XU.js +226 -0
  151. package/dist/chunk-AJBTKGJZ.js +48 -0
  152. package/dist/chunk-AJJNW7XT.js +248 -0
  153. package/dist/chunk-AJN5Y5YZ.js +160 -0
  154. package/dist/chunk-ALDTFNZ3.js +150 -0
  155. package/dist/chunk-ARARVIZK.js +281 -0
  156. package/dist/chunk-ATBM6AW5.js +53 -0
  157. package/dist/chunk-ATKEKM3E.js +206 -0
  158. package/dist/chunk-AUJLW6JZ.js +92 -0
  159. package/dist/chunk-AY3MHEFM.js +24 -0
  160. package/dist/chunk-B7PYTV72.js +236 -0
  161. package/dist/chunk-B7YRSULE.js +1423 -0
  162. package/dist/chunk-BAWJNMWF.js +339 -0
  163. package/dist/chunk-BBPIILO2.js +88 -0
  164. package/dist/chunk-BD3PGW6A.js +957 -0
  165. package/dist/chunk-BDPYFAB4.js +44 -0
  166. package/dist/chunk-BIUV23HH.js +729 -0
  167. package/dist/chunk-BQ3VAGXH.js +243 -0
  168. package/dist/chunk-BQJOGAWT.js +31 -0
  169. package/dist/chunk-BT7EG7CO.js +68 -0
  170. package/dist/chunk-BV3XQQLU.js +27 -0
  171. package/dist/chunk-BY6ODHNZ.js +78 -0
  172. package/dist/chunk-BZRLSZNV.js +79 -0
  173. package/dist/chunk-C3DH3NCY.js +2773 -0
  174. package/dist/chunk-C3KCMAJP.js +756 -0
  175. package/dist/chunk-C7AGUJ5K.js +267 -0
  176. package/dist/chunk-CBSWKQCX.js +342 -0
  177. package/dist/chunk-CE5OZBYY.js +21 -0
  178. package/dist/chunk-CFCVYHSW.js +373 -0
  179. package/dist/chunk-CHA2HDE5.js +126 -0
  180. package/dist/chunk-CIHWPGS5.js +146 -0
  181. package/dist/chunk-CJBSZX3F.js +127 -0
  182. package/dist/chunk-CLA6QIJM.js +43 -0
  183. package/dist/chunk-CPSVQR2I.js +365 -0
  184. package/dist/chunk-CRPRVNQ4.js +254 -0
  185. package/dist/chunk-CSIE4Y7C.js +1093 -0
  186. package/dist/chunk-CSOPSYDY.js +142 -0
  187. package/dist/chunk-CSXLLEGB.js +205 -0
  188. package/dist/chunk-CTDLERWU.js +293 -0
  189. package/dist/chunk-CULBXMSK.js +110 -0
  190. package/dist/chunk-D2M2Y5ID.js +21 -0
  191. package/dist/chunk-D37WY7SH.js +1060 -0
  192. package/dist/chunk-D4VJFCGZ.js +89 -0
  193. package/dist/chunk-D6WTZMZB.js +48 -0
  194. package/dist/chunk-DBHOWDUH.js +976 -0
  195. package/dist/chunk-DCACX7BV.js +19364 -0
  196. package/dist/chunk-DFHX4XYA.js +128 -0
  197. package/dist/chunk-DIKBRRKE.js +176 -0
  198. package/dist/chunk-DJG5RIOR.js +9 -0
  199. package/dist/chunk-DNIOIISW.js +616 -0
  200. package/dist/chunk-DOA5G7IA.js +315 -0
  201. package/dist/chunk-DP7FKP6W.js +855 -0
  202. package/dist/chunk-DS2JZ677.js +79 -0
  203. package/dist/chunk-DSRLVYBS.js +82 -0
  204. package/dist/chunk-EAEVKNJA.js +209 -0
  205. package/dist/chunk-EEUESN3T.js +575 -0
  206. package/dist/chunk-EJ4LUU5Y.js +73 -0
  207. package/dist/chunk-EJIWTPSZ.js +1063 -0
  208. package/dist/chunk-EJVCDTAN.js +116 -0
  209. package/dist/chunk-EKLUBDBQ.js +627 -0
  210. package/dist/chunk-EN62WLFD.js +849 -0
  211. package/dist/chunk-ENYRCL43.js +26 -0
  212. package/dist/chunk-EP22ZMFO.js +273 -0
  213. package/dist/chunk-EP4SVAVI.js +87 -0
  214. package/dist/chunk-EPAMVGI3.js +1562 -0
  215. package/dist/chunk-EPRMH6HO.js +24 -0
  216. package/dist/chunk-ES6NWJ36.js +4057 -0
  217. package/dist/chunk-ETA2K4KI.js +30 -0
  218. package/dist/chunk-ETAD4NKD.js +31 -0
  219. package/dist/chunk-EUWIIDQ7.js +93 -0
  220. package/dist/chunk-EVE3OD2W.js +141 -0
  221. package/dist/chunk-EXA26FFC.js +459 -0
  222. package/dist/chunk-EXSPAA35.js +244 -0
  223. package/dist/chunk-F55YABKV.js +258 -0
  224. package/dist/chunk-F5DNGU5W.js +27 -0
  225. package/dist/chunk-F622MJYV.js +103 -0
  226. package/dist/chunk-F6NIQD74.js +5111 -0
  227. package/dist/chunk-FAWJTC34.js +43 -0
  228. package/dist/chunk-FAZBRHGG.js +47 -0
  229. package/dist/chunk-FIEUSGNJ.js +39 -0
  230. package/dist/chunk-FKCG7K6H.js +1146 -0
  231. package/dist/chunk-FOVVXV45.js +229 -0
  232. package/dist/chunk-FRBPKBFE.js +30 -0
  233. package/dist/chunk-FSA5BPFI.js +268 -0
  234. package/dist/chunk-FSJ5TTZ3.js +29 -0
  235. package/dist/chunk-FT7SGLXT.js +193 -0
  236. package/dist/chunk-FUUVZZUB.js +62 -0
  237. package/dist/chunk-FYMRVUNZ.js +1697 -0
  238. package/dist/chunk-FYTAA5FG.js +162 -0
  239. package/dist/chunk-G3VB2UCZ.js +3892 -0
  240. package/dist/chunk-G6OIMVXH.js +405 -0
  241. package/dist/chunk-G7PQCXVH.js +228 -0
  242. package/dist/chunk-GAB6UW52.js +1087 -0
  243. package/dist/chunk-GAFJEYLK.js +29 -0
  244. package/dist/chunk-GC4CRMTS.js +642 -0
  245. package/dist/chunk-GFHSIWHL.js +5297 -0
  246. package/dist/chunk-GI5M67YA.js +735 -0
  247. package/dist/chunk-GIJWHUWF.js +191 -0
  248. package/dist/chunk-GO6DRECI.js +1 -0
  249. package/dist/chunk-GOFVBTMN.js +314 -0
  250. package/dist/chunk-GQIP72ZJ.js +13 -0
  251. package/dist/chunk-GQXLXRFA.js +28 -0
  252. package/dist/chunk-GT6HFKJB.js +48 -0
  253. package/dist/chunk-GVJGSSKM.js +174 -0
  254. package/dist/chunk-GWHGTG37.js +36 -0
  255. package/dist/chunk-GZAJHLKO.js +18 -0
  256. package/dist/chunk-GZK62I3N.js +314 -0
  257. package/dist/chunk-H3HGMISG.js +483 -0
  258. package/dist/chunk-H3ISZJMJ.js +403 -0
  259. package/dist/chunk-H4HXVWEC.js +9 -0
  260. package/dist/chunk-H6MJGKAW.js +120 -0
  261. package/dist/chunk-HBTTGVR5.js +353 -0
  262. package/dist/chunk-HBZ322WZ.js +35 -0
  263. package/dist/chunk-HEP3ILP4.js +34 -0
  264. package/dist/chunk-HGFXODKO.js +106 -0
  265. package/dist/chunk-HHWDTB6S.js +59 -0
  266. package/dist/chunk-HKO45NLZ.js +286 -0
  267. package/dist/chunk-HLVTEA6A.js +89 -0
  268. package/dist/chunk-HS3SY6HN.js +160 -0
  269. package/dist/chunk-HT5OCGKN.js +899 -0
  270. package/dist/chunk-HXIIEMGF.js +78 -0
  271. package/dist/chunk-I56TH7VM.js +46 -0
  272. package/dist/chunk-I6FN3R5D.js +352 -0
  273. package/dist/chunk-I7SCBHJN.js +655 -0
  274. package/dist/chunk-IJM4GKAT.js +529 -0
  275. package/dist/chunk-IMEYU36R.js +344 -0
  276. package/dist/chunk-INJ3SS6A.js +936 -0
  277. package/dist/chunk-IUKOPCCE.js +441 -0
  278. package/dist/chunk-IVEPZQJO.js +33 -0
  279. package/dist/chunk-IVZEONFC.js +43 -0
  280. package/dist/chunk-IWBHRSG7.js +936 -0
  281. package/dist/chunk-IYY3HQAD.js +484 -0
  282. package/dist/chunk-J2ICMYDA.js +48 -0
  283. package/dist/chunk-J2QCFLUN.js +505 -0
  284. package/dist/chunk-J5HM6V5P.js +1039 -0
  285. package/dist/chunk-J6NZX6QH.js +398 -0
  286. package/dist/chunk-JBBB2Y47.js +432 -0
  287. package/dist/chunk-JDCZQZU7.js +42 -0
  288. package/dist/chunk-JEDNXGSS.js +169 -0
  289. package/dist/chunk-JHP7OT6K.js +89 -0
  290. package/dist/chunk-JLS65NUQ.js +14 -0
  291. package/dist/chunk-JNMWYMC6.js +38 -0
  292. package/dist/chunk-JNSTWNNW.js +25 -0
  293. package/dist/chunk-JOSK6GEU.js +9564 -0
  294. package/dist/chunk-JQUMSIGC.js +100 -0
  295. package/dist/chunk-JVHGMURQ.js +1106 -0
  296. package/dist/chunk-JYXS47FQ.js +951 -0
  297. package/dist/chunk-K2ZMW2Z5.js +2147 -0
  298. package/dist/chunk-K7FAFORM.js +14 -0
  299. package/dist/chunk-KBJXX3ON.js +333 -0
  300. package/dist/chunk-KHFKVQWY.js +486 -0
  301. package/dist/chunk-KHMEI3DQ.js +173 -0
  302. package/dist/chunk-KHW3UD7B.js +69 -0
  303. package/dist/chunk-KKQKWAMF.js +365 -0
  304. package/dist/chunk-KOKXJV2U.js +455 -0
  305. package/dist/chunk-KQ4NGOJI.js +14 -0
  306. package/dist/chunk-KQYQOXMS.js +342 -0
  307. package/dist/chunk-KTMW65UX.js +14 -0
  308. package/dist/chunk-KUEWV2JH.js +593 -0
  309. package/dist/chunk-KXYDV3HP.js +1523 -0
  310. package/dist/chunk-KZ4JMGZ5.js +96 -0
  311. package/dist/chunk-KZWGBUIO.js +2659 -0
  312. package/dist/chunk-L2CBG5IX.js +1892 -0
  313. package/dist/chunk-L2S235CU.js +314 -0
  314. package/dist/chunk-L32WD57F.js +98 -0
  315. package/dist/chunk-LDJCKAJP.js +123 -0
  316. package/dist/chunk-LDWNBEN7.js +541 -0
  317. package/dist/chunk-LJVH57G2.js +241 -0
  318. package/dist/chunk-LPQ7NZEN.js +209 -0
  319. package/dist/chunk-LRGH3CCV.js +148 -0
  320. package/dist/chunk-LULOGMMM.js +1133 -0
  321. package/dist/chunk-LWGKN2GY.js +8284 -0
  322. package/dist/chunk-LX4F24ZH.js +157 -0
  323. package/dist/chunk-M4BUEXXF.js +839 -0
  324. package/dist/chunk-M6UHOC4I.js +553 -0
  325. package/dist/chunk-MCBNWZNO.js +657 -0
  326. package/dist/chunk-MEMSY6VY.js +223 -0
  327. package/dist/chunk-MHLEZQQI.js +145 -0
  328. package/dist/chunk-MJ6WNSIX.js +192 -0
  329. package/dist/chunk-MKASIKWZ.js +442 -0
  330. package/dist/chunk-MNOVBYFU.js +60 -0
  331. package/dist/chunk-MO2BYAAF.js +2896 -0
  332. package/dist/chunk-MP6P7Y2A.js +180 -0
  333. package/dist/chunk-MQ4TFJAG.js +3365 -0
  334. package/dist/chunk-MT2WAIR3.js +131 -0
  335. package/dist/chunk-MTU7VTNN.js +1122 -0
  336. package/dist/chunk-MULMXX4V.js +150 -0
  337. package/dist/chunk-MW2DWWVA.js +147 -0
  338. package/dist/chunk-MY3ZPUN4.js +151 -0
  339. package/dist/chunk-MYLH2S4G.js +64 -0
  340. package/dist/chunk-MZ5WYKNA.js +28 -0
  341. package/dist/chunk-N3Q2JNPV.js +249 -0
  342. package/dist/chunk-N5DX4JAK.js +12 -0
  343. package/dist/chunk-NAINGKHF.js +35 -0
  344. package/dist/chunk-NEQUQHOX.js +223 -0
  345. package/dist/chunk-NHNPEZS4.js +1408 -0
  346. package/dist/chunk-NHTIXJCR.js +17 -0
  347. package/dist/chunk-NKLPMBNV.js +1154 -0
  348. package/dist/chunk-NMVZ5YES.js +199 -0
  349. package/dist/chunk-NRFRY3GE.js +256 -0
  350. package/dist/chunk-NTD6CSI6.js +1266 -0
  351. package/dist/chunk-NUO7B3SH.js +318 -0
  352. package/dist/chunk-NVOHIJKG.js +1319 -0
  353. package/dist/chunk-NXH3RX7O.js +523 -0
  354. package/dist/chunk-NXZ3DURF.js +257 -0
  355. package/dist/chunk-O26L6F7F.js +209 -0
  356. package/dist/chunk-O5RRIUEP.js +124 -0
  357. package/dist/chunk-O5X6BKAO.js +10 -0
  358. package/dist/chunk-O6ZQUVTR.js +31 -0
  359. package/dist/chunk-O7FE75QZ.js +306 -0
  360. package/dist/chunk-OC7Z6AJQ.js +81 -0
  361. package/dist/chunk-OH7IEZ63.js +307 -0
  362. package/dist/chunk-OI734GRS.js +115 -0
  363. package/dist/chunk-OJBH62DK.js +684 -0
  364. package/dist/chunk-OKJLVSTJ.js +247 -0
  365. package/dist/chunk-OMX5VL43.js +79 -0
  366. package/dist/chunk-ONHAMBN6.js +12 -0
  367. package/dist/chunk-ONYPLIVE.js +753 -0
  368. package/dist/chunk-OSK7QWPD.js +637 -0
  369. package/dist/chunk-OTXLMJKP.js +438 -0
  370. package/dist/chunk-OV5PKQMA.js +213 -0
  371. package/dist/chunk-OXK5HA4B.js +31 -0
  372. package/dist/chunk-OYDWFQA4.js +272 -0
  373. package/dist/chunk-OZAUKWHB.js +22 -0
  374. package/dist/chunk-P5P2EKGH.js +94 -0
  375. package/dist/chunk-P6SSWAKY.js +29 -0
  376. package/dist/chunk-P7CQGPLS.js +30 -0
  377. package/dist/chunk-P7RVCHQV.js +197 -0
  378. package/dist/chunk-PEBWDYIW.js +726 -0
  379. package/dist/chunk-PFCXFO7A.js +9193 -0
  380. package/dist/chunk-PFLDWMX6.js +168 -0
  381. package/dist/chunk-PHZGLQSK.js +15883 -0
  382. package/dist/chunk-PNFTPNSR.js +20 -0
  383. package/dist/chunk-PPHXX4W7.js +175 -0
  384. package/dist/chunk-PPZESPJT.js +262 -0
  385. package/dist/chunk-PRDHI2L5.js +296 -0
  386. package/dist/chunk-PSTKWHHN.js +32 -0
  387. package/dist/chunk-PV53ODOT.js +555 -0
  388. package/dist/chunk-PVJBTI7P.js +24 -0
  389. package/dist/chunk-PXKERBKX.js +100 -0
  390. package/dist/chunk-PZ5AY32C.js +10 -0
  391. package/dist/chunk-Q3DHEZA5.js +85 -0
  392. package/dist/chunk-Q5RZDVQK.js +204 -0
  393. package/dist/chunk-Q64IR2Z2.js +4933 -0
  394. package/dist/chunk-Q6Z5UGPL.js +398 -0
  395. package/dist/chunk-QAMAV4DT.js +627 -0
  396. package/dist/chunk-QBWF4444.js +112 -0
  397. package/dist/chunk-QDKYT4CI.js +22805 -0
  398. package/dist/chunk-QFLBRWCJ.js +126 -0
  399. package/dist/chunk-QILAQACE.js +107 -0
  400. package/dist/chunk-QJ6OEH4K.js +3683 -0
  401. package/dist/chunk-QK3NLYTV.js +320 -0
  402. package/dist/chunk-QOFLUS24.js +535 -0
  403. package/dist/chunk-QOIASNEJ.js +98 -0
  404. package/dist/chunk-QP4UTSOV.js +3587 -0
  405. package/dist/chunk-QP5ST7RI.js +904 -0
  406. package/dist/chunk-QS5AGRZN.js +167 -0
  407. package/dist/chunk-QTAOU53N.js +107 -0
  408. package/dist/chunk-QTCLJHLZ.js +351 -0
  409. package/dist/chunk-QTOLTG4L.js +119 -0
  410. package/dist/chunk-QWDV4OSL.js +142 -0
  411. package/dist/chunk-QWMQ74KD.js +508 -0
  412. package/dist/chunk-QX6LOMMH.js +158 -0
  413. package/dist/chunk-QXBKIHY7.js +238 -0
  414. package/dist/chunk-QXBKTJT6.js +270 -0
  415. package/dist/chunk-QYQOVOLR.js +196 -0
  416. package/dist/chunk-R2CKS6WT.js +73 -0
  417. package/dist/chunk-R2TIYG7U.js +68 -0
  418. package/dist/chunk-R2W3LESH.js +465 -0
  419. package/dist/chunk-R5EXHMUN.js +100 -0
  420. package/dist/chunk-RBBTBCHX.js +289 -0
  421. package/dist/chunk-RBKLOUXA.js +121 -0
  422. package/dist/chunk-RCP2Y6DQ.js +86 -0
  423. package/dist/chunk-RCYEJSD5.js +260 -0
  424. package/dist/chunk-RFDVSI4N.js +134 -0
  425. package/dist/chunk-RGJU63FV.js +1717 -0
  426. package/dist/chunk-RI4EX2QE.js +18 -0
  427. package/dist/chunk-RIMARPCF.js +152 -0
  428. package/dist/chunk-RJ6RSNT6.js +42 -0
  429. package/dist/chunk-RNI2GUIF.js +131 -0
  430. package/dist/chunk-RPHUWJ3K.js +264 -0
  431. package/dist/chunk-RPIMAMV7.js +361 -0
  432. package/dist/chunk-RQFX6YRN.js +49 -0
  433. package/dist/chunk-RRC5BL7B.js +285 -0
  434. package/dist/chunk-RSYTSBOO.js +79 -0
  435. package/dist/chunk-RWQQIKIC.js +35 -0
  436. package/dist/chunk-RZUBOZH2.js +373 -0
  437. package/dist/chunk-S2E632FQ.js +380 -0
  438. package/dist/chunk-S2VQCZO4.js +22 -0
  439. package/dist/chunk-S2XIMQJQ.js +681 -0
  440. package/dist/chunk-S47ISY27.js +183 -0
  441. package/dist/chunk-S6I4KISU.js +142 -0
  442. package/dist/chunk-S6TAFQI5.js +2403 -0
  443. package/dist/chunk-SBBNT5ZJ.js +27 -0
  444. package/dist/chunk-SDJV2DTQ.js +491 -0
  445. package/dist/chunk-SDPIFNLD.js +33 -0
  446. package/dist/chunk-SF6YK3JI.js +835 -0
  447. package/dist/chunk-SK3ZFONO.js +976 -0
  448. package/dist/chunk-SKTFRFQ7.js +1775 -0
  449. package/dist/chunk-SNGM452K.js +2002 -0
  450. package/dist/chunk-STW7OFOA.js +581 -0
  451. package/dist/chunk-SU6UFUQY.js +1351 -0
  452. package/dist/chunk-SVJM3LJA.js +27 -0
  453. package/dist/chunk-SVUNZOPG.js +129 -0
  454. package/dist/chunk-T5XLUYV3.js +34 -0
  455. package/dist/chunk-T7E5JS6N.js +57 -0
  456. package/dist/chunk-TA3N5MMO.js +450 -0
  457. package/dist/chunk-TAJPCXSB.js +13 -0
  458. package/dist/chunk-TDEMDRN2.js +1639 -0
  459. package/dist/chunk-TDSEX5CF.js +24 -0
  460. package/dist/chunk-TGCVL74Y.js +28 -0
  461. package/dist/chunk-TGXXYDL4.js +1012 -0
  462. package/dist/chunk-TIKTYUJO.js +296 -0
  463. package/dist/chunk-TIZAJD4M.js +6029 -0
  464. package/dist/chunk-TLSEZZCG.js +5549 -0
  465. package/dist/chunk-TMBDRHAX.js +19 -0
  466. package/dist/chunk-TRURQUBP.js +1217 -0
  467. package/dist/chunk-TSQ256N5.js +152 -0
  468. package/dist/chunk-TUQMMFEQ.js +2244 -0
  469. package/dist/chunk-U424MJLC.js +81 -0
  470. package/dist/chunk-U4DRS6ZW.js +11 -0
  471. package/dist/chunk-U5KS22IN.js +204 -0
  472. package/dist/chunk-UAPHFJPN.js +673 -0
  473. package/dist/chunk-UB7LQITD.js +1040 -0
  474. package/dist/chunk-UBVRHGAZ.js +237 -0
  475. package/dist/chunk-UDNNAQN5.js +98 -0
  476. package/dist/chunk-UJP3XM54.js +342 -0
  477. package/dist/chunk-UJYDH2MW.js +305 -0
  478. package/dist/chunk-UKMPASIC.js +399 -0
  479. package/dist/chunk-UKUL3EHK.js +763 -0
  480. package/dist/chunk-UPHU64NB.js +1327 -0
  481. package/dist/chunk-URMSZXAD.js +43 -0
  482. package/dist/chunk-US3RT6CY.js +525 -0
  483. package/dist/chunk-UTDEOPKV.js +30 -0
  484. package/dist/chunk-UUUDKV7N.js +13 -0
  485. package/dist/chunk-UVKWU3SM.js +39 -0
  486. package/dist/chunk-V2TVPO3X.js +521 -0
  487. package/dist/chunk-V3KLVK7B.js +398 -0
  488. package/dist/chunk-V3YFFHDR.js +17 -0
  489. package/dist/chunk-VAG6GUBR.js +1162 -0
  490. package/dist/chunk-VBENAHDA.js +1814 -0
  491. package/dist/chunk-VBNCKCCI.js +16 -0
  492. package/dist/chunk-VCGSWN5M.js +1300 -0
  493. package/dist/chunk-VE2O2I7M.js +115 -0
  494. package/dist/chunk-VIFHYTI5.js +31 -0
  495. package/dist/chunk-VJER3FUS.js +5080 -0
  496. package/dist/chunk-VLMDM56Q.js +268 -0
  497. package/dist/chunk-VMFZEVVX.js +99 -0
  498. package/dist/chunk-VNDPQVUZ.js +1853 -0
  499. package/dist/chunk-VSPJRLGA.js +4783 -0
  500. package/dist/chunk-VSWC5ZXI.js +103 -0
  501. package/dist/chunk-VYI4DSZP.js +2017 -0
  502. package/dist/chunk-W4BAIQ6L.js +1212 -0
  503. package/dist/chunk-W7VQAL2K.js +2203 -0
  504. package/dist/chunk-WEI3ZDZH.js +33 -0
  505. package/dist/chunk-WEIUS5NT.js +26 -0
  506. package/dist/chunk-WEW7UFWE.js +546 -0
  507. package/dist/chunk-WGQAXLNA.js +384 -0
  508. package/dist/chunk-WJAL4JL3.js +206 -0
  509. package/dist/chunk-WKFOJ5ZG.js +141 -0
  510. package/dist/chunk-WMWQEFLN.js +2177 -0
  511. package/dist/chunk-WNECKKO2.js +154 -0
  512. package/dist/chunk-WPKLYOAM.js +195 -0
  513. package/dist/chunk-WSH37YQN.js +243 -0
  514. package/dist/chunk-WV3KQDPE.js +84 -0
  515. package/dist/chunk-WZFC4V4U.js +389 -0
  516. package/dist/chunk-XCOIBNET.js +13 -0
  517. package/dist/chunk-XDM65LBX.js +53 -0
  518. package/dist/chunk-XGU6PBO5.js +645 -0
  519. package/dist/chunk-XHOI4V62.js +43 -0
  520. package/dist/chunk-XIBVFMLQ.js +6858 -0
  521. package/dist/chunk-XJ2E7YEJ.js +117 -0
  522. package/dist/chunk-XON4ZCYX.js +69 -0
  523. package/dist/chunk-XQPEWJPI.js +127 -0
  524. package/dist/chunk-XSJTIFUY.js +31 -0
  525. package/dist/chunk-XT7OVWM3.js +64 -0
  526. package/dist/chunk-XTXYIISX.js +1104 -0
  527. package/dist/chunk-XV7NZL4B.js +57 -0
  528. package/dist/chunk-XWIZ6B5S.js +109 -0
  529. package/dist/chunk-XWLIKOBX.js +240 -0
  530. package/dist/chunk-XZOXEIN4.js +156 -0
  531. package/dist/chunk-XZRS7I7W.js +3673 -0
  532. package/dist/chunk-Y4ZDDHB6.js +44 -0
  533. package/dist/chunk-Y733RB7T.js +194 -0
  534. package/dist/chunk-YBHUB4XR.js +14931 -0
  535. package/dist/chunk-YKC75XK4.js +147 -0
  536. package/dist/chunk-YKMXFVK7.js +27 -0
  537. package/dist/chunk-YO5BAJ55.js +225 -0
  538. package/dist/chunk-YPDCLXNZ.js +568 -0
  539. package/dist/chunk-YSQPGVFK.js +116 -0
  540. package/dist/chunk-YTVVTIWJ.js +716 -0
  541. package/dist/chunk-YVCMDVBB.js +698 -0
  542. package/dist/chunk-YYTOA6DP.js +114 -0
  543. package/dist/chunk-Z5WGNULV.js +88 -0
  544. package/dist/chunk-Z6NGVVXG.js +66 -0
  545. package/dist/chunk-Z7CDQNH3.js +20 -0
  546. package/dist/chunk-ZH4G3FU5.js +187 -0
  547. package/dist/chunk-ZJB6SQPA.js +257 -0
  548. package/dist/chunk-ZL6E4HMY.js +42 -0
  549. package/dist/chunk-ZMLOC3DW.js +62 -0
  550. package/dist/chunk-ZORW64QH.js +101 -0
  551. package/dist/chunk-ZVVD2XGV.js +1369 -0
  552. package/dist/cli-utils-ER33ODC5.js +31 -0
  553. package/dist/cli-validation-6CHPTANV.js +146 -0
  554. package/dist/commit-utils-3STAY7C7.js +49 -0
  555. package/dist/compatibility-notice-IHNXR2JR.js +21 -0
  556. package/dist/compete-config-resolver-V3GDNJMM.js +125 -0
  557. package/dist/compete-results-XUM3PJHS.js +318 -0
  558. package/dist/competition-outcome-tracker-NWSMVUPG.js +62 -0
  559. package/dist/config-4HHE4OIK.js +238 -0
  560. package/dist/config-D6YAZASH.js +10 -0
  561. package/dist/config-MIPRDYYV.js +46 -0
  562. package/dist/config-audit-GA5DCUAI.js +371 -0
  563. package/dist/config-explain-4LMO556H.js +71 -0
  564. package/dist/config-loader-RGP634PC.js +51 -0
  565. package/dist/config-manager-J2UTO635.js +114 -0
  566. package/dist/config-merger-TVUJBZTS.js +14 -0
  567. package/dist/config-validator-H4ZVIE56.js +122 -0
  568. package/dist/cost-HSIV5U2Y.js +85 -0
  569. package/dist/data-migration-state-OAUL2WPQ.js +35 -0
  570. package/dist/db-migrator-NUOW5NKR.js +34 -0
  571. package/dist/default-paths-UILB62BN.js +49 -0
  572. package/dist/discovery-V6GYUJUC.js +53 -0
  573. package/dist/doctor-BBSMCDWE.js +271 -0
  574. package/dist/doctor-context-blocks-LPY75VZA.js +231 -0
  575. package/dist/doctor-instructions-import-JXHDDOE5.js +70 -0
  576. package/dist/doctor-purpose-6YJOXPBE.js +30 -0
  577. package/dist/domain-analyzer-S35ABUNA.js +48 -0
  578. package/dist/ensure-initialized-Q6PEU55O.js +78 -0
  579. package/dist/entry.js +922 -0
  580. package/dist/error-parser-core-MZFMFANN.js +70 -0
  581. package/dist/evaluation-context-GQBYDAEV.js +63 -0
  582. package/dist/evidence-plan-resolver-5FUAP3SA.js +111 -0
  583. package/dist/execution-data-writer-YCG5UH37.js +72 -0
  584. package/dist/execution-policy-applier-PWZM5LNA.js +63 -0
  585. package/dist/execution-preflight-DASJWWQF.js +223 -0
  586. package/dist/execution-roles-KNSNNB2D.js +112 -0
  587. package/dist/exit-code-error-QIY5DOKF.js +18 -0
  588. package/dist/external-temp-cleanup-CAW7LQ6R.js +360 -0
  589. package/dist/factory-YCYCB7AE.js +209 -0
  590. package/dist/file-utils-ZQYNBQBW.js +45 -0
  591. package/dist/flywheel-allowlist-revert-H22HA6JE.js +21 -0
  592. package/dist/flywheel-autoloop-executor-PDOASJEZ.js +316 -0
  593. package/dist/flywheel-autoloop-unverified-TOKYFCDC.js +36 -0
  594. package/dist/flywheel-competition-executor-YJUMMEJO.js +320 -0
  595. package/dist/flywheel-executor-JWU43KAY.js +437 -0
  596. package/dist/flywheel-git-isolation-FQ2TRWMB.js +56 -0
  597. package/dist/flywheel-manifest-HDPNMWWF.js +114 -0
  598. package/dist/flywheel-model-defaults-UHAKTZT3.js +110 -0
  599. package/dist/flywheel-preflight-III2C7TH.js +163 -0
  600. package/dist/flywheel-resume-2O45MS7Y.js +31 -0
  601. package/dist/flywheel-resume-execution-lease-P2BK257J.js +59 -0
  602. package/dist/flywheel-safety-AVAVFXIJ.js +19 -0
  603. package/dist/flywheel-scope-decision-ALJUUNGG.js +14 -0
  604. package/dist/get-telemetry-logger-MPDVO2JA.js +155 -0
  605. package/dist/git-message-style-KCXGMTDT.js +104 -0
  606. package/dist/guidance-profile-config-G3K5YQOO.js +113 -0
  607. package/dist/guidance-profile-renderer-TX4TNDFL.js +23 -0
  608. package/dist/identity-XLQJ4YJF.js +25 -0
  609. package/dist/index-query-6NUY4FKX.js +37 -0
  610. package/dist/index.js +85588 -0
  611. package/dist/indexer-DL54CM3K.js +44 -0
  612. package/dist/init-OUZ72344.js +245 -0
  613. package/dist/instructions-import-offer-NSYXSITC.js +363 -0
  614. package/dist/instructions-run-NBII7W36.js +114 -0
  615. package/dist/interrupted-work-E4TLKJLE.js +43 -0
  616. package/dist/invoke-provider-auth-NEWEIHCI.js +38 -0
  617. package/dist/judge-scoring.json +64 -0
  618. package/dist/lab-mode-YXQ2Y3NG.js +44 -0
  619. package/dist/lab-utils-5QAMDJR5.js +29 -0
  620. package/dist/linux-host-class-YDWQX65O.js +31 -0
  621. package/dist/llm-judge-executor-OJSNCTFJ.js +179 -0
  622. package/dist/local-ci-config-VZ4WWDT5.js +48 -0
  623. package/dist/local-executor-DCMDDRX2.js +280 -0
  624. package/dist/logger-5KNKS3O4.js +154 -0
  625. package/dist/matrix-prompt-builder-4ZBOGZSB.js +527 -0
  626. package/dist/matrix-result-parser-EZJM4YXP.js +14 -0
  627. package/dist/merge-batch-RTEEMVDB.js +313 -0
  628. package/dist/merge-batch-members-VIWGVRHP.js +24 -0
  629. package/dist/merge-queue-TIIUSOY6.js +126 -0
  630. package/dist/merge-queue-trust-DUNZ4J65.js +21 -0
  631. package/dist/metadata-UKSMWM6J.js +29 -0
  632. package/dist/model-catalog-readiness-R5KJBP5M.js +202 -0
  633. package/dist/model-invocations-db-XTUPPQ34.js +70 -0
  634. package/dist/model-resolver-JFZ2MHOJ.js +39 -0
  635. package/dist/model-resolver-SCQ57KAQ.js +217 -0
  636. package/dist/model-roles-D3OR4NAH.js +110 -0
  637. package/dist/monitor-F4SIU5G2.js +96 -0
  638. package/dist/nested-worktrees-2WUWQ75N.js +23 -0
  639. package/dist/next-S3IRLVER.js +96 -0
  640. package/dist/next-UKPFRXOO.js +148 -0
  641. package/dist/opportunistic-TL4S26VX.js +86 -0
  642. package/dist/optimization-config-resolver-NAEIYU6A.js +148 -0
  643. package/dist/package-HJR4QJT4.js +10 -0
  644. package/dist/parse-git-status-7PIYFPS2.js +21 -0
  645. package/dist/path-security-5WJSY56G.js +41 -0
  646. package/dist/paths-JUI7C4KU.js +45 -0
  647. package/dist/plan-backlog-list-fast-SSTQL5XP.js +89 -0
  648. package/dist/plan-backlog-show-fast-PFRAQ3JB.js +114 -0
  649. package/dist/plan-sprint-list-fast-HD2KBZSY.js +110 -0
  650. package/dist/plan-sprint-status-fast-YFYH7WQF.js +116 -0
  651. package/dist/plan-task-status-fast-JU5PNLUU.js +114 -0
  652. package/dist/plan-to-flywheel-PY5MSY2H.js +529 -0
  653. package/dist/preflight-PJHGTJNZ.js +12 -0
  654. package/dist/process-registry-B4ZUCN7T.js +28 -0
  655. package/dist/process-scanner-TU3QHWVB.js +23 -0
  656. package/dist/processes-6FHVGT72.js +28 -0
  657. package/dist/processes-json-fast-YWTMUWXB.js +72 -0
  658. package/dist/profile-HWTC56SG.js +209 -0
  659. package/dist/project-runtime-paths-2UQNG5Y4.js +41 -0
  660. package/dist/protected-branches-N36LDBFY.js +71 -0
  661. package/dist/provider-aware-resource-manager-YTFAHNGJ.js +40 -0
  662. package/dist/provider-matrix-2KO2V5OM.js +214 -0
  663. package/dist/provider-matrix-renderer-MEC3PER7.js +139 -0
  664. package/dist/provider-output-forwarding-2AHL7YMA.js +26 -0
  665. package/dist/provider-registry-WCZ65QHC.js +136 -0
  666. package/dist/provider-temp-cleanup-IUNPJCZF.js +14 -0
  667. package/dist/prune-engine-EOQVWEP6.js +448 -0
  668. package/dist/publication-turn-S42RZTQF.js +44 -0
  669. package/dist/quality-gate-JQMVCKOO.js +72 -0
  670. package/dist/quickstart-3O6FRCFB.js +105 -0
  671. package/dist/readiness-GW6ABRHB.js +139 -0
  672. package/dist/readiness-KFZCXDTT.js +215 -0
  673. package/dist/registry-7XG376SY.js +217 -0
  674. package/dist/repair-helpers-TTV5FEF4.js +117 -0
  675. package/dist/repo-root-YG6MQZDW.js +28 -0
  676. package/dist/resolution-engine-FFEK7HQ2.js +187 -0
  677. package/dist/resolve-provider-cli-ZEXVZKET.js +44 -0
  678. package/dist/restore-telemetry-db-E7BR5PO7.js +459 -0
  679. package/dist/routing-events-AW6SRZ6G.js +13 -0
  680. package/dist/routing-history-store-FMDDQL6W.js +39 -0
  681. package/dist/routing-planner-C6OHO3EY.js +221 -0
  682. package/dist/runner-KHDNQ5KU.js +1194 -0
  683. package/dist/runner-PIS2BW7Y.js +1801 -0
  684. package/dist/safety-E3JJVSFL.js +67 -0
  685. package/dist/safety-stanza-HE6GXP4G.js +49 -0
  686. package/dist/sandbox-7LBZC5PE.js +72 -0
  687. package/dist/sandbox-ASWCDYZL.js +80 -0
  688. package/dist/schema-extensions.sql +234 -0
  689. package/dist/security-JI22TBEB.js +17 -0
  690. package/dist/setup-bb-plugin-FQXLA76Y.js +531 -0
  691. package/dist/setup-claude-code-NF4ZSHVE.js +197 -0
  692. package/dist/setup-temp-cleanup-2APZ4HMA.js +29 -0
  693. package/dist/shared-utils-34HJZ6JX.js +40 -0
  694. package/dist/simple-artifact-goal-MQLDDMMX.js +29 -0
  695. package/dist/sprint-N7JFTEGL.js +403 -0
  696. package/dist/sprint-bridge-KJXIBNRJ.js +117 -0
  697. package/dist/sprint-completion-dependents-34SR7JVL.js +43 -0
  698. package/dist/sprint-execution-service-A6JPUBWY.js +376 -0
  699. package/dist/sprint-manager-MU2ZUWN2.js +140 -0
  700. package/dist/sqlite-timeout-config-U2CT4LKU.js +13 -0
  701. package/dist/sqlite-wrapper-FSGJ2QEW.js +19 -0
  702. package/dist/stale-cleanup-orchestrator-4X6324VF.js +71 -0
  703. package/dist/storage-access-I3V5LER7.js +31 -0
  704. package/dist/store-XVG64ZR2.js +25 -0
  705. package/dist/task-classifier-MN2DYE5R.js +558 -0
  706. package/dist/task-list-fast-7AT6LTTA.js +112 -0
  707. package/dist/task-manager-LHPBBRRE.js +136 -0
  708. package/dist/task-report-OLPAWBTK.js +64 -0
  709. package/dist/task-type-detector-6RG3YNZF.js +27 -0
  710. package/dist/task-type-resolver-EQHOUIHT.js +64 -0
  711. package/dist/telemetry-5VYSK772.js +212 -0
  712. package/dist/telemetry-health-tracker-4GFW2EMP.js +12 -0
  713. package/dist/telemetry-path-resolver-H3NFHHQY.js +34 -0
  714. package/dist/telemetry-reliability-URJDTNPZ.js +76 -0
  715. package/dist/transports-NREZQTJK.js +106 -0
  716. package/dist/unified-cost-tracker-5MTBJ2CL.js +59 -0
  717. package/dist/unified-state-cleanup-GGAXLHXF.js +292 -0
  718. package/dist/universal-cost-calculator-JTFEJVWK.js +70 -0
  719. package/dist/user-agents-QLIGPLP2.js +30 -0
  720. package/dist/variant-execution-I4ZZUAWY.js +319 -0
  721. package/dist/variant-opt-parser-JHCXKY2K.js +251 -0
  722. package/dist/variant-request-input-PL37N3AN.js +11 -0
  723. package/dist/variant-state-cleanup-KHMXRBZN.js +287 -0
  724. package/dist/variant-worker-COB5YTJX.js +4517 -0
  725. package/dist/variant-worker.js +26 -0
  726. package/dist/variants-XGDSXYG3.js +267 -0
  727. package/dist/verification-command-YSPU4VBH.js +66 -0
  728. package/dist/verify-command-timeout-DQUYH33Z.js +11 -0
  729. package/dist/verify-commands-LOHMOFKC.js +65 -0
  730. package/dist/warning-JSKSFKBT.js +13 -0
  731. package/dist/work-edge-K65NHVWY.js +50 -0
  732. package/dist/work-item-writer-KY5ZWZWW.js +30 -0
  733. package/dist/worker-JNXXEIT6.js +1504 -0
  734. package/dist/worker.js +21 -0
  735. package/dist/workspace-tree-identity-JO5PSKTW.js +52 -0
  736. package/dist/worktree-environment-WTVABAQW.js +64 -0
  737. package/dist/worktree-metrics-VZRQI26T.js +66 -0
  738. package/dist/writeback-TQFQBJNU.js +50 -0
  739. package/docs/CLI_REFERENCE.md +8287 -0
  740. package/docs/PROVIDER_SETUP.md +1737 -0
  741. package/docs/README.md +96 -0
  742. package/docs/TROUBLESHOOTING.md +2209 -0
  743. package/docs/getting-started/QUICK_START.md +260 -0
  744. package/docs/getting-started/README.md +198 -0
  745. package/docs/getting-started/SETUP.md +25 -0
  746. package/docs/reference/AGENT_CONTEXT.md +84 -0
  747. package/docs/reference/DELIVERY_ONBOARDING.md +198 -0
  748. package/docs/reference/PROVIDER_RELEASE_TIERS.md +34 -0
  749. package/docs/reference/SECURITY.md +291 -0
  750. package/docs/reference/TELEMETRY_PRIVACY.md +259 -0
  751. package/integrations/bb-plugin-build/README.md +391 -0
  752. package/integrations/bb-plugin-build/app.css +431 -0
  753. package/integrations/bb-plugin-build/app.tsx +6210 -0
  754. package/integrations/bb-plugin-build/package.json +45 -0
  755. package/integrations/bb-plugin-build/server.ts +488 -0
  756. package/integrations/bb-plugin-build/src/adapter.ts +3672 -0
  757. package/integrations/bb-plugin-build/src/backlog-authoring-model.ts +150 -0
  758. package/integrations/bb-plugin-build/src/backlog-authoring-schema.ts +458 -0
  759. package/integrations/bb-plugin-build/src/backlog-authoring-service.ts +467 -0
  760. package/integrations/bb-plugin-build/src/backlog-authoring-view.ts +1025 -0
  761. package/integrations/bb-plugin-build/src/command-evidence-schema.ts +13 -0
  762. package/integrations/bb-plugin-build/src/competition-defaults-schema.ts +122 -0
  763. package/integrations/bb-plugin-build/src/competition-defaults-service.ts +105 -0
  764. package/integrations/bb-plugin-build/src/competition-defaults-view.ts +138 -0
  765. package/integrations/bb-plugin-build/src/contract.ts +718 -0
  766. package/integrations/bb-plugin-build/src/execution-activity-view.ts +98 -0
  767. package/integrations/bb-plugin-build/src/finalize-model.ts +205 -0
  768. package/integrations/bb-plugin-build/src/finalize-schema.ts +150 -0
  769. package/integrations/bb-plugin-build/src/finalize-service.ts +330 -0
  770. package/integrations/bb-plugin-build/src/finalize-view.ts +159 -0
  771. package/integrations/bb-plugin-build/src/judge-operations-readback.ts +303 -0
  772. package/integrations/bb-plugin-build/src/judge-operations-schema.ts +33 -0
  773. package/integrations/bb-plugin-build/src/judge-operations-view.ts +459 -0
  774. package/integrations/bb-plugin-build/src/model.ts +1318 -0
  775. package/integrations/bb-plugin-build/src/monitor-service.ts +233 -0
  776. package/integrations/bb-plugin-build/src/panel-read-ux.ts +97 -0
  777. package/integrations/bb-plugin-build/src/patch-preview-schema.ts +166 -0
  778. package/integrations/bb-plugin-build/src/patch-preview-service.ts +480 -0
  779. package/integrations/bb-plugin-build/src/patch-preview-view.ts +109 -0
  780. package/integrations/bb-plugin-build/src/planning-readback.ts +228 -0
  781. package/integrations/bb-plugin-build/src/planning-service.ts +647 -0
  782. package/integrations/bb-plugin-build/src/planning-sync-evidence.ts +37 -0
  783. package/integrations/bb-plugin-build/src/planning-workspace-readback.ts +33 -0
  784. package/integrations/bb-plugin-build/src/planning-workspace-schema.ts +131 -0
  785. package/integrations/bb-plugin-build/src/planning-workspace.ts +333 -0
  786. package/integrations/bb-plugin-build/src/poll-handoff.ts +136 -0
  787. package/integrations/bb-plugin-build/src/project-target-server.ts +21 -0
  788. package/integrations/bb-plugin-build/src/project-target.ts +45 -0
  789. package/integrations/bb-plugin-build/src/reference-index.ts +218 -0
  790. package/integrations/bb-plugin-build/src/repository-identity.ts +17 -0
  791. package/integrations/bb-plugin-build/src/repository-schema.ts +30 -0
  792. package/integrations/bb-plugin-build/src/result-explorer-view.ts +597 -0
  793. package/integrations/bb-plugin-build/src/result-readback-model.ts +163 -0
  794. package/integrations/bb-plugin-build/src/result-readback-schema.ts +319 -0
  795. package/integrations/bb-plugin-build/src/result-readback-service.ts +333 -0
  796. package/integrations/bb-plugin-build/src/run-page-view.ts +332 -0
  797. package/integrations/bb-plugin-build/src/setup-config-schema.ts +293 -0
  798. package/integrations/bb-plugin-build/src/setup-config-service.ts +620 -0
  799. package/integrations/bb-plugin-build/src/setup-config-view.ts +388 -0
  800. package/integrations/bb-plugin-build/src/sprint-close-model.ts +150 -0
  801. package/integrations/bb-plugin-build/src/sprint-close-schema.ts +128 -0
  802. package/integrations/bb-plugin-build/src/sprint-close-service.ts +293 -0
  803. package/integrations/bb-plugin-build/src/sprint-close-view.ts +116 -0
  804. package/integrations/bb-plugin-build/src/sprint-composition-model.ts +709 -0
  805. package/integrations/bb-plugin-build/src/sprint-composition-readback.ts +231 -0
  806. package/integrations/bb-plugin-build/src/sprint-composition-schema.ts +597 -0
  807. package/integrations/bb-plugin-build/src/sprint-composition-service.ts +773 -0
  808. package/integrations/bb-plugin-build/src/sprint-composition-view.ts +1221 -0
  809. package/integrations/bb-plugin-build/src/sprint-execution-schema.ts +188 -0
  810. package/integrations/bb-plugin-build/src/sprint-execution-service.ts +289 -0
  811. package/integrations/bb-plugin-build/src/sprint-execution-view.ts +54 -0
  812. package/integrations/bb-plugin-build/src/task-authoring-model.ts +563 -0
  813. package/integrations/bb-plugin-build/src/task-authoring-schema.ts +629 -0
  814. package/integrations/bb-plugin-build/src/task-authoring-service.ts +842 -0
  815. package/integrations/bb-plugin-build/src/task-authoring-view.ts +1227 -0
  816. package/integrations/bb-plugin-build/src/task-catalog-schema.ts +46 -0
  817. package/integrations/bb-plugin-build/src/task-catalog-service.ts +44 -0
  818. package/integrations/bb-plugin-build/src/task-detail-readback.ts +56 -0
  819. package/integrations/bb-plugin-build/src/task-detail-schema.ts +171 -0
  820. package/integrations/bb-plugin-build/src/task-navigation.ts +446 -0
  821. package/integrations/bb-plugin-build/src/workbench-route.ts +74 -0
  822. package/package.json +306 -4
  823. package/schemas/README.md +80 -0
  824. package/schemas/build.config.schema.json +2110 -0
  825. package/schemas/config-v1.schema.json +136 -0
  826. package/schemas/execution-config.schema.json +47 -0
  827. package/schemas/judge-scoring.schema.json +257 -0
  828. package/schemas/provider-config.schema.json +179 -0
  829. package/schemas/telemetry-config.schema.json +79 -0
  830. package/schemas/user-preferences.schema.json +141 -0
  831. package/scripts/assert-node-runtime.mjs +146 -0
  832. package/scripts/preflight-native.mjs +49 -0
  833. package/scripts/setup-git-hooks.mjs +39 -0
  834. package/scripts/sync.sh +2871 -0
  835. package/scripts/write-node-launcher.sh +138 -0
  836. package/src/resources/gemini/slash-packs/default/plan.toml +15 -0
  837. package/src/resources/gemini/slash-packs/default/summary.toml +16 -0
  838. package/src/resources/gemini/slash-packs/default/tests.toml +16 -0
  839. package/templates/.build-project/README.md +41 -0
  840. package/templates/.build-project/agents/README.md +50 -0
  841. package/templates/.build-project/agents/architect.md +10 -0
  842. package/templates/.build-project/agents/code-reviewer.md +10 -0
  843. package/templates/.build-project/agents/debugger.md +10 -0
  844. package/templates/.build-project/agents/documenter.md +10 -0
  845. package/templates/.build-project/agents/test-automator.md +10 -0
  846. package/templates/.build-project/config/build.config.jsonc +64 -0
  847. package/templates/.build-project/config/project-context.template.json +6 -0
  848. package/templates/.build-project/config/task-type-aliases.presets/kanban.yaml +14 -0
  849. package/templates/.build-project/config/task-type-aliases.presets/scrum.yaml +17 -0
  850. package/templates/.build-project/config/task-type-aliases.presets/xp.yaml +12 -0
  851. package/templates/.build-project/config/task-type-aliases.yaml +29 -0
  852. package/templates/.build-project/gitignore.template +70 -0
  853. package/templates/.build-project/task-types/analysis.yaml +38 -0
  854. package/templates/.build-project/task-types/architecture.yaml +35 -0
  855. package/templates/.build-project/task-types/doc.yaml +34 -0
  856. package/templates/.build-project/task-types/feature.yaml +22 -0
  857. package/templates/.build-project/task-types/general.yaml +4 -0
  858. package/templates/.build-project/task-types/security.yaml +38 -0
  859. package/templates/AGENTS.template.md +85 -0
  860. package/templates/CLAUDE.template.md +1 -0
  861. package/templates/GEMINI.template.md +1 -0
  862. package/templates/builtin-workflows/code-review.yaml +73 -0
  863. package/templates/builtin-workflows/skills/code-review/SKILL.md +61 -0
  864. package/templates/builtin-workflows/skills/execution-contract-analysis/SKILL.md +24 -0
  865. package/templates/builtin-workflows/skills/execution-contract-code/SKILL.md +19 -0
  866. package/templates/builtin-workflows/skills/execution-contract-structured/SKILL.md +17 -0
  867. package/templates/ci/incident-intake.mjs +787 -0
  868. package/templates/ci/incidents.yml.template +103 -0
  869. package/templates/ci/node.yml.template +17 -0
  870. package/templates/ci/python.yml.template +17 -0
  871. package/templates/guard-kit/GUARD_SETUP.md.template +258 -0
  872. package/templates/guard-kit/build-guard.yml.template +189 -0
  873. package/templates/guard-kit/check.mjs.template +932 -0
  874. package/templates/guard-kit/config.json.template +7 -0
  875. package/templates/guard-kit/policy.json.template +1 -0
  876. package/templates/profiles/README.md +57 -0
  877. package/templates/profiles/frontier-claude.json +23 -0
  878. package/templates/profiles/frontier-codex.json +23 -0
@@ -0,0 +1,3673 @@
1
+ import {
2
+ GitHubMergeOperationError,
3
+ MERGE_BATCH_BRANCH_PREFIX,
4
+ closeMergedBatchMembers,
5
+ fetchGitHubMergeObjects,
6
+ mergeGitHubPullRequestImmediately,
7
+ readGitHubMergePullRequest,
8
+ readGitHubMergeRepository,
9
+ readGitHubRequiredChecks,
10
+ requireCommandOk,
11
+ resolveGitHubMergeMethod,
12
+ runGitHubCli,
13
+ updateGitHubMergeBranch,
14
+ verifyGitHubMechanicalMerge
15
+ } from "./chunk-TA3N5MMO.js";
16
+ import {
17
+ claimHaltOverrideGrant,
18
+ followHaltOverrideGrant,
19
+ inheritEntrypointApproval,
20
+ localCiTrustForRequest,
21
+ recordTrustGrants
22
+ } from "./chunk-QBWF4444.js";
23
+ import {
24
+ EntrypointApprovalRequired,
25
+ acquireLocalCiSlot,
26
+ appendLandingLogEntry,
27
+ approvalCommand,
28
+ dispositionPatch,
29
+ keepMacAwakeForCurrentProcess,
30
+ queueLandingFields,
31
+ readGitHubCommitStatusDetail,
32
+ reconcileLocalCiCandidate,
33
+ releaseHold,
34
+ releaseIfApproved,
35
+ resolveCanonicalMainRepoRoot,
36
+ resolveEntrypointFingerprint,
37
+ resolveGitHubBranchSha,
38
+ resumeApprovedHolds,
39
+ runLocalCiWorker,
40
+ submitLocalCi
41
+ } from "./chunk-G3VB2UCZ.js";
42
+ import {
43
+ resolveLandingLeaseEnabled,
44
+ resolveMergeBatchingEnabled
45
+ } from "./chunk-PEBWDYIW.js";
46
+ import {
47
+ registerDetachedWorkerProcess,
48
+ signalDetachedWorkerProcess,
49
+ startDetachedWorkerRegistryHeartbeat,
50
+ unregisterDetachedWorkerProcess
51
+ } from "./chunk-P7RVCHQV.js";
52
+ import {
53
+ inspectCiIntegration
54
+ } from "./chunk-S2XIMQJQ.js";
55
+ import {
56
+ acquirePublicationTurn,
57
+ readPublicationTurn
58
+ } from "./chunk-63DHNHGA.js";
59
+ import {
60
+ DEFAULT_LOCAL_CI_STATUS_CONTEXT,
61
+ ENTRYPOINT_FINGERPRINT_PATTERN,
62
+ LocalCiStateStore,
63
+ parseLocalCiRepositoryIdentity,
64
+ repoStateKey
65
+ } from "./chunk-VBENAHDA.js";
66
+ import {
67
+ collectAutoFinalizeMergeEvidence,
68
+ collectTrailerItemIds,
69
+ triageTrailerItems
70
+ } from "./chunk-AJJNW7XT.js";
71
+ import {
72
+ TaskIdSchema
73
+ } from "./chunk-AISTV5XU.js";
74
+ import {
75
+ resolveGitHubRepositoryFromRemote,
76
+ resolveOptionalGitHubRepositoryFromRemote
77
+ } from "./chunk-BDPYFAB4.js";
78
+ import {
79
+ evaluateAutoFinalizeEligibility,
80
+ getBacklogExecutionProvenancePath,
81
+ loadBacklogExecutionProvenance
82
+ } from "./chunk-3WY5TU2O.js";
83
+ import {
84
+ atomicWriteJson
85
+ } from "./chunk-RFDVSI4N.js";
86
+ import {
87
+ acquireFencedFileLock
88
+ } from "./chunk-QYQOVOLR.js";
89
+ import {
90
+ resolveDefaultBranch
91
+ } from "./chunk-XGU6PBO5.js";
92
+ import {
93
+ buildConfigCandidates,
94
+ parseCanonicalBuildConfig
95
+ } from "./chunk-FKCG7K6H.js";
96
+ import {
97
+ GitExecError,
98
+ execGit
99
+ } from "./chunk-MHLEZQQI.js";
100
+ import {
101
+ createLocalCiDetachedChildEnv
102
+ } from "./chunk-SVJM3LJA.js";
103
+ import {
104
+ getLocalCiStateRoot
105
+ } from "./chunk-BV3XQQLU.js";
106
+ import {
107
+ processOwnershipState,
108
+ readProcessIdentity
109
+ } from "./chunk-MT2WAIR3.js";
110
+ import {
111
+ DEFAULT_TIMEOUT_MS,
112
+ PLANNING_GIT_NETWORK_TIMEOUT_MS
113
+ } from "./chunk-I56TH7VM.js";
114
+ import {
115
+ getStateDir
116
+ } from "./chunk-56WPYDJH.js";
117
+ import {
118
+ BUILD_NIGHTLY_STATE_DIR,
119
+ emitCustomWarning,
120
+ readEnv
121
+ } from "./chunk-7VRJHTGZ.js";
122
+
123
+ // src/core/ci/merge-queue.ts
124
+ import path8 from "path";
125
+ import { z as z6 } from "zod";
126
+
127
+ // src/core/ci/merge-queue-validation.ts
128
+ async function validationStore(request, stateRoot) {
129
+ const repository = await resolveGitHubRepositoryFromRemote(request.repoRoot, "origin");
130
+ if (repository.toLowerCase() !== request.repository.toLowerCase()) {
131
+ throw new Error("Repository origin changed after merge admission.");
132
+ }
133
+ return { repository, store: new LocalCiStateStore(stateRoot ?? getLocalCiStateRoot()) };
134
+ }
135
+ async function acquireMergeValidationSlot(request, stateRoot, checkContinue) {
136
+ const { repository, store } = await validationStore(request, stateRoot);
137
+ return acquireLocalCiSlot({
138
+ repository,
139
+ stateRoot: store.root,
140
+ priority: true,
141
+ ...checkContinue ? { checkContinue } : {}
142
+ });
143
+ }
144
+ async function gitAtBase(repoRoot, args) {
145
+ const result = await execGit(args, { cwd: repoRoot });
146
+ if (result.exitCode !== 0) {
147
+ throw new Error(`git ${args[0]} failed: ${result.stderr.trim() || `exit ${result.exitCode}`}`);
148
+ }
149
+ return result.stdout;
150
+ }
151
+ async function resolveMergeStatusContext(request, baseSha) {
152
+ const [jsonc, json] = buildConfigCandidates(".build-project/config/build.config.jsonc");
153
+ const listing = await gitAtBase(request.repoRoot, [
154
+ "ls-tree",
155
+ "--full-tree",
156
+ baseSha,
157
+ "--",
158
+ jsonc,
159
+ json
160
+ ]);
161
+ const entries = new Map(
162
+ listing.split("\n").filter(Boolean).map((line) => {
163
+ const [meta = "", file2 = ""] = line.split(" ");
164
+ return [file2, meta.split(" ")[2] ?? ""];
165
+ })
166
+ );
167
+ const file = [jsonc, json].find((candidate) => entries.has(candidate));
168
+ if (!file) return DEFAULT_LOCAL_CI_STATUS_CONTEXT;
169
+ const source = await gitAtBase(request.repoRoot, ["cat-file", "blob", entries.get(file) ?? ""]);
170
+ const config = parseCanonicalBuildConfig(source, `${baseSha}:${file}`);
171
+ if (config.status === "invalid") throw new Error("Invalid project configuration.");
172
+ return config.config.ci?.statusContext ?? DEFAULT_LOCAL_CI_STATUS_CONTEXT;
173
+ }
174
+ async function inspectMergeValidation(request, stateRoot) {
175
+ const { repository, store } = await validationStore(request, stateRoot);
176
+ try {
177
+ const record = await store.read(repository, request.acceptedHeadSha);
178
+ if (record.repository.toLowerCase() !== repository.toLowerCase() || record.sha.toLowerCase() !== request.acceptedHeadSha.toLowerCase()) {
179
+ throw new Error("Stored Local CI identity differs from the selected merge candidate.");
180
+ }
181
+ return record;
182
+ } catch (error) {
183
+ const cause = error instanceof Error ? error.cause : void 0;
184
+ if (error.code === "ENOENT" || cause?.code === "ENOENT") {
185
+ return void 0;
186
+ }
187
+ throw error;
188
+ }
189
+ }
190
+ async function reuseMergeValidation(request, baseSha, stateRoot) {
191
+ const existing = await inspectMergeValidation(request, stateRoot);
192
+ if (!existing) return void 0;
193
+ const { repository, store } = await validationStore(request, stateRoot);
194
+ return reconcileLocalCiCandidate({
195
+ repoRoot: request.repoRoot,
196
+ repository,
197
+ sha: request.acceptedHeadSha,
198
+ stateRoot: store.root,
199
+ expectedBaseSha: baseSha,
200
+ trust: localCiTrustForRequest(request)
201
+ });
202
+ }
203
+ async function validateMergeHead(request, slot, stateRoot, expectedBaseSha, signal, claimHaltOverride) {
204
+ const { repository, store } = await validationStore(request, stateRoot);
205
+ const trust = localCiTrustForRequest(request, claimHaltOverride);
206
+ let existing = await inspectMergeValidation(request, stateRoot);
207
+ if (existing && (existing.status === "succeeded" || existing.status === "succeeded_pending" || existing.status === "publication_pending")) {
208
+ existing = await reconcileLocalCiCandidate({
209
+ repoRoot: request.repoRoot,
210
+ repository,
211
+ sha: request.acceptedHeadSha,
212
+ stateRoot: store.root,
213
+ ...expectedBaseSha ? { expectedBaseSha } : {},
214
+ trust
215
+ });
216
+ }
217
+ if (existing?.status === "failed") return existing;
218
+ const runClaimed = async (record, runStore) => {
219
+ const claim = await runStore.claimWorker(record);
220
+ if (!claim) {
221
+ return { record: await runStore.read(repository, record.sha), spawned: false };
222
+ }
223
+ try {
224
+ const identity2 = readProcessIdentity(process.pid);
225
+ if (!identity2) throw new Error("Cannot establish in-process CI ownership.");
226
+ const assigned = await runStore.assignWorkerPid(record, process.pid, identity2);
227
+ if (!assigned.applied) return { record: assigned.record, spawned: false };
228
+ const completed = await runLocalCiWorker({
229
+ repository,
230
+ sha: record.sha,
231
+ stateRoot: store.root,
232
+ slot,
233
+ manageProcess: false,
234
+ trust,
235
+ signal
236
+ });
237
+ return { record: completed, spawned: false };
238
+ } finally {
239
+ await runStore.releaseWorkerClaim(repository, record.sha, process.pid, claim.generationKey).catch(() => void 0);
240
+ }
241
+ };
242
+ const submission = await submitLocalCi(
243
+ {
244
+ repoRoot: request.repoRoot,
245
+ sha: request.acceptedHeadSha,
246
+ branch: request.headBranch,
247
+ remote: "origin",
248
+ stateRoot: store.root,
249
+ trust
250
+ },
251
+ { spawnWorker: runClaimed }
252
+ );
253
+ if (submission.skipped) throw new Error(submission.reason);
254
+ let result = submission.record;
255
+ for (let recovery = 0; recovery <= 1; recovery += 1) {
256
+ if (result.status !== "publication_pending" && result.status !== "succeeded_pending") {
257
+ return result;
258
+ }
259
+ const reconciled = await reconcileLocalCiCandidate({
260
+ repoRoot: request.repoRoot,
261
+ repository,
262
+ sha: result.sha,
263
+ stateRoot: store.root,
264
+ trust
265
+ });
266
+ if (reconciled.status !== "queued" || reconciled.pid) return reconciled;
267
+ if (recovery === 1) break;
268
+ result = (await runClaimed(reconciled, store)).record;
269
+ }
270
+ throw new Error(
271
+ `Local CI revalidation remained queued after one in-process retry. Run build ci submit --sha ${request.acceptedHeadSha} from the PR branch, then hand off the merge again.`
272
+ );
273
+ }
274
+
275
+ // src/core/ci/merge-batch-ci-status.ts
276
+ async function resolveLiveBatchStatusContext(input, dependencies = {}) {
277
+ const git = dependencies.git ?? execGit;
278
+ const cwd = input.repoRoot;
279
+ const tip = requireCommandOk(
280
+ await git(["ls-remote", "origin", `refs/heads/${input.branch}`], { cwd, timeoutMs: 6e4 }),
281
+ "Reading the target branch tip"
282
+ ).split(/\s+/, 1)[0];
283
+ if (!tip || !/^[a-f0-9]{40}$/.test(tip)) {
284
+ throw new Error("Could not resolve the target branch tip.");
285
+ }
286
+ requireCommandOk(
287
+ await git(["fetch", "--no-tags", "--no-write-fetch-head", "--refmap=", "origin", tip], {
288
+ cwd,
289
+ timeoutMs: 6e4
290
+ }),
291
+ "Fetching the target branch tip"
292
+ );
293
+ return (dependencies.resolveContext ?? resolveMergeStatusContext)({ repoRoot: cwd }, tip);
294
+ }
295
+ function classifyBatchMemberCiStatus(status) {
296
+ if (status.state === "failure" || status.state === "error") return "failed";
297
+ if (status.state === "pending") return "pending";
298
+ return "clear";
299
+ }
300
+ async function assertBatchMembersCiClear(input, read) {
301
+ const failed = [];
302
+ const pending = [];
303
+ for (const member of input.members) {
304
+ const label = `#${member.prNumber} at ${member.reviewedSha.slice(0, 12)}`;
305
+ let status;
306
+ try {
307
+ status = await read({
308
+ repository: input.repository,
309
+ sha: member.reviewedSha,
310
+ context: input.context
311
+ });
312
+ } catch (error) {
313
+ throw new Error(
314
+ `Batch refused: could not read the ${input.context} status of PR ${label} (${error instanceof Error ? error.message : String(error)}). Nothing was admitted; retry once GitHub is reachable.`
315
+ );
316
+ }
317
+ const verdict = classifyBatchMemberCiStatus(status);
318
+ if (verdict === "failed") {
319
+ const detail = [status.description, status.targetUrl].filter(Boolean).join(" ");
320
+ failed.push(`${label} (${status.state}${detail ? `: ${detail}` : ""})`);
321
+ } else if (verdict === "pending") {
322
+ pending.push(label);
323
+ }
324
+ }
325
+ if (failed.length === 0 && pending.length === 0) return;
326
+ const lines = ["Batch refused; nothing was admitted."];
327
+ if (failed.length > 0) {
328
+ lines.push(
329
+ `${input.context} failed for PR ${failed.join("; PR ")}. Fix it, or land the other members without it.`
330
+ );
331
+ }
332
+ if (pending.length > 0) {
333
+ lines.push(
334
+ `${input.context} is still pending for PR ${pending.join("; PR ")}. Retry once it finishes.`
335
+ );
336
+ }
337
+ throw new Error(lines.join(" "));
338
+ }
339
+
340
+ // src/core/ci/merge-queue-batching.ts
341
+ var MERGE_QUEUE_BATCH_LIMIT = 4;
342
+ var MERGE_QUEUE_BATCH_COLLECT_MS = 15e3;
343
+ function active(request) {
344
+ return request.status === "queued" || request.status === "running";
345
+ }
346
+ function holding(batch) {
347
+ return batch !== void 0 && (active(batch) || batch.status === "awaiting_approval" || batch.outstandingOperation?.kind === "merge");
348
+ }
349
+ function queuePosition(request, requests) {
350
+ if (!request.batchMembers) return request.order;
351
+ return Math.min(
352
+ ...request.batchMembers.map(
353
+ (member) => requests.find((r) => r.id === member.requestId)?.order ?? request.order
354
+ )
355
+ );
356
+ }
357
+ function runnable(request, requests) {
358
+ return active(request) && !heldByBatch(request, requests);
359
+ }
360
+ function heldByBatch(request, requests) {
361
+ const batchId = request.batchedInto?.requestId;
362
+ return batchId !== void 0 && holding(requests.find((r) => r.id === batchId));
363
+ }
364
+ function batchable(request) {
365
+ return request.status === "queued" && !request.outstandingOperation && request.preparationCount === 0 && request.preparedHeads.length === 0 && request.acceptedHeadSha === request.reviewedSha && !request.batchAttempted && !request.batchedInto && !request.batchMembers && !request.headBranch.startsWith(MERGE_BATCH_BRANCH_PREFIX);
366
+ }
367
+ function openAt(pr, sha, targetBranch) {
368
+ return pr.state === "open" && !pr.draft && pr.headSha === sha && pr.baseBranch === targetBranch;
369
+ }
370
+ async function settleBatchMembers(store, repository, workerToken, deps) {
371
+ const requests = await store.list(repository);
372
+ const closed = /* @__PURE__ */ new Set();
373
+ for (const member of requests) {
374
+ if (!member.batchedInto || heldByBatch(member, requests)) continue;
375
+ const batch = requests.find((r) => r.id === member.batchedInto?.requestId);
376
+ const prNumber = member.batchedInto.prNumber;
377
+ if (batch?.status === "merged" && batch.mergeCommit) {
378
+ if (!active(member)) continue;
379
+ await store.compareAndSave(member, {
380
+ ...member,
381
+ workerToken,
382
+ status: "merged",
383
+ mergeCommit: batch.mergeCommit,
384
+ closeout: {
385
+ status: "not_applicable",
386
+ message: `Landed through batch #${prNumber}. This PR was closed, not merged; backlog items its commits complete are finalized by the batch closeout.`
387
+ }
388
+ });
389
+ continue;
390
+ }
391
+ if (batch && !closed.has(batch.id)) {
392
+ closed.add(batch.id);
393
+ await deps.closePullRequest(batch, prNumber).catch(() => void 0);
394
+ }
395
+ await store.compareAndSave(member, {
396
+ ...member,
397
+ workerToken,
398
+ batchedInto: void 0,
399
+ ...active(member) ? { lastError: `Batch #${prNumber} did not land; this PR lands on its own.` } : {}
400
+ });
401
+ }
402
+ }
403
+ async function selectBatchCandidates(waiting, first, deps) {
404
+ const candidates = [];
405
+ for (const request of waiting) {
406
+ if (candidates.length === MERGE_QUEUE_BATCH_LIMIT) break;
407
+ let eligible = batchable(request) && request.mergeMethod === first.mergeMethod && await deps.ownCiPassed(request) && openAt(
408
+ await deps.readPullRequest(request, request.prNumber),
409
+ request.reviewedSha,
410
+ request.targetBranch
411
+ );
412
+ if (eligible) {
413
+ try {
414
+ eligible = classifyBatchMemberCiStatus(await deps.readOwnCiStatus(request)) === "clear";
415
+ } catch {
416
+ return void 0;
417
+ }
418
+ }
419
+ if (eligible) candidates.push(request);
420
+ else if (request === first) return void 0;
421
+ }
422
+ return candidates;
423
+ }
424
+ function busy(requests) {
425
+ return requests.some(
426
+ (r) => r.status === "running" || r.outstandingOperation?.kind === "merge" || active(r) && r.batchMembers !== void 0
427
+ );
428
+ }
429
+ async function formMergeBatch(store, repository, deps) {
430
+ let requests = await store.list(repository);
431
+ if (busy(requests)) return void 0;
432
+ let waiting = requests.filter((r) => active(r) && !heldByBatch(r, requests));
433
+ let first = waiting[0];
434
+ if (!first || !batchable(first)) return void 0;
435
+ if (waiting.length < 2) {
436
+ if (!deps.collect || !await deps.enabled(first.repoRoot) || !await deps.ownCiPassed(first))
437
+ return void 0;
438
+ await deps.collect(first);
439
+ requests = await store.list(repository);
440
+ if (busy(requests)) return void 0;
441
+ waiting = requests.filter((r) => active(r) && !heldByBatch(r, requests));
442
+ first = waiting[0];
443
+ if (!first || !batchable(first) || waiting.length < 2) return void 0;
444
+ }
445
+ if (!await deps.enabled(first.repoRoot)) return void 0;
446
+ const candidates = await selectBatchCandidates(waiting, first, deps);
447
+ if (!candidates || candidates.length < 2) return void 0;
448
+ const members = await store.markBatchAttempt(
449
+ repository,
450
+ candidates.map((r) => r.id)
451
+ );
452
+ if (members.length < 2) return void 0;
453
+ let built;
454
+ try {
455
+ built = await deps.build({
456
+ root: first.repoRoot,
457
+ repository,
458
+ defaultBranch: first.targetBranch,
459
+ members: members.map((r) => ({ prNumber: r.prNumber, reviewedSha: r.reviewedSha }))
460
+ });
461
+ } catch (error) {
462
+ const reason = `Automatic batch not formed: ${error instanceof Error ? error.message : String(error)}`;
463
+ for (const member of members) {
464
+ await store.compareAndSave(member, { ...member, lastError: reason }).catch(() => void 0);
465
+ }
466
+ return void 0;
467
+ }
468
+ try {
469
+ return await store.enqueueBatch(
470
+ {
471
+ prNumber: built.batchPrNumber,
472
+ reviewedSha: built.batchHeadSha,
473
+ headBranch: built.batchBranch
474
+ },
475
+ members
476
+ );
477
+ } catch {
478
+ await deps.closePullRequest(first, built.batchPrNumber).catch(() => void 0);
479
+ return void 0;
480
+ }
481
+ }
482
+ async function assertBatchMembersUnchanged(batch, store, deps) {
483
+ if (!batch.batchMembers) return;
484
+ const pullRequests = /* @__PURE__ */ new Map();
485
+ for (const member of batch.batchMembers) {
486
+ pullRequests.set(member.prNumber, await deps.readPullRequest(batch, member.prNumber));
487
+ }
488
+ const requests = await store.list(batch.repository);
489
+ for (const member of batch.batchMembers) {
490
+ const record = requests.find((r) => r.id === member.requestId);
491
+ const pr = pullRequests.get(member.prNumber);
492
+ const unchanged = record?.status === "queued" && record.batchedInto?.requestId === batch.id && record.acceptedHeadSha === member.reviewedSha && pr !== void 0 && openAt(pr, member.reviewedSha, record.targetBranch);
493
+ if (!unchanged) {
494
+ throw new Error(
495
+ `Batch member #${member.prNumber} was withdrawn, superseded or changed; the batch is abandoned and its other members land on their own.`
496
+ );
497
+ }
498
+ }
499
+ }
500
+
501
+ // src/core/ci/merge-queue-closeout.ts
502
+ import { readdir } from "fs/promises";
503
+ import path from "path";
504
+ import { execa } from "execa";
505
+ import { z } from "zod";
506
+ var ShaSchema = z.string().regex(/^[a-f0-9]{40}$/i);
507
+ var PrEvidenceSchema = z.object({
508
+ state: z.enum(["OPEN", "CLOSED", "MERGED"]),
509
+ mergedAt: z.string().nullable(),
510
+ mergeCommit: z.object({ oid: ShaSchema }).nullable(),
511
+ headRefOid: ShaSchema,
512
+ commits: z.array(
513
+ z.object({
514
+ oid: z.string().optional(),
515
+ messageHeadline: z.string().optional(),
516
+ messageBody: z.string().optional()
517
+ })
518
+ )
519
+ });
520
+ function finalizeAction(input, itemId) {
521
+ return `build plan backlog finalize ${itemId} --pr ${input.prNumber} --commit ${input.mergeCommit}`;
522
+ }
523
+ function attention(input, message2, itemId) {
524
+ return {
525
+ status: "needs_attention",
526
+ ...itemId ? { itemId } : {},
527
+ message: `${message2}${itemId ? ` After resolving this, run: ${finalizeAction(input, itemId)}.` : ""}`
528
+ };
529
+ }
530
+ function closeoutEnv(input, repoRoot) {
531
+ return createLocalCiDetachedChildEnv({
532
+ GH_REPO: input.repository,
533
+ BUILD_PROJECT_ROOT: repoRoot,
534
+ BUILD_REPO: repoRoot,
535
+ BUILD_LOCAL_CI_WORKER: "1",
536
+ BUILD_CI_RECONCILING: "1"
537
+ });
538
+ }
539
+ async function runFinalizeCli(input, repoRoot, env, itemId, mergeCommit) {
540
+ const entry = process.argv[1];
541
+ if (!entry) throw new Error("Unable to resolve the current Build CLI entry path.");
542
+ return execa(
543
+ process.execPath,
544
+ [
545
+ path.resolve(entry),
546
+ "plan",
547
+ "backlog",
548
+ "finalize",
549
+ itemId,
550
+ "--pr",
551
+ String(input.prNumber),
552
+ "--commit",
553
+ mergeCommit
554
+ ],
555
+ { cwd: repoRoot, env, extendEnv: false, reject: false, timeout: DEFAULT_TIMEOUT_MS }
556
+ );
557
+ }
558
+ async function finalizeTrailerItems(input, repoRoot, satisfied = [], handled = []) {
559
+ const env = closeoutEnv(input, repoRoot);
560
+ const view = await execa(
561
+ "gh",
562
+ ["pr", "view", String(input.prNumber), "--json", "commits", "--repo", input.repository],
563
+ { cwd: repoRoot, env, extendEnv: false, timeout: PLANNING_GIT_NETWORK_TIMEOUT_MS }
564
+ );
565
+ const { commits } = PrEvidenceSchema.pick({ commits: true }).parse(JSON.parse(view.stdout));
566
+ const trailers = collectTrailerItemIds(commits);
567
+ const declared = trailers.declared.filter((id) => !handled.includes(id));
568
+ const reverted = trailers.reverted.filter((id) => !handled.includes(id));
569
+ const pending = declared.filter((id) => !satisfied.includes(id));
570
+ if (pending.length === 0 && reverted.length === 0 && satisfied.length === 0) {
571
+ return handled.length > 0 ? { status: "not_applicable" } : {
572
+ status: "not_applicable",
573
+ message: "No execution provenance or backlog item trailer for this PR."
574
+ };
575
+ }
576
+ const { eligible, skipped } = await triageTrailerItems(repoRoot, pending, {
577
+ prNumber: input.prNumber,
578
+ batchMemberPrs: input.batchMemberPrs
579
+ });
580
+ for (const itemId of reverted) {
581
+ skipped.push({ itemId, reason: "its trailer commit was reverted; item left pending" });
582
+ }
583
+ const finalized = [];
584
+ const failed = [];
585
+ for (const itemId of eligible) {
586
+ const result = await runFinalizeCli(input, repoRoot, env, itemId, input.mergeCommit);
587
+ if (result.exitCode === 0) {
588
+ finalized.push(itemId);
589
+ continue;
590
+ }
591
+ const detail = (result.stderr.trim() || result.stdout.trim()).split(/\r?\n/).pop();
592
+ failed.push(`${itemId}${detail ? ` (${detail})` : ""}; run: ${finalizeAction(input, itemId)}`);
593
+ }
594
+ const message2 = [
595
+ satisfied.length > 0 ? `Already finalized for this landing: ${satisfied.join(", ")}.` : "",
596
+ finalized.length > 0 ? `Finalized from commit trailers: ${finalized.join(", ")}.` : "",
597
+ failed.length > 0 ? `Finalize failed for ${failed.join("; ")}.` : "",
598
+ skipped.length > 0 ? `Not finalized: ${skipped.map((entry) => `${entry.itemId} (${entry.reason})`).join(", ")}.` : ""
599
+ ].filter(Boolean).join(" ");
600
+ const status = failed.length > 0 ? "needs_attention" : finalized.length + satisfied.length > 0 ? "done" : "not_applicable";
601
+ const everyItem = [...satisfied, ...eligible];
602
+ return {
603
+ status,
604
+ ...everyItem.length === 1 ? { itemId: everyItem[0] } : {},
605
+ ...message2 ? { message: message2 } : {}
606
+ };
607
+ }
608
+ function recordedDisposition(input, record) {
609
+ const itemId = record.backlogItemId;
610
+ const matchingCommit = record.mergeCommit?.toLowerCase() === input.mergeCommit.toLowerCase();
611
+ if (record.prNumber !== input.prNumber || record.mergeCommit && !matchingCommit) {
612
+ return attention(
613
+ input,
614
+ "Existing provenance identifies a different PR or merge commit.",
615
+ itemId
616
+ );
617
+ }
618
+ if (record.status === "archived" && matchingCommit && record.lastCompletedStep === "finalize" && !record.failedAtStep && !record.errorMessage && !record.archiveSyncWarning) {
619
+ return { status: "done", itemId };
620
+ }
621
+ if (record.status === "archive-sync-pending") {
622
+ return attention(
623
+ input,
624
+ record.archiveSyncPrNumber ? `Bookkeeping awaits archive sync PR #${record.archiveSyncPrNumber}. Merge that PR through its normal reviewed landing path.` : "Bookkeeping publication is pending without a recorded archive sync PR.",
625
+ itemId
626
+ );
627
+ }
628
+ if (record.status !== "pr-open") {
629
+ return attention(
630
+ input,
631
+ `Provenance status is ${record.status}; automatic finalize is ineligible.`,
632
+ itemId
633
+ );
634
+ }
635
+ return void 0;
636
+ }
637
+ async function associatedRecords(repoRoot, prNumber) {
638
+ const directory = path.dirname(getBacklogExecutionProvenancePath(repoRoot, ""));
639
+ if (directory !== path.join(repoRoot, ".build-project", "provenance")) {
640
+ throw new Error("Shared provenance root does not match the canonical repository root.");
641
+ }
642
+ let entries;
643
+ try {
644
+ entries = await readdir(directory);
645
+ } catch (error) {
646
+ if (error.code === "ENOENT") return [];
647
+ throw error;
648
+ }
649
+ const matches = [];
650
+ for (const entry of entries) {
651
+ if (!entry.endsWith(".json")) continue;
652
+ const itemId = entry.slice(0, -".json".length);
653
+ const record = await loadBacklogExecutionProvenance(repoRoot, itemId, {
654
+ cleanupTerminalLocks: false
655
+ });
656
+ if (record?.prNumber !== prNumber) continue;
657
+ TaskIdSchema.parse(record.backlogItemId);
658
+ matches.push(record);
659
+ }
660
+ return matches;
661
+ }
662
+ async function finalizeMergedRequest(input) {
663
+ let itemId;
664
+ try {
665
+ const repoRoot = await resolveCanonicalMainRepoRoot(path.resolve(input.repoRoot));
666
+ const repository = await resolveGitHubRepositoryFromRemote(repoRoot, "origin");
667
+ if (repository.toLowerCase() !== input.repository.toLowerCase()) {
668
+ return attention(input, "Canonical repository origin differs from the merged request.");
669
+ }
670
+ const records = await associatedRecords(repoRoot, input.prNumber);
671
+ const completed = records.filter(
672
+ (candidate) => recordedDisposition(input, candidate)?.status === "done"
673
+ );
674
+ const open2 = records.filter((candidate) => !completed.includes(candidate));
675
+ if (open2.length === 0) {
676
+ return await finalizeTrailerItems(
677
+ input,
678
+ repoRoot,
679
+ completed.map((candidate) => candidate.backlogItemId)
680
+ );
681
+ }
682
+ if (open2.length !== 1) {
683
+ return attention(
684
+ input,
685
+ `PR #${input.prNumber} has ambiguous execution provenance: ${open2.map((record2) => record2.backlogItemId).sort().join(", ")}. Resolve the association before finalizing.`
686
+ );
687
+ }
688
+ const record = open2[0];
689
+ itemId = record.backlogItemId;
690
+ const disposition = recordedDisposition(input, record);
691
+ if (disposition) return disposition;
692
+ const defaultBranch = await resolveDefaultBranch(repoRoot);
693
+ const fetched = await execGit(["fetch", "origin", defaultBranch], {
694
+ cwd: repoRoot,
695
+ timeoutMs: PLANNING_GIT_NETWORK_TIMEOUT_MS
696
+ });
697
+ if (fetched.exitCode !== 0) {
698
+ return attention(
699
+ input,
700
+ `Cannot refresh merge evidence: ${fetched.stderr || fetched.stdout}`,
701
+ itemId
702
+ );
703
+ }
704
+ const env = closeoutEnv(input, repoRoot);
705
+ let headSha;
706
+ const runEvidence = (async (command, args) => {
707
+ if (command === "git") {
708
+ const result3 = await execGit(args, { cwd: repoRoot });
709
+ if (result3.exitCode !== 0) throw new Error(result3.stderr || result3.stdout);
710
+ return result3;
711
+ }
712
+ if (command !== "gh") throw new Error(`Unexpected evidence command: ${command}`);
713
+ const result2 = await execa(
714
+ "gh",
715
+ args.filter((arg, index) => arg !== "--repo" && args[index - 1] !== "--repo").map(
716
+ (arg, index, boundArgs) => boundArgs[index - 1] === "--json" ? `${arg},headRefOid` : arg
717
+ ).concat("--repo", input.repository),
718
+ { cwd: repoRoot, env, extendEnv: false, timeout: PLANNING_GIT_NETWORK_TIMEOUT_MS }
719
+ );
720
+ const parsed = PrEvidenceSchema.parse(JSON.parse(result2.stdout));
721
+ headSha = parsed.headRefOid;
722
+ return result2;
723
+ });
724
+ const evidence = await collectAutoFinalizeMergeEvidence(
725
+ runEvidence,
726
+ repoRoot,
727
+ input.prNumber,
728
+ input.mergeCommit,
729
+ defaultBranch,
730
+ itemId
731
+ );
732
+ const eligibility = evaluateAutoFinalizeEligibility(record, input.mergeCommit, evidence);
733
+ if (!eligibility.eligible) return attention(input, eligibility.reason, itemId);
734
+ if (!headSha) return attention(input, "Unable to confirm the merged PR head.", itemId);
735
+ if (headSha.toLowerCase() !== input.reviewedSha.toLowerCase()) {
736
+ const ancestry = await execGit(["merge-base", "--is-ancestor", input.reviewedSha, headSha], {
737
+ cwd: repoRoot
738
+ });
739
+ if (ancestry.exitCode !== 0) {
740
+ return attention(
741
+ input,
742
+ "Cannot associate the reviewed SHA with the merged PR head.",
743
+ itemId
744
+ );
745
+ }
746
+ }
747
+ const current = await loadBacklogExecutionProvenance(repoRoot, itemId, {
748
+ cleanupTerminalLocks: false
749
+ });
750
+ if (!current)
751
+ return attention(input, "Execution provenance disappeared before finalize.", itemId);
752
+ const currentDisposition = recordedDisposition(input, current);
753
+ if (currentDisposition) return currentDisposition;
754
+ const result = await runFinalizeCli(input, repoRoot, env, itemId, eligibility.mergeCommit);
755
+ const after = await loadBacklogExecutionProvenance(repoRoot, itemId, {
756
+ cleanupTerminalLocks: false
757
+ });
758
+ const finalDisposition = after ? recordedDisposition(input, after) : void 0;
759
+ if (result.exitCode === 0 && finalDisposition?.status === "done") {
760
+ const extra = await finalizeTrailerItems(input, repoRoot, [], [itemId]).catch(
761
+ (error) => ({
762
+ status: "needs_attention",
763
+ message: `Trailer check failed: ${error instanceof Error ? error.message : String(error)}`
764
+ })
765
+ );
766
+ return extra.status === "needs_attention" ? { ...extra, itemId } : { ...finalDisposition, ...extra.message ? { message: extra.message } : {} };
767
+ }
768
+ if (finalDisposition?.status === "needs_attention") return finalDisposition;
769
+ const detail = after?.errorMessage || after?.archiveSyncWarning || result.stderr.trim() || result.stdout.trim() || "Finalization did not confirm completed archive publication.";
770
+ return attention(input, detail, itemId);
771
+ } catch (error) {
772
+ return attention(input, error instanceof Error ? error.message : String(error), itemId);
773
+ }
774
+ }
775
+ function finalizeMergedQueueRequest(request) {
776
+ return finalizeMergedRequest({
777
+ repoRoot: request.repoRoot,
778
+ repository: request.repository,
779
+ prNumber: request.prNumber,
780
+ reviewedSha: request.reviewedSha,
781
+ mergeCommit: request.mergeCommit,
782
+ batchMemberPrs: request.batchMembers?.map((member) => member.prNumber) ?? request.batchPrNumbers
783
+ });
784
+ }
785
+
786
+ // src/core/ci/merge-queue-lease.ts
787
+ import { createHash } from "crypto";
788
+ import { readFile as readFile2 } from "fs/promises";
789
+ import path3 from "path";
790
+
791
+ // src/core/ci/landing-lease.ts
792
+ import { randomUUID } from "crypto";
793
+ import { link, mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
794
+ import path2 from "path";
795
+ import { z as z2 } from "zod";
796
+ var LANDING_LEASE_SCHEMA_VERSION = 1;
797
+ var DEFAULT_LANDING_LEASE_REF_PREFIX = "refs/build/landing/";
798
+ var DEFAULT_LANDING_LEASE_EXPIRY_MS = 45e4;
799
+ var DEFAULT_GIT_TIMEOUT_MS = 3e4;
800
+ var LandingLeaseRecordSchema = z2.object({
801
+ schemaVersion: z2.literal(LANDING_LEASE_SCHEMA_VERSION),
802
+ state: z2.enum(["held", "free"]),
803
+ token: z2.string().min(1),
804
+ hostId: z2.string().min(1),
805
+ requestId: z2.string().min(1),
806
+ pr: z2.number().int().positive(),
807
+ head: z2.string().min(1),
808
+ base: z2.string().min(1),
809
+ /** Starts at 0 on acquire; each renewal and the release add one. */
810
+ generation: z2.number().int().nonnegative()
811
+ });
812
+ function landingLeaseRef(context) {
813
+ const ref = context.ref ?? `${DEFAULT_LANDING_LEASE_REF_PREFIX}${context.target}`;
814
+ const invalid = !/^refs\/[A-Za-z0-9._/-]+$/.test(ref) || /\.\.|\/\/|\/\.|\/$|\.$|\.lock(\/|$)/.test(ref);
815
+ if (invalid) throw new Error(`Invalid landing lease ref: ${ref}`);
816
+ return ref;
817
+ }
818
+ async function loadLandingHostId(filePath = path2.join(getLocalCiStateRoot(), "landing-host-id")) {
819
+ const read = async () => {
820
+ try {
821
+ const value = (await readFile(filePath, "utf8")).trim();
822
+ return z2.string().uuid().safeParse(value).success ? value : void 0;
823
+ } catch (error) {
824
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
825
+ return void 0;
826
+ }
827
+ throw error;
828
+ }
829
+ };
830
+ const existing = await read();
831
+ if (existing) return existing;
832
+ await mkdir(path2.dirname(filePath), { recursive: true, mode: 448 });
833
+ const created = randomUUID();
834
+ const temporary = `${filePath}.${randomUUID()}.tmp`;
835
+ try {
836
+ await writeFile(temporary, `${created}
837
+ `, { flag: "wx", mode: 384, flush: true });
838
+ try {
839
+ await link(temporary, filePath);
840
+ return created;
841
+ } catch (error) {
842
+ if (!(error && typeof error === "object" && "code" in error && error.code === "EEXIST")) {
843
+ throw error;
844
+ }
845
+ }
846
+ const winner = await read();
847
+ if (winner) return winner;
848
+ await rename(temporary, filePath);
849
+ return created;
850
+ } finally {
851
+ await unlink(temporary).catch(() => void 0);
852
+ }
853
+ }
854
+ async function runGit(context, args, input) {
855
+ try {
856
+ const result = await execGit(args, {
857
+ cwd: context.cwd,
858
+ timeoutMs: context.timeoutMs ?? DEFAULT_GIT_TIMEOUT_MS,
859
+ input,
860
+ // Never let a credential prompt or the user's pre-push hook stall or veto a lease write.
861
+ env: { GIT_TERMINAL_PROMPT: "0", LC_ALL: "C" }
862
+ });
863
+ if (result.exitCode === 0) return { ok: true, stdout: result.stdout };
864
+ return { ok: false, message: (result.stderr || result.stdout).trim().replace(/\s+/g, " ") };
865
+ } catch (error) {
866
+ const message2 = error instanceof GitExecError ? error.message : String(error);
867
+ return { ok: false, message: message2 };
868
+ }
869
+ }
870
+ function parseRecord(message2) {
871
+ let json;
872
+ try {
873
+ json = JSON.parse(message2);
874
+ } catch {
875
+ return { error: "lease commit message is not JSON" };
876
+ }
877
+ const parsed = LandingLeaseRecordSchema.safeParse(json);
878
+ return parsed.success ? parsed.data : { error: "lease record failed schema validation" };
879
+ }
880
+ async function readLandingLease(context) {
881
+ const ref = landingLeaseRef(context);
882
+ const listed = await runGit(context, ["ls-remote", "--", context.remote, ref]);
883
+ if (!listed.ok) return { status: "unavailable", message: listed.message };
884
+ const line = listed.stdout.split("\n").find((entry) => entry.endsWith(` ${ref}`));
885
+ if (!line) return { status: "absent" };
886
+ const oid = line.split(" ")[0]?.trim() ?? "";
887
+ if (!/^[0-9a-f]{40,64}$/.test(oid)) {
888
+ return { status: "invalid", oid, reason: "remote advertised a malformed object id" };
889
+ }
890
+ if (!(await runGit(context, ["cat-file", "-e", `${oid}^{commit}`])).ok) {
891
+ const fetched = await runGit(context, [
892
+ "fetch",
893
+ "--no-tags",
894
+ "--no-write-fetch-head",
895
+ "--refmap=",
896
+ "--",
897
+ context.remote,
898
+ oid
899
+ ]);
900
+ if (!fetched.ok) return { status: "unavailable", message: fetched.message };
901
+ }
902
+ const raw = await runGit(context, ["cat-file", "commit", oid]);
903
+ if (!raw.ok) return { status: "invalid", oid, reason: raw.message };
904
+ const split = raw.stdout.indexOf("\n\n");
905
+ const record = parseRecord(split < 0 ? "" : raw.stdout.slice(split + 2).trim());
906
+ if ("error" in record) return { status: "invalid", oid, reason: record.error };
907
+ return { status: "present", oid, record };
908
+ }
909
+ async function stageLeaseCommit(context, record) {
910
+ const tree = await runGit(context, ["mktree"], "");
911
+ if (!tree.ok) return { status: "unavailable", message: tree.message };
912
+ const commit = await runGit(context, [
913
+ "commit-tree",
914
+ tree.stdout.trim(),
915
+ "-m",
916
+ JSON.stringify(record)
917
+ ]);
918
+ if (!commit.ok) return { status: "unavailable", message: commit.message };
919
+ return commit.stdout.trim();
920
+ }
921
+ async function writeLease(context, expectedOid, record) {
922
+ const ref = landingLeaseRef(context);
923
+ const staged = await stageLeaseCommit(context, record);
924
+ if (typeof staged !== "string") return staged;
925
+ const pushed = await runGit(context, [
926
+ "push",
927
+ "--no-verify",
928
+ `--force-with-lease=${ref}:${expectedOid ?? ""}`,
929
+ "--",
930
+ context.remote,
931
+ `${staged}:${ref}`
932
+ ]);
933
+ if (pushed.ok) return { status: "written", handle: { oid: staged, record } };
934
+ const readback = await readLandingLease(context);
935
+ if (readback.status === "unavailable") return readback;
936
+ if (readback.status === "present" && readback.record.token === record.token && readback.record.generation === record.generation && readback.record.state === record.state) {
937
+ return { status: "written", handle: { oid: readback.oid, record: readback.record } };
938
+ }
939
+ const observedOid = readback.status === "absent" ? void 0 : readback.oid;
940
+ if (observedOid !== expectedOid) return { status: "lost-race", observation: readback };
941
+ return { status: "unavailable", message: pushed.message };
942
+ }
943
+ async function acquireLandingLease(context, claim, options = {}) {
944
+ const observation = options.observed ?? await readLandingLease(context);
945
+ if (observation.status === "unavailable") return observation;
946
+ const acquirable = observation.status === "absent" || observation.status === "present" && observation.record.state === "free" || options.takeOver === true;
947
+ if (!acquirable) return { status: "held", observation };
948
+ const written = await writeLease(
949
+ context,
950
+ observation.status === "absent" ? void 0 : observation.oid,
951
+ {
952
+ schemaVersion: LANDING_LEASE_SCHEMA_VERSION,
953
+ state: "held",
954
+ token: randomUUID(),
955
+ hostId: context.hostId,
956
+ requestId: claim.requestId,
957
+ pr: claim.pr,
958
+ head: claim.head,
959
+ base: claim.base,
960
+ generation: 0
961
+ }
962
+ );
963
+ if (written.status === "written") return { status: "acquired", handle: written.handle };
964
+ if (written.status === "lost-race")
965
+ return { status: "contended", observation: written.observation };
966
+ return written;
967
+ }
968
+ async function advanceOwnedLease(context, held, state) {
969
+ const current = await readLandingLease(context);
970
+ if (current.status === "unavailable") return current;
971
+ if (current.status !== "present" || current.oid !== held.oid || current.record.token !== held.record.token || current.record.state !== "held") {
972
+ return { status: "lost-race", observation: current };
973
+ }
974
+ return await writeLease(context, current.oid, {
975
+ ...current.record,
976
+ state,
977
+ generation: current.record.generation + 1
978
+ });
979
+ }
980
+ async function renewLandingLease(context, held) {
981
+ const written = await advanceOwnedLease(context, held, "held");
982
+ if (written.status === "written") return { status: "renewed", handle: written.handle };
983
+ if (written.status === "lost-race") return { status: "lost", observation: written.observation };
984
+ return written;
985
+ }
986
+ async function releaseLandingLease(context, held) {
987
+ const written = await advanceOwnedLease(context, held, "free");
988
+ if (written.status === "written") return { status: "released", handle: written.handle };
989
+ if (written.status === "lost-race") return { status: "lost", observation: written.observation };
990
+ return written;
991
+ }
992
+ var LandingLeaseExpiryTracker = class {
993
+ expiryMs;
994
+ nowMs;
995
+ watched;
996
+ constructor(options = {}) {
997
+ this.expiryMs = options.expiryMs ?? DEFAULT_LANDING_LEASE_EXPIRY_MS;
998
+ this.nowMs = options.nowMs ?? (() => performance.now());
999
+ }
1000
+ /** True when this commit has been seen unchanged for at least the expiry duration. */
1001
+ observe(observation) {
1002
+ if (observation.status === "unavailable") return false;
1003
+ const oid = observation.status === "invalid" || observation.status === "present" && observation.record.state === "held" ? observation.oid : void 0;
1004
+ if (oid === void 0) {
1005
+ this.watched = void 0;
1006
+ return false;
1007
+ }
1008
+ const now = this.nowMs();
1009
+ if (this.watched?.oid !== oid) this.watched = { oid, firstSeenMs: now };
1010
+ return now - this.watched.firstSeenMs >= this.expiryMs;
1011
+ }
1012
+ };
1013
+
1014
+ // src/core/ci/merge-queue-lease.ts
1015
+ var LANDING_LEASE_POLL_MS = 3e4;
1016
+ var LANDING_LEASE_BACKOFF_MS = 6e4;
1017
+ var LANDING_LEASE_RENEW_MS = 6e4;
1018
+ var LANDING_LEASE_MAX_HOLD_MS = 2 * 60 * 60 * 1e3;
1019
+ var MAX_WAIT_MS = 6 * 60 * 60 * 1e3;
1020
+ var MAX_RETRIES = 8;
1021
+ var LeaseRetry = class extends Error {
1022
+ };
1023
+ function fileReleaseMemory(file) {
1024
+ return {
1025
+ read: async () => {
1026
+ try {
1027
+ const value = JSON.parse(await readFile2(file, "utf8")).releasedAtMs;
1028
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1029
+ } catch {
1030
+ return void 0;
1031
+ }
1032
+ },
1033
+ write: (releasedAtMs) => atomicWriteJson(file, { releasedAtMs })
1034
+ };
1035
+ }
1036
+ function createMergeQueueLeaseDependencies(input) {
1037
+ const target = createHash("sha256").update(input.targetBranch).digest("hex");
1038
+ return {
1039
+ context: async () => ({
1040
+ cwd: input.repoRoot,
1041
+ remote: "origin",
1042
+ target: input.targetBranch,
1043
+ hostId: await loadLandingHostId()
1044
+ }),
1045
+ releaseMemory: fileReleaseMemory(
1046
+ path3.join(
1047
+ input.stateRoot,
1048
+ "landing-lease-releases",
1049
+ repoStateKey(input.repository),
1050
+ `${target}.json`
1051
+ )
1052
+ )
1053
+ };
1054
+ }
1055
+ function defaultLog(message2) {
1056
+ emitCustomWarning("INFO" /* INFO */, "configuration" /* CONFIGURATION */, message2, {
1057
+ subsystem: "ci/merge-queue-lease",
1058
+ executionPhase: "execution"
1059
+ });
1060
+ }
1061
+ var RequestLandingLease = class {
1062
+ constructor(deps, env) {
1063
+ this.deps = deps;
1064
+ this.env = env;
1065
+ this.inactive = deps === void 0;
1066
+ this.ops = {
1067
+ read: readLandingLease,
1068
+ acquire: acquireLandingLease,
1069
+ renew: renewLandingLease,
1070
+ release: releaseLandingLease,
1071
+ ...deps?.ops
1072
+ };
1073
+ }
1074
+ deps;
1075
+ env;
1076
+ ops;
1077
+ context;
1078
+ handle;
1079
+ heldSinceMs = 0;
1080
+ renewedAtMs = 0;
1081
+ renewing;
1082
+ lost = false;
1083
+ lostController = new AbortController();
1084
+ /** A held lease this request may replace: its own earlier lease, or an expired one. */
1085
+ replaceable;
1086
+ retries = 0;
1087
+ inactive;
1088
+ note;
1089
+ /** Shown in `build ci status` when the lease could not be used for this request. */
1090
+ statusNote() {
1091
+ return this.note;
1092
+ }
1093
+ /** The gate's signal plus this lease's loss: a lost lease stops the gate like moved main. */
1094
+ withSignal(signal) {
1095
+ return AbortSignal.any([signal, this.lostController.signal]);
1096
+ }
1097
+ /**
1098
+ * Wait until the lease is free, expired, or this request's own, holding neither capacity nor
1099
+ * the turn. Returns early (fail open) when the lease is unavailable or the wait is exhausted.
1100
+ */
1101
+ async wait(input) {
1102
+ if (this.inactive || !this.deps) return;
1103
+ try {
1104
+ this.context ??= await this.deps.context();
1105
+ } catch (error) {
1106
+ this.markUnavailable(`no host identity: ${describe(error)}`);
1107
+ return;
1108
+ }
1109
+ const context = this.context;
1110
+ const tracker = new LandingLeaseExpiryTracker({
1111
+ expiryMs: this.deps.expiryMs,
1112
+ nowMs: this.env.nowMs
1113
+ });
1114
+ const deadline = this.env.nowMs() + (this.deps.maxWaitMs ?? MAX_WAIT_MS);
1115
+ let announced;
1116
+ this.replaceable = void 0;
1117
+ const backoffUntilMs = this.env.nowMs() + await this.backoffRemainingMs();
1118
+ for (; ; ) {
1119
+ await input.checkContinue();
1120
+ const seen = await this.ops.read(context);
1121
+ if (seen.status === "unavailable") {
1122
+ this.markUnavailable(seen.message);
1123
+ return;
1124
+ }
1125
+ const expired = tracker.observe(seen);
1126
+ const held = seen.status === "present" && seen.record.state === "held" ? seen.record : void 0;
1127
+ if (!held && seen.status !== "invalid") {
1128
+ const pause = backoffUntilMs - this.env.nowMs();
1129
+ if (pause <= 0 || seen.status === "present" && seen.record.hostId !== context.hostId) {
1130
+ return;
1131
+ }
1132
+ await this.env.pause(pause);
1133
+ continue;
1134
+ }
1135
+ if (held?.hostId === context.hostId && held.requestId === this.env.requestId || expired) {
1136
+ this.replaceable = seen;
1137
+ return;
1138
+ }
1139
+ const text = held ? `Waiting for another host (lease): host ${held.hostId.slice(0, 8)} is landing PR #${held.pr}.` : "Waiting for another host (lease): the lease record is unreadable; waiting for it to expire.";
1140
+ if (text !== announced) {
1141
+ announced = text;
1142
+ await input.announce(text);
1143
+ }
1144
+ if (this.env.nowMs() >= deadline) {
1145
+ this.markUnavailable("waited too long for the lease");
1146
+ return;
1147
+ }
1148
+ await this.env.pause(this.deps.pollMs ?? LANDING_LEASE_POLL_MS);
1149
+ }
1150
+ }
1151
+ /** Take the lease after capacity is held. Throws {@link LeaseRetry} when another host won. */
1152
+ async acquire(baseSha) {
1153
+ if (this.inactive || !this.context) return;
1154
+ const replaceable = this.replaceable;
1155
+ this.replaceable = void 0;
1156
+ let result;
1157
+ try {
1158
+ result = await this.ops.acquire(
1159
+ this.context,
1160
+ this.env.claim(baseSha),
1161
+ replaceable ? { observed: replaceable, takeOver: true } : {}
1162
+ );
1163
+ } catch (error) {
1164
+ result = { status: "unavailable", message: describe(error) };
1165
+ }
1166
+ if (result.status === "acquired") {
1167
+ this.handle = result.handle;
1168
+ this.heldSinceMs = this.renewedAtMs = this.env.nowMs();
1169
+ this.lost = false;
1170
+ } else if (result.status === "unavailable") {
1171
+ this.markUnavailable(result.message);
1172
+ } else {
1173
+ this.requeue("another host took the lease first");
1174
+ }
1175
+ }
1176
+ /**
1177
+ * Renew a held lease. `force` is the watcher tick; otherwise at most once per interval, so the
1178
+ * processor's guard keeps a long prepare or wait alive without a second timer. Never throws:
1179
+ * `lost` stops the current round, an unreachable remote is retried on the next call.
1180
+ */
1181
+ async renew(force = false) {
1182
+ if (!this.handle || this.lost) return;
1183
+ if (!force && this.env.nowMs() - this.renewedAtMs < (this.deps?.renewMs ?? LANDING_LEASE_RENEW_MS)) {
1184
+ return;
1185
+ }
1186
+ this.renewing ??= this.renewOnce().finally(() => {
1187
+ this.renewing = void 0;
1188
+ });
1189
+ await this.renewing;
1190
+ }
1191
+ async renewOnce() {
1192
+ const held = this.handle;
1193
+ if (!held || !this.context) return;
1194
+ try {
1195
+ const result = await this.ops.renew(this.context, held);
1196
+ if (result.status === "renewed") {
1197
+ this.handle = result.handle;
1198
+ this.renewedAtMs = this.env.nowMs();
1199
+ } else if (result.status === "lost") {
1200
+ this.lost = true;
1201
+ this.lostController.abort(
1202
+ new Error("The cross-host landing lease was taken over; waiting for it again.")
1203
+ );
1204
+ }
1205
+ } catch {
1206
+ }
1207
+ }
1208
+ /**
1209
+ * `round`: before starting a round, stop if the lease was lost or held past the maximum.
1210
+ * `merge`: renew now and stop if the lease was lost, so no merge follows a lost lease that
1211
+ * could be detected. Never called after a merge was dispatched.
1212
+ */
1213
+ async checkpoint(point) {
1214
+ if (!this.handle) return;
1215
+ if (point === "merge") await this.renew(true);
1216
+ this.throwIfLost();
1217
+ if (point === "round" && this.env.nowMs() - this.heldSinceMs >= this.holdLimitMs()) {
1218
+ this.requeue("held the lease for the maximum time");
1219
+ }
1220
+ }
1221
+ throwIfLost() {
1222
+ if (this.lost) this.requeue("the lease was taken over");
1223
+ }
1224
+ /** Best-effort; never throws, so it cannot mask the request's real outcome. */
1225
+ async release() {
1226
+ await this.renewing;
1227
+ const held = this.handle;
1228
+ this.handle = void 0;
1229
+ this.lost = false;
1230
+ this.lostController = new AbortController();
1231
+ if (!held || !this.context) return;
1232
+ try {
1233
+ const result = await this.ops.release(this.context, held);
1234
+ if (result.status === "released") {
1235
+ await this.deps?.releaseMemory?.write((this.deps.wallNowMs ?? Date.now)());
1236
+ }
1237
+ } catch {
1238
+ }
1239
+ }
1240
+ holdLimitMs() {
1241
+ return this.deps?.maxHoldMs ?? LANDING_LEASE_MAX_HOLD_MS;
1242
+ }
1243
+ /** Count a re-queue; past the cap the request stops using the lease instead of looping. */
1244
+ requeue(reason) {
1245
+ this.retries += 1;
1246
+ if (this.retries > MAX_RETRIES) {
1247
+ this.markUnavailable("too many lease re-queues");
1248
+ return;
1249
+ }
1250
+ throw new LeaseRetry(`Landing lease: ${reason}.`);
1251
+ }
1252
+ /** Back-off left since this host's last release, read once when a wait starts. */
1253
+ async backoffRemainingMs() {
1254
+ const backoffMs = this.deps?.backoffMs ?? LANDING_LEASE_BACKOFF_MS;
1255
+ try {
1256
+ const released = await this.deps?.releaseMemory?.read();
1257
+ if (released === void 0) return 0;
1258
+ const elapsed = (this.deps?.wallNowMs ?? Date.now)() - released;
1259
+ return Math.min(backoffMs, Math.max(0, backoffMs - elapsed));
1260
+ } catch {
1261
+ return 0;
1262
+ }
1263
+ }
1264
+ markUnavailable(detail) {
1265
+ this.inactive = true;
1266
+ if (this.note) return;
1267
+ const scrubbed = detail.replace(/\/\/[^/@\s]+@/g, "//").slice(0, 200);
1268
+ this.note = "Landing lease unavailable; landing without cross-host coordination.";
1269
+ (this.deps?.log ?? defaultLog)(`${this.note} (${scrubbed})`);
1270
+ }
1271
+ };
1272
+ function describe(error) {
1273
+ return error instanceof Error ? error.message : String(error);
1274
+ }
1275
+
1276
+ // src/core/ci/merge-queue-processor.ts
1277
+ import { setTimeout as delay } from "timers/promises";
1278
+
1279
+ // src/core/ci/main-watcher.ts
1280
+ var DEFAULT_MAIN_WATCH_INTERVAL_MS = 6e4;
1281
+ var LS_REMOTE_TIMEOUT_MS = 2e4;
1282
+ var SHA_PATTERN = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i;
1283
+ var MainReadError = class extends Error {
1284
+ constructor(message2) {
1285
+ super(message2);
1286
+ this.name = "MainReadError";
1287
+ }
1288
+ };
1289
+ var MainMovedError = class extends Error {
1290
+ constructor(fromSha, toSha) {
1291
+ super(`Target branch moved from ${fromSha.slice(0, 12)} to ${toSha.slice(0, 12)}.`);
1292
+ this.fromSha = fromSha;
1293
+ this.toSha = toSha;
1294
+ this.name = "MainMovedError";
1295
+ }
1296
+ fromSha;
1297
+ toSha;
1298
+ };
1299
+ function normalizeSha(value, source) {
1300
+ const sha = value.trim();
1301
+ if (!SHA_PATTERN.test(sha)) throw new MainReadError(`${source} returned a malformed SHA.`);
1302
+ return sha.toLowerCase();
1303
+ }
1304
+ async function readRemoteBranchTip(input) {
1305
+ const ref = `refs/heads/${input.branch}`;
1306
+ const result = await execGit(["ls-remote", input.remote ?? "origin", ref], {
1307
+ cwd: input.repoRoot,
1308
+ timeoutMs: input.timeoutMs ?? LS_REMOTE_TIMEOUT_MS,
1309
+ env: { GIT_TERMINAL_PROMPT: "0" }
1310
+ });
1311
+ if (result.exitCode !== 0) {
1312
+ throw new MainReadError(`git ls-remote failed: ${result.stderr.trim() || result.exitCode}`);
1313
+ }
1314
+ const lines = result.stdout.split("\n").map((line) => line.trim().split(/\s+/)).filter((fields) => fields.length === 2 && fields[1] === ref);
1315
+ if (lines.length !== 1) {
1316
+ throw new MainReadError(`git ls-remote did not return exactly one ${ref}.`);
1317
+ }
1318
+ return normalizeSha(lines[0]?.[0] ?? "", "git ls-remote");
1319
+ }
1320
+ var MainWatcher = class {
1321
+ constructor(options) {
1322
+ this.options = options;
1323
+ this.baseSha = options.baseSha.toLowerCase();
1324
+ }
1325
+ options;
1326
+ controller = new AbortController();
1327
+ baseSha;
1328
+ timer;
1329
+ stopped = false;
1330
+ movedToSha;
1331
+ failures = 0;
1332
+ failure;
1333
+ /** Aborts, with a {@link MainMovedError} reason, once movement is confirmed. */
1334
+ get signal() {
1335
+ return this.controller.signal;
1336
+ }
1337
+ get moved() {
1338
+ return this.movedToSha !== void 0;
1339
+ }
1340
+ get errorCount() {
1341
+ return this.failures;
1342
+ }
1343
+ get lastError() {
1344
+ return this.failure;
1345
+ }
1346
+ throwIfMoved() {
1347
+ if (this.movedToSha !== void 0) throw new MainMovedError(this.baseSha, this.movedToSha);
1348
+ }
1349
+ start() {
1350
+ if (!this.stopped && !this.timer) this.schedule();
1351
+ return this;
1352
+ }
1353
+ /** After stop() this watcher can never abort anything, even from an in-flight read. */
1354
+ stop() {
1355
+ this.stopped = true;
1356
+ if (this.timer) clearTimeout(this.timer);
1357
+ this.timer = void 0;
1358
+ }
1359
+ /** One check. Exposed so callers and tests can drive it without the timer. */
1360
+ async tick() {
1361
+ if (this.moved) return "moved";
1362
+ let confirmed;
1363
+ try {
1364
+ const tip = normalizeSha(await this.options.readTip(), "Branch tip read");
1365
+ if (tip === this.baseSha) return "unchanged";
1366
+ confirmed = normalizeSha(await this.options.confirmBase(), "Base read");
1367
+ } catch (error) {
1368
+ this.failures += 1;
1369
+ this.failure = error;
1370
+ return "error";
1371
+ }
1372
+ if (confirmed === this.baseSha) return "unchanged";
1373
+ if (this.stopped) return "unchanged";
1374
+ this.movedToSha = confirmed;
1375
+ this.controller.abort(new MainMovedError(this.baseSha, confirmed));
1376
+ return "moved";
1377
+ }
1378
+ schedule() {
1379
+ const timer = setTimeout(async () => {
1380
+ this.timer = void 0;
1381
+ await this.tick();
1382
+ try {
1383
+ if (!this.stopped) await this.options.onTick?.();
1384
+ } catch {
1385
+ }
1386
+ if (!this.stopped && !this.moved) this.schedule();
1387
+ }, this.options.intervalMs ?? DEFAULT_MAIN_WATCH_INTERVAL_MS);
1388
+ timer.unref?.();
1389
+ this.timer = timer;
1390
+ }
1391
+ };
1392
+
1393
+ // src/core/ci/merge-queue-processor-helpers.ts
1394
+ var RequestChanged = class extends Error {
1395
+ };
1396
+ var BaseMoved = class extends Error {
1397
+ };
1398
+ var CapacityRequired = class extends Error {
1399
+ };
1400
+ var ForeignCiRace = class extends Error {
1401
+ constructor(record) {
1402
+ super("Foreign CI acquired the candidate while merge preparation was admitting it.");
1403
+ this.record = record;
1404
+ }
1405
+ record;
1406
+ };
1407
+ function active2(request) {
1408
+ return request.status === "queued" || request.status === "running";
1409
+ }
1410
+ function message(error) {
1411
+ return error instanceof Error ? error.message : String(error);
1412
+ }
1413
+ function sameRepository(left, right) {
1414
+ return left?.toLowerCase() === right.toLowerCase();
1415
+ }
1416
+ function busy2(record, headSha) {
1417
+ return record?.sha === headSha && (record.status === "queued" || record.status === "running");
1418
+ }
1419
+ function refusedForMovedBase(detail) {
1420
+ return /base branch (?:was )?modified|not up to date|out of date with (?:the )?base/i.test(
1421
+ detail
1422
+ );
1423
+ }
1424
+ function attestationPending(record) {
1425
+ return record.status === "publication_pending" || record.status === "succeeded_pending";
1426
+ }
1427
+ async function isAncestor(input) {
1428
+ const result = await execGit(["merge-base", "--is-ancestor", input.ancestorSha, input.headSha], {
1429
+ cwd: input.repoRoot,
1430
+ timeoutMs: 6e4,
1431
+ env: { GIT_NO_REPLACE_OBJECTS: "1" }
1432
+ });
1433
+ if (result.exitCode === 0) return true;
1434
+ if (result.exitCode === 1) return false;
1435
+ throw new Error(`Unable to inspect PR ancestry: ${result.stderr || result.stdout}`);
1436
+ }
1437
+ function onlyTrustChanged(before, after) {
1438
+ if (after.version <= before.version) return false;
1439
+ const ignored = /* @__PURE__ */ new Set(["version", "updatedAt", "trustGrants"]);
1440
+ const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
1441
+ return [...keys].every(
1442
+ (key) => ignored.has(key) || JSON.stringify(before[key]) === JSON.stringify(after[key])
1443
+ );
1444
+ }
1445
+ function assertPrIdentity(request, pr) {
1446
+ if (!sameRepository(pr.repository, request.repository) || pr.prNumber !== request.prNumber || pr.baseBranch !== request.targetBranch || !sameRepository(pr.headRepository, request.repository) || pr.headBranch !== request.headBranch) {
1447
+ throw new Error("PR identity, head repository or target changed after admission.");
1448
+ }
1449
+ }
1450
+ function assertPrAdmitted(request, pr) {
1451
+ assertPrIdentity(request, pr);
1452
+ if (pr.state !== "open" || pr.draft || pr.autoMergeEnabled) {
1453
+ throw new Error("PR is closed, draft or independently armed for native auto-merge.");
1454
+ }
1455
+ if (pr.headSha !== request.acceptedHeadSha) {
1456
+ throw new Error("PR author head changed from the accepted reviewed lineage.");
1457
+ }
1458
+ }
1459
+ function cachedProofMatches(request, record, baseSha) {
1460
+ return record?.status === "succeeded" && sameRepository(record.repository, request.repository) && record.sha === request.acceptedHeadSha && record.trustedBaseSha === baseSha && record.trustedBaseRef === request.targetBranch && !record.entrypointApprovalPending && !record.outstandingRemoteStatusPublication;
1461
+ }
1462
+ async function ownerStateOf(record, override) {
1463
+ if (override) return override(record);
1464
+ if (!record.pid) return "unknown";
1465
+ return processOwnershipState(
1466
+ record.pid,
1467
+ record.pidIdentity,
1468
+ record.startedAt ?? record.updatedAt
1469
+ );
1470
+ }
1471
+ function classifyMergeReadback(pr, operation, request, late) {
1472
+ const sameRequest = pr !== void 0 && sameRepository(pr.repository, request.repository) && pr.prNumber === request.prNumber;
1473
+ if (pr && (pr.state === "merged" || pr.state === "closed")) {
1474
+ if (!sameRequest) {
1475
+ throw new Error("Terminal PR readback does not identify this merge request.");
1476
+ }
1477
+ if (pr.state === "merged" && pr.headSha === operation.expectedHeadSha && pr.baseBranch === request.targetBranch && pr.mergeCommit) {
1478
+ return { kind: "merged", mergeCommit: pr.mergeCommit };
1479
+ }
1480
+ const detail = pr.state === "closed" ? "PR was closed without a confirmed merge; publication is no longer blocked." : pr.headSha !== operation.expectedHeadSha || pr.baseBranch !== request.targetBranch ? "PR was merged at another head or target; this request did not confirm its reviewed merge." : "PR is merged but its merge commit could not be confirmed.";
1481
+ return { kind: "stop", detail };
1482
+ }
1483
+ if (pr?.state === "open" && sameRequest && pr.headSha !== operation.expectedHeadSha) {
1484
+ return {
1485
+ kind: "stop",
1486
+ detail: "PR head moved before the reviewed merge was confirmed; it did not merge."
1487
+ };
1488
+ }
1489
+ if (late && pr?.state === "open" && sameRequest && pr.headSha === operation.expectedHeadSha) {
1490
+ return {
1491
+ kind: "stop",
1492
+ detail: "GitHub still shows the PR open at the reviewed head ten minutes after the merge request; it did not merge."
1493
+ };
1494
+ }
1495
+ return { kind: "wait" };
1496
+ }
1497
+
1498
+ // src/core/ci/merge-queue-processor.ts
1499
+ var MAX_PREPARATIONS = 2;
1500
+ var POLL_DELAY_MS = 2e3;
1501
+ var MAX_SETTLEMENT_CAS_ATTEMPTS = 2;
1502
+ var CI_WAIT_TIMEOUT_MS = 6 * 60 * 60 * 1e3;
1503
+ var CONTROL_PLANE_TIMEOUT_MS = 10 * 60 * 1e3;
1504
+ var MAX_FOREIGN_CI_RETRIES = 1;
1505
+ var MAX_CAPACITY_FALLBACKS = 1;
1506
+ async function processMergeRequest(input, dependencies) {
1507
+ if (!input.request.workerToken) {
1508
+ throw new Error("A merge processor request requires the current coordinator workerToken.");
1509
+ }
1510
+ return new RequestProcessor(input, dependencies).run();
1511
+ }
1512
+ var RequestProcessor = class {
1513
+ constructor(input, deps) {
1514
+ this.input = input;
1515
+ this.deps = deps;
1516
+ this.current = input.request;
1517
+ this.token = input.request.workerToken;
1518
+ this.ciWaitDeadline = this.tick() + CI_WAIT_TIMEOUT_MS;
1519
+ this.lease = new RequestLandingLease(deps.landingLease, {
1520
+ requestId: input.request.id,
1521
+ nowMs: () => this.tick(),
1522
+ pause: (milliseconds) => this.pauseFor(milliseconds),
1523
+ claim: (base) => ({
1524
+ requestId: this.current.id,
1525
+ pr: this.current.prNumber,
1526
+ head: this.current.acceptedHeadSha,
1527
+ base
1528
+ })
1529
+ });
1530
+ }
1531
+ input;
1532
+ deps;
1533
+ current;
1534
+ token;
1535
+ slot;
1536
+ turn;
1537
+ ciWaitDeadline;
1538
+ forceCapacity = false;
1539
+ watcher;
1540
+ lease;
1541
+ get target() {
1542
+ return {
1543
+ repository: this.current.repository,
1544
+ targetBranch: this.current.targetBranch,
1545
+ stateRoot: this.input.stateRoot ?? this.input.store.root
1546
+ };
1547
+ }
1548
+ get prInput() {
1549
+ const { repoRoot, repository, prNumber } = this.current;
1550
+ return { repoRoot, repository, prNumber };
1551
+ }
1552
+ async fresh() {
1553
+ const fresh = await this.input.store.read(this.current.repository, this.current.id);
1554
+ if (!fresh) throw new RequestChanged("Merge request no longer exists.");
1555
+ this.current = fresh;
1556
+ if (fresh.workerToken !== this.token) {
1557
+ throw new RequestChanged("Merge request coordinator ownership changed.");
1558
+ }
1559
+ return fresh;
1560
+ }
1561
+ /** Last awaited check immediately before each new remote write. */
1562
+ async guard(requireActive = true) {
1563
+ this.deps.signal?.throwIfAborted();
1564
+ if (this.watcher?.moved) throw new BaseMoved();
1565
+ await this.deps.assertCoordinatorOwned?.();
1566
+ await this.turn?.assertOwned();
1567
+ await this.lease.renew();
1568
+ const before = this.current;
1569
+ const fresh = await this.fresh();
1570
+ if (fresh.version !== before.version && !onlyTrustChanged(before, fresh) || requireActive && !active2(fresh)) {
1571
+ throw new RequestChanged("Merge request changed or was withdrawn.");
1572
+ }
1573
+ }
1574
+ async save(patch) {
1575
+ for (let attempt = 0; ; attempt += 1) {
1576
+ const before = this.current;
1577
+ const result = await this.input.store.compareAndSave(before, {
1578
+ ...before,
1579
+ ...patch,
1580
+ workerToken: this.token,
1581
+ updatedAt: (this.deps.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
1582
+ });
1583
+ this.current = result.record;
1584
+ if (result.applied) return;
1585
+ if (attempt > 0 || patch.trustGrants || !onlyTrustChanged(before, result.record)) {
1586
+ throw new RequestChanged("Merge request CAS changed.");
1587
+ }
1588
+ }
1589
+ }
1590
+ /**
1591
+ * Readback may settle an in-flight operation after cancellation. Retry only
1592
+ * this narrow CAS, at most once, retaining a stopped status unless merged.
1593
+ */
1594
+ async settle(operation, patch) {
1595
+ for (let attempt = 0; attempt < MAX_SETTLEMENT_CAS_ATTEMPTS; attempt += 1) {
1596
+ await this.deps.assertCoordinatorOwned?.();
1597
+ await this.turn?.assertOwned();
1598
+ await this.fresh();
1599
+ const outstanding = this.current.outstandingOperation;
1600
+ if (outstanding?.kind !== operation.kind || outstanding.expectedHeadSha !== operation.expectedHeadSha || outstanding.requestedAt !== operation.requestedAt) {
1601
+ throw new RequestChanged("Outstanding merge operation changed.");
1602
+ }
1603
+ const grants = patch.acceptedHeadSha ? await followHaltOverrideGrant(
1604
+ this.current,
1605
+ patch.acceptedHeadSha,
1606
+ () => this.deps.inspectValidation?.(this.validationInput(operation.baseSha ?? ""))
1607
+ ) : void 0;
1608
+ try {
1609
+ await this.save({
1610
+ ...patch,
1611
+ ...grants ? { trustGrants: grants } : {},
1612
+ status: patch.status === "merged" ? "merged" : active2(this.current) ? patch.status ?? this.current.status : this.current.status,
1613
+ outstandingOperation: void 0
1614
+ });
1615
+ return;
1616
+ } catch (error) {
1617
+ if (!(error instanceof RequestChanged)) throw error;
1618
+ if (attempt + 1 === MAX_SETTLEMENT_CAS_ATTEMPTS) throw error;
1619
+ }
1620
+ }
1621
+ }
1622
+ /** Stop the request: held for an entrypoint approval, or needing attention. */
1623
+ async disposition(error) {
1624
+ await this.fresh();
1625
+ await this.save(dispositionPatch(this.current, error, this.deps.now?.() ?? /* @__PURE__ */ new Date()));
1626
+ }
1627
+ /** Spend the request's recorded halt override durably before any run exists. */
1628
+ async claimHaltOverride() {
1629
+ await this.guard();
1630
+ const claim = claimHaltOverrideGrant(this.current, this.deps.now?.() ?? /* @__PURE__ */ new Date());
1631
+ if (claim.grants) await this.save({ trustGrants: claim.grants });
1632
+ return claim.covered;
1633
+ }
1634
+ tick() {
1635
+ return this.deps.monotonicNow?.() ?? performance.now();
1636
+ }
1637
+ async waitUntil(deadline) {
1638
+ await this.pauseFor(Math.min(POLL_DELAY_MS, Math.max(0, deadline - this.tick())));
1639
+ }
1640
+ async pauseFor(milliseconds) {
1641
+ this.deps.signal?.throwIfAborted();
1642
+ if (this.deps.pause) await this.deps.pause(milliseconds);
1643
+ else await delay(milliseconds, void 0, { signal: this.deps.signal });
1644
+ }
1645
+ async readPr() {
1646
+ return (this.deps.readPullRequest ?? readGitHubMergePullRequest)(this.prInput);
1647
+ }
1648
+ assertAdmitted(pr) {
1649
+ assertPrAdmitted(this.current, pr);
1650
+ }
1651
+ async checkRepository() {
1652
+ const repository = await (this.deps.readRepository ?? readGitHubMergeRepository)({
1653
+ repoRoot: this.current.repoRoot,
1654
+ repository: this.current.repository
1655
+ });
1656
+ if (!sameRepository(repository.repository, this.current.repository) || repository.defaultBranch !== this.current.targetBranch || !repository.allowedMergeMethods.includes(this.current.mergeMethod)) {
1657
+ throw new Error("Repository default target or allowed merge method changed.");
1658
+ }
1659
+ }
1660
+ async base() {
1661
+ return (this.deps.readBaseSha ?? resolveGitHubBranchSha)({
1662
+ repository: this.current.repository,
1663
+ branch: this.current.targetBranch
1664
+ });
1665
+ }
1666
+ async fetch(headSha, baseSha) {
1667
+ await (this.deps.fetchObjects ?? fetchGitHubMergeObjects)({
1668
+ ...this.prInput,
1669
+ headSha,
1670
+ baseSha,
1671
+ baseBranch: this.current.targetBranch
1672
+ });
1673
+ }
1674
+ async headOnBase(baseSha) {
1675
+ await this.fetch(this.current.acceptedHeadSha, baseSha);
1676
+ return (this.deps.isAncestor ?? isAncestor)({
1677
+ repoRoot: this.current.repoRoot,
1678
+ ancestorSha: baseSha,
1679
+ headSha: this.current.acceptedHeadSha
1680
+ });
1681
+ }
1682
+ async verify(head) {
1683
+ await this.fetch(head.sha, head.baseSha);
1684
+ const verification = await (this.deps.verifyMechanicalMerge ?? verifyGitHubMechanicalMerge)({
1685
+ repoRoot: this.current.repoRoot,
1686
+ preparedSha: head.sha,
1687
+ parentSha: head.parentSha,
1688
+ baseSha: head.baseSha
1689
+ });
1690
+ if (!verification.verified) {
1691
+ throw new Error(
1692
+ `Prepared head is not the reviewed mechanical merge: ${verification.reason}.`
1693
+ );
1694
+ }
1695
+ }
1696
+ async verifyOriginalLineage() {
1697
+ if (this.current.preparedHeads.length > MAX_PREPARATIONS || this.current.preparedHeads.length > this.current.preparationCount) {
1698
+ throw new Error("Invalid recorded preparation budget or lineage.");
1699
+ }
1700
+ let parent = this.current.reviewedSha;
1701
+ for (const head of this.current.preparedHeads) {
1702
+ if (head.parentSha !== parent)
1703
+ throw new Error("Prepared lineage does not start at the reviewed head.");
1704
+ await this.verify(head);
1705
+ parent = head.sha;
1706
+ }
1707
+ if (parent !== this.current.acceptedHeadSha) {
1708
+ throw new Error("Accepted head does not match the original reviewed preparation lineage.");
1709
+ }
1710
+ const operation = this.current.outstandingOperation;
1711
+ if (operation && operation.expectedHeadSha !== parent) {
1712
+ throw new Error("Outstanding operation does not match the accepted preparation lineage.");
1713
+ }
1714
+ }
1715
+ validationInput(baseSha) {
1716
+ return { request: this.current, headSha: this.current.acceptedHeadSha, baseSha };
1717
+ }
1718
+ /**
1719
+ * Watch the target branch while `run` depends on `baseSha`. Guarded waits see
1720
+ * movement as BaseMoved; a gate the queue runs itself is aborted through the
1721
+ * watcher's signal. The watcher is stopped before returning, so it can never
1722
+ * abort later work.
1723
+ */
1724
+ async watching(baseSha, run) {
1725
+ const watcher = new MainWatcher({
1726
+ baseSha,
1727
+ intervalMs: this.deps.mainWatch?.intervalMs,
1728
+ readTip: () => (this.deps.mainWatch?.readTip ?? readRemoteBranchTip)({
1729
+ repoRoot: this.current.repoRoot,
1730
+ branch: this.current.targetBranch
1731
+ }),
1732
+ confirmBase: () => this.base(),
1733
+ onTick: () => this.lease.renew(true)
1734
+ }).start();
1735
+ this.watcher = watcher;
1736
+ try {
1737
+ return await run(watcher);
1738
+ } finally {
1739
+ watcher.stop();
1740
+ this.watcher = void 0;
1741
+ }
1742
+ }
1743
+ /**
1744
+ * Another process's run is not ours to stop: no run-bound cancel exists for it,
1745
+ * so on movement this only stops waiting and lets the request prepare against
1746
+ * the new base (that run's head can no longer become landing evidence).
1747
+ */
1748
+ async awaitExistingCi(baseSha, observed) {
1749
+ try {
1750
+ await this.watching(baseSha, () => this.waitForExistingCi(baseSha, observed));
1751
+ } catch (error) {
1752
+ if (!(error instanceof BaseMoved)) throw error;
1753
+ }
1754
+ }
1755
+ /** Holds neither capacity nor publication while the existing CI owner progresses. */
1756
+ async waitForExistingCi(baseSha, observed) {
1757
+ await this.guard();
1758
+ let record = observed ?? await this.deps.inspectValidation?.(this.validationInput(baseSha));
1759
+ while (record && busy2(record, this.current.acceptedHeadSha)) {
1760
+ await this.guard();
1761
+ if (await ownerStateOf(record, this.deps.validationOwnerState) === "reclaimable") return;
1762
+ if (!this.deps.inspectValidation) {
1763
+ throw new Error("Active CI cannot be awaited without an inspectValidation boundary.");
1764
+ }
1765
+ if (this.tick() >= this.ciWaitDeadline) {
1766
+ throw new Error(
1767
+ "Existing Local CI wait expired after the six-hour capacity-compatible bound."
1768
+ );
1769
+ }
1770
+ await this.waitUntil(this.ciWaitDeadline);
1771
+ await this.guard();
1772
+ record = await this.deps.inspectValidation(this.validationInput(baseSha));
1773
+ }
1774
+ }
1775
+ async checkForeignCi(baseSha) {
1776
+ const record = await this.deps.inspectValidation?.(this.validationInput(baseSha));
1777
+ if (record && busy2(record, this.current.acceptedHeadSha) && await ownerStateOf(record, this.deps.validationOwnerState) !== "reclaimable") {
1778
+ throw new ForeignCiRace(record);
1779
+ }
1780
+ }
1781
+ async waitForAttestation(record, validationInput) {
1782
+ if (!attestationPending(record) || record.entrypointApprovalPending) return record;
1783
+ const deadline = this.tick() + CONTROL_PLANE_TIMEOUT_MS;
1784
+ if (this.deps.reconcileValidation) {
1785
+ await this.guard();
1786
+ record = await this.deps.reconcileValidation(validationInput);
1787
+ }
1788
+ while (attestationPending(record) && !record.entrypointApprovalPending) {
1789
+ await this.guard();
1790
+ if (this.tick() >= deadline) {
1791
+ throw new Error(
1792
+ "Candidate attestation remains pending after the ten-minute control-plane deadline."
1793
+ );
1794
+ }
1795
+ await this.waitUntil(deadline);
1796
+ await this.guard();
1797
+ record = await this.deps.inspectValidation?.(validationInput) ?? record;
1798
+ }
1799
+ return record;
1800
+ }
1801
+ async reconcileUpdate(operation) {
1802
+ if (!operation.baseSha) throw new Error("Recorded update has no identified base.");
1803
+ let lastError = "GitHub update result is still unresolved.";
1804
+ const deadline = this.tick() + CONTROL_PLANE_TIMEOUT_MS;
1805
+ for (; ; ) {
1806
+ await this.guard(false);
1807
+ let pr;
1808
+ try {
1809
+ pr = await this.readPr();
1810
+ } catch (error) {
1811
+ lastError = message(error);
1812
+ }
1813
+ if (pr) {
1814
+ assertPrIdentity(this.current, pr);
1815
+ if (pr.headSha !== operation.expectedHeadSha) {
1816
+ const prepared = {
1817
+ sha: pr.headSha,
1818
+ parentSha: operation.expectedHeadSha,
1819
+ baseSha: operation.baseSha
1820
+ };
1821
+ await this.verify(prepared);
1822
+ await this.settle(operation, {
1823
+ acceptedHeadSha: pr.headSha,
1824
+ preparedHeads: [...this.current.preparedHeads, prepared],
1825
+ lastError: void 0
1826
+ });
1827
+ return;
1828
+ }
1829
+ }
1830
+ if (this.tick() >= deadline) break;
1831
+ await this.waitUntil(deadline);
1832
+ }
1833
+ throw new Error(`Update outcome unresolved after bounded readback: ${lastError}`);
1834
+ }
1835
+ async prepare(baseSha) {
1836
+ if (this.current.preparationCount >= MAX_PREPARATIONS) {
1837
+ throw new Error(
1838
+ "Automatic preparation limit (2) exhausted; fresh reviewed authorization is required."
1839
+ );
1840
+ }
1841
+ await this.guard();
1842
+ const operation = {
1843
+ kind: "update",
1844
+ expectedHeadSha: this.current.acceptedHeadSha,
1845
+ baseSha,
1846
+ requestedAt: (this.deps.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
1847
+ };
1848
+ await this.save({
1849
+ preparationCount: this.current.preparationCount + 1,
1850
+ outstandingOperation: operation
1851
+ });
1852
+ let dispatched = false;
1853
+ try {
1854
+ await this.guard();
1855
+ dispatched = true;
1856
+ await (this.deps.updateBranch ?? updateGitHubMergeBranch)({
1857
+ ...this.prInput,
1858
+ expectedHeadSha: operation.expectedHeadSha
1859
+ });
1860
+ } catch (error) {
1861
+ if (!dispatched || error instanceof GitHubMergeOperationError && error.outcome === "rejected") {
1862
+ await this.settle(operation, {});
1863
+ throw error;
1864
+ }
1865
+ }
1866
+ await this.reconcileUpdate(operation);
1867
+ }
1868
+ async clearMergeRecovery(operation) {
1869
+ await this.deps.assertCoordinatorOwned?.();
1870
+ await this.fresh();
1871
+ const outstanding = this.current.outstandingOperation;
1872
+ if (outstanding?.kind !== "merge" || outstanding.expectedHeadSha !== operation.expectedHeadSha || outstanding.requestedAt !== operation.requestedAt) {
1873
+ throw new RequestChanged("Outstanding merge operation changed before clearing recovery.");
1874
+ }
1875
+ await this.turn.clearRecovery();
1876
+ }
1877
+ async confirmMerged(operation, mergeCommit) {
1878
+ await this.clearMergeRecovery(operation);
1879
+ await this.settle(operation, { status: "merged", mergeCommit, lastError: void 0 });
1880
+ }
1881
+ async reconcileMerge(operation) {
1882
+ let lastError = "PR is not yet observed merged.";
1883
+ const remaining = Date.parse(operation.requestedAt) + CONTROL_PLANE_TIMEOUT_MS - (this.deps.now?.() ?? /* @__PURE__ */ new Date()).getTime();
1884
+ const deadline = this.tick() + Math.max(0, remaining);
1885
+ for (; ; ) {
1886
+ const late = this.tick() >= deadline;
1887
+ await this.guard(false);
1888
+ let pr;
1889
+ try {
1890
+ pr = await this.readPr();
1891
+ } catch (error) {
1892
+ lastError = message(error);
1893
+ }
1894
+ const outcome = classifyMergeReadback(pr, operation, this.current, late);
1895
+ if (outcome.kind === "merged") {
1896
+ await this.confirmMerged(operation, outcome.mergeCommit);
1897
+ return;
1898
+ }
1899
+ if (outcome.kind === "stop") {
1900
+ await this.clearMergeRecovery(operation);
1901
+ await this.settle(operation, { status: "needs_attention", lastError: outcome.detail });
1902
+ return;
1903
+ }
1904
+ if (late) break;
1905
+ await this.waitUntil(deadline);
1906
+ }
1907
+ throw new Error(`Merge outcome unresolved after bounded readback: ${lastError}`);
1908
+ }
1909
+ /** Resolves 'base-moved' when GitHub refused the merge because the target moved. */
1910
+ async merge(baseSha) {
1911
+ const operation = {
1912
+ kind: "merge",
1913
+ expectedHeadSha: this.current.acceptedHeadSha,
1914
+ baseSha,
1915
+ requestedAt: (this.deps.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
1916
+ };
1917
+ await this.guard();
1918
+ await this.save({ outstandingOperation: operation });
1919
+ let dispatched = false;
1920
+ try {
1921
+ await this.turn.requireRecovery(
1922
+ `PR #${this.current.prNumber} merge outcome may be unresolved.`
1923
+ );
1924
+ await this.guard();
1925
+ dispatched = true;
1926
+ const response = await (this.deps.merge ?? mergeGitHubPullRequestImmediately)({
1927
+ ...this.prInput,
1928
+ expectedHeadSha: operation.expectedHeadSha,
1929
+ method: this.current.mergeMethod
1930
+ });
1931
+ if (response.merged) {
1932
+ await this.confirmMerged(operation, response.mergeCommit);
1933
+ return "done";
1934
+ }
1935
+ await this.clearMergeRecovery(operation);
1936
+ if (refusedForMovedBase(response.message)) {
1937
+ await this.settle(operation, {});
1938
+ return "base-moved";
1939
+ }
1940
+ await this.settle(operation, { status: "needs_attention", lastError: response.message });
1941
+ return "done";
1942
+ } catch (error) {
1943
+ if (!dispatched || error instanceof GitHubMergeOperationError && error.outcome === "rejected") {
1944
+ await this.clearMergeRecovery(operation);
1945
+ await this.settle(operation, {});
1946
+ if (dispatched && refusedForMovedBase(message(error))) return "base-moved";
1947
+ throw error;
1948
+ }
1949
+ if (this.current.status === "merged" && !this.current.outstandingOperation) throw error;
1950
+ }
1951
+ await this.reconcileMerge(operation);
1952
+ return "done";
1953
+ }
1954
+ async assertValidation(record, headSha, baseSha) {
1955
+ const statusContext = typeof this.deps.statusContext === "string" ? this.deps.statusContext : await this.deps.statusContext({ request: this.current, headSha, baseSha });
1956
+ if (!sameRepository(record.repository, this.current.repository) || record.sha !== headSha || record.trustedBaseSha !== baseSha || record.trustedBaseRef !== this.current.targetBranch || record.statusContext !== statusContext) {
1957
+ throw new Error("Local CI evidence does not match the exact repository/head/base/context.");
1958
+ }
1959
+ if (record.status !== "succeeded" || record.outstandingRemoteStatusPublication || record.entrypointApprovalPending) {
1960
+ throw new Error(
1961
+ `Local CI did not produce confirmed success: ${record.error ?? record.status}.`
1962
+ );
1963
+ }
1964
+ }
1965
+ cacheMatches(record, baseSha) {
1966
+ return cachedProofMatches(this.current, record, baseSha);
1967
+ }
1968
+ async reusable(baseSha) {
1969
+ if (!this.slot && !this.cacheMatches(
1970
+ await this.deps.inspectValidation?.(this.validationInput(baseSha)),
1971
+ baseSha
1972
+ ))
1973
+ return void 0;
1974
+ const record = await this.deps.reuseValidation?.(this.validationInput(baseSha));
1975
+ return this.cacheMatches(record, baseSha) ? record : void 0;
1976
+ }
1977
+ async requiredChecks() {
1978
+ const deadline = this.tick() + CONTROL_PLANE_TIMEOUT_MS;
1979
+ while (this.tick() < deadline) {
1980
+ await this.guard();
1981
+ const checks = await (this.deps.readRequiredChecks ?? readGitHubRequiredChecks)({
1982
+ ...this.prInput,
1983
+ expectedHeadSha: this.current.acceptedHeadSha
1984
+ });
1985
+ if (checks.headSha !== this.current.acceptedHeadSha) {
1986
+ throw new Error("Required check evidence is for a different head.");
1987
+ }
1988
+ if (checks.state === "fail") throw new Error("A required GitHub check failed.");
1989
+ if (checks.state === "pass") return;
1990
+ await this.waitUntil(deadline);
1991
+ }
1992
+ throw new Error(
1993
+ "Required GitHub checks remain pending after the ten-minute control-plane deadline."
1994
+ );
1995
+ }
1996
+ async processActive() {
1997
+ for (let round = 0; round <= MAX_PREPARATIONS; round += 1) {
1998
+ try {
1999
+ await this.lease.checkpoint("round");
2000
+ if (await this.processRound()) return;
2001
+ } catch (error) {
2002
+ if (!(error instanceof BaseMoved)) throw error;
2003
+ await this.guard();
2004
+ await this.save({
2005
+ lastError: "Target branch moved during validation; preparing against the new base."
2006
+ });
2007
+ }
2008
+ }
2009
+ throw new Error("Preparation/base movement observation limit exhausted; needs attention.");
2010
+ }
2011
+ /** Validate and merge against the live base; false means the base moved, try the next round. */
2012
+ async processRound() {
2013
+ await this.guard();
2014
+ await this.checkRepository();
2015
+ let pr = await this.readPr();
2016
+ this.assertAdmitted(pr);
2017
+ let baseSha = await this.base();
2018
+ await this.fetch(pr.headSha, baseSha);
2019
+ if (!await (this.deps.isAncestor ?? isAncestor)({
2020
+ repoRoot: this.current.repoRoot,
2021
+ ancestorSha: baseSha,
2022
+ headSha: pr.headSha
2023
+ })) {
2024
+ if (!this.slot) throw new CapacityRequired("Preparation needs real CI capacity.");
2025
+ await this.prepare(baseSha);
2026
+ await this.guard();
2027
+ pr = await this.readPr();
2028
+ this.assertAdmitted(pr);
2029
+ const updatedBase = await this.base();
2030
+ if (updatedBase !== baseSha) return false;
2031
+ baseSha = updatedBase;
2032
+ }
2033
+ if (!await this.watching(baseSha, (watcher) => this.validateUnderWatch(baseSha, watcher))) {
2034
+ return false;
2035
+ }
2036
+ await this.checkRepository();
2037
+ pr = await this.readPr();
2038
+ this.assertAdmitted(pr);
2039
+ if (await this.base() !== baseSha) return false;
2040
+ await this.deps.assertBatchMembers?.(this.current);
2041
+ await this.lease.checkpoint("merge");
2042
+ return await this.merge(baseSha) === "done";
2043
+ }
2044
+ /** Gate, attestation and required checks: all void once `baseSha` is no longer the base. */
2045
+ async validateUnderWatch(baseSha, watcher) {
2046
+ await this.checkForeignCi(baseSha);
2047
+ await this.guard();
2048
+ const headSha = this.current.acceptedHeadSha;
2049
+ let record = await this.reusable(baseSha);
2050
+ if (!record) {
2051
+ const slot = this.slot;
2052
+ if (!slot)
2053
+ throw new CapacityRequired(
2054
+ "Cached proof no longer matches; fresh validation needs capacity."
2055
+ );
2056
+ try {
2057
+ record = await this.deps.validateHead({
2058
+ ...this.validationInput(baseSha),
2059
+ slot,
2060
+ signal: this.lease.withSignal(watcher.signal),
2061
+ claimHaltOverride: () => this.claimHaltOverride()
2062
+ });
2063
+ } catch (error) {
2064
+ if (watcher.moved) throw new BaseMoved();
2065
+ this.lease.throwIfLost();
2066
+ throw error;
2067
+ }
2068
+ }
2069
+ this.lease.throwIfLost();
2070
+ await this.guard();
2071
+ if (busy2(record, headSha)) throw new ForeignCiRace(record);
2072
+ record = await this.waitForAttestation(record, this.validationInput(baseSha));
2073
+ await this.guard();
2074
+ if (busy2(record, headSha)) throw new ForeignCiRace(record);
2075
+ if (record.entrypointApprovalPending) throw new EntrypointApprovalRequired(record);
2076
+ await this.assertValidation(record, headSha, baseSha);
2077
+ const pr = await this.readPr();
2078
+ this.assertAdmitted(pr);
2079
+ if (await this.base() !== baseSha) return false;
2080
+ await this.requiredChecks();
2081
+ return true;
2082
+ }
2083
+ async runOwnedAttempt() {
2084
+ await this.guard(false);
2085
+ const outstanding = this.current.outstandingOperation;
2086
+ const priorTurn = await (this.deps.readTurn ?? readPublicationTurn)(this.target);
2087
+ const settledBarrier = !outstanding && priorTurn?.recoveryRequired && priorTurn.ownerId === this.current.id;
2088
+ if (!active2(this.current) && !outstanding && !settledBarrier) return;
2089
+ if (active2(this.current) && outstanding?.kind !== "merge") {
2090
+ let baseSha = await this.base();
2091
+ if (!outstanding) {
2092
+ const existing = await this.deps.inspectValidation?.(this.validationInput(baseSha));
2093
+ if (!busy2(existing, this.current.acceptedHeadSha) || await this.headOnBase(baseSha)) {
2094
+ await this.awaitExistingCi(baseSha, existing);
2095
+ }
2096
+ baseSha = await this.base();
2097
+ }
2098
+ await this.lease.wait({
2099
+ checkContinue: () => this.guard(),
2100
+ announce: (text) => this.save({ lastError: text })
2101
+ });
2102
+ const possibleCache = !outstanding && !this.forceCapacity && this.deps.reuseValidation !== void 0 && this.cacheMatches(
2103
+ await this.deps.inspectValidation?.(this.validationInput(baseSha)),
2104
+ baseSha
2105
+ );
2106
+ if (!possibleCache) {
2107
+ this.slot = await this.deps.acquireSlot({
2108
+ request: this.current,
2109
+ checkContinue: () => this.guard()
2110
+ });
2111
+ }
2112
+ await this.guard();
2113
+ await this.lease.acquire(baseSha);
2114
+ }
2115
+ this.turn = await (this.deps.acquireTurn ?? acquirePublicationTurn)({
2116
+ ...this.target,
2117
+ ownerId: this.current.id,
2118
+ recover: true,
2119
+ signal: this.deps.signal
2120
+ });
2121
+ await this.guard(false);
2122
+ if (!outstanding || outstanding.kind === "update") await this.turn.clearRecovery();
2123
+ else await this.turn.requireRecovery("Recovering an outstanding expected-head merge.");
2124
+ await this.verifyOriginalLineage();
2125
+ if (outstanding?.kind === "merge") {
2126
+ await this.reconcileMerge(outstanding);
2127
+ return;
2128
+ }
2129
+ if (outstanding?.kind === "update") await this.reconcileUpdate(outstanding);
2130
+ if (!active2(this.current)) return;
2131
+ await this.save({ status: "running", lastError: this.lease.statusNote() });
2132
+ await this.processActive();
2133
+ }
2134
+ async releaseResources() {
2135
+ const turn = this.turn;
2136
+ const slot = this.slot;
2137
+ this.turn = void 0;
2138
+ this.slot = void 0;
2139
+ try {
2140
+ await turn?.release();
2141
+ } finally {
2142
+ try {
2143
+ await slot?.release();
2144
+ } finally {
2145
+ await this.lease.release();
2146
+ }
2147
+ }
2148
+ }
2149
+ /** A lease problem re-queues for the lease: resources are released and the wait restarts. */
2150
+ async runLeasedAttempt() {
2151
+ for (; ; ) {
2152
+ try {
2153
+ return await this.runOwnedAttempt();
2154
+ } catch (error) {
2155
+ if (!(error instanceof LeaseRetry)) throw error;
2156
+ await this.releaseResources();
2157
+ }
2158
+ }
2159
+ }
2160
+ async run() {
2161
+ try {
2162
+ let foreignCiRetries = 0;
2163
+ let capacityFallbacks = 0;
2164
+ for (let attempt = 0; attempt <= MAX_FOREIGN_CI_RETRIES + MAX_CAPACITY_FALLBACKS; attempt += 1) {
2165
+ try {
2166
+ await this.runLeasedAttempt();
2167
+ break;
2168
+ } catch (error) {
2169
+ if (error instanceof CapacityRequired) {
2170
+ await this.releaseResources();
2171
+ if (capacityFallbacks++ === MAX_CAPACITY_FALLBACKS) {
2172
+ throw new Error(
2173
+ "Cached-validation capacity fallback exhausted; merge request needs attention."
2174
+ );
2175
+ }
2176
+ this.forceCapacity = true;
2177
+ await this.guard();
2178
+ await this.save({
2179
+ lastError: "Cached proof requires preparation or fresh validation; retrying once with real capacity."
2180
+ });
2181
+ continue;
2182
+ }
2183
+ if (!(error instanceof ForeignCiRace)) throw error;
2184
+ await this.releaseResources();
2185
+ if (foreignCiRetries++ === MAX_FOREIGN_CI_RETRIES) {
2186
+ throw new Error("Foreign-CI admission retry exhausted; merge request needs attention.");
2187
+ }
2188
+ await this.guard();
2189
+ await this.save({
2190
+ lastError: "Foreign CI admission raced; waiting outside capacity/publication before one internal retry."
2191
+ });
2192
+ await this.awaitExistingCi(await this.base(), error.record);
2193
+ }
2194
+ }
2195
+ } catch (error) {
2196
+ if (!(error instanceof RequestChanged)) {
2197
+ try {
2198
+ await this.deps.assertCoordinatorOwned?.();
2199
+ await this.disposition(error);
2200
+ } catch (saveError) {
2201
+ if (!(saveError instanceof RequestChanged)) throw saveError;
2202
+ }
2203
+ }
2204
+ } finally {
2205
+ await this.releaseResources();
2206
+ }
2207
+ return this.current;
2208
+ }
2209
+ };
2210
+
2211
+ // src/core/ci/merge-queue-runner.ts
2212
+ import { spawn } from "child_process";
2213
+ import { closeSync, openSync } from "fs";
2214
+ import path6 from "path";
2215
+ import { setTimeout as delay2 } from "timers/promises";
2216
+
2217
+ // src/core/ci/merge-queue-state.ts
2218
+ import { randomUUID as randomUUID2 } from "crypto";
2219
+ import { mkdir as mkdir2, open, readFile as readFile3, rename as rename2, unlink as unlink2 } from "fs/promises";
2220
+ import path5 from "path";
2221
+ import { z as z4 } from "zod";
2222
+
2223
+ // src/core/ci/merge-queue-types.ts
2224
+ import path4 from "path";
2225
+ import { z as z3 } from "zod";
2226
+ var MergeQueueRepositorySchema = z3.string().refine(
2227
+ (value) => parseLocalCiRepositoryIdentity(value) !== null && value.split("/").length === 2,
2228
+ {
2229
+ message: "Expected a GitHub owner/repository identity."
2230
+ }
2231
+ ).transform((value) => value.trim().toLowerCase());
2232
+ var ShaSchema2 = z3.string().regex(/^[a-f0-9]{40}$/i).transform((value) => value.toLowerCase());
2233
+ var CounterSchema = z3.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
2234
+ var BranchSchema = z3.string().min(1);
2235
+ var RepoRootSchema = z3.string().refine(path4.isAbsolute, { message: "Expected an absolute repository root." });
2236
+ var MergeRequestCloseoutSchema = z3.object({
2237
+ status: z3.enum(["done", "not_applicable", "needs_attention"]),
2238
+ itemId: z3.string().min(1).optional(),
2239
+ message: z3.string().optional()
2240
+ });
2241
+ var TrustGrantBaseSchema = z3.object({
2242
+ /** The request the human decision was recorded for; a grant never applies to another. */
2243
+ sourceRequestId: z3.string().uuid(),
2244
+ grantedAt: z3.iso.datetime(),
2245
+ /** How the human supplied it: a command-line flag or an admission-time environment alias. */
2246
+ provenance: z3.enum(["flag", "env"])
2247
+ });
2248
+ var EntrypointFingerprintSchema = z3.string().regex(ENTRYPOINT_FINGERPRINT_PATTERN);
2249
+ var MergeTrustGrantSchema = z3.discriminatedUnion("kind", [
2250
+ TrustGrantBaseSchema.extend({
2251
+ kind: z3.literal("entrypoint-approval"),
2252
+ /** `inherited` copies a predecessor request's approval (see inheritedFromRequestId). */
2253
+ provenance: z3.enum(["flag", "env", "inherited"]),
2254
+ /**
2255
+ * Exact-head binding (the approved candidate head SHA) plus, when it could be
2256
+ * computed at approval time, the fingerprint of the approved definitions: a
2257
+ * later head whose recomputed fingerprint equals it is covered as well.
2258
+ */
2259
+ scope: z3.object({ headSha: ShaSchema2, fingerprint: EntrypointFingerprintSchema.optional() }),
2260
+ /** For `inherited` provenance: the request whose approval this copies. */
2261
+ inheritedFromRequestId: z3.string().uuid().optional()
2262
+ }),
2263
+ TrustGrantBaseSchema.extend({
2264
+ kind: z3.literal("halt-override"),
2265
+ /** Set once the override bypassed a halt; `consumedHeadSha` is the head it covered. */
2266
+ consumedAt: z3.iso.datetime().optional(),
2267
+ consumedHeadSha: ShaSchema2.optional()
2268
+ })
2269
+ ]);
2270
+ var EnqueueMergeRequestInputSchema = z3.object({
2271
+ repository: MergeQueueRepositorySchema,
2272
+ targetBranch: BranchSchema,
2273
+ /** The store resolves linked worktrees to their stable canonical repository. */
2274
+ repoRoot: RepoRootSchema,
2275
+ prNumber: z3.number().int().positive(),
2276
+ reviewedSha: ShaSchema2,
2277
+ headBranch: BranchSchema,
2278
+ mergeMethod: z3.enum(["merge", "squash", "rebase"])
2279
+ });
2280
+ var MergeRequestSchema = EnqueueMergeRequestInputSchema.extend({
2281
+ schemaVersion: z3.literal(1),
2282
+ id: z3.string().uuid(),
2283
+ version: CounterSchema.min(1),
2284
+ acceptedHeadSha: ShaSchema2,
2285
+ status: z3.enum([
2286
+ "queued",
2287
+ "running",
2288
+ "awaiting_approval",
2289
+ "needs_attention",
2290
+ "cancelled",
2291
+ "merged"
2292
+ ]),
2293
+ createdAt: z3.iso.datetime(),
2294
+ updatedAt: z3.iso.datetime(),
2295
+ order: CounterSchema.min(1),
2296
+ preparationCount: CounterSchema,
2297
+ recoveryCount: CounterSchema,
2298
+ preparedHeads: z3.array(
2299
+ z3.object({
2300
+ sha: ShaSchema2,
2301
+ parentSha: ShaSchema2,
2302
+ baseSha: ShaSchema2
2303
+ })
2304
+ ),
2305
+ outstandingOperation: z3.object({
2306
+ kind: z3.enum(["update", "merge"]),
2307
+ expectedHeadSha: ShaSchema2,
2308
+ baseSha: ShaSchema2.optional(),
2309
+ requestedAt: z3.iso.datetime()
2310
+ }).optional(),
2311
+ lastError: z3.string().optional(),
2312
+ mergeCommit: ShaSchema2.optional(),
2313
+ closeout: MergeRequestCloseoutSchema.optional(),
2314
+ /** An automatic batch request: the admitted single requests it lands. */
2315
+ batchMembers: z3.array(
2316
+ z3.object({
2317
+ requestId: z3.string().uuid(),
2318
+ prNumber: z3.number().int().positive(),
2319
+ reviewedSha: ShaSchema2
2320
+ })
2321
+ ).min(2).optional(),
2322
+ /**
2323
+ * An explicit batch PR (`ci merge <pr>:<sha> <pr>:<sha> ...`): the member PR
2324
+ * numbers it was assembled from. Used to recognize a replacement batch for
2325
+ * entrypoint approval inheritance and to match member-linked backlog items at
2326
+ * closeout; it does not change how the batch lands.
2327
+ */
2328
+ batchPrNumbers: z3.array(z3.number().int().positive()).min(2).optional(),
2329
+ /** A member held by an automatic batch; it is not selected singly meanwhile. */
2330
+ batchedInto: z3.object({ requestId: z3.string().uuid(), prNumber: z3.number().int().positive() }).optional(),
2331
+ /** One automatic batch attempt was spent; this request now lands on its own. */
2332
+ batchAttempted: z3.literal(true).optional(),
2333
+ /**
2334
+ * Set when validation stopped at a trusted verification entrypoint change that
2335
+ * needs a human decision. The request waits as `awaiting_approval` until a
2336
+ * matching approval releases it (`releasedAt`), and keeps the record afterwards
2337
+ * so status can say the run is an approved one.
2338
+ */
2339
+ entrypointHold: z3.object({
2340
+ /** The candidate head the entrypoint check blocked; an approval must name it. */
2341
+ headSha: ShaSchema2,
2342
+ /** What Local CI reported as redefined, for status. */
2343
+ reason: z3.string().min(1),
2344
+ /** The redefinition fingerprint Local CI computed for the blocked head, for status. */
2345
+ fingerprint: EntrypointFingerprintSchema.optional(),
2346
+ heldAt: z3.iso.datetime(),
2347
+ releasedAt: z3.iso.datetime().optional()
2348
+ }).optional(),
2349
+ /** Human trust decisions recorded at admission; absent on requests saved before they existed. */
2350
+ trustGrants: z3.array(MergeTrustGrantSchema).max(2).optional(),
2351
+ /** When supplied on a worker save, must match the current coordinator token. */
2352
+ workerToken: z3.string().uuid().optional()
2353
+ });
2354
+ var MergeCoordinatorClaimSchema = z3.object({
2355
+ repository: MergeQueueRepositorySchema,
2356
+ token: z3.string().uuid(),
2357
+ ownerPid: z3.number().int().positive(),
2358
+ ownerPidIdentity: z3.string().min(1),
2359
+ claimedAt: z3.iso.datetime(),
2360
+ transferredAt: z3.iso.datetime().optional(),
2361
+ ready: z3.boolean(),
2362
+ readyAt: z3.iso.datetime().optional()
2363
+ });
2364
+
2365
+ // src/core/ci/merge-queue-state.ts
2366
+ var QueueSchema = z4.object({
2367
+ schemaVersion: z4.literal(1),
2368
+ repository: MergeQueueRepositorySchema,
2369
+ requests: z4.array(MergeRequestSchema),
2370
+ coordinator: MergeCoordinatorClaimSchema.optional(),
2371
+ // A fast drainer may release before its launcher polls. Keep the child-written
2372
+ // receipt independently of the active claim, including across later launches.
2373
+ acknowledgedCoordinatorTokens: z4.array(z4.string().uuid()).default([])
2374
+ });
2375
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
2376
+ "merged",
2377
+ "needs_attention",
2378
+ "cancelled"
2379
+ ]);
2380
+ function active3(request) {
2381
+ return request.status === "queued" || request.status === "running";
2382
+ }
2383
+ function openIntent(request) {
2384
+ return active3(request) || request.status === "awaiting_approval";
2385
+ }
2386
+ function holdsQueueAnchor(request) {
2387
+ return openIntent(request) || request.outstandingOperation?.kind === "merge";
2388
+ }
2389
+ function timestampAfter(previous) {
2390
+ return new Date(Math.max(Date.now(), Date.parse(previous) + 1)).toISOString();
2391
+ }
2392
+ function bump(request, changes) {
2393
+ return MergeRequestSchema.parse({
2394
+ ...request,
2395
+ ...changes,
2396
+ version: request.version + 1,
2397
+ updatedAt: timestampAfter(request.updatedAt)
2398
+ });
2399
+ }
2400
+ function identity(request) {
2401
+ return JSON.stringify([
2402
+ request.repository,
2403
+ request.targetBranch,
2404
+ request.repoRoot,
2405
+ request.id,
2406
+ request.prNumber,
2407
+ request.reviewedSha,
2408
+ request.headBranch,
2409
+ request.mergeMethod,
2410
+ request.createdAt,
2411
+ request.order
2412
+ ]);
2413
+ }
2414
+ function assertTransition(current, next) {
2415
+ if (identity(current) !== identity(next)) {
2416
+ throw new Error("Merge request identity cannot change during compareAndSave.");
2417
+ }
2418
+ if (next.preparationCount < current.preparationCount || next.recoveryCount < current.recoveryCount) {
2419
+ throw new Error("Merge request attempt counters cannot decrease.");
2420
+ }
2421
+ if (current.preparedHeads.some(
2422
+ (head, index) => JSON.stringify(head) !== JSON.stringify(next.preparedHeads[index])
2423
+ )) {
2424
+ throw new Error("Merge request preparation lineage cannot be removed or rewritten.");
2425
+ }
2426
+ const settlesMerge = current.outstandingOperation?.kind === "merge" && next.status === "merged" && next.mergeCommit !== void 0 && next.outstandingOperation === void 0;
2427
+ if (!active3(current) && next.status !== current.status && !settlesMerge) {
2428
+ throw new Error("A stopped merge request cannot be reopened.");
2429
+ }
2430
+ if (current.mergeCommit && next.mergeCommit !== current.mergeCommit) {
2431
+ throw new Error("A confirmed merge result cannot be removed or rewritten.");
2432
+ }
2433
+ }
2434
+ var MergeQueueStore = class {
2435
+ constructor(root = getLocalCiStateRoot(), options = {}) {
2436
+ this.options = options;
2437
+ this.root = path5.resolve(root);
2438
+ }
2439
+ options;
2440
+ root;
2441
+ /** Requests as last written under each held mutex, for landing history. */
2442
+ written = /* @__PURE__ */ new WeakMap();
2443
+ /** Append one landing entry per request that entered a terminal status; never throws. */
2444
+ async logTerminalTransitions(prior, requests) {
2445
+ for (const request of requests) {
2446
+ if (!TERMINAL_STATUSES.has(request.status)) continue;
2447
+ if (prior.get(request.id) === request.status) continue;
2448
+ await appendLandingLogEntry(this.root, "queue", queueLandingFields(request));
2449
+ }
2450
+ }
2451
+ filePath(repository) {
2452
+ return path5.join(this.root, "merge-queue", repoStateKey(repository), "queue.json");
2453
+ }
2454
+ async readQueue(repository) {
2455
+ const file = this.filePath(repository);
2456
+ let raw;
2457
+ try {
2458
+ raw = await readFile3(file, "utf8");
2459
+ } catch (error) {
2460
+ if (error.code === "ENOENT") {
2461
+ return { schemaVersion: 1, repository, requests: [], acknowledgedCoordinatorTokens: [] };
2462
+ }
2463
+ throw new Error(`Unable to read merge queue state at ${file}.`, { cause: error });
2464
+ }
2465
+ try {
2466
+ const queue = QueueSchema.parse(JSON.parse(raw));
2467
+ const anchor = queue.requests.find(holdsQueueAnchor);
2468
+ const ids = /* @__PURE__ */ new Set();
2469
+ const orders = /* @__PURE__ */ new Set();
2470
+ if (queue.repository !== repository || queue.coordinator && queue.coordinator.repository !== repository) {
2471
+ throw new Error("Repository identity does not match the queue path.");
2472
+ }
2473
+ for (const request of queue.requests) {
2474
+ if (request.repository !== repository || holdsQueueAnchor(request) && (request.targetBranch !== anchor?.targetBranch || request.repoRoot !== anchor?.repoRoot) || ids.has(request.id) || orders.has(request.order)) {
2475
+ throw new Error("Request identity or ordering does not match its queue.");
2476
+ }
2477
+ ids.add(request.id);
2478
+ orders.add(request.order);
2479
+ }
2480
+ return queue;
2481
+ } catch (error) {
2482
+ throw new Error(`Invalid merge queue state at ${file}.`, { cause: error });
2483
+ }
2484
+ }
2485
+ async writeQueue(queue, fence) {
2486
+ const file = this.filePath(queue.repository);
2487
+ const temporary = `${file}.${process.pid}.${randomUUID2()}.tmp`;
2488
+ try {
2489
+ const handle = await open(temporary, "wx", 384);
2490
+ try {
2491
+ await handle.writeFile(`${JSON.stringify(QueueSchema.parse(queue), null, 2)}
2492
+ `, "utf8");
2493
+ await handle.sync();
2494
+ } finally {
2495
+ await handle.close();
2496
+ }
2497
+ await this.options.beforePublish?.();
2498
+ await fence.assertOwned();
2499
+ await rename2(temporary, file);
2500
+ this.written.set(fence, queue.requests);
2501
+ } catch (error) {
2502
+ await unlink2(temporary).catch(() => void 0);
2503
+ throw error;
2504
+ }
2505
+ }
2506
+ async mutate(repository, operation, fn) {
2507
+ const normalized = MergeQueueRepositorySchema.parse(repository);
2508
+ const target = `${this.filePath(normalized)}.mutex`;
2509
+ await mkdir2(path5.dirname(target), { recursive: true, mode: 448 });
2510
+ const fence = await acquireFencedFileLock(target, {
2511
+ stale: 3e4,
2512
+ retries: { retries: 100, minTimeout: 50, maxTimeout: 100, factor: 1.2 },
2513
+ description: "Merge queue state mutex"
2514
+ });
2515
+ let priorStatus;
2516
+ try {
2517
+ await this.options.afterLockAcquired?.(operation);
2518
+ await fence.assertOwned();
2519
+ const queue = await this.readQueue(normalized);
2520
+ await fence.assertOwned();
2521
+ priorStatus = new Map(queue.requests.map((request) => [request.id, request.status]));
2522
+ return await fn(queue, fence);
2523
+ } finally {
2524
+ const written = this.written.get(fence);
2525
+ this.written.delete(fence);
2526
+ await fence();
2527
+ if (written && priorStatus) await this.logTerminalTransitions(priorStatus, written);
2528
+ }
2529
+ }
2530
+ async list(repository) {
2531
+ const queue = await this.readQueue(MergeQueueRepositorySchema.parse(repository));
2532
+ return queue.requests.sort((a, b) => a.order - b.order);
2533
+ }
2534
+ async read(repository, id) {
2535
+ return (await this.list(repository)).find((request) => request.id === id);
2536
+ }
2537
+ async latest(repository, prNumber) {
2538
+ return (await this.list(repository)).filter((request) => request.prNumber === prNumber).at(-1);
2539
+ }
2540
+ /** Only fresh positive admission may request resumeStopped; recovery omits it. */
2541
+ async enqueue(input, options = {}) {
2542
+ const parsed = EnqueueMergeRequestInputSchema.parse(input);
2543
+ const resolveRoot = this.options.resolveRepoRoot ?? resolveCanonicalMainRepoRoot;
2544
+ const repoRoot = path5.resolve(await resolveRoot(parsed.repoRoot));
2545
+ return this.mutate(parsed.repository, "enqueue", async (queue, fence) => {
2546
+ const anchor = queue.requests.find(holdsQueueAnchor);
2547
+ if (anchor && anchor.targetBranch !== parsed.targetBranch) {
2548
+ throw new Error("Merge queue target branch differs from the recorded default target.");
2549
+ }
2550
+ if (anchor && anchor.repoRoot !== repoRoot) {
2551
+ throw new Error("Merge queue canonical repository root differs from the recorded root.");
2552
+ }
2553
+ const existing = queue.requests.filter(
2554
+ (request2) => request2.prNumber === parsed.prNumber && request2.reviewedSha === parsed.reviewedSha
2555
+ ).sort((a, b) => b.order - a.order)[0];
2556
+ const resumedFrom = options.resumeStopped && (existing?.status === "cancelled" || existing?.status === "needs_attention") ? existing : void 0;
2557
+ if (existing && !resumedFrom) {
2558
+ if (!options.trust || !openIntent(existing)) return { request: existing, created: false };
2559
+ const now2 = /* @__PURE__ */ new Date();
2560
+ const trustGrants = recordTrustGrants(
2561
+ existing.id,
2562
+ options.trust,
2563
+ existing.trustGrants,
2564
+ now2
2565
+ );
2566
+ const updated = bump(existing, {
2567
+ trustGrants,
2568
+ ...releaseIfApproved({ ...existing, trustGrants }, now2)
2569
+ });
2570
+ queue.requests = queue.requests.map(
2571
+ (current) => current.id === existing.id ? updated : current
2572
+ );
2573
+ await this.writeQueue(queue, fence);
2574
+ return { request: updated, created: false };
2575
+ }
2576
+ if (resumedFrom && queue.requests.some(
2577
+ (request2) => request2.prNumber === parsed.prNumber && request2.outstandingOperation?.kind === "merge"
2578
+ )) {
2579
+ throw new Error(
2580
+ "Cannot resume a request while this PR has an unresolved dispatched merge."
2581
+ );
2582
+ }
2583
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2584
+ const id = randomUUID2();
2585
+ const inherited = options.trust?.entrypointApproval ? void 0 : inheritEntrypointApproval(queue.requests, parsed.prNumber, options.batchPrNumbers);
2586
+ const trust = options.trust || inherited ? { ...options.trust, ...inherited ? { entrypointApproval: inherited } : {} } : void 0;
2587
+ const request = MergeRequestSchema.parse({
2588
+ ...parsed,
2589
+ repoRoot,
2590
+ schemaVersion: 1,
2591
+ id,
2592
+ ...trust ? { trustGrants: recordTrustGrants(id, trust, void 0, new Date(now)) } : {},
2593
+ ...options.batchPrNumbers ? { batchPrNumbers: options.batchPrNumbers } : {},
2594
+ version: 1,
2595
+ acceptedHeadSha: resumedFrom?.acceptedHeadSha ?? parsed.reviewedSha,
2596
+ status: "queued",
2597
+ createdAt: now,
2598
+ updatedAt: now,
2599
+ order: queue.requests.reduce((max, current) => Math.max(max, current.order), 0) + 1,
2600
+ preparationCount: resumedFrom?.preparationCount ?? 0,
2601
+ recoveryCount: resumedFrom?.recoveryCount ?? 0,
2602
+ preparedHeads: resumedFrom?.preparedHeads ?? [],
2603
+ // A dispatched update may still need its mechanical result verified.
2604
+ // Its attempt was already charged; do not forget it on fresh admission.
2605
+ outstandingOperation: resumedFrom?.outstandingOperation
2606
+ });
2607
+ queue.requests = queue.requests.map(
2608
+ (current) => current.prNumber === parsed.prNumber && openIntent(current) ? bump(current, {
2609
+ status: "cancelled",
2610
+ lastError: `Superseded by reviewed SHA ${parsed.reviewedSha}.`
2611
+ }) : current
2612
+ );
2613
+ queue.requests.push(request);
2614
+ await this.writeQueue(queue, fence);
2615
+ return { request, created: true };
2616
+ });
2617
+ }
2618
+ /**
2619
+ * Spend the one automatic batch attempt of each still-unstarted request before
2620
+ * any remote effect, so a crash mid-batch leaves singles, never a retry loop.
2621
+ */
2622
+ async markBatchAttempt(repository, ids) {
2623
+ return this.mutate(repository, "batch", async (queue, fence) => {
2624
+ const marked = [];
2625
+ queue.requests = queue.requests.map((current) => {
2626
+ if (!ids.includes(current.id) || current.status !== "queued" || current.batchAttempted || current.batchedInto || current.outstandingOperation)
2627
+ return current;
2628
+ const next = bump(current, { batchAttempted: true });
2629
+ marked.push(next);
2630
+ return next;
2631
+ });
2632
+ if (marked.length > 0) await this.writeQueue(queue, fence);
2633
+ return marked.sort((a, b) => a.order - b.order);
2634
+ });
2635
+ }
2636
+ /** Queue an automatic batch and hold its members in the same atomic write. */
2637
+ async enqueueBatch(batch, members) {
2638
+ const first = members[0];
2639
+ if (!first || members.length < 2) throw new Error("A merge batch needs at least two members.");
2640
+ return this.mutate(first.repository, "batch", async (queue, fence) => {
2641
+ const current = members.map((member) => queue.requests.find((r) => r.id === member.id));
2642
+ if (current.some(
2643
+ (record, index) => !record || record.version !== members[index].version || record.status !== "queued" || record.batchedInto !== void 0 || record.outstandingOperation !== void 0
2644
+ )) {
2645
+ throw new Error("A batch member changed before the batch was queued.");
2646
+ }
2647
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2648
+ const request = MergeRequestSchema.parse({
2649
+ repository: first.repository,
2650
+ targetBranch: first.targetBranch,
2651
+ repoRoot: first.repoRoot,
2652
+ prNumber: batch.prNumber,
2653
+ reviewedSha: batch.reviewedSha,
2654
+ headBranch: batch.headBranch,
2655
+ mergeMethod: first.mergeMethod,
2656
+ schemaVersion: 1,
2657
+ id: randomUUID2(),
2658
+ version: 1,
2659
+ acceptedHeadSha: batch.reviewedSha,
2660
+ status: "queued",
2661
+ createdAt: now,
2662
+ updatedAt: now,
2663
+ order: queue.requests.reduce((max, r) => Math.max(max, r.order), 0) + 1,
2664
+ preparationCount: 0,
2665
+ recoveryCount: 0,
2666
+ preparedHeads: [],
2667
+ batchMembers: members.map((member) => ({
2668
+ requestId: member.id,
2669
+ prNumber: member.prNumber,
2670
+ reviewedSha: member.acceptedHeadSha
2671
+ }))
2672
+ });
2673
+ queue.requests = queue.requests.map(
2674
+ (record) => members.some((member) => member.id === record.id) ? bump(record, { batchedInto: { requestId: request.id, prNumber: batch.prNumber } }) : record
2675
+ );
2676
+ queue.requests.push(request);
2677
+ await this.writeQueue(queue, fence);
2678
+ return request;
2679
+ });
2680
+ }
2681
+ async compareAndSave(expected, next) {
2682
+ const previous = MergeRequestSchema.parse(expected);
2683
+ const candidate = MergeRequestSchema.parse(next);
2684
+ if (identity(previous) !== identity(candidate)) {
2685
+ throw new Error("compareAndSave requires the same merge request identity.");
2686
+ }
2687
+ return this.mutate(previous.repository, "save", async (queue, fence) => {
2688
+ const index = queue.requests.findIndex((request) => request.id === previous.id);
2689
+ const current = queue.requests[index];
2690
+ if (!current) throw new Error("Merge request no longer exists.");
2691
+ if (current.version !== previous.version) return { applied: false, record: current };
2692
+ assertTransition(current, candidate);
2693
+ if (candidate.workerToken) {
2694
+ const claim = this.requireClaim(queue, candidate.workerToken);
2695
+ this.requireOwnerProcess(claim);
2696
+ } else if (current.workerToken && active3(candidate)) {
2697
+ throw new Error("An active worker save cannot discard coordinator ownership.");
2698
+ }
2699
+ const saved = bump(current, candidate);
2700
+ queue.requests[index] = saved;
2701
+ await this.writeQueue(queue, fence);
2702
+ return { applied: true, record: saved };
2703
+ });
2704
+ }
2705
+ /**
2706
+ * Release one held request back to the queue if it is still the version the
2707
+ * caller judged approvable; a cancelled, superseded or changed request stays.
2708
+ */
2709
+ async releaseApprovalHold(repository, id, expectedVersion, now = /* @__PURE__ */ new Date()) {
2710
+ return this.mutate(repository, "resume", async (queue, fence) => {
2711
+ const index = queue.requests.findIndex((request) => request.id === id);
2712
+ const current = queue.requests[index];
2713
+ if (!current || current.version !== expectedVersion) return void 0;
2714
+ const patch = releaseHold(current, now);
2715
+ if (!patch.status) return void 0;
2716
+ const released = bump(current, patch);
2717
+ queue.requests[index] = released;
2718
+ await this.writeQueue(queue, fence);
2719
+ return released;
2720
+ });
2721
+ }
2722
+ async cancel(repository, prNumber, reviewedSha) {
2723
+ return this.mutate(repository, "cancel", async (queue, fence) => {
2724
+ const current = queue.requests.filter(
2725
+ (request) => request.prNumber === prNumber && (reviewedSha === void 0 || request.reviewedSha === reviewedSha.toLowerCase())
2726
+ ).sort((a, b) => b.order - a.order)[0];
2727
+ if (!current || current.status === "merged" || current.status === "cancelled") return current;
2728
+ const cancelled = bump(current, { status: "cancelled" });
2729
+ queue.requests[queue.requests.indexOf(current)] = cancelled;
2730
+ await this.writeQueue(queue, fence);
2731
+ return cancelled;
2732
+ });
2733
+ }
2734
+ ownership(claim) {
2735
+ return (this.options.processOwnership ?? processOwnershipState)(
2736
+ claim.ownerPid,
2737
+ claim.ownerPidIdentity,
2738
+ claim.claimedAt
2739
+ );
2740
+ }
2741
+ requireClaim(queue, token) {
2742
+ if (!queue.coordinator || queue.coordinator.token !== token) {
2743
+ throw new Error("Merge coordinator ownership changed.");
2744
+ }
2745
+ return queue.coordinator;
2746
+ }
2747
+ requireOwnerProcess(claim) {
2748
+ if (claim.ownerPid !== process.pid || this.ownership(claim) !== "owned") {
2749
+ throw new Error(
2750
+ "Only the recorded merge coordinator owner process may perform this operation."
2751
+ );
2752
+ }
2753
+ }
2754
+ async claimCoordinator(repository) {
2755
+ return this.mutate(repository, "claim", async (queue, fence) => {
2756
+ if (queue.coordinator && this.ownership(queue.coordinator) !== "reclaimable")
2757
+ return void 0;
2758
+ const ownerPidIdentity = (this.options.processIdentity ?? readProcessIdentity)(process.pid);
2759
+ if (!ownerPidIdentity)
2760
+ throw new Error("Unable to establish merge coordinator process identity.");
2761
+ const claim = MergeCoordinatorClaimSchema.parse({
2762
+ repository: queue.repository,
2763
+ token: randomUUID2(),
2764
+ ownerPid: process.pid,
2765
+ ownerPidIdentity,
2766
+ claimedAt: (/* @__PURE__ */ new Date()).toISOString(),
2767
+ ready: false
2768
+ });
2769
+ queue.coordinator = claim;
2770
+ await this.writeQueue(queue, fence);
2771
+ return claim;
2772
+ });
2773
+ }
2774
+ async transferCoordinator(repository, token, pid, identity2) {
2775
+ return this.mutate(repository, "transfer", async (queue, fence) => {
2776
+ const current = this.requireClaim(queue, token);
2777
+ if (current.ownerPid === pid && current.ownerPidIdentity === identity2) return current;
2778
+ this.requireOwnerProcess(current);
2779
+ if (current.ready) throw new Error("A ready coordinator cannot be transferred.");
2780
+ const transferred = MergeCoordinatorClaimSchema.parse({
2781
+ ...current,
2782
+ ownerPid: pid,
2783
+ ownerPidIdentity: identity2,
2784
+ transferredAt: (/* @__PURE__ */ new Date()).toISOString(),
2785
+ ready: false,
2786
+ readyAt: void 0
2787
+ });
2788
+ if (this.ownership(transferred) !== "owned") {
2789
+ throw new Error("Unable to verify the new merge coordinator process identity.");
2790
+ }
2791
+ queue.coordinator = transferred;
2792
+ await this.writeQueue(queue, fence);
2793
+ return transferred;
2794
+ });
2795
+ }
2796
+ async acknowledgeCoordinator(repository, token) {
2797
+ return this.mutate(repository, "acknowledge", async (queue, fence) => {
2798
+ const current = this.requireClaim(queue, token);
2799
+ this.requireOwnerProcess(current);
2800
+ if (!current.transferredAt) {
2801
+ throw new Error("Only a transferred worker can acknowledge merge coordinator readiness.");
2802
+ }
2803
+ if (current.ready && queue.acknowledgedCoordinatorTokens.includes(token)) return current;
2804
+ const ready = {
2805
+ ...current,
2806
+ ready: true,
2807
+ readyAt: current.readyAt ?? (/* @__PURE__ */ new Date()).toISOString()
2808
+ };
2809
+ queue.coordinator = ready;
2810
+ if (!queue.acknowledgedCoordinatorTokens.includes(token)) {
2811
+ queue.acknowledgedCoordinatorTokens.push(token);
2812
+ }
2813
+ await this.writeQueue(queue, fence);
2814
+ return ready;
2815
+ });
2816
+ }
2817
+ async readCoordinator(repository) {
2818
+ return (await this.readQueue(MergeQueueRepositorySchema.parse(repository))).coordinator;
2819
+ }
2820
+ async hasCoordinatorAcknowledgement(repository, token) {
2821
+ const queue = await this.readQueue(MergeQueueRepositorySchema.parse(repository));
2822
+ return queue.acknowledgedCoordinatorTokens.includes(token);
2823
+ }
2824
+ async releaseCoordinator(repository, token, options = {}) {
2825
+ return this.mutate(repository, "release", async (queue, fence) => {
2826
+ const current = queue.coordinator;
2827
+ if (!current || current.token !== token) return false;
2828
+ const ownership2 = this.ownership(current);
2829
+ if (ownership2 === "unknown" || ownership2 === "owned" && current.ownerPid !== process.pid)
2830
+ return false;
2831
+ if (options.onlyIfIdle && queue.requests.some(
2832
+ (request) => runnable(request, queue.requests) || request.outstandingOperation?.kind === "merge"
2833
+ ))
2834
+ return false;
2835
+ if (options.startupError) {
2836
+ queue.requests = queue.requests.map(
2837
+ (request) => request.workerToken === token && active3(request) ? bump(request, {
2838
+ status: request.recoveryCount >= 1 ? "needs_attention" : request.status,
2839
+ lastError: options.startupError
2840
+ }) : request
2841
+ );
2842
+ }
2843
+ queue.coordinator = void 0;
2844
+ await this.writeQueue(queue, fence);
2845
+ return true;
2846
+ });
2847
+ }
2848
+ };
2849
+
2850
+ // src/core/ci/merge-queue-runner.ts
2851
+ var STARTUP_TIMEOUT_MS = 3e4;
2852
+ var POLL_MS = 50;
2853
+ var MAX_OWNERSHIP_RACES = 3;
2854
+ var MAX_WORKER_RECOVERIES = 1;
2855
+ function active4(request) {
2856
+ return request.status === "queued" || request.status === "running";
2857
+ }
2858
+ function unresolvedMerge(request) {
2859
+ return request.outstandingOperation?.kind === "merge";
2860
+ }
2861
+ function releasedHold(request) {
2862
+ return request.status === "queued" && request.workerToken === void 0 && request.entrypointHold?.releasedAt !== void 0;
2863
+ }
2864
+ function pendingCloseout(request) {
2865
+ return request.status === "merged" && request.closeout === void 0;
2866
+ }
2867
+ function selectRequest(requests) {
2868
+ const waiting = requests.filter((request) => runnable(request, requests)).sort((a, b) => queuePosition(a, requests) - queuePosition(b, requests));
2869
+ return requests.find(unresolvedMerge) ?? waiting[0] ?? requests.find(pendingCloseout);
2870
+ }
2871
+ function ownership(claim, deps) {
2872
+ return (deps.processOwnership ?? processOwnershipState)(
2873
+ claim.ownerPid,
2874
+ claim.ownerPidIdentity,
2875
+ claim.claimedAt
2876
+ );
2877
+ }
2878
+ function wait(deps) {
2879
+ return (deps.sleep ?? delay2)(deps.pollMs ?? POLL_MS);
2880
+ }
2881
+ async function requireWorker(store, repository, token) {
2882
+ const claim = await store.readCoordinator(repository);
2883
+ if (!claim || claim.token !== token || claim.ownerPid !== process.pid || processOwnershipState(claim.ownerPid, claim.ownerPidIdentity, claim.claimedAt) !== "owned")
2884
+ throw new Error("Merge coordinator ownership changed.");
2885
+ return claim;
2886
+ }
2887
+ async function adoptRequest(request, store, token, activate) {
2888
+ await requireWorker(store, request.repository, token);
2889
+ const previousAttempt = request.workerToken !== token && (request.workerToken !== void 0 || request.status === "running");
2890
+ const recovering = active4(request) && previousAttempt;
2891
+ const exhausted = recovering && request.recoveryCount >= MAX_WORKER_RECOVERIES;
2892
+ const next = {
2893
+ ...request,
2894
+ workerToken: token,
2895
+ recoveryCount: recovering && !exhausted ? request.recoveryCount + 1 : request.recoveryCount,
2896
+ status: exhausted ? "needs_attention" : activate && active4(request) ? "running" : request.status,
2897
+ lastError: exhausted ? "Automatic merge worker recovery exhausted; fresh reviewed authorization is required." : request.lastError
2898
+ };
2899
+ if (next.workerToken === request.workerToken && next.status === request.status && next.recoveryCount === request.recoveryCount)
2900
+ return request;
2901
+ return (await store.compareAndSave(request, next)).record;
2902
+ }
2903
+ async function terminalAcknowledgement(store, repository, token) {
2904
+ if (!await store.hasCoordinatorAcknowledgement(repository, token)) return void 0;
2905
+ const requests = await store.list(repository);
2906
+ const owned = requests.filter((request) => request.workerToken === token);
2907
+ if (owned.length === 0 || owned.some((request) => runnable(request, requests))) {
2908
+ return void 0;
2909
+ }
2910
+ const blocker = requests.find(unresolvedMerge);
2911
+ if (requests.some((request) => runnable(request, requests)) && !blocker) return void 0;
2912
+ const newestByPr = /* @__PURE__ */ new Map();
2913
+ for (const request of owned) newestByPr.set(request.prNumber, request);
2914
+ const summary = [...newestByPr.values()].map((request) => {
2915
+ if (request.status === "needs_attention") return `PR #${request.prNumber} needs attention.`;
2916
+ if (request.status === "awaiting_approval") {
2917
+ return `PR #${request.prNumber} awaits entrypoint approval.`;
2918
+ }
2919
+ if (request.status === "cancelled") return `PR #${request.prNumber} cancelled.`;
2920
+ return `PR #${request.prNumber} merged.${request.closeout === void 0 ? " Bookkeeping is pending." : request.closeout.status === "needs_attention" ? " Bookkeeping needs attention." : ""}`;
2921
+ });
2922
+ if (blocker)
2923
+ summary.push(`Publication is blocked by the unresolved merge for PR #${blocker.prNumber}.`);
2924
+ return { accepted: true, message: summary.join(" ") };
2925
+ }
2926
+ async function settledQueueReceipt(store, repository) {
2927
+ const last = (await store.list(repository)).at(-1);
2928
+ const receipt = last?.workerToken ? await terminalAcknowledgement(store, repository, last.workerToken) : void 0;
2929
+ return receipt ? { ...receipt, spawned: false } : { accepted: false, spawned: false, message: "No runnable merge intent remains." };
2930
+ }
2931
+ async function awaitAcknowledgement(store, repository, token, deadline, deps, childError = () => void 0) {
2932
+ do {
2933
+ const claim = await store.readCoordinator(repository);
2934
+ const terminal = await terminalAcknowledgement(store, repository, token);
2935
+ if (terminal) return terminal;
2936
+ if (claim?.ready && ownership(claim, deps) === "owned") return { accepted: true };
2937
+ const error = childError();
2938
+ if (error) throw error;
2939
+ if (!claim || ownership(claim, deps) === "reclaimable") return { accepted: false };
2940
+ if (Date.now() >= deadline) break;
2941
+ await wait(deps);
2942
+ } while (Date.now() < deadline);
2943
+ return { accepted: false };
2944
+ }
2945
+ async function abandonLaunch(child, identity2, startedAt, deps) {
2946
+ if (!child?.pid) return;
2947
+ const state = () => (deps.processOwnership ?? processOwnershipState)(
2948
+ child.pid,
2949
+ identity2,
2950
+ (/* @__PURE__ */ new Date()).toISOString()
2951
+ );
2952
+ if (child.exitCode === null && child.signalCode === null && identity2 && state() === "owned") {
2953
+ (deps.signalWorker ?? signalDetachedWorkerProcess)(child);
2954
+ const deadline = Date.now() + 2e3;
2955
+ while (Date.now() < deadline && state() === "owned") await delay2(25);
2956
+ }
2957
+ if (child.exitCode !== null || child.signalCode !== null || state() === "reclaimable") {
2958
+ await (deps.unregisterProcess ?? unregisterDetachedWorkerProcess)(child.pid, startedAt);
2959
+ }
2960
+ }
2961
+ async function ensureMergeQueueWorker(input, deps = {}) {
2962
+ const repository = MergeQueueRepositorySchema.parse(input.repository);
2963
+ const { store } = input;
2964
+ const deadline = Date.now() + (deps.startupTimeoutMs ?? STARTUP_TIMEOUT_MS);
2965
+ for (let race = 0; race < MAX_OWNERSHIP_RACES && Date.now() < deadline; race += 1) {
2966
+ if (!selectRequest(await store.list(repository))) {
2967
+ return settledQueueReceipt(store, repository);
2968
+ }
2969
+ const claim = await store.claimCoordinator(repository);
2970
+ if (!claim) {
2971
+ const existing = await store.readCoordinator(repository);
2972
+ if (!existing) continue;
2973
+ const acknowledgement = await awaitAcknowledgement(
2974
+ store,
2975
+ repository,
2976
+ existing.token,
2977
+ deadline,
2978
+ deps
2979
+ );
2980
+ if (acknowledgement.accepted) {
2981
+ return { ...acknowledgement, spawned: false };
2982
+ }
2983
+ const current = await store.readCoordinator(repository);
2984
+ if (!current || current.token !== existing.token || ownership(current, deps) === "reclaimable")
2985
+ continue;
2986
+ return {
2987
+ accepted: false,
2988
+ spawned: false,
2989
+ message: "Merge worker readiness is unconfirmed; intent remains recorded. Use ci reconcile to recover."
2990
+ };
2991
+ }
2992
+ let child;
2993
+ let identity2;
2994
+ let registeredAt;
2995
+ let launchError;
2996
+ try {
2997
+ const candidate = selectRequest(await store.list(repository));
2998
+ if (!candidate) {
2999
+ if (await store.releaseCoordinator(repository, claim.token, { onlyIfIdle: true })) {
3000
+ return settledQueueReceipt(store, repository);
3001
+ }
3002
+ await store.releaseCoordinator(repository, claim.token);
3003
+ continue;
3004
+ }
3005
+ await adoptRequest(candidate, store, claim.token, false);
3006
+ if (!selectRequest(await store.list(repository))) {
3007
+ if (!await store.releaseCoordinator(repository, claim.token, { onlyIfIdle: true })) {
3008
+ await store.releaseCoordinator(repository, claim.token);
3009
+ continue;
3010
+ }
3011
+ return {
3012
+ accepted: false,
3013
+ spawned: false,
3014
+ message: "Automatic merge worker recovery exhausted; request needs attention."
3015
+ };
3016
+ }
3017
+ const entryPath = input.entryPath ?? process.argv[1];
3018
+ if (!entryPath) throw new Error("Unable to resolve the Build CLI entry path.");
3019
+ const logPath = path6.join(
3020
+ store.root,
3021
+ "merge-queue",
3022
+ repoStateKey(repository),
3023
+ `worker-${claim.token}.log`
3024
+ );
3025
+ const logFd = openSync(logPath, "a", 384);
3026
+ try {
3027
+ child = (deps.spawn ?? spawn)(
3028
+ process.execPath,
3029
+ [
3030
+ path6.resolve(entryPath),
3031
+ "ci",
3032
+ "merge-worker",
3033
+ "--repository",
3034
+ repository,
3035
+ "--token",
3036
+ claim.token,
3037
+ "--state-root",
3038
+ store.root
3039
+ ],
3040
+ {
3041
+ cwd: candidate.repoRoot,
3042
+ detached: process.platform !== "win32",
3043
+ stdio: ["ignore", logFd, logFd],
3044
+ env: createLocalCiDetachedChildEnv({
3045
+ BUILD_LOCAL_CI_WORKER: "1",
3046
+ BUILD_CI_RECONCILING: "1"
3047
+ })
3048
+ }
3049
+ );
3050
+ child.on("error", (error) => {
3051
+ launchError = error;
3052
+ });
3053
+ child.unref();
3054
+ } finally {
3055
+ closeSync(logFd);
3056
+ }
3057
+ if (!child.pid) throw new Error("Merge worker did not receive a process id.");
3058
+ while (Date.now() < deadline) {
3059
+ if (launchError) throw launchError;
3060
+ identity2 = (deps.processIdentity ?? readProcessIdentity)(child.pid);
3061
+ if (identity2) break;
3062
+ if (child.exitCode !== null || child.signalCode !== null) break;
3063
+ await wait(deps);
3064
+ }
3065
+ if (!identity2) throw new Error("Merge worker process identity could not be established.");
3066
+ registeredAt = await (deps.registerProcess ?? registerDetachedWorkerProcess)({
3067
+ pid: child.pid,
3068
+ taskId: `merge-queue-${claim.token}`,
3069
+ command: "build ci merge-worker",
3070
+ owner: "merge-queue",
3071
+ metadata: { repository, token: claim.token }
3072
+ });
3073
+ await store.transferCoordinator(repository, claim.token, child.pid, identity2);
3074
+ const acknowledgement = await awaitAcknowledgement(
3075
+ store,
3076
+ repository,
3077
+ claim.token,
3078
+ deadline,
3079
+ deps,
3080
+ () => {
3081
+ if (launchError) return launchError;
3082
+ if (child?.exitCode !== null || child?.signalCode !== null) {
3083
+ return new Error("Merge worker exited before acknowledging responsibility.");
3084
+ }
3085
+ return void 0;
3086
+ }
3087
+ );
3088
+ if (acknowledgement.accepted) return { ...acknowledgement, spawned: true };
3089
+ throw new Error("Merge worker readiness was not acknowledged before the startup deadline.");
3090
+ } catch (error) {
3091
+ await abandonLaunch(child, identity2, registeredAt, deps);
3092
+ const message2 = error instanceof Error ? error.message : String(error);
3093
+ await store.releaseCoordinator(repository, claim.token, { startupError: message2 }).catch(() => false);
3094
+ return {
3095
+ accepted: false,
3096
+ spawned: Boolean(child?.pid),
3097
+ message: `${message2} Intent remains recorded; use ci reconcile to recover.`
3098
+ };
3099
+ }
3100
+ }
3101
+ return {
3102
+ accepted: false,
3103
+ spawned: false,
3104
+ message: "Merge worker ownership did not settle within the bounded startup attempt."
3105
+ };
3106
+ }
3107
+ async function waitForTransfer(store, repository, token, deps) {
3108
+ const deadline = Date.now() + (deps.startupTimeoutMs ?? STARTUP_TIMEOUT_MS);
3109
+ while (Date.now() < deadline) {
3110
+ deps.signal?.throwIfAborted();
3111
+ const claim = await store.readCoordinator(repository);
3112
+ if (!claim || claim.token !== token)
3113
+ throw new Error("Merge coordinator ownership changed before startup.");
3114
+ if (claim.ownerPid === process.pid && claim.transferredAt) {
3115
+ await requireWorker(store, repository, token);
3116
+ return;
3117
+ }
3118
+ if (ownership(claim, deps) === "reclaimable")
3119
+ throw new Error("Merge worker launcher exited before transfer.");
3120
+ await wait(deps);
3121
+ }
3122
+ throw new Error("Merge worker assignment was not transferred before the startup deadline.");
3123
+ }
3124
+ async function stopRequest(store, repository, id, token, message2) {
3125
+ const request = await store.read(repository, id);
3126
+ if (!request) throw new Error("Merge request disappeared.");
3127
+ if (!active4(request)) return request;
3128
+ await requireWorker(store, repository, token);
3129
+ return (await store.compareAndSave(request, {
3130
+ ...request,
3131
+ status: "needs_attention",
3132
+ workerToken: token,
3133
+ lastError: message2
3134
+ })).record;
3135
+ }
3136
+ async function closeoutRequest(request, store, token, deps) {
3137
+ if (!pendingCloseout(request)) return;
3138
+ await requireWorker(store, request.repository, token);
3139
+ let closeout;
3140
+ try {
3141
+ if (!request.mergeCommit) throw new Error("Merged request has no confirmed merge commit.");
3142
+ if (!deps.closeout) throw new Error("Merged; bookkeeping handler is unavailable.");
3143
+ closeout = MergeRequestCloseoutSchema.parse(await deps.closeout(request));
3144
+ } catch (error) {
3145
+ closeout = {
3146
+ status: "needs_attention",
3147
+ message: error instanceof Error ? error.message : String(error)
3148
+ };
3149
+ }
3150
+ await store.compareAndSave(request, { ...request, workerToken: token, closeout });
3151
+ }
3152
+ async function runMergeQueueWorkerLoop(input, deps) {
3153
+ const repository = MergeQueueRepositorySchema.parse(input.repository);
3154
+ const store = deps.store ?? new MergeQueueStore(input.stateRoot);
3155
+ await waitForTransfer(store, repository, input.token, deps);
3156
+ let heartbeat;
3157
+ let released = false;
3158
+ const processed = /* @__PURE__ */ new Set();
3159
+ let selectionRaces = 0;
3160
+ try {
3161
+ (deps.keepAwake ?? keepMacAwakeForCurrentProcess)();
3162
+ heartbeat = await (deps.startHeartbeat ?? startDetachedWorkerRegistryHeartbeat)({
3163
+ taskId: `merge-queue-${input.token}`,
3164
+ command: "build ci merge-worker",
3165
+ owner: "merge-queue",
3166
+ metadata: { repository, token: input.token }
3167
+ });
3168
+ await store.acknowledgeCoordinator(repository, input.token);
3169
+ while (!deps.signal?.aborted) {
3170
+ await requireWorker(store, repository, input.token);
3171
+ await deps.beforeSelect?.(store, repository, input.token).catch(() => void 0);
3172
+ const selected = selectRequest(await store.list(repository));
3173
+ if (!selected) {
3174
+ released = await store.releaseCoordinator(repository, input.token, { onlyIfIdle: true });
3175
+ if (released) return;
3176
+ continue;
3177
+ }
3178
+ const request = await adoptRequest(selected, store, input.token, true);
3179
+ if (!active4(request) && !unresolvedMerge(request) && !pendingCloseout(request)) continue;
3180
+ if (request.workerToken !== input.token) {
3181
+ if (++selectionRaces >= MAX_OWNERSHIP_RACES)
3182
+ throw new Error("Merge request adoption race limit reached.");
3183
+ continue;
3184
+ }
3185
+ selectionRaces = 0;
3186
+ if (pendingCloseout(request) && !unresolvedMerge(request)) {
3187
+ if (processed.has(request.id))
3188
+ throw new Error("Merge closeout changed concurrently; needs attention.");
3189
+ processed.add(request.id);
3190
+ await closeoutRequest(request, store, input.token, deps);
3191
+ continue;
3192
+ }
3193
+ if (processed.has(request.id))
3194
+ throw new Error("Merge request processing attempt already consumed.");
3195
+ processed.add(request.id);
3196
+ try {
3197
+ await deps.processRequest(request, store);
3198
+ } catch (error) {
3199
+ await stopRequest(
3200
+ store,
3201
+ repository,
3202
+ request.id,
3203
+ input.token,
3204
+ error instanceof Error ? error.message : String(error)
3205
+ );
3206
+ }
3207
+ let current = await store.read(repository, request.id);
3208
+ if (!current) throw new Error("Merge request disappeared after processing.");
3209
+ if (current.status === "awaiting_approval") processed.delete(request.id);
3210
+ else if (releasedHold(current)) {
3211
+ processed.delete(request.id);
3212
+ continue;
3213
+ }
3214
+ if (active4(current)) {
3215
+ current = await stopRequest(
3216
+ store,
3217
+ repository,
3218
+ request.id,
3219
+ input.token,
3220
+ "Merge processor returned without a terminal disposition; needs attention."
3221
+ );
3222
+ }
3223
+ if (unresolvedMerge(current)) return;
3224
+ await closeoutRequest(current, store, input.token, deps);
3225
+ }
3226
+ } finally {
3227
+ try {
3228
+ await heartbeat?.unregister();
3229
+ } finally {
3230
+ if (!released) await store.releaseCoordinator(repository, input.token).catch(() => false);
3231
+ }
3232
+ }
3233
+ }
3234
+
3235
+ // src/core/ci/nightly-backstop.ts
3236
+ import { readdir as readdir2, readFile as readFile4 } from "fs/promises";
3237
+ import path7 from "path";
3238
+ import { z as z5 } from "zod";
3239
+ var NightlyResultSchema = z5.object({
3240
+ sha: z5.string().regex(/^[a-f0-9]{40}$/i),
3241
+ result: z5.enum(["pass", "fail", "interrupted"]),
3242
+ stamp: z5.string().min(1),
3243
+ repository: z5.string().min(1),
3244
+ reason: z5.string().optional()
3245
+ });
3246
+ var NightlyFailureSchema = z5.object({
3247
+ kind: z5.string(),
3248
+ phase: z5.string().optional(),
3249
+ file: z5.string().nullable().optional(),
3250
+ test: z5.string().nullable().optional(),
3251
+ rerun: z5.array(z5.string()).nullable().optional()
3252
+ });
3253
+ var NightlyFailuresSchema = z5.object({ failures: z5.array(z5.unknown()) });
3254
+ var SHOWN_FAILURES = 3;
3255
+ function failureLabel(failure) {
3256
+ if (failure.kind === "test") return `${failure.file} > ${failure.test}`;
3257
+ if (failure.kind === "file") return `${failure.file} (file failed)`;
3258
+ return `${failure.phase ?? "nightly"} (${failure.kind})`;
3259
+ }
3260
+ function shellQuote(arg) {
3261
+ return /^[\w@%+=:,./-]+$/.test(arg) ? arg : `'${arg.replace(/'/g, `'\\''`)}'`;
3262
+ }
3263
+ async function describeFailures(result) {
3264
+ const runDir = path7.dirname(result.path);
3265
+ const detailsPath = path7.join(runDir, "failures.json");
3266
+ let failures;
3267
+ try {
3268
+ const parsed = NightlyFailuresSchema.parse(JSON.parse(await readFile4(detailsPath, "utf8")));
3269
+ failures = parsed.failures.flatMap((entry) => {
3270
+ const failure = NightlyFailureSchema.safeParse(entry);
3271
+ return failure.success ? [failure.data] : [];
3272
+ });
3273
+ } catch {
3274
+ return {
3275
+ text: ` Per-test detail unavailable for this run; see phase logs under ${path7.join(runDir, "phases")}.`
3276
+ };
3277
+ }
3278
+ if (failures.length === 0) return { text: ` Failure detail is empty; see ${detailsPath}.` };
3279
+ const named = failures.filter((failure) => failure.kind === "test" || failure.kind === "file");
3280
+ const shown = (named.length > 0 ? named : failures).slice(0, SHOWN_FAILURES).map(failureLabel);
3281
+ const more = failures.length > shown.length ? ` (+${failures.length - shown.length} more)` : "";
3282
+ const rerun = failures.find((failure) => failure.rerun && failure.rerun.length > 0)?.rerun;
3283
+ return {
3284
+ text: ` Failed (${failures.length}): ${shown.join("; ")}${more}. Details: ${detailsPath}.`,
3285
+ ...rerun ? { rerun: rerun.map(shellQuote).join(" ") } : {}
3286
+ };
3287
+ }
3288
+ async function readNightlyOptIn(repoRoot) {
3289
+ const result = await execGit(["config", "--local", "--get", "build.nightlyLocal"], {
3290
+ cwd: repoRoot
3291
+ });
3292
+ return result.exitCode === 0 && result.stdout.trim() === "true";
3293
+ }
3294
+ async function readCompletedResults(runsDir) {
3295
+ let entries;
3296
+ try {
3297
+ entries = await readdir2(runsDir);
3298
+ } catch (error) {
3299
+ if (error.code === "ENOENT") return [];
3300
+ throw error;
3301
+ }
3302
+ const results = [];
3303
+ for (const entry of entries) {
3304
+ const file = path7.join(runsDir, entry, "result.json");
3305
+ let raw;
3306
+ try {
3307
+ raw = await readFile4(file, "utf8");
3308
+ } catch {
3309
+ continue;
3310
+ }
3311
+ let json;
3312
+ try {
3313
+ json = JSON.parse(raw);
3314
+ } catch {
3315
+ continue;
3316
+ }
3317
+ const parsed = NightlyResultSchema.safeParse(json);
3318
+ if (parsed.success) results.push({ ...parsed.data, path: file });
3319
+ }
3320
+ return results;
3321
+ }
3322
+ function latest(results) {
3323
+ return results.reduce(
3324
+ (best, candidate) => !best || candidate.stamp > best.stamp ? candidate : best,
3325
+ void 0
3326
+ );
3327
+ }
3328
+ async function checkNightlyBackstop(input, dependencies = {}) {
3329
+ const stateDir = dependencies.stateDir ?? (readEnv(BUILD_NIGHTLY_STATE_DIR).value || path7.join(getStateDir(), "nightly-local"));
3330
+ if (!await (dependencies.readOptIn ?? readNightlyOptIn)(input.repoRoot)) {
3331
+ return {
3332
+ notice: "Info: nightly backstop not opted in on this checkout; merge admission did not consult it."
3333
+ };
3334
+ }
3335
+ const results = (await readCompletedResults(path7.join(stateDir, "runs"))).filter(
3336
+ (result) => result.result !== "interrupted" && result.repository.toLowerCase() === input.repository.toLowerCase()
3337
+ );
3338
+ const baseSha = input.baseSha.toLowerCase();
3339
+ const selected = latest(results.filter((result) => result.sha.toLowerCase() === baseSha)) ?? latest(results);
3340
+ if (!selected) {
3341
+ return {
3342
+ notice: `Info: no completed nightly result under ${stateDir}; merge admission did not consult it.`
3343
+ };
3344
+ }
3345
+ if (selected.result === "pass") return {};
3346
+ const scope = selected.sha.toLowerCase() === baseSha ? `base tip ${baseSha.slice(0, 12)}` : `${selected.sha.slice(0, 12)}, no result yet for base tip ${baseSha.slice(0, 12)}`;
3347
+ const failures = await describeFailures(selected);
3348
+ const rerunLine = failures.rerun ? `
3349
+ Rerun the first failure from the repo root:
3350
+ ${failures.rerun}` : "";
3351
+ return {
3352
+ notice: `Warning: nightly backstop is red (${scope}; failed: ${selected.reason ?? "unspecified"}). Summary: ${selected.path}.${failures.text}${rerunLine}`
3353
+ };
3354
+ }
3355
+
3356
+ // src/core/ci/merge-queue.ts
3357
+ var PrSchema = z6.number().int().positive();
3358
+ var ShaSchema3 = z6.string().regex(/^[a-f0-9]{40}$/i).transform((value) => value.toLowerCase());
3359
+ async function checkMechanicalMergeSupport(repoRoot) {
3360
+ const result = await execGit(["--version"], { cwd: repoRoot });
3361
+ const version = /git version (\d+)\.(\d+)/.exec(result.stdout);
3362
+ if (result.exitCode !== 0 || !version || Number(version[1]) < 2 || Number(version[1]) === 2 && Number(version[2]) < 38) {
3363
+ throw new Error(
3364
+ "Local merge handoff requires Git 2.38 or newer for mechanical merge verification."
3365
+ );
3366
+ }
3367
+ }
3368
+ async function resolveQueueLocation(repoRoot, dependencies) {
3369
+ const root = await (dependencies.resolveRoot ?? resolveCanonicalMainRepoRoot)(
3370
+ path8.resolve(repoRoot)
3371
+ );
3372
+ const repository = await (dependencies.resolveRepository ?? resolveOptionalGitHubRepositoryFromRemote)(root, "origin");
3373
+ return { root, repository, store: dependencies.store ?? new MergeQueueStore() };
3374
+ }
3375
+ function requireRepositoryBinding(expected, metadata) {
3376
+ if (expected.toLowerCase() !== metadata.repository.toLowerCase()) {
3377
+ throw new Error("GitHub repository differs from the canonical origin.");
3378
+ }
3379
+ }
3380
+ function assertMergeAdmissiblePullRequest(pr, repository, defaultBranch, expectedHeadSha) {
3381
+ if (pr.state !== "open" || pr.draft)
3382
+ throw new Error(`Merge admission requires an open, non-draft PR (#${pr.prNumber}).`);
3383
+ if (pr.baseBranch !== defaultBranch || pr.headRepository?.toLowerCase() !== repository.toLowerCase()) {
3384
+ throw new Error("Local merge handoff supports same-repository PRs to the default branch.");
3385
+ }
3386
+ if (pr.autoMergeEnabled) {
3387
+ throw new Error(
3388
+ "Disable independently armed GitHub auto-merge before handing this PR to Build."
3389
+ );
3390
+ }
3391
+ if (pr.headSha !== expectedHeadSha) {
3392
+ throw new Error(
3393
+ "The PR head differs from the reviewed SHA or this request\u2019s verified preparation."
3394
+ );
3395
+ }
3396
+ }
3397
+ async function withApprovalFingerprint(trust, pr, location, dependencies) {
3398
+ const approval = trust?.entrypointApproval;
3399
+ if (!trust || !approval || approval.fingerprint) return trust;
3400
+ if (approval.headSha.toLowerCase() !== pr.headSha.toLowerCase()) return trust;
3401
+ const fingerprint = await (dependencies.resolveEntrypointFingerprint ?? resolveEntrypointFingerprint)({
3402
+ repoRoot: location.root,
3403
+ remote: "origin",
3404
+ baseRef: location.targetBranch,
3405
+ baseSha: pr.baseSha,
3406
+ headSha: pr.headSha
3407
+ });
3408
+ return fingerprint ? { ...trust, entrypointApproval: { ...approval, fingerprint } } : trust;
3409
+ }
3410
+ async function submitMergeRequest(input, dependencies = {}) {
3411
+ const prNumber = PrSchema.parse(input.prNumber);
3412
+ const reviewedSha = ShaSchema3.parse(input.reviewedSha);
3413
+ const { root, repository, store } = await resolveQueueLocation(input.repoRoot, dependencies);
3414
+ if (!repository) throw new Error("Local merge handoff requires a supported GitHub origin.");
3415
+ const suppliedRepository = await (dependencies.resolveRepository ?? resolveOptionalGitHubRepositoryFromRemote)(path8.resolve(input.repoRoot), "origin");
3416
+ if (suppliedRepository?.toLowerCase() !== repository.toLowerCase()) {
3417
+ throw new Error("The submitting worktree and canonical repository have different origins.");
3418
+ }
3419
+ const metadata = await (dependencies.readRepository ?? readGitHubMergeRepository)({
3420
+ repoRoot: root,
3421
+ repository
3422
+ });
3423
+ requireRepositoryBinding(repository, metadata);
3424
+ const previous = (await store.list(repository)).reverse().find((request2) => request2.prNumber === prNumber && request2.reviewedSha === reviewedSha);
3425
+ if (previous?.status === "merged") {
3426
+ return {
3427
+ request: previous,
3428
+ accepted: true,
3429
+ spawned: false,
3430
+ message: previous.lastError ?? "This request already merged."
3431
+ };
3432
+ }
3433
+ if (previous?.outstandingOperation?.kind === "merge") {
3434
+ return {
3435
+ request: previous,
3436
+ accepted: false,
3437
+ spawned: false,
3438
+ message: "The prior merge outcome is unresolved. Run build ci reconcile; it settles the outcome from GitHub, or as not merged once the PR is still open at the reviewed head ten minutes after the merge request."
3439
+ };
3440
+ }
3441
+ const ci = await (dependencies.inspectCi ?? inspectCiIntegration)(root, {
3442
+ repository,
3443
+ remote: false
3444
+ });
3445
+ if (ci.intentError || ci.primary !== "local" || !ci.local.enabled || ci.local.commands.length === 0) {
3446
+ throw new Error(
3447
+ "Configure usable local CI with build init and choose Build Local CI before handing off a merge."
3448
+ );
3449
+ }
3450
+ await (dependencies.checkGit ?? checkMechanicalMergeSupport)(root);
3451
+ const pr = await (dependencies.readPullRequest ?? readGitHubMergePullRequest)({
3452
+ repoRoot: root,
3453
+ repository,
3454
+ prNumber
3455
+ });
3456
+ assertMergeAdmissiblePullRequest(
3457
+ pr,
3458
+ repository,
3459
+ metadata.defaultBranch,
3460
+ previous?.acceptedHeadSha ?? reviewedSha
3461
+ );
3462
+ const nightly = await (dependencies.checkNightly ?? checkNightlyBackstop)({
3463
+ repoRoot: root,
3464
+ repository,
3465
+ baseSha: pr.baseSha
3466
+ }).catch((error) => ({
3467
+ notice: `Warning: could not read the nightly backstop result: ${error instanceof Error ? error.message : String(error)}`
3468
+ }));
3469
+ const trust = await withApprovalFingerprint(
3470
+ input.trust,
3471
+ pr,
3472
+ { root, targetBranch: metadata.defaultBranch },
3473
+ dependencies
3474
+ );
3475
+ const queued = await store.enqueue(
3476
+ {
3477
+ repository,
3478
+ targetBranch: metadata.defaultBranch,
3479
+ repoRoot: root,
3480
+ prNumber,
3481
+ reviewedSha,
3482
+ headBranch: pr.headBranch,
3483
+ mergeMethod: resolveGitHubMergeMethod(metadata)
3484
+ },
3485
+ {
3486
+ resumeStopped: true,
3487
+ ...trust ? { trust } : {},
3488
+ ...input.batchPrNumbers ? { batchPrNumbers: input.batchPrNumbers } : {}
3489
+ }
3490
+ );
3491
+ const launch = await (dependencies.ensureWorker ?? ensureMergeQueueWorker)({
3492
+ repository: queued.request.repository,
3493
+ store
3494
+ });
3495
+ const request = await store.read(repository, queued.request.id) ?? queued.request;
3496
+ return {
3497
+ request,
3498
+ accepted: request.status === "merged" || // Held for an entrypoint approval: the intent is durable and needs no worker yet.
3499
+ request.status === "awaiting_approval" || launch.accepted && (request.status === "queued" || request.status === "running"),
3500
+ spawned: launch.spawned,
3501
+ message: request.lastError ?? (request.status === "awaiting_approval" ? approvalNotice(request) : launch.message),
3502
+ ...nightly.notice ? { nightly: nightly.notice } : {}
3503
+ };
3504
+ }
3505
+ async function cancelMergeRequest(input, dependencies = {}) {
3506
+ const { repository, store } = await resolveQueueLocation(input.repoRoot, dependencies);
3507
+ if (!repository) throw new Error("Local merge handoff requires a supported GitHub origin.");
3508
+ return store.cancel(
3509
+ repository,
3510
+ PrSchema.parse(input.prNumber),
3511
+ input.reviewedSha === void 0 ? void 0 : ShaSchema3.parse(input.reviewedSha)
3512
+ );
3513
+ }
3514
+ async function inspectMergeQueue(input, dependencies = {}) {
3515
+ const { repository, store } = await resolveQueueLocation(input.repoRoot, dependencies);
3516
+ if (!repository) return [];
3517
+ const records = await store.list(repository);
3518
+ if (input.prNumber === void 0) return records;
3519
+ const prNumber = PrSchema.parse(input.prNumber);
3520
+ return records.filter((request) => request.prNumber === prNumber);
3521
+ }
3522
+ async function retireSettledUpdates(store, repository, dependencies) {
3523
+ const readPullRequest = dependencies.readPullRequest ?? readGitHubMergePullRequest;
3524
+ for (const request of await store.list(repository)) {
3525
+ if (request.status === "queued" || request.status === "running") continue;
3526
+ if (request.outstandingOperation?.kind !== "update") continue;
3527
+ const pr = await readPullRequest({
3528
+ repoRoot: request.repoRoot,
3529
+ repository: request.repository,
3530
+ prNumber: request.prNumber
3531
+ }).catch(() => void 0);
3532
+ if (pr?.state !== "merged") continue;
3533
+ await store.compareAndSave(request, {
3534
+ ...request,
3535
+ workerToken: void 0,
3536
+ outstandingOperation: void 0
3537
+ }).catch(() => void 0);
3538
+ }
3539
+ }
3540
+ function approvalNotice(request) {
3541
+ return `Awaiting entrypoint approval: ${request.entrypointHold?.reason ?? "trusted verification entrypoint changed"}. After reviewing the change, approve with: ${approvalCommand(request)}`;
3542
+ }
3543
+ function approvalResumeDependencies(stateRoot, dependencies = {}) {
3544
+ return {
3545
+ readPullRequest: (request) => (dependencies.readPullRequest ?? readGitHubMergePullRequest)({
3546
+ repoRoot: request.repoRoot,
3547
+ repository: request.repository,
3548
+ prNumber: request.prNumber
3549
+ }),
3550
+ inspectValidation: (request) => (dependencies.inspectValidation ?? inspectMergeValidation)(request, stateRoot)
3551
+ };
3552
+ }
3553
+ async function reconcileMergeQueue(input, dependencies = {}) {
3554
+ const { repository, store } = await resolveQueueLocation(input.repoRoot, dependencies);
3555
+ if (!repository) return [];
3556
+ await retireSettledUpdates(store, repository, dependencies);
3557
+ await resumeApprovedHolds(
3558
+ store,
3559
+ repository,
3560
+ approvalResumeDependencies(store.root, dependencies)
3561
+ );
3562
+ const records = await store.list(repository);
3563
+ if (records.some(
3564
+ (request) => request.status === "queued" || request.status === "running" || request.outstandingOperation?.kind === "merge" || request.status === "merged" && request.closeout === void 0
3565
+ )) {
3566
+ const launch = await (dependencies.ensureWorker ?? ensureMergeQueueWorker)({
3567
+ repository,
3568
+ store
3569
+ });
3570
+ if (!launch.accepted) {
3571
+ throw new Error(launch.message ?? "Merge queue worker responsibility was not acknowledged.");
3572
+ }
3573
+ }
3574
+ return store.list(repository);
3575
+ }
3576
+ var mergeBatching = (stateRoot) => ({
3577
+ enabled: async (repoRoot) => resolveMergeBatchingEnabled(repoRoot),
3578
+ ownCiPassed: async (request) => (await inspectMergeValidation(request, stateRoot))?.status === "succeeded",
3579
+ // The context is read from the live target tip's committed config, as the
3580
+ // publisher does; an unresolvable or unreadable status skips batching.
3581
+ readOwnCiStatus: async (request) => readGitHubCommitStatusDetail({
3582
+ repository: request.repository,
3583
+ sha: request.reviewedSha,
3584
+ context: await resolveLiveBatchStatusContext({
3585
+ repoRoot: request.repoRoot,
3586
+ branch: request.targetBranch
3587
+ })
3588
+ }),
3589
+ readPullRequest: (request, prNumber) => readGitHubMergePullRequest({
3590
+ repoRoot: request.repoRoot,
3591
+ repository: request.repository,
3592
+ prNumber
3593
+ }),
3594
+ build: async (input) => (await import("./merge-batch-RTEEMVDB.js")).buildMergeBatchPullRequest(input),
3595
+ closePullRequest: async (request, prNumber) => {
3596
+ requireCommandOk(
3597
+ await runGitHubCli(
3598
+ [
3599
+ "api",
3600
+ "--hostname",
3601
+ "github.com",
3602
+ "--method",
3603
+ "PATCH",
3604
+ `repos/${request.repository}/pulls/${prNumber}`,
3605
+ "-f",
3606
+ "state=closed"
3607
+ ],
3608
+ { cwd: request.repoRoot }
3609
+ ),
3610
+ `Closing batch PR #${prNumber}`
3611
+ );
3612
+ }
3613
+ });
3614
+ async function runMergeQueueWorker(input) {
3615
+ const collected = /* @__PURE__ */ new Set();
3616
+ await runMergeQueueWorkerLoop(input, {
3617
+ beforeSelect: async (store, repository, token) => {
3618
+ await resumeApprovedHolds(store, repository, approvalResumeDependencies(store.root));
3619
+ await settleBatchMembers(store, repository, token, mergeBatching(store.root));
3620
+ await formMergeBatch(store, repository, {
3621
+ ...mergeBatching(store.root),
3622
+ collect: async (request) => {
3623
+ if (collected.has(request.id)) return;
3624
+ collected.add(request.id);
3625
+ await new Promise((resolve) => setTimeout(resolve, MERGE_QUEUE_BATCH_COLLECT_MS));
3626
+ }
3627
+ });
3628
+ },
3629
+ processRequest: (request, store) => processMergeRequest(
3630
+ { request, store, stateRoot: store.root },
3631
+ {
3632
+ statusContext: ({ request: candidate, baseSha }) => resolveMergeStatusContext(candidate, baseSha),
3633
+ acquireSlot: ({ request: candidate, checkContinue }) => acquireMergeValidationSlot(candidate, store.root, checkContinue),
3634
+ inspectValidation: ({ request: candidate }) => inspectMergeValidation(candidate, store.root),
3635
+ reuseValidation: ({ request: candidate, baseSha }) => reuseMergeValidation(candidate, baseSha, store.root),
3636
+ validateHead: ({ request: candidate, slot, baseSha, signal, claimHaltOverride }) => validateMergeHead(candidate, slot, store.root, baseSha, signal, claimHaltOverride),
3637
+ assertBatchMembers: (candidate) => assertBatchMembersUnchanged(candidate, store, mergeBatching(store.root)),
3638
+ landingLease: resolveLandingLeaseEnabled(request.repoRoot) ? createMergeQueueLeaseDependencies({
3639
+ repoRoot: request.repoRoot,
3640
+ repository: request.repository,
3641
+ targetBranch: request.targetBranch,
3642
+ stateRoot: store.root
3643
+ }) : void 0
3644
+ }
3645
+ ),
3646
+ closeout: async (request) => {
3647
+ const closeout = await finalizeMergedQueueRequest(request);
3648
+ if (!request.headBranch.startsWith(MERGE_BATCH_BRANCH_PREFIX)) return closeout;
3649
+ const members = await closeMergedBatchMembers({
3650
+ repoRoot: request.repoRoot,
3651
+ repository: request.repository,
3652
+ batchPrNumber: request.prNumber,
3653
+ batchHeadSha: request.acceptedHeadSha
3654
+ }).catch(
3655
+ (error) => `Batch member closeout failed: ${error instanceof Error ? error.message : String(error)}`
3656
+ );
3657
+ return members ? { ...closeout, message: [closeout.message, members].filter(Boolean).join(" ") } : closeout;
3658
+ }
3659
+ });
3660
+ }
3661
+
3662
+ export {
3663
+ resolveLiveBatchStatusContext,
3664
+ assertBatchMembersCiClear,
3665
+ assertMergeAdmissiblePullRequest,
3666
+ submitMergeRequest,
3667
+ cancelMergeRequest,
3668
+ inspectMergeQueue,
3669
+ reconcileMergeQueue,
3670
+ mergeBatching,
3671
+ runMergeQueueWorker
3672
+ };
3673
+ //# sourceMappingURL=chunk-XZRS7I7W.js.map