@askexenow/exe-os 0.9.384 → 0.9.389

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 (481) hide show
  1. package/deploy/compose/.env.askexe-control-plane.example +6 -0
  2. package/deploy/compose/.env.customer.example +1 -1
  3. package/deploy/compose/.env.example +1 -1
  4. package/deploy/compose/docker-compose.yml +16 -0
  5. package/deploy/compose/generate-env.ts +1 -1
  6. package/deploy/compose/sso-edge/sso-redirect.conf +18 -3
  7. package/deploy/stack-manifests/v0.9.json +1160 -1
  8. package/dist/.dist-verified.json +3 -3
  9. package/dist/{active-agent-F5V4E4UL.js → active-agent-RZ24KSJN.js} +8 -6
  10. package/dist/{active-agent-YTBB4ZQM.js → active-agent-YWSWPIEA.js} +8 -6
  11. package/dist/{agent-defs-generator-7TFISIJQ.js → agent-defs-generator-GY3UBREJ.js} +8 -6
  12. package/dist/{agent-heartbeat-CQPGJIFF.js → agent-heartbeat-GUVUUQVM.js} +1 -1
  13. package/dist/{agent-loop-EZ7LDAGD.js → agent-loop-HPNCH3HK.js} +1 -1
  14. package/dist/{agentic-ontology-XVTUVV7W.js → agentic-ontology-RHYNXEKE.js} +1 -1
  15. package/dist/{artifact-backup-TF6YWQT2.js → artifact-backup-HGUFSIJH.js} +19 -6
  16. package/dist/{backfill-metadata-J6MXIDCW.js → backfill-metadata-XLFASEOD.js} +11 -9
  17. package/dist/{backup-verify-6DVBQMDP.js → backup-verify-TUQEICWA.js} +1 -1
  18. package/dist/{behavior-injection-XDA7ZCYN.js → behavior-injection-GXO7ERGN.js} +14 -12
  19. package/dist/{behavior-promotion-4VLQ7LXK.js → behavior-promotion-V63WIZ4N.js} +13 -11
  20. package/dist/{behaviors-SCCZP7RZ.js → behaviors-RMLDPLOF.js} +18 -11
  21. package/dist/bin/agentic-ontology-backfill.js +12 -10
  22. package/dist/bin/agentic-reflection-backfill.js +13 -11
  23. package/dist/bin/agentic-semantic-label.js +12 -10
  24. package/dist/bin/backfill-conversations.js +11 -9
  25. package/dist/bin/backfill-responses.js +11 -9
  26. package/dist/bin/backfill-vectors.js +13 -11
  27. package/dist/bin/bulk-sync-postgres.js +20 -18
  28. package/dist/bin/cc-doctor.js +15 -13
  29. package/dist/bin/cleanup-stale-review-tasks.js +44 -32
  30. package/dist/bin/cli.js +109 -30
  31. package/dist/bin/customer-readiness.js +55 -25
  32. package/dist/bin/deferred-daemon-restart.js +111 -108
  33. package/dist/bin/exe-actions.js +7 -1
  34. package/dist/bin/exe-agent-config.js +8 -6
  35. package/dist/bin/exe-agent.js +18 -16
  36. package/dist/bin/exe-assign.js +13 -11
  37. package/dist/bin/exe-boot.js +49 -47
  38. package/dist/bin/exe-call.js +9 -7
  39. package/dist/bin/exe-cicd.js +19 -8
  40. package/dist/bin/exe-cloud.js +59 -25
  41. package/dist/bin/exe-config-dump.js +8 -6
  42. package/dist/bin/exe-dispatch.js +23 -21
  43. package/dist/bin/exe-doctor.js +11 -8
  44. package/dist/bin/exe-export-behaviors.js +16 -14
  45. package/dist/bin/exe-forget.js +14 -12
  46. package/dist/bin/exe-gateway.js +12 -10
  47. package/dist/bin/exe-grant.js +6 -6
  48. package/dist/bin/exe-healthcheck.js +15 -13
  49. package/dist/bin/exe-heartbeat.js +28 -26
  50. package/dist/bin/exe-kill.js +28 -26
  51. package/dist/bin/exe-launch-agent.js +35 -33
  52. package/dist/bin/exe-mcp.js +9 -7
  53. package/dist/bin/exe-memory-reads.js +11 -9
  54. package/dist/bin/exe-new-employee.js +13 -11
  55. package/dist/bin/exe-os-mcp-proxy.js +9 -7
  56. package/dist/bin/exe-pending-messages.js +23 -21
  57. package/dist/bin/exe-pending-notifications.js +22 -20
  58. package/dist/bin/exe-pending-reviews.js +23 -21
  59. package/dist/bin/exe-preflight.js +1 -1
  60. package/dist/bin/exe-rename.js +9 -7
  61. package/dist/bin/exe-review.js +59 -55
  62. package/dist/bin/exe-search-quality.js +16 -3
  63. package/dist/bin/exe-search.js +13 -11
  64. package/dist/bin/exe-session-cleanup.js +29 -27
  65. package/dist/bin/exe-settings.js +22 -20
  66. package/dist/bin/exe-start-codex.js +23 -21
  67. package/dist/bin/exe-start-opencode.js +21 -19
  68. package/dist/bin/exe-status.js +24 -22
  69. package/dist/bin/exe-support.js +3 -3
  70. package/dist/bin/exe-team.js +8 -6
  71. package/dist/bin/exe-verify-running-images.js +644 -17
  72. package/dist/bin/git-sweep.js +24 -22
  73. package/dist/bin/graph-backfill.js +12 -10
  74. package/dist/bin/graph-export.js +12 -10
  75. package/dist/bin/hooks-cli.js +4 -4
  76. package/dist/bin/import-history.js +16 -14
  77. package/dist/bin/install.js +113 -110
  78. package/dist/bin/intercom-check.js +5 -5
  79. package/dist/bin/mcp-sessions.js +2 -2
  80. package/dist/bin/orchestration-metrics.js +8 -6
  81. package/dist/bin/postgres-agentic-reflection-backfill.js +2 -2
  82. package/dist/bin/postgres-agentic-semantic-backfill.js +1 -1
  83. package/dist/bin/pre-build-guard.js +55 -16
  84. package/dist/bin/pre-publish.js +1 -1
  85. package/dist/bin/prune-orphan-chunks.js +154 -28
  86. package/dist/bin/reconcile-backlog.js +5 -3
  87. package/dist/bin/registry-proxy.js +1 -1
  88. package/dist/bin/repair-document-chunks.js +11 -9
  89. package/dist/bin/scan-tasks.js +23 -21
  90. package/dist/bin/setup.js +3 -3
  91. package/dist/bin/shard-migrate.js +12 -10
  92. package/dist/bin/stack-update.js +622 -43
  93. package/dist/bin/verify-stack.js +2 -2
  94. package/dist/bin/vps-health-gate.js +1 -1
  95. package/dist/{boot-generation-Q7LGQ3AO.js → boot-generation-VCQXVYGT.js} +7 -5
  96. package/dist/{boot-review-sections-G5774FR5.js → boot-review-sections-3QO6Q7MP.js} +21 -19
  97. package/dist/{branch-hygiene-44WLSOZR.js → branch-hygiene-DDXXI6ZA.js} +59 -21
  98. package/dist/bug-landed-reconciler-TQVNOUCU.js +353 -0
  99. package/dist/build-manifest.json +1269 -1194
  100. package/dist/{capability-cards-I3RHCF4F.js → capability-cards-XPDLT6LU.js} +6 -4
  101. package/dist/{capacity-monitor-7I7GDONU.js → capacity-monitor-N3AW242L.js} +23 -21
  102. package/dist/{catchup-brief-3T5PARIR.js → catchup-brief-3PKX7FUP.js} +29 -27
  103. package/dist/{chunk-C5ISP5FO.js → chunk-2BKJAOO4.js} +11 -1
  104. package/dist/{chunk-JJTPPAZY.js → chunk-2CUQHG6H.js} +1609 -1356
  105. package/dist/{chunk-HU3VD4MQ.js → chunk-2Q2GGYBX.js} +1 -1
  106. package/dist/{chunk-CHIUS5QB.js → chunk-36LMIOVU.js} +9 -9
  107. package/dist/{chunk-46VSBDUW.js → chunk-3DCBDG4T.js} +2 -2
  108. package/dist/{chunk-W3KPQE7H.js → chunk-3GL3E4TD.js} +3 -3
  109. package/dist/{chunk-GL47M7NY.js → chunk-3UD3OF3X.js} +202 -117
  110. package/dist/{chunk-WNBZJSQT.js → chunk-3VW5ZP2F.js} +1 -1
  111. package/dist/{chunk-JDDM6C57.js → chunk-3X74IQBU.js} +1 -1
  112. package/dist/{chunk-NVFNADTX.js → chunk-3ZL4MDQ6.js} +18 -4
  113. package/dist/{chunk-R5E7WNAC.js → chunk-4ESNDGMB.js} +3 -3
  114. package/dist/{chunk-CYCIX7AW.js → chunk-4FLLIQRQ.js} +2 -2
  115. package/dist/{chunk-3RGTLZNO.js → chunk-4GAKF7KR.js} +99 -74
  116. package/dist/{chunk-RVO5O5SR.js → chunk-4S3A52AV.js} +38 -10
  117. package/dist/{chunk-7J2ZQ4XN.js → chunk-4VQ3QR6B.js} +29 -28
  118. package/dist/{chunk-MROSARM5.js → chunk-5BJBTFFQ.js} +7 -1
  119. package/dist/chunk-5LQZABW5.js +6 -0
  120. package/dist/chunk-5ONMHTNI.js +236 -0
  121. package/dist/{chunk-APL7HUE3.js → chunk-5QQJUO3F.js} +200 -31
  122. package/dist/{chunk-UOPCVFVR.js → chunk-5RK75SWH.js} +205 -278
  123. package/dist/{chunk-T5FEAQES.js → chunk-5TYJYGS5.js} +1 -1
  124. package/dist/{chunk-P3QQ52QK.js → chunk-5U6EOTZV.js} +3 -3
  125. package/dist/{chunk-ALRHGEAJ.js → chunk-637FL62O.js} +1 -1
  126. package/dist/{chunk-PUBA5B4M.js → chunk-6L4VIYLZ.js} +33 -19
  127. package/dist/{chunk-VF2KRROX.js → chunk-754P2UXM.js} +1 -1
  128. package/dist/{chunk-CM7N5MBY.js → chunk-7FZG7WP4.js} +5 -5
  129. package/dist/{chunk-NYLJQ2M2.js → chunk-7NEBFB3L.js} +4 -4
  130. package/dist/{chunk-E7C2WNR5.js → chunk-AEIOFFFX.js} +5 -5
  131. package/dist/{chunk-VOVFUKEB.js → chunk-AIB2EWO2.js} +5 -5
  132. package/dist/{chunk-EGOEB42T.js → chunk-AP2C4OGS.js} +3 -3
  133. package/dist/{chunk-MPZ2AVNS.js → chunk-ATNV3BVZ.js} +1 -1
  134. package/dist/{chunk-F4BBMIPG.js → chunk-B4XJQBQB.js} +2 -2
  135. package/dist/{chunk-BPVFZ6Q4.js → chunk-B5EGDNG6.js} +2 -2
  136. package/dist/{chunk-AGC2V7JS.js → chunk-B6CBN6EF.js} +1 -1
  137. package/dist/{chunk-KTKIFXIP.js → chunk-B7FDRN7G.js} +1 -1
  138. package/dist/{chunk-C5HUMAA7.js → chunk-B7WVYLAQ.js} +67 -15
  139. package/dist/chunk-BGVP5RMT.js +185 -0
  140. package/dist/{chunk-FVLNNNH6.js → chunk-BNI4LIU5.js} +35 -4
  141. package/dist/{chunk-P4XLIPB7.js → chunk-BU5JRLZT.js} +1 -1
  142. package/dist/{chunk-PHUHIVD5.js → chunk-C6U7XWOC.js} +34 -5
  143. package/dist/chunk-C7GHNJPW.js +8 -0
  144. package/dist/{chunk-4AR5XKCH.js → chunk-CL2MU3OX.js} +1 -1
  145. package/dist/{chunk-ZPP63JSQ.js → chunk-CL3AB7Z2.js} +1 -1
  146. package/dist/{chunk-FHIFTSPL.js → chunk-CP7XUSOF.js} +8 -3
  147. package/dist/{chunk-3OX34OC3.js → chunk-CUDNRHHT.js} +30 -10
  148. package/dist/chunk-CYUUNIEL.js +21 -0
  149. package/dist/{chunk-P2UY34XE.js → chunk-D4C57S2H.js} +5 -5
  150. package/dist/{chunk-XSUALA32.js → chunk-DDYFNBYV.js} +3 -3
  151. package/dist/{chunk-LSNBTGW4.js → chunk-DPYZW42H.js} +1 -1
  152. package/dist/{chunk-FMIYFHLE.js → chunk-E2PRXEZK.js} +9 -3
  153. package/dist/{chunk-L63V25HS.js → chunk-E54SPUYJ.js} +3 -3
  154. package/dist/{chunk-DMUOTCDN.js → chunk-E5CKRHIZ.js} +1 -1
  155. package/dist/{chunk-BV2IET6B.js → chunk-EVMOKRBC.js} +15 -2
  156. package/dist/{chunk-YLFCH3VA.js → chunk-FDWI3T6I.js} +133 -31
  157. package/dist/{chunk-PWC3C5ME.js → chunk-FNJTCJGY.js} +4 -4
  158. package/dist/{chunk-TNS63ATU.js → chunk-FWRPDSO3.js} +350 -50
  159. package/dist/{chunk-JA3Y5TV6.js → chunk-GDLB6BPM.js} +141 -74
  160. package/dist/{chunk-IGJMS7C5.js → chunk-GOVGG7HU.js} +2 -2
  161. package/dist/{chunk-MB47DRF5.js → chunk-GP7CTIUK.js} +2 -2
  162. package/dist/{chunk-L5EPGGDP.js → chunk-GWAN2BDB.js} +4 -4
  163. package/dist/{chunk-YRQ26MNP.js → chunk-H3YF7X4J.js} +12 -5
  164. package/dist/{chunk-VLBHZ6TF.js → chunk-H6PLIFUE.js} +2 -2
  165. package/dist/{chunk-GSLFH4AI.js → chunk-HRNM3N4Y.js} +6 -15
  166. package/dist/chunk-IG2ISRTF.js +48 -0
  167. package/dist/{chunk-MWEIBVBS.js → chunk-IJCWBYA5.js} +14 -1
  168. package/dist/{chunk-NKEFYE4R.js → chunk-IPTUKX3N.js} +1 -1
  169. package/dist/{chunk-RB3J2WYQ.js → chunk-IVE7H6KA.js} +1 -1
  170. package/dist/{chunk-HA63SH5W.js → chunk-IWY2OGQG.js} +1 -1
  171. package/dist/{chunk-BQEZPRUC.js → chunk-IZWMWJZT.js} +64 -46
  172. package/dist/{chunk-AHESD3KW.js → chunk-J4UVI652.js} +1 -1
  173. package/dist/{chunk-PLII76NJ.js → chunk-JBUCA4OX.js} +33 -6
  174. package/dist/{chunk-2PZTMSMG.js → chunk-JOF4HQDF.js} +5 -5
  175. package/dist/{chunk-OBBO6EJR.js → chunk-JQN3QGSF.js} +1 -1
  176. package/dist/{chunk-P4PIZOL2.js → chunk-JWYIH5DR.js} +3 -3
  177. package/dist/{chunk-3XY57PKT.js → chunk-KZLKE7I3.js} +210 -48
  178. package/dist/{chunk-DYVLEMKS.js → chunk-L3GXMYBK.js} +1 -1
  179. package/dist/{chunk-72V3S44T.js → chunk-LUA4SLJY.js} +46 -19
  180. package/dist/{chunk-ZT4LFTUJ.js → chunk-LW3JCBXP.js} +20 -3
  181. package/dist/{chunk-YLB365YX.js → chunk-M5XHN4OM.js} +3 -3
  182. package/dist/{chunk-VHHX4UCP.js → chunk-MDTWARSF.js} +22 -93
  183. package/dist/{chunk-JFJP6ATJ.js → chunk-MWKXQSF6.js} +3 -2
  184. package/dist/{chunk-VJEJ7JNB.js → chunk-N32DVIKY.js} +1 -1
  185. package/dist/{chunk-MUCNYSJP.js → chunk-NRLJOV7M.js} +32 -4
  186. package/dist/{chunk-PL4CFSQ7.js → chunk-NSXJOSH7.js} +1 -1
  187. package/dist/{chunk-NF5FOSLD.js → chunk-NVTG66CK.js} +1 -1
  188. package/dist/chunk-O45GBJEH.js +1500 -0
  189. package/dist/{chunk-4AAMFWJT.js → chunk-OHZSTZXN.js} +4 -2
  190. package/dist/chunk-OSIS7ZQW.js +344 -0
  191. package/dist/{chunk-E3ZM32LF.js → chunk-P22VFGFK.js} +14 -5
  192. package/dist/{chunk-KILR4FBR.js → chunk-PAFLR7WD.js} +3 -3
  193. package/dist/{chunk-R3CZWFFC.js → chunk-PEYQ6MWO.js} +15 -3
  194. package/dist/{chunk-RDWIKDMY.js → chunk-PZR54BEI.js} +8 -20
  195. package/dist/{chunk-2FKU4ZEO.js → chunk-QDBKMEYY.js} +9 -9
  196. package/dist/{chunk-7UHZBVP3.js → chunk-QDVLVLXP.js} +97 -31
  197. package/dist/{chunk-PRDDPQLC.js → chunk-QLXEQRHD.js} +5 -5
  198. package/dist/{chunk-MUAWCFW6.js → chunk-QNPDXV5V.js} +5 -2
  199. package/dist/{chunk-LR5O2XWZ.js → chunk-QUZYLDNJ.js} +5 -5
  200. package/dist/chunk-RBQUXHYK.js +48 -0
  201. package/dist/{chunk-3SUPEE6H.js → chunk-RDMBSHGG.js} +3 -3
  202. package/dist/{chunk-I5XIW2BP.js → chunk-RGUM474V.js} +7 -7
  203. package/dist/{chunk-OAFENIHN.js → chunk-RJ67XAJU.js} +76 -28
  204. package/dist/{chunk-LFUBEHFW.js → chunk-RT57D7C4.js} +39 -1
  205. package/dist/{chunk-MKM6D7JM.js → chunk-RWG7DLOG.js} +42 -5
  206. package/dist/{chunk-P3UX5EH4.js → chunk-RWKAHHIX.js} +3 -3
  207. package/dist/{chunk-XEBKIWI7.js → chunk-RYNT3HB7.js} +12 -12
  208. package/dist/{chunk-EUGEFBGV.js → chunk-S2V272KJ.js} +3 -3
  209. package/dist/{chunk-W6BZ4UGA.js → chunk-SGYEM2PD.js} +1 -1
  210. package/dist/{chunk-VROK4X2L.js → chunk-SJP57MQM.js} +1 -1
  211. package/dist/{chunk-5MVKTNZP.js → chunk-SL4JWAMQ.js} +54 -4
  212. package/dist/{chunk-MOANZQXF.js → chunk-SLMKLEU6.js} +1 -1
  213. package/dist/{chunk-RZN2LEAK.js → chunk-SQA7XCYI.js} +3 -3
  214. package/dist/{chunk-4YNA3CM6.js → chunk-SSFS4NCH.js} +262 -56
  215. package/dist/{chunk-6FIC57PH.js → chunk-T4P2G3PO.js} +1 -1
  216. package/dist/{chunk-5U2IJBFV.js → chunk-TA3B4N24.js} +1 -1
  217. package/dist/{chunk-ZOTTYCP5.js → chunk-TEWWBVQP.js} +40 -4
  218. package/dist/{chunk-SDYWZRPN.js → chunk-TLSFGXXE.js} +1 -1
  219. package/dist/{chunk-L3VUW5M4.js → chunk-TQS2YHLB.js} +1 -1
  220. package/dist/{chunk-KXYKSLW4.js → chunk-TU4M757W.js} +399 -116
  221. package/dist/{chunk-MC4IM2CC.js → chunk-TYD4F4XU.js} +6 -6
  222. package/dist/{chunk-TKS5GMYA.js → chunk-U44Y7RC7.js} +2 -0
  223. package/dist/{chunk-B7AMNZTC.js → chunk-UKBPEEEQ.js} +2 -2
  224. package/dist/{chunk-ZBPZDADW.js → chunk-UWVXQEQQ.js} +467 -78
  225. package/dist/{chunk-P2VVMGMI.js → chunk-V4WH3FV7.js} +35 -16
  226. package/dist/{worktree-ownership-guard-O6JQF2CL.js → chunk-V6F4I2XY.js} +13 -14
  227. package/dist/{chunk-JFTUAV4J.js → chunk-VBKUESIW.js} +1 -1
  228. package/dist/{chunk-AZSTIAVL.js → chunk-VBPYYJPC.js} +1 -1
  229. package/dist/{chunk-LKTN22L6.js → chunk-VIP4LKLO.js} +57 -24
  230. package/dist/{chunk-L5CGXEZC.js → chunk-WAAGVFFN.js} +63 -71
  231. package/dist/{chunk-D3LXVAIQ.js → chunk-WC7AE3PT.js} +244 -103
  232. package/dist/{chunk-EOTZSAQL.js → chunk-WUCUDHKR.js} +1 -1
  233. package/dist/{chunk-VF3BZOZK.js → chunk-X7HZHKY4.js} +50 -3
  234. package/dist/{chunk-LFBLQE2P.js → chunk-XCAKYVKI.js} +1 -1
  235. package/dist/{chunk-UXN4BICC.js → chunk-XMX5J52Z.js} +1 -1
  236. package/dist/{chunk-6YDY6ZTD.js → chunk-XTCXGWTV.js} +9 -4
  237. package/dist/{chunk-XUZK23V7.js → chunk-XW2FW6LS.js} +2 -2
  238. package/dist/{chunk-CH7BUBP3.js → chunk-XWVHUDEK.js} +5 -4
  239. package/dist/{chunk-S26CV7BE.js → chunk-YA5LK6KB.js} +45 -8
  240. package/dist/{chunk-ELCRJGCH.js → chunk-YMRQIENV.js} +2 -2
  241. package/dist/{chunk-QNZ4562V.js → chunk-Z3LXWCB7.js} +1 -1
  242. package/dist/{chunk-EYMFVITZ.js → chunk-Z7I6NQMA.js} +2 -2
  243. package/dist/{chunk-6BJCDWSM.js → chunk-ZB4FS2EG.js} +1 -1
  244. package/dist/{chunk-ZAEUEDTN.js → chunk-ZD6FZ3WL.js} +1 -1
  245. package/dist/{chunk-FEU6QEEL.js → chunk-ZM6EZAVY.js} +1 -0
  246. package/dist/{chunk-2KY7WZZF.js → chunk-ZPA4R2UI.js} +1 -1
  247. package/dist/chunk-ZPPPHVSU.js +285 -0
  248. package/dist/{chunk-ELEGH4DX.js → chunk-ZUGQPYUY.js} +51 -4
  249. package/dist/{chunk-UX2EO653.js → chunk-ZZWFZ46U.js} +66 -8
  250. package/dist/{cleanup-graph-garbage-VQAJ2RBT.js → cleanup-graph-garbage-HFUD5ZQT.js} +12 -10
  251. package/dist/{cloud-sync-command-J7NEFUY7.js → cloud-sync-command-3JHJJKPN.js} +25 -3
  252. package/dist/{cloud-sync-db-init-KWEMNMGA.js → cloud-sync-db-init-2LHIQOSF.js} +1 -1
  253. package/dist/{cloud-sync-shutdown-P63AUTUI.js → cloud-sync-shutdown-ZXAXA3Z7.js} +6 -4
  254. package/dist/{cloud-sync-state-QF3TMMJ4.js → cloud-sync-state-VSQFGPP6.js} +1 -1
  255. package/dist/{co-activation-7IHRPBZN.js → co-activation-NX4EH4AP.js} +6 -4
  256. package/dist/{co-occurrence-R5AG4OAU.js → co-occurrence-5LBYCW3U.js} +10 -8
  257. package/dist/{consent-passphrase-5ZBDJ5G7.js → consent-passphrase-65W3DJIJ.js} +2 -2
  258. package/dist/{content-extractor-ZLXC6GNR.js → content-extractor-OGXETBOX.js} +2 -2
  259. package/dist/{conversation-entity-extractor-7FRKLLBD.js → conversation-entity-extractor-FQCOAH4F.js} +5 -4
  260. package/dist/conversations-retention-XTBFJBUM.js +166 -0
  261. package/dist/{crdt-sync-IFRZOEIS.js → crdt-sync-7TDUBTLH.js} +4 -4
  262. package/dist/{crm-webhook-L2S63ASV.js → crm-webhook-776JTY55.js} +2 -2
  263. package/dist/{cto-delegation-gate-3WU7OHX7.js → cto-delegation-gate-ZNUWVYCN.js} +25 -23
  264. package/dist/{daemon-orchestration-3VQYKLOR.js → daemon-orchestration-76BOFDEP.js} +30 -24
  265. package/dist/{doc-graph-extractor-YIR5E7OF.js → doc-graph-extractor-F724KZJX.js} +10 -8
  266. package/dist/{documents-QEHPTYIY.js → documents-Q24V4GRD.js} +13 -11
  267. package/dist/{dreaming-LNFE7DTN.js → dreaming-IY6NQTSP.js} +27 -25
  268. package/dist/{employee-boot-brief-ZTXDYNWM.js → employee-boot-brief-CCTXO5DQ.js} +35 -29
  269. package/dist/{entity-boost-R5N6JWXD.js → entity-boost-XGOB7HDM.js} +1 -1
  270. package/dist/{exe-drift-WYSJVZFO.js → exe-drift-NEPXLNGP.js} +7 -5
  271. package/dist/{exe-export-GOLNAEAV.js → exe-export-ZHJQFKR2.js} +14 -12
  272. package/dist/{exe-import-U4BSI6OH.js → exe-import-7SVNI4FW.js} +14 -12
  273. package/dist/{exe-key-V42G3NGO.js → exe-key-QJKU56WZ.js} +119 -108
  274. package/dist/{exe-org-U4GLQ6OP.js → exe-org-SJXHLV6K.js} +76 -51
  275. package/dist/{exe-snapshot-U7ONBYNY.js → exe-snapshot-EFWPXTXE.js} +45 -36
  276. package/dist/{fast-db-init-W3NGKAEU.js → fast-db-init-BQZOQNT3.js} +1 -1
  277. package/dist/{founder-context-ZSIBRVDM.js → founder-context-KG2ZAOXY.js} +2 -2
  278. package/dist/{founder-monitor-envelope-L3IWZLFY.js → founder-monitor-envelope-NYB5LLU4.js} +4 -4
  279. package/dist/gateway/index.js +20 -18
  280. package/dist/{gateway-client-DJFCVIPT.js → gateway-client-ODMUXUL3.js} +1 -1
  281. package/dist/{git-staleness-OF24QXFZ.js → git-staleness-OPGEAJA7.js} +6 -4
  282. package/dist/{global-procedures-WSIOWO43.js → global-procedures-3KXY4LL6.js} +8 -6
  283. package/dist/{graph-auto-extract-IEVVN3L3.js → graph-auto-extract-I7H2F5NJ.js} +10 -8
  284. package/dist/{graph-query-JDZWAVCU.js → graph-query-E7JBSNY5.js} +1 -1
  285. package/dist/{graph-rag-BKAD2XQA.js → graph-rag-H24CMFP2.js} +5 -4
  286. package/dist/{hook-integrity-TPWDGX2M.js → hook-integrity-PGQ3OX4S.js} +1 -1
  287. package/dist/{hook-prefetch-FD6VGJJA.js → hook-prefetch-7AZJER6Z.js} +23 -21
  288. package/dist/hook-registration-liveness-NGQRFKAA.js +11 -0
  289. package/dist/hooks/bug-report-worker.js +26 -24
  290. package/dist/hooks/codex-stop-task-finalizer.js +28 -26
  291. package/dist/hooks/commit-complete.js +26 -24
  292. package/dist/hooks/error-recall.js +16 -14
  293. package/dist/hooks/exe-heartbeat-hook.js +9 -7
  294. package/dist/hooks/ingest-worker.js +12 -10
  295. package/dist/hooks/ingest.js +22 -17
  296. package/dist/hooks/instructions-loaded.js +11 -9
  297. package/dist/hooks/manifest.json +20 -20
  298. package/dist/hooks/notification.js +10 -8
  299. package/dist/hooks/post-compact.js +9 -7
  300. package/dist/hooks/post-tool-combined.js +14 -14
  301. package/dist/hooks/pre-compact.js +26 -24
  302. package/dist/hooks/pre-tool-use.js +2 -2
  303. package/dist/hooks/prompt-submit.js +47 -41
  304. package/dist/hooks/session-end.js +38 -42
  305. package/dist/hooks/session-start.js +26 -18
  306. package/dist/hooks/stop.js +29 -27
  307. package/dist/hooks/subagent-stop.js +29 -27
  308. package/dist/hooks/summary-worker.js +32 -30
  309. package/dist/index.js +37 -35
  310. package/dist/{installer-OTI5SA43.js → installer-2ZS3LHCU.js} +12 -9
  311. package/dist/{installer-NMOBSEZ2.js → installer-NH3A3HWC.js} +14 -9
  312. package/dist/{installer-SRJGE4GU.js → installer-UJ73PGRD.js} +21 -9
  313. package/dist/{intercom-queue-4FFXCBHO.js → intercom-queue-OE25GJF2.js} +21 -19
  314. package/dist/lib/agent-config.js +2 -2
  315. package/dist/lib/cloud-sync.js +24 -18
  316. package/dist/lib/consolidation.js +13 -11
  317. package/dist/lib/crypto.js +3 -3
  318. package/dist/lib/database.js +6 -4
  319. package/dist/lib/db-backup.js +1 -1
  320. package/dist/lib/db-write-worker-client.js +3 -1
  321. package/dist/lib/db-write-worker.js +65 -89
  322. package/dist/lib/db.js +6 -4
  323. package/dist/lib/delivery-truth.js +5 -1
  324. package/dist/lib/device-registry.js +1 -1
  325. package/dist/lib/embedder.js +2 -2
  326. package/dist/lib/employee-templates.js +9 -7
  327. package/dist/lib/employees.js +6 -4
  328. package/dist/lib/exe-daemon.js +425 -314
  329. package/dist/lib/file-grep.js +15 -21
  330. package/dist/lib/hybrid-search.js +19 -13
  331. package/dist/lib/identity-templates.js +1 -1
  332. package/dist/lib/identity.js +6 -4
  333. package/dist/lib/keychain.js +1 -1
  334. package/dist/lib/license.js +22 -6
  335. package/dist/lib/messaging.js +22 -20
  336. package/dist/lib/post-tool-memory.js +10 -8
  337. package/dist/lib/registry-proxy.js +1 -1
  338. package/dist/lib/reminders.js +7 -5
  339. package/dist/lib/runtime-table.js +1 -1
  340. package/dist/lib/schedules.js +12 -10
  341. package/dist/lib/session-registry.js +8 -6
  342. package/dist/lib/skill-learning.js +13 -11
  343. package/dist/lib/status-brief.js +2 -2
  344. package/dist/lib/store.js +17 -9
  345. package/dist/lib/task-router.js +7 -5
  346. package/dist/lib/tasks.js +23 -21
  347. package/dist/lib/tmux-routing.js +21 -19
  348. package/dist/lib/tmux-status.js +4 -1
  349. package/dist/lib/token-spend.js +7 -5
  350. package/dist/lib/ws-client.js +1 -1
  351. package/dist/{license-gate-R4JWTWM4.js → license-gate-XQ2KN5I5.js} +3 -3
  352. package/dist/{license-recheck-3MBJ4IJX.js → license-recheck-IIBKR2V5.js} +1 -1
  353. package/dist/{license-sync-BVSG3WMW.js → license-sync-76JVI2M5.js} +151 -8
  354. package/dist/{local-secret-inventory-JWNBHZZ4.js → local-secret-inventory-MJT7NJLY.js} +2 -2
  355. package/dist/{mailbox-inbox-MTAT4MXX.js → mailbox-inbox-BPZP4GIZ.js} +1 -1
  356. package/dist/maintenance-worker-54VZTFIK.js +371 -0
  357. package/dist/mcp/register-tools.js +110 -107
  358. package/dist/mcp/server.js +113 -110
  359. package/dist/mcp/tools/complete-reminder.js +8 -6
  360. package/dist/mcp/tools/create-reminder.js +8 -6
  361. package/dist/mcp/tools/create-task.js +26 -24
  362. package/dist/mcp/tools/deactivate-behavior.js +15 -13
  363. package/dist/mcp/tools/list-reminders.js +8 -6
  364. package/dist/mcp/tools/list-tasks.js +26 -24
  365. package/dist/mcp/tools/send-message.js +25 -23
  366. package/dist/mcp/tools/update-task.js +24 -22
  367. package/dist/{mcp-child-registry-TIZERM7O.js → mcp-child-registry-5VUG5ATZ.js} +1 -1
  368. package/dist/{mcp-http-config-VUMSFWFH.js → mcp-http-config-SKXESWHA.js} +9 -7
  369. package/dist/{mcp-session-identity-DZHWEJRV.js → mcp-session-identity-HYMLJXXA.js} +21 -19
  370. package/dist/{memory-cards-JVXTMSAT.js → memory-cards-PAZDUQ3B.js} +16 -32
  371. package/dist/{memory-graph-extractor-PPBJMYFJ.js → memory-graph-extractor-BC6QKBY6.js} +11 -9
  372. package/dist/{memory-poisoning-defense-K5Q74XQ4.js → memory-poisoning-defense-BRGIHF6J.js} +9 -7
  373. package/dist/{memory-pressure-watchdog-YMJH5CSA.js → memory-pressure-watchdog-HR6TPOMT.js} +22 -20
  374. package/dist/{memory-reflection-7BZWIHRE.js → memory-reflection-LO7VFBL3.js} +38 -59
  375. package/dist/memory-write-repository-J25USX63.js +16 -0
  376. package/dist/{monitor-grants-Q7X3MYQP.js → monitor-grants-YQTO6NWR.js} +3 -3
  377. package/dist/{monitor-identity-ZQB4IXEE.js → monitor-identity-POVTG76Y.js} +2 -2
  378. package/dist/{notification-ledger-XRAXL5OI.js → notification-ledger-KLD6GZTG.js} +22 -20
  379. package/dist/{notifications-JE3PX55X.js → notifications-3R6HNHDR.js} +21 -19
  380. package/dist/{orchestration-events-JCQ5F5IJ.js → orchestration-events-CHY3GOJD.js} +7 -5
  381. package/dist/{orchestrator-X65INDTR.js → orchestrator-GAL3HOAD.js} +23 -21
  382. package/dist/{org-brief-session-3PQ22UIX.js → org-brief-session-LQ2DBYVF.js} +17 -15
  383. package/dist/{org-consent-policy-VGELJ7K7.js → org-consent-policy-KFUP7QPG.js} +5 -5
  384. package/dist/{org-join-6TO5ZYUZ.js → org-join-MXZCIQWX.js} +8 -8
  385. package/dist/{org-key-admin-XHYVRRAZ.js → org-key-admin-4UK6QUT7.js} +9 -9
  386. package/dist/{org-key-pin-TTTYGTQL.js → org-key-pin-ZOX4OM3O.js} +3 -3
  387. package/dist/{org-membership-Y7T2BS57.js → org-membership-D5TPN45F.js} +4 -4
  388. package/dist/{org-partner-config-OP4NEUNF.js → org-partner-config-3FXSYPQA.js} +7 -7
  389. package/dist/{org-partner-runtime-YNVUNT2R.js → org-partner-runtime-TH3LMPII.js} +20 -16
  390. package/dist/{org-row-visibility-JBBXNYXD.js → org-row-visibility-Y5R33C5U.js} +1 -1
  391. package/dist/{pipeline-router-EIBB7QNG.js → pipeline-router-E2JZLDK6.js} +7 -5
  392. package/dist/{plan-limits-MEWXYLEN.js → plan-limits-NYI6R4IK.js} +9 -7
  393. package/dist/{platform-procedures-Z7ZGBJWN.js → platform-procedures-RTD5E3BD.js} +5 -3
  394. package/dist/{pre-tool-use-impl-C6KJY22W.js → pre-tool-use-impl-LT4FRXBP.js} +127 -83
  395. package/dist/{project-boot-VEYFCCSL.js → project-boot-LIMXQKAF.js} +25 -17
  396. package/dist/{project-name-UYORSNGE.js → project-name-V62GWALX.js} +7 -5
  397. package/dist/{projection-worker-WYFDWS57.js → projection-worker-KLE3IZQ2.js} +5 -2
  398. package/dist/{prospective-memory-E6XBQFJA.js → prospective-memory-HRN7JFT2.js} +6 -4
  399. package/dist/{push-notifications-M7YU63FY.js → push-notifications-XC6EQEXF.js} +22 -20
  400. package/dist/{reproject-worktree-memories-B5XMGJSD.js → reproject-worktree-memories-4CAVZSFE.js} +21 -19
  401. package/dist/{reranker-SRA4BR7N.js → reranker-2RU6IIX2.js} +1 -1
  402. package/dist/{retrieval-health-P2JPZUPV.js → retrieval-health-4Q5TOV2S.js} +1 -1
  403. package/dist/{review-polling-KQPN7IQE.js → review-polling-WOWIMBCL.js} +22 -20
  404. package/dist/runtime/index.js +30 -28
  405. package/dist/{session-events-6LDDKKY3.js → session-events-OPMS2C5F.js} +22 -20
  406. package/dist/{session-kill-telemetry-5DSRGWCL.js → session-kill-telemetry-3GWATSIX.js} +22 -20
  407. package/dist/{session-scope-XG5R3P3D.js → session-scope-TIRGR4MH.js} +21 -19
  408. package/dist/{setup-wizard-7QFLZIKT.js → setup-wizard-B2JBVGNH.js} +3 -3
  409. package/dist/{shard-manager-HHHHV2KZ.js → shard-manager-C7LYX6E4.js} +5 -1
  410. package/dist/{skill-refinement-I5RMAHSC.js → skill-refinement-FWQFYNBI.js} +7 -5
  411. package/dist/skill-retrieval-PPLFC5RY.js +166 -0
  412. package/dist/skill-usage-NDB67PU2.js +25 -0
  413. package/dist/{stack-generate-manifest-AKMZ3AUX.js → stack-generate-manifest-AXI55MEQ.js} +6 -6
  414. package/dist/{stack-promote-34CE2HHF.js → stack-promote-6N23OGZY.js} +6 -6
  415. package/dist/stack-release-2CXFYVWM.js +2770 -0
  416. package/dist/{stack-update-ZQQ5YYPJ.js → stack-update-U53FBVCQ.js} +13 -5
  417. package/dist/{steward-gate-AQJ6KT2C.js → steward-gate-TUA3QCN7.js} +7 -5
  418. package/dist/{subagent-brief-injection-3GU7YAWY.js → subagent-brief-injection-N52RWYFZ.js} +34 -32
  419. package/dist/{subagent-spawn-ledger-52I66CLW.js → subagent-spawn-ledger-OI23OT4I.js} +1 -1
  420. package/dist/support-bug-actions-XWCJI6S5.js +30 -0
  421. package/dist/support-inbox-MMVTCXRX.js +77 -0
  422. package/dist/{support-outbox-KNI3FR3G.js → support-outbox-2TG2UV6J.js} +2 -2
  423. package/dist/{task-enforcement-CAA6D2MH.js → task-enforcement-JDMBAAOY.js} +22 -20
  424. package/dist/{task-request-inbox-QBZVU66E.js → task-request-inbox-PKJCYKJV.js} +9 -5
  425. package/dist/{task-scope-NHXB4ZRN.js → task-scope-32HQEV6W.js} +27 -19
  426. package/dist/{tasks-crud-JHSWL5NM.js → tasks-crud-CYKUKS3F.js} +21 -19
  427. package/dist/{tasks-notify-7PBE4U7T.js → tasks-notify-YND5MKWJ.js} +22 -20
  428. package/dist/{tasks-review-PPPVEE62.js → tasks-review-CG2A5GM3.js} +23 -19
  429. package/dist/{telemetry-upload-YU2VRNHX.js → telemetry-upload-WLUMEFAB.js} +11 -9
  430. package/dist/{token-budget-QCMWKG3K.js → token-budget-3YHAY5LL.js} +7 -5
  431. package/dist/{tool-capability-index-7AWQAJRP.js → tool-capability-index-2ADOWH43.js} +2 -2
  432. package/dist/{tool-gates-5Q3RSQEG.js → tool-gates-7ENOOYMR.js} +5 -3
  433. package/dist/{tool-gates-QD26D2NE.js → tool-gates-EKJTLVGN.js} +1 -1
  434. package/dist/{tool-telemetry-LTOIC64I.js → tool-telemetry-VUP7S6HC.js} +1 -1
  435. package/dist/tui/App.js +59 -35
  436. package/dist/{tui-data-NDBXD6U7.js → tui-data-BOQTOW2G.js} +25 -23
  437. package/dist/{update-support-BMJM4NG7.js → update-support-WT3S3AQ4.js} +21 -21
  438. package/dist/{whatsapp-QQLDAHUX.js → whatsapp-OZGIF2EM.js} +2 -2
  439. package/dist/{wiki-acl-W6WDCVCP.js → wiki-acl-LTEQJVDA.js} +1 -1
  440. package/dist/{work-claims-6DM32MGO.js → work-claims-CMV2GWIW.js} +8 -6
  441. package/dist/{work-state-heartbeat-HG7D4AWJ.js → work-state-heartbeat-S45TXK4M.js} +1 -1
  442. package/dist/{worker-gate-FTMW7GBB.js → worker-gate-7Z6NMM3G.js} +1 -1
  443. package/dist/{workflow-engine-ZWC6CCXE.js → workflow-engine-ZSQDXZQ7.js} +2 -2
  444. package/dist/{worktree-EMQCPGFE.js → worktree-NMWDVT6N.js} +8 -6
  445. package/dist/worktree-ownership-guard-ZVIWJAOP.js +35 -0
  446. package/dist/{worktree-sweep-XVUJZBXV.js → worktree-sweep-Q62ZIT3U.js} +9 -7
  447. package/package.json +14 -8
  448. package/release-notes.json +7 -41
  449. package/src/commands/exe/afk.md +6 -0
  450. package/src/commands/exe/call.md +6 -0
  451. package/src/commands/exe/employee-heartbeat.md +6 -0
  452. package/src/commands/exe/intercom.md +6 -0
  453. package/src/commands/exe/kill.md +6 -0
  454. package/src/commands/exe/launch.md +6 -0
  455. package/src/commands/exe/logs.md +1 -1
  456. package/src/commands/exe/sessions.md +6 -0
  457. package/src/commands/exe/setup.md +7 -3
  458. package/src/commands/exe/status.md +6 -0
  459. package/src/commands/exe-reference.md +25 -36
  460. package/src/commands/exe.md +19 -28
  461. package/stack.release.json +11 -11
  462. package/dist/chunk-3B6FLNLV.js +0 -30
  463. package/dist/chunk-FXU7JOXK.js +0 -6
  464. package/dist/chunk-QFAFKEB5.js +0 -820
  465. package/dist/conversations-retention-C4BLKFEL.js +0 -54
  466. package/dist/stack-release-5PGWV5D4.js +0 -1607
  467. package/dist/support-inbox-NY5TB3FN.js +0 -51
  468. /package/dist/{chunk-U2D6PJ5T.js → chunk-65G7ZTGE.js} +0 -0
  469. /package/dist/{chunk-DN3ZR3UE.js → chunk-CP7Z35MJ.js} +0 -0
  470. /package/dist/{chunk-XDVOGDBC.js → chunk-KBBWALJV.js} +0 -0
  471. /package/dist/{chunk-JRIH6I7D.js → chunk-LZXYPTY4.js} +0 -0
  472. /package/dist/{chunk-GIRD7O5X.js → chunk-MCWEW2SG.js} +0 -0
  473. /package/dist/{chunk-TKAU7IJ2.js → chunk-MQHUJLCA.js} +0 -0
  474. /package/dist/{chunk-M4XY4JRK.js → chunk-O53LTOXK.js} +0 -0
  475. /package/dist/{chunk-DZN7SDY2.js → chunk-OSVTH3SG.js} +0 -0
  476. /package/dist/{chunk-AJK2PVQW.js → chunk-QXXEXGX3.js} +0 -0
  477. /package/dist/{chunk-AWWDKF22.js → chunk-SFWNTO2F.js} +0 -0
  478. /package/dist/{chunk-2Q2CD6CD.js → chunk-WC5HF2FN.js} +0 -0
  479. /package/dist/{core-memory-O6D5UC3T.js → core-memory-MMMW3TF3.js} +0 -0
  480. /package/dist/{memory-isolation-EBFFC6HF.js → memory-isolation-67ACKQ2M.js} +0 -0
  481. /package/dist/{oauth-server-ZKBUHDFI.js → oauth-server-OILIKR7D.js} +0 -0
@@ -1,1607 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- writeSignedStackManifest
4
- } from "./chunk-MOANZQXF.js";
5
- import "./chunk-ZBPZDADW.js";
6
- import "./chunk-EC5H53YD.js";
7
- import {
8
- assertPreflightGate,
9
- runPreflightOnManifest
10
- } from "./chunk-PLII76NJ.js";
11
- import "./chunk-C5HUMAA7.js";
12
- import "./chunk-L25OAFOU.js";
13
- import "./chunk-LFUBEHFW.js";
14
- import "./chunk-TNS63ATU.js";
15
- import "./chunk-A7XLR3K7.js";
16
- import "./chunk-DZN7SDY2.js";
17
- import "./chunk-MMS2HBRT.js";
18
- import "./chunk-RH4AH3YB.js";
19
- import "./chunk-HSGBZSCA.js";
20
- import "./chunk-JKKZGDLP.js";
21
- import "./chunk-MLKGABMK.js";
22
-
23
- // src/bin/stack-release.ts
24
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
25
- import { execFile as execFileCb } from "child_process";
26
- import { promisify } from "util";
27
- import path2 from "path";
28
- import { createInterface } from "readline";
29
-
30
- // src/lib/release-lock.ts
31
- import {
32
- closeSync,
33
- existsSync,
34
- mkdirSync,
35
- openSync,
36
- readFileSync,
37
- unlinkSync,
38
- writeFileSync
39
- } from "fs";
40
- import { homedir, hostname as osHostname } from "os";
41
- import path from "path";
42
- import process2 from "process";
43
- var DEFAULT_RELEASE_LOCK_TTL_MS = 2 * 60 * 60 * 1e3;
44
- function defaultReleaseLockPath() {
45
- return path.join(homedir(), ".exe-os", "release.lock");
46
- }
47
- function defaultIsPidAlive(pid) {
48
- if (!Number.isInteger(pid) || pid <= 0) return false;
49
- try {
50
- process2.kill(pid, 0);
51
- return true;
52
- } catch (err) {
53
- const code = err && typeof err === "object" && "code" in err ? String(err.code) : "";
54
- return code === "EPERM";
55
- }
56
- }
57
- function readLock(lockPath) {
58
- const raw = readFileSync(lockPath, "utf8");
59
- const parsed = JSON.parse(raw);
60
- if (typeof parsed.pid !== "number" || typeof parsed.startedAt !== "string" || typeof parsed.targetVersion !== "string" || typeof parsed.branch !== "string" || typeof parsed.hostname !== "string") {
61
- throw new Error("lock file is missing required fields");
62
- }
63
- return parsed;
64
- }
65
- function writeLockAtomic(lockPath, lock) {
66
- mkdirSync(path.dirname(lockPath), { recursive: true });
67
- const fd = openSync(lockPath, "wx", 384);
68
- try {
69
- writeFileSync(fd, JSON.stringify(lock, null, 2) + "\n", "utf8");
70
- } finally {
71
- closeSync(fd);
72
- }
73
- }
74
- function sameLock(a, b) {
75
- return a.pid === b.pid && a.startedAt === b.startedAt && a.targetVersion === b.targetVersion && a.branch === b.branch && a.hostname === b.hostname;
76
- }
77
- function acquireReleaseLock(opts) {
78
- const lockPath = opts.lockPath ?? defaultReleaseLockPath();
79
- const ttlMs = opts.ttlMs ?? DEFAULT_RELEASE_LOCK_TTL_MS;
80
- const now = opts.now ?? /* @__PURE__ */ new Date();
81
- const isPidAlive = opts.isPidAlive ?? defaultIsPidAlive;
82
- const logNote = opts.log ?? (() => {
83
- });
84
- if (existsSync(lockPath)) {
85
- let existing = null;
86
- try {
87
- existing = readLock(lockPath);
88
- } catch (err) {
89
- const detail = err instanceof Error ? err.message : String(err);
90
- if (!opts.force) {
91
- throw new Error(
92
- `release lock exists but cannot be read safely (${detail}); re-run with --force to override: ${lockPath}`
93
- );
94
- }
95
- unlinkSync(lockPath);
96
- logNote(`release lock force-cleared unreadable lock at ${lockPath}`);
97
- }
98
- if (existing) {
99
- const startedMs = Date.parse(existing.startedAt);
100
- if (!Number.isFinite(startedMs) && !opts.force) {
101
- throw new Error(
102
- `release lock exists with an invalid timestamp (${existing.startedAt}); re-run with --force to override: ${lockPath}`
103
- );
104
- }
105
- const ageMs = Number.isFinite(startedMs) ? now.getTime() - startedMs : Number.POSITIVE_INFINITY;
106
- const pidAlive = isPidAlive(existing.pid);
107
- const fresh = ageMs < ttlMs && pidAlive;
108
- if (fresh && !opts.force) {
109
- throw new Error(
110
- `release already in progress: pid ${existing.pid} started ${existing.startedAt}; re-run with --force to override`
111
- );
112
- }
113
- unlinkSync(lockPath);
114
- if (fresh) {
115
- logNote(`release lock force-cleared: pid ${existing.pid} started ${existing.startedAt}`);
116
- } else {
117
- const reason = !pidAlive ? "pid is not alive" : `older than ${Math.round(ttlMs / 6e4)}m TTL`;
118
- logNote(`stale release lock cleared: pid ${existing.pid} started ${existing.startedAt} (${reason})`);
119
- }
120
- }
121
- }
122
- const lock = {
123
- pid: opts.pid ?? process2.pid,
124
- startedAt: now.toISOString(),
125
- targetVersion: opts.targetVersion,
126
- branch: opts.branch,
127
- hostname: opts.hostname ?? osHostname()
128
- };
129
- try {
130
- writeLockAtomic(lockPath, lock);
131
- } catch (err) {
132
- const code = err && typeof err === "object" && "code" in err ? String(err.code) : "";
133
- if (code === "EEXIST") {
134
- throw new Error(`release lock was acquired by another process; re-run when it completes: ${lockPath}`);
135
- }
136
- throw err;
137
- }
138
- return {
139
- lockPath,
140
- lock,
141
- release: () => {
142
- if (!existsSync(lockPath)) return;
143
- try {
144
- const current = readLock(lockPath);
145
- if (sameLock(current, lock)) unlinkSync(lockPath);
146
- } catch {
147
- }
148
- }
149
- };
150
- }
151
-
152
- // scripts/lib/stack-release-repos.mjs
153
- var STACK_RELEASE_REPO_CONFIGS = Object.freeze([
154
- {
155
- serviceKey: "exe-os",
156
- ghRepo: "AskExe/exe-os",
157
- localDir: ".",
158
- imageName: "update.askexe.com/askexe/exe-os",
159
- releaseWorkflow: "release-stack-image.yml",
160
- simpleTag: true,
161
- defaultBranch: "main"
162
- },
163
- {
164
- serviceKey: "gateway",
165
- ghRepo: "AskExe/exe-gateway",
166
- localDir: "exe-gateway",
167
- imageName: "update.askexe.com/askexe/exe-gateway",
168
- releaseWorkflow: "release-image.yml",
169
- simpleTag: true,
170
- defaultBranch: "main"
171
- },
172
- {
173
- serviceKey: "crm",
174
- ghRepo: "AskExe/exe-crm",
175
- localDir: "exe-crm",
176
- imageName: "update.askexe.com/askexe/exe-crm",
177
- releaseWorkflow: "release-stack-image.yml",
178
- simpleTag: true,
179
- defaultBranch: "main"
180
- },
181
- {
182
- serviceKey: "erp",
183
- ghRepo: "AskExe/exe-erp",
184
- localDir: "exe-erp",
185
- imageName: "update.askexe.com/askexe/exe-erp",
186
- releaseWorkflow: "release-stack-image.yml",
187
- simpleTag: true,
188
- defaultBranch: "main"
189
- },
190
- {
191
- serviceKey: "wiki",
192
- ghRepo: "AskExe/exe-wiki",
193
- localDir: "exe-wiki",
194
- imageName: "update.askexe.com/askexe/exe-wiki",
195
- releaseWorkflow: "release-stack-image.yml",
196
- simpleTag: true,
197
- defaultBranch: "master"
198
- },
199
- {
200
- serviceKey: "monitor-hub",
201
- ghRepo: "AskExe/exe-monitor",
202
- localDir: "exe-monitor",
203
- imageName: "update.askexe.com/askexe/exe-monitor-hub",
204
- releaseWorkflow: "release-stack-image.yml",
205
- simpleTag: true,
206
- defaultBranch: "main"
207
- },
208
- {
209
- serviceKey: "monitor-agent",
210
- ghRepo: "AskExe/exe-monitor",
211
- localDir: "exe-monitor",
212
- imageName: "update.askexe.com/askexe/exe-monitor-agent",
213
- releaseWorkflow: "release-stack-image.yml",
214
- simpleTag: true,
215
- defaultBranch: "main"
216
- },
217
- {
218
- serviceKey: "exe-build",
219
- ghRepo: "AskExe/exe-build",
220
- localDir: "exe-build",
221
- imageName: "update.askexe.com/askexe/exe-build",
222
- releaseWorkflow: "release-stack-image.yml",
223
- simpleTag: true,
224
- defaultBranch: "main"
225
- }
226
- ]);
227
- var STACK_RECONCILE_APP_IMAGE_REPOS = Object.freeze([
228
- // exe-auth is shipped in stack manifests but is not released by
229
- // `stack-release`, so it must be covered by G6's exact-repo reconciliation map.
230
- {
231
- imageName: "update.askexe.com/askexe/exe-auth",
232
- ghRepo: "AskExe/exe-auth"
233
- },
234
- // exe-dashboard: same gap — it was absent from both maps in this file, so
235
- // G6's reconcile classified an unstamped dashboard image as "no expected
236
- // source repo mapped here" (third-party, informational), exactly the false
237
- // pass the "unstamped-third-party" distinction exists to avoid for images
238
- // this repo family DOES build.
239
- //
240
- // 🔴 THE REPO HERE IS THE REPO THE IMAGE IS BUILT IN, NOT THE ONE THAT WILL
241
- // EVENTUALLY OWN IT. G6 compares this value against the image's org.exe.repo
242
- // label, which the shared release lane stamps with the repository the build
243
- // actually ran in. exe-dashboard is still built from `dashboard-page/` INSIDE
244
- // AskExe/exe-os (.github/workflows/release-dashboard-image.yml passes
245
- // `context: dashboard-page`); AskExe/exe-dashboard exists but its history
246
- // extraction is unfinished and nothing is built there yet. The real published
247
- // ghcr.io/askexe/exe-dashboard:v0.9.382 carries org.exe.repo=AskExe/exe-os
248
- // (recorded verbatim in release-service.yml's G2 arming note), so naming the
249
- // destination repo here turns every correctly-stamped dashboard release into
250
- // a "wrong-repo image (bug 218b10e4 class)" failure — and simultaneously
251
- // makes that signal useless for the one service it would be reporting on.
252
- //
253
- // release-contract-services.mjs's `ghRepo: "AskExe/exe-dashboard"` is a
254
- // different field with a different meaning: it names the repo that OWNS the
255
- // service in the migration table. Copying it into this provenance map is how
256
- // the wrong value got here.
257
- //
258
- // When the extraction completes, the lane's `context` flips to `.` and moves
259
- // to AskExe/exe-dashboard; flip this with it. A test in
260
- // tests/scripts/image-provenance.test.ts reads that `context` and fails if
261
- // the two disagree, so this cannot drift silently a second time.
262
- {
263
- imageName: "update.askexe.com/askexe/exe-dashboard",
264
- ghRepo: "AskExe/exe-os"
265
- }
266
- ]);
267
-
268
- // src/bin/stack-release.ts
269
- var execFile = promisify(execFileCb);
270
- function findExeOsRoot() {
271
- const envRoot = process.env.EXE_OS_ROOT;
272
- if (envRoot && existsSync2(envRoot)) return path2.resolve(envRoot);
273
- let dir = path2.dirname(new URL(import.meta.url).pathname);
274
- for (let i = 0; i < 5; i++) {
275
- const pkgPath = path2.join(dir, "package.json");
276
- if (existsSync2(pkgPath)) {
277
- try {
278
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
279
- if (pkg.name === "@askexenow/exe-os") return dir;
280
- } catch {
281
- }
282
- }
283
- dir = path2.dirname(dir);
284
- }
285
- return path2.resolve(path2.dirname(new URL(import.meta.url).pathname), "..", "..");
286
- }
287
- var EXE_OS_ROOT = findExeOsRoot();
288
- var PARENT_DIR = path2.dirname(EXE_OS_ROOT);
289
- var MANIFEST_PATH = path2.join(EXE_OS_ROOT, "deploy", "stack-manifests", "v0.9.json");
290
- function applyRootOverride(rootPath) {
291
- const resolved = path2.resolve(rootPath);
292
- if (!existsSync2(resolved)) {
293
- throw new Error(`--root path does not exist: ${resolved}`);
294
- }
295
- EXE_OS_ROOT = resolved;
296
- PARENT_DIR = path2.dirname(EXE_OS_ROOT);
297
- MANIFEST_PATH = path2.join(EXE_OS_ROOT, "deploy", "stack-manifests", "v0.9.json");
298
- }
299
- function resolveRepoPath(dirName) {
300
- const direct = path2.join(PARENT_DIR, dirName);
301
- if (existsSync2(direct)) return direct;
302
- const aliases = {
303
- "exe-crm": ["openclaw", "exe-crm"],
304
- "exe-wiki": ["ink", "exe-wiki"]
305
- };
306
- const candidates = aliases[dirName];
307
- if (candidates) {
308
- for (const alias of candidates) {
309
- const p = path2.join(PARENT_DIR, alias);
310
- if (existsSync2(p)) return p;
311
- }
312
- }
313
- return direct;
314
- }
315
- function buildRepoConfigs() {
316
- return STACK_RELEASE_REPO_CONFIGS.map(({ localDir, ...repo }) => ({
317
- ...repo,
318
- localPath: localDir === "." ? EXE_OS_ROOT : resolveRepoPath(localDir)
319
- }));
320
- }
321
- function parseReleaseArgs(argv) {
322
- const flags = {
323
- stack: false,
324
- dryRun: false,
325
- yes: false,
326
- repos: null,
327
- major: false,
328
- minor: false,
329
- skipImageVerify: false,
330
- allowRed: false,
331
- root: null,
332
- forceBuildRejected: false,
333
- canary: false,
334
- allowDirty: false,
335
- force: false
336
- };
337
- for (let i = 0; i < argv.length; i++) {
338
- const arg = argv[i];
339
- if (arg === "--stack") flags.stack = true;
340
- else if (arg === "--dry-run") flags.dryRun = true;
341
- else if (arg === "--yes" || arg === "-y") flags.yes = true;
342
- else if (arg === "--major") flags.major = true;
343
- else if (arg === "--minor") flags.minor = true;
344
- else if (arg === "--skip-image-verify") flags.skipImageVerify = true;
345
- else if (arg === "--allow-red") flags.allowRed = true;
346
- else if (arg === "--allow-dirty") flags.allowDirty = true;
347
- else if (arg === "--canary") flags.canary = true;
348
- else if (arg === "--force") flags.force = true;
349
- else if (arg === "--repos" && argv[i + 1]) {
350
- flags.repos = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
351
- } else if (arg.startsWith("--repos=")) {
352
- flags.repos = arg.slice("--repos=".length).split(",").map((s) => s.trim()).filter(Boolean);
353
- } else if (arg === "--root" && argv[i + 1]) {
354
- flags.root = argv[++i];
355
- } else if (arg.startsWith("--root=")) {
356
- flags.root = arg.slice("--root=".length);
357
- } else if (arg === "--force-build") {
358
- flags.forceBuildRejected = true;
359
- }
360
- }
361
- return flags;
362
- }
363
- async function git(repoPath, ...args) {
364
- const { stdout } = await execFile("git", ["-C", repoPath, ...args], {
365
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
366
- });
367
- return stdout.trim();
368
- }
369
- async function assertCleanWorkingTree(opts) {
370
- const gitStatus = opts.gitStatus ?? (() => git(EXE_OS_ROOT, "status", "--porcelain"));
371
- const status = (await gitStatus()).trim();
372
- if (!status) return;
373
- const dirtyPaths = status.split("\n").map((l) => l.trim()).filter(Boolean);
374
- if (opts.allowDirty) {
375
- console.warn("\n \x1B[33;1m\u26A0 --allow-dirty: releasing with a DIRTY working tree\x1B[0m");
376
- console.warn(" \x1B[33m Uncommitted changes are present in exe-os and may leak into the release:\x1B[0m");
377
- for (const p of dirtyPaths) console.warn(` \x1B[33m ${p}\x1B[0m`);
378
- console.warn(" \x1B[33m Use only for break-glass emergencies.\x1B[0m\n");
379
- return;
380
- }
381
- throw new Error(
382
- `Refusing to release: the exe-os working tree is not clean (${dirtyPaths.length} dirty path(s)).
383
- ` + dirtyPaths.map((p) => ` ${p}`).join("\n") + `
384
- Commit, stash, or discard these changes first \u2014 or pass --allow-dirty to override (break-glass).`
385
- );
386
- }
387
- async function getLastTag(repoPath) {
388
- try {
389
- return await git(repoPath, "describe", "--tags", "--abbrev=0");
390
- } catch {
391
- return null;
392
- }
393
- }
394
- async function getCommitsSinceTag(repoPath, tag) {
395
- try {
396
- const output = await git(repoPath, "log", `${tag}..HEAD`, "--oneline");
397
- return output.split("\n").filter(Boolean);
398
- } catch {
399
- return [];
400
- }
401
- }
402
- async function ghRun(args) {
403
- const { stdout } = await execFile("gh", args);
404
- return stdout.trim();
405
- }
406
- function isStrictSemverTag(tag) {
407
- return /^v\d+\.\d+\.\d+$/.test(tag);
408
- }
409
- function compareSemver(a, b) {
410
- const pa = a.split(".").map(Number);
411
- const pb = b.split(".").map(Number);
412
- for (let i = 0; i < 3; i++) {
413
- const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
414
- if (diff !== 0) return diff;
415
- }
416
- return 0;
417
- }
418
- function higherSemver(a, b) {
419
- return compareSemver(a, b) >= 0 ? a : b;
420
- }
421
- function parseRemoteTagNames(lsRemoteOutput) {
422
- return lsRemoteOutput.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => line.split(" ")[1] ?? "").filter((ref) => ref.startsWith("refs/tags/")).map((ref) => ref.slice("refs/tags/".length).replace(/\^\{\}$/, ""));
423
- }
424
- function highestReleasedVersion(tagNames) {
425
- let best = null;
426
- for (const tag of tagNames) {
427
- if (!isStrictSemverTag(tag)) continue;
428
- const version = tag.slice(1);
429
- best = best === null ? version : higherSemver(best, version);
430
- }
431
- return best;
432
- }
433
- async function getRemoteReleaseTags(repoPath, gitRunner = git) {
434
- try {
435
- const out = await gitRunner(repoPath, "ls-remote", "--tags", "origin");
436
- return parseRemoteTagNames(out);
437
- } catch {
438
- return [];
439
- }
440
- }
441
- async function resolveVersionBasis(repoPath, packageVersion, gitRunner = git) {
442
- const highestTag = highestReleasedVersion(await getRemoteReleaseTags(repoPath, gitRunner));
443
- return highestTag ? higherSemver(packageVersion, highestTag) : packageVersion;
444
- }
445
- async function assertVersionNotAlreadyReleased(repoPath, tag, gitRunner = git) {
446
- const out = await gitRunner(repoPath, "ls-remote", "--tags", "origin", `refs/tags/${tag}`);
447
- if (out.trim().length > 0) {
448
- throw new Error(
449
- `refusing to cut ${tag}: this tag already exists on origin. package.json (and/or the computed bump basis) has drifted behind real release history \u2014 resync before releasing. (bug 343af002)`
450
- );
451
- }
452
- }
453
- async function assertReleaseFreshness(opts) {
454
- const runGit = opts.gitRunner ?? git;
455
- const logFn = opts.log ?? log;
456
- const originRef = `origin/${opts.defaultBranch}`;
457
- logFn("info", `Freshness gate: fetching origin ${opts.defaultBranch} before publish...`);
458
- await runGit(opts.repoPath, "fetch", "origin", opts.defaultBranch);
459
- const missing = (await runGit(opts.repoPath, "log", `${opts.releaseCommit}..${originRef}`, "--oneline")).trim();
460
- if (missing) {
461
- throw new Error(
462
- `Freshness gate failed: ${originRef} contains commits not included in release commit ${opts.releaseCommit}.
463
- Missing-PR list:
464
- ${missing}
465
- Abort: rebase/re-cut the release from current ${originRef} before publishing.`
466
- );
467
- }
468
- try {
469
- await runGit(opts.repoPath, "merge-base", "--is-ancestor", originRef, opts.releaseCommit);
470
- } catch (err) {
471
- const detail = err instanceof Error ? err.message : String(err);
472
- throw new Error(
473
- `Freshness gate failed: release commit ${opts.releaseCommit} is not a descendant of ${originRef}. Abort: rebase/re-cut the release from current ${originRef} before publishing. (${detail})`
474
- );
475
- }
476
- logFn("ok", `Freshness gate passed: release commit contains current ${originRef}`);
477
- }
478
- async function printOpenPrGate(opts) {
479
- const baseBranch = opts.baseBranch ?? "main";
480
- const runGh = opts.ghRunner ?? ghRun;
481
- const logFn = opts.log ?? log;
482
- try {
483
- const output = await runGh([
484
- "pr",
485
- "list",
486
- "--repo",
487
- opts.ghRepo,
488
- "--base",
489
- baseBranch,
490
- "--json",
491
- "number,title"
492
- ]);
493
- const prs = JSON.parse(output);
494
- if (prs.length === 0) {
495
- logFn("info", `Open PR gate: no open PRs targeting ${baseBranch} in ${opts.ghRepo}`);
496
- return;
497
- }
498
- logFn("warn", `Open PR gate: ${prs.length} open PR(s) targeting ${baseBranch} in ${opts.ghRepo}:`);
499
- for (const pr of prs) {
500
- const number = typeof pr.number === "number" ? `#${pr.number}` : "#?";
501
- logFn("warn", ` ${number} ${pr.title ?? "(untitled)"}`);
502
- }
503
- } catch (err) {
504
- const detail = err instanceof Error ? err.message : String(err);
505
- logFn("warn", `Open PR gate: unable to list open PRs for ${opts.ghRepo} targeting ${baseBranch}; continuing (${detail})`);
506
- }
507
- }
508
- function bumpVersion(version, mode) {
509
- const parts = version.split(".").map(Number);
510
- if (parts.length < 3) throw new Error(`Invalid version: ${version}`);
511
- const [major, minor, patch] = parts;
512
- switch (mode) {
513
- case "major":
514
- return `${major + 1}.0.0`;
515
- case "minor":
516
- return `${major}.${minor + 1}.0`;
517
- case "patch":
518
- default:
519
- return `${major}.${minor}.${patch + 1}`;
520
- }
521
- }
522
- function readPackageVersion(repoPath) {
523
- const pkgPath = path2.join(repoPath, "package.json");
524
- if (!existsSync2(pkgPath)) {
525
- throw new Error(`Missing package.json at ${pkgPath}`);
526
- }
527
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
528
- return pkg.version;
529
- }
530
- function readPackageVersionOrNull(repoPath) {
531
- const pkgPath = path2.join(repoPath, "package.json");
532
- if (!existsSync2(pkgPath)) return null;
533
- return readPackageVersion(repoPath);
534
- }
535
- function repoName(repo) {
536
- return repo.ghRepo.split("/").pop() ?? repo.serviceKey;
537
- }
538
- function warnMissingRepoPackage(repo) {
539
- log(
540
- "warn",
541
- `repo not cloned: clone ${repo.ghRepo} or pass --repos to skip ${repoName(repo)} (${repo.localPath}/package.json missing)`
542
- );
543
- }
544
- function hasPackageJson(repo) {
545
- return existsSync2(path2.join(repo.localPath, "package.json"));
546
- }
547
- function deriveCloneUrl(repo) {
548
- return `https://github.com/${repo.ghRepo}.git`;
549
- }
550
- function isCloneTargetSafe(targetPath, parentDir) {
551
- const resolvedParent = path2.resolve(parentDir);
552
- const resolvedTarget = path2.resolve(targetPath);
553
- return resolvedTarget === resolvedParent || resolvedTarget.startsWith(resolvedParent + path2.sep);
554
- }
555
- var defaultCloneRunner = async (url, targetPath) => {
556
- await execFile("git", ["clone", url, targetPath], {
557
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
558
- });
559
- };
560
- async function ensureRepoCloned(repo, parentDir = PARENT_DIR, runClone = defaultCloneRunner) {
561
- if (hasPackageJson(repo)) return false;
562
- const target = repo.localPath;
563
- if (!isCloneTargetSafe(target, parentDir)) {
564
- throw new Error(
565
- `refusing to auto-clone ${repo.ghRepo}: target ${target} is outside resolved root ${parentDir}`
566
- );
567
- }
568
- const url = deriveCloneUrl(repo);
569
- log("info", `repo not cloned: auto-cloning ${repo.ghRepo} \u2192 ${target}`);
570
- try {
571
- await runClone(url, target);
572
- } catch (err) {
573
- const detail = err instanceof Error ? err.message : String(err);
574
- throw new Error(`git clone failed for ${repo.ghRepo} (${url} \u2192 ${target}): ${detail}`);
575
- }
576
- log("ok", `cloned ${repo.ghRepo} \u2192 ${target}`);
577
- return true;
578
- }
579
- function readStackReleaseJson(repoPath) {
580
- const p = path2.join(repoPath, "stack.release.json");
581
- if (!existsSync2(p)) return null;
582
- return JSON.parse(readFileSync2(p, "utf8"));
583
- }
584
- async function detectChanges(repos) {
585
- const changes = [];
586
- for (const repo of repos) {
587
- let currentVersion = readPackageVersionOrNull(repo.localPath);
588
- if (!currentVersion) {
589
- await ensureRepoCloned(repo);
590
- currentVersion = readPackageVersionOrNull(repo.localPath);
591
- if (!currentVersion) {
592
- warnMissingRepoPackage(repo);
593
- continue;
594
- }
595
- }
596
- const basisVersion = await resolveVersionBasis(repo.localPath, currentVersion);
597
- if (basisVersion !== currentVersion) {
598
- log(
599
- "warn",
600
- `${repo.serviceKey}: package.json (${currentVersion}) is behind the highest release tag on origin \u2014 using ${basisVersion} as the bump basis`
601
- );
602
- currentVersion = basisVersion;
603
- }
604
- const lastTag = await getLastTag(repo.localPath);
605
- if (!lastTag) {
606
- log("warn", `No tags found in ${repo.serviceKey} \u2014 skipping`);
607
- continue;
608
- }
609
- const commits = await getCommitsSinceTag(repo.localPath, lastTag);
610
- if (commits.length === 0) {
611
- log("info", `${repo.serviceKey}: no changes since ${lastTag}`);
612
- continue;
613
- }
614
- changes.push({
615
- repo,
616
- currentVersion,
617
- newVersion: "",
618
- // filled in step 2
619
- commits,
620
- tag: ""
621
- // filled in step 2
622
- });
623
- }
624
- return changes;
625
- }
626
- function rebaseImageTag(image, newVersion) {
627
- if (/@sha256:[0-9a-f]{64}/i.test(image)) return image;
628
- if (/:v[^:/]+$/.test(image)) {
629
- return `${image.replace(/:v[^:/]+$/, "")}:v${newVersion}`;
630
- }
631
- return `${image}:v${newVersion}`;
632
- }
633
- function imageRepoPath(image) {
634
- const withoutDigest = image.split("@")[0] ?? image;
635
- const lastColon = withoutDigest.lastIndexOf(":");
636
- const lastSlash = withoutDigest.lastIndexOf("/");
637
- return lastColon > lastSlash ? withoutDigest.slice(0, lastColon) : withoutDigest;
638
- }
639
- function mayRebaseComponent(component, componentImage, ownImage) {
640
- if (component && typeof component === "object" && component.versionLockstep === true) {
641
- return true;
642
- }
643
- if (typeof ownImage !== "string" || ownImage.length === 0) return false;
644
- return imageRepoPath(componentImage) === imageRepoPath(ownImage);
645
- }
646
- async function bumpVersions(changes, mode, dryRun) {
647
- for (const change of changes) {
648
- const newVersion = bumpVersion(change.currentVersion, mode);
649
- change.newVersion = newVersion;
650
- change.tag = `v${newVersion}`;
651
- await assertVersionNotAlreadyReleased(change.repo.localPath, change.tag);
652
- if (dryRun) {
653
- log("dry", `${change.repo.serviceKey}: ${change.currentVersion} -> ${newVersion}`);
654
- continue;
655
- }
656
- const pkgPath = path2.join(change.repo.localPath, "package.json");
657
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
658
- pkg.version = newVersion;
659
- writeFileSync2(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
660
- const srf = readStackReleaseJson(change.repo.localPath);
661
- if (srf) {
662
- srf.version = newVersion;
663
- srf.sourceVersion = newVersion;
664
- if (typeof srf.image === "string") {
665
- srf.image = rebaseImageTag(srf.image, newVersion);
666
- }
667
- const ownImage = typeof srf.image === "string" ? srf.image : void 0;
668
- if (srf.components && typeof srf.components === "object") {
669
- const components = srf.components;
670
- for (const key of Object.keys(components)) {
671
- const val = components[key];
672
- if (typeof val === "string") {
673
- if (mayRebaseComponent(val, val, ownImage)) {
674
- components[key] = rebaseImageTag(val, newVersion);
675
- }
676
- } else if (val && typeof val === "object" && "image" in val) {
677
- const obj = val;
678
- if (typeof obj.image === "string" && mayRebaseComponent(obj, obj.image, ownImage)) {
679
- obj.image = rebaseImageTag(obj.image, newVersion);
680
- }
681
- }
682
- }
683
- }
684
- writeFileSync2(
685
- path2.join(change.repo.localPath, "stack.release.json"),
686
- JSON.stringify(srf, null, 2) + "\n"
687
- );
688
- }
689
- await git(change.repo.localPath, "add", "package.json", "stack.release.json");
690
- await git(change.repo.localPath, "commit", "-m", `chore: bump v${newVersion} for stack release`);
691
- log("ok", `${change.repo.serviceKey}: bumped ${change.currentVersion} -> ${newVersion} (local commit, push deferred)`);
692
- }
693
- }
694
- var RELEASE_PR_POLL_JQ = "[.state, .mergeable, .mergeStateStatus] | @tsv";
695
- async function landViaPR(repoPath, ghRepo, defaultBranch, branch, title) {
696
- const pushedSha = await git(repoPath, "rev-parse", "HEAD");
697
- await git(repoPath, "push", "--force-with-lease", "origin", `HEAD:refs/heads/${branch}`);
698
- try {
699
- await ghRun([
700
- "pr",
701
- "create",
702
- "--repo",
703
- ghRepo,
704
- "--base",
705
- defaultBranch,
706
- "--head",
707
- branch,
708
- "--title",
709
- title,
710
- "--body",
711
- "Automated release version bump \u2014 auto-merges when required CI is green."
712
- ]);
713
- } catch {
714
- }
715
- const deadline = Date.now() + 30 * 60 * 1e3;
716
- let releasePr;
717
- for (; ; ) {
718
- try {
719
- const output = await ghRun([
720
- "pr",
721
- "list",
722
- "--repo",
723
- ghRepo,
724
- "--head",
725
- branch,
726
- "--state",
727
- "all",
728
- "--json",
729
- "number,state,headRefOid"
730
- ]);
731
- releasePr = findPullRequestForHeadSha(JSON.parse(output), pushedSha);
732
- } catch {
733
- }
734
- if (releasePr) break;
735
- if (Date.now() > deadline) {
736
- throw new Error(
737
- `No PR on ${ghRepo} (${branch}) matches released commit ${pushedSha.slice(0, 12)} within 30m \u2014 failing closed rather than attributing another PR's verdict. Aborting release; ${defaultBranch} left untouched.`
738
- );
739
- }
740
- await sleep(15e3);
741
- }
742
- if (releasePr.state !== "MERGED") {
743
- await ghRun(["pr", "merge", String(releasePr.number), "--repo", ghRepo, "--auto", "--squash"]);
744
- }
745
- let lastMergeable = "";
746
- for (; ; ) {
747
- const observed = (await ghRun([
748
- "pr",
749
- "view",
750
- String(releasePr.number),
751
- "--repo",
752
- ghRepo,
753
- "--json",
754
- "state,mergeable,mergeStateStatus",
755
- "-q",
756
- RELEASE_PR_POLL_JQ
757
- ])).trim();
758
- const [state, mergeable = "", mergeStateStatus = ""] = observed.split(" ");
759
- if (mergeable) lastMergeable = `mergeable=${mergeable} mergeStateStatus=${mergeStateStatus}`;
760
- if (state === "MERGED") break;
761
- if (mergeable === "CONFLICTING") {
762
- throw new Error(
763
- `Release PR ${ghRepo}#${releasePr.number} (${branch} @ ${pushedSha.slice(0, 12)}) CONFLICTS with ${defaultBranch}. GitHub cannot compute a merge commit for it, so NO CI will run and auto-merge can never fire \u2014 waiting would not help. Rebase the release branch and re-run. Aborting release; ${defaultBranch} left untouched.`
764
- );
765
- }
766
- if (Date.now() > deadline) {
767
- throw new Error(
768
- `Release PR ${ghRepo}#${releasePr.number} (${branch} @ ${pushedSha.slice(0, 12)}) did not merge within 30m (${lastMergeable || "mergeability never readable"}). Aborting release; ${defaultBranch} left untouched.`
769
- );
770
- }
771
- await sleep(3e4);
772
- }
773
- await git(repoPath, "fetch", "origin", defaultBranch);
774
- await git(repoPath, "checkout", defaultBranch);
775
- await git(repoPath, "reset", "--hard", `origin/${defaultBranch}`);
776
- }
777
- async function tagAndTriggerCI(changes, dryRun) {
778
- for (const change of changes) {
779
- if (dryRun) {
780
- log("dry", `Would land ${change.repo.serviceKey} bump via PR + tag ${change.tag}`);
781
- continue;
782
- }
783
- const branch = `release/bump-${change.tag}`;
784
- await landViaPR(
785
- change.repo.localPath,
786
- change.repo.ghRepo,
787
- change.repo.defaultBranch,
788
- branch,
789
- `chore: bump ${change.tag} for stack release`
790
- );
791
- change.releaseSha = await git(change.repo.localPath, "rev-parse", "HEAD");
792
- await assertReleaseFreshness({
793
- repoPath: change.repo.localPath,
794
- defaultBranch: change.repo.defaultBranch,
795
- releaseCommit: change.releaseSha
796
- });
797
- await printOpenPrGate({ ghRepo: change.repo.ghRepo, baseBranch: "main" });
798
- await git(change.repo.localPath, "tag", change.tag);
799
- await git(change.repo.localPath, "push", "origin", change.tag);
800
- log("ok", `Landed ${change.repo.serviceKey} ${change.tag} via PR + tagged ${change.releaseSha.slice(0, 12)} (green-CI gated)`);
801
- }
802
- }
803
- async function rollbackFailedReleases(changes, ciStatuses, dryRun) {
804
- if (dryRun) return;
805
- const failed = changes.filter((c) => {
806
- const ci = ciStatuses.find((s) => s.repo.serviceKey === c.repo.serviceKey);
807
- return ci && (ci.status !== "completed" || ci.conclusion !== "success");
808
- });
809
- if (failed.length === 0) return;
810
- log("warn", `Rolling back ${failed.length} failed release(s)...`);
811
- for (const change of failed) {
812
- const name = repoName(change.repo);
813
- try {
814
- await git(change.repo.localPath, "push", "origin", `:refs/tags/${change.tag}`);
815
- log("ok", `Deleted remote tag ${change.tag} from ${name}`);
816
- } catch (err) {
817
- log("warn", `Could not delete remote tag ${change.tag} from ${name}: ${err instanceof Error ? err.message : String(err)}`);
818
- }
819
- try {
820
- await git(change.repo.localPath, "tag", "-d", change.tag);
821
- } catch {
822
- }
823
- try {
824
- await git(change.repo.localPath, "revert", "--no-edit", "HEAD");
825
- await landViaPR(
826
- change.repo.localPath,
827
- change.repo.ghRepo,
828
- change.repo.defaultBranch,
829
- `release/revert-${change.tag}`,
830
- `Revert bump ${change.tag} (release CI failed)`
831
- );
832
- log("ok", `Reverted bump commit for ${name} on ${change.repo.defaultBranch} (via PR)`);
833
- } catch (err) {
834
- log("fail", `Could not revert bump commit for ${name}: ${err instanceof Error ? err.message : String(err)}. Manual cleanup required.`);
835
- }
836
- }
837
- }
838
- function workflowRunHeadSha(run) {
839
- return run.headSha ?? run.head_sha;
840
- }
841
- function findPullRequestForHeadSha(prs, pushedSha) {
842
- const normalizedSha = pushedSha.toLowerCase();
843
- return prs.find((pr) => pr.headRefOid?.toLowerCase() === normalizedSha);
844
- }
845
- function findWorkflowRunForHeadSha(runs, releaseSha) {
846
- const normalizedReleaseSha = releaseSha.toLowerCase();
847
- return runs.find((run) => workflowRunHeadSha(run)?.toLowerCase() === normalizedReleaseSha);
848
- }
849
- async function resolveReleaseSha(change) {
850
- if (change.releaseSha) return change.releaseSha;
851
- try {
852
- change.releaseSha = await git(change.repo.localPath, "rev-parse", `${change.tag}^{}`);
853
- return change.releaseSha;
854
- } catch {
855
- return void 0;
856
- }
857
- }
858
- async function waitForCI(changes, dryRun) {
859
- if (dryRun) {
860
- return changes.map((c) => ({
861
- repo: c.repo,
862
- tag: c.tag,
863
- status: "completed",
864
- conclusion: "success"
865
- }));
866
- }
867
- const TIMEOUT_MS = 15 * 60 * 1e3;
868
- const POLL_INTERVAL_MS = 30 * 1e3;
869
- const startTime = Date.now();
870
- const statuses = changes.map((c) => ({
871
- repo: c.repo,
872
- tag: c.tag,
873
- status: "in_progress"
874
- }));
875
- log("info", `Waiting for ${changes.length} CI build(s)... (timeout: 15m)`);
876
- while (Date.now() - startTime < TIMEOUT_MS) {
877
- const pending = statuses.filter((s) => s.status === "in_progress");
878
- if (pending.length === 0) break;
879
- for (const st of pending) {
880
- try {
881
- const change = changes.find((c) => c.repo.serviceKey === st.repo.serviceKey && c.tag === st.tag);
882
- if (!change) continue;
883
- const releaseSha = await resolveReleaseSha(change);
884
- if (!releaseSha) continue;
885
- const output = await ghRun([
886
- "run",
887
- "list",
888
- "--repo",
889
- st.repo.ghRepo,
890
- "--workflow",
891
- st.repo.releaseWorkflow,
892
- "--limit",
893
- "50",
894
- "--json",
895
- "status,conclusion,url,headSha"
896
- ]);
897
- const runs = JSON.parse(output);
898
- const releaseRun = findWorkflowRunForHeadSha(runs, releaseSha);
899
- if (!releaseRun) continue;
900
- if (releaseRun.status === "completed") {
901
- st.status = "completed";
902
- st.conclusion = releaseRun.conclusion ?? "unknown";
903
- st.url = releaseRun.url;
904
- if (st.conclusion === "success") {
905
- log("ok", `CI passed: ${st.repo.serviceKey} ${st.tag}`);
906
- } else {
907
- log("fail", `CI failed: ${st.repo.serviceKey} ${st.tag} (${st.conclusion}) \u2014 ${st.url}`);
908
- }
909
- }
910
- } catch (err) {
911
- log("warn", `Failed to check CI for ${st.repo.serviceKey}: ${err instanceof Error ? err.message : String(err)}`);
912
- }
913
- }
914
- const stillPending = statuses.filter((s) => s.status === "in_progress");
915
- if (stillPending.length === 0) break;
916
- log("info", `${stillPending.length} CI build(s) still running... (${Math.round((Date.now() - startTime) / 1e3)}s elapsed)`);
917
- await sleep(POLL_INTERVAL_MS);
918
- }
919
- for (const st of statuses) {
920
- if (st.status === "in_progress") {
921
- st.status = "timed_out";
922
- log("fail", `CI timed out: ${st.repo.serviceKey} ${st.tag}`);
923
- }
924
- }
925
- return statuses;
926
- }
927
- var PROXY_REGISTRY = "update.askexe.com";
928
- var GHCR_REGISTRY = "ghcr.io";
929
- function shortImageName(imageName) {
930
- const parts = imageName.split("/");
931
- return parts[parts.length - 1] ?? imageName;
932
- }
933
- async function checkRegistryManifest(registry, repo, tag, token) {
934
- const url = `https://${registry}/v2/${repo}/manifests/${tag}`;
935
- const headers = [
936
- "-H",
937
- "Accept: application/vnd.oci.image.index.v1+json",
938
- // Also accept legacy Docker v2 manifest list / single manifest so the
939
- // registry returns a Docker-Content-Digest for non-OCI images too.
940
- "-H",
941
- "Accept: application/vnd.docker.distribution.manifest.list.v2+json",
942
- "-H",
943
- "Accept: application/vnd.docker.distribution.manifest.v2+json"
944
- ];
945
- if (token) {
946
- headers.push("-H", `Authorization: Bearer ${token}`);
947
- }
948
- try {
949
- const { stdout } = await execFile("curl", [
950
- "-sS",
951
- "-m",
952
- "30",
953
- "-I",
954
- ...headers,
955
- url
956
- ]);
957
- const statusMatch = stdout.match(/^HTTP\/[\d.]+\s+(\d{3})/m);
958
- const code = statusMatch ? parseInt(statusMatch[1], 10) : 0;
959
- const digestMatch = stdout.match(/^docker-content-digest:\s*(sha256:[0-9a-f]{64})/im);
960
- const digest = digestMatch ? digestMatch[1] : void 0;
961
- return { status: code, ok: code >= 200 && code < 300, digest };
962
- } catch {
963
- return { status: 0, ok: false };
964
- }
965
- }
966
- async function warmProxyCache(imageRef) {
967
- try {
968
- await execFile("docker", ["pull", imageRef], { timeout: 12e4 });
969
- return true;
970
- } catch {
971
- return false;
972
- }
973
- }
974
- async function resolveDigestViaInspect(imageName, tag) {
975
- const shortName = shortImageName(imageName);
976
- const refs = [
977
- `${GHCR_REGISTRY}/askexe/${shortName}:${tag}`,
978
- `${PROXY_REGISTRY}/askexe/${shortName}:${tag}`
979
- ];
980
- for (const ref of refs) {
981
- try {
982
- const { stdout } = await execFile("docker", ["manifest", "inspect", ref, "--verbose"], {
983
- timeout: 6e4
984
- });
985
- const parsed = JSON.parse(stdout);
986
- let digest;
987
- if (Array.isArray(parsed)) {
988
- digest = parsed[0]?.Descriptor?.digest;
989
- } else {
990
- digest = parsed.Descriptor?.digest;
991
- }
992
- if (digest && /^sha256:[0-9a-f]{64}$/i.test(digest)) {
993
- log("ok", `Resolved digest via docker manifest inspect: ${shortName}:${tag} -> ${digest.slice(0, 19)}...`);
994
- return digest;
995
- }
996
- } catch {
997
- }
998
- }
999
- try {
1000
- const ghcrRef = `${GHCR_REGISTRY}/askexe/${shortName}:${tag}`;
1001
- const { stdout } = await execFile("docker", ["manifest", "inspect", ghcrRef], {
1002
- timeout: 6e4
1003
- });
1004
- const parsed = JSON.parse(stdout);
1005
- if (parsed.digest && /^sha256:[0-9a-f]{64}$/i.test(parsed.digest)) {
1006
- log("ok", `Resolved digest via docker manifest inspect (non-verbose): ${shortName}:${tag} -> ${parsed.digest.slice(0, 19)}...`);
1007
- return parsed.digest;
1008
- }
1009
- } catch {
1010
- }
1011
- return void 0;
1012
- }
1013
- async function verifyImages(changes, ciStatuses, dryRun) {
1014
- if (dryRun) {
1015
- return changes.map((c) => ({
1016
- repo: c.repo,
1017
- tag: `v${c.newVersion}`,
1018
- ghcr: "ok",
1019
- proxy: "ok"
1020
- }));
1021
- }
1022
- const proxyToken = process.env.EXE_REGISTRY_PROXY_PULL_TOKEN ?? void 0;
1023
- const results = [];
1024
- let ghcrToken = process.env.GHCR_TOKEN?.trim() || void 0;
1025
- if (!ghcrToken) {
1026
- try {
1027
- const { stdout } = await execFile("gh", ["auth", "token"]);
1028
- ghcrToken = stdout.trim();
1029
- } catch {
1030
- }
1031
- }
1032
- const ghcrBearer = ghcrToken ? Buffer.from(ghcrToken).toString("base64") : void 0;
1033
- log("info", "Verifying Docker images are pullable (with retry for digest resolution)...");
1034
- for (const change of changes) {
1035
- const ci = ciStatuses.find((s) => s.repo.serviceKey === change.repo.serviceKey);
1036
- if (!ci || ci.status !== "completed" || ci.conclusion !== "success") continue;
1037
- const tag = `v${change.newVersion}`;
1038
- const shortName = shortImageName(change.repo.imageName);
1039
- const proxyRepo = `askexe/${shortName}`;
1040
- const ghcrRepo = `askexe/${shortName}`;
1041
- const result = {
1042
- repo: change.repo,
1043
- tag,
1044
- ghcr: "error",
1045
- proxy: "error"
1046
- };
1047
- const GHCR_RETRY_DELAYS = [5e3, 1e4, 2e4, 4e4];
1048
- let ghcrCheck = { status: 0, ok: false };
1049
- for (let attempt = 0; attempt <= GHCR_RETRY_DELAYS.length; attempt++) {
1050
- ghcrCheck = await checkRegistryManifest(GHCR_REGISTRY, ghcrRepo, tag, ghcrBearer);
1051
- if (ghcrCheck.ok && ghcrCheck.digest) {
1052
- break;
1053
- }
1054
- if (ghcrCheck.status === 401 || ghcrCheck.status === 403) {
1055
- break;
1056
- }
1057
- if (attempt < GHCR_RETRY_DELAYS.length) {
1058
- const delayMs = GHCR_RETRY_DELAYS[attempt];
1059
- log("info", `${shortName}:${tag} not yet available on GHCR (HTTP ${ghcrCheck.status}) \u2014 retry in ${delayMs / 1e3}s (${attempt + 1}/${GHCR_RETRY_DELAYS.length})`);
1060
- await sleep(delayMs);
1061
- }
1062
- }
1063
- if (ghcrCheck.ok) {
1064
- result.ghcr = "ok";
1065
- result.digest = ghcrCheck.digest;
1066
- if (!ghcrCheck.digest) {
1067
- log("warn", `${shortName}:${tag} is on GHCR but Docker-Content-Digest header missing \u2014 trying docker manifest inspect fallback`);
1068
- const inspectDigest = await resolveDigestViaInspect(change.repo.imageName, tag);
1069
- if (inspectDigest) {
1070
- result.digest = inspectDigest;
1071
- } else {
1072
- log("warn", `${shortName}:${tag} \u2014 docker manifest inspect also failed to resolve digest. Manifest entry will be tag-only.`);
1073
- }
1074
- }
1075
- } else if (ghcrCheck.status === 401 || ghcrCheck.status === 403) {
1076
- log("warn", `GHCR auth returned ${ghcrCheck.status} for ${shortName}:${tag} \u2014 trying docker manifest inspect fallback`);
1077
- const inspectDigest = await resolveDigestViaInspect(change.repo.imageName, tag);
1078
- if (inspectDigest) {
1079
- result.ghcr = "ok";
1080
- result.digest = inspectDigest;
1081
- log("ok", `${shortName}:${tag} \u2014 digest resolved via docker manifest inspect despite GHCR HEAD ${ghcrCheck.status}`);
1082
- } else {
1083
- result.ghcr = "error";
1084
- result.detail = `GHCR auth failed (${ghcrCheck.status}) and docker manifest inspect failed \u2014 cannot verify image. Fix gh auth or GHCR_TOKEN.`;
1085
- log("fail", `GHCR auth returned ${ghcrCheck.status} for ${shortName}:${tag} and inspect fallback failed \u2014 cannot verify`);
1086
- }
1087
- } else {
1088
- result.ghcr = "missing";
1089
- result.detail = `GHCR returned ${ghcrCheck.status} for ${ghcrRepo}:${tag} after ${GHCR_RETRY_DELAYS.length} retries`;
1090
- log("fail", `Image NOT FOUND on GHCR after retries: ${shortName}:${tag} \u2014 release may be broken!`);
1091
- }
1092
- const proxyCheck = await checkRegistryManifest(PROXY_REGISTRY, proxyRepo, tag, proxyToken);
1093
- if (proxyCheck.ok) {
1094
- result.proxy = "ok";
1095
- if (!result.digest && proxyCheck.digest) {
1096
- result.digest = proxyCheck.digest;
1097
- }
1098
- } else {
1099
- result.proxy = "pending";
1100
- }
1101
- if (result.ghcr === "ok" && result.proxy !== "ok") {
1102
- log("info", `Warming proxy cache for ${shortName}:${tag}...`);
1103
- const proxyRef = `${PROXY_REGISTRY}/${proxyRepo}:${tag}`;
1104
- const warmed = await warmProxyCache(proxyRef);
1105
- if (warmed) {
1106
- result.proxy = "ok";
1107
- log("ok", `Proxy cache warmed: ${shortName}:${tag}`);
1108
- } else {
1109
- result.proxy = "pending";
1110
- log("warn", `Could not warm proxy for ${shortName}:${tag} \u2014 GHCR has it, proxy will cache on first customer pull`);
1111
- }
1112
- }
1113
- if (result.ghcr === "ok" && result.proxy === "ok") {
1114
- const digestSuffix = result.digest ? ` [${result.digest.slice(0, 19)}...]` : "";
1115
- log("ok", `Image verified: ${shortName}:${tag} (GHCR + proxy)${digestSuffix}`);
1116
- } else if (result.ghcr === "ok" && result.proxy === "pending") {
1117
- log("warn", `Image on GHCR only: ${shortName}:${tag} (proxy will cache on first pull)`);
1118
- }
1119
- results.push(result);
1120
- }
1121
- return results;
1122
- }
1123
- function printImageVerification(results) {
1124
- if (results.length === 0) return;
1125
- console.log("\n Image verification:");
1126
- for (const r of results) {
1127
- const shortName = shortImageName(r.repo.imageName);
1128
- const ghcrIcon = r.ghcr === "ok" ? "\x1B[32m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m";
1129
- const proxyIcon = r.proxy === "ok" ? "\x1B[32m\u2713\x1B[0m" : r.proxy === "pending" ? "\x1B[33m~\x1B[0m" : "\x1B[31m\u2717\x1B[0m";
1130
- const proxyLabel = r.proxy === "pending" ? "pending (first pull will cache)" : r.proxy;
1131
- console.log(
1132
- ` ${shortName}:${r.tag}`.padEnd(40) + `${ghcrIcon} GHCR ${proxyIcon} proxy${r.proxy === "pending" ? ` (${proxyLabel})` : ""}`
1133
- );
1134
- }
1135
- console.log("");
1136
- }
1137
- async function updateStackManifest(changes, ciStatuses, dryRun, imageResults, canary = false) {
1138
- const manifest = JSON.parse(readFileSync2(MANIFEST_PATH, "utf8"));
1139
- const currentLatest = manifest.latest;
1140
- const currentParts = currentLatest.split(".").map(Number);
1141
- const newStackVersion = `${currentParts[0]}.${currentParts[1]}.${(currentParts[2] ?? 0) + 1}`;
1142
- const baseEntry = manifest.stacks[currentLatest];
1143
- const baseServices = baseEntry?.services ?? {};
1144
- const services = {};
1145
- for (const [key, svc] of Object.entries(baseServices)) {
1146
- services[key] = { ...svc };
1147
- }
1148
- const successfulChanges = [];
1149
- for (const change of changes) {
1150
- const ciStatus = ciStatuses.find((s) => s.repo.serviceKey === change.repo.serviceKey);
1151
- if (ciStatus && ciStatus.status === "completed" && ciStatus.conclusion === "success") {
1152
- successfulChanges.push(change);
1153
- const svc = services[change.repo.serviceKey];
1154
- if (svc) {
1155
- const tag = `v${change.newVersion}`;
1156
- const imageTag = `${change.repo.imageName}:${tag}`;
1157
- let digest = imageResults?.find((r) => r.repo.serviceKey === change.repo.serviceKey)?.digest;
1158
- if (!digest && !dryRun) {
1159
- log("info", `No digest from image verification for ${imageTag} \u2014 trying docker manifest inspect fallback (bug 85034df0)`);
1160
- digest = await resolveDigestViaInspect(change.repo.imageName, tag);
1161
- }
1162
- if (!digest && !dryRun) {
1163
- log("warn", `No content digest resolved for ${imageTag} \u2014 manifest entry will be tag-only and the digest-pinning gate will reject it.`);
1164
- }
1165
- svc.image = digest ? `${imageTag}@${digest}` : imageTag;
1166
- const gitSha = await resolveReleaseSha(change);
1167
- if (gitSha) {
1168
- svc.gitSha = gitSha;
1169
- } else if (!dryRun) {
1170
- log("warn", `No git SHA resolved for ${imageTag} \u2014 manifest entry will omit gitSha (G4 deploy-time provenance check will skip this service).`);
1171
- }
1172
- }
1173
- }
1174
- }
1175
- if (successfulChanges.length === 0 && !dryRun) {
1176
- log("warn", "No successful CI builds \u2014 manifest not updated");
1177
- return currentLatest;
1178
- }
1179
- const notesParts = [];
1180
- for (const change of successfulChanges) {
1181
- const summaries = change.commits.slice(0, 5).map((c) => {
1182
- const msg = c.replace(/^[a-f0-9]+ /, "");
1183
- return msg;
1184
- });
1185
- notesParts.push(`${change.repo.serviceKey} v${change.newVersion}: ${summaries.join("; ")}`);
1186
- }
1187
- const notes = notesParts.join(". ");
1188
- const exeOsVersion = readPackageVersion(EXE_OS_ROOT);
1189
- const newEntry = {
1190
- version: newStackVersion,
1191
- releasedAt: (/* @__PURE__ */ new Date()).toISOString().replace(/T.*/, "T00:00:00Z"),
1192
- notes,
1193
- npmVersion: exeOsVersion,
1194
- breakingChanges: [],
1195
- services,
1196
- // Staged rollout (release-safety canary hardening): "canary" entries are
1197
- // published (images pinned + pullable) but NOT promoted for implicit
1198
- // adoption — see assertRolloutStageAllowed in src/lib/stack-update.ts.
1199
- rolloutStage: canary ? "canary" : "ga"
1200
- };
1201
- if (dryRun) {
1202
- log("dry", `Would create stack ${newStackVersion} in manifest${canary ? " (CANARY \u2014 latest would NOT be bumped)" : ""}`);
1203
- log("dry", `Notes: ${notes}`);
1204
- return newStackVersion;
1205
- }
1206
- manifest.stacks[newStackVersion] = newEntry;
1207
- if (canary) {
1208
- writeSignedStackManifest(MANIFEST_PATH, manifest, { warn: (msg) => log("warn", msg) });
1209
- log("ok", `Stack manifest updated: added CANARY ${newStackVersion} (latest unchanged: ${currentLatest})`);
1210
- log("warn", `${newStackVersion} is a CANARY release. Deploy to a designated canary host with:`);
1211
- log("warn", ` exe-os stack-update --target ${newStackVersion} --yes`);
1212
- log("warn", `Then verify health (vps-health-gate / verify-stack) and promote with:`);
1213
- log("warn", ` exe-os stack-promote --version ${newStackVersion} --yes`);
1214
- } else {
1215
- manifest.latest = newStackVersion;
1216
- writeSignedStackManifest(MANIFEST_PATH, manifest, { warn: (msg) => log("warn", msg) });
1217
- log("ok", `Stack manifest updated: ${currentLatest} -> ${newStackVersion}`);
1218
- }
1219
- return newStackVersion;
1220
- }
1221
- function gatePublishWithPreflight(newStackVersion, dryRun, allowRed) {
1222
- let manifest;
1223
- try {
1224
- manifest = JSON.parse(readFileSync2(MANIFEST_PATH, "utf8"));
1225
- } catch (err) {
1226
- throw new Error(`Preflight gate: cannot read manifest ${MANIFEST_PATH}: ${err instanceof Error ? err.message : String(err)}`);
1227
- }
1228
- const composeDir = path2.join(EXE_OS_ROOT, "deploy", "compose");
1229
- const report = runPreflightOnManifest({
1230
- manifest,
1231
- stackVersion: newStackVersion,
1232
- label: `${MANIFEST_PATH} (stack ${newStackVersion})`,
1233
- envExamplePath: path2.join(composeDir, ".env.example"),
1234
- composePath: path2.join(composeDir, "docker-compose.yml"),
1235
- monitorComposePath: path2.join(EXE_OS_ROOT, "deploy", "monitor", "docker-compose.hub.yml")
1236
- });
1237
- log("info", "Running static preflight gate against the freshly-written manifest...");
1238
- if (dryRun) {
1239
- console.log(` [dry-run] preflight verdict: ${report.verdict} (pass ${report.passed} / warn ${report.warned} / fail ${report.failed})`);
1240
- if (report.verdict !== "GREEN") {
1241
- for (const f of report.findings) {
1242
- if (f.status !== "pass") log(report.verdict === "RED" && f.status === "fail" ? "fail" : "warn", `${f.check}: ${f.message}`);
1243
- }
1244
- }
1245
- return;
1246
- }
1247
- assertPreflightGate(report, {
1248
- context: "stack-release",
1249
- allowRed,
1250
- log: (m) => log("ok", m.replace(/^\[stack-release\]\s*/, "")),
1251
- warn: (m) => log("warn", m.replace(/^\[stack-release\]\s*/, ""))
1252
- });
1253
- }
1254
- function gatePreMutationStructure(changes, dryRun, allowRed) {
1255
- let baseManifest;
1256
- try {
1257
- baseManifest = JSON.parse(readFileSync2(MANIFEST_PATH, "utf8"));
1258
- } catch (err) {
1259
- throw new Error(`Pre-mutation gate: cannot read manifest ${MANIFEST_PATH}: ${err instanceof Error ? err.message : String(err)}`);
1260
- }
1261
- const currentLatest = baseManifest.latest ?? "";
1262
- const baseServices = baseManifest.stacks?.[currentLatest]?.services ?? {};
1263
- const services = {};
1264
- for (const [key, svc] of Object.entries(baseServices)) services[key] = { ...svc };
1265
- for (const change of changes) {
1266
- const svc = services[change.repo.serviceKey];
1267
- if (svc) svc.image = `${change.repo.imageName}:v${change.newVersion}`;
1268
- }
1269
- const currentParts = currentLatest.split(".").map(Number);
1270
- const simVersion = `${currentParts[0] ?? 0}.${currentParts[1] ?? 9}.${(currentParts[2] ?? 0) + 1}`;
1271
- const simManifest = {
1272
- schemaVersion: 1,
1273
- latest: simVersion,
1274
- stacks: { [simVersion]: { version: simVersion, services } }
1275
- };
1276
- const composeDir = path2.join(EXE_OS_ROOT, "deploy", "compose");
1277
- const report = runPreflightOnManifest({
1278
- manifest: simManifest,
1279
- stackVersion: simVersion,
1280
- label: `pre-mutation simulated manifest (stack ${simVersion})`,
1281
- envExamplePath: path2.join(composeDir, ".env.example"),
1282
- composePath: path2.join(composeDir, "docker-compose.yml"),
1283
- monitorComposePath: path2.join(EXE_OS_ROOT, "deploy", "monitor", "docker-compose.hub.yml")
1284
- });
1285
- const adjusted = {
1286
- ...report,
1287
- findings: report.findings.map(
1288
- (f) => f.check === "digest-pinning" && f.status === "fail" ? { ...f, status: "warn", message: `${f.message} (pending image build \u2014 enforced after build)` } : f
1289
- )
1290
- };
1291
- const failed = adjusted.findings.filter((f) => f.status === "fail").length;
1292
- const warned = adjusted.findings.filter((f) => f.status === "warn").length;
1293
- const passed = adjusted.findings.filter((f) => f.status === "pass").length;
1294
- const verdict = failed > 0 ? "RED" : warned > 0 ? "YELLOW" : "GREEN";
1295
- const adjustedReport = { ...adjusted, verdict, failed, warned, passed };
1296
- log("info", "Running pre-mutation structural gate (before any remote push)...");
1297
- if (dryRun) {
1298
- console.log(` [dry-run] pre-mutation verdict: ${adjustedReport.verdict} (pass ${passed} / warn ${warned} / fail ${failed})`);
1299
- for (const f of adjustedReport.findings) {
1300
- if (f.status !== "pass") log(f.status === "fail" ? "fail" : "warn", `${f.check}: ${f.message}`);
1301
- }
1302
- return;
1303
- }
1304
- assertPreflightGate(adjustedReport, {
1305
- context: "stack-release:pre-mutation",
1306
- allowRed,
1307
- log: (m) => log("ok", m.replace(/^\[stack-release:pre-mutation\]\s*/, "")),
1308
- warn: (m) => log("warn", m.replace(/^\[stack-release:pre-mutation\]\s*/, ""))
1309
- });
1310
- }
1311
- async function commitAndTagExeOs(newStackVersion, _exeOsChange, dryRun) {
1312
- if (dryRun) {
1313
- log("dry", `Would commit manifest + tag exe-os stack-v${newStackVersion}`);
1314
- return;
1315
- }
1316
- const status = await git(EXE_OS_ROOT, "status", "--porcelain", "deploy/stack-manifests/v0.9.json");
1317
- if (status) {
1318
- await git(EXE_OS_ROOT, "add", "deploy/stack-manifests/v0.9.json");
1319
- const commitMsg = `chore: stack release v${newStackVersion}`;
1320
- await git(EXE_OS_ROOT, "commit", "-m", commitMsg);
1321
- await landViaPR(EXE_OS_ROOT, "AskExe/exe-os", "main", `release/stack-v${newStackVersion}`, commitMsg);
1322
- log("ok", `Committed stack manifest v${newStackVersion} (via PR)`);
1323
- }
1324
- const stackTag = `stack-v${newStackVersion}`;
1325
- const releaseCommit = await git(EXE_OS_ROOT, "rev-parse", "HEAD");
1326
- await assertReleaseFreshness({
1327
- repoPath: EXE_OS_ROOT,
1328
- defaultBranch: "main",
1329
- releaseCommit
1330
- });
1331
- await printOpenPrGate({ ghRepo: "AskExe/exe-os", baseBranch: "main" });
1332
- try {
1333
- await git(EXE_OS_ROOT, "tag", stackTag);
1334
- await git(EXE_OS_ROOT, "push", "origin", stackTag);
1335
- log("ok", `Tagged exe-os ${stackTag}`);
1336
- } catch (err) {
1337
- const message = err instanceof Error ? err.message : String(err);
1338
- log("fail", `Failed to tag/push exe-os ${stackTag}: ${message}`);
1339
- throw new Error(
1340
- `Aborting: failed to tag/push exe-os ${stackTag}: ${message}. The manifest was committed but the stack-v tag that publishes it was not \u2014 customers will keep reading the previous manifest. Fix the cause and re-run; publishing is idempotent.`
1341
- );
1342
- }
1343
- }
1344
- function printSummary(changes, ciStatuses, newStackVersion, dryRun) {
1345
- const divider = "\u2500".repeat(70);
1346
- console.log(`
1347
- ${divider}`);
1348
- console.log(` ${dryRun ? "[DRY RUN] " : ""}Stack Release Summary \u2014 v${newStackVersion}`);
1349
- console.log(divider);
1350
- console.log(
1351
- ` ${"Service".padEnd(14)} ${"Version".padEnd(20)} ${"Commits".padEnd(8)} ${"CI".padEnd(12)}`
1352
- );
1353
- console.log(` ${"\u2500".repeat(14)} ${"\u2500".repeat(20)} ${"\u2500".repeat(8)} ${"\u2500".repeat(12)}`);
1354
- for (const change of changes) {
1355
- const ci = ciStatuses.find((s) => s.repo.serviceKey === change.repo.serviceKey);
1356
- const ciLabel = dryRun ? "\x1B[33mpending\x1B[0m" : ci?.status === "completed" && ci.conclusion === "success" ? "\x1B[32msuccess\x1B[0m" : ci?.status === "timed_out" ? "\x1B[31mtimed out\x1B[0m" : `\x1B[31m${ci?.conclusion ?? ci?.status ?? "unknown"}\x1B[0m`;
1357
- const versionStr = `${change.currentVersion} -> ${change.newVersion}`;
1358
- console.log(
1359
- ` ${change.repo.serviceKey.padEnd(14)} ${versionStr.padEnd(20)} ${String(change.commits.length).padEnd(8)} ${ciLabel}`
1360
- );
1361
- }
1362
- if (changes.length === 0) {
1363
- console.log(" No changes detected across any repo.");
1364
- }
1365
- console.log(divider);
1366
- if (!dryRun) {
1367
- const failed = ciStatuses.filter(
1368
- (s) => s.status !== "completed" || s.conclusion !== "success"
1369
- );
1370
- if (failed.length > 0) {
1371
- console.log("\n \x1B[31mWarning:\x1B[0m Some CI builds failed. Check the links above.");
1372
- } else if (changes.length > 0) {
1373
- console.log(
1374
- `
1375
- \x1B[32mAll done.\x1B[0m Customers will pull new images from update.askexe.com on next stack-update.`
1376
- );
1377
- }
1378
- }
1379
- console.log("");
1380
- }
1381
- function log(level, msg) {
1382
- const prefix = {
1383
- info: "\x1B[36m[info]\x1B[0m",
1384
- ok: "\x1B[32m[ok]\x1B[0m",
1385
- warn: "\x1B[33m[warn]\x1B[0m",
1386
- fail: "\x1B[31m[fail]\x1B[0m",
1387
- dry: "\x1B[35m[dry-run]\x1B[0m"
1388
- };
1389
- console.log(` ${prefix[level]} ${msg}`);
1390
- }
1391
- function sleep(ms) {
1392
- return new Promise((resolve) => setTimeout(resolve, ms));
1393
- }
1394
- async function confirm(message) {
1395
- const rl = createInterface({ input: process.stdin, output: process.stdout });
1396
- return new Promise((resolve) => {
1397
- rl.question(`
1398
- ${message} [y/N] `, (answer) => {
1399
- rl.close();
1400
- resolve(answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes");
1401
- });
1402
- });
1403
- }
1404
- function nextStackVersionForLock() {
1405
- try {
1406
- const manifest = JSON.parse(readFileSync2(MANIFEST_PATH, "utf8"));
1407
- const parts = String(manifest.latest ?? "0.9.0").split(".").map(Number);
1408
- return `stack-v${parts[0] ?? 0}.${parts[1] ?? 9}.${(parts[2] ?? 0) + 1}`;
1409
- } catch {
1410
- return "stack-vunknown";
1411
- }
1412
- }
1413
- async function currentReleaseBranch() {
1414
- try {
1415
- return await git(EXE_OS_ROOT, "branch", "--show-current") || "unknown";
1416
- } catch {
1417
- return "unknown";
1418
- }
1419
- }
1420
- function forceBuildRejection(flags) {
1421
- if (!flags.forceBuildRejected) return null;
1422
- return [
1423
- "--force-build is not supported and has been removed (bug b55695a5).",
1424
- "It never bypassed any gate: nothing downstream read it, images are built by tag push,",
1425
- "and docker.yml's green-CI gate stayed fully in force. It only printed a bypass banner.",
1426
- "Fix the red CI, or cut the release from a green SHA. Re-run without --force-build."
1427
- ].join("\n");
1428
- }
1429
- async function runStackRelease(flags) {
1430
- if (!flags.stack) {
1431
- console.error("Usage: exe-os release --stack [--dry-run] [--repos <list>] [--yes] [--skip-image-verify] [--allow-red] [--allow-dirty] [--canary] [--root <path>] [--force]");
1432
- process.exit(1);
1433
- }
1434
- if (flags.root) {
1435
- applyRootOverride(flags.root);
1436
- }
1437
- const forceBuildError = forceBuildRejection(flags);
1438
- if (forceBuildError) {
1439
- console.error(forceBuildError);
1440
- process.exit(1);
1441
- }
1442
- if (flags.force) {
1443
- console.warn("\n \x1B[33;1m\u26A0 --force: release mutex override is ACTIVE\x1B[0m");
1444
- console.warn(" \x1B[33m This only overrides ~/.exe-os/release.lock; freshness and publish gates still apply.\x1B[0m");
1445
- console.warn(" \x1B[33m Use only after verifying no other release is actually running.\x1B[0m\n");
1446
- }
1447
- let releaseLock;
1448
- if (!flags.dryRun) {
1449
- releaseLock = acquireReleaseLock({
1450
- targetVersion: nextStackVersionForLock(),
1451
- branch: await currentReleaseBranch(),
1452
- force: flags.force,
1453
- log: (message) => log("warn", message)
1454
- });
1455
- log("ok", `Acquired release lock: ${releaseLock.lockPath}`);
1456
- }
1457
- try {
1458
- await assertCleanWorkingTree({ allowDirty: flags.allowDirty || flags.dryRun });
1459
- console.log(`
1460
- \x1B[1mexe-os stack release\x1B[0m${flags.dryRun ? " (dry run)" : ""}
1461
- `);
1462
- const allRepos = buildRepoConfigs();
1463
- let repos;
1464
- if (flags.repos) {
1465
- repos = [];
1466
- for (const name of flags.repos) {
1467
- const match = allRepos.find(
1468
- (r) => r.serviceKey === name || r.ghRepo.endsWith(`/${name}`) || r.ghRepo === name
1469
- );
1470
- if (match) {
1471
- repos.push(match);
1472
- } else {
1473
- log("warn", `Unknown repo: ${name} \u2014 skipping`);
1474
- }
1475
- }
1476
- if (repos.length === 0) {
1477
- throw new Error("No valid repos specified.");
1478
- }
1479
- } else {
1480
- repos = allRepos;
1481
- }
1482
- repos = repos.filter((repo) => {
1483
- if (hasPackageJson(repo)) return true;
1484
- warnMissingRepoPackage(repo);
1485
- return false;
1486
- });
1487
- log("info", "Detecting changes across repos...");
1488
- const changes = await detectChanges(repos);
1489
- if (changes.length === 0) {
1490
- console.log("\n No changes detected since last tags. Nothing to release.\n");
1491
- return;
1492
- }
1493
- const bumpMode = flags.major ? "major" : flags.minor ? "minor" : "patch";
1494
- for (const change of changes) {
1495
- const newVersion = bumpVersion(change.currentVersion, bumpMode);
1496
- change.newVersion = newVersion;
1497
- change.tag = `v${newVersion}`;
1498
- }
1499
- console.log("\n Planned releases:");
1500
- for (const change of changes) {
1501
- console.log(
1502
- ` ${change.repo.serviceKey.padEnd(14)} ${change.currentVersion} -> ${change.newVersion} (${change.commits.length} commit${change.commits.length === 1 ? "" : "s"})`
1503
- );
1504
- }
1505
- gatePreMutationStructure(changes, flags.dryRun, flags.allowRed);
1506
- if (!flags.yes && !flags.dryRun) {
1507
- const ok = await confirm("Proceed with release?");
1508
- if (!ok) {
1509
- console.log(" Aborted.\n");
1510
- return;
1511
- }
1512
- }
1513
- await bumpVersions(changes, bumpMode, flags.dryRun);
1514
- log("info", "Tagging repos and triggering CI...");
1515
- await tagAndTriggerCI(changes, flags.dryRun);
1516
- const ciStatuses = await waitForCI(changes, flags.dryRun);
1517
- const ciFailures = ciStatuses.filter(
1518
- (s) => s.status !== "completed" || s.conclusion !== "success"
1519
- );
1520
- if (ciFailures.length > 0 && !flags.dryRun) {
1521
- await rollbackFailedReleases(changes, ciStatuses, flags.dryRun);
1522
- const ciSuccesses = ciStatuses.filter(
1523
- (s) => s.status === "completed" && s.conclusion === "success"
1524
- );
1525
- if (ciSuccesses.length === 0) {
1526
- log("fail", "All CI builds failed. Release aborted, all bumps rolled back.");
1527
- throw new Error("All CI builds failed. Release aborted, all bumps rolled back.");
1528
- }
1529
- log("warn", `${ciFailures.length} CI build(s) failed and were rolled back. Continuing with ${ciSuccesses.length} successful build(s).`);
1530
- }
1531
- let imageResults = [];
1532
- if (flags.skipImageVerify) {
1533
- log("warn", "Image verification skipped (--skip-image-verify)");
1534
- } else {
1535
- imageResults = await verifyImages(changes, ciStatuses, flags.dryRun);
1536
- const failed = imageResults.filter((r) => (r.ghcr === "missing" || r.ghcr === "error") && r.proxy !== "ok");
1537
- if (failed.length > 0 && !flags.dryRun) {
1538
- for (const m of failed) {
1539
- log("fail", `${shortImageName(m.repo.imageName)}:${m.tag} \u2014 GHCR status: ${m.ghcr}, proxy: ${m.proxy} \u2014 cannot release`);
1540
- }
1541
- log("fail", "Aborting: images not verified. Fix GHCR auth/CI or use --skip-image-verify to override.");
1542
- throw new Error("Aborting: images not verified. Fix GHCR auth/CI or use --skip-image-verify to override.");
1543
- }
1544
- const pending = imageResults.filter((r) => r.proxy === "pending");
1545
- if (pending.length > 0) {
1546
- for (const p of pending) {
1547
- log("warn", `${shortImageName(p.repo.imageName)}:${p.tag} \u2014 proxy cache pending`);
1548
- }
1549
- if (!flags.dryRun) {
1550
- log("fail", "Aborting: proxy cache not ready for all images. Wait for proxy propagation or use --skip-image-verify.");
1551
- throw new Error("Aborting: proxy cache not ready for all images. Wait for proxy propagation or use --skip-image-verify.");
1552
- }
1553
- }
1554
- printImageVerification(imageResults);
1555
- }
1556
- log("info", "Updating stack manifest...");
1557
- const newStackVersion = await updateStackManifest(changes, ciStatuses, flags.dryRun, imageResults, flags.canary);
1558
- gatePublishWithPreflight(newStackVersion, flags.dryRun, flags.allowRed);
1559
- const exeOsChange = changes.find((c) => c.repo.serviceKey === "exe-os");
1560
- await commitAndTagExeOs(newStackVersion, exeOsChange, flags.dryRun);
1561
- printSummary(changes, ciStatuses, newStackVersion, flags.dryRun);
1562
- if (!flags.skipImageVerify && !flags.dryRun && imageResults.length > 0) {
1563
- const pendingImages = imageResults.filter((r) => r.proxy === "pending");
1564
- if (pendingImages.length > 0) {
1565
- log("info", "Re-checking proxy availability for pending images...");
1566
- for (const r of pendingImages) {
1567
- const shortName = shortImageName(r.repo.imageName);
1568
- const proxyRepo = `askexe/${shortName}`;
1569
- const proxyToken = process.env.EXE_REGISTRY_PROXY_PULL_TOKEN ?? void 0;
1570
- const recheck = await checkRegistryManifest(PROXY_REGISTRY, proxyRepo, r.tag, proxyToken);
1571
- if (recheck.ok) {
1572
- r.proxy = "ok";
1573
- }
1574
- }
1575
- printImageVerification(imageResults);
1576
- }
1577
- }
1578
- } finally {
1579
- releaseLock?.release();
1580
- if (releaseLock) log("ok", `Released release lock: ${releaseLock.lockPath}`);
1581
- }
1582
- }
1583
- export {
1584
- RELEASE_PR_POLL_JQ,
1585
- assertCleanWorkingTree,
1586
- assertReleaseFreshness,
1587
- assertVersionNotAlreadyReleased,
1588
- compareSemver,
1589
- deriveCloneUrl,
1590
- ensureRepoCloned,
1591
- findPullRequestForHeadSha,
1592
- findWorkflowRunForHeadSha,
1593
- forceBuildRejection,
1594
- getRemoteReleaseTags,
1595
- higherSemver,
1596
- highestReleasedVersion,
1597
- imageRepoPath,
1598
- isCloneTargetSafe,
1599
- isStrictSemverTag,
1600
- mayRebaseComponent,
1601
- parseReleaseArgs,
1602
- parseRemoteTagNames,
1603
- printOpenPrGate,
1604
- rebaseImageTag,
1605
- resolveVersionBasis,
1606
- runStackRelease
1607
- };