@maycombhq/atticus 2.0.0-beta.18 → 2.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +8 -4
- package/dist/{chunk-MUSJJ6CQ.js → chunk-265LWMOA.js} +16 -16
- package/dist/chunk-26WBY34Y.js +37 -0
- package/dist/{chunk-6H5JG5GL.js → chunk-26Z7OGQW.js} +63 -30
- package/dist/{chunk-4REDBQOZ.js → chunk-2D7PWSOK.js} +20 -20
- package/dist/{chunk-YPH6ZJ6R.js → chunk-2HH3UCZM.js} +4 -4
- package/dist/{chunk-RTUUX7PX.js → chunk-2HTEMYTB.js} +12 -26
- package/dist/{chunk-EHQUY26D.js → chunk-2UCRFXFZ.js} +11 -11
- package/dist/{chunk-ILDGIXNH.js → chunk-2V2ZPQQU.js} +81 -13
- package/dist/{chunk-YSEZQ6D3.js → chunk-36RY7ZXM.js} +107 -64
- package/dist/{chunk-QXFDM2FZ.js → chunk-37WE3LS2.js} +3 -3
- package/dist/{chunk-BAXM3JFQ.js → chunk-3AQ2R5NL.js} +1212 -754
- package/dist/chunk-3G6S7LAA.js +43 -0
- package/dist/{chunk-7TOAS7KI.js → chunk-3J5I7ZOU.js} +4 -4
- package/dist/{chunk-TAKAEJRD.js → chunk-3LZ6P5EP.js} +1 -1
- package/dist/chunk-3SSTNB4E.js +191 -0
- package/dist/chunk-3TD6S4TC.js +49 -0
- package/dist/{chunk-UUL6ZGMU.js → chunk-3UTYPHN3.js} +1 -1
- package/dist/{chunk-6AP2ZUUM.js → chunk-3WON4G3M.js} +2 -2
- package/dist/{chunk-AXY6LBW4.js → chunk-42EPP7OB.js} +12 -12
- package/dist/{chunk-QKMDT7VU.js → chunk-455SYIIE.js} +2 -2
- package/dist/{chunk-MFUFB3LN.js → chunk-4B6BT75O.js} +110 -20
- package/dist/{chunk-75MYLZSB.js → chunk-4CLK2RJO.js} +7 -7
- package/dist/chunk-4CYTJ5TZ.js +38 -0
- package/dist/{chunk-IH7RANGB.js → chunk-4D62IZII.js} +5 -5
- package/dist/{chunk-2QMZOCWN.js → chunk-4FOAYLI4.js} +14 -7
- package/dist/{chunk-V3UEPUHT.js → chunk-4KNOJC6J.js} +294 -215
- package/dist/{chunk-O6ILVBYB.js → chunk-4PYC2UGC.js} +1 -1
- package/dist/{chunk-EZBAS5BL.js → chunk-4SLBCEPD.js} +82 -50
- package/dist/{chunk-VIRTPODJ.js → chunk-4TKVU2I2.js} +6 -14
- package/dist/{chunk-IVCDU443.js → chunk-4WXRPQDE.js} +281 -51
- package/dist/{chunk-RMEEHH24.js → chunk-5CMPVC3N.js} +24 -24
- package/dist/chunk-5G7ILBUG.js +66 -0
- package/dist/{chunk-Z75XDWQR.js → chunk-5PV7S2ZP.js} +3 -3
- package/dist/{chunk-WWPC2AZ2.js → chunk-5QUOPNLN.js} +4 -4
- package/dist/{chunk-7OVH2L7L.js → chunk-5TDRBOAV.js} +5 -5
- package/dist/{chunk-YXUIUOQZ.js → chunk-5WERBKLE.js} +199 -63
- package/dist/{chunk-6WNNXEG6.js → chunk-62DLJSLV.js} +8 -8
- package/dist/{chunk-KCQS52TH.js → chunk-64AADMTW.js} +99 -25
- package/dist/{chunk-KLRUXRPN.js → chunk-67BP7HXL.js} +17 -17
- package/dist/chunk-6DQYZU2W.js +379 -0
- package/dist/{chunk-RONDYDMQ.js → chunk-6EXDI5GI.js} +51 -34
- package/dist/chunk-6FNAKCET.js +38 -0
- package/dist/{chunk-6SNEA73E.js → chunk-75ASLHBT.js} +21 -15
- package/dist/{chunk-EFT4KM6K.js → chunk-75ZOZFGH.js} +18 -18
- package/dist/{chunk-B3KW7EQW.js → chunk-77O633L5.js} +12 -12
- package/dist/{chunk-TLDL2ZZI.js → chunk-7AB6OWTV.js} +3 -3
- package/dist/{chunk-R5YXY6NT.js → chunk-7CZHUZBL.js} +25 -25
- package/dist/{chunk-F4D4G2A6.js → chunk-7FHNFCHK.js} +306 -125
- package/dist/{chunk-CC5RNYZF.js → chunk-7FUIVIBG.js} +19 -9
- package/dist/{chunk-UBTOSSNK.js → chunk-7QPCGTPS.js} +21 -21
- package/dist/{chunk-CDLTRJ4N.js → chunk-A2G72UI4.js} +7 -7
- package/dist/{chunk-YGSOQ5LX.js → chunk-AE4P57KK.js} +23 -17
- package/dist/{chunk-PGQL5YGH.js → chunk-AHW3RY3X.js} +7 -7
- package/dist/chunk-AM6FP6JW.js +43 -0
- package/dist/{chunk-DYNC6BAQ.js → chunk-AOCWFA7T.js} +93 -10
- package/dist/{chunk-CYX36GKZ.js → chunk-API5TOUB.js} +1 -1
- package/dist/{chunk-BJ5T3KDF.js → chunk-AZDITBCT.js} +3 -3
- package/dist/{chunk-U6GZSUMC.js → chunk-BA2HUMDN.js} +25 -3
- package/dist/chunk-BA3U7ALS.js +99 -0
- package/dist/{chunk-63565QTF.js → chunk-BC6KKZ67.js} +5 -5
- package/dist/{chunk-XO5RQKNM.js → chunk-BEPCKDCP.js} +1843 -577
- package/dist/{chunk-KUH62AQ3.js → chunk-BPPU3PW7.js} +4 -4
- package/dist/{chunk-52ULYRV4.js → chunk-BVWWPBKD.js} +18 -18
- package/dist/{chunk-R2FGEBIU.js → chunk-BXZ4PKIT.js} +13 -11
- package/dist/{chunk-4BU26GFB.js → chunk-C7CEK3KW.js} +4 -4
- package/dist/chunk-CFH3QH26.js +29 -0
- package/dist/chunk-CG6CULP6.js +170 -0
- package/dist/chunk-CLD4RVTA.js +45 -0
- package/dist/{chunk-BU543ZEC.js → chunk-CNPY4NTZ.js} +13 -13
- package/dist/{chunk-PLBIWEPV.js → chunk-CNU5Q2D5.js} +32 -31
- package/dist/{chunk-JTYYEYRR.js → chunk-COYN3UAN.js} +124 -21
- package/dist/{chunk-TNCROWPQ.js → chunk-CPK7QSBG.js} +10 -10
- package/dist/{chunk-43E6XX67.js → chunk-D2WSW4GU.js} +1 -1
- package/dist/{chunk-SHEUEHKC.js → chunk-D57MFJPP.js} +3 -3
- package/dist/chunk-D5KV5PSK.js +45 -0
- package/dist/chunk-D5MASS4I.js +48 -0
- package/dist/{chunk-TQYBXDVH.js → chunk-DCWN3HFV.js} +1 -1
- package/dist/{chunk-FF4IGX4Z.js → chunk-DDJPM5YX.js} +2 -2
- package/dist/chunk-DN4A2Q2O.js +57 -0
- package/dist/{chunk-4ZCFCIEL.js → chunk-DUPZPDCJ.js} +52 -16
- package/dist/{chunk-CUJ5TIEB.js → chunk-E2VXBSIR.js} +4 -4
- package/dist/{chunk-BX47X5DJ.js → chunk-E57AOFYN.js} +13 -13
- package/dist/{chunk-SSTI7GAV.js → chunk-ECLXZWAG.js} +12 -12
- package/dist/{chunk-KYK3TA4Q.js → chunk-EDZ45WIR.js} +14 -14
- package/dist/chunk-EFCQSMXB.js +37 -0
- package/dist/{chunk-YC2K3FJG.js → chunk-EZIN5E5B.js} +8 -8
- package/dist/{chunk-MIZNACDI.js → chunk-F6H2RJ3N.js} +1 -1
- package/dist/{chunk-EMZVHDJP.js → chunk-FAJDKNSD.js} +7 -3
- package/dist/chunk-FGAGWYZB.js +47 -0
- package/dist/{chunk-KBGQ3IAM.js → chunk-FLVZFTLQ.js} +18 -18
- package/dist/{chunk-2NNXLH53.js → chunk-FQ6WH6PB.js} +22 -7
- package/dist/{chunk-QWSLMVHI.js → chunk-FRHD22WM.js} +61 -23
- package/dist/{chunk-5MEN4SM5.js → chunk-FVMPFCDS.js} +4 -4
- package/dist/{chunk-KLRZKW7Y.js → chunk-FY3QIRTL.js} +3 -3
- package/dist/{chunk-B3SWPNJF.js → chunk-FYBF5MB2.js} +13 -11
- package/dist/{chunk-CVZCW3NI.js → chunk-G5YJBNDZ.js} +14 -5
- package/dist/{chunk-U6EPS5PF.js → chunk-G6IQYM25.js} +5 -5
- package/dist/chunk-G6X4YL4D.js +161 -0
- package/dist/{chunk-DOSX3MRW.js → chunk-GC2VK3JT.js} +25 -25
- package/dist/{chunk-EEWBTFRP.js → chunk-GCS3FZOB.js} +27 -3
- package/dist/{chunk-6H6OPNEP.js → chunk-GDZB57IU.js} +5 -5
- package/dist/chunk-GIFMBCTW.js +221 -0
- package/dist/{chunk-X5WYPIH3.js → chunk-GK4PYVEC.js} +18 -8
- package/dist/{chunk-VBSJ5NRB.js → chunk-GNWAOVFQ.js} +5 -5
- package/dist/{chunk-TXFDQRBL.js → chunk-GS76RXQE.js} +11 -11
- package/dist/{chunk-U3YC4RGC.js → chunk-GSGIZ2OO.js} +23 -23
- package/dist/chunk-GUYL7RMK.js +60 -0
- package/dist/{chunk-AZYBHPV4.js → chunk-GX74YWR7.js} +14 -13
- package/dist/{chunk-YY7VP7KW.js → chunk-HE3HRDNZ.js} +2 -2
- package/dist/{chunk-Z7HYYDZM.js → chunk-HGHRSF3E.js} +14 -13
- package/dist/{chunk-Q2X4IREC.js → chunk-HK3VOHIE.js} +106 -36
- package/dist/{chunk-P5JYMO7Y.js → chunk-HS4BGB4G.js} +22 -22
- package/dist/{chunk-IOW6BF3U.js → chunk-HSZMPH5T.js} +798 -141
- package/dist/{chunk-L3T33FA7.js → chunk-HWD4JPU7.js} +4 -2
- package/dist/{chunk-7NYCH5HY.js → chunk-HXXP2NTE.js} +6 -6
- package/dist/{chunk-7S7OFVIU.js → chunk-I2A3Z5AK.js} +4 -4
- package/dist/{chunk-WQSDKX7M.js → chunk-ICHDSWEY.js} +7 -7
- package/dist/chunk-IFFRGC3N.js +31 -0
- package/dist/{chunk-HMZQAIR5.js → chunk-IFHDV6KQ.js} +24 -17
- package/dist/chunk-J2O4LFVI.js +80 -0
- package/dist/chunk-J5AAZ6OJ.js +161 -0
- package/dist/{chunk-CVRWAG76.js → chunk-JAABT3DA.js} +3 -3
- package/dist/chunk-JITJP64K.js +34 -0
- package/dist/chunk-JKIGL3XQ.js +162 -0
- package/dist/chunk-JRVNXLZP.js +615 -0
- package/dist/chunk-JRXHON4E.js +41 -0
- package/dist/{chunk-XFCK5B27.js → chunk-JS6WMC3J.js} +8 -8
- package/dist/chunk-JU4QNJSR.js +62 -0
- package/dist/{chunk-IPSFMTK2.js → chunk-JYMIB5XY.js} +152 -42
- package/dist/{chunk-L336AJCZ.js → chunk-JYTIHTIK.js} +8 -6
- package/dist/{chunk-VEJYZEPC.js → chunk-K332XW7K.js} +135 -16
- package/dist/{chunk-KCF7TFOE.js → chunk-K6JB6XTX.js} +11 -11
- package/dist/{chunk-LFIIX4OV.js → chunk-KDAHXQIE.js} +46 -34
- package/dist/chunk-KYZMRSPS.js +93 -0
- package/dist/chunk-L4CESDPB.js +22 -0
- package/dist/{chunk-3LR4522O.js → chunk-LEBR5NHW.js} +14 -14
- package/dist/{chunk-US3QZU23.js → chunk-LIJJCBQI.js} +67 -7
- package/dist/{chunk-XLHL6WTI.js → chunk-LIRVU3HW.js} +16 -16
- package/dist/{chunk-BAT4CBB7.js → chunk-LN77DXBM.js} +19 -10
- package/dist/{chunk-E5FM44EJ.js → chunk-LNWEWYCM.js} +2474 -315
- package/dist/{chunk-TX4OGZKU.js → chunk-LO3BLMVG.js} +3 -3
- package/dist/{chunk-RUOGHYSK.js → chunk-LPV6T362.js} +5 -5
- package/dist/{chunk-2T5CQH4R.js → chunk-LQH4P45K.js} +1 -1
- package/dist/chunk-LU4MQVAL.js +41 -0
- package/dist/{chunk-HW4RRIJA.js → chunk-LWDNKE4X.js} +12 -10
- package/dist/chunk-M4H2UG4R.js +218 -0
- package/dist/{chunk-CANMOOE6.js → chunk-M6D2U3YM.js} +3 -3
- package/dist/{chunk-AB5E5HCN.js → chunk-MD7KUEJ3.js} +14 -272
- package/dist/{chunk-ENTAAEDK.js → chunk-MMZUN7ZO.js} +20 -20
- package/dist/{chunk-L4MHJG3I.js → chunk-MZGRDONP.js} +46 -36
- package/dist/{chunk-IYI2S6BB.js → chunk-N3RUGTGV.js} +18 -18
- package/dist/{chunk-WZRCGGXG.js → chunk-NCB6656Z.js} +7 -7
- package/dist/{chunk-SBE773JU.js → chunk-NRQ65LTQ.js} +5 -5
- package/dist/{chunk-NRPMPZWJ.js → chunk-NTAVOWYD.js} +117 -18
- package/dist/{chunk-UJBZS5I7.js → chunk-NYOABJNK.js} +36 -27
- package/dist/{chunk-LXAPJ3BL.js → chunk-O4IKAIZ2.js} +24 -20
- package/dist/{chunk-MMB2OHUY.js → chunk-ODMBPDDV.js} +48 -13
- package/dist/chunk-OKUB6RJA.js +533 -0
- package/dist/{chunk-MKFYQQ5Q.js → chunk-OMPENNWY.js} +4 -1
- package/dist/chunk-OPQZISG3.js +25 -0
- package/dist/{chunk-ROJMXG4E.js → chunk-OV57AHCG.js} +24 -24
- package/dist/{chunk-7KGXDIWV.js → chunk-OZJ3JLYI.js} +9 -8
- package/dist/{chunk-G5BYKJIM.js → chunk-P2VBXX7N.js} +11 -10
- package/dist/{chunk-YK6CSODO.js → chunk-PAINMSID.js} +4353 -1106
- package/dist/chunk-PKJAV25S.js +43 -0
- package/dist/{chunk-R5MNPIMD.js → chunk-PNQLCRSS.js} +4 -4
- package/dist/{chunk-3SMMFQED.js → chunk-PQH56YF6.js} +37 -5
- package/dist/chunk-PRTBPXYA.js +50 -0
- package/dist/{chunk-FF4ZMLSL.js → chunk-PRW3HXM3.js} +2 -2
- package/dist/chunk-PRZ7KMWL.js +42 -0
- package/dist/{chunk-IFV6MKYV.js → chunk-PTQ3ZALZ.js} +42 -4
- package/dist/{chunk-ELQSLAP6.js → chunk-PUEWDYGP.js} +2 -2
- package/dist/{chunk-F2ZCZYIK.js → chunk-Q26LDIUV.js} +12 -11
- package/dist/chunk-Q46QDDMF.js +277 -0
- package/dist/chunk-QBNWF5ZU.js +19 -0
- package/dist/{chunk-ANETFNTV.js → chunk-QE527XRY.js} +24 -21
- package/dist/{chunk-QNMLLYTN.js → chunk-QHJJMZLX.js} +397 -32
- package/dist/chunk-QJCJOMW3.js +425 -0
- package/dist/{chunk-3HOONZUE.js → chunk-QKMRFNMA.js} +15 -15
- package/dist/{chunk-7S6VJ643.js → chunk-QOOEYDNJ.js} +101 -13
- package/dist/{chunk-4YNKN4DF.js → chunk-QSIDVFB7.js} +20 -18
- package/dist/{chunk-2IT7SIJY.js → chunk-RA33EYS4.js} +19 -18
- package/dist/{chunk-P7NV3FEJ.js → chunk-RALY3E5U.js} +4 -4
- package/dist/{chunk-6NESNLVU.js → chunk-S2RY3BOD.js} +35 -21
- package/dist/chunk-SB5GRUFM.js +54 -0
- package/dist/{chunk-EUQHM2CH.js → chunk-SH27FYPF.js} +18 -18
- package/dist/chunk-T74WQQGS.js +40 -0
- package/dist/{chunk-LGDW4G73.js → chunk-TGVVUKTG.js} +2 -2
- package/dist/chunk-THN26IEF.js +48 -0
- package/dist/{chunk-ZV2VCBMQ.js → chunk-TJH2EUAZ.js} +18 -12
- package/dist/{chunk-NGT5LLSE.js → chunk-TQ4TG57V.js} +6 -6
- package/dist/{chunk-WEDXFJPG.js → chunk-TQTRWENN.js} +12 -12
- package/dist/{chunk-ZRWS54RI.js → chunk-TSM6T6GQ.js} +23 -23
- package/dist/{chunk-KET7KOVE.js → chunk-TW3KOSQK.js} +15 -7
- package/dist/{chunk-RBXDHCN7.js → chunk-TWQRPM76.js} +2 -2
- package/dist/{chunk-IO6DQCZX.js → chunk-TYD6I4CZ.js} +4 -4
- package/dist/{chunk-5NLJH6R4.js → chunk-U3NBK4EU.js} +56 -17
- package/dist/{chunk-U542BJME.js → chunk-UDXWIPCB.js} +7 -7
- package/dist/{chunk-OSB6AKF2.js → chunk-UEVZZZU2.js} +4 -4
- package/dist/{chunk-5ZV7BK22.js → chunk-UF4UHCIG.js} +2 -2
- package/dist/{chunk-PHJOOL6U.js → chunk-UFE36WJH.js} +4 -4
- package/dist/{chunk-IHRP4OYV.js → chunk-URIKUID7.js} +12 -12
- package/dist/chunk-UYLBRRFK.js +1106 -0
- package/dist/chunk-UZACEG2C.js +515 -0
- package/dist/{chunk-LR4XBH4L.js → chunk-VAMHOHUU.js} +12 -12
- package/dist/{chunk-S5PSGAQ2.js → chunk-VUVGX5L2.js} +1 -1
- package/dist/{chunk-2TG2QGKW.js → chunk-VYOCPTUB.js} +18 -16
- package/dist/{chunk-VTEDQ6GY.js → chunk-WAZEXEJH.js} +175 -3
- package/dist/{chunk-UTSFKZ72.js → chunk-WCEF7H6L.js} +3 -3
- package/dist/{chunk-DXPUYG5L.js → chunk-WEOMJHPV.js} +12 -12
- package/dist/{chunk-NFKBU2UH.js → chunk-WFU77T3A.js} +5 -5
- package/dist/chunk-WIWTJMMR.js +292 -0
- package/dist/{chunk-MRFSUAJH.js → chunk-WNTIOBOG.js} +18 -18
- package/dist/{chunk-UROTYIGJ.js → chunk-X2CUXTWU.js} +6 -6
- package/dist/chunk-X7EMBM5X.js +34 -0
- package/dist/{chunk-OO5TKEAG.js → chunk-XEQCQO66.js} +8 -8
- package/dist/{chunk-NBQO75XT.js → chunk-XG4WJHLQ.js} +17 -1
- package/dist/{chunk-ZQC7MNQB.js → chunk-XOL3GFW2.js} +101 -30
- package/dist/{chunk-GXDATVSY.js → chunk-XQ3YZ4QW.js} +22 -22
- package/dist/{chunk-GTW54D3D.js → chunk-Y6K3CJBU.js} +726 -88
- package/dist/{chunk-CQCAJI6G.js → chunk-YAH5IQAL.js} +5 -5
- package/dist/{chunk-U6TD7UOH.js → chunk-YBOVNMHC.js} +51 -14
- package/dist/{chunk-OJICVOZH.js → chunk-YF5DZCPN.js} +79 -39
- package/dist/chunk-YIGBJNH4.js +37 -0
- package/dist/{chunk-7C7OZARH.js → chunk-YJ3TBOTK.js} +25 -25
- package/dist/{chunk-HXFSM4LG.js → chunk-YKTE2RU3.js} +2 -2
- package/dist/{chunk-7IM4BVVH.js → chunk-YO2PKTXT.js} +268 -36
- package/dist/{chunk-Z3UU7XV6.js → chunk-YTTNTQOX.js} +15 -15
- package/dist/{chunk-2ZICHO3V.js → chunk-YWAKDNMH.js} +183 -33
- package/dist/{chunk-OXOSJXOI.js → chunk-YZDZOEV4.js} +4 -4
- package/dist/{chunk-MRCETHW5.js → chunk-Z3QFH7YG.js} +4 -4
- package/dist/{chunk-5KZTRD4V.js → chunk-Z6VLC5X4.js} +17 -17
- package/dist/{chunk-EDURU5DF.js → chunk-ZFQMMU5Y.js} +2 -2
- package/dist/{chunk-3GLDB4IT.js → chunk-ZKYRDV7A.js} +12 -12
- package/dist/{chunk-GBGJ6WSO.js → chunk-ZLS6PU2O.js} +3 -3
- package/dist/{chunk-JZOOX3PQ.js → chunk-ZOSD4FXV.js} +3 -3
- package/dist/chunk-ZPDDEVQX.js +47 -0
- package/dist/{chunk-2TTSX7NB.js → chunk-ZQLRCA2V.js} +7 -7
- package/dist/{chunk-ESP3RUUR.js → chunk-ZX7235KR.js} +128 -54
- package/dist/{chunk-MH4XG5XS.js → chunk-ZZFHLRQW.js} +18 -16
- package/dist/files.json +559 -439
- package/dist/packages/sdk/npm/index.js +4679 -2672
- package/dist/packages/ui/dist/assets/{elk-api-BuFBvNyV.js → elk-api-CowTnhEJ.js} +2 -2
- package/dist/packages/ui/dist/assets/index-Dw20IRhQ.css +1 -0
- package/dist/packages/ui/dist/assets/index-z2wv0d5d.js +47 -0
- package/dist/packages/ui/dist/index.html +2 -2
- package/package.json +3 -3
- package/dist/chunk-25TJC2CL.js +0 -48
- package/dist/chunk-3Q5RHGCA.js +0 -47
- package/dist/chunk-4W7FOGAB.js +0 -41
- package/dist/chunk-6AF3CVJT.js +0 -48
- package/dist/chunk-A3KEXFQS.js +0 -34
- package/dist/chunk-BAUINGGJ.js +0 -41
- package/dist/chunk-BOFUCRJX.js +0 -47
- package/dist/chunk-GGKGILIP.js +0 -54
- package/dist/chunk-IKN6C67W.js +0 -53
- package/dist/chunk-K7MYVIFK.js +0 -54
- package/dist/chunk-KHA5LVPE.js +0 -34
- package/dist/chunk-LSXGM6MB.js +0 -37
- package/dist/chunk-M54TDRM3.js +0 -29
- package/dist/chunk-M5GF4NAB.js +0 -45
- package/dist/chunk-UTZGBCXE.js +0 -38
- package/dist/chunk-UVTSMBM4.js +0 -43
- package/dist/chunk-XXE3S3OG.js +0 -44
- package/dist/chunk-YJRQCUVJ.js +0 -43
- package/dist/chunk-ZFTK4BDN.js +0 -41
- package/dist/chunk-ZMARAW37.js +0 -38
- package/dist/packages/ui/dist/assets/index-C5QnK82A.css +0 -1
- package/dist/packages/ui/dist/assets/index-CQ0a1cBg.js +0 -29
|
@@ -10,13 +10,25 @@ import {
|
|
|
10
10
|
FlowDelta,
|
|
11
11
|
STAGE_PHASE,
|
|
12
12
|
STAGE_SCHEMA_VERSION,
|
|
13
|
+
Stage,
|
|
13
14
|
StageLoop,
|
|
14
15
|
fieldNames,
|
|
15
16
|
parseAt,
|
|
16
17
|
parseNewFile,
|
|
17
18
|
rerunOf,
|
|
18
19
|
usesOf
|
|
19
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-HWD4JPU7.js";
|
|
21
|
+
import {
|
|
22
|
+
GAP_STEP,
|
|
23
|
+
candidateRef,
|
|
24
|
+
contextToolBox,
|
|
25
|
+
defaultCodeDir,
|
|
26
|
+
gapNameOf,
|
|
27
|
+
gapQuestion,
|
|
28
|
+
namesInCode,
|
|
29
|
+
nodesById,
|
|
30
|
+
openIndex
|
|
31
|
+
} from "./chunk-5WERBKLE.js";
|
|
20
32
|
import {
|
|
21
33
|
CriticError,
|
|
22
34
|
MERGED_FLOWS,
|
|
@@ -42,12 +54,13 @@ import {
|
|
|
42
54
|
readRoundMeta,
|
|
43
55
|
readSnowDrafts,
|
|
44
56
|
readTasks,
|
|
57
|
+
roundsOf,
|
|
45
58
|
runCritic,
|
|
46
59
|
sectionLines,
|
|
47
60
|
triggerReason,
|
|
48
61
|
waitingLenses,
|
|
49
62
|
writeQuarantineItems
|
|
50
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-K332XW7K.js";
|
|
51
64
|
import {
|
|
52
65
|
changeFolder,
|
|
53
66
|
changeFootprint,
|
|
@@ -63,20 +76,20 @@ import {
|
|
|
63
76
|
scoreRisk,
|
|
64
77
|
stageCommand,
|
|
65
78
|
testPlanFor
|
|
66
|
-
} from "./chunk-
|
|
79
|
+
} from "./chunk-HSZMPH5T.js";
|
|
67
80
|
import {
|
|
68
81
|
ARTIFACT_STAGE,
|
|
69
82
|
artifactsOfStage,
|
|
70
83
|
createOpenSpec
|
|
71
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-D2WSW4GU.js";
|
|
72
85
|
import {
|
|
73
86
|
AdapterError,
|
|
74
87
|
createAdapters,
|
|
75
88
|
fileStepStore
|
|
76
|
-
} from "./chunk-
|
|
89
|
+
} from "./chunk-4KNOJC6J.js";
|
|
77
90
|
import {
|
|
78
91
|
readCatalog
|
|
79
|
-
} from "./chunk-
|
|
92
|
+
} from "./chunk-4SLBCEPD.js";
|
|
80
93
|
import {
|
|
81
94
|
CONFLICT_STEP,
|
|
82
95
|
checkQuote,
|
|
@@ -95,28 +108,17 @@ import {
|
|
|
95
108
|
snapshotPages,
|
|
96
109
|
syncSources,
|
|
97
110
|
workingDays
|
|
98
|
-
} from "./chunk-
|
|
111
|
+
} from "./chunk-64AADMTW.js";
|
|
99
112
|
import {
|
|
100
113
|
changeInputSources,
|
|
101
114
|
citedParts,
|
|
102
115
|
heldOutInputs,
|
|
103
116
|
readServiceRequestExports,
|
|
104
117
|
readWorkItemExports
|
|
105
|
-
} from "./chunk-
|
|
118
|
+
} from "./chunk-CPK7QSBG.js";
|
|
106
119
|
import {
|
|
107
120
|
readDocument
|
|
108
|
-
} from "./chunk-
|
|
109
|
-
import {
|
|
110
|
-
GAP_STEP,
|
|
111
|
-
candidateRef,
|
|
112
|
-
contextToolBox,
|
|
113
|
-
defaultCodeDir,
|
|
114
|
-
gapNameOf,
|
|
115
|
-
gapQuestion,
|
|
116
|
-
namesInCode,
|
|
117
|
-
nodesById,
|
|
118
|
-
openIndex
|
|
119
|
-
} from "./chunk-YXUIUOQZ.js";
|
|
121
|
+
} from "./chunk-YO2PKTXT.js";
|
|
120
122
|
import {
|
|
121
123
|
APPLIES_SPEC,
|
|
122
124
|
ASKS_CHECK_SPEC,
|
|
@@ -137,9 +139,10 @@ import {
|
|
|
137
139
|
toRunSummary,
|
|
138
140
|
verdictOf,
|
|
139
141
|
wrapData
|
|
140
|
-
} from "./chunk-
|
|
142
|
+
} from "./chunk-QHJJMZLX.js";
|
|
141
143
|
import {
|
|
142
144
|
AgentDefinition,
|
|
145
|
+
JOINED_BY_AI,
|
|
143
146
|
JOIN_GUESS_FLAGS,
|
|
144
147
|
KNOWLEDGE_KINDS,
|
|
145
148
|
Procedure,
|
|
@@ -149,18 +152,22 @@ import {
|
|
|
149
152
|
RecordId,
|
|
150
153
|
RuntimeKind,
|
|
151
154
|
ServiceRequestTemplate,
|
|
155
|
+
answerByWords,
|
|
152
156
|
answerText,
|
|
153
157
|
answerWords,
|
|
154
158
|
authorityRank,
|
|
155
159
|
catalogDirOf,
|
|
156
160
|
changesDirOf,
|
|
157
161
|
changesRelOf,
|
|
162
|
+
componentTypeRulesText,
|
|
163
|
+
effectiveComponentTypes,
|
|
158
164
|
external_exports,
|
|
159
165
|
factsDirOf,
|
|
160
166
|
fileJudgments,
|
|
161
167
|
gateRoundLimit,
|
|
162
168
|
identifierWords,
|
|
163
169
|
inputsDirOf,
|
|
170
|
+
isAiBy,
|
|
164
171
|
isSecretPath,
|
|
165
172
|
judgmentKey,
|
|
166
173
|
knowledgeKindOf,
|
|
@@ -170,6 +177,7 @@ import {
|
|
|
170
177
|
normalizePath,
|
|
171
178
|
openspecDirOf,
|
|
172
179
|
orgPackDirOf,
|
|
180
|
+
orgPackRelOf,
|
|
173
181
|
overrideJudgment,
|
|
174
182
|
ownerOfQuestion,
|
|
175
183
|
proposalsDirOf,
|
|
@@ -194,10 +202,10 @@ import {
|
|
|
194
202
|
taskBranch,
|
|
195
203
|
toCanonicalYaml,
|
|
196
204
|
writeQuestion
|
|
197
|
-
} from "./chunk-
|
|
205
|
+
} from "./chunk-PAINMSID.js";
|
|
198
206
|
import {
|
|
199
207
|
writeFileAtomic
|
|
200
|
-
} from "./chunk-
|
|
208
|
+
} from "./chunk-ODMBPDDV.js";
|
|
201
209
|
import {
|
|
202
210
|
__toESM
|
|
203
211
|
} from "./chunk-TV45EUGM.js";
|
|
@@ -236,10 +244,10 @@ function stem(word) {
|
|
|
236
244
|
w = w.slice(0, -1);
|
|
237
245
|
return w;
|
|
238
246
|
}
|
|
239
|
-
function contentWords(
|
|
247
|
+
function contentWords(text4) {
|
|
240
248
|
const out = [];
|
|
241
249
|
const seen = /* @__PURE__ */ new Set();
|
|
242
|
-
for (const raw of
|
|
250
|
+
for (const raw of text4.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9']+/)) {
|
|
243
251
|
const w = raw.replace(/^'+|'+$/g, "");
|
|
244
252
|
if (!w || STOP.has(w) || /^\d+$/.test(w))
|
|
245
253
|
continue;
|
|
@@ -298,12 +306,12 @@ function readRequirements(files) {
|
|
|
298
306
|
then
|
|
299
307
|
});
|
|
300
308
|
}
|
|
301
|
-
const
|
|
309
|
+
const text4 = doc.lines.slice(req.line - 1, req.end).map((l) => strip(l.replace(/^#+\s*/, ""))).filter(Boolean).join("\n");
|
|
302
310
|
out.push({
|
|
303
311
|
file: doc.path,
|
|
304
312
|
name: req.heading.replace(/^requirement:\s*/i, "").trim(),
|
|
305
313
|
line: req.line,
|
|
306
|
-
text:
|
|
314
|
+
text: text4,
|
|
307
315
|
scenarios
|
|
308
316
|
});
|
|
309
317
|
}
|
|
@@ -363,7 +371,7 @@ async function loadPlanContext(root, changeId, o = {}) {
|
|
|
363
371
|
return systemOf2(t.dataStore);
|
|
364
372
|
return void 0;
|
|
365
373
|
};
|
|
366
|
-
const
|
|
374
|
+
const repoOf2 = (component) => get("Component", component)?.repo ?? manifestRepos.find((r) => r.component === component)?.id;
|
|
367
375
|
const runtime = lifecycle.orgPack.runtime;
|
|
368
376
|
const intro = docOf(files, "proposal.md");
|
|
369
377
|
const questions = [];
|
|
@@ -396,7 +404,7 @@ async function loadPlanContext(root, changeId, o = {}) {
|
|
|
396
404
|
boundaryOf: (s) => get("System", s)?.boundary ?? profileSystems.get(s)?.boundary,
|
|
397
405
|
systemName: (s) => get("System", s)?.name ?? profileSystems.get(s)?.name ?? s,
|
|
398
406
|
isKnown: (id) => get("Component", id) !== void 0 || get("System", id) !== void 0 || profileSystems.has(id) || endpointOwners.has(id),
|
|
399
|
-
repoOf,
|
|
407
|
+
repoOf: repoOf2,
|
|
400
408
|
componentOfRepo: (repo) => records.find((r) => r.kind === "Component" && r.repo === repo)?.id ?? manifestRepos.find((r) => r.id === repo)?.component,
|
|
401
409
|
knownRepos: /* @__PURE__ */ new Set([
|
|
402
410
|
...manifestRepos.map((r) => r.id),
|
|
@@ -440,9 +448,9 @@ function requirementIsAboutHop(ctx, r, h) {
|
|
|
440
448
|
}
|
|
441
449
|
function requirementMentionsData(r, d) {
|
|
442
450
|
const flat = (s) => ` ${words(s).replace(/[^a-z0-9]+/g, " ").trim()} `;
|
|
443
|
-
const
|
|
451
|
+
const text4 = flat(r.text);
|
|
444
452
|
const names = d.newTable ? [d.table] : d.newColumns.map((c) => c.name);
|
|
445
|
-
return names.some((n) =>
|
|
453
|
+
return names.some((n) => text4.includes(flat(n)));
|
|
446
454
|
}
|
|
447
455
|
var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
448
456
|
function hopKey(ctx, h) {
|
|
@@ -451,10 +459,10 @@ function hopKey(ctx, h) {
|
|
|
451
459
|
const last = seg ? slug(seg) : slug(h.operation);
|
|
452
460
|
return last.startsWith(slug(sys)) ? last : `${slug(sys)}-${last}`;
|
|
453
461
|
}
|
|
454
|
-
function traceToProposal(ctx,
|
|
462
|
+
function traceToProposal(ctx, text4) {
|
|
455
463
|
let best;
|
|
456
464
|
for (const b of ctx.changes) {
|
|
457
|
-
const n = overlap(
|
|
465
|
+
const n = overlap(text4, b.text);
|
|
458
466
|
if (n >= 2 && (!best || n > best.n))
|
|
459
467
|
best = { ...b, n };
|
|
460
468
|
}
|
|
@@ -518,7 +526,7 @@ function buildSkeleton(ctx) {
|
|
|
518
526
|
material: []
|
|
519
527
|
});
|
|
520
528
|
const known = (h) => ctx.isKnown(h.callee) && ctx.isKnown(h.caller);
|
|
521
|
-
const
|
|
529
|
+
const named2 = new Set(hops.filter((h) => known(h) && ctx.requirements.some((r) => requirementMentionsHop(ctx, r, h))));
|
|
522
530
|
for (const r of ctx.requirements) {
|
|
523
531
|
const s = blank(slug(r.name), "requirement");
|
|
524
532
|
s.requirement = r;
|
|
@@ -531,7 +539,7 @@ function buildSkeleton(ctx) {
|
|
|
531
539
|
}
|
|
532
540
|
if (s.hops.length === 0) {
|
|
533
541
|
for (const h of hops)
|
|
534
|
-
if (known(h) && !
|
|
542
|
+
if (known(h) && !named2.has(h) && requirementIsAboutHop(ctx, r, h)) {
|
|
535
543
|
s.hops.push(h);
|
|
536
544
|
used.add(h);
|
|
537
545
|
}
|
|
@@ -663,12 +671,12 @@ ${m}`);
|
|
|
663
671
|
uiRepos.add(repo);
|
|
664
672
|
}
|
|
665
673
|
for (const s of stories) {
|
|
666
|
-
const
|
|
674
|
+
const text4 = s.hops.map((h) => ctx.mapping(h)).join("\n").toLowerCase();
|
|
667
675
|
for (const o of stories) {
|
|
668
676
|
if (o === s)
|
|
669
677
|
continue;
|
|
670
678
|
const adds = o.data.flatMap((d) => d.newColumns.map((c) => `${d.table.toLowerCase()}.${c.name.toLowerCase()}`));
|
|
671
|
-
if (adds.some((a) =>
|
|
679
|
+
if (adds.some((a) => text4.includes(a)))
|
|
672
680
|
s.dependsOn.push(o.id);
|
|
673
681
|
}
|
|
674
682
|
}
|
|
@@ -879,13 +887,13 @@ function storyPack(ctx, skeleton, agent, now) {
|
|
|
879
887
|
}
|
|
880
888
|
];
|
|
881
889
|
const add = (rel, priority) => {
|
|
882
|
-
const
|
|
883
|
-
if (
|
|
890
|
+
const text4 = doc(rel);
|
|
891
|
+
if (text4)
|
|
884
892
|
sources.push({
|
|
885
893
|
id: rel,
|
|
886
894
|
kind: "artifact",
|
|
887
895
|
origin: `${changesRelOf(ctx.root)}/${ctx.change.id}/${rel}`,
|
|
888
|
-
text:
|
|
896
|
+
text: text4,
|
|
889
897
|
classification: "internal",
|
|
890
898
|
priority
|
|
891
899
|
});
|
|
@@ -1280,6 +1288,98 @@ function readDrafts(ctx) {
|
|
|
1280
1288
|
return [{ template: t, record, fields: fields2, heading: d.heading }];
|
|
1281
1289
|
});
|
|
1282
1290
|
}
|
|
1291
|
+
var SnowFillError = class extends Error {
|
|
1292
|
+
constructor(message) {
|
|
1293
|
+
super(message);
|
|
1294
|
+
this.name = "SnowFillError";
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
var rowCells = (line) => {
|
|
1298
|
+
let s = line.trim();
|
|
1299
|
+
if (s.startsWith("|"))
|
|
1300
|
+
s = s.slice(1);
|
|
1301
|
+
if (s.endsWith("|"))
|
|
1302
|
+
s = s.slice(0, -1);
|
|
1303
|
+
return s.split("|").map((c) => c.trim());
|
|
1304
|
+
};
|
|
1305
|
+
function fillSnowFields(text4, heading, values) {
|
|
1306
|
+
const given = Object.entries(values).map(([name, v]) => [name.trim(), v.trim()]);
|
|
1307
|
+
if (!given.length)
|
|
1308
|
+
throw new SnowFillError("Give at least one field to fill.");
|
|
1309
|
+
for (const [name, v] of given)
|
|
1310
|
+
if (!name || /[|\r\n]/.test(v) || /[|\r\n]/.test(name))
|
|
1311
|
+
throw new SnowFillError(`${name || "A field"}: a value can't hold | or a line break (snow.md keeps it in a table cell).`);
|
|
1312
|
+
const eol = text4.includes("\r\n") ? "\r\n" : "\n";
|
|
1313
|
+
const doc = parseDoc("snow.md", text4);
|
|
1314
|
+
const raw = doc.rawLines;
|
|
1315
|
+
const s = doc.sections.find((x) => x.level === 2 && x.heading === heading);
|
|
1316
|
+
if (!s)
|
|
1317
|
+
throw new SnowFillError(`snow.md has no request "${heading}". Reload the page to see its requests.`);
|
|
1318
|
+
const table = doc.tables.find((t) => t.line >= s.line && t.line <= s.end && t.headers.includes("field"));
|
|
1319
|
+
if (!table || !table.headers.includes("value"))
|
|
1320
|
+
throw new SnowFillError(`"${heading}" in snow.md has no Field | Value table to fill.`);
|
|
1321
|
+
const fieldAt = table.headers.indexOf("field");
|
|
1322
|
+
const valueAt2 = table.headers.indexOf("value");
|
|
1323
|
+
const edited = /* @__PURE__ */ new Map();
|
|
1324
|
+
const added = [];
|
|
1325
|
+
const row = (cells) => `| ${cells.join(" | ")} |`;
|
|
1326
|
+
for (const [name, v] of given) {
|
|
1327
|
+
const r = table.rows.find((x) => (x.cells.field ?? "").toLowerCase() === name.toLowerCase());
|
|
1328
|
+
if (!r) {
|
|
1329
|
+
const cells2 = table.headers.map(() => "");
|
|
1330
|
+
cells2[fieldAt] = name;
|
|
1331
|
+
cells2[valueAt2] = v;
|
|
1332
|
+
added.push(row(cells2));
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
const at2 = r.line - 1;
|
|
1336
|
+
if (raw[at2].includes("<!--"))
|
|
1337
|
+
throw new SnowFillError(`${name}'s row in snow.md holds a comment: edit that row there.`);
|
|
1338
|
+
const cells = rowCells(raw[at2]);
|
|
1339
|
+
while (cells.length < table.headers.length)
|
|
1340
|
+
cells.push("");
|
|
1341
|
+
cells[valueAt2] = v;
|
|
1342
|
+
edited.set(at2, row(cells));
|
|
1343
|
+
}
|
|
1344
|
+
const filled = given.filter(([, v]) => v).map(([n]) => n.toLowerCase());
|
|
1345
|
+
const drop = /* @__PURE__ */ new Set();
|
|
1346
|
+
const lines = doc.lines;
|
|
1347
|
+
for (let i = s.line; i < s.end; i++) {
|
|
1348
|
+
if (lines[i].trim() !== "To fill:")
|
|
1349
|
+
continue;
|
|
1350
|
+
let j = i + 1;
|
|
1351
|
+
const bullets2 = [];
|
|
1352
|
+
for (; j < s.end; j++) {
|
|
1353
|
+
const l = lines[j].trim();
|
|
1354
|
+
if (!l)
|
|
1355
|
+
continue;
|
|
1356
|
+
if (!l.startsWith("- "))
|
|
1357
|
+
break;
|
|
1358
|
+
bullets2.push(j);
|
|
1359
|
+
}
|
|
1360
|
+
for (const b of bullets2) {
|
|
1361
|
+
const l = lines[b].trim().slice(2).toLowerCase();
|
|
1362
|
+
if (filled.some((n) => l.startsWith(`${n}:`)))
|
|
1363
|
+
drop.add(b);
|
|
1364
|
+
}
|
|
1365
|
+
if (bullets2.length && bullets2.every((b) => drop.has(b))) {
|
|
1366
|
+
const last = bullets2[bullets2.length - 1];
|
|
1367
|
+
for (let k = i; k <= last; k++)
|
|
1368
|
+
drop.add(k);
|
|
1369
|
+
if (last + 1 < raw.length && !raw[last + 1].trim() && last + 1 < s.end)
|
|
1370
|
+
drop.add(last + 1);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
const lastRow = table.rows.length ? table.rows[table.rows.length - 1].line - 1 : table.line;
|
|
1374
|
+
const out = [];
|
|
1375
|
+
raw.forEach((l, i) => {
|
|
1376
|
+
if (!drop.has(i))
|
|
1377
|
+
out.push(edited.get(i) ?? l);
|
|
1378
|
+
if (i === lastRow)
|
|
1379
|
+
out.push(...added);
|
|
1380
|
+
});
|
|
1381
|
+
return out.join(eol);
|
|
1382
|
+
}
|
|
1283
1383
|
|
|
1284
1384
|
// packages/planner/dist/plan.js
|
|
1285
1385
|
import { readdir, readFile, rm } from "node:fs/promises";
|
|
@@ -1295,8 +1395,8 @@ async function guard(ctx, rels, force) {
|
|
|
1295
1395
|
const p = join(ctx.dir, rel);
|
|
1296
1396
|
if (!await exists(p))
|
|
1297
1397
|
continue;
|
|
1298
|
-
const
|
|
1299
|
-
if (!
|
|
1398
|
+
const text4 = await readFile(p, "utf8");
|
|
1399
|
+
if (!text4.startsWith(GENERATED_MARK))
|
|
1300
1400
|
own.push(rel);
|
|
1301
1401
|
}
|
|
1302
1402
|
if (own.length)
|
|
@@ -1430,15 +1530,19 @@ var body = (doc, re) => {
|
|
|
1430
1530
|
};
|
|
1431
1531
|
var bullets = (lines = []) => lines.map((l) => /^\s*[-*]\s+(.*)$/.exec(l)?.[1]?.trim()).filter((x) => !!x);
|
|
1432
1532
|
var list = (s) => s.split(/[,;]/).map((x) => x.trim()).filter((x) => x && !/^(none|nothing)\.?$/i.test(x));
|
|
1433
|
-
function parseStory(path,
|
|
1434
|
-
const doc = parseDoc(path,
|
|
1533
|
+
function parseStory(path, text4) {
|
|
1534
|
+
const doc = parseDoc(path, text4);
|
|
1435
1535
|
const id = path.replace(/^stories\//, "").replace(/\.md$/, "");
|
|
1436
1536
|
const h1 = doc.sections.find((s) => s.level === 1);
|
|
1437
1537
|
const firstH2 = doc.sections.find((s) => s.level === 2);
|
|
1438
1538
|
const intro = h1 ? doc.lines.slice(h1.line, firstH2 ? firstH2.line - 1 : doc.lines.length).join(" ") : "";
|
|
1439
1539
|
const w = /\bas an?\s+(.+?)\s*,?\s+i want\s+(.+?)\s*,?\s+so that\s+(.+?)\s*$/i.exec(intro.replace(/\s+/g, " ").trim());
|
|
1440
|
-
const
|
|
1540
|
+
const sysSection = section(doc, /^systems and repos$/i);
|
|
1541
|
+
const sysLines = sysSection ? sectionLines(doc, sysSection) : [];
|
|
1542
|
+
const sysEnd = sysLines.findIndex((l, i) => i > 0 && l.n !== sysLines[i - 1].n + 1);
|
|
1543
|
+
const sysLine = sysLines.slice(0, sysEnd < 0 ? void 0 : sysEnd).map((l) => l.text).join(" ").trim();
|
|
1441
1544
|
const m = /^(.*?)(?:\((.*)\))?\s*$/.exec(sysLine);
|
|
1545
|
+
const parens = !!m?.[2];
|
|
1442
1546
|
const links2 = bullets(body(doc, /^links$/i));
|
|
1443
1547
|
const interfaces = links2.flatMap((l) => {
|
|
1444
1548
|
const x = /^Interface\s+(\S+):\s*(\S+)\s*→\s*(\S+)\s+([A-Z]+\s+\S+)/.exec(l);
|
|
@@ -1459,6 +1563,7 @@ function parseStory(path, text2) {
|
|
|
1459
1563
|
...body(doc, /^traces to$/i)?.[0] ? { traces: body(doc, /^traces to$/i)[0].trim() } : {},
|
|
1460
1564
|
systems: list(m?.[1] ?? ""),
|
|
1461
1565
|
repos: list(m?.[2] ?? ""),
|
|
1566
|
+
...sysLine && !parens ? { bare: true } : {},
|
|
1462
1567
|
tests: list((body(doc, /^tests$/i) ?? []).join(" ")),
|
|
1463
1568
|
...dep ? { dependsOn: list(dep.join(" ")) } : {},
|
|
1464
1569
|
interfaces,
|
|
@@ -1529,6 +1634,8 @@ function knownNames(ctx, storyIds) {
|
|
|
1529
1634
|
const names = /* @__PURE__ */ new Set([
|
|
1530
1635
|
...ctx.records.map((r) => r.id),
|
|
1531
1636
|
...storyIds,
|
|
1637
|
+
// The change's own id: the stage names it in each task's prompt line.
|
|
1638
|
+
ctx.change.id,
|
|
1532
1639
|
...systems,
|
|
1533
1640
|
...ctx.knownRepos
|
|
1534
1641
|
]);
|
|
@@ -1559,6 +1666,18 @@ function knownNames(ctx, storyIds) {
|
|
|
1559
1666
|
for (const g of n.text.matchAll(/\bgw-[a-z0-9-]+\b/g))
|
|
1560
1667
|
names.add(g[0]);
|
|
1561
1668
|
}
|
|
1669
|
+
const design = [
|
|
1670
|
+
...(ctx.files.flows ?? []).map((d) => d.raw),
|
|
1671
|
+
ctx.files.docs.get("design.md")?.raw ?? ""
|
|
1672
|
+
];
|
|
1673
|
+
for (const text4 of design) {
|
|
1674
|
+
for (const m of text4.matchAll(COMPONENT_LIKE))
|
|
1675
|
+
names.add(m[0]);
|
|
1676
|
+
for (const m of text4.matchAll(/\bcomponent:\s*([a-z0-9][a-z0-9._-]*)/g))
|
|
1677
|
+
names.add(m[1]);
|
|
1678
|
+
for (const m of text4.matchAll(OPERATION))
|
|
1679
|
+
operations.add(op(m[1], m[2].replace(/[.:'"]+$/, "")));
|
|
1680
|
+
}
|
|
1562
1681
|
const records = /* @__PURE__ */ new Set([
|
|
1563
1682
|
...ctx.records.filter((r) => r.kind === "Table" || r.kind === "Column").map((r) => r.id),
|
|
1564
1683
|
...ctx.data.map((d) => d.tableId)
|
|
@@ -1579,12 +1698,39 @@ function knownNames(ctx, storyIds) {
|
|
|
1579
1698
|
}
|
|
1580
1699
|
var COMPONENT_LIKE = /(?<![\w-])[a-z][a-z0-9]*(?:-[a-z0-9]+)*-(?:api|svc|service|bff|ui|db|job|engine|gateway|app|worker)(?![\w-])/g;
|
|
1581
1700
|
var OPERATION = /\b(GET|POST|PUT|PATCH|DELETE|HEAD)\s+(\/[^\s,;)]*)/g;
|
|
1701
|
+
function named(s, known) {
|
|
1702
|
+
if (!s.bare)
|
|
1703
|
+
return { systems: s.systems, repos: s.repos };
|
|
1704
|
+
return {
|
|
1705
|
+
systems: s.systems.filter((x) => !known.repos.has(x)),
|
|
1706
|
+
repos: s.systems.filter((x) => known.repos.has(x))
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
function knownOperation(method, path, known) {
|
|
1710
|
+
const key3 = `${method} ${normalizePath2(path)}`;
|
|
1711
|
+
if (known.has(key3))
|
|
1712
|
+
return true;
|
|
1713
|
+
const want = normalizePath2(path).split("/");
|
|
1714
|
+
const wild = (x) => x === "{}" || x === "*" || x === "**";
|
|
1715
|
+
for (const k of known) {
|
|
1716
|
+
const sp = k.indexOf(" ");
|
|
1717
|
+
if (k.slice(0, sp) !== method)
|
|
1718
|
+
continue;
|
|
1719
|
+
const have = k.slice(sp + 1).split("/");
|
|
1720
|
+
if (have.length !== want.length)
|
|
1721
|
+
continue;
|
|
1722
|
+
if (have.every((h, i) => h === want[i] || wild(h) || wild(want[i])))
|
|
1723
|
+
return true;
|
|
1724
|
+
}
|
|
1725
|
+
return false;
|
|
1726
|
+
}
|
|
1582
1727
|
function invented(s, known) {
|
|
1583
1728
|
const out = /* @__PURE__ */ new Set();
|
|
1584
|
-
|
|
1729
|
+
const n = named(s, known);
|
|
1730
|
+
for (const x of n.systems)
|
|
1585
1731
|
if (!known.systems.has(x))
|
|
1586
1732
|
out.add(`system ${x}`);
|
|
1587
|
-
for (const x of
|
|
1733
|
+
for (const x of n.repos)
|
|
1588
1734
|
if (!known.repos.has(x))
|
|
1589
1735
|
out.add(`repo ${x}`);
|
|
1590
1736
|
for (const r of s.records)
|
|
@@ -1604,11 +1750,9 @@ function invented(s, known) {
|
|
|
1604
1750
|
for (const m of prose.matchAll(COMPONENT_LIKE))
|
|
1605
1751
|
if (!known.names.has(m[0]))
|
|
1606
1752
|
out.add(m[0]);
|
|
1607
|
-
for (const m of prose.matchAll(OPERATION))
|
|
1608
|
-
|
|
1609
|
-
if (!known.operations.has(key3))
|
|
1753
|
+
for (const m of prose.matchAll(OPERATION))
|
|
1754
|
+
if (!knownOperation(m[1], m[2].replace(/[.:'"]+$/, ""), known.operations))
|
|
1610
1755
|
out.add(`${m[1]} ${m[2]}`);
|
|
1611
|
-
}
|
|
1612
1756
|
return [...out].sort();
|
|
1613
1757
|
}
|
|
1614
1758
|
function checkStory(ctx, s, checks, known) {
|
|
@@ -1619,10 +1763,18 @@ function checkStory(ctx, s, checks, known) {
|
|
|
1619
1763
|
if (checks.has("traced")) {
|
|
1620
1764
|
const m = s.traces ? /^(\S+?):(\d+)/.exec(s.traces) : null;
|
|
1621
1765
|
const doc = m ? ctx.files.docs.get(m[1]) : void 0;
|
|
1622
|
-
|
|
1766
|
+
const paths = m ? [] : (s.traces ?? "").split(";").map((p) => /^\s*((?:target-flows|specs)\/\S+\.(?:ya?ml|md))\b/.exec(p)?.[1]).filter((p) => !!p);
|
|
1767
|
+
const inChange = (p) => ctx.files.docs.has(p) || (ctx.files.flows ?? []).some((f) => f.path === p);
|
|
1768
|
+
if (m) {
|
|
1769
|
+
if (!doc || Number(m[2]) > doc.lines.length)
|
|
1770
|
+
problems.push(`traces to ${s.traces}, which isn't in the change`);
|
|
1771
|
+
} else if (paths.length === 0)
|
|
1623
1772
|
problems.push("doesn't trace to the brief, proposal or a spec");
|
|
1624
|
-
else
|
|
1625
|
-
|
|
1773
|
+
else {
|
|
1774
|
+
const missing = paths.filter((p) => !inChange(p));
|
|
1775
|
+
if (missing.length)
|
|
1776
|
+
problems.push(`traces to ${missing.join(", ")}, which isn't in the change`);
|
|
1777
|
+
}
|
|
1626
1778
|
}
|
|
1627
1779
|
if (checks.has("criteria")) {
|
|
1628
1780
|
const gwt = s.criteria.filter((c) => GWT.test(c));
|
|
@@ -1632,9 +1784,10 @@ function checkStory(ctx, s, checks, known) {
|
|
|
1632
1784
|
wording.push(`criterion not in Given/When/Then form: ${c.slice(0, 60)}`);
|
|
1633
1785
|
}
|
|
1634
1786
|
if (checks.has("systems")) {
|
|
1635
|
-
|
|
1787
|
+
const n = named(s, known);
|
|
1788
|
+
if (n.systems.length === 0 && n.repos.length === 0)
|
|
1636
1789
|
problems.push("names no system");
|
|
1637
|
-
if (
|
|
1790
|
+
if (n.repos.length === 0)
|
|
1638
1791
|
problems.push("names no repo");
|
|
1639
1792
|
}
|
|
1640
1793
|
if (checks.has("depends") && !s.dependsOn)
|
|
@@ -2104,8 +2257,8 @@ import { join as join4, relative, sep } from "node:path";
|
|
|
2104
2257
|
var PROPOSALS_DIR = "proposals/templates";
|
|
2105
2258
|
var HOW_TO_TITLE = /^how to\s+(?:request|raise|ask for|get|open|submit|create|file)\s+(?:an?\s+|the\s+)?(.+?)\s*$/i;
|
|
2106
2259
|
var ref = (h, n) => h.lineRefs ? `${h.path}:${n}` : h.path;
|
|
2107
|
-
function linesOf(
|
|
2108
|
-
return
|
|
2260
|
+
function linesOf(text4) {
|
|
2261
|
+
return text4.split(/\r?\n/).map((t, i) => ({ n: i + 1, text: t }));
|
|
2109
2262
|
}
|
|
2110
2263
|
function isHowTo(h) {
|
|
2111
2264
|
return HOW_TO_TITLE.test(h.title) || h.lines.some((l) => /\bfields?\s*:/i.test(l.text));
|
|
@@ -2601,11 +2754,11 @@ async function uploads(root, inputsDir, skip) {
|
|
|
2601
2754
|
const bytes = new Uint8Array(await readFile4(full));
|
|
2602
2755
|
const doc = readDocument(e.name, bytes);
|
|
2603
2756
|
const plain2 = /\.(md|markdown|txt)$/i.test(e.name);
|
|
2604
|
-
const
|
|
2757
|
+
const text4 = plain2 ? new TextDecoder().decode(bytes) : doc.text.sections.map((s) => [s.heading ? `# ${s.heading}` : "", s.text].filter(Boolean).join("\n")).join("\n");
|
|
2605
2758
|
out.push({
|
|
2606
2759
|
path: relative(root, full).split(sep).join("/"),
|
|
2607
2760
|
title: doc.text.title,
|
|
2608
|
-
lines: linesOf(
|
|
2761
|
+
lines: linesOf(text4),
|
|
2609
2762
|
kind: "upload",
|
|
2610
2763
|
lineRefs: plain2
|
|
2611
2764
|
});
|
|
@@ -2635,13 +2788,13 @@ async function draftTemplates(root, o = {}) {
|
|
|
2635
2788
|
if (s.type === "markdown-folder")
|
|
2636
2789
|
wikiDirs.push(join4(orgPackDir, s.location));
|
|
2637
2790
|
for (const p of wiki.pages) {
|
|
2638
|
-
const
|
|
2639
|
-
if (
|
|
2791
|
+
const text4 = await readFile4(join4(root, p.path), "utf8").catch(() => void 0);
|
|
2792
|
+
if (text4 === void 0)
|
|
2640
2793
|
continue;
|
|
2641
2794
|
wikiHowTos.push({
|
|
2642
2795
|
path: p.path,
|
|
2643
2796
|
title: p.title,
|
|
2644
|
-
lines: linesOf(
|
|
2797
|
+
lines: linesOf(text4),
|
|
2645
2798
|
kind: "wiki",
|
|
2646
2799
|
lineRefs: true
|
|
2647
2800
|
});
|
|
@@ -2734,8 +2887,8 @@ async function draftTemplates(root, o = {}) {
|
|
|
2734
2887
|
});
|
|
2735
2888
|
}
|
|
2736
2889
|
const files = [];
|
|
2737
|
-
const write3 = async (rel,
|
|
2738
|
-
await writeFileAtomic(join4(proposalsDirOf(root), "templates", rel),
|
|
2890
|
+
const write3 = async (rel, text4) => {
|
|
2891
|
+
await writeFileAtomic(join4(proposalsDirOf(root), "templates", rel), text4);
|
|
2739
2892
|
files.push(`${proposalsRelOf(root)}/templates/${rel}`);
|
|
2740
2893
|
};
|
|
2741
2894
|
for (const d of drafts)
|
|
@@ -2831,9 +2984,9 @@ function stem2(w) {
|
|
|
2831
2984
|
s = s.slice(0, -1);
|
|
2832
2985
|
return s;
|
|
2833
2986
|
}
|
|
2834
|
-
function words2(
|
|
2987
|
+
function words2(text4) {
|
|
2835
2988
|
const out = /* @__PURE__ */ new Set();
|
|
2836
|
-
for (const w of
|
|
2989
|
+
for (const w of text4.split(/[^A-Za-z0-9]+/)) {
|
|
2837
2990
|
if (w.length < 3 || /^\d+$/.test(w))
|
|
2838
2991
|
continue;
|
|
2839
2992
|
for (const p of w.split(/(?<=[a-z])(?=[A-Z])/)) {
|
|
@@ -2860,9 +3013,9 @@ function relevantMap(records, brief, max = 60) {
|
|
|
2860
3013
|
want.set(w, 3);
|
|
2861
3014
|
const all2 = records;
|
|
2862
3015
|
const byId = new Map(all2.map((r) => [r.id, r]));
|
|
2863
|
-
const
|
|
2864
|
-
const top = (kind,
|
|
2865
|
-
const pool = all2.filter((r) => r.kind === kind).map((r) => ({ r, w: words2(
|
|
3016
|
+
const named2 = new Set(all2.filter((r) => new RegExp(`\\b${r.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(brief)).map((r) => r.id));
|
|
3017
|
+
const top = (kind, text4, n) => {
|
|
3018
|
+
const pool = all2.filter((r) => r.kind === kind).map((r) => ({ r, w: words2(text4(r)) }));
|
|
2866
3019
|
const df = /* @__PURE__ */ new Map();
|
|
2867
3020
|
for (const p of pool)
|
|
2868
3021
|
for (const w of p.w)
|
|
@@ -2873,7 +3026,7 @@ function relevantMap(records, brief, max = 60) {
|
|
|
2873
3026
|
};
|
|
2874
3027
|
const scored = pool.map((p) => ({
|
|
2875
3028
|
r: p.r,
|
|
2876
|
-
s: [...p.w].filter((w) => want.has(w)).reduce((a, w) => a + weight(w) * want.get(w), 0) + (
|
|
3029
|
+
s: [...p.w].filter((w) => want.has(w)).reduce((a, w) => a + weight(w) * want.get(w), 0) + (named2.has(p.r.id) ? 10 : 0)
|
|
2877
3030
|
})).filter((x) => x.s > 0);
|
|
2878
3031
|
const best = Math.max(0, ...scored.map((x) => x.s));
|
|
2879
3032
|
return scored.filter((x) => x.s >= best * 0.5).sort((a, b) => b.s - a.s || a.r.id.localeCompare(b.r.id)).slice(0, n).map((x) => x.r);
|
|
@@ -2895,7 +3048,7 @@ function relevantMap(records, brief, max = 60) {
|
|
|
2895
3048
|
for (const e of endpoints.values())
|
|
2896
3049
|
if (str(e.component))
|
|
2897
3050
|
components.add(str(e.component));
|
|
2898
|
-
for (const id of
|
|
3051
|
+
for (const id of named2)
|
|
2899
3052
|
if (byId.get(id)?.kind === "Component")
|
|
2900
3053
|
components.add(id);
|
|
2901
3054
|
const calls = all2.filter((r) => r.kind === "Interface" && (str(r.endpoint) && endpoints.has(str(r.endpoint)) || [...endpoints.values()].some((e) => str(e.component) === str(r.to) && str(e.method) === str(r.method) && (str(e.pathOrTopic) ?? "").replace(/\{[^}]*\}/g, "{}") === (str(r.normalizedPath) ?? "").replace(/\{[^}]*\}/g, "{}"))));
|
|
@@ -2913,7 +3066,7 @@ function relevantMap(records, brief, max = 60) {
|
|
|
2913
3066
|
if (s)
|
|
2914
3067
|
systems.add(s);
|
|
2915
3068
|
}
|
|
2916
|
-
for (const id of
|
|
3069
|
+
for (const id of named2)
|
|
2917
3070
|
if (byId.get(id)?.kind === "System")
|
|
2918
3071
|
systems.add(id);
|
|
2919
3072
|
const lines = [
|
|
@@ -2949,7 +3102,7 @@ function relevantMap(records, brief, max = 60) {
|
|
|
2949
3102
|
lines.push("Nothing in the catalog shares a word with the brief: name systems or components by their catalog id in the brief's Systems section.");
|
|
2950
3103
|
return { text: lines.join("\n"), ids };
|
|
2951
3104
|
}
|
|
2952
|
-
function parseDraft(
|
|
3105
|
+
function parseDraft(text4) {
|
|
2953
3106
|
const files = /* @__PURE__ */ new Map();
|
|
2954
3107
|
let path;
|
|
2955
3108
|
let body2 = [];
|
|
@@ -2958,7 +3111,7 @@ function parseDraft(text2) {
|
|
|
2958
3111
|
files.set(path, `${body2.join("\n").trim()}
|
|
2959
3112
|
`);
|
|
2960
3113
|
};
|
|
2961
|
-
for (const line of
|
|
3114
|
+
for (const line of text4.replace(/\r\n/g, "\n").split("\n")) {
|
|
2962
3115
|
const m = /^===\s*(?:file:\s*)?(\S+)\s*(?:===)?\s*$/.exec(line);
|
|
2963
3116
|
if (m) {
|
|
2964
3117
|
flush();
|
|
@@ -3149,8 +3302,8 @@ async function draftChange(root, changeId, o) {
|
|
|
3149
3302
|
};
|
|
3150
3303
|
const own = [];
|
|
3151
3304
|
const isOwn = async (rel) => {
|
|
3152
|
-
const
|
|
3153
|
-
return
|
|
3305
|
+
const text4 = await readFile5(join6(dir, rel), "utf8").catch(() => void 0);
|
|
3306
|
+
return text4 !== void 0 && !text4.startsWith(DRAFT_MARK);
|
|
3154
3307
|
};
|
|
3155
3308
|
for (const rel of ["proposal.md", "interfaces.md"])
|
|
3156
3309
|
if (!o.force && await isOwn(rel))
|
|
@@ -3205,10 +3358,10 @@ async function draftChange(root, changeId, o) {
|
|
|
3205
3358
|
|
|
3206
3359
|
`;
|
|
3207
3360
|
const files = [];
|
|
3208
|
-
for (const [rel,
|
|
3361
|
+
for (const [rel, text4] of [...answer].sort(([a], [b]) => a.localeCompare(b))) {
|
|
3209
3362
|
const path = join6(dir, rel);
|
|
3210
3363
|
await mkdir(dirname(path), { recursive: true });
|
|
3211
|
-
await writeFileAtomic(path, stamp2 +
|
|
3364
|
+
await writeFileAtomic(path, stamp2 + text4);
|
|
3212
3365
|
files.push(rel);
|
|
3213
3366
|
}
|
|
3214
3367
|
return { ...base, state: "written", files, discarded, problems: [] };
|
|
@@ -3400,7 +3553,7 @@ var ESTATE_KINDS = ["System", "Component", "Capability", "Flow", "Endpoint", "Ta
|
|
|
3400
3553
|
var ESTATE_HEADING = "The estate, from the repos' specs and the scanned catalog. Ids are exact: name only these, or say something is new.";
|
|
3401
3554
|
function scopeRepos(e, touched) {
|
|
3402
3555
|
const recs = e.records;
|
|
3403
|
-
const
|
|
3556
|
+
const repoOf2 = (id) => str2(e.byId.get(str2(id) ?? "")?.repo);
|
|
3404
3557
|
const out = new Set(touched);
|
|
3405
3558
|
const systems = new Set(recs.filter((r) => r.kind === "Component" && out.has(str2(r.repo) ?? "")).flatMap((r) => str2(r.system) ? [str2(r.system)] : []));
|
|
3406
3559
|
for (const r of recs)
|
|
@@ -3410,8 +3563,8 @@ function scopeRepos(e, touched) {
|
|
|
3410
3563
|
for (const r of recs) {
|
|
3411
3564
|
if (r.kind !== "Interface")
|
|
3412
3565
|
continue;
|
|
3413
|
-
const callee =
|
|
3414
|
-
const caller =
|
|
3566
|
+
const callee = repoOf2(r.to) ?? repoOf2(e.byId.get(str2(r.endpoint) ?? "")?.component);
|
|
3567
|
+
const caller = repoOf2(r.from);
|
|
3415
3568
|
if (caller && seed.has(caller) && callee)
|
|
3416
3569
|
out.add(callee);
|
|
3417
3570
|
if (callee && seed.has(callee) && caller)
|
|
@@ -3539,9 +3692,9 @@ function systemOf(e, id) {
|
|
|
3539
3692
|
}
|
|
3540
3693
|
|
|
3541
3694
|
// packages/planner/dist/stages/brief-endpoints.js
|
|
3542
|
-
function endpointsIn(
|
|
3695
|
+
function endpointsIn(text4) {
|
|
3543
3696
|
const out = [];
|
|
3544
|
-
for (const m of
|
|
3697
|
+
for (const m of text4.matchAll(/\b(GET|POST|PUT|PATCH|DELETE)\s+(\/[^\s`'",;)\]]*)/g)) {
|
|
3545
3698
|
const e = `${m[1]} ${m[2].replace(/[.:]+$/, "")}`;
|
|
3546
3699
|
if (!out.some((x) => endpointKey(x) === endpointKey(e)))
|
|
3547
3700
|
out.push(e);
|
|
@@ -3642,8 +3795,8 @@ function readFigma(json) {
|
|
|
3642
3795
|
screen.fields.push(f);
|
|
3643
3796
|
ids.add(f.figmaId);
|
|
3644
3797
|
};
|
|
3645
|
-
const message = (figmaId,
|
|
3646
|
-
screen.messages.push({ figmaId, text:
|
|
3798
|
+
const message = (figmaId, text4) => {
|
|
3799
|
+
screen.messages.push({ figmaId, text: text4 });
|
|
3647
3800
|
ids.add(figmaId);
|
|
3648
3801
|
};
|
|
3649
3802
|
const visit = (n) => {
|
|
@@ -3812,10 +3965,10 @@ function designGaps(d) {
|
|
|
3812
3965
|
}
|
|
3813
3966
|
|
|
3814
3967
|
// packages/planner/dist/stages/loop.js
|
|
3815
|
-
var
|
|
3816
|
-
import { createHash as
|
|
3817
|
-
import { copyFile, mkdir as mkdir3, readdir as readdir7, readFile as
|
|
3818
|
-
import { join as
|
|
3968
|
+
var import_yaml10 = __toESM(require_dist(), 1);
|
|
3969
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3970
|
+
import { copyFile, mkdir as mkdir3, readdir as readdir7, readFile as readFile9, rm as rm3, stat } from "node:fs/promises";
|
|
3971
|
+
import { join as join11 } from "node:path";
|
|
3819
3972
|
|
|
3820
3973
|
// packages/planner/dist/stages/cite.js
|
|
3821
3974
|
var escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -3901,9 +4054,9 @@ function issueWords(issue, value) {
|
|
|
3901
4054
|
}
|
|
3902
4055
|
return `${where2}: ${issue.message}.`;
|
|
3903
4056
|
}
|
|
3904
|
-
function schemaProblems(file,
|
|
4057
|
+
function schemaProblems(file, text4, issues) {
|
|
3905
4058
|
const lc = new import_yaml5.LineCounter();
|
|
3906
|
-
const doc = (0, import_yaml5.parseDocument)(
|
|
4059
|
+
const doc = (0, import_yaml5.parseDocument)(text4, { lineCounter: lc, keepSourceTokens: false });
|
|
3907
4060
|
const data = doc.toJS();
|
|
3908
4061
|
return issues.map((i) => {
|
|
3909
4062
|
let line = 1;
|
|
@@ -3926,12 +4079,12 @@ function schemaProblems(file, text2, issues) {
|
|
|
3926
4079
|
}
|
|
3927
4080
|
|
|
3928
4081
|
// packages/planner/dist/stages/answer.js
|
|
3929
|
-
var unfence = (
|
|
4082
|
+
var unfence = (text4) => text4.replace(/\r\n/g, "\n").replace(/^\s*```(?:ya?ml)?\s*\n/, "").replace(/\n```\s*$/, "");
|
|
3930
4083
|
var YAML_QUOTE_RULE = "quote any value containing { } [ ] : or # in double quotes";
|
|
3931
4084
|
var capitalQuote = `${YAML_QUOTE_RULE.charAt(0).toUpperCase()}${YAML_QUOTE_RULE.slice(1)}.`;
|
|
3932
|
-
function parseYamlAs(
|
|
4085
|
+
function parseYamlAs(text4, schema, fill, target) {
|
|
3933
4086
|
let data;
|
|
3934
|
-
const body2 = unfence(
|
|
4087
|
+
const body2 = unfence(text4);
|
|
3935
4088
|
try {
|
|
3936
4089
|
data = (0, import_yaml6.parse)(body2);
|
|
3937
4090
|
} catch (e) {
|
|
@@ -4016,7 +4169,9 @@ async function questionsOnFile(root, repo) {
|
|
|
4016
4169
|
`- ${q2.id} (${q2.state}): ${q2.question.trim()} [${q2.evidence.join(", ")}]`,
|
|
4017
4170
|
...q2.state === "answered" ? (
|
|
4018
4171
|
// B-run2-fixes 8: the option's meaning, or the person's own words, not the label alone.
|
|
4019
|
-
[
|
|
4172
|
+
[
|
|
4173
|
+
` Answer (${q2.answer ? answerByWords(q2.answer.by) : "a person"}): ${answerText(q2) ?? ""}`
|
|
4174
|
+
]
|
|
4020
4175
|
) : []
|
|
4021
4176
|
].join("\n")).join("\n"),
|
|
4022
4177
|
classification: "internal",
|
|
@@ -4036,22 +4191,378 @@ function stepJudges(root, runtime, change, now) {
|
|
|
4036
4191
|
return { judge: questionJudge(o), answerJudge: answerJudge(o) };
|
|
4037
4192
|
}
|
|
4038
4193
|
|
|
4194
|
+
// packages/planner/dist/stages/drafts.js
|
|
4195
|
+
var import_yaml7 = __toESM(require_dist(), 1);
|
|
4196
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
4197
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
4198
|
+
import { join as join7 } from "node:path";
|
|
4199
|
+
var DRAFTS_SCHEMA_VERSION = 1;
|
|
4200
|
+
var DRAFTED_FILE = "stage/drafted.yaml";
|
|
4201
|
+
var CORRECTIONS_FILE = "stage/corrections.yaml";
|
|
4202
|
+
var V = external_exports.literal(DRAFTS_SCHEMA_VERSION);
|
|
4203
|
+
var text = () => external_exports.string().trim().min(1);
|
|
4204
|
+
var Id = external_exports.string().regex(/^[a-z0-9][a-z0-9._-]*$/);
|
|
4205
|
+
var DraftedRecord = external_exports.discriminatedUnion("by", [
|
|
4206
|
+
external_exports.strictObject({
|
|
4207
|
+
by: external_exports.literal("ai"),
|
|
4208
|
+
stage: Stage,
|
|
4209
|
+
/** The run that drafted it (a hand-off, a model run or a replay). */
|
|
4210
|
+
run: external_exports.string().optional(),
|
|
4211
|
+
/** The stage's draft number. */
|
|
4212
|
+
iteration: external_exports.number().int().min(0),
|
|
4213
|
+
at: external_exports.string(),
|
|
4214
|
+
sha256: external_exports.string().regex(/^[0-9a-f]{64}$/)
|
|
4215
|
+
}),
|
|
4216
|
+
external_exports.strictObject({
|
|
4217
|
+
by: external_exports.literal("person"),
|
|
4218
|
+
who: text(),
|
|
4219
|
+
at: external_exports.string(),
|
|
4220
|
+
sha256: external_exports.string().regex(/^[0-9a-f]{64}$/)
|
|
4221
|
+
})
|
|
4222
|
+
]);
|
|
4223
|
+
var DraftedFile = external_exports.strictObject({
|
|
4224
|
+
schemaVersion: V,
|
|
4225
|
+
change: Id,
|
|
4226
|
+
/** By change-folder path: who wrote the file last. */
|
|
4227
|
+
files: external_exports.record(external_exports.string(), DraftedRecord).default({})
|
|
4228
|
+
});
|
|
4229
|
+
var CorrectionScope = external_exports.enum(["one", "similar"]);
|
|
4230
|
+
var CorrectionState = external_exports.enum(["waiting", "read", "applied"]);
|
|
4231
|
+
var Correction = external_exports.strictObject({
|
|
4232
|
+
id: external_exports.string().regex(/^c-[0-9a-f]{10}$/),
|
|
4233
|
+
/** The stage whose next run reads it: the one that drafted the file. */
|
|
4234
|
+
stage: Stage,
|
|
4235
|
+
/** The file, change-folder relative. */
|
|
4236
|
+
file: text(),
|
|
4237
|
+
/** The item in it (draftItems' id); left out, the whole file. */
|
|
4238
|
+
item: external_exports.string().optional(),
|
|
4239
|
+
/** What the person saw: the item's label. */
|
|
4240
|
+
label: text(),
|
|
4241
|
+
/** What the person says, in their words. */
|
|
4242
|
+
text: text().max(4e3),
|
|
4243
|
+
/** one: this item; similar: this item and every item like it. */
|
|
4244
|
+
scope: CorrectionScope,
|
|
4245
|
+
/** similar: the name the similar items share (a component, an entry, a table, a term). */
|
|
4246
|
+
like: external_exports.string().trim().min(1).optional(),
|
|
4247
|
+
by: text(),
|
|
4248
|
+
at: external_exports.string(),
|
|
4249
|
+
/**
|
|
4250
|
+
* waiting: no draft has read it yet; read: a draft had it and didn't change the item;
|
|
4251
|
+
* applied: a draft had it and changed the item. Kept on file either way.
|
|
4252
|
+
*/
|
|
4253
|
+
state: CorrectionState,
|
|
4254
|
+
/** The run of the draft that first read it, and that draft's number. */
|
|
4255
|
+
run: external_exports.string().optional(),
|
|
4256
|
+
iteration: external_exports.number().int().min(0).optional(),
|
|
4257
|
+
/** When the draft changed the item. */
|
|
4258
|
+
appliedAt: external_exports.string().optional(),
|
|
4259
|
+
/** similar: the other items a draft with it changed, as `<file>#<item>`. */
|
|
4260
|
+
touched: external_exports.array(external_exports.string()).optional()
|
|
4261
|
+
});
|
|
4262
|
+
var CorrectionsFile = external_exports.strictObject({
|
|
4263
|
+
schemaVersion: V,
|
|
4264
|
+
change: Id,
|
|
4265
|
+
corrections: external_exports.array(Correction).default([])
|
|
4266
|
+
});
|
|
4267
|
+
var sha2 = (s) => createHash2("sha256").update(s).digest("hex");
|
|
4268
|
+
var draftSha = (s) => sha2(s.replace(/\r\n?/g, "\n"));
|
|
4269
|
+
async function readYaml(file, schema, empty) {
|
|
4270
|
+
const t = await readFile6(file, "utf8").catch(() => void 0);
|
|
4271
|
+
if (t === void 0)
|
|
4272
|
+
return { data: empty, present: false };
|
|
4273
|
+
let raw;
|
|
4274
|
+
try {
|
|
4275
|
+
raw = (0, import_yaml7.parse)(t);
|
|
4276
|
+
} catch (e) {
|
|
4277
|
+
throw new PlannerError("invalid", `${file} doesn't read as YAML: ${e.message}`);
|
|
4278
|
+
}
|
|
4279
|
+
const r = schema.safeParse(raw);
|
|
4280
|
+
if (!r.success)
|
|
4281
|
+
throw new PlannerError("invalid", `${file} doesn't read: ${r.error.issues.map((i) => `${i.path.join(".") || "(top)"}: ${i.message}`).join("; ")}`);
|
|
4282
|
+
return { data: r.data, present: true };
|
|
4283
|
+
}
|
|
4284
|
+
async function readDrafted(dir, change) {
|
|
4285
|
+
return (await readYaml(join7(dir, DRAFTED_FILE), DraftedFile, {
|
|
4286
|
+
schemaVersion: DRAFTS_SCHEMA_VERSION,
|
|
4287
|
+
change,
|
|
4288
|
+
files: {}
|
|
4289
|
+
})).data;
|
|
4290
|
+
}
|
|
4291
|
+
async function writeDrafted(dir, d) {
|
|
4292
|
+
const files = Object.fromEntries(Object.entries(d.files).sort(([a], [b]) => a.localeCompare(b)));
|
|
4293
|
+
await writeFileAtomic(join7(dir, DRAFTED_FILE), `# Who wrote each stage draft last (ADR 0181): the AI, with its stage and run, or a person who saved it in the app. Atticus writes this file.
|
|
4294
|
+
${toCanonicalYaml(DraftedFile.parse({ ...d, files }), ["schemaVersion", "change", "files"])}`);
|
|
4295
|
+
}
|
|
4296
|
+
async function recordAiDraft(dir, change, o) {
|
|
4297
|
+
const d = await readDrafted(dir, change).catch(() => void 0);
|
|
4298
|
+
const out = d ?? { schemaVersion: DRAFTS_SCHEMA_VERSION, change, files: {} };
|
|
4299
|
+
let any = false;
|
|
4300
|
+
for (const f of o.files) {
|
|
4301
|
+
const t = await readFile6(join7(dir, ...f.split("/")), "utf8").catch(() => void 0);
|
|
4302
|
+
if (t === void 0)
|
|
4303
|
+
continue;
|
|
4304
|
+
const h = draftSha(t);
|
|
4305
|
+
const was = out.files[f];
|
|
4306
|
+
if (was?.by === "person" && was.sha256 === h)
|
|
4307
|
+
continue;
|
|
4308
|
+
if (was?.by === "ai" && was.sha256 === h && was.stage === o.stage)
|
|
4309
|
+
continue;
|
|
4310
|
+
out.files[f] = {
|
|
4311
|
+
by: "ai",
|
|
4312
|
+
stage: o.stage,
|
|
4313
|
+
...o.run ? { run: o.run } : {},
|
|
4314
|
+
iteration: o.iteration,
|
|
4315
|
+
at: o.at,
|
|
4316
|
+
sha256: h
|
|
4317
|
+
};
|
|
4318
|
+
any = true;
|
|
4319
|
+
}
|
|
4320
|
+
if (any)
|
|
4321
|
+
await writeDrafted(dir, out);
|
|
4322
|
+
}
|
|
4323
|
+
async function recordPersonSave(dir, change, o) {
|
|
4324
|
+
const d = await readDrafted(dir, change).catch(() => void 0) ?? {
|
|
4325
|
+
schemaVersion: DRAFTS_SCHEMA_VERSION,
|
|
4326
|
+
change,
|
|
4327
|
+
files: {}
|
|
4328
|
+
};
|
|
4329
|
+
d.files[o.file] = { by: "person", who: o.who, at: o.at, sha256: draftSha(o.content) };
|
|
4330
|
+
await writeDrafted(dir, d);
|
|
4331
|
+
}
|
|
4332
|
+
async function draftProvenance(dir, change, files, written = []) {
|
|
4333
|
+
const d = await readDrafted(dir, change).catch(() => void 0);
|
|
4334
|
+
const out = {};
|
|
4335
|
+
for (const f of files) {
|
|
4336
|
+
const t = await readFile6(join7(dir, ...f.split("/")), "utf8").catch(() => void 0);
|
|
4337
|
+
if (t === void 0)
|
|
4338
|
+
continue;
|
|
4339
|
+
const h = draftSha(t);
|
|
4340
|
+
const r = d?.files[f];
|
|
4341
|
+
if (r) {
|
|
4342
|
+
if (r.sha256 !== h)
|
|
4343
|
+
out[f] = { by: "person" };
|
|
4344
|
+
else if (r.by === "ai")
|
|
4345
|
+
out[f] = { by: "ai", stage: r.stage, ...r.run ? { run: r.run } : {}, at: r.at };
|
|
4346
|
+
else
|
|
4347
|
+
out[f] = { by: "person", who: r.who, at: r.at };
|
|
4348
|
+
continue;
|
|
4349
|
+
}
|
|
4350
|
+
const w = written.find((x) => x.hashes[f] !== void 0);
|
|
4351
|
+
if (w)
|
|
4352
|
+
out[f] = w.hashes[f] === h ? { by: "ai", stage: w.stage } : { by: "person" };
|
|
4353
|
+
}
|
|
4354
|
+
return out;
|
|
4355
|
+
}
|
|
4356
|
+
var DRAFT_ITEMS_MAX = 80;
|
|
4357
|
+
var slug2 = (s) => s.toLowerCase().replace(/[`*_]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "section";
|
|
4358
|
+
function markdownSpans(t) {
|
|
4359
|
+
const lines = t.replace(/\r\n?/g, "\n").split("\n");
|
|
4360
|
+
const heads = [];
|
|
4361
|
+
let fence = false;
|
|
4362
|
+
lines.forEach((l, i) => {
|
|
4363
|
+
if (/^\s*(```|~~~)/.test(l))
|
|
4364
|
+
fence = !fence;
|
|
4365
|
+
const m = !fence && /^(#{2,4})\s+(.+?)\s*#*\s*$/.exec(l);
|
|
4366
|
+
if (m)
|
|
4367
|
+
heads.push({ at: i, level: m[1].length, title: m[2] });
|
|
4368
|
+
});
|
|
4369
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4370
|
+
return heads.map((h, k) => {
|
|
4371
|
+
const end = heads.slice(k + 1).find((x) => x.level <= h.level)?.at ?? lines.length;
|
|
4372
|
+
const base = slug2(h.title);
|
|
4373
|
+
const n = (seen.get(base) ?? 0) + 1;
|
|
4374
|
+
seen.set(base, n);
|
|
4375
|
+
return {
|
|
4376
|
+
item: { id: n === 1 ? base : `${base}-${n}`, label: h.title.replace(/`/g, "") },
|
|
4377
|
+
text: lines.slice(h.at, end).join("\n").trim()
|
|
4378
|
+
};
|
|
4379
|
+
});
|
|
4380
|
+
}
|
|
4381
|
+
function taskSpans(t) {
|
|
4382
|
+
const out = [];
|
|
4383
|
+
for (const l of t.replace(/\r\n?/g, "\n").split("\n")) {
|
|
4384
|
+
const m = /^\s*- \[[ xX]\]\s+(\d+(?:\.\d+)*)\s+(.*)$/.exec(l);
|
|
4385
|
+
if (m)
|
|
4386
|
+
out.push({ item: { id: `task-${m[1]}`, label: `${m[1]} ${m[2]}` }, text: l.trim() });
|
|
4387
|
+
}
|
|
4388
|
+
return out;
|
|
4389
|
+
}
|
|
4390
|
+
function flowSpans(t) {
|
|
4391
|
+
let raw;
|
|
4392
|
+
try {
|
|
4393
|
+
raw = (0, import_yaml7.parse)(t);
|
|
4394
|
+
} catch {
|
|
4395
|
+
return [];
|
|
4396
|
+
}
|
|
4397
|
+
const steps = raw?.steps;
|
|
4398
|
+
if (!Array.isArray(steps))
|
|
4399
|
+
return [];
|
|
4400
|
+
return steps.map((s, i) => {
|
|
4401
|
+
const st = s ?? {};
|
|
4402
|
+
const like = [st.to?.component, st.to?.entry, st.store?.table].filter((x) => typeof x === "string" && x.trim() !== "");
|
|
4403
|
+
const head = [st.op, st.kind].filter(Boolean).join(" ");
|
|
4404
|
+
return {
|
|
4405
|
+
item: {
|
|
4406
|
+
id: `step-${i + 1}`,
|
|
4407
|
+
label: `Step ${i + 1}${head ? ` (${head})` : ""}${st.what ? `: ${st.what}` : ""}`,
|
|
4408
|
+
...like.length ? { like } : {}
|
|
4409
|
+
},
|
|
4410
|
+
text: JSON.stringify(s)
|
|
4411
|
+
};
|
|
4412
|
+
});
|
|
4413
|
+
}
|
|
4414
|
+
function spansOf(file, t) {
|
|
4415
|
+
if (/^target-flows\/[^/]+\.ya?ml$/.test(file))
|
|
4416
|
+
return flowSpans(t);
|
|
4417
|
+
if (file === "tasks.md")
|
|
4418
|
+
return taskSpans(t);
|
|
4419
|
+
if (file.endsWith(".md"))
|
|
4420
|
+
return markdownSpans(t);
|
|
4421
|
+
return [];
|
|
4422
|
+
}
|
|
4423
|
+
function draftItems(file, t) {
|
|
4424
|
+
return spansOf(file, t).slice(0, DRAFT_ITEMS_MAX).map((s) => s.item);
|
|
4425
|
+
}
|
|
4426
|
+
function draftItemText(file, t, item) {
|
|
4427
|
+
if (!item)
|
|
4428
|
+
return t;
|
|
4429
|
+
return spansOf(file, t).find((s) => s.item.id === item)?.text;
|
|
4430
|
+
}
|
|
4431
|
+
async function readCorrections(dir, change) {
|
|
4432
|
+
return (await readYaml(join7(dir, CORRECTIONS_FILE), CorrectionsFile, {
|
|
4433
|
+
schemaVersion: DRAFTS_SCHEMA_VERSION,
|
|
4434
|
+
change,
|
|
4435
|
+
corrections: []
|
|
4436
|
+
})).data.corrections;
|
|
4437
|
+
}
|
|
4438
|
+
async function writeCorrections(dir, change, cs) {
|
|
4439
|
+
await writeFileAtomic(join7(dir, CORRECTIONS_FILE), `# A person's corrections of AI-drafted items (ADR 0181): each stage's next run reads them as the person's word; Atticus marks what a draft applied. Atticus writes this file.
|
|
4440
|
+
${toCanonicalYaml(CorrectionsFile.parse({ schemaVersion: DRAFTS_SCHEMA_VERSION, change, corrections: cs }), ["schemaVersion", "change", "corrections"])}`);
|
|
4441
|
+
}
|
|
4442
|
+
async function addCorrection(dir, change, o) {
|
|
4443
|
+
const words5 = o.text.trim();
|
|
4444
|
+
if (!words5)
|
|
4445
|
+
throw new PlannerError("invalid", "Write the correction first.");
|
|
4446
|
+
if (words5.length > 4e3)
|
|
4447
|
+
throw new PlannerError("invalid", "A correction is at most 4000 characters.");
|
|
4448
|
+
const cs = await readCorrections(dir, change);
|
|
4449
|
+
const id = `c-${sha2([o.stage, o.file, o.item ?? "", words5, o.by, o.at].join("\n")).slice(0, 10)}`;
|
|
4450
|
+
if (cs.some((c2) => c2.id === id))
|
|
4451
|
+
return cs.find((c2) => c2.id === id);
|
|
4452
|
+
const like = o.like?.trim();
|
|
4453
|
+
const c = Correction.parse({
|
|
4454
|
+
id,
|
|
4455
|
+
stage: o.stage,
|
|
4456
|
+
file: o.file,
|
|
4457
|
+
...o.item ? { item: o.item } : {},
|
|
4458
|
+
label: o.label.trim() || o.file,
|
|
4459
|
+
text: words5,
|
|
4460
|
+
scope: o.similar ? "similar" : "one",
|
|
4461
|
+
...o.similar && like ? { like } : {},
|
|
4462
|
+
by: o.by,
|
|
4463
|
+
at: o.at,
|
|
4464
|
+
state: "waiting"
|
|
4465
|
+
});
|
|
4466
|
+
await writeCorrections(dir, change, [...cs, c]);
|
|
4467
|
+
return c;
|
|
4468
|
+
}
|
|
4469
|
+
async function removeCorrection(dir, change, id) {
|
|
4470
|
+
const cs = await readCorrections(dir, change);
|
|
4471
|
+
const c = cs.find((x) => x.id === id);
|
|
4472
|
+
if (!c)
|
|
4473
|
+
return false;
|
|
4474
|
+
if (c.state !== "waiting")
|
|
4475
|
+
throw new PlannerError("refused", `Correction ${id} was read by run ${c.run ?? "of the stage"}; it stays on file. Write a new correction to change it: a later correction wins.`);
|
|
4476
|
+
await writeCorrections(dir, change, cs.filter((x) => x.id !== id));
|
|
4477
|
+
return true;
|
|
4478
|
+
}
|
|
4479
|
+
var CORRECTIONS_RULE = `A person corrected items of the stage's drafts; the "hand-backs" data block lists each correction under "Corrections a person made", with the item and the person's words. Each one is the person's instruction, like an answer: follow it in this draft, and where it says to apply it to similar items, make the same correction to every other item that names the same thing. A later correction of an item wins over an earlier one. A correction never overrides a fact verified from code: where one contradicts a cited line, keep what the code says and ask a question naming the correction.`;
|
|
4480
|
+
function correctionsText(cs) {
|
|
4481
|
+
if (!cs.length)
|
|
4482
|
+
return void 0;
|
|
4483
|
+
return [
|
|
4484
|
+
"Corrections a person made (the person's instruction; follow each one):",
|
|
4485
|
+
...cs.map((c) => {
|
|
4486
|
+
const where2 = `${c.file}${c.item ? `, ${c.label}` : ""}`;
|
|
4487
|
+
const similar = c.scope === "similar" ? ` Apply the same correction to every similar item${c.like ? ` (every other step, story, section or task naming ${c.like})` : " (every other item like it)"}.` : "";
|
|
4488
|
+
return `- ${where2}: "${c.text}" (${c.by}, ${c.at.slice(0, 10)}).${similar}`;
|
|
4489
|
+
})
|
|
4490
|
+
].join("\n");
|
|
4491
|
+
}
|
|
4492
|
+
async function stageCorrections(dir, change, stage) {
|
|
4493
|
+
const all2 = (await readCorrections(dir, change).catch(() => [])).filter((c) => c.stage === stage).sort((a, b) => a.at < b.at ? -1 : a.at > b.at ? 1 : 0);
|
|
4494
|
+
return { all: all2, waiting: all2.filter((c) => c.state === "waiting") };
|
|
4495
|
+
}
|
|
4496
|
+
async function snapshotFiles(dir, files) {
|
|
4497
|
+
const out = /* @__PURE__ */ new Map();
|
|
4498
|
+
for (const f of [...new Set(files)]) {
|
|
4499
|
+
const t = await readFile6(join7(dir, ...f.split("/")), "utf8").catch(() => void 0);
|
|
4500
|
+
if (t !== void 0)
|
|
4501
|
+
out.set(f, t);
|
|
4502
|
+
}
|
|
4503
|
+
return out;
|
|
4504
|
+
}
|
|
4505
|
+
async function settleCorrections(dir, change, o) {
|
|
4506
|
+
const cs = await readCorrections(dir, change).catch(() => void 0);
|
|
4507
|
+
if (!cs?.length)
|
|
4508
|
+
return [];
|
|
4509
|
+
const after2 = await snapshotFiles(dir, [...o.files, ...o.before.keys()]);
|
|
4510
|
+
const settled = [];
|
|
4511
|
+
const changedItems = (likeOf) => {
|
|
4512
|
+
const out = [];
|
|
4513
|
+
for (const f of [.../* @__PURE__ */ new Set([...o.before.keys(), ...after2.keys()])].sort()) {
|
|
4514
|
+
const was = new Map(spansOf(f, o.before.get(f) ?? "").map((s) => [s.item.id, s.text]));
|
|
4515
|
+
for (const s of spansOf(f, after2.get(f) ?? ""))
|
|
4516
|
+
if (was.get(s.item.id) !== s.text && likeOf(s))
|
|
4517
|
+
out.push(`${f}#${s.item.id}`);
|
|
4518
|
+
}
|
|
4519
|
+
return out;
|
|
4520
|
+
};
|
|
4521
|
+
for (const c of cs) {
|
|
4522
|
+
if (c.stage !== o.stage || c.state === "applied")
|
|
4523
|
+
continue;
|
|
4524
|
+
const before = o.before.has(c.file) ? draftItemText(c.file, o.before.get(c.file), c.item) : void 0;
|
|
4525
|
+
const now = after2.has(c.file) ? draftItemText(c.file, after2.get(c.file), c.item) : void 0;
|
|
4526
|
+
if (c.state === "waiting") {
|
|
4527
|
+
if (o.run)
|
|
4528
|
+
c.run = o.run;
|
|
4529
|
+
c.iteration = o.iteration;
|
|
4530
|
+
}
|
|
4531
|
+
if (before !== now) {
|
|
4532
|
+
c.state = "applied";
|
|
4533
|
+
c.appliedAt = o.at;
|
|
4534
|
+
} else
|
|
4535
|
+
c.state = "read";
|
|
4536
|
+
if (c.scope === "similar") {
|
|
4537
|
+
const like = c.like?.toLowerCase();
|
|
4538
|
+
const mine = `${c.file}#${c.item ?? ""}`;
|
|
4539
|
+
const touched = changedItems((s) => !like || s.text.toLowerCase().includes(like) || s.item.label.toLowerCase().includes(like) || (s.item.like ?? []).some((x) => x.toLowerCase() === like)).filter((x) => x !== mine);
|
|
4540
|
+
if (touched.length)
|
|
4541
|
+
c.touched = [.../* @__PURE__ */ new Set([...c.touched ?? [], ...touched])].sort();
|
|
4542
|
+
}
|
|
4543
|
+
settled.push(c);
|
|
4544
|
+
}
|
|
4545
|
+
if (settled.length)
|
|
4546
|
+
await writeCorrections(dir, change, cs);
|
|
4547
|
+
return settled;
|
|
4548
|
+
}
|
|
4549
|
+
|
|
4039
4550
|
// packages/planner/dist/gaps.js
|
|
4040
|
-
import { readdir as readdir5, readFile as
|
|
4041
|
-
import { join as
|
|
4042
|
-
var
|
|
4551
|
+
import { readdir as readdir5, readFile as readFile8 } from "node:fs/promises";
|
|
4552
|
+
import { join as join9 } from "node:path";
|
|
4553
|
+
var import_yaml9 = __toESM(require_dist(), 1);
|
|
4043
4554
|
|
|
4044
4555
|
// packages/planner/dist/stages/expand.js
|
|
4045
|
-
import { createHash as
|
|
4556
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
4046
4557
|
|
|
4047
4558
|
// packages/planner/dist/stages/judged.js
|
|
4048
|
-
var
|
|
4049
|
-
import { mkdir as mkdir2, readFile as
|
|
4050
|
-
import { dirname as dirname2, join as
|
|
4559
|
+
var import_yaml8 = __toESM(require_dist(), 1);
|
|
4560
|
+
import { mkdir as mkdir2, readFile as readFile7, rm as rm2, writeFile } from "node:fs/promises";
|
|
4561
|
+
import { dirname as dirname2, join as join8 } from "node:path";
|
|
4051
4562
|
function filePending(root) {
|
|
4052
|
-
const file = (kind, key3) =>
|
|
4563
|
+
const file = (kind, key3) => join8(stateDirOf(root), ".index", "pending-judgments", kind, `${key3}.txt`);
|
|
4053
4564
|
return {
|
|
4054
|
-
get: async (kind, key3) => (await
|
|
4565
|
+
get: async (kind, key3) => (await readFile7(file(kind, key3), "utf8").catch(() => void 0))?.trim() || void 0,
|
|
4055
4566
|
set: async (kind, key3, runId) => {
|
|
4056
4567
|
const f = file(kind, key3);
|
|
4057
4568
|
if (runId === void 0)
|
|
@@ -4138,10 +4649,10 @@ async function judged(o) {
|
|
|
4138
4649
|
});
|
|
4139
4650
|
return finish(await o.runtime.run(prepared.runId), false);
|
|
4140
4651
|
}
|
|
4141
|
-
function yamlOf(
|
|
4142
|
-
const fenced = /```(?:ya?ml)?\s*\n([\s\S]*?)```/.exec(
|
|
4652
|
+
function yamlOf(text4) {
|
|
4653
|
+
const fenced = /```(?:ya?ml)?\s*\n([\s\S]*?)```/.exec(text4);
|
|
4143
4654
|
try {
|
|
4144
|
-
return (0,
|
|
4655
|
+
return (0, import_yaml8.parse)(fenced ? fenced[1] : text4);
|
|
4145
4656
|
} catch {
|
|
4146
4657
|
return void 0;
|
|
4147
4658
|
}
|
|
@@ -4172,8 +4683,17 @@ var Answer = external_exports.object({
|
|
|
4172
4683
|
})).default([])
|
|
4173
4684
|
});
|
|
4174
4685
|
var fold = (s) => s.replace(/\s+/g, " ").trim().toLowerCase();
|
|
4175
|
-
function
|
|
4176
|
-
|
|
4686
|
+
function titledText(title, text4) {
|
|
4687
|
+
if (!title)
|
|
4688
|
+
return text4;
|
|
4689
|
+
const first = text4.replace(/^\uFEFF/, "").trimStart().split(/\r?\n/, 1)[0].trim();
|
|
4690
|
+
const heading = /^#\s+(.*?)\s*#*$/.exec(first)?.[1]?.trim();
|
|
4691
|
+
return heading === title.trim() ? text4 : `# ${title}
|
|
4692
|
+
|
|
4693
|
+
${text4}`;
|
|
4694
|
+
}
|
|
4695
|
+
function checkExpansion(text4, input, ix) {
|
|
4696
|
+
const r = Answer.safeParse(yamlOf(text4));
|
|
4177
4697
|
if (!r.success)
|
|
4178
4698
|
return { problem: "the expansion isn't YAML with terms and names" };
|
|
4179
4699
|
const all2 = fold(`${input.title ?? ""}
|
|
@@ -4255,7 +4775,7 @@ async function expandFor(root, change, input, o) {
|
|
|
4255
4775
|
}
|
|
4256
4776
|
try {
|
|
4257
4777
|
const digest = digestSources(ix);
|
|
4258
|
-
const names =
|
|
4778
|
+
const names = createHash3("sha256").update(digest.map((d) => d.text).join("\n\n")).digest("hex");
|
|
4259
4779
|
const r = await judged({
|
|
4260
4780
|
kind: "expansion",
|
|
4261
4781
|
inputs: { input, names, runtime: o.runtime.kind, v: 2 },
|
|
@@ -4277,9 +4797,7 @@ async function expandFor(root, change, input, o) {
|
|
|
4277
4797
|
id: "input",
|
|
4278
4798
|
kind: "document",
|
|
4279
4799
|
origin: "the step's input",
|
|
4280
|
-
text:
|
|
4281
|
-
|
|
4282
|
-
` : ""}${input.text}`,
|
|
4800
|
+
text: titledText(input.title, input.text),
|
|
4283
4801
|
classification: "internal",
|
|
4284
4802
|
priority: 50
|
|
4285
4803
|
},
|
|
@@ -4292,7 +4810,7 @@ async function expandFor(root, change, input, o) {
|
|
|
4292
4810
|
store: o.store,
|
|
4293
4811
|
pending: filePending(root),
|
|
4294
4812
|
step: `${o.step} (expansion)`,
|
|
4295
|
-
read: (
|
|
4813
|
+
read: (text4) => checkExpansion(text4, input, ix)
|
|
4296
4814
|
});
|
|
4297
4815
|
if ("waiting" in r)
|
|
4298
4816
|
return r;
|
|
@@ -4329,8 +4847,8 @@ List only candidates from the list, each once.`;
|
|
|
4329
4847
|
function candidatesOf(s) {
|
|
4330
4848
|
return [...s.picked.map((p) => ({ kind: p.kind, id: p.id })), ...s.leftOut];
|
|
4331
4849
|
}
|
|
4332
|
-
function checkRerank(
|
|
4333
|
-
const r = Answer2.safeParse(yamlOf(
|
|
4850
|
+
function checkRerank(text4, shown) {
|
|
4851
|
+
const r = Answer2.safeParse(yamlOf(text4));
|
|
4334
4852
|
if (!r.success)
|
|
4335
4853
|
return { problem: "the rerank isn't YAML with keep" };
|
|
4336
4854
|
const set = new Set(shown);
|
|
@@ -4371,9 +4889,7 @@ async function rerankFor(root, change, input, selection, o) {
|
|
|
4371
4889
|
id: "input",
|
|
4372
4890
|
kind: "document",
|
|
4373
4891
|
origin: "the step's input",
|
|
4374
|
-
text:
|
|
4375
|
-
|
|
4376
|
-
` : ""}${input.text}`,
|
|
4892
|
+
text: titledText(input.title, input.text),
|
|
4377
4893
|
classification: "internal",
|
|
4378
4894
|
priority: 50
|
|
4379
4895
|
},
|
|
@@ -4394,7 +4910,7 @@ ${lines.join("\n")}`,
|
|
|
4394
4910
|
store: o.store,
|
|
4395
4911
|
pending: filePending(root),
|
|
4396
4912
|
step: `${o.step} (rerank)`,
|
|
4397
|
-
read: (
|
|
4913
|
+
read: (text4) => checkRerank(text4, shown)
|
|
4398
4914
|
});
|
|
4399
4915
|
if ("waiting" in r)
|
|
4400
4916
|
return r;
|
|
@@ -4489,11 +5005,11 @@ var wholeLists = () => ({
|
|
|
4489
5005
|
leftOut: [],
|
|
4490
5006
|
unknown: []
|
|
4491
5007
|
});
|
|
4492
|
-
var instructionSource = (
|
|
5008
|
+
var instructionSource = (text4) => ({
|
|
4493
5009
|
id: "instructions",
|
|
4494
5010
|
kind: "instruction",
|
|
4495
5011
|
origin: "atticus",
|
|
4496
|
-
text:
|
|
5012
|
+
text: text4,
|
|
4497
5013
|
classification: "public",
|
|
4498
5014
|
trusted: true
|
|
4499
5015
|
});
|
|
@@ -4564,7 +5080,7 @@ function oneSource(s, o) {
|
|
|
4564
5080
|
}
|
|
4565
5081
|
}
|
|
4566
5082
|
const left = s.leftOut.filter((l) => keep(l.kind)).map((l) => l.id);
|
|
4567
|
-
const
|
|
5083
|
+
const text4 = [
|
|
4568
5084
|
...o.heading ? [o.heading, ""] : [],
|
|
4569
5085
|
...parts.length ? parts : [o.empty ?? "Nothing in the index matches."],
|
|
4570
5086
|
...left.length ? [
|
|
@@ -4576,7 +5092,7 @@ function oneSource(s, o) {
|
|
|
4576
5092
|
id: o.id,
|
|
4577
5093
|
kind: o.kind,
|
|
4578
5094
|
origin: o.origin,
|
|
4579
|
-
text:
|
|
5095
|
+
text: text4,
|
|
4580
5096
|
classification: "internal",
|
|
4581
5097
|
priority: o.priority
|
|
4582
5098
|
};
|
|
@@ -4595,18 +5111,18 @@ function asSources(s, kind) {
|
|
|
4595
5111
|
// packages/planner/dist/gaps.js
|
|
4596
5112
|
var TARGET_DIR = "target-flows";
|
|
4597
5113
|
async function changeTexts(root, change) {
|
|
4598
|
-
const dir =
|
|
5114
|
+
const dir = join9(changesDirOf(root), change);
|
|
4599
5115
|
const out = [];
|
|
4600
5116
|
const top = (await readdir5(dir).catch(() => [])).filter((f) => /\.md$/.test(f)).sort();
|
|
4601
5117
|
for (const f of ["brief.md", ...top.filter((x) => x !== "brief.md")]) {
|
|
4602
|
-
const t = await
|
|
5118
|
+
const t = await readFile8(join9(dir, f), "utf8").catch(() => void 0);
|
|
4603
5119
|
if (t !== void 0)
|
|
4604
5120
|
out.push([f, t]);
|
|
4605
5121
|
}
|
|
4606
5122
|
for (const sub of ["stories", "target-flows"])
|
|
4607
|
-
for (const f of (await readdir5(
|
|
5123
|
+
for (const f of (await readdir5(join9(dir, sub)).catch(() => [])).sort())
|
|
4608
5124
|
if (/\.(md|ya?ml)$/.test(f)) {
|
|
4609
|
-
const t = await
|
|
5125
|
+
const t = await readFile8(join9(dir, sub, f), "utf8").catch(() => void 0);
|
|
4610
5126
|
if (t !== void 0)
|
|
4611
5127
|
out.push([`${sub}/${f}`, t]);
|
|
4612
5128
|
}
|
|
@@ -4637,9 +5153,9 @@ async function gapsOf(root, change, unknown) {
|
|
|
4637
5153
|
return out;
|
|
4638
5154
|
}
|
|
4639
5155
|
async function codeReaders() {
|
|
4640
|
-
const x = await import("./chunk-
|
|
5156
|
+
const x = await import("./chunk-UEVZZZU2.js");
|
|
4641
5157
|
return {
|
|
4642
|
-
outline: (path,
|
|
5158
|
+
outline: (path, text4) => x.declarationOutline(path, text4),
|
|
4643
5159
|
outlines: (path) => x.syntaxLanguageOf(path) !== void 0
|
|
4644
5160
|
};
|
|
4645
5161
|
}
|
|
@@ -4658,24 +5174,24 @@ var sameName = (a, b) => {
|
|
|
4658
5174
|
};
|
|
4659
5175
|
async function ownNames(root, change) {
|
|
4660
5176
|
const rel = `${changesRelOf(root)}/${change}/${TARGET_DIR}`;
|
|
4661
|
-
const dir =
|
|
5177
|
+
const dir = join9(changesDirOf(root), change, TARGET_DIR);
|
|
4662
5178
|
const out = [];
|
|
4663
5179
|
for (const f of (await readdir5(dir).catch(() => [])).sort()) {
|
|
4664
5180
|
if (!/\.ya?ml$/.test(f))
|
|
4665
5181
|
continue;
|
|
4666
|
-
const
|
|
4667
|
-
if (
|
|
5182
|
+
const text4 = await readFile8(join9(dir, f), "utf8").catch(() => void 0);
|
|
5183
|
+
if (text4 === void 0)
|
|
4668
5184
|
continue;
|
|
4669
5185
|
let d;
|
|
4670
5186
|
try {
|
|
4671
|
-
const r = FlowDelta.safeParse((0,
|
|
5187
|
+
const r = FlowDelta.safeParse((0, import_yaml9.parse)(text4));
|
|
4672
5188
|
if (!r.success)
|
|
4673
5189
|
continue;
|
|
4674
5190
|
d = r.data;
|
|
4675
5191
|
} catch {
|
|
4676
5192
|
continue;
|
|
4677
5193
|
}
|
|
4678
|
-
const lines =
|
|
5194
|
+
const lines = text4.split(/\r?\n/);
|
|
4679
5195
|
const add = (name, key3) => {
|
|
4680
5196
|
if (!name?.trim())
|
|
4681
5197
|
return;
|
|
@@ -4853,7 +5369,7 @@ async function gapAnswers(root, change) {
|
|
|
4853
5369
|
origin: "questions/ (answered gaps)",
|
|
4854
5370
|
text: [
|
|
4855
5371
|
"People said what these names are; their answers are asserted knowledge (who and when), not read from code:",
|
|
4856
|
-
...answered.map((q2) => `- ${gapNameOf(q2)}: ${answerText(q2) ?? ""} (${q2.answer.by}, ${q2.answer.at}, question ${q2.id})`)
|
|
5372
|
+
...answered.map((q2) => `- ${gapNameOf(q2)}: ${answerText(q2) ?? ""} (${answerByWords(q2.answer.by)}, ${q2.answer.at}, question ${q2.id})`)
|
|
4857
5373
|
].join("\n"),
|
|
4858
5374
|
classification: "internal",
|
|
4859
5375
|
priority: 96
|
|
@@ -4863,12 +5379,12 @@ async function gapAnswers(root, change) {
|
|
|
4863
5379
|
function reliedOn(gaps, answer) {
|
|
4864
5380
|
let data;
|
|
4865
5381
|
try {
|
|
4866
|
-
data = (0,
|
|
5382
|
+
data = (0, import_yaml9.parse)(unfence(answer));
|
|
4867
5383
|
} catch {
|
|
4868
5384
|
return { relied: [], unknown: [] };
|
|
4869
5385
|
}
|
|
4870
|
-
const
|
|
4871
|
-
const names = Array.isArray(
|
|
5386
|
+
const listed2 = data?.relies_on_gaps;
|
|
5387
|
+
const names = Array.isArray(listed2) ? listed2.filter((x) => typeof x === "string" && x.trim() !== "") : [];
|
|
4872
5388
|
const relied = [];
|
|
4873
5389
|
const unknown = [];
|
|
4874
5390
|
for (const n of names) {
|
|
@@ -4903,7 +5419,7 @@ async function askGaps(root, change, gaps, answer, by) {
|
|
|
4903
5419
|
}
|
|
4904
5420
|
|
|
4905
5421
|
// packages/planner/dist/stages/finding-kind.js
|
|
4906
|
-
import { join as
|
|
5422
|
+
import { join as join10 } from "node:path";
|
|
4907
5423
|
import { readdir as readdir6 } from "node:fs/promises";
|
|
4908
5424
|
var DRAFTING_CHECKS = /* @__PURE__ */ new Set([
|
|
4909
5425
|
"story-has-acceptance-criteria",
|
|
@@ -4979,17 +5495,17 @@ async function criticRuleChecks(root) {
|
|
|
4979
5495
|
};
|
|
4980
5496
|
add((await loadConstitution()).rules);
|
|
4981
5497
|
const org = orgPackDirOf(root);
|
|
4982
|
-
for (const p of (await readPackFiles(root,
|
|
5498
|
+
for (const p of (await readPackFiles(root, join10(org, "checklists"))).packs)
|
|
4983
5499
|
add(p.pack.rules);
|
|
4984
|
-
const teams = await readdir6(
|
|
5500
|
+
const teams = await readdir6(join10(org, "teams"), { withFileTypes: true }).catch(() => []);
|
|
4985
5501
|
for (const t of teams.filter((x) => x.isDirectory()).sort((a, b) => a.name.localeCompare(b.name)))
|
|
4986
|
-
for (const p of (await readPackFiles(root,
|
|
5502
|
+
for (const p of (await readPackFiles(root, join10(org, "teams", t.name, "checklists"))).packs)
|
|
4987
5503
|
add(p.pack.rules);
|
|
4988
5504
|
return out;
|
|
4989
5505
|
}
|
|
4990
5506
|
|
|
4991
5507
|
// packages/planner/dist/stages/loop.js
|
|
4992
|
-
var issueFingerprint = (rule, target, evidence = "") =>
|
|
5508
|
+
var issueFingerprint = (rule, target, evidence = "") => createHash4("sha256").update([rule, target, evidence].join("\n")).digest("hex").slice(0, 12);
|
|
4993
5509
|
var loopRel = (stage) => `stage/${stage}.yaml`;
|
|
4994
5510
|
var STAGE_WORDS = {
|
|
4995
5511
|
feature: "feature placement",
|
|
@@ -4998,9 +5514,9 @@ var STAGE_WORDS = {
|
|
|
4998
5514
|
small: "small change's plan"
|
|
4999
5515
|
};
|
|
5000
5516
|
async function readLoop(dir, change, stage) {
|
|
5001
|
-
const
|
|
5002
|
-
if (
|
|
5003
|
-
const r = StageLoop.safeParse((0,
|
|
5517
|
+
const text4 = await readFile9(join11(dir, loopRel(stage)), "utf8").catch(() => void 0);
|
|
5518
|
+
if (text4 !== void 0) {
|
|
5519
|
+
const r = StageLoop.safeParse((0, import_yaml10.parse)(text4));
|
|
5004
5520
|
if (r.success)
|
|
5005
5521
|
return r.data;
|
|
5006
5522
|
}
|
|
@@ -5014,12 +5530,12 @@ async function readLoop(dir, change, stage) {
|
|
|
5014
5530
|
});
|
|
5015
5531
|
}
|
|
5016
5532
|
async function readStageLoop(dir, change, stage) {
|
|
5017
|
-
const present = await stat(
|
|
5533
|
+
const present = await stat(join11(dir, loopRel(stage))).then((s) => s.isFile(), () => false);
|
|
5018
5534
|
return { loop: await readLoop(dir, change, stage), present, file: loopRel(stage) };
|
|
5019
5535
|
}
|
|
5020
5536
|
var writeStageLoop = (dir, l) => writeLoop(dir, l);
|
|
5021
5537
|
async function writeLoop(dir, l) {
|
|
5022
|
-
await writeFileAtomic(
|
|
5538
|
+
await writeFileAtomic(join11(dir, loopRel(l.stage)), `# The ${l.stage} stage's loop (ADR 0087): drafts, the findings each one had, and the questions it raised. Atticus writes this file.
|
|
5023
5539
|
${toCanonicalYaml(StageLoop.parse(l), ["schemaVersion", "change", "stage", "iteration", "state", "updatedAt", "run"])}`);
|
|
5024
5540
|
}
|
|
5025
5541
|
function stageBudget(ctx, agent) {
|
|
@@ -5058,7 +5574,7 @@ async function refuseWhileEarlierStuck(root, ctx, change, stage) {
|
|
|
5058
5574
|
for (const earlier of path.slice(0, at2)) {
|
|
5059
5575
|
if (change.handoffs.some((h) => h.phase === earlier.phase && h.state === "merged"))
|
|
5060
5576
|
continue;
|
|
5061
|
-
const ran = await
|
|
5577
|
+
const ran = await readFile9(join11(dir, ...earlier.loop.split("/"))).then(() => true, () => false);
|
|
5062
5578
|
if (!ran)
|
|
5063
5579
|
continue;
|
|
5064
5580
|
const gate = phaseGates(ctx, earlier.phase)[0];
|
|
@@ -5162,8 +5678,10 @@ async function runStageLoop(root, changeId, spec, o) {
|
|
|
5162
5678
|
loop.run = w.runId;
|
|
5163
5679
|
loop.updatedAt = at2;
|
|
5164
5680
|
await writeLoop(dir, loop);
|
|
5165
|
-
return result("waiting", { ...extra, waiting:
|
|
5681
|
+
return result("waiting", { ...extra, waiting: waitingOf(w) });
|
|
5166
5682
|
};
|
|
5683
|
+
if (await retireSettledEscalations(root, change.id, loop, at2))
|
|
5684
|
+
await writeLoop(dir, loop);
|
|
5167
5685
|
if (!o.resume && loop.state === "questions") {
|
|
5168
5686
|
const open = (await questionsOf(loop.questions)).filter((q2) => q2.state === "open");
|
|
5169
5687
|
if (open.length)
|
|
@@ -5191,11 +5709,36 @@ async function runStageLoop(root, changeId, spec, o) {
|
|
|
5191
5709
|
...spec.sources,
|
|
5192
5710
|
...a !== void 0 && spec.reviewSources ? spec.reviewSources(a) : []
|
|
5193
5711
|
];
|
|
5194
|
-
const
|
|
5712
|
+
const corrections = await stageCorrections(dir, change.id, spec.stage).catch(() => ({
|
|
5713
|
+
all: [],
|
|
5714
|
+
waiting: []
|
|
5715
|
+
}));
|
|
5716
|
+
const write3 = async (a, drafted) => {
|
|
5195
5717
|
const before = await hashFiles(dir, [
|
|
5196
5718
|
.../* @__PURE__ */ new Set([...Object.keys(loop.written ?? {}), ...await sourceFiles(dir, spec.source)])
|
|
5197
5719
|
]);
|
|
5720
|
+
const texts2 = drafted && corrections.all.length ? await snapshotFiles(dir, [...Object.keys(before), ...corrections.all.map((c) => c.file)]) : void 0;
|
|
5198
5721
|
const files = await spec.write(a);
|
|
5722
|
+
if (drafted) {
|
|
5723
|
+
const draft = {
|
|
5724
|
+
stage: spec.stage,
|
|
5725
|
+
...drafted.run ? { run: drafted.run } : {},
|
|
5726
|
+
iteration: loop.iteration,
|
|
5727
|
+
at: at2
|
|
5728
|
+
};
|
|
5729
|
+
await recordAiDraft(dir, change.id, { ...draft, files }).catch(() => void 0);
|
|
5730
|
+
if (texts2) {
|
|
5731
|
+
const settled = await settleCorrections(dir, change.id, {
|
|
5732
|
+
...draft,
|
|
5733
|
+
before: texts2,
|
|
5734
|
+
files
|
|
5735
|
+
}).catch(() => []);
|
|
5736
|
+
const n = settled.length;
|
|
5737
|
+
const applied = settled.filter((c) => c.state === "applied").length;
|
|
5738
|
+
if (n)
|
|
5739
|
+
notes.push(`The draft had ${n === 1 ? "a person's correction" : `${n} corrections people made`} (stage/corrections.yaml); ${n === 1 ? applied ? "it changed the item" : "it didn't change the item" : `it changed the items of ${applied} of them`}.`);
|
|
5740
|
+
}
|
|
5741
|
+
}
|
|
5199
5742
|
loop.source = spec.source;
|
|
5200
5743
|
loop.written = await hashFiles(dir, [
|
|
5201
5744
|
.../* @__PURE__ */ new Set([...files, ...await sourceFiles(dir, spec.source)])
|
|
@@ -5205,7 +5748,7 @@ async function runStageLoop(root, changeId, spec, o) {
|
|
|
5205
5748
|
changed.add(f);
|
|
5206
5749
|
if (loop.failedAnswer) {
|
|
5207
5750
|
if (loop.failedAnswer !== spec.source)
|
|
5208
|
-
await rm3(
|
|
5751
|
+
await rm3(join11(dir, ...loop.failedAnswer.split("/")), { force: true });
|
|
5209
5752
|
delete loop.failedAnswer;
|
|
5210
5753
|
}
|
|
5211
5754
|
return files;
|
|
@@ -5225,18 +5768,22 @@ async function runStageLoop(root, changeId, spec, o) {
|
|
|
5225
5768
|
let carried;
|
|
5226
5769
|
let reviewAgain = false;
|
|
5227
5770
|
if (!o.resume && loop.state === "waiting" && loop.run) {
|
|
5228
|
-
const run = await
|
|
5771
|
+
const run = await readFile9(join11(runsDirOf(root), change.id, loop.run, "run.json"), "utf8").then((t) => {
|
|
5229
5772
|
try {
|
|
5230
5773
|
return JSON.parse(t).state ?? "unreadable";
|
|
5231
5774
|
} catch {
|
|
5232
5775
|
return "unreadable";
|
|
5233
5776
|
}
|
|
5234
5777
|
}, (e) => e.code === "ENOENT" ? void 0 : "unreadable").catch(() => void 0);
|
|
5235
|
-
const
|
|
5778
|
+
const metaLenses = loop.review ? (await readRoundMeta(root, change.id, loop.review.gate, loop.review.round))?.lenses ?? [] : [];
|
|
5779
|
+
const batched = metaLenses.filter((l) => l.batch === loop.run && l.state === "waiting").map((l) => l.lens);
|
|
5780
|
+
const lens = loop.review ? metaLenses.find((l) => l.runId === loop.run)?.lens ?? (batched.length ? listed(batched) : "AI") : void 0;
|
|
5236
5781
|
const out = ["waiting", "prepared", "produced", "unreadable", "failed"];
|
|
5237
5782
|
if (run !== void 0 && out.includes(run)) {
|
|
5238
|
-
notes.push(lens ? `The ${lens} lens's review of the draft is still with your assistant (run ${loop.run}).` : `The draft is still with your assistant (run ${loop.run}).`);
|
|
5239
|
-
return result("waiting", {
|
|
5783
|
+
notes.push(batched.length ? `The critic's ${lens} ${batched.length === 1 ? "lens's" : "lenses'"} review of the draft is still with your assistant (run ${loop.run}).` : lens ? `The ${lens} lens's review of the draft is still with your assistant (run ${loop.run}).` : `The draft is still with your assistant (run ${loop.run}).`);
|
|
5784
|
+
return result("waiting", {
|
|
5785
|
+
waiting: batched.length ? { runId: loop.run, lenses: batched } : { runId: loop.run, ...lens ? { lens } : {} }
|
|
5786
|
+
});
|
|
5240
5787
|
}
|
|
5241
5788
|
if (lens) {
|
|
5242
5789
|
reviewAgain = true;
|
|
@@ -5253,7 +5800,7 @@ async function runStageLoop(root, changeId, spec, o) {
|
|
|
5253
5800
|
const edited = !o.resume && !carried && loop.state !== "waiting" && loop.iteration > 0 ? await editedFiles(dir, loop, spec.source) : [];
|
|
5254
5801
|
if (edited.length) {
|
|
5255
5802
|
const kept = await keepFiles(dir, rel, spec.stage, loop.iteration + 1, edited);
|
|
5256
|
-
if (o.force || unreadAnswers.length) {
|
|
5803
|
+
if (o.force || unreadAnswers.length || corrections.waiting.length) {
|
|
5257
5804
|
personDraft = await draftText(dir, edited);
|
|
5258
5805
|
(loop.byPerson ??= []).push({
|
|
5259
5806
|
iteration: loop.iteration + 1,
|
|
@@ -5262,7 +5809,7 @@ async function runStageLoop(root, changeId, spec, o) {
|
|
|
5262
5809
|
kept,
|
|
5263
5810
|
how: "redrafted"
|
|
5264
5811
|
});
|
|
5265
|
-
notes.push(`A person edited ${edited.join(", ")}; a copy is in ${kept}, and your assistant drafts again from those files${o.force ? " (--force)" : ", with the answers"}.`);
|
|
5812
|
+
notes.push(`A person edited ${edited.join(", ")}; a copy is in ${kept}, and your assistant drafts again from those files${o.force ? " (--force)" : unreadAnswers.length ? ", with the answers" : ", with the corrections"}.`);
|
|
5266
5813
|
} else {
|
|
5267
5814
|
loop.iteration++;
|
|
5268
5815
|
(loop.byPerson ??= []).push({
|
|
@@ -5276,9 +5823,9 @@ async function runStageLoop(root, changeId, spec, o) {
|
|
|
5276
5823
|
let problems = await spec.currentProblems?.() ?? [];
|
|
5277
5824
|
const answerFile = loop.failedAnswer && edited.includes(loop.failedAnswer) ? loop.failedAnswer : cur === void 0 && !spec.source.endsWith("/") && edited.includes(spec.source) ? spec.source : void 0;
|
|
5278
5825
|
if (answerFile) {
|
|
5279
|
-
const
|
|
5280
|
-
if (
|
|
5281
|
-
const p = spec.parse(
|
|
5826
|
+
const text4 = await readFile9(join11(dir, ...answerFile.split("/")), "utf8").catch(() => void 0);
|
|
5827
|
+
if (text4 !== void 0) {
|
|
5828
|
+
const p = spec.parse(text4);
|
|
5282
5829
|
const bad = p.issues.filter((i) => i.blocking);
|
|
5283
5830
|
if (p.answer !== void 0 && !bad.length) {
|
|
5284
5831
|
cur = p.answer;
|
|
@@ -5323,7 +5870,7 @@ async function runStageLoop(root, changeId, spec, o) {
|
|
|
5323
5870
|
})) : [];
|
|
5324
5871
|
const answers = await questionsOf(loop.questions);
|
|
5325
5872
|
const unread = answers.filter((q2) => q2.state === "answered" && !loop.read.includes(q2.id));
|
|
5326
|
-
if (!o.resume && !o.force && loop.state === "done" && unread.length === 0) {
|
|
5873
|
+
if (!o.resume && !o.force && loop.state === "done" && unread.length === 0 && corrections.waiting.length === 0) {
|
|
5327
5874
|
const cur = await spec.current();
|
|
5328
5875
|
if (cur !== void 0) {
|
|
5329
5876
|
issues = (await spec.check(cur, { onFile: true })).filter((i) => i.blocking);
|
|
@@ -5430,15 +5977,15 @@ async function runStageLoop(root, changeId, spec, o) {
|
|
|
5430
5977
|
const risks = issues.filter((i) => !i.blocking && i.risk);
|
|
5431
5978
|
if (risks.length)
|
|
5432
5979
|
loop.handedBack = [.../* @__PURE__ */ new Set([...loop.handedBack ?? [], ...risks.map(riskKey)])].sort();
|
|
5433
|
-
const back = handBacks(fix, answered, risks);
|
|
5434
|
-
const last = (fix.length || risks.length) && !personDraft && (await sourceFiles(dir, spec.source)).length ? `${rel}/${spec.source}` : void 0;
|
|
5980
|
+
const back = handBacks(fix, answered, risks, corrections.all);
|
|
5981
|
+
const last = (fix.length || risks.length || corrections.all.length) && !personDraft && (await sourceFiles(dir, spec.source)).length ? `${rel}/${spec.source}` : void 0;
|
|
5435
5982
|
const sources = [
|
|
5436
5983
|
{
|
|
5437
5984
|
id: "instructions",
|
|
5438
5985
|
kind: "instruction",
|
|
5439
5986
|
origin: "atticus",
|
|
5440
5987
|
text: [
|
|
5441
|
-
instructionsWith(spec.instructions, fix, answered, risks, last),
|
|
5988
|
+
instructionsWith(spec.instructions, fix, answered, risks, last, corrections.all),
|
|
5442
5989
|
// The last answer is in the new run's folder already (2026-10-26): an edit, not a
|
|
5443
5990
|
// rewrite. A person's own draft replaces it, so it isn't carried then.
|
|
5444
5991
|
...carriesAnswer && lastAnswerText !== void 0 && !personDraft ? ["", CARRIED_ANSWER_RULE] : [],
|
|
@@ -5506,7 +6053,7 @@ async function runStageLoop(root, changeId, spec, o) {
|
|
|
5506
6053
|
await writeQuarantineItems(root, change.id, prepared.runId, quarantineItems(pack.defences ?? []));
|
|
5507
6054
|
if (carriesAnswer && lastAnswerText !== void 0 && !personDraft) {
|
|
5508
6055
|
const where2 = `${runsRelOf(root)}/${change.id}/${prepared.runId}/${spec.output}`;
|
|
5509
|
-
await writeFileAtomic(
|
|
6056
|
+
await writeFileAtomic(join11(runsDirOf(root), change.id, prepared.runId, spec.output), lastAnswerText);
|
|
5510
6057
|
notes.push(`Your last answer was copied to ${where2}: fix it there.`);
|
|
5511
6058
|
}
|
|
5512
6059
|
draftRun = await o.runtime.run(prepared.runId);
|
|
@@ -5598,7 +6145,7 @@ ${unfence(run.output)}`,
|
|
|
5598
6145
|
failedOutput = unfence(run.output);
|
|
5599
6146
|
if (answer !== void 0) {
|
|
5600
6147
|
found.push(...await spec.check(answer));
|
|
5601
|
-
files = await write3(answer);
|
|
6148
|
+
files = await write3(answer, { run: run.runId });
|
|
5602
6149
|
}
|
|
5603
6150
|
}
|
|
5604
6151
|
if (answer !== void 0 && !found.some((i) => i.blocking)) {
|
|
@@ -5670,6 +6217,12 @@ ${unfence(run.output)}`,
|
|
|
5670
6217
|
loop.findings = [...byFp.values()].sort((a, b) => a.fingerprint.localeCompare(b.fingerprint));
|
|
5671
6218
|
loop.updatedAt = at2;
|
|
5672
6219
|
loop.state = "drafting";
|
|
6220
|
+
if (answer !== void 0) {
|
|
6221
|
+
for (const f of loop.findings)
|
|
6222
|
+
if (f.question && f.source === "stage" && f.seenIn.at(-1) !== n)
|
|
6223
|
+
await retireOpen(root, f.question, `Draft ${n} no longer has it: ${f.message}`, at2);
|
|
6224
|
+
}
|
|
6225
|
+
await retireSettledEscalations(root, change.id, loop, at2);
|
|
5673
6226
|
await writeLoop(dir, loop);
|
|
5674
6227
|
await stopOnProposed(found, n);
|
|
5675
6228
|
const drafted = [];
|
|
@@ -5733,7 +6286,7 @@ ${unfence(run.output)}`,
|
|
|
5733
6286
|
}, { ...judges, hold: (q2) => !handedBack.has(q2.id) });
|
|
5734
6287
|
held.push(...raised.held);
|
|
5735
6288
|
for (const [i, a] of raised.decidedBy)
|
|
5736
|
-
notes.push(`Your assistant asked again what ${a.id} answered ("${mine[i].q.question.trim()}"); the stage reads ${a.answer
|
|
6289
|
+
notes.push(`Your assistant asked again what ${a.id} answered ("${mine[i].q.question.trim()}"); the stage reads ${a.answer ? answerByWords(a.answer.by) : "the person"}'s answer instead of asking again.`);
|
|
5737
6290
|
qs.push(...raised.raised, ...raised.known);
|
|
5738
6291
|
mine.forEach((x, i) => {
|
|
5739
6292
|
if (x.f && raised.of[i])
|
|
@@ -5786,15 +6339,15 @@ ${unfence(run.output)}`,
|
|
|
5786
6339
|
const to = failedAnswerRel(spec);
|
|
5787
6340
|
let kept;
|
|
5788
6341
|
if (to === spec.source) {
|
|
5789
|
-
const valid = await
|
|
6342
|
+
const valid = await readFile9(join11(dir, ...to.split("/")), "utf8").catch(() => void 0);
|
|
5790
6343
|
if (valid !== void 0 && valid !== failedOutput) {
|
|
5791
|
-
await writeFileAtomic(
|
|
6344
|
+
await writeFileAtomic(join11(dir, ...lastValidRel(spec).split("/")), valid);
|
|
5792
6345
|
kept = `${rel}/${lastValidRel(spec)}`;
|
|
5793
6346
|
}
|
|
5794
6347
|
}
|
|
5795
|
-
await writeFileAtomic(
|
|
6348
|
+
await writeFileAtomic(join11(dir, ...to.split("/")), failedOutput);
|
|
5796
6349
|
loop.failedAnswer = to;
|
|
5797
|
-
loop.written = { ...loop.written ?? {}, [to]:
|
|
6350
|
+
loop.written = { ...loop.written ?? {}, [to]: sha3(failedOutput) };
|
|
5798
6351
|
const lines = new Map(found.filter((i) => i.line).map((i) => [issueFingerprint(i.rule, i.target, i.evidence ?? ""), i.line]));
|
|
5799
6352
|
return { file: `${rel}/${to}`, ...kept ? { lastValid: kept } : {}, lines };
|
|
5800
6353
|
};
|
|
@@ -5955,19 +6508,20 @@ var SELF_CHECK_RULE = "Check your answer with the stage's own checks before you
|
|
|
5955
6508
|
var failedAnswerRel = (spec) => `stage/${spec.output}`;
|
|
5956
6509
|
var lastValidRel = (spec) => `stage/${spec.stage}-answer.last-valid.yaml`;
|
|
5957
6510
|
var PERSON_DRAFT = "current-draft";
|
|
5958
|
-
var
|
|
6511
|
+
var sha3 = (b) => createHash4("sha256").update(b).digest("hex");
|
|
5959
6512
|
async function sourceFiles(dir, source) {
|
|
5960
6513
|
if (!source.endsWith("/"))
|
|
5961
|
-
return await stat(
|
|
6514
|
+
return await stat(join11(dir, source)).then((s) => s.isFile(), () => false) ? [source] : [];
|
|
5962
6515
|
const sub = source.replace(/\/$/, "");
|
|
5963
|
-
return (await readdir7(
|
|
6516
|
+
return (await readdir7(join11(dir, sub)).catch(() => [])).filter((f) => !f.startsWith(".")).sort().map((f) => `${sub}/${f}`);
|
|
5964
6517
|
}
|
|
6518
|
+
var lf = (b) => b.includes(13) ? Buffer.from(b.toString("utf8").replace(/\r\n?/g, "\n"), "utf8") : b;
|
|
5965
6519
|
async function hashFiles(dir, files) {
|
|
5966
6520
|
const out = {};
|
|
5967
6521
|
for (const f of [...files].sort()) {
|
|
5968
|
-
const b = await
|
|
6522
|
+
const b = await readFile9(join11(dir, ...f.split("/"))).catch(() => void 0);
|
|
5969
6523
|
if (b)
|
|
5970
|
-
out[f] =
|
|
6524
|
+
out[f] = sha3(lf(b));
|
|
5971
6525
|
}
|
|
5972
6526
|
return out;
|
|
5973
6527
|
}
|
|
@@ -5984,12 +6538,12 @@ async function editedFiles(dir, loop, source) {
|
|
|
5984
6538
|
out2.add(f);
|
|
5985
6539
|
return [...out2].sort();
|
|
5986
6540
|
}
|
|
5987
|
-
const since = await stat(
|
|
6541
|
+
const since = await stat(join11(dir, loopRel(loop.stage))).then((s) => s.mtimeMs, () => void 0);
|
|
5988
6542
|
if (since === void 0)
|
|
5989
6543
|
return [];
|
|
5990
6544
|
const out = [];
|
|
5991
6545
|
for (const f of on) {
|
|
5992
|
-
const m = await stat(
|
|
6546
|
+
const m = await stat(join11(dir, ...f.split("/"))).then((s) => s.mtimeMs, () => 0);
|
|
5993
6547
|
if (m > since + 1e3)
|
|
5994
6548
|
out.push(f);
|
|
5995
6549
|
}
|
|
@@ -5998,9 +6552,9 @@ async function editedFiles(dir, loop, source) {
|
|
|
5998
6552
|
async function keepFiles(dir, rel, stage, n, files) {
|
|
5999
6553
|
const to = `stage/${stage}-by-person/${n}`;
|
|
6000
6554
|
for (const f of files) {
|
|
6001
|
-
const from =
|
|
6002
|
-
const dest =
|
|
6003
|
-
await mkdir3(
|
|
6555
|
+
const from = join11(dir, ...f.split("/"));
|
|
6556
|
+
const dest = join11(dir, ...to.split("/"), ...f.split("/"));
|
|
6557
|
+
await mkdir3(join11(dest, ".."), { recursive: true });
|
|
6004
6558
|
await copyFile(from, dest).catch(() => void 0);
|
|
6005
6559
|
}
|
|
6006
6560
|
return `${rel}/${to}/`;
|
|
@@ -6008,13 +6562,13 @@ async function keepFiles(dir, rel, stage, n, files) {
|
|
|
6008
6562
|
async function draftText(dir, files) {
|
|
6009
6563
|
const out = [];
|
|
6010
6564
|
for (const f of files) {
|
|
6011
|
-
const t = await
|
|
6565
|
+
const t = await readFile9(join11(dir, ...f.split("/")), "utf8").catch(() => void 0);
|
|
6012
6566
|
out.push(`# ${f}${t === void 0 ? " (deleted by the person)" : ""}`, ...t === void 0 ? [] : [t.replace(/\r\n/g, "\n")]);
|
|
6013
6567
|
}
|
|
6014
6568
|
return out.join("\n");
|
|
6015
6569
|
}
|
|
6016
6570
|
var HAND_BACKS = "hand-backs";
|
|
6017
|
-
function instructionsWith(base, fix, answered, risks = [], last) {
|
|
6571
|
+
function instructionsWith(base, fix, answered, risks = [], last, corrections = []) {
|
|
6018
6572
|
const out = [base];
|
|
6019
6573
|
if (last)
|
|
6020
6574
|
out.push("", `Your last draft is in ${last}, as Atticus wrote it from your answer. Start from it: keep what the hand-backs don't ask you to change, and answer in full again.`);
|
|
@@ -6024,9 +6578,11 @@ function instructionsWith(base, fix, answered, risks = [], last) {
|
|
|
6024
6578
|
out.push("", `The critic also saw risks in your last draft; the "${HAND_BACKS}" data block lists them with what to fix. Fix each one the brief and the answers allow. A risk doesn't block: this draft is the one that may fix it.`);
|
|
6025
6579
|
if (answered.length)
|
|
6026
6580
|
out.push("", `People answered questions; the "${HAND_BACKS}" data block lists each question with its answer. Their answers are part of the brief: follow them, and never ask any of those questions again, in any words, or ask what an answer already says.`);
|
|
6581
|
+
if (corrections.length)
|
|
6582
|
+
out.push("", CORRECTIONS_RULE);
|
|
6027
6583
|
return out.join("\n");
|
|
6028
6584
|
}
|
|
6029
|
-
function handBacks(fix, answered, risks = []) {
|
|
6585
|
+
function handBacks(fix, answered, risks = [], corrections = []) {
|
|
6030
6586
|
const out = [];
|
|
6031
6587
|
if (fix.length)
|
|
6032
6588
|
out.push("Problems in the last draft:", ...fix.map((i) => `- ${i.message}${i.evidence ? ` (${i.evidence})` : ""}`));
|
|
@@ -6034,6 +6590,9 @@ function handBacks(fix, answered, risks = []) {
|
|
|
6034
6590
|
out.push(...out.length ? [""] : [], "Risks the critic saw in the last draft:", ...risks.map((i) => `- ${i.message}${i.evidence ? ` (${i.evidence})` : ""}`));
|
|
6035
6591
|
if (answered.length)
|
|
6036
6592
|
out.push(...out.length ? [""] : [], "Questions people answered:", ...answered.map((q2) => `- ${q2.question} Answer: ${answerWords(q2) ?? ""}`));
|
|
6593
|
+
const said2 = correctionsText(corrections);
|
|
6594
|
+
if (said2)
|
|
6595
|
+
out.push(...out.length ? [""] : [], said2);
|
|
6037
6596
|
return out.length ? out.join("\n") : void 0;
|
|
6038
6597
|
}
|
|
6039
6598
|
function askedAnswered(h) {
|
|
@@ -6054,14 +6613,16 @@ async function lineExists(root, at2) {
|
|
|
6054
6613
|
const n = Number(at2.slice(cut + 1));
|
|
6055
6614
|
if (!Number.isInteger(n) || n < 1 || path.includes(".."))
|
|
6056
6615
|
return false;
|
|
6057
|
-
const
|
|
6058
|
-
if (
|
|
6616
|
+
const text4 = await readFile9(join11(root, ...path.split("/")), "utf8").catch(() => void 0);
|
|
6617
|
+
if (text4 === void 0)
|
|
6059
6618
|
return false;
|
|
6060
|
-
return n <=
|
|
6619
|
+
return n <= text4.replace(/\n$/, "").split(/\r?\n/).length;
|
|
6061
6620
|
}
|
|
6621
|
+
var waitingOf = (w) => w.lenses ? { runId: w.runId, lenses: w.lenses } : { runId: w.runId, lens: w.lens };
|
|
6622
|
+
var listed = (names) => names.length < 2 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
|
|
6062
6623
|
async function isLensOf(root, change, review, runId) {
|
|
6063
6624
|
const meta = await readRoundMeta(root, change, review.gate, review.round);
|
|
6064
|
-
return !!meta?.lenses.some((l) => l.runId === runId);
|
|
6625
|
+
return !!meta?.lenses.some((l) => l.runId === runId || l.batch === runId);
|
|
6065
6626
|
}
|
|
6066
6627
|
async function failedLenses(root, change, gate, round2) {
|
|
6067
6628
|
const meta = await readRoundMeta(root, change, gate, round2);
|
|
@@ -6072,6 +6633,64 @@ function lensRuntimeOf(ctx, o) {
|
|
|
6072
6633
|
return void 0;
|
|
6073
6634
|
return o.criticRuntime ?? o.runtime;
|
|
6074
6635
|
}
|
|
6636
|
+
async function retireOpen(root, id, why, at2) {
|
|
6637
|
+
const q2 = await readQuestion(root, id).catch(() => void 0);
|
|
6638
|
+
if (q2?.state !== "open")
|
|
6639
|
+
return false;
|
|
6640
|
+
await retireQuestion(root, id, { why, at: at2 });
|
|
6641
|
+
return true;
|
|
6642
|
+
}
|
|
6643
|
+
function settledWords(f) {
|
|
6644
|
+
const r = f.resolution;
|
|
6645
|
+
if (f.state === "open" || !r)
|
|
6646
|
+
return void 0;
|
|
6647
|
+
const reason = r.reason ? `: ${r.reason.trim().replace(/[.!?]+$/, "")}` : "";
|
|
6648
|
+
if (f.state === "dismissed")
|
|
6649
|
+
return `dismissed as not relevant by ${r.by}${reason}`;
|
|
6650
|
+
if (f.state === "waived")
|
|
6651
|
+
return `waived by ${r.by}${reason}`;
|
|
6652
|
+
if (f.state === "applied")
|
|
6653
|
+
return `fixed by ${r.by}${reason}`;
|
|
6654
|
+
if (f.state === "asked")
|
|
6655
|
+
return `asked of ${r.askedOf ?? "its owner"} by ${r.by}`;
|
|
6656
|
+
return void 0;
|
|
6657
|
+
}
|
|
6658
|
+
async function retireSettledEscalations(root, change, loop, at2) {
|
|
6659
|
+
const asked = loop.findings.filter((f) => f.question && f.source === "critic");
|
|
6660
|
+
if (!asked.length || !loop.critic.length)
|
|
6661
|
+
return false;
|
|
6662
|
+
const gate = loop.critic.at(-1).gate;
|
|
6663
|
+
const round2 = (await roundsOf(root, change, gate).catch(() => [])).at(-1);
|
|
6664
|
+
if (round2 === void 0)
|
|
6665
|
+
return false;
|
|
6666
|
+
const meta = await readRoundMeta(root, change, gate, round2).catch(() => void 0);
|
|
6667
|
+
const complete = !(meta?.lenses ?? []).some((l) => l.state === "waiting" || l.state === "failed");
|
|
6668
|
+
const { findings } = await readRound(root, change, gate, round2);
|
|
6669
|
+
let any = false;
|
|
6670
|
+
for (const f of asked) {
|
|
6671
|
+
const hit = findings.find((x) => fingerprint(x.finding) === f.target);
|
|
6672
|
+
let why;
|
|
6673
|
+
if (hit) {
|
|
6674
|
+
const words5 = assistantClosed(hit.finding) ? void 0 : settledWords(hit.finding);
|
|
6675
|
+
if (words5)
|
|
6676
|
+
why = `Its critic finding ${hit.finding.id} was ${words5}.`;
|
|
6677
|
+
} else if (complete)
|
|
6678
|
+
why = `Round ${round2} at the ${gate} gate no longer raises it: ${f.message}`;
|
|
6679
|
+
if (why && await retireOpen(root, f.question, why, at2))
|
|
6680
|
+
any = true;
|
|
6681
|
+
}
|
|
6682
|
+
return any;
|
|
6683
|
+
}
|
|
6684
|
+
async function retireChangeEscalations(root, change, at2) {
|
|
6685
|
+
const dir = changeFolder(root, change);
|
|
6686
|
+
const touched = [];
|
|
6687
|
+
for (const stage of Stage.options) {
|
|
6688
|
+
const { loop, present } = await readStageLoop(dir, change, stage);
|
|
6689
|
+
if (present && await retireSettledEscalations(root, change, loop, at2))
|
|
6690
|
+
touched.push(stage);
|
|
6691
|
+
}
|
|
6692
|
+
return touched;
|
|
6693
|
+
}
|
|
6075
6694
|
async function criticIssues(root, change, findings, kindOf2) {
|
|
6076
6695
|
const rel = `${changesRelOf(root)}/${change}`;
|
|
6077
6696
|
const out = [];
|
|
@@ -6102,6 +6721,7 @@ async function criticIssues(root, change, findings, kindOf2) {
|
|
|
6102
6721
|
}
|
|
6103
6722
|
async function lensesIn(root, change, review, o, at2, kindOf2) {
|
|
6104
6723
|
const rt = o.criticRuntime ?? o.runtime;
|
|
6724
|
+
const out = [];
|
|
6105
6725
|
for (const w of await waitingLenses(root, change, review.gate, review.round)) {
|
|
6106
6726
|
const run = await rt.result(w.runId).catch(() => void 0);
|
|
6107
6727
|
if (run?.state === "validated") {
|
|
@@ -6112,7 +6732,29 @@ async function lensesIn(root, change, review, o, at2, kindOf2) {
|
|
|
6112
6732
|
await failLens(root, change, review.gate, review.round, w.runId, run ? `its run ${w.runId} was cancelled` : `its run ${w.runId} is no longer there`);
|
|
6113
6733
|
continue;
|
|
6114
6734
|
}
|
|
6115
|
-
|
|
6735
|
+
out.push({ ...w, run });
|
|
6736
|
+
}
|
|
6737
|
+
if (out.length) {
|
|
6738
|
+
const first = out[0];
|
|
6739
|
+
const batch = first.run.batch ? await rt.result(first.run.batch).catch(() => void 0) : void 0;
|
|
6740
|
+
if (batch && batch.state !== "validated" && batch.state !== "cancelled")
|
|
6741
|
+
return {
|
|
6742
|
+
waiting: {
|
|
6743
|
+
runId: batch.runId,
|
|
6744
|
+
lenses: out.filter((x) => x.run.batch === batch.runId).map((x) => x.lens),
|
|
6745
|
+
gate: review.gate,
|
|
6746
|
+
round: review.round
|
|
6747
|
+
},
|
|
6748
|
+
issues: [],
|
|
6749
|
+
failed: [],
|
|
6750
|
+
discarded: []
|
|
6751
|
+
};
|
|
6752
|
+
return {
|
|
6753
|
+
waiting: { runId: first.runId, lens: first.lens, gate: review.gate, round: review.round },
|
|
6754
|
+
issues: [],
|
|
6755
|
+
failed: [],
|
|
6756
|
+
discarded: []
|
|
6757
|
+
};
|
|
6116
6758
|
}
|
|
6117
6759
|
const { findings } = await readRound(root, change, review.gate, review.round);
|
|
6118
6760
|
return {
|
|
@@ -6138,6 +6780,8 @@ async function criticRound(root, ctx, change, spec, o, loop, notes, kindOf2, dra
|
|
|
6138
6780
|
gate,
|
|
6139
6781
|
...lensRuntime ? { runtime: lensRuntime } : {},
|
|
6140
6782
|
review: { stage: spec.stage, files: draft.files, sources: draft.sources },
|
|
6783
|
+
// ADR 0185: the lenses on the draft go to the person's assistant in one prompt.
|
|
6784
|
+
batchLenses: true,
|
|
6141
6785
|
now: (o.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z")
|
|
6142
6786
|
});
|
|
6143
6787
|
} catch (e) {
|
|
@@ -6155,6 +6799,16 @@ async function criticRound(root, ctx, change, spec, o, loop, notes, kindOf2, dra
|
|
|
6155
6799
|
return { issues: [] };
|
|
6156
6800
|
}
|
|
6157
6801
|
loop.critic = [...loop.critic, { gate, round: r.round }];
|
|
6802
|
+
if (r.batch)
|
|
6803
|
+
return {
|
|
6804
|
+
issues: [],
|
|
6805
|
+
waiting: {
|
|
6806
|
+
runId: r.batch,
|
|
6807
|
+
lenses: r.lenses.filter((l) => l.batch === r.batch).map((l) => l.lens),
|
|
6808
|
+
gate,
|
|
6809
|
+
round: r.round
|
|
6810
|
+
}
|
|
6811
|
+
};
|
|
6158
6812
|
const first = r.lenses.find((l) => l.state === "waiting" && l.runId);
|
|
6159
6813
|
if (first)
|
|
6160
6814
|
return {
|
|
@@ -6228,14 +6882,14 @@ var withoutResume = (o) => {
|
|
|
6228
6882
|
};
|
|
6229
6883
|
|
|
6230
6884
|
// packages/planner/dist/stages/feature.js
|
|
6231
|
-
var
|
|
6232
|
-
import { copyFile as copyFile2, mkdir as mkdir6, readdir as readdir9, readFile as
|
|
6233
|
-
import { basename as basename2, join as
|
|
6885
|
+
var import_yaml13 = __toESM(require_dist(), 1);
|
|
6886
|
+
import { copyFile as copyFile2, mkdir as mkdir6, readdir as readdir9, readFile as readFile13 } from "node:fs/promises";
|
|
6887
|
+
import { basename as basename2, join as join15 } from "node:path";
|
|
6234
6888
|
|
|
6235
6889
|
// packages/planner/dist/stages/knowledge-applies.js
|
|
6236
|
-
var
|
|
6237
|
-
import { mkdir as mkdir4, readFile as
|
|
6238
|
-
import { join as
|
|
6890
|
+
var import_yaml11 = __toESM(require_dist(), 1);
|
|
6891
|
+
import { mkdir as mkdir4, readFile as readFile10, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
|
|
6892
|
+
import { join as join12 } from "node:path";
|
|
6239
6893
|
var KNOWLEDGE_INDEX_KINDS = [
|
|
6240
6894
|
"Pattern",
|
|
6241
6895
|
"Decision",
|
|
@@ -6282,12 +6936,12 @@ var KnowledgeApplies = external_exports.strictObject({
|
|
|
6282
6936
|
})).default([])
|
|
6283
6937
|
});
|
|
6284
6938
|
var FILE = "knowledge.yaml";
|
|
6285
|
-
var pathOf = (root, change) =>
|
|
6939
|
+
var pathOf = (root, change) => join12(changeFolder(root, change), FILE);
|
|
6286
6940
|
async function readKnowledgeApplies(root, change) {
|
|
6287
|
-
const
|
|
6288
|
-
if (
|
|
6941
|
+
const text4 = await readFile10(pathOf(root, change), "utf8").catch(() => void 0);
|
|
6942
|
+
if (text4 === void 0)
|
|
6289
6943
|
return void 0;
|
|
6290
|
-
const r = KnowledgeApplies.safeParse((0,
|
|
6944
|
+
const r = KnowledgeApplies.safeParse((0, import_yaml11.parse)(text4));
|
|
6291
6945
|
return r.success ? r.data : void 0;
|
|
6292
6946
|
}
|
|
6293
6947
|
async function write(root, doc) {
|
|
@@ -6513,8 +7167,8 @@ function knowledgeAppliesMarkdown(doc) {
|
|
|
6513
7167
|
}
|
|
6514
7168
|
|
|
6515
7169
|
// packages/planner/dist/stages/estate-context.js
|
|
6516
|
-
import { readFile as
|
|
6517
|
-
import { join as
|
|
7170
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
7171
|
+
import { join as join13 } from "node:path";
|
|
6518
7172
|
var SECTION_KINDS = ["WikiSection", "DocumentSection", "Party", "GlossaryTerm"];
|
|
6519
7173
|
var SECTION_TOKENS = 1500;
|
|
6520
7174
|
var SECTION_SHARE = 0.15;
|
|
@@ -6604,7 +7258,7 @@ async function estateContext(root, step, input, o) {
|
|
|
6604
7258
|
priority: o.sections.priority
|
|
6605
7259
|
}));
|
|
6606
7260
|
if (e && o.change) {
|
|
6607
|
-
const brief = await
|
|
7261
|
+
const brief = await readFile11(join13(changeFolder(root, o.change), "brief.md"), "utf8").catch(() => "");
|
|
6608
7262
|
sources.push(...briefEndpointsSource(brief, e));
|
|
6609
7263
|
}
|
|
6610
7264
|
const report = reportOf(s, room);
|
|
@@ -6639,16 +7293,16 @@ function sectionLine(n) {
|
|
|
6639
7293
|
return void 0;
|
|
6640
7294
|
const x = n.json ?? {};
|
|
6641
7295
|
const cite3 = /\[[^\]]*\]$/.exec(n.line)?.[0];
|
|
6642
|
-
const
|
|
7296
|
+
const text4 = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
|
|
6643
7297
|
const list2 = (v) => Array.isArray(v) ? v.filter((s) => typeof s === "string") : [];
|
|
6644
|
-
const about = n.kind === "GlossaryTerm" ?
|
|
6645
|
-
|
|
7298
|
+
const about = n.kind === "GlossaryTerm" ? text4(x.definition) : [
|
|
7299
|
+
text4(x.owner) && `owner ${text4(x.owner)}`,
|
|
6646
7300
|
list2(x.owns).length && `owns ${list2(x.owns).join(", ")}`,
|
|
6647
7301
|
list2(x.responsibilities)[0],
|
|
6648
|
-
|
|
7302
|
+
text4(x.quote && x.quote.text)
|
|
6649
7303
|
].filter(Boolean).join("; ");
|
|
6650
7304
|
const word = n.kind === "GlossaryTerm" ? "term" : x.partyKind === "vendor" ? "vendor" : "team";
|
|
6651
|
-
const name =
|
|
7305
|
+
const name = text4(x.term) ?? text4(x.name) ?? n.name;
|
|
6652
7306
|
return `${word} ${n.id} "${name}"${about ? `: ${about}` : ""}${cite3 ? ` ${cite3}` : ""}`;
|
|
6653
7307
|
}
|
|
6654
7308
|
async function sectionsFor(root, input, o) {
|
|
@@ -6731,18 +7385,18 @@ function dropUncitedQuestions(d) {
|
|
|
6731
7385
|
}
|
|
6732
7386
|
|
|
6733
7387
|
// packages/planner/dist/stages/openspec-files.js
|
|
6734
|
-
var
|
|
7388
|
+
var import_yaml12 = __toESM(require_dist(), 1);
|
|
6735
7389
|
import { existsSync } from "node:fs";
|
|
6736
|
-
import { cp, mkdir as mkdir5, mkdtemp, readdir as readdir8, readFile as
|
|
7390
|
+
import { cp, mkdir as mkdir5, mkdtemp, readdir as readdir8, readFile as readFile12, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
|
|
6737
7391
|
import { tmpdir } from "node:os";
|
|
6738
|
-
import { basename, dirname as dirname3, join as
|
|
7392
|
+
import { basename, dirname as dirname3, join as join14 } from "node:path";
|
|
6739
7393
|
var SPEC_MARK = "<!-- Generated by atticus plan stories from the stories' acceptance criteria. Edit the stories and run the stage again. -->";
|
|
6740
7394
|
var PROPOSAL_MARK = "<!-- Generated by atticus change feature from brief.md and feature.yaml. Edit feature.yaml and run the stage again. -->";
|
|
6741
7395
|
var MAX_REQUIREMENT = 480;
|
|
6742
7396
|
var sentence3 = (s) => /[.!?]$/.test(s.trim()) ? s.trim() : `${s.trim()}.`;
|
|
6743
7397
|
var bare = (s) => s.trim().replace(/[.\s]+$/, "");
|
|
6744
7398
|
var cap = (s) => s ? `${s[0].toUpperCase()}${s.slice(1)}` : s;
|
|
6745
|
-
var
|
|
7399
|
+
var slug3 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
6746
7400
|
var clip = (s, n) => {
|
|
6747
7401
|
if (s.length <= n)
|
|
6748
7402
|
return s;
|
|
@@ -6754,7 +7408,7 @@ function openspecRootOf(root) {
|
|
|
6754
7408
|
const dir = openspecDirOf(root);
|
|
6755
7409
|
if (basename(dir) !== "openspec")
|
|
6756
7410
|
return void 0;
|
|
6757
|
-
return existsSync(
|
|
7411
|
+
return existsSync(join14(dir, "config.yaml")) ? dirname3(dir) : void 0;
|
|
6758
7412
|
}
|
|
6759
7413
|
function metarepoOpenSpec(root) {
|
|
6760
7414
|
const r = openspecRootOf(root);
|
|
@@ -6781,13 +7435,13 @@ function capabilityOf(flow, repo, o) {
|
|
|
6781
7435
|
const had = o.existing?.find((s) => s.flow === flow);
|
|
6782
7436
|
if (had)
|
|
6783
7437
|
return had.id;
|
|
6784
|
-
const part =
|
|
6785
|
-
const r =
|
|
7438
|
+
const part = slug3(flow.split(".").slice(1).join("-") || flow);
|
|
7439
|
+
const r = slug3(repo);
|
|
6786
7440
|
const name = o.multiRepo && !part.startsWith(r) ? `${r}-${part}` : part;
|
|
6787
7441
|
return name.slice(0, 80).replace(/-+$/, "") || r;
|
|
6788
7442
|
}
|
|
6789
|
-
function scenarioOf(
|
|
6790
|
-
const t = bare(
|
|
7443
|
+
function scenarioOf(text4) {
|
|
7444
|
+
const t = bare(text4);
|
|
6791
7445
|
const m = /^\s*given\s+(.+?),?\s+when\s+(.+?),?\s+then\s+(.+)$/is.exec(t) ?? /^\s*()when\s+(.+?),?\s+then\s+(.+)$/is.exec(t);
|
|
6792
7446
|
const given = m?.[1]?.trim();
|
|
6793
7447
|
const when = m?.[2]?.trim();
|
|
@@ -6843,15 +7497,15 @@ function specDeltas(change, stories, answer, o) {
|
|
|
6843
7497
|
} else {
|
|
6844
7498
|
const fresh = uniqueNames(criteriaOf(s, w).map((c) => scenarioOf(c.text)));
|
|
6845
7499
|
if (fresh.length) {
|
|
6846
|
-
const
|
|
7500
|
+
const text4 = requirementText(change, s, w);
|
|
6847
7501
|
if (prior) {
|
|
6848
7502
|
const byName2 = new Map(fresh.map((x) => [x.name.toLowerCase(), x]));
|
|
6849
7503
|
const kept = prior.scenarios.map((x) => byName2.get(x.name.toLowerCase()) ?? { name: x.name, body: x.rawText.trim() });
|
|
6850
7504
|
const had2 = new Set(prior.scenarios.map((x) => x.name.toLowerCase()));
|
|
6851
7505
|
const added = fresh.filter((x) => !had2.has(x.name.toLowerCase()));
|
|
6852
|
-
d.modified.push({ name: prior.name, text:
|
|
7506
|
+
d.modified.push({ name: prior.name, text: text4, scenarios: [...kept, ...added] });
|
|
6853
7507
|
} else
|
|
6854
|
-
d.added.push({ name: bare(s.name), text:
|
|
7508
|
+
d.added.push({ name: bare(s.name), text: text4, scenarios: fresh });
|
|
6855
7509
|
}
|
|
6856
7510
|
}
|
|
6857
7511
|
if (d.added.length + d.modified.length + d.removed.length)
|
|
@@ -6891,34 +7545,34 @@ function renderSpecDelta(d, mark = SPEC_MARK) {
|
|
|
6891
7545
|
}
|
|
6892
7546
|
var specFiles = (deltas) => Object.fromEntries(deltas.map((d) => [`specs/${d.capability}/spec.md`, renderSpecDelta(d)]));
|
|
6893
7547
|
async function writeSpecFiles(dir, files, mark = SPEC_MARK) {
|
|
6894
|
-
const specs =
|
|
7548
|
+
const specs = join14(dir, "specs");
|
|
6895
7549
|
for (const c of await readdir8(specs).catch(() => [])) {
|
|
6896
|
-
const f =
|
|
7550
|
+
const f = join14(specs, c, "spec.md");
|
|
6897
7551
|
if (files[`specs/${c}/spec.md`] !== void 0)
|
|
6898
7552
|
continue;
|
|
6899
|
-
const t = await
|
|
7553
|
+
const t = await readFile12(f, "utf8").catch(() => void 0);
|
|
6900
7554
|
if (t?.startsWith(mark))
|
|
6901
|
-
await rm4(
|
|
7555
|
+
await rm4(join14(specs, c), { recursive: true, force: true });
|
|
6902
7556
|
}
|
|
6903
7557
|
const written = [];
|
|
6904
|
-
for (const [rel,
|
|
6905
|
-
const f =
|
|
6906
|
-
const had = await
|
|
7558
|
+
for (const [rel, text4] of Object.entries(files)) {
|
|
7559
|
+
const f = join14(dir, ...rel.split("/"));
|
|
7560
|
+
const had = await readFile12(f, "utf8").catch(() => void 0);
|
|
6907
7561
|
if (had !== void 0 && !had.startsWith(mark))
|
|
6908
7562
|
continue;
|
|
6909
7563
|
await mkdir5(dirname3(f), { recursive: true });
|
|
6910
|
-
await writeFileAtomic(f,
|
|
7564
|
+
await writeFileAtomic(f, text4);
|
|
6911
7565
|
written.push(rel);
|
|
6912
7566
|
}
|
|
6913
7567
|
await setSkipSpecs(dir, Object.keys(files).length === 0);
|
|
6914
7568
|
return written;
|
|
6915
7569
|
}
|
|
6916
7570
|
async function setSkipSpecs(dir, skip) {
|
|
6917
|
-
const f =
|
|
6918
|
-
const
|
|
6919
|
-
if (
|
|
7571
|
+
const f = join14(dir, ".openspec.yaml");
|
|
7572
|
+
const text4 = await readFile12(f, "utf8").catch(() => void 0);
|
|
7573
|
+
if (text4 === void 0)
|
|
6920
7574
|
return;
|
|
6921
|
-
const doc = (0,
|
|
7575
|
+
const doc = (0, import_yaml12.parseDocument)(text4);
|
|
6922
7576
|
if (Boolean(doc.get("skip_specs")) === skip)
|
|
6923
7577
|
return;
|
|
6924
7578
|
if (skip)
|
|
@@ -6928,9 +7582,9 @@ async function setSkipSpecs(dir, skip) {
|
|
|
6928
7582
|
await writeFile3(f, doc.toString({ lineWidth: 0 }), "utf8");
|
|
6929
7583
|
}
|
|
6930
7584
|
function briefWhy(brief) {
|
|
6931
|
-
const
|
|
6932
|
-
const under = /^#{1,3}\s*(?:problem|why|the problem|background)\b[^\n]*\n+([\s\S]*?)(?=\n#{1,3}\s|$)/im.exec(
|
|
6933
|
-
const paras = (under ??
|
|
7585
|
+
const text4 = brief.replace(/\r\n/g, "\n").replace(/<!--[\s\S]*?-->/g, "");
|
|
7586
|
+
const under = /^#{1,3}\s*(?:problem|why|the problem|background)\b[^\n]*\n+([\s\S]*?)(?=\n#{1,3}\s|$)/im.exec(text4)?.[1];
|
|
7587
|
+
const paras = (under ?? text4.replace(/^#[^\n]*\n/gm, "")).split(/\n\s*\n/).map((p) => p.replace(/\s+/g, " ").trim()).filter((p) => p && !p.startsWith("|"));
|
|
6934
7588
|
return paras[0] ?? "";
|
|
6935
7589
|
}
|
|
6936
7590
|
function renderProposal(title, p, brief, o = {}) {
|
|
@@ -6975,12 +7629,12 @@ function renderProposal(title, p, brief, o = {}) {
|
|
|
6975
7629
|
lines.push("## Impact", "", repos.length ? `- Repos: ${repos.join(", ")}.` : "- Repos: not placed yet.", ...p.flows.filter((f) => f.how === "read").map((f) => `- Reads flow \`${f.id}\`, unchanged.`), "", "## Open questions", "", ...p.gaps.length ? p.gaps.map((g) => `- ${sentence3(g.what.replace(/\s+/g, " "))}`) : ["- None found by the feature stage; see needs.md for what the change waits on."], "");
|
|
6976
7630
|
return lines.join("\n");
|
|
6977
7631
|
}
|
|
6978
|
-
async function writeProposal(dir,
|
|
6979
|
-
const f =
|
|
6980
|
-
const had = await
|
|
7632
|
+
async function writeProposal(dir, text4) {
|
|
7633
|
+
const f = join14(dir, "proposal.md");
|
|
7634
|
+
const had = await readFile12(f, "utf8").catch(() => void 0);
|
|
6981
7635
|
if (had !== void 0 && !had.startsWith(PROPOSAL_MARK))
|
|
6982
7636
|
return false;
|
|
6983
|
-
await writeFileAtomic(f,
|
|
7637
|
+
await writeFileAtomic(f, text4);
|
|
6984
7638
|
return true;
|
|
6985
7639
|
}
|
|
6986
7640
|
async function openspecFiles(root, change, stories, answer, estate) {
|
|
@@ -6994,11 +7648,11 @@ async function openspecFiles(root, change, stories, answer, estate) {
|
|
|
6994
7648
|
const deltas = specDeltas(change, stories, answer, o);
|
|
6995
7649
|
const capabilityOfFlow = new Map(stories.map((s) => [s.flow, capabilityOf(s.flow, s.repo, o)]).filter(([, c]) => deltas.some((d) => d.capability === c)));
|
|
6996
7650
|
const dir = changeFolder(root, change);
|
|
6997
|
-
const placed = await
|
|
7651
|
+
const placed = await readFile12(join14(dir, "feature.yaml"), "utf8").then((t) => FeaturePlacement.safeParse((0, import_yaml12.parse)(t))).catch(() => void 0);
|
|
6998
7652
|
let proposal;
|
|
6999
7653
|
if (placed?.success) {
|
|
7000
7654
|
const title = await changeStore(root).read(change).then((c) => c.title).catch(() => change);
|
|
7001
|
-
const brief = await
|
|
7655
|
+
const brief = await readFile12(join14(dir, "brief.md"), "utf8").catch(() => "");
|
|
7002
7656
|
proposal = renderProposal(title, placed.data, brief, {
|
|
7003
7657
|
repoOf: (f) => estate?.flowById.get(f)?.repo,
|
|
7004
7658
|
capabilities: deltas.map((d) => {
|
|
@@ -7064,21 +7718,21 @@ async function validateDraft(root, change, dir, files, o = {}) {
|
|
|
7064
7718
|
const osRoot = openspecRootOf(root);
|
|
7065
7719
|
if (!osRoot)
|
|
7066
7720
|
return [];
|
|
7067
|
-
const tmp = await mkdtemp(
|
|
7721
|
+
const tmp = await mkdtemp(join14(tmpdir(), "atticus-validate-"));
|
|
7068
7722
|
try {
|
|
7069
|
-
const os =
|
|
7070
|
-
await mkdir5(
|
|
7723
|
+
const os = join14(osRoot, "openspec");
|
|
7724
|
+
await mkdir5(join14(tmp, "openspec", "changes", change), { recursive: true });
|
|
7071
7725
|
for (const f of ["config.yaml", "schemas", "specs"])
|
|
7072
|
-
if (existsSync(
|
|
7073
|
-
await cp(
|
|
7074
|
-
const target =
|
|
7726
|
+
if (existsSync(join14(os, f)))
|
|
7727
|
+
await cp(join14(os, f), join14(tmp, "openspec", f), { recursive: true });
|
|
7728
|
+
const target = join14(tmp, "openspec", "changes", change);
|
|
7075
7729
|
for (const f of [".openspec.yaml", "proposal.md", "design.md", "stories.md", "tasks.md"])
|
|
7076
|
-
if (existsSync(
|
|
7077
|
-
await cp(
|
|
7078
|
-
for (const [rel,
|
|
7079
|
-
const f =
|
|
7730
|
+
if (existsSync(join14(dir, f)))
|
|
7731
|
+
await cp(join14(dir, f), join14(target, f));
|
|
7732
|
+
for (const [rel, text4] of Object.entries(files)) {
|
|
7733
|
+
const f = join14(target, ...rel.split("/"));
|
|
7080
7734
|
await mkdir5(dirname3(f), { recursive: true });
|
|
7081
|
-
await writeFile3(f,
|
|
7735
|
+
await writeFile3(f, text4, "utf8");
|
|
7082
7736
|
}
|
|
7083
7737
|
await setSkipSpecs(target, !Object.keys(files).some((f) => f.startsWith("specs/")));
|
|
7084
7738
|
const r = await createOpenSpec({ root: tmp, ...o.bin ? { bin: o.bin } : {} }).validate({
|
|
@@ -7157,15 +7811,15 @@ async function pastCorrectionsSource(root, q2) {
|
|
|
7157
7811
|
}).catch(() => []);
|
|
7158
7812
|
if (!past.length)
|
|
7159
7813
|
return void 0;
|
|
7160
|
-
const
|
|
7161
|
-
if (!
|
|
7814
|
+
const text4 = pastCorrectionsText(past, Math.floor(q2.budget * PAST_CORRECTIONS_SHARE));
|
|
7815
|
+
if (!text4)
|
|
7162
7816
|
return void 0;
|
|
7163
7817
|
return {
|
|
7164
7818
|
source: {
|
|
7165
7819
|
id: "past-corrections",
|
|
7166
7820
|
kind: "artifact",
|
|
7167
7821
|
origin: past.map((p) => `${p.folder}/stage/${p.stage}.drafted/ and .merged/`).join("; "),
|
|
7168
|
-
text:
|
|
7822
|
+
text: text4,
|
|
7169
7823
|
classification: "internal",
|
|
7170
7824
|
priority: 60
|
|
7171
7825
|
},
|
|
@@ -7238,20 +7892,20 @@ Rules:
|
|
|
7238
7892
|
var isTemplate = (brief) => brief.replace(/<!--[\s\S]*?-->/g, "").split("\n").filter((l) => l.trim() && !/^#/.test(l.trim())).length === 0;
|
|
7239
7893
|
async function personStories(dir) {
|
|
7240
7894
|
const out = [];
|
|
7241
|
-
for (const f of (await readdir9(
|
|
7895
|
+
for (const f of (await readdir9(join15(dir, "stories")).catch(() => [])).sort()) {
|
|
7242
7896
|
if (!f.endsWith(".md"))
|
|
7243
7897
|
continue;
|
|
7244
|
-
const
|
|
7245
|
-
if (!
|
|
7246
|
-
out.push({ file: `stories/${f}`, text:
|
|
7898
|
+
const text4 = await readFile13(join15(dir, "stories", f), "utf8");
|
|
7899
|
+
if (!text4.startsWith(GENERATED_MARK))
|
|
7900
|
+
out.push({ file: `stories/${f}`, text: text4 });
|
|
7247
7901
|
}
|
|
7248
7902
|
return out;
|
|
7249
7903
|
}
|
|
7250
7904
|
async function readPlacement(dir) {
|
|
7251
|
-
const
|
|
7252
|
-
if (
|
|
7905
|
+
const text4 = await readFile13(join15(dir, FEATURE_FILE), "utf8").catch(() => void 0);
|
|
7906
|
+
if (text4 === void 0)
|
|
7253
7907
|
return void 0;
|
|
7254
|
-
const r = FeaturePlacement.safeParse((0,
|
|
7908
|
+
const r = FeaturePlacement.safeParse((0, import_yaml13.parse)(text4));
|
|
7255
7909
|
return r.success ? r.data : void 0;
|
|
7256
7910
|
}
|
|
7257
7911
|
async function conflictsOf(root, change, p, e) {
|
|
@@ -7269,7 +7923,7 @@ async function conflictsOf(root, change, p, e) {
|
|
|
7269
7923
|
continue;
|
|
7270
7924
|
const dir = changeFolder(root, other.id);
|
|
7271
7925
|
const theirs = new Set((await readPlacement(dir))?.flows.map((f) => f.id) ?? []);
|
|
7272
|
-
for (const f of await readdir9(
|
|
7926
|
+
for (const f of await readdir9(join15(dir, "target-flows")).catch(() => []))
|
|
7273
7927
|
if (f.endsWith(".yaml"))
|
|
7274
7928
|
theirs.add(f.slice(0, -5));
|
|
7275
7929
|
const flows = [...mine].filter((x) => theirs.has(x)).sort();
|
|
@@ -7294,10 +7948,10 @@ async function writePlacement(root, change, p, estate, by = "atticus change feat
|
|
|
7294
7948
|
gaps.push(g);
|
|
7295
7949
|
}
|
|
7296
7950
|
const full = FeaturePlacement.parse({ ...p, gaps, conflicts, questions: [] });
|
|
7297
|
-
await writeFileAtomic(
|
|
7298
|
-
${(0,
|
|
7299
|
-
await writeFileAtomic(
|
|
7300
|
-
const brief = await
|
|
7951
|
+
await writeFileAtomic(join15(dir, FEATURE_FILE), `# The feature placed against the estate (ADR 0087). Drafted by ${by}; review it in the feature PR.
|
|
7952
|
+
${(0, import_yaml13.stringify)(full, { lineWidth: 0 })}`);
|
|
7953
|
+
await writeFileAtomic(join15(dir, "feature.md"), renderFeature(full));
|
|
7954
|
+
const brief = await readFile13(join15(dir, "brief.md"), "utf8").catch(() => "");
|
|
7301
7955
|
const proposal = renderProposal(change.title, full, brief, {
|
|
7302
7956
|
repoOf: (f) => estate.flowById.get(f)?.repo
|
|
7303
7957
|
});
|
|
@@ -7376,21 +8030,21 @@ async function featureStage(root, changeId, o) {
|
|
|
7376
8030
|
let designRel = (await readPlacement(dir))?.design?.file;
|
|
7377
8031
|
if (o.fromFigma) {
|
|
7378
8032
|
designRel = `design/${basename2(o.fromFigma).replace(/[^\w.-]+/g, "-")}`;
|
|
7379
|
-
await mkdir6(
|
|
7380
|
-
await copyFile2(o.fromFigma,
|
|
8033
|
+
await mkdir6(join15(dir, "design"), { recursive: true });
|
|
8034
|
+
await copyFile2(o.fromFigma, join15(dir, ...designRel.split("/")));
|
|
7381
8035
|
}
|
|
7382
8036
|
let design;
|
|
7383
8037
|
if (designRel) {
|
|
7384
|
-
const
|
|
7385
|
-
if (
|
|
8038
|
+
const text4 = await readFile13(join15(dir, ...designRel.split("/")), "utf8").catch(() => void 0);
|
|
8039
|
+
if (text4 === void 0)
|
|
7386
8040
|
throw new PlannerError("refused", `The design file ${rel}/${designRel} isn't there; run with --from-figma <file.json>.`);
|
|
7387
8041
|
try {
|
|
7388
|
-
design = readFigma(
|
|
8042
|
+
design = readFigma(text4);
|
|
7389
8043
|
} catch (e) {
|
|
7390
8044
|
throw new PlannerError("refused", e.message);
|
|
7391
8045
|
}
|
|
7392
8046
|
}
|
|
7393
|
-
const brief = await
|
|
8047
|
+
const brief = await readFile13(join15(dir, "brief.md"), "utf8").catch(() => "");
|
|
7394
8048
|
const stories = await personStories(dir);
|
|
7395
8049
|
const attached = await changeInputSources(dir, rel);
|
|
7396
8050
|
const hasBrief = !!brief.trim() && !isTemplate(brief);
|
|
@@ -7530,9 +8184,9 @@ async function featureStage(root, changeId, o) {
|
|
|
7530
8184
|
instructions: instructions3,
|
|
7531
8185
|
...context.gaps?.length ? { gaps: context.gaps } : {},
|
|
7532
8186
|
sources,
|
|
7533
|
-
parse: (
|
|
8187
|
+
parse: (text4) => {
|
|
7534
8188
|
let repairs = [];
|
|
7535
|
-
const r = parseYamlAs(
|
|
8189
|
+
const r = parseYamlAs(text4, FeaturePlacement, (d) => {
|
|
7536
8190
|
repairs = dropUncitedQuestions(d);
|
|
7537
8191
|
return fill(d);
|
|
7538
8192
|
}, "feature.yaml");
|
|
@@ -7555,12 +8209,12 @@ async function featureStage(root, changeId, o) {
|
|
|
7555
8209
|
}
|
|
7556
8210
|
|
|
7557
8211
|
// packages/planner/dist/stages/architecture.js
|
|
7558
|
-
var
|
|
7559
|
-
import { readdir as readdir10, readFile as
|
|
7560
|
-
import { join as
|
|
8212
|
+
var import_yaml14 = __toESM(require_dist(), 1);
|
|
8213
|
+
import { readdir as readdir10, readFile as readFile15, rm as rm5 } from "node:fs/promises";
|
|
8214
|
+
import { join as join16 } from "node:path";
|
|
7561
8215
|
|
|
7562
8216
|
// packages/planner/dist/stages/merge.js
|
|
7563
|
-
import { createHash as
|
|
8217
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
7564
8218
|
function flowBase(flow) {
|
|
7565
8219
|
const steps = flow.steps.map((s) => ({
|
|
7566
8220
|
kind: s.kind,
|
|
@@ -7568,7 +8222,7 @@ function flowBase(flow) {
|
|
|
7568
8222
|
to: s.to ? { component: s.to.component, entry: s.to.entry } : null,
|
|
7569
8223
|
store: s.store ? { table: s.store.table, columns: s.store.columns } : null
|
|
7570
8224
|
}));
|
|
7571
|
-
return
|
|
8225
|
+
return createHash5("sha256").update(JSON.stringify(steps)).digest("hex");
|
|
7572
8226
|
}
|
|
7573
8227
|
function deltaBase(d, today) {
|
|
7574
8228
|
if (d.change === "new" || !today)
|
|
@@ -7657,14 +8311,14 @@ function mergeDelta(d, today) {
|
|
|
7657
8311
|
steps.push(...addsAfter(0));
|
|
7658
8312
|
today.steps.forEach((s, i) => {
|
|
7659
8313
|
const n = i + 1;
|
|
7660
|
-
const
|
|
8314
|
+
const rm8 = removes.get(n);
|
|
7661
8315
|
const ch = changes.get(n);
|
|
7662
|
-
if (
|
|
8316
|
+
if (rm8)
|
|
7663
8317
|
steps.push({
|
|
7664
8318
|
mark: "removed",
|
|
7665
8319
|
today: n,
|
|
7666
8320
|
...fromToday(s),
|
|
7667
|
-
...
|
|
8321
|
+
...rm8.why ? { why: rm8.why } : {}
|
|
7668
8322
|
});
|
|
7669
8323
|
else {
|
|
7670
8324
|
const m = ch ? {
|
|
@@ -7711,7 +8365,7 @@ function mergeDelta(d, today) {
|
|
|
7711
8365
|
}
|
|
7712
8366
|
|
|
7713
8367
|
// packages/planner/dist/stages/story-code.js
|
|
7714
|
-
import { readFile as
|
|
8368
|
+
import { readFile as readFile14, realpath, stat as stat3 } from "node:fs/promises";
|
|
7715
8369
|
import { isAbsolute, relative as relative2, resolve, sep as sep2 } from "node:path";
|
|
7716
8370
|
var STORY_CODE_AROUND = 3;
|
|
7717
8371
|
var STORY_CODE_SHARE = 0.2;
|
|
@@ -7745,7 +8399,7 @@ async function readCheckoutFile(root, repo, path) {
|
|
|
7745
8399
|
return void 0;
|
|
7746
8400
|
const base = defaultCodeDir(root, repo);
|
|
7747
8401
|
const file = base ? await inside(base, path) : void 0;
|
|
7748
|
-
return file ? await
|
|
8402
|
+
return file ? await readFile14(file, "utf8").catch(() => void 0) : void 0;
|
|
7749
8403
|
}
|
|
7750
8404
|
async function checkoutHas(root, repo, path) {
|
|
7751
8405
|
const base = defaultCodeDir(root, repo);
|
|
@@ -7796,12 +8450,12 @@ async function storyCodeSource(root, stories, budget, o = {}) {
|
|
|
7796
8450
|
continue;
|
|
7797
8451
|
const base = defaultCodeDir(root, f.repo);
|
|
7798
8452
|
const file = base ? await inside(base, f.path) : void 0;
|
|
7799
|
-
const
|
|
7800
|
-
if (
|
|
8453
|
+
const text4 = file ? await readFile14(file, "utf8").catch(() => void 0) : void 0;
|
|
8454
|
+
if (text4 === void 0) {
|
|
7801
8455
|
missing.push(key3);
|
|
7802
8456
|
continue;
|
|
7803
8457
|
}
|
|
7804
|
-
const all2 =
|
|
8458
|
+
const all2 = text4.replace(/\r\n?/g, "\n").split("\n");
|
|
7805
8459
|
if (all2.length > 1 && all2.at(-1) === "")
|
|
7806
8460
|
all2.pop();
|
|
7807
8461
|
const lines = f.lines.filter((l) => l >= 1 && l <= all2.length).sort((a, b) => a - b);
|
|
@@ -8244,7 +8898,7 @@ function deployNeeds(deltas, e) {
|
|
|
8244
8898
|
if (d.steps.some((s) => s.op !== "remove" && s.store?.table === table))
|
|
8245
8899
|
add({ first: u.key, then: codeKey(d.repo), why: { kind: "migration", table } });
|
|
8246
8900
|
}
|
|
8247
|
-
const
|
|
8901
|
+
const repoOf2 = (id) => {
|
|
8248
8902
|
const r = e.byId.get(id);
|
|
8249
8903
|
return r?.kind === "Component" && typeof r.repo === "string" ? r.repo : void 0;
|
|
8250
8904
|
};
|
|
@@ -8260,7 +8914,7 @@ function deployNeeds(deltas, e) {
|
|
|
8260
8914
|
const to = s.to ?? (was?.to ? { ...was.to, new: false } : void 0);
|
|
8261
8915
|
if (!to)
|
|
8262
8916
|
continue;
|
|
8263
|
-
const callee =
|
|
8917
|
+
const callee = repoOf2(to.component);
|
|
8264
8918
|
if (!callee || callee === d.repo || !units.has(codeKey(callee)))
|
|
8265
8919
|
continue;
|
|
8266
8920
|
const key3 = entryKey2(to.entry);
|
|
@@ -8413,7 +9067,7 @@ flows:
|
|
|
8413
9067
|
trigger: <new flows only> { kind: endpoint | action | page | listener | job, what: <plain words>, entry: <the HTTP method and the path, such as "GET /orders/{id}", or a topic> }
|
|
8414
9068
|
why: <one sentence: why this flow changes>
|
|
8415
9069
|
consumers: [<when the change breaks the flow's entry: every component that calls it today>]
|
|
8416
|
-
runsOn: <a new flow of a component that doesn't exist yet: where it runs> { component: <its id>, system: <the system it belongs to>, platform: <the platform it runs on>, cloud: <the cloud, or on-prem>, framework: <what it is built on> }
|
|
9070
|
+
runsOn: <a new flow of a component that doesn't exist yet: where it runs> { component: <its id>, system: <the system it belongs to>, platform: <the platform it runs on>, cloud: <the cloud, or on-prem>, framework: <what it is built on>, type: <what it is: one of the organisation's component types, by id> }
|
|
8417
9071
|
steps:
|
|
8418
9072
|
- op: add | change | remove
|
|
8419
9073
|
step: <change and remove: the step's number in today's flow, as listed>
|
|
@@ -8477,19 +9131,20 @@ function splitAnswer(a) {
|
|
|
8477
9131
|
reads.push({ flow: d.flow, why: d.why });
|
|
8478
9132
|
return { deltas: a.flows.filter((d) => !isEmptyDelta(d)), reads };
|
|
8479
9133
|
}
|
|
9134
|
+
var SKETCH_RULE = "A person drew some of this change on the canvas: the sketch block holds their steps (their kind, the flow they are on, where each call or publish goes, which table a step reads or writes). Keep every one of those as drawn, on its flow, and fill in what a sketch can't say: its fields, errors, place in the code (at, new, uses), and the steps around it the change needs. Add new flows or steps only where the feature asks for them; never remove or move a drawn step.";
|
|
8480
9135
|
async function readDeltas(dir) {
|
|
8481
9136
|
const deltas = [];
|
|
8482
9137
|
const problems = [];
|
|
8483
|
-
for (const f of (await readdir10(
|
|
9138
|
+
for (const f of (await readdir10(join16(dir, TARGET_DIR2)).catch(() => [])).sort()) {
|
|
8484
9139
|
if (!f.endsWith(".yaml"))
|
|
8485
9140
|
continue;
|
|
8486
9141
|
try {
|
|
8487
|
-
const
|
|
8488
|
-
const r = FlowDelta.safeParse((0,
|
|
9142
|
+
const text4 = await readFile15(join16(dir, TARGET_DIR2, f), "utf8");
|
|
9143
|
+
const r = FlowDelta.safeParse((0, import_yaml14.parse)(text4));
|
|
8489
9144
|
if (r.success)
|
|
8490
9145
|
deltas.push(r.data);
|
|
8491
9146
|
else
|
|
8492
|
-
problems.push(...schemaProblems(`${TARGET_DIR2}/${f}`,
|
|
9147
|
+
problems.push(...schemaProblems(`${TARGET_DIR2}/${f}`, text4, r.error.issues));
|
|
8493
9148
|
} catch (e) {
|
|
8494
9149
|
problems.push(`${TARGET_DIR2}/${f}: ${e.message.split("\n")[0]}`);
|
|
8495
9150
|
}
|
|
@@ -8760,6 +9415,24 @@ function storeOfTable(e, table) {
|
|
|
8760
9415
|
return prefix && e.byId.get(prefix)?.kind === "DataStore" ? prefix : void 0;
|
|
8761
9416
|
}
|
|
8762
9417
|
var NUMBERED = /^([A-Za-z]*)(\d+)([^A-Za-z0-9]+)([A-Za-z0-9].*?)(\.[A-Za-z0-9]+)$/;
|
|
9418
|
+
var VERSIONED = /^([A-Za-z]*)(\d+(?:[._]\d+)*)(__)([A-Za-z0-9].*?)(\.[A-Za-z0-9]+)$/;
|
|
9419
|
+
var versionOf = (v) => v.split(/[._]/).map(Number);
|
|
9420
|
+
var cmpVersion = (a, b) => {
|
|
9421
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
9422
|
+
const d = (a[i] ?? 0) - (b[i] ?? 0);
|
|
9423
|
+
if (d)
|
|
9424
|
+
return d;
|
|
9425
|
+
}
|
|
9426
|
+
return 0;
|
|
9427
|
+
};
|
|
9428
|
+
function versionIn(name, p) {
|
|
9429
|
+
if (p.vsep) {
|
|
9430
|
+
const v = VERSIONED.exec(name);
|
|
9431
|
+
return v && v[1] === p.prefix && v[5] === p.ext ? versionOf(v[2]) : void 0;
|
|
9432
|
+
}
|
|
9433
|
+
const x = NUMBERED.exec(name);
|
|
9434
|
+
return x && x[1] === p.prefix && x[3] === p.sep && x[5] === p.ext ? [Number(x[2])] : void 0;
|
|
9435
|
+
}
|
|
8763
9436
|
function migrationsOf(e, store) {
|
|
8764
9437
|
const recs = e.records.filter((r) => r.kind === "Migration" && r.dataStore === store && r.file);
|
|
8765
9438
|
const nested = recs.length > 0 && recs.every((r) => r.file.split(/[\\/]/).length > 2 && fileOf2(r.file) === "migration.sql");
|
|
@@ -8769,7 +9442,22 @@ function migrationsOf(e, store) {
|
|
|
8769
9442
|
const all2 = parts.every((p) => p) && parts.length > 0;
|
|
8770
9443
|
const same = (k) => all2 && new Set(parts.map((p) => p[k])).size === 1;
|
|
8771
9444
|
let pattern;
|
|
8772
|
-
|
|
9445
|
+
const vparts = names.map((n) => VERSIONED.exec(n));
|
|
9446
|
+
const dotted = vparts.map((v) => v?.[2]?.match(/[._]/)?.[0]).find((x) => x);
|
|
9447
|
+
if (dotted && vparts.every((v) => v) && new Set(vparts.map((v) => v[1])).size === 1 && new Set(vparts.map((v) => v[5])).size === 1) {
|
|
9448
|
+
const at2 = vparts.reduce((a, b) => cmpVersion(versionOf(a[2]), versionOf(b[2])) >= 0 ? a : b);
|
|
9449
|
+
const newest = versionOf(at2[2]);
|
|
9450
|
+
pattern = {
|
|
9451
|
+
prefix: vparts[0][1],
|
|
9452
|
+
sep: "__",
|
|
9453
|
+
ext: vparts[0][5],
|
|
9454
|
+
width: 0,
|
|
9455
|
+
newest: newest[0],
|
|
9456
|
+
version: newest,
|
|
9457
|
+
vtext: at2[2].split(/[._]/),
|
|
9458
|
+
vsep: dotted
|
|
9459
|
+
};
|
|
9460
|
+
} else if (same(1) && same(3) && same(5)) {
|
|
8773
9461
|
const nums = parts.map((p) => p[2]);
|
|
8774
9462
|
const padded = nums.some((x) => x.startsWith("0")) && new Set(nums.map((x) => x.length)).size === 1;
|
|
8775
9463
|
pattern = {
|
|
@@ -8780,8 +9468,11 @@ function migrationsOf(e, store) {
|
|
|
8780
9468
|
newest: Math.max(...nums.map(Number))
|
|
8781
9469
|
};
|
|
8782
9470
|
}
|
|
8783
|
-
const
|
|
8784
|
-
|
|
9471
|
+
const files = [...names].sort((a, b) => {
|
|
9472
|
+
if (!pattern)
|
|
9473
|
+
return b.localeCompare(a);
|
|
9474
|
+
return cmpVersion(versionIn(b, pattern) ?? [-1], versionIn(a, pattern) ?? [-1]) || b.localeCompare(a);
|
|
9475
|
+
});
|
|
8785
9476
|
const folders = [
|
|
8786
9477
|
...new Set(recs.map((r) => unit(r.file).split(/[\\/]/).slice(0, -1).join("/")))
|
|
8787
9478
|
];
|
|
@@ -8804,6 +9495,12 @@ function nextMigration(m, words5) {
|
|
|
8804
9495
|
const p = m.pattern;
|
|
8805
9496
|
if (!p)
|
|
8806
9497
|
return void 0;
|
|
9498
|
+
if (p.version && p.vsep) {
|
|
9499
|
+
const next = [...p.vtext ?? p.version.map(String)];
|
|
9500
|
+
const last = next.at(-1);
|
|
9501
|
+
next[next.length - 1] = String(Number(last) + 1).padStart(last.length, "0");
|
|
9502
|
+
return `${p.prefix}${next.join(p.vsep)}__${words5}${p.ext}`;
|
|
9503
|
+
}
|
|
8807
9504
|
return `${p.prefix}${String(p.newest + 1).padStart(p.width, "0")}${p.sep}${words5}${p.ext}`;
|
|
8808
9505
|
}
|
|
8809
9506
|
function columnTypesOf(e, store) {
|
|
@@ -8833,17 +9530,17 @@ function migrationProblem(name, table, e, example, onFile = false) {
|
|
|
8833
9530
|
return void 0;
|
|
8834
9531
|
if (m?.files.includes(file)) {
|
|
8835
9532
|
const where2 = m.folder ? `, in ${m.folder}` : "";
|
|
8836
|
-
const stem4 = file.replace(/^[A-Za-z]*\d+[^A-Za-z0-9]
|
|
9533
|
+
const stem4 = file.replace(/^[A-Za-z]*\d+(?:[._]\d+)*(?:__|[^A-Za-z0-9]+)/, "").replace(/\.[A-Za-z0-9]+$/, "");
|
|
8837
9534
|
const next = nextMigration(m, stem4);
|
|
8838
9535
|
return `names the migration ${name}, which ${store} already has: a change to its tables is a new migration beside it${where2}: name the file ${next ?? `as its migrations are named (${m.files.slice(0, 3).join(", ")})`}.`;
|
|
8839
9536
|
}
|
|
8840
9537
|
const p = m?.pattern;
|
|
8841
9538
|
if (m && p) {
|
|
8842
|
-
const
|
|
8843
|
-
const fits =
|
|
9539
|
+
const v = versionIn(file, p);
|
|
9540
|
+
const fits = !!v && cmpVersion(v, p.version ?? [p.newest]) > 0;
|
|
8844
9541
|
if (fits)
|
|
8845
9542
|
return void 0;
|
|
8846
|
-
const what = m.tool === "flyway" ? `Flyway, the newest ${p.prefix}${p.newest}` : `the newest ${m.files[0]}`;
|
|
9543
|
+
const what = m.tool === "flyway" ? `Flyway, the newest ${p.prefix}${p.vtext && p.vsep ? p.vtext.join(p.vsep) : p.newest}` : `the newest ${m.files[0]}`;
|
|
8847
9544
|
const where2 = m.folder ? `, in ${m.folder}` : "";
|
|
8848
9545
|
const wordy = /^[A-Za-z0-9][\w.-]*$/.test(name.trim());
|
|
8849
9546
|
const stem4 = file.replace(/^[A-Za-z]*\d+[^A-Za-z0-9]+/, "").replace(/\.[A-Za-z0-9]+$/, "");
|
|
@@ -8892,15 +9589,15 @@ var PLACES_HOW = "at: path:line of the line it goes after, for code that changes
|
|
|
8892
9589
|
async function placeIssues(deltas, e, root) {
|
|
8893
9590
|
const out = [];
|
|
8894
9591
|
const read = async (repo, path) => root ? await readCheckoutFile(root, repo, path) : void 0;
|
|
8895
|
-
const linesOf2 = (
|
|
9592
|
+
const linesOf2 = (text4) => text4.replace(/\r\n?/g, "\n").replace(/\n$/, "").split("\n");
|
|
8896
9593
|
for (const d of deltas)
|
|
8897
9594
|
for (const [i, s] of d.steps.entries()) {
|
|
8898
9595
|
const change = `Change ${i + 1} to ${d.flow}`;
|
|
8899
9596
|
const issue = (key3, rule, message, blocking = true) => out.push({ rule, target: `${d.flow}#${i + 1}:${key3}`, message, blocking, repo: d.repo });
|
|
8900
9597
|
for (const u of usesOf(s)) {
|
|
8901
9598
|
const p = parseAt(u);
|
|
8902
|
-
const
|
|
8903
|
-
if (!p ||
|
|
9599
|
+
const text4 = p ? await read(d.repo, p.path) : void 0;
|
|
9600
|
+
if (!p || text4 === void 0 && !citesFile(e, d.repo, p.path)) {
|
|
8904
9601
|
issue("uses", "delta.uses-unknown", `${change} uses ${u}, but ${d.repo} has no such file in its checkout or the catalog: give path:line of the code it calls as the repo has it.`);
|
|
8905
9602
|
continue;
|
|
8906
9603
|
}
|
|
@@ -8908,7 +9605,7 @@ async function placeIssues(deltas, e, root) {
|
|
|
8908
9605
|
issue("uses", "delta.uses-no-line", `${change} uses ${u}, with no line: give path:line of the code it calls (the method's first line).`);
|
|
8909
9606
|
continue;
|
|
8910
9607
|
}
|
|
8911
|
-
const n =
|
|
9608
|
+
const n = text4 === void 0 ? void 0 : linesOf2(text4).length;
|
|
8912
9609
|
if (n !== void 0 && (p.line < 1 || p.line > n))
|
|
8913
9610
|
issue("uses", "delta.uses-line", `${change} uses line ${p.line} of ${p.path}, which has ${n} lines in ${d.repo}'s checkout: give the line of the code it calls.`);
|
|
8914
9611
|
}
|
|
@@ -8927,8 +9624,8 @@ async function placeIssues(deltas, e, root) {
|
|
|
8927
9624
|
}
|
|
8928
9625
|
if (s.at) {
|
|
8929
9626
|
const p = parseAt(s.at);
|
|
8930
|
-
const
|
|
8931
|
-
if (!p ||
|
|
9627
|
+
const text4 = p ? await read(d.repo, p.path) : void 0;
|
|
9628
|
+
if (!p || text4 === void 0 && !citesFile(e, d.repo, p.path)) {
|
|
8932
9629
|
issue("at", "delta.at-unknown", `${change} puts the new step at ${s.at}, but ${d.repo} has no such file in its checkout or the catalog: give the path as the repo has it, path:line of the line the step goes after. For code in a file the change creates, write new: <path> instead.`);
|
|
8933
9630
|
continue;
|
|
8934
9631
|
}
|
|
@@ -8936,7 +9633,7 @@ async function placeIssues(deltas, e, root) {
|
|
|
8936
9633
|
issue("at", "delta.at-no-line", `${change} puts the new step at ${s.at}, with no line. Say where its code goes: ${PLACES_HOW}.`);
|
|
8937
9634
|
continue;
|
|
8938
9635
|
}
|
|
8939
|
-
const lines =
|
|
9636
|
+
const lines = text4 === void 0 ? void 0 : linesOf2(text4);
|
|
8940
9637
|
if (lines && (p.line < 1 || p.line > lines.length)) {
|
|
8941
9638
|
issue("at", "delta.at-line", `${change} puts the new step after line ${p.line} of ${p.path}, which has ${lines.length} lines in ${d.repo}'s checkout: give the line it goes after.`);
|
|
8942
9639
|
continue;
|
|
@@ -9103,13 +9800,13 @@ function prototypeNewCallsSource(inputs) {
|
|
|
9103
9800
|
];
|
|
9104
9801
|
}
|
|
9105
9802
|
function storeFactsSource(e, p) {
|
|
9106
|
-
const
|
|
9107
|
-
return
|
|
9803
|
+
const text4 = storeFacts(e, storesFor(e, p), touchedTables(e, p));
|
|
9804
|
+
return text4 ? [
|
|
9108
9805
|
{
|
|
9109
9806
|
id: "store-facts",
|
|
9110
9807
|
kind: "catalog",
|
|
9111
9808
|
origin: "the catalog's data stores, tables, columns and migrations (deterministic)",
|
|
9112
|
-
text:
|
|
9809
|
+
text: text4,
|
|
9113
9810
|
classification: "internal",
|
|
9114
9811
|
priority: 90
|
|
9115
9812
|
}
|
|
@@ -9375,8 +10072,8 @@ function checkDeltas(deltas, e, p, reads = [], o = {}) {
|
|
|
9375
10072
|
g.migration = true;
|
|
9376
10073
|
}
|
|
9377
10074
|
}
|
|
9378
|
-
const
|
|
9379
|
-
if (kind && MOVES_DATA.has(kind) &&
|
|
10075
|
+
const named2 = fieldNames(s.fields).length + (s.store?.columns.length ?? 0);
|
|
10076
|
+
if (kind && MOVES_DATA.has(kind) && named2 === 0)
|
|
9380
10077
|
issue("delta.no-fields", at2, `Change ${i + 1} to ${t} (${kind}) names no fields: say what it sends, returns, reads or stores.`, d.repo);
|
|
9381
10078
|
});
|
|
9382
10079
|
if (today && d.change === "modify")
|
|
@@ -9477,10 +10174,10 @@ var GRPC_CODES = [
|
|
|
9477
10174
|
"UNAUTHENTICATED"
|
|
9478
10175
|
];
|
|
9479
10176
|
var pascal = (c) => c.toLowerCase().split("_").map((w) => w[0].toUpperCase() + w.slice(1)).join("");
|
|
9480
|
-
function grpcCodesIn(
|
|
10177
|
+
function grpcCodesIn(text4) {
|
|
9481
10178
|
return GRPC_CODES.filter((c) => {
|
|
9482
10179
|
const name = c.includes("_") ? pascal(c) : `(?:StatusCode|Status|codes|Code)\\.${pascal(c)}`;
|
|
9483
|
-
return new RegExp(`(^|[^A-Za-z_])(${c}|${name})([^A-Za-z_]|$)`).test(
|
|
10180
|
+
return new RegExp(`(^|[^A-Za-z_])(${c}|${name})([^A-Za-z_]|$)`).test(text4);
|
|
9484
10181
|
});
|
|
9485
10182
|
}
|
|
9486
10183
|
function newRpcStatuses(d, e) {
|
|
@@ -9738,7 +10435,12 @@ function renderRollback(deltas, e) {
|
|
|
9738
10435
|
rows.set(s.store.table, `| ${s.store.table} | writes ${s.store.columns.join(", ") || "rows"} (flow ${d.flow}) | A redeploy of the old code stops the new writes; no schema to undo | The rows the new code wrote stay; a person decides whether to keep them |`);
|
|
9739
10436
|
}
|
|
9740
10437
|
if (!rows.size)
|
|
9741
|
-
return [
|
|
10438
|
+
return [
|
|
10439
|
+
"## Rollout and rollback",
|
|
10440
|
+
"",
|
|
10441
|
+
"The change writes and alters no table, so it has nothing to migrate back: roll back by redeploying the previous version of each repo, in the reverse of the deploy order. Atticus wrote this from the target flows; a person confirms it in the architecture PR.",
|
|
10442
|
+
""
|
|
10443
|
+
];
|
|
9742
10444
|
return [
|
|
9743
10445
|
"## Rollout and rollback",
|
|
9744
10446
|
"",
|
|
@@ -9751,14 +10453,14 @@ function renderRollback(deltas, e) {
|
|
|
9751
10453
|
];
|
|
9752
10454
|
}
|
|
9753
10455
|
async function writeTargetFlows(dir, answer, estate, by = "atticus change architecture") {
|
|
9754
|
-
const readsFile =
|
|
9755
|
-
const deployFile =
|
|
10456
|
+
const readsFile = join16(dir, ...READS_FILE.split("/"));
|
|
10457
|
+
const deployFile = join16(dir, ...DEPLOY_FILE.split("/"));
|
|
9756
10458
|
const deploy = answer.deploy?.length ? answer.deploy : void 0;
|
|
9757
10459
|
const a = splitAnswer(answer);
|
|
9758
10460
|
const keep = new Set(a.deltas.map((d) => `${d.flow}.yaml`));
|
|
9759
|
-
for (const f of await readdir10(
|
|
10461
|
+
for (const f of await readdir10(join16(dir, TARGET_DIR2)).catch(() => []))
|
|
9760
10462
|
if (f.endsWith(".yaml") && !keep.has(f))
|
|
9761
|
-
await rm5(
|
|
10463
|
+
await rm5(join16(dir, TARGET_DIR2, f), { force: true });
|
|
9762
10464
|
const files = [];
|
|
9763
10465
|
for (const d of a.deltas) {
|
|
9764
10466
|
const today = d.change === "new" ? void 0 : estate.flowById.get(d.flow)?.flow;
|
|
@@ -9767,48 +10469,48 @@ async function writeTargetFlows(dir, answer, estate, by = "atticus change archit
|
|
|
9767
10469
|
schemaVersion: FLOW_DELTA_SCHEMA_VERSION,
|
|
9768
10470
|
...today ? { base: { steps: flowBase(today) } } : {}
|
|
9769
10471
|
};
|
|
9770
|
-
await writeFileAtomic(
|
|
9771
|
-
${(0,
|
|
10472
|
+
await writeFileAtomic(join16(dir, TARGET_DIR2, `${d.flow}.yaml`), `# The target of flow ${d.flow} (ADR 0087): the steps this change adds, changes or removes. Drafted by ${by}; base pins the version of today's flow it was written against (ADR 0111).
|
|
10473
|
+
${(0, import_yaml14.stringify)(FlowDelta.parse(pinned), { lineWidth: 0 })}`);
|
|
9772
10474
|
files.push(`${TARGET_DIR2}/${d.flow}.yaml`);
|
|
9773
10475
|
}
|
|
9774
|
-
const own = await
|
|
10476
|
+
const own = await readFile15(join16(dir, "design.md"), "utf8").catch(() => void 0);
|
|
9775
10477
|
if (own === void 0 || own.startsWith(MARK)) {
|
|
9776
|
-
await writeFileAtomic(
|
|
10478
|
+
await writeFileAtomic(join16(dir, "design.md"), renderArchitecture(a.deltas, estate, a.reads, deploy));
|
|
9777
10479
|
files.push("design.md");
|
|
9778
10480
|
}
|
|
9779
10481
|
if (deploy)
|
|
9780
10482
|
await writeFileAtomic(deployFile, `# The order the change deploys in across its repos (ADR 0124 addendum B-arch), as the architecture stage drafted it and its checks passed. Atticus writes this file.
|
|
9781
|
-
${(0,
|
|
10483
|
+
${(0, import_yaml14.stringify)({ deploy }, { lineWidth: 0 })}`);
|
|
9782
10484
|
else
|
|
9783
10485
|
await rm5(deployFile, { force: true });
|
|
9784
10486
|
if (a.reads.length)
|
|
9785
10487
|
await writeFileAtomic(readsFile, `# The flows the change reads without changing a step (ADR 0087): context, not target flows. Atticus writes this file.
|
|
9786
|
-
${(0,
|
|
10488
|
+
${(0, import_yaml14.stringify)({ reads: a.reads }, { lineWidth: 0 })}`);
|
|
9787
10489
|
else
|
|
9788
10490
|
await rm5(readsFile, { force: true });
|
|
9789
10491
|
const data = renderData(a.deltas, estate);
|
|
9790
|
-
const ownData = await
|
|
10492
|
+
const ownData = await readFile15(join16(dir, "data.md"), "utf8").catch(() => void 0);
|
|
9791
10493
|
if (data && (ownData === void 0 || ownData.startsWith(MARK))) {
|
|
9792
|
-
await writeFileAtomic(
|
|
10494
|
+
await writeFileAtomic(join16(dir, "data.md"), data);
|
|
9793
10495
|
files.push("data.md");
|
|
9794
10496
|
} else if (!data && ownData?.startsWith(MARK))
|
|
9795
|
-
await rm5(
|
|
10497
|
+
await rm5(join16(dir, "data.md"), { force: true });
|
|
9796
10498
|
return files;
|
|
9797
10499
|
}
|
|
9798
10500
|
async function locateInTargetFlows(dir, rel, target) {
|
|
9799
|
-
const files = (await readdir10(
|
|
10501
|
+
const files = (await readdir10(join16(dir, TARGET_DIR2)).catch(() => [])).filter((f) => f.endsWith(".yaml")).sort();
|
|
9800
10502
|
const docs = [];
|
|
9801
10503
|
for (const f of files) {
|
|
9802
|
-
const
|
|
9803
|
-
if (
|
|
10504
|
+
const text4 = await readFile15(join16(dir, TARGET_DIR2, f), "utf8").catch(() => void 0);
|
|
10505
|
+
if (text4 === void 0)
|
|
9804
10506
|
continue;
|
|
9805
|
-
const lc = new
|
|
9806
|
-
docs.push({ file: f, text:
|
|
10507
|
+
const lc = new import_yaml14.LineCounter();
|
|
10508
|
+
docs.push({ file: f, text: text4, doc: (0, import_yaml14.parseDocument)(text4, { lineCounter: lc }), lc });
|
|
9807
10509
|
}
|
|
9808
10510
|
const at2 = (d, offset) => `${rel}/${TARGET_DIR2}/${d.file}:${d.lc.linePos(offset).line}`;
|
|
9809
10511
|
const steps = (d) => {
|
|
9810
10512
|
const s = d.doc.get("steps");
|
|
9811
|
-
return (0,
|
|
10513
|
+
return (0, import_yaml14.isSeq)(s) ? s.items : [];
|
|
9812
10514
|
};
|
|
9813
10515
|
const m = /^(.+?)#(\d+)(?::.*)?$/.exec(target);
|
|
9814
10516
|
const flow = m ? m[1] : target;
|
|
@@ -9823,8 +10525,8 @@ async function locateInTargetFlows(dir, rel, target) {
|
|
|
9823
10525
|
let first;
|
|
9824
10526
|
for (const d of docs)
|
|
9825
10527
|
for (const item of steps(d)) {
|
|
9826
|
-
const store = (0,
|
|
9827
|
-
if (!(0,
|
|
10528
|
+
const store = (0, import_yaml14.isMap)(item) ? item.get("store") : void 0;
|
|
10529
|
+
if (!(0, import_yaml14.isMap)(store) || store.get("table") !== table)
|
|
9828
10530
|
continue;
|
|
9829
10531
|
const where2 = item.range;
|
|
9830
10532
|
const here = where2 ? at2(d, where2[0]) : void 0;
|
|
@@ -9838,10 +10540,10 @@ async function readTargetFlows(dir) {
|
|
|
9838
10540
|
const r = await readDeltas(dir);
|
|
9839
10541
|
if (!r.deltas.length)
|
|
9840
10542
|
return void 0;
|
|
9841
|
-
const t = await
|
|
9842
|
-
const reads = t === void 0 ? void 0 : external_exports.array(ReadFlow).safeParse((0,
|
|
9843
|
-
const dt = await
|
|
9844
|
-
const deploy = dt === void 0 ? void 0 : external_exports.array(DeployStep).safeParse((0,
|
|
10543
|
+
const t = await readFile15(join16(dir, ...READS_FILE.split("/")), "utf8").catch(() => void 0);
|
|
10544
|
+
const reads = t === void 0 ? void 0 : external_exports.array(ReadFlow).safeParse((0, import_yaml14.parse)(t)?.reads);
|
|
10545
|
+
const dt = await readFile15(join16(dir, ...DEPLOY_FILE.split("/")), "utf8").catch(() => void 0);
|
|
10546
|
+
const deploy = dt === void 0 ? void 0 : external_exports.array(DeployStep).safeParse((0, import_yaml14.parse)(dt)?.deploy);
|
|
9845
10547
|
return {
|
|
9846
10548
|
flows: r.deltas,
|
|
9847
10549
|
reads: reads?.success ? reads.data : [],
|
|
@@ -9866,6 +10568,13 @@ async function architectureStage(root, changeId, o) {
|
|
|
9866
10568
|
const flowsText = touched.map((f) => `# ${f.flow.id} (repo ${f.repo}). Steps are numbered from 1 in the order listed.
|
|
9867
10569
|
${serializeRecord(f.flow)}`).join("\n---\n");
|
|
9868
10570
|
const osInstructions = await openspecInstructionSource(root, change.id, "architecture");
|
|
10571
|
+
const drawn = /drawn on the canvas by .+?, stroke [a-z0-9]+/i;
|
|
10572
|
+
const sketched = (await readDeltas(dir)).deltas.map((d) => ({ ...d, steps: d.steps.filter((s) => drawn.test(s.why ?? "")) })).filter((d) => d.steps.length);
|
|
10573
|
+
const componentTypes = effectiveComponentTypes((await loadOrgPack(orgPackDirOf(root)).catch(() => void 0))?.componentTypes ?? []);
|
|
10574
|
+
const typeRules = [
|
|
10575
|
+
`Component types (runsOn.type of a new flow): ${componentTypes.map((t) => t.id).join(", ")}.`,
|
|
10576
|
+
componentTypeRulesText(componentTypes, orgPackRelOf(root))
|
|
10577
|
+
].filter(Boolean).join("\n");
|
|
9869
10578
|
const attached = await changeInputSources(dir, rel);
|
|
9870
10579
|
const newCalls = prototypeNewCallsSource(attached);
|
|
9871
10580
|
const sources = [
|
|
@@ -9875,7 +10584,7 @@ ${serializeRecord(f.flow)}`).join("\n---\n");
|
|
|
9875
10584
|
id: "feature.yaml",
|
|
9876
10585
|
kind: "artifact",
|
|
9877
10586
|
origin: `${rel}/feature.yaml`,
|
|
9878
|
-
text: (await
|
|
10587
|
+
text: (await readFile15(join16(dir, "feature.yaml"), "utf8")).replace(/\r\n/g, "\n"),
|
|
9879
10588
|
classification: "internal",
|
|
9880
10589
|
priority: 100
|
|
9881
10590
|
},
|
|
@@ -9887,6 +10596,30 @@ ${serializeRecord(f.flow)}`).join("\n---\n");
|
|
|
9887
10596
|
classification: "internal",
|
|
9888
10597
|
priority: 95
|
|
9889
10598
|
},
|
|
10599
|
+
// ADR 0180: what the organisation's component types must and must not do, for a new
|
|
10600
|
+
// component the stage drafts (data, never instructions); nothing when no type says anything.
|
|
10601
|
+
...typeRules ? [
|
|
10602
|
+
{
|
|
10603
|
+
id: "component-types",
|
|
10604
|
+
kind: "artifact",
|
|
10605
|
+
origin: `${orgPackRelOf(root)}/component-types/`,
|
|
10606
|
+
text: typeRules,
|
|
10607
|
+
classification: "internal",
|
|
10608
|
+
priority: 90
|
|
10609
|
+
}
|
|
10610
|
+
] : [],
|
|
10611
|
+
// ADR 0179: what a person drew on the canvas is the frame the stage fills in, never redrawn.
|
|
10612
|
+
...sketched.length ? [
|
|
10613
|
+
{
|
|
10614
|
+
id: "sketch",
|
|
10615
|
+
kind: "artifact",
|
|
10616
|
+
origin: `${rel}/${TARGET_DIR2}/ (drawn on the canvas)`,
|
|
10617
|
+
text: sketched.map((d) => `# ${d.flow}
|
|
10618
|
+
${(0, import_yaml14.stringify)(d, { lineWidth: 0 })}`).join("\n---\n"),
|
|
10619
|
+
classification: "internal",
|
|
10620
|
+
priority: 98
|
|
10621
|
+
}
|
|
10622
|
+
] : [],
|
|
9890
10623
|
// ADR 0111 addendum: how the stores the change may write name their migrations and types.
|
|
9891
10624
|
...storeFactsSource(estate, placement),
|
|
9892
10625
|
// S2: the values the code allows in the columns of the tables the change may store into.
|
|
@@ -9957,12 +10690,13 @@ ${serializeRecord(f.flow)}`).join("\n---\n");
|
|
|
9957
10690
|
formatFor(classificationLevels(ctx)),
|
|
9958
10691
|
"",
|
|
9959
10692
|
SELF_CHECK_RULE,
|
|
10693
|
+
...sketched.length ? ["", SKETCH_RULE] : [],
|
|
9960
10694
|
...newCalls.length ? ["", PROTOTYPE_NEW_RULE] : [],
|
|
9961
10695
|
...past ? ["", past.rule] : []
|
|
9962
10696
|
].join("\n");
|
|
9963
10697
|
const patternById = new Map(patterns.map((i) => [i.id, i]));
|
|
9964
10698
|
const selected = await estateContext(root, "architecture", {
|
|
9965
|
-
text: [(0,
|
|
10699
|
+
text: [(0, import_yaml14.stringify)(placement, { lineWidth: 0 }), flowsText].join("\n\n"),
|
|
9966
10700
|
title: change.title,
|
|
9967
10701
|
ids: [...placement.flows.map((f) => f.id), ...placement.features.map((f) => f.id)]
|
|
9968
10702
|
}, {
|
|
@@ -10008,8 +10742,8 @@ ${serializeRecord(f.flow)}`).join("\n---\n");
|
|
|
10008
10742
|
instructions: instructions3,
|
|
10009
10743
|
...context.gaps?.length ? { gaps: context.gaps } : {},
|
|
10010
10744
|
sources,
|
|
10011
|
-
parse: (
|
|
10012
|
-
const r = parseYamlAs(
|
|
10745
|
+
parse: (text4) => {
|
|
10746
|
+
const r = parseYamlAs(text4, Answer3, (d) => ({
|
|
10013
10747
|
// The version and the pin are Atticus's (ADR 0111), never the assistant's.
|
|
10014
10748
|
flows: (Array.isArray(d.flows) ? d.flows : []).map((f) => {
|
|
10015
10749
|
const { base: _base, schemaVersion: _v, ...rest } = f ?? {};
|
|
@@ -10038,7 +10772,7 @@ ${serializeRecord(f.flow)}`).join("\n---\n");
|
|
|
10038
10772
|
// B-places: an added step's place is a file of its repo, a line it has, a function in it.
|
|
10039
10773
|
...await placeIssues(a.flows, estate, root),
|
|
10040
10774
|
// W5: an endpoint the brief names whose flow the design leaves alone (a note, not blocking).
|
|
10041
|
-
...briefEndpointNotes(await
|
|
10775
|
+
...briefEndpointNotes(await readFile15(join16(dir, "brief.md"), "utf8").catch(() => ""), estate, a.flows)
|
|
10042
10776
|
],
|
|
10043
10777
|
write: (answer) => writeTargetFlows(dir, answer, estate),
|
|
10044
10778
|
reviewSources: (a) => [mergedFlowsSource(a.flows, estate, rel)],
|
|
@@ -10059,14 +10793,14 @@ ${serializeRecord(f.flow)}`).join("\n---\n");
|
|
|
10059
10793
|
var classificationLevels = (ctx) => ctx.orgPack.pack?.classification ?? ["public", "internal", "confidential", "restricted"];
|
|
10060
10794
|
|
|
10061
10795
|
// packages/planner/dist/stages/stories.js
|
|
10062
|
-
var
|
|
10063
|
-
import { mkdir as mkdir7, readdir as readdir14, readFile as
|
|
10064
|
-
import { join as
|
|
10796
|
+
var import_yaml18 = __toESM(require_dist(), 1);
|
|
10797
|
+
import { mkdir as mkdir7, readdir as readdir14, readFile as readFile20, rm as rm6 } from "node:fs/promises";
|
|
10798
|
+
import { join as join22 } from "node:path";
|
|
10065
10799
|
|
|
10066
10800
|
// packages/planner/dist/needs.js
|
|
10067
|
-
var
|
|
10068
|
-
import { readdir as readdir11, readFile as
|
|
10069
|
-
import { join as
|
|
10801
|
+
var import_yaml15 = __toESM(require_dist(), 1);
|
|
10802
|
+
import { readdir as readdir11, readFile as readFile16 } from "node:fs/promises";
|
|
10803
|
+
import { join as join17 } from "node:path";
|
|
10070
10804
|
var NEEDS_FILE = "needs.yaml";
|
|
10071
10805
|
var NEEDS_MD = "needs.md";
|
|
10072
10806
|
var NEEDS_OUTPUT = "change-needs.yaml";
|
|
@@ -10147,11 +10881,34 @@ var daysOf = (root, r) => {
|
|
|
10147
10881
|
var stamp = (d) => d.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
10148
10882
|
var key = (n) => `${n.item}|${n.for}`;
|
|
10149
10883
|
var fold4 = (s) => s.replace(/[‘’]/g, "'").replace(/[“”]/g, '"').replace(/\s+/g, " ").trim().toLowerCase();
|
|
10884
|
+
function lineAtFolded(text4, at2) {
|
|
10885
|
+
let count = 0;
|
|
10886
|
+
let pending = false;
|
|
10887
|
+
let line = 1;
|
|
10888
|
+
for (const ch of text4) {
|
|
10889
|
+
if (ch === "\n") {
|
|
10890
|
+
if (count < at2)
|
|
10891
|
+
line++;
|
|
10892
|
+
else
|
|
10893
|
+
break;
|
|
10894
|
+
}
|
|
10895
|
+
if (/\s/.test(ch)) {
|
|
10896
|
+
if (count > 0)
|
|
10897
|
+
pending = true;
|
|
10898
|
+
} else {
|
|
10899
|
+
if (pending)
|
|
10900
|
+
count++;
|
|
10901
|
+
pending = false;
|
|
10902
|
+
count += ch.toLowerCase().length;
|
|
10903
|
+
}
|
|
10904
|
+
}
|
|
10905
|
+
return line;
|
|
10906
|
+
}
|
|
10150
10907
|
async function readNeeds(root, change) {
|
|
10151
|
-
const
|
|
10152
|
-
if (
|
|
10908
|
+
const text4 = await readFile16(join17(changeFolder(root, change), NEEDS_FILE), "utf8").catch(() => void 0);
|
|
10909
|
+
if (text4 === void 0)
|
|
10153
10910
|
return void 0;
|
|
10154
|
-
const r = NeedsFile.safeParse((0,
|
|
10911
|
+
const r = NeedsFile.safeParse((0, import_yaml15.parse)(text4));
|
|
10155
10912
|
return r.success ? r.data : void 0;
|
|
10156
10913
|
}
|
|
10157
10914
|
async function changeFiles(dir) {
|
|
@@ -10164,14 +10921,14 @@ async function changeFiles(dir) {
|
|
|
10164
10921
|
"interfaces.md",
|
|
10165
10922
|
"tasks.md"
|
|
10166
10923
|
]) {
|
|
10167
|
-
const t = await
|
|
10924
|
+
const t = await readFile16(join17(dir, f), "utf8").catch(() => void 0);
|
|
10168
10925
|
if (t !== void 0)
|
|
10169
10926
|
out.set(f, t.replace(/\r\n/g, "\n"));
|
|
10170
10927
|
}
|
|
10171
10928
|
for (const sub of ["stories", "target-flows"])
|
|
10172
|
-
for (const f of (await readdir11(
|
|
10929
|
+
for (const f of (await readdir11(join17(dir, sub)).catch(() => [])).sort())
|
|
10173
10930
|
if (/\.(md|ya?ml)$/.test(f))
|
|
10174
|
-
out.set(`${sub}/${f}`, (await
|
|
10931
|
+
out.set(`${sub}/${f}`, (await readFile16(join17(dir, sub, f), "utf8")).replace(/\r\n/g, "\n"));
|
|
10175
10932
|
return out;
|
|
10176
10933
|
}
|
|
10177
10934
|
function changeFile(files, cited, change) {
|
|
@@ -10185,10 +10942,10 @@ function changeFile(files, cited, change) {
|
|
|
10185
10942
|
}
|
|
10186
10943
|
async function targetsOf(dir, files) {
|
|
10187
10944
|
const out = /* @__PURE__ */ new Set(["change"]);
|
|
10188
|
-
const feature = await
|
|
10945
|
+
const feature = await readFile16(join17(dir, "feature.yaml"), "utf8").catch(() => void 0);
|
|
10189
10946
|
if (feature)
|
|
10190
10947
|
try {
|
|
10191
|
-
const f = (0,
|
|
10948
|
+
const f = (0, import_yaml15.parse)(feature);
|
|
10192
10949
|
for (const x of f.features ?? [])
|
|
10193
10950
|
if (x.id)
|
|
10194
10951
|
out.add(`feature:${x.id}`);
|
|
@@ -10333,10 +11090,10 @@ function renderNeeds(f, reviews = {}) {
|
|
|
10333
11090
|
}
|
|
10334
11091
|
async function write2(root, f) {
|
|
10335
11092
|
const dir = changeFolder(root, f.change);
|
|
10336
|
-
await writeFileAtomic(
|
|
10337
|
-
${(0,
|
|
11093
|
+
await writeFileAtomic(join17(dir, NEEDS_FILE), `# What this change needs from the organisation (ADR 0101). atticus change needs writes it; status, note, by and at are a person's.
|
|
11094
|
+
${(0, import_yaml15.stringify)(NeedsFile.parse(f), { lineWidth: 0 })}`);
|
|
10338
11095
|
const k = await readKnowledge(root, { cacheRoot: root });
|
|
10339
|
-
await writeFileAtomic(
|
|
11096
|
+
await writeFileAtomic(join17(dir, NEEDS_MD), renderNeeds(f, k.reviews));
|
|
10340
11097
|
}
|
|
10341
11098
|
var INSTRUCTIONS3 = (targets) => `# Task: what this change needs from the organisation
|
|
10342
11099
|
|
|
@@ -10409,11 +11166,11 @@ async function findNeeds(root, change, o) {
|
|
|
10409
11166
|
classification: "public",
|
|
10410
11167
|
trusted: true
|
|
10411
11168
|
},
|
|
10412
|
-
...[...files].map(([f,
|
|
11169
|
+
...[...files].map(([f, text4], i) => ({
|
|
10413
11170
|
id: `change-${String(i + 1).padStart(3, "0")}`,
|
|
10414
11171
|
kind: "document",
|
|
10415
11172
|
origin: `${change}/${f}`,
|
|
10416
|
-
text:
|
|
11173
|
+
text: text4,
|
|
10417
11174
|
classification: "internal",
|
|
10418
11175
|
priority: 20
|
|
10419
11176
|
}))
|
|
@@ -10551,7 +11308,7 @@ async function findNeeds(root, change, o) {
|
|
|
10551
11308
|
problems.push(...run.errors.length ? run.errors : [`the run ended ${run.state}`]);
|
|
10552
11309
|
else
|
|
10553
11310
|
try {
|
|
10554
|
-
const a = AnswerIn.safeParse((0,
|
|
11311
|
+
const a = AnswerIn.safeParse((0, import_yaml15.parse)(unfence2(run.output)));
|
|
10555
11312
|
if (a.success)
|
|
10556
11313
|
answer = a.data;
|
|
10557
11314
|
else
|
|
@@ -10577,23 +11334,17 @@ async function findNeeds(root, change, o) {
|
|
|
10577
11334
|
continue;
|
|
10578
11335
|
}
|
|
10579
11336
|
const file = changeFile(files, n.because.file, change);
|
|
10580
|
-
const
|
|
11337
|
+
const text4 = file !== void 0 ? files.get(file) : void 0;
|
|
10581
11338
|
const words5 = fold4(n.because.quote).replace(/[\s.,;:!?]+$/, "");
|
|
10582
|
-
const at2 =
|
|
10583
|
-
if (!
|
|
10584
|
-
problems.push(
|
|
11339
|
+
const at2 = text4 && words5 ? fold4(text4).indexOf(words5) : -1;
|
|
11340
|
+
if (!text4 || at2 < 0) {
|
|
11341
|
+
problems.push(text4 ? `${n.item} quotes ${n.because.file}, but "${n.because.quote}" isn't in it` : `${n.item} quotes ${n.because.file}, which isn't one of the change's files`);
|
|
10585
11342
|
continue;
|
|
10586
11343
|
}
|
|
10587
11344
|
if (seen.has(key(n)))
|
|
10588
11345
|
continue;
|
|
10589
11346
|
seen.add(key(n));
|
|
10590
|
-
const
|
|
10591
|
-
let line = 1;
|
|
10592
|
-
for (let j = 1; j < lines.length; j++)
|
|
10593
|
-
if (fold4(lines.slice(0, j).join("\n")).length < at2)
|
|
10594
|
-
line = j + 1;
|
|
10595
|
-
else
|
|
10596
|
-
break;
|
|
11347
|
+
const line = lineAtFolded(text4, at2);
|
|
10597
11348
|
fresh.push(needFrom(r, {
|
|
10598
11349
|
item: n.item,
|
|
10599
11350
|
for: n.for,
|
|
@@ -10693,15 +11444,15 @@ async function mergeStoryRequests(root, change, requests, now) {
|
|
|
10693
11444
|
}
|
|
10694
11445
|
|
|
10695
11446
|
// packages/planner/dist/task-knowledge.js
|
|
10696
|
-
var
|
|
10697
|
-
import { readdir as readdir12, readFile as
|
|
10698
|
-
import { join as
|
|
11447
|
+
var import_yaml16 = __toESM(require_dist(), 1);
|
|
11448
|
+
import { readdir as readdir12, readFile as readFile17 } from "node:fs/promises";
|
|
11449
|
+
import { join as join19 } from "node:path";
|
|
10699
11450
|
|
|
10700
11451
|
// packages/planner/dist/knowledge-conflicts.js
|
|
10701
|
-
import { join as
|
|
11452
|
+
import { join as join18 } from "node:path";
|
|
10702
11453
|
var pageFile = (page) => /\.(md|markdown)$/i.test(page) ? page : `${page}.md`;
|
|
10703
11454
|
async function codeFactRecords(root) {
|
|
10704
|
-
return (await readCatalog(
|
|
11455
|
+
return (await readCatalog(join18(catalogDirOf(root), "generated"), { cacheRoot: root }).catch(() => void 0))?.records ?? [];
|
|
10705
11456
|
}
|
|
10706
11457
|
function conflictTouches(v, used) {
|
|
10707
11458
|
if (v.conflict.about.some((id) => used.has(id)))
|
|
@@ -10808,9 +11559,9 @@ function sectionParts(id) {
|
|
|
10808
11559
|
const slash = path.indexOf("/");
|
|
10809
11560
|
return slash < 0 ? { source: "", page: path, heading } : { source: path.slice(0, slash), page: path.slice(slash + 1), heading };
|
|
10810
11561
|
}
|
|
10811
|
-
function listSteps(
|
|
11562
|
+
function listSteps(text4) {
|
|
10812
11563
|
const out = [];
|
|
10813
|
-
for (const l of
|
|
11564
|
+
for (const l of text4.split("\n")) {
|
|
10814
11565
|
const m = /^\s*(?:\d+[.)]|[-*])\s+(.*)$/.exec(l);
|
|
10815
11566
|
if (m && m[1].trim())
|
|
10816
11567
|
out.push(m[1].trim());
|
|
@@ -10932,8 +11683,8 @@ function disagree(v, id, other) {
|
|
|
10932
11683
|
const pages = itemPages(other);
|
|
10933
11684
|
return v.conflict.pages.some((p) => pages.includes(`${p.source}/${pageFile2(p.page)}`));
|
|
10934
11685
|
}
|
|
10935
|
-
function placesNamed(
|
|
10936
|
-
const t =
|
|
11686
|
+
function placesNamed(text4, input) {
|
|
11687
|
+
const t = text4.toLowerCase();
|
|
10937
11688
|
const out = [];
|
|
10938
11689
|
for (const id of [...input.targets ?? [], ...input.ids])
|
|
10939
11690
|
if (id.length > 3 && t.includes(id.toLowerCase()) && !out.includes(id))
|
|
@@ -10978,13 +11729,13 @@ async function placesContext(root, records) {
|
|
|
10978
11729
|
}
|
|
10979
11730
|
}
|
|
10980
11731
|
const repos = /* @__PURE__ */ new Map();
|
|
10981
|
-
const profiles =
|
|
11732
|
+
const profiles = join19(orgPackDirOf(root), "profiles");
|
|
10982
11733
|
for (const d of await readdir12(profiles).catch(() => [])) {
|
|
10983
|
-
const
|
|
10984
|
-
if (
|
|
11734
|
+
const text4 = await readFile17(join19(profiles, d, "repos.manifest.yaml"), "utf8").catch(() => void 0);
|
|
11735
|
+
if (text4 === void 0)
|
|
10985
11736
|
continue;
|
|
10986
11737
|
try {
|
|
10987
|
-
for (const r of (0,
|
|
11738
|
+
for (const r of (0, import_yaml16.parse)(text4)?.repos ?? []) {
|
|
10988
11739
|
const id = str4(r.id);
|
|
10989
11740
|
if (!id)
|
|
10990
11741
|
continue;
|
|
@@ -11003,7 +11754,7 @@ async function placesContext(root, records) {
|
|
|
11003
11754
|
const fw = /* @__PURE__ */ new Map();
|
|
11004
11755
|
const frameworks = (repo) => {
|
|
11005
11756
|
if (!fw.has(repo))
|
|
11006
|
-
fw.set(repo,
|
|
11757
|
+
fw.set(repo, readFile17(join19(factsDirOf(root), `${repo}.jsonl`), "utf8").then((t) => {
|
|
11007
11758
|
const out = /* @__PURE__ */ new Set();
|
|
11008
11759
|
for (const l of t.split("\n")) {
|
|
11009
11760
|
if (!l.includes('"framework"'))
|
|
@@ -11173,7 +11924,7 @@ async function selectTaskKnowledge(root, change, inputs, o = {}) {
|
|
|
11173
11924
|
const said2 = wordsOf(input.changes);
|
|
11174
11925
|
const changes = new Map([...said2].filter(([s]) => !context.has(s)));
|
|
11175
11926
|
const all2 = new Map([...context, ...wordsOf([input.title]), ...said2]);
|
|
11176
|
-
const
|
|
11927
|
+
const named2 = namesOf(input.changes);
|
|
11177
11928
|
const kept = /* @__PURE__ */ new Map();
|
|
11178
11929
|
const cands = candidatesOf2(sel);
|
|
11179
11930
|
const near = o.keep === "words" ? void 0 : await ix.closeness([input.changes.join("; ")], cands.flatMap((c) => nodeKeys(c, items, itemOfPage)));
|
|
@@ -11233,7 +11984,7 @@ async function selectTaskKnowledge(root, change, inputs, o = {}) {
|
|
|
11233
11984
|
const cos = cosOf(c, item);
|
|
11234
11985
|
const always = c.named || target || isConfirmed && shared.length > 0;
|
|
11235
11986
|
const close = cos === void 0 ? false : matched.length === 1 ? cos >= KEEP_ONE_WORD_CLOSE : matched.length === 0 && cos >= KEEP_BY_MEANING;
|
|
11236
|
-
const byWords = cos === void 0 ? matched.length > 0 : matched.length >= KEEP_WORDS || matched.some((w) =>
|
|
11987
|
+
const byWords = cos === void 0 ? matched.length > 0 : matched.length >= KEEP_WORDS || matched.some((w) => named2.has(w));
|
|
11237
11988
|
const terms = (matched.length ? matched : shared).map((w) => all2.get(w) ?? w);
|
|
11238
11989
|
const words5 = terms.slice(0, 4).map((w) => `"${w}"`);
|
|
11239
11990
|
const where2 = places2.length ? ` It names ${and2(places2)}, where this task works.` : "";
|
|
@@ -11549,11 +12300,11 @@ function groupNearDuplicates(ks) {
|
|
|
11549
12300
|
|
|
11550
12301
|
// packages/planner/dist/stages/openspec-seed.js
|
|
11551
12302
|
import { existsSync as existsSync2 } from "node:fs";
|
|
11552
|
-
import { readdir as readdir13, readFile as
|
|
11553
|
-
import { join as
|
|
12303
|
+
import { readdir as readdir13, readFile as readFile18 } from "node:fs/promises";
|
|
12304
|
+
import { join as join20 } from "node:path";
|
|
11554
12305
|
var SEED_MARK = "<!-- Generated by atticus spec seed from the repo's verified flows. Run it again to refresh it; delete this line to keep your edits. A person accepts the seed with atticus spec accept <repo>. -->";
|
|
11555
12306
|
var MAX_REQUIREMENT2 = 480;
|
|
11556
|
-
var
|
|
12307
|
+
var slug4 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
11557
12308
|
var bare3 = (s) => s.trim().replace(/\s+/g, " ").replace(/[.\s]+$/, "");
|
|
11558
12309
|
var lowerFirst = (s) => /^[A-Z][a-z]*(\s|$)/.test(s) ? `${s[0].toLowerCase()}${s.slice(1)}` : s;
|
|
11559
12310
|
var clip2 = (s, n) => {
|
|
@@ -11562,7 +12313,7 @@ var clip2 = (s, n) => {
|
|
|
11562
12313
|
const cut = s.slice(0, n - 1);
|
|
11563
12314
|
return `${cut.slice(0, Math.max(cut.lastIndexOf(" "), n / 2)).trimEnd()}\u2026`;
|
|
11564
12315
|
};
|
|
11565
|
-
var seedChangeId = (repo) => `seed-${
|
|
12316
|
+
var seedChangeId = (repo) => `seed-${slug4(repo)}`.slice(0, 80).replace(/-+$/, "");
|
|
11566
12317
|
function citeOf2(evidence, repo) {
|
|
11567
12318
|
const seen = /* @__PURE__ */ new Set();
|
|
11568
12319
|
for (const e of evidence) {
|
|
@@ -11692,8 +12443,8 @@ function renderSeedProposal(repo, deltas, skipped, flows) {
|
|
|
11692
12443
|
""
|
|
11693
12444
|
].join("\n");
|
|
11694
12445
|
}
|
|
11695
|
-
function addedFlowsOf(
|
|
11696
|
-
const sections =
|
|
12446
|
+
function addedFlowsOf(text4) {
|
|
12447
|
+
const sections = text4.replace(/\r\n/g, "\n").split(/^## /m);
|
|
11697
12448
|
const added = sections.find((x) => /^ADDED Requirements\b/.test(x));
|
|
11698
12449
|
if (added === void 0)
|
|
11699
12450
|
return [];
|
|
@@ -11707,12 +12458,12 @@ function addedFlowsOf(text2) {
|
|
|
11707
12458
|
}
|
|
11708
12459
|
async function addedIn(dir) {
|
|
11709
12460
|
const out = [];
|
|
11710
|
-
const caps = (await readdir13(
|
|
12461
|
+
const caps = (await readdir13(join20(dir, "specs")).catch(() => [])).sort();
|
|
11711
12462
|
for (const c of caps) {
|
|
11712
|
-
const
|
|
11713
|
-
if (
|
|
12463
|
+
const text4 = await readFile18(join20(dir, "specs", c, "spec.md"), "utf8").catch(() => void 0);
|
|
12464
|
+
if (text4 === void 0 || !/^## ADDED Requirements\b/m.test(text4))
|
|
11714
12465
|
continue;
|
|
11715
|
-
out.push({ capability: c, flows: addedFlowsOf(
|
|
12466
|
+
out.push({ capability: c, flows: addedFlowsOf(text4) });
|
|
11716
12467
|
}
|
|
11717
12468
|
return out;
|
|
11718
12469
|
}
|
|
@@ -11721,10 +12472,10 @@ async function openSeeds(root) {
|
|
|
11721
12472
|
const out = [];
|
|
11722
12473
|
const ids = (await readdir13(base, { withFileTypes: true }).catch(() => [])).filter((d) => d.isDirectory() && d.name.startsWith("seed-")).map((d) => d.name).sort();
|
|
11723
12474
|
for (const id of ids) {
|
|
11724
|
-
const dir =
|
|
11725
|
-
if (!existsSync2(
|
|
12475
|
+
const dir = join20(base, id);
|
|
12476
|
+
if (!existsSync2(join20(dir, ".openspec.yaml")))
|
|
11726
12477
|
continue;
|
|
11727
|
-
const proposal = await
|
|
12478
|
+
const proposal = await readFile18(join20(dir, "proposal.md"), "utf8").catch(() => "");
|
|
11728
12479
|
const repo = /^# Proposal: Starting spec for (\S+)\s*$/m.exec(proposal)?.[1] ?? id.slice(5);
|
|
11729
12480
|
out.push({ change: id, repo, adds: await addedIn(dir) });
|
|
11730
12481
|
}
|
|
@@ -11740,7 +12491,7 @@ async function seedCollisions(root, change) {
|
|
|
11740
12491
|
for (const id of ids) {
|
|
11741
12492
|
if (id.startsWith("seed-"))
|
|
11742
12493
|
continue;
|
|
11743
|
-
for (const a of await addedIn(
|
|
12494
|
+
for (const a of await addedIn(join20(base, id)))
|
|
11744
12495
|
for (const s of seeds)
|
|
11745
12496
|
for (const b of s.adds) {
|
|
11746
12497
|
const flow = a.flows.find((f) => b.flows.includes(f)) ?? (a.capability === b.capability ? a.flows[0] ?? b.flows[0] : void 0);
|
|
@@ -11800,11 +12551,11 @@ function storyWords2(stories) {
|
|
|
11800
12551
|
}
|
|
11801
12552
|
return out;
|
|
11802
12553
|
}
|
|
11803
|
-
function trimmed(id,
|
|
11804
|
-
if (estimateTokens(
|
|
11805
|
-
return { text:
|
|
11806
|
-
const lines =
|
|
11807
|
-
const parts = citedParts(
|
|
12554
|
+
function trimmed(id, text4, budget, about) {
|
|
12555
|
+
if (estimateTokens(text4) <= budget)
|
|
12556
|
+
return { text: text4 };
|
|
12557
|
+
const lines = text4.split("\n");
|
|
12558
|
+
const parts = citedParts(text4);
|
|
11808
12559
|
const isPart = new Set(parts.map((p) => p.readLine));
|
|
11809
12560
|
const score = (t) => words4(t).filter((w) => about.has(w)).length;
|
|
11810
12561
|
const keep = new Set(lines.map((_, i) => i + 1).filter((n) => !isPart.has(n)));
|
|
@@ -12038,14 +12789,14 @@ function noteConflictLines(conflicts) {
|
|
|
12038
12789
|
var fromLine = (c) => c.from && c.quote ? [` From ${c.from}, quoting: "${c.quote}"`] : [];
|
|
12039
12790
|
|
|
12040
12791
|
// packages/planner/dist/task-context.js
|
|
12041
|
-
var
|
|
12042
|
-
import { readFile as
|
|
12043
|
-
import { join as
|
|
12792
|
+
var import_yaml17 = __toESM(require_dist(), 1);
|
|
12793
|
+
import { readFile as readFile19 } from "node:fs/promises";
|
|
12794
|
+
import { join as join21 } from "node:path";
|
|
12044
12795
|
var TASK_LINE = /^\s*[-*]\s+\[([ xX]?)\]\s+(\S+)\s+(.*)$/;
|
|
12045
|
-
function readTasks2(
|
|
12796
|
+
function readTasks2(text4) {
|
|
12046
12797
|
const out = [];
|
|
12047
12798
|
let repo = "";
|
|
12048
|
-
for (const l of
|
|
12799
|
+
for (const l of text4.replace(/\r\n/g, "\n").split("\n")) {
|
|
12049
12800
|
const h = /^##\s+(.+?)\s*$/.exec(l);
|
|
12050
12801
|
if (h) {
|
|
12051
12802
|
repo = h[1].replace(/[`*]/g, "").trim().split(/\s+/)[0].toLowerCase();
|
|
@@ -12065,8 +12816,8 @@ function readTasks2(text2) {
|
|
|
12065
12816
|
}
|
|
12066
12817
|
return out;
|
|
12067
12818
|
}
|
|
12068
|
-
function readStoryFile(
|
|
12069
|
-
const lines =
|
|
12819
|
+
function readStoryFile(text4) {
|
|
12820
|
+
const lines = text4.replace(/\r\n/g, "\n").split("\n");
|
|
12070
12821
|
const title = lines.find((l) => /^#\s/.test(l))?.replace(/^#\s+/, "") ?? "";
|
|
12071
12822
|
const start = lines.findIndex((l) => /^#\s/.test(l)) + 1;
|
|
12072
12823
|
const end = lines.findIndex((l, i) => i >= start && /^##\s/.test(l));
|
|
@@ -12085,19 +12836,19 @@ function readStoryFile(text2) {
|
|
|
12085
12836
|
}
|
|
12086
12837
|
var cite2 = (repo, e) => e.filter((x) => x.path).map((x) => `${repo}:${x.path}${x.line ? `:${x.line}` : ""}`);
|
|
12087
12838
|
async function worktreeOf(dir, repo) {
|
|
12088
|
-
const t = await
|
|
12839
|
+
const t = await readFile19(join21(dir, "repos.yaml"), "utf8").catch(() => void 0);
|
|
12089
12840
|
if (!t)
|
|
12090
12841
|
return void 0;
|
|
12091
12842
|
try {
|
|
12092
|
-
const d = (0,
|
|
12843
|
+
const d = (0, import_yaml17.parse)(t);
|
|
12093
12844
|
return d.repos?.find((r) => r.id === repo)?.worktree;
|
|
12094
12845
|
} catch {
|
|
12095
12846
|
return void 0;
|
|
12096
12847
|
}
|
|
12097
12848
|
}
|
|
12098
|
-
function migrationPath(
|
|
12099
|
-
if (
|
|
12100
|
-
return
|
|
12849
|
+
function migrationPath(named2, table, estate) {
|
|
12850
|
+
if (named2.includes("/"))
|
|
12851
|
+
return named2;
|
|
12101
12852
|
const store = estate.byId.get(table)?.dataStore;
|
|
12102
12853
|
const files = estate.records.filter((r) => r.kind === "Migration" && r.dataStore === store && r.file).map((r) => r.file);
|
|
12103
12854
|
const nested = files.length > 0 && files.every((f) => f.split("/").length > 2 && f.endsWith("/migration.sql"));
|
|
@@ -12105,8 +12856,8 @@ function migrationPath(named, table, estate) {
|
|
|
12105
12856
|
...new Set(files.map((f) => f.split("/").slice(0, nested ? -2 : -1).join("/")))
|
|
12106
12857
|
];
|
|
12107
12858
|
if (!(folders.length === 1 && folders[0]))
|
|
12108
|
-
return
|
|
12109
|
-
return nested && !/\.sql$/i.test(
|
|
12859
|
+
return named2;
|
|
12860
|
+
return nested && !/\.sql$/i.test(named2) ? `${folders[0]}/${named2}/migration.sql` : `${folders[0]}/${named2}`;
|
|
12110
12861
|
}
|
|
12111
12862
|
async function taskContext(root, changeId, taskId, o = {}) {
|
|
12112
12863
|
const change = await changeStore(root).read(changeId).catch((e) => {
|
|
@@ -12115,18 +12866,18 @@ async function taskContext(root, changeId, taskId, o = {}) {
|
|
|
12115
12866
|
const dir = changeFolder(root, change.id);
|
|
12116
12867
|
const rel = `${changesRelOf(root)}/${change.id}`;
|
|
12117
12868
|
const notes = [];
|
|
12118
|
-
const tasks = readTasks2(await
|
|
12869
|
+
const tasks = readTasks2(await readFile19(join21(dir, "tasks.md"), "utf8").catch(() => ""));
|
|
12119
12870
|
const ctx = await loadChangeContext(root);
|
|
12120
12871
|
const estate = await loadEstate(root, ctx.records);
|
|
12121
12872
|
const deltas = (await readDeltas(dir)).deltas.filter((d) => !isEmptyDelta(d));
|
|
12122
12873
|
const stories = deltas.length ? storiesFromDeltas(deltas, estate) : [];
|
|
12123
12874
|
const asked = taskId.toLowerCase();
|
|
12124
|
-
const
|
|
12125
|
-
const line = tasks.find((t) => t.id === (
|
|
12875
|
+
const named2 = tasks.some((t) => t.id === asked) ? void 0 : findTask(stories, asked);
|
|
12876
|
+
const line = tasks.find((t) => t.id === (named2 ? taskLabel(named2.s, named2.t) : asked) && (!named2 || !t.story || t.story === named2.s.id));
|
|
12126
12877
|
if (!line)
|
|
12127
12878
|
throw new PlannerError("not-found", tasks.length ? `There is no task ${taskId} in ${change.id}'s tasks.md. Tasks: ${tasks.map((t) => t.id).join(", ")}.` : `${change.id} has no tasks yet: plan its stories first (atticus next says how).`);
|
|
12128
12879
|
const id = line.id;
|
|
12129
|
-
const found0 =
|
|
12880
|
+
const found0 = named2 ?? findTask(stories, id, line.story);
|
|
12130
12881
|
const dt = found0?.t;
|
|
12131
12882
|
const ds = found0?.s;
|
|
12132
12883
|
const key3 = dt?.id ?? id;
|
|
@@ -12137,7 +12888,7 @@ async function taskContext(root, changeId, taskId, o = {}) {
|
|
|
12137
12888
|
if (!dt)
|
|
12138
12889
|
notes.push(`Task ${id} isn't one the target flows give, so it has no step or cited code here; read its story.`);
|
|
12139
12890
|
const storyId = line.story ?? dt?.story;
|
|
12140
|
-
const storyText = storyId ? await
|
|
12891
|
+
const storyText = storyId ? await readFile19(join21(dir, "stories", `${storyId}.md`), "utf8").catch(() => void 0) : void 0;
|
|
12141
12892
|
const sf = storyText ? readStoryFile(storyText) : void 0;
|
|
12142
12893
|
const answer = await readStoriesAnswer(dir);
|
|
12143
12894
|
const found = answer?.stories.find((s) => s.id === storyId) ?? answer?.stories.find((s) => s.tasks.some((t) => t.id === key3));
|
|
@@ -12264,7 +13015,7 @@ async function taskContext(root, changeId, taskId, o = {}) {
|
|
|
12264
13015
|
name: s.input.name,
|
|
12265
13016
|
kind: s.input.kind
|
|
12266
13017
|
}));
|
|
12267
|
-
const worktree = await worktreeOf(dir, line.repo);
|
|
13018
|
+
const worktree = o.at?.worktree ?? await worktreeOf(dir, line.repo);
|
|
12268
13019
|
if (!worktree)
|
|
12269
13020
|
notes.push(`${line.repo} has no worktree for ${change.id} yet: open one with atticus workspace open ${change.id} --repos ${line.repo}.`);
|
|
12270
13021
|
const apply = await metarepoOpenSpec(root)?.applyInstructions(change.id).catch(() => void 0);
|
|
@@ -12279,7 +13030,7 @@ async function taskContext(root, changeId, taskId, o = {}) {
|
|
|
12279
13030
|
repo: line.repo,
|
|
12280
13031
|
done: line.done,
|
|
12281
13032
|
...storyId ? { story: storyId } : {},
|
|
12282
|
-
branch: taskBranch(change.id, id),
|
|
13033
|
+
branch: o.at?.branch ?? taskBranch(change.id, id),
|
|
12283
13034
|
...worktree ? { worktree } : {}
|
|
12284
13035
|
},
|
|
12285
13036
|
...storyId && sf ? {
|
|
@@ -12337,7 +13088,7 @@ async function taskContext(root, changeId, taskId, o = {}) {
|
|
|
12337
13088
|
inputs,
|
|
12338
13089
|
files,
|
|
12339
13090
|
uses,
|
|
12340
|
-
prompt: dt && ds ? taskPrompt(change.id, ds, dt, own?.length ? ownCriteria : criteria.map((
|
|
13091
|
+
prompt: dt && ds ? taskPrompt(change.id, ds, dt, own?.length ? ownCriteria : criteria.map((text4) => ({ text: text4, checks: [], outcome: "success" }))) : [
|
|
12341
13092
|
`Implement task ${id} of the change ${change.id} in the repo ${line.repo}: ${line.title}.`,
|
|
12342
13093
|
"Acceptance criteria:",
|
|
12343
13094
|
...criteria.map((c) => `- ${c}`),
|
|
@@ -12907,7 +13658,7 @@ function taskFiles(d, s, old, entry, evidence, e) {
|
|
|
12907
13658
|
});
|
|
12908
13659
|
}
|
|
12909
13660
|
const takes = kind === "entry" || kind === "validate";
|
|
12910
|
-
const
|
|
13661
|
+
const listed2 = (k) => (s.fields?.[k] ?? []).some((f) => changed.has(foldField(f.split(".").at(-1))));
|
|
12911
13662
|
const hasCall = owners.some((o) => o.call);
|
|
12912
13663
|
const today = e.flowById.get(d.flow)?.flow;
|
|
12913
13664
|
const flowFiles = /* @__PURE__ */ new Set([
|
|
@@ -12926,7 +13677,7 @@ function taskFiles(d, s, old, entry, evidence, e) {
|
|
|
12926
13677
|
const has = new Set(fields2.map((f) => foldField(f.name)));
|
|
12927
13678
|
const fresh = [...changed].filter((f) => !has.has(f));
|
|
12928
13679
|
const held = fields2.filter((f) => required.has(foldField(f.name)) && f.required !== true).map((f) => f.name).filter((x, i, xs) => xs.indexOf(x) === i);
|
|
12929
|
-
const own = o.call ? (side2 === "request" || side2 === "message") &&
|
|
13680
|
+
const own = o.call ? (side2 === "request" || side2 === "message") && listed2("sends") || side2 === "response" && listed2("returns") : !hasCall && !calls && (takes && side2 !== "response" || kind === "respond" && (side2 === "response" || side2 === "screen"));
|
|
12930
13681
|
if (!held.length && !(own && fresh.length))
|
|
12931
13682
|
continue;
|
|
12932
13683
|
const whose = o.label.endsWith("s") ? `${o.label}'` : `${o.label}'s`;
|
|
@@ -13252,7 +14003,7 @@ var CriterionObject = external_exports.strictObject({
|
|
|
13252
14003
|
quote: Words.optional()
|
|
13253
14004
|
});
|
|
13254
14005
|
var StoryCriterion = external_exports.union([
|
|
13255
|
-
Words.transform((
|
|
14006
|
+
Words.transform((text4) => CriterionObject.parse({ text: text4 })),
|
|
13256
14007
|
CriterionObject
|
|
13257
14008
|
]);
|
|
13258
14009
|
var NEEDS_ERROR_CASE = ["validate", "write", "call-out", "publish"];
|
|
@@ -13500,11 +14251,11 @@ function knownFieldNames(e, deltas) {
|
|
|
13500
14251
|
}
|
|
13501
14252
|
return new Set(names.map(foldName).filter(Boolean));
|
|
13502
14253
|
}
|
|
13503
|
-
function fieldLikeNames(
|
|
14254
|
+
function fieldLikeNames(text4) {
|
|
13504
14255
|
const out = /* @__PURE__ */ new Set();
|
|
13505
|
-
for (const m of
|
|
14256
|
+
for (const m of text4.matchAll(/(?<![\w.$/{-])[a-z][a-z0-9]*(?:_[a-z0-9]+)+(?![\w(-])/g))
|
|
13506
14257
|
out.add(m[0]);
|
|
13507
|
-
for (const m of
|
|
14258
|
+
for (const m of text4.matchAll(/(?<![\w.$/{-])[a-z]+[A-Z][A-Za-z0-9]*(?![\w(-])/g))
|
|
13508
14259
|
out.add(m[0]);
|
|
13509
14260
|
return [...out];
|
|
13510
14261
|
}
|
|
@@ -13549,7 +14300,7 @@ function checkSections(answer, stories, deltas, o = {}) {
|
|
|
13549
14300
|
if (!w)
|
|
13550
14301
|
continue;
|
|
13551
14302
|
const fields2 = new Set(s.tasks.flatMap((t) => t.fields.map(foldName)));
|
|
13552
|
-
const
|
|
14303
|
+
const named2 = [...new Set(s.tasks.flatMap((t) => t.fields))];
|
|
13553
14304
|
const criteria = s.tasks.flatMap((t) => (w.tasks.find((x) => x.id === t.id)?.criteria ?? []).map((c) => ({
|
|
13554
14305
|
t,
|
|
13555
14306
|
c,
|
|
@@ -13576,11 +14327,11 @@ function checkSections(answer, stories, deltas, o = {}) {
|
|
|
13576
14327
|
...stepTasks(s).flatMap((t) => t.to?.new && t.op !== "remove" ? [t.to.entry] : [])
|
|
13577
14328
|
];
|
|
13578
14329
|
if (w.interfaces !== void 0) {
|
|
13579
|
-
const
|
|
14330
|
+
const listed2 = Array.isArray(w.interfaces) ? w.interfaces : [];
|
|
13580
14331
|
for (const x of [...new Set(added)])
|
|
13581
|
-
if (!
|
|
14332
|
+
if (!listed2.some((i) => endpointKey(i.entry) === endpointKey(x)))
|
|
13582
14333
|
push("stories.interface-missing", `${s.id}:${x}`, `Story ${s.id}: the target flow adds ${x}, but ${Array.isArray(w.interfaces) ? "the interface changes don't list it" : `the interface changes say none ("${w.interfaces.none}")`}: list it with its fields (side, type, required, default), whether it is breaking, and its consumers.`, true, s.repo);
|
|
13583
|
-
for (const i of
|
|
14334
|
+
for (const i of listed2) {
|
|
13584
14335
|
if (!entries.has(endpointKey(i.entry)))
|
|
13585
14336
|
push("stories.unknown-entry", `${s.id}:${i.entry}`, `Story ${s.id}: the interface change names ${i.entry}, which neither the target flows nor the catalog have. Name an entry of the flow, or add it in the target flows (atticus change architecture).`, true, s.repo);
|
|
13586
14337
|
if (e)
|
|
@@ -13591,7 +14342,7 @@ function checkSections(answer, stories, deltas, o = {}) {
|
|
|
13591
14342
|
const target = `${s.id}:${i.entry}:${f.name}`;
|
|
13592
14343
|
const isNew = f.change !== "removed";
|
|
13593
14344
|
if (!(isNew ? fields2 : known).has(foldName(f.name)))
|
|
13594
|
-
push("stories.invented-field", target, isNew ? `Story ${s.id}: ${i.entry} lists ${f.name} as ${f.change}, but no step of the target flow carries it (the story's fields: ${
|
|
14345
|
+
push("stories.invented-field", target, isNew ? `Story ${s.id}: ${i.entry} lists ${f.name} as ${f.change}, but no step of the target flow carries it (the story's fields: ${named2.join(", ") || "none"}). Name the field as the target flow does, or add it to the step in the target flows (atticus change architecture).` : `Story ${s.id}: ${i.entry} lists ${f.name} as removed, but neither the target flows nor the catalog know a field of that name.`, true, s.repo);
|
|
13595
14346
|
if (!inbound || !isNew || f.side === "response" || !fields2.has(foldName(f.name)))
|
|
13596
14347
|
continue;
|
|
13597
14348
|
if (!f.validation)
|
|
@@ -13820,11 +14571,11 @@ var oldForm = (question2) => /^Does the criterion ".*" of task \S+ check \S+\? A
|
|
|
13820
14571
|
async function settleCoversAnswers(root, change, dir, j) {
|
|
13821
14572
|
const notes = [];
|
|
13822
14573
|
const decided = /* @__PURE__ */ new Map();
|
|
13823
|
-
const file =
|
|
13824
|
-
const
|
|
13825
|
-
if (
|
|
14574
|
+
const file = join22(dir, "stage", "stories.yaml");
|
|
14575
|
+
const text4 = await readFile20(file, "utf8").catch(() => void 0);
|
|
14576
|
+
if (text4 === void 0)
|
|
13826
14577
|
return { notes, decided };
|
|
13827
|
-
const parsed = StageLoop.safeParse((0,
|
|
14578
|
+
const parsed = StageLoop.safeParse((0, import_yaml18.parse)(text4));
|
|
13828
14579
|
if (!parsed.success)
|
|
13829
14580
|
return { notes, decided };
|
|
13830
14581
|
const loop = parsed.data;
|
|
@@ -13880,7 +14631,7 @@ async function settleCoversAnswers(root, change, dir, j) {
|
|
|
13880
14631
|
});
|
|
13881
14632
|
});
|
|
13882
14633
|
}
|
|
13883
|
-
for (const { c, q: q2, v } of verdicts)
|
|
14634
|
+
for (const { c, q: q2, v } of verdicts.filter((x) => !isAiBy(x.q.answer.by)))
|
|
13884
14635
|
for (const field of c.fields)
|
|
13885
14636
|
await personVerdict(j.store, coversInputs(field, c.criterion), {
|
|
13886
14637
|
verdict: v,
|
|
@@ -13959,7 +14710,7 @@ ${s.c.criterion}`;
|
|
|
13959
14710
|
loop.questions = loop.questions.filter((id) => !retiredIds.has(id));
|
|
13960
14711
|
loop.findings.sort((a, b) => a.fingerprint.localeCompare(b.fingerprint));
|
|
13961
14712
|
await writeFileAtomic(file, `# The stories stage's loop (ADR 0087): drafts, the findings each one had, and the questions it raised. Atticus writes this file.
|
|
13962
|
-
${(0,
|
|
14713
|
+
${(0, import_yaml18.stringify)(StageLoop.parse(loop), { lineWidth: 0 })}`);
|
|
13963
14714
|
}
|
|
13964
14715
|
}
|
|
13965
14716
|
return { notes, decided };
|
|
@@ -14354,9 +15105,17 @@ function renderStoryFile(change, s, words5, unjudged = [], x = {}) {
|
|
|
14354
15105
|
...waits.length ? [`A person has raised ${andList2(waits.map((r) => `${r.id} ${r.title}`))} (requests.md)`] : []
|
|
14355
15106
|
].map((d) => `- ${sentence5(d)}`), "");
|
|
14356
15107
|
lines.push("## Traces to", "", `target-flows/${s.flow}.yaml (${s.change} flow ${s.flow}: ${s.name})${x.capability ? `; specs/${x.capability}/spec.md` : ""}`, "");
|
|
14357
|
-
lines.push("## Systems and repos", "", s.repo, "",
|
|
15108
|
+
lines.push("## Systems and repos", "", s.repo, "", buildLine(s.repo, command), "");
|
|
14358
15109
|
return lines.join("\n");
|
|
14359
15110
|
}
|
|
15111
|
+
function buildLine(repo, command) {
|
|
15112
|
+
return command ? `Build and test with \`${command}\`.` : `repos.manifest.yaml names no build command for ${repo}: use the repo's own build and tests.`;
|
|
15113
|
+
}
|
|
15114
|
+
function withBuildLine(story, repo, command) {
|
|
15115
|
+
const esc = repo.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15116
|
+
const line = new RegExp(`^(?:Build and test with \`.*\`\\.|repos\\.manifest\\.yaml names no build command for ${esc}: use the repo's own build and tests\\.)$`, "gm");
|
|
15117
|
+
return story.replace(line, () => buildLine(repo, command));
|
|
15118
|
+
}
|
|
14360
15119
|
function renderIndex2(stories, a) {
|
|
14361
15120
|
return [
|
|
14362
15121
|
`${GENERATED_MARK} stories from the target flows. -->`,
|
|
@@ -14377,9 +15136,9 @@ function renderIndex2(stories, a) {
|
|
|
14377
15136
|
""
|
|
14378
15137
|
].join("\n");
|
|
14379
15138
|
}
|
|
14380
|
-
function tickedTasks(
|
|
15139
|
+
function tickedTasks(text4) {
|
|
14381
15140
|
const out = /* @__PURE__ */ new Set();
|
|
14382
|
-
for (const m of
|
|
15141
|
+
for (const m of text4.matchAll(/^\s*[-*]\s+\[[xX]\]\s+(.+?)\s*$/gm))
|
|
14383
15142
|
out.add(m[1]);
|
|
14384
15143
|
return out;
|
|
14385
15144
|
}
|
|
@@ -14437,11 +15196,11 @@ async function writeStories(root, change, stories, answer, requests, now, unjudg
|
|
|
14437
15196
|
const a = foldAnswer(answer, stories);
|
|
14438
15197
|
const dir = changeFolder(root, change);
|
|
14439
15198
|
const keep = new Set(stories.map((s) => `${s.id}.md`));
|
|
14440
|
-
for (const f of await readdir14(
|
|
15199
|
+
for (const f of await readdir14(join22(dir, "stories")).catch(() => [])) {
|
|
14441
15200
|
if (keep.has(f) || !f.endsWith(".md"))
|
|
14442
15201
|
continue;
|
|
14443
|
-
if ((await
|
|
14444
|
-
await rm6(
|
|
15202
|
+
if ((await readFile20(join22(dir, "stories", f), "utf8")).startsWith(GENERATED_MARK))
|
|
15203
|
+
await rm6(join22(dir, "stories", f));
|
|
14445
15204
|
}
|
|
14446
15205
|
const { byTask: knowledge } = await storiesKnowledge(root, change, stories, {
|
|
14447
15206
|
...extras.estate ? { estate: extras.estate } : {},
|
|
@@ -14449,9 +15208,9 @@ async function writeStories(root, change, stories, answer, requests, now, unjudg
|
|
|
14449
15208
|
...extras.asker ? { asker: extras.asker } : {},
|
|
14450
15209
|
...extras.knowledge ? { selected: extras.knowledge } : {}
|
|
14451
15210
|
});
|
|
14452
|
-
for (const p of await readdir14(
|
|
14453
|
-
if (p.endsWith(".prompt.md") && (await
|
|
14454
|
-
await rm6(
|
|
15211
|
+
for (const p of await readdir14(join22(dir, "tasks")).catch(() => []))
|
|
15212
|
+
if (p.endsWith(".prompt.md") && (await readFile20(join22(dir, "tasks", p), "utf8")).startsWith(GENERATED_MARK))
|
|
15213
|
+
await rm6(join22(dir, "tasks", p));
|
|
14455
15214
|
const spec = await openspecFiles(root, change, stories, a, extras.estate);
|
|
14456
15215
|
const files = [];
|
|
14457
15216
|
for (const s of stories) {
|
|
@@ -14470,24 +15229,24 @@ async function writeStories(root, change, stories, answer, requests, now, unjudg
|
|
|
14470
15229
|
knowledge,
|
|
14471
15230
|
...spec.capabilityOf.get(s.flow) ? { capability: spec.capabilityOf.get(s.flow) } : {}
|
|
14472
15231
|
};
|
|
14473
|
-
await writeFileAtomic(
|
|
15232
|
+
await writeFileAtomic(join22(dir, "stories", `${s.id}.md`), renderStoryFile(change, s, w, unjudged, x));
|
|
14474
15233
|
files.push(`stories/${s.id}.md`);
|
|
14475
|
-
for (const [rel,
|
|
14476
|
-
await mkdir7(
|
|
14477
|
-
await writeFileAtomic(
|
|
15234
|
+
for (const [rel, text4] of Object.entries(taskPromptFiles(change, s, w, x))) {
|
|
15235
|
+
await mkdir7(join22(dir, ...rel.split("/").slice(0, -1)), { recursive: true });
|
|
15236
|
+
await writeFileAtomic(join22(dir, ...rel.split("/")), text4);
|
|
14478
15237
|
files.push(rel);
|
|
14479
15238
|
}
|
|
14480
15239
|
}
|
|
14481
|
-
await writeFileAtomic(
|
|
14482
|
-
const ticked = tickedTasks(await
|
|
14483
|
-
await writeFileAtomic(
|
|
15240
|
+
await writeFileAtomic(join22(dir, "stories.md"), renderIndex2(stories, a));
|
|
15241
|
+
const ticked = tickedTasks(await readFile20(join22(dir, "tasks.md"), "utf8").catch(() => ""));
|
|
15242
|
+
await writeFileAtomic(join22(dir, "tasks.md"), renderTasks2(stories, requests, ticked));
|
|
14484
15243
|
files.push(...await writeSpecFiles(dir, spec.files));
|
|
14485
15244
|
if (spec.proposal && await writeProposal(dir, spec.proposal))
|
|
14486
15245
|
files.push("proposal.md");
|
|
14487
|
-
await writeFileAtomic(
|
|
15246
|
+
await writeFileAtomic(join22(dir, "requests.md"), renderRequests(requests));
|
|
14488
15247
|
await mergeStoryRequests(root, change, requests, now);
|
|
14489
|
-
await writeFileAtomic(
|
|
14490
|
-
${(0,
|
|
15248
|
+
await writeFileAtomic(join22(dir, ...ANSWER_FILE.split("/")), `# The story writer's last answer (ADR 0087).
|
|
15249
|
+
${(0, import_yaml18.stringify)(a, { lineWidth: 0 })}`);
|
|
14491
15250
|
return [...files, "stories.md", "tasks.md", "requests.md", "needs.yaml", "needs.md"];
|
|
14492
15251
|
}
|
|
14493
15252
|
async function selectStoriesKnowledge(root, change, stories, estate) {
|
|
@@ -14535,16 +15294,16 @@ async function storiesKnowledge(root, change, stories, o) {
|
|
|
14535
15294
|
return r;
|
|
14536
15295
|
}
|
|
14537
15296
|
async function readStoriesAnswer(dir) {
|
|
14538
|
-
const t = await
|
|
15297
|
+
const t = await readFile20(join22(dir, ...ANSWER_FILE.split("/")), "utf8").catch(() => void 0);
|
|
14539
15298
|
if (t === void 0)
|
|
14540
15299
|
return void 0;
|
|
14541
15300
|
return parseYamlAs(t, Answer4, (d) => d, "stories").value;
|
|
14542
15301
|
}
|
|
14543
15302
|
async function locateInStories(dir, rel, words5) {
|
|
14544
|
-
for (const f of (await readdir14(
|
|
15303
|
+
for (const f of (await readdir14(join22(dir, "stories")).catch(() => [])).sort()) {
|
|
14545
15304
|
if (!f.endsWith(".md"))
|
|
14546
15305
|
continue;
|
|
14547
|
-
const lines = (await
|
|
15306
|
+
const lines = (await readFile20(join22(dir, "stories", f), "utf8")).split(/\r?\n/);
|
|
14548
15307
|
const n = lines.findIndex((l) => l.includes(words5));
|
|
14549
15308
|
if (n >= 0)
|
|
14550
15309
|
return `${rel}/stories/${f}:${n + 1}`;
|
|
@@ -14644,7 +15403,7 @@ async function storiesStage(root, changeId, o) {
|
|
|
14644
15403
|
if (!o.force) {
|
|
14645
15404
|
const own = [];
|
|
14646
15405
|
for (const f of ["stories.md", "tasks.md", "requests.md"]) {
|
|
14647
|
-
const t = await
|
|
15406
|
+
const t = await readFile20(join22(dir, f), "utf8").catch(() => void 0);
|
|
14648
15407
|
if (t !== void 0 && !t.startsWith(GENERATED_MARK))
|
|
14649
15408
|
own.push(f);
|
|
14650
15409
|
}
|
|
@@ -14690,7 +15449,7 @@ async function storiesStage(root, changeId, o) {
|
|
|
14690
15449
|
id: "deltas",
|
|
14691
15450
|
kind: "artifact",
|
|
14692
15451
|
origin: `${rel}/target-flows/`,
|
|
14693
|
-
text: (0,
|
|
15452
|
+
text: (0, import_yaml18.stringify)(deltas, { lineWidth: 0 }),
|
|
14694
15453
|
classification: "internal",
|
|
14695
15454
|
priority: 90
|
|
14696
15455
|
},
|
|
@@ -14814,11 +15573,11 @@ Rules:
|
|
|
14814
15573
|
classification: "internal",
|
|
14815
15574
|
priority: 80
|
|
14816
15575
|
};
|
|
14817
|
-
const brief = await
|
|
14818
|
-
const design = await
|
|
15576
|
+
const brief = await readFile20(join22(dir, "brief.md"), "utf8").catch(() => "");
|
|
15577
|
+
const design = await readFile20(join22(dir, "design.md"), "utf8").catch(() => "");
|
|
14819
15578
|
const scope = scopeRepos(estate, deltas.map((d) => d.repo));
|
|
14820
15579
|
const selected = await estateContext(root, "stories", {
|
|
14821
|
-
text: [brief, skeletonText, (0,
|
|
15580
|
+
text: [brief, skeletonText, (0, import_yaml18.stringify)(deltas, { lineWidth: 0 }), design].filter((x) => x.trim()).join("\n\n"),
|
|
14822
15581
|
title: change.title,
|
|
14823
15582
|
ids: [...new Set(deltas.map((d) => d.flow))]
|
|
14824
15583
|
}, {
|
|
@@ -14895,8 +15654,8 @@ Rules:
|
|
|
14895
15654
|
instructions: instructions3,
|
|
14896
15655
|
...context.gaps?.length ? { gaps: context.gaps } : {},
|
|
14897
15656
|
sources,
|
|
14898
|
-
parse: (
|
|
14899
|
-
const r = parseYamlAs(
|
|
15657
|
+
parse: (text4) => {
|
|
15658
|
+
const r = parseYamlAs(text4, Answer4, (d) => d, "stories");
|
|
14900
15659
|
const value = r.value && noted ? notesCitedInFolder(r.value, rel) : r.value;
|
|
14901
15660
|
return { ...value ? { answer: foldAnswer(value, stories) } : {}, issues: r.issues };
|
|
14902
15661
|
},
|
|
@@ -14952,33 +15711,33 @@ Rules:
|
|
|
14952
15711
|
}
|
|
14953
15712
|
|
|
14954
15713
|
// packages/planner/dist/stages/small.js
|
|
14955
|
-
var
|
|
14956
|
-
import { readFile as
|
|
14957
|
-
import { join as
|
|
15714
|
+
var import_yaml19 = __toESM(require_dist(), 1);
|
|
15715
|
+
import { readFile as readFile21 } from "node:fs/promises";
|
|
15716
|
+
import { join as join23 } from "node:path";
|
|
14958
15717
|
var SMALL_AGENT = "small-change-planner";
|
|
14959
15718
|
var SMALL_ANSWER_FILE = "stage/small-answer.yaml";
|
|
14960
15719
|
var MARK2 = "<!-- Generated by atticus change small";
|
|
14961
|
-
var
|
|
14962
|
-
var
|
|
15720
|
+
var text2 = () => external_exports.string().trim().min(1);
|
|
15721
|
+
var Id2 = external_exports.string().regex(/^[a-z0-9][a-z0-9._-]*$/, "an id is lower case: letters, digits, . _ -");
|
|
14963
15722
|
var SmallAnswer = external_exports.strictObject({
|
|
14964
|
-
summary:
|
|
14965
|
-
features: external_exports.array(external_exports.strictObject({ id:
|
|
15723
|
+
summary: text2(),
|
|
15724
|
+
features: external_exports.array(external_exports.strictObject({ id: Id2, why: text2() })).default([]),
|
|
14966
15725
|
flows: external_exports.array(external_exports.strictObject({
|
|
14967
|
-
id:
|
|
14968
|
-
why:
|
|
15726
|
+
id: Id2,
|
|
15727
|
+
why: text2(),
|
|
14969
15728
|
how: external_exports.enum(["changed", "read"]).default("changed")
|
|
14970
15729
|
})).default([]),
|
|
14971
|
-
newFlows: external_exports.array(external_exports.strictObject({ name:
|
|
14972
|
-
gaps: external_exports.array(external_exports.strictObject({ what:
|
|
15730
|
+
newFlows: external_exports.array(external_exports.strictObject({ name: text2(), repo: Id2, why: text2() })).default([]),
|
|
15731
|
+
gaps: external_exports.array(external_exports.strictObject({ what: text2(), why: text2(), from: external_exports.string().optional() })).default([]),
|
|
14973
15732
|
targetFlows: external_exports.array(FlowDelta).default([]),
|
|
14974
15733
|
reads: external_exports.array(ReadFlow).default([]),
|
|
14975
15734
|
stories: external_exports.array(external_exports.strictObject({
|
|
14976
15735
|
/** The target flow the story is for. */
|
|
14977
|
-
flow:
|
|
14978
|
-
title:
|
|
14979
|
-
asA:
|
|
14980
|
-
iWant:
|
|
14981
|
-
soThat:
|
|
15736
|
+
flow: Id2,
|
|
15737
|
+
title: text2(),
|
|
15738
|
+
asA: text2(),
|
|
15739
|
+
iWant: text2(),
|
|
15740
|
+
soThat: text2(),
|
|
14982
15741
|
tasks: external_exports.array(external_exports.strictObject({
|
|
14983
15742
|
/** The step's item number in the flow's target steps, from 1. */
|
|
14984
15743
|
step: external_exports.number().int().positive(),
|
|
@@ -15123,7 +15882,7 @@ async function smallStage(root, changeId, o) {
|
|
|
15123
15882
|
throw new PlannerError("refused", `${size.words} Run atticus change feature ${change.id} (or /atticus-feature ${change.id}).`);
|
|
15124
15883
|
const dir = changeFolder(root, change.id);
|
|
15125
15884
|
const rel = `${changesRelOf(root)}/${change.id}`;
|
|
15126
|
-
const brief = await
|
|
15885
|
+
const brief = await readFile21(join23(dir, "brief.md"), "utf8").catch(() => "");
|
|
15127
15886
|
if (!isFilled(brief))
|
|
15128
15887
|
throw new PlannerError("refused", `${change.id} has no brief yet: write brief.md (what changes, where, and when it is done), then run atticus change small ${change.id} again.`);
|
|
15129
15888
|
const estate = await loadEstate(root, ctx.records);
|
|
@@ -15327,20 +16086,20 @@ ${storeRules(classificationLevels(ctx)).join("\n")}
|
|
|
15327
16086
|
}
|
|
15328
16087
|
)
|
|
15329
16088
|
];
|
|
15330
|
-
const own = await
|
|
16089
|
+
const own = await readFile21(join23(dir, "proposal.md"), "utf8").catch(() => void 0);
|
|
15331
16090
|
if (own === void 0 || !isFilled(own) || own.startsWith(MARK2)) {
|
|
15332
|
-
await writeFileAtomic(
|
|
16091
|
+
await writeFileAtomic(join23(dir, "proposal.md"), renderProposal2(change.title, a, d2.deltas));
|
|
15333
16092
|
files.push("proposal.md");
|
|
15334
16093
|
}
|
|
15335
|
-
await writeFileAtomic(
|
|
15336
|
-
${(0,
|
|
16094
|
+
await writeFileAtomic(join23(dir, ...SMALL_ANSWER_FILE.split("/")), `# The fast path's last answer (ADR 0113). Atticus writes this file.
|
|
16095
|
+
${(0, import_yaml19.stringify)(a, { lineWidth: 0 })}`);
|
|
15337
16096
|
return files;
|
|
15338
16097
|
},
|
|
15339
16098
|
current: async () => {
|
|
15340
|
-
const t = await
|
|
16099
|
+
const t = await readFile21(join23(dir, ...SMALL_ANSWER_FILE.split("/")), "utf8").catch(() => void 0);
|
|
15341
16100
|
if (t === void 0)
|
|
15342
16101
|
return void 0;
|
|
15343
|
-
const r = SmallAnswer.safeParse((0,
|
|
16102
|
+
const r = SmallAnswer.safeParse((0, import_yaml19.parse)(t));
|
|
15344
16103
|
return r.success ? r.data : void 0;
|
|
15345
16104
|
},
|
|
15346
16105
|
questions: (a) => a.questions
|
|
@@ -15359,10 +16118,10 @@ function stageOfOutput(output) {
|
|
|
15359
16118
|
const m = /^(feature|architecture|stories|small)-answer\.yaml$/.exec(output);
|
|
15360
16119
|
return m ? m[1] : void 0;
|
|
15361
16120
|
}
|
|
15362
|
-
async function checkStageDraft(root, change, stage,
|
|
16121
|
+
async function checkStageDraft(root, change, stage, text4) {
|
|
15363
16122
|
const o = {
|
|
15364
16123
|
runtime: new HandoffRuntime({ root, validate: async () => [] }),
|
|
15365
|
-
checkDraft:
|
|
16124
|
+
checkDraft: text4,
|
|
15366
16125
|
context: { engine: false }
|
|
15367
16126
|
};
|
|
15368
16127
|
const r = stage === "feature" ? await featureStage(root, change, o) : stage === "architecture" ? await architectureStage(root, change, o) : stage === "stories" ? await storiesStage(root, change, o) : await smallStage(root, change, o);
|
|
@@ -15400,13 +16159,13 @@ function draftCheckBox(root, request) {
|
|
|
15400
16159
|
async call(name, input) {
|
|
15401
16160
|
if (name !== CHECK_DRAFT_TOOL)
|
|
15402
16161
|
throw new ToolError(`There is no tool ${name}.`);
|
|
15403
|
-
const
|
|
15404
|
-
if (!
|
|
16162
|
+
const text4 = typeof input.draft === "string" ? input.draft : "";
|
|
16163
|
+
if (!text4.trim())
|
|
15405
16164
|
throw new ToolError("Give your whole answer under draft.");
|
|
15406
16165
|
if (used >= limit)
|
|
15407
16166
|
return `You have used the ${limit} checks a draft may have: hand your answer in as it is.`;
|
|
15408
16167
|
used++;
|
|
15409
|
-
return formatDraftProblems(await checkStageDraft(root, request.change, stage,
|
|
16168
|
+
return formatDraftProblems(await checkStageDraft(root, request.change, stage, text4));
|
|
15410
16169
|
}
|
|
15411
16170
|
};
|
|
15412
16171
|
}
|
|
@@ -15423,12 +16182,12 @@ function withTools(a, b) {
|
|
|
15423
16182
|
}
|
|
15424
16183
|
|
|
15425
16184
|
// packages/planner/dist/stages/comments.js
|
|
15426
|
-
import { join as
|
|
16185
|
+
import { join as join24 } from "node:path";
|
|
15427
16186
|
var COMMENT_COMMAND = "atticus ui: a comment on a critic finding";
|
|
15428
16187
|
var isComment = (q2, stage) => q2.askedBy.step === `change-${stage}` && q2.askedBy.command === COMMENT_COMMAND;
|
|
15429
16188
|
var aboutOf = (q2) => q2.evidence[0].replace(/:\d+$/, "");
|
|
15430
16189
|
async function stageComments(root, change, stage) {
|
|
15431
|
-
const dir =
|
|
16190
|
+
const dir = join24(changesDirOf(root), change);
|
|
15432
16191
|
const { loop, present } = await readStageLoop(dir, change, stage);
|
|
15433
16192
|
const comments = [];
|
|
15434
16193
|
const others = [];
|
|
@@ -15440,7 +16199,7 @@ async function stageComments(root, change, stage) {
|
|
|
15440
16199
|
comments.push({
|
|
15441
16200
|
question: q2.id,
|
|
15442
16201
|
text: q2.answer.text,
|
|
15443
|
-
by: q2.answer.by,
|
|
16202
|
+
by: answerByWords(q2.answer.by),
|
|
15444
16203
|
at: q2.answer.at,
|
|
15445
16204
|
read: loop.read.includes(q2.id),
|
|
15446
16205
|
about: aboutOf(q2)
|
|
@@ -15452,10 +16211,10 @@ async function stageComments(root, change, stage) {
|
|
|
15452
16211
|
return { comments, others, present };
|
|
15453
16212
|
}
|
|
15454
16213
|
async function commentOnFinding(root, o) {
|
|
15455
|
-
const
|
|
15456
|
-
if (!
|
|
16214
|
+
const text4 = o.text.trim();
|
|
16215
|
+
if (!text4)
|
|
15457
16216
|
throw new PlannerError("invalid", "Write the comment first.");
|
|
15458
|
-
const dir =
|
|
16217
|
+
const dir = join24(changesDirOf(root), o.change);
|
|
15459
16218
|
const { loop, present, file } = await readStageLoop(dir, o.change, o.stage);
|
|
15460
16219
|
if (!present)
|
|
15461
16220
|
throw new PlannerError("refused", `The ${o.stage} stage of ${o.change} hasn't run yet, so nothing would read a comment: run ${rerunOf(o.stage, o.change)} first.`);
|
|
@@ -15476,7 +16235,7 @@ async function commentOnFinding(root, o) {
|
|
|
15476
16235
|
scope: { level: "repo", id: o.change },
|
|
15477
16236
|
state: "answered",
|
|
15478
16237
|
rerun: rerunOf(o.stage, o.change),
|
|
15479
|
-
answer: { text:
|
|
16238
|
+
answer: { text: text4, by: o.by, at: o.at, scope: { level: "repo", id: o.change } }
|
|
15480
16239
|
});
|
|
15481
16240
|
await writeQuestion(root, q2);
|
|
15482
16241
|
loop.questions = [.../* @__PURE__ */ new Set([...loop.questions, q2.id])].sort();
|
|
@@ -15517,7 +16276,7 @@ function callersOfComponent(e, x, entries) {
|
|
|
15517
16276
|
out.set(id, l);
|
|
15518
16277
|
return l;
|
|
15519
16278
|
};
|
|
15520
|
-
const
|
|
16279
|
+
const repoOf2 = (c) => str5(e.byId.get(c)?.repo);
|
|
15521
16280
|
const componentOfEndpoint = (id) => str5(e.byId.get(id)?.component);
|
|
15522
16281
|
for (const r of e.records) {
|
|
15523
16282
|
if (r.kind !== "Interface" || typeof r.from !== "string" || r.from === x)
|
|
@@ -15532,7 +16291,7 @@ function callersOfComponent(e, x, entries) {
|
|
|
15532
16291
|
if (typeof r.route === "string")
|
|
15533
16292
|
l.routes.add(r.route);
|
|
15534
16293
|
for (const ev2 of r.evidence)
|
|
15535
|
-
if (ev2.side === "caller" || !ev2.side && ev2.repo ===
|
|
16294
|
+
if (ev2.side === "caller" || !ev2.side && ev2.repo === repoOf2(r.from)) {
|
|
15536
16295
|
const w = at(ev2);
|
|
15537
16296
|
if (w)
|
|
15538
16297
|
l.at.add(w);
|
|
@@ -15553,15 +16312,15 @@ function callersOfComponent(e, x, entries) {
|
|
|
15553
16312
|
for (const s of f.flow.steps) {
|
|
15554
16313
|
if (s.kind !== "call-out" && s.kind !== "publish")
|
|
15555
16314
|
continue;
|
|
15556
|
-
const
|
|
15557
|
-
const hit =
|
|
16315
|
+
const named2 = [...s.refs, ...s.to?.entry ? [s.to.entry] : []].filter((id) => componentOfEndpoint(id) === x && (!entries || entries.has(id)));
|
|
16316
|
+
const hit = named2.length > 0 || !entries && s.to?.component === x;
|
|
15558
16317
|
if (!hit)
|
|
15559
16318
|
continue;
|
|
15560
16319
|
const l = got(f.flow.component);
|
|
15561
16320
|
l.flows.add(f.flow.id);
|
|
15562
|
-
for (const n of
|
|
16321
|
+
for (const n of named2)
|
|
15563
16322
|
l.entries.add(n);
|
|
15564
|
-
if (!
|
|
16323
|
+
if (!named2.length && s.to?.entry)
|
|
15565
16324
|
l.entries.add(s.to.entry);
|
|
15566
16325
|
}
|
|
15567
16326
|
}
|
|
@@ -15576,7 +16335,7 @@ function entriesLeadingTo(e, flows) {
|
|
|
15576
16335
|
}
|
|
15577
16336
|
function changeImpact(change, deltas, e, o) {
|
|
15578
16337
|
const ownerOf = (repo, who) => repo ? ownerOfQuestion({ repo }, { owners: o.owners, teams: o.teams }) : { none: `the catalog names no repo for ${who}, so no team owns it` };
|
|
15579
|
-
const
|
|
16338
|
+
const repoOf2 = (component) => str5(e.byId.get(component)?.repo);
|
|
15580
16339
|
const items = breakingChanges(deltas, e).map((c) => {
|
|
15581
16340
|
const own = e.flowById.get(c.flow)?.flow.component;
|
|
15582
16341
|
const callers2 = /* @__PURE__ */ new Map();
|
|
@@ -15608,7 +16367,7 @@ function changeImpact(change, deltas, e, o) {
|
|
|
15608
16367
|
if (typeof r.route === "string")
|
|
15609
16368
|
got.routes.add(r.route);
|
|
15610
16369
|
for (const ev2 of r.evidence)
|
|
15611
|
-
if (ev2.side === "caller" || !ev2.side && ev2.repo ===
|
|
16370
|
+
if (ev2.side === "caller" || !ev2.side && ev2.repo === repoOf2(r.from)) {
|
|
15612
16371
|
const w = at(ev2);
|
|
15613
16372
|
if (w)
|
|
15614
16373
|
got.at.add(w);
|
|
@@ -15637,12 +16396,14 @@ function changeImpact(change, deltas, e, o) {
|
|
|
15637
16396
|
const got = callers2.get(id);
|
|
15638
16397
|
const guesses = got.ifaces.map((r) => r.confidence === "inferred" ? GUESSES.find((g) => (r.flags ?? []).includes(g)) : void 0);
|
|
15639
16398
|
const guessed = got.ifaces.length > 0 && got.flows.size === 0 && guesses.every((g) => g !== void 0) ? guesses[0] : void 0;
|
|
15640
|
-
const repo =
|
|
16399
|
+
const repo = repoOf2(id);
|
|
16400
|
+
const byAi = !guessed && got.ifaces.length > 0 && got.flows.size === 0 && got.ifaces.every((r) => (r.flags ?? []).includes(JOINED_BY_AI));
|
|
15641
16401
|
return {
|
|
15642
16402
|
component: id,
|
|
15643
16403
|
...repo ? { repo } : {},
|
|
15644
16404
|
verdict: guessed ? "may-break" : "breaks",
|
|
15645
16405
|
...guessed ? { guessed } : {},
|
|
16406
|
+
...byAi ? { decidedByAi: true } : {},
|
|
15646
16407
|
interfaces: sorted2(got.ifaces.map((r) => r.id)),
|
|
15647
16408
|
flows: sorted2(got.flows),
|
|
15648
16409
|
routes: sorted2(got.routes),
|
|
@@ -15703,7 +16464,7 @@ function changeImpact(change, deltas, e, o) {
|
|
|
15703
16464
|
const ownGuess = guessedLinks(first.l);
|
|
15704
16465
|
const guessed = sure ? void 0 : ownGuess ?? first.down.guessed;
|
|
15705
16466
|
const restsOn = sure || ownGuess ? void 0 : first.down.restsOn;
|
|
15706
|
-
const repo =
|
|
16467
|
+
const repo = repoOf2(id);
|
|
15707
16468
|
const flows = sorted2(xs.flatMap((x) => [...x.l.flows]));
|
|
15708
16469
|
const hop = {
|
|
15709
16470
|
component: id,
|
|
@@ -15775,6 +16536,7 @@ function changeImpact(change, deltas, e, o) {
|
|
|
15775
16536
|
change,
|
|
15776
16537
|
breaks: callers.filter((c) => c.verdict === "breaks").length,
|
|
15777
16538
|
mayBreak: callers.filter((c) => c.verdict === "may-break").length,
|
|
16539
|
+
byAi: callers.filter((c) => c.decidedByAi).length,
|
|
15778
16540
|
routes: new Set(items.flatMap((i) => i.routes.map((r) => r.id))).size,
|
|
15779
16541
|
upstream: {
|
|
15780
16542
|
breaks: hops.filter((h) => h.verdict === "breaks").length,
|
|
@@ -15786,6 +16548,394 @@ function changeImpact(change, deltas, e, o) {
|
|
|
15786
16548
|
};
|
|
15787
16549
|
}
|
|
15788
16550
|
|
|
16551
|
+
// packages/planner/dist/stages/sketch.js
|
|
16552
|
+
var import_yaml20 = __toESM(require_dist(), 1);
|
|
16553
|
+
import { createHash as createHash6, randomBytes } from "node:crypto";
|
|
16554
|
+
import { readdir as readdir15, readFile as readFile22, rm as rm7 } from "node:fs/promises";
|
|
16555
|
+
import { join as join25 } from "node:path";
|
|
16556
|
+
var import_yaml21 = __toESM(require_dist(), 1);
|
|
16557
|
+
var Id3 = external_exports.string().regex(/^[a-z0-9][a-z0-9._-]*$/, "an id is lower case: letters, digits, . _ -");
|
|
16558
|
+
var text3 = () => external_exports.string().trim().min(1).max(500);
|
|
16559
|
+
var From = external_exports.object({
|
|
16560
|
+
component: Id3,
|
|
16561
|
+
/** The flow the step goes on; left out when the component has one flow, or a new one is named. */
|
|
16562
|
+
flow: Id3.optional(),
|
|
16563
|
+
/** A new flow (the component has none fitting, or is new): what a person calls it, and what starts it. */
|
|
16564
|
+
newFlow: external_exports.object({
|
|
16565
|
+
id: Id3,
|
|
16566
|
+
name: text3(),
|
|
16567
|
+
trigger: external_exports.object({
|
|
16568
|
+
kind: external_exports.enum(["endpoint", "action", "page", "listener", "job"]),
|
|
16569
|
+
what: text3(),
|
|
16570
|
+
entry: external_exports.string().trim().min(1).optional()
|
|
16571
|
+
}),
|
|
16572
|
+
/** A component the catalog doesn't have yet: where it runs (ADR 0132), drawn as a new box. */
|
|
16573
|
+
runsOn: external_exports.object({
|
|
16574
|
+
system: Id3.optional(),
|
|
16575
|
+
platform: external_exports.string().trim().min(1).optional(),
|
|
16576
|
+
cloud: external_exports.string().trim().min(1).optional(),
|
|
16577
|
+
framework: external_exports.string().trim().min(1).optional(),
|
|
16578
|
+
/** What the new box is (ADR 0180): one of the organisation's component types. */
|
|
16579
|
+
type: Id3.optional()
|
|
16580
|
+
}).optional()
|
|
16581
|
+
}).optional()
|
|
16582
|
+
});
|
|
16583
|
+
var Stroke = external_exports.discriminatedUnion("kind", [
|
|
16584
|
+
/** A line from a component to another's API: a call-out step. */
|
|
16585
|
+
external_exports.object({
|
|
16586
|
+
kind: external_exports.literal("call"),
|
|
16587
|
+
from: From,
|
|
16588
|
+
to: external_exports.object({
|
|
16589
|
+
component: Id3,
|
|
16590
|
+
/** An Endpoint id, "METHOD /path" or a topic; `new` when the API doesn't exist yet. */
|
|
16591
|
+
entry: text3(),
|
|
16592
|
+
new: external_exports.boolean().optional()
|
|
16593
|
+
}),
|
|
16594
|
+
what: text3().optional(),
|
|
16595
|
+
/** The step it goes after (0: first); left out, the end of the flow. */
|
|
16596
|
+
after: external_exports.number().int().min(0).optional()
|
|
16597
|
+
}),
|
|
16598
|
+
/** A topic a component publishes, and (optionally) a component that listens. */
|
|
16599
|
+
external_exports.object({
|
|
16600
|
+
kind: external_exports.literal("topic"),
|
|
16601
|
+
from: From,
|
|
16602
|
+
topic: text3(),
|
|
16603
|
+
listener: From.optional(),
|
|
16604
|
+
what: text3().optional()
|
|
16605
|
+
}),
|
|
16606
|
+
/** A table a step reads or writes. */
|
|
16607
|
+
external_exports.object({
|
|
16608
|
+
kind: external_exports.literal("table"),
|
|
16609
|
+
from: From,
|
|
16610
|
+
op: external_exports.enum(["read", "write", "lookup"]),
|
|
16611
|
+
table: Id3,
|
|
16612
|
+
columns: external_exports.array(external_exports.string().trim().min(1)).default([]),
|
|
16613
|
+
new: external_exports.boolean().optional(),
|
|
16614
|
+
what: text3().optional()
|
|
16615
|
+
}),
|
|
16616
|
+
/** A step of today's flow taken out (a line deleted). */
|
|
16617
|
+
external_exports.object({ kind: external_exports.literal("remove"), flow: Id3, step: external_exports.number().int().positive(), why: text3() }),
|
|
16618
|
+
/** A step of today's flow sent elsewhere (a line's end dragged): removed, and added to the new target. */
|
|
16619
|
+
external_exports.object({
|
|
16620
|
+
kind: external_exports.literal("retarget"),
|
|
16621
|
+
flow: Id3,
|
|
16622
|
+
step: external_exports.number().int().positive(),
|
|
16623
|
+
to: external_exports.object({ component: Id3, entry: text3(), new: external_exports.boolean().optional() }),
|
|
16624
|
+
why: text3()
|
|
16625
|
+
}),
|
|
16626
|
+
/** A stroke taken back: a drawn step, by flow and the step's index in the delta (its whole stroke goes). */
|
|
16627
|
+
external_exports.object({ kind: external_exports.literal("undo"), flow: Id3, index: external_exports.number().int().min(0) })
|
|
16628
|
+
]);
|
|
16629
|
+
var slug5 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
16630
|
+
var DRAWN = "drawn on the canvas";
|
|
16631
|
+
var MARK_RE = /drawn on the canvas by (.+?), stroke ([a-z0-9]+)\)?$/i;
|
|
16632
|
+
var deltaWhy = (by) => `Drawn on the canvas by ${by}.`;
|
|
16633
|
+
var stepWhy = (by, id, reason) => `${reason ? `${reason} (` : ""}${DRAWN} by ${by}, stroke ${id}${reason ? ")" : ""}`;
|
|
16634
|
+
var isDrawnDelta = (d) => d.why.toLowerCase().startsWith(DRAWN);
|
|
16635
|
+
var strokeIdOf = (s) => MARK_RE.exec(s.why ?? "")?.[2];
|
|
16636
|
+
var isDrawnStep = (s) => strokeIdOf(s) !== void 0;
|
|
16637
|
+
var drawnBy = (s) => MARK_RE.exec(s.why ?? "")?.[1];
|
|
16638
|
+
var newStrokeId = () => randomBytes(4).toString("hex");
|
|
16639
|
+
function flowsOf(deltas, component, estate) {
|
|
16640
|
+
const out = /* @__PURE__ */ new Map();
|
|
16641
|
+
for (const f of estate.flows)
|
|
16642
|
+
if (f.flow.component === component)
|
|
16643
|
+
out.set(f.flow.id, f.flow.name);
|
|
16644
|
+
for (const d of deltas)
|
|
16645
|
+
if (!out.has(d.flow) && d.change !== "remove" && (d.runsOn?.component === component || d.change === "new" && d.flow.startsWith(`${component}.`)))
|
|
16646
|
+
out.set(d.flow, d.name ?? d.flow);
|
|
16647
|
+
return [...out].map(([id, name]) => ({ id, name }));
|
|
16648
|
+
}
|
|
16649
|
+
function deltaFor(deltas, from, estate, by) {
|
|
16650
|
+
const own = flowsOf(deltas, from.component, estate);
|
|
16651
|
+
if (from.newFlow) {
|
|
16652
|
+
const id = from.newFlow.id;
|
|
16653
|
+
const had2 = deltas.find((d3) => d3.flow === id);
|
|
16654
|
+
if (had2?.change === "remove")
|
|
16655
|
+
return {
|
|
16656
|
+
problem: `${id} is removed in this change: take the removal back, or draw on another flow.`
|
|
16657
|
+
};
|
|
16658
|
+
if (had2)
|
|
16659
|
+
return { delta: had2, deltas };
|
|
16660
|
+
if (estate.flows.some((f) => f.flow.id === id))
|
|
16661
|
+
return { problem: `${id} is a flow of today's map already: draw on it, not a new one.` };
|
|
16662
|
+
const d2 = {
|
|
16663
|
+
schemaVersion: FLOW_DELTA_SCHEMA_VERSION,
|
|
16664
|
+
flow: id,
|
|
16665
|
+
repo: repoOf(estate, from.component) ?? from.component,
|
|
16666
|
+
change: "new",
|
|
16667
|
+
name: from.newFlow.name,
|
|
16668
|
+
trigger: from.newFlow.trigger,
|
|
16669
|
+
why: deltaWhy(by),
|
|
16670
|
+
...from.newFlow.runsOn || !estate.ids.has(from.component) ? { runsOn: { component: from.component, ...from.newFlow.runsOn ?? {} } } : {},
|
|
16671
|
+
steps: []
|
|
16672
|
+
};
|
|
16673
|
+
return { delta: d2, deltas: [...deltas, d2] };
|
|
16674
|
+
}
|
|
16675
|
+
const flowId = from.flow ?? (own.length === 1 ? own[0].id : void 0);
|
|
16676
|
+
if (!flowId) {
|
|
16677
|
+
if (!own.length)
|
|
16678
|
+
return {
|
|
16679
|
+
problem: `${from.component} has no flow on the map yet: name a new one for the stroke to start.`
|
|
16680
|
+
};
|
|
16681
|
+
return {
|
|
16682
|
+
problem: `${from.component} has ${own.length} flows: say which the stroke is on.`,
|
|
16683
|
+
pick: { flows: own }
|
|
16684
|
+
};
|
|
16685
|
+
}
|
|
16686
|
+
if (from.flow && !own.some((f) => f.id === from.flow))
|
|
16687
|
+
return { problem: `${from.flow} isn't a flow of ${from.component}.` };
|
|
16688
|
+
const had = deltas.find((d2) => d2.flow === flowId);
|
|
16689
|
+
if (had?.change === "remove")
|
|
16690
|
+
return {
|
|
16691
|
+
problem: `${flowId} is removed in this change: take the removal back, or draw on another flow.`
|
|
16692
|
+
};
|
|
16693
|
+
if (had)
|
|
16694
|
+
return { delta: had, deltas };
|
|
16695
|
+
const today = estate.flows.find((f) => f.flow.id === flowId);
|
|
16696
|
+
if (!today)
|
|
16697
|
+
return { problem: `No flow of today's map is ${flowId}.` };
|
|
16698
|
+
const d = {
|
|
16699
|
+
schemaVersion: FLOW_DELTA_SCHEMA_VERSION,
|
|
16700
|
+
flow: flowId,
|
|
16701
|
+
repo: today.repo,
|
|
16702
|
+
change: "modify",
|
|
16703
|
+
why: deltaWhy(by),
|
|
16704
|
+
steps: []
|
|
16705
|
+
};
|
|
16706
|
+
return { delta: d, deltas: [...deltas, d] };
|
|
16707
|
+
}
|
|
16708
|
+
var repoOf = (estate, component) => {
|
|
16709
|
+
const fromFlows = estate.flows.find((f) => f.flow.component === component)?.repo;
|
|
16710
|
+
if (fromFlows)
|
|
16711
|
+
return fromFlows;
|
|
16712
|
+
const r = estate.byId?.get(component);
|
|
16713
|
+
return r?.kind === "Component" && typeof r.repo === "string" && r.repo ? r.repo : void 0;
|
|
16714
|
+
};
|
|
16715
|
+
function noTodayFlow(estate, flow) {
|
|
16716
|
+
const component = flow.split(".")[0] ?? flow;
|
|
16717
|
+
const repo = repoOf(estate, component);
|
|
16718
|
+
if (repo && !estate.flows.some((f) => f.repo === repo))
|
|
16719
|
+
return `No flow of today's map is ${flow}: ${repo}'s spec has no flows yet, so there is no step to take out. Its spec step writes them: atticus spec draft ${repo}`;
|
|
16720
|
+
return `No flow of today's map is ${flow}.`;
|
|
16721
|
+
}
|
|
16722
|
+
function applyStroke(deltas, stroke, estate, by, strokeId = newStrokeId()) {
|
|
16723
|
+
const before = deltas.map((d) => ({ ...d, steps: [...d.steps] }));
|
|
16724
|
+
const replace = (all2, d, step) => all2.map((x) => x.flow === d.flow ? { ...x, steps: [...x.steps, step] } : x);
|
|
16725
|
+
const stepWhat = (what, fallback) => what ?? fallback;
|
|
16726
|
+
const why = (reason) => stepWhy(by, strokeId, reason);
|
|
16727
|
+
switch (stroke.kind) {
|
|
16728
|
+
case "call": {
|
|
16729
|
+
const r = deltaFor(before, stroke.from, estate, by);
|
|
16730
|
+
if ("problem" in r)
|
|
16731
|
+
return { deltas: before, changed: [], ...r };
|
|
16732
|
+
const step = {
|
|
16733
|
+
op: "add",
|
|
16734
|
+
...stroke.after !== void 0 ? { after: stroke.after } : {},
|
|
16735
|
+
kind: "call-out",
|
|
16736
|
+
what: stepWhat(stroke.what, `Calls ${stroke.to.component}'s ${stroke.to.entry}.`),
|
|
16737
|
+
to: { component: stroke.to.component, entry: stroke.to.entry, new: !!stroke.to.new },
|
|
16738
|
+
why: why()
|
|
16739
|
+
};
|
|
16740
|
+
return { deltas: replace(r.deltas, r.delta, step), changed: [r.delta.flow] };
|
|
16741
|
+
}
|
|
16742
|
+
case "topic": {
|
|
16743
|
+
const r = deltaFor(before, stroke.from, estate, by);
|
|
16744
|
+
if ("problem" in r)
|
|
16745
|
+
return { deltas: before, changed: [], ...r };
|
|
16746
|
+
const pub = {
|
|
16747
|
+
op: "add",
|
|
16748
|
+
kind: "publish",
|
|
16749
|
+
what: stepWhat(stroke.what, `Publishes ${stroke.topic}.`),
|
|
16750
|
+
...stroke.listener && stroke.listener.component !== stroke.from.component ? { to: { component: stroke.listener.component, entry: stroke.topic, new: false } } : {},
|
|
16751
|
+
why: why()
|
|
16752
|
+
};
|
|
16753
|
+
let all2 = replace(r.deltas, r.delta, pub);
|
|
16754
|
+
const changed = [r.delta.flow];
|
|
16755
|
+
if (stroke.listener) {
|
|
16756
|
+
const l = deltaFor(all2, stroke.listener, estate, by);
|
|
16757
|
+
if ("problem" in l)
|
|
16758
|
+
return { deltas: before, changed: [], ...l };
|
|
16759
|
+
const listen = {
|
|
16760
|
+
op: "add",
|
|
16761
|
+
kind: "entry",
|
|
16762
|
+
what: `Listens on ${stroke.topic}.`,
|
|
16763
|
+
why: why()
|
|
16764
|
+
};
|
|
16765
|
+
all2 = replace(l.deltas, l.delta, listen);
|
|
16766
|
+
changed.push(l.delta.flow);
|
|
16767
|
+
}
|
|
16768
|
+
return { deltas: all2, changed };
|
|
16769
|
+
}
|
|
16770
|
+
case "table": {
|
|
16771
|
+
const r = deltaFor(before, stroke.from, estate, by);
|
|
16772
|
+
if ("problem" in r)
|
|
16773
|
+
return { deltas: before, changed: [], ...r };
|
|
16774
|
+
const step = {
|
|
16775
|
+
op: "add",
|
|
16776
|
+
kind: stroke.op,
|
|
16777
|
+
what: stepWhat(stroke.what, `${stroke.op === "write" ? "Writes" : "Reads"} ${stroke.table}${stroke.columns.length ? ` (${stroke.columns.join(", ")})` : ""}.`),
|
|
16778
|
+
store: { table: stroke.table, columns: stroke.columns, new: !!stroke.new },
|
|
16779
|
+
why: why()
|
|
16780
|
+
};
|
|
16781
|
+
return { deltas: replace(r.deltas, r.delta, step), changed: [r.delta.flow] };
|
|
16782
|
+
}
|
|
16783
|
+
case "remove":
|
|
16784
|
+
case "retarget": {
|
|
16785
|
+
const today = estate.flows.find((f) => f.flow.id === stroke.flow);
|
|
16786
|
+
if (!today)
|
|
16787
|
+
return {
|
|
16788
|
+
deltas: before,
|
|
16789
|
+
changed: [],
|
|
16790
|
+
problem: noTodayFlow(estate, stroke.flow)
|
|
16791
|
+
};
|
|
16792
|
+
const was = today.flow.steps[stroke.step - 1];
|
|
16793
|
+
if (!was)
|
|
16794
|
+
return {
|
|
16795
|
+
deltas: before,
|
|
16796
|
+
changed: [],
|
|
16797
|
+
problem: `${stroke.flow} has no step ${stroke.step}.`
|
|
16798
|
+
};
|
|
16799
|
+
let all2 = before;
|
|
16800
|
+
let d = all2.find((x) => x.flow === stroke.flow);
|
|
16801
|
+
if (d?.change === "remove")
|
|
16802
|
+
return {
|
|
16803
|
+
deltas: before,
|
|
16804
|
+
changed: [],
|
|
16805
|
+
problem: `${stroke.flow} is removed in this change: take the removal back, or draw on another flow.`
|
|
16806
|
+
};
|
|
16807
|
+
if (!d) {
|
|
16808
|
+
d = {
|
|
16809
|
+
schemaVersion: FLOW_DELTA_SCHEMA_VERSION,
|
|
16810
|
+
flow: stroke.flow,
|
|
16811
|
+
repo: today.repo,
|
|
16812
|
+
change: "modify",
|
|
16813
|
+
why: deltaWhy(by),
|
|
16814
|
+
steps: []
|
|
16815
|
+
};
|
|
16816
|
+
all2 = [...all2, d];
|
|
16817
|
+
}
|
|
16818
|
+
if (d.steps.some((s) => s.op !== "add" && s.step === stroke.step))
|
|
16819
|
+
return {
|
|
16820
|
+
deltas: before,
|
|
16821
|
+
changed: [],
|
|
16822
|
+
problem: `Step ${stroke.step} of ${stroke.flow} is changed already in this change.`
|
|
16823
|
+
};
|
|
16824
|
+
all2 = replace(all2, d, { op: "remove", step: stroke.step, why: why(stroke.why) });
|
|
16825
|
+
if (stroke.kind === "retarget")
|
|
16826
|
+
all2 = replace(all2, d, {
|
|
16827
|
+
op: "add",
|
|
16828
|
+
after: stroke.step,
|
|
16829
|
+
kind: was.kind,
|
|
16830
|
+
what: was.what,
|
|
16831
|
+
to: { component: stroke.to.component, entry: stroke.to.entry, new: !!stroke.to.new },
|
|
16832
|
+
why: why(stroke.why)
|
|
16833
|
+
});
|
|
16834
|
+
return { deltas: all2, changed: [stroke.flow] };
|
|
16835
|
+
}
|
|
16836
|
+
case "undo": {
|
|
16837
|
+
const d = before.find((x) => x.flow === stroke.flow);
|
|
16838
|
+
const s = d?.steps[stroke.index];
|
|
16839
|
+
const id = s ? strokeIdOf(s) : void 0;
|
|
16840
|
+
if (!d || !s || !id)
|
|
16841
|
+
return {
|
|
16842
|
+
deltas: before,
|
|
16843
|
+
changed: [],
|
|
16844
|
+
problem: s ? `Step ${stroke.index} of ${stroke.flow} wasn't drawn on the canvas: the stage wrote it, so edit the file or run the stage again.` : `${stroke.flow} has no step ${stroke.index} to take back.`
|
|
16845
|
+
};
|
|
16846
|
+
const all2 = before.map((x) => ({ ...x, steps: x.steps.filter((y) => strokeIdOf(y) !== id) })).filter((x) => !(x.steps.length === 0 && isDrawnDelta(x)));
|
|
16847
|
+
const changed = before.filter((x) => x.steps.some((y) => strokeIdOf(y) === id)).map((x) => x.flow);
|
|
16848
|
+
return { deltas: all2, changed };
|
|
16849
|
+
}
|
|
16850
|
+
}
|
|
16851
|
+
}
|
|
16852
|
+
function drawnStrokes(deltas) {
|
|
16853
|
+
const out = [];
|
|
16854
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16855
|
+
for (const d of deltas)
|
|
16856
|
+
d.steps.forEach((s, index) => {
|
|
16857
|
+
const id = strokeIdOf(s);
|
|
16858
|
+
if (!id || seen.has(id))
|
|
16859
|
+
return;
|
|
16860
|
+
seen.add(id);
|
|
16861
|
+
const pair = d.steps.find((y, j) => j !== index && strokeIdOf(y) === id);
|
|
16862
|
+
const what = s.op === "remove" ? pair?.to ? `step ${s.step} sent to ${pair.to.component} ${pair.to.entry}` : `step ${s.step} removed` : `${s.kind ?? s.op}${s.to ? ` \u2192 ${s.to.component} ${s.to.entry}` : ""}${s.store ? ` ${s.store.table}` : ""}`;
|
|
16863
|
+
const by = drawnBy(s);
|
|
16864
|
+
out.push({ flow: d.flow, index, what, ...by ? { by } : {} });
|
|
16865
|
+
});
|
|
16866
|
+
return out;
|
|
16867
|
+
}
|
|
16868
|
+
function sketchOf(deltas) {
|
|
16869
|
+
return deltas.map((d) => ({ ...d, steps: d.steps.filter(isDrawnStep) })).filter((d) => d.steps.length || isDrawnDelta(d));
|
|
16870
|
+
}
|
|
16871
|
+
function renderSketchDelta(d, estate, by) {
|
|
16872
|
+
const today = d.change === "new" ? void 0 : estate.flowById.get(d.flow)?.flow;
|
|
16873
|
+
const pinned = {
|
|
16874
|
+
...d,
|
|
16875
|
+
schemaVersion: FLOW_DELTA_SCHEMA_VERSION,
|
|
16876
|
+
...today ? { base: { steps: flowBase(today) } } : {}
|
|
16877
|
+
};
|
|
16878
|
+
return `# The target of flow ${d.flow} (ADR 0087): the steps this change adds, changes or removes. Drafted by ${by}; base pins the version of today's flow it was written against (ADR 0111).
|
|
16879
|
+
${(0, import_yaml20.stringify)(FlowDelta.parse(pinned), { lineWidth: 0 })}`;
|
|
16880
|
+
}
|
|
16881
|
+
var MARK3 = "<!-- Generated by atticus change architecture";
|
|
16882
|
+
async function planSketchWrite(dir, deltas, changed, estate, by) {
|
|
16883
|
+
const out = [];
|
|
16884
|
+
for (const flow of [...new Set(changed)]) {
|
|
16885
|
+
const d = deltas.find((x) => x.flow === flow);
|
|
16886
|
+
out.push({
|
|
16887
|
+
file: `${TARGET_DIR2}/${flow}.yaml`,
|
|
16888
|
+
...d ? { now: renderSketchDelta(d, estate, by) } : {}
|
|
16889
|
+
});
|
|
16890
|
+
}
|
|
16891
|
+
const read = async (f) => readFile22(join25(dir, f), "utf8").catch(() => void 0);
|
|
16892
|
+
const design = await read("design.md");
|
|
16893
|
+
if (design === void 0 || design.startsWith(MARK3)) {
|
|
16894
|
+
const t = await read(READS_FILE);
|
|
16895
|
+
const reads = t === void 0 ? void 0 : external_exports.array(ReadFlow).safeParse((0, import_yaml21.parse)(t)?.reads);
|
|
16896
|
+
const dt = await read(DEPLOY_FILE);
|
|
16897
|
+
const deploy = dt === void 0 ? void 0 : external_exports.array(DeployStep).safeParse((0, import_yaml21.parse)(dt)?.deploy);
|
|
16898
|
+
const all2 = deltas.filter((d) => d.steps.length || d.change !== "modify");
|
|
16899
|
+
const now = renderArchitecture(all2, estate, reads?.success ? reads.data : [], deploy?.success && deploy.data.length ? deploy.data : void 0);
|
|
16900
|
+
if (now !== design)
|
|
16901
|
+
out.push({ file: "design.md", now });
|
|
16902
|
+
}
|
|
16903
|
+
const data = await read("data.md");
|
|
16904
|
+
if (data === void 0 || data.startsWith(MARK3)) {
|
|
16905
|
+
const now = renderData([...deltas], estate);
|
|
16906
|
+
if (now && now !== data)
|
|
16907
|
+
out.push({ file: "data.md", now });
|
|
16908
|
+
else if (!now && data?.startsWith(MARK3))
|
|
16909
|
+
out.push({ file: "data.md" });
|
|
16910
|
+
}
|
|
16911
|
+
return out;
|
|
16912
|
+
}
|
|
16913
|
+
async function writeSketch(dir, files) {
|
|
16914
|
+
for (const f of files) {
|
|
16915
|
+
const path = join25(dir, ...f.file.split("/"));
|
|
16916
|
+
if (f.now === void 0)
|
|
16917
|
+
await rm7(path, { force: true });
|
|
16918
|
+
else
|
|
16919
|
+
await writeFileAtomic(path, f.now);
|
|
16920
|
+
}
|
|
16921
|
+
}
|
|
16922
|
+
async function sketchFingerprint(dir) {
|
|
16923
|
+
const h = createHash6("sha256");
|
|
16924
|
+
const take = async (rel) => {
|
|
16925
|
+
h.update(rel);
|
|
16926
|
+
h.update("\0");
|
|
16927
|
+
h.update(await readFile22(join25(dir, ...rel.split("/")), "utf8").catch(() => ""));
|
|
16928
|
+
h.update("\0");
|
|
16929
|
+
};
|
|
16930
|
+
for (const f of (await readdir15(join25(dir, TARGET_DIR2)).catch(() => [])).sort())
|
|
16931
|
+
if (f.endsWith(".yaml"))
|
|
16932
|
+
await take(`${TARGET_DIR2}/${f}`);
|
|
16933
|
+
for (const f of [READS_FILE, DEPLOY_FILE, "design.md", "data.md"])
|
|
16934
|
+
await take(f);
|
|
16935
|
+
return h.digest("hex").slice(0, 16);
|
|
16936
|
+
}
|
|
16937
|
+
var sketchSlug = slug5;
|
|
16938
|
+
|
|
15789
16939
|
// packages/planner/dist/export-work-items.js
|
|
15790
16940
|
var sentence6 = (s) => /[.!?]$/.test(s.trim()) ? s.trim() : `${s.trim()}.`;
|
|
15791
16941
|
var VERB = { add: "Add", change: "Change", remove: "Remove" };
|
|
@@ -15824,9 +16974,9 @@ function workItemsOf(d) {
|
|
|
15824
16974
|
}
|
|
15825
16975
|
|
|
15826
16976
|
// packages/planner/dist/conform.js
|
|
15827
|
-
var
|
|
15828
|
-
import { readdir as
|
|
15829
|
-
import { join as
|
|
16977
|
+
var import_yaml22 = __toESM(require_dist(), 1);
|
|
16978
|
+
import { readdir as readdir16, readFile as readFile23 } from "node:fs/promises";
|
|
16979
|
+
import { join as join26, posix as posix2 } from "node:path";
|
|
15830
16980
|
var str6 = (v) => typeof v === "string" ? v : void 0;
|
|
15831
16981
|
function httpEntry(entry) {
|
|
15832
16982
|
const m = /^([A-Za-z]+)\s+(\S+)$/.exec(entry.trim());
|
|
@@ -15878,6 +17028,17 @@ function conform(input) {
|
|
|
15878
17028
|
out.push(...records.filter((r) => r.kind === "Unresolved" && r.unresolvedKind === "event-publish" && r.owner === from && r.raw === to.entry.trim()));
|
|
15879
17029
|
return out;
|
|
15880
17030
|
};
|
|
17031
|
+
const unsettledCall = (records, from, entry) => {
|
|
17032
|
+
const http = httpEntry(entry);
|
|
17033
|
+
if (!http)
|
|
17034
|
+
return void 0;
|
|
17035
|
+
return records.find((r) => {
|
|
17036
|
+
if (r.kind !== "Unresolved" || r.unresolvedKind !== "call" || r.owner !== from)
|
|
17037
|
+
return false;
|
|
17038
|
+
const m = /^(\S+)\s+\S+\s+(\S+)$/.exec(str6(r.raw) ?? "");
|
|
17039
|
+
return !!m && m[1].toUpperCase() === http.method && normalizePath(m[2]) === http.path;
|
|
17040
|
+
});
|
|
17041
|
+
};
|
|
15881
17042
|
const endpointsFor2 = (records, component, entry) => {
|
|
15882
17043
|
const http = httpEntry(entry);
|
|
15883
17044
|
return records.filter((r) => {
|
|
@@ -15894,29 +17055,29 @@ function conform(input) {
|
|
|
15894
17055
|
};
|
|
15895
17056
|
const columnOf = (records, table, name) => records.find((r) => r.kind === "Column" && r.table === table && r.name === name);
|
|
15896
17057
|
const tableOf = (records, table) => records.find((r) => r.kind === "Table" && r.id === table);
|
|
15897
|
-
const
|
|
17058
|
+
const named2 = /* @__PURE__ */ new Set();
|
|
15898
17059
|
for (const d of input.deltas) {
|
|
15899
17060
|
const from = componentOf(d);
|
|
15900
17061
|
if (d.delta.change === "new" && d.delta.trigger) {
|
|
15901
17062
|
const entry = d.delta.trigger.entry ?? d.delta.trigger.what;
|
|
15902
17063
|
for (const r of endpointsFor2(after2, from, entry))
|
|
15903
|
-
|
|
17064
|
+
named2.add(key2(r));
|
|
15904
17065
|
}
|
|
15905
17066
|
for (const s of d.delta.steps) {
|
|
15906
17067
|
if (s.op === "remove")
|
|
15907
17068
|
continue;
|
|
15908
17069
|
if (s.to) {
|
|
15909
17070
|
for (const r of callsTo(after2, from, s.to))
|
|
15910
|
-
|
|
17071
|
+
named2.add(key2(r));
|
|
15911
17072
|
for (const r of endpointsFor2(after2, s.to.component, s.to.entry))
|
|
15912
|
-
|
|
17073
|
+
named2.add(key2(r));
|
|
15913
17074
|
}
|
|
15914
17075
|
if (s.store) {
|
|
15915
|
-
|
|
17076
|
+
named2.add(`Table/${s.store.table}`);
|
|
15916
17077
|
for (const c of s.store.columns) {
|
|
15917
17078
|
const col = columnOf(after2, s.store.table, c);
|
|
15918
17079
|
if (col)
|
|
15919
|
-
|
|
17080
|
+
named2.add(key2(col));
|
|
15920
17081
|
}
|
|
15921
17082
|
}
|
|
15922
17083
|
}
|
|
@@ -15928,12 +17089,32 @@ function conform(input) {
|
|
|
15928
17089
|
const designed = (d, line) => [
|
|
15929
17090
|
{ source: "artifact", path: d.file, line }
|
|
15930
17091
|
];
|
|
17092
|
+
const basis = input.basis;
|
|
17093
|
+
const decisive = !basis || basis.from === "facts" && basis.unread.length === 0;
|
|
17094
|
+
const readAgain = `/atticus-map ${repo}, then atticus verify ${repo} --write`;
|
|
17095
|
+
const unconfirmed = (claim) => {
|
|
17096
|
+
if (basis?.from === "facts") {
|
|
17097
|
+
const shown = basis.unread.slice(0, 3).join(", ");
|
|
17098
|
+
const more = basis.unread.length > 3 ? ` and ${basis.unread.length - 3} more` : "";
|
|
17099
|
+
return `No fact covers what changed in ${shown}${more} since ${repo} was read, so Atticus couldn't confirm ${claim} from the code; check it by hand, or read those files again (${readAgain}) and check the code again.`;
|
|
17100
|
+
}
|
|
17101
|
+
return `${repo} hasn't been read into facts at this commit, and the built-in readers alone are only a cross-check, so Atticus couldn't confirm ${claim} from the code; check it by hand, or read the repo (${readAgain}) and check the code again.`;
|
|
17102
|
+
};
|
|
15931
17103
|
for (const d of input.deltas) {
|
|
15932
17104
|
let stillThere2 = function(d2, item, to, line, verb) {
|
|
15933
17105
|
const kept = input.deltas.some((o) => o.delta.steps.some((x) => x.op !== "remove" && componentOf(o) === from && x.to?.component === to.component && x.to.entry === to.entry));
|
|
15934
17106
|
if (kept)
|
|
15935
17107
|
return;
|
|
15936
17108
|
const left = callsTo(after2, from, to)[0];
|
|
17109
|
+
if (!left && !decisive) {
|
|
17110
|
+
unchecked.push({
|
|
17111
|
+
flow: d2.delta.flow,
|
|
17112
|
+
item,
|
|
17113
|
+
kind: "call",
|
|
17114
|
+
why: unconfirmed(`that ${from} no longer calls ${to.component} ${to.entry}`)
|
|
17115
|
+
});
|
|
17116
|
+
return;
|
|
17117
|
+
}
|
|
15937
17118
|
push(left ? {
|
|
15938
17119
|
status: "still-there",
|
|
15939
17120
|
check: "call",
|
|
@@ -15986,7 +17167,18 @@ function conform(input) {
|
|
|
15986
17167
|
why: `The scans read no column of ${st.table} (its columns are kept where the reader doesn't look), so ${st.columns.join(", ")} can't be checked here.`
|
|
15987
17168
|
});
|
|
15988
17169
|
} else if (st.new || table) {
|
|
15989
|
-
|
|
17170
|
+
const absent = [
|
|
17171
|
+
...st.new && !table ? [`the table ${st.table}`] : [],
|
|
17172
|
+
...st.columns.filter((c) => !columnOf(after2, st.table, c)).map((c) => `${st.table}.${c}`)
|
|
17173
|
+
];
|
|
17174
|
+
if (absent.length && !decisive)
|
|
17175
|
+
unchecked.push({
|
|
17176
|
+
flow: d2.delta.flow,
|
|
17177
|
+
item,
|
|
17178
|
+
kind: s.kind ?? "step",
|
|
17179
|
+
why: unconfirmed(`that the code has ${absent.join(", ")}`)
|
|
17180
|
+
});
|
|
17181
|
+
if (st.new && (table || decisive))
|
|
15990
17182
|
push(table ? {
|
|
15991
17183
|
status: "found",
|
|
15992
17184
|
check: "table",
|
|
@@ -16003,6 +17195,8 @@ function conform(input) {
|
|
|
16003
17195
|
});
|
|
16004
17196
|
for (const c of st.columns) {
|
|
16005
17197
|
const col = columnOf(after2, st.table, c);
|
|
17198
|
+
if (!col && !decisive)
|
|
17199
|
+
continue;
|
|
16006
17200
|
push(col ? {
|
|
16007
17201
|
status: "found",
|
|
16008
17202
|
check: "column",
|
|
@@ -16043,18 +17237,25 @@ function conform(input) {
|
|
|
16043
17237
|
const entry = t?.entry ?? t?.what ?? "";
|
|
16044
17238
|
if (t && (t.kind === "endpoint" || t.kind === "listener") && (httpEntry(entry) || t.entry)) {
|
|
16045
17239
|
const found = endpointsFor2(after2, from, entry)[0];
|
|
16046
|
-
|
|
16047
|
-
|
|
16048
|
-
|
|
16049
|
-
|
|
16050
|
-
|
|
16051
|
-
|
|
16052
|
-
|
|
16053
|
-
|
|
16054
|
-
|
|
16055
|
-
|
|
16056
|
-
|
|
16057
|
-
|
|
17240
|
+
if (!found && !decisive)
|
|
17241
|
+
unchecked.push({
|
|
17242
|
+
flow,
|
|
17243
|
+
kind: t.kind,
|
|
17244
|
+
why: unconfirmed(`that ${from} serves ${entry}, the trigger of ${flow}`)
|
|
17245
|
+
});
|
|
17246
|
+
else
|
|
17247
|
+
push(found ? {
|
|
17248
|
+
status: "found",
|
|
17249
|
+
check: "trigger",
|
|
17250
|
+
what: `${from} serves ${entry}, the trigger of ${flow}.`,
|
|
17251
|
+
record: found.id,
|
|
17252
|
+
at: citesIn(found, repo)
|
|
17253
|
+
} : {
|
|
17254
|
+
status: "missing",
|
|
17255
|
+
check: "trigger",
|
|
17256
|
+
what: `${from} serves no ${entry}, the trigger of new flow ${flow} (${d.file}).`,
|
|
17257
|
+
at: designed(d, d.triggerLine)
|
|
17258
|
+
});
|
|
16058
17259
|
} else
|
|
16059
17260
|
unchecked.push({
|
|
16060
17261
|
flow,
|
|
@@ -16097,20 +17298,36 @@ function conform(input) {
|
|
|
16097
17298
|
const found = callsTo(after2, from, s.to)[0];
|
|
16098
17299
|
const publish = kind === "publish";
|
|
16099
17300
|
const verb = publish ? "publishes to" : "calls";
|
|
16100
|
-
|
|
16101
|
-
|
|
16102
|
-
|
|
16103
|
-
|
|
16104
|
-
|
|
16105
|
-
|
|
16106
|
-
|
|
16107
|
-
|
|
16108
|
-
|
|
16109
|
-
|
|
16110
|
-
|
|
16111
|
-
|
|
16112
|
-
|
|
16113
|
-
|
|
17301
|
+
const unsettled = found ? void 0 : unsettledCall(after2, from, s.to.entry);
|
|
17302
|
+
if (!found && !decisive)
|
|
17303
|
+
unchecked.push({
|
|
17304
|
+
flow,
|
|
17305
|
+
item,
|
|
17306
|
+
kind,
|
|
17307
|
+
why: unconfirmed(`that ${from} ${verb} ${s.to.component} ${s.to.entry}`)
|
|
17308
|
+
});
|
|
17309
|
+
else if (unsettled)
|
|
17310
|
+
unchecked.push({
|
|
17311
|
+
flow,
|
|
17312
|
+
item,
|
|
17313
|
+
kind,
|
|
17314
|
+
why: `${from} calls ${unsettled.raw}, but where it goes isn't settled yet, so Atticus couldn't confirm it reaches ${s.to.component}; answer its map question on Questions, then check the code again.`
|
|
17315
|
+
});
|
|
17316
|
+
else
|
|
17317
|
+
push(found ? {
|
|
17318
|
+
status: "found",
|
|
17319
|
+
check: publish ? "publish" : "call",
|
|
17320
|
+
item,
|
|
17321
|
+
what: `${from} ${verb} ${s.to.component} ${s.to.entry}.`,
|
|
17322
|
+
record: found.id,
|
|
17323
|
+
at: citesIn(found, repo)
|
|
17324
|
+
} : {
|
|
17325
|
+
status: "missing",
|
|
17326
|
+
check: publish ? "publish" : "call",
|
|
17327
|
+
item,
|
|
17328
|
+
what: `${from} makes no ${publish ? "publish to" : "call to"} ${s.to.component} ${s.to.entry} (${where2(item)}).`,
|
|
17329
|
+
at: designed(d, line)
|
|
17330
|
+
});
|
|
16114
17331
|
if (s.op === "change" && old?.to && (old.to.component !== s.to.component || old.to.entry !== s.to.entry))
|
|
16115
17332
|
stillThere2(d, item, old.to, line, "sends elsewhere");
|
|
16116
17333
|
}
|
|
@@ -16128,7 +17345,7 @@ function conform(input) {
|
|
|
16128
17345
|
});
|
|
16129
17346
|
});
|
|
16130
17347
|
}
|
|
16131
|
-
const added = (r) => !beforeKeys.has(key2(r)) && !
|
|
17348
|
+
const added = (r) => !beforeKeys.has(key2(r)) && !named2.has(key2(r));
|
|
16132
17349
|
const scope = [];
|
|
16133
17350
|
const unexpectedTables = /* @__PURE__ */ new Set();
|
|
16134
17351
|
for (const r of mine(after2)) {
|
|
@@ -16193,13 +17410,13 @@ function componentOf(d) {
|
|
|
16193
17410
|
async function readConformDeltas(changeDir) {
|
|
16194
17411
|
const deltas = [];
|
|
16195
17412
|
const problems = [];
|
|
16196
|
-
const names = (await
|
|
17413
|
+
const names = (await readdir16(join26(changeDir, TARGET_DIR2)).catch(() => [])).filter((f) => f.endsWith(".yaml")).sort();
|
|
16197
17414
|
for (const f of names) {
|
|
16198
17415
|
const file = `${TARGET_DIR2}/${f}`;
|
|
16199
17416
|
try {
|
|
16200
|
-
const
|
|
16201
|
-
const lines = new
|
|
16202
|
-
const doc = (0,
|
|
17417
|
+
const text4 = await readFile23(join26(changeDir, TARGET_DIR2, f), "utf8");
|
|
17418
|
+
const lines = new import_yaml22.LineCounter();
|
|
17419
|
+
const doc = (0, import_yaml22.parseDocument)(text4, { lineCounter: lines });
|
|
16203
17420
|
const r = FlowDelta.safeParse(doc.toJS());
|
|
16204
17421
|
if (!r.success) {
|
|
16205
17422
|
problems.push(`${file}: ${r.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`);
|
|
@@ -16210,10 +17427,10 @@ async function readConformDeltas(changeDir) {
|
|
|
16210
17427
|
return range ? lines.linePos(range[0]).line : 1;
|
|
16211
17428
|
};
|
|
16212
17429
|
const steps = doc.get("steps", true);
|
|
16213
|
-
const stepLines = (0,
|
|
17430
|
+
const stepLines = (0, import_yaml22.isSeq)(steps) ? steps.items.map(lineOf2) : [];
|
|
16214
17431
|
const root = doc.contents;
|
|
16215
|
-
const trigger = (0,
|
|
16216
|
-
const flowKey = (0,
|
|
17432
|
+
const trigger = (0, import_yaml22.isMap)(root) ? root.items.find((p) => String(p.key) === "trigger") : void 0;
|
|
17433
|
+
const flowKey = (0, import_yaml22.isMap)(root) ? root.items.find((p) => String(p.key) === "flow") : void 0;
|
|
16217
17434
|
deltas.push({
|
|
16218
17435
|
file,
|
|
16219
17436
|
delta: r.data,
|
|
@@ -16228,13 +17445,13 @@ async function readConformDeltas(changeDir) {
|
|
|
16228
17445
|
}
|
|
16229
17446
|
async function readTaskSteps(changeDir) {
|
|
16230
17447
|
const out = /* @__PURE__ */ new Map();
|
|
16231
|
-
const dir =
|
|
16232
|
-
for (const f of (await
|
|
17448
|
+
const dir = join26(changeDir, "stories");
|
|
17449
|
+
for (const f of (await readdir16(dir).catch(() => [])).sort()) {
|
|
16233
17450
|
if (!f.endsWith(".md"))
|
|
16234
17451
|
continue;
|
|
16235
|
-
const
|
|
17452
|
+
const text4 = await readFile23(join26(dir, f), "utf8").catch(() => "");
|
|
16236
17453
|
let task;
|
|
16237
|
-
for (const line of
|
|
17454
|
+
for (const line of text4.split(/\r?\n/)) {
|
|
16238
17455
|
const t = /^###\s+Task\s+(\S+?):/.exec(line);
|
|
16239
17456
|
if (t) {
|
|
16240
17457
|
task = t[1].toLowerCase();
|
|
@@ -16276,6 +17493,8 @@ export {
|
|
|
16276
17493
|
draftFor,
|
|
16277
17494
|
renderSnow,
|
|
16278
17495
|
readDrafts,
|
|
17496
|
+
SnowFillError,
|
|
17497
|
+
fillSnowFields,
|
|
16279
17498
|
planStories,
|
|
16280
17499
|
snowDraft,
|
|
16281
17500
|
StoryEval,
|
|
@@ -16348,12 +17567,38 @@ export {
|
|
|
16348
17567
|
questionsOnFile,
|
|
16349
17568
|
stepQuestionJudge,
|
|
16350
17569
|
stepJudges,
|
|
16351
|
-
|
|
17570
|
+
DRAFTS_SCHEMA_VERSION,
|
|
17571
|
+
DRAFTED_FILE,
|
|
17572
|
+
CORRECTIONS_FILE,
|
|
17573
|
+
DraftedRecord,
|
|
17574
|
+
DraftedFile,
|
|
17575
|
+
CorrectionScope,
|
|
17576
|
+
CorrectionState,
|
|
17577
|
+
Correction,
|
|
17578
|
+
CorrectionsFile,
|
|
17579
|
+
draftSha,
|
|
17580
|
+
readDrafted,
|
|
17581
|
+
recordAiDraft,
|
|
17582
|
+
recordPersonSave,
|
|
17583
|
+
draftProvenance,
|
|
17584
|
+
DRAFT_ITEMS_MAX,
|
|
17585
|
+
draftItems,
|
|
17586
|
+
draftItemText,
|
|
17587
|
+
readCorrections,
|
|
17588
|
+
addCorrection,
|
|
17589
|
+
removeCorrection,
|
|
17590
|
+
CORRECTIONS_RULE,
|
|
17591
|
+
correctionsText,
|
|
17592
|
+
stageCorrections,
|
|
17593
|
+
snapshotFiles,
|
|
17594
|
+
settleCorrections,
|
|
17595
|
+
import_yaml8 as import_yaml,
|
|
16352
17596
|
filePending,
|
|
16353
17597
|
judged,
|
|
16354
17598
|
yamlOf,
|
|
16355
17599
|
EXPAND_AGENT,
|
|
16356
17600
|
DIGEST_KINDS,
|
|
17601
|
+
titledText,
|
|
16357
17602
|
checkExpansion,
|
|
16358
17603
|
expansionInput,
|
|
16359
17604
|
expandFor,
|
|
@@ -16411,6 +17656,8 @@ export {
|
|
|
16411
17656
|
PERSON_DRAFT,
|
|
16412
17657
|
HAND_BACKS,
|
|
16413
17658
|
lineExists,
|
|
17659
|
+
retireSettledEscalations,
|
|
17660
|
+
retireChangeEscalations,
|
|
16414
17661
|
stageRetrievalAi,
|
|
16415
17662
|
retrievalWaiting,
|
|
16416
17663
|
withoutResume,
|
|
@@ -16497,6 +17744,7 @@ export {
|
|
|
16497
17744
|
isEmptyDelta,
|
|
16498
17745
|
emptyDeltaIssues,
|
|
16499
17746
|
splitAnswer,
|
|
17747
|
+
SKETCH_RULE,
|
|
16500
17748
|
readDeltas,
|
|
16501
17749
|
entriesOf,
|
|
16502
17750
|
mergedFlowsText,
|
|
@@ -16536,6 +17784,7 @@ export {
|
|
|
16536
17784
|
NeedsFile,
|
|
16537
17785
|
needQuoteProblem,
|
|
16538
17786
|
whenFromLeadTime,
|
|
17787
|
+
lineAtFolded,
|
|
16539
17788
|
readNeeds,
|
|
16540
17789
|
renderNeeds,
|
|
16541
17790
|
findNeeds,
|
|
@@ -16633,6 +17882,8 @@ export {
|
|
|
16633
17882
|
promptFileOf,
|
|
16634
17883
|
taskPromptFiles,
|
|
16635
17884
|
renderStoryFile,
|
|
17885
|
+
buildLine,
|
|
17886
|
+
withBuildLine,
|
|
16636
17887
|
tickedTasks,
|
|
16637
17888
|
mergeRequests,
|
|
16638
17889
|
writeStories,
|
|
@@ -16660,6 +17911,21 @@ export {
|
|
|
16660
17911
|
MAX_CHAIN_DEPTH,
|
|
16661
17912
|
GUESSED_LINK_RULE,
|
|
16662
17913
|
changeImpact,
|
|
17914
|
+
Stroke,
|
|
17915
|
+
DRAWN,
|
|
17916
|
+
isDrawnDelta,
|
|
17917
|
+
strokeIdOf,
|
|
17918
|
+
isDrawnStep,
|
|
17919
|
+
drawnBy,
|
|
17920
|
+
newStrokeId,
|
|
17921
|
+
applyStroke,
|
|
17922
|
+
drawnStrokes,
|
|
17923
|
+
sketchOf,
|
|
17924
|
+
renderSketchDelta,
|
|
17925
|
+
planSketchWrite,
|
|
17926
|
+
writeSketch,
|
|
17927
|
+
sketchFingerprint,
|
|
17928
|
+
sketchSlug,
|
|
16663
17929
|
workItemsOf,
|
|
16664
17930
|
conform,
|
|
16665
17931
|
readConformDeltas,
|