@poetic-ai/poetic 1.42.1-bootstrap.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-MXTSOSST.js +1597 -0
- package/dist/PromptStore-U6ZD3ZMQ.js +18 -0
- package/dist/actions-M3AESHFF.js +322 -0
- package/dist/agent-ingest-HO77TH26.js +53 -0
- package/dist/aggregator-DNCBINFO.js +12 -0
- package/dist/ai-judge-LDWHURKK.js +156 -0
- package/dist/allowlist-grounding-RO6HKUFI.js +16 -0
- package/dist/allowlist-utils-23V7TJ3G.js +28 -0
- package/dist/anchored-turn-service-GFYU6GCQ.js +489 -0
- package/dist/api-transport-FS3CQRMN.js +1087 -0
- package/dist/apply-completion-mode-JBT2CTQO.js +89 -0
- package/dist/artifact-migrator-23L45ISD.js +267 -0
- package/dist/ask-UYVZY7JH.js +135 -0
- package/dist/auth-CSQP5RHJ.js +67 -0
- package/dist/auth-E6XUNZ5M.js +68 -0
- package/dist/auth-KQLJPZ2E.js +134 -0
- package/dist/auth-OC4E6633.js +71 -0
- package/dist/auth-R3NQ2ZF5.js +78 -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-HA3T23SF.js +80 -0
- package/dist/autoloop-cleanup-JOGKTLWA.js +109 -0
- package/dist/autoloop-evidence-gates-ACB5YW5G.js +36 -0
- package/dist/autoloop-gate-policy-XZA5EQFN.js +175 -0
- package/dist/autoloop-helpers-EFJLBQUP.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-OPR74YEX.js +85 -0
- package/dist/autoloop-termination-summary-RN75F422.js +58 -0
- package/dist/autoloop-trajectory-K5I45UBR.js +282 -0
- package/dist/autoloop-wall-clock-T7ZVIQOC.js +12 -0
- package/dist/autonomous-loop-controller-4QJMOIIN.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-WXZHUT42.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-YFASRB5Z.js +71 -0
- package/dist/change-summary-WY7J5TOX.js +33 -0
- package/dist/chunk-25QL3M3L.js +899 -0
- package/dist/chunk-2ABC6SEC.js +2626 -0
- package/dist/chunk-2AICM4P3.js +1763 -0
- package/dist/chunk-2AQUWJPW.js +225 -0
- package/dist/chunk-2DY5KNVM.js +276 -0
- package/dist/chunk-2QJ3H3L7.js +42 -0
- package/dist/chunk-2UH6VQRZ.js +328 -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-2YXZCZTX.js +1160 -0
- package/dist/chunk-32VLDFRT.js +797 -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-3733QTI7.js +6818 -0
- package/dist/chunk-3A5PDH5L.js +12513 -0
- package/dist/chunk-3AOKYKA7.js +22 -0
- package/dist/chunk-3BCUNHKF.js +475 -0
- package/dist/chunk-3C26DPFK.js +566 -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-3JWNRJSB.js +736 -0
- package/dist/chunk-3LUMR3D3.js +156 -0
- package/dist/chunk-3M57DADF.js +44 -0
- package/dist/chunk-3S62LLWJ.js +543 -0
- package/dist/chunk-3VABAKT2.js +1784 -0
- package/dist/chunk-44N5WZZH.js +45 -0
- package/dist/chunk-45FPSA3B.js +125 -0
- package/dist/chunk-4BIPZBT7.js +791 -0
- package/dist/chunk-4BJ3O4RQ.js +929 -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-4HN6DF7V.js +1027 -0
- package/dist/chunk-4HXKHDNH.js +1064 -0
- package/dist/chunk-4KKI477Q.js +44 -0
- package/dist/chunk-4NA7FSTV.js +595 -0
- package/dist/chunk-4NXPXE62.js +84 -0
- package/dist/chunk-4SALLK63.js +9831 -0
- package/dist/chunk-4SHKPCGK.js +110 -0
- package/dist/chunk-4VJITV4P.js +445 -0
- package/dist/chunk-4W2NRXQL.js +1205 -0
- package/dist/chunk-4XICK3HN.js +784 -0
- package/dist/chunk-55EJVV3C.js +385 -0
- package/dist/chunk-5DLKYTQX.js +4003 -0
- package/dist/chunk-5EERLVZB.js +139 -0
- package/dist/chunk-5HDP7XZG.js +91 -0
- package/dist/chunk-5LAMRH4X.js +969 -0
- package/dist/chunk-5REDMNLZ.js +310 -0
- package/dist/chunk-5UCJARII.js +1984 -0
- package/dist/chunk-5WCQRDRB.js +588 -0
- package/dist/chunk-5WRLK5KW.js +700 -0
- package/dist/chunk-66AGFTBQ.js +131 -0
- package/dist/chunk-66CIO2SV.js +48 -0
- package/dist/chunk-6BM72ZMG.js +31 -0
- package/dist/chunk-6BTFXYRO.js +276 -0
- package/dist/chunk-6EDAJH2I.js +68 -0
- package/dist/chunk-6EQKFDBH.js +457 -0
- package/dist/chunk-6KMJKHIQ.js +2193 -0
- package/dist/chunk-6MFRHTRI.js +115 -0
- package/dist/chunk-6QMUKUHH.js +632 -0
- package/dist/chunk-6TZJRKNW.js +321 -0
- package/dist/chunk-6V7HGQFZ.js +20 -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-73NCTWKL.js +188 -0
- package/dist/chunk-77I6G4CO.js +1 -0
- package/dist/chunk-7D7QT3BE.js +688 -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-7IL4A2PP.js +948 -0
- package/dist/chunk-7NPSXSHO.js +975 -0
- package/dist/chunk-7PS6INZP.js +302 -0
- package/dist/chunk-7QCZSLBH.js +1 -0
- package/dist/chunk-7R2WLDOS.js +62 -0
- package/dist/chunk-7XV3QXLT.js +878 -0
- package/dist/chunk-A2WKSVX7.js +43 -0
- package/dist/chunk-A3YGID55.js +133 -0
- package/dist/chunk-A5MRWHMI.js +1604 -0
- package/dist/chunk-A722DEVA.js +14 -0
- package/dist/chunk-AG2Z2SUZ.js +554 -0
- package/dist/chunk-AHWAZ2MU.js +137 -0
- package/dist/chunk-AIAQ3HS3.js +119 -0
- package/dist/chunk-ALAB3PBR.js +2206 -0
- package/dist/chunk-APPNGGN7.js +365 -0
- package/dist/chunk-APV6MK5E.js +137 -0
- package/dist/chunk-AQNPGRWS.js +395 -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-AX6GWE7V.js +47 -0
- package/dist/chunk-AZQILSNQ.js +1376 -0
- package/dist/chunk-B76KHLX6.js +13 -0
- package/dist/chunk-BAV2HJXS.js +194 -0
- package/dist/chunk-BBHRN366.js +89 -0
- package/dist/chunk-BCZAENOH.js +448 -0
- package/dist/chunk-BIGSACCY.js +19527 -0
- package/dist/chunk-BPYMCIVD.js +9243 -0
- package/dist/chunk-BSE4R6XD.js +322 -0
- package/dist/chunk-BVPWRDCO.js +2321 -0
- package/dist/chunk-BXI4NXL3.js +507 -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-CHQV73H5.js +408 -0
- package/dist/chunk-CJC6GZ46.js +457 -0
- package/dist/chunk-CJHRFSNY.js +924 -0
- package/dist/chunk-CKP2Q3TP.js +1905 -0
- package/dist/chunk-CLKSETWE.js +10 -0
- package/dist/chunk-CQM3A35X.js +844 -0
- package/dist/chunk-CSLAS5GW.js +4007 -0
- package/dist/chunk-CTLUBNCW.js +622 -0
- package/dist/chunk-CVMVZWG4.js +3544 -0
- package/dist/chunk-CYB6QDCT.js +263 -0
- package/dist/chunk-D5EP5D2W.js +26 -0
- package/dist/chunk-DC4ZYMXJ.js +115 -0
- package/dist/chunk-DCAVAABI.js +348 -0
- package/dist/chunk-DCYF7EKG.js +26 -0
- package/dist/chunk-DF5SLDE4.js +966 -0
- package/dist/chunk-DN7UMLF7.js +5678 -0
- package/dist/chunk-DSFM56OA.js +1203 -0
- package/dist/chunk-DTY4APYV.js +218 -0
- package/dist/chunk-E3NTPHEO.js +194 -0
- package/dist/chunk-E74LUIID.js +302 -0
- package/dist/chunk-EJ42BNGY.js +2904 -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-EQGIDAUF.js +62 -0
- package/dist/chunk-EX3OM35G.js +20414 -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-FCNAS3W2.js +1033 -0
- package/dist/chunk-FD4ERYEG.js +22 -0
- package/dist/chunk-FDVZLG6Z.js +3104 -0
- package/dist/chunk-FEPIFY7D.js +511 -0
- package/dist/chunk-FEQ4HXL7.js +412 -0
- package/dist/chunk-FFH5HHJU.js +5351 -0
- package/dist/chunk-FH7WOJAE.js +79 -0
- package/dist/chunk-FKAUPHMR.js +96 -0
- package/dist/chunk-FRG57FPM.js +240 -0
- package/dist/chunk-FUVP64ER.js +31 -0
- package/dist/chunk-G2PTLLEL.js +284 -0
- package/dist/chunk-G35UYQ6Z.js +69 -0
- package/dist/chunk-G36W2T2S.js +1143 -0
- package/dist/chunk-G4THLFV3.js +148 -0
- package/dist/chunk-GAFJEYLK.js +29 -0
- package/dist/chunk-GCCAPO3S.js +118 -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-GJTWJNLV.js +1374 -0
- package/dist/chunk-GKN3HMN4.js +2809 -0
- package/dist/chunk-GNH3QGTL.js +1342 -0
- package/dist/chunk-GOAQ2I7Y.js +7000 -0
- package/dist/chunk-GSGD5E4C.js +30 -0
- package/dist/chunk-GXWAJQOJ.js +5011 -0
- package/dist/chunk-H2AYQIHW.js +224 -0
- package/dist/chunk-H6G7QRMQ.js +35 -0
- package/dist/chunk-H7JAGHZT.js +59 -0
- package/dist/chunk-HAC6EHZZ.js +79 -0
- package/dist/chunk-HE2KH6EQ.js +161 -0
- package/dist/chunk-HIHENQDX.js +66 -0
- package/dist/chunk-HIO33P3G.js +19 -0
- package/dist/chunk-HK4AQZSR.js +438 -0
- package/dist/chunk-HMVQIPH3.js +547 -0
- package/dist/chunk-HOYBYAOA.js +257 -0
- package/dist/chunk-HQLGXODD.js +123 -0
- package/dist/chunk-HQXYYUNY.js +1231 -0
- package/dist/chunk-HY3ESWCA.js +708 -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-I6BL3PDH.js +1024 -0
- package/dist/chunk-IATQ7NBI.js +679 -0
- package/dist/chunk-IC7I6YIJ.js +301 -0
- package/dist/chunk-IDCSOZVN.js +78 -0
- package/dist/chunk-IEXU4RR6.js +5742 -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-IP7BYVUV.js +338 -0
- package/dist/chunk-IRJ2I6KH.js +166 -0
- package/dist/chunk-IUXNGKA6.js +152 -0
- package/dist/chunk-IZYIQQ7X.js +53 -0
- package/dist/chunk-IZZK3H6I.js +2175 -0
- package/dist/chunk-J3J4P7RW.js +353 -0
- package/dist/chunk-J62KPGII.js +464 -0
- package/dist/chunk-J6P3FWWW.js +130 -0
- package/dist/chunk-JD6SIZED.js +712 -0
- package/dist/chunk-JEV6UI7H.js +537 -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-JQD5VYKR.js +483 -0
- package/dist/chunk-JSOWEFEQ.js +351 -0
- package/dist/chunk-JSWMRQYG.js +28 -0
- package/dist/chunk-K4XK7SGB.js +6783 -0
- package/dist/chunk-K54Y7MLK.js +160 -0
- package/dist/chunk-KAT4RXUT.js +521 -0
- package/dist/chunk-KDMKKIRK.js +103 -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-KQEBSK3T.js +274 -0
- package/dist/chunk-KT6VHSPF.js +84 -0
- package/dist/chunk-KTMW65UX.js +14 -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-LGHII5ET.js +23 -0
- package/dist/chunk-LH34OGSB.js +265 -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-LUVKFFWR.js +315 -0
- package/dist/chunk-LWCBNGH6.js +35 -0
- package/dist/chunk-LWTN3WRV.js +260 -0
- package/dist/chunk-LZBBXYWF.js +1004 -0
- package/dist/chunk-M34PS4NE.js +2213 -0
- package/dist/chunk-M3HQYKQX.js +308 -0
- package/dist/chunk-MCPQNJMK.js +482 -0
- package/dist/chunk-MF5HP6XV.js +221 -0
- package/dist/chunk-MFADRRTR.js +112 -0
- package/dist/chunk-MN6PL4AN.js +120 -0
- package/dist/chunk-MQ4WA34C.js +206 -0
- package/dist/chunk-MTVZITQ2.js +185 -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-N5DX4JAK.js +12 -0
- package/dist/chunk-NEDCMN7E.js +415 -0
- package/dist/chunk-NEQUQHOX.js +223 -0
- package/dist/chunk-NGK6PEOG.js +25 -0
- package/dist/chunk-NM6PMYY4.js +768 -0
- package/dist/chunk-NOZGR2QO.js +2373 -0
- package/dist/chunk-NP2V3L7K.js +226 -0
- package/dist/chunk-NPZJMUZ4.js +194 -0
- package/dist/chunk-NRLY536M.js +260 -0
- package/dist/chunk-NS33V3IM.js +51 -0
- package/dist/chunk-NWOYOA6H.js +378 -0
- package/dist/chunk-NWRUSEXG.js +322 -0
- package/dist/chunk-O6V4PNLO.js +63 -0
- package/dist/chunk-OGMSYL6J.js +227 -0
- package/dist/chunk-OIS7J26S.js +319 -0
- package/dist/chunk-OKBTRSZI.js +205 -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-OSDYVKHW.js +2440 -0
- package/dist/chunk-OUM5S64R.js +3429 -0
- package/dist/chunk-OWB7JXUS.js +751 -0
- package/dist/chunk-OYZYO7TV.js +47 -0
- package/dist/chunk-P47VN5F4.js +282 -0
- package/dist/chunk-P6VQROCO.js +594 -0
- package/dist/chunk-P7CQGPLS.js +30 -0
- package/dist/chunk-PA6BCSOX.js +2872 -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-PTH5E5XO.js +195 -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-QGJDMEOV.js +34 -0
- package/dist/chunk-QJYIHVXT.js +100 -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-QU6JQJMH.js +3063 -0
- package/dist/chunk-QVFV5IP2.js +232 -0
- package/dist/chunk-QVZMFDYG.js +29 -0
- package/dist/chunk-R2ZYWNW3.js +1581 -0
- package/dist/chunk-R4UWBC35.js +200 -0
- package/dist/chunk-R6W23LKN.js +254 -0
- package/dist/chunk-RAK5HOIJ.js +459 -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-RIKFAWZF.js +71 -0
- package/dist/chunk-RJAAKDRX.js +17 -0
- package/dist/chunk-RSXSD4MH.js +1016 -0
- package/dist/chunk-RW7RVSDV.js +79 -0
- package/dist/chunk-RWREC3KJ.js +517 -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-S77XPALC.js +239 -0
- package/dist/chunk-SCW4ZF6R.js +150 -0
- package/dist/chunk-SKHGYQ7W.js +405 -0
- package/dist/chunk-SNLBO4BF.js +247 -0
- package/dist/chunk-SP3LFU6B.js +60 -0
- package/dist/chunk-SQZRPY23.js +5181 -0
- package/dist/chunk-SSB5UQHA.js +4128 -0
- package/dist/chunk-STJOHHSF.js +605 -0
- package/dist/chunk-STV6LYYE.js +45 -0
- package/dist/chunk-SUN4OFTW.js +740 -0
- package/dist/chunk-SURZ2WFE.js +326 -0
- package/dist/chunk-SUZ7UYZH.js +1972 -0
- package/dist/chunk-SZ7DL357.js +1047 -0
- package/dist/chunk-T2SZELJL.js +624 -0
- package/dist/chunk-T6VPI2XP.js +10338 -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-TIBZPAD2.js +9924 -0
- package/dist/chunk-TK644IOG.js +1813 -0
- package/dist/chunk-TRSVUPCX.js +25 -0
- package/dist/chunk-TSPQONRT.js +4533 -0
- package/dist/chunk-TSXGRLPR.js +107 -0
- package/dist/chunk-TWYLVAS2.js +152 -0
- package/dist/chunk-U2S3YEX3.js +224 -0
- package/dist/chunk-U32HWI76.js +437 -0
- package/dist/chunk-U45RTRGY.js +818 -0
- package/dist/chunk-U62VCQGR.js +49 -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-UKMAFG7A.js +189 -0
- package/dist/chunk-UUH2RVKS.js +585 -0
- package/dist/chunk-UVBBFDFQ.js +270 -0
- package/dist/chunk-UXZF3B7T.js +75 -0
- package/dist/chunk-V6YLL5WA.js +3746 -0
- package/dist/chunk-V72RMBM4.js +48 -0
- package/dist/chunk-VBNCKCCI.js +16 -0
- package/dist/chunk-VCAUV5XQ.js +3679 -0
- package/dist/chunk-VDSH7O4Y.js +152 -0
- package/dist/chunk-VNSYKC25.js +24 -0
- package/dist/chunk-VTLNJQ44.js +133 -0
- package/dist/chunk-VV6HOXRC.js +302 -0
- package/dist/chunk-VVGEPBPS.js +218 -0
- package/dist/chunk-VXF4Q3FW.js +20 -0
- package/dist/chunk-W3ZGW3C5.js +331 -0
- package/dist/chunk-W4OMESPG.js +271 -0
- package/dist/chunk-WDUORIHF.js +877 -0
- package/dist/chunk-WDVTY4U6.js +148 -0
- package/dist/chunk-WGXYDLMX.js +4386 -0
- package/dist/chunk-WMPU2UOV.js +82 -0
- package/dist/chunk-WOEVVPDP.js +1125 -0
- package/dist/chunk-WTLMSPBQ.js +256 -0
- package/dist/chunk-X43NMU4Z.js +1124 -0
- package/dist/chunk-X5NTI5U5.js +368 -0
- package/dist/chunk-X6UIEADS.js +949 -0
- package/dist/chunk-XAONGNST.js +57 -0
- package/dist/chunk-XB5YLCLB.js +13 -0
- package/dist/chunk-XGYF2QMZ.js +351 -0
- package/dist/chunk-XHEKQENB.js +255 -0
- package/dist/chunk-XPHKYHO4.js +878 -0
- package/dist/chunk-XQD2B6BB.js +507 -0
- package/dist/chunk-XT2ZQVTC.js +558 -0
- package/dist/chunk-XUSRFFA7.js +1859 -0
- package/dist/chunk-XUXVDPSZ.js +50 -0
- package/dist/chunk-XV7NZL4B.js +57 -0
- package/dist/chunk-XXCGJGSQ.js +121 -0
- package/dist/chunk-XYGDZCB5.js +659 -0
- package/dist/chunk-XZAMZUX5.js +992 -0
- package/dist/chunk-XZE4XARH.js +563 -0
- package/dist/chunk-Y34DUC4A.js +1356 -0
- package/dist/chunk-Y3J4JMTU.js +61 -0
- package/dist/chunk-Y4RC2YFX.js +1270 -0
- package/dist/chunk-Y55LZN5U.js +52 -0
- package/dist/chunk-Y5677NDO.js +183 -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-YQAAKTVH.js +712 -0
- package/dist/chunk-YVORHQ2S.js +579 -0
- package/dist/chunk-YWNAHW24.js +1552 -0
- package/dist/chunk-Z476RATD.js +1689 -0
- package/dist/chunk-ZDTGECTN.js +235 -0
- package/dist/chunk-ZDTMICNY.js +316 -0
- package/dist/chunk-ZIPWI2LY.js +151 -0
- package/dist/chunk-ZIVYXF7A.js +2344 -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-HUOTBRM3.js +40 -0
- package/dist/cli-validation-WLXWLUJL.js +111 -0
- package/dist/commit-utils-3QV2P6RA.js +202 -0
- package/dist/compete-config-resolver-SNH3TT2Q.js +76 -0
- package/dist/compete-request-XWNPOARL.js +364 -0
- package/dist/compete-results-PAP67LDR.js +208 -0
- package/dist/competition-outcome-tracker-GOZXNAQU.js +38 -0
- package/dist/config-HWHUNA72.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-L3VRNTSG.js +67 -0
- package/dist/config-validator-SIIQEOFQ.js +72 -0
- package/dist/cost-7CX37DU3.js +53 -0
- package/dist/coverage-orchestrator-MNKHD6YC.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-CGBCD7IQ.js +186 -0
- package/dist/domain-analyzer-DL6BDR2N.js +29 -0
- package/dist/ensure-initialized-4BY73HLE.js +52 -0
- package/dist/entry.js +940 -0
- package/dist/environment-IGVH5X53.js +81 -0
- package/dist/error-parser-core-PPZ36A4S.js +50 -0
- package/dist/escalation-ladder-NWY5BJ57.js +130 -0
- package/dist/evaluation-context-KMWDSDYD.js +35 -0
- package/dist/evidence-plan-resolver-YIVH76PU.js +85 -0
- package/dist/execution-data-writer-OHAQ7G5H.js +53 -0
- package/dist/execution-policy-applier-ZHD3DCF3.js +31 -0
- package/dist/execution-preflight-Z4Y64V3I.js +209 -0
- package/dist/execution-roles-KBH2MC3M.js +66 -0
- package/dist/exit-code-error-Z4SW2DVK.js +14 -0
- package/dist/external-temp-cleanup-N2RZH4QP.js +311 -0
- package/dist/factory-IYBMCZG7.js +149 -0
- package/dist/file-utils-XIVR2ZMA.js +47 -0
- package/dist/flywheel-autoloop-executor-R3KD2G4D.js +249 -0
- package/dist/flywheel-competition-executor-XCFGVJSK.js +259 -0
- package/dist/flywheel-executor-LTSIZN2L.js +369 -0
- package/dist/flywheel-git-isolation-CZ5YVVZY.js +48 -0
- package/dist/flywheel-manifest-5UC4VBMA.js +108 -0
- package/dist/flywheel-model-defaults-7CVKP53V.js +27 -0
- package/dist/flywheel-preflight-6E33DJDK.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-AZ46EPRP.js +73 -0
- package/dist/git-worktree-IY6V6DUO.js +18 -0
- package/dist/github-pr-manager-4XUO2YTM.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 +108644 -0
- package/dist/instructions-CM6THDV7.js +64 -0
- package/dist/invoke-provider-auth-V5TC5UDS.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-VA6VXNOV.js +23 -0
- package/dist/linux-host-class-UY4KVLFD.js +29 -0
- package/dist/llm-judge-executor-34YPIRJ7.js +133 -0
- package/dist/local-executor-LJJSCWZX.js +223 -0
- package/dist/logger-6PRYM56M.js +72 -0
- package/dist/loop-spec-XTLQAVZ7.js +191 -0
- package/dist/manager-2HJG5CLT.js +25 -0
- package/dist/matrix-prompt-builder-LHDTJ4TY.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-MCI3F4PX.js +72 -0
- package/dist/next-HQKDXIIR.js +90 -0
- package/dist/next-JC3V2UCS.js +69 -0
- package/dist/optimization-config-resolver-QPSFLCJU.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-DZK24LSZ.js +270 -0
- package/dist/plan-backlog-show-fast-63GBBH6Q.js +406 -0
- package/dist/plan-sprint-list-fast-TJ6ZUF5E.js +162 -0
- package/dist/plan-sprint-status-fast-YWE5X3NG.js +208 -0
- package/dist/plan-task-status-fast-K22BTTBW.js +349 -0
- package/dist/plan-to-flywheel-VFGCPINA.js +456 -0
- package/dist/planning-backlog-Y6TIJYWU.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-2XP3FWH2.js +149 -0
- package/dist/prompts-EPAAAMMF.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-KWTYA5QV.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-33SHK43X.js +70 -0
- package/dist/quickstart-E32SODTA.js +62 -0
- package/dist/readiness-REWW7OSV.js +96 -0
- package/dist/registry-N6L36RE2.js +153 -0
- package/dist/repair-helpers-TTV5FEF4.js +117 -0
- package/dist/repo-root-2IGD4H7X.js +22 -0
- package/dist/resolution-engine-LXQYDSKS.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-VGQU35QM.js +160 -0
- package/dist/run-poetic-tui-ZZXFDOQ4.js +13517 -0
- package/dist/run-simulation.js +376 -0
- package/dist/runner-2YPKOJ6A.js +1092 -0
- package/dist/runner-HEJJ65AJ.js +1571 -0
- package/dist/safety-OCC4KJJR.js +64 -0
- package/dist/safety-stanza-BSVE6OJU.js +60 -0
- package/dist/sandbox-3AG2WJLM.js +94 -0
- package/dist/sandbox-5JOFBS75.js +81 -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-QGXLN3SO.js +338 -0
- package/dist/sprint-bridge-FNKQCJGL.js +68 -0
- package/dist/sprint-execution-service-4ELKNJDS.js +311 -0
- package/dist/sprint-manager-7TNCXCIX.js +91 -0
- package/dist/sqlite-wrapper-UDWCTNLJ.js +17 -0
- package/dist/stale-cleanup-orchestrator-H5XKGZP2.js +62 -0
- package/dist/storage-access-F6ALDN4S.js +22 -0
- package/dist/synthesis-TGQUVAW3.js +185 -0
- package/dist/task-classifier-DZIRBSS2.js +494 -0
- package/dist/task-list-fast-PJ2EXVZE.js +147 -0
- package/dist/task-manager-5742GMJK.js +93 -0
- package/dist/task-splitter-6MBXSWF6.js +79 -0
- package/dist/task-type-detector-OLVOMXZO.js +21 -0
- package/dist/task-type-resolver-4HG6TXFV.js +42 -0
- package/dist/telemetry-JX7EMNOZ.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-2MLJ4J6B.js +125 -0
- package/dist/token-estimator-PDU345YE.js +37 -0
- package/dist/transports-3UEO6C5Y.js +255 -0
- package/dist/unified-cost-tracker-F3W5NMPG.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-XCOYVCM4.js +33 -0
- package/dist/variant-state-cleanup-QIEKRULB.js +280 -0
- package/dist/variant-success-PJXWE3ZV.js +12 -0
- package/dist/variant-worker-HPTUA37Z.js +4196 -0
- package/dist/variant-worker.js +24 -0
- package/dist/variants-2HDYZOCG.js +233 -0
- package/dist/verification-toolchains-VVKT3GBF.js +28 -0
- package/dist/verify-commands-5F6CJV4B.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-AGWHD4D2.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 +7040 -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/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,4386 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ProcessLifecycleManager,
|
|
3
|
+
parseCitationTrace,
|
|
4
|
+
serializeCitationTrace
|
|
5
|
+
} from "./chunk-PA6BCSOX.js";
|
|
6
|
+
import {
|
|
7
|
+
AutomaticPromptOptimizer
|
|
8
|
+
} from "./chunk-VCAUV5XQ.js";
|
|
9
|
+
import {
|
|
10
|
+
detectHostConcurrencyBudget
|
|
11
|
+
} from "./chunk-PYITM4N2.js";
|
|
12
|
+
import {
|
|
13
|
+
LocalExecutor,
|
|
14
|
+
WorkspaceAgentRunner
|
|
15
|
+
} from "./chunk-BIGSACCY.js";
|
|
16
|
+
import {
|
|
17
|
+
GitHubPRManager
|
|
18
|
+
} from "./chunk-5UCJARII.js";
|
|
19
|
+
import {
|
|
20
|
+
enqueueRemoteBranchCleanup,
|
|
21
|
+
isRemoteBranchMissingError,
|
|
22
|
+
isRetryableRemoteDeleteError,
|
|
23
|
+
resolveExpectedRemoteShaForQueuedBranch
|
|
24
|
+
} from "./chunk-BXI4NXL3.js";
|
|
25
|
+
import {
|
|
26
|
+
UnifiedCostTracker
|
|
27
|
+
} from "./chunk-FCNAS3W2.js";
|
|
28
|
+
import {
|
|
29
|
+
WORKER_EXIT_DRAIN_MS,
|
|
30
|
+
applyComplementaryPatchToWinnerWorktree,
|
|
31
|
+
buildSynthesisTelemetrySnapshot,
|
|
32
|
+
countCompletedCompetitionObservations,
|
|
33
|
+
createLocalVariantResourceManager,
|
|
34
|
+
didSynthesisArtifactSucceed,
|
|
35
|
+
resolveLocalWorktreeConcurrency,
|
|
36
|
+
resolveSynthesisOutcomeStatusForArtifact,
|
|
37
|
+
resolveSynthesisSelectedVariants,
|
|
38
|
+
sealVariantResultSnapshot,
|
|
39
|
+
toConcurrencyProviderId
|
|
40
|
+
} from "./chunk-BPYMCIVD.js";
|
|
41
|
+
import {
|
|
42
|
+
createEvaluationHandleFinalState,
|
|
43
|
+
finalizeEvaluationHandle,
|
|
44
|
+
initEvaluationHandle,
|
|
45
|
+
isDurableHandleCompletion,
|
|
46
|
+
updateEvaluationHandleWithResult
|
|
47
|
+
} from "./chunk-4HXKHDNH.js";
|
|
48
|
+
import {
|
|
49
|
+
sanitizeSynthesisArtifactForPersistence
|
|
50
|
+
} from "./chunk-GG6KJYW6.js";
|
|
51
|
+
import {
|
|
52
|
+
ProviderAwareResourceManager
|
|
53
|
+
} from "./chunk-L7E6XCRA.js";
|
|
54
|
+
import {
|
|
55
|
+
UniversalOutputValidator,
|
|
56
|
+
VariantOrchestrator
|
|
57
|
+
} from "./chunk-3A5PDH5L.js";
|
|
58
|
+
import {
|
|
59
|
+
ConfigManager
|
|
60
|
+
} from "./chunk-GJTWJNLV.js";
|
|
61
|
+
import {
|
|
62
|
+
recordProviderRateLimit
|
|
63
|
+
} from "./chunk-GHTZSQB7.js";
|
|
64
|
+
import {
|
|
65
|
+
UniversalCostCalculator
|
|
66
|
+
} from "./chunk-ASEP3M2W.js";
|
|
67
|
+
import {
|
|
68
|
+
getEvaluationQualityScore,
|
|
69
|
+
getEvaluationRankingScore,
|
|
70
|
+
getEvaluationRankingScoreOrZero
|
|
71
|
+
} from "./chunk-4SALLK63.js";
|
|
72
|
+
import {
|
|
73
|
+
buildProjectVerificationGuidance
|
|
74
|
+
} from "./chunk-MCPQNJMK.js";
|
|
75
|
+
import {
|
|
76
|
+
resolveSynthesisModel
|
|
77
|
+
} from "./chunk-IZZK3H6I.js";
|
|
78
|
+
import {
|
|
79
|
+
getProcessLifecycleManager,
|
|
80
|
+
setProcessLifecycleManager
|
|
81
|
+
} from "./chunk-SQZRPY23.js";
|
|
82
|
+
import {
|
|
83
|
+
matchesErrorPattern
|
|
84
|
+
} from "./chunk-2YFUIER7.js";
|
|
85
|
+
import {
|
|
86
|
+
TelemetryLogger
|
|
87
|
+
} from "./chunk-T6VPI2XP.js";
|
|
88
|
+
import {
|
|
89
|
+
formatVariantIdentifier,
|
|
90
|
+
formatVariantIdentifierFromResult
|
|
91
|
+
} from "./chunk-YWNAHW24.js";
|
|
92
|
+
import {
|
|
93
|
+
parseJsonWithContext
|
|
94
|
+
} from "./chunk-QVZMFDYG.js";
|
|
95
|
+
import {
|
|
96
|
+
calculateRetryDelayMs,
|
|
97
|
+
retryOperation
|
|
98
|
+
} from "./chunk-C57KJOUQ.js";
|
|
99
|
+
import {
|
|
100
|
+
pruneGitWorktreeEntries
|
|
101
|
+
} from "./chunk-5EERLVZB.js";
|
|
102
|
+
import {
|
|
103
|
+
isProtectedPipelineBranch
|
|
104
|
+
} from "./chunk-XQD2B6BB.js";
|
|
105
|
+
import {
|
|
106
|
+
isPoeticOwnedBranch,
|
|
107
|
+
poeticVariantBranchName,
|
|
108
|
+
poeticVariantBranchPrefix
|
|
109
|
+
} from "./chunk-34CNP2HB.js";
|
|
110
|
+
import {
|
|
111
|
+
runVerifyCommands
|
|
112
|
+
} from "./chunk-QU6JQJMH.js";
|
|
113
|
+
import {
|
|
114
|
+
MAX_PER_VARIANT_TIMEOUT_MINUTES
|
|
115
|
+
} from "./chunk-LAMKBTXX.js";
|
|
116
|
+
import {
|
|
117
|
+
detectProjectCapabilities,
|
|
118
|
+
hasProjectLanguage
|
|
119
|
+
} from "./chunk-GNH3QGTL.js";
|
|
120
|
+
import {
|
|
121
|
+
isQuietOutputRequested,
|
|
122
|
+
isVerboseOutputRequested
|
|
123
|
+
} from "./chunk-FKAUPHMR.js";
|
|
124
|
+
import {
|
|
125
|
+
toVariantSlug
|
|
126
|
+
} from "./chunk-TSXGRLPR.js";
|
|
127
|
+
import {
|
|
128
|
+
CompetitionTimeoutError
|
|
129
|
+
} from "./chunk-LUVKFFWR.js";
|
|
130
|
+
import {
|
|
131
|
+
getRuntimeEnv
|
|
132
|
+
} from "./chunk-ENRGKNR5.js";
|
|
133
|
+
import {
|
|
134
|
+
LabPathResolver
|
|
135
|
+
} from "./chunk-7GDRQBQC.js";
|
|
136
|
+
import {
|
|
137
|
+
rethrowConfigValidityError
|
|
138
|
+
} from "./chunk-AT4TNPWW.js";
|
|
139
|
+
import {
|
|
140
|
+
resolvePoeticDirFromContext
|
|
141
|
+
} from "./chunk-UI2F6DJ5.js";
|
|
142
|
+
import {
|
|
143
|
+
findProjectRoot
|
|
144
|
+
} from "./chunk-L6OM4A22.js";
|
|
145
|
+
import {
|
|
146
|
+
debugLog,
|
|
147
|
+
emitWarning
|
|
148
|
+
} from "./chunk-EJGCZGUF.js";
|
|
149
|
+
|
|
150
|
+
// src/orchestrator/pr-workflow-coordinator-impl-core.ts
|
|
151
|
+
import fs from "fs/promises";
|
|
152
|
+
import chalk3 from "chalk";
|
|
153
|
+
import { execa } from "execa";
|
|
154
|
+
|
|
155
|
+
// src/config/validation-helpers.ts
|
|
156
|
+
function validateTimeout(value, unit, options = {}) {
|
|
157
|
+
const {
|
|
158
|
+
defaultMs = 3e5,
|
|
159
|
+
// 5 minutes default
|
|
160
|
+
minMs = 1e3,
|
|
161
|
+
// 1 second minimum
|
|
162
|
+
maxMs = 72e5,
|
|
163
|
+
// 2 hours maximum
|
|
164
|
+
fieldName = "timeout"
|
|
165
|
+
} = options;
|
|
166
|
+
if (value === void 0 || value === null) {
|
|
167
|
+
return defaultMs;
|
|
168
|
+
}
|
|
169
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
170
|
+
throw new Error(`${fieldName} must be a finite number, got: ${value}`);
|
|
171
|
+
}
|
|
172
|
+
let timeoutMs;
|
|
173
|
+
switch (unit) {
|
|
174
|
+
case "milliseconds":
|
|
175
|
+
timeoutMs = value;
|
|
176
|
+
break;
|
|
177
|
+
case "seconds":
|
|
178
|
+
timeoutMs = value * 1e3;
|
|
179
|
+
break;
|
|
180
|
+
case "minutes":
|
|
181
|
+
timeoutMs = value * 60 * 1e3;
|
|
182
|
+
break;
|
|
183
|
+
default:
|
|
184
|
+
throw new Error(`Invalid timeout unit: ${unit}`);
|
|
185
|
+
}
|
|
186
|
+
if (timeoutMs < 0) {
|
|
187
|
+
throw new Error(`${fieldName} must be non-negative, got: ${value} ${unit}`);
|
|
188
|
+
}
|
|
189
|
+
if (timeoutMs < minMs) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`${fieldName} (${timeoutMs}ms) is below minimum (${minMs}ms). Provide at least ${minMs / 1e3}s.`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
if (timeoutMs > maxMs) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`${fieldName} (${timeoutMs}ms) exceeds maximum (${maxMs}ms). Provide at most ${maxMs / 6e4} minutes.`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return Math.round(timeoutMs);
|
|
200
|
+
}
|
|
201
|
+
function validateVerbosity(verboseBoolean, verbosityEnum) {
|
|
202
|
+
if (verbosityEnum !== void 0) {
|
|
203
|
+
const validLevels = ["silent", "minimal", "normal", "detailed", "debug"];
|
|
204
|
+
if (!validLevels.includes(verbosityEnum)) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`Invalid verbosity level: ${verbosityEnum}. Must be one of: ${validLevels.join(", ")}`
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return verbosityEnum;
|
|
210
|
+
}
|
|
211
|
+
if (verboseBoolean === true) {
|
|
212
|
+
return "detailed";
|
|
213
|
+
}
|
|
214
|
+
if (verboseBoolean === false) {
|
|
215
|
+
return "normal";
|
|
216
|
+
}
|
|
217
|
+
return "normal";
|
|
218
|
+
}
|
|
219
|
+
function validatePercentage(value, fieldName, options = {}) {
|
|
220
|
+
const {
|
|
221
|
+
min = 0,
|
|
222
|
+
max = 100,
|
|
223
|
+
defaultValue = min,
|
|
224
|
+
fieldName: name = fieldName,
|
|
225
|
+
allowFractional = false
|
|
226
|
+
} = options;
|
|
227
|
+
if (value === void 0 || value === null) {
|
|
228
|
+
return defaultValue;
|
|
229
|
+
}
|
|
230
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
231
|
+
throw new Error(`${name} must be a finite number, got: ${value}`);
|
|
232
|
+
}
|
|
233
|
+
if (value < min || value > max) {
|
|
234
|
+
throw new Error(`${name} must be between ${min} and ${max}, got: ${value}`);
|
|
235
|
+
}
|
|
236
|
+
if (!allowFractional && !Number.isInteger(value)) {
|
|
237
|
+
throw new Error(`${name} must be an integer, got: ${value}`);
|
|
238
|
+
}
|
|
239
|
+
return value;
|
|
240
|
+
}
|
|
241
|
+
function validateConfidence(value, options = {}) {
|
|
242
|
+
if (value === void 0 || value === null) {
|
|
243
|
+
return options.defaultValue ?? 85;
|
|
244
|
+
}
|
|
245
|
+
const fieldName = options.fieldName ?? "confidence";
|
|
246
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
247
|
+
throw new Error(`${fieldName} must be a finite number, got: ${value}`);
|
|
248
|
+
}
|
|
249
|
+
if (value >= 0 && value <= 1) {
|
|
250
|
+
return Math.round(value * 100);
|
|
251
|
+
}
|
|
252
|
+
return validatePercentage(value, fieldName, {
|
|
253
|
+
min: 0,
|
|
254
|
+
max: 100,
|
|
255
|
+
allowFractional: false
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/orchestrator/dependency-graph-resolver.ts
|
|
260
|
+
var DependencyGraphResolver = class {
|
|
261
|
+
/**
|
|
262
|
+
* Builds a dependency graph from task specifications
|
|
263
|
+
*
|
|
264
|
+
* Algorithm:
|
|
265
|
+
* 1. Create nodes with initial state (O(V))
|
|
266
|
+
* 2. Build adjacency list by reversing dependencies (O(E))
|
|
267
|
+
* 3. Compute topological levels via Kahn's algorithm (O(V+E))
|
|
268
|
+
*
|
|
269
|
+
* Complexity: O(V+E) where V = tasks, E = dependencies
|
|
270
|
+
*
|
|
271
|
+
* @param tasks - Array of task dependency specifications
|
|
272
|
+
* @returns Complete dependency graph with topological levels
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```typescript
|
|
276
|
+
* const graph = resolver.buildGraph([
|
|
277
|
+
* { id: "task1", dependencies: [] },
|
|
278
|
+
* { id: "task2", dependencies: ["task1"] }
|
|
279
|
+
* ]);
|
|
280
|
+
* // graph.nodes.get("task1").topologicalLevel === 0
|
|
281
|
+
* // graph.nodes.get("task2").topologicalLevel === 1
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
buildGraph(tasks) {
|
|
285
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
286
|
+
const adjacency = {};
|
|
287
|
+
for (const task of tasks) {
|
|
288
|
+
nodes.set(task.id, {
|
|
289
|
+
id: task.id,
|
|
290
|
+
state: task.dependencies.length === 0 ? "ready" : "pending",
|
|
291
|
+
dependencies: [...task.dependencies],
|
|
292
|
+
// Defensive copy
|
|
293
|
+
dependents: [],
|
|
294
|
+
// Will be populated below
|
|
295
|
+
topologicalLevel: -1,
|
|
296
|
+
// Will be computed below
|
|
297
|
+
metadata: task.metadata ? { ...task.metadata } : void 0
|
|
298
|
+
});
|
|
299
|
+
adjacency[task.id] = [];
|
|
300
|
+
}
|
|
301
|
+
for (const task of tasks) {
|
|
302
|
+
for (const depId of task.dependencies) {
|
|
303
|
+
const depNode = nodes.get(depId);
|
|
304
|
+
if (depNode) {
|
|
305
|
+
depNode.dependents.push(task.id);
|
|
306
|
+
if (!adjacency[depId]) {
|
|
307
|
+
adjacency[depId] = [];
|
|
308
|
+
}
|
|
309
|
+
const depAdjacency = adjacency[depId];
|
|
310
|
+
if (depAdjacency) {
|
|
311
|
+
depAdjacency.push(task.id);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
this.computeTopologicalLevels(nodes, adjacency);
|
|
317
|
+
return { nodes, adjacency };
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Computes topological levels using Kahn's algorithm
|
|
321
|
+
*
|
|
322
|
+
* Algorithm (modified Kahn's for level assignment):
|
|
323
|
+
* 1. Start with tasks that have no dependencies (level 0)
|
|
324
|
+
* 2. Process tasks level by level
|
|
325
|
+
* 3. When all dependencies of a task are processed, assign it to next level
|
|
326
|
+
* 4. Repeat until all tasks are assigned
|
|
327
|
+
*
|
|
328
|
+
* Complexity: O(V+E)
|
|
329
|
+
*
|
|
330
|
+
* Side effect: Updates topologicalLevel field in nodes
|
|
331
|
+
*
|
|
332
|
+
* @private
|
|
333
|
+
*/
|
|
334
|
+
computeTopologicalLevels(nodes, adjacency) {
|
|
335
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
336
|
+
for (const [id, node] of nodes.entries()) {
|
|
337
|
+
inDegree.set(id, node.dependencies.length);
|
|
338
|
+
}
|
|
339
|
+
let currentLevel = [];
|
|
340
|
+
for (const [id, degree] of inDegree.entries()) {
|
|
341
|
+
if (degree === 0) {
|
|
342
|
+
currentLevel.push(id);
|
|
343
|
+
const node = nodes.get(id);
|
|
344
|
+
if (node) {
|
|
345
|
+
node.topologicalLevel = 0;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
let level = 0;
|
|
350
|
+
while (currentLevel.length > 0) {
|
|
351
|
+
const nextLevel = [];
|
|
352
|
+
for (const taskId of currentLevel) {
|
|
353
|
+
const dependents = adjacency[taskId] || [];
|
|
354
|
+
for (const depId of dependents) {
|
|
355
|
+
const currentDegree = inDegree.get(depId) ?? 0;
|
|
356
|
+
const newDegree = currentDegree - 1;
|
|
357
|
+
inDegree.set(depId, newDegree);
|
|
358
|
+
if (newDegree === 0) {
|
|
359
|
+
nextLevel.push(depId);
|
|
360
|
+
const node = nodes.get(depId);
|
|
361
|
+
if (node) {
|
|
362
|
+
node.topologicalLevel = level + 1;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
currentLevel = nextLevel;
|
|
368
|
+
level++;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Validates graph structure for safe execution
|
|
373
|
+
*
|
|
374
|
+
* Checks:
|
|
375
|
+
* 1. No cycles (circular dependencies)
|
|
376
|
+
* 2. No missing dependencies (broken references)
|
|
377
|
+
* 3. No self-loops (task depends on itself)
|
|
378
|
+
* 4. Warns about orphaned nodes (unreachable tasks)
|
|
379
|
+
*
|
|
380
|
+
* Complexity: O(V+E)
|
|
381
|
+
*
|
|
382
|
+
* @param graph - Graph to validate
|
|
383
|
+
* @returns Validation result with errors and warnings
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```typescript
|
|
387
|
+
* const validation = resolver.validateGraph(graph);
|
|
388
|
+
* if (!validation.valid) {
|
|
389
|
+
* console.error("Invalid graph:", validation.errors);
|
|
390
|
+
* }
|
|
391
|
+
* if (validation.warnings?.length) {
|
|
392
|
+
* console.warn("Warnings:", validation.warnings);
|
|
393
|
+
* }
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
validateGraph(graph) {
|
|
397
|
+
const errors = [];
|
|
398
|
+
const warnings = [];
|
|
399
|
+
const cycleResult = this.detectCycles(graph);
|
|
400
|
+
if (cycleResult.hasCycle) {
|
|
401
|
+
const cyclePath = cycleResult.cyclePath?.join(" \u2192 ") || "unknown";
|
|
402
|
+
errors.push(`Circular dependency detected: ${cyclePath}`);
|
|
403
|
+
}
|
|
404
|
+
for (const [id, node] of graph.nodes.entries()) {
|
|
405
|
+
for (const depId of node.dependencies) {
|
|
406
|
+
if (!graph.nodes.has(depId)) {
|
|
407
|
+
errors.push(`Task "${id}" depends on non-existent task "${depId}"`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
for (const [id, node] of graph.nodes.entries()) {
|
|
412
|
+
if (node.dependencies.includes(id)) {
|
|
413
|
+
errors.push(`Task "${id}" has a self-loop (depends on itself)`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
const reachable = this.findReachableNodes(graph);
|
|
417
|
+
for (const id of graph.nodes.keys()) {
|
|
418
|
+
if (!reachable.has(id)) {
|
|
419
|
+
warnings.push(`Task "${id}" is orphaned (unreachable from entry points)`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
for (const [id, node] of graph.nodes.entries()) {
|
|
423
|
+
if (node.topologicalLevel === -1 && reachable.has(id)) {
|
|
424
|
+
warnings.push(`Task "${id}" has unassigned topological level (possible algorithm bug)`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
valid: errors.length === 0,
|
|
429
|
+
errors,
|
|
430
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Detects cycles in the graph using DFS with colored nodes
|
|
435
|
+
*
|
|
436
|
+
* Algorithm:
|
|
437
|
+
* 1. Mark all nodes as WHITE (unvisited)
|
|
438
|
+
* 2. For each WHITE node, start DFS traversal
|
|
439
|
+
* 3. Mark nodes GRAY when entering, BLACK when leaving
|
|
440
|
+
* 4. If we encounter a GRAY node, we've found a cycle
|
|
441
|
+
*
|
|
442
|
+
* Complexity: O(V+E)
|
|
443
|
+
*
|
|
444
|
+
* @private
|
|
445
|
+
* @returns Cycle detection result with path if found
|
|
446
|
+
*/
|
|
447
|
+
detectCycles(graph) {
|
|
448
|
+
const colors = /* @__PURE__ */ new Map();
|
|
449
|
+
const parent = /* @__PURE__ */ new Map();
|
|
450
|
+
for (const id of graph.nodes.keys()) {
|
|
451
|
+
colors.set(id, "WHITE");
|
|
452
|
+
}
|
|
453
|
+
for (const id of graph.nodes.keys()) {
|
|
454
|
+
if (colors.get(id) === "WHITE") {
|
|
455
|
+
const cyclePath = this.dfsVisit(id, graph, colors, parent, []);
|
|
456
|
+
if (cyclePath.length > 0) {
|
|
457
|
+
return { hasCycle: true, cyclePath };
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return { hasCycle: false };
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* DFS visit helper for cycle detection
|
|
465
|
+
*
|
|
466
|
+
* @private
|
|
467
|
+
* @returns Cycle path if found, empty array otherwise
|
|
468
|
+
*/
|
|
469
|
+
dfsVisit(nodeId, graph, colors, parent, path3) {
|
|
470
|
+
colors.set(nodeId, "GRAY");
|
|
471
|
+
path3.push(nodeId);
|
|
472
|
+
const adjacentNodes = graph.adjacency[nodeId] || [];
|
|
473
|
+
for (const adjId of adjacentNodes) {
|
|
474
|
+
const color = colors.get(adjId);
|
|
475
|
+
if (color === "GRAY") {
|
|
476
|
+
const cycleStart = path3.indexOf(adjId);
|
|
477
|
+
return [...path3.slice(cycleStart), adjId];
|
|
478
|
+
}
|
|
479
|
+
if (color === "WHITE") {
|
|
480
|
+
parent.set(adjId, nodeId);
|
|
481
|
+
const cyclePath = this.dfsVisit(adjId, graph, colors, parent, path3);
|
|
482
|
+
if (cyclePath.length > 0) {
|
|
483
|
+
return cyclePath;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
path3.pop();
|
|
488
|
+
colors.set(nodeId, "BLACK");
|
|
489
|
+
return [];
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Finds all reachable nodes from entry points (tasks with no dependencies)
|
|
493
|
+
*
|
|
494
|
+
* Uses Tarjan's algorithm to detect strongly connected components (cycles),
|
|
495
|
+
* then performs reachability analysis that properly handles cycles.
|
|
496
|
+
*
|
|
497
|
+
* Algorithm:
|
|
498
|
+
* 1. Find all SCCs (strongly connected components) using Tarjan's algorithm
|
|
499
|
+
* 2. Build condensed graph where each SCC is a single node
|
|
500
|
+
* 3. Perform BFS from entry point SCCs
|
|
501
|
+
* 4. A node is reachable if its SCC is reachable
|
|
502
|
+
*
|
|
503
|
+
* This ensures nodes in cycles are marked reachable if any node in the cycle
|
|
504
|
+
* is reachable from an entry point.
|
|
505
|
+
*
|
|
506
|
+
* Complexity: O(V+E)
|
|
507
|
+
*
|
|
508
|
+
* @private
|
|
509
|
+
* @returns Set of reachable task IDs
|
|
510
|
+
*/
|
|
511
|
+
findReachableNodes(graph) {
|
|
512
|
+
const sccs = this.findStronglyConnectedComponents(graph);
|
|
513
|
+
const nodeToScc = /* @__PURE__ */ new Map();
|
|
514
|
+
for (let i = 0; i < sccs.length; i++) {
|
|
515
|
+
const scc = sccs[i];
|
|
516
|
+
if (!scc) continue;
|
|
517
|
+
for (const nodeId of scc) {
|
|
518
|
+
nodeToScc.set(nodeId, i);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
const sccDeps = Array.from({ length: sccs.length }, () => /* @__PURE__ */ new Set());
|
|
522
|
+
for (const [id, node] of graph.nodes.entries()) {
|
|
523
|
+
const sccIdx = nodeToScc.get(id);
|
|
524
|
+
if (sccIdx === void 0) continue;
|
|
525
|
+
for (const depId of node.dependencies) {
|
|
526
|
+
const depSccIdx = nodeToScc.get(depId);
|
|
527
|
+
if (depSccIdx !== void 0 && depSccIdx !== sccIdx) {
|
|
528
|
+
sccDeps[depSccIdx]?.add(sccIdx);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
const entrySCCs = /* @__PURE__ */ new Set();
|
|
533
|
+
for (let i = 0; i < sccs.length; i++) {
|
|
534
|
+
const scc = sccs[i];
|
|
535
|
+
if (!scc) continue;
|
|
536
|
+
for (const nodeId of scc) {
|
|
537
|
+
const node = graph.nodes.get(nodeId);
|
|
538
|
+
if (!node) continue;
|
|
539
|
+
if (node.dependencies.length === 0) {
|
|
540
|
+
entrySCCs.add(i);
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
const hasOnlyInternalDeps = node.dependencies.every((depId) => {
|
|
544
|
+
const depSccIdx = nodeToScc.get(depId);
|
|
545
|
+
return depSccIdx === i;
|
|
546
|
+
});
|
|
547
|
+
if (hasOnlyInternalDeps && scc.length === 1) {
|
|
548
|
+
entrySCCs.add(i);
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const reachableSCCs = /* @__PURE__ */ new Set();
|
|
554
|
+
const queue = Array.from(entrySCCs);
|
|
555
|
+
for (const sccIdx of queue) {
|
|
556
|
+
reachableSCCs.add(sccIdx);
|
|
557
|
+
}
|
|
558
|
+
while (queue.length > 0) {
|
|
559
|
+
const current = queue.shift();
|
|
560
|
+
const deps = sccDeps[current];
|
|
561
|
+
if (!deps) continue;
|
|
562
|
+
for (const depIdx of deps) {
|
|
563
|
+
if (!reachableSCCs.has(depIdx)) {
|
|
564
|
+
reachableSCCs.add(depIdx);
|
|
565
|
+
queue.push(depIdx);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
570
|
+
for (const sccIdx of reachableSCCs) {
|
|
571
|
+
const scc = sccs[sccIdx];
|
|
572
|
+
if (!scc) continue;
|
|
573
|
+
for (const nodeId of scc) {
|
|
574
|
+
reachable.add(nodeId);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return reachable;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Finds strongly connected components using Tarjan's algorithm
|
|
581
|
+
*
|
|
582
|
+
* A strongly connected component is a maximal set of nodes where every node
|
|
583
|
+
* is reachable from every other node. Cycles form SCCs.
|
|
584
|
+
*
|
|
585
|
+
* Complexity: O(V+E)
|
|
586
|
+
*
|
|
587
|
+
* @private
|
|
588
|
+
* @returns Array of SCCs, where each SCC is an array of node IDs
|
|
589
|
+
*/
|
|
590
|
+
findStronglyConnectedComponents(graph) {
|
|
591
|
+
const sccs = [];
|
|
592
|
+
const index = /* @__PURE__ */ new Map();
|
|
593
|
+
const lowlink = /* @__PURE__ */ new Map();
|
|
594
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
595
|
+
const stack = [];
|
|
596
|
+
let currentIndex = 0;
|
|
597
|
+
const strongConnect = (nodeId) => {
|
|
598
|
+
index.set(nodeId, currentIndex);
|
|
599
|
+
lowlink.set(nodeId, currentIndex);
|
|
600
|
+
currentIndex++;
|
|
601
|
+
stack.push(nodeId);
|
|
602
|
+
onStack.add(nodeId);
|
|
603
|
+
const dependents = graph.adjacency[nodeId] || [];
|
|
604
|
+
for (const depId of dependents) {
|
|
605
|
+
if (!index.has(depId)) {
|
|
606
|
+
strongConnect(depId);
|
|
607
|
+
lowlink.set(nodeId, Math.min(lowlink.get(nodeId), lowlink.get(depId)));
|
|
608
|
+
} else if (onStack.has(depId)) {
|
|
609
|
+
lowlink.set(nodeId, Math.min(lowlink.get(nodeId), index.get(depId)));
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (lowlink.get(nodeId) === index.get(nodeId)) {
|
|
613
|
+
const scc = [];
|
|
614
|
+
let w;
|
|
615
|
+
do {
|
|
616
|
+
w = stack.pop();
|
|
617
|
+
onStack.delete(w);
|
|
618
|
+
scc.push(w);
|
|
619
|
+
} while (w !== nodeId);
|
|
620
|
+
sccs.push(scc);
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
for (const nodeId of graph.nodes.keys()) {
|
|
624
|
+
if (!index.has(nodeId)) {
|
|
625
|
+
strongConnect(nodeId);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return sccs;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Gets all tasks that are ready to execute
|
|
632
|
+
*
|
|
633
|
+
* A task is ready if:
|
|
634
|
+
* - All dependencies are completed
|
|
635
|
+
* - Current state is "ready"
|
|
636
|
+
*
|
|
637
|
+
* Complexity: O(V) - scans all nodes
|
|
638
|
+
*
|
|
639
|
+
* @param graph - Current graph state
|
|
640
|
+
* @returns Array of task IDs ready to execute, sorted by topological level
|
|
641
|
+
*
|
|
642
|
+
* @example
|
|
643
|
+
* ```typescript
|
|
644
|
+
* const readyTasks = resolver.getReadyTasks(graph);
|
|
645
|
+
* // Execute ready tasks in parallel
|
|
646
|
+
* await Promise.all(readyTasks.map(id => executeTask(id)));
|
|
647
|
+
* ```
|
|
648
|
+
*/
|
|
649
|
+
getReadyTasks(graph) {
|
|
650
|
+
const ready = [];
|
|
651
|
+
for (const [id, node] of graph.nodes.entries()) {
|
|
652
|
+
if (node.state === "ready") {
|
|
653
|
+
ready.push({ id, level: node.topologicalLevel });
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
ready.sort((a, b) => a.level - b.level);
|
|
657
|
+
return ready.map((r) => r.id);
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Marks a task as running
|
|
661
|
+
*
|
|
662
|
+
* Complexity: O(1)
|
|
663
|
+
*
|
|
664
|
+
* @param graph - Graph to update
|
|
665
|
+
* @param taskId - Task to mark as running
|
|
666
|
+
* @throws Error if task is not in "ready" state
|
|
667
|
+
*/
|
|
668
|
+
markRunning(graph, taskId) {
|
|
669
|
+
const node = graph.nodes.get(taskId);
|
|
670
|
+
if (!node) {
|
|
671
|
+
throw new Error(`Task "${taskId}" not found in graph`);
|
|
672
|
+
}
|
|
673
|
+
if (node.state !== "ready") {
|
|
674
|
+
throw new Error(`Task "${taskId}" is not ready (current state: ${node.state})`);
|
|
675
|
+
}
|
|
676
|
+
node.state = "running";
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Marks a task as completed and updates dependent tasks
|
|
680
|
+
*
|
|
681
|
+
* Algorithm:
|
|
682
|
+
* 1. Set task state to "completed"
|
|
683
|
+
* 2. For each dependent task:
|
|
684
|
+
* - Check if all dependencies are completed
|
|
685
|
+
* - If yes, mark as "ready"
|
|
686
|
+
*
|
|
687
|
+
* Complexity: O(D) where D = number of dependents
|
|
688
|
+
*
|
|
689
|
+
* @param graph - Graph to update
|
|
690
|
+
* @param taskId - Task to mark as completed
|
|
691
|
+
* @returns Array of newly ready task IDs
|
|
692
|
+
*
|
|
693
|
+
* @example
|
|
694
|
+
* ```typescript
|
|
695
|
+
* const newlyReady = resolver.markCompleted(graph, "compile");
|
|
696
|
+
* // Execute newly ready tasks
|
|
697
|
+
* for (const id of newlyReady) {
|
|
698
|
+
* await executeTask(id);
|
|
699
|
+
* }
|
|
700
|
+
* ```
|
|
701
|
+
*/
|
|
702
|
+
markCompleted(graph, taskId) {
|
|
703
|
+
const node = graph.nodes.get(taskId);
|
|
704
|
+
if (!node) {
|
|
705
|
+
throw new Error(`Task "${taskId}" not found in graph`);
|
|
706
|
+
}
|
|
707
|
+
if (node.state !== "running") {
|
|
708
|
+
throw new Error(`Task "${taskId}" cannot be completed from state "${node.state}"`);
|
|
709
|
+
}
|
|
710
|
+
node.state = "completed";
|
|
711
|
+
const newlyReady = [];
|
|
712
|
+
for (const depId of node.dependents) {
|
|
713
|
+
const depNode = graph.nodes.get(depId);
|
|
714
|
+
if (!depNode) continue;
|
|
715
|
+
if (depNode.state !== "pending" && depNode.state !== "blocked") {
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
const allDepsCompleted = depNode.dependencies.every((d) => {
|
|
719
|
+
const dNode = graph.nodes.get(d);
|
|
720
|
+
return dNode?.state === "completed";
|
|
721
|
+
});
|
|
722
|
+
if (allDepsCompleted) {
|
|
723
|
+
depNode.state = "ready";
|
|
724
|
+
newlyReady.push(depId);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return newlyReady;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Marks a task as failed and optionally blocks or resets dependent tasks
|
|
731
|
+
*
|
|
732
|
+
* Algorithm:
|
|
733
|
+
* 1. Set task state to "failed"
|
|
734
|
+
* 2. If blockDependents=true: Recursively mark all transitive dependents as "blocked"
|
|
735
|
+
* 3. If blockDependents=false: Reset dependents to "pending" for retry workflows
|
|
736
|
+
*
|
|
737
|
+
* Complexity: O(V) worst case (if failure cascades through entire graph)
|
|
738
|
+
*
|
|
739
|
+
* @param graph - Graph to update
|
|
740
|
+
* @param taskId - Task to mark as failed
|
|
741
|
+
* @param error - Optional error message
|
|
742
|
+
* @param blockDependents - If true, blocks dependents; if false, resets to pending (default: true)
|
|
743
|
+
* @returns Array of affected task IDs (blocked or reset)
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* ```typescript
|
|
747
|
+
* // Block dependents (default behavior)
|
|
748
|
+
* const blocked = resolver.markFailed(graph, "compile", "Type errors");
|
|
749
|
+
* console.log(`Blocked ${blocked.length} dependent tasks`);
|
|
750
|
+
*
|
|
751
|
+
* // Allow retry by resetting dependents to pending
|
|
752
|
+
* const reset = resolver.markFailed(graph, "compile", "Type errors", false);
|
|
753
|
+
* console.log(`Reset ${reset.length} dependents for retry`);
|
|
754
|
+
* ```
|
|
755
|
+
*/
|
|
756
|
+
markFailed(graph, taskId, error, blockDependents = true) {
|
|
757
|
+
const node = graph.nodes.get(taskId);
|
|
758
|
+
if (!node) {
|
|
759
|
+
throw new Error(`Task "${taskId}" not found in graph`);
|
|
760
|
+
}
|
|
761
|
+
node.state = "failed";
|
|
762
|
+
if (error && node.metadata) {
|
|
763
|
+
node.metadata.error = error;
|
|
764
|
+
} else if (error) {
|
|
765
|
+
node.metadata = { error };
|
|
766
|
+
}
|
|
767
|
+
const affected = [];
|
|
768
|
+
const queue = [...node.dependents];
|
|
769
|
+
const visited = /* @__PURE__ */ new Set();
|
|
770
|
+
while (queue.length > 0) {
|
|
771
|
+
const depId = queue.shift();
|
|
772
|
+
if (visited.has(depId)) continue;
|
|
773
|
+
visited.add(depId);
|
|
774
|
+
const depNode = graph.nodes.get(depId);
|
|
775
|
+
if (!depNode) continue;
|
|
776
|
+
if (depNode.state === "completed" || depNode.state === "failed") {
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
if (blockDependents) {
|
|
780
|
+
depNode.state = "blocked";
|
|
781
|
+
affected.push(depId);
|
|
782
|
+
queue.push(...depNode.dependents);
|
|
783
|
+
} else {
|
|
784
|
+
const hasFailedDependency = depNode.dependencies.some(
|
|
785
|
+
(dependencyId) => dependencyId !== taskId && graph.nodes.get(dependencyId)?.state === "failed"
|
|
786
|
+
);
|
|
787
|
+
depNode.state = hasFailedDependency ? "blocked" : "pending";
|
|
788
|
+
affected.push(depId);
|
|
789
|
+
queue.push(...depNode.dependents);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return affected;
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Checks if all tasks are in a terminal state
|
|
796
|
+
*
|
|
797
|
+
* Terminal states: completed, failed, blocked
|
|
798
|
+
*
|
|
799
|
+
* Complexity: O(V)
|
|
800
|
+
*
|
|
801
|
+
* @param graph - Graph to check
|
|
802
|
+
* @returns True if all tasks are completed, failed, or blocked
|
|
803
|
+
*/
|
|
804
|
+
isComplete(graph) {
|
|
805
|
+
for (const node of graph.nodes.values()) {
|
|
806
|
+
if (node.state !== "completed" && node.state !== "failed" && node.state !== "blocked") {
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return true;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Gets tasks at a specific topological level
|
|
814
|
+
*
|
|
815
|
+
* Useful for level-by-level execution strategies.
|
|
816
|
+
*
|
|
817
|
+
* Complexity: O(V)
|
|
818
|
+
*
|
|
819
|
+
* @param graph - Graph to query
|
|
820
|
+
* @param level - Topological level
|
|
821
|
+
* @returns Array of task IDs at the specified level
|
|
822
|
+
*
|
|
823
|
+
* @example
|
|
824
|
+
* ```typescript
|
|
825
|
+
* // Execute all level 0 tasks first
|
|
826
|
+
* const level0 = resolver.getTasksByLevel(graph, 0);
|
|
827
|
+
* await Promise.all(level0.map(id => executeTask(id)));
|
|
828
|
+
* ```
|
|
829
|
+
*/
|
|
830
|
+
getTasksByLevel(graph, level) {
|
|
831
|
+
const tasks = [];
|
|
832
|
+
for (const [id, node] of graph.nodes.entries()) {
|
|
833
|
+
if (node.topologicalLevel === level) {
|
|
834
|
+
tasks.push(id);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return tasks;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Gets the maximum topological level in the graph
|
|
841
|
+
*
|
|
842
|
+
* Complexity: O(V)
|
|
843
|
+
*
|
|
844
|
+
* @param graph - Graph to analyze
|
|
845
|
+
* @returns Maximum level, or -1 if graph is empty
|
|
846
|
+
*/
|
|
847
|
+
getMaxLevel(graph) {
|
|
848
|
+
let maxLevel = -1;
|
|
849
|
+
for (const node of graph.nodes.values()) {
|
|
850
|
+
if (node.topologicalLevel > maxLevel) {
|
|
851
|
+
maxLevel = node.topologicalLevel;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
return maxLevel;
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Computes comprehensive statistics about the graph
|
|
858
|
+
*
|
|
859
|
+
* Useful for monitoring, debugging, and optimization.
|
|
860
|
+
*
|
|
861
|
+
* Complexity: O(V)
|
|
862
|
+
*
|
|
863
|
+
* @param graph - Graph to analyze
|
|
864
|
+
* @returns Detailed statistics
|
|
865
|
+
*
|
|
866
|
+
* @example
|
|
867
|
+
* ```typescript
|
|
868
|
+
* const stats = resolver.getStatistics(graph);
|
|
869
|
+
* console.log(`Progress: ${stats.completedTasks}/${stats.totalTasks}`);
|
|
870
|
+
* console.log(`Graph depth: ${stats.maxLevel + 1} levels`);
|
|
871
|
+
* ```
|
|
872
|
+
*/
|
|
873
|
+
getStatistics(graph) {
|
|
874
|
+
let readyCount = 0;
|
|
875
|
+
let runningCount = 0;
|
|
876
|
+
let completedCount = 0;
|
|
877
|
+
let failedCount = 0;
|
|
878
|
+
let blockedCount = 0;
|
|
879
|
+
let maxLevel = -1;
|
|
880
|
+
let totalDeps = 0;
|
|
881
|
+
let entryPoints = 0;
|
|
882
|
+
let terminalNodes = 0;
|
|
883
|
+
for (const node of graph.nodes.values()) {
|
|
884
|
+
switch (node.state) {
|
|
885
|
+
case "ready":
|
|
886
|
+
readyCount++;
|
|
887
|
+
break;
|
|
888
|
+
case "running":
|
|
889
|
+
runningCount++;
|
|
890
|
+
break;
|
|
891
|
+
case "completed":
|
|
892
|
+
completedCount++;
|
|
893
|
+
break;
|
|
894
|
+
case "failed":
|
|
895
|
+
failedCount++;
|
|
896
|
+
break;
|
|
897
|
+
case "blocked":
|
|
898
|
+
blockedCount++;
|
|
899
|
+
break;
|
|
900
|
+
}
|
|
901
|
+
if (node.topologicalLevel > maxLevel) {
|
|
902
|
+
maxLevel = node.topologicalLevel;
|
|
903
|
+
}
|
|
904
|
+
totalDeps += node.dependencies.length;
|
|
905
|
+
if (node.dependencies.length === 0) {
|
|
906
|
+
entryPoints++;
|
|
907
|
+
}
|
|
908
|
+
if (node.dependents.length === 0) {
|
|
909
|
+
terminalNodes++;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
const totalTasks = graph.nodes.size;
|
|
913
|
+
return {
|
|
914
|
+
totalTasks,
|
|
915
|
+
readyTasks: readyCount,
|
|
916
|
+
runningTasks: runningCount,
|
|
917
|
+
completedTasks: completedCount,
|
|
918
|
+
failedTasks: failedCount,
|
|
919
|
+
blockedTasks: blockedCount,
|
|
920
|
+
maxLevel,
|
|
921
|
+
averageDependencies: totalTasks > 0 ? totalDeps / totalTasks : 0,
|
|
922
|
+
entryPoints,
|
|
923
|
+
terminalNodes
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Resets all task states to initial values
|
|
928
|
+
*
|
|
929
|
+
* Useful for re-executing the graph or recovering from failures.
|
|
930
|
+
*
|
|
931
|
+
* Complexity: O(V)
|
|
932
|
+
*
|
|
933
|
+
* @param graph - Graph to reset
|
|
934
|
+
*
|
|
935
|
+
* @example
|
|
936
|
+
* ```typescript
|
|
937
|
+
* // Retry execution after fixing issues
|
|
938
|
+
* resolver.reset(graph);
|
|
939
|
+
* const validation = resolver.validateGraph(graph);
|
|
940
|
+
* if (validation.valid) {
|
|
941
|
+
* // Execute again
|
|
942
|
+
* }
|
|
943
|
+
* ```
|
|
944
|
+
*/
|
|
945
|
+
reset(graph) {
|
|
946
|
+
for (const node of graph.nodes.values()) {
|
|
947
|
+
node.state = node.dependencies.length === 0 ? "ready" : "pending";
|
|
948
|
+
if (node.metadata?.error) {
|
|
949
|
+
delete node.metadata.error;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
// src/orchestrator/services/cleanup-service.ts
|
|
956
|
+
import path from "path";
|
|
957
|
+
import chalk from "chalk";
|
|
958
|
+
var CleanupService = class {
|
|
959
|
+
constructor(prManager, verbose = false, worktreeCleanup, repoRoot = process.cwd()) {
|
|
960
|
+
this.prManager = prManager;
|
|
961
|
+
this.verbose = verbose;
|
|
962
|
+
this.worktreeCleanup = worktreeCleanup;
|
|
963
|
+
this.repoRoot = path.resolve(repoRoot);
|
|
964
|
+
}
|
|
965
|
+
prManager;
|
|
966
|
+
verbose;
|
|
967
|
+
worktreeCleanup;
|
|
968
|
+
repoRoot;
|
|
969
|
+
/**
|
|
970
|
+
* Clean up non-winning PRs and branches after competition
|
|
971
|
+
*/
|
|
972
|
+
async cleanupCompetition(results, winner, options = {}) {
|
|
973
|
+
const labMode = options.labMode === true || LabPathResolver.getInstance().isLabMode();
|
|
974
|
+
if (labMode) {
|
|
975
|
+
if (this.verbose || options.verbose) {
|
|
976
|
+
console.log(
|
|
977
|
+
chalk.cyan("Lab mode active - skipping cleanup to preserve worktrees for analysis")
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
const { verbose = this.verbose, preserveSubmittedOnly = false, taskId } = options;
|
|
983
|
+
const losers = results.filter((r) => {
|
|
984
|
+
if (!r.prNumber) {
|
|
985
|
+
if (verbose) {
|
|
986
|
+
console.log(chalk.gray(` \u2022 Skipping ${r.provider} ${r.strategy} - no PR number`));
|
|
987
|
+
}
|
|
988
|
+
return false;
|
|
989
|
+
}
|
|
990
|
+
if (winner.prNumber) {
|
|
991
|
+
const isLoser2 = r.prNumber !== winner.prNumber;
|
|
992
|
+
if (verbose && !isLoser2) {
|
|
993
|
+
console.log(
|
|
994
|
+
chalk.gray(` \u2022 Skipping ${r.provider} ${r.strategy} - winner's PR #${r.prNumber}`)
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
return isLoser2;
|
|
998
|
+
}
|
|
999
|
+
const isLoser = r.variant !== winner.variant || r.provider !== winner.provider;
|
|
1000
|
+
if (verbose && !isLoser) {
|
|
1001
|
+
console.log(chalk.gray(` \u2022 Skipping ${r.provider} ${r.strategy} - winner's variant`));
|
|
1002
|
+
}
|
|
1003
|
+
return isLoser;
|
|
1004
|
+
});
|
|
1005
|
+
const applyPendingLosers = [];
|
|
1006
|
+
const eligibleLosers = losers.filter((r) => {
|
|
1007
|
+
if (this.requiresApplyRecovery(r)) {
|
|
1008
|
+
applyPendingLosers.push(r);
|
|
1009
|
+
return false;
|
|
1010
|
+
}
|
|
1011
|
+
return true;
|
|
1012
|
+
});
|
|
1013
|
+
if (applyPendingLosers.length > 0) {
|
|
1014
|
+
console.log(chalk.gray("\nPreserving PRs awaiting local apply:"));
|
|
1015
|
+
applyPendingLosers.forEach((variant) => {
|
|
1016
|
+
const label = this.formatProviderVariantLabel(variant);
|
|
1017
|
+
const prLabel = variant.prNumber ? `PR #${variant.prNumber}` : "PR pending";
|
|
1018
|
+
const hint = this.getRecoveryHint(variant);
|
|
1019
|
+
console.log(
|
|
1020
|
+
chalk.gray(
|
|
1021
|
+
` \u2022 ${prLabel} - ${label}${hint ? ` \u2192 ${hint}` : " (run apply command before cleanup)"}`
|
|
1022
|
+
)
|
|
1023
|
+
);
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
const shouldPreserveSubmittedPRs = preserveSubmittedOnly;
|
|
1027
|
+
const preservedSubmittedPRs = [];
|
|
1028
|
+
const losersToCleanup = eligibleLosers.filter((r) => {
|
|
1029
|
+
if (!shouldPreserveSubmittedPRs) {
|
|
1030
|
+
return true;
|
|
1031
|
+
}
|
|
1032
|
+
if (this.isPreservedSubmittedVariant(r)) {
|
|
1033
|
+
preservedSubmittedPRs.push(r);
|
|
1034
|
+
if (verbose) {
|
|
1035
|
+
console.log(
|
|
1036
|
+
chalk.gray(
|
|
1037
|
+
` \u2022 Preserving ${r.provider} ${r.strategy} PR #${r.prNumber ?? "?"} (submit-only mode)`
|
|
1038
|
+
)
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
return false;
|
|
1042
|
+
}
|
|
1043
|
+
return true;
|
|
1044
|
+
});
|
|
1045
|
+
if (preservedSubmittedPRs.length > 0) {
|
|
1046
|
+
await this.displayPreservedPRs(preservedSubmittedPRs);
|
|
1047
|
+
}
|
|
1048
|
+
if (losersToCleanup.length === 0) {
|
|
1049
|
+
if (preservedSubmittedPRs.length === 0 && verbose) {
|
|
1050
|
+
console.log(chalk.gray("\nNo losing PRs to close - checking for orphan branches..."));
|
|
1051
|
+
}
|
|
1052
|
+
if (taskId) {
|
|
1053
|
+
await this.cleanupOrphanBranchesByTask(taskId, winner);
|
|
1054
|
+
}
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
const hasBranchOnlyLosers = results.some(
|
|
1058
|
+
(r) => !r.prNumber && (r.variant !== winner.variant || r.provider !== winner.provider)
|
|
1059
|
+
);
|
|
1060
|
+
console.log(chalk.blue(`
|
|
1061
|
+
Cleaning up ${losersToCleanup.length} non-winning variant(s)...
|
|
1062
|
+
`));
|
|
1063
|
+
const cleanupResults = {
|
|
1064
|
+
success: 0,
|
|
1065
|
+
degraded: 0,
|
|
1066
|
+
failed: 0,
|
|
1067
|
+
errors: [],
|
|
1068
|
+
warnings: [],
|
|
1069
|
+
preserved: preservedSubmittedPRs
|
|
1070
|
+
};
|
|
1071
|
+
const CONCURRENCY_LIMIT = 3;
|
|
1072
|
+
const startTime = Date.now();
|
|
1073
|
+
for (let batchStart = 0; batchStart < losersToCleanup.length; batchStart += CONCURRENCY_LIMIT) {
|
|
1074
|
+
const batchEnd = Math.min(batchStart + CONCURRENCY_LIMIT, losersToCleanup.length);
|
|
1075
|
+
const batch = losersToCleanup.slice(batchStart, batchEnd);
|
|
1076
|
+
if (verbose) {
|
|
1077
|
+
console.log(
|
|
1078
|
+
chalk.gray(
|
|
1079
|
+
`
|
|
1080
|
+
[Batch ${Math.floor(batchStart / CONCURRENCY_LIMIT) + 1}] Processing ${batch.length} variant(s) in parallel...`
|
|
1081
|
+
)
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
const batchPromises = batch.map(
|
|
1085
|
+
(loser, batchIndex) => this.cleanupSingleVariant(loser, batchStart + batchIndex, losersToCleanup.length, verbose)
|
|
1086
|
+
);
|
|
1087
|
+
const batchResults = await Promise.allSettled(batchPromises);
|
|
1088
|
+
batchResults.forEach((result, index) => {
|
|
1089
|
+
if (result.status === "fulfilled") {
|
|
1090
|
+
const variantResult = result.value;
|
|
1091
|
+
cleanupResults.success += variantResult.success ? 1 : 0;
|
|
1092
|
+
cleanupResults.degraded += variantResult.degraded ? 1 : 0;
|
|
1093
|
+
cleanupResults.failed += variantResult.success ? 0 : 1;
|
|
1094
|
+
if (variantResult.error) {
|
|
1095
|
+
cleanupResults.errors.push(variantResult.error);
|
|
1096
|
+
}
|
|
1097
|
+
if (variantResult.warning) {
|
|
1098
|
+
cleanupResults.warnings.push(variantResult.warning);
|
|
1099
|
+
}
|
|
1100
|
+
} else {
|
|
1101
|
+
const loser = batch[index];
|
|
1102
|
+
if (loser) {
|
|
1103
|
+
const errorMsg = `Unhandled exception during cleanup of ${loser.provider} ${loser.strategy}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`;
|
|
1104
|
+
cleanupResults.errors.push(errorMsg);
|
|
1105
|
+
cleanupResults.failed++;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
if (batchEnd < losersToCleanup.length) {
|
|
1110
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
const elapsedSeconds = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
1114
|
+
console.log(chalk.bold(`
|
|
1115
|
+
Cleanup Summary (${elapsedSeconds}s):`));
|
|
1116
|
+
console.log(chalk.green(` Successfully cleaned up: ${cleanupResults.success} variant(s)`));
|
|
1117
|
+
if (cleanupResults.degraded > 0) {
|
|
1118
|
+
console.log(chalk.yellow(` Completed with warnings: ${cleanupResults.degraded} variant(s)`));
|
|
1119
|
+
cleanupResults.warnings.forEach((warning) => {
|
|
1120
|
+
console.log(chalk.gray(` \u2022 ${warning}`));
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
if (cleanupResults.failed > 0) {
|
|
1124
|
+
console.log(chalk.red(` Failed cleanup: ${cleanupResults.failed} variant(s)`));
|
|
1125
|
+
console.log(chalk.yellow("\nManual cleanup required for:"));
|
|
1126
|
+
cleanupResults.errors.forEach((error) => {
|
|
1127
|
+
console.log(chalk.gray(` \u2022 ${error}`));
|
|
1128
|
+
});
|
|
1129
|
+
console.log(chalk.gray("\nManual cleanup commands:"));
|
|
1130
|
+
losersToCleanup.forEach((loser) => {
|
|
1131
|
+
console.log(chalk.gray(` # For ${loser.provider} ${loser.strategy}:`));
|
|
1132
|
+
console.log(chalk.gray(` gh pr close ${loser.prNumber} || true`));
|
|
1133
|
+
if (taskId && loser.provider && loser.strategy && loser.executionMode) {
|
|
1134
|
+
const branch = poeticVariantBranchName({
|
|
1135
|
+
taskId,
|
|
1136
|
+
provider: loser.provider,
|
|
1137
|
+
strategy: loser.strategy,
|
|
1138
|
+
executionMode: loser.executionMode,
|
|
1139
|
+
model: loser.model
|
|
1140
|
+
});
|
|
1141
|
+
console.log(chalk.gray(` git branch -D ${branch} || true`));
|
|
1142
|
+
} else {
|
|
1143
|
+
console.log(chalk.gray(" gh pr view <PR_NUMBER> --json headRefName -q '.headRefName'"));
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
} else {
|
|
1147
|
+
console.log(chalk.green("\nAll cleanup operations completed successfully!\n"));
|
|
1148
|
+
}
|
|
1149
|
+
if (hasBranchOnlyLosers && taskId) {
|
|
1150
|
+
await this.cleanupOrphanBranchesByTask(taskId, winner);
|
|
1151
|
+
}
|
|
1152
|
+
if (cleanupResults.failed > 0) {
|
|
1153
|
+
const summary = cleanupResults.errors.join("; ");
|
|
1154
|
+
throw new Error(
|
|
1155
|
+
`Cleanup partially failed: ${cleanupResults.failed}/${losersToCleanup.length} variant(s) \u2014 ${summary}`
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Cleanup a single variant (PR close + branch delete)
|
|
1161
|
+
* Returns result object for collection by Promise.allSettled()
|
|
1162
|
+
*/
|
|
1163
|
+
async cleanupSingleVariant(loser, index, total, verbose = false) {
|
|
1164
|
+
const label = `${loser.provider} ${loser.strategy}`;
|
|
1165
|
+
try {
|
|
1166
|
+
if (verbose) {
|
|
1167
|
+
console.log(
|
|
1168
|
+
chalk.gray(` [${index + 1}/${total}] Processing ${label} (PR #${loser.prNumber})`)
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
let prState = "unknown";
|
|
1172
|
+
let branch;
|
|
1173
|
+
try {
|
|
1174
|
+
const prData = await this.prManager.executeWithRetry(
|
|
1175
|
+
async () => {
|
|
1176
|
+
const { execa: execa2 } = await import("execa");
|
|
1177
|
+
const result = await execa2(
|
|
1178
|
+
"gh",
|
|
1179
|
+
["pr", "view", String(loser.prNumber), "--json", "state,mergeable,headRefName"],
|
|
1180
|
+
{ stdio: "pipe" }
|
|
1181
|
+
);
|
|
1182
|
+
return parseJsonWithContext(result.stdout, "[Cleanup] gh pr view output");
|
|
1183
|
+
},
|
|
1184
|
+
`validate PR #${loser.prNumber} state`,
|
|
1185
|
+
2
|
|
1186
|
+
// 2 retries
|
|
1187
|
+
);
|
|
1188
|
+
if (prData) {
|
|
1189
|
+
prState = prData.state ?? "unknown";
|
|
1190
|
+
branch = prData.headRefName ?? "";
|
|
1191
|
+
if (prState === "closed") {
|
|
1192
|
+
console.log(
|
|
1193
|
+
chalk.yellow(` PR #${loser.prNumber} already closed, skipping to branch cleanup`)
|
|
1194
|
+
);
|
|
1195
|
+
} else if (prState === "merged") {
|
|
1196
|
+
const warning = `PR #${loser.prNumber} was already merged; cleanup skipped`;
|
|
1197
|
+
console.log(chalk.yellow(` PR #${loser.prNumber} already merged, skipping cleanup`));
|
|
1198
|
+
return { success: true, degraded: true, warning };
|
|
1199
|
+
}
|
|
1200
|
+
if (branch && isProtectedPipelineBranch(branch)) {
|
|
1201
|
+
const warning = `PR #${loser.prNumber} branch ${branch} is protected; cleanup skipped`;
|
|
1202
|
+
return { success: true, degraded: true, warning };
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
} catch (validationError) {
|
|
1206
|
+
console.log(
|
|
1207
|
+
chalk.yellow(` Could not validate PR #${loser.prNumber} state: ${validationError}`)
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
if (branch) {
|
|
1211
|
+
await this.releaseWorktreesForBranch(branch, verbose, false);
|
|
1212
|
+
}
|
|
1213
|
+
if (prState === "open") {
|
|
1214
|
+
try {
|
|
1215
|
+
if (verbose) {
|
|
1216
|
+
console.log(chalk.gray(` \u2192 Closing PR #${loser.prNumber}`));
|
|
1217
|
+
}
|
|
1218
|
+
const prNumber = loser.prNumber;
|
|
1219
|
+
if (typeof prNumber === "number") {
|
|
1220
|
+
await this.prManager.closePR(prNumber);
|
|
1221
|
+
console.log(chalk.green(` Closed PR #${prNumber}`));
|
|
1222
|
+
}
|
|
1223
|
+
} catch (closeError) {
|
|
1224
|
+
console.log(chalk.yellow(` Failed to close PR #${loser.prNumber}: ${closeError}`));
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
if (!branch) {
|
|
1228
|
+
try {
|
|
1229
|
+
const prNumber = loser.prNumber;
|
|
1230
|
+
if (typeof prNumber === "number") {
|
|
1231
|
+
branch = await this.prManager.getPRBranch(prNumber);
|
|
1232
|
+
}
|
|
1233
|
+
if (verbose) {
|
|
1234
|
+
console.log(chalk.gray(` \u2192 PR branch: ${branch}`));
|
|
1235
|
+
}
|
|
1236
|
+
} catch (branchError) {
|
|
1237
|
+
console.log(
|
|
1238
|
+
chalk.yellow(` Could not get branch name for PR #${loser.prNumber}: ${branchError}`)
|
|
1239
|
+
);
|
|
1240
|
+
return {
|
|
1241
|
+
success: false,
|
|
1242
|
+
error: `Could not determine branch name for PR #${loser.prNumber}. Try: gh pr view <PR_NUMBER> --json headRefName -q '.headRefName'`
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
if (branch) {
|
|
1247
|
+
if (isProtectedPipelineBranch(branch)) {
|
|
1248
|
+
const warning = `Branch ${branch} is protected; cleanup skipped`;
|
|
1249
|
+
return { success: true, degraded: true, warning };
|
|
1250
|
+
}
|
|
1251
|
+
try {
|
|
1252
|
+
if (verbose) {
|
|
1253
|
+
console.log(chalk.gray(` \u2192 Deleting branch: ${branch}`));
|
|
1254
|
+
}
|
|
1255
|
+
try {
|
|
1256
|
+
await this.prManager.deleteBranch(branch, { force: false });
|
|
1257
|
+
} catch (_regularDeleteError) {
|
|
1258
|
+
if (verbose) {
|
|
1259
|
+
console.log(chalk.gray(" \u2192 Regular delete failed, trying force delete"));
|
|
1260
|
+
}
|
|
1261
|
+
await this.prManager.deleteBranch(branch, { force: true });
|
|
1262
|
+
}
|
|
1263
|
+
console.log(chalk.green(` Deleted branch: ${branch}`));
|
|
1264
|
+
} catch (deleteError) {
|
|
1265
|
+
const errorMsg = `Failed to delete branch ${branch}: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`;
|
|
1266
|
+
console.log(chalk.yellow(` ${errorMsg}`));
|
|
1267
|
+
return { success: false, error: errorMsg };
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
console.log(chalk.green(` Successfully cleaned up ${label} (PR #${loser.prNumber})`));
|
|
1271
|
+
return { success: true };
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
const errorMsg = `Failed to cleanup ${loser.provider} ${loser.strategy}: ${error instanceof Error ? error.message : String(error)}`;
|
|
1274
|
+
console.log(chalk.red(` ${errorMsg}`));
|
|
1275
|
+
return { success: false, error: errorMsg };
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Proactively remove git worktrees that are checked out on the given branch.
|
|
1280
|
+
*
|
|
1281
|
+
* This prevents "branch is checked out at ..." errors when deleting branches
|
|
1282
|
+
* during cleanup, especially when worktree pooling is enabled.
|
|
1283
|
+
*/
|
|
1284
|
+
async releaseWorktreesForBranch(branch, verbose = false, labMode = LabPathResolver.getInstance().isLabMode()) {
|
|
1285
|
+
if (labMode || LabPathResolver.getInstance().isLabMode()) {
|
|
1286
|
+
if (verbose) {
|
|
1287
|
+
console.log(chalk.gray(" Lab mode - preserving worktrees"));
|
|
1288
|
+
}
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
try {
|
|
1292
|
+
const { execa: execa2 } = await import("execa");
|
|
1293
|
+
const { stdout } = await execa2("git", ["worktree", "list", "--porcelain"], {
|
|
1294
|
+
cwd: this.repoRoot,
|
|
1295
|
+
stdio: "pipe"
|
|
1296
|
+
});
|
|
1297
|
+
const lines = stdout.split("\n");
|
|
1298
|
+
const worktrees = [];
|
|
1299
|
+
let currentPath = null;
|
|
1300
|
+
for (const line of lines) {
|
|
1301
|
+
if (line.startsWith("worktree ")) {
|
|
1302
|
+
currentPath = line.substring("worktree ".length).trim();
|
|
1303
|
+
} else if (line.startsWith("branch ") && currentPath) {
|
|
1304
|
+
const branchRef = line.substring("branch ".length).trim();
|
|
1305
|
+
worktrees.push({ path: currentPath, branch: branchRef });
|
|
1306
|
+
currentPath = null;
|
|
1307
|
+
} else if (line.startsWith("detached ") && currentPath) {
|
|
1308
|
+
worktrees.push({ path: currentPath, branch: "" });
|
|
1309
|
+
currentPath = null;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
if (worktrees.length === 0) {
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
const normalizedBranch = branch.startsWith("refs/heads/") ? branch : `refs/heads/${branch}`;
|
|
1316
|
+
const matching = worktrees.filter(
|
|
1317
|
+
(wt) => wt.branch === normalizedBranch || wt.branch === branch
|
|
1318
|
+
);
|
|
1319
|
+
for (const wt of matching) {
|
|
1320
|
+
try {
|
|
1321
|
+
if (verbose) {
|
|
1322
|
+
console.log(chalk.gray(` \u2192 Releasing worktree for branch ${branch}: ${wt.path}`));
|
|
1323
|
+
}
|
|
1324
|
+
if (this.worktreeCleanup) {
|
|
1325
|
+
const released = await this.worktreeCleanup.cleanup(wt.path, {
|
|
1326
|
+
preserve: false,
|
|
1327
|
+
gitCwd: this.repoRoot
|
|
1328
|
+
});
|
|
1329
|
+
if (released === false) {
|
|
1330
|
+
throw new Error(`worktree cleanup refused ${wt.path}`);
|
|
1331
|
+
}
|
|
1332
|
+
} else {
|
|
1333
|
+
await execa2("git", ["worktree", "remove", wt.path, "--force"], {
|
|
1334
|
+
cwd: this.repoRoot,
|
|
1335
|
+
stdio: "pipe"
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
if (verbose) {
|
|
1340
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1341
|
+
console.log(chalk.yellow(` Failed to remove worktree ${wt.path}: ${msg}`));
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
if (matching.length > 0) {
|
|
1346
|
+
await pruneGitWorktreeEntries({ cwd: this.repoRoot });
|
|
1347
|
+
}
|
|
1348
|
+
} catch (error) {
|
|
1349
|
+
if (verbose) {
|
|
1350
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1351
|
+
console.log(chalk.gray(` Skipping worktree release for branch ${branch}: ${msg}`));
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
requiresApplyRecovery(result) {
|
|
1356
|
+
if (result.submittedOnly) {
|
|
1357
|
+
return true;
|
|
1358
|
+
}
|
|
1359
|
+
return result.applied === false;
|
|
1360
|
+
}
|
|
1361
|
+
isPreservedSubmittedVariant(result) {
|
|
1362
|
+
return result.submittedOnly === true;
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Display preserved PRs that still need manual attention
|
|
1366
|
+
*/
|
|
1367
|
+
async displayPreservedPRs(preserved) {
|
|
1368
|
+
if (preserved.length === 0) {
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
console.log(chalk.blue(`
|
|
1372
|
+
Preserved ${preserved.length} PR(s) needing attention:
|
|
1373
|
+
`));
|
|
1374
|
+
for (const variant of preserved) {
|
|
1375
|
+
const label = this.formatProviderVariantLabel(variant);
|
|
1376
|
+
const prLabel = variant.prNumber ? `PR #${variant.prNumber}` : "PR pending";
|
|
1377
|
+
const appliedStatus = this.formatAppliedStatus(variant);
|
|
1378
|
+
const hint = this.getRecoveryHint(variant);
|
|
1379
|
+
console.log(chalk.gray(` \u2022 ${prLabel} - ${label}`));
|
|
1380
|
+
if (appliedStatus) {
|
|
1381
|
+
console.log(chalk.gray(` ${appliedStatus}`));
|
|
1382
|
+
}
|
|
1383
|
+
if (hint) {
|
|
1384
|
+
console.log(chalk.gray(` Recovery: ${hint}`));
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
console.log();
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Format provider variant label
|
|
1391
|
+
*/
|
|
1392
|
+
formatProviderVariantLabel(result) {
|
|
1393
|
+
return `${result.provider} ${result.model || "default"} ${result.strategy}`;
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Format applied status
|
|
1397
|
+
*/
|
|
1398
|
+
formatAppliedStatus(result) {
|
|
1399
|
+
if (result.applied === true) {
|
|
1400
|
+
return "Applied locally";
|
|
1401
|
+
}
|
|
1402
|
+
if (result.applied === false) {
|
|
1403
|
+
return "Apply failed";
|
|
1404
|
+
}
|
|
1405
|
+
if (result.submittedOnly) {
|
|
1406
|
+
return "Awaiting local apply";
|
|
1407
|
+
}
|
|
1408
|
+
return "";
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Get recovery hint
|
|
1412
|
+
*/
|
|
1413
|
+
getRecoveryHint(result) {
|
|
1414
|
+
if (result.provider === "codex" && result.cloudTaskId) {
|
|
1415
|
+
return `Inspect historical task ${result.cloudTaskId}`;
|
|
1416
|
+
}
|
|
1417
|
+
if (result.provider === "cursor" || result.provider === "cursor-api") {
|
|
1418
|
+
return "Apply via Cursor IDE";
|
|
1419
|
+
}
|
|
1420
|
+
return void 0;
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* OPTIONAL/OPT-IN: Prune old variant_results rows from the telemetry database.
|
|
1424
|
+
*
|
|
1425
|
+
* This method is intentionally separate from cleanupCompetition() and is NOT
|
|
1426
|
+
* called automatically. Historical telemetry has analytical value; callers
|
|
1427
|
+
* should only invoke this when explicitly managing disk space.
|
|
1428
|
+
*
|
|
1429
|
+
* The automatic size-triggered path (DbSizeManager) also covers variant_results,
|
|
1430
|
+
* so manual pruning is rarely needed. Use this when you want fine-grained control
|
|
1431
|
+
* over the retention window or need a dry-run estimate first.
|
|
1432
|
+
*
|
|
1433
|
+
* @param db - The variants telemetry database instance (DatabaseLike).
|
|
1434
|
+
* Obtain via TelemetryDatabase.getVariantsDb() or equivalent.
|
|
1435
|
+
* If undefined, the method is a no-op and returns zero deleted rows.
|
|
1436
|
+
* @param options - Pruning configuration (retentionDays, verbose, dryRun).
|
|
1437
|
+
* @returns Summary of what was (or would be) deleted.
|
|
1438
|
+
*
|
|
1439
|
+
* @example
|
|
1440
|
+
* ```typescript
|
|
1441
|
+
* // Dry-run first to see the impact
|
|
1442
|
+
* const estimate = await cleanupService.pruneVariantResults(variantsDb, {
|
|
1443
|
+
* retentionDays: 90,
|
|
1444
|
+
* dryRun: true,
|
|
1445
|
+
* verbose: true,
|
|
1446
|
+
* });
|
|
1447
|
+
* console.log(`Would delete ${estimate.deletedRows} rows`);
|
|
1448
|
+
*
|
|
1449
|
+
* // Then execute for real
|
|
1450
|
+
* const result = await cleanupService.pruneVariantResults(variantsDb, {
|
|
1451
|
+
* retentionDays: 90,
|
|
1452
|
+
* });
|
|
1453
|
+
* ```
|
|
1454
|
+
*/
|
|
1455
|
+
async pruneVariantResults(db, options = {}) {
|
|
1456
|
+
const { retentionDays = 90, verbose = false, dryRun = false } = options;
|
|
1457
|
+
const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1e3);
|
|
1458
|
+
const cutoffTimestamp = cutoff.toISOString();
|
|
1459
|
+
if (!db) {
|
|
1460
|
+
if (verbose) {
|
|
1461
|
+
console.log(
|
|
1462
|
+
chalk.gray("[CleanupService] pruneVariantResults: no database provided, skipping")
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
return { deletedRows: 0, cutoffTimestamp, dryRun };
|
|
1466
|
+
}
|
|
1467
|
+
if (verbose) {
|
|
1468
|
+
console.log(
|
|
1469
|
+
chalk.gray(
|
|
1470
|
+
`[CleanupService] pruneVariantResults: targeting rows with completed_at < ${cutoffTimestamp} (retention: ${retentionDays}d)${dryRun ? " [DRY RUN]" : ""}`
|
|
1471
|
+
)
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
try {
|
|
1475
|
+
if (dryRun) {
|
|
1476
|
+
const countStmt = db.prepare(
|
|
1477
|
+
"SELECT COUNT(*) as count FROM variant_results WHERE completed_at < ? AND completed_at IS NOT NULL"
|
|
1478
|
+
);
|
|
1479
|
+
const row = countStmt.get(cutoffTimestamp);
|
|
1480
|
+
const deletedRows2 = row?.count ?? 0;
|
|
1481
|
+
if (verbose) {
|
|
1482
|
+
console.log(
|
|
1483
|
+
chalk.gray(
|
|
1484
|
+
`[CleanupService] pruneVariantResults: dry-run \u2014 would delete ${deletedRows2} row(s)`
|
|
1485
|
+
)
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
return { deletedRows: deletedRows2, cutoffTimestamp, dryRun: true };
|
|
1489
|
+
}
|
|
1490
|
+
const deleteStmt = db.prepare(
|
|
1491
|
+
"DELETE FROM variant_results WHERE completed_at < ? AND completed_at IS NOT NULL"
|
|
1492
|
+
);
|
|
1493
|
+
const result = deleteStmt.run(cutoffTimestamp);
|
|
1494
|
+
const deletedRows = result.changes ?? 0;
|
|
1495
|
+
if (verbose) {
|
|
1496
|
+
console.log(
|
|
1497
|
+
chalk.gray(
|
|
1498
|
+
`[CleanupService] pruneVariantResults: deleted ${deletedRows} row(s) older than ${cutoffTimestamp}`
|
|
1499
|
+
)
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
return { deletedRows, cutoffTimestamp, dryRun: false };
|
|
1503
|
+
} catch (error) {
|
|
1504
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1505
|
+
console.warn(`[CleanupService] pruneVariantResults failed: ${msg}`);
|
|
1506
|
+
return { deletedRows: 0, cutoffTimestamp, dryRun };
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Cleanup orphan branches by task ID
|
|
1511
|
+
*/
|
|
1512
|
+
async cleanupOrphanBranchesByTask(taskId, winner) {
|
|
1513
|
+
try {
|
|
1514
|
+
const { execa: execa2 } = await import("execa");
|
|
1515
|
+
const result = await execa2("git", ["branch", "--list", `*${taskId}*`], {
|
|
1516
|
+
stdio: "pipe"
|
|
1517
|
+
});
|
|
1518
|
+
const branches = result.stdout.split("\n").map((b) => b.trim().replace(/^\*\s*/, "")).filter((b) => b.length > 0);
|
|
1519
|
+
let deletedCount = 0;
|
|
1520
|
+
let winnerBranch = "";
|
|
1521
|
+
if (winner.prNumber) {
|
|
1522
|
+
try {
|
|
1523
|
+
winnerBranch = await this.prManager.getPRBranch(winner.prNumber);
|
|
1524
|
+
} catch (error) {
|
|
1525
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1526
|
+
console.warn(
|
|
1527
|
+
`[CleanupService] Skipping orphan branch cleanup for task ${taskId}: failed to resolve winner branch for PR #${winner.prNumber}: ${msg}`
|
|
1528
|
+
);
|
|
1529
|
+
return 0;
|
|
1530
|
+
}
|
|
1531
|
+
} else {
|
|
1532
|
+
console.warn(
|
|
1533
|
+
`[CleanupService] Skipping orphan branch cleanup for task ${taskId}: winner has no PR number, cannot identify winning branch`
|
|
1534
|
+
);
|
|
1535
|
+
return 0;
|
|
1536
|
+
}
|
|
1537
|
+
for (const branch of branches) {
|
|
1538
|
+
if (branch === winnerBranch) {
|
|
1539
|
+
continue;
|
|
1540
|
+
}
|
|
1541
|
+
if (!isPoeticOwnedBranch(branch)) {
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
if (isProtectedPipelineBranch(branch)) {
|
|
1545
|
+
continue;
|
|
1546
|
+
}
|
|
1547
|
+
try {
|
|
1548
|
+
await this.prManager.deleteBranch(branch, { force: true });
|
|
1549
|
+
deletedCount++;
|
|
1550
|
+
} catch (_error) {
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
return deletedCount;
|
|
1554
|
+
} catch (_error) {
|
|
1555
|
+
return 0;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
|
|
1560
|
+
// src/orchestrator/services/judge-display-service.ts
|
|
1561
|
+
import chalk2 from "chalk";
|
|
1562
|
+
var JudgeDisplayService = class {
|
|
1563
|
+
verbosity;
|
|
1564
|
+
showRationale;
|
|
1565
|
+
streaming;
|
|
1566
|
+
resultStreamer;
|
|
1567
|
+
judgeProgressHandler;
|
|
1568
|
+
liveScoringHandler;
|
|
1569
|
+
judgeEvaluatedHandler;
|
|
1570
|
+
judgeWinnerSelectedHandler;
|
|
1571
|
+
constructor(options = {}) {
|
|
1572
|
+
this.verbosity = options.verbosity ?? "normal";
|
|
1573
|
+
this.showRationale = options.showRationale ?? false;
|
|
1574
|
+
this.streaming = options.streaming ?? false;
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Set result streamer for real-time updates
|
|
1578
|
+
*/
|
|
1579
|
+
setResultStreamer(streamer) {
|
|
1580
|
+
this.resultStreamer = streamer;
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Setup judge streaming listeners for a task
|
|
1584
|
+
* @returns Cleanup function to remove listeners
|
|
1585
|
+
*/
|
|
1586
|
+
setupStreamingListeners(taskId) {
|
|
1587
|
+
if (!this.resultStreamer || !this.streaming) return () => {
|
|
1588
|
+
};
|
|
1589
|
+
this.removeStreamingListeners();
|
|
1590
|
+
this.judgeProgressHandler = (event) => {
|
|
1591
|
+
if (event.taskId === taskId) {
|
|
1592
|
+
this.displayJudgeProgress(event);
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
this.liveScoringHandler = (event) => {
|
|
1596
|
+
if (event.taskId === taskId) {
|
|
1597
|
+
this.displayLiveScoring(event);
|
|
1598
|
+
}
|
|
1599
|
+
};
|
|
1600
|
+
this.judgeEvaluatedHandler = (event) => {
|
|
1601
|
+
if (event.taskId === taskId) {
|
|
1602
|
+
this.displayJudgeEvaluation(event);
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
this.judgeWinnerSelectedHandler = (event) => {
|
|
1606
|
+
if (event.taskId === taskId) {
|
|
1607
|
+
this.displayWinnerSelection(event);
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
this.resultStreamer.on("judgeProgress", this.judgeProgressHandler);
|
|
1611
|
+
this.resultStreamer.on("liveScoring", this.liveScoringHandler);
|
|
1612
|
+
this.resultStreamer.on("judgeEvaluated", this.judgeEvaluatedHandler);
|
|
1613
|
+
this.resultStreamer.on("judgeWinnerSelected", this.judgeWinnerSelectedHandler);
|
|
1614
|
+
return () => this.removeStreamingListeners();
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Remove all streaming listeners
|
|
1618
|
+
*/
|
|
1619
|
+
removeStreamingListeners() {
|
|
1620
|
+
if (!this.resultStreamer) return;
|
|
1621
|
+
if (this.judgeProgressHandler) {
|
|
1622
|
+
this.resultStreamer.removeListener("judgeProgress", this.judgeProgressHandler);
|
|
1623
|
+
this.judgeProgressHandler = void 0;
|
|
1624
|
+
}
|
|
1625
|
+
if (this.liveScoringHandler) {
|
|
1626
|
+
this.resultStreamer.removeListener("liveScoring", this.liveScoringHandler);
|
|
1627
|
+
this.liveScoringHandler = void 0;
|
|
1628
|
+
}
|
|
1629
|
+
if (this.judgeEvaluatedHandler) {
|
|
1630
|
+
this.resultStreamer.removeListener("judgeEvaluated", this.judgeEvaluatedHandler);
|
|
1631
|
+
this.judgeEvaluatedHandler = void 0;
|
|
1632
|
+
}
|
|
1633
|
+
if (this.judgeWinnerSelectedHandler) {
|
|
1634
|
+
this.resultStreamer.removeListener("judgeWinnerSelected", this.judgeWinnerSelectedHandler);
|
|
1635
|
+
this.judgeWinnerSelectedHandler = void 0;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Display AI Judge evaluation results
|
|
1640
|
+
*
|
|
1641
|
+
* @param evaluation - The judge synthesis result to display
|
|
1642
|
+
* @param variantResultsMap - Optional map of variant name to result for analysis-mode detection
|
|
1643
|
+
*/
|
|
1644
|
+
displayEvaluation(evaluation, variantResultsMap) {
|
|
1645
|
+
if (this.verbosity === "silent" || isQuietOutputRequested()) return;
|
|
1646
|
+
const summary = this.buildSummary(evaluation, variantResultsMap);
|
|
1647
|
+
const shouldShowDetails = this.verbosity === "detailed" || this.verbosity === "debug";
|
|
1648
|
+
if (!shouldShowDetails) {
|
|
1649
|
+
if (this.verbosity === "minimal") {
|
|
1650
|
+
const confidenceSuffix = summary.confidenceLabel ? ` (${summary.confidenceLabel})` : "";
|
|
1651
|
+
const analysisSuffix = summary.isAnalysisMode ? " [analysis mode]" : "";
|
|
1652
|
+
console.log(chalk2.bold(`Winner: ${summary.winner}${confidenceSuffix}${analysisSuffix}`));
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
console.log(chalk2.bold.cyan("\nAI Judge Result\n"));
|
|
1656
|
+
const analysisTag = summary.isAnalysisMode ? chalk2.yellow(" [analysis mode]") : "";
|
|
1657
|
+
console.log(chalk2.bold(`Winner: ${summary.winner}${analysisTag}`));
|
|
1658
|
+
if (summary.confidenceLabel) {
|
|
1659
|
+
console.log(chalk2.gray(` Confidence: ${summary.confidenceLabel}`));
|
|
1660
|
+
}
|
|
1661
|
+
if (summary.runnerUp) {
|
|
1662
|
+
console.log(
|
|
1663
|
+
chalk2.gray(
|
|
1664
|
+
` Runner-up: ${summary.runnerUp}${summary.margin ? ` (margin ${summary.margin} pts)` : ""}`
|
|
1665
|
+
)
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
if (summary.modelLabel) {
|
|
1669
|
+
console.log(chalk2.gray(` Model: ${summary.modelLabel}`));
|
|
1670
|
+
}
|
|
1671
|
+
if (summary.durationLabel) {
|
|
1672
|
+
console.log(chalk2.gray(` Evaluation Time: ${summary.durationLabel}`));
|
|
1673
|
+
}
|
|
1674
|
+
if (summary.reasons.length > 0) {
|
|
1675
|
+
console.log(chalk2.gray(` Reasons: ${summary.reasons.join(" | ")}`));
|
|
1676
|
+
}
|
|
1677
|
+
console.log();
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
console.log(chalk2.bold.cyan("\nAI Judge Evaluation:\n"));
|
|
1681
|
+
const detailedAnalysisTag = summary.isAnalysisMode ? chalk2.yellow(" [analysis mode]") : "";
|
|
1682
|
+
console.log(chalk2.bold(`Winner: ${summary.winner}${detailedAnalysisTag}`));
|
|
1683
|
+
const confidenceValue = typeof summary.confidence === "number" && Number.isFinite(summary.confidence) ? summary.confidence : 0;
|
|
1684
|
+
console.log(chalk2.gray(` Confidence: ${confidenceValue}%`));
|
|
1685
|
+
if (summary.modelLabel) {
|
|
1686
|
+
console.log(chalk2.gray(` Model: ${summary.modelLabel}`));
|
|
1687
|
+
}
|
|
1688
|
+
if (summary.durationLabel) {
|
|
1689
|
+
console.log(chalk2.gray(` Evaluation Time: ${summary.durationLabel}
|
|
1690
|
+
`));
|
|
1691
|
+
}
|
|
1692
|
+
if (evaluation.reasoning && this.showRationale) {
|
|
1693
|
+
console.log(chalk2.bold.blue("Reasoning:"));
|
|
1694
|
+
console.log(chalk2.gray(` ${evaluation.reasoning}
|
|
1695
|
+
`));
|
|
1696
|
+
}
|
|
1697
|
+
console.log(chalk2.bold.blue("Detailed Evaluations:"));
|
|
1698
|
+
evaluation.evaluations.forEach((eval_, index) => {
|
|
1699
|
+
const isWinner = eval_.variant === evaluation.winner;
|
|
1700
|
+
const variantColor = isWinner ? chalk2.green : chalk2.white;
|
|
1701
|
+
const variantName = eval_.variant ? eval_.variant.toUpperCase() : "UNKNOWN";
|
|
1702
|
+
const rankingScore = getEvaluationRankingScore(eval_);
|
|
1703
|
+
const qualityScore = getEvaluationQualityScore(eval_);
|
|
1704
|
+
console.log(variantColor(`
|
|
1705
|
+
${index + 1}. ${variantName}${isWinner ? " [winner]" : ""}`));
|
|
1706
|
+
if (rankingScore != null) {
|
|
1707
|
+
console.log(chalk2.gray(` Ranking Score: ${rankingScore.toFixed(1)}/100`));
|
|
1708
|
+
}
|
|
1709
|
+
if (qualityScore != null && (rankingScore == null || Math.abs(rankingScore - qualityScore) > 0.05)) {
|
|
1710
|
+
console.log(chalk2.gray(` Quality Score: ${qualityScore.toFixed(1)}/100`));
|
|
1711
|
+
}
|
|
1712
|
+
if (eval_.codeQuality) {
|
|
1713
|
+
console.log(chalk2.gray(` Correctness: ${Number(eval_.codeQuality.correctness)}/100`));
|
|
1714
|
+
console.log(chalk2.gray(` Readability: ${Number(eval_.codeQuality.readability)}/100`));
|
|
1715
|
+
console.log(
|
|
1716
|
+
chalk2.gray(` Maintainability: ${Number(eval_.codeQuality.maintainability)}/100`)
|
|
1717
|
+
);
|
|
1718
|
+
console.log(
|
|
1719
|
+
chalk2.gray(` Completeness: ${Number(eval_.codeQuality.completeness)}/100`)
|
|
1720
|
+
);
|
|
1721
|
+
}
|
|
1722
|
+
if (eval_.strengths && eval_.strengths.length > 0) {
|
|
1723
|
+
console.log(chalk2.green(" Strengths:"));
|
|
1724
|
+
eval_.strengths.forEach((strength) => {
|
|
1725
|
+
console.log(chalk2.gray(` ${strength}`));
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
if (eval_.weaknesses && eval_.weaknesses.length > 0) {
|
|
1729
|
+
console.log(chalk2.yellow(" Weaknesses:"));
|
|
1730
|
+
eval_.weaknesses.forEach((weakness) => {
|
|
1731
|
+
console.log(chalk2.gray(` \u2022 ${weakness}`));
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
});
|
|
1735
|
+
if (evaluation.recommendations && evaluation.recommendations.length > 0) {
|
|
1736
|
+
console.log(chalk2.bold.blue("\nRecommendations:"));
|
|
1737
|
+
evaluation.recommendations.forEach((rec, index) => {
|
|
1738
|
+
console.log(chalk2.gray(` ${index + 1}. ${rec}`));
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
console.log();
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* Display judge progress updates
|
|
1745
|
+
*/
|
|
1746
|
+
displayJudgeProgress(event) {
|
|
1747
|
+
if (isQuietOutputRequested()) return;
|
|
1748
|
+
if (this.verbosity === "silent" || this.verbosity === "minimal") return;
|
|
1749
|
+
const progressBar = this.createProgressBar(event.progress, 40);
|
|
1750
|
+
let output = `\r${event.variant}: ${progressBar} ${event.progress.toFixed(1)}%`;
|
|
1751
|
+
if (event.step) {
|
|
1752
|
+
output += ` | ${event.step}`;
|
|
1753
|
+
}
|
|
1754
|
+
if (event.currentScore !== void 0) {
|
|
1755
|
+
output += ` | Score: ${event.currentScore.toFixed(1)}`;
|
|
1756
|
+
}
|
|
1757
|
+
if (event.estimatedTimeRemaining) {
|
|
1758
|
+
const seconds = Math.ceil(event.estimatedTimeRemaining / 1e3);
|
|
1759
|
+
output += ` | ETA: ${seconds}s`;
|
|
1760
|
+
}
|
|
1761
|
+
process.stdout.write(output);
|
|
1762
|
+
if (event.progress >= 100) {
|
|
1763
|
+
process.stdout.write("\n");
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Display live scoring updates
|
|
1768
|
+
*/
|
|
1769
|
+
displayLiveScoring(event) {
|
|
1770
|
+
if (isQuietOutputRequested()) return;
|
|
1771
|
+
if (this.verbosity === "silent" || this.verbosity === "minimal") return;
|
|
1772
|
+
console.log(
|
|
1773
|
+
chalk2.gray(` ${event.variant}: ${event.criteria} score updated to ${event.score}`)
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1776
|
+
/**
|
|
1777
|
+
* Display judge evaluation results (streaming event)
|
|
1778
|
+
*/
|
|
1779
|
+
displayJudgeEvaluation(event) {
|
|
1780
|
+
if (isQuietOutputRequested()) return;
|
|
1781
|
+
if (this.verbosity === "silent") return;
|
|
1782
|
+
if (this.verbosity !== "detailed" && this.verbosity !== "debug") return;
|
|
1783
|
+
const overall = typeof event.evaluation.rankingScore === "number" && Number.isFinite(event.evaluation.rankingScore) ? event.evaluation.rankingScore : typeof event.evaluation.compositeScore === "number" && Number.isFinite(event.evaluation.compositeScore) ? event.evaluation.compositeScore : typeof event.evaluation.overallScore === "number" && Number.isFinite(event.evaluation.overallScore) ? event.evaluation.overallScore : 0;
|
|
1784
|
+
const weighted = typeof event.evaluation.weightedScore === "number" && Number.isFinite(event.evaluation.weightedScore) ? event.evaluation.weightedScore : 0;
|
|
1785
|
+
const quality = typeof event.evaluation.qualityScore === "number" && Number.isFinite(event.evaluation.qualityScore) ? event.evaluation.qualityScore : null;
|
|
1786
|
+
console.log(chalk2.green(`
|
|
1787
|
+
${event.variant} evaluation completed`));
|
|
1788
|
+
console.log(chalk2.gray(` Ranking Score: ${overall.toFixed(1)}`));
|
|
1789
|
+
if (quality != null && Math.abs(overall - quality) > 0.05) {
|
|
1790
|
+
console.log(chalk2.gray(` Quality Score: ${quality.toFixed(1)}`));
|
|
1791
|
+
}
|
|
1792
|
+
console.log(chalk2.gray(` Weighted Score: ${weighted.toFixed(1)}`));
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Display winner selection results (streaming event)
|
|
1796
|
+
*/
|
|
1797
|
+
displayWinnerSelection(event) {
|
|
1798
|
+
if (isQuietOutputRequested()) return;
|
|
1799
|
+
if (this.verbosity === "silent") return;
|
|
1800
|
+
const winnerLabel = event.winner === "none" ? "No winner" : event.winner;
|
|
1801
|
+
const confidenceLabel = `${(event.confidence * 100).toFixed(1)}%`;
|
|
1802
|
+
if (this.verbosity === "minimal" || this.verbosity === "normal") {
|
|
1803
|
+
console.log(chalk2.bold.green(`
|
|
1804
|
+
Winner: ${winnerLabel} (${confidenceLabel})`));
|
|
1805
|
+
if (this.showRationale) {
|
|
1806
|
+
console.log(chalk2.gray(` Reason: ${event.rationale}`));
|
|
1807
|
+
}
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
console.log(chalk2.bold.green(`
|
|
1811
|
+
Winner Selected: ${winnerLabel}`));
|
|
1812
|
+
console.log(chalk2.gray(` Confidence: ${confidenceLabel}`));
|
|
1813
|
+
if (this.showRationale) {
|
|
1814
|
+
console.log(chalk2.gray(` Rationale: ${event.rationale}`));
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
/**
|
|
1818
|
+
* Create a simple progress bar
|
|
1819
|
+
*/
|
|
1820
|
+
createProgressBar(progress, width) {
|
|
1821
|
+
const filled = Math.round(progress / 100 * width);
|
|
1822
|
+
return "#".repeat(filled) + ".".repeat(width - filled);
|
|
1823
|
+
}
|
|
1824
|
+
buildSummary(evaluation, variantResultsMap) {
|
|
1825
|
+
const evaluations = Array.isArray(evaluation.evaluations) ? evaluation.evaluations : [];
|
|
1826
|
+
const scored = evaluations.map((item) => ({
|
|
1827
|
+
variant: item.variant ?? "UNKNOWN",
|
|
1828
|
+
score: getEvaluationRankingScoreOrZero(item, "judge-display.buildSummary"),
|
|
1829
|
+
primaryReason: item.primaryReason,
|
|
1830
|
+
strengths: item.strengths ?? []
|
|
1831
|
+
})).sort((a, b) => b.score - a.score);
|
|
1832
|
+
const winnerScore = scored.find((entry) => entry.variant === evaluation.winner)?.score;
|
|
1833
|
+
const runnerUp = scored.find((entry) => entry.variant !== evaluation.winner);
|
|
1834
|
+
const margin = typeof winnerScore === "number" && runnerUp ? (winnerScore - runnerUp.score).toFixed(1) : void 0;
|
|
1835
|
+
const confidence = typeof evaluation.metadata.confidence === "number" && Number.isFinite(evaluation.metadata.confidence) ? evaluation.metadata.confidence : void 0;
|
|
1836
|
+
const durationLabel = evaluation.metadata.evaluationTimeMs < 1e3 ? `${evaluation.metadata.evaluationTimeMs}ms` : `${(evaluation.metadata.evaluationTimeMs / 1e3).toFixed(1)}s`;
|
|
1837
|
+
const reasons = [];
|
|
1838
|
+
if (evaluation.winnerPrimaryReason) {
|
|
1839
|
+
reasons.push(evaluation.winnerPrimaryReason);
|
|
1840
|
+
}
|
|
1841
|
+
const winnerReason = scored.find((entry) => entry.variant === evaluation.winner);
|
|
1842
|
+
if (winnerReason?.primaryReason) {
|
|
1843
|
+
reasons.push(winnerReason.primaryReason);
|
|
1844
|
+
}
|
|
1845
|
+
if (winnerReason?.strengths && reasons.length < 2) {
|
|
1846
|
+
reasons.push(...winnerReason.strengths.slice(0, 2 - reasons.length));
|
|
1847
|
+
}
|
|
1848
|
+
let isAnalysisMode = false;
|
|
1849
|
+
if (variantResultsMap && evaluation.winner) {
|
|
1850
|
+
const winnerResult = variantResultsMap.get(evaluation.winner);
|
|
1851
|
+
if (winnerResult) {
|
|
1852
|
+
const executionCategory = winnerResult.executionCategory;
|
|
1853
|
+
const editMode = winnerResult.completenessScore?.editMode;
|
|
1854
|
+
isAnalysisMode = executionCategory === "analysis" || editMode === "analysis" || winnerResult.executionIntent === "analysis";
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
return {
|
|
1858
|
+
winner: evaluation.winner ?? "none",
|
|
1859
|
+
runnerUp: runnerUp?.variant,
|
|
1860
|
+
margin,
|
|
1861
|
+
confidence,
|
|
1862
|
+
confidenceLabel: confidence !== void 0 ? `${confidence.toFixed(1)}%` : void 0,
|
|
1863
|
+
modelLabel: evaluation.metadata.model,
|
|
1864
|
+
durationLabel,
|
|
1865
|
+
reasons: reasons.slice(0, 2),
|
|
1866
|
+
isAnalysisMode
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
|
|
1871
|
+
// src/orchestrator/pr-workflow-coordinator-impl-core.ts
|
|
1872
|
+
var SynthesisWinnerRestorationError = class extends Error {
|
|
1873
|
+
constructor(message) {
|
|
1874
|
+
super(message);
|
|
1875
|
+
this.name = "SynthesisWinnerRestorationError";
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
var CompetitionTimeoutFailure = class extends CompetitionTimeoutError {
|
|
1879
|
+
observedResults;
|
|
1880
|
+
drain;
|
|
1881
|
+
/** True only when the execution cohort joined within the drain bound. */
|
|
1882
|
+
cleanupJoined;
|
|
1883
|
+
constructor(args) {
|
|
1884
|
+
super(args.timeoutMs, args.completedVariants, args.totalVariants);
|
|
1885
|
+
this.observedResults = sealVariantResultSnapshot(args.observedResults);
|
|
1886
|
+
this.drain = args.drain;
|
|
1887
|
+
this.cleanupJoined = args.drain.joined;
|
|
1888
|
+
}
|
|
1889
|
+
};
|
|
1890
|
+
async function awaitBoundedCohortDrain(cohortPromise, drainMs = WORKER_EXIT_DRAIN_MS) {
|
|
1891
|
+
const startedAt = Date.now();
|
|
1892
|
+
let joined = false;
|
|
1893
|
+
let drainTimer;
|
|
1894
|
+
try {
|
|
1895
|
+
await Promise.race([
|
|
1896
|
+
Promise.resolve(cohortPromise).then(
|
|
1897
|
+
() => {
|
|
1898
|
+
joined = true;
|
|
1899
|
+
},
|
|
1900
|
+
() => {
|
|
1901
|
+
joined = true;
|
|
1902
|
+
}
|
|
1903
|
+
),
|
|
1904
|
+
new Promise((resolve) => {
|
|
1905
|
+
drainTimer = setTimeout(resolve, drainMs);
|
|
1906
|
+
})
|
|
1907
|
+
]);
|
|
1908
|
+
} finally {
|
|
1909
|
+
if (drainTimer) {
|
|
1910
|
+
clearTimeout(drainTimer);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
return {
|
|
1914
|
+
joined,
|
|
1915
|
+
awaitedMs: Date.now() - startedAt,
|
|
1916
|
+
pendingAtDeadline: joined ? 0 : 1,
|
|
1917
|
+
settledCount: joined ? 1 : 0,
|
|
1918
|
+
aborted: false
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
function markSynthesisWinnerFallback(mergedResult, fallbackReason, winner, applyError) {
|
|
1922
|
+
mergedResult.deliverySucceeded = true;
|
|
1923
|
+
mergedResult.synthesisApplied = false;
|
|
1924
|
+
mergedResult.winnerFallback = true;
|
|
1925
|
+
mergedResult.fallbackReason = fallbackReason;
|
|
1926
|
+
mergedResult.patchApplyValidated = false;
|
|
1927
|
+
mergedResult.gatesPassed = false;
|
|
1928
|
+
mergedResult.winnerClone = true;
|
|
1929
|
+
mergedResult.noveltyScore ??= 0;
|
|
1930
|
+
if (winner) {
|
|
1931
|
+
const winnerId = formatVariantIdentifierFromResult(winner);
|
|
1932
|
+
mergedResult.mergedVariants = [winnerId];
|
|
1933
|
+
mergedResult.contributingVariants = [winnerId];
|
|
1934
|
+
}
|
|
1935
|
+
if (mergedResult.aiGuidedFallbackAttempted === true) {
|
|
1936
|
+
if (applyError) {
|
|
1937
|
+
mergedResult.aiGuidedFallbackOutcome = "candidate_rejected";
|
|
1938
|
+
} else if (mergedResult.aiGuidedFallbackOutcome !== "generation_failed") {
|
|
1939
|
+
mergedResult.aiGuidedFallbackOutcome = "winner_equivalent";
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
if (applyError) {
|
|
1943
|
+
mergedResult.applyError = applyError;
|
|
1944
|
+
mergedResult.error ??= applyError;
|
|
1945
|
+
}
|
|
1946
|
+
if (!mergedResult.synthesisOutcomeStatus || mergedResult.synthesisOutcomeStatus === "applied") {
|
|
1947
|
+
mergedResult.synthesisOutcomeStatus = applyError ? "gate_failed" : "winner_carry_forward";
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
function markTypecheckOnlySynthesisFallback(mergedResult, winner, detail) {
|
|
1951
|
+
const fallbackReason = detail ? `Synthesis passed legacy typecheck-only validation, but full deterministic gates were not run; winner-only result retained (${detail})` : "Synthesis passed legacy typecheck-only validation, but full deterministic gates were not run; winner-only result retained";
|
|
1952
|
+
markSynthesisWinnerFallback(mergedResult, fallbackReason, winner);
|
|
1953
|
+
mergedResult.validationScope = "typecheck-only";
|
|
1954
|
+
}
|
|
1955
|
+
async function cleanupAgenticSynthesisWorktree(synthesisWorktree, preserveWorktrees) {
|
|
1956
|
+
if (preserveWorktrees || !synthesisWorktree.trim()) return;
|
|
1957
|
+
const worktreeAvailable = await fs.access(synthesisWorktree).then(() => true).catch(() => false);
|
|
1958
|
+
if (!worktreeAvailable) return;
|
|
1959
|
+
const repoRootResult = await execa("git", ["rev-parse", "--show-toplevel"], {
|
|
1960
|
+
cwd: synthesisWorktree,
|
|
1961
|
+
reject: false
|
|
1962
|
+
});
|
|
1963
|
+
await execa("git", ["worktree", "remove", synthesisWorktree, "--force"], {
|
|
1964
|
+
cwd: repoRootResult.stdout.trim() || process.cwd(),
|
|
1965
|
+
reject: false
|
|
1966
|
+
});
|
|
1967
|
+
await fs.rm(synthesisWorktree, { recursive: true, force: true }).catch(() => {
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
function resolveSynthesisVerifyCommands(worktreePath) {
|
|
1971
|
+
try {
|
|
1972
|
+
const capabilities = detectProjectCapabilities(worktreePath);
|
|
1973
|
+
const guidance = buildProjectVerificationGuidance(worktreePath);
|
|
1974
|
+
const derived = [];
|
|
1975
|
+
for (const command of guidance.commands) {
|
|
1976
|
+
if (command.kind === "typecheck" || command.kind === "build") {
|
|
1977
|
+
derived.push(command.command);
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
const deduped = [...new Set(derived)];
|
|
1981
|
+
if (deduped.length > 0) {
|
|
1982
|
+
return { available: true, commands: deduped };
|
|
1983
|
+
}
|
|
1984
|
+
if (hasProjectLanguage(capabilities, "typescript") && capabilities.manifests?.tsconfig) {
|
|
1985
|
+
return { available: true, commands: ["npx --no-install tsc --noEmit"] };
|
|
1986
|
+
}
|
|
1987
|
+
return { available: false, commands: [] };
|
|
1988
|
+
} catch {
|
|
1989
|
+
return { available: false, commands: [] };
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
function shouldSelectCompetitionWinner(input) {
|
|
1993
|
+
return input.total > 0 && input.completed > 0 && !(input.judgeOff && input.total > 1);
|
|
1994
|
+
}
|
|
1995
|
+
var PRWorkflowOrchestrator = class {
|
|
1996
|
+
prManager;
|
|
1997
|
+
localExecutor;
|
|
1998
|
+
variantOrchestrator;
|
|
1999
|
+
verbose;
|
|
2000
|
+
orchestratorTimeoutMs;
|
|
2001
|
+
orchestratorDeadlineMs;
|
|
2002
|
+
executorTimeoutMs;
|
|
2003
|
+
cleanupOnCompletion;
|
|
2004
|
+
localFastMode;
|
|
2005
|
+
pushLocalVariants;
|
|
2006
|
+
preserveWorktrees;
|
|
2007
|
+
aiJudge;
|
|
2008
|
+
judgeOff;
|
|
2009
|
+
aiJudgeModel;
|
|
2010
|
+
aiJudgeVerbose;
|
|
2011
|
+
aiJudgeShowRationale;
|
|
2012
|
+
aiJudgeVerbosity;
|
|
2013
|
+
aiJudgeCriteriaWeights;
|
|
2014
|
+
aiJudgeMinScore;
|
|
2015
|
+
aiJudgeConfidenceThreshold;
|
|
2016
|
+
aiJudgeStreaming;
|
|
2017
|
+
aiJudgeTelemetry;
|
|
2018
|
+
aiJudgeRun;
|
|
2019
|
+
aiJudgePrimary;
|
|
2020
|
+
judgeModels;
|
|
2021
|
+
judgeModel;
|
|
2022
|
+
judgeAllowUnsafeModel;
|
|
2023
|
+
judgeStrict;
|
|
2024
|
+
anonymizeJudge;
|
|
2025
|
+
verificationMode;
|
|
2026
|
+
synthesize;
|
|
2027
|
+
synthesisTopN;
|
|
2028
|
+
synthesisStrategy;
|
|
2029
|
+
synthesisModel;
|
|
2030
|
+
resultStreamer;
|
|
2031
|
+
processLifecycle;
|
|
2032
|
+
ownsProcessLifecycle;
|
|
2033
|
+
enableAutoTuning;
|
|
2034
|
+
enablePromptOptimization;
|
|
2035
|
+
costTracker;
|
|
2036
|
+
workspaceAgentRunner;
|
|
2037
|
+
outputValidator;
|
|
2038
|
+
branchListCache;
|
|
2039
|
+
BRANCH_LIST_CACHE_TTL_MS = 60 * 1e3;
|
|
2040
|
+
// 60 seconds
|
|
2041
|
+
cleanupService;
|
|
2042
|
+
judgeDisplayService;
|
|
2043
|
+
judgeListenerCleanup;
|
|
2044
|
+
ownsResultStreamer;
|
|
2045
|
+
repoRoot;
|
|
2046
|
+
// Dependency-aware execution (experimental)
|
|
2047
|
+
flywheel;
|
|
2048
|
+
taskDependencies;
|
|
2049
|
+
globalMaxConcurrent;
|
|
2050
|
+
sharedVariantResourceManager;
|
|
2051
|
+
dependencyResolver;
|
|
2052
|
+
resourceManager;
|
|
2053
|
+
constructor(options = {}) {
|
|
2054
|
+
this.repoRoot = findProjectRoot(process.cwd()) ?? process.cwd();
|
|
2055
|
+
const verbosityLevel = validateVerbosity(options.verbose);
|
|
2056
|
+
this.verbose = verbosityLevel === "detailed" || verbosityLevel === "debug";
|
|
2057
|
+
this.localFastMode = options.localFast ?? false;
|
|
2058
|
+
this.pushLocalVariants = options.noPush === true ? false : options.pushLocalVariants ?? getRuntimeEnv("POETIC_PUSH_VARIANTS") === "1";
|
|
2059
|
+
this.cleanupOnCompletion = (options.cleanupOnCompletion ?? true) && !this.localFastMode;
|
|
2060
|
+
this.preserveWorktrees = options.preserveWorktrees ?? getRuntimeEnv("POETIC_PRESERVE_WORKTREES") === "1";
|
|
2061
|
+
this.aiJudge = options.aiJudge ?? true;
|
|
2062
|
+
this.judgeOff = options.judgeOff ?? false;
|
|
2063
|
+
this.aiJudgeModel = options.aiJudgeModel;
|
|
2064
|
+
this.aiJudgeShowRationale = options.aiJudgeShowRationale ?? false;
|
|
2065
|
+
this.aiJudgeRun = options.aiJudgeRun;
|
|
2066
|
+
this.aiJudgePrimary = options.aiJudgePrimary;
|
|
2067
|
+
this.judgeModels = options.judgeModels;
|
|
2068
|
+
this.judgeModel = options.judgeModel;
|
|
2069
|
+
this.judgeAllowUnsafeModel = options.judgeAllowUnsafeModel;
|
|
2070
|
+
this.judgeStrict = options.judgeStrict ?? false;
|
|
2071
|
+
this.anonymizeJudge = options.anonymizeJudge;
|
|
2072
|
+
this.aiJudgeVerbosity = validateVerbosity(void 0, options.aiJudgeVerbosity);
|
|
2073
|
+
this.aiJudgeVerbose = this.aiJudgeVerbosity === "detailed" || this.aiJudgeVerbosity === "debug";
|
|
2074
|
+
this.aiJudgeCriteriaWeights = options.aiJudgeCriteriaWeights;
|
|
2075
|
+
if (options.aiJudgeMinScore !== void 0) {
|
|
2076
|
+
this.aiJudgeMinScore = validatePercentage(options.aiJudgeMinScore, "aiJudgeMinScore", {
|
|
2077
|
+
min: 0,
|
|
2078
|
+
max: 100,
|
|
2079
|
+
allowFractional: false
|
|
2080
|
+
});
|
|
2081
|
+
} else {
|
|
2082
|
+
this.aiJudgeMinScore = void 0;
|
|
2083
|
+
}
|
|
2084
|
+
if (options.aiJudgeConfidenceThreshold !== void 0) {
|
|
2085
|
+
this.aiJudgeConfidenceThreshold = validateConfidence(options.aiJudgeConfidenceThreshold, {
|
|
2086
|
+
fieldName: "aiJudgeConfidenceThreshold"
|
|
2087
|
+
});
|
|
2088
|
+
} else {
|
|
2089
|
+
this.aiJudgeConfidenceThreshold = void 0;
|
|
2090
|
+
}
|
|
2091
|
+
this.aiJudgeStreaming = options.aiJudgeStreaming;
|
|
2092
|
+
this.aiJudgeTelemetry = options.aiJudgeTelemetry ?? true;
|
|
2093
|
+
this.verificationMode = options.verificationMode ?? "auto";
|
|
2094
|
+
this.synthesize = options.synthesize ?? true;
|
|
2095
|
+
this.synthesisTopN = options.synthesisTopN ?? 2;
|
|
2096
|
+
this.synthesisStrategy = options.synthesisStrategy ?? "agentic-synthesis";
|
|
2097
|
+
this.synthesisModel = options.synthesisModel ?? resolveSynthesisModel().value;
|
|
2098
|
+
this.enableAutoTuning = options.enableAutoTuning ?? false;
|
|
2099
|
+
this.enablePromptOptimization = options.enablePromptOptimization ?? true;
|
|
2100
|
+
this.resultStreamer = options.resultStreamer;
|
|
2101
|
+
this.ownsResultStreamer = !options.resultStreamer;
|
|
2102
|
+
this.branchListCache = /* @__PURE__ */ new Map();
|
|
2103
|
+
this.flywheel = options.flywheel ?? false;
|
|
2104
|
+
this.taskDependencies = options.taskDependencies;
|
|
2105
|
+
this.globalMaxConcurrent = Math.max(
|
|
2106
|
+
1,
|
|
2107
|
+
resolveLocalWorktreeConcurrency(options.globalMaxConcurrent, void 0)
|
|
2108
|
+
);
|
|
2109
|
+
this.sharedVariantResourceManager = options.sharedVariantResourceManager;
|
|
2110
|
+
if (this.flywheel) {
|
|
2111
|
+
this.dependencyResolver = new DependencyGraphResolver();
|
|
2112
|
+
this.resourceManager = new ProviderAwareResourceManager({
|
|
2113
|
+
globalMaxConcurrent: this.globalMaxConcurrent,
|
|
2114
|
+
fairnessMode: "weighted-fair",
|
|
2115
|
+
enableTelemetry: true
|
|
2116
|
+
});
|
|
2117
|
+
if (this.taskDependencies) {
|
|
2118
|
+
const graph = this.dependencyResolver.buildGraph(this.taskDependencies);
|
|
2119
|
+
const validation = this.dependencyResolver.validateGraph(graph);
|
|
2120
|
+
if (!validation.valid) {
|
|
2121
|
+
throw new Error(`Invalid task dependencies: ${validation.errors.join(", ")}`);
|
|
2122
|
+
}
|
|
2123
|
+
if (validation.warnings && validation.warnings.length > 0 && this.verbose) {
|
|
2124
|
+
emitWarning(
|
|
2125
|
+
"CFG-002",
|
|
2126
|
+
{
|
|
2127
|
+
taskId: "unknown",
|
|
2128
|
+
subsystem: "orchestrator",
|
|
2129
|
+
executionPhase: "setup"
|
|
2130
|
+
},
|
|
2131
|
+
{
|
|
2132
|
+
severity: "INFO" /* INFO */,
|
|
2133
|
+
metadata: {
|
|
2134
|
+
operation: "validateDependencyGraph",
|
|
2135
|
+
warnings: validation.warnings
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
this.executorTimeoutMs = validateTimeout(options.executorTimeoutMinutes, "minutes", {
|
|
2143
|
+
defaultMs: 45 * 60 * 1e3,
|
|
2144
|
+
// 45 minutes default
|
|
2145
|
+
minMs: 1 * 60 * 1e3,
|
|
2146
|
+
// 1 minute minimum
|
|
2147
|
+
maxMs: MAX_PER_VARIANT_TIMEOUT_MINUTES * 60 * 1e3,
|
|
2148
|
+
fieldName: "executorTimeoutMinutes"
|
|
2149
|
+
});
|
|
2150
|
+
this.orchestratorTimeoutMs = validateTimeout(options.timeoutMinutes, "minutes", {
|
|
2151
|
+
defaultMs: 90 * 60 * 1e3,
|
|
2152
|
+
// 90 minutes default
|
|
2153
|
+
minMs: 1 * 60 * 1e3,
|
|
2154
|
+
// 1 minute minimum
|
|
2155
|
+
maxMs: 1440 * 60 * 1e3,
|
|
2156
|
+
// 24 hours maximum
|
|
2157
|
+
fieldName: "timeoutMinutes"
|
|
2158
|
+
});
|
|
2159
|
+
if (this.orchestratorTimeoutMs < this.executorTimeoutMs) {
|
|
2160
|
+
const originalOrchestratorMin = this.orchestratorTimeoutMs / 6e4;
|
|
2161
|
+
const executorMin = this.executorTimeoutMs / 6e4;
|
|
2162
|
+
emitWarning(
|
|
2163
|
+
"CFG-002",
|
|
2164
|
+
{
|
|
2165
|
+
taskId: "unknown",
|
|
2166
|
+
subsystem: "orchestrator",
|
|
2167
|
+
executionPhase: "setup"
|
|
2168
|
+
},
|
|
2169
|
+
{
|
|
2170
|
+
severity: "WARN" /* WARN */,
|
|
2171
|
+
metadata: {
|
|
2172
|
+
operation: "validateTimeouts",
|
|
2173
|
+
originalOrchestratorTimeoutMin: originalOrchestratorMin,
|
|
2174
|
+
executorTimeoutMin: executorMin,
|
|
2175
|
+
adjustedOrchestratorTimeoutMin: executorMin,
|
|
2176
|
+
reason: "Orchestrator timeout must be >= executor timeout to prevent premature termination",
|
|
2177
|
+
suggestedFix: `Set --timeout ${Math.ceil(executorMin)} (or higher) to avoid auto-adjustment`
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
);
|
|
2181
|
+
console.warn(
|
|
2182
|
+
`
|
|
2183
|
+
Timeout Auto-Adjustment:
|
|
2184
|
+
Orchestrator timeout (${originalOrchestratorMin} min) was less than executor timeout (${executorMin} min).
|
|
2185
|
+
Auto-adjusted orchestrator timeout to ${executorMin} min to prevent premature termination.
|
|
2186
|
+
|
|
2187
|
+
To configure properly:
|
|
2188
|
+
- Set orchestrator timeout: --timeout ${Math.ceil(executorMin)} (or higher)
|
|
2189
|
+
- Or reduce executor timeout: --executor-timeout ${Math.floor(originalOrchestratorMin)}
|
|
2190
|
+
`
|
|
2191
|
+
);
|
|
2192
|
+
this.orchestratorTimeoutMs = this.executorTimeoutMs;
|
|
2193
|
+
}
|
|
2194
|
+
if (options.deadlineMs !== void 0) {
|
|
2195
|
+
if (!Number.isFinite(options.deadlineMs)) {
|
|
2196
|
+
throw new Error(`deadlineMs must be a finite timestamp, got: ${options.deadlineMs}`);
|
|
2197
|
+
}
|
|
2198
|
+
this.orchestratorDeadlineMs = options.deadlineMs;
|
|
2199
|
+
this.orchestratorTimeoutMs = this.resolveRemainingOrchestratorTimeoutMs();
|
|
2200
|
+
this.executorTimeoutMs = Math.min(this.executorTimeoutMs, this.orchestratorTimeoutMs);
|
|
2201
|
+
}
|
|
2202
|
+
this.prManager = new GitHubPRManager();
|
|
2203
|
+
this.workspaceAgentRunner = new WorkspaceAgentRunner();
|
|
2204
|
+
this.outputValidator = new UniversalOutputValidator();
|
|
2205
|
+
try {
|
|
2206
|
+
const costCalculator = new UniversalCostCalculator(TelemetryLogger.getInstance());
|
|
2207
|
+
this.costTracker = new UnifiedCostTracker(costCalculator, TelemetryLogger.getInstance(), {
|
|
2208
|
+
enabled: true,
|
|
2209
|
+
budgetThresholds: {
|
|
2210
|
+
warning: 10,
|
|
2211
|
+
critical: 50,
|
|
2212
|
+
daily: 100,
|
|
2213
|
+
monthly: 1e3
|
|
2214
|
+
},
|
|
2215
|
+
optimization: {
|
|
2216
|
+
enableSuggestions: false,
|
|
2217
|
+
// Disable suggestions for now
|
|
2218
|
+
minSavingsThreshold: 1,
|
|
2219
|
+
maxSuggestionsPerHour: 5
|
|
2220
|
+
},
|
|
2221
|
+
updateIntervalMs: 3e4
|
|
2222
|
+
// 30 seconds
|
|
2223
|
+
});
|
|
2224
|
+
} catch (error) {
|
|
2225
|
+
if (this.verbose) {
|
|
2226
|
+
emitWarning(
|
|
2227
|
+
"CFG-002",
|
|
2228
|
+
{
|
|
2229
|
+
taskId: "unknown",
|
|
2230
|
+
subsystem: "orchestrator",
|
|
2231
|
+
executionPhase: "setup"
|
|
2232
|
+
},
|
|
2233
|
+
{
|
|
2234
|
+
severity: "WARN" /* WARN */,
|
|
2235
|
+
metadata: {
|
|
2236
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2237
|
+
operation: "costTrackingInit"
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
this.localExecutor = new LocalExecutor({
|
|
2244
|
+
prManager: this.prManager,
|
|
2245
|
+
verbose: this.verbose,
|
|
2246
|
+
executorTimeoutMs: this.executorTimeoutMs,
|
|
2247
|
+
costTracker: this.costTracker,
|
|
2248
|
+
workspaceAgentRunner: this.workspaceAgentRunner,
|
|
2249
|
+
outputValidator: this.outputValidator,
|
|
2250
|
+
preserveWorktrees: this.preserveWorktrees,
|
|
2251
|
+
resultStreamer: this.resultStreamer
|
|
2252
|
+
});
|
|
2253
|
+
this.variantOrchestrator = new VariantOrchestrator();
|
|
2254
|
+
const existingProcessLifecycle = getProcessLifecycleManager();
|
|
2255
|
+
if (existingProcessLifecycle instanceof ProcessLifecycleManager) {
|
|
2256
|
+
this.processLifecycle = existingProcessLifecycle;
|
|
2257
|
+
this.ownsProcessLifecycle = false;
|
|
2258
|
+
} else {
|
|
2259
|
+
this.processLifecycle = new ProcessLifecycleManager({
|
|
2260
|
+
verbose: this.verbose,
|
|
2261
|
+
defaultTimeoutMinutes: options.executorTimeoutMinutes ?? 45,
|
|
2262
|
+
maxConcurrentProcesses: 50,
|
|
2263
|
+
cleanupOnTermination: true
|
|
2264
|
+
});
|
|
2265
|
+
setProcessLifecycleManager(this.processLifecycle);
|
|
2266
|
+
this.ownsProcessLifecycle = true;
|
|
2267
|
+
}
|
|
2268
|
+
this.cleanupService = new CleanupService(
|
|
2269
|
+
this.prManager,
|
|
2270
|
+
this.verbose,
|
|
2271
|
+
{
|
|
2272
|
+
cleanup: (worktreePath) => this.localExecutor.releaseWorktreeForBranchCleanup(worktreePath)
|
|
2273
|
+
},
|
|
2274
|
+
this.repoRoot
|
|
2275
|
+
);
|
|
2276
|
+
this.judgeDisplayService = new JudgeDisplayService({
|
|
2277
|
+
verbosity: this.aiJudgeVerbosity,
|
|
2278
|
+
showRationale: this.aiJudgeShowRationale,
|
|
2279
|
+
streaming: this.aiJudgeStreaming
|
|
2280
|
+
});
|
|
2281
|
+
}
|
|
2282
|
+
async executeLocalVariant(taskId, variant, prompt, retryMeta, abortSignal) {
|
|
2283
|
+
if (this.pushLocalVariants) {
|
|
2284
|
+
return this.localExecutor.executeVariantWithRetry(taskId, variant, prompt, {
|
|
2285
|
+
iterationIndex: retryMeta?.iterationIndex ?? 0,
|
|
2286
|
+
abortSignal
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
const outcome = await this.localExecutor.executeVariantFastWithRetry(taskId, variant, prompt, {
|
|
2290
|
+
commit: false,
|
|
2291
|
+
cleanup: false,
|
|
2292
|
+
iterationIndex: retryMeta?.iterationIndex ?? 0,
|
|
2293
|
+
abortSignal
|
|
2294
|
+
});
|
|
2295
|
+
return outcome.result;
|
|
2296
|
+
}
|
|
2297
|
+
resolveRemainingOrchestratorTimeoutMs() {
|
|
2298
|
+
if (this.orchestratorDeadlineMs === void 0) {
|
|
2299
|
+
return this.orchestratorTimeoutMs;
|
|
2300
|
+
}
|
|
2301
|
+
const remainingDeadlineMs = this.orchestratorDeadlineMs - Date.now();
|
|
2302
|
+
if (remainingDeadlineMs <= 0) {
|
|
2303
|
+
throw new Error("deadlineMs must be in the future");
|
|
2304
|
+
}
|
|
2305
|
+
return Math.min(this.orchestratorTimeoutMs, remainingDeadlineMs);
|
|
2306
|
+
}
|
|
2307
|
+
/**
|
|
2308
|
+
* Execute all variants in competition
|
|
2309
|
+
* Returns results for all variants
|
|
2310
|
+
*/
|
|
2311
|
+
async executeVariants(taskId, prompt, variants, retryMeta) {
|
|
2312
|
+
const observedResults = [];
|
|
2313
|
+
try {
|
|
2314
|
+
initEvaluationHandle(taskId, prompt, variants);
|
|
2315
|
+
} catch (handleError) {
|
|
2316
|
+
console.warn(
|
|
2317
|
+
`Failed to initialize competition handle for task ${taskId}: ${handleError instanceof Error ? handleError.message : String(handleError)}`
|
|
2318
|
+
);
|
|
2319
|
+
debugLog("Full stack:", handleError);
|
|
2320
|
+
}
|
|
2321
|
+
this.orchestratorTimeoutMs = this.resolveRemainingOrchestratorTimeoutMs();
|
|
2322
|
+
const outerController = new AbortController();
|
|
2323
|
+
let timeoutHandle;
|
|
2324
|
+
let timedOut = false;
|
|
2325
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
2326
|
+
timeoutHandle = setTimeout(() => {
|
|
2327
|
+
timedOut = true;
|
|
2328
|
+
outerController.abort(
|
|
2329
|
+
new Error(`Orchestrator timeout after ${this.orchestratorTimeoutMs / 6e4} minutes`)
|
|
2330
|
+
);
|
|
2331
|
+
reject(
|
|
2332
|
+
new Error(`Orchestrator timeout after ${this.orchestratorTimeoutMs / 6e4} minutes`)
|
|
2333
|
+
);
|
|
2334
|
+
}, this.orchestratorTimeoutMs);
|
|
2335
|
+
});
|
|
2336
|
+
let finalResult = null;
|
|
2337
|
+
let finalError;
|
|
2338
|
+
let lastDrain;
|
|
2339
|
+
const cohortPromise = this.executeVariantsInternal(
|
|
2340
|
+
taskId,
|
|
2341
|
+
prompt,
|
|
2342
|
+
variants,
|
|
2343
|
+
retryMeta,
|
|
2344
|
+
observedResults,
|
|
2345
|
+
outerController.signal
|
|
2346
|
+
);
|
|
2347
|
+
try {
|
|
2348
|
+
finalResult = await Promise.race([cohortPromise, timeoutPromise]);
|
|
2349
|
+
if (timeoutHandle) {
|
|
2350
|
+
clearTimeout(timeoutHandle);
|
|
2351
|
+
timeoutHandle = void 0;
|
|
2352
|
+
}
|
|
2353
|
+
try {
|
|
2354
|
+
await this.close();
|
|
2355
|
+
} catch (cleanupErr) {
|
|
2356
|
+
console.warn(
|
|
2357
|
+
`Cleanup error (post-success): ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}`
|
|
2358
|
+
);
|
|
2359
|
+
}
|
|
2360
|
+
return finalResult;
|
|
2361
|
+
} catch (error) {
|
|
2362
|
+
if (timedOut || error instanceof Error && error.message.includes("Orchestrator timeout")) {
|
|
2363
|
+
const sealedResults = sealVariantResultSnapshot(observedResults);
|
|
2364
|
+
const completedCount = countCompletedCompetitionObservations(sealedResults);
|
|
2365
|
+
lastDrain = await awaitBoundedCohortDrain(cohortPromise);
|
|
2366
|
+
const timeoutFailure = new CompetitionTimeoutFailure({
|
|
2367
|
+
timeoutMs: this.orchestratorTimeoutMs,
|
|
2368
|
+
completedVariants: completedCount,
|
|
2369
|
+
totalVariants: variants.length,
|
|
2370
|
+
observedResults: sealedResults,
|
|
2371
|
+
drain: lastDrain
|
|
2372
|
+
});
|
|
2373
|
+
finalError = timeoutFailure;
|
|
2374
|
+
console.log(chalk3.yellow(`
|
|
2375
|
+
${timeoutFailure.message}`));
|
|
2376
|
+
if (completedCount === 0) {
|
|
2377
|
+
console.log(chalk3.red("Timeout occurred before any variants completed\n"));
|
|
2378
|
+
} else {
|
|
2379
|
+
console.log(
|
|
2380
|
+
chalk3.red(
|
|
2381
|
+
`Timeout after ${completedCount}/${variants.length} variants completed` + (lastDrain.joined ? "" : " (cleanup did not fully join within drain bound)") + "\n"
|
|
2382
|
+
)
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
try {
|
|
2386
|
+
await this.close();
|
|
2387
|
+
} catch (cleanupErr) {
|
|
2388
|
+
console.warn(
|
|
2389
|
+
`Cleanup error: ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}`
|
|
2390
|
+
);
|
|
2391
|
+
debugLog("Full cleanup error:", cleanupErr);
|
|
2392
|
+
}
|
|
2393
|
+
throw timeoutFailure;
|
|
2394
|
+
}
|
|
2395
|
+
finalError = error;
|
|
2396
|
+
try {
|
|
2397
|
+
await this.close();
|
|
2398
|
+
} catch (cleanupErr) {
|
|
2399
|
+
console.warn(
|
|
2400
|
+
`Cleanup error: ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}`
|
|
2401
|
+
);
|
|
2402
|
+
debugLog("Full cleanup error:", cleanupErr);
|
|
2403
|
+
emitWarning(
|
|
2404
|
+
"PR-001",
|
|
2405
|
+
{
|
|
2406
|
+
taskId,
|
|
2407
|
+
subsystem: "orchestrator",
|
|
2408
|
+
executionPhase: "cleanup"
|
|
2409
|
+
},
|
|
2410
|
+
{
|
|
2411
|
+
severity: "WARN" /* WARN */,
|
|
2412
|
+
metadata: {
|
|
2413
|
+
error: cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr),
|
|
2414
|
+
operation: "cleanup"
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
);
|
|
2418
|
+
}
|
|
2419
|
+
throw error;
|
|
2420
|
+
} finally {
|
|
2421
|
+
if (timeoutHandle) {
|
|
2422
|
+
clearTimeout(timeoutHandle);
|
|
2423
|
+
}
|
|
2424
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2425
|
+
if (finalResult) {
|
|
2426
|
+
const anySuccess = finalResult.variants.some((variant) => variant.success);
|
|
2427
|
+
const ungradedIdentity = this.judgeOff && finalResult.variants.length === 1 && finalResult.winner !== null;
|
|
2428
|
+
const decision = this.judgeOff ? {
|
|
2429
|
+
outcome: "none",
|
|
2430
|
+
reason: "judge_off",
|
|
2431
|
+
decisionStatus: "not_run",
|
|
2432
|
+
decisionOutcome: "no_judge",
|
|
2433
|
+
primaryJudge: "n/a"
|
|
2434
|
+
} : void 0;
|
|
2435
|
+
const finalState = ungradedIdentity ? createEvaluationHandleFinalState({
|
|
2436
|
+
outcome: "no_winner",
|
|
2437
|
+
completedAt,
|
|
2438
|
+
winner: null,
|
|
2439
|
+
decision
|
|
2440
|
+
}) : finalResult.winner ? createEvaluationHandleFinalState({
|
|
2441
|
+
outcome: "winner_selected",
|
|
2442
|
+
completedAt,
|
|
2443
|
+
winner: {
|
|
2444
|
+
provider: finalResult.winner.provider,
|
|
2445
|
+
model: finalResult.winner.model,
|
|
2446
|
+
strategy: finalResult.winner.strategy,
|
|
2447
|
+
executionMode: finalResult.winner.executionMode,
|
|
2448
|
+
variant: finalResult.winner.variant
|
|
2449
|
+
},
|
|
2450
|
+
decision
|
|
2451
|
+
}) : createEvaluationHandleFinalState({
|
|
2452
|
+
outcome: anySuccess ? "no_winner" : "all_failed",
|
|
2453
|
+
completedAt,
|
|
2454
|
+
winner: null,
|
|
2455
|
+
decision
|
|
2456
|
+
});
|
|
2457
|
+
const finalizeResult = finalizeEvaluationHandle(taskId, finalState);
|
|
2458
|
+
if (finalizeResult.kind === "persistence_failed" && !isDurableHandleCompletion(finalizeResult, anySuccess ? "completed" : "failed")) {
|
|
2459
|
+
console.warn(
|
|
2460
|
+
`Evaluation handle persistence failed for ${taskId}: ${finalizeResult.error}`
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
} else if (finalError) {
|
|
2464
|
+
const message = finalError instanceof Error ? finalError.message : String(finalError);
|
|
2465
|
+
const isTimeout = finalError instanceof CompetitionTimeoutFailure || /orchestrator timeout|competition timed out/i.test(message);
|
|
2466
|
+
const finalizeResult = finalizeEvaluationHandle(
|
|
2467
|
+
taskId,
|
|
2468
|
+
createEvaluationHandleFinalState({
|
|
2469
|
+
outcome: isTimeout ? "timed_out" : "error",
|
|
2470
|
+
completedAt,
|
|
2471
|
+
errorMessage: finalError instanceof CompetitionTimeoutFailure ? `${message}; cleanupJoined=${finalError.cleanupJoined}` : message,
|
|
2472
|
+
winner: null
|
|
2473
|
+
})
|
|
2474
|
+
);
|
|
2475
|
+
if (finalizeResult.kind === "persistence_failed") {
|
|
2476
|
+
console.warn(
|
|
2477
|
+
`Evaluation handle persistence failed for ${taskId}: ${finalizeResult.error}`
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
/**
|
|
2484
|
+
* Internal method to execute all variants (without timeout wrapper)
|
|
2485
|
+
*/
|
|
2486
|
+
async executeVariantsInternal(taskId, prompt, variants, retryMeta, observedResults = [], outerSignal) {
|
|
2487
|
+
console.log(chalk3.bold.cyan("\nPoetic Variant Competition\n"));
|
|
2488
|
+
console.log(chalk3.gray(`Task ID: ${taskId}`));
|
|
2489
|
+
console.log(chalk3.gray(`Variants: ${variants.length}`));
|
|
2490
|
+
console.log(chalk3.gray(`Executor timeout: ${this.executorTimeoutMs / 6e4} minutes`));
|
|
2491
|
+
console.log(
|
|
2492
|
+
chalk3.gray(
|
|
2493
|
+
`Orchestrator timeout: ${this.orchestratorTimeoutMs / 6e4} minutes (safety net)
|
|
2494
|
+
`
|
|
2495
|
+
)
|
|
2496
|
+
);
|
|
2497
|
+
if (this.aiJudgeStreaming) {
|
|
2498
|
+
const { ResultStreamer } = await import("./result-streamer-WEHW476R.js");
|
|
2499
|
+
if (!this.resultStreamer) {
|
|
2500
|
+
this.resultStreamer = new ResultStreamer();
|
|
2501
|
+
this.ownsResultStreamer = true;
|
|
2502
|
+
}
|
|
2503
|
+
this.localExecutor.setResultStreamer(this.resultStreamer);
|
|
2504
|
+
this.judgeDisplayService.setResultStreamer(this.resultStreamer);
|
|
2505
|
+
this.judgeListenerCleanup = this.judgeDisplayService.setupStreamingListeners(taskId);
|
|
2506
|
+
}
|
|
2507
|
+
const localVariants = variants;
|
|
2508
|
+
console.log(chalk3.blue("Execution Plan:"));
|
|
2509
|
+
console.log(chalk3.gray(` Local variants: ${localVariants.length}
|
|
2510
|
+
`));
|
|
2511
|
+
const results = [];
|
|
2512
|
+
if (this.flywheel && this.dependencyResolver && this.resourceManager) {
|
|
2513
|
+
console.log(chalk3.bold.blue("Executing Variants with Dependency Awareness\n"));
|
|
2514
|
+
return this.executeVariantsWithDependencies(
|
|
2515
|
+
taskId,
|
|
2516
|
+
prompt,
|
|
2517
|
+
variants,
|
|
2518
|
+
results,
|
|
2519
|
+
retryMeta,
|
|
2520
|
+
outerSignal
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
console.log(chalk3.bold.blue("Executing All Variants in Parallel\n"));
|
|
2524
|
+
const allPromises = [];
|
|
2525
|
+
const localResourceManager = this.sharedVariantResourceManager ?? createLocalVariantResourceManager(this.globalMaxConcurrent);
|
|
2526
|
+
const controller = new AbortController();
|
|
2527
|
+
const signal = controller.signal;
|
|
2528
|
+
if (outerSignal) {
|
|
2529
|
+
if (outerSignal.aborted) {
|
|
2530
|
+
controller.abort(outerSignal.reason);
|
|
2531
|
+
} else {
|
|
2532
|
+
outerSignal.addEventListener("abort", () => controller.abort(outerSignal.reason), {
|
|
2533
|
+
once: true
|
|
2534
|
+
});
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
const geminiCount = localVariants.filter((v) => v.provider === "gemini").length;
|
|
2538
|
+
if (geminiCount > 1 && isVerboseOutputRequested()) {
|
|
2539
|
+
console.log(
|
|
2540
|
+
chalk3.cyan(
|
|
2541
|
+
`Running ${geminiCount} Gemini variant(s) in parallel (HOME isolation, auto-fallback to serial on ECONNRESET)
|
|
2542
|
+
`
|
|
2543
|
+
)
|
|
2544
|
+
);
|
|
2545
|
+
}
|
|
2546
|
+
const localPromises = localVariants.map((variant) => {
|
|
2547
|
+
const variantLabel = `${variant.provider} ${variant.model || "default-model"} ${variant.strategy}`;
|
|
2548
|
+
return (async () => {
|
|
2549
|
+
let providerId;
|
|
2550
|
+
try {
|
|
2551
|
+
providerId = toConcurrencyProviderId(variant.provider);
|
|
2552
|
+
} catch (err) {
|
|
2553
|
+
const failureResult = this.buildFailureResult(variant, err, 0);
|
|
2554
|
+
this.logVariantCompleteSafe(taskId, variant, failureResult);
|
|
2555
|
+
return failureResult;
|
|
2556
|
+
}
|
|
2557
|
+
const acquisitionTimeoutMs = this.orchestratorTimeoutMs ?? 30 * 60 * 1e3;
|
|
2558
|
+
if (process.env.DEBUG_ORCHESTRATOR) {
|
|
2559
|
+
console.warn(
|
|
2560
|
+
`[DEBUG ORCHESTRATOR] Acquiring resource for ${providerId} (timeout: ${acquisitionTimeoutMs}ms)...`
|
|
2561
|
+
);
|
|
2562
|
+
}
|
|
2563
|
+
const handle = await localResourceManager.acquire(providerId, signal, acquisitionTimeoutMs);
|
|
2564
|
+
const startTime = Date.now();
|
|
2565
|
+
this.logVariantStartSafe(taskId, variant);
|
|
2566
|
+
console.log(chalk3.cyan(`\u2192 Starting: ${variantLabel} ${chalk3.gray("(local)")}`));
|
|
2567
|
+
try {
|
|
2568
|
+
const result = await this.executeLocalVariant(taskId, variant, prompt, retryMeta, signal);
|
|
2569
|
+
observedResults.push(result);
|
|
2570
|
+
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
2571
|
+
if (result?.success) {
|
|
2572
|
+
console.log(
|
|
2573
|
+
chalk3.green(
|
|
2574
|
+
`${variantLabel}: Completed in ${elapsed}s (local)${result?.prNumber ? ` (PR #${result.prNumber})` : ""}
|
|
2575
|
+
`
|
|
2576
|
+
)
|
|
2577
|
+
);
|
|
2578
|
+
} else {
|
|
2579
|
+
console.log(
|
|
2580
|
+
chalk3.red(
|
|
2581
|
+
`${variantLabel}: Failed after ${elapsed}s - ${result?.error?.message || "Unknown error"}
|
|
2582
|
+
`
|
|
2583
|
+
)
|
|
2584
|
+
);
|
|
2585
|
+
const errorMsg = result?.error?.message?.toLowerCase() ?? "";
|
|
2586
|
+
if (errorMsg.includes("rate limit") || errorMsg.includes("ratelimit") || errorMsg.includes("too many requests") || matchesErrorPattern(errorMsg, "429")) {
|
|
2587
|
+
recordProviderRateLimit(providerId);
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
try {
|
|
2591
|
+
updateEvaluationHandleWithResult(taskId, result);
|
|
2592
|
+
} catch (handleError) {
|
|
2593
|
+
console.warn(
|
|
2594
|
+
`Failed to update competition handle for local task ${taskId} variant ${variant.provider}-${variant.strategy}: ${handleError instanceof Error ? handleError.message : String(handleError)}`
|
|
2595
|
+
);
|
|
2596
|
+
debugLog("Full stack:", handleError);
|
|
2597
|
+
}
|
|
2598
|
+
this.logVariantCompleteSafe(taskId, variant, result);
|
|
2599
|
+
return result;
|
|
2600
|
+
} catch (error) {
|
|
2601
|
+
const elapsedSeconds = (Date.now() - startTime) / 1e3;
|
|
2602
|
+
const elapsed = elapsedSeconds.toFixed(1);
|
|
2603
|
+
console.log(
|
|
2604
|
+
chalk3.red(
|
|
2605
|
+
`${variantLabel}: Error after ${elapsed}s - ${error instanceof Error ? error.message : String(error)}
|
|
2606
|
+
`
|
|
2607
|
+
)
|
|
2608
|
+
);
|
|
2609
|
+
const errorMsg = error instanceof Error ? error.message.toLowerCase() : "";
|
|
2610
|
+
if (errorMsg.includes("rate limit") || errorMsg.includes("ratelimit") || errorMsg.includes("too many requests") || matchesErrorPattern(errorMsg, "429")) {
|
|
2611
|
+
recordProviderRateLimit(providerId);
|
|
2612
|
+
}
|
|
2613
|
+
const failureResult = this.buildFailureResult(variant, error, elapsedSeconds);
|
|
2614
|
+
observedResults.push(failureResult);
|
|
2615
|
+
try {
|
|
2616
|
+
updateEvaluationHandleWithResult(taskId, failureResult);
|
|
2617
|
+
} catch (handleError) {
|
|
2618
|
+
console.warn(
|
|
2619
|
+
`Failed to update competition handle for failed local task ${taskId} variant ${variant.provider}-${variant.strategy}: ${handleError instanceof Error ? handleError.message : String(handleError)}`
|
|
2620
|
+
);
|
|
2621
|
+
debugLog("Full stack:", handleError);
|
|
2622
|
+
}
|
|
2623
|
+
this.logVariantCompleteSafe(taskId, variant, failureResult);
|
|
2624
|
+
return failureResult;
|
|
2625
|
+
} finally {
|
|
2626
|
+
await handle.release();
|
|
2627
|
+
}
|
|
2628
|
+
})();
|
|
2629
|
+
});
|
|
2630
|
+
allPromises.push(...localPromises);
|
|
2631
|
+
const allResults = await Promise.allSettled(allPromises);
|
|
2632
|
+
if (outerSignal?.aborted) {
|
|
2633
|
+
throw outerSignal.reason instanceof Error ? outerSignal.reason : new Error("Variant execution aborted");
|
|
2634
|
+
}
|
|
2635
|
+
allResults.forEach((result) => {
|
|
2636
|
+
if (result.status === "fulfilled") {
|
|
2637
|
+
results.push(result.value);
|
|
2638
|
+
} else if (result.status === "rejected") {
|
|
2639
|
+
const failureResult = {
|
|
2640
|
+
success: false,
|
|
2641
|
+
error: result.reason instanceof Error ? result.reason : new Error(String(result.reason)),
|
|
2642
|
+
duration: 0,
|
|
2643
|
+
provider: "unknown",
|
|
2644
|
+
variant: "unknown",
|
|
2645
|
+
executionMode: "local",
|
|
2646
|
+
strategy: "unknown",
|
|
2647
|
+
completeness: 0
|
|
2648
|
+
};
|
|
2649
|
+
results.push(failureResult);
|
|
2650
|
+
}
|
|
2651
|
+
});
|
|
2652
|
+
const state = this.getVariantState(results);
|
|
2653
|
+
this.logVariantApplicationWarnings(results);
|
|
2654
|
+
console.log(chalk3.bold.blue("\nCompetition Results\n"));
|
|
2655
|
+
if (state.completed < state.total || state.succeeded === 0) {
|
|
2656
|
+
console.log(
|
|
2657
|
+
chalk3.yellow(
|
|
2658
|
+
state.succeeded === 0 ? this.judgeOff ? "No successful variants; no judge ran (--judge off)" : "No successful variants - judges will still evaluate available results" : `Partial Results - Only ${state.completed}/${state.total} variants completed`
|
|
2659
|
+
)
|
|
2660
|
+
);
|
|
2661
|
+
console.log(
|
|
2662
|
+
chalk3.gray(
|
|
2663
|
+
` Succeeded: ${state.succeeded}, Failed: ${state.failed}, Timed out: ${state.timedOut}
|
|
2664
|
+
`
|
|
2665
|
+
)
|
|
2666
|
+
);
|
|
2667
|
+
}
|
|
2668
|
+
if (outerSignal?.aborted) {
|
|
2669
|
+
throw outerSignal.reason instanceof Error ? outerSignal.reason : new Error("Variant execution aborted before judge selection");
|
|
2670
|
+
}
|
|
2671
|
+
const winner = this.shouldRunWinnerSelection(state) ? await this.variantOrchestrator.selectWinner(results, {
|
|
2672
|
+
useAIJudge: this.aiJudge,
|
|
2673
|
+
originalPrompt: prompt,
|
|
2674
|
+
model: this.aiJudgeModel,
|
|
2675
|
+
aiJudgeVerbose: this.aiJudgeVerbose,
|
|
2676
|
+
aiJudgeShowRationale: this.aiJudgeShowRationale,
|
|
2677
|
+
aiJudgeCriteriaWeights: this.aiJudgeCriteriaWeights,
|
|
2678
|
+
aiJudgeMinScore: this.aiJudgeMinScore,
|
|
2679
|
+
aiJudgeConfidenceThreshold: this.aiJudgeConfidenceThreshold,
|
|
2680
|
+
aiJudgeStreaming: this.aiJudgeStreaming,
|
|
2681
|
+
aiJudgeRun: this.aiJudgeRun,
|
|
2682
|
+
aiJudgePrimary: this.aiJudgePrimary,
|
|
2683
|
+
judgeModels: this.judgeModels,
|
|
2684
|
+
judgeModel: this.judgeModel,
|
|
2685
|
+
judgeAllowUnsafeModel: this.judgeAllowUnsafeModel,
|
|
2686
|
+
judgeStrict: this.judgeStrict,
|
|
2687
|
+
anonymizeJudge: this.anonymizeJudge,
|
|
2688
|
+
aiJudgeVerbosity: this.aiJudgeVerbosity,
|
|
2689
|
+
verificationMode: this.verificationMode,
|
|
2690
|
+
resultStreamer: this.resultStreamer,
|
|
2691
|
+
// Enable streaming for final results
|
|
2692
|
+
taskId,
|
|
2693
|
+
competitionMode: true,
|
|
2694
|
+
aiJudgeTelemetry: this.aiJudgeTelemetry,
|
|
2695
|
+
ungradedIdentity: this.judgeOff && state.total === 1
|
|
2696
|
+
}) : null;
|
|
2697
|
+
if (winner && !this.judgeOff && this.shouldTriggerOptimization(results)) {
|
|
2698
|
+
try {
|
|
2699
|
+
const optimizer = new AutomaticPromptOptimizer({
|
|
2700
|
+
telemetryLogger: this.getTelemetryLogger(),
|
|
2701
|
+
enableAutoTuning: this.enableAutoTuning ?? false
|
|
2702
|
+
});
|
|
2703
|
+
const recommendation = await optimizer.analyzePrompt(prompt);
|
|
2704
|
+
if (recommendation && recommendation.action !== "no_action" && recommendation.action !== "needs_more_data") {
|
|
2705
|
+
const optimizationResult = await optimizer.optimizePrompt(prompt, recommendation);
|
|
2706
|
+
if (this.verbose) {
|
|
2707
|
+
console.log(chalk3.gray("Prompt optimization analysis complete"));
|
|
2708
|
+
if (optimizationResult.improvements.length > 0) {
|
|
2709
|
+
console.log(chalk3.gray(` Improvements: ${optimizationResult.improvements.length}`));
|
|
2710
|
+
}
|
|
2711
|
+
if (optimizationResult.warnings.length > 0) {
|
|
2712
|
+
console.log(chalk3.yellow(` Warnings: ${optimizationResult.warnings.length}`));
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
optimizer.close();
|
|
2717
|
+
} catch (optimizationError) {
|
|
2718
|
+
if (this.verbose) {
|
|
2719
|
+
console.log(
|
|
2720
|
+
chalk3.yellow(
|
|
2721
|
+
`Prompt optimization skipped: ${optimizationError instanceof Error ? optimizationError.message : String(optimizationError)}`
|
|
2722
|
+
)
|
|
2723
|
+
);
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
this.displayResults(results, winner);
|
|
2728
|
+
if (winner?.judgeEvaluation) {
|
|
2729
|
+
const variantResultsMap = new Map(results.map((r) => [r.variant, r]));
|
|
2730
|
+
this.judgeDisplayService.displayEvaluation(winner.judgeEvaluation, variantResultsMap);
|
|
2731
|
+
}
|
|
2732
|
+
let mergedResult = null;
|
|
2733
|
+
if (this.synthesize && winner) {
|
|
2734
|
+
console.log(chalk3.blue("\nGenerating synthesized solution from top variants...\n"));
|
|
2735
|
+
try {
|
|
2736
|
+
const { VariantResultsMerger } = await import("./synthesis-TGQUVAW3.js");
|
|
2737
|
+
const merger = new VariantResultsMerger({
|
|
2738
|
+
defaultTopN: 2,
|
|
2739
|
+
defaultStrategy: "agentic-synthesis",
|
|
2740
|
+
maxVariantsToMerge: 5
|
|
2741
|
+
});
|
|
2742
|
+
const synthesisMergeOptions = {
|
|
2743
|
+
enabled: true,
|
|
2744
|
+
topN: this.synthesisTopN,
|
|
2745
|
+
createPR: false,
|
|
2746
|
+
strategy: this.synthesisStrategy,
|
|
2747
|
+
taskId,
|
|
2748
|
+
aiModel: this.synthesisModel,
|
|
2749
|
+
taskDescription: prompt,
|
|
2750
|
+
judgeRationale: winner?.judgeEvaluation?.winnerPrimaryReason ?? winner?.judgeEvaluation?.rationalePacket?.selectionRationale.primary ?? winner?.judgeEvaluation?.reasoning ?? void 0,
|
|
2751
|
+
judgeContext: this.variantOrchestrator.getLastSynthesisJudgeContext(),
|
|
2752
|
+
declaredWinner: winner ? { provider: winner.provider, variant: winner.variant } : void 0
|
|
2753
|
+
};
|
|
2754
|
+
mergedResult = await merger.merge(results, synthesisMergeOptions);
|
|
2755
|
+
if (mergedResult?.success) {
|
|
2756
|
+
mergedResult.mergeGenerated = true;
|
|
2757
|
+
mergedResult.complementaryPatchApplied = false;
|
|
2758
|
+
mergedResult.deliverySucceeded = true;
|
|
2759
|
+
mergedResult.synthesisApplied = false;
|
|
2760
|
+
mergedResult.winnerFallback = mergedResult.winnerFallback === true || mergedResult.winnerClone === true;
|
|
2761
|
+
mergedResult.patchApplyValidated = false;
|
|
2762
|
+
mergedResult.gatesPassed = false;
|
|
2763
|
+
if (mergedResult.winnerClone === true) {
|
|
2764
|
+
mergedResult.fallbackReason = mergedResult.reasoning;
|
|
2765
|
+
mergedResult.synthesisOutcomeStatus ??= "winner_carry_forward";
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
if (mergedResult) {
|
|
2769
|
+
const selectedVariants = resolveSynthesisSelectedVariants(
|
|
2770
|
+
results,
|
|
2771
|
+
mergedResult.inputVariants
|
|
2772
|
+
);
|
|
2773
|
+
const synthesisTelemetry = buildSynthesisTelemetrySnapshot({
|
|
2774
|
+
results,
|
|
2775
|
+
topVariants: selectedVariants,
|
|
2776
|
+
declaredWinner: synthesisMergeOptions.declaredWinner,
|
|
2777
|
+
synthesisIntents: synthesisMergeOptions.synthesisIntents,
|
|
2778
|
+
intentFilteredMaterial: synthesisMergeOptions.intentFilteredMaterial
|
|
2779
|
+
});
|
|
2780
|
+
if (synthesisTelemetry) {
|
|
2781
|
+
Object.assign(mergedResult, synthesisTelemetry);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
if (mergedResult?.success) {
|
|
2785
|
+
console.log(chalk3.green("\nSynthesis complete"));
|
|
2786
|
+
console.log(chalk3.gray(` ${mergedResult.reasoning}`));
|
|
2787
|
+
const winnerWorktree = winner?.retainedWorktreePath;
|
|
2788
|
+
if (this.synthesisStrategy === "agentic-synthesis" && mergedResult.synthesisWorktreePath) {
|
|
2789
|
+
const synthesisWorktree = mergedResult.synthesisWorktreePath.trim();
|
|
2790
|
+
try {
|
|
2791
|
+
if (!synthesisWorktree) {
|
|
2792
|
+
throw new Error("missing synthesis worktree path");
|
|
2793
|
+
}
|
|
2794
|
+
const verifyPlan = resolveSynthesisVerifyCommands(synthesisWorktree);
|
|
2795
|
+
if (!verifyPlan.available) {
|
|
2796
|
+
markSynthesisWinnerFallback(
|
|
2797
|
+
mergedResult,
|
|
2798
|
+
"Agentic synthesis worktree verification unavailable for the target language; winner-only result retained",
|
|
2799
|
+
winner
|
|
2800
|
+
);
|
|
2801
|
+
console.warn(
|
|
2802
|
+
chalk3.yellow(
|
|
2803
|
+
" Agentic synthesis worktree verification unavailable: no deterministic typecheck/build signal detected for the target language."
|
|
2804
|
+
)
|
|
2805
|
+
);
|
|
2806
|
+
console.warn(
|
|
2807
|
+
chalk3.gray(
|
|
2808
|
+
" Retaining the judged winner without complementary donor changes."
|
|
2809
|
+
)
|
|
2810
|
+
);
|
|
2811
|
+
await cleanupAgenticSynthesisWorktree(synthesisWorktree, this.preserveWorktrees);
|
|
2812
|
+
} else {
|
|
2813
|
+
const verifyResult = await runVerifyCommands({
|
|
2814
|
+
cwd: synthesisWorktree,
|
|
2815
|
+
baselineCwd: winnerWorktree,
|
|
2816
|
+
// Never let validation auto-install into the product worktree.
|
|
2817
|
+
// If the toolchain is unavailable, retain the judged winner.
|
|
2818
|
+
commands: verifyPlan.commands,
|
|
2819
|
+
timeoutMs: 6e4
|
|
2820
|
+
});
|
|
2821
|
+
mergedResult.validationScope = "typecheck-only";
|
|
2822
|
+
if (verifyResult.passed) {
|
|
2823
|
+
markTypecheckOnlySynthesisFallback(
|
|
2824
|
+
mergedResult,
|
|
2825
|
+
winner,
|
|
2826
|
+
"Agentic synthesis was not promoted because PR workflow validation is typecheck-only"
|
|
2827
|
+
);
|
|
2828
|
+
await cleanupAgenticSynthesisWorktree(synthesisWorktree, this.preserveWorktrees);
|
|
2829
|
+
} else {
|
|
2830
|
+
const failedResult = verifyResult.results.find((result) => result.exitCode !== 0);
|
|
2831
|
+
const baselineAwareMessage = failedResult && typeof failedResult.newErrorCount === "number" && typeof failedResult.currentErrorCount === "number" && typeof failedResult.baselineErrorCount === "number" ? `agentic synthesis introduced ${failedResult.newErrorCount} new typecheck error(s) (baseline=${failedResult.baselineErrorCount}, current=${failedResult.currentErrorCount})` : "agentic synthesis failed build gate";
|
|
2832
|
+
markSynthesisWinnerFallback(
|
|
2833
|
+
mergedResult,
|
|
2834
|
+
"Agentic synthesis edited worktree failed deterministic gates; winner-only result retained",
|
|
2835
|
+
winner,
|
|
2836
|
+
baselineAwareMessage
|
|
2837
|
+
);
|
|
2838
|
+
console.warn(
|
|
2839
|
+
chalk3.yellow(
|
|
2840
|
+
` Agentic synthesis worktree failed validation: ${baselineAwareMessage}`
|
|
2841
|
+
)
|
|
2842
|
+
);
|
|
2843
|
+
console.warn(
|
|
2844
|
+
chalk3.gray(
|
|
2845
|
+
" Retaining the judged winner without complementary donor changes."
|
|
2846
|
+
)
|
|
2847
|
+
);
|
|
2848
|
+
await cleanupAgenticSynthesisWorktree(synthesisWorktree, this.preserveWorktrees);
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
} catch (validationError) {
|
|
2852
|
+
const validationErrorMessage = validationError instanceof Error ? validationError.message : String(validationError);
|
|
2853
|
+
markSynthesisWinnerFallback(
|
|
2854
|
+
mergedResult,
|
|
2855
|
+
"Agentic synthesis worktree validation failed; winner-only result retained",
|
|
2856
|
+
winner,
|
|
2857
|
+
validationErrorMessage
|
|
2858
|
+
);
|
|
2859
|
+
console.warn(
|
|
2860
|
+
chalk3.yellow(
|
|
2861
|
+
` Could not validate agentic synthesis worktree: ${validationError instanceof Error ? validationError.message : String(validationError)}`
|
|
2862
|
+
)
|
|
2863
|
+
);
|
|
2864
|
+
console.warn(
|
|
2865
|
+
chalk3.gray(" Retaining the judged winner without complementary donor changes.")
|
|
2866
|
+
);
|
|
2867
|
+
await cleanupAgenticSynthesisWorktree(synthesisWorktree, this.preserveWorktrees);
|
|
2868
|
+
}
|
|
2869
|
+
} else if (this.synthesisStrategy !== "agentic-synthesis" && winnerWorktree && mergedResult.complementaryPatch) {
|
|
2870
|
+
try {
|
|
2871
|
+
const augmentation = await applyComplementaryPatchToWinnerWorktree({
|
|
2872
|
+
winnerWorktree,
|
|
2873
|
+
complementaryPatch: mergedResult.complementaryPatch,
|
|
2874
|
+
validate: async (worktreePath, baselineWorktreePath) => {
|
|
2875
|
+
const verifyPlan = resolveSynthesisVerifyCommands(worktreePath);
|
|
2876
|
+
if (!verifyPlan.available) {
|
|
2877
|
+
return {
|
|
2878
|
+
ok: false,
|
|
2879
|
+
error: "Complementary synthesis was not promoted because target-language verification is unavailable"
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2882
|
+
const verifyResult = await runVerifyCommands({
|
|
2883
|
+
cwd: worktreePath,
|
|
2884
|
+
baselineCwd: baselineWorktreePath,
|
|
2885
|
+
// Never let validation auto-install into the product worktree.
|
|
2886
|
+
// If the toolchain is unavailable, retain the judged winner.
|
|
2887
|
+
commands: verifyPlan.commands,
|
|
2888
|
+
timeoutMs: 6e4
|
|
2889
|
+
});
|
|
2890
|
+
if (mergedResult) {
|
|
2891
|
+
mergedResult.validationScope = "typecheck-only";
|
|
2892
|
+
}
|
|
2893
|
+
if (verifyResult.passed) {
|
|
2894
|
+
return {
|
|
2895
|
+
ok: false,
|
|
2896
|
+
error: "Complementary synthesis was not promoted because PR workflow validation is typecheck-only"
|
|
2897
|
+
};
|
|
2898
|
+
}
|
|
2899
|
+
const failedResult = verifyResult.results.find((result) => result.exitCode !== 0);
|
|
2900
|
+
const baselineAwareMessage = failedResult && typeof failedResult.newErrorCount === "number" && typeof failedResult.currentErrorCount === "number" && typeof failedResult.baselineErrorCount === "number" ? `augmented code introduced ${failedResult.newErrorCount} new typecheck error(s) (baseline=${failedResult.baselineErrorCount}, current=${failedResult.currentErrorCount})` : "augmented code failed build gate";
|
|
2901
|
+
return {
|
|
2902
|
+
ok: false,
|
|
2903
|
+
error: baselineAwareMessage
|
|
2904
|
+
};
|
|
2905
|
+
}
|
|
2906
|
+
});
|
|
2907
|
+
mergedResult.applyMode = augmentation.applyMode;
|
|
2908
|
+
mergedResult.applyError = augmentation.applyError;
|
|
2909
|
+
if (augmentation.winnerWorktreeDirty) {
|
|
2910
|
+
throw new SynthesisWinnerRestorationError(
|
|
2911
|
+
`Complementary synthesis failed and the winner worktree could not be restored exactly: ${augmentation.applyError ?? "unknown restoration failure"}`
|
|
2912
|
+
);
|
|
2913
|
+
}
|
|
2914
|
+
if (augmentation.applied && augmentation.augmentedFiles.length > 0) {
|
|
2915
|
+
mergedResult.complementaryPatchApplied = true;
|
|
2916
|
+
mergedResult.synthesisApplied = true;
|
|
2917
|
+
mergedResult.winnerFallback = false;
|
|
2918
|
+
delete mergedResult.fallbackReason;
|
|
2919
|
+
mergedResult.patchApplyValidated = true;
|
|
2920
|
+
mergedResult.gatesPassed = false;
|
|
2921
|
+
mergedResult.validationScope = "typecheck-only";
|
|
2922
|
+
console.log(
|
|
2923
|
+
chalk3.green(
|
|
2924
|
+
` ${augmentation.augmentedFiles.length} file(s) augmented with complementary changes (${augmentation.applyMode ?? "unknown"} apply, typecheck verified)`
|
|
2925
|
+
)
|
|
2926
|
+
);
|
|
2927
|
+
console.log(chalk3.gray(` ${augmentation.augmentedFiles.join(", ")}`));
|
|
2928
|
+
} else if (augmentation.applyError) {
|
|
2929
|
+
if (augmentation.applyError === "Complementary synthesis was not promoted because PR workflow validation is typecheck-only") {
|
|
2930
|
+
markTypecheckOnlySynthesisFallback(mergedResult, winner, augmentation.applyError);
|
|
2931
|
+
} else {
|
|
2932
|
+
markSynthesisWinnerFallback(
|
|
2933
|
+
mergedResult,
|
|
2934
|
+
"Complementary patch application failed; winner-only result retained",
|
|
2935
|
+
winner,
|
|
2936
|
+
augmentation.applyError
|
|
2937
|
+
);
|
|
2938
|
+
}
|
|
2939
|
+
console.warn(
|
|
2940
|
+
chalk3.yellow(
|
|
2941
|
+
` Could not apply complementary patch: ${augmentation.applyError}. Retaining judged winner.`
|
|
2942
|
+
)
|
|
2943
|
+
);
|
|
2944
|
+
} else {
|
|
2945
|
+
markSynthesisWinnerFallback(
|
|
2946
|
+
mergedResult,
|
|
2947
|
+
"Complementary patch produced no validated winner-worktree changes; winner-only result retained",
|
|
2948
|
+
winner
|
|
2949
|
+
);
|
|
2950
|
+
}
|
|
2951
|
+
} catch (applyError) {
|
|
2952
|
+
if (applyError instanceof SynthesisWinnerRestorationError) {
|
|
2953
|
+
throw applyError;
|
|
2954
|
+
}
|
|
2955
|
+
const applyErrorMessage = applyError instanceof Error ? applyError.message : String(applyError);
|
|
2956
|
+
markSynthesisWinnerFallback(
|
|
2957
|
+
mergedResult,
|
|
2958
|
+
"Complementary patch application failed; winner-only result retained",
|
|
2959
|
+
winner,
|
|
2960
|
+
applyErrorMessage
|
|
2961
|
+
);
|
|
2962
|
+
console.warn(
|
|
2963
|
+
chalk3.yellow(
|
|
2964
|
+
` Could not apply complementary patch: ${applyErrorMessage}. Retaining judged winner.`
|
|
2965
|
+
)
|
|
2966
|
+
);
|
|
2967
|
+
}
|
|
2968
|
+
} else if (this.synthesisStrategy === "agentic-synthesis") {
|
|
2969
|
+
markSynthesisWinnerFallback(
|
|
2970
|
+
mergedResult,
|
|
2971
|
+
"Agentic synthesis did not retain an editable synthesis worktree for direct validation; winner-only result retained",
|
|
2972
|
+
winner
|
|
2973
|
+
);
|
|
2974
|
+
} else if (!mergedResult.complementaryPatch) {
|
|
2975
|
+
markSynthesisWinnerFallback(
|
|
2976
|
+
mergedResult,
|
|
2977
|
+
mergedResult.winnerClone === true ? mergedResult.reasoning ?? "Synthesis retained judged winner" : "No complementary changes from losers; winner-only result retained",
|
|
2978
|
+
winner
|
|
2979
|
+
);
|
|
2980
|
+
console.log(
|
|
2981
|
+
chalk3.gray(" No complementary changes from losers (all overlapped winner)")
|
|
2982
|
+
);
|
|
2983
|
+
} else {
|
|
2984
|
+
markSynthesisWinnerFallback(
|
|
2985
|
+
mergedResult,
|
|
2986
|
+
"Complementary patch could not be applied because the winner worktree was unavailable; winner-only result retained",
|
|
2987
|
+
winner
|
|
2988
|
+
);
|
|
2989
|
+
}
|
|
2990
|
+
} else if (mergedResult) {
|
|
2991
|
+
mergedResult.deliverySucceeded = true;
|
|
2992
|
+
mergedResult.synthesisApplied = false;
|
|
2993
|
+
mergedResult.winnerFallback = true;
|
|
2994
|
+
mergedResult.fallbackReason = mergedResult.error ?? "Synthesis generation failed; winner-only result retained";
|
|
2995
|
+
mergedResult.patchApplyValidated = false;
|
|
2996
|
+
mergedResult.gatesPassed = false;
|
|
2997
|
+
if (mergedResult.error) {
|
|
2998
|
+
console.log(
|
|
2999
|
+
chalk3.yellow(`
|
|
3000
|
+
Synthesis completed with warnings: ${mergedResult.error}
|
|
3001
|
+
`)
|
|
3002
|
+
);
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
} catch (error) {
|
|
3006
|
+
if (error instanceof SynthesisWinnerRestorationError) {
|
|
3007
|
+
throw error;
|
|
3008
|
+
}
|
|
3009
|
+
console.warn(chalk3.yellow("\nSynthesis failed, but winner is still selected"));
|
|
3010
|
+
console.warn(
|
|
3011
|
+
chalk3.gray(` Error: ${error instanceof Error ? error.message : String(error)}
|
|
3012
|
+
`)
|
|
3013
|
+
);
|
|
3014
|
+
}
|
|
3015
|
+
try {
|
|
3016
|
+
const fs2 = await import("fs/promises");
|
|
3017
|
+
const pathMod = await import("path");
|
|
3018
|
+
const dir = pathMod.join(resolvePoeticDirFromContext(), "telemetry", "synthesis");
|
|
3019
|
+
await fs2.mkdir(dir, { recursive: true });
|
|
3020
|
+
const code = mergedResult?.code;
|
|
3021
|
+
const selectedVariants = resolveSynthesisSelectedVariants(
|
|
3022
|
+
results,
|
|
3023
|
+
mergedResult?.inputVariants
|
|
3024
|
+
);
|
|
3025
|
+
const artifact = {
|
|
3026
|
+
taskId,
|
|
3027
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3028
|
+
selectedVariants: selectedVariants.map((variant) => ({
|
|
3029
|
+
provider: variant.provider,
|
|
3030
|
+
variant: variant.variant ?? variant.strategy
|
|
3031
|
+
})),
|
|
3032
|
+
declaredWinner: winner ? {
|
|
3033
|
+
provider: winner.provider,
|
|
3034
|
+
variant: winner.variant ?? winner.strategy
|
|
3035
|
+
} : void 0,
|
|
3036
|
+
synthesisStrategy: this.synthesisStrategy,
|
|
3037
|
+
synthesisModel: this.synthesisModel,
|
|
3038
|
+
synthesisOutcomeStatus: resolveSynthesisOutcomeStatusForArtifact(mergedResult),
|
|
3039
|
+
success: didSynthesisArtifactSucceed({ mergedResult }),
|
|
3040
|
+
mergeGenerated: mergedResult?.mergeGenerated ?? false,
|
|
3041
|
+
complementaryPatchApplied: mergedResult?.complementaryPatchApplied ?? false,
|
|
3042
|
+
deliverySucceeded: mergedResult?.deliverySucceeded ?? false,
|
|
3043
|
+
synthesisApplied: mergedResult?.synthesisApplied ?? false,
|
|
3044
|
+
winnerFallback: mergedResult?.winnerFallback ?? false,
|
|
3045
|
+
fallbackReason: mergedResult?.fallbackReason,
|
|
3046
|
+
patchApplyValidated: mergedResult?.patchApplyValidated ?? false,
|
|
3047
|
+
gatesPassed: mergedResult?.gatesPassed ?? false,
|
|
3048
|
+
validationScope: mergedResult?.validationScope,
|
|
3049
|
+
gateFailureDetails: mergedResult?.gateFailureDetails,
|
|
3050
|
+
synthesisWorktree: mergedResult?.synthesisWorktreePath ? {
|
|
3051
|
+
scope: "session-local",
|
|
3052
|
+
name: pathMod.basename(mergedResult.synthesisWorktreePath)
|
|
3053
|
+
} : void 0,
|
|
3054
|
+
synthesisBaseRef: mergedResult?.synthesisBaseRef,
|
|
3055
|
+
synthesisBranch: mergedResult?.synthesisBranch,
|
|
3056
|
+
applyMode: mergedResult?.applyMode,
|
|
3057
|
+
applyError: mergedResult?.applyError,
|
|
3058
|
+
aiGuidedFallbackAttempted: mergedResult?.aiGuidedFallbackAttempted ?? false,
|
|
3059
|
+
aiGuidedFallbackOutcome: mergedResult?.aiGuidedFallbackOutcome,
|
|
3060
|
+
winnerClone: mergedResult?.winnerClone,
|
|
3061
|
+
noveltyScore: mergedResult?.noveltyScore,
|
|
3062
|
+
mergedVariants: mergedResult?.mergedVariants ?? [],
|
|
3063
|
+
contributingVariants: mergedResult?.contributingVariants,
|
|
3064
|
+
synthesisIntents: mergedResult?.synthesisIntents,
|
|
3065
|
+
donorProfiles: mergedResult?.donorProfiles,
|
|
3066
|
+
donorSelectionDecisions: mergedResult?.donorSelectionDecisions,
|
|
3067
|
+
intentMaterialSummaries: mergedResult?.intentMaterialSummaries,
|
|
3068
|
+
donorMaterialDiagnostic: mergedResult?.donorMaterialDiagnostic,
|
|
3069
|
+
harvestManifest: mergedResult?.harvestManifest,
|
|
3070
|
+
winnerFirstReview: mergedResult?.winnerFirstReview,
|
|
3071
|
+
intentFilteredHunkCount: mergedResult?.intentFilteredHunkCount,
|
|
3072
|
+
excludedHunkCount: mergedResult?.excludedHunkCount,
|
|
3073
|
+
unknownIncludedHunkCount: mergedResult?.unknownIncludedHunkCount,
|
|
3074
|
+
unknownExcludedHunkCount: mergedResult?.unknownExcludedHunkCount,
|
|
3075
|
+
overlapRejectedHunkCount: mergedResult?.overlapRejectedHunkCount,
|
|
3076
|
+
reasoning: mergedResult?.reasoning,
|
|
3077
|
+
error: mergedResult?.error,
|
|
3078
|
+
synthesisOutputLines: code != null ? code.split("\n").length : void 0
|
|
3079
|
+
};
|
|
3080
|
+
await fs2.writeFile(
|
|
3081
|
+
pathMod.join(dir, `${taskId}.json`),
|
|
3082
|
+
JSON.stringify(sanitizeSynthesisArtifactForPersistence(artifact), null, 2),
|
|
3083
|
+
"utf-8"
|
|
3084
|
+
);
|
|
3085
|
+
if (this.verbose) {
|
|
3086
|
+
console.log(
|
|
3087
|
+
chalk3.gray(` Synthesis artifact saved to .poetic/telemetry/synthesis/${taskId}.json`)
|
|
3088
|
+
);
|
|
3089
|
+
}
|
|
3090
|
+
} catch {
|
|
3091
|
+
}
|
|
3092
|
+
if (this.synthesisStrategy === "agentic-synthesis" && mergedResult?.synthesisApplied === true && mergedResult.synthesisWorktreePath) {
|
|
3093
|
+
await cleanupAgenticSynthesisWorktree(
|
|
3094
|
+
mergedResult.synthesisWorktreePath,
|
|
3095
|
+
this.preserveWorktrees
|
|
3096
|
+
);
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
await this.cleanupIfEnabled(results, winner, taskId);
|
|
3100
|
+
return {
|
|
3101
|
+
taskId,
|
|
3102
|
+
variants: results,
|
|
3103
|
+
winner,
|
|
3104
|
+
synthesis: mergedResult,
|
|
3105
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3106
|
+
};
|
|
3107
|
+
}
|
|
3108
|
+
/**
|
|
3109
|
+
* DAG-aware variant execution with dependency resolution and resource management
|
|
3110
|
+
*
|
|
3111
|
+
* Algorithm:
|
|
3112
|
+
* 1. Build dependency graph from task dependencies (or infer from variants)
|
|
3113
|
+
* 2. Validate graph structure (detect cycles, missing nodes)
|
|
3114
|
+
* 3. Execute variants in topological order:
|
|
3115
|
+
* - Cloud variants: Submit async, mark completed immediately (non-blocking)
|
|
3116
|
+
* - Local variants: Acquire resource slot, execute, release slot
|
|
3117
|
+
* - Gemini variants: Parallel by default (HOME isolation), auto-fallback on exit 54
|
|
3118
|
+
* 4. Mark tasks completed/failed, unblock dependents
|
|
3119
|
+
* 5. Continue until all tasks terminal or blocked
|
|
3120
|
+
*
|
|
3121
|
+
* @param taskId - Unique task identifier
|
|
3122
|
+
* @param prompt - Task prompt
|
|
3123
|
+
* @param variants - Variant configurations
|
|
3124
|
+
* @param results - Results array (mutated in place)
|
|
3125
|
+
* @returns Competition result
|
|
3126
|
+
*/
|
|
3127
|
+
async executeVariantsWithDependencies(taskId, prompt, variants, results, retryMeta, outerSignal) {
|
|
3128
|
+
if (!this.dependencyResolver || !this.resourceManager) {
|
|
3129
|
+
throw new Error("Dependency components not initialized (enable via flywheel)");
|
|
3130
|
+
}
|
|
3131
|
+
const taskDeps = this.taskDependencies || variants.map((v) => ({
|
|
3132
|
+
id: this.getVariantId(v),
|
|
3133
|
+
dependencies: [],
|
|
3134
|
+
// No dependencies if not specified (all parallel)
|
|
3135
|
+
metadata: { variant: v }
|
|
3136
|
+
}));
|
|
3137
|
+
const graph = this.dependencyResolver.buildGraph(taskDeps);
|
|
3138
|
+
const validation = this.dependencyResolver.validateGraph(graph);
|
|
3139
|
+
if (!validation.valid) {
|
|
3140
|
+
throw new Error(`Invalid dependency graph: ${validation.errors.join(", ")}`);
|
|
3141
|
+
}
|
|
3142
|
+
if (validation.warnings && validation.warnings.length > 0) {
|
|
3143
|
+
console.warn(chalk3.yellow(`Dependency warnings: ${validation.warnings.join(", ")}`));
|
|
3144
|
+
}
|
|
3145
|
+
const abortController = new AbortController();
|
|
3146
|
+
const signal = abortController.signal;
|
|
3147
|
+
if (outerSignal) {
|
|
3148
|
+
if (outerSignal.aborted) {
|
|
3149
|
+
abortController.abort(outerSignal.reason);
|
|
3150
|
+
} else {
|
|
3151
|
+
outerSignal.addEventListener("abort", () => abortController.abort(outerSignal.reason), {
|
|
3152
|
+
once: true
|
|
3153
|
+
});
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
const variantMap = /* @__PURE__ */ new Map();
|
|
3157
|
+
for (const variant of variants) {
|
|
3158
|
+
variantMap.set(this.getVariantId(variant), variant);
|
|
3159
|
+
}
|
|
3160
|
+
const localVariants = variants;
|
|
3161
|
+
const geminiVariants = localVariants.filter((v) => v.provider === "gemini");
|
|
3162
|
+
const nonGeminiLocalVariants = localVariants.filter((v) => v.provider !== "gemini");
|
|
3163
|
+
console.log(chalk3.gray("DAG Execution Plan:"));
|
|
3164
|
+
console.log(chalk3.gray(` Total tasks: ${graph.nodes.size}`));
|
|
3165
|
+
console.log(
|
|
3166
|
+
chalk3.gray(` Max depth: ${this.dependencyResolver.getMaxLevel(graph) + 1} levels`)
|
|
3167
|
+
);
|
|
3168
|
+
console.log(
|
|
3169
|
+
chalk3.gray(` Local variants: ${nonGeminiLocalVariants.length} (resource-managed)`)
|
|
3170
|
+
);
|
|
3171
|
+
console.log(
|
|
3172
|
+
chalk3.gray(` Gemini variants: ${geminiVariants.length} (parallel + auto-fallback)
|
|
3173
|
+
`)
|
|
3174
|
+
);
|
|
3175
|
+
while (!this.dependencyResolver.isComplete(graph)) {
|
|
3176
|
+
const readyTasks = this.dependencyResolver.getReadyTasks(graph);
|
|
3177
|
+
if (readyTasks.length === 0) {
|
|
3178
|
+
const stats2 = this.dependencyResolver.getStatistics(graph);
|
|
3179
|
+
if (stats2.blockedTasks > 0) {
|
|
3180
|
+
console.warn(
|
|
3181
|
+
chalk3.yellow(`${stats2.blockedTasks} task(s) blocked due to dependency failures`)
|
|
3182
|
+
);
|
|
3183
|
+
}
|
|
3184
|
+
break;
|
|
3185
|
+
}
|
|
3186
|
+
console.log(chalk3.cyan(`\u2192 Ready tasks: ${readyTasks.length} (${readyTasks.join(", ")})`));
|
|
3187
|
+
const executionPromises = [];
|
|
3188
|
+
for (const variantId of readyTasks) {
|
|
3189
|
+
const variant = variantMap.get(variantId);
|
|
3190
|
+
if (!variant) {
|
|
3191
|
+
console.warn(chalk3.yellow(`Variant not found for task ${variantId}`));
|
|
3192
|
+
this.dependencyResolver.markFailed(graph, variantId, "Variant not found");
|
|
3193
|
+
continue;
|
|
3194
|
+
}
|
|
3195
|
+
this.dependencyResolver.markRunning(graph, variantId);
|
|
3196
|
+
const executionPromise = this.executeVariantWithResources(
|
|
3197
|
+
taskId,
|
|
3198
|
+
prompt,
|
|
3199
|
+
variant,
|
|
3200
|
+
variantId,
|
|
3201
|
+
signal,
|
|
3202
|
+
retryMeta
|
|
3203
|
+
).then((result) => {
|
|
3204
|
+
results.push(result);
|
|
3205
|
+
if (result.success) {
|
|
3206
|
+
if (this.dependencyResolver) {
|
|
3207
|
+
const newlyReady = this.dependencyResolver.markCompleted(graph, variantId);
|
|
3208
|
+
if (newlyReady.length > 0 && this.verbose) {
|
|
3209
|
+
console.log(chalk3.gray(` Unblocked: ${newlyReady.join(", ")}`));
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
} else {
|
|
3213
|
+
if (this.dependencyResolver) {
|
|
3214
|
+
const blocked = this.dependencyResolver.markFailed(
|
|
3215
|
+
graph,
|
|
3216
|
+
variantId,
|
|
3217
|
+
result.error?.message
|
|
3218
|
+
);
|
|
3219
|
+
if (blocked.length > 0) {
|
|
3220
|
+
console.warn(chalk3.yellow(` Blocked dependents: ${blocked.join(", ")}`));
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
try {
|
|
3225
|
+
updateEvaluationHandleWithResult(taskId, result);
|
|
3226
|
+
} catch (handleError) {
|
|
3227
|
+
console.warn(
|
|
3228
|
+
`Failed to update competition handle: ${handleError instanceof Error ? handleError.message : String(handleError)}`
|
|
3229
|
+
);
|
|
3230
|
+
debugLog("Full stack:", handleError);
|
|
3231
|
+
}
|
|
3232
|
+
}).catch((error) => {
|
|
3233
|
+
console.error(
|
|
3234
|
+
chalk3.red(
|
|
3235
|
+
`Execution error for ${variantId}: ${error instanceof Error ? error.message : String(error)}`
|
|
3236
|
+
)
|
|
3237
|
+
);
|
|
3238
|
+
if (this.dependencyResolver) {
|
|
3239
|
+
this.dependencyResolver.markFailed(graph, variantId, String(error));
|
|
3240
|
+
}
|
|
3241
|
+
});
|
|
3242
|
+
executionPromises.push(executionPromise);
|
|
3243
|
+
}
|
|
3244
|
+
await Promise.allSettled(executionPromises);
|
|
3245
|
+
if (outerSignal?.aborted) {
|
|
3246
|
+
throw outerSignal.reason instanceof Error ? outerSignal.reason : new Error("Dependency-aware variant execution aborted");
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
const stats = this.dependencyResolver.getStatistics(graph);
|
|
3250
|
+
console.log(chalk3.blue("\nDAG Execution Summary:"));
|
|
3251
|
+
console.log(chalk3.gray(` Completed: ${stats.completedTasks}/${stats.totalTasks}`));
|
|
3252
|
+
console.log(chalk3.gray(` Failed: ${stats.failedTasks}`));
|
|
3253
|
+
console.log(chalk3.gray(` Blocked: ${stats.blockedTasks}
|
|
3254
|
+
`));
|
|
3255
|
+
const state = this.getVariantState(results);
|
|
3256
|
+
this.logVariantApplicationWarnings(results);
|
|
3257
|
+
console.log(chalk3.bold.blue("\nCompetition Results\n"));
|
|
3258
|
+
if (outerSignal?.aborted) {
|
|
3259
|
+
throw outerSignal.reason instanceof Error ? outerSignal.reason : new Error("Variant execution aborted before judge selection");
|
|
3260
|
+
}
|
|
3261
|
+
const winner = this.shouldRunWinnerSelection(state) ? await this.variantOrchestrator.selectWinner(results, {
|
|
3262
|
+
useAIJudge: this.aiJudge,
|
|
3263
|
+
originalPrompt: prompt,
|
|
3264
|
+
model: this.aiJudgeModel,
|
|
3265
|
+
aiJudgeVerbose: this.aiJudgeVerbose,
|
|
3266
|
+
aiJudgeShowRationale: this.aiJudgeShowRationale,
|
|
3267
|
+
aiJudgeCriteriaWeights: this.aiJudgeCriteriaWeights,
|
|
3268
|
+
aiJudgeMinScore: this.aiJudgeMinScore,
|
|
3269
|
+
aiJudgeConfidenceThreshold: this.aiJudgeConfidenceThreshold,
|
|
3270
|
+
aiJudgeStreaming: this.aiJudgeStreaming,
|
|
3271
|
+
aiJudgeRun: this.aiJudgeRun,
|
|
3272
|
+
aiJudgePrimary: this.aiJudgePrimary,
|
|
3273
|
+
judgeModels: this.judgeModels,
|
|
3274
|
+
judgeModel: this.judgeModel,
|
|
3275
|
+
judgeAllowUnsafeModel: this.judgeAllowUnsafeModel,
|
|
3276
|
+
judgeStrict: this.judgeStrict,
|
|
3277
|
+
anonymizeJudge: this.anonymizeJudge,
|
|
3278
|
+
aiJudgeVerbosity: this.aiJudgeVerbosity,
|
|
3279
|
+
verificationMode: this.verificationMode,
|
|
3280
|
+
resultStreamer: this.resultStreamer,
|
|
3281
|
+
taskId,
|
|
3282
|
+
competitionMode: true,
|
|
3283
|
+
aiJudgeTelemetry: this.aiJudgeTelemetry
|
|
3284
|
+
}) : null;
|
|
3285
|
+
this.displayResults(results, winner);
|
|
3286
|
+
if (winner?.judgeEvaluation) {
|
|
3287
|
+
const variantResultsMap = new Map(results.map((r) => [r.variant, r]));
|
|
3288
|
+
this.judgeDisplayService.displayEvaluation(winner.judgeEvaluation, variantResultsMap);
|
|
3289
|
+
}
|
|
3290
|
+
await this.cleanupIfEnabled(results, winner, taskId);
|
|
3291
|
+
return {
|
|
3292
|
+
taskId,
|
|
3293
|
+
variants: results,
|
|
3294
|
+
winner,
|
|
3295
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3296
|
+
};
|
|
3297
|
+
}
|
|
3298
|
+
/**
|
|
3299
|
+
* Execute a single variant with resource management
|
|
3300
|
+
*
|
|
3301
|
+
* Handles three execution modes:
|
|
3302
|
+
* 1. Cloud: Submit async, return immediately (non-blocking)
|
|
3303
|
+
* 2. Local (non-Gemini): Acquire resource slot, execute, release
|
|
3304
|
+
* 3. Gemini: Parallel by default with auto-fallback to serial on exit 54
|
|
3305
|
+
*
|
|
3306
|
+
* @param taskId - Task ID
|
|
3307
|
+
* @param prompt - Task prompt
|
|
3308
|
+
* @param variant - Variant configuration
|
|
3309
|
+
* @param variantId - Unique variant identifier
|
|
3310
|
+
* @param signal - Abort signal for cancellation
|
|
3311
|
+
* @returns Variant result
|
|
3312
|
+
*/
|
|
3313
|
+
async executeVariantWithResources(taskId, prompt, variant, _variantId, signal, retryMeta) {
|
|
3314
|
+
const variantLabel = `${variant.provider} ${variant.model || "default-model"} ${variant.strategy}`;
|
|
3315
|
+
const startTime = Date.now();
|
|
3316
|
+
this.logVariantStartSafe(taskId, variant);
|
|
3317
|
+
try {
|
|
3318
|
+
let result;
|
|
3319
|
+
console.log(
|
|
3320
|
+
chalk3.cyan(`\u2192 Starting: ${variantLabel} ${chalk3.gray("(local, resource-managed)")}`)
|
|
3321
|
+
);
|
|
3322
|
+
if (!this.resourceManager) {
|
|
3323
|
+
throw new Error("Resource manager not initialized");
|
|
3324
|
+
}
|
|
3325
|
+
const handle = await this.resourceManager.acquire(
|
|
3326
|
+
variant.provider,
|
|
3327
|
+
signal,
|
|
3328
|
+
this.orchestratorTimeoutMs
|
|
3329
|
+
);
|
|
3330
|
+
try {
|
|
3331
|
+
result = await this.executeLocalVariant(taskId, variant, prompt, retryMeta, signal);
|
|
3332
|
+
} finally {
|
|
3333
|
+
await handle.release();
|
|
3334
|
+
}
|
|
3335
|
+
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
3336
|
+
if (result.success) {
|
|
3337
|
+
console.log(
|
|
3338
|
+
chalk3.green(
|
|
3339
|
+
`${variantLabel}: Completed in ${elapsed}s ${result.prNumber ? `(PR #${result.prNumber})` : ""}
|
|
3340
|
+
`
|
|
3341
|
+
)
|
|
3342
|
+
);
|
|
3343
|
+
} else {
|
|
3344
|
+
console.log(
|
|
3345
|
+
chalk3.red(
|
|
3346
|
+
`${variantLabel}: Failed after ${elapsed}s - ${result.error?.message || "Unknown error"}
|
|
3347
|
+
`
|
|
3348
|
+
)
|
|
3349
|
+
);
|
|
3350
|
+
}
|
|
3351
|
+
this.logVariantCompleteSafe(taskId, variant, result);
|
|
3352
|
+
return result;
|
|
3353
|
+
} catch (error) {
|
|
3354
|
+
const elapsedSeconds = (Date.now() - startTime) / 1e3;
|
|
3355
|
+
const elapsed = elapsedSeconds.toFixed(1);
|
|
3356
|
+
console.log(
|
|
3357
|
+
chalk3.red(
|
|
3358
|
+
`${variantLabel}: Error after ${elapsed}s - ${error instanceof Error ? error.message : String(error)}
|
|
3359
|
+
`
|
|
3360
|
+
)
|
|
3361
|
+
);
|
|
3362
|
+
const failureResult = this.buildFailureResult(variant, error, elapsedSeconds);
|
|
3363
|
+
this.logVariantCompleteSafe(taskId, variant, failureResult);
|
|
3364
|
+
return failureResult;
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
/**
|
|
3368
|
+
* Get unique variant identifier for DAG tracking
|
|
3369
|
+
*
|
|
3370
|
+
* @param variant - Variant configuration
|
|
3371
|
+
* @returns Unique variant ID
|
|
3372
|
+
*/
|
|
3373
|
+
getVariantId(variant) {
|
|
3374
|
+
return `${variant.provider}:${variant.model || "default"}:${variant.strategy}:${variant.executionMode}`;
|
|
3375
|
+
}
|
|
3376
|
+
/**
|
|
3377
|
+
* Log cleanup telemetry event (non-blocking, best-effort)
|
|
3378
|
+
*/
|
|
3379
|
+
logCleanupTelemetry(event) {
|
|
3380
|
+
try {
|
|
3381
|
+
console.log(
|
|
3382
|
+
JSON.stringify({
|
|
3383
|
+
eventType: event.eventType,
|
|
3384
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3385
|
+
taskId: event.taskId,
|
|
3386
|
+
...event.data
|
|
3387
|
+
})
|
|
3388
|
+
);
|
|
3389
|
+
} catch (telemetryError) {
|
|
3390
|
+
console.warn(
|
|
3391
|
+
`Failed to log cleanup telemetry event ${event.eventType}: ${telemetryError instanceof Error ? telemetryError.message : String(telemetryError)}`
|
|
3392
|
+
);
|
|
3393
|
+
debugLog("Full stack:", telemetryError);
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
/**
|
|
3397
|
+
* Best-effort cleanup trigger that runs when enabled and a winner exists.
|
|
3398
|
+
* Used from both the normal completion path and the timeout partial-results path.
|
|
3399
|
+
*
|
|
3400
|
+
* ENHANCED: Added robust error handling, retry logic, and detailed logging
|
|
3401
|
+
* to ensure cleanup triggers consistently even under error conditions.
|
|
3402
|
+
*/
|
|
3403
|
+
async cleanupIfEnabled(results, winner, taskId, context) {
|
|
3404
|
+
const cleanupStartTime = Date.now();
|
|
3405
|
+
if (!this.cleanupOnCompletion) {
|
|
3406
|
+
this.logCleanupTelemetry({
|
|
3407
|
+
eventType: "cleanup.disabled",
|
|
3408
|
+
taskId,
|
|
3409
|
+
data: { reason: "configuration" }
|
|
3410
|
+
});
|
|
3411
|
+
if (this.verbose) {
|
|
3412
|
+
console.log(chalk3.gray("Cleanup disabled by configuration"));
|
|
3413
|
+
}
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
if (!winner) {
|
|
3417
|
+
this.logCleanupTelemetry({
|
|
3418
|
+
eventType: "cleanup.skipped",
|
|
3419
|
+
taskId,
|
|
3420
|
+
data: { reason: context?.reason ?? "no_winner" }
|
|
3421
|
+
});
|
|
3422
|
+
if (this.verbose) {
|
|
3423
|
+
console.log(chalk3.yellow("Cleanup skipped - no winner determined"));
|
|
3424
|
+
}
|
|
3425
|
+
return;
|
|
3426
|
+
}
|
|
3427
|
+
if (!results || results.length === 0) {
|
|
3428
|
+
this.logCleanupTelemetry({
|
|
3429
|
+
eventType: "cleanup.skipped",
|
|
3430
|
+
taskId,
|
|
3431
|
+
data: { reason: "no_results" }
|
|
3432
|
+
});
|
|
3433
|
+
if (this.verbose) {
|
|
3434
|
+
console.log(chalk3.yellow("Cleanup skipped - no results available"));
|
|
3435
|
+
}
|
|
3436
|
+
return;
|
|
3437
|
+
}
|
|
3438
|
+
const losersWithPRs = results.filter((r) => {
|
|
3439
|
+
const hasPrNumber = typeof r.prNumber === "number" && r.prNumber > 0;
|
|
3440
|
+
return hasPrNumber && (winner.prNumber ? r.prNumber !== winner.prNumber : r.variant !== winner.variant || r.provider !== winner.provider);
|
|
3441
|
+
});
|
|
3442
|
+
const hasLosers = losersWithPRs.length > 0;
|
|
3443
|
+
if (!hasLosers) {
|
|
3444
|
+
if (this.verbose) {
|
|
3445
|
+
console.log(chalk3.gray("No losing PRs detected, checking for orphan branches by task..."));
|
|
3446
|
+
}
|
|
3447
|
+
if (taskId && results.length > 1) {
|
|
3448
|
+
try {
|
|
3449
|
+
const orphanScanStart = Date.now();
|
|
3450
|
+
await this.cleanupService.cleanupCompetition(results, winner, {
|
|
3451
|
+
verbose: this.verbose,
|
|
3452
|
+
preserveSubmittedOnly: this.shouldPreserveSubmittedOnly(),
|
|
3453
|
+
taskId
|
|
3454
|
+
});
|
|
3455
|
+
const orphanCount = await this.cleanupOrphanBranchesByTask(taskId, winner);
|
|
3456
|
+
this.logCleanupTelemetry({
|
|
3457
|
+
eventType: "cleanup.orphan_scan",
|
|
3458
|
+
taskId,
|
|
3459
|
+
data: {
|
|
3460
|
+
branchesDeleted: orphanCount,
|
|
3461
|
+
durationMs: Date.now() - orphanScanStart
|
|
3462
|
+
}
|
|
3463
|
+
});
|
|
3464
|
+
if (orphanCount > 0) {
|
|
3465
|
+
console.log(chalk3.green(`Removed ${orphanCount} orphan branch(es) for task ${taskId}`));
|
|
3466
|
+
} else if (this.verbose) {
|
|
3467
|
+
console.log(chalk3.gray(`No orphan branches found for task ${taskId}`));
|
|
3468
|
+
}
|
|
3469
|
+
} catch (orphanError) {
|
|
3470
|
+
if (this.verbose) {
|
|
3471
|
+
console.log(
|
|
3472
|
+
chalk3.yellow(
|
|
3473
|
+
`Failed scanning orphan branches: ${orphanError instanceof Error ? orphanError.message : String(orphanError)}`
|
|
3474
|
+
)
|
|
3475
|
+
);
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
} else if (results.length === 1) {
|
|
3479
|
+
if (this.verbose) {
|
|
3480
|
+
console.log(
|
|
3481
|
+
chalk3.gray("Skipping orphan branch scan - single variant (no orphans possible)")
|
|
3482
|
+
);
|
|
3483
|
+
}
|
|
3484
|
+
} else if (!taskId && this.verbose) {
|
|
3485
|
+
console.log(chalk3.gray("Skipping orphan branch scan - taskId not provided"));
|
|
3486
|
+
}
|
|
3487
|
+
return;
|
|
3488
|
+
}
|
|
3489
|
+
this.logCleanupTelemetry({
|
|
3490
|
+
eventType: "cleanup.started",
|
|
3491
|
+
taskId,
|
|
3492
|
+
data: {
|
|
3493
|
+
loserCount: losersWithPRs.length
|
|
3494
|
+
}
|
|
3495
|
+
});
|
|
3496
|
+
console.log(
|
|
3497
|
+
chalk3.blue(`
|
|
3498
|
+
Starting cleanup of ${losersWithPRs.length} non-winning variant(s)...`)
|
|
3499
|
+
);
|
|
3500
|
+
const maxRetries = 3;
|
|
3501
|
+
let cleanupSuccessful = false;
|
|
3502
|
+
let lastError = null;
|
|
3503
|
+
let lastErrorMessage = null;
|
|
3504
|
+
let cleanupAttempts = 0;
|
|
3505
|
+
try {
|
|
3506
|
+
await retryOperation(
|
|
3507
|
+
async () => {
|
|
3508
|
+
cleanupAttempts += 1;
|
|
3509
|
+
if (this.verbose) {
|
|
3510
|
+
console.log(chalk3.gray(`Cleanup attempt ${cleanupAttempts}/${maxRetries}`));
|
|
3511
|
+
}
|
|
3512
|
+
try {
|
|
3513
|
+
await this.cleanupService.cleanupCompetition(results, winner, {
|
|
3514
|
+
verbose: this.verbose,
|
|
3515
|
+
preserveSubmittedOnly: this.shouldPreserveSubmittedOnly(),
|
|
3516
|
+
taskId
|
|
3517
|
+
});
|
|
3518
|
+
} catch (cleanupError) {
|
|
3519
|
+
lastError = cleanupError instanceof Error ? cleanupError : new Error(String(cleanupError));
|
|
3520
|
+
lastErrorMessage = lastError.message;
|
|
3521
|
+
this.logCleanupTelemetry({
|
|
3522
|
+
eventType: "cleanup.retry_attempt",
|
|
3523
|
+
taskId,
|
|
3524
|
+
data: {
|
|
3525
|
+
attempt: cleanupAttempts,
|
|
3526
|
+
maxRetries,
|
|
3527
|
+
error: lastErrorMessage
|
|
3528
|
+
}
|
|
3529
|
+
});
|
|
3530
|
+
console.log(
|
|
3531
|
+
chalk3.yellow(`Cleanup attempt ${cleanupAttempts} failed: ${lastErrorMessage}`)
|
|
3532
|
+
);
|
|
3533
|
+
throw lastError;
|
|
3534
|
+
}
|
|
3535
|
+
cleanupSuccessful = true;
|
|
3536
|
+
if (this.verbose) {
|
|
3537
|
+
console.log(
|
|
3538
|
+
chalk3.green(`Cleanup completed successfully on attempt ${cleanupAttempts}`)
|
|
3539
|
+
);
|
|
3540
|
+
}
|
|
3541
|
+
},
|
|
3542
|
+
{
|
|
3543
|
+
maxRetries,
|
|
3544
|
+
initialDelayMs: 1e3,
|
|
3545
|
+
backoffMultiplier: 2,
|
|
3546
|
+
maxDelayMs: 5e3,
|
|
3547
|
+
onRetry: (_error, attempt) => {
|
|
3548
|
+
const backoffMs = calculateRetryDelayMs(attempt, {
|
|
3549
|
+
initialDelayMs: 1e3,
|
|
3550
|
+
backoffMultiplier: 2,
|
|
3551
|
+
maxDelayMs: 5e3
|
|
3552
|
+
});
|
|
3553
|
+
console.log(chalk3.gray(` Retrying in ${backoffMs}ms...`));
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
);
|
|
3557
|
+
} catch {
|
|
3558
|
+
}
|
|
3559
|
+
const cleanupDurationMs = Date.now() - cleanupStartTime;
|
|
3560
|
+
this.logCleanupTelemetry({
|
|
3561
|
+
eventType: "cleanup.completed",
|
|
3562
|
+
taskId,
|
|
3563
|
+
data: {
|
|
3564
|
+
success: cleanupSuccessful,
|
|
3565
|
+
totalDurationMs: cleanupDurationMs,
|
|
3566
|
+
retryAttempts: cleanupAttempts || maxRetries
|
|
3567
|
+
}
|
|
3568
|
+
});
|
|
3569
|
+
if (!cleanupSuccessful && lastErrorMessage) {
|
|
3570
|
+
console.log(chalk3.yellow(`
|
|
3571
|
+
Cleanup failed after ${maxRetries} attempts:`));
|
|
3572
|
+
console.log(chalk3.yellow(` ${lastErrorMessage}`));
|
|
3573
|
+
console.log(chalk3.gray("\nManual cleanup may be required for the following variants:"));
|
|
3574
|
+
losersWithPRs.forEach((loser) => {
|
|
3575
|
+
console.log(chalk3.gray(` \u2022 ${loser.provider} ${loser.variant} (PR #${loser.prNumber})`));
|
|
3576
|
+
});
|
|
3577
|
+
console.log(chalk3.gray("\nManual cleanup commands:"));
|
|
3578
|
+
losersWithPRs.forEach((loser) => {
|
|
3579
|
+
console.log(chalk3.gray(` gh pr close ${loser.prNumber}`));
|
|
3580
|
+
if (taskId && loser.provider && loser.strategy && loser.executionMode) {
|
|
3581
|
+
const branch = poeticVariantBranchName({
|
|
3582
|
+
taskId,
|
|
3583
|
+
provider: loser.provider,
|
|
3584
|
+
strategy: loser.strategy,
|
|
3585
|
+
executionMode: loser.executionMode,
|
|
3586
|
+
model: loser.model
|
|
3587
|
+
});
|
|
3588
|
+
console.log(chalk3.gray(` git branch -D ${branch}`));
|
|
3589
|
+
} else {
|
|
3590
|
+
console.log(chalk3.gray(" gh pr view <PR_NUMBER> --json headRefName -q '.headRefName'"));
|
|
3591
|
+
}
|
|
3592
|
+
});
|
|
3593
|
+
}
|
|
3594
|
+
if (taskId) {
|
|
3595
|
+
this.clearBranchListCache(taskId);
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
/**
|
|
3599
|
+
* Calculate variant state from results
|
|
3600
|
+
*/
|
|
3601
|
+
getVariantState(results) {
|
|
3602
|
+
const isSubmittedOnly = (r) => r.submittedOnly === true;
|
|
3603
|
+
const validResults = results.filter((r) => r && r.success !== void 0);
|
|
3604
|
+
return {
|
|
3605
|
+
total: results.length,
|
|
3606
|
+
// Do not count submitted-only variants as completed
|
|
3607
|
+
completed: validResults.filter((r) => !isSubmittedOnly(r)).length,
|
|
3608
|
+
succeeded: validResults.filter((r) => r.success === true && !isSubmittedOnly(r)).length,
|
|
3609
|
+
failed: validResults.filter((r) => r.success === false && !r.timedOut).length,
|
|
3610
|
+
timedOut: validResults.filter((r) => r.timedOut === true).length
|
|
3611
|
+
};
|
|
3612
|
+
}
|
|
3613
|
+
/**
|
|
3614
|
+
* Check whether there is any provider result to send to winner selection.
|
|
3615
|
+
* Completion/success counts are telemetry only; the judges decide among the available outputs.
|
|
3616
|
+
*/
|
|
3617
|
+
shouldRunWinnerSelection(state) {
|
|
3618
|
+
return shouldSelectCompetitionWinner({
|
|
3619
|
+
total: state.total,
|
|
3620
|
+
completed: state.completed,
|
|
3621
|
+
judgeOff: this.judgeOff
|
|
3622
|
+
});
|
|
3623
|
+
}
|
|
3624
|
+
/**
|
|
3625
|
+
* Display results in a formatted table
|
|
3626
|
+
*/
|
|
3627
|
+
displayResults(results, winner) {
|
|
3628
|
+
console.log(chalk3.bold("Results Summary:\n"));
|
|
3629
|
+
const validResults = results.filter((r) => r);
|
|
3630
|
+
validResults.forEach((result) => {
|
|
3631
|
+
const isWinner = !this.judgeOff && winner && result.variant === winner.variant && result.provider === winner.provider;
|
|
3632
|
+
const prefix = isWinner ? chalk3.green("[winner]") : chalk3.gray(" ");
|
|
3633
|
+
const needsApply = this.requiresApplyRecovery(result);
|
|
3634
|
+
const status = result.submittedOnly ? chalk3.cyan("Submitted") : result.success && needsApply ? chalk3.yellow("Apply required") : result.success ? chalk3.green("ok") : chalk3.red("failed");
|
|
3635
|
+
const provider = chalk3.blue(`${result.provider}${result.model ? `:${result.model}` : ""}`);
|
|
3636
|
+
const strategy = chalk3.yellow(result.variant);
|
|
3637
|
+
const mode = chalk3.gray("local");
|
|
3638
|
+
const duration = chalk3.gray(`${result.duration.toFixed(1)}s`);
|
|
3639
|
+
const pr = result.prNumber ? chalk3.gray(`#${result.prNumber}`) : "";
|
|
3640
|
+
console.log(`${prefix} ${status} ${mode} ${provider} ${strategy} - ${duration} ${pr}`);
|
|
3641
|
+
});
|
|
3642
|
+
console.log();
|
|
3643
|
+
let selectionSummary;
|
|
3644
|
+
if (this.judgeOff && results.length === 1 && winner) {
|
|
3645
|
+
selectionSummary = chalk3.bold(
|
|
3646
|
+
`Ungraded result: ${winner.provider}${winner.model ? `:${winner.model}` : ""} ${winner.variant}`
|
|
3647
|
+
);
|
|
3648
|
+
} else if (this.judgeOff) {
|
|
3649
|
+
selectionSummary = chalk3.yellow(
|
|
3650
|
+
"Manual comparison (--judge off): no automatic winner selected."
|
|
3651
|
+
);
|
|
3652
|
+
} else if (winner) {
|
|
3653
|
+
selectionSummary = chalk3.bold.green(
|
|
3654
|
+
`Winner: ${winner.provider}${winner.model ? `:${winner.model}` : ""} ${winner.variant}`
|
|
3655
|
+
);
|
|
3656
|
+
} else {
|
|
3657
|
+
const state = this.getVariantState(results);
|
|
3658
|
+
selectionSummary = chalk3.yellow(
|
|
3659
|
+
state.completed < state.total * 0.5 ? "No winner declared by judges for the available results." : "No winner declared by judges."
|
|
3660
|
+
);
|
|
3661
|
+
}
|
|
3662
|
+
console.log(selectionSummary);
|
|
3663
|
+
if (!this.judgeOff && winner?.prNumber) {
|
|
3664
|
+
console.log(chalk3.gray(` PR: #${winner.prNumber}`));
|
|
3665
|
+
}
|
|
3666
|
+
console.log();
|
|
3667
|
+
const hasRetainedLocalWorktrees = results.some((r) => Boolean(r?.retainedWorktreePath));
|
|
3668
|
+
if (this.localFastMode || !this.pushLocalVariants && hasRetainedLocalWorktrees) {
|
|
3669
|
+
this.displayLocalFastSummary(results);
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
/**
|
|
3673
|
+
* Get comparison and recommendations
|
|
3674
|
+
*/
|
|
3675
|
+
async getComparison(results, resolvedWinner, noWinnerMode) {
|
|
3676
|
+
const comparison = await this.variantOrchestrator.compareVariants(
|
|
3677
|
+
results,
|
|
3678
|
+
resolvedWinner,
|
|
3679
|
+
noWinnerMode
|
|
3680
|
+
);
|
|
3681
|
+
return {
|
|
3682
|
+
...comparison,
|
|
3683
|
+
analysis: comparison.analysis
|
|
3684
|
+
};
|
|
3685
|
+
}
|
|
3686
|
+
/**
|
|
3687
|
+
* Cleanup non-winning variant PRs and branches after competition
|
|
3688
|
+
* Delegates to CleanupService for all cleanup logic
|
|
3689
|
+
*
|
|
3690
|
+
* @deprecated Use CleanupService directly for new code
|
|
3691
|
+
*/
|
|
3692
|
+
async cleanupCompetition(results, winner, taskId) {
|
|
3693
|
+
return this.cleanupService.cleanupCompetition(results, winner, {
|
|
3694
|
+
verbose: this.verbose,
|
|
3695
|
+
preserveSubmittedOnly: this.shouldPreserveSubmittedOnly(),
|
|
3696
|
+
taskId
|
|
3697
|
+
});
|
|
3698
|
+
}
|
|
3699
|
+
requiresApplyRecovery(result) {
|
|
3700
|
+
if (result.submittedOnly) {
|
|
3701
|
+
return true;
|
|
3702
|
+
}
|
|
3703
|
+
return result.applied === false;
|
|
3704
|
+
}
|
|
3705
|
+
isCloudVariant(result) {
|
|
3706
|
+
return result.submittedOnly === true;
|
|
3707
|
+
}
|
|
3708
|
+
formatProviderVariantLabel(result) {
|
|
3709
|
+
const provider = result.provider ? this.capitalizeWords(result.provider) : "Cloud";
|
|
3710
|
+
const variantName = typeof result.variant === "string" && result.variant.length > 0 ? result.variant : typeof result.variant === "string" ? result.variant : "";
|
|
3711
|
+
const variantLabel = variantName ? this.capitalizeWords(String(variantName)) : "";
|
|
3712
|
+
return variantLabel ? `${provider} ${variantLabel}` : provider;
|
|
3713
|
+
}
|
|
3714
|
+
logVariantApplicationWarnings(results) {
|
|
3715
|
+
const applyIssues = results.filter((result) => this.requiresApplyRecovery(result));
|
|
3716
|
+
if (applyIssues.length > 0) {
|
|
3717
|
+
console.log(chalk3.yellow(`${applyIssues.length} variant(s) have local-apply observations:`));
|
|
3718
|
+
applyIssues.forEach((result) => {
|
|
3719
|
+
const label = this.formatProviderVariantLabel(result);
|
|
3720
|
+
const prLabel = result.prNumber ? `PR #${result.prNumber}` : "PR pending";
|
|
3721
|
+
const hint = this.getCloudRecoveryHint(result);
|
|
3722
|
+
console.log(
|
|
3723
|
+
chalk3.gray(
|
|
3724
|
+
` - ${label} (${prLabel}) - local result not applied${hint ? ` | ${hint}` : ""}`
|
|
3725
|
+
)
|
|
3726
|
+
);
|
|
3727
|
+
});
|
|
3728
|
+
}
|
|
3729
|
+
const submittedOnly = results.filter((result) => result.submittedOnly === true);
|
|
3730
|
+
if (submittedOnly.length > 0) {
|
|
3731
|
+
console.log(
|
|
3732
|
+
chalk3.gray(`${submittedOnly.length} variant(s) were submitted without local results.`)
|
|
3733
|
+
);
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
getCloudRecoveryHint(result) {
|
|
3737
|
+
if (!this.isCloudVariant(result) || !this.requiresApplyRecovery(result)) {
|
|
3738
|
+
return void 0;
|
|
3739
|
+
}
|
|
3740
|
+
if (result.provider === "cursor") {
|
|
3741
|
+
if (result.prUrl) {
|
|
3742
|
+
return result.prUrl;
|
|
3743
|
+
}
|
|
3744
|
+
if (result.prNumber) {
|
|
3745
|
+
return `gh pr checkout ${result.prNumber}`;
|
|
3746
|
+
}
|
|
3747
|
+
return "Cursor runs locally; review the commits on the run branch";
|
|
3748
|
+
}
|
|
3749
|
+
return result.prUrl ?? (result.prNumber ? `gh pr view ${result.prNumber}` : void 0);
|
|
3750
|
+
}
|
|
3751
|
+
displayLocalFastSummary(results) {
|
|
3752
|
+
const retained = results.filter((r) => r.retainedWorktreePath);
|
|
3753
|
+
if (retained.length === 0) {
|
|
3754
|
+
console.log(chalk3.gray("No retained local worktrees (clean)"));
|
|
3755
|
+
return;
|
|
3756
|
+
}
|
|
3757
|
+
console.log(chalk3.bold.blue("Local fast-path worktrees:"));
|
|
3758
|
+
retained.forEach((variant) => {
|
|
3759
|
+
const label = this.formatProviderVariantLabel(variant);
|
|
3760
|
+
console.log(
|
|
3761
|
+
chalk3.gray(
|
|
3762
|
+
` \u2022 ${label}: ${variant.retainedWorktreePath}${// Show workspace edits: prefer requested count (includes git-detected), fallback to applied
|
|
3763
|
+
(variant.workspaceEditsRequested ?? variant.workspaceEditsApplied) !== void 0 ? ` (${variant.workspaceEditsRequested ?? variant.workspaceEditsApplied} file${(variant.workspaceEditsRequested ?? variant.workspaceEditsApplied) === 1 ? "" : "s"} changed)` : ""}`
|
|
3764
|
+
)
|
|
3765
|
+
);
|
|
3766
|
+
if (variant.worktreeSummary) {
|
|
3767
|
+
console.log(
|
|
3768
|
+
chalk3.gray(
|
|
3769
|
+
` Diff: ${variant.worktreeSummary.filesChanged} files (+${variant.worktreeSummary.insertions}/-${variant.worktreeSummary.deletions})`
|
|
3770
|
+
)
|
|
3771
|
+
);
|
|
3772
|
+
variant.worktreeSummary.statusLines.slice(0, 5).forEach((line) => {
|
|
3773
|
+
console.log(chalk3.gray(` ${line}`));
|
|
3774
|
+
});
|
|
3775
|
+
if (variant.worktreeSummary.statusLines.length > 5) {
|
|
3776
|
+
console.log(
|
|
3777
|
+
chalk3.gray(` \u2026 ${variant.worktreeSummary.statusLines.length - 5} more file(s)`)
|
|
3778
|
+
);
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
console.log(
|
|
3782
|
+
chalk3.cyan(
|
|
3783
|
+
` Next: cd ${variant.retainedWorktreePath} && git status && git commit -am "<message>"`
|
|
3784
|
+
)
|
|
3785
|
+
);
|
|
3786
|
+
});
|
|
3787
|
+
console.log();
|
|
3788
|
+
}
|
|
3789
|
+
capitalizeWords(value) {
|
|
3790
|
+
return value.split(/[\s_-]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
3791
|
+
}
|
|
3792
|
+
/**
|
|
3793
|
+
* Retrieve cached branch list or fetch fresh from git
|
|
3794
|
+
* Implements 60-second TTL cache to reduce git operations during cleanup
|
|
3795
|
+
* Thread-safe: validates timestamp before use
|
|
3796
|
+
*
|
|
3797
|
+
* @param taskId - Task ID for cache key
|
|
3798
|
+
* @param pattern - Git branch pattern (e.g., "origin/feature/T123-*")
|
|
3799
|
+
* @returns Array of matching branch names
|
|
3800
|
+
*/
|
|
3801
|
+
async getCachedBranchList(taskId, pattern) {
|
|
3802
|
+
const now = Date.now();
|
|
3803
|
+
const cached = this.branchListCache.get(taskId);
|
|
3804
|
+
if (cached && now - cached.timestamp < this.BRANCH_LIST_CACHE_TTL_MS) {
|
|
3805
|
+
if (this.verbose) {
|
|
3806
|
+
console.log(
|
|
3807
|
+
chalk3.gray(
|
|
3808
|
+
` [cache hit] Using cached branch list for task ${taskId} (${Math.round((now - cached.timestamp) / 1e3)}s old)`
|
|
3809
|
+
)
|
|
3810
|
+
);
|
|
3811
|
+
}
|
|
3812
|
+
return cached.branches;
|
|
3813
|
+
}
|
|
3814
|
+
if (this.verbose && cached) {
|
|
3815
|
+
console.log(
|
|
3816
|
+
chalk3.gray(
|
|
3817
|
+
` [cache expired] Refreshing branch list for task ${taskId} (${Math.round((now - cached.timestamp) / 1e3)}s old)`
|
|
3818
|
+
)
|
|
3819
|
+
);
|
|
3820
|
+
}
|
|
3821
|
+
try {
|
|
3822
|
+
const { execa: execa2 } = await import("execa");
|
|
3823
|
+
const { stdout } = await execa2("git", ["branch", "-r", "--list", pattern], {
|
|
3824
|
+
cwd: this.repoRoot,
|
|
3825
|
+
reject: false
|
|
3826
|
+
});
|
|
3827
|
+
const branches = stdout.split("\n").map((b) => b.trim()).filter(Boolean);
|
|
3828
|
+
this.branchListCache.set(taskId, {
|
|
3829
|
+
branches,
|
|
3830
|
+
timestamp: now
|
|
3831
|
+
});
|
|
3832
|
+
if (this.verbose) {
|
|
3833
|
+
console.log(
|
|
3834
|
+
chalk3.gray(` [cache set] Cached ${branches.length} branches for task ${taskId}`)
|
|
3835
|
+
);
|
|
3836
|
+
}
|
|
3837
|
+
return branches;
|
|
3838
|
+
} catch (_error) {
|
|
3839
|
+
if (cached) {
|
|
3840
|
+
if (this.verbose) {
|
|
3841
|
+
console.log(
|
|
3842
|
+
chalk3.yellow(
|
|
3843
|
+
`Git branch list failed for task ${taskId}, using stale cache (${Math.round((now - cached.timestamp) / 1e3)}s old)`
|
|
3844
|
+
)
|
|
3845
|
+
);
|
|
3846
|
+
}
|
|
3847
|
+
return cached.branches;
|
|
3848
|
+
}
|
|
3849
|
+
return [];
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
/**
|
|
3853
|
+
* Clear branch list cache entries (best-effort cleanup)
|
|
3854
|
+
* Can be called during orchestrator shutdown to free memory
|
|
3855
|
+
* Optionally filter by taskId, or clear all entries if taskId is undefined
|
|
3856
|
+
*
|
|
3857
|
+
* @param taskId - Optional task ID to clear specific cache entry, or undefined to clear all
|
|
3858
|
+
*/
|
|
3859
|
+
clearBranchListCache(taskId) {
|
|
3860
|
+
if (taskId) {
|
|
3861
|
+
const hadEntry = this.branchListCache.has(taskId);
|
|
3862
|
+
this.branchListCache.delete(taskId);
|
|
3863
|
+
if (this.verbose && hadEntry) {
|
|
3864
|
+
console.log(chalk3.gray(` [cache cleared] Removed branch list cache for task ${taskId}`));
|
|
3865
|
+
}
|
|
3866
|
+
} else {
|
|
3867
|
+
const size = this.branchListCache.size;
|
|
3868
|
+
this.branchListCache.clear();
|
|
3869
|
+
if (this.verbose && size > 0) {
|
|
3870
|
+
console.log(chalk3.gray(` [cache cleared] Cleared ${size} branch list cache entries`));
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
/**
|
|
3875
|
+
* Best-effort removal of orphan competition branches that may not have associated PR results
|
|
3876
|
+
* Pattern: origin/poetic/var-{taskId}-*
|
|
3877
|
+
* - Deletes remote branches first, then attempts local removal
|
|
3878
|
+
* - Skips winner branch if identifiable
|
|
3879
|
+
*/
|
|
3880
|
+
async cleanupOrphanBranchesByTask(taskId, winner) {
|
|
3881
|
+
try {
|
|
3882
|
+
const { execa: execa2 } = await import("execa");
|
|
3883
|
+
const pattern = `origin/${poeticVariantBranchPrefix(taskId)}*`;
|
|
3884
|
+
const remoteBranches = await this.getCachedBranchList(taskId, pattern);
|
|
3885
|
+
if (remoteBranches.length === 0) {
|
|
3886
|
+
return 0;
|
|
3887
|
+
}
|
|
3888
|
+
let winnerBranch;
|
|
3889
|
+
if (winner?.prNumber) {
|
|
3890
|
+
try {
|
|
3891
|
+
winnerBranch = await this.prManager.getPRBranch(winner.prNumber);
|
|
3892
|
+
} catch (error) {
|
|
3893
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3894
|
+
console.warn(
|
|
3895
|
+
chalk3.yellow(
|
|
3896
|
+
`Skipping orphan branch cleanup for task ${taskId}: failed to resolve winner branch for PR #${winner.prNumber}: ${msg}`
|
|
3897
|
+
)
|
|
3898
|
+
);
|
|
3899
|
+
return 0;
|
|
3900
|
+
}
|
|
3901
|
+
} else {
|
|
3902
|
+
if (this.verbose) {
|
|
3903
|
+
emitWarning(
|
|
3904
|
+
"PR-001",
|
|
3905
|
+
{
|
|
3906
|
+
taskId,
|
|
3907
|
+
subsystem: "orchestrator",
|
|
3908
|
+
executionPhase: "cleanup"
|
|
3909
|
+
},
|
|
3910
|
+
{
|
|
3911
|
+
severity: "INFO" /* INFO */,
|
|
3912
|
+
metadata: {
|
|
3913
|
+
operation: "skipOrphanBranchCleanup",
|
|
3914
|
+
reason: "winner has no PR number, cannot identify winning branch"
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
);
|
|
3918
|
+
}
|
|
3919
|
+
return 0;
|
|
3920
|
+
}
|
|
3921
|
+
let deletedCount = 0;
|
|
3922
|
+
for (const fullRef of remoteBranches) {
|
|
3923
|
+
const branch = fullRef.replace(/^origin\//, "");
|
|
3924
|
+
if (winnerBranch) {
|
|
3925
|
+
const isExactMatch = branch === winnerBranch;
|
|
3926
|
+
if (isExactMatch) {
|
|
3927
|
+
if (this.verbose) {
|
|
3928
|
+
console.log(chalk3.gray(` Skipping winner branch: ${branch}`));
|
|
3929
|
+
}
|
|
3930
|
+
continue;
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
if (isProtectedPipelineBranch(branch)) {
|
|
3934
|
+
continue;
|
|
3935
|
+
}
|
|
3936
|
+
try {
|
|
3937
|
+
await this.prManager.deleteBranch(branch, {
|
|
3938
|
+
deleteLocal: true,
|
|
3939
|
+
deleteRemote: true,
|
|
3940
|
+
force: true,
|
|
3941
|
+
cwd: this.repoRoot
|
|
3942
|
+
});
|
|
3943
|
+
deletedCount++;
|
|
3944
|
+
} catch (_error) {
|
|
3945
|
+
try {
|
|
3946
|
+
const result = await execa2("git", ["push", "origin", "--delete", branch], {
|
|
3947
|
+
cwd: this.repoRoot,
|
|
3948
|
+
reject: false
|
|
3949
|
+
});
|
|
3950
|
+
if (result.exitCode === 0) {
|
|
3951
|
+
deletedCount++;
|
|
3952
|
+
continue;
|
|
3953
|
+
}
|
|
3954
|
+
const message = `${result.stderr ?? ""}
|
|
3955
|
+
${result.stdout ?? ""}`.trim();
|
|
3956
|
+
if (isRemoteBranchMissingError(message)) {
|
|
3957
|
+
deletedCount++;
|
|
3958
|
+
continue;
|
|
3959
|
+
}
|
|
3960
|
+
if (isRetryableRemoteDeleteError(message)) {
|
|
3961
|
+
const expectedRemoteSha = await resolveExpectedRemoteShaForQueuedBranch(branch, {
|
|
3962
|
+
remote: "origin",
|
|
3963
|
+
cwd: this.repoRoot
|
|
3964
|
+
});
|
|
3965
|
+
await enqueueRemoteBranchCleanup(branch, {
|
|
3966
|
+
remote: "origin",
|
|
3967
|
+
reason: "cleanupOrphanBranchesByTask(fallback)",
|
|
3968
|
+
expectedRemoteSha,
|
|
3969
|
+
cwd: this.repoRoot
|
|
3970
|
+
}).catch(() => {
|
|
3971
|
+
});
|
|
3972
|
+
}
|
|
3973
|
+
} catch (error) {
|
|
3974
|
+
console.warn(
|
|
3975
|
+
`Failed to delete remote branch: ${error instanceof Error ? error.message : String(error)}`
|
|
3976
|
+
);
|
|
3977
|
+
debugLog("Full stack:", error);
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
return deletedCount;
|
|
3982
|
+
} catch (_error) {
|
|
3983
|
+
return 0;
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
/**
|
|
3987
|
+
* Determine if prompt optimization should be triggered for these results
|
|
3988
|
+
* Only trigger if we have at least 2 successful variants
|
|
3989
|
+
*/
|
|
3990
|
+
shouldTriggerOptimization(results) {
|
|
3991
|
+
if (!this.enablePromptOptimization) {
|
|
3992
|
+
return false;
|
|
3993
|
+
}
|
|
3994
|
+
const successfulVariants = results.filter((r) => r && r.success === true);
|
|
3995
|
+
return successfulVariants.length >= 2;
|
|
3996
|
+
}
|
|
3997
|
+
/**
|
|
3998
|
+
* Get telemetry logger instance (if available)
|
|
3999
|
+
* Returns undefined if telemetry is not available
|
|
4000
|
+
*/
|
|
4001
|
+
getTelemetryLogger() {
|
|
4002
|
+
try {
|
|
4003
|
+
return TelemetryLogger.getInstance();
|
|
4004
|
+
} catch {
|
|
4005
|
+
return void 0;
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
logVariantStartSafe(taskId, variant) {
|
|
4009
|
+
try {
|
|
4010
|
+
this.variantOrchestrator.logVariantStart(
|
|
4011
|
+
taskId,
|
|
4012
|
+
formatVariantIdentifier(variant),
|
|
4013
|
+
"unversioned",
|
|
4014
|
+
{
|
|
4015
|
+
provider: variant.provider,
|
|
4016
|
+
model: variant.model,
|
|
4017
|
+
executionMode: variant.executionMode,
|
|
4018
|
+
strategy: variant.strategy
|
|
4019
|
+
}
|
|
4020
|
+
);
|
|
4021
|
+
} catch (_error) {
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
logVariantCompleteSafe(taskId, variant, result) {
|
|
4025
|
+
try {
|
|
4026
|
+
this.variantOrchestrator.logVariantComplete(
|
|
4027
|
+
taskId,
|
|
4028
|
+
formatVariantIdentifier(variant),
|
|
4029
|
+
result,
|
|
4030
|
+
result.prNumber,
|
|
4031
|
+
{
|
|
4032
|
+
provider: variant.provider,
|
|
4033
|
+
model: result.model || variant.model,
|
|
4034
|
+
executionMode: variant.executionMode,
|
|
4035
|
+
strategy: variant.strategy
|
|
4036
|
+
}
|
|
4037
|
+
);
|
|
4038
|
+
} catch (_error) {
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
buildFailureResult(variant, error, durationSeconds) {
|
|
4042
|
+
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
4043
|
+
const variantId = `${variant.provider}:${variant.model ?? "default"}:${variant.strategy}:${variant.executionMode ?? "local"}`;
|
|
4044
|
+
return {
|
|
4045
|
+
variant: variantId,
|
|
4046
|
+
provider: variant.provider,
|
|
4047
|
+
model: variant.model,
|
|
4048
|
+
strategy: variant.strategy,
|
|
4049
|
+
executionMode: variant.executionMode,
|
|
4050
|
+
success: false,
|
|
4051
|
+
error: normalizedError,
|
|
4052
|
+
duration: Math.max(0, durationSeconds),
|
|
4053
|
+
completeness: 0,
|
|
4054
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4055
|
+
};
|
|
4056
|
+
}
|
|
4057
|
+
shouldPreserveSubmittedOnly() {
|
|
4058
|
+
return false;
|
|
4059
|
+
}
|
|
4060
|
+
/**
|
|
4061
|
+
* Close resources
|
|
4062
|
+
*/
|
|
4063
|
+
async close() {
|
|
4064
|
+
if (this.verbose) {
|
|
4065
|
+
console.log(chalk3.gray("Closing PRWorkflowOrchestrator..."));
|
|
4066
|
+
}
|
|
4067
|
+
const closers = [];
|
|
4068
|
+
try {
|
|
4069
|
+
if (this.localExecutor && typeof this.localExecutor.close === "function") {
|
|
4070
|
+
closers.push(Promise.resolve(this.localExecutor.close()));
|
|
4071
|
+
}
|
|
4072
|
+
} catch {
|
|
4073
|
+
}
|
|
4074
|
+
if (closers.length > 0) {
|
|
4075
|
+
await Promise.allSettled(closers);
|
|
4076
|
+
}
|
|
4077
|
+
this.variantOrchestrator.close();
|
|
4078
|
+
if (this.judgeListenerCleanup) {
|
|
4079
|
+
try {
|
|
4080
|
+
this.judgeListenerCleanup();
|
|
4081
|
+
} catch {
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
if (this.resultStreamer && this.ownsResultStreamer) {
|
|
4085
|
+
try {
|
|
4086
|
+
this.resultStreamer.close();
|
|
4087
|
+
} catch {
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
if (this.ownsProcessLifecycle) {
|
|
4091
|
+
try {
|
|
4092
|
+
await this.processLifecycle.close();
|
|
4093
|
+
} catch (error) {
|
|
4094
|
+
emitWarning(
|
|
4095
|
+
"PR-001",
|
|
4096
|
+
{
|
|
4097
|
+
taskId: "unknown",
|
|
4098
|
+
subsystem: "orchestrator",
|
|
4099
|
+
executionPhase: "cleanup"
|
|
4100
|
+
},
|
|
4101
|
+
{
|
|
4102
|
+
severity: "WARN" /* WARN */,
|
|
4103
|
+
metadata: {
|
|
4104
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4105
|
+
operation: "processLifecycle.close"
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
);
|
|
4109
|
+
} finally {
|
|
4110
|
+
if (getProcessLifecycleManager() === this.processLifecycle) {
|
|
4111
|
+
setProcessLifecycleManager(void 0);
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
}
|
|
4115
|
+
if (this.verbose) {
|
|
4116
|
+
console.log(chalk3.green("PRWorkflowOrchestrator closed"));
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
};
|
|
4120
|
+
async function executeVariantsLocalFast(taskId, prompt, variants, options = {}) {
|
|
4121
|
+
const prManager = new GitHubPRManager();
|
|
4122
|
+
let costTracker;
|
|
4123
|
+
try {
|
|
4124
|
+
const costCalculator = new UniversalCostCalculator(TelemetryLogger.getInstance());
|
|
4125
|
+
costTracker = new UnifiedCostTracker(costCalculator, TelemetryLogger.getInstance(), {
|
|
4126
|
+
enabled: true
|
|
4127
|
+
});
|
|
4128
|
+
} catch {
|
|
4129
|
+
costTracker = void 0;
|
|
4130
|
+
}
|
|
4131
|
+
const localExecutor = new LocalExecutor({
|
|
4132
|
+
prManager,
|
|
4133
|
+
verbose: options.verbose,
|
|
4134
|
+
costTracker,
|
|
4135
|
+
resultStreamer: options.resultStreamer,
|
|
4136
|
+
preserveWorktrees: options.preserveWorktrees ?? getRuntimeEnv("POETIC_PRESERVE_WORKTREES") === "1"
|
|
4137
|
+
});
|
|
4138
|
+
try {
|
|
4139
|
+
const outputs = [];
|
|
4140
|
+
for (const variant of variants) {
|
|
4141
|
+
if (variant.executionMode !== "local") {
|
|
4142
|
+
throw new Error("Local fast path only supports variants with executionMode=local.");
|
|
4143
|
+
}
|
|
4144
|
+
const result = await localExecutor.executeVariantFastWithRetry(taskId, variant, prompt, {
|
|
4145
|
+
commit: options.commit,
|
|
4146
|
+
cleanup: options.cleanup
|
|
4147
|
+
});
|
|
4148
|
+
outputs.push(result);
|
|
4149
|
+
}
|
|
4150
|
+
return {
|
|
4151
|
+
taskId,
|
|
4152
|
+
variants: outputs,
|
|
4153
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4154
|
+
};
|
|
4155
|
+
} finally {
|
|
4156
|
+
await localExecutor.close();
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
// src/orchestrator/judge-completeness-telemetry.ts
|
|
4161
|
+
function isRecord(value) {
|
|
4162
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4163
|
+
}
|
|
4164
|
+
function toJsonValue(value) {
|
|
4165
|
+
if (value === null) return null;
|
|
4166
|
+
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
4167
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
4168
|
+
if (Array.isArray(value)) {
|
|
4169
|
+
return value.map((item) => toJsonValue(item)).filter((item) => item !== void 0);
|
|
4170
|
+
}
|
|
4171
|
+
if (isRecord(value)) {
|
|
4172
|
+
const record = {};
|
|
4173
|
+
for (const [key, item] of Object.entries(value)) {
|
|
4174
|
+
const jsonValue = toJsonValue(item);
|
|
4175
|
+
if (jsonValue !== void 0) {
|
|
4176
|
+
record[key] = jsonValue;
|
|
4177
|
+
}
|
|
4178
|
+
}
|
|
4179
|
+
return record;
|
|
4180
|
+
}
|
|
4181
|
+
return void 0;
|
|
4182
|
+
}
|
|
4183
|
+
function asJsonArray(value) {
|
|
4184
|
+
const jsonValue = toJsonValue(value);
|
|
4185
|
+
return Array.isArray(jsonValue) && jsonValue.length > 0 ? jsonValue : void 0;
|
|
4186
|
+
}
|
|
4187
|
+
function getStringArray(value) {
|
|
4188
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
4189
|
+
}
|
|
4190
|
+
function collectNegativeSpace(workerPackets) {
|
|
4191
|
+
if (!Array.isArray(workerPackets)) return void 0;
|
|
4192
|
+
const entries = [];
|
|
4193
|
+
for (const packet of workerPackets) {
|
|
4194
|
+
if (!isRecord(packet) || typeof packet.variantId !== "string") continue;
|
|
4195
|
+
const items = getStringArray(packet.negativeSpace);
|
|
4196
|
+
if (items.length > 0) {
|
|
4197
|
+
entries.push({ variant: packet.variantId, items });
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4200
|
+
return entries.length > 0 ? entries : void 0;
|
|
4201
|
+
}
|
|
4202
|
+
function collectEvidenceConcerns(workerEvidenceMatrix) {
|
|
4203
|
+
if (!isRecord(workerEvidenceMatrix)) return void 0;
|
|
4204
|
+
const concerns = [];
|
|
4205
|
+
const criteriaRows = Array.isArray(workerEvidenceMatrix.criteriaRows) ? workerEvidenceMatrix.criteriaRows : [];
|
|
4206
|
+
for (const row of criteriaRows) {
|
|
4207
|
+
if (!isRecord(row) || !isRecord(row.variants)) continue;
|
|
4208
|
+
const item = typeof row.criterion === "string" ? row.criterion : typeof row.criterionKey === "string" ? row.criterionKey : "acceptance criterion";
|
|
4209
|
+
for (const [variant, cell] of Object.entries(row.variants)) {
|
|
4210
|
+
if (!isRecord(cell)) continue;
|
|
4211
|
+
const evidenceStatus = cell.evidenceStatus === "valid" || cell.evidenceStatus === "invalid" || cell.evidenceStatus === "missing" ? cell.evidenceStatus : void 0;
|
|
4212
|
+
const evidenceKind = typeof cell.evidenceKind === "string" ? cell.evidenceKind : void 0;
|
|
4213
|
+
const category = evidenceKind === "unverifiable" || evidenceStatus === "invalid" ? "unverifiable" : evidenceStatus === "missing" || evidenceKind === "missing" ? "missing" : void 0;
|
|
4214
|
+
if (category) {
|
|
4215
|
+
concerns.push({
|
|
4216
|
+
variant,
|
|
4217
|
+
item,
|
|
4218
|
+
category,
|
|
4219
|
+
...evidenceStatus ? { evidenceStatus } : {},
|
|
4220
|
+
...evidenceKind ? { evidenceKind } : {}
|
|
4221
|
+
});
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
const emergentFindings = Array.isArray(workerEvidenceMatrix.emergentFindings) ? workerEvidenceMatrix.emergentFindings : [];
|
|
4226
|
+
for (const finding of emergentFindings) {
|
|
4227
|
+
if (!isRecord(finding) || !isRecord(finding.variants)) continue;
|
|
4228
|
+
const item = typeof finding.finding === "string" ? finding.finding : typeof finding.findingKey === "string" ? finding.findingKey : "emergent finding";
|
|
4229
|
+
for (const [variant, cell] of Object.entries(finding.variants)) {
|
|
4230
|
+
if (!isRecord(cell)) continue;
|
|
4231
|
+
const evidenceStatus = cell.evidenceStatus === "valid" || cell.evidenceStatus === "invalid" || cell.evidenceStatus === "missing" ? cell.evidenceStatus : void 0;
|
|
4232
|
+
const status = typeof cell.status === "string" ? cell.status : void 0;
|
|
4233
|
+
const category = status === "unverifiable" || evidenceStatus === "invalid" ? "unverifiable" : evidenceStatus === "missing" ? "missing" : void 0;
|
|
4234
|
+
if (category) {
|
|
4235
|
+
concerns.push({
|
|
4236
|
+
variant,
|
|
4237
|
+
item,
|
|
4238
|
+
category,
|
|
4239
|
+
...evidenceStatus ? { evidenceStatus } : {}
|
|
4240
|
+
});
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
return concerns.length > 0 ? concerns : void 0;
|
|
4245
|
+
}
|
|
4246
|
+
function buildJudgeCompletenessTelemetryFromEvaluation(judgeEvaluation) {
|
|
4247
|
+
const audit = judgeEvaluation?.metadata?.audit;
|
|
4248
|
+
if (!isRecord(audit)) return void 0;
|
|
4249
|
+
const coverageGaps = asJsonArray(audit.coverageGaps);
|
|
4250
|
+
const negativeSpaceByVariant = collectNegativeSpace(audit.workerPackets);
|
|
4251
|
+
const evidenceConcerns = collectEvidenceConcerns(audit.workerEvidenceMatrix);
|
|
4252
|
+
const workerEvidenceMatrix = toJsonValue(audit.workerEvidenceMatrix);
|
|
4253
|
+
if (!coverageGaps && !negativeSpaceByVariant && !evidenceConcerns && workerEvidenceMatrix === void 0) {
|
|
4254
|
+
return void 0;
|
|
4255
|
+
}
|
|
4256
|
+
return {
|
|
4257
|
+
source: "judge-audit",
|
|
4258
|
+
...coverageGaps ? { coverageGaps } : {},
|
|
4259
|
+
...negativeSpaceByVariant ? { negativeSpaceByVariant } : {},
|
|
4260
|
+
...evidenceConcerns ? { evidenceConcerns } : {},
|
|
4261
|
+
...workerEvidenceMatrix !== void 0 ? { workerEvidenceMatrix } : {}
|
|
4262
|
+
};
|
|
4263
|
+
}
|
|
4264
|
+
function buildJudgeCompletenessTelemetryFromVariants(variants) {
|
|
4265
|
+
for (const variant of variants) {
|
|
4266
|
+
const telemetry = buildJudgeCompletenessTelemetryFromEvaluation(variant.judgeEvaluation);
|
|
4267
|
+
if (telemetry) return telemetry;
|
|
4268
|
+
}
|
|
4269
|
+
return void 0;
|
|
4270
|
+
}
|
|
4271
|
+
|
|
4272
|
+
// src/cli/competition-interruption.ts
|
|
4273
|
+
function shouldStopBeforeJudgeOrSynthesis(isShuttingDown, variants) {
|
|
4274
|
+
return isShuttingDown || variants.some((variant) => variant.terminationReason === "canceled_user");
|
|
4275
|
+
}
|
|
4276
|
+
|
|
4277
|
+
// src/cli/handlers/compete/concurrency-config.ts
|
|
4278
|
+
function asValidConcurrency(value) {
|
|
4279
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
|
|
4280
|
+
}
|
|
4281
|
+
async function loadConfiguredConcurrency(explicitRequest) {
|
|
4282
|
+
if (explicitRequest !== void 0) return {};
|
|
4283
|
+
try {
|
|
4284
|
+
const merged = await new ConfigManager().loadConfig();
|
|
4285
|
+
return {
|
|
4286
|
+
maxConcurrent: asValidConcurrency(merged.config.variants?.maxConcurrent),
|
|
4287
|
+
worktreeMax: asValidConcurrency(merged.config.worktree?.maxConcurrent)
|
|
4288
|
+
};
|
|
4289
|
+
} catch (error) {
|
|
4290
|
+
rethrowConfigValidityError(error);
|
|
4291
|
+
return {};
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
function concurrencyAdvisoryMessage(effectiveMaxConcurrent) {
|
|
4295
|
+
if (typeof effectiveMaxConcurrent !== "number") return void 0;
|
|
4296
|
+
const legSlots = detectHostConcurrencyBudget().legSlots;
|
|
4297
|
+
if (effectiveMaxConcurrent <= legSlots) return void 0;
|
|
4298
|
+
return `Requested concurrency ${effectiveMaxConcurrent} exceeds this host's recommendation of ${legSlots} legs (see poetic doctor); running as asked with validation pacing.`;
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4301
|
+
// src/cli/handlers/compete/research-trace-persistence.ts
|
|
4302
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
4303
|
+
import path2 from "path";
|
|
4304
|
+
function resolveNativeResearchTraceDir(poeticDir, taskId, resolver = LabPathResolver.getInstance()) {
|
|
4305
|
+
const labContext = resolver.getContext();
|
|
4306
|
+
if (labContext) {
|
|
4307
|
+
return path2.join(
|
|
4308
|
+
poeticDir,
|
|
4309
|
+
"labs",
|
|
4310
|
+
labContext.labId,
|
|
4311
|
+
"runs",
|
|
4312
|
+
labContext.runId,
|
|
4313
|
+
"research",
|
|
4314
|
+
taskId
|
|
4315
|
+
);
|
|
4316
|
+
}
|
|
4317
|
+
return path2.join(poeticDir, "research", taskId);
|
|
4318
|
+
}
|
|
4319
|
+
async function persistNativeResearchTraces(input) {
|
|
4320
|
+
const traceDir = input.traceDir ?? resolveNativeResearchTraceDir(
|
|
4321
|
+
input.poeticDir ?? path2.join(process.cwd(), ".poetic"),
|
|
4322
|
+
input.taskId
|
|
4323
|
+
);
|
|
4324
|
+
const cleared = new Set(
|
|
4325
|
+
Array.from(input.clearedProviders, (provider) => provider.trim().toLowerCase())
|
|
4326
|
+
);
|
|
4327
|
+
let traceDirReady = false;
|
|
4328
|
+
let tracesPersisted = 0;
|
|
4329
|
+
const failures = [];
|
|
4330
|
+
for (const variant of input.variants) {
|
|
4331
|
+
if (variant.success !== true) continue;
|
|
4332
|
+
if (!cleared.has(variant.provider.trim().toLowerCase())) continue;
|
|
4333
|
+
const variantKey = formatVariantIdentifierFromResult(variant);
|
|
4334
|
+
const trace = parseCitationTrace({
|
|
4335
|
+
variantId: variantKey,
|
|
4336
|
+
provider: variant.provider,
|
|
4337
|
+
mode: "native",
|
|
4338
|
+
output: variant.result?.stdout ?? variant.output ?? "",
|
|
4339
|
+
corroboratedUrls: variant.result?.researchCorroboratedUrls,
|
|
4340
|
+
observedWebToolEvent: variant.result?.researchObservedWebToolEvent,
|
|
4341
|
+
excludedDomains: input.excludedDomains
|
|
4342
|
+
});
|
|
4343
|
+
variant.researchTrace = trace;
|
|
4344
|
+
try {
|
|
4345
|
+
if (!traceDirReady) {
|
|
4346
|
+
await mkdir(traceDir, { recursive: true });
|
|
4347
|
+
traceDirReady = true;
|
|
4348
|
+
}
|
|
4349
|
+
const tracePath = path2.join(
|
|
4350
|
+
traceDir,
|
|
4351
|
+
`${toVariantSlug(variantKey) || "variant"}-research-trace.json`
|
|
4352
|
+
);
|
|
4353
|
+
trace.artifactPath = tracePath;
|
|
4354
|
+
await writeFile(tracePath, serializeCitationTrace(trace), "utf-8");
|
|
4355
|
+
tracesPersisted++;
|
|
4356
|
+
} catch (error) {
|
|
4357
|
+
failures.push({
|
|
4358
|
+
variantId: variantKey,
|
|
4359
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4360
|
+
});
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
const totalCitations = input.variants.reduce(
|
|
4364
|
+
(sum, variant) => sum + (variant.researchTrace?.citations.length ?? 0),
|
|
4365
|
+
0
|
|
4366
|
+
);
|
|
4367
|
+
const excludedDomainViolations = input.variants.reduce(
|
|
4368
|
+
(sum, variant) => sum + (variant.researchTrace?.citations.filter((citation) => citation.excludedDomainViolation).length ?? 0),
|
|
4369
|
+
0
|
|
4370
|
+
);
|
|
4371
|
+
return { traceDir, totalCitations, excludedDomainViolations, tracesPersisted, failures };
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4374
|
+
export {
|
|
4375
|
+
DependencyGraphResolver,
|
|
4376
|
+
validateVerbosity,
|
|
4377
|
+
PRWorkflowOrchestrator,
|
|
4378
|
+
executeVariantsLocalFast,
|
|
4379
|
+
buildJudgeCompletenessTelemetryFromVariants,
|
|
4380
|
+
shouldStopBeforeJudgeOrSynthesis,
|
|
4381
|
+
loadConfiguredConcurrency,
|
|
4382
|
+
concurrencyAdvisoryMessage,
|
|
4383
|
+
resolveNativeResearchTraceDir,
|
|
4384
|
+
persistNativeResearchTraces
|
|
4385
|
+
};
|
|
4386
|
+
//# sourceMappingURL=chunk-WGXYDLMX.js.map
|