@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,4196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createCliBreadcrumb,
|
|
4
|
+
evaluateExecutionSpec
|
|
5
|
+
} from "./chunk-VVLLL7I4.js";
|
|
6
|
+
import {
|
|
7
|
+
ProcessLifecycleManager,
|
|
8
|
+
isNativeResearchEnabledFor,
|
|
9
|
+
scanWebToolEvents
|
|
10
|
+
} from "./chunk-LG3PDRTR.js";
|
|
11
|
+
import {
|
|
12
|
+
allFailuresAreRegistered,
|
|
13
|
+
getOrCaptureVerificationBaseline,
|
|
14
|
+
loadKnownPreExistingFailures,
|
|
15
|
+
verificationOutcomesFromGateArtifacts,
|
|
16
|
+
withValidationSlot
|
|
17
|
+
} from "./chunk-XNO4FM6X.js";
|
|
18
|
+
import "./chunk-PYITM4N2.js";
|
|
19
|
+
import {
|
|
20
|
+
ExitCodeError
|
|
21
|
+
} from "./chunk-FUVP64ER.js";
|
|
22
|
+
import "./chunk-OMX5VL43.js";
|
|
23
|
+
import {
|
|
24
|
+
classifyTask
|
|
25
|
+
} from "./chunk-FQ7FSZRW.js";
|
|
26
|
+
import {
|
|
27
|
+
presentUntrustedRepairEvidence,
|
|
28
|
+
renderRepairConstraints,
|
|
29
|
+
renderUntrustedCommandOutput,
|
|
30
|
+
renderUntrustedFailingTests
|
|
31
|
+
} from "./chunk-LRGMHROI.js";
|
|
32
|
+
import "./chunk-QNOB37UH.js";
|
|
33
|
+
import "./chunk-ACKIPAOJ.js";
|
|
34
|
+
import {
|
|
35
|
+
FIELD_DISPLAY_NAMES,
|
|
36
|
+
generateSpecQualitySignals
|
|
37
|
+
} from "./chunk-P6VQROCO.js";
|
|
38
|
+
import {
|
|
39
|
+
WORKER_CLEANUP_BUDGET_MS,
|
|
40
|
+
estimateTimeoutCompletenessFromScope,
|
|
41
|
+
parseShortStatSummary
|
|
42
|
+
} from "./chunk-IUXNGKA6.js";
|
|
43
|
+
import {
|
|
44
|
+
isSensitiveKey,
|
|
45
|
+
sanitizeSynthesisArtifactForPersistence,
|
|
46
|
+
sanitizeSynthesisStringForPersistence
|
|
47
|
+
} from "./chunk-GG6KJYW6.js";
|
|
48
|
+
import {
|
|
49
|
+
ACTIVE_HANDLES_MARKER_PREFIX,
|
|
50
|
+
CLEANUP_COMPLETE_MARKER_PREFIX,
|
|
51
|
+
CLEANUP_START_MARKER_PREFIX,
|
|
52
|
+
POST_PROVIDER_PHASES_MARKER_PREFIX,
|
|
53
|
+
RESULT_WRITTEN_MARKER_PREFIX
|
|
54
|
+
} from "./chunk-BBHRN366.js";
|
|
55
|
+
import {
|
|
56
|
+
computeProcessReliabilityMetrics,
|
|
57
|
+
sanitizeVariantResult
|
|
58
|
+
} from "./chunk-6EQKFDBH.js";
|
|
59
|
+
import {
|
|
60
|
+
getVerificationResult,
|
|
61
|
+
hasVerifiedTouchedTestFailure
|
|
62
|
+
} from "./chunk-I4NPDSCB.js";
|
|
63
|
+
import "./chunk-OA4GPILI.js";
|
|
64
|
+
import "./chunk-NEQUQHOX.js";
|
|
65
|
+
import {
|
|
66
|
+
applyExactArtifactGoalSuccessGate,
|
|
67
|
+
isSimpleArtifactGoalSatisfied,
|
|
68
|
+
parseSimpleArtifactGoal
|
|
69
|
+
} from "./chunk-OGMSYL6J.js";
|
|
70
|
+
import {
|
|
71
|
+
PROVIDER_ACTIVITY_WORKER_EVENT_PREFIX,
|
|
72
|
+
PROVIDER_OUTPUT_WORKER_EVENT_PREFIX,
|
|
73
|
+
ResultStreamer
|
|
74
|
+
} from "./chunk-HMVQIPH3.js";
|
|
75
|
+
import "./chunk-GHTZSQB7.js";
|
|
76
|
+
import "./chunk-RE5VZDFQ.js";
|
|
77
|
+
import "./chunk-4RCCF5BR.js";
|
|
78
|
+
import {
|
|
79
|
+
isValidationArtifactEntry
|
|
80
|
+
} from "./chunk-EJ4ZAWLV.js";
|
|
81
|
+
import {
|
|
82
|
+
analyzeRepairScope,
|
|
83
|
+
buildRepairDiffSnapshot,
|
|
84
|
+
resolveRepairArtifactPolicy
|
|
85
|
+
} from "./chunk-XHEKQENB.js";
|
|
86
|
+
import {
|
|
87
|
+
compareVerificationStates,
|
|
88
|
+
extractStableVerificationState,
|
|
89
|
+
parseVerificationErrorsForChannel
|
|
90
|
+
} from "./chunk-WIRN3F75.js";
|
|
91
|
+
import {
|
|
92
|
+
captureWorktreeDiff,
|
|
93
|
+
normalizeWorktreeDiff
|
|
94
|
+
} from "./chunk-QVFV5IP2.js";
|
|
95
|
+
import {
|
|
96
|
+
getGitChangeSummary
|
|
97
|
+
} from "./chunk-JKBHJQMT.js";
|
|
98
|
+
import "./chunk-5D4A7E3D.js";
|
|
99
|
+
import "./chunk-Y5VN7DMP.js";
|
|
100
|
+
import {
|
|
101
|
+
runDeterministicTestGateWithArtifacts,
|
|
102
|
+
runLintOnDiffWithArtifacts
|
|
103
|
+
} from "./chunk-3ZPQLGV7.js";
|
|
104
|
+
import {
|
|
105
|
+
deriveVariantOutcome
|
|
106
|
+
} from "./chunk-ATVKCSGT.js";
|
|
107
|
+
import "./chunk-72XCRQED.js";
|
|
108
|
+
import {
|
|
109
|
+
extractChangedFilesMeta
|
|
110
|
+
} from "./chunk-CFBIG37O.js";
|
|
111
|
+
import "./chunk-KT6VHSPF.js";
|
|
112
|
+
import {
|
|
113
|
+
parsePromptSpec
|
|
114
|
+
} from "./chunk-7T7RUJI7.js";
|
|
115
|
+
import "./chunk-JOSYEJKY.js";
|
|
116
|
+
import "./chunk-VTW7HP3R.js";
|
|
117
|
+
import "./chunk-4CUY7AFY.js";
|
|
118
|
+
import "./chunk-L4427KOE.js";
|
|
119
|
+
import {
|
|
120
|
+
runTypecheckWithArtifacts
|
|
121
|
+
} from "./chunk-NS2K2GTP.js";
|
|
122
|
+
import "./chunk-MN6PL4AN.js";
|
|
123
|
+
import "./chunk-NS33V3IM.js";
|
|
124
|
+
import {
|
|
125
|
+
resolveVerificationChannelScope,
|
|
126
|
+
selectScopedTestCommands
|
|
127
|
+
} from "./chunk-KP6YILMU.js";
|
|
128
|
+
import "./chunk-H2AYQIHW.js";
|
|
129
|
+
import "./chunk-FATUVIIN.js";
|
|
130
|
+
import {
|
|
131
|
+
setProcessLifecycleManager
|
|
132
|
+
} from "./chunk-PFTQBXS6.js";
|
|
133
|
+
import "./chunk-E3NTPHEO.js";
|
|
134
|
+
import "./chunk-SP3LFU6B.js";
|
|
135
|
+
import "./chunk-2YFUIER7.js";
|
|
136
|
+
import {
|
|
137
|
+
TelemetryLogger
|
|
138
|
+
} from "./chunk-CKNX3TTP.js";
|
|
139
|
+
import "./chunk-FYOILAN5.js";
|
|
140
|
+
import "./chunk-J62KPGII.js";
|
|
141
|
+
import "./chunk-XAONGNST.js";
|
|
142
|
+
import "./chunk-GAFJEYLK.js";
|
|
143
|
+
import "./chunk-H6G7QRMQ.js";
|
|
144
|
+
import "./chunk-J6P3FWWW.js";
|
|
145
|
+
import {
|
|
146
|
+
formatVariantIdentifier
|
|
147
|
+
} from "./chunk-CP7H3HSP.js";
|
|
148
|
+
import "./chunk-ETYDXCK7.js";
|
|
149
|
+
import "./chunk-BSE4R6XD.js";
|
|
150
|
+
import "./chunk-UR2IBGIM.js";
|
|
151
|
+
import "./chunk-JSWMRQYG.js";
|
|
152
|
+
import "./chunk-XGYF2QMZ.js";
|
|
153
|
+
import {
|
|
154
|
+
signalCoordinator
|
|
155
|
+
} from "./chunk-72YTZMAM.js";
|
|
156
|
+
import "./chunk-S2VQCZO4.js";
|
|
157
|
+
import "./chunk-WMPU2UOV.js";
|
|
158
|
+
import "./chunk-VX3UMWB7.js";
|
|
159
|
+
import "./chunk-XT2ZQVTC.js";
|
|
160
|
+
import "./chunk-YGXKOBQQ.js";
|
|
161
|
+
import "./chunk-V72RMBM4.js";
|
|
162
|
+
import "./chunk-ZDTGECTN.js";
|
|
163
|
+
import "./chunk-G36W2T2S.js";
|
|
164
|
+
import "./chunk-GKN3HMN4.js";
|
|
165
|
+
import {
|
|
166
|
+
parseJsonWithContext
|
|
167
|
+
} from "./chunk-QVZMFDYG.js";
|
|
168
|
+
import "./chunk-S54MKU6V.js";
|
|
169
|
+
import "./chunk-3S62LLWJ.js";
|
|
170
|
+
import "./chunk-45FPSA3B.js";
|
|
171
|
+
import "./chunk-YMX76IOS.js";
|
|
172
|
+
import "./chunk-6Y5U7UFY.js";
|
|
173
|
+
import "./chunk-XV7NZL4B.js";
|
|
174
|
+
import "./chunk-CD7ISXA4.js";
|
|
175
|
+
import "./chunk-QGJDMEOV.js";
|
|
176
|
+
import "./chunk-D5RYINO7.js";
|
|
177
|
+
import "./chunk-6TZJRKNW.js";
|
|
178
|
+
import "./chunk-PCAGC5MR.js";
|
|
179
|
+
import "./chunk-5WCQRDRB.js";
|
|
180
|
+
import "./chunk-5WRLK5KW.js";
|
|
181
|
+
import {
|
|
182
|
+
mkPoeticTempDir
|
|
183
|
+
} from "./chunk-C57KJOUQ.js";
|
|
184
|
+
import "./chunk-DF5SLDE4.js";
|
|
185
|
+
import "./chunk-TGON4N4O.js";
|
|
186
|
+
import "./chunk-I5WRT7ZR.js";
|
|
187
|
+
import "./chunk-A3YGID55.js";
|
|
188
|
+
import "./chunk-5EERLVZB.js";
|
|
189
|
+
import "./chunk-XQD2B6BB.js";
|
|
190
|
+
import {
|
|
191
|
+
sanitizeRefPart
|
|
192
|
+
} from "./chunk-34CNP2HB.js";
|
|
193
|
+
import "./chunk-KNDYMVWN.js";
|
|
194
|
+
import "./chunk-RWREC3KJ.js";
|
|
195
|
+
import "./chunk-LO23NABI.js";
|
|
196
|
+
import "./chunk-7R2WLDOS.js";
|
|
197
|
+
import {
|
|
198
|
+
DEFAULT_PER_VARIANT_TIMEOUT_MINUTES,
|
|
199
|
+
DEFAULT_REPAIR_LOOP_ITERATION_TIMEOUT_MS,
|
|
200
|
+
DEFAULT_REPAIR_LOOP_MAX_ITERATIONS
|
|
201
|
+
} from "./chunk-LAMKBTXX.js";
|
|
202
|
+
import "./chunk-FD4ERYEG.js";
|
|
203
|
+
import "./chunk-KTMW65UX.js";
|
|
204
|
+
import "./chunk-FHOWVDF5.js";
|
|
205
|
+
import {
|
|
206
|
+
PROJECT_SOURCE_EXTENSIONS
|
|
207
|
+
} from "./chunk-UDMS5AD3.js";
|
|
208
|
+
import "./chunk-MWHLBPNU.js";
|
|
209
|
+
import "./chunk-JLS65NUQ.js";
|
|
210
|
+
import "./chunk-TRSVUPCX.js";
|
|
211
|
+
import "./chunk-FKAUPHMR.js";
|
|
212
|
+
import {
|
|
213
|
+
truncateToByteLimit
|
|
214
|
+
} from "./chunk-TSXGRLPR.js";
|
|
215
|
+
import {
|
|
216
|
+
ProviderExecutionError
|
|
217
|
+
} from "./chunk-LUVKFFWR.js";
|
|
218
|
+
import {
|
|
219
|
+
getRuntimeEnv
|
|
220
|
+
} from "./chunk-ENRGKNR5.js";
|
|
221
|
+
import "./chunk-XB5YLCLB.js";
|
|
222
|
+
import "./chunk-QOCOVLUH.js";
|
|
223
|
+
import "./chunk-2VSQMRCB.js";
|
|
224
|
+
import "./chunk-ON3G73BU.js";
|
|
225
|
+
import "./chunk-3G6FZCRF.js";
|
|
226
|
+
import "./chunk-N5DX4JAK.js";
|
|
227
|
+
import {
|
|
228
|
+
describeVerificationCommand
|
|
229
|
+
} from "./chunk-2QLYTVYX.js";
|
|
230
|
+
import {
|
|
231
|
+
initializeLabModeFromEnv
|
|
232
|
+
} from "./chunk-7GDRQBQC.js";
|
|
233
|
+
import "./chunk-PGSPX4SU.js";
|
|
234
|
+
import "./chunk-AT4TNPWW.js";
|
|
235
|
+
import "./chunk-GSGD5E4C.js";
|
|
236
|
+
import {
|
|
237
|
+
mergeExecutionContextEnvOverrides
|
|
238
|
+
} from "./chunk-UI2F6DJ5.js";
|
|
239
|
+
import "./chunk-4BTDUT2S.js";
|
|
240
|
+
import "./chunk-L6OM4A22.js";
|
|
241
|
+
import "./chunk-G4THLFV3.js";
|
|
242
|
+
import "./chunk-RGWHUI5E.js";
|
|
243
|
+
import "./chunk-UUH2RVKS.js";
|
|
244
|
+
import {
|
|
245
|
+
resolvePoeticRoot
|
|
246
|
+
} from "./chunk-LEIIWPWQ.js";
|
|
247
|
+
import "./chunk-L7JZLXQL.js";
|
|
248
|
+
import "./chunk-IZYIQQ7X.js";
|
|
249
|
+
import {
|
|
250
|
+
assertPoeticRuntimeReady
|
|
251
|
+
} from "./chunk-JPKSXKNB.js";
|
|
252
|
+
import "./chunk-TAJPCXSB.js";
|
|
253
|
+
import "./chunk-XZE4XARH.js";
|
|
254
|
+
import {
|
|
255
|
+
emitWarning
|
|
256
|
+
} from "./chunk-EJGCZGUF.js";
|
|
257
|
+
import {
|
|
258
|
+
sanitizeError
|
|
259
|
+
} from "./chunk-LPITVM7M.js";
|
|
260
|
+
import {
|
|
261
|
+
POETIC_ALLOW_OFF_SCOPE_REPAIR,
|
|
262
|
+
POETIC_COMPLETION_MODE,
|
|
263
|
+
readEnv
|
|
264
|
+
} from "./chunk-5DLKYTQX.js";
|
|
265
|
+
import "./chunk-PZ5AY32C.js";
|
|
266
|
+
|
|
267
|
+
// src/cli/variant-worker.ts
|
|
268
|
+
import { randomBytes } from "crypto";
|
|
269
|
+
import { closeSync, fsyncSync, openSync } from "fs";
|
|
270
|
+
import * as fs from "fs/promises";
|
|
271
|
+
import * as path from "path";
|
|
272
|
+
import { fileURLToPath } from "url";
|
|
273
|
+
import chalk from "chalk";
|
|
274
|
+
import { Command } from "commander";
|
|
275
|
+
import { execa as execa3 } from "execa";
|
|
276
|
+
|
|
277
|
+
// src/core/verification/baseline-comparison.ts
|
|
278
|
+
var VALID_VERIFICATION_LANGUAGES = /* @__PURE__ */ new Set([
|
|
279
|
+
"node",
|
|
280
|
+
"python",
|
|
281
|
+
"native",
|
|
282
|
+
"go",
|
|
283
|
+
"rust",
|
|
284
|
+
"jvm",
|
|
285
|
+
"dotnet",
|
|
286
|
+
"swift",
|
|
287
|
+
"dart",
|
|
288
|
+
"ruby",
|
|
289
|
+
"php",
|
|
290
|
+
"shell",
|
|
291
|
+
"r",
|
|
292
|
+
"sql",
|
|
293
|
+
"unknown"
|
|
294
|
+
]);
|
|
295
|
+
function languageFor(value) {
|
|
296
|
+
if (value == null || value.length === 0) return "node";
|
|
297
|
+
return VALID_VERIFICATION_LANGUAGES.has(value) ? value : "unknown";
|
|
298
|
+
}
|
|
299
|
+
function generateSummaryMessage(comparison) {
|
|
300
|
+
const parts = [];
|
|
301
|
+
const totalNew = comparison.tests.newFailures.length + comparison.types.newFailures.length + comparison.lint.newFailures.length;
|
|
302
|
+
const totalFixed = comparison.tests.fixedIssues.length + comparison.types.fixedIssues.length + comparison.lint.fixedIssues.length;
|
|
303
|
+
const totalPersisting = comparison.tests.persistingIssues.length + comparison.types.persistingIssues.length + comparison.lint.persistingIssues.length;
|
|
304
|
+
switch (comparison.verdict) {
|
|
305
|
+
case "pass":
|
|
306
|
+
parts.push("No verification issues");
|
|
307
|
+
break;
|
|
308
|
+
case "regression":
|
|
309
|
+
parts.push(`Introduced ${totalNew} new failure(s)`);
|
|
310
|
+
if (comparison.tests.newFailures.length > 0) {
|
|
311
|
+
parts.push(` - ${comparison.tests.newFailures.length} test failure(s)`);
|
|
312
|
+
}
|
|
313
|
+
if (comparison.types.newFailures.length > 0) {
|
|
314
|
+
parts.push(` - ${comparison.types.newFailures.length} type error(s)`);
|
|
315
|
+
}
|
|
316
|
+
if (comparison.lint.newFailures.length > 0) {
|
|
317
|
+
parts.push(` - ${comparison.lint.newFailures.length} lint error(s)`);
|
|
318
|
+
}
|
|
319
|
+
break;
|
|
320
|
+
case "improvement":
|
|
321
|
+
parts.push(`Fixed ${totalFixed} pre-existing issue(s)`);
|
|
322
|
+
break;
|
|
323
|
+
case "neutral":
|
|
324
|
+
parts.push(`No change (${totalPersisting} pre-existing issue(s) unchanged)`);
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
if (totalPersisting > 0 && comparison.verdict !== "neutral") {
|
|
328
|
+
parts.push(`${totalPersisting} pre-existing issue(s) not penalized`);
|
|
329
|
+
}
|
|
330
|
+
return parts.join("\n");
|
|
331
|
+
}
|
|
332
|
+
function statusDidNotRun(status) {
|
|
333
|
+
return status === "skipped" || status === "unavailable" || status === "not_run";
|
|
334
|
+
}
|
|
335
|
+
function detectGateRegressions(baseline, currentGates) {
|
|
336
|
+
const build = currentGates.build != null && !statusDidNotRun(currentGates.build.status) && currentGates.build.exitCode !== 0 && baseline.typeErrors.exitCode === 0;
|
|
337
|
+
const lint = currentGates.lint != null && !statusDidNotRun(currentGates.lint.status) && currentGates.lint.exitCode !== 0 && baseline.lintErrors.exitCode === 0;
|
|
338
|
+
const test = currentGates.tests != null && !statusDidNotRun(currentGates.tests.status) && currentGates.tests.exitCode !== 0 && baseline.testResults.exitCode === 0;
|
|
339
|
+
return {
|
|
340
|
+
build,
|
|
341
|
+
lint,
|
|
342
|
+
test,
|
|
343
|
+
total: Number(build) + Number(lint) + Number(test)
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function statusFromCommandSummaryExitCode(exitCode) {
|
|
347
|
+
if (exitCode === 0) return "passed";
|
|
348
|
+
if (exitCode === 124) return "timeout";
|
|
349
|
+
return "failed";
|
|
350
|
+
}
|
|
351
|
+
function commandResultsFromSummaries(commands) {
|
|
352
|
+
if (!commands || commands.length === 0) return void 0;
|
|
353
|
+
return commands.map((command) => ({
|
|
354
|
+
command: command.command,
|
|
355
|
+
language: languageFor(command.language),
|
|
356
|
+
status: statusFromCommandSummaryExitCode(command.exitCode),
|
|
357
|
+
exitCode: command.exitCode,
|
|
358
|
+
outputTail: command.outputTail,
|
|
359
|
+
parsed: command.parsed,
|
|
360
|
+
failingTests: command.failedTests
|
|
361
|
+
}));
|
|
362
|
+
}
|
|
363
|
+
function baselineToStableState(baseline) {
|
|
364
|
+
const buildCommands = baseline.summary?.gates.build.commands;
|
|
365
|
+
const lintCommands = baseline.summary?.gates.lint.commands;
|
|
366
|
+
const testCommands = baseline.summary?.gates.test.commands;
|
|
367
|
+
const multiBuild = buildCommands && buildCommands.length > 1;
|
|
368
|
+
const multiLint = lintCommands && lintCommands.length > 1;
|
|
369
|
+
const multiTest = testCommands && testCommands.length > 1;
|
|
370
|
+
const singleBuild = buildCommands && buildCommands.length === 1 ? buildCommands[0] : void 0;
|
|
371
|
+
const singleLint = lintCommands && lintCommands.length === 1 ? lintCommands[0] : void 0;
|
|
372
|
+
const singleTest = testCommands && testCommands.length === 1 ? testCommands[0] : void 0;
|
|
373
|
+
const state = extractStableVerificationState({
|
|
374
|
+
build: {
|
|
375
|
+
exitCode: multiBuild ? buildCommands.some((c) => c.exitCode !== 0) ? 1 : 0 : singleBuild?.exitCode ?? baseline.typeErrors.exitCode,
|
|
376
|
+
outputTail: multiBuild ? "" : singleBuild?.outputTail ?? baseline.typeErrors.outputTail,
|
|
377
|
+
language: languageFor(
|
|
378
|
+
multiBuild ? baseline.summary?.gates.build.language : singleBuild?.language ?? baseline.summary?.gates.build.language
|
|
379
|
+
),
|
|
380
|
+
commandResults: multiBuild ? commandResultsFromSummaries(buildCommands) : void 0
|
|
381
|
+
},
|
|
382
|
+
lint: {
|
|
383
|
+
exitCode: multiLint ? lintCommands.some((c) => c.exitCode !== 0) ? 1 : 0 : singleLint?.exitCode ?? baseline.lintErrors.exitCode,
|
|
384
|
+
outputTail: multiLint ? "" : singleLint?.outputTail ?? baseline.lintErrors.outputTail,
|
|
385
|
+
language: languageFor(
|
|
386
|
+
multiLint ? baseline.summary?.gates.lint.language : singleLint?.language ?? baseline.summary?.gates.lint.language
|
|
387
|
+
),
|
|
388
|
+
commandResults: multiLint ? commandResultsFromSummaries(lintCommands) : void 0
|
|
389
|
+
},
|
|
390
|
+
tests: {
|
|
391
|
+
exitCode: multiTest ? testCommands.some((c) => c.exitCode !== 0) ? 1 : 0 : singleTest?.exitCode ?? baseline.testResults.exitCode,
|
|
392
|
+
outputTail: multiTest ? "" : singleTest?.outputTail ?? baseline.testResults.outputTail,
|
|
393
|
+
language: languageFor(
|
|
394
|
+
multiTest ? baseline.summary?.gates.test.language : singleTest?.language ?? baseline.summary?.gates.test.language
|
|
395
|
+
),
|
|
396
|
+
failingTests: multiTest ? Array.from(new Set(testCommands.flatMap((c) => c.failedTests ?? []))) : singleTest?.failedTests ?? baseline.testResults.failedTests,
|
|
397
|
+
commandResults: multiTest ? commandResultsFromSummaries(testCommands) : void 0
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
if (!multiBuild && !multiLint && !multiTest && !singleBuild && !singleLint && !singleTest) {
|
|
401
|
+
const buildFailed = baseline.typeErrors.exitCode !== 0;
|
|
402
|
+
const lintFailed = baseline.lintErrors.exitCode !== 0;
|
|
403
|
+
const testFailed = baseline.testResults.exitCode !== 0;
|
|
404
|
+
const buildIssues = parseVerificationErrorsForChannel(
|
|
405
|
+
"build",
|
|
406
|
+
languageFor(baseline.summary?.gates.build.language),
|
|
407
|
+
baseline.typeErrors.outputTail,
|
|
408
|
+
buildFailed
|
|
409
|
+
).issues;
|
|
410
|
+
const lintIssues = parseVerificationErrorsForChannel(
|
|
411
|
+
"lint",
|
|
412
|
+
languageFor(baseline.summary?.gates.lint.language),
|
|
413
|
+
baseline.lintErrors.outputTail,
|
|
414
|
+
lintFailed
|
|
415
|
+
).issues;
|
|
416
|
+
const testIssues = parseVerificationErrorsForChannel(
|
|
417
|
+
"test",
|
|
418
|
+
languageFor(baseline.summary?.gates.test.language),
|
|
419
|
+
baseline.testResults.outputTail,
|
|
420
|
+
testFailed,
|
|
421
|
+
baseline.testResults.failedTests
|
|
422
|
+
).issues;
|
|
423
|
+
return {
|
|
424
|
+
buildIssues,
|
|
425
|
+
lintIssues,
|
|
426
|
+
testIssues,
|
|
427
|
+
totalIssues: buildIssues.size + lintIssues.size + testIssues.size,
|
|
428
|
+
gates: {
|
|
429
|
+
build: { passed: baseline.typeErrors.exitCode === 0 },
|
|
430
|
+
lint: { passed: baseline.lintErrors.exitCode === 0 },
|
|
431
|
+
test: { passed: baseline.testResults.exitCode === 0 }
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
buildIssues: state.buildIssues,
|
|
437
|
+
lintIssues: state.lintIssues,
|
|
438
|
+
testIssues: state.testIssues,
|
|
439
|
+
totalIssues: state.totalIssues,
|
|
440
|
+
gates: {
|
|
441
|
+
build: { passed: baseline.typeErrors.exitCode === 0 },
|
|
442
|
+
lint: { passed: baseline.lintErrors.exitCode === 0 },
|
|
443
|
+
test: { passed: baseline.testResults.exitCode === 0 }
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
function determineStableVerdict(delta) {
|
|
448
|
+
if (delta.hasAnyRegressions) {
|
|
449
|
+
return "regression";
|
|
450
|
+
}
|
|
451
|
+
if (delta.hasAnyImprovements) {
|
|
452
|
+
return "improvement";
|
|
453
|
+
}
|
|
454
|
+
if (delta.totalPersistingIssues > 0) {
|
|
455
|
+
return "neutral";
|
|
456
|
+
}
|
|
457
|
+
return "pass";
|
|
458
|
+
}
|
|
459
|
+
function formatStableIssueLabel(issue) {
|
|
460
|
+
if (issue.gate === "test") {
|
|
461
|
+
return issue.file ? `${issue.file}: ${issue.code}` : issue.code;
|
|
462
|
+
}
|
|
463
|
+
const loc = issue.line ? `:${issue.line}${issue.col ? `:${issue.col}` : ""}` : "";
|
|
464
|
+
if (issue.originalMessage) {
|
|
465
|
+
return `${issue.file}${loc}: ${issue.code} - ${issue.originalMessage}`;
|
|
466
|
+
}
|
|
467
|
+
return `${issue.file}${loc}: ${issue.code}`;
|
|
468
|
+
}
|
|
469
|
+
function issueSetToDimensionComparison(issueDelta) {
|
|
470
|
+
return {
|
|
471
|
+
newFailures: issueDelta.newIssues.toArray().map(formatStableIssueLabel),
|
|
472
|
+
fixedIssues: issueDelta.fixedIssues.toArray().map(formatStableIssueLabel),
|
|
473
|
+
persistingIssues: issueDelta.persistingIssues.toArray().map(formatStableIssueLabel),
|
|
474
|
+
netChange: issueDelta.netChange
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function stableComparisonToBaselineComparison(stable) {
|
|
478
|
+
const tests = issueSetToDimensionComparison(stable.delta.test);
|
|
479
|
+
const types = issueSetToDimensionComparison(stable.delta.build);
|
|
480
|
+
const lint = issueSetToDimensionComparison(stable.delta.lint);
|
|
481
|
+
const totalNewFailures = tests.newFailures.length + types.newFailures.length + lint.newFailures.length;
|
|
482
|
+
const totalFixedIssues = tests.fixedIssues.length + types.fixedIssues.length + lint.fixedIssues.length;
|
|
483
|
+
const totalPersistingIssues = tests.persistingIssues.length + types.persistingIssues.length + lint.persistingIssues.length;
|
|
484
|
+
return {
|
|
485
|
+
verdict: stable.verdict,
|
|
486
|
+
tests,
|
|
487
|
+
types,
|
|
488
|
+
lint,
|
|
489
|
+
summary: {
|
|
490
|
+
totalNewFailures,
|
|
491
|
+
totalFixedIssues,
|
|
492
|
+
totalPersistingIssues,
|
|
493
|
+
shouldPenalize: !stable.passed,
|
|
494
|
+
scoreAdjustment: 0,
|
|
495
|
+
message: generateSummaryMessage({ verdict: stable.verdict, tests, types, lint })
|
|
496
|
+
},
|
|
497
|
+
baselineRef: stable.baselineRef
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
function compareVerificationOutcomeToBaseline(baseline, currentGates) {
|
|
501
|
+
return compareCurrentGatesToBaseline(baseline, currentGates);
|
|
502
|
+
}
|
|
503
|
+
function compareCurrentGatesToBaseline(baseline, currentGates) {
|
|
504
|
+
const baselineState = baselineToStableState(baseline);
|
|
505
|
+
const currentState = extractStableVerificationState(currentGates);
|
|
506
|
+
const testVerificationUnavailable = currentGates.tests == null || currentGates.tests.status === "skipped" || currentGates.tests.status === "unavailable" || currentGates.tests.status === "not_run";
|
|
507
|
+
if (testVerificationUnavailable) {
|
|
508
|
+
currentState.testIssues = baselineState.testIssues;
|
|
509
|
+
currentState.totalIssues = currentState.buildIssues.size + currentState.lintIssues.size + currentState.testIssues.size;
|
|
510
|
+
currentState.gates.test = { ...baselineState.gates.test, unavailable: true };
|
|
511
|
+
}
|
|
512
|
+
const delta = compareVerificationStates(baselineState, currentState);
|
|
513
|
+
const baseVerdict = determineStableVerdict(delta);
|
|
514
|
+
const gateRegressions = detectGateRegressions(baseline, currentGates);
|
|
515
|
+
const comparableTestTotalRegressed = !testVerificationUnavailable && baseline.testResults.run !== void 0 && currentGates.tests?.testsRun !== void 0 && currentGates.tests.testsRun < baseline.testResults.run;
|
|
516
|
+
const gateRegressed = gateRegressions.total > 0 || comparableTestTotalRegressed;
|
|
517
|
+
const verdict = gateRegressed ? "regression" : baseVerdict;
|
|
518
|
+
return {
|
|
519
|
+
delta,
|
|
520
|
+
baseline: baselineState,
|
|
521
|
+
current: currentState,
|
|
522
|
+
baselineRef: baseline.baselineRef,
|
|
523
|
+
passed: !delta.hasAnyRegressions && !gateRegressed,
|
|
524
|
+
testVerificationUnavailable,
|
|
525
|
+
verdict,
|
|
526
|
+
scoreAdjustment: 0
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// src/orchestrator/verification/verification-loop.ts
|
|
531
|
+
import { execa as execa2 } from "execa";
|
|
532
|
+
|
|
533
|
+
// src/core/verification/repair-artifacts.ts
|
|
534
|
+
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
535
|
+
import { dirname, join } from "path";
|
|
536
|
+
import { execa } from "execa";
|
|
537
|
+
var DEFAULT_CONFIG = {
|
|
538
|
+
modes: ["patch"],
|
|
539
|
+
patchDir: ".poetic/repair-patches",
|
|
540
|
+
refNamespace: "refs/poetic/repair",
|
|
541
|
+
branchPrefix: "poetic/repair"
|
|
542
|
+
};
|
|
543
|
+
function sanitizeForPathSegment(input) {
|
|
544
|
+
const trimmed = input.trim();
|
|
545
|
+
if (!trimmed) return "unknown";
|
|
546
|
+
return trimmed.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 200);
|
|
547
|
+
}
|
|
548
|
+
function sanitizeForGitRefSegment(input) {
|
|
549
|
+
const trimmed = input.trim();
|
|
550
|
+
if (!trimmed) return "unknown";
|
|
551
|
+
const safe = trimmed.replace(/[\s~^:?*[\\]+/g, "_").replace(/\.\.+/g, ".").replace(/^\.*/, "").replace(/\/+/g, "/");
|
|
552
|
+
return (safe || "unknown").slice(0, 200);
|
|
553
|
+
}
|
|
554
|
+
async function generatePatch(worktreePath, baseRef) {
|
|
555
|
+
const scratch = await mkPoeticTempDir("repair-patch");
|
|
556
|
+
const indexPath = join(scratch, "index");
|
|
557
|
+
const env = { ...process.env, GIT_INDEX_FILE: indexPath };
|
|
558
|
+
try {
|
|
559
|
+
const readTree = await execa("git", ["read-tree", baseRef], {
|
|
560
|
+
cwd: worktreePath,
|
|
561
|
+
env,
|
|
562
|
+
stdio: "pipe",
|
|
563
|
+
reject: false
|
|
564
|
+
});
|
|
565
|
+
if (readTree.exitCode !== 0) {
|
|
566
|
+
throw new Error(`Failed to read base tree: ${readTree.stderr}`);
|
|
567
|
+
}
|
|
568
|
+
const add = await execa("git", ["add", "-A"], {
|
|
569
|
+
cwd: worktreePath,
|
|
570
|
+
env,
|
|
571
|
+
stdio: "pipe",
|
|
572
|
+
reject: false
|
|
573
|
+
});
|
|
574
|
+
if (add.exitCode !== 0) {
|
|
575
|
+
throw new Error(`Failed to stage worktree changes: ${add.stderr}`);
|
|
576
|
+
}
|
|
577
|
+
const diff = await execa("git", ["diff", "--cached", baseRef, "--"], {
|
|
578
|
+
cwd: worktreePath,
|
|
579
|
+
env,
|
|
580
|
+
stdio: "pipe",
|
|
581
|
+
reject: false
|
|
582
|
+
});
|
|
583
|
+
if (diff.exitCode !== 0) {
|
|
584
|
+
throw new Error(`Failed to generate patch: ${diff.stderr}`);
|
|
585
|
+
}
|
|
586
|
+
return diff.stdout;
|
|
587
|
+
} finally {
|
|
588
|
+
await rm(scratch, { recursive: true, force: true });
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
async function createCommitFromPatch(opts) {
|
|
592
|
+
const scratch = await mkPoeticTempDir("repair");
|
|
593
|
+
const indexPath = join(scratch, "index");
|
|
594
|
+
const env = { ...process.env, GIT_INDEX_FILE: indexPath };
|
|
595
|
+
const patch = opts.patchContent.endsWith("\n") ? opts.patchContent : `${opts.patchContent}
|
|
596
|
+
`;
|
|
597
|
+
try {
|
|
598
|
+
const readTree = await execa("git", ["read-tree", opts.baseRef], {
|
|
599
|
+
cwd: opts.worktreePath,
|
|
600
|
+
env,
|
|
601
|
+
stdio: "pipe",
|
|
602
|
+
reject: false
|
|
603
|
+
});
|
|
604
|
+
if (readTree.exitCode !== 0) {
|
|
605
|
+
throw new Error(`Failed to read base tree: ${readTree.stderr}`);
|
|
606
|
+
}
|
|
607
|
+
const apply = await execa("git", ["apply", "--cached", "-"], {
|
|
608
|
+
cwd: opts.worktreePath,
|
|
609
|
+
env,
|
|
610
|
+
input: patch,
|
|
611
|
+
stdio: "pipe",
|
|
612
|
+
reject: false
|
|
613
|
+
});
|
|
614
|
+
if (apply.exitCode !== 0) {
|
|
615
|
+
throw new Error(`Failed to apply patch to temp index: ${apply.stderr}`);
|
|
616
|
+
}
|
|
617
|
+
const writeTree = await execa("git", ["write-tree"], {
|
|
618
|
+
cwd: opts.worktreePath,
|
|
619
|
+
env,
|
|
620
|
+
stdio: "pipe",
|
|
621
|
+
reject: false
|
|
622
|
+
});
|
|
623
|
+
if (writeTree.exitCode !== 0) {
|
|
624
|
+
throw new Error(`Failed to write tree: ${writeTree.stderr}`);
|
|
625
|
+
}
|
|
626
|
+
const treeSha = writeTree.stdout.trim();
|
|
627
|
+
const commitTree = await execa(
|
|
628
|
+
"git",
|
|
629
|
+
["commit-tree", treeSha, "-p", opts.baseRef, "-m", opts.message],
|
|
630
|
+
{ cwd: opts.worktreePath, env, stdio: "pipe", reject: false }
|
|
631
|
+
);
|
|
632
|
+
if (commitTree.exitCode !== 0) {
|
|
633
|
+
throw new Error(`Failed to create commit: ${commitTree.stderr}`);
|
|
634
|
+
}
|
|
635
|
+
return commitTree.stdout.trim();
|
|
636
|
+
} finally {
|
|
637
|
+
await rm(scratch, { recursive: true, force: true });
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
async function savePatchFile(patchContent, patchDir, taskId, variantId, iteration) {
|
|
641
|
+
const safeTaskId = sanitizeForPathSegment(taskId);
|
|
642
|
+
const safeVariantId = sanitizeForPathSegment(variantId);
|
|
643
|
+
const patchPath = join(patchDir, safeTaskId, `${safeVariantId}-iteration-${iteration}.patch`);
|
|
644
|
+
await mkdir(dirname(patchPath), { recursive: true });
|
|
645
|
+
await writeFile(patchPath, patchContent, "utf-8");
|
|
646
|
+
return patchPath;
|
|
647
|
+
}
|
|
648
|
+
async function createGitRef(worktreePath, refNamespace, taskId, variantId, iteration, commitSha) {
|
|
649
|
+
const safeTaskId = sanitizeForGitRefSegment(taskId);
|
|
650
|
+
const safeVariantId = sanitizeForGitRefSegment(variantId);
|
|
651
|
+
const refName = `${refNamespace}/${safeTaskId}/${safeVariantId}/${iteration}`;
|
|
652
|
+
const updateResult = await execa("git", ["update-ref", refName, commitSha], {
|
|
653
|
+
cwd: worktreePath,
|
|
654
|
+
stdio: "pipe",
|
|
655
|
+
reject: false
|
|
656
|
+
});
|
|
657
|
+
if (updateResult.exitCode !== 0) {
|
|
658
|
+
throw new Error(`Failed to create ref: ${updateResult.stderr}`);
|
|
659
|
+
}
|
|
660
|
+
return refName;
|
|
661
|
+
}
|
|
662
|
+
async function createBranch(worktreePath, branchPrefix, taskId, variantId, iteration, commitSha) {
|
|
663
|
+
const safeTaskId = sanitizeForGitRefSegment(taskId);
|
|
664
|
+
const safeVariantId = sanitizeForGitRefSegment(variantId);
|
|
665
|
+
const branchName = `${branchPrefix}/${safeTaskId}/${safeVariantId}/${iteration}`;
|
|
666
|
+
const result = await execa("git", ["branch", "-f", branchName, commitSha], {
|
|
667
|
+
cwd: worktreePath,
|
|
668
|
+
stdio: "pipe",
|
|
669
|
+
reject: false
|
|
670
|
+
});
|
|
671
|
+
if (result.exitCode !== 0) {
|
|
672
|
+
throw new Error(`Failed to create branch: ${result.stderr}`);
|
|
673
|
+
}
|
|
674
|
+
return branchName;
|
|
675
|
+
}
|
|
676
|
+
async function preserveRepairIteration(worktreePath, baseRef, taskId, variantId, iteration, config = {}) {
|
|
677
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
678
|
+
const results = [];
|
|
679
|
+
let patchContent = null;
|
|
680
|
+
let preservedCommitSha = null;
|
|
681
|
+
for (const mode of cfg.modes) {
|
|
682
|
+
try {
|
|
683
|
+
let artifactPath;
|
|
684
|
+
switch (mode) {
|
|
685
|
+
case "patch": {
|
|
686
|
+
patchContent = patchContent ?? await generatePatch(worktreePath, baseRef);
|
|
687
|
+
artifactPath = await savePatchFile(
|
|
688
|
+
patchContent,
|
|
689
|
+
cfg.patchDir,
|
|
690
|
+
taskId,
|
|
691
|
+
variantId,
|
|
692
|
+
iteration.iteration
|
|
693
|
+
);
|
|
694
|
+
break;
|
|
695
|
+
}
|
|
696
|
+
case "ref": {
|
|
697
|
+
patchContent = patchContent ?? await generatePatch(worktreePath, baseRef);
|
|
698
|
+
preservedCommitSha = preservedCommitSha ?? await createCommitFromPatch({
|
|
699
|
+
worktreePath,
|
|
700
|
+
baseRef,
|
|
701
|
+
patchContent,
|
|
702
|
+
message: `poetic: preserve repair (${taskId}, ${variantId}, iter ${iteration.iteration})`
|
|
703
|
+
});
|
|
704
|
+
artifactPath = await createGitRef(
|
|
705
|
+
worktreePath,
|
|
706
|
+
cfg.refNamespace,
|
|
707
|
+
taskId,
|
|
708
|
+
variantId,
|
|
709
|
+
iteration.iteration,
|
|
710
|
+
preservedCommitSha
|
|
711
|
+
);
|
|
712
|
+
break;
|
|
713
|
+
}
|
|
714
|
+
case "branch": {
|
|
715
|
+
patchContent = patchContent ?? await generatePatch(worktreePath, baseRef);
|
|
716
|
+
preservedCommitSha = preservedCommitSha ?? await createCommitFromPatch({
|
|
717
|
+
worktreePath,
|
|
718
|
+
baseRef,
|
|
719
|
+
patchContent,
|
|
720
|
+
message: `poetic: preserve repair (${taskId}, ${variantId}, iter ${iteration.iteration})`
|
|
721
|
+
});
|
|
722
|
+
artifactPath = await createBranch(
|
|
723
|
+
worktreePath,
|
|
724
|
+
cfg.branchPrefix,
|
|
725
|
+
taskId,
|
|
726
|
+
variantId,
|
|
727
|
+
iteration.iteration,
|
|
728
|
+
preservedCommitSha
|
|
729
|
+
);
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
results.push({
|
|
734
|
+
artifactPath,
|
|
735
|
+
mode,
|
|
736
|
+
success: true
|
|
737
|
+
});
|
|
738
|
+
} catch (error) {
|
|
739
|
+
results.push({
|
|
740
|
+
artifactPath: "",
|
|
741
|
+
mode,
|
|
742
|
+
success: false,
|
|
743
|
+
error: error instanceof Error ? error.message : String(error)
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return results;
|
|
748
|
+
}
|
|
749
|
+
function findBestIteration(iterations) {
|
|
750
|
+
if (iterations.length === 0) {
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
let bestIndex = 0;
|
|
754
|
+
let bestIteration = iterations[0];
|
|
755
|
+
for (let i = 1; i < iterations.length; i++) {
|
|
756
|
+
const current = iterations[i];
|
|
757
|
+
if (current.passed && !bestIteration.passed) {
|
|
758
|
+
bestIndex = i;
|
|
759
|
+
bestIteration = current;
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
if (current.passed === bestIteration.passed) {
|
|
763
|
+
if (current.newIssueCount < bestIteration.newIssueCount) {
|
|
764
|
+
bestIndex = i;
|
|
765
|
+
bestIteration = current;
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
if (current.newIssueCount === bestIteration.newIssueCount && current.fixedIssueCount > bestIteration.fixedIssueCount) {
|
|
769
|
+
bestIndex = i;
|
|
770
|
+
bestIteration = current;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return { index: bestIndex, iteration: bestIteration };
|
|
775
|
+
}
|
|
776
|
+
async function applyPatchFile(worktreePath, patchPath) {
|
|
777
|
+
const raw = await readFile(patchPath, "utf-8");
|
|
778
|
+
const patchContent = raw.endsWith("\n") ? raw : `${raw}
|
|
779
|
+
`;
|
|
780
|
+
const result = await execa("git", ["apply", "--3way", "-"], {
|
|
781
|
+
cwd: worktreePath,
|
|
782
|
+
input: patchContent,
|
|
783
|
+
stdio: "pipe",
|
|
784
|
+
reject: false
|
|
785
|
+
});
|
|
786
|
+
if (result.exitCode !== 0) {
|
|
787
|
+
return {
|
|
788
|
+
success: false,
|
|
789
|
+
error: `Failed to apply patch: ${result.stderr}`
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
return { success: true };
|
|
793
|
+
}
|
|
794
|
+
async function cherryPickFromRef(worktreePath, refName) {
|
|
795
|
+
const result = await execa("git", ["cherry-pick", "--no-commit", refName], {
|
|
796
|
+
cwd: worktreePath,
|
|
797
|
+
stdio: "pipe",
|
|
798
|
+
reject: false
|
|
799
|
+
});
|
|
800
|
+
if (result.exitCode !== 0) {
|
|
801
|
+
return {
|
|
802
|
+
success: false,
|
|
803
|
+
error: `Failed to cherry-pick: ${result.stderr}`
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
return { success: true };
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// src/orchestrator/verification/verification-loop.ts
|
|
810
|
+
function channelCommands(channel) {
|
|
811
|
+
if (Array.isArray(channel.commands) && channel.commands.length > 0) return channel.commands;
|
|
812
|
+
if (typeof channel.command === "string" && channel.command.trim()) return [channel.command];
|
|
813
|
+
return [];
|
|
814
|
+
}
|
|
815
|
+
function dominateGateStatus(statuses) {
|
|
816
|
+
if (statuses.length === 0) return "unavailable";
|
|
817
|
+
const hard = statuses.find((status) => status === "failed" || status === "error");
|
|
818
|
+
if (hard) return hard;
|
|
819
|
+
if (statuses.some((status) => status === "timeout")) return "timeout";
|
|
820
|
+
if (statuses.some((status) => status === "unavailable")) return "unavailable";
|
|
821
|
+
if (statuses.some((status) => status === "skipped")) return "skipped";
|
|
822
|
+
return "passed";
|
|
823
|
+
}
|
|
824
|
+
function isUnavailableCoverage(coverage) {
|
|
825
|
+
return coverage?.status === "skipped_no_command" || coverage?.status === "skipped_tool_unavailable";
|
|
826
|
+
}
|
|
827
|
+
function isHardGateStatus(status) {
|
|
828
|
+
return status === "failed" || status === "timeout" || status === "error";
|
|
829
|
+
}
|
|
830
|
+
function buildStatusFromArtifact(build) {
|
|
831
|
+
if (build.ok) return "passed";
|
|
832
|
+
if (isUnavailableCoverage(build.verificationCoverage)) return "unavailable";
|
|
833
|
+
if (build.exitCode === 124) return "timeout";
|
|
834
|
+
return "failed";
|
|
835
|
+
}
|
|
836
|
+
function lintStatusFromArtifact(lint) {
|
|
837
|
+
if (isUnavailableCoverage(lint.verificationCoverage)) return "unavailable";
|
|
838
|
+
if (lint.exitCode === 0) return "passed";
|
|
839
|
+
if (lint.exitCode === 124) return "timeout";
|
|
840
|
+
return "failed";
|
|
841
|
+
}
|
|
842
|
+
function testStatusFromArtifact(status) {
|
|
843
|
+
if (status === "passed") return "passed";
|
|
844
|
+
if (status === "timeout") return "timeout";
|
|
845
|
+
if (status === "error") return "error";
|
|
846
|
+
if (status === "unavailable") return "unavailable";
|
|
847
|
+
if (status === "skipped") return "skipped";
|
|
848
|
+
return "failed";
|
|
849
|
+
}
|
|
850
|
+
async function getChangedFiles(worktreePath, baselineRef) {
|
|
851
|
+
const result = await execa2("git", ["diff", "--name-only", baselineRef, "--"], {
|
|
852
|
+
cwd: worktreePath,
|
|
853
|
+
stdio: "pipe",
|
|
854
|
+
reject: false
|
|
855
|
+
});
|
|
856
|
+
const stdout = String(result.stdout ?? "");
|
|
857
|
+
return stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).sort();
|
|
858
|
+
}
|
|
859
|
+
async function getDiffSize(worktreePath, baselineRef) {
|
|
860
|
+
const result = await execa2("git", ["diff", "--numstat", baselineRef, "--"], {
|
|
861
|
+
cwd: worktreePath,
|
|
862
|
+
stdio: "pipe",
|
|
863
|
+
reject: false
|
|
864
|
+
});
|
|
865
|
+
const stdout = String(result.stdout ?? "");
|
|
866
|
+
let total = 0;
|
|
867
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
868
|
+
const trimmed = line.trim();
|
|
869
|
+
if (!trimmed) continue;
|
|
870
|
+
const parts = trimmed.split(/\s+/);
|
|
871
|
+
if (parts.length < 3) continue;
|
|
872
|
+
const added = Number.parseInt(parts[0] ?? "0", 10);
|
|
873
|
+
const deleted = Number.parseInt(parts[1] ?? "0", 10);
|
|
874
|
+
total += (Number.isFinite(added) ? added : 0) + (Number.isFinite(deleted) ? deleted : 0);
|
|
875
|
+
}
|
|
876
|
+
return total;
|
|
877
|
+
}
|
|
878
|
+
async function detectRemovedExportsInPublicBarrels(worktreePath, baselineRef) {
|
|
879
|
+
const PUBLIC_BARRELS = /* @__PURE__ */ new Set(["src/cli/index.ts", "src/orchestrator/index.ts"]);
|
|
880
|
+
const result = await execa2("git", ["diff", "-U0", baselineRef, "--", "src"], {
|
|
881
|
+
cwd: worktreePath,
|
|
882
|
+
stdio: "pipe",
|
|
883
|
+
reject: false
|
|
884
|
+
});
|
|
885
|
+
const diff = String(result.stdout ?? "");
|
|
886
|
+
const removed = [];
|
|
887
|
+
let currentFile = null;
|
|
888
|
+
for (const line of diff.split(/\r?\n/)) {
|
|
889
|
+
if (line.startsWith("diff --git ")) {
|
|
890
|
+
const match = /^diff --git a\/(.+?) b\/(.+)$/.exec(line);
|
|
891
|
+
currentFile = match?.[2] ?? null;
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
if (!currentFile) continue;
|
|
895
|
+
if (!PUBLIC_BARRELS.has(currentFile)) continue;
|
|
896
|
+
if (line.startsWith("-export") || line.startsWith("- export")) {
|
|
897
|
+
removed.push(`${currentFile}: ${line.slice(1).trim()}`);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return {
|
|
901
|
+
removedExportLines: removed.length,
|
|
902
|
+
sample: removed.slice(0, 3).join("\n") || void 0
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
async function runDeterministicGates(worktreePath, baselineRef, timeoutMs, changedFiles = []) {
|
|
906
|
+
return withValidationSlot(
|
|
907
|
+
`gates:${worktreePath}`,
|
|
908
|
+
() => runDeterministicGatesUnpaced(worktreePath, baselineRef, timeoutMs, changedFiles),
|
|
909
|
+
{ maxWaitMs: Math.min(5 * 60 * 1e3, Math.max(5e3, timeoutMs)) }
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
async function runDeterministicGatesUnpaced(worktreePath, baselineRef, timeoutMs, changedFiles = []) {
|
|
913
|
+
const MIN_GATE_TIMEOUT_MS = 5e3;
|
|
914
|
+
const effectiveTimeoutMs = Math.max(MIN_GATE_TIMEOUT_MS, timeoutMs);
|
|
915
|
+
const scope = resolveVerificationChannelScope(
|
|
916
|
+
worktreePath,
|
|
917
|
+
changedFiles
|
|
918
|
+
);
|
|
919
|
+
const buildCommands = channelCommands(scope.build);
|
|
920
|
+
const lintCommands = channelCommands(scope.lint);
|
|
921
|
+
const testCommands = selectScopedTestCommands(scope);
|
|
922
|
+
const buildStart = Date.now();
|
|
923
|
+
const buildChannelBudgetMs = Math.min(6e4, effectiveTimeoutMs);
|
|
924
|
+
const buildArtifacts = scope.build.availability === "unavailable" ? {
|
|
925
|
+
status: "unavailable",
|
|
926
|
+
exitCode: 127,
|
|
927
|
+
command: "build (verification unavailable)",
|
|
928
|
+
durationMs: 0,
|
|
929
|
+
stdoutTail: "",
|
|
930
|
+
stderrTail: "",
|
|
931
|
+
outputTail: "Build verification unavailable: no covering command for changed language.",
|
|
932
|
+
verificationCoverage: {
|
|
933
|
+
status: "skipped_no_command",
|
|
934
|
+
reason: "No covering build/typecheck command for changed language."
|
|
935
|
+
}
|
|
936
|
+
} : scope.build.relevant ? await (async () => {
|
|
937
|
+
if (buildCommands.length > 0) {
|
|
938
|
+
let remaining = buildChannelBudgetMs;
|
|
939
|
+
const commandResults = [];
|
|
940
|
+
for (const command of buildCommands) {
|
|
941
|
+
if (remaining <= 0) {
|
|
942
|
+
commandResults.push({
|
|
943
|
+
command,
|
|
944
|
+
language: describeVerificationCommand(command).language,
|
|
945
|
+
status: "timeout",
|
|
946
|
+
exitCode: 124,
|
|
947
|
+
durationMs: 0,
|
|
948
|
+
outputTail: "Build command skipped: channel timeout budget exhausted."
|
|
949
|
+
});
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
const cmdStart = Date.now();
|
|
953
|
+
const build2 = await runTypecheckWithArtifacts(worktreePath, {
|
|
954
|
+
timeoutMs: remaining,
|
|
955
|
+
command
|
|
956
|
+
});
|
|
957
|
+
const durationMs = Math.max(build2.durationMs ?? 0, Date.now() - cmdStart);
|
|
958
|
+
remaining = Math.max(0, remaining - durationMs);
|
|
959
|
+
const status = buildStatusFromArtifact(build2);
|
|
960
|
+
commandResults.push({
|
|
961
|
+
command: build2.command,
|
|
962
|
+
language: build2.language ?? describeVerificationCommand(command).language,
|
|
963
|
+
status,
|
|
964
|
+
exitCode: build2.exitCode,
|
|
965
|
+
durationMs,
|
|
966
|
+
outputTail: build2.outputTail,
|
|
967
|
+
stdoutTail: build2.stdoutTail,
|
|
968
|
+
stderrTail: build2.stderrTail
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
const statuses = commandResults.map((r) => r.status);
|
|
972
|
+
const aggregateStatus = dominateGateStatus(statuses);
|
|
973
|
+
const representative = commandResults.find((r) => r.status === aggregateStatus) ?? commandResults[0];
|
|
974
|
+
const totalDuration = commandResults.reduce((sum, r) => sum + (r.durationMs ?? 0), 0);
|
|
975
|
+
return {
|
|
976
|
+
status: aggregateStatus,
|
|
977
|
+
exitCode: representative.exitCode,
|
|
978
|
+
command: representative.command,
|
|
979
|
+
durationMs: Math.max(totalDuration, Date.now() - buildStart),
|
|
980
|
+
stdoutTail: representative.stdoutTail ?? "",
|
|
981
|
+
stderrTail: representative.stderrTail ?? "",
|
|
982
|
+
outputTail: representative.outputTail,
|
|
983
|
+
language: representative.language,
|
|
984
|
+
verificationCoverage: aggregateStatus === "unavailable" ? {
|
|
985
|
+
status: "skipped_tool_unavailable",
|
|
986
|
+
command: representative.command,
|
|
987
|
+
reason: "One or more scoped build commands were unavailable."
|
|
988
|
+
} : aggregateStatus === "passed" ? { status: "verified", command: representative.command } : { status: "unverified", command: representative.command },
|
|
989
|
+
commandResults
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
const build = await runTypecheckWithArtifacts(worktreePath, {
|
|
993
|
+
timeoutMs: buildChannelBudgetMs,
|
|
994
|
+
...scope.scoped && scope.build.command ? { command: scope.build.command } : {}
|
|
995
|
+
});
|
|
996
|
+
const buildDurationMs = Date.now() - buildStart;
|
|
997
|
+
const buildStatus = buildStatusFromArtifact(build);
|
|
998
|
+
return {
|
|
999
|
+
status: buildStatus,
|
|
1000
|
+
exitCode: build.exitCode,
|
|
1001
|
+
command: build.command,
|
|
1002
|
+
durationMs: Math.max(build.durationMs ?? 0, buildDurationMs),
|
|
1003
|
+
stdoutTail: build.stdoutTail,
|
|
1004
|
+
stderrTail: build.stderrTail,
|
|
1005
|
+
outputTail: build.outputTail,
|
|
1006
|
+
verificationCoverage: build.verificationCoverage,
|
|
1007
|
+
// Carry the SOURCE-tagged language through so the current side parses by the
|
|
1008
|
+
// same (channel, language) the baseline recorded (root invariant). Fall back
|
|
1009
|
+
// to the scoped command's descriptor when the gate did not tag one.
|
|
1010
|
+
language: build.language ?? (scope.build.command ? describeVerificationCommand(scope.build.command).language : void 0)
|
|
1011
|
+
};
|
|
1012
|
+
})() : {
|
|
1013
|
+
// OUT-OF-SCOPE: this channel was intentionally not run for the task scope
|
|
1014
|
+
// (e.g. a Python-only change does not exercise the Node build). This is a
|
|
1015
|
+
// 'skipped' status (deliberately not run), DISTINCT from 'unavailable' (the
|
|
1016
|
+
// tool was relevant but missing/could-not-run). gatesHavePromotableCoverage
|
|
1017
|
+
// allows an out-of-scope 'skipped' build but still rejects an in-scope
|
|
1018
|
+
// 'unavailable' build, so an out-of-scope channel does not block promotion
|
|
1019
|
+
// while a genuinely-missing in-scope tool stays non-promotable (fail-closed).
|
|
1020
|
+
status: "skipped",
|
|
1021
|
+
exitCode: 0,
|
|
1022
|
+
command: "build (not task-relevant)",
|
|
1023
|
+
durationMs: 0,
|
|
1024
|
+
stdoutTail: "",
|
|
1025
|
+
stderrTail: "",
|
|
1026
|
+
outputTail: "Skipped build gate: not task-relevant to the changed files."
|
|
1027
|
+
};
|
|
1028
|
+
const remainingMs = Math.max(MIN_GATE_TIMEOUT_MS, effectiveTimeoutMs - (Date.now() - buildStart));
|
|
1029
|
+
const lintTimeoutMs = remainingMs;
|
|
1030
|
+
const testTimeoutMs = remainingMs;
|
|
1031
|
+
const runLintChannel = async () => {
|
|
1032
|
+
if (scope.lint.availability === "unavailable") {
|
|
1033
|
+
return {
|
|
1034
|
+
status: "unavailable",
|
|
1035
|
+
exitCode: 127,
|
|
1036
|
+
command: "lint (verification unavailable)",
|
|
1037
|
+
durationMs: 0,
|
|
1038
|
+
outputTail: "Lint verification unavailable: no covering command for changed language.",
|
|
1039
|
+
verificationCoverage: {
|
|
1040
|
+
status: "skipped_no_command",
|
|
1041
|
+
reason: "No covering lint command for changed language."
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
if (!scope.lint.relevant) {
|
|
1046
|
+
return {
|
|
1047
|
+
status: "skipped",
|
|
1048
|
+
exitCode: 0,
|
|
1049
|
+
command: "lint (not task-relevant)",
|
|
1050
|
+
durationMs: 0,
|
|
1051
|
+
outputTail: "Skipped lint gate: not task-relevant to the changed files."
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
if (lintCommands.length > 0) {
|
|
1055
|
+
let remaining = lintTimeoutMs;
|
|
1056
|
+
const commandResults = [];
|
|
1057
|
+
for (const command of lintCommands) {
|
|
1058
|
+
if (remaining <= 0) {
|
|
1059
|
+
commandResults.push({
|
|
1060
|
+
command,
|
|
1061
|
+
language: describeVerificationCommand(command).language,
|
|
1062
|
+
status: "timeout",
|
|
1063
|
+
exitCode: 124,
|
|
1064
|
+
durationMs: 0,
|
|
1065
|
+
outputTail: "Lint command skipped: channel timeout budget exhausted."
|
|
1066
|
+
});
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
const lint3 = await runLintOnDiffWithArtifacts(worktreePath, {
|
|
1070
|
+
baseBranch: baselineRef,
|
|
1071
|
+
timeoutMs: remaining,
|
|
1072
|
+
command
|
|
1073
|
+
});
|
|
1074
|
+
remaining = Math.max(0, remaining - (lint3.durationMs ?? 0));
|
|
1075
|
+
commandResults.push({
|
|
1076
|
+
command: lint3.command,
|
|
1077
|
+
language: lint3.language ?? describeVerificationCommand(command).language,
|
|
1078
|
+
status: lintStatusFromArtifact(lint3),
|
|
1079
|
+
exitCode: lint3.exitCode,
|
|
1080
|
+
durationMs: lint3.durationMs,
|
|
1081
|
+
outputTail: lint3.outputTail
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
const aggregateStatus = dominateGateStatus(commandResults.map((r) => r.status));
|
|
1085
|
+
const representative = commandResults.find((r) => r.status === aggregateStatus) ?? commandResults[0];
|
|
1086
|
+
return {
|
|
1087
|
+
status: aggregateStatus,
|
|
1088
|
+
exitCode: representative.exitCode,
|
|
1089
|
+
command: representative.command,
|
|
1090
|
+
durationMs: commandResults.reduce((sum, r) => sum + (r.durationMs ?? 0), 0),
|
|
1091
|
+
outputTail: representative.outputTail,
|
|
1092
|
+
language: representative.language,
|
|
1093
|
+
verificationCoverage: aggregateStatus === "unavailable" ? {
|
|
1094
|
+
status: "skipped_tool_unavailable",
|
|
1095
|
+
command: representative.command,
|
|
1096
|
+
reason: "One or more scoped lint commands were unavailable."
|
|
1097
|
+
} : aggregateStatus === "passed" ? { status: "verified", command: representative.command } : { status: "unverified", command: representative.command },
|
|
1098
|
+
commandResults
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
const lint2 = await runLintOnDiffWithArtifacts(worktreePath, {
|
|
1102
|
+
baseBranch: baselineRef,
|
|
1103
|
+
timeoutMs: lintTimeoutMs,
|
|
1104
|
+
...scope.scoped && scope.lint.command ? { command: scope.lint.command } : {}
|
|
1105
|
+
});
|
|
1106
|
+
return {
|
|
1107
|
+
status: lintStatusFromArtifact(lint2),
|
|
1108
|
+
exitCode: lint2.exitCode,
|
|
1109
|
+
command: lint2.command,
|
|
1110
|
+
durationMs: lint2.durationMs,
|
|
1111
|
+
outputTail: lint2.outputTail,
|
|
1112
|
+
verificationCoverage: lint2.verificationCoverage,
|
|
1113
|
+
language: lint2.language ?? (scope.lint.command ? describeVerificationCommand(scope.lint.command).language : void 0)
|
|
1114
|
+
};
|
|
1115
|
+
};
|
|
1116
|
+
const runTestChannel = async () => {
|
|
1117
|
+
if (scope.test.availability === "unavailable") {
|
|
1118
|
+
return {
|
|
1119
|
+
status: "unavailable",
|
|
1120
|
+
exitCode: 127,
|
|
1121
|
+
command: "test (verification unavailable)",
|
|
1122
|
+
durationMs: 0,
|
|
1123
|
+
outputTail: "Test verification unavailable: no covering command for changed language.",
|
|
1124
|
+
failingTests: []
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
if (!scope.test.relevant) {
|
|
1128
|
+
return {
|
|
1129
|
+
// OUT-OF-SCOPE: intentionally not run for this task scope. 'skipped' (not
|
|
1130
|
+
// run on purpose) is distinct from 'unavailable' (relevant tool missing).
|
|
1131
|
+
status: "skipped",
|
|
1132
|
+
exitCode: 0,
|
|
1133
|
+
command: "test (not task-relevant)",
|
|
1134
|
+
durationMs: 0,
|
|
1135
|
+
outputTail: "Skipped test gate: not task-relevant to the changed files.",
|
|
1136
|
+
failingTests: []
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
if (testCommands.length > 0) {
|
|
1140
|
+
let remaining = testTimeoutMs;
|
|
1141
|
+
const commandResults = [];
|
|
1142
|
+
for (const command of testCommands) {
|
|
1143
|
+
if (remaining <= 0) {
|
|
1144
|
+
commandResults.push({
|
|
1145
|
+
command,
|
|
1146
|
+
language: describeVerificationCommand(command).language,
|
|
1147
|
+
status: "timeout",
|
|
1148
|
+
exitCode: 124,
|
|
1149
|
+
durationMs: 0,
|
|
1150
|
+
outputTail: "Test command skipped: channel timeout budget exhausted.",
|
|
1151
|
+
failingTests: []
|
|
1152
|
+
});
|
|
1153
|
+
continue;
|
|
1154
|
+
}
|
|
1155
|
+
const tests3 = await runDeterministicTestGateWithArtifacts(worktreePath, {
|
|
1156
|
+
baseRef: baselineRef,
|
|
1157
|
+
timeoutMs: remaining,
|
|
1158
|
+
command
|
|
1159
|
+
});
|
|
1160
|
+
remaining = Math.max(0, remaining - (tests3.durationMs ?? 0));
|
|
1161
|
+
const status = testStatusFromArtifact(tests3.status);
|
|
1162
|
+
commandResults.push({
|
|
1163
|
+
command: tests3.command,
|
|
1164
|
+
language: tests3.language ?? describeVerificationCommand(command).language,
|
|
1165
|
+
status,
|
|
1166
|
+
exitCode: tests3.exitCode,
|
|
1167
|
+
durationMs: tests3.durationMs,
|
|
1168
|
+
outputTail: tests3.outputTail,
|
|
1169
|
+
failingTests: tests3.failingTests
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
const aggregateStatus = dominateGateStatus(commandResults.map((r) => r.status));
|
|
1173
|
+
const representative = commandResults.find((r) => r.status === aggregateStatus) ?? commandResults[0];
|
|
1174
|
+
const failingTests = Array.from(new Set(commandResults.flatMap((r) => r.failingTests ?? [])));
|
|
1175
|
+
return {
|
|
1176
|
+
status: aggregateStatus,
|
|
1177
|
+
exitCode: representative.exitCode,
|
|
1178
|
+
command: representative.command,
|
|
1179
|
+
durationMs: commandResults.reduce((sum, r) => sum + (r.durationMs ?? 0), 0),
|
|
1180
|
+
outputTail: representative.outputTail,
|
|
1181
|
+
failingTests,
|
|
1182
|
+
language: representative.language,
|
|
1183
|
+
commandResults
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
const tests2 = await runDeterministicTestGateWithArtifacts(worktreePath, {
|
|
1187
|
+
baseRef: baselineRef,
|
|
1188
|
+
timeoutMs: testTimeoutMs,
|
|
1189
|
+
...scope.scoped && scope.test.command ? { command: scope.test.command } : {}
|
|
1190
|
+
});
|
|
1191
|
+
return {
|
|
1192
|
+
status: testStatusFromArtifact(tests2.status),
|
|
1193
|
+
exitCode: tests2.exitCode,
|
|
1194
|
+
command: tests2.command,
|
|
1195
|
+
durationMs: tests2.durationMs,
|
|
1196
|
+
outputTail: tests2.outputTail,
|
|
1197
|
+
failingTests: tests2.failingTests,
|
|
1198
|
+
language: tests2.language ?? (scope.test.command ? describeVerificationCommand(scope.test.command).language : void 0)
|
|
1199
|
+
};
|
|
1200
|
+
};
|
|
1201
|
+
const [lint, tests] = await Promise.all([runLintChannel(), runTestChannel()]);
|
|
1202
|
+
const gates = {
|
|
1203
|
+
build: buildArtifacts,
|
|
1204
|
+
lint: {
|
|
1205
|
+
status: lint.status,
|
|
1206
|
+
exitCode: lint.exitCode,
|
|
1207
|
+
command: lint.command,
|
|
1208
|
+
durationMs: lint.durationMs,
|
|
1209
|
+
outputTail: lint.outputTail,
|
|
1210
|
+
verificationCoverage: lint.verificationCoverage,
|
|
1211
|
+
language: lint.language,
|
|
1212
|
+
...lint.commandResults ? { commandResults: lint.commandResults } : {}
|
|
1213
|
+
},
|
|
1214
|
+
tests: {
|
|
1215
|
+
status: tests.status,
|
|
1216
|
+
exitCode: tests.exitCode,
|
|
1217
|
+
command: tests.command,
|
|
1218
|
+
durationMs: tests.durationMs,
|
|
1219
|
+
outputTail: tests.outputTail,
|
|
1220
|
+
failingTests: tests.failingTests,
|
|
1221
|
+
language: tests.language,
|
|
1222
|
+
...tests.commandResults ? { commandResults: tests.commandResults } : {}
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
const passed = buildArtifacts.status === "passed" && (tests.status === "passed" || tests.status === "skipped");
|
|
1226
|
+
return { passed, gates };
|
|
1227
|
+
}
|
|
1228
|
+
function gatesHaveNoHardFailures(gates, checkTestGate) {
|
|
1229
|
+
if (isHardGateStatus(gates.build.status)) return false;
|
|
1230
|
+
if (!checkTestGate) return true;
|
|
1231
|
+
const testStatus = gates.tests?.status;
|
|
1232
|
+
return testStatus === void 0 || !isHardGateStatus(testStatus);
|
|
1233
|
+
}
|
|
1234
|
+
function gatesHavePromotableCoverage(gates, checkTestGate) {
|
|
1235
|
+
if (gates.build.status === "unavailable") return false;
|
|
1236
|
+
if (!checkTestGate) return true;
|
|
1237
|
+
if (gates.build.status !== "passed" && gates.build.status !== "skipped") return true;
|
|
1238
|
+
const testStatus = gates.tests?.status;
|
|
1239
|
+
return testStatus === "passed" || testStatus === "failed" || testStatus === "timeout" || testStatus === "error";
|
|
1240
|
+
}
|
|
1241
|
+
function buildVerificationFindings(gates, passedBaseline) {
|
|
1242
|
+
if (passedBaseline) return [];
|
|
1243
|
+
const findings = [];
|
|
1244
|
+
if (isHardGateStatus(gates.build.status)) {
|
|
1245
|
+
findings.push({
|
|
1246
|
+
kind: "build",
|
|
1247
|
+
command: gates.build.command,
|
|
1248
|
+
exitCode: gates.build.exitCode,
|
|
1249
|
+
outputTail: gates.build.outputTail,
|
|
1250
|
+
severity: "error"
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
if (gates.tests && gates.tests.status !== "passed" && gates.tests.status !== "skipped" && gates.tests.status !== "unavailable") {
|
|
1254
|
+
findings.push({
|
|
1255
|
+
kind: "test",
|
|
1256
|
+
command: gates.tests.command,
|
|
1257
|
+
exitCode: gates.tests.exitCode,
|
|
1258
|
+
outputTail: gates.tests.outputTail,
|
|
1259
|
+
failingTests: gates.tests.failingTests,
|
|
1260
|
+
severity: "error"
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
return findings;
|
|
1264
|
+
}
|
|
1265
|
+
async function runDeterministicVerificationOnce(params) {
|
|
1266
|
+
const checkTestGate = params.checkTestGate ?? true;
|
|
1267
|
+
const started = Date.now();
|
|
1268
|
+
const gateRun = params.precomputedGates ? {
|
|
1269
|
+
gates: params.precomputedGates,
|
|
1270
|
+
passed: params.precomputedGates.build.status === "passed" && (!checkTestGate || params.precomputedGates.tests?.status === "passed")
|
|
1271
|
+
} : await runDeterministicGates(
|
|
1272
|
+
params.worktreePath,
|
|
1273
|
+
params.baselineRef,
|
|
1274
|
+
params.timeoutMs,
|
|
1275
|
+
params.changedFiles ?? []
|
|
1276
|
+
);
|
|
1277
|
+
let passedBaseline = gatesHaveNoHardFailures(gateRun.gates, checkTestGate);
|
|
1278
|
+
let newIssueCount = 0;
|
|
1279
|
+
let fixedIssueCount = 0;
|
|
1280
|
+
if (params.baseline) {
|
|
1281
|
+
const comparisonOutcomes = verificationOutcomesFromGateArtifacts(gateRun.gates, {
|
|
1282
|
+
source: "verification-loop",
|
|
1283
|
+
cwd: params.worktreePath
|
|
1284
|
+
});
|
|
1285
|
+
const baselineComparison = compareVerificationOutcomeToBaseline(
|
|
1286
|
+
params.baseline,
|
|
1287
|
+
comparisonOutcomes
|
|
1288
|
+
);
|
|
1289
|
+
const gateExitRegressions = cleanBaselineGateExitRegressions(
|
|
1290
|
+
params.baseline,
|
|
1291
|
+
gateRun.gates,
|
|
1292
|
+
checkTestGate
|
|
1293
|
+
);
|
|
1294
|
+
const hardStructuredNewIssueCount = baselineComparison.delta.build.newIssues.size + (checkTestGate ? baselineComparison.delta.test.newIssues.size : 0);
|
|
1295
|
+
passedBaseline = hardStructuredNewIssueCount === 0 && gateExitRegressions.length === 0;
|
|
1296
|
+
const structuredNewIssueCount = checkTestGate ? hardStructuredNewIssueCount : baselineComparison.delta.build.newIssues.size;
|
|
1297
|
+
const unparsedGateRegressionCount = gateExitRegressions.filter((kind) => {
|
|
1298
|
+
if (kind === "build") return baselineComparison.delta.build.newIssues.size === 0;
|
|
1299
|
+
return baselineComparison.delta.test.newIssues.size === 0;
|
|
1300
|
+
}).length;
|
|
1301
|
+
newIssueCount = structuredNewIssueCount + unparsedGateRegressionCount;
|
|
1302
|
+
fixedIssueCount = baselineComparison.delta.totalFixedIssues;
|
|
1303
|
+
}
|
|
1304
|
+
const durationMs = Date.now() - started;
|
|
1305
|
+
const findings = buildVerificationFindings(gateRun.gates, passedBaseline);
|
|
1306
|
+
const promotable = passedBaseline && gatesHavePromotableCoverage(gateRun.gates, checkTestGate);
|
|
1307
|
+
return {
|
|
1308
|
+
verification: {
|
|
1309
|
+
passed: promotable,
|
|
1310
|
+
passedBaseline,
|
|
1311
|
+
findings
|
|
1312
|
+
},
|
|
1313
|
+
verificationLoop: {
|
|
1314
|
+
enabled: true,
|
|
1315
|
+
passed: promotable,
|
|
1316
|
+
passedBaseline,
|
|
1317
|
+
stopReason: passedBaseline ? "single_iteration_no_new_regressions" : "single_iteration_gates_failed",
|
|
1318
|
+
bestIterationIndex: 0,
|
|
1319
|
+
bestIterationPromotable: promotable,
|
|
1320
|
+
iterations: [
|
|
1321
|
+
{
|
|
1322
|
+
iteration: 1,
|
|
1323
|
+
passed: gateRun.passed,
|
|
1324
|
+
passedBaseline,
|
|
1325
|
+
newIssueCount,
|
|
1326
|
+
fixedIssueCount,
|
|
1327
|
+
durationMs,
|
|
1328
|
+
costUsd: 0,
|
|
1329
|
+
cumulativeCostUsd: 0,
|
|
1330
|
+
constraints: {
|
|
1331
|
+
maxFilesTouched: 0,
|
|
1332
|
+
diffSizeCap: 0,
|
|
1333
|
+
apiBreakAllowed: false,
|
|
1334
|
+
violations: []
|
|
1335
|
+
},
|
|
1336
|
+
build: gateRun.gates.build,
|
|
1337
|
+
lint: gateRun.gates.lint,
|
|
1338
|
+
tests: gateRun.gates.tests,
|
|
1339
|
+
...params.precomputedGates ? { gateSource: "precomputed" } : {}
|
|
1340
|
+
}
|
|
1341
|
+
]
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
function gateVector(gates) {
|
|
1346
|
+
const build = gates.build.status === "passed" ? 1 : 0;
|
|
1347
|
+
const lint = 1;
|
|
1348
|
+
const tests = gates.tests?.status === "passed" ? 1 : 0;
|
|
1349
|
+
return [build, lint, tests];
|
|
1350
|
+
}
|
|
1351
|
+
function isRegression(prev, next) {
|
|
1352
|
+
const [pb, pl, pt] = gateVector(prev);
|
|
1353
|
+
const [nb, nl, nt] = gateVector(next);
|
|
1354
|
+
if (nb < pb) return true;
|
|
1355
|
+
const prevSum = pb + pl + pt;
|
|
1356
|
+
const nextSum = nb + nl + nt;
|
|
1357
|
+
return nextSum < prevSum;
|
|
1358
|
+
}
|
|
1359
|
+
function cleanBaselineGateExitRegressions(baseline, gates, checkTestGate) {
|
|
1360
|
+
const regressions = [];
|
|
1361
|
+
if (isHardGateStatus(gates.build.status) && gates.build.exitCode !== 0 && baseline.typeErrors.exitCode === 0) {
|
|
1362
|
+
regressions.push("build");
|
|
1363
|
+
}
|
|
1364
|
+
if (checkTestGate && gates.tests && gates.tests.status !== "skipped" && gates.tests.status !== "unavailable" && gates.tests.exitCode !== 0 && baseline.testResults.exitCode === 0) {
|
|
1365
|
+
regressions.push("test");
|
|
1366
|
+
}
|
|
1367
|
+
return regressions;
|
|
1368
|
+
}
|
|
1369
|
+
function buildRepairPrompt(opts) {
|
|
1370
|
+
const { originalPrompt, iteration, gates, constraints, allowedFiles, scopePolicy } = opts;
|
|
1371
|
+
const parts = [];
|
|
1372
|
+
parts.push(originalPrompt.trimEnd());
|
|
1373
|
+
parts.push("\n\n---\n\n");
|
|
1374
|
+
parts.push(
|
|
1375
|
+
`VERIFICATION-DRIVEN REPAIR (iteration ${iteration}): Fix the deterministic gate failures below.`
|
|
1376
|
+
);
|
|
1377
|
+
parts.push(
|
|
1378
|
+
"Stabilization approach:",
|
|
1379
|
+
"- Prioritize high-leverage, low-cost fixes first (e.g., lint/formatting, obvious type errors).",
|
|
1380
|
+
"- Keep the diff cohesive and targeted; preserve the core solution unless a change is clearly required.",
|
|
1381
|
+
"- Only attempt deeper behavioral/test fixes once cheap failures are resolved or if time remains."
|
|
1382
|
+
);
|
|
1383
|
+
parts.push("Make the MINIMUM change set required to improve gate outcomes.");
|
|
1384
|
+
parts.push(
|
|
1385
|
+
`HARD CONSTRAINTS (enforced): maxFilesTouched=${constraints.maxFilesTouched}, diffSizeCap=${constraints.diffSizeCap}, apiBreakAllowed=${constraints.apiBreakAllowed}`
|
|
1386
|
+
);
|
|
1387
|
+
if (constraints.violations.length > 0) {
|
|
1388
|
+
parts.push(
|
|
1389
|
+
`CONSTRAINT VIOLATIONS DETECTED (must be fixed/undone):
|
|
1390
|
+
- ${constraints.violations.join("\n- ")}`
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
if (allowedFiles.length > 0) {
|
|
1394
|
+
parts.push(`ALLOWED FILES (derived from prompt/gates):
|
|
1395
|
+
- ${allowedFiles.join("\n- ")}`);
|
|
1396
|
+
if (scopePolicy === "strict") {
|
|
1397
|
+
parts.push("SCOPE POLICY: strict - do not modify files outside the allowed list.");
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
const gateBlock = (label, g) => {
|
|
1401
|
+
return [
|
|
1402
|
+
`
|
|
1403
|
+
${label}:`,
|
|
1404
|
+
`Command: ${g.command}`,
|
|
1405
|
+
`Exit Code: ${g.exitCode}`,
|
|
1406
|
+
`Output (tail):
|
|
1407
|
+
${renderUntrustedCommandOutput(g.outputTail)}`
|
|
1408
|
+
].join("\n");
|
|
1409
|
+
};
|
|
1410
|
+
parts.push(
|
|
1411
|
+
gateBlock("BUILD/TYPECHECK", {
|
|
1412
|
+
command: gates.build.command,
|
|
1413
|
+
exitCode: gates.build.exitCode,
|
|
1414
|
+
outputTail: gates.build.outputTail
|
|
1415
|
+
})
|
|
1416
|
+
);
|
|
1417
|
+
if (gates.lint && gates.lint.status !== "passed") {
|
|
1418
|
+
parts.push(
|
|
1419
|
+
gateBlock("LINT", {
|
|
1420
|
+
command: gates.lint.command,
|
|
1421
|
+
exitCode: gates.lint.exitCode,
|
|
1422
|
+
outputTail: gates.lint.outputTail
|
|
1423
|
+
})
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1426
|
+
if (gates.tests && gates.tests.status !== "passed" && gates.tests.status !== "skipped" && gates.tests.status !== "unavailable") {
|
|
1427
|
+
const failing = renderUntrustedFailingTests(gates.tests.failingTests);
|
|
1428
|
+
parts.push(
|
|
1429
|
+
`${gateBlock("TESTS", { command: gates.tests.command, exitCode: gates.tests.exitCode, outputTail: gates.tests.outputTail })}
|
|
1430
|
+
${failing}`
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
parts.push("\nDo not remove public exports unless explicitly allowed.");
|
|
1434
|
+
parts.push("Keep refactors tightly scoped to what is required for correctness.");
|
|
1435
|
+
parts.push(`
|
|
1436
|
+
${renderRepairConstraints()}`);
|
|
1437
|
+
return parts.join("\n");
|
|
1438
|
+
}
|
|
1439
|
+
var GATE_EVIDENCE_EXTRA_EXTENSIONS = ["json", "css", "scss", "md", "ya?ml", "toml", "snap"];
|
|
1440
|
+
function buildGateEvidencePathPattern() {
|
|
1441
|
+
const escapeForClass = (ext) => ext.replace(/[+.]/g, "\\$&");
|
|
1442
|
+
const sourceExtensions = [...PROJECT_SOURCE_EXTENSIONS].map(
|
|
1443
|
+
(ext) => escapeForClass(ext.replace(/^\./, ""))
|
|
1444
|
+
);
|
|
1445
|
+
const extensionAlternation = [...sourceExtensions, ...GATE_EVIDENCE_EXTRA_EXTENSIONS].join("|");
|
|
1446
|
+
return new RegExp(
|
|
1447
|
+
`(?:^|[\\s'"([])((?:\\.{1,2}\\/)?(?:[A-Za-z0-9_.@+-]+\\/)*[A-Za-z0-9_.@+-]+\\.(?:${extensionAlternation}))(?:[:(]|$)`,
|
|
1448
|
+
"gm"
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
var GATE_EVIDENCE_PATH_PATTERN = buildGateEvidencePathPattern();
|
|
1452
|
+
function extractFilePathsFromText(text) {
|
|
1453
|
+
if (!text) return [];
|
|
1454
|
+
const paths = /* @__PURE__ */ new Set();
|
|
1455
|
+
for (const match of text.matchAll(GATE_EVIDENCE_PATH_PATTERN)) {
|
|
1456
|
+
const filePath = match[1]?.trim();
|
|
1457
|
+
if (filePath) paths.add(filePath.replace(/^\.\//, ""));
|
|
1458
|
+
}
|
|
1459
|
+
return [...paths];
|
|
1460
|
+
}
|
|
1461
|
+
function collectIssueFiles(issues) {
|
|
1462
|
+
return issues?.toArray?.().flatMap((issue) => issue.file ? [issue.file] : []) ?? [];
|
|
1463
|
+
}
|
|
1464
|
+
function deriveAllowedFilesFromGateEvidence(gates, comparison) {
|
|
1465
|
+
const files = /* @__PURE__ */ new Set();
|
|
1466
|
+
for (const file of [
|
|
1467
|
+
...collectIssueFiles(comparison?.delta.build.newIssues),
|
|
1468
|
+
...collectIssueFiles(comparison?.delta.lint.newIssues),
|
|
1469
|
+
...collectIssueFiles(comparison?.delta.test.newIssues),
|
|
1470
|
+
...extractFilePathsFromText(gates.build.outputTail),
|
|
1471
|
+
...extractFilePathsFromText(gates.build.stderrTail),
|
|
1472
|
+
...extractFilePathsFromText(gates.build.stdoutTail),
|
|
1473
|
+
...extractFilePathsFromText(gates.lint?.outputTail),
|
|
1474
|
+
...extractFilePathsFromText(gates.tests?.outputTail),
|
|
1475
|
+
...gates.tests?.failingTests.flatMap(extractFilePathsFromText) ?? []
|
|
1476
|
+
]) {
|
|
1477
|
+
files.add(file);
|
|
1478
|
+
}
|
|
1479
|
+
return [...files].sort();
|
|
1480
|
+
}
|
|
1481
|
+
function mergeRepairAllowedFiles(promptAllowedFiles, gateDerivedAllowedFiles) {
|
|
1482
|
+
const merged = /* @__PURE__ */ new Set();
|
|
1483
|
+
for (const file of [...promptAllowedFiles, ...gateDerivedAllowedFiles]) {
|
|
1484
|
+
const normalized = file.trim();
|
|
1485
|
+
if (normalized.length > 0) {
|
|
1486
|
+
merged.add(normalized);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
return [...merged].sort();
|
|
1490
|
+
}
|
|
1491
|
+
function buildScopedRepairPrompt(opts) {
|
|
1492
|
+
const { originalPrompt, iteration, gates, constraints, comparison, allowedFiles, scopePolicy } = opts;
|
|
1493
|
+
const parts = [];
|
|
1494
|
+
parts.push(originalPrompt.trimEnd());
|
|
1495
|
+
parts.push("\n\n---\n\n");
|
|
1496
|
+
parts.push(
|
|
1497
|
+
`BASELINE-AWARE REPAIR (iteration ${iteration}): Fix only the NEW issues introduced by your changes.`
|
|
1498
|
+
);
|
|
1499
|
+
if (allowedFiles.length > 0) {
|
|
1500
|
+
parts.push(`
|
|
1501
|
+
ALLOWED FILES (you may only modify these):
|
|
1502
|
+
- ${allowedFiles.join("\n- ")}`);
|
|
1503
|
+
if (scopePolicy === "strict") {
|
|
1504
|
+
parts.push(
|
|
1505
|
+
"\nSCOPE POLICY: strict - Do NOT modify files outside the allowed list, even if they have errors."
|
|
1506
|
+
);
|
|
1507
|
+
} else {
|
|
1508
|
+
parts.push(
|
|
1509
|
+
"\nSCOPE POLICY: expand - You may modify dependent files if they have new errors caused by your changes."
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
parts.push("\nStabilization approach:");
|
|
1514
|
+
parts.push("- Focus ONLY on NEW issues listed below - ignore pre-existing issues.");
|
|
1515
|
+
parts.push("- Prioritize high-leverage, low-cost fixes (lint, obvious type errors).");
|
|
1516
|
+
parts.push("- Keep the diff cohesive; preserve your core solution.");
|
|
1517
|
+
parts.push("- Make the MINIMUM change set to resolve new issues.");
|
|
1518
|
+
const newBuild = comparison.delta.build.newIssues.toArray();
|
|
1519
|
+
const newLint = comparison.delta.lint.newIssues.toArray();
|
|
1520
|
+
const newTest = comparison.delta.test.newIssues.toArray();
|
|
1521
|
+
const totalNew = newBuild.length + newLint.length + newTest.length;
|
|
1522
|
+
if (totalNew > 0) {
|
|
1523
|
+
parts.push(`
|
|
1524
|
+
=== NEW ISSUES TO FIX (${totalNew} total) ===`);
|
|
1525
|
+
if (newBuild.length > 0) {
|
|
1526
|
+
parts.push(`
|
|
1527
|
+
NEW TYPE ERRORS (${newBuild.length}):`);
|
|
1528
|
+
for (const issue of newBuild.slice(0, 20)) {
|
|
1529
|
+
const loc = issue.line ? `:${issue.line}${issue.col ? `:${issue.col}` : ""}` : "";
|
|
1530
|
+
const message = presentUntrustedRepairEvidence(issue.originalMessage ?? "");
|
|
1531
|
+
parts.push(`- ${issue.file}${loc}: ${issue.code} - ${message}`);
|
|
1532
|
+
}
|
|
1533
|
+
if (newBuild.length > 20) {
|
|
1534
|
+
parts.push(` ... and ${newBuild.length - 20} more`);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
if (newLint.length > 0) {
|
|
1538
|
+
parts.push(`
|
|
1539
|
+
NEW LINT ERRORS (${newLint.length}):`);
|
|
1540
|
+
for (const issue of newLint.slice(0, 20)) {
|
|
1541
|
+
const loc = issue.line ? `:${issue.line}${issue.col ? `:${issue.col}` : ""}` : "";
|
|
1542
|
+
parts.push(`- ${issue.file}${loc}: ${issue.code}`);
|
|
1543
|
+
}
|
|
1544
|
+
if (newLint.length > 20) {
|
|
1545
|
+
parts.push(` ... and ${newLint.length - 20} more`);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
if (newTest.length > 0) {
|
|
1549
|
+
parts.push(`
|
|
1550
|
+
NEW TEST FAILURES (${newTest.length}):`);
|
|
1551
|
+
for (const issue of newTest.slice(0, 10)) {
|
|
1552
|
+
parts.push(`- ${issue.file ? `${issue.file}: ` : ""}${issue.code}`);
|
|
1553
|
+
}
|
|
1554
|
+
if (newTest.length > 10) {
|
|
1555
|
+
parts.push(` ... and ${newTest.length - 10} more`);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
} else {
|
|
1559
|
+
parts.push("\n=== NO NEW ISSUES ===");
|
|
1560
|
+
parts.push("Your changes have not introduced any new issues vs baseline.");
|
|
1561
|
+
parts.push("If gates are still failing, it is due to pre-existing issues (listed below).");
|
|
1562
|
+
}
|
|
1563
|
+
if (!comparison.passed && totalNew === 0) {
|
|
1564
|
+
const gateBlock = (label, g) => [
|
|
1565
|
+
`
|
|
1566
|
+
${label}:`,
|
|
1567
|
+
`Status: ${g.status}`,
|
|
1568
|
+
`Command: ${g.command}`,
|
|
1569
|
+
`Exit Code: ${g.exitCode}`,
|
|
1570
|
+
`Output (tail):
|
|
1571
|
+
${renderUntrustedCommandOutput(g.outputTail)}`
|
|
1572
|
+
].join("\n");
|
|
1573
|
+
parts.push("\n=== UNPARSED GATE REGRESSION OUTPUT ===");
|
|
1574
|
+
parts.push(
|
|
1575
|
+
"A clean baseline gate now exits non-zero, but no stable issue identity could be extracted. Fix the gate failure shown below."
|
|
1576
|
+
);
|
|
1577
|
+
if (gates.build.status !== "passed") {
|
|
1578
|
+
parts.push(gateBlock("BUILD/TYPECHECK", gates.build));
|
|
1579
|
+
}
|
|
1580
|
+
if (gates.lint && gates.lint.status !== "passed") {
|
|
1581
|
+
parts.push(gateBlock("LINT", gates.lint));
|
|
1582
|
+
}
|
|
1583
|
+
if (gates.tests && gates.tests.status !== "passed" && gates.tests.status !== "skipped" && gates.tests.status !== "unavailable") {
|
|
1584
|
+
parts.push(gateBlock("TESTS", gates.tests));
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
const persistBuild = comparison.delta.build.persistingIssues.size;
|
|
1588
|
+
const persistLint = comparison.delta.lint.persistingIssues.size;
|
|
1589
|
+
const persistTest = comparison.delta.test.persistingIssues.size;
|
|
1590
|
+
const totalPersist = persistBuild + persistLint + persistTest;
|
|
1591
|
+
if (totalPersist > 0) {
|
|
1592
|
+
parts.push(`
|
|
1593
|
+
=== PRE-EXISTING ISSUES (${totalPersist} total - DO NOT fix these) ===`);
|
|
1594
|
+
parts.push("These existed before your changes and are NOT your responsibility:");
|
|
1595
|
+
if (persistBuild > 0) parts.push(`- ${persistBuild} type error(s)`);
|
|
1596
|
+
if (persistLint > 0) parts.push(`- ${persistLint} lint error(s)`);
|
|
1597
|
+
if (persistTest > 0) parts.push(`- ${persistTest} test failure(s)`);
|
|
1598
|
+
}
|
|
1599
|
+
parts.push(
|
|
1600
|
+
`
|
|
1601
|
+
HARD CONSTRAINTS: maxFilesTouched=${constraints.maxFilesTouched}, diffSizeCap=${constraints.diffSizeCap}`
|
|
1602
|
+
);
|
|
1603
|
+
if (constraints.violations.length > 0) {
|
|
1604
|
+
parts.push(`CONSTRAINT VIOLATIONS:
|
|
1605
|
+
- ${constraints.violations.join("\n- ")}`);
|
|
1606
|
+
}
|
|
1607
|
+
parts.push("\nDo not remove public exports unless explicitly allowed.");
|
|
1608
|
+
parts.push(`
|
|
1609
|
+
${renderRepairConstraints()}`);
|
|
1610
|
+
return parts.join("\n");
|
|
1611
|
+
}
|
|
1612
|
+
async function runVerificationDrivenRepairLoop(params) {
|
|
1613
|
+
const {
|
|
1614
|
+
config,
|
|
1615
|
+
worktreePath,
|
|
1616
|
+
baselineRef,
|
|
1617
|
+
originalPrompt,
|
|
1618
|
+
executeRepair,
|
|
1619
|
+
baseline,
|
|
1620
|
+
taskId,
|
|
1621
|
+
variantId
|
|
1622
|
+
} = params;
|
|
1623
|
+
const repairMode = config.repairMode ?? "auto";
|
|
1624
|
+
const scopePolicy = config.scopePolicy ?? "strict";
|
|
1625
|
+
const allowedFiles = config.allowedFiles ?? [];
|
|
1626
|
+
const repairSaveModes = config.repairSaveModes ?? ["patch"];
|
|
1627
|
+
const checkTestGate = config.checkTestGate ?? true;
|
|
1628
|
+
if (!config.enabled || repairMode === "none") {
|
|
1629
|
+
return {
|
|
1630
|
+
verification: { passed: true, passedBaseline: true, findings: [] },
|
|
1631
|
+
verificationLoop: {
|
|
1632
|
+
enabled: false,
|
|
1633
|
+
passed: true,
|
|
1634
|
+
passedBaseline: true,
|
|
1635
|
+
bestIterationIndex: 0,
|
|
1636
|
+
bestIterationPromotable: true,
|
|
1637
|
+
iterations: []
|
|
1638
|
+
}
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
const repairBaseRefResult = await execa2("git", ["rev-parse", "HEAD"], {
|
|
1642
|
+
cwd: worktreePath,
|
|
1643
|
+
stdio: "pipe",
|
|
1644
|
+
reject: false
|
|
1645
|
+
});
|
|
1646
|
+
const repairBaseRef = (repairBaseRefResult.exitCode ?? 0) === 0 ? repairBaseRefResult.stdout.trim() : baselineRef;
|
|
1647
|
+
const iterations = [];
|
|
1648
|
+
const initialCostUsdRaw = Number(params.initialCostUsd ?? 0);
|
|
1649
|
+
const initialCostUsd = Number.isFinite(initialCostUsdRaw) && initialCostUsdRaw > 0 ? initialCostUsdRaw : 0;
|
|
1650
|
+
let cumulativeCostUsd = initialCostUsd;
|
|
1651
|
+
let initialFilesTouched = 0;
|
|
1652
|
+
let initialDiffSize = 0;
|
|
1653
|
+
let stopReason;
|
|
1654
|
+
let bestIterationIndex = 0;
|
|
1655
|
+
const preserved = [];
|
|
1656
|
+
for (let iteration = 1; iteration <= config.maxIterations; iteration++) {
|
|
1657
|
+
const iterStart = Date.now();
|
|
1658
|
+
if (iteration === 1) {
|
|
1659
|
+
const [files, diffSize] = await Promise.all([
|
|
1660
|
+
getChangedFiles(worktreePath, baselineRef),
|
|
1661
|
+
getDiffSize(worktreePath, baselineRef)
|
|
1662
|
+
]);
|
|
1663
|
+
initialFilesTouched = files.length;
|
|
1664
|
+
initialDiffSize = Math.max(1, diffSize);
|
|
1665
|
+
}
|
|
1666
|
+
const maxFilesTouched = initialFilesTouched + 1;
|
|
1667
|
+
const diffSizeCap = initialDiffSize * 2;
|
|
1668
|
+
const constraints = {
|
|
1669
|
+
maxFilesTouched,
|
|
1670
|
+
diffSizeCap,
|
|
1671
|
+
apiBreakAllowed: config.allowApiBreak,
|
|
1672
|
+
violations: []
|
|
1673
|
+
};
|
|
1674
|
+
const [currentFiles, currentDiffSize, removedExports] = await Promise.all([
|
|
1675
|
+
getChangedFiles(worktreePath, baselineRef),
|
|
1676
|
+
getDiffSize(worktreePath, baselineRef),
|
|
1677
|
+
detectRemovedExportsInPublicBarrels(worktreePath, baselineRef)
|
|
1678
|
+
]);
|
|
1679
|
+
if (currentFiles.length > maxFilesTouched) {
|
|
1680
|
+
constraints.violations.push(
|
|
1681
|
+
`max_files_touched exceeded: ${currentFiles.length} > ${maxFilesTouched} (${currentFiles.slice(0, 8).join(", ")}${currentFiles.length > 8 ? "\u2026" : ""})`
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
if (currentDiffSize > diffSizeCap) {
|
|
1685
|
+
constraints.violations.push(`diff_size_cap exceeded: ${currentDiffSize} > ${diffSizeCap}`);
|
|
1686
|
+
}
|
|
1687
|
+
if (!config.allowApiBreak && removedExports.removedExportLines > 0) {
|
|
1688
|
+
constraints.violations.push(
|
|
1689
|
+
`api_break_detection: removed export lines detected (${removedExports.removedExportLines})${removedExports.sample ? `
|
|
1690
|
+
${removedExports.sample}` : ""}`
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
const timeboxMs = config.iterationTimeoutMs;
|
|
1694
|
+
const timeRemainingMs = Math.max(0, timeboxMs - (Date.now() - iterStart));
|
|
1695
|
+
const gateRun = constraints.violations.length ? {
|
|
1696
|
+
passed: false,
|
|
1697
|
+
gates: {
|
|
1698
|
+
build: {
|
|
1699
|
+
status: "failed",
|
|
1700
|
+
exitCode: 1,
|
|
1701
|
+
command: "constraint-check",
|
|
1702
|
+
durationMs: 0,
|
|
1703
|
+
stdoutTail: "",
|
|
1704
|
+
stderrTail: "",
|
|
1705
|
+
outputTail: `Constraint violations:
|
|
1706
|
+
- ${constraints.violations.join("\n- ")}`
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
} : (
|
|
1710
|
+
// Scope the repair-iteration gates to the same changed files used elsewhere
|
|
1711
|
+
// (symmetry with task-scoped baselines). `currentFiles` is the diff vs the
|
|
1712
|
+
// baselineRef computed just above for constraint checks.
|
|
1713
|
+
await runDeterministicGates(worktreePath, baselineRef, timeRemainingMs, currentFiles)
|
|
1714
|
+
);
|
|
1715
|
+
const passed = gateRun.passed;
|
|
1716
|
+
const durationMs = Date.now() - iterStart;
|
|
1717
|
+
let baselineComparison;
|
|
1718
|
+
let passedBaseline = gatesHaveNoHardFailures(gateRun.gates, checkTestGate);
|
|
1719
|
+
let newIssueCount = 0;
|
|
1720
|
+
let fixedIssueCount = 0;
|
|
1721
|
+
if (baseline) {
|
|
1722
|
+
baselineComparison = compareVerificationOutcomeToBaseline(
|
|
1723
|
+
baseline,
|
|
1724
|
+
verificationOutcomesFromGateArtifacts(gateRun.gates, {
|
|
1725
|
+
source: "verification-loop",
|
|
1726
|
+
cwd: worktreePath
|
|
1727
|
+
})
|
|
1728
|
+
);
|
|
1729
|
+
const gateExitRegressions = cleanBaselineGateExitRegressions(
|
|
1730
|
+
baseline,
|
|
1731
|
+
gateRun.gates,
|
|
1732
|
+
checkTestGate
|
|
1733
|
+
);
|
|
1734
|
+
const hardStructuredNewIssueCount = baselineComparison.delta.build.newIssues.size + (checkTestGate ? baselineComparison.delta.test.newIssues.size : 0);
|
|
1735
|
+
passedBaseline = hardStructuredNewIssueCount === 0 && gateExitRegressions.length === 0;
|
|
1736
|
+
const structuredNewIssueCount = checkTestGate ? hardStructuredNewIssueCount : baselineComparison.delta.build.newIssues.size;
|
|
1737
|
+
const comparisonForCounts = baselineComparison;
|
|
1738
|
+
const unparsedGateRegressionCount = gateExitRegressions.filter((kind) => {
|
|
1739
|
+
if (kind === "build") return comparisonForCounts.delta.build.newIssues.size === 0;
|
|
1740
|
+
return comparisonForCounts.delta.test.newIssues.size === 0;
|
|
1741
|
+
}).length;
|
|
1742
|
+
newIssueCount = structuredNewIssueCount + unparsedGateRegressionCount;
|
|
1743
|
+
fixedIssueCount = baselineComparison.delta.totalFixedIssues;
|
|
1744
|
+
}
|
|
1745
|
+
const record = {
|
|
1746
|
+
iteration,
|
|
1747
|
+
passed,
|
|
1748
|
+
passedBaseline,
|
|
1749
|
+
newIssueCount,
|
|
1750
|
+
fixedIssueCount,
|
|
1751
|
+
durationMs,
|
|
1752
|
+
costUsd: 0,
|
|
1753
|
+
cumulativeCostUsd,
|
|
1754
|
+
constraints,
|
|
1755
|
+
gates: gateRun.gates,
|
|
1756
|
+
baselineComparison
|
|
1757
|
+
};
|
|
1758
|
+
iterations.push(record);
|
|
1759
|
+
if (taskId && variantId && repairSaveModes.length > 0) {
|
|
1760
|
+
try {
|
|
1761
|
+
const info = {
|
|
1762
|
+
iteration,
|
|
1763
|
+
newIssueCount,
|
|
1764
|
+
fixedIssueCount,
|
|
1765
|
+
passed: passedBaseline,
|
|
1766
|
+
durationMs,
|
|
1767
|
+
costUsd: 0
|
|
1768
|
+
};
|
|
1769
|
+
const artifacts = await preserveRepairIteration(
|
|
1770
|
+
worktreePath,
|
|
1771
|
+
repairBaseRef,
|
|
1772
|
+
taskId,
|
|
1773
|
+
variantId,
|
|
1774
|
+
info,
|
|
1775
|
+
{ modes: repairSaveModes }
|
|
1776
|
+
);
|
|
1777
|
+
preserved.push({ info, artifacts });
|
|
1778
|
+
} catch {
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
if (config.abortIfRegression && iterations.length >= 2) {
|
|
1782
|
+
const prev = iterations[iterations.length - 2];
|
|
1783
|
+
const next = iterations[iterations.length - 1];
|
|
1784
|
+
const hasBaselineRegression = baseline && next.newIssueCount > prev.newIssueCount;
|
|
1785
|
+
const hasGateRegression = isRegression(prev.gates, next.gates);
|
|
1786
|
+
const hasTestRegression = checkTestGate && baseline && next.baselineComparison && prev.baselineComparison && next.baselineComparison.delta.test.newIssues.size > prev.baselineComparison.delta.test.newIssues.size;
|
|
1787
|
+
if (hasBaselineRegression || hasGateRegression || hasTestRegression) {
|
|
1788
|
+
stopReason = "abort_if_regression";
|
|
1789
|
+
break;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
const effectivePassed = repairMode === "strict" ? passed : passedBaseline;
|
|
1793
|
+
if (effectivePassed) {
|
|
1794
|
+
stopReason = passed ? "gates_passed" : "no_new_regressions";
|
|
1795
|
+
break;
|
|
1796
|
+
}
|
|
1797
|
+
if (iteration >= config.maxIterations) {
|
|
1798
|
+
stopReason = "max_iterations_reached";
|
|
1799
|
+
break;
|
|
1800
|
+
}
|
|
1801
|
+
if (durationMs > config.iterationTimeoutMs) {
|
|
1802
|
+
stopReason = "iteration_timebox_exceeded";
|
|
1803
|
+
break;
|
|
1804
|
+
}
|
|
1805
|
+
if (baseline && iterations.length >= 2) {
|
|
1806
|
+
const prevRecord = iterations[iterations.length - 2];
|
|
1807
|
+
if (prevRecord.newIssueCount === newIssueCount && newIssueCount > 0) {
|
|
1808
|
+
stopReason = "no_progress";
|
|
1809
|
+
break;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
let prompt;
|
|
1813
|
+
const repairAllowedFiles = mergeRepairAllowedFiles(
|
|
1814
|
+
allowedFiles,
|
|
1815
|
+
deriveAllowedFilesFromGateEvidence(gateRun.gates, baselineComparison)
|
|
1816
|
+
);
|
|
1817
|
+
if (baseline && baselineComparison) {
|
|
1818
|
+
prompt = buildScopedRepairPrompt({
|
|
1819
|
+
originalPrompt,
|
|
1820
|
+
iteration,
|
|
1821
|
+
gates: gateRun.gates,
|
|
1822
|
+
constraints,
|
|
1823
|
+
comparison: baselineComparison,
|
|
1824
|
+
allowedFiles: repairAllowedFiles,
|
|
1825
|
+
scopePolicy
|
|
1826
|
+
});
|
|
1827
|
+
} else {
|
|
1828
|
+
prompt = buildRepairPrompt({
|
|
1829
|
+
originalPrompt,
|
|
1830
|
+
iteration,
|
|
1831
|
+
gates: gateRun.gates,
|
|
1832
|
+
constraints,
|
|
1833
|
+
allowedFiles: repairAllowedFiles,
|
|
1834
|
+
scopePolicy
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
const repairStart = Date.now();
|
|
1838
|
+
const repairTimeoutMs = Math.max(1, config.iterationTimeoutMs);
|
|
1839
|
+
const repairAbort = new AbortController();
|
|
1840
|
+
let timeoutId;
|
|
1841
|
+
let repairTimedOut = false;
|
|
1842
|
+
const timeoutRepair = new Promise((_, reject) => {
|
|
1843
|
+
timeoutId = setTimeout(() => {
|
|
1844
|
+
repairTimedOut = true;
|
|
1845
|
+
repairAbort.abort();
|
|
1846
|
+
reject(
|
|
1847
|
+
new Error(
|
|
1848
|
+
`executeRepair exceeded iteration timeout (${repairTimeoutMs}ms) at iteration ${iteration}`
|
|
1849
|
+
)
|
|
1850
|
+
);
|
|
1851
|
+
}, repairTimeoutMs);
|
|
1852
|
+
timeoutId.unref?.();
|
|
1853
|
+
});
|
|
1854
|
+
const repairPromise = executeRepair(prompt, iteration - 1, repairAbort.signal);
|
|
1855
|
+
let repair;
|
|
1856
|
+
try {
|
|
1857
|
+
repair = await Promise.race([repairPromise, timeoutRepair]);
|
|
1858
|
+
} catch (err) {
|
|
1859
|
+
repairAbort.abort();
|
|
1860
|
+
repairPromise.catch((repairErr) => {
|
|
1861
|
+
if (repairTimedOut && process.env.POETIC_DEBUG_REPAIR_ABORT === "1") {
|
|
1862
|
+
console.warn(
|
|
1863
|
+
`[verification-loop] repair subprocess exited after abort: ${repairErr instanceof Error ? repairErr.message : String(repairErr)}`
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1866
|
+
});
|
|
1867
|
+
stopReason = repairTimedOut ? "iteration_timebox_exceeded" : "repair_execution_failed";
|
|
1868
|
+
record.durationMs = Math.max(record.durationMs, Date.now() - iterStart);
|
|
1869
|
+
if (err instanceof Error) {
|
|
1870
|
+
console.warn(`[verification-loop] ${err.message}`);
|
|
1871
|
+
}
|
|
1872
|
+
break;
|
|
1873
|
+
} finally {
|
|
1874
|
+
if (timeoutId) {
|
|
1875
|
+
clearTimeout(timeoutId);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
const repairDuration = Date.now() - repairStart;
|
|
1879
|
+
cumulativeCostUsd += Math.max(0, repair.costUsd);
|
|
1880
|
+
record.costUsd = Math.max(0, repair.costUsd);
|
|
1881
|
+
record.cumulativeCostUsd = cumulativeCostUsd;
|
|
1882
|
+
record.durationMs = Math.max(record.durationMs, Date.now() - iterStart);
|
|
1883
|
+
if (preserved.length > 0) {
|
|
1884
|
+
const lastPreserved = preserved[preserved.length - 1];
|
|
1885
|
+
if (lastPreserved?.info.iteration === iteration) {
|
|
1886
|
+
lastPreserved.info.costUsd = record.costUsd;
|
|
1887
|
+
lastPreserved.info.durationMs = record.durationMs;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
if (Date.now() - iterStart > config.iterationTimeoutMs || repairDuration > config.iterationTimeoutMs) {
|
|
1891
|
+
stopReason = "iteration_timebox_exceeded";
|
|
1892
|
+
break;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
const lastIteration = iterations.length > 0 ? iterations[iterations.length - 1] : void 0;
|
|
1896
|
+
let finalPassed = lastIteration?.passed ?? false;
|
|
1897
|
+
let finalPassedBaseline = lastIteration?.passedBaseline ?? false;
|
|
1898
|
+
const bestChoice = findBestIteration(
|
|
1899
|
+
iterations.map((it) => ({
|
|
1900
|
+
iteration: it.iteration,
|
|
1901
|
+
newIssueCount: it.newIssueCount,
|
|
1902
|
+
fixedIssueCount: it.fixedIssueCount,
|
|
1903
|
+
passed: it.passedBaseline,
|
|
1904
|
+
durationMs: it.durationMs,
|
|
1905
|
+
costUsd: it.costUsd
|
|
1906
|
+
}))
|
|
1907
|
+
);
|
|
1908
|
+
bestIterationIndex = bestChoice?.index ?? 0;
|
|
1909
|
+
let bestIteration = iterations[bestIterationIndex];
|
|
1910
|
+
let bestIterationPromotable = bestIteration !== void 0 && (bestChoice?.iteration.passed ?? false) ? gatesHavePromotableCoverage(bestIteration.gates, checkTestGate) : false;
|
|
1911
|
+
let artifactSelectionFinding;
|
|
1912
|
+
let restoreFinding;
|
|
1913
|
+
let repairArtifacts;
|
|
1914
|
+
if (taskId && variantId && preserved.length > 0) {
|
|
1915
|
+
let bestIndex = bestIteration ? preserved.findIndex((p) => p.info.iteration === bestIteration?.iteration) : -1;
|
|
1916
|
+
if (bestIndex < 0) {
|
|
1917
|
+
const bestPreserved = findBestIteration(preserved.map((p) => p.info));
|
|
1918
|
+
bestIndex = bestPreserved?.index ?? 0;
|
|
1919
|
+
const fallbackIteration = preserved[bestIndex]?.info;
|
|
1920
|
+
if (fallbackIteration) {
|
|
1921
|
+
const fallbackIndex = iterations.findIndex(
|
|
1922
|
+
(it) => it.iteration === fallbackIteration.iteration
|
|
1923
|
+
);
|
|
1924
|
+
if (fallbackIndex >= 0) {
|
|
1925
|
+
bestIterationIndex = fallbackIndex;
|
|
1926
|
+
const selectedIteration = iterations[fallbackIndex];
|
|
1927
|
+
bestIteration = selectedIteration;
|
|
1928
|
+
bestIterationPromotable = fallbackIteration.passed && selectedIteration !== void 0 && gatesHavePromotableCoverage(selectedIteration.gates, checkTestGate);
|
|
1929
|
+
}
|
|
1930
|
+
artifactSelectionFinding = {
|
|
1931
|
+
kind: "other",
|
|
1932
|
+
command: "select-best-repair-artifact",
|
|
1933
|
+
exitCode: 0,
|
|
1934
|
+
severity: "warning",
|
|
1935
|
+
outputTail: "Best verification iteration did not have preserved repair artifacts; selected the best preserved iteration instead."
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
const bestIterationInfo = preserved[bestIndex]?.info ?? preserved[0]?.info;
|
|
1940
|
+
const bestArtifacts = preserved[bestIndex]?.artifacts ?? [];
|
|
1941
|
+
const bestArtifactPath = bestArtifacts.find((a) => a.success)?.artifactPath;
|
|
1942
|
+
repairArtifacts = {
|
|
1943
|
+
taskId,
|
|
1944
|
+
variantId,
|
|
1945
|
+
iterations: preserved,
|
|
1946
|
+
bestIterationIndex: bestIndex,
|
|
1947
|
+
bestIteration: bestIterationInfo,
|
|
1948
|
+
bestArtifactPath
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
const iterForFindings = bestIteration ?? lastIteration;
|
|
1952
|
+
const findings = iterForFindings ? buildVerificationFindings(iterForFindings.gates, iterForFindings.passedBaseline) : [];
|
|
1953
|
+
if (repairArtifacts && repairArtifacts.iterations.length > 0 && repairArtifacts.bestIteration.passed && repairArtifacts.bestIterationIndex !== repairArtifacts.iterations.length - 1) {
|
|
1954
|
+
const bestArtifacts = repairArtifacts.iterations[repairArtifacts.bestIterationIndex]?.artifacts ?? [];
|
|
1955
|
+
const lastArtifacts = repairArtifacts.iterations[repairArtifacts.iterations.length - 1]?.artifacts ?? [];
|
|
1956
|
+
const bestPatch = bestArtifacts.find((a) => a.success && a.mode === "patch")?.artifactPath;
|
|
1957
|
+
const bestRef = bestArtifacts.find(
|
|
1958
|
+
(a) => a.success && (a.mode === "ref" || a.mode === "branch")
|
|
1959
|
+
)?.artifactPath;
|
|
1960
|
+
const lastPatch = lastArtifacts.find((a) => a.success && a.mode === "patch")?.artifactPath;
|
|
1961
|
+
const lastRef = lastArtifacts.find(
|
|
1962
|
+
(a) => a.success && (a.mode === "ref" || a.mode === "branch")
|
|
1963
|
+
)?.artifactPath;
|
|
1964
|
+
const resetToBase = async () => {
|
|
1965
|
+
await execa2("git", ["reset", "--hard", repairBaseRef], {
|
|
1966
|
+
cwd: worktreePath,
|
|
1967
|
+
stdio: "pipe",
|
|
1968
|
+
reject: false
|
|
1969
|
+
});
|
|
1970
|
+
await execa2("git", ["clean", "-fd"], {
|
|
1971
|
+
cwd: worktreePath,
|
|
1972
|
+
stdio: "pipe",
|
|
1973
|
+
reject: false
|
|
1974
|
+
});
|
|
1975
|
+
};
|
|
1976
|
+
const applyArtifact = async (patchPath, refName) => {
|
|
1977
|
+
if (patchPath) {
|
|
1978
|
+
const applied = await applyPatchFile(worktreePath, patchPath);
|
|
1979
|
+
return applied.success;
|
|
1980
|
+
}
|
|
1981
|
+
if (refName) {
|
|
1982
|
+
const applied = await cherryPickFromRef(worktreePath, refName);
|
|
1983
|
+
return applied.success;
|
|
1984
|
+
}
|
|
1985
|
+
return false;
|
|
1986
|
+
};
|
|
1987
|
+
try {
|
|
1988
|
+
await resetToBase();
|
|
1989
|
+
const ok = await applyArtifact(bestPatch, bestRef);
|
|
1990
|
+
if (!ok) {
|
|
1991
|
+
await resetToBase();
|
|
1992
|
+
const restoredLast = await applyArtifact(lastPatch, lastRef);
|
|
1993
|
+
if (!restoredLast) {
|
|
1994
|
+
throw new Error("failed to restore best or last repair artifact");
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
} catch (restoreError) {
|
|
1998
|
+
finalPassed = false;
|
|
1999
|
+
finalPassedBaseline = false;
|
|
2000
|
+
bestIterationPromotable = false;
|
|
2001
|
+
stopReason = "artifact_restore_failed";
|
|
2002
|
+
restoreFinding = {
|
|
2003
|
+
kind: "other",
|
|
2004
|
+
command: "restore-best-repair-artifact",
|
|
2005
|
+
exitCode: 1,
|
|
2006
|
+
severity: "error",
|
|
2007
|
+
outputTail: restoreError instanceof Error ? restoreError.message : String(restoreError)
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
return {
|
|
2012
|
+
verification: {
|
|
2013
|
+
passed: finalPassed,
|
|
2014
|
+
passedBaseline: finalPassedBaseline,
|
|
2015
|
+
findings: [
|
|
2016
|
+
...findings,
|
|
2017
|
+
...artifactSelectionFinding ? [artifactSelectionFinding] : [],
|
|
2018
|
+
...restoreFinding ? [restoreFinding] : []
|
|
2019
|
+
]
|
|
2020
|
+
},
|
|
2021
|
+
verificationLoop: {
|
|
2022
|
+
enabled: true,
|
|
2023
|
+
passed: finalPassed,
|
|
2024
|
+
passedBaseline: finalPassedBaseline,
|
|
2025
|
+
stopReason: stopReason ?? (finalPassedBaseline ? "no_new_regressions" : "gates_failed"),
|
|
2026
|
+
bestIterationIndex,
|
|
2027
|
+
bestIterationPromotable,
|
|
2028
|
+
iterations: iterations.map((it) => ({
|
|
2029
|
+
iteration: it.iteration,
|
|
2030
|
+
passed: it.passed,
|
|
2031
|
+
passedBaseline: it.passedBaseline,
|
|
2032
|
+
newIssueCount: it.newIssueCount,
|
|
2033
|
+
fixedIssueCount: it.fixedIssueCount,
|
|
2034
|
+
durationMs: it.durationMs,
|
|
2035
|
+
costUsd: it.costUsd,
|
|
2036
|
+
cumulativeCostUsd: it.cumulativeCostUsd,
|
|
2037
|
+
constraints: it.constraints,
|
|
2038
|
+
build: it.gates.build,
|
|
2039
|
+
lint: it.gates.lint,
|
|
2040
|
+
tests: it.gates.tests
|
|
2041
|
+
}))
|
|
2042
|
+
},
|
|
2043
|
+
repairArtifacts
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
// src/cli/variant-worker.ts
|
|
2048
|
+
var MIN_ANALYSIS_COMPLETENESS = 60;
|
|
2049
|
+
var PARTIAL_GIT_TIMEOUT_MS = 3e3;
|
|
2050
|
+
var SIGNAL_CLEANUP_TIMEOUT_MS = WORKER_CLEANUP_BUDGET_MS;
|
|
2051
|
+
var SIGNAL_CLEANUP_WRAPPER_TIMEOUT_MS = SIGNAL_CLEANUP_TIMEOUT_MS + 200;
|
|
2052
|
+
var POST_PROVIDER_PHASE_WARN_MS = 3e4;
|
|
2053
|
+
function elapsedMsSince(startTime) {
|
|
2054
|
+
return Number((process.hrtime.bigint() - startTime) / 1000000n);
|
|
2055
|
+
}
|
|
2056
|
+
function addPostProviderPhaseDuration(durations, phase, durationMs) {
|
|
2057
|
+
if (!Number.isFinite(durationMs) || durationMs < 0) return;
|
|
2058
|
+
durations[phase] = (durations[phase] ?? 0) + Math.round(durationMs);
|
|
2059
|
+
}
|
|
2060
|
+
async function measurePostProviderPhase(durations, phase, verbose, run) {
|
|
2061
|
+
const started = process.hrtime.bigint();
|
|
2062
|
+
try {
|
|
2063
|
+
return await run();
|
|
2064
|
+
} finally {
|
|
2065
|
+
const durationMs = Number((process.hrtime.bigint() - started) / 1000000n);
|
|
2066
|
+
addPostProviderPhaseDuration(durations, phase, durationMs);
|
|
2067
|
+
if (verbose && durationMs >= POST_PROVIDER_PHASE_WARN_MS) {
|
|
2068
|
+
console.log(chalk.gray(` post-provider phase ${phase} took ${durationMs}ms`));
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
function measurePostProviderPhaseSync(durations, phase, verbose, run) {
|
|
2073
|
+
const started = process.hrtime.bigint();
|
|
2074
|
+
try {
|
|
2075
|
+
return run();
|
|
2076
|
+
} finally {
|
|
2077
|
+
const durationMs = Number((process.hrtime.bigint() - started) / 1000000n);
|
|
2078
|
+
addPostProviderPhaseDuration(durations, phase, durationMs);
|
|
2079
|
+
if (verbose && durationMs >= POST_PROVIDER_PHASE_WARN_MS) {
|
|
2080
|
+
console.log(chalk.gray(` post-provider phase ${phase} took ${durationMs}ms`));
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
function shouldDowngradeSuccessfulResultForShutdown(input) {
|
|
2085
|
+
if (!input.shutdownRequested) return false;
|
|
2086
|
+
if (input.shutdownRequestedAtMs === void 0) return true;
|
|
2087
|
+
if (input.executionResultSettledAtMs === void 0) return true;
|
|
2088
|
+
return input.shutdownRequestedAtMs <= input.executionResultSettledAtMs;
|
|
2089
|
+
}
|
|
2090
|
+
function activeHandleSummary() {
|
|
2091
|
+
const getActiveHandles = process._getActiveHandles;
|
|
2092
|
+
if (typeof getActiveHandles !== "function") return [];
|
|
2093
|
+
return getActiveHandles().map((handle) => {
|
|
2094
|
+
const record = handle;
|
|
2095
|
+
const type = record.constructor?.name ?? typeof handle;
|
|
2096
|
+
let detail;
|
|
2097
|
+
if (typeof record.fd === "number") {
|
|
2098
|
+
detail = `fd=${record.fd}`;
|
|
2099
|
+
} else if (typeof record.path === "string") {
|
|
2100
|
+
detail = record.path;
|
|
2101
|
+
} else if (typeof record.address === "function") {
|
|
2102
|
+
try {
|
|
2103
|
+
const address = record.address();
|
|
2104
|
+
detail = typeof address === "string" ? address : JSON.stringify(address);
|
|
2105
|
+
} catch {
|
|
2106
|
+
detail = void 0;
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
return detail ? { type, detail } : { type };
|
|
2110
|
+
});
|
|
2111
|
+
}
|
|
2112
|
+
function emitWorkerLifecycleMarker(prefix, value) {
|
|
2113
|
+
try {
|
|
2114
|
+
const payload = typeof value === "number" ? String(value) : JSON.stringify(value);
|
|
2115
|
+
process.stdout.write(`${prefix}${payload}
|
|
2116
|
+
`);
|
|
2117
|
+
} catch {
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
function createProviderOutputForwarder(enabled) {
|
|
2121
|
+
if (!enabled) {
|
|
2122
|
+
return { close: () => {
|
|
2123
|
+
} };
|
|
2124
|
+
}
|
|
2125
|
+
const resultStreamer = new ResultStreamer();
|
|
2126
|
+
const listener = (event) => {
|
|
2127
|
+
try {
|
|
2128
|
+
process.stdout.write(`${PROVIDER_OUTPUT_WORKER_EVENT_PREFIX}${JSON.stringify(event)}
|
|
2129
|
+
`);
|
|
2130
|
+
} catch {
|
|
2131
|
+
}
|
|
2132
|
+
};
|
|
2133
|
+
const activityListener = (event) => {
|
|
2134
|
+
try {
|
|
2135
|
+
process.stdout.write(`${PROVIDER_ACTIVITY_WORKER_EVENT_PREFIX}${JSON.stringify(event)}
|
|
2136
|
+
`);
|
|
2137
|
+
} catch {
|
|
2138
|
+
}
|
|
2139
|
+
};
|
|
2140
|
+
resultStreamer.on("providerOutput", listener);
|
|
2141
|
+
resultStreamer.on("providerActivity", activityListener);
|
|
2142
|
+
return {
|
|
2143
|
+
resultStreamer,
|
|
2144
|
+
close: () => {
|
|
2145
|
+
resultStreamer.off("providerOutput", listener);
|
|
2146
|
+
resultStreamer.off("providerActivity", activityListener);
|
|
2147
|
+
resultStreamer.close();
|
|
2148
|
+
}
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
async function tryLoadPackageJsonScripts(repoRoot) {
|
|
2152
|
+
try {
|
|
2153
|
+
const raw = await fs.readFile(path.join(repoRoot, "package.json"), "utf-8");
|
|
2154
|
+
const parsed = JSON.parse(raw);
|
|
2155
|
+
const scripts = parsed.scripts && typeof parsed.scripts === "object" ? parsed.scripts : {};
|
|
2156
|
+
return new Set(Object.keys(scripts));
|
|
2157
|
+
} catch {
|
|
2158
|
+
return void 0;
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
function extractOriginalPrompt(fullPrompt) {
|
|
2162
|
+
const parts = fullPrompt.split(/\n\n---\n\n/);
|
|
2163
|
+
const first = parts[0];
|
|
2164
|
+
if (first?.trimStart().startsWith("[SPEC:") && parts.length > 1) {
|
|
2165
|
+
return parts.slice(1).join("\n\n---\n\n").trim();
|
|
2166
|
+
}
|
|
2167
|
+
return (first ?? fullPrompt).trim();
|
|
2168
|
+
}
|
|
2169
|
+
function toBaselineComparisonResult(comparison) {
|
|
2170
|
+
return {
|
|
2171
|
+
verdict: comparison.verdict,
|
|
2172
|
+
summary: comparison.summary,
|
|
2173
|
+
baselineRef: comparison.baselineRef,
|
|
2174
|
+
tests: {
|
|
2175
|
+
newFailures: comparison.tests.newFailures,
|
|
2176
|
+
fixedIssues: comparison.tests.fixedIssues,
|
|
2177
|
+
persistingIssues: comparison.tests.persistingIssues
|
|
2178
|
+
},
|
|
2179
|
+
types: {
|
|
2180
|
+
newFailures: comparison.types.newFailures,
|
|
2181
|
+
fixedIssues: comparison.types.fixedIssues,
|
|
2182
|
+
persistingIssues: comparison.types.persistingIssues
|
|
2183
|
+
},
|
|
2184
|
+
lint: {
|
|
2185
|
+
newFailures: comparison.lint.newFailures,
|
|
2186
|
+
fixedIssues: comparison.lint.fixedIssues,
|
|
2187
|
+
persistingIssues: comparison.lint.persistingIssues
|
|
2188
|
+
}
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2191
|
+
function latestVerificationIteration(loop) {
|
|
2192
|
+
if (!loop?.enabled || !Array.isArray(loop.iterations) || loop.iterations.length === 0) {
|
|
2193
|
+
return null;
|
|
2194
|
+
}
|
|
2195
|
+
return loop.iterations.reduce(
|
|
2196
|
+
(best, current) => (current.iteration ?? 0) > (best.iteration ?? 0) ? current : best
|
|
2197
|
+
);
|
|
2198
|
+
}
|
|
2199
|
+
function shouldTrustVerificationLoopBaseline(result) {
|
|
2200
|
+
const loop = result.verificationLoop;
|
|
2201
|
+
const last = latestVerificationIteration(loop);
|
|
2202
|
+
if (hasVerifiedTouchedTestFailure(result) && !hasTrustworthyRepairOutcome(result)) {
|
|
2203
|
+
return false;
|
|
2204
|
+
}
|
|
2205
|
+
return loop?.enabled === true && loop.passed === true && loop.passedBaseline === true && (last?.newIssueCount ?? 0) === 0;
|
|
2206
|
+
}
|
|
2207
|
+
function hasVerifiedTouchedTestFailure2(result) {
|
|
2208
|
+
return hasVerifiedTouchedTestFailure(result);
|
|
2209
|
+
}
|
|
2210
|
+
function hasTrustworthyRepairOutcome(result) {
|
|
2211
|
+
if (result.repairOutcome?.attempted !== true || result.repairOutcome.passed !== true) {
|
|
2212
|
+
return false;
|
|
2213
|
+
}
|
|
2214
|
+
return result.repairOutcome.classification === "mechanical" || result.repairOutcome.classification === "substantive_in_scope" || result.repairOutcome.classification === "scope_expanded";
|
|
2215
|
+
}
|
|
2216
|
+
function shouldSkipRepairLoopForPassingGates(result) {
|
|
2217
|
+
return (result.verificationDisposition ?? getVerificationResult(result)).outcome === "passed";
|
|
2218
|
+
}
|
|
2219
|
+
function isValidationArtifactOnlyPrompt(prompt) {
|
|
2220
|
+
const hasValidationOnlyDirective = /\bvalidation(?:\s+run)?\s+only\b/i.test(prompt);
|
|
2221
|
+
const hasValidationArtifactTarget = /\bvalidation\s+artifact\b/i.test(prompt) || /(?:^|[\s`"'])(?:\.poetic\/validation\/|dogfood-validation-artifacts\/)/i.test(prompt);
|
|
2222
|
+
const forbidsOtherFileChanges = /\b(?:do\s+not|don't)\s+modify\s+(?:any\s+)?other\s+files\b/i.test(prompt) || /\bmodify\s+no\s+other\s+files\b/i.test(prompt);
|
|
2223
|
+
return hasValidationOnlyDirective && hasValidationArtifactTarget && forbidsOtherFileChanges;
|
|
2224
|
+
}
|
|
2225
|
+
async function isValidationArtifactOnlyDiffSatisfied(input) {
|
|
2226
|
+
if (!input.worktreePath || !isValidationArtifactOnlyPrompt(input.prompt)) return false;
|
|
2227
|
+
try {
|
|
2228
|
+
const summary = await getGitChangeSummary(input.worktreePath, input.baselineRef, true);
|
|
2229
|
+
return summary.changedPaths.length > 0 && summary.changedPaths.every((changedPath) => isValidationArtifactEntry(changedPath));
|
|
2230
|
+
} catch {
|
|
2231
|
+
return false;
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
function decideRepairAdmission(input) {
|
|
2235
|
+
const baseEvidence = {
|
|
2236
|
+
...input.initialVerificationOutcome ? { initialVerificationOutcome: input.initialVerificationOutcome } : {},
|
|
2237
|
+
hasMissingFileTargets: input.hasMissingFileTargets,
|
|
2238
|
+
isClarifyingNoOp: input.isClarifyingNoOp,
|
|
2239
|
+
providerExecutionSucceeded: input.providerExecutionSucceeded,
|
|
2240
|
+
hasMaterializedDeliverable: input.hasMaterializedDeliverable,
|
|
2241
|
+
validationArtifactSatisfied: input.validationArtifactSatisfied,
|
|
2242
|
+
filesChanged: input.filesChanged
|
|
2243
|
+
};
|
|
2244
|
+
if (!input.repairLoopEnabled) {
|
|
2245
|
+
return {
|
|
2246
|
+
action: "skip",
|
|
2247
|
+
reason: "skip_disabled",
|
|
2248
|
+
summary: "Repair loop disabled",
|
|
2249
|
+
...baseEvidence
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
if (input.isAnalysisIntent) {
|
|
2253
|
+
return {
|
|
2254
|
+
action: "skip",
|
|
2255
|
+
reason: "skip_analysis_intent",
|
|
2256
|
+
summary: "Skipped repair loop for analysis intent",
|
|
2257
|
+
...baseEvidence
|
|
2258
|
+
};
|
|
2259
|
+
}
|
|
2260
|
+
if (!input.hasRetainedWorktree || !input.hasBaselineRef) {
|
|
2261
|
+
return {
|
|
2262
|
+
action: "skip",
|
|
2263
|
+
reason: "skip_missing_worktree",
|
|
2264
|
+
summary: "Skipped repair loop because retained worktree or baseline ref is unavailable",
|
|
2265
|
+
...baseEvidence
|
|
2266
|
+
};
|
|
2267
|
+
}
|
|
2268
|
+
if (!input.providerExecutionSucceeded) {
|
|
2269
|
+
return {
|
|
2270
|
+
action: "skip",
|
|
2271
|
+
reason: "skip_provider_failed",
|
|
2272
|
+
summary: "Skipped repair loop because provider execution failed",
|
|
2273
|
+
...baseEvidence
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
if (!input.hasMaterializedDeliverable) {
|
|
2277
|
+
return {
|
|
2278
|
+
action: "skip",
|
|
2279
|
+
reason: "skip_no_deliverable",
|
|
2280
|
+
summary: "Skipped repair loop because provider produced no materialized deliverable",
|
|
2281
|
+
...baseEvidence
|
|
2282
|
+
};
|
|
2283
|
+
}
|
|
2284
|
+
if (input.validationArtifactSatisfied === true) {
|
|
2285
|
+
return {
|
|
2286
|
+
action: "skip",
|
|
2287
|
+
reason: "skip_validation_artifact_satisfied",
|
|
2288
|
+
summary: "Skipped repair loop because validation artifact target is already satisfied",
|
|
2289
|
+
...baseEvidence
|
|
2290
|
+
};
|
|
2291
|
+
}
|
|
2292
|
+
if (input.providerGatesAlreadyPassed) {
|
|
2293
|
+
return {
|
|
2294
|
+
action: "skip",
|
|
2295
|
+
reason: "skip_passing_gates",
|
|
2296
|
+
summary: "Skipped repair loop: provider verification already passed",
|
|
2297
|
+
...baseEvidence
|
|
2298
|
+
};
|
|
2299
|
+
}
|
|
2300
|
+
if (input.hasMissingFileTargets && input.isClarifyingNoOp) {
|
|
2301
|
+
return {
|
|
2302
|
+
action: "skip",
|
|
2303
|
+
reason: "skip_clarifying_noop",
|
|
2304
|
+
summary: "Skipped repair loop: missing FILE targets + clarifying no-op response",
|
|
2305
|
+
...baseEvidence
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
return {
|
|
2309
|
+
action: "run",
|
|
2310
|
+
reason: "run_gate_failure",
|
|
2311
|
+
summary: "Repair loop admitted for deterministic gate failure",
|
|
2312
|
+
...baseEvidence
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
function repairPolicyCompletionMode() {
|
|
2316
|
+
const raw = readEnv(POETIC_COMPLETION_MODE).value;
|
|
2317
|
+
return raw === "branch" || raw === "pr" || raw === "isolated" ? raw : "isolated";
|
|
2318
|
+
}
|
|
2319
|
+
function isOffScopeRepairOverrideEnabled() {
|
|
2320
|
+
return readEnv(POETIC_ALLOW_OFF_SCOPE_REPAIR).value === true;
|
|
2321
|
+
}
|
|
2322
|
+
function resolveRepairPromotionPolicy(input) {
|
|
2323
|
+
const outsideFiles = input.repairDelta?.filesOutsidePreRepairDiff ?? [];
|
|
2324
|
+
const mechanicalFiles = new Set(input.repairDelta?.mechanicalAllowlistMatches ?? []);
|
|
2325
|
+
const strictScopeExpansion = input.scopePolicy === "strict" && outsideFiles.some((file) => !mechanicalFiles.has(file));
|
|
2326
|
+
const classificationForPolicy = strictScopeExpansion && input.classification !== "off_scope" ? "off_scope" : input.classification;
|
|
2327
|
+
const policy = resolveRepairArtifactPolicy({
|
|
2328
|
+
classification: classificationForPolicy,
|
|
2329
|
+
effectiveMode: input.effectiveMode,
|
|
2330
|
+
allowOffScopeRepair: input.allowOffScopeRepair
|
|
2331
|
+
});
|
|
2332
|
+
return strictScopeExpansion ? {
|
|
2333
|
+
...policy,
|
|
2334
|
+
reason: `${policy.reason}; strict repair scope forbids non-mechanical files outside the provider diff`
|
|
2335
|
+
} : policy;
|
|
2336
|
+
}
|
|
2337
|
+
function applyVerifiedTouchedTestFailureGuard(result, repairOutcome) {
|
|
2338
|
+
const repairedToPassingState = (repairOutcome?.verification?.passed === true || repairOutcome?.verificationLoop?.passed === true) && hasTrustworthyRepairOutcome(result);
|
|
2339
|
+
if (repairedToPassingState || !hasVerifiedTouchedTestFailure2(result)) {
|
|
2340
|
+
result.verificationDisposition = getVerificationResult(result);
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2343
|
+
const verificationResult = getVerificationResult(result);
|
|
2344
|
+
result.verificationDisposition = verificationResult;
|
|
2345
|
+
}
|
|
2346
|
+
async function readWorktreeHeadCommit(worktreePath) {
|
|
2347
|
+
const head = await execa3("git", ["rev-parse", "HEAD"], {
|
|
2348
|
+
cwd: worktreePath,
|
|
2349
|
+
stdio: "pipe",
|
|
2350
|
+
reject: false
|
|
2351
|
+
});
|
|
2352
|
+
if ((head.exitCode ?? 1) !== 0) return void 0;
|
|
2353
|
+
const commit = String(head.stdout ?? "").trim();
|
|
2354
|
+
return commit.length > 0 ? commit : void 0;
|
|
2355
|
+
}
|
|
2356
|
+
async function captureRepairDiffSnapshot(worktreePath, baselineRef) {
|
|
2357
|
+
try {
|
|
2358
|
+
const [commit, diff] = await Promise.all([
|
|
2359
|
+
readWorktreeHeadCommit(worktreePath),
|
|
2360
|
+
captureWorktreeDiff(worktreePath, baselineRef, true)
|
|
2361
|
+
]);
|
|
2362
|
+
return buildRepairDiffSnapshot(diff, commit);
|
|
2363
|
+
} catch {
|
|
2364
|
+
return void 0;
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
function buildRepairTriggerEvidence(repairOutcome) {
|
|
2368
|
+
const firstIteration = repairOutcome.verificationLoop?.iterations?.[0];
|
|
2369
|
+
const stopReason = typeof repairOutcome.verificationLoop?.stopReason === "string" ? repairOutcome.verificationLoop.stopReason : void 0;
|
|
2370
|
+
if (!firstIteration) {
|
|
2371
|
+
return {
|
|
2372
|
+
type: "unknown",
|
|
2373
|
+
gateId: "verification-loop",
|
|
2374
|
+
summary: stopReason ? `Verification-driven repair loop recorded no iterations; stopReason=${stopReason}` : "Verification-driven repair loop recorded no iterations"
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
const failedGate = firstIteration.build?.status !== "passed" ? "build" : firstIteration.lint && firstIteration.lint.status !== "passed" ? "lint" : firstIteration.tests && firstIteration.tests.status !== "passed" ? "test" : void 0;
|
|
2378
|
+
if (!failedGate) {
|
|
2379
|
+
return {
|
|
2380
|
+
type: "policy",
|
|
2381
|
+
gateId: "verification-loop",
|
|
2382
|
+
summary: stopReason ? `Verification policy ran without an initial gate failure; stopReason=${stopReason}` : "Verification policy ran without an initial gate failure"
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
const failedConstraintCheck = firstIteration.build?.command === "constraint-check" || firstIteration.lint?.command === "constraint-check" || firstIteration.tests?.command === "constraint-check";
|
|
2386
|
+
if (failedConstraintCheck) {
|
|
2387
|
+
return {
|
|
2388
|
+
type: "policy",
|
|
2389
|
+
gateId: "constraint-check",
|
|
2390
|
+
summary: stopReason ? `Verification repair was triggered by policy constraint failure; stopReason=${stopReason}` : "Verification repair was triggered by policy constraint failure"
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
return {
|
|
2394
|
+
type: "gate_failure",
|
|
2395
|
+
gateId: failedGate,
|
|
2396
|
+
summary: stopReason ? `Verification repair was triggered by initial ${failedGate} gate failure; stopReason=${stopReason}` : `Verification repair was triggered by initial ${failedGate} gate failure`
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
async function taskScopedChangedFiles(worktreePath, baselineRef) {
|
|
2400
|
+
try {
|
|
2401
|
+
const diff = await captureWorktreeDiff(worktreePath, baselineRef, true);
|
|
2402
|
+
if (!diff || diff.trim().length === 0) return [];
|
|
2403
|
+
return extractChangedFilesMeta(diff).map((meta) => meta.path).filter(Boolean);
|
|
2404
|
+
} catch {
|
|
2405
|
+
return [];
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
function coerceComparisonGateStatus(status) {
|
|
2409
|
+
switch (status) {
|
|
2410
|
+
case "passed":
|
|
2411
|
+
case "failed":
|
|
2412
|
+
case "skipped":
|
|
2413
|
+
case "timeout":
|
|
2414
|
+
case "error":
|
|
2415
|
+
case "unavailable":
|
|
2416
|
+
case "not_run":
|
|
2417
|
+
return status;
|
|
2418
|
+
default:
|
|
2419
|
+
return void 0;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
function statusFromExitCode(exitCode) {
|
|
2423
|
+
return exitCode === 0 ? "passed" : "failed";
|
|
2424
|
+
}
|
|
2425
|
+
function toGateArtifactStatus(status) {
|
|
2426
|
+
return status === "not_run" ? "skipped" : status;
|
|
2427
|
+
}
|
|
2428
|
+
async function attachBaselineComparisonIfAvailable(result) {
|
|
2429
|
+
const baselineRef = typeof result.baselineRef === "string" ? result.baselineRef.trim() : "";
|
|
2430
|
+
if (!baselineRef) return;
|
|
2431
|
+
const last = latestVerificationIteration(result.verificationLoop);
|
|
2432
|
+
if (!last) return;
|
|
2433
|
+
if (shouldTrustVerificationLoopBaseline(result)) {
|
|
2434
|
+
result.baselineComparison = {
|
|
2435
|
+
verdict: "pass",
|
|
2436
|
+
baselineRef,
|
|
2437
|
+
summary: {
|
|
2438
|
+
totalNewFailures: 0,
|
|
2439
|
+
totalFixedIssues: last.fixedIssueCount ?? 0,
|
|
2440
|
+
totalPersistingIssues: 0,
|
|
2441
|
+
shouldPenalize: false,
|
|
2442
|
+
scoreAdjustment: 0,
|
|
2443
|
+
message: "Verification loop passed baseline comparison"
|
|
2444
|
+
},
|
|
2445
|
+
tests: { newFailures: [], fixedIssues: [], persistingIssues: [] },
|
|
2446
|
+
types: { newFailures: [], fixedIssues: [], persistingIssues: [] },
|
|
2447
|
+
lint: { newFailures: [], fixedIssues: [], persistingIssues: [] }
|
|
2448
|
+
};
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
const repoRoot = process.cwd();
|
|
2452
|
+
const cacheDir = path.join(repoRoot, ".poetic", "cache", "verification-baseline");
|
|
2453
|
+
const changedFiles = typeof result.worktreeDiff === "string" && result.worktreeDiff.trim().length > 0 ? extractChangedFilesMeta(result.worktreeDiff).map((meta) => meta.path).filter(Boolean) : [];
|
|
2454
|
+
const baseline = await getOrCaptureVerificationBaseline({
|
|
2455
|
+
repoRoot,
|
|
2456
|
+
baselineRef,
|
|
2457
|
+
cacheDir,
|
|
2458
|
+
captureOptions: { gateTimeoutMs: 6e4, changedFiles }
|
|
2459
|
+
});
|
|
2460
|
+
if (!baseline.metadata?.captureSucceeded) {
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
const currentArtifacts = {
|
|
2464
|
+
build: {
|
|
2465
|
+
exitCode: last.build.exitCode,
|
|
2466
|
+
outputTail: last.build.outputTail,
|
|
2467
|
+
command: last.build.command,
|
|
2468
|
+
durationMs: 0,
|
|
2469
|
+
stdoutTail: "",
|
|
2470
|
+
stderrTail: "",
|
|
2471
|
+
language: last.build.language,
|
|
2472
|
+
status: toGateArtifactStatus(
|
|
2473
|
+
coerceComparisonGateStatus(last.build.status) ?? statusFromExitCode(last.build.exitCode)
|
|
2474
|
+
)
|
|
2475
|
+
},
|
|
2476
|
+
lint: last.lint ? {
|
|
2477
|
+
exitCode: last.lint.exitCode,
|
|
2478
|
+
outputTail: last.lint.outputTail,
|
|
2479
|
+
command: last.lint.command,
|
|
2480
|
+
durationMs: 0,
|
|
2481
|
+
language: last.lint.language,
|
|
2482
|
+
status: toGateArtifactStatus(
|
|
2483
|
+
coerceComparisonGateStatus(last.lint.status) ?? statusFromExitCode(last.lint.exitCode)
|
|
2484
|
+
)
|
|
2485
|
+
} : void 0,
|
|
2486
|
+
tests: last.tests ? {
|
|
2487
|
+
exitCode: last.tests.exitCode,
|
|
2488
|
+
status: toGateArtifactStatus(
|
|
2489
|
+
coerceComparisonGateStatus(last.tests.status) ?? statusFromExitCode(last.tests.exitCode)
|
|
2490
|
+
),
|
|
2491
|
+
failingTests: last.tests.failingTests,
|
|
2492
|
+
outputTail: last.tests.outputTail,
|
|
2493
|
+
command: last.tests.command,
|
|
2494
|
+
durationMs: 0,
|
|
2495
|
+
language: last.tests.language
|
|
2496
|
+
} : void 0
|
|
2497
|
+
};
|
|
2498
|
+
const currentOutcomes = verificationOutcomesFromGateArtifacts(currentArtifacts, {
|
|
2499
|
+
cwd: repoRoot,
|
|
2500
|
+
source: "variant-worker"
|
|
2501
|
+
});
|
|
2502
|
+
const stableComparison = compareVerificationOutcomeToBaseline(baseline, currentOutcomes);
|
|
2503
|
+
result.baselineComparison = toBaselineComparisonResult(
|
|
2504
|
+
stableComparisonToBaselineComparison(stableComparison)
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2507
|
+
async function applyRegistryDowngradeIfApplicable(result) {
|
|
2508
|
+
const comparison = result.baselineComparison;
|
|
2509
|
+
if (!comparison || comparison.summary.shouldPenalize !== true) return;
|
|
2510
|
+
const testNewFailures = comparison.tests?.newFailures ?? [];
|
|
2511
|
+
if (testNewFailures.length === 0) return;
|
|
2512
|
+
if ((comparison.types?.newFailures?.length ?? 0) > 0) return;
|
|
2513
|
+
if ((comparison.lint?.newFailures?.length ?? 0) > 0) return;
|
|
2514
|
+
const last = latestVerificationIteration(result.verificationLoop);
|
|
2515
|
+
const testOutputTail = last?.tests?.outputTail ?? "";
|
|
2516
|
+
const registry = await loadKnownPreExistingFailures(process.cwd());
|
|
2517
|
+
const baselineRef = (result.baselineRef ?? comparison.baselineRef)?.trim().toLowerCase();
|
|
2518
|
+
const registryBaseCommit = registry.baseCommit?.trim().toLowerCase();
|
|
2519
|
+
const MIN_SHA_PREFIX_LEN = 7;
|
|
2520
|
+
if (!baselineRef || !registryBaseCommit || baselineRef.length < MIN_SHA_PREFIX_LEN || registryBaseCommit.length < MIN_SHA_PREFIX_LEN || !baselineRef.startsWith(registryBaseCommit) && !registryBaseCommit.startsWith(baselineRef)) {
|
|
2521
|
+
return;
|
|
2522
|
+
}
|
|
2523
|
+
if (!allFailuresAreRegistered(testNewFailures, registry, testOutputTail)) {
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
comparison.summary.shouldPenalize = false;
|
|
2527
|
+
comparison.verdict = "neutral";
|
|
2528
|
+
comparison.summary.message = [
|
|
2529
|
+
comparison.summary.message,
|
|
2530
|
+
`[registry] All ${testNewFailures.length} new test failure(s) matched known-pre-existing-failures registry \u2014 downgraded to baseline_clean.`
|
|
2531
|
+
].filter(Boolean).join("\n");
|
|
2532
|
+
const matchedNames = testNewFailures.join(", ");
|
|
2533
|
+
process.stderr.write(
|
|
2534
|
+
`[poetic:registry] Downgraded shouldPenalize=true \u2192 false for ${testNewFailures.length} registered failure(s): ${matchedNames}
|
|
2535
|
+
`
|
|
2536
|
+
);
|
|
2537
|
+
}
|
|
2538
|
+
function getWarningCodes(result) {
|
|
2539
|
+
const warnings = result.warnings;
|
|
2540
|
+
if (!Array.isArray(warnings)) return /* @__PURE__ */ new Set();
|
|
2541
|
+
return new Set(
|
|
2542
|
+
warnings.map((w) => w?.code).filter((code) => typeof code === "string" && code.trim().length > 0)
|
|
2543
|
+
);
|
|
2544
|
+
}
|
|
2545
|
+
function looksLikeClarifyingQuestion(text) {
|
|
2546
|
+
const lower = text.toLowerCase();
|
|
2547
|
+
if (lower.includes("?")) return true;
|
|
2548
|
+
return /(\bdoes(?:n't| not)\s+exist\b|\bnot\s+found\b|\bcannot\s+find\b|\bcan't\s+find\b|\bcould\s+you\s+clarify\b|\bplease\s+clarify\b|\bwhich\s+file\b|\bwhat\s+file\b)/i.test(
|
|
2549
|
+
lower
|
|
2550
|
+
);
|
|
2551
|
+
}
|
|
2552
|
+
initializeLabModeFromEnv("initializing-worker");
|
|
2553
|
+
function formatElapsedRuntime(elapsedMs) {
|
|
2554
|
+
const totalSeconds = Math.max(0, Math.floor(elapsedMs / 1e3));
|
|
2555
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
2556
|
+
const seconds = totalSeconds % 60;
|
|
2557
|
+
return `${minutes}m ${seconds}s`;
|
|
2558
|
+
}
|
|
2559
|
+
async function runWithTimeout(promise, timeoutMs, label) {
|
|
2560
|
+
let timeoutHandle;
|
|
2561
|
+
try {
|
|
2562
|
+
return await Promise.race([
|
|
2563
|
+
promise,
|
|
2564
|
+
new Promise((_, reject) => {
|
|
2565
|
+
timeoutHandle = setTimeout(() => {
|
|
2566
|
+
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
2567
|
+
}, timeoutMs);
|
|
2568
|
+
})
|
|
2569
|
+
]);
|
|
2570
|
+
} finally {
|
|
2571
|
+
if (timeoutHandle) {
|
|
2572
|
+
clearTimeout(timeoutHandle);
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
function parseRepairLoopConfig() {
|
|
2577
|
+
const enabledEnv = process.env.POETIC_REPAIR_LOOP_ENABLED;
|
|
2578
|
+
const enabled = enabledEnv === "1";
|
|
2579
|
+
const maxIterationsRaw = Number(process.env.POETIC_REPAIR_LOOP_MAX_ITERATIONS);
|
|
2580
|
+
const maxIterations = Number.isFinite(maxIterationsRaw) && maxIterationsRaw > 0 ? maxIterationsRaw : DEFAULT_REPAIR_LOOP_MAX_ITERATIONS;
|
|
2581
|
+
const iterationTimeoutMsRaw = Number(process.env.POETIC_REPAIR_LOOP_ITERATION_TIMEOUT_MS);
|
|
2582
|
+
const iterationTimeoutMs = Number.isFinite(iterationTimeoutMsRaw) && iterationTimeoutMsRaw > 0 ? iterationTimeoutMsRaw : DEFAULT_REPAIR_LOOP_ITERATION_TIMEOUT_MS;
|
|
2583
|
+
const abortIfRegression = process.env.POETIC_REPAIR_LOOP_ABORT_IF_REGRESSION !== "0";
|
|
2584
|
+
const allowApiBreak = process.env.POETIC_ALLOW_API_BREAK === "1";
|
|
2585
|
+
const repairModeEnv = process.env.POETIC_REPAIR_MODE;
|
|
2586
|
+
const repairMode = repairModeEnv === "strict" || repairModeEnv === "none" || repairModeEnv === "auto" ? repairModeEnv : "auto";
|
|
2587
|
+
const scopePolicyEnv = process.env.POETIC_REPAIR_SCOPE_POLICY;
|
|
2588
|
+
const scopePolicy = scopePolicyEnv === "expand" ? "expand" : "strict";
|
|
2589
|
+
const saveModesEnv = process.env.POETIC_REPAIR_SAVE_MODES;
|
|
2590
|
+
const repairSaveModes = (saveModesEnv?.trim().length ? saveModesEnv : "patch").split(",").map((m) => m.trim()).filter(Boolean).filter((m) => m === "patch" || m === "ref" || m === "branch");
|
|
2591
|
+
const normalizedRepairSaveModes = repairSaveModes.length > 0 ? repairSaveModes : ["patch"];
|
|
2592
|
+
return {
|
|
2593
|
+
enabled,
|
|
2594
|
+
maxIterations,
|
|
2595
|
+
iterationTimeoutMs,
|
|
2596
|
+
abortIfRegression,
|
|
2597
|
+
allowApiBreak,
|
|
2598
|
+
repairMode,
|
|
2599
|
+
scopePolicy,
|
|
2600
|
+
repairSaveModes: [...normalizedRepairSaveModes]
|
|
2601
|
+
};
|
|
2602
|
+
}
|
|
2603
|
+
async function tryCleanupRetainedWorktree(worktreePath, verbose) {
|
|
2604
|
+
const normalized = path.resolve(worktreePath);
|
|
2605
|
+
const poeticRoot = resolvePoeticRoot();
|
|
2606
|
+
const allowedRoots = [
|
|
2607
|
+
path.resolve(poeticRoot, ".poetic", "tmp", "worktrees"),
|
|
2608
|
+
path.resolve(poeticRoot, "poetic-worktrees"),
|
|
2609
|
+
path.join("/tmp", "poetic-worktrees"),
|
|
2610
|
+
path.join(process.env.TMPDIR ?? "/tmp", "poetic-worktrees")
|
|
2611
|
+
];
|
|
2612
|
+
const allowed = allowedRoots.some((root) => {
|
|
2613
|
+
const rootWithSep = root.endsWith(path.sep) ? root : `${root}${path.sep}`;
|
|
2614
|
+
return normalized === root || normalized.startsWith(rootWithSep);
|
|
2615
|
+
});
|
|
2616
|
+
if (!allowed) {
|
|
2617
|
+
if (verbose) {
|
|
2618
|
+
console.warn(
|
|
2619
|
+
chalk.yellow(`Refusing to cleanup retained worktree outside known roots: ${worktreePath}`)
|
|
2620
|
+
);
|
|
2621
|
+
}
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
try {
|
|
2625
|
+
await execa3("git", ["worktree", "remove", "--force", normalized], {
|
|
2626
|
+
cwd: poeticRoot,
|
|
2627
|
+
stdio: "pipe",
|
|
2628
|
+
timeout: 3e4,
|
|
2629
|
+
reject: false
|
|
2630
|
+
});
|
|
2631
|
+
} catch (error) {
|
|
2632
|
+
if (verbose) {
|
|
2633
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2634
|
+
console.warn(chalk.yellow(`Failed to cleanup retained worktree: ${msg}`));
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
async function ensureGitMetrics(result) {
|
|
2639
|
+
if (result.gitMetrics) return;
|
|
2640
|
+
const worktreePath = result.retainedWorktreePath;
|
|
2641
|
+
if (worktreePath) {
|
|
2642
|
+
const summary = await getGitChangeSummary(worktreePath, result.baselineRef, true);
|
|
2643
|
+
if (summary.filesChanged > 0) {
|
|
2644
|
+
result.gitMetrics = {
|
|
2645
|
+
filesChanged: summary.filesChanged,
|
|
2646
|
+
linesAdded: summary.linesAdded,
|
|
2647
|
+
linesDeleted: summary.linesRemoved,
|
|
2648
|
+
source: summary.source === "fallback" ? "unknown" : "worktree"
|
|
2649
|
+
};
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
if (result.worktreeSummary) {
|
|
2654
|
+
result.gitMetrics = {
|
|
2655
|
+
filesChanged: result.worktreeSummary.filesChanged,
|
|
2656
|
+
linesAdded: result.worktreeSummary.insertions,
|
|
2657
|
+
linesDeleted: result.worktreeSummary.deletions,
|
|
2658
|
+
source: "unknown"
|
|
2659
|
+
};
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
function parseWorktreeListPorcelain(stdout) {
|
|
2663
|
+
const entries = [];
|
|
2664
|
+
let current = null;
|
|
2665
|
+
const flushCurrent = () => {
|
|
2666
|
+
if (current?.path) {
|
|
2667
|
+
entries.push(current);
|
|
2668
|
+
}
|
|
2669
|
+
current = null;
|
|
2670
|
+
};
|
|
2671
|
+
for (const rawLine of stdout.split("\n")) {
|
|
2672
|
+
const line = rawLine.trim();
|
|
2673
|
+
if (line.length === 0) {
|
|
2674
|
+
flushCurrent();
|
|
2675
|
+
continue;
|
|
2676
|
+
}
|
|
2677
|
+
if (line.startsWith("worktree ")) {
|
|
2678
|
+
flushCurrent();
|
|
2679
|
+
current = { path: line.substring("worktree ".length).trim() };
|
|
2680
|
+
continue;
|
|
2681
|
+
}
|
|
2682
|
+
if (line.startsWith("branch ") && current) {
|
|
2683
|
+
const branch = line.substring("branch ".length).trim().replace(/^refs\/heads\//, "");
|
|
2684
|
+
current.branch = branch.length > 0 ? branch : void 0;
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
flushCurrent();
|
|
2688
|
+
return entries;
|
|
2689
|
+
}
|
|
2690
|
+
async function findVariantWorktreePathForTimeout(taskId, variant, repoRoot) {
|
|
2691
|
+
try {
|
|
2692
|
+
const listResult = await execa3("git", ["worktree", "list", "--porcelain"], {
|
|
2693
|
+
cwd: repoRoot,
|
|
2694
|
+
reject: false,
|
|
2695
|
+
timeout: PARTIAL_GIT_TIMEOUT_MS
|
|
2696
|
+
});
|
|
2697
|
+
if ((listResult.exitCode ?? 0) !== 0 || !listResult.stdout?.trim()) {
|
|
2698
|
+
return void 0;
|
|
2699
|
+
}
|
|
2700
|
+
const entries = parseWorktreeListPorcelain(listResult.stdout);
|
|
2701
|
+
if (entries.length === 0) {
|
|
2702
|
+
return void 0;
|
|
2703
|
+
}
|
|
2704
|
+
const taskToken = sanitizeRefPart(taskId, { fallback: "task", maxLen: 80 });
|
|
2705
|
+
const providerToken = sanitizeRefPart(variant.provider, { fallback: "provider" });
|
|
2706
|
+
const strategyToken = sanitizeRefPart(variant.strategy, { fallback: "strategy" });
|
|
2707
|
+
const modeToken = sanitizeRefPart(variant.executionMode, { fallback: "mode", maxLen: 16 });
|
|
2708
|
+
const branchPrefix = `poetic/var-${taskToken}-`;
|
|
2709
|
+
const branchNeedle = `-${providerToken}-${strategyToken}-${modeToken}-`;
|
|
2710
|
+
const matches = entries.filter((entry) => {
|
|
2711
|
+
if (!entry.branch) return false;
|
|
2712
|
+
return entry.branch.startsWith(branchPrefix) && entry.branch.includes(branchNeedle);
|
|
2713
|
+
});
|
|
2714
|
+
if (matches.length === 0) {
|
|
2715
|
+
return void 0;
|
|
2716
|
+
}
|
|
2717
|
+
if (matches.length === 1) {
|
|
2718
|
+
return matches[0]?.path;
|
|
2719
|
+
}
|
|
2720
|
+
const withMtime = await Promise.all(
|
|
2721
|
+
matches.map(async (entry) => {
|
|
2722
|
+
try {
|
|
2723
|
+
const stat2 = await fs.stat(entry.path);
|
|
2724
|
+
return { path: entry.path, mtimeMs: stat2.mtimeMs };
|
|
2725
|
+
} catch {
|
|
2726
|
+
return { path: entry.path, mtimeMs: 0 };
|
|
2727
|
+
}
|
|
2728
|
+
})
|
|
2729
|
+
);
|
|
2730
|
+
withMtime.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
2731
|
+
return withMtime[0]?.path;
|
|
2732
|
+
} catch {
|
|
2733
|
+
return void 0;
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
async function main() {
|
|
2737
|
+
const program = new Command();
|
|
2738
|
+
program.name("variant-worker").description("Execute a single variant task and write result to file").requiredOption("--task-id <id>", "Task identifier").requiredOption("--variant <json>", "Variant configuration as JSON string").requiredOption("--prompt-file <path>", "Path to file containing prompt text").requiredOption("--result-file <path>", "Path to write result JSON").option(
|
|
2739
|
+
"--partial-result-file <path>",
|
|
2740
|
+
"Path to write timeout-partial result JSON (best-effort)"
|
|
2741
|
+
).option("--timeout-minutes <minutes>", "Execution timeout in minutes", parseFloat).option(
|
|
2742
|
+
"--idle-warn-minutes <minutes>",
|
|
2743
|
+
"Provider idle warning threshold in minutes",
|
|
2744
|
+
parseFloat
|
|
2745
|
+
).option("--idle-kill-minutes <minutes>", "Provider idle stop threshold in minutes", parseFloat).option("--verbose", "Enable verbose logging", false).action(async (options) => {
|
|
2746
|
+
await executeWorker(options);
|
|
2747
|
+
});
|
|
2748
|
+
await program.parseAsync(process.argv);
|
|
2749
|
+
}
|
|
2750
|
+
async function warnOnce(message, state) {
|
|
2751
|
+
if (state.warned) return;
|
|
2752
|
+
state.warned = true;
|
|
2753
|
+
console.log(chalk.yellow(message));
|
|
2754
|
+
}
|
|
2755
|
+
var RESULT_PERSISTENCE_PATH_KEYS = /* @__PURE__ */ new Set([
|
|
2756
|
+
"artifactManifest",
|
|
2757
|
+
"resultFile",
|
|
2758
|
+
"resultPath",
|
|
2759
|
+
"retainedWorktreePath",
|
|
2760
|
+
"worktreePath"
|
|
2761
|
+
]);
|
|
2762
|
+
function shouldPreserveResultPersistenceString(key) {
|
|
2763
|
+
return key !== void 0 && RESULT_PERSISTENCE_PATH_KEYS.has(key);
|
|
2764
|
+
}
|
|
2765
|
+
function serializeErrorForJson(error) {
|
|
2766
|
+
if (error instanceof Error) {
|
|
2767
|
+
const sanitized = sanitizeError(error);
|
|
2768
|
+
const serialized = {
|
|
2769
|
+
name: sanitized.name,
|
|
2770
|
+
message: sanitized.message,
|
|
2771
|
+
stack: sanitized.stack
|
|
2772
|
+
};
|
|
2773
|
+
if (error instanceof ProviderExecutionError) {
|
|
2774
|
+
serialized.code = error.code;
|
|
2775
|
+
serialized.reason = sanitizeSynthesisArtifactForPersistence(error.reason);
|
|
2776
|
+
serialized.retryable = error.retryable;
|
|
2777
|
+
serialized.exitCode = error.exitCode;
|
|
2778
|
+
serialized.statusCode = error.statusCode;
|
|
2779
|
+
serialized.idleTerminated = error.idleTerminated;
|
|
2780
|
+
serialized.signal = sanitizeSynthesisArtifactForPersistence(error.signal);
|
|
2781
|
+
serialized.terminationSource = sanitizeSynthesisArtifactForPersistence(
|
|
2782
|
+
error.terminationSource
|
|
2783
|
+
);
|
|
2784
|
+
serialized.context = sanitizeSynthesisArtifactForPersistence(error.context);
|
|
2785
|
+
serialized.parsedError = sanitizeSynthesisArtifactForPersistence(error.parsedError);
|
|
2786
|
+
}
|
|
2787
|
+
return serialized;
|
|
2788
|
+
}
|
|
2789
|
+
return sanitizeSynthesisArtifactForPersistence(error);
|
|
2790
|
+
}
|
|
2791
|
+
function sanitizeResultPayloadForJson(value, seen = /* @__PURE__ */ new WeakSet(), key = void 0) {
|
|
2792
|
+
if (value instanceof Error) {
|
|
2793
|
+
return serializeErrorForJson(value);
|
|
2794
|
+
}
|
|
2795
|
+
if (typeof value === "string") {
|
|
2796
|
+
return shouldPreserveResultPersistenceString(key) ? value : sanitizeSynthesisStringForPersistence(value);
|
|
2797
|
+
}
|
|
2798
|
+
if (typeof value === "number") {
|
|
2799
|
+
return Number.isFinite(value) ? value : null;
|
|
2800
|
+
}
|
|
2801
|
+
if (typeof value === "boolean" || value === null) {
|
|
2802
|
+
return value;
|
|
2803
|
+
}
|
|
2804
|
+
if (typeof value === "bigint") {
|
|
2805
|
+
return value.toString();
|
|
2806
|
+
}
|
|
2807
|
+
if (typeof value === "symbol" || typeof value === "function" || value === void 0) {
|
|
2808
|
+
return void 0;
|
|
2809
|
+
}
|
|
2810
|
+
if (value instanceof Date) {
|
|
2811
|
+
return value.toISOString();
|
|
2812
|
+
}
|
|
2813
|
+
if (seen.has(value)) {
|
|
2814
|
+
return "[Circular]";
|
|
2815
|
+
}
|
|
2816
|
+
seen.add(value);
|
|
2817
|
+
try {
|
|
2818
|
+
if (Array.isArray(value)) {
|
|
2819
|
+
return value.map((entry) => sanitizeResultPayloadForJson(entry, seen, key));
|
|
2820
|
+
}
|
|
2821
|
+
const sanitized = {};
|
|
2822
|
+
for (const [entryKey, entry] of Object.entries(value)) {
|
|
2823
|
+
const sanitizedEntry = entryKey === "error" ? serializeErrorForJson(entry) : sanitizeResultPayloadForJson(entry, seen, entryKey);
|
|
2824
|
+
const finalValue = isSensitiveKey(entryKey) ? "[REDACTED]" : sanitizedEntry;
|
|
2825
|
+
if (finalValue !== void 0) {
|
|
2826
|
+
sanitized[entryKey] = finalValue;
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
return sanitized;
|
|
2830
|
+
} finally {
|
|
2831
|
+
seen.delete(value);
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
async function writeResultFileAtomically(resultPath, payload) {
|
|
2835
|
+
const resultJson = JSON.stringify(sanitizeResultPayloadForJson(payload), null, 2);
|
|
2836
|
+
const tmpFile = `${resultPath}.tmp.${randomBytes(4).toString("hex")}`;
|
|
2837
|
+
await fs.writeFile(tmpFile, resultJson, "utf-8");
|
|
2838
|
+
const fd = openSync(tmpFile, "r+");
|
|
2839
|
+
try {
|
|
2840
|
+
fsyncSync(fd);
|
|
2841
|
+
} finally {
|
|
2842
|
+
closeSync(fd);
|
|
2843
|
+
}
|
|
2844
|
+
await fs.rename(tmpFile, resultPath);
|
|
2845
|
+
}
|
|
2846
|
+
var __testOnlyWriteResultFileAtomically = writeResultFileAtomically;
|
|
2847
|
+
function formatVerboseWorkerCompletionMetric(variant, result) {
|
|
2848
|
+
if (variant.strategy === "fusion") {
|
|
2849
|
+
return `Contribution: ${result.success ? "completed" : "not completed"}`;
|
|
2850
|
+
}
|
|
2851
|
+
return `Completeness: ${result.completeness.toFixed(1)}%`;
|
|
2852
|
+
}
|
|
2853
|
+
async function executeWorker(options) {
|
|
2854
|
+
const {
|
|
2855
|
+
taskId,
|
|
2856
|
+
variant: variantJson,
|
|
2857
|
+
promptFile,
|
|
2858
|
+
resultFile,
|
|
2859
|
+
timeoutMinutes,
|
|
2860
|
+
idleWarnMinutes,
|
|
2861
|
+
idleKillMinutes,
|
|
2862
|
+
verbose
|
|
2863
|
+
} = options;
|
|
2864
|
+
const repairLoopConfig = parseRepairLoopConfig();
|
|
2865
|
+
if (idleWarnMinutes == null !== (idleKillMinutes == null)) {
|
|
2866
|
+
throw new Error("--idle-warn-minutes and --idle-kill-minutes must be provided together");
|
|
2867
|
+
}
|
|
2868
|
+
initializeLabModeFromEnv(taskId);
|
|
2869
|
+
if (verbose) {
|
|
2870
|
+
console.log(chalk.blue("Variant Worker starting"));
|
|
2871
|
+
console.log(chalk.gray(` Task ID: ${taskId}`));
|
|
2872
|
+
console.log(chalk.gray(` Prompt: ${promptFile}`));
|
|
2873
|
+
console.log(chalk.gray(` Result: ${resultFile}`));
|
|
2874
|
+
console.log(
|
|
2875
|
+
chalk.gray(` Partial Result: ${options.partialResultFile ?? `${resultFile}.partial.json`}`)
|
|
2876
|
+
);
|
|
2877
|
+
}
|
|
2878
|
+
let variant;
|
|
2879
|
+
try {
|
|
2880
|
+
variant = parseJsonWithContext(
|
|
2881
|
+
variantJson,
|
|
2882
|
+
"[Variant Worker] variant configuration"
|
|
2883
|
+
);
|
|
2884
|
+
} catch (error) {
|
|
2885
|
+
throw new Error(
|
|
2886
|
+
`Invalid variant JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
2887
|
+
);
|
|
2888
|
+
}
|
|
2889
|
+
const workerProcessLifecycle = new ProcessLifecycleManager({
|
|
2890
|
+
verbose: verbose === true,
|
|
2891
|
+
defaultTimeoutMinutes: timeoutMinutes ?? DEFAULT_PER_VARIANT_TIMEOUT_MINUTES,
|
|
2892
|
+
maxConcurrentProcesses: 10,
|
|
2893
|
+
cleanupOnTermination: true
|
|
2894
|
+
});
|
|
2895
|
+
setProcessLifecycleManager(workerProcessLifecycle);
|
|
2896
|
+
const processLifecycleSignalHandlerName = "variant-worker-process-lifecycle";
|
|
2897
|
+
const signalHandlerName = "variant-worker-timeout-partial-result";
|
|
2898
|
+
let postProviderSignalHandlersInstalled = false;
|
|
2899
|
+
const onPostProviderSigterm = () => {
|
|
2900
|
+
void postProviderShutdown?.("SIGTERM");
|
|
2901
|
+
};
|
|
2902
|
+
const onPostProviderSigint = () => {
|
|
2903
|
+
void postProviderShutdown?.("SIGINT");
|
|
2904
|
+
};
|
|
2905
|
+
let postProviderShutdown;
|
|
2906
|
+
const installPostProviderSignalHandlers = () => {
|
|
2907
|
+
if (postProviderSignalHandlersInstalled) return;
|
|
2908
|
+
signalCoordinator.suspendProcessSignalHandlers();
|
|
2909
|
+
process.on("SIGTERM", onPostProviderSigterm);
|
|
2910
|
+
process.on("SIGINT", onPostProviderSigint);
|
|
2911
|
+
postProviderSignalHandlersInstalled = true;
|
|
2912
|
+
};
|
|
2913
|
+
const uninstallPostProviderSignalHandlers = () => {
|
|
2914
|
+
if (!postProviderSignalHandlersInstalled) return;
|
|
2915
|
+
process.removeListener("SIGTERM", onPostProviderSigterm);
|
|
2916
|
+
process.removeListener("SIGINT", onPostProviderSigint);
|
|
2917
|
+
signalCoordinator.resumeProcessSignalHandlers();
|
|
2918
|
+
postProviderSignalHandlersInstalled = false;
|
|
2919
|
+
};
|
|
2920
|
+
signalCoordinator.register(
|
|
2921
|
+
processLifecycleSignalHandlerName,
|
|
2922
|
+
async (context) => {
|
|
2923
|
+
if (context.reason === "SIGTERM" || context.reason === "SIGINT") {
|
|
2924
|
+
await workerProcessLifecycle.handleSignal(context.reason);
|
|
2925
|
+
return;
|
|
2926
|
+
}
|
|
2927
|
+
await workerProcessLifecycle.close();
|
|
2928
|
+
},
|
|
2929
|
+
{ priority: "critical", phase: "process-termination" }
|
|
2930
|
+
);
|
|
2931
|
+
try {
|
|
2932
|
+
variant.nativeWebSearch = isNativeResearchEnabledFor(variant.provider);
|
|
2933
|
+
const runMode = variant.isBasicCompetition ? "basic-cli-native" : "advanced";
|
|
2934
|
+
const partialResultFile = typeof options.partialResultFile === "string" && options.partialResultFile.trim().length > 0 ? options.partialResultFile.trim() : `${resultFile}.partial.json`;
|
|
2935
|
+
const workerStartMs = Date.now();
|
|
2936
|
+
const startTime = process.hrtime.bigint();
|
|
2937
|
+
const timeoutContext = {};
|
|
2938
|
+
let executorSettled = false;
|
|
2939
|
+
const persistTimeoutPartialResult = async () => {
|
|
2940
|
+
const timeoutWorktreeCandidate = timeoutContext.worktreePath ?? variant.workflowWorktreePath;
|
|
2941
|
+
let timeoutWorktreePath = typeof timeoutWorktreeCandidate === "string" && timeoutWorktreeCandidate.trim().length > 0 ? timeoutWorktreeCandidate.trim() : void 0;
|
|
2942
|
+
if (!timeoutWorktreePath) {
|
|
2943
|
+
timeoutWorktreePath = await findVariantWorktreePathForTimeout(
|
|
2944
|
+
taskId,
|
|
2945
|
+
variant,
|
|
2946
|
+
process.cwd()
|
|
2947
|
+
);
|
|
2948
|
+
if (!timeoutWorktreePath && verbose) {
|
|
2949
|
+
console.warn("[partial-save] Worktree context unavailable; needle discovery also failed");
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
const elapsedText = formatElapsedRuntime(Date.now() - workerStartMs);
|
|
2953
|
+
let filesChanged = 0;
|
|
2954
|
+
let linesAdded = 0;
|
|
2955
|
+
let linesRemoved = 0;
|
|
2956
|
+
let statusLines = [];
|
|
2957
|
+
let partialEdits = false;
|
|
2958
|
+
let autoCommitFailed = false;
|
|
2959
|
+
let worktreeDiff;
|
|
2960
|
+
let baselineRef = typeof timeoutContext.baselineRef === "string" && timeoutContext.baselineRef.trim().length > 0 ? timeoutContext.baselineRef.trim() : void 0;
|
|
2961
|
+
let partialErrorDetail;
|
|
2962
|
+
if (timeoutWorktreePath) {
|
|
2963
|
+
try {
|
|
2964
|
+
const canMutateWorktreeForSnapshot = executorSettled;
|
|
2965
|
+
if (canMutateWorktreeForSnapshot) {
|
|
2966
|
+
await execa3("git", ["add", "-A"], {
|
|
2967
|
+
cwd: timeoutWorktreePath,
|
|
2968
|
+
stdio: "pipe",
|
|
2969
|
+
reject: false,
|
|
2970
|
+
timeout: 1200
|
|
2971
|
+
});
|
|
2972
|
+
} else {
|
|
2973
|
+
partialErrorDetail = "executor still running during timeout; skipped git add/commit partial snapshot";
|
|
2974
|
+
}
|
|
2975
|
+
const [cachedStat, cachedShortStat, statusResult] = await Promise.all([
|
|
2976
|
+
execa3(
|
|
2977
|
+
"git",
|
|
2978
|
+
canMutateWorktreeForSnapshot ? ["diff", "--cached", "--stat"] : ["diff", "--stat"],
|
|
2979
|
+
{
|
|
2980
|
+
cwd: timeoutWorktreePath,
|
|
2981
|
+
stdio: "pipe",
|
|
2982
|
+
reject: false,
|
|
2983
|
+
timeout: 1200
|
|
2984
|
+
}
|
|
2985
|
+
),
|
|
2986
|
+
execa3(
|
|
2987
|
+
"git",
|
|
2988
|
+
canMutateWorktreeForSnapshot ? ["diff", "--cached", "--shortstat"] : ["diff", "--shortstat"],
|
|
2989
|
+
{
|
|
2990
|
+
cwd: timeoutWorktreePath,
|
|
2991
|
+
stdio: "pipe",
|
|
2992
|
+
reject: false,
|
|
2993
|
+
timeout: 1200
|
|
2994
|
+
}
|
|
2995
|
+
),
|
|
2996
|
+
execa3("git", ["status", "--porcelain", "--untracked-files=all"], {
|
|
2997
|
+
cwd: timeoutWorktreePath,
|
|
2998
|
+
stdio: "pipe",
|
|
2999
|
+
reject: false,
|
|
3000
|
+
timeout: 1200
|
|
3001
|
+
})
|
|
3002
|
+
]);
|
|
3003
|
+
partialEdits = cachedStat.stdout.trim().length > 0;
|
|
3004
|
+
const parsedShortStat = parseShortStatSummary(cachedShortStat.stdout);
|
|
3005
|
+
filesChanged = parsedShortStat.filesChanged;
|
|
3006
|
+
linesAdded = parsedShortStat.linesAdded;
|
|
3007
|
+
linesRemoved = parsedShortStat.linesRemoved;
|
|
3008
|
+
statusLines = statusResult.stdout.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim().length > 0);
|
|
3009
|
+
if (partialEdits && filesChanged === 0 && statusLines.length > 0) {
|
|
3010
|
+
filesChanged = statusLines.length;
|
|
3011
|
+
}
|
|
3012
|
+
if (partialEdits && canMutateWorktreeForSnapshot) {
|
|
3013
|
+
const commitMessage = `chore: partial work (timeout at ${elapsedText})`;
|
|
3014
|
+
const commit = await execa3("git", ["commit", "--no-verify", "-m", commitMessage], {
|
|
3015
|
+
cwd: timeoutWorktreePath,
|
|
3016
|
+
stdio: "pipe",
|
|
3017
|
+
reject: false,
|
|
3018
|
+
timeout: 1500
|
|
3019
|
+
});
|
|
3020
|
+
autoCommitFailed = (commit.exitCode ?? 0) !== 0;
|
|
3021
|
+
if (!autoCommitFailed && !baselineRef) {
|
|
3022
|
+
const parent = await execa3("git", ["rev-parse", "HEAD~1"], {
|
|
3023
|
+
cwd: timeoutWorktreePath,
|
|
3024
|
+
stdio: "pipe",
|
|
3025
|
+
reject: false,
|
|
3026
|
+
timeout: 1200
|
|
3027
|
+
});
|
|
3028
|
+
if ((parent.exitCode ?? 0) === 0 && parent.stdout.trim().length > 0) {
|
|
3029
|
+
baselineRef = parent.stdout.trim();
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
try {
|
|
3033
|
+
const diffText = await captureWorktreeDiff(timeoutWorktreePath, baselineRef, true);
|
|
3034
|
+
if (diffText.trim().length > 0) {
|
|
3035
|
+
worktreeDiff = normalizeWorktreeDiff(diffText);
|
|
3036
|
+
}
|
|
3037
|
+
} catch {
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
} catch (error) {
|
|
3041
|
+
partialErrorDetail = error instanceof Error ? error.message : String(error);
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
if (!partialErrorDetail && partialEdits) {
|
|
3045
|
+
if (autoCommitFailed) {
|
|
3046
|
+
partialErrorDetail = "auto-commit failed";
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
const completeness = partialEdits ? estimateTimeoutCompletenessFromScope({
|
|
3050
|
+
filesChanged,
|
|
3051
|
+
linesAdded,
|
|
3052
|
+
linesRemoved,
|
|
3053
|
+
statusEntries: statusLines.length
|
|
3054
|
+
}) : 0;
|
|
3055
|
+
const timeoutErrorMessage = (() => {
|
|
3056
|
+
if (partialErrorDetail) {
|
|
3057
|
+
if (partialEdits && !autoCommitFailed) {
|
|
3058
|
+
return `Timed out at ${elapsedText}; partial edits detected (preservation warning: ${partialErrorDetail})`;
|
|
3059
|
+
}
|
|
3060
|
+
if (partialEdits && autoCommitFailed) {
|
|
3061
|
+
return `Timed out at ${elapsedText}; partial edits detected but auto-commit failed`;
|
|
3062
|
+
}
|
|
3063
|
+
return `Timed out at ${elapsedText}; partial preservation warning: ${partialErrorDetail}`;
|
|
3064
|
+
}
|
|
3065
|
+
if (partialEdits) {
|
|
3066
|
+
return `Timed out at ${elapsedText}; partial edits preserved`;
|
|
3067
|
+
}
|
|
3068
|
+
return `Timed out at ${elapsedText}; no staged edits detected`;
|
|
3069
|
+
})();
|
|
3070
|
+
const partialResult = {
|
|
3071
|
+
variant: formatVariantIdentifier(variant),
|
|
3072
|
+
provider: variant.provider,
|
|
3073
|
+
model: variant.model,
|
|
3074
|
+
strategy: variant.strategy,
|
|
3075
|
+
executionMode: variant.executionMode,
|
|
3076
|
+
success: false,
|
|
3077
|
+
duration: Number(process.hrtime.bigint() - startTime) / 1e9,
|
|
3078
|
+
completeness,
|
|
3079
|
+
timedOut: true,
|
|
3080
|
+
partialEdits,
|
|
3081
|
+
terminationReason: "timeout",
|
|
3082
|
+
executionIntent: variant.executionIntent,
|
|
3083
|
+
isBasicCompetition: variant.isBasicCompetition,
|
|
3084
|
+
emptyChanges: !partialEdits,
|
|
3085
|
+
filesChanged,
|
|
3086
|
+
linesAdded,
|
|
3087
|
+
linesRemoved,
|
|
3088
|
+
worktreeDiff,
|
|
3089
|
+
gitMetrics: partialEdits ? {
|
|
3090
|
+
filesChanged,
|
|
3091
|
+
linesAdded,
|
|
3092
|
+
linesDeleted: linesRemoved,
|
|
3093
|
+
source: "worktree"
|
|
3094
|
+
} : void 0,
|
|
3095
|
+
worktreeSummary: timeoutWorktreePath ? {
|
|
3096
|
+
filesChanged,
|
|
3097
|
+
insertions: linesAdded,
|
|
3098
|
+
deletions: linesRemoved,
|
|
3099
|
+
statusLines
|
|
3100
|
+
} : void 0,
|
|
3101
|
+
retainedWorktreePath: timeoutWorktreePath,
|
|
3102
|
+
baselineRef,
|
|
3103
|
+
error: new Error(timeoutErrorMessage)
|
|
3104
|
+
};
|
|
3105
|
+
partialResult.outcome = deriveVariantOutcome(partialResult);
|
|
3106
|
+
sanitizeVariantResult(partialResult);
|
|
3107
|
+
await writeResultFileAtomically(partialResultFile, partialResult);
|
|
3108
|
+
};
|
|
3109
|
+
const executionAbortController = new AbortController();
|
|
3110
|
+
let shutdownRequested = false;
|
|
3111
|
+
let shutdownRequestedAtMs;
|
|
3112
|
+
let shutdownSignal;
|
|
3113
|
+
const signalExitCode = (signal) => signal === "SIGINT" ? 130 : 143;
|
|
3114
|
+
const requestShutdown = async (signal, options2) => {
|
|
3115
|
+
if (shutdownRequested) {
|
|
3116
|
+
return;
|
|
3117
|
+
}
|
|
3118
|
+
shutdownRequested = true;
|
|
3119
|
+
shutdownRequestedAtMs = elapsedMsSince(startTime);
|
|
3120
|
+
shutdownSignal = signal;
|
|
3121
|
+
if (signal === "SIGINT") {
|
|
3122
|
+
mergeExecutionContextEnvOverrides({ POETIC_PRESERVE_WORKTREES: "1" });
|
|
3123
|
+
}
|
|
3124
|
+
if (!executionAbortController.signal.aborted) {
|
|
3125
|
+
executionAbortController.abort(new Error(`Variant worker received ${signal}`));
|
|
3126
|
+
}
|
|
3127
|
+
const exitAfter = options2?.exitAfter ?? true;
|
|
3128
|
+
try {
|
|
3129
|
+
if (verbose) {
|
|
3130
|
+
console.log(chalk.yellow(`Received ${signal}; beginning timeout shutdown handling.`));
|
|
3131
|
+
}
|
|
3132
|
+
if (signal === "SIGTERM") {
|
|
3133
|
+
try {
|
|
3134
|
+
await runWithTimeout(persistTimeoutPartialResult(), 2500, "partial timeout save");
|
|
3135
|
+
if (verbose) {
|
|
3136
|
+
console.log(chalk.gray(` Partial timeout result written to ${partialResultFile}`));
|
|
3137
|
+
}
|
|
3138
|
+
} catch (error) {
|
|
3139
|
+
if (verbose) {
|
|
3140
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3141
|
+
console.warn(chalk.yellow(`Partial timeout preservation skipped: ${message}`));
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
if (exitAfter) {
|
|
3146
|
+
try {
|
|
3147
|
+
await runWithTimeout(
|
|
3148
|
+
signalCoordinator.runCleanupHandlers(`variant-worker ${signal.toLowerCase()}`, {
|
|
3149
|
+
timeoutMs: SIGNAL_CLEANUP_TIMEOUT_MS
|
|
3150
|
+
}),
|
|
3151
|
+
SIGNAL_CLEANUP_WRAPPER_TIMEOUT_MS,
|
|
3152
|
+
"signal cleanup"
|
|
3153
|
+
);
|
|
3154
|
+
} catch {
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
} catch (shutdownError) {
|
|
3158
|
+
console.error(
|
|
3159
|
+
`[variant-worker] Shutdown error (${signal}): ${shutdownError instanceof Error ? shutdownError.message : String(shutdownError)}`
|
|
3160
|
+
);
|
|
3161
|
+
} finally {
|
|
3162
|
+
if (exitAfter) {
|
|
3163
|
+
process.exit(signalExitCode(signal));
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
};
|
|
3167
|
+
postProviderShutdown = async (signal) => {
|
|
3168
|
+
await requestShutdown(signal, { exitAfter: false });
|
|
3169
|
+
};
|
|
3170
|
+
signalCoordinator.register(
|
|
3171
|
+
signalHandlerName,
|
|
3172
|
+
async (context) => {
|
|
3173
|
+
if (context.reason === "SIGTERM" || context.reason === "SIGINT") {
|
|
3174
|
+
await requestShutdown(context.reason, { exitAfter: false });
|
|
3175
|
+
}
|
|
3176
|
+
},
|
|
3177
|
+
{ priority: "critical", phase: "artifact-cleanup" }
|
|
3178
|
+
);
|
|
3179
|
+
if (variant.executionMode !== "local") {
|
|
3180
|
+
throw new Error(`Worker only supports local execution mode (got: ${variant.executionMode})`);
|
|
3181
|
+
}
|
|
3182
|
+
let prompt;
|
|
3183
|
+
try {
|
|
3184
|
+
prompt = await fs.readFile(promptFile, "utf-8");
|
|
3185
|
+
} catch (error) {
|
|
3186
|
+
throw new Error(
|
|
3187
|
+
`Failed to read prompt file: ${error instanceof Error ? error.message : String(error)}`
|
|
3188
|
+
);
|
|
3189
|
+
}
|
|
3190
|
+
if (verbose) {
|
|
3191
|
+
console.log(chalk.blue(`Loaded prompt (${prompt.length} chars)`));
|
|
3192
|
+
console.log(chalk.blue(`Executing ${variant.provider} ${variant.strategy}...`));
|
|
3193
|
+
}
|
|
3194
|
+
const repoRoot = process.cwd();
|
|
3195
|
+
const packageJsonScripts = await tryLoadPackageJsonScripts(repoRoot);
|
|
3196
|
+
const originalPrompt = getRuntimeEnv("POETIC_ORIGINAL_TASK_DESCRIPTION") ?? extractOriginalPrompt(prompt);
|
|
3197
|
+
const specInjectionUsed = /\[SPEC:/.test(prompt);
|
|
3198
|
+
const specSignal = evaluateExecutionSpec(originalPrompt);
|
|
3199
|
+
let guidanceTier;
|
|
3200
|
+
let specQualitySignals;
|
|
3201
|
+
if (specSignal) {
|
|
3202
|
+
guidanceTier = specSignal.guidanceTier;
|
|
3203
|
+
specQualitySignals = generateSpecQualitySignals(originalPrompt, repoRoot, packageJsonScripts);
|
|
3204
|
+
}
|
|
3205
|
+
const classification = classifyTask(prompt, { stripInjections: true });
|
|
3206
|
+
let telemetry = null;
|
|
3207
|
+
let telemetryEnabled = true;
|
|
3208
|
+
const telemetryWarning = { warned: false };
|
|
3209
|
+
const costTrackerWarning = { warned: false };
|
|
3210
|
+
const applyWorkerEnvOverrides = (overrides) => {
|
|
3211
|
+
mergeExecutionContextEnvOverrides(overrides);
|
|
3212
|
+
};
|
|
3213
|
+
const prevRetainBasicWorktree = getRuntimeEnv("POETIC_RETAIN_BASIC_WORKTREE");
|
|
3214
|
+
const prevPreserveWorktrees = getRuntimeEnv("POETIC_PRESERVE_WORKTREES");
|
|
3215
|
+
const preserveWorktreesForDebug = prevRetainBasicWorktree === "1" && getRuntimeEnv("POETIC_PRESERVE_WORKTREES") !== "1";
|
|
3216
|
+
if (preserveWorktreesForDebug) {
|
|
3217
|
+
applyWorkerEnvOverrides({ POETIC_PRESERVE_WORKTREES: "1" });
|
|
3218
|
+
}
|
|
3219
|
+
try {
|
|
3220
|
+
telemetry = TelemetryLogger.getInstance();
|
|
3221
|
+
telemetry.logVariantEvent(taskId, formatVariantIdentifier(variant), "started", {
|
|
3222
|
+
provider: variant.provider,
|
|
3223
|
+
model: variant.model,
|
|
3224
|
+
executionMode: variant.executionMode,
|
|
3225
|
+
strategy: variant.strategy,
|
|
3226
|
+
runMode,
|
|
3227
|
+
executionIntent: variant.executionIntent,
|
|
3228
|
+
...typeof guidanceTier === "number" ? { guidanceTier } : {},
|
|
3229
|
+
...specInjectionUsed ? { specInjectionUsed: true } : {},
|
|
3230
|
+
...Array.isArray(specQualitySignals) && specQualitySignals.length > 0 ? { specQualitySignals } : {},
|
|
3231
|
+
...classification.drift ? { classificationDrift: classification.drift } : {}
|
|
3232
|
+
});
|
|
3233
|
+
} catch (_err) {
|
|
3234
|
+
await warnOnce(
|
|
3235
|
+
"Telemetry unavailable for worker; continuing with JSON output only.",
|
|
3236
|
+
telemetryWarning
|
|
3237
|
+
);
|
|
3238
|
+
telemetryEnabled = false;
|
|
3239
|
+
}
|
|
3240
|
+
let result;
|
|
3241
|
+
let cleanupWorktreeAfterRun = false;
|
|
3242
|
+
let executionResultSettledAtMs;
|
|
3243
|
+
const providerOutputForwarder = createProviderOutputForwarder(
|
|
3244
|
+
getRuntimeEnv("POETIC_PROVIDER_OUTPUT_EVENTS") === "1"
|
|
3245
|
+
);
|
|
3246
|
+
try {
|
|
3247
|
+
const { GitHubPRManager } = await import("./github-pr-manager-4LXEKJTA.js");
|
|
3248
|
+
const { LocalExecutor } = await import("./local-executor-CEHWE2PL.js");
|
|
3249
|
+
if (repairLoopConfig.enabled && prevRetainBasicWorktree === void 0 && getRuntimeEnv("POETIC_RETAIN_BASIC_WORKTREE") !== "1") {
|
|
3250
|
+
applyWorkerEnvOverrides({ POETIC_RETAIN_BASIC_WORKTREE: "1" });
|
|
3251
|
+
cleanupWorktreeAfterRun = getRuntimeEnv("POETIC_PRESERVE_WORKTREES") !== "1";
|
|
3252
|
+
}
|
|
3253
|
+
let costTracker;
|
|
3254
|
+
if (telemetry) {
|
|
3255
|
+
try {
|
|
3256
|
+
const { UnifiedCostTracker } = await import("./unified-cost-tracker-4TFRBVPR.js");
|
|
3257
|
+
const { UniversalCostCalculator } = await import("./universal-cost-calculator-BAJV35GA.js");
|
|
3258
|
+
const costCalculator = new UniversalCostCalculator(telemetry);
|
|
3259
|
+
costTracker = new UnifiedCostTracker(costCalculator, telemetry, {
|
|
3260
|
+
enabled: true,
|
|
3261
|
+
budgetThresholds: {
|
|
3262
|
+
warning: 1,
|
|
3263
|
+
critical: 5,
|
|
3264
|
+
daily: 10,
|
|
3265
|
+
monthly: 100
|
|
3266
|
+
},
|
|
3267
|
+
optimization: {
|
|
3268
|
+
enableSuggestions: false,
|
|
3269
|
+
minSavingsThreshold: 1,
|
|
3270
|
+
maxSuggestionsPerHour: 0
|
|
3271
|
+
},
|
|
3272
|
+
updateIntervalMs: 6e4
|
|
3273
|
+
});
|
|
3274
|
+
} catch (costErr) {
|
|
3275
|
+
await warnOnce(
|
|
3276
|
+
"Cost tracking initialization failed in worker; continuing without a UnifiedCostTracker.",
|
|
3277
|
+
costTrackerWarning
|
|
3278
|
+
);
|
|
3279
|
+
if (verbose) {
|
|
3280
|
+
const message = costErr instanceof Error ? costErr.message : String(costErr);
|
|
3281
|
+
console.warn(chalk.yellow(` Cost tracking init error: ${message}`));
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
const prManager = new GitHubPRManager();
|
|
3286
|
+
const executor = new LocalExecutor({
|
|
3287
|
+
prManager,
|
|
3288
|
+
repoRoot,
|
|
3289
|
+
verbose: verbose ?? false,
|
|
3290
|
+
executorTimeoutMs: timeoutMinutes != null ? timeoutMinutes * 6e4 : void 0,
|
|
3291
|
+
idleWarnMs: idleWarnMinutes != null ? idleWarnMinutes * 6e4 : void 0,
|
|
3292
|
+
idleKillMs: idleKillMinutes != null ? idleKillMinutes * 6e4 : void 0,
|
|
3293
|
+
costTracker,
|
|
3294
|
+
resultStreamer: providerOutputForwarder.resultStreamer,
|
|
3295
|
+
onWorktreeReady: ({ worktreePath, baselineRef }) => {
|
|
3296
|
+
timeoutContext.worktreePath = worktreePath;
|
|
3297
|
+
timeoutContext.baselineRef = baselineRef;
|
|
3298
|
+
}
|
|
3299
|
+
});
|
|
3300
|
+
const executionBreadcrumb = createCliBreadcrumb({
|
|
3301
|
+
label: `Variant ${variant.provider}:${variant.strategy}`,
|
|
3302
|
+
detail: "execution started; this step can stay quiet while the provider works."
|
|
3303
|
+
});
|
|
3304
|
+
let execResult;
|
|
3305
|
+
try {
|
|
3306
|
+
execResult = await executor.executeVariant(taskId, variant, prompt, {
|
|
3307
|
+
abortSignal: executionAbortController.signal
|
|
3308
|
+
});
|
|
3309
|
+
executionResultSettledAtMs = elapsedMsSince(startTime);
|
|
3310
|
+
installPostProviderSignalHandlers();
|
|
3311
|
+
executionBreadcrumb.complete();
|
|
3312
|
+
} finally {
|
|
3313
|
+
executorSettled = true;
|
|
3314
|
+
executionBreadcrumb.dispose();
|
|
3315
|
+
}
|
|
3316
|
+
result = execResult;
|
|
3317
|
+
const postProviderPhaseDurationsMs = {};
|
|
3318
|
+
result.postProviderPhaseDurationsMs = postProviderPhaseDurationsMs;
|
|
3319
|
+
timeoutContext.worktreePath = result.retainedWorktreePath ?? timeoutContext.worktreePath;
|
|
3320
|
+
timeoutContext.baselineRef = result.baselineRef ?? timeoutContext.baselineRef;
|
|
3321
|
+
measurePostProviderPhaseSync(
|
|
3322
|
+
postProviderPhaseDurationsMs,
|
|
3323
|
+
"resultNormalization",
|
|
3324
|
+
verbose,
|
|
3325
|
+
() => {
|
|
3326
|
+
if (variant.executionIntent === "analysis" && result.success !== false && (result.completeness ?? 0) < MIN_ANALYSIS_COMPLETENESS) {
|
|
3327
|
+
const boosted = Math.max(result.completeness ?? 0, MIN_ANALYSIS_COMPLETENESS);
|
|
3328
|
+
result.completeness = boosted;
|
|
3329
|
+
result.completenessScore = {
|
|
3330
|
+
score: boosted,
|
|
3331
|
+
category: "analysis",
|
|
3332
|
+
fileTargetMatchRate: result.completenessScore?.fileTargetMatchRate
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
if (result.completeness < 0) {
|
|
3336
|
+
result.completeness = 0;
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
);
|
|
3340
|
+
const duration = Number(process.hrtime.bigint() - startTime) / 1e9;
|
|
3341
|
+
result.executionDurationMs = Math.round(duration * 1e3);
|
|
3342
|
+
await measurePostProviderPhase(
|
|
3343
|
+
postProviderPhaseDurationsMs,
|
|
3344
|
+
"gitMetrics",
|
|
3345
|
+
verbose,
|
|
3346
|
+
async () => ensureGitMetrics(result)
|
|
3347
|
+
);
|
|
3348
|
+
const isAnalysisIntent = variant.executionIntent === "analysis";
|
|
3349
|
+
let repairOutcomeForGuards = null;
|
|
3350
|
+
const hasRetainedRepairWorktree = typeof result.retainedWorktreePath === "string" && result.retainedWorktreePath.trim().length > 0;
|
|
3351
|
+
const hasRepairBaselineRef = typeof result.baselineRef === "string" && result.baselineRef.trim().length > 0;
|
|
3352
|
+
const warningCodes = getWarningCodes(result);
|
|
3353
|
+
const hasMissingFileTargets = warningCodes.has("WS-008");
|
|
3354
|
+
const filesChanged = result.gitMetrics?.filesChanged ?? result.filesChanged ?? 0;
|
|
3355
|
+
const hasMaterializedDeliverable = filesChanged > 0 || result.outcome?.delivery?.hasChanges === true;
|
|
3356
|
+
const outputText = `${result.result?.stdout ?? ""}
|
|
3357
|
+
${result.result?.stderr ?? ""}`;
|
|
3358
|
+
const isClarifyingNoOp = filesChanged === 0 && looksLikeClarifyingQuestion(outputText);
|
|
3359
|
+
const initialVerificationDisposition = result.verificationDisposition ?? getVerificationResult(result);
|
|
3360
|
+
const providerGatesAlreadyPassed = initialVerificationDisposition.outcome === "passed";
|
|
3361
|
+
const exactArtifactGate = await measurePostProviderPhase(
|
|
3362
|
+
postProviderPhaseDurationsMs,
|
|
3363
|
+
"exactArtifactGoalGate",
|
|
3364
|
+
verbose,
|
|
3365
|
+
async () => {
|
|
3366
|
+
if (!hasRetainedRepairWorktree) {
|
|
3367
|
+
return { success: result.success === true };
|
|
3368
|
+
}
|
|
3369
|
+
return applyExactArtifactGoalSuccessGate({
|
|
3370
|
+
prompt,
|
|
3371
|
+
worktreePath: result.retainedWorktreePath,
|
|
3372
|
+
success: result.success === true
|
|
3373
|
+
});
|
|
3374
|
+
}
|
|
3375
|
+
);
|
|
3376
|
+
if (result.success === true && !exactArtifactGate.success) {
|
|
3377
|
+
result.success = false;
|
|
3378
|
+
result.completeness = 0;
|
|
3379
|
+
result.error = new Error(
|
|
3380
|
+
exactArtifactGate.failureReason ?? "Exact artifact content mismatch"
|
|
3381
|
+
);
|
|
3382
|
+
}
|
|
3383
|
+
const validationArtifactSatisfied = await measurePostProviderPhase(
|
|
3384
|
+
postProviderPhaseDurationsMs,
|
|
3385
|
+
"validationArtifactCheck",
|
|
3386
|
+
verbose,
|
|
3387
|
+
async () => {
|
|
3388
|
+
if (!hasRetainedRepairWorktree || result.success !== true) return false;
|
|
3389
|
+
const simpleArtifactGoal = parseSimpleArtifactGoal(prompt);
|
|
3390
|
+
if (simpleArtifactGoal) {
|
|
3391
|
+
return isSimpleArtifactGoalSatisfied(result.retainedWorktreePath, simpleArtifactGoal);
|
|
3392
|
+
}
|
|
3393
|
+
return isValidationArtifactOnlyDiffSatisfied({
|
|
3394
|
+
prompt,
|
|
3395
|
+
worktreePath: result.retainedWorktreePath,
|
|
3396
|
+
baselineRef: result.baselineRef
|
|
3397
|
+
});
|
|
3398
|
+
}
|
|
3399
|
+
);
|
|
3400
|
+
const repairAdmission = measurePostProviderPhaseSync(
|
|
3401
|
+
postProviderPhaseDurationsMs,
|
|
3402
|
+
"repairAdmission",
|
|
3403
|
+
verbose,
|
|
3404
|
+
() => {
|
|
3405
|
+
return decideRepairAdmission({
|
|
3406
|
+
repairLoopEnabled: repairLoopConfig.enabled,
|
|
3407
|
+
isAnalysisIntent,
|
|
3408
|
+
hasRetainedWorktree: hasRetainedRepairWorktree,
|
|
3409
|
+
hasBaselineRef: hasRepairBaselineRef,
|
|
3410
|
+
initialVerificationOutcome: initialVerificationDisposition.outcome,
|
|
3411
|
+
providerGatesAlreadyPassed,
|
|
3412
|
+
hasMissingFileTargets,
|
|
3413
|
+
isClarifyingNoOp,
|
|
3414
|
+
providerExecutionSucceeded: result.success === true,
|
|
3415
|
+
hasMaterializedDeliverable,
|
|
3416
|
+
validationArtifactSatisfied,
|
|
3417
|
+
filesChanged
|
|
3418
|
+
});
|
|
3419
|
+
}
|
|
3420
|
+
);
|
|
3421
|
+
result.repairAdmission = repairAdmission;
|
|
3422
|
+
if (repairLoopConfig.enabled && result.repairAdmission.action === "skip" && (result.repairAdmission.reason === "skip_passing_gates" || result.repairAdmission.reason === "skip_clarifying_noop")) {
|
|
3423
|
+
emitWarning(
|
|
3424
|
+
"PERF-003",
|
|
3425
|
+
{
|
|
3426
|
+
taskId,
|
|
3427
|
+
subsystem: "repair-loop",
|
|
3428
|
+
executionPhase: "validation"
|
|
3429
|
+
},
|
|
3430
|
+
{
|
|
3431
|
+
metadata: {
|
|
3432
|
+
reason: result.repairAdmission.summary,
|
|
3433
|
+
admissionReason: result.repairAdmission.reason
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
);
|
|
3437
|
+
}
|
|
3438
|
+
if (result.repairAdmission.action === "run") {
|
|
3439
|
+
let repairOutcome = null;
|
|
3440
|
+
let preRepairDiffSnapshot;
|
|
3441
|
+
{
|
|
3442
|
+
const worktreePath = result.retainedWorktreePath;
|
|
3443
|
+
const baselineRef = result.baselineRef;
|
|
3444
|
+
const initialCostUsd = result.costMetrics?.totalCost ?? result.result?.totalCostUsd ?? 0;
|
|
3445
|
+
preRepairDiffSnapshot = await measurePostProviderPhase(
|
|
3446
|
+
postProviderPhaseDurationsMs,
|
|
3447
|
+
"preRepairDiffSnapshot",
|
|
3448
|
+
verbose,
|
|
3449
|
+
async () => captureRepairDiffSnapshot(worktreePath, baselineRef)
|
|
3450
|
+
);
|
|
3451
|
+
if (!preRepairDiffSnapshot) {
|
|
3452
|
+
emitWarning(
|
|
3453
|
+
"PERF-003",
|
|
3454
|
+
{
|
|
3455
|
+
taskId,
|
|
3456
|
+
subsystem: "repair-loop",
|
|
3457
|
+
executionPhase: "validation"
|
|
3458
|
+
},
|
|
3459
|
+
{
|
|
3460
|
+
metadata: {
|
|
3461
|
+
reason: "pre-repair diff snapshot unavailable; repair-scope classification will be unknown",
|
|
3462
|
+
baselineRef,
|
|
3463
|
+
worktreePath
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
);
|
|
3467
|
+
}
|
|
3468
|
+
const repoRoot2 = process.cwd();
|
|
3469
|
+
const cacheDir = path.join(repoRoot2, ".poetic", "cache", "verification-baseline");
|
|
3470
|
+
const repairChangedFiles = await taskScopedChangedFiles(worktreePath, baselineRef);
|
|
3471
|
+
const baselineForRepair = await measurePostProviderPhase(
|
|
3472
|
+
postProviderPhaseDurationsMs,
|
|
3473
|
+
"verificationBaselineCapture",
|
|
3474
|
+
verbose,
|
|
3475
|
+
async () => getOrCaptureVerificationBaseline({
|
|
3476
|
+
repoRoot: repoRoot2,
|
|
3477
|
+
baselineRef,
|
|
3478
|
+
cacheDir,
|
|
3479
|
+
captureOptions: { gateTimeoutMs: 6e4, changedFiles: repairChangedFiles }
|
|
3480
|
+
}).catch(() => null)
|
|
3481
|
+
);
|
|
3482
|
+
const baselineSucceeded = !!baselineForRepair?.metadata?.captureSucceeded;
|
|
3483
|
+
const allowedFiles = (() => {
|
|
3484
|
+
try {
|
|
3485
|
+
const spec = parsePromptSpec(prompt);
|
|
3486
|
+
return Array.isArray(spec.fileConstraint.targets) ? spec.fileConstraint.targets : [];
|
|
3487
|
+
} catch {
|
|
3488
|
+
return [];
|
|
3489
|
+
}
|
|
3490
|
+
})();
|
|
3491
|
+
repairOutcome = await measurePostProviderPhase(
|
|
3492
|
+
postProviderPhaseDurationsMs,
|
|
3493
|
+
"repairLoop",
|
|
3494
|
+
verbose,
|
|
3495
|
+
async () => runVerificationDrivenRepairLoop({
|
|
3496
|
+
config: {
|
|
3497
|
+
enabled: true,
|
|
3498
|
+
maxIterations: Math.max(1, Math.floor(repairLoopConfig.maxIterations)),
|
|
3499
|
+
iterationTimeoutMs: Math.max(
|
|
3500
|
+
1e3,
|
|
3501
|
+
Math.floor(repairLoopConfig.iterationTimeoutMs)
|
|
3502
|
+
),
|
|
3503
|
+
abortIfRegression: repairLoopConfig.abortIfRegression,
|
|
3504
|
+
allowApiBreak: repairLoopConfig.allowApiBreak,
|
|
3505
|
+
repairMode: repairLoopConfig.repairMode,
|
|
3506
|
+
scopePolicy: repairLoopConfig.scopePolicy,
|
|
3507
|
+
repairSaveModes: repairLoopConfig.repairSaveModes,
|
|
3508
|
+
allowedFiles,
|
|
3509
|
+
// Default to true for software-aware judges (Hybrid, Multi-lens).
|
|
3510
|
+
// This enables test failure detection in regression checks.
|
|
3511
|
+
checkTestGate: true
|
|
3512
|
+
},
|
|
3513
|
+
worktreePath,
|
|
3514
|
+
baselineRef,
|
|
3515
|
+
baseline: baselineSucceeded ? baselineForRepair : void 0,
|
|
3516
|
+
taskId,
|
|
3517
|
+
variantId: formatVariantIdentifier(variant),
|
|
3518
|
+
originalPrompt: prompt,
|
|
3519
|
+
initialCostUsd: Number.isFinite(initialCostUsd) && initialCostUsd > 0 ? initialCostUsd : 0,
|
|
3520
|
+
executeRepair: async (repairPrompt, iterationIndex, abortSignal) => {
|
|
3521
|
+
const repairTaskId = `${taskId}-verify-${iterationIndex + 1}`;
|
|
3522
|
+
const repairVariant = {
|
|
3523
|
+
...variant,
|
|
3524
|
+
// Enforce same worktree + prevent auto-commit drift during repair loops.
|
|
3525
|
+
workflowWorktreePath: worktreePath,
|
|
3526
|
+
disableAutoCommit: true,
|
|
3527
|
+
isBasicCompetition: true
|
|
3528
|
+
};
|
|
3529
|
+
const repairResult = await executor.executeVariant(
|
|
3530
|
+
repairTaskId,
|
|
3531
|
+
repairVariant,
|
|
3532
|
+
repairPrompt,
|
|
3533
|
+
{ abortSignal }
|
|
3534
|
+
);
|
|
3535
|
+
const costUsd = repairResult.costMetrics?.totalCost ?? repairResult.result?.totalCostUsd ?? 0;
|
|
3536
|
+
return { costUsd: Number.isFinite(costUsd) ? costUsd : 0 };
|
|
3537
|
+
}
|
|
3538
|
+
})
|
|
3539
|
+
);
|
|
3540
|
+
repairOutcomeForGuards = repairOutcome;
|
|
3541
|
+
result.verification = repairOutcome.verification;
|
|
3542
|
+
result.verificationLoop = repairOutcome.verificationLoop;
|
|
3543
|
+
}
|
|
3544
|
+
if (repairOutcome) {
|
|
3545
|
+
const { verificationLoop } = repairOutcome;
|
|
3546
|
+
const shouldPromoteRepairs = verificationLoop.iterations.length > 0 && verificationLoop.bestIterationPromotable;
|
|
3547
|
+
if (!shouldPromoteRepairs) {
|
|
3548
|
+
} else {
|
|
3549
|
+
cleanupWorktreeAfterRun = false;
|
|
3550
|
+
try {
|
|
3551
|
+
const worktreePath = result.retainedWorktreePath;
|
|
3552
|
+
const baselineRef = result.baselineRef;
|
|
3553
|
+
const iterCount = verificationLoop.iterations.length;
|
|
3554
|
+
let commitSucceeded = false;
|
|
3555
|
+
const status = await measurePostProviderPhase(
|
|
3556
|
+
postProviderPhaseDurationsMs,
|
|
3557
|
+
"repairPromotionStatus",
|
|
3558
|
+
verbose,
|
|
3559
|
+
async () => execa3("git", ["status", "--porcelain"], {
|
|
3560
|
+
cwd: worktreePath,
|
|
3561
|
+
stdio: "pipe",
|
|
3562
|
+
reject: false
|
|
3563
|
+
})
|
|
3564
|
+
);
|
|
3565
|
+
const isDirty = String(status.stdout ?? "").trim().length > 0;
|
|
3566
|
+
const repairedSummary = await measurePostProviderPhase(
|
|
3567
|
+
postProviderPhaseDurationsMs,
|
|
3568
|
+
"repairPromotionChangeSummary",
|
|
3569
|
+
verbose,
|
|
3570
|
+
async () => getGitChangeSummary(worktreePath, baselineRef, true)
|
|
3571
|
+
);
|
|
3572
|
+
const finalDiffSnapshot = await measurePostProviderPhase(
|
|
3573
|
+
postProviderPhaseDurationsMs,
|
|
3574
|
+
"repairPromotionDiffSnapshot",
|
|
3575
|
+
verbose,
|
|
3576
|
+
async () => captureRepairDiffSnapshot(worktreePath, baselineRef)
|
|
3577
|
+
);
|
|
3578
|
+
if (!finalDiffSnapshot) {
|
|
3579
|
+
emitWarning(
|
|
3580
|
+
"PERF-003",
|
|
3581
|
+
{
|
|
3582
|
+
taskId,
|
|
3583
|
+
subsystem: "repair-loop",
|
|
3584
|
+
executionPhase: "validation"
|
|
3585
|
+
},
|
|
3586
|
+
{
|
|
3587
|
+
metadata: {
|
|
3588
|
+
reason: "post-repair diff snapshot unavailable; repair-scope classification will be unknown",
|
|
3589
|
+
baselineRef,
|
|
3590
|
+
worktreePath
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
);
|
|
3594
|
+
}
|
|
3595
|
+
const repairScopeAnalysis = analyzeRepairScope({
|
|
3596
|
+
preRepairDiff: preRepairDiffSnapshot,
|
|
3597
|
+
finalDiff: finalDiffSnapshot
|
|
3598
|
+
});
|
|
3599
|
+
const repairPolicy = resolveRepairPromotionPolicy({
|
|
3600
|
+
classification: repairScopeAnalysis.classification,
|
|
3601
|
+
repairDelta: repairScopeAnalysis.repairDelta,
|
|
3602
|
+
scopePolicy: repairLoopConfig.scopePolicy,
|
|
3603
|
+
effectiveMode: repairPolicyCompletionMode(),
|
|
3604
|
+
allowOffScopeRepair: isOffScopeRepairOverrideEnabled()
|
|
3605
|
+
});
|
|
3606
|
+
const allowRepairCommit = repairPolicy.action === "allow" || repairPolicy.action === "warn";
|
|
3607
|
+
if (isDirty && !allowRepairCommit) {
|
|
3608
|
+
emitWarning(
|
|
3609
|
+
"PERF-003",
|
|
3610
|
+
{
|
|
3611
|
+
taskId,
|
|
3612
|
+
subsystem: "repair-loop",
|
|
3613
|
+
executionPhase: "validation"
|
|
3614
|
+
},
|
|
3615
|
+
{
|
|
3616
|
+
metadata: {
|
|
3617
|
+
reason: `Skipped repair commit: ${repairPolicy.reason}`,
|
|
3618
|
+
policyAction: repairPolicy.action,
|
|
3619
|
+
classification: repairScopeAnalysis.classification,
|
|
3620
|
+
finalArtifactTrust: repairPolicy.finalArtifactTrust,
|
|
3621
|
+
worktreePath
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
);
|
|
3625
|
+
} else if (isDirty) {
|
|
3626
|
+
const add = await measurePostProviderPhase(
|
|
3627
|
+
postProviderPhaseDurationsMs,
|
|
3628
|
+
"repairPromotionStage",
|
|
3629
|
+
verbose,
|
|
3630
|
+
async () => execa3("git", ["add", "-A"], {
|
|
3631
|
+
cwd: worktreePath,
|
|
3632
|
+
stdio: "pipe",
|
|
3633
|
+
reject: false
|
|
3634
|
+
})
|
|
3635
|
+
);
|
|
3636
|
+
if ((add.exitCode ?? 0) !== 0 && verbose) {
|
|
3637
|
+
console.warn(
|
|
3638
|
+
chalk.yellow(
|
|
3639
|
+
`Failed to stage repairs (exit ${add.exitCode ?? "unknown"}); preserving worktree for manual commit.`
|
|
3640
|
+
)
|
|
3641
|
+
);
|
|
3642
|
+
}
|
|
3643
|
+
const isFullyClean = verificationLoop.passed;
|
|
3644
|
+
const commitMsg = isFullyClean ? `fix: repair-loop (${iterCount} iterations) - all gates pass` : `fix: repair-loop (${iterCount} iterations) - no new regressions vs baseline`;
|
|
3645
|
+
const commit = await measurePostProviderPhase(
|
|
3646
|
+
postProviderPhaseDurationsMs,
|
|
3647
|
+
"repairPromotionCommit",
|
|
3648
|
+
verbose,
|
|
3649
|
+
async () => execa3("git", ["commit", "--no-verify", "-m", commitMsg], {
|
|
3650
|
+
cwd: worktreePath,
|
|
3651
|
+
stdio: "pipe",
|
|
3652
|
+
reject: false
|
|
3653
|
+
})
|
|
3654
|
+
);
|
|
3655
|
+
commitSucceeded = (commit.exitCode ?? 0) === 0;
|
|
3656
|
+
if ((commit.exitCode ?? 0) !== 0 && verbose) {
|
|
3657
|
+
const output = String(commit.stdout ?? commit.stderr ?? "").trim();
|
|
3658
|
+
console.warn(
|
|
3659
|
+
chalk.yellow(
|
|
3660
|
+
`Failed to commit repairs (exit ${commit.exitCode ?? "unknown"}); preserving worktree for manual commit.${output ? `
|
|
3661
|
+
${output}` : ""}`
|
|
3662
|
+
)
|
|
3663
|
+
);
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
result.repairOutcome = {
|
|
3667
|
+
attempted: true,
|
|
3668
|
+
passed: verificationLoop.passed,
|
|
3669
|
+
passedBaseline: verificationLoop.passedBaseline,
|
|
3670
|
+
iterations: verificationLoop.iterations.length,
|
|
3671
|
+
stopReason: verificationLoop.stopReason,
|
|
3672
|
+
filesChanged: repairedSummary.filesChanged,
|
|
3673
|
+
committed: commitSucceeded,
|
|
3674
|
+
repairTrigger: buildRepairTriggerEvidence(repairOutcome),
|
|
3675
|
+
...repairScopeAnalysis.preRepairDiff ? { preRepairDiff: repairScopeAnalysis.preRepairDiff } : {},
|
|
3676
|
+
...repairScopeAnalysis.finalDiff ? { finalDiff: repairScopeAnalysis.finalDiff } : {},
|
|
3677
|
+
...repairScopeAnalysis.repairDelta ? { repairDelta: repairScopeAnalysis.repairDelta } : {},
|
|
3678
|
+
classification: repairScopeAnalysis.classification,
|
|
3679
|
+
judgeVerdictApplicability: repairScopeAnalysis.judgeVerdictApplicability,
|
|
3680
|
+
finalArtifactTrust: repairPolicy.finalArtifactTrust,
|
|
3681
|
+
policy: repairPolicy
|
|
3682
|
+
};
|
|
3683
|
+
if (repairedSummary.filesChanged > 0) {
|
|
3684
|
+
result.gitMetrics = {
|
|
3685
|
+
filesChanged: repairedSummary.filesChanged,
|
|
3686
|
+
linesAdded: repairedSummary.linesAdded,
|
|
3687
|
+
linesDeleted: repairedSummary.linesRemoved,
|
|
3688
|
+
source: repairedSummary.source === "fallback" ? "unknown" : "worktree"
|
|
3689
|
+
};
|
|
3690
|
+
}
|
|
3691
|
+
if (repairedSummary.filesChanged > 0) {
|
|
3692
|
+
result.emptyChanges = false;
|
|
3693
|
+
}
|
|
3694
|
+
if (verbose) {
|
|
3695
|
+
const baselineStatus = verificationLoop.passed ? "all gates pass" : "no new regressions";
|
|
3696
|
+
console.log(
|
|
3697
|
+
chalk.green(
|
|
3698
|
+
commitSucceeded ? `Verification repairs committed (${iterCount} iterations, ${repairedSummary.filesChanged} files, ${baselineStatus})` : `Verification repairs preserved (${iterCount} iterations, ${repairedSummary.filesChanged} files, ${baselineStatus})`
|
|
3699
|
+
)
|
|
3700
|
+
);
|
|
3701
|
+
}
|
|
3702
|
+
} catch (commitError) {
|
|
3703
|
+
if (verbose) {
|
|
3704
|
+
const msg = commitError instanceof Error ? commitError.message : String(commitError);
|
|
3705
|
+
console.warn(chalk.yellow(`Failed to commit repairs: ${msg}`));
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
measurePostProviderPhaseSync(
|
|
3712
|
+
postProviderPhaseDurationsMs,
|
|
3713
|
+
"verifiedTouchedTestGuard",
|
|
3714
|
+
verbose,
|
|
3715
|
+
() => applyVerifiedTouchedTestFailureGuard(result, repairOutcomeForGuards)
|
|
3716
|
+
);
|
|
3717
|
+
if (result.gitMetrics && result.gitMetrics.filesChanged > 0) {
|
|
3718
|
+
result.filesChanged = result.gitMetrics.filesChanged;
|
|
3719
|
+
result.linesAdded = result.gitMetrics.linesAdded;
|
|
3720
|
+
result.linesRemoved = result.gitMetrics.linesDeleted;
|
|
3721
|
+
}
|
|
3722
|
+
const workspaceEditsApplied = result.workspaceEditsApplied ?? 0;
|
|
3723
|
+
if (result.gitMetrics && result.gitMetrics.filesChanged > 0 && (result.completeness ?? 0) === 0 && workspaceEditsApplied === 0) {
|
|
3724
|
+
console.warn(
|
|
3725
|
+
chalk.yellow(
|
|
3726
|
+
"[Telemetry] Git shows changes but completeness is 0 and no workspace edits were applied."
|
|
3727
|
+
),
|
|
3728
|
+
{
|
|
3729
|
+
filesChanged: result.gitMetrics.filesChanged,
|
|
3730
|
+
linesAdded: result.gitMetrics.linesAdded,
|
|
3731
|
+
linesDeleted: result.gitMetrics.linesDeleted,
|
|
3732
|
+
provider: variant.provider,
|
|
3733
|
+
strategy: variant.strategy,
|
|
3734
|
+
executionIntent: variant.executionIntent
|
|
3735
|
+
}
|
|
3736
|
+
);
|
|
3737
|
+
}
|
|
3738
|
+
if (!result.verificationLoop && validationArtifactSatisfied !== true && !isAnalysisIntent && result.success === true && hasMaterializedDeliverable && typeof result.retainedWorktreePath === "string" && result.retainedWorktreePath.trim().length > 0 && typeof result.baselineRef === "string" && result.baselineRef.trim().length > 0) {
|
|
3739
|
+
const worktreePath = result.retainedWorktreePath.trim();
|
|
3740
|
+
const baselineRef = result.baselineRef.trim();
|
|
3741
|
+
const repoRoot2 = process.cwd();
|
|
3742
|
+
const cacheDir = path.join(repoRoot2, ".poetic", "cache", "verification-baseline");
|
|
3743
|
+
const verificationChangedFiles = await taskScopedChangedFiles(worktreePath, baselineRef);
|
|
3744
|
+
const baselineForVerification = await measurePostProviderPhase(
|
|
3745
|
+
postProviderPhaseDurationsMs,
|
|
3746
|
+
"verificationBaselineCapture",
|
|
3747
|
+
verbose,
|
|
3748
|
+
async () => getOrCaptureVerificationBaseline({
|
|
3749
|
+
repoRoot: repoRoot2,
|
|
3750
|
+
baselineRef,
|
|
3751
|
+
cacheDir,
|
|
3752
|
+
captureOptions: { gateTimeoutMs: 6e4, changedFiles: verificationChangedFiles }
|
|
3753
|
+
}).catch(() => null)
|
|
3754
|
+
);
|
|
3755
|
+
const verificationOutcome = await measurePostProviderPhase(
|
|
3756
|
+
postProviderPhaseDurationsMs,
|
|
3757
|
+
"deterministicVerification",
|
|
3758
|
+
verbose,
|
|
3759
|
+
async () => runDeterministicVerificationOnce({
|
|
3760
|
+
worktreePath,
|
|
3761
|
+
baselineRef,
|
|
3762
|
+
timeoutMs: Math.max(5e3, Math.floor(repairLoopConfig.iterationTimeoutMs)),
|
|
3763
|
+
baseline: baselineForVerification?.metadata?.captureSucceeded ? baselineForVerification : void 0,
|
|
3764
|
+
checkTestGate: true,
|
|
3765
|
+
// Same scope as the baseline capture above (symmetry invariant).
|
|
3766
|
+
changedFiles: verificationChangedFiles
|
|
3767
|
+
})
|
|
3768
|
+
);
|
|
3769
|
+
result.verification = verificationOutcome.verification;
|
|
3770
|
+
result.verificationLoop = verificationOutcome.verificationLoop;
|
|
3771
|
+
}
|
|
3772
|
+
try {
|
|
3773
|
+
await measurePostProviderPhase(
|
|
3774
|
+
postProviderPhaseDurationsMs,
|
|
3775
|
+
"baselineComparison",
|
|
3776
|
+
verbose,
|
|
3777
|
+
async () => attachBaselineComparisonIfAvailable(result)
|
|
3778
|
+
);
|
|
3779
|
+
} catch {
|
|
3780
|
+
}
|
|
3781
|
+
try {
|
|
3782
|
+
await measurePostProviderPhase(
|
|
3783
|
+
postProviderPhaseDurationsMs,
|
|
3784
|
+
"registryDowngrade",
|
|
3785
|
+
verbose,
|
|
3786
|
+
async () => applyRegistryDowngradeIfApplicable(result)
|
|
3787
|
+
);
|
|
3788
|
+
} catch {
|
|
3789
|
+
}
|
|
3790
|
+
measurePostProviderPhaseSync(
|
|
3791
|
+
postProviderPhaseDurationsMs,
|
|
3792
|
+
"outcomeDerivation",
|
|
3793
|
+
verbose,
|
|
3794
|
+
() => {
|
|
3795
|
+
result.outcome = deriveVariantOutcome(result);
|
|
3796
|
+
result.verificationDisposition = getVerificationResult(result);
|
|
3797
|
+
}
|
|
3798
|
+
);
|
|
3799
|
+
if (specSignal) {
|
|
3800
|
+
result.inputSpecWarning = {
|
|
3801
|
+
completenessScore: specSignal.completenessScore,
|
|
3802
|
+
guidanceTier: specSignal.guidanceTier,
|
|
3803
|
+
missingFields: [...specSignal.missingFields],
|
|
3804
|
+
hasFillPlaceholders: specSignal.hasFillPlaceholders
|
|
3805
|
+
};
|
|
3806
|
+
}
|
|
3807
|
+
if (verbose) {
|
|
3808
|
+
console.log(
|
|
3809
|
+
chalk.green(`Execution completed in ${duration.toFixed(2)}s (success: ${result.success})`)
|
|
3810
|
+
);
|
|
3811
|
+
console.log(chalk.gray(` ${formatVerboseWorkerCompletionMetric(variant, result)}`));
|
|
3812
|
+
if (specSignal) {
|
|
3813
|
+
const missingNames = specSignal.missingFields.map(
|
|
3814
|
+
(field) => FIELD_DISPLAY_NAMES[field] ?? field
|
|
3815
|
+
);
|
|
3816
|
+
console.log(
|
|
3817
|
+
chalk.yellow(
|
|
3818
|
+
` Note: execution started from an incomplete spec (${Math.round(specSignal.completenessScore * 100)}% complete; missing: ${missingNames.join(", ")})`
|
|
3819
|
+
)
|
|
3820
|
+
);
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
if (telemetryEnabled && telemetry) {
|
|
3824
|
+
try {
|
|
3825
|
+
const processReliability = computeProcessReliabilityMetrics({
|
|
3826
|
+
success: result.success,
|
|
3827
|
+
result: result.result
|
|
3828
|
+
});
|
|
3829
|
+
let terminationReason;
|
|
3830
|
+
if (result.terminationReason) {
|
|
3831
|
+
terminationReason = result.terminationReason;
|
|
3832
|
+
} else if (result.success) {
|
|
3833
|
+
terminationReason = "completed";
|
|
3834
|
+
} else if (result.timedOut) {
|
|
3835
|
+
terminationReason = "timeout";
|
|
3836
|
+
} else {
|
|
3837
|
+
terminationReason = "failed_error";
|
|
3838
|
+
}
|
|
3839
|
+
telemetry.logVariantEvent(taskId, formatVariantIdentifier(variant), "completed", {
|
|
3840
|
+
success: result.success,
|
|
3841
|
+
completeness: result.completeness,
|
|
3842
|
+
duration,
|
|
3843
|
+
provider: variant.provider,
|
|
3844
|
+
model: variant.model,
|
|
3845
|
+
executionMode: variant.executionMode,
|
|
3846
|
+
strategy: variant.strategy,
|
|
3847
|
+
runMode,
|
|
3848
|
+
executionIntent: variant.executionIntent,
|
|
3849
|
+
behavioralMetrics: { processReliability },
|
|
3850
|
+
terminationReason
|
|
3851
|
+
});
|
|
3852
|
+
const verdict = result.success ? "success" : "failure";
|
|
3853
|
+
telemetry.logVariantEvent(taskId, formatVariantIdentifier(variant), "evaluated", {
|
|
3854
|
+
verdict,
|
|
3855
|
+
completeness: result.completeness,
|
|
3856
|
+
codeDelivered: result.success && result.completeness > 0,
|
|
3857
|
+
provider: variant.provider,
|
|
3858
|
+
model: variant.model,
|
|
3859
|
+
executionMode: variant.executionMode,
|
|
3860
|
+
strategy: variant.strategy,
|
|
3861
|
+
runMode,
|
|
3862
|
+
executionIntent: variant.executionIntent
|
|
3863
|
+
});
|
|
3864
|
+
await measurePostProviderPhase(
|
|
3865
|
+
postProviderPhaseDurationsMs,
|
|
3866
|
+
"telemetryArtifactWrite",
|
|
3867
|
+
verbose,
|
|
3868
|
+
async () => {
|
|
3869
|
+
const artifactResult = await telemetry.logVariantResultWithArtifact(
|
|
3870
|
+
taskId,
|
|
3871
|
+
formatVariantIdentifier(variant),
|
|
3872
|
+
result,
|
|
3873
|
+
{
|
|
3874
|
+
provider: variant.provider,
|
|
3875
|
+
model: variant.model,
|
|
3876
|
+
executionMode: variant.executionMode,
|
|
3877
|
+
runMode,
|
|
3878
|
+
executionIntent: variant.executionIntent
|
|
3879
|
+
}
|
|
3880
|
+
);
|
|
3881
|
+
if (artifactResult?.success && artifactResult.manifestPath) {
|
|
3882
|
+
result.artifactManifest = artifactResult.manifestPath;
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
);
|
|
3886
|
+
} catch (_err) {
|
|
3887
|
+
await warnOnce(
|
|
3888
|
+
"Telemetry logging failed mid-run; disabling telemetry for this worker.",
|
|
3889
|
+
telemetryWarning
|
|
3890
|
+
);
|
|
3891
|
+
telemetryEnabled = false;
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
} catch (error) {
|
|
3895
|
+
const duration = Number(process.hrtime.bigint() - startTime) / 1e9;
|
|
3896
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
3897
|
+
result = {
|
|
3898
|
+
variant: formatVariantIdentifier(variant),
|
|
3899
|
+
provider: variant.provider,
|
|
3900
|
+
model: variant.model,
|
|
3901
|
+
strategy: variant.strategy,
|
|
3902
|
+
executionMode: variant.executionMode,
|
|
3903
|
+
success: false,
|
|
3904
|
+
error: errorObj,
|
|
3905
|
+
duration,
|
|
3906
|
+
completeness: 0,
|
|
3907
|
+
executionIntent: variant.executionIntent,
|
|
3908
|
+
isBasicCompetition: variant.isBasicCompetition,
|
|
3909
|
+
// Record requested prompt mode even on failure
|
|
3910
|
+
promptModeRequested: variant.promptMode,
|
|
3911
|
+
...specSignal ? {
|
|
3912
|
+
inputSpecWarning: {
|
|
3913
|
+
completenessScore: specSignal.completenessScore,
|
|
3914
|
+
guidanceTier: specSignal.guidanceTier,
|
|
3915
|
+
missingFields: [...specSignal.missingFields],
|
|
3916
|
+
hasFillPlaceholders: specSignal.hasFillPlaceholders
|
|
3917
|
+
}
|
|
3918
|
+
} : {}
|
|
3919
|
+
};
|
|
3920
|
+
const errorTerminationReason = result.terminationReason || (result.timedOut ? "timeout" : "failed_crash");
|
|
3921
|
+
result.terminationReason = errorTerminationReason;
|
|
3922
|
+
result.outcome = deriveVariantOutcome(result);
|
|
3923
|
+
if (telemetryEnabled && telemetry) {
|
|
3924
|
+
try {
|
|
3925
|
+
const processReliability = computeProcessReliabilityMetrics({
|
|
3926
|
+
success: false,
|
|
3927
|
+
result: result.result
|
|
3928
|
+
});
|
|
3929
|
+
telemetry.logVariantEvent(taskId, formatVariantIdentifier(variant), "completed", {
|
|
3930
|
+
success: false,
|
|
3931
|
+
completeness: 0,
|
|
3932
|
+
duration,
|
|
3933
|
+
provider: variant.provider,
|
|
3934
|
+
model: variant.model,
|
|
3935
|
+
executionMode: variant.executionMode,
|
|
3936
|
+
strategy: variant.strategy,
|
|
3937
|
+
runMode,
|
|
3938
|
+
executionIntent: variant.executionIntent,
|
|
3939
|
+
behavioralMetrics: { processReliability },
|
|
3940
|
+
terminationReason: errorTerminationReason
|
|
3941
|
+
});
|
|
3942
|
+
telemetry.logVariantEvent(taskId, formatVariantIdentifier(variant), "evaluated", {
|
|
3943
|
+
verdict: "failure",
|
|
3944
|
+
completeness: 0,
|
|
3945
|
+
codeDelivered: false,
|
|
3946
|
+
provider: variant.provider,
|
|
3947
|
+
model: variant.model,
|
|
3948
|
+
executionMode: variant.executionMode,
|
|
3949
|
+
strategy: variant.strategy,
|
|
3950
|
+
runMode,
|
|
3951
|
+
executionIntent: variant.executionIntent
|
|
3952
|
+
});
|
|
3953
|
+
const artifactResult = await telemetry.logVariantResultWithArtifact(
|
|
3954
|
+
taskId,
|
|
3955
|
+
formatVariantIdentifier(variant),
|
|
3956
|
+
result,
|
|
3957
|
+
{
|
|
3958
|
+
provider: variant.provider,
|
|
3959
|
+
model: variant.model,
|
|
3960
|
+
executionMode: variant.executionMode,
|
|
3961
|
+
runMode,
|
|
3962
|
+
executionIntent: variant.executionIntent
|
|
3963
|
+
}
|
|
3964
|
+
);
|
|
3965
|
+
if (artifactResult?.success && artifactResult.manifestPath) {
|
|
3966
|
+
result.artifactManifest = artifactResult.manifestPath;
|
|
3967
|
+
}
|
|
3968
|
+
} catch (_err) {
|
|
3969
|
+
await warnOnce(
|
|
3970
|
+
"Telemetry logging failed mid-run; disabling telemetry for this worker.",
|
|
3971
|
+
telemetryWarning
|
|
3972
|
+
);
|
|
3973
|
+
telemetryEnabled = false;
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3976
|
+
if (verbose) {
|
|
3977
|
+
console.error(chalk.red(`Execution failed: ${errorObj.message}`));
|
|
3978
|
+
if (specSignal) {
|
|
3979
|
+
const missingNames = specSignal.missingFields.map(
|
|
3980
|
+
(field) => FIELD_DISPLAY_NAMES[field] ?? field
|
|
3981
|
+
);
|
|
3982
|
+
console.error(
|
|
3983
|
+
chalk.yellow(
|
|
3984
|
+
` Note: execution started from an incomplete spec (${Math.round(specSignal.completenessScore * 100)}% complete; missing: ${missingNames.join(", ")})`
|
|
3985
|
+
)
|
|
3986
|
+
);
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
providerOutputForwarder.close();
|
|
3991
|
+
if (!result.postProviderPhaseDurationsMs) {
|
|
3992
|
+
result.postProviderPhaseDurationsMs = {};
|
|
3993
|
+
}
|
|
3994
|
+
const finalPostProviderPhaseDurationsMs = result.postProviderPhaseDurationsMs;
|
|
3995
|
+
if (result.success === true && shouldDowngradeSuccessfulResultForShutdown({
|
|
3996
|
+
shutdownRequested,
|
|
3997
|
+
shutdownRequestedAtMs,
|
|
3998
|
+
executionResultSettledAtMs
|
|
3999
|
+
})) {
|
|
4000
|
+
const signal = shutdownSignal ?? "SIGTERM";
|
|
4001
|
+
result.success = false;
|
|
4002
|
+
result.timedOut = signal === "SIGTERM";
|
|
4003
|
+
result.terminationReason = signal === "SIGINT" ? "canceled_user" : "timeout";
|
|
4004
|
+
result.error = new Error(
|
|
4005
|
+
signal === "SIGINT" ? "Worker interrupted before final result could be trusted" : "Worker received SIGTERM before final result could be trusted"
|
|
4006
|
+
);
|
|
4007
|
+
}
|
|
4008
|
+
measurePostProviderPhaseSync(
|
|
4009
|
+
finalPostProviderPhaseDurationsMs,
|
|
4010
|
+
"finalOutcomeDerivation",
|
|
4011
|
+
verbose,
|
|
4012
|
+
() => {
|
|
4013
|
+
result.outcome = deriveVariantOutcome(result);
|
|
4014
|
+
}
|
|
4015
|
+
);
|
|
4016
|
+
const worktreePathForCleanup = cleanupWorktreeAfterRun && result.success === true && typeof result.retainedWorktreePath === "string" ? result.retainedWorktreePath.trim() : void 0;
|
|
4017
|
+
if (worktreePathForCleanup) {
|
|
4018
|
+
result.retainedWorktreePath = void 0;
|
|
4019
|
+
}
|
|
4020
|
+
measurePostProviderPhaseSync(
|
|
4021
|
+
finalPostProviderPhaseDurationsMs,
|
|
4022
|
+
"resultFinalize",
|
|
4023
|
+
verbose,
|
|
4024
|
+
() => {
|
|
4025
|
+
if (result.result) {
|
|
4026
|
+
if (isNativeResearchEnabledFor(variant.provider)) {
|
|
4027
|
+
const rawTranscript = `${result.result.rawStdout ?? ""}
|
|
4028
|
+
${result.result.rawStderr ?? ""}`;
|
|
4029
|
+
if (rawTranscript.trim().length > 0) {
|
|
4030
|
+
const scan = scanWebToolEvents(rawTranscript);
|
|
4031
|
+
if (scan.corroboratedUrls.size > 0) {
|
|
4032
|
+
result.result.researchCorroboratedUrls = Array.from(scan.corroboratedUrls);
|
|
4033
|
+
}
|
|
4034
|
+
if (scan.observedWebToolEvent) {
|
|
4035
|
+
result.result.researchObservedWebToolEvent = true;
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
result.result.rawStdout = void 0;
|
|
4040
|
+
result.result.rawStderr = void 0;
|
|
4041
|
+
if (result.result.stdout && Buffer.byteLength(result.result.stdout, "utf8") > 2e5) {
|
|
4042
|
+
result.result.stdout = truncateToByteLimit(result.result.stdout, 2e5);
|
|
4043
|
+
}
|
|
4044
|
+
if (result.result.stderr && Buffer.byteLength(result.result.stderr, "utf8") > 2e5) {
|
|
4045
|
+
result.result.stderr = truncateToByteLimit(result.result.stderr, 2e5);
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
);
|
|
4050
|
+
sanitizeVariantResult(result);
|
|
4051
|
+
try {
|
|
4052
|
+
await measurePostProviderPhase(
|
|
4053
|
+
finalPostProviderPhaseDurationsMs,
|
|
4054
|
+
"resultWrite",
|
|
4055
|
+
verbose,
|
|
4056
|
+
async () => writeResultFileAtomically(resultFile, result)
|
|
4057
|
+
);
|
|
4058
|
+
emitWorkerLifecycleMarker(RESULT_WRITTEN_MARKER_PREFIX, elapsedMsSince(startTime));
|
|
4059
|
+
emitWorkerLifecycleMarker(
|
|
4060
|
+
POST_PROVIDER_PHASES_MARKER_PREFIX,
|
|
4061
|
+
finalPostProviderPhaseDurationsMs
|
|
4062
|
+
);
|
|
4063
|
+
if (verbose) {
|
|
4064
|
+
console.log(chalk.green(`Result written to ${resultFile}`));
|
|
4065
|
+
}
|
|
4066
|
+
if (telemetryEnabled && telemetry) {
|
|
4067
|
+
try {
|
|
4068
|
+
TelemetryLogger.disposeShared();
|
|
4069
|
+
} catch (_err) {
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4072
|
+
if (worktreePathForCleanup && worktreePathForCleanup.length > 0) {
|
|
4073
|
+
await measurePostProviderPhase(
|
|
4074
|
+
finalPostProviderPhaseDurationsMs,
|
|
4075
|
+
"cleanup",
|
|
4076
|
+
verbose,
|
|
4077
|
+
async () => tryCleanupRetainedWorktree(worktreePathForCleanup, verbose)
|
|
4078
|
+
);
|
|
4079
|
+
}
|
|
4080
|
+
applyWorkerEnvOverrides({
|
|
4081
|
+
POETIC_RETAIN_BASIC_WORKTREE: prevRetainBasicWorktree
|
|
4082
|
+
});
|
|
4083
|
+
emitWorkerLifecycleMarker(CLEANUP_START_MARKER_PREFIX, elapsedMsSince(startTime));
|
|
4084
|
+
await measurePostProviderPhase(
|
|
4085
|
+
finalPostProviderPhaseDurationsMs,
|
|
4086
|
+
"cleanup",
|
|
4087
|
+
verbose,
|
|
4088
|
+
async () => signalCoordinator.runCleanupHandlers("variant-worker exit", {
|
|
4089
|
+
timeoutMs: SIGNAL_CLEANUP_TIMEOUT_MS
|
|
4090
|
+
})
|
|
4091
|
+
);
|
|
4092
|
+
emitWorkerLifecycleMarker(CLEANUP_COMPLETE_MARKER_PREFIX, elapsedMsSince(startTime));
|
|
4093
|
+
emitWorkerLifecycleMarker(
|
|
4094
|
+
POST_PROVIDER_PHASES_MARKER_PREFIX,
|
|
4095
|
+
finalPostProviderPhaseDurationsMs
|
|
4096
|
+
);
|
|
4097
|
+
emitWorkerLifecycleMarker(ACTIVE_HANDLES_MARKER_PREFIX, activeHandleSummary());
|
|
4098
|
+
if (preserveWorktreesForDebug) {
|
|
4099
|
+
applyWorkerEnvOverrides({
|
|
4100
|
+
POETIC_PRESERVE_WORKTREES: prevPreserveWorktrees
|
|
4101
|
+
});
|
|
4102
|
+
}
|
|
4103
|
+
return;
|
|
4104
|
+
} catch (error) {
|
|
4105
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4106
|
+
console.error(chalk.red(`Failed to write result: ${message}`));
|
|
4107
|
+
if (telemetryEnabled && telemetry) {
|
|
4108
|
+
try {
|
|
4109
|
+
TelemetryLogger.disposeShared();
|
|
4110
|
+
} catch (_err) {
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
applyWorkerEnvOverrides({
|
|
4114
|
+
POETIC_RETAIN_BASIC_WORKTREE: prevRetainBasicWorktree
|
|
4115
|
+
});
|
|
4116
|
+
await signalCoordinator.runCleanupHandlers("variant-worker fatal-exit", {
|
|
4117
|
+
timeoutMs: SIGNAL_CLEANUP_TIMEOUT_MS
|
|
4118
|
+
});
|
|
4119
|
+
if (preserveWorktreesForDebug) {
|
|
4120
|
+
applyWorkerEnvOverrides({
|
|
4121
|
+
POETIC_PRESERVE_WORKTREES: prevPreserveWorktrees
|
|
4122
|
+
});
|
|
4123
|
+
}
|
|
4124
|
+
throw new ExitCodeError(1, message);
|
|
4125
|
+
}
|
|
4126
|
+
} finally {
|
|
4127
|
+
uninstallPostProviderSignalHandlers();
|
|
4128
|
+
signalCoordinator.unregister(signalHandlerName);
|
|
4129
|
+
signalCoordinator.unregister(processLifecycleSignalHandlerName);
|
|
4130
|
+
setProcessLifecycleManager(void 0);
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
var workerEntrypoint = fileURLToPath(import.meta.url);
|
|
4134
|
+
var invokedScript = process.argv[1] ? path.resolve(process.argv[1]) : null;
|
|
4135
|
+
var isDirectInvocation = invokedScript !== null && path.resolve(workerEntrypoint) === invokedScript;
|
|
4136
|
+
async function runVariantWorkerEntrypoint(entryOptions = {}) {
|
|
4137
|
+
if (!entryOptions.runtimeAlreadyChecked) {
|
|
4138
|
+
assertPoeticRuntimeReady({ command: "variant-worker" });
|
|
4139
|
+
}
|
|
4140
|
+
try {
|
|
4141
|
+
await main();
|
|
4142
|
+
return 0;
|
|
4143
|
+
} catch (error) {
|
|
4144
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4145
|
+
console.error(chalk.red(`Unhandled error: ${message}`));
|
|
4146
|
+
try {
|
|
4147
|
+
const argv = process.argv;
|
|
4148
|
+
const flagIdx = argv.indexOf("--result-file");
|
|
4149
|
+
const resultFile = flagIdx !== -1 && flagIdx + 1 < argv.length ? argv[flagIdx + 1] : void 0;
|
|
4150
|
+
if (resultFile) {
|
|
4151
|
+
const payload = {
|
|
4152
|
+
success: false,
|
|
4153
|
+
error: { message, stage: "bootstrap" },
|
|
4154
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4155
|
+
};
|
|
4156
|
+
await writeResultFileAtomically(resultFile, payload);
|
|
4157
|
+
}
|
|
4158
|
+
} catch {
|
|
4159
|
+
}
|
|
4160
|
+
return error instanceof ExitCodeError ? error.exitCode : 1;
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
if (process.env.VITEST !== "1" && isDirectInvocation) {
|
|
4164
|
+
runVariantWorkerEntrypoint().then((exitCode) => {
|
|
4165
|
+
if (exitCode === 0) {
|
|
4166
|
+
process.exitCode = 0;
|
|
4167
|
+
} else {
|
|
4168
|
+
process.exit(exitCode);
|
|
4169
|
+
}
|
|
4170
|
+
}).catch((error) => {
|
|
4171
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
4172
|
+
`);
|
|
4173
|
+
process.exit(1);
|
|
4174
|
+
});
|
|
4175
|
+
}
|
|
4176
|
+
export {
|
|
4177
|
+
__testOnlyWriteResultFileAtomically,
|
|
4178
|
+
applyVerifiedTouchedTestFailureGuard,
|
|
4179
|
+
attachBaselineComparisonIfAvailable,
|
|
4180
|
+
buildRepairTriggerEvidence,
|
|
4181
|
+
createProviderOutputForwarder,
|
|
4182
|
+
decideRepairAdmission,
|
|
4183
|
+
executeWorker,
|
|
4184
|
+
extractOriginalPrompt,
|
|
4185
|
+
formatVerboseWorkerCompletionMetric,
|
|
4186
|
+
hasVerifiedTouchedTestFailure2 as hasVerifiedTouchedTestFailure,
|
|
4187
|
+
isValidationArtifactOnlyPrompt,
|
|
4188
|
+
resolveRepairPromotionPolicy,
|
|
4189
|
+
runVariantWorkerEntrypoint,
|
|
4190
|
+
serializeErrorForJson,
|
|
4191
|
+
shouldDowngradeSuccessfulResultForShutdown,
|
|
4192
|
+
shouldSkipRepairLoopForPassingGates,
|
|
4193
|
+
shouldTrustVerificationLoopBaseline,
|
|
4194
|
+
tryCleanupRetainedWorktree
|
|
4195
|
+
};
|
|
4196
|
+
//# sourceMappingURL=variant-worker-KUWKBLAH.js.map
|