@maycombhq/atticus 2.0.0-beta.2 → 2.0.0-beta.22
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-23XVSEV5.js +49 -0
- package/dist/{chunk-ZDPAS56H.js → chunk-24OXGB3P.js} +8 -2
- package/dist/{chunk-7EOBHUMC.js → chunk-24XLDU72.js} +4 -3
- package/dist/{chunk-CR3MTH32.js → chunk-2F4CYZKJ.js} +86 -11
- package/dist/{chunk-6IU67AEK.js → chunk-2GF7OLF3.js} +23 -15
- package/dist/chunk-2IIMFOPM.js +41 -0
- package/dist/chunk-2LGRMVCA.js +617 -0
- package/dist/{chunk-XPCM366L.js → chunk-2MMCFP5C.js} +10 -8
- package/dist/{chunk-JBTK5OP2.js → chunk-2Q56PQPS.js} +6 -4
- package/dist/{chunk-Z6LF7HQN.js → chunk-2Q5EDGEO.js} +7 -5
- package/dist/{chunk-3USYZGRF.js → chunk-2RLKFUUL.js} +38 -27
- package/dist/chunk-2SKB4O2E.js +59 -0
- package/dist/{chunk-SP2I63OO.js → chunk-34S2WGOB.js} +87 -4
- package/dist/{chunk-GQ4N4WKF.js → chunk-3NFFJL5D.js} +33 -27
- package/dist/{chunk-SJRGMG6A.js → chunk-3NWDLGG6.js} +28 -25
- package/dist/{chunk-4WBVUHXN.js → chunk-3RM4UK6W.js} +20 -18
- package/dist/chunk-42SIDKEU.js +583 -0
- package/dist/{chunk-GLUX4EEP.js → chunk-4EURQWQD.js} +20 -15
- package/dist/chunk-4U276ICE.js +39 -0
- package/dist/{chunk-ZMFPW66A.js → chunk-53T4O55J.js} +4 -3
- package/dist/{chunk-6J5OKA4Y.js → chunk-57TDNMI3.js} +17 -15
- package/dist/chunk-5BD2KYLE.js +57 -0
- package/dist/{chunk-7EFILTCI.js → chunk-5F7GFN2F.js} +28 -0
- package/dist/chunk-5NMKONLP.js +95 -0
- package/dist/{chunk-7ZUIMTOU.js → chunk-5SJZW52K.js} +26 -24
- package/dist/{chunk-4RS5KZQD.js → chunk-63YRMR6J.js} +6 -5
- package/dist/chunk-66PPNOON.js +62 -0
- package/dist/chunk-6H3Z5JHH.js +64 -0
- package/dist/{chunk-CEXSU4S6.js → chunk-6MUGR4LS.js} +775 -89
- package/dist/{chunk-QSXK2RGH.js → chunk-6OJQOPUN.js} +5 -3
- package/dist/{chunk-MVZVGYXH.js → chunk-6PPOWY5E.js} +84 -14
- package/dist/{chunk-7EKXJZ2Y.js → chunk-6ZGLAACW.js} +299 -38
- package/dist/chunk-6ZJ5A7AF.js +222 -0
- package/dist/{chunk-L24C55VN.js → chunk-745DAZ4L.js} +142 -30
- package/dist/{chunk-Q4D5X7QB.js → chunk-7ASNL4Q4.js} +46 -24
- package/dist/chunk-7BR7IPOH.js +35 -0
- package/dist/{chunk-7WD63HU2.js → chunk-7DOR27HL.js} +6 -6
- package/dist/chunk-7FTTA3HD.js +341 -0
- package/dist/{chunk-OLQJTJJU.js → chunk-7GJS6DNX.js} +12 -3
- package/dist/{chunk-7JUKDRSZ.js → chunk-7IGKHZ4Z.js} +347 -226
- package/dist/{chunk-G2B4ZQGH.js → chunk-7IOWA5D5.js} +11 -6
- package/dist/{chunk-EBEW3XXQ.js → chunk-7JITZFJC.js} +7 -5
- package/dist/chunk-7MJSQTL5.js +174 -0
- package/dist/{chunk-I4JRCD6M.js → chunk-7UOMVMFG.js} +25 -17
- package/dist/{chunk-YY6ZQQBP.js → chunk-AFWUDXEM.js} +19 -7
- package/dist/chunk-AFZ3QA6W.js +47 -0
- package/dist/chunk-AJQ72EBM.js +420 -0
- package/dist/chunk-AJYXNTNZ.js +317 -0
- package/dist/chunk-AVTBS4OW.js +49 -0
- package/dist/{chunk-FHH4I4ZV.js → chunk-BDKC6H5P.js} +12 -6
- package/dist/{chunk-IPKHE5D6.js → chunk-BEVTIBRH.js} +1526 -875
- package/dist/chunk-BHT7QK4G.js +162 -0
- package/dist/{chunk-6JUBZBMW.js → chunk-BIGDQDHD.js} +6 -4
- package/dist/{chunk-2BFCZ3RK.js → chunk-BO4KX37X.js} +20 -18
- package/dist/chunk-BPK5P77O.js +115 -0
- package/dist/chunk-BQ4SQQC6.js +59 -0
- package/dist/chunk-BQGDB6ZL.js +7342 -0
- package/dist/{chunk-2ANOROX3.js → chunk-BTGPE6CR.js} +11 -9
- package/dist/chunk-BTY3HZBO.js +93 -0
- package/dist/{chunk-DPZHZQKE.js → chunk-BXYMYEJ4.js} +44 -10
- package/dist/{chunk-2RMYG36Q.js → chunk-C3RY6KU6.js} +309 -346
- package/dist/chunk-C4R4JLHE.js +40 -0
- package/dist/chunk-CADWLE2T.js +18 -0
- package/dist/chunk-CBGGOQWU.js +82 -0
- package/dist/chunk-CMDXGQTS.js +47 -0
- package/dist/{chunk-MC3H7736.js → chunk-CQDGOPGH.js} +58 -18
- package/dist/{chunk-HFQCFSJR.js → chunk-CX4RZ2KS.js} +4 -3
- package/dist/{chunk-PAMPPUR6.js → chunk-CXJ6FUKN.js} +1852 -207
- package/dist/{chunk-ABWHYWNI.js → chunk-D73UEXYW.js} +3 -3
- package/dist/chunk-DCZWQLYT.js +1205 -0
- package/dist/chunk-DLOBCJL6.js +48 -0
- package/dist/{chunk-4BN53TG3.js → chunk-DOBQKHIF.js} +31 -23
- package/dist/chunk-E3ISYXO3.js +423 -0
- package/dist/{chunk-7SW2QWLU.js → chunk-E6ZIEVOI.js} +16 -13
- package/dist/chunk-EBL2R3YG.js +43 -0
- package/dist/{chunk-AIOQPLA5.js → chunk-EGQRREQH.js} +41 -15
- package/dist/chunk-EHA7RMZ3.js +58 -0
- package/dist/{chunk-M766ZJ6W.js → chunk-EIPTLPFC.js} +38 -27
- package/dist/chunk-EMRIQUND.js +517 -0
- package/dist/{chunk-DFBCSHVT.js → chunk-EPCJQQWD.js} +6 -4
- package/dist/{chunk-RZPYKDWS.js → chunk-F3P7N7SF.js} +1 -1
- package/dist/{chunk-R77KLRB3.js → chunk-FFLNS6VP.js} +21 -18
- package/dist/chunk-FFTRTL6R.js +26 -0
- package/dist/{chunk-IHMLFRVV.js → chunk-FPJPLKDW.js} +62 -16
- package/dist/{chunk-JCZIKA4C.js → chunk-FSMSFDLD.js} +5 -4
- package/dist/{chunk-AVJDSJ2F.js → chunk-FUJM6RTF.js} +8 -7
- package/dist/chunk-FZOJFH7Q.js +132 -0
- package/dist/{chunk-QRAOANID.js → chunk-G6X77UJZ.js} +23 -21
- package/dist/{chunk-3ALD2W2A.js → chunk-GCWRWSU7.js} +10 -4
- package/dist/chunk-GIW4ZVDB.js +39 -0
- package/dist/{chunk-B2WLAWNJ.js → chunk-GUX3PTYM.js} +3 -3
- package/dist/{chunk-DPUYDDQG.js → chunk-GZY6RUZ6.js} +25 -3
- package/dist/chunk-H4VO6WG3.js +10 -0
- package/dist/{chunk-MMOSZPWJ.js → chunk-H5HBGNXU.js} +5 -4
- package/dist/chunk-H7KM7LZE.js +229 -0
- package/dist/{chunk-AA6YULYB.js → chunk-HNM2YQT2.js} +7 -7
- package/dist/{chunk-3C7EYRFZ.js → chunk-HYU5VLGA.js} +1 -1
- package/dist/{chunk-XY3P7JW7.js → chunk-IDKCWSM5.js} +1032 -172
- package/dist/chunk-IFKEI4SA.js +237 -0
- package/dist/{chunk-F7HYOUFF.js → chunk-IGOE6KPG.js} +67 -22
- package/dist/{chunk-TDD55CJH.js → chunk-IH2XURVQ.js} +6 -5
- package/dist/chunk-INQHESFB.js +38 -0
- package/dist/chunk-IP74LCOQ.js +57 -0
- package/dist/chunk-IPHHDDC6.js +45 -0
- package/dist/{chunk-T6AIP7H5.js → chunk-ISUAAMGS.js} +110 -153
- package/dist/{chunk-A7UPUQFY.js → chunk-IUPG3UKI.js} +624 -319
- package/dist/{chunk-L6SGQIR3.js → chunk-IVBOQAGB.js} +7 -3
- package/dist/{chunk-27NKUS3X.js → chunk-J2JAKCIW.js} +2205 -796
- package/dist/{chunk-V2YTAIQO.js → chunk-J3IOLXWM.js} +24 -7
- package/dist/chunk-JA5FI3QV.js +142 -0
- package/dist/chunk-JAVI7JSP.js +49 -0
- package/dist/{chunk-HLJAXMM4.js → chunk-JB5LINEM.js} +6 -2
- package/dist/{chunk-2VHF7OXM.js → chunk-JENSEMXF.js} +1 -1
- package/dist/{chunk-6BVONWJ7.js → chunk-JJF6HALY.js} +4 -3
- package/dist/chunk-JLUCKAEL.js +38 -0
- package/dist/chunk-JRBFANFE.js +221 -0
- package/dist/{chunk-R6XF6UR3.js → chunk-KDNTAPLX.js} +4 -4
- package/dist/chunk-KFAVQZSX.js +33 -0
- package/dist/chunk-KJXD4PNL.js +50 -0
- package/dist/chunk-KN5BXMWN.js +44 -0
- package/dist/{chunk-PCVDV3UZ.js → chunk-KQGURXFU.js} +9 -7
- package/dist/{chunk-WY6PP6AA.js → chunk-KXITGYLF.js} +40 -6
- package/dist/{chunk-66YVAGGM.js → chunk-L27SV425.js} +22 -20
- package/dist/{chunk-NE2W3OO7.js → chunk-LDLL7LB3.js} +24 -22
- package/dist/chunk-LFGTYX5U.js +379 -0
- package/dist/{chunk-EYWIDJFU.js → chunk-LGBMTVNT.js} +7 -5
- package/dist/{chunk-Q5AMFEWD.js → chunk-LGFL54FP.js} +3 -2
- package/dist/{chunk-6TACQN3N.js → chunk-LJXMYKZJ.js} +7 -5
- package/dist/{chunk-BKVWTZ6A.js → chunk-LKOR5UET.js} +29 -26
- package/dist/chunk-LNBQOP7D.js +294 -0
- package/dist/{chunk-O3WI5KWW.js → chunk-LO6P2VYG.js} +2 -2
- package/dist/chunk-LQJPN536.js +122 -0
- package/dist/{chunk-YEDDOYSC.js → chunk-LYBNHMVZ.js} +29 -5
- package/dist/{chunk-A3GY6SOA.js → chunk-LZ7NUC62.js} +20 -18
- package/dist/chunk-M32BLWDH.js +37 -0
- package/dist/{chunk-VYDBJ73M.js → chunk-M3NANSDZ.js} +17 -15
- package/dist/chunk-MFHL5URL.js +31 -0
- package/dist/chunk-MFR5OPBD.js +109 -0
- package/dist/{chunk-DE76JROX.js → chunk-MG4ZL5RW.js} +32 -20
- package/dist/{chunk-N7IXEHK3.js → chunk-MO6P2JZH.js} +10 -8
- package/dist/{chunk-65NYPGRC.js → chunk-MUWDB4R4.js} +1 -1
- package/dist/chunk-MZCGSXVR.js +34 -0
- package/dist/{chunk-MGYZM27B.js → chunk-N3XIYPU3.js} +51 -23
- package/dist/chunk-NDCF4R6S.js +172 -0
- package/dist/{chunk-DHPACDG7.js → chunk-NR2UJWOC.js} +10 -8
- package/dist/chunk-NXJ5URP6.js +51 -0
- package/dist/{chunk-UNM6ZOKP.js → chunk-NY2XSR5K.js} +147 -18
- package/dist/{chunk-5GDRCDQB.js → chunk-O45HFTFS.js} +3 -2
- package/dist/{chunk-ONLS5QA5.js → chunk-O4GXGGBX.js} +14 -12
- package/dist/chunk-O5PXQR67.js +249 -0
- package/dist/{chunk-PRPPGYZG.js → chunk-O66DM6DJ.js} +6 -4
- package/dist/{chunk-KEM4SDDP.js → chunk-O7FDLW7L.js} +11 -8
- package/dist/{chunk-T2UYZSCQ.js → chunk-O7XLYEFA.js} +169 -29
- package/dist/{chunk-FITWGOJ2.js → chunk-OHNCWBKF.js} +4 -4
- package/dist/{chunk-652QWTLZ.js → chunk-OHVTTOPS.js} +3 -3
- package/dist/chunk-OMI6FPPN.js +43 -0
- package/dist/chunk-ONUPY3H6.js +53 -0
- package/dist/chunk-OTLDSZDV.js +69 -0
- package/dist/chunk-OUBGGKDU.js +22 -0
- package/dist/chunk-OWNCM4ZN.js +330 -0
- package/dist/{chunk-YISXLPHN.js → chunk-P2V7QW4D.js} +235 -35
- package/dist/{chunk-CNUKCS2T.js → chunk-P32RUW3B.js} +1632 -93
- package/dist/{chunk-BWZ77RQT.js → chunk-PAQ2AS5E.js} +1 -1
- package/dist/{chunk-ZMAINWBW.js → chunk-PBPBMP2B.js} +4 -3
- package/dist/{chunk-ZVM6AKKM.js → chunk-PJSJPMJI.js} +3 -3
- package/dist/{chunk-EFHQ3TWP.js → chunk-PKBHS4AA.js} +233 -56
- package/dist/{chunk-GT4I4ZWM.js → chunk-PMUAJGLI.js} +20 -16
- package/dist/chunk-PPD7HTDP.js +152 -0
- package/dist/chunk-PSOFBQWB.js +42 -0
- package/dist/{chunk-S2A7YC3Q.js → chunk-Q2TECDIT.js} +10694 -8896
- package/dist/chunk-Q4K5WB2W.js +42 -0
- package/dist/{chunk-UGLN7QHV.js → chunk-Q6DFNVRJ.js} +14 -12
- package/dist/chunk-QA4O2GGC.js +265 -0
- package/dist/chunk-QAXW2N3B.js +1216 -0
- package/dist/chunk-QBNWF5ZU.js +19 -0
- package/dist/chunk-QFWA2DPH.js +224 -0
- package/dist/{chunk-23W7VWKR.js → chunk-QKUJMING.js} +1363 -114
- package/dist/{chunk-NXFPS2LR.js → chunk-R44CYFPV.js} +16 -13
- package/dist/{chunk-CKSITVS4.js → chunk-R7NYHMUU.js} +2687 -786
- package/dist/chunk-REWWDVQB.js +162 -0
- package/dist/{chunk-JMSCMUCA.js → chunk-RGERPOIV.js} +145 -21
- package/dist/{chunk-6JCPI55B.js → chunk-RLDGWLQ7.js} +49 -7
- package/dist/chunk-RUGXBPWI.js +384 -0
- package/dist/{chunk-CPA6HPVR.js → chunk-RXPJMEOW.js} +18 -12
- package/dist/{chunk-3HI6Z55Y.js → chunk-S4PMJU6H.js} +339 -43
- package/dist/chunk-S5LSDXV4.js +40 -0
- package/dist/chunk-S7FCV7S5.js +51 -0
- package/dist/{chunk-5GGOLAKC.js → chunk-SDKLKB6I.js} +28 -26
- package/dist/chunk-SFSPGFCV.js +62 -0
- package/dist/{chunk-ZNCEKR4J.js → chunk-SFUP7TTO.js} +12 -8
- package/dist/{chunk-64EQYWCA.js → chunk-SLXZOMA6.js} +69 -19
- package/dist/chunk-SQV37XO2.js +273 -0
- package/dist/{chunk-K65BJIMT.js → chunk-SU74NDMQ.js} +19 -17
- package/dist/chunk-SYV5LHML.js +165 -0
- package/dist/chunk-T4YPZIQN.js +36 -0
- package/dist/chunk-T6DQR5TV.js +323 -0
- package/dist/{chunk-JIITQXFB.js → chunk-THDMSZ3R.js} +3075 -802
- package/dist/{chunk-JGZCG5VM.js → chunk-TNR75OXT.js} +7 -7
- package/dist/{chunk-D3PO4DOZ.js → chunk-TUULXQRW.js} +174 -15
- package/dist/chunk-TYQBFTBR.js +516 -0
- package/dist/chunk-U76OWS3Q.js +49 -0
- package/dist/{chunk-YYQN46FY.js → chunk-UCROWG2D.js} +27 -24
- package/dist/{chunk-6M5NK3K3.js → chunk-UF4AZOZB.js} +50 -10
- package/dist/chunk-UF4GLFBX.js +345 -0
- package/dist/{chunk-OT2O76OS.js → chunk-UGUP3ZM6.js} +240 -39
- package/dist/chunk-UNAGJYET.js +45 -0
- package/dist/{chunk-5WG5YP4T.js → chunk-UOR2DTHH.js} +16 -14
- package/dist/{chunk-S3QNRWYR.js → chunk-UPOY4YSQ.js} +14 -12
- package/dist/{chunk-EPTBZ3GP.js → chunk-UPS2DGFC.js} +1 -1
- package/dist/{chunk-4AK7IBVP.js → chunk-UUU4CLJG.js} +4 -2
- package/dist/{chunk-W3BQAZ3Q.js → chunk-UWMJKX5U.js} +14 -12
- package/dist/{chunk-QZSX3F7C.js → chunk-UWOJLHFT.js} +27 -23
- package/dist/chunk-VBTP56CE.js +535 -0
- package/dist/chunk-VCTWPLAO.js +211 -0
- package/dist/chunk-VNU26RCO.js +1215 -0
- package/dist/{chunk-CSZ4C5N6.js → chunk-VTZMAJ3W.js} +93 -10
- package/dist/{chunk-TXLH6WHQ.js → chunk-VW62D6QE.js} +14 -12
- package/dist/{chunk-CAQZUNOH.js → chunk-VY72R2XZ.js} +610 -3
- package/dist/{chunk-2DIQQJCB.js → chunk-VZALIKIF.js} +18 -15
- package/dist/{chunk-TYAID7B7.js → chunk-W62H2XSO.js} +25 -23
- package/dist/{chunk-HVUPKCGJ.js → chunk-W7VWI2YH.js} +9 -6
- package/dist/{chunk-TANFV3MZ.js → chunk-WERM6KPJ.js} +7 -4
- package/dist/{chunk-BOHJZTEQ.js → chunk-WF7B67YE.js} +208 -87
- package/dist/{chunk-7FBGJFEE.js → chunk-WG5U3F5V.js} +14 -12
- package/dist/{chunk-4NDAAFQT.js → chunk-WJF2CS6M.js} +8 -5
- package/dist/{chunk-XAKWMCXI.js → chunk-WKUJ3KPV.js} +4 -4
- package/dist/chunk-WMTRHUI5.js +103 -0
- package/dist/chunk-WNKPRSGI.js +36 -0
- package/dist/chunk-WXFLMQSR.js +223 -0
- package/dist/{chunk-RRTKKSLQ.js → chunk-X3WY6MN3.js} +8 -6
- package/dist/{chunk-ZCJDOK2S.js → chunk-X4CXA2IL.js} +47 -23
- package/dist/{chunk-UBBC2XSG.js → chunk-X5F3Q5LV.js} +560 -105
- package/dist/{chunk-C6EKFH7J.js → chunk-XD75VZIH.js} +9 -7
- package/dist/chunk-XERYFDB5.js +297 -0
- package/dist/chunk-XI2LCCYQ.js +420 -0
- package/dist/chunk-Y2EQNWKV.js +933 -0
- package/dist/{chunk-LR3R6Z2A.js → chunk-YHCMZC2J.js} +7 -7
- package/dist/chunk-YYY3AZFE.js +122 -0
- package/dist/{chunk-GDLULHTC.js → chunk-Z5IA3WDR.js} +19 -31
- package/dist/{chunk-T6XQL73F.js → chunk-ZFFYNRIO.js} +63 -23
- package/dist/{chunk-EDS2L2LD.js → chunk-ZFL7NY4F.js} +56 -27
- package/dist/{chunk-YMZ3TNYB.js → chunk-ZGULQAPZ.js} +7 -4
- package/dist/{chunk-4SKZDI62.js → chunk-ZIUFM5HT.js} +3 -1
- package/dist/{chunk-DKJA76CJ.js → chunk-ZJFRMSJT.js} +15 -13
- package/dist/chunk-ZJXLNDH4.js +239 -0
- package/dist/chunk-ZQ3ON2VQ.js +252 -0
- package/dist/{chunk-HITVBIJS.js → chunk-ZQ7SKESZ.js} +150 -415
- package/dist/{chunk-6QSERWXH.js → chunk-ZQXZ2SES.js} +24 -21
- package/dist/chunk-ZX57KSEQ.js +39 -0
- package/dist/{chunk-I3E7CX4H.js → chunk-ZZK56BLP.js} +5 -3
- package/dist/files.json +664 -368
- package/dist/packages/naysayer/constitution/constitution.yaml +19 -0
- package/dist/packages/sdk/npm/index.js +6194 -2542
- package/dist/packages/ui/dist/assets/{elk-api-tma0MCuh.js → elk-api-CIf-0gz5.js} +2 -2
- package/dist/packages/ui/dist/assets/index-CoK_Dl0D.js +47 -0
- package/dist/packages/ui/dist/assets/index-Dw20IRhQ.css +1 -0
- package/dist/packages/ui/dist/index.html +3 -2
- package/package.json +5 -5
- 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-RUVFXE2L.js +0 -666
- package/dist/chunk-RVH5SUW5.js +0 -69
- package/dist/chunk-VN3DP4LQ.js +0 -43
- package/dist/chunk-W5BVQF44.js +0 -36
- package/dist/chunk-WVPJSRRJ.js +0 -52
- 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-UUU4CLJG.js";
|
|
12
12
|
import {
|
|
13
13
|
AdapterError,
|
|
14
14
|
mergeBlockers,
|
|
15
15
|
requireApproval
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-7IGKHZ4Z.js";
|
|
17
17
|
import {
|
|
18
|
+
outsideProfileSystems,
|
|
18
19
|
readCatalog
|
|
19
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-J2JAKCIW.js";
|
|
20
21
|
import {
|
|
22
|
+
baseClonePath,
|
|
21
23
|
createGit,
|
|
22
|
-
readChangeRepos
|
|
23
|
-
|
|
24
|
+
readChangeRepos,
|
|
25
|
+
writeChangeRepos
|
|
26
|
+
} from "./chunk-PKBHS4AA.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,23 @@ 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,
|
|
45
|
-
|
|
52
|
+
readQuestion,
|
|
46
53
|
specFlowFiles,
|
|
47
54
|
toCanonicalYaml
|
|
48
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-Q2TECDIT.js";
|
|
49
56
|
import {
|
|
50
57
|
writeFileAtomic
|
|
51
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-TUULXQRW.js";
|
|
59
|
+
import {
|
|
60
|
+
require_dist
|
|
61
|
+
} from "./chunk-BQGDB6ZL.js";
|
|
52
62
|
import {
|
|
53
63
|
__toESM
|
|
54
64
|
} from "./chunk-TV45EUGM.js";
|
|
@@ -142,8 +152,11 @@ var StageStep = external_exports.strictObject({
|
|
|
142
152
|
loop: external_exports.string().min(1),
|
|
143
153
|
/** The command that runs it; `<change>` stands for the change's id. */
|
|
144
154
|
command: external_exports.string().min(1),
|
|
145
|
-
/**
|
|
146
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Files it writes, relative to the change folder; a folder ends in "/", and a "*" in the last
|
|
157
|
+
* part matches any name in that folder ("tasks/*.prompt.md"). They ride with the hand-off.
|
|
158
|
+
*/
|
|
159
|
+
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
160
|
/** The change type's artifacts of the phase that this stage writes: on the stage path, only these are due. */
|
|
148
161
|
artifacts: external_exports.array(ChangeArtifact).default([])
|
|
149
162
|
});
|
|
@@ -153,7 +166,7 @@ var DEFAULT_STAGES = [
|
|
|
153
166
|
phase: "define",
|
|
154
167
|
loop: "stage/feature.yaml",
|
|
155
168
|
command: "atticus change feature <change>",
|
|
156
|
-
writes: ["feature.yaml", "feature.md", "design/"],
|
|
169
|
+
writes: ["feature.yaml", "feature.md", "proposal.md", "design/"],
|
|
157
170
|
artifacts: []
|
|
158
171
|
},
|
|
159
172
|
{
|
|
@@ -161,7 +174,7 @@ var DEFAULT_STAGES = [
|
|
|
161
174
|
phase: "design",
|
|
162
175
|
loop: "stage/architecture.yaml",
|
|
163
176
|
command: "atticus change architecture <change>",
|
|
164
|
-
writes: ["target-flows/"],
|
|
177
|
+
writes: ["target-flows/", "stage/architecture-deploy.yaml", "stage/architecture-reads.yaml"],
|
|
165
178
|
artifacts: ["design", "data"]
|
|
166
179
|
},
|
|
167
180
|
{
|
|
@@ -169,10 +182,27 @@ var DEFAULT_STAGES = [
|
|
|
169
182
|
phase: "plan",
|
|
170
183
|
loop: "stage/stories.yaml",
|
|
171
184
|
command: "atticus plan stories <change>",
|
|
172
|
-
writes: [
|
|
185
|
+
writes: [
|
|
186
|
+
"requests.md",
|
|
187
|
+
"proposal.md",
|
|
188
|
+
"needs.yaml",
|
|
189
|
+
"needs.md",
|
|
190
|
+
"specs/",
|
|
191
|
+
"tasks/*.prompt.md",
|
|
192
|
+
"stage/stories-answer.yaml"
|
|
193
|
+
],
|
|
173
194
|
artifacts: ["stories", "tasks"]
|
|
174
195
|
}
|
|
175
196
|
];
|
|
197
|
+
var isWritePattern = (w) => w.includes("*");
|
|
198
|
+
function writeMatches(w, path) {
|
|
199
|
+
if (w.endsWith("/"))
|
|
200
|
+
return path.startsWith(w);
|
|
201
|
+
if (!isWritePattern(w))
|
|
202
|
+
return path === w;
|
|
203
|
+
const re = new RegExp(`^${w.split("*").map((x) => x.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
|
|
204
|
+
return re.test(path);
|
|
205
|
+
}
|
|
176
206
|
var Privacy = external_exports.strictObject({
|
|
177
207
|
role: RecordId.default("privacy-officer"),
|
|
178
208
|
classifications: external_exports.array(external_exports.string().min(1)).default(["personal", "pii", "sensitive-personal", "special-category"]),
|
|
@@ -530,7 +560,16 @@ var LEADING = [
|
|
|
530
560
|
"from",
|
|
531
561
|
"to"
|
|
532
562
|
];
|
|
563
|
+
var isPathLike = (id) => !id || id === "." || id === ".." || /[\\/]/.test(id) || /^[A-Za-z]:/.test(id) || id.includes("\0");
|
|
564
|
+
var isFolderRef = (id) => {
|
|
565
|
+
const parts = id.split("/");
|
|
566
|
+
if (parts.length === 2 && parts[0] === "archive")
|
|
567
|
+
return !isPathLike(parts[1]);
|
|
568
|
+
return !isPathLike(id);
|
|
569
|
+
};
|
|
533
570
|
function changeFolder(root, id) {
|
|
571
|
+
if (!isFolderRef(id))
|
|
572
|
+
throw new Error(`${id} isn't a change id.`);
|
|
534
573
|
return join3(changesDirOf(root), id);
|
|
535
574
|
}
|
|
536
575
|
function changeFilePath(root, id) {
|
|
@@ -553,6 +592,8 @@ function serializeChange(value) {
|
|
|
553
592
|
}
|
|
554
593
|
function changeStore(root) {
|
|
555
594
|
async function tryRead(id) {
|
|
595
|
+
if (!RecordId.safeParse(id).success)
|
|
596
|
+
return void 0;
|
|
556
597
|
const path = changeFilePath(root, id);
|
|
557
598
|
let text;
|
|
558
599
|
try {
|
|
@@ -589,22 +630,33 @@ function changeStore(root) {
|
|
|
589
630
|
await writeFileAtomic(path, serializeChange(value));
|
|
590
631
|
return path;
|
|
591
632
|
},
|
|
633
|
+
listWithProblems,
|
|
592
634
|
async list() {
|
|
593
|
-
|
|
635
|
+
return (await listWithProblems()).changes;
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
async function listWithProblems() {
|
|
639
|
+
let names;
|
|
640
|
+
try {
|
|
641
|
+
names = (await readdir2(changesDirOf(root), { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== "archive" && RecordId.safeParse(d.name).success).map((d) => d.name).sort();
|
|
642
|
+
} catch {
|
|
643
|
+
return { changes: [], problems: [] };
|
|
644
|
+
}
|
|
645
|
+
const changes = [];
|
|
646
|
+
const problems = [];
|
|
647
|
+
for (const n of names) {
|
|
594
648
|
try {
|
|
595
|
-
names = (await readdir2(changesDirOf(root), { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== "archive").map((d) => d.name).sort();
|
|
596
|
-
} catch {
|
|
597
|
-
return [];
|
|
598
|
-
}
|
|
599
|
-
const out = [];
|
|
600
|
-
for (const n of names) {
|
|
601
649
|
const c = await tryRead(n);
|
|
602
650
|
if (c)
|
|
603
|
-
|
|
651
|
+
changes.push(c);
|
|
652
|
+
} catch (e) {
|
|
653
|
+
if (!(e instanceof ChangeFileError))
|
|
654
|
+
throw e;
|
|
655
|
+
problems.push({ file: e.path, message: e.message });
|
|
604
656
|
}
|
|
605
|
-
return out;
|
|
606
657
|
}
|
|
607
|
-
|
|
658
|
+
return { changes, problems };
|
|
659
|
+
}
|
|
608
660
|
}
|
|
609
661
|
|
|
610
662
|
// packages/changes/dist/codeowners.js
|
|
@@ -712,6 +764,21 @@ function changeTypeOf(ctx, type) {
|
|
|
712
764
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
713
765
|
import { readdir as readdir3, readFile as readFile5 } from "node:fs/promises";
|
|
714
766
|
import { join as join6 } from "node:path";
|
|
767
|
+
|
|
768
|
+
// packages/changes/dist/outside.js
|
|
769
|
+
function outsideSystemsOf(op) {
|
|
770
|
+
return new Map(outsideProfileSystems(op).map((s) => [s.id, s.boundary === "vendor" ? "vendor" : "other-team"]));
|
|
771
|
+
}
|
|
772
|
+
function outsideKindOf(outside, system, catalogBoundary) {
|
|
773
|
+
if (!system)
|
|
774
|
+
return void 0;
|
|
775
|
+
if (catalogBoundary === "vendor")
|
|
776
|
+
return "vendor";
|
|
777
|
+
return outside.get(system);
|
|
778
|
+
}
|
|
779
|
+
var outsideWords = (k) => k === "vendor" ? "a vendor" : "another team's system";
|
|
780
|
+
|
|
781
|
+
// packages/changes/dist/footprint.js
|
|
715
782
|
function personalLevels(ctx) {
|
|
716
783
|
const pack = ctx.orgPack.pack?.classification ?? [
|
|
717
784
|
"public",
|
|
@@ -799,6 +866,8 @@ async function changeFootprint(ctx, change) {
|
|
|
799
866
|
};
|
|
800
867
|
const profileSystems = new Map(ctx.orgPack.profiles.flatMap((p) => p.profile?.systems ?? []).map((s) => [s.id, s]));
|
|
801
868
|
const isSystem = (id) => get("System", id) !== void 0 || profileSystems.has(id);
|
|
869
|
+
const outsideSystems = outsideSystemsOf(ctx.orgPack);
|
|
870
|
+
const outsideKind = (id) => outsideKindOf(outsideSystems, id, get("System", id)?.boundary ?? profileSystems.get(id)?.boundary);
|
|
802
871
|
const systems = new Set(change.systems);
|
|
803
872
|
const components = /* @__PURE__ */ new Set();
|
|
804
873
|
const interfaces = /* @__PURE__ */ new Map();
|
|
@@ -846,7 +915,7 @@ async function changeFootprint(ctx, change) {
|
|
|
846
915
|
if (table)
|
|
847
916
|
tables.add(table.includes(".") ? slug(table) : `${store}.${slug(table)}`);
|
|
848
917
|
}
|
|
849
|
-
const staged = await stageFootprint(ctx, dir, { get, isSystem,
|
|
918
|
+
const staged = await stageFootprint(ctx, dir, { get, isSystem, outsideKind, ref });
|
|
850
919
|
for (const f of staged.from)
|
|
851
920
|
from.add(f);
|
|
852
921
|
for (const r of staged.repos)
|
|
@@ -905,7 +974,8 @@ async function changeFootprint(ctx, change) {
|
|
|
905
974
|
repos.add(r.id);
|
|
906
975
|
} catch {
|
|
907
976
|
}
|
|
908
|
-
const vendors = [...systems].filter((s) => (
|
|
977
|
+
const vendors = [...systems].filter((s) => outsideKind(s) === "vendor");
|
|
978
|
+
const otherTeams = [...systems].filter((s) => outsideKind(s) === "other-team");
|
|
909
979
|
const personalSet = new Set(personalLevels(ctx).levels.map((c) => c.toLowerCase()));
|
|
910
980
|
const personal = [];
|
|
911
981
|
for (const s of staged.stores) {
|
|
@@ -962,6 +1032,7 @@ async function changeFootprint(ctx, change) {
|
|
|
962
1032
|
records: sorted(change.touches.records),
|
|
963
1033
|
repos: sorted(repos),
|
|
964
1034
|
vendors: vendors.sort(),
|
|
1035
|
+
...otherTeams.length ? { otherTeams: otherTeams.sort() } : {},
|
|
965
1036
|
personal,
|
|
966
1037
|
flows,
|
|
967
1038
|
entries: sorted(staged.entries),
|
|
@@ -995,7 +1066,7 @@ async function stageFootprint(ctx, dir, h) {
|
|
|
995
1066
|
const tasksText = await read(join6(dir, "tasks.md"));
|
|
996
1067
|
if (!files.length && !featureText && !tasksText)
|
|
997
1068
|
return out;
|
|
998
|
-
const { get, isSystem,
|
|
1069
|
+
const { get, isSystem, outsideKind, ref } = h;
|
|
999
1070
|
const specs = await specIndex(ctx);
|
|
1000
1071
|
const catalogRepos = /* @__PURE__ */ new Set();
|
|
1001
1072
|
for (const r of ctx.records)
|
|
@@ -1009,9 +1080,9 @@ async function stageFootprint(ctx, dir, h) {
|
|
|
1009
1080
|
const outsideOf = async (id) => {
|
|
1010
1081
|
const c = get("Component", id);
|
|
1011
1082
|
if (c)
|
|
1012
|
-
return
|
|
1083
|
+
return outsideKind(c.system) !== void 0;
|
|
1013
1084
|
if (isSystem(id))
|
|
1014
|
-
return (
|
|
1085
|
+
return outsideKind(id) !== void 0;
|
|
1015
1086
|
if (catalogRepos.has(id))
|
|
1016
1087
|
return false;
|
|
1017
1088
|
return !await specs.hasComponent(id) && !await specs.hasRepo(id);
|
|
@@ -1098,7 +1169,7 @@ async function stageFootprint(ctx, dir, h) {
|
|
|
1098
1169
|
const was = op !== "add" && n ? today?.flow.steps[n - 1] : void 0;
|
|
1099
1170
|
const kind = str(s.kind) ?? was?.kind ?? "step";
|
|
1100
1171
|
const what = str(s.what) ?? was?.what ?? "";
|
|
1101
|
-
const
|
|
1172
|
+
const live2 = op === "add" || op === "change";
|
|
1102
1173
|
const store = obj(s.store);
|
|
1103
1174
|
const table = str(store?.table) ?? was?.store?.table;
|
|
1104
1175
|
if (table) {
|
|
@@ -1111,7 +1182,7 @@ async function stageFootprint(ctx, dir, h) {
|
|
|
1111
1182
|
if (typeof v === "string")
|
|
1112
1183
|
classification[c] = v;
|
|
1113
1184
|
out.stores.push({ table, columns: cols, classification, kind, cite });
|
|
1114
|
-
if (
|
|
1185
|
+
if (live2) {
|
|
1115
1186
|
const hit = [...wordsOf(table), ...cols.flatMap(wordsOf)].filter((w) => personalWords.has(w));
|
|
1116
1187
|
if (hit.length)
|
|
1117
1188
|
out.signals.push({
|
|
@@ -1139,7 +1210,7 @@ async function stageFootprint(ctx, dir, h) {
|
|
|
1139
1210
|
if (entry)
|
|
1140
1211
|
o.entries.add(entry);
|
|
1141
1212
|
outside.set(target, o);
|
|
1142
|
-
if (
|
|
1213
|
+
if (live2)
|
|
1143
1214
|
out.signals.push({
|
|
1144
1215
|
factor: "outside-call",
|
|
1145
1216
|
detail: `${target}${entry ? ` (${entry}${to?.new === true ? ", new" : ""})` : ""}`,
|
|
@@ -1163,7 +1234,7 @@ async function stageFootprint(ctx, dir, h) {
|
|
|
1163
1234
|
out.calls.add(iface.id);
|
|
1164
1235
|
}
|
|
1165
1236
|
}
|
|
1166
|
-
if (!
|
|
1237
|
+
if (!live2)
|
|
1167
1238
|
continue;
|
|
1168
1239
|
const fields = obj(s.fields) ?? {};
|
|
1169
1240
|
const names = FIELD_LISTS.flatMap((k) => strs(fields[k]));
|
|
@@ -1291,9 +1362,14 @@ function scoreRisk(input, policy) {
|
|
|
1291
1362
|
add("systems", `${f.systems.length} systems touched (${f.systems.join(", ")})`, (f.systems.length - 1) * w.extraSystem);
|
|
1292
1363
|
for (const s of f.dataStores)
|
|
1293
1364
|
add("data-store", `data store ${s}`, w.dataStore);
|
|
1294
|
-
const
|
|
1295
|
-
|
|
1296
|
-
|
|
1365
|
+
const notCalled = (v) => !(f.outside ?? []).some((o) => o.id === v);
|
|
1366
|
+
const crossed = f.vendors.filter(notCalled);
|
|
1367
|
+
const teams = (f.otherTeams ?? []).filter(notCalled);
|
|
1368
|
+
if (crossed.length > 0 || teams.length > 0)
|
|
1369
|
+
add("vendor", [
|
|
1370
|
+
...crossed.length ? [`crosses the vendor boundary (${crossed.join(", ")})`] : [],
|
|
1371
|
+
...teams.length ? [`calls another team's system (${teams.join(", ")})`] : []
|
|
1372
|
+
].join("; "), w.vendor);
|
|
1297
1373
|
for (const i of f.interfaces) {
|
|
1298
1374
|
if (i.status === "planned-modify")
|
|
1299
1375
|
add("interface", `interface ${i.id} planned-modify`, w.interfaceModify);
|
|
@@ -1414,7 +1490,7 @@ function handoffName(type, phase) {
|
|
|
1414
1490
|
case "plan":
|
|
1415
1491
|
return "Plan ready";
|
|
1416
1492
|
case "build":
|
|
1417
|
-
return "
|
|
1493
|
+
return "Build ready";
|
|
1418
1494
|
case "qa":
|
|
1419
1495
|
return "QA sign-off";
|
|
1420
1496
|
case "release":
|
|
@@ -1470,8 +1546,15 @@ async function checkStage(root, change, step) {
|
|
|
1470
1546
|
break;
|
|
1471
1547
|
case "stopped":
|
|
1472
1548
|
return reason(`The ${step.stage} stage stopped and isn't done: see what stopped it, then run ${run} again.`);
|
|
1473
|
-
case "questions":
|
|
1474
|
-
|
|
1549
|
+
case "questions": {
|
|
1550
|
+
const asked = Array.isArray(loop?.questions) ? loop.questions.filter((q) => typeof q === "string") : [];
|
|
1551
|
+
if (asked.length) {
|
|
1552
|
+
const open = new Set((await listQuestions(root, { state: "open" }).catch(() => ({ questions: [] }))).questions.map((q) => q.id));
|
|
1553
|
+
if (!asked.some((q) => open.has(q)))
|
|
1554
|
+
return reason(`The ${step.stage} stage's questions are answered: run it again so it reads the answers (${run}).`);
|
|
1555
|
+
}
|
|
1556
|
+
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}).`);
|
|
1557
|
+
}
|
|
1475
1558
|
case "waiting":
|
|
1476
1559
|
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
1560
|
case "drafting":
|
|
@@ -1480,7 +1563,8 @@ async function checkStage(root, change, step) {
|
|
|
1480
1563
|
return reason(`${step.loop} doesn't read as a stage's loop state: run ${run} again.`);
|
|
1481
1564
|
}
|
|
1482
1565
|
const out = [];
|
|
1483
|
-
|
|
1566
|
+
const artifactFile = new Set(Object.values(FILE));
|
|
1567
|
+
for (const w of step.writes.filter((x) => !x.endsWith("/") && !x.startsWith("stage/") && !isWritePattern(x) && !artifactFile.has(x)))
|
|
1484
1568
|
if (!await readFile6(join7(dir, ...w.split("/"))).then(() => true, () => false))
|
|
1485
1569
|
out.push(...reason(`${w} is missing, though the ${step.stage} stage is done: run ${run} again.`));
|
|
1486
1570
|
return out;
|
|
@@ -1490,7 +1574,10 @@ async function stageFiles(dir, step) {
|
|
|
1490
1574
|
for (const w of [...step.writes, step.loop]) {
|
|
1491
1575
|
if (w.endsWith("/"))
|
|
1492
1576
|
out.push(...await walk(dir, w.slice(0, -1)));
|
|
1493
|
-
else if (
|
|
1577
|
+
else if (isWritePattern(w)) {
|
|
1578
|
+
const parent = w.split("/").slice(0, -1).join("/");
|
|
1579
|
+
out.push(...(await walk(dir, parent)).filter((p) => writeMatches(w, p)));
|
|
1580
|
+
} else if (await readFile6(join7(dir, ...w.split("/"))).then(() => true, () => false))
|
|
1494
1581
|
out.push(w);
|
|
1495
1582
|
}
|
|
1496
1583
|
return [...new Set(out)].sort();
|
|
@@ -1576,13 +1663,24 @@ async function checkFindings(root, change, gates) {
|
|
|
1576
1663
|
continue;
|
|
1577
1664
|
const round2 = Math.max(...rounds);
|
|
1578
1665
|
const dir = join7(base, String(round2));
|
|
1666
|
+
const files = (await readdir4(dir)).filter((f) => /\.ya?ml$/.test(f)).sort();
|
|
1667
|
+
const unfinished = {
|
|
1668
|
+
code: "round-unfinished",
|
|
1669
|
+
message: `Critic round ${round2} on the ${gate} gate didn't finish: run atticus critic ${change.id} --gate ${gate} again.`
|
|
1670
|
+
};
|
|
1671
|
+
let hasMeta = false;
|
|
1579
1672
|
try {
|
|
1580
1673
|
const meta = (0, import_yaml5.parse)(await readFile6(join7(dir, "meta", "round.yaml"), "utf8"));
|
|
1674
|
+
hasMeta = true;
|
|
1675
|
+
if (meta?.state === "in-progress") {
|
|
1676
|
+
reasons.push(unfinished);
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1581
1679
|
for (const l of meta?.lenses ?? [])
|
|
1582
1680
|
if (l.state === "waiting")
|
|
1583
1681
|
reasons.push({
|
|
1584
1682
|
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})` : ""}.`
|
|
1683
|
+
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
1684
|
});
|
|
1587
1685
|
else if (l.state === "failed")
|
|
1588
1686
|
reasons.push({
|
|
@@ -1591,7 +1689,10 @@ async function checkFindings(root, change, gates) {
|
|
|
1591
1689
|
});
|
|
1592
1690
|
} catch {
|
|
1593
1691
|
}
|
|
1594
|
-
|
|
1692
|
+
if (!hasMeta && files.length === 0) {
|
|
1693
|
+
reasons.push(unfinished);
|
|
1694
|
+
continue;
|
|
1695
|
+
}
|
|
1595
1696
|
for (const f of files) {
|
|
1596
1697
|
const rel = `review/${gate}/${round2}/${f}`;
|
|
1597
1698
|
let data;
|
|
@@ -1609,6 +1710,16 @@ async function checkFindings(root, change, gates) {
|
|
|
1609
1710
|
continue;
|
|
1610
1711
|
}
|
|
1611
1712
|
const x = r.data;
|
|
1713
|
+
if (x.severity === "gap" && x.state === "asked") {
|
|
1714
|
+
const q = await readQuestion(root, askedGapQuestionId(change.id, x)).catch(() => void 0);
|
|
1715
|
+
if (q?.state === "open")
|
|
1716
|
+
reasons.push({
|
|
1717
|
+
code: "open-gap",
|
|
1718
|
+
finding: x.id,
|
|
1719
|
+
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}`
|
|
1720
|
+
});
|
|
1721
|
+
continue;
|
|
1722
|
+
}
|
|
1612
1723
|
const byAssistant = x.severity === "blocking" && isAssistantResolution(x.resolution);
|
|
1613
1724
|
if (x.state !== "open" && !byAssistant)
|
|
1614
1725
|
continue;
|
|
@@ -1728,8 +1839,459 @@ function prApprovers(handoff2, approvers) {
|
|
|
1728
1839
|
return [...new Set(approvers.map(strip))].filter((p) => p !== raiser).sort();
|
|
1729
1840
|
}
|
|
1730
1841
|
|
|
1842
|
+
// packages/changes/dist/assess.js
|
|
1843
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
1844
|
+
import { join as join10 } from "node:path";
|
|
1845
|
+
|
|
1846
|
+
// packages/changes/dist/release-prs.js
|
|
1847
|
+
var import_yaml7 = __toESM(require_dist(), 1);
|
|
1848
|
+
import { readFile as readFile8, writeFile } from "node:fs/promises";
|
|
1849
|
+
import { existsSync } from "node:fs";
|
|
1850
|
+
import { join as join9 } from "node:path";
|
|
1851
|
+
var RELEASE_PR_STATES = [
|
|
1852
|
+
"qa",
|
|
1853
|
+
"release-ready",
|
|
1854
|
+
"released",
|
|
1855
|
+
"post-release-review"
|
|
1856
|
+
];
|
|
1857
|
+
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
1858
|
+
var RELEASE_PRS_FILE = "release-prs.yaml";
|
|
1859
|
+
var NothingFile = external_exports.strictObject({
|
|
1860
|
+
schemaVersion: external_exports.literal(1),
|
|
1861
|
+
nothingToRelease: external_exports.array(external_exports.strictObject({
|
|
1862
|
+
repo: external_exports.string().min(1),
|
|
1863
|
+
/** The change branch's commit when it was found to hold nothing beyond the default branch. */
|
|
1864
|
+
commit: external_exports.string().regex(/^[0-9a-f]{7,64}$/).optional(),
|
|
1865
|
+
at: external_exports.string().min(1),
|
|
1866
|
+
by: external_exports.string().min(1)
|
|
1867
|
+
})),
|
|
1868
|
+
/**
|
|
1869
|
+
* Review round 48: the change branch's commit when Sync first read each release PR merged, so
|
|
1870
|
+
* a merged PR stands only while the branch is there (work landed since needs a PR of its own).
|
|
1871
|
+
*/
|
|
1872
|
+
merged: external_exports.array(external_exports.strictObject({
|
|
1873
|
+
repo: external_exports.string().min(1),
|
|
1874
|
+
number: external_exports.number().int().positive(),
|
|
1875
|
+
commit: external_exports.string().regex(/^[0-9a-f]{7,64}$/),
|
|
1876
|
+
at: external_exports.string().min(1)
|
|
1877
|
+
})).optional()
|
|
1878
|
+
});
|
|
1879
|
+
async function readReleaseFile(root, id) {
|
|
1880
|
+
const empty = { schemaVersion: 1, nothingToRelease: [] };
|
|
1881
|
+
const text = await readFile8(join9(changeFolder(root, id), RELEASE_PRS_FILE), "utf8").catch(() => void 0);
|
|
1882
|
+
if (text === void 0)
|
|
1883
|
+
return empty;
|
|
1884
|
+
try {
|
|
1885
|
+
const r = NothingFile.safeParse((0, import_yaml7.parse)(text));
|
|
1886
|
+
return r.success ? r.data : empty;
|
|
1887
|
+
} catch {
|
|
1888
|
+
return empty;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
async function readNothing(root, id) {
|
|
1892
|
+
return (await readReleaseFile(root, id)).nothingToRelease;
|
|
1893
|
+
}
|
|
1894
|
+
async function writeReleaseFile(root, id, nothing, merged) {
|
|
1895
|
+
const byRepo = (xs) => [...xs].sort((a, b) => a.repo < b.repo ? -1 : a.repo > b.repo ? 1 : 0);
|
|
1896
|
+
await writeFile(join9(changeFolder(root, id), RELEASE_PRS_FILE), toCanonicalYaml(NothingFile.parse({
|
|
1897
|
+
schemaVersion: 1,
|
|
1898
|
+
nothingToRelease: byRepo(nothing),
|
|
1899
|
+
...merged.length ? { merged: byRepo(merged) } : {}
|
|
1900
|
+
})), "utf8");
|
|
1901
|
+
}
|
|
1902
|
+
function repoDir(root, r) {
|
|
1903
|
+
const dirs = [join9(root, r.worktree)];
|
|
1904
|
+
try {
|
|
1905
|
+
dirs.push(baseClonePath(root, r.id));
|
|
1906
|
+
} catch {
|
|
1907
|
+
}
|
|
1908
|
+
return dirs.find((d) => existsSync(d));
|
|
1909
|
+
}
|
|
1910
|
+
async function staleReleasePrs(root, id, o = {}) {
|
|
1911
|
+
const out = /* @__PURE__ */ new Set();
|
|
1912
|
+
const cr = await readChangeRepos(root, id).catch(() => void 0);
|
|
1913
|
+
const merged = (await readReleaseFile(root, id)).merged ?? [];
|
|
1914
|
+
if (!cr || !merged.length)
|
|
1915
|
+
return out;
|
|
1916
|
+
const git = o.git ?? createGit({ timeoutMs: 3e4 });
|
|
1917
|
+
const remote = o.remote ?? "origin";
|
|
1918
|
+
for (const r of cr.repos) {
|
|
1919
|
+
if (r.pr?.state !== "merged")
|
|
1920
|
+
continue;
|
|
1921
|
+
const entry = merged.find((x) => x.repo === r.id && x.number === r.pr.number);
|
|
1922
|
+
if (!entry)
|
|
1923
|
+
continue;
|
|
1924
|
+
const dir = repoDir(root, r);
|
|
1925
|
+
if (!dir)
|
|
1926
|
+
continue;
|
|
1927
|
+
if (await movedPast(git, dir, remote, r.branch, entry.commit))
|
|
1928
|
+
out.add(r.id);
|
|
1929
|
+
}
|
|
1930
|
+
return out;
|
|
1931
|
+
}
|
|
1932
|
+
async function movedPast(git, dir, remote, branch, commit) {
|
|
1933
|
+
const ref = `refs/heads/${branch}`;
|
|
1934
|
+
const known = (sha) => git(["cat-file", "-e", `${sha}^{commit}`], dir).then(() => true, () => false);
|
|
1935
|
+
const here = await git(["rev-parse", "--verify", "--quiet", `${ref}^{commit}`], dir).then((x) => x.stdout.trim() || void 0, () => void 0);
|
|
1936
|
+
const onHost = await git(["ls-remote", "--heads", remote, ref], dir).then((x) => x.stdout.trim().split(/\s+/)[0] || void 0, () => void 0);
|
|
1937
|
+
if (onHost && !sameSha(onHost, commit) && !await known(onHost))
|
|
1938
|
+
await git(["fetch", "-q", remote, `+${ref}:refs/remotes/${remote}/${branch}`], dir).catch(() => void 0);
|
|
1939
|
+
const ahead = async (sha) => {
|
|
1940
|
+
if (!sha || sameSha(sha, commit))
|
|
1941
|
+
return false;
|
|
1942
|
+
if (!await known(sha) || !await known(commit))
|
|
1943
|
+
return true;
|
|
1944
|
+
return git(["merge-base", "--is-ancestor", sha, commit], dir).then(() => false, () => true);
|
|
1945
|
+
};
|
|
1946
|
+
return await ahead(here) || await ahead(onHost);
|
|
1947
|
+
}
|
|
1948
|
+
async function nothingToRelease(root, id, o = {}) {
|
|
1949
|
+
const entries = await readNothing(root, id);
|
|
1950
|
+
const out = /* @__PURE__ */ new Set();
|
|
1951
|
+
if (!entries.length)
|
|
1952
|
+
return out;
|
|
1953
|
+
const cr = await readChangeRepos(root, id).catch(() => void 0);
|
|
1954
|
+
const git = o.git ?? createGit({ timeoutMs: 3e4 });
|
|
1955
|
+
const remote = o.remote ?? "origin";
|
|
1956
|
+
for (const x of entries) {
|
|
1957
|
+
const r = cr?.repos.find((y) => y.id === x.repo);
|
|
1958
|
+
if (r && await moved(r, x.commit))
|
|
1959
|
+
continue;
|
|
1960
|
+
out.add(x.repo);
|
|
1961
|
+
}
|
|
1962
|
+
return out;
|
|
1963
|
+
async function moved(r, commit) {
|
|
1964
|
+
const dir = repoDir(root, r);
|
|
1965
|
+
if (!dir)
|
|
1966
|
+
return false;
|
|
1967
|
+
const ref = `refs/heads/${r.branch}`;
|
|
1968
|
+
const here = await git(["rev-parse", "--verify", "--quiet", `${ref}^{commit}`], dir).then((x) => x.stdout.trim() || void 0, () => void 0);
|
|
1969
|
+
const onHost = await git(["ls-remote", "--heads", remote, ref], dir).then((x) => x.stdout.trim().split(/\s+/)[0] || void 0, () => void 0);
|
|
1970
|
+
const at = (sha) => sha !== void 0 && (commit === void 0 || !sameSha(sha, commit));
|
|
1971
|
+
return at(here) || at(onHost);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
var sameSha = (a, b) => a.startsWith(b) || b.startsWith(a);
|
|
1975
|
+
var firstLine = (e) => (e instanceof Error ? e.message : String(e)).split("\n")[0].replace(/\.$/, "");
|
|
1976
|
+
var prOf = (pr, now) => ({
|
|
1977
|
+
url: pr.url,
|
|
1978
|
+
number: pr.number,
|
|
1979
|
+
state: pr.state,
|
|
1980
|
+
updatedAt: now
|
|
1981
|
+
});
|
|
1982
|
+
var live = (pr) => !!pr && (pr.state === "open" || pr.state === "draft" || pr.state === "merged");
|
|
1983
|
+
function releaseBody(id, title, branch, base) {
|
|
1984
|
+
return [
|
|
1985
|
+
`Release of change ${id} (${title}): merges ${branch}, with the task PRs merged into it, into ${base}.`,
|
|
1986
|
+
"",
|
|
1987
|
+
"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."
|
|
1988
|
+
].join("\n");
|
|
1989
|
+
}
|
|
1990
|
+
async function raiseReleasePrs(root, id, o) {
|
|
1991
|
+
requireApproval("changes", o.approval);
|
|
1992
|
+
const change = await changeStore(root).read(id);
|
|
1993
|
+
if (!RELEASE_PR_STATES.includes(change.state))
|
|
1994
|
+
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.`);
|
|
1995
|
+
const cr = await readChangeRepos(root, id);
|
|
1996
|
+
if (!cr?.repos.length)
|
|
1997
|
+
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>).`);
|
|
1998
|
+
const now = o.now ?? nowIso();
|
|
1999
|
+
const git = o.git ?? createGit();
|
|
2000
|
+
const remote = o.remote ?? "origin";
|
|
2001
|
+
const expected = changeBranch(id);
|
|
2002
|
+
const rows = [];
|
|
2003
|
+
let changed2 = false;
|
|
2004
|
+
const repos = cr.repos.map((r) => ({ ...r }));
|
|
2005
|
+
const file = await readReleaseFile(root, id);
|
|
2006
|
+
const before = file.nothingToRelease;
|
|
2007
|
+
const nothing = new Map(before.map((x) => [x.repo, x]));
|
|
2008
|
+
const stale = await staleReleasePrs(root, id, { git, remote });
|
|
2009
|
+
for (const r of repos) {
|
|
2010
|
+
const movedOn = r.pr?.state === "merged" && stale.has(r.id) ? `Release PR #${r.pr.number} merged before ${r.branch} moved on; this one carries what landed since.` : void 0;
|
|
2011
|
+
if (!live(r.pr) || movedOn)
|
|
2012
|
+
nothing.delete(r.id);
|
|
2013
|
+
if (live(r.pr) && !movedOn) {
|
|
2014
|
+
rows.push({
|
|
2015
|
+
repo: r.id,
|
|
2016
|
+
outcome: "kept",
|
|
2017
|
+
pr: r.pr,
|
|
2018
|
+
message: `${r.id}: release PR #${r.pr.number} is already ${r.pr.state} (${r.pr.url}).`
|
|
2019
|
+
});
|
|
2020
|
+
continue;
|
|
2021
|
+
}
|
|
2022
|
+
if (r.branch !== expected) {
|
|
2023
|
+
rows.push({
|
|
2024
|
+
repo: r.id,
|
|
2025
|
+
outcome: "refused",
|
|
2026
|
+
message: `${r.id}: its branch is ${r.branch}, not ${expected}; Atticus raises a release PR only from the change branch.`
|
|
2027
|
+
});
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
let base;
|
|
2031
|
+
try {
|
|
2032
|
+
const info = await o.gitHost.getRepo(r.id);
|
|
2033
|
+
base = info.defaultBranch;
|
|
2034
|
+
if (info.protectedBranches.includes(r.branch)) {
|
|
2035
|
+
rows.push({
|
|
2036
|
+
repo: r.id,
|
|
2037
|
+
outcome: "refused",
|
|
2038
|
+
message: `${r.id}: ${r.branch} is protected; Atticus never pushes to or raises a PR from a protected branch.`
|
|
2039
|
+
});
|
|
2040
|
+
continue;
|
|
2041
|
+
}
|
|
2042
|
+
if (r.branch === base) {
|
|
2043
|
+
rows.push({
|
|
2044
|
+
repo: r.id,
|
|
2045
|
+
outcome: "refused",
|
|
2046
|
+
message: `${r.id}: ${r.branch} is the default branch; a release PR goes from the change branch into it.`
|
|
2047
|
+
});
|
|
2048
|
+
continue;
|
|
2049
|
+
}
|
|
2050
|
+
} catch (e) {
|
|
2051
|
+
if (!(e instanceof AdapterError))
|
|
2052
|
+
throw e;
|
|
2053
|
+
rows.push({
|
|
2054
|
+
repo: r.id,
|
|
2055
|
+
outcome: "failed",
|
|
2056
|
+
message: `${r.id}: the Git host didn't answer (${firstLine(e)}); raise the release PRs again once it does.`
|
|
2057
|
+
});
|
|
2058
|
+
continue;
|
|
2059
|
+
}
|
|
2060
|
+
const notes = movedOn ? [movedOn] : [];
|
|
2061
|
+
const dir = join9(root, r.worktree);
|
|
2062
|
+
if (existsSync(dir)) {
|
|
2063
|
+
const onHost = await git(["ls-remote", "--heads", remote, `refs/heads/${r.branch}`], dir).then((x) => x.stdout.trim() ? "yes" : "no").catch((e) => {
|
|
2064
|
+
notes.push(`Atticus couldn't reach ${remote} (${firstLine(e)}): push ${r.branch} yourself if the host lacks it.`);
|
|
2065
|
+
return "unknown";
|
|
2066
|
+
});
|
|
2067
|
+
if (onHost === "no") {
|
|
2068
|
+
const from = r.baseSha ?? `${remote}/${base}`;
|
|
2069
|
+
const ahead = await git(["rev-list", "--count", `${from}..refs/heads/${r.branch}`], dir).then((x) => Number(x.stdout.trim()) || 0).catch(() => -1);
|
|
2070
|
+
if (ahead === 0) {
|
|
2071
|
+
const head = await git(["rev-parse", `refs/heads/${r.branch}`], dir).then((x) => x.stdout.trim()).catch(() => "");
|
|
2072
|
+
nothing.set(r.id, {
|
|
2073
|
+
repo: r.id,
|
|
2074
|
+
.../^[0-9a-f]{7,64}$/.test(head) ? { commit: head } : {},
|
|
2075
|
+
at: now,
|
|
2076
|
+
by: o.approval.by
|
|
2077
|
+
});
|
|
2078
|
+
rows.push({
|
|
2079
|
+
repo: r.id,
|
|
2080
|
+
outcome: "nothing",
|
|
2081
|
+
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.`
|
|
2082
|
+
});
|
|
2083
|
+
continue;
|
|
2084
|
+
}
|
|
2085
|
+
try {
|
|
2086
|
+
await git(["push", "-q", remote, `refs/heads/${r.branch}:refs/heads/${r.branch}`], dir);
|
|
2087
|
+
notes.push(`Pushed ${r.branch}, which the Git host didn't have.`);
|
|
2088
|
+
} catch (e) {
|
|
2089
|
+
rows.push({
|
|
2090
|
+
repo: r.id,
|
|
2091
|
+
outcome: "failed",
|
|
2092
|
+
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.`
|
|
2093
|
+
});
|
|
2094
|
+
continue;
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
try {
|
|
2099
|
+
const pr = await o.gitHost.createPullRequest({
|
|
2100
|
+
repo: r.id,
|
|
2101
|
+
head: r.branch,
|
|
2102
|
+
base,
|
|
2103
|
+
title: `${change.title} (release of ${id})`,
|
|
2104
|
+
body: releaseBody(id, change.title, r.branch, base)
|
|
2105
|
+
}, o.approval);
|
|
2106
|
+
r.pr = prOf(pr, now);
|
|
2107
|
+
changed2 = true;
|
|
2108
|
+
rows.push({
|
|
2109
|
+
repo: r.id,
|
|
2110
|
+
outcome: "raised",
|
|
2111
|
+
pr: r.pr,
|
|
2112
|
+
message: [
|
|
2113
|
+
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}`,
|
|
2114
|
+
...notes
|
|
2115
|
+
].join(" ")
|
|
2116
|
+
});
|
|
2117
|
+
} catch (e) {
|
|
2118
|
+
if (!(e instanceof AdapterError))
|
|
2119
|
+
throw e;
|
|
2120
|
+
if (e.code === "conflict") {
|
|
2121
|
+
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);
|
|
2122
|
+
if (open) {
|
|
2123
|
+
r.pr = prOf(open, now);
|
|
2124
|
+
changed2 = true;
|
|
2125
|
+
rows.push({
|
|
2126
|
+
repo: r.id,
|
|
2127
|
+
outcome: "kept",
|
|
2128
|
+
pr: r.pr,
|
|
2129
|
+
message: `${r.id}: release PR #${open.number} was already open on the Git host; recorded it (${open.url}).`
|
|
2130
|
+
});
|
|
2131
|
+
continue;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
rows.push({
|
|
2135
|
+
repo: r.id,
|
|
2136
|
+
outcome: "failed",
|
|
2137
|
+
message: `${r.id}: the release PR wasn't raised (${firstLine(e)}); fix that and raise the release PRs again.`
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
if (changed2)
|
|
2142
|
+
await writeChangeRepos(root, { ...cr, repos });
|
|
2143
|
+
const after = [...nothing.values()].sort((a, b) => a.repo < b.repo ? -1 : 1);
|
|
2144
|
+
const mergedBefore = file.merged ?? [];
|
|
2145
|
+
const mergedAfter = mergedBefore.filter((x) => repos.some((r) => r.id === x.repo && r.pr?.number === x.number && r.pr.state === "merged"));
|
|
2146
|
+
if (JSON.stringify(after) !== JSON.stringify(before) || mergedAfter.length !== mergedBefore.length)
|
|
2147
|
+
await writeReleaseFile(root, id, after, mergedAfter);
|
|
2148
|
+
return { change: id, rows };
|
|
2149
|
+
}
|
|
2150
|
+
async function syncReleasePrs(root, id, o) {
|
|
2151
|
+
const cr = await readChangeRepos(root, id);
|
|
2152
|
+
const rows = [];
|
|
2153
|
+
if (!cr)
|
|
2154
|
+
return { change: id, rows };
|
|
2155
|
+
const now = o.now ?? nowIso();
|
|
2156
|
+
let changed2 = false;
|
|
2157
|
+
const repos = cr.repos.map((r) => ({ ...r }));
|
|
2158
|
+
const file = await readReleaseFile(root, id);
|
|
2159
|
+
const merged = [...file.merged ?? []];
|
|
2160
|
+
let mergedChanged = false;
|
|
2161
|
+
for (const r of repos) {
|
|
2162
|
+
if (!r.pr)
|
|
2163
|
+
continue;
|
|
2164
|
+
let pr;
|
|
2165
|
+
try {
|
|
2166
|
+
pr = await o.gitHost.getPullRequest(r.id, r.pr.number);
|
|
2167
|
+
if (pr.head !== r.branch) {
|
|
2168
|
+
const mine = await o.gitHost.listPullRequests(r.id, { head: r.branch });
|
|
2169
|
+
const own = mine.find((x) => x.url === r.pr.url) ?? (mine.length === 1 ? mine[0] : void 0);
|
|
2170
|
+
if (!own) {
|
|
2171
|
+
rows.push({
|
|
2172
|
+
repo: r.id,
|
|
2173
|
+
outcome: "failed",
|
|
2174
|
+
pr: r.pr,
|
|
2175
|
+
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.`
|
|
2176
|
+
});
|
|
2177
|
+
continue;
|
|
2178
|
+
}
|
|
2179
|
+
pr = own;
|
|
2180
|
+
}
|
|
2181
|
+
} catch (e) {
|
|
2182
|
+
if (!(e instanceof AdapterError))
|
|
2183
|
+
throw e;
|
|
2184
|
+
rows.push({
|
|
2185
|
+
repo: r.id,
|
|
2186
|
+
outcome: "failed",
|
|
2187
|
+
pr: r.pr,
|
|
2188
|
+
message: `${r.id}: release PR #${r.pr.number} couldn't be read (${firstLine(e)}).`
|
|
2189
|
+
});
|
|
2190
|
+
continue;
|
|
2191
|
+
}
|
|
2192
|
+
const build = buildOf(pr);
|
|
2193
|
+
if (pr.state !== r.pr.state || pr.url !== r.pr.url || pr.number !== r.pr.number) {
|
|
2194
|
+
r.pr = prOf(pr, now);
|
|
2195
|
+
changed2 = true;
|
|
2196
|
+
}
|
|
2197
|
+
if (build && build !== r.build.status) {
|
|
2198
|
+
r.build = { ...r.build, status: build, at: now };
|
|
2199
|
+
changed2 = true;
|
|
2200
|
+
}
|
|
2201
|
+
if (pr.state === "merged" && !merged.some((x) => x.repo === r.id && x.number === pr.number)) {
|
|
2202
|
+
const commit = await branchTip(root, r, o.git, o.remote ?? "origin");
|
|
2203
|
+
if (commit) {
|
|
2204
|
+
for (let i = merged.length - 1; i >= 0; i--)
|
|
2205
|
+
if (merged[i].repo === r.id)
|
|
2206
|
+
merged.splice(i, 1);
|
|
2207
|
+
merged.push({ repo: r.id, number: pr.number, commit, at: now });
|
|
2208
|
+
mergedChanged = true;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
rows.push({
|
|
2212
|
+
repo: r.id,
|
|
2213
|
+
outcome: "kept",
|
|
2214
|
+
pr: r.pr,
|
|
2215
|
+
message: `${r.id}: release PR #${r.pr.number} is ${r.pr.state}${build ? `, checks ${build}` : ""} (${r.pr.url}).`
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
if (changed2)
|
|
2219
|
+
await writeChangeRepos(root, { ...cr, repos });
|
|
2220
|
+
if (mergedChanged)
|
|
2221
|
+
await writeReleaseFile(root, id, file.nothingToRelease, merged);
|
|
2222
|
+
return { change: id, rows };
|
|
2223
|
+
}
|
|
2224
|
+
async function branchTip(root, r, g, remote) {
|
|
2225
|
+
const dir = repoDir(root, r);
|
|
2226
|
+
if (!dir)
|
|
2227
|
+
return void 0;
|
|
2228
|
+
const git = g ?? createGit({ timeoutMs: 3e4 });
|
|
2229
|
+
const ref = `refs/heads/${r.branch}`;
|
|
2230
|
+
const onHost = await git(["ls-remote", "--heads", remote, ref], dir).then((x) => x.stdout.trim().split(/\s+/)[0] || void 0, () => void 0);
|
|
2231
|
+
if (onHost) {
|
|
2232
|
+
await git(["fetch", "-q", remote, `+${ref}:refs/remotes/${remote}/${r.branch}`], dir).catch(() => void 0);
|
|
2233
|
+
if (/^[0-9a-f]{7,64}$/.test(onHost))
|
|
2234
|
+
return onHost;
|
|
2235
|
+
}
|
|
2236
|
+
const here = await git(["rev-parse", "--verify", "--quiet", `${ref}^{commit}`], dir).then((x) => x.stdout.trim(), () => "");
|
|
2237
|
+
return /^[0-9a-f]{7,64}$/.test(here) ? here : void 0;
|
|
2238
|
+
}
|
|
2239
|
+
function buildOf(pr) {
|
|
2240
|
+
if (!pr.checks.length)
|
|
2241
|
+
return void 0;
|
|
2242
|
+
if (pr.checks.some((c) => c.status === "failed"))
|
|
2243
|
+
return "failed";
|
|
2244
|
+
if (pr.checks.some((c) => c.status === "pending"))
|
|
2245
|
+
return "running";
|
|
2246
|
+
return "passed";
|
|
2247
|
+
}
|
|
2248
|
+
async function requireMergedPrs(root) {
|
|
2249
|
+
const text = await readFile8(join9(orgPackDirOf(root), "process", "ci.yaml"), "utf8").catch(() => void 0);
|
|
2250
|
+
if (text === void 0)
|
|
2251
|
+
return true;
|
|
2252
|
+
try {
|
|
2253
|
+
const v = (0, import_yaml7.parse)(text)?.archive?.requireMergedPrs;
|
|
2254
|
+
return v !== false;
|
|
2255
|
+
} catch {
|
|
2256
|
+
return true;
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
async function releasePrReasons(root, id, o = {}) {
|
|
2260
|
+
const cr = await readChangeRepos(root, id).catch(() => void 0);
|
|
2261
|
+
if (!cr?.repos.length)
|
|
2262
|
+
return [];
|
|
2263
|
+
const merged = await requireMergedPrs(root);
|
|
2264
|
+
const nothing = await nothingToRelease(root, id, o);
|
|
2265
|
+
const stale = await staleReleasePrs(root, id, o);
|
|
2266
|
+
const out = [];
|
|
2267
|
+
for (const r of cr.repos) {
|
|
2268
|
+
if (!r.pr && nothing.has(r.id))
|
|
2269
|
+
continue;
|
|
2270
|
+
if (r.pr?.state === "merged" && stale.has(r.id)) {
|
|
2271
|
+
out.push({
|
|
2272
|
+
code: "release-pr-stale",
|
|
2273
|
+
message: `${r.id}: ${r.branch} has commits since release PR #${r.pr.number} merged, so they haven't reached its default branch: raise the release PRs again (Release \u203A Raise the release PRs, or atticus change release-prs ${id} --approve).`
|
|
2274
|
+
});
|
|
2275
|
+
continue;
|
|
2276
|
+
}
|
|
2277
|
+
if (!r.pr || r.pr.state === "closed")
|
|
2278
|
+
out.push({
|
|
2279
|
+
code: "release-pr-missing",
|
|
2280
|
+
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).`
|
|
2281
|
+
});
|
|
2282
|
+
else if (merged && r.pr.state !== "merged")
|
|
2283
|
+
out.push({
|
|
2284
|
+
code: "release-pr-not-merged",
|
|
2285
|
+
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.`
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
return out;
|
|
2289
|
+
}
|
|
2290
|
+
|
|
1731
2291
|
// packages/changes/dist/assess.js
|
|
1732
2292
|
var liveHandoff = (c) => c.handoffs.find((h) => h.state === "open" || h.state === "changes-requested");
|
|
2293
|
+
var isManualPrUrl = (url) => /^manual:\/\//i.test(url.trim());
|
|
2294
|
+
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
2295
|
async function assess(ctx, change) {
|
|
1734
2296
|
const type = changeTypeOf(ctx, change.type);
|
|
1735
2297
|
const footprint = await changeFootprint(ctx, change);
|
|
@@ -1770,29 +2332,32 @@ async function assess(ctx, change) {
|
|
|
1770
2332
|
...stageReasons,
|
|
1771
2333
|
...await checkArtifacts(ctx.root, change, due),
|
|
1772
2334
|
...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)))
|
|
2335
|
+
...stageReasons.length ? [] : await checkMandatoryRounds(ctx.root, change, phaseGates(ctx, phase).filter((g) => mandatoryGates(ctx).includes(g))),
|
|
2336
|
+
// ADR 0184: go/no-go is decided over the release PRs, each repo's change branch into its
|
|
2337
|
+
// default branch, merged (unless the org pack's ci.yaml says archive.requireMergedPrs: false).
|
|
2338
|
+
...phase === "release" ? await releasePrReasons(ctx.root, change.id) : []
|
|
1774
2339
|
];
|
|
1775
2340
|
const next = change.state === "returned" ? phaseState(phase) : nextState(type, phase);
|
|
1776
|
-
const
|
|
2341
|
+
const live2 = change.state === "returned" ? void 0 : liveHandoff(change);
|
|
1777
2342
|
const reasons = [...blockers];
|
|
1778
|
-
if (!
|
|
2343
|
+
if (!live2) {
|
|
1779
2344
|
reasons.push({
|
|
1780
2345
|
code: "no-handoff",
|
|
1781
2346
|
message: `No hand-off PR yet: raise "${handoffName(type, phase)}" when the ${phase} work is done.`
|
|
1782
2347
|
});
|
|
1783
|
-
} else if (
|
|
2348
|
+
} else if (live2.state === "changes-requested") {
|
|
1784
2349
|
reasons.push({
|
|
1785
2350
|
code: "changes-requested",
|
|
1786
|
-
message: `Changes were requested on PR #${
|
|
2351
|
+
message: `Changes were requested on PR #${live2.pr.number}; update the artifacts and hand off again.`
|
|
1787
2352
|
});
|
|
1788
2353
|
} else {
|
|
1789
|
-
const approved = prApprovers(
|
|
2354
|
+
const approved = prApprovers(live2, live2.approvals.map((a) => a.by));
|
|
1790
2355
|
reasons.push({
|
|
1791
2356
|
code: "pr-not-merged",
|
|
1792
|
-
message: approved.length ? `PR #${
|
|
2357
|
+
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
2358
|
});
|
|
1794
2359
|
}
|
|
1795
|
-
return { ...base, phase, next, ...
|
|
2360
|
+
return { ...base, phase, next, ...live2 ? { handoff: live2 } : {}, blockers, reasons };
|
|
1796
2361
|
}
|
|
1797
2362
|
async function canAdvance(change, root) {
|
|
1798
2363
|
const ctx = await loadChangeContext(root);
|
|
@@ -1809,9 +2374,15 @@ async function canAdvance(change, root) {
|
|
|
1809
2374
|
async function changeReport(root, id) {
|
|
1810
2375
|
const ctx = await loadChangeContext(root);
|
|
1811
2376
|
const a = await assess(ctx, await changeStore(root).read(id));
|
|
1812
|
-
|
|
2377
|
+
const tasks = a.phase === "build" ? await taskTicks(root, id) : void 0;
|
|
2378
|
+
return { ...a, nextStep: nextStep(a, tasks), problems: ctx.problems };
|
|
2379
|
+
}
|
|
2380
|
+
async function taskTicks(root, id) {
|
|
2381
|
+
const text = await readFile9(join10(changeFolder(root, id), "tasks.md"), "utf8").catch(() => "");
|
|
2382
|
+
const boxes = [...text.matchAll(/^\s*[-*]\s+\[([ xX])\]/gm)].map((m) => m[1] !== " ");
|
|
2383
|
+
return boxes.length ? { done: boxes.filter(Boolean).length, total: boxes.length } : void 0;
|
|
1813
2384
|
}
|
|
1814
|
-
function nextStep(a) {
|
|
2385
|
+
function nextStep(a, tasks) {
|
|
1815
2386
|
switch (a.state) {
|
|
1816
2387
|
case "draft":
|
|
1817
2388
|
return "Create the change with atticus change new.";
|
|
@@ -1825,25 +2396,34 @@ function nextStep(a) {
|
|
|
1825
2396
|
return `Revise the work, then hand off ${a.phase} again.`;
|
|
1826
2397
|
}
|
|
1827
2398
|
const name = handoffName(a.type, a.phase);
|
|
1828
|
-
if (!a.handoff)
|
|
2399
|
+
if (!a.handoff) {
|
|
2400
|
+
if (!a.blockers.length && tasks && tasks.done < tasks.total)
|
|
2401
|
+
return `Finish the ${a.phase} work (${tasks.done} of ${tasks.total} tasks ticked in tasks.md), then raise "${name}".`;
|
|
1829
2402
|
return a.blockers.length ? `Finish the ${a.phase} work, then raise "${name}".` : `Ready to raise "${name}".`;
|
|
2403
|
+
}
|
|
1830
2404
|
if (a.handoff.state === "changes-requested")
|
|
1831
2405
|
return `Update the artifacts and hand off again on PR #${a.handoff.pr.number}.`;
|
|
2406
|
+
if (isManualPrUrl(a.handoff.pr.url))
|
|
2407
|
+
return manualHandoffWords(a.handoff.phase);
|
|
1832
2408
|
return `Merge PR #${a.handoff.pr.number} once it is approved (${a.handoff.pr.url}); syncing then moves the change on.`;
|
|
1833
2409
|
}
|
|
1834
2410
|
|
|
1835
2411
|
// packages/changes/dist/new-change.js
|
|
1836
|
-
var
|
|
1837
|
-
import { mkdir, readFile as
|
|
1838
|
-
import { join as
|
|
1839
|
-
var
|
|
2412
|
+
var import_yaml8 = __toESM(require_dist(), 1);
|
|
2413
|
+
import { mkdir, readFile as readFile10, writeFile as writeFile2 } from "node:fs/promises";
|
|
2414
|
+
import { join as join11 } from "node:path";
|
|
2415
|
+
var nowIso2 = () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
1840
2416
|
var strip2 = (h) => h.startsWith("@") ? h.slice(1) : h;
|
|
2417
|
+
function windowsUnsafeName(id) {
|
|
2418
|
+
return /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|$)/i.test(id) || /[. ]$/.test(id);
|
|
2419
|
+
}
|
|
1841
2420
|
function slugify(title) {
|
|
1842
|
-
|
|
2421
|
+
const id = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80).replace(/-+$/, "");
|
|
2422
|
+
return windowsUnsafeName(id) ? `${id}-change` : id;
|
|
1843
2423
|
}
|
|
1844
2424
|
async function exists(path) {
|
|
1845
2425
|
try {
|
|
1846
|
-
await
|
|
2426
|
+
await readFile10(path);
|
|
1847
2427
|
return true;
|
|
1848
2428
|
} catch {
|
|
1849
2429
|
return false;
|
|
@@ -1851,7 +2431,7 @@ async function exists(path) {
|
|
|
1851
2431
|
}
|
|
1852
2432
|
async function defaultSchema(root) {
|
|
1853
2433
|
try {
|
|
1854
|
-
const cfg = (0,
|
|
2434
|
+
const cfg = (0, import_yaml8.parse)(await readFile10(join11(openspecDirOf(root), "config.yaml"), "utf8"));
|
|
1855
2435
|
if (cfg && typeof cfg.schema === "string")
|
|
1856
2436
|
return cfg.schema;
|
|
1857
2437
|
} catch {
|
|
@@ -1864,26 +2444,28 @@ async function newChange(root, opts) {
|
|
|
1864
2444
|
const id = opts.id ?? slugify(opts.title);
|
|
1865
2445
|
if (!RecordId.safeParse(id).success)
|
|
1866
2446
|
throw new ChangeError("invalid", `"${id}" can't be a change id; use a lowercase slug such as add-transfer-limit.`);
|
|
2447
|
+
if (windowsUnsafeName(id))
|
|
2448
|
+
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
2449
|
const template = pickTemplate(ctx.templates, type.id, opts.template);
|
|
1868
2450
|
if (!template)
|
|
1869
2451
|
throw new ChangeError("invalid", `Unknown change template ${opts.template}. Use one of: ${ctx.templates.map((t) => t.id).join(", ")}.`);
|
|
1870
2452
|
const store = changeStore(root);
|
|
1871
2453
|
if (await store.tryRead(id).catch(() => true))
|
|
1872
2454
|
throw new ChangeError("conflict", `Change ${id} already exists.`);
|
|
1873
|
-
const now = opts.now ??
|
|
2455
|
+
const now = opts.now ?? nowIso2();
|
|
1874
2456
|
const by = strip2(opts.by);
|
|
1875
2457
|
const dir = changeFolder(root, id);
|
|
1876
|
-
if (opts.openspec && !await exists(
|
|
2458
|
+
if (opts.openspec && !await exists(join11(dir, ".openspec.yaml"))) {
|
|
1877
2459
|
await opts.openspec.newChange(id, { description: opts.title });
|
|
1878
2460
|
} else {
|
|
1879
2461
|
await mkdir(dir, { recursive: true });
|
|
1880
|
-
if (!await exists(
|
|
1881
|
-
await
|
|
2462
|
+
if (!await exists(join11(dir, ".openspec.yaml")))
|
|
2463
|
+
await writeFile2(join11(dir, ".openspec.yaml"), `schema: ${await defaultSchema(root)}
|
|
1882
2464
|
created: ${now.slice(0, 10)}
|
|
1883
2465
|
`, "utf8");
|
|
1884
2466
|
}
|
|
1885
|
-
if (!await exists(
|
|
1886
|
-
await writeFileAtomic(
|
|
2467
|
+
if (!await exists(join11(dir, "brief.md")))
|
|
2468
|
+
await writeFileAtomic(join11(dir, "brief.md"), renderTemplate(template, {
|
|
1887
2469
|
title: opts.title,
|
|
1888
2470
|
id,
|
|
1889
2471
|
type: type.id,
|
|
@@ -1922,31 +2504,42 @@ created: ${now.slice(0, 10)}
|
|
|
1922
2504
|
|
|
1923
2505
|
// packages/changes/dist/publish.js
|
|
1924
2506
|
import { rm } from "node:fs/promises";
|
|
1925
|
-
import {
|
|
2507
|
+
import { randomUUID } from "node:crypto";
|
|
2508
|
+
import { join as join12, resolve } from "node:path";
|
|
1926
2509
|
function gitHandoffPublisher(opts = {}) {
|
|
1927
2510
|
const remote = opts.remote ?? "origin";
|
|
1928
2511
|
const env = opts.env ?? process.env;
|
|
1929
2512
|
return {
|
|
1930
|
-
async publish(
|
|
1931
|
-
requireApproval("git", approval);
|
|
2513
|
+
async publish(input) {
|
|
2514
|
+
requireApproval("git", input.approval);
|
|
2515
|
+
const key = resolve(input.root);
|
|
2516
|
+
const before = queue.get(key) ?? Promise.resolve();
|
|
2517
|
+
const mine = before.catch(() => void 0).then(() => publishOne(input));
|
|
2518
|
+
queue.set(key, mine.catch(() => void 0));
|
|
2519
|
+
return mine;
|
|
2520
|
+
}
|
|
2521
|
+
};
|
|
2522
|
+
async function publishOne({ root, branch, base, files, message }) {
|
|
2523
|
+
{
|
|
1932
2524
|
const git = createGit({ env });
|
|
1933
2525
|
const out = async (args) => (await git(args, root)).stdout.trim();
|
|
1934
2526
|
const tryOut = (args) => out(args).catch(() => void 0);
|
|
1935
2527
|
const gitDir = await out(["rev-parse", "--absolute-git-dir"]);
|
|
1936
2528
|
await tryOut(["fetch", "--quiet", remote, base]);
|
|
1937
2529
|
await tryOut(["fetch", "--quiet", remote, branch]);
|
|
1938
|
-
const
|
|
2530
|
+
const onRemote = await tryOut([
|
|
1939
2531
|
"rev-parse",
|
|
1940
2532
|
"--verify",
|
|
1941
2533
|
"--quiet",
|
|
1942
2534
|
`refs/remotes/${remote}/${branch}^{commit}`
|
|
1943
|
-
])
|
|
2535
|
+
]);
|
|
2536
|
+
const start = onRemote ?? await tryOut([
|
|
1944
2537
|
"rev-parse",
|
|
1945
2538
|
"--verify",
|
|
1946
2539
|
"--quiet",
|
|
1947
2540
|
`refs/remotes/${remote}/${base}^{commit}`
|
|
1948
2541
|
]) ?? await out(["rev-parse", "--verify", `refs/heads/${base}^{commit}`]);
|
|
1949
|
-
const index =
|
|
2542
|
+
const index = join12(gitDir, `atticus-handoff-index-${process.pid}-${randomUUID()}`);
|
|
1950
2543
|
const staged = createGit({ env: { ...env, GIT_INDEX_FILE: index } });
|
|
1951
2544
|
try {
|
|
1952
2545
|
await staged(["read-tree", start], root);
|
|
@@ -1955,24 +2548,42 @@ function gitHandoffPublisher(opts = {}) {
|
|
|
1955
2548
|
const tree = (await staged(["write-tree"], root)).stdout.trim();
|
|
1956
2549
|
const startTree = await out(["rev-parse", `${start}^{tree}`]);
|
|
1957
2550
|
const commit = tree === startTree ? start : await out(["commit-tree", tree, "-p", start, "-m", message]);
|
|
1958
|
-
await out(["update-ref", `refs/heads/${branch}`, commit]);
|
|
1959
2551
|
await out(["push", "--quiet", remote, `${commit}:refs/heads/${branch}`]);
|
|
1960
|
-
|
|
2552
|
+
await keepLocalBranch(out, tryOut, branch, start, commit);
|
|
2553
|
+
return { commit, changed: commit !== onRemote };
|
|
1961
2554
|
} finally {
|
|
1962
2555
|
await rm(index, { force: true });
|
|
1963
2556
|
}
|
|
1964
2557
|
}
|
|
1965
|
-
}
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
var queue = /* @__PURE__ */ new Map();
|
|
2561
|
+
async function keepLocalBranch(out, tryOut, branch, start, commit) {
|
|
2562
|
+
const ref = `refs/heads/${branch}`;
|
|
2563
|
+
const local = await tryOut(["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]);
|
|
2564
|
+
if (local === commit)
|
|
2565
|
+
return;
|
|
2566
|
+
const list = await tryOut(["worktree", "list", "--porcelain"]) ?? "";
|
|
2567
|
+
if (list.split(/\r?\n/).includes(`branch ${ref}`))
|
|
2568
|
+
return;
|
|
2569
|
+
if (!local) {
|
|
2570
|
+
await tryOut(["update-ref", ref, commit, ""]);
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
const own = await tryOut(["rev-list", "--count", `${start}..${local}`]);
|
|
2574
|
+
if (own === void 0 || own !== "0")
|
|
2575
|
+
return;
|
|
2576
|
+
await out(["update-ref", ref, commit, local]).catch(() => void 0);
|
|
1966
2577
|
}
|
|
1967
2578
|
|
|
1968
2579
|
// packages/changes/dist/handoff.js
|
|
1969
|
-
import { readdir as readdir6, readFile as
|
|
1970
|
-
import { join as
|
|
2580
|
+
import { readdir as readdir6, readFile as readFile12 } from "node:fs/promises";
|
|
2581
|
+
import { join as join14 } from "node:path";
|
|
1971
2582
|
|
|
1972
2583
|
// packages/changes/dist/drafted.js
|
|
1973
|
-
var
|
|
1974
|
-
import { mkdir as mkdir2, readdir as readdir5, readFile as
|
|
1975
|
-
import { dirname, join as
|
|
2584
|
+
var import_yaml9 = __toESM(require_dist(), 1);
|
|
2585
|
+
import { mkdir as mkdir2, readdir as readdir5, readFile as readFile11, rm as rm2 } from "node:fs/promises";
|
|
2586
|
+
import { dirname, join as join13, relative, sep } from "node:path";
|
|
1976
2587
|
var CAPTURE_SCHEMA_VERSION = 1;
|
|
1977
2588
|
var DraftCapture = external_exports.object({
|
|
1978
2589
|
schemaVersion: external_exports.literal(CAPTURE_SCHEMA_VERSION),
|
|
@@ -2026,7 +2637,7 @@ function draftedPath(path, step, artifacts) {
|
|
|
2026
2637
|
return false;
|
|
2027
2638
|
if (step) {
|
|
2028
2639
|
for (const w of step.writes)
|
|
2029
|
-
if (
|
|
2640
|
+
if (writeMatches(w, path))
|
|
2030
2641
|
return !machinery(path);
|
|
2031
2642
|
return step.artifacts.some((a) => artifactMatch(path, a));
|
|
2032
2643
|
}
|
|
@@ -2047,7 +2658,7 @@ var sameText = (text, recorded) => sha256(text) === recorded || sha256(text.repl
|
|
|
2047
2658
|
async function walk2(dir, rel = "") {
|
|
2048
2659
|
let entries;
|
|
2049
2660
|
try {
|
|
2050
|
-
entries = await readdir5(
|
|
2661
|
+
entries = await readdir5(join13(dir, rel), { withFileTypes: true });
|
|
2051
2662
|
} catch {
|
|
2052
2663
|
return [];
|
|
2053
2664
|
}
|
|
@@ -2192,7 +2803,7 @@ async function captureHandoff(input) {
|
|
|
2192
2803
|
} else {
|
|
2193
2804
|
const texts = await Promise.all(recorded.map(async (r) => [
|
|
2194
2805
|
r,
|
|
2195
|
-
await
|
|
2806
|
+
await readFile11(join13(folder, ...r.path.split("/")), "utf8").catch(() => void 0)
|
|
2196
2807
|
]));
|
|
2197
2808
|
if (texts.every(([r, t]) => t !== void 0 && sameText(t, r.sha256))) {
|
|
2198
2809
|
from = "working-tree";
|
|
@@ -2256,10 +2867,10 @@ async function captureHandoff(input) {
|
|
|
2256
2867
|
} : {}
|
|
2257
2868
|
};
|
|
2258
2869
|
const p = capturePaths(name);
|
|
2259
|
-
await rm2(
|
|
2260
|
-
await rm2(
|
|
2870
|
+
await rm2(join13(folder, ...p.drafted.split("/")), { recursive: true, force: true });
|
|
2871
|
+
await rm2(join13(folder, ...p.merged.split("/")), { recursive: true, force: true });
|
|
2261
2872
|
const put = async (base, rel, text) => {
|
|
2262
|
-
const f =
|
|
2873
|
+
const f = join13(folder, ...base.split("/"), ...rel.split("/"));
|
|
2263
2874
|
await mkdir2(dirname(f), { recursive: true });
|
|
2264
2875
|
await writeFileAtomic(f, text);
|
|
2265
2876
|
};
|
|
@@ -2268,37 +2879,37 @@ async function captureHandoff(input) {
|
|
|
2268
2879
|
if (state === "captured")
|
|
2269
2880
|
for (const [rel, t] of mergedFiles)
|
|
2270
2881
|
await put(p.merged, rel, t);
|
|
2271
|
-
await mkdir2(
|
|
2272
|
-
await writeFileAtomic(
|
|
2882
|
+
await mkdir2(join13(folder, "stage"), { recursive: true });
|
|
2883
|
+
await writeFileAtomic(join13(folder, ...p.capture.split("/")), HEADER + (0, import_yaml9.stringify)(capture, { lineWidth: 0 }));
|
|
2273
2884
|
return { capture, file: p.capture };
|
|
2274
2885
|
}
|
|
2275
2886
|
async function readFolderFiles(dir) {
|
|
2276
2887
|
const out = /* @__PURE__ */ new Map();
|
|
2277
2888
|
for (const f of await walk2(dir))
|
|
2278
|
-
out.set(f, (await
|
|
2889
|
+
out.set(f, (await readFile11(join13(dir, ...f.split("/")), "utf8")).replace(/\r\n/g, "\n"));
|
|
2279
2890
|
return out;
|
|
2280
2891
|
}
|
|
2281
2892
|
async function changeFolders(root) {
|
|
2282
|
-
const base =
|
|
2893
|
+
const base = join13(root, ...changesRelOf(root).split("/"));
|
|
2283
2894
|
const out = [];
|
|
2284
2895
|
const list = async (dir) => (await readdir5(dir, { withFileTypes: true }).catch(() => [])).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
2285
2896
|
for (const name of await list(base)) {
|
|
2286
2897
|
if (name === "archive")
|
|
2287
|
-
for (const a of await list(
|
|
2288
|
-
out.push(
|
|
2898
|
+
for (const a of await list(join13(base, "archive")))
|
|
2899
|
+
out.push(join13(base, "archive", a));
|
|
2289
2900
|
else
|
|
2290
|
-
out.push(
|
|
2901
|
+
out.push(join13(base, name));
|
|
2291
2902
|
}
|
|
2292
2903
|
return out;
|
|
2293
2904
|
}
|
|
2294
2905
|
async function readDraftPairs(root) {
|
|
2295
2906
|
const out = [];
|
|
2296
2907
|
for (const folder of await changeFolders(root)) {
|
|
2297
|
-
const names = (await readdir5(
|
|
2908
|
+
const names = (await readdir5(join13(folder, "stage")).catch(() => [])).filter((f) => f.endsWith(".capture.yaml")).sort();
|
|
2298
2909
|
for (const f of names) {
|
|
2299
2910
|
let capture;
|
|
2300
2911
|
try {
|
|
2301
|
-
capture = DraftCapture.parse((0,
|
|
2912
|
+
capture = DraftCapture.parse((0, import_yaml9.parse)(await readFile11(join13(folder, "stage", f), "utf8")));
|
|
2302
2913
|
} catch {
|
|
2303
2914
|
continue;
|
|
2304
2915
|
}
|
|
@@ -2306,8 +2917,8 @@ async function readDraftPairs(root) {
|
|
|
2306
2917
|
out.push({
|
|
2307
2918
|
capture,
|
|
2308
2919
|
folder: relative(root, folder).split(sep).join("/"),
|
|
2309
|
-
drafted: await readFolderFiles(
|
|
2310
|
-
merged: await readFolderFiles(
|
|
2920
|
+
drafted: await readFolderFiles(join13(folder, ...p.drafted.split("/"))),
|
|
2921
|
+
merged: await readFolderFiles(join13(folder, ...p.merged.split("/")))
|
|
2311
2922
|
});
|
|
2312
2923
|
}
|
|
2313
2924
|
}
|
|
@@ -2327,8 +2938,8 @@ async function completeCaptures(root, ctx, o) {
|
|
|
2327
2938
|
continue;
|
|
2328
2939
|
for (const h of change.handoffs.filter((x) => x.state === "merged")) {
|
|
2329
2940
|
const { name } = await captureStageOf(ctx, change, h.phase);
|
|
2330
|
-
const file =
|
|
2331
|
-
const prior = await
|
|
2941
|
+
const file = join13(changeFolder(root, change.id), ...capturePaths(name).capture.split("/"));
|
|
2942
|
+
const prior = await readFile11(file, "utf8").then((t) => DraftCapture.safeParse((0, import_yaml9.parse)(t)), () => void 0);
|
|
2332
2943
|
if (prior?.success && prior.data.pr.number === h.pr.number && prior.data.state !== "waiting-for-merge")
|
|
2333
2944
|
continue;
|
|
2334
2945
|
const base = prior?.success ? prior.data.base : o.defaultBase ?? "main";
|
|
@@ -2350,7 +2961,7 @@ async function completeCaptures(root, ctx, o) {
|
|
|
2350
2961
|
}
|
|
2351
2962
|
|
|
2352
2963
|
// packages/changes/dist/handoff.js
|
|
2353
|
-
var
|
|
2964
|
+
var nowIso3 = () => (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
2354
2965
|
var strip3 = (h) => h.startsWith("@") ? h.slice(1) : h;
|
|
2355
2966
|
function metarepo(ctx, repo) {
|
|
2356
2967
|
const fromRuntime = ctx.orgPack.runtime.adapters.gitHost?.settings.metarepo;
|
|
@@ -2368,7 +2979,7 @@ async function load(root, id) {
|
|
|
2368
2979
|
}
|
|
2369
2980
|
async function walkReview(dir, gate) {
|
|
2370
2981
|
const out = [];
|
|
2371
|
-
const base =
|
|
2982
|
+
const base = join14(dir, "review", gate);
|
|
2372
2983
|
let rounds;
|
|
2373
2984
|
try {
|
|
2374
2985
|
rounds = (await readdir6(base)).filter((r) => /^\d+$/.test(r)).sort((a, b) => Number(a) - Number(b));
|
|
@@ -2377,7 +2988,7 @@ async function walkReview(dir, gate) {
|
|
|
2377
2988
|
}
|
|
2378
2989
|
for (const r of rounds) {
|
|
2379
2990
|
try {
|
|
2380
|
-
for (const f of (await readdir6(
|
|
2991
|
+
for (const f of (await readdir6(join14(base, r))).filter((x) => /\.ya?ml$/.test(x)).sort())
|
|
2381
2992
|
out.push(`review/${gate}/${r}/${f}`);
|
|
2382
2993
|
} catch {
|
|
2383
2994
|
}
|
|
@@ -2388,12 +2999,12 @@ async function keptPairFiles(dir) {
|
|
|
2388
2999
|
const out = [];
|
|
2389
3000
|
let entries;
|
|
2390
3001
|
try {
|
|
2391
|
-
entries = await readdir6(
|
|
3002
|
+
entries = await readdir6(join14(dir, "stage"), { withFileTypes: true });
|
|
2392
3003
|
} catch {
|
|
2393
3004
|
return out;
|
|
2394
3005
|
}
|
|
2395
3006
|
const walk3 = async (rel) => {
|
|
2396
|
-
for (const e of await readdir6(
|
|
3007
|
+
for (const e of await readdir6(join14(dir, ...rel.split("/")), { withFileTypes: true }).catch(() => [])) {
|
|
2397
3008
|
const p = `${rel}/${e.name}`;
|
|
2398
3009
|
if (e.isDirectory())
|
|
2399
3010
|
await walk3(p);
|
|
@@ -2421,18 +3032,23 @@ async function handoffFiles(ctx, a, phase) {
|
|
|
2421
3032
|
for (const x of f)
|
|
2422
3033
|
files.add(x);
|
|
2423
3034
|
}
|
|
2424
|
-
const
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
3035
|
+
const at = PHASES.indexOf(phase);
|
|
3036
|
+
for (const step of ctx.policy.stages.filter((s) => PHASES.indexOf(s.phase) <= at)) {
|
|
3037
|
+
if (await phaseStage(ctx, a.change, step.phase))
|
|
3038
|
+
for (const f of await stageFiles(dir, step))
|
|
3039
|
+
files.add(f);
|
|
3040
|
+
}
|
|
3041
|
+
const present = async (f) => readFile12(join14(dir, ...f.split("/"))).then(() => true, () => false);
|
|
3042
|
+
for (const own of ["stage/corrections.yaml", "stage/drafted.yaml"])
|
|
3043
|
+
if (await present(own))
|
|
3044
|
+
files.add(own);
|
|
2428
3045
|
if (phase === "build") {
|
|
2429
|
-
for (const extra of ["tasks.md", "repos.yaml"])
|
|
2430
|
-
|
|
2431
|
-
await readFile10(join12(dir, extra));
|
|
3046
|
+
for (const extra of ["tasks.md", "repos.yaml"])
|
|
3047
|
+
if (await present(extra))
|
|
2432
3048
|
files.add(extra);
|
|
2433
|
-
|
|
2434
|
-
}
|
|
2435
|
-
|
|
3049
|
+
for (const t of await readdir6(join14(dir, "tasks"), { withFileTypes: true }).catch(() => []))
|
|
3050
|
+
if (t.isDirectory() && await present(`tasks/${t.name}/report.md`))
|
|
3051
|
+
files.add(`tasks/${t.name}/report.md`);
|
|
2436
3052
|
}
|
|
2437
3053
|
for (const gate of phaseGates(ctx, phase))
|
|
2438
3054
|
for (const f of await walkReview(dir, gate))
|
|
@@ -2441,6 +3057,18 @@ async function handoffFiles(ctx, a, phase) {
|
|
|
2441
3057
|
files.add(f);
|
|
2442
3058
|
return { files: [...files].sort(), artifacts };
|
|
2443
3059
|
}
|
|
3060
|
+
function namesChange(command, id) {
|
|
3061
|
+
const w = (command ?? "").trim().split(/\s+/);
|
|
3062
|
+
if (w[0] !== "atticus")
|
|
3063
|
+
return false;
|
|
3064
|
+
const changeStep = w[1] === "change" || w[1] === "plan" || w[1] === "critic";
|
|
3065
|
+
return changeStep && w.slice(2).includes(id);
|
|
3066
|
+
}
|
|
3067
|
+
async function changeQuestionFiles(root, id) {
|
|
3068
|
+
const { questions } = await listQuestions(root, { state: "answered" });
|
|
3069
|
+
const rel = questionsRelOf(root);
|
|
3070
|
+
return questions.filter((q) => namesChange(q.askedBy.command, id) || namesChange(q.rerun, id)).map((q) => `${rel}/${q.id}.yaml`).sort();
|
|
3071
|
+
}
|
|
2444
3072
|
function prBody(a, phase, next, paths, reviewers, review) {
|
|
2445
3073
|
const c = a.change;
|
|
2446
3074
|
const lines2 = [
|
|
@@ -2475,7 +3103,7 @@ async function handoff(root, id, opts) {
|
|
|
2475
3103
|
requireApproval("changes", opts.approval);
|
|
2476
3104
|
const { ctx, change } = await load(root, id);
|
|
2477
3105
|
const repo = metarepo(ctx, opts.repo);
|
|
2478
|
-
const now = opts.now ??
|
|
3106
|
+
const now = opts.now ?? nowIso3();
|
|
2479
3107
|
const by = strip3(opts.by);
|
|
2480
3108
|
const a = await assess(ctx, change);
|
|
2481
3109
|
const phase = a.phase;
|
|
@@ -2487,7 +3115,7 @@ async function handoff(root, id, opts) {
|
|
|
2487
3115
|
await requireValid(opts.openspec, id);
|
|
2488
3116
|
const { files, artifacts } = await handoffFiles(ctx, a, phase);
|
|
2489
3117
|
const dir = changeFolder(root, id);
|
|
2490
|
-
const hashed = await Promise.all(files.map(async (path) => ({ path, sha256: sha256(await
|
|
3118
|
+
const hashed = await Promise.all(files.map(async (path) => ({ path, sha256: sha256(await readFile12(join14(dir, path))) })));
|
|
2491
3119
|
const authors = { ...change.authors };
|
|
2492
3120
|
for (const art of artifacts) {
|
|
2493
3121
|
const people = (opts.authors?.[art] ?? [by]).map(strip3);
|
|
@@ -2495,15 +3123,20 @@ async function handoff(root, id, opts) {
|
|
|
2495
3123
|
}
|
|
2496
3124
|
const branch = handoffBranch(id, phase);
|
|
2497
3125
|
const rel = changesRelOf(root);
|
|
2498
|
-
const
|
|
3126
|
+
const questionFiles = await changeQuestionFiles(root, id);
|
|
3127
|
+
const metaFiles = [
|
|
3128
|
+
...files.map((f) => `${rel}/${id}/${f}`),
|
|
3129
|
+
`${rel}/${id}/change.yaml`,
|
|
3130
|
+
...questionFiles
|
|
3131
|
+
];
|
|
2499
3132
|
const reviewers = [
|
|
2500
3133
|
...new Set(files.flatMap((f) => ownersFor(ctx.codeowners, `${rel}/${id}/${f}`)))
|
|
2501
3134
|
].filter((p) => p !== by).sort();
|
|
2502
|
-
const
|
|
2503
|
-
if (
|
|
2504
|
-
const pr2 = await opts.gitHost
|
|
3135
|
+
const live2 = change.state === "returned" ? void 0 : liveHandoff(change);
|
|
3136
|
+
if (live2 && live2.phase === phase) {
|
|
3137
|
+
const pr2 = await handoffPr(opts.gitHost, repo, id, live2);
|
|
2505
3138
|
if (pr2.state === "open" || pr2.state === "draft") {
|
|
2506
|
-
await opts.publisher?.publish({
|
|
3139
|
+
const published = await opts.publisher?.publish({
|
|
2507
3140
|
root,
|
|
2508
3141
|
branch,
|
|
2509
3142
|
base: pr2.base,
|
|
@@ -2511,9 +3144,11 @@ async function handoff(root, id, opts) {
|
|
|
2511
3144
|
message: `${handoffName(a.type, phase)}: update`,
|
|
2512
3145
|
approval: opts.approval
|
|
2513
3146
|
});
|
|
2514
|
-
|
|
3147
|
+
const unchanged = published ? published.changed === false : sameArtifacts(hashed, live2.artifacts);
|
|
3148
|
+
if (!unchanged)
|
|
3149
|
+
await opts.gitHost.comment(repo, pr2.number, `${by} updated the hand-off for another review.`, opts.approval);
|
|
2515
3150
|
const updated = {
|
|
2516
|
-
...
|
|
3151
|
+
...live2,
|
|
2517
3152
|
state: "open",
|
|
2518
3153
|
updatedAt: now,
|
|
2519
3154
|
reviewers,
|
|
@@ -2523,14 +3158,21 @@ async function handoff(root, id, opts) {
|
|
|
2523
3158
|
await changeStore(root).write({
|
|
2524
3159
|
...change,
|
|
2525
3160
|
authors,
|
|
2526
|
-
handoffs: change.handoffs.map((h2) => h2 ===
|
|
3161
|
+
handoffs: change.handoffs.map((h2) => h2 === live2 ? updated : h2)
|
|
2527
3162
|
});
|
|
2528
3163
|
return updated;
|
|
2529
3164
|
}
|
|
2530
3165
|
if (pr2.state === "merged")
|
|
2531
3166
|
throw new ChangeError("refused", `PR #${pr2.number} is already merged on the Git host; sync the change instead.`);
|
|
2532
|
-
|
|
2533
|
-
|
|
3167
|
+
live2.state = "closed";
|
|
3168
|
+
live2.updatedAt = now;
|
|
3169
|
+
}
|
|
3170
|
+
if (change.state === "returned") {
|
|
3171
|
+
const open = (await opts.gitHost.listPullRequests(repo, { head: branch })).find((p) => p.state === "open" || p.state === "draft");
|
|
3172
|
+
if (open) {
|
|
3173
|
+
const message = `PR #${open.number} from ${branch} is still open on the Git host (${open.url}): close it there first (the return asked for that), then resubmit the change.`;
|
|
3174
|
+
throw new ChangeError("refused", message, [{ code: "host-refused", message }]);
|
|
3175
|
+
}
|
|
2534
3176
|
}
|
|
2535
3177
|
const base = opts.base ?? (await opts.gitHost.getRepo(repo)).defaultBranch;
|
|
2536
3178
|
const next = a.next;
|
|
@@ -2542,13 +3184,23 @@ async function handoff(root, id, opts) {
|
|
|
2542
3184
|
message: `${handoffName(a.type, phase)}: ${change.title}`,
|
|
2543
3185
|
approval: opts.approval
|
|
2544
3186
|
});
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
3187
|
+
let pr;
|
|
3188
|
+
try {
|
|
3189
|
+
pr = await opts.gitHost.createPullRequest({
|
|
3190
|
+
repo,
|
|
3191
|
+
head: branch,
|
|
3192
|
+
base,
|
|
3193
|
+
title: `${change.title}: ${handoffName(a.type, phase)}`,
|
|
3194
|
+
body: prBody(a, phase, change.state === "returned" ? nextAfterResubmit(a, phase) : next, [...files.map((f) => `${rel}/${id}/${f}`), ...questionFiles], reviewers, opts.review)
|
|
3195
|
+
}, opts.approval);
|
|
3196
|
+
} catch (e) {
|
|
3197
|
+
if (!(e instanceof AdapterError) || e.code !== "conflict")
|
|
3198
|
+
throw e;
|
|
3199
|
+
const open = (await opts.gitHost.listPullRequests(repo, { head: branch }).catch(() => [])).filter((p) => p.base === base && (p.state === "open" || p.state === "draft")).at(0);
|
|
3200
|
+
if (!open)
|
|
3201
|
+
throw e;
|
|
3202
|
+
pr = open;
|
|
3203
|
+
}
|
|
2552
3204
|
const h = {
|
|
2553
3205
|
phase,
|
|
2554
3206
|
branch,
|
|
@@ -2575,28 +3227,44 @@ async function handoff(root, id, opts) {
|
|
|
2575
3227
|
});
|
|
2576
3228
|
return h;
|
|
2577
3229
|
}
|
|
3230
|
+
function sameArtifacts(a, b) {
|
|
3231
|
+
const key = (xs) => JSON.stringify([...xs].map((x) => `${x.path} ${x.sha256}`).sort());
|
|
3232
|
+
return key(a) === key(b);
|
|
3233
|
+
}
|
|
2578
3234
|
function nextAfterResubmit(a, phase) {
|
|
2579
3235
|
const next = a.type.phases[a.type.phases.indexOf(phase) + 1];
|
|
2580
3236
|
return next ? phaseState(next) : "released";
|
|
2581
3237
|
}
|
|
3238
|
+
async function handoffPr(gitHost, repo, id, live2) {
|
|
3239
|
+
const branch = handoffBranch(id, live2.phase);
|
|
3240
|
+
const pr = await gitHost.getPullRequest(repo, live2.pr.number);
|
|
3241
|
+
if (pr.head === branch)
|
|
3242
|
+
return pr;
|
|
3243
|
+
const mine = await gitHost.listPullRequests(repo, { head: branch });
|
|
3244
|
+
const own = mine.find((x) => x.url === live2.pr.url) ?? (mine.length === 1 ? mine[0] : void 0);
|
|
3245
|
+
if (own)
|
|
3246
|
+
return own;
|
|
3247
|
+
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.`;
|
|
3248
|
+
throw new ChangeError("refused", message, [{ code: "host-refused", message }]);
|
|
3249
|
+
}
|
|
2582
3250
|
async function syncHandoff(root, id, opts) {
|
|
2583
3251
|
const { ctx, change } = await load(root, id);
|
|
2584
3252
|
const repo = metarepo(ctx, opts.repo);
|
|
2585
|
-
const now = opts.now ??
|
|
2586
|
-
const
|
|
2587
|
-
if (!
|
|
3253
|
+
const now = opts.now ?? nowIso3();
|
|
3254
|
+
const live2 = liveHandoff(change);
|
|
3255
|
+
if (!live2)
|
|
2588
3256
|
throw new ChangeError("refused", `${id} has no open hand-off PR to sync.`, [
|
|
2589
3257
|
{ code: "no-handoff", message: `${id} has no open hand-off PR.` }
|
|
2590
3258
|
]);
|
|
2591
3259
|
const a = await assess(ctx, change);
|
|
2592
|
-
const pr = await opts.gitHost
|
|
2593
|
-
const current = pr.url !==
|
|
3260
|
+
const pr = await handoffPr(opts.gitHost, repo, id, live2);
|
|
3261
|
+
const current = pr.url !== live2.pr.url || pr.number !== live2.pr.number ? { ...live2, pr: { number: pr.number, url: pr.url } } : live2;
|
|
2594
3262
|
const store = changeStore(root);
|
|
2595
3263
|
const save = async (h2, extra = {}) => {
|
|
2596
3264
|
const updated = {
|
|
2597
3265
|
...change,
|
|
2598
3266
|
...extra,
|
|
2599
|
-
handoffs: change.handoffs.map((x) => x ===
|
|
3267
|
+
handoffs: change.handoffs.map((x) => x === live2 ? h2 : x)
|
|
2600
3268
|
};
|
|
2601
3269
|
await store.write(updated);
|
|
2602
3270
|
return updated;
|
|
@@ -2618,7 +3286,7 @@ async function syncHandoff(root, id, opts) {
|
|
|
2618
3286
|
latest.set(strip3(r.by), r.state);
|
|
2619
3287
|
const approvers = [...latest].filter(([, s]) => s === "approved").map(([p]) => p);
|
|
2620
3288
|
const requested = [...latest].filter(([, s]) => s === "changes-requested").map(([p]) => p).sort();
|
|
2621
|
-
const counted = prApprovers(
|
|
3289
|
+
const counted = prApprovers(live2, approvers);
|
|
2622
3290
|
const approvals = counted.map((by) => ({ by, at: now }));
|
|
2623
3291
|
const advance = async (by) => {
|
|
2624
3292
|
const h2 = { ...current, state: "merged", updatedAt: now, approvals };
|
|
@@ -2631,7 +3299,7 @@ async function syncHandoff(root, id, opts) {
|
|
|
2631
3299
|
at: now,
|
|
2632
3300
|
by,
|
|
2633
3301
|
pr: pr.number,
|
|
2634
|
-
reason: `${handoffName(a.type,
|
|
3302
|
+
reason: `${handoffName(a.type, live2.phase)} merged`
|
|
2635
3303
|
}
|
|
2636
3304
|
];
|
|
2637
3305
|
let state = next;
|
|
@@ -2666,7 +3334,7 @@ async function syncHandoff(root, id, opts) {
|
|
|
2666
3334
|
};
|
|
2667
3335
|
};
|
|
2668
3336
|
if (pr.state === "merged")
|
|
2669
|
-
return advance(strip3(opts.approval?.by ??
|
|
3337
|
+
return advance(strip3(opts.approval?.by ?? live2.by));
|
|
2670
3338
|
if (requested.length > 0) {
|
|
2671
3339
|
const h2 = { ...current, state: "changes-requested", updatedAt: now, approvals };
|
|
2672
3340
|
await save(h2);
|
|
@@ -2687,8 +3355,8 @@ async function syncHandoff(root, id, opts) {
|
|
|
2687
3355
|
if (counted.length === 0)
|
|
2688
3356
|
reasons.push(approvers.length > 0 ? {
|
|
2689
3357
|
code: "self-approval",
|
|
2690
|
-
person: strip3(
|
|
2691
|
-
message: `PR #${pr.number} is approved only by ${strip3(
|
|
3358
|
+
person: strip3(live2.by),
|
|
3359
|
+
message: `PR #${pr.number} is approved only by ${strip3(live2.by)}, who raised it; it needs someone else's approval.`
|
|
2692
3360
|
} : {
|
|
2693
3361
|
code: "approval-missing",
|
|
2694
3362
|
message: `PR #${pr.number} isn't approved yet: ask a reviewer on the Git host (${pr.url}).`
|
|
@@ -2727,19 +3395,19 @@ async function syncHandoff(root, id, opts) {
|
|
|
2727
3395
|
}
|
|
2728
3396
|
async function returnToPo(root, id, opts) {
|
|
2729
3397
|
const { ctx, change } = await load(root, id);
|
|
2730
|
-
const
|
|
2731
|
-
if (!
|
|
3398
|
+
const live2 = liveHandoff(change);
|
|
3399
|
+
if (!live2)
|
|
2732
3400
|
throw new ChangeError("refused", `${id} has no open hand-off PR to return.`, [
|
|
2733
3401
|
{ code: "no-handoff", message: `${id} has no open hand-off PR.` }
|
|
2734
3402
|
]);
|
|
2735
3403
|
requireApproval("changes", opts.approval);
|
|
2736
3404
|
const repo = metarepo(ctx, opts.repo);
|
|
2737
|
-
const now = opts.now ??
|
|
3405
|
+
const now = opts.now ?? nowIso3();
|
|
2738
3406
|
const by = strip3(opts.by);
|
|
2739
3407
|
const host = opts.gitHost;
|
|
2740
|
-
await host.comment(repo,
|
|
2741
|
-
const closedOnHost = host.closePullRequest ? (await host.closePullRequest(repo,
|
|
2742
|
-
const h = { ...
|
|
3408
|
+
await host.comment(repo, live2.pr.number, `Returned to the product owner by ${by}: ${opts.reason}`, opts.approval);
|
|
3409
|
+
const closedOnHost = host.closePullRequest ? (await host.closePullRequest(repo, live2.pr.number, opts.approval)).state === "closed" : false;
|
|
3410
|
+
const h = { ...live2, state: "closed", updatedAt: now };
|
|
2743
3411
|
await changeStore(root).write({
|
|
2744
3412
|
...change,
|
|
2745
3413
|
state: "returned",
|
|
@@ -2750,11 +3418,11 @@ async function returnToPo(root, id, opts) {
|
|
|
2750
3418
|
to: "returned",
|
|
2751
3419
|
at: now,
|
|
2752
3420
|
by,
|
|
2753
|
-
pr:
|
|
3421
|
+
pr: live2.pr.number,
|
|
2754
3422
|
reason: `Returned to the product owner (${opts.reason})`
|
|
2755
3423
|
}
|
|
2756
3424
|
],
|
|
2757
|
-
handoffs: change.handoffs.map((x) => x ===
|
|
3425
|
+
handoffs: change.handoffs.map((x) => x === live2 ? h : x)
|
|
2758
3426
|
});
|
|
2759
3427
|
return { state: "returned", closedOnHost, handoff: h };
|
|
2760
3428
|
}
|
|
@@ -2774,7 +3442,7 @@ async function archiveChange(root, id, opts) {
|
|
|
2774
3442
|
throw new ChangeError("refused", `${id} can't be archived yet.`, a.reasons);
|
|
2775
3443
|
if (opts.openspec)
|
|
2776
3444
|
await requireValid(opts.openspec, id);
|
|
2777
|
-
const now = opts.now ??
|
|
3445
|
+
const now = opts.now ?? nowIso3();
|
|
2778
3446
|
const updated = {
|
|
2779
3447
|
...change,
|
|
2780
3448
|
state: "archived",
|
|
@@ -2790,9 +3458,9 @@ async function archiveChange(root, id, opts) {
|
|
|
2790
3458
|
}
|
|
2791
3459
|
|
|
2792
3460
|
// packages/changes/dist/learn.js
|
|
2793
|
-
var
|
|
2794
|
-
import { readFile as
|
|
2795
|
-
import { join as
|
|
3461
|
+
var import_yaml10 = __toESM(require_dist(), 1);
|
|
3462
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
3463
|
+
import { join as join15 } from "node:path";
|
|
2796
3464
|
var LEARN_SCHEMA_VERSION = 1;
|
|
2797
3465
|
var MAX_CELLS = 4e6;
|
|
2798
3466
|
var lines = (text) => {
|
|
@@ -3139,7 +3807,7 @@ async function pairTouches(root, p) {
|
|
|
3139
3807
|
let entry;
|
|
3140
3808
|
const yaml = (t) => {
|
|
3141
3809
|
try {
|
|
3142
|
-
const v = (0,
|
|
3810
|
+
const v = (0, import_yaml10.parse)(t);
|
|
3143
3811
|
return v && typeof v === "object" ? v : void 0;
|
|
3144
3812
|
} catch {
|
|
3145
3813
|
return void 0;
|
|
@@ -3181,7 +3849,7 @@ async function pairTouches(root, p) {
|
|
|
3181
3849
|
flows.push(m[1]);
|
|
3182
3850
|
}
|
|
3183
3851
|
}
|
|
3184
|
-
const change = yaml(await
|
|
3852
|
+
const change = yaml(await readFile13(join15(root, ...p.folder.split("/"), "change.yaml"), "utf8").catch(() => ""));
|
|
3185
3853
|
const touches = change?.touches ?? {};
|
|
3186
3854
|
for (const r of Array.isArray(touches.repos) ? touches.repos : [])
|
|
3187
3855
|
if (typeof r === "string")
|
|
@@ -3274,8 +3942,8 @@ async function detectConflicts(root) {
|
|
|
3274
3942
|
}
|
|
3275
3943
|
|
|
3276
3944
|
// packages/changes/dist/size.js
|
|
3277
|
-
import { readFile as
|
|
3278
|
-
import { join as
|
|
3945
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
3946
|
+
import { join as join16 } from "node:path";
|
|
3279
3947
|
async function changeSize(ctx, change) {
|
|
3280
3948
|
const type = ctx.orgPack.changeTypes.types.find((t) => t.id === change.type);
|
|
3281
3949
|
if (!type)
|
|
@@ -3284,7 +3952,7 @@ async function changeSize(ctx, change) {
|
|
|
3284
3952
|
why: "standard",
|
|
3285
3953
|
words: `${change.type} isn't a change type in change-types.yaml, so ${change.id} takes the full path.`
|
|
3286
3954
|
};
|
|
3287
|
-
const brief = await
|
|
3955
|
+
const brief = await readFile14(join16(changeFolder(ctx.root, change.id), "brief.md"), "utf8").catch(() => "");
|
|
3288
3956
|
const f = fastPathOf(type, brief);
|
|
3289
3957
|
const gate = f.fast ? fastGateOf(type, ctx.orgPack.gates) : void 0;
|
|
3290
3958
|
const words = {
|
|
@@ -3298,14 +3966,14 @@ async function changeSize(ctx, change) {
|
|
|
3298
3966
|
}
|
|
3299
3967
|
|
|
3300
3968
|
// packages/changes/dist/openspec-stores.js
|
|
3301
|
-
import { existsSync } from "node:fs";
|
|
3302
|
-
import { readFile as
|
|
3969
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
3970
|
+
import { readFile as readFile15, writeFile as writeFile3 } from "node:fs/promises";
|
|
3303
3971
|
import { homedir } from "node:os";
|
|
3304
|
-
import { basename, isAbsolute, join as
|
|
3972
|
+
import { basename, isAbsolute, join as join17, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
3305
3973
|
function storeDirOf(root, path) {
|
|
3306
3974
|
if (path === "~" || path.startsWith("~/") || path.startsWith("~\\"))
|
|
3307
|
-
return
|
|
3308
|
-
return isAbsolute(path) ?
|
|
3975
|
+
return join17(homedir(), path.slice(2));
|
|
3976
|
+
return isAbsolute(path) ? resolve2(path) : resolve2(root, path);
|
|
3309
3977
|
}
|
|
3310
3978
|
function packRel(root) {
|
|
3311
3979
|
return relative2(root, orgPackDirOf(root)).split(sep2).join("/");
|
|
@@ -3334,7 +4002,7 @@ async function storeSpecLookup(root, capability) {
|
|
|
3334
4002
|
var REMOTE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
3335
4003
|
var SCP = /^[^/\\]+@[^:/\\]+:/;
|
|
3336
4004
|
async function metarepoOrigin(root, git = createGit()) {
|
|
3337
|
-
if (!
|
|
4005
|
+
if (!existsSync2(join17(root, ".git")))
|
|
3338
4006
|
return void 0;
|
|
3339
4007
|
try {
|
|
3340
4008
|
const url = (await git(["remote", "get-url", "origin"], root)).stdout.trim();
|
|
@@ -3345,7 +4013,7 @@ async function metarepoOrigin(root, git = createGit()) {
|
|
|
3345
4013
|
return void 0;
|
|
3346
4014
|
}
|
|
3347
4015
|
}
|
|
3348
|
-
async function referenceMetarepoIn(root,
|
|
4016
|
+
async function referenceMetarepoIn(root, repoDir2, o = {}) {
|
|
3349
4017
|
const { settings } = await loadOpenSpecSettings(orgPackDirOf(root));
|
|
3350
4018
|
if (!settings.referenceInRepos)
|
|
3351
4019
|
return { action: "off" };
|
|
@@ -3357,8 +4025,8 @@ async function referenceMetarepoIn(root, repoDir, o = {}) {
|
|
|
3357
4025
|
file,
|
|
3358
4026
|
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
4027
|
};
|
|
3360
|
-
const path =
|
|
3361
|
-
const text = await
|
|
4028
|
+
const path = join17(repoDir2, "openspec", "config.yaml");
|
|
4029
|
+
const text = await readFile15(path, "utf8").catch(() => void 0);
|
|
3362
4030
|
if (text === void 0)
|
|
3363
4031
|
return {
|
|
3364
4032
|
action: "skipped",
|
|
@@ -3370,10 +4038,180 @@ async function referenceMetarepoIn(root, repoDir, o = {}) {
|
|
|
3370
4038
|
if ("problem" in r)
|
|
3371
4039
|
return { action: "skipped", file, why: r.problem };
|
|
3372
4040
|
if (r.action === "added")
|
|
3373
|
-
await
|
|
4041
|
+
await writeFile3(path, r.text, "utf8");
|
|
3374
4042
|
return { action: r.action, file };
|
|
3375
4043
|
}
|
|
3376
4044
|
|
|
4045
|
+
// packages/changes/dist/merge-record.js
|
|
4046
|
+
var import_yaml11 = __toESM(require_dist(), 1);
|
|
4047
|
+
import { isDeepStrictEqual } from "node:util";
|
|
4048
|
+
var HANDOFF_RANK = {
|
|
4049
|
+
open: 0,
|
|
4050
|
+
"changes-requested": 1,
|
|
4051
|
+
closed: 2,
|
|
4052
|
+
merged: 3
|
|
4053
|
+
};
|
|
4054
|
+
var Clash = class extends Error {
|
|
4055
|
+
field;
|
|
4056
|
+
constructor(field) {
|
|
4057
|
+
super(field);
|
|
4058
|
+
this.field = field;
|
|
4059
|
+
}
|
|
4060
|
+
};
|
|
4061
|
+
function three(field, base, ours, theirs) {
|
|
4062
|
+
if (isDeepStrictEqual(ours, theirs))
|
|
4063
|
+
return ours;
|
|
4064
|
+
if (base.has) {
|
|
4065
|
+
if (isDeepStrictEqual(ours, base.value))
|
|
4066
|
+
return theirs;
|
|
4067
|
+
if (isDeepStrictEqual(theirs, base.value))
|
|
4068
|
+
return ours;
|
|
4069
|
+
}
|
|
4070
|
+
throw new Clash(field);
|
|
4071
|
+
}
|
|
4072
|
+
function union(ours, theirs, key, pick) {
|
|
4073
|
+
const out = /* @__PURE__ */ new Map();
|
|
4074
|
+
for (const x of ours)
|
|
4075
|
+
if (!out.has(key(x)))
|
|
4076
|
+
out.set(key(x), x);
|
|
4077
|
+
for (const x of theirs) {
|
|
4078
|
+
const k = key(x);
|
|
4079
|
+
const had = out.get(k);
|
|
4080
|
+
out.set(k, had === void 0 ? x : pick ? pick(had, x) : had);
|
|
4081
|
+
}
|
|
4082
|
+
return [...out.values()];
|
|
4083
|
+
}
|
|
4084
|
+
var startsWith = (a, b) => b.length <= a.length && b.every((x, i) => isDeepStrictEqual(x, a[i]));
|
|
4085
|
+
function mergeHistory(ours, theirs) {
|
|
4086
|
+
if (startsWith(ours, theirs))
|
|
4087
|
+
return ours;
|
|
4088
|
+
if (startsWith(theirs, ours))
|
|
4089
|
+
return theirs;
|
|
4090
|
+
const all = union(ours, theirs, (h) => `${h.from}>${h.to}@${h.at}`);
|
|
4091
|
+
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);
|
|
4092
|
+
let at = "draft";
|
|
4093
|
+
for (const h of merged) {
|
|
4094
|
+
if (h.from !== at)
|
|
4095
|
+
throw new Clash("history");
|
|
4096
|
+
at = h.to;
|
|
4097
|
+
}
|
|
4098
|
+
return merged;
|
|
4099
|
+
}
|
|
4100
|
+
function mergeHandoff(a, b) {
|
|
4101
|
+
if (isDeepStrictEqual(a, b))
|
|
4102
|
+
return a;
|
|
4103
|
+
const when = (h) => h.updatedAt ?? h.openedAt;
|
|
4104
|
+
const ra = HANDOFF_RANK[a.state];
|
|
4105
|
+
const rb = HANDOFF_RANK[b.state];
|
|
4106
|
+
const top = rb > ra || rb === ra && when(b) > when(a) ? b : a;
|
|
4107
|
+
return { ...top, reviewers: [.../* @__PURE__ */ new Set([...a.reviewers, ...b.reviewers])] };
|
|
4108
|
+
}
|
|
4109
|
+
function mergeHandoffs(ours, theirs) {
|
|
4110
|
+
const all = union(ours, theirs, (h) => `${h.phase}#${h.pr.number}`, mergeHandoff);
|
|
4111
|
+
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);
|
|
4112
|
+
}
|
|
4113
|
+
function byKey(field, base, ours, theirs) {
|
|
4114
|
+
const out = {};
|
|
4115
|
+
for (const k of [.../* @__PURE__ */ new Set([...Object.keys(ours), ...Object.keys(theirs)])]) {
|
|
4116
|
+
const v = three(`${field}.${k}`, { has: base !== void 0, value: base?.[k] }, ours[k], theirs[k]);
|
|
4117
|
+
if (v !== void 0)
|
|
4118
|
+
out[k] = v;
|
|
4119
|
+
}
|
|
4120
|
+
return out;
|
|
4121
|
+
}
|
|
4122
|
+
function mergeChangeRecords(base, ours, theirs) {
|
|
4123
|
+
const b = base === void 0 ? void 0 : ChangeLifecycle.parse(base);
|
|
4124
|
+
const o = ChangeLifecycle.parse(ours);
|
|
4125
|
+
const t = ChangeLifecycle.parse(theirs);
|
|
4126
|
+
const has = b !== void 0;
|
|
4127
|
+
const at = (k) => ({ has, value: b?.[k] });
|
|
4128
|
+
try {
|
|
4129
|
+
const history = mergeHistory(o.history, t.history);
|
|
4130
|
+
const authors = {};
|
|
4131
|
+
for (const k of [.../* @__PURE__ */ new Set([...Object.keys(o.authors), ...Object.keys(t.authors)])]) {
|
|
4132
|
+
const key = k;
|
|
4133
|
+
authors[key] = [.../* @__PURE__ */ new Set([...o.authors[key] ?? [], ...t.authors[key] ?? []])];
|
|
4134
|
+
}
|
|
4135
|
+
const template = three("template", at("template"), o.template, t.template);
|
|
4136
|
+
const value = {
|
|
4137
|
+
schemaVersion: o.schemaVersion,
|
|
4138
|
+
id: three("id", at("id"), o.id, t.id),
|
|
4139
|
+
title: three("title", at("title"), o.title, t.title),
|
|
4140
|
+
type: three("type", at("type"), o.type, t.type),
|
|
4141
|
+
...template !== void 0 ? { template } : {},
|
|
4142
|
+
state: history.length ? history.at(-1).to : three("state", at("state"), o.state, t.state),
|
|
4143
|
+
owners: byKey("owners", b?.owners, o.owners, t.owners),
|
|
4144
|
+
systems: three("systems", at("systems"), o.systems, t.systems),
|
|
4145
|
+
touches: byKey("touches", b?.touches, o.touches, t.touches),
|
|
4146
|
+
authors,
|
|
4147
|
+
createdAt: three("createdAt", at("createdAt"), o.createdAt, t.createdAt),
|
|
4148
|
+
createdBy: three("createdBy", at("createdBy"), o.createdBy, t.createdBy),
|
|
4149
|
+
history,
|
|
4150
|
+
handoffs: mergeHandoffs(o.handoffs, t.handoffs)
|
|
4151
|
+
};
|
|
4152
|
+
const r = ChangeLifecycle.safeParse(value);
|
|
4153
|
+
if (!r.success)
|
|
4154
|
+
return { ok: false, field: r.error.issues[0]?.path.map(String).join(".") || "record" };
|
|
4155
|
+
return { ok: true, value: r.data };
|
|
4156
|
+
} catch (e) {
|
|
4157
|
+
if (e instanceof Clash)
|
|
4158
|
+
return { ok: false, field: e.field };
|
|
4159
|
+
throw e;
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
function readRecord(text) {
|
|
4163
|
+
try {
|
|
4164
|
+
const r = ChangeLifecycle.safeParse((0, import_yaml11.parse)(text));
|
|
4165
|
+
return r.success ? r.data : void 0;
|
|
4166
|
+
} catch {
|
|
4167
|
+
return void 0;
|
|
4168
|
+
}
|
|
4169
|
+
}
|
|
4170
|
+
function mergeChangeText(baseText, oursText, theirsText) {
|
|
4171
|
+
const ours = readRecord(oursText);
|
|
4172
|
+
const theirs = readRecord(theirsText);
|
|
4173
|
+
const base = baseText.trim() ? readRecord(baseText) : void 0;
|
|
4174
|
+
if (!ours || !theirs || baseText.trim() && !base)
|
|
4175
|
+
return void 0;
|
|
4176
|
+
const r = mergeChangeRecords(base, ours, theirs);
|
|
4177
|
+
return r.ok ? { ok: true, text: serializeChange(r.value) } : r;
|
|
4178
|
+
}
|
|
4179
|
+
|
|
4180
|
+
// packages/changes/dist/owners.js
|
|
4181
|
+
var OWNER_ROLE_KEYS = [
|
|
4182
|
+
"product-owner",
|
|
4183
|
+
"architect",
|
|
4184
|
+
"business-analyst",
|
|
4185
|
+
"team-lead"
|
|
4186
|
+
];
|
|
4187
|
+
var ChangeOwnersError = class extends Error {
|
|
4188
|
+
constructor(message) {
|
|
4189
|
+
super(message);
|
|
4190
|
+
this.name = "ChangeOwnersError";
|
|
4191
|
+
}
|
|
4192
|
+
};
|
|
4193
|
+
var strip4 = (h) => h.trim().replace(/^@/, "");
|
|
4194
|
+
async function setChangeOwners(root, id, owners) {
|
|
4195
|
+
const store = changeStore(root);
|
|
4196
|
+
const c = await store.read(id);
|
|
4197
|
+
const next = { ...c.owners };
|
|
4198
|
+
for (const [key, people] of Object.entries(owners)) {
|
|
4199
|
+
if (!OWNER_ROLE_KEYS.includes(key))
|
|
4200
|
+
throw new ChangeOwnersError(`The change page names owners for ${OWNER_ROLE_KEYS.join(", ")}, not ${key}: edit change.yaml for other roles.`);
|
|
4201
|
+
const list = [...new Set(people.map(strip4).filter(Boolean))];
|
|
4202
|
+
if (list.length)
|
|
4203
|
+
next[key] = list;
|
|
4204
|
+
else
|
|
4205
|
+
delete next[key];
|
|
4206
|
+
}
|
|
4207
|
+
const sorted2 = Object.fromEntries(Object.entries(next).sort(([a], [b]) => a.localeCompare(b)));
|
|
4208
|
+
const r = ChangeLifecycle.safeParse({ ...c, owners: sorted2 });
|
|
4209
|
+
if (!r.success)
|
|
4210
|
+
throw new ChangeOwnersError(`Those owners can't be written: ${r.error.issues.map((i) => i.message).join("; ")}.`);
|
|
4211
|
+
await store.write(r.data);
|
|
4212
|
+
return r.data;
|
|
4213
|
+
}
|
|
4214
|
+
|
|
3377
4215
|
export {
|
|
3378
4216
|
ChangeError,
|
|
3379
4217
|
readTables,
|
|
@@ -3381,6 +4219,8 @@ export {
|
|
|
3381
4219
|
isFilled,
|
|
3382
4220
|
CHANGE_POLICY_FILE,
|
|
3383
4221
|
StageStep,
|
|
4222
|
+
isWritePattern,
|
|
4223
|
+
writeMatches,
|
|
3384
4224
|
SIGNAL_FACTORS,
|
|
3385
4225
|
ChangePolicy,
|
|
3386
4226
|
DEFAULT_CHANGE_POLICY,
|
|
@@ -3405,6 +4245,9 @@ export {
|
|
|
3405
4245
|
readCodeowners,
|
|
3406
4246
|
loadChangeContext,
|
|
3407
4247
|
changeTypeOf,
|
|
4248
|
+
outsideSystemsOf,
|
|
4249
|
+
outsideKindOf,
|
|
4250
|
+
outsideWords,
|
|
3408
4251
|
personalLevels,
|
|
3409
4252
|
wordsOf,
|
|
3410
4253
|
changeFootprint,
|
|
@@ -3435,10 +4278,21 @@ export {
|
|
|
3435
4278
|
workProgress,
|
|
3436
4279
|
privacyImpact,
|
|
3437
4280
|
prApprovers,
|
|
4281
|
+
RELEASE_PR_STATES,
|
|
4282
|
+
RELEASE_PRS_FILE,
|
|
4283
|
+
staleReleasePrs,
|
|
4284
|
+
nothingToRelease,
|
|
4285
|
+
raiseReleasePrs,
|
|
4286
|
+
syncReleasePrs,
|
|
4287
|
+
requireMergedPrs,
|
|
4288
|
+
releasePrReasons,
|
|
3438
4289
|
liveHandoff,
|
|
4290
|
+
isManualPrUrl,
|
|
4291
|
+
manualHandoffWords,
|
|
3439
4292
|
assess,
|
|
3440
4293
|
canAdvance,
|
|
3441
4294
|
changeReport,
|
|
4295
|
+
windowsUnsafeName,
|
|
3442
4296
|
slugify,
|
|
3443
4297
|
newChange,
|
|
3444
4298
|
gitHandoffPublisher,
|
|
@@ -3450,6 +4304,7 @@ export {
|
|
|
3450
4304
|
captureHandoff,
|
|
3451
4305
|
readDraftPairs,
|
|
3452
4306
|
completeCaptures,
|
|
4307
|
+
namesChange,
|
|
3453
4308
|
handoff,
|
|
3454
4309
|
syncHandoff,
|
|
3455
4310
|
returnToPo,
|
|
@@ -3475,5 +4330,10 @@ export {
|
|
|
3475
4330
|
openspecStores,
|
|
3476
4331
|
storeSpecLookup,
|
|
3477
4332
|
metarepoOrigin,
|
|
3478
|
-
referenceMetarepoIn
|
|
4333
|
+
referenceMetarepoIn,
|
|
4334
|
+
mergeChangeRecords,
|
|
4335
|
+
mergeChangeText,
|
|
4336
|
+
OWNER_ROLE_KEYS,
|
|
4337
|
+
ChangeOwnersError,
|
|
4338
|
+
setChangeOwners
|
|
3479
4339
|
};
|