@maycombhq/atticus 2.0.0-beta.18 → 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 (270) hide show
  1. package/dist/bin.js +8 -4
  2. package/dist/{chunk-MUSJJ6CQ.js → chunk-265LWMOA.js} +16 -16
  3. package/dist/chunk-26WBY34Y.js +37 -0
  4. package/dist/{chunk-6H5JG5GL.js → chunk-26Z7OGQW.js} +63 -30
  5. package/dist/{chunk-4REDBQOZ.js → chunk-2D7PWSOK.js} +20 -20
  6. package/dist/{chunk-YPH6ZJ6R.js → chunk-2HH3UCZM.js} +4 -4
  7. package/dist/{chunk-RTUUX7PX.js → chunk-2HTEMYTB.js} +12 -26
  8. package/dist/{chunk-EHQUY26D.js → chunk-2UCRFXFZ.js} +11 -11
  9. package/dist/{chunk-ILDGIXNH.js → chunk-2V2ZPQQU.js} +81 -13
  10. package/dist/{chunk-YSEZQ6D3.js → chunk-36RY7ZXM.js} +107 -64
  11. package/dist/{chunk-QXFDM2FZ.js → chunk-37WE3LS2.js} +3 -3
  12. package/dist/{chunk-BAXM3JFQ.js → chunk-3AQ2R5NL.js} +1212 -754
  13. package/dist/chunk-3G6S7LAA.js +43 -0
  14. package/dist/{chunk-7TOAS7KI.js → chunk-3J5I7ZOU.js} +4 -4
  15. package/dist/{chunk-TAKAEJRD.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-UUL6ZGMU.js → chunk-3UTYPHN3.js} +1 -1
  19. package/dist/{chunk-6AP2ZUUM.js → chunk-3WON4G3M.js} +2 -2
  20. package/dist/{chunk-AXY6LBW4.js → chunk-42EPP7OB.js} +12 -12
  21. package/dist/{chunk-QKMDT7VU.js → chunk-455SYIIE.js} +2 -2
  22. package/dist/{chunk-MFUFB3LN.js → chunk-4B6BT75O.js} +110 -20
  23. package/dist/{chunk-75MYLZSB.js → chunk-4CLK2RJO.js} +7 -7
  24. package/dist/chunk-4CYTJ5TZ.js +38 -0
  25. package/dist/{chunk-IH7RANGB.js → chunk-4D62IZII.js} +5 -5
  26. package/dist/{chunk-2QMZOCWN.js → chunk-4FOAYLI4.js} +14 -7
  27. package/dist/{chunk-V3UEPUHT.js → chunk-4KNOJC6J.js} +294 -215
  28. package/dist/{chunk-O6ILVBYB.js → chunk-4PYC2UGC.js} +1 -1
  29. package/dist/{chunk-EZBAS5BL.js → chunk-4SLBCEPD.js} +82 -50
  30. package/dist/{chunk-VIRTPODJ.js → chunk-4TKVU2I2.js} +6 -14
  31. package/dist/{chunk-IVCDU443.js → chunk-4WXRPQDE.js} +281 -51
  32. package/dist/{chunk-RMEEHH24.js → chunk-5CMPVC3N.js} +24 -24
  33. package/dist/chunk-5G7ILBUG.js +66 -0
  34. package/dist/{chunk-Z75XDWQR.js → chunk-5PV7S2ZP.js} +3 -3
  35. package/dist/{chunk-WWPC2AZ2.js → chunk-5QUOPNLN.js} +4 -4
  36. package/dist/{chunk-7OVH2L7L.js → chunk-5TDRBOAV.js} +5 -5
  37. package/dist/{chunk-YXUIUOQZ.js → chunk-5WERBKLE.js} +199 -63
  38. package/dist/{chunk-6WNNXEG6.js → chunk-62DLJSLV.js} +8 -8
  39. package/dist/{chunk-KCQS52TH.js → chunk-64AADMTW.js} +99 -25
  40. package/dist/{chunk-KLRUXRPN.js → chunk-67BP7HXL.js} +17 -17
  41. package/dist/chunk-6DQYZU2W.js +379 -0
  42. package/dist/{chunk-RONDYDMQ.js → chunk-6EXDI5GI.js} +51 -34
  43. package/dist/chunk-6FNAKCET.js +38 -0
  44. package/dist/{chunk-6SNEA73E.js → chunk-75ASLHBT.js} +21 -15
  45. package/dist/{chunk-EFT4KM6K.js → chunk-75ZOZFGH.js} +18 -18
  46. package/dist/{chunk-B3KW7EQW.js → chunk-77O633L5.js} +12 -12
  47. package/dist/{chunk-TLDL2ZZI.js → chunk-7AB6OWTV.js} +3 -3
  48. package/dist/{chunk-R5YXY6NT.js → chunk-7CZHUZBL.js} +25 -25
  49. package/dist/{chunk-F4D4G2A6.js → chunk-7FHNFCHK.js} +306 -125
  50. package/dist/{chunk-CC5RNYZF.js → chunk-7FUIVIBG.js} +19 -9
  51. package/dist/{chunk-UBTOSSNK.js → chunk-7QPCGTPS.js} +21 -21
  52. package/dist/{chunk-CDLTRJ4N.js → chunk-A2G72UI4.js} +7 -7
  53. package/dist/{chunk-YGSOQ5LX.js → chunk-AE4P57KK.js} +23 -17
  54. package/dist/{chunk-PGQL5YGH.js → chunk-AHW3RY3X.js} +7 -7
  55. package/dist/chunk-AM6FP6JW.js +43 -0
  56. package/dist/{chunk-DYNC6BAQ.js → chunk-AOCWFA7T.js} +93 -10
  57. package/dist/{chunk-CYX36GKZ.js → chunk-API5TOUB.js} +1 -1
  58. package/dist/{chunk-BJ5T3KDF.js → chunk-AZDITBCT.js} +3 -3
  59. package/dist/{chunk-U6GZSUMC.js → chunk-BA2HUMDN.js} +25 -3
  60. package/dist/chunk-BA3U7ALS.js +99 -0
  61. package/dist/{chunk-63565QTF.js → chunk-BC6KKZ67.js} +5 -5
  62. package/dist/{chunk-XO5RQKNM.js → chunk-BEPCKDCP.js} +1843 -577
  63. package/dist/{chunk-KUH62AQ3.js → chunk-BPPU3PW7.js} +4 -4
  64. package/dist/{chunk-52ULYRV4.js → chunk-BVWWPBKD.js} +18 -18
  65. package/dist/{chunk-R2FGEBIU.js → chunk-BXZ4PKIT.js} +13 -11
  66. package/dist/{chunk-4BU26GFB.js → chunk-C7CEK3KW.js} +4 -4
  67. package/dist/chunk-CFH3QH26.js +29 -0
  68. package/dist/chunk-CG6CULP6.js +170 -0
  69. package/dist/chunk-CLD4RVTA.js +45 -0
  70. package/dist/{chunk-BU543ZEC.js → chunk-CNPY4NTZ.js} +13 -13
  71. package/dist/{chunk-PLBIWEPV.js → chunk-CNU5Q2D5.js} +32 -31
  72. package/dist/{chunk-JTYYEYRR.js → chunk-COYN3UAN.js} +124 -21
  73. package/dist/{chunk-TNCROWPQ.js → chunk-CPK7QSBG.js} +10 -10
  74. package/dist/{chunk-43E6XX67.js → chunk-D2WSW4GU.js} +1 -1
  75. package/dist/{chunk-SHEUEHKC.js → chunk-D57MFJPP.js} +3 -3
  76. package/dist/chunk-D5KV5PSK.js +45 -0
  77. package/dist/chunk-D5MASS4I.js +48 -0
  78. package/dist/{chunk-TQYBXDVH.js → chunk-DCWN3HFV.js} +1 -1
  79. package/dist/{chunk-FF4IGX4Z.js → chunk-DDJPM5YX.js} +2 -2
  80. package/dist/chunk-DN4A2Q2O.js +57 -0
  81. package/dist/{chunk-4ZCFCIEL.js → chunk-DUPZPDCJ.js} +52 -16
  82. package/dist/{chunk-CUJ5TIEB.js → chunk-E2VXBSIR.js} +4 -4
  83. package/dist/{chunk-BX47X5DJ.js → chunk-E57AOFYN.js} +13 -13
  84. package/dist/{chunk-SSTI7GAV.js → chunk-ECLXZWAG.js} +12 -12
  85. package/dist/{chunk-KYK3TA4Q.js → chunk-EDZ45WIR.js} +14 -14
  86. package/dist/chunk-EFCQSMXB.js +37 -0
  87. package/dist/{chunk-YC2K3FJG.js → chunk-EZIN5E5B.js} +8 -8
  88. package/dist/{chunk-MIZNACDI.js → chunk-F6H2RJ3N.js} +1 -1
  89. package/dist/{chunk-EMZVHDJP.js → chunk-FAJDKNSD.js} +7 -3
  90. package/dist/chunk-FGAGWYZB.js +47 -0
  91. package/dist/{chunk-KBGQ3IAM.js → chunk-FLVZFTLQ.js} +18 -18
  92. package/dist/{chunk-2NNXLH53.js → chunk-FQ6WH6PB.js} +22 -7
  93. package/dist/{chunk-QWSLMVHI.js → chunk-FRHD22WM.js} +61 -23
  94. package/dist/{chunk-5MEN4SM5.js → chunk-FVMPFCDS.js} +4 -4
  95. package/dist/{chunk-KLRZKW7Y.js → chunk-FY3QIRTL.js} +3 -3
  96. package/dist/{chunk-B3SWPNJF.js → chunk-FYBF5MB2.js} +13 -11
  97. package/dist/{chunk-CVZCW3NI.js → chunk-G5YJBNDZ.js} +14 -5
  98. package/dist/{chunk-U6EPS5PF.js → chunk-G6IQYM25.js} +5 -5
  99. package/dist/chunk-G6X4YL4D.js +161 -0
  100. package/dist/{chunk-DOSX3MRW.js → chunk-GC2VK3JT.js} +25 -25
  101. package/dist/{chunk-EEWBTFRP.js → chunk-GCS3FZOB.js} +27 -3
  102. package/dist/{chunk-6H6OPNEP.js → chunk-GDZB57IU.js} +5 -5
  103. package/dist/chunk-GIFMBCTW.js +221 -0
  104. package/dist/{chunk-X5WYPIH3.js → chunk-GK4PYVEC.js} +18 -8
  105. package/dist/{chunk-VBSJ5NRB.js → chunk-GNWAOVFQ.js} +5 -5
  106. package/dist/{chunk-TXFDQRBL.js → chunk-GS76RXQE.js} +11 -11
  107. package/dist/{chunk-U3YC4RGC.js → chunk-GSGIZ2OO.js} +23 -23
  108. package/dist/chunk-GUYL7RMK.js +60 -0
  109. package/dist/{chunk-AZYBHPV4.js → chunk-GX74YWR7.js} +14 -13
  110. package/dist/{chunk-YY7VP7KW.js → chunk-HE3HRDNZ.js} +2 -2
  111. package/dist/{chunk-Z7HYYDZM.js → chunk-HGHRSF3E.js} +14 -13
  112. package/dist/{chunk-Q2X4IREC.js → chunk-HK3VOHIE.js} +106 -36
  113. package/dist/{chunk-P5JYMO7Y.js → chunk-HS4BGB4G.js} +22 -22
  114. package/dist/{chunk-IOW6BF3U.js → chunk-HSZMPH5T.js} +798 -141
  115. package/dist/{chunk-L3T33FA7.js → chunk-HWD4JPU7.js} +4 -2
  116. package/dist/{chunk-7NYCH5HY.js → chunk-HXXP2NTE.js} +6 -6
  117. package/dist/{chunk-7S7OFVIU.js → chunk-I2A3Z5AK.js} +4 -4
  118. package/dist/{chunk-WQSDKX7M.js → chunk-ICHDSWEY.js} +7 -7
  119. package/dist/chunk-IFFRGC3N.js +31 -0
  120. package/dist/{chunk-HMZQAIR5.js → chunk-IFHDV6KQ.js} +24 -17
  121. package/dist/chunk-J2O4LFVI.js +80 -0
  122. package/dist/chunk-J5AAZ6OJ.js +161 -0
  123. package/dist/{chunk-CVRWAG76.js → chunk-JAABT3DA.js} +3 -3
  124. package/dist/chunk-JITJP64K.js +34 -0
  125. package/dist/chunk-JKIGL3XQ.js +162 -0
  126. package/dist/chunk-JRVNXLZP.js +615 -0
  127. package/dist/chunk-JRXHON4E.js +41 -0
  128. package/dist/{chunk-XFCK5B27.js → chunk-JS6WMC3J.js} +8 -8
  129. package/dist/chunk-JU4QNJSR.js +62 -0
  130. package/dist/{chunk-IPSFMTK2.js → chunk-JYMIB5XY.js} +152 -42
  131. package/dist/{chunk-L336AJCZ.js → chunk-JYTIHTIK.js} +8 -6
  132. package/dist/{chunk-VEJYZEPC.js → chunk-K332XW7K.js} +135 -16
  133. package/dist/{chunk-KCF7TFOE.js → chunk-K6JB6XTX.js} +11 -11
  134. package/dist/{chunk-LFIIX4OV.js → chunk-KDAHXQIE.js} +46 -34
  135. package/dist/chunk-KYZMRSPS.js +93 -0
  136. package/dist/chunk-L4CESDPB.js +22 -0
  137. package/dist/{chunk-3LR4522O.js → chunk-LEBR5NHW.js} +14 -14
  138. package/dist/{chunk-US3QZU23.js → chunk-LIJJCBQI.js} +67 -7
  139. package/dist/{chunk-XLHL6WTI.js → chunk-LIRVU3HW.js} +16 -16
  140. package/dist/{chunk-BAT4CBB7.js → chunk-LN77DXBM.js} +19 -10
  141. package/dist/{chunk-E5FM44EJ.js → chunk-LNWEWYCM.js} +2474 -315
  142. package/dist/{chunk-TX4OGZKU.js → chunk-LO3BLMVG.js} +3 -3
  143. package/dist/{chunk-RUOGHYSK.js → chunk-LPV6T362.js} +5 -5
  144. package/dist/{chunk-2T5CQH4R.js → chunk-LQH4P45K.js} +1 -1
  145. package/dist/chunk-LU4MQVAL.js +41 -0
  146. package/dist/{chunk-HW4RRIJA.js → chunk-LWDNKE4X.js} +12 -10
  147. package/dist/chunk-M4H2UG4R.js +218 -0
  148. package/dist/{chunk-CANMOOE6.js → chunk-M6D2U3YM.js} +3 -3
  149. package/dist/{chunk-AB5E5HCN.js → chunk-MD7KUEJ3.js} +14 -272
  150. package/dist/{chunk-ENTAAEDK.js → chunk-MMZUN7ZO.js} +20 -20
  151. package/dist/{chunk-L4MHJG3I.js → chunk-MZGRDONP.js} +46 -36
  152. package/dist/{chunk-IYI2S6BB.js → chunk-N3RUGTGV.js} +18 -18
  153. package/dist/{chunk-WZRCGGXG.js → chunk-NCB6656Z.js} +7 -7
  154. package/dist/{chunk-SBE773JU.js → chunk-NRQ65LTQ.js} +5 -5
  155. package/dist/{chunk-NRPMPZWJ.js → chunk-NTAVOWYD.js} +117 -18
  156. package/dist/{chunk-UJBZS5I7.js → chunk-NYOABJNK.js} +36 -27
  157. package/dist/{chunk-LXAPJ3BL.js → chunk-O4IKAIZ2.js} +24 -20
  158. package/dist/{chunk-MMB2OHUY.js → chunk-ODMBPDDV.js} +48 -13
  159. package/dist/chunk-OKUB6RJA.js +533 -0
  160. package/dist/{chunk-MKFYQQ5Q.js → chunk-OMPENNWY.js} +4 -1
  161. package/dist/chunk-OPQZISG3.js +25 -0
  162. package/dist/{chunk-ROJMXG4E.js → chunk-OV57AHCG.js} +24 -24
  163. package/dist/{chunk-7KGXDIWV.js → chunk-OZJ3JLYI.js} +9 -8
  164. package/dist/{chunk-G5BYKJIM.js → chunk-P2VBXX7N.js} +11 -10
  165. package/dist/{chunk-YK6CSODO.js → chunk-PAINMSID.js} +4353 -1106
  166. package/dist/chunk-PKJAV25S.js +43 -0
  167. package/dist/{chunk-R5MNPIMD.js → chunk-PNQLCRSS.js} +4 -4
  168. package/dist/{chunk-3SMMFQED.js → chunk-PQH56YF6.js} +37 -5
  169. package/dist/chunk-PRTBPXYA.js +50 -0
  170. package/dist/{chunk-FF4ZMLSL.js → chunk-PRW3HXM3.js} +2 -2
  171. package/dist/chunk-PRZ7KMWL.js +42 -0
  172. package/dist/{chunk-IFV6MKYV.js → chunk-PTQ3ZALZ.js} +42 -4
  173. package/dist/{chunk-ELQSLAP6.js → chunk-PUEWDYGP.js} +2 -2
  174. package/dist/{chunk-F2ZCZYIK.js → chunk-Q26LDIUV.js} +12 -11
  175. package/dist/chunk-Q46QDDMF.js +277 -0
  176. package/dist/chunk-QBNWF5ZU.js +19 -0
  177. package/dist/{chunk-ANETFNTV.js → chunk-QE527XRY.js} +24 -21
  178. package/dist/{chunk-QNMLLYTN.js → chunk-QHJJMZLX.js} +397 -32
  179. package/dist/chunk-QJCJOMW3.js +425 -0
  180. package/dist/{chunk-3HOONZUE.js → chunk-QKMRFNMA.js} +15 -15
  181. package/dist/{chunk-7S6VJ643.js → chunk-QOOEYDNJ.js} +101 -13
  182. package/dist/{chunk-4YNKN4DF.js → chunk-QSIDVFB7.js} +20 -18
  183. package/dist/{chunk-2IT7SIJY.js → chunk-RA33EYS4.js} +19 -18
  184. package/dist/{chunk-P7NV3FEJ.js → chunk-RALY3E5U.js} +4 -4
  185. package/dist/{chunk-6NESNLVU.js → chunk-S2RY3BOD.js} +35 -21
  186. package/dist/chunk-SB5GRUFM.js +54 -0
  187. package/dist/{chunk-EUQHM2CH.js → chunk-SH27FYPF.js} +18 -18
  188. package/dist/chunk-T74WQQGS.js +40 -0
  189. package/dist/{chunk-LGDW4G73.js → chunk-TGVVUKTG.js} +2 -2
  190. package/dist/chunk-THN26IEF.js +48 -0
  191. package/dist/{chunk-ZV2VCBMQ.js → chunk-TJH2EUAZ.js} +18 -12
  192. package/dist/{chunk-NGT5LLSE.js → chunk-TQ4TG57V.js} +6 -6
  193. package/dist/{chunk-WEDXFJPG.js → chunk-TQTRWENN.js} +12 -12
  194. package/dist/{chunk-ZRWS54RI.js → chunk-TSM6T6GQ.js} +23 -23
  195. package/dist/{chunk-KET7KOVE.js → chunk-TW3KOSQK.js} +15 -7
  196. package/dist/{chunk-RBXDHCN7.js → chunk-TWQRPM76.js} +2 -2
  197. package/dist/{chunk-IO6DQCZX.js → chunk-TYD6I4CZ.js} +4 -4
  198. package/dist/{chunk-5NLJH6R4.js → chunk-U3NBK4EU.js} +56 -17
  199. package/dist/{chunk-U542BJME.js → chunk-UDXWIPCB.js} +7 -7
  200. package/dist/{chunk-OSB6AKF2.js → chunk-UEVZZZU2.js} +4 -4
  201. package/dist/{chunk-5ZV7BK22.js → chunk-UF4UHCIG.js} +2 -2
  202. package/dist/{chunk-PHJOOL6U.js → chunk-UFE36WJH.js} +4 -4
  203. package/dist/{chunk-IHRP4OYV.js → chunk-URIKUID7.js} +12 -12
  204. package/dist/chunk-UYLBRRFK.js +1106 -0
  205. package/dist/chunk-UZACEG2C.js +515 -0
  206. package/dist/{chunk-LR4XBH4L.js → chunk-VAMHOHUU.js} +12 -12
  207. package/dist/{chunk-S5PSGAQ2.js → chunk-VUVGX5L2.js} +1 -1
  208. package/dist/{chunk-2TG2QGKW.js → chunk-VYOCPTUB.js} +18 -16
  209. package/dist/{chunk-VTEDQ6GY.js → chunk-WAZEXEJH.js} +175 -3
  210. package/dist/{chunk-UTSFKZ72.js → chunk-WCEF7H6L.js} +3 -3
  211. package/dist/{chunk-DXPUYG5L.js → chunk-WEOMJHPV.js} +12 -12
  212. package/dist/{chunk-NFKBU2UH.js → chunk-WFU77T3A.js} +5 -5
  213. package/dist/chunk-WIWTJMMR.js +292 -0
  214. package/dist/{chunk-MRFSUAJH.js → chunk-WNTIOBOG.js} +18 -18
  215. package/dist/{chunk-UROTYIGJ.js → chunk-X2CUXTWU.js} +6 -6
  216. package/dist/chunk-X7EMBM5X.js +34 -0
  217. package/dist/{chunk-OO5TKEAG.js → chunk-XEQCQO66.js} +8 -8
  218. package/dist/{chunk-NBQO75XT.js → chunk-XG4WJHLQ.js} +17 -1
  219. package/dist/{chunk-ZQC7MNQB.js → chunk-XOL3GFW2.js} +101 -30
  220. package/dist/{chunk-GXDATVSY.js → chunk-XQ3YZ4QW.js} +22 -22
  221. package/dist/{chunk-GTW54D3D.js → chunk-Y6K3CJBU.js} +726 -88
  222. package/dist/{chunk-CQCAJI6G.js → chunk-YAH5IQAL.js} +5 -5
  223. package/dist/{chunk-U6TD7UOH.js → chunk-YBOVNMHC.js} +51 -14
  224. package/dist/{chunk-OJICVOZH.js → chunk-YF5DZCPN.js} +79 -39
  225. package/dist/chunk-YIGBJNH4.js +37 -0
  226. package/dist/{chunk-7C7OZARH.js → chunk-YJ3TBOTK.js} +25 -25
  227. package/dist/{chunk-HXFSM4LG.js → chunk-YKTE2RU3.js} +2 -2
  228. package/dist/{chunk-7IM4BVVH.js → chunk-YO2PKTXT.js} +268 -36
  229. package/dist/{chunk-Z3UU7XV6.js → chunk-YTTNTQOX.js} +15 -15
  230. package/dist/{chunk-2ZICHO3V.js → chunk-YWAKDNMH.js} +183 -33
  231. package/dist/{chunk-OXOSJXOI.js → chunk-YZDZOEV4.js} +4 -4
  232. package/dist/{chunk-MRCETHW5.js → chunk-Z3QFH7YG.js} +4 -4
  233. package/dist/{chunk-5KZTRD4V.js → chunk-Z6VLC5X4.js} +17 -17
  234. package/dist/{chunk-EDURU5DF.js → chunk-ZFQMMU5Y.js} +2 -2
  235. package/dist/{chunk-3GLDB4IT.js → chunk-ZKYRDV7A.js} +12 -12
  236. package/dist/{chunk-GBGJ6WSO.js → chunk-ZLS6PU2O.js} +3 -3
  237. package/dist/{chunk-JZOOX3PQ.js → chunk-ZOSD4FXV.js} +3 -3
  238. package/dist/chunk-ZPDDEVQX.js +47 -0
  239. package/dist/{chunk-2TTSX7NB.js → chunk-ZQLRCA2V.js} +7 -7
  240. package/dist/{chunk-ESP3RUUR.js → chunk-ZX7235KR.js} +128 -54
  241. package/dist/{chunk-MH4XG5XS.js → chunk-ZZFHLRQW.js} +18 -16
  242. package/dist/files.json +559 -439
  243. package/dist/packages/sdk/npm/index.js +4679 -2672
  244. package/dist/packages/ui/dist/assets/{elk-api-BuFBvNyV.js → elk-api-CowTnhEJ.js} +2 -2
  245. package/dist/packages/ui/dist/assets/index-Dw20IRhQ.css +1 -0
  246. package/dist/packages/ui/dist/assets/index-z2wv0d5d.js +47 -0
  247. package/dist/packages/ui/dist/index.html +2 -2
  248. package/package.json +3 -3
  249. package/dist/chunk-25TJC2CL.js +0 -48
  250. package/dist/chunk-3Q5RHGCA.js +0 -47
  251. package/dist/chunk-4W7FOGAB.js +0 -41
  252. package/dist/chunk-6AF3CVJT.js +0 -48
  253. package/dist/chunk-A3KEXFQS.js +0 -34
  254. package/dist/chunk-BAUINGGJ.js +0 -41
  255. package/dist/chunk-BOFUCRJX.js +0 -47
  256. package/dist/chunk-GGKGILIP.js +0 -54
  257. package/dist/chunk-IKN6C67W.js +0 -53
  258. package/dist/chunk-K7MYVIFK.js +0 -54
  259. package/dist/chunk-KHA5LVPE.js +0 -34
  260. package/dist/chunk-LSXGM6MB.js +0 -37
  261. package/dist/chunk-M54TDRM3.js +0 -29
  262. package/dist/chunk-M5GF4NAB.js +0 -45
  263. package/dist/chunk-UTZGBCXE.js +0 -38
  264. package/dist/chunk-UVTSMBM4.js +0 -43
  265. package/dist/chunk-XXE3S3OG.js +0 -44
  266. package/dist/chunk-YJRQCUVJ.js +0 -43
  267. package/dist/chunk-ZFTK4BDN.js +0 -41
  268. package/dist/chunk-ZMARAW37.js +0 -38
  269. package/dist/packages/ui/dist/assets/index-C5QnK82A.css +0 -1
  270. package/dist/packages/ui/dist/assets/index-CQ0a1cBg.js +0 -29
@@ -8,20 +8,22 @@ import {
8
8
  addStoreReference,
9
9
  readStoreMarker,
10
10
  readStoreVia
11
- } from "./chunk-43E6XX67.js";
11
+ } from "./chunk-D2WSW4GU.js";
12
12
  import {
13
13
  AdapterError,
14
14
  mergeBlockers,
15
15
  requireApproval
16
- } from "./chunk-V3UEPUHT.js";
16
+ } from "./chunk-4KNOJC6J.js";
17
17
  import {
18
18
  outsideProfileSystems,
19
19
  readCatalog
20
- } from "./chunk-EZBAS5BL.js";
20
+ } from "./chunk-4SLBCEPD.js";
21
21
  import {
22
+ baseClonePath,
22
23
  createGit,
23
- readChangeRepos
24
- } from "./chunk-ZQC7MNQB.js";
24
+ readChangeRepos,
25
+ writeChangeRepos
26
+ } from "./chunk-XOL3GFW2.js";
25
27
  import {
26
28
  ChangeArtifact,
27
29
  ChangeLifecycle,
@@ -31,6 +33,8 @@ import {
31
33
  Phase,
32
34
  RecordId,
33
35
  TestType,
36
+ askedGapQuestionId,
37
+ changeBranch,
34
38
  changesDirOf,
35
39
  changesRelOf,
36
40
  external_exports,
@@ -38,18 +42,21 @@ import {
38
42
  fastPathOf,
39
43
  handoffBranch,
40
44
  isAssistantResolution,
45
+ listQuestions,
41
46
  loadOpenSpecSettings,
42
47
  loadOrgPack,
43
48
  openspecDirOf,
44
49
  orgPackDirOf,
50
+ questionsRelOf,
45
51
  readFlow,
52
+ readQuestion,
46
53
  require_dist,
47
54
  specFlowFiles,
48
55
  toCanonicalYaml
49
- } from "./chunk-YK6CSODO.js";
56
+ } from "./chunk-PAINMSID.js";
50
57
  import {
51
58
  writeFileAtomic
52
- } from "./chunk-MMB2OHUY.js";
59
+ } from "./chunk-ODMBPDDV.js";
53
60
  import {
54
61
  __toESM
55
62
  } from "./chunk-TV45EUGM.js";
@@ -143,8 +150,11 @@ var StageStep = external_exports.strictObject({
143
150
  loop: external_exports.string().min(1),
144
151
  /** The command that runs it; `<change>` stands for the change's id. */
145
152
  command: external_exports.string().min(1),
146
- /** Files it writes, relative to the change folder; a folder ends in "/". They ride with the hand-off. */
147
- 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([]),
148
158
  /** The change type's artifacts of the phase that this stage writes: on the stage path, only these are due. */
149
159
  artifacts: external_exports.array(ChangeArtifact).default([])
150
160
  });
@@ -154,7 +164,7 @@ var DEFAULT_STAGES = [
154
164
  phase: "define",
155
165
  loop: "stage/feature.yaml",
156
166
  command: "atticus change feature <change>",
157
- writes: ["feature.yaml", "feature.md", "design/"],
167
+ writes: ["feature.yaml", "feature.md", "proposal.md", "design/"],
158
168
  artifacts: []
159
169
  },
160
170
  {
@@ -162,7 +172,7 @@ var DEFAULT_STAGES = [
162
172
  phase: "design",
163
173
  loop: "stage/architecture.yaml",
164
174
  command: "atticus change architecture <change>",
165
- writes: ["target-flows/"],
175
+ writes: ["target-flows/", "stage/architecture-deploy.yaml", "stage/architecture-reads.yaml"],
166
176
  artifacts: ["design", "data"]
167
177
  },
168
178
  {
@@ -170,10 +180,27 @@ var DEFAULT_STAGES = [
170
180
  phase: "plan",
171
181
  loop: "stage/stories.yaml",
172
182
  command: "atticus plan stories <change>",
173
- 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
+ ],
174
192
  artifacts: ["stories", "tasks"]
175
193
  }
176
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
+ }
177
204
  var Privacy = external_exports.strictObject({
178
205
  role: RecordId.default("privacy-officer"),
179
206
  classifications: external_exports.array(external_exports.string().min(1)).default(["personal", "pii", "sensitive-personal", "special-category"]),
@@ -531,7 +558,16 @@ var LEADING = [
531
558
  "from",
532
559
  "to"
533
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
+ };
534
568
  function changeFolder(root, id) {
569
+ if (!isFolderRef(id))
570
+ throw new Error(`${id} isn't a change id.`);
535
571
  return join3(changesDirOf(root), id);
536
572
  }
537
573
  function changeFilePath(root, id) {
@@ -554,6 +590,8 @@ function serializeChange(value) {
554
590
  }
555
591
  function changeStore(root) {
556
592
  async function tryRead(id) {
593
+ if (!RecordId.safeParse(id).success)
594
+ return void 0;
557
595
  const path = changeFilePath(root, id);
558
596
  let text;
559
597
  try {
@@ -593,7 +631,7 @@ function changeStore(root) {
593
631
  async list() {
594
632
  let names;
595
633
  try {
596
- 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();
597
635
  } catch {
598
636
  return [];
599
637
  }
@@ -1118,7 +1156,7 @@ async function stageFootprint(ctx, dir, h) {
1118
1156
  const was = op !== "add" && n ? today?.flow.steps[n - 1] : void 0;
1119
1157
  const kind = str(s.kind) ?? was?.kind ?? "step";
1120
1158
  const what = str(s.what) ?? was?.what ?? "";
1121
- const live = op === "add" || op === "change";
1159
+ const live2 = op === "add" || op === "change";
1122
1160
  const store = obj(s.store);
1123
1161
  const table = str(store?.table) ?? was?.store?.table;
1124
1162
  if (table) {
@@ -1131,7 +1169,7 @@ async function stageFootprint(ctx, dir, h) {
1131
1169
  if (typeof v === "string")
1132
1170
  classification[c] = v;
1133
1171
  out.stores.push({ table, columns: cols, classification, kind, cite });
1134
- if (live) {
1172
+ if (live2) {
1135
1173
  const hit = [...wordsOf(table), ...cols.flatMap(wordsOf)].filter((w) => personalWords.has(w));
1136
1174
  if (hit.length)
1137
1175
  out.signals.push({
@@ -1159,7 +1197,7 @@ async function stageFootprint(ctx, dir, h) {
1159
1197
  if (entry)
1160
1198
  o.entries.add(entry);
1161
1199
  outside.set(target, o);
1162
- if (live)
1200
+ if (live2)
1163
1201
  out.signals.push({
1164
1202
  factor: "outside-call",
1165
1203
  detail: `${target}${entry ? ` (${entry}${to?.new === true ? ", new" : ""})` : ""}`,
@@ -1183,7 +1221,7 @@ async function stageFootprint(ctx, dir, h) {
1183
1221
  out.calls.add(iface.id);
1184
1222
  }
1185
1223
  }
1186
- if (!live)
1224
+ if (!live2)
1187
1225
  continue;
1188
1226
  const fields = obj(s.fields) ?? {};
1189
1227
  const names = FIELD_LISTS.flatMap((k) => strs(fields[k]));
@@ -1439,7 +1477,7 @@ function handoffName(type, phase) {
1439
1477
  case "plan":
1440
1478
  return "Plan ready";
1441
1479
  case "build":
1442
- return "Raise PR";
1480
+ return "Build ready";
1443
1481
  case "qa":
1444
1482
  return "QA sign-off";
1445
1483
  case "release":
@@ -1495,8 +1533,15 @@ async function checkStage(root, change, step) {
1495
1533
  break;
1496
1534
  case "stopped":
1497
1535
  return reason(`The ${step.stage} stage stopped and isn't done: see what stopped it, then run ${run} again.`);
1498
- case "questions":
1499
- 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
+ }
1500
1545
  case "waiting":
1501
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.`);
1502
1547
  case "drafting":
@@ -1505,7 +1550,8 @@ async function checkStage(root, change, step) {
1505
1550
  return reason(`${step.loop} doesn't read as a stage's loop state: run ${run} again.`);
1506
1551
  }
1507
1552
  const out = [];
1508
- 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)))
1509
1555
  if (!await readFile6(join7(dir, ...w.split("/"))).then(() => true, () => false))
1510
1556
  out.push(...reason(`${w} is missing, though the ${step.stage} stage is done: run ${run} again.`));
1511
1557
  return out;
@@ -1515,7 +1561,10 @@ async function stageFiles(dir, step) {
1515
1561
  for (const w of [...step.writes, step.loop]) {
1516
1562
  if (w.endsWith("/"))
1517
1563
  out.push(...await walk(dir, w.slice(0, -1)));
1518
- 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))
1519
1568
  out.push(w);
1520
1569
  }
1521
1570
  return [...new Set(out)].sort();
@@ -1607,7 +1656,7 @@ async function checkFindings(root, change, gates) {
1607
1656
  if (l.state === "waiting")
1608
1657
  reasons.push({
1609
1658
  code: "lens-waiting",
1610
- 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})` : ""}.`
1611
1660
  });
1612
1661
  else if (l.state === "failed")
1613
1662
  reasons.push({
@@ -1634,6 +1683,16 @@ async function checkFindings(root, change, gates) {
1634
1683
  continue;
1635
1684
  }
1636
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
+ }
1637
1696
  const byAssistant = x.severity === "blocking" && isAssistantResolution(x.resolution);
1638
1697
  if (x.state !== "open" && !byAssistant)
1639
1698
  continue;
@@ -1753,8 +1812,353 @@ function prApprovers(handoff2, approvers) {
1753
1812
  return [...new Set(approvers.map(strip))].filter((p) => p !== raiser).sort();
1754
1813
  }
1755
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
+
1756
2158
  // packages/changes/dist/assess.js
1757
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.`;
1758
2162
  async function assess(ctx, change) {
1759
2163
  const type = changeTypeOf(ctx, change.type);
1760
2164
  const footprint = await changeFootprint(ctx, change);
@@ -1795,29 +2199,32 @@ async function assess(ctx, change) {
1795
2199
  ...stageReasons,
1796
2200
  ...await checkArtifacts(ctx.root, change, due),
1797
2201
  ...await checkFindings(ctx.root, change, phaseFindingGates(ctx, phase)),
1798
- ...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) : []
1799
2206
  ];
1800
2207
  const next = change.state === "returned" ? phaseState(phase) : nextState(type, phase);
1801
- const live = change.state === "returned" ? void 0 : liveHandoff(change);
2208
+ const live2 = change.state === "returned" ? void 0 : liveHandoff(change);
1802
2209
  const reasons = [...blockers];
1803
- if (!live) {
2210
+ if (!live2) {
1804
2211
  reasons.push({
1805
2212
  code: "no-handoff",
1806
2213
  message: `No hand-off PR yet: raise "${handoffName(type, phase)}" when the ${phase} work is done.`
1807
2214
  });
1808
- } else if (live.state === "changes-requested") {
2215
+ } else if (live2.state === "changes-requested") {
1809
2216
  reasons.push({
1810
2217
  code: "changes-requested",
1811
- 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.`
1812
2219
  });
1813
2220
  } else {
1814
- const approved = prApprovers(live, live.approvals.map((a) => a.by));
2221
+ const approved = prApprovers(live2, live2.approvals.map((a) => a.by));
1815
2222
  reasons.push({
1816
2223
  code: "pr-not-merged",
1817
- 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}`
1818
2225
  });
1819
2226
  }
1820
- return { ...base, phase, next, ...live ? { handoff: live } : {}, blockers, reasons };
2227
+ return { ...base, phase, next, ...live2 ? { handoff: live2 } : {}, blockers, reasons };
1821
2228
  }
1822
2229
  async function canAdvance(change, root) {
1823
2230
  const ctx = await loadChangeContext(root);
@@ -1834,9 +2241,15 @@ async function canAdvance(change, root) {
1834
2241
  async function changeReport(root, id) {
1835
2242
  const ctx = await loadChangeContext(root);
1836
2243
  const a = await assess(ctx, await changeStore(root).read(id));
1837
- 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;
1838
2251
  }
1839
- function nextStep(a) {
2252
+ function nextStep(a, tasks) {
1840
2253
  switch (a.state) {
1841
2254
  case "draft":
1842
2255
  return "Create the change with atticus change new.";
@@ -1850,25 +2263,34 @@ function nextStep(a) {
1850
2263
  return `Revise the work, then hand off ${a.phase} again.`;
1851
2264
  }
1852
2265
  const name = handoffName(a.type, a.phase);
1853
- 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}".`;
1854
2269
  return a.blockers.length ? `Finish the ${a.phase} work, then raise "${name}".` : `Ready to raise "${name}".`;
2270
+ }
1855
2271
  if (a.handoff.state === "changes-requested")
1856
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);
1857
2275
  return `Merge PR #${a.handoff.pr.number} once it is approved (${a.handoff.pr.url}); syncing then moves the change on.`;
1858
2276
  }
1859
2277
 
1860
2278
  // packages/changes/dist/new-change.js
1861
- var import_yaml7 = __toESM(require_dist(), 1);
1862
- import { mkdir, readFile as readFile8, writeFile } from "node:fs/promises";
1863
- import { join as join9 } from "node:path";
1864
- 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");
1865
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
+ }
1866
2287
  function slugify(title) {
1867
- 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;
1868
2290
  }
1869
2291
  async function exists(path) {
1870
2292
  try {
1871
- await readFile8(path);
2293
+ await readFile10(path);
1872
2294
  return true;
1873
2295
  } catch {
1874
2296
  return false;
@@ -1876,7 +2298,7 @@ async function exists(path) {
1876
2298
  }
1877
2299
  async function defaultSchema(root) {
1878
2300
  try {
1879
- 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"));
1880
2302
  if (cfg && typeof cfg.schema === "string")
1881
2303
  return cfg.schema;
1882
2304
  } catch {
@@ -1889,26 +2311,28 @@ async function newChange(root, opts) {
1889
2311
  const id = opts.id ?? slugify(opts.title);
1890
2312
  if (!RecordId.safeParse(id).success)
1891
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")}.`);
1892
2316
  const template = pickTemplate(ctx.templates, type.id, opts.template);
1893
2317
  if (!template)
1894
2318
  throw new ChangeError("invalid", `Unknown change template ${opts.template}. Use one of: ${ctx.templates.map((t) => t.id).join(", ")}.`);
1895
2319
  const store = changeStore(root);
1896
2320
  if (await store.tryRead(id).catch(() => true))
1897
2321
  throw new ChangeError("conflict", `Change ${id} already exists.`);
1898
- const now = opts.now ?? nowIso();
2322
+ const now = opts.now ?? nowIso2();
1899
2323
  const by = strip2(opts.by);
1900
2324
  const dir = changeFolder(root, id);
1901
- if (opts.openspec && !await exists(join9(dir, ".openspec.yaml"))) {
2325
+ if (opts.openspec && !await exists(join11(dir, ".openspec.yaml"))) {
1902
2326
  await opts.openspec.newChange(id, { description: opts.title });
1903
2327
  } else {
1904
2328
  await mkdir(dir, { recursive: true });
1905
- if (!await exists(join9(dir, ".openspec.yaml")))
1906
- 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)}
1907
2331
  created: ${now.slice(0, 10)}
1908
2332
  `, "utf8");
1909
2333
  }
1910
- if (!await exists(join9(dir, "brief.md")))
1911
- 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, {
1912
2336
  title: opts.title,
1913
2337
  id,
1914
2338
  type: type.id,
@@ -1947,13 +2371,23 @@ created: ${now.slice(0, 10)}
1947
2371
 
1948
2372
  // packages/changes/dist/publish.js
1949
2373
  import { rm } from "node:fs/promises";
1950
- import { join as join10 } from "node:path";
2374
+ import { randomUUID } from "node:crypto";
2375
+ import { join as join12, resolve } from "node:path";
1951
2376
  function gitHandoffPublisher(opts = {}) {
1952
2377
  const remote = opts.remote ?? "origin";
1953
2378
  const env = opts.env ?? process.env;
1954
2379
  return {
1955
- async publish({ root, branch, base, files, message, approval }) {
1956
- 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
+ {
1957
2391
  const git = createGit({ env });
1958
2392
  const out = async (args) => (await git(args, root)).stdout.trim();
1959
2393
  const tryOut = (args) => out(args).catch(() => void 0);
@@ -1971,7 +2405,7 @@ function gitHandoffPublisher(opts = {}) {
1971
2405
  "--quiet",
1972
2406
  `refs/remotes/${remote}/${base}^{commit}`
1973
2407
  ]) ?? await out(["rev-parse", "--verify", `refs/heads/${base}^{commit}`]);
1974
- const index = join10(gitDir, "atticus-handoff-index");
2408
+ const index = join12(gitDir, `atticus-handoff-index-${process.pid}-${randomUUID()}`);
1975
2409
  const staged = createGit({ env: { ...env, GIT_INDEX_FILE: index } });
1976
2410
  try {
1977
2411
  await staged(["read-tree", start], root);
@@ -1987,17 +2421,18 @@ function gitHandoffPublisher(opts = {}) {
1987
2421
  await rm(index, { force: true });
1988
2422
  }
1989
2423
  }
1990
- };
2424
+ }
1991
2425
  }
2426
+ var queue = /* @__PURE__ */ new Map();
1992
2427
 
1993
2428
  // packages/changes/dist/handoff.js
1994
- import { readdir as readdir6, readFile as readFile10 } from "node:fs/promises";
1995
- 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";
1996
2431
 
1997
2432
  // packages/changes/dist/drafted.js
1998
- var import_yaml8 = __toESM(require_dist(), 1);
1999
- import { mkdir as mkdir2, readdir as readdir5, readFile as readFile9, rm as rm2 } from "node:fs/promises";
2000
- 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";
2001
2436
  var CAPTURE_SCHEMA_VERSION = 1;
2002
2437
  var DraftCapture = external_exports.object({
2003
2438
  schemaVersion: external_exports.literal(CAPTURE_SCHEMA_VERSION),
@@ -2051,7 +2486,7 @@ function draftedPath(path, step, artifacts) {
2051
2486
  return false;
2052
2487
  if (step) {
2053
2488
  for (const w of step.writes)
2054
- if (w.endsWith("/") ? path.startsWith(w) : path === w)
2489
+ if (writeMatches(w, path))
2055
2490
  return !machinery(path);
2056
2491
  return step.artifacts.some((a) => artifactMatch(path, a));
2057
2492
  }
@@ -2072,7 +2507,7 @@ var sameText = (text, recorded) => sha256(text) === recorded || sha256(text.repl
2072
2507
  async function walk2(dir, rel = "") {
2073
2508
  let entries;
2074
2509
  try {
2075
- entries = await readdir5(join11(dir, rel), { withFileTypes: true });
2510
+ entries = await readdir5(join13(dir, rel), { withFileTypes: true });
2076
2511
  } catch {
2077
2512
  return [];
2078
2513
  }
@@ -2217,7 +2652,7 @@ async function captureHandoff(input) {
2217
2652
  } else {
2218
2653
  const texts = await Promise.all(recorded.map(async (r) => [
2219
2654
  r,
2220
- await readFile9(join11(folder, ...r.path.split("/")), "utf8").catch(() => void 0)
2655
+ await readFile11(join13(folder, ...r.path.split("/")), "utf8").catch(() => void 0)
2221
2656
  ]));
2222
2657
  if (texts.every(([r, t]) => t !== void 0 && sameText(t, r.sha256))) {
2223
2658
  from = "working-tree";
@@ -2281,10 +2716,10 @@ async function captureHandoff(input) {
2281
2716
  } : {}
2282
2717
  };
2283
2718
  const p = capturePaths(name);
2284
- await rm2(join11(folder, ...p.drafted.split("/")), { recursive: true, force: true });
2285
- 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 });
2286
2721
  const put = async (base, rel, text) => {
2287
- const f = join11(folder, ...base.split("/"), ...rel.split("/"));
2722
+ const f = join13(folder, ...base.split("/"), ...rel.split("/"));
2288
2723
  await mkdir2(dirname(f), { recursive: true });
2289
2724
  await writeFileAtomic(f, text);
2290
2725
  };
@@ -2293,37 +2728,37 @@ async function captureHandoff(input) {
2293
2728
  if (state === "captured")
2294
2729
  for (const [rel, t] of mergedFiles)
2295
2730
  await put(p.merged, rel, t);
2296
- await mkdir2(join11(folder, "stage"), { recursive: true });
2297
- 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 }));
2298
2733
  return { capture, file: p.capture };
2299
2734
  }
2300
2735
  async function readFolderFiles(dir) {
2301
2736
  const out = /* @__PURE__ */ new Map();
2302
2737
  for (const f of await walk2(dir))
2303
- 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"));
2304
2739
  return out;
2305
2740
  }
2306
2741
  async function changeFolders(root) {
2307
- const base = join11(root, ...changesRelOf(root).split("/"));
2742
+ const base = join13(root, ...changesRelOf(root).split("/"));
2308
2743
  const out = [];
2309
2744
  const list = async (dir) => (await readdir5(dir, { withFileTypes: true }).catch(() => [])).filter((e) => e.isDirectory()).map((e) => e.name).sort();
2310
2745
  for (const name of await list(base)) {
2311
2746
  if (name === "archive")
2312
- for (const a of await list(join11(base, "archive")))
2313
- out.push(join11(base, "archive", a));
2747
+ for (const a of await list(join13(base, "archive")))
2748
+ out.push(join13(base, "archive", a));
2314
2749
  else
2315
- out.push(join11(base, name));
2750
+ out.push(join13(base, name));
2316
2751
  }
2317
2752
  return out;
2318
2753
  }
2319
2754
  async function readDraftPairs(root) {
2320
2755
  const out = [];
2321
2756
  for (const folder of await changeFolders(root)) {
2322
- 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();
2323
2758
  for (const f of names) {
2324
2759
  let capture;
2325
2760
  try {
2326
- 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")));
2327
2762
  } catch {
2328
2763
  continue;
2329
2764
  }
@@ -2331,8 +2766,8 @@ async function readDraftPairs(root) {
2331
2766
  out.push({
2332
2767
  capture,
2333
2768
  folder: relative(root, folder).split(sep).join("/"),
2334
- drafted: await readFolderFiles(join11(folder, ...p.drafted.split("/"))),
2335
- 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("/")))
2336
2771
  });
2337
2772
  }
2338
2773
  }
@@ -2352,8 +2787,8 @@ async function completeCaptures(root, ctx, o) {
2352
2787
  continue;
2353
2788
  for (const h of change.handoffs.filter((x) => x.state === "merged")) {
2354
2789
  const { name } = await captureStageOf(ctx, change, h.phase);
2355
- const file = join11(changeFolder(root, change.id), ...capturePaths(name).capture.split("/"));
2356
- 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);
2357
2792
  if (prior?.success && prior.data.pr.number === h.pr.number && prior.data.state !== "waiting-for-merge")
2358
2793
  continue;
2359
2794
  const base = prior?.success ? prior.data.base : o.defaultBase ?? "main";
@@ -2375,7 +2810,7 @@ async function completeCaptures(root, ctx, o) {
2375
2810
  }
2376
2811
 
2377
2812
  // packages/changes/dist/handoff.js
2378
- var nowIso2 = () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
2813
+ var nowIso3 = () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
2379
2814
  var strip3 = (h) => h.startsWith("@") ? h.slice(1) : h;
2380
2815
  function metarepo(ctx, repo) {
2381
2816
  const fromRuntime = ctx.orgPack.runtime.adapters.gitHost?.settings.metarepo;
@@ -2393,7 +2828,7 @@ async function load(root, id) {
2393
2828
  }
2394
2829
  async function walkReview(dir, gate) {
2395
2830
  const out = [];
2396
- const base = join12(dir, "review", gate);
2831
+ const base = join14(dir, "review", gate);
2397
2832
  let rounds;
2398
2833
  try {
2399
2834
  rounds = (await readdir6(base)).filter((r) => /^\d+$/.test(r)).sort((a, b) => Number(a) - Number(b));
@@ -2402,7 +2837,7 @@ async function walkReview(dir, gate) {
2402
2837
  }
2403
2838
  for (const r of rounds) {
2404
2839
  try {
2405
- 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())
2406
2841
  out.push(`review/${gate}/${r}/${f}`);
2407
2842
  } catch {
2408
2843
  }
@@ -2413,12 +2848,12 @@ async function keptPairFiles(dir) {
2413
2848
  const out = [];
2414
2849
  let entries;
2415
2850
  try {
2416
- entries = await readdir6(join12(dir, "stage"), { withFileTypes: true });
2851
+ entries = await readdir6(join14(dir, "stage"), { withFileTypes: true });
2417
2852
  } catch {
2418
2853
  return out;
2419
2854
  }
2420
2855
  const walk3 = async (rel) => {
2421
- 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(() => [])) {
2422
2857
  const p = `${rel}/${e.name}`;
2423
2858
  if (e.isDirectory())
2424
2859
  await walk3(p);
@@ -2446,18 +2881,23 @@ async function handoffFiles(ctx, a, phase) {
2446
2881
  for (const x of f)
2447
2882
  files.add(x);
2448
2883
  }
2449
- const stage = await phaseStage(ctx, a.change, phase);
2450
- if (stage)
2451
- for (const f of await stageFiles(dir, stage))
2452
- 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);
2453
2894
  if (phase === "build") {
2454
- for (const extra of ["tasks.md", "repos.yaml"]) {
2455
- try {
2456
- await readFile10(join12(dir, extra));
2895
+ for (const extra of ["tasks.md", "repos.yaml"])
2896
+ if (await present(extra))
2457
2897
  files.add(extra);
2458
- } catch {
2459
- }
2460
- }
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`);
2461
2901
  }
2462
2902
  for (const gate of phaseGates(ctx, phase))
2463
2903
  for (const f of await walkReview(dir, gate))
@@ -2466,6 +2906,18 @@ async function handoffFiles(ctx, a, phase) {
2466
2906
  files.add(f);
2467
2907
  return { files: [...files].sort(), artifacts };
2468
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
+ }
2469
2921
  function prBody(a, phase, next, paths, reviewers, review) {
2470
2922
  const c = a.change;
2471
2923
  const lines2 = [
@@ -2500,7 +2952,7 @@ async function handoff(root, id, opts) {
2500
2952
  requireApproval("changes", opts.approval);
2501
2953
  const { ctx, change } = await load(root, id);
2502
2954
  const repo = metarepo(ctx, opts.repo);
2503
- const now = opts.now ?? nowIso2();
2955
+ const now = opts.now ?? nowIso3();
2504
2956
  const by = strip3(opts.by);
2505
2957
  const a = await assess(ctx, change);
2506
2958
  const phase = a.phase;
@@ -2512,7 +2964,7 @@ async function handoff(root, id, opts) {
2512
2964
  await requireValid(opts.openspec, id);
2513
2965
  const { files, artifacts } = await handoffFiles(ctx, a, phase);
2514
2966
  const dir = changeFolder(root, id);
2515
- 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))) })));
2516
2968
  const authors = { ...change.authors };
2517
2969
  for (const art of artifacts) {
2518
2970
  const people = (opts.authors?.[art] ?? [by]).map(strip3);
@@ -2520,13 +2972,18 @@ async function handoff(root, id, opts) {
2520
2972
  }
2521
2973
  const branch = handoffBranch(id, phase);
2522
2974
  const rel = changesRelOf(root);
2523
- 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
+ ];
2524
2981
  const reviewers = [
2525
2982
  ...new Set(files.flatMap((f) => ownersFor(ctx.codeowners, `${rel}/${id}/${f}`)))
2526
2983
  ].filter((p) => p !== by).sort();
2527
- const live = change.state === "returned" ? void 0 : liveHandoff(change);
2528
- if (live && live.phase === phase) {
2529
- 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);
2530
2987
  if (pr2.state === "open" || pr2.state === "draft") {
2531
2988
  await opts.publisher?.publish({
2532
2989
  root,
@@ -2536,9 +2993,9 @@ async function handoff(root, id, opts) {
2536
2993
  message: `${handoffName(a.type, phase)}: update`,
2537
2994
  approval: opts.approval
2538
2995
  });
2539
- 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);
2540
2997
  const updated = {
2541
- ...live,
2998
+ ...live2,
2542
2999
  state: "open",
2543
3000
  updatedAt: now,
2544
3001
  reviewers,
@@ -2548,14 +3005,14 @@ async function handoff(root, id, opts) {
2548
3005
  await changeStore(root).write({
2549
3006
  ...change,
2550
3007
  authors,
2551
- handoffs: change.handoffs.map((h2) => h2 === live ? updated : h2)
3008
+ handoffs: change.handoffs.map((h2) => h2 === live2 ? updated : h2)
2552
3009
  });
2553
3010
  return updated;
2554
3011
  }
2555
3012
  if (pr2.state === "merged")
2556
3013
  throw new ChangeError("refused", `PR #${pr2.number} is already merged on the Git host; sync the change instead.`);
2557
- live.state = "closed";
2558
- live.updatedAt = now;
3014
+ live2.state = "closed";
3015
+ live2.updatedAt = now;
2559
3016
  }
2560
3017
  const base = opts.base ?? (await opts.gitHost.getRepo(repo)).defaultBranch;
2561
3018
  const next = a.next;
@@ -2572,7 +3029,7 @@ async function handoff(root, id, opts) {
2572
3029
  head: branch,
2573
3030
  base,
2574
3031
  title: `${change.title}: ${handoffName(a.type, phase)}`,
2575
- 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)
2576
3033
  }, opts.approval);
2577
3034
  const h = {
2578
3035
  phase,
@@ -2604,24 +3061,36 @@ function nextAfterResubmit(a, phase) {
2604
3061
  const next = a.type.phases[a.type.phases.indexOf(phase) + 1];
2605
3062
  return next ? phaseState(next) : "released";
2606
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
+ }
2607
3076
  async function syncHandoff(root, id, opts) {
2608
3077
  const { ctx, change } = await load(root, id);
2609
3078
  const repo = metarepo(ctx, opts.repo);
2610
- const now = opts.now ?? nowIso2();
2611
- const live = liveHandoff(change);
2612
- if (!live)
3079
+ const now = opts.now ?? nowIso3();
3080
+ const live2 = liveHandoff(change);
3081
+ if (!live2)
2613
3082
  throw new ChangeError("refused", `${id} has no open hand-off PR to sync.`, [
2614
3083
  { code: "no-handoff", message: `${id} has no open hand-off PR.` }
2615
3084
  ]);
2616
3085
  const a = await assess(ctx, change);
2617
- const pr = await opts.gitHost.getPullRequest(repo, live.pr.number);
2618
- 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;
2619
3088
  const store = changeStore(root);
2620
3089
  const save = async (h2, extra = {}) => {
2621
3090
  const updated = {
2622
3091
  ...change,
2623
3092
  ...extra,
2624
- handoffs: change.handoffs.map((x) => x === live ? h2 : x)
3093
+ handoffs: change.handoffs.map((x) => x === live2 ? h2 : x)
2625
3094
  };
2626
3095
  await store.write(updated);
2627
3096
  return updated;
@@ -2643,7 +3112,7 @@ async function syncHandoff(root, id, opts) {
2643
3112
  latest.set(strip3(r.by), r.state);
2644
3113
  const approvers = [...latest].filter(([, s]) => s === "approved").map(([p]) => p);
2645
3114
  const requested = [...latest].filter(([, s]) => s === "changes-requested").map(([p]) => p).sort();
2646
- const counted = prApprovers(live, approvers);
3115
+ const counted = prApprovers(live2, approvers);
2647
3116
  const approvals = counted.map((by) => ({ by, at: now }));
2648
3117
  const advance = async (by) => {
2649
3118
  const h2 = { ...current, state: "merged", updatedAt: now, approvals };
@@ -2656,7 +3125,7 @@ async function syncHandoff(root, id, opts) {
2656
3125
  at: now,
2657
3126
  by,
2658
3127
  pr: pr.number,
2659
- reason: `${handoffName(a.type, live.phase)} merged`
3128
+ reason: `${handoffName(a.type, live2.phase)} merged`
2660
3129
  }
2661
3130
  ];
2662
3131
  let state = next;
@@ -2691,7 +3160,7 @@ async function syncHandoff(root, id, opts) {
2691
3160
  };
2692
3161
  };
2693
3162
  if (pr.state === "merged")
2694
- return advance(strip3(opts.approval?.by ?? live.by));
3163
+ return advance(strip3(opts.approval?.by ?? live2.by));
2695
3164
  if (requested.length > 0) {
2696
3165
  const h2 = { ...current, state: "changes-requested", updatedAt: now, approvals };
2697
3166
  await save(h2);
@@ -2712,8 +3181,8 @@ async function syncHandoff(root, id, opts) {
2712
3181
  if (counted.length === 0)
2713
3182
  reasons.push(approvers.length > 0 ? {
2714
3183
  code: "self-approval",
2715
- person: strip3(live.by),
2716
- 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.`
2717
3186
  } : {
2718
3187
  code: "approval-missing",
2719
3188
  message: `PR #${pr.number} isn't approved yet: ask a reviewer on the Git host (${pr.url}).`
@@ -2752,19 +3221,19 @@ async function syncHandoff(root, id, opts) {
2752
3221
  }
2753
3222
  async function returnToPo(root, id, opts) {
2754
3223
  const { ctx, change } = await load(root, id);
2755
- const live = liveHandoff(change);
2756
- if (!live)
3224
+ const live2 = liveHandoff(change);
3225
+ if (!live2)
2757
3226
  throw new ChangeError("refused", `${id} has no open hand-off PR to return.`, [
2758
3227
  { code: "no-handoff", message: `${id} has no open hand-off PR.` }
2759
3228
  ]);
2760
3229
  requireApproval("changes", opts.approval);
2761
3230
  const repo = metarepo(ctx, opts.repo);
2762
- const now = opts.now ?? nowIso2();
3231
+ const now = opts.now ?? nowIso3();
2763
3232
  const by = strip3(opts.by);
2764
3233
  const host = opts.gitHost;
2765
- await host.comment(repo, live.pr.number, `Returned to the product owner by ${by}: ${opts.reason}`, opts.approval);
2766
- const closedOnHost = host.closePullRequest ? (await host.closePullRequest(repo, live.pr.number, opts.approval)).state === "closed" : false;
2767
- 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 };
2768
3237
  await changeStore(root).write({
2769
3238
  ...change,
2770
3239
  state: "returned",
@@ -2775,11 +3244,11 @@ async function returnToPo(root, id, opts) {
2775
3244
  to: "returned",
2776
3245
  at: now,
2777
3246
  by,
2778
- pr: live.pr.number,
3247
+ pr: live2.pr.number,
2779
3248
  reason: `Returned to the product owner (${opts.reason})`
2780
3249
  }
2781
3250
  ],
2782
- handoffs: change.handoffs.map((x) => x === live ? h : x)
3251
+ handoffs: change.handoffs.map((x) => x === live2 ? h : x)
2783
3252
  });
2784
3253
  return { state: "returned", closedOnHost, handoff: h };
2785
3254
  }
@@ -2799,7 +3268,7 @@ async function archiveChange(root, id, opts) {
2799
3268
  throw new ChangeError("refused", `${id} can't be archived yet.`, a.reasons);
2800
3269
  if (opts.openspec)
2801
3270
  await requireValid(opts.openspec, id);
2802
- const now = opts.now ?? nowIso2();
3271
+ const now = opts.now ?? nowIso3();
2803
3272
  const updated = {
2804
3273
  ...change,
2805
3274
  state: "archived",
@@ -2815,9 +3284,9 @@ async function archiveChange(root, id, opts) {
2815
3284
  }
2816
3285
 
2817
3286
  // packages/changes/dist/learn.js
2818
- var import_yaml9 = __toESM(require_dist(), 1);
2819
- import { readFile as readFile11 } from "node:fs/promises";
2820
- 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";
2821
3290
  var LEARN_SCHEMA_VERSION = 1;
2822
3291
  var MAX_CELLS = 4e6;
2823
3292
  var lines = (text) => {
@@ -3164,7 +3633,7 @@ async function pairTouches(root, p) {
3164
3633
  let entry;
3165
3634
  const yaml = (t) => {
3166
3635
  try {
3167
- const v = (0, import_yaml9.parse)(t);
3636
+ const v = (0, import_yaml10.parse)(t);
3168
3637
  return v && typeof v === "object" ? v : void 0;
3169
3638
  } catch {
3170
3639
  return void 0;
@@ -3206,7 +3675,7 @@ async function pairTouches(root, p) {
3206
3675
  flows.push(m[1]);
3207
3676
  }
3208
3677
  }
3209
- 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(() => ""));
3210
3679
  const touches = change?.touches ?? {};
3211
3680
  for (const r of Array.isArray(touches.repos) ? touches.repos : [])
3212
3681
  if (typeof r === "string")
@@ -3299,8 +3768,8 @@ async function detectConflicts(root) {
3299
3768
  }
3300
3769
 
3301
3770
  // packages/changes/dist/size.js
3302
- import { readFile as readFile12 } from "node:fs/promises";
3303
- import { join as join14 } from "node:path";
3771
+ import { readFile as readFile14 } from "node:fs/promises";
3772
+ import { join as join16 } from "node:path";
3304
3773
  async function changeSize(ctx, change) {
3305
3774
  const type = ctx.orgPack.changeTypes.types.find((t) => t.id === change.type);
3306
3775
  if (!type)
@@ -3309,7 +3778,7 @@ async function changeSize(ctx, change) {
3309
3778
  why: "standard",
3310
3779
  words: `${change.type} isn't a change type in change-types.yaml, so ${change.id} takes the full path.`
3311
3780
  };
3312
- 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(() => "");
3313
3782
  const f = fastPathOf(type, brief);
3314
3783
  const gate = f.fast ? fastGateOf(type, ctx.orgPack.gates) : void 0;
3315
3784
  const words = {
@@ -3323,14 +3792,14 @@ async function changeSize(ctx, change) {
3323
3792
  }
3324
3793
 
3325
3794
  // packages/changes/dist/openspec-stores.js
3326
- import { existsSync } from "node:fs";
3327
- 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";
3328
3797
  import { homedir } from "node:os";
3329
- 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";
3330
3799
  function storeDirOf(root, path) {
3331
3800
  if (path === "~" || path.startsWith("~/") || path.startsWith("~\\"))
3332
- return join15(homedir(), path.slice(2));
3333
- return isAbsolute(path) ? resolve(path) : resolve(root, path);
3801
+ return join17(homedir(), path.slice(2));
3802
+ return isAbsolute(path) ? resolve2(path) : resolve2(root, path);
3334
3803
  }
3335
3804
  function packRel(root) {
3336
3805
  return relative2(root, orgPackDirOf(root)).split(sep2).join("/");
@@ -3359,7 +3828,7 @@ async function storeSpecLookup(root, capability) {
3359
3828
  var REMOTE = /^[a-z][a-z0-9+.-]*:\/\//i;
3360
3829
  var SCP = /^[^/\\]+@[^:/\\]+:/;
3361
3830
  async function metarepoOrigin(root, git = createGit()) {
3362
- if (!existsSync(join15(root, ".git")))
3831
+ if (!existsSync2(join17(root, ".git")))
3363
3832
  return void 0;
3364
3833
  try {
3365
3834
  const url = (await git(["remote", "get-url", "origin"], root)).stdout.trim();
@@ -3382,8 +3851,8 @@ async function referenceMetarepoIn(root, repoDir, o = {}) {
3382
3851
  file,
3383
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("; ")})`
3384
3853
  };
3385
- const path = join15(repoDir, "openspec", "config.yaml");
3386
- 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);
3387
3856
  if (text === void 0)
3388
3857
  return {
3389
3858
  action: "skipped",
@@ -3395,10 +3864,180 @@ async function referenceMetarepoIn(root, repoDir, o = {}) {
3395
3864
  if ("problem" in r)
3396
3865
  return { action: "skipped", file, why: r.problem };
3397
3866
  if (r.action === "added")
3398
- await writeFile2(path, r.text, "utf8");
3867
+ await writeFile3(path, r.text, "utf8");
3399
3868
  return { action: r.action, file };
3400
3869
  }
3401
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
+
3402
4041
  export {
3403
4042
  ChangeError,
3404
4043
  readTables,
@@ -3406,6 +4045,8 @@ export {
3406
4045
  isFilled,
3407
4046
  CHANGE_POLICY_FILE,
3408
4047
  StageStep,
4048
+ isWritePattern,
4049
+ writeMatches,
3409
4050
  SIGNAL_FACTORS,
3410
4051
  ChangePolicy,
3411
4052
  DEFAULT_CHANGE_POLICY,
@@ -3463,10 +4104,20 @@ export {
3463
4104
  workProgress,
3464
4105
  privacyImpact,
3465
4106
  prApprovers,
4107
+ RELEASE_PR_STATES,
4108
+ RELEASE_PRS_FILE,
4109
+ nothingToRelease,
4110
+ raiseReleasePrs,
4111
+ syncReleasePrs,
4112
+ requireMergedPrs,
4113
+ releasePrReasons,
3466
4114
  liveHandoff,
4115
+ isManualPrUrl,
4116
+ manualHandoffWords,
3467
4117
  assess,
3468
4118
  canAdvance,
3469
4119
  changeReport,
4120
+ windowsUnsafeName,
3470
4121
  slugify,
3471
4122
  newChange,
3472
4123
  gitHandoffPublisher,
@@ -3478,6 +4129,7 @@ export {
3478
4129
  captureHandoff,
3479
4130
  readDraftPairs,
3480
4131
  completeCaptures,
4132
+ namesChange,
3481
4133
  handoff,
3482
4134
  syncHandoff,
3483
4135
  returnToPo,
@@ -3503,5 +4155,10 @@ export {
3503
4155
  openspecStores,
3504
4156
  storeSpecLookup,
3505
4157
  metarepoOrigin,
3506
- referenceMetarepoIn
4158
+ referenceMetarepoIn,
4159
+ mergeChangeRecords,
4160
+ mergeChangeText,
4161
+ OWNER_ROLE_KEYS,
4162
+ ChangeOwnersError,
4163
+ setChangeOwners
3507
4164
  };