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