@poetic-ai/poetic 1.42.0
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/.github/SECURITY.md +47 -0
- package/.nvmrc +1 -0
- package/.poetic/README.md +37 -0
- package/.poetic/providers/catalog.json +12051 -0
- package/.poetic/providers/pricing.json +1272 -0
- package/.poetic/providers/registry.json +3369 -0
- package/CHANGELOG.md +552 -0
- package/CODE_OF_CONDUCT.md +40 -0
- package/CONTRIBUTING.md +23 -0
- package/INSTALL.md +454 -0
- package/LICENSE +21 -0
- package/README.md +474 -0
- package/dist/BasicOptimizationCompetitionRunner-5H5TBYO4.js +153 -0
- package/dist/ExecutionTracker-UVONC4C6.js +16 -0
- package/dist/OptimizationConfig-7DFY2TST.js +17 -0
- package/dist/OptimizationEngine-JIMGP3LY.js +1597 -0
- package/dist/PromptStore-U6ZD3ZMQ.js +18 -0
- package/dist/actions-LUJPINLJ.js +322 -0
- package/dist/agent-ingest-3DJUJ7FU.js +53 -0
- package/dist/aggregator-DNCBINFO.js +12 -0
- package/dist/ai-judge-5QT5QJVU.js +156 -0
- package/dist/allowlist-grounding-RO6HKUFI.js +16 -0
- package/dist/allowlist-utils-23V7TJ3G.js +28 -0
- package/dist/anchored-turn-service-JULUCUIK.js +489 -0
- package/dist/api-transport-4TYTNLTJ.js +1087 -0
- package/dist/apply-completion-mode-AW2MOS3R.js +89 -0
- package/dist/artifact-migrator-23L45ISD.js +267 -0
- package/dist/ask-HWVV6QFR.js +135 -0
- package/dist/auth-5KEDKAPP.js +67 -0
- package/dist/auth-7TT3JINV.js +78 -0
- package/dist/auth-E6XUNZ5M.js +68 -0
- package/dist/auth-H3XDZE22.js +134 -0
- package/dist/auth-OC4E6633.js +71 -0
- package/dist/auth-S4CTX4GQ.js +44 -0
- package/dist/auth-YLH3S7EY.js +69 -0
- package/dist/auth-liveness-TDDEZY3T.js +46 -0
- package/dist/auto-optimizer-VPRWOWGU.js +80 -0
- package/dist/autoloop-cleanup-JOGKTLWA.js +109 -0
- package/dist/autoloop-evidence-gates-PCDALGVJ.js +38 -0
- package/dist/autoloop-gate-policy-Z7FVJVXV.js +175 -0
- package/dist/autoloop-helpers-2KTK43GL.js +22 -0
- package/dist/autoloop-ledger-W7QO62PX.js +96 -0
- package/dist/autoloop-ledger-subscriber-Z5RW5EIE.js +162 -0
- package/dist/autoloop-run-defaults-FERGRZGP.js +19 -0
- package/dist/autoloop-run-lock-CT2NWSV3.js +197 -0
- package/dist/autoloop-success-J2VKKBXQ.js +85 -0
- package/dist/autoloop-termination-summary-RN75F422.js +58 -0
- package/dist/autoloop-trajectory-XFIEXP5Z.js +282 -0
- package/dist/autoloop-wall-clock-T7ZVIQOC.js +12 -0
- package/dist/autonomous-loop-controller-PW4XVFGQ.js +4120 -0
- package/dist/backend-P3DAYRP7.js +29 -0
- package/dist/background-executor-CBN552V4.js +257 -0
- package/dist/backlog-execution-intent-U4XYPTV6.js +15 -0
- package/dist/backup-active-store-5WXG6SXT.js +17 -0
- package/dist/backup-telemetry-db-XGLXAA3N.js +254 -0
- package/dist/basic-competition-master-VCSBJVRN.js +344 -0
- package/dist/branch-archive-manager-RP6FP3ZC.js +254 -0
- package/dist/branch-cleanup-manager-RSHAI7YR.js +20 -0
- package/dist/build-gate-YHGRB4QB.js +71 -0
- package/dist/change-summary-WY7J5TOX.js +33 -0
- package/dist/chunk-23A26CSN.js +123 -0
- package/dist/chunk-27UTJD3M.js +265 -0
- package/dist/chunk-2A5CILN3.js +310 -0
- package/dist/chunk-2AQUWJPW.js +225 -0
- package/dist/chunk-2BZ53J6J.js +2344 -0
- package/dist/chunk-2DY5KNVM.js +276 -0
- package/dist/chunk-2EIA4RBE.js +659 -0
- package/dist/chunk-2IUXIGQD.js +1064 -0
- package/dist/chunk-2N42MS2Z.js +2193 -0
- package/dist/chunk-2QJ3H3L7.js +42 -0
- package/dist/chunk-2QLYTVYX.js +791 -0
- package/dist/chunk-2VSQMRCB.js +18 -0
- package/dist/chunk-2XQXYBM2.js +130 -0
- package/dist/chunk-2YFUIER7.js +426 -0
- package/dist/chunk-34CNP2HB.js +148 -0
- package/dist/chunk-35P3W3JX.js +456 -0
- package/dist/chunk-36PEBZAF.js +18 -0
- package/dist/chunk-3AOKYKA7.js +22 -0
- package/dist/chunk-3BCUNHKF.js +475 -0
- package/dist/chunk-3FLXLLB7.js +26 -0
- package/dist/chunk-3FOZ2VHV.js +100 -0
- package/dist/chunk-3G6FZCRF.js +106 -0
- package/dist/chunk-3M57DADF.js +44 -0
- package/dist/chunk-3S62LLWJ.js +543 -0
- package/dist/chunk-3ZPQLGV7.js +2904 -0
- package/dist/chunk-44N5WZZH.js +45 -0
- package/dist/chunk-45FPSA3B.js +125 -0
- package/dist/chunk-4BJ3O4RQ.js +929 -0
- package/dist/chunk-4BOP7T6L.js +2321 -0
- package/dist/chunk-4BTDUT2S.js +42 -0
- package/dist/chunk-4CSS7WVI.js +143 -0
- package/dist/chunk-4CUY7AFY.js +247 -0
- package/dist/chunk-4GSN5O5J.js +62 -0
- package/dist/chunk-4HN6DF7V.js +1027 -0
- package/dist/chunk-4IAPOG5V.js +415 -0
- package/dist/chunk-4IYIZQWV.js +48 -0
- package/dist/chunk-4NXPXE62.js +84 -0
- package/dist/chunk-4RCCF5BR.js +712 -0
- package/dist/chunk-4V7NAQ4R.js +19527 -0
- package/dist/chunk-4VJITV4P.js +445 -0
- package/dist/chunk-4YX7J6QL.js +1859 -0
- package/dist/chunk-527OBWQB.js +338 -0
- package/dist/chunk-5D4A7E3D.js +448 -0
- package/dist/chunk-5DLKYTQX.js +4003 -0
- package/dist/chunk-5EERLVZB.js +139 -0
- package/dist/chunk-5H72XE4H.js +256 -0
- package/dist/chunk-5HDP7XZG.js +91 -0
- package/dist/chunk-5LAMRH4X.js +969 -0
- package/dist/chunk-5LTN43AS.js +1972 -0
- package/dist/chunk-5RBHQYDB.js +2206 -0
- package/dist/chunk-5WCQRDRB.js +588 -0
- package/dist/chunk-5WRLK5KW.js +700 -0
- package/dist/chunk-5ZCXC23G.js +595 -0
- package/dist/chunk-6BM72ZMG.js +31 -0
- package/dist/chunk-6EDAJH2I.js +68 -0
- package/dist/chunk-6EQKFDBH.js +457 -0
- package/dist/chunk-6MFRHTRI.js +115 -0
- package/dist/chunk-6TZJRKNW.js +321 -0
- package/dist/chunk-6V7HGQFZ.js +20 -0
- package/dist/chunk-6VWVO2TU.js +3746 -0
- package/dist/chunk-6Y5TWI7H.js +146 -0
- package/dist/chunk-6Y5U7UFY.js +91 -0
- package/dist/chunk-6ZLAK3XG.js +14 -0
- package/dist/chunk-72XCRQED.js +34 -0
- package/dist/chunk-72YTZMAM.js +589 -0
- package/dist/chunk-73BBJOKA.js +263 -0
- package/dist/chunk-73NCTWKL.js +188 -0
- package/dist/chunk-77I6G4CO.js +1 -0
- package/dist/chunk-7DHRDWFS.js +3104 -0
- package/dist/chunk-7DNSNKJV.js +78 -0
- package/dist/chunk-7FMJVYBS.js +307 -0
- package/dist/chunk-7GDRQBQC.js +419 -0
- package/dist/chunk-7GPCEFUV.js +270 -0
- package/dist/chunk-7IL4A2PP.js +948 -0
- package/dist/chunk-7MMLMXO6.js +3679 -0
- package/dist/chunk-7QCZSLBH.js +1 -0
- package/dist/chunk-7R2WLDOS.js +62 -0
- package/dist/chunk-7T7RUJI7.js +949 -0
- package/dist/chunk-A2WKSVX7.js +43 -0
- package/dist/chunk-A3YGID55.js +133 -0
- package/dist/chunk-A722DEVA.js +14 -0
- package/dist/chunk-ACDIT7OP.js +1376 -0
- package/dist/chunk-ACKIPAOJ.js +302 -0
- package/dist/chunk-APPNGGN7.js +365 -0
- package/dist/chunk-APV6MK5E.js +137 -0
- package/dist/chunk-ASEP3M2W.js +968 -0
- package/dist/chunk-AT4TNPWW.js +2931 -0
- package/dist/chunk-ATVKCSGT.js +250 -0
- package/dist/chunk-AURFMVCD.js +899 -0
- package/dist/chunk-AX6GWE7V.js +47 -0
- package/dist/chunk-AY5EWJPF.js +385 -0
- package/dist/chunk-BAV2HJXS.js +194 -0
- package/dist/chunk-BBHRN366.js +89 -0
- package/dist/chunk-BBPSTTPA.js +1004 -0
- package/dist/chunk-BDATUDDJ.js +992 -0
- package/dist/chunk-BKE5PG5L.js +20414 -0
- package/dist/chunk-BLPHWMDK.js +797 -0
- package/dist/chunk-BSE4R6XD.js +322 -0
- package/dist/chunk-BXGMAU4A.js +396 -0
- package/dist/chunk-BXI4NXL3.js +507 -0
- package/dist/chunk-BZU2O43F.js +1125 -0
- package/dist/chunk-C3L6YQ7P.js +147 -0
- package/dist/chunk-C57KJOUQ.js +121 -0
- package/dist/chunk-CCV7BPSY.js +118 -0
- package/dist/chunk-CD7ISXA4.js +826 -0
- package/dist/chunk-CE5OZBYY.js +21 -0
- package/dist/chunk-CFBIG37O.js +154 -0
- package/dist/chunk-CJC6GZ46.js +457 -0
- package/dist/chunk-CKCDGXIV.js +408 -0
- package/dist/chunk-CKNX3TTP.js +10338 -0
- package/dist/chunk-CLKSETWE.js +10 -0
- package/dist/chunk-CP7H3HSP.js +1552 -0
- package/dist/chunk-CPLEWXNZ.js +66 -0
- package/dist/chunk-CQM3A35X.js +844 -0
- package/dist/chunk-CS6P76D3.js +103 -0
- package/dist/chunk-CTLUBNCW.js +622 -0
- package/dist/chunk-CUSRN6RJ.js +632 -0
- package/dist/chunk-D5EP5D2W.js +26 -0
- package/dist/chunk-D5RYINO7.js +412 -0
- package/dist/chunk-DBFS4HMH.js +751 -0
- package/dist/chunk-DC4ZYMXJ.js +115 -0
- package/dist/chunk-DCBZP442.js +9243 -0
- package/dist/chunk-DCYF7EKG.js +26 -0
- package/dist/chunk-DDMAFNUK.js +5351 -0
- package/dist/chunk-DF5SLDE4.js +966 -0
- package/dist/chunk-DJFW7SMX.js +348 -0
- package/dist/chunk-DQFLLBJW.js +121 -0
- package/dist/chunk-DSFM56OA.js +1203 -0
- package/dist/chunk-DTY4APYV.js +218 -0
- package/dist/chunk-DXH5JBRS.js +437 -0
- package/dist/chunk-E3NTPHEO.js +194 -0
- package/dist/chunk-E74LUIID.js +302 -0
- package/dist/chunk-EI57M5BI.js +194 -0
- package/dist/chunk-EJ4ZAWLV.js +305 -0
- package/dist/chunk-EJGCZGUF.js +1483 -0
- package/dist/chunk-EK2U3YEG.js +486 -0
- package/dist/chunk-ELMFYNV7.js +109 -0
- package/dist/chunk-ENRGKNR5.js +25 -0
- package/dist/chunk-ETYDXCK7.js +679 -0
- package/dist/chunk-EYRH3W74.js +239 -0
- package/dist/chunk-F23DIWNN.js +1024 -0
- package/dist/chunk-F5JO7HAS.js +94 -0
- package/dist/chunk-F7ER6ANO.js +174 -0
- package/dist/chunk-FATUVIIN.js +374 -0
- package/dist/chunk-FC23CTXV.js +322 -0
- package/dist/chunk-FD4ERYEG.js +22 -0
- package/dist/chunk-FH7WOJAE.js +79 -0
- package/dist/chunk-FHOWVDF5.js +1342 -0
- package/dist/chunk-FKAUPHMR.js +96 -0
- package/dist/chunk-FONNFJXA.js +137 -0
- package/dist/chunk-FQ7FSZRW.js +75 -0
- package/dist/chunk-FRG57FPM.js +240 -0
- package/dist/chunk-FUVP64ER.js +31 -0
- package/dist/chunk-FYOILAN5.js +6818 -0
- package/dist/chunk-G35UYQ6Z.js +69 -0
- package/dist/chunk-G36W2T2S.js +1143 -0
- package/dist/chunk-G3L2L5XZ.js +49 -0
- package/dist/chunk-G4THLFV3.js +148 -0
- package/dist/chunk-GAFJEYLK.js +29 -0
- package/dist/chunk-GFGYB2ZU.js +1689 -0
- package/dist/chunk-GG6KJYW6.js +85 -0
- package/dist/chunk-GHHFDU2U.js +55 -0
- package/dist/chunk-GHTZSQB7.js +57 -0
- package/dist/chunk-GK37ILCX.js +1231 -0
- package/dist/chunk-GKN3HMN4.js +2809 -0
- package/dist/chunk-GNVDBVJP.js +156 -0
- package/dist/chunk-GPEL3FBM.js +924 -0
- package/dist/chunk-GPSNGPZS.js +1604 -0
- package/dist/chunk-GSGD5E4C.js +30 -0
- package/dist/chunk-GSUQJJXQ.js +331 -0
- package/dist/chunk-GSV226X6.js +110 -0
- package/dist/chunk-H2AYQIHW.js +224 -0
- package/dist/chunk-H66DXFS5.js +1374 -0
- package/dist/chunk-H6G7QRMQ.js +35 -0
- package/dist/chunk-H7JAGHZT.js +59 -0
- package/dist/chunk-H7ZEFECA.js +316 -0
- package/dist/chunk-HAC6EHZZ.js +79 -0
- package/dist/chunk-HIO33P3G.js +19 -0
- package/dist/chunk-HMVQIPH3.js +547 -0
- package/dist/chunk-HOYBYAOA.js +257 -0
- package/dist/chunk-HX5P3CSP.js +1784 -0
- package/dist/chunk-I46EG2XQ.js +65 -0
- package/dist/chunk-I4NPDSCB.js +279 -0
- package/dist/chunk-I5WRT7ZR.js +514 -0
- package/dist/chunk-IC7I6YIJ.js +301 -0
- package/dist/chunk-IDCSOZVN.js +78 -0
- package/dist/chunk-IFCBYECK.js +112 -0
- package/dist/chunk-IFZCPEY2.js +66 -0
- package/dist/chunk-IKFP2KUR.js +48 -0
- package/dist/chunk-IRFBN336.js +878 -0
- package/dist/chunk-IUXNGKA6.js +152 -0
- package/dist/chunk-IV2RVAN7.js +1356 -0
- package/dist/chunk-IZYIQQ7X.js +53 -0
- package/dist/chunk-J3UUR7DE.js +210 -0
- package/dist/chunk-J54VUAY2.js +566 -0
- package/dist/chunk-J62KPGII.js +464 -0
- package/dist/chunk-J6P3FWWW.js +130 -0
- package/dist/chunk-JKBHJQMT.js +434 -0
- package/dist/chunk-JLS65NUQ.js +14 -0
- package/dist/chunk-JOSYEJKY.js +141 -0
- package/dist/chunk-JPKSXKNB.js +94 -0
- package/dist/chunk-JQNJB2BB.js +2440 -0
- package/dist/chunk-JRVNSWSQ.js +2626 -0
- package/dist/chunk-JSWMRQYG.js +28 -0
- package/dist/chunk-JZKNBBIB.js +152 -0
- package/dist/chunk-K54Y7MLK.js +160 -0
- package/dist/chunk-K65546RE.js +183 -0
- package/dist/chunk-KM3F7SYE.js +702 -0
- package/dist/chunk-KNDYMVWN.js +102 -0
- package/dist/chunk-KNT72VR6.js +132 -0
- package/dist/chunk-KP6YILMU.js +482 -0
- package/dist/chunk-KQEBSK3T.js +274 -0
- package/dist/chunk-KT6VHSPF.js +84 -0
- package/dist/chunk-KTMW65UX.js +14 -0
- package/dist/chunk-KZ5CJVBP.js +1813 -0
- package/dist/chunk-L4427KOE.js +26 -0
- package/dist/chunk-L6OM4A22.js +189 -0
- package/dist/chunk-L7E6XCRA.js +1042 -0
- package/dist/chunk-L7JZLXQL.js +173 -0
- package/dist/chunk-LAMKBTXX.js +40 -0
- package/dist/chunk-LDCF2MKN.js +132 -0
- package/dist/chunk-LEIIWPWQ.js +78 -0
- package/dist/chunk-LFTQVYBB.js +31 -0
- package/dist/chunk-LG3PDRTR.js +2872 -0
- package/dist/chunk-LGHII5ET.js +23 -0
- package/dist/chunk-LH5QY22X.js +1205 -0
- package/dist/chunk-LIVJ6266.js +438 -0
- package/dist/chunk-LKRAR3IH.js +276 -0
- package/dist/chunk-LO23NABI.js +3063 -0
- package/dist/chunk-LO3X5NLS.js +326 -0
- package/dist/chunk-LPITVM7M.js +339 -0
- package/dist/chunk-LR5IJIGV.js +810 -0
- package/dist/chunk-LRGMHROI.js +79 -0
- package/dist/chunk-LUVKFFWR.js +315 -0
- package/dist/chunk-LWCBNGH6.js +35 -0
- package/dist/chunk-LWTN3WRV.js +260 -0
- package/dist/chunk-M3HQYKQX.js +308 -0
- package/dist/chunk-M6GVSPEH.js +1016 -0
- package/dist/chunk-MF5HP6XV.js +221 -0
- package/dist/chunk-MHLCBZJB.js +328 -0
- package/dist/chunk-MN6PL4AN.js +120 -0
- package/dist/chunk-MOEY65Z6.js +5678 -0
- package/dist/chunk-MQ4WA34C.js +206 -0
- package/dist/chunk-MWHF5V7U.js +223 -0
- package/dist/chunk-MWHLBPNU.js +211 -0
- package/dist/chunk-MWK5UZQ3.js +1068 -0
- package/dist/chunk-MYLH2S4G.js +64 -0
- package/dist/chunk-MZ5WYKNA.js +28 -0
- package/dist/chunk-N4VTRA7K.js +131 -0
- package/dist/chunk-N5DX4JAK.js +12 -0
- package/dist/chunk-N65O4XTH.js +5011 -0
- package/dist/chunk-NEQUQHOX.js +223 -0
- package/dist/chunk-NGK6PEOG.js +25 -0
- package/dist/chunk-NJVD4PWE.js +1033 -0
- package/dist/chunk-NM6PMYY4.js +768 -0
- package/dist/chunk-NN4PJDHP.js +708 -0
- package/dist/chunk-NP2V3L7K.js +226 -0
- package/dist/chunk-NRLY536M.js +260 -0
- package/dist/chunk-NRNE225N.js +1270 -0
- package/dist/chunk-NS2K2GTP.js +605 -0
- package/dist/chunk-NS33V3IM.js +51 -0
- package/dist/chunk-NSHASMZZ.js +71 -0
- package/dist/chunk-NWOYOA6H.js +378 -0
- package/dist/chunk-NWRUSEXG.js +322 -0
- package/dist/chunk-NZGDAWIP.js +1905 -0
- package/dist/chunk-O47UOKU3.js +395 -0
- package/dist/chunk-O4G2W3ST.js +9831 -0
- package/dist/chunk-O6V4PNLO.js +63 -0
- package/dist/chunk-OA4GPILI.js +624 -0
- package/dist/chunk-OGMSYL6J.js +227 -0
- package/dist/chunk-OIS7J26S.js +319 -0
- package/dist/chunk-OMX5VL43.js +79 -0
- package/dist/chunk-ON3G73BU.js +264 -0
- package/dist/chunk-OPYFYTWQ.js +220 -0
- package/dist/chunk-OQ6K46CK.js +1734 -0
- package/dist/chunk-OYZYO7TV.js +47 -0
- package/dist/chunk-OZ4REHIN.js +4386 -0
- package/dist/chunk-P47VN5F4.js +282 -0
- package/dist/chunk-P5I4X7A7.js +353 -0
- package/dist/chunk-P6VQROCO.js +594 -0
- package/dist/chunk-P7CQGPLS.js +30 -0
- package/dist/chunk-PCAGC5MR.js +1763 -0
- package/dist/chunk-PFTQBXS6.js +5181 -0
- package/dist/chunk-PGSPX4SU.js +15 -0
- package/dist/chunk-PIKLF7BM.js +432 -0
- package/dist/chunk-PJWJ3SAV.js +22 -0
- package/dist/chunk-PKIFMV72.js +100 -0
- package/dist/chunk-PL37JDNA.js +189 -0
- package/dist/chunk-PTH5E5XO.js +195 -0
- package/dist/chunk-PW6AXLQC.js +4128 -0
- package/dist/chunk-PYITM4N2.js +79 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-Q3RO7N35.js +45 -0
- package/dist/chunk-Q3WIC6GQ.js +2670 -0
- package/dist/chunk-Q5XIXSHX.js +271 -0
- package/dist/chunk-QGJDMEOV.js +34 -0
- package/dist/chunk-QNOB37UH.js +58 -0
- package/dist/chunk-QOCOVLUH.js +462 -0
- package/dist/chunk-QTCJ5CC5.js +172 -0
- package/dist/chunk-QVFV5IP2.js +232 -0
- package/dist/chunk-QVZMFDYG.js +29 -0
- package/dist/chunk-R35PEUKH.js +511 -0
- package/dist/chunk-R4UWBC35.js +200 -0
- package/dist/chunk-R6MLCREW.js +44 -0
- package/dist/chunk-R6W23LKN.js +254 -0
- package/dist/chunk-R7XDDX2A.js +4007 -0
- package/dist/chunk-RDFRCT64.js +168 -0
- package/dist/chunk-RE5VZDFQ.js +58 -0
- package/dist/chunk-RGROYC2G.js +638 -0
- package/dist/chunk-RGWHUI5E.js +68 -0
- package/dist/chunk-RHVG6UNL.js +205 -0
- package/dist/chunk-RI4EX2QE.js +18 -0
- package/dist/chunk-RJAAKDRX.js +17 -0
- package/dist/chunk-ROMHOSMQ.js +185 -0
- package/dist/chunk-RWREC3KJ.js +517 -0
- package/dist/chunk-RYNETRDJ.js +118 -0
- package/dist/chunk-RZY7RKM5.js +500 -0
- package/dist/chunk-S2MNWFAG.js +52 -0
- package/dist/chunk-S2VQCZO4.js +22 -0
- package/dist/chunk-S54MKU6V.js +117 -0
- package/dist/chunk-S5OIDFRM.js +302 -0
- package/dist/chunk-SCW4ZF6R.js +150 -0
- package/dist/chunk-SLS2N4PH.js +2213 -0
- package/dist/chunk-SNLBO4BF.js +247 -0
- package/dist/chunk-SP3LFU6B.js +60 -0
- package/dist/chunk-SQQTDDQA.js +537 -0
- package/dist/chunk-STV6LYYE.js +45 -0
- package/dist/chunk-SURZ2WFE.js +326 -0
- package/dist/chunk-SZ7DL357.js +1047 -0
- package/dist/chunk-T5E4NQCM.js +5742 -0
- package/dist/chunk-TAJPCXSB.js +13 -0
- package/dist/chunk-TAOOYK3P.js +358 -0
- package/dist/chunk-TDSEX5CF.js +24 -0
- package/dist/chunk-TFTGP7Z7.js +174 -0
- package/dist/chunk-TGON4N4O.js +197 -0
- package/dist/chunk-TGQZHDY2.js +2373 -0
- package/dist/chunk-TJJZ2OKV.js +6783 -0
- package/dist/chunk-TRSVUPCX.js +25 -0
- package/dist/chunk-TSXGRLPR.js +107 -0
- package/dist/chunk-U2S3YEX3.js +224 -0
- package/dist/chunk-U45RTRGY.js +818 -0
- package/dist/chunk-U6C3VVWS.js +13 -0
- package/dist/chunk-UDMS5AD3.js +307 -0
- package/dist/chunk-UGX7GI37.js +94 -0
- package/dist/chunk-UH3JJSHK.js +1 -0
- package/dist/chunk-UHRBTZYY.js +208 -0
- package/dist/chunk-UHT2KRG5.js +193 -0
- package/dist/chunk-UI2F6DJ5.js +118 -0
- package/dist/chunk-UMAKSB4Z.js +284 -0
- package/dist/chunk-UR2IBGIM.js +521 -0
- package/dist/chunk-UUH2RVKS.js +585 -0
- package/dist/chunk-UVJ4PJDK.js +4533 -0
- package/dist/chunk-V53UG2OT.js +9924 -0
- package/dist/chunk-V72RMBM4.js +48 -0
- package/dist/chunk-VBNCKCCI.js +16 -0
- package/dist/chunk-VNSYKC25.js +24 -0
- package/dist/chunk-VPHVFK4A.js +1984 -0
- package/dist/chunk-VTLNJQ44.js +133 -0
- package/dist/chunk-VTW7HP3R.js +100 -0
- package/dist/chunk-VVGEPBPS.js +218 -0
- package/dist/chunk-VVLLL7I4.js +166 -0
- package/dist/chunk-VX3UMWB7.js +483 -0
- package/dist/chunk-VXF4Q3FW.js +20 -0
- package/dist/chunk-W3JMT2YY.js +712 -0
- package/dist/chunk-WDUORIHF.js +877 -0
- package/dist/chunk-WDVTY4U6.js +148 -0
- package/dist/chunk-WIFZCHEG.js +12566 -0
- package/dist/chunk-WIRN3F75.js +736 -0
- package/dist/chunk-WMPU2UOV.js +82 -0
- package/dist/chunk-WTOKKE2V.js +784 -0
- package/dist/chunk-WX5IRLF6.js +740 -0
- package/dist/chunk-WZ6U3O7I.js +1581 -0
- package/dist/chunk-XAONGNST.js +57 -0
- package/dist/chunk-XB5YLCLB.js +13 -0
- package/dist/chunk-XEP5NQ5Y.js +688 -0
- package/dist/chunk-XGYF2QMZ.js +351 -0
- package/dist/chunk-XHEKQENB.js +255 -0
- package/dist/chunk-XNO4FM6X.js +1124 -0
- package/dist/chunk-XPHKYHO4.js +878 -0
- package/dist/chunk-XPK2I4NC.js +405 -0
- package/dist/chunk-XPU2XADL.js +2175 -0
- package/dist/chunk-XQD2B6BB.js +507 -0
- package/dist/chunk-XT2ZQVTC.js +558 -0
- package/dist/chunk-XTXJGGUV.js +351 -0
- package/dist/chunk-XUXVDPSZ.js +50 -0
- package/dist/chunk-XV7NZL4B.js +57 -0
- package/dist/chunk-XZE4XARH.js +563 -0
- package/dist/chunk-XZYR5EDO.js +152 -0
- package/dist/chunk-Y3J4JMTU.js +61 -0
- package/dist/chunk-Y55LZN5U.js +52 -0
- package/dist/chunk-Y5VN7DMP.js +112 -0
- package/dist/chunk-Y67VAHN4.js +459 -0
- package/dist/chunk-YAXSKEUH.js +3544 -0
- package/dist/chunk-YB3JD4E6.js +975 -0
- package/dist/chunk-YESZJFK6.js +220 -0
- package/dist/chunk-YGXKOBQQ.js +292 -0
- package/dist/chunk-YMX76IOS.js +31 -0
- package/dist/chunk-YOTXESEH.js +257 -0
- package/dist/chunk-YTYIEZQY.js +119 -0
- package/dist/chunk-YVORHQ2S.js +579 -0
- package/dist/chunk-Z2CUQDAR.js +3429 -0
- package/dist/chunk-Z2KYOFHS.js +7000 -0
- package/dist/chunk-Z6PC7QX6.js +161 -0
- package/dist/chunk-ZDTGECTN.js +235 -0
- package/dist/chunk-ZIPWI2LY.js +151 -0
- package/dist/chunk-ZSKKMTQJ.js +554 -0
- package/dist/chunk-ZTT5IVPY.js +1160 -0
- package/dist/chunk-ZX65UI5W.js +78 -0
- package/dist/chunk-ZYG7GGKO.js +218 -0
- package/dist/chunk-ZZZUL4OF.js +38 -0
- package/dist/cli-utils-IXL26KJT.js +40 -0
- package/dist/cli-validation-WLXWLUJL.js +111 -0
- package/dist/commit-utils-P7CRJF5N.js +202 -0
- package/dist/compete-config-resolver-R5ULXMYH.js +76 -0
- package/dist/compete-request-62JESVET.js +364 -0
- package/dist/compete-results-QEPG6FZX.js +208 -0
- package/dist/competition-outcome-tracker-R32OOZ43.js +38 -0
- package/dist/config-JCOKXOQT.js +166 -0
- package/dist/config-audit-EL3GHXS7.js +360 -0
- package/dist/config-explain-TNILDEVJ.js +12 -0
- package/dist/config-loader-VPFPFO4B.js +38 -0
- package/dist/config-manager-7T4E2BQO.js +67 -0
- package/dist/config-validator-KKOHOW7O.js +72 -0
- package/dist/cost-YC42XUF3.js +53 -0
- package/dist/coverage-orchestrator-CISNKJC2.js +772 -0
- package/dist/coverage-scanner-YVD62MLH.js +12 -0
- package/dist/createCompetitionRunner-LJG6ZQSO.js +46 -0
- package/dist/data-migration-state-VJK64DLT.js +31 -0
- package/dist/db-migrator-XSEIOGWY.js +34 -0
- package/dist/discovery-JGYGDD25.js +25 -0
- package/dist/doctor-GJOYE7AE.js +186 -0
- package/dist/domain-analyzer-AWF3RP6E.js +29 -0
- package/dist/ensure-initialized-744DIZEZ.js +52 -0
- package/dist/entry.js +940 -0
- package/dist/environment-YS45U5ZI.js +81 -0
- package/dist/error-parser-core-PPZ36A4S.js +50 -0
- package/dist/escalation-ladder-MKTCEGSA.js +130 -0
- package/dist/evaluation-context-KV4GZNN7.js +35 -0
- package/dist/evidence-plan-resolver-GBSYSAPX.js +85 -0
- package/dist/execution-data-writer-3ODTBDLH.js +53 -0
- package/dist/execution-policy-applier-ZHD3DCF3.js +31 -0
- package/dist/execution-preflight-Z4Y64V3I.js +209 -0
- package/dist/execution-roles-R3DPPMD3.js +66 -0
- package/dist/exit-code-error-Z4SW2DVK.js +14 -0
- package/dist/external-temp-cleanup-N2RZH4QP.js +311 -0
- package/dist/factory-XBN7DFIM.js +149 -0
- package/dist/file-utils-XIVR2ZMA.js +47 -0
- package/dist/flywheel-autoloop-executor-UILGOFFK.js +249 -0
- package/dist/flywheel-competition-executor-JOBEXTRB.js +259 -0
- package/dist/flywheel-executor-6JFLA6J5.js +369 -0
- package/dist/flywheel-git-isolation-UC5NIEVU.js +48 -0
- package/dist/flywheel-manifest-5UC4VBMA.js +108 -0
- package/dist/flywheel-model-defaults-7CVKP53V.js +27 -0
- package/dist/flywheel-preflight-XEY5SHM6.js +104 -0
- package/dist/flywheel-resume-7HELH7QS.js +23 -0
- package/dist/flywheel-safety-7U7DPOAP.js +28 -0
- package/dist/flywheel-scope-decision-ZHKF6ZIN.js +11 -0
- package/dist/get-telemetry-logger-DPVWWIYY.js +73 -0
- package/dist/git-worktree-IY6V6DUO.js +18 -0
- package/dist/github-pr-manager-4LXEKJTA.js +211 -0
- package/dist/guidance-profile-config-E7XJL2VP.js +117 -0
- package/dist/guidance-profile-renderer-QK2YMAAP.js +21 -0
- package/dist/index-query-4JEUKA44.js +31 -0
- package/dist/index.js +108661 -0
- package/dist/instructions-6BURLQJC.js +64 -0
- package/dist/invoke-provider-auth-GRUYP44M.js +223 -0
- package/dist/judge-scoring.json +80 -0
- package/dist/judge-test.js +555 -0
- package/dist/lab-mode-OTWVHN63.js +39 -0
- package/dist/lab-utils-BJRFLGK4.js +23 -0
- package/dist/linux-host-class-UY4KVLFD.js +29 -0
- package/dist/llm-judge-executor-F7Z3HO77.js +133 -0
- package/dist/local-executor-CEHWE2PL.js +223 -0
- package/dist/logger-33TR6EE5.js +72 -0
- package/dist/loop-spec-XTLQAVZ7.js +191 -0
- package/dist/manager-2HJG5CLT.js +25 -0
- package/dist/matrix-prompt-builder-6UDN5X3F.js +497 -0
- package/dist/matrix-result-parser-7W35EUOV.js +15 -0
- package/dist/metadata-HQ43NDEK.js +22 -0
- package/dist/model-invocations-db-QBO4Y7WZ.js +38 -0
- package/dist/monitor-XGYBG2DW.js +72 -0
- package/dist/next-PB5ZXZKB.js +90 -0
- package/dist/next-ZBDK725T.js +69 -0
- package/dist/optimization-config-resolver-O6WKR47U.js +75 -0
- package/dist/package-3YCWIQQ5.js +10 -0
- package/dist/parallel-orchestrator-NL7YJJU2.js +297 -0
- package/dist/parallel-worker.js +279 -0
- package/dist/parse-git-status-PIAF3OTP.js +10 -0
- package/dist/path-security-ETTH6TZL.js +39 -0
- package/dist/pattern-bank-UNJR5ADV.js +15 -0
- package/dist/plan-backlog-list-fast-VAGYBV3J.js +270 -0
- package/dist/plan-backlog-show-fast-SG2CZPW3.js +406 -0
- package/dist/plan-sprint-list-fast-LURAX47V.js +162 -0
- package/dist/plan-sprint-status-fast-FJAODCHD.js +208 -0
- package/dist/plan-task-status-fast-HNYBMQI4.js +349 -0
- package/dist/plan-to-flywheel-Z4U3GWPN.js +456 -0
- package/dist/planning-backlog-3CQ5QQBQ.js +319 -0
- package/dist/poetic-root-V5DNXEAE.js +17 -0
- package/dist/preflight-OPANWMME.js +12 -0
- package/dist/process-registry-PDZJPTAS.js +27 -0
- package/dist/process-scanner-C22SYXYD.js +22 -0
- package/dist/processes-KMQUD7HY.js +29 -0
- package/dist/processes-json-fast-IBDXZ6WW.js +135 -0
- package/dist/profile-SE5EVXAP.js +149 -0
- package/dist/prompts-WZZPGWBX.js +168 -0
- package/dist/protected-branches-RNTEX7ET.js +57 -0
- package/dist/provider-aware-resource-manager-QBTZJMNP.js +18 -0
- package/dist/provider-matrix-N5X57V3Y.js +154 -0
- package/dist/provider-matrix-renderer-MZDWKKID.js +139 -0
- package/dist/provider-output-forwarding-DERID3TP.js +30 -0
- package/dist/provider-registry-J5NRBJKI.js +128 -0
- package/dist/provider-temp-cleanup-KYGLF6YU.js +14 -0
- package/dist/prune-engine-Q6E2DXBN.js +451 -0
- package/dist/quality-gate-YRHMQAEI.js +70 -0
- package/dist/quickstart-FIRLKGJI.js +62 -0
- package/dist/readiness-VZTMSG6J.js +96 -0
- package/dist/registry-YODFY77A.js +153 -0
- package/dist/repair-helpers-TTV5FEF4.js +117 -0
- package/dist/repo-root-2IGD4H7X.js +22 -0
- package/dist/resolution-engine-D6TX6FRG.js +127 -0
- package/dist/resolve-provider-cli-O2P6XZBZ.js +35 -0
- package/dist/restore-telemetry-db-BQL6DTQF.js +360 -0
- package/dist/result-streamer-WEHW476R.js +19 -0
- package/dist/routing-events-DHPUZDDF.js +27 -0
- package/dist/routing-history-store-UWCEXAP3.js +46 -0
- package/dist/routing-planner-F5WQEUOM.js +160 -0
- package/dist/run-poetic-tui-J6LWRJUP.js +13517 -0
- package/dist/run-simulation.js +376 -0
- package/dist/runner-CS2TNGHA.js +1092 -0
- package/dist/runner-HHH7ZN2Z.js +1571 -0
- package/dist/safety-OCC4KJJR.js +64 -0
- package/dist/safety-stanza-7SV3HGG3.js +60 -0
- package/dist/sandbox-32QXJEMV.js +81 -0
- package/dist/sandbox-RSO7VP6V.js +94 -0
- package/dist/schema-extensions.sql +234 -0
- package/dist/security-2KI5XNDG.js +17 -0
- package/dist/setup-bb-plugin-QAQWZCV5.js +738 -0
- package/dist/setup-claude-code-P7BTTSPO.js +317 -0
- package/dist/setup-temp-cleanup-GASCWZ2I.js +20 -0
- package/dist/shared-utils-D6U6UREP.js +37 -0
- package/dist/simple-artifact-goal-OTXNGZY6.js +25 -0
- package/dist/sprint-UIXPYOXQ.js +338 -0
- package/dist/sprint-bridge-4BHMSC3M.js +68 -0
- package/dist/sprint-execution-service-2B7WHL5L.js +311 -0
- package/dist/sprint-manager-IDQUDNQI.js +91 -0
- package/dist/sqlite-wrapper-UDWCTNLJ.js +17 -0
- package/dist/stale-cleanup-orchestrator-XWXV7KCZ.js +62 -0
- package/dist/storage-access-F6ALDN4S.js +22 -0
- package/dist/synthesis-XD6EDVHF.js +185 -0
- package/dist/task-classifier-LNLXSHOM.js +494 -0
- package/dist/task-list-fast-PTMA4LUC.js +147 -0
- package/dist/task-manager-XAT7GOWB.js +93 -0
- package/dist/task-splitter-U7DJ7W6A.js +79 -0
- package/dist/task-type-detector-OLVOMXZO.js +21 -0
- package/dist/task-type-resolver-KKNQYSB4.js +42 -0
- package/dist/telemetry-5HWBBXLX.js +131 -0
- package/dist/telemetry-health-tracker-DMSSGBZ2.js +11 -0
- package/dist/telemetry-path-resolver-7ICSG276.js +28 -0
- package/dist/telemetry-reliability-XUOZNZZ6.js +125 -0
- package/dist/token-estimator-MEQUL3EJ.js +37 -0
- package/dist/transports-KNSSV2V4.js +255 -0
- package/dist/unified-cost-tracker-4TFRBVPR.js +35 -0
- package/dist/unified-state-cleanup-LJPF2BOW.js +276 -0
- package/dist/universal-cost-calculator-BAJV35GA.js +42 -0
- package/dist/user-agents-GHUGCSKN.js +22 -0
- package/dist/variant-delivery-state-KLRNG6XA.js +33 -0
- package/dist/variant-state-cleanup-MZGGLQAJ.js +280 -0
- package/dist/variant-success-PJXWE3ZV.js +12 -0
- package/dist/variant-worker-KUWKBLAH.js +4196 -0
- package/dist/variant-worker.js +24 -0
- package/dist/variants-JE2FSAQS.js +233 -0
- package/dist/verification-toolchains-V24UHW6Q.js +28 -0
- package/dist/verify-commands-H3DTCSGI.js +37 -0
- package/dist/warning-EGQGUUIO.js +12 -0
- package/dist/work-edge-RWEDWJZF.js +49 -0
- package/dist/work-item-writer-WHAVZXAY.js +19 -0
- package/dist/worker-7SA2VS2Y.js +1435 -0
- package/dist/worker.js +19 -0
- package/dist/worktree-metrics-3DFUYVYE.js +26 -0
- package/dist/writeback-VZKHYHSN.js +44 -0
- package/docs/CLI_REFERENCE.md +7042 -0
- package/docs/PROVIDER_SETUP.md +1570 -0
- package/docs/README.md +96 -0
- package/docs/TROUBLESHOOTING.md +2232 -0
- package/docs/getting-started/QUICK_START.md +294 -0
- package/docs/getting-started/README.md +188 -0
- package/docs/getting-started/SETUP.md +52 -0
- package/docs/reference/AGENT_CONTEXT.md +38 -0
- package/docs/reference/PROVIDER_RELEASE_TIERS.md +34 -0
- package/docs/reference/README.md +381 -0
- package/docs/reference/SECURITY.md +262 -0
- package/integrations/bb-plugin-poetic/README.md +362 -0
- package/integrations/bb-plugin-poetic/app.css +341 -0
- package/integrations/bb-plugin-poetic/app.tsx +5059 -0
- package/integrations/bb-plugin-poetic/package.json +45 -0
- package/integrations/bb-plugin-poetic/server.ts +444 -0
- package/integrations/bb-plugin-poetic/src/adapter.ts +3312 -0
- package/integrations/bb-plugin-poetic/src/backlog-authoring-model.ts +150 -0
- package/integrations/bb-plugin-poetic/src/backlog-authoring-schema.ts +355 -0
- package/integrations/bb-plugin-poetic/src/backlog-authoring-service.ts +419 -0
- package/integrations/bb-plugin-poetic/src/backlog-authoring-view.ts +1026 -0
- package/integrations/bb-plugin-poetic/src/competition-defaults-schema.ts +122 -0
- package/integrations/bb-plugin-poetic/src/competition-defaults-service.ts +105 -0
- package/integrations/bb-plugin-poetic/src/competition-defaults-view.ts +138 -0
- package/integrations/bb-plugin-poetic/src/contract.ts +615 -0
- package/integrations/bb-plugin-poetic/src/finalize-model.ts +205 -0
- package/integrations/bb-plugin-poetic/src/finalize-schema.ts +158 -0
- package/integrations/bb-plugin-poetic/src/finalize-service.ts +382 -0
- package/integrations/bb-plugin-poetic/src/finalize-view.ts +159 -0
- package/integrations/bb-plugin-poetic/src/judge-operations-readback.ts +303 -0
- package/integrations/bb-plugin-poetic/src/judge-operations-schema.ts +33 -0
- package/integrations/bb-plugin-poetic/src/judge-operations-view.ts +459 -0
- package/integrations/bb-plugin-poetic/src/model.ts +1302 -0
- package/integrations/bb-plugin-poetic/src/monitor-service.ts +233 -0
- package/integrations/bb-plugin-poetic/src/panel-read-ux.ts +97 -0
- package/integrations/bb-plugin-poetic/src/patch-preview-schema.ts +166 -0
- package/integrations/bb-plugin-poetic/src/patch-preview-service.ts +480 -0
- package/integrations/bb-plugin-poetic/src/patch-preview-view.ts +109 -0
- package/integrations/bb-plugin-poetic/src/planning-readback.ts +204 -0
- package/integrations/bb-plugin-poetic/src/planning-service.ts +594 -0
- package/integrations/bb-plugin-poetic/src/planning-workspace-schema.ts +95 -0
- package/integrations/bb-plugin-poetic/src/planning-workspace.ts +323 -0
- package/integrations/bb-plugin-poetic/src/poll-handoff.ts +136 -0
- package/integrations/bb-plugin-poetic/src/project-target-server.ts +21 -0
- package/integrations/bb-plugin-poetic/src/project-target.ts +45 -0
- package/integrations/bb-plugin-poetic/src/reference-index.ts +192 -0
- package/integrations/bb-plugin-poetic/src/repository-schema.ts +30 -0
- package/integrations/bb-plugin-poetic/src/result-explorer-view.ts +597 -0
- package/integrations/bb-plugin-poetic/src/result-readback-model.ts +163 -0
- package/integrations/bb-plugin-poetic/src/result-readback-schema.ts +319 -0
- package/integrations/bb-plugin-poetic/src/result-readback-service.ts +333 -0
- package/integrations/bb-plugin-poetic/src/run-page-view.ts +177 -0
- package/integrations/bb-plugin-poetic/src/setup-config-schema.ts +291 -0
- package/integrations/bb-plugin-poetic/src/setup-config-service.ts +590 -0
- package/integrations/bb-plugin-poetic/src/setup-config-view.ts +413 -0
- package/integrations/bb-plugin-poetic/src/sprint-close-model.ts +150 -0
- package/integrations/bb-plugin-poetic/src/sprint-close-schema.ts +136 -0
- package/integrations/bb-plugin-poetic/src/sprint-close-service.ts +341 -0
- package/integrations/bb-plugin-poetic/src/sprint-close-view.ts +116 -0
- package/integrations/bb-plugin-poetic/src/sprint-composition-model.ts +714 -0
- package/integrations/bb-plugin-poetic/src/sprint-composition-readback.ts +231 -0
- package/integrations/bb-plugin-poetic/src/sprint-composition-schema.ts +601 -0
- package/integrations/bb-plugin-poetic/src/sprint-composition-service.ts +775 -0
- package/integrations/bb-plugin-poetic/src/sprint-composition-view.ts +1221 -0
- package/integrations/bb-plugin-poetic/src/task-authoring-model.ts +567 -0
- package/integrations/bb-plugin-poetic/src/task-authoring-schema.ts +635 -0
- package/integrations/bb-plugin-poetic/src/task-authoring-service.ts +842 -0
- package/integrations/bb-plugin-poetic/src/task-authoring-view.ts +1227 -0
- package/integrations/bb-plugin-poetic/src/task-detail-readback.ts +56 -0
- package/integrations/bb-plugin-poetic/src/task-detail-schema.ts +144 -0
- package/integrations/bb-plugin-poetic/src/task-navigation.ts +446 -0
- package/integrations/bb-plugin-poetic/src/workbench-route.ts +74 -0
- package/integrations/bb-plugin-poetic/tests/host-contract.test.ts +773 -0
- package/integrations/bb-plugin-poetic/tsconfig.json +18 -0
- package/integrations/bb-plugin-poetic/types/PROVENANCE.json +52 -0
- package/integrations/bb-plugin-poetic/types/bb-plugin-sdk-app.d.ts +1444 -0
- package/integrations/bb-plugin-poetic/types/bb-plugin-sdk.d.ts +13030 -0
- package/integrations/bb-plugin-poetic/vitest.host.config.ts +71 -0
- package/npm-shrinkwrap.json +4950 -0
- package/package.json +331 -0
- package/schemas/README.md +80 -0
- package/schemas/config-v1.schema.json +136 -0
- package/schemas/execution-config.schema.json +47 -0
- package/schemas/judge-scoring.schema.json +370 -0
- package/schemas/poetic.config.schema.json +2214 -0
- package/schemas/provider-config.schema.json +203 -0
- package/schemas/telemetry-config.schema.json +79 -0
- package/schemas/user-preferences.schema.json +141 -0
- package/scripts/assert-node-runtime.mjs +140 -0
- package/scripts/preflight-native.mjs +49 -0
- package/scripts/preinstall-node-check.mjs +78 -0
- package/scripts/setup-git-hooks.mjs +24 -0
- package/scripts/sync.sh +2722 -0
- package/scripts/write-node-launcher.sh +108 -0
- package/src/resources/gemini/slash-packs/default/plan.toml +15 -0
- package/src/resources/gemini/slash-packs/default/summary.toml +16 -0
- package/src/resources/gemini/slash-packs/default/tests.toml +16 -0
- package/templates/.poetic/README.md +37 -0
- package/templates/.poetic/agents/README.md +296 -0
- package/templates/.poetic/agents/api-documenter.md +147 -0
- package/templates/.poetic/agents/backend-architect.md +31 -0
- package/templates/.poetic/agents/code-reviewer.md +157 -0
- package/templates/.poetic/agents/data-scientist.md +179 -0
- package/templates/.poetic/agents/database-optimizer.md +145 -0
- package/templates/.poetic/agents/debugger.md +31 -0
- package/templates/.poetic/agents/deployment-engineer.md +164 -0
- package/templates/.poetic/agents/devops-troubleshooter.md +139 -0
- package/templates/.poetic/agents/frontend-developer.md +150 -0
- package/templates/.poetic/agents/javascript-pro.md +36 -0
- package/templates/.poetic/agents/performance-engineer.md +151 -0
- package/templates/.poetic/agents/python-pro.md +137 -0
- package/templates/.poetic/agents/test-automator.md +147 -0
- package/templates/.poetic/agents/typescript-pro.md +34 -0
- package/templates/.poetic/config/poetic.config.jsonc +69 -0
- package/templates/.poetic/config/project-context.template.json +6 -0
- package/templates/.poetic/config/task-type-aliases.presets/kanban.yaml +14 -0
- package/templates/.poetic/config/task-type-aliases.presets/scrum.yaml +17 -0
- package/templates/.poetic/config/task-type-aliases.presets/xp.yaml +12 -0
- package/templates/.poetic/config/task-type-aliases.yaml +28 -0
- package/templates/.poetic/gitignore.template +55 -0
- package/templates/.poetic/task-types/analysis.yaml +40 -0
- package/templates/.poetic/task-types/architecture.yaml +38 -0
- package/templates/.poetic/task-types/doc.yaml +35 -0
- package/templates/.poetic/task-types/feature.yaml +23 -0
- package/templates/.poetic/task-types/general.yaml +6 -0
- package/templates/.poetic/task-types/security.yaml +39 -0
- package/templates/AGENTS.template.md +99 -0
- package/templates/CLAUDE.template.md +1 -0
- package/templates/GEMINI.template.md +1 -0
- package/templates/builtin-workflows/code-review.yaml +73 -0
- package/templates/builtin-workflows/compete-streak.yaml +78 -0
- package/templates/builtin-workflows/hello-verify.yaml +10 -0
- package/templates/builtin-workflows/judge-regression.yaml +114 -0
- package/templates/builtin-workflows/skills/code-review/SKILL.md +60 -0
- package/templates/builtin-workflows/skills/hello-verify/SKILL.md +6 -0
- package/templates/guard-kit/GUARD_SETUP.md.template +255 -0
- package/templates/guard-kit/check.mjs.template +777 -0
- package/templates/guard-kit/config.json.template +6 -0
- package/templates/guard-kit/poetic-guard.yml.template +189 -0
- package/templates/profiles/README.md +56 -0
- package/templates/profiles/frontier-claude.json +27 -0
- package/templates/profiles/frontier-codex.json +26 -0
|
@@ -0,0 +1,4007 @@
|
|
|
1
|
+
import {
|
|
2
|
+
renderRepairConstraints,
|
|
3
|
+
renderUntrustedCommandOutput
|
|
4
|
+
} from "./chunk-LRGMHROI.js";
|
|
5
|
+
import {
|
|
6
|
+
extractExplicitVerificationCommands,
|
|
7
|
+
hasExplicitVerificationIntent
|
|
8
|
+
} from "./chunk-J3UUR7DE.js";
|
|
9
|
+
import {
|
|
10
|
+
evaluateStageCompletionProof,
|
|
11
|
+
hostSurfaceEvaluatorFailureProof,
|
|
12
|
+
mergeIterationRetryFeedback,
|
|
13
|
+
resolveProviderExecutionIntentFromStage,
|
|
14
|
+
resolveStageExecutionIntent,
|
|
15
|
+
stageHasHostDeterministicCompletionSurface,
|
|
16
|
+
toAutoloopCompletionEvidence
|
|
17
|
+
} from "./chunk-5ZCXC23G.js";
|
|
18
|
+
import {
|
|
19
|
+
collectDeclaredStageTargets
|
|
20
|
+
} from "./chunk-IFCBYECK.js";
|
|
21
|
+
import {
|
|
22
|
+
decideStageChangeScope
|
|
23
|
+
} from "./chunk-HAC6EHZZ.js";
|
|
24
|
+
import {
|
|
25
|
+
extractAcceptanceCriteria,
|
|
26
|
+
formatAcceptanceCriteriaForPrompt,
|
|
27
|
+
prepareInitialAutoloopPolicy,
|
|
28
|
+
validateRequirements
|
|
29
|
+
} from "./chunk-35P3W3JX.js";
|
|
30
|
+
import {
|
|
31
|
+
completionStatusAllowsSuccess,
|
|
32
|
+
isSuccessLikeControllerReason,
|
|
33
|
+
unsafeCompletionAllowsSuccess
|
|
34
|
+
} from "./chunk-4IAPOG5V.js";
|
|
35
|
+
import {
|
|
36
|
+
changesHaveVerifiableCodeSurface,
|
|
37
|
+
getProviderDetectionMode,
|
|
38
|
+
normalizeProviderRunDiagnostics,
|
|
39
|
+
providerDiagnosticsReportFatalRuntimeIssue,
|
|
40
|
+
resolveTokenAccounting
|
|
41
|
+
} from "./chunk-M3HQYKQX.js";
|
|
42
|
+
import {
|
|
43
|
+
readCostMetricsUnavailableReason
|
|
44
|
+
} from "./chunk-XUXVDPSZ.js";
|
|
45
|
+
import {
|
|
46
|
+
generateCompetitionId
|
|
47
|
+
} from "./chunk-WIFZCHEG.js";
|
|
48
|
+
import {
|
|
49
|
+
isSimpleArtifactGoalSatisfiedForChanges,
|
|
50
|
+
parseSimpleArtifactGoal
|
|
51
|
+
} from "./chunk-OGMSYL6J.js";
|
|
52
|
+
import {
|
|
53
|
+
getPlanningRoot,
|
|
54
|
+
withIndexRegeneration,
|
|
55
|
+
withPlanningLock
|
|
56
|
+
} from "./chunk-OPYFYTWQ.js";
|
|
57
|
+
import {
|
|
58
|
+
getBucketArchivePathByKind,
|
|
59
|
+
getBucketPathByKind
|
|
60
|
+
} from "./chunk-HOYBYAOA.js";
|
|
61
|
+
import {
|
|
62
|
+
buildProviderOutputExcerpt
|
|
63
|
+
} from "./chunk-MYLH2S4G.js";
|
|
64
|
+
import {
|
|
65
|
+
parseGitStatus,
|
|
66
|
+
parseGitStatusPaths
|
|
67
|
+
} from "./chunk-MN6PL4AN.js";
|
|
68
|
+
import {
|
|
69
|
+
shouldExcludeFile
|
|
70
|
+
} from "./chunk-NS33V3IM.js";
|
|
71
|
+
import {
|
|
72
|
+
listMarkdownFiles,
|
|
73
|
+
listMarkdownFilesRecursive,
|
|
74
|
+
readMarkdownFile,
|
|
75
|
+
writeMarkdownFile
|
|
76
|
+
} from "./chunk-OIS7J26S.js";
|
|
77
|
+
import {
|
|
78
|
+
resolveDefaultProvider
|
|
79
|
+
} from "./chunk-XPU2XADL.js";
|
|
80
|
+
import {
|
|
81
|
+
emitBoundaryTriggered
|
|
82
|
+
} from "./chunk-PFTQBXS6.js";
|
|
83
|
+
import {
|
|
84
|
+
normalizeEngineId
|
|
85
|
+
} from "./chunk-SURZ2WFE.js";
|
|
86
|
+
import {
|
|
87
|
+
filterWorktreeRunSentinelPaths,
|
|
88
|
+
isWorktreeRunSentinelPath
|
|
89
|
+
} from "./chunk-I5WRT7ZR.js";
|
|
90
|
+
import {
|
|
91
|
+
atomicWriteJson
|
|
92
|
+
} from "./chunk-A3YGID55.js";
|
|
93
|
+
import {
|
|
94
|
+
execGit
|
|
95
|
+
} from "./chunk-KNDYMVWN.js";
|
|
96
|
+
import {
|
|
97
|
+
runVerifyCommands
|
|
98
|
+
} from "./chunk-LO23NABI.js";
|
|
99
|
+
import {
|
|
100
|
+
resolveExecutionBoundary
|
|
101
|
+
} from "./chunk-7R2WLDOS.js";
|
|
102
|
+
import {
|
|
103
|
+
detectProjectCapabilities
|
|
104
|
+
} from "./chunk-FHOWVDF5.js";
|
|
105
|
+
import {
|
|
106
|
+
isProjectConfigPath,
|
|
107
|
+
isProjectTestPath,
|
|
108
|
+
languageForProjectPath
|
|
109
|
+
} from "./chunk-UDMS5AD3.js";
|
|
110
|
+
import {
|
|
111
|
+
truncateTail
|
|
112
|
+
} from "./chunk-TSXGRLPR.js";
|
|
113
|
+
import {
|
|
114
|
+
describeVerificationCommand,
|
|
115
|
+
detectVerificationToolUnavailable
|
|
116
|
+
} from "./chunk-2QLYTVYX.js";
|
|
117
|
+
import {
|
|
118
|
+
safeRmDir
|
|
119
|
+
} from "./chunk-G4THLFV3.js";
|
|
120
|
+
import {
|
|
121
|
+
resolvePoeticRoot
|
|
122
|
+
} from "./chunk-LEIIWPWQ.js";
|
|
123
|
+
|
|
124
|
+
// src/automation/flywheel-autoloop-executor.ts
|
|
125
|
+
import * as fs4 from "fs/promises";
|
|
126
|
+
import * as path5 from "path";
|
|
127
|
+
import { performance as performance2 } from "perf_hooks";
|
|
128
|
+
import chalk from "chalk";
|
|
129
|
+
import { execa as execa2 } from "execa";
|
|
130
|
+
|
|
131
|
+
// src/core/providers/claude/simple-artifact-tool-policy.ts
|
|
132
|
+
var CLAUDE_SIMPLE_ARTIFACT_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep"];
|
|
133
|
+
var CLAUDE_SIMPLE_ARTIFACT_DISALLOWED_TOOLS = ["Bash"];
|
|
134
|
+
function claudeSimpleArtifactToolPolicy() {
|
|
135
|
+
return {
|
|
136
|
+
allowedTools: [...CLAUDE_SIMPLE_ARTIFACT_TOOLS],
|
|
137
|
+
disallowedTools: [...CLAUDE_SIMPLE_ARTIFACT_DISALLOWED_TOOLS],
|
|
138
|
+
providerOutputSuppression: "claude-disallowed-bash"
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/core/providers/simple-artifact-tool-policy.ts
|
|
143
|
+
var SIMPLE_ARTIFACT_TOOL_POLICIES = {
|
|
144
|
+
claude: claudeSimpleArtifactToolPolicy
|
|
145
|
+
};
|
|
146
|
+
function simpleArtifactToolPolicy(provider) {
|
|
147
|
+
const factory = SIMPLE_ARTIFACT_TOOL_POLICIES[normalizeEngineId(provider)];
|
|
148
|
+
return factory ? factory() : null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/orchestrator/autoloop-termination-map.ts
|
|
152
|
+
function mapControllerReasonToFlywheel(reason) {
|
|
153
|
+
switch (reason) {
|
|
154
|
+
case "max_iterations_reached":
|
|
155
|
+
case "timebox_exceeded":
|
|
156
|
+
case "progress_stagnation":
|
|
157
|
+
case "no_op_loop":
|
|
158
|
+
case "duplicate_change_loop":
|
|
159
|
+
case "resource_exhaustion":
|
|
160
|
+
return reason;
|
|
161
|
+
case "identical_error_pattern":
|
|
162
|
+
return "progress_stagnation";
|
|
163
|
+
case "recursion_limit":
|
|
164
|
+
return "resource_exhaustion";
|
|
165
|
+
case "system_error":
|
|
166
|
+
case "ai_judge_recommendation":
|
|
167
|
+
case "max_retries_exceeded":
|
|
168
|
+
return "error";
|
|
169
|
+
case "completion_signal":
|
|
170
|
+
case "semantic_complete":
|
|
171
|
+
case "deterministic_complete":
|
|
172
|
+
return "success";
|
|
173
|
+
case "heuristic_complete":
|
|
174
|
+
return "heuristic_complete";
|
|
175
|
+
case "baseline_blocked":
|
|
176
|
+
return "baseline_blocked";
|
|
177
|
+
case "verification_unavailable":
|
|
178
|
+
return "verification_unavailable";
|
|
179
|
+
case "compilation_errors":
|
|
180
|
+
return "quality_gate_failure";
|
|
181
|
+
case "semantic_criteria_unsatisfied":
|
|
182
|
+
return "evidence_unsatisfied";
|
|
183
|
+
default: {
|
|
184
|
+
const exhaustive = reason;
|
|
185
|
+
return exhaustive;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/automation/autoloop-termination-events.ts
|
|
191
|
+
function createAutoloopEventRecorder() {
|
|
192
|
+
const events = [];
|
|
193
|
+
return {
|
|
194
|
+
events,
|
|
195
|
+
emit(event) {
|
|
196
|
+
const recorded = {
|
|
197
|
+
...event,
|
|
198
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
199
|
+
};
|
|
200
|
+
events.push(recorded);
|
|
201
|
+
return recorded;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/automation/flywheel-autoloop-durability.ts
|
|
207
|
+
import { createHash, randomUUID } from "crypto";
|
|
208
|
+
import * as fs from "fs/promises";
|
|
209
|
+
import * as path from "path";
|
|
210
|
+
import { execa } from "execa";
|
|
211
|
+
async function computeWorktreeFingerprint(worktreePath) {
|
|
212
|
+
try {
|
|
213
|
+
const head = await execa("git", ["rev-parse", "HEAD"], {
|
|
214
|
+
cwd: worktreePath,
|
|
215
|
+
stdio: "pipe",
|
|
216
|
+
timeout: 3e4,
|
|
217
|
+
reject: false
|
|
218
|
+
});
|
|
219
|
+
const headSha = head.stdout.trim();
|
|
220
|
+
if ((head.exitCode ?? 1) !== 0 || headSha.length === 0) return void 0;
|
|
221
|
+
const runGit = async (args) => {
|
|
222
|
+
const result = await execa("git", args, {
|
|
223
|
+
cwd: worktreePath,
|
|
224
|
+
stdio: "pipe",
|
|
225
|
+
timeout: 3e4,
|
|
226
|
+
reject: false
|
|
227
|
+
});
|
|
228
|
+
if ((result.exitCode ?? 1) !== 0) {
|
|
229
|
+
throw new Error(`git ${args[0] ?? ""} failed while fingerprinting`);
|
|
230
|
+
}
|
|
231
|
+
return result.stdout;
|
|
232
|
+
};
|
|
233
|
+
const [porcelainZ, unstagedDiff, stagedDiff] = await Promise.all([
|
|
234
|
+
// -z gives NUL-delimited entries with unquoted paths, which is robust for
|
|
235
|
+
// extracting the changed-path set used by the resume superset check.
|
|
236
|
+
runGit(["status", "--porcelain", "--untracked-files=all", "-z"]),
|
|
237
|
+
runGit(["diff", "--binary", "--no-ext-diff"]),
|
|
238
|
+
runGit(["diff", "--cached", "--binary", "--no-ext-diff"])
|
|
239
|
+
]);
|
|
240
|
+
const changedPaths = parsePorcelainPaths(porcelainZ);
|
|
241
|
+
const digest = createHash("sha256").update("status-z\0").update(porcelainZ).update("\0unstaged\0").update(unstagedDiff.replace(/\r/g, "")).update("\0staged\0").update(stagedDiff.replace(/\r/g, ""));
|
|
242
|
+
const root = path.resolve(worktreePath);
|
|
243
|
+
for (const relativePath of changedPaths) {
|
|
244
|
+
const absolutePath = path.resolve(root, relativePath);
|
|
245
|
+
const insideRoot = absolutePath === root || !path.relative(root, absolutePath).startsWith("..") && !path.isAbsolute(path.relative(root, absolutePath));
|
|
246
|
+
if (!insideRoot) throw new Error(`Dirty path escapes worktree: ${relativePath}`);
|
|
247
|
+
digest.update("\0path\0").update(relativePath);
|
|
248
|
+
try {
|
|
249
|
+
const stat3 = await fs.lstat(absolutePath);
|
|
250
|
+
if (stat3.isSymbolicLink()) {
|
|
251
|
+
digest.update("\0symlink\0").update(await fs.readlink(absolutePath));
|
|
252
|
+
} else if (stat3.isFile()) {
|
|
253
|
+
digest.update("\0file\0").update(String(stat3.mode & 73)).update("\0");
|
|
254
|
+
digest.update(await fs.readFile(absolutePath));
|
|
255
|
+
} else {
|
|
256
|
+
throw new Error(`Unsupported dirty path type: ${relativePath}`);
|
|
257
|
+
}
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (error.code === "ENOENT") {
|
|
260
|
+
digest.update("\0missing\0");
|
|
261
|
+
} else {
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return { headSha, dirtyDigest: digest.digest("hex"), changedPaths };
|
|
267
|
+
} catch {
|
|
268
|
+
return void 0;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function parsePorcelainPaths(porcelainZ) {
|
|
272
|
+
const paths = /* @__PURE__ */ new Set();
|
|
273
|
+
const entries = porcelainZ.split("\0");
|
|
274
|
+
for (let i = 0; i < entries.length; i++) {
|
|
275
|
+
const entry = entries[i];
|
|
276
|
+
if (!entry || entry.length === 0) continue;
|
|
277
|
+
const status = entry.slice(0, 2);
|
|
278
|
+
const p = entry.length > 3 ? entry.slice(3) : entry;
|
|
279
|
+
if (p.length > 0) paths.add(p);
|
|
280
|
+
if ((status[0] === "R" || status[0] === "C") && i + 1 < entries.length) {
|
|
281
|
+
const source = entries[i + 1];
|
|
282
|
+
if (source && source.length > 0) paths.add(source);
|
|
283
|
+
i++;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return [...paths].sort();
|
|
287
|
+
}
|
|
288
|
+
function worktreeFingerprintExactlyMatches(recorded, current) {
|
|
289
|
+
return Boolean(
|
|
290
|
+
recorded && current && recorded.headSha === current.headSha && recorded.dirtyDigest === current.dirtyDigest
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
function worktreeFingerprintResumable(recorded, current) {
|
|
294
|
+
if (!recorded || !current) return true;
|
|
295
|
+
if (recorded.headSha !== current.headSha) return false;
|
|
296
|
+
if (recorded.dirtyDigest === current.dirtyDigest) return true;
|
|
297
|
+
if (recorded.changedPaths === void 0 || current.changedPaths === void 0) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
const currentPaths = new Set(current.changedPaths);
|
|
301
|
+
return recorded.changedPaths.every((p) => currentPaths.has(p));
|
|
302
|
+
}
|
|
303
|
+
function safeFilename(input) {
|
|
304
|
+
return input.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
305
|
+
}
|
|
306
|
+
function isValidOptionalFingerprint(value) {
|
|
307
|
+
if (value === void 0) return true;
|
|
308
|
+
if (!value || typeof value !== "object") return false;
|
|
309
|
+
const fp = value;
|
|
310
|
+
if (typeof fp.headSha !== "string" || typeof fp.dirtyDigest !== "string") return false;
|
|
311
|
+
if (fp.changedPaths !== void 0) {
|
|
312
|
+
if (!Array.isArray(fp.changedPaths)) return false;
|
|
313
|
+
if (!fp.changedPaths.every((p) => typeof p === "string")) return false;
|
|
314
|
+
}
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
function isDurableAutoloopRunRecord(input) {
|
|
318
|
+
if (!input || typeof input !== "object") return false;
|
|
319
|
+
const obj = input;
|
|
320
|
+
return obj.schemaVersion === 1 && typeof obj.recordedAt === "string" && typeof obj.workflowId === "string" && typeof obj.stageId === "string" && obj.stageType === "autoloop" && typeof obj.validationPassed === "boolean" && typeof obj.loopSuccess === "boolean" && typeof obj.terminationReason === "string" && typeof obj.totalIterations === "number" && typeof obj.finalProgressScore === "number" && typeof obj.cost === "number" && isValidOptionalFingerprint(obj.worktreeFingerprint);
|
|
321
|
+
}
|
|
322
|
+
function isDurableAutoloopIterationSpill(input) {
|
|
323
|
+
if (!input || typeof input !== "object") return false;
|
|
324
|
+
const obj = input;
|
|
325
|
+
return obj.schemaVersion === 1 && typeof obj.updatedAt === "string" && typeof obj.workflowId === "string" && typeof obj.stageId === "string" && typeof obj.writer === "string" && (obj.runStartedAtMs === void 0 || typeof obj.runStartedAtMs === "number" && Number.isFinite(obj.runStartedAtMs)) && isValidOptionalFingerprint(obj.worktreeFingerprint) && Array.isArray(obj.iterations);
|
|
326
|
+
}
|
|
327
|
+
function pickFailureFromIterations(result) {
|
|
328
|
+
const last = result.iterationHistory.at(-1);
|
|
329
|
+
if (!last) return void 0;
|
|
330
|
+
const failing = last.qualityGateCommandResults?.find((r) => r.exitCode !== 0);
|
|
331
|
+
const allowlistViolations = (last.allowlistViolations?.length ? last.allowlistViolations : void 0) ?? result.iterationHistory.find((r) => r.allowlistViolations && r.allowlistViolations.length > 0)?.allowlistViolations;
|
|
332
|
+
const allowlistRevertedFiles = (last.allowlistRevertedFiles?.length ? last.allowlistRevertedFiles : void 0) ?? result.iterationHistory.find(
|
|
333
|
+
(r) => r.allowlistRevertedFiles && r.allowlistRevertedFiles.length > 0
|
|
334
|
+
)?.allowlistRevertedFiles;
|
|
335
|
+
const changedFiles = last.changedFiles?.length ? last.changedFiles : void 0;
|
|
336
|
+
if (result.validationPassed && result.loopSuccess && !last.allowlistDrift && last.qualityGatePassed !== false) {
|
|
337
|
+
return void 0;
|
|
338
|
+
}
|
|
339
|
+
return {
|
|
340
|
+
iteration: last.iteration,
|
|
341
|
+
qualityGateError: last.qualityGateError,
|
|
342
|
+
failingCommand: failing?.command,
|
|
343
|
+
failingExitCode: failing?.exitCode,
|
|
344
|
+
outputTail: failing?.outputTail,
|
|
345
|
+
allowlistViolations,
|
|
346
|
+
allowlistRevertedFiles,
|
|
347
|
+
changedFiles
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function buildDurableAutoloopRunRecord(input) {
|
|
351
|
+
const recordedAt = input.recordedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
352
|
+
const failure = input.result.validationPassed ? void 0 : pickFailureFromIterations(input.result);
|
|
353
|
+
return {
|
|
354
|
+
schemaVersion: 1,
|
|
355
|
+
recordedAt,
|
|
356
|
+
workflowId: input.workflowId,
|
|
357
|
+
stageId: input.stage.id,
|
|
358
|
+
stageType: "autoloop",
|
|
359
|
+
validationPassed: input.result.validationPassed,
|
|
360
|
+
loopSuccess: input.result.loopSuccess,
|
|
361
|
+
terminationReason: input.result.terminationReason,
|
|
362
|
+
totalIterations: input.result.totalIterations,
|
|
363
|
+
finalProgressScore: input.result.finalProgressScore,
|
|
364
|
+
cost: input.result.cost,
|
|
365
|
+
outcomeMetadata: input.result.outcomeMetadata,
|
|
366
|
+
...input.worktreeFingerprint ? { worktreeFingerprint: input.worktreeFingerprint } : {},
|
|
367
|
+
allowlist: input.stage.allowlist,
|
|
368
|
+
allowlistMode: input.stage.allowlistMode,
|
|
369
|
+
...failure ? { failure } : {}
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
async function writeJsonAtomic(filePath, json) {
|
|
373
|
+
const dir = path.dirname(filePath);
|
|
374
|
+
const base = path.basename(filePath);
|
|
375
|
+
const tmpPath = path.join(dir, `${base}.tmp-${process.pid}-${Date.now()}-${randomUUID()}`);
|
|
376
|
+
await fs.mkdir(dir, { recursive: true });
|
|
377
|
+
const file = await fs.open(tmpPath, "w");
|
|
378
|
+
try {
|
|
379
|
+
await file.writeFile(JSON.stringify(json, null, 2), "utf-8");
|
|
380
|
+
await file.sync();
|
|
381
|
+
} finally {
|
|
382
|
+
await file.close().catch(() => {
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
await fs.rename(tmpPath, filePath);
|
|
387
|
+
} catch (renameError) {
|
|
388
|
+
await fs.unlink(tmpPath).catch(() => {
|
|
389
|
+
});
|
|
390
|
+
throw renameError;
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
const dirHandle = await fs.open(dir, "r");
|
|
394
|
+
try {
|
|
395
|
+
await dirHandle.sync();
|
|
396
|
+
} finally {
|
|
397
|
+
await dirHandle.close().catch(() => {
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
} catch {
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function taskIdMatchesBasename(basename2, taskId) {
|
|
404
|
+
if (!basename2.startsWith(taskId)) return false;
|
|
405
|
+
const rest = basename2.slice(taskId.length);
|
|
406
|
+
return rest.length === 0 || /^[^a-zA-Z0-9]/.test(rest);
|
|
407
|
+
}
|
|
408
|
+
async function findTaskFilePath(taskId) {
|
|
409
|
+
const planningRoot = getPlanningRoot();
|
|
410
|
+
const tasksDir = getBucketPathByKind("task", planningRoot);
|
|
411
|
+
const activeFiles = await listMarkdownFiles(tasksDir).catch(() => []);
|
|
412
|
+
const active = activeFiles.find((f) => taskIdMatchesBasename(path.basename(f), taskId));
|
|
413
|
+
if (active) return active;
|
|
414
|
+
const archiveDir = getBucketArchivePathByKind("task", planningRoot);
|
|
415
|
+
const archivedFiles = await listMarkdownFilesRecursive(archiveDir).catch(() => []);
|
|
416
|
+
const archived = archivedFiles.find((f) => taskIdMatchesBasename(path.basename(f), taskId));
|
|
417
|
+
return archived ?? null;
|
|
418
|
+
}
|
|
419
|
+
function normalizeTaskStatus(raw) {
|
|
420
|
+
const statusRaw = typeof raw === "string" ? raw : "todo";
|
|
421
|
+
const statusMap = {
|
|
422
|
+
todo: "todo",
|
|
423
|
+
in_progress: "in_progress",
|
|
424
|
+
review: "review",
|
|
425
|
+
done: "done",
|
|
426
|
+
blocked: "blocked",
|
|
427
|
+
pending: "todo",
|
|
428
|
+
"in-progress": "in_progress",
|
|
429
|
+
completed: "done",
|
|
430
|
+
archived: "done"
|
|
431
|
+
};
|
|
432
|
+
return statusMap[statusRaw] ?? "todo";
|
|
433
|
+
}
|
|
434
|
+
async function persistToTaskFrontmatter(record) {
|
|
435
|
+
const planningRoot = getPlanningRoot();
|
|
436
|
+
const taskFile = await findTaskFilePath(record.stageId);
|
|
437
|
+
if (!taskFile) return;
|
|
438
|
+
await withIndexRegeneration(planningRoot, async () => {
|
|
439
|
+
const { frontMatter, body } = await readMarkdownFile(taskFile);
|
|
440
|
+
const now = record.recordedAt;
|
|
441
|
+
const currentStatus = normalizeTaskStatus(frontMatter.status);
|
|
442
|
+
const shouldBlock = record.validationPassed === false && currentStatus !== "blocked" && currentStatus !== "done";
|
|
443
|
+
const nextFrontMatter = {
|
|
444
|
+
...frontMatter,
|
|
445
|
+
updated_at: now,
|
|
446
|
+
version: (typeof frontMatter.version === "number" ? frontMatter.version : 0) + 1,
|
|
447
|
+
lastRun: {
|
|
448
|
+
at: now,
|
|
449
|
+
workflowId: record.workflowId,
|
|
450
|
+
stageType: record.stageType,
|
|
451
|
+
terminationReason: record.terminationReason,
|
|
452
|
+
success: record.validationPassed,
|
|
453
|
+
iterations: record.totalIterations,
|
|
454
|
+
finalProgressScore: record.finalProgressScore,
|
|
455
|
+
cost: record.cost,
|
|
456
|
+
allowlistMode: record.allowlistMode
|
|
457
|
+
},
|
|
458
|
+
...record.validationPassed === false ? {
|
|
459
|
+
lastFailure: {
|
|
460
|
+
at: now,
|
|
461
|
+
workflowId: record.workflowId,
|
|
462
|
+
terminationReason: record.terminationReason,
|
|
463
|
+
...record.failure
|
|
464
|
+
}
|
|
465
|
+
} : {},
|
|
466
|
+
...shouldBlock ? { status: "blocked" } : {}
|
|
467
|
+
};
|
|
468
|
+
await writeMarkdownFile(taskFile, nextFrontMatter, body);
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
async function persistToTelemetryState(record) {
|
|
472
|
+
const root = getPlanningRoot();
|
|
473
|
+
const stageSafe = safeFilename(record.stageId);
|
|
474
|
+
const workflowSafe = safeFilename(record.workflowId);
|
|
475
|
+
const outPath = path.join(
|
|
476
|
+
root,
|
|
477
|
+
".poetic",
|
|
478
|
+
"telemetry",
|
|
479
|
+
"state",
|
|
480
|
+
"flywheel",
|
|
481
|
+
workflowSafe,
|
|
482
|
+
"autoloop",
|
|
483
|
+
`${stageSafe}.json`
|
|
484
|
+
);
|
|
485
|
+
await writeJsonAtomic(outPath, record);
|
|
486
|
+
}
|
|
487
|
+
function getIterationSpillPath(input) {
|
|
488
|
+
const root = getPlanningRoot();
|
|
489
|
+
const stageSafe = safeFilename(input.stageId);
|
|
490
|
+
const workflowSafe = safeFilename(input.workflowId);
|
|
491
|
+
return path.join(
|
|
492
|
+
root,
|
|
493
|
+
".poetic",
|
|
494
|
+
"telemetry",
|
|
495
|
+
"state",
|
|
496
|
+
"flywheel",
|
|
497
|
+
workflowSafe,
|
|
498
|
+
"autoloop",
|
|
499
|
+
`${stageSafe}.iterations.json`
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
async function loadDurableAutoloopIterationSpill(input) {
|
|
503
|
+
const filePath = getIterationSpillPath(input);
|
|
504
|
+
try {
|
|
505
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
506
|
+
const parsed = JSON.parse(raw);
|
|
507
|
+
return isDurableAutoloopIterationSpill(parsed) ? parsed : null;
|
|
508
|
+
} catch {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
var iterationSpillWriteLocks = /* @__PURE__ */ new Map();
|
|
513
|
+
async function persistDurableAutoloopIterationSpill(input) {
|
|
514
|
+
const lockKey = `${input.workflowId}::${input.stageId}`;
|
|
515
|
+
const prior = iterationSpillWriteLocks.get(lockKey) ?? Promise.resolve();
|
|
516
|
+
const run = prior.then(async () => {
|
|
517
|
+
const existing = await loadDurableAutoloopIterationSpill({
|
|
518
|
+
workflowId: input.workflowId,
|
|
519
|
+
stageId: input.stageId
|
|
520
|
+
});
|
|
521
|
+
const iterationsByIndex = /* @__PURE__ */ new Map();
|
|
522
|
+
for (const record of existing?.iterations ?? []) {
|
|
523
|
+
iterationsByIndex.set(record.index, record);
|
|
524
|
+
}
|
|
525
|
+
iterationsByIndex.set(input.record.index, input.record);
|
|
526
|
+
const worktreeFingerprint = input.worktreeFingerprint ?? existing?.worktreeFingerprint;
|
|
527
|
+
const runStartedAtMs = input.runStartedAtMs ?? existing?.runStartedAtMs;
|
|
528
|
+
const spill = {
|
|
529
|
+
schemaVersion: 1,
|
|
530
|
+
updatedAt: new Date(input.record.timestamp).toISOString(),
|
|
531
|
+
workflowId: input.workflowId,
|
|
532
|
+
stageId: input.stageId,
|
|
533
|
+
writer: input.writer,
|
|
534
|
+
...runStartedAtMs !== void 0 ? { runStartedAtMs } : {},
|
|
535
|
+
...worktreeFingerprint ? { worktreeFingerprint } : {},
|
|
536
|
+
iterations: [...iterationsByIndex.values()].sort((left, right) => left.index - right.index)
|
|
537
|
+
};
|
|
538
|
+
await writeJsonAtomic(getIterationSpillPath(input), spill);
|
|
539
|
+
return spill;
|
|
540
|
+
});
|
|
541
|
+
const tail = run.catch(() => void 0);
|
|
542
|
+
iterationSpillWriteLocks.set(lockKey, tail);
|
|
543
|
+
void tail.then(() => {
|
|
544
|
+
if (iterationSpillWriteLocks.get(lockKey) === tail) {
|
|
545
|
+
iterationSpillWriteLocks.delete(lockKey);
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
return run;
|
|
549
|
+
}
|
|
550
|
+
async function loadDurableAutoloopDiagnostics(input) {
|
|
551
|
+
const root = getPlanningRoot();
|
|
552
|
+
const stageSafe = safeFilename(input.stageId);
|
|
553
|
+
const workflowSafe = safeFilename(input.workflowId);
|
|
554
|
+
const filePath = path.join(
|
|
555
|
+
root,
|
|
556
|
+
".poetic",
|
|
557
|
+
"telemetry",
|
|
558
|
+
"state",
|
|
559
|
+
"flywheel",
|
|
560
|
+
workflowSafe,
|
|
561
|
+
"autoloop",
|
|
562
|
+
`${stageSafe}.json`
|
|
563
|
+
);
|
|
564
|
+
try {
|
|
565
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
566
|
+
const parsed = JSON.parse(raw);
|
|
567
|
+
return isDurableAutoloopRunRecord(parsed) ? parsed : null;
|
|
568
|
+
} catch {
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function priorOutcomeFromDiagnostics(diagnostics) {
|
|
573
|
+
return {
|
|
574
|
+
loopSuccess: diagnostics.loopSuccess,
|
|
575
|
+
validationPassed: diagnostics.validationPassed,
|
|
576
|
+
terminationReason: diagnostics.terminationReason,
|
|
577
|
+
finalProgressScore: diagnostics.finalProgressScore,
|
|
578
|
+
totalIterations: diagnostics.totalIterations,
|
|
579
|
+
cost: diagnostics.cost,
|
|
580
|
+
outcomeMetadata: diagnostics.outcomeMetadata,
|
|
581
|
+
failure: diagnostics.failure
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
async function loadDurableAutoloopResumeState(input) {
|
|
585
|
+
const current = input.worktreePath ? await computeWorktreeFingerprint(input.worktreePath) : void 0;
|
|
586
|
+
const diagnostics = await loadDurableAutoloopDiagnostics(input);
|
|
587
|
+
const diagnosticsExact = diagnostics?.stageType === "autoloop" && worktreeFingerprintExactlyMatches(diagnostics.worktreeFingerprint, current);
|
|
588
|
+
const spill = await loadDurableAutoloopIterationSpill(input);
|
|
589
|
+
if (spill !== null && spill.iterations.length > 0) {
|
|
590
|
+
const maxIndex = spill.iterations.reduce((max, r) => Math.max(max, r.index), -1);
|
|
591
|
+
const completedIterations = maxIndex + 1;
|
|
592
|
+
const diagnosticsRecordedAt = diagnostics ? Date.parse(diagnostics.recordedAt) : Number.NaN;
|
|
593
|
+
const spillUpdatedAt = Date.parse(spill.updatedAt);
|
|
594
|
+
if (diagnostics !== null && diagnosticsExact && Number.isFinite(diagnosticsRecordedAt) && Number.isFinite(spillUpdatedAt) && diagnosticsRecordedAt >= spillUpdatedAt) {
|
|
595
|
+
return {
|
|
596
|
+
completedIterations: diagnostics.totalIterations,
|
|
597
|
+
source: "diagnostics",
|
|
598
|
+
finalProgressScore: diagnostics.finalProgressScore,
|
|
599
|
+
priorOutcome: priorOutcomeFromDiagnostics(diagnostics)
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
if (input.worktreePath && !worktreeFingerprintResumable(spill.worktreeFingerprint, current)) {
|
|
603
|
+
await clearDurableAutoloopIterationSpill(input);
|
|
604
|
+
return { completedIterations: 0, source: "spill", fingerprintMismatch: true };
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
completedIterations,
|
|
608
|
+
source: "spill",
|
|
609
|
+
...spill.runStartedAtMs !== void 0 ? { runStartedAtMs: spill.runStartedAtMs } : {}
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
if (diagnostics !== null && diagnostics.stageType === "autoloop") {
|
|
613
|
+
if (!diagnosticsExact) {
|
|
614
|
+
return { completedIterations: 0, source: "diagnostics", fingerprintMismatch: true };
|
|
615
|
+
}
|
|
616
|
+
return {
|
|
617
|
+
completedIterations: diagnostics.totalIterations,
|
|
618
|
+
source: "diagnostics",
|
|
619
|
+
finalProgressScore: diagnostics.finalProgressScore,
|
|
620
|
+
priorOutcome: priorOutcomeFromDiagnostics(diagnostics)
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
async function clearDurableAutoloopIterationSpill(input) {
|
|
626
|
+
const filePath = getIterationSpillPath(input);
|
|
627
|
+
try {
|
|
628
|
+
await fs.unlink(filePath);
|
|
629
|
+
} catch {
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
async function persistDurableAutoloopDiagnostics(input) {
|
|
633
|
+
const record = buildDurableAutoloopRunRecord(input);
|
|
634
|
+
const results = await Promise.allSettled([
|
|
635
|
+
persistToTelemetryState(record),
|
|
636
|
+
persistToTaskFrontmatter(record)
|
|
637
|
+
]);
|
|
638
|
+
const canonicalPersisted = results[0].status === "fulfilled";
|
|
639
|
+
const errors = results.map((r, idx) => ({ r, idx })).filter(({ r }) => r.status === "rejected").map(({ r, idx }) => ({
|
|
640
|
+
target: idx === 0 ? "telemetry_state" : "task_frontmatter",
|
|
641
|
+
error: r.reason
|
|
642
|
+
}));
|
|
643
|
+
if (errors.length > 0) {
|
|
644
|
+
console.warn(
|
|
645
|
+
`[flywheel-autoloop-durability] Best-effort persistence errors: ${errors.map((e) => `${e.target}: ${e.error instanceof Error ? e.error.message : String(e.error)}`).join("; ")}`
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
return { record, canonicalPersisted };
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// src/automation/flywheel-autoloop-trajectory.ts
|
|
652
|
+
import * as path2 from "path";
|
|
653
|
+
var MAX_CHANGED_FILES = 200;
|
|
654
|
+
var MAX_PROVIDER_OUTPUT_BYTES = 4e3;
|
|
655
|
+
var MAX_OUTPUT_TAIL_BYTES = 1500;
|
|
656
|
+
var MAX_POLICY_EVENTS = 100;
|
|
657
|
+
function safeFilename2(input) {
|
|
658
|
+
return input.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
659
|
+
}
|
|
660
|
+
function tailUtf8(input, maxBytes) {
|
|
661
|
+
if (input === void 0 || input === null || input.length === 0) return void 0;
|
|
662
|
+
if (Buffer.byteLength(input, "utf8") <= maxBytes) return input;
|
|
663
|
+
const buf = Buffer.from(input, "utf8");
|
|
664
|
+
const slice = buf.subarray(buf.length - maxBytes);
|
|
665
|
+
return slice.toString("utf8");
|
|
666
|
+
}
|
|
667
|
+
function detectCompletionSignal(events) {
|
|
668
|
+
if (!events || events.length === 0) return false;
|
|
669
|
+
return events.some((event) => {
|
|
670
|
+
const details = event.details;
|
|
671
|
+
return details?.completionSignalDetected === true;
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
function semanticValidationStatusFor(record) {
|
|
675
|
+
if (record.aiValidationShortCircuited === true) return "short_circuited";
|
|
676
|
+
return "unknown";
|
|
677
|
+
}
|
|
678
|
+
function buildIteration(record) {
|
|
679
|
+
const failing = record.qualityGateCommandResults?.find((r) => r.exitCode !== 0);
|
|
680
|
+
const changedFiles = record.changedFiles ?? [];
|
|
681
|
+
const policyEvents = record.policyEvents;
|
|
682
|
+
return {
|
|
683
|
+
iteration: record.iteration,
|
|
684
|
+
success: record.success,
|
|
685
|
+
providerExecutionStatus: record.providerExecutionStatus,
|
|
686
|
+
providerOutputStatus: record.providerOutputStatus,
|
|
687
|
+
providerStatusDetail: record.providerStatusDetail,
|
|
688
|
+
durationMs: record.durationMs,
|
|
689
|
+
cost: record.cost,
|
|
690
|
+
changedFiles: changedFiles.slice(0, MAX_CHANGED_FILES),
|
|
691
|
+
changedFileCount: changedFiles.length,
|
|
692
|
+
progressScore: record.progressScore,
|
|
693
|
+
hadDiff: record.progressMetrics?.hadDiff,
|
|
694
|
+
wasNoOp: record.progressMetrics?.wasNoOp,
|
|
695
|
+
qualityGatePassed: record.qualityGatePassed,
|
|
696
|
+
qualityGateError: record.qualityGateError,
|
|
697
|
+
failingCommand: failing?.command,
|
|
698
|
+
failingExitCode: failing?.exitCode,
|
|
699
|
+
failingOutputTail: tailUtf8(failing?.outputTail, MAX_OUTPUT_TAIL_BYTES),
|
|
700
|
+
allowlistDrift: record.allowlistDrift,
|
|
701
|
+
allowlistViolationCount: record.allowlistViolations?.length,
|
|
702
|
+
repairAttempted: record.repairAttempted,
|
|
703
|
+
repairSuccesses: record.repairSuccesses,
|
|
704
|
+
testsCreated: record.testsCreated,
|
|
705
|
+
aiValidationShortCircuited: record.aiValidationShortCircuited,
|
|
706
|
+
completionEvidence: record.completionEvidence,
|
|
707
|
+
completionSignalDetected: detectCompletionSignal(policyEvents),
|
|
708
|
+
semanticValidationStatus: semanticValidationStatusFor(record),
|
|
709
|
+
error: record.error,
|
|
710
|
+
providerOutputExcerpt: tailUtf8(record.providerOutputText, MAX_PROVIDER_OUTPUT_BYTES)
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function buildAutoloopTrajectory(input) {
|
|
714
|
+
const { result, summary } = input;
|
|
715
|
+
const iterations = (result.iterationHistory ?? []).map(buildIteration);
|
|
716
|
+
const policyEvents = result.policyEvents ? result.policyEvents.slice(-MAX_POLICY_EVENTS) : void 0;
|
|
717
|
+
const completionEvidence = iterations.slice().reverse().find((iteration) => iteration.completionEvidence !== void 0)?.completionEvidence;
|
|
718
|
+
return {
|
|
719
|
+
schemaVersion: 1,
|
|
720
|
+
runId: summary.runId,
|
|
721
|
+
taskId: summary.taskId,
|
|
722
|
+
workflowId: input.workflowId,
|
|
723
|
+
stageId: input.stage.id,
|
|
724
|
+
startedAt: summary.startedAt,
|
|
725
|
+
completedAt: summary.completedAt,
|
|
726
|
+
durationMs: summary.durationMs,
|
|
727
|
+
provider: summary.provider,
|
|
728
|
+
model: summary.model,
|
|
729
|
+
terminationReason: result.terminationReason,
|
|
730
|
+
outcome: summary.outcome,
|
|
731
|
+
success: summary.success,
|
|
732
|
+
loopSuccess: result.loopSuccess,
|
|
733
|
+
totalIterations: result.totalIterations,
|
|
734
|
+
finalProgressScore: result.finalProgressScore,
|
|
735
|
+
completionEvidence,
|
|
736
|
+
totalCost: summary.totalCost,
|
|
737
|
+
noOpIterationCount: summary.noOpIterationCount,
|
|
738
|
+
repairAttempts: summary.repairAttempts,
|
|
739
|
+
repairSuccesses: summary.repairSuccesses,
|
|
740
|
+
aiValidationShortCircuited: summary.aiValidationShortCircuited,
|
|
741
|
+
iterationHistoryArtifact: result.iterationHistoryArtifact,
|
|
742
|
+
resultArtifact: result.resultArtifact,
|
|
743
|
+
failureArtifact: result.failureArtifact,
|
|
744
|
+
iterations,
|
|
745
|
+
policyEvents
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function getAutoloopTrajectoryPath(input) {
|
|
749
|
+
const root = input.projectRoot ?? getPlanningRoot();
|
|
750
|
+
return path2.join(
|
|
751
|
+
root,
|
|
752
|
+
".poetic",
|
|
753
|
+
"telemetry",
|
|
754
|
+
"state",
|
|
755
|
+
"flywheel",
|
|
756
|
+
safeFilename2(input.workflowId),
|
|
757
|
+
"autoloop",
|
|
758
|
+
`${safeFilename2(input.stageId)}.trajectory.json`
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
async function persistAutoloopTrajectory(input) {
|
|
762
|
+
const trajectory = buildAutoloopTrajectory(input);
|
|
763
|
+
const workflowId = input.workflowId;
|
|
764
|
+
if (!workflowId) {
|
|
765
|
+
return { trajectory, path: null };
|
|
766
|
+
}
|
|
767
|
+
const filePath = getAutoloopTrajectoryPath({
|
|
768
|
+
workflowId,
|
|
769
|
+
stageId: input.stage.id
|
|
770
|
+
});
|
|
771
|
+
try {
|
|
772
|
+
await atomicWriteJson(filePath, trajectory);
|
|
773
|
+
return { trajectory, path: filePath };
|
|
774
|
+
} catch (err) {
|
|
775
|
+
console.warn(
|
|
776
|
+
`[flywheel-autoloop-trajectory] Persist failed: ${err instanceof Error ? err.message : String(err)}`
|
|
777
|
+
);
|
|
778
|
+
return { trajectory, path: null };
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// src/automation/flywheel-gate-repair.ts
|
|
783
|
+
import { performance } from "perf_hooks";
|
|
784
|
+
|
|
785
|
+
// src/automation/repair-evidence.ts
|
|
786
|
+
import * as fs2 from "fs/promises";
|
|
787
|
+
import * as path3 from "path";
|
|
788
|
+
function formatRepairSuppressionWarning(evidence) {
|
|
789
|
+
return `Review required: ${evidence.kind} in ${evidence.path}: ${evidence.addedLine}`;
|
|
790
|
+
}
|
|
791
|
+
var SUPPRESSION_PATTERNS = [
|
|
792
|
+
/\bbiome-ignore\b/i,
|
|
793
|
+
/\beslint-disable(?:-next-line|-line)?\b/i,
|
|
794
|
+
/@ts-(?:ignore|nocheck|expect-error)\b/i,
|
|
795
|
+
/#\s*noqa\b/i,
|
|
796
|
+
/#\s*nolint\b/i,
|
|
797
|
+
/\bnoinspection\b/i,
|
|
798
|
+
/\bshellcheck\s+disable\b/i,
|
|
799
|
+
/\bpragma:\s*no\s+cover\b/i,
|
|
800
|
+
/\bistanbul\s+ignore\b/i,
|
|
801
|
+
/\bc8\s+ignore\b/i,
|
|
802
|
+
/\bpytest\.mark\.(?:skip|skipif|xfail)\b/i
|
|
803
|
+
];
|
|
804
|
+
async function snapshotRepairSuppressions(cwd) {
|
|
805
|
+
let stdout;
|
|
806
|
+
try {
|
|
807
|
+
const diff = await execGit(["diff", "--unified=0", "--no-ext-diff", "HEAD"], {
|
|
808
|
+
cwd,
|
|
809
|
+
timeoutMs: 3e4
|
|
810
|
+
});
|
|
811
|
+
if (diff.exitCode !== 0) return void 0;
|
|
812
|
+
stdout = diff.stdout;
|
|
813
|
+
} catch {
|
|
814
|
+
return void 0;
|
|
815
|
+
}
|
|
816
|
+
const counts = /* @__PURE__ */ new Map();
|
|
817
|
+
let currentPath = "";
|
|
818
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
819
|
+
if (line.startsWith("+++ b/")) {
|
|
820
|
+
currentPath = line.slice("+++ b/".length);
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
if (!currentPath || !line.startsWith("+") || line.startsWith("+++")) continue;
|
|
824
|
+
const addedLine = line.slice(1).trim();
|
|
825
|
+
if (!SUPPRESSION_PATTERNS.some((pattern) => pattern.test(addedLine))) continue;
|
|
826
|
+
const key = `${currentPath}\0${addedLine}`;
|
|
827
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
828
|
+
}
|
|
829
|
+
try {
|
|
830
|
+
const untracked = await execGit(["ls-files", "--others", "--exclude-standard", "-z"], {
|
|
831
|
+
cwd,
|
|
832
|
+
timeoutMs: 3e4
|
|
833
|
+
});
|
|
834
|
+
if (untracked.exitCode !== 0) return void 0;
|
|
835
|
+
for (const relativePath of untracked.stdout.split("\0").filter(Boolean)) {
|
|
836
|
+
if (isWorktreeRunSentinelPath(relativePath) || shouldExcludeFile(relativePath)) continue;
|
|
837
|
+
const absolutePath = path3.resolve(cwd, relativePath);
|
|
838
|
+
const stat3 = await fs2.stat(absolutePath);
|
|
839
|
+
if (!stat3.isFile() || stat3.size > 1e6) continue;
|
|
840
|
+
const content = await fs2.readFile(absolutePath, "utf8");
|
|
841
|
+
for (const line of content.split(/\r?\n/)) {
|
|
842
|
+
const addedLine = line.trim();
|
|
843
|
+
if (!SUPPRESSION_PATTERNS.some((pattern) => pattern.test(addedLine))) continue;
|
|
844
|
+
const key = `${relativePath}\0${addedLine}`;
|
|
845
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
} catch {
|
|
849
|
+
return void 0;
|
|
850
|
+
}
|
|
851
|
+
return counts;
|
|
852
|
+
}
|
|
853
|
+
async function detectNewRepairSuppressions(cwd, before) {
|
|
854
|
+
if (!before) return [];
|
|
855
|
+
const after = await snapshotRepairSuppressions(cwd);
|
|
856
|
+
if (!after) return [];
|
|
857
|
+
const evidence = [];
|
|
858
|
+
for (const [key, count] of after) {
|
|
859
|
+
const added = count - (before.get(key) ?? 0);
|
|
860
|
+
if (added <= 0) continue;
|
|
861
|
+
const [path6, addedLine] = key.split("\0");
|
|
862
|
+
for (let i = 0; i < added; i++) {
|
|
863
|
+
evidence.push({
|
|
864
|
+
kind: "new_suppression_directive",
|
|
865
|
+
path: path6 ?? "",
|
|
866
|
+
addedLine: addedLine ?? "",
|
|
867
|
+
reviewRequired: true
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return evidence;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/automation/repair-scope-enforcement.ts
|
|
875
|
+
import * as fs3 from "fs/promises";
|
|
876
|
+
import * as path4 from "path";
|
|
877
|
+
|
|
878
|
+
// src/core/project/verification-control-paths.ts
|
|
879
|
+
var SOURCE_LIKE_VERIFICATION_CONTROLS = /* @__PURE__ */ new Set(["build.rs", "conftest.py"]);
|
|
880
|
+
function isVerificationControlPath(filePath) {
|
|
881
|
+
const normalized = filePath.replaceAll("\\", "/").replace(/^\.\//, "").toLowerCase();
|
|
882
|
+
const basename2 = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
883
|
+
return isProjectConfigPath(filePath) || SOURCE_LIKE_VERIFICATION_CONTROLS.has(basename2);
|
|
884
|
+
}
|
|
885
|
+
function isAutomaticRepairProtectedPath(filePath) {
|
|
886
|
+
return isProjectTestPath(filePath) || isVerificationControlPath(filePath);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// src/automation/repair-scope-enforcement.ts
|
|
890
|
+
var LITERAL_PATHSPEC_ENV = { GIT_LITERAL_PATHSPECS: "1" };
|
|
891
|
+
function isRelevantRepairPath(filePath) {
|
|
892
|
+
return !isWorktreeRunSentinelPath(filePath) && !shouldExcludeFile(filePath);
|
|
893
|
+
}
|
|
894
|
+
async function detectRepairScopeEvidence(worktreePath) {
|
|
895
|
+
const status = await execGit(["status", "--porcelain=v1", "--untracked-files=all"], {
|
|
896
|
+
cwd: worktreePath,
|
|
897
|
+
timeoutMs: 3e4
|
|
898
|
+
});
|
|
899
|
+
if (status.exitCode !== 0) {
|
|
900
|
+
throw new Error("Automatic repair scope status is unavailable");
|
|
901
|
+
}
|
|
902
|
+
const changedFiles = /* @__PURE__ */ new Set();
|
|
903
|
+
const linkedEndpoints = /* @__PURE__ */ new Map();
|
|
904
|
+
for (const entry of parseGitStatus(status.stdout)) {
|
|
905
|
+
if (isRelevantRepairPath(entry.path)) changedFiles.add(entry.path);
|
|
906
|
+
if (entry.originalPath && isRelevantRepairPath(entry.originalPath)) {
|
|
907
|
+
changedFiles.add(entry.originalPath);
|
|
908
|
+
if (isRelevantRepairPath(entry.path)) {
|
|
909
|
+
linkedEndpoints.set(entry.originalPath, entry.path);
|
|
910
|
+
linkedEndpoints.set(entry.path, entry.originalPath);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return {
|
|
915
|
+
changedFiles: [...changedFiles].sort((a, b) => a.localeCompare(b)),
|
|
916
|
+
linkedEndpoints
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
async function snapshotPath(worktreePath, relativePath) {
|
|
920
|
+
const absolutePath = path4.resolve(worktreePath, relativePath);
|
|
921
|
+
let worktree;
|
|
922
|
+
try {
|
|
923
|
+
const stat3 = await fs3.lstat(absolutePath);
|
|
924
|
+
if (stat3.isSymbolicLink()) {
|
|
925
|
+
worktree = { kind: "symlink", target: await fs3.readlink(absolutePath) };
|
|
926
|
+
} else if (stat3.isFile()) {
|
|
927
|
+
worktree = {
|
|
928
|
+
kind: "file",
|
|
929
|
+
contents: await fs3.readFile(absolutePath),
|
|
930
|
+
executable: (stat3.mode & 73) !== 0
|
|
931
|
+
};
|
|
932
|
+
} else {
|
|
933
|
+
throw new Error(`Unsupported repair snapshot path type: ${relativePath}`);
|
|
934
|
+
}
|
|
935
|
+
} catch (error) {
|
|
936
|
+
if (error.code !== "ENOENT") throw error;
|
|
937
|
+
worktree = { kind: "missing" };
|
|
938
|
+
}
|
|
939
|
+
const indexResult = await execGit(["ls-files", "--stage", "--", relativePath], {
|
|
940
|
+
cwd: worktreePath,
|
|
941
|
+
timeoutMs: 3e4,
|
|
942
|
+
env: LITERAL_PATHSPEC_ENV
|
|
943
|
+
});
|
|
944
|
+
const match = /^(\d+)\s+([0-9a-f]+)\s+0\t/.exec(indexResult.stdout);
|
|
945
|
+
return {
|
|
946
|
+
worktree,
|
|
947
|
+
...indexResult.exitCode === 0 && match?.[1] && match[2] ? { index: { mode: match[1], blob: match[2] } } : {}
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
async function captureAutomaticRepairScopeBaseline(worktreePath) {
|
|
951
|
+
const evidence = await detectRepairScopeEvidence(worktreePath);
|
|
952
|
+
const entries = /* @__PURE__ */ new Map();
|
|
953
|
+
for (const relativePath of evidence.changedFiles) {
|
|
954
|
+
entries.set(relativePath, await snapshotPath(worktreePath, relativePath));
|
|
955
|
+
}
|
|
956
|
+
return { entries };
|
|
957
|
+
}
|
|
958
|
+
async function pathMatchesSnapshot(worktreePath, relativePath, snapshot) {
|
|
959
|
+
const current = await snapshotPath(worktreePath, relativePath);
|
|
960
|
+
if (current.index?.mode !== snapshot.index?.mode || current.index?.blob !== snapshot.index?.blob) {
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
963
|
+
if (current.worktree.kind !== snapshot.worktree.kind) return false;
|
|
964
|
+
if (current.worktree.kind === "missing") return true;
|
|
965
|
+
if (current.worktree.kind === "symlink" && snapshot.worktree.kind === "symlink") {
|
|
966
|
+
return current.worktree.target === snapshot.worktree.target;
|
|
967
|
+
}
|
|
968
|
+
if (current.worktree.kind === "file" && snapshot.worktree.kind === "file") {
|
|
969
|
+
return current.worktree.executable === snapshot.worktree.executable && current.worktree.contents.equals(snapshot.worktree.contents);
|
|
970
|
+
}
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
async function restoreSnapshotPath(worktreePath, relativePath, snapshot) {
|
|
974
|
+
const removeRepairPath = async (absolutePath2) => {
|
|
975
|
+
try {
|
|
976
|
+
await fs3.lstat(absolutePath2);
|
|
977
|
+
} catch (error) {
|
|
978
|
+
if (error.code === "ENOENT") return;
|
|
979
|
+
throw error;
|
|
980
|
+
}
|
|
981
|
+
const removal = await safeRmDir(absolutePath2, {
|
|
982
|
+
managedRoots: [worktreePath],
|
|
983
|
+
protectedPaths: [worktreePath]
|
|
984
|
+
});
|
|
985
|
+
if (!removal.deleted) {
|
|
986
|
+
throw new Error(removal.reason ?? `Refusing to remove repair path ${absolutePath2}`);
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
if (!snapshot) {
|
|
990
|
+
const checkout = await execGit(["checkout", "HEAD", "--", relativePath], {
|
|
991
|
+
cwd: worktreePath,
|
|
992
|
+
timeoutMs: 3e4,
|
|
993
|
+
env: LITERAL_PATHSPEC_ENV
|
|
994
|
+
}).catch(() => void 0);
|
|
995
|
+
if (checkout?.exitCode === 0) return;
|
|
996
|
+
await execGit(["update-index", "--force-remove", "--", relativePath], {
|
|
997
|
+
cwd: worktreePath,
|
|
998
|
+
timeoutMs: 3e4,
|
|
999
|
+
env: LITERAL_PATHSPEC_ENV
|
|
1000
|
+
}).catch(() => void 0);
|
|
1001
|
+
await removeRepairPath(path4.resolve(worktreePath, relativePath));
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
const absolutePath = path4.resolve(worktreePath, relativePath);
|
|
1005
|
+
if (snapshot.worktree.kind === "missing") {
|
|
1006
|
+
await removeRepairPath(absolutePath);
|
|
1007
|
+
} else {
|
|
1008
|
+
await fs3.mkdir(path4.dirname(absolutePath), { recursive: true });
|
|
1009
|
+
await removeRepairPath(absolutePath);
|
|
1010
|
+
if (snapshot.worktree.kind === "symlink") {
|
|
1011
|
+
await fs3.symlink(snapshot.worktree.target, absolutePath);
|
|
1012
|
+
} else {
|
|
1013
|
+
await fs3.writeFile(absolutePath, snapshot.worktree.contents, {
|
|
1014
|
+
mode: snapshot.worktree.executable ? 493 : 420
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
if (snapshot.index) {
|
|
1019
|
+
await execGit(
|
|
1020
|
+
[
|
|
1021
|
+
"update-index",
|
|
1022
|
+
"--add",
|
|
1023
|
+
"--cacheinfo",
|
|
1024
|
+
snapshot.index.mode,
|
|
1025
|
+
snapshot.index.blob,
|
|
1026
|
+
relativePath
|
|
1027
|
+
],
|
|
1028
|
+
{ cwd: worktreePath, timeoutMs: 3e4, env: LITERAL_PATHSPEC_ENV }
|
|
1029
|
+
);
|
|
1030
|
+
} else {
|
|
1031
|
+
await execGit(["update-index", "--force-remove", "--", relativePath], {
|
|
1032
|
+
cwd: worktreePath,
|
|
1033
|
+
timeoutMs: 3e4,
|
|
1034
|
+
env: LITERAL_PATHSPEC_ENV
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
function decideAutomaticRepairScope(changedFiles, explicitScope) {
|
|
1039
|
+
const protectedPaths = changedFiles.filter(isAutomaticRepairProtectedPath);
|
|
1040
|
+
return decideStageChangeScope({
|
|
1041
|
+
changedPaths: changedFiles,
|
|
1042
|
+
explicitScope,
|
|
1043
|
+
allowlistMode: explicitScope.length > 0 ? "strict" : "off",
|
|
1044
|
+
denylistPolicy: {
|
|
1045
|
+
denylist: protectedPaths,
|
|
1046
|
+
mode: "strict",
|
|
1047
|
+
allow: explicitScope
|
|
1048
|
+
},
|
|
1049
|
+
mutationOrigin: "provider"
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
async function changedSinceBaseline(worktreePath, baseline) {
|
|
1053
|
+
const current = await detectRepairScopeEvidence(worktreePath);
|
|
1054
|
+
const candidates = /* @__PURE__ */ new Set([...baseline.entries.keys(), ...current.changedFiles]);
|
|
1055
|
+
const changed = [];
|
|
1056
|
+
for (const relativePath of candidates) {
|
|
1057
|
+
const prior = baseline.entries.get(relativePath);
|
|
1058
|
+
if (!prior || !await pathMatchesSnapshot(worktreePath, relativePath, prior)) {
|
|
1059
|
+
changed.push(relativePath);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
return {
|
|
1063
|
+
changedFiles: changed.sort((a, b) => a.localeCompare(b)),
|
|
1064
|
+
linkedEndpoints: current.linkedEndpoints
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
async function enforceAutomaticRepairScope(input) {
|
|
1068
|
+
const explicitScope = [...input.explicitScope ?? []];
|
|
1069
|
+
const repairEvidence = await changedSinceBaseline(input.worktreePath, input.baseline);
|
|
1070
|
+
const decision = decideAutomaticRepairScope(repairEvidence.changedFiles, explicitScope);
|
|
1071
|
+
const violationSet = /* @__PURE__ */ new Set([
|
|
1072
|
+
.../* @__PURE__ */ new Set([...decision.allowlistViolations, ...decision.denylistViolations])
|
|
1073
|
+
]);
|
|
1074
|
+
for (const violation of [...violationSet]) {
|
|
1075
|
+
const counterpart = repairEvidence.linkedEndpoints.get(violation);
|
|
1076
|
+
if (counterpart) violationSet.add(counterpart);
|
|
1077
|
+
}
|
|
1078
|
+
const violations = [...violationSet].sort((a, b) => a.localeCompare(b));
|
|
1079
|
+
for (const relativePath of violations) {
|
|
1080
|
+
await restoreSnapshotPath(
|
|
1081
|
+
input.worktreePath,
|
|
1082
|
+
relativePath,
|
|
1083
|
+
input.baseline.entries.get(relativePath)
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
const residualEvidence = await changedSinceBaseline(input.worktreePath, input.baseline);
|
|
1087
|
+
const residual = decideAutomaticRepairScope(residualEvidence.changedFiles, explicitScope);
|
|
1088
|
+
const residualViolations = [
|
|
1089
|
+
.../* @__PURE__ */ new Set([...residual.allowlistViolations, ...residual.denylistViolations])
|
|
1090
|
+
];
|
|
1091
|
+
return {
|
|
1092
|
+
changedFiles: residualEvidence.changedFiles,
|
|
1093
|
+
violations,
|
|
1094
|
+
revertedFiles: violations,
|
|
1095
|
+
...residualViolations.length > 0 ? { error: `Automatic repair scope violation: ${residualViolations.join(", ")}` } : {}
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// src/automation/flywheel-gate-repair.ts
|
|
1100
|
+
function normalizeGates(gates) {
|
|
1101
|
+
return gates.map((gate, index) => {
|
|
1102
|
+
if (typeof gate === "string") {
|
|
1103
|
+
return {
|
|
1104
|
+
id: `gate-${index + 1}`,
|
|
1105
|
+
command: gate,
|
|
1106
|
+
deterministic: true,
|
|
1107
|
+
severity: "error",
|
|
1108
|
+
mustPass: true
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
const severity = gate.severity ?? "error";
|
|
1112
|
+
return {
|
|
1113
|
+
...gate,
|
|
1114
|
+
deterministic: gate.deterministic ?? true,
|
|
1115
|
+
severity,
|
|
1116
|
+
mustPass: gate.mustPass ?? severity !== "warn"
|
|
1117
|
+
};
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
async function enforceAllowlistAfterEdit(input) {
|
|
1121
|
+
const result = await enforceAutomaticRepairScope({
|
|
1122
|
+
worktreePath: input.worktreePath,
|
|
1123
|
+
baseline: input.baseline,
|
|
1124
|
+
explicitScope: input.allowlist
|
|
1125
|
+
});
|
|
1126
|
+
return {
|
|
1127
|
+
changedFiles: result.changedFiles,
|
|
1128
|
+
violations: result.violations,
|
|
1129
|
+
autoRevertedFiles: result.revertedFiles,
|
|
1130
|
+
error: result.error
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
var DEFAULT_GATE_COMMAND_TIMEOUT_MS = 10 * 6e4;
|
|
1134
|
+
async function runGate(gate, cwd, options = {}) {
|
|
1135
|
+
const start = performance.now();
|
|
1136
|
+
const envTimeoutMsRaw = Number(process.env.POETIC_GATE_COMMAND_TIMEOUT_MS);
|
|
1137
|
+
const envTimeoutMs = Number.isFinite(envTimeoutMsRaw) && envTimeoutMsRaw > 0 ? envTimeoutMsRaw : void 0;
|
|
1138
|
+
try {
|
|
1139
|
+
const result = await runVerifyCommands({
|
|
1140
|
+
cwd,
|
|
1141
|
+
commands: [gate.command],
|
|
1142
|
+
commandProvenance: "candidate-gate-config",
|
|
1143
|
+
timeoutMs: gate.timeoutMs ?? envTimeoutMs ?? DEFAULT_GATE_COMMAND_TIMEOUT_MS,
|
|
1144
|
+
signal: options.abortSignal
|
|
1145
|
+
});
|
|
1146
|
+
const commandResult = result.results[0];
|
|
1147
|
+
const durationMs = performance.now() - start;
|
|
1148
|
+
const exitCode = commandResult?.exitCode ?? 1;
|
|
1149
|
+
const expectedExitCode = gate.expectedExitCode ?? 0;
|
|
1150
|
+
return {
|
|
1151
|
+
gate,
|
|
1152
|
+
passed: exitCode === expectedExitCode,
|
|
1153
|
+
exitCode,
|
|
1154
|
+
durationMs,
|
|
1155
|
+
outputTail: result.combinedOutput ? truncateTail(result.combinedOutput, 4e3) : commandResult?.outputTail
|
|
1156
|
+
};
|
|
1157
|
+
} catch (error) {
|
|
1158
|
+
const durationMs = performance.now() - start;
|
|
1159
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1160
|
+
return {
|
|
1161
|
+
gate,
|
|
1162
|
+
passed: false,
|
|
1163
|
+
exitCode: 124,
|
|
1164
|
+
durationMs,
|
|
1165
|
+
outputTail: truncateTail(message, 4e3)
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
async function runAllGates(gates, cwd, options = {}) {
|
|
1170
|
+
const results = [];
|
|
1171
|
+
for (const gate of gates) {
|
|
1172
|
+
const result = await runGate(gate, cwd, { abortSignal: options.abortSignal });
|
|
1173
|
+
if (!result.passed && gate.mustPass && options.baselineCwd) {
|
|
1174
|
+
const baselineResult = await runGate(gate, options.baselineCwd, {
|
|
1175
|
+
abortSignal: options.abortSignal
|
|
1176
|
+
});
|
|
1177
|
+
result.baselinePassed = baselineResult.passed;
|
|
1178
|
+
result.isRegression = baselineResult.passed;
|
|
1179
|
+
}
|
|
1180
|
+
results.push(result);
|
|
1181
|
+
if (!result.passed && gate.mustPass && result.isRegression !== false) {
|
|
1182
|
+
break;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
const allMustPassPassed = results.every((r) => r.passed || !r.gate.mustPass);
|
|
1186
|
+
const regressionsPassed = results.every(
|
|
1187
|
+
(r) => r.passed || !r.gate.mustPass || r.isRegression === false
|
|
1188
|
+
);
|
|
1189
|
+
return {
|
|
1190
|
+
passed: allMustPassPassed,
|
|
1191
|
+
regressionsPassed,
|
|
1192
|
+
results
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
function buildRepairPrompt(opts) {
|
|
1196
|
+
const { failedGate, gateResult, changedFiles, repairConfig } = opts;
|
|
1197
|
+
const template = repairConfig?.promptTemplate || `The following quality gate is failing:
|
|
1198
|
+
|
|
1199
|
+
Command: ${failedGate.command}
|
|
1200
|
+
Exit Code: ${gateResult.exitCode}
|
|
1201
|
+
Expected: ${failedGate.expectedExitCode ?? 0}
|
|
1202
|
+
|
|
1203
|
+
Output (last 4000 chars):
|
|
1204
|
+
${renderUntrustedCommandOutput(gateResult.outputTail)}
|
|
1205
|
+
|
|
1206
|
+
Changed Files:
|
|
1207
|
+
${changedFiles.length > 0 ? changedFiles.map((f) => `- ${f}`).join("\n") : "(none)"}
|
|
1208
|
+
|
|
1209
|
+
Please make the MINIMUM changes required to make this gate pass. Fix the underlying cause of the specific issue reported by the gate command.`;
|
|
1210
|
+
return `${template}
|
|
1211
|
+
|
|
1212
|
+
${renderRepairConstraints()}`;
|
|
1213
|
+
}
|
|
1214
|
+
async function attemptGateRepair(opts) {
|
|
1215
|
+
const start = performance.now();
|
|
1216
|
+
const {
|
|
1217
|
+
failedGate,
|
|
1218
|
+
gateResult,
|
|
1219
|
+
allGates,
|
|
1220
|
+
worktreePath,
|
|
1221
|
+
changedFiles,
|
|
1222
|
+
executor,
|
|
1223
|
+
provider,
|
|
1224
|
+
model,
|
|
1225
|
+
attemptNumber,
|
|
1226
|
+
allowlist,
|
|
1227
|
+
allowlistMode,
|
|
1228
|
+
baselineCwd,
|
|
1229
|
+
abortSignal,
|
|
1230
|
+
timeoutMs
|
|
1231
|
+
} = opts;
|
|
1232
|
+
const repairConfig = failedGate.repair;
|
|
1233
|
+
const repairScopeBaseline = await captureAutomaticRepairScopeBaseline(worktreePath);
|
|
1234
|
+
const suppressionBaseline = await snapshotRepairSuppressions(worktreePath);
|
|
1235
|
+
if (repairConfig?.preCommands) {
|
|
1236
|
+
for (const cmd of repairConfig.preCommands) {
|
|
1237
|
+
await runVerifyCommands({
|
|
1238
|
+
cwd: worktreePath,
|
|
1239
|
+
commands: [cmd],
|
|
1240
|
+
commandProvenance: "repair-config",
|
|
1241
|
+
signal: abortSignal
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
const repairPrompt = buildRepairPrompt({
|
|
1246
|
+
failedGate,
|
|
1247
|
+
gateResult,
|
|
1248
|
+
changedFiles,
|
|
1249
|
+
repairConfig
|
|
1250
|
+
});
|
|
1251
|
+
let providerError;
|
|
1252
|
+
let providerCost = 0;
|
|
1253
|
+
let providerCostUnavailableReason;
|
|
1254
|
+
let providerInputTokens;
|
|
1255
|
+
let providerOutputTokens;
|
|
1256
|
+
let tokenUsageSource;
|
|
1257
|
+
try {
|
|
1258
|
+
const variantConfig = {
|
|
1259
|
+
provider,
|
|
1260
|
+
model,
|
|
1261
|
+
strategy: "conservative",
|
|
1262
|
+
executionMode: "local",
|
|
1263
|
+
isBasicCompetition: true,
|
|
1264
|
+
workflowWorktreePath: worktreePath,
|
|
1265
|
+
disableAutoCommit: true,
|
|
1266
|
+
structuredFileTargets: allowlist ?? [],
|
|
1267
|
+
...typeof timeoutMs === "number" && timeoutMs > 0 ? { timeoutMs } : {}
|
|
1268
|
+
};
|
|
1269
|
+
const result = await executor.executeVariant(
|
|
1270
|
+
generateCompetitionId(),
|
|
1271
|
+
variantConfig,
|
|
1272
|
+
repairPrompt,
|
|
1273
|
+
{ abortSignal }
|
|
1274
|
+
);
|
|
1275
|
+
providerCostUnavailableReason = readCostMetricsUnavailableReason(result.costMetrics);
|
|
1276
|
+
const reportedCost = result.costMetrics?.totalCost;
|
|
1277
|
+
if (providerCostUnavailableReason === void 0 && typeof reportedCost === "number" && Number.isFinite(reportedCost)) {
|
|
1278
|
+
providerCost = reportedCost;
|
|
1279
|
+
}
|
|
1280
|
+
providerInputTokens = result.costMetrics?.inputTokens;
|
|
1281
|
+
providerOutputTokens = result.costMetrics?.outputTokens;
|
|
1282
|
+
tokenUsageSource = result.costMetrics?.tokenUsageSource;
|
|
1283
|
+
providerError = result.success ? void 0 : result.error?.message ?? "Repair provider failed";
|
|
1284
|
+
} catch (error) {
|
|
1285
|
+
providerError = error instanceof Error ? error.message : String(error);
|
|
1286
|
+
}
|
|
1287
|
+
if (repairConfig?.postCommands) {
|
|
1288
|
+
for (const cmd of repairConfig.postCommands) {
|
|
1289
|
+
await runVerifyCommands({
|
|
1290
|
+
cwd: worktreePath,
|
|
1291
|
+
commands: [cmd],
|
|
1292
|
+
commandProvenance: "repair-config",
|
|
1293
|
+
signal: abortSignal
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
const allowlistState = await enforceAllowlistAfterEdit({
|
|
1298
|
+
worktreePath,
|
|
1299
|
+
baseline: repairScopeBaseline,
|
|
1300
|
+
allowlist,
|
|
1301
|
+
allowlistMode: allowlistMode ?? "warn"
|
|
1302
|
+
});
|
|
1303
|
+
const reviewRequiredEvidence = await detectNewRepairSuppressions(
|
|
1304
|
+
worktreePath,
|
|
1305
|
+
suppressionBaseline
|
|
1306
|
+
);
|
|
1307
|
+
if (allowlistState.error) {
|
|
1308
|
+
const durationMs2 = performance.now() - start;
|
|
1309
|
+
const combinedError = [providerError, allowlistState.error].filter((value) => Boolean(value)).join("\n");
|
|
1310
|
+
return {
|
|
1311
|
+
success: false,
|
|
1312
|
+
attemptNumber,
|
|
1313
|
+
cost: providerCost,
|
|
1314
|
+
costUnavailableReason: providerCostUnavailableReason,
|
|
1315
|
+
inputTokens: providerInputTokens,
|
|
1316
|
+
outputTokens: providerOutputTokens,
|
|
1317
|
+
tokenUsageSource,
|
|
1318
|
+
durationMs: durationMs2,
|
|
1319
|
+
gatesPassedAfterRepair: false,
|
|
1320
|
+
gateResults: [],
|
|
1321
|
+
providerError: combinedError,
|
|
1322
|
+
allowlistState,
|
|
1323
|
+
abortFurtherRepairs: true
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
const gateCheck = await runAllGates(allGates, worktreePath, {
|
|
1327
|
+
baselineCwd,
|
|
1328
|
+
abortSignal
|
|
1329
|
+
});
|
|
1330
|
+
const durationMs = performance.now() - start;
|
|
1331
|
+
return {
|
|
1332
|
+
success: !providerError && gateCheck.regressionsPassed,
|
|
1333
|
+
attemptNumber,
|
|
1334
|
+
cost: providerCost,
|
|
1335
|
+
costUnavailableReason: providerCostUnavailableReason,
|
|
1336
|
+
inputTokens: providerInputTokens,
|
|
1337
|
+
outputTokens: providerOutputTokens,
|
|
1338
|
+
tokenUsageSource,
|
|
1339
|
+
durationMs,
|
|
1340
|
+
gatesPassedAfterRepair: gateCheck.passed,
|
|
1341
|
+
gateResults: gateCheck.results,
|
|
1342
|
+
providerError,
|
|
1343
|
+
allowlistState,
|
|
1344
|
+
...reviewRequiredEvidence.length > 0 ? { reviewRequiredEvidence } : {}
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
function shouldAttemptRepair(gate) {
|
|
1348
|
+
if (!gate.mustPass) return false;
|
|
1349
|
+
if (!gate.deterministic) return false;
|
|
1350
|
+
if (gate.repair === void 0) return false;
|
|
1351
|
+
return true;
|
|
1352
|
+
}
|
|
1353
|
+
function getMaxRepairAttempts(gate) {
|
|
1354
|
+
return gate.repair?.maxAttempts ?? 2;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// src/automation/flywheel-provider-defaults.ts
|
|
1358
|
+
function resolveFlywheelProvider(provider, cwd = process.cwd()) {
|
|
1359
|
+
const explicitProvider = provider?.trim();
|
|
1360
|
+
return resolveDefaultProvider(
|
|
1361
|
+
explicitProvider && explicitProvider.length > 0 ? { provider: explicitProvider } : void 0,
|
|
1362
|
+
cwd
|
|
1363
|
+
).value;
|
|
1364
|
+
}
|
|
1365
|
+
function resolveFlywheelStageProvider(stage, cwd = process.cwd()) {
|
|
1366
|
+
return resolveFlywheelProvider(stage.provider, cwd);
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// src/automation/flywheel-autoloop-executor.ts
|
|
1370
|
+
var FLYWHEEL_AUTOLOOP_CONTROLLER_RESOURCE_LIMITS = {
|
|
1371
|
+
maxMemoryMB: 1048576,
|
|
1372
|
+
maxCpuPercent: 1e3,
|
|
1373
|
+
maxProcesses: Number.MAX_SAFE_INTEGER
|
|
1374
|
+
};
|
|
1375
|
+
var LITERAL_PATHSPEC_ENV2 = { GIT_LITERAL_PATHSPECS: "1" };
|
|
1376
|
+
var AutoloopTerminationError = class extends Error {
|
|
1377
|
+
terminationSignal;
|
|
1378
|
+
constructor(message, terminationSignal) {
|
|
1379
|
+
super(message);
|
|
1380
|
+
this.name = "AutoloopTerminationError";
|
|
1381
|
+
this.terminationSignal = terminationSignal;
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
function policyForTerminationReason(reason) {
|
|
1385
|
+
if (reason === "max_iterations_reached") return "max_iterations";
|
|
1386
|
+
if (reason === "timebox_exceeded") return "timebox";
|
|
1387
|
+
if (reason === "quality_gate_failure" || reason === "baseline_blocked" || reason === "allowlist_violation" || reason === "tests_required_not_met") {
|
|
1388
|
+
return "quality_gate";
|
|
1389
|
+
}
|
|
1390
|
+
if (reason === "resource_exhaustion") return "resource_limit";
|
|
1391
|
+
if (reason === "progress_stagnation") return "provider_pivot";
|
|
1392
|
+
if (reason === "success" || reason === "heuristic_complete") return "success";
|
|
1393
|
+
return "goal_gate";
|
|
1394
|
+
}
|
|
1395
|
+
function mapQualityGateCommandResultsToCustomGateResults(commandResults, toDurationMs) {
|
|
1396
|
+
if (!commandResults || commandResults.length === 0) {
|
|
1397
|
+
return [];
|
|
1398
|
+
}
|
|
1399
|
+
return commandResults.filter((r) => r.mustPass !== false).map((r, idx) => {
|
|
1400
|
+
const expectedExitCode = r.expectedExitCode ?? 0;
|
|
1401
|
+
const passed = typeof r.passed === "boolean" ? r.passed : r.exitCode === expectedExitCode;
|
|
1402
|
+
const mapped = {
|
|
1403
|
+
id: r.id && r.id.length > 0 ? r.id : `gate-${idx + 1}`,
|
|
1404
|
+
command: r.command,
|
|
1405
|
+
passed,
|
|
1406
|
+
durationMs: toDurationMs(Math.max(0, Number.isFinite(r.durationMs) ? r.durationMs : 0)),
|
|
1407
|
+
exitCode: r.exitCode
|
|
1408
|
+
};
|
|
1409
|
+
if (r.outputTail !== void 0) {
|
|
1410
|
+
mapped.outputTail = r.outputTail;
|
|
1411
|
+
}
|
|
1412
|
+
if (r.isRegression !== void 0) {
|
|
1413
|
+
mapped.isRegression = r.isRegression;
|
|
1414
|
+
}
|
|
1415
|
+
return mapped;
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
var MIN_GATE_REPAIR_TIMEOUT_MS = 3e4;
|
|
1419
|
+
var DEFAULT_TEST_FILE_PATTERNS = [
|
|
1420
|
+
"**/*.test.ts",
|
|
1421
|
+
"**/*.spec.ts",
|
|
1422
|
+
"tests/**/*.ts",
|
|
1423
|
+
"__tests__/**/*.ts"
|
|
1424
|
+
];
|
|
1425
|
+
var TEST_FILE_PATTERNS_BY_LANGUAGE = {
|
|
1426
|
+
node: DEFAULT_TEST_FILE_PATTERNS,
|
|
1427
|
+
typescript: DEFAULT_TEST_FILE_PATTERNS,
|
|
1428
|
+
javascript: ["**/*.test.js", "**/*.spec.js", "tests/**/*.js", "__tests__/**/*.js"],
|
|
1429
|
+
python: ["test_*.py", "*_test.py", "tests/**/*.py"],
|
|
1430
|
+
go: ["*_test.go"],
|
|
1431
|
+
rust: ["tests/**/*.rs", "#[test] functions in src/**/*.rs"],
|
|
1432
|
+
java: ["**/*Test.java", "**/*Tests.java", "src/test/**/*.java"],
|
|
1433
|
+
dotnet: ["**/*Tests.cs", "**/*Test.cs"],
|
|
1434
|
+
swift: ["**/*Tests.swift", "Tests/**/*.swift"],
|
|
1435
|
+
kotlin: ["**/*Test.kt", "**/*Tests.kt", "src/test/**/*.kt"],
|
|
1436
|
+
dart: ["*_test.dart", "test/**/*_test.dart"],
|
|
1437
|
+
ruby: ["*_test.rb", "*_spec.rb", "spec/**/*_spec.rb", "test/**/*_test.rb"],
|
|
1438
|
+
php: ["*Test.php", "tests/**/*Test.php"],
|
|
1439
|
+
c: ["*_test.c", "tests/**/*.c"],
|
|
1440
|
+
cpp: ["*_test.cc", "*_test.cpp", "tests/**/*.cc", "tests/**/*.cpp"],
|
|
1441
|
+
shell: ["*_test.sh", "tests/**/*.bats"],
|
|
1442
|
+
powershell: ["*.Tests.ps1"],
|
|
1443
|
+
r: ["tests/testthat/test-*.R"]
|
|
1444
|
+
};
|
|
1445
|
+
function selectDisplayTestFilePatterns(language, configuredPatterns) {
|
|
1446
|
+
if (configuredPatterns && configuredPatterns.length > 0) {
|
|
1447
|
+
return configuredPatterns;
|
|
1448
|
+
}
|
|
1449
|
+
if (!language) return null;
|
|
1450
|
+
return TEST_FILE_PATTERNS_BY_LANGUAGE[language] ?? null;
|
|
1451
|
+
}
|
|
1452
|
+
function selectPrimaryPromptLanguage(languages) {
|
|
1453
|
+
const concrete = languages.filter(
|
|
1454
|
+
(language) => language !== "docs" && language !== "unknown" && language !== "node"
|
|
1455
|
+
);
|
|
1456
|
+
if (concrete.length === 1) return concrete[0] ?? null;
|
|
1457
|
+
if (concrete.length > 1) {
|
|
1458
|
+
const nonJs = concrete.filter(
|
|
1459
|
+
(language) => language !== "typescript" && language !== "javascript"
|
|
1460
|
+
);
|
|
1461
|
+
if (nonJs.length === 0) {
|
|
1462
|
+
return concrete.includes("typescript") ? "typescript" : "javascript";
|
|
1463
|
+
}
|
|
1464
|
+
return null;
|
|
1465
|
+
}
|
|
1466
|
+
if (languages.includes("node")) {
|
|
1467
|
+
return "typescript";
|
|
1468
|
+
}
|
|
1469
|
+
return null;
|
|
1470
|
+
}
|
|
1471
|
+
function renderTestRequirementBody(language, configuredPatterns) {
|
|
1472
|
+
const patterns = selectDisplayTestFilePatterns(language, configuredPatterns);
|
|
1473
|
+
if (patterns && patterns.length > 0) {
|
|
1474
|
+
return `Expected patterns:
|
|
1475
|
+
${patterns.map((p) => `- ${p}`).join("\n")}`;
|
|
1476
|
+
}
|
|
1477
|
+
return "Follow the existing test conventions of the target project (match the naming, directory layout, and framework already used for tests in this repository).";
|
|
1478
|
+
}
|
|
1479
|
+
function isCreatedTargetTestFile(filePath) {
|
|
1480
|
+
if (!isProjectTestPath(filePath)) return false;
|
|
1481
|
+
const language = languageForProjectPath(filePath);
|
|
1482
|
+
return language !== null && language !== "docs";
|
|
1483
|
+
}
|
|
1484
|
+
async function detectTestFiles(changedFiles, patterns) {
|
|
1485
|
+
if (patterns && patterns.length > 0) {
|
|
1486
|
+
const minimatchModule = await import("minimatch");
|
|
1487
|
+
const minimatch = typeof minimatchModule.minimatch === "function" ? minimatchModule.minimatch : typeof minimatchModule.default === "function" ? minimatchModule.default : void 0;
|
|
1488
|
+
if (!minimatch) {
|
|
1489
|
+
throw new Error("Failed to load minimatch for test file detection");
|
|
1490
|
+
}
|
|
1491
|
+
return changedFiles.filter((file) => patterns.some((pattern) => minimatch(file, pattern)));
|
|
1492
|
+
}
|
|
1493
|
+
return changedFiles.filter((file) => isCreatedTargetTestFile(file));
|
|
1494
|
+
}
|
|
1495
|
+
function verifyRuntimeEvidence(input) {
|
|
1496
|
+
if (!input.config) {
|
|
1497
|
+
return { passed: true, blockingIssues: [] };
|
|
1498
|
+
}
|
|
1499
|
+
if (input.config.requireAcceptanceCriteria !== true) {
|
|
1500
|
+
return { passed: true, blockingIssues: [] };
|
|
1501
|
+
}
|
|
1502
|
+
const validation = validateRequirements({
|
|
1503
|
+
requirementsTaskText: input.basePrompt,
|
|
1504
|
+
providerOutputText: input.providerOutputText,
|
|
1505
|
+
workingDir: input.worktreePath,
|
|
1506
|
+
changedFiles: input.changedFiles,
|
|
1507
|
+
testVerification: input.testVerification,
|
|
1508
|
+
config: {
|
|
1509
|
+
mode: "on",
|
|
1510
|
+
minCoverage: input.config.minCoverage ?? 0.9,
|
|
1511
|
+
requireEvidence: input.config.requireEvidence ?? true,
|
|
1512
|
+
blockNewRepoRootMarkdown: input.config.blockNewRepoRootMarkdown ?? true
|
|
1513
|
+
}
|
|
1514
|
+
});
|
|
1515
|
+
return {
|
|
1516
|
+
passed: validation.passed,
|
|
1517
|
+
blockingIssues: validation.blockingIssues,
|
|
1518
|
+
validation
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
var AUTOLOOP_TERMINATION_REASON_SET = /* @__PURE__ */ new Set([
|
|
1522
|
+
"max_iterations_reached",
|
|
1523
|
+
"timebox_exceeded",
|
|
1524
|
+
"progress_stagnation",
|
|
1525
|
+
"no_op_loop",
|
|
1526
|
+
"duplicate_change_loop",
|
|
1527
|
+
"resource_exhaustion",
|
|
1528
|
+
"quality_gate_failure",
|
|
1529
|
+
"allowlist_violation",
|
|
1530
|
+
"tests_required_not_met",
|
|
1531
|
+
"evidence_unsatisfied",
|
|
1532
|
+
"verification_unavailable",
|
|
1533
|
+
"no_op_intervention_exceeded",
|
|
1534
|
+
"goal_satisfied",
|
|
1535
|
+
"heuristic_complete",
|
|
1536
|
+
"baseline_blocked",
|
|
1537
|
+
"success",
|
|
1538
|
+
"error"
|
|
1539
|
+
]);
|
|
1540
|
+
function coerceAutoloopTerminationReason(reason) {
|
|
1541
|
+
return AUTOLOOP_TERMINATION_REASON_SET.has(reason) ? reason : "max_iterations_reached";
|
|
1542
|
+
}
|
|
1543
|
+
function reconstructExhaustedAutoloopOutcome(input) {
|
|
1544
|
+
const successCompatibleReasons = /* @__PURE__ */ new Set([
|
|
1545
|
+
"success",
|
|
1546
|
+
"goal_satisfied",
|
|
1547
|
+
"heuristic_complete",
|
|
1548
|
+
"max_iterations_reached"
|
|
1549
|
+
]);
|
|
1550
|
+
if (!input.priorOutcome) {
|
|
1551
|
+
return {
|
|
1552
|
+
terminationReason: "max_iterations_reached",
|
|
1553
|
+
loopSuccess: false,
|
|
1554
|
+
finalProgressScore: input.finalProgressScore ?? 0,
|
|
1555
|
+
evidenceComplete: false
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
if (input.priorOutcome.loopSuccess !== true) {
|
|
1559
|
+
return {
|
|
1560
|
+
terminationReason: coerceAutoloopTerminationReason(input.priorOutcome.terminationReason),
|
|
1561
|
+
loopSuccess: false,
|
|
1562
|
+
finalProgressScore: input.priorOutcome.finalProgressScore,
|
|
1563
|
+
evidenceComplete: true
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
if (input.priorOutcome.validationPassed !== true || !successCompatibleReasons.has(input.priorOutcome.terminationReason)) {
|
|
1567
|
+
return {
|
|
1568
|
+
terminationReason: coerceAutoloopTerminationReason(input.priorOutcome.terminationReason),
|
|
1569
|
+
loopSuccess: false,
|
|
1570
|
+
finalProgressScore: input.priorOutcome.finalProgressScore,
|
|
1571
|
+
evidenceComplete: false
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
return {
|
|
1575
|
+
terminationReason: coerceAutoloopTerminationReason(input.priorOutcome.terminationReason),
|
|
1576
|
+
loopSuccess: true,
|
|
1577
|
+
finalProgressScore: input.priorOutcome.finalProgressScore,
|
|
1578
|
+
evidenceComplete: true
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
var GATE_TYPE_ORDER = {
|
|
1582
|
+
lint: 0,
|
|
1583
|
+
typecheck: 1,
|
|
1584
|
+
unit: 2,
|
|
1585
|
+
integration: 3,
|
|
1586
|
+
build: 4,
|
|
1587
|
+
other: 5
|
|
1588
|
+
};
|
|
1589
|
+
function classifyGateType(command) {
|
|
1590
|
+
const descriptor = describeVerificationCommand(command);
|
|
1591
|
+
if (descriptor.kind === "lint" || descriptor.kind === "typecheck" || descriptor.kind === "build") {
|
|
1592
|
+
return descriptor.kind;
|
|
1593
|
+
}
|
|
1594
|
+
if (descriptor.kind === "test") {
|
|
1595
|
+
return /\b(?:integration|e2e|playwright|cypress)\b/i.test(command) ? "integration" : "unit";
|
|
1596
|
+
}
|
|
1597
|
+
return "other";
|
|
1598
|
+
}
|
|
1599
|
+
function classifyAutoloopGateTypeForTest(command) {
|
|
1600
|
+
return classifyGateType(command);
|
|
1601
|
+
}
|
|
1602
|
+
function orderGatesByType(gates) {
|
|
1603
|
+
return [...gates].sort((a, b) => {
|
|
1604
|
+
const orderA = GATE_TYPE_ORDER[classifyGateType(a.command)];
|
|
1605
|
+
const orderB = GATE_TYPE_ORDER[classifyGateType(b.command)];
|
|
1606
|
+
return orderA !== orderB ? orderA - orderB : gates.indexOf(a) - gates.indexOf(b);
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
async function resolveGatePreset(input) {
|
|
1610
|
+
const { configuredPreset, escalation, changedFiles, repairAttempts, failedGateIds } = input;
|
|
1611
|
+
const basePreset = configuredPreset ?? "fast";
|
|
1612
|
+
if (!escalation || basePreset === "full") {
|
|
1613
|
+
return { preset: basePreset };
|
|
1614
|
+
}
|
|
1615
|
+
const reasons = [];
|
|
1616
|
+
if (escalation.escalationPaths && escalation.escalationPaths.length > 0 && changedFiles.length > 0) {
|
|
1617
|
+
try {
|
|
1618
|
+
const minimatchModule = await import("minimatch");
|
|
1619
|
+
const minimatch = typeof minimatchModule.minimatch === "function" ? minimatchModule.minimatch : typeof minimatchModule.default === "function" ? minimatchModule.default : void 0;
|
|
1620
|
+
if (minimatch) {
|
|
1621
|
+
const escalationPatterns = escalation.escalationPaths;
|
|
1622
|
+
const matchedFile = changedFiles.find(
|
|
1623
|
+
(file) => escalationPatterns.some((pattern) => minimatch(file, pattern))
|
|
1624
|
+
);
|
|
1625
|
+
if (matchedFile) {
|
|
1626
|
+
reasons.push(`path match: ${matchedFile}`);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
} catch {
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
const repairThreshold = escalation.escalationRepairThreshold ?? 2;
|
|
1633
|
+
if (repairAttempts >= repairThreshold) {
|
|
1634
|
+
reasons.push(`repair attempts (${repairAttempts}) >= threshold (${repairThreshold})`);
|
|
1635
|
+
}
|
|
1636
|
+
if (escalation.baselineRegressionGateIds && escalation.baselineRegressionGateIds.length > 0) {
|
|
1637
|
+
const baselineRegressionGateIds = escalation.baselineRegressionGateIds;
|
|
1638
|
+
const regressionMatch = failedGateIds.find((id) => baselineRegressionGateIds.includes(id));
|
|
1639
|
+
if (regressionMatch) {
|
|
1640
|
+
reasons.push(`baseline regression gate failed: ${regressionMatch}`);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
if (escalation.escalateOnTestChanges === true && changedFiles.length > 0) {
|
|
1644
|
+
const testFile = changedFiles.find((file) => isCreatedTargetTestFile(file));
|
|
1645
|
+
if (testFile) {
|
|
1646
|
+
reasons.push(`test file changed: ${testFile}`);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
if (reasons.length > 0) {
|
|
1650
|
+
return { preset: "full", escalationReason: reasons.join(", ") };
|
|
1651
|
+
}
|
|
1652
|
+
return { preset: basePreset };
|
|
1653
|
+
}
|
|
1654
|
+
function filterGatesByPreset(gates, preset) {
|
|
1655
|
+
if (preset === "full") return gates;
|
|
1656
|
+
return gates.filter((gate) => {
|
|
1657
|
+
const gateType = classifyGateType(gate.command);
|
|
1658
|
+
if (preset === "fast") {
|
|
1659
|
+
return gateType === "lint" || gateType === "typecheck";
|
|
1660
|
+
}
|
|
1661
|
+
return gateType === "lint" || gateType === "typecheck" || gateType === "unit";
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
var ZERO_TIME_METRICS = {
|
|
1665
|
+
totalProviderMs: 0,
|
|
1666
|
+
totalGateMs: 0,
|
|
1667
|
+
totalRepairMs: 0,
|
|
1668
|
+
totalTestMs: 0,
|
|
1669
|
+
totalBuildMs: 0,
|
|
1670
|
+
totalOverheadMs: 0,
|
|
1671
|
+
avgIterationMs: 0,
|
|
1672
|
+
noOpIterationCount: 0,
|
|
1673
|
+
providerTimePct: 0,
|
|
1674
|
+
deterministicTimePct: 0
|
|
1675
|
+
};
|
|
1676
|
+
function buildAutoloopRunSummary(ctx, result, options) {
|
|
1677
|
+
const timeMetrics = result.aggregateTimeMetrics ?? ZERO_TIME_METRICS;
|
|
1678
|
+
return {
|
|
1679
|
+
runId: options.runId,
|
|
1680
|
+
taskId: ctx.workflowId ?? ctx.stage.id,
|
|
1681
|
+
stageId: ctx.stage.id,
|
|
1682
|
+
startedAt: options.startedAt,
|
|
1683
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1684
|
+
durationMs: result.durationMs ?? 0,
|
|
1685
|
+
totalIterations: result.totalIterations,
|
|
1686
|
+
terminationReason: result.terminationReason,
|
|
1687
|
+
finalProgressScore: result.finalProgressScore,
|
|
1688
|
+
success: result.loopSuccess,
|
|
1689
|
+
outcome: result.outcomeMetadata?.outcome ?? "red",
|
|
1690
|
+
terminationClass: result.outcomeMetadata?.terminationClass,
|
|
1691
|
+
totalCost: result.cost ?? 0,
|
|
1692
|
+
costUnavailableReason: result.costUnavailableReason,
|
|
1693
|
+
timeMetrics,
|
|
1694
|
+
provider: resolveFlywheelStageProvider(ctx.stage, ctx.worktreePath),
|
|
1695
|
+
model: ctx.stage.model,
|
|
1696
|
+
gatePreset: options.gatePreset,
|
|
1697
|
+
noOpIterationCount: options.noOpIterationCount,
|
|
1698
|
+
repairAttempts: options.repairAttempts,
|
|
1699
|
+
repairSuccesses: options.repairSuccesses,
|
|
1700
|
+
aiValidationShortCircuited: options.aiValidationShortCircuited,
|
|
1701
|
+
policyEvents: result.policyEvents
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
function firstCostUnavailableReason(records) {
|
|
1705
|
+
return records.find((record) => record.costUnavailableReason)?.costUnavailableReason;
|
|
1706
|
+
}
|
|
1707
|
+
function formatAutoloopIterationCost(record) {
|
|
1708
|
+
if (record.costUnavailableReason === "provider_not_reported") {
|
|
1709
|
+
return "cost not reported";
|
|
1710
|
+
}
|
|
1711
|
+
if (record.costUnavailableReason === "pricing_unavailable") {
|
|
1712
|
+
return "cost unavailable";
|
|
1713
|
+
}
|
|
1714
|
+
return `$${record.cost.toFixed(4)}`;
|
|
1715
|
+
}
|
|
1716
|
+
async function finalizeAutoloopRun(ctx, result, summary, options = {}) {
|
|
1717
|
+
const now = Date.now();
|
|
1718
|
+
const writer = `flywheel-autoloop:${ctx.stage.id}`;
|
|
1719
|
+
const updateHistoryPersistenceBestEffort = async (history) => {
|
|
1720
|
+
if (!ctx.stateManager || !ctx.workflowId) {
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
try {
|
|
1724
|
+
const state = await ctx.stateManager.load(ctx.workflowId);
|
|
1725
|
+
const current = state.autoloopFinal;
|
|
1726
|
+
if (!current) {
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
await ctx.stateManager.recordAutoloopFinal(
|
|
1730
|
+
ctx.workflowId,
|
|
1731
|
+
{
|
|
1732
|
+
...current,
|
|
1733
|
+
persistence: {
|
|
1734
|
+
...current.persistence,
|
|
1735
|
+
history
|
|
1736
|
+
}
|
|
1737
|
+
},
|
|
1738
|
+
writer
|
|
1739
|
+
);
|
|
1740
|
+
} catch (err) {
|
|
1741
|
+
console.warn(
|
|
1742
|
+
`[Autoloop] State persistence update failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
if (ctx.stateManager && ctx.workflowId) {
|
|
1747
|
+
const marker = {
|
|
1748
|
+
completedAt: now,
|
|
1749
|
+
terminationReason: result.terminationReason,
|
|
1750
|
+
success: result.loopSuccess,
|
|
1751
|
+
totalIterations: result.totalIterations,
|
|
1752
|
+
summary,
|
|
1753
|
+
persistence: {
|
|
1754
|
+
history: {
|
|
1755
|
+
written: false,
|
|
1756
|
+
lastAttemptAt: now,
|
|
1757
|
+
lastError: void 0
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
};
|
|
1761
|
+
try {
|
|
1762
|
+
await ctx.stateManager.recordAutoloopFinal(ctx.workflowId, marker, writer);
|
|
1763
|
+
} catch (err) {
|
|
1764
|
+
console.warn(
|
|
1765
|
+
`[Autoloop] State persistence failed (canonical summary not written): ${err instanceof Error ? err.message : String(err)}`
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
try {
|
|
1770
|
+
const projectRoot = resolvePoeticRoot(ctx.worktreePath);
|
|
1771
|
+
const historyPath = path5.join(projectRoot, ".poetic", "telemetry", "autoloop-history.jsonl");
|
|
1772
|
+
await withPlanningLock(projectRoot, async () => {
|
|
1773
|
+
await fs4.mkdir(path5.dirname(historyPath), { recursive: true });
|
|
1774
|
+
await fs4.appendFile(historyPath, `${JSON.stringify(summary)}
|
|
1775
|
+
`, "utf8");
|
|
1776
|
+
});
|
|
1777
|
+
await updateHistoryPersistenceBestEffort({
|
|
1778
|
+
written: true,
|
|
1779
|
+
lastAttemptAt: now,
|
|
1780
|
+
lastError: void 0
|
|
1781
|
+
});
|
|
1782
|
+
} catch (err) {
|
|
1783
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1784
|
+
console.warn(`[Autoloop] History persistence failed: ${errorMsg}`);
|
|
1785
|
+
await updateHistoryPersistenceBestEffort({
|
|
1786
|
+
written: false,
|
|
1787
|
+
lastAttemptAt: now,
|
|
1788
|
+
lastError: errorMsg
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
try {
|
|
1792
|
+
await persistAutoloopTrajectory({
|
|
1793
|
+
workflowId: ctx.workflowId,
|
|
1794
|
+
stage: ctx.stage,
|
|
1795
|
+
result,
|
|
1796
|
+
summary
|
|
1797
|
+
});
|
|
1798
|
+
} catch (err) {
|
|
1799
|
+
console.warn(
|
|
1800
|
+
`[Autoloop] Trajectory persistence failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1803
|
+
let diagnosticsPersisted = options.preserveDurableDiagnostics === true;
|
|
1804
|
+
if (ctx.workflowId && !options.preserveDurableDiagnostics) {
|
|
1805
|
+
try {
|
|
1806
|
+
const worktreeFingerprint = await computeWorktreeFingerprint(ctx.worktreePath);
|
|
1807
|
+
const { canonicalPersisted } = await persistDurableAutoloopDiagnostics({
|
|
1808
|
+
workflowId: ctx.workflowId,
|
|
1809
|
+
stage: ctx.stage,
|
|
1810
|
+
result,
|
|
1811
|
+
worktreeFingerprint
|
|
1812
|
+
});
|
|
1813
|
+
diagnosticsPersisted = canonicalPersisted;
|
|
1814
|
+
} catch (err) {
|
|
1815
|
+
console.warn(
|
|
1816
|
+
`[Autoloop] Durable diagnostics persistence failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
if (ctx.workflowId && diagnosticsPersisted) {
|
|
1821
|
+
await clearDurableAutoloopIterationSpill({
|
|
1822
|
+
workflowId: ctx.workflowId,
|
|
1823
|
+
stageId: ctx.stage.id
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
async function computeEvidencePlanFromPrompt(input) {
|
|
1828
|
+
const [{ DomainAnalyzer }, { resolveEvidencePlan }] = await Promise.all([
|
|
1829
|
+
import("./domain-analyzer-AWF3RP6E.js"),
|
|
1830
|
+
import("./evidence-plan-resolver-GBSYSAPX.js")
|
|
1831
|
+
]);
|
|
1832
|
+
const analyzer = new DomainAnalyzer();
|
|
1833
|
+
const analysis = await analyzer.analyze(input.prompt);
|
|
1834
|
+
return resolveEvidencePlan(analysis, input.prompt, input.cwd);
|
|
1835
|
+
}
|
|
1836
|
+
function buildRecoveryPrompt(input) {
|
|
1837
|
+
const { config, iteration, lastDurationMs, consecutiveNoOps } = input;
|
|
1838
|
+
if (config.recoveryPromptTemplate) {
|
|
1839
|
+
return config.recoveryPromptTemplate.replace("{iteration}", String(iteration)).replace("{lastDuration}", String(Math.round(lastDurationMs / 1e3))).replace("{consecutiveCount}", String(consecutiveNoOps));
|
|
1840
|
+
}
|
|
1841
|
+
return `
|
|
1842
|
+
## Recovery Notice
|
|
1843
|
+
|
|
1844
|
+
This is iteration ${iteration}. The previous ${consecutiveNoOps} iteration(s) made no changes to the codebase.
|
|
1845
|
+
|
|
1846
|
+
Please take a different approach:
|
|
1847
|
+
1. Review the task requirements carefully
|
|
1848
|
+
2. Consider if you're blocked by something not mentioned
|
|
1849
|
+
3. Try a minimal first step to verify you can make changes
|
|
1850
|
+
4. If truly stuck, emit the completion signal with a note about what's blocking progress
|
|
1851
|
+
|
|
1852
|
+
Last iteration took ${Math.round(lastDurationMs / 1e3)}s without producing any file changes.
|
|
1853
|
+
`.trim();
|
|
1854
|
+
}
|
|
1855
|
+
function isAutoloopStage(stage) {
|
|
1856
|
+
return stage.type === "autoloop";
|
|
1857
|
+
}
|
|
1858
|
+
function resolveAutoloopWorkspaceMode(provider, executionIntent) {
|
|
1859
|
+
if (executionIntent !== "code" && executionIntent !== "mixed") return void 0;
|
|
1860
|
+
const detection = getProviderDetectionMode(provider);
|
|
1861
|
+
if (!detection.emitsJsonl) return void 0;
|
|
1862
|
+
return "enhanced";
|
|
1863
|
+
}
|
|
1864
|
+
async function detectNonEmptyEditsJsonl(worktreePath) {
|
|
1865
|
+
try {
|
|
1866
|
+
const stat3 = await fs4.stat(path5.join(worktreePath, ".poetic", "edits.jsonl"));
|
|
1867
|
+
if (!stat3.isFile()) return null;
|
|
1868
|
+
if (stat3.size <= 0) return null;
|
|
1869
|
+
return { bytes: stat3.size };
|
|
1870
|
+
} catch {
|
|
1871
|
+
return null;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
async function executeAutoloopStage(ctx) {
|
|
1875
|
+
const startTime = performance2.now();
|
|
1876
|
+
const invocationStartedAtMs = Date.now();
|
|
1877
|
+
let result;
|
|
1878
|
+
let runSummary;
|
|
1879
|
+
let executionError;
|
|
1880
|
+
const iterationHistory = [];
|
|
1881
|
+
const policyRecorder = createAutoloopEventRecorder();
|
|
1882
|
+
let runStartedAtMsForSummary = invocationStartedAtMs;
|
|
1883
|
+
let previousIterationCountForResult = 0;
|
|
1884
|
+
let resumedFromTerminalDiagnostics = false;
|
|
1885
|
+
let priorTerminalCostForResult;
|
|
1886
|
+
const repairReviewWarnings = /* @__PURE__ */ new Set();
|
|
1887
|
+
let iterationHistoryArtifactPersisted = false;
|
|
1888
|
+
let resultArtifactPersisted = false;
|
|
1889
|
+
let failureArtifactPersisted = false;
|
|
1890
|
+
const pendingIterationRecords = [];
|
|
1891
|
+
const iterationFingerprints = /* @__PURE__ */ new Map();
|
|
1892
|
+
try {
|
|
1893
|
+
const config = ctx.stage.autoloop ?? {};
|
|
1894
|
+
const repairScopeBaseline = ctx.stage.automaticRepair === true ? ctx.repairScopeBaseline ?? await captureAutomaticRepairScopeBaseline(ctx.worktreePath) : void 0;
|
|
1895
|
+
let promptLanguage = null;
|
|
1896
|
+
try {
|
|
1897
|
+
promptLanguage = selectPrimaryPromptLanguage(
|
|
1898
|
+
detectProjectCapabilities(ctx.worktreePath).languages
|
|
1899
|
+
);
|
|
1900
|
+
} catch {
|
|
1901
|
+
promptLanguage = null;
|
|
1902
|
+
}
|
|
1903
|
+
const {
|
|
1904
|
+
AutonomousLoopController,
|
|
1905
|
+
createDurationMs,
|
|
1906
|
+
createIterationCount,
|
|
1907
|
+
createProgressScore,
|
|
1908
|
+
matchesCompletionSignal,
|
|
1909
|
+
parseCompletionSignal
|
|
1910
|
+
} = await import("./autonomous-loop-controller-PW4XVFGQ.js");
|
|
1911
|
+
const log = (msg) => {
|
|
1912
|
+
if (ctx.verbose) console.log(chalk.gray(` [${ctx.stage.id}] ${msg}`));
|
|
1913
|
+
};
|
|
1914
|
+
const maxIterations = config.maxIterations ?? 3;
|
|
1915
|
+
const { resolveAutoloopTimeboxMs, DEFAULT_AUTOLOOP_VALIDATION_MODE } = await import("./autoloop-run-defaults-FERGRZGP.js");
|
|
1916
|
+
const timeboxMs = resolveAutoloopTimeboxMs(config.timeboxMs);
|
|
1917
|
+
const minProgress = config.minProgress ?? 0.05;
|
|
1918
|
+
const validationMode = config.validationMode === "heuristic" || config.validationMode === "none" || config.validationMode === "deterministic" ? config.validationMode : DEFAULT_AUTOLOOP_VALIDATION_MODE;
|
|
1919
|
+
const iterationTimeboxMs = config.iterationTimeboxMs;
|
|
1920
|
+
const requireEarlyDiff = config.requireEarlyDiff ?? true;
|
|
1921
|
+
let completionSignal = validationMode === "deterministic" ? null : typeof config.completionSignal === "string" && config.completionSignal.length > 0 ? parseCompletionSignal(config.completionSignal) : null;
|
|
1922
|
+
let completionSignalXmlTag = completionSignal?.type === "xml" ? completionSignal.tag : void 0;
|
|
1923
|
+
const backoffMaxRetries = (() => {
|
|
1924
|
+
const raw = config.backoffMaxRetries;
|
|
1925
|
+
if (raw === void 0) return 0;
|
|
1926
|
+
const n = Number(raw);
|
|
1927
|
+
if (!Number.isFinite(n)) return 0;
|
|
1928
|
+
return Math.max(0, Math.floor(n));
|
|
1929
|
+
})();
|
|
1930
|
+
let rawGateCommands = [];
|
|
1931
|
+
let evidencePlanUsed = false;
|
|
1932
|
+
let validationSourceExplanation = "";
|
|
1933
|
+
const explicitValidationConfigured = Array.isArray(config.gates) && config.gates.length > 0 || Array.isArray(config.qualityGateCommands) && config.qualityGateCommands.length > 0 || config.stopOnQualityGate === true;
|
|
1934
|
+
const simpleArtifactGoal = parseSimpleArtifactGoal(ctx.stage.prompt ?? "");
|
|
1935
|
+
const explicitVerificationCommands = extractExplicitVerificationCommands(
|
|
1936
|
+
ctx.stage.prompt ?? ""
|
|
1937
|
+
);
|
|
1938
|
+
const explicitVerificationIntent = hasExplicitVerificationIntent(ctx.stage.prompt ?? "") || explicitVerificationCommands.length > 0;
|
|
1939
|
+
const shouldSuppressInferredGatesForSimpleArtifact = simpleArtifactGoal !== void 0 && !explicitValidationConfigured && !explicitVerificationIntent;
|
|
1940
|
+
const shouldComputeEvidencePlan = !explicitValidationConfigured && !shouldSuppressInferredGatesForSimpleArtifact;
|
|
1941
|
+
let evidencePlan = ctx.evidencePlan;
|
|
1942
|
+
if (!evidencePlan && shouldComputeEvidencePlan) {
|
|
1943
|
+
try {
|
|
1944
|
+
const basePrompt = ctx.stage.prompt ?? "";
|
|
1945
|
+
if (basePrompt.trim().length > 0) {
|
|
1946
|
+
evidencePlan = await computeEvidencePlanFromPrompt({
|
|
1947
|
+
prompt: basePrompt,
|
|
1948
|
+
cwd: ctx.worktreePath
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1951
|
+
} catch (err) {
|
|
1952
|
+
if (ctx.verbose) {
|
|
1953
|
+
log(
|
|
1954
|
+
`Evidence plan unavailable (skipping): ${err instanceof Error ? err.message : String(err)}`
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
if (Array.isArray(config.gates) && config.gates.length > 0) {
|
|
1960
|
+
validationSourceExplanation = "explicit autoloop.gates config";
|
|
1961
|
+
} else if (Array.isArray(config.qualityGateCommands) && config.qualityGateCommands.length > 0) {
|
|
1962
|
+
rawGateCommands = config.qualityGateCommands;
|
|
1963
|
+
validationSourceExplanation = "explicit autoloop.qualityGateCommands config";
|
|
1964
|
+
} else if (config.stopOnQualityGate === true) {
|
|
1965
|
+
rawGateCommands = ctx.stage.verify?.commands ?? [];
|
|
1966
|
+
validationSourceExplanation = "stage verify.commands";
|
|
1967
|
+
} else if (explicitVerificationCommands.length > 0) {
|
|
1968
|
+
rawGateCommands = explicitVerificationCommands;
|
|
1969
|
+
validationSourceExplanation = "explicit verification commands from prompt";
|
|
1970
|
+
} else if (evidencePlan && !shouldSuppressInferredGatesForSimpleArtifact) {
|
|
1971
|
+
rawGateCommands = evidencePlan.surfaces.flatMap((surface) => surface.commands);
|
|
1972
|
+
evidencePlanUsed = true;
|
|
1973
|
+
validationSourceExplanation = `evidence plan (${evidencePlan.surfaces.length} surface(s))`;
|
|
1974
|
+
if (ctx.verbose) {
|
|
1975
|
+
log(
|
|
1976
|
+
`Using validation surface from evidence plan (${evidencePlan.surfaces.length} surface(s))`
|
|
1977
|
+
);
|
|
1978
|
+
for (const surface of evidencePlan.surfaces) {
|
|
1979
|
+
log(` - ${surface.description} (source: ${surface.source})`);
|
|
1980
|
+
if (surface.rationale && surface.rationale.length > 0) {
|
|
1981
|
+
for (const reason of surface.rationale) {
|
|
1982
|
+
log(` \u2022 ${reason}`);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
if (evidencePlan.isFallback) {
|
|
1987
|
+
log("Evidence plan is fallback (minimal safe checks)");
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
const shouldDefaultGateOrdering = (!Array.isArray(config.qualityGateCommands) || config.qualityGateCommands.length === 0) && (!Array.isArray(config.gates) || config.gates.length === 0) && !evidencePlanUsed;
|
|
1992
|
+
const gatesFromConfig = Array.isArray(config.gates) && config.gates.length > 0 ? normalizeGates(config.gates) : null;
|
|
1993
|
+
const rawGatesFromCommands = rawGateCommands.length > 0 ? normalizeGates(rawGateCommands) : [];
|
|
1994
|
+
const defaultGatesFromCommands = shouldDefaultGateOrdering && rawGatesFromCommands.length > 1 ? orderGatesByType(rawGatesFromCommands) : rawGatesFromCommands;
|
|
1995
|
+
const gatesConfigured = (gatesFromConfig ?? defaultGatesFromCommands).map(
|
|
1996
|
+
(g, idx) => ({
|
|
1997
|
+
...g,
|
|
1998
|
+
id: g.id || `gate-${idx + 1}`,
|
|
1999
|
+
repair: g.repair ?? (g.deterministic !== false && g.mustPass !== false ? { maxAttempts: 2 } : void 0)
|
|
2000
|
+
})
|
|
2001
|
+
);
|
|
2002
|
+
const qualityGatePolicy = config.qualityGatePolicy ?? "deterministic";
|
|
2003
|
+
const { gatesToRun, filteredGates } = qualityGatePolicy === "deterministic" ? filterDeterministicGates(gatesConfigured) : { gatesToRun: gatesConfigured, filteredGates: [] };
|
|
2004
|
+
const gatesEnabled = gatesToRun.length > 0;
|
|
2005
|
+
const durableResumeState = ctx.workflowId !== void 0 ? await loadDurableAutoloopResumeState({
|
|
2006
|
+
workflowId: ctx.workflowId,
|
|
2007
|
+
stageId: ctx.stage.id,
|
|
2008
|
+
// Verify the worktree still holds the prior iterations' work before
|
|
2009
|
+
// trusting the count; on mismatch this resets to 0 (re-run from
|
|
2010
|
+
// scratch) rather than silently under-iterating against a fresh tree.
|
|
2011
|
+
worktreePath: ctx.worktreePath
|
|
2012
|
+
}) : null;
|
|
2013
|
+
if (durableResumeState?.fingerprintMismatch) {
|
|
2014
|
+
log(
|
|
2015
|
+
"Warning: prior autoloop state does not match the current worktree (HEAD or working-tree changes differ); re-running from iteration 0."
|
|
2016
|
+
);
|
|
2017
|
+
}
|
|
2018
|
+
const previousIterationCount = durableResumeState?.completedIterations ?? 0;
|
|
2019
|
+
previousIterationCountForResult = previousIterationCount;
|
|
2020
|
+
const remainingIterations = Math.max(0, maxIterations - previousIterationCount);
|
|
2021
|
+
const hasTerminalDurableOutcome = durableResumeState?.source === "diagnostics" && durableResumeState.priorOutcome !== void 0;
|
|
2022
|
+
resumedFromTerminalDiagnostics = hasTerminalDurableOutcome;
|
|
2023
|
+
priorTerminalCostForResult = durableResumeState?.priorOutcome?.cost;
|
|
2024
|
+
const wallClockRunStartMs = durableResumeState?.runStartedAtMs ?? invocationStartedAtMs;
|
|
2025
|
+
runStartedAtMsForSummary = wallClockRunStartMs;
|
|
2026
|
+
const elapsedLoopMs = () => Math.max(0, Date.now() - wallClockRunStartMs, performance2.now() - startTime);
|
|
2027
|
+
log(
|
|
2028
|
+
`Starting autoloop (maxIterations: ${maxIterations}, timeboxMs: ${timeboxMs}, previousIterations: ${previousIterationCount})`
|
|
2029
|
+
);
|
|
2030
|
+
if (validationSourceExplanation && ctx.verbose) {
|
|
2031
|
+
log(`Validation source: ${validationSourceExplanation}`);
|
|
2032
|
+
}
|
|
2033
|
+
{
|
|
2034
|
+
const provider = resolveFlywheelStageProvider(ctx.stage, ctx.worktreePath);
|
|
2035
|
+
const executionIntent = resolveStageExecutionIntent(ctx.stage);
|
|
2036
|
+
const detection = getProviderDetectionMode(provider);
|
|
2037
|
+
if (executionIntent === "edit") {
|
|
2038
|
+
log(
|
|
2039
|
+
detection.emitsJsonl ? `Workspace mode: enhanced (provider '${provider}' supports JSONL edit contract)` : `Workspace mode: passthrough (provider '${provider}' is git-only; JSONL protocol disabled)`
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
if (typeof config.completionSignal === "string" && config.completionSignal.length > 0) {
|
|
2044
|
+
if (validationMode === "deterministic") {
|
|
2045
|
+
log("Completion signal ignored in deterministic mode");
|
|
2046
|
+
} else if (!completionSignal) {
|
|
2047
|
+
log(`Invalid completionSignal: ${config.completionSignal}`);
|
|
2048
|
+
} else {
|
|
2049
|
+
log(`Completion signal enabled (${completionSignal.type})`);
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
if (validationMode !== "heuristic") {
|
|
2053
|
+
log(`Validation mode: ${validationMode}`);
|
|
2054
|
+
}
|
|
2055
|
+
if (gatesEnabled && filteredGates.length > 0) {
|
|
2056
|
+
log(
|
|
2057
|
+
`Filtered ${filteredGates.length} non-deterministic gate(s) (policy: ${qualityGatePolicy})`
|
|
2058
|
+
);
|
|
2059
|
+
if (ctx.verbose) {
|
|
2060
|
+
for (const g of filteredGates.slice(0, 6)) log(` - ${g.command}`);
|
|
2061
|
+
if (filteredGates.length > 6) log(` ... and ${filteredGates.length - 6} more`);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
if (Array.isArray(config.qualityGateCommands) && config.qualityGateCommands.length > 0 || config.stopOnQualityGate === true || Array.isArray(config.gates) && config.gates.length > 0) {
|
|
2065
|
+
if (!gatesEnabled) {
|
|
2066
|
+
log(
|
|
2067
|
+
"Quality gates enabled but no deterministic gates were available; check autoloop.gates/qualityGateCommands and policy."
|
|
2068
|
+
);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
if (validationMode !== "deterministic" && config.completionOnQualityGatePass === true && !completionSignal) {
|
|
2072
|
+
const { createAutoloopCompletionToken } = await import("./autoloop-helpers-2KTK43GL.js");
|
|
2073
|
+
const generated = createAutoloopCompletionToken({});
|
|
2074
|
+
completionSignal = parseCompletionSignal(generated.completionSignal);
|
|
2075
|
+
completionSignalXmlTag = completionSignal?.type === "xml" ? completionSignal.tag : void 0;
|
|
2076
|
+
log(
|
|
2077
|
+
`completionOnQualityGatePass: auto-generated completion signal (${completionSignalXmlTag ?? "none"})`
|
|
2078
|
+
);
|
|
2079
|
+
}
|
|
2080
|
+
const dynamicGates = [];
|
|
2081
|
+
let totalTokens = 0;
|
|
2082
|
+
let finalProgressScore = 0;
|
|
2083
|
+
const configuredGatePreset = config.gatePreset;
|
|
2084
|
+
const gateEscalation = config.gateEscalation;
|
|
2085
|
+
const noOpIntervention = config.noOpIntervention;
|
|
2086
|
+
let consecutiveNoOps = 0;
|
|
2087
|
+
let interventionActive = false;
|
|
2088
|
+
let lastIterationDurationMs = 0;
|
|
2089
|
+
let totalRepairAttemptsForEscalation = 0;
|
|
2090
|
+
const failedGateIdsForEscalation = [];
|
|
2091
|
+
let currentGatePreset = configuredGatePreset ?? "fast";
|
|
2092
|
+
let aiValidationShortCircuited = false;
|
|
2093
|
+
const gateCommandsForPrompt = () => [...gatesToRun, ...dynamicGates].map((g) => g.command);
|
|
2094
|
+
const loopController = new AutonomousLoopController({
|
|
2095
|
+
maxIterations: createIterationCount(Math.max(1, remainingIterations)),
|
|
2096
|
+
timeboxMs: createDurationMs(timeboxMs),
|
|
2097
|
+
minProgressThreshold: createProgressScore(minProgress),
|
|
2098
|
+
validationMode,
|
|
2099
|
+
...completionSignal ? { completionSignal } : {},
|
|
2100
|
+
requirementsTaskText: ctx.stage.prompt ?? "",
|
|
2101
|
+
requirements: {
|
|
2102
|
+
mode: "auto",
|
|
2103
|
+
minCoverage: 1,
|
|
2104
|
+
requireEvidence: true,
|
|
2105
|
+
blockNewRepoRootMarkdown: true
|
|
2106
|
+
},
|
|
2107
|
+
wallClockRunStartMs,
|
|
2108
|
+
resourceLimits: FLYWHEEL_AUTOLOOP_CONTROLLER_RESOURCE_LIMITS,
|
|
2109
|
+
// Flywheel runs quality gates in this executor so we can:
|
|
2110
|
+
// - use stage-specific commands (matching verify)
|
|
2111
|
+
// - feed failures back into subsequent iteration prompts
|
|
2112
|
+
runQualityGates: false,
|
|
2113
|
+
backoffConfig: {
|
|
2114
|
+
initialDelayMs: createDurationMs(0),
|
|
2115
|
+
maxDelayMs: createDurationMs(0),
|
|
2116
|
+
multiplier: 1,
|
|
2117
|
+
maxRetries: backoffMaxRetries
|
|
2118
|
+
},
|
|
2119
|
+
progressConfig: {
|
|
2120
|
+
windowSize: 3,
|
|
2121
|
+
trendThreshold: 0.01,
|
|
2122
|
+
confidenceThreshold: 0.7,
|
|
2123
|
+
// Compare working tree against the stage HEAD so uncommitted changes count as progress.
|
|
2124
|
+
baseRef: "HEAD"
|
|
2125
|
+
}
|
|
2126
|
+
});
|
|
2127
|
+
loopController.setWorkingDir(ctx.worktreePath);
|
|
2128
|
+
const flywheelInitialPolicy = prepareInitialAutoloopPolicy({
|
|
2129
|
+
taskText: ctx.stage.prompt ?? "",
|
|
2130
|
+
userProvidedValidationMode: validationMode,
|
|
2131
|
+
userProvidedAIJudge: false
|
|
2132
|
+
});
|
|
2133
|
+
loopController.setInitialPolicyLevel(flywheelInitialPolicy.level);
|
|
2134
|
+
let terminationReason = "success";
|
|
2135
|
+
let loopSuccess = false;
|
|
2136
|
+
let runtimeEvidenceCheck;
|
|
2137
|
+
if (ctx.workflowId && ctx.stateManager) {
|
|
2138
|
+
const writer = `flywheel-autoloop:${ctx.stage.id}`;
|
|
2139
|
+
loopController.on("iteration_complete", (payload) => {
|
|
2140
|
+
pendingIterationRecords.push(
|
|
2141
|
+
(async () => {
|
|
2142
|
+
const result2 = payload?.result;
|
|
2143
|
+
const iteration = typeof result2?._iteration === "number" ? result2._iteration : void 0;
|
|
2144
|
+
const score = typeof result2?.progress?.score === "number" ? result2.progress.score : void 0;
|
|
2145
|
+
if (iteration === void 0) return;
|
|
2146
|
+
const recordIdx = Math.max(0, iteration - 1);
|
|
2147
|
+
const durableIdx = previousIterationCount + recordIdx;
|
|
2148
|
+
const local = iterationHistory[recordIdx];
|
|
2149
|
+
if (local && score !== void 0) {
|
|
2150
|
+
local.progressScore = score;
|
|
2151
|
+
}
|
|
2152
|
+
const success = local ? local.success : typeof result2?.success === "boolean" ? result2.success : true;
|
|
2153
|
+
const durationMs2 = local ? local.durationMs : typeof result2?.duration === "number" ? result2.duration : 0;
|
|
2154
|
+
if (ctx.stateManager && ctx.workflowId) {
|
|
2155
|
+
const iterationRecord = {
|
|
2156
|
+
index: durableIdx,
|
|
2157
|
+
success,
|
|
2158
|
+
duration: Math.max(0, Math.round(durationMs2)),
|
|
2159
|
+
timestamp: Date.now(),
|
|
2160
|
+
...score !== void 0 ? { score } : {}
|
|
2161
|
+
};
|
|
2162
|
+
let spillWritten = false;
|
|
2163
|
+
try {
|
|
2164
|
+
const worktreeFingerprint = iterationFingerprints.get(iteration);
|
|
2165
|
+
await persistDurableAutoloopIterationSpill({
|
|
2166
|
+
workflowId: ctx.workflowId,
|
|
2167
|
+
stageId: ctx.stage.id,
|
|
2168
|
+
writer,
|
|
2169
|
+
record: iterationRecord,
|
|
2170
|
+
worktreeFingerprint,
|
|
2171
|
+
runStartedAtMs: wallClockRunStartMs
|
|
2172
|
+
});
|
|
2173
|
+
spillWritten = true;
|
|
2174
|
+
} catch (spillError) {
|
|
2175
|
+
console.warn(
|
|
2176
|
+
`[flywheel-autoloop] iteration spill persistence failed for ${ctx.stage.id} (iteration ${durableIdx}): ${spillError instanceof Error ? spillError.message : String(spillError)}`
|
|
2177
|
+
);
|
|
2178
|
+
}
|
|
2179
|
+
try {
|
|
2180
|
+
await ctx.stateManager.recordIteration(ctx.workflowId, iterationRecord, writer);
|
|
2181
|
+
} catch (error) {
|
|
2182
|
+
if (spillWritten) {
|
|
2183
|
+
console.warn(
|
|
2184
|
+
`[flywheel-autoloop] recordIteration failed for ${ctx.stage.id} (iteration ${durableIdx}; durable spill already written): ${error instanceof Error ? error.message : String(error)}`
|
|
2185
|
+
);
|
|
2186
|
+
} else {
|
|
2187
|
+
console.warn(
|
|
2188
|
+
`[flywheel-autoloop] recordIteration failed for ${ctx.stage.id} (iteration ${durableIdx}) AND durable spill was NOT written \u2014 this iteration is not durably recorded in either store and may be lost on resume: ${error instanceof Error ? error.message : String(error)}`
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
})()
|
|
2194
|
+
);
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
let previousIterationWorktreeFingerprint;
|
|
2198
|
+
try {
|
|
2199
|
+
previousIterationWorktreeFingerprint = await computeWorktreeFingerprint(ctx.worktreePath);
|
|
2200
|
+
} catch {
|
|
2201
|
+
previousIterationWorktreeFingerprint = void 0;
|
|
2202
|
+
}
|
|
2203
|
+
const captureIterationFingerprint = async (iteration) => {
|
|
2204
|
+
try {
|
|
2205
|
+
const current = await computeWorktreeFingerprint(ctx.worktreePath);
|
|
2206
|
+
iterationFingerprints.set(iteration, current);
|
|
2207
|
+
const prior = previousIterationWorktreeFingerprint;
|
|
2208
|
+
previousIterationWorktreeFingerprint = current;
|
|
2209
|
+
if (!prior || !current) return void 0;
|
|
2210
|
+
if (prior.headSha === current.headSha && prior.dirtyDigest === current.dirtyDigest) {
|
|
2211
|
+
return "";
|
|
2212
|
+
}
|
|
2213
|
+
return `${current.headSha}:${current.dirtyDigest}`;
|
|
2214
|
+
} catch {
|
|
2215
|
+
iterationFingerprints.set(iteration, void 0);
|
|
2216
|
+
previousIterationWorktreeFingerprint = void 0;
|
|
2217
|
+
return void 0;
|
|
2218
|
+
}
|
|
2219
|
+
};
|
|
2220
|
+
try {
|
|
2221
|
+
const finalIteration = hasTerminalDurableOutcome || remainingIterations === 0 ? 0 : await loopController.start(async (iteration) => {
|
|
2222
|
+
const effectiveIteration = previousIterationCount + iteration;
|
|
2223
|
+
const iterationStart = performance2.now();
|
|
2224
|
+
const timeBreakdown = {
|
|
2225
|
+
providerMs: 0,
|
|
2226
|
+
gateMs: 0,
|
|
2227
|
+
repairMs: 0,
|
|
2228
|
+
testMs: 0,
|
|
2229
|
+
buildMs: 0,
|
|
2230
|
+
overheadMs: 0
|
|
2231
|
+
};
|
|
2232
|
+
const recordEarlyFailure = async (message, terminationSignal) => {
|
|
2233
|
+
const changedFiles2 = (await getWorktreeChangedFiles(ctx.worktreePath)).files;
|
|
2234
|
+
const durationMs2 = performance2.now() - iterationStart;
|
|
2235
|
+
const record2 = {
|
|
2236
|
+
iteration: effectiveIteration,
|
|
2237
|
+
success: false,
|
|
2238
|
+
providerExecutionStatus: "failed",
|
|
2239
|
+
providerOutputStatus: "missing",
|
|
2240
|
+
providerStatusDetail: message,
|
|
2241
|
+
changedFiles: changedFiles2,
|
|
2242
|
+
cost: 0,
|
|
2243
|
+
durationMs: durationMs2,
|
|
2244
|
+
timeBreakdown: {
|
|
2245
|
+
...timeBreakdown,
|
|
2246
|
+
overheadMs: Math.max(0, durationMs2)
|
|
2247
|
+
},
|
|
2248
|
+
error: message,
|
|
2249
|
+
terminationSignal,
|
|
2250
|
+
policyEvents: policyRecorder.events.filter(
|
|
2251
|
+
(event) => event.iteration === effectiveIteration
|
|
2252
|
+
)
|
|
2253
|
+
};
|
|
2254
|
+
iterationHistory.push(record2);
|
|
2255
|
+
if (ctx.artifactStore) {
|
|
2256
|
+
await saveBestEffort(
|
|
2257
|
+
ctx.artifactStore,
|
|
2258
|
+
ctx.stage.id,
|
|
2259
|
+
`iteration-${effectiveIteration}`,
|
|
2260
|
+
record2
|
|
2261
|
+
);
|
|
2262
|
+
}
|
|
2263
|
+
return record2;
|
|
2264
|
+
};
|
|
2265
|
+
let basePromptWithIntervention = ctx.stage.prompt ?? "";
|
|
2266
|
+
if (interventionActive && noOpIntervention) {
|
|
2267
|
+
const recoveryPrompt = buildRecoveryPrompt({
|
|
2268
|
+
config: noOpIntervention,
|
|
2269
|
+
iteration: effectiveIteration,
|
|
2270
|
+
lastDurationMs: lastIterationDurationMs,
|
|
2271
|
+
consecutiveNoOps
|
|
2272
|
+
});
|
|
2273
|
+
basePromptWithIntervention = `${recoveryPrompt}
|
|
2274
|
+
|
|
2275
|
+
---
|
|
2276
|
+
|
|
2277
|
+
${basePromptWithIntervention}`;
|
|
2278
|
+
log(
|
|
2279
|
+
`Intervention active: injecting recovery prompt (${consecutiveNoOps} consecutive no-ops)`
|
|
2280
|
+
);
|
|
2281
|
+
}
|
|
2282
|
+
const prompt = buildIterationPrompt({
|
|
2283
|
+
basePrompt: basePromptWithIntervention,
|
|
2284
|
+
iteration: effectiveIteration,
|
|
2285
|
+
iterationHistory,
|
|
2286
|
+
gateCommands: gateCommandsForPrompt(),
|
|
2287
|
+
requiresTests: config.requiresTests,
|
|
2288
|
+
testFilePatterns: config.testFilePatterns,
|
|
2289
|
+
promptLanguage
|
|
2290
|
+
});
|
|
2291
|
+
log(`Iteration ${effectiveIteration}: executing provider...`);
|
|
2292
|
+
let providerResult;
|
|
2293
|
+
let providerCost = 0;
|
|
2294
|
+
let providerCostUnavailableReason;
|
|
2295
|
+
let providerTokens = 0;
|
|
2296
|
+
let providerError;
|
|
2297
|
+
let providerOutputText;
|
|
2298
|
+
let providerExecutionStatus;
|
|
2299
|
+
let providerOutputStatus;
|
|
2300
|
+
let providerStatusDetail;
|
|
2301
|
+
let providerHardExitFailure = false;
|
|
2302
|
+
let providerVerificationUnavailableRecovered = false;
|
|
2303
|
+
const providerStart = performance2.now();
|
|
2304
|
+
try {
|
|
2305
|
+
const elapsedLoopMsForProvider = elapsedLoopMs();
|
|
2306
|
+
const remainingLoopMs = Math.max(0, timeboxMs - elapsedLoopMsForProvider);
|
|
2307
|
+
if (remainingLoopMs <= 0) {
|
|
2308
|
+
const message = "Autoloop timebox exhausted before provider execution";
|
|
2309
|
+
policyRecorder.emit({
|
|
2310
|
+
policy: "timebox",
|
|
2311
|
+
eventType: "terminated",
|
|
2312
|
+
iteration: effectiveIteration,
|
|
2313
|
+
reason: message,
|
|
2314
|
+
details: {
|
|
2315
|
+
elapsedLoopMs: elapsedLoopMsForProvider,
|
|
2316
|
+
timeboxMs
|
|
2317
|
+
}
|
|
2318
|
+
});
|
|
2319
|
+
await recordEarlyFailure(message, "timebox_exceeded");
|
|
2320
|
+
throw new AutoloopTerminationError(message, "timebox_exceeded");
|
|
2321
|
+
}
|
|
2322
|
+
const perIterationCapMs = typeof iterationTimeboxMs === "number" && iterationTimeboxMs > 0 ? Math.min(remainingLoopMs, iterationTimeboxMs) : remainingLoopMs;
|
|
2323
|
+
providerResult = await executeProviderIteration({
|
|
2324
|
+
executor: ctx.executor,
|
|
2325
|
+
stage: ctx.stage,
|
|
2326
|
+
worktreePath: ctx.worktreePath,
|
|
2327
|
+
prompt,
|
|
2328
|
+
timeoutMs: perIterationCapMs,
|
|
2329
|
+
abortSignal: ctx.abortSignal,
|
|
2330
|
+
...ctx.providerExecutionTransport !== void 0 ? {
|
|
2331
|
+
defaultProviderExecutionTransport: ctx.providerExecutionTransport
|
|
2332
|
+
} : {}
|
|
2333
|
+
});
|
|
2334
|
+
const rawProviderExitCode = providerResult.result?.exitCode;
|
|
2335
|
+
providerHardExitFailure = typeof rawProviderExitCode === "number" && rawProviderExitCode !== 0;
|
|
2336
|
+
providerCostUnavailableReason = readCostMetricsUnavailableReason(
|
|
2337
|
+
providerResult.costMetrics
|
|
2338
|
+
);
|
|
2339
|
+
const reportedProviderCost = providerResult.costMetrics?.totalCost;
|
|
2340
|
+
if (providerCostUnavailableReason === void 0 && typeof reportedProviderCost === "number" && Number.isFinite(reportedProviderCost)) {
|
|
2341
|
+
providerCost = reportedProviderCost;
|
|
2342
|
+
}
|
|
2343
|
+
providerTokens = resolveTokenAccounting(providerResult.costMetrics).totalTokens;
|
|
2344
|
+
timeBreakdown.providerMs = performance2.now() - providerStart;
|
|
2345
|
+
const { selectProviderEngineStdout } = await import("./autoloop-helpers-2KTK43GL.js");
|
|
2346
|
+
const engineStdout = selectProviderEngineStdout(
|
|
2347
|
+
providerResult.result
|
|
2348
|
+
);
|
|
2349
|
+
const engineStderr = providerResult.result?.stderr ?? "";
|
|
2350
|
+
const stdoutText = typeof engineStdout === "string" ? engineStdout : String(engineStdout ?? "");
|
|
2351
|
+
const stderrText = typeof engineStderr === "string" ? engineStderr : String(engineStderr ?? "");
|
|
2352
|
+
const trimmed = buildProviderOutputExcerpt(
|
|
2353
|
+
resolveFlywheelStageProvider(ctx.stage, ctx.worktreePath),
|
|
2354
|
+
stdoutText,
|
|
2355
|
+
stderrText
|
|
2356
|
+
);
|
|
2357
|
+
if (trimmed.length > 0) {
|
|
2358
|
+
providerOutputText = trimmed;
|
|
2359
|
+
} else if (typeof providerResult.output === "string" && providerResult.output.length > 0) {
|
|
2360
|
+
providerOutputText = providerResult.output;
|
|
2361
|
+
} else if (providerResult.result !== void 0 && providerResult.result !== null) {
|
|
2362
|
+
try {
|
|
2363
|
+
providerOutputText = JSON.stringify(providerResult.result);
|
|
2364
|
+
} catch {
|
|
2365
|
+
providerOutputText = String(providerResult.result);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
const providerStatus = classifyAutoloopProviderStatus({
|
|
2369
|
+
providerResult,
|
|
2370
|
+
stdoutText,
|
|
2371
|
+
stderrText
|
|
2372
|
+
});
|
|
2373
|
+
providerExecutionStatus = providerStatus.executionStatus;
|
|
2374
|
+
providerOutputStatus = providerStatus.outputStatus;
|
|
2375
|
+
providerStatusDetail = providerStatus.detail;
|
|
2376
|
+
providerError = providerResult.success ? providerStatus.error : providerResult.error?.message ?? providerStatus.error;
|
|
2377
|
+
} catch (error) {
|
|
2378
|
+
if (error instanceof AutoloopTerminationError) {
|
|
2379
|
+
throw error;
|
|
2380
|
+
}
|
|
2381
|
+
timeBreakdown.providerMs = performance2.now() - providerStart;
|
|
2382
|
+
providerResult = {
|
|
2383
|
+
success: false,
|
|
2384
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
2385
|
+
};
|
|
2386
|
+
providerError = error instanceof Error ? error.message : String(error);
|
|
2387
|
+
providerExecutionStatus = "failed";
|
|
2388
|
+
providerOutputStatus = "missing";
|
|
2389
|
+
providerStatusDetail = providerError;
|
|
2390
|
+
}
|
|
2391
|
+
totalTokens += providerTokens;
|
|
2392
|
+
const allowlist = ctx.stage.allowlist;
|
|
2393
|
+
const allowlistMode2 = ctx.stage.allowlistMode ?? "warn";
|
|
2394
|
+
let allowlistViolations = [];
|
|
2395
|
+
let allowlistRevertedFiles;
|
|
2396
|
+
let allowlistDrift = false;
|
|
2397
|
+
let allowlistError;
|
|
2398
|
+
let changedFilesResult = await getWorktreeChangedFiles(ctx.worktreePath);
|
|
2399
|
+
let changedFiles = changedFilesResult.files;
|
|
2400
|
+
let simpleArtifactGoalSatisfied;
|
|
2401
|
+
if (!changedFilesResult.readable) {
|
|
2402
|
+
allowlistError = "Changed-file status unavailable: git status failed";
|
|
2403
|
+
providerError = providerError ?? allowlistError;
|
|
2404
|
+
}
|
|
2405
|
+
if (changedFiles.length === 0) {
|
|
2406
|
+
const provider = resolveFlywheelStageProvider(ctx.stage, ctx.worktreePath);
|
|
2407
|
+
const detection = getProviderDetectionMode(provider);
|
|
2408
|
+
if (!detection.emitsJsonl) {
|
|
2409
|
+
const jsonl = await detectNonEmptyEditsJsonl(ctx.worktreePath);
|
|
2410
|
+
if (jsonl) {
|
|
2411
|
+
providerError = providerError ?? `Protocol mismatch: detected .poetic/edits.jsonl (${jsonl.bytes} bytes) but provider '${provider}' is configured as git-only; no tracked changes were applied.`;
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
if (ctx.stage.automaticRepair === true && changedFilesResult.readable && changedFiles.length > 0) {
|
|
2416
|
+
const repairScope = await enforceAutomaticRepairScope({
|
|
2417
|
+
worktreePath: ctx.worktreePath,
|
|
2418
|
+
baseline: repairScopeBaseline,
|
|
2419
|
+
explicitScope: allowlist
|
|
2420
|
+
});
|
|
2421
|
+
changedFiles = repairScope.changedFiles;
|
|
2422
|
+
allowlistViolations = repairScope.violations;
|
|
2423
|
+
allowlistRevertedFiles = repairScope.revertedFiles;
|
|
2424
|
+
allowlistDrift = repairScope.violations.length > 0;
|
|
2425
|
+
if (repairScope.error) {
|
|
2426
|
+
allowlistError = repairScope.error;
|
|
2427
|
+
providerError = providerError ?? repairScope.error;
|
|
2428
|
+
}
|
|
2429
|
+
} else if (allowlistMode2 !== "off" && allowlist !== void 0 && changedFiles.length > 0) {
|
|
2430
|
+
let scopeDecision = decideStageChangeScope({
|
|
2431
|
+
changedPaths: changedFiles,
|
|
2432
|
+
explicitScope: allowlist ?? [],
|
|
2433
|
+
allowlistMode: allowlistMode2,
|
|
2434
|
+
mutationOrigin: "provider"
|
|
2435
|
+
});
|
|
2436
|
+
allowlistViolations = scopeDecision.allowlistViolations;
|
|
2437
|
+
allowlistDrift = allowlistViolations.length > 0;
|
|
2438
|
+
if (scopeDecision.disposition === "rollback") {
|
|
2439
|
+
allowlistRevertedFiles = allowlistViolations;
|
|
2440
|
+
await revertFilesToHead(ctx.worktreePath, allowlistViolations);
|
|
2441
|
+
changedFilesResult = await getWorktreeChangedFiles(ctx.worktreePath);
|
|
2442
|
+
changedFiles = changedFilesResult.files;
|
|
2443
|
+
if (!changedFilesResult.readable) {
|
|
2444
|
+
allowlistError = "Changed-file status unavailable after allowlist revert";
|
|
2445
|
+
providerError = providerError ?? allowlistError;
|
|
2446
|
+
}
|
|
2447
|
+
scopeDecision = decideStageChangeScope({
|
|
2448
|
+
changedPaths: changedFiles,
|
|
2449
|
+
explicitScope: allowlist ?? [],
|
|
2450
|
+
allowlistMode: allowlistMode2,
|
|
2451
|
+
mutationOrigin: "provider"
|
|
2452
|
+
});
|
|
2453
|
+
allowlistViolations = scopeDecision.allowlistViolations;
|
|
2454
|
+
allowlistDrift = allowlistViolations.length > 0;
|
|
2455
|
+
if (allowlistDrift) {
|
|
2456
|
+
allowlistError = `Allowlist violation: ${allowlistViolations.length} file(s) outside allowlist`;
|
|
2457
|
+
providerError = providerError ?? allowlistError;
|
|
2458
|
+
}
|
|
2459
|
+
} else if (scopeDecision.disposition === "filter") {
|
|
2460
|
+
const filteredViolations = allowlistViolations;
|
|
2461
|
+
allowlistRevertedFiles = filteredViolations;
|
|
2462
|
+
await revertFilesToHead(ctx.worktreePath, filteredViolations);
|
|
2463
|
+
changedFilesResult = await getWorktreeChangedFiles(ctx.worktreePath);
|
|
2464
|
+
changedFiles = changedFilesResult.files;
|
|
2465
|
+
if (!changedFilesResult.readable) {
|
|
2466
|
+
allowlistError = "Changed-file status unavailable after allowlist filter";
|
|
2467
|
+
providerError = providerError ?? allowlistError;
|
|
2468
|
+
}
|
|
2469
|
+
const residualViolations = decideStageChangeScope({
|
|
2470
|
+
changedPaths: changedFiles,
|
|
2471
|
+
explicitScope: allowlist ?? [],
|
|
2472
|
+
allowlistMode: allowlistMode2,
|
|
2473
|
+
mutationOrigin: "provider"
|
|
2474
|
+
}).allowlistViolations;
|
|
2475
|
+
allowlistViolations = filteredViolations;
|
|
2476
|
+
allowlistDrift = true;
|
|
2477
|
+
if (residualViolations.length > 0) {
|
|
2478
|
+
allowlistError = `Allowlist filter violation: ${residualViolations.length} file(s) outside allowlist could not be filtered`;
|
|
2479
|
+
providerError = providerError ?? allowlistError;
|
|
2480
|
+
allowlistViolations = residualViolations;
|
|
2481
|
+
}
|
|
2482
|
+
} else if (allowlistDrift && allowlistMode2 === "warn") {
|
|
2483
|
+
providerError = providerError ?? `Allowlist drift (warn): ${allowlistViolations.length} file(s) outside allowlist`;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
if (!allowlistError && !explicitValidationConfigured && !explicitVerificationIntent && changedFilesResult.readable) {
|
|
2487
|
+
const simpleGoalResult = await evaluateSimpleArtifactGoal({
|
|
2488
|
+
prompt: ctx.stage.prompt ?? "",
|
|
2489
|
+
worktreePath: ctx.worktreePath,
|
|
2490
|
+
changedFiles
|
|
2491
|
+
});
|
|
2492
|
+
if (simpleGoalResult.satisfied && simpleGoalResult.goal) {
|
|
2493
|
+
simpleArtifactGoalSatisfied = simpleGoalResult.goal;
|
|
2494
|
+
policyRecorder.emit({
|
|
2495
|
+
policy: "goal_gate",
|
|
2496
|
+
eventType: "terminated",
|
|
2497
|
+
iteration: effectiveIteration,
|
|
2498
|
+
reason: "simple artifact goal satisfied",
|
|
2499
|
+
details: { path: simpleArtifactGoalSatisfied.path }
|
|
2500
|
+
});
|
|
2501
|
+
log(`Simple artifact goal satisfied: ${simpleArtifactGoalSatisfied.path}`);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
let iterationCompletionProof;
|
|
2505
|
+
if (stageHasHostDeterministicCompletionSurface(ctx.stage)) {
|
|
2506
|
+
try {
|
|
2507
|
+
iterationCompletionProof = await evaluateStageCompletionProof({
|
|
2508
|
+
stage: ctx.stage,
|
|
2509
|
+
worktreePath: ctx.worktreePath,
|
|
2510
|
+
stdout: providerOutputText ?? "",
|
|
2511
|
+
providerText: providerOutputText,
|
|
2512
|
+
attemptChangedFiles: changedFiles,
|
|
2513
|
+
allowlist: collectDeclaredStageTargets(ctx.stage),
|
|
2514
|
+
stageId: ctx.stage.id
|
|
2515
|
+
});
|
|
2516
|
+
if (iterationCompletionProof.contractsSatisfied && iterationCompletionProof.mayAcceptOutcome) {
|
|
2517
|
+
policyRecorder.emit({
|
|
2518
|
+
policy: "goal_gate",
|
|
2519
|
+
eventType: "evaluated",
|
|
2520
|
+
iteration: effectiveIteration,
|
|
2521
|
+
reason: "host completion surface satisfied",
|
|
2522
|
+
details: {
|
|
2523
|
+
alreadySatisfied: iterationCompletionProof.alreadySatisfied,
|
|
2524
|
+
source: iterationCompletionProof.evidence?.source
|
|
2525
|
+
}
|
|
2526
|
+
});
|
|
2527
|
+
} else if (iterationCompletionProof.hasHostSurface && !iterationCompletionProof.contractsSatisfied) {
|
|
2528
|
+
policyRecorder.emit({
|
|
2529
|
+
policy: "goal_gate",
|
|
2530
|
+
eventType: "evaluated",
|
|
2531
|
+
iteration: effectiveIteration,
|
|
2532
|
+
reason: "host completion surface not satisfied",
|
|
2533
|
+
details: {
|
|
2534
|
+
feedback: iterationCompletionProof.feedback
|
|
2535
|
+
}
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2538
|
+
} catch (error) {
|
|
2539
|
+
iterationCompletionProof = hostSurfaceEvaluatorFailureProof(error);
|
|
2540
|
+
providerError = mergeIterationRetryFeedback(
|
|
2541
|
+
providerError,
|
|
2542
|
+
iterationCompletionProof.feedback
|
|
2543
|
+
);
|
|
2544
|
+
policyRecorder.emit({
|
|
2545
|
+
policy: "goal_gate",
|
|
2546
|
+
eventType: "evaluated",
|
|
2547
|
+
iteration: effectiveIteration,
|
|
2548
|
+
reason: "host completion evaluator error",
|
|
2549
|
+
details: {
|
|
2550
|
+
feedback: iterationCompletionProof.feedback
|
|
2551
|
+
}
|
|
2552
|
+
});
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
const hostContractsBlockShortCircuit = iterationCompletionProof?.hasHostSurface === true && iterationCompletionProof.mayAcceptOutcome !== true;
|
|
2556
|
+
const hostContractsBackCompletion = !iterationCompletionProof?.hasHostSurface || iterationCompletionProof.mayAcceptOutcome === true;
|
|
2557
|
+
const allConfiguredGates = [...gatesToRun, ...dynamicGates];
|
|
2558
|
+
let normalizedGates;
|
|
2559
|
+
if (configuredGatePreset) {
|
|
2560
|
+
const presetResult = await resolveGatePreset({
|
|
2561
|
+
configuredPreset: configuredGatePreset,
|
|
2562
|
+
escalation: gateEscalation,
|
|
2563
|
+
changedFiles,
|
|
2564
|
+
repairAttempts: totalRepairAttemptsForEscalation,
|
|
2565
|
+
failedGateIds: failedGateIdsForEscalation
|
|
2566
|
+
});
|
|
2567
|
+
currentGatePreset = presetResult.preset;
|
|
2568
|
+
if (presetResult.escalationReason) {
|
|
2569
|
+
log(`Gate escalation: ${currentGatePreset} (${presetResult.escalationReason})`);
|
|
2570
|
+
}
|
|
2571
|
+
const presetFilteredGates = filterGatesByPreset(
|
|
2572
|
+
allConfiguredGates,
|
|
2573
|
+
currentGatePreset
|
|
2574
|
+
);
|
|
2575
|
+
normalizedGates = orderGatesByType(presetFilteredGates);
|
|
2576
|
+
if (normalizedGates.length !== allConfiguredGates.length) {
|
|
2577
|
+
log(
|
|
2578
|
+
`Gates filtered by preset '${currentGatePreset}': ${normalizedGates.length}/${allConfiguredGates.length}`
|
|
2579
|
+
);
|
|
2580
|
+
}
|
|
2581
|
+
} else {
|
|
2582
|
+
normalizedGates = orderGatesByType(allConfiguredGates);
|
|
2583
|
+
currentGatePreset = "full";
|
|
2584
|
+
}
|
|
2585
|
+
let finalGateResults;
|
|
2586
|
+
let iterRepairAttempted = false;
|
|
2587
|
+
let iterRepairSuccesses = 0;
|
|
2588
|
+
if (normalizedGates.length > 0 && !allowlistError && !simpleArtifactGoalSatisfied) {
|
|
2589
|
+
const gateNow = performance2.now();
|
|
2590
|
+
const elapsedLoopMsForGates = elapsedLoopMs();
|
|
2591
|
+
const elapsedIterationMsForGates = Math.max(0, gateNow - iterationStart);
|
|
2592
|
+
const hasIterationGateCeiling = typeof iterationTimeboxMs === "number" && iterationTimeboxMs > 0;
|
|
2593
|
+
const iterationGateRemainingMs = hasIterationGateCeiling ? Math.floor(iterationTimeboxMs - elapsedIterationMsForGates) : void 0;
|
|
2594
|
+
if (iterationGateRemainingMs !== void 0 && iterationGateRemainingMs <= 0) {
|
|
2595
|
+
providerError = providerError ?? "Autoloop timebox exhausted before quality gates";
|
|
2596
|
+
log(providerError);
|
|
2597
|
+
} else {
|
|
2598
|
+
const loopGateRemainingMs = Math.floor(timeboxMs - elapsedLoopMsForGates);
|
|
2599
|
+
if (loopGateRemainingMs <= 0) {
|
|
2600
|
+
providerError = providerError ?? "Autoloop timebox exhausted before quality gates";
|
|
2601
|
+
log(providerError);
|
|
2602
|
+
} else {
|
|
2603
|
+
const effectiveGateRemainingMs = Math.max(
|
|
2604
|
+
1,
|
|
2605
|
+
iterationGateRemainingMs !== void 0 ? Math.min(iterationGateRemainingMs, loopGateRemainingMs) : loopGateRemainingMs
|
|
2606
|
+
);
|
|
2607
|
+
const gatesWithTimeout = normalizedGates.map(
|
|
2608
|
+
(gate) => typeof gate.timeoutMs === "number" && gate.timeoutMs > 0 ? (
|
|
2609
|
+
// Tighten an explicit operator-set timeout to the
|
|
2610
|
+
// remaining budget; only ever shrink, never widen.
|
|
2611
|
+
{
|
|
2612
|
+
...gate,
|
|
2613
|
+
timeoutMs: Math.min(gate.timeoutMs, effectiveGateRemainingMs)
|
|
2614
|
+
}
|
|
2615
|
+
) : { ...gate, timeoutMs: effectiveGateRemainingMs }
|
|
2616
|
+
);
|
|
2617
|
+
const gateStart = performance2.now();
|
|
2618
|
+
const gateRun = await runAllGates(gatesWithTimeout, ctx.worktreePath, {
|
|
2619
|
+
baselineCwd: ctx.baselineWorktreePath,
|
|
2620
|
+
abortSignal: ctx.abortSignal
|
|
2621
|
+
});
|
|
2622
|
+
timeBreakdown.gateMs = performance2.now() - gateStart;
|
|
2623
|
+
finalGateResults = gateRun.results;
|
|
2624
|
+
const unavailableGateSignal2 = detectUnavailableGateVerification(
|
|
2625
|
+
gateRun.results
|
|
2626
|
+
);
|
|
2627
|
+
if (unavailableGateSignal2) {
|
|
2628
|
+
providerError = providerError ?? `Verification unavailable: ${unavailableGateSignal2.message}`;
|
|
2629
|
+
}
|
|
2630
|
+
if (!gateRun.passed && !unavailableGateSignal2) {
|
|
2631
|
+
const failedMustPassGates = gateRun.results.filter(
|
|
2632
|
+
(r) => !r.passed && r.gate.mustPass && r.isRegression !== false
|
|
2633
|
+
);
|
|
2634
|
+
const failedGate = failedMustPassGates.find(
|
|
2635
|
+
(r) => shouldAttemptRepair(r.gate)
|
|
2636
|
+
);
|
|
2637
|
+
if (failedGate && shouldAttemptRepair(failedGate.gate)) {
|
|
2638
|
+
const holisticFailedGate = failedMustPassGates.length > 1 ? {
|
|
2639
|
+
...failedGate,
|
|
2640
|
+
outputTail: failedMustPassGates.map(
|
|
2641
|
+
(r) => `Gate ${r.gate.id} (${r.gate.command}) failed with exit ${r.exitCode}:
|
|
2642
|
+
${r.outputTail || "(no output)"}`
|
|
2643
|
+
).join("\n\n")
|
|
2644
|
+
} : failedGate;
|
|
2645
|
+
const maxAttempts = getMaxRepairAttempts(failedGate.gate);
|
|
2646
|
+
const provider = resolveFlywheelStageProvider(ctx.stage, ctx.worktreePath);
|
|
2647
|
+
const { applyFlywheelModelDefaults } = await import("./flywheel-model-defaults-7CVKP53V.js");
|
|
2648
|
+
const model = applyFlywheelModelDefaults(ctx.stage, provider);
|
|
2649
|
+
const recentFailingGates = [failedGate.gate.id];
|
|
2650
|
+
if (!failedGateIdsForEscalation.includes(failedGate.gate.id)) {
|
|
2651
|
+
failedGateIdsForEscalation.push(failedGate.gate.id);
|
|
2652
|
+
}
|
|
2653
|
+
const repairStart = performance2.now();
|
|
2654
|
+
for (let attemptNum = 1; attemptNum <= maxAttempts; attemptNum++) {
|
|
2655
|
+
const repairNow = performance2.now();
|
|
2656
|
+
const loopRepairRemainingMs = Math.floor(timeboxMs - elapsedLoopMs());
|
|
2657
|
+
const iterationRepairRemainingMs = typeof iterationTimeboxMs === "number" && iterationTimeboxMs > 0 ? Math.floor(
|
|
2658
|
+
iterationTimeboxMs - Math.max(0, repairNow - iterationStart)
|
|
2659
|
+
) : void 0;
|
|
2660
|
+
const effectiveRepairRemainingMs = iterationRepairRemainingMs !== void 0 ? Math.min(iterationRepairRemainingMs, loopRepairRemainingMs) : loopRepairRemainingMs;
|
|
2661
|
+
if (effectiveRepairRemainingMs < MIN_GATE_REPAIR_TIMEOUT_MS) {
|
|
2662
|
+
log(
|
|
2663
|
+
`Skipping gate repair ${attemptNum}/${maxAttempts} for ${failedGate.gate.id}: insufficient remaining timebox (${Math.max(0, effectiveRepairRemainingMs)}ms < ${MIN_GATE_REPAIR_TIMEOUT_MS}ms)`
|
|
2664
|
+
);
|
|
2665
|
+
break;
|
|
2666
|
+
}
|
|
2667
|
+
const repairTimeoutMs = effectiveRepairRemainingMs;
|
|
2668
|
+
if (!iterRepairAttempted) {
|
|
2669
|
+
iterRepairAttempted = true;
|
|
2670
|
+
totalRepairAttemptsForEscalation++;
|
|
2671
|
+
}
|
|
2672
|
+
log(
|
|
2673
|
+
`Attempting gate repair ${attemptNum}/${maxAttempts} for ${failedGate.gate.id}`
|
|
2674
|
+
);
|
|
2675
|
+
const repairResult = await attemptGateRepair({
|
|
2676
|
+
failedGate: failedGate.gate,
|
|
2677
|
+
gateResult: holisticFailedGate,
|
|
2678
|
+
allGates: gatesWithTimeout,
|
|
2679
|
+
worktreePath: ctx.worktreePath,
|
|
2680
|
+
changedFiles,
|
|
2681
|
+
executor: ctx.executor,
|
|
2682
|
+
provider,
|
|
2683
|
+
model,
|
|
2684
|
+
attemptNumber: attemptNum,
|
|
2685
|
+
// Use the SAME raw allowlist the main iteration enforces
|
|
2686
|
+
// against (ctx.stage.allowlist). collectDeclaredStageTargets
|
|
2687
|
+
// returns a different set — it normalizes/filters entries
|
|
2688
|
+
// (drops node_modules/absolute/.. paths) and folds in
|
|
2689
|
+
// requiredFiles, specHarness targets, and prompt-declared
|
|
2690
|
+
// targets — so the repair allowlist diverged from the main
|
|
2691
|
+
// check and could flag files the iteration had permitted.
|
|
2692
|
+
allowlist: ctx.stage.allowlist,
|
|
2693
|
+
allowlistMode: allowlistMode2,
|
|
2694
|
+
baselineCwd: ctx.baselineWorktreePath,
|
|
2695
|
+
abortSignal: ctx.abortSignal,
|
|
2696
|
+
timeoutMs: repairTimeoutMs
|
|
2697
|
+
});
|
|
2698
|
+
providerCost += repairResult.cost;
|
|
2699
|
+
providerCostUnavailableReason ??= repairResult.costUnavailableReason;
|
|
2700
|
+
const repairTokenAccounting = resolveTokenAccounting({
|
|
2701
|
+
inputTokens: repairResult.inputTokens ?? 0,
|
|
2702
|
+
outputTokens: repairResult.outputTokens ?? 0,
|
|
2703
|
+
totalCost: repairResult.cost,
|
|
2704
|
+
tokenUsageSource: repairResult.tokenUsageSource
|
|
2705
|
+
});
|
|
2706
|
+
providerTokens += repairTokenAccounting.totalTokens;
|
|
2707
|
+
totalTokens += repairTokenAccounting.totalTokens;
|
|
2708
|
+
for (const evidence of repairResult.reviewRequiredEvidence ?? []) {
|
|
2709
|
+
repairReviewWarnings.add(formatRepairSuppressionWarning(evidence));
|
|
2710
|
+
}
|
|
2711
|
+
if (repairResult.allowlistState) {
|
|
2712
|
+
changedFiles = repairResult.allowlistState.changedFiles;
|
|
2713
|
+
allowlistViolations = repairResult.allowlistState.violations;
|
|
2714
|
+
allowlistDrift = allowlistViolations.length > 0;
|
|
2715
|
+
if (repairResult.allowlistState.autoRevertedFiles.length > 0) {
|
|
2716
|
+
allowlistRevertedFiles = repairResult.allowlistState.autoRevertedFiles;
|
|
2717
|
+
}
|
|
2718
|
+
if (repairResult.allowlistState.error) {
|
|
2719
|
+
allowlistError = repairResult.allowlistState.error;
|
|
2720
|
+
providerError = providerError ?? allowlistError;
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
if (repairResult.abortFurtherRepairs) {
|
|
2724
|
+
finalGateResults = repairResult.gateResults;
|
|
2725
|
+
break;
|
|
2726
|
+
}
|
|
2727
|
+
if (repairResult.success) {
|
|
2728
|
+
iterRepairSuccesses++;
|
|
2729
|
+
finalGateResults = repairResult.gateResults;
|
|
2730
|
+
log(`Gate repair successful after ${attemptNum} attempt(s)`);
|
|
2731
|
+
break;
|
|
2732
|
+
}
|
|
2733
|
+
const nowFailingGate = repairResult.gateResults?.find(
|
|
2734
|
+
(r) => !r.passed && r.gate.mustPass
|
|
2735
|
+
);
|
|
2736
|
+
if (nowFailingGate) {
|
|
2737
|
+
recentFailingGates.push(nowFailingGate.gate.id);
|
|
2738
|
+
if (recentFailingGates.length > 4) recentFailingGates.shift();
|
|
2739
|
+
const uniqueRecent = new Set(recentFailingGates);
|
|
2740
|
+
const isFlipPattern = recentFailingGates.length >= 3 && recentFailingGates[0] === recentFailingGates[2] && recentFailingGates[0] !== recentFailingGates[1];
|
|
2741
|
+
const isTooManyDistinct = uniqueRecent.size >= 3;
|
|
2742
|
+
if (isFlipPattern || isTooManyDistinct) {
|
|
2743
|
+
const pattern = [...uniqueRecent].join("<->");
|
|
2744
|
+
const reason = isFlipPattern ? "flip pattern detected" : "too many distinct failing gates";
|
|
2745
|
+
log(
|
|
2746
|
+
`Gate repair thrashing: ${pattern} (${reason}). Stopping repairs this iteration.`
|
|
2747
|
+
);
|
|
2748
|
+
providerError = providerError ?? `Gate repair thrashing (${pattern}): fixes for one gate break another. Next iteration should address all failing gates holistically.`;
|
|
2749
|
+
finalGateResults = repairResult.gateResults;
|
|
2750
|
+
break;
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
if (attemptNum < maxAttempts) {
|
|
2754
|
+
log(`Gate repair attempt ${attemptNum} failed, retrying...`);
|
|
2755
|
+
} else {
|
|
2756
|
+
log(`Gate repair exhausted all ${maxAttempts} attempts`);
|
|
2757
|
+
finalGateResults = repairResult.gateResults;
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
timeBreakdown.repairMs = performance2.now() - repairStart;
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
const qualityGate = finalGateResults ? {
|
|
2767
|
+
passed: finalGateResults.every(
|
|
2768
|
+
(r) => r.passed || !r.gate.mustPass || r.isRegression === false
|
|
2769
|
+
),
|
|
2770
|
+
error: (() => {
|
|
2771
|
+
const failedGate = finalGateResults.find(
|
|
2772
|
+
(r) => !r.passed && r.gate.mustPass && r.isRegression !== false
|
|
2773
|
+
);
|
|
2774
|
+
return failedGate ? `Verify command failed (exit ${failedGate.exitCode}): ${failedGate.gate.command}` : void 0;
|
|
2775
|
+
})(),
|
|
2776
|
+
commandResults: finalGateResults.map((r) => ({
|
|
2777
|
+
id: r.gate.id,
|
|
2778
|
+
command: r.gate.command,
|
|
2779
|
+
exitCode: r.exitCode,
|
|
2780
|
+
expectedExitCode: r.gate.expectedExitCode ?? 0,
|
|
2781
|
+
passed: r.passed,
|
|
2782
|
+
durationMs: r.durationMs,
|
|
2783
|
+
outputTail: r.outputTail,
|
|
2784
|
+
baselinePassed: r.baselinePassed,
|
|
2785
|
+
isRegression: r.isRegression,
|
|
2786
|
+
mustPass: r.gate.mustPass ?? true
|
|
2787
|
+
}))
|
|
2788
|
+
} : void 0;
|
|
2789
|
+
if (qualityGate) {
|
|
2790
|
+
policyRecorder.emit({
|
|
2791
|
+
policy: "quality_gate",
|
|
2792
|
+
eventType: qualityGate.passed ? "evaluated" : "blocked",
|
|
2793
|
+
iteration: effectiveIteration,
|
|
2794
|
+
reason: qualityGate.passed ? "Quality gates passed" : qualityGate.error,
|
|
2795
|
+
details: {
|
|
2796
|
+
gateCount: qualityGate.commandResults.length,
|
|
2797
|
+
failedCommandCount: qualityGate.commandResults.filter((r) => !r.passed).length
|
|
2798
|
+
}
|
|
2799
|
+
});
|
|
2800
|
+
}
|
|
2801
|
+
if (qualityGate?.passed === true && finalGateResults !== void 0 && finalGateResults.length > 0 && !providerHardExitFailure && isVerificationUnavailableError(providerError) && !hostContractsBlockShortCircuit && hostContractsBackCompletion) {
|
|
2802
|
+
providerVerificationUnavailableRecovered = false;
|
|
2803
|
+
}
|
|
2804
|
+
const testFilesInIteration = config.requiresTests === true ? await detectTestFiles(changedFiles, config.testFilePatterns) : [];
|
|
2805
|
+
const testsCreated = testFilesInIteration.length > 0;
|
|
2806
|
+
const totalTrackedMs = timeBreakdown.providerMs + timeBreakdown.gateMs + timeBreakdown.repairMs + (timeBreakdown.testMs ?? 0) + (timeBreakdown.buildMs ?? 0);
|
|
2807
|
+
const iterationTotalMs = performance2.now() - iterationStart;
|
|
2808
|
+
timeBreakdown.overheadMs = Math.max(0, iterationTotalMs - totalTrackedMs);
|
|
2809
|
+
const progressMetrics = computeIterationProgressMetrics({
|
|
2810
|
+
changedFiles,
|
|
2811
|
+
qualityGate,
|
|
2812
|
+
previousIteration: iterationHistory.at(-1)
|
|
2813
|
+
});
|
|
2814
|
+
let shouldTerminateNoOp = false;
|
|
2815
|
+
let noOpTerminationMessage = "";
|
|
2816
|
+
let shouldTerminateIterationTimebox = false;
|
|
2817
|
+
let iterationTimeboxMessage = "";
|
|
2818
|
+
let iterationTerminationSignal;
|
|
2819
|
+
if (providerResult.success && requireEarlyDiff && progressMetrics.wasNoOp && effectiveIteration > 1) {
|
|
2820
|
+
consecutiveNoOps++;
|
|
2821
|
+
lastIterationDurationMs = iterationTotalMs;
|
|
2822
|
+
providerError = providerError ?? "No-op iteration: no file changes detected";
|
|
2823
|
+
log(`No-op iteration detected (consecutive: ${consecutiveNoOps})`);
|
|
2824
|
+
if (noOpIntervention) {
|
|
2825
|
+
const maxConsecutive = noOpIntervention.maxConsecutiveNoOps ?? 2;
|
|
2826
|
+
const triggerDelay = noOpIntervention.triggerDelayMs ?? 45e3;
|
|
2827
|
+
if (consecutiveNoOps >= maxConsecutive) {
|
|
2828
|
+
shouldTerminateNoOp = true;
|
|
2829
|
+
iterationTerminationSignal = "no_op_intervention_exceeded";
|
|
2830
|
+
noOpTerminationMessage = `No-op intervention exceeded: ${consecutiveNoOps} consecutive no-ops`;
|
|
2831
|
+
providerError = noOpTerminationMessage;
|
|
2832
|
+
log(`Max consecutive no-ops reached (${consecutiveNoOps}/${maxConsecutive})`);
|
|
2833
|
+
} else if (iterationTotalMs >= triggerDelay) {
|
|
2834
|
+
interventionActive = true;
|
|
2835
|
+
log(
|
|
2836
|
+
`Intervention triggered: iteration took ${iterationTotalMs}ms >= ${triggerDelay}ms`
|
|
2837
|
+
);
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
} else if (providerResult.success && !progressMetrics.wasNoOp) {
|
|
2841
|
+
consecutiveNoOps = 0;
|
|
2842
|
+
interventionActive = false;
|
|
2843
|
+
}
|
|
2844
|
+
if (iterationTimeboxMs && iterationTotalMs > iterationTimeboxMs) {
|
|
2845
|
+
iterationTimeboxMessage = `Iteration exceeded per-iteration timebox (${iterationTotalMs.toFixed(0)}ms > ${iterationTimeboxMs}ms)`;
|
|
2846
|
+
log(iterationTimeboxMessage);
|
|
2847
|
+
shouldTerminateIterationTimebox = true;
|
|
2848
|
+
iterationTerminationSignal = "timebox_exceeded";
|
|
2849
|
+
providerError = providerError ?? iterationTimeboxMessage;
|
|
2850
|
+
}
|
|
2851
|
+
if (simpleArtifactGoalSatisfied && !iterationTerminationSignal) {
|
|
2852
|
+
iterationTerminationSignal = "goal_satisfied";
|
|
2853
|
+
}
|
|
2854
|
+
let shouldTerminateVerificationUnavailable = false;
|
|
2855
|
+
let verificationUnavailableMessage = "";
|
|
2856
|
+
const unavailableGateSignal = detectUnavailableGateVerification(finalGateResults);
|
|
2857
|
+
if (providerResult.success && !allowlistError && isVerificationUnavailableError(providerError) && (!changesHaveVerifiableCodeSurface(changedFiles) || unavailableGateSignal || /\bverification(?:\s+tool)?\s+unavailable\b/i.test(providerError ?? ""))) {
|
|
2858
|
+
shouldTerminateVerificationUnavailable = true;
|
|
2859
|
+
verificationUnavailableMessage = unavailableGateSignal ? `Verification unavailable: ${unavailableGateSignal.message} Stopping instead of asking the provider to install or repair target tooling; this is not reported as success.` : "Verification unavailable: post-execution tests did not run and the changed files expose no verifiable code surface (read-only / docs-only task). Stopping instead of re-iterating; this is not reported as success.";
|
|
2860
|
+
providerError = verificationUnavailableMessage;
|
|
2861
|
+
iterationTerminationSignal = "verification_unavailable";
|
|
2862
|
+
log(verificationUnavailableMessage);
|
|
2863
|
+
policyRecorder.emit({
|
|
2864
|
+
policy: "goal_gate",
|
|
2865
|
+
eventType: "terminated",
|
|
2866
|
+
iteration: effectiveIteration,
|
|
2867
|
+
reason: "verification_unavailable",
|
|
2868
|
+
details: { changedFiles: changedFiles.length }
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
2871
|
+
const record = {
|
|
2872
|
+
iteration: effectiveIteration,
|
|
2873
|
+
success: providerResult.success && !providerVerificationUnavailableRecovered && !allowlistError && !providerError,
|
|
2874
|
+
providerExecutionStatus,
|
|
2875
|
+
providerOutputStatus,
|
|
2876
|
+
providerStatusDetail,
|
|
2877
|
+
changedFiles,
|
|
2878
|
+
providerOutputText,
|
|
2879
|
+
cost: providerCost,
|
|
2880
|
+
costUnavailableReason: providerCostUnavailableReason,
|
|
2881
|
+
durationMs: iterationTotalMs,
|
|
2882
|
+
timeBreakdown,
|
|
2883
|
+
progressMetrics,
|
|
2884
|
+
progressScore: void 0,
|
|
2885
|
+
error: providerError,
|
|
2886
|
+
terminationSignal: iterationTerminationSignal ?? (allowlistError ? "allowlist_violation" : void 0),
|
|
2887
|
+
qualityGatePassed: qualityGate?.passed,
|
|
2888
|
+
qualityGateError: qualityGate?.error,
|
|
2889
|
+
qualityGateCommandResults: qualityGate?.commandResults,
|
|
2890
|
+
allowlistViolations: allowlistDrift ? allowlistViolations : void 0,
|
|
2891
|
+
allowlistRevertedFiles: allowlistRevertedFiles?.length ? allowlistRevertedFiles : void 0,
|
|
2892
|
+
allowlistDrift,
|
|
2893
|
+
repairAttempted: iterRepairAttempted,
|
|
2894
|
+
repairSuccesses: iterRepairSuccesses,
|
|
2895
|
+
testsRequired: config.requiresTests === true,
|
|
2896
|
+
testsCreated,
|
|
2897
|
+
// Priority 3: Short-circuit detection
|
|
2898
|
+
aiValidationShortCircuited: false,
|
|
2899
|
+
// Set below if conditions are met
|
|
2900
|
+
completionEvidence: simpleArtifactGoalSatisfied ? {
|
|
2901
|
+
durable: true,
|
|
2902
|
+
source: "simple-artifact",
|
|
2903
|
+
detail: simpleArtifactGoal?.path ?? "simple artifact",
|
|
2904
|
+
...iterationCompletionProof?.evidence?.providerAdvisory ? {
|
|
2905
|
+
providerAdvisory: iterationCompletionProof.evidence.providerAdvisory
|
|
2906
|
+
} : {},
|
|
2907
|
+
...iterationCompletionProof?.evidence?.attemptScopedEdits ? {
|
|
2908
|
+
attemptScopedEdits: iterationCompletionProof.evidence.attemptScopedEdits
|
|
2909
|
+
} : {},
|
|
2910
|
+
...iterationCompletionProof?.evidence?.contractResults ? {
|
|
2911
|
+
contractResults: iterationCompletionProof.evidence.contractResults
|
|
2912
|
+
} : {}
|
|
2913
|
+
} : toAutoloopCompletionEvidence(
|
|
2914
|
+
iterationCompletionProof ?? {
|
|
2915
|
+
contractsSatisfied: false,
|
|
2916
|
+
requireChangesSatisfied: "n/a",
|
|
2917
|
+
alreadySatisfied: false,
|
|
2918
|
+
mayAcceptOutcome: false,
|
|
2919
|
+
hasHostSurface: false,
|
|
2920
|
+
reasons: []
|
|
2921
|
+
}
|
|
2922
|
+
),
|
|
2923
|
+
policyEvents: policyRecorder.events.filter(
|
|
2924
|
+
(event) => event.iteration === effectiveIteration
|
|
2925
|
+
)
|
|
2926
|
+
};
|
|
2927
|
+
const cumulativeAutoloopCost = iterationHistory.reduce((sum, item) => sum + item.cost, 0) + record.cost;
|
|
2928
|
+
const incrementalTracker = ctx.budgetTracker;
|
|
2929
|
+
incrementalTracker.trackIncrementalStageCost?.(ctx.stage.id, cumulativeAutoloopCost);
|
|
2930
|
+
let completionSignalDetected = false;
|
|
2931
|
+
if (completionSignal && providerOutputText) {
|
|
2932
|
+
completionSignalDetected = matchesCompletionSignal(
|
|
2933
|
+
providerOutputText,
|
|
2934
|
+
completionSignal
|
|
2935
|
+
);
|
|
2936
|
+
}
|
|
2937
|
+
const canShortCircuitAiValidation = qualityGate?.passed === true && record.success && !interventionActive && !hostContractsBlockShortCircuit && hostContractsBackCompletion && (completionSignalDetected || validationMode === "deterministic" || iterationCompletionProof?.alreadySatisfied === true || record.completionEvidence?.durable === true);
|
|
2938
|
+
if (canShortCircuitAiValidation) {
|
|
2939
|
+
record.aiValidationShortCircuited = true;
|
|
2940
|
+
aiValidationShortCircuited = true;
|
|
2941
|
+
const reason = iterationCompletionProof?.alreadySatisfied ? "host completion surface already satisfied" : completionSignalDetected ? "deterministic gates passed + completion signal detected" : "deterministic validation mode + gates passed";
|
|
2942
|
+
policyRecorder.emit({
|
|
2943
|
+
policy: "goal_gate",
|
|
2944
|
+
eventType: "terminated",
|
|
2945
|
+
iteration: effectiveIteration,
|
|
2946
|
+
reason,
|
|
2947
|
+
details: {
|
|
2948
|
+
completionSignalDetected,
|
|
2949
|
+
validationMode,
|
|
2950
|
+
hostContractsSatisfied: iterationCompletionProof?.contractsSatisfied
|
|
2951
|
+
}
|
|
2952
|
+
});
|
|
2953
|
+
record.policyEvents = policyRecorder.events.filter(
|
|
2954
|
+
(event) => event.iteration === effectiveIteration
|
|
2955
|
+
);
|
|
2956
|
+
log(`Short-circuiting AI validation (${reason})`);
|
|
2957
|
+
}
|
|
2958
|
+
const hasPassingHardQualityGate = (record.qualityGateCommandResults ?? []).some(
|
|
2959
|
+
(gate) => gate.mustPass !== false && gate.passed === true
|
|
2960
|
+
);
|
|
2961
|
+
if (validationMode !== "deterministic" && config.completionOnQualityGatePass === true && completionSignalXmlTag && record.success && record.qualityGatePassed === true && hasPassingHardQualityGate && !hostContractsBlockShortCircuit && hostContractsBackCompletion) {
|
|
2962
|
+
const cumulativeChangedFiles = [
|
|
2963
|
+
...iterationHistory.flatMap((item) => item.changedFiles),
|
|
2964
|
+
...record.changedFiles
|
|
2965
|
+
];
|
|
2966
|
+
const requiredTestsSatisfied = config.requiresTests !== true || (await detectTestFiles(cumulativeChangedFiles, config.testFilePatterns)).length > 0;
|
|
2967
|
+
const currentTestGateResults = (record.qualityGateCommandResults ?? []).filter(
|
|
2968
|
+
(gate) => gate.mustPass !== false && describeVerificationCommand(gate.command).kind === "test"
|
|
2969
|
+
);
|
|
2970
|
+
const runtimeEvidenceSatisfied = verifyRuntimeEvidence({
|
|
2971
|
+
config: config.runtimeEvidence,
|
|
2972
|
+
providerOutputText: record.providerOutputText,
|
|
2973
|
+
worktreePath: ctx.worktreePath,
|
|
2974
|
+
basePrompt: ctx.stage.prompt ?? "",
|
|
2975
|
+
changedFiles: record.changedFiles,
|
|
2976
|
+
testVerification: {
|
|
2977
|
+
status: currentTestGateResults.length === 0 ? "unavailable" : currentTestGateResults.every((gate) => gate.passed === true) ? "passed" : "failed",
|
|
2978
|
+
checksRun: currentTestGateResults.length
|
|
2979
|
+
}
|
|
2980
|
+
}).passed;
|
|
2981
|
+
if (requiredTestsSatisfied && runtimeEvidenceSatisfied) {
|
|
2982
|
+
record.completionEvidence ??= {
|
|
2983
|
+
durable: true,
|
|
2984
|
+
source: "external",
|
|
2985
|
+
detail: "Flywheel quality gates passed"
|
|
2986
|
+
};
|
|
2987
|
+
record.providerOutputText = `${record.providerOutputText ?? ""}
|
|
2988
|
+
${completionSignalXmlTag}`.trim().concat("\n");
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
const hostCustomGateResults = mapQualityGateCommandResultsToCustomGateResults(
|
|
2992
|
+
record.qualityGateCommandResults,
|
|
2993
|
+
createDurationMs
|
|
2994
|
+
);
|
|
2995
|
+
if (hostCustomGateResults.length > 0) {
|
|
2996
|
+
Object.assign(record, { customGateResults: hostCustomGateResults });
|
|
2997
|
+
}
|
|
2998
|
+
record.iterationFingerprint = await captureIterationFingerprint(iteration);
|
|
2999
|
+
iterationHistory.push(record);
|
|
3000
|
+
if (ctx.artifactStore) {
|
|
3001
|
+
await saveBestEffort(
|
|
3002
|
+
ctx.artifactStore,
|
|
3003
|
+
ctx.stage.id,
|
|
3004
|
+
`iteration-${effectiveIteration}`,
|
|
3005
|
+
record
|
|
3006
|
+
);
|
|
3007
|
+
}
|
|
3008
|
+
const driftMsg = record.allowlistDrift ? ` [drift: ${record.allowlistViolations?.length ?? 0}]` : "";
|
|
3009
|
+
log(
|
|
3010
|
+
`Iteration ${effectiveIteration} ${record.success ? "ok" : "failed"} (${record.changedFiles.length} files, ${formatAutoloopIterationCost(record)})${driftMsg}`
|
|
3011
|
+
);
|
|
3012
|
+
if (shouldTerminateNoOp) {
|
|
3013
|
+
throw new Error(noOpTerminationMessage);
|
|
3014
|
+
}
|
|
3015
|
+
if (shouldTerminateVerificationUnavailable) {
|
|
3016
|
+
throw new AutoloopTerminationError(
|
|
3017
|
+
verificationUnavailableMessage,
|
|
3018
|
+
"verification_unavailable"
|
|
3019
|
+
);
|
|
3020
|
+
}
|
|
3021
|
+
if (shouldTerminateIterationTimebox) {
|
|
3022
|
+
try {
|
|
3023
|
+
loopController.terminate("timebox_exceeded");
|
|
3024
|
+
} catch {
|
|
3025
|
+
}
|
|
3026
|
+
throw new Error(iterationTimeboxMessage);
|
|
3027
|
+
}
|
|
3028
|
+
if (simpleArtifactGoalSatisfied) {
|
|
3029
|
+
try {
|
|
3030
|
+
loopController.terminate("deterministic_complete");
|
|
3031
|
+
} catch {
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
return record;
|
|
3035
|
+
});
|
|
3036
|
+
await Promise.allSettled(pendingIterationRecords);
|
|
3037
|
+
if (hasTerminalDurableOutcome || remainingIterations === 0) {
|
|
3038
|
+
const reconstructed = reconstructExhaustedAutoloopOutcome({
|
|
3039
|
+
priorOutcome: durableResumeState?.priorOutcome,
|
|
3040
|
+
finalProgressScore: durableResumeState?.finalProgressScore
|
|
3041
|
+
});
|
|
3042
|
+
terminationReason = reconstructed.terminationReason;
|
|
3043
|
+
loopSuccess = reconstructed.loopSuccess;
|
|
3044
|
+
finalProgressScore = reconstructed.finalProgressScore;
|
|
3045
|
+
policyRecorder.emit({
|
|
3046
|
+
policy: policyForTerminationReason(terminationReason),
|
|
3047
|
+
eventType: reconstructed.loopSuccess ? "evaluated" : "terminated",
|
|
3048
|
+
reason: terminationReason,
|
|
3049
|
+
details: {
|
|
3050
|
+
finalProgressScore,
|
|
3051
|
+
iterations: 0,
|
|
3052
|
+
reconstructedFromDurableEvidence: true,
|
|
3053
|
+
evidenceComplete: reconstructed.evidenceComplete,
|
|
3054
|
+
source: durableResumeState?.source
|
|
3055
|
+
}
|
|
3056
|
+
});
|
|
3057
|
+
} else {
|
|
3058
|
+
finalProgressScore = finalIteration !== 0 ? finalIteration.progress.score : 0;
|
|
3059
|
+
const loopIterations = loopController.getIterationHistory();
|
|
3060
|
+
const lastIterationRecord = iterationHistory.at(-1);
|
|
3061
|
+
const testGateResults = (lastIterationRecord?.qualityGateCommandResults ?? []).filter(
|
|
3062
|
+
(gate) => gate.mustPass !== false && describeVerificationCommand(gate.command).kind === "test"
|
|
3063
|
+
);
|
|
3064
|
+
runtimeEvidenceCheck = verifyRuntimeEvidence({
|
|
3065
|
+
config: config.runtimeEvidence,
|
|
3066
|
+
providerOutputText: iterationHistory.length > 0 ? iterationHistory.at(-1)?.providerOutputText : void 0,
|
|
3067
|
+
worktreePath: ctx.worktreePath,
|
|
3068
|
+
basePrompt: ctx.stage.prompt ?? "",
|
|
3069
|
+
changedFiles: lastIterationRecord?.changedFiles,
|
|
3070
|
+
testVerification: {
|
|
3071
|
+
status: testGateResults.length === 0 ? "unavailable" : testGateResults.every((gate) => gate.passed === true) ? "passed" : "failed",
|
|
3072
|
+
checksRun: testGateResults.length
|
|
3073
|
+
}
|
|
3074
|
+
});
|
|
3075
|
+
const controllerState = loopController.getState();
|
|
3076
|
+
const acceptanceCriteriaEvidenceConfigured = config.runtimeEvidence?.requireAcceptanceCriteria === true;
|
|
3077
|
+
terminationReason = await inferTerminationReason({
|
|
3078
|
+
maxIterations: remainingIterations,
|
|
3079
|
+
timeboxMs,
|
|
3080
|
+
iterationHistory,
|
|
3081
|
+
loopIterations,
|
|
3082
|
+
minProgress,
|
|
3083
|
+
gatesEnabled: gatesEnabled || dynamicGates.length > 0,
|
|
3084
|
+
requiresTests: config.requiresTests,
|
|
3085
|
+
testFilePatterns: config.testFilePatterns,
|
|
3086
|
+
runtimeEvidenceCheck,
|
|
3087
|
+
runtimeEvidenceConfigured: acceptanceCriteriaEvidenceConfigured,
|
|
3088
|
+
controllerTerminationReason: controllerState.status === "terminated" ? controllerState.reason : controllerState.status === "completed" ? controllerState.completionReason : void 0
|
|
3089
|
+
});
|
|
3090
|
+
policyRecorder.emit({
|
|
3091
|
+
policy: policyForTerminationReason(terminationReason),
|
|
3092
|
+
eventType: terminationReason === "success" ? "evaluated" : "terminated",
|
|
3093
|
+
reason: terminationReason,
|
|
3094
|
+
details: {
|
|
3095
|
+
finalProgressScore,
|
|
3096
|
+
iterations: iterationHistory.length,
|
|
3097
|
+
loopStatus: controllerState.status
|
|
3098
|
+
}
|
|
3099
|
+
});
|
|
3100
|
+
const requiresPassingGates = terminationReason === "goal_satisfied" ? false : gatesEnabled || dynamicGates.length > 0;
|
|
3101
|
+
const finalGatePassed = requiresPassingGates ? iterationHistory.at(-1)?.qualityGatePassed === true : true;
|
|
3102
|
+
const controllerUnsafeCompletion = finalIteration !== 0 && finalIteration.completionSummary?.unsafeCompletion === true;
|
|
3103
|
+
const completionSummarySupportsSuccess = finalIteration !== 0 && finalIteration.completionSummary !== void 0 && completionStatusAllowsSuccess(finalIteration.completionSummary.status) && finalIteration.completionSummary.hasSubstantiveEvidence === true && finalIteration.completionSummary.blockingIssues.length === 0;
|
|
3104
|
+
const controllerReportedSuccess = controllerState.status === "terminated" && isSuccessLikeControllerReason(controllerState.reason);
|
|
3105
|
+
const lastIterRecord = iterationHistory.at(-1);
|
|
3106
|
+
const lastIterationOk = lastIterRecord?.success !== false;
|
|
3107
|
+
const hardFailure = (
|
|
3108
|
+
// Parity with the controller start() consumer: unsafeCompletion forces
|
|
3109
|
+
// non-success. Shared predicate keeps the two surfaces fail-closed in
|
|
3110
|
+
// lockstep (see autoloop-success.ts:unsafeCompletionAllowsSuccess).
|
|
3111
|
+
!unsafeCompletionAllowsSuccess(controllerUnsafeCompletion) || controllerReportedSuccess && !completionSummarySupportsSuccess || terminationReason === "resource_exhaustion" || terminationReason === "error" || terminationReason === "timebox_exceeded" || terminationReason === "allowlist_violation" || terminationReason === "quality_gate_failure" || terminationReason === "baseline_blocked" || terminationReason === "tests_required_not_met" || terminationReason === "evidence_unsatisfied" || terminationReason === "progress_stagnation" || terminationReason === "no_op_intervention_exceeded"
|
|
3112
|
+
);
|
|
3113
|
+
if (!finalGatePassed || !lastIterationOk || hardFailure) {
|
|
3114
|
+
loopSuccess = false;
|
|
3115
|
+
} else if (terminationReason === "max_iterations_reached") {
|
|
3116
|
+
const maxIterationsSuccessThreshold = ctx.stage.validation?.enabled === false ? minProgress : 0.5;
|
|
3117
|
+
loopSuccess = finalProgressScore >= maxIterationsSuccessThreshold && completionSummarySupportsSuccess;
|
|
3118
|
+
const lastControllerIteration = loopIterations.at(-1);
|
|
3119
|
+
if (lastControllerIteration?.requirements?.enabled && !lastControllerIteration.requirements.passed) {
|
|
3120
|
+
loopSuccess = false;
|
|
3121
|
+
}
|
|
3122
|
+
} else if (terminationReason === "success" || terminationReason === "goal_satisfied" || terminationReason === "heuristic_complete") {
|
|
3123
|
+
loopSuccess = true;
|
|
3124
|
+
} else {
|
|
3125
|
+
loopSuccess = false;
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
} catch (error) {
|
|
3129
|
+
terminationReason = inferFailureTerminationReason(error, iterationHistory);
|
|
3130
|
+
loopSuccess = false;
|
|
3131
|
+
policyRecorder.emit({
|
|
3132
|
+
policy: policyForTerminationReason(terminationReason),
|
|
3133
|
+
eventType: "terminated",
|
|
3134
|
+
reason: terminationReason,
|
|
3135
|
+
details: {
|
|
3136
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3137
|
+
iterations: iterationHistory.length
|
|
3138
|
+
}
|
|
3139
|
+
});
|
|
3140
|
+
if (ctx.artifactStore) {
|
|
3141
|
+
const failureDiagnostic = createFailureDiagnostic({
|
|
3142
|
+
error,
|
|
3143
|
+
iterationHistory,
|
|
3144
|
+
stageId: ctx.stage.id,
|
|
3145
|
+
terminationReason,
|
|
3146
|
+
allowlist: ctx.stage.allowlist
|
|
3147
|
+
});
|
|
3148
|
+
failureArtifactPersisted = await saveBestEffort(
|
|
3149
|
+
ctx.artifactStore,
|
|
3150
|
+
ctx.stage.id,
|
|
3151
|
+
"autoloop-failure",
|
|
3152
|
+
failureDiagnostic
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
const durationMs = performance2.now() - startTime;
|
|
3157
|
+
if (terminationReason === "timebox_exceeded") {
|
|
3158
|
+
emitBoundaryTriggered({
|
|
3159
|
+
trigger: "ceiling",
|
|
3160
|
+
workloadClass: "bounded-loop",
|
|
3161
|
+
effectiveCeilingMs: timeboxMs,
|
|
3162
|
+
elapsedMs: Math.round(durationMs),
|
|
3163
|
+
source: "autoloop",
|
|
3164
|
+
taskId: ctx.stage.id,
|
|
3165
|
+
metadata: {
|
|
3166
|
+
stageId: ctx.stage.id
|
|
3167
|
+
}
|
|
3168
|
+
});
|
|
3169
|
+
} else if (terminationReason === "progress_stagnation" || terminationReason === "no_op_intervention_exceeded") {
|
|
3170
|
+
emitBoundaryTriggered({
|
|
3171
|
+
trigger: "progress",
|
|
3172
|
+
workloadClass: "bounded-loop",
|
|
3173
|
+
effectiveCeilingMs: timeboxMs,
|
|
3174
|
+
elapsedMs: Math.round(durationMs),
|
|
3175
|
+
source: "autoloop",
|
|
3176
|
+
taskId: ctx.stage.id,
|
|
3177
|
+
stagnantChecks: terminationReason === "progress_stagnation" ? resolveExecutionBoundary("bounded-loop").progress?.maxStagnantChecks : void 0,
|
|
3178
|
+
metadata: {
|
|
3179
|
+
stageId: ctx.stage.id,
|
|
3180
|
+
terminationReason
|
|
3181
|
+
}
|
|
3182
|
+
});
|
|
3183
|
+
}
|
|
3184
|
+
const totalCost = hasTerminalDurableOutcome ? durableResumeState?.priorOutcome?.cost ?? 0 : iterationHistory.reduce((sum, r) => sum + r.cost, 0);
|
|
3185
|
+
const totalIterationsOverall = previousIterationCount + iterationHistory.length;
|
|
3186
|
+
if (terminationReason === "tests_required_not_met" && iterationHistory.length > 0) {
|
|
3187
|
+
const lastIter2 = iterationHistory[iterationHistory.length - 1];
|
|
3188
|
+
if (lastIter2) {
|
|
3189
|
+
const displayPatterns = selectDisplayTestFilePatterns(
|
|
3190
|
+
promptLanguage,
|
|
3191
|
+
config.testFilePatterns
|
|
3192
|
+
);
|
|
3193
|
+
const expectation = displayPatterns && displayPatterns.length > 0 ? `Expected patterns: ${displayPatterns.join(", ")}` : "Follow the existing test conventions of the target project.";
|
|
3194
|
+
lastIter2.error = lastIter2.error ?? `Test files required but none created. ${expectation}`;
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
if (terminationReason === "evidence_unsatisfied" && iterationHistory.length > 0) {
|
|
3198
|
+
const lastIter2 = iterationHistory[iterationHistory.length - 1];
|
|
3199
|
+
if (lastIter2 && runtimeEvidenceCheck && runtimeEvidenceCheck.blockingIssues.length > 0) {
|
|
3200
|
+
const issueList = runtimeEvidenceCheck.blockingIssues.join("; ");
|
|
3201
|
+
lastIter2.error = lastIter2.error ?? `Runtime evidence requirements not satisfied: ${issueList}. Completion blocked until evidence is verified.`;
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
const aggregateTimeMetrics = computeAggregateTimeMetrics(iterationHistory);
|
|
3205
|
+
const costUnavailableReason = firstCostUnavailableReason(iterationHistory);
|
|
3206
|
+
if (ctx.verbose && aggregateTimeMetrics) {
|
|
3207
|
+
log("Aggregate time breakdown:");
|
|
3208
|
+
log(
|
|
3209
|
+
` Provider (model): ${aggregateTimeMetrics.totalProviderMs.toFixed(0)}ms (${aggregateTimeMetrics.providerTimePct.toFixed(1)}%)`
|
|
3210
|
+
);
|
|
3211
|
+
log(` Gates: ${aggregateTimeMetrics.totalGateMs.toFixed(0)}ms`);
|
|
3212
|
+
log(` Repair: ${aggregateTimeMetrics.totalRepairMs.toFixed(0)}ms`);
|
|
3213
|
+
log(` Deterministic work: ${aggregateTimeMetrics.deterministicTimePct.toFixed(1)}%`);
|
|
3214
|
+
log(
|
|
3215
|
+
` No-op iterations: ${aggregateTimeMetrics.noOpIterationCount}/${iterationHistory.length}`
|
|
3216
|
+
);
|
|
3217
|
+
log(` Avg iteration: ${aggregateTimeMetrics.avgIterationMs.toFixed(0)}ms`);
|
|
3218
|
+
}
|
|
3219
|
+
if (ctx.artifactStore) {
|
|
3220
|
+
iterationHistoryArtifactPersisted = await saveBestEffort(
|
|
3221
|
+
ctx.artifactStore,
|
|
3222
|
+
ctx.stage.id,
|
|
3223
|
+
"iteration-history",
|
|
3224
|
+
iterationHistory
|
|
3225
|
+
);
|
|
3226
|
+
resultArtifactPersisted = await saveBestEffort(
|
|
3227
|
+
ctx.artifactStore,
|
|
3228
|
+
ctx.stage.id,
|
|
3229
|
+
"autoloop-result",
|
|
3230
|
+
{
|
|
3231
|
+
totalIterations: totalIterationsOverall,
|
|
3232
|
+
terminationReason,
|
|
3233
|
+
finalProgressScore,
|
|
3234
|
+
loopSuccess,
|
|
3235
|
+
totalDurationMs: durationMs,
|
|
3236
|
+
totalCost,
|
|
3237
|
+
costUnavailableReason,
|
|
3238
|
+
totalTokens,
|
|
3239
|
+
aggregateTimeMetrics,
|
|
3240
|
+
policyEvents: policyRecorder.events
|
|
3241
|
+
}
|
|
3242
|
+
);
|
|
3243
|
+
if (!loopSuccess || iterationHistory.some((r) => r.allowlistDrift)) {
|
|
3244
|
+
const failureDiagnostic = createFailureDiagnostic({
|
|
3245
|
+
error: new Error(`Autoloop terminated: ${terminationReason}`),
|
|
3246
|
+
iterationHistory,
|
|
3247
|
+
stageId: ctx.stage.id,
|
|
3248
|
+
terminationReason,
|
|
3249
|
+
allowlist: ctx.stage.allowlist
|
|
3250
|
+
});
|
|
3251
|
+
await saveBestEffort(
|
|
3252
|
+
ctx.artifactStore,
|
|
3253
|
+
ctx.stage.id,
|
|
3254
|
+
"autoloop-diagnostics",
|
|
3255
|
+
failureDiagnostic
|
|
3256
|
+
);
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
const allowlistMode = ctx.stage.allowlistMode ?? "warn";
|
|
3260
|
+
const gatesForOutcome = [...gatesToRun, ...dynamicGates];
|
|
3261
|
+
let finalGateResultsForOutcome;
|
|
3262
|
+
const lastIter = iterationHistory.at(-1);
|
|
3263
|
+
if (lastIter?.qualityGateCommandResults && gatesForOutcome.length > 0) {
|
|
3264
|
+
finalGateResultsForOutcome = lastIter.qualityGateCommandResults.map((cmdResult, idx) => {
|
|
3265
|
+
const gate = (cmdResult.id ? gatesForOutcome.find((g) => g.id === cmdResult.id) : void 0) ?? gatesForOutcome[idx] ?? {
|
|
3266
|
+
id: `gate-${idx + 1}`,
|
|
3267
|
+
command: cmdResult.command,
|
|
3268
|
+
deterministic: true,
|
|
3269
|
+
severity: cmdResult.mustPass === false ? "warn" : "error",
|
|
3270
|
+
// Preserve persisted authority; default hard when field is absent (legacy).
|
|
3271
|
+
mustPass: cmdResult.mustPass ?? true
|
|
3272
|
+
};
|
|
3273
|
+
const expectedExitCode = gate.expectedExitCode ?? cmdResult.expectedExitCode ?? 0;
|
|
3274
|
+
const passed = typeof cmdResult.passed === "boolean" ? cmdResult.passed : cmdResult.exitCode === expectedExitCode;
|
|
3275
|
+
return {
|
|
3276
|
+
gate,
|
|
3277
|
+
passed,
|
|
3278
|
+
exitCode: cmdResult.exitCode,
|
|
3279
|
+
durationMs: cmdResult.durationMs,
|
|
3280
|
+
outputTail: cmdResult.outputTail,
|
|
3281
|
+
baselinePassed: cmdResult.baselinePassed,
|
|
3282
|
+
isRegression: cmdResult.isRegression
|
|
3283
|
+
};
|
|
3284
|
+
});
|
|
3285
|
+
}
|
|
3286
|
+
const totalRepairAttempted = iterationHistory.some((r) => r.repairAttempted === true);
|
|
3287
|
+
const totalRepairSuccesses = iterationHistory.reduce(
|
|
3288
|
+
(sum, r) => sum + (r.repairSuccesses ?? 0),
|
|
3289
|
+
0
|
|
3290
|
+
);
|
|
3291
|
+
const outcomeMetadata = durableResumeState?.priorOutcome?.outcomeMetadata ?? computeAutoloopOutcome({
|
|
3292
|
+
finalGateResults: finalGateResultsForOutcome,
|
|
3293
|
+
iterationHistory,
|
|
3294
|
+
terminationReason,
|
|
3295
|
+
allowlistMode,
|
|
3296
|
+
repairAttempted: totalRepairAttempted,
|
|
3297
|
+
repairSuccesses: totalRepairSuccesses
|
|
3298
|
+
});
|
|
3299
|
+
const noOpCount = iterationHistory.filter((r) => r.progressMetrics?.wasNoOp).length;
|
|
3300
|
+
const finalIterationCompletionEvidence = iterationHistory.slice().reverse().find((item) => item.completionEvidence !== void 0)?.completionEvidence;
|
|
3301
|
+
result = {
|
|
3302
|
+
stageId: ctx.stage.id,
|
|
3303
|
+
stageType: "autoloop",
|
|
3304
|
+
validationPassed: loopSuccess,
|
|
3305
|
+
cost: totalCost,
|
|
3306
|
+
costUnavailableReason,
|
|
3307
|
+
...repairReviewWarnings.size > 0 ? { warnings: [...repairReviewWarnings] } : {},
|
|
3308
|
+
durationMs,
|
|
3309
|
+
summary: `Autoloop: ${totalIterationsOverall} iterations, ${terminationReason} (${outcomeMetadata.outcome})`,
|
|
3310
|
+
iterationHistory,
|
|
3311
|
+
totalIterations: totalIterationsOverall,
|
|
3312
|
+
terminationReason,
|
|
3313
|
+
finalProgressScore,
|
|
3314
|
+
loopSuccess,
|
|
3315
|
+
outcomeMetadata,
|
|
3316
|
+
aggregateTimeMetrics,
|
|
3317
|
+
policyEvents: policyRecorder.events,
|
|
3318
|
+
iterationHistoryArtifact: iterationHistoryArtifactPersisted ? `${ctx.stage.id}.iteration-history` : void 0,
|
|
3319
|
+
resultArtifact: resultArtifactPersisted ? `${ctx.stage.id}.autoloop-result` : void 0,
|
|
3320
|
+
failureArtifact: terminationReason === "error" && failureArtifactPersisted ? `${ctx.stage.id}.autoloop-failure` : void 0,
|
|
3321
|
+
...finalIterationCompletionEvidence ? { completionEvidence: finalIterationCompletionEvidence } : {}
|
|
3322
|
+
};
|
|
3323
|
+
runSummary = buildAutoloopRunSummary(ctx, result, {
|
|
3324
|
+
runId: generateCompetitionId(),
|
|
3325
|
+
startedAt: new Date(runStartedAtMsForSummary).toISOString(),
|
|
3326
|
+
noOpIterationCount: noOpCount,
|
|
3327
|
+
repairAttempts: totalRepairAttemptsForEscalation,
|
|
3328
|
+
repairSuccesses: totalRepairSuccesses,
|
|
3329
|
+
gatePreset: currentGatePreset,
|
|
3330
|
+
aiValidationShortCircuited
|
|
3331
|
+
});
|
|
3332
|
+
} catch (err) {
|
|
3333
|
+
executionError = err instanceof Error ? err : new Error(String(err));
|
|
3334
|
+
const durationMs = performance2.now() - startTime;
|
|
3335
|
+
policyRecorder.emit({
|
|
3336
|
+
policy: "goal_gate",
|
|
3337
|
+
eventType: "terminated",
|
|
3338
|
+
reason: "error",
|
|
3339
|
+
details: { error: executionError.message }
|
|
3340
|
+
});
|
|
3341
|
+
const outerCatchCost = resumedFromTerminalDiagnostics ? priorTerminalCostForResult ?? 0 : iterationHistory.reduce((sum, record) => sum + record.cost, 0);
|
|
3342
|
+
const costUnavailableReason = firstCostUnavailableReason(iterationHistory);
|
|
3343
|
+
if (ctx.artifactStore) {
|
|
3344
|
+
if (iterationHistory.length > 0) {
|
|
3345
|
+
iterationHistoryArtifactPersisted = await saveBestEffort(
|
|
3346
|
+
ctx.artifactStore,
|
|
3347
|
+
ctx.stage.id,
|
|
3348
|
+
"iteration-history",
|
|
3349
|
+
iterationHistory
|
|
3350
|
+
) || iterationHistoryArtifactPersisted;
|
|
3351
|
+
}
|
|
3352
|
+
failureArtifactPersisted = await saveBestEffort(
|
|
3353
|
+
ctx.artifactStore,
|
|
3354
|
+
ctx.stage.id,
|
|
3355
|
+
"autoloop-failure",
|
|
3356
|
+
createFailureDiagnostic({
|
|
3357
|
+
error: executionError,
|
|
3358
|
+
iterationHistory,
|
|
3359
|
+
stageId: ctx.stage.id,
|
|
3360
|
+
terminationReason: "error",
|
|
3361
|
+
allowlist: ctx.stage.allowlist
|
|
3362
|
+
})
|
|
3363
|
+
) || failureArtifactPersisted;
|
|
3364
|
+
}
|
|
3365
|
+
result = {
|
|
3366
|
+
stageId: ctx.stage.id,
|
|
3367
|
+
stageType: "autoloop",
|
|
3368
|
+
validationPassed: false,
|
|
3369
|
+
cost: outerCatchCost,
|
|
3370
|
+
costUnavailableReason,
|
|
3371
|
+
...repairReviewWarnings.size > 0 ? { warnings: [...repairReviewWarnings] } : {},
|
|
3372
|
+
durationMs,
|
|
3373
|
+
summary: `Autoloop: ${iterationHistory.length} iteration(s), error (red)`,
|
|
3374
|
+
iterationHistory,
|
|
3375
|
+
totalIterations: previousIterationCountForResult + iterationHistory.length,
|
|
3376
|
+
terminationReason: "error",
|
|
3377
|
+
finalProgressScore: iterationHistory.length > 0 ? iterationHistory.at(-1)?.progressScore ?? 0 : 0,
|
|
3378
|
+
loopSuccess: false,
|
|
3379
|
+
outcomeMetadata: {
|
|
3380
|
+
outcome: "red",
|
|
3381
|
+
terminationClass: "execution_error",
|
|
3382
|
+
failedMustPassGates: 0,
|
|
3383
|
+
failedWarnGates: 0,
|
|
3384
|
+
repairAttempted: false,
|
|
3385
|
+
repairSuccesses: 0,
|
|
3386
|
+
hadDrift: false,
|
|
3387
|
+
summary: executionError.message
|
|
3388
|
+
},
|
|
3389
|
+
policyEvents: policyRecorder.events,
|
|
3390
|
+
iterationHistoryArtifact: iterationHistory.length > 0 && iterationHistoryArtifactPersisted ? `${ctx.stage.id}.iteration-history` : void 0,
|
|
3391
|
+
resultArtifact: void 0,
|
|
3392
|
+
failureArtifact: failureArtifactPersisted ? `${ctx.stage.id}.autoloop-failure` : void 0
|
|
3393
|
+
};
|
|
3394
|
+
runSummary = buildAutoloopRunSummary(ctx, result, {
|
|
3395
|
+
runId: generateCompetitionId(),
|
|
3396
|
+
startedAt: new Date(runStartedAtMsForSummary).toISOString(),
|
|
3397
|
+
noOpIterationCount: 0,
|
|
3398
|
+
repairAttempts: 0,
|
|
3399
|
+
repairSuccesses: 0,
|
|
3400
|
+
gatePreset: "fast",
|
|
3401
|
+
aiValidationShortCircuited: false
|
|
3402
|
+
});
|
|
3403
|
+
} finally {
|
|
3404
|
+
await Promise.allSettled(pendingIterationRecords);
|
|
3405
|
+
await finalizeAutoloopRun(ctx, result, runSummary, {
|
|
3406
|
+
preserveDurableDiagnostics: resumedFromTerminalDiagnostics
|
|
3407
|
+
});
|
|
3408
|
+
}
|
|
3409
|
+
if (executionError) {
|
|
3410
|
+
throw executionError;
|
|
3411
|
+
}
|
|
3412
|
+
return result;
|
|
3413
|
+
}
|
|
3414
|
+
function filterDeterministicGates(gates) {
|
|
3415
|
+
const filteredGates = [];
|
|
3416
|
+
const gatesToRun = [];
|
|
3417
|
+
const isNonDeterministic = (gate) => {
|
|
3418
|
+
const c = gate.command.trim();
|
|
3419
|
+
if (!c) return true;
|
|
3420
|
+
if (gate.deterministic === false) return true;
|
|
3421
|
+
const patterns = [
|
|
3422
|
+
/\bpoetic\s+(compete|run|flywheel|sprint)\b/i,
|
|
3423
|
+
/\btests\/e2e\b/i,
|
|
3424
|
+
/\btest:e2e\b/i,
|
|
3425
|
+
/\bvitest\b.*\btests\/e2e\b/i,
|
|
3426
|
+
/\bplaywright\b/i,
|
|
3427
|
+
/\bcypress\b/i,
|
|
3428
|
+
/\b--ai-judge\b/i
|
|
3429
|
+
];
|
|
3430
|
+
return patterns.some((re) => re.test(c));
|
|
3431
|
+
};
|
|
3432
|
+
for (const gate of gates) {
|
|
3433
|
+
const c = gate.command.trim();
|
|
3434
|
+
if (!c) continue;
|
|
3435
|
+
if (isNonDeterministic(gate)) {
|
|
3436
|
+
filteredGates.push(gate);
|
|
3437
|
+
continue;
|
|
3438
|
+
}
|
|
3439
|
+
gatesToRun.push({ ...gate, command: c });
|
|
3440
|
+
}
|
|
3441
|
+
return { gatesToRun, filteredGates };
|
|
3442
|
+
}
|
|
3443
|
+
async function executeProviderIteration(opts) {
|
|
3444
|
+
const provider = resolveFlywheelStageProvider(opts.stage, opts.worktreePath);
|
|
3445
|
+
const executionIntent = resolveProviderExecutionIntentFromStage(opts.stage);
|
|
3446
|
+
const workspaceMode = resolveAutoloopWorkspaceMode(provider, executionIntent);
|
|
3447
|
+
const { applyFlywheelModelDefaults } = await import("./flywheel-model-defaults-7CVKP53V.js");
|
|
3448
|
+
const model = applyFlywheelModelDefaults(opts.stage, provider);
|
|
3449
|
+
const simpleArtifactGoal = parseSimpleArtifactGoal(opts.prompt);
|
|
3450
|
+
const simpleArtifactTools = simpleArtifactGoal !== void 0 ? simpleArtifactToolPolicy(provider) : null;
|
|
3451
|
+
const variantConfig = {
|
|
3452
|
+
provider,
|
|
3453
|
+
model,
|
|
3454
|
+
strategy: "conservative",
|
|
3455
|
+
executionMode: "local",
|
|
3456
|
+
executionIntent,
|
|
3457
|
+
...opts.stage.providerExecutionTransport ?? opts.defaultProviderExecutionTransport ? {
|
|
3458
|
+
executionTransport: opts.stage.providerExecutionTransport ?? opts.defaultProviderExecutionTransport
|
|
3459
|
+
} : {},
|
|
3460
|
+
...workspaceMode ? { workspaceMode } : {},
|
|
3461
|
+
// Force basic CLI execution against the provided stage worktree.
|
|
3462
|
+
isBasicCompetition: true,
|
|
3463
|
+
workflowWorktreePath: opts.worktreePath,
|
|
3464
|
+
disableAutoCommit: true,
|
|
3465
|
+
structuredFileTargets: collectDeclaredStageTargets(opts.stage),
|
|
3466
|
+
...simpleArtifactTools ? {
|
|
3467
|
+
allowedTools: simpleArtifactTools.allowedTools,
|
|
3468
|
+
tools: simpleArtifactTools.allowedTools,
|
|
3469
|
+
disallowedTools: simpleArtifactTools.disallowedTools,
|
|
3470
|
+
providerOutputSuppression: simpleArtifactTools.providerOutputSuppression
|
|
3471
|
+
} : {},
|
|
3472
|
+
...typeof opts.timeoutMs === "number" && opts.timeoutMs > 0 ? { timeoutMs: Math.max(1, Math.floor(opts.timeoutMs)) } : {}
|
|
3473
|
+
};
|
|
3474
|
+
return opts.abortSignal ? opts.executor.executeVariant(generateCompetitionId(), variantConfig, opts.prompt, {
|
|
3475
|
+
abortSignal: opts.abortSignal
|
|
3476
|
+
}) : opts.executor.executeVariant(generateCompetitionId(), variantConfig, opts.prompt);
|
|
3477
|
+
}
|
|
3478
|
+
function classifyAutoloopProviderStatus(input) {
|
|
3479
|
+
const runResult = input.providerResult.result;
|
|
3480
|
+
const providerDiagnostics = normalizeProviderRunDiagnostics(runResult?.providerDiagnostics) ?? normalizeProviderRunDiagnostics(input.providerResult.providerDiagnostics);
|
|
3481
|
+
const exitCode = typeof runResult?.exitCode === "number" ? runResult.exitCode : void 0;
|
|
3482
|
+
const executionStatus = exitCode === void 0 ? input.providerResult.success ? "completed" : "failed" : exitCode === 0 ? "completed" : "failed";
|
|
3483
|
+
const hasLegacyUsableStdout = input.stdoutText.trim().length > 0 || typeof input.providerResult.output === "string" && input.providerResult.output.trim().length > 0;
|
|
3484
|
+
const hasUsableOutput = typeof providerDiagnostics?.finalAnswerPresent === "boolean" ? providerDiagnostics.finalAnswerPresent : hasLegacyUsableStdout;
|
|
3485
|
+
const stderrText = input.stderrText.trim();
|
|
3486
|
+
const diagnosticsLookFatal = providerDiagnosticsReportFatalRuntimeIssue(providerDiagnostics);
|
|
3487
|
+
const stderrLooksFatal = providerDiagnostics === void 0 && looksLikeProviderToolOrRuntimeError(stderrText);
|
|
3488
|
+
if (executionStatus === "failed") {
|
|
3489
|
+
const detail = exitCode === void 0 ? "Provider execution failed before reporting an exit code" : `Provider execution failed with exit code ${exitCode}`;
|
|
3490
|
+
return {
|
|
3491
|
+
executionStatus,
|
|
3492
|
+
outputStatus: hasUsableOutput ? "usable" : "missing",
|
|
3493
|
+
detail,
|
|
3494
|
+
error: input.providerResult.error?.message ?? detail
|
|
3495
|
+
};
|
|
3496
|
+
}
|
|
3497
|
+
if (!hasUsableOutput && (diagnosticsLookFatal || stderrLooksFatal)) {
|
|
3498
|
+
const detail = providerDiagnostics === void 0 ? "Provider completed but stderr reports tool/runtime errors and no usable stdout" : "Provider completed but structured diagnostics report tool/runtime errors and no usable output";
|
|
3499
|
+
const stderrTail = truncateTail(stderrText, 500);
|
|
3500
|
+
return {
|
|
3501
|
+
executionStatus,
|
|
3502
|
+
outputStatus: "invalid",
|
|
3503
|
+
detail,
|
|
3504
|
+
error: stderrTail ? `${detail}: ${stderrTail}` : detail
|
|
3505
|
+
};
|
|
3506
|
+
}
|
|
3507
|
+
return {
|
|
3508
|
+
executionStatus,
|
|
3509
|
+
outputStatus: hasUsableOutput ? "usable" : "missing",
|
|
3510
|
+
detail: hasUsableOutput ? void 0 : "Provider completed without usable output"
|
|
3511
|
+
};
|
|
3512
|
+
}
|
|
3513
|
+
function looksLikeProviderToolOrRuntimeError(stderrText) {
|
|
3514
|
+
if (!stderrText) return false;
|
|
3515
|
+
return [
|
|
3516
|
+
/\btool(?:\s+\w+)?\s+(?:error|failed|failure)\b/i,
|
|
3517
|
+
/\btool call\b.*\b(?:error|failed|failure)\b/i,
|
|
3518
|
+
/\bfailed to (?:call|execute|run) tool\b/i,
|
|
3519
|
+
/\berror executing tool\b/i,
|
|
3520
|
+
/\btransport channel closed\b/i,
|
|
3521
|
+
/\bAuthorizationRequired\b/i,
|
|
3522
|
+
/^error[:\s]/i,
|
|
3523
|
+
/\buncaught (?:exception|error)\b/i
|
|
3524
|
+
].some((pattern) => pattern.test(stderrText));
|
|
3525
|
+
}
|
|
3526
|
+
function isVerificationUnavailableError(message) {
|
|
3527
|
+
if (!message) return false;
|
|
3528
|
+
return /\b(?:tests?|verification)\b[\s\S]{0,40}\bdid not run\b/i.test(message) || /\bverification(?:\s+tool)?\s+unavailable\b/i.test(message);
|
|
3529
|
+
}
|
|
3530
|
+
function detectUnavailableGateVerification(results) {
|
|
3531
|
+
if (!results) return void 0;
|
|
3532
|
+
for (const result of results) {
|
|
3533
|
+
if (result.passed || !result.gate.mustPass || result.isRegression === false) continue;
|
|
3534
|
+
const signal = detectVerificationToolUnavailable(result.gate.command, result.outputTail ?? "");
|
|
3535
|
+
if (signal) {
|
|
3536
|
+
return {
|
|
3537
|
+
command: result.gate.command,
|
|
3538
|
+
message: signal.message
|
|
3539
|
+
};
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
return void 0;
|
|
3543
|
+
}
|
|
3544
|
+
async function evaluateSimpleArtifactGoal(input) {
|
|
3545
|
+
const goal = parseSimpleArtifactGoal(input.prompt);
|
|
3546
|
+
if (!goal) return { satisfied: false };
|
|
3547
|
+
return {
|
|
3548
|
+
satisfied: await isSimpleArtifactGoalSatisfiedForChanges(
|
|
3549
|
+
input.worktreePath,
|
|
3550
|
+
goal,
|
|
3551
|
+
input.changedFiles
|
|
3552
|
+
),
|
|
3553
|
+
goal
|
|
3554
|
+
};
|
|
3555
|
+
}
|
|
3556
|
+
async function getWorktreeChangedFiles(worktreePath) {
|
|
3557
|
+
try {
|
|
3558
|
+
const { stdout } = await execa2("git", ["status", "--porcelain", "--untracked-files=all"], {
|
|
3559
|
+
cwd: worktreePath,
|
|
3560
|
+
stdio: "pipe",
|
|
3561
|
+
timeout: 3e4
|
|
3562
|
+
});
|
|
3563
|
+
const files = parseGitStatusPaths(stdout, { includeOriginalPaths: true }).filter(
|
|
3564
|
+
(file) => file.length > 0
|
|
3565
|
+
);
|
|
3566
|
+
return {
|
|
3567
|
+
files: filterWorktreeRunSentinelPaths(Array.from(new Set(files))).sort(
|
|
3568
|
+
(a, b) => a.localeCompare(b)
|
|
3569
|
+
),
|
|
3570
|
+
readable: true
|
|
3571
|
+
};
|
|
3572
|
+
} catch {
|
|
3573
|
+
return { files: [], readable: false };
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
function buildIterationPrompt(input) {
|
|
3577
|
+
const { basePrompt, iteration, iterationHistory, gateCommands } = input;
|
|
3578
|
+
const acceptanceCriteria = extractAcceptanceCriteria(basePrompt);
|
|
3579
|
+
const acceptanceCriteriaForPrompt = formatAcceptanceCriteriaForPrompt(acceptanceCriteria);
|
|
3580
|
+
const acceptanceCriteriaSection = acceptanceCriteria.length > 0 ? `
|
|
3581
|
+
## Acceptance Criteria (treat as a checklist)
|
|
3582
|
+
${acceptanceCriteriaForPrompt}
|
|
3583
|
+
` : "";
|
|
3584
|
+
const criteriaReportSection = acceptanceCriteria.length > 0 ? '\n## Criteria Report (required every iteration)\nAt the end of your response, output a machine-parseable JSON report exactly like:\n\nPOETIC_CRITERIA_REPORT:\n{\n "criteria": [\n { "id": "AC1", "status": "met", "evidence": ["src/path.ts: explains how criterion is satisfied"] },\n { "id": "AC2", "status": "partial", "evidence": ["..."] },\n { "id": "AC3", "status": "not_met", "evidence": [] }\n ]\n}\n\nRules:\n- Status must be one of: met | partial | not_met\n- Evidence must be concrete (file paths, flags, behavior, outputs)\n- Do not add features beyond the acceptance criteria unless explicitly requested\n' : "";
|
|
3585
|
+
if (iteration <= 1) {
|
|
3586
|
+
const testRequirementSection = input.requiresTests === true ? "\n\n## Test Requirement\nThis autoloop requires test files to be created. " + renderTestRequirementBody(input.promptLanguage, input.testFilePatterns) + "\n" : "";
|
|
3587
|
+
if (gateCommands.length === 0)
|
|
3588
|
+
return `${basePrompt}${acceptanceCriteriaSection}${testRequirementSection}${criteriaReportSection}`;
|
|
3589
|
+
return `${basePrompt}
|
|
3590
|
+
|
|
3591
|
+
` + acceptanceCriteriaSection + testRequirementSection + "## Autoloop Quality Gates\nMake these commands pass (they will be re-run after each iteration):\n" + gateCommands.map((c) => `- ${c}`).join("\n") + `
|
|
3592
|
+
${criteriaReportSection}`;
|
|
3593
|
+
}
|
|
3594
|
+
const prev = iterationHistory.at(-1);
|
|
3595
|
+
const parts = [`${basePrompt}${acceptanceCriteriaSection}${criteriaReportSection}`];
|
|
3596
|
+
if (input.requiresTests === true) {
|
|
3597
|
+
parts.push(
|
|
3598
|
+
"\n## Test Requirement\nThis autoloop requires test files to be created. " + renderTestRequirementBody(input.promptLanguage, input.testFilePatterns)
|
|
3599
|
+
);
|
|
3600
|
+
}
|
|
3601
|
+
if (gateCommands.length > 0) {
|
|
3602
|
+
parts.push(
|
|
3603
|
+
"\n## Autoloop Quality Gates\nMake these commands pass (they will be re-run after each iteration):\n" + gateCommands.map((c) => `- ${c}`).join("\n")
|
|
3604
|
+
);
|
|
3605
|
+
}
|
|
3606
|
+
if (prev) {
|
|
3607
|
+
const changed = prev.changedFiles.slice(0, 50).map((f) => `- ${f}`).join("\n");
|
|
3608
|
+
parts.push(`
|
|
3609
|
+
## Previous Iteration Feedback (iteration ${prev.iteration})`);
|
|
3610
|
+
if (prev.error) parts.push(`- Provider error: ${prev.error}`);
|
|
3611
|
+
if (prev.allowlistDrift && prev.allowlistViolations && prev.allowlistViolations.length > 0) {
|
|
3612
|
+
const driftFiles = prev.allowlistViolations.slice(0, 20).map((f) => ` - ${f}`).join("\n");
|
|
3613
|
+
parts.push(
|
|
3614
|
+
`
|
|
3615
|
+
### Allowlist Drift Detected
|
|
3616
|
+
The following ${prev.allowlistViolations.length} file(s) are outside the stage allowlist:
|
|
3617
|
+
${driftFiles}` + (prev.allowlistViolations.length > 20 ? `
|
|
3618
|
+
... and ${prev.allowlistViolations.length - 20} more` : "") + "\n\n**Action required**: Revert changes to these files or ensure they are within the allowlist scope."
|
|
3619
|
+
);
|
|
3620
|
+
}
|
|
3621
|
+
if (prev.allowlistRevertedFiles && prev.allowlistRevertedFiles.length > 0) {
|
|
3622
|
+
const reverted = prev.allowlistRevertedFiles.slice(0, 20).map((f) => ` - ${f}`).join("\n");
|
|
3623
|
+
parts.push(
|
|
3624
|
+
`
|
|
3625
|
+
### Allowlist Auto-Revert (strict mode)
|
|
3626
|
+
Flywheel automatically reverted edits to ${prev.allowlistRevertedFiles.length} file(s) outside the stage allowlist:
|
|
3627
|
+
${reverted}` + (prev.allowlistRevertedFiles.length > 20 ? `
|
|
3628
|
+
... and ${prev.allowlistRevertedFiles.length - 20} more` : "") + "\n\n**Action required**: Keep all edits within the allowlist; do not reintroduce these changes."
|
|
3629
|
+
);
|
|
3630
|
+
}
|
|
3631
|
+
if (prev.qualityGatePassed === false) {
|
|
3632
|
+
parts.push(`- Quality gate failed: ${prev.qualityGateError ?? "unknown"}`);
|
|
3633
|
+
const tail = prev.qualityGateCommandResults?.find((r) => r.exitCode !== 0)?.outputTail ?? prev.qualityGateCommandResults?.at(-1)?.outputTail;
|
|
3634
|
+
if (tail) {
|
|
3635
|
+
parts.push(`
|
|
3636
|
+
### Failing Command Output (tail)
|
|
3637
|
+
\`\`\`
|
|
3638
|
+
${tail}
|
|
3639
|
+
\`\`\``);
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
if (changed) parts.push(`
|
|
3643
|
+
### Changed Files (latest)
|
|
3644
|
+
${changed}`);
|
|
3645
|
+
}
|
|
3646
|
+
return parts.join("\n");
|
|
3647
|
+
}
|
|
3648
|
+
async function revertFilesToHead(worktreePath, files) {
|
|
3649
|
+
const uniqueFiles = Array.from(new Set(files)).filter((f) => typeof f === "string" && f.length);
|
|
3650
|
+
if (uniqueFiles.length === 0) return;
|
|
3651
|
+
try {
|
|
3652
|
+
await execa2("git", ["checkout", "--", ...uniqueFiles], {
|
|
3653
|
+
cwd: worktreePath,
|
|
3654
|
+
stdio: "pipe",
|
|
3655
|
+
reject: false,
|
|
3656
|
+
timeout: 3e4,
|
|
3657
|
+
env: LITERAL_PATHSPEC_ENV2
|
|
3658
|
+
});
|
|
3659
|
+
} catch {
|
|
3660
|
+
}
|
|
3661
|
+
try {
|
|
3662
|
+
await execa2("git", ["clean", "-fd", "--", ...uniqueFiles], {
|
|
3663
|
+
cwd: worktreePath,
|
|
3664
|
+
stdio: "pipe",
|
|
3665
|
+
reject: false,
|
|
3666
|
+
timeout: 3e4,
|
|
3667
|
+
env: LITERAL_PATHSPEC_ENV2
|
|
3668
|
+
});
|
|
3669
|
+
} catch {
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
async function saveBestEffort(store, stageId, name, payload) {
|
|
3673
|
+
try {
|
|
3674
|
+
await store.save(stageId, name, "json", JSON.stringify(payload, null, 2));
|
|
3675
|
+
return true;
|
|
3676
|
+
} catch {
|
|
3677
|
+
return false;
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
function createFailureDiagnostic(input) {
|
|
3681
|
+
const lastIteration = input.iterationHistory.at(-1);
|
|
3682
|
+
const failingGateCommand = lastIteration?.qualityGateCommandResults?.find(
|
|
3683
|
+
(r) => r.exitCode !== 0
|
|
3684
|
+
);
|
|
3685
|
+
return {
|
|
3686
|
+
stageId: input.stageId,
|
|
3687
|
+
terminationReason: input.terminationReason,
|
|
3688
|
+
errorMessage: input.error instanceof Error ? input.error.message : String(input.error),
|
|
3689
|
+
errorStack: input.error instanceof Error ? input.error.stack : void 0,
|
|
3690
|
+
lastIteration: lastIteration ? {
|
|
3691
|
+
iteration: lastIteration.iteration,
|
|
3692
|
+
changedFiles: lastIteration.changedFiles,
|
|
3693
|
+
allowlistViolations: lastIteration.allowlistViolations,
|
|
3694
|
+
qualityGateError: lastIteration.qualityGateError,
|
|
3695
|
+
failingCommand: failingGateCommand ? {
|
|
3696
|
+
command: failingGateCommand.command,
|
|
3697
|
+
exitCode: failingGateCommand.exitCode,
|
|
3698
|
+
outputTail: failingGateCommand.outputTail
|
|
3699
|
+
} : void 0
|
|
3700
|
+
} : void 0,
|
|
3701
|
+
allowlist: input.allowlist,
|
|
3702
|
+
totalIterations: input.iterationHistory.length,
|
|
3703
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3704
|
+
};
|
|
3705
|
+
}
|
|
3706
|
+
function inferFailureTerminationReason(error, iterationHistory = []) {
|
|
3707
|
+
const lastIterationSignal = iterationHistory.at(-1)?.terminationSignal;
|
|
3708
|
+
if (lastIterationSignal) {
|
|
3709
|
+
return lastIterationSignal;
|
|
3710
|
+
}
|
|
3711
|
+
if (error instanceof AutoloopTerminationError) {
|
|
3712
|
+
return error.terminationSignal;
|
|
3713
|
+
}
|
|
3714
|
+
return "error";
|
|
3715
|
+
}
|
|
3716
|
+
async function inferTerminationReason(input) {
|
|
3717
|
+
const lastIteration = input.iterationHistory.at(-1);
|
|
3718
|
+
if (input.controllerTerminationReason) {
|
|
3719
|
+
const controllerReason = input.controllerTerminationReason;
|
|
3720
|
+
if (controllerReason === "completion_signal" || controllerReason === "semantic_complete" || controllerReason === "deterministic_complete" || controllerReason === "heuristic_complete" || controllerReason === "progress_stagnation") {
|
|
3721
|
+
} else {
|
|
3722
|
+
return mapControllerReasonToFlywheel(controllerReason);
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
if (lastIteration?.terminationSignal && lastIteration.terminationSignal !== "goal_satisfied") {
|
|
3726
|
+
return lastIteration.terminationSignal;
|
|
3727
|
+
}
|
|
3728
|
+
if (input.gatesEnabled && input.iterationHistory.length > 0 && input.iterationHistory.at(-1)?.qualityGatePassed !== true) {
|
|
3729
|
+
const errorText = input.iterationHistory.at(-1)?.error ?? "";
|
|
3730
|
+
if (/timebox exhausted before quality gates/i.test(errorText)) {
|
|
3731
|
+
return "timebox_exceeded";
|
|
3732
|
+
}
|
|
3733
|
+
return "quality_gate_failure";
|
|
3734
|
+
}
|
|
3735
|
+
const hasBaselineOnlyFailure = lastIteration?.qualityGateCommandResults?.some(
|
|
3736
|
+
(gate) => gate.mustPass !== false && gate.passed === false && gate.isRegression === false
|
|
3737
|
+
);
|
|
3738
|
+
const hasPassingGateEvidence = lastIteration?.qualityGateCommandResults?.some(
|
|
3739
|
+
(gate) => gate.mustPass !== false && gate.passed === true
|
|
3740
|
+
);
|
|
3741
|
+
if (hasBaselineOnlyFailure && !hasPassingGateEvidence) {
|
|
3742
|
+
return "baseline_blocked";
|
|
3743
|
+
}
|
|
3744
|
+
if (lastIteration?.terminationSignal === "goal_satisfied") {
|
|
3745
|
+
return "goal_satisfied";
|
|
3746
|
+
}
|
|
3747
|
+
if (input.controllerTerminationReason === "progress_stagnation") {
|
|
3748
|
+
return "progress_stagnation";
|
|
3749
|
+
}
|
|
3750
|
+
if (input.requiresTests === true) {
|
|
3751
|
+
const allChangedFiles = input.iterationHistory.flatMap((r) => r.changedFiles);
|
|
3752
|
+
const testFiles = await detectTestFiles(allChangedFiles, input.testFilePatterns);
|
|
3753
|
+
if (testFiles.length === 0) {
|
|
3754
|
+
return "tests_required_not_met";
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
if (input.runtimeEvidenceCheck && !input.runtimeEvidenceCheck.passed) {
|
|
3758
|
+
return "evidence_unsatisfied";
|
|
3759
|
+
}
|
|
3760
|
+
if (input.iterationHistory.length >= input.maxIterations) return "max_iterations_reached";
|
|
3761
|
+
const lastTwo = input.loopIterations.slice(-2);
|
|
3762
|
+
if (lastTwo.length === 2) {
|
|
3763
|
+
const prev = lastTwo[0];
|
|
3764
|
+
const curr = lastTwo[1];
|
|
3765
|
+
const noDelta = Math.abs(curr.progress.score - prev.progress.score) < input.minProgress;
|
|
3766
|
+
const bothNotSignificant = !prev.progress.isSignificant && !curr.progress.isSignificant;
|
|
3767
|
+
const stable = curr.progress.trend === "stable";
|
|
3768
|
+
if (stable && noDelta && bothNotSignificant) return "progress_stagnation";
|
|
3769
|
+
}
|
|
3770
|
+
if (input.controllerTerminationReason) {
|
|
3771
|
+
if (!hasFlywheelSubstantiveCompletionEvidence(input) && isSuccessLikeControllerReason(input.controllerTerminationReason)) {
|
|
3772
|
+
return "evidence_unsatisfied";
|
|
3773
|
+
}
|
|
3774
|
+
return mapControllerReasonToFlywheel(input.controllerTerminationReason);
|
|
3775
|
+
}
|
|
3776
|
+
return "error";
|
|
3777
|
+
}
|
|
3778
|
+
function hasFlywheelSubstantiveCompletionEvidence(input) {
|
|
3779
|
+
if (input.iterationHistory.some(
|
|
3780
|
+
(iteration) => iteration.changedFiles.length > 0 || iteration.qualityGatePassed === true || iteration.terminationSignal === "goal_satisfied"
|
|
3781
|
+
)) {
|
|
3782
|
+
return true;
|
|
3783
|
+
}
|
|
3784
|
+
return input.runtimeEvidenceConfigured === true && input.runtimeEvidenceCheck?.passed === true;
|
|
3785
|
+
}
|
|
3786
|
+
function computeAutoloopOutcome(input) {
|
|
3787
|
+
const {
|
|
3788
|
+
finalGateResults,
|
|
3789
|
+
iterationHistory,
|
|
3790
|
+
terminationReason,
|
|
3791
|
+
allowlistMode,
|
|
3792
|
+
repairAttempted,
|
|
3793
|
+
repairSuccesses
|
|
3794
|
+
} = input;
|
|
3795
|
+
const failedMustPassGates = finalGateResults?.filter((r) => !r.passed && r.gate.mustPass && r.isRegression !== false).length ?? 0;
|
|
3796
|
+
const failedWarnGates = finalGateResults?.filter((r) => !r.passed && !r.gate.mustPass).length ?? 0;
|
|
3797
|
+
const baselineOnlyFailures = finalGateResults?.filter((r) => !r.passed && r.gate.mustPass && r.isRegression === false).length ?? 0;
|
|
3798
|
+
const lastFailingGate = finalGateResults?.find((r) => !r.passed && r.isRegression !== false);
|
|
3799
|
+
const gatesExecuted = finalGateResults !== void 0 && finalGateResults.length > 0;
|
|
3800
|
+
const hadDrift = iterationHistory.some((r) => r.allowlistDrift === true);
|
|
3801
|
+
let outcome;
|
|
3802
|
+
let summary;
|
|
3803
|
+
const terminationClassByReason = {
|
|
3804
|
+
allowlist_violation: "allowlist_violation",
|
|
3805
|
+
baseline_blocked: "quality_gate",
|
|
3806
|
+
error: "execution_error",
|
|
3807
|
+
duplicate_change_loop: "no_op_loop",
|
|
3808
|
+
evidence_unsatisfied: "evidence_unsatisfied",
|
|
3809
|
+
goal_satisfied: "success",
|
|
3810
|
+
heuristic_complete: "success",
|
|
3811
|
+
max_iterations_reached: "iteration_budget",
|
|
3812
|
+
no_op_intervention_exceeded: "no_op_loop",
|
|
3813
|
+
no_op_loop: "no_op_loop",
|
|
3814
|
+
progress_stagnation: "progress_stagnation",
|
|
3815
|
+
quality_gate_failure: "quality_gate",
|
|
3816
|
+
resource_exhaustion: "resource_limit",
|
|
3817
|
+
success: "success",
|
|
3818
|
+
tests_required_not_met: "quality_gate",
|
|
3819
|
+
timebox_exceeded: "timebox",
|
|
3820
|
+
verification_unavailable: "verification_unavailable"
|
|
3821
|
+
};
|
|
3822
|
+
const terminationClass = terminationClassByReason[terminationReason];
|
|
3823
|
+
if (terminationReason === "allowlist_violation" && allowlistMode === "strict") {
|
|
3824
|
+
outcome = "red";
|
|
3825
|
+
summary = "Hard allowlist violation (strict mode)";
|
|
3826
|
+
} else if (terminationReason === "resource_exhaustion") {
|
|
3827
|
+
outcome = "red";
|
|
3828
|
+
summary = "Resource limit (memory/CPU/process) exhausted before completion";
|
|
3829
|
+
} else if (terminationReason === "error") {
|
|
3830
|
+
outcome = "red";
|
|
3831
|
+
summary = "Unrecoverable execution error";
|
|
3832
|
+
} else if (terminationReason === "verification_unavailable") {
|
|
3833
|
+
outcome = "yellow";
|
|
3834
|
+
summary = "Verification unavailable; completion is unverified by deterministic gates";
|
|
3835
|
+
} else if (terminationReason === "baseline_blocked") {
|
|
3836
|
+
outcome = "yellow";
|
|
3837
|
+
summary = `${baselineOnlyFailures} baseline gate failure(s) remain; zero-green verification is not success`;
|
|
3838
|
+
} else if (failedMustPassGates > 0) {
|
|
3839
|
+
outcome = "yellow";
|
|
3840
|
+
summary = `${failedMustPassGates} must-pass gate(s) still failing after ${repairAttempted ? "repair attempts" : "iterations"}`;
|
|
3841
|
+
} else if (hadDrift && allowlistMode === "warn") {
|
|
3842
|
+
outcome = "yellow";
|
|
3843
|
+
summary = "Allowlist drift detected (warn mode)";
|
|
3844
|
+
} else if (hadDrift && allowlistMode === "filter") {
|
|
3845
|
+
outcome = "yellow";
|
|
3846
|
+
summary = "Allowlist drift filtered (filter mode)";
|
|
3847
|
+
} else if (failedWarnGates > 0) {
|
|
3848
|
+
outcome = "yellow";
|
|
3849
|
+
summary = `${failedWarnGates} warn-level gate(s) failing`;
|
|
3850
|
+
} else if (!gatesExecuted) {
|
|
3851
|
+
outcome = "yellow";
|
|
3852
|
+
summary = "No deterministic quality gates were executed; completion is unverified by gates";
|
|
3853
|
+
} else {
|
|
3854
|
+
outcome = "green";
|
|
3855
|
+
summary = baselineOnlyFailures > 0 ? `No new gate regressions; ${baselineOnlyFailures} baseline gate failure(s) remain` : `All gates passed${repairAttempted ? ` (after ${repairSuccesses} repair iteration(s))` : ""}`;
|
|
3856
|
+
}
|
|
3857
|
+
return {
|
|
3858
|
+
outcome,
|
|
3859
|
+
terminationClass,
|
|
3860
|
+
failedMustPassGates,
|
|
3861
|
+
failedWarnGates,
|
|
3862
|
+
lastFailingGateId: lastFailingGate?.gate.id,
|
|
3863
|
+
repairAttempted,
|
|
3864
|
+
repairSuccesses,
|
|
3865
|
+
hadDrift,
|
|
3866
|
+
summary
|
|
3867
|
+
};
|
|
3868
|
+
}
|
|
3869
|
+
function computeAggregateTimeMetrics(iterationHistory) {
|
|
3870
|
+
if (iterationHistory.length === 0) {
|
|
3871
|
+
return {
|
|
3872
|
+
totalProviderMs: 0,
|
|
3873
|
+
totalGateMs: 0,
|
|
3874
|
+
totalRepairMs: 0,
|
|
3875
|
+
totalTestMs: 0,
|
|
3876
|
+
totalBuildMs: 0,
|
|
3877
|
+
totalOverheadMs: 0,
|
|
3878
|
+
avgIterationMs: 0,
|
|
3879
|
+
noOpIterationCount: 0,
|
|
3880
|
+
providerTimePct: 0,
|
|
3881
|
+
deterministicTimePct: 0
|
|
3882
|
+
};
|
|
3883
|
+
}
|
|
3884
|
+
let totalProviderMs = 0;
|
|
3885
|
+
let totalGateMs = 0;
|
|
3886
|
+
let totalRepairMs = 0;
|
|
3887
|
+
let totalTestMs = 0;
|
|
3888
|
+
let totalBuildMs = 0;
|
|
3889
|
+
let totalOverheadMs = 0;
|
|
3890
|
+
let totalDurationMs = 0;
|
|
3891
|
+
let noOpIterationCount = 0;
|
|
3892
|
+
for (const iter of iterationHistory) {
|
|
3893
|
+
if (iter.timeBreakdown) {
|
|
3894
|
+
totalProviderMs += iter.timeBreakdown.providerMs;
|
|
3895
|
+
totalGateMs += iter.timeBreakdown.gateMs;
|
|
3896
|
+
totalRepairMs += iter.timeBreakdown.repairMs;
|
|
3897
|
+
totalTestMs += iter.timeBreakdown.testMs ?? 0;
|
|
3898
|
+
totalBuildMs += iter.timeBreakdown.buildMs ?? 0;
|
|
3899
|
+
totalOverheadMs += iter.timeBreakdown.overheadMs;
|
|
3900
|
+
}
|
|
3901
|
+
totalDurationMs += iter.durationMs;
|
|
3902
|
+
if (iter.progressMetrics?.wasNoOp) {
|
|
3903
|
+
noOpIterationCount++;
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
const avgIterationMs = totalDurationMs / iterationHistory.length;
|
|
3907
|
+
const providerTimePct = totalDurationMs > 0 ? totalProviderMs / totalDurationMs * 100 : 0;
|
|
3908
|
+
const deterministicMs = totalGateMs + totalRepairMs + totalTestMs + totalBuildMs;
|
|
3909
|
+
const deterministicTimePct = totalDurationMs > 0 ? deterministicMs / totalDurationMs * 100 : 0;
|
|
3910
|
+
return {
|
|
3911
|
+
totalProviderMs,
|
|
3912
|
+
totalGateMs,
|
|
3913
|
+
totalRepairMs,
|
|
3914
|
+
totalTestMs,
|
|
3915
|
+
totalBuildMs,
|
|
3916
|
+
totalOverheadMs,
|
|
3917
|
+
avgIterationMs,
|
|
3918
|
+
noOpIterationCount,
|
|
3919
|
+
providerTimePct,
|
|
3920
|
+
deterministicTimePct
|
|
3921
|
+
};
|
|
3922
|
+
}
|
|
3923
|
+
function computeIterationProgressMetrics(input) {
|
|
3924
|
+
const { changedFiles, qualityGate, previousIteration } = input;
|
|
3925
|
+
const hadDiff = changedFiles.length > 0;
|
|
3926
|
+
const wasNoOp = !hadDiff;
|
|
3927
|
+
let failingTestsEnd;
|
|
3928
|
+
let coverageEnd;
|
|
3929
|
+
if (qualityGate?.commandResults) {
|
|
3930
|
+
for (const result of qualityGate.commandResults) {
|
|
3931
|
+
const isTestCommand = describeVerificationCommand(result.command).kind === "test";
|
|
3932
|
+
if (isTestCommand && result.exitCode !== 0) {
|
|
3933
|
+
const parsedFailingTestCount = parseFailingTestCount(result.command, result.outputTail);
|
|
3934
|
+
if (parsedFailingTestCount !== void 0) {
|
|
3935
|
+
failingTestsEnd = parsedFailingTestCount;
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
const failingTestsStart = previousIteration?.progressMetrics?.failingTestsEnd;
|
|
3941
|
+
const failingTestsDelta = failingTestsStart !== void 0 && failingTestsEnd !== void 0 ? failingTestsEnd - failingTestsStart : void 0;
|
|
3942
|
+
const coverageStart = previousIteration?.progressMetrics?.coverageEnd;
|
|
3943
|
+
const coverageDelta = coverageStart !== void 0 && coverageEnd !== void 0 ? coverageEnd - coverageStart : void 0;
|
|
3944
|
+
return {
|
|
3945
|
+
failingTestsStart,
|
|
3946
|
+
failingTestsEnd,
|
|
3947
|
+
failingTestsDelta,
|
|
3948
|
+
coverageStart,
|
|
3949
|
+
coverageEnd,
|
|
3950
|
+
coverageDelta,
|
|
3951
|
+
hadDiff,
|
|
3952
|
+
wasNoOp
|
|
3953
|
+
};
|
|
3954
|
+
}
|
|
3955
|
+
function parseFailingTestCount(command, output) {
|
|
3956
|
+
const descriptor = describeVerificationCommand(command);
|
|
3957
|
+
if (descriptor.kind !== "test") return void 0;
|
|
3958
|
+
const text = output?.trim();
|
|
3959
|
+
if (!text) return void 0;
|
|
3960
|
+
if (descriptor.language === "node") {
|
|
3961
|
+
const vitestTestsLine = /^\s*Tests?\s+(\d+)\s+failed\b/im.exec(text);
|
|
3962
|
+
if (vitestTestsLine?.[1]) return Number(vitestTestsLine[1]);
|
|
3963
|
+
const jestTestsLine = /^\s*Tests?:.*?\b(\d+)\s+failed\b/im.exec(text);
|
|
3964
|
+
if (jestTestsLine?.[1]) return Number(jestTestsLine[1]);
|
|
3965
|
+
}
|
|
3966
|
+
if (descriptor.language === "python") {
|
|
3967
|
+
const pytestSummary = /^\s*=+\s+.*?\b([1-9]\d*)\s+failed\b.*?=+\s*$/im.exec(text) ?? /^\s*([1-9]\d*)\s+failed\b.*\bin\s+\d+(?:\.\d+)?s\s*$/im.exec(text);
|
|
3968
|
+
if (pytestSummary?.[1]) return Number(pytestSummary[1]);
|
|
3969
|
+
}
|
|
3970
|
+
if (descriptor.language === "go") {
|
|
3971
|
+
const goFailures = text.match(/^--- FAIL:/gm);
|
|
3972
|
+
if (goFailures && goFailures.length > 0) return goFailures.length;
|
|
3973
|
+
}
|
|
3974
|
+
if (descriptor.language === "rust") {
|
|
3975
|
+
const cargoSummary = /\btest result:\s+FAILED\..*?\b(\d+)\s+failed\b/im.exec(text);
|
|
3976
|
+
if (cargoSummary?.[1]) return Number(cargoSummary[1]);
|
|
3977
|
+
}
|
|
3978
|
+
if (descriptor.language === "swift") {
|
|
3979
|
+
const swiftSummary = /\bExecuted\s+\d+\s+tests?,\s+with\s+(\d+)\s+failures?\b/im.exec(text);
|
|
3980
|
+
if (swiftSummary?.[1]) return Number(swiftSummary[1]);
|
|
3981
|
+
}
|
|
3982
|
+
return void 0;
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3985
|
+
export {
|
|
3986
|
+
simpleArtifactToolPolicy,
|
|
3987
|
+
mapControllerReasonToFlywheel,
|
|
3988
|
+
formatRepairSuppressionWarning,
|
|
3989
|
+
snapshotRepairSuppressions,
|
|
3990
|
+
detectNewRepairSuppressions,
|
|
3991
|
+
captureAutomaticRepairScopeBaseline,
|
|
3992
|
+
normalizeGates,
|
|
3993
|
+
runAllGates,
|
|
3994
|
+
resolveFlywheelProvider,
|
|
3995
|
+
selectDisplayTestFilePatterns,
|
|
3996
|
+
selectPrimaryPromptLanguage,
|
|
3997
|
+
detectTestFiles,
|
|
3998
|
+
verifyRuntimeEvidence,
|
|
3999
|
+
reconstructExhaustedAutoloopOutcome,
|
|
4000
|
+
classifyAutoloopGateTypeForTest,
|
|
4001
|
+
isAutoloopStage,
|
|
4002
|
+
executeAutoloopStage,
|
|
4003
|
+
isVerificationUnavailableError,
|
|
4004
|
+
detectUnavailableGateVerification,
|
|
4005
|
+
computeIterationProgressMetrics
|
|
4006
|
+
};
|
|
4007
|
+
//# sourceMappingURL=chunk-R7XDDX2A.js.map
|