@maycombhq/atticus 2.0.0-beta.2 → 2.0.0-beta.21

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 (273) hide show
  1. package/dist/bin.js +8 -4
  2. package/dist/chunk-265LWMOA.js +91 -0
  3. package/dist/chunk-26WBY34Y.js +37 -0
  4. package/dist/{chunk-UBBC2XSG.js → chunk-26Z7OGQW.js} +518 -93
  5. package/dist/chunk-2D7PWSOK.js +172 -0
  6. package/dist/{chunk-JCZIKA4C.js → chunk-2HH3UCZM.js} +4 -4
  7. package/dist/{chunk-GDLULHTC.js → chunk-2HTEMYTB.js} +12 -26
  8. package/dist/chunk-2UCRFXFZ.js +130 -0
  9. package/dist/{chunk-MVZVGYXH.js → chunk-2V2ZPQQU.js} +81 -13
  10. package/dist/{chunk-BOHJZTEQ.js → chunk-36RY7ZXM.js} +130 -71
  11. package/dist/{chunk-I3E7CX4H.js → chunk-37WE3LS2.js} +3 -3
  12. package/dist/{chunk-IPKHE5D6.js → chunk-3AQ2R5NL.js} +1520 -871
  13. package/dist/chunk-3G6S7LAA.js +43 -0
  14. package/dist/chunk-3J5I7ZOU.js +58 -0
  15. package/dist/{chunk-65NYPGRC.js → chunk-3LZ6P5EP.js} +1 -1
  16. package/dist/chunk-3SSTNB4E.js +191 -0
  17. package/dist/chunk-3TD6S4TC.js +49 -0
  18. package/dist/{chunk-3C7EYRFZ.js → chunk-3UTYPHN3.js} +1 -1
  19. package/dist/{chunk-G2B4ZQGH.js → chunk-3WON4G3M.js} +2 -2
  20. package/dist/{chunk-S3QNRWYR.js → chunk-42EPP7OB.js} +12 -12
  21. package/dist/chunk-455SYIIE.js +113 -0
  22. package/dist/{chunk-YISXLPHN.js → chunk-4B6BT75O.js} +166 -23
  23. package/dist/{chunk-C6EKFH7J.js → chunk-4CLK2RJO.js} +7 -7
  24. package/dist/chunk-4CYTJ5TZ.js +38 -0
  25. package/dist/{chunk-EYWIDJFU.js → chunk-4D62IZII.js} +5 -5
  26. package/dist/chunk-4FOAYLI4.js +328 -0
  27. package/dist/{chunk-7JUKDRSZ.js → chunk-4KNOJC6J.js} +294 -215
  28. package/dist/{chunk-YMZ3TNYB.js → chunk-4PYC2UGC.js} +2 -2
  29. package/dist/{chunk-27NKUS3X.js → chunk-4SLBCEPD.js} +2104 -726
  30. package/dist/chunk-4TKVU2I2.js +122 -0
  31. package/dist/{chunk-RUVFXE2L.js → chunk-4WXRPQDE.js} +448 -47
  32. package/dist/{chunk-7ZUIMTOU.js → chunk-5CMPVC3N.js} +24 -24
  33. package/dist/chunk-5G7ILBUG.js +66 -0
  34. package/dist/{chunk-HFQCFSJR.js → chunk-5PV7S2ZP.js} +3 -3
  35. package/dist/{chunk-FITWGOJ2.js → chunk-5QUOPNLN.js} +4 -4
  36. package/dist/{chunk-Z6LF7HQN.js → chunk-5TDRBOAV.js} +5 -5
  37. package/dist/{chunk-T6AIP7H5.js → chunk-5WERBKLE.js} +199 -63
  38. package/dist/{chunk-XPCM366L.js → chunk-62DLJSLV.js} +8 -8
  39. package/dist/{chunk-L24C55VN.js → chunk-64AADMTW.js} +99 -25
  40. package/dist/{chunk-K65BJIMT.js → chunk-67BP7HXL.js} +17 -17
  41. package/dist/chunk-6DQYZU2W.js +379 -0
  42. package/dist/{chunk-EDS2L2LD.js → chunk-6EXDI5GI.js} +52 -26
  43. package/dist/chunk-6FNAKCET.js +38 -0
  44. package/dist/{chunk-6IU67AEK.js → chunk-75ASLHBT.js} +21 -15
  45. package/dist/{chunk-A3GY6SOA.js → chunk-75ZOZFGH.js} +18 -18
  46. package/dist/{chunk-MGYZM27B.js → chunk-77O633L5.js} +49 -23
  47. package/dist/{chunk-GQ4N4WKF.js → chunk-7AB6OWTV.js} +3 -3
  48. package/dist/{chunk-M766ZJ6W.js → chunk-7CZHUZBL.js} +25 -25
  49. package/dist/{chunk-A7UPUQFY.js → chunk-7FHNFCHK.js} +643 -124
  50. package/dist/chunk-7FUIVIBG.js +415 -0
  51. package/dist/{chunk-6QSERWXH.js → chunk-7QPCGTPS.js} +21 -21
  52. package/dist/{chunk-LR3R6Z2A.js → chunk-A2G72UI4.js} +7 -7
  53. package/dist/{chunk-I4JRCD6M.js → chunk-AE4P57KK.js} +23 -17
  54. package/dist/{chunk-AVJDSJ2F.js → chunk-AHW3RY3X.js} +7 -7
  55. package/dist/chunk-AM6FP6JW.js +43 -0
  56. package/dist/{chunk-CSZ4C5N6.js → chunk-AOCWFA7T.js} +93 -10
  57. package/dist/{chunk-EPTBZ3GP.js → chunk-API5TOUB.js} +1 -1
  58. package/dist/{chunk-652QWTLZ.js → chunk-AZDITBCT.js} +3 -3
  59. package/dist/{chunk-DPUYDDQG.js → chunk-BA2HUMDN.js} +25 -3
  60. package/dist/chunk-BA3U7ALS.js +99 -0
  61. package/dist/{chunk-HVUPKCGJ.js → chunk-BC6KKZ67.js} +5 -5
  62. package/dist/{chunk-CKSITVS4.js → chunk-BEPCKDCP.js} +2554 -722
  63. package/dist/{chunk-3ALD2W2A.js → chunk-BPPU3PW7.js} +4 -4
  64. package/dist/{chunk-QRAOANID.js → chunk-BVWWPBKD.js} +21 -21
  65. package/dist/{chunk-ZDPAS56H.js → chunk-BXBCMGIQ.js} +7 -1
  66. package/dist/chunk-BXZ4PKIT.js +233 -0
  67. package/dist/chunk-C7CEK3KW.js +247 -0
  68. package/dist/chunk-CFH3QH26.js +29 -0
  69. package/dist/chunk-CG6CULP6.js +170 -0
  70. package/dist/chunk-CLD4RVTA.js +45 -0
  71. package/dist/{chunk-DKJA76CJ.js → chunk-CNPY4NTZ.js} +13 -13
  72. package/dist/chunk-CNU5Q2D5.js +575 -0
  73. package/dist/{chunk-T2UYZSCQ.js → chunk-COYN3UAN.js} +126 -23
  74. package/dist/{chunk-23W7VWKR.js → chunk-CPK7QSBG.js} +1360 -113
  75. package/dist/{chunk-4AK7IBVP.js → chunk-D2WSW4GU.js} +1 -1
  76. package/dist/{chunk-ABWHYWNI.js → chunk-D57MFJPP.js} +3 -3
  77. package/dist/chunk-D5KV5PSK.js +45 -0
  78. package/dist/chunk-D5MASS4I.js +48 -0
  79. package/dist/{chunk-7EOBHUMC.js → chunk-DCWN3HFV.js} +2 -2
  80. package/dist/{chunk-QSXK2RGH.js → chunk-DDJPM5YX.js} +2 -2
  81. package/dist/chunk-DN4A2Q2O.js +57 -0
  82. package/dist/{chunk-IHMLFRVV.js → chunk-DUPZPDCJ.js} +58 -16
  83. package/dist/{chunk-PRPPGYZG.js → chunk-E2VXBSIR.js} +4 -4
  84. package/dist/chunk-E57AOFYN.js +32 -0
  85. package/dist/{chunk-UGLN7QHV.js → chunk-ECLXZWAG.js} +12 -12
  86. package/dist/{chunk-5WG5YP4T.js → chunk-EDZ45WIR.js} +14 -14
  87. package/dist/chunk-EFCQSMXB.js +37 -0
  88. package/dist/{chunk-2ANOROX3.js → chunk-EZIN5E5B.js} +8 -8
  89. package/dist/{chunk-BWZ77RQT.js → chunk-F6H2RJ3N.js} +1 -1
  90. package/dist/{chunk-OLQJTJJU.js → chunk-FAJDKNSD.js} +7 -3
  91. package/dist/chunk-FGAGWYZB.js +47 -0
  92. package/dist/{chunk-4WBVUHXN.js → chunk-FLVZFTLQ.js} +18 -18
  93. package/dist/chunk-FQ6WH6PB.js +140 -0
  94. package/dist/{chunk-T6XQL73F.js → chunk-FRHD22WM.js} +61 -23
  95. package/dist/{chunk-XAKWMCXI.js → chunk-FVMPFCDS.js} +4 -4
  96. package/dist/{chunk-B2WLAWNJ.js → chunk-FY3QIRTL.js} +3 -3
  97. package/dist/{chunk-CPA6HPVR.js → chunk-FYBF5MB2.js} +15 -11
  98. package/dist/{chunk-V2YTAIQO.js → chunk-G5YJBNDZ.js} +21 -6
  99. package/dist/chunk-G6IQYM25.js +339 -0
  100. package/dist/chunk-G6X4YL4D.js +161 -0
  101. package/dist/{chunk-SJRGMG6A.js → chunk-GC2VK3JT.js} +25 -25
  102. package/dist/{chunk-SP2I63OO.js → chunk-GCS3FZOB.js} +77 -3
  103. package/dist/{chunk-6TACQN3N.js → chunk-GDZB57IU.js} +5 -5
  104. package/dist/chunk-GIFMBCTW.js +221 -0
  105. package/dist/{chunk-YY6ZQQBP.js → chunk-GK4PYVEC.js} +18 -8
  106. package/dist/{chunk-TDD55CJH.js → chunk-GNWAOVFQ.js} +6 -5
  107. package/dist/chunk-GS76RXQE.js +226 -0
  108. package/dist/{chunk-YYQN46FY.js → chunk-GSGIZ2OO.js} +23 -23
  109. package/dist/chunk-GUYL7RMK.js +60 -0
  110. package/dist/chunk-GX74YWR7.js +321 -0
  111. package/dist/chunk-HE3HRDNZ.js +122 -0
  112. package/dist/{chunk-AIOQPLA5.js → chunk-HGHRSF3E.js} +30 -12
  113. package/dist/{chunk-2RMYG36Q.js → chunk-HK3VOHIE.js} +198 -336
  114. package/dist/{chunk-QZSX3F7C.js → chunk-HS4BGB4G.js} +24 -22
  115. package/dist/{chunk-XY3P7JW7.js → chunk-HSZMPH5T.js} +834 -149
  116. package/dist/{chunk-L6SGQIR3.js → chunk-HWD4JPU7.js} +4 -2
  117. package/dist/{chunk-PCVDV3UZ.js → chunk-HXXP2NTE.js} +6 -6
  118. package/dist/{chunk-AA6YULYB.js → chunk-I2A3Z5AK.js} +7 -7
  119. package/dist/{chunk-N7IXEHK3.js → chunk-ICHDSWEY.js} +7 -7
  120. package/dist/chunk-IFFRGC3N.js +31 -0
  121. package/dist/{chunk-4BN53TG3.js → chunk-IFHDV6KQ.js} +31 -23
  122. package/dist/chunk-J2O4LFVI.js +80 -0
  123. package/dist/chunk-J5AAZ6OJ.js +161 -0
  124. package/dist/{chunk-ZMFPW66A.js → chunk-JAABT3DA.js} +3 -3
  125. package/dist/chunk-JITJP64K.js +34 -0
  126. package/dist/chunk-JKIGL3XQ.js +162 -0
  127. package/dist/chunk-JRVNXLZP.js +615 -0
  128. package/dist/chunk-JRXHON4E.js +41 -0
  129. package/dist/{chunk-DHPACDG7.js → chunk-JS6WMC3J.js} +8 -8
  130. package/dist/chunk-JU4QNJSR.js +62 -0
  131. package/dist/{chunk-PAMPPUR6.js → chunk-JYMIB5XY.js} +1697 -172
  132. package/dist/{chunk-RRTKKSLQ.js → chunk-JYTIHTIK.js} +8 -6
  133. package/dist/{chunk-3HI6Z55Y.js → chunk-K332XW7K.js} +324 -39
  134. package/dist/{chunk-GLUX4EEP.js → chunk-K6JB6XTX.js} +9 -9
  135. package/dist/chunk-KDAHXQIE.js +294 -0
  136. package/dist/chunk-KYZMRSPS.js +93 -0
  137. package/dist/chunk-L4CESDPB.js +22 -0
  138. package/dist/{chunk-NXFPS2LR.js → chunk-LEBR5NHW.js} +14 -14
  139. package/dist/chunk-LIJJCBQI.js +223 -0
  140. package/dist/{chunk-DE76JROX.js → chunk-LIRVU3HW.js} +16 -16
  141. package/dist/{chunk-JMSCMUCA.js → chunk-LN77DXBM.js} +145 -21
  142. package/dist/{chunk-JIITQXFB.js → chunk-LNWEWYCM.js} +2969 -788
  143. package/dist/{chunk-TANFV3MZ.js → chunk-LO3BLMVG.js} +3 -3
  144. package/dist/chunk-LPV6T362.js +250 -0
  145. package/dist/{chunk-2VHF7OXM.js → chunk-LQH4P45K.js} +1 -1
  146. package/dist/chunk-LU4MQVAL.js +41 -0
  147. package/dist/chunk-LWDNKE4X.js +273 -0
  148. package/dist/chunk-M4H2UG4R.js +218 -0
  149. package/dist/{chunk-6BVONWJ7.js → chunk-M6D2U3YM.js} +3 -3
  150. package/dist/chunk-MD7KUEJ3.js +514 -0
  151. package/dist/{chunk-66YVAGGM.js → chunk-MMZUN7ZO.js} +20 -20
  152. package/dist/chunk-MZGRDONP.js +916 -0
  153. package/dist/chunk-N3RUGTGV.js +39 -0
  154. package/dist/{chunk-JGZCG5VM.js → chunk-NCB6656Z.js} +7 -7
  155. package/dist/{chunk-4NDAAFQT.js → chunk-NRQ65LTQ.js} +7 -5
  156. package/dist/{chunk-UNM6ZOKP.js → chunk-NTAVOWYD.js} +145 -18
  157. package/dist/{chunk-3USYZGRF.js → chunk-NYOABJNK.js} +36 -27
  158. package/dist/{chunk-WVPJSRRJ.js → chunk-O4IKAIZ2.js} +24 -20
  159. package/dist/{chunk-D3PO4DOZ.js → chunk-ODMBPDDV.js} +51 -14
  160. package/dist/chunk-OKUB6RJA.js +533 -0
  161. package/dist/chunk-OMPENNWY.js +39 -0
  162. package/dist/chunk-OPQZISG3.js +25 -0
  163. package/dist/{chunk-5GGOLAKC.js → chunk-OV57AHCG.js} +26 -26
  164. package/dist/chunk-OZJ3JLYI.js +58 -0
  165. package/dist/{chunk-KEM4SDDP.js → chunk-P2VBXX7N.js} +11 -10
  166. package/dist/{chunk-S2A7YC3Q.js → chunk-PAINMSID.js} +10150 -1720
  167. package/dist/chunk-PKJAV25S.js +43 -0
  168. package/dist/{chunk-EBEW3XXQ.js → chunk-PNQLCRSS.js} +4 -4
  169. package/dist/{chunk-WY6PP6AA.js → chunk-PQH56YF6.js} +37 -5
  170. package/dist/chunk-PRTBPXYA.js +50 -0
  171. package/dist/{chunk-YEDDOYSC.js → chunk-PRW3HXM3.js} +2 -2
  172. package/dist/chunk-PRZ7KMWL.js +42 -0
  173. package/dist/{chunk-6M5NK3K3.js → chunk-PTQ3ZALZ.js} +42 -4
  174. package/dist/{chunk-5GDRCDQB.js → chunk-PUEWDYGP.js} +2 -2
  175. package/dist/chunk-Q26LDIUV.js +236 -0
  176. package/dist/{chunk-CR3MTH32.js → chunk-Q46QDDMF.js} +77 -7
  177. package/dist/chunk-QBNWF5ZU.js +19 -0
  178. package/dist/chunk-QE527XRY.js +342 -0
  179. package/dist/{chunk-CNUKCS2T.js → chunk-QHJJMZLX.js} +1518 -54
  180. package/dist/chunk-QJCJOMW3.js +425 -0
  181. package/dist/{chunk-VYDBJ73M.js → chunk-QKMRFNMA.js} +15 -15
  182. package/dist/chunk-QOOEYDNJ.js +317 -0
  183. package/dist/chunk-QSIDVFB7.js +45 -0
  184. package/dist/{chunk-R77KLRB3.js → chunk-RA33EYS4.js} +19 -18
  185. package/dist/{chunk-DFBCSHVT.js → chunk-RALY3E5U.js} +4 -4
  186. package/dist/{chunk-ZCJDOK2S.js → chunk-S2RY3BOD.js} +35 -21
  187. package/dist/chunk-S5LSDXV4.js +40 -0
  188. package/dist/chunk-SB5GRUFM.js +54 -0
  189. package/dist/chunk-SH27FYPF.js +150 -0
  190. package/dist/chunk-T74WQQGS.js +40 -0
  191. package/dist/{chunk-O3WI5KWW.js → chunk-TGVVUKTG.js} +2 -2
  192. package/dist/chunk-THN26IEF.js +48 -0
  193. package/dist/chunk-TJH2EUAZ.js +47 -0
  194. package/dist/{chunk-ZNCEKR4J.js → chunk-TQ4TG57V.js} +12 -8
  195. package/dist/{chunk-7FBGJFEE.js → chunk-TQTRWENN.js} +12 -12
  196. package/dist/{chunk-TYAID7B7.js → chunk-TSM6T6GQ.js} +23 -23
  197. package/dist/{chunk-DPZHZQKE.js → chunk-TW3KOSQK.js} +43 -10
  198. package/dist/{chunk-MC3H7736.js → chunk-TWQRPM76.js} +37 -4
  199. package/dist/chunk-TYD6I4CZ.js +109 -0
  200. package/dist/chunk-U3NBK4EU.js +420 -0
  201. package/dist/{chunk-6JCPI55B.js → chunk-UDXWIPCB.js} +47 -7
  202. package/dist/{chunk-MMOSZPWJ.js → chunk-UEVZZZU2.js} +4 -4
  203. package/dist/{chunk-JBTK5OP2.js → chunk-UF4UHCIG.js} +2 -2
  204. package/dist/{chunk-6JUBZBMW.js → chunk-UFE36WJH.js} +4 -4
  205. package/dist/{chunk-ONLS5QA5.js → chunk-URIKUID7.js} +12 -12
  206. package/dist/chunk-UYLBRRFK.js +1106 -0
  207. package/dist/chunk-UZACEG2C.js +515 -0
  208. package/dist/{chunk-7SW2QWLU.js → chunk-VAMHOHUU.js} +12 -12
  209. package/dist/{chunk-RZPYKDWS.js → chunk-VUVGX5L2.js} +1 -1
  210. package/dist/{chunk-Q4D5X7QB.js → chunk-VYOCPTUB.js} +44 -24
  211. package/dist/{chunk-CAQZUNOH.js → chunk-WAZEXEJH.js} +567 -3
  212. package/dist/chunk-WCEF7H6L.js +221 -0
  213. package/dist/{chunk-2DIQQJCB.js → chunk-WEOMJHPV.js} +12 -12
  214. package/dist/{chunk-FHH4I4ZV.js → chunk-WFU77T3A.js} +12 -6
  215. package/dist/chunk-WIWTJMMR.js +292 -0
  216. package/dist/{chunk-2BFCZ3RK.js → chunk-WNTIOBOG.js} +18 -18
  217. package/dist/{chunk-7WD63HU2.js → chunk-X2CUXTWU.js} +6 -6
  218. package/dist/chunk-X7EMBM5X.js +34 -0
  219. package/dist/chunk-XEQCQO66.js +33 -0
  220. package/dist/{chunk-F7HYOUFF.js → chunk-XG4WJHLQ.js} +20 -4
  221. package/dist/{chunk-EFHQ3TWP.js → chunk-XOL3GFW2.js} +171 -31
  222. package/dist/{chunk-NE2W3OO7.js → chunk-XQ3YZ4QW.js} +22 -22
  223. package/dist/{chunk-CEXSU4S6.js → chunk-Y6K3CJBU.js} +723 -85
  224. package/dist/{chunk-4RS5KZQD.js → chunk-YAH5IQAL.js} +5 -5
  225. package/dist/{chunk-64EQYWCA.js → chunk-YBOVNMHC.js} +51 -14
  226. package/dist/chunk-YF5DZCPN.js +1212 -0
  227. package/dist/chunk-YIGBJNH4.js +37 -0
  228. package/dist/{chunk-BKVWTZ6A.js → chunk-YJ3TBOTK.js} +25 -25
  229. package/dist/{chunk-Q5AMFEWD.js → chunk-YKTE2RU3.js} +2 -2
  230. package/dist/{chunk-7EKXJZ2Y.js → chunk-YO2PKTXT.js} +296 -37
  231. package/dist/{chunk-6J5OKA4Y.js → chunk-YTTNTQOX.js} +15 -15
  232. package/dist/{chunk-OT2O76OS.js → chunk-YWAKDNMH.js} +183 -33
  233. package/dist/{chunk-R6XF6UR3.js → chunk-YZDZOEV4.js} +4 -4
  234. package/dist/chunk-Z3QFH7YG.js +17 -0
  235. package/dist/{chunk-W5BVQF44.js → chunk-Z6VLC5X4.js} +17 -17
  236. package/dist/{chunk-HLJAXMM4.js → chunk-ZFQMMU5Y.js} +2 -2
  237. package/dist/{chunk-W3BQAZ3Q.js → chunk-ZKYRDV7A.js} +12 -12
  238. package/dist/{chunk-ZMAINWBW.js → chunk-ZLS6PU2O.js} +3 -3
  239. package/dist/{chunk-ZVM6AKKM.js → chunk-ZOSD4FXV.js} +3 -3
  240. package/dist/chunk-ZPDDEVQX.js +47 -0
  241. package/dist/{chunk-TXLH6WHQ.js → chunk-ZQLRCA2V.js} +11 -11
  242. package/dist/{chunk-HITVBIJS.js → chunk-ZX7235KR.js} +128 -54
  243. package/dist/{chunk-GT4I4ZWM.js → chunk-ZZFHLRQW.js} +18 -16
  244. package/dist/files.json +636 -364
  245. package/dist/packages/naysayer/constitution/constitution.yaml +19 -0
  246. package/dist/packages/sdk/npm/index.js +5614 -2343
  247. package/dist/packages/ui/dist/assets/{elk-api-tma0MCuh.js → elk-api-CowTnhEJ.js} +2 -2
  248. package/dist/packages/ui/dist/assets/index-Dw20IRhQ.css +1 -0
  249. package/dist/packages/ui/dist/assets/index-z2wv0d5d.js +47 -0
  250. package/dist/packages/ui/dist/index.html +2 -2
  251. package/package.json +4 -4
  252. package/dist/chunk-2BF2EYJV.js +0 -174
  253. package/dist/chunk-42EVSKME.js +0 -37
  254. package/dist/chunk-4BKGMUIK.js +0 -53
  255. package/dist/chunk-5KLGBXG5.js +0 -48
  256. package/dist/chunk-6BNC6EQ4.js +0 -41
  257. package/dist/chunk-6D2CQ5II.js +0 -41
  258. package/dist/chunk-7AVHLZGD.js +0 -45
  259. package/dist/chunk-D46WUV3T.js +0 -33
  260. package/dist/chunk-E36QOJMD.js +0 -47
  261. package/dist/chunk-FQXYPF7E.js +0 -147
  262. package/dist/chunk-GBRFVT63.js +0 -45
  263. package/dist/chunk-ICY36XCH.js +0 -34
  264. package/dist/chunk-J4K3KVLD.js +0 -131
  265. package/dist/chunk-JZU3H6UQ.js +0 -54
  266. package/dist/chunk-NN2ZQEB4.js +0 -38
  267. package/dist/chunk-PCOCACY3.js +0 -385
  268. package/dist/chunk-RTEYOSJY.js +0 -29
  269. package/dist/chunk-RVH5SUW5.js +0 -69
  270. package/dist/chunk-VN3DP4LQ.js +0 -43
  271. package/dist/chunk-XPTOBS2D.js +0 -48
  272. package/dist/packages/ui/dist/assets/index-B54fm9FQ.js +0 -29
  273. package/dist/packages/ui/dist/assets/index-DD_hufQz.css +0 -1
@@ -8,19 +8,22 @@ import {
8
8
  addStoreReference,
9
9
  readStoreMarker,
10
10
  readStoreVia
11
- } from "./chunk-4AK7IBVP.js";
11
+ } from "./chunk-D2WSW4GU.js";
12
12
  import {
13
13
  AdapterError,
14
14
  mergeBlockers,
15
15
  requireApproval
16
- } from "./chunk-7JUKDRSZ.js";
16
+ } from "./chunk-4KNOJC6J.js";
17
17
  import {
18
+ outsideProfileSystems,
18
19
  readCatalog
19
- } from "./chunk-27NKUS3X.js";
20
+ } from "./chunk-4SLBCEPD.js";
20
21
  import {
22
+ baseClonePath,
21
23
  createGit,
22
- readChangeRepos
23
- } from "./chunk-EFHQ3TWP.js";
24
+ readChangeRepos,
25
+ writeChangeRepos
26
+ } from "./chunk-XOL3GFW2.js";
24
27
  import {
25
28
  ChangeArtifact,
26
29
  ChangeLifecycle,
@@ -30,6 +33,8 @@ import {
30
33
  Phase,
31
34
  RecordId,
32
35
  TestType,
36
+ askedGapQuestionId,
37
+ changeBranch,
33
38
  changesDirOf,
34
39
  changesRelOf,
35
40
  external_exports,
@@ -37,18 +42,21 @@ import {
37
42
  fastPathOf,
38
43
  handoffBranch,
39
44
  isAssistantResolution,
45
+ listQuestions,
40
46
  loadOpenSpecSettings,
41
47
  loadOrgPack,
42
48
  openspecDirOf,
43
49
  orgPackDirOf,
50
+ questionsRelOf,
44
51
  readFlow,
52
+ readQuestion,
45
53
  require_dist,
46
54
  specFlowFiles,
47
55
  toCanonicalYaml
48
- } from "./chunk-S2A7YC3Q.js";
56
+ } from "./chunk-PAINMSID.js";
49
57
  import {
50
58
  writeFileAtomic
51
- } from "./chunk-D3PO4DOZ.js";
59
+ } from "./chunk-ODMBPDDV.js";
52
60
  import {
53
61
  __toESM
54
62
  } from "./chunk-TV45EUGM.js";
@@ -142,8 +150,11 @@ var StageStep = external_exports.strictObject({
142
150
  loop: external_exports.string().min(1),
143
151
  /** The command that runs it; `<change>` stands for the change's id. */
144
152
  command: external_exports.string().min(1),
145
- /** Files it writes, relative to the change folder; a folder ends in "/". They ride with the hand-off. */
146
- writes: external_exports.array(external_exports.string().min(1)).default([]),
153
+ /**
154
+ * Files it writes, relative to the change folder; a folder ends in "/", and a "*" in the last
155
+ * part matches any name in that folder ("tasks/*.prompt.md"). They ride with the hand-off.
156
+ */
157
+ writes: external_exports.array(external_exports.string().min(1).refine((w) => !/(^|[/\\])\.\.([/\\]|$)/.test(w) && !/^([/\\]|[a-zA-Z]:)/.test(w), "a write stays inside the change folder (no .. and no absolute path)").refine((w) => !w.split("/").slice(0, -1).some((part) => part.includes("*")), 'a "*" may only be in the last part of a write (tasks/*.prompt.md, not tasks/*/notes.md)')).default([]),
147
158
  /** The change type's artifacts of the phase that this stage writes: on the stage path, only these are due. */
148
159
  artifacts: external_exports.array(ChangeArtifact).default([])
149
160
  });
@@ -153,7 +164,7 @@ var DEFAULT_STAGES = [
153
164
  phase: "define",
154
165
  loop: "stage/feature.yaml",
155
166
  command: "atticus change feature <change>",
156
- writes: ["feature.yaml", "feature.md", "design/"],
167
+ writes: ["feature.yaml", "feature.md", "proposal.md", "design/"],
157
168
  artifacts: []
158
169
  },
159
170
  {
@@ -161,7 +172,7 @@ var DEFAULT_STAGES = [
161
172
  phase: "design",
162
173
  loop: "stage/architecture.yaml",
163
174
  command: "atticus change architecture <change>",
164
- writes: ["target-flows/"],
175
+ writes: ["target-flows/", "stage/architecture-deploy.yaml", "stage/architecture-reads.yaml"],
165
176
  artifacts: ["design", "data"]
166
177
  },
167
178
  {
@@ -169,10 +180,27 @@ var DEFAULT_STAGES = [
169
180
  phase: "plan",
170
181
  loop: "stage/stories.yaml",
171
182
  command: "atticus plan stories <change>",
172
- writes: ["requests.md", "stage/stories-answer.yaml"],
183
+ writes: [
184
+ "requests.md",
185
+ "proposal.md",
186
+ "needs.yaml",
187
+ "needs.md",
188
+ "specs/",
189
+ "tasks/*.prompt.md",
190
+ "stage/stories-answer.yaml"
191
+ ],
173
192
  artifacts: ["stories", "tasks"]
174
193
  }
175
194
  ];
195
+ var isWritePattern = (w) => w.includes("*");
196
+ function writeMatches(w, path) {
197
+ if (w.endsWith("/"))
198
+ return path.startsWith(w);
199
+ if (!isWritePattern(w))
200
+ return path === w;
201
+ const re = new RegExp(`^${w.split("*").map((x) => x.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
202
+ return re.test(path);
203
+ }
176
204
  var Privacy = external_exports.strictObject({
177
205
  role: RecordId.default("privacy-officer"),
178
206
  classifications: external_exports.array(external_exports.string().min(1)).default(["personal", "pii", "sensitive-personal", "special-category"]),
@@ -530,7 +558,16 @@ var LEADING = [
530
558
  "from",
531
559
  "to"
532
560
  ];
561
+ var isPathLike = (id) => !id || id === "." || id === ".." || /[\\/]/.test(id) || /^[A-Za-z]:/.test(id) || id.includes("\0");
562
+ var isFolderRef = (id) => {
563
+ const parts = id.split("/");
564
+ if (parts.length === 2 && parts[0] === "archive")
565
+ return !isPathLike(parts[1]);
566
+ return !isPathLike(id);
567
+ };
533
568
  function changeFolder(root, id) {
569
+ if (!isFolderRef(id))
570
+ throw new Error(`${id} isn't a change id.`);
534
571
  return join3(changesDirOf(root), id);
535
572
  }
536
573
  function changeFilePath(root, id) {
@@ -553,6 +590,8 @@ function serializeChange(value) {
553
590
  }
554
591
  function changeStore(root) {
555
592
  async function tryRead(id) {
593
+ if (!RecordId.safeParse(id).success)
594
+ return void 0;
556
595
  const path = changeFilePath(root, id);
557
596
  let text;
558
597
  try {
@@ -592,7 +631,7 @@ function changeStore(root) {
592
631
  async list() {
593
632
  let names;
594
633
  try {
595
- names = (await readdir2(changesDirOf(root), { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== "archive").map((d) => d.name).sort();
634
+ names = (await readdir2(changesDirOf(root), { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== "archive" && RecordId.safeParse(d.name).success).map((d) => d.name).sort();
596
635
  } catch {
597
636
  return [];
598
637
  }
@@ -712,6 +751,21 @@ function changeTypeOf(ctx, type) {
712
751
  var import_yaml4 = __toESM(require_dist(), 1);
713
752
  import { readdir as readdir3, readFile as readFile5 } from "node:fs/promises";
714
753
  import { join as join6 } from "node:path";
754
+
755
+ // packages/changes/dist/outside.js
756
+ function outsideSystemsOf(op) {
757
+ return new Map(outsideProfileSystems(op).map((s) => [s.id, s.boundary === "vendor" ? "vendor" : "other-team"]));
758
+ }
759
+ function outsideKindOf(outside, system, catalogBoundary) {
760
+ if (!system)
761
+ return void 0;
762
+ if (catalogBoundary === "vendor")
763
+ return "vendor";
764
+ return outside.get(system);
765
+ }
766
+ var outsideWords = (k) => k === "vendor" ? "a vendor" : "another team's system";
767
+
768
+ // packages/changes/dist/footprint.js
715
769
  function personalLevels(ctx) {
716
770
  const pack = ctx.orgPack.pack?.classification ?? [
717
771
  "public",
@@ -799,6 +853,8 @@ async function changeFootprint(ctx, change) {
799
853
  };
800
854
  const profileSystems = new Map(ctx.orgPack.profiles.flatMap((p) => p.profile?.systems ?? []).map((s) => [s.id, s]));
801
855
  const isSystem = (id) => get("System", id) !== void 0 || profileSystems.has(id);
856
+ const outsideSystems = outsideSystemsOf(ctx.orgPack);
857
+ const outsideKind = (id) => outsideKindOf(outsideSystems, id, get("System", id)?.boundary ?? profileSystems.get(id)?.boundary);
802
858
  const systems = new Set(change.systems);
803
859
  const components = /* @__PURE__ */ new Set();
804
860
  const interfaces = /* @__PURE__ */ new Map();
@@ -846,7 +902,7 @@ async function changeFootprint(ctx, change) {
846
902
  if (table)
847
903
  tables.add(table.includes(".") ? slug(table) : `${store}.${slug(table)}`);
848
904
  }
849
- const staged = await stageFootprint(ctx, dir, { get, isSystem, profileSystems, ref });
905
+ const staged = await stageFootprint(ctx, dir, { get, isSystem, outsideKind, ref });
850
906
  for (const f of staged.from)
851
907
  from.add(f);
852
908
  for (const r of staged.repos)
@@ -905,7 +961,8 @@ async function changeFootprint(ctx, change) {
905
961
  repos.add(r.id);
906
962
  } catch {
907
963
  }
908
- const vendors = [...systems].filter((s) => (get("System", s)?.boundary ?? profileSystems.get(s)?.boundary) === "vendor");
964
+ const vendors = [...systems].filter((s) => outsideKind(s) === "vendor");
965
+ const otherTeams = [...systems].filter((s) => outsideKind(s) === "other-team");
909
966
  const personalSet = new Set(personalLevels(ctx).levels.map((c) => c.toLowerCase()));
910
967
  const personal = [];
911
968
  for (const s of staged.stores) {
@@ -962,6 +1019,7 @@ async function changeFootprint(ctx, change) {
962
1019
  records: sorted(change.touches.records),
963
1020
  repos: sorted(repos),
964
1021
  vendors: vendors.sort(),
1022
+ ...otherTeams.length ? { otherTeams: otherTeams.sort() } : {},
965
1023
  personal,
966
1024
  flows,
967
1025
  entries: sorted(staged.entries),
@@ -995,7 +1053,7 @@ async function stageFootprint(ctx, dir, h) {
995
1053
  const tasksText = await read(join6(dir, "tasks.md"));
996
1054
  if (!files.length && !featureText && !tasksText)
997
1055
  return out;
998
- const { get, isSystem, profileSystems, ref } = h;
1056
+ const { get, isSystem, outsideKind, ref } = h;
999
1057
  const specs = await specIndex(ctx);
1000
1058
  const catalogRepos = /* @__PURE__ */ new Set();
1001
1059
  for (const r of ctx.records)
@@ -1009,9 +1067,9 @@ async function stageFootprint(ctx, dir, h) {
1009
1067
  const outsideOf = async (id) => {
1010
1068
  const c = get("Component", id);
1011
1069
  if (c)
1012
- return get("System", c.system)?.boundary === "vendor";
1070
+ return outsideKind(c.system) !== void 0;
1013
1071
  if (isSystem(id))
1014
- return (get("System", id)?.boundary ?? profileSystems.get(id)?.boundary) === "vendor";
1072
+ return outsideKind(id) !== void 0;
1015
1073
  if (catalogRepos.has(id))
1016
1074
  return false;
1017
1075
  return !await specs.hasComponent(id) && !await specs.hasRepo(id);
@@ -1098,7 +1156,7 @@ async function stageFootprint(ctx, dir, h) {
1098
1156
  const was = op !== "add" && n ? today?.flow.steps[n - 1] : void 0;
1099
1157
  const kind = str(s.kind) ?? was?.kind ?? "step";
1100
1158
  const what = str(s.what) ?? was?.what ?? "";
1101
- const live = op === "add" || op === "change";
1159
+ const live2 = op === "add" || op === "change";
1102
1160
  const store = obj(s.store);
1103
1161
  const table = str(store?.table) ?? was?.store?.table;
1104
1162
  if (table) {
@@ -1111,7 +1169,7 @@ async function stageFootprint(ctx, dir, h) {
1111
1169
  if (typeof v === "string")
1112
1170
  classification[c] = v;
1113
1171
  out.stores.push({ table, columns: cols, classification, kind, cite });
1114
- if (live) {
1172
+ if (live2) {
1115
1173
  const hit = [...wordsOf(table), ...cols.flatMap(wordsOf)].filter((w) => personalWords.has(w));
1116
1174
  if (hit.length)
1117
1175
  out.signals.push({
@@ -1139,7 +1197,7 @@ async function stageFootprint(ctx, dir, h) {
1139
1197
  if (entry)
1140
1198
  o.entries.add(entry);
1141
1199
  outside.set(target, o);
1142
- if (live)
1200
+ if (live2)
1143
1201
  out.signals.push({
1144
1202
  factor: "outside-call",
1145
1203
  detail: `${target}${entry ? ` (${entry}${to?.new === true ? ", new" : ""})` : ""}`,
@@ -1163,7 +1221,7 @@ async function stageFootprint(ctx, dir, h) {
1163
1221
  out.calls.add(iface.id);
1164
1222
  }
1165
1223
  }
1166
- if (!live)
1224
+ if (!live2)
1167
1225
  continue;
1168
1226
  const fields = obj(s.fields) ?? {};
1169
1227
  const names = FIELD_LISTS.flatMap((k) => strs(fields[k]));
@@ -1291,9 +1349,14 @@ function scoreRisk(input, policy) {
1291
1349
  add("systems", `${f.systems.length} systems touched (${f.systems.join(", ")})`, (f.systems.length - 1) * w.extraSystem);
1292
1350
  for (const s of f.dataStores)
1293
1351
  add("data-store", `data store ${s}`, w.dataStore);
1294
- const crossed = f.vendors.filter((v) => !(f.outside ?? []).some((o) => o.id === v));
1295
- if (crossed.length > 0)
1296
- add("vendor", `crosses the vendor boundary (${crossed.join(", ")})`, w.vendor);
1352
+ const notCalled = (v) => !(f.outside ?? []).some((o) => o.id === v);
1353
+ const crossed = f.vendors.filter(notCalled);
1354
+ const teams = (f.otherTeams ?? []).filter(notCalled);
1355
+ if (crossed.length > 0 || teams.length > 0)
1356
+ add("vendor", [
1357
+ ...crossed.length ? [`crosses the vendor boundary (${crossed.join(", ")})`] : [],
1358
+ ...teams.length ? [`calls another team's system (${teams.join(", ")})`] : []
1359
+ ].join("; "), w.vendor);
1297
1360
  for (const i of f.interfaces) {
1298
1361
  if (i.status === "planned-modify")
1299
1362
  add("interface", `interface ${i.id} planned-modify`, w.interfaceModify);
@@ -1414,7 +1477,7 @@ function handoffName(type, phase) {
1414
1477
  case "plan":
1415
1478
  return "Plan ready";
1416
1479
  case "build":
1417
- return "Raise PR";
1480
+ return "Build ready";
1418
1481
  case "qa":
1419
1482
  return "QA sign-off";
1420
1483
  case "release":
@@ -1470,8 +1533,15 @@ async function checkStage(root, change, step) {
1470
1533
  break;
1471
1534
  case "stopped":
1472
1535
  return reason(`The ${step.stage} stage stopped and isn't done: see what stopped it, then run ${run} again.`);
1473
- case "questions":
1474
- return reason(`The ${step.stage} stage waits for a person to answer its questions: answer them (atticus questions), then run ${run} again.`);
1536
+ case "questions": {
1537
+ const asked = Array.isArray(loop?.questions) ? loop.questions.filter((q) => typeof q === "string") : [];
1538
+ if (asked.length) {
1539
+ const open = new Set((await listQuestions(root, { state: "open" }).catch(() => ({ questions: [] }))).questions.map((q) => q.id));
1540
+ if (!asked.some((q) => open.has(q)))
1541
+ return reason(`The ${step.stage} stage's questions are answered: run it again so it reads the answers (${run}).`);
1542
+ }
1543
+ return reason(`The ${step.stage} stage waits for a person to answer its questions: answer them on Questions (or atticus questions), then run the stage again (${run}).`);
1544
+ }
1475
1545
  case "waiting":
1476
1546
  return reason(`The ${step.stage} stage is with your assistant${typeof loop?.run === "string" ? ` (run ${loop.run})` : ""}: finish the hand-off, then run ${run} again.`);
1477
1547
  case "drafting":
@@ -1480,7 +1550,8 @@ async function checkStage(root, change, step) {
1480
1550
  return reason(`${step.loop} doesn't read as a stage's loop state: run ${run} again.`);
1481
1551
  }
1482
1552
  const out = [];
1483
- for (const w of step.writes.filter((x) => !x.endsWith("/") && !x.startsWith("stage/")))
1553
+ const artifactFile = new Set(Object.values(FILE));
1554
+ for (const w of step.writes.filter((x) => !x.endsWith("/") && !x.startsWith("stage/") && !isWritePattern(x) && !artifactFile.has(x)))
1484
1555
  if (!await readFile6(join7(dir, ...w.split("/"))).then(() => true, () => false))
1485
1556
  out.push(...reason(`${w} is missing, though the ${step.stage} stage is done: run ${run} again.`));
1486
1557
  return out;
@@ -1490,7 +1561,10 @@ async function stageFiles(dir, step) {
1490
1561
  for (const w of [...step.writes, step.loop]) {
1491
1562
  if (w.endsWith("/"))
1492
1563
  out.push(...await walk(dir, w.slice(0, -1)));
1493
- else if (await readFile6(join7(dir, ...w.split("/"))).then(() => true, () => false))
1564
+ else if (isWritePattern(w)) {
1565
+ const parent = w.split("/").slice(0, -1).join("/");
1566
+ out.push(...(await walk(dir, parent)).filter((p) => writeMatches(w, p)));
1567
+ } else if (await readFile6(join7(dir, ...w.split("/"))).then(() => true, () => false))
1494
1568
  out.push(w);
1495
1569
  }
1496
1570
  return [...new Set(out)].sort();
@@ -1582,7 +1656,7 @@ async function checkFindings(root, change, gates) {
1582
1656
  if (l.state === "waiting")
1583
1657
  reasons.push({
1584
1658
  code: "lens-waiting",
1585
- message: `The ${l.lens ?? "AI"} lens on the ${gate} gate (round ${round2}) is waiting for its hand-off answer${l.runId ? ` (atticus handoff done ${l.runId})` : ""}.`
1659
+ message: `The ${l.lens ?? "AI"} lens on the ${gate} gate (round ${round2}) is waiting for its hand-off answer${l.runId ? ` (Done on run ${l.runId} in Model runs, or atticus handoff done ${l.runId})` : ""}.`
1586
1660
  });
1587
1661
  else if (l.state === "failed")
1588
1662
  reasons.push({
@@ -1609,6 +1683,16 @@ async function checkFindings(root, change, gates) {
1609
1683
  continue;
1610
1684
  }
1611
1685
  const x = r.data;
1686
+ if (x.severity === "gap" && x.state === "asked") {
1687
+ const q = await readQuestion(root, askedGapQuestionId(change.id, x)).catch(() => void 0);
1688
+ if (q?.state === "open")
1689
+ reasons.push({
1690
+ code: "open-gap",
1691
+ finding: x.id,
1692
+ message: `Knowledge gap ${x.id} on the ${gate} gate (round ${round2}) was asked of ${q.askedOf ?? x.resolution?.askedOf ?? "its owner"} and waits for the answer (owner question ${q.id}, in Questions, Owner questions): ${x.summary}`
1693
+ });
1694
+ continue;
1695
+ }
1612
1696
  const byAssistant = x.severity === "blocking" && isAssistantResolution(x.resolution);
1613
1697
  if (x.state !== "open" && !byAssistant)
1614
1698
  continue;
@@ -1728,8 +1812,353 @@ function prApprovers(handoff2, approvers) {
1728
1812
  return [...new Set(approvers.map(strip))].filter((p) => p !== raiser).sort();
1729
1813
  }
1730
1814
 
1815
+ // packages/changes/dist/assess.js
1816
+ import { readFile as readFile9 } from "node:fs/promises";
1817
+ import { join as join10 } from "node:path";
1818
+
1819
+ // packages/changes/dist/release-prs.js
1820
+ var import_yaml7 = __toESM(require_dist(), 1);
1821
+ import { readFile as readFile8, writeFile } from "node:fs/promises";
1822
+ import { existsSync } from "node:fs";
1823
+ import { join as join9 } from "node:path";
1824
+ var RELEASE_PR_STATES = [
1825
+ "qa",
1826
+ "release-ready",
1827
+ "released",
1828
+ "post-release-review"
1829
+ ];
1830
+ var nowIso = () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
1831
+ var RELEASE_PRS_FILE = "release-prs.yaml";
1832
+ var NothingFile = external_exports.strictObject({
1833
+ schemaVersion: external_exports.literal(1),
1834
+ nothingToRelease: external_exports.array(external_exports.strictObject({
1835
+ repo: external_exports.string().min(1),
1836
+ /** The change branch's commit when it was found to hold nothing beyond the default branch. */
1837
+ commit: external_exports.string().regex(/^[0-9a-f]{7,64}$/).optional(),
1838
+ at: external_exports.string().min(1),
1839
+ by: external_exports.string().min(1)
1840
+ }))
1841
+ });
1842
+ async function readNothing(root, id) {
1843
+ const text = await readFile8(join9(changeFolder(root, id), RELEASE_PRS_FILE), "utf8").catch(() => void 0);
1844
+ if (text === void 0)
1845
+ return [];
1846
+ try {
1847
+ const r = NothingFile.safeParse((0, import_yaml7.parse)(text));
1848
+ return r.success ? r.data.nothingToRelease : [];
1849
+ } catch {
1850
+ return [];
1851
+ }
1852
+ }
1853
+ async function nothingToRelease(root, id, o = {}) {
1854
+ const entries = await readNothing(root, id);
1855
+ const out = /* @__PURE__ */ new Set();
1856
+ if (!entries.length)
1857
+ return out;
1858
+ const cr = await readChangeRepos(root, id).catch(() => void 0);
1859
+ const git = o.git ?? createGit({ timeoutMs: 3e4 });
1860
+ const remote = o.remote ?? "origin";
1861
+ for (const x of entries) {
1862
+ const r = cr?.repos.find((y) => y.id === x.repo);
1863
+ if (r && await moved(r, x.commit))
1864
+ continue;
1865
+ out.add(x.repo);
1866
+ }
1867
+ return out;
1868
+ async function moved(r, commit) {
1869
+ const dirs = [join9(root, r.worktree)];
1870
+ try {
1871
+ dirs.push(baseClonePath(root, r.id));
1872
+ } catch {
1873
+ }
1874
+ const dir = dirs.find((d) => existsSync(d));
1875
+ if (!dir)
1876
+ return false;
1877
+ const ref = `refs/heads/${r.branch}`;
1878
+ const here = await git(["rev-parse", "--verify", "--quiet", `${ref}^{commit}`], dir).then((x) => x.stdout.trim() || void 0, () => void 0);
1879
+ const onHost = await git(["ls-remote", "--heads", remote, ref], dir).then((x) => x.stdout.trim().split(/\s+/)[0] || void 0, () => void 0);
1880
+ const at = (sha) => sha !== void 0 && (commit === void 0 || !sameSha(sha, commit));
1881
+ return at(here) || at(onHost);
1882
+ }
1883
+ }
1884
+ var sameSha = (a, b) => a.startsWith(b) || b.startsWith(a);
1885
+ var firstLine = (e) => (e instanceof Error ? e.message : String(e)).split("\n")[0].replace(/\.$/, "");
1886
+ var prOf = (pr, now) => ({
1887
+ url: pr.url,
1888
+ number: pr.number,
1889
+ state: pr.state,
1890
+ updatedAt: now
1891
+ });
1892
+ var live = (pr) => !!pr && (pr.state === "open" || pr.state === "draft" || pr.state === "merged");
1893
+ function releaseBody(id, title, branch, base) {
1894
+ return [
1895
+ `Release of change ${id} (${title}): merges ${branch}, with the task PRs merged into it, into ${base}.`,
1896
+ "",
1897
+ "The go/no-go hand-off is decided over this PR. A person reviews and merges it on the Git host; Atticus never pushes to or merges into the default branch, and never deploys."
1898
+ ].join("\n");
1899
+ }
1900
+ async function raiseReleasePrs(root, id, o) {
1901
+ requireApproval("changes", o.approval);
1902
+ const change = await changeStore(root).read(id);
1903
+ if (!RELEASE_PR_STATES.includes(change.state))
1904
+ throw new ChangeError("refused", `${id} is ${change.state}; the release PRs are raised once the build is done (in QA or at go/no-go), so go/no-go is decided over the PRs that will ship.`);
1905
+ const cr = await readChangeRepos(root, id);
1906
+ if (!cr?.repos.length)
1907
+ throw new ChangeError("refused", `${id} has no code repos (repos.yaml), so there is no release PR to raise: open its workspace first (atticus workspace open ${id} <repo>).`);
1908
+ const now = o.now ?? nowIso();
1909
+ const git = o.git ?? createGit();
1910
+ const remote = o.remote ?? "origin";
1911
+ const expected = changeBranch(id);
1912
+ const rows = [];
1913
+ let changed2 = false;
1914
+ const repos = cr.repos.map((r) => ({ ...r }));
1915
+ const before = await readNothing(root, id);
1916
+ const nothing = new Map(before.map((x) => [x.repo, x]));
1917
+ for (const r of repos) {
1918
+ if (!live(r.pr))
1919
+ nothing.delete(r.id);
1920
+ if (live(r.pr)) {
1921
+ rows.push({
1922
+ repo: r.id,
1923
+ outcome: "kept",
1924
+ pr: r.pr,
1925
+ message: `${r.id}: release PR #${r.pr.number} is already ${r.pr.state} (${r.pr.url}).`
1926
+ });
1927
+ continue;
1928
+ }
1929
+ if (r.branch !== expected) {
1930
+ rows.push({
1931
+ repo: r.id,
1932
+ outcome: "refused",
1933
+ message: `${r.id}: its branch is ${r.branch}, not ${expected}; Atticus raises a release PR only from the change branch.`
1934
+ });
1935
+ continue;
1936
+ }
1937
+ let base;
1938
+ try {
1939
+ const info = await o.gitHost.getRepo(r.id);
1940
+ base = info.defaultBranch;
1941
+ if (info.protectedBranches.includes(r.branch)) {
1942
+ rows.push({
1943
+ repo: r.id,
1944
+ outcome: "refused",
1945
+ message: `${r.id}: ${r.branch} is protected; Atticus never pushes to or raises a PR from a protected branch.`
1946
+ });
1947
+ continue;
1948
+ }
1949
+ if (r.branch === base) {
1950
+ rows.push({
1951
+ repo: r.id,
1952
+ outcome: "refused",
1953
+ message: `${r.id}: ${r.branch} is the default branch; a release PR goes from the change branch into it.`
1954
+ });
1955
+ continue;
1956
+ }
1957
+ } catch (e) {
1958
+ if (!(e instanceof AdapterError))
1959
+ throw e;
1960
+ rows.push({
1961
+ repo: r.id,
1962
+ outcome: "failed",
1963
+ message: `${r.id}: the Git host didn't answer (${firstLine(e)}); raise the release PRs again once it does.`
1964
+ });
1965
+ continue;
1966
+ }
1967
+ const notes = [];
1968
+ const dir = join9(root, r.worktree);
1969
+ if (existsSync(dir)) {
1970
+ const onHost = await git(["ls-remote", "--heads", remote, `refs/heads/${r.branch}`], dir).then((x) => x.stdout.trim() ? "yes" : "no").catch((e) => {
1971
+ notes.push(`Atticus couldn't reach ${remote} (${firstLine(e)}): push ${r.branch} yourself if the host lacks it.`);
1972
+ return "unknown";
1973
+ });
1974
+ if (onHost === "no") {
1975
+ const from = r.baseSha ?? `${remote}/${base}`;
1976
+ const ahead = await git(["rev-list", "--count", `${from}..refs/heads/${r.branch}`], dir).then((x) => Number(x.stdout.trim()) || 0).catch(() => -1);
1977
+ if (ahead === 0) {
1978
+ const head = await git(["rev-parse", `refs/heads/${r.branch}`], dir).then((x) => x.stdout.trim()).catch(() => "");
1979
+ nothing.set(r.id, {
1980
+ repo: r.id,
1981
+ .../^[0-9a-f]{7,64}$/.test(head) ? { commit: head } : {},
1982
+ at: now,
1983
+ by: o.approval.by
1984
+ });
1985
+ rows.push({
1986
+ repo: r.id,
1987
+ outcome: "nothing",
1988
+ message: `${r.id}: ${r.branch} has no commits beyond ${base} and isn't on the Git host: there is nothing to release from it, so go/no-go doesn't wait for a PR.`
1989
+ });
1990
+ continue;
1991
+ }
1992
+ try {
1993
+ await git(["push", "-q", remote, `refs/heads/${r.branch}:refs/heads/${r.branch}`], dir);
1994
+ notes.push(`Pushed ${r.branch}, which the Git host didn't have.`);
1995
+ } catch (e) {
1996
+ rows.push({
1997
+ repo: r.id,
1998
+ outcome: "failed",
1999
+ message: `${r.id}: ${r.branch} isn't on the Git host and couldn't be pushed (${firstLine(e)}): push it yourself, then raise the release PRs again.`
2000
+ });
2001
+ continue;
2002
+ }
2003
+ }
2004
+ }
2005
+ try {
2006
+ const pr = await o.gitHost.createPullRequest({
2007
+ repo: r.id,
2008
+ head: r.branch,
2009
+ base,
2010
+ title: `${change.title} (release of ${id})`,
2011
+ body: releaseBody(id, change.title, r.branch, base)
2012
+ }, o.approval);
2013
+ r.pr = prOf(pr, now);
2014
+ changed2 = true;
2015
+ rows.push({
2016
+ repo: r.id,
2017
+ outcome: "raised",
2018
+ pr: r.pr,
2019
+ message: [
2020
+ pr.url.startsWith("manual:") ? `${r.id}: the release PR is a step for you (Home \u203A Manual steps): open it on the Git host from ${r.branch} into ${base}, then record its link.` : `${r.id}: release PR #${pr.number} from ${r.branch} into ${base} is open: ${pr.url}`,
2021
+ ...notes
2022
+ ].join(" ")
2023
+ });
2024
+ } catch (e) {
2025
+ if (!(e instanceof AdapterError))
2026
+ throw e;
2027
+ if (e.code === "conflict") {
2028
+ const open = (await o.gitHost.listPullRequests(r.id, { head: r.branch }).catch(() => [])).filter((p) => p.base === base && (p.state === "open" || p.state === "draft")).at(0);
2029
+ if (open) {
2030
+ r.pr = prOf(open, now);
2031
+ changed2 = true;
2032
+ rows.push({
2033
+ repo: r.id,
2034
+ outcome: "kept",
2035
+ pr: r.pr,
2036
+ message: `${r.id}: release PR #${open.number} was already open on the Git host; recorded it (${open.url}).`
2037
+ });
2038
+ continue;
2039
+ }
2040
+ }
2041
+ rows.push({
2042
+ repo: r.id,
2043
+ outcome: "failed",
2044
+ message: `${r.id}: the release PR wasn't raised (${firstLine(e)}); fix that and raise the release PRs again.`
2045
+ });
2046
+ }
2047
+ }
2048
+ if (changed2)
2049
+ await writeChangeRepos(root, { ...cr, repos });
2050
+ const after = [...nothing.values()].sort((a, b) => a.repo < b.repo ? -1 : 1);
2051
+ if (JSON.stringify(after) !== JSON.stringify(before))
2052
+ await writeFile(join9(changeFolder(root, id), RELEASE_PRS_FILE), toCanonicalYaml(NothingFile.parse({ schemaVersion: 1, nothingToRelease: after })), "utf8");
2053
+ return { change: id, rows };
2054
+ }
2055
+ async function syncReleasePrs(root, id, o) {
2056
+ const cr = await readChangeRepos(root, id);
2057
+ const rows = [];
2058
+ if (!cr)
2059
+ return { change: id, rows };
2060
+ const now = o.now ?? nowIso();
2061
+ let changed2 = false;
2062
+ const repos = cr.repos.map((r) => ({ ...r }));
2063
+ for (const r of repos) {
2064
+ if (!r.pr)
2065
+ continue;
2066
+ let pr;
2067
+ try {
2068
+ pr = await o.gitHost.getPullRequest(r.id, r.pr.number);
2069
+ if (pr.head !== r.branch) {
2070
+ const mine = await o.gitHost.listPullRequests(r.id, { head: r.branch });
2071
+ const own = mine.find((x) => x.url === r.pr.url) ?? (mine.length === 1 ? mine[0] : void 0);
2072
+ if (!own) {
2073
+ rows.push({
2074
+ repo: r.id,
2075
+ outcome: "failed",
2076
+ pr: r.pr,
2077
+ message: `${r.id}: PR #${r.pr.number} on the Git host is from ${pr.head}, not ${r.branch}: check the release PR's number on the Git host (on a manual host, on its step), then sync again.`
2078
+ });
2079
+ continue;
2080
+ }
2081
+ pr = own;
2082
+ }
2083
+ } catch (e) {
2084
+ if (!(e instanceof AdapterError))
2085
+ throw e;
2086
+ rows.push({
2087
+ repo: r.id,
2088
+ outcome: "failed",
2089
+ pr: r.pr,
2090
+ message: `${r.id}: release PR #${r.pr.number} couldn't be read (${firstLine(e)}).`
2091
+ });
2092
+ continue;
2093
+ }
2094
+ const build = buildOf(pr);
2095
+ if (pr.state !== r.pr.state || pr.url !== r.pr.url || pr.number !== r.pr.number) {
2096
+ r.pr = prOf(pr, now);
2097
+ changed2 = true;
2098
+ }
2099
+ if (build && build !== r.build.status) {
2100
+ r.build = { ...r.build, status: build, at: now };
2101
+ changed2 = true;
2102
+ }
2103
+ rows.push({
2104
+ repo: r.id,
2105
+ outcome: "kept",
2106
+ pr: r.pr,
2107
+ message: `${r.id}: release PR #${r.pr.number} is ${r.pr.state}${build ? `, checks ${build}` : ""} (${r.pr.url}).`
2108
+ });
2109
+ }
2110
+ if (changed2)
2111
+ await writeChangeRepos(root, { ...cr, repos });
2112
+ return { change: id, rows };
2113
+ }
2114
+ function buildOf(pr) {
2115
+ if (!pr.checks.length)
2116
+ return void 0;
2117
+ if (pr.checks.some((c) => c.status === "failed"))
2118
+ return "failed";
2119
+ if (pr.checks.some((c) => c.status === "pending"))
2120
+ return "running";
2121
+ return "passed";
2122
+ }
2123
+ async function requireMergedPrs(root) {
2124
+ const text = await readFile8(join9(orgPackDirOf(root), "process", "ci.yaml"), "utf8").catch(() => void 0);
2125
+ if (text === void 0)
2126
+ return true;
2127
+ try {
2128
+ const v = (0, import_yaml7.parse)(text)?.archive?.requireMergedPrs;
2129
+ return v !== false;
2130
+ } catch {
2131
+ return true;
2132
+ }
2133
+ }
2134
+ async function releasePrReasons(root, id, o = {}) {
2135
+ const cr = await readChangeRepos(root, id).catch(() => void 0);
2136
+ if (!cr?.repos.length)
2137
+ return [];
2138
+ const merged = await requireMergedPrs(root);
2139
+ const nothing = await nothingToRelease(root, id, o);
2140
+ const out = [];
2141
+ for (const r of cr.repos) {
2142
+ if (!r.pr && nothing.has(r.id))
2143
+ continue;
2144
+ if (!r.pr || r.pr.state === "closed")
2145
+ out.push({
2146
+ code: "release-pr-missing",
2147
+ message: `${r.id}: no release PR from ${r.branch} into its default branch${r.pr ? ` (PR #${r.pr.number} was closed)` : ""}: raise the release PRs (Release \u203A Raise the release PRs, or atticus change release-prs ${id} --approve).`
2148
+ });
2149
+ else if (merged && r.pr.state !== "merged")
2150
+ out.push({
2151
+ code: "release-pr-not-merged",
2152
+ message: `${r.id}: release PR #${r.pr.number} is ${r.pr.state}: a person merges it on the Git host (${r.pr.url}), then Sync records it.`
2153
+ });
2154
+ }
2155
+ return out;
2156
+ }
2157
+
1731
2158
  // packages/changes/dist/assess.js
1732
2159
  var liveHandoff = (c) => c.handoffs.find((h) => h.state === "open" || h.state === "changes-requested");
2160
+ var isManualPrUrl = (url) => /^manual:\/\//i.test(url.trim());
2161
+ var manualHandoffWords = (phase) => `The ${phase} hand-off's PR isn't open yet: your Git host is manual, so opening it is a step waiting on you (atticus todo, or Home \u203A Manual steps in the app). Open it on the Git host as its step says, record its link, then sync; merging it approves the gate.`;
1733
2162
  async function assess(ctx, change) {
1734
2163
  const type = changeTypeOf(ctx, change.type);
1735
2164
  const footprint = await changeFootprint(ctx, change);
@@ -1770,29 +2199,32 @@ async function assess(ctx, change) {
1770
2199
  ...stageReasons,
1771
2200
  ...await checkArtifacts(ctx.root, change, due),
1772
2201
  ...await checkFindings(ctx.root, change, phaseFindingGates(ctx, phase)),
1773
- ...stageReasons.length ? [] : await checkMandatoryRounds(ctx.root, change, phaseGates(ctx, phase).filter((g) => mandatoryGates(ctx).includes(g)))
2202
+ ...stageReasons.length ? [] : await checkMandatoryRounds(ctx.root, change, phaseGates(ctx, phase).filter((g) => mandatoryGates(ctx).includes(g))),
2203
+ // ADR 0184: go/no-go is decided over the release PRs, each repo's change branch into its
2204
+ // default branch, merged (unless the org pack's ci.yaml says archive.requireMergedPrs: false).
2205
+ ...phase === "release" ? await releasePrReasons(ctx.root, change.id) : []
1774
2206
  ];
1775
2207
  const next = change.state === "returned" ? phaseState(phase) : nextState(type, phase);
1776
- const live = change.state === "returned" ? void 0 : liveHandoff(change);
2208
+ const live2 = change.state === "returned" ? void 0 : liveHandoff(change);
1777
2209
  const reasons = [...blockers];
1778
- if (!live) {
2210
+ if (!live2) {
1779
2211
  reasons.push({
1780
2212
  code: "no-handoff",
1781
2213
  message: `No hand-off PR yet: raise "${handoffName(type, phase)}" when the ${phase} work is done.`
1782
2214
  });
1783
- } else if (live.state === "changes-requested") {
2215
+ } else if (live2.state === "changes-requested") {
1784
2216
  reasons.push({
1785
2217
  code: "changes-requested",
1786
- message: `Changes were requested on PR #${live.pr.number}; update the artifacts and hand off again.`
2218
+ message: `Changes were requested on PR #${live2.pr.number}; update the artifacts and hand off again.`
1787
2219
  });
1788
2220
  } else {
1789
- const approved = prApprovers(live, live.approvals.map((a) => a.by));
2221
+ const approved = prApprovers(live2, live2.approvals.map((a) => a.by));
1790
2222
  reasons.push({
1791
2223
  code: "pr-not-merged",
1792
- message: approved.length ? `PR #${live.pr.number} is approved (${approved.join(", ")}) but not merged yet: ${live.pr.url}` : `Waiting for PR #${live.pr.number} to be approved and merged on the Git host: ${live.pr.url}`
2224
+ message: isManualPrUrl(live2.pr.url) ? manualHandoffWords(phase) : approved.length ? `PR #${live2.pr.number} is approved (${approved.join(", ")}) but not merged yet: ${live2.pr.url}` : `Waiting for PR #${live2.pr.number} to be approved and merged on the Git host: ${live2.pr.url}`
1793
2225
  });
1794
2226
  }
1795
- return { ...base, phase, next, ...live ? { handoff: live } : {}, blockers, reasons };
2227
+ return { ...base, phase, next, ...live2 ? { handoff: live2 } : {}, blockers, reasons };
1796
2228
  }
1797
2229
  async function canAdvance(change, root) {
1798
2230
  const ctx = await loadChangeContext(root);
@@ -1809,9 +2241,15 @@ async function canAdvance(change, root) {
1809
2241
  async function changeReport(root, id) {
1810
2242
  const ctx = await loadChangeContext(root);
1811
2243
  const a = await assess(ctx, await changeStore(root).read(id));
1812
- return { ...a, nextStep: nextStep(a), problems: ctx.problems };
2244
+ const tasks = a.phase === "build" ? await taskTicks(root, id) : void 0;
2245
+ return { ...a, nextStep: nextStep(a, tasks), problems: ctx.problems };
2246
+ }
2247
+ async function taskTicks(root, id) {
2248
+ const text = await readFile9(join10(changeFolder(root, id), "tasks.md"), "utf8").catch(() => "");
2249
+ const boxes = [...text.matchAll(/^\s*[-*]\s+\[([ xX])\]/gm)].map((m) => m[1] !== " ");
2250
+ return boxes.length ? { done: boxes.filter(Boolean).length, total: boxes.length } : void 0;
1813
2251
  }
1814
- function nextStep(a) {
2252
+ function nextStep(a, tasks) {
1815
2253
  switch (a.state) {
1816
2254
  case "draft":
1817
2255
  return "Create the change with atticus change new.";
@@ -1825,25 +2263,34 @@ function nextStep(a) {
1825
2263
  return `Revise the work, then hand off ${a.phase} again.`;
1826
2264
  }
1827
2265
  const name = handoffName(a.type, a.phase);
1828
- if (!a.handoff)
2266
+ if (!a.handoff) {
2267
+ if (!a.blockers.length && tasks && tasks.done < tasks.total)
2268
+ return `Finish the ${a.phase} work (${tasks.done} of ${tasks.total} tasks ticked in tasks.md), then raise "${name}".`;
1829
2269
  return a.blockers.length ? `Finish the ${a.phase} work, then raise "${name}".` : `Ready to raise "${name}".`;
2270
+ }
1830
2271
  if (a.handoff.state === "changes-requested")
1831
2272
  return `Update the artifacts and hand off again on PR #${a.handoff.pr.number}.`;
2273
+ if (isManualPrUrl(a.handoff.pr.url))
2274
+ return manualHandoffWords(a.handoff.phase);
1832
2275
  return `Merge PR #${a.handoff.pr.number} once it is approved (${a.handoff.pr.url}); syncing then moves the change on.`;
1833
2276
  }
1834
2277
 
1835
2278
  // packages/changes/dist/new-change.js
1836
- var import_yaml7 = __toESM(require_dist(), 1);
1837
- import { mkdir, readFile as readFile8, writeFile } from "node:fs/promises";
1838
- import { join as join9 } from "node:path";
1839
- var nowIso = () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
2279
+ var import_yaml8 = __toESM(require_dist(), 1);
2280
+ import { mkdir, readFile as readFile10, writeFile as writeFile2 } from "node:fs/promises";
2281
+ import { join as join11 } from "node:path";
2282
+ var nowIso2 = () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
1840
2283
  var strip2 = (h) => h.startsWith("@") ? h.slice(1) : h;
2284
+ function windowsUnsafeName(id) {
2285
+ return /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|$)/i.test(id) || /[. ]$/.test(id);
2286
+ }
1841
2287
  function slugify(title) {
1842
- return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80).replace(/-+$/, "");
2288
+ const id = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80).replace(/-+$/, "");
2289
+ return windowsUnsafeName(id) ? `${id}-change` : id;
1843
2290
  }
1844
2291
  async function exists(path) {
1845
2292
  try {
1846
- await readFile8(path);
2293
+ await readFile10(path);
1847
2294
  return true;
1848
2295
  } catch {
1849
2296
  return false;
@@ -1851,7 +2298,7 @@ async function exists(path) {
1851
2298
  }
1852
2299
  async function defaultSchema(root) {
1853
2300
  try {
1854
- const cfg = (0, import_yaml7.parse)(await readFile8(join9(openspecDirOf(root), "config.yaml"), "utf8"));
2301
+ const cfg = (0, import_yaml8.parse)(await readFile10(join11(openspecDirOf(root), "config.yaml"), "utf8"));
1855
2302
  if (cfg && typeof cfg.schema === "string")
1856
2303
  return cfg.schema;
1857
2304
  } catch {
@@ -1864,26 +2311,28 @@ async function newChange(root, opts) {
1864
2311
  const id = opts.id ?? slugify(opts.title);
1865
2312
  if (!RecordId.safeParse(id).success)
1866
2313
  throw new ChangeError("invalid", `"${id}" can't be a change id; use a lowercase slug such as add-transfer-limit.`);
2314
+ if (windowsUnsafeName(id))
2315
+ throw new ChangeError("invalid", `"${id}" can't be a change id: Windows can't open a folder named that (con, nul, com1 and the like, or a name ending in a dot). Pick another, such as ${id.replace(/[. ]+$/, "").replace(/^([^.]+)/, "$1-change")}.`);
1867
2316
  const template = pickTemplate(ctx.templates, type.id, opts.template);
1868
2317
  if (!template)
1869
2318
  throw new ChangeError("invalid", `Unknown change template ${opts.template}. Use one of: ${ctx.templates.map((t) => t.id).join(", ")}.`);
1870
2319
  const store = changeStore(root);
1871
2320
  if (await store.tryRead(id).catch(() => true))
1872
2321
  throw new ChangeError("conflict", `Change ${id} already exists.`);
1873
- const now = opts.now ?? nowIso();
2322
+ const now = opts.now ?? nowIso2();
1874
2323
  const by = strip2(opts.by);
1875
2324
  const dir = changeFolder(root, id);
1876
- if (opts.openspec && !await exists(join9(dir, ".openspec.yaml"))) {
2325
+ if (opts.openspec && !await exists(join11(dir, ".openspec.yaml"))) {
1877
2326
  await opts.openspec.newChange(id, { description: opts.title });
1878
2327
  } else {
1879
2328
  await mkdir(dir, { recursive: true });
1880
- if (!await exists(join9(dir, ".openspec.yaml")))
1881
- await writeFile(join9(dir, ".openspec.yaml"), `schema: ${await defaultSchema(root)}
2329
+ if (!await exists(join11(dir, ".openspec.yaml")))
2330
+ await writeFile2(join11(dir, ".openspec.yaml"), `schema: ${await defaultSchema(root)}
1882
2331
  created: ${now.slice(0, 10)}
1883
2332
  `, "utf8");
1884
2333
  }
1885
- if (!await exists(join9(dir, "brief.md")))
1886
- await writeFileAtomic(join9(dir, "brief.md"), renderTemplate(template, {
2334
+ if (!await exists(join11(dir, "brief.md")))
2335
+ await writeFileAtomic(join11(dir, "brief.md"), renderTemplate(template, {
1887
2336
  title: opts.title,
1888
2337
  id,
1889
2338
  type: type.id,
@@ -1922,13 +2371,23 @@ created: ${now.slice(0, 10)}
1922
2371
 
1923
2372
  // packages/changes/dist/publish.js
1924
2373
  import { rm } from "node:fs/promises";
1925
- import { join as join10 } from "node:path";
2374
+ import { randomUUID } from "node:crypto";
2375
+ import { join as join12, resolve } from "node:path";
1926
2376
  function gitHandoffPublisher(opts = {}) {
1927
2377
  const remote = opts.remote ?? "origin";
1928
2378
  const env = opts.env ?? process.env;
1929
2379
  return {
1930
- async publish({ root, branch, base, files, message, approval }) {
1931
- requireApproval("git", approval);
2380
+ async publish(input) {
2381
+ requireApproval("git", input.approval);
2382
+ const key = resolve(input.root);
2383
+ const before = queue.get(key) ?? Promise.resolve();
2384
+ const mine = before.catch(() => void 0).then(() => publishOne(input));
2385
+ queue.set(key, mine.catch(() => void 0));
2386
+ return mine;
2387
+ }
2388
+ };
2389
+ async function publishOne({ root, branch, base, files, message }) {
2390
+ {
1932
2391
  const git = createGit({ env });
1933
2392
  const out = async (args) => (await git(args, root)).stdout.trim();
1934
2393
  const tryOut = (args) => out(args).catch(() => void 0);
@@ -1946,7 +2405,7 @@ function gitHandoffPublisher(opts = {}) {
1946
2405
  "--quiet",
1947
2406
  `refs/remotes/${remote}/${base}^{commit}`
1948
2407
  ]) ?? await out(["rev-parse", "--verify", `refs/heads/${base}^{commit}`]);
1949
- const index = join10(gitDir, "atticus-handoff-index");
2408
+ const index = join12(gitDir, `atticus-handoff-index-${process.pid}-${randomUUID()}`);
1950
2409
  const staged = createGit({ env: { ...env, GIT_INDEX_FILE: index } });
1951
2410
  try {
1952
2411
  await staged(["read-tree", start], root);
@@ -1962,17 +2421,18 @@ function gitHandoffPublisher(opts = {}) {
1962
2421
  await rm(index, { force: true });
1963
2422
  }
1964
2423
  }
1965
- };
2424
+ }
1966
2425
  }
2426
+ var queue = /* @__PURE__ */ new Map();
1967
2427
 
1968
2428
  // packages/changes/dist/handoff.js
1969
- import { readdir as readdir6, readFile as readFile10 } from "node:fs/promises";
1970
- import { join as join12 } from "node:path";
2429
+ import { readdir as readdir6, readFile as readFile12 } from "node:fs/promises";
2430
+ import { join as join14 } from "node:path";
1971
2431
 
1972
2432
  // packages/changes/dist/drafted.js
1973
- var import_yaml8 = __toESM(require_dist(), 1);
1974
- import { mkdir as mkdir2, readdir as readdir5, readFile as readFile9, rm as rm2 } from "node:fs/promises";
1975
- import { dirname, join as join11, relative, sep } from "node:path";
2433
+ var import_yaml9 = __toESM(require_dist(), 1);
2434
+ import { mkdir as mkdir2, readdir as readdir5, readFile as readFile11, rm as rm2 } from "node:fs/promises";
2435
+ import { dirname, join as join13, relative, sep } from "node:path";
1976
2436
  var CAPTURE_SCHEMA_VERSION = 1;
1977
2437
  var DraftCapture = external_exports.object({
1978
2438
  schemaVersion: external_exports.literal(CAPTURE_SCHEMA_VERSION),
@@ -2026,7 +2486,7 @@ function draftedPath(path, step, artifacts) {
2026
2486
  return false;
2027
2487
  if (step) {
2028
2488
  for (const w of step.writes)
2029
- if (w.endsWith("/") ? path.startsWith(w) : path === w)
2489
+ if (writeMatches(w, path))
2030
2490
  return !machinery(path);
2031
2491
  return step.artifacts.some((a) => artifactMatch(path, a));
2032
2492
  }
@@ -2047,7 +2507,7 @@ var sameText = (text, recorded) => sha256(text) === recorded || sha256(text.repl
2047
2507
  async function walk2(dir, rel = "") {
2048
2508
  let entries;
2049
2509
  try {
2050
- entries = await readdir5(join11(dir, rel), { withFileTypes: true });
2510
+ entries = await readdir5(join13(dir, rel), { withFileTypes: true });
2051
2511
  } catch {
2052
2512
  return [];
2053
2513
  }
@@ -2192,7 +2652,7 @@ async function captureHandoff(input) {
2192
2652
  } else {
2193
2653
  const texts = await Promise.all(recorded.map(async (r) => [
2194
2654
  r,
2195
- await readFile9(join11(folder, ...r.path.split("/")), "utf8").catch(() => void 0)
2655
+ await readFile11(join13(folder, ...r.path.split("/")), "utf8").catch(() => void 0)
2196
2656
  ]));
2197
2657
  if (texts.every(([r, t]) => t !== void 0 && sameText(t, r.sha256))) {
2198
2658
  from = "working-tree";
@@ -2256,10 +2716,10 @@ async function captureHandoff(input) {
2256
2716
  } : {}
2257
2717
  };
2258
2718
  const p = capturePaths(name);
2259
- await rm2(join11(folder, ...p.drafted.split("/")), { recursive: true, force: true });
2260
- await rm2(join11(folder, ...p.merged.split("/")), { recursive: true, force: true });
2719
+ await rm2(join13(folder, ...p.drafted.split("/")), { recursive: true, force: true });
2720
+ await rm2(join13(folder, ...p.merged.split("/")), { recursive: true, force: true });
2261
2721
  const put = async (base, rel, text) => {
2262
- const f = join11(folder, ...base.split("/"), ...rel.split("/"));
2722
+ const f = join13(folder, ...base.split("/"), ...rel.split("/"));
2263
2723
  await mkdir2(dirname(f), { recursive: true });
2264
2724
  await writeFileAtomic(f, text);
2265
2725
  };
@@ -2268,37 +2728,37 @@ async function captureHandoff(input) {
2268
2728
  if (state === "captured")
2269
2729
  for (const [rel, t] of mergedFiles)
2270
2730
  await put(p.merged, rel, t);
2271
- await mkdir2(join11(folder, "stage"), { recursive: true });
2272
- await writeFileAtomic(join11(folder, ...p.capture.split("/")), HEADER + (0, import_yaml8.stringify)(capture, { lineWidth: 0 }));
2731
+ await mkdir2(join13(folder, "stage"), { recursive: true });
2732
+ await writeFileAtomic(join13(folder, ...p.capture.split("/")), HEADER + (0, import_yaml9.stringify)(capture, { lineWidth: 0 }));
2273
2733
  return { capture, file: p.capture };
2274
2734
  }
2275
2735
  async function readFolderFiles(dir) {
2276
2736
  const out = /* @__PURE__ */ new Map();
2277
2737
  for (const f of await walk2(dir))
2278
- out.set(f, (await readFile9(join11(dir, ...f.split("/")), "utf8")).replace(/\r\n/g, "\n"));
2738
+ out.set(f, (await readFile11(join13(dir, ...f.split("/")), "utf8")).replace(/\r\n/g, "\n"));
2279
2739
  return out;
2280
2740
  }
2281
2741
  async function changeFolders(root) {
2282
- const base = join11(root, ...changesRelOf(root).split("/"));
2742
+ const base = join13(root, ...changesRelOf(root).split("/"));
2283
2743
  const out = [];
2284
2744
  const list = async (dir) => (await readdir5(dir, { withFileTypes: true }).catch(() => [])).filter((e) => e.isDirectory()).map((e) => e.name).sort();
2285
2745
  for (const name of await list(base)) {
2286
2746
  if (name === "archive")
2287
- for (const a of await list(join11(base, "archive")))
2288
- out.push(join11(base, "archive", a));
2747
+ for (const a of await list(join13(base, "archive")))
2748
+ out.push(join13(base, "archive", a));
2289
2749
  else
2290
- out.push(join11(base, name));
2750
+ out.push(join13(base, name));
2291
2751
  }
2292
2752
  return out;
2293
2753
  }
2294
2754
  async function readDraftPairs(root) {
2295
2755
  const out = [];
2296
2756
  for (const folder of await changeFolders(root)) {
2297
- const names = (await readdir5(join11(folder, "stage")).catch(() => [])).filter((f) => f.endsWith(".capture.yaml")).sort();
2757
+ const names = (await readdir5(join13(folder, "stage")).catch(() => [])).filter((f) => f.endsWith(".capture.yaml")).sort();
2298
2758
  for (const f of names) {
2299
2759
  let capture;
2300
2760
  try {
2301
- capture = DraftCapture.parse((0, import_yaml8.parse)(await readFile9(join11(folder, "stage", f), "utf8")));
2761
+ capture = DraftCapture.parse((0, import_yaml9.parse)(await readFile11(join13(folder, "stage", f), "utf8")));
2302
2762
  } catch {
2303
2763
  continue;
2304
2764
  }
@@ -2306,8 +2766,8 @@ async function readDraftPairs(root) {
2306
2766
  out.push({
2307
2767
  capture,
2308
2768
  folder: relative(root, folder).split(sep).join("/"),
2309
- drafted: await readFolderFiles(join11(folder, ...p.drafted.split("/"))),
2310
- merged: await readFolderFiles(join11(folder, ...p.merged.split("/")))
2769
+ drafted: await readFolderFiles(join13(folder, ...p.drafted.split("/"))),
2770
+ merged: await readFolderFiles(join13(folder, ...p.merged.split("/")))
2311
2771
  });
2312
2772
  }
2313
2773
  }
@@ -2327,8 +2787,8 @@ async function completeCaptures(root, ctx, o) {
2327
2787
  continue;
2328
2788
  for (const h of change.handoffs.filter((x) => x.state === "merged")) {
2329
2789
  const { name } = await captureStageOf(ctx, change, h.phase);
2330
- const file = join11(changeFolder(root, change.id), ...capturePaths(name).capture.split("/"));
2331
- const prior = await readFile9(file, "utf8").then((t) => DraftCapture.safeParse((0, import_yaml8.parse)(t)), () => void 0);
2790
+ const file = join13(changeFolder(root, change.id), ...capturePaths(name).capture.split("/"));
2791
+ const prior = await readFile11(file, "utf8").then((t) => DraftCapture.safeParse((0, import_yaml9.parse)(t)), () => void 0);
2332
2792
  if (prior?.success && prior.data.pr.number === h.pr.number && prior.data.state !== "waiting-for-merge")
2333
2793
  continue;
2334
2794
  const base = prior?.success ? prior.data.base : o.defaultBase ?? "main";
@@ -2350,7 +2810,7 @@ async function completeCaptures(root, ctx, o) {
2350
2810
  }
2351
2811
 
2352
2812
  // packages/changes/dist/handoff.js
2353
- var nowIso2 = () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
2813
+ var nowIso3 = () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
2354
2814
  var strip3 = (h) => h.startsWith("@") ? h.slice(1) : h;
2355
2815
  function metarepo(ctx, repo) {
2356
2816
  const fromRuntime = ctx.orgPack.runtime.adapters.gitHost?.settings.metarepo;
@@ -2368,7 +2828,7 @@ async function load(root, id) {
2368
2828
  }
2369
2829
  async function walkReview(dir, gate) {
2370
2830
  const out = [];
2371
- const base = join12(dir, "review", gate);
2831
+ const base = join14(dir, "review", gate);
2372
2832
  let rounds;
2373
2833
  try {
2374
2834
  rounds = (await readdir6(base)).filter((r) => /^\d+$/.test(r)).sort((a, b) => Number(a) - Number(b));
@@ -2377,7 +2837,7 @@ async function walkReview(dir, gate) {
2377
2837
  }
2378
2838
  for (const r of rounds) {
2379
2839
  try {
2380
- for (const f of (await readdir6(join12(base, r))).filter((x) => /\.ya?ml$/.test(x)).sort())
2840
+ for (const f of (await readdir6(join14(base, r))).filter((x) => /\.ya?ml$/.test(x)).sort())
2381
2841
  out.push(`review/${gate}/${r}/${f}`);
2382
2842
  } catch {
2383
2843
  }
@@ -2388,12 +2848,12 @@ async function keptPairFiles(dir) {
2388
2848
  const out = [];
2389
2849
  let entries;
2390
2850
  try {
2391
- entries = await readdir6(join12(dir, "stage"), { withFileTypes: true });
2851
+ entries = await readdir6(join14(dir, "stage"), { withFileTypes: true });
2392
2852
  } catch {
2393
2853
  return out;
2394
2854
  }
2395
2855
  const walk3 = async (rel) => {
2396
- for (const e of await readdir6(join12(dir, ...rel.split("/")), { withFileTypes: true }).catch(() => [])) {
2856
+ for (const e of await readdir6(join14(dir, ...rel.split("/")), { withFileTypes: true }).catch(() => [])) {
2397
2857
  const p = `${rel}/${e.name}`;
2398
2858
  if (e.isDirectory())
2399
2859
  await walk3(p);
@@ -2421,18 +2881,23 @@ async function handoffFiles(ctx, a, phase) {
2421
2881
  for (const x of f)
2422
2882
  files.add(x);
2423
2883
  }
2424
- const stage = await phaseStage(ctx, a.change, phase);
2425
- if (stage)
2426
- for (const f of await stageFiles(dir, stage))
2427
- files.add(f);
2884
+ const at = PHASES.indexOf(phase);
2885
+ for (const step of ctx.policy.stages.filter((s) => PHASES.indexOf(s.phase) <= at)) {
2886
+ if (await phaseStage(ctx, a.change, step.phase))
2887
+ for (const f of await stageFiles(dir, step))
2888
+ files.add(f);
2889
+ }
2890
+ const present = async (f) => readFile12(join14(dir, ...f.split("/"))).then(() => true, () => false);
2891
+ for (const own of ["stage/corrections.yaml", "stage/drafted.yaml"])
2892
+ if (await present(own))
2893
+ files.add(own);
2428
2894
  if (phase === "build") {
2429
- for (const extra of ["tasks.md", "repos.yaml"]) {
2430
- try {
2431
- await readFile10(join12(dir, extra));
2895
+ for (const extra of ["tasks.md", "repos.yaml"])
2896
+ if (await present(extra))
2432
2897
  files.add(extra);
2433
- } catch {
2434
- }
2435
- }
2898
+ for (const t of await readdir6(join14(dir, "tasks"), { withFileTypes: true }).catch(() => []))
2899
+ if (t.isDirectory() && await present(`tasks/${t.name}/report.md`))
2900
+ files.add(`tasks/${t.name}/report.md`);
2436
2901
  }
2437
2902
  for (const gate of phaseGates(ctx, phase))
2438
2903
  for (const f of await walkReview(dir, gate))
@@ -2441,6 +2906,18 @@ async function handoffFiles(ctx, a, phase) {
2441
2906
  files.add(f);
2442
2907
  return { files: [...files].sort(), artifacts };
2443
2908
  }
2909
+ function namesChange(command, id) {
2910
+ const w = (command ?? "").trim().split(/\s+/);
2911
+ if (w[0] !== "atticus")
2912
+ return false;
2913
+ const changeStep = w[1] === "change" || w[1] === "plan" || w[1] === "critic";
2914
+ return changeStep && w.slice(2).includes(id);
2915
+ }
2916
+ async function changeQuestionFiles(root, id) {
2917
+ const { questions } = await listQuestions(root, { state: "answered" });
2918
+ const rel = questionsRelOf(root);
2919
+ return questions.filter((q) => namesChange(q.askedBy.command, id) || namesChange(q.rerun, id)).map((q) => `${rel}/${q.id}.yaml`).sort();
2920
+ }
2444
2921
  function prBody(a, phase, next, paths, reviewers, review) {
2445
2922
  const c = a.change;
2446
2923
  const lines2 = [
@@ -2475,7 +2952,7 @@ async function handoff(root, id, opts) {
2475
2952
  requireApproval("changes", opts.approval);
2476
2953
  const { ctx, change } = await load(root, id);
2477
2954
  const repo = metarepo(ctx, opts.repo);
2478
- const now = opts.now ?? nowIso2();
2955
+ const now = opts.now ?? nowIso3();
2479
2956
  const by = strip3(opts.by);
2480
2957
  const a = await assess(ctx, change);
2481
2958
  const phase = a.phase;
@@ -2487,7 +2964,7 @@ async function handoff(root, id, opts) {
2487
2964
  await requireValid(opts.openspec, id);
2488
2965
  const { files, artifacts } = await handoffFiles(ctx, a, phase);
2489
2966
  const dir = changeFolder(root, id);
2490
- const hashed = await Promise.all(files.map(async (path) => ({ path, sha256: sha256(await readFile10(join12(dir, path))) })));
2967
+ const hashed = await Promise.all(files.map(async (path) => ({ path, sha256: sha256(await readFile12(join14(dir, path))) })));
2491
2968
  const authors = { ...change.authors };
2492
2969
  for (const art of artifacts) {
2493
2970
  const people = (opts.authors?.[art] ?? [by]).map(strip3);
@@ -2495,13 +2972,18 @@ async function handoff(root, id, opts) {
2495
2972
  }
2496
2973
  const branch = handoffBranch(id, phase);
2497
2974
  const rel = changesRelOf(root);
2498
- const metaFiles = [...files.map((f) => `${rel}/${id}/${f}`), `${rel}/${id}/change.yaml`];
2975
+ const questionFiles = await changeQuestionFiles(root, id);
2976
+ const metaFiles = [
2977
+ ...files.map((f) => `${rel}/${id}/${f}`),
2978
+ `${rel}/${id}/change.yaml`,
2979
+ ...questionFiles
2980
+ ];
2499
2981
  const reviewers = [
2500
2982
  ...new Set(files.flatMap((f) => ownersFor(ctx.codeowners, `${rel}/${id}/${f}`)))
2501
2983
  ].filter((p) => p !== by).sort();
2502
- const live = change.state === "returned" ? void 0 : liveHandoff(change);
2503
- if (live && live.phase === phase) {
2504
- const pr2 = await opts.gitHost.getPullRequest(repo, live.pr.number);
2984
+ const live2 = change.state === "returned" ? void 0 : liveHandoff(change);
2985
+ if (live2 && live2.phase === phase) {
2986
+ const pr2 = await handoffPr(opts.gitHost, repo, id, live2);
2505
2987
  if (pr2.state === "open" || pr2.state === "draft") {
2506
2988
  await opts.publisher?.publish({
2507
2989
  root,
@@ -2511,9 +2993,9 @@ async function handoff(root, id, opts) {
2511
2993
  message: `${handoffName(a.type, phase)}: update`,
2512
2994
  approval: opts.approval
2513
2995
  });
2514
- await opts.gitHost.comment(repo, live.pr.number, `${by} updated the hand-off for another review.`, opts.approval);
2996
+ await opts.gitHost.comment(repo, pr2.number, `${by} updated the hand-off for another review.`, opts.approval);
2515
2997
  const updated = {
2516
- ...live,
2998
+ ...live2,
2517
2999
  state: "open",
2518
3000
  updatedAt: now,
2519
3001
  reviewers,
@@ -2523,14 +3005,14 @@ async function handoff(root, id, opts) {
2523
3005
  await changeStore(root).write({
2524
3006
  ...change,
2525
3007
  authors,
2526
- handoffs: change.handoffs.map((h2) => h2 === live ? updated : h2)
3008
+ handoffs: change.handoffs.map((h2) => h2 === live2 ? updated : h2)
2527
3009
  });
2528
3010
  return updated;
2529
3011
  }
2530
3012
  if (pr2.state === "merged")
2531
3013
  throw new ChangeError("refused", `PR #${pr2.number} is already merged on the Git host; sync the change instead.`);
2532
- live.state = "closed";
2533
- live.updatedAt = now;
3014
+ live2.state = "closed";
3015
+ live2.updatedAt = now;
2534
3016
  }
2535
3017
  const base = opts.base ?? (await opts.gitHost.getRepo(repo)).defaultBranch;
2536
3018
  const next = a.next;
@@ -2547,7 +3029,7 @@ async function handoff(root, id, opts) {
2547
3029
  head: branch,
2548
3030
  base,
2549
3031
  title: `${change.title}: ${handoffName(a.type, phase)}`,
2550
- body: prBody(a, phase, change.state === "returned" ? nextAfterResubmit(a, phase) : next, files.map((f) => `${rel}/${id}/${f}`), reviewers, opts.review)
3032
+ body: prBody(a, phase, change.state === "returned" ? nextAfterResubmit(a, phase) : next, [...files.map((f) => `${rel}/${id}/${f}`), ...questionFiles], reviewers, opts.review)
2551
3033
  }, opts.approval);
2552
3034
  const h = {
2553
3035
  phase,
@@ -2579,24 +3061,36 @@ function nextAfterResubmit(a, phase) {
2579
3061
  const next = a.type.phases[a.type.phases.indexOf(phase) + 1];
2580
3062
  return next ? phaseState(next) : "released";
2581
3063
  }
3064
+ async function handoffPr(gitHost, repo, id, live2) {
3065
+ const branch = handoffBranch(id, live2.phase);
3066
+ const pr = await gitHost.getPullRequest(repo, live2.pr.number);
3067
+ if (pr.head === branch)
3068
+ return pr;
3069
+ const mine = await gitHost.listPullRequests(repo, { head: branch });
3070
+ const own = mine.find((x) => x.url === live2.pr.url) ?? (mine.length === 1 ? mine[0] : void 0);
3071
+ if (own)
3072
+ return own;
3073
+ const message = `PR #${live2.pr.number} on the Git host is from ${pr.head}, not ${branch}, and no PR from ${branch} was found: Atticus uses only the hand-off's own PR. Check the PR's number on the Git host (on a manual host, on its step: atticus todo), then sync again.`;
3074
+ throw new ChangeError("refused", message, [{ code: "host-refused", message }]);
3075
+ }
2582
3076
  async function syncHandoff(root, id, opts) {
2583
3077
  const { ctx, change } = await load(root, id);
2584
3078
  const repo = metarepo(ctx, opts.repo);
2585
- const now = opts.now ?? nowIso2();
2586
- const live = liveHandoff(change);
2587
- if (!live)
3079
+ const now = opts.now ?? nowIso3();
3080
+ const live2 = liveHandoff(change);
3081
+ if (!live2)
2588
3082
  throw new ChangeError("refused", `${id} has no open hand-off PR to sync.`, [
2589
3083
  { code: "no-handoff", message: `${id} has no open hand-off PR.` }
2590
3084
  ]);
2591
3085
  const a = await assess(ctx, change);
2592
- const pr = await opts.gitHost.getPullRequest(repo, live.pr.number);
2593
- const current = pr.url !== live.pr.url || pr.number !== live.pr.number ? { ...live, pr: { number: pr.number, url: pr.url } } : live;
3086
+ const pr = await handoffPr(opts.gitHost, repo, id, live2);
3087
+ const current = pr.url !== live2.pr.url || pr.number !== live2.pr.number ? { ...live2, pr: { number: pr.number, url: pr.url } } : live2;
2594
3088
  const store = changeStore(root);
2595
3089
  const save = async (h2, extra = {}) => {
2596
3090
  const updated = {
2597
3091
  ...change,
2598
3092
  ...extra,
2599
- handoffs: change.handoffs.map((x) => x === live ? h2 : x)
3093
+ handoffs: change.handoffs.map((x) => x === live2 ? h2 : x)
2600
3094
  };
2601
3095
  await store.write(updated);
2602
3096
  return updated;
@@ -2618,7 +3112,7 @@ async function syncHandoff(root, id, opts) {
2618
3112
  latest.set(strip3(r.by), r.state);
2619
3113
  const approvers = [...latest].filter(([, s]) => s === "approved").map(([p]) => p);
2620
3114
  const requested = [...latest].filter(([, s]) => s === "changes-requested").map(([p]) => p).sort();
2621
- const counted = prApprovers(live, approvers);
3115
+ const counted = prApprovers(live2, approvers);
2622
3116
  const approvals = counted.map((by) => ({ by, at: now }));
2623
3117
  const advance = async (by) => {
2624
3118
  const h2 = { ...current, state: "merged", updatedAt: now, approvals };
@@ -2631,7 +3125,7 @@ async function syncHandoff(root, id, opts) {
2631
3125
  at: now,
2632
3126
  by,
2633
3127
  pr: pr.number,
2634
- reason: `${handoffName(a.type, live.phase)} merged`
3128
+ reason: `${handoffName(a.type, live2.phase)} merged`
2635
3129
  }
2636
3130
  ];
2637
3131
  let state = next;
@@ -2666,7 +3160,7 @@ async function syncHandoff(root, id, opts) {
2666
3160
  };
2667
3161
  };
2668
3162
  if (pr.state === "merged")
2669
- return advance(strip3(opts.approval?.by ?? live.by));
3163
+ return advance(strip3(opts.approval?.by ?? live2.by));
2670
3164
  if (requested.length > 0) {
2671
3165
  const h2 = { ...current, state: "changes-requested", updatedAt: now, approvals };
2672
3166
  await save(h2);
@@ -2687,8 +3181,8 @@ async function syncHandoff(root, id, opts) {
2687
3181
  if (counted.length === 0)
2688
3182
  reasons.push(approvers.length > 0 ? {
2689
3183
  code: "self-approval",
2690
- person: strip3(live.by),
2691
- message: `PR #${pr.number} is approved only by ${strip3(live.by)}, who raised it; it needs someone else's approval.`
3184
+ person: strip3(live2.by),
3185
+ message: `PR #${pr.number} is approved only by ${strip3(live2.by)}, who raised it; it needs someone else's approval.`
2692
3186
  } : {
2693
3187
  code: "approval-missing",
2694
3188
  message: `PR #${pr.number} isn't approved yet: ask a reviewer on the Git host (${pr.url}).`
@@ -2727,19 +3221,19 @@ async function syncHandoff(root, id, opts) {
2727
3221
  }
2728
3222
  async function returnToPo(root, id, opts) {
2729
3223
  const { ctx, change } = await load(root, id);
2730
- const live = liveHandoff(change);
2731
- if (!live)
3224
+ const live2 = liveHandoff(change);
3225
+ if (!live2)
2732
3226
  throw new ChangeError("refused", `${id} has no open hand-off PR to return.`, [
2733
3227
  { code: "no-handoff", message: `${id} has no open hand-off PR.` }
2734
3228
  ]);
2735
3229
  requireApproval("changes", opts.approval);
2736
3230
  const repo = metarepo(ctx, opts.repo);
2737
- const now = opts.now ?? nowIso2();
3231
+ const now = opts.now ?? nowIso3();
2738
3232
  const by = strip3(opts.by);
2739
3233
  const host = opts.gitHost;
2740
- await host.comment(repo, live.pr.number, `Returned to the product owner by ${by}: ${opts.reason}`, opts.approval);
2741
- const closedOnHost = host.closePullRequest ? (await host.closePullRequest(repo, live.pr.number, opts.approval)).state === "closed" : false;
2742
- const h = { ...live, state: "closed", updatedAt: now };
3234
+ await host.comment(repo, live2.pr.number, `Returned to the product owner by ${by}: ${opts.reason}`, opts.approval);
3235
+ const closedOnHost = host.closePullRequest ? (await host.closePullRequest(repo, live2.pr.number, opts.approval)).state === "closed" : false;
3236
+ const h = { ...live2, state: "closed", updatedAt: now };
2743
3237
  await changeStore(root).write({
2744
3238
  ...change,
2745
3239
  state: "returned",
@@ -2750,11 +3244,11 @@ async function returnToPo(root, id, opts) {
2750
3244
  to: "returned",
2751
3245
  at: now,
2752
3246
  by,
2753
- pr: live.pr.number,
3247
+ pr: live2.pr.number,
2754
3248
  reason: `Returned to the product owner (${opts.reason})`
2755
3249
  }
2756
3250
  ],
2757
- handoffs: change.handoffs.map((x) => x === live ? h : x)
3251
+ handoffs: change.handoffs.map((x) => x === live2 ? h : x)
2758
3252
  });
2759
3253
  return { state: "returned", closedOnHost, handoff: h };
2760
3254
  }
@@ -2774,7 +3268,7 @@ async function archiveChange(root, id, opts) {
2774
3268
  throw new ChangeError("refused", `${id} can't be archived yet.`, a.reasons);
2775
3269
  if (opts.openspec)
2776
3270
  await requireValid(opts.openspec, id);
2777
- const now = opts.now ?? nowIso2();
3271
+ const now = opts.now ?? nowIso3();
2778
3272
  const updated = {
2779
3273
  ...change,
2780
3274
  state: "archived",
@@ -2790,9 +3284,9 @@ async function archiveChange(root, id, opts) {
2790
3284
  }
2791
3285
 
2792
3286
  // packages/changes/dist/learn.js
2793
- var import_yaml9 = __toESM(require_dist(), 1);
2794
- import { readFile as readFile11 } from "node:fs/promises";
2795
- import { join as join13 } from "node:path";
3287
+ var import_yaml10 = __toESM(require_dist(), 1);
3288
+ import { readFile as readFile13 } from "node:fs/promises";
3289
+ import { join as join15 } from "node:path";
2796
3290
  var LEARN_SCHEMA_VERSION = 1;
2797
3291
  var MAX_CELLS = 4e6;
2798
3292
  var lines = (text) => {
@@ -3139,7 +3633,7 @@ async function pairTouches(root, p) {
3139
3633
  let entry;
3140
3634
  const yaml = (t) => {
3141
3635
  try {
3142
- const v = (0, import_yaml9.parse)(t);
3636
+ const v = (0, import_yaml10.parse)(t);
3143
3637
  return v && typeof v === "object" ? v : void 0;
3144
3638
  } catch {
3145
3639
  return void 0;
@@ -3181,7 +3675,7 @@ async function pairTouches(root, p) {
3181
3675
  flows.push(m[1]);
3182
3676
  }
3183
3677
  }
3184
- const change = yaml(await readFile11(join13(root, ...p.folder.split("/"), "change.yaml"), "utf8").catch(() => ""));
3678
+ const change = yaml(await readFile13(join15(root, ...p.folder.split("/"), "change.yaml"), "utf8").catch(() => ""));
3185
3679
  const touches = change?.touches ?? {};
3186
3680
  for (const r of Array.isArray(touches.repos) ? touches.repos : [])
3187
3681
  if (typeof r === "string")
@@ -3274,8 +3768,8 @@ async function detectConflicts(root) {
3274
3768
  }
3275
3769
 
3276
3770
  // packages/changes/dist/size.js
3277
- import { readFile as readFile12 } from "node:fs/promises";
3278
- import { join as join14 } from "node:path";
3771
+ import { readFile as readFile14 } from "node:fs/promises";
3772
+ import { join as join16 } from "node:path";
3279
3773
  async function changeSize(ctx, change) {
3280
3774
  const type = ctx.orgPack.changeTypes.types.find((t) => t.id === change.type);
3281
3775
  if (!type)
@@ -3284,7 +3778,7 @@ async function changeSize(ctx, change) {
3284
3778
  why: "standard",
3285
3779
  words: `${change.type} isn't a change type in change-types.yaml, so ${change.id} takes the full path.`
3286
3780
  };
3287
- const brief = await readFile12(join14(changeFolder(ctx.root, change.id), "brief.md"), "utf8").catch(() => "");
3781
+ const brief = await readFile14(join16(changeFolder(ctx.root, change.id), "brief.md"), "utf8").catch(() => "");
3288
3782
  const f = fastPathOf(type, brief);
3289
3783
  const gate = f.fast ? fastGateOf(type, ctx.orgPack.gates) : void 0;
3290
3784
  const words = {
@@ -3298,14 +3792,14 @@ async function changeSize(ctx, change) {
3298
3792
  }
3299
3793
 
3300
3794
  // packages/changes/dist/openspec-stores.js
3301
- import { existsSync } from "node:fs";
3302
- import { readFile as readFile13, writeFile as writeFile2 } from "node:fs/promises";
3795
+ import { existsSync as existsSync2 } from "node:fs";
3796
+ import { readFile as readFile15, writeFile as writeFile3 } from "node:fs/promises";
3303
3797
  import { homedir } from "node:os";
3304
- import { basename, isAbsolute, join as join15, relative as relative2, resolve, sep as sep2 } from "node:path";
3798
+ import { basename, isAbsolute, join as join17, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
3305
3799
  function storeDirOf(root, path) {
3306
3800
  if (path === "~" || path.startsWith("~/") || path.startsWith("~\\"))
3307
- return join15(homedir(), path.slice(2));
3308
- return isAbsolute(path) ? resolve(path) : resolve(root, path);
3801
+ return join17(homedir(), path.slice(2));
3802
+ return isAbsolute(path) ? resolve2(path) : resolve2(root, path);
3309
3803
  }
3310
3804
  function packRel(root) {
3311
3805
  return relative2(root, orgPackDirOf(root)).split(sep2).join("/");
@@ -3334,7 +3828,7 @@ async function storeSpecLookup(root, capability) {
3334
3828
  var REMOTE = /^[a-z][a-z0-9+.-]*:\/\//i;
3335
3829
  var SCP = /^[^/\\]+@[^:/\\]+:/;
3336
3830
  async function metarepoOrigin(root, git = createGit()) {
3337
- if (!existsSync(join15(root, ".git")))
3831
+ if (!existsSync2(join17(root, ".git")))
3338
3832
  return void 0;
3339
3833
  try {
3340
3834
  const url = (await git(["remote", "get-url", "origin"], root)).stdout.trim();
@@ -3357,8 +3851,8 @@ async function referenceMetarepoIn(root, repoDir, o = {}) {
3357
3851
  file,
3358
3852
  why: marker.missing ? `this metarepo isn't an OpenSpec store yet (no ${STORE_MARKER}): run atticus upgrade --openspec-store, commit the file, then hand down again` : `this metarepo's ${STORE_MARKER} can't be read (${marker.notes.join("; ")})`
3359
3853
  };
3360
- const path = join15(repoDir, "openspec", "config.yaml");
3361
- const text = await readFile13(path, "utf8").catch(() => void 0);
3854
+ const path = join17(repoDir, "openspec", "config.yaml");
3855
+ const text = await readFile15(path, "utf8").catch(() => void 0);
3362
3856
  if (text === void 0)
3363
3857
  return {
3364
3858
  action: "skipped",
@@ -3370,10 +3864,180 @@ async function referenceMetarepoIn(root, repoDir, o = {}) {
3370
3864
  if ("problem" in r)
3371
3865
  return { action: "skipped", file, why: r.problem };
3372
3866
  if (r.action === "added")
3373
- await writeFile2(path, r.text, "utf8");
3867
+ await writeFile3(path, r.text, "utf8");
3374
3868
  return { action: r.action, file };
3375
3869
  }
3376
3870
 
3871
+ // packages/changes/dist/merge-record.js
3872
+ var import_yaml11 = __toESM(require_dist(), 1);
3873
+ import { isDeepStrictEqual } from "node:util";
3874
+ var HANDOFF_RANK = {
3875
+ open: 0,
3876
+ "changes-requested": 1,
3877
+ closed: 2,
3878
+ merged: 3
3879
+ };
3880
+ var Clash = class extends Error {
3881
+ field;
3882
+ constructor(field) {
3883
+ super(field);
3884
+ this.field = field;
3885
+ }
3886
+ };
3887
+ function three(field, base, ours, theirs) {
3888
+ if (isDeepStrictEqual(ours, theirs))
3889
+ return ours;
3890
+ if (base.has) {
3891
+ if (isDeepStrictEqual(ours, base.value))
3892
+ return theirs;
3893
+ if (isDeepStrictEqual(theirs, base.value))
3894
+ return ours;
3895
+ }
3896
+ throw new Clash(field);
3897
+ }
3898
+ function union(ours, theirs, key, pick) {
3899
+ const out = /* @__PURE__ */ new Map();
3900
+ for (const x of ours)
3901
+ if (!out.has(key(x)))
3902
+ out.set(key(x), x);
3903
+ for (const x of theirs) {
3904
+ const k = key(x);
3905
+ const had = out.get(k);
3906
+ out.set(k, had === void 0 ? x : pick ? pick(had, x) : had);
3907
+ }
3908
+ return [...out.values()];
3909
+ }
3910
+ var startsWith = (a, b) => b.length <= a.length && b.every((x, i) => isDeepStrictEqual(x, a[i]));
3911
+ function mergeHistory(ours, theirs) {
3912
+ if (startsWith(ours, theirs))
3913
+ return ours;
3914
+ if (startsWith(theirs, ours))
3915
+ return theirs;
3916
+ const all = union(ours, theirs, (h) => `${h.from}>${h.to}@${h.at}`);
3917
+ const merged = all.map((h, i) => ({ h, i })).sort((a, b) => a.h.at < b.h.at ? -1 : a.h.at > b.h.at ? 1 : a.i - b.i).map((x) => x.h);
3918
+ let at = "draft";
3919
+ for (const h of merged) {
3920
+ if (h.from !== at)
3921
+ throw new Clash("history");
3922
+ at = h.to;
3923
+ }
3924
+ return merged;
3925
+ }
3926
+ function mergeHandoff(a, b) {
3927
+ if (isDeepStrictEqual(a, b))
3928
+ return a;
3929
+ const when = (h) => h.updatedAt ?? h.openedAt;
3930
+ const ra = HANDOFF_RANK[a.state];
3931
+ const rb = HANDOFF_RANK[b.state];
3932
+ const top = rb > ra || rb === ra && when(b) > when(a) ? b : a;
3933
+ return { ...top, reviewers: [.../* @__PURE__ */ new Set([...a.reviewers, ...b.reviewers])] };
3934
+ }
3935
+ function mergeHandoffs(ours, theirs) {
3936
+ const all = union(ours, theirs, (h) => `${h.phase}#${h.pr.number}`, mergeHandoff);
3937
+ return all.map((h, i) => ({ h, i })).sort((a, b) => a.h.openedAt < b.h.openedAt ? -1 : a.h.openedAt > b.h.openedAt ? 1 : a.i - b.i).map((x) => x.h);
3938
+ }
3939
+ function byKey(field, base, ours, theirs) {
3940
+ const out = {};
3941
+ for (const k of [.../* @__PURE__ */ new Set([...Object.keys(ours), ...Object.keys(theirs)])]) {
3942
+ const v = three(`${field}.${k}`, { has: base !== void 0, value: base?.[k] }, ours[k], theirs[k]);
3943
+ if (v !== void 0)
3944
+ out[k] = v;
3945
+ }
3946
+ return out;
3947
+ }
3948
+ function mergeChangeRecords(base, ours, theirs) {
3949
+ const b = base === void 0 ? void 0 : ChangeLifecycle.parse(base);
3950
+ const o = ChangeLifecycle.parse(ours);
3951
+ const t = ChangeLifecycle.parse(theirs);
3952
+ const has = b !== void 0;
3953
+ const at = (k) => ({ has, value: b?.[k] });
3954
+ try {
3955
+ const history = mergeHistory(o.history, t.history);
3956
+ const authors = {};
3957
+ for (const k of [.../* @__PURE__ */ new Set([...Object.keys(o.authors), ...Object.keys(t.authors)])]) {
3958
+ const key = k;
3959
+ authors[key] = [.../* @__PURE__ */ new Set([...o.authors[key] ?? [], ...t.authors[key] ?? []])];
3960
+ }
3961
+ const template = three("template", at("template"), o.template, t.template);
3962
+ const value = {
3963
+ schemaVersion: o.schemaVersion,
3964
+ id: three("id", at("id"), o.id, t.id),
3965
+ title: three("title", at("title"), o.title, t.title),
3966
+ type: three("type", at("type"), o.type, t.type),
3967
+ ...template !== void 0 ? { template } : {},
3968
+ state: history.length ? history.at(-1).to : three("state", at("state"), o.state, t.state),
3969
+ owners: byKey("owners", b?.owners, o.owners, t.owners),
3970
+ systems: three("systems", at("systems"), o.systems, t.systems),
3971
+ touches: byKey("touches", b?.touches, o.touches, t.touches),
3972
+ authors,
3973
+ createdAt: three("createdAt", at("createdAt"), o.createdAt, t.createdAt),
3974
+ createdBy: three("createdBy", at("createdBy"), o.createdBy, t.createdBy),
3975
+ history,
3976
+ handoffs: mergeHandoffs(o.handoffs, t.handoffs)
3977
+ };
3978
+ const r = ChangeLifecycle.safeParse(value);
3979
+ if (!r.success)
3980
+ return { ok: false, field: r.error.issues[0]?.path.map(String).join(".") || "record" };
3981
+ return { ok: true, value: r.data };
3982
+ } catch (e) {
3983
+ if (e instanceof Clash)
3984
+ return { ok: false, field: e.field };
3985
+ throw e;
3986
+ }
3987
+ }
3988
+ function readRecord(text) {
3989
+ try {
3990
+ const r = ChangeLifecycle.safeParse((0, import_yaml11.parse)(text));
3991
+ return r.success ? r.data : void 0;
3992
+ } catch {
3993
+ return void 0;
3994
+ }
3995
+ }
3996
+ function mergeChangeText(baseText, oursText, theirsText) {
3997
+ const ours = readRecord(oursText);
3998
+ const theirs = readRecord(theirsText);
3999
+ const base = baseText.trim() ? readRecord(baseText) : void 0;
4000
+ if (!ours || !theirs || baseText.trim() && !base)
4001
+ return void 0;
4002
+ const r = mergeChangeRecords(base, ours, theirs);
4003
+ return r.ok ? { ok: true, text: serializeChange(r.value) } : r;
4004
+ }
4005
+
4006
+ // packages/changes/dist/owners.js
4007
+ var OWNER_ROLE_KEYS = [
4008
+ "product-owner",
4009
+ "architect",
4010
+ "business-analyst",
4011
+ "team-lead"
4012
+ ];
4013
+ var ChangeOwnersError = class extends Error {
4014
+ constructor(message) {
4015
+ super(message);
4016
+ this.name = "ChangeOwnersError";
4017
+ }
4018
+ };
4019
+ var strip4 = (h) => h.trim().replace(/^@/, "");
4020
+ async function setChangeOwners(root, id, owners) {
4021
+ const store = changeStore(root);
4022
+ const c = await store.read(id);
4023
+ const next = { ...c.owners };
4024
+ for (const [key, people] of Object.entries(owners)) {
4025
+ if (!OWNER_ROLE_KEYS.includes(key))
4026
+ throw new ChangeOwnersError(`The change page names owners for ${OWNER_ROLE_KEYS.join(", ")}, not ${key}: edit change.yaml for other roles.`);
4027
+ const list = [...new Set(people.map(strip4).filter(Boolean))];
4028
+ if (list.length)
4029
+ next[key] = list;
4030
+ else
4031
+ delete next[key];
4032
+ }
4033
+ const sorted2 = Object.fromEntries(Object.entries(next).sort(([a], [b]) => a.localeCompare(b)));
4034
+ const r = ChangeLifecycle.safeParse({ ...c, owners: sorted2 });
4035
+ if (!r.success)
4036
+ throw new ChangeOwnersError(`Those owners can't be written: ${r.error.issues.map((i) => i.message).join("; ")}.`);
4037
+ await store.write(r.data);
4038
+ return r.data;
4039
+ }
4040
+
3377
4041
  export {
3378
4042
  ChangeError,
3379
4043
  readTables,
@@ -3381,6 +4045,8 @@ export {
3381
4045
  isFilled,
3382
4046
  CHANGE_POLICY_FILE,
3383
4047
  StageStep,
4048
+ isWritePattern,
4049
+ writeMatches,
3384
4050
  SIGNAL_FACTORS,
3385
4051
  ChangePolicy,
3386
4052
  DEFAULT_CHANGE_POLICY,
@@ -3405,6 +4071,9 @@ export {
3405
4071
  readCodeowners,
3406
4072
  loadChangeContext,
3407
4073
  changeTypeOf,
4074
+ outsideSystemsOf,
4075
+ outsideKindOf,
4076
+ outsideWords,
3408
4077
  personalLevels,
3409
4078
  wordsOf,
3410
4079
  changeFootprint,
@@ -3435,10 +4104,20 @@ export {
3435
4104
  workProgress,
3436
4105
  privacyImpact,
3437
4106
  prApprovers,
4107
+ RELEASE_PR_STATES,
4108
+ RELEASE_PRS_FILE,
4109
+ nothingToRelease,
4110
+ raiseReleasePrs,
4111
+ syncReleasePrs,
4112
+ requireMergedPrs,
4113
+ releasePrReasons,
3438
4114
  liveHandoff,
4115
+ isManualPrUrl,
4116
+ manualHandoffWords,
3439
4117
  assess,
3440
4118
  canAdvance,
3441
4119
  changeReport,
4120
+ windowsUnsafeName,
3442
4121
  slugify,
3443
4122
  newChange,
3444
4123
  gitHandoffPublisher,
@@ -3450,6 +4129,7 @@ export {
3450
4129
  captureHandoff,
3451
4130
  readDraftPairs,
3452
4131
  completeCaptures,
4132
+ namesChange,
3453
4133
  handoff,
3454
4134
  syncHandoff,
3455
4135
  returnToPo,
@@ -3475,5 +4155,10 @@ export {
3475
4155
  openspecStores,
3476
4156
  storeSpecLookup,
3477
4157
  metarepoOrigin,
3478
- referenceMetarepoIn
4158
+ referenceMetarepoIn,
4159
+ mergeChangeRecords,
4160
+ mergeChangeText,
4161
+ OWNER_ROLE_KEYS,
4162
+ ChangeOwnersError,
4163
+ setChangeOwners
3479
4164
  };