@ebrindley/build 0.0.0-stage → 1.42.1-rc28
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/.build-project/providers/catalog.json +13951 -0
- package/.build-project/providers/pricing.json +1978 -0
- package/.build-project/providers/registry.json +4103 -0
- package/.github/SECURITY.md +75 -0
- package/.nvmrc +1 -0
- package/CHANGELOG.md +661 -0
- package/CODE_OF_CONDUCT.md +44 -0
- package/CONTRIBUTING.md +24 -0
- package/INSTALL.md +623 -0
- package/LICENSE +21 -0
- package/README.md +118 -2
- package/THIRD_PARTY_NOTICES.txt +26 -0
- package/dist/ai-judge-UE5UOYOO.js +191 -0
- package/dist/allowlist-grounding-KPLN6NC5.js +16 -0
- package/dist/allowlist-utils-MZZKKXRD.js +31 -0
- package/dist/apply-completion-mode-EM37UZHB.js +110 -0
- package/dist/artifact-migrator-WPACNSEP.js +276 -0
- package/dist/ask-2OZ4JFQR.js +176 -0
- package/dist/auth-6UKBTJUQ.js +185 -0
- package/dist/auth-H5KJL6HL.js +165 -0
- package/dist/auth-LVJRJDMU.js +12 -0
- package/dist/auth-NHZOCNRR.js +94 -0
- package/dist/auth-PBXHWXGW.js +95 -0
- package/dist/auth-T7Y5DCAH.js +91 -0
- package/dist/auth-TVVKCHQW.js +13 -0
- package/dist/auth-UHB2UCSS.js +97 -0
- package/dist/auth-VACA3VUL.js +98 -0
- package/dist/auth-VVLC77S3.js +44 -0
- package/dist/auth-liveness-XO4AGDDD.js +53 -0
- package/dist/autoloop-cleanup-SI3VGAAR.js +106 -0
- package/dist/autoloop-evidence-gates-J5GG5YQB.js +39 -0
- package/dist/autoloop-gate-policy-SUNAEP6Q.js +169 -0
- package/dist/autoloop-helpers-VQGQNTT6.js +52 -0
- package/dist/autoloop-ledger-P6TIDP7C.js +96 -0
- package/dist/autoloop-ledger-subscriber-HFAGOZXH.js +161 -0
- package/dist/autoloop-run-defaults-GWGZBNT7.js +27 -0
- package/dist/autoloop-run-lock-GBWRM5A2.js +197 -0
- package/dist/autoloop-success-C32QVWOP.js +93 -0
- package/dist/autoloop-termination-summary-GI6IBQWD.js +58 -0
- package/dist/autoloop-trajectory-AMKSQISD.js +70 -0
- package/dist/autoloop-wall-clock-T7ZVIQOC.js +12 -0
- package/dist/automatic-branch-cleanup-R62X2C5O.js +49 -0
- package/dist/autonomous-loop-controller-2WL363LW.js +4386 -0
- package/dist/backend-YUTFDZXZ.js +30 -0
- package/dist/background-executor-Z3ODTMD7.js +260 -0
- package/dist/backlog-75QPEAMW.js +403 -0
- package/dist/backlog-execution-intent-GWUV5TUK.js +19 -0
- package/dist/backup-active-store-XHWLN34M.js +16 -0
- package/dist/backup-machine-stores-UI4LTJOY.js +251 -0
- package/dist/backup-telemetry-db-Z4ICYCNW.js +26 -0
- package/dist/branch-archive-manager-FMSQ7YJF.js +18 -0
- package/dist/branch-cleanup-manager-ST6FJGND.js +35 -0
- package/dist/build-gate-7NKWPY62.js +68 -0
- package/dist/build-root-S7DINODY.js +18 -0
- package/dist/change-summary-R6CB6VXJ.js +50 -0
- package/dist/chunk-24MNKKOM.js +173 -0
- package/dist/chunk-2H6VZFIO.js +235 -0
- package/dist/chunk-2JIX3EKV.js +39 -0
- package/dist/chunk-2KO7HVZM.js +106 -0
- package/dist/chunk-2L36U25O.js +100 -0
- package/dist/chunk-2NMNFUQA.js +475 -0
- package/dist/chunk-2OUAEH67.js +83 -0
- package/dist/chunk-2VLJUXJU.js +115 -0
- package/dist/chunk-2VUEDIUM.js +77 -0
- package/dist/chunk-2Y2IIHPI.js +430 -0
- package/dist/chunk-2YRG3LHZ.js +220 -0
- package/dist/chunk-2Z5Q3SUY.js +65 -0
- package/dist/chunk-32IYXIE5.js +873 -0
- package/dist/chunk-33IGPPUI.js +292 -0
- package/dist/chunk-352PCJLK.js +283 -0
- package/dist/chunk-35J4NE6S.js +226 -0
- package/dist/chunk-3C7WBIVK.js +1016 -0
- package/dist/chunk-3H3EY6U7.js +1225 -0
- package/dist/chunk-3H4MWYIC.js +198 -0
- package/dist/chunk-3HHWQR3N.js +2479 -0
- package/dist/chunk-3JKNRYEE.js +14 -0
- package/dist/chunk-3LECCYZY.js +128 -0
- package/dist/chunk-3LOY6YZI.js +852 -0
- package/dist/chunk-3M57DADF.js +44 -0
- package/dist/chunk-3NOTLVLG.js +3803 -0
- package/dist/chunk-3OPP2OZB.js +1480 -0
- package/dist/chunk-3P5IN5E7.js +9 -0
- package/dist/chunk-3PL22IYZ.js +20 -0
- package/dist/chunk-3REDAJUJ.js +14 -0
- package/dist/chunk-3S2R4XJI.js +203 -0
- package/dist/chunk-3UBZI5DW.js +61 -0
- package/dist/chunk-3VV5OBGG.js +308 -0
- package/dist/chunk-3WY5TU2O.js +823 -0
- package/dist/chunk-43OKHTW4.js +82 -0
- package/dist/chunk-45KUJBDI.js +112 -0
- package/dist/chunk-47X5LM7B.js +224 -0
- package/dist/chunk-4B6FQJ77.js +268 -0
- package/dist/chunk-4BTDUT2S.js +42 -0
- package/dist/chunk-4DJL2VXA.js +24 -0
- package/dist/chunk-4EBSXZ73.js +48 -0
- package/dist/chunk-4QKP6I4C.js +294 -0
- package/dist/chunk-4U6XK2DG.js +300 -0
- package/dist/chunk-4UMRUFBD.js +132 -0
- package/dist/chunk-4US4SYJ4.js +569 -0
- package/dist/chunk-4ZPLWEZO.js +154 -0
- package/dist/chunk-56WPYDJH.js +624 -0
- package/dist/chunk-57WRCJJW.js +50 -0
- package/dist/chunk-5AW4RMNG.js +24 -0
- package/dist/chunk-5EQN56OZ.js +155 -0
- package/dist/chunk-5F43TUIN.js +38 -0
- package/dist/chunk-5FKHIKTS.js +115 -0
- package/dist/chunk-5HDP7XZG.js +91 -0
- package/dist/chunk-5KQETTNS.js +152 -0
- package/dist/chunk-5PDVN2KO.js +498 -0
- package/dist/chunk-5UBIFVRD.js +509 -0
- package/dist/chunk-5YDE3Q62.js +46 -0
- package/dist/chunk-626AJVMU.js +302 -0
- package/dist/chunk-633XZ7UF.js +3583 -0
- package/dist/chunk-63DHNHGA.js +236 -0
- package/dist/chunk-666656AR.js +568 -0
- package/dist/chunk-67YRJPJO.js +19 -0
- package/dist/chunk-6CAU7VBA.js +20 -0
- package/dist/chunk-6CB3KJRY.js +21 -0
- package/dist/chunk-6DBMALXL.js +29 -0
- package/dist/chunk-6E25C4IA.js +269 -0
- package/dist/chunk-6FIBNIQU.js +73 -0
- package/dist/chunk-6G57FYBV.js +189 -0
- package/dist/chunk-6KZKRTG3.js +4885 -0
- package/dist/chunk-6LAH2UV2.js +525 -0
- package/dist/chunk-6PZY2TTU.js +921 -0
- package/dist/chunk-6T6NHXK4.js +230 -0
- package/dist/chunk-6TBEX4R4.js +432 -0
- package/dist/chunk-6TRD7TEE.js +911 -0
- package/dist/chunk-6XCP2RAI.js +47 -0
- package/dist/chunk-726G6WDL.js +351 -0
- package/dist/chunk-75EMT7BF.js +616 -0
- package/dist/chunk-762ACDZJ.js +4696 -0
- package/dist/chunk-77I6G4CO.js +1 -0
- package/dist/chunk-7DJ6SQH3.js +510 -0
- package/dist/chunk-7HPCIOMA.js +1383 -0
- package/dist/chunk-7HYBSRPZ.js +17503 -0
- package/dist/chunk-7KBEOPVB.js +73 -0
- package/dist/chunk-7LHJDJB3.js +2222 -0
- package/dist/chunk-7NVJMH52.js +306 -0
- package/dist/chunk-7RMARDVH.js +33 -0
- package/dist/chunk-7SEWU4QG.js +36 -0
- package/dist/chunk-7TUTZTI2.js +911 -0
- package/dist/chunk-7VBDFPUM.js +340 -0
- package/dist/chunk-7VRJHTGZ.js +6406 -0
- package/dist/chunk-7YAOUUKK.js +493 -0
- package/dist/chunk-AAAODV76.js +860 -0
- package/dist/chunk-AB4HUJVQ.js +339 -0
- package/dist/chunk-AEQWV746.js +611 -0
- package/dist/chunk-AHU3FKPA.js +11639 -0
- package/dist/chunk-AISTV5XU.js +226 -0
- package/dist/chunk-AJBTKGJZ.js +48 -0
- package/dist/chunk-AJJNW7XT.js +248 -0
- package/dist/chunk-AJN5Y5YZ.js +160 -0
- package/dist/chunk-ALDTFNZ3.js +150 -0
- package/dist/chunk-ARARVIZK.js +281 -0
- package/dist/chunk-ATBM6AW5.js +53 -0
- package/dist/chunk-ATKEKM3E.js +206 -0
- package/dist/chunk-AUJLW6JZ.js +92 -0
- package/dist/chunk-AY3MHEFM.js +24 -0
- package/dist/chunk-B7PYTV72.js +236 -0
- package/dist/chunk-B7YRSULE.js +1423 -0
- package/dist/chunk-BAWJNMWF.js +339 -0
- package/dist/chunk-BBPIILO2.js +88 -0
- package/dist/chunk-BD3PGW6A.js +957 -0
- package/dist/chunk-BDPYFAB4.js +44 -0
- package/dist/chunk-BIUV23HH.js +729 -0
- package/dist/chunk-BQ3VAGXH.js +243 -0
- package/dist/chunk-BQJOGAWT.js +31 -0
- package/dist/chunk-BT7EG7CO.js +68 -0
- package/dist/chunk-BV3XQQLU.js +27 -0
- package/dist/chunk-BY6ODHNZ.js +78 -0
- package/dist/chunk-BZRLSZNV.js +79 -0
- package/dist/chunk-C3DH3NCY.js +2773 -0
- package/dist/chunk-C3KCMAJP.js +756 -0
- package/dist/chunk-C7AGUJ5K.js +267 -0
- package/dist/chunk-CBSWKQCX.js +342 -0
- package/dist/chunk-CE5OZBYY.js +21 -0
- package/dist/chunk-CFCVYHSW.js +373 -0
- package/dist/chunk-CHA2HDE5.js +126 -0
- package/dist/chunk-CIHWPGS5.js +146 -0
- package/dist/chunk-CJBSZX3F.js +127 -0
- package/dist/chunk-CLA6QIJM.js +43 -0
- package/dist/chunk-CPSVQR2I.js +365 -0
- package/dist/chunk-CRPRVNQ4.js +254 -0
- package/dist/chunk-CSIE4Y7C.js +1093 -0
- package/dist/chunk-CSOPSYDY.js +142 -0
- package/dist/chunk-CSXLLEGB.js +205 -0
- package/dist/chunk-CTDLERWU.js +293 -0
- package/dist/chunk-CULBXMSK.js +110 -0
- package/dist/chunk-D2M2Y5ID.js +21 -0
- package/dist/chunk-D37WY7SH.js +1060 -0
- package/dist/chunk-D4VJFCGZ.js +89 -0
- package/dist/chunk-D6WTZMZB.js +48 -0
- package/dist/chunk-DBHOWDUH.js +976 -0
- package/dist/chunk-DCACX7BV.js +19364 -0
- package/dist/chunk-DFHX4XYA.js +128 -0
- package/dist/chunk-DIKBRRKE.js +176 -0
- package/dist/chunk-DJG5RIOR.js +9 -0
- package/dist/chunk-DNIOIISW.js +616 -0
- package/dist/chunk-DOA5G7IA.js +315 -0
- package/dist/chunk-DP7FKP6W.js +855 -0
- package/dist/chunk-DS2JZ677.js +79 -0
- package/dist/chunk-DSRLVYBS.js +82 -0
- package/dist/chunk-EAEVKNJA.js +209 -0
- package/dist/chunk-EEUESN3T.js +575 -0
- package/dist/chunk-EJ4LUU5Y.js +73 -0
- package/dist/chunk-EJIWTPSZ.js +1063 -0
- package/dist/chunk-EJVCDTAN.js +116 -0
- package/dist/chunk-EKLUBDBQ.js +627 -0
- package/dist/chunk-EN62WLFD.js +849 -0
- package/dist/chunk-ENYRCL43.js +26 -0
- package/dist/chunk-EP22ZMFO.js +273 -0
- package/dist/chunk-EP4SVAVI.js +87 -0
- package/dist/chunk-EPAMVGI3.js +1562 -0
- package/dist/chunk-EPRMH6HO.js +24 -0
- package/dist/chunk-ES6NWJ36.js +4057 -0
- package/dist/chunk-ETA2K4KI.js +30 -0
- package/dist/chunk-ETAD4NKD.js +31 -0
- package/dist/chunk-EUWIIDQ7.js +93 -0
- package/dist/chunk-EVE3OD2W.js +141 -0
- package/dist/chunk-EXA26FFC.js +459 -0
- package/dist/chunk-EXSPAA35.js +244 -0
- package/dist/chunk-F55YABKV.js +258 -0
- package/dist/chunk-F5DNGU5W.js +27 -0
- package/dist/chunk-F622MJYV.js +103 -0
- package/dist/chunk-F6NIQD74.js +5111 -0
- package/dist/chunk-FAWJTC34.js +43 -0
- package/dist/chunk-FAZBRHGG.js +47 -0
- package/dist/chunk-FIEUSGNJ.js +39 -0
- package/dist/chunk-FKCG7K6H.js +1146 -0
- package/dist/chunk-FOVVXV45.js +229 -0
- package/dist/chunk-FRBPKBFE.js +30 -0
- package/dist/chunk-FSA5BPFI.js +268 -0
- package/dist/chunk-FSJ5TTZ3.js +29 -0
- package/dist/chunk-FT7SGLXT.js +193 -0
- package/dist/chunk-FUUVZZUB.js +62 -0
- package/dist/chunk-FYMRVUNZ.js +1697 -0
- package/dist/chunk-FYTAA5FG.js +162 -0
- package/dist/chunk-G3VB2UCZ.js +3892 -0
- package/dist/chunk-G6OIMVXH.js +405 -0
- package/dist/chunk-G7PQCXVH.js +228 -0
- package/dist/chunk-GAB6UW52.js +1087 -0
- package/dist/chunk-GAFJEYLK.js +29 -0
- package/dist/chunk-GC4CRMTS.js +642 -0
- package/dist/chunk-GFHSIWHL.js +5297 -0
- package/dist/chunk-GI5M67YA.js +735 -0
- package/dist/chunk-GIJWHUWF.js +191 -0
- package/dist/chunk-GO6DRECI.js +1 -0
- package/dist/chunk-GOFVBTMN.js +314 -0
- package/dist/chunk-GQIP72ZJ.js +13 -0
- package/dist/chunk-GQXLXRFA.js +28 -0
- package/dist/chunk-GT6HFKJB.js +48 -0
- package/dist/chunk-GVJGSSKM.js +174 -0
- package/dist/chunk-GWHGTG37.js +36 -0
- package/dist/chunk-GZAJHLKO.js +18 -0
- package/dist/chunk-GZK62I3N.js +314 -0
- package/dist/chunk-H3HGMISG.js +483 -0
- package/dist/chunk-H3ISZJMJ.js +403 -0
- package/dist/chunk-H4HXVWEC.js +9 -0
- package/dist/chunk-H6MJGKAW.js +120 -0
- package/dist/chunk-HBTTGVR5.js +353 -0
- package/dist/chunk-HBZ322WZ.js +35 -0
- package/dist/chunk-HEP3ILP4.js +34 -0
- package/dist/chunk-HGFXODKO.js +106 -0
- package/dist/chunk-HHWDTB6S.js +59 -0
- package/dist/chunk-HKO45NLZ.js +286 -0
- package/dist/chunk-HLVTEA6A.js +89 -0
- package/dist/chunk-HS3SY6HN.js +160 -0
- package/dist/chunk-HT5OCGKN.js +899 -0
- package/dist/chunk-HXIIEMGF.js +78 -0
- package/dist/chunk-I56TH7VM.js +46 -0
- package/dist/chunk-I6FN3R5D.js +352 -0
- package/dist/chunk-I7SCBHJN.js +655 -0
- package/dist/chunk-IJM4GKAT.js +529 -0
- package/dist/chunk-IMEYU36R.js +344 -0
- package/dist/chunk-INJ3SS6A.js +936 -0
- package/dist/chunk-IUKOPCCE.js +441 -0
- package/dist/chunk-IVEPZQJO.js +33 -0
- package/dist/chunk-IVZEONFC.js +43 -0
- package/dist/chunk-IWBHRSG7.js +936 -0
- package/dist/chunk-IYY3HQAD.js +484 -0
- package/dist/chunk-J2ICMYDA.js +48 -0
- package/dist/chunk-J2QCFLUN.js +505 -0
- package/dist/chunk-J5HM6V5P.js +1039 -0
- package/dist/chunk-J6NZX6QH.js +398 -0
- package/dist/chunk-JBBB2Y47.js +432 -0
- package/dist/chunk-JDCZQZU7.js +42 -0
- package/dist/chunk-JEDNXGSS.js +169 -0
- package/dist/chunk-JHP7OT6K.js +89 -0
- package/dist/chunk-JLS65NUQ.js +14 -0
- package/dist/chunk-JNMWYMC6.js +38 -0
- package/dist/chunk-JNSTWNNW.js +25 -0
- package/dist/chunk-JOSK6GEU.js +9564 -0
- package/dist/chunk-JQUMSIGC.js +100 -0
- package/dist/chunk-JVHGMURQ.js +1106 -0
- package/dist/chunk-JYXS47FQ.js +951 -0
- package/dist/chunk-K2ZMW2Z5.js +2147 -0
- package/dist/chunk-K7FAFORM.js +14 -0
- package/dist/chunk-KBJXX3ON.js +333 -0
- package/dist/chunk-KHFKVQWY.js +486 -0
- package/dist/chunk-KHMEI3DQ.js +173 -0
- package/dist/chunk-KHW3UD7B.js +69 -0
- package/dist/chunk-KKQKWAMF.js +365 -0
- package/dist/chunk-KOKXJV2U.js +455 -0
- package/dist/chunk-KQ4NGOJI.js +14 -0
- package/dist/chunk-KQYQOXMS.js +342 -0
- package/dist/chunk-KTMW65UX.js +14 -0
- package/dist/chunk-KUEWV2JH.js +593 -0
- package/dist/chunk-KXYDV3HP.js +1523 -0
- package/dist/chunk-KZ4JMGZ5.js +96 -0
- package/dist/chunk-KZWGBUIO.js +2659 -0
- package/dist/chunk-L2CBG5IX.js +1892 -0
- package/dist/chunk-L2S235CU.js +314 -0
- package/dist/chunk-L32WD57F.js +98 -0
- package/dist/chunk-LDJCKAJP.js +123 -0
- package/dist/chunk-LDWNBEN7.js +541 -0
- package/dist/chunk-LJVH57G2.js +241 -0
- package/dist/chunk-LPQ7NZEN.js +209 -0
- package/dist/chunk-LRGH3CCV.js +148 -0
- package/dist/chunk-LULOGMMM.js +1133 -0
- package/dist/chunk-LWGKN2GY.js +8284 -0
- package/dist/chunk-LX4F24ZH.js +157 -0
- package/dist/chunk-M4BUEXXF.js +839 -0
- package/dist/chunk-M6UHOC4I.js +553 -0
- package/dist/chunk-MCBNWZNO.js +657 -0
- package/dist/chunk-MEMSY6VY.js +223 -0
- package/dist/chunk-MHLEZQQI.js +145 -0
- package/dist/chunk-MJ6WNSIX.js +192 -0
- package/dist/chunk-MKASIKWZ.js +442 -0
- package/dist/chunk-MNOVBYFU.js +60 -0
- package/dist/chunk-MO2BYAAF.js +2896 -0
- package/dist/chunk-MP6P7Y2A.js +180 -0
- package/dist/chunk-MQ4TFJAG.js +3365 -0
- package/dist/chunk-MT2WAIR3.js +131 -0
- package/dist/chunk-MTU7VTNN.js +1122 -0
- package/dist/chunk-MULMXX4V.js +150 -0
- package/dist/chunk-MW2DWWVA.js +147 -0
- package/dist/chunk-MY3ZPUN4.js +151 -0
- package/dist/chunk-MYLH2S4G.js +64 -0
- package/dist/chunk-MZ5WYKNA.js +28 -0
- package/dist/chunk-N3Q2JNPV.js +249 -0
- package/dist/chunk-N5DX4JAK.js +12 -0
- package/dist/chunk-NAINGKHF.js +35 -0
- package/dist/chunk-NEQUQHOX.js +223 -0
- package/dist/chunk-NHNPEZS4.js +1408 -0
- package/dist/chunk-NHTIXJCR.js +17 -0
- package/dist/chunk-NKLPMBNV.js +1154 -0
- package/dist/chunk-NMVZ5YES.js +199 -0
- package/dist/chunk-NRFRY3GE.js +256 -0
- package/dist/chunk-NTD6CSI6.js +1266 -0
- package/dist/chunk-NUO7B3SH.js +318 -0
- package/dist/chunk-NVOHIJKG.js +1319 -0
- package/dist/chunk-NXH3RX7O.js +523 -0
- package/dist/chunk-NXZ3DURF.js +257 -0
- package/dist/chunk-O26L6F7F.js +209 -0
- package/dist/chunk-O5RRIUEP.js +124 -0
- package/dist/chunk-O5X6BKAO.js +10 -0
- package/dist/chunk-O6ZQUVTR.js +31 -0
- package/dist/chunk-O7FE75QZ.js +306 -0
- package/dist/chunk-OC7Z6AJQ.js +81 -0
- package/dist/chunk-OH7IEZ63.js +307 -0
- package/dist/chunk-OI734GRS.js +115 -0
- package/dist/chunk-OJBH62DK.js +684 -0
- package/dist/chunk-OKJLVSTJ.js +247 -0
- package/dist/chunk-OMX5VL43.js +79 -0
- package/dist/chunk-ONHAMBN6.js +12 -0
- package/dist/chunk-ONYPLIVE.js +753 -0
- package/dist/chunk-OSK7QWPD.js +637 -0
- package/dist/chunk-OTXLMJKP.js +438 -0
- package/dist/chunk-OV5PKQMA.js +213 -0
- package/dist/chunk-OXK5HA4B.js +31 -0
- package/dist/chunk-OYDWFQA4.js +272 -0
- package/dist/chunk-OZAUKWHB.js +22 -0
- package/dist/chunk-P5P2EKGH.js +94 -0
- package/dist/chunk-P6SSWAKY.js +29 -0
- package/dist/chunk-P7CQGPLS.js +30 -0
- package/dist/chunk-P7RVCHQV.js +197 -0
- package/dist/chunk-PEBWDYIW.js +726 -0
- package/dist/chunk-PFCXFO7A.js +9193 -0
- package/dist/chunk-PFLDWMX6.js +168 -0
- package/dist/chunk-PHZGLQSK.js +15883 -0
- package/dist/chunk-PNFTPNSR.js +20 -0
- package/dist/chunk-PPHXX4W7.js +175 -0
- package/dist/chunk-PPZESPJT.js +262 -0
- package/dist/chunk-PRDHI2L5.js +296 -0
- package/dist/chunk-PSTKWHHN.js +32 -0
- package/dist/chunk-PV53ODOT.js +555 -0
- package/dist/chunk-PVJBTI7P.js +24 -0
- package/dist/chunk-PXKERBKX.js +100 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-Q3DHEZA5.js +85 -0
- package/dist/chunk-Q5RZDVQK.js +204 -0
- package/dist/chunk-Q64IR2Z2.js +4933 -0
- package/dist/chunk-Q6Z5UGPL.js +398 -0
- package/dist/chunk-QAMAV4DT.js +627 -0
- package/dist/chunk-QBWF4444.js +112 -0
- package/dist/chunk-QDKYT4CI.js +22805 -0
- package/dist/chunk-QFLBRWCJ.js +126 -0
- package/dist/chunk-QILAQACE.js +107 -0
- package/dist/chunk-QJ6OEH4K.js +3683 -0
- package/dist/chunk-QK3NLYTV.js +320 -0
- package/dist/chunk-QOFLUS24.js +535 -0
- package/dist/chunk-QOIASNEJ.js +98 -0
- package/dist/chunk-QP4UTSOV.js +3587 -0
- package/dist/chunk-QP5ST7RI.js +904 -0
- package/dist/chunk-QS5AGRZN.js +167 -0
- package/dist/chunk-QTAOU53N.js +107 -0
- package/dist/chunk-QTCLJHLZ.js +351 -0
- package/dist/chunk-QTOLTG4L.js +119 -0
- package/dist/chunk-QWDV4OSL.js +142 -0
- package/dist/chunk-QWMQ74KD.js +508 -0
- package/dist/chunk-QX6LOMMH.js +158 -0
- package/dist/chunk-QXBKIHY7.js +238 -0
- package/dist/chunk-QXBKTJT6.js +270 -0
- package/dist/chunk-QYQOVOLR.js +196 -0
- package/dist/chunk-R2CKS6WT.js +73 -0
- package/dist/chunk-R2TIYG7U.js +68 -0
- package/dist/chunk-R2W3LESH.js +465 -0
- package/dist/chunk-R5EXHMUN.js +100 -0
- package/dist/chunk-RBBTBCHX.js +289 -0
- package/dist/chunk-RBKLOUXA.js +121 -0
- package/dist/chunk-RCP2Y6DQ.js +86 -0
- package/dist/chunk-RCYEJSD5.js +260 -0
- package/dist/chunk-RFDVSI4N.js +134 -0
- package/dist/chunk-RGJU63FV.js +1717 -0
- package/dist/chunk-RI4EX2QE.js +18 -0
- package/dist/chunk-RIMARPCF.js +152 -0
- package/dist/chunk-RJ6RSNT6.js +42 -0
- package/dist/chunk-RNI2GUIF.js +131 -0
- package/dist/chunk-RPHUWJ3K.js +264 -0
- package/dist/chunk-RPIMAMV7.js +361 -0
- package/dist/chunk-RQFX6YRN.js +49 -0
- package/dist/chunk-RRC5BL7B.js +285 -0
- package/dist/chunk-RSYTSBOO.js +79 -0
- package/dist/chunk-RWQQIKIC.js +35 -0
- package/dist/chunk-RZUBOZH2.js +373 -0
- package/dist/chunk-S2E632FQ.js +380 -0
- package/dist/chunk-S2VQCZO4.js +22 -0
- package/dist/chunk-S2XIMQJQ.js +681 -0
- package/dist/chunk-S47ISY27.js +183 -0
- package/dist/chunk-S6I4KISU.js +142 -0
- package/dist/chunk-S6TAFQI5.js +2403 -0
- package/dist/chunk-SBBNT5ZJ.js +27 -0
- package/dist/chunk-SDJV2DTQ.js +491 -0
- package/dist/chunk-SDPIFNLD.js +33 -0
- package/dist/chunk-SF6YK3JI.js +835 -0
- package/dist/chunk-SK3ZFONO.js +976 -0
- package/dist/chunk-SKTFRFQ7.js +1775 -0
- package/dist/chunk-SNGM452K.js +2002 -0
- package/dist/chunk-STW7OFOA.js +581 -0
- package/dist/chunk-SU6UFUQY.js +1351 -0
- package/dist/chunk-SVJM3LJA.js +27 -0
- package/dist/chunk-SVUNZOPG.js +129 -0
- package/dist/chunk-T5XLUYV3.js +34 -0
- package/dist/chunk-T7E5JS6N.js +57 -0
- package/dist/chunk-TA3N5MMO.js +450 -0
- package/dist/chunk-TAJPCXSB.js +13 -0
- package/dist/chunk-TDEMDRN2.js +1639 -0
- package/dist/chunk-TDSEX5CF.js +24 -0
- package/dist/chunk-TGCVL74Y.js +28 -0
- package/dist/chunk-TGXXYDL4.js +1012 -0
- package/dist/chunk-TIKTYUJO.js +296 -0
- package/dist/chunk-TIZAJD4M.js +6029 -0
- package/dist/chunk-TLSEZZCG.js +5549 -0
- package/dist/chunk-TMBDRHAX.js +19 -0
- package/dist/chunk-TRURQUBP.js +1217 -0
- package/dist/chunk-TSQ256N5.js +152 -0
- package/dist/chunk-TUQMMFEQ.js +2244 -0
- package/dist/chunk-U424MJLC.js +81 -0
- package/dist/chunk-U4DRS6ZW.js +11 -0
- package/dist/chunk-U5KS22IN.js +204 -0
- package/dist/chunk-UAPHFJPN.js +673 -0
- package/dist/chunk-UB7LQITD.js +1040 -0
- package/dist/chunk-UBVRHGAZ.js +237 -0
- package/dist/chunk-UDNNAQN5.js +98 -0
- package/dist/chunk-UJP3XM54.js +342 -0
- package/dist/chunk-UJYDH2MW.js +305 -0
- package/dist/chunk-UKMPASIC.js +399 -0
- package/dist/chunk-UKUL3EHK.js +763 -0
- package/dist/chunk-UPHU64NB.js +1327 -0
- package/dist/chunk-URMSZXAD.js +43 -0
- package/dist/chunk-US3RT6CY.js +525 -0
- package/dist/chunk-UTDEOPKV.js +30 -0
- package/dist/chunk-UUUDKV7N.js +13 -0
- package/dist/chunk-UVKWU3SM.js +39 -0
- package/dist/chunk-V2TVPO3X.js +521 -0
- package/dist/chunk-V3KLVK7B.js +398 -0
- package/dist/chunk-V3YFFHDR.js +17 -0
- package/dist/chunk-VAG6GUBR.js +1162 -0
- package/dist/chunk-VBENAHDA.js +1814 -0
- package/dist/chunk-VBNCKCCI.js +16 -0
- package/dist/chunk-VCGSWN5M.js +1300 -0
- package/dist/chunk-VE2O2I7M.js +115 -0
- package/dist/chunk-VIFHYTI5.js +31 -0
- package/dist/chunk-VJER3FUS.js +5080 -0
- package/dist/chunk-VLMDM56Q.js +268 -0
- package/dist/chunk-VMFZEVVX.js +99 -0
- package/dist/chunk-VNDPQVUZ.js +1853 -0
- package/dist/chunk-VSPJRLGA.js +4783 -0
- package/dist/chunk-VSWC5ZXI.js +103 -0
- package/dist/chunk-VYI4DSZP.js +2017 -0
- package/dist/chunk-W4BAIQ6L.js +1212 -0
- package/dist/chunk-W7VQAL2K.js +2203 -0
- package/dist/chunk-WEI3ZDZH.js +33 -0
- package/dist/chunk-WEIUS5NT.js +26 -0
- package/dist/chunk-WEW7UFWE.js +546 -0
- package/dist/chunk-WGQAXLNA.js +384 -0
- package/dist/chunk-WJAL4JL3.js +206 -0
- package/dist/chunk-WKFOJ5ZG.js +141 -0
- package/dist/chunk-WMWQEFLN.js +2177 -0
- package/dist/chunk-WNECKKO2.js +154 -0
- package/dist/chunk-WPKLYOAM.js +195 -0
- package/dist/chunk-WSH37YQN.js +243 -0
- package/dist/chunk-WV3KQDPE.js +84 -0
- package/dist/chunk-WZFC4V4U.js +389 -0
- package/dist/chunk-XCOIBNET.js +13 -0
- package/dist/chunk-XDM65LBX.js +53 -0
- package/dist/chunk-XGU6PBO5.js +645 -0
- package/dist/chunk-XHOI4V62.js +43 -0
- package/dist/chunk-XIBVFMLQ.js +6858 -0
- package/dist/chunk-XJ2E7YEJ.js +117 -0
- package/dist/chunk-XON4ZCYX.js +69 -0
- package/dist/chunk-XQPEWJPI.js +127 -0
- package/dist/chunk-XSJTIFUY.js +31 -0
- package/dist/chunk-XT7OVWM3.js +64 -0
- package/dist/chunk-XTXYIISX.js +1104 -0
- package/dist/chunk-XV7NZL4B.js +57 -0
- package/dist/chunk-XWIZ6B5S.js +109 -0
- package/dist/chunk-XWLIKOBX.js +240 -0
- package/dist/chunk-XZOXEIN4.js +156 -0
- package/dist/chunk-XZRS7I7W.js +3673 -0
- package/dist/chunk-Y4ZDDHB6.js +44 -0
- package/dist/chunk-Y733RB7T.js +194 -0
- package/dist/chunk-YBHUB4XR.js +14931 -0
- package/dist/chunk-YKC75XK4.js +147 -0
- package/dist/chunk-YKMXFVK7.js +27 -0
- package/dist/chunk-YO5BAJ55.js +225 -0
- package/dist/chunk-YPDCLXNZ.js +568 -0
- package/dist/chunk-YSQPGVFK.js +116 -0
- package/dist/chunk-YTVVTIWJ.js +716 -0
- package/dist/chunk-YVCMDVBB.js +698 -0
- package/dist/chunk-YYTOA6DP.js +114 -0
- package/dist/chunk-Z5WGNULV.js +88 -0
- package/dist/chunk-Z6NGVVXG.js +66 -0
- package/dist/chunk-Z7CDQNH3.js +20 -0
- package/dist/chunk-ZH4G3FU5.js +187 -0
- package/dist/chunk-ZJB6SQPA.js +257 -0
- package/dist/chunk-ZL6E4HMY.js +42 -0
- package/dist/chunk-ZMLOC3DW.js +62 -0
- package/dist/chunk-ZORW64QH.js +101 -0
- package/dist/chunk-ZVVD2XGV.js +1369 -0
- package/dist/cli-utils-ER33ODC5.js +31 -0
- package/dist/cli-validation-6CHPTANV.js +146 -0
- package/dist/commit-utils-3STAY7C7.js +49 -0
- package/dist/compatibility-notice-IHNXR2JR.js +21 -0
- package/dist/compete-config-resolver-V3GDNJMM.js +125 -0
- package/dist/compete-results-XUM3PJHS.js +318 -0
- package/dist/competition-outcome-tracker-NWSMVUPG.js +62 -0
- package/dist/config-4HHE4OIK.js +238 -0
- package/dist/config-D6YAZASH.js +10 -0
- package/dist/config-MIPRDYYV.js +46 -0
- package/dist/config-audit-GA5DCUAI.js +371 -0
- package/dist/config-explain-4LMO556H.js +71 -0
- package/dist/config-loader-RGP634PC.js +51 -0
- package/dist/config-manager-J2UTO635.js +114 -0
- package/dist/config-merger-TVUJBZTS.js +14 -0
- package/dist/config-validator-H4ZVIE56.js +122 -0
- package/dist/cost-HSIV5U2Y.js +85 -0
- package/dist/data-migration-state-OAUL2WPQ.js +35 -0
- package/dist/db-migrator-NUOW5NKR.js +34 -0
- package/dist/default-paths-UILB62BN.js +49 -0
- package/dist/discovery-V6GYUJUC.js +53 -0
- package/dist/doctor-BBSMCDWE.js +271 -0
- package/dist/doctor-context-blocks-LPY75VZA.js +231 -0
- package/dist/doctor-instructions-import-JXHDDOE5.js +70 -0
- package/dist/doctor-purpose-6YJOXPBE.js +30 -0
- package/dist/domain-analyzer-S35ABUNA.js +48 -0
- package/dist/ensure-initialized-Q6PEU55O.js +78 -0
- package/dist/entry.js +922 -0
- package/dist/error-parser-core-MZFMFANN.js +70 -0
- package/dist/evaluation-context-GQBYDAEV.js +63 -0
- package/dist/evidence-plan-resolver-5FUAP3SA.js +111 -0
- package/dist/execution-data-writer-YCG5UH37.js +72 -0
- package/dist/execution-policy-applier-PWZM5LNA.js +63 -0
- package/dist/execution-preflight-DASJWWQF.js +223 -0
- package/dist/execution-roles-KNSNNB2D.js +112 -0
- package/dist/exit-code-error-QIY5DOKF.js +18 -0
- package/dist/external-temp-cleanup-CAW7LQ6R.js +360 -0
- package/dist/factory-YCYCB7AE.js +209 -0
- package/dist/file-utils-ZQYNBQBW.js +45 -0
- package/dist/flywheel-allowlist-revert-H22HA6JE.js +21 -0
- package/dist/flywheel-autoloop-executor-PDOASJEZ.js +316 -0
- package/dist/flywheel-autoloop-unverified-TOKYFCDC.js +36 -0
- package/dist/flywheel-competition-executor-YJUMMEJO.js +320 -0
- package/dist/flywheel-executor-JWU43KAY.js +437 -0
- package/dist/flywheel-git-isolation-FQ2TRWMB.js +56 -0
- package/dist/flywheel-manifest-HDPNMWWF.js +114 -0
- package/dist/flywheel-model-defaults-UHAKTZT3.js +110 -0
- package/dist/flywheel-preflight-III2C7TH.js +163 -0
- package/dist/flywheel-resume-2O45MS7Y.js +31 -0
- package/dist/flywheel-resume-execution-lease-P2BK257J.js +59 -0
- package/dist/flywheel-safety-AVAVFXIJ.js +19 -0
- package/dist/flywheel-scope-decision-ALJUUNGG.js +14 -0
- package/dist/get-telemetry-logger-MPDVO2JA.js +155 -0
- package/dist/git-message-style-KCXGMTDT.js +104 -0
- package/dist/guidance-profile-config-G3K5YQOO.js +113 -0
- package/dist/guidance-profile-renderer-TX4TNDFL.js +23 -0
- package/dist/identity-XLQJ4YJF.js +25 -0
- package/dist/index-query-6NUY4FKX.js +37 -0
- package/dist/index.js +85588 -0
- package/dist/indexer-DL54CM3K.js +44 -0
- package/dist/init-OUZ72344.js +245 -0
- package/dist/instructions-import-offer-NSYXSITC.js +363 -0
- package/dist/instructions-run-NBII7W36.js +114 -0
- package/dist/interrupted-work-E4TLKJLE.js +43 -0
- package/dist/invoke-provider-auth-NEWEIHCI.js +38 -0
- package/dist/judge-scoring.json +64 -0
- package/dist/lab-mode-YXQ2Y3NG.js +44 -0
- package/dist/lab-utils-5QAMDJR5.js +29 -0
- package/dist/linux-host-class-YDWQX65O.js +31 -0
- package/dist/llm-judge-executor-OJSNCTFJ.js +179 -0
- package/dist/local-ci-config-VZ4WWDT5.js +48 -0
- package/dist/local-executor-DCMDDRX2.js +280 -0
- package/dist/logger-5KNKS3O4.js +154 -0
- package/dist/matrix-prompt-builder-4ZBOGZSB.js +527 -0
- package/dist/matrix-result-parser-EZJM4YXP.js +14 -0
- package/dist/merge-batch-RTEEMVDB.js +313 -0
- package/dist/merge-batch-members-VIWGVRHP.js +24 -0
- package/dist/merge-queue-TIIUSOY6.js +126 -0
- package/dist/merge-queue-trust-DUNZ4J65.js +21 -0
- package/dist/metadata-UKSMWM6J.js +29 -0
- package/dist/model-catalog-readiness-R5KJBP5M.js +202 -0
- package/dist/model-invocations-db-XTUPPQ34.js +70 -0
- package/dist/model-resolver-JFZ2MHOJ.js +39 -0
- package/dist/model-resolver-SCQ57KAQ.js +217 -0
- package/dist/model-roles-D3OR4NAH.js +110 -0
- package/dist/monitor-F4SIU5G2.js +96 -0
- package/dist/nested-worktrees-2WUWQ75N.js +23 -0
- package/dist/next-S3IRLVER.js +96 -0
- package/dist/next-UKPFRXOO.js +148 -0
- package/dist/opportunistic-TL4S26VX.js +86 -0
- package/dist/optimization-config-resolver-NAEIYU6A.js +148 -0
- package/dist/package-HJR4QJT4.js +10 -0
- package/dist/parse-git-status-7PIYFPS2.js +21 -0
- package/dist/path-security-5WJSY56G.js +41 -0
- package/dist/paths-JUI7C4KU.js +45 -0
- package/dist/plan-backlog-list-fast-SSTQL5XP.js +89 -0
- package/dist/plan-backlog-show-fast-PFRAQ3JB.js +114 -0
- package/dist/plan-sprint-list-fast-HD2KBZSY.js +110 -0
- package/dist/plan-sprint-status-fast-YFYH7WQF.js +116 -0
- package/dist/plan-task-status-fast-JU5PNLUU.js +114 -0
- package/dist/plan-to-flywheel-PY5MSY2H.js +529 -0
- package/dist/preflight-PJHGTJNZ.js +12 -0
- package/dist/process-registry-B4ZUCN7T.js +28 -0
- package/dist/process-scanner-TU3QHWVB.js +23 -0
- package/dist/processes-6FHVGT72.js +28 -0
- package/dist/processes-json-fast-YWTMUWXB.js +72 -0
- package/dist/profile-HWTC56SG.js +209 -0
- package/dist/project-runtime-paths-2UQNG5Y4.js +41 -0
- package/dist/protected-branches-N36LDBFY.js +71 -0
- package/dist/provider-aware-resource-manager-YTFAHNGJ.js +40 -0
- package/dist/provider-matrix-2KO2V5OM.js +214 -0
- package/dist/provider-matrix-renderer-MEC3PER7.js +139 -0
- package/dist/provider-output-forwarding-2AHL7YMA.js +26 -0
- package/dist/provider-registry-WCZ65QHC.js +136 -0
- package/dist/provider-temp-cleanup-IUNPJCZF.js +14 -0
- package/dist/prune-engine-EOQVWEP6.js +448 -0
- package/dist/publication-turn-S42RZTQF.js +44 -0
- package/dist/quality-gate-JQMVCKOO.js +72 -0
- package/dist/quickstart-3O6FRCFB.js +105 -0
- package/dist/readiness-GW6ABRHB.js +139 -0
- package/dist/readiness-KFZCXDTT.js +215 -0
- package/dist/registry-7XG376SY.js +217 -0
- package/dist/repair-helpers-TTV5FEF4.js +117 -0
- package/dist/repo-root-YG6MQZDW.js +28 -0
- package/dist/resolution-engine-FFEK7HQ2.js +187 -0
- package/dist/resolve-provider-cli-ZEXVZKET.js +44 -0
- package/dist/restore-telemetry-db-E7BR5PO7.js +459 -0
- package/dist/routing-events-AW6SRZ6G.js +13 -0
- package/dist/routing-history-store-FMDDQL6W.js +39 -0
- package/dist/routing-planner-C6OHO3EY.js +221 -0
- package/dist/runner-KHDNQ5KU.js +1194 -0
- package/dist/runner-PIS2BW7Y.js +1801 -0
- package/dist/safety-E3JJVSFL.js +67 -0
- package/dist/safety-stanza-HE6GXP4G.js +49 -0
- package/dist/sandbox-7LBZC5PE.js +72 -0
- package/dist/sandbox-ASWCDYZL.js +80 -0
- package/dist/schema-extensions.sql +234 -0
- package/dist/security-JI22TBEB.js +17 -0
- package/dist/setup-bb-plugin-FQXLA76Y.js +531 -0
- package/dist/setup-claude-code-NF4ZSHVE.js +197 -0
- package/dist/setup-temp-cleanup-2APZ4HMA.js +29 -0
- package/dist/shared-utils-34HJZ6JX.js +40 -0
- package/dist/simple-artifact-goal-MQLDDMMX.js +29 -0
- package/dist/sprint-N7JFTEGL.js +403 -0
- package/dist/sprint-bridge-KJXIBNRJ.js +117 -0
- package/dist/sprint-completion-dependents-34SR7JVL.js +43 -0
- package/dist/sprint-execution-service-A6JPUBWY.js +376 -0
- package/dist/sprint-manager-MU2ZUWN2.js +140 -0
- package/dist/sqlite-timeout-config-U2CT4LKU.js +13 -0
- package/dist/sqlite-wrapper-FSGJ2QEW.js +19 -0
- package/dist/stale-cleanup-orchestrator-4X6324VF.js +71 -0
- package/dist/storage-access-I3V5LER7.js +31 -0
- package/dist/store-XVG64ZR2.js +25 -0
- package/dist/task-classifier-MN2DYE5R.js +558 -0
- package/dist/task-list-fast-7AT6LTTA.js +112 -0
- package/dist/task-manager-LHPBBRRE.js +136 -0
- package/dist/task-report-OLPAWBTK.js +64 -0
- package/dist/task-type-detector-6RG3YNZF.js +27 -0
- package/dist/task-type-resolver-EQHOUIHT.js +64 -0
- package/dist/telemetry-5VYSK772.js +212 -0
- package/dist/telemetry-health-tracker-4GFW2EMP.js +12 -0
- package/dist/telemetry-path-resolver-H3NFHHQY.js +34 -0
- package/dist/telemetry-reliability-URJDTNPZ.js +76 -0
- package/dist/transports-NREZQTJK.js +106 -0
- package/dist/unified-cost-tracker-5MTBJ2CL.js +59 -0
- package/dist/unified-state-cleanup-GGAXLHXF.js +292 -0
- package/dist/universal-cost-calculator-JTFEJVWK.js +70 -0
- package/dist/user-agents-QLIGPLP2.js +30 -0
- package/dist/variant-execution-I4ZZUAWY.js +319 -0
- package/dist/variant-opt-parser-JHCXKY2K.js +251 -0
- package/dist/variant-request-input-PL37N3AN.js +11 -0
- package/dist/variant-state-cleanup-KHMXRBZN.js +287 -0
- package/dist/variant-worker-COB5YTJX.js +4517 -0
- package/dist/variant-worker.js +26 -0
- package/dist/variants-XGDSXYG3.js +267 -0
- package/dist/verification-command-YSPU4VBH.js +66 -0
- package/dist/verify-command-timeout-DQUYH33Z.js +11 -0
- package/dist/verify-commands-LOHMOFKC.js +65 -0
- package/dist/warning-JSKSFKBT.js +13 -0
- package/dist/work-edge-K65NHVWY.js +50 -0
- package/dist/work-item-writer-KY5ZWZWW.js +30 -0
- package/dist/worker-JNXXEIT6.js +1504 -0
- package/dist/worker.js +21 -0
- package/dist/workspace-tree-identity-JO5PSKTW.js +52 -0
- package/dist/worktree-environment-WTVABAQW.js +64 -0
- package/dist/worktree-metrics-VZRQI26T.js +66 -0
- package/dist/writeback-TQFQBJNU.js +50 -0
- package/docs/CLI_REFERENCE.md +8287 -0
- package/docs/PROVIDER_SETUP.md +1737 -0
- package/docs/README.md +96 -0
- package/docs/TROUBLESHOOTING.md +2209 -0
- package/docs/getting-started/QUICK_START.md +260 -0
- package/docs/getting-started/README.md +198 -0
- package/docs/getting-started/SETUP.md +25 -0
- package/docs/reference/AGENT_CONTEXT.md +84 -0
- package/docs/reference/DELIVERY_ONBOARDING.md +198 -0
- package/docs/reference/PROVIDER_RELEASE_TIERS.md +34 -0
- package/docs/reference/SECURITY.md +291 -0
- package/docs/reference/TELEMETRY_PRIVACY.md +259 -0
- package/integrations/bb-plugin-build/README.md +391 -0
- package/integrations/bb-plugin-build/app.css +431 -0
- package/integrations/bb-plugin-build/app.tsx +6210 -0
- package/integrations/bb-plugin-build/package.json +45 -0
- package/integrations/bb-plugin-build/server.ts +488 -0
- package/integrations/bb-plugin-build/src/adapter.ts +3672 -0
- package/integrations/bb-plugin-build/src/backlog-authoring-model.ts +150 -0
- package/integrations/bb-plugin-build/src/backlog-authoring-schema.ts +458 -0
- package/integrations/bb-plugin-build/src/backlog-authoring-service.ts +467 -0
- package/integrations/bb-plugin-build/src/backlog-authoring-view.ts +1025 -0
- package/integrations/bb-plugin-build/src/command-evidence-schema.ts +13 -0
- package/integrations/bb-plugin-build/src/competition-defaults-schema.ts +122 -0
- package/integrations/bb-plugin-build/src/competition-defaults-service.ts +105 -0
- package/integrations/bb-plugin-build/src/competition-defaults-view.ts +138 -0
- package/integrations/bb-plugin-build/src/contract.ts +718 -0
- package/integrations/bb-plugin-build/src/execution-activity-view.ts +98 -0
- package/integrations/bb-plugin-build/src/finalize-model.ts +205 -0
- package/integrations/bb-plugin-build/src/finalize-schema.ts +150 -0
- package/integrations/bb-plugin-build/src/finalize-service.ts +330 -0
- package/integrations/bb-plugin-build/src/finalize-view.ts +159 -0
- package/integrations/bb-plugin-build/src/judge-operations-readback.ts +303 -0
- package/integrations/bb-plugin-build/src/judge-operations-schema.ts +33 -0
- package/integrations/bb-plugin-build/src/judge-operations-view.ts +459 -0
- package/integrations/bb-plugin-build/src/model.ts +1318 -0
- package/integrations/bb-plugin-build/src/monitor-service.ts +233 -0
- package/integrations/bb-plugin-build/src/panel-read-ux.ts +97 -0
- package/integrations/bb-plugin-build/src/patch-preview-schema.ts +166 -0
- package/integrations/bb-plugin-build/src/patch-preview-service.ts +480 -0
- package/integrations/bb-plugin-build/src/patch-preview-view.ts +109 -0
- package/integrations/bb-plugin-build/src/planning-readback.ts +228 -0
- package/integrations/bb-plugin-build/src/planning-service.ts +647 -0
- package/integrations/bb-plugin-build/src/planning-sync-evidence.ts +37 -0
- package/integrations/bb-plugin-build/src/planning-workspace-readback.ts +33 -0
- package/integrations/bb-plugin-build/src/planning-workspace-schema.ts +131 -0
- package/integrations/bb-plugin-build/src/planning-workspace.ts +333 -0
- package/integrations/bb-plugin-build/src/poll-handoff.ts +136 -0
- package/integrations/bb-plugin-build/src/project-target-server.ts +21 -0
- package/integrations/bb-plugin-build/src/project-target.ts +45 -0
- package/integrations/bb-plugin-build/src/reference-index.ts +218 -0
- package/integrations/bb-plugin-build/src/repository-identity.ts +17 -0
- package/integrations/bb-plugin-build/src/repository-schema.ts +30 -0
- package/integrations/bb-plugin-build/src/result-explorer-view.ts +597 -0
- package/integrations/bb-plugin-build/src/result-readback-model.ts +163 -0
- package/integrations/bb-plugin-build/src/result-readback-schema.ts +319 -0
- package/integrations/bb-plugin-build/src/result-readback-service.ts +333 -0
- package/integrations/bb-plugin-build/src/run-page-view.ts +332 -0
- package/integrations/bb-plugin-build/src/setup-config-schema.ts +293 -0
- package/integrations/bb-plugin-build/src/setup-config-service.ts +620 -0
- package/integrations/bb-plugin-build/src/setup-config-view.ts +388 -0
- package/integrations/bb-plugin-build/src/sprint-close-model.ts +150 -0
- package/integrations/bb-plugin-build/src/sprint-close-schema.ts +128 -0
- package/integrations/bb-plugin-build/src/sprint-close-service.ts +293 -0
- package/integrations/bb-plugin-build/src/sprint-close-view.ts +116 -0
- package/integrations/bb-plugin-build/src/sprint-composition-model.ts +709 -0
- package/integrations/bb-plugin-build/src/sprint-composition-readback.ts +231 -0
- package/integrations/bb-plugin-build/src/sprint-composition-schema.ts +597 -0
- package/integrations/bb-plugin-build/src/sprint-composition-service.ts +773 -0
- package/integrations/bb-plugin-build/src/sprint-composition-view.ts +1221 -0
- package/integrations/bb-plugin-build/src/sprint-execution-schema.ts +188 -0
- package/integrations/bb-plugin-build/src/sprint-execution-service.ts +289 -0
- package/integrations/bb-plugin-build/src/sprint-execution-view.ts +54 -0
- package/integrations/bb-plugin-build/src/task-authoring-model.ts +563 -0
- package/integrations/bb-plugin-build/src/task-authoring-schema.ts +629 -0
- package/integrations/bb-plugin-build/src/task-authoring-service.ts +842 -0
- package/integrations/bb-plugin-build/src/task-authoring-view.ts +1227 -0
- package/integrations/bb-plugin-build/src/task-catalog-schema.ts +46 -0
- package/integrations/bb-plugin-build/src/task-catalog-service.ts +44 -0
- package/integrations/bb-plugin-build/src/task-detail-readback.ts +56 -0
- package/integrations/bb-plugin-build/src/task-detail-schema.ts +171 -0
- package/integrations/bb-plugin-build/src/task-navigation.ts +446 -0
- package/integrations/bb-plugin-build/src/workbench-route.ts +74 -0
- package/package.json +306 -4
- package/schemas/README.md +80 -0
- package/schemas/build.config.schema.json +2110 -0
- package/schemas/config-v1.schema.json +136 -0
- package/schemas/execution-config.schema.json +47 -0
- package/schemas/judge-scoring.schema.json +257 -0
- package/schemas/provider-config.schema.json +179 -0
- package/schemas/telemetry-config.schema.json +79 -0
- package/schemas/user-preferences.schema.json +141 -0
- package/scripts/assert-node-runtime.mjs +146 -0
- package/scripts/preflight-native.mjs +49 -0
- package/scripts/setup-git-hooks.mjs +39 -0
- package/scripts/sync.sh +2871 -0
- package/scripts/write-node-launcher.sh +138 -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/.build-project/README.md +41 -0
- package/templates/.build-project/agents/README.md +50 -0
- package/templates/.build-project/agents/architect.md +10 -0
- package/templates/.build-project/agents/code-reviewer.md +10 -0
- package/templates/.build-project/agents/debugger.md +10 -0
- package/templates/.build-project/agents/documenter.md +10 -0
- package/templates/.build-project/agents/test-automator.md +10 -0
- package/templates/.build-project/config/build.config.jsonc +64 -0
- package/templates/.build-project/config/project-context.template.json +6 -0
- package/templates/.build-project/config/task-type-aliases.presets/kanban.yaml +14 -0
- package/templates/.build-project/config/task-type-aliases.presets/scrum.yaml +17 -0
- package/templates/.build-project/config/task-type-aliases.presets/xp.yaml +12 -0
- package/templates/.build-project/config/task-type-aliases.yaml +29 -0
- package/templates/.build-project/gitignore.template +70 -0
- package/templates/.build-project/task-types/analysis.yaml +38 -0
- package/templates/.build-project/task-types/architecture.yaml +35 -0
- package/templates/.build-project/task-types/doc.yaml +34 -0
- package/templates/.build-project/task-types/feature.yaml +22 -0
- package/templates/.build-project/task-types/general.yaml +4 -0
- package/templates/.build-project/task-types/security.yaml +38 -0
- package/templates/AGENTS.template.md +85 -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/skills/code-review/SKILL.md +61 -0
- package/templates/builtin-workflows/skills/execution-contract-analysis/SKILL.md +24 -0
- package/templates/builtin-workflows/skills/execution-contract-code/SKILL.md +19 -0
- package/templates/builtin-workflows/skills/execution-contract-structured/SKILL.md +17 -0
- package/templates/ci/incident-intake.mjs +787 -0
- package/templates/ci/incidents.yml.template +103 -0
- package/templates/ci/node.yml.template +17 -0
- package/templates/ci/python.yml.template +17 -0
- package/templates/guard-kit/GUARD_SETUP.md.template +258 -0
- package/templates/guard-kit/build-guard.yml.template +189 -0
- package/templates/guard-kit/check.mjs.template +932 -0
- package/templates/guard-kit/config.json.template +7 -0
- package/templates/guard-kit/policy.json.template +1 -0
- package/templates/profiles/README.md +57 -0
- package/templates/profiles/frontier-claude.json +23 -0
- package/templates/profiles/frontier-codex.json +23 -0
|
@@ -0,0 +1,4057 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getWorktreeManager
|
|
3
|
+
} from "./chunk-57WRCJJW.js";
|
|
4
|
+
import {
|
|
5
|
+
isPathSafeIdentifier,
|
|
6
|
+
parseArtifactRef
|
|
7
|
+
} from "./chunk-SK3ZFONO.js";
|
|
8
|
+
import {
|
|
9
|
+
decideDistRebuild,
|
|
10
|
+
resolveDistOwnership,
|
|
11
|
+
writeDistBuildStamp
|
|
12
|
+
} from "./chunk-WKFOJ5ZG.js";
|
|
13
|
+
import {
|
|
14
|
+
normalizeProviderForEstimator
|
|
15
|
+
} from "./chunk-VJER3FUS.js";
|
|
16
|
+
import {
|
|
17
|
+
findInstalledDependencyDrift
|
|
18
|
+
} from "./chunk-BIUV23HH.js";
|
|
19
|
+
import {
|
|
20
|
+
isProviderFailureKeptRecord
|
|
21
|
+
} from "./chunk-UJYDH2MW.js";
|
|
22
|
+
import {
|
|
23
|
+
validateCompetitionJudgeConfig
|
|
24
|
+
} from "./chunk-EJVCDTAN.js";
|
|
25
|
+
import {
|
|
26
|
+
extractCreateAnnotatedFilePaths,
|
|
27
|
+
parseFileDirectiveLines
|
|
28
|
+
} from "./chunk-ETA2K4KI.js";
|
|
29
|
+
import {
|
|
30
|
+
getProviderHealth
|
|
31
|
+
} from "./chunk-CRPRVNQ4.js";
|
|
32
|
+
import {
|
|
33
|
+
isValidCheckPath,
|
|
34
|
+
validateCompletionContracts
|
|
35
|
+
} from "./chunk-MCBNWZNO.js";
|
|
36
|
+
import {
|
|
37
|
+
getBucketPathByKind
|
|
38
|
+
} from "./chunk-V3KLVK7B.js";
|
|
39
|
+
import {
|
|
40
|
+
applyFlywheelModelDefaults
|
|
41
|
+
} from "./chunk-DSRLVYBS.js";
|
|
42
|
+
import {
|
|
43
|
+
WORKTREE_RECORD_RECOVERY_REASON,
|
|
44
|
+
getProjectRoot,
|
|
45
|
+
getWorktreeRecordsByStatus,
|
|
46
|
+
listManifests,
|
|
47
|
+
reconcileAndSaveManifest,
|
|
48
|
+
updateWorktreeRecordStatus
|
|
49
|
+
} from "./chunk-6TRD7TEE.js";
|
|
50
|
+
import {
|
|
51
|
+
signalCoordinator
|
|
52
|
+
} from "./chunk-GC4CRMTS.js";
|
|
53
|
+
import {
|
|
54
|
+
resolveDefaultProvider
|
|
55
|
+
} from "./chunk-C3DH3NCY.js";
|
|
56
|
+
import {
|
|
57
|
+
getProviderReadiness
|
|
58
|
+
} from "./chunk-XTXYIISX.js";
|
|
59
|
+
import {
|
|
60
|
+
pruneGitWorktreeEntries
|
|
61
|
+
} from "./chunk-33IGPPUI.js";
|
|
62
|
+
import {
|
|
63
|
+
WorktreeCleanupManager
|
|
64
|
+
} from "./chunk-UPHU64NB.js";
|
|
65
|
+
import {
|
|
66
|
+
filterWorktreeRunSentinelStatusOutput,
|
|
67
|
+
isActiveRun,
|
|
68
|
+
withCleanupLock
|
|
69
|
+
} from "./chunk-2NMNFUQA.js";
|
|
70
|
+
import {
|
|
71
|
+
tryRemoveWorktreeNestedAware
|
|
72
|
+
} from "./chunk-UJP3XM54.js";
|
|
73
|
+
import {
|
|
74
|
+
ModelMetadataResolver,
|
|
75
|
+
findOpencodeFallbackContextWindow,
|
|
76
|
+
getOpencodeCatalogEntry,
|
|
77
|
+
isExactCompatibilityMatch,
|
|
78
|
+
resolveOpencodeContextWindow
|
|
79
|
+
} from "./chunk-YVCMDVBB.js";
|
|
80
|
+
import {
|
|
81
|
+
assertSafePathComponent
|
|
82
|
+
} from "./chunk-S2VQCZO4.js";
|
|
83
|
+
import {
|
|
84
|
+
stripBuildContextBlocks
|
|
85
|
+
} from "./chunk-KUEWV2JH.js";
|
|
86
|
+
import {
|
|
87
|
+
classifyBranchRole,
|
|
88
|
+
isProtectedPipelineBranch
|
|
89
|
+
} from "./chunk-XGU6PBO5.js";
|
|
90
|
+
import {
|
|
91
|
+
buildBranchKindPrefix,
|
|
92
|
+
buildVariantBranchPrefixes,
|
|
93
|
+
isBuildOwnedBranch,
|
|
94
|
+
runChildBranchTargets,
|
|
95
|
+
sanitizeRefPart
|
|
96
|
+
} from "./chunk-F55YABKV.js";
|
|
97
|
+
import {
|
|
98
|
+
summarizeRepoPaths
|
|
99
|
+
} from "./chunk-RBBTBCHX.js";
|
|
100
|
+
import {
|
|
101
|
+
checkVerifyCommandSafety
|
|
102
|
+
} from "./chunk-AHU3FKPA.js";
|
|
103
|
+
import {
|
|
104
|
+
isDualContainedInManagedRoots,
|
|
105
|
+
safeRmDir
|
|
106
|
+
} from "./chunk-ALDTFNZ3.js";
|
|
107
|
+
import {
|
|
108
|
+
parsePorcelainLinePaths
|
|
109
|
+
} from "./chunk-5KQETTNS.js";
|
|
110
|
+
import {
|
|
111
|
+
splitClaudeModelContextSuffix
|
|
112
|
+
} from "./chunk-H3HGMISG.js";
|
|
113
|
+
import {
|
|
114
|
+
execGit
|
|
115
|
+
} from "./chunk-MHLEZQQI.js";
|
|
116
|
+
import {
|
|
117
|
+
ProviderRegistry
|
|
118
|
+
} from "./chunk-QJ6OEH4K.js";
|
|
119
|
+
import {
|
|
120
|
+
isJsonOutputRequested,
|
|
121
|
+
isQuietOutputRequested,
|
|
122
|
+
isVerboseOutputRequested
|
|
123
|
+
} from "./chunk-XWIZ6B5S.js";
|
|
124
|
+
import {
|
|
125
|
+
GIT_COMMAND_TIMEOUT_MS
|
|
126
|
+
} from "./chunk-I56TH7VM.js";
|
|
127
|
+
import {
|
|
128
|
+
WORKTREE_HYGIENE_MAX_MANIFESTS_PER_PASS,
|
|
129
|
+
WORKTREE_HYGIENE_MAX_RECOVERY_RECORDS_PER_PASS,
|
|
130
|
+
WORKTREE_HYGIENE_MAX_RETAINED_TOTAL_BYTES,
|
|
131
|
+
WORKTREE_HYGIENE_MAX_RETENTION_COLLATION,
|
|
132
|
+
WORKTREE_HYGIENE_MAX_RETENTION_PRUNE_OPS,
|
|
133
|
+
getBuildFlywheelManifestsRoot,
|
|
134
|
+
getBuildRepoWorktreePoolRoot,
|
|
135
|
+
getWorktreeBaseDir
|
|
136
|
+
} from "./chunk-Z5WGNULV.js";
|
|
137
|
+
|
|
138
|
+
// src/automation/flywheel-preflight.ts
|
|
139
|
+
import { appendFileSync, existsSync as existsSync2, readFileSync } from "fs";
|
|
140
|
+
import * as fs3 from "fs/promises";
|
|
141
|
+
import os2 from "os";
|
|
142
|
+
import * as path4 from "path";
|
|
143
|
+
import { fileURLToPath } from "url";
|
|
144
|
+
import chalk from "chalk";
|
|
145
|
+
import { execa as execa2 } from "execa";
|
|
146
|
+
|
|
147
|
+
// src/orchestrator/token-estimator.ts
|
|
148
|
+
var UTILIZATION_WARN_PERCENT = 80;
|
|
149
|
+
var UTILIZATION_SPLIT_PERCENT = 85;
|
|
150
|
+
var CONTEXT_WINDOWS = {
|
|
151
|
+
// Provider marker for normalization; Claude context windows return early
|
|
152
|
+
// through ProviderRegistry so model metadata stays in providers.yaml.
|
|
153
|
+
claude: {},
|
|
154
|
+
// Provider marker for normalization; Codex context windows return early
|
|
155
|
+
// through ProviderRegistry so model metadata stays in providers.yaml.
|
|
156
|
+
codex: {},
|
|
157
|
+
// Compatibility-only estimator bucket for OpenAI-family model names.
|
|
158
|
+
// This is not a runnable Build provider; Codex owns OpenAI-backed CLI execution.
|
|
159
|
+
openai: {},
|
|
160
|
+
// Provider marker for normalization; Gemini context windows return early
|
|
161
|
+
// through ProviderRegistry so model metadata stays in providers.yaml.
|
|
162
|
+
gemini: {},
|
|
163
|
+
// Provider marker for normalization; Cursor context windows resolve through
|
|
164
|
+
// ProviderRegistry first and keep conservative fallbacks for legacy aliases.
|
|
165
|
+
cursor: {},
|
|
166
|
+
// Provider marker for normalization; OpenCode context windows return early
|
|
167
|
+
// through the provider catalog so runtime and estimator share one fallback table.
|
|
168
|
+
opencode: {},
|
|
169
|
+
// Provider marker for normalization; Copilot context windows return early
|
|
170
|
+
// through ProviderRegistry, with legacy fallbacks for removed aliases.
|
|
171
|
+
copilot: {},
|
|
172
|
+
// Provider marker for normalization; Grok context windows resolve through
|
|
173
|
+
// ModelMetadataResolver (config override, harness window, model maximum).
|
|
174
|
+
// Grok is not an OpenAI-tokenizer provider, so it uses the generic heuristic.
|
|
175
|
+
grok: {}
|
|
176
|
+
};
|
|
177
|
+
function lookupProviderEntry(table, provider) {
|
|
178
|
+
return Object.hasOwn(table, provider) ? table[provider] : void 0;
|
|
179
|
+
}
|
|
180
|
+
function isKnownEstimatorProviderInput(provider) {
|
|
181
|
+
const normalizedProvider = (provider || "").toLowerCase().trim();
|
|
182
|
+
if (!normalizedProvider) return false;
|
|
183
|
+
if (normalizedProvider in CONTEXT_WINDOWS) return true;
|
|
184
|
+
return ESTIMATOR_PROVIDER_ALIASES.has(normalizedProvider);
|
|
185
|
+
}
|
|
186
|
+
var DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
187
|
+
var GPT_FAMILY = "gpt";
|
|
188
|
+
var CLAUDE_FAMILY = "claude";
|
|
189
|
+
var ESTIMATOR_PROVIDER_ALIASES = /* @__PURE__ */ new Set(["anthropic", "google", "github", "opencode"]);
|
|
190
|
+
var OPENAI_TOKENIZER_PROVIDERS = /* @__PURE__ */ new Set([
|
|
191
|
+
"codex",
|
|
192
|
+
"openai",
|
|
193
|
+
"copilot"
|
|
194
|
+
]);
|
|
195
|
+
var CLAUDE_TOKENIZER_PROVIDERS = /* @__PURE__ */ new Set(["claude"]);
|
|
196
|
+
var GPT_4_TIKTOKEN_MODEL = [GPT_FAMILY, "4"].join("-");
|
|
197
|
+
var GPT_4_TURBO_MODEL = [GPT_FAMILY, "4", "turbo"].join("-");
|
|
198
|
+
var GPT_4_TURBO_PREVIEW_TIKTOKEN_MODEL = [GPT_FAMILY, "4", "turbo", "preview"].join("-");
|
|
199
|
+
var GPT_4O_TIKTOKEN_MODEL = `${GPT_FAMILY}-4o`;
|
|
200
|
+
var GPT_4O_MINI_TIKTOKEN_MODEL = `${GPT_FAMILY}-4o-mini`;
|
|
201
|
+
var GPT_3_5_TURBO_TIKTOKEN_MODEL = `${GPT_FAMILY}-3.5-turbo`;
|
|
202
|
+
var GPT_3_5_TIKTOKEN_ALIAS = `${GPT_FAMILY}-3.5`;
|
|
203
|
+
var GPT_5_TIKTOKEN_ALIAS = [GPT_FAMILY, "5"].join("-");
|
|
204
|
+
var GPT_5_CODEX_MINI_TIKTOKEN_ALIAS = [GPT_FAMILY, "5", "codex", "mini"].join("-");
|
|
205
|
+
var CLAUDE_OPUS_4_1_COMPAT_MODEL = [CLAUDE_FAMILY, "opus", "4", "1"].join("-");
|
|
206
|
+
var CLAUDE_3_OPUS_COMPAT_MODEL = [CLAUDE_FAMILY, "3", "opus"].join("-");
|
|
207
|
+
var CLAUDE_3_SONNET_COMPAT_MODEL = [CLAUDE_FAMILY, "3", "sonnet"].join("-");
|
|
208
|
+
var CLAUDE_3_HAIKU_COMPAT_MODEL = [CLAUDE_FAMILY, "3", "haiku"].join("-");
|
|
209
|
+
var CLAUDE_LEGACY_CONTEXT_WINDOWS = {
|
|
210
|
+
[CLAUDE_OPUS_4_1_COMPAT_MODEL]: 2e5,
|
|
211
|
+
"sonnet-4": 2e5,
|
|
212
|
+
"sonnet-3-5": 2e5,
|
|
213
|
+
"sonnet-3": 2e5,
|
|
214
|
+
[CLAUDE_3_OPUS_COMPAT_MODEL]: 2e5,
|
|
215
|
+
[CLAUDE_3_SONNET_COMPAT_MODEL]: 2e5,
|
|
216
|
+
[CLAUDE_3_HAIKU_COMPAT_MODEL]: 2e5
|
|
217
|
+
};
|
|
218
|
+
var CODEX_LEGACY_CONTEXT_WINDOWS = {
|
|
219
|
+
codex: 8e3
|
|
220
|
+
};
|
|
221
|
+
var COPILOT_LEGACY_CONTEXT_WINDOWS = {
|
|
222
|
+
[GPT_4_TIKTOKEN_MODEL]: 128e3,
|
|
223
|
+
[GPT_3_5_TURBO_TIKTOKEN_MODEL]: 16e3
|
|
224
|
+
};
|
|
225
|
+
function normalizeModelKey(model) {
|
|
226
|
+
return model.trim().toLowerCase();
|
|
227
|
+
}
|
|
228
|
+
function findFallbackContextWindowMatch(model, windows) {
|
|
229
|
+
const normalizedModel = normalizeModelKey(model);
|
|
230
|
+
if (normalizedModel === "") {
|
|
231
|
+
return void 0;
|
|
232
|
+
}
|
|
233
|
+
const exactMatch = windows[normalizedModel];
|
|
234
|
+
if (exactMatch !== void 0) {
|
|
235
|
+
return { contextWindow: exactMatch, exact: true };
|
|
236
|
+
}
|
|
237
|
+
for (const [knownModel, contextWindow] of Object.entries(windows)) {
|
|
238
|
+
if (normalizedModel.includes(knownModel) || knownModel.includes(normalizedModel)) {
|
|
239
|
+
return { contextWindow, exact: false };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return void 0;
|
|
243
|
+
}
|
|
244
|
+
var DEFAULT_CHARS_PER_TOKEN = 4;
|
|
245
|
+
var HEURISTIC_CHARS_PER_TOKEN = {
|
|
246
|
+
// Gemini uses similar tokenization to GPT-4 (~4 chars per token)
|
|
247
|
+
gemini: 4
|
|
248
|
+
};
|
|
249
|
+
var MODEL_CONFIDENCE_RESOLVERS = {
|
|
250
|
+
openai: (_provider, model) => getOpenAiCompatibilityConfidence(model) ?? 0.3,
|
|
251
|
+
opencode: (_provider, model) => getOpencodeCatalogConfidence(model) ?? 0.3,
|
|
252
|
+
cursor: (_provider, model) => normalizeModelKey(model) === "auto" ? 0.8 : void 0,
|
|
253
|
+
claude: (provider, model) => {
|
|
254
|
+
const legacyConfidence = getLegacyContextConfidence(model, CLAUDE_LEGACY_CONTEXT_WINDOWS);
|
|
255
|
+
if (legacyConfidence === 1) {
|
|
256
|
+
return legacyConfidence;
|
|
257
|
+
}
|
|
258
|
+
const registryConfidence = getProviderRegistryModelConfidence(provider, model);
|
|
259
|
+
if (registryConfidence !== void 0) {
|
|
260
|
+
return registryConfidence;
|
|
261
|
+
}
|
|
262
|
+
return legacyConfidence;
|
|
263
|
+
},
|
|
264
|
+
codex: (_provider, model) => CODEX_LEGACY_CONTEXT_WINDOWS[normalizeModelKey(model)] !== void 0 ? 0.9 : void 0,
|
|
265
|
+
copilot: (provider, model) => {
|
|
266
|
+
const exactLegacyConfidence = COPILOT_LEGACY_CONTEXT_WINDOWS[normalizeModelKey(model)] !== void 0 ? 0.9 : void 0;
|
|
267
|
+
if (exactLegacyConfidence !== void 0) {
|
|
268
|
+
return exactLegacyConfidence;
|
|
269
|
+
}
|
|
270
|
+
const registryConfidence = getProviderRegistryModelConfidence(provider, model);
|
|
271
|
+
if (registryConfidence !== void 0) {
|
|
272
|
+
return registryConfidence;
|
|
273
|
+
}
|
|
274
|
+
return getLegacyContextConfidence(model, COPILOT_LEGACY_CONTEXT_WINDOWS, 0.9) ?? 0.3;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
var CONTEXT_WINDOW_RESOLVERS = {
|
|
278
|
+
opencode: (model) => resolveOpencodeContextWindow(model, DEFAULT_CONTEXT_WINDOW),
|
|
279
|
+
claude: (model) => {
|
|
280
|
+
const baseModel = splitClaudeModelContextSuffix(model).base;
|
|
281
|
+
return CLAUDE_LEGACY_CONTEXT_WINDOWS[normalizeModelKey(baseModel)];
|
|
282
|
+
},
|
|
283
|
+
codex: (model) => CODEX_LEGACY_CONTEXT_WINDOWS[normalizeModelKey(model)],
|
|
284
|
+
copilot: (model) => COPILOT_LEGACY_CONTEXT_WINDOWS[normalizeModelKey(model)]
|
|
285
|
+
};
|
|
286
|
+
var PROVIDER_OVERHEAD_PROFILES = {
|
|
287
|
+
claude: {
|
|
288
|
+
systemPromptTokens: 1500,
|
|
289
|
+
outputReservePercent: 0.3
|
|
290
|
+
// 30% for Claude's verbose outputs
|
|
291
|
+
},
|
|
292
|
+
codex: {
|
|
293
|
+
systemPromptTokens: 800,
|
|
294
|
+
outputReservePercent: 0.25
|
|
295
|
+
},
|
|
296
|
+
openai: {
|
|
297
|
+
systemPromptTokens: 800,
|
|
298
|
+
outputReservePercent: 0.25
|
|
299
|
+
},
|
|
300
|
+
gemini: {
|
|
301
|
+
systemPromptTokens: 1e3,
|
|
302
|
+
outputReservePercent: 0.25
|
|
303
|
+
},
|
|
304
|
+
cursor: {
|
|
305
|
+
systemPromptTokens: 1e3,
|
|
306
|
+
outputReservePercent: 0.25
|
|
307
|
+
},
|
|
308
|
+
opencode: {
|
|
309
|
+
systemPromptTokens: 1e3,
|
|
310
|
+
outputReservePercent: 0.25
|
|
311
|
+
},
|
|
312
|
+
copilot: {
|
|
313
|
+
systemPromptTokens: 800,
|
|
314
|
+
outputReservePercent: 0.25
|
|
315
|
+
},
|
|
316
|
+
grok: {
|
|
317
|
+
systemPromptTokens: 1e3,
|
|
318
|
+
outputReservePercent: 0.25
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
var GUESSED_MODEL_MATCH_CONFIDENCE = 0.4;
|
|
322
|
+
function isGuessedModelMatch(resolved, substringMatched) {
|
|
323
|
+
if (!resolved) return false;
|
|
324
|
+
if (resolved.matchType === "fuzzy") return true;
|
|
325
|
+
return resolved.matchType === "fallback" && substringMatched;
|
|
326
|
+
}
|
|
327
|
+
function getProviderRegistryModelConfidence(provider, model) {
|
|
328
|
+
const resolved = ModelMetadataResolver.resolveModel(provider, model, { allowFuzzy: true });
|
|
329
|
+
if (!resolved) return void 0;
|
|
330
|
+
return resolved.matchType === "fuzzy" ? GUESSED_MODEL_MATCH_CONFIDENCE : 1;
|
|
331
|
+
}
|
|
332
|
+
function getLegacyContextConfidence(model, windows, exactConfidence = 1) {
|
|
333
|
+
const match = findFallbackContextWindowMatch(model, windows);
|
|
334
|
+
if (!match) {
|
|
335
|
+
return void 0;
|
|
336
|
+
}
|
|
337
|
+
return match.exact ? exactConfidence : GUESSED_MODEL_MATCH_CONFIDENCE;
|
|
338
|
+
}
|
|
339
|
+
function getOpenAiCompatibilityConfidence(model) {
|
|
340
|
+
const resolved = ModelMetadataResolver.resolveModel("openai", model, {
|
|
341
|
+
allowFallback: true,
|
|
342
|
+
allowFuzzy: true
|
|
343
|
+
});
|
|
344
|
+
if (!resolved) return void 0;
|
|
345
|
+
return isGuessedModelMatch(resolved, isExactCompatibilityMatch("openai", model) === false) ? GUESSED_MODEL_MATCH_CONFIDENCE : 1;
|
|
346
|
+
}
|
|
347
|
+
function getOpencodeCatalogConfidence(model) {
|
|
348
|
+
const catalogEntry = getOpencodeCatalogEntry(model);
|
|
349
|
+
if (catalogEntry.source === "registry") {
|
|
350
|
+
return 1;
|
|
351
|
+
}
|
|
352
|
+
const fallbackMatch = findOpencodeFallbackContextWindow(model);
|
|
353
|
+
if (fallbackMatch) {
|
|
354
|
+
return fallbackMatch.exact ? 0.9 : GUESSED_MODEL_MATCH_CONFIDENCE;
|
|
355
|
+
}
|
|
356
|
+
return void 0;
|
|
357
|
+
}
|
|
358
|
+
var DEFAULT_THRESHOLD_PERCENT = 0.65;
|
|
359
|
+
var TokenEstimator = class {
|
|
360
|
+
// tiktoken is a declared dependency; assume available at runtime.
|
|
361
|
+
// estimateOpenAITokens / estimateClaudeTokens each contain their own try/catch
|
|
362
|
+
// that falls back to chars/4 or chars/3.5 if the dynamic import fails.
|
|
363
|
+
tiketokenAvailable = true;
|
|
364
|
+
encodingCache = /* @__PURE__ */ new Map();
|
|
365
|
+
/**
|
|
366
|
+
* Analyze task to determine if splitting is needed
|
|
367
|
+
*
|
|
368
|
+
* @param prompt - Task description
|
|
369
|
+
* @param provider - Target provider ID (e.g., 'claude', 'openai', or a config-defined provider)
|
|
370
|
+
* @param model - Target model (e.g., 'claude-sonnet-4-5', 'gpt-4')
|
|
371
|
+
* @returns Decomposition decision with token analysis
|
|
372
|
+
*/
|
|
373
|
+
async analyzeTask(prompt, provider, model) {
|
|
374
|
+
const normalizedProvider = normalizeProviderForEstimator(provider, model);
|
|
375
|
+
const inputProviderKnown = isKnownEstimatorProviderInput(provider);
|
|
376
|
+
const promptForEstimation = stripBuildInjectedContext(prompt);
|
|
377
|
+
const estimatedTokens = await this.estimateTokens(
|
|
378
|
+
promptForEstimation,
|
|
379
|
+
normalizedProvider,
|
|
380
|
+
model
|
|
381
|
+
);
|
|
382
|
+
const contextWindow = this.getContextWindow(normalizedProvider, model);
|
|
383
|
+
const thresholdTokens = this.getRecommendedThreshold(normalizedProvider, model);
|
|
384
|
+
const overhead = this.getContextOverhead(normalizedProvider, model);
|
|
385
|
+
const utilizationPercent = estimatedTokens / contextWindow * 100;
|
|
386
|
+
const adjustedUtilizationPercent = (estimatedTokens + overhead.total) / contextWindow * 100;
|
|
387
|
+
const estimationConfidence = inputProviderKnown ? this.getModelConfidence(normalizedProvider, model) : 0.5;
|
|
388
|
+
const validationMetadata = this.computeValidationMetadata(
|
|
389
|
+
promptForEstimation,
|
|
390
|
+
utilizationPercent
|
|
391
|
+
);
|
|
392
|
+
if (utilizationPercent < 60) {
|
|
393
|
+
return {
|
|
394
|
+
shouldSplit: false,
|
|
395
|
+
reason: "within-threshold",
|
|
396
|
+
estimatedTokens,
|
|
397
|
+
contextWindow,
|
|
398
|
+
utilizationPercent,
|
|
399
|
+
thresholdTokens,
|
|
400
|
+
adjustedUtilizationPercent,
|
|
401
|
+
overhead,
|
|
402
|
+
estimationConfidence,
|
|
403
|
+
validationMetadata
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
const strategy = this.selectSplitStrategy(
|
|
407
|
+
promptForEstimation,
|
|
408
|
+
estimatedTokens,
|
|
409
|
+
thresholdTokens
|
|
410
|
+
);
|
|
411
|
+
return {
|
|
412
|
+
shouldSplit: true,
|
|
413
|
+
reason: "exceeds-threshold",
|
|
414
|
+
estimatedTokens,
|
|
415
|
+
contextWindow,
|
|
416
|
+
utilizationPercent,
|
|
417
|
+
thresholdTokens,
|
|
418
|
+
strategy,
|
|
419
|
+
adjustedUtilizationPercent,
|
|
420
|
+
overhead,
|
|
421
|
+
estimationConfidence,
|
|
422
|
+
validationMetadata
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Compute validation metadata for autoloop gate selection
|
|
427
|
+
*
|
|
428
|
+
* Provides hints about quality gate policy based on task characteristics:
|
|
429
|
+
* - Simple tasks with low token utilization → deterministic gates only (fast CI)
|
|
430
|
+
* - Complex tasks or high utilization → all gates (comprehensive verification)
|
|
431
|
+
*
|
|
432
|
+
* @param prompt - Task description
|
|
433
|
+
* @param utilizationPercent - Context window utilization percentage
|
|
434
|
+
* @returns Validation metadata with policy recommendation
|
|
435
|
+
*/
|
|
436
|
+
computeValidationMetadata(prompt, utilizationPercent) {
|
|
437
|
+
const rationale = [];
|
|
438
|
+
if (utilizationPercent < 40) {
|
|
439
|
+
rationale.push("Low complexity task (< 40% context usage)");
|
|
440
|
+
rationale.push("Recommend deterministic gates only for fast CI velocity");
|
|
441
|
+
return {
|
|
442
|
+
recommendedGatePolicy: "deterministic",
|
|
443
|
+
rationale
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const complexitySignals = [
|
|
447
|
+
"refactor",
|
|
448
|
+
"architecture",
|
|
449
|
+
"breaking change",
|
|
450
|
+
"migration",
|
|
451
|
+
"security"
|
|
452
|
+
];
|
|
453
|
+
const isComplex = complexitySignals.some(
|
|
454
|
+
(signal) => prompt.toLowerCase().includes(signal.toLowerCase())
|
|
455
|
+
);
|
|
456
|
+
if (isComplex || utilizationPercent >= 40) {
|
|
457
|
+
rationale.push(
|
|
458
|
+
isComplex ? "Complex task detected (refactor/architecture/migration)" : "High complexity task (\u2265 40% context usage)"
|
|
459
|
+
);
|
|
460
|
+
rationale.push("Recommend all gates for comprehensive verification");
|
|
461
|
+
return {
|
|
462
|
+
recommendedGatePolicy: "all",
|
|
463
|
+
rationale
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
rationale.push("Default to deterministic gates for CI velocity");
|
|
467
|
+
return {
|
|
468
|
+
recommendedGatePolicy: "deterministic",
|
|
469
|
+
rationale
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Estimate token count for text
|
|
474
|
+
*
|
|
475
|
+
* @param text - Text to analyze
|
|
476
|
+
* @param provider - Target provider
|
|
477
|
+
* @param model - Target model
|
|
478
|
+
* @returns Estimated token count
|
|
479
|
+
*/
|
|
480
|
+
async estimateTokens(text, provider, model) {
|
|
481
|
+
if (OPENAI_TOKENIZER_PROVIDERS.has(provider)) {
|
|
482
|
+
return this.estimateOpenAITokens(text, model);
|
|
483
|
+
}
|
|
484
|
+
if (CLAUDE_TOKENIZER_PROVIDERS.has(provider)) {
|
|
485
|
+
return this.estimateClaudeTokens(text, model);
|
|
486
|
+
}
|
|
487
|
+
const charsPerToken = lookupProviderEntry(HEURISTIC_CHARS_PER_TOKEN, provider) ?? DEFAULT_CHARS_PER_TOKEN;
|
|
488
|
+
return Math.ceil(text.length / charsPerToken);
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Estimate tokens using OpenAI's tiktoken
|
|
492
|
+
* Falls back to character-based approximation if tiktoken unavailable
|
|
493
|
+
*/
|
|
494
|
+
async estimateOpenAITokens(text, model) {
|
|
495
|
+
if (!this.tiketokenAvailable) {
|
|
496
|
+
return Math.ceil(text.length / 4);
|
|
497
|
+
}
|
|
498
|
+
try {
|
|
499
|
+
const { encoding_for_model } = await import("tiktoken");
|
|
500
|
+
const modelMap = {
|
|
501
|
+
[GPT_4_TIKTOKEN_MODEL]: GPT_4_TIKTOKEN_MODEL,
|
|
502
|
+
[GPT_4_TURBO_MODEL]: GPT_4_TURBO_PREVIEW_TIKTOKEN_MODEL,
|
|
503
|
+
[GPT_4O_TIKTOKEN_MODEL]: GPT_4O_TIKTOKEN_MODEL,
|
|
504
|
+
[GPT_4O_MINI_TIKTOKEN_MODEL]: GPT_4O_MINI_TIKTOKEN_MODEL,
|
|
505
|
+
[GPT_3_5_TURBO_TIKTOKEN_MODEL]: GPT_3_5_TURBO_TIKTOKEN_MODEL,
|
|
506
|
+
[GPT_3_5_TIKTOKEN_ALIAS]: GPT_3_5_TURBO_TIKTOKEN_MODEL,
|
|
507
|
+
[GPT_5_CODEX_MINI_TIKTOKEN_ALIAS]: GPT_4_TIKTOKEN_MODEL,
|
|
508
|
+
// Use GPT-4 tokenizer for Codex
|
|
509
|
+
[GPT_5_TIKTOKEN_ALIAS]: GPT_4_TIKTOKEN_MODEL,
|
|
510
|
+
codex: GPT_4_TIKTOKEN_MODEL
|
|
511
|
+
};
|
|
512
|
+
const tiketokenModel = modelMap[model] || GPT_4_TIKTOKEN_MODEL;
|
|
513
|
+
const cacheKey = tiketokenModel;
|
|
514
|
+
let encoding = this.encodingCache.get(cacheKey);
|
|
515
|
+
if (!encoding) {
|
|
516
|
+
encoding = encoding_for_model(
|
|
517
|
+
tiketokenModel
|
|
518
|
+
);
|
|
519
|
+
this.encodingCache.set(cacheKey, encoding);
|
|
520
|
+
}
|
|
521
|
+
const tokens = encoding.encode(text);
|
|
522
|
+
return tokens.length;
|
|
523
|
+
} catch {
|
|
524
|
+
return Math.ceil(text.length / 4);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Estimate tokens for Claude models using tiktoken cl100k_base.
|
|
529
|
+
* Falls back to chars/3.5 if tiktoken is unavailable.
|
|
530
|
+
*/
|
|
531
|
+
async estimateClaudeTokens(text, _model) {
|
|
532
|
+
if (!this.tiketokenAvailable) {
|
|
533
|
+
return Math.ceil(text.length / 3.5);
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
const { get_encoding } = await import("tiktoken");
|
|
537
|
+
const cacheKey = "cl100k_base";
|
|
538
|
+
let encoding = this.encodingCache.get(cacheKey);
|
|
539
|
+
if (!encoding) {
|
|
540
|
+
encoding = get_encoding("cl100k_base");
|
|
541
|
+
this.encodingCache.set(cacheKey, encoding);
|
|
542
|
+
}
|
|
543
|
+
const tokens = encoding.encode(text);
|
|
544
|
+
return tokens.length;
|
|
545
|
+
} catch {
|
|
546
|
+
return Math.ceil(text.length / 3.5);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Get confidence score for a provider and model
|
|
551
|
+
*
|
|
552
|
+
* @param provider - Provider ID
|
|
553
|
+
* @param model - Model identifier
|
|
554
|
+
* @returns Confidence score (0.0-1.0)
|
|
555
|
+
*/
|
|
556
|
+
getModelConfidence(provider, model) {
|
|
557
|
+
const resolverConfidence = lookupProviderEntry(MODEL_CONFIDENCE_RESOLVERS, provider)?.(
|
|
558
|
+
provider,
|
|
559
|
+
model
|
|
560
|
+
);
|
|
561
|
+
if (resolverConfidence !== void 0) {
|
|
562
|
+
return resolverConfidence;
|
|
563
|
+
}
|
|
564
|
+
return getProviderRegistryModelConfidence(provider, model) ?? 0.3;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Get context window size for provider and model
|
|
568
|
+
*
|
|
569
|
+
* @param provider - Provider ID
|
|
570
|
+
* @param model - Model identifier
|
|
571
|
+
* @returns Context window in tokens
|
|
572
|
+
*/
|
|
573
|
+
getContextWindow(provider, model) {
|
|
574
|
+
const resolvedWindow = lookupProviderEntry(CONTEXT_WINDOW_RESOLVERS, provider)?.(model);
|
|
575
|
+
if (resolvedWindow !== void 0) {
|
|
576
|
+
return resolvedWindow;
|
|
577
|
+
}
|
|
578
|
+
try {
|
|
579
|
+
return ModelMetadataResolver.getContextWindow(provider, model, {
|
|
580
|
+
allowFallback: true,
|
|
581
|
+
allowFuzzy: true
|
|
582
|
+
}) ?? DEFAULT_CONTEXT_WINDOW;
|
|
583
|
+
} catch {
|
|
584
|
+
return DEFAULT_CONTEXT_WINDOW;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Get recommended threshold in tokens (65% of context window)
|
|
589
|
+
*
|
|
590
|
+
* @param provider - Provider ID
|
|
591
|
+
* @param model - Model identifier
|
|
592
|
+
* @returns Recommended threshold in tokens
|
|
593
|
+
*/
|
|
594
|
+
getRecommendedThreshold(provider, model) {
|
|
595
|
+
const contextWindow = this.getContextWindow(provider, model);
|
|
596
|
+
return Math.floor(contextWindow * DEFAULT_THRESHOLD_PERCENT);
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Calculate context overhead for a provider and model
|
|
600
|
+
*
|
|
601
|
+
* Conservative overhead estimates:
|
|
602
|
+
* - Claude: 1500 tokens system, 30% output reserve
|
|
603
|
+
* - Codex/OpenAI: 800 tokens system, 25% output reserve
|
|
604
|
+
* - Gemini: 1000 tokens system, 25% output reserve
|
|
605
|
+
* - Other providers: 1000 tokens system, 25% output reserve
|
|
606
|
+
* - Always add 5% safety margin
|
|
607
|
+
*
|
|
608
|
+
* @param provider - Provider ID
|
|
609
|
+
* @param model - Model identifier
|
|
610
|
+
* @returns Token overhead breakdown
|
|
611
|
+
*/
|
|
612
|
+
getContextOverhead(provider, model) {
|
|
613
|
+
const contextWindow = this.getContextWindow(provider, model);
|
|
614
|
+
const profile = lookupProviderEntry(PROVIDER_OVERHEAD_PROFILES, provider) || {
|
|
615
|
+
systemPromptTokens: 1e3,
|
|
616
|
+
outputReservePercent: 0.25
|
|
617
|
+
};
|
|
618
|
+
const systemPrompt = profile.systemPromptTokens;
|
|
619
|
+
const outputReservePercent = profile.outputReservePercent;
|
|
620
|
+
const outputReserve = Math.floor(contextWindow * outputReservePercent);
|
|
621
|
+
const safetyMargin = Math.floor(contextWindow * 0.05);
|
|
622
|
+
const total = systemPrompt + outputReserve + safetyMargin;
|
|
623
|
+
return {
|
|
624
|
+
systemPrompt,
|
|
625
|
+
outputReserve,
|
|
626
|
+
safetyMargin,
|
|
627
|
+
total
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Select best split strategy based on task content and token count
|
|
632
|
+
*
|
|
633
|
+
* @param prompt - Task description
|
|
634
|
+
* @param estimatedTokens - Estimated token count
|
|
635
|
+
* @param threshold - Recommended threshold
|
|
636
|
+
* @returns Recommended split strategy
|
|
637
|
+
*/
|
|
638
|
+
selectSplitStrategy(prompt, estimatedTokens, threshold) {
|
|
639
|
+
const sampleText = prompt.length > 5e3 ? prompt.slice(0, 5e3) : prompt;
|
|
640
|
+
const fileMatches = sampleText.match(
|
|
641
|
+
/([a-zA-Z0-9_\-/]+\.(?:ts|js|tsx|jsx|py|java|go|rs|cpp|c|h))\b/g
|
|
642
|
+
);
|
|
643
|
+
if (fileMatches && fileMatches.length > 1) {
|
|
644
|
+
return "file-based";
|
|
645
|
+
}
|
|
646
|
+
const firstLine = sampleText.split("\n")[0] || "";
|
|
647
|
+
const colonPattern = /^(.+?):\s*(.+)$/;
|
|
648
|
+
const colonMatch = firstLine.match(colonPattern);
|
|
649
|
+
if (colonMatch) {
|
|
650
|
+
const featureList = colonMatch[2] || "";
|
|
651
|
+
const features = featureList.split(/\s*,\s*|\s+and\s+/i);
|
|
652
|
+
if (features.length > 1) {
|
|
653
|
+
return "feature-based";
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (estimatedTokens > threshold * 2) {
|
|
657
|
+
return "sequential";
|
|
658
|
+
}
|
|
659
|
+
return "semantic";
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Free cached encodings
|
|
663
|
+
*/
|
|
664
|
+
cleanup() {
|
|
665
|
+
for (const encoding of this.encodingCache.values()) {
|
|
666
|
+
try {
|
|
667
|
+
encoding.free?.();
|
|
668
|
+
} catch {
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
this.encodingCache.clear();
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
function stripBuildInjectedContext(prompt) {
|
|
675
|
+
if (!prompt) return "";
|
|
676
|
+
let text = stripBuildContextBlocks(String(prompt));
|
|
677
|
+
const toolPolicySplit = text.split(/\n---\n\n/);
|
|
678
|
+
if (toolPolicySplit.length > 1 && /##\s*Tool Usage Policy/i.test(toolPolicySplit[0] ?? "")) {
|
|
679
|
+
text = toolPolicySplit.slice(1).join("\n---\n\n");
|
|
680
|
+
}
|
|
681
|
+
text = text.replace(/(^|\n)\s*FILE TARGETS:\s*\n(?:\s*[-•]\s+[^\n]+\n?)+/gi, "\n");
|
|
682
|
+
return text;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// src/utils/sync-stdout-io.ts
|
|
686
|
+
import { execFileSync } from "child_process";
|
|
687
|
+
import { accessSync, constants as fsConstants, fstatSync, statSync, writeSync } from "fs";
|
|
688
|
+
var MAX_CONSECUTIVE_ZERO_BYTE_WRITES = 1e3;
|
|
689
|
+
var TRUSTED_LSOF_PATHS = ["/usr/sbin/lsof", "/usr/bin/lsof"];
|
|
690
|
+
function recordSyncWriteNoProgress(functionName, attempts) {
|
|
691
|
+
const nextAttempts = attempts + 1;
|
|
692
|
+
if (nextAttempts > MAX_CONSECUTIVE_ZERO_BYTE_WRITES) {
|
|
693
|
+
throw new Error(`${functionName} made no progress (repeated 0-byte writes or EAGAIN)`);
|
|
694
|
+
}
|
|
695
|
+
return nextAttempts;
|
|
696
|
+
}
|
|
697
|
+
function writeAllSyncToFd(fd, text, functionName = "writeAllSyncToFd") {
|
|
698
|
+
const buffer = Buffer.from(text, "utf8");
|
|
699
|
+
let offset = 0;
|
|
700
|
+
let noProgressAttempts = 0;
|
|
701
|
+
while (offset < buffer.length) {
|
|
702
|
+
try {
|
|
703
|
+
const written = writeSync(fd, buffer, offset, buffer.length - offset);
|
|
704
|
+
offset += written;
|
|
705
|
+
if (written === 0) {
|
|
706
|
+
noProgressAttempts = recordSyncWriteNoProgress(functionName, noProgressAttempts);
|
|
707
|
+
} else {
|
|
708
|
+
noProgressAttempts = 0;
|
|
709
|
+
}
|
|
710
|
+
} catch (error) {
|
|
711
|
+
if (error.code === "EAGAIN") {
|
|
712
|
+
noProgressAttempts = recordSyncWriteNoProgress(functionName, noProgressAttempts);
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
throw error;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
function resolveTrustedLsofPath() {
|
|
720
|
+
for (const candidate of TRUSTED_LSOF_PATHS) {
|
|
721
|
+
try {
|
|
722
|
+
if (!statSync(candidate).isFile()) continue;
|
|
723
|
+
accessSync(candidate, fsConstants.X_OK);
|
|
724
|
+
return candidate;
|
|
725
|
+
} catch {
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return void 0;
|
|
729
|
+
}
|
|
730
|
+
function resolveStdoutRegularFilePath() {
|
|
731
|
+
try {
|
|
732
|
+
const stat4 = fstatSync(1);
|
|
733
|
+
if (!stat4.isFile()) return void 0;
|
|
734
|
+
} catch {
|
|
735
|
+
return void 0;
|
|
736
|
+
}
|
|
737
|
+
const lsofPath = resolveTrustedLsofPath();
|
|
738
|
+
if (!lsofPath) return void 0;
|
|
739
|
+
try {
|
|
740
|
+
const output = execFileSync(lsofPath, ["-p", String(process.pid), "-a", "-d", "1", "-Fn"], {
|
|
741
|
+
encoding: "utf8",
|
|
742
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
743
|
+
timeout: 1e3
|
|
744
|
+
});
|
|
745
|
+
const rawPath = output.split("\n").find((line) => line.startsWith("n/"))?.slice(1).replace(/ \(deleted\)$/, "");
|
|
746
|
+
if (!rawPath) return void 0;
|
|
747
|
+
const pathStat = statSync(rawPath);
|
|
748
|
+
return pathStat.isFile() ? rawPath : void 0;
|
|
749
|
+
} catch {
|
|
750
|
+
return void 0;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/automation/dist-entry-integrity.ts
|
|
755
|
+
import * as fs from "fs/promises";
|
|
756
|
+
import * as path from "path";
|
|
757
|
+
function collectReferencedDistChunks(entrySource) {
|
|
758
|
+
const matches = entrySource.matchAll(
|
|
759
|
+
/(?:from\s+|import\()\s*['"]\.\/([^'"]+\.js(?:\.map)?)['"]/g
|
|
760
|
+
);
|
|
761
|
+
const chunks = /* @__PURE__ */ new Set();
|
|
762
|
+
for (const match of matches) {
|
|
763
|
+
const chunk = match[1];
|
|
764
|
+
if (!chunk || chunk === "entry.js") continue;
|
|
765
|
+
chunks.add(chunk);
|
|
766
|
+
}
|
|
767
|
+
return [...chunks];
|
|
768
|
+
}
|
|
769
|
+
async function readDistEntryIntegrity(projectRoot) {
|
|
770
|
+
let entrySource;
|
|
771
|
+
try {
|
|
772
|
+
entrySource = await fs.readFile(path.join(projectRoot, "dist", "entry.js"), "utf8");
|
|
773
|
+
} catch (error) {
|
|
774
|
+
return { readable: false, reason: error instanceof Error ? error.message : String(error) };
|
|
775
|
+
}
|
|
776
|
+
const missingChunks = [];
|
|
777
|
+
for (const relativeChunk of collectReferencedDistChunks(entrySource)) {
|
|
778
|
+
try {
|
|
779
|
+
await fs.stat(path.join(projectRoot, "dist", relativeChunk));
|
|
780
|
+
} catch {
|
|
781
|
+
missingChunks.push(relativeChunk);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
return { readable: true, missingChunks };
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// src/automation/flywheel-run-cleanup.ts
|
|
788
|
+
import * as fs2 from "fs";
|
|
789
|
+
import { stat as stat2 } from "fs/promises";
|
|
790
|
+
import * as path3 from "path";
|
|
791
|
+
import { execa } from "execa";
|
|
792
|
+
|
|
793
|
+
// src/core/extensions/paths.ts
|
|
794
|
+
import { mkdirSync } from "fs";
|
|
795
|
+
import os from "os";
|
|
796
|
+
import * as path2 from "path";
|
|
797
|
+
function getDefaultExtensionsRootDir() {
|
|
798
|
+
return path2.join(os.homedir(), ".build", "extensions");
|
|
799
|
+
}
|
|
800
|
+
function getExtensionsRootDir() {
|
|
801
|
+
return process.env.BUILD_EXTENSIONS_DIR?.trim() || getDefaultExtensionsRootDir();
|
|
802
|
+
}
|
|
803
|
+
function getExtensionDirs(extensionId) {
|
|
804
|
+
const safeId = assertSafePathComponent(extensionId, "extensionId");
|
|
805
|
+
const rootDir = path2.join(getExtensionsRootDir(), safeId);
|
|
806
|
+
return {
|
|
807
|
+
id: safeId,
|
|
808
|
+
rootDir,
|
|
809
|
+
packageDir: path2.join(rootDir, "package"),
|
|
810
|
+
configDir: path2.join(rootDir, "config"),
|
|
811
|
+
dataDir: path2.join(rootDir, "data"),
|
|
812
|
+
cacheDir: path2.join(rootDir, "cache"),
|
|
813
|
+
logsDir: path2.join(rootDir, "logs"),
|
|
814
|
+
stateDir: path2.join(rootDir, "state")
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
function getBuildRepoWorktreePoolRoot2() {
|
|
818
|
+
return getBuildRepoWorktreePoolRoot();
|
|
819
|
+
}
|
|
820
|
+
function getBuildFlywheelWorkflowsRoot(projectRoot) {
|
|
821
|
+
return getBuildFlywheelManifestsRoot(projectRoot);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/automation/flywheel-run-cleanup.ts
|
|
825
|
+
async function runAsyncWithOptionalTimeout(fn, timeoutMs, label) {
|
|
826
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
827
|
+
return fn();
|
|
828
|
+
}
|
|
829
|
+
let timeoutId;
|
|
830
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
831
|
+
timeoutId = setTimeout(() => {
|
|
832
|
+
reject(new Error(`${label} timed out after ${timeoutMs}ms`));
|
|
833
|
+
}, timeoutMs);
|
|
834
|
+
});
|
|
835
|
+
try {
|
|
836
|
+
return await Promise.race([fn(), timeoutPromise]);
|
|
837
|
+
} finally {
|
|
838
|
+
if (timeoutId !== void 0) {
|
|
839
|
+
clearTimeout(timeoutId);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
function resolvePrimaryWorktreePoolRoot() {
|
|
844
|
+
return path3.resolve(getBuildRepoWorktreePoolRoot2());
|
|
845
|
+
}
|
|
846
|
+
function resolveWorktreePoolRootViaFlywheelLayout() {
|
|
847
|
+
return path3.resolve(path3.join(getBuildFlywheelWorkflowsRoot(), "..", "worktrees"));
|
|
848
|
+
}
|
|
849
|
+
function getWorktreePoolPathRootsForHygiene() {
|
|
850
|
+
const primary = resolvePrimaryWorktreePoolRoot();
|
|
851
|
+
const viaFlywheel = resolveWorktreePoolRootViaFlywheelLayout();
|
|
852
|
+
return primary === viaFlywheel ? [primary] : [primary, viaFlywheel];
|
|
853
|
+
}
|
|
854
|
+
function isPathUnderWorktreePoolRoots(candidatePath, roots) {
|
|
855
|
+
const abs = path3.resolve(candidatePath.trim());
|
|
856
|
+
return roots.some((root) => abs === root || abs.startsWith(`${root}${path3.sep}`));
|
|
857
|
+
}
|
|
858
|
+
function getWorktreeCleanupProtectedPaths(extraPaths = []) {
|
|
859
|
+
return Array.from(
|
|
860
|
+
new Set(
|
|
861
|
+
[
|
|
862
|
+
getProjectRoot(),
|
|
863
|
+
getWorktreeBaseDir(),
|
|
864
|
+
...getWorktreePoolPathRootsForHygiene(),
|
|
865
|
+
...extraPaths
|
|
866
|
+
].map((protectedPath) => path3.resolve(protectedPath))
|
|
867
|
+
)
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
function getWorktreeCleanupManagedRoots(extraRoots = []) {
|
|
871
|
+
return Array.from(
|
|
872
|
+
new Set(
|
|
873
|
+
[getWorktreeBaseDir(), ...getWorktreePoolPathRootsForHygiene(), ...extraRoots].map(
|
|
874
|
+
(managedRoot) => path3.resolve(managedRoot)
|
|
875
|
+
)
|
|
876
|
+
)
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
async function removeWorktreeDirectoryWithGuard(worktreePath, protectedPaths, managedRoots) {
|
|
880
|
+
const result = await safeRmDir(worktreePath, {
|
|
881
|
+
protectedPaths: Array.from(protectedPaths),
|
|
882
|
+
managedRoots: Array.from(managedRoots)
|
|
883
|
+
});
|
|
884
|
+
if (!result.deleted) {
|
|
885
|
+
throw new Error(result.reason ?? `Refusing to delete unsafe worktree path ${worktreePath}`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
async function roughDirectoryFootprintBytes(worktreePath) {
|
|
889
|
+
try {
|
|
890
|
+
const st = await stat2(worktreePath);
|
|
891
|
+
const blocks = typeof st.blocks === "number" && st.blocks > 0 ? st.blocks * 512 : 0;
|
|
892
|
+
return st.size + blocks;
|
|
893
|
+
} catch {
|
|
894
|
+
return 0;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
function normalizeGitErrorText(error) {
|
|
898
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
899
|
+
const stderr = error?.stderr ?? "";
|
|
900
|
+
return `${message}
|
|
901
|
+
${stderr}`;
|
|
902
|
+
}
|
|
903
|
+
function isWorktreeMetadataGone(error, worktreePath) {
|
|
904
|
+
const text = normalizeGitErrorText(error);
|
|
905
|
+
return !fs2.existsSync(worktreePath) || /cwd option is invalid/i.test(text) || /no such file or directory/i.test(text) || /not a git repository/i.test(text) || /this operation must be run in a work tree/i.test(text) || /not a working tree/i.test(text) || /bad default revision/i.test(text) || /unknown revision or path/i.test(text) || /ambiguous argument.*HEAD/i.test(text);
|
|
906
|
+
}
|
|
907
|
+
function isNotAGitRepositoryError(error) {
|
|
908
|
+
const text = normalizeGitErrorText(error);
|
|
909
|
+
return /not a git repository/i.test(text) || /not a git repo/i.test(text) || /this operation must be run in a work tree/i.test(text);
|
|
910
|
+
}
|
|
911
|
+
function isBenignGitBranchDeleteError(error) {
|
|
912
|
+
const text = normalizeGitErrorText(error);
|
|
913
|
+
return /error: branch ['"][^'"]+['"] not found/i.test(text) || /couldn't find local branch/i.test(text) || /has already been deleted/i.test(text);
|
|
914
|
+
}
|
|
915
|
+
function getCleanupWorktreeManager(gitCwd) {
|
|
916
|
+
return getWorktreeManager({
|
|
917
|
+
baseDir: getWorktreeBaseDir(),
|
|
918
|
+
gitCwd,
|
|
919
|
+
enablePooling: false
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
function isManagedCleanupWorktreePath(worktreePath, gitCwd) {
|
|
923
|
+
const repoPools = [
|
|
924
|
+
path3.resolve(getWorktreeBaseDir(gitCwd)),
|
|
925
|
+
path3.resolve(gitCwd, ".build-project", "tmp", "worktrees")
|
|
926
|
+
];
|
|
927
|
+
return isDualContainedInManagedRoots(
|
|
928
|
+
worktreePath.trim(),
|
|
929
|
+
getWorktreeCleanupManagedRoots(repoPools)
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
async function safeUpdateWorktreeRecordStatus(workflowId, worktreePath, status, retentionReason, errors) {
|
|
933
|
+
try {
|
|
934
|
+
await updateWorktreeRecordStatus(workflowId, worktreePath, status, retentionReason);
|
|
935
|
+
return true;
|
|
936
|
+
} catch (error) {
|
|
937
|
+
errors.push({
|
|
938
|
+
path: worktreePath,
|
|
939
|
+
error: error instanceof Error ? error.message : String(error)
|
|
940
|
+
});
|
|
941
|
+
return false;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
async function listGitWorktrees(repoCwd = getProjectRoot()) {
|
|
945
|
+
let stdout;
|
|
946
|
+
try {
|
|
947
|
+
({ stdout } = await execa("git", ["worktree", "list", "--porcelain"], {
|
|
948
|
+
cwd: repoCwd,
|
|
949
|
+
stdio: "pipe",
|
|
950
|
+
timeout: 1e4
|
|
951
|
+
}));
|
|
952
|
+
} catch (error) {
|
|
953
|
+
if (isNotAGitRepositoryError(error)) {
|
|
954
|
+
return [];
|
|
955
|
+
}
|
|
956
|
+
throw error;
|
|
957
|
+
}
|
|
958
|
+
const entries = [];
|
|
959
|
+
let current;
|
|
960
|
+
for (const rawLine of stdout.split("\n")) {
|
|
961
|
+
const line = rawLine.trimEnd();
|
|
962
|
+
if (line.startsWith("worktree ")) {
|
|
963
|
+
if (current) entries.push(current);
|
|
964
|
+
current = { path: line.slice("worktree ".length).trim() };
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
if (line.startsWith("branch ") && current) {
|
|
968
|
+
const ref = line.slice("branch ".length).trim();
|
|
969
|
+
const prefix = "refs/heads/";
|
|
970
|
+
if (ref.startsWith(prefix)) {
|
|
971
|
+
current.branch = ref.slice(prefix.length);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
if (current) entries.push(current);
|
|
976
|
+
return entries;
|
|
977
|
+
}
|
|
978
|
+
async function listLocalBranches(repoCwd = getProjectRoot()) {
|
|
979
|
+
let stdout;
|
|
980
|
+
try {
|
|
981
|
+
({ stdout } = await execa("git", ["branch", "--format=%(refname:short)"], {
|
|
982
|
+
cwd: repoCwd,
|
|
983
|
+
stdio: "pipe",
|
|
984
|
+
timeout: 1e4
|
|
985
|
+
}));
|
|
986
|
+
} catch (error) {
|
|
987
|
+
if (isNotAGitRepositoryError(error)) {
|
|
988
|
+
return [];
|
|
989
|
+
}
|
|
990
|
+
throw error;
|
|
991
|
+
}
|
|
992
|
+
return stdout.split("\n").map((b) => b.trim()).filter(Boolean);
|
|
993
|
+
}
|
|
994
|
+
function buildRelatedBranchPrefixes(taskId) {
|
|
995
|
+
const safeTaskId = sanitizeRefPart(taskId, { fallback: "task", maxLen: 80 });
|
|
996
|
+
return [
|
|
997
|
+
...buildVariantBranchPrefixes(taskId),
|
|
998
|
+
`${buildBranchKindPrefix("lab")}${safeTaskId}-`,
|
|
999
|
+
`${buildBranchKindPrefix("stage")}${safeTaskId}-`,
|
|
1000
|
+
`build/substage/${safeTaskId}-`,
|
|
1001
|
+
`build/wt-${safeTaskId}-`
|
|
1002
|
+
];
|
|
1003
|
+
}
|
|
1004
|
+
function matchesCleanupBranchPrefix(branch, prefix) {
|
|
1005
|
+
if (!branch.startsWith(prefix)) {
|
|
1006
|
+
return false;
|
|
1007
|
+
}
|
|
1008
|
+
if (prefix.endsWith("/") || prefix.endsWith("-")) {
|
|
1009
|
+
return true;
|
|
1010
|
+
}
|
|
1011
|
+
const next = branch.charAt(prefix.length);
|
|
1012
|
+
return next === "/" || next === "-";
|
|
1013
|
+
}
|
|
1014
|
+
async function cleanupWorkflowRelatedBranchesAndWorktrees(options) {
|
|
1015
|
+
const relatedTaskIds = (options.relatedTaskIds ?? []).map((t) => t.trim()).filter(Boolean);
|
|
1016
|
+
const relatedSafeTaskIds = relatedTaskIds.map(
|
|
1017
|
+
(taskId) => sanitizeRefPart(taskId, { fallback: "task", maxLen: 80 })
|
|
1018
|
+
);
|
|
1019
|
+
const trackedBranches = (options.trackedBranches ?? []).map((b) => b.trim()).filter(Boolean);
|
|
1020
|
+
const trackedWorktrees = (options.trackedWorktrees ?? []).map((w) => w.trim()).filter(Boolean);
|
|
1021
|
+
const enablePooledWorktreePathRecovery = options.enablePooledWorktreePathRecovery ?? true;
|
|
1022
|
+
const childTargets = runChildBranchTargets(options.integrationBranch);
|
|
1023
|
+
const repoCwd = getProjectRoot();
|
|
1024
|
+
const [branches, worktrees] = await Promise.all([
|
|
1025
|
+
listLocalBranches(repoCwd),
|
|
1026
|
+
listGitWorktrees(repoCwd)
|
|
1027
|
+
]);
|
|
1028
|
+
const prefixes = [
|
|
1029
|
+
...childTargets.prefixes,
|
|
1030
|
+
...relatedTaskIds.flatMap((taskId) => buildRelatedBranchPrefixes(taskId))
|
|
1031
|
+
];
|
|
1032
|
+
const branchesToDelete = Array.from(
|
|
1033
|
+
new Set(
|
|
1034
|
+
branches.filter(
|
|
1035
|
+
(branch) => trackedBranches.includes(branch) || childTargets.exact.includes(branch) || prefixes.some((prefix) => matchesCleanupBranchPrefix(branch, prefix))
|
|
1036
|
+
)
|
|
1037
|
+
)
|
|
1038
|
+
);
|
|
1039
|
+
const worktreesToRemove = Array.from(
|
|
1040
|
+
new Set(
|
|
1041
|
+
[
|
|
1042
|
+
...trackedWorktrees,
|
|
1043
|
+
...worktrees.filter((w) => {
|
|
1044
|
+
if (trackedWorktrees.includes(w.path)) return true;
|
|
1045
|
+
if (w.branch && branchesToDelete.includes(w.branch)) return true;
|
|
1046
|
+
if (!enablePooledWorktreePathRecovery) return false;
|
|
1047
|
+
const base = path3.basename(w.path);
|
|
1048
|
+
return relatedSafeTaskIds.some((taskId) => base.startsWith(`${taskId}-`));
|
|
1049
|
+
}).map((w) => w.path)
|
|
1050
|
+
].filter(Boolean)
|
|
1051
|
+
)
|
|
1052
|
+
);
|
|
1053
|
+
if (options.dryRun) {
|
|
1054
|
+
return {
|
|
1055
|
+
branchesToDelete,
|
|
1056
|
+
worktreesToRemove,
|
|
1057
|
+
skippedWorktreesMissingOnDisk: [],
|
|
1058
|
+
skippedBranchesAlreadyAbsent: [],
|
|
1059
|
+
cleanupWarnings: [],
|
|
1060
|
+
cleanupErrors: []
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
const skippedWorktreesMissingOnDisk = worktreesToRemove.filter((p) => !fs2.existsSync(p));
|
|
1064
|
+
const skippedWorktreesMissing = new Set(skippedWorktreesMissingOnDisk);
|
|
1065
|
+
const skippedBranchesAlreadyAbsent = [];
|
|
1066
|
+
const cleanupWarnings = [];
|
|
1067
|
+
const cleanupErrors = [];
|
|
1068
|
+
const activeWorktreeBranches = /* @__PURE__ */ new Set();
|
|
1069
|
+
const branchByWorktreePath = new Map(
|
|
1070
|
+
worktrees.filter(
|
|
1071
|
+
(worktree) => Boolean(worktree.branch)
|
|
1072
|
+
).map((worktree) => [path3.resolve(worktree.path), worktree.branch])
|
|
1073
|
+
);
|
|
1074
|
+
for (const worktreePath of worktreesToRemove) {
|
|
1075
|
+
if (!fs2.existsSync(worktreePath)) {
|
|
1076
|
+
skippedWorktreesMissing.add(worktreePath);
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
if (await isActiveRun(worktreePath)) {
|
|
1080
|
+
const resolvedWorktreePath = path3.resolve(worktreePath);
|
|
1081
|
+
const attachedBranch = branchByWorktreePath.get(resolvedWorktreePath);
|
|
1082
|
+
if (attachedBranch) {
|
|
1083
|
+
activeWorktreeBranches.add(attachedBranch);
|
|
1084
|
+
}
|
|
1085
|
+
cleanupWarnings.push({
|
|
1086
|
+
path: worktreePath,
|
|
1087
|
+
message: "Preserved active worktree; its owning run is still active"
|
|
1088
|
+
});
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
if (!isManagedCleanupWorktreePath(worktreePath, repoCwd)) {
|
|
1092
|
+
cleanupWarnings.push({
|
|
1093
|
+
path: worktreePath,
|
|
1094
|
+
message: "Refusing to clean worktree path outside canonical Build worktree pools"
|
|
1095
|
+
});
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
try {
|
|
1099
|
+
const worktreeManager = getCleanupWorktreeManager(repoCwd);
|
|
1100
|
+
await worktreeManager.cleanup(worktreePath, { preserve: false, gitCwd: repoCwd });
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
if (!fs2.existsSync(worktreePath)) {
|
|
1103
|
+
skippedWorktreesMissing.add(worktreePath);
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
cleanupWarnings.push({
|
|
1107
|
+
path: worktreePath,
|
|
1108
|
+
message: normalizeGitErrorText(error)
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
const branchesLive = await listLocalBranches(repoCwd);
|
|
1113
|
+
const liveBranchSet = new Set(branchesLive);
|
|
1114
|
+
for (const branch of branchesToDelete) {
|
|
1115
|
+
if (activeWorktreeBranches.has(branch)) {
|
|
1116
|
+
cleanupWarnings.push({
|
|
1117
|
+
path: branch,
|
|
1118
|
+
message: "Preserved branch attached to an active worktree"
|
|
1119
|
+
});
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
if (!liveBranchSet.has(branch)) {
|
|
1123
|
+
skippedBranchesAlreadyAbsent.push(branch);
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
const branchRole = classifyBranchRole(branch);
|
|
1127
|
+
if (!isBuildOwnedBranch(branch) || isProtectedPipelineBranch(branch) || branchRole !== "generated-artifact") {
|
|
1128
|
+
cleanupWarnings.push({
|
|
1129
|
+
path: branch,
|
|
1130
|
+
message: `Refusing to delete non-generated or protected branch ${branch}`
|
|
1131
|
+
});
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
try {
|
|
1135
|
+
await execa("git", ["branch", "-D", branch], {
|
|
1136
|
+
cwd: repoCwd,
|
|
1137
|
+
stdio: "pipe",
|
|
1138
|
+
timeout: 1e4
|
|
1139
|
+
});
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
if (isBenignGitBranchDeleteError(error)) {
|
|
1142
|
+
skippedBranchesAlreadyAbsent.push(branch);
|
|
1143
|
+
} else {
|
|
1144
|
+
cleanupErrors.push({
|
|
1145
|
+
path: branch,
|
|
1146
|
+
error: normalizeGitErrorText(error)
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (worktreesToRemove.length > 0) {
|
|
1152
|
+
await pruneGitWorktreeEntries({ cwd: repoCwd, timeoutMs: 1e4, paths: worktreesToRemove });
|
|
1153
|
+
}
|
|
1154
|
+
return {
|
|
1155
|
+
branchesToDelete,
|
|
1156
|
+
worktreesToRemove,
|
|
1157
|
+
skippedWorktreesMissingOnDisk: Array.from(skippedWorktreesMissing),
|
|
1158
|
+
skippedBranchesAlreadyAbsent,
|
|
1159
|
+
cleanupWarnings,
|
|
1160
|
+
cleanupErrors
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
async function recoverPendingWorktrees(options = {}) {
|
|
1164
|
+
const {
|
|
1165
|
+
dryRun = false,
|
|
1166
|
+
verbose = false,
|
|
1167
|
+
includeCleanupFailed = true,
|
|
1168
|
+
maxManifests = WORKTREE_HYGIENE_MAX_MANIFESTS_PER_PASS,
|
|
1169
|
+
maxRecords = WORKTREE_HYGIENE_MAX_RECOVERY_RECORDS_PER_PASS,
|
|
1170
|
+
restrictToBuildWorktreePool = false
|
|
1171
|
+
} = options;
|
|
1172
|
+
const manifestCap = Math.max(1, maxManifests);
|
|
1173
|
+
const recordCap = Math.max(1, maxRecords);
|
|
1174
|
+
const result = {
|
|
1175
|
+
recovered: [],
|
|
1176
|
+
skippedActive: [],
|
|
1177
|
+
skippedDirty: [],
|
|
1178
|
+
alreadyGone: [],
|
|
1179
|
+
errors: []
|
|
1180
|
+
};
|
|
1181
|
+
const protectedPaths = getWorktreeCleanupProtectedPaths();
|
|
1182
|
+
const managedRoots = getWorktreeCleanupManagedRoots();
|
|
1183
|
+
const runRecovery = async () => {
|
|
1184
|
+
let manifests;
|
|
1185
|
+
try {
|
|
1186
|
+
manifests = await listManifests();
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
result.errors.push({
|
|
1189
|
+
path: "<manifests>",
|
|
1190
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1191
|
+
});
|
|
1192
|
+
return result;
|
|
1193
|
+
}
|
|
1194
|
+
const manifestsSlice = manifests.slice(0, manifestCap);
|
|
1195
|
+
if (manifestsSlice.length < manifests.length) {
|
|
1196
|
+
result.hygieneCapReached = true;
|
|
1197
|
+
}
|
|
1198
|
+
const poolRoots = getWorktreePoolPathRootsForHygiene();
|
|
1199
|
+
let recordsExamined = 0;
|
|
1200
|
+
const seenRecoveryKeys = /* @__PURE__ */ new Set();
|
|
1201
|
+
for (let mi = 0; mi < manifestsSlice.length; mi++) {
|
|
1202
|
+
const manifest = manifestsSlice[mi];
|
|
1203
|
+
const reconciledManifest = dryRun ? manifest : await reconcileAndSaveManifest(manifest.workflowId).catch(() => manifest);
|
|
1204
|
+
const statuses = ["pending"];
|
|
1205
|
+
if (includeCleanupFailed) {
|
|
1206
|
+
statuses.push("cleanup_failed");
|
|
1207
|
+
}
|
|
1208
|
+
const pending = statuses.flatMap(
|
|
1209
|
+
(status) => getWorktreeRecordsByStatus(reconciledManifest, status)
|
|
1210
|
+
);
|
|
1211
|
+
for (const record of pending) {
|
|
1212
|
+
const recoveryKey = `${reconciledManifest.workflowId}::${record.worktreePath}`;
|
|
1213
|
+
if (seenRecoveryKeys.has(recoveryKey)) {
|
|
1214
|
+
continue;
|
|
1215
|
+
}
|
|
1216
|
+
seenRecoveryKeys.add(recoveryKey);
|
|
1217
|
+
if (recordsExamined >= recordCap) {
|
|
1218
|
+
result.hygieneCapReached = true;
|
|
1219
|
+
break;
|
|
1220
|
+
}
|
|
1221
|
+
recordsExamined += 1;
|
|
1222
|
+
const worktreePath = record.worktreePath;
|
|
1223
|
+
try {
|
|
1224
|
+
if (restrictToBuildWorktreePool && !isPathUnderWorktreePoolRoots(worktreePath, poolRoots)) {
|
|
1225
|
+
if (verbose) {
|
|
1226
|
+
console.log(`[recover] Skipping worktree outside pool roots: ${worktreePath}`);
|
|
1227
|
+
}
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
const exists = fs2.existsSync(worktreePath);
|
|
1231
|
+
if (!exists) {
|
|
1232
|
+
if (verbose) {
|
|
1233
|
+
console.log(`[recover] Worktree already gone: ${worktreePath}`);
|
|
1234
|
+
}
|
|
1235
|
+
if (!dryRun) {
|
|
1236
|
+
const ok = await safeUpdateWorktreeRecordStatus(
|
|
1237
|
+
reconciledManifest.workflowId,
|
|
1238
|
+
worktreePath,
|
|
1239
|
+
"cleaned",
|
|
1240
|
+
WORKTREE_RECORD_RECOVERY_REASON.PATH_ALREADY_ABSENT,
|
|
1241
|
+
result.errors
|
|
1242
|
+
);
|
|
1243
|
+
if (!ok) {
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
result.alreadyGone.push(worktreePath);
|
|
1248
|
+
continue;
|
|
1249
|
+
}
|
|
1250
|
+
if (await isActiveRun(worktreePath)) {
|
|
1251
|
+
if (verbose) {
|
|
1252
|
+
console.log(`[recover] Preserving active worktree: ${worktreePath}`);
|
|
1253
|
+
}
|
|
1254
|
+
result.skippedActive.push(worktreePath);
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
let dirty = false;
|
|
1258
|
+
try {
|
|
1259
|
+
const { stdout } = await execa("git", ["status", "--porcelain"], {
|
|
1260
|
+
cwd: worktreePath,
|
|
1261
|
+
stdio: "pipe",
|
|
1262
|
+
timeout: GIT_COMMAND_TIMEOUT_MS
|
|
1263
|
+
});
|
|
1264
|
+
dirty = filterWorktreeRunSentinelStatusOutput(stdout).trim().length > 0;
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
const text = normalizeGitErrorText(error);
|
|
1267
|
+
const disappearedDuringStatus = isWorktreeMetadataGone(error, worktreePath);
|
|
1268
|
+
if (disappearedDuringStatus) {
|
|
1269
|
+
if (verbose) {
|
|
1270
|
+
console.log(
|
|
1271
|
+
`[recover] Worktree path missing or not a git repository, marking cleaned: ${worktreePath}`
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
if (!dryRun) {
|
|
1275
|
+
const ok = await safeUpdateWorktreeRecordStatus(
|
|
1276
|
+
reconciledManifest.workflowId,
|
|
1277
|
+
worktreePath,
|
|
1278
|
+
"cleaned",
|
|
1279
|
+
WORKTREE_RECORD_RECOVERY_REASON.PATH_INVALID_DURING_STATUS_PROBE,
|
|
1280
|
+
result.errors
|
|
1281
|
+
);
|
|
1282
|
+
if (!ok) {
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
result.alreadyGone.push(worktreePath);
|
|
1287
|
+
} else {
|
|
1288
|
+
result.errors.push({
|
|
1289
|
+
path: worktreePath,
|
|
1290
|
+
error: text
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
if (dirty) {
|
|
1296
|
+
if (verbose) {
|
|
1297
|
+
console.log(`[recover] Skipping dirty worktree: ${worktreePath}`);
|
|
1298
|
+
}
|
|
1299
|
+
result.skippedDirty.push(worktreePath);
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
if (verbose) {
|
|
1303
|
+
console.log(`[recover] Recovering clean worktree: ${worktreePath}`);
|
|
1304
|
+
}
|
|
1305
|
+
if (!dryRun) {
|
|
1306
|
+
const gitRemoval = await tryRemoveWorktreeNestedAware(worktreePath, {
|
|
1307
|
+
force: true,
|
|
1308
|
+
repoCwd: getProjectRoot()
|
|
1309
|
+
});
|
|
1310
|
+
if (gitRemoval.refusal) {
|
|
1311
|
+
result.errors.push({ path: worktreePath, error: gitRemoval.refusal });
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
if (fs2.existsSync(worktreePath)) {
|
|
1315
|
+
try {
|
|
1316
|
+
await removeWorktreeDirectoryWithGuard(worktreePath, protectedPaths, managedRoots);
|
|
1317
|
+
} catch (rmErr) {
|
|
1318
|
+
result.errors.push({
|
|
1319
|
+
path: worktreePath,
|
|
1320
|
+
error: normalizeGitErrorText(rmErr)
|
|
1321
|
+
});
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
if (fs2.existsSync(worktreePath)) {
|
|
1326
|
+
result.errors.push({
|
|
1327
|
+
path: worktreePath,
|
|
1328
|
+
error: "Recovery could not remove worktree directory; leaving manifest unchanged"
|
|
1329
|
+
});
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
const updated = await safeUpdateWorktreeRecordStatus(
|
|
1333
|
+
reconciledManifest.workflowId,
|
|
1334
|
+
worktreePath,
|
|
1335
|
+
"cleaned",
|
|
1336
|
+
null,
|
|
1337
|
+
result.errors
|
|
1338
|
+
);
|
|
1339
|
+
if (!updated) {
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
result.recovered.push(worktreePath);
|
|
1344
|
+
} catch (error) {
|
|
1345
|
+
result.errors.push({
|
|
1346
|
+
path: worktreePath,
|
|
1347
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
if (recordsExamined >= recordCap) {
|
|
1352
|
+
if (mi < manifestsSlice.length - 1) {
|
|
1353
|
+
result.hygieneCapReached = true;
|
|
1354
|
+
}
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
if (!dryRun && (result.recovered.length > 0 || result.alreadyGone.length > 0)) {
|
|
1359
|
+
await pruneGitWorktreeEntries({
|
|
1360
|
+
cwd: getProjectRoot(),
|
|
1361
|
+
paths: [...result.recovered, ...result.alreadyGone]
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
return result;
|
|
1365
|
+
};
|
|
1366
|
+
if (dryRun) {
|
|
1367
|
+
return runRecovery();
|
|
1368
|
+
}
|
|
1369
|
+
return withCleanupLock(getWorktreeBaseDir(), runRecovery);
|
|
1370
|
+
}
|
|
1371
|
+
async function enforceRetentionBounds(options = {}) {
|
|
1372
|
+
const {
|
|
1373
|
+
maxAgeMs = 7 * 24 * 60 * 60 * 1e3,
|
|
1374
|
+
maxCount = 20,
|
|
1375
|
+
maxManifests = WORKTREE_HYGIENE_MAX_MANIFESTS_PER_PASS,
|
|
1376
|
+
maxGitStatusProbes = WORKTREE_HYGIENE_MAX_RETENTION_PRUNE_OPS,
|
|
1377
|
+
dryRun = false,
|
|
1378
|
+
verbose = false
|
|
1379
|
+
} = options;
|
|
1380
|
+
const diskCapResolved = options.maxRetainedTotalDiskBytes !== void 0 ? options.maxRetainedTotalDiskBytes : WORKTREE_HYGIENE_MAX_RETAINED_TOTAL_BYTES;
|
|
1381
|
+
const manifestCap = Math.max(1, maxManifests);
|
|
1382
|
+
const probeCap = Math.max(1, maxGitStatusProbes);
|
|
1383
|
+
const result = {
|
|
1384
|
+
pruned: [],
|
|
1385
|
+
preserved: [],
|
|
1386
|
+
errors: []
|
|
1387
|
+
};
|
|
1388
|
+
const protectedPaths = getWorktreeCleanupProtectedPaths();
|
|
1389
|
+
const managedRoots = getWorktreeCleanupManagedRoots();
|
|
1390
|
+
const runRetention = async () => {
|
|
1391
|
+
let manifests;
|
|
1392
|
+
try {
|
|
1393
|
+
manifests = await listManifests();
|
|
1394
|
+
} catch (error) {
|
|
1395
|
+
result.errors.push({
|
|
1396
|
+
path: "<manifests>",
|
|
1397
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1398
|
+
});
|
|
1399
|
+
return result;
|
|
1400
|
+
}
|
|
1401
|
+
const manifestsSlice = manifests.slice(0, manifestCap);
|
|
1402
|
+
if (manifestsSlice.length < manifests.length) {
|
|
1403
|
+
result.hygieneCapReached = true;
|
|
1404
|
+
}
|
|
1405
|
+
const retainedEntries = [];
|
|
1406
|
+
const retainedByWorkflowPath = /* @__PURE__ */ new Map();
|
|
1407
|
+
for (const manifest of manifestsSlice) {
|
|
1408
|
+
const retained = getWorktreeRecordsByStatus(manifest, "retained");
|
|
1409
|
+
for (const record of retained) {
|
|
1410
|
+
if (isProviderFailureKeptRecord(record)) continue;
|
|
1411
|
+
const key = `${manifest.workflowId}::${record.worktreePath}`;
|
|
1412
|
+
const next = {
|
|
1413
|
+
workflowId: manifest.workflowId,
|
|
1414
|
+
worktreePath: record.worktreePath,
|
|
1415
|
+
updatedAt: record.updatedAt
|
|
1416
|
+
};
|
|
1417
|
+
const existing = retainedByWorkflowPath.get(key);
|
|
1418
|
+
if (!existing || new Date(next.updatedAt).getTime() > new Date(existing.updatedAt).getTime()) {
|
|
1419
|
+
retainedByWorkflowPath.set(key, next);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
retainedEntries.push(...retainedByWorkflowPath.values());
|
|
1424
|
+
retainedEntries.sort(
|
|
1425
|
+
(a, b) => new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime()
|
|
1426
|
+
);
|
|
1427
|
+
const diskCap = diskCapResolved > 0 ? diskCapResolved : 0;
|
|
1428
|
+
const footprintByPath = /* @__PURE__ */ new Map();
|
|
1429
|
+
let aggregateRetainedBytes = 0;
|
|
1430
|
+
let diskFootprintTruncated = false;
|
|
1431
|
+
if (diskCap > 0) {
|
|
1432
|
+
const maxStat = WORKTREE_HYGIENE_MAX_RETENTION_COLLATION;
|
|
1433
|
+
for (const [si, entry] of retainedEntries.entries()) {
|
|
1434
|
+
if (si >= maxStat) {
|
|
1435
|
+
diskFootprintTruncated = true;
|
|
1436
|
+
result.hygieneCapReached = true;
|
|
1437
|
+
break;
|
|
1438
|
+
}
|
|
1439
|
+
const p = entry.worktreePath;
|
|
1440
|
+
if (!fs2.existsSync(p)) {
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
const bytes = await roughDirectoryFootprintBytes(p);
|
|
1444
|
+
footprintByPath.set(p, bytes);
|
|
1445
|
+
aggregateRetainedBytes += bytes;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
const now = Date.now();
|
|
1449
|
+
let remainingCountExcess = Math.max(0, retainedEntries.length - maxCount);
|
|
1450
|
+
let gitStatusProbes = 0;
|
|
1451
|
+
const consumeFootprintForPath = (wp) => {
|
|
1452
|
+
if (diskCap <= 0 || diskFootprintTruncated) {
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
const used = footprintByPath.get(wp);
|
|
1456
|
+
if (used !== void 0) {
|
|
1457
|
+
aggregateRetainedBytes -= used;
|
|
1458
|
+
footprintByPath.delete(wp);
|
|
1459
|
+
}
|
|
1460
|
+
};
|
|
1461
|
+
for (let i = 0; i < retainedEntries.length; i++) {
|
|
1462
|
+
const entry = retainedEntries[i];
|
|
1463
|
+
if (!entry) continue;
|
|
1464
|
+
const worktreePath = entry.worktreePath;
|
|
1465
|
+
const ageMs = now - new Date(entry.updatedAt).getTime();
|
|
1466
|
+
const isOverAge = ageMs > maxAgeMs;
|
|
1467
|
+
const isOverCount = remainingCountExcess > 0;
|
|
1468
|
+
const isOverDisk = diskCap > 0 && !diskFootprintTruncated && aggregateRetainedBytes > diskCap;
|
|
1469
|
+
if (!isOverAge && !isOverCount && !isOverDisk) {
|
|
1470
|
+
result.preserved.push(worktreePath);
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
if (await isActiveRun(worktreePath)) {
|
|
1474
|
+
if (verbose) {
|
|
1475
|
+
console.log(`[retention] Preserving active retained worktree: ${worktreePath}`);
|
|
1476
|
+
}
|
|
1477
|
+
result.preserved.push(worktreePath);
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
let dirty = false;
|
|
1481
|
+
if (fs2.existsSync(worktreePath)) {
|
|
1482
|
+
if (gitStatusProbes >= probeCap) {
|
|
1483
|
+
result.hygieneCapReached = true;
|
|
1484
|
+
for (const retainedEntry of retainedEntries.slice(i)) {
|
|
1485
|
+
result.preserved.push(retainedEntry.worktreePath);
|
|
1486
|
+
}
|
|
1487
|
+
break;
|
|
1488
|
+
}
|
|
1489
|
+
gitStatusProbes += 1;
|
|
1490
|
+
try {
|
|
1491
|
+
const { stdout } = await execa("git", ["status", "--porcelain"], {
|
|
1492
|
+
cwd: worktreePath,
|
|
1493
|
+
stdio: "pipe",
|
|
1494
|
+
timeout: GIT_COMMAND_TIMEOUT_MS
|
|
1495
|
+
});
|
|
1496
|
+
dirty = filterWorktreeRunSentinelStatusOutput(stdout).trim().length > 0;
|
|
1497
|
+
} catch (error) {
|
|
1498
|
+
const disappearedDuringStatus = isWorktreeMetadataGone(error, worktreePath);
|
|
1499
|
+
if (disappearedDuringStatus) {
|
|
1500
|
+
if (verbose) {
|
|
1501
|
+
console.log(
|
|
1502
|
+
`[retention] Worktree path missing or not a git repository, marking cleaned: ${worktreePath}`
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
if (!dryRun) {
|
|
1506
|
+
try {
|
|
1507
|
+
await updateWorktreeRecordStatus(entry.workflowId, worktreePath, "cleaned", null);
|
|
1508
|
+
} catch (updateError) {
|
|
1509
|
+
result.errors.push({
|
|
1510
|
+
path: worktreePath,
|
|
1511
|
+
error: updateError instanceof Error ? updateError.message : String(updateError)
|
|
1512
|
+
});
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
if (remainingCountExcess > 0) {
|
|
1517
|
+
remainingCountExcess--;
|
|
1518
|
+
}
|
|
1519
|
+
consumeFootprintForPath(worktreePath);
|
|
1520
|
+
result.pruned.push(worktreePath);
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
dirty = true;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
if (dirty) {
|
|
1527
|
+
if (verbose) {
|
|
1528
|
+
console.log(`[retention] Preserving dirty retained worktree: ${worktreePath}`);
|
|
1529
|
+
}
|
|
1530
|
+
result.preserved.push(worktreePath);
|
|
1531
|
+
continue;
|
|
1532
|
+
}
|
|
1533
|
+
if (verbose) {
|
|
1534
|
+
console.log(`[retention] Pruning retained worktree: ${worktreePath}`);
|
|
1535
|
+
}
|
|
1536
|
+
if (!dryRun) {
|
|
1537
|
+
try {
|
|
1538
|
+
if (fs2.existsSync(worktreePath)) {
|
|
1539
|
+
const gitRemoval = await tryRemoveWorktreeNestedAware(worktreePath, {
|
|
1540
|
+
force: true,
|
|
1541
|
+
repoCwd: getProjectRoot()
|
|
1542
|
+
});
|
|
1543
|
+
if (gitRemoval.refusal) {
|
|
1544
|
+
result.errors.push({ path: worktreePath, error: gitRemoval.refusal });
|
|
1545
|
+
continue;
|
|
1546
|
+
}
|
|
1547
|
+
if (!gitRemoval.removed) {
|
|
1548
|
+
await removeWorktreeDirectoryWithGuard(worktreePath, protectedPaths, managedRoots);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
await updateWorktreeRecordStatus(entry.workflowId, worktreePath, "cleaned", null);
|
|
1552
|
+
} catch (error) {
|
|
1553
|
+
result.errors.push({
|
|
1554
|
+
path: worktreePath,
|
|
1555
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1556
|
+
});
|
|
1557
|
+
continue;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
if (remainingCountExcess > 0) {
|
|
1561
|
+
remainingCountExcess--;
|
|
1562
|
+
}
|
|
1563
|
+
consumeFootprintForPath(worktreePath);
|
|
1564
|
+
result.pruned.push(worktreePath);
|
|
1565
|
+
}
|
|
1566
|
+
if (!dryRun && result.pruned.length > 0) {
|
|
1567
|
+
try {
|
|
1568
|
+
await pruneGitWorktreeEntries({ cwd: getProjectRoot(), paths: result.pruned });
|
|
1569
|
+
} catch {
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
return result;
|
|
1573
|
+
};
|
|
1574
|
+
if (dryRun) {
|
|
1575
|
+
return runRetention();
|
|
1576
|
+
}
|
|
1577
|
+
return withCleanupLock(getWorktreeBaseDir(), runRetention);
|
|
1578
|
+
}
|
|
1579
|
+
async function runFlywheelWorktreeHygiene(options) {
|
|
1580
|
+
const recoverResult = await recoverPendingWorktrees({
|
|
1581
|
+
dryRun: options.dryRun,
|
|
1582
|
+
verbose: options.verbose,
|
|
1583
|
+
maxManifests: options.maxManifests,
|
|
1584
|
+
maxRecords: options.maxRecoveryRecords,
|
|
1585
|
+
restrictToBuildWorktreePool: options.restrictRecoveryToBuildWorktreePool
|
|
1586
|
+
});
|
|
1587
|
+
const retentionResult = await enforceRetentionBounds({
|
|
1588
|
+
dryRun: options.dryRun,
|
|
1589
|
+
verbose: options.verbose,
|
|
1590
|
+
maxAgeMs: options.maxAgeMs,
|
|
1591
|
+
maxCount: options.maxCount,
|
|
1592
|
+
maxRetainedTotalDiskBytes: options.maxRetainedTotalDiskBytes,
|
|
1593
|
+
maxManifests: options.maxManifests,
|
|
1594
|
+
maxGitStatusProbes: options.maxGitStatusProbes
|
|
1595
|
+
});
|
|
1596
|
+
return {
|
|
1597
|
+
recoverPendingWorktrees: recoverResult,
|
|
1598
|
+
enforceRetentionBounds: retentionResult
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
async function getFlywheelCleanupDryRunWorktreeSummary() {
|
|
1602
|
+
const { recoverPendingWorktrees: recoverPendingWorktrees2, enforceRetentionBounds: enforceRetentionBounds2 } = await runFlywheelWorktreeHygiene({
|
|
1603
|
+
dryRun: true,
|
|
1604
|
+
verbose: false
|
|
1605
|
+
});
|
|
1606
|
+
return {
|
|
1607
|
+
recoverPendingWorktrees: recoverPendingWorktrees2,
|
|
1608
|
+
enforceRetentionBounds: enforceRetentionBounds2,
|
|
1609
|
+
retained: enforceRetentionBounds2.preserved.length,
|
|
1610
|
+
pruned: enforceRetentionBounds2.pruned.length,
|
|
1611
|
+
skipped: recoverPendingWorktrees2.skippedActive.length + recoverPendingWorktrees2.skippedDirty.length
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
// src/automation/flywheel-retry-policies.ts
|
|
1616
|
+
var VALID_ON_FAILURE_POLICIES = [
|
|
1617
|
+
"abort",
|
|
1618
|
+
"retry-with-feedback",
|
|
1619
|
+
"warn",
|
|
1620
|
+
"skip"
|
|
1621
|
+
];
|
|
1622
|
+
function isValidOnFailurePolicy(value) {
|
|
1623
|
+
return typeof value === "string" && VALID_ON_FAILURE_POLICIES.includes(value);
|
|
1624
|
+
}
|
|
1625
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
1626
|
+
onFailure: "abort",
|
|
1627
|
+
maxRetries: 2,
|
|
1628
|
+
feedbackTemplate: void 0,
|
|
1629
|
+
// Use built-in template
|
|
1630
|
+
backoffMultiplier: 2,
|
|
1631
|
+
initialDelaySeconds: 5,
|
|
1632
|
+
maxDelaySeconds: 60
|
|
1633
|
+
};
|
|
1634
|
+
var DEFAULT_FEEDBACK_TEMPLATE = `
|
|
1635
|
+
## Previous Attempt Failed
|
|
1636
|
+
|
|
1637
|
+
**Feedback attempt:** {{attempt}} of {{maxAttempts}}
|
|
1638
|
+
**Error:** {{error}}
|
|
1639
|
+
{{#if validation}}
|
|
1640
|
+
**Validation Failure:** {{validation}}
|
|
1641
|
+
{{/if}}
|
|
1642
|
+
|
|
1643
|
+
Please address the issue and try again. Focus on:
|
|
1644
|
+
1. Understanding why the previous attempt failed
|
|
1645
|
+
2. Fixing the specific issue mentioned above
|
|
1646
|
+
3. Ensuring your output passes all validation checks
|
|
1647
|
+
|
|
1648
|
+
---
|
|
1649
|
+
|
|
1650
|
+
## Original Task
|
|
1651
|
+
|
|
1652
|
+
{{originalPrompt}}
|
|
1653
|
+
`;
|
|
1654
|
+
var FEEDBACK_RETRYABLE_TAXONOMY_OVERRIDES = [
|
|
1655
|
+
"VALIDATION_FAILED",
|
|
1656
|
+
"SEMANTIC_CHECK_FAILED",
|
|
1657
|
+
"ALLOWLIST_VIOLATION",
|
|
1658
|
+
"ARTIFACT_INVALID"
|
|
1659
|
+
];
|
|
1660
|
+
var FEEDBACK_RETRYABLE_ERRORS = new Set(
|
|
1661
|
+
FEEDBACK_RETRYABLE_TAXONOMY_OVERRIDES
|
|
1662
|
+
);
|
|
1663
|
+
var NON_RETRYABLE_ERRORS = /* @__PURE__ */ new Set([
|
|
1664
|
+
"CONFIG_INVALID",
|
|
1665
|
+
"STAGE_NOT_FOUND",
|
|
1666
|
+
"REPO_DIRTY",
|
|
1667
|
+
"GIT_CONFLICT",
|
|
1668
|
+
"MODEL_NOT_FOUND"
|
|
1669
|
+
]);
|
|
1670
|
+
function resolveRetryAuthority(config = {}) {
|
|
1671
|
+
const fullConfig = { ...DEFAULT_RETRY_CONFIG, ...config };
|
|
1672
|
+
return {
|
|
1673
|
+
...fullConfig,
|
|
1674
|
+
// maxRetries is additional feedback retries after the first eligible attempt.
|
|
1675
|
+
maxAttempts: fullConfig.maxRetries + 1
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
function getFeedbackRetryEligibility(error) {
|
|
1679
|
+
if (NON_RETRYABLE_ERRORS.has(error.code)) {
|
|
1680
|
+
return {
|
|
1681
|
+
retryable: false,
|
|
1682
|
+
reason: `Error code ${error.code} is blocked from feedback retry`,
|
|
1683
|
+
overridesTaxonomy: false
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
if (error.retryable) {
|
|
1687
|
+
return {
|
|
1688
|
+
retryable: true,
|
|
1689
|
+
reason: `Error code ${error.code} is retryable by taxonomy`,
|
|
1690
|
+
overridesTaxonomy: false
|
|
1691
|
+
};
|
|
1692
|
+
}
|
|
1693
|
+
if (FEEDBACK_RETRYABLE_ERRORS.has(error.code)) {
|
|
1694
|
+
return {
|
|
1695
|
+
retryable: true,
|
|
1696
|
+
reason: `Error code ${error.code} is eligible for corrective retry under retry-with-feedback despite taxonomy retryable:false`,
|
|
1697
|
+
overridesTaxonomy: true
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
return {
|
|
1701
|
+
retryable: false,
|
|
1702
|
+
reason: `Error code ${error.code} is not retryable by taxonomy or feedback policy`,
|
|
1703
|
+
overridesTaxonomy: false
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
function isResolvedRetryAuthority(value) {
|
|
1707
|
+
return typeof value === "object";
|
|
1708
|
+
}
|
|
1709
|
+
function shouldRetry(authorityOrPolicy, error, attempt, maxAttempts) {
|
|
1710
|
+
const authority = isResolvedRetryAuthority(authorityOrPolicy) ? authorityOrPolicy : {
|
|
1711
|
+
...DEFAULT_RETRY_CONFIG,
|
|
1712
|
+
onFailure: authorityOrPolicy,
|
|
1713
|
+
maxRetries: typeof maxAttempts === "number" ? maxAttempts - 1 : DEFAULT_RETRY_CONFIG.maxRetries,
|
|
1714
|
+
maxAttempts: typeof maxAttempts === "number" ? maxAttempts : DEFAULT_RETRY_CONFIG.maxRetries + 1
|
|
1715
|
+
};
|
|
1716
|
+
if (authority.onFailure !== "retry-with-feedback") {
|
|
1717
|
+
return false;
|
|
1718
|
+
}
|
|
1719
|
+
if (attempt >= authority.maxAttempts) {
|
|
1720
|
+
return false;
|
|
1721
|
+
}
|
|
1722
|
+
return getFeedbackRetryEligibility(error).retryable;
|
|
1723
|
+
}
|
|
1724
|
+
function buildRetryPrompt(context, template) {
|
|
1725
|
+
const feedbackTemplate = template || DEFAULT_FEEDBACK_TEMPLATE;
|
|
1726
|
+
let validationMessage = "";
|
|
1727
|
+
if (context.lastValidationResult) {
|
|
1728
|
+
validationMessage = `[${context.lastValidationResult.type}] ${context.lastValidationResult.message}`;
|
|
1729
|
+
} else if (context.allValidationResults && context.allValidationResults.length > 0) {
|
|
1730
|
+
const failed = context.allValidationResults.filter((r) => !r.passed);
|
|
1731
|
+
if (failed.length > 0) {
|
|
1732
|
+
validationMessage = failed.map((r) => `[${r.type}] ${r.message}`).join("\n");
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
let feedback = feedbackTemplate.replace(/\{\{error\}\}/g, context.lastError.message).replace(/\{\{errorCode\}\}/g, context.lastError.code).replace(/\{\{attempt\}\}/g, String(context.attempt)).replace(/\{\{maxAttempts\}\}/g, String(context.maxAttempts)).replace(/\{\{originalPrompt\}\}/g, context.originalPrompt);
|
|
1736
|
+
if (validationMessage) {
|
|
1737
|
+
feedback = feedback.replace(/\{\{#if validation\}\}([\s\S]*?)\{\{\/if\}\}/g, "$1");
|
|
1738
|
+
feedback = feedback.replace(/\{\{validation\}\}/g, validationMessage);
|
|
1739
|
+
} else {
|
|
1740
|
+
feedback = feedback.replace(/\{\{#if validation\}\}[\s\S]*?\{\{\/if\}\}/g, "");
|
|
1741
|
+
}
|
|
1742
|
+
return feedback.trim();
|
|
1743
|
+
}
|
|
1744
|
+
function calculateRetryDelay(attempt, config) {
|
|
1745
|
+
const initial = config?.initialDelaySeconds ?? DEFAULT_RETRY_CONFIG.initialDelaySeconds;
|
|
1746
|
+
const multiplier = config?.backoffMultiplier ?? DEFAULT_RETRY_CONFIG.backoffMultiplier;
|
|
1747
|
+
const max = config?.maxDelaySeconds ?? DEFAULT_RETRY_CONFIG.maxDelaySeconds;
|
|
1748
|
+
const delay = initial * multiplier ** (attempt - 1);
|
|
1749
|
+
const jitter = delay * 0.2 * (Math.random() * 2 - 1);
|
|
1750
|
+
return Math.min(delay + jitter, max);
|
|
1751
|
+
}
|
|
1752
|
+
function makeRetryDecision(context, config = {}) {
|
|
1753
|
+
const authority = resolveRetryAuthority(config);
|
|
1754
|
+
const authoritativeContext = { ...context, maxAttempts: authority.maxAttempts };
|
|
1755
|
+
const feedbackEligibility = getFeedbackRetryEligibility(context.lastError);
|
|
1756
|
+
const policy = isValidOnFailurePolicy(authority.onFailure) ? authority.onFailure : "abort";
|
|
1757
|
+
const willRetry = policy === "retry-with-feedback" && shouldRetry(authority, context.lastError, context.attempt);
|
|
1758
|
+
if (!willRetry) {
|
|
1759
|
+
let action;
|
|
1760
|
+
let reason;
|
|
1761
|
+
if (policy === "warn") {
|
|
1762
|
+
action = "warn-continue";
|
|
1763
|
+
reason = "Policy is 'warn': record warning and continue workflow (validation remains failed)";
|
|
1764
|
+
} else if (policy === "skip") {
|
|
1765
|
+
action = "skip-continue";
|
|
1766
|
+
reason = "Policy is 'skip': skip remaining stage work and continue workflow";
|
|
1767
|
+
} else if (policy === "retry-with-feedback") {
|
|
1768
|
+
action = "abort";
|
|
1769
|
+
if (context.attempt >= authority.maxAttempts) {
|
|
1770
|
+
reason = `Exceeded maximum retries (${authority.maxRetries})`;
|
|
1771
|
+
} else {
|
|
1772
|
+
reason = feedbackEligibility.reason;
|
|
1773
|
+
}
|
|
1774
|
+
} else if (!isValidOnFailurePolicy(authority.onFailure)) {
|
|
1775
|
+
action = "abort";
|
|
1776
|
+
reason = `Policy '${String(authority.onFailure)}' is not a valid onFailure value; treating as abort`;
|
|
1777
|
+
} else {
|
|
1778
|
+
action = "abort";
|
|
1779
|
+
reason = "Policy is 'abort': stop workflow on failure";
|
|
1780
|
+
}
|
|
1781
|
+
return {
|
|
1782
|
+
shouldRetry: false,
|
|
1783
|
+
action,
|
|
1784
|
+
reason,
|
|
1785
|
+
retryEligibility: feedbackEligibility,
|
|
1786
|
+
delaySeconds: 0
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
const feedbackInjected = buildRetryPrompt(authoritativeContext, authority.feedbackTemplate);
|
|
1790
|
+
const modifiedPrompt = feedbackInjected;
|
|
1791
|
+
const delaySeconds = calculateRetryDelay(context.attempt, authority);
|
|
1792
|
+
return {
|
|
1793
|
+
shouldRetry: true,
|
|
1794
|
+
action: "retry",
|
|
1795
|
+
reason: `Retrying after ${context.lastError.code} (feedback attempt ${context.attempt + 1} of ${authority.maxAttempts}): ${feedbackEligibility.reason}`,
|
|
1796
|
+
retryEligibility: feedbackEligibility,
|
|
1797
|
+
delaySeconds,
|
|
1798
|
+
modifiedPrompt,
|
|
1799
|
+
feedbackInjected
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
// src/automation/flywheel-validation.ts
|
|
1804
|
+
function getValidProviderIds() {
|
|
1805
|
+
try {
|
|
1806
|
+
return { ids: ProviderRegistry.getAllProviderIds() };
|
|
1807
|
+
} catch (error) {
|
|
1808
|
+
return {
|
|
1809
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
var VALID_OUTPUT_TYPES = ["diff", "files", "text", "testResults", "json"];
|
|
1814
|
+
var VALIDATION_NO_SIGNALS_WARNING_DETAILS = "has validation enabled but no outputs or checks defined";
|
|
1815
|
+
var VALID_CHECK_TYPES = ["file-exists", "file-contains", "file-extension"];
|
|
1816
|
+
var VALID_PROVIDER_EXECUTION_TRANSPORTS = [
|
|
1817
|
+
"auto",
|
|
1818
|
+
"headless",
|
|
1819
|
+
"tmux-exec",
|
|
1820
|
+
"interactive"
|
|
1821
|
+
];
|
|
1822
|
+
function isValidProviderExecutionTransport(value) {
|
|
1823
|
+
return VALID_PROVIDER_EXECUTION_TRANSPORTS.includes(
|
|
1824
|
+
value
|
|
1825
|
+
);
|
|
1826
|
+
}
|
|
1827
|
+
function appendSeededPlanCompletionContractErrors(parentStageId, plan, errors, pathPrefix = "") {
|
|
1828
|
+
const subStages = plan?.subStages;
|
|
1829
|
+
if (!subStages || subStages.length === 0) {
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
for (const sub of subStages) {
|
|
1833
|
+
const subId = typeof sub.id === "string" && sub.id.length > 0 ? sub.id : "(anonymous)";
|
|
1834
|
+
const nestedPath = pathPrefix ? `${pathPrefix}/${subId}` : subId;
|
|
1835
|
+
if (sub.validation?.completionContract !== void 0) {
|
|
1836
|
+
const contractValidation = validateCompletionContracts(sub.validation.completionContract);
|
|
1837
|
+
for (const contractError of contractValidation.errors) {
|
|
1838
|
+
errors.push(
|
|
1839
|
+
`Stage '${parentStageId}' seeded substage '${nestedPath}' ${contractError.field}: ${contractError.message}`
|
|
1840
|
+
);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
const nestedPlan = sub.seededPlan;
|
|
1844
|
+
appendSeededPlanCompletionContractErrors(parentStageId, nestedPlan, errors, nestedPath);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
function validateVerifyCommandStrings(commands, label, errors, options) {
|
|
1848
|
+
if (!Array.isArray(commands) || options.requireNonEmpty && commands.length === 0) {
|
|
1849
|
+
errors.push(`${label} must be a non-empty string[]`);
|
|
1850
|
+
return false;
|
|
1851
|
+
}
|
|
1852
|
+
for (const [idx, cmd] of commands.entries()) {
|
|
1853
|
+
if (typeof cmd !== "string" || cmd.trim().length === 0) {
|
|
1854
|
+
errors.push(`${label}[${idx}] must be a non-empty string`);
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1857
|
+
const safety = checkVerifyCommandSafety(cmd);
|
|
1858
|
+
if (!safety.safe) {
|
|
1859
|
+
errors.push(`${label}[${idx}] is unsafe: ${safety.error}`);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
return true;
|
|
1863
|
+
}
|
|
1864
|
+
function validateGeneratedSubStageVerifyCommands(subStages) {
|
|
1865
|
+
const errors = [];
|
|
1866
|
+
for (const subStage of subStages) {
|
|
1867
|
+
const label = `Sub-stage '${subStage.id}' verify.commands`;
|
|
1868
|
+
if (subStage.type === "verify") {
|
|
1869
|
+
validateVerifyCommandStrings(subStage.verify?.commands, label, errors, {
|
|
1870
|
+
requireNonEmpty: true
|
|
1871
|
+
});
|
|
1872
|
+
} else if (subStage.verify?.commands !== void 0) {
|
|
1873
|
+
validateVerifyCommandStrings(subStage.verify.commands, label, errors, {
|
|
1874
|
+
requireNonEmpty: false
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
return errors;
|
|
1879
|
+
}
|
|
1880
|
+
function validateExplicitCheck(check, stageId) {
|
|
1881
|
+
const errors = [];
|
|
1882
|
+
if (!VALID_CHECK_TYPES.includes(check.type)) {
|
|
1883
|
+
errors.push(`Stage '${stageId}' has unknown check type: ${check.type}`);
|
|
1884
|
+
}
|
|
1885
|
+
if ("path" in check && check.path) {
|
|
1886
|
+
const pathResult = isValidCheckPath(check.path);
|
|
1887
|
+
if (!pathResult.valid) {
|
|
1888
|
+
errors.push(`Stage '${stageId}' check path invalid: ${pathResult.error}`);
|
|
1889
|
+
}
|
|
1890
|
+
} else if ("path" in check && !check.path) {
|
|
1891
|
+
errors.push(`Stage '${stageId}' check of type '${check.type}' missing required 'path' field`);
|
|
1892
|
+
}
|
|
1893
|
+
if (check.type === "file-contains") {
|
|
1894
|
+
const containsCheck = check;
|
|
1895
|
+
if (!containsCheck.content || containsCheck.content.length === 0) {
|
|
1896
|
+
errors.push(`Stage '${stageId}' file-contains check missing 'content' field`);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
if (errors.length > 0) {
|
|
1900
|
+
return { valid: false, error: errors.join("; ") };
|
|
1901
|
+
}
|
|
1902
|
+
return { valid: true };
|
|
1903
|
+
}
|
|
1904
|
+
function validateStageConfig(stage) {
|
|
1905
|
+
const errors = [];
|
|
1906
|
+
const warnings = [];
|
|
1907
|
+
if (!stage.id || typeof stage.id !== "string") {
|
|
1908
|
+
errors.push("Stage must have a valid id");
|
|
1909
|
+
} else if (!isPathSafeIdentifier(stage.id)) {
|
|
1910
|
+
errors.push(
|
|
1911
|
+
`Stage id '${stage.id}' contains invalid characters. Use only alphanumeric, underscore, and hyphen.`
|
|
1912
|
+
);
|
|
1913
|
+
}
|
|
1914
|
+
if (stage.type !== void 0 && stage.type !== "standard" && stage.type !== "competition" && stage.type !== "verify" && stage.type !== "refinement" && stage.type !== "autoloop" && stage.type !== "orchestrator") {
|
|
1915
|
+
errors.push(
|
|
1916
|
+
`Stage '${stage.id}' has invalid type '${String(stage.type)}'. Valid types: standard, competition, verify, refinement, autoloop, orchestrator`
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
if (stage.type === "verify") {
|
|
1920
|
+
const cmds = stage.verify?.commands;
|
|
1921
|
+
validateVerifyCommandStrings(cmds, `Stage '${stage.id}' verify.commands`, errors, {
|
|
1922
|
+
requireNonEmpty: true
|
|
1923
|
+
});
|
|
1924
|
+
if (stage.provider) {
|
|
1925
|
+
warnings.push(
|
|
1926
|
+
`Stage '${stage.id}' is type 'verify' but specifies provider '${stage.provider}'. Provider will be ignored (verification stages run locally).`
|
|
1927
|
+
);
|
|
1928
|
+
}
|
|
1929
|
+
if (stage.cacheable === true) {
|
|
1930
|
+
warnings.push(
|
|
1931
|
+
`Stage '${stage.id}' sets deprecated cacheable: true; verify stage caching is disabled and commands will run`
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
} else if (stage.cacheable === true) {
|
|
1935
|
+
errors.push(
|
|
1936
|
+
`Stage '${stage.id}' sets cacheable: true, but deterministic stage caching is only supported for type 'verify' stages`
|
|
1937
|
+
);
|
|
1938
|
+
} else if (stage.verify?.commands !== void 0) {
|
|
1939
|
+
validateVerifyCommandStrings(
|
|
1940
|
+
stage.verify.commands,
|
|
1941
|
+
`Stage '${stage.id}' verify.commands`,
|
|
1942
|
+
errors,
|
|
1943
|
+
{
|
|
1944
|
+
requireNonEmpty: false
|
|
1945
|
+
}
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
if (stage.competition?.candidateGate) {
|
|
1949
|
+
const gate = stage.competition.candidateGate;
|
|
1950
|
+
const stageId = stage.id || "unknown";
|
|
1951
|
+
if (!Array.isArray(gate.commands) || gate.commands.length === 0) {
|
|
1952
|
+
errors.push(
|
|
1953
|
+
`Stage '${stageId}' competition.candidateGate.commands must be a non-empty string[]`
|
|
1954
|
+
);
|
|
1955
|
+
} else {
|
|
1956
|
+
for (const [idx, cmd] of gate.commands.entries()) {
|
|
1957
|
+
if (typeof cmd !== "string" || cmd.trim().length === 0) {
|
|
1958
|
+
errors.push(
|
|
1959
|
+
`Stage '${stageId}' competition.candidateGate.commands[${idx}] must be a non-empty string`
|
|
1960
|
+
);
|
|
1961
|
+
continue;
|
|
1962
|
+
}
|
|
1963
|
+
const safety = checkVerifyCommandSafety(cmd);
|
|
1964
|
+
if (!safety.safe) {
|
|
1965
|
+
errors.push(
|
|
1966
|
+
`Stage '${stageId}' competition.candidateGate.commands[${idx}] is unsafe: ${safety.error}`
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
if (gate.coverage) {
|
|
1972
|
+
const coverage = gate.coverage;
|
|
1973
|
+
if (!Array.isArray(coverage.targets) || coverage.targets.length === 0) {
|
|
1974
|
+
errors.push(
|
|
1975
|
+
`Stage '${stageId}' competition.candidateGate.coverage.targets must be string[]`
|
|
1976
|
+
);
|
|
1977
|
+
} else {
|
|
1978
|
+
for (const [idx, target] of coverage.targets.entries()) {
|
|
1979
|
+
if (typeof target !== "string" || target.trim().length === 0) {
|
|
1980
|
+
errors.push(
|
|
1981
|
+
`Stage '${stageId}' competition.candidateGate.coverage.targets[${idx}] must be a non-empty string`
|
|
1982
|
+
);
|
|
1983
|
+
continue;
|
|
1984
|
+
}
|
|
1985
|
+
const pathResult = isValidCheckPath(target);
|
|
1986
|
+
if (!pathResult.valid) {
|
|
1987
|
+
errors.push(
|
|
1988
|
+
`Stage '${stageId}' competition.candidateGate.coverage.targets[${idx}] invalid: ${pathResult.error}`
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
if (coverage.summaryPath !== void 0) {
|
|
1994
|
+
if (typeof coverage.summaryPath !== "string" || coverage.summaryPath.trim().length === 0) {
|
|
1995
|
+
errors.push(
|
|
1996
|
+
`Stage '${stageId}' competition.candidateGate.coverage.summaryPath must be a non-empty string when provided`
|
|
1997
|
+
);
|
|
1998
|
+
} else {
|
|
1999
|
+
const pathResult = isValidCheckPath(coverage.summaryPath);
|
|
2000
|
+
if (!pathResult.valid) {
|
|
2001
|
+
errors.push(
|
|
2002
|
+
`Stage '${stageId}' competition.candidateGate.coverage.summaryPath invalid: ${pathResult.error}`
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
if (coverage.threshold !== void 0) {
|
|
2008
|
+
if (typeof coverage.threshold !== "number" || !Number.isFinite(coverage.threshold)) {
|
|
2009
|
+
errors.push(
|
|
2010
|
+
`Stage '${stageId}' competition.candidateGate.coverage.threshold must be a finite number when provided`
|
|
2011
|
+
);
|
|
2012
|
+
} else if (coverage.threshold < 0 || coverage.threshold > 100) {
|
|
2013
|
+
errors.push(
|
|
2014
|
+
`Stage '${stageId}' competition.candidateGate.coverage.threshold must be between 0 and 100`
|
|
2015
|
+
);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
if (stage.competition?.judgeConfig) {
|
|
2021
|
+
const judgeValidation = validateCompetitionJudgeConfig(
|
|
2022
|
+
stage.id || "unknown",
|
|
2023
|
+
stage.competition.judgeConfig
|
|
2024
|
+
);
|
|
2025
|
+
if (!judgeValidation.valid && judgeValidation.error) {
|
|
2026
|
+
errors.push(judgeValidation.error);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
if (stage.type === "refinement") {
|
|
2030
|
+
if (!stage.refinement) {
|
|
2031
|
+
warnings.push(
|
|
2032
|
+
`Stage '${stage.id}' is type 'refinement' but has no refinement config; it will run as a normal stage without post-competition inputs/routing.`
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
if (stage.competition) {
|
|
2036
|
+
warnings.push(
|
|
2037
|
+
`Stage '${stage.id}' is type 'refinement' but also specifies competition config; competition config will be ignored.`
|
|
2038
|
+
);
|
|
2039
|
+
}
|
|
2040
|
+
if (stage.verify?.commands?.length) {
|
|
2041
|
+
warnings.push(
|
|
2042
|
+
`Stage '${stage.id}' is type 'refinement' but also specifies verify.commands; verify commands will be ignored.`
|
|
2043
|
+
);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
if (stage.type === "orchestrator") {
|
|
2047
|
+
if (!stage.orchestrator) {
|
|
2048
|
+
warnings.push(
|
|
2049
|
+
`Stage '${stage.id}' is type 'orchestrator' but has no orchestrator config; using defaults.`
|
|
2050
|
+
);
|
|
2051
|
+
} else {
|
|
2052
|
+
const cfg = stage.orchestrator;
|
|
2053
|
+
if (cfg.orchestrationProvider !== void 0) {
|
|
2054
|
+
try {
|
|
2055
|
+
ProviderRegistry.getProviderConfig(cfg.orchestrationProvider);
|
|
2056
|
+
} catch (error) {
|
|
2057
|
+
errors.push(
|
|
2058
|
+
`Stage '${stage.id}' orchestrator.orchestrationProvider is invalid: ${error instanceof Error ? error.message : String(error)}`
|
|
2059
|
+
);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
if (cfg.orchestrationProvider !== void 0 && cfg.orchestrationModel !== void 0) {
|
|
2063
|
+
try {
|
|
2064
|
+
ProviderRegistry.resolveModel(cfg.orchestrationProvider, cfg.orchestrationModel);
|
|
2065
|
+
} catch (error) {
|
|
2066
|
+
errors.push(
|
|
2067
|
+
`Stage '${stage.id}' orchestrator.orchestrationModel is invalid for provider '${cfg.orchestrationProvider}': ${error instanceof Error ? error.message : String(error)}`
|
|
2068
|
+
);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
if (cfg.maxSubStages !== void 0) {
|
|
2072
|
+
if (!Number.isInteger(cfg.maxSubStages) || cfg.maxSubStages < 1) {
|
|
2073
|
+
errors.push(
|
|
2074
|
+
`Stage '${stage.id}' orchestrator.maxSubStages must be a positive integer (got ${cfg.maxSubStages})`
|
|
2075
|
+
);
|
|
2076
|
+
} else if (cfg.maxSubStages > 20) {
|
|
2077
|
+
warnings.push(
|
|
2078
|
+
`Stage '${stage.id}' orchestrator.maxSubStages is very high (${cfg.maxSubStages}). Consider limiting to \u226410 for cost control.`
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
if (cfg.budgetPerSubStage !== void 0) {
|
|
2083
|
+
if (!Number.isFinite(cfg.budgetPerSubStage) || cfg.budgetPerSubStage < 0) {
|
|
2084
|
+
errors.push(
|
|
2085
|
+
`Stage '${stage.id}' orchestrator.budgetPerSubStage must be non-negative (got ${cfg.budgetPerSubStage})`
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
if (cfg.totalBudget !== void 0) {
|
|
2090
|
+
if (!Number.isFinite(cfg.totalBudget) || cfg.totalBudget < 0) {
|
|
2091
|
+
errors.push(
|
|
2092
|
+
`Stage '${stage.id}' orchestrator.totalBudget must be non-negative (got ${cfg.totalBudget})`
|
|
2093
|
+
);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
if (cfg.maxReplanningAttempts !== void 0) {
|
|
2097
|
+
if (!Number.isInteger(cfg.maxReplanningAttempts) || cfg.maxReplanningAttempts < 0) {
|
|
2098
|
+
errors.push(
|
|
2099
|
+
`Stage '${stage.id}' orchestrator.maxReplanningAttempts must be non-negative integer (got ${cfg.maxReplanningAttempts})`
|
|
2100
|
+
);
|
|
2101
|
+
} else if (cfg.maxReplanningAttempts > 1) {
|
|
2102
|
+
errors.push(
|
|
2103
|
+
`Stage '${stage.id}' orchestrator.maxReplanningAttempts cannot exceed 1 until multi-pass replanning is supported (got ${cfg.maxReplanningAttempts})`
|
|
2104
|
+
);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
if (cfg.retryBudgetPerSubStage !== void 0) {
|
|
2108
|
+
if (!Number.isInteger(cfg.retryBudgetPerSubStage) || cfg.retryBudgetPerSubStage < 0) {
|
|
2109
|
+
errors.push(
|
|
2110
|
+
`Stage '${stage.id}' orchestrator.retryBudgetPerSubStage must be non-negative integer (got ${cfg.retryBudgetPerSubStage})`
|
|
2111
|
+
);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
if (cfg.maxRecursionDepth !== void 0) {
|
|
2115
|
+
if (!Number.isInteger(cfg.maxRecursionDepth) || cfg.maxRecursionDepth < 0) {
|
|
2116
|
+
errors.push(
|
|
2117
|
+
`Stage '${stage.id}' orchestrator.maxRecursionDepth must be non-negative integer (got ${cfg.maxRecursionDepth})`
|
|
2118
|
+
);
|
|
2119
|
+
} else if (cfg.maxRecursionDepth > 3) {
|
|
2120
|
+
warnings.push(
|
|
2121
|
+
`Stage '${stage.id}' orchestrator.maxRecursionDepth is high (${cfg.maxRecursionDepth}). Deep recursion may be expensive.`
|
|
2122
|
+
);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
if (stage.competition) {
|
|
2127
|
+
warnings.push(
|
|
2128
|
+
`Stage '${stage.id}' is type 'orchestrator' but also specifies competition config; competition config will be ignored.`
|
|
2129
|
+
);
|
|
2130
|
+
}
|
|
2131
|
+
if (stage.autoloop) {
|
|
2132
|
+
warnings.push(
|
|
2133
|
+
`Stage '${stage.id}' is type 'orchestrator' but also specifies autoloop config; autoloop config will be ignored.`
|
|
2134
|
+
);
|
|
2135
|
+
}
|
|
2136
|
+
if (stage.verify?.commands?.length) {
|
|
2137
|
+
warnings.push(
|
|
2138
|
+
`Stage '${stage.id}' is type 'orchestrator' but also specifies verify.commands; verify commands will be ignored.`
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2141
|
+
if (stage.refinement) {
|
|
2142
|
+
warnings.push(
|
|
2143
|
+
`Stage '${stage.id}' is type 'orchestrator' but also specifies refinement config; refinement config will be ignored.`
|
|
2144
|
+
);
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
if (stage.type === "autoloop") {
|
|
2148
|
+
if (!stage.autoloop) {
|
|
2149
|
+
warnings.push(
|
|
2150
|
+
`Stage '${stage.id}' is type 'autoloop' but has no autoloop config; using defaults (maxIterations: 3, timeboxMs: 1800000).`
|
|
2151
|
+
);
|
|
2152
|
+
} else {
|
|
2153
|
+
const cfg = stage.autoloop;
|
|
2154
|
+
if (cfg.maxIterations !== void 0) {
|
|
2155
|
+
if (!Number.isInteger(cfg.maxIterations) || cfg.maxIterations < 1) {
|
|
2156
|
+
errors.push(
|
|
2157
|
+
`Stage '${stage.id}' autoloop.maxIterations must be a positive integer (got ${cfg.maxIterations})`
|
|
2158
|
+
);
|
|
2159
|
+
} else if (cfg.maxIterations > 20) {
|
|
2160
|
+
warnings.push(
|
|
2161
|
+
`Stage '${stage.id}' autoloop.maxIterations is very high (${cfg.maxIterations}). Consider limiting to \u226410 for cost control.`
|
|
2162
|
+
);
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
if (cfg.timeboxMs !== void 0) {
|
|
2166
|
+
if (!Number.isFinite(cfg.timeboxMs) || cfg.timeboxMs < 1e3) {
|
|
2167
|
+
errors.push(
|
|
2168
|
+
`Stage '${stage.id}' autoloop.timeboxMs must be at least 1000ms (got ${cfg.timeboxMs})`
|
|
2169
|
+
);
|
|
2170
|
+
} else if (cfg.timeboxMs > 36e5) {
|
|
2171
|
+
warnings.push(
|
|
2172
|
+
`Stage '${stage.id}' autoloop.timeboxMs is very high (${cfg.timeboxMs}ms = ${(cfg.timeboxMs / 6e4).toFixed(1)} minutes). Consider limiting to \u226430 minutes.`
|
|
2173
|
+
);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
if (cfg.minProgress !== void 0) {
|
|
2177
|
+
if (!Number.isFinite(cfg.minProgress) || cfg.minProgress < 0 || cfg.minProgress > 1) {
|
|
2178
|
+
errors.push(
|
|
2179
|
+
`Stage '${stage.id}' autoloop.minProgress must be between 0 and 1 (got ${cfg.minProgress})`
|
|
2180
|
+
);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
if (stage.competition) {
|
|
2185
|
+
warnings.push(
|
|
2186
|
+
`Stage '${stage.id}' is type 'autoloop' but also specifies competition config; competition config will be ignored.`
|
|
2187
|
+
);
|
|
2188
|
+
}
|
|
2189
|
+
if (stage.verify?.commands?.length) {
|
|
2190
|
+
const gatesEnabled = stage.autoloop?.stopOnQualityGate === true || Array.isArray(stage.autoloop?.qualityGateCommands) && stage.autoloop?.qualityGateCommands.length > 0;
|
|
2191
|
+
if (!gatesEnabled) {
|
|
2192
|
+
warnings.push(
|
|
2193
|
+
`Stage '${stage.id}' is type 'autoloop' but also specifies verify.commands; verify commands will be ignored unless autoloop.stopOnQualityGate=true (or autoloop.qualityGateCommands is set).`
|
|
2194
|
+
);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
if (stage.refinement) {
|
|
2198
|
+
warnings.push(
|
|
2199
|
+
`Stage '${stage.id}' is type 'autoloop' but also specifies refinement config; refinement config will be ignored.`
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
if (stage.outputs) {
|
|
2204
|
+
for (const [outputName, outputType] of Object.entries(stage.outputs)) {
|
|
2205
|
+
if (!isPathSafeIdentifier(outputName)) {
|
|
2206
|
+
errors.push(
|
|
2207
|
+
`Stage '${stage.id}' output name '${outputName}' contains invalid characters. Use only alphanumeric, underscore, and hyphen.`
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
2210
|
+
if (!VALID_OUTPUT_TYPES.includes(outputType)) {
|
|
2211
|
+
errors.push(
|
|
2212
|
+
`Stage '${stage.id}' output '${outputName}' has invalid type '${outputType}'. Valid types: ${VALID_OUTPUT_TYPES.join(", ")}`
|
|
2213
|
+
);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
if (stage.provider !== void 0) {
|
|
2218
|
+
const providerIds = getValidProviderIds();
|
|
2219
|
+
if (!providerIds.ids) {
|
|
2220
|
+
errors.push(
|
|
2221
|
+
`Stage '${stage.id}' provider '${stage.provider}' could not be validated because the provider registry is unavailable: ${providerIds.error}`
|
|
2222
|
+
);
|
|
2223
|
+
} else if (!providerIds.ids.includes(stage.provider)) {
|
|
2224
|
+
errors.push(
|
|
2225
|
+
`Stage '${stage.id}' has invalid provider '${stage.provider}'. Valid providers: ${providerIds.ids.join(", ")}`
|
|
2226
|
+
);
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
if (stage.model !== void 0 && stage.provider === void 0) {
|
|
2230
|
+
warnings.push(
|
|
2231
|
+
`Stage '${stage.id}' specifies model '${stage.model}' without provider. Model may be ignored during routing. Consider setting provider explicitly.`
|
|
2232
|
+
);
|
|
2233
|
+
}
|
|
2234
|
+
if (stage.providerExecutionTransport !== void 0 && !isValidProviderExecutionTransport(stage.providerExecutionTransport)) {
|
|
2235
|
+
errors.push(
|
|
2236
|
+
`Stage '${stage.id}' has invalid providerExecutionTransport '${String(
|
|
2237
|
+
stage.providerExecutionTransport
|
|
2238
|
+
)}'. Valid values: ${VALID_PROVIDER_EXECUTION_TRANSPORTS.join(", ")}`
|
|
2239
|
+
);
|
|
2240
|
+
}
|
|
2241
|
+
if (stage.competition?.variants) {
|
|
2242
|
+
for (const [variantIdx, variant] of stage.competition.variants.entries()) {
|
|
2243
|
+
if (variant.providerExecutionTransport !== void 0 && !isValidProviderExecutionTransport(variant.providerExecutionTransport)) {
|
|
2244
|
+
errors.push(
|
|
2245
|
+
`Stage '${stage.id}' competition.variants[${variantIdx}] has invalid providerExecutionTransport '${String(
|
|
2246
|
+
variant.providerExecutionTransport
|
|
2247
|
+
)}'. Valid values: ${VALID_PROVIDER_EXECUTION_TRANSPORTS.join(", ")}`
|
|
2248
|
+
);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
if (stage.modelRole !== void 0 && stage.provider === void 0) {
|
|
2253
|
+
warnings.push(
|
|
2254
|
+
`Stage '${stage.id}' specifies modelRole '${stage.modelRole}' without provider. modelRole requires provider to be set for registry role lookup.`
|
|
2255
|
+
);
|
|
2256
|
+
}
|
|
2257
|
+
if (stage.validation?.enabled && stage.outputs) {
|
|
2258
|
+
const outputCount = Object.keys(stage.outputs).length;
|
|
2259
|
+
if (outputCount > 1 && !stage.validation.target) {
|
|
2260
|
+
errors.push(
|
|
2261
|
+
`Stage '${stage.id}' has ${outputCount} outputs but validation.target not specified. Must specify which output to validate: [${Object.keys(stage.outputs).join(", ")}]`
|
|
2262
|
+
);
|
|
2263
|
+
}
|
|
2264
|
+
if (stage.validation.target) {
|
|
2265
|
+
if (!Object.hasOwn(stage.outputs, stage.validation.target)) {
|
|
2266
|
+
errors.push(
|
|
2267
|
+
`Stage '${stage.id}' validation target '${stage.validation.target}' not found in outputs: [${Object.keys(stage.outputs).join(", ")}]`
|
|
2268
|
+
);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
if (stage.validation?.threshold !== void 0) {
|
|
2273
|
+
if (stage.validation.threshold < 0 || stage.validation.threshold > 100) {
|
|
2274
|
+
errors.push(
|
|
2275
|
+
`Stage '${stage.id}' validation threshold ${stage.validation.threshold} must be between 0 and 100`
|
|
2276
|
+
);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
if (stage.validation?.maxRetries !== void 0) {
|
|
2280
|
+
if (stage.validation.maxRetries < 0 || stage.validation.maxRetries > 10) {
|
|
2281
|
+
errors.push(
|
|
2282
|
+
`Stage '${stage.id}' validation maxRetries ${stage.validation.maxRetries} must be between 0 and 10`
|
|
2283
|
+
);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
if (stage.validation?.onFailure !== void 0) {
|
|
2287
|
+
if (!isValidOnFailurePolicy(stage.validation.onFailure)) {
|
|
2288
|
+
errors.push(
|
|
2289
|
+
`Stage '${stage.id}' validation.onFailure '${String(stage.validation.onFailure)}' is invalid. Valid values: ${VALID_ON_FAILURE_POLICIES.join(", ")}`
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2292
|
+
if (stage.validation.onFailure === "retry-with-feedback" && stage.type !== void 0 && stage.type !== "standard" && stage.type !== "refinement") {
|
|
2293
|
+
errors.push(
|
|
2294
|
+
`Stage '${stage.id}' validation.onFailure 'retry-with-feedback' is only supported for standard and refinement stages`
|
|
2295
|
+
);
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
if (stage.validation?.checks && stage.validation.checks.length > 0) {
|
|
2299
|
+
for (const check of stage.validation.checks) {
|
|
2300
|
+
const checkResult = validateExplicitCheck(check, stage.id);
|
|
2301
|
+
if (!checkResult.valid && checkResult.error) {
|
|
2302
|
+
errors.push(checkResult.error);
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
if (stage.validation?.requiredFiles && stage.validation.requiredFiles.length > 0) {
|
|
2307
|
+
for (const requiredFile of stage.validation.requiredFiles) {
|
|
2308
|
+
const pathResult = isValidCheckPath(requiredFile);
|
|
2309
|
+
if (!pathResult.valid) {
|
|
2310
|
+
errors.push(`Stage '${stage.id}' requiredFiles path invalid: ${pathResult.error}`);
|
|
2311
|
+
continue;
|
|
2312
|
+
}
|
|
2313
|
+
if (requiredFile.endsWith("/")) {
|
|
2314
|
+
errors.push(
|
|
2315
|
+
`Stage '${stage.id}' requiredFiles must use exact file paths, not directories: ${requiredFile}`
|
|
2316
|
+
);
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
if (stage.validation?.completionContract !== void 0) {
|
|
2321
|
+
const contractValidation = validateCompletionContracts(stage.validation.completionContract);
|
|
2322
|
+
for (const contractError of contractValidation.errors) {
|
|
2323
|
+
errors.push(`Stage '${stage.id}' ${contractError.field}: ${contractError.message}`);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
appendSeededPlanCompletionContractErrors(stage.id, stage.seededPlan, errors);
|
|
2327
|
+
if (stage.allowlist && stage.allowlist.length > 0) {
|
|
2328
|
+
for (const allowlistPath of stage.allowlist) {
|
|
2329
|
+
const pathResult = isValidCheckPath(allowlistPath);
|
|
2330
|
+
if (!pathResult.valid) {
|
|
2331
|
+
errors.push(`Stage '${stage.id}' allowlist path invalid: ${pathResult.error}`);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
if (stage.type !== "verify" && stage.capabilities?.executionIntent === "analysis" && (!stage.outputs || Object.keys(stage.outputs).length === 0)) {
|
|
2336
|
+
errors.push(
|
|
2337
|
+
`Stage '${stage.id}' is analysis-intent but has no outputs configured. Add outputs (recommended: outputs: { report: 'text' }).`
|
|
2338
|
+
);
|
|
2339
|
+
}
|
|
2340
|
+
if (stage.allowlist && stage.allowlist.length > 0 && stage.outputs) {
|
|
2341
|
+
const outputTypes = Object.values(stage.outputs);
|
|
2342
|
+
const hasCodeOutput = outputTypes.some((t) => t === "diff" || t === "files");
|
|
2343
|
+
const hasTextOutput = outputTypes.some(
|
|
2344
|
+
(t) => t === "text" || t === "json" || t === "testResults"
|
|
2345
|
+
);
|
|
2346
|
+
if (hasTextOutput && !hasCodeOutput) {
|
|
2347
|
+
if (stage.capabilities?.executionIntent === "analysis") {
|
|
2348
|
+
warnings.push(
|
|
2349
|
+
`Stage '${stage.id}' sets executionIntent: analysis with a non-empty allowlist. Providers run read-only; allowlisted edits will be denied unless intent or outputs change.`
|
|
2350
|
+
);
|
|
2351
|
+
} else if (stage.capabilities?.executionIntent !== "edit") {
|
|
2352
|
+
warnings.push(
|
|
2353
|
+
`Stage '${stage.id}' declares an allowlist but only text-type outputs. Execution runs with edit intent because the allowlist signals file modifications. Add a code-type output (e.g. outputs: { patch: diff }) or set capabilities.executionIntent: edit.`
|
|
2354
|
+
);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
if (stage.allowlistMode === "strict") {
|
|
2359
|
+
if (!stage.allowlist || stage.allowlist.length === 0) {
|
|
2360
|
+
warnings.push(
|
|
2361
|
+
`Stage '${stage.id}' uses allowlistMode: strict with no allowlist entries. Any file changes will fail the stage.`
|
|
2362
|
+
);
|
|
2363
|
+
} else if (!stage.allowlist.some((p) => p.endsWith("/"))) {
|
|
2364
|
+
warnings.push(
|
|
2365
|
+
`Stage '${stage.id}' uses allowlistMode: strict with file-level allowlist entries only. This commonly fails on ripple edits (e.g. async propagation). Consider directory prefixes or allowlistMode: warn.`
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
if (stage.validation?.enabled && stage.type !== "verify" && !stage.outputs && !stage.validation?.checks?.length && !stage.validation?.semanticChecks?.length && !stage.verify?.commands?.length) {
|
|
2370
|
+
warnings.push(
|
|
2371
|
+
`Stage '${stage.id}' ${VALIDATION_NO_SIGNALS_WARNING_DETAILS}. This is informational only and does not cause stage failure. Consider adding validation.checks or outputs for meaningful validation signals.`
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
if (stage.gates && stage.gates.length > 0) {
|
|
2375
|
+
for (const [idx, gate] of stage.gates.entries()) {
|
|
2376
|
+
const validGateTypes = ["biome", "test", "build", "custom"];
|
|
2377
|
+
if (!validGateTypes.includes(gate.type)) {
|
|
2378
|
+
errors.push(
|
|
2379
|
+
`Stage '${stage.id}' gates[${idx}].type is invalid: '${gate.type}'. Valid types: ${validGateTypes.join(", ")}`
|
|
2380
|
+
);
|
|
2381
|
+
}
|
|
2382
|
+
const validGateModes = ["block", "warn"];
|
|
2383
|
+
if (!validGateModes.includes(gate.mode)) {
|
|
2384
|
+
errors.push(
|
|
2385
|
+
`Stage '${stage.id}' gates[${idx}].mode is invalid: '${gate.mode}'. Valid modes: ${validGateModes.join(", ")}`
|
|
2386
|
+
);
|
|
2387
|
+
}
|
|
2388
|
+
if (gate.files && gate.files.length > 0) {
|
|
2389
|
+
for (const [fileIdx, filePath] of gate.files.entries()) {
|
|
2390
|
+
const pathResult = isValidCheckPath(filePath);
|
|
2391
|
+
if (!pathResult.valid) {
|
|
2392
|
+
errors.push(
|
|
2393
|
+
`Stage '${stage.id}' gates[${idx}].files[${fileIdx}] invalid: ${pathResult.error}`
|
|
2394
|
+
);
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
if (gate.type === "custom" && (!gate.command || gate.command.trim().length === 0)) {
|
|
2399
|
+
errors.push(
|
|
2400
|
+
`Stage '${stage.id}' gates[${idx}] type 'custom' requires non-empty 'command' field`
|
|
2401
|
+
);
|
|
2402
|
+
}
|
|
2403
|
+
if (gate.maxDiagnostics !== void 0) {
|
|
2404
|
+
if (gate.type !== "biome") {
|
|
2405
|
+
warnings.push(
|
|
2406
|
+
`Stage '${stage.id}' gates[${idx}].maxDiagnostics is set but gate type is '${gate.type}' (only applies to 'biome')`
|
|
2407
|
+
);
|
|
2408
|
+
} else if (!Number.isInteger(gate.maxDiagnostics) || gate.maxDiagnostics < 1 || gate.maxDiagnostics > 1e3) {
|
|
2409
|
+
errors.push(
|
|
2410
|
+
`Stage '${stage.id}' gates[${idx}].maxDiagnostics must be an integer between 1 and 1000 (got ${gate.maxDiagnostics})`
|
|
2411
|
+
);
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
if (errors.length > 0) {
|
|
2417
|
+
return {
|
|
2418
|
+
valid: false,
|
|
2419
|
+
error: errors.join("; "),
|
|
2420
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
return {
|
|
2424
|
+
valid: true,
|
|
2425
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2428
|
+
function isValidationNoSignalsWarning(warning, stageId) {
|
|
2429
|
+
return warning.startsWith(`Stage '${stageId}' ${VALIDATION_NO_SIGNALS_WARNING_DETAILS}.`);
|
|
2430
|
+
}
|
|
2431
|
+
function validateWorkflowConfig(workflow) {
|
|
2432
|
+
const errors = [];
|
|
2433
|
+
const warnings = [];
|
|
2434
|
+
const validationNoSignalsStageIds = [];
|
|
2435
|
+
if (!workflow.stages || workflow.stages.length === 0) {
|
|
2436
|
+
return {
|
|
2437
|
+
valid: false,
|
|
2438
|
+
error: "Workflow must have at least one stage"
|
|
2439
|
+
};
|
|
2440
|
+
}
|
|
2441
|
+
const stageIds = new Set(workflow.stages.map((s) => s.id));
|
|
2442
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
2443
|
+
for (const stage of workflow.stages) {
|
|
2444
|
+
if (seenIds.has(stage.id)) {
|
|
2445
|
+
errors.push(`Duplicate stage id: '${stage.id}'`);
|
|
2446
|
+
}
|
|
2447
|
+
seenIds.add(stage.id);
|
|
2448
|
+
}
|
|
2449
|
+
for (const stage of workflow.stages) {
|
|
2450
|
+
const stageResult = validateStageConfig(stage);
|
|
2451
|
+
if (!stageResult.valid && stageResult.error) {
|
|
2452
|
+
errors.push(stageResult.error);
|
|
2453
|
+
}
|
|
2454
|
+
if (stageResult.warnings) {
|
|
2455
|
+
for (const warning of stageResult.warnings) {
|
|
2456
|
+
if (isValidationNoSignalsWarning(warning, stage.id)) {
|
|
2457
|
+
validationNoSignalsStageIds.push(stage.id);
|
|
2458
|
+
} else {
|
|
2459
|
+
warnings.push(warning);
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
if (stage.deps) {
|
|
2464
|
+
for (const dep of stage.deps) {
|
|
2465
|
+
if (!stageIds.has(dep)) {
|
|
2466
|
+
errors.push(
|
|
2467
|
+
`Stage '${stage.id}' has dependency '${dep}' which does not exist. Available stages: [${Array.from(stageIds).join(", ")}]`
|
|
2468
|
+
);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
const cycleError = detectCircularDependencies(workflow.stages);
|
|
2474
|
+
if (cycleError) {
|
|
2475
|
+
errors.push(cycleError);
|
|
2476
|
+
}
|
|
2477
|
+
errors.push(...validateArtifactReferences(workflow));
|
|
2478
|
+
if (workflow.strict?.denylist && workflow.strict.denylist.length > 0) {
|
|
2479
|
+
for (const denylistPath of workflow.strict.denylist) {
|
|
2480
|
+
const pathResult = isValidCheckPath(denylistPath);
|
|
2481
|
+
if (!pathResult.valid) {
|
|
2482
|
+
errors.push(`Workflow strict.denylist path invalid: ${pathResult.error}`);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
if (workflow.strict?.denylistMode) {
|
|
2487
|
+
const mode = workflow.strict.denylistMode;
|
|
2488
|
+
if (mode !== "off" && mode !== "warn" && mode !== "strict") {
|
|
2489
|
+
errors.push(
|
|
2490
|
+
`Workflow strict.denylistMode invalid: '${String(mode)}' (expected off|warn|strict)`
|
|
2491
|
+
);
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
if (validationNoSignalsStageIds.length > 0) {
|
|
2495
|
+
warnings.push(
|
|
2496
|
+
`Workflow has validation enabled but no outputs or checks defined for stages: ${validationNoSignalsStageIds.join(
|
|
2497
|
+
", "
|
|
2498
|
+
)}. This is informational only; add validation.checks or outputs for meaningful validation signals.`
|
|
2499
|
+
);
|
|
2500
|
+
}
|
|
2501
|
+
if (errors.length > 0) {
|
|
2502
|
+
return {
|
|
2503
|
+
valid: false,
|
|
2504
|
+
error: errors.join("; "),
|
|
2505
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
2506
|
+
};
|
|
2507
|
+
}
|
|
2508
|
+
return {
|
|
2509
|
+
valid: true,
|
|
2510
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
2511
|
+
};
|
|
2512
|
+
}
|
|
2513
|
+
function validateArtifactReferences(workflow) {
|
|
2514
|
+
const errors = [];
|
|
2515
|
+
const stageOutputs = /* @__PURE__ */ new Map();
|
|
2516
|
+
const stageMap = /* @__PURE__ */ new Map();
|
|
2517
|
+
for (const stage of workflow.stages ?? []) {
|
|
2518
|
+
stageMap.set(stage.id, stage);
|
|
2519
|
+
stageOutputs.set(stage.id, new Set(Object.keys(stage.outputs ?? {})));
|
|
2520
|
+
}
|
|
2521
|
+
const dependencyClosureCache = /* @__PURE__ */ new Map();
|
|
2522
|
+
const getDependencyClosure = (stageId) => {
|
|
2523
|
+
const cached = dependencyClosureCache.get(stageId);
|
|
2524
|
+
if (cached) return cached;
|
|
2525
|
+
const closure = /* @__PURE__ */ new Set();
|
|
2526
|
+
const visit = (depId) => {
|
|
2527
|
+
if (closure.has(depId)) return;
|
|
2528
|
+
closure.add(depId);
|
|
2529
|
+
const depStage = stageMap.get(depId);
|
|
2530
|
+
for (const transitiveDep of depStage?.deps ?? []) {
|
|
2531
|
+
visit(transitiveDep);
|
|
2532
|
+
}
|
|
2533
|
+
};
|
|
2534
|
+
const stage = stageMap.get(stageId);
|
|
2535
|
+
for (const dep of stage?.deps ?? []) {
|
|
2536
|
+
visit(dep);
|
|
2537
|
+
}
|
|
2538
|
+
dependencyClosureCache.set(stageId, closure);
|
|
2539
|
+
return closure;
|
|
2540
|
+
};
|
|
2541
|
+
for (const stage of workflow.stages ?? []) {
|
|
2542
|
+
for (const ref of stage.inputs ?? []) {
|
|
2543
|
+
try {
|
|
2544
|
+
const { stageId, outputName } = parseArtifactRef(ref);
|
|
2545
|
+
const outputs = stageOutputs.get(stageId);
|
|
2546
|
+
if (!outputs) {
|
|
2547
|
+
errors.push(
|
|
2548
|
+
`Stage '${stage.id}' references artifact '${ref}' from non-existent stage '${stageId}'`
|
|
2549
|
+
);
|
|
2550
|
+
continue;
|
|
2551
|
+
}
|
|
2552
|
+
if (!outputs.has(outputName)) {
|
|
2553
|
+
const available = outputs.size > 0 ? Array.from(outputs).join(", ") : "none";
|
|
2554
|
+
errors.push(
|
|
2555
|
+
`Stage '${stage.id}' references output '${outputName}' which doesn't exist in stage '${stageId}'. Available: [${available}]`
|
|
2556
|
+
);
|
|
2557
|
+
continue;
|
|
2558
|
+
}
|
|
2559
|
+
if (!getDependencyClosure(stage.id).has(stageId)) {
|
|
2560
|
+
errors.push(
|
|
2561
|
+
`Stage '${stage.id}' references artifact '${ref}' but does not depend on producer stage '${stageId}'`
|
|
2562
|
+
);
|
|
2563
|
+
}
|
|
2564
|
+
} catch (err) {
|
|
2565
|
+
errors.push(
|
|
2566
|
+
`Stage '${stage.id}' has invalid artifact reference '${ref}': ${err instanceof Error ? err.message : String(err)}`
|
|
2567
|
+
);
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
return errors;
|
|
2572
|
+
}
|
|
2573
|
+
function detectCircularDependencies(stages) {
|
|
2574
|
+
const stageMap = new Map(stages.map((s) => [s.id, s]));
|
|
2575
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
2576
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2577
|
+
function dfs(stageId, path5) {
|
|
2578
|
+
if (visited.has(stageId)) {
|
|
2579
|
+
return null;
|
|
2580
|
+
}
|
|
2581
|
+
if (visiting.has(stageId)) {
|
|
2582
|
+
const cycleStart = path5.indexOf(stageId);
|
|
2583
|
+
const cycle = [...path5.slice(cycleStart), stageId].join(" \u2192 ");
|
|
2584
|
+
return `Circular dependency detected: ${cycle}`;
|
|
2585
|
+
}
|
|
2586
|
+
visiting.add(stageId);
|
|
2587
|
+
path5.push(stageId);
|
|
2588
|
+
const stage = stageMap.get(stageId);
|
|
2589
|
+
if (stage?.deps) {
|
|
2590
|
+
for (const dep of stage.deps) {
|
|
2591
|
+
const result = dfs(dep, [...path5]);
|
|
2592
|
+
if (result) {
|
|
2593
|
+
return result;
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
visiting.delete(stageId);
|
|
2598
|
+
visited.add(stageId);
|
|
2599
|
+
return null;
|
|
2600
|
+
}
|
|
2601
|
+
for (const stage of stages) {
|
|
2602
|
+
const result = dfs(stage.id, []);
|
|
2603
|
+
if (result) {
|
|
2604
|
+
return result;
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
return null;
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
// src/automation/installed-dependency-drift.ts
|
|
2611
|
+
function formatInstalledDependencyDriftWarning(drift, projectRoot) {
|
|
2612
|
+
const [first] = drift;
|
|
2613
|
+
if (!first) return null;
|
|
2614
|
+
const more = drift.length > 1 ? ` and ${drift.length - 1} more` : "";
|
|
2615
|
+
return `Warning: installed packages do not match package-lock.json (${first.name} ${first.installed}, locked ${first.locked}${more}). Run \`npm ci\` in ${projectRoot} to install them.`;
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
// src/automation/flywheel-preflight.ts
|
|
2619
|
+
var PACKAGE_ROOT_ENV = "BUILD_PACKAGE_ROOT";
|
|
2620
|
+
function isCodexProviderInsideCodexSandbox(providerId) {
|
|
2621
|
+
return providerId === "codex" && process.env.CODEX_SANDBOX !== void 0;
|
|
2622
|
+
}
|
|
2623
|
+
async function checkRepoClean(cwd, mode, suppressExpectedSetupWarning = false) {
|
|
2624
|
+
const start = Date.now();
|
|
2625
|
+
const check = "repo-clean";
|
|
2626
|
+
const description = "Repository working directory is clean";
|
|
2627
|
+
try {
|
|
2628
|
+
const { stdout } = await execa2("git", ["status", "--porcelain", "--untracked-files=all"], {
|
|
2629
|
+
cwd,
|
|
2630
|
+
stdio: "pipe",
|
|
2631
|
+
timeout: 1e4
|
|
2632
|
+
});
|
|
2633
|
+
const lines = filterWorktreeRunSentinelStatusOutput(stdout).trimEnd().split("\n").filter((line) => line.length > 0);
|
|
2634
|
+
if (lines.length === 0) {
|
|
2635
|
+
return {
|
|
2636
|
+
check,
|
|
2637
|
+
description,
|
|
2638
|
+
passed: true,
|
|
2639
|
+
durationMs: Date.now() - start
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
const changedPaths = lines.flatMap(parsePorcelainLinePaths);
|
|
2643
|
+
const pathSummary = summarizeRepoPaths(changedPaths);
|
|
2644
|
+
const conflictStatuses = /* @__PURE__ */ new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
|
|
2645
|
+
const hasConflictStatuses = lines.some((line) => conflictStatuses.has(line.slice(0, 2)));
|
|
2646
|
+
const runtimeArtifactOnly = pathSummary.categories.length > 0 && pathSummary.categories.every(
|
|
2647
|
+
(category) => category === "runtime-artifact" || category === "generated-artifact"
|
|
2648
|
+
);
|
|
2649
|
+
const expectedSetupDirt = pathSummary.systemWriteOnly;
|
|
2650
|
+
const unstaged = lines.filter((l) => l.startsWith(" M") || l.startsWith("??"));
|
|
2651
|
+
const staged = lines.filter((l) => l.startsWith("M ") || l.startsWith("A "));
|
|
2652
|
+
let message = `Repository has ${lines.length} uncommitted change(s):
|
|
2653
|
+
`;
|
|
2654
|
+
if (staged.length > 0) {
|
|
2655
|
+
message += ` Staged: ${staged.length} file(s)
|
|
2656
|
+
`;
|
|
2657
|
+
}
|
|
2658
|
+
if (unstaged.length > 0) {
|
|
2659
|
+
message += ` Unstaged/Untracked: ${unstaged.length} file(s)
|
|
2660
|
+
`;
|
|
2661
|
+
}
|
|
2662
|
+
if (hasConflictStatuses) {
|
|
2663
|
+
message += " Scope: merge/conflict state detected\n";
|
|
2664
|
+
} else if (pathSummary.systemWriteOnly) {
|
|
2665
|
+
message += " Scope: managed/runtime metadata only (task/, sprint/, backlog/, .build-project/codex-home/, .build-project/plans/, .build-project/planning/)\n";
|
|
2666
|
+
} else if (pathSummary.harmlessOnly) {
|
|
2667
|
+
message += " Scope: generated/runtime artifacts only\n";
|
|
2668
|
+
} else {
|
|
2669
|
+
message += " Scope: includes repository content changes\n";
|
|
2670
|
+
}
|
|
2671
|
+
message += `Run 'git status' to see details.`;
|
|
2672
|
+
const managedSetupOnly = pathSummary.systemWriteOnly || pathSummary.harmlessOnly;
|
|
2673
|
+
const metadata = {
|
|
2674
|
+
changedPaths,
|
|
2675
|
+
systemWriteOnly: pathSummary.systemWriteOnly,
|
|
2676
|
+
harmlessOnly: pathSummary.harmlessOnly,
|
|
2677
|
+
categories: pathSummary.categories,
|
|
2678
|
+
hasConflictStatuses,
|
|
2679
|
+
runtimeArtifactOnly,
|
|
2680
|
+
expectedSetupDirt
|
|
2681
|
+
};
|
|
2682
|
+
if (hasConflictStatuses || mode === "fail" && !runtimeArtifactOnly && !managedSetupOnly) {
|
|
2683
|
+
message += hasConflictStatuses ? " Resolve conflicts before running Flywheel." : " Commit or stash changes before running Flywheel (or run without strict cleanliness).";
|
|
2684
|
+
return {
|
|
2685
|
+
check,
|
|
2686
|
+
description,
|
|
2687
|
+
passed: false,
|
|
2688
|
+
error: message,
|
|
2689
|
+
metadata,
|
|
2690
|
+
durationMs: Date.now() - start
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
if (suppressExpectedSetupWarning && managedSetupOnly) {
|
|
2694
|
+
return {
|
|
2695
|
+
check,
|
|
2696
|
+
description,
|
|
2697
|
+
passed: true,
|
|
2698
|
+
metadata,
|
|
2699
|
+
durationMs: Date.now() - start
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2702
|
+
return {
|
|
2703
|
+
check,
|
|
2704
|
+
description,
|
|
2705
|
+
passed: true,
|
|
2706
|
+
warning: message,
|
|
2707
|
+
metadata,
|
|
2708
|
+
durationMs: Date.now() - start
|
|
2709
|
+
};
|
|
2710
|
+
} catch (err) {
|
|
2711
|
+
return {
|
|
2712
|
+
check,
|
|
2713
|
+
description,
|
|
2714
|
+
passed: false,
|
|
2715
|
+
error: `Failed to check git status: ${err instanceof Error ? err.message : String(err)}`,
|
|
2716
|
+
durationMs: Date.now() - start
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
async function checkWorktreeSanity(baseDir) {
|
|
2721
|
+
const start = Date.now();
|
|
2722
|
+
const check = "worktree-sanity";
|
|
2723
|
+
const description = "Worktree base directory is accessible and writable";
|
|
2724
|
+
try {
|
|
2725
|
+
await fs3.mkdir(baseDir, { recursive: true });
|
|
2726
|
+
const testFile = path4.join(baseDir, ".preflight-test");
|
|
2727
|
+
await fs3.writeFile(testFile, "test");
|
|
2728
|
+
await fs3.unlink(testFile);
|
|
2729
|
+
return {
|
|
2730
|
+
check,
|
|
2731
|
+
description,
|
|
2732
|
+
passed: true,
|
|
2733
|
+
durationMs: Date.now() - start
|
|
2734
|
+
};
|
|
2735
|
+
} catch (err) {
|
|
2736
|
+
return {
|
|
2737
|
+
check,
|
|
2738
|
+
description,
|
|
2739
|
+
passed: false,
|
|
2740
|
+
error: `Worktree base directory ${baseDir} is not writable: ${err instanceof Error ? err.message : String(err)}`,
|
|
2741
|
+
durationMs: Date.now() - start
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
async function registeredWorktreeCount(repoRoot) {
|
|
2746
|
+
try {
|
|
2747
|
+
const result = await execGit(["worktree", "list", "--porcelain"], { cwd: repoRoot });
|
|
2748
|
+
if (result.exitCode !== 0) return void 0;
|
|
2749
|
+
return result.stdout.split("\n").filter((line) => line.startsWith("worktree ")).length;
|
|
2750
|
+
} catch {
|
|
2751
|
+
return void 0;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
async function checkWorktreeHygiene(baseDir, repoRoot, hygieneConfig) {
|
|
2755
|
+
const start = Date.now();
|
|
2756
|
+
const check = "worktree-hygiene";
|
|
2757
|
+
const description = "Worktree count and age are within acceptable limits";
|
|
2758
|
+
const warnThreshold = hygieneConfig?.warnThreshold ?? 50;
|
|
2759
|
+
const blockThreshold = hygieneConfig?.blockThreshold ?? 100;
|
|
2760
|
+
const registeredWarnThreshold = hygieneConfig?.registeredWarnThreshold ?? warnThreshold;
|
|
2761
|
+
const staleAgeDays = hygieneConfig?.staleAgeDays ?? 7;
|
|
2762
|
+
const autoCleanup = hygieneConfig?.autoCleanup ?? false;
|
|
2763
|
+
const autoCleanupDryRun = hygieneConfig?.autoCleanupDryRun ?? false;
|
|
2764
|
+
try {
|
|
2765
|
+
const entries = await fs3.readdir(baseDir, { withFileTypes: true });
|
|
2766
|
+
const worktrees = entries.filter((e) => e.isDirectory());
|
|
2767
|
+
const totalCount = worktrees.length;
|
|
2768
|
+
const registeredCount = await registeredWorktreeCount(repoRoot);
|
|
2769
|
+
const staleAgeMs = staleAgeDays * 24 * 60 * 60 * 1e3;
|
|
2770
|
+
const now = Date.now();
|
|
2771
|
+
let staleCount = 0;
|
|
2772
|
+
for (const entry of worktrees) {
|
|
2773
|
+
try {
|
|
2774
|
+
const wtPath = path4.join(baseDir, entry.name);
|
|
2775
|
+
const stats = await fs3.stat(wtPath);
|
|
2776
|
+
const age = now - stats.mtimeMs;
|
|
2777
|
+
if (age > staleAgeMs) {
|
|
2778
|
+
staleCount++;
|
|
2779
|
+
}
|
|
2780
|
+
} catch {
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
let currentCount = totalCount;
|
|
2784
|
+
if (autoCleanup && staleCount > 0) {
|
|
2785
|
+
try {
|
|
2786
|
+
const cleanupManager = new WorktreeCleanupManager(baseDir);
|
|
2787
|
+
const report = await cleanupManager.cleanup({
|
|
2788
|
+
maxAgeMs: staleAgeMs,
|
|
2789
|
+
dryRun: autoCleanupDryRun
|
|
2790
|
+
});
|
|
2791
|
+
if (!autoCleanupDryRun && report.deleted > 0) {
|
|
2792
|
+
console.log(`Cleaned ${report.deleted} stale worktree(s) (older than ${staleAgeDays}d)`);
|
|
2793
|
+
const updatedEntries = await fs3.readdir(baseDir, { withFileTypes: true });
|
|
2794
|
+
const updatedWorktrees = updatedEntries.filter((e) => e.isDirectory());
|
|
2795
|
+
currentCount = updatedWorktrees.length;
|
|
2796
|
+
staleCount = 0;
|
|
2797
|
+
} else if (autoCleanupDryRun && report.deleted > 0) {
|
|
2798
|
+
console.log(
|
|
2799
|
+
`Would clean ${report.deleted} stale worktree(s) (older than ${staleAgeDays}d)`
|
|
2800
|
+
);
|
|
2801
|
+
}
|
|
2802
|
+
} catch (cleanupErr) {
|
|
2803
|
+
console.warn(
|
|
2804
|
+
`Auto-cleanup failed: ${cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)}`
|
|
2805
|
+
);
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
const metadata = {
|
|
2809
|
+
baseDirectoryCount: currentCount,
|
|
2810
|
+
...registeredCount === void 0 ? {} : { registeredWorktreeCount: registeredCount }
|
|
2811
|
+
};
|
|
2812
|
+
if (currentCount >= blockThreshold) {
|
|
2813
|
+
return {
|
|
2814
|
+
check,
|
|
2815
|
+
description,
|
|
2816
|
+
passed: false,
|
|
2817
|
+
error: `Worktree count (${currentCount}) exceeds block threshold (${blockThreshold}). Run build cleanup doctor or build cleanup worktrees --show-preserved to review preserved categories (dirty, uninspectable, active-run, lab/manual-only, nested-child, external, missing path, too-recent, branch-attached/protected/current) before any cleanup.`,
|
|
2818
|
+
metadata,
|
|
2819
|
+
durationMs: Date.now() - start
|
|
2820
|
+
};
|
|
2821
|
+
}
|
|
2822
|
+
const warnings = [];
|
|
2823
|
+
if (currentCount >= warnThreshold) {
|
|
2824
|
+
warnings.push(
|
|
2825
|
+
`Worktree count (${currentCount}) exceeds warning threshold (${warnThreshold}). Consider cleaning up old worktrees.`
|
|
2826
|
+
);
|
|
2827
|
+
}
|
|
2828
|
+
if (registeredCount !== void 0 && registeredCount >= registeredWarnThreshold) {
|
|
2829
|
+
warnings.push(
|
|
2830
|
+
`Worktrees registered to this repository (${registeredCount}, across all linked roots) exceed advisory threshold (${registeredWarnThreshold}). Review with build cleanup worktrees --show-preserved --verbose.`
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
if (!autoCleanup && staleCount > 0) {
|
|
2834
|
+
warnings.push(
|
|
2835
|
+
`Found ${staleCount} stale worktree(s) (older than ${staleAgeDays}d). Consider enabling --auto-cleanup.`
|
|
2836
|
+
);
|
|
2837
|
+
}
|
|
2838
|
+
if (warnings.length > 0) {
|
|
2839
|
+
return {
|
|
2840
|
+
check,
|
|
2841
|
+
description,
|
|
2842
|
+
passed: true,
|
|
2843
|
+
warning: warnings.join(" "),
|
|
2844
|
+
metadata,
|
|
2845
|
+
durationMs: Date.now() - start
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
return {
|
|
2849
|
+
check,
|
|
2850
|
+
description,
|
|
2851
|
+
passed: true,
|
|
2852
|
+
metadata,
|
|
2853
|
+
durationMs: Date.now() - start
|
|
2854
|
+
};
|
|
2855
|
+
} catch (err) {
|
|
2856
|
+
return {
|
|
2857
|
+
check,
|
|
2858
|
+
description,
|
|
2859
|
+
passed: false,
|
|
2860
|
+
error: `Worktree hygiene check failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2861
|
+
durationMs: Date.now() - start
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
async function checkWorktreeRecovery(hygieneConfig) {
|
|
2866
|
+
const start = Date.now();
|
|
2867
|
+
const check = "worktree-recovery";
|
|
2868
|
+
const description = "Recover orphaned worktrees from prior runs";
|
|
2869
|
+
try {
|
|
2870
|
+
const maxAgeMs = (hygieneConfig?.staleAgeDays ?? 7) * 24 * 60 * 60 * 1e3;
|
|
2871
|
+
const { recoverPendingWorktrees: recovery, enforceRetentionBounds: retention } = await runFlywheelWorktreeHygiene({
|
|
2872
|
+
dryRun: hygieneConfig?.recoveryDryRun ?? true,
|
|
2873
|
+
verbose: false,
|
|
2874
|
+
maxAgeMs,
|
|
2875
|
+
maxCount: 20
|
|
2876
|
+
});
|
|
2877
|
+
const totalRecovered = recovery.recovered.length + recovery.alreadyGone.length;
|
|
2878
|
+
const totalPruned = retention.pruned.length;
|
|
2879
|
+
const totalErrors = recovery.errors.length + retention.errors.length;
|
|
2880
|
+
const metadata = {
|
|
2881
|
+
dryRun: hygieneConfig?.recoveryDryRun ?? true,
|
|
2882
|
+
recovered: recovery.recovered,
|
|
2883
|
+
recoveredCount: recovery.recovered.length,
|
|
2884
|
+
alreadyGone: recovery.alreadyGone,
|
|
2885
|
+
alreadyGoneCount: recovery.alreadyGone.length,
|
|
2886
|
+
skippedActive: recovery.skippedActive,
|
|
2887
|
+
skippedActiveCount: recovery.skippedActive.length,
|
|
2888
|
+
skippedDirty: recovery.skippedDirty,
|
|
2889
|
+
skippedDirtyCount: recovery.skippedDirty.length,
|
|
2890
|
+
pruned: retention.pruned,
|
|
2891
|
+
prunedCount: retention.pruned.length,
|
|
2892
|
+
errors: [...recovery.errors, ...retention.errors],
|
|
2893
|
+
errorCount: totalErrors,
|
|
2894
|
+
totalRecovered,
|
|
2895
|
+
totalPruned
|
|
2896
|
+
};
|
|
2897
|
+
let warning;
|
|
2898
|
+
if (totalRecovered > 0 || totalPruned > 0 || totalErrors > 0 || recovery.skippedActive.length > 0 || recovery.skippedDirty.length > 0) {
|
|
2899
|
+
const parts = [];
|
|
2900
|
+
if (recovery.recovered.length > 0) {
|
|
2901
|
+
parts.push(`recovered ${recovery.recovered.length} orphaned worktree(s)`);
|
|
2902
|
+
}
|
|
2903
|
+
if (recovery.alreadyGone.length > 0) {
|
|
2904
|
+
parts.push(`reconciled ${recovery.alreadyGone.length} already-removed record(s)`);
|
|
2905
|
+
}
|
|
2906
|
+
if (totalPruned > 0) {
|
|
2907
|
+
parts.push(`pruned ${totalPruned} over-retained worktree(s)`);
|
|
2908
|
+
}
|
|
2909
|
+
if (recovery.skippedActive.length > 0) {
|
|
2910
|
+
parts.push(`preserved ${recovery.skippedActive.length} active worktree(s)`);
|
|
2911
|
+
}
|
|
2912
|
+
if (recovery.skippedDirty.length > 0) {
|
|
2913
|
+
parts.push(`skipped ${recovery.skippedDirty.length} dirty worktree(s)`);
|
|
2914
|
+
}
|
|
2915
|
+
if (totalErrors > 0) {
|
|
2916
|
+
const sampleError = recovery.errors[0]?.error ?? retention.errors[0]?.error ?? "see logs for details";
|
|
2917
|
+
const trimmedSample = sampleError.length > 200 ? `${sampleError.slice(0, 197)}...` : sampleError;
|
|
2918
|
+
parts.push(`encountered ${totalErrors} error(s) during cleanup (first: ${trimmedSample})`);
|
|
2919
|
+
}
|
|
2920
|
+
warning = `Worktree recovery: ${parts.join("; ")}`;
|
|
2921
|
+
}
|
|
2922
|
+
return {
|
|
2923
|
+
check,
|
|
2924
|
+
description,
|
|
2925
|
+
passed: true,
|
|
2926
|
+
warning,
|
|
2927
|
+
metadata,
|
|
2928
|
+
durationMs: Date.now() - start
|
|
2929
|
+
};
|
|
2930
|
+
} catch (err) {
|
|
2931
|
+
return {
|
|
2932
|
+
check,
|
|
2933
|
+
description,
|
|
2934
|
+
passed: true,
|
|
2935
|
+
// Don't block execution on recovery failure
|
|
2936
|
+
warning: `Worktree recovery failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2937
|
+
durationMs: Date.now() - start
|
|
2938
|
+
};
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
async function checkProviderAuth(workflow, cwd, authProbeEnv) {
|
|
2942
|
+
const start = Date.now();
|
|
2943
|
+
const check = "provider-auth";
|
|
2944
|
+
const description = "All providers are authenticated and available";
|
|
2945
|
+
try {
|
|
2946
|
+
const providers = /* @__PURE__ */ new Set();
|
|
2947
|
+
const implicitDefaultProviderStages = [];
|
|
2948
|
+
const defaultProvider = resolveDefaultProvider(void 0, cwd).value;
|
|
2949
|
+
for (const stage of workflow.stages) {
|
|
2950
|
+
const hasCompetitionVariants = Array.isArray(stage.competition?.variants) && stage.competition.variants.length > 0;
|
|
2951
|
+
if (stage.provider) {
|
|
2952
|
+
providers.add(stage.provider);
|
|
2953
|
+
} else if (stage.type !== "verify" && !hasCompetitionVariants) {
|
|
2954
|
+
providers.add(defaultProvider);
|
|
2955
|
+
implicitDefaultProviderStages.push(stage.id);
|
|
2956
|
+
}
|
|
2957
|
+
if (stage.orchestrator?.orchestrationProvider) {
|
|
2958
|
+
providers.add(stage.orchestrator.orchestrationProvider);
|
|
2959
|
+
}
|
|
2960
|
+
if (stage.competition?.variants) {
|
|
2961
|
+
for (const variant of stage.competition.variants) {
|
|
2962
|
+
providers.add(variant.provider);
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
if (providers.size === 0) {
|
|
2967
|
+
return {
|
|
2968
|
+
check,
|
|
2969
|
+
description,
|
|
2970
|
+
passed: true,
|
|
2971
|
+
metadata: {
|
|
2972
|
+
info: "No provider-backed stages found"
|
|
2973
|
+
},
|
|
2974
|
+
durationMs: Date.now() - start
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2977
|
+
const errors = [];
|
|
2978
|
+
const warnings = [];
|
|
2979
|
+
for (const providerId of providers) {
|
|
2980
|
+
try {
|
|
2981
|
+
const isEnabled = ProviderRegistry.isProviderEnabled(providerId);
|
|
2982
|
+
if (!isEnabled) {
|
|
2983
|
+
errors.push(`Provider '${providerId}' is not enabled`);
|
|
2984
|
+
continue;
|
|
2985
|
+
}
|
|
2986
|
+
if (isCodexProviderInsideCodexSandbox(providerId)) {
|
|
2987
|
+
errors.push(
|
|
2988
|
+
"Provider 'codex' cannot run from inside Codex's outer sandbox; run Codex-provider stages from a normal terminal or choose a different provider."
|
|
2989
|
+
);
|
|
2990
|
+
continue;
|
|
2991
|
+
}
|
|
2992
|
+
const providerConfig = ProviderRegistry.getProviderConfig(providerId);
|
|
2993
|
+
if (providerConfig?.experimental) {
|
|
2994
|
+
warnings.push(
|
|
2995
|
+
`Provider '${providerId}' is experimental; startup behavior may be less stable.`
|
|
2996
|
+
);
|
|
2997
|
+
}
|
|
2998
|
+
const health = getProviderHealth(providerId);
|
|
2999
|
+
if (health.lastFailurePhase && health.consecutiveFailures > 0) {
|
|
3000
|
+
warnings.push(
|
|
3001
|
+
`Provider '${providerId}' has recorded ${health.lastFailurePhase} failure history (health ${health.score.toFixed(2)}); watch for recurrence.`
|
|
3002
|
+
);
|
|
3003
|
+
}
|
|
3004
|
+
for (const stage of workflow.stages) {
|
|
3005
|
+
const pinnedModels = [];
|
|
3006
|
+
if (stage.provider === providerId) {
|
|
3007
|
+
pinnedModels.push({ provider: providerId, model: stage.model, stageId: stage.id });
|
|
3008
|
+
}
|
|
3009
|
+
if (stage.orchestrator?.orchestrationProvider === providerId) {
|
|
3010
|
+
pinnedModels.push({
|
|
3011
|
+
provider: providerId,
|
|
3012
|
+
model: stage.orchestrator.orchestrationModel,
|
|
3013
|
+
stageId: stage.id
|
|
3014
|
+
});
|
|
3015
|
+
}
|
|
3016
|
+
if (stage.competition?.variants) {
|
|
3017
|
+
for (const variant of stage.competition.variants) {
|
|
3018
|
+
if (variant.provider === providerId) {
|
|
3019
|
+
pinnedModels.push({
|
|
3020
|
+
provider: providerId,
|
|
3021
|
+
model: variant.model,
|
|
3022
|
+
stageId: stage.id
|
|
3023
|
+
});
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
for (const pinned of pinnedModels) {
|
|
3028
|
+
try {
|
|
3029
|
+
ProviderRegistry.resolveModel(pinned.provider, pinned.model);
|
|
3030
|
+
} catch (err) {
|
|
3031
|
+
errors.push(
|
|
3032
|
+
`Stage '${pinned.stageId}' pins invalid model for provider '${pinned.provider}': ` + (err instanceof Error ? err.message : String(err))
|
|
3033
|
+
);
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
const readiness = await getProviderReadiness(providerId, {
|
|
3038
|
+
mode: "execute",
|
|
3039
|
+
authProbeMode: "passive",
|
|
3040
|
+
...authProbeEnv ? { authProbeEnv } : {}
|
|
3041
|
+
});
|
|
3042
|
+
if (readiness.known === false || readiness.configured === false) {
|
|
3043
|
+
errors.push(
|
|
3044
|
+
`Provider '${providerId}' is unavailable: ${readiness.reason ?? "provider is not configured for CLI execution"}`
|
|
3045
|
+
);
|
|
3046
|
+
} else if (!readiness.installed) {
|
|
3047
|
+
errors.push(
|
|
3048
|
+
`Provider '${providerId}' is unavailable: ${readiness.reason ?? "CLI is not installed"}`
|
|
3049
|
+
);
|
|
3050
|
+
} else if (!readiness.cliHealthy) {
|
|
3051
|
+
errors.push(
|
|
3052
|
+
`Provider '${providerId}' is unavailable: ${readiness.reason ?? "CLI health check failed"}`
|
|
3053
|
+
);
|
|
3054
|
+
} else if (readiness.sandboxCapable === false) {
|
|
3055
|
+
errors.push(
|
|
3056
|
+
`Provider '${providerId}' is unavailable: ${readiness.sandboxReason ?? readiness.reason ?? "sandbox capability check failed"}`
|
|
3057
|
+
);
|
|
3058
|
+
} else if (readiness.authenticated === "no") {
|
|
3059
|
+
errors.push(
|
|
3060
|
+
`Provider '${providerId}' has its CLI but authentication is missing.` + (readiness.authCommand ? ` ${readiness.authCommand}` : "")
|
|
3061
|
+
);
|
|
3062
|
+
} else if (readiness.authenticated === "unknown") {
|
|
3063
|
+
warnings.push(
|
|
3064
|
+
`Provider '${providerId}' auth status is unreported by passive preflight; runtime will defer to the provider CLI.`
|
|
3065
|
+
);
|
|
3066
|
+
}
|
|
3067
|
+
} catch (err) {
|
|
3068
|
+
warnings.push(
|
|
3069
|
+
`Could not verify provider '${providerId}': ${err instanceof Error ? err.message : String(err)}`
|
|
3070
|
+
);
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
if (errors.length > 0) {
|
|
3074
|
+
return {
|
|
3075
|
+
check,
|
|
3076
|
+
description,
|
|
3077
|
+
passed: false,
|
|
3078
|
+
error: errors.join("\n"),
|
|
3079
|
+
durationMs: Date.now() - start
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
return {
|
|
3083
|
+
check,
|
|
3084
|
+
description,
|
|
3085
|
+
passed: true,
|
|
3086
|
+
warning: warnings.length > 0 ? warnings.join("\n") : void 0,
|
|
3087
|
+
metadata: implicitDefaultProviderStages.length > 0 ? { implicitDefaultProvider: defaultProvider, implicitDefaultProviderStages } : void 0,
|
|
3088
|
+
durationMs: Date.now() - start
|
|
3089
|
+
};
|
|
3090
|
+
} catch (err) {
|
|
3091
|
+
return {
|
|
3092
|
+
check,
|
|
3093
|
+
description,
|
|
3094
|
+
passed: false,
|
|
3095
|
+
error: `Provider auth check failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
3096
|
+
durationMs: Date.now() - start
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
async function checkArtifactRefs(workflow) {
|
|
3101
|
+
const start = Date.now();
|
|
3102
|
+
const check = "artifact-refs";
|
|
3103
|
+
const description = "All artifact references are valid (DAG validation)";
|
|
3104
|
+
try {
|
|
3105
|
+
const errors = [];
|
|
3106
|
+
const stageOutputs = /* @__PURE__ */ new Map();
|
|
3107
|
+
for (const stage of workflow.stages) {
|
|
3108
|
+
const outputs = /* @__PURE__ */ new Set();
|
|
3109
|
+
if (stage.outputs) {
|
|
3110
|
+
for (const outputName of Object.keys(stage.outputs)) {
|
|
3111
|
+
outputs.add(outputName);
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
stageOutputs.set(stage.id, outputs);
|
|
3115
|
+
}
|
|
3116
|
+
const stageOrder = /* @__PURE__ */ new Map();
|
|
3117
|
+
for (let i = 0; i < workflow.stages.length; i++) {
|
|
3118
|
+
const stage = workflow.stages[i];
|
|
3119
|
+
if (stage) {
|
|
3120
|
+
stageOrder.set(stage.id, i);
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
const stageMap = new Map(workflow.stages.map((stage) => [stage.id, stage]));
|
|
3124
|
+
const closureCache = /* @__PURE__ */ new Map();
|
|
3125
|
+
const getDependencyClosure = (stageId) => {
|
|
3126
|
+
const cached = closureCache.get(stageId);
|
|
3127
|
+
if (cached) return cached;
|
|
3128
|
+
const closure = /* @__PURE__ */ new Set();
|
|
3129
|
+
const visit = (depId) => {
|
|
3130
|
+
if (closure.has(depId)) return;
|
|
3131
|
+
closure.add(depId);
|
|
3132
|
+
for (const transitiveDep of stageMap.get(depId)?.deps ?? []) {
|
|
3133
|
+
visit(transitiveDep);
|
|
3134
|
+
}
|
|
3135
|
+
};
|
|
3136
|
+
for (const dep of stageMap.get(stageId)?.deps ?? []) {
|
|
3137
|
+
visit(dep);
|
|
3138
|
+
}
|
|
3139
|
+
closureCache.set(stageId, closure);
|
|
3140
|
+
return closure;
|
|
3141
|
+
};
|
|
3142
|
+
for (const stage of workflow.stages) {
|
|
3143
|
+
if (!stage.inputs || stage.inputs.length === 0) continue;
|
|
3144
|
+
const stageIndex = stageOrder.get(stage.id) ?? 0;
|
|
3145
|
+
for (const ref of stage.inputs) {
|
|
3146
|
+
try {
|
|
3147
|
+
const { stageId, outputName } = parseArtifactRef(ref);
|
|
3148
|
+
if (!stageOutputs.has(stageId)) {
|
|
3149
|
+
errors.push(
|
|
3150
|
+
`Stage '${stage.id}' references artifact '${ref}' from non-existent stage '${stageId}'`
|
|
3151
|
+
);
|
|
3152
|
+
continue;
|
|
3153
|
+
}
|
|
3154
|
+
const outputs = stageOutputs.get(stageId);
|
|
3155
|
+
if (!outputs) {
|
|
3156
|
+
errors.push(
|
|
3157
|
+
`Stage '${stage.id}' references artifact '${ref}' from stage '${stageId}', but that stage has no recorded outputs`
|
|
3158
|
+
);
|
|
3159
|
+
continue;
|
|
3160
|
+
}
|
|
3161
|
+
if (!outputs.has(outputName)) {
|
|
3162
|
+
const available = outputs.size > 0 ? Array.from(outputs).join(", ") : "none";
|
|
3163
|
+
errors.push(
|
|
3164
|
+
`Stage '${stage.id}' references output '${outputName}' which doesn't exist in stage '${stageId}'. Available: [${available}]`
|
|
3165
|
+
);
|
|
3166
|
+
continue;
|
|
3167
|
+
}
|
|
3168
|
+
const sourceIndex = stageOrder.get(stageId) ?? 0;
|
|
3169
|
+
if (sourceIndex >= stageIndex) {
|
|
3170
|
+
errors.push(
|
|
3171
|
+
`Stage '${stage.id}' has forward reference to '${ref}' (source stage '${stageId}' must come before this stage)`
|
|
3172
|
+
);
|
|
3173
|
+
continue;
|
|
3174
|
+
}
|
|
3175
|
+
if (!getDependencyClosure(stage.id).has(stageId)) {
|
|
3176
|
+
errors.push(
|
|
3177
|
+
`Stage '${stage.id}' references artifact '${ref}' but does not depend on producer stage '${stageId}'`
|
|
3178
|
+
);
|
|
3179
|
+
}
|
|
3180
|
+
} catch (err) {
|
|
3181
|
+
errors.push(
|
|
3182
|
+
`Stage '${stage.id}' has invalid artifact reference '${ref}': ${err instanceof Error ? err.message : String(err)}`
|
|
3183
|
+
);
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
if (errors.length > 0) {
|
|
3188
|
+
return {
|
|
3189
|
+
check,
|
|
3190
|
+
description,
|
|
3191
|
+
passed: false,
|
|
3192
|
+
error: errors.join("\n"),
|
|
3193
|
+
durationMs: Date.now() - start
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
return {
|
|
3197
|
+
check,
|
|
3198
|
+
description,
|
|
3199
|
+
passed: true,
|
|
3200
|
+
durationMs: Date.now() - start
|
|
3201
|
+
};
|
|
3202
|
+
} catch (err) {
|
|
3203
|
+
return {
|
|
3204
|
+
check,
|
|
3205
|
+
description,
|
|
3206
|
+
passed: false,
|
|
3207
|
+
error: `Artifact reference validation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
3208
|
+
durationMs: Date.now() - start
|
|
3209
|
+
};
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
async function checkConfig(workflow) {
|
|
3213
|
+
const start = Date.now();
|
|
3214
|
+
const check = "config-validation";
|
|
3215
|
+
const description = "Workflow configuration is valid";
|
|
3216
|
+
try {
|
|
3217
|
+
const result = validateWorkflowConfig(workflow);
|
|
3218
|
+
if (!result.valid) {
|
|
3219
|
+
return {
|
|
3220
|
+
check,
|
|
3221
|
+
description,
|
|
3222
|
+
passed: false,
|
|
3223
|
+
error: result.error || "Configuration validation failed",
|
|
3224
|
+
durationMs: Date.now() - start
|
|
3225
|
+
};
|
|
3226
|
+
}
|
|
3227
|
+
return {
|
|
3228
|
+
check,
|
|
3229
|
+
description,
|
|
3230
|
+
passed: true,
|
|
3231
|
+
warning: result.warnings?.join("\n"),
|
|
3232
|
+
durationMs: Date.now() - start
|
|
3233
|
+
};
|
|
3234
|
+
} catch (err) {
|
|
3235
|
+
return {
|
|
3236
|
+
check,
|
|
3237
|
+
description,
|
|
3238
|
+
passed: false,
|
|
3239
|
+
error: `Configuration validation error: ${err instanceof Error ? err.message : String(err)}`,
|
|
3240
|
+
durationMs: Date.now() - start
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
async function checkContextUtilization(workflow, cwd) {
|
|
3245
|
+
const start = Date.now();
|
|
3246
|
+
const check = "context-utilization";
|
|
3247
|
+
const description = "Prompt context fits within context window limits";
|
|
3248
|
+
try {
|
|
3249
|
+
const estimator = new TokenEstimator();
|
|
3250
|
+
const warnings = [];
|
|
3251
|
+
const WARN_THRESHOLD = 0.65;
|
|
3252
|
+
const defaultProvider = resolveDefaultProvider(void 0, cwd).value;
|
|
3253
|
+
for (const stage of workflow.stages) {
|
|
3254
|
+
if (!stage.prompt) continue;
|
|
3255
|
+
const provider = stage.provider || defaultProvider;
|
|
3256
|
+
const model = stage.model || applyFlywheelModelDefaults(stage, provider);
|
|
3257
|
+
if (!model) continue;
|
|
3258
|
+
const decision = await estimator.analyzeTask(stage.prompt, provider, model);
|
|
3259
|
+
if (decision.utilizationPercent / 100 >= WARN_THRESHOLD) {
|
|
3260
|
+
const utilizationPct = decision.utilizationPercent.toFixed(1);
|
|
3261
|
+
const adjustedPct = decision.adjustedUtilizationPercent.toFixed(1);
|
|
3262
|
+
warnings.push(
|
|
3263
|
+
`Stage '${stage.id}': Prompt context utilization is ${utilizationPct}% (${adjustedPct}% with overhead) of ${decision.contextWindow.toLocaleString()} token context window. Estimated: ${decision.estimatedTokens.toLocaleString()} tokens. Consider splitting task or reducing context to improve reliability.`
|
|
3264
|
+
);
|
|
3265
|
+
}
|
|
3266
|
+
if (stage.competition?.variants && stage.prompt) {
|
|
3267
|
+
for (const variant of stage.competition.variants) {
|
|
3268
|
+
const variantProvider = variant.provider || provider;
|
|
3269
|
+
const variantModel = variant.model || applyFlywheelModelDefaults(stage, variantProvider);
|
|
3270
|
+
if (!variantModel) continue;
|
|
3271
|
+
if (variantProvider === provider && variantModel === model) continue;
|
|
3272
|
+
const variantDecision = await estimator.analyzeTask(
|
|
3273
|
+
stage.prompt,
|
|
3274
|
+
variantProvider,
|
|
3275
|
+
variantModel
|
|
3276
|
+
);
|
|
3277
|
+
if (variantDecision.utilizationPercent / 100 >= WARN_THRESHOLD) {
|
|
3278
|
+
const utilizationPct = variantDecision.utilizationPercent.toFixed(1);
|
|
3279
|
+
const adjustedPct = variantDecision.adjustedUtilizationPercent.toFixed(1);
|
|
3280
|
+
warnings.push(
|
|
3281
|
+
`Stage '${stage.id}' variant '${[variant.provider, variant.model].filter(Boolean).join(":")}': Prompt context utilization is ${utilizationPct}% (${adjustedPct}% with overhead) of ${variantDecision.contextWindow.toLocaleString()} token context window. Estimated: ${variantDecision.estimatedTokens.toLocaleString()} tokens. Consider splitting task or reducing context to improve reliability.`
|
|
3282
|
+
);
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
estimator.cleanup();
|
|
3288
|
+
return {
|
|
3289
|
+
check,
|
|
3290
|
+
description,
|
|
3291
|
+
passed: true,
|
|
3292
|
+
warning: warnings.length > 0 ? warnings.join("\n") : void 0,
|
|
3293
|
+
durationMs: Date.now() - start
|
|
3294
|
+
};
|
|
3295
|
+
} catch (err) {
|
|
3296
|
+
return {
|
|
3297
|
+
check,
|
|
3298
|
+
description,
|
|
3299
|
+
passed: true,
|
|
3300
|
+
warning: `Context utilization check failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
3301
|
+
durationMs: Date.now() - start
|
|
3302
|
+
};
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
function getStringProp(obj, key) {
|
|
3306
|
+
if (typeof obj === "object" && obj !== null) {
|
|
3307
|
+
const val = obj[key];
|
|
3308
|
+
return typeof val === "string" ? val : void 0;
|
|
3309
|
+
}
|
|
3310
|
+
return void 0;
|
|
3311
|
+
}
|
|
3312
|
+
function extractFileRefs(prompt) {
|
|
3313
|
+
return parseFileDirectiveLines(prompt).map((line) => line.path);
|
|
3314
|
+
}
|
|
3315
|
+
function extractCreateAnnotatedFileRefs(prompt) {
|
|
3316
|
+
return new Set(extractCreateAnnotatedFilePaths(prompt));
|
|
3317
|
+
}
|
|
3318
|
+
async function checkTaskSpecFreshness(workflow, cwd) {
|
|
3319
|
+
const start = Date.now();
|
|
3320
|
+
const check = "task-spec-freshness";
|
|
3321
|
+
const description = "Task spec files exist and git refs are valid";
|
|
3322
|
+
const errors = [];
|
|
3323
|
+
const warnings = [];
|
|
3324
|
+
try {
|
|
3325
|
+
const displayPath = (filePath) => path4.relative(cwd, filePath).replace(/\\/g, "/");
|
|
3326
|
+
for (const stage of workflow.stages) {
|
|
3327
|
+
const stageUnknown = stage;
|
|
3328
|
+
const taskId = getStringProp(stageUnknown, "taskId");
|
|
3329
|
+
if (taskId) {
|
|
3330
|
+
const taskBucketPath = getBucketPathByKind("task", cwd);
|
|
3331
|
+
const taskFilePath = path4.join(taskBucketPath, `${taskId}.md`);
|
|
3332
|
+
try {
|
|
3333
|
+
await fs3.access(taskFilePath);
|
|
3334
|
+
} catch {
|
|
3335
|
+
errors.push(`Stage '${stage.id}': task file not found: ${displayPath(taskFilePath)}`);
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
const backlogItemId = getStringProp(stageUnknown, "backlogItemId");
|
|
3339
|
+
if (backlogItemId) {
|
|
3340
|
+
const backlogBucketPath = getBucketPathByKind("backlog", cwd);
|
|
3341
|
+
const backlogFilePath = path4.join(backlogBucketPath, `${backlogItemId}.md`);
|
|
3342
|
+
try {
|
|
3343
|
+
await fs3.access(backlogFilePath);
|
|
3344
|
+
} catch {
|
|
3345
|
+
errors.push(
|
|
3346
|
+
`Stage '${stage.id}': backlog file not found: ${displayPath(backlogFilePath)}`
|
|
3347
|
+
);
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
if (stage.prompt) {
|
|
3351
|
+
const fileRefs = extractFileRefs(stage.prompt);
|
|
3352
|
+
const createAnnotatedRefs = extractCreateAnnotatedFileRefs(stage.prompt);
|
|
3353
|
+
for (const fileRef of fileRefs) {
|
|
3354
|
+
const filePath = path4.isAbsolute(fileRef) ? fileRef : path4.join(cwd, fileRef);
|
|
3355
|
+
try {
|
|
3356
|
+
await fs3.access(filePath);
|
|
3357
|
+
} catch {
|
|
3358
|
+
if (!createAnnotatedRefs.has(fileRef)) {
|
|
3359
|
+
errors.push(`Stage '${stage.id}': FILE reference not found on disk: ${fileRef}`);
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
const baseRef = getStringProp(stageUnknown, "baseRef");
|
|
3365
|
+
if (baseRef) {
|
|
3366
|
+
try {
|
|
3367
|
+
await execa2("git", ["rev-parse", "--verify", baseRef], {
|
|
3368
|
+
cwd,
|
|
3369
|
+
stdio: "pipe",
|
|
3370
|
+
timeout: 1e4
|
|
3371
|
+
});
|
|
3372
|
+
} catch {
|
|
3373
|
+
errors.push(`Stage '${stage.id}': baseRef '${baseRef}' does not exist in git repository`);
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
const baseSha = getStringProp(stageUnknown, "baseSha");
|
|
3377
|
+
if (baseSha) {
|
|
3378
|
+
try {
|
|
3379
|
+
await execa2("git", ["rev-parse", "--verify", baseSha], {
|
|
3380
|
+
cwd,
|
|
3381
|
+
stdio: "pipe",
|
|
3382
|
+
timeout: 1e4
|
|
3383
|
+
});
|
|
3384
|
+
try {
|
|
3385
|
+
await execa2("git", ["merge-base", "--is-ancestor", baseSha, "HEAD"], {
|
|
3386
|
+
cwd,
|
|
3387
|
+
stdio: "pipe",
|
|
3388
|
+
timeout: 1e4
|
|
3389
|
+
});
|
|
3390
|
+
} catch {
|
|
3391
|
+
warnings.push(
|
|
3392
|
+
`Stage '${stage.id}': baseSha '${baseSha}' exists but is not an ancestor of HEAD \u2014 may reference a rebased or force-pushed commit`
|
|
3393
|
+
);
|
|
3394
|
+
}
|
|
3395
|
+
} catch {
|
|
3396
|
+
errors.push(
|
|
3397
|
+
`Stage '${stage.id}': baseSha '${baseSha}' does not exist in git repository (SHA unreachable \u2014 commit may have been pruned or the ref was never pushed)`
|
|
3398
|
+
);
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
if (errors.length > 0) {
|
|
3403
|
+
return {
|
|
3404
|
+
check,
|
|
3405
|
+
description,
|
|
3406
|
+
passed: false,
|
|
3407
|
+
error: errors.join("\n"),
|
|
3408
|
+
warning: warnings.length > 0 ? warnings.join("\n") : void 0,
|
|
3409
|
+
durationMs: Date.now() - start
|
|
3410
|
+
};
|
|
3411
|
+
}
|
|
3412
|
+
return {
|
|
3413
|
+
check,
|
|
3414
|
+
description,
|
|
3415
|
+
passed: true,
|
|
3416
|
+
warning: warnings.length > 0 ? warnings.join("\n") : void 0,
|
|
3417
|
+
durationMs: Date.now() - start
|
|
3418
|
+
};
|
|
3419
|
+
} catch (err) {
|
|
3420
|
+
return {
|
|
3421
|
+
check,
|
|
3422
|
+
description,
|
|
3423
|
+
passed: false,
|
|
3424
|
+
error: `Task spec freshness check failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
3425
|
+
durationMs: Date.now() - start
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
async function collectBuildInputFiles(dir) {
|
|
3430
|
+
let entries;
|
|
3431
|
+
try {
|
|
3432
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
3433
|
+
} catch (error) {
|
|
3434
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
3435
|
+
return [];
|
|
3436
|
+
}
|
|
3437
|
+
throw error;
|
|
3438
|
+
}
|
|
3439
|
+
const files = [];
|
|
3440
|
+
for (const entry of entries) {
|
|
3441
|
+
const fullPath = path4.join(dir, entry.name);
|
|
3442
|
+
if (entry.isDirectory()) {
|
|
3443
|
+
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") {
|
|
3444
|
+
continue;
|
|
3445
|
+
}
|
|
3446
|
+
files.push(...await collectBuildInputFiles(fullPath));
|
|
3447
|
+
continue;
|
|
3448
|
+
}
|
|
3449
|
+
if (!entry.isFile()) continue;
|
|
3450
|
+
if (!entry.name.endsWith(".ts") && !entry.name.endsWith(".tsx")) continue;
|
|
3451
|
+
if (entry.name.endsWith(".test.ts") || entry.name.endsWith(".spec.ts")) continue;
|
|
3452
|
+
files.push(fullPath);
|
|
3453
|
+
}
|
|
3454
|
+
return files;
|
|
3455
|
+
}
|
|
3456
|
+
async function collectGeneratedProviderFiles(dir) {
|
|
3457
|
+
let entries;
|
|
3458
|
+
try {
|
|
3459
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
3460
|
+
} catch (error) {
|
|
3461
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
3462
|
+
return [];
|
|
3463
|
+
}
|
|
3464
|
+
throw error;
|
|
3465
|
+
}
|
|
3466
|
+
const files = [];
|
|
3467
|
+
for (const entry of entries) {
|
|
3468
|
+
const fullPath = path4.join(dir, entry.name);
|
|
3469
|
+
if (entry.isDirectory()) {
|
|
3470
|
+
files.push(...await collectGeneratedProviderFiles(fullPath));
|
|
3471
|
+
continue;
|
|
3472
|
+
}
|
|
3473
|
+
if (!entry.isFile()) continue;
|
|
3474
|
+
if (entry.name.endsWith(".lock")) continue;
|
|
3475
|
+
if (!entry.name.endsWith(".json")) continue;
|
|
3476
|
+
files.push(fullPath);
|
|
3477
|
+
}
|
|
3478
|
+
return files;
|
|
3479
|
+
}
|
|
3480
|
+
var BUILD_INPUT_PATHS = [
|
|
3481
|
+
"src",
|
|
3482
|
+
"package.json",
|
|
3483
|
+
"tsconfig.json",
|
|
3484
|
+
"tsup.config.ts",
|
|
3485
|
+
".build-project/config/providers.yaml",
|
|
3486
|
+
"providers"
|
|
3487
|
+
];
|
|
3488
|
+
function parseLineOutput(stdout) {
|
|
3489
|
+
return stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
3490
|
+
}
|
|
3491
|
+
async function getDirtyBuildInputPaths(projectRoot) {
|
|
3492
|
+
try {
|
|
3493
|
+
const tracked = await execa2(
|
|
3494
|
+
"git",
|
|
3495
|
+
["diff", "--name-only", "HEAD", "--", ...BUILD_INPUT_PATHS],
|
|
3496
|
+
{
|
|
3497
|
+
cwd: projectRoot,
|
|
3498
|
+
stdio: "pipe",
|
|
3499
|
+
reject: false
|
|
3500
|
+
}
|
|
3501
|
+
);
|
|
3502
|
+
const untracked = await execa2(
|
|
3503
|
+
"git",
|
|
3504
|
+
["ls-files", "--others", "--exclude-standard", "--", ...BUILD_INPUT_PATHS],
|
|
3505
|
+
{
|
|
3506
|
+
cwd: projectRoot,
|
|
3507
|
+
stdio: "pipe",
|
|
3508
|
+
reject: false
|
|
3509
|
+
}
|
|
3510
|
+
);
|
|
3511
|
+
if (tracked.exitCode !== 0 || untracked.exitCode !== 0) {
|
|
3512
|
+
return null;
|
|
3513
|
+
}
|
|
3514
|
+
return new Set(
|
|
3515
|
+
[...parseLineOutput(tracked.stdout), ...parseLineOutput(untracked.stdout)].map(
|
|
3516
|
+
(relativePath) => path4.join(projectRoot, relativePath)
|
|
3517
|
+
)
|
|
3518
|
+
);
|
|
3519
|
+
} catch {
|
|
3520
|
+
return null;
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
async function hasSourceBuildInputs(projectRoot) {
|
|
3524
|
+
try {
|
|
3525
|
+
await fs3.stat(path4.join(projectRoot, "src"));
|
|
3526
|
+
await fs3.stat(path4.join(projectRoot, "tsup.config.ts"));
|
|
3527
|
+
return true;
|
|
3528
|
+
} catch (error) {
|
|
3529
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
3530
|
+
return false;
|
|
3531
|
+
}
|
|
3532
|
+
throw error;
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
async function getLatestCommittedBuildInput(projectRoot) {
|
|
3536
|
+
try {
|
|
3537
|
+
const latestCommit = await execa2(
|
|
3538
|
+
"git",
|
|
3539
|
+
["log", "-1", "--format=%ct%x00%H", "--", ...BUILD_INPUT_PATHS],
|
|
3540
|
+
{
|
|
3541
|
+
cwd: projectRoot,
|
|
3542
|
+
stdio: "pipe",
|
|
3543
|
+
reject: false
|
|
3544
|
+
}
|
|
3545
|
+
);
|
|
3546
|
+
if (latestCommit.exitCode !== 0 || !latestCommit.stdout) {
|
|
3547
|
+
return null;
|
|
3548
|
+
}
|
|
3549
|
+
const [timestampRaw, commitSha] = latestCommit.stdout.split("\0");
|
|
3550
|
+
const committedAtMs = Number.parseInt(timestampRaw ?? "", 10) * 1e3;
|
|
3551
|
+
if (!Number.isFinite(committedAtMs) || !commitSha) {
|
|
3552
|
+
return null;
|
|
3553
|
+
}
|
|
3554
|
+
const latestPaths = await execa2(
|
|
3555
|
+
"git",
|
|
3556
|
+
["diff-tree", "--no-commit-id", "--name-only", "-r", commitSha, "--", ...BUILD_INPUT_PATHS],
|
|
3557
|
+
{
|
|
3558
|
+
cwd: projectRoot,
|
|
3559
|
+
stdio: "pipe",
|
|
3560
|
+
reject: false
|
|
3561
|
+
}
|
|
3562
|
+
);
|
|
3563
|
+
const latestPath = latestPaths.exitCode === 0 ? parseLineOutput(latestPaths.stdout)[0] : void 0;
|
|
3564
|
+
return {
|
|
3565
|
+
path: latestPath,
|
|
3566
|
+
committedAtMs
|
|
3567
|
+
};
|
|
3568
|
+
} catch {
|
|
3569
|
+
return null;
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
function resolveBuildPackageRoot() {
|
|
3573
|
+
const configuredRoot = process.env[PACKAGE_ROOT_ENV]?.trim();
|
|
3574
|
+
if (configuredRoot) return path4.resolve(configuredRoot);
|
|
3575
|
+
let current = path4.dirname(fileURLToPath(import.meta.url));
|
|
3576
|
+
let distOnlyPackageRoot;
|
|
3577
|
+
while (true) {
|
|
3578
|
+
if (path4.basename(current) === "dist" && existsSync2(path4.join(current, "entry.js"))) {
|
|
3579
|
+
distOnlyPackageRoot ??= path4.dirname(current);
|
|
3580
|
+
}
|
|
3581
|
+
const packageJsonPath = path4.join(current, "package.json");
|
|
3582
|
+
if (existsSync2(packageJsonPath)) {
|
|
3583
|
+
try {
|
|
3584
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
3585
|
+
if (packageJson.name === "@ebrindley/build") {
|
|
3586
|
+
return current;
|
|
3587
|
+
}
|
|
3588
|
+
} catch {
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
const parent = path4.dirname(current);
|
|
3592
|
+
if (parent === current) {
|
|
3593
|
+
return distOnlyPackageRoot ?? getProjectRoot();
|
|
3594
|
+
}
|
|
3595
|
+
current = parent;
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
async function checkBuildFreshness(projectRoot = resolveBuildPackageRoot()) {
|
|
3599
|
+
const start = Date.now();
|
|
3600
|
+
const check = "build-freshness";
|
|
3601
|
+
const description = "Built CLI artifacts are up-to-date";
|
|
3602
|
+
const distEntry = path4.join(projectRoot, "dist", "entry.js");
|
|
3603
|
+
try {
|
|
3604
|
+
let distStat;
|
|
3605
|
+
try {
|
|
3606
|
+
distStat = await fs3.stat(distEntry);
|
|
3607
|
+
} catch {
|
|
3608
|
+
return {
|
|
3609
|
+
check,
|
|
3610
|
+
description,
|
|
3611
|
+
passed: false,
|
|
3612
|
+
error: "dist/entry.js not found. Run `npm run build` before executing the pipeline.",
|
|
3613
|
+
durationMs: Date.now() - start
|
|
3614
|
+
};
|
|
3615
|
+
}
|
|
3616
|
+
const distMtimeMs = distStat.mtimeMs;
|
|
3617
|
+
const entryIntegrity = await readDistEntryIntegrity(projectRoot);
|
|
3618
|
+
if (!entryIntegrity.readable) {
|
|
3619
|
+
return {
|
|
3620
|
+
check,
|
|
3621
|
+
description,
|
|
3622
|
+
passed: false,
|
|
3623
|
+
error: `Build is unusable: dist/entry.js could not be read (${entryIntegrity.reason}). Run \`npm run build\` before executing.`,
|
|
3624
|
+
durationMs: Date.now() - start
|
|
3625
|
+
};
|
|
3626
|
+
}
|
|
3627
|
+
if (entryIntegrity.missingChunks.length > 0) {
|
|
3628
|
+
return {
|
|
3629
|
+
check,
|
|
3630
|
+
description,
|
|
3631
|
+
passed: false,
|
|
3632
|
+
error: `Build is inconsistent: dist/entry.js references missing dist/${entryIntegrity.missingChunks[0]}. Run \`npm run build\` before executing.`,
|
|
3633
|
+
durationMs: Date.now() - start
|
|
3634
|
+
};
|
|
3635
|
+
}
|
|
3636
|
+
const sourceBuildInputsPresent = await hasSourceBuildInputs(projectRoot);
|
|
3637
|
+
if (!sourceBuildInputsPresent) {
|
|
3638
|
+
return {
|
|
3639
|
+
check,
|
|
3640
|
+
description,
|
|
3641
|
+
passed: true,
|
|
3642
|
+
metadata: { packageMode: true },
|
|
3643
|
+
durationMs: Date.now() - start
|
|
3644
|
+
};
|
|
3645
|
+
}
|
|
3646
|
+
const buildInputs = [
|
|
3647
|
+
...await collectBuildInputFiles(path4.join(projectRoot, "src")),
|
|
3648
|
+
...await collectGeneratedProviderFiles(path4.join(projectRoot, "providers")),
|
|
3649
|
+
...BUILD_INPUT_PATHS.slice(1).filter((relativePath) => relativePath !== "providers").map((relativePath) => path4.join(projectRoot, relativePath))
|
|
3650
|
+
];
|
|
3651
|
+
const dirtyBuildInputPaths = await getDirtyBuildInputPaths(projectRoot);
|
|
3652
|
+
const latestCommittedBuildInput = dirtyBuildInputPaths ? await getLatestCommittedBuildInput(projectRoot) : null;
|
|
3653
|
+
let newestInputPath;
|
|
3654
|
+
let newestInputMtimeMs = Number.NEGATIVE_INFINITY;
|
|
3655
|
+
for (const buildInput of buildInputs) {
|
|
3656
|
+
try {
|
|
3657
|
+
const inputStat = await fs3.stat(buildInput);
|
|
3658
|
+
const isDirty = dirtyBuildInputPaths?.has(buildInput) ?? true;
|
|
3659
|
+
if (isDirty && inputStat.mtimeMs > distMtimeMs) {
|
|
3660
|
+
return {
|
|
3661
|
+
check,
|
|
3662
|
+
description,
|
|
3663
|
+
passed: false,
|
|
3664
|
+
error: `Build is stale: ${path4.relative(projectRoot, buildInput)} is newer than dist/entry.js. Run \`npm run build\` before executing.`,
|
|
3665
|
+
durationMs: Date.now() - start
|
|
3666
|
+
};
|
|
3667
|
+
}
|
|
3668
|
+
if (inputStat.mtimeMs > newestInputMtimeMs) {
|
|
3669
|
+
newestInputMtimeMs = inputStat.mtimeMs;
|
|
3670
|
+
newestInputPath = buildInput;
|
|
3671
|
+
}
|
|
3672
|
+
} catch (error) {
|
|
3673
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
3674
|
+
continue;
|
|
3675
|
+
}
|
|
3676
|
+
throw error;
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
if (latestCommittedBuildInput && latestCommittedBuildInput.committedAtMs > distMtimeMs) {
|
|
3680
|
+
const rel = latestCommittedBuildInput.path ? latestCommittedBuildInput.path : BUILD_INPUT_PATHS.join(", ");
|
|
3681
|
+
return {
|
|
3682
|
+
check,
|
|
3683
|
+
description,
|
|
3684
|
+
passed: false,
|
|
3685
|
+
error: `Build is stale: ${rel} is newer than dist/entry.js. Run \`npm run build\` before executing.`,
|
|
3686
|
+
durationMs: Date.now() - start
|
|
3687
|
+
};
|
|
3688
|
+
}
|
|
3689
|
+
if (!dirtyBuildInputPaths && newestInputPath && newestInputMtimeMs > distMtimeMs) {
|
|
3690
|
+
const rel = path4.relative(projectRoot, newestInputPath);
|
|
3691
|
+
return {
|
|
3692
|
+
check,
|
|
3693
|
+
description,
|
|
3694
|
+
passed: false,
|
|
3695
|
+
error: `Build is stale: ${rel} is newer than dist/entry.js. Run \`npm run build\` before executing.`,
|
|
3696
|
+
durationMs: Date.now() - start
|
|
3697
|
+
};
|
|
3698
|
+
}
|
|
3699
|
+
return {
|
|
3700
|
+
check,
|
|
3701
|
+
description,
|
|
3702
|
+
passed: true,
|
|
3703
|
+
durationMs: Date.now() - start
|
|
3704
|
+
};
|
|
3705
|
+
} catch (err) {
|
|
3706
|
+
return {
|
|
3707
|
+
check,
|
|
3708
|
+
description,
|
|
3709
|
+
passed: false,
|
|
3710
|
+
error: `Build freshness check failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
3711
|
+
durationMs: Date.now() - start
|
|
3712
|
+
};
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
async function describeInstalledDependencyDrift() {
|
|
3716
|
+
const projectRoot = resolveBuildPackageRoot();
|
|
3717
|
+
const drift = await findInstalledDependencyDrift(projectRoot);
|
|
3718
|
+
return formatInstalledDependencyDriftWarning(drift, projectRoot);
|
|
3719
|
+
}
|
|
3720
|
+
async function ensureFreshBuild(options) {
|
|
3721
|
+
if (process.env.BUILD_CLI_REEXEC_AFTER_BUILD === "1") {
|
|
3722
|
+
return { fresh: true, rebuilt: false };
|
|
3723
|
+
}
|
|
3724
|
+
const initialResult = await checkBuildFreshness();
|
|
3725
|
+
if (initialResult.passed) {
|
|
3726
|
+
return { fresh: true, rebuilt: false };
|
|
3727
|
+
}
|
|
3728
|
+
const originalError = initialResult.error ?? "Build is stale or missing.";
|
|
3729
|
+
const quiet = options?.quiet ?? isQuietOutputRequested();
|
|
3730
|
+
const projectRoot = resolveBuildPackageRoot();
|
|
3731
|
+
const entryExists = existsSync2(path4.join(projectRoot, "dist", "entry.js"));
|
|
3732
|
+
const entryIntegrity = entryExists ? await readDistEntryIntegrity(projectRoot) : null;
|
|
3733
|
+
const ownership = await resolveDistOwnership(projectRoot);
|
|
3734
|
+
const decision = decideDistRebuild(ownership, {
|
|
3735
|
+
artifactExists: entryExists,
|
|
3736
|
+
artifactRelativePath: "dist/entry.js",
|
|
3737
|
+
missingBundledFiles: entryIntegrity?.readable ? entryIntegrity.missingChunks : [],
|
|
3738
|
+
unusableReason: entryIntegrity && !entryIntegrity.readable ? `it could not be read (${entryIntegrity.reason})` : void 0
|
|
3739
|
+
});
|
|
3740
|
+
if (decision.action === "fail") {
|
|
3741
|
+
throw new Error(decision.message);
|
|
3742
|
+
}
|
|
3743
|
+
if (decision.action === "proceed") {
|
|
3744
|
+
if (options?.requireFresh) {
|
|
3745
|
+
throw new Error(originalError);
|
|
3746
|
+
}
|
|
3747
|
+
if (!quiet && isVerboseOutputRequested()) {
|
|
3748
|
+
console.log(decision.diagnostic);
|
|
3749
|
+
}
|
|
3750
|
+
return { fresh: true, rebuilt: false };
|
|
3751
|
+
}
|
|
3752
|
+
if (!quiet) {
|
|
3753
|
+
console.log(chalk.yellow("Build is stale. Auto-rebuilding..."));
|
|
3754
|
+
}
|
|
3755
|
+
const buildResult = await execa2("npm", ["run", "build"], {
|
|
3756
|
+
cwd: projectRoot,
|
|
3757
|
+
stdio: quiet ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
3758
|
+
reject: false
|
|
3759
|
+
});
|
|
3760
|
+
if (buildResult.exitCode !== 0) {
|
|
3761
|
+
const output = [buildResult.stderr, buildResult.stdout].map((value) => value?.trim()).filter(Boolean).join("\n").slice(0, 4e3);
|
|
3762
|
+
throw new Error(
|
|
3763
|
+
`npm run build failed with exit code ${buildResult.exitCode}${output ? `:
|
|
3764
|
+
${output}` : ""}`
|
|
3765
|
+
);
|
|
3766
|
+
}
|
|
3767
|
+
await writeDistBuildStamp(projectRoot, { ownership });
|
|
3768
|
+
const verifyResult = await checkBuildFreshness(projectRoot);
|
|
3769
|
+
if (verifyResult.passed) {
|
|
3770
|
+
return { fresh: true, rebuilt: true };
|
|
3771
|
+
}
|
|
3772
|
+
throw new Error(`${originalError} Auto-rebuild did not resolve the issue.`);
|
|
3773
|
+
}
|
|
3774
|
+
var CLI_REEXEC_ENV = "BUILD_CLI_REEXEC_AFTER_BUILD";
|
|
3775
|
+
var JSONL_CAPTURE_FILE_ENV = "BUILD_JSONL_CAPTURE_FILE";
|
|
3776
|
+
var REEXEC_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
3777
|
+
function signalToExitCode(signal) {
|
|
3778
|
+
switch (signal) {
|
|
3779
|
+
case "SIGINT":
|
|
3780
|
+
return 130;
|
|
3781
|
+
case "SIGTERM":
|
|
3782
|
+
return 143;
|
|
3783
|
+
case "SIGHUP":
|
|
3784
|
+
return 129;
|
|
3785
|
+
default:
|
|
3786
|
+
return signal ? 1 : null;
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
function resolveCliReexecEntry() {
|
|
3790
|
+
const packageRoot = resolveBuildPackageRoot();
|
|
3791
|
+
const fallbackEntry = path4.join(packageRoot, "dist", "entry.js");
|
|
3792
|
+
const invokedEntry = process.argv[1] ? path4.resolve(process.argv[1]) : fallbackEntry;
|
|
3793
|
+
return invokedEntry.endsWith(".js") || invokedEntry.endsWith(".mjs") ? invokedEntry : fallbackEntry;
|
|
3794
|
+
}
|
|
3795
|
+
function writeJsonStdoutText(text, stdoutFilePath) {
|
|
3796
|
+
if (!text) return;
|
|
3797
|
+
if (stdoutFilePath) {
|
|
3798
|
+
appendFileSync(stdoutFilePath, text, "utf8");
|
|
3799
|
+
return;
|
|
3800
|
+
}
|
|
3801
|
+
writeAllSyncToFd(1, text);
|
|
3802
|
+
}
|
|
3803
|
+
function argvRequestsJsonOutput(argv) {
|
|
3804
|
+
return argv.includes("--json") || argv.includes("-j") || argv.includes("--output=json");
|
|
3805
|
+
}
|
|
3806
|
+
async function reexecCurrentCliCommand(cwd) {
|
|
3807
|
+
const entryPath = resolveCliReexecEntry();
|
|
3808
|
+
const captureJsonStdout = isJsonOutputRequested() || argvRequestsJsonOutput(process.argv);
|
|
3809
|
+
const jsonCaptureDir = captureJsonStdout ? await fs3.mkdtemp(path4.join(os2.tmpdir(), "build-jsonl-reexec-")) : void 0;
|
|
3810
|
+
const jsonCaptureFile = jsonCaptureDir ? path4.join(jsonCaptureDir, "stdout.jsonl") : void 0;
|
|
3811
|
+
const stdoutFilePath = captureJsonStdout ? resolveStdoutRegularFilePath() : void 0;
|
|
3812
|
+
signalCoordinator.suspendProcessSignalHandlers();
|
|
3813
|
+
let child = null;
|
|
3814
|
+
let pendingSignal = null;
|
|
3815
|
+
const forwardSignal = (signal) => {
|
|
3816
|
+
if (!child) {
|
|
3817
|
+
pendingSignal = signal;
|
|
3818
|
+
return;
|
|
3819
|
+
}
|
|
3820
|
+
if (signal === "SIGINT" && process.stdin.isTTY) {
|
|
3821
|
+
return;
|
|
3822
|
+
}
|
|
3823
|
+
if (process.platform !== "win32" && typeof child.pid === "number") {
|
|
3824
|
+
try {
|
|
3825
|
+
process.kill(child.pid, signal);
|
|
3826
|
+
return;
|
|
3827
|
+
} catch {
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
child.kill?.(signal);
|
|
3831
|
+
};
|
|
3832
|
+
const forwardSigint = () => forwardSignal("SIGINT");
|
|
3833
|
+
const forwardSigterm = () => forwardSignal("SIGTERM");
|
|
3834
|
+
const forwardSighup = () => forwardSignal("SIGHUP");
|
|
3835
|
+
const forwarders = {
|
|
3836
|
+
SIGINT: forwardSigint,
|
|
3837
|
+
SIGTERM: forwardSigterm,
|
|
3838
|
+
SIGHUP: forwardSighup
|
|
3839
|
+
};
|
|
3840
|
+
for (const signal of REEXEC_SIGNALS) {
|
|
3841
|
+
process.on(signal, forwarders[signal]);
|
|
3842
|
+
}
|
|
3843
|
+
const exitCode = await (async () => {
|
|
3844
|
+
try {
|
|
3845
|
+
child = execa2(process.execPath, [entryPath, ...process.argv.slice(2)], {
|
|
3846
|
+
cwd: cwd ?? getProjectRoot(),
|
|
3847
|
+
stdio: captureJsonStdout ? ["inherit", "pipe", "inherit"] : "inherit",
|
|
3848
|
+
reject: false,
|
|
3849
|
+
env: {
|
|
3850
|
+
...process.env,
|
|
3851
|
+
[CLI_REEXEC_ENV]: "1",
|
|
3852
|
+
...jsonCaptureFile ? { [JSONL_CAPTURE_FILE_ENV]: jsonCaptureFile } : {}
|
|
3853
|
+
}
|
|
3854
|
+
});
|
|
3855
|
+
if (pendingSignal) {
|
|
3856
|
+
forwardSignal(pendingSignal);
|
|
3857
|
+
}
|
|
3858
|
+
const result = await child;
|
|
3859
|
+
if (jsonCaptureFile) {
|
|
3860
|
+
const captured = await fs3.readFile(jsonCaptureFile, "utf8").catch(() => "");
|
|
3861
|
+
if (captured) {
|
|
3862
|
+
writeJsonStdoutText(captured, stdoutFilePath);
|
|
3863
|
+
} else if (typeof result.stdout === "string" && result.stdout) {
|
|
3864
|
+
writeJsonStdoutText(result.stdout, stdoutFilePath);
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
return result.exitCode ?? signalToExitCode(result.signal) ?? 1;
|
|
3868
|
+
} finally {
|
|
3869
|
+
if (jsonCaptureDir) {
|
|
3870
|
+
await fs3.rm(jsonCaptureDir, { recursive: true, force: true }).catch(() => void 0);
|
|
3871
|
+
}
|
|
3872
|
+
for (const signal of REEXEC_SIGNALS) {
|
|
3873
|
+
process.removeListener(signal, forwarders[signal]);
|
|
3874
|
+
}
|
|
3875
|
+
signalCoordinator.resumeProcessSignalHandlers();
|
|
3876
|
+
}
|
|
3877
|
+
})();
|
|
3878
|
+
process.exit(exitCode);
|
|
3879
|
+
}
|
|
3880
|
+
async function runWorkflowPreflight(workflow, options = {}) {
|
|
3881
|
+
const startTime = Date.now();
|
|
3882
|
+
const cwd = options.cwd || process.cwd();
|
|
3883
|
+
const worktreeRoot = options.worktreeHygiene?.baseDir ?? getWorktreeBaseDir();
|
|
3884
|
+
const skip = options.skip || {};
|
|
3885
|
+
const checks = [];
|
|
3886
|
+
const errors = [];
|
|
3887
|
+
const warnings = [];
|
|
3888
|
+
const repoCleanMode = options.repoCleanMode ?? "warn";
|
|
3889
|
+
const suppressExpectedSetupRepoDirtyWarning = options.suppressExpectedSetupRepoDirtyWarning ?? false;
|
|
3890
|
+
const runCheck = async (name, skipFlag, fn) => {
|
|
3891
|
+
if (skipFlag) {
|
|
3892
|
+
if (options.verbose) {
|
|
3893
|
+
console.log(` [SKIP] ${name}`);
|
|
3894
|
+
}
|
|
3895
|
+
return;
|
|
3896
|
+
}
|
|
3897
|
+
if (options.verbose) {
|
|
3898
|
+
process.stdout.write(` [....] ${name}`);
|
|
3899
|
+
}
|
|
3900
|
+
const result = await fn();
|
|
3901
|
+
checks.push(result);
|
|
3902
|
+
if (!result.passed && result.error) {
|
|
3903
|
+
errors.push(result.error);
|
|
3904
|
+
}
|
|
3905
|
+
if (result.warning) {
|
|
3906
|
+
warnings.push(result.warning);
|
|
3907
|
+
}
|
|
3908
|
+
if (options.verbose) {
|
|
3909
|
+
const status = result.passed ? result.warning ? "WARN" : "PASS" : "FAIL";
|
|
3910
|
+
process.stdout.write(`\r [${status}] ${name} (${result.durationMs}ms)
|
|
3911
|
+
`);
|
|
3912
|
+
}
|
|
3913
|
+
};
|
|
3914
|
+
if (options.verbose) {
|
|
3915
|
+
console.log("\nRunning preflight checks...\n");
|
|
3916
|
+
}
|
|
3917
|
+
await runCheck(
|
|
3918
|
+
"Build freshness",
|
|
3919
|
+
skip.buildFreshness || process.env[CLI_REEXEC_ENV] === "1",
|
|
3920
|
+
() => checkBuildFreshness()
|
|
3921
|
+
);
|
|
3922
|
+
await runCheck("Configuration validation", skip.configValidation, () => checkConfig(workflow));
|
|
3923
|
+
await runCheck(
|
|
3924
|
+
"Task spec freshness",
|
|
3925
|
+
skip.taskSpecFreshness,
|
|
3926
|
+
() => checkTaskSpecFreshness(workflow, cwd)
|
|
3927
|
+
);
|
|
3928
|
+
await runCheck(
|
|
3929
|
+
"Repository cleanliness",
|
|
3930
|
+
skip.repoClean,
|
|
3931
|
+
() => checkRepoClean(cwd, repoCleanMode, suppressExpectedSetupRepoDirtyWarning)
|
|
3932
|
+
);
|
|
3933
|
+
await runCheck("Worktree sanity", skip.worktreeSanity, () => checkWorktreeSanity(worktreeRoot));
|
|
3934
|
+
await runCheck(
|
|
3935
|
+
"Worktree hygiene",
|
|
3936
|
+
skip.worktreeHygiene,
|
|
3937
|
+
() => checkWorktreeHygiene(worktreeRoot, cwd, { ...options.worktreeHygiene, baseDir: worktreeRoot })
|
|
3938
|
+
);
|
|
3939
|
+
await runCheck(
|
|
3940
|
+
"Worktree recovery",
|
|
3941
|
+
skip.worktreeRecovery,
|
|
3942
|
+
() => checkWorktreeRecovery({ ...options.worktreeHygiene, baseDir: worktreeRoot })
|
|
3943
|
+
);
|
|
3944
|
+
await runCheck(
|
|
3945
|
+
"Provider authentication",
|
|
3946
|
+
skip.providerAuth,
|
|
3947
|
+
() => checkProviderAuth(workflow, cwd, options.authProbeEnv)
|
|
3948
|
+
);
|
|
3949
|
+
await runCheck("Artifact references", skip.artifactRefs, () => checkArtifactRefs(workflow));
|
|
3950
|
+
await runCheck(
|
|
3951
|
+
"Context window utilization",
|
|
3952
|
+
skip.contextUtilization,
|
|
3953
|
+
() => checkContextUtilization(workflow, cwd)
|
|
3954
|
+
);
|
|
3955
|
+
const totalDurationMs = Date.now() - startTime;
|
|
3956
|
+
const passed = errors.length === 0;
|
|
3957
|
+
const passedCount = checks.filter((c) => c.passed).length;
|
|
3958
|
+
const failedCount = checks.filter((c) => !c.passed).length;
|
|
3959
|
+
const warnCount = checks.filter((c) => c.warning).length;
|
|
3960
|
+
let summary = `Preflight: ${passedCount}/${checks.length} checks passed`;
|
|
3961
|
+
if (failedCount > 0) {
|
|
3962
|
+
summary += `, ${failedCount} failed`;
|
|
3963
|
+
}
|
|
3964
|
+
if (warnCount > 0) {
|
|
3965
|
+
summary += `, ${warnCount} warnings`;
|
|
3966
|
+
}
|
|
3967
|
+
summary += ` (${totalDurationMs}ms)`;
|
|
3968
|
+
if (options.verbose) {
|
|
3969
|
+
console.log(`
|
|
3970
|
+
${passed ? "Preflight PASSED" : "Preflight FAILED"}: ${summary}
|
|
3971
|
+
`);
|
|
3972
|
+
if (errors.length > 0) {
|
|
3973
|
+
console.log("Errors:");
|
|
3974
|
+
for (const error of errors) {
|
|
3975
|
+
console.log(` - ${error}`);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
if (warnings.length > 0) {
|
|
3979
|
+
console.log("Warnings:");
|
|
3980
|
+
for (const warning of warnings) {
|
|
3981
|
+
console.log(` - ${warning}`);
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3985
|
+
return {
|
|
3986
|
+
passed,
|
|
3987
|
+
checks,
|
|
3988
|
+
errors,
|
|
3989
|
+
warnings,
|
|
3990
|
+
totalDurationMs,
|
|
3991
|
+
summary
|
|
3992
|
+
};
|
|
3993
|
+
}
|
|
3994
|
+
async function runPlanPreflight(cwd, options) {
|
|
3995
|
+
let buildResult;
|
|
3996
|
+
try {
|
|
3997
|
+
buildResult = await ensureFreshBuild({ requireFresh: true });
|
|
3998
|
+
} catch (error) {
|
|
3999
|
+
return { passed: false, error: error instanceof Error ? error.message : String(error) };
|
|
4000
|
+
}
|
|
4001
|
+
if (options?.reexecOnRebuild === true && buildResult.rebuilt && process.env[CLI_REEXEC_ENV] !== "1") {
|
|
4002
|
+
await reexecCurrentCliCommand(cwd);
|
|
4003
|
+
}
|
|
4004
|
+
const workingDir = cwd ?? process.cwd();
|
|
4005
|
+
const repoResult = await checkRepoClean(workingDir, "warn");
|
|
4006
|
+
if (!repoResult.passed) {
|
|
4007
|
+
const meta = repoResult.metadata;
|
|
4008
|
+
return {
|
|
4009
|
+
passed: false,
|
|
4010
|
+
error: meta?.hasConflictStatuses ? `${repoResult.error?.split("\n")[0] ?? "Repository has uncommitted changes"}
|
|
4011
|
+
Resolve conflicts before running planned build.` : repoResult.error
|
|
4012
|
+
};
|
|
4013
|
+
}
|
|
4014
|
+
if (repoResult.warning) {
|
|
4015
|
+
const meta = repoResult.metadata;
|
|
4016
|
+
if (meta?.hasConflictStatuses) {
|
|
4017
|
+
return {
|
|
4018
|
+
passed: false,
|
|
4019
|
+
error: `${repoResult.warning.split("\n")[0]}
|
|
4020
|
+
Resolve conflicts before running planned build.`
|
|
4021
|
+
};
|
|
4022
|
+
}
|
|
4023
|
+
return {
|
|
4024
|
+
passed: true,
|
|
4025
|
+
warning: repoResult.warning
|
|
4026
|
+
};
|
|
4027
|
+
}
|
|
4028
|
+
return { passed: true };
|
|
4029
|
+
}
|
|
4030
|
+
|
|
4031
|
+
export {
|
|
4032
|
+
UTILIZATION_WARN_PERCENT,
|
|
4033
|
+
UTILIZATION_SPLIT_PERCENT,
|
|
4034
|
+
TokenEstimator,
|
|
4035
|
+
recordSyncWriteNoProgress,
|
|
4036
|
+
writeAllSyncToFd,
|
|
4037
|
+
resolveStdoutRegularFilePath,
|
|
4038
|
+
getExtensionsRootDir,
|
|
4039
|
+
getExtensionDirs,
|
|
4040
|
+
runAsyncWithOptionalTimeout,
|
|
4041
|
+
cleanupWorkflowRelatedBranchesAndWorktrees,
|
|
4042
|
+
recoverPendingWorktrees,
|
|
4043
|
+
enforceRetentionBounds,
|
|
4044
|
+
getFlywheelCleanupDryRunWorktreeSummary,
|
|
4045
|
+
makeRetryDecision,
|
|
4046
|
+
validateGeneratedSubStageVerifyCommands,
|
|
4047
|
+
validateWorkflowConfig,
|
|
4048
|
+
BUILD_INPUT_PATHS,
|
|
4049
|
+
checkBuildFreshness,
|
|
4050
|
+
describeInstalledDependencyDrift,
|
|
4051
|
+
ensureFreshBuild,
|
|
4052
|
+
writeJsonStdoutText,
|
|
4053
|
+
reexecCurrentCliCommand,
|
|
4054
|
+
runWorkflowPreflight,
|
|
4055
|
+
runPlanPreflight
|
|
4056
|
+
};
|
|
4057
|
+
//# sourceMappingURL=chunk-ES6NWJ36.js.map
|