@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.
- package/dist/bin.js +8 -4
- package/dist/{chunk-MUSJJ6CQ.js → chunk-265LWMOA.js} +16 -16
- package/dist/chunk-26WBY34Y.js +37 -0
- package/dist/{chunk-6H5JG5GL.js → chunk-26Z7OGQW.js} +63 -30
- package/dist/{chunk-4REDBQOZ.js → chunk-2D7PWSOK.js} +20 -20
- package/dist/{chunk-YPH6ZJ6R.js → chunk-2HH3UCZM.js} +4 -4
- package/dist/{chunk-RTUUX7PX.js → chunk-2HTEMYTB.js} +12 -26
- package/dist/{chunk-EHQUY26D.js → chunk-2UCRFXFZ.js} +11 -11
- package/dist/{chunk-ILDGIXNH.js → chunk-2V2ZPQQU.js} +81 -13
- package/dist/{chunk-YSEZQ6D3.js → chunk-36RY7ZXM.js} +107 -64
- package/dist/{chunk-QXFDM2FZ.js → chunk-37WE3LS2.js} +3 -3
- package/dist/{chunk-BAXM3JFQ.js → chunk-3AQ2R5NL.js} +1212 -754
- package/dist/chunk-3G6S7LAA.js +43 -0
- package/dist/{chunk-7TOAS7KI.js → chunk-3J5I7ZOU.js} +4 -4
- package/dist/{chunk-TAKAEJRD.js → chunk-3LZ6P5EP.js} +1 -1
- package/dist/chunk-3SSTNB4E.js +191 -0
- package/dist/chunk-3TD6S4TC.js +49 -0
- package/dist/{chunk-UUL6ZGMU.js → chunk-3UTYPHN3.js} +1 -1
- package/dist/{chunk-6AP2ZUUM.js → chunk-3WON4G3M.js} +2 -2
- package/dist/{chunk-AXY6LBW4.js → chunk-42EPP7OB.js} +12 -12
- package/dist/{chunk-QKMDT7VU.js → chunk-455SYIIE.js} +2 -2
- package/dist/{chunk-MFUFB3LN.js → chunk-4B6BT75O.js} +110 -20
- package/dist/{chunk-75MYLZSB.js → chunk-4CLK2RJO.js} +7 -7
- package/dist/chunk-4CYTJ5TZ.js +38 -0
- package/dist/{chunk-IH7RANGB.js → chunk-4D62IZII.js} +5 -5
- package/dist/{chunk-2QMZOCWN.js → chunk-4FOAYLI4.js} +14 -7
- package/dist/{chunk-V3UEPUHT.js → chunk-4KNOJC6J.js} +294 -215
- package/dist/{chunk-O6ILVBYB.js → chunk-4PYC2UGC.js} +1 -1
- package/dist/{chunk-EZBAS5BL.js → chunk-4SLBCEPD.js} +82 -50
- package/dist/{chunk-VIRTPODJ.js → chunk-4TKVU2I2.js} +6 -14
- package/dist/{chunk-IVCDU443.js → chunk-4WXRPQDE.js} +281 -51
- package/dist/{chunk-RMEEHH24.js → chunk-5CMPVC3N.js} +24 -24
- package/dist/chunk-5G7ILBUG.js +66 -0
- package/dist/{chunk-Z75XDWQR.js → chunk-5PV7S2ZP.js} +3 -3
- package/dist/{chunk-WWPC2AZ2.js → chunk-5QUOPNLN.js} +4 -4
- package/dist/{chunk-7OVH2L7L.js → chunk-5TDRBOAV.js} +5 -5
- package/dist/{chunk-YXUIUOQZ.js → chunk-5WERBKLE.js} +199 -63
- package/dist/{chunk-6WNNXEG6.js → chunk-62DLJSLV.js} +8 -8
- package/dist/{chunk-KCQS52TH.js → chunk-64AADMTW.js} +99 -25
- package/dist/{chunk-KLRUXRPN.js → chunk-67BP7HXL.js} +17 -17
- package/dist/chunk-6DQYZU2W.js +379 -0
- package/dist/{chunk-RONDYDMQ.js → chunk-6EXDI5GI.js} +51 -34
- package/dist/chunk-6FNAKCET.js +38 -0
- package/dist/{chunk-6SNEA73E.js → chunk-75ASLHBT.js} +21 -15
- package/dist/{chunk-EFT4KM6K.js → chunk-75ZOZFGH.js} +18 -18
- package/dist/{chunk-B3KW7EQW.js → chunk-77O633L5.js} +12 -12
- package/dist/{chunk-TLDL2ZZI.js → chunk-7AB6OWTV.js} +3 -3
- package/dist/{chunk-R5YXY6NT.js → chunk-7CZHUZBL.js} +25 -25
- package/dist/{chunk-F4D4G2A6.js → chunk-7FHNFCHK.js} +306 -125
- package/dist/{chunk-CC5RNYZF.js → chunk-7FUIVIBG.js} +19 -9
- package/dist/{chunk-UBTOSSNK.js → chunk-7QPCGTPS.js} +21 -21
- package/dist/{chunk-CDLTRJ4N.js → chunk-A2G72UI4.js} +7 -7
- package/dist/{chunk-YGSOQ5LX.js → chunk-AE4P57KK.js} +23 -17
- package/dist/{chunk-PGQL5YGH.js → chunk-AHW3RY3X.js} +7 -7
- package/dist/chunk-AM6FP6JW.js +43 -0
- package/dist/{chunk-DYNC6BAQ.js → chunk-AOCWFA7T.js} +93 -10
- package/dist/{chunk-CYX36GKZ.js → chunk-API5TOUB.js} +1 -1
- package/dist/{chunk-BJ5T3KDF.js → chunk-AZDITBCT.js} +3 -3
- package/dist/{chunk-U6GZSUMC.js → chunk-BA2HUMDN.js} +25 -3
- package/dist/chunk-BA3U7ALS.js +99 -0
- package/dist/{chunk-63565QTF.js → chunk-BC6KKZ67.js} +5 -5
- package/dist/{chunk-XO5RQKNM.js → chunk-BEPCKDCP.js} +1843 -577
- package/dist/{chunk-KUH62AQ3.js → chunk-BPPU3PW7.js} +4 -4
- package/dist/{chunk-52ULYRV4.js → chunk-BVWWPBKD.js} +18 -18
- package/dist/{chunk-R2FGEBIU.js → chunk-BXZ4PKIT.js} +13 -11
- package/dist/{chunk-4BU26GFB.js → chunk-C7CEK3KW.js} +4 -4
- package/dist/chunk-CFH3QH26.js +29 -0
- package/dist/chunk-CG6CULP6.js +170 -0
- package/dist/chunk-CLD4RVTA.js +45 -0
- package/dist/{chunk-BU543ZEC.js → chunk-CNPY4NTZ.js} +13 -13
- package/dist/{chunk-PLBIWEPV.js → chunk-CNU5Q2D5.js} +32 -31
- package/dist/{chunk-JTYYEYRR.js → chunk-COYN3UAN.js} +124 -21
- package/dist/{chunk-TNCROWPQ.js → chunk-CPK7QSBG.js} +10 -10
- package/dist/{chunk-43E6XX67.js → chunk-D2WSW4GU.js} +1 -1
- package/dist/{chunk-SHEUEHKC.js → chunk-D57MFJPP.js} +3 -3
- package/dist/chunk-D5KV5PSK.js +45 -0
- package/dist/chunk-D5MASS4I.js +48 -0
- package/dist/{chunk-TQYBXDVH.js → chunk-DCWN3HFV.js} +1 -1
- package/dist/{chunk-FF4IGX4Z.js → chunk-DDJPM5YX.js} +2 -2
- package/dist/chunk-DN4A2Q2O.js +57 -0
- package/dist/{chunk-4ZCFCIEL.js → chunk-DUPZPDCJ.js} +52 -16
- package/dist/{chunk-CUJ5TIEB.js → chunk-E2VXBSIR.js} +4 -4
- package/dist/{chunk-BX47X5DJ.js → chunk-E57AOFYN.js} +13 -13
- package/dist/{chunk-SSTI7GAV.js → chunk-ECLXZWAG.js} +12 -12
- package/dist/{chunk-KYK3TA4Q.js → chunk-EDZ45WIR.js} +14 -14
- package/dist/chunk-EFCQSMXB.js +37 -0
- package/dist/{chunk-YC2K3FJG.js → chunk-EZIN5E5B.js} +8 -8
- package/dist/{chunk-MIZNACDI.js → chunk-F6H2RJ3N.js} +1 -1
- package/dist/{chunk-EMZVHDJP.js → chunk-FAJDKNSD.js} +7 -3
- package/dist/chunk-FGAGWYZB.js +47 -0
- package/dist/{chunk-KBGQ3IAM.js → chunk-FLVZFTLQ.js} +18 -18
- package/dist/{chunk-2NNXLH53.js → chunk-FQ6WH6PB.js} +22 -7
- package/dist/{chunk-QWSLMVHI.js → chunk-FRHD22WM.js} +61 -23
- package/dist/{chunk-5MEN4SM5.js → chunk-FVMPFCDS.js} +4 -4
- package/dist/{chunk-KLRZKW7Y.js → chunk-FY3QIRTL.js} +3 -3
- package/dist/{chunk-B3SWPNJF.js → chunk-FYBF5MB2.js} +13 -11
- package/dist/{chunk-CVZCW3NI.js → chunk-G5YJBNDZ.js} +14 -5
- package/dist/{chunk-U6EPS5PF.js → chunk-G6IQYM25.js} +5 -5
- package/dist/chunk-G6X4YL4D.js +161 -0
- package/dist/{chunk-DOSX3MRW.js → chunk-GC2VK3JT.js} +25 -25
- package/dist/{chunk-EEWBTFRP.js → chunk-GCS3FZOB.js} +27 -3
- package/dist/{chunk-6H6OPNEP.js → chunk-GDZB57IU.js} +5 -5
- package/dist/chunk-GIFMBCTW.js +221 -0
- package/dist/{chunk-X5WYPIH3.js → chunk-GK4PYVEC.js} +18 -8
- package/dist/{chunk-VBSJ5NRB.js → chunk-GNWAOVFQ.js} +5 -5
- package/dist/{chunk-TXFDQRBL.js → chunk-GS76RXQE.js} +11 -11
- package/dist/{chunk-U3YC4RGC.js → chunk-GSGIZ2OO.js} +23 -23
- package/dist/chunk-GUYL7RMK.js +60 -0
- package/dist/{chunk-AZYBHPV4.js → chunk-GX74YWR7.js} +14 -13
- package/dist/{chunk-YY7VP7KW.js → chunk-HE3HRDNZ.js} +2 -2
- package/dist/{chunk-Z7HYYDZM.js → chunk-HGHRSF3E.js} +14 -13
- package/dist/{chunk-Q2X4IREC.js → chunk-HK3VOHIE.js} +106 -36
- package/dist/{chunk-P5JYMO7Y.js → chunk-HS4BGB4G.js} +22 -22
- package/dist/{chunk-IOW6BF3U.js → chunk-HSZMPH5T.js} +798 -141
- package/dist/{chunk-L3T33FA7.js → chunk-HWD4JPU7.js} +4 -2
- package/dist/{chunk-7NYCH5HY.js → chunk-HXXP2NTE.js} +6 -6
- package/dist/{chunk-7S7OFVIU.js → chunk-I2A3Z5AK.js} +4 -4
- package/dist/{chunk-WQSDKX7M.js → chunk-ICHDSWEY.js} +7 -7
- package/dist/chunk-IFFRGC3N.js +31 -0
- package/dist/{chunk-HMZQAIR5.js → chunk-IFHDV6KQ.js} +24 -17
- package/dist/chunk-J2O4LFVI.js +80 -0
- package/dist/chunk-J5AAZ6OJ.js +161 -0
- package/dist/{chunk-CVRWAG76.js → chunk-JAABT3DA.js} +3 -3
- package/dist/chunk-JITJP64K.js +34 -0
- package/dist/chunk-JKIGL3XQ.js +162 -0
- package/dist/chunk-JRVNXLZP.js +615 -0
- package/dist/chunk-JRXHON4E.js +41 -0
- package/dist/{chunk-XFCK5B27.js → chunk-JS6WMC3J.js} +8 -8
- package/dist/chunk-JU4QNJSR.js +62 -0
- package/dist/{chunk-IPSFMTK2.js → chunk-JYMIB5XY.js} +152 -42
- package/dist/{chunk-L336AJCZ.js → chunk-JYTIHTIK.js} +8 -6
- package/dist/{chunk-VEJYZEPC.js → chunk-K332XW7K.js} +135 -16
- package/dist/{chunk-KCF7TFOE.js → chunk-K6JB6XTX.js} +11 -11
- package/dist/{chunk-LFIIX4OV.js → chunk-KDAHXQIE.js} +46 -34
- package/dist/chunk-KYZMRSPS.js +93 -0
- package/dist/chunk-L4CESDPB.js +22 -0
- package/dist/{chunk-3LR4522O.js → chunk-LEBR5NHW.js} +14 -14
- package/dist/{chunk-US3QZU23.js → chunk-LIJJCBQI.js} +67 -7
- package/dist/{chunk-XLHL6WTI.js → chunk-LIRVU3HW.js} +16 -16
- package/dist/{chunk-BAT4CBB7.js → chunk-LN77DXBM.js} +19 -10
- package/dist/{chunk-E5FM44EJ.js → chunk-LNWEWYCM.js} +2474 -315
- package/dist/{chunk-TX4OGZKU.js → chunk-LO3BLMVG.js} +3 -3
- package/dist/{chunk-RUOGHYSK.js → chunk-LPV6T362.js} +5 -5
- package/dist/{chunk-2T5CQH4R.js → chunk-LQH4P45K.js} +1 -1
- package/dist/chunk-LU4MQVAL.js +41 -0
- package/dist/{chunk-HW4RRIJA.js → chunk-LWDNKE4X.js} +12 -10
- package/dist/chunk-M4H2UG4R.js +218 -0
- package/dist/{chunk-CANMOOE6.js → chunk-M6D2U3YM.js} +3 -3
- package/dist/{chunk-AB5E5HCN.js → chunk-MD7KUEJ3.js} +14 -272
- package/dist/{chunk-ENTAAEDK.js → chunk-MMZUN7ZO.js} +20 -20
- package/dist/{chunk-L4MHJG3I.js → chunk-MZGRDONP.js} +46 -36
- package/dist/{chunk-IYI2S6BB.js → chunk-N3RUGTGV.js} +18 -18
- package/dist/{chunk-WZRCGGXG.js → chunk-NCB6656Z.js} +7 -7
- package/dist/{chunk-SBE773JU.js → chunk-NRQ65LTQ.js} +5 -5
- package/dist/{chunk-NRPMPZWJ.js → chunk-NTAVOWYD.js} +117 -18
- package/dist/{chunk-UJBZS5I7.js → chunk-NYOABJNK.js} +36 -27
- package/dist/{chunk-LXAPJ3BL.js → chunk-O4IKAIZ2.js} +24 -20
- package/dist/{chunk-MMB2OHUY.js → chunk-ODMBPDDV.js} +48 -13
- package/dist/chunk-OKUB6RJA.js +533 -0
- package/dist/{chunk-MKFYQQ5Q.js → chunk-OMPENNWY.js} +4 -1
- package/dist/chunk-OPQZISG3.js +25 -0
- package/dist/{chunk-ROJMXG4E.js → chunk-OV57AHCG.js} +24 -24
- package/dist/{chunk-7KGXDIWV.js → chunk-OZJ3JLYI.js} +9 -8
- package/dist/{chunk-G5BYKJIM.js → chunk-P2VBXX7N.js} +11 -10
- package/dist/{chunk-YK6CSODO.js → chunk-PAINMSID.js} +4353 -1106
- package/dist/chunk-PKJAV25S.js +43 -0
- package/dist/{chunk-R5MNPIMD.js → chunk-PNQLCRSS.js} +4 -4
- package/dist/{chunk-3SMMFQED.js → chunk-PQH56YF6.js} +37 -5
- package/dist/chunk-PRTBPXYA.js +50 -0
- package/dist/{chunk-FF4ZMLSL.js → chunk-PRW3HXM3.js} +2 -2
- package/dist/chunk-PRZ7KMWL.js +42 -0
- package/dist/{chunk-IFV6MKYV.js → chunk-PTQ3ZALZ.js} +42 -4
- package/dist/{chunk-ELQSLAP6.js → chunk-PUEWDYGP.js} +2 -2
- package/dist/{chunk-F2ZCZYIK.js → chunk-Q26LDIUV.js} +12 -11
- package/dist/chunk-Q46QDDMF.js +277 -0
- package/dist/chunk-QBNWF5ZU.js +19 -0
- package/dist/{chunk-ANETFNTV.js → chunk-QE527XRY.js} +24 -21
- package/dist/{chunk-QNMLLYTN.js → chunk-QHJJMZLX.js} +397 -32
- package/dist/chunk-QJCJOMW3.js +425 -0
- package/dist/{chunk-3HOONZUE.js → chunk-QKMRFNMA.js} +15 -15
- package/dist/{chunk-7S6VJ643.js → chunk-QOOEYDNJ.js} +101 -13
- package/dist/{chunk-4YNKN4DF.js → chunk-QSIDVFB7.js} +20 -18
- package/dist/{chunk-2IT7SIJY.js → chunk-RA33EYS4.js} +19 -18
- package/dist/{chunk-P7NV3FEJ.js → chunk-RALY3E5U.js} +4 -4
- package/dist/{chunk-6NESNLVU.js → chunk-S2RY3BOD.js} +35 -21
- package/dist/chunk-SB5GRUFM.js +54 -0
- package/dist/{chunk-EUQHM2CH.js → chunk-SH27FYPF.js} +18 -18
- package/dist/chunk-T74WQQGS.js +40 -0
- package/dist/{chunk-LGDW4G73.js → chunk-TGVVUKTG.js} +2 -2
- package/dist/chunk-THN26IEF.js +48 -0
- package/dist/{chunk-ZV2VCBMQ.js → chunk-TJH2EUAZ.js} +18 -12
- package/dist/{chunk-NGT5LLSE.js → chunk-TQ4TG57V.js} +6 -6
- package/dist/{chunk-WEDXFJPG.js → chunk-TQTRWENN.js} +12 -12
- package/dist/{chunk-ZRWS54RI.js → chunk-TSM6T6GQ.js} +23 -23
- package/dist/{chunk-KET7KOVE.js → chunk-TW3KOSQK.js} +15 -7
- package/dist/{chunk-RBXDHCN7.js → chunk-TWQRPM76.js} +2 -2
- package/dist/{chunk-IO6DQCZX.js → chunk-TYD6I4CZ.js} +4 -4
- package/dist/{chunk-5NLJH6R4.js → chunk-U3NBK4EU.js} +56 -17
- package/dist/{chunk-U542BJME.js → chunk-UDXWIPCB.js} +7 -7
- package/dist/{chunk-OSB6AKF2.js → chunk-UEVZZZU2.js} +4 -4
- package/dist/{chunk-5ZV7BK22.js → chunk-UF4UHCIG.js} +2 -2
- package/dist/{chunk-PHJOOL6U.js → chunk-UFE36WJH.js} +4 -4
- package/dist/{chunk-IHRP4OYV.js → chunk-URIKUID7.js} +12 -12
- package/dist/chunk-UYLBRRFK.js +1106 -0
- package/dist/chunk-UZACEG2C.js +515 -0
- package/dist/{chunk-LR4XBH4L.js → chunk-VAMHOHUU.js} +12 -12
- package/dist/{chunk-S5PSGAQ2.js → chunk-VUVGX5L2.js} +1 -1
- package/dist/{chunk-2TG2QGKW.js → chunk-VYOCPTUB.js} +18 -16
- package/dist/{chunk-VTEDQ6GY.js → chunk-WAZEXEJH.js} +175 -3
- package/dist/{chunk-UTSFKZ72.js → chunk-WCEF7H6L.js} +3 -3
- package/dist/{chunk-DXPUYG5L.js → chunk-WEOMJHPV.js} +12 -12
- package/dist/{chunk-NFKBU2UH.js → chunk-WFU77T3A.js} +5 -5
- package/dist/chunk-WIWTJMMR.js +292 -0
- package/dist/{chunk-MRFSUAJH.js → chunk-WNTIOBOG.js} +18 -18
- package/dist/{chunk-UROTYIGJ.js → chunk-X2CUXTWU.js} +6 -6
- package/dist/chunk-X7EMBM5X.js +34 -0
- package/dist/{chunk-OO5TKEAG.js → chunk-XEQCQO66.js} +8 -8
- package/dist/{chunk-NBQO75XT.js → chunk-XG4WJHLQ.js} +17 -1
- package/dist/{chunk-ZQC7MNQB.js → chunk-XOL3GFW2.js} +101 -30
- package/dist/{chunk-GXDATVSY.js → chunk-XQ3YZ4QW.js} +22 -22
- package/dist/{chunk-GTW54D3D.js → chunk-Y6K3CJBU.js} +726 -88
- package/dist/{chunk-CQCAJI6G.js → chunk-YAH5IQAL.js} +5 -5
- package/dist/{chunk-U6TD7UOH.js → chunk-YBOVNMHC.js} +51 -14
- package/dist/{chunk-OJICVOZH.js → chunk-YF5DZCPN.js} +79 -39
- package/dist/chunk-YIGBJNH4.js +37 -0
- package/dist/{chunk-7C7OZARH.js → chunk-YJ3TBOTK.js} +25 -25
- package/dist/{chunk-HXFSM4LG.js → chunk-YKTE2RU3.js} +2 -2
- package/dist/{chunk-7IM4BVVH.js → chunk-YO2PKTXT.js} +268 -36
- package/dist/{chunk-Z3UU7XV6.js → chunk-YTTNTQOX.js} +15 -15
- package/dist/{chunk-2ZICHO3V.js → chunk-YWAKDNMH.js} +183 -33
- package/dist/{chunk-OXOSJXOI.js → chunk-YZDZOEV4.js} +4 -4
- package/dist/{chunk-MRCETHW5.js → chunk-Z3QFH7YG.js} +4 -4
- package/dist/{chunk-5KZTRD4V.js → chunk-Z6VLC5X4.js} +17 -17
- package/dist/{chunk-EDURU5DF.js → chunk-ZFQMMU5Y.js} +2 -2
- package/dist/{chunk-3GLDB4IT.js → chunk-ZKYRDV7A.js} +12 -12
- package/dist/{chunk-GBGJ6WSO.js → chunk-ZLS6PU2O.js} +3 -3
- package/dist/{chunk-JZOOX3PQ.js → chunk-ZOSD4FXV.js} +3 -3
- package/dist/chunk-ZPDDEVQX.js +47 -0
- package/dist/{chunk-2TTSX7NB.js → chunk-ZQLRCA2V.js} +7 -7
- package/dist/{chunk-ESP3RUUR.js → chunk-ZX7235KR.js} +128 -54
- package/dist/{chunk-MH4XG5XS.js → chunk-ZZFHLRQW.js} +18 -16
- package/dist/files.json +559 -439
- package/dist/packages/sdk/npm/index.js +4679 -2672
- package/dist/packages/ui/dist/assets/{elk-api-BuFBvNyV.js → elk-api-CowTnhEJ.js} +2 -2
- package/dist/packages/ui/dist/assets/index-Dw20IRhQ.css +1 -0
- package/dist/packages/ui/dist/assets/index-z2wv0d5d.js +47 -0
- package/dist/packages/ui/dist/index.html +2 -2
- package/package.json +3 -3
- package/dist/chunk-25TJC2CL.js +0 -48
- package/dist/chunk-3Q5RHGCA.js +0 -47
- package/dist/chunk-4W7FOGAB.js +0 -41
- package/dist/chunk-6AF3CVJT.js +0 -48
- package/dist/chunk-A3KEXFQS.js +0 -34
- package/dist/chunk-BAUINGGJ.js +0 -41
- package/dist/chunk-BOFUCRJX.js +0 -47
- package/dist/chunk-GGKGILIP.js +0 -54
- package/dist/chunk-IKN6C67W.js +0 -53
- package/dist/chunk-K7MYVIFK.js +0 -54
- package/dist/chunk-KHA5LVPE.js +0 -34
- package/dist/chunk-LSXGM6MB.js +0 -37
- package/dist/chunk-M54TDRM3.js +0 -29
- package/dist/chunk-M5GF4NAB.js +0 -45
- package/dist/chunk-UTZGBCXE.js +0 -38
- package/dist/chunk-UVTSMBM4.js +0 -43
- package/dist/chunk-XXE3S3OG.js +0 -44
- package/dist/chunk-YJRQCUVJ.js +0 -43
- package/dist/chunk-ZFTK4BDN.js +0 -41
- package/dist/chunk-ZMARAW37.js +0 -38
- package/dist/packages/ui/dist/assets/index-C5QnK82A.css +0 -1
- 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-
|
|
11
|
+
} from "./chunk-D2WSW4GU.js";
|
|
12
12
|
import {
|
|
13
13
|
AdapterError,
|
|
14
14
|
mergeBlockers,
|
|
15
15
|
requireApproval
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-4KNOJC6J.js";
|
|
17
17
|
import {
|
|
18
18
|
outsideProfileSystems,
|
|
19
19
|
readCatalog
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-4SLBCEPD.js";
|
|
21
21
|
import {
|
|
22
|
+
baseClonePath,
|
|
22
23
|
createGit,
|
|
23
|
-
readChangeRepos
|
|
24
|
-
|
|
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-
|
|
56
|
+
} from "./chunk-PAINMSID.js";
|
|
50
57
|
import {
|
|
51
58
|
writeFileAtomic
|
|
52
|
-
} from "./chunk-
|
|
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
|
-
/**
|
|
147
|
-
|
|
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: [
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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 "
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
2208
|
+
const live2 = change.state === "returned" ? void 0 : liveHandoff(change);
|
|
1802
2209
|
const reasons = [...blockers];
|
|
1803
|
-
if (!
|
|
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 (
|
|
2215
|
+
} else if (live2.state === "changes-requested") {
|
|
1809
2216
|
reasons.push({
|
|
1810
2217
|
code: "changes-requested",
|
|
1811
|
-
message: `Changes were requested on PR #${
|
|
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(
|
|
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 #${
|
|
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, ...
|
|
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
|
-
|
|
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
|
|
1862
|
-
import { mkdir, readFile as
|
|
1863
|
-
import { join as
|
|
1864
|
-
var
|
|
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
|
-
|
|
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
|
|
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,
|
|
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 ??
|
|
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(
|
|
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(
|
|
1906
|
-
await
|
|
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(
|
|
1911
|
-
await writeFileAtomic(
|
|
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 {
|
|
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(
|
|
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 =
|
|
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
|
|
1995
|
-
import { join as
|
|
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
|
|
1999
|
-
import { mkdir as mkdir2, readdir as readdir5, readFile as
|
|
2000
|
-
import { dirname, join as
|
|
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 (
|
|
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(
|
|
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
|
|
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(
|
|
2285
|
-
await rm2(
|
|
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 =
|
|
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(
|
|
2297
|
-
await writeFileAtomic(
|
|
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
|
|
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 =
|
|
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(
|
|
2313
|
-
out.push(
|
|
2747
|
+
for (const a of await list(join13(base, "archive")))
|
|
2748
|
+
out.push(join13(base, "archive", a));
|
|
2314
2749
|
else
|
|
2315
|
-
out.push(
|
|
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(
|
|
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,
|
|
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(
|
|
2335
|
-
merged: await readFolderFiles(
|
|
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 =
|
|
2356
|
-
const prior = await
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ??
|
|
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
|
|
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
|
|
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
|
|
2528
|
-
if (
|
|
2529
|
-
const pr2 = await opts.gitHost
|
|
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,
|
|
2996
|
+
await opts.gitHost.comment(repo, pr2.number, `${by} updated the hand-off for another review.`, opts.approval);
|
|
2540
2997
|
const updated = {
|
|
2541
|
-
...
|
|
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 ===
|
|
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
|
-
|
|
2558
|
-
|
|
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 ??
|
|
2611
|
-
const
|
|
2612
|
-
if (!
|
|
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
|
|
2618
|
-
const current = pr.url !==
|
|
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 ===
|
|
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(
|
|
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,
|
|
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 ??
|
|
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(
|
|
2716
|
-
message: `PR #${pr.number} is approved only by ${strip3(
|
|
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
|
|
2756
|
-
if (!
|
|
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 ??
|
|
3231
|
+
const now = opts.now ?? nowIso3();
|
|
2763
3232
|
const by = strip3(opts.by);
|
|
2764
3233
|
const host = opts.gitHost;
|
|
2765
|
-
await host.comment(repo,
|
|
2766
|
-
const closedOnHost = host.closePullRequest ? (await host.closePullRequest(repo,
|
|
2767
|
-
const h = { ...
|
|
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:
|
|
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 ===
|
|
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 ??
|
|
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
|
|
2819
|
-
import { readFile as
|
|
2820
|
-
import { join as
|
|
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,
|
|
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
|
|
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
|
|
3303
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3333
|
-
return isAbsolute(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 (!
|
|
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 =
|
|
3386
|
-
const text = await
|
|
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
|
|
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
|
};
|