@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,3544 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolvePlanArtifactPath
|
|
3
|
+
} from "./chunk-RJAAKDRX.js";
|
|
4
|
+
import {
|
|
5
|
+
abortPendingSquashMerge,
|
|
6
|
+
acquireFlywheelApplyLock,
|
|
7
|
+
trackWorktreeCleaned
|
|
8
|
+
} from "./chunk-EX3OM35G.js";
|
|
9
|
+
import {
|
|
10
|
+
listFlywheelBranches
|
|
11
|
+
} from "./chunk-HY3ESWCA.js";
|
|
12
|
+
import {
|
|
13
|
+
isSystemWritePath
|
|
14
|
+
} from "./chunk-ZIPWI2LY.js";
|
|
15
|
+
import {
|
|
16
|
+
applyTaskTransitionOutcome,
|
|
17
|
+
completeTask,
|
|
18
|
+
createTask,
|
|
19
|
+
deriveTransitionIdentity,
|
|
20
|
+
getTask
|
|
21
|
+
} from "./chunk-SUZ7UYZH.js";
|
|
22
|
+
import {
|
|
23
|
+
cleanupWorkflowRelatedBranchesAndWorktrees
|
|
24
|
+
} from "./chunk-V6YLL5WA.js";
|
|
25
|
+
import {
|
|
26
|
+
WorktreeManagerPresets
|
|
27
|
+
} from "./chunk-66CIO2SV.js";
|
|
28
|
+
import {
|
|
29
|
+
listTasksInSprint,
|
|
30
|
+
refreshSprintMarkdown
|
|
31
|
+
} from "./chunk-XUSRFFA7.js";
|
|
32
|
+
import {
|
|
33
|
+
getPlanningRoot,
|
|
34
|
+
withDeferredIndexRegeneration
|
|
35
|
+
} from "./chunk-OPYFYTWQ.js";
|
|
36
|
+
import {
|
|
37
|
+
getBucketArchivePathByKind,
|
|
38
|
+
getBucketPathByKind,
|
|
39
|
+
getBuckets
|
|
40
|
+
} from "./chunk-HOYBYAOA.js";
|
|
41
|
+
import {
|
|
42
|
+
getArtifactDir,
|
|
43
|
+
getManifestStatus,
|
|
44
|
+
getProjectRoot,
|
|
45
|
+
getResultPath,
|
|
46
|
+
getTrackedCleanupTargets,
|
|
47
|
+
loadManifest,
|
|
48
|
+
loadTriage,
|
|
49
|
+
updateManifestApplyStatus
|
|
50
|
+
} from "./chunk-CQM3A35X.js";
|
|
51
|
+
import {
|
|
52
|
+
shouldExcludeFile
|
|
53
|
+
} from "./chunk-NS33V3IM.js";
|
|
54
|
+
import {
|
|
55
|
+
buildProjectVerificationGuidance
|
|
56
|
+
} from "./chunk-MCPQNJMK.js";
|
|
57
|
+
import {
|
|
58
|
+
isRecord
|
|
59
|
+
} from "./chunk-S54MKU6V.js";
|
|
60
|
+
import {
|
|
61
|
+
createVerificationCommandEnv
|
|
62
|
+
} from "./chunk-FEQ4HXL7.js";
|
|
63
|
+
import {
|
|
64
|
+
wrapWithSandbox
|
|
65
|
+
} from "./chunk-2AICM4P3.js";
|
|
66
|
+
import {
|
|
67
|
+
isWorktreeRunSentinelPath
|
|
68
|
+
} from "./chunk-I5WRT7ZR.js";
|
|
69
|
+
import {
|
|
70
|
+
pruneGitWorktreeEntries
|
|
71
|
+
} from "./chunk-5EERLVZB.js";
|
|
72
|
+
import {
|
|
73
|
+
assertBranchAllowsCodeApply,
|
|
74
|
+
classifyBranchRole,
|
|
75
|
+
isDisposableBranchRole
|
|
76
|
+
} from "./chunk-XQD2B6BB.js";
|
|
77
|
+
import {
|
|
78
|
+
execGit
|
|
79
|
+
} from "./chunk-KNDYMVWN.js";
|
|
80
|
+
import {
|
|
81
|
+
isAllowedVerifyCommand,
|
|
82
|
+
isBaselineComparableTypeScriptCommand
|
|
83
|
+
} from "./chunk-QU6JQJMH.js";
|
|
84
|
+
import {
|
|
85
|
+
resolveExecutionBoundary
|
|
86
|
+
} from "./chunk-7R2WLDOS.js";
|
|
87
|
+
import {
|
|
88
|
+
detectProjectCapabilities
|
|
89
|
+
} from "./chunk-GNH3QGTL.js";
|
|
90
|
+
import {
|
|
91
|
+
isProjectTestPath
|
|
92
|
+
} from "./chunk-UDMS5AD3.js";
|
|
93
|
+
import {
|
|
94
|
+
isQuietOutputRequested
|
|
95
|
+
} from "./chunk-FKAUPHMR.js";
|
|
96
|
+
import {
|
|
97
|
+
quotePosixShellArg
|
|
98
|
+
} from "./chunk-N5DX4JAK.js";
|
|
99
|
+
import {
|
|
100
|
+
isLabModeFromEnv
|
|
101
|
+
} from "./chunk-PGSPX4SU.js";
|
|
102
|
+
import {
|
|
103
|
+
emitCustomWarning,
|
|
104
|
+
emitWarning
|
|
105
|
+
} from "./chunk-EJGCZGUF.js";
|
|
106
|
+
import {
|
|
107
|
+
POETIC_AUTOMERGE_MODE,
|
|
108
|
+
POETIC_PRESERVE_WORKTREES,
|
|
109
|
+
readEnv
|
|
110
|
+
} from "./chunk-5DLKYTQX.js";
|
|
111
|
+
|
|
112
|
+
// src/planning/task-status-sync.ts
|
|
113
|
+
function emitTaskStatusSyncWarning(message, metadata) {
|
|
114
|
+
emitCustomWarning(
|
|
115
|
+
"WARN" /* WARN */,
|
|
116
|
+
"validation" /* VALIDATION */,
|
|
117
|
+
message,
|
|
118
|
+
{
|
|
119
|
+
taskId: "task-status-sync",
|
|
120
|
+
subsystem: "planning/task-status-sync",
|
|
121
|
+
executionPhase: "execution"
|
|
122
|
+
},
|
|
123
|
+
{ metadata, skipConsole: true }
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
async function calculateSprintProgress(sprintId) {
|
|
127
|
+
try {
|
|
128
|
+
const tasks = await listTasksInSprint(sprintId);
|
|
129
|
+
if (tasks.length === 0) return 0;
|
|
130
|
+
const completedTasks = tasks.filter((t) => t.status === "done");
|
|
131
|
+
return completedTasks.length / tasks.length * 100;
|
|
132
|
+
} catch {
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function formatSyncSummary(result) {
|
|
137
|
+
const lines = [];
|
|
138
|
+
lines.push(`Tasks updated: ${result.tasksUpdated}`);
|
|
139
|
+
lines.push(`Tasks completed: ${result.tasksCompleted}`);
|
|
140
|
+
lines.push(`Tasks already satisfied: ${result.tasksAlreadySatisfied}`);
|
|
141
|
+
lines.push(`Tasks failed: ${result.tasksFailed}`);
|
|
142
|
+
lines.push(`Tasks ephemeral (skipped): ${result.tasksEphemeral}`);
|
|
143
|
+
lines.push(`Sprint progress: ${result.sprintProgress.toFixed(1)}%`);
|
|
144
|
+
if (result.details.some((d) => d.error)) {
|
|
145
|
+
lines.push("");
|
|
146
|
+
lines.push("Errors:");
|
|
147
|
+
for (const detail of result.details) {
|
|
148
|
+
if (detail.error) {
|
|
149
|
+
lines.push(` ${detail.taskId}: ${detail.error}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return lines.join("\n");
|
|
154
|
+
}
|
|
155
|
+
async function applyTaskTransitions(sprintId, transitions, options) {
|
|
156
|
+
const details = [];
|
|
157
|
+
let tasksUpdated = 0;
|
|
158
|
+
let tasksCompleted = 0;
|
|
159
|
+
let tasksAlreadySatisfied = 0;
|
|
160
|
+
let tasksFailed = 0;
|
|
161
|
+
let tasksEphemeral = 0;
|
|
162
|
+
const statusPriority = {
|
|
163
|
+
done: 4,
|
|
164
|
+
review: 3,
|
|
165
|
+
in_progress: 2,
|
|
166
|
+
blocked: 1,
|
|
167
|
+
todo: 0
|
|
168
|
+
};
|
|
169
|
+
function mergeTransitionGroup(group, winner) {
|
|
170
|
+
const resultArtifactPath = winner.resultArtifactPath ?? group.find((t) => t.resultArtifactPath)?.resultArtifactPath;
|
|
171
|
+
const reason = winner.reason ?? group.find((t) => t.reason)?.reason;
|
|
172
|
+
return {
|
|
173
|
+
taskId: winner.taskId,
|
|
174
|
+
targetStatus: winner.targetStatus,
|
|
175
|
+
...resultArtifactPath ? { resultArtifactPath } : {},
|
|
176
|
+
...reason ? { reason } : {}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const deduplicatedTransitions = [];
|
|
180
|
+
const byTaskId = /* @__PURE__ */ new Map();
|
|
181
|
+
for (const t of transitions) {
|
|
182
|
+
const existing = byTaskId.get(t.taskId);
|
|
183
|
+
if (existing) {
|
|
184
|
+
existing.push(t);
|
|
185
|
+
} else {
|
|
186
|
+
byTaskId.set(t.taskId, [t]);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
for (const [, group] of byTaskId) {
|
|
190
|
+
if (group.length === 1) {
|
|
191
|
+
deduplicatedTransitions.push({
|
|
192
|
+
merged: group[0],
|
|
193
|
+
constituents: group
|
|
194
|
+
});
|
|
195
|
+
} else {
|
|
196
|
+
const allSame = group.every((t) => t.targetStatus === group[0]?.targetStatus);
|
|
197
|
+
if (allSame) {
|
|
198
|
+
const winner = group[0];
|
|
199
|
+
deduplicatedTransitions.push({
|
|
200
|
+
merged: mergeTransitionGroup(group, winner),
|
|
201
|
+
constituents: group
|
|
202
|
+
});
|
|
203
|
+
} else {
|
|
204
|
+
const winner = group.reduce((best, t) => {
|
|
205
|
+
const bp = statusPriority[best.targetStatus] ?? 0;
|
|
206
|
+
const tp = statusPriority[t.targetStatus] ?? 0;
|
|
207
|
+
return tp > bp ? t : best;
|
|
208
|
+
});
|
|
209
|
+
const statuses = group.map((t) => t.targetStatus).join(", ");
|
|
210
|
+
emitTaskStatusSyncWarning(
|
|
211
|
+
`[task-status-sync] Conflicting transitions for task ${winner.taskId}: [${statuses}] - keeping "${winner.targetStatus}"`,
|
|
212
|
+
{
|
|
213
|
+
taskId: winner.taskId,
|
|
214
|
+
statuses: group.map((t) => t.targetStatus),
|
|
215
|
+
selectedStatus: winner.targetStatus,
|
|
216
|
+
operation: "deduplicate-transitions"
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
deduplicatedTransitions.push({
|
|
220
|
+
merged: mergeTransitionGroup(group, winner),
|
|
221
|
+
constituents: group
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
let sprintTaskIds = /* @__PURE__ */ new Set();
|
|
227
|
+
let sprintQueryable = true;
|
|
228
|
+
try {
|
|
229
|
+
const sprintTasks = await listTasksInSprint(sprintId);
|
|
230
|
+
sprintTaskIds = new Set(sprintTasks.map((task) => task.id));
|
|
231
|
+
} catch {
|
|
232
|
+
sprintQueryable = false;
|
|
233
|
+
}
|
|
234
|
+
const effectiveStatusOf = (targetStatus) => options?.promoteToDone && targetStatus === "review" ? "done" : targetStatus;
|
|
235
|
+
const routeDoneThroughCompleteTask = (effectiveTargetStatus) => options?.promoteToDone === true && effectiveTargetStatus === "done";
|
|
236
|
+
const ancillaryTargetStatus = (transition, task, effectiveTargetStatus) => {
|
|
237
|
+
if (!routeDoneThroughCompleteTask(effectiveTargetStatus)) {
|
|
238
|
+
return effectiveTargetStatus;
|
|
239
|
+
}
|
|
240
|
+
if (transition.targetStatus === "review") {
|
|
241
|
+
return "review";
|
|
242
|
+
}
|
|
243
|
+
return task.status;
|
|
244
|
+
};
|
|
245
|
+
const pendingCompletions = [];
|
|
246
|
+
let sprintProgress = await withDeferredIndexRegeneration(getPlanningRoot(), async () => {
|
|
247
|
+
for (const prepared of deduplicatedTransitions) {
|
|
248
|
+
const transition = prepared.merged;
|
|
249
|
+
const detail = {
|
|
250
|
+
taskId: transition.taskId,
|
|
251
|
+
previousStatus: "todo",
|
|
252
|
+
updated: false
|
|
253
|
+
};
|
|
254
|
+
try {
|
|
255
|
+
if (!sprintQueryable) {
|
|
256
|
+
detail.error = `Sprint artifact not queryable for ${sprintId}; transition skipped as ephemeral.`;
|
|
257
|
+
detail.ephemeral = true;
|
|
258
|
+
tasksEphemeral++;
|
|
259
|
+
details.push(detail);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (!sprintTaskIds.has(transition.taskId)) {
|
|
263
|
+
if (options?.promoteToDone) {
|
|
264
|
+
try {
|
|
265
|
+
const archivedLookup = await getTask(transition.taskId, { includeArchive: true });
|
|
266
|
+
if (archivedLookup.isArchived) {
|
|
267
|
+
detail.previousStatus = archivedLookup.yaml.status;
|
|
268
|
+
detail.newStatus = "done";
|
|
269
|
+
tasksAlreadySatisfied++;
|
|
270
|
+
details.push(detail);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
} catch {
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
detail.error = `Task ${transition.taskId} is not linked to sprint ${sprintId}; transition skipped as ephemeral.`;
|
|
277
|
+
detail.ephemeral = true;
|
|
278
|
+
tasksEphemeral++;
|
|
279
|
+
details.push(detail);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const { yaml: task, isArchived } = await getTask(transition.taskId, {
|
|
283
|
+
includeArchive: true
|
|
284
|
+
});
|
|
285
|
+
detail.previousStatus = task.status;
|
|
286
|
+
const effectiveTargetStatus = effectiveStatusOf(transition.targetStatus);
|
|
287
|
+
const finalizeViaCompleteTask = routeDoneThroughCompleteTask(effectiveTargetStatus);
|
|
288
|
+
if (finalizeViaCompleteTask && isArchived) {
|
|
289
|
+
detail.newStatus = "done";
|
|
290
|
+
tasksAlreadySatisfied++;
|
|
291
|
+
details.push(detail);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const outcomeTargetStatus = ancillaryTargetStatus(transition, task, effectiveTargetStatus);
|
|
295
|
+
const constituents = prepared.constituents.map((c) => ({
|
|
296
|
+
identity: deriveTransitionIdentity({
|
|
297
|
+
taskId: c.taskId,
|
|
298
|
+
targetStatus: effectiveStatusOf(c.targetStatus),
|
|
299
|
+
hoursLogged: c.hoursLogged,
|
|
300
|
+
resultArtifactPath: c.resultArtifactPath,
|
|
301
|
+
transitionId: c.transitionId
|
|
302
|
+
}),
|
|
303
|
+
hoursLogged: c.hoursLogged
|
|
304
|
+
}));
|
|
305
|
+
const outcome = await applyTaskTransitionOutcome(transition.taskId, {
|
|
306
|
+
targetStatus: outcomeTargetStatus,
|
|
307
|
+
resultArtifactPath: transition.resultArtifactPath,
|
|
308
|
+
reason: transition.reason,
|
|
309
|
+
constituents
|
|
310
|
+
});
|
|
311
|
+
if (outcome.hoursApplied > 0) {
|
|
312
|
+
detail.hoursLogged = outcome.hoursApplied;
|
|
313
|
+
}
|
|
314
|
+
if (finalizeViaCompleteTask) {
|
|
315
|
+
pendingCompletions.push({
|
|
316
|
+
taskId: transition.taskId,
|
|
317
|
+
detail,
|
|
318
|
+
previousStatus: task.status,
|
|
319
|
+
ancillaryFileUpdated: outcome.fileUpdated
|
|
320
|
+
});
|
|
321
|
+
details.push(detail);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
detail.newStatus = effectiveTargetStatus;
|
|
325
|
+
if (outcome.statusChanged) {
|
|
326
|
+
detail.updated = true;
|
|
327
|
+
tasksUpdated++;
|
|
328
|
+
if (effectiveTargetStatus === "done") {
|
|
329
|
+
tasksCompleted++;
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
detail.fileUpdated = outcome.fileUpdated;
|
|
333
|
+
tasksAlreadySatisfied++;
|
|
334
|
+
}
|
|
335
|
+
details.push(detail);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
338
|
+
if (errorMsg.includes("ENOENT") || errorMsg.includes("not found")) {
|
|
339
|
+
detail.error = `Task artifact not queryable for ${transition.taskId}; transition skipped as ephemeral.`;
|
|
340
|
+
detail.ephemeral = true;
|
|
341
|
+
tasksEphemeral++;
|
|
342
|
+
} else {
|
|
343
|
+
detail.error = errorMsg;
|
|
344
|
+
tasksFailed++;
|
|
345
|
+
}
|
|
346
|
+
details.push(detail);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const progress = await calculateSprintProgress(sprintId);
|
|
350
|
+
try {
|
|
351
|
+
await refreshSprintMarkdown(sprintId);
|
|
352
|
+
} catch {
|
|
353
|
+
}
|
|
354
|
+
return progress;
|
|
355
|
+
});
|
|
356
|
+
const planningRoot = getPlanningRoot();
|
|
357
|
+
let completionsSucceeded = 0;
|
|
358
|
+
for (const pending of pendingCompletions) {
|
|
359
|
+
try {
|
|
360
|
+
await completeTask(pending.taskId, { completionContractRoot: planningRoot });
|
|
361
|
+
completionsSucceeded++;
|
|
362
|
+
pending.detail.previousStatus = pending.previousStatus;
|
|
363
|
+
pending.detail.newStatus = "done";
|
|
364
|
+
pending.detail.updated = true;
|
|
365
|
+
tasksUpdated++;
|
|
366
|
+
tasksCompleted++;
|
|
367
|
+
} catch (error) {
|
|
368
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
369
|
+
pending.detail.error = errorMsg;
|
|
370
|
+
pending.detail.updated = false;
|
|
371
|
+
pending.detail.newStatus = "review";
|
|
372
|
+
if (pending.ancillaryFileUpdated) {
|
|
373
|
+
pending.detail.fileUpdated = true;
|
|
374
|
+
}
|
|
375
|
+
tasksFailed++;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (completionsSucceeded > 0) {
|
|
379
|
+
sprintProgress = await calculateSprintProgress(sprintId);
|
|
380
|
+
try {
|
|
381
|
+
await refreshSprintMarkdown(sprintId);
|
|
382
|
+
} catch {
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return {
|
|
386
|
+
tasksUpdated,
|
|
387
|
+
tasksCompleted,
|
|
388
|
+
tasksAlreadySatisfied,
|
|
389
|
+
tasksFailed,
|
|
390
|
+
tasksEphemeral,
|
|
391
|
+
sprintProgress,
|
|
392
|
+
details
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/automation/flywheel-merge.ts
|
|
397
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
398
|
+
import * as fs2 from "fs/promises";
|
|
399
|
+
import * as os from "os";
|
|
400
|
+
import * as path2 from "path";
|
|
401
|
+
import chalk3 from "chalk";
|
|
402
|
+
import { execa as execa4 } from "execa";
|
|
403
|
+
import yaml from "js-yaml";
|
|
404
|
+
|
|
405
|
+
// src/automation/flywheel-contract-drift.ts
|
|
406
|
+
var CROSS_SUBSTAGE_CONTRACT_DRIFT = "cross-substage contract drift";
|
|
407
|
+
var DRIFT_PATTERNS = [
|
|
408
|
+
/expected\s+\{[^}]*\}\s+to\s+have\s+property\s+['"]length['"]/i,
|
|
409
|
+
/\w+\s*\.\s*map\s+is\s+not\s+a\s+function/i,
|
|
410
|
+
/\w+\s*\.\s*filter\s+is\s+not\s+a\s+function/i,
|
|
411
|
+
/\w+\s*\.\s*forEach\s+is\s+not\s+a\s+function/i,
|
|
412
|
+
/\w+\s*\.\s*reduce\s+is\s+not\s+a\s+function/i,
|
|
413
|
+
/\w+\s*\.\s*find\s+is\s+not\s+a\s+function/i,
|
|
414
|
+
/is\s+not\s+iterable/i,
|
|
415
|
+
/Cannot\s+read\s+propert(?:y|ies)\s+of\s+undefined.*\.\s*(?:length|map|filter|find|reduce)/i,
|
|
416
|
+
/TypeError.*\.\s*(?:map|filter|forEach|find|reduce)\s+is\s+not\s+a\s+function/i
|
|
417
|
+
];
|
|
418
|
+
var SYMBOL_EXTRACTION_PATTERN = /(\b\w+(?:\.\w+)*)\s*\.\s*(?:map|filter|forEach|find|reduce|length)\b/gi;
|
|
419
|
+
function detectContractDrift(output) {
|
|
420
|
+
if (!output || typeof output !== "string") {
|
|
421
|
+
return { isDrift: false, symbols: [], matchedPatterns: [] };
|
|
422
|
+
}
|
|
423
|
+
const matchedPatterns = [];
|
|
424
|
+
for (const pattern of DRIFT_PATTERNS) {
|
|
425
|
+
if (pattern.test(output)) {
|
|
426
|
+
matchedPatterns.push(pattern.source);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (matchedPatterns.length === 0) {
|
|
430
|
+
return { isDrift: false, symbols: [], matchedPatterns: [] };
|
|
431
|
+
}
|
|
432
|
+
const symbolSet = /* @__PURE__ */ new Set();
|
|
433
|
+
let match;
|
|
434
|
+
const extractor = new RegExp(SYMBOL_EXTRACTION_PATTERN.source, "gi");
|
|
435
|
+
match = extractor.exec(output);
|
|
436
|
+
while (match !== null) {
|
|
437
|
+
const sym = match[1];
|
|
438
|
+
if (sym && sym.length > 0 && sym !== "a" && sym !== "is") {
|
|
439
|
+
symbolSet.add(sym);
|
|
440
|
+
}
|
|
441
|
+
match = extractor.exec(output);
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
isDrift: true,
|
|
445
|
+
symbols: [...symbolSet],
|
|
446
|
+
matchedPatterns
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function formatContractDriftMessage(detection, failedCommand) {
|
|
450
|
+
if (!detection.isDrift) {
|
|
451
|
+
return "";
|
|
452
|
+
}
|
|
453
|
+
const symbolDesc = detection.symbols.length > 0 ? `Symbols involved: ${detection.symbols.slice(0, 5).join(", ")}.` : "Symbol name could not be extracted from output.";
|
|
454
|
+
return `Failure classification: ${CROSS_SUBSTAGE_CONTRACT_DRIFT}. A sibling substage generated tests asserting an incompatible return shape for a shared symbol. ${symbolDesc} Failed command: ${failedCommand}.`;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/automation/flywheel-merge-apply.ts
|
|
458
|
+
import chalk from "chalk";
|
|
459
|
+
import { execa } from "execa";
|
|
460
|
+
var SquashMergeConflictError = class extends Error {
|
|
461
|
+
conflictFiles;
|
|
462
|
+
constructor(message, conflictFiles) {
|
|
463
|
+
super(message);
|
|
464
|
+
this.name = "SquashMergeConflictError";
|
|
465
|
+
this.conflictFiles = conflictFiles;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
function formatPathList(paths, max = 8) {
|
|
469
|
+
const shown = paths.slice(0, max);
|
|
470
|
+
const suffix = paths.length > shown.length ? ` ... (+${paths.length - shown.length} more)` : "";
|
|
471
|
+
return shown.join(", ") + suffix;
|
|
472
|
+
}
|
|
473
|
+
async function squashMergeRunBranch(params) {
|
|
474
|
+
const { cwd, integrationBranch } = params;
|
|
475
|
+
console.log(chalk.cyan(" Squash-merging run branch..."));
|
|
476
|
+
const mergeResult = await execa("git", ["merge", "--squash", integrationBranch], {
|
|
477
|
+
cwd,
|
|
478
|
+
stdio: "pipe",
|
|
479
|
+
timeout: 6e4,
|
|
480
|
+
reject: false
|
|
481
|
+
});
|
|
482
|
+
if (mergeResult.timedOut) {
|
|
483
|
+
throw new Error("Squash merge timed out after 60000ms");
|
|
484
|
+
}
|
|
485
|
+
if (mergeResult.exitCode !== 0) {
|
|
486
|
+
if (mergeResult.exitCode === 128) {
|
|
487
|
+
const fatalMsg = (mergeResult.stderr ?? "").trim() || "Fatal git error during squash merge";
|
|
488
|
+
throw new Error(`Fatal git error: ${fatalMsg}`);
|
|
489
|
+
}
|
|
490
|
+
if (mergeResult.exitCode !== 1) {
|
|
491
|
+
const genericMsg = (mergeResult.stderr ?? "").trim() || `Squash merge failed with exit code ${mergeResult.exitCode}`;
|
|
492
|
+
throw new Error(`Squash merge failed: ${genericMsg}`);
|
|
493
|
+
}
|
|
494
|
+
const { stdout: conflictStdout } = await execa(
|
|
495
|
+
"git",
|
|
496
|
+
["diff", "--name-only", "--diff-filter=U"],
|
|
497
|
+
{
|
|
498
|
+
cwd,
|
|
499
|
+
stdio: "pipe",
|
|
500
|
+
timeout: 3e4,
|
|
501
|
+
reject: false
|
|
502
|
+
}
|
|
503
|
+
);
|
|
504
|
+
const conflicts = conflictStdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
505
|
+
const msg = `Squash-merge failed due to conflicts (${conflicts.length}).
|
|
506
|
+
Conflicts: ${conflicts.length > 0 ? formatPathList(conflicts) : "(unknown)"}`;
|
|
507
|
+
throw new SquashMergeConflictError(msg, conflicts);
|
|
508
|
+
}
|
|
509
|
+
if (mergeResult.stdout) process.stdout.write(mergeResult.stdout);
|
|
510
|
+
if (mergeResult.stderr) process.stderr.write(mergeResult.stderr);
|
|
511
|
+
}
|
|
512
|
+
async function snapshotDirtyPlanningPaths(params) {
|
|
513
|
+
const { cwd, allowlistPrefixes } = params;
|
|
514
|
+
const status = await execa(
|
|
515
|
+
"git",
|
|
516
|
+
["status", "--porcelain=v1", "-z", "--", ...allowlistPrefixes],
|
|
517
|
+
{ cwd, stdio: "pipe", timeout: 3e4, reject: false }
|
|
518
|
+
);
|
|
519
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
520
|
+
const fields = status.stdout.split("\0");
|
|
521
|
+
for (let i = 0; i < fields.length; i++) {
|
|
522
|
+
const entry = fields[i];
|
|
523
|
+
if (entry === void 0 || entry.length < 4) continue;
|
|
524
|
+
const x = entry[0];
|
|
525
|
+
const y = entry[1];
|
|
526
|
+
dirty.add(entry.slice(3));
|
|
527
|
+
if (x === "R" || x === "C" || y === "R" || y === "C") {
|
|
528
|
+
const source = fields[i + 1];
|
|
529
|
+
if (source !== void 0 && source.length > 0) {
|
|
530
|
+
dirty.add(source);
|
|
531
|
+
}
|
|
532
|
+
i++;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
return dirty;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// src/automation/flywheel-merge-biome-repair.ts
|
|
539
|
+
import * as fs from "fs/promises";
|
|
540
|
+
import chalk2 from "chalk";
|
|
541
|
+
import { execa as execa3 } from "execa";
|
|
542
|
+
|
|
543
|
+
// src/automation/flywheel-biome-repair.ts
|
|
544
|
+
var MAX_TIMEBOX_MS = resolveExecutionBoundary("short-command").hardCeilingMs;
|
|
545
|
+
|
|
546
|
+
// src/automation/sandboxed-project-bin.ts
|
|
547
|
+
import { existsSync } from "fs";
|
|
548
|
+
import path from "path";
|
|
549
|
+
import { execa as execa2 } from "execa";
|
|
550
|
+
|
|
551
|
+
// src/automation/flywheel-merge.ts
|
|
552
|
+
function readPackageJson(projectRoot) {
|
|
553
|
+
try {
|
|
554
|
+
const raw = readFileSync(path2.join(projectRoot, "package.json"), "utf-8");
|
|
555
|
+
const parsed = JSON.parse(raw);
|
|
556
|
+
return isRecord(parsed) ? parsed : void 0;
|
|
557
|
+
} catch {
|
|
558
|
+
return void 0;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function packageJsonDeclaresDependency(pkg, name) {
|
|
562
|
+
if (!pkg) return false;
|
|
563
|
+
for (const key of [
|
|
564
|
+
"dependencies",
|
|
565
|
+
"devDependencies",
|
|
566
|
+
"peerDependencies",
|
|
567
|
+
"optionalDependencies"
|
|
568
|
+
]) {
|
|
569
|
+
const value = pkg[key];
|
|
570
|
+
if (isRecord(value) && typeof value[name] === "string") return true;
|
|
571
|
+
}
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
function commandUsesVitest(command) {
|
|
575
|
+
return /(?:^|\s)(?:(?:npx|pnpm exec|pnpx|yarn|bunx)\s+)?vitest(?:\s|$)/.test(command);
|
|
576
|
+
}
|
|
577
|
+
function resolvePostMergeFocusedTestCommand(params) {
|
|
578
|
+
const { testFile, capabilities, existingCommands } = params;
|
|
579
|
+
const packageJson = readPackageJson(capabilities.root);
|
|
580
|
+
const hasVitest = existingCommands.some(commandUsesVitest) || packageJsonDeclaresDependency(packageJson, "vitest");
|
|
581
|
+
if (hasVitest) {
|
|
582
|
+
return {
|
|
583
|
+
kind: "focused",
|
|
584
|
+
command: `npx --no-install vitest run ${quotePosixShellArg(testFile)}`
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
const wholeSuite = capabilities.verification.test;
|
|
588
|
+
if (wholeSuite.length > 0) {
|
|
589
|
+
return { kind: "whole-suite", commands: wholeSuite };
|
|
590
|
+
}
|
|
591
|
+
return { kind: "unavailable" };
|
|
592
|
+
}
|
|
593
|
+
async function applyTriageDecisions(workflowId, options = {}) {
|
|
594
|
+
const triage = await loadTriage(workflowId);
|
|
595
|
+
if (!triage) return { followUpFailures: [] };
|
|
596
|
+
const manifest = await loadManifest(workflowId);
|
|
597
|
+
const cwd = options.cwd ?? getProjectRoot();
|
|
598
|
+
const currentBranch = await getCurrentBranch(cwd);
|
|
599
|
+
if (options.expectedBranch && currentBranch !== options.expectedBranch) {
|
|
600
|
+
throw new Error(
|
|
601
|
+
`Triage apply target branch changed: expected ${options.expectedBranch}, found ${currentBranch}`
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
assertBranchAllowsCodeApply({
|
|
605
|
+
allowMain: options.allowMain,
|
|
606
|
+
allowProtected: options.allowProtected,
|
|
607
|
+
command: `flywheel merge-result ${workflowId}`,
|
|
608
|
+
cwd,
|
|
609
|
+
currentBranch
|
|
610
|
+
});
|
|
611
|
+
const sprintId = manifest.sprintId;
|
|
612
|
+
const followUpFailures = [];
|
|
613
|
+
for (const [stageId, stageTriage] of Object.entries(triage.stages)) {
|
|
614
|
+
const decisions = stageTriage.decisions;
|
|
615
|
+
const toDiscard = [];
|
|
616
|
+
const followUps = [];
|
|
617
|
+
for (const decision of decisions) {
|
|
618
|
+
if (decision.outcome === "accept") {
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
toDiscard.push(decision.file);
|
|
622
|
+
if (decision.outcome === "follow-up") {
|
|
623
|
+
followUps.push(decision.file);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (toDiscard.length > 0) {
|
|
627
|
+
console.log(
|
|
628
|
+
chalk3.cyan(
|
|
629
|
+
` Applying triage for stage ${stageId}: discarding ${toDiscard.length} file(s)...`
|
|
630
|
+
)
|
|
631
|
+
);
|
|
632
|
+
await execa4("git", ["reset", "HEAD", "--", ...toDiscard], {
|
|
633
|
+
cwd,
|
|
634
|
+
stdio: "pipe",
|
|
635
|
+
timeout: 3e4
|
|
636
|
+
});
|
|
637
|
+
const tracked = [];
|
|
638
|
+
const untracked = [];
|
|
639
|
+
for (const f of toDiscard) {
|
|
640
|
+
const check = await execa4("git", ["ls-files", "--error-unmatch", "--", f], {
|
|
641
|
+
cwd,
|
|
642
|
+
stdio: "pipe",
|
|
643
|
+
timeout: 1e4,
|
|
644
|
+
reject: false
|
|
645
|
+
});
|
|
646
|
+
if (check.exitCode === 0) {
|
|
647
|
+
tracked.push(f);
|
|
648
|
+
} else {
|
|
649
|
+
untracked.push(f);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
if (tracked.length > 0) {
|
|
653
|
+
await execa4("git", ["checkout", "--", ...tracked], {
|
|
654
|
+
cwd,
|
|
655
|
+
stdio: "pipe",
|
|
656
|
+
timeout: 3e4
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
if (untracked.length > 0) {
|
|
660
|
+
await execa4("git", ["clean", "-f", "--", ...untracked], {
|
|
661
|
+
cwd,
|
|
662
|
+
stdio: "pipe",
|
|
663
|
+
timeout: 3e4
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
console.log(chalk3.dim(` Discarded: ${formatPathList(toDiscard)}`));
|
|
667
|
+
}
|
|
668
|
+
if (followUps.length > 0 && sprintId) {
|
|
669
|
+
console.log(chalk3.cyan(` Creating ${followUps.length} follow-up task(s)...`));
|
|
670
|
+
for (const file of followUps) {
|
|
671
|
+
try {
|
|
672
|
+
const task = await createTask(`Follow-up: Refine drift in ${file}`, sprintId, {
|
|
673
|
+
priority: "P2",
|
|
674
|
+
estimatedHours: 2
|
|
675
|
+
});
|
|
676
|
+
console.log(chalk3.dim(` - Created task ${task.id} for ${file}`));
|
|
677
|
+
} catch (error) {
|
|
678
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
679
|
+
console.log(
|
|
680
|
+
chalk3.yellow(` Warning: Failed to create follow-up task for ${file}: ${errMsg}`)
|
|
681
|
+
);
|
|
682
|
+
followUpFailures.push(`Follow-up: Refine drift in ${file}: ${errMsg}`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
} else if (followUps.length > 0 && !sprintId) {
|
|
686
|
+
console.log(
|
|
687
|
+
chalk3.yellow(` Warning: ${followUps.length} follow-up(s) skipped: no sprintId in manifest`)
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return { followUpFailures };
|
|
692
|
+
}
|
|
693
|
+
function parseGitStatusPorcelain(output) {
|
|
694
|
+
const lines = output.split("\n").map((l) => l.trimEnd()).filter((l) => l.length > 0);
|
|
695
|
+
return lines.map((line) => {
|
|
696
|
+
const status = line.slice(0, 2);
|
|
697
|
+
const filePath = line.slice(3);
|
|
698
|
+
const arrowIndex = filePath.indexOf(" -> ");
|
|
699
|
+
if (arrowIndex >= 0) {
|
|
700
|
+
return {
|
|
701
|
+
status,
|
|
702
|
+
originalPath: filePath.slice(0, arrowIndex),
|
|
703
|
+
path: filePath.slice(arrowIndex + " -> ".length)
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
return { status, path: filePath };
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
function getStatusEntryPathspecs(entry) {
|
|
710
|
+
return Array.from(new Set([entry.originalPath, entry.path].filter(Boolean)));
|
|
711
|
+
}
|
|
712
|
+
function isStatusEntryInAllowlist(entry, allowlistPrefixes) {
|
|
713
|
+
return getStatusEntryPathspecs(entry).every(
|
|
714
|
+
(pathname) => isPathInAllowlist(pathname, allowlistPrefixes)
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
function normalizeDirPrefix(prefix) {
|
|
718
|
+
const normalized = prefix.trim().replace(/\\/g, "/");
|
|
719
|
+
if (normalized.length === 0) return normalized;
|
|
720
|
+
return normalized.endsWith("/") ? normalized : `${normalized}/`;
|
|
721
|
+
}
|
|
722
|
+
function toPosixRelative(fromDir, toPath) {
|
|
723
|
+
return path2.relative(fromDir, toPath).replace(/\\/g, "/");
|
|
724
|
+
}
|
|
725
|
+
function getPlanningDirtyAllowlistPrefixes(repoRoot) {
|
|
726
|
+
const bucketPaths = getBuckets(repoRoot).map((b) => b.path);
|
|
727
|
+
const extraPaths = [".poetic/", "planning/"];
|
|
728
|
+
return [...bucketPaths, ...extraPaths].map(normalizeDirPrefix);
|
|
729
|
+
}
|
|
730
|
+
function isPathInAllowlist(pathname, allowlistPrefixes) {
|
|
731
|
+
const normalized = pathname.replace(/\\/g, "/");
|
|
732
|
+
return allowlistPrefixes.some((prefix) => normalized.startsWith(prefix));
|
|
733
|
+
}
|
|
734
|
+
async function resolveStashShaByMessage(cwd, message) {
|
|
735
|
+
const listResult = await execa4("git", ["stash", "list", "--format=%H %gs"], {
|
|
736
|
+
cwd,
|
|
737
|
+
stdio: "pipe",
|
|
738
|
+
timeout: 3e4,
|
|
739
|
+
reject: false
|
|
740
|
+
});
|
|
741
|
+
if ((listResult.exitCode ?? 1) !== 0) return void 0;
|
|
742
|
+
const matches = listResult.stdout.split("\n").map((line) => line.replace(/\r$/, "")).filter((line) => line.length > 0).map((line) => {
|
|
743
|
+
const sep = line.indexOf(" ");
|
|
744
|
+
if (sep < 0) return void 0;
|
|
745
|
+
return { sha: line.slice(0, sep), subject: line.slice(sep + 1) };
|
|
746
|
+
}).filter((entry) => entry !== void 0).filter((entry) => entry.subject.endsWith(message) || entry.subject.includes(message));
|
|
747
|
+
if (matches.length === 1) return matches[0]?.sha;
|
|
748
|
+
return void 0;
|
|
749
|
+
}
|
|
750
|
+
function hasUnstagedTrackedChange(status) {
|
|
751
|
+
const worktreeStatus = status[1] ?? " ";
|
|
752
|
+
return worktreeStatus !== " " && worktreeStatus !== "?";
|
|
753
|
+
}
|
|
754
|
+
function isTrackedStatusChange(status) {
|
|
755
|
+
const indexStatus = status[0] ?? " ";
|
|
756
|
+
const worktreeStatus = status[1] ?? " ";
|
|
757
|
+
if (indexStatus === "?" || worktreeStatus === "?") return false;
|
|
758
|
+
return indexStatus !== " " || worktreeStatus !== " ";
|
|
759
|
+
}
|
|
760
|
+
function relevantPostVerifyMutations(entries) {
|
|
761
|
+
return entries.filter(
|
|
762
|
+
(entry) => entry.status === "??" || isTrackedStatusChange(entry.status) && hasUnstagedTrackedChange(entry.status)
|
|
763
|
+
).flatMap(getStatusEntryPathspecs).filter((pathname) => !isWorktreeRunSentinelPath(pathname) && !shouldExcludeFile(pathname));
|
|
764
|
+
}
|
|
765
|
+
async function captureProspectiveTreeFingerprint(cwd) {
|
|
766
|
+
const prospectiveTree = await execGit(["write-tree"], { cwd, timeoutMs: 3e4 });
|
|
767
|
+
const fingerprint = prospectiveTree.stdout.trim();
|
|
768
|
+
if (prospectiveTree.exitCode !== 0 || fingerprint.length === 0) {
|
|
769
|
+
throw new Error("Post-merge verify gate could not fingerprint the prospective commit tree.");
|
|
770
|
+
}
|
|
771
|
+
return fingerprint;
|
|
772
|
+
}
|
|
773
|
+
async function finalizeVerifiedTreeFingerprint(cwd, prospectiveFingerprint) {
|
|
774
|
+
const statusAfterVerify = await execGit(["status", "--porcelain=v1", "--untracked-files=all"], {
|
|
775
|
+
cwd,
|
|
776
|
+
timeoutMs: 3e4
|
|
777
|
+
});
|
|
778
|
+
if (statusAfterVerify.exitCode !== 0) {
|
|
779
|
+
throw new Error("Post-merge verify gate could not inspect the verified worktree state.");
|
|
780
|
+
}
|
|
781
|
+
const verifyMutations = relevantPostVerifyMutations(
|
|
782
|
+
parseGitStatusPorcelain(statusAfterVerify.stdout)
|
|
783
|
+
);
|
|
784
|
+
if (verifyMutations.length > 0) {
|
|
785
|
+
throw new Error(
|
|
786
|
+
`Post-merge verify mutated relevant worktree content: ${formatPathList(verifyMutations)}`
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
const tree = await execGit(["write-tree"], { cwd, timeoutMs: 3e4 });
|
|
790
|
+
const treeSha = tree.stdout.trim();
|
|
791
|
+
if (tree.exitCode !== 0 || treeSha.length === 0) {
|
|
792
|
+
throw new Error("Post-merge verify gate could not fingerprint the verified tree.");
|
|
793
|
+
}
|
|
794
|
+
if (treeSha !== prospectiveFingerprint) {
|
|
795
|
+
throw new Error("Post-merge verify changed the prospective commit tree.");
|
|
796
|
+
}
|
|
797
|
+
return treeSha;
|
|
798
|
+
}
|
|
799
|
+
async function assertCommittedTreeMatchesFingerprint(cwd, verifiedTreeFingerprint) {
|
|
800
|
+
const finalHead = await execGit(["rev-parse", "HEAD"], { cwd, timeoutMs: 3e4 });
|
|
801
|
+
const finalTree = await execGit(["rev-parse", "HEAD^{tree}"], { cwd, timeoutMs: 3e4 });
|
|
802
|
+
if (finalHead.exitCode !== 0 || finalHead.stdout.trim().length === 0 || finalTree.exitCode !== 0 || finalTree.stdout.trim() !== verifiedTreeFingerprint) {
|
|
803
|
+
throw new Error("Committed HEAD tree does not match the final verified tree.");
|
|
804
|
+
}
|
|
805
|
+
return finalHead.stdout.trim();
|
|
806
|
+
}
|
|
807
|
+
function buildApplyOnlyProofStatus(verifiedTreeFingerprint, stagedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
808
|
+
return { stagedAt, verifiedTreeFingerprint };
|
|
809
|
+
}
|
|
810
|
+
function getAutomergeDeprecationWarning() {
|
|
811
|
+
const envMode = process.env[POETIC_AUTOMERGE_MODE.name]?.trim();
|
|
812
|
+
if (envMode === "label" || envMode === "auto") {
|
|
813
|
+
return "Auto-merge configuration is deprecated and has no effect; Poetic creates the PR but never labels or merges it.";
|
|
814
|
+
}
|
|
815
|
+
return void 0;
|
|
816
|
+
}
|
|
817
|
+
function readArrayField(obj, key) {
|
|
818
|
+
const value = obj[key];
|
|
819
|
+
return Array.isArray(value) ? value : void 0;
|
|
820
|
+
}
|
|
821
|
+
function readStringField(obj, key) {
|
|
822
|
+
const value = obj[key];
|
|
823
|
+
return typeof value === "string" ? value : void 0;
|
|
824
|
+
}
|
|
825
|
+
function readBooleanField(obj, key) {
|
|
826
|
+
const value = obj[key];
|
|
827
|
+
return typeof value === "boolean" ? value : void 0;
|
|
828
|
+
}
|
|
829
|
+
function readNumberField(obj, key) {
|
|
830
|
+
const value = obj[key];
|
|
831
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
832
|
+
}
|
|
833
|
+
function readStringArrayField(obj, key) {
|
|
834
|
+
const value = obj[key];
|
|
835
|
+
if (!Array.isArray(value)) return void 0;
|
|
836
|
+
const strings = value.filter((v) => typeof v === "string");
|
|
837
|
+
return strings.length === value.length ? strings : void 0;
|
|
838
|
+
}
|
|
839
|
+
function summarizeWorkflowGateFromVerificationStatuses(statuses) {
|
|
840
|
+
if (statuses.length === 0) {
|
|
841
|
+
return {
|
|
842
|
+
passed: false,
|
|
843
|
+
source: "execution",
|
|
844
|
+
decidedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
845
|
+
reason: "Workflow verification evidence missing or empty",
|
|
846
|
+
blockingStageIds: [],
|
|
847
|
+
needsRepairCount: 0,
|
|
848
|
+
verificationFailedCount: 0
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
const blockingStages = statuses.filter(
|
|
852
|
+
(status) => status.winnerNeedsRepair || !status.verificationPassed
|
|
853
|
+
);
|
|
854
|
+
const needsRepairCount = blockingStages.filter((status) => status.winnerNeedsRepair).length;
|
|
855
|
+
const verificationFailedCount = blockingStages.length - needsRepairCount;
|
|
856
|
+
const passed = blockingStages.length === 0;
|
|
857
|
+
return {
|
|
858
|
+
passed,
|
|
859
|
+
source: "execution",
|
|
860
|
+
decidedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
861
|
+
reason: passed ? "All stage gates passed" : [
|
|
862
|
+
needsRepairCount > 0 ? `${needsRepairCount} stage(s) need repair` : null,
|
|
863
|
+
verificationFailedCount > 0 ? `${verificationFailedCount} stage(s) failed verification` : null
|
|
864
|
+
].filter((part) => typeof part === "string" && part.length > 0).join(", ") || "Workflow gate failed",
|
|
865
|
+
blockingStageIds: blockingStages.map((status) => status.stageId),
|
|
866
|
+
needsRepairCount,
|
|
867
|
+
verificationFailedCount
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
function isCoherentStructuralWorkflowGate(params) {
|
|
871
|
+
const { passed, blockingStageIds, needsRepairCount, verificationFailedCount } = params;
|
|
872
|
+
if (!Number.isInteger(needsRepairCount) || !Number.isInteger(verificationFailedCount) || needsRepairCount < 0 || verificationFailedCount < 0) {
|
|
873
|
+
return false;
|
|
874
|
+
}
|
|
875
|
+
if (needsRepairCount + verificationFailedCount !== blockingStageIds.length) {
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
878
|
+
if (passed) {
|
|
879
|
+
return blockingStageIds.length === 0 && needsRepairCount === 0 && verificationFailedCount === 0;
|
|
880
|
+
}
|
|
881
|
+
return true;
|
|
882
|
+
}
|
|
883
|
+
function failClosedWorkflowGateSummary(params) {
|
|
884
|
+
const blockingStageIds = params.blockingStageIds ?? [];
|
|
885
|
+
return {
|
|
886
|
+
passed: false,
|
|
887
|
+
source: "execution",
|
|
888
|
+
decidedAt: typeof params.decidedAt === "string" && params.decidedAt.trim().length > 0 ? params.decidedAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
889
|
+
reason: params.reason,
|
|
890
|
+
blockingStageIds,
|
|
891
|
+
needsRepairCount: 0,
|
|
892
|
+
verificationFailedCount: blockingStageIds.length
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
function getWorkflowGateSummaryFromResult(parsed) {
|
|
896
|
+
if (!isRecord(parsed)) return null;
|
|
897
|
+
if ("workflowGate" in parsed) {
|
|
898
|
+
const workflowGate = isRecord(parsed.workflowGate) ? parsed.workflowGate : null;
|
|
899
|
+
if (!workflowGate || typeof workflowGate.passed !== "boolean") {
|
|
900
|
+
return failClosedWorkflowGateSummary({
|
|
901
|
+
reason: "Workflow gate evidence is malformed or incomplete"
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
const decidedAt = typeof workflowGate.decidedAt === "string" && workflowGate.decidedAt.trim().length > 0 ? workflowGate.decidedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
905
|
+
const blockingStageIds = readStringArrayField(workflowGate, "blockingStageIds");
|
|
906
|
+
const needsRepairCount = typeof workflowGate.needsRepairCount === "number" && Number.isFinite(workflowGate.needsRepairCount) ? workflowGate.needsRepairCount : void 0;
|
|
907
|
+
const verificationFailedCount = typeof workflowGate.verificationFailedCount === "number" && Number.isFinite(workflowGate.verificationFailedCount) ? workflowGate.verificationFailedCount : void 0;
|
|
908
|
+
if (blockingStageIds === void 0 || needsRepairCount === void 0 || verificationFailedCount === void 0) {
|
|
909
|
+
return failClosedWorkflowGateSummary({
|
|
910
|
+
decidedAt,
|
|
911
|
+
reason: "Workflow gate evidence is incomplete"
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
if (!isCoherentStructuralWorkflowGate({
|
|
915
|
+
passed: workflowGate.passed,
|
|
916
|
+
blockingStageIds,
|
|
917
|
+
needsRepairCount,
|
|
918
|
+
verificationFailedCount
|
|
919
|
+
})) {
|
|
920
|
+
return failClosedWorkflowGateSummary({
|
|
921
|
+
decidedAt,
|
|
922
|
+
reason: "Workflow gate evidence is contradictory",
|
|
923
|
+
// Preserve declared blockers for diagnostics when they look like strings.
|
|
924
|
+
blockingStageIds
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
return {
|
|
928
|
+
passed: workflowGate.passed,
|
|
929
|
+
source: "execution",
|
|
930
|
+
decidedAt,
|
|
931
|
+
reason: typeof workflowGate.reason === "string" && workflowGate.reason.trim().length > 0 ? workflowGate.reason : workflowGate.passed ? "All stage gates passed" : "Workflow gate failed",
|
|
932
|
+
blockingStageIds,
|
|
933
|
+
needsRepairCount,
|
|
934
|
+
verificationFailedCount
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
const stageStatuses = getWorkflowVerificationGateStatusFromResult(parsed);
|
|
938
|
+
return stageStatuses ? summarizeWorkflowGateFromVerificationStatuses(stageStatuses) : null;
|
|
939
|
+
}
|
|
940
|
+
var VALID_TASK_TRANSITION_STATUSES = /* @__PURE__ */ new Set([
|
|
941
|
+
"todo",
|
|
942
|
+
"in_progress",
|
|
943
|
+
"review",
|
|
944
|
+
"done",
|
|
945
|
+
"blocked"
|
|
946
|
+
]);
|
|
947
|
+
function extractTaskTransitionsFromResult(parsed) {
|
|
948
|
+
if (!isRecord(parsed)) return null;
|
|
949
|
+
const stageResults = readArrayField(parsed, "stageResults");
|
|
950
|
+
const allTransitions = [];
|
|
951
|
+
const collectTransitions = (transitions) => {
|
|
952
|
+
if (!Array.isArray(transitions)) return;
|
|
953
|
+
for (const transition of transitions) {
|
|
954
|
+
if (!isRecord(transition)) continue;
|
|
955
|
+
const taskId = readStringField(transition, "taskId");
|
|
956
|
+
const targetStatus = readStringField(transition, "targetStatus");
|
|
957
|
+
if (!taskId || !targetStatus) continue;
|
|
958
|
+
if (!VALID_TASK_TRANSITION_STATUSES.has(targetStatus)) {
|
|
959
|
+
console.log(
|
|
960
|
+
chalk3.yellow(
|
|
961
|
+
` Warning: Skipping task transition for ${taskId}: invalid targetStatus '${targetStatus}'`
|
|
962
|
+
)
|
|
963
|
+
);
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
const hoursLogged = typeof transition.hoursLogged === "number" ? transition.hoursLogged : void 0;
|
|
967
|
+
const reason = readStringField(transition, "reason");
|
|
968
|
+
const resultArtifactPath = readStringField(transition, "resultArtifactPath");
|
|
969
|
+
const transitionId = readStringField(transition, "transitionId");
|
|
970
|
+
allTransitions.push({
|
|
971
|
+
taskId,
|
|
972
|
+
targetStatus,
|
|
973
|
+
hoursLogged,
|
|
974
|
+
reason,
|
|
975
|
+
...resultArtifactPath ? { resultArtifactPath } : {},
|
|
976
|
+
...transitionId ? { transitionId } : {}
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
for (const stageResult of stageResults ?? []) {
|
|
981
|
+
if (!isRecord(stageResult)) continue;
|
|
982
|
+
collectTransitions(readArrayField(stageResult, "taskTransitions"));
|
|
983
|
+
}
|
|
984
|
+
collectTransitions(readArrayField(parsed, "taskTransitions"));
|
|
985
|
+
return allTransitions.length > 0 ? allTransitions : null;
|
|
986
|
+
}
|
|
987
|
+
function getStagesWithDriftFromWorkflowResult(parsed) {
|
|
988
|
+
if (!isRecord(parsed)) return null;
|
|
989
|
+
const stageResults = readArrayField(parsed, "stageResults");
|
|
990
|
+
if (!stageResults) return null;
|
|
991
|
+
const stagesWithDrift = [];
|
|
992
|
+
for (const stageResult of stageResults) {
|
|
993
|
+
if (!isRecord(stageResult)) continue;
|
|
994
|
+
const drift = stageResult.driftContext;
|
|
995
|
+
if (!isRecord(drift)) continue;
|
|
996
|
+
const violations = readStringArrayField(drift, "violations");
|
|
997
|
+
if (!violations || violations.length === 0) continue;
|
|
998
|
+
stagesWithDrift.push({
|
|
999
|
+
stageId: readStringField(stageResult, "stageId") ?? "(unknown)",
|
|
1000
|
+
violations,
|
|
1001
|
+
mode: readStringField(drift, "mode") ?? "warn"
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
return stagesWithDrift;
|
|
1005
|
+
}
|
|
1006
|
+
async function listStagedFiles(cwd) {
|
|
1007
|
+
const { stdout } = await execa4("git", ["diff", "--cached", "--name-only", "--diff-filter=d"], {
|
|
1008
|
+
cwd,
|
|
1009
|
+
stdio: "pipe",
|
|
1010
|
+
timeout: 3e4
|
|
1011
|
+
});
|
|
1012
|
+
return stdout.split("\n").map((line) => line.replace(/\r$/, "")).filter(Boolean);
|
|
1013
|
+
}
|
|
1014
|
+
var BIOME_SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1015
|
+
".js",
|
|
1016
|
+
".cjs",
|
|
1017
|
+
".mjs",
|
|
1018
|
+
".jsx",
|
|
1019
|
+
".ts",
|
|
1020
|
+
".cts",
|
|
1021
|
+
".mts",
|
|
1022
|
+
".tsx",
|
|
1023
|
+
".json",
|
|
1024
|
+
".jsonc",
|
|
1025
|
+
".css",
|
|
1026
|
+
".graphql",
|
|
1027
|
+
".gql"
|
|
1028
|
+
]);
|
|
1029
|
+
function filterBiomePaths(files) {
|
|
1030
|
+
return files.filter((file) => BIOME_SUPPORTED_EXTENSIONS.has(path2.extname(file).toLowerCase()));
|
|
1031
|
+
}
|
|
1032
|
+
async function runBiome(args, cwd) {
|
|
1033
|
+
const env = createVerificationCommandEnv();
|
|
1034
|
+
const sandboxed = wrapWithSandbox({
|
|
1035
|
+
workingDir: cwd,
|
|
1036
|
+
command: "npx",
|
|
1037
|
+
args: ["--no-install", "biome", ...args],
|
|
1038
|
+
providerName: "project-verification",
|
|
1039
|
+
env,
|
|
1040
|
+
allowGitWrites: false,
|
|
1041
|
+
providerId: "project-verification"
|
|
1042
|
+
});
|
|
1043
|
+
const result = await execa4(sandboxed.command, sandboxed.args, {
|
|
1044
|
+
cwd,
|
|
1045
|
+
env,
|
|
1046
|
+
extendEnv: false,
|
|
1047
|
+
stdio: "pipe",
|
|
1048
|
+
timeout: 3e5,
|
|
1049
|
+
reject: false,
|
|
1050
|
+
shell: false
|
|
1051
|
+
});
|
|
1052
|
+
return {
|
|
1053
|
+
ok: result.exitCode === 0,
|
|
1054
|
+
stdout: result.stdout ?? "",
|
|
1055
|
+
stderr: result.stderr ?? ""
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
function isResolverBiomeAvailable(root) {
|
|
1059
|
+
const guidance = buildProjectVerificationGuidance(root, []);
|
|
1060
|
+
return guidance.commands.some(
|
|
1061
|
+
(command) => (command.kind === "format" || command.kind === "lint") && /(?:^|\s)biome(?:\s|$)/.test(command.command)
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
function formatBiomeFailureSummary(result) {
|
|
1065
|
+
const combined = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
1066
|
+
const lines = combined.split("\n");
|
|
1067
|
+
const clipped = lines.slice(0, 60).join("\n");
|
|
1068
|
+
const suffix = lines.length > 60 ? `
|
|
1069
|
+
... (${lines.length - 60} more lines)` : "";
|
|
1070
|
+
return `${clipped}${suffix}`.trim();
|
|
1071
|
+
}
|
|
1072
|
+
function getWorkflowVerificationGateStatusFromResult(parsed) {
|
|
1073
|
+
if (!isRecord(parsed)) return null;
|
|
1074
|
+
const stageResults = readArrayField(parsed, "stageResults");
|
|
1075
|
+
if (!stageResults || stageResults.length === 0) return null;
|
|
1076
|
+
const statuses = [];
|
|
1077
|
+
for (const r of stageResults) {
|
|
1078
|
+
if (!isRecord(r)) {
|
|
1079
|
+
statuses.push({
|
|
1080
|
+
stageId: "(malformed)",
|
|
1081
|
+
winnerNeedsRepair: false,
|
|
1082
|
+
verificationPassed: false
|
|
1083
|
+
});
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
const stageId = readStringField(r, "stageId") ?? "(unknown)";
|
|
1087
|
+
const winnerNeedsRepair = Boolean(readBooleanField(r, "winnerNeedsRepair"));
|
|
1088
|
+
const mergeGate = isRecord(r.mergeGate) ? r.mergeGate : void 0;
|
|
1089
|
+
const verification = r.verification;
|
|
1090
|
+
const stageType = readStringField(r, "stageType");
|
|
1091
|
+
const verifyCommands = readArrayField(r, "verifyCommands");
|
|
1092
|
+
const isVerifyStage = stageType === "verify" || Boolean(verifyCommands);
|
|
1093
|
+
const canonicalMergeGatePassed = isRecord(mergeGate) && typeof mergeGate.passed === "boolean" ? mergeGate.passed : void 0;
|
|
1094
|
+
const effectiveWinnerNeedsRepair = canonicalMergeGatePassed === true ? false : winnerNeedsRepair;
|
|
1095
|
+
const explicitVerificationPassed = isRecord(verification) && typeof verification.passed === "boolean" ? verification.passed : void 0;
|
|
1096
|
+
const validationPassed = readBooleanField(r, "validationPassed");
|
|
1097
|
+
const resolvedVerification = canonicalMergeGatePassed ?? explicitVerificationPassed ?? (isVerifyStage || stageType === "orchestrator" ? validationPassed : void 0);
|
|
1098
|
+
const verificationPassed = resolvedVerification === true;
|
|
1099
|
+
statuses.push({
|
|
1100
|
+
stageId,
|
|
1101
|
+
winnerNeedsRepair: effectiveWinnerNeedsRepair,
|
|
1102
|
+
verificationPassed
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
if (statuses.length === 0) return null;
|
|
1106
|
+
return statuses;
|
|
1107
|
+
}
|
|
1108
|
+
function getWorkflowVerifyStageStatusFromResult(parsed) {
|
|
1109
|
+
if (!isRecord(parsed)) return null;
|
|
1110
|
+
const stageResults = readArrayField(parsed, "stageResults");
|
|
1111
|
+
if (!stageResults) return null;
|
|
1112
|
+
const statuses = [];
|
|
1113
|
+
for (const r of stageResults) {
|
|
1114
|
+
if (!isRecord(r)) continue;
|
|
1115
|
+
const stageType = readStringField(r, "stageType");
|
|
1116
|
+
const verifyCommands = readArrayField(r, "verifyCommands");
|
|
1117
|
+
const isVerifyStage = stageType === "verify" || Boolean(verifyCommands);
|
|
1118
|
+
if (!isVerifyStage) continue;
|
|
1119
|
+
const stageId = readStringField(r, "stageId") ?? "(unknown)";
|
|
1120
|
+
const validationPassed = Boolean(readBooleanField(r, "validationPassed"));
|
|
1121
|
+
const durationMs = typeof r.durationMs === "number" && Number.isFinite(r.durationMs) ? r.durationMs : void 0;
|
|
1122
|
+
const commandsCount = Array.isArray(verifyCommands) ? verifyCommands.length : 0;
|
|
1123
|
+
statuses.push({ stageId, validationPassed, durationMs, commandsCount });
|
|
1124
|
+
}
|
|
1125
|
+
return statuses;
|
|
1126
|
+
}
|
|
1127
|
+
function formatVerifyStatusForCommitMessage(parsed) {
|
|
1128
|
+
const verifyStages = getWorkflowVerifyStageStatusFromResult(parsed);
|
|
1129
|
+
if (!verifyStages || verifyStages.length === 0) return null;
|
|
1130
|
+
const totalStages = verifyStages.length;
|
|
1131
|
+
const passedStages = verifyStages.filter((s) => s.validationPassed).length;
|
|
1132
|
+
const totalCommands = verifyStages.reduce((sum, s) => sum + s.commandsCount, 0);
|
|
1133
|
+
const stageLabel = totalStages === 1 ? ` (${verifyStages[0]?.stageId ?? "(unknown)"})` : ` (${passedStages}/${totalStages} stages)`;
|
|
1134
|
+
const commandsLabel = totalCommands === 1 ? "1 cmd" : `${totalCommands} cmds`;
|
|
1135
|
+
return `Verify: ${passedStages}/${totalStages} passed, ${commandsLabel}${stageLabel}`;
|
|
1136
|
+
}
|
|
1137
|
+
async function loadWorkflowResultForGate(workflowId) {
|
|
1138
|
+
let content;
|
|
1139
|
+
try {
|
|
1140
|
+
const resultPath = getResultPath(workflowId);
|
|
1141
|
+
content = await fs2.readFile(resultPath, "utf-8");
|
|
1142
|
+
} catch {
|
|
1143
|
+
return {
|
|
1144
|
+
kind: "unreadable",
|
|
1145
|
+
summary: failClosedWorkflowGateSummary({
|
|
1146
|
+
reason: "Workflow gate evidence cannot be read"
|
|
1147
|
+
})
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
let parsed;
|
|
1151
|
+
try {
|
|
1152
|
+
parsed = JSON.parse(content);
|
|
1153
|
+
} catch {
|
|
1154
|
+
return {
|
|
1155
|
+
kind: "unreadable",
|
|
1156
|
+
summary: failClosedWorkflowGateSummary({
|
|
1157
|
+
reason: "Workflow gate evidence cannot be parsed"
|
|
1158
|
+
})
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
if (!isRecord(parsed)) {
|
|
1162
|
+
return {
|
|
1163
|
+
kind: "unreadable",
|
|
1164
|
+
summary: failClosedWorkflowGateSummary({
|
|
1165
|
+
reason: "Workflow gate evidence cannot be parsed"
|
|
1166
|
+
})
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
return { kind: "ok", parsed };
|
|
1170
|
+
}
|
|
1171
|
+
async function getWorkflowVerificationGateStatus(workflowId) {
|
|
1172
|
+
const loaded = await loadWorkflowResultForGate(workflowId);
|
|
1173
|
+
if (loaded.kind === "absent") return null;
|
|
1174
|
+
if (loaded.kind === "unreadable") {
|
|
1175
|
+
return [
|
|
1176
|
+
{
|
|
1177
|
+
stageId: "(unreadable)",
|
|
1178
|
+
winnerNeedsRepair: false,
|
|
1179
|
+
verificationPassed: false
|
|
1180
|
+
}
|
|
1181
|
+
];
|
|
1182
|
+
}
|
|
1183
|
+
return getWorkflowVerificationGateStatusFromResult(loaded.parsed);
|
|
1184
|
+
}
|
|
1185
|
+
async function getWorkflowGateSummary(workflowId) {
|
|
1186
|
+
const loaded = await loadWorkflowResultForGate(workflowId);
|
|
1187
|
+
if (loaded.kind === "absent") return null;
|
|
1188
|
+
if (loaded.kind === "unreadable") return loaded.summary;
|
|
1189
|
+
return getWorkflowGateSummaryFromResult(loaded.parsed) ?? failClosedWorkflowGateSummary({
|
|
1190
|
+
reason: "Workflow verification evidence missing or empty"
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
function isPostMergeDiscoveredTestFile(filePath) {
|
|
1194
|
+
return isProjectTestPath(filePath);
|
|
1195
|
+
}
|
|
1196
|
+
function collectHistoricalVerifyCommandResults(parsed) {
|
|
1197
|
+
const results = [];
|
|
1198
|
+
const stack = [parsed];
|
|
1199
|
+
while (stack.length > 0) {
|
|
1200
|
+
const current = stack.pop();
|
|
1201
|
+
if (Array.isArray(current)) {
|
|
1202
|
+
stack.push(...current);
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1205
|
+
if (!isRecord(current)) continue;
|
|
1206
|
+
const verifyCommands = readArrayField(current, "verifyCommands");
|
|
1207
|
+
if (verifyCommands) {
|
|
1208
|
+
for (const entry of verifyCommands) {
|
|
1209
|
+
if (!isRecord(entry)) continue;
|
|
1210
|
+
const command = readStringField(entry, "command");
|
|
1211
|
+
const exitCode = readNumberField(entry, "exitCode");
|
|
1212
|
+
const durationMs = readNumberField(entry, "durationMs");
|
|
1213
|
+
if (!command || exitCode === void 0 || durationMs === void 0) continue;
|
|
1214
|
+
results.push({
|
|
1215
|
+
command,
|
|
1216
|
+
exitCode,
|
|
1217
|
+
durationMs,
|
|
1218
|
+
outputTail: readStringField(entry, "outputTail"),
|
|
1219
|
+
stdoutTail: readStringField(entry, "stdoutTail"),
|
|
1220
|
+
stderrTail: readStringField(entry, "stderrTail"),
|
|
1221
|
+
baselineErrorCount: readNumberField(entry, "baselineErrorCount"),
|
|
1222
|
+
currentErrorCount: readNumberField(entry, "currentErrorCount"),
|
|
1223
|
+
newErrorCount: readNumberField(entry, "newErrorCount")
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
for (const value of Object.values(current)) {
|
|
1228
|
+
if (value && typeof value === "object") {
|
|
1229
|
+
stack.push(value);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
return results;
|
|
1234
|
+
}
|
|
1235
|
+
function formatBaselineComparison(value) {
|
|
1236
|
+
return value === void 0 ? "n/a" : String(value);
|
|
1237
|
+
}
|
|
1238
|
+
function buildPostMergeBaselineDivergenceMessage(params) {
|
|
1239
|
+
const { command, stageResult, mergeResult, stageBaseSha, mergeBaseSha } = params;
|
|
1240
|
+
return [
|
|
1241
|
+
`Historical stage verify passed for "${command}", but post-merge verify failed against a different baseline.`,
|
|
1242
|
+
`Stage baseline counts (base ${stageBaseSha ?? "unknown"}): baseline=${formatBaselineComparison(stageResult.baselineErrorCount)}, current=${formatBaselineComparison(stageResult.currentErrorCount)}, new=${formatBaselineComparison(stageResult.newErrorCount)}.`,
|
|
1243
|
+
`Post-merge baseline counts (base ${mergeBaseSha ?? "unknown"}): baseline=${formatBaselineComparison(mergeResult.baselineErrorCount)}, current=${formatBaselineComparison(mergeResult.currentErrorCount)}, new=${formatBaselineComparison(mergeResult.newErrorCount)}.`
|
|
1244
|
+
].join(" ");
|
|
1245
|
+
}
|
|
1246
|
+
function buildBaselineWorktreeCleanupWarning(params) {
|
|
1247
|
+
const { baselineCwd, projectRoot, stillPresent, registrationState } = params;
|
|
1248
|
+
if (!stillPresent && registrationState === "removed") return void 0;
|
|
1249
|
+
const recovery = `Check it with: git -C ${projectRoot} worktree list --porcelain. Reclaim it with: git -C ${projectRoot} worktree remove --force ${baselineCwd} && git -C ${projectRoot} worktree prune`;
|
|
1250
|
+
if (!stillPresent && registrationState === "indeterminate") {
|
|
1251
|
+
return `Post-merge verify baseline worktree removal could not be verified because its git registration lookup failed (path: ${baselineCwd}). ${recovery}`;
|
|
1252
|
+
}
|
|
1253
|
+
return `Post-merge verify baseline worktree could not be fully removed (path: ${baselineCwd}). ` + recovery;
|
|
1254
|
+
}
|
|
1255
|
+
async function runPostMergeVerifyGate(params) {
|
|
1256
|
+
const {
|
|
1257
|
+
workflowId,
|
|
1258
|
+
manifest,
|
|
1259
|
+
preMergeHeadSha,
|
|
1260
|
+
projectRoot,
|
|
1261
|
+
mergeCwd,
|
|
1262
|
+
ambientUnstagedAllowlistedPaths = [],
|
|
1263
|
+
onVerificationStarted
|
|
1264
|
+
} = params;
|
|
1265
|
+
let orphanedStashWarning;
|
|
1266
|
+
let verifyPassed = false;
|
|
1267
|
+
let verifiedTreeFingerprint;
|
|
1268
|
+
const commands = await resolvePostMergeVerifyCommands({
|
|
1269
|
+
workflowId,
|
|
1270
|
+
manifest,
|
|
1271
|
+
projectRoot,
|
|
1272
|
+
verificationRoot: mergeCwd
|
|
1273
|
+
});
|
|
1274
|
+
const capabilities = detectProjectCapabilities(projectRoot);
|
|
1275
|
+
const pushCommand = (cmd) => {
|
|
1276
|
+
if (!commands.includes(cmd)) {
|
|
1277
|
+
commands.push(cmd);
|
|
1278
|
+
}
|
|
1279
|
+
};
|
|
1280
|
+
const stagedFiles = await listStagedFiles(mergeCwd);
|
|
1281
|
+
const stagedTestFiles = stagedFiles.filter(isPostMergeDiscoveredTestFile);
|
|
1282
|
+
let unverifiableStagedTest = false;
|
|
1283
|
+
for (const testFile of stagedTestFiles) {
|
|
1284
|
+
const augmentation = resolvePostMergeFocusedTestCommand({
|
|
1285
|
+
testFile,
|
|
1286
|
+
capabilities,
|
|
1287
|
+
existingCommands: commands
|
|
1288
|
+
});
|
|
1289
|
+
if (augmentation.kind === "focused") {
|
|
1290
|
+
if (!commands.some((c) => c.includes(testFile))) {
|
|
1291
|
+
pushCommand(augmentation.command);
|
|
1292
|
+
}
|
|
1293
|
+
} else if (augmentation.kind === "whole-suite") {
|
|
1294
|
+
for (const cmd of augmentation.commands) pushCommand(cmd);
|
|
1295
|
+
} else {
|
|
1296
|
+
unverifiableStagedTest = true;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
if (unverifiableStagedTest) {
|
|
1300
|
+
console.log(
|
|
1301
|
+
chalk3.yellow(
|
|
1302
|
+
" Warning: staged test file(s) detected but no resolver test command is available for this target language; augmented post-merge verification is unavailable for them."
|
|
1303
|
+
)
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
if (commands.length === 0) {
|
|
1307
|
+
throw new Error(
|
|
1308
|
+
"Post-merge verify gate cannot prove the final tree: no verify commands found."
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
console.log(
|
|
1312
|
+
chalk3.cyan(` Post-merge verify (${commands.length} cmd${commands.length === 1 ? "" : "s"})...`)
|
|
1313
|
+
);
|
|
1314
|
+
const { runVerifyCommands } = await import("./verify-commands-5F6CJV4B.js");
|
|
1315
|
+
let baselineCwd;
|
|
1316
|
+
let historicalVerifyResults = [];
|
|
1317
|
+
let verifyAllowlistStashSha;
|
|
1318
|
+
if (preMergeHeadSha && commands.some((command) => isBaselineComparableTypeScriptCommand(command))) {
|
|
1319
|
+
const baselinePrefix = path2.join(projectRoot, ".poetic", "tmp", "post-merge-verify-");
|
|
1320
|
+
baselineCwd = await fs2.mkdtemp(baselinePrefix);
|
|
1321
|
+
try {
|
|
1322
|
+
const addResult = await execGit(
|
|
1323
|
+
["worktree", "add", "--detach", baselineCwd, preMergeHeadSha],
|
|
1324
|
+
{
|
|
1325
|
+
cwd: projectRoot,
|
|
1326
|
+
timeoutMs: 6e4
|
|
1327
|
+
}
|
|
1328
|
+
);
|
|
1329
|
+
if (addResult.exitCode !== 0) {
|
|
1330
|
+
throw new Error(addResult.stderr.trim() || "git worktree add failed");
|
|
1331
|
+
}
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
await fs2.rm(baselineCwd, { recursive: true, force: true });
|
|
1334
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1335
|
+
throw new Error(`Failed to create baseline worktree for post-merge verify: ${message}`);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
try {
|
|
1339
|
+
const resultPath = getResultPath(workflowId);
|
|
1340
|
+
const raw = await fs2.readFile(resultPath, "utf-8");
|
|
1341
|
+
historicalVerifyResults = collectHistoricalVerifyCommandResults(JSON.parse(raw));
|
|
1342
|
+
} catch {
|
|
1343
|
+
historicalVerifyResults = [];
|
|
1344
|
+
}
|
|
1345
|
+
const ambientAllowlistedPaths = Array.from(
|
|
1346
|
+
new Set(
|
|
1347
|
+
ambientUnstagedAllowlistedPaths.map((candidate) => candidate.trim()).filter((candidate) => candidate.length > 0)
|
|
1348
|
+
)
|
|
1349
|
+
);
|
|
1350
|
+
const restoreAllowlistStash = async () => {
|
|
1351
|
+
if (verifyAllowlistStashSha) {
|
|
1352
|
+
const applyResult = await execa4("git", ["stash", "apply", verifyAllowlistStashSha], {
|
|
1353
|
+
cwd: mergeCwd,
|
|
1354
|
+
stdio: "pipe",
|
|
1355
|
+
timeout: 6e4,
|
|
1356
|
+
reject: false
|
|
1357
|
+
});
|
|
1358
|
+
if ((applyResult.exitCode ?? 1) === 0) {
|
|
1359
|
+
const listResult = await execa4("git", ["stash", "list", "--format=%gd %H"], {
|
|
1360
|
+
cwd: mergeCwd,
|
|
1361
|
+
stdio: "pipe",
|
|
1362
|
+
timeout: 3e4,
|
|
1363
|
+
reject: false
|
|
1364
|
+
});
|
|
1365
|
+
const matchingRef = listResult.stdout.split("\n").map((line) => line.trim()).find((line) => line.endsWith(` ${verifyAllowlistStashSha}`))?.split(" ")[0];
|
|
1366
|
+
if (matchingRef) {
|
|
1367
|
+
await execa4("git", ["stash", "drop", matchingRef], {
|
|
1368
|
+
cwd: mergeCwd,
|
|
1369
|
+
stdio: "pipe",
|
|
1370
|
+
timeout: 3e4,
|
|
1371
|
+
reject: false
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
} else {
|
|
1375
|
+
orphanedStashWarning = `Post-merge verify allowlist stash ${verifyAllowlistStashSha} could not be reapplied and was preserved (worktree: ${mergeCwd}). Recover it with: git -C ${mergeCwd} stash apply ${verifyAllowlistStashSha}`;
|
|
1376
|
+
console.log(
|
|
1377
|
+
chalk3.yellow(
|
|
1378
|
+
` Warning: Failed to restore post-merge verify allowlist stash (${verifyAllowlistStashSha.slice(0, 7)}); it is preserved.`
|
|
1379
|
+
)
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
const removeBaselineWorktree = async () => {
|
|
1385
|
+
if (!baselineCwd) return;
|
|
1386
|
+
let worktreeRemoved = false;
|
|
1387
|
+
try {
|
|
1388
|
+
const removeResult = await execGit(["worktree", "remove", "--force", baselineCwd], {
|
|
1389
|
+
cwd: projectRoot,
|
|
1390
|
+
timeoutMs: 6e4
|
|
1391
|
+
});
|
|
1392
|
+
worktreeRemoved = removeResult.exitCode === 0;
|
|
1393
|
+
} catch {
|
|
1394
|
+
}
|
|
1395
|
+
try {
|
|
1396
|
+
await fs2.rm(baselineCwd, { recursive: true, force: true });
|
|
1397
|
+
} catch {
|
|
1398
|
+
}
|
|
1399
|
+
try {
|
|
1400
|
+
await pruneGitWorktreeEntries({ cwd: projectRoot, timeoutMs: 1e4 });
|
|
1401
|
+
} catch {
|
|
1402
|
+
}
|
|
1403
|
+
let stillPresent = false;
|
|
1404
|
+
try {
|
|
1405
|
+
stillPresent = existsSync2(baselineCwd);
|
|
1406
|
+
} catch {
|
|
1407
|
+
stillPresent = false;
|
|
1408
|
+
}
|
|
1409
|
+
let registrationState = worktreeRemoved ? "removed" : "indeterminate";
|
|
1410
|
+
if (!worktreeRemoved) {
|
|
1411
|
+
try {
|
|
1412
|
+
const listResult = await execGit(["worktree", "list", "--porcelain"], {
|
|
1413
|
+
cwd: projectRoot,
|
|
1414
|
+
timeoutMs: 1e4
|
|
1415
|
+
});
|
|
1416
|
+
if (listResult.exitCode === 0) {
|
|
1417
|
+
const resolvedBaseline = path2.resolve(baselineCwd);
|
|
1418
|
+
registrationState = listResult.stdout.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => path2.resolve(line.slice("worktree ".length).trim())).includes(resolvedBaseline) ? "registered" : "removed";
|
|
1419
|
+
}
|
|
1420
|
+
} catch {
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
const residualWarning = buildBaselineWorktreeCleanupWarning({
|
|
1424
|
+
baselineCwd,
|
|
1425
|
+
projectRoot,
|
|
1426
|
+
stillPresent,
|
|
1427
|
+
registrationState
|
|
1428
|
+
});
|
|
1429
|
+
if (residualWarning) {
|
|
1430
|
+
orphanedStashWarning = orphanedStashWarning ? `${orphanedStashWarning} ${residualWarning}` : residualWarning;
|
|
1431
|
+
console.log(
|
|
1432
|
+
chalk3.yellow(
|
|
1433
|
+
" Warning: post-merge verify baseline worktree may not have been fully removed."
|
|
1434
|
+
)
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
try {
|
|
1439
|
+
if (ambientAllowlistedPaths.length > 0) {
|
|
1440
|
+
const stashMessage = `[Poetic Flywheel] post-merge verify allowlist stash ${workflowId}`;
|
|
1441
|
+
const stashResult = await execa4(
|
|
1442
|
+
"git",
|
|
1443
|
+
["stash", "push", "--keep-index", "-m", stashMessage, "--", ...ambientAllowlistedPaths],
|
|
1444
|
+
{
|
|
1445
|
+
cwd: mergeCwd,
|
|
1446
|
+
stdio: "pipe",
|
|
1447
|
+
timeout: 6e4,
|
|
1448
|
+
reject: false
|
|
1449
|
+
}
|
|
1450
|
+
);
|
|
1451
|
+
const stashOutput = `${stashResult.stdout ?? ""}
|
|
1452
|
+
${stashResult.stderr ?? ""}`;
|
|
1453
|
+
if ((stashResult.exitCode ?? 1) !== 0) {
|
|
1454
|
+
throw new Error(
|
|
1455
|
+
"Failed to isolate allowlisted unstaged changes before post-merge verify gate."
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
if (!/No local changes/i.test(stashOutput)) {
|
|
1459
|
+
try {
|
|
1460
|
+
verifyAllowlistStashSha = await resolveStashShaByMessage(mergeCwd, stashMessage);
|
|
1461
|
+
} catch {
|
|
1462
|
+
verifyAllowlistStashSha = void 0;
|
|
1463
|
+
}
|
|
1464
|
+
if (!verifyAllowlistStashSha) {
|
|
1465
|
+
orphanedStashWarning = `Post-merge verify allowlist stash was created but its commit SHA could not be resolved (worktree: ${mergeCwd}). Your allowlisted changes are preserved on the stash stack. Recover with: git -C ${mergeCwd} stash list | grep -F ${JSON.stringify(stashMessage)}`;
|
|
1466
|
+
console.log(
|
|
1467
|
+
chalk3.yellow(
|
|
1468
|
+
" Warning: post-merge verify allowlist stash created but unresolved; preserved on the stash stack."
|
|
1469
|
+
)
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
const prospectiveTreeFingerprint = await captureProspectiveTreeFingerprint(mergeCwd);
|
|
1475
|
+
onVerificationStarted?.();
|
|
1476
|
+
const verifyOutput = await runVerifyCommands({
|
|
1477
|
+
cwd: mergeCwd,
|
|
1478
|
+
commands,
|
|
1479
|
+
baselineCwd
|
|
1480
|
+
});
|
|
1481
|
+
if (verifyOutput.passed) {
|
|
1482
|
+
const { summarizeVerifyCommandRun } = await import("./verify-commands-5F6CJV4B.js");
|
|
1483
|
+
console.log(
|
|
1484
|
+
chalk3.green(` ok ${summarizeVerifyCommandRun(verifyOutput.results, commands.length)}`)
|
|
1485
|
+
);
|
|
1486
|
+
verifyPassed = true;
|
|
1487
|
+
verifiedTreeFingerprint = await finalizeVerifiedTreeFingerprint(
|
|
1488
|
+
mergeCwd,
|
|
1489
|
+
prospectiveTreeFingerprint
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
if (!verifyPassed) {
|
|
1493
|
+
const failedCmd = verifyOutput.results.find((r) => r.exitCode !== 0);
|
|
1494
|
+
const failedLabel = failedCmd?.command ?? "(unknown)";
|
|
1495
|
+
const outputSnippet = failedCmd?.stderrTail || failedCmd?.stdoutTail || "";
|
|
1496
|
+
const matchingStageVerify = failedCmd ? historicalVerifyResults.find(
|
|
1497
|
+
(result) => result.command === failedCmd.command && result.exitCode === 0
|
|
1498
|
+
) : void 0;
|
|
1499
|
+
const baselineDivergenceMessage = failedCmd && matchingStageVerify ? buildPostMergeBaselineDivergenceMessage({
|
|
1500
|
+
command: failedCmd.command,
|
|
1501
|
+
stageResult: matchingStageVerify,
|
|
1502
|
+
mergeResult: failedCmd,
|
|
1503
|
+
stageBaseSha: manifest.baseSha,
|
|
1504
|
+
mergeBaseSha: preMergeHeadSha
|
|
1505
|
+
}) : null;
|
|
1506
|
+
const driftDetection = detectContractDrift(outputSnippet);
|
|
1507
|
+
console.error(chalk3.red("\n Post-merge verify FAILED"));
|
|
1508
|
+
console.error(
|
|
1509
|
+
chalk3.red(" Verify passed on orchestrator-root but failed on squash-merged tree.")
|
|
1510
|
+
);
|
|
1511
|
+
console.error(
|
|
1512
|
+
chalk3.red(` Failed command: ${failedLabel} (exit ${failedCmd?.exitCode ?? 1})`)
|
|
1513
|
+
);
|
|
1514
|
+
if (baselineDivergenceMessage) {
|
|
1515
|
+
console.error(chalk3.yellow(` ${baselineDivergenceMessage}`));
|
|
1516
|
+
}
|
|
1517
|
+
if (driftDetection.isDrift) {
|
|
1518
|
+
console.error(
|
|
1519
|
+
chalk3.yellow(
|
|
1520
|
+
`
|
|
1521
|
+
Failure classification: ${CROSS_SUBSTAGE_CONTRACT_DRIFT}` + (driftDetection.symbols.length > 0 ? ` (symbols: ${driftDetection.symbols.slice(0, 5).join(", ")})` : "")
|
|
1522
|
+
)
|
|
1523
|
+
);
|
|
1524
|
+
console.error(
|
|
1525
|
+
chalk3.yellow(
|
|
1526
|
+
" A sibling substage generated tests asserting an incompatible return shape for a shared symbol."
|
|
1527
|
+
)
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
if (outputSnippet) {
|
|
1531
|
+
const truncated = outputSnippet.split("\n").slice(-20).join("\n");
|
|
1532
|
+
console.error(chalk3.dim(truncated));
|
|
1533
|
+
}
|
|
1534
|
+
console.error(
|
|
1535
|
+
chalk3.yellow(`
|
|
1536
|
+
Integration branch preserved: ${manifest.integrationBranch}`)
|
|
1537
|
+
);
|
|
1538
|
+
console.error(chalk3.yellow(` Workflow: ${workflowId}`));
|
|
1539
|
+
console.error(
|
|
1540
|
+
chalk3.gray(
|
|
1541
|
+
"\n To retry after fixing: poetic flywheel merge-result " + workflowId + "\n To skip this gate: poetic flywheel merge-result " + workflowId + " --skip-verify"
|
|
1542
|
+
)
|
|
1543
|
+
);
|
|
1544
|
+
const baseErrorMessage = driftDetection.isDrift ? formatContractDriftMessage(driftDetection, failedLabel) : `Post-merge verify failed: ${failedLabel} (exit ${failedCmd?.exitCode ?? 1}). Integration branch preserved for debugging.`;
|
|
1545
|
+
throw new Error(
|
|
1546
|
+
baselineDivergenceMessage ? `${baseErrorMessage} ${baselineDivergenceMessage}` : baseErrorMessage
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
} finally {
|
|
1550
|
+
try {
|
|
1551
|
+
await restoreAllowlistStash();
|
|
1552
|
+
} finally {
|
|
1553
|
+
await removeBaselineWorktree();
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
return { orphanedStashWarning, verifiedTreeFingerprint };
|
|
1557
|
+
}
|
|
1558
|
+
function extractVerifyCommandsFromPlan(planData) {
|
|
1559
|
+
if (!isRecord(planData)) return [];
|
|
1560
|
+
const payload = isRecord(planData.payload) ? planData.payload : planData;
|
|
1561
|
+
const plan = isRecord(payload.plan) ? payload.plan : payload;
|
|
1562
|
+
const subStages = Array.isArray(plan.subStages) ? plan.subStages : [];
|
|
1563
|
+
const commands = [];
|
|
1564
|
+
for (const stage of subStages) {
|
|
1565
|
+
if (!isRecord(stage)) continue;
|
|
1566
|
+
if (stage.type !== "verify") continue;
|
|
1567
|
+
const verify = isRecord(stage.verify) ? stage.verify : null;
|
|
1568
|
+
if (!verify) continue;
|
|
1569
|
+
const cmds = Array.isArray(verify.commands) ? verify.commands : [];
|
|
1570
|
+
for (const cmd of cmds) {
|
|
1571
|
+
if (typeof cmd === "string" && cmd.trim().length > 0 && !commands.includes(cmd)) {
|
|
1572
|
+
commands.push(cmd);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
return commands;
|
|
1577
|
+
}
|
|
1578
|
+
function extractVerifyCommandsFromWorkflow(workflow) {
|
|
1579
|
+
const commands = [];
|
|
1580
|
+
const collect = (stage) => {
|
|
1581
|
+
if (stage.type === "verify") {
|
|
1582
|
+
for (const command of stage.verify?.commands ?? []) {
|
|
1583
|
+
if (command.trim().length > 0 && !commands.includes(command)) {
|
|
1584
|
+
commands.push(command);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
for (const subStage of stage.seededPlan?.subStages ?? []) {
|
|
1589
|
+
collect(subStage);
|
|
1590
|
+
}
|
|
1591
|
+
};
|
|
1592
|
+
for (const stage of workflow.stages) collect(stage);
|
|
1593
|
+
return commands;
|
|
1594
|
+
}
|
|
1595
|
+
async function resolvePostMergeVerifyCommands(params) {
|
|
1596
|
+
const { workflowId, manifest, projectRoot, verificationRoot = projectRoot } = params;
|
|
1597
|
+
let commands;
|
|
1598
|
+
let generatedWorkflow = false;
|
|
1599
|
+
if (manifest.planArtifactPath) {
|
|
1600
|
+
try {
|
|
1601
|
+
const planPath = resolvePlanArtifactPath(manifest.planArtifactPath, projectRoot);
|
|
1602
|
+
const raw = await fs2.readFile(planPath, "utf-8");
|
|
1603
|
+
commands = extractVerifyCommandsFromPlan(JSON.parse(raw));
|
|
1604
|
+
} catch (error) {
|
|
1605
|
+
throw new Error(
|
|
1606
|
+
`Post-merge verify gate cannot run: unable to read plan artifact ${manifest.planArtifactPath}. Cause: ${error instanceof Error ? error.message : String(error)}`
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
} else {
|
|
1610
|
+
generatedWorkflow = true;
|
|
1611
|
+
if (!manifest.generatedYamlPath) {
|
|
1612
|
+
throw new Error(
|
|
1613
|
+
`Post-merge verify gate cannot run: workflow ${workflowId} has neither a plan artifact nor generated workflow YAML.`
|
|
1614
|
+
);
|
|
1615
|
+
}
|
|
1616
|
+
try {
|
|
1617
|
+
const workflowPath = path2.isAbsolute(manifest.generatedYamlPath) ? manifest.generatedYamlPath : path2.resolve(projectRoot, manifest.generatedYamlPath);
|
|
1618
|
+
const raw = await fs2.readFile(workflowPath, "utf-8");
|
|
1619
|
+
const parsed = yaml.load(raw);
|
|
1620
|
+
if (isRecord(parsed) && Array.isArray(parsed.stages)) {
|
|
1621
|
+
const explicitCommands = extractVerifyCommandsFromWorkflow(
|
|
1622
|
+
parsed
|
|
1623
|
+
);
|
|
1624
|
+
const rejected = explicitCommands.filter((command) => !isAllowedVerifyCommand(command));
|
|
1625
|
+
if (rejected.length > 0) {
|
|
1626
|
+
throw new Error(
|
|
1627
|
+
"Post-merge verify gate rejected explicit verify command(s): " + rejected.map((command) => JSON.stringify(command)).join(", ")
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
const { WorkflowExecutor } = await import("./flywheel-executor-LTSIZN2L.js");
|
|
1632
|
+
const workflow = await new WorkflowExecutor().loadWorkflow(workflowPath);
|
|
1633
|
+
commands = extractVerifyCommandsFromWorkflow(workflow);
|
|
1634
|
+
} catch (error) {
|
|
1635
|
+
throw new Error(
|
|
1636
|
+
`Post-merge verify gate cannot run: unable to load generated workflow ${manifest.generatedYamlPath}. Cause: ${error instanceof Error ? error.message : String(error)}`
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
if (!generatedWorkflow) {
|
|
1641
|
+
return commands.filter(isAllowedVerifyCommand);
|
|
1642
|
+
}
|
|
1643
|
+
if (commands.length > 0) {
|
|
1644
|
+
const rejected = commands.filter((command) => !isAllowedVerifyCommand(command));
|
|
1645
|
+
if (rejected.length > 0) {
|
|
1646
|
+
throw new Error(
|
|
1647
|
+
"Post-merge verify gate rejected explicit verify command(s): " + rejected.map((command) => JSON.stringify(command)).join(", ")
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
return commands;
|
|
1651
|
+
}
|
|
1652
|
+
return buildProjectVerificationGuidance(verificationRoot).commands.map(({ command }) => command).filter(isAllowedVerifyCommand);
|
|
1653
|
+
}
|
|
1654
|
+
function flywheelMergeCommitSubject(workflowId) {
|
|
1655
|
+
return `[Poetic Flywheel] Merge workflow result ${workflowId}`;
|
|
1656
|
+
}
|
|
1657
|
+
function flywheelApplyTrailers(workflowId, role) {
|
|
1658
|
+
return `Poetic-Workflow-Id: ${workflowId}
|
|
1659
|
+
Poetic-Apply-Role: ${role}`;
|
|
1660
|
+
}
|
|
1661
|
+
function flywheelPrTitle(workflowId) {
|
|
1662
|
+
return `[Flywheel] Merge workflow result ${workflowId}`;
|
|
1663
|
+
}
|
|
1664
|
+
async function reconcileAppliedFromGitLog(params) {
|
|
1665
|
+
const { cwd, workflowId, baseSha, integrationBranch, expectedBranch } = params;
|
|
1666
|
+
const subject = flywheelMergeCommitSubject(workflowId);
|
|
1667
|
+
if (expectedBranch) {
|
|
1668
|
+
const branchResult = await execGit(["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
1669
|
+
cwd,
|
|
1670
|
+
timeoutMs: 1e4
|
|
1671
|
+
});
|
|
1672
|
+
if (branchResult.exitCode !== 0) {
|
|
1673
|
+
return {
|
|
1674
|
+
kind: "ambiguous",
|
|
1675
|
+
reason: `Could not resolve current branch while reconciling appliedAt for ${workflowId}`
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
const currentBranch = branchResult.stdout.trim();
|
|
1679
|
+
if (currentBranch !== expectedBranch) {
|
|
1680
|
+
return {
|
|
1681
|
+
kind: "ambiguous",
|
|
1682
|
+
reason: `Applied-commit reconciliation refused: current branch '${currentBranch}' does not match expected apply target '${expectedBranch}'`
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
const logResult = await execGit(["log", "-1", "--format=%H%x1f%s%x1f%cI%x1f%B", "HEAD"], {
|
|
1687
|
+
cwd,
|
|
1688
|
+
timeoutMs: 3e4
|
|
1689
|
+
});
|
|
1690
|
+
if (logResult.exitCode !== 0) {
|
|
1691
|
+
return { kind: "none" };
|
|
1692
|
+
}
|
|
1693
|
+
const [sha, msgSubject, committedAt, ...bodyParts] = logResult.stdout.replace(/\r?\n$/, "").split("");
|
|
1694
|
+
if (!sha || !committedAt?.trim()) return { kind: "none" };
|
|
1695
|
+
const body = bodyParts.join("");
|
|
1696
|
+
const applyRoleFromBody = (value) => {
|
|
1697
|
+
const workflowIds = [];
|
|
1698
|
+
const roles = [];
|
|
1699
|
+
for (const line of value.split(/\r?\n/)) {
|
|
1700
|
+
if (line.startsWith("Poetic-Workflow-Id: ")) {
|
|
1701
|
+
workflowIds.push(line.slice("Poetic-Workflow-Id: ".length));
|
|
1702
|
+
} else if (line.startsWith("Poetic-Apply-Role: ")) {
|
|
1703
|
+
roles.push(line.slice("Poetic-Apply-Role: ".length));
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
if (workflowIds.length !== 1 || workflowIds[0] !== workflowId || roles.length !== 1) {
|
|
1707
|
+
return void 0;
|
|
1708
|
+
}
|
|
1709
|
+
return roles[0] === "code" || roles[0] === "metadata" ? roles[0] : void 0;
|
|
1710
|
+
};
|
|
1711
|
+
const headRole = applyRoleFromBody(body);
|
|
1712
|
+
const hasApplyTrailerLines = body.split(/\r?\n/).some(
|
|
1713
|
+
(line) => line.startsWith("Poetic-Workflow-Id: ") || line.startsWith("Poetic-Apply-Role: ")
|
|
1714
|
+
);
|
|
1715
|
+
const [parentResult, headTreeResult, integrationTreeResult] = await Promise.all([
|
|
1716
|
+
execGit(["rev-parse", "HEAD^"], { cwd, timeoutMs: 1e4 }),
|
|
1717
|
+
execGit(["rev-parse", "HEAD^{tree}"], { cwd, timeoutMs: 1e4 }),
|
|
1718
|
+
execGit(["rev-parse", `${integrationBranch}^{tree}`], { cwd, timeoutMs: 1e4 })
|
|
1719
|
+
]);
|
|
1720
|
+
if (parentResult.exitCode !== 0 || headTreeResult.exitCode !== 0 || integrationTreeResult.exitCode !== 0) {
|
|
1721
|
+
return { kind: "none" };
|
|
1722
|
+
}
|
|
1723
|
+
const parentSha = parentResult.stdout.trim();
|
|
1724
|
+
if (!hasApplyTrailerLines && msgSubject === subject && parentSha === baseSha && headTreeResult.stdout.trim() === integrationTreeResult.stdout.trim()) {
|
|
1725
|
+
return { kind: "adopt", value: { sha, committedAt: committedAt.trim() } };
|
|
1726
|
+
}
|
|
1727
|
+
if (headRole === "code" && msgSubject === subject && parentSha === baseSha) {
|
|
1728
|
+
const statusResult = await execGit(["status", "--porcelain=v1", "--untracked-files=all"], {
|
|
1729
|
+
cwd,
|
|
1730
|
+
timeoutMs: 1e4
|
|
1731
|
+
});
|
|
1732
|
+
if (statusResult.exitCode !== 0) return { kind: "none" };
|
|
1733
|
+
const hasPendingSystemWrites = parseGitStatusPorcelain(statusResult.stdout).some(
|
|
1734
|
+
(entry) => getStatusEntryPathspecs(entry).some((filePath) => isSystemWritePath(filePath))
|
|
1735
|
+
);
|
|
1736
|
+
if (hasPendingSystemWrites) return { kind: "none" };
|
|
1737
|
+
return { kind: "adopt", value: { sha, committedAt: committedAt.trim() } };
|
|
1738
|
+
}
|
|
1739
|
+
if (headRole !== "metadata" || msgSubject !== "chore: sync task/sprint metadata [flywheel]")
|
|
1740
|
+
return { kind: "none" };
|
|
1741
|
+
if (parentSha === baseSha) {
|
|
1742
|
+
return { kind: "adopt", value: { sha, committedAt: committedAt.trim() } };
|
|
1743
|
+
}
|
|
1744
|
+
const parentDetails = await execGit(["show", "-s", "--format=%P%x1f%s%x1f%B", parentSha], {
|
|
1745
|
+
cwd,
|
|
1746
|
+
timeoutMs: 1e4
|
|
1747
|
+
});
|
|
1748
|
+
if (parentDetails.exitCode !== 0) return { kind: "none" };
|
|
1749
|
+
const [grandparents, parentSubject, ...parentBodyParts] = parentDetails.stdout.replace(/\r?\n$/, "").split("");
|
|
1750
|
+
const parentBody = parentBodyParts.join("");
|
|
1751
|
+
if (grandparents?.trim() !== baseSha || parentSubject !== subject || applyRoleFromBody(parentBody) !== "code") {
|
|
1752
|
+
return { kind: "none" };
|
|
1753
|
+
}
|
|
1754
|
+
return { kind: "adopt", value: { sha, committedAt: committedAt.trim() } };
|
|
1755
|
+
}
|
|
1756
|
+
async function reconcileApplyOnlyStagedEvidence(params) {
|
|
1757
|
+
const { cwd, baseSha, integrationBranch } = params;
|
|
1758
|
+
const headResult = await execGit(["rev-parse", "HEAD"], { cwd, timeoutMs: 1e4 });
|
|
1759
|
+
if (headResult.exitCode !== 0 || headResult.stdout.trim() !== baseSha) {
|
|
1760
|
+
return { kind: "none" };
|
|
1761
|
+
}
|
|
1762
|
+
const stagedQuiet = await execGit(["diff", "--cached", "--quiet"], {
|
|
1763
|
+
cwd,
|
|
1764
|
+
timeoutMs: 3e4
|
|
1765
|
+
});
|
|
1766
|
+
if (stagedQuiet.exitCode === 0) {
|
|
1767
|
+
return { kind: "none" };
|
|
1768
|
+
}
|
|
1769
|
+
if (stagedQuiet.exitCode !== 1) {
|
|
1770
|
+
return {
|
|
1771
|
+
kind: "ambiguous",
|
|
1772
|
+
reason: `Could not inspect staged apply-only evidence (git exit ${stagedQuiet.exitCode})`
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
const indexTree = await execGit(["write-tree"], { cwd, timeoutMs: 3e4 });
|
|
1776
|
+
const integTree = await execGit(["rev-parse", `${integrationBranch}^{tree}`], {
|
|
1777
|
+
cwd,
|
|
1778
|
+
timeoutMs: 1e4
|
|
1779
|
+
});
|
|
1780
|
+
if (indexTree.exitCode !== 0 || integTree.exitCode !== 0) {
|
|
1781
|
+
return { kind: "none" };
|
|
1782
|
+
}
|
|
1783
|
+
const indexSha = indexTree.stdout.trim();
|
|
1784
|
+
const integSha = integTree.stdout.trim();
|
|
1785
|
+
if (!indexSha || !integSha) return { kind: "none" };
|
|
1786
|
+
if (indexSha === integSha) {
|
|
1787
|
+
return { kind: "adopt", value: { stagedTree: indexSha } };
|
|
1788
|
+
}
|
|
1789
|
+
return { kind: "none" };
|
|
1790
|
+
}
|
|
1791
|
+
async function reconcilePrFromGh(params) {
|
|
1792
|
+
const { cwd, workflowId, headBranch, baseBranch } = params;
|
|
1793
|
+
const expectedTitle = flywheelPrTitle(workflowId);
|
|
1794
|
+
const localTipResult = await execGit(["rev-parse", headBranch], { cwd, timeoutMs: 1e4 });
|
|
1795
|
+
if (localTipResult.exitCode !== 0 || !localTipResult.stdout.trim()) return { kind: "none" };
|
|
1796
|
+
const localHeadOid = localTipResult.stdout.trim();
|
|
1797
|
+
let stdout = "";
|
|
1798
|
+
try {
|
|
1799
|
+
const listResult = await execa4(
|
|
1800
|
+
"gh",
|
|
1801
|
+
[
|
|
1802
|
+
"pr",
|
|
1803
|
+
"list",
|
|
1804
|
+
"--head",
|
|
1805
|
+
headBranch,
|
|
1806
|
+
"--state",
|
|
1807
|
+
"open",
|
|
1808
|
+
"--json",
|
|
1809
|
+
"url,number,title,headRefName,headRefOid,baseRefName",
|
|
1810
|
+
"--limit",
|
|
1811
|
+
"20"
|
|
1812
|
+
],
|
|
1813
|
+
{
|
|
1814
|
+
cwd,
|
|
1815
|
+
stdio: "pipe",
|
|
1816
|
+
timeout: 6e4,
|
|
1817
|
+
reject: false
|
|
1818
|
+
}
|
|
1819
|
+
);
|
|
1820
|
+
if ((listResult.exitCode ?? 1) !== 0) {
|
|
1821
|
+
return { kind: "none" };
|
|
1822
|
+
}
|
|
1823
|
+
stdout = typeof listResult.stdout === "string" ? listResult.stdout : String(listResult.stdout ?? "");
|
|
1824
|
+
} catch {
|
|
1825
|
+
return { kind: "none" };
|
|
1826
|
+
}
|
|
1827
|
+
let parsed;
|
|
1828
|
+
try {
|
|
1829
|
+
parsed = JSON.parse(stdout);
|
|
1830
|
+
} catch {
|
|
1831
|
+
return { kind: "none" };
|
|
1832
|
+
}
|
|
1833
|
+
if (!Array.isArray(parsed)) return { kind: "none" };
|
|
1834
|
+
const candidates = [];
|
|
1835
|
+
for (const entry of parsed) {
|
|
1836
|
+
if (!isRecord(entry)) continue;
|
|
1837
|
+
const headRef = typeof entry.headRefName === "string" ? entry.headRefName : void 0;
|
|
1838
|
+
const baseRef = typeof entry.baseRefName === "string" ? entry.baseRefName : void 0;
|
|
1839
|
+
const headRefOid = typeof entry.headRefOid === "string" ? entry.headRefOid : void 0;
|
|
1840
|
+
const title = typeof entry.title === "string" ? entry.title : void 0;
|
|
1841
|
+
const url = typeof entry.url === "string" ? entry.url : void 0;
|
|
1842
|
+
const number = typeof entry.number === "number" ? entry.number : void 0;
|
|
1843
|
+
if (!headRef || !headRefOid || !baseRef || !title || !url || number === void 0 || !Number.isFinite(number))
|
|
1844
|
+
continue;
|
|
1845
|
+
if (headRef !== headBranch) continue;
|
|
1846
|
+
if (headRefOid !== localHeadOid) continue;
|
|
1847
|
+
if (baseRef !== baseBranch) continue;
|
|
1848
|
+
if (title !== expectedTitle) continue;
|
|
1849
|
+
candidates.push({
|
|
1850
|
+
prUrl: url,
|
|
1851
|
+
prNumber: number,
|
|
1852
|
+
prHeadSha: headRefOid
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
if (candidates.length === 0) return { kind: "none" };
|
|
1856
|
+
if (candidates.length > 1) {
|
|
1857
|
+
return {
|
|
1858
|
+
kind: "ambiguous",
|
|
1859
|
+
reason: `Found ${candidates.length} exact open PRs for head '${headBranch}' base '${baseBranch}'; refusing to adopt prUrl under ambiguity`
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
const chosen = candidates[0];
|
|
1863
|
+
return { kind: "adopt", value: chosen };
|
|
1864
|
+
}
|
|
1865
|
+
async function readPrHeadSha(cwd, prUrl) {
|
|
1866
|
+
const result = await execa4("gh", ["pr", "view", prUrl, "--json", "headRefOid"], {
|
|
1867
|
+
cwd,
|
|
1868
|
+
stdio: "pipe",
|
|
1869
|
+
timeout: 6e4,
|
|
1870
|
+
reject: false
|
|
1871
|
+
});
|
|
1872
|
+
if ((result.exitCode ?? 0) !== 0) {
|
|
1873
|
+
throw new Error("Failed to read PR head OID after PR creation");
|
|
1874
|
+
}
|
|
1875
|
+
const parsed = JSON.parse(
|
|
1876
|
+
typeof result.stdout === "string" ? result.stdout : String(result.stdout ?? "")
|
|
1877
|
+
);
|
|
1878
|
+
if (typeof parsed.headRefOid !== "string" || parsed.headRefOid.length === 0) {
|
|
1879
|
+
throw new Error("PR head OID is missing");
|
|
1880
|
+
}
|
|
1881
|
+
return parsed.headRefOid;
|
|
1882
|
+
}
|
|
1883
|
+
async function mergeWorkflowResult(options) {
|
|
1884
|
+
const { workflowId } = options;
|
|
1885
|
+
const mergeCwd = getProjectRoot();
|
|
1886
|
+
const mergeLockPath = path2.join(getArtifactDir(workflowId), ".merge.lock");
|
|
1887
|
+
const legacyLockPath = path2.join(getArtifactDir(workflowId), ".integrate.lock");
|
|
1888
|
+
const thisPid = String(process.pid);
|
|
1889
|
+
function buildLockPayload() {
|
|
1890
|
+
const payload = {
|
|
1891
|
+
pid: process.pid,
|
|
1892
|
+
host: os.hostname(),
|
|
1893
|
+
lockedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1894
|
+
};
|
|
1895
|
+
return JSON.stringify(payload);
|
|
1896
|
+
}
|
|
1897
|
+
async function readLockPid(lockFilePath) {
|
|
1898
|
+
try {
|
|
1899
|
+
const raw = (await fs2.readFile(lockFilePath, "utf-8")).trim();
|
|
1900
|
+
try {
|
|
1901
|
+
const parsed = JSON.parse(raw);
|
|
1902
|
+
if (parsed !== null && typeof parsed === "object" && "pid" in parsed && typeof parsed.pid === "number") {
|
|
1903
|
+
return String(parsed.pid);
|
|
1904
|
+
}
|
|
1905
|
+
} catch {
|
|
1906
|
+
}
|
|
1907
|
+
return raw;
|
|
1908
|
+
} catch {
|
|
1909
|
+
return "";
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
async function breakLocks() {
|
|
1913
|
+
await fs2.unlink(mergeLockPath).catch(() => {
|
|
1914
|
+
});
|
|
1915
|
+
await fs2.unlink(legacyLockPath).catch(() => {
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
let integrationBranchForRecovery;
|
|
1919
|
+
let preMergeHeadSha;
|
|
1920
|
+
let didCommit = false;
|
|
1921
|
+
let mergeMutationStarted = false;
|
|
1922
|
+
let applyMaterialized = false;
|
|
1923
|
+
let currentBranchForRecovery;
|
|
1924
|
+
let didAutoStash = false;
|
|
1925
|
+
let autoStashSha;
|
|
1926
|
+
let dirtyPlanningUnstagedPaths = [];
|
|
1927
|
+
let taskSyncFailures;
|
|
1928
|
+
let followUpFailures;
|
|
1929
|
+
const postMergeWarnings = [];
|
|
1930
|
+
let verifiedTreeFingerprint;
|
|
1931
|
+
let postMergeVerificationStarted = false;
|
|
1932
|
+
let appliedCommitSha;
|
|
1933
|
+
const recordCommitMaterialized = async () => {
|
|
1934
|
+
didCommit = true;
|
|
1935
|
+
applyMaterialized = true;
|
|
1936
|
+
try {
|
|
1937
|
+
const currentHead = await execGit(["rev-parse", "HEAD"], {
|
|
1938
|
+
cwd: mergeCwd,
|
|
1939
|
+
timeoutMs: 3e4
|
|
1940
|
+
});
|
|
1941
|
+
if (currentHead.exitCode === 0 && currentHead.stdout.trim().length > 0) {
|
|
1942
|
+
appliedCommitSha = currentHead.stdout.trim();
|
|
1943
|
+
}
|
|
1944
|
+
} catch {
|
|
1945
|
+
}
|
|
1946
|
+
};
|
|
1947
|
+
let hadPreExistingTrackedPlanningEdits = false;
|
|
1948
|
+
let branchRoleApplyTarget;
|
|
1949
|
+
let mergeLockAcquired = false;
|
|
1950
|
+
let releaseApplyLock;
|
|
1951
|
+
const assertApplyLockOwned = async () => {
|
|
1952
|
+
if (releaseApplyLock) {
|
|
1953
|
+
await releaseApplyLock.assertOwned();
|
|
1954
|
+
}
|
|
1955
|
+
};
|
|
1956
|
+
const acquireMergeLock = async () => {
|
|
1957
|
+
if (options.dryRun || mergeLockAcquired) return;
|
|
1958
|
+
await fs2.mkdir(getArtifactDir(workflowId), { recursive: true });
|
|
1959
|
+
try {
|
|
1960
|
+
await fs2.access(legacyLockPath);
|
|
1961
|
+
if (!options.force) {
|
|
1962
|
+
const existingPid = await readLockPid(legacyLockPath);
|
|
1963
|
+
throw new Error(
|
|
1964
|
+
`Another merge is already in progress (lock file exists).
|
|
1965
|
+
If you're sure it's stale, retry with --force.
|
|
1966
|
+
Lock: ${legacyLockPath}${existingPid ? ` (pid: ${existingPid})` : ""}`
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
await breakLocks();
|
|
1970
|
+
} catch (error) {
|
|
1971
|
+
const code = error.code;
|
|
1972
|
+
if (code && code !== "ENOENT") throw error;
|
|
1973
|
+
}
|
|
1974
|
+
try {
|
|
1975
|
+
await fs2.writeFile(mergeLockPath, buildLockPayload(), { flag: "wx" });
|
|
1976
|
+
mergeLockAcquired = true;
|
|
1977
|
+
} catch (error) {
|
|
1978
|
+
if (error.code !== "EEXIST") throw error;
|
|
1979
|
+
if (!options.force) {
|
|
1980
|
+
const existingPid = await readLockPid(mergeLockPath);
|
|
1981
|
+
throw new Error(
|
|
1982
|
+
`Another merge is already in progress (lock file exists).
|
|
1983
|
+
If you're sure it's stale, retry with --force.
|
|
1984
|
+
Lock: ${mergeLockPath}${existingPid ? ` (pid: ${existingPid})` : ""}`
|
|
1985
|
+
);
|
|
1986
|
+
}
|
|
1987
|
+
await breakLocks();
|
|
1988
|
+
await fs2.writeFile(mergeLockPath, buildLockPayload(), { flag: "wx" });
|
|
1989
|
+
mergeLockAcquired = true;
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
const restoreAutoStash = async () => {
|
|
1993
|
+
if (!didAutoStash) return;
|
|
1994
|
+
didAutoStash = false;
|
|
1995
|
+
const quiet = isQuietOutputRequested();
|
|
1996
|
+
if (!quiet) {
|
|
1997
|
+
console.log(chalk3.cyan(" Restoring auto-stashed changes..."));
|
|
1998
|
+
}
|
|
1999
|
+
if (releaseApplyLock) {
|
|
2000
|
+
try {
|
|
2001
|
+
await releaseApplyLock.assertOwned();
|
|
2002
|
+
} catch (ownershipError) {
|
|
2003
|
+
const detail = ownershipError instanceof Error ? ownershipError.message : String(ownershipError);
|
|
2004
|
+
const warning = `Auto-stashed changes were NOT restored because checkout apply-lock ownership was lost (${detail}). The stash is preserved${autoStashSha ? ` at ${autoStashSha}` : ""}. Recover manually with: git stash list | grep "Poetic Flywheel"`;
|
|
2005
|
+
console.error(chalk3.yellow(` Warning: ${warning}`));
|
|
2006
|
+
postMergeWarnings.push(warning);
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
if (!autoStashSha) {
|
|
2011
|
+
console.error(
|
|
2012
|
+
chalk3.yellow(
|
|
2013
|
+
' Warning: could not identify the auto-stash commit; leaving it stashed. Recover manually with: git stash list | grep "Poetic Flywheel"'
|
|
2014
|
+
)
|
|
2015
|
+
);
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
try {
|
|
2019
|
+
const applyResult = await execa4("git", ["stash", "apply", autoStashSha], {
|
|
2020
|
+
cwd: mergeCwd,
|
|
2021
|
+
stdio: quiet ? "pipe" : "inherit",
|
|
2022
|
+
timeout: 6e4,
|
|
2023
|
+
reject: false
|
|
2024
|
+
});
|
|
2025
|
+
if ((applyResult.exitCode ?? 1) !== 0) {
|
|
2026
|
+
console.error(
|
|
2027
|
+
chalk3.yellow(
|
|
2028
|
+
` Warning: failed to restore auto-stashed changes (git stash apply ${autoStashSha.slice(0, 7)} exited ${applyResult.exitCode ?? "unknown"}); the stash is preserved. Recover manually with: git stash list`
|
|
2029
|
+
)
|
|
2030
|
+
);
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
const listResult = await execa4("git", ["stash", "list", "--format=%gd %H"], {
|
|
2034
|
+
cwd: mergeCwd,
|
|
2035
|
+
stdio: "pipe",
|
|
2036
|
+
timeout: 3e4,
|
|
2037
|
+
reject: false
|
|
2038
|
+
});
|
|
2039
|
+
const matchingRef = listResult.stdout.split("\n").map((line) => line.trim()).find((line) => line.endsWith(` ${autoStashSha}`))?.split(" ")[0];
|
|
2040
|
+
if (matchingRef) {
|
|
2041
|
+
await execa4("git", ["stash", "drop", matchingRef], {
|
|
2042
|
+
cwd: mergeCwd,
|
|
2043
|
+
stdio: "pipe",
|
|
2044
|
+
timeout: 3e4,
|
|
2045
|
+
reject: false
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
} catch (restoreError) {
|
|
2049
|
+
const detail = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
2050
|
+
console.error(
|
|
2051
|
+
chalk3.yellow(
|
|
2052
|
+
` Warning: error while restoring auto-stashed changes (${detail}); the stash is preserved. Recover manually with: git stash list | grep "Poetic Flywheel"`
|
|
2053
|
+
)
|
|
2054
|
+
);
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
try {
|
|
2058
|
+
console.log(chalk3.bold(`
|
|
2059
|
+
Merging Workflow Result: ${workflowId}
|
|
2060
|
+
`));
|
|
2061
|
+
const manifest = await loadManifest(workflowId);
|
|
2062
|
+
integrationBranchForRecovery = manifest.integrationBranch;
|
|
2063
|
+
const status = getManifestStatus(manifest);
|
|
2064
|
+
const branchRoleApplyStatus = (action) => {
|
|
2065
|
+
const sourceBranch = integrationBranchForRecovery ?? null;
|
|
2066
|
+
const sourceBranchRole = sourceBranch ? classifyBranchRole(sourceBranch) : null;
|
|
2067
|
+
const applyTargetBranch = branchRoleApplyTarget ?? null;
|
|
2068
|
+
const applyTargetBranchRole = applyTargetBranch ? classifyBranchRole(applyTargetBranch) : null;
|
|
2069
|
+
const branchRoleOverride = applyTargetBranchRole === "protected" ? options.allowProtected ? "--allow-protected" : options.allowMain ? "--allow-main" : null : null;
|
|
2070
|
+
return {
|
|
2071
|
+
sourceBranch,
|
|
2072
|
+
sourceBranchRole,
|
|
2073
|
+
applyTargetBranch,
|
|
2074
|
+
applyTargetBranchRole,
|
|
2075
|
+
branchRoleAction: action ?? (branchRoleOverride ? "overridden" : "product-fix"),
|
|
2076
|
+
branchRoleOverride,
|
|
2077
|
+
branchRoleRefused: false
|
|
2078
|
+
};
|
|
2079
|
+
};
|
|
2080
|
+
const refuseWithBranchRoleProvenance = async (message) => {
|
|
2081
|
+
console.error(chalk3.red(message));
|
|
2082
|
+
if (!options.dryRun) {
|
|
2083
|
+
try {
|
|
2084
|
+
await updateManifestApplyStatus(workflowId, {
|
|
2085
|
+
sourceBranch: manifest.integrationBranch ?? null,
|
|
2086
|
+
sourceBranchRole: manifest.integrationBranch ? classifyBranchRole(manifest.integrationBranch) : null,
|
|
2087
|
+
applyTargetBranch: branchRoleApplyTarget ?? null,
|
|
2088
|
+
applyTargetBranchRole: branchRoleApplyTarget ? classifyBranchRole(branchRoleApplyTarget) : null,
|
|
2089
|
+
branchRoleAction: "refused",
|
|
2090
|
+
branchRoleRefused: true
|
|
2091
|
+
});
|
|
2092
|
+
} catch {
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
return { success: false, workflowId, error: message };
|
|
2096
|
+
};
|
|
2097
|
+
const assertCurrentBranchStillMatchesApplyTarget = async () => {
|
|
2098
|
+
if (!branchRoleApplyTarget) return;
|
|
2099
|
+
const liveBranch = await getCurrentBranch(mergeCwd);
|
|
2100
|
+
if (liveBranch !== branchRoleApplyTarget) {
|
|
2101
|
+
throw new Error(
|
|
2102
|
+
`Apply target branch changed during merge-result: expected ${branchRoleApplyTarget}, found ${liveBranch}`
|
|
2103
|
+
);
|
|
2104
|
+
}
|
|
2105
|
+
assertBranchAllowsCodeApply({
|
|
2106
|
+
allowMain: options.allowMain,
|
|
2107
|
+
allowProtected: options.allowProtected,
|
|
2108
|
+
command: `flywheel merge-result ${workflowId}`,
|
|
2109
|
+
cwd: mergeCwd,
|
|
2110
|
+
currentBranch: liveBranch
|
|
2111
|
+
});
|
|
2112
|
+
};
|
|
2113
|
+
const integrationBranchRole = classifyBranchRole(manifest.integrationBranch);
|
|
2114
|
+
if (integrationBranchRole !== "generated-artifact") {
|
|
2115
|
+
return await refuseWithBranchRoleProvenance(
|
|
2116
|
+
`Refusing to merge workflow result from non-generated branch ${manifest.integrationBranch} (role: ${integrationBranchRole})`
|
|
2117
|
+
);
|
|
2118
|
+
}
|
|
2119
|
+
await acquireMergeLock();
|
|
2120
|
+
if (status === "applied" && !options.force) {
|
|
2121
|
+
console.log(chalk3.yellow(` Already merged on ${manifest.appliedAt}`));
|
|
2122
|
+
console.log(chalk3.yellow(" Use --force to re-merge"));
|
|
2123
|
+
if (!options.dryRun) {
|
|
2124
|
+
return { success: true, workflowId, alreadyMerged: true };
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
if (!options.dryRun && !options.pr && !manifest.appliedAt) {
|
|
2128
|
+
const appliedReconcile = await reconcileAppliedFromGitLog({
|
|
2129
|
+
cwd: mergeCwd,
|
|
2130
|
+
workflowId,
|
|
2131
|
+
baseSha: manifest.baseSha,
|
|
2132
|
+
integrationBranch: manifest.integrationBranch,
|
|
2133
|
+
expectedBranch: manifest.applyTargetBranch ?? void 0
|
|
2134
|
+
});
|
|
2135
|
+
if (appliedReconcile.kind === "ambiguous") {
|
|
2136
|
+
return {
|
|
2137
|
+
success: false,
|
|
2138
|
+
workflowId,
|
|
2139
|
+
error: `Cannot reconcile applied state: ${appliedReconcile.reason}`
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
if (appliedReconcile.kind === "adopt") {
|
|
2143
|
+
console.log(
|
|
2144
|
+
chalk3.yellow(
|
|
2145
|
+
` Reconciled appliedAt from existing merge commit ${appliedReconcile.value.sha.slice(0, 7)}`
|
|
2146
|
+
)
|
|
2147
|
+
);
|
|
2148
|
+
try {
|
|
2149
|
+
await updateManifestApplyStatus(workflowId, {
|
|
2150
|
+
appliedAt: appliedReconcile.value.committedAt,
|
|
2151
|
+
appliedBy: await getGitUsername(),
|
|
2152
|
+
...branchRoleApplyStatus()
|
|
2153
|
+
});
|
|
2154
|
+
} catch {
|
|
2155
|
+
}
|
|
2156
|
+
return {
|
|
2157
|
+
success: true,
|
|
2158
|
+
workflowId,
|
|
2159
|
+
alreadyMerged: true,
|
|
2160
|
+
applied: true
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
if (!options.dryRun && !options.force && options.applyOnly && status === "staged" && manifest.stagedAt) {
|
|
2165
|
+
console.log(chalk3.yellow(` Already staged on ${manifest.stagedAt}`));
|
|
2166
|
+
console.log(chalk3.yellow(" Use --force to re-stage"));
|
|
2167
|
+
return { success: true, workflowId, alreadyStaged: true };
|
|
2168
|
+
}
|
|
2169
|
+
if (!options.dryRun && options.applyOnly && !manifest.stagedAt) {
|
|
2170
|
+
const stagedReconcile = await reconcileApplyOnlyStagedEvidence({
|
|
2171
|
+
cwd: mergeCwd,
|
|
2172
|
+
baseSha: manifest.baseSha,
|
|
2173
|
+
integrationBranch: manifest.integrationBranch
|
|
2174
|
+
});
|
|
2175
|
+
if (stagedReconcile.kind === "adopt") {
|
|
2176
|
+
console.log(
|
|
2177
|
+
chalk3.yellow(
|
|
2178
|
+
` Reconciled stagedAt from exact apply-only index residue (tree ${stagedReconcile.value.stagedTree.slice(0, 7)})`
|
|
2179
|
+
)
|
|
2180
|
+
);
|
|
2181
|
+
try {
|
|
2182
|
+
await updateManifestApplyStatus(workflowId, {
|
|
2183
|
+
...buildApplyOnlyProofStatus(stagedReconcile.value.stagedTree),
|
|
2184
|
+
...branchRoleApplyStatus()
|
|
2185
|
+
});
|
|
2186
|
+
} catch {
|
|
2187
|
+
}
|
|
2188
|
+
return { success: true, workflowId, alreadyStaged: true };
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
if (status === "failed" && manifest.applyError) {
|
|
2192
|
+
console.log(chalk3.red(" Previous merge failed:"));
|
|
2193
|
+
console.log(chalk3.red(` ${manifest.applyError}`));
|
|
2194
|
+
if (!options.dryRun) {
|
|
2195
|
+
console.log(chalk3.yellow(" Use --force to retry"));
|
|
2196
|
+
if (!options.force) {
|
|
2197
|
+
return { success: false, workflowId, error: manifest.applyError };
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
if (!options.dryRun && !options.pr) {
|
|
2202
|
+
const { stdout: branchStdout } = await execa4("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
2203
|
+
cwd: mergeCwd,
|
|
2204
|
+
stdio: "pipe",
|
|
2205
|
+
timeout: 1e4
|
|
2206
|
+
});
|
|
2207
|
+
const currentBranch2 = branchStdout.trim();
|
|
2208
|
+
branchRoleApplyTarget = currentBranch2;
|
|
2209
|
+
try {
|
|
2210
|
+
assertBranchAllowsCodeApply({
|
|
2211
|
+
allowMain: options.allowMain,
|
|
2212
|
+
allowProtected: options.allowProtected,
|
|
2213
|
+
command: `flywheel merge-result ${workflowId}`,
|
|
2214
|
+
cwd: mergeCwd,
|
|
2215
|
+
currentBranch: currentBranch2
|
|
2216
|
+
});
|
|
2217
|
+
} catch (error) {
|
|
2218
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2219
|
+
const validationDryRunHint = message.includes("validation branch") ? `
|
|
2220
|
+
|
|
2221
|
+
Preview safely from this validation branch with:
|
|
2222
|
+
poetic flywheel merge-result ${workflowId} --dry-run` : "";
|
|
2223
|
+
return await refuseWithBranchRoleProvenance(`${message}${validationDryRunHint}`);
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
const branches = await listFlywheelBranches({ testOnly: false });
|
|
2227
|
+
const branchExists = branches.some((b) => b.name === manifest.integrationBranch);
|
|
2228
|
+
if (!branchExists) {
|
|
2229
|
+
if (options.dryRun && !options.force) {
|
|
2230
|
+
if (status === "applied") {
|
|
2231
|
+
console.log(chalk3.dim(" Run branch already cleaned up; no merge preview available."));
|
|
2232
|
+
return { success: true, workflowId, alreadyMerged: true };
|
|
2233
|
+
}
|
|
2234
|
+
if (status === "failed" && manifest.applyError) {
|
|
2235
|
+
console.log(chalk3.dim(" Run branch already cleaned up; no failure preview available."));
|
|
2236
|
+
return { success: true, workflowId };
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
throw new Error(
|
|
2240
|
+
`Run branch not found: ${manifest.integrationBranch}
|
|
2241
|
+
The branch may have been deleted. Cannot merge.`
|
|
2242
|
+
);
|
|
2243
|
+
}
|
|
2244
|
+
const workflowGateSummary = await getWorkflowGateSummary(workflowId);
|
|
2245
|
+
if (options.pr) {
|
|
2246
|
+
const warnings2 = [getAutomergeDeprecationWarning()].filter(
|
|
2247
|
+
(warning) => Boolean(warning)
|
|
2248
|
+
);
|
|
2249
|
+
if (options.dryRun) {
|
|
2250
|
+
console.log(chalk3.cyan(" Would create PR for workflow result\n"));
|
|
2251
|
+
console.log(chalk3.dim(" No changes made (--dry-run)"));
|
|
2252
|
+
return { success: true, workflowId };
|
|
2253
|
+
}
|
|
2254
|
+
if (manifest.prUrl && !options.force) {
|
|
2255
|
+
const localHeadResult = await execGit(["rev-parse", manifest.integrationBranch], {
|
|
2256
|
+
cwd: mergeCwd,
|
|
2257
|
+
timeoutMs: 1e4
|
|
2258
|
+
});
|
|
2259
|
+
const localHeadSha = localHeadResult.stdout.trim();
|
|
2260
|
+
if (localHeadResult.exitCode !== 0 || localHeadSha.length === 0) {
|
|
2261
|
+
return {
|
|
2262
|
+
success: false,
|
|
2263
|
+
workflowId,
|
|
2264
|
+
error: "Failed to capture source branch HEAD for recorded PR verification"
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
let prHeadSha;
|
|
2268
|
+
try {
|
|
2269
|
+
prHeadSha = await readPrHeadSha(mergeCwd, manifest.prUrl);
|
|
2270
|
+
} catch (error) {
|
|
2271
|
+
return {
|
|
2272
|
+
success: false,
|
|
2273
|
+
workflowId,
|
|
2274
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
if (prHeadSha !== localHeadSha) {
|
|
2278
|
+
return {
|
|
2279
|
+
success: false,
|
|
2280
|
+
workflowId,
|
|
2281
|
+
error: `PR head mismatch: expected ${localHeadSha}, found ${prHeadSha}`
|
|
2282
|
+
};
|
|
2283
|
+
}
|
|
2284
|
+
await updateManifestApplyStatus(workflowId, { prHeadSha });
|
|
2285
|
+
console.log(chalk3.yellow(` PR already recorded: ${manifest.prUrl}`));
|
|
2286
|
+
return {
|
|
2287
|
+
success: true,
|
|
2288
|
+
workflowId,
|
|
2289
|
+
prUrl: manifest.prUrl,
|
|
2290
|
+
prNumber: manifest.prNumber,
|
|
2291
|
+
prHeadSha,
|
|
2292
|
+
degraded: warnings2.length > 0,
|
|
2293
|
+
warnings: warnings2.length > 0 ? warnings2 : void 0
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
console.log(chalk3.cyan(" Creating PR for workflow result..."));
|
|
2297
|
+
const prBaseBranch = options.baseBranch ?? await getCurrentBranch(mergeCwd);
|
|
2298
|
+
branchRoleApplyTarget = prBaseBranch;
|
|
2299
|
+
const prBaseRole = classifyBranchRole(prBaseBranch);
|
|
2300
|
+
if (prBaseRole === "detached" || prBaseRole === "local-scratch" || isDisposableBranchRole(prBaseRole)) {
|
|
2301
|
+
return await refuseWithBranchRoleProvenance(
|
|
2302
|
+
`Refusing to create PR targeting ${prBaseRole} branch '${prBaseBranch}'. Choose a product or protected base branch with --base.`
|
|
2303
|
+
);
|
|
2304
|
+
}
|
|
2305
|
+
const sourceHeadResult = await execGit(["rev-parse", manifest.integrationBranch], {
|
|
2306
|
+
cwd: mergeCwd,
|
|
2307
|
+
timeoutMs: 1e4
|
|
2308
|
+
});
|
|
2309
|
+
const sourceHeadSha = sourceHeadResult.stdout.trim();
|
|
2310
|
+
if (sourceHeadResult.exitCode !== 0 || sourceHeadSha.length === 0) {
|
|
2311
|
+
return {
|
|
2312
|
+
success: false,
|
|
2313
|
+
workflowId,
|
|
2314
|
+
error: `Failed to capture source branch HEAD before push: ${(sourceHeadResult.stderr ?? "").trim() || "unknown git error"}`
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
try {
|
|
2318
|
+
const pushResult = await execGit(["push", "origin", manifest.integrationBranch], {
|
|
2319
|
+
cwd: mergeCwd,
|
|
2320
|
+
timeoutMs: 6e4
|
|
2321
|
+
});
|
|
2322
|
+
if (pushResult.exitCode !== 0) {
|
|
2323
|
+
throw new Error((pushResult.stderr ?? "").trim() || "git push failed");
|
|
2324
|
+
}
|
|
2325
|
+
} catch (error) {
|
|
2326
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2327
|
+
return {
|
|
2328
|
+
success: false,
|
|
2329
|
+
workflowId,
|
|
2330
|
+
error: `Failed to push run branch to origin: ${errorMsg}`
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
{
|
|
2334
|
+
const existingPr = await reconcilePrFromGh({
|
|
2335
|
+
cwd: mergeCwd,
|
|
2336
|
+
workflowId,
|
|
2337
|
+
headBranch: manifest.integrationBranch,
|
|
2338
|
+
baseBranch: prBaseBranch
|
|
2339
|
+
});
|
|
2340
|
+
if (existingPr.kind === "ambiguous") {
|
|
2341
|
+
return {
|
|
2342
|
+
success: false,
|
|
2343
|
+
workflowId,
|
|
2344
|
+
error: `Cannot reconcile PR state: ${existingPr.reason}`
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
if (existingPr.kind === "adopt") {
|
|
2348
|
+
console.log(chalk3.yellow(` Reconciled existing open PR: ${existingPr.value.prUrl}`));
|
|
2349
|
+
if (existingPr.value.prHeadSha !== sourceHeadSha) {
|
|
2350
|
+
return {
|
|
2351
|
+
success: false,
|
|
2352
|
+
workflowId,
|
|
2353
|
+
error: `PR head mismatch: expected ${sourceHeadSha}, found ${existingPr.value.prHeadSha}`
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
await updateManifestApplyStatus(workflowId, {
|
|
2357
|
+
prUrl: existingPr.value.prUrl,
|
|
2358
|
+
prNumber: existingPr.value.prNumber,
|
|
2359
|
+
prHeadSha: existingPr.value.prHeadSha,
|
|
2360
|
+
applyError: null,
|
|
2361
|
+
...branchRoleApplyStatus("pr-created")
|
|
2362
|
+
});
|
|
2363
|
+
return {
|
|
2364
|
+
success: true,
|
|
2365
|
+
degraded: warnings2.length > 0,
|
|
2366
|
+
workflowId,
|
|
2367
|
+
prUrl: existingPr.value.prUrl,
|
|
2368
|
+
prNumber: existingPr.value.prNumber,
|
|
2369
|
+
prHeadSha: existingPr.value.prHeadSha,
|
|
2370
|
+
warnings: warnings2.length > 0 ? warnings2 : void 0
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
const prTitle = flywheelPrTitle(workflowId);
|
|
2375
|
+
const observabilityLines = [];
|
|
2376
|
+
const stageCount = (manifest.stageIds ?? []).length;
|
|
2377
|
+
if (stageCount > 0) {
|
|
2378
|
+
observabilityLines.push(`| Stages | ${stageCount} |`);
|
|
2379
|
+
}
|
|
2380
|
+
if (workflowGateSummary) {
|
|
2381
|
+
observabilityLines.push(
|
|
2382
|
+
`| Workflow gate | ${workflowGateSummary.passed ? "pass" : "fail"} |`
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
const observabilitySection = observabilityLines.length > 0 ? `
|
|
2386
|
+
|
|
2387
|
+
### Observability
|
|
2388
|
+
|
|
2389
|
+
| Field | Value |
|
|
2390
|
+
|---|---|
|
|
2391
|
+
${observabilityLines.join("\n")}` : "";
|
|
2392
|
+
const prBody = `## Workflow Result
|
|
2393
|
+
|
|
2394
|
+
**Source**: ${manifest.sourceType}${manifest.sprintId ? ` (${manifest.sprintId})` : ""}
|
|
2395
|
+
**Branch**: ${manifest.integrationBranch}
|
|
2396
|
+
**Base**: ${manifest.baseSha.slice(0, 7)}
|
|
2397
|
+
**Executed**: ${manifest.executedAt}
|
|
2398
|
+
**Executed by**: ${manifest.executedBy}${observabilitySection}
|
|
2399
|
+
|
|
2400
|
+
This PR contains the result of a Flywheel workflow execution.
|
|
2401
|
+
It will go through Quality Gate validation (CI tests, auto-fix, etc.) before merging.
|
|
2402
|
+
|
|
2403
|
+
---
|
|
2404
|
+
*Generated by Poetic Flywheel*`;
|
|
2405
|
+
try {
|
|
2406
|
+
const prResult = await execa4(
|
|
2407
|
+
"gh",
|
|
2408
|
+
[
|
|
2409
|
+
"pr",
|
|
2410
|
+
"create",
|
|
2411
|
+
"--title",
|
|
2412
|
+
prTitle,
|
|
2413
|
+
"--body",
|
|
2414
|
+
prBody,
|
|
2415
|
+
"--head",
|
|
2416
|
+
manifest.integrationBranch,
|
|
2417
|
+
"--base",
|
|
2418
|
+
prBaseBranch
|
|
2419
|
+
],
|
|
2420
|
+
{
|
|
2421
|
+
stdio: "pipe",
|
|
2422
|
+
timeout: 6e4
|
|
2423
|
+
}
|
|
2424
|
+
);
|
|
2425
|
+
const stdout = typeof prResult.stdout === "string" ? prResult.stdout : String(prResult.stdout);
|
|
2426
|
+
const prUrl = stdout.match(/https:\/\/github\.com[^\s]+/)?.[0]?.trim() ?? "";
|
|
2427
|
+
if (!prUrl) {
|
|
2428
|
+
throw new Error("Failed to extract PR URL from gh CLI output");
|
|
2429
|
+
}
|
|
2430
|
+
const prNumber = parseInt(prUrl.split("/").pop() || "0", 10);
|
|
2431
|
+
console.log(chalk3.green(` PR created: ${prUrl}`));
|
|
2432
|
+
const prHeadSha = await readPrHeadSha(mergeCwd, prUrl);
|
|
2433
|
+
if (prHeadSha !== sourceHeadSha) {
|
|
2434
|
+
return {
|
|
2435
|
+
success: false,
|
|
2436
|
+
workflowId,
|
|
2437
|
+
error: `PR head mismatch: expected ${sourceHeadSha}, found ${prHeadSha}`
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2440
|
+
await updateManifestApplyStatus(workflowId, {
|
|
2441
|
+
prUrl,
|
|
2442
|
+
prNumber: Number.isFinite(prNumber) && prNumber > 0 ? prNumber : void 0,
|
|
2443
|
+
prHeadSha,
|
|
2444
|
+
applyError: null,
|
|
2445
|
+
...branchRoleApplyStatus("pr-created")
|
|
2446
|
+
});
|
|
2447
|
+
return {
|
|
2448
|
+
success: true,
|
|
2449
|
+
degraded: warnings2.length > 0,
|
|
2450
|
+
workflowId,
|
|
2451
|
+
prUrl,
|
|
2452
|
+
prNumber: prNumber > 0 ? prNumber : void 0,
|
|
2453
|
+
prHeadSha,
|
|
2454
|
+
warnings: warnings2.length > 0 ? warnings2 : void 0
|
|
2455
|
+
};
|
|
2456
|
+
} catch (error) {
|
|
2457
|
+
const existingAfterCreate = await reconcilePrFromGh({
|
|
2458
|
+
cwd: mergeCwd,
|
|
2459
|
+
workflowId,
|
|
2460
|
+
headBranch: manifest.integrationBranch,
|
|
2461
|
+
baseBranch: prBaseBranch
|
|
2462
|
+
});
|
|
2463
|
+
if (existingAfterCreate.kind === "adopt") {
|
|
2464
|
+
console.log(
|
|
2465
|
+
chalk3.yellow(
|
|
2466
|
+
` PR create failed but exact open PR exists; adopting ${existingAfterCreate.value.prUrl}`
|
|
2467
|
+
)
|
|
2468
|
+
);
|
|
2469
|
+
if (existingAfterCreate.value.prHeadSha !== sourceHeadSha) {
|
|
2470
|
+
return {
|
|
2471
|
+
success: false,
|
|
2472
|
+
workflowId,
|
|
2473
|
+
error: `PR head mismatch: expected ${sourceHeadSha}, found ${existingAfterCreate.value.prHeadSha}`
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
await updateManifestApplyStatus(workflowId, {
|
|
2477
|
+
prUrl: existingAfterCreate.value.prUrl,
|
|
2478
|
+
prNumber: existingAfterCreate.value.prNumber,
|
|
2479
|
+
prHeadSha: existingAfterCreate.value.prHeadSha,
|
|
2480
|
+
applyError: null,
|
|
2481
|
+
...branchRoleApplyStatus("pr-created")
|
|
2482
|
+
});
|
|
2483
|
+
return {
|
|
2484
|
+
success: true,
|
|
2485
|
+
degraded: warnings2.length > 0,
|
|
2486
|
+
workflowId,
|
|
2487
|
+
prUrl: existingAfterCreate.value.prUrl,
|
|
2488
|
+
prNumber: existingAfterCreate.value.prNumber,
|
|
2489
|
+
prHeadSha: existingAfterCreate.value.prHeadSha,
|
|
2490
|
+
warnings: warnings2.length > 0 ? warnings2 : void 0
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
if (existingAfterCreate.kind === "ambiguous") {
|
|
2494
|
+
return {
|
|
2495
|
+
success: false,
|
|
2496
|
+
workflowId,
|
|
2497
|
+
error: `Cannot reconcile PR state: ${existingAfterCreate.reason}`
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2501
|
+
let hint;
|
|
2502
|
+
if (errorMsg.includes("command not found") || errorMsg.includes("ENOENT")) {
|
|
2503
|
+
hint = "Install gh CLI: https://cli.github.com/";
|
|
2504
|
+
} else if (errorMsg.includes("unable to access") || errorMsg.includes("Could not resolve host") || errorMsg.includes("rate limit") || errorMsg.includes("timed out")) {
|
|
2505
|
+
hint = "Network or rate-limit error; check connectivity and retry";
|
|
2506
|
+
} else {
|
|
2507
|
+
hint = "Ensure gh CLI is authenticated: gh auth login";
|
|
2508
|
+
}
|
|
2509
|
+
const err = `Failed to create PR: ${errorMsg}
|
|
2510
|
+
${hint}`;
|
|
2511
|
+
return { success: false, workflowId, error: err };
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
if (options.dryRun) {
|
|
2515
|
+
console.log(chalk3.cyan(" Merge Preview:\n"));
|
|
2516
|
+
const { stdout: diffStat } = await execa4(
|
|
2517
|
+
"git",
|
|
2518
|
+
["diff", "--stat", `${manifest.baseSha}..${manifest.integrationBranch}`],
|
|
2519
|
+
{ stdio: "pipe", timeout: 3e4 }
|
|
2520
|
+
);
|
|
2521
|
+
console.log(diffStat);
|
|
2522
|
+
console.log("");
|
|
2523
|
+
console.log(chalk3.dim(" No changes made (--dry-run)"));
|
|
2524
|
+
return { success: true, workflowId };
|
|
2525
|
+
}
|
|
2526
|
+
const allowlistPrefixes = getPlanningDirtyAllowlistPrefixes(mergeCwd);
|
|
2527
|
+
const { stdout: currentBranchStdout } = await execa4(
|
|
2528
|
+
"git",
|
|
2529
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
2530
|
+
{ cwd: mergeCwd, stdio: "pipe", timeout: 3e4 }
|
|
2531
|
+
);
|
|
2532
|
+
const currentBranch = currentBranchStdout.trim();
|
|
2533
|
+
currentBranchForRecovery = currentBranch;
|
|
2534
|
+
if (currentBranch === "HEAD") {
|
|
2535
|
+
throw new Error("Cannot merge in detached HEAD state. Checkout a branch and retry.");
|
|
2536
|
+
}
|
|
2537
|
+
if (currentBranch === manifest.integrationBranch) {
|
|
2538
|
+
throw new Error(
|
|
2539
|
+
`You are currently on the run branch (${manifest.integrationBranch}).
|
|
2540
|
+
Checkout the target branch and re-run merge.`
|
|
2541
|
+
);
|
|
2542
|
+
}
|
|
2543
|
+
const { stdout: headShaStdout } = await execa4("git", ["rev-parse", "HEAD"], {
|
|
2544
|
+
cwd: mergeCwd,
|
|
2545
|
+
stdio: "pipe",
|
|
2546
|
+
timeout: 3e4
|
|
2547
|
+
});
|
|
2548
|
+
const headSha = headShaStdout.trim();
|
|
2549
|
+
preMergeHeadSha = headSha;
|
|
2550
|
+
if (headSha !== manifest.baseSha) {
|
|
2551
|
+
const msg = `Current HEAD (${headSha.slice(0, 7)}) does not match workflow base (${manifest.baseSha.slice(0, 7)}).`;
|
|
2552
|
+
if (!options.force) {
|
|
2553
|
+
throw new Error(
|
|
2554
|
+
`${msg}
|
|
2555
|
+
For safest merge, checkout the base commit/branch and retry.
|
|
2556
|
+
Use --force to merge anyway.`
|
|
2557
|
+
);
|
|
2558
|
+
}
|
|
2559
|
+
console.log(chalk3.yellow(` Warning: ${msg}`));
|
|
2560
|
+
}
|
|
2561
|
+
if (!options.skipVerify) {
|
|
2562
|
+
const blocking = workflowGateSummary !== null ? workflowGateSummary.passed === false ? workflowGateSummary.blockingStageIds : [] : (await getWorkflowVerificationGateStatus(workflowId))?.filter((s) => s.winnerNeedsRepair || !s.verificationPassed).map((s) => s.stageId) ?? [];
|
|
2563
|
+
if (workflowGateSummary?.passed === false || blocking.length > 0) {
|
|
2564
|
+
const blockingLabelSource = workflowGateSummary?.reason ?? "Workflow gate failed";
|
|
2565
|
+
const lines = blocking.slice(0, 6).map((stageId) => `- ${stageId}`).join("\n");
|
|
2566
|
+
const verificationPolicy = manifest.verificationPolicy;
|
|
2567
|
+
const whySection = (workflowGateSummary?.needsRepairCount ?? 0) > 0 ? `Why did this happen?
|
|
2568
|
+
Automatic repair was either disabled (--no-repair-on-findings) or failed to resolve all issues.
|
|
2569
|
+
|
|
2570
|
+
RECOMMENDED: Re-run with automatic repair enabled (default behavior)
|
|
2571
|
+
This will allow the system to automatically fix verification findings:
|
|
2572
|
+
|
|
2573
|
+
poetic sprint run --apply
|
|
2574
|
+
# Defaults: --verification-policy findings (non-blocking) with repair enabled
|
|
2575
|
+
# The winner will automatically attempt to fix verification findings
|
|
2576
|
+
|
|
2577
|
+
ALTERNATIVE: Manually fix on the run branch
|
|
2578
|
+
If you prefer manual control:
|
|
2579
|
+
|
|
2580
|
+
git checkout ${manifest.integrationBranch}
|
|
2581
|
+
# Review findings, fix issues, and commit changes
|
|
2582
|
+
poetic flywheel merge-result ${workflowId}
|
|
2583
|
+
|
|
2584
|
+
` : verificationPolicy === "blocking" ? `Why did this happen?
|
|
2585
|
+
Verification failed in blocking mode (--verification-policy blocking).
|
|
2586
|
+
In blocking mode, failing candidates are disqualified and the workflow gate stays closed.
|
|
2587
|
+
|
|
2588
|
+
RECOMMENDED: Re-run with non-blocking verification (default)
|
|
2589
|
+
This allows repair to fix issues automatically:
|
|
2590
|
+
|
|
2591
|
+
poetic sprint run --apply
|
|
2592
|
+
# Defaults: --verification-policy findings with repair enabled
|
|
2593
|
+
|
|
2594
|
+
ALTERNATIVE: Manually fix verification failures
|
|
2595
|
+
git checkout ${manifest.integrationBranch}
|
|
2596
|
+
# Review findings, fix issues, and commit changes
|
|
2597
|
+
poetic flywheel merge-result ${workflowId}
|
|
2598
|
+
|
|
2599
|
+
` : `Why did this happen?
|
|
2600
|
+
Verification failed during execution and the workflow was recorded as not mergeable.
|
|
2601
|
+
Repair was not sufficient to clear the gate.
|
|
2602
|
+
|
|
2603
|
+
RECOMMENDED: Re-run with automatic repair enabled (default behavior)
|
|
2604
|
+
This allows the system to automatically fix verification issues:
|
|
2605
|
+
|
|
2606
|
+
poetic sprint run --apply
|
|
2607
|
+
# Defaults: --verification-policy findings with repair enabled
|
|
2608
|
+
|
|
2609
|
+
ALTERNATIVE: Manually fix verification failures
|
|
2610
|
+
git checkout ${manifest.integrationBranch}
|
|
2611
|
+
# Review findings, fix issues, and commit changes
|
|
2612
|
+
poetic flywheel merge-result ${workflowId}
|
|
2613
|
+
|
|
2614
|
+
`;
|
|
2615
|
+
throw new Error(
|
|
2616
|
+
"Cannot apply workflow result: " + blockingLabelSource + "\n" + (lines ? `
|
|
2617
|
+
Blocking stages:
|
|
2618
|
+
${lines}
|
|
2619
|
+
` : "") + "\n" + whySection + `OVERRIDE (not recommended): Skip verification gate
|
|
2620
|
+
Only use this if you understand the risks:
|
|
2621
|
+
|
|
2622
|
+
poetic flywheel merge-result ${workflowId} --skip-verify
|
|
2623
|
+
|
|
2624
|
+
Learn more: Run 'poetic sprint run --help' to see all verification and repair options.`
|
|
2625
|
+
);
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
if (!options.dryRun) {
|
|
2629
|
+
releaseApplyLock = await acquireFlywheelApplyLock(getProjectRoot());
|
|
2630
|
+
await assertApplyLockOwned();
|
|
2631
|
+
}
|
|
2632
|
+
if (!options.dryRun) {
|
|
2633
|
+
const { stdout: lockedHeadStdout } = await execa4("git", ["rev-parse", "HEAD"], {
|
|
2634
|
+
cwd: mergeCwd,
|
|
2635
|
+
stdio: "pipe",
|
|
2636
|
+
timeout: 3e4
|
|
2637
|
+
});
|
|
2638
|
+
const lockedHeadSha = lockedHeadStdout.trim();
|
|
2639
|
+
if (lockedHeadSha !== preMergeHeadSha) {
|
|
2640
|
+
if (!options.force && lockedHeadSha !== manifest.baseSha) {
|
|
2641
|
+
throw new Error(
|
|
2642
|
+
`Apply target advanced while waiting for the merge lock (now ${lockedHeadSha.slice(0, 7)}, workflow base ${manifest.baseSha.slice(0, 7)}).
|
|
2643
|
+
Another merge landed first. Re-run merge from the current base, or use --force.`
|
|
2644
|
+
);
|
|
2645
|
+
}
|
|
2646
|
+
preMergeHeadSha = lockedHeadSha;
|
|
2647
|
+
}
|
|
2648
|
+
await assertCurrentBranchStillMatchesApplyTarget();
|
|
2649
|
+
}
|
|
2650
|
+
{
|
|
2651
|
+
const { stdout: statusOutput } = await execa4("git", ["status", "--porcelain=v1"], {
|
|
2652
|
+
cwd: mergeCwd,
|
|
2653
|
+
stdio: "pipe",
|
|
2654
|
+
timeout: 3e4
|
|
2655
|
+
});
|
|
2656
|
+
const entries = parseGitStatusPorcelain(statusOutput);
|
|
2657
|
+
const dirtyEntries = entries.filter(
|
|
2658
|
+
(e) => !getStatusEntryPathspecs(e).every((pathname) => isWorktreeRunSentinelPath(pathname))
|
|
2659
|
+
);
|
|
2660
|
+
const dirtyPlanning = dirtyEntries.filter(
|
|
2661
|
+
(e) => isStatusEntryInAllowlist(e, allowlistPrefixes)
|
|
2662
|
+
);
|
|
2663
|
+
const dirtyOther = dirtyEntries.filter(
|
|
2664
|
+
(e) => !isStatusEntryInAllowlist(e, allowlistPrefixes)
|
|
2665
|
+
);
|
|
2666
|
+
dirtyPlanningUnstagedPaths = dirtyPlanning.filter((entry) => hasUnstagedTrackedChange(entry.status)).flatMap((entry) => getStatusEntryPathspecs(entry));
|
|
2667
|
+
hadPreExistingTrackedPlanningEdits = dirtyPlanning.some(
|
|
2668
|
+
(entry) => isTrackedStatusChange(entry.status)
|
|
2669
|
+
);
|
|
2670
|
+
if (dirtyOther.length > 0) {
|
|
2671
|
+
console.log(
|
|
2672
|
+
chalk3.yellow(
|
|
2673
|
+
` Detected uncommitted changes outside planning buckets (${dirtyOther.length}); auto-stashing for merge.`
|
|
2674
|
+
)
|
|
2675
|
+
);
|
|
2676
|
+
console.log(
|
|
2677
|
+
chalk3.dim(` ${formatPathList(dirtyOther.flatMap(getStatusEntryPathspecs))}`)
|
|
2678
|
+
);
|
|
2679
|
+
const stashMessage = `[Poetic Flywheel] auto-stash before merge-result ${workflowId}`;
|
|
2680
|
+
const dirtyOtherPaths = Array.from(new Set(dirtyOther.flatMap(getStatusEntryPathspecs)));
|
|
2681
|
+
const stashResult = await execa4(
|
|
2682
|
+
"git",
|
|
2683
|
+
["stash", "push", "-u", "-m", stashMessage, "--", ...dirtyOtherPaths],
|
|
2684
|
+
{ cwd: mergeCwd, stdio: "pipe", timeout: 6e4, reject: false }
|
|
2685
|
+
);
|
|
2686
|
+
const stashOutput = `${stashResult.stdout ?? ""}
|
|
2687
|
+
${stashResult.stderr ?? ""}`;
|
|
2688
|
+
didAutoStash = stashResult.exitCode === 0 && !/No local changes/i.test(stashOutput);
|
|
2689
|
+
if (!didAutoStash) {
|
|
2690
|
+
throw new Error(
|
|
2691
|
+
"Working tree is not clean and auto-stash did not succeed. Commit/stash changes and retry."
|
|
2692
|
+
);
|
|
2693
|
+
}
|
|
2694
|
+
autoStashSha = await resolveStashShaByMessage(mergeCwd, stashMessage);
|
|
2695
|
+
const { stdout: postStashStatusOutput } = await execa4("git", ["status", "--porcelain=v1"], {
|
|
2696
|
+
cwd: mergeCwd,
|
|
2697
|
+
stdio: "pipe",
|
|
2698
|
+
timeout: 3e4
|
|
2699
|
+
});
|
|
2700
|
+
const postStashDirtyOther = parseGitStatusPorcelain(postStashStatusOutput).filter(
|
|
2701
|
+
(e) => !getStatusEntryPathspecs(e).every((pathname) => isWorktreeRunSentinelPath(pathname))
|
|
2702
|
+
).filter((e) => !isStatusEntryInAllowlist(e, allowlistPrefixes));
|
|
2703
|
+
if (postStashDirtyOther.length > 0) {
|
|
2704
|
+
throw new Error(
|
|
2705
|
+
"Working tree is not clean after auto-stash. Remaining non-planning changes: " + formatPathList(postStashDirtyOther.flatMap(getStatusEntryPathspecs))
|
|
2706
|
+
);
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
if (dirtyPlanning.length > 0) {
|
|
2710
|
+
console.log(
|
|
2711
|
+
chalk3.dim(
|
|
2712
|
+
` Ignoring ${dirtyPlanning.length} dirty planning file(s) for merge-result (${formatPathList(
|
|
2713
|
+
dirtyPlanning.map((d) => d.path)
|
|
2714
|
+
)})`
|
|
2715
|
+
)
|
|
2716
|
+
);
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
const dirtyPlanningBaseline = options.dryRun ? /* @__PURE__ */ new Set() : await snapshotDirtyPlanningPaths({ cwd: mergeCwd, allowlistPrefixes });
|
|
2720
|
+
await assertApplyLockOwned();
|
|
2721
|
+
mergeMutationStarted = true;
|
|
2722
|
+
await squashMergeRunBranch({
|
|
2723
|
+
cwd: mergeCwd,
|
|
2724
|
+
integrationBranch: manifest.integrationBranch
|
|
2725
|
+
});
|
|
2726
|
+
await assertApplyLockOwned();
|
|
2727
|
+
async function runPostSquashValidationPipeline() {
|
|
2728
|
+
const triageResult = await applyTriageDecisions(workflowId, {
|
|
2729
|
+
cwd: mergeCwd,
|
|
2730
|
+
expectedBranch: branchRoleApplyTarget ?? void 0,
|
|
2731
|
+
allowMain: options.allowMain,
|
|
2732
|
+
allowProtected: options.allowProtected
|
|
2733
|
+
});
|
|
2734
|
+
if (triageResult.followUpFailures.length > 0) {
|
|
2735
|
+
followUpFailures = [...followUpFailures ?? [], ...triageResult.followUpFailures];
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
await runPostSquashValidationPipeline();
|
|
2739
|
+
await assertCurrentBranchStillMatchesApplyTarget();
|
|
2740
|
+
async function runBiomeMergeGate() {
|
|
2741
|
+
const biomeAvailable = isResolverBiomeAvailable(mergeCwd);
|
|
2742
|
+
const stagedFiles = await listStagedFiles(mergeCwd);
|
|
2743
|
+
const biomeFiles = filterBiomePaths(stagedFiles);
|
|
2744
|
+
if (!biomeAvailable) {
|
|
2745
|
+
if (biomeFiles.length > 0) {
|
|
2746
|
+
console.log(
|
|
2747
|
+
chalk3.dim(
|
|
2748
|
+
` Biome merge gate unavailable for this target (${biomeFiles.length} Biome-lintable file(s) staged but the resolver does not declare Biome available); lint/format is verified by the resolver-derived commands.`
|
|
2749
|
+
)
|
|
2750
|
+
);
|
|
2751
|
+
} else {
|
|
2752
|
+
console.log(
|
|
2753
|
+
chalk3.dim(
|
|
2754
|
+
" Biome merge gate unavailable for this target language (no Node lint surface); lint/format is verified by the resolver-derived commands."
|
|
2755
|
+
)
|
|
2756
|
+
);
|
|
2757
|
+
}
|
|
2758
|
+
return { ok: true };
|
|
2759
|
+
}
|
|
2760
|
+
if (biomeFiles.length === 0) {
|
|
2761
|
+
return { ok: true };
|
|
2762
|
+
}
|
|
2763
|
+
console.log(chalk3.cyan(` Biome check (merged files): ${biomeFiles.length}`));
|
|
2764
|
+
await assertApplyLockOwned();
|
|
2765
|
+
await runBiome(["check", "--write", ...biomeFiles], mergeCwd);
|
|
2766
|
+
await assertApplyLockOwned();
|
|
2767
|
+
await execa4("git", ["add", "--", ...biomeFiles], {
|
|
2768
|
+
cwd: mergeCwd,
|
|
2769
|
+
stdio: "pipe",
|
|
2770
|
+
timeout: 3e4
|
|
2771
|
+
});
|
|
2772
|
+
const checkResult = await runBiome(
|
|
2773
|
+
["check", "--max-diagnostics", "50", ...biomeFiles],
|
|
2774
|
+
mergeCwd
|
|
2775
|
+
);
|
|
2776
|
+
if (!checkResult.ok) {
|
|
2777
|
+
return {
|
|
2778
|
+
ok: false,
|
|
2779
|
+
summary: formatBiomeFailureSummary(checkResult),
|
|
2780
|
+
biomeFiles
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
return { ok: true };
|
|
2784
|
+
}
|
|
2785
|
+
{
|
|
2786
|
+
const gate = await runBiomeMergeGate();
|
|
2787
|
+
if (!gate.ok) {
|
|
2788
|
+
throw new Error(
|
|
2789
|
+
`Biome check failed for merged files; merge aborted to avoid landing new lint/format errors.
|
|
2790
|
+
|
|
2791
|
+
Fix on the run branch, then retry merge:
|
|
2792
|
+
git checkout ${manifest.integrationBranch}
|
|
2793
|
+
npx --no-install biome check --write --unsafe ${gate.biomeFiles.slice(0, 8).join(" ")}${gate.biomeFiles.length > 8 ? " ..." : ""}
|
|
2794
|
+
git commit -am "chore: satisfy biome"
|
|
2795
|
+
git checkout ${currentBranch}
|
|
2796
|
+
poetic flywheel merge-result ${workflowId}
|
|
2797
|
+
|
|
2798
|
+
Biome output:
|
|
2799
|
+
${gate.summary}`
|
|
2800
|
+
);
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
await assertCurrentBranchStillMatchesApplyTarget();
|
|
2804
|
+
if (manifest.sourceType === "sprint" && manifest.sprintId) {
|
|
2805
|
+
const taskStatus = options.taskStatus ?? "apply";
|
|
2806
|
+
if (taskStatus === "apply" || taskStatus === "preview") {
|
|
2807
|
+
let workflowResult;
|
|
2808
|
+
try {
|
|
2809
|
+
const resultPath = getResultPath(workflowId);
|
|
2810
|
+
const content = await fs2.readFile(resultPath, "utf-8");
|
|
2811
|
+
workflowResult = JSON.parse(content);
|
|
2812
|
+
} catch (error) {
|
|
2813
|
+
console.log(
|
|
2814
|
+
chalk3.yellow(
|
|
2815
|
+
` Warning: Failed to load workflow result for status sync: ${error instanceof Error ? error.message : String(error)}`
|
|
2816
|
+
)
|
|
2817
|
+
);
|
|
2818
|
+
}
|
|
2819
|
+
const taskTransitions = extractTaskTransitionsFromResult(workflowResult);
|
|
2820
|
+
if (taskTransitions) {
|
|
2821
|
+
if (taskStatus === "apply") {
|
|
2822
|
+
console.log(chalk3.cyan(" Applying task transitions (artifact-driven)..."));
|
|
2823
|
+
try {
|
|
2824
|
+
await assertApplyLockOwned();
|
|
2825
|
+
const syncResult = await applyTaskTransitions(manifest.sprintId, taskTransitions, {
|
|
2826
|
+
promoteToDone: true
|
|
2827
|
+
});
|
|
2828
|
+
console.log(chalk3.dim(formatSyncSummary(syncResult)));
|
|
2829
|
+
if (syncResult.tasksFailed > 0) {
|
|
2830
|
+
const failedDetails = syncResult.details.filter((d) => d.error !== void 0).map((d) => ({
|
|
2831
|
+
taskId: d.taskId,
|
|
2832
|
+
error: d.error ?? "unknown error"
|
|
2833
|
+
}));
|
|
2834
|
+
console.log(
|
|
2835
|
+
chalk3.yellow(
|
|
2836
|
+
` Warning: ${syncResult.tasksFailed} task transition(s) failed (merge still succeeded):
|
|
2837
|
+
` + failedDetails.map((d) => ` - ${d.taskId}: ${d.error}`).join("\n")
|
|
2838
|
+
)
|
|
2839
|
+
);
|
|
2840
|
+
taskSyncFailures = {
|
|
2841
|
+
count: syncResult.tasksFailed,
|
|
2842
|
+
details: failedDetails
|
|
2843
|
+
};
|
|
2844
|
+
emitWarning(
|
|
2845
|
+
"FM-001",
|
|
2846
|
+
{ subsystem: "flywheel", taskId: workflowId },
|
|
2847
|
+
{
|
|
2848
|
+
severity: "WARN" /* WARN */,
|
|
2849
|
+
metadata: {
|
|
2850
|
+
type: "task_sync_partial_failure",
|
|
2851
|
+
workflowId,
|
|
2852
|
+
tasksFailed: syncResult.tasksFailed,
|
|
2853
|
+
failedTaskIds: failedDetails.map((d) => d.taskId)
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
);
|
|
2857
|
+
}
|
|
2858
|
+
const dirtyAfterSync = await snapshotDirtyPlanningPaths({
|
|
2859
|
+
cwd: mergeCwd,
|
|
2860
|
+
allowlistPrefixes
|
|
2861
|
+
});
|
|
2862
|
+
const sprintPrefix = toPosixRelative(
|
|
2863
|
+
mergeCwd,
|
|
2864
|
+
getBucketPathByKind("sprint", mergeCwd)
|
|
2865
|
+
);
|
|
2866
|
+
const sprintArchivePrefix = toPosixRelative(
|
|
2867
|
+
mergeCwd,
|
|
2868
|
+
getBucketArchivePathByKind("sprint", mergeCwd)
|
|
2869
|
+
);
|
|
2870
|
+
const taskPrefix = toPosixRelative(mergeCwd, getBucketPathByKind("task", mergeCwd));
|
|
2871
|
+
const taskArchivePrefix = toPosixRelative(
|
|
2872
|
+
mergeCwd,
|
|
2873
|
+
getBucketArchivePathByKind("task", mergeCwd)
|
|
2874
|
+
);
|
|
2875
|
+
const syncTargetCandidates = /* @__PURE__ */ new Set([
|
|
2876
|
+
`${sprintPrefix}/${manifest.sprintId}.md`,
|
|
2877
|
+
`${sprintArchivePrefix}/${manifest.sprintId}.md`
|
|
2878
|
+
]);
|
|
2879
|
+
for (const detail of syncResult.details ?? []) {
|
|
2880
|
+
if (!detail.updated && !detail.fileUpdated) continue;
|
|
2881
|
+
syncTargetCandidates.add(`${taskPrefix}/${detail.taskId}.md`);
|
|
2882
|
+
syncTargetCandidates.add(`${taskArchivePrefix}/${detail.taskId}.md`);
|
|
2883
|
+
}
|
|
2884
|
+
const taskSyncPaths = [...dirtyAfterSync].filter(
|
|
2885
|
+
(p) => !dirtyPlanningBaseline.has(p) && isPathInAllowlist(p, allowlistPrefixes) || syncTargetCandidates.has(p)
|
|
2886
|
+
);
|
|
2887
|
+
if (taskSyncPaths.length > 0) {
|
|
2888
|
+
await execa4("git", ["add", "--", ...taskSyncPaths], {
|
|
2889
|
+
cwd: mergeCwd,
|
|
2890
|
+
stdio: "pipe",
|
|
2891
|
+
timeout: 3e4
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2894
|
+
} catch (error) {
|
|
2895
|
+
console.log(
|
|
2896
|
+
chalk3.yellow(
|
|
2897
|
+
` Warning: Task transition application failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2898
|
+
)
|
|
2899
|
+
);
|
|
2900
|
+
}
|
|
2901
|
+
} else {
|
|
2902
|
+
console.log(chalk3.cyan(" Task transition preview (artifact-driven):"));
|
|
2903
|
+
if (taskTransitions.length > 0) {
|
|
2904
|
+
for (const t of taskTransitions) {
|
|
2905
|
+
console.log(
|
|
2906
|
+
chalk3.dim(
|
|
2907
|
+
` - ${t.taskId}: ${t.targetStatus}${t.hoursLogged ? ` (${t.hoursLogged.toFixed(2)}h)` : ""}`
|
|
2908
|
+
)
|
|
2909
|
+
);
|
|
2910
|
+
}
|
|
2911
|
+
} else {
|
|
2912
|
+
console.log(chalk3.dim(" (No transitions recorded)"));
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
} else {
|
|
2916
|
+
if (manifest.sourceType === "sprint") {
|
|
2917
|
+
emitWarning(
|
|
2918
|
+
"FM-002",
|
|
2919
|
+
{
|
|
2920
|
+
taskId: manifest.sprintId ?? workflowId,
|
|
2921
|
+
subsystem: "flywheel-merge",
|
|
2922
|
+
executionPhase: "cleanup"
|
|
2923
|
+
},
|
|
2924
|
+
{
|
|
2925
|
+
severity: "WARN" /* WARN */,
|
|
2926
|
+
metadata: {
|
|
2927
|
+
workflowId,
|
|
2928
|
+
sprintId: manifest.sprintId ?? null,
|
|
2929
|
+
operation: "task-transition-artifact-missing"
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
);
|
|
2933
|
+
console.log(
|
|
2934
|
+
chalk3.yellow(
|
|
2935
|
+
` Warning: taskTransitions artifact missing for sprint workflow '${manifest.sprintId ?? workflowId}'. Legacy task-status sync skipped (stageIds are not task IDs for sprint workflows). Ensure sprint-bridge.ts populates taskTransitions on stageResults.`
|
|
2936
|
+
)
|
|
2937
|
+
);
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
} else {
|
|
2941
|
+
console.log(chalk3.dim(" Task status sync skipped (--task-status=skip)"));
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
{
|
|
2945
|
+
let workflowResultForDrift;
|
|
2946
|
+
try {
|
|
2947
|
+
const resultPath = getResultPath(workflowId);
|
|
2948
|
+
const content = await fs2.readFile(resultPath, "utf-8");
|
|
2949
|
+
workflowResultForDrift = JSON.parse(content);
|
|
2950
|
+
} catch {
|
|
2951
|
+
}
|
|
2952
|
+
const stagesWithDrift = getStagesWithDriftFromWorkflowResult(workflowResultForDrift);
|
|
2953
|
+
if (stagesWithDrift && stagesWithDrift.length > 0) {
|
|
2954
|
+
const totalViolations = stagesWithDrift.reduce((sum, s) => sum + s.violations.length, 0);
|
|
2955
|
+
console.log(
|
|
2956
|
+
chalk3.yellow(
|
|
2957
|
+
`
|
|
2958
|
+
Drift detected: ${totalViolations} file(s) outside allowlist across ${stagesWithDrift.length} stage(s)`
|
|
2959
|
+
)
|
|
2960
|
+
);
|
|
2961
|
+
for (const stage of stagesWithDrift.slice(0, 3)) {
|
|
2962
|
+
console.log(chalk3.dim(` - ${stage.stageId}: ${stage.violations.length} file(s)`));
|
|
2963
|
+
}
|
|
2964
|
+
if (stagesWithDrift.length > 3) {
|
|
2965
|
+
console.log(chalk3.dim(` ... and ${stagesWithDrift.length - 3} more stage(s)`));
|
|
2966
|
+
}
|
|
2967
|
+
console.log(chalk3.dim(" (Drift was committed in warn mode - review changes if needed)"));
|
|
2968
|
+
console.log("");
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
if (!options.dryRun) {
|
|
2972
|
+
await assertApplyLockOwned();
|
|
2973
|
+
const dirtyPlanningNow = await snapshotDirtyPlanningPaths({
|
|
2974
|
+
cwd: mergeCwd,
|
|
2975
|
+
allowlistPrefixes
|
|
2976
|
+
});
|
|
2977
|
+
const mergeDirtiedPlanningPaths = [...dirtyPlanningNow].filter(
|
|
2978
|
+
(p) => !dirtyPlanningBaseline.has(p) && isPathInAllowlist(p, allowlistPrefixes)
|
|
2979
|
+
);
|
|
2980
|
+
if (mergeDirtiedPlanningPaths.length > 0) {
|
|
2981
|
+
await execa4("git", ["add", "--", ...mergeDirtiedPlanningPaths], {
|
|
2982
|
+
cwd: mergeCwd,
|
|
2983
|
+
stdio: "pipe",
|
|
2984
|
+
timeout: 3e4
|
|
2985
|
+
});
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
if (!options.skipVerify) {
|
|
2989
|
+
let gateResult;
|
|
2990
|
+
gateResult = await runPostMergeVerifyGate({
|
|
2991
|
+
workflowId,
|
|
2992
|
+
manifest,
|
|
2993
|
+
preMergeHeadSha,
|
|
2994
|
+
projectRoot: getProjectRoot(),
|
|
2995
|
+
mergeCwd,
|
|
2996
|
+
ambientUnstagedAllowlistedPaths: dirtyPlanningUnstagedPaths,
|
|
2997
|
+
onVerificationStarted: () => {
|
|
2998
|
+
postMergeVerificationStarted = true;
|
|
2999
|
+
}
|
|
3000
|
+
});
|
|
3001
|
+
verifiedTreeFingerprint = gateResult.verifiedTreeFingerprint;
|
|
3002
|
+
if (gateResult.orphanedStashWarning) {
|
|
3003
|
+
postMergeWarnings.push(gateResult.orphanedStashWarning);
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
const assertVerifiedTreeStillCurrent = async () => {
|
|
3007
|
+
if (!verifiedTreeFingerprint) return;
|
|
3008
|
+
const current = await execGit(["write-tree"], { cwd: mergeCwd, timeoutMs: 3e4 });
|
|
3009
|
+
if (current.exitCode !== 0 || current.stdout.trim() !== verifiedTreeFingerprint) {
|
|
3010
|
+
throw new Error(
|
|
3011
|
+
"The staged tree changed after final verification; refusing to commit or report apply success."
|
|
3012
|
+
);
|
|
3013
|
+
}
|
|
3014
|
+
};
|
|
3015
|
+
await assertVerifiedTreeStillCurrent();
|
|
3016
|
+
await assertApplyLockOwned();
|
|
3017
|
+
if (options.applyOnly) {
|
|
3018
|
+
console.log(chalk3.yellow(" Changes staged (--apply-only)"));
|
|
3019
|
+
console.log(chalk3.yellow(" Review and commit manually"));
|
|
3020
|
+
if (!options.dryRun) {
|
|
3021
|
+
try {
|
|
3022
|
+
await updateManifestApplyStatus(workflowId, {
|
|
3023
|
+
...verifiedTreeFingerprint ? buildApplyOnlyProofStatus(verifiedTreeFingerprint) : { stagedAt: (/* @__PURE__ */ new Date()).toISOString() },
|
|
3024
|
+
...branchRoleApplyStatus()
|
|
3025
|
+
});
|
|
3026
|
+
} catch {
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
return { success: true, workflowId };
|
|
3030
|
+
}
|
|
3031
|
+
const diffCheck = await execa4("git", ["diff", "--cached", "--quiet"], {
|
|
3032
|
+
cwd: mergeCwd,
|
|
3033
|
+
stdio: "pipe",
|
|
3034
|
+
timeout: 3e4,
|
|
3035
|
+
reject: false
|
|
3036
|
+
});
|
|
3037
|
+
if (diffCheck.exitCode === 0) {
|
|
3038
|
+
console.log(chalk3.dim(" No changes to commit (workflow produced no diff)."));
|
|
3039
|
+
applyMaterialized = true;
|
|
3040
|
+
const currentHead = await execGit(["rev-parse", "HEAD"], {
|
|
3041
|
+
cwd: mergeCwd,
|
|
3042
|
+
timeoutMs: 3e4
|
|
3043
|
+
});
|
|
3044
|
+
if (!options.skipVerify) {
|
|
3045
|
+
if (!verifiedTreeFingerprint) {
|
|
3046
|
+
throw new Error("Final verification did not produce a tree fingerprint.");
|
|
3047
|
+
}
|
|
3048
|
+
appliedCommitSha = await assertCommittedTreeMatchesFingerprint(
|
|
3049
|
+
mergeCwd,
|
|
3050
|
+
verifiedTreeFingerprint
|
|
3051
|
+
);
|
|
3052
|
+
} else {
|
|
3053
|
+
appliedCommitSha = currentHead.exitCode === 0 && currentHead.stdout.trim().length > 0 ? currentHead.stdout.trim() : void 0;
|
|
3054
|
+
}
|
|
3055
|
+
await updateManifestApplyStatus(workflowId, {
|
|
3056
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3057
|
+
appliedBy: await getGitUsername(),
|
|
3058
|
+
verifiedTreeFingerprint,
|
|
3059
|
+
appliedCommitSha,
|
|
3060
|
+
...branchRoleApplyStatus()
|
|
3061
|
+
});
|
|
3062
|
+
console.log(chalk3.green(" Manifest updated"));
|
|
3063
|
+
if (options.cleanup && !options.keepArtifacts) {
|
|
3064
|
+
const trackedCleanupTargets = getTrackedCleanupTargets(manifest);
|
|
3065
|
+
await cleanupArtifacts({
|
|
3066
|
+
integrationBranch: manifest.integrationBranch,
|
|
3067
|
+
workflowId,
|
|
3068
|
+
relatedTaskIds: [...manifest.relatedTaskIds ?? [], ...manifest.stageIds ?? []],
|
|
3069
|
+
trackedBranches: trackedCleanupTargets.trackedBranches,
|
|
3070
|
+
trackedWorktrees: trackedCleanupTargets.trackedWorktrees
|
|
3071
|
+
});
|
|
3072
|
+
} else if (!options.keepArtifacts) {
|
|
3073
|
+
console.log(chalk3.cyan(" Cleaning up run branch/worktree..."));
|
|
3074
|
+
await cleanupBranchAndWorktree(manifest.integrationBranch, workflowId);
|
|
3075
|
+
const trackedCleanupTargets = getTrackedCleanupTargets(manifest);
|
|
3076
|
+
await cleanupRelatedBranchesAndWorktrees({
|
|
3077
|
+
integrationBranch: manifest.integrationBranch,
|
|
3078
|
+
relatedTaskIds: [...manifest.relatedTaskIds ?? [], ...manifest.stageIds ?? []],
|
|
3079
|
+
trackedBranches: trackedCleanupTargets.trackedBranches,
|
|
3080
|
+
trackedWorktrees: trackedCleanupTargets.trackedWorktrees
|
|
3081
|
+
});
|
|
3082
|
+
} else {
|
|
3083
|
+
await logPreservedRunArtifacts(manifest.integrationBranch);
|
|
3084
|
+
}
|
|
3085
|
+
console.log(chalk3.bold.green("\nMerge complete!\n"));
|
|
3086
|
+
const warnings2 = [
|
|
3087
|
+
...taskSyncFailures ? [`Task sync completed with ${taskSyncFailures.count} failure(s)`] : [],
|
|
3088
|
+
...followUpFailures ?? [],
|
|
3089
|
+
...postMergeWarnings
|
|
3090
|
+
];
|
|
3091
|
+
return {
|
|
3092
|
+
success: true,
|
|
3093
|
+
degraded: warnings2.length > 0,
|
|
3094
|
+
workflowId,
|
|
3095
|
+
applied: true,
|
|
3096
|
+
...taskSyncFailures ? { taskSyncFailures } : {},
|
|
3097
|
+
...followUpFailures ? { followUpFailures } : {},
|
|
3098
|
+
...warnings2.length > 0 ? { warnings: warnings2 } : {}
|
|
3099
|
+
};
|
|
3100
|
+
}
|
|
3101
|
+
const commitMsg = `${flywheelMergeCommitSubject(workflowId)}
|
|
3102
|
+
|
|
3103
|
+
Source: ${manifest.sourceType}${manifest.sprintId ? ` (${manifest.sprintId})` : ""}
|
|
3104
|
+
Branch: ${manifest.integrationBranch}
|
|
3105
|
+
Base: ${manifest.baseSha.slice(0, 7)}`;
|
|
3106
|
+
let finalCommitMsg = commitMsg;
|
|
3107
|
+
try {
|
|
3108
|
+
const content = await fs2.readFile(getResultPath(workflowId), "utf-8");
|
|
3109
|
+
const parsed = JSON.parse(content);
|
|
3110
|
+
const verifySummary = formatVerifyStatusForCommitMessage(parsed);
|
|
3111
|
+
if (verifySummary) {
|
|
3112
|
+
finalCommitMsg = `${commitMsg}
|
|
3113
|
+
${verifySummary}`;
|
|
3114
|
+
}
|
|
3115
|
+
} catch {
|
|
3116
|
+
}
|
|
3117
|
+
finalCommitMsg = `${finalCommitMsg}
|
|
3118
|
+
|
|
3119
|
+
${flywheelApplyTrailers(workflowId, "code")}`;
|
|
3120
|
+
const metadataCommitMsg = "chore: sync task/sprint metadata [flywheel]\n\n" + flywheelApplyTrailers(workflowId, "metadata");
|
|
3121
|
+
const allStagedFiles = await listStagedFiles(mergeCwd);
|
|
3122
|
+
const systemWriteFiles = allStagedFiles.filter((f) => isSystemWritePath(f));
|
|
3123
|
+
const codeFiles = allStagedFiles.filter((f) => !isSystemWritePath(f));
|
|
3124
|
+
const quiet = isQuietOutputRequested();
|
|
3125
|
+
const commitStdio = quiet ? "pipe" : "inherit";
|
|
3126
|
+
await assertVerifiedTreeStillCurrent();
|
|
3127
|
+
if (systemWriteFiles.length > 0 && codeFiles.length > 0) {
|
|
3128
|
+
await execa4("git", ["restore", "--staged", ...systemWriteFiles], {
|
|
3129
|
+
cwd: mergeCwd,
|
|
3130
|
+
stdio: "pipe",
|
|
3131
|
+
timeout: 3e4
|
|
3132
|
+
});
|
|
3133
|
+
await assertApplyLockOwned();
|
|
3134
|
+
await execa4("git", ["commit", "-m", finalCommitMsg], {
|
|
3135
|
+
cwd: mergeCwd,
|
|
3136
|
+
stdio: commitStdio,
|
|
3137
|
+
timeout: 3e4
|
|
3138
|
+
});
|
|
3139
|
+
await recordCommitMaterialized();
|
|
3140
|
+
if (!quiet) console.log(chalk3.green(" Code changes committed"));
|
|
3141
|
+
await execa4("git", ["add", ...systemWriteFiles], {
|
|
3142
|
+
cwd: mergeCwd,
|
|
3143
|
+
stdio: "pipe",
|
|
3144
|
+
timeout: 3e4
|
|
3145
|
+
});
|
|
3146
|
+
await assertApplyLockOwned();
|
|
3147
|
+
await execa4("git", ["commit", "-m", metadataCommitMsg], {
|
|
3148
|
+
cwd: mergeCwd,
|
|
3149
|
+
stdio: commitStdio,
|
|
3150
|
+
timeout: 3e4,
|
|
3151
|
+
env: { ...process.env, POETIC_BACKLOG_SYNC: "1" }
|
|
3152
|
+
});
|
|
3153
|
+
await recordCommitMaterialized();
|
|
3154
|
+
if (!quiet) console.log(chalk3.green(" Task/sprint metadata committed separately"));
|
|
3155
|
+
} else if (systemWriteFiles.length > 0) {
|
|
3156
|
+
await assertApplyLockOwned();
|
|
3157
|
+
await execa4("git", ["commit", "-m", metadataCommitMsg], {
|
|
3158
|
+
cwd: mergeCwd,
|
|
3159
|
+
stdio: commitStdio,
|
|
3160
|
+
timeout: 3e4,
|
|
3161
|
+
env: { ...process.env, POETIC_BACKLOG_SYNC: "1" }
|
|
3162
|
+
});
|
|
3163
|
+
await recordCommitMaterialized();
|
|
3164
|
+
if (!quiet) console.log(chalk3.green(" Task/sprint metadata committed"));
|
|
3165
|
+
} else {
|
|
3166
|
+
await assertApplyLockOwned();
|
|
3167
|
+
await execa4("git", ["commit", "-m", finalCommitMsg], {
|
|
3168
|
+
cwd: mergeCwd,
|
|
3169
|
+
stdio: commitStdio,
|
|
3170
|
+
timeout: 3e4
|
|
3171
|
+
});
|
|
3172
|
+
await recordCommitMaterialized();
|
|
3173
|
+
if (!quiet) console.log(chalk3.green(" Changes committed"));
|
|
3174
|
+
}
|
|
3175
|
+
await assertApplyLockOwned();
|
|
3176
|
+
const finalHead = await execGit(["rev-parse", "HEAD"], { cwd: mergeCwd, timeoutMs: 3e4 });
|
|
3177
|
+
if (finalHead.exitCode !== 0 || finalHead.stdout.trim().length === 0) {
|
|
3178
|
+
throw new Error("Could not resolve the applied commit SHA.");
|
|
3179
|
+
}
|
|
3180
|
+
if (!options.skipVerify) {
|
|
3181
|
+
if (!verifiedTreeFingerprint) {
|
|
3182
|
+
throw new Error("Final verification did not produce a tree fingerprint.");
|
|
3183
|
+
}
|
|
3184
|
+
await assertCommittedTreeMatchesFingerprint(mergeCwd, verifiedTreeFingerprint);
|
|
3185
|
+
}
|
|
3186
|
+
appliedCommitSha = finalHead.stdout.trim();
|
|
3187
|
+
await assertApplyLockOwned();
|
|
3188
|
+
await restoreAutoStash();
|
|
3189
|
+
await assertApplyLockOwned();
|
|
3190
|
+
if (releaseApplyLock) {
|
|
3191
|
+
try {
|
|
3192
|
+
await releaseApplyLock();
|
|
3193
|
+
releaseApplyLock = void 0;
|
|
3194
|
+
} catch {
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
await updateManifestApplyStatus(workflowId, {
|
|
3198
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3199
|
+
appliedBy: await getGitUsername(),
|
|
3200
|
+
verifiedTreeFingerprint,
|
|
3201
|
+
appliedCommitSha,
|
|
3202
|
+
...branchRoleApplyStatus()
|
|
3203
|
+
});
|
|
3204
|
+
console.log(chalk3.green(" Manifest updated"));
|
|
3205
|
+
if (options.cleanup && !options.keepArtifacts) {
|
|
3206
|
+
const trackedCleanupTargets = getTrackedCleanupTargets(manifest);
|
|
3207
|
+
await cleanupArtifacts({
|
|
3208
|
+
integrationBranch: manifest.integrationBranch,
|
|
3209
|
+
workflowId,
|
|
3210
|
+
relatedTaskIds: [...manifest.relatedTaskIds ?? [], ...manifest.stageIds ?? []],
|
|
3211
|
+
trackedBranches: trackedCleanupTargets.trackedBranches,
|
|
3212
|
+
trackedWorktrees: trackedCleanupTargets.trackedWorktrees
|
|
3213
|
+
});
|
|
3214
|
+
} else if (!options.keepArtifacts) {
|
|
3215
|
+
console.log(chalk3.cyan(" Cleaning up run branch/worktree..."));
|
|
3216
|
+
await cleanupBranchAndWorktree(manifest.integrationBranch, workflowId);
|
|
3217
|
+
const trackedCleanupTargets = getTrackedCleanupTargets(manifest);
|
|
3218
|
+
await cleanupRelatedBranchesAndWorktrees({
|
|
3219
|
+
integrationBranch: manifest.integrationBranch,
|
|
3220
|
+
relatedTaskIds: [...manifest.relatedTaskIds ?? [], ...manifest.stageIds ?? []],
|
|
3221
|
+
trackedBranches: trackedCleanupTargets.trackedBranches,
|
|
3222
|
+
trackedWorktrees: trackedCleanupTargets.trackedWorktrees
|
|
3223
|
+
});
|
|
3224
|
+
} else {
|
|
3225
|
+
await logPreservedRunArtifacts(manifest.integrationBranch);
|
|
3226
|
+
}
|
|
3227
|
+
console.log(chalk3.bold.green("\nMerge complete!\n"));
|
|
3228
|
+
const warnings = [
|
|
3229
|
+
...taskSyncFailures ? [`Task sync completed with ${taskSyncFailures.count} failure(s)`] : [],
|
|
3230
|
+
...followUpFailures ?? [],
|
|
3231
|
+
...postMergeWarnings
|
|
3232
|
+
];
|
|
3233
|
+
return {
|
|
3234
|
+
success: true,
|
|
3235
|
+
degraded: warnings.length > 0,
|
|
3236
|
+
workflowId,
|
|
3237
|
+
applied: true,
|
|
3238
|
+
...taskSyncFailures ? { taskSyncFailures } : {},
|
|
3239
|
+
...followUpFailures ? { followUpFailures } : {},
|
|
3240
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
3241
|
+
};
|
|
3242
|
+
} catch (error) {
|
|
3243
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3244
|
+
let leftMidMergeWarning;
|
|
3245
|
+
let cleanupRan = false;
|
|
3246
|
+
try {
|
|
3247
|
+
if (!didCommit) {
|
|
3248
|
+
if (postMergeVerificationStarted) {
|
|
3249
|
+
leftMidMergeWarning = `Post-merge verification failed; staged merge state was preserved at ${mergeCwd}. Destructive rollback was skipped because checkout ownership cannot be proven against external actors.`;
|
|
3250
|
+
console.warn(chalk3.yellow(`
|
|
3251
|
+
Warning: ${leftMidMergeWarning}
|
|
3252
|
+
`));
|
|
3253
|
+
postMergeWarnings.push(leftMidMergeWarning);
|
|
3254
|
+
} else if (mergeMutationStarted && hadPreExistingTrackedPlanningEdits) {
|
|
3255
|
+
leftMidMergeWarning = `Merge failed after checkout mutation began; staged merge state was preserved at ${mergeCwd}. Destructive rollback was skipped because pre-existing tracked planning edits were present.`;
|
|
3256
|
+
console.warn(chalk3.yellow(`
|
|
3257
|
+
Warning: ${leftMidMergeWarning}
|
|
3258
|
+
`));
|
|
3259
|
+
postMergeWarnings.push(leftMidMergeWarning);
|
|
3260
|
+
} else if (mergeMutationStarted) {
|
|
3261
|
+
cleanupRan = true;
|
|
3262
|
+
await abortPendingSquashMerge(mergeCwd, preMergeHeadSha);
|
|
3263
|
+
}
|
|
3264
|
+
} else {
|
|
3265
|
+
cleanupRan = true;
|
|
3266
|
+
await execa4("git", ["merge", "--abort"], {
|
|
3267
|
+
cwd: mergeCwd,
|
|
3268
|
+
stdio: "pipe",
|
|
3269
|
+
timeout: 3e4,
|
|
3270
|
+
reject: false
|
|
3271
|
+
});
|
|
3272
|
+
}
|
|
3273
|
+
if (cleanupRan && !postMergeVerificationStarted) {
|
|
3274
|
+
const { stdout: postAbortStatus } = await execa4("git", ["status", "--porcelain=v1"], {
|
|
3275
|
+
cwd: mergeCwd,
|
|
3276
|
+
stdio: "pipe",
|
|
3277
|
+
timeout: 3e4,
|
|
3278
|
+
reject: false
|
|
3279
|
+
});
|
|
3280
|
+
const residue = parseGitStatusPorcelain(postAbortStatus).filter(
|
|
3281
|
+
(e) => !getStatusEntryPathspecs(e).every((p) => isWorktreeRunSentinelPath(p)) && !isStatusEntryInAllowlist(e, getPlanningDirtyAllowlistPrefixes(mergeCwd))
|
|
3282
|
+
);
|
|
3283
|
+
if (residue.length > 0) {
|
|
3284
|
+
leftMidMergeWarning = `Worktree left mid-merge after rollback; manual cleanup needed at ${mergeCwd}. Unexpected residue: ${formatPathList(residue.flatMap(getStatusEntryPathspecs))}`;
|
|
3285
|
+
console.warn(chalk3.yellow(`
|
|
3286
|
+
Warning: ${leftMidMergeWarning}
|
|
3287
|
+
`));
|
|
3288
|
+
postMergeWarnings.push(leftMidMergeWarning);
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
} catch (cleanupError) {
|
|
3292
|
+
leftMidMergeWarning = `Worktree cleanup after merge failure did not complete; manual cleanup may be needed at ${mergeCwd}. Cause: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`;
|
|
3293
|
+
console.warn(chalk3.yellow(`
|
|
3294
|
+
Warning: ${leftMidMergeWarning}
|
|
3295
|
+
`));
|
|
3296
|
+
postMergeWarnings.push(leftMidMergeWarning);
|
|
3297
|
+
}
|
|
3298
|
+
if (!options.dryRun) {
|
|
3299
|
+
try {
|
|
3300
|
+
const sourceBranch = integrationBranchForRecovery ?? null;
|
|
3301
|
+
const sourceBranchRole = sourceBranch ? classifyBranchRole(sourceBranch) : null;
|
|
3302
|
+
const applyTargetBranch = branchRoleApplyTarget ?? null;
|
|
3303
|
+
const applyTargetBranchRole = applyTargetBranch ? classifyBranchRole(applyTargetBranch) : null;
|
|
3304
|
+
const branchRoleOverride = applyTargetBranchRole === "protected" ? options.allowProtected ? "--allow-protected" : options.allowMain ? "--allow-main" : null : null;
|
|
3305
|
+
const branchRoleErrorStatus = {
|
|
3306
|
+
sourceBranch,
|
|
3307
|
+
sourceBranchRole,
|
|
3308
|
+
applyTargetBranch,
|
|
3309
|
+
applyTargetBranchRole,
|
|
3310
|
+
branchRoleAction: branchRoleOverride ? "overridden" : "product-fix",
|
|
3311
|
+
branchRoleOverride,
|
|
3312
|
+
branchRoleRefused: false
|
|
3313
|
+
};
|
|
3314
|
+
await updateManifestApplyStatus(
|
|
3315
|
+
workflowId,
|
|
3316
|
+
applyMaterialized ? {
|
|
3317
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3318
|
+
postApplyError: errorMsg,
|
|
3319
|
+
verifiedTreeFingerprint,
|
|
3320
|
+
appliedCommitSha,
|
|
3321
|
+
...branchRoleErrorStatus
|
|
3322
|
+
} : { applyError: errorMsg, ...branchRoleErrorStatus }
|
|
3323
|
+
);
|
|
3324
|
+
} catch {
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
if (applyMaterialized) {
|
|
3328
|
+
console.warn(chalk3.yellow(`
|
|
3329
|
+
Post-apply cleanup failed: ${errorMsg}
|
|
3330
|
+
`));
|
|
3331
|
+
console.warn(
|
|
3332
|
+
chalk3.yellow(" Applied changes were preserved; post-apply cleanup needs follow-up.")
|
|
3333
|
+
);
|
|
3334
|
+
return {
|
|
3335
|
+
success: true,
|
|
3336
|
+
degraded: true,
|
|
3337
|
+
workflowId,
|
|
3338
|
+
applied: true,
|
|
3339
|
+
cleanupFailed: true,
|
|
3340
|
+
postApplyError: errorMsg,
|
|
3341
|
+
warnings: [errorMsg, ...leftMidMergeWarning ? [leftMidMergeWarning] : []]
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
3344
|
+
console.error(chalk3.red(`
|
|
3345
|
+
Merge failed: ${errorMsg}
|
|
3346
|
+
`));
|
|
3347
|
+
if (error instanceof SquashMergeConflictError && integrationBranchForRecovery && currentBranchForRecovery) {
|
|
3348
|
+
try {
|
|
3349
|
+
const conflictBranch = `poetic/merge-conflict/${workflowId}`;
|
|
3350
|
+
const worktreePath = path2.join(
|
|
3351
|
+
mergeCwd,
|
|
3352
|
+
".poetic",
|
|
3353
|
+
"tmp",
|
|
3354
|
+
"merge-conflicts",
|
|
3355
|
+
workflowId.replace(/[^a-zA-Z0-9._-]/g, "_")
|
|
3356
|
+
);
|
|
3357
|
+
await fs2.mkdir(worktreePath, { recursive: true });
|
|
3358
|
+
const conflictWorktreeResult = await execGit(
|
|
3359
|
+
["worktree", "add", "-b", conflictBranch, worktreePath, currentBranchForRecovery],
|
|
3360
|
+
{
|
|
3361
|
+
cwd: mergeCwd,
|
|
3362
|
+
timeoutMs: 6e4
|
|
3363
|
+
}
|
|
3364
|
+
);
|
|
3365
|
+
if (conflictWorktreeResult.exitCode !== 0) {
|
|
3366
|
+
throw new Error(
|
|
3367
|
+
conflictWorktreeResult.stderr.trim() || "git worktree add for conflict recovery failed"
|
|
3368
|
+
);
|
|
3369
|
+
}
|
|
3370
|
+
await execa4("git", ["merge", "--squash", integrationBranchForRecovery], {
|
|
3371
|
+
cwd: worktreePath,
|
|
3372
|
+
stdio: isQuietOutputRequested() ? "pipe" : "inherit",
|
|
3373
|
+
timeout: 6e4,
|
|
3374
|
+
reject: false
|
|
3375
|
+
});
|
|
3376
|
+
console.error(chalk3.yellow(" Conflict-resolution worktree created:"));
|
|
3377
|
+
console.error(chalk3.yellow(` ${worktreePath}`));
|
|
3378
|
+
console.error(
|
|
3379
|
+
chalk3.yellow(
|
|
3380
|
+
` Resolve conflicts there, commit, then cherry-pick onto ${currentBranchForRecovery} (or open a PR).`
|
|
3381
|
+
)
|
|
3382
|
+
);
|
|
3383
|
+
} catch {
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
console.error(chalk3.yellow(" Run branch preserved for manual recovery:"));
|
|
3387
|
+
console.error(
|
|
3388
|
+
chalk3.yellow(` ${integrationBranchForRecovery ?? "(unknown - manifest unavailable)"}`)
|
|
3389
|
+
);
|
|
3390
|
+
return {
|
|
3391
|
+
success: false,
|
|
3392
|
+
workflowId,
|
|
3393
|
+
error: errorMsg,
|
|
3394
|
+
...leftMidMergeWarning ? { degraded: true, warnings: [leftMidMergeWarning] } : {}
|
|
3395
|
+
};
|
|
3396
|
+
} finally {
|
|
3397
|
+
await restoreAutoStash();
|
|
3398
|
+
if (releaseApplyLock) {
|
|
3399
|
+
try {
|
|
3400
|
+
await releaseApplyLock();
|
|
3401
|
+
} catch {
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
if (mergeLockAcquired) {
|
|
3405
|
+
try {
|
|
3406
|
+
const currentLockPid = await readLockPid(mergeLockPath);
|
|
3407
|
+
if (currentLockPid === thisPid) {
|
|
3408
|
+
await fs2.unlink(mergeLockPath);
|
|
3409
|
+
}
|
|
3410
|
+
} catch {
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
async function cleanupBranchAndWorktree(integrationBranch, workflowId) {
|
|
3416
|
+
const preserveWorktrees = readEnv(POETIC_PRESERVE_WORKTREES).value || isLabModeFromEnv();
|
|
3417
|
+
if (classifyBranchRole(integrationBranch) !== "generated-artifact") {
|
|
3418
|
+
console.log(
|
|
3419
|
+
chalk3.yellow(` Warning: Refusing to delete non-generated branch ${integrationBranch}`)
|
|
3420
|
+
);
|
|
3421
|
+
return;
|
|
3422
|
+
}
|
|
3423
|
+
const worktreeManager = WorktreeManagerPresets.orchestrator();
|
|
3424
|
+
try {
|
|
3425
|
+
const worktreePath = await findWorktreePathForBranch(integrationBranch);
|
|
3426
|
+
if (worktreePath) {
|
|
3427
|
+
await worktreeManager.cleanup(worktreePath, {
|
|
3428
|
+
preserve: preserveWorktrees
|
|
3429
|
+
});
|
|
3430
|
+
if (workflowId) {
|
|
3431
|
+
await trackWorktreeCleaned(workflowId, worktreePath);
|
|
3432
|
+
}
|
|
3433
|
+
console.log(chalk3.dim(" Worktree cleaned up"));
|
|
3434
|
+
}
|
|
3435
|
+
} catch (error) {
|
|
3436
|
+
console.log(
|
|
3437
|
+
chalk3.yellow(
|
|
3438
|
+
` Warning: Failed to cleanup worktree: ${error instanceof Error ? error.message : String(error)}`
|
|
3439
|
+
)
|
|
3440
|
+
);
|
|
3441
|
+
}
|
|
3442
|
+
try {
|
|
3443
|
+
const deleteResult = await execGit(["branch", "-D", integrationBranch], {
|
|
3444
|
+
cwd: getProjectRoot(),
|
|
3445
|
+
timeoutMs: 1e4
|
|
3446
|
+
});
|
|
3447
|
+
if (deleteResult.exitCode !== 0) {
|
|
3448
|
+
throw new Error(deleteResult.stderr.trim() || "git branch -D failed");
|
|
3449
|
+
}
|
|
3450
|
+
console.log(chalk3.dim(" Run branch deleted"));
|
|
3451
|
+
} catch (error) {
|
|
3452
|
+
console.log(
|
|
3453
|
+
chalk3.yellow(
|
|
3454
|
+
` Warning: Failed to delete branch: ${error instanceof Error ? error.message : String(error)}`
|
|
3455
|
+
)
|
|
3456
|
+
);
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
async function cleanupRelatedBranchesAndWorktrees(opts) {
|
|
3460
|
+
const preserveWorktrees = readEnv(POETIC_PRESERVE_WORKTREES).value || isLabModeFromEnv();
|
|
3461
|
+
if (preserveWorktrees) return;
|
|
3462
|
+
await cleanupWorkflowRelatedBranchesAndWorktrees({
|
|
3463
|
+
integrationBranch: opts.integrationBranch,
|
|
3464
|
+
relatedTaskIds: opts.relatedTaskIds,
|
|
3465
|
+
trackedBranches: opts.trackedBranches,
|
|
3466
|
+
trackedWorktrees: opts.trackedWorktrees,
|
|
3467
|
+
dryRun: false
|
|
3468
|
+
});
|
|
3469
|
+
}
|
|
3470
|
+
async function cleanupArtifacts(opts) {
|
|
3471
|
+
console.log(chalk3.cyan(" Cleaning up run branch/worktree and artifacts..."));
|
|
3472
|
+
await cleanupBranchAndWorktree(opts.integrationBranch, opts.workflowId);
|
|
3473
|
+
await cleanupRelatedBranchesAndWorktrees({
|
|
3474
|
+
integrationBranch: opts.integrationBranch,
|
|
3475
|
+
relatedTaskIds: opts.relatedTaskIds,
|
|
3476
|
+
trackedBranches: opts.trackedBranches,
|
|
3477
|
+
trackedWorktrees: opts.trackedWorktrees
|
|
3478
|
+
});
|
|
3479
|
+
try {
|
|
3480
|
+
const artifactDir = getArtifactDir(opts.workflowId);
|
|
3481
|
+
await fs2.rm(artifactDir, { recursive: true, force: true });
|
|
3482
|
+
console.log(chalk3.dim(" Artifact directory removed"));
|
|
3483
|
+
} catch (error) {
|
|
3484
|
+
console.log(
|
|
3485
|
+
chalk3.yellow(
|
|
3486
|
+
` Warning: Failed to remove artifacts: ${error instanceof Error ? error.message : String(error)}`
|
|
3487
|
+
)
|
|
3488
|
+
);
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
async function logPreservedRunArtifacts(integrationBranch) {
|
|
3492
|
+
const worktreePath = await findWorktreePathForBranch(integrationBranch);
|
|
3493
|
+
console.log(chalk3.dim(" Run branch/worktree preserved (--keep-artifacts)"));
|
|
3494
|
+
console.log(chalk3.dim(` Branch: ${integrationBranch}`));
|
|
3495
|
+
if (worktreePath) {
|
|
3496
|
+
console.log(chalk3.dim(` Worktree: ${worktreePath}`));
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
async function findWorktreePathForBranch(branchName, repoCwd = getProjectRoot()) {
|
|
3500
|
+
const { stdout } = await execa4("git", ["worktree", "list", "--porcelain"], {
|
|
3501
|
+
cwd: repoCwd,
|
|
3502
|
+
stdio: "pipe",
|
|
3503
|
+
timeout: 1e4
|
|
3504
|
+
});
|
|
3505
|
+
const targetRef = `refs/heads/${branchName}`;
|
|
3506
|
+
let currentWorktreePath;
|
|
3507
|
+
for (const line of stdout.split("\n")) {
|
|
3508
|
+
if (line.startsWith("worktree ")) {
|
|
3509
|
+
currentWorktreePath = line.slice("worktree ".length).trim();
|
|
3510
|
+
continue;
|
|
3511
|
+
}
|
|
3512
|
+
if (line.startsWith("branch ") && currentWorktreePath) {
|
|
3513
|
+
const ref = line.slice("branch ".length).trim();
|
|
3514
|
+
if (ref === targetRef) {
|
|
3515
|
+
return currentWorktreePath;
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
return void 0;
|
|
3520
|
+
}
|
|
3521
|
+
async function getGitUsername() {
|
|
3522
|
+
try {
|
|
3523
|
+
const { stdout } = await execa4("git", ["config", "user.name"], {
|
|
3524
|
+
timeout: 5e3
|
|
3525
|
+
});
|
|
3526
|
+
return stdout.trim();
|
|
3527
|
+
} catch {
|
|
3528
|
+
return "unknown";
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
async function getCurrentBranch(cwd = getProjectRoot()) {
|
|
3532
|
+
const { stdout } = await execa4("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
3533
|
+
cwd,
|
|
3534
|
+
stdio: "pipe",
|
|
3535
|
+
timeout: 5e3
|
|
3536
|
+
});
|
|
3537
|
+
return stdout.trim();
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
export {
|
|
3541
|
+
applyTaskTransitions,
|
|
3542
|
+
mergeWorkflowResult
|
|
3543
|
+
};
|
|
3544
|
+
//# sourceMappingURL=chunk-CVMVZWG4.js.map
|