@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,2002 @@
|
|
|
1
|
+
import {
|
|
2
|
+
acquirePublicationTurn,
|
|
3
|
+
readPublicationTurn
|
|
4
|
+
} from "./chunk-63DHNHGA.js";
|
|
5
|
+
import {
|
|
6
|
+
repositoryFromGitHubRemoteUrl
|
|
7
|
+
} from "./chunk-BDPYFAB4.js";
|
|
8
|
+
import {
|
|
9
|
+
BUCKET_ALIASES,
|
|
10
|
+
defaultBucketArchivePath,
|
|
11
|
+
getBuckets
|
|
12
|
+
} from "./chunk-V3KLVK7B.js";
|
|
13
|
+
import {
|
|
14
|
+
fetchStorageBranch,
|
|
15
|
+
hasCompletedCheckout,
|
|
16
|
+
isAncestor,
|
|
17
|
+
isLinkedWorktree,
|
|
18
|
+
isStorageLockBusyError,
|
|
19
|
+
listTopLevelTreeEntries,
|
|
20
|
+
pathExists,
|
|
21
|
+
readLastFetchTime,
|
|
22
|
+
readPorcelainStatus,
|
|
23
|
+
readWorktreeGitDir,
|
|
24
|
+
readWorktreeId,
|
|
25
|
+
refreshDefaultBranchTracking,
|
|
26
|
+
resolveCommonGitDir,
|
|
27
|
+
resolveFetchTimeoutMs,
|
|
28
|
+
resolvePushTimeoutMs,
|
|
29
|
+
resolveRevision,
|
|
30
|
+
trackingRef,
|
|
31
|
+
withStorageLock
|
|
32
|
+
} from "./chunk-BAWJNMWF.js";
|
|
33
|
+
import {
|
|
34
|
+
resolveDefaultBranch
|
|
35
|
+
} from "./chunk-XGU6PBO5.js";
|
|
36
|
+
import {
|
|
37
|
+
parseGitStatus,
|
|
38
|
+
parseGitStatusPaths,
|
|
39
|
+
parsePorcelainLinePath
|
|
40
|
+
} from "./chunk-5KQETTNS.js";
|
|
41
|
+
import {
|
|
42
|
+
execGit
|
|
43
|
+
} from "./chunk-MHLEZQQI.js";
|
|
44
|
+
import {
|
|
45
|
+
isPlanningItemFile
|
|
46
|
+
} from "./chunk-352PCJLK.js";
|
|
47
|
+
import {
|
|
48
|
+
processOwnershipState
|
|
49
|
+
} from "./chunk-MT2WAIR3.js";
|
|
50
|
+
import {
|
|
51
|
+
GIT_HOOK_COMMIT_TIMEOUT_MS
|
|
52
|
+
} from "./chunk-I56TH7VM.js";
|
|
53
|
+
import {
|
|
54
|
+
getStateDir
|
|
55
|
+
} from "./chunk-56WPYDJH.js";
|
|
56
|
+
import {
|
|
57
|
+
DEFAULT_PRE_COMMIT_POLICY,
|
|
58
|
+
PUBLISH_OUTCOME_EXIT_CODES,
|
|
59
|
+
SHARED_BUCKET_NAMES,
|
|
60
|
+
loadStorageConfig,
|
|
61
|
+
parseStorageConfigLenient,
|
|
62
|
+
resolvePreCommitPolicy
|
|
63
|
+
} from "./chunk-7VRJHTGZ.js";
|
|
64
|
+
|
|
65
|
+
// src/planning/storage/guards.ts
|
|
66
|
+
import * as fs2 from "fs";
|
|
67
|
+
import * as path2 from "path";
|
|
68
|
+
import yaml2 from "js-yaml";
|
|
69
|
+
|
|
70
|
+
// src/planning/storage/adopt.ts
|
|
71
|
+
import * as fs from "fs/promises";
|
|
72
|
+
import * as path from "path";
|
|
73
|
+
import yaml from "js-yaml";
|
|
74
|
+
var NO_ADOPTION = { moved: [], skippedIdentical: [] };
|
|
75
|
+
function cleanRelativeDir(value) {
|
|
76
|
+
if (typeof value !== "string") return void 0;
|
|
77
|
+
const posix3 = path.posix.normalize(value.replace(/\\/g, "/").trim()).replace(/\/+$/, "");
|
|
78
|
+
if (!posix3 || posix3 === "." || posix3.startsWith("/") || /^[A-Za-z]:/.test(posix3)) {
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
if (posix3 === ".." || posix3.startsWith("../")) return void 0;
|
|
82
|
+
return posix3;
|
|
83
|
+
}
|
|
84
|
+
async function readSeamBucketPaths(ctx) {
|
|
85
|
+
const mainSha = ctx.seam?.mainSha;
|
|
86
|
+
if (!mainSha) return void 0;
|
|
87
|
+
const shown = await execGit(["show", `${mainSha}:.build-project/config/buckets.yaml`], {
|
|
88
|
+
cwd: ctx.projectRoot
|
|
89
|
+
});
|
|
90
|
+
if (shown.exitCode !== 0) return void 0;
|
|
91
|
+
let parsed;
|
|
92
|
+
try {
|
|
93
|
+
parsed = yaml.load(shown.stdout, { schema: yaml.JSON_SCHEMA });
|
|
94
|
+
} catch {
|
|
95
|
+
return void 0;
|
|
96
|
+
}
|
|
97
|
+
const buckets = parsed?.buckets;
|
|
98
|
+
if (!buckets || typeof buckets !== "object") return void 0;
|
|
99
|
+
const result2 = /* @__PURE__ */ new Map();
|
|
100
|
+
for (const [name, raw] of Object.entries(buckets)) {
|
|
101
|
+
const entry = raw ?? {};
|
|
102
|
+
const dir = cleanRelativeDir(entry.path);
|
|
103
|
+
const archiveDir = cleanRelativeDir(entry.archivePath);
|
|
104
|
+
const key = name.toLowerCase().trim();
|
|
105
|
+
result2.set(BUCKET_ALIASES[key] ?? key, {
|
|
106
|
+
...dir ? { dir } : {},
|
|
107
|
+
...archiveDir ? { archiveDir } : {}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return result2;
|
|
111
|
+
}
|
|
112
|
+
async function resolveLegacyBucketLayout(ctx) {
|
|
113
|
+
const seamPaths = await readSeamBucketPaths(ctx);
|
|
114
|
+
const shared = new Set(SHARED_BUCKET_NAMES);
|
|
115
|
+
return getBuckets(ctx.projectRoot).filter((bucket) => shared.has(bucket.name)).flatMap((bucket) => {
|
|
116
|
+
const newDir = cleanRelativeDir(bucket.path);
|
|
117
|
+
const newArchiveDir = cleanRelativeDir(bucket.archivePath);
|
|
118
|
+
if (!newDir || !newArchiveDir) return [];
|
|
119
|
+
const old = seamPaths?.get(bucket.name);
|
|
120
|
+
const oldDir = old?.dir ?? newDir;
|
|
121
|
+
const builtInArchiveDir = cleanRelativeDir(defaultBucketArchivePath(bucket.name));
|
|
122
|
+
const defaultArchiveDir = old?.dir ? builtInArchiveDir ?? `${old.dir}/archive` : newArchiveDir;
|
|
123
|
+
return [
|
|
124
|
+
{
|
|
125
|
+
name: bucket.name,
|
|
126
|
+
oldDir,
|
|
127
|
+
oldArchiveDir: old?.archiveDir ?? defaultArchiveDir,
|
|
128
|
+
newArchiveDir,
|
|
129
|
+
archivePublished: bucket.archivePublished !== false
|
|
130
|
+
}
|
|
131
|
+
];
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function isUnder(file, dir) {
|
|
135
|
+
return file.startsWith(`${dir}/`);
|
|
136
|
+
}
|
|
137
|
+
async function listLegacyBucketFilesOnRef(ctx, ref) {
|
|
138
|
+
const layout = await resolveLegacyBucketLayout(ctx);
|
|
139
|
+
const dirs = [...new Set(layout.map((bucket) => bucket.oldDir))];
|
|
140
|
+
if (dirs.length === 0) return [];
|
|
141
|
+
const tree = await execGit(["ls-tree", "-r", "--name-only", ref, "--", ...dirs], {
|
|
142
|
+
cwd: ctx.projectRoot
|
|
143
|
+
});
|
|
144
|
+
if (tree.exitCode !== 0) return [];
|
|
145
|
+
return tree.stdout.split("\n").filter(Boolean);
|
|
146
|
+
}
|
|
147
|
+
async function lsFiles(projectRoot, args, dirs) {
|
|
148
|
+
const result2 = await execGit(["ls-files", "-z", ...args, "--", ...dirs], { cwd: projectRoot });
|
|
149
|
+
if (result2.exitCode !== 0) {
|
|
150
|
+
throw new Error(`cannot list files under the old bucket locations: ${result2.stderr.trim()}`);
|
|
151
|
+
}
|
|
152
|
+
return result2.stdout.split("\0").filter(Boolean);
|
|
153
|
+
}
|
|
154
|
+
async function sameContent(a, b) {
|
|
155
|
+
const [left, right] = await Promise.all([fs.readFile(a), fs.readFile(b)]);
|
|
156
|
+
return left.equals(right);
|
|
157
|
+
}
|
|
158
|
+
function refusal(kind, paths, lead, tail) {
|
|
159
|
+
const sorted = [...new Set(paths)].sort();
|
|
160
|
+
return { kind, paths: sorted, message: `${lead}: ${sorted.join(", ")}. ${tail}` };
|
|
161
|
+
}
|
|
162
|
+
async function adoptLocalArchives(ctx) {
|
|
163
|
+
const layout = await resolveLegacyBucketLayout(ctx);
|
|
164
|
+
const storageRel = ctx.worktree ? cleanRelativeDir(ctx.worktree) : void 0;
|
|
165
|
+
const buckets = layout.filter((b) => !storageRel || !isUnder(`${b.oldDir}/`, storageRel));
|
|
166
|
+
const existing = [];
|
|
167
|
+
const candidateDirs = new Set(buckets.flatMap((b) => [b.oldDir, b.oldArchiveDir]));
|
|
168
|
+
for (const dir of candidateDirs) {
|
|
169
|
+
if (await pathExists(path.join(ctx.projectRoot, dir))) existing.push(dir);
|
|
170
|
+
}
|
|
171
|
+
if (existing.length === 0) return NO_ADOPTION;
|
|
172
|
+
const tracked = await lsFiles(ctx.projectRoot, ["--cached"], existing);
|
|
173
|
+
const untracked = await lsFiles(ctx.projectRoot, ["--others", "--exclude-standard"], existing);
|
|
174
|
+
const archiveDirs = buckets.map((b) => b.oldArchiveDir);
|
|
175
|
+
const activeDirs = [...new Set(buckets.map((b) => b.oldDir))].filter(
|
|
176
|
+
(dir) => existing.includes(dir)
|
|
177
|
+
);
|
|
178
|
+
const ignoredActive = activeDirs.length ? (await lsFiles(ctx.projectRoot, ["--others", "--ignored", "--exclude-standard"], activeDirs)).filter((f) => isPlanningItemFile(path.posix.basename(f))).filter((f) => !archiveDirs.some((dir) => isUnder(f, dir))) : [];
|
|
179
|
+
const unpublished = buckets.filter((b) => !b.archivePublished);
|
|
180
|
+
const ignored = unpublished.length ? await lsFiles(
|
|
181
|
+
ctx.projectRoot,
|
|
182
|
+
["--others", "--ignored", "--exclude-standard"],
|
|
183
|
+
unpublished.map((b) => b.oldArchiveDir).filter((dir) => existing.includes(dir))
|
|
184
|
+
) : [];
|
|
185
|
+
const archiveOf = (file) => unpublished.find((b) => isUnder(file, b.oldArchiveDir));
|
|
186
|
+
const movable = [.../* @__PURE__ */ new Set([...untracked, ...ignored])].filter((f) => archiveOf(f));
|
|
187
|
+
const leftovers = [];
|
|
188
|
+
for (const file of tracked) {
|
|
189
|
+
if (await pathExists(path.join(ctx.projectRoot, file))) leftovers.push(file);
|
|
190
|
+
}
|
|
191
|
+
leftovers.push(...untracked.filter((f) => !archiveOf(f)), ...ignoredActive);
|
|
192
|
+
if (leftovers.length > 0) {
|
|
193
|
+
return {
|
|
194
|
+
...NO_ADOPTION,
|
|
195
|
+
refusal: refusal(
|
|
196
|
+
"leftover",
|
|
197
|
+
leftovers,
|
|
198
|
+
"planning storage moved to the git-branch backend, but tracked or uncommitted files remain in the old bucket locations",
|
|
199
|
+
"Commit, restore or remove them, then rerun; nothing was moved."
|
|
200
|
+
)
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const plan = [];
|
|
204
|
+
const collisions = [];
|
|
205
|
+
for (const file of movable) {
|
|
206
|
+
const bucket = archiveOf(file);
|
|
207
|
+
if (!bucket) continue;
|
|
208
|
+
const src = path.join(ctx.projectRoot, file);
|
|
209
|
+
const stat4 = await fs.lstat(src).catch(() => void 0);
|
|
210
|
+
if (!stat4) continue;
|
|
211
|
+
const dst = path.join(
|
|
212
|
+
ctx.storageRoot,
|
|
213
|
+
bucket.newArchiveDir,
|
|
214
|
+
...file.slice(bucket.oldArchiveDir.length + 1).split("/")
|
|
215
|
+
);
|
|
216
|
+
if (!stat4.isFile()) {
|
|
217
|
+
collisions.push(file);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const dstStat = await fs.lstat(dst).catch(() => void 0);
|
|
221
|
+
if (!dstStat) {
|
|
222
|
+
plan.push({ file, src, dst, identical: false });
|
|
223
|
+
} else if (dstStat.isFile() && await sameContent(src, dst)) {
|
|
224
|
+
plan.push({ file, src, dst, identical: true });
|
|
225
|
+
} else {
|
|
226
|
+
collisions.push(path.relative(ctx.projectRoot, dst).split(path.sep).join("/"));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (collisions.length > 0) {
|
|
230
|
+
return {
|
|
231
|
+
...NO_ADOPTION,
|
|
232
|
+
refusal: refusal(
|
|
233
|
+
"collision",
|
|
234
|
+
collisions,
|
|
235
|
+
"cannot move local archive files into planning storage; these paths already exist there with different content (or are not regular files)",
|
|
236
|
+
"Compare and remove one copy, then rerun; nothing was moved."
|
|
237
|
+
)
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const moved = [];
|
|
241
|
+
const skippedIdentical = [];
|
|
242
|
+
for (const item of plan) {
|
|
243
|
+
if (item.identical) {
|
|
244
|
+
await fs.unlink(item.src);
|
|
245
|
+
skippedIdentical.push(item.file);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
await fs.mkdir(path.dirname(item.dst), { recursive: true });
|
|
249
|
+
await fs.copyFile(item.src, item.dst, fs.constants.COPYFILE_EXCL);
|
|
250
|
+
const srcStat = await fs.stat(item.src);
|
|
251
|
+
await fs.utimes(item.dst, srcStat.atime, srcStat.mtime);
|
|
252
|
+
await fs.unlink(item.src);
|
|
253
|
+
moved.push(item.file);
|
|
254
|
+
}
|
|
255
|
+
return { moved, skippedIdentical };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/planning/storage/guards.ts
|
|
259
|
+
var HOLD_FILE_NAME = "planning-storage-hold";
|
|
260
|
+
function planningStorageHoldPath() {
|
|
261
|
+
return path2.join(getStateDir(), HOLD_FILE_NAME);
|
|
262
|
+
}
|
|
263
|
+
function isPlanningStorageHeld() {
|
|
264
|
+
try {
|
|
265
|
+
return fs2.existsSync(planningStorageHoldPath());
|
|
266
|
+
} catch {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function assertNoPlanningStorageHold() {
|
|
271
|
+
const holdPath = planningStorageHoldPath();
|
|
272
|
+
if (!isPlanningStorageHeld()) return;
|
|
273
|
+
throw new Error(
|
|
274
|
+
`planning storage is held by ${holdPath}; a migration is in progress. Remove the hold only when the migration runbook says so.`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
async function assertNoStorageDrift(ctx, opts = {}) {
|
|
278
|
+
const remote = opts.remote ?? ctx.remote ?? "origin";
|
|
279
|
+
const defaultBranch = opts.defaultBranch ?? await resolveDefaultBranch(ctx.projectRoot);
|
|
280
|
+
if (!defaultBranch) return;
|
|
281
|
+
const ref = `${remote}/${defaultBranch}:.build-project/config/buckets.yaml`;
|
|
282
|
+
let raw;
|
|
283
|
+
try {
|
|
284
|
+
const result2 = await execGit(["show", ref], { cwd: ctx.projectRoot });
|
|
285
|
+
if (result2.exitCode !== 0) return;
|
|
286
|
+
raw = yaml2.load(result2.stdout, { schema: yaml2.JSON_SCHEMA });
|
|
287
|
+
} catch {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const remoteConfig = parseStorageConfigLenient(raw, { source: ref });
|
|
291
|
+
if (remoteConfig === null) return;
|
|
292
|
+
const target = `${remote}/${defaultBranch}`;
|
|
293
|
+
if (remoteConfig.backend === ctx.backend) {
|
|
294
|
+
if (ctx.backend === "git-branch" && !opts.allowLegacyBucketFiles) {
|
|
295
|
+
await assertDefaultBranchTracksNoBuckets(ctx, target);
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (remoteConfig.backend === "git-branch") {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`planning storage moved to branch ${remoteConfig.branch} on ${target}; merge ${target} into this branch before writing managed items.`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
throw new Error(
|
|
305
|
+
`planning storage rolled back to the checkout on ${target}; merge ${target} before writing managed items.`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
async function assertDefaultBranchTracksNoBuckets(ctx, target) {
|
|
309
|
+
let files;
|
|
310
|
+
try {
|
|
311
|
+
files = await listLegacyBucketFilesOnRef(ctx, `refs/remotes/${target}`);
|
|
312
|
+
} catch {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (files.length === 0) return;
|
|
316
|
+
const shown = files.slice(0, 10).join(", ");
|
|
317
|
+
const more = files.length > 10 ? ` and ${files.length - 10} more` : "";
|
|
318
|
+
throw new Error(
|
|
319
|
+
`${target} tracks planning bucket files again (${shown}${more}) although planning storage lives on branch ${ctx.branch}; nothing is published until those files are removed from ${target}.`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/planning/storage/publish-transaction.ts
|
|
324
|
+
import { randomUUID } from "crypto";
|
|
325
|
+
import * as fs5 from "fs/promises";
|
|
326
|
+
import * as path4 from "path";
|
|
327
|
+
|
|
328
|
+
// src/planning/storage/materialize.ts
|
|
329
|
+
var CODE_TREE_ENTRY = "src";
|
|
330
|
+
async function materializeUnlocked(ctx, policy) {
|
|
331
|
+
if (ctx.backend !== "git-branch") {
|
|
332
|
+
return { materialized: true, created: false };
|
|
333
|
+
}
|
|
334
|
+
if (!ctx.branch || !ctx.remote) {
|
|
335
|
+
return {
|
|
336
|
+
materialized: false,
|
|
337
|
+
created: false,
|
|
338
|
+
reason: "planning storage backend is git-branch but branch/remote are not configured"
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
const fetchOutcome = await fetchStorageBranch(ctx, policy);
|
|
342
|
+
const ref = trackingRef(ctx);
|
|
343
|
+
const tip = await resolveRevision(ctx.projectRoot, ref);
|
|
344
|
+
if (!await pathExists(ctx.storageRoot)) {
|
|
345
|
+
return await withAdoption(ctx, await createWorktree(ctx, tip, fetchOutcome.reason));
|
|
346
|
+
}
|
|
347
|
+
if (!await isLinkedWorktree(ctx.storageRoot)) {
|
|
348
|
+
return {
|
|
349
|
+
materialized: false,
|
|
350
|
+
created: false,
|
|
351
|
+
reason: `${ctx.storageRoot} exists but is not a linked git worktree; move it aside manually`
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
const headSha = await resolveRevision(ctx.storageRoot, "HEAD");
|
|
355
|
+
const advanced = await advanceHeadWhenClean(ctx, headSha, tip);
|
|
356
|
+
const reason = fetchOutcome.reason ?? advanced.reason;
|
|
357
|
+
return await withAdoption(ctx, {
|
|
358
|
+
materialized: true,
|
|
359
|
+
created: false,
|
|
360
|
+
...advanced.headSha ? { headSha: advanced.headSha } : headSha ? { headSha } : {},
|
|
361
|
+
...advanced.headSha && headSha ? { advancedFrom: headSha } : {},
|
|
362
|
+
...reason ? { reason } : {}
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
async function withAdoption(ctx, result2) {
|
|
366
|
+
if (!result2.materialized) return result2;
|
|
367
|
+
try {
|
|
368
|
+
const adopted = await adoptLocalArchives(ctx);
|
|
369
|
+
const reason = adopted.refusal ? [result2.reason, adopted.refusal.message].filter(Boolean).join("; ") : result2.reason;
|
|
370
|
+
return {
|
|
371
|
+
...result2,
|
|
372
|
+
...adopted.moved.length ? { adoptedFiles: adopted.moved } : {},
|
|
373
|
+
...adopted.refusal ? { adoptionRefusal: adopted.refusal } : {},
|
|
374
|
+
...reason ? { reason } : {}
|
|
375
|
+
};
|
|
376
|
+
} catch (error) {
|
|
377
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
378
|
+
const message = `adopting local archives into planning storage failed: ${detail}. Fix the cause and rerun; files already moved stay in storage.`;
|
|
379
|
+
return {
|
|
380
|
+
...result2,
|
|
381
|
+
adoptionRefusal: { kind: "error", paths: [], message },
|
|
382
|
+
reason: [result2.reason, message].filter(Boolean).join("; ")
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async function advanceHeadWhenClean(ctx, headSha, tip) {
|
|
387
|
+
if (!tip || !headSha || tip === headSha) return {};
|
|
388
|
+
const dirty = (await readPorcelainStatus(ctx.storageRoot)).split("\n").filter(Boolean);
|
|
389
|
+
if (!await isAncestor(ctx.storageRoot, headSha, tip)) return {};
|
|
390
|
+
if (dirty.length > 0) return { reason: staleDirtyTreeReason(tip, dirty) };
|
|
391
|
+
const refusal2 = await refuseUnsuitableTip(ctx, tip);
|
|
392
|
+
if (refusal2) return { reason: refusal2 };
|
|
393
|
+
const checkout = await execGit(["checkout", "--detach", "--no-overwrite-ignore", tip], {
|
|
394
|
+
cwd: ctx.storageRoot
|
|
395
|
+
});
|
|
396
|
+
if (checkout.exitCode === 0) return { headSha: tip };
|
|
397
|
+
const detail = (checkout.stderr || checkout.stdout).trim().replace(/\s+/g, " ");
|
|
398
|
+
return {
|
|
399
|
+
reason: `cannot advance planning storage to ${tip}; preserving the current worktree: ${detail}. Resolve conflicting local files before retrying.`
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function staleDirtyTreeReason(tip, porcelainLines) {
|
|
403
|
+
const paths = porcelainLines.map(parsePorcelainLinePath);
|
|
404
|
+
const shown = paths.slice(0, 3).join(", ");
|
|
405
|
+
const more = paths.length > 3 ? ` and ${paths.length - 3} more` : "";
|
|
406
|
+
return `serving an older tree: uncommitted changes in the storage worktree (${shown}${more}) keep it from advancing to ${tip}. Finish and publish those edits, or discard them.`;
|
|
407
|
+
}
|
|
408
|
+
async function createWorktree(ctx, tip, fetchReason) {
|
|
409
|
+
if (!tip) {
|
|
410
|
+
return {
|
|
411
|
+
materialized: false,
|
|
412
|
+
created: false,
|
|
413
|
+
reason: fetchReason ?? `planning storage is unavailable: ${trackingRef(ctx)} does not exist and could not be fetched`
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
const refusal2 = await refuseUnsuitableTip(ctx, tip);
|
|
417
|
+
if (refusal2) return { materialized: false, created: false, reason: refusal2 };
|
|
418
|
+
const added = await execGit(["worktree", "add", "--detach", ctx.storageRoot, tip], {
|
|
419
|
+
cwd: ctx.projectRoot
|
|
420
|
+
});
|
|
421
|
+
if (added.exitCode !== 0) {
|
|
422
|
+
return {
|
|
423
|
+
materialized: false,
|
|
424
|
+
created: false,
|
|
425
|
+
reason: `cannot create the planning storage worktree at ${ctx.storageRoot}: ${added.stderr.trim()}`
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
return { materialized: true, created: true, headSha: tip };
|
|
429
|
+
}
|
|
430
|
+
async function refuseUnsuitableTip(ctx, tip) {
|
|
431
|
+
const seamRoot = ctx.seam?.workRootSha;
|
|
432
|
+
if (seamRoot && tip !== seamRoot && !await isAncestor(ctx.projectRoot, seamRoot, tip)) {
|
|
433
|
+
return `refusing to materialize ${tip}: it does not descend from the storage seam root ${seamRoot}`;
|
|
434
|
+
}
|
|
435
|
+
const entries = await listTopLevelTreeEntries(ctx.projectRoot, tip);
|
|
436
|
+
if (entries.includes(CODE_TREE_ENTRY)) {
|
|
437
|
+
return `refusing to materialize ${tip}: its tree contains a top-level ${CODE_TREE_ENTRY}/ entry, so it is a code commit`;
|
|
438
|
+
}
|
|
439
|
+
return void 0;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/planning/storage/pending-refs.ts
|
|
443
|
+
import { randomBytes } from "crypto";
|
|
444
|
+
var PENDING_REF_PREFIX = "refs/build/pending/";
|
|
445
|
+
function generateOpId(now = /* @__PURE__ */ new Date()) {
|
|
446
|
+
const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
447
|
+
return `${stamp}-${randomBytes(4).toString("hex")}`;
|
|
448
|
+
}
|
|
449
|
+
function pendingRefFor(opId) {
|
|
450
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(opId) || opId.includes("..") || opId.endsWith(".lock")) {
|
|
451
|
+
throw new Error(`Invalid planning storage operation id: ${opId}`);
|
|
452
|
+
}
|
|
453
|
+
return `${PENDING_REF_PREFIX}${opId}`;
|
|
454
|
+
}
|
|
455
|
+
async function listPendingRefs(projectRoot) {
|
|
456
|
+
const result2 = await execGit(
|
|
457
|
+
["for-each-ref", "--format=%(refname) %(objectname)", "--sort=refname", PENDING_REF_PREFIX],
|
|
458
|
+
{ cwd: projectRoot }
|
|
459
|
+
);
|
|
460
|
+
if (result2.exitCode !== 0) {
|
|
461
|
+
throw new Error(
|
|
462
|
+
`git for-each-ref ${PENDING_REF_PREFIX} failed (exit ${result2.exitCode}): ${result2.stderr.trim()}`
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
return result2.stdout.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
466
|
+
const [ref, sha] = line.split(/\s+/);
|
|
467
|
+
return { ref: ref ?? "", sha: sha ?? "" };
|
|
468
|
+
}).filter((entry) => entry.ref.length > 0 && entry.sha.length > 0);
|
|
469
|
+
}
|
|
470
|
+
async function writePendingRef(projectRoot, ref, sha, expectedSha) {
|
|
471
|
+
const args = ["update-ref", "--create-reflog", ref, sha];
|
|
472
|
+
if (expectedSha !== void 0) args.push(expectedSha);
|
|
473
|
+
const result2 = await execGit(args, { cwd: projectRoot });
|
|
474
|
+
if (result2.exitCode !== 0) {
|
|
475
|
+
throw new Error(`Cannot record planning storage candidate ${ref}: ${result2.stderr.trim()}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async function pendingRefHistory(projectRoot, entry) {
|
|
479
|
+
const result2 = await execGit(["reflog", "show", "--format=%H", entry.ref, "--"], {
|
|
480
|
+
cwd: projectRoot
|
|
481
|
+
});
|
|
482
|
+
const logged = result2.exitCode === 0 ? result2.stdout.split("\n").map((line) => line.trim()).filter(Boolean) : [];
|
|
483
|
+
if (logged.length > 0) {
|
|
484
|
+
const before = await execGit(
|
|
485
|
+
["rev-parse", "--verify", "--quiet", `${entry.ref}@{${logged.length}}`],
|
|
486
|
+
{ cwd: projectRoot }
|
|
487
|
+
);
|
|
488
|
+
if (before.exitCode === 0 && before.stdout.trim()) logged.push(before.stdout.trim());
|
|
489
|
+
}
|
|
490
|
+
return [.../* @__PURE__ */ new Set([entry.sha, ...logged])];
|
|
491
|
+
}
|
|
492
|
+
async function deletePendingRef(projectRoot, ref, expectedSha) {
|
|
493
|
+
const result2 = await execGit(["update-ref", "-d", ref, expectedSha], { cwd: projectRoot });
|
|
494
|
+
if (result2.exitCode !== 0) {
|
|
495
|
+
throw new Error(`Cannot delete planning storage candidate ${ref}: ${result2.stderr.trim()}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/planning/storage/pending-order.ts
|
|
500
|
+
function dependencyClosure(ref, parents) {
|
|
501
|
+
const closure = /* @__PURE__ */ new Set();
|
|
502
|
+
const queue = [ref];
|
|
503
|
+
for (let next = queue.pop(); next !== void 0; next = queue.pop()) {
|
|
504
|
+
if (closure.has(next)) continue;
|
|
505
|
+
closure.add(next);
|
|
506
|
+
queue.push(...parents.get(next) ?? []);
|
|
507
|
+
}
|
|
508
|
+
return closure;
|
|
509
|
+
}
|
|
510
|
+
function closureOf(order, roots) {
|
|
511
|
+
const selected = new Set(roots.flatMap((root) => [...dependencyClosure(root, order.parents)]));
|
|
512
|
+
return {
|
|
513
|
+
ordered: order.ordered.filter((entry) => selected.has(entry.ref)),
|
|
514
|
+
parents: order.parents
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
async function orderByDependency(projectRoot, refs) {
|
|
518
|
+
const histories = /* @__PURE__ */ new Map();
|
|
519
|
+
for (const entry of refs) histories.set(entry.ref, await pendingRefHistory(projectRoot, entry));
|
|
520
|
+
const parents = /* @__PURE__ */ new Map();
|
|
521
|
+
for (const child of refs) {
|
|
522
|
+
const found = /* @__PURE__ */ new Set();
|
|
523
|
+
for (const other of refs) {
|
|
524
|
+
if (other.ref === child.ref) continue;
|
|
525
|
+
for (const sha of histories.get(other.ref) ?? []) {
|
|
526
|
+
if (sha !== child.sha && await isAncestor(projectRoot, sha, child.sha)) {
|
|
527
|
+
found.add(other.ref);
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
parents.set(child.ref, found);
|
|
533
|
+
}
|
|
534
|
+
const remaining = [...refs];
|
|
535
|
+
const ordered = [];
|
|
536
|
+
while (remaining.length > 0) {
|
|
537
|
+
const waiting = new Set(remaining.map((entry) => entry.ref));
|
|
538
|
+
const index = remaining.findIndex(
|
|
539
|
+
(entry) => ![...parents.get(entry.ref) ?? []].some((parent) => waiting.has(parent))
|
|
540
|
+
);
|
|
541
|
+
ordered.push(...remaining.splice(Math.max(index, 0), 1));
|
|
542
|
+
}
|
|
543
|
+
return { ordered, parents };
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/planning/storage/publication-owner.ts
|
|
547
|
+
import { createHash } from "crypto";
|
|
548
|
+
import * as fs4 from "fs/promises";
|
|
549
|
+
|
|
550
|
+
// src/planning/storage/publish-staging.ts
|
|
551
|
+
import * as fs3 from "fs/promises";
|
|
552
|
+
import * as path3 from "path";
|
|
553
|
+
var BRANCH_ROOT_FILES = ["README.md", ".gitignore"];
|
|
554
|
+
var PATHSPEC_CHUNK = 100;
|
|
555
|
+
var PlanningStoragePublishError = class extends Error {
|
|
556
|
+
paths;
|
|
557
|
+
constructor(message, paths = []) {
|
|
558
|
+
super(message);
|
|
559
|
+
this.name = "PlanningStoragePublishError";
|
|
560
|
+
this.paths = paths;
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
function sharedBuckets(ctx) {
|
|
564
|
+
const shared = new Set(SHARED_BUCKET_NAMES);
|
|
565
|
+
return getBuckets(ctx.projectRoot).filter(
|
|
566
|
+
(bucket) => shared.has(bucket.name)
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
function asDirPrefix(relativePath) {
|
|
570
|
+
return relativePath.endsWith("/") ? relativePath : `${relativePath}/`;
|
|
571
|
+
}
|
|
572
|
+
function sharedBucketDirs(ctx) {
|
|
573
|
+
return sharedBuckets(ctx).map((bucket) => normalizeRelativePath(bucket.path)).map(asDirPrefix);
|
|
574
|
+
}
|
|
575
|
+
function publishableBucketDirs(ctx) {
|
|
576
|
+
const dirs = new Set(sharedBucketDirs(ctx));
|
|
577
|
+
for (const bucket of sharedBuckets(ctx)) {
|
|
578
|
+
if (bucket.archivePath) dirs.add(asDirPrefix(normalizeRelativePath(bucket.archivePath)));
|
|
579
|
+
}
|
|
580
|
+
return [...dirs];
|
|
581
|
+
}
|
|
582
|
+
function unpublishedArchiveDirs(ctx) {
|
|
583
|
+
return sharedBuckets(ctx).filter((bucket) => bucket.archivePublished === false).map((bucket) => normalizeRelativePath(bucket.archivePath)).map(asDirPrefix);
|
|
584
|
+
}
|
|
585
|
+
function normalizeRelativePath(input) {
|
|
586
|
+
const posix3 = input.replace(/\\/g, "/").trim();
|
|
587
|
+
if (path3.isAbsolute(posix3) || /^[A-Za-z]:/.test(posix3)) {
|
|
588
|
+
throw new PlanningStoragePublishError(
|
|
589
|
+
`Planning storage touched paths must be storage-root relative: ${input}`,
|
|
590
|
+
[input]
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
const normalized = path3.posix.normalize(posix3).replace(/^\.\//, "");
|
|
594
|
+
if (normalized === ".." || normalized.startsWith("../")) {
|
|
595
|
+
throw new PlanningStoragePublishError(
|
|
596
|
+
`Planning storage touched path escapes the storage root: ${input}`,
|
|
597
|
+
[input]
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
return normalized;
|
|
601
|
+
}
|
|
602
|
+
function isAllowlisted(relativePath, bucketDirs) {
|
|
603
|
+
if (BRANCH_ROOT_FILES.includes(relativePath)) return true;
|
|
604
|
+
return bucketDirs.some((dir) => relativePath.startsWith(dir));
|
|
605
|
+
}
|
|
606
|
+
async function filterIgnoredPaths(storageRoot, relativePaths) {
|
|
607
|
+
if (relativePaths.length === 0) return /* @__PURE__ */ new Set();
|
|
608
|
+
const result2 = await execGit(["check-ignore", "-z", "--stdin"], {
|
|
609
|
+
cwd: storageRoot,
|
|
610
|
+
input: `${relativePaths.join("\0")}\0`
|
|
611
|
+
});
|
|
612
|
+
if (result2.exitCode === 1) return /* @__PURE__ */ new Set();
|
|
613
|
+
if (result2.exitCode !== 0) {
|
|
614
|
+
throw new PlanningStoragePublishError(
|
|
615
|
+
`Cannot classify planning storage paths against the branch ignore rules: ${result2.stderr.trim()}`
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
return new Set(result2.stdout.split("\0").filter(Boolean));
|
|
619
|
+
}
|
|
620
|
+
async function describeIgnoreSources(storageRoot, relativePaths) {
|
|
621
|
+
const result2 = await execGit(["check-ignore", "-v", "--stdin"], {
|
|
622
|
+
cwd: storageRoot,
|
|
623
|
+
input: `${relativePaths.join("\n")}
|
|
624
|
+
`
|
|
625
|
+
});
|
|
626
|
+
if (result2.exitCode !== 0 && result2.exitCode !== 1) return [...relativePaths];
|
|
627
|
+
const described = result2.stdout.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
628
|
+
const [rule, pathSpec] = line.split(" ");
|
|
629
|
+
const source = rule?.split(":")[0]?.trim() ?? "an ignore rule";
|
|
630
|
+
return `${pathSpec?.trim() ?? ""} (ignored by ${source})`;
|
|
631
|
+
});
|
|
632
|
+
return described.length > 0 ? described : [...relativePaths];
|
|
633
|
+
}
|
|
634
|
+
async function dropNeverPublishedAbsentPaths(storageRoot, relativePaths) {
|
|
635
|
+
const absent = [];
|
|
636
|
+
for (const relativePath of relativePaths) {
|
|
637
|
+
try {
|
|
638
|
+
await fs3.lstat(path3.join(storageRoot, relativePath));
|
|
639
|
+
} catch {
|
|
640
|
+
absent.push(relativePath);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
if (absent.length === 0) return [...relativePaths];
|
|
644
|
+
const tracked = await snapshotIndexEntries(storageRoot, absent);
|
|
645
|
+
const drop = new Set(absent.filter((relativePath) => !tracked.has(relativePath)));
|
|
646
|
+
return relativePaths.filter((relativePath) => !drop.has(relativePath));
|
|
647
|
+
}
|
|
648
|
+
async function planAllowlist(ctx, touchedPaths) {
|
|
649
|
+
const bucketDirs = publishableBucketDirs(ctx);
|
|
650
|
+
const unpublishedArchives = unpublishedArchiveDirs(ctx);
|
|
651
|
+
const declared = new Set(touchedPaths.map(normalizeRelativePath));
|
|
652
|
+
const allowlisted = [...declared].filter(
|
|
653
|
+
(relativePath) => isAllowlisted(relativePath, bucketDirs)
|
|
654
|
+
);
|
|
655
|
+
const localByPolicy = allowlisted.filter(
|
|
656
|
+
(relativePath) => unpublishedArchives.some((dir) => relativePath.startsWith(dir))
|
|
657
|
+
);
|
|
658
|
+
const publishable = await dropNeverPublishedAbsentPaths(
|
|
659
|
+
ctx.storageRoot,
|
|
660
|
+
allowlisted.filter((relativePath) => !localByPolicy.includes(relativePath))
|
|
661
|
+
);
|
|
662
|
+
const ignored = await filterIgnoredPaths(ctx.storageRoot, publishable);
|
|
663
|
+
if (ignored.size > 0) {
|
|
664
|
+
const described = await describeIgnoreSources(ctx.storageRoot, [...ignored].sort());
|
|
665
|
+
throw new PlanningStoragePublishError(
|
|
666
|
+
`Planning storage publish aborted: ${described.join(", ")} would not be committed on the storage branch; remove the ignore rule or mark the bucket archive unpublished`,
|
|
667
|
+
[...ignored].sort()
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
await assertNoForeignDirtyPath(ctx.storageRoot, new Set(allowlisted));
|
|
671
|
+
return { staged: publishable, localByPolicy };
|
|
672
|
+
}
|
|
673
|
+
async function assertNoForeignDirtyPath(storageRoot, owned) {
|
|
674
|
+
const status = await readPorcelainStatus(storageRoot, { untracked: true });
|
|
675
|
+
for (const entry of parseGitStatus(status)) {
|
|
676
|
+
if (entry.indexStatus === " " || entry.indexStatus === "?") continue;
|
|
677
|
+
for (const candidate of [entry.path, entry.originalPath]) {
|
|
678
|
+
if (!candidate) continue;
|
|
679
|
+
const relativePath = normalizeRelativePath(candidate);
|
|
680
|
+
if (!owned.has(relativePath)) {
|
|
681
|
+
throw new PlanningStoragePublishError(
|
|
682
|
+
`Planning storage publish aborted: ${relativePath} is modified in ${storageRoot} but was not part of this operation`,
|
|
683
|
+
[relativePath]
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
async function stageAllowlist(storageRoot, relativePaths) {
|
|
690
|
+
for (let index = 0; index < relativePaths.length; index += PATHSPEC_CHUNK) {
|
|
691
|
+
const chunk = relativePaths.slice(index, index + PATHSPEC_CHUNK);
|
|
692
|
+
const result2 = await execGit(["add", "-A", "--", ...chunk], { cwd: storageRoot });
|
|
693
|
+
if (result2.exitCode !== 0) {
|
|
694
|
+
throw new PlanningStoragePublishError(
|
|
695
|
+
`Cannot stage planning storage paths: ${result2.stderr.trim()}`,
|
|
696
|
+
chunk
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
async function snapshotIndexEntries(storageRoot, relativePaths) {
|
|
702
|
+
const entries = /* @__PURE__ */ new Map();
|
|
703
|
+
for (let index = 0; index < relativePaths.length; index += PATHSPEC_CHUNK) {
|
|
704
|
+
const chunk = relativePaths.slice(index, index + PATHSPEC_CHUNK);
|
|
705
|
+
const result2 = await execGit(["ls-files", "-s", "-z", "--", ...chunk], { cwd: storageRoot });
|
|
706
|
+
if (result2.exitCode !== 0) {
|
|
707
|
+
throw new PlanningStoragePublishError(
|
|
708
|
+
`Cannot read the planning storage index: ${result2.stderr.trim()}`,
|
|
709
|
+
chunk
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
for (const record of result2.stdout.split("\0")) {
|
|
713
|
+
const match = record.match(/^(\d{6}) ([0-9a-f]+) 0\t(.+)$/s);
|
|
714
|
+
if (match?.[1] && match[2] && match[3]) entries.set(match[3], `${match[1]} ${match[2]}`);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return entries;
|
|
718
|
+
}
|
|
719
|
+
async function restoreIndexEntries(storageRoot, relativePaths, prior) {
|
|
720
|
+
const current = await snapshotIndexEntries(storageRoot, relativePaths);
|
|
721
|
+
const added = [...current.keys()].filter((entryPath) => !prior.has(entryPath));
|
|
722
|
+
const changed = [...prior].filter(([entryPath, entry]) => current.get(entryPath) !== entry);
|
|
723
|
+
if (added.length > 0) {
|
|
724
|
+
await execGit(["update-index", "--force-remove", "-z", "--stdin"], {
|
|
725
|
+
cwd: storageRoot,
|
|
726
|
+
input: `${added.join("\0")}\0`
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
if (changed.length > 0) {
|
|
730
|
+
await execGit(["update-index", "-z", "--index-info"], {
|
|
731
|
+
cwd: storageRoot,
|
|
732
|
+
input: changed.map(([entryPath, entry]) => `${entry} ${entryPath}\0`).join("")
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
async function withIndexRestoredOnFailure(storageRoot, relativePaths, prior, step) {
|
|
737
|
+
const headBefore = await resolveRevision(storageRoot, "HEAD");
|
|
738
|
+
try {
|
|
739
|
+
return await step();
|
|
740
|
+
} catch (error) {
|
|
741
|
+
if (await resolveRevision(storageRoot, "HEAD") === headBefore) {
|
|
742
|
+
await restoreIndexEntries(storageRoot, relativePaths, prior).catch(() => void 0);
|
|
743
|
+
}
|
|
744
|
+
throw error;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
async function hasStagedChanges(storageRoot) {
|
|
748
|
+
const result2 = await execGit(["diff", "--cached", "--quiet"], { cwd: storageRoot });
|
|
749
|
+
return result2.exitCode !== 0;
|
|
750
|
+
}
|
|
751
|
+
async function resolveEffectiveHooksPath(projectRoot) {
|
|
752
|
+
const configured = await execGit(["config", "--get", "core.hooksPath"], { cwd: projectRoot });
|
|
753
|
+
if (configured.exitCode !== 0 && configured.exitCode !== 1) {
|
|
754
|
+
throw new PlanningStoragePublishError(
|
|
755
|
+
`Refusing to publish planning storage: cannot read core.hooksPath in ${projectRoot}: ${configured.stderr.trim()}`
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
const value = configured.exitCode === 0 ? configured.stdout.trim() : "";
|
|
759
|
+
if (value) {
|
|
760
|
+
return path3.isAbsolute(value) ? value : path3.resolve(projectRoot, value);
|
|
761
|
+
}
|
|
762
|
+
return path3.join(await resolveCommonGitDir(projectRoot), "hooks");
|
|
763
|
+
}
|
|
764
|
+
async function assertPreCommitHookRunnable(hooksPath, policy = DEFAULT_PRE_COMMIT_POLICY) {
|
|
765
|
+
const hookPath = path3.join(hooksPath, "pre-commit");
|
|
766
|
+
let entry;
|
|
767
|
+
try {
|
|
768
|
+
entry = await fs3.lstat(hookPath);
|
|
769
|
+
} catch (error) {
|
|
770
|
+
const code = error?.code;
|
|
771
|
+
if (code === "ENOENT") {
|
|
772
|
+
if (policy === "optional") return;
|
|
773
|
+
throw new PlanningStoragePublishError(
|
|
774
|
+
`Refusing to publish planning storage: no pre-commit hook at ${hookPath} (storage.preCommitPolicy: required)`,
|
|
775
|
+
[hookPath]
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
throw new PlanningStoragePublishError(
|
|
779
|
+
`Refusing to publish planning storage: cannot inspect pre-commit hook at ${hookPath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
780
|
+
[hookPath]
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
const stat4 = entry.isSymbolicLink() ? await fs3.stat(hookPath).catch(() => void 0) : entry;
|
|
784
|
+
if (!stat4?.isFile()) {
|
|
785
|
+
throw new PlanningStoragePublishError(
|
|
786
|
+
`Refusing to publish planning storage: pre-commit hook at ${hookPath} is not a regular file git can run`,
|
|
787
|
+
[hookPath]
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
if ((stat4.mode & 73) === 0) {
|
|
791
|
+
throw new PlanningStoragePublishError(
|
|
792
|
+
`Refusing to publish planning storage: pre-commit hook at ${hookPath} is not executable, so git would skip it`,
|
|
793
|
+
[hookPath]
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
async function commitCandidate(ctx, message, backlogGatePassed = false) {
|
|
798
|
+
const policy = resolvePreCommitPolicy(loadStorageConfig(ctx.projectRoot));
|
|
799
|
+
const hooksPath = await resolveEffectiveHooksPath(ctx.projectRoot);
|
|
800
|
+
await assertPreCommitHookRunnable(hooksPath, policy);
|
|
801
|
+
const commit = await execGit(["-c", `core.hooksPath=${hooksPath}`, "commit", "-m", message], {
|
|
802
|
+
cwd: ctx.storageRoot,
|
|
803
|
+
timeoutMs: GIT_HOOK_COMMIT_TIMEOUT_MS,
|
|
804
|
+
...backlogGatePassed ? { env: { BUILD_BACKLOG_SYNC: "1" } } : {}
|
|
805
|
+
});
|
|
806
|
+
if (commit.exitCode !== 0) {
|
|
807
|
+
throw new PlanningStoragePublishError(
|
|
808
|
+
`Planning storage commit refused (exit ${commit.exitCode}): ${commit.stderr.trim() || commit.stdout.trim()}`
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
const head = await execGit(["rev-parse", "HEAD"], { cwd: ctx.storageRoot });
|
|
812
|
+
const sha = head.stdout.trim();
|
|
813
|
+
if (head.exitCode !== 0 || !sha) {
|
|
814
|
+
throw new PlanningStoragePublishError("Planning storage commit produced no resolvable HEAD");
|
|
815
|
+
}
|
|
816
|
+
return { sha, hooksPath };
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// src/planning/storage/publication-owner.ts
|
|
820
|
+
var OWNER_PREFIX = "planning-storage:";
|
|
821
|
+
var PUSH_DETAIL = /^planning storage push ([0-9a-f]{40}(?:[0-9a-f]{24})?)(?: held in (.+))?$/;
|
|
822
|
+
var retainedTurns = /* @__PURE__ */ new Map();
|
|
823
|
+
function hash(value, length) {
|
|
824
|
+
return createHash("sha256").update(value).digest("hex").slice(0, length);
|
|
825
|
+
}
|
|
826
|
+
async function resolveStoragePublicationOwner(ctx) {
|
|
827
|
+
const remote = ctx.remote ?? "origin";
|
|
828
|
+
const urlResult = await execGit(["remote", "get-url", "--push", "--all", remote], {
|
|
829
|
+
cwd: ctx.projectRoot
|
|
830
|
+
});
|
|
831
|
+
if (urlResult.exitCode !== 0) return void 0;
|
|
832
|
+
const urls = urlResult.stdout.trim().split(/\r?\n/).filter(Boolean);
|
|
833
|
+
const repositories = urls.map(repositoryFromGitHubRemoteUrl);
|
|
834
|
+
if (urls.length > 1 && repositories.some(Boolean)) {
|
|
835
|
+
throw new PlanningStoragePublishError(
|
|
836
|
+
"Planning storage publication requires one GitHub push destination"
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
const repository = repositories[0]?.toLowerCase();
|
|
840
|
+
if (!repository) return void 0;
|
|
841
|
+
const branch = ctx.branch ?? "";
|
|
842
|
+
const commonGitDir = await resolveCommonGitDir(ctx.projectRoot);
|
|
843
|
+
return {
|
|
844
|
+
repository,
|
|
845
|
+
branch,
|
|
846
|
+
ownerId: `${OWNER_PREFIX}clone:${hash(`${commonGitDir}\0${repository}\0${branch}`, 24)}`,
|
|
847
|
+
legacyOwnerId: `${OWNER_PREFIX}${hash(`${ctx.storageRoot}\0${ctx.branch}`, 16)}`,
|
|
848
|
+
commonGitDir
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
function parseRetainedPush(detail) {
|
|
852
|
+
const match = PUSH_DETAIL.exec(detail?.trim() ?? "");
|
|
853
|
+
if (!match) return {};
|
|
854
|
+
return { sha: match[1], ...match[2] ? { holdingClone: match[2] } : {} };
|
|
855
|
+
}
|
|
856
|
+
function ownerProcess(record) {
|
|
857
|
+
try {
|
|
858
|
+
const state = processOwnershipState(record.ownerPid, record.ownerPidIdentity, record.claimedAt);
|
|
859
|
+
return state === "owned" ? "live" : state === "reclaimable" ? "dead" : "unknown";
|
|
860
|
+
} catch {
|
|
861
|
+
return "unknown";
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
async function hasCommit(projectRoot, sha) {
|
|
865
|
+
const probe = await execGit(["cat-file", "-e", `${sha}^{commit}`], { cwd: projectRoot });
|
|
866
|
+
return probe.exitCode === 0;
|
|
867
|
+
}
|
|
868
|
+
async function describeHoldingClone(holdingClone) {
|
|
869
|
+
if (!holdingClone) return "an unrecorded clone (the record predates holding-clone metadata)";
|
|
870
|
+
const present = await fs4.stat(holdingClone).then(
|
|
871
|
+
() => true,
|
|
872
|
+
(error) => error && typeof error === "object" && "code" in error && error.code === "ENOENT" ? false : void 0
|
|
873
|
+
);
|
|
874
|
+
if (present === false) return `${holdingClone} (not present on this machine)`;
|
|
875
|
+
return holdingClone;
|
|
876
|
+
}
|
|
877
|
+
async function classifyTurn(ctx, owner, record) {
|
|
878
|
+
const holder = record.ownerId === owner.ownerId ? "this-clone" : record.ownerId === owner.legacyOwnerId ? "this-checkout-legacy" : "other";
|
|
879
|
+
const ownerState = ownerProcess(record);
|
|
880
|
+
const { sha, holdingClone } = parseRetainedPush(record.detail);
|
|
881
|
+
const available = sha ? await hasCommit(ctx.projectRoot, sha) : void 0;
|
|
882
|
+
const fields = {
|
|
883
|
+
token: record.token,
|
|
884
|
+
ownerId: record.ownerId,
|
|
885
|
+
holder,
|
|
886
|
+
ownerProcess: ownerState,
|
|
887
|
+
recoveryRequired: record.recoveryRequired,
|
|
888
|
+
...sha ? { retainedPush: sha } : {},
|
|
889
|
+
...holdingClone ? { holdingClone } : {},
|
|
890
|
+
...available === void 0 ? {} : { retainedPushAvailable: available }
|
|
891
|
+
};
|
|
892
|
+
const retained = record.recoveryRequired ? ` with an unresolved push${sha ? ` of ${sha}` : ""}` : "";
|
|
893
|
+
if (ownerState !== "dead") {
|
|
894
|
+
const evidence = ownerState === "live" ? "running" : "alive but unjudgeable";
|
|
895
|
+
return {
|
|
896
|
+
disposition: "active",
|
|
897
|
+
explanation: `held by ${record.ownerId} (process ${record.ownerPid}, ${evidence})${retained}; publication waits for it`,
|
|
898
|
+
...fields
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
const legacyHere = holder === "this-checkout-legacy" && available === true;
|
|
902
|
+
if (!record.recoveryRequired || holder === "this-clone" || legacyHere) {
|
|
903
|
+
return {
|
|
904
|
+
disposition: "recoverable",
|
|
905
|
+
explanation: `left by exited process ${record.ownerPid}${retained}; the next publish from this clone claims it`,
|
|
906
|
+
...fields
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
const where = await describeHoldingClone(holdingClone);
|
|
910
|
+
const objects = sha ? available ? "this clone has its commit" : "this clone does not have its commit" : "the record names no commit, so its outcome cannot be established";
|
|
911
|
+
return {
|
|
912
|
+
disposition: "stranded",
|
|
913
|
+
explanation: `${record.ownerId} left an unresolved push${sha ? ` of ${sha}` : ""} held in ${where} behind exited process ${record.ownerPid}; ${objects}. Publish it from the holding clone with: build plan storage sync, or settle it from remote evidence with: build plan storage sync --recover-turn ${record.token}`,
|
|
914
|
+
...fields
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
async function inspectStorageTurn(ctx) {
|
|
918
|
+
let owner;
|
|
919
|
+
let record;
|
|
920
|
+
try {
|
|
921
|
+
owner = await resolveStoragePublicationOwner(ctx);
|
|
922
|
+
if (!owner) return void 0;
|
|
923
|
+
record = await readPublicationTurn({
|
|
924
|
+
repository: owner.repository,
|
|
925
|
+
targetBranch: owner.branch
|
|
926
|
+
});
|
|
927
|
+
} catch (error) {
|
|
928
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
929
|
+
return { disposition: "unreadable", explanation: reason.split("\n")[0] ?? reason };
|
|
930
|
+
}
|
|
931
|
+
return record ? await classifyTurn(ctx, owner, record) : void 0;
|
|
932
|
+
}
|
|
933
|
+
async function acquireStorageTurn(ctx) {
|
|
934
|
+
const owner = await resolveStoragePublicationOwner(ctx);
|
|
935
|
+
if (!owner) return void 0;
|
|
936
|
+
const target = { repository: owner.repository, targetBranch: owner.branch };
|
|
937
|
+
const retainKey = `${owner.repository}\0${owner.branch}\0${owner.commonGitDir}`;
|
|
938
|
+
const retained = retainedTurns.get(retainKey);
|
|
939
|
+
if (retained) {
|
|
940
|
+
retainedTurns.delete(retainKey);
|
|
941
|
+
const stillOwned = await retained.assertOwned().then(
|
|
942
|
+
() => true,
|
|
943
|
+
() => false
|
|
944
|
+
);
|
|
945
|
+
if (stillOwned) return retained;
|
|
946
|
+
}
|
|
947
|
+
const current = await readPublicationTurn(target).catch(() => void 0);
|
|
948
|
+
const status = current ? await classifyTurn(ctx, owner, current) : void 0;
|
|
949
|
+
if (status?.disposition === "stranded") {
|
|
950
|
+
throw new PlanningStoragePublishError(status.explanation);
|
|
951
|
+
}
|
|
952
|
+
const aliasToken = status?.disposition === "recoverable" && status.holder === "this-checkout-legacy" ? current?.token : void 0;
|
|
953
|
+
const handle = await acquirePublicationTurn({
|
|
954
|
+
...target,
|
|
955
|
+
ownerId: owner.ownerId,
|
|
956
|
+
recover: true,
|
|
957
|
+
adopt: (record) => aliasToken !== void 0 && record.token === aliasToken
|
|
958
|
+
});
|
|
959
|
+
let unresolved = current?.recoveryRequired === true;
|
|
960
|
+
const turn = {
|
|
961
|
+
...handle,
|
|
962
|
+
retainPush: async (sha) => {
|
|
963
|
+
await handle.requireRecovery(`planning storage push ${sha} held in ${owner.commonGitDir}`);
|
|
964
|
+
unresolved = true;
|
|
965
|
+
},
|
|
966
|
+
clearRecovery: async () => {
|
|
967
|
+
await handle.clearRecovery();
|
|
968
|
+
unresolved = false;
|
|
969
|
+
},
|
|
970
|
+
settle: async () => {
|
|
971
|
+
if (unresolved) {
|
|
972
|
+
retainedTurns.set(retainKey, turn);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
await handle.release();
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
return turn;
|
|
979
|
+
}
|
|
980
|
+
async function fetchSplitFromPush(ctx, owner) {
|
|
981
|
+
const remote = ctx.remote ?? "origin";
|
|
982
|
+
const urlResult = await execGit(["remote", "get-url", "--all", remote], {
|
|
983
|
+
cwd: ctx.projectRoot
|
|
984
|
+
});
|
|
985
|
+
if (urlResult.exitCode !== 0) return `cannot read the fetch URL of ${remote}`;
|
|
986
|
+
const urls = urlResult.stdout.trim().split(/\r?\n/).filter(Boolean);
|
|
987
|
+
const elsewhere = urls.some(
|
|
988
|
+
(url) => repositoryFromGitHubRemoteUrl(url)?.toLowerCase() !== owner.repository
|
|
989
|
+
);
|
|
990
|
+
if (urls.length > 0 && !elsewhere) return void 0;
|
|
991
|
+
return `${remote} fetches from a different repository than its push destination ${owner.repository}, so a fetch cannot prove what the push did`;
|
|
992
|
+
}
|
|
993
|
+
async function retainedPushOutcome(ctx, owner, sha) {
|
|
994
|
+
const cwd = ctx.projectRoot;
|
|
995
|
+
const split = await fetchSplitFromPush(ctx, owner);
|
|
996
|
+
if (split) return { state: "unknown", reason: split };
|
|
997
|
+
const fetched = await fetchStorageBranch(ctx, "always");
|
|
998
|
+
if (!fetched.fetched) {
|
|
999
|
+
const reason = fetched.reason ?? "the fetch was skipped";
|
|
1000
|
+
return { state: "unknown", reason: `fresh remote evidence unavailable: ${reason}` };
|
|
1001
|
+
}
|
|
1002
|
+
const tip = await resolveRevision(cwd, trackingRef(ctx));
|
|
1003
|
+
if (!tip) return { state: "unknown", reason: `${trackingRef(ctx)} does not resolve` };
|
|
1004
|
+
if (tip === sha) return { state: "present" };
|
|
1005
|
+
if (!await hasCommit(cwd, sha)) {
|
|
1006
|
+
const shallow = await execGit(["rev-parse", "--is-shallow-repository"], { cwd });
|
|
1007
|
+
const promisor = await execGit(
|
|
1008
|
+
["config", "--get", `remote.${ctx.remote ?? "origin"}.promisor`],
|
|
1009
|
+
{ cwd }
|
|
1010
|
+
);
|
|
1011
|
+
if (shallow.exitCode !== 0 || shallow.stdout.trim() !== "false" || promisor.stdout.trim()) {
|
|
1012
|
+
return {
|
|
1013
|
+
state: "unknown",
|
|
1014
|
+
reason: "this clone is shallow or partial, so a missing commit proves nothing"
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
return { state: "absent" };
|
|
1018
|
+
}
|
|
1019
|
+
const ancestry = await execGit(["merge-base", "--is-ancestor", sha, tip], { cwd });
|
|
1020
|
+
if (ancestry.exitCode === 0) return { state: "present" };
|
|
1021
|
+
if (ancestry.exitCode === 1) return { state: "absent" };
|
|
1022
|
+
return { state: "unknown", reason: `cannot compare ${sha} with ${tip}` };
|
|
1023
|
+
}
|
|
1024
|
+
async function recoverRetainedTurn(ctx, token) {
|
|
1025
|
+
const refuse = (why) => {
|
|
1026
|
+
throw new PlanningStoragePublishError(`Cannot recover publication turn ${token}: ${why}`);
|
|
1027
|
+
};
|
|
1028
|
+
const owner = await resolveStoragePublicationOwner(ctx);
|
|
1029
|
+
if (!owner) return refuse("the storage remote is not a GitHub destination, so it has no turn");
|
|
1030
|
+
const target = { repository: owner.repository, targetBranch: owner.branch };
|
|
1031
|
+
const record = await readPublicationTurn(target);
|
|
1032
|
+
if (!record) return refuse("no publication turn is recorded for this storage branch");
|
|
1033
|
+
if (record.token !== token) {
|
|
1034
|
+
return refuse(`the recorded turn is ${record.token}; inspect with: build plan storage status`);
|
|
1035
|
+
}
|
|
1036
|
+
if (!record.ownerId.startsWith(OWNER_PREFIX)) {
|
|
1037
|
+
return refuse(`it belongs to ${record.ownerId}, not a planning storage publisher`);
|
|
1038
|
+
}
|
|
1039
|
+
if (!record.recoveryRequired) return refuse("it retains no unresolved push");
|
|
1040
|
+
const ownerState = ownerProcess(record);
|
|
1041
|
+
if (ownerState !== "dead") {
|
|
1042
|
+
const evidence = ownerState === "live" ? "running" : "unjudgeable";
|
|
1043
|
+
return refuse(`owner process ${record.ownerPid} is ${evidence}`);
|
|
1044
|
+
}
|
|
1045
|
+
const { sha, holdingClone } = parseRetainedPush(record.detail);
|
|
1046
|
+
if (!sha) return refuse("the record names no pushed commit, so its outcome is unknowable");
|
|
1047
|
+
const outcome = await retainedPushOutcome(ctx, owner, sha);
|
|
1048
|
+
if (outcome.state === "unknown") return refuse(outcome.reason);
|
|
1049
|
+
let adopted = false;
|
|
1050
|
+
const handle = await acquirePublicationTurn({
|
|
1051
|
+
...target,
|
|
1052
|
+
ownerId: owner.ownerId,
|
|
1053
|
+
adopt: (current) => {
|
|
1054
|
+
adopted = current.token === token;
|
|
1055
|
+
return adopted;
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
1058
|
+
try {
|
|
1059
|
+
if (!adopted) return refuse("the recorded turn changed while it was being recovered");
|
|
1060
|
+
await handle.clearRecovery();
|
|
1061
|
+
} finally {
|
|
1062
|
+
await handle.release();
|
|
1063
|
+
}
|
|
1064
|
+
const remote = `${ctx.remote ?? "origin"}/${owner.branch}`;
|
|
1065
|
+
if (outcome.state === "present") {
|
|
1066
|
+
return `retained push ${sha} is on ${remote}; turn ${token} cleared`;
|
|
1067
|
+
}
|
|
1068
|
+
const cleared = `retained push ${sha} is not on ${remote} and its process exited; turn ${token} cleared`;
|
|
1069
|
+
const here = await heldPendingRef(ctx.projectRoot, sha);
|
|
1070
|
+
if (here) return `${cleared}. Its candidate is held here as ${here} and publishes with this sync`;
|
|
1071
|
+
return `${cleared}. Its candidate remains only in ${await describeHoldingClone(holdingClone)}; publish it there with: build plan storage sync`;
|
|
1072
|
+
}
|
|
1073
|
+
async function heldPendingRef(projectRoot, sha) {
|
|
1074
|
+
for (const entry of await listPendingRefs(projectRoot)) {
|
|
1075
|
+
if ((await pendingRefHistory(projectRoot, entry)).includes(sha)) return entry.ref;
|
|
1076
|
+
}
|
|
1077
|
+
return void 0;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// src/planning/storage/recover-candidates.ts
|
|
1081
|
+
var OPERATION_TRAILER = "Build-Planning-Operation";
|
|
1082
|
+
function storageCommitMessage(message, opId) {
|
|
1083
|
+
pendingRefFor(opId);
|
|
1084
|
+
return `${message.trimEnd()}
|
|
1085
|
+
|
|
1086
|
+
${OPERATION_TRAILER}: ${opId}`;
|
|
1087
|
+
}
|
|
1088
|
+
function operationId(message) {
|
|
1089
|
+
const matches = [
|
|
1090
|
+
...message.matchAll(/^Build-Planning-Operation: ([A-Za-z0-9][A-Za-z0-9._-]*)$/gm)
|
|
1091
|
+
];
|
|
1092
|
+
const id = matches.at(-1)?.[1];
|
|
1093
|
+
if (id) pendingRefFor(id);
|
|
1094
|
+
return id;
|
|
1095
|
+
}
|
|
1096
|
+
async function contentOnTip(ctx, sha, tip) {
|
|
1097
|
+
const paths = await execGit(
|
|
1098
|
+
["diff-tree", "-r", "-z", "--no-commit-id", "--no-renames", "--name-only", sha],
|
|
1099
|
+
{ cwd: ctx.projectRoot }
|
|
1100
|
+
);
|
|
1101
|
+
if (paths.exitCode !== 0) return false;
|
|
1102
|
+
const changed = paths.stdout.split("\0").filter(Boolean);
|
|
1103
|
+
if (changed.length === 0) return true;
|
|
1104
|
+
const compared = await execGit(
|
|
1105
|
+
[
|
|
1106
|
+
"diff",
|
|
1107
|
+
"--quiet",
|
|
1108
|
+
"--no-ext-diff",
|
|
1109
|
+
"--no-textconv",
|
|
1110
|
+
sha,
|
|
1111
|
+
tip,
|
|
1112
|
+
"--",
|
|
1113
|
+
...changed.map((file) => `:(literal)${file}`)
|
|
1114
|
+
],
|
|
1115
|
+
{ cwd: ctx.projectRoot }
|
|
1116
|
+
);
|
|
1117
|
+
return compared.exitCode === 0;
|
|
1118
|
+
}
|
|
1119
|
+
async function recoverStorageCandidates(ctx) {
|
|
1120
|
+
const tip = await resolveRevision(ctx.projectRoot, trackingRef(ctx));
|
|
1121
|
+
if (!tip) {
|
|
1122
|
+
throw new PlanningStoragePublishError(
|
|
1123
|
+
"Cannot reconcile planning storage without its tracking tip"
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
const refs = await listPendingRefs(ctx.projectRoot);
|
|
1127
|
+
const histories = await Promise.all(refs.map((ref) => pendingRefHistory(ctx.projectRoot, ref)));
|
|
1128
|
+
const known = new Set(histories.flat());
|
|
1129
|
+
const commits = await execGit(
|
|
1130
|
+
[
|
|
1131
|
+
"log",
|
|
1132
|
+
"--reverse",
|
|
1133
|
+
"--format=%H%x00%P%x00%B%x00",
|
|
1134
|
+
"HEAD",
|
|
1135
|
+
...refs.map((ref) => ref.sha),
|
|
1136
|
+
"--not",
|
|
1137
|
+
tip,
|
|
1138
|
+
"--"
|
|
1139
|
+
],
|
|
1140
|
+
{ cwd: ctx.storageRoot }
|
|
1141
|
+
);
|
|
1142
|
+
if (commits.exitCode !== 0) {
|
|
1143
|
+
throw new PlanningStoragePublishError(
|
|
1144
|
+
`Cannot read unrecorded storage commits: ${commits.stderr.trim()}`
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
const fields = commits.stdout.split("\0");
|
|
1148
|
+
const missing = [];
|
|
1149
|
+
let published;
|
|
1150
|
+
for (let index = 0; index + 2 < fields.length; index += 3) {
|
|
1151
|
+
const sha = fields[index]?.trim() ?? "";
|
|
1152
|
+
if (!sha || known.has(sha)) continue;
|
|
1153
|
+
const id = operationId(fields[index + 2] ?? "");
|
|
1154
|
+
const ordinary = fields[index + 1]?.trim().split(/\s+/).filter(Boolean).length === 1;
|
|
1155
|
+
if (!id && ordinary && await contentOnTip(ctx, sha, tip)) continue;
|
|
1156
|
+
if (!id || !ordinary) {
|
|
1157
|
+
throw new PlanningStoragePublishError(
|
|
1158
|
+
`Cannot record or publish over unrecorded storage commit ${sha}: ownership is unavailable; HEAD and files were preserved. Inspect the commit and refresh publication evidence with build plan storage sync before retrying`
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
if (!published) {
|
|
1162
|
+
const messages = await execGit(["log", "--format=%B%x00", tip, "--"], {
|
|
1163
|
+
cwd: ctx.projectRoot
|
|
1164
|
+
});
|
|
1165
|
+
if (messages.exitCode !== 0)
|
|
1166
|
+
throw new PlanningStoragePublishError("Cannot read published storage operations");
|
|
1167
|
+
published = new Set(
|
|
1168
|
+
messages.stdout.split("\0").flatMap((message) => {
|
|
1169
|
+
const operation = operationId(message);
|
|
1170
|
+
return operation ? [operation] : [];
|
|
1171
|
+
})
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
if (!published.has(id)) missing.push({ ref: pendingRefFor(id), sha });
|
|
1175
|
+
}
|
|
1176
|
+
const dropped = missing.length > 0 ? await droppedByRemote(ctx, tip) : /* @__PURE__ */ new Set();
|
|
1177
|
+
const recovered = missing.filter((candidate) => !dropped.has(candidate.sha));
|
|
1178
|
+
for (const candidate of recovered) {
|
|
1179
|
+
await writePendingRef(ctx.projectRoot, candidate.ref, candidate.sha, "");
|
|
1180
|
+
}
|
|
1181
|
+
return recovered;
|
|
1182
|
+
}
|
|
1183
|
+
async function droppedByRemote(ctx, tip) {
|
|
1184
|
+
const reflog = await execGit(["reflog", "show", "--format=%H", trackingRef(ctx), "--"], {
|
|
1185
|
+
cwd: ctx.projectRoot
|
|
1186
|
+
});
|
|
1187
|
+
const earlierTips = [
|
|
1188
|
+
...new Set(reflog.exitCode === 0 ? reflog.stdout.split("\n").map((line) => line.trim()) : [])
|
|
1189
|
+
].filter((sha) => sha && sha !== tip);
|
|
1190
|
+
if (earlierTips.length === 0) return /* @__PURE__ */ new Set();
|
|
1191
|
+
const reachable = await execGit(
|
|
1192
|
+
["rev-list", "--ignore-missing", ...earlierTips, "--not", tip, "--"],
|
|
1193
|
+
{ cwd: ctx.projectRoot }
|
|
1194
|
+
);
|
|
1195
|
+
if (reachable.exitCode !== 0) {
|
|
1196
|
+
throw new PlanningStoragePublishError(
|
|
1197
|
+
`Cannot read earlier storage tips: ${reachable.stderr.trim()}`
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
return new Set(
|
|
1201
|
+
reachable.stdout.split("\n").map((line) => line.trim()).filter(Boolean)
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// src/planning/storage/publish-transaction.ts
|
|
1206
|
+
var MAX_PUBLISH_ATTEMPTS = 3;
|
|
1207
|
+
function result(outcome, fields) {
|
|
1208
|
+
return { outcome, exitCode: PUBLISH_OUTCOME_EXIT_CODES[outcome], ...fields };
|
|
1209
|
+
}
|
|
1210
|
+
function firstLine(value) {
|
|
1211
|
+
return value.trim().split("\n")[0]?.trim() ?? "";
|
|
1212
|
+
}
|
|
1213
|
+
function rewoundRefusal(outcome) {
|
|
1214
|
+
if (!outcome.rewound) return void 0;
|
|
1215
|
+
return `remote storage branch was rewound (${outcome.rewound.previous}..${outcome.rewound.remote}); refusing to publish onto stale tracking evidence; inspect with: build plan storage status`;
|
|
1216
|
+
}
|
|
1217
|
+
function fetchRefusal(outcome) {
|
|
1218
|
+
if (outcome.fetched) return void 0;
|
|
1219
|
+
return rewoundRefusal(outcome) ?? `required fetch failed: ${outcome.reason ?? "no reason reported"}`;
|
|
1220
|
+
}
|
|
1221
|
+
function isNonFastForward(output) {
|
|
1222
|
+
return /non-fast-forward|fetch first|rejected.*(?:fetch|behind)|\[rejected\]/i.test(output);
|
|
1223
|
+
}
|
|
1224
|
+
function describeError(error) {
|
|
1225
|
+
return error instanceof Error ? firstLine(error.message) : String(error);
|
|
1226
|
+
}
|
|
1227
|
+
var PublicationOwnershipError = class extends Error {
|
|
1228
|
+
constructor(message) {
|
|
1229
|
+
super(message);
|
|
1230
|
+
this.name = "PublicationOwnershipError";
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
async function fenced(assertOwned, owner) {
|
|
1234
|
+
try {
|
|
1235
|
+
await assertOwned();
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
throw new PublicationOwnershipError(`${owner} ownership was lost: ${describeError(error)}`);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
async function withOwnedStorageLock(storageRoot, step) {
|
|
1241
|
+
let entered = false;
|
|
1242
|
+
try {
|
|
1243
|
+
return await withStorageLock(storageRoot, async (lock) => {
|
|
1244
|
+
entered = true;
|
|
1245
|
+
return await step(lock);
|
|
1246
|
+
});
|
|
1247
|
+
} catch (error) {
|
|
1248
|
+
if (entered) throw error;
|
|
1249
|
+
throw new PublicationOwnershipError(
|
|
1250
|
+
`planning storage lock unavailable: ${describeError(error)}`
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
async function withPublicationOwnership(ctx, run) {
|
|
1255
|
+
let turn;
|
|
1256
|
+
try {
|
|
1257
|
+
turn = await acquireStorageTurn(ctx);
|
|
1258
|
+
} catch (error) {
|
|
1259
|
+
throw new PublicationOwnershipError(`publication turn unavailable: ${describeError(error)}`);
|
|
1260
|
+
}
|
|
1261
|
+
if (!turn) {
|
|
1262
|
+
return await withOwnedStorageLock(
|
|
1263
|
+
ctx.storageRoot,
|
|
1264
|
+
(lock) => run({
|
|
1265
|
+
local: (step) => step(lock),
|
|
1266
|
+
assertOwned: () => fenced(() => lock.assertOwned(), "planning storage lock")
|
|
1267
|
+
})
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
const owned = turn;
|
|
1271
|
+
try {
|
|
1272
|
+
return await run({
|
|
1273
|
+
turn: owned,
|
|
1274
|
+
local: (step) => withOwnedStorageLock(ctx.storageRoot, step),
|
|
1275
|
+
assertOwned: () => fenced(() => owned.assertOwned(), "publication turn")
|
|
1276
|
+
});
|
|
1277
|
+
} finally {
|
|
1278
|
+
await owned.settle().catch(() => void 0);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
async function publicationBoundaryRefusal(ctx) {
|
|
1282
|
+
if (isPlanningStorageHeld()) {
|
|
1283
|
+
return "planning storage is held: the storage hold file exists, so nothing is pushed until it is removed";
|
|
1284
|
+
}
|
|
1285
|
+
const remote = ctx.remote ?? "origin";
|
|
1286
|
+
const refresh = await refreshDefaultBranchTracking(ctx.projectRoot, remote);
|
|
1287
|
+
if (!refresh.ok) return refresh.reason;
|
|
1288
|
+
try {
|
|
1289
|
+
await assertNoStorageDrift(ctx, { remote, defaultBranch: refresh.defaultBranch });
|
|
1290
|
+
} catch (error) {
|
|
1291
|
+
return describeError(error);
|
|
1292
|
+
}
|
|
1293
|
+
return void 0;
|
|
1294
|
+
}
|
|
1295
|
+
async function remotePresence(ctx, sha, timeoutMs) {
|
|
1296
|
+
const ref = `refs/heads/${ctx.branch ?? ""}`;
|
|
1297
|
+
const lookup = await execGit(["ls-remote", "--heads", "--", ctx.remote ?? "origin", ref], {
|
|
1298
|
+
cwd: ctx.projectRoot,
|
|
1299
|
+
timeoutMs
|
|
1300
|
+
}).catch(() => void 0);
|
|
1301
|
+
if (!lookup || lookup.exitCode !== 0) return { state: "unknown" };
|
|
1302
|
+
const tips = lookup.stdout.split("\n").map((line) => line.trim().split(/\s+/)).filter(([, name]) => name === ref).map(([tipSha]) => tipSha ?? "");
|
|
1303
|
+
if (tips.length === 0) return { state: "absent" };
|
|
1304
|
+
const tip = tips[0];
|
|
1305
|
+
if (tips.length > 1 || !tip) return { state: "unknown" };
|
|
1306
|
+
if (tip === sha) return { state: "present", tip };
|
|
1307
|
+
const ancestry = await execGit(["merge-base", "--is-ancestor", sha, tip], {
|
|
1308
|
+
cwd: ctx.projectRoot
|
|
1309
|
+
});
|
|
1310
|
+
if (ancestry.exitCode === 0) return { state: "present", tip };
|
|
1311
|
+
return { state: ancestry.exitCode === 1 ? "absent" : "unknown", tip };
|
|
1312
|
+
}
|
|
1313
|
+
async function pushCandidate(ctx, sha) {
|
|
1314
|
+
try {
|
|
1315
|
+
const push = await execGit(
|
|
1316
|
+
["push", "--", ctx.remote ?? "origin", `${sha}:refs/heads/${ctx.branch ?? ""}`],
|
|
1317
|
+
{ cwd: ctx.projectRoot, timeoutMs: resolvePushTimeoutMs() }
|
|
1318
|
+
);
|
|
1319
|
+
return {
|
|
1320
|
+
exitCode: push.exitCode,
|
|
1321
|
+
output: `${push.stderr}
|
|
1322
|
+
${push.stdout}`,
|
|
1323
|
+
detail: firstLine(push.stderr) || firstLine(push.stdout) || `exit ${push.exitCode}`
|
|
1324
|
+
};
|
|
1325
|
+
} catch (error) {
|
|
1326
|
+
return { output: "", detail: describeError(error) };
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
function assertPublishable(ctx) {
|
|
1330
|
+
if (ctx.backend !== "git-branch") {
|
|
1331
|
+
throw new Error(
|
|
1332
|
+
'publishManagedChange: planning storage backend is "checkout"; the sync dispatcher must not call the git-branch publisher'
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
if (!ctx.branch || !ctx.remote) {
|
|
1336
|
+
throw new PlanningStoragePublishError(
|
|
1337
|
+
"Planning storage backend is git-branch but branch/remote are not configured"
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
if (isPlanningStorageHeld()) {
|
|
1341
|
+
throw new PlanningStoragePublishError(
|
|
1342
|
+
"Planning storage is held: the storage hold file exists, so every publisher refuses until it is removed"
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
async function blobIdAt(cwd, revision, relativePath) {
|
|
1347
|
+
const result_ = await execGit(
|
|
1348
|
+
["rev-parse", "--verify", "--quiet", `${revision}:${relativePath}`],
|
|
1349
|
+
{
|
|
1350
|
+
cwd
|
|
1351
|
+
}
|
|
1352
|
+
);
|
|
1353
|
+
const sha = result_.stdout.trim();
|
|
1354
|
+
return result_.exitCode === 0 && sha ? sha : void 0;
|
|
1355
|
+
}
|
|
1356
|
+
async function divergedPaths(cwd, base, tip, relativePaths) {
|
|
1357
|
+
const diverged = [];
|
|
1358
|
+
for (const relativePath of relativePaths) {
|
|
1359
|
+
const [before, after] = await Promise.all([
|
|
1360
|
+
blobIdAt(cwd, base, relativePath),
|
|
1361
|
+
blobIdAt(cwd, tip, relativePath)
|
|
1362
|
+
]);
|
|
1363
|
+
if (before !== after) diverged.push(relativePath);
|
|
1364
|
+
}
|
|
1365
|
+
return diverged;
|
|
1366
|
+
}
|
|
1367
|
+
async function rebuildOnto(ctx, candidate, tip) {
|
|
1368
|
+
const gitDir = await readWorktreeGitDir(ctx.storageRoot);
|
|
1369
|
+
const indexFile = path4.join(gitDir, `build-storage-index-${randomUUID()}`);
|
|
1370
|
+
const env = { GIT_INDEX_FILE: indexFile };
|
|
1371
|
+
const run = async (args) => {
|
|
1372
|
+
const outcome = await execGit(args, { cwd: ctx.storageRoot, env });
|
|
1373
|
+
if (outcome.exitCode !== 0) {
|
|
1374
|
+
throw new PlanningStoragePublishError(
|
|
1375
|
+
`Cannot re-apply the planning storage candidate onto ${tip}: git ${args[0]} failed: ${outcome.stderr.trim()}`
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
return outcome.stdout.trim();
|
|
1379
|
+
};
|
|
1380
|
+
try {
|
|
1381
|
+
await run(["read-tree", tip]);
|
|
1382
|
+
for (const relativePath of candidate.stagedPaths) {
|
|
1383
|
+
const entry = await readTreeEntry(ctx.storageRoot, candidate.sha, relativePath);
|
|
1384
|
+
if (!entry) {
|
|
1385
|
+
await run(["update-index", "--force-remove", "--", relativePath]);
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1388
|
+
await run([
|
|
1389
|
+
"update-index",
|
|
1390
|
+
"--add",
|
|
1391
|
+
"--cacheinfo",
|
|
1392
|
+
`${entry.mode},${entry.sha},${relativePath}`
|
|
1393
|
+
]);
|
|
1394
|
+
}
|
|
1395
|
+
const tree = await run(["write-tree"]);
|
|
1396
|
+
return await run(["commit-tree", tree, "-p", tip, "-m", candidate.message]);
|
|
1397
|
+
} finally {
|
|
1398
|
+
await fs5.rm(indexFile, { force: true }).catch(() => void 0);
|
|
1399
|
+
await fs5.rm(`${indexFile}.lock`, { force: true }).catch(() => void 0);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
async function readTreeEntry(cwd, revision, relativePath) {
|
|
1403
|
+
const outcome = await execGit(["ls-tree", revision, "--", relativePath], { cwd });
|
|
1404
|
+
if (outcome.exitCode !== 0) return void 0;
|
|
1405
|
+
const line = outcome.stdout.split("\n").find((candidate) => candidate.trim().length > 0);
|
|
1406
|
+
if (!line) return void 0;
|
|
1407
|
+
const [meta] = line.split(" ");
|
|
1408
|
+
const [mode, type, sha] = (meta ?? "").trim().split(/\s+/);
|
|
1409
|
+
if (type !== "blob" || !mode || !sha) return void 0;
|
|
1410
|
+
return { mode, sha };
|
|
1411
|
+
}
|
|
1412
|
+
async function retargetWorktreeHead(storageRoot, previous, next) {
|
|
1413
|
+
try {
|
|
1414
|
+
const head = await resolveRevision(storageRoot, "HEAD");
|
|
1415
|
+
if (head !== previous) {
|
|
1416
|
+
return `its HEAD is ${head ?? "unreadable"}, not the replaced candidate ${previous}`;
|
|
1417
|
+
}
|
|
1418
|
+
const diff = await execGit(["diff", "-z", "--name-only", "--no-renames", previous, next], {
|
|
1419
|
+
cwd: storageRoot
|
|
1420
|
+
});
|
|
1421
|
+
if (diff.exitCode !== 0) return `cannot compare ${previous} with ${next}`;
|
|
1422
|
+
const moved = diff.stdout.split("\0").filter(Boolean);
|
|
1423
|
+
if (moved.length > 0) {
|
|
1424
|
+
const status = await execGit(
|
|
1425
|
+
[
|
|
1426
|
+
"status",
|
|
1427
|
+
"--porcelain",
|
|
1428
|
+
"--untracked-files=all",
|
|
1429
|
+
"--ignored=traditional",
|
|
1430
|
+
"--",
|
|
1431
|
+
...moved.map((file) => `:(literal)${file}`)
|
|
1432
|
+
],
|
|
1433
|
+
{ cwd: storageRoot }
|
|
1434
|
+
);
|
|
1435
|
+
if (status.exitCode !== 0) return "cannot read its status";
|
|
1436
|
+
const local = parseGitStatus(status.stdout).map((entry) => entry.path);
|
|
1437
|
+
if (local.length > 0) {
|
|
1438
|
+
return `local, in-flight or ignored files would be overwritten: ${local.join(", ")}`;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
const checkout = await execGit(["checkout", "--detach", next], { cwd: storageRoot });
|
|
1442
|
+
if (checkout.exitCode !== 0) return `checkout failed: ${checkout.stderr.trim()}`;
|
|
1443
|
+
return void 0;
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
return describeError(error);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
async function finalizeCandidate(ctx, initial, scope) {
|
|
1449
|
+
const remote = ctx.remote ?? "origin";
|
|
1450
|
+
const timeoutMs = resolveFetchTimeoutMs();
|
|
1451
|
+
const { turn } = scope;
|
|
1452
|
+
let candidate = initial;
|
|
1453
|
+
let attempts = 0;
|
|
1454
|
+
let lastRejection = "";
|
|
1455
|
+
const base = (outcome) => ({
|
|
1456
|
+
commitSha: candidate.sha,
|
|
1457
|
+
pendingRef: candidate.pendingRef,
|
|
1458
|
+
stagedPaths: candidate.stagedPaths,
|
|
1459
|
+
localByPolicyPaths: candidate.localByPolicyPaths,
|
|
1460
|
+
attempts,
|
|
1461
|
+
...outcome.reason ? { reason: outcome.reason } : {},
|
|
1462
|
+
...outcome.remoteTip ? { remoteTip: outcome.remoteTip } : {}
|
|
1463
|
+
});
|
|
1464
|
+
const pending = (reason) => result("pending", base({ reason }));
|
|
1465
|
+
const accept = async (tip) => {
|
|
1466
|
+
await scope.local(() => deletePendingRef(ctx.projectRoot, candidate.pendingRef, candidate.sha)).catch(() => void 0);
|
|
1467
|
+
await turn?.clearRecovery().catch(() => void 0);
|
|
1468
|
+
const { pendingRef: _recorded, ...published } = base({ ...tip ? { remoteTip: tip } : {} });
|
|
1469
|
+
return result("published", published);
|
|
1470
|
+
};
|
|
1471
|
+
try {
|
|
1472
|
+
for (let attempt = 1; attempt <= MAX_PUBLISH_ATTEMPTS; attempt += 1) {
|
|
1473
|
+
attempts = attempt;
|
|
1474
|
+
const refused = fetchRefusal(await fetchStorageBranch(ctx, "always"));
|
|
1475
|
+
if (refused) return pending(refused);
|
|
1476
|
+
const tip = await resolveRevision(ctx.projectRoot, trackingRef(ctx));
|
|
1477
|
+
if (tip && await isAncestor(ctx.projectRoot, candidate.sha, tip)) {
|
|
1478
|
+
return await accept(tip);
|
|
1479
|
+
}
|
|
1480
|
+
if (tip && candidate.base && tip !== candidate.base) {
|
|
1481
|
+
const conflicts = await divergedPaths(
|
|
1482
|
+
ctx.projectRoot,
|
|
1483
|
+
candidate.base,
|
|
1484
|
+
tip,
|
|
1485
|
+
candidate.stagedPaths
|
|
1486
|
+
);
|
|
1487
|
+
if (conflicts.length > 0) {
|
|
1488
|
+
return pending(`conflict: ${conflicts.join(", ")}`);
|
|
1489
|
+
}
|
|
1490
|
+
const unsuitable = await refuseUnsuitableTip(ctx, tip);
|
|
1491
|
+
if (unsuitable) return pending(unsuitable);
|
|
1492
|
+
const previous = candidate;
|
|
1493
|
+
const rebuilt = await scope.local(async (lock) => {
|
|
1494
|
+
await fenced(() => lock.assertOwned(), "planning storage lock");
|
|
1495
|
+
const sha = await rebuildOnto(ctx, previous, tip);
|
|
1496
|
+
await writePendingRef(ctx.projectRoot, previous.pendingRef, sha, previous.sha);
|
|
1497
|
+
await retargetWorktreeHead(ctx.storageRoot, previous.sha, sha);
|
|
1498
|
+
return sha;
|
|
1499
|
+
});
|
|
1500
|
+
candidate = { ...candidate, sha: rebuilt, base: tip };
|
|
1501
|
+
}
|
|
1502
|
+
await scope.assertOwned();
|
|
1503
|
+
await turn?.retainPush(candidate.sha).catch(() => void 0);
|
|
1504
|
+
const push = await pushCandidate(ctx, candidate.sha);
|
|
1505
|
+
if (push.exitCode !== 0) {
|
|
1506
|
+
lastRejection = push.detail;
|
|
1507
|
+
const definitive = push.exitCode !== void 0;
|
|
1508
|
+
if (definitive && isNonFastForward(push.output) && attempt < MAX_PUBLISH_ATTEMPTS) {
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
const presence2 = await remotePresence(ctx, candidate.sha, timeoutMs);
|
|
1512
|
+
if (presence2.state === "present") return await accept(presence2.tip);
|
|
1513
|
+
if (presence2.state === "absent" && definitive) {
|
|
1514
|
+
await turn?.clearRecovery().catch(() => void 0);
|
|
1515
|
+
return pending(`push failed: ${push.detail}`);
|
|
1516
|
+
}
|
|
1517
|
+
return pending(
|
|
1518
|
+
`push outcome unknown: ${push.detail}; ` + (presence2.state === "absent" ? "the remote does not show the commit yet" : "the remote could not be checked")
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
const verifyRefresh = rewoundRefusal(await fetchStorageBranch(ctx, "always"));
|
|
1522
|
+
const presence = await remotePresence(ctx, candidate.sha, timeoutMs);
|
|
1523
|
+
if (presence.state === "present") return await accept(presence.tip);
|
|
1524
|
+
if (verifyRefresh) return pending(verifyRefresh);
|
|
1525
|
+
return result(
|
|
1526
|
+
"unverified",
|
|
1527
|
+
base({
|
|
1528
|
+
reason: presence.state === "absent" ? `push reported success but ${remote}/${ctx.branch} does not contain ${candidate.sha}` : `push reported success but ${remote}/${ctx.branch} could not be checked for ${candidate.sha}`
|
|
1529
|
+
})
|
|
1530
|
+
);
|
|
1531
|
+
}
|
|
1532
|
+
} catch (error) {
|
|
1533
|
+
if (!(error instanceof PublicationOwnershipError)) throw error;
|
|
1534
|
+
scope.lost = error.message;
|
|
1535
|
+
return pending(error.message);
|
|
1536
|
+
}
|
|
1537
|
+
return pending(
|
|
1538
|
+
`push rejected after ${MAX_PUBLISH_ATTEMPTS} attempts: ${lastRejection || "non-fast-forward"}`
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
async function recordCandidate(ctx, request) {
|
|
1542
|
+
const opId = request.opId ?? generateOpId();
|
|
1543
|
+
const pendingRef = pendingRefFor(opId);
|
|
1544
|
+
const message = storageCommitMessage(request.message, opId);
|
|
1545
|
+
return await withStorageLock(ctx.storageRoot, async (lock) => {
|
|
1546
|
+
if (!await isLinkedWorktree(ctx.storageRoot)) {
|
|
1547
|
+
throw new PlanningStoragePublishError(
|
|
1548
|
+
`Planning storage is not materialized at ${ctx.storageRoot}; nothing was published`
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
const recovered = await recoverStorageCandidates(ctx);
|
|
1552
|
+
const plan = await planAllowlist(ctx, request.touchedPaths);
|
|
1553
|
+
const localOnly = () => result("local-only", {
|
|
1554
|
+
stagedPaths: [],
|
|
1555
|
+
localByPolicyPaths: plan.localByPolicy,
|
|
1556
|
+
attempts: 0,
|
|
1557
|
+
...plan.localByPolicy.length > 0 ? { reason: "all touched paths are local by policy on the storage branch" } : { reason: "nothing to publish" }
|
|
1558
|
+
});
|
|
1559
|
+
if (plan.staged.length === 0) return localOnly();
|
|
1560
|
+
const priorIndex = await snapshotIndexEntries(ctx.storageRoot, plan.staged);
|
|
1561
|
+
const sha = await withIndexRestoredOnFailure(
|
|
1562
|
+
ctx.storageRoot,
|
|
1563
|
+
plan.staged,
|
|
1564
|
+
priorIndex,
|
|
1565
|
+
async () => {
|
|
1566
|
+
await stageAllowlist(ctx.storageRoot, plan.staged);
|
|
1567
|
+
if (!await hasStagedChanges(ctx.storageRoot)) return void 0;
|
|
1568
|
+
await lock.assertOwned();
|
|
1569
|
+
return (await commitCandidate(ctx, message, request.backlogGatePassed)).sha;
|
|
1570
|
+
}
|
|
1571
|
+
);
|
|
1572
|
+
if (!sha) {
|
|
1573
|
+
const head = await resolveRevision(ctx.storageRoot, "HEAD");
|
|
1574
|
+
const retry = recovered.find((candidate) => candidate.sha === head);
|
|
1575
|
+
return retry ? {
|
|
1576
|
+
sha: retry.sha,
|
|
1577
|
+
message: await readCommitMessage(ctx.projectRoot, retry.sha),
|
|
1578
|
+
stagedPaths: await candidateTouchedPaths(ctx.projectRoot, retry.sha),
|
|
1579
|
+
localByPolicyPaths: plan.localByPolicy,
|
|
1580
|
+
pendingRef: retry.ref
|
|
1581
|
+
} : localOnly();
|
|
1582
|
+
}
|
|
1583
|
+
await writePendingRef(ctx.projectRoot, pendingRef, sha);
|
|
1584
|
+
return {
|
|
1585
|
+
sha,
|
|
1586
|
+
message,
|
|
1587
|
+
stagedPaths: plan.staged,
|
|
1588
|
+
localByPolicyPaths: plan.localByPolicy,
|
|
1589
|
+
pendingRef
|
|
1590
|
+
};
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
async function publishRecorded(ctx, own) {
|
|
1594
|
+
const unpublished = (reason) => reconcileOwnCandidate(ctx, own, reason);
|
|
1595
|
+
try {
|
|
1596
|
+
return await withPublicationOwnership(ctx, async (scope) => {
|
|
1597
|
+
const refused = await publicationBoundaryRefusal(ctx);
|
|
1598
|
+
if (refused) return await unpublished(refused);
|
|
1599
|
+
const { ordered: refs, parents } = closureOf(
|
|
1600
|
+
await orderByDependency(ctx.projectRoot, await listPendingRefs(ctx.projectRoot)),
|
|
1601
|
+
[own.pendingRef]
|
|
1602
|
+
);
|
|
1603
|
+
const index = refs.findIndex((entry) => entry.ref === own.pendingRef);
|
|
1604
|
+
if (index < 0) return await unpublished("the pending ref disappeared while this waited");
|
|
1605
|
+
const results = await publishInOrder(ctx, scope, refs, parents, own);
|
|
1606
|
+
return results[index] ?? await unpublished("publication produced no result for it");
|
|
1607
|
+
});
|
|
1608
|
+
} catch (error) {
|
|
1609
|
+
const reason = error instanceof PublicationOwnershipError ? error.message : `publication failed: ${describeError(error)}`;
|
|
1610
|
+
return await unpublished(reason).catch(
|
|
1611
|
+
(lookup) => result("unverified", {
|
|
1612
|
+
reason: `${reason}; recorded ${own.pendingRef} at ${own.sha}, but its current state could not be read: ${describeError(lookup)}`,
|
|
1613
|
+
stagedPaths: own.stagedPaths,
|
|
1614
|
+
localByPolicyPaths: own.localByPolicyPaths,
|
|
1615
|
+
attempts: 0
|
|
1616
|
+
})
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
async function reconcileOwnCandidate(ctx, own, reason) {
|
|
1621
|
+
const fields = {
|
|
1622
|
+
stagedPaths: own.stagedPaths,
|
|
1623
|
+
localByPolicyPaths: own.localByPolicyPaths,
|
|
1624
|
+
attempts: 0
|
|
1625
|
+
};
|
|
1626
|
+
const ref = await execGit(["rev-parse", "--verify", "--quiet", `${own.pendingRef}^{commit}`], {
|
|
1627
|
+
cwd: ctx.projectRoot
|
|
1628
|
+
});
|
|
1629
|
+
if (ref.exitCode > 1) throw new Error(`cannot read ${own.pendingRef}: ${firstLine(ref.stderr)}`);
|
|
1630
|
+
const current = ref.exitCode === 0 ? ref.stdout.trim() : "";
|
|
1631
|
+
if (current) {
|
|
1632
|
+
return result("pending", { reason, commitSha: current, pendingRef: own.pendingRef, ...fields });
|
|
1633
|
+
}
|
|
1634
|
+
const presence = await remotePresence(ctx, own.sha, resolveFetchTimeoutMs());
|
|
1635
|
+
if (presence.state === "present") {
|
|
1636
|
+
const tip = presence.tip ? { remoteTip: presence.tip } : {};
|
|
1637
|
+
return result("published", { commitSha: own.sha, ...tip, ...fields });
|
|
1638
|
+
}
|
|
1639
|
+
const checked = presence.state === "absent" ? "does not contain" : "could not be checked for";
|
|
1640
|
+
return result("unverified", {
|
|
1641
|
+
reason: `${reason}; ${own.pendingRef} is gone and ${ctx.remote}/${ctx.branch} ${checked} ${own.sha}`,
|
|
1642
|
+
...fields
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
async function publishManagedChange(ctx, request) {
|
|
1646
|
+
assertPublishable(ctx);
|
|
1647
|
+
const recorded = await recordCandidate(ctx, request);
|
|
1648
|
+
if ("outcome" in recorded) return recorded;
|
|
1649
|
+
return await publishRecorded(ctx, recorded);
|
|
1650
|
+
}
|
|
1651
|
+
async function recordManagedChange(ctx, request) {
|
|
1652
|
+
assertPublishable(ctx);
|
|
1653
|
+
const recorded = await recordCandidate(ctx, request);
|
|
1654
|
+
if ("outcome" in recorded) return recorded;
|
|
1655
|
+
return result("pending", {
|
|
1656
|
+
reason: "publication deferred; nothing was fetched or pushed",
|
|
1657
|
+
commitSha: recorded.sha,
|
|
1658
|
+
pendingRef: recorded.pendingRef,
|
|
1659
|
+
stagedPaths: recorded.stagedPaths,
|
|
1660
|
+
localByPolicyPaths: recorded.localByPolicyPaths,
|
|
1661
|
+
attempts: 0
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
async function candidateTouchedPaths(projectRoot, sha) {
|
|
1665
|
+
const outcome = await execGit(
|
|
1666
|
+
["diff-tree", "-z", "-r", "--no-commit-id", "--no-renames", "--name-only", sha],
|
|
1667
|
+
{ cwd: projectRoot }
|
|
1668
|
+
);
|
|
1669
|
+
if (outcome.exitCode !== 0) {
|
|
1670
|
+
throw new PlanningStoragePublishError(
|
|
1671
|
+
`Cannot read the changes of pending candidate ${sha}: ${outcome.stderr.trim()}`
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
return outcome.stdout.split("\0").filter(Boolean);
|
|
1675
|
+
}
|
|
1676
|
+
async function readCommitMessage(projectRoot, sha) {
|
|
1677
|
+
const outcome = await execGit(["log", "-1", "--format=%B", sha], { cwd: projectRoot });
|
|
1678
|
+
return outcome.exitCode === 0 ? outcome.stdout.replace(/\n+$/, "") : "";
|
|
1679
|
+
}
|
|
1680
|
+
async function publishInOrder(ctx, scope, refs, parents, own) {
|
|
1681
|
+
const results = [];
|
|
1682
|
+
const unpublished = /* @__PURE__ */ new Set();
|
|
1683
|
+
for (const { ref, sha } of refs) {
|
|
1684
|
+
const mine = own?.pendingRef === ref ? own : void 0;
|
|
1685
|
+
const stagedPaths = mine?.stagedPaths ?? await candidateTouchedPaths(ctx.projectRoot, sha);
|
|
1686
|
+
const localByPolicyPaths = mine?.localByPolicyPaths ?? [];
|
|
1687
|
+
const blocker = [...parents.get(ref) ?? []].find((parent2) => unpublished.has(parent2));
|
|
1688
|
+
const held = scope.lost ?? (blocker ? `pending operation ${blocker} must publish first` : "");
|
|
1689
|
+
if (held) {
|
|
1690
|
+
results.push(
|
|
1691
|
+
result("pending", {
|
|
1692
|
+
reason: held,
|
|
1693
|
+
commitSha: sha,
|
|
1694
|
+
pendingRef: ref,
|
|
1695
|
+
stagedPaths,
|
|
1696
|
+
localByPolicyPaths,
|
|
1697
|
+
attempts: 0
|
|
1698
|
+
})
|
|
1699
|
+
);
|
|
1700
|
+
unpublished.add(ref);
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
const parent = await resolveRevision(ctx.projectRoot, `${sha}^`);
|
|
1704
|
+
const finalized = await finalizeCandidate(
|
|
1705
|
+
ctx,
|
|
1706
|
+
{
|
|
1707
|
+
sha,
|
|
1708
|
+
...parent ? { base: parent } : {},
|
|
1709
|
+
message: mine?.message ?? await readCommitMessage(ctx.projectRoot, sha),
|
|
1710
|
+
stagedPaths,
|
|
1711
|
+
localByPolicyPaths,
|
|
1712
|
+
pendingRef: ref
|
|
1713
|
+
},
|
|
1714
|
+
scope
|
|
1715
|
+
);
|
|
1716
|
+
if (finalized.outcome !== "published") unpublished.add(ref);
|
|
1717
|
+
results.push(finalized);
|
|
1718
|
+
}
|
|
1719
|
+
return results;
|
|
1720
|
+
}
|
|
1721
|
+
async function publishPending(ctx, select) {
|
|
1722
|
+
if (ctx.backend !== "git-branch") return [];
|
|
1723
|
+
assertPublishable(ctx);
|
|
1724
|
+
if (!select) await withStorageLock(ctx.storageRoot, () => recoverStorageCandidates(ctx));
|
|
1725
|
+
if ((await listPendingRefs(ctx.projectRoot)).length === 0) return [];
|
|
1726
|
+
const retainedAll = async (reason, refs) => {
|
|
1727
|
+
const results = [];
|
|
1728
|
+
for (const { ref, sha } of refs) {
|
|
1729
|
+
results.push(
|
|
1730
|
+
result("pending", {
|
|
1731
|
+
reason,
|
|
1732
|
+
commitSha: sha,
|
|
1733
|
+
pendingRef: ref,
|
|
1734
|
+
stagedPaths: await candidateTouchedPaths(ctx.projectRoot, sha),
|
|
1735
|
+
localByPolicyPaths: [],
|
|
1736
|
+
attempts: 0
|
|
1737
|
+
})
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
return results;
|
|
1741
|
+
};
|
|
1742
|
+
try {
|
|
1743
|
+
return await withPublicationOwnership(ctx, async (scope) => {
|
|
1744
|
+
const snapshot = await orderByDependency(
|
|
1745
|
+
ctx.projectRoot,
|
|
1746
|
+
await listPendingRefs(ctx.projectRoot)
|
|
1747
|
+
);
|
|
1748
|
+
const { ordered, parents } = select ? closureOf(snapshot, await select(snapshot.ordered)) : snapshot;
|
|
1749
|
+
if (ordered.length === 0) return [];
|
|
1750
|
+
const refused = await publicationBoundaryRefusal(ctx);
|
|
1751
|
+
if (refused) return await retainedAll(refused, ordered);
|
|
1752
|
+
return await publishInOrder(ctx, scope, ordered, parents);
|
|
1753
|
+
});
|
|
1754
|
+
} catch (error) {
|
|
1755
|
+
if (!(error instanceof PublicationOwnershipError)) throw error;
|
|
1756
|
+
return await retainedAll(error.message, await listPendingRefs(ctx.projectRoot));
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
async function publishReplacement(ctx, ref, prepare) {
|
|
1760
|
+
assertPublishable(ctx);
|
|
1761
|
+
try {
|
|
1762
|
+
return await withPublicationOwnership(ctx, async (scope) => {
|
|
1763
|
+
await prepare(scope.local);
|
|
1764
|
+
const { ordered, parents } = closureOf(
|
|
1765
|
+
await orderByDependency(ctx.projectRoot, await listPendingRefs(ctx.projectRoot)),
|
|
1766
|
+
[ref]
|
|
1767
|
+
);
|
|
1768
|
+
const refused = await publicationBoundaryRefusal(ctx);
|
|
1769
|
+
if (!refused) return await publishInOrder(ctx, scope, ordered, parents);
|
|
1770
|
+
return ordered.map(
|
|
1771
|
+
({ ref: pendingRef, sha }) => result("pending", {
|
|
1772
|
+
reason: refused,
|
|
1773
|
+
commitSha: sha,
|
|
1774
|
+
pendingRef,
|
|
1775
|
+
stagedPaths: [],
|
|
1776
|
+
localByPolicyPaths: [],
|
|
1777
|
+
attempts: 0
|
|
1778
|
+
})
|
|
1779
|
+
);
|
|
1780
|
+
});
|
|
1781
|
+
} catch (error) {
|
|
1782
|
+
if (!(error instanceof PublicationOwnershipError)) throw error;
|
|
1783
|
+
throw new PlanningStoragePublishError(`${error.message}; nothing was resolved`);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
// src/planning/storage/pending-resolution.ts
|
|
1788
|
+
var CONFLICT_CHOICES = ["candidate", "remote"];
|
|
1789
|
+
var RESOLVED_PREFIX = "Resolved conflict: ";
|
|
1790
|
+
function replacedCommits(message) {
|
|
1791
|
+
return message.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.startsWith(RESOLVED_PREFIX)).map((line) => /; replaces ([0-9a-f]{40,64})$/.exec(line)?.[1]).filter((sha) => sha !== void 0);
|
|
1792
|
+
}
|
|
1793
|
+
async function candidateChanges(cwd, sha) {
|
|
1794
|
+
const outcome = await execGit(
|
|
1795
|
+
["diff-tree", "-z", "-r", "--no-commit-id", "--no-renames", "--name-only", sha],
|
|
1796
|
+
{ cwd }
|
|
1797
|
+
);
|
|
1798
|
+
if (outcome.exitCode !== 0) {
|
|
1799
|
+
throw new PlanningStoragePublishError(
|
|
1800
|
+
`Cannot read the changes of pending candidate ${sha}: ${outcome.stderr.trim()}`
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1803
|
+
return outcome.stdout.split("\0").filter(Boolean);
|
|
1804
|
+
}
|
|
1805
|
+
function dependentsOf(order, ref) {
|
|
1806
|
+
return order.ordered.filter((entry) => order.parents.get(entry.ref)?.has(ref)).map((entry) => entry.ref);
|
|
1807
|
+
}
|
|
1808
|
+
async function conflictsWithTip(cwd, sha, paths, tip) {
|
|
1809
|
+
if (!tip || await isAncestor(cwd, sha, tip)) return [];
|
|
1810
|
+
const base = await resolveRevision(cwd, `${sha}^`);
|
|
1811
|
+
if (!base || base === tip) return [];
|
|
1812
|
+
return await divergedPaths(cwd, base, tip, paths);
|
|
1813
|
+
}
|
|
1814
|
+
async function inspectPendingCandidates(ctx, refs) {
|
|
1815
|
+
if (refs.length === 0) return [];
|
|
1816
|
+
const cwd = ctx.projectRoot;
|
|
1817
|
+
const order = await orderByDependency(cwd, refs);
|
|
1818
|
+
const tip = await resolveRevision(cwd, trackingRef(ctx));
|
|
1819
|
+
const details = [];
|
|
1820
|
+
for (const { ref, sha } of order.ordered) {
|
|
1821
|
+
const dependsOn = [...order.parents.get(ref) ?? []];
|
|
1822
|
+
const paths = await candidateChanges(cwd, sha).catch(() => []);
|
|
1823
|
+
const conflicts = dependsOn.length > 0 ? [] : await conflictsWithTip(cwd, sha, paths, tip);
|
|
1824
|
+
details.push({ ref, sha, paths, conflicts, dependsOn, dependents: dependentsOf(order, ref) });
|
|
1825
|
+
}
|
|
1826
|
+
return details;
|
|
1827
|
+
}
|
|
1828
|
+
function pendingRefArgument(input) {
|
|
1829
|
+
const opId = input.startsWith(PENDING_REF_PREFIX) ? input.slice(PENDING_REF_PREFIX.length) : input;
|
|
1830
|
+
return pendingRefFor(opId);
|
|
1831
|
+
}
|
|
1832
|
+
async function resolvePendingConflict(ctx, refInput, take) {
|
|
1833
|
+
const ref = pendingRefArgument(refInput);
|
|
1834
|
+
const cwd = ctx.projectRoot;
|
|
1835
|
+
const refuse = (why) => {
|
|
1836
|
+
throw new PlanningStoragePublishError(`Cannot resolve ${ref}: ${why}`);
|
|
1837
|
+
};
|
|
1838
|
+
const state = { dependents: [] };
|
|
1839
|
+
const publication = await publishReplacement(ctx, ref, async (local) => {
|
|
1840
|
+
const fetched = await fetchStorageBranch(ctx, "always");
|
|
1841
|
+
if (!fetched.fetched) {
|
|
1842
|
+
refuse(
|
|
1843
|
+
fetched.rewound ? `remote storage branch was rewound (${fetched.rewound.previous}..${fetched.rewound.remote})` : `required fetch failed: ${fetched.reason ?? "no reason reported"}`
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1846
|
+
const tip = await resolveRevision(cwd, trackingRef(ctx)) ?? refuse("no remote tip");
|
|
1847
|
+
const order = await orderByDependency(cwd, await listPendingRefs(cwd));
|
|
1848
|
+
const entry = order.ordered.find((candidate) => candidate.ref === ref) ?? refuse("no such pending candidate; inspect with: build plan storage status");
|
|
1849
|
+
const parents = [...order.parents.get(ref) ?? []];
|
|
1850
|
+
if (parents.length > 0) {
|
|
1851
|
+
refuse(`it depends on pending ${parents.join(", ")}; resolve or publish that first`);
|
|
1852
|
+
}
|
|
1853
|
+
state.dependents = dependentsOf(order, ref);
|
|
1854
|
+
if (await isAncestor(cwd, entry.sha, tip)) {
|
|
1855
|
+
refuse("the remote already contains it; finish with: build plan storage sync");
|
|
1856
|
+
}
|
|
1857
|
+
const paths = await candidateChanges(cwd, entry.sha);
|
|
1858
|
+
const conflicts = await conflictsWithTip(cwd, entry.sha, paths, tip);
|
|
1859
|
+
if (conflicts.length === 0) {
|
|
1860
|
+
refuse("it does not conflict with the remote tip; publish it with: build plan storage sync");
|
|
1861
|
+
}
|
|
1862
|
+
const unsuitable = await refuseUnsuitableTip(ctx, tip);
|
|
1863
|
+
if (unsuitable) refuse(unsuitable);
|
|
1864
|
+
const message = await execGit(["log", "-1", "--format=%B", entry.sha], { cwd });
|
|
1865
|
+
if (message.exitCode !== 0) refuse(`cannot read the message of ${entry.sha}`);
|
|
1866
|
+
const kept = new Set(take === "remote" ? conflicts : []);
|
|
1867
|
+
const replacementMessage = `${message.stdout.replace(/\s+$/, "")}
|
|
1868
|
+
|
|
1869
|
+
${RESOLVED_PREFIX}kept the ${take} version of ${conflicts.join(", ")}; replaces ${entry.sha}`;
|
|
1870
|
+
const outcome = await local(async (lock) => {
|
|
1871
|
+
await lock.assertOwned();
|
|
1872
|
+
const stagedPaths = paths.filter((file) => !kept.has(file));
|
|
1873
|
+
const replacement = await rebuildOnto(
|
|
1874
|
+
ctx,
|
|
1875
|
+
{ sha: entry.sha, stagedPaths, message: replacementMessage },
|
|
1876
|
+
tip
|
|
1877
|
+
);
|
|
1878
|
+
await writePendingRef(cwd, ref, replacement, entry.sha);
|
|
1879
|
+
const viewKept = await retargetWorktreeHead(ctx.storageRoot, entry.sha, replacement);
|
|
1880
|
+
return { replacement, viewKept };
|
|
1881
|
+
});
|
|
1882
|
+
state.recorded = {
|
|
1883
|
+
ref,
|
|
1884
|
+
original: entry.sha,
|
|
1885
|
+
replacement: outcome.replacement,
|
|
1886
|
+
take,
|
|
1887
|
+
conflicts,
|
|
1888
|
+
...outcome.viewKept ? { viewKept: outcome.viewKept } : {}
|
|
1889
|
+
};
|
|
1890
|
+
});
|
|
1891
|
+
const recorded = state.recorded ?? refuse("no replacement was recorded");
|
|
1892
|
+
const remaining = new Set((await listPendingRefs(cwd)).map((entry) => entry.ref));
|
|
1893
|
+
return {
|
|
1894
|
+
...recorded,
|
|
1895
|
+
publication,
|
|
1896
|
+
dependents: state.dependents.filter((dependent) => remaining.has(dependent))
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
// src/planning/storage/work-branch.ts
|
|
1901
|
+
async function ensureMaterialized(ctx, opts) {
|
|
1902
|
+
if (ctx.backend !== "git-branch") {
|
|
1903
|
+
return { materialized: true, created: false };
|
|
1904
|
+
}
|
|
1905
|
+
const run = async () => materializeUnlocked(ctx, opts?.fetch ?? "stale");
|
|
1906
|
+
if (opts?.skipWhenBusy && await hasCompletedCheckout(ctx.storageRoot)) {
|
|
1907
|
+
try {
|
|
1908
|
+
return await withStorageLock(ctx.storageRoot, run, { wait: false });
|
|
1909
|
+
} catch (error) {
|
|
1910
|
+
if (isStorageLockBusyError(error)) return { materialized: true, created: false };
|
|
1911
|
+
throw error;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
return await withStorageLock(ctx.storageRoot, run);
|
|
1915
|
+
}
|
|
1916
|
+
async function getStorageStatus(ctx) {
|
|
1917
|
+
const hold = isPlanningStorageHeld();
|
|
1918
|
+
let pendingRefs = [];
|
|
1919
|
+
let pendingRefsError;
|
|
1920
|
+
try {
|
|
1921
|
+
pendingRefs = await listPendingRefs(ctx.projectRoot);
|
|
1922
|
+
} catch (error) {
|
|
1923
|
+
pendingRefsError = error instanceof Error ? error.message : String(error);
|
|
1924
|
+
}
|
|
1925
|
+
if (ctx.backend !== "git-branch") {
|
|
1926
|
+
return {
|
|
1927
|
+
backend: ctx.backend,
|
|
1928
|
+
projectRoot: ctx.projectRoot,
|
|
1929
|
+
storageRoot: ctx.storageRoot,
|
|
1930
|
+
materialized: true,
|
|
1931
|
+
available: true,
|
|
1932
|
+
pendingRefs,
|
|
1933
|
+
...pendingRefsError ? { pendingRefsError } : {},
|
|
1934
|
+
hold
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
const materialized = await pathExists(ctx.storageRoot);
|
|
1938
|
+
const available = materialized && await isLinkedWorktree(ctx.storageRoot);
|
|
1939
|
+
const remoteTipSha = await resolveRevision(ctx.projectRoot, trackingRef(ctx));
|
|
1940
|
+
const remoteTipFetchedAt = remoteTipSha ? await readLastFetchTime(ctx) : void 0;
|
|
1941
|
+
const headSha = available ? await resolveRevision(ctx.storageRoot, "HEAD") : void 0;
|
|
1942
|
+
const worktreeId = available ? await readWorktreeId(ctx.storageRoot) : void 0;
|
|
1943
|
+
const behind = available && headSha && remoteTipSha ? await countBehind(ctx.storageRoot, headSha, remoteTipSha) : void 0;
|
|
1944
|
+
const publicationTurn = await inspectStorageTurn(ctx);
|
|
1945
|
+
const pendingDetails = pendingRefsError ? void 0 : await inspectPendingCandidates(ctx, pendingRefs).catch(() => void 0);
|
|
1946
|
+
const uncommitted = available ? await inspectUncommittedStoragePaths(ctx.storageRoot) : {};
|
|
1947
|
+
return {
|
|
1948
|
+
backend: ctx.backend,
|
|
1949
|
+
projectRoot: ctx.projectRoot,
|
|
1950
|
+
storageRoot: ctx.storageRoot,
|
|
1951
|
+
...ctx.branch ? { branch: ctx.branch } : {},
|
|
1952
|
+
...ctx.remote ? { remote: ctx.remote } : {},
|
|
1953
|
+
materialized,
|
|
1954
|
+
available,
|
|
1955
|
+
...available ? {} : {
|
|
1956
|
+
unavailableReason: materialized ? `${ctx.storageRoot} exists but is not a linked git worktree` : `${ctx.storageRoot} is not materialized`
|
|
1957
|
+
},
|
|
1958
|
+
...headSha ? { headSha } : {},
|
|
1959
|
+
...remoteTipSha ? { remoteTipSha } : {},
|
|
1960
|
+
...remoteTipFetchedAt ? { remoteTipFetchedAt } : {},
|
|
1961
|
+
...behind === void 0 ? {} : { behind },
|
|
1962
|
+
pendingRefs,
|
|
1963
|
+
...pendingRefsError ? { pendingRefsError } : {},
|
|
1964
|
+
...pendingDetails?.length ? { pendingDetails } : {},
|
|
1965
|
+
...uncommitted,
|
|
1966
|
+
hold,
|
|
1967
|
+
...worktreeId ? { worktreeId } : {},
|
|
1968
|
+
...publicationTurn ? { publicationTurn } : {}
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1971
|
+
async function inspectUncommittedStoragePaths(storageRoot) {
|
|
1972
|
+
try {
|
|
1973
|
+
const porcelain = await readPorcelainStatus(storageRoot, { untracked: true });
|
|
1974
|
+
return { uncommittedPaths: parseGitStatusPaths(porcelain) };
|
|
1975
|
+
} catch (error) {
|
|
1976
|
+
return { uncommittedPathsError: error instanceof Error ? error.message : String(error) };
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
async function countBehind(storageRoot, headSha, tip) {
|
|
1980
|
+
if (headSha === tip) return 0;
|
|
1981
|
+
const result2 = await execGit(["rev-list", "--count", `${headSha}..${tip}`], { cwd: storageRoot });
|
|
1982
|
+
if (result2.exitCode !== 0) return void 0;
|
|
1983
|
+
const count = Number.parseInt(result2.stdout.trim(), 10);
|
|
1984
|
+
return Number.isFinite(count) ? count : void 0;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
export {
|
|
1988
|
+
resolveLegacyBucketLayout,
|
|
1989
|
+
assertNoPlanningStorageHold,
|
|
1990
|
+
assertNoStorageDrift,
|
|
1991
|
+
recoverRetainedTurn,
|
|
1992
|
+
publishManagedChange,
|
|
1993
|
+
recordManagedChange,
|
|
1994
|
+
publishPending,
|
|
1995
|
+
CONFLICT_CHOICES,
|
|
1996
|
+
replacedCommits,
|
|
1997
|
+
resolvePendingConflict,
|
|
1998
|
+
ensureMaterialized,
|
|
1999
|
+
getStorageStatus,
|
|
2000
|
+
inspectUncommittedStoragePaths
|
|
2001
|
+
};
|
|
2002
|
+
//# sourceMappingURL=chunk-SNGM452K.js.map
|