@poetic-ai/poetic 1.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/SECURITY.md +47 -0
- package/.nvmrc +1 -0
- package/.poetic/README.md +37 -0
- package/.poetic/providers/catalog.json +12051 -0
- package/.poetic/providers/pricing.json +1272 -0
- package/.poetic/providers/registry.json +3369 -0
- package/CHANGELOG.md +552 -0
- package/CODE_OF_CONDUCT.md +40 -0
- package/CONTRIBUTING.md +23 -0
- package/INSTALL.md +454 -0
- package/LICENSE +21 -0
- package/README.md +474 -0
- package/dist/BasicOptimizationCompetitionRunner-5H5TBYO4.js +153 -0
- package/dist/ExecutionTracker-UVONC4C6.js +16 -0
- package/dist/OptimizationConfig-7DFY2TST.js +17 -0
- package/dist/OptimizationEngine-JIMGP3LY.js +1597 -0
- package/dist/PromptStore-U6ZD3ZMQ.js +18 -0
- package/dist/actions-LUJPINLJ.js +322 -0
- package/dist/agent-ingest-3DJUJ7FU.js +53 -0
- package/dist/aggregator-DNCBINFO.js +12 -0
- package/dist/ai-judge-5QT5QJVU.js +156 -0
- package/dist/allowlist-grounding-RO6HKUFI.js +16 -0
- package/dist/allowlist-utils-23V7TJ3G.js +28 -0
- package/dist/anchored-turn-service-JULUCUIK.js +489 -0
- package/dist/api-transport-4TYTNLTJ.js +1087 -0
- package/dist/apply-completion-mode-AW2MOS3R.js +89 -0
- package/dist/artifact-migrator-23L45ISD.js +267 -0
- package/dist/ask-HWVV6QFR.js +135 -0
- package/dist/auth-5KEDKAPP.js +67 -0
- package/dist/auth-7TT3JINV.js +78 -0
- package/dist/auth-E6XUNZ5M.js +68 -0
- package/dist/auth-H3XDZE22.js +134 -0
- package/dist/auth-OC4E6633.js +71 -0
- package/dist/auth-S4CTX4GQ.js +44 -0
- package/dist/auth-YLH3S7EY.js +69 -0
- package/dist/auth-liveness-TDDEZY3T.js +46 -0
- package/dist/auto-optimizer-VPRWOWGU.js +80 -0
- package/dist/autoloop-cleanup-JOGKTLWA.js +109 -0
- package/dist/autoloop-evidence-gates-PCDALGVJ.js +38 -0
- package/dist/autoloop-gate-policy-Z7FVJVXV.js +175 -0
- package/dist/autoloop-helpers-2KTK43GL.js +22 -0
- package/dist/autoloop-ledger-W7QO62PX.js +96 -0
- package/dist/autoloop-ledger-subscriber-Z5RW5EIE.js +162 -0
- package/dist/autoloop-run-defaults-FERGRZGP.js +19 -0
- package/dist/autoloop-run-lock-CT2NWSV3.js +197 -0
- package/dist/autoloop-success-J2VKKBXQ.js +85 -0
- package/dist/autoloop-termination-summary-RN75F422.js +58 -0
- package/dist/autoloop-trajectory-XFIEXP5Z.js +282 -0
- package/dist/autoloop-wall-clock-T7ZVIQOC.js +12 -0
- package/dist/autonomous-loop-controller-PW4XVFGQ.js +4120 -0
- package/dist/backend-P3DAYRP7.js +29 -0
- package/dist/background-executor-CBN552V4.js +257 -0
- package/dist/backlog-execution-intent-U4XYPTV6.js +15 -0
- package/dist/backup-active-store-5WXG6SXT.js +17 -0
- package/dist/backup-telemetry-db-XGLXAA3N.js +254 -0
- package/dist/basic-competition-master-VCSBJVRN.js +344 -0
- package/dist/branch-archive-manager-RP6FP3ZC.js +254 -0
- package/dist/branch-cleanup-manager-RSHAI7YR.js +20 -0
- package/dist/build-gate-YHGRB4QB.js +71 -0
- package/dist/change-summary-WY7J5TOX.js +33 -0
- package/dist/chunk-23A26CSN.js +123 -0
- package/dist/chunk-27UTJD3M.js +265 -0
- package/dist/chunk-2A5CILN3.js +310 -0
- package/dist/chunk-2AQUWJPW.js +225 -0
- package/dist/chunk-2BZ53J6J.js +2344 -0
- package/dist/chunk-2DY5KNVM.js +276 -0
- package/dist/chunk-2EIA4RBE.js +659 -0
- package/dist/chunk-2IUXIGQD.js +1064 -0
- package/dist/chunk-2N42MS2Z.js +2193 -0
- package/dist/chunk-2QJ3H3L7.js +42 -0
- package/dist/chunk-2QLYTVYX.js +791 -0
- package/dist/chunk-2VSQMRCB.js +18 -0
- package/dist/chunk-2XQXYBM2.js +130 -0
- package/dist/chunk-2YFUIER7.js +426 -0
- package/dist/chunk-34CNP2HB.js +148 -0
- package/dist/chunk-35P3W3JX.js +456 -0
- package/dist/chunk-36PEBZAF.js +18 -0
- package/dist/chunk-3AOKYKA7.js +22 -0
- package/dist/chunk-3BCUNHKF.js +475 -0
- package/dist/chunk-3FLXLLB7.js +26 -0
- package/dist/chunk-3FOZ2VHV.js +100 -0
- package/dist/chunk-3G6FZCRF.js +106 -0
- package/dist/chunk-3M57DADF.js +44 -0
- package/dist/chunk-3S62LLWJ.js +543 -0
- package/dist/chunk-3ZPQLGV7.js +2904 -0
- package/dist/chunk-44N5WZZH.js +45 -0
- package/dist/chunk-45FPSA3B.js +125 -0
- package/dist/chunk-4BJ3O4RQ.js +929 -0
- package/dist/chunk-4BOP7T6L.js +2321 -0
- package/dist/chunk-4BTDUT2S.js +42 -0
- package/dist/chunk-4CSS7WVI.js +143 -0
- package/dist/chunk-4CUY7AFY.js +247 -0
- package/dist/chunk-4GSN5O5J.js +62 -0
- package/dist/chunk-4HN6DF7V.js +1027 -0
- package/dist/chunk-4IAPOG5V.js +415 -0
- package/dist/chunk-4IYIZQWV.js +48 -0
- package/dist/chunk-4NXPXE62.js +84 -0
- package/dist/chunk-4RCCF5BR.js +712 -0
- package/dist/chunk-4V7NAQ4R.js +19527 -0
- package/dist/chunk-4VJITV4P.js +445 -0
- package/dist/chunk-4YX7J6QL.js +1859 -0
- package/dist/chunk-527OBWQB.js +338 -0
- package/dist/chunk-5D4A7E3D.js +448 -0
- package/dist/chunk-5DLKYTQX.js +4003 -0
- package/dist/chunk-5EERLVZB.js +139 -0
- package/dist/chunk-5H72XE4H.js +256 -0
- package/dist/chunk-5HDP7XZG.js +91 -0
- package/dist/chunk-5LAMRH4X.js +969 -0
- package/dist/chunk-5LTN43AS.js +1972 -0
- package/dist/chunk-5RBHQYDB.js +2206 -0
- package/dist/chunk-5WCQRDRB.js +588 -0
- package/dist/chunk-5WRLK5KW.js +700 -0
- package/dist/chunk-5ZCXC23G.js +595 -0
- package/dist/chunk-6BM72ZMG.js +31 -0
- package/dist/chunk-6EDAJH2I.js +68 -0
- package/dist/chunk-6EQKFDBH.js +457 -0
- package/dist/chunk-6MFRHTRI.js +115 -0
- package/dist/chunk-6TZJRKNW.js +321 -0
- package/dist/chunk-6V7HGQFZ.js +20 -0
- package/dist/chunk-6VWVO2TU.js +3746 -0
- package/dist/chunk-6Y5TWI7H.js +146 -0
- package/dist/chunk-6Y5U7UFY.js +91 -0
- package/dist/chunk-6ZLAK3XG.js +14 -0
- package/dist/chunk-72XCRQED.js +34 -0
- package/dist/chunk-72YTZMAM.js +589 -0
- package/dist/chunk-73BBJOKA.js +263 -0
- package/dist/chunk-73NCTWKL.js +188 -0
- package/dist/chunk-77I6G4CO.js +1 -0
- package/dist/chunk-7DHRDWFS.js +3104 -0
- package/dist/chunk-7DNSNKJV.js +78 -0
- package/dist/chunk-7FMJVYBS.js +307 -0
- package/dist/chunk-7GDRQBQC.js +419 -0
- package/dist/chunk-7GPCEFUV.js +270 -0
- package/dist/chunk-7IL4A2PP.js +948 -0
- package/dist/chunk-7MMLMXO6.js +3679 -0
- package/dist/chunk-7QCZSLBH.js +1 -0
- package/dist/chunk-7R2WLDOS.js +62 -0
- package/dist/chunk-7T7RUJI7.js +949 -0
- package/dist/chunk-A2WKSVX7.js +43 -0
- package/dist/chunk-A3YGID55.js +133 -0
- package/dist/chunk-A722DEVA.js +14 -0
- package/dist/chunk-ACDIT7OP.js +1376 -0
- package/dist/chunk-ACKIPAOJ.js +302 -0
- package/dist/chunk-APPNGGN7.js +365 -0
- package/dist/chunk-APV6MK5E.js +137 -0
- package/dist/chunk-ASEP3M2W.js +968 -0
- package/dist/chunk-AT4TNPWW.js +2931 -0
- package/dist/chunk-ATVKCSGT.js +250 -0
- package/dist/chunk-AURFMVCD.js +899 -0
- package/dist/chunk-AX6GWE7V.js +47 -0
- package/dist/chunk-AY5EWJPF.js +385 -0
- package/dist/chunk-BAV2HJXS.js +194 -0
- package/dist/chunk-BBHRN366.js +89 -0
- package/dist/chunk-BBPSTTPA.js +1004 -0
- package/dist/chunk-BDATUDDJ.js +992 -0
- package/dist/chunk-BKE5PG5L.js +20414 -0
- package/dist/chunk-BLPHWMDK.js +797 -0
- package/dist/chunk-BSE4R6XD.js +322 -0
- package/dist/chunk-BXGMAU4A.js +396 -0
- package/dist/chunk-BXI4NXL3.js +507 -0
- package/dist/chunk-BZU2O43F.js +1125 -0
- package/dist/chunk-C3L6YQ7P.js +147 -0
- package/dist/chunk-C57KJOUQ.js +121 -0
- package/dist/chunk-CCV7BPSY.js +118 -0
- package/dist/chunk-CD7ISXA4.js +826 -0
- package/dist/chunk-CE5OZBYY.js +21 -0
- package/dist/chunk-CFBIG37O.js +154 -0
- package/dist/chunk-CJC6GZ46.js +457 -0
- package/dist/chunk-CKCDGXIV.js +408 -0
- package/dist/chunk-CKNX3TTP.js +10338 -0
- package/dist/chunk-CLKSETWE.js +10 -0
- package/dist/chunk-CP7H3HSP.js +1552 -0
- package/dist/chunk-CPLEWXNZ.js +66 -0
- package/dist/chunk-CQM3A35X.js +844 -0
- package/dist/chunk-CS6P76D3.js +103 -0
- package/dist/chunk-CTLUBNCW.js +622 -0
- package/dist/chunk-CUSRN6RJ.js +632 -0
- package/dist/chunk-D5EP5D2W.js +26 -0
- package/dist/chunk-D5RYINO7.js +412 -0
- package/dist/chunk-DBFS4HMH.js +751 -0
- package/dist/chunk-DC4ZYMXJ.js +115 -0
- package/dist/chunk-DCBZP442.js +9243 -0
- package/dist/chunk-DCYF7EKG.js +26 -0
- package/dist/chunk-DDMAFNUK.js +5351 -0
- package/dist/chunk-DF5SLDE4.js +966 -0
- package/dist/chunk-DJFW7SMX.js +348 -0
- package/dist/chunk-DQFLLBJW.js +121 -0
- package/dist/chunk-DSFM56OA.js +1203 -0
- package/dist/chunk-DTY4APYV.js +218 -0
- package/dist/chunk-DXH5JBRS.js +437 -0
- package/dist/chunk-E3NTPHEO.js +194 -0
- package/dist/chunk-E74LUIID.js +302 -0
- package/dist/chunk-EI57M5BI.js +194 -0
- package/dist/chunk-EJ4ZAWLV.js +305 -0
- package/dist/chunk-EJGCZGUF.js +1483 -0
- package/dist/chunk-EK2U3YEG.js +486 -0
- package/dist/chunk-ELMFYNV7.js +109 -0
- package/dist/chunk-ENRGKNR5.js +25 -0
- package/dist/chunk-ETYDXCK7.js +679 -0
- package/dist/chunk-EYRH3W74.js +239 -0
- package/dist/chunk-F23DIWNN.js +1024 -0
- package/dist/chunk-F5JO7HAS.js +94 -0
- package/dist/chunk-F7ER6ANO.js +174 -0
- package/dist/chunk-FATUVIIN.js +374 -0
- package/dist/chunk-FC23CTXV.js +322 -0
- package/dist/chunk-FD4ERYEG.js +22 -0
- package/dist/chunk-FH7WOJAE.js +79 -0
- package/dist/chunk-FHOWVDF5.js +1342 -0
- package/dist/chunk-FKAUPHMR.js +96 -0
- package/dist/chunk-FONNFJXA.js +137 -0
- package/dist/chunk-FQ7FSZRW.js +75 -0
- package/dist/chunk-FRG57FPM.js +240 -0
- package/dist/chunk-FUVP64ER.js +31 -0
- package/dist/chunk-FYOILAN5.js +6818 -0
- package/dist/chunk-G35UYQ6Z.js +69 -0
- package/dist/chunk-G36W2T2S.js +1143 -0
- package/dist/chunk-G3L2L5XZ.js +49 -0
- package/dist/chunk-G4THLFV3.js +148 -0
- package/dist/chunk-GAFJEYLK.js +29 -0
- package/dist/chunk-GFGYB2ZU.js +1689 -0
- package/dist/chunk-GG6KJYW6.js +85 -0
- package/dist/chunk-GHHFDU2U.js +55 -0
- package/dist/chunk-GHTZSQB7.js +57 -0
- package/dist/chunk-GK37ILCX.js +1231 -0
- package/dist/chunk-GKN3HMN4.js +2809 -0
- package/dist/chunk-GNVDBVJP.js +156 -0
- package/dist/chunk-GPEL3FBM.js +924 -0
- package/dist/chunk-GPSNGPZS.js +1604 -0
- package/dist/chunk-GSGD5E4C.js +30 -0
- package/dist/chunk-GSUQJJXQ.js +331 -0
- package/dist/chunk-GSV226X6.js +110 -0
- package/dist/chunk-H2AYQIHW.js +224 -0
- package/dist/chunk-H66DXFS5.js +1374 -0
- package/dist/chunk-H6G7QRMQ.js +35 -0
- package/dist/chunk-H7JAGHZT.js +59 -0
- package/dist/chunk-H7ZEFECA.js +316 -0
- package/dist/chunk-HAC6EHZZ.js +79 -0
- package/dist/chunk-HIO33P3G.js +19 -0
- package/dist/chunk-HMVQIPH3.js +547 -0
- package/dist/chunk-HOYBYAOA.js +257 -0
- package/dist/chunk-HX5P3CSP.js +1784 -0
- package/dist/chunk-I46EG2XQ.js +65 -0
- package/dist/chunk-I4NPDSCB.js +279 -0
- package/dist/chunk-I5WRT7ZR.js +514 -0
- package/dist/chunk-IC7I6YIJ.js +301 -0
- package/dist/chunk-IDCSOZVN.js +78 -0
- package/dist/chunk-IFCBYECK.js +112 -0
- package/dist/chunk-IFZCPEY2.js +66 -0
- package/dist/chunk-IKFP2KUR.js +48 -0
- package/dist/chunk-IRFBN336.js +878 -0
- package/dist/chunk-IUXNGKA6.js +152 -0
- package/dist/chunk-IV2RVAN7.js +1356 -0
- package/dist/chunk-IZYIQQ7X.js +53 -0
- package/dist/chunk-J3UUR7DE.js +210 -0
- package/dist/chunk-J54VUAY2.js +566 -0
- package/dist/chunk-J62KPGII.js +464 -0
- package/dist/chunk-J6P3FWWW.js +130 -0
- package/dist/chunk-JKBHJQMT.js +434 -0
- package/dist/chunk-JLS65NUQ.js +14 -0
- package/dist/chunk-JOSYEJKY.js +141 -0
- package/dist/chunk-JPKSXKNB.js +94 -0
- package/dist/chunk-JQNJB2BB.js +2440 -0
- package/dist/chunk-JRVNSWSQ.js +2626 -0
- package/dist/chunk-JSWMRQYG.js +28 -0
- package/dist/chunk-JZKNBBIB.js +152 -0
- package/dist/chunk-K54Y7MLK.js +160 -0
- package/dist/chunk-K65546RE.js +183 -0
- package/dist/chunk-KM3F7SYE.js +702 -0
- package/dist/chunk-KNDYMVWN.js +102 -0
- package/dist/chunk-KNT72VR6.js +132 -0
- package/dist/chunk-KP6YILMU.js +482 -0
- package/dist/chunk-KQEBSK3T.js +274 -0
- package/dist/chunk-KT6VHSPF.js +84 -0
- package/dist/chunk-KTMW65UX.js +14 -0
- package/dist/chunk-KZ5CJVBP.js +1813 -0
- package/dist/chunk-L4427KOE.js +26 -0
- package/dist/chunk-L6OM4A22.js +189 -0
- package/dist/chunk-L7E6XCRA.js +1042 -0
- package/dist/chunk-L7JZLXQL.js +173 -0
- package/dist/chunk-LAMKBTXX.js +40 -0
- package/dist/chunk-LDCF2MKN.js +132 -0
- package/dist/chunk-LEIIWPWQ.js +78 -0
- package/dist/chunk-LFTQVYBB.js +31 -0
- package/dist/chunk-LG3PDRTR.js +2872 -0
- package/dist/chunk-LGHII5ET.js +23 -0
- package/dist/chunk-LH5QY22X.js +1205 -0
- package/dist/chunk-LIVJ6266.js +438 -0
- package/dist/chunk-LKRAR3IH.js +276 -0
- package/dist/chunk-LO23NABI.js +3063 -0
- package/dist/chunk-LO3X5NLS.js +326 -0
- package/dist/chunk-LPITVM7M.js +339 -0
- package/dist/chunk-LR5IJIGV.js +810 -0
- package/dist/chunk-LRGMHROI.js +79 -0
- package/dist/chunk-LUVKFFWR.js +315 -0
- package/dist/chunk-LWCBNGH6.js +35 -0
- package/dist/chunk-LWTN3WRV.js +260 -0
- package/dist/chunk-M3HQYKQX.js +308 -0
- package/dist/chunk-M6GVSPEH.js +1016 -0
- package/dist/chunk-MF5HP6XV.js +221 -0
- package/dist/chunk-MHLCBZJB.js +328 -0
- package/dist/chunk-MN6PL4AN.js +120 -0
- package/dist/chunk-MOEY65Z6.js +5678 -0
- package/dist/chunk-MQ4WA34C.js +206 -0
- package/dist/chunk-MWHF5V7U.js +223 -0
- package/dist/chunk-MWHLBPNU.js +211 -0
- package/dist/chunk-MWK5UZQ3.js +1068 -0
- package/dist/chunk-MYLH2S4G.js +64 -0
- package/dist/chunk-MZ5WYKNA.js +28 -0
- package/dist/chunk-N4VTRA7K.js +131 -0
- package/dist/chunk-N5DX4JAK.js +12 -0
- package/dist/chunk-N65O4XTH.js +5011 -0
- package/dist/chunk-NEQUQHOX.js +223 -0
- package/dist/chunk-NGK6PEOG.js +25 -0
- package/dist/chunk-NJVD4PWE.js +1033 -0
- package/dist/chunk-NM6PMYY4.js +768 -0
- package/dist/chunk-NN4PJDHP.js +708 -0
- package/dist/chunk-NP2V3L7K.js +226 -0
- package/dist/chunk-NRLY536M.js +260 -0
- package/dist/chunk-NRNE225N.js +1270 -0
- package/dist/chunk-NS2K2GTP.js +605 -0
- package/dist/chunk-NS33V3IM.js +51 -0
- package/dist/chunk-NSHASMZZ.js +71 -0
- package/dist/chunk-NWOYOA6H.js +378 -0
- package/dist/chunk-NWRUSEXG.js +322 -0
- package/dist/chunk-NZGDAWIP.js +1905 -0
- package/dist/chunk-O47UOKU3.js +395 -0
- package/dist/chunk-O4G2W3ST.js +9831 -0
- package/dist/chunk-O6V4PNLO.js +63 -0
- package/dist/chunk-OA4GPILI.js +624 -0
- package/dist/chunk-OGMSYL6J.js +227 -0
- package/dist/chunk-OIS7J26S.js +319 -0
- package/dist/chunk-OMX5VL43.js +79 -0
- package/dist/chunk-ON3G73BU.js +264 -0
- package/dist/chunk-OPYFYTWQ.js +220 -0
- package/dist/chunk-OQ6K46CK.js +1734 -0
- package/dist/chunk-OYZYO7TV.js +47 -0
- package/dist/chunk-OZ4REHIN.js +4386 -0
- package/dist/chunk-P47VN5F4.js +282 -0
- package/dist/chunk-P5I4X7A7.js +353 -0
- package/dist/chunk-P6VQROCO.js +594 -0
- package/dist/chunk-P7CQGPLS.js +30 -0
- package/dist/chunk-PCAGC5MR.js +1763 -0
- package/dist/chunk-PFTQBXS6.js +5181 -0
- package/dist/chunk-PGSPX4SU.js +15 -0
- package/dist/chunk-PIKLF7BM.js +432 -0
- package/dist/chunk-PJWJ3SAV.js +22 -0
- package/dist/chunk-PKIFMV72.js +100 -0
- package/dist/chunk-PL37JDNA.js +189 -0
- package/dist/chunk-PTH5E5XO.js +195 -0
- package/dist/chunk-PW6AXLQC.js +4128 -0
- package/dist/chunk-PYITM4N2.js +79 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-Q3RO7N35.js +45 -0
- package/dist/chunk-Q3WIC6GQ.js +2670 -0
- package/dist/chunk-Q5XIXSHX.js +271 -0
- package/dist/chunk-QGJDMEOV.js +34 -0
- package/dist/chunk-QNOB37UH.js +58 -0
- package/dist/chunk-QOCOVLUH.js +462 -0
- package/dist/chunk-QTCJ5CC5.js +172 -0
- package/dist/chunk-QVFV5IP2.js +232 -0
- package/dist/chunk-QVZMFDYG.js +29 -0
- package/dist/chunk-R35PEUKH.js +511 -0
- package/dist/chunk-R4UWBC35.js +200 -0
- package/dist/chunk-R6MLCREW.js +44 -0
- package/dist/chunk-R6W23LKN.js +254 -0
- package/dist/chunk-R7XDDX2A.js +4007 -0
- package/dist/chunk-RDFRCT64.js +168 -0
- package/dist/chunk-RE5VZDFQ.js +58 -0
- package/dist/chunk-RGROYC2G.js +638 -0
- package/dist/chunk-RGWHUI5E.js +68 -0
- package/dist/chunk-RHVG6UNL.js +205 -0
- package/dist/chunk-RI4EX2QE.js +18 -0
- package/dist/chunk-RJAAKDRX.js +17 -0
- package/dist/chunk-ROMHOSMQ.js +185 -0
- package/dist/chunk-RWREC3KJ.js +517 -0
- package/dist/chunk-RYNETRDJ.js +118 -0
- package/dist/chunk-RZY7RKM5.js +500 -0
- package/dist/chunk-S2MNWFAG.js +52 -0
- package/dist/chunk-S2VQCZO4.js +22 -0
- package/dist/chunk-S54MKU6V.js +117 -0
- package/dist/chunk-S5OIDFRM.js +302 -0
- package/dist/chunk-SCW4ZF6R.js +150 -0
- package/dist/chunk-SLS2N4PH.js +2213 -0
- package/dist/chunk-SNLBO4BF.js +247 -0
- package/dist/chunk-SP3LFU6B.js +60 -0
- package/dist/chunk-SQQTDDQA.js +537 -0
- package/dist/chunk-STV6LYYE.js +45 -0
- package/dist/chunk-SURZ2WFE.js +326 -0
- package/dist/chunk-SZ7DL357.js +1047 -0
- package/dist/chunk-T5E4NQCM.js +5742 -0
- package/dist/chunk-TAJPCXSB.js +13 -0
- package/dist/chunk-TAOOYK3P.js +358 -0
- package/dist/chunk-TDSEX5CF.js +24 -0
- package/dist/chunk-TFTGP7Z7.js +174 -0
- package/dist/chunk-TGON4N4O.js +197 -0
- package/dist/chunk-TGQZHDY2.js +2373 -0
- package/dist/chunk-TJJZ2OKV.js +6783 -0
- package/dist/chunk-TRSVUPCX.js +25 -0
- package/dist/chunk-TSXGRLPR.js +107 -0
- package/dist/chunk-U2S3YEX3.js +224 -0
- package/dist/chunk-U45RTRGY.js +818 -0
- package/dist/chunk-U6C3VVWS.js +13 -0
- package/dist/chunk-UDMS5AD3.js +307 -0
- package/dist/chunk-UGX7GI37.js +94 -0
- package/dist/chunk-UH3JJSHK.js +1 -0
- package/dist/chunk-UHRBTZYY.js +208 -0
- package/dist/chunk-UHT2KRG5.js +193 -0
- package/dist/chunk-UI2F6DJ5.js +118 -0
- package/dist/chunk-UMAKSB4Z.js +284 -0
- package/dist/chunk-UR2IBGIM.js +521 -0
- package/dist/chunk-UUH2RVKS.js +585 -0
- package/dist/chunk-UVJ4PJDK.js +4533 -0
- package/dist/chunk-V53UG2OT.js +9924 -0
- package/dist/chunk-V72RMBM4.js +48 -0
- package/dist/chunk-VBNCKCCI.js +16 -0
- package/dist/chunk-VNSYKC25.js +24 -0
- package/dist/chunk-VPHVFK4A.js +1984 -0
- package/dist/chunk-VTLNJQ44.js +133 -0
- package/dist/chunk-VTW7HP3R.js +100 -0
- package/dist/chunk-VVGEPBPS.js +218 -0
- package/dist/chunk-VVLLL7I4.js +166 -0
- package/dist/chunk-VX3UMWB7.js +483 -0
- package/dist/chunk-VXF4Q3FW.js +20 -0
- package/dist/chunk-W3JMT2YY.js +712 -0
- package/dist/chunk-WDUORIHF.js +877 -0
- package/dist/chunk-WDVTY4U6.js +148 -0
- package/dist/chunk-WIFZCHEG.js +12566 -0
- package/dist/chunk-WIRN3F75.js +736 -0
- package/dist/chunk-WMPU2UOV.js +82 -0
- package/dist/chunk-WTOKKE2V.js +784 -0
- package/dist/chunk-WX5IRLF6.js +740 -0
- package/dist/chunk-WZ6U3O7I.js +1581 -0
- package/dist/chunk-XAONGNST.js +57 -0
- package/dist/chunk-XB5YLCLB.js +13 -0
- package/dist/chunk-XEP5NQ5Y.js +688 -0
- package/dist/chunk-XGYF2QMZ.js +351 -0
- package/dist/chunk-XHEKQENB.js +255 -0
- package/dist/chunk-XNO4FM6X.js +1124 -0
- package/dist/chunk-XPHKYHO4.js +878 -0
- package/dist/chunk-XPK2I4NC.js +405 -0
- package/dist/chunk-XPU2XADL.js +2175 -0
- package/dist/chunk-XQD2B6BB.js +507 -0
- package/dist/chunk-XT2ZQVTC.js +558 -0
- package/dist/chunk-XTXJGGUV.js +351 -0
- package/dist/chunk-XUXVDPSZ.js +50 -0
- package/dist/chunk-XV7NZL4B.js +57 -0
- package/dist/chunk-XZE4XARH.js +563 -0
- package/dist/chunk-XZYR5EDO.js +152 -0
- package/dist/chunk-Y3J4JMTU.js +61 -0
- package/dist/chunk-Y55LZN5U.js +52 -0
- package/dist/chunk-Y5VN7DMP.js +112 -0
- package/dist/chunk-Y67VAHN4.js +459 -0
- package/dist/chunk-YAXSKEUH.js +3544 -0
- package/dist/chunk-YB3JD4E6.js +975 -0
- package/dist/chunk-YESZJFK6.js +220 -0
- package/dist/chunk-YGXKOBQQ.js +292 -0
- package/dist/chunk-YMX76IOS.js +31 -0
- package/dist/chunk-YOTXESEH.js +257 -0
- package/dist/chunk-YTYIEZQY.js +119 -0
- package/dist/chunk-YVORHQ2S.js +579 -0
- package/dist/chunk-Z2CUQDAR.js +3429 -0
- package/dist/chunk-Z2KYOFHS.js +7000 -0
- package/dist/chunk-Z6PC7QX6.js +161 -0
- package/dist/chunk-ZDTGECTN.js +235 -0
- package/dist/chunk-ZIPWI2LY.js +151 -0
- package/dist/chunk-ZSKKMTQJ.js +554 -0
- package/dist/chunk-ZTT5IVPY.js +1160 -0
- package/dist/chunk-ZX65UI5W.js +78 -0
- package/dist/chunk-ZYG7GGKO.js +218 -0
- package/dist/chunk-ZZZUL4OF.js +38 -0
- package/dist/cli-utils-IXL26KJT.js +40 -0
- package/dist/cli-validation-WLXWLUJL.js +111 -0
- package/dist/commit-utils-P7CRJF5N.js +202 -0
- package/dist/compete-config-resolver-R5ULXMYH.js +76 -0
- package/dist/compete-request-62JESVET.js +364 -0
- package/dist/compete-results-QEPG6FZX.js +208 -0
- package/dist/competition-outcome-tracker-R32OOZ43.js +38 -0
- package/dist/config-JCOKXOQT.js +166 -0
- package/dist/config-audit-EL3GHXS7.js +360 -0
- package/dist/config-explain-TNILDEVJ.js +12 -0
- package/dist/config-loader-VPFPFO4B.js +38 -0
- package/dist/config-manager-7T4E2BQO.js +67 -0
- package/dist/config-validator-KKOHOW7O.js +72 -0
- package/dist/cost-YC42XUF3.js +53 -0
- package/dist/coverage-orchestrator-CISNKJC2.js +772 -0
- package/dist/coverage-scanner-YVD62MLH.js +12 -0
- package/dist/createCompetitionRunner-LJG6ZQSO.js +46 -0
- package/dist/data-migration-state-VJK64DLT.js +31 -0
- package/dist/db-migrator-XSEIOGWY.js +34 -0
- package/dist/discovery-JGYGDD25.js +25 -0
- package/dist/doctor-GJOYE7AE.js +186 -0
- package/dist/domain-analyzer-AWF3RP6E.js +29 -0
- package/dist/ensure-initialized-744DIZEZ.js +52 -0
- package/dist/entry.js +940 -0
- package/dist/environment-YS45U5ZI.js +81 -0
- package/dist/error-parser-core-PPZ36A4S.js +50 -0
- package/dist/escalation-ladder-MKTCEGSA.js +130 -0
- package/dist/evaluation-context-KV4GZNN7.js +35 -0
- package/dist/evidence-plan-resolver-GBSYSAPX.js +85 -0
- package/dist/execution-data-writer-3ODTBDLH.js +53 -0
- package/dist/execution-policy-applier-ZHD3DCF3.js +31 -0
- package/dist/execution-preflight-Z4Y64V3I.js +209 -0
- package/dist/execution-roles-R3DPPMD3.js +66 -0
- package/dist/exit-code-error-Z4SW2DVK.js +14 -0
- package/dist/external-temp-cleanup-N2RZH4QP.js +311 -0
- package/dist/factory-XBN7DFIM.js +149 -0
- package/dist/file-utils-XIVR2ZMA.js +47 -0
- package/dist/flywheel-autoloop-executor-UILGOFFK.js +249 -0
- package/dist/flywheel-competition-executor-JOBEXTRB.js +259 -0
- package/dist/flywheel-executor-6JFLA6J5.js +369 -0
- package/dist/flywheel-git-isolation-UC5NIEVU.js +48 -0
- package/dist/flywheel-manifest-5UC4VBMA.js +108 -0
- package/dist/flywheel-model-defaults-7CVKP53V.js +27 -0
- package/dist/flywheel-preflight-XEY5SHM6.js +104 -0
- package/dist/flywheel-resume-7HELH7QS.js +23 -0
- package/dist/flywheel-safety-7U7DPOAP.js +28 -0
- package/dist/flywheel-scope-decision-ZHKF6ZIN.js +11 -0
- package/dist/get-telemetry-logger-DPVWWIYY.js +73 -0
- package/dist/git-worktree-IY6V6DUO.js +18 -0
- package/dist/github-pr-manager-4LXEKJTA.js +211 -0
- package/dist/guidance-profile-config-E7XJL2VP.js +117 -0
- package/dist/guidance-profile-renderer-QK2YMAAP.js +21 -0
- package/dist/index-query-4JEUKA44.js +31 -0
- package/dist/index.js +108661 -0
- package/dist/instructions-6BURLQJC.js +64 -0
- package/dist/invoke-provider-auth-GRUYP44M.js +223 -0
- package/dist/judge-scoring.json +80 -0
- package/dist/judge-test.js +555 -0
- package/dist/lab-mode-OTWVHN63.js +39 -0
- package/dist/lab-utils-BJRFLGK4.js +23 -0
- package/dist/linux-host-class-UY4KVLFD.js +29 -0
- package/dist/llm-judge-executor-F7Z3HO77.js +133 -0
- package/dist/local-executor-CEHWE2PL.js +223 -0
- package/dist/logger-33TR6EE5.js +72 -0
- package/dist/loop-spec-XTLQAVZ7.js +191 -0
- package/dist/manager-2HJG5CLT.js +25 -0
- package/dist/matrix-prompt-builder-6UDN5X3F.js +497 -0
- package/dist/matrix-result-parser-7W35EUOV.js +15 -0
- package/dist/metadata-HQ43NDEK.js +22 -0
- package/dist/model-invocations-db-QBO4Y7WZ.js +38 -0
- package/dist/monitor-XGYBG2DW.js +72 -0
- package/dist/next-PB5ZXZKB.js +90 -0
- package/dist/next-ZBDK725T.js +69 -0
- package/dist/optimization-config-resolver-O6WKR47U.js +75 -0
- package/dist/package-3YCWIQQ5.js +10 -0
- package/dist/parallel-orchestrator-NL7YJJU2.js +297 -0
- package/dist/parallel-worker.js +279 -0
- package/dist/parse-git-status-PIAF3OTP.js +10 -0
- package/dist/path-security-ETTH6TZL.js +39 -0
- package/dist/pattern-bank-UNJR5ADV.js +15 -0
- package/dist/plan-backlog-list-fast-VAGYBV3J.js +270 -0
- package/dist/plan-backlog-show-fast-SG2CZPW3.js +406 -0
- package/dist/plan-sprint-list-fast-LURAX47V.js +162 -0
- package/dist/plan-sprint-status-fast-FJAODCHD.js +208 -0
- package/dist/plan-task-status-fast-HNYBMQI4.js +349 -0
- package/dist/plan-to-flywheel-Z4U3GWPN.js +456 -0
- package/dist/planning-backlog-3CQ5QQBQ.js +319 -0
- package/dist/poetic-root-V5DNXEAE.js +17 -0
- package/dist/preflight-OPANWMME.js +12 -0
- package/dist/process-registry-PDZJPTAS.js +27 -0
- package/dist/process-scanner-C22SYXYD.js +22 -0
- package/dist/processes-KMQUD7HY.js +29 -0
- package/dist/processes-json-fast-IBDXZ6WW.js +135 -0
- package/dist/profile-SE5EVXAP.js +149 -0
- package/dist/prompts-WZZPGWBX.js +168 -0
- package/dist/protected-branches-RNTEX7ET.js +57 -0
- package/dist/provider-aware-resource-manager-QBTZJMNP.js +18 -0
- package/dist/provider-matrix-N5X57V3Y.js +154 -0
- package/dist/provider-matrix-renderer-MZDWKKID.js +139 -0
- package/dist/provider-output-forwarding-DERID3TP.js +30 -0
- package/dist/provider-registry-J5NRBJKI.js +128 -0
- package/dist/provider-temp-cleanup-KYGLF6YU.js +14 -0
- package/dist/prune-engine-Q6E2DXBN.js +451 -0
- package/dist/quality-gate-YRHMQAEI.js +70 -0
- package/dist/quickstart-FIRLKGJI.js +62 -0
- package/dist/readiness-VZTMSG6J.js +96 -0
- package/dist/registry-YODFY77A.js +153 -0
- package/dist/repair-helpers-TTV5FEF4.js +117 -0
- package/dist/repo-root-2IGD4H7X.js +22 -0
- package/dist/resolution-engine-D6TX6FRG.js +127 -0
- package/dist/resolve-provider-cli-O2P6XZBZ.js +35 -0
- package/dist/restore-telemetry-db-BQL6DTQF.js +360 -0
- package/dist/result-streamer-WEHW476R.js +19 -0
- package/dist/routing-events-DHPUZDDF.js +27 -0
- package/dist/routing-history-store-UWCEXAP3.js +46 -0
- package/dist/routing-planner-F5WQEUOM.js +160 -0
- package/dist/run-poetic-tui-J6LWRJUP.js +13517 -0
- package/dist/run-simulation.js +376 -0
- package/dist/runner-CS2TNGHA.js +1092 -0
- package/dist/runner-HHH7ZN2Z.js +1571 -0
- package/dist/safety-OCC4KJJR.js +64 -0
- package/dist/safety-stanza-7SV3HGG3.js +60 -0
- package/dist/sandbox-32QXJEMV.js +81 -0
- package/dist/sandbox-RSO7VP6V.js +94 -0
- package/dist/schema-extensions.sql +234 -0
- package/dist/security-2KI5XNDG.js +17 -0
- package/dist/setup-bb-plugin-QAQWZCV5.js +738 -0
- package/dist/setup-claude-code-P7BTTSPO.js +317 -0
- package/dist/setup-temp-cleanup-GASCWZ2I.js +20 -0
- package/dist/shared-utils-D6U6UREP.js +37 -0
- package/dist/simple-artifact-goal-OTXNGZY6.js +25 -0
- package/dist/sprint-UIXPYOXQ.js +338 -0
- package/dist/sprint-bridge-4BHMSC3M.js +68 -0
- package/dist/sprint-execution-service-2B7WHL5L.js +311 -0
- package/dist/sprint-manager-IDQUDNQI.js +91 -0
- package/dist/sqlite-wrapper-UDWCTNLJ.js +17 -0
- package/dist/stale-cleanup-orchestrator-XWXV7KCZ.js +62 -0
- package/dist/storage-access-F6ALDN4S.js +22 -0
- package/dist/synthesis-XD6EDVHF.js +185 -0
- package/dist/task-classifier-LNLXSHOM.js +494 -0
- package/dist/task-list-fast-PTMA4LUC.js +147 -0
- package/dist/task-manager-XAT7GOWB.js +93 -0
- package/dist/task-splitter-U7DJ7W6A.js +79 -0
- package/dist/task-type-detector-OLVOMXZO.js +21 -0
- package/dist/task-type-resolver-KKNQYSB4.js +42 -0
- package/dist/telemetry-5HWBBXLX.js +131 -0
- package/dist/telemetry-health-tracker-DMSSGBZ2.js +11 -0
- package/dist/telemetry-path-resolver-7ICSG276.js +28 -0
- package/dist/telemetry-reliability-XUOZNZZ6.js +125 -0
- package/dist/token-estimator-MEQUL3EJ.js +37 -0
- package/dist/transports-KNSSV2V4.js +255 -0
- package/dist/unified-cost-tracker-4TFRBVPR.js +35 -0
- package/dist/unified-state-cleanup-LJPF2BOW.js +276 -0
- package/dist/universal-cost-calculator-BAJV35GA.js +42 -0
- package/dist/user-agents-GHUGCSKN.js +22 -0
- package/dist/variant-delivery-state-KLRNG6XA.js +33 -0
- package/dist/variant-state-cleanup-MZGGLQAJ.js +280 -0
- package/dist/variant-success-PJXWE3ZV.js +12 -0
- package/dist/variant-worker-KUWKBLAH.js +4196 -0
- package/dist/variant-worker.js +24 -0
- package/dist/variants-JE2FSAQS.js +233 -0
- package/dist/verification-toolchains-V24UHW6Q.js +28 -0
- package/dist/verify-commands-H3DTCSGI.js +37 -0
- package/dist/warning-EGQGUUIO.js +12 -0
- package/dist/work-edge-RWEDWJZF.js +49 -0
- package/dist/work-item-writer-WHAVZXAY.js +19 -0
- package/dist/worker-7SA2VS2Y.js +1435 -0
- package/dist/worker.js +19 -0
- package/dist/worktree-metrics-3DFUYVYE.js +26 -0
- package/dist/writeback-VZKHYHSN.js +44 -0
- package/docs/CLI_REFERENCE.md +7042 -0
- package/docs/PROVIDER_SETUP.md +1570 -0
- package/docs/README.md +96 -0
- package/docs/TROUBLESHOOTING.md +2232 -0
- package/docs/getting-started/QUICK_START.md +294 -0
- package/docs/getting-started/README.md +188 -0
- package/docs/getting-started/SETUP.md +52 -0
- package/docs/reference/AGENT_CONTEXT.md +38 -0
- package/docs/reference/PROVIDER_RELEASE_TIERS.md +34 -0
- package/docs/reference/README.md +381 -0
- package/docs/reference/SECURITY.md +262 -0
- package/integrations/bb-plugin-poetic/README.md +362 -0
- package/integrations/bb-plugin-poetic/app.css +341 -0
- package/integrations/bb-plugin-poetic/app.tsx +5059 -0
- package/integrations/bb-plugin-poetic/package.json +45 -0
- package/integrations/bb-plugin-poetic/server.ts +444 -0
- package/integrations/bb-plugin-poetic/src/adapter.ts +3312 -0
- package/integrations/bb-plugin-poetic/src/backlog-authoring-model.ts +150 -0
- package/integrations/bb-plugin-poetic/src/backlog-authoring-schema.ts +355 -0
- package/integrations/bb-plugin-poetic/src/backlog-authoring-service.ts +419 -0
- package/integrations/bb-plugin-poetic/src/backlog-authoring-view.ts +1026 -0
- package/integrations/bb-plugin-poetic/src/competition-defaults-schema.ts +122 -0
- package/integrations/bb-plugin-poetic/src/competition-defaults-service.ts +105 -0
- package/integrations/bb-plugin-poetic/src/competition-defaults-view.ts +138 -0
- package/integrations/bb-plugin-poetic/src/contract.ts +615 -0
- package/integrations/bb-plugin-poetic/src/finalize-model.ts +205 -0
- package/integrations/bb-plugin-poetic/src/finalize-schema.ts +158 -0
- package/integrations/bb-plugin-poetic/src/finalize-service.ts +382 -0
- package/integrations/bb-plugin-poetic/src/finalize-view.ts +159 -0
- package/integrations/bb-plugin-poetic/src/judge-operations-readback.ts +303 -0
- package/integrations/bb-plugin-poetic/src/judge-operations-schema.ts +33 -0
- package/integrations/bb-plugin-poetic/src/judge-operations-view.ts +459 -0
- package/integrations/bb-plugin-poetic/src/model.ts +1302 -0
- package/integrations/bb-plugin-poetic/src/monitor-service.ts +233 -0
- package/integrations/bb-plugin-poetic/src/panel-read-ux.ts +97 -0
- package/integrations/bb-plugin-poetic/src/patch-preview-schema.ts +166 -0
- package/integrations/bb-plugin-poetic/src/patch-preview-service.ts +480 -0
- package/integrations/bb-plugin-poetic/src/patch-preview-view.ts +109 -0
- package/integrations/bb-plugin-poetic/src/planning-readback.ts +204 -0
- package/integrations/bb-plugin-poetic/src/planning-service.ts +594 -0
- package/integrations/bb-plugin-poetic/src/planning-workspace-schema.ts +95 -0
- package/integrations/bb-plugin-poetic/src/planning-workspace.ts +323 -0
- package/integrations/bb-plugin-poetic/src/poll-handoff.ts +136 -0
- package/integrations/bb-plugin-poetic/src/project-target-server.ts +21 -0
- package/integrations/bb-plugin-poetic/src/project-target.ts +45 -0
- package/integrations/bb-plugin-poetic/src/reference-index.ts +192 -0
- package/integrations/bb-plugin-poetic/src/repository-schema.ts +30 -0
- package/integrations/bb-plugin-poetic/src/result-explorer-view.ts +597 -0
- package/integrations/bb-plugin-poetic/src/result-readback-model.ts +163 -0
- package/integrations/bb-plugin-poetic/src/result-readback-schema.ts +319 -0
- package/integrations/bb-plugin-poetic/src/result-readback-service.ts +333 -0
- package/integrations/bb-plugin-poetic/src/run-page-view.ts +177 -0
- package/integrations/bb-plugin-poetic/src/setup-config-schema.ts +291 -0
- package/integrations/bb-plugin-poetic/src/setup-config-service.ts +590 -0
- package/integrations/bb-plugin-poetic/src/setup-config-view.ts +413 -0
- package/integrations/bb-plugin-poetic/src/sprint-close-model.ts +150 -0
- package/integrations/bb-plugin-poetic/src/sprint-close-schema.ts +136 -0
- package/integrations/bb-plugin-poetic/src/sprint-close-service.ts +341 -0
- package/integrations/bb-plugin-poetic/src/sprint-close-view.ts +116 -0
- package/integrations/bb-plugin-poetic/src/sprint-composition-model.ts +714 -0
- package/integrations/bb-plugin-poetic/src/sprint-composition-readback.ts +231 -0
- package/integrations/bb-plugin-poetic/src/sprint-composition-schema.ts +601 -0
- package/integrations/bb-plugin-poetic/src/sprint-composition-service.ts +775 -0
- package/integrations/bb-plugin-poetic/src/sprint-composition-view.ts +1221 -0
- package/integrations/bb-plugin-poetic/src/task-authoring-model.ts +567 -0
- package/integrations/bb-plugin-poetic/src/task-authoring-schema.ts +635 -0
- package/integrations/bb-plugin-poetic/src/task-authoring-service.ts +842 -0
- package/integrations/bb-plugin-poetic/src/task-authoring-view.ts +1227 -0
- package/integrations/bb-plugin-poetic/src/task-detail-readback.ts +56 -0
- package/integrations/bb-plugin-poetic/src/task-detail-schema.ts +144 -0
- package/integrations/bb-plugin-poetic/src/task-navigation.ts +446 -0
- package/integrations/bb-plugin-poetic/src/workbench-route.ts +74 -0
- package/integrations/bb-plugin-poetic/tests/host-contract.test.ts +773 -0
- package/integrations/bb-plugin-poetic/tsconfig.json +18 -0
- package/integrations/bb-plugin-poetic/types/PROVENANCE.json +52 -0
- package/integrations/bb-plugin-poetic/types/bb-plugin-sdk-app.d.ts +1444 -0
- package/integrations/bb-plugin-poetic/types/bb-plugin-sdk.d.ts +13030 -0
- package/integrations/bb-plugin-poetic/vitest.host.config.ts +71 -0
- package/npm-shrinkwrap.json +4950 -0
- package/package.json +331 -0
- package/schemas/README.md +80 -0
- package/schemas/config-v1.schema.json +136 -0
- package/schemas/execution-config.schema.json +47 -0
- package/schemas/judge-scoring.schema.json +370 -0
- package/schemas/poetic.config.schema.json +2214 -0
- package/schemas/provider-config.schema.json +203 -0
- package/schemas/telemetry-config.schema.json +79 -0
- package/schemas/user-preferences.schema.json +141 -0
- package/scripts/assert-node-runtime.mjs +140 -0
- package/scripts/preflight-native.mjs +49 -0
- package/scripts/preinstall-node-check.mjs +78 -0
- package/scripts/setup-git-hooks.mjs +24 -0
- package/scripts/sync.sh +2722 -0
- package/scripts/write-node-launcher.sh +108 -0
- package/src/resources/gemini/slash-packs/default/plan.toml +15 -0
- package/src/resources/gemini/slash-packs/default/summary.toml +16 -0
- package/src/resources/gemini/slash-packs/default/tests.toml +16 -0
- package/templates/.poetic/README.md +37 -0
- package/templates/.poetic/agents/README.md +296 -0
- package/templates/.poetic/agents/api-documenter.md +147 -0
- package/templates/.poetic/agents/backend-architect.md +31 -0
- package/templates/.poetic/agents/code-reviewer.md +157 -0
- package/templates/.poetic/agents/data-scientist.md +179 -0
- package/templates/.poetic/agents/database-optimizer.md +145 -0
- package/templates/.poetic/agents/debugger.md +31 -0
- package/templates/.poetic/agents/deployment-engineer.md +164 -0
- package/templates/.poetic/agents/devops-troubleshooter.md +139 -0
- package/templates/.poetic/agents/frontend-developer.md +150 -0
- package/templates/.poetic/agents/javascript-pro.md +36 -0
- package/templates/.poetic/agents/performance-engineer.md +151 -0
- package/templates/.poetic/agents/python-pro.md +137 -0
- package/templates/.poetic/agents/test-automator.md +147 -0
- package/templates/.poetic/agents/typescript-pro.md +34 -0
- package/templates/.poetic/config/poetic.config.jsonc +69 -0
- package/templates/.poetic/config/project-context.template.json +6 -0
- package/templates/.poetic/config/task-type-aliases.presets/kanban.yaml +14 -0
- package/templates/.poetic/config/task-type-aliases.presets/scrum.yaml +17 -0
- package/templates/.poetic/config/task-type-aliases.presets/xp.yaml +12 -0
- package/templates/.poetic/config/task-type-aliases.yaml +28 -0
- package/templates/.poetic/gitignore.template +55 -0
- package/templates/.poetic/task-types/analysis.yaml +40 -0
- package/templates/.poetic/task-types/architecture.yaml +38 -0
- package/templates/.poetic/task-types/doc.yaml +35 -0
- package/templates/.poetic/task-types/feature.yaml +23 -0
- package/templates/.poetic/task-types/general.yaml +6 -0
- package/templates/.poetic/task-types/security.yaml +39 -0
- package/templates/AGENTS.template.md +99 -0
- package/templates/CLAUDE.template.md +1 -0
- package/templates/GEMINI.template.md +1 -0
- package/templates/builtin-workflows/code-review.yaml +73 -0
- package/templates/builtin-workflows/compete-streak.yaml +78 -0
- package/templates/builtin-workflows/hello-verify.yaml +10 -0
- package/templates/builtin-workflows/judge-regression.yaml +114 -0
- package/templates/builtin-workflows/skills/code-review/SKILL.md +60 -0
- package/templates/builtin-workflows/skills/hello-verify/SKILL.md +6 -0
- package/templates/guard-kit/GUARD_SETUP.md.template +255 -0
- package/templates/guard-kit/check.mjs.template +777 -0
- package/templates/guard-kit/config.json.template +6 -0
- package/templates/guard-kit/poetic-guard.yml.template +189 -0
- package/templates/profiles/README.md +56 -0
- package/templates/profiles/frontier-claude.json +27 -0
- package/templates/profiles/frontier-codex.json +26 -0
|
@@ -0,0 +1,1859 @@
|
|
|
1
|
+
import {
|
|
2
|
+
evaluateStaleReclaim
|
|
3
|
+
} from "./chunk-P7CQGPLS.js";
|
|
4
|
+
import {
|
|
5
|
+
validateStateTransition
|
|
6
|
+
} from "./chunk-KNT72VR6.js";
|
|
7
|
+
import {
|
|
8
|
+
classifyTaskDependency
|
|
9
|
+
} from "./chunk-3BCUNHKF.js";
|
|
10
|
+
import {
|
|
11
|
+
getArchivedFilePath
|
|
12
|
+
} from "./chunk-OYZYO7TV.js";
|
|
13
|
+
import {
|
|
14
|
+
generateId
|
|
15
|
+
} from "./chunk-VNSYKC25.js";
|
|
16
|
+
import {
|
|
17
|
+
assertCompletionContractsMet,
|
|
18
|
+
normalizeCompletionContracts
|
|
19
|
+
} from "./chunk-R35PEUKH.js";
|
|
20
|
+
import {
|
|
21
|
+
getPlanningRoot,
|
|
22
|
+
withIndexRegeneration
|
|
23
|
+
} from "./chunk-OPYFYTWQ.js";
|
|
24
|
+
import {
|
|
25
|
+
getBucketArchivePathByKind,
|
|
26
|
+
getBucketByName,
|
|
27
|
+
getBucketPathByKind
|
|
28
|
+
} from "./chunk-HOYBYAOA.js";
|
|
29
|
+
import {
|
|
30
|
+
assertSafePlanningFileId,
|
|
31
|
+
ensureDirectoryExists,
|
|
32
|
+
listMarkdownFiles,
|
|
33
|
+
listMarkdownFilesRecursive,
|
|
34
|
+
readMarkdownFile,
|
|
35
|
+
resolvePlanningItemPath,
|
|
36
|
+
writeMarkdownFile
|
|
37
|
+
} from "./chunk-OIS7J26S.js";
|
|
38
|
+
import {
|
|
39
|
+
signalCoordinator
|
|
40
|
+
} from "./chunk-72YTZMAM.js";
|
|
41
|
+
import {
|
|
42
|
+
assertSafePathComponent
|
|
43
|
+
} from "./chunk-S2VQCZO4.js";
|
|
44
|
+
import {
|
|
45
|
+
emitCustomWarning
|
|
46
|
+
} from "./chunk-EJGCZGUF.js";
|
|
47
|
+
import {
|
|
48
|
+
POETIC_SPRINT_LOCK_RETRY_TIMEOUT_MS,
|
|
49
|
+
POETIC_SPRINT_LOCK_STALE_AFTER_MS,
|
|
50
|
+
readEnv
|
|
51
|
+
} from "./chunk-5DLKYTQX.js";
|
|
52
|
+
|
|
53
|
+
// src/planning/sprint-manager.ts
|
|
54
|
+
import * as fs2 from "fs/promises";
|
|
55
|
+
import * as path2 from "path";
|
|
56
|
+
|
|
57
|
+
// src/planning/sprint-lock.ts
|
|
58
|
+
import { randomUUID } from "crypto";
|
|
59
|
+
import * as fs from "fs/promises";
|
|
60
|
+
import * as path from "path";
|
|
61
|
+
var heldSprintLocks = /* @__PURE__ */ new Map();
|
|
62
|
+
var DEFAULT_LOCK_RETRY_TIMEOUT_MS = 3e4;
|
|
63
|
+
var DEFAULT_LOCK_STALE_AFTER_MS = 30 * 6e4;
|
|
64
|
+
var SPRINT_LOCK_HANDOFF_ENV = "POETIC_SPRINT_LOCK_HANDOFF_TOKEN";
|
|
65
|
+
function shouldReclaimSprintLock(params) {
|
|
66
|
+
if (params.ownerToken === null) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
return evaluateStaleReclaim(
|
|
70
|
+
{
|
|
71
|
+
ageMs: params.ageMs,
|
|
72
|
+
holder: params.existingPid === null ? null : { sameHost: true, pidAlive: params.ownerAlive }
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
staleMs: getSprintLockStaleAfterMs(),
|
|
76
|
+
corruptPayload: "reclaim",
|
|
77
|
+
foreignHost: { kind: "never" },
|
|
78
|
+
ageBoundary: "gte",
|
|
79
|
+
sameHostDeadPid: "immediate"
|
|
80
|
+
}
|
|
81
|
+
) === "reclaim";
|
|
82
|
+
}
|
|
83
|
+
function emitSprintLockWarning(message, metadata) {
|
|
84
|
+
emitCustomWarning(
|
|
85
|
+
"WARN" /* WARN */,
|
|
86
|
+
"workspace_edits" /* WORKSPACE_EDITS */,
|
|
87
|
+
message,
|
|
88
|
+
{
|
|
89
|
+
taskId: "sprint-lock",
|
|
90
|
+
subsystem: "planning/sprint-lock",
|
|
91
|
+
executionPhase: "execution"
|
|
92
|
+
},
|
|
93
|
+
{ metadata, skipConsole: true }
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
function getSprintLockRetryTimeoutMs() {
|
|
97
|
+
return readEnv(POETIC_SPRINT_LOCK_RETRY_TIMEOUT_MS).value ?? DEFAULT_LOCK_RETRY_TIMEOUT_MS;
|
|
98
|
+
}
|
|
99
|
+
function getSprintLockStaleAfterMs() {
|
|
100
|
+
return readEnv(POETIC_SPRINT_LOCK_STALE_AFTER_MS).value ?? DEFAULT_LOCK_STALE_AFTER_MS;
|
|
101
|
+
}
|
|
102
|
+
function getSprintLockPath(sprintId) {
|
|
103
|
+
const safeSprintId = assertSafePathComponent(sprintId, "sprintId");
|
|
104
|
+
return path.join(getPlanningRoot(), ".poetic", `sprint-${safeSprintId}.lock`);
|
|
105
|
+
}
|
|
106
|
+
function getSprintActivationLockPath() {
|
|
107
|
+
return path.join(getPlanningRoot(), ".poetic", "sprint-activation.lock");
|
|
108
|
+
}
|
|
109
|
+
async function readSprintLockOwnerToken(lockPath) {
|
|
110
|
+
try {
|
|
111
|
+
const content = await fs.readFile(lockPath, "utf-8");
|
|
112
|
+
return content.trim().split("\n")[0] ?? "";
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function readSprintLockSnapshot(lockPath) {
|
|
118
|
+
const snapshotPath = `${lockPath}.snapshot.${process.pid}.${Date.now().toString(36)}.${randomUUID()}`;
|
|
119
|
+
try {
|
|
120
|
+
await fs.link(lockPath, snapshotPath);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error.code === "ENOENT") {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const [content, stat2] = await Promise.all([
|
|
129
|
+
fs.readFile(snapshotPath, "utf-8"),
|
|
130
|
+
fs.stat(snapshotPath)
|
|
131
|
+
]);
|
|
132
|
+
return {
|
|
133
|
+
ownerToken: content.trim().split("\n")[0] ?? "",
|
|
134
|
+
dev: stat2.dev,
|
|
135
|
+
ino: stat2.ino,
|
|
136
|
+
mtimeMs: stat2.mtimeMs
|
|
137
|
+
};
|
|
138
|
+
} finally {
|
|
139
|
+
await fs.unlink(snapshotPath).catch(() => void 0);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function unlinkSprintLockIfOwnerStillMatches(lockPath, expectedOwnerToken, expectedIdentity) {
|
|
143
|
+
const reclaimPath = `${lockPath}.reclaim.${process.pid}.${Date.now().toString(36)}.${randomUUID()}`;
|
|
144
|
+
try {
|
|
145
|
+
await fs.link(lockPath, reclaimPath);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (error.code === "ENOENT") {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const [content, reclaimStat] = await Promise.all([
|
|
154
|
+
fs.readFile(reclaimPath, "utf-8"),
|
|
155
|
+
fs.stat(reclaimPath)
|
|
156
|
+
]);
|
|
157
|
+
const currentOwnerToken = content.trim().split("\n")[0] ?? "";
|
|
158
|
+
if (currentOwnerToken !== expectedOwnerToken) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
if (expectedIdentity && (reclaimStat.dev !== expectedIdentity.dev || reclaimStat.ino !== expectedIdentity.ino)) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
const currentStat = await fs.stat(lockPath).catch(() => null);
|
|
165
|
+
if (!currentStat || currentStat.dev !== reclaimStat.dev || currentStat.ino !== reclaimStat.ino) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
await fs.unlink(lockPath);
|
|
169
|
+
return true;
|
|
170
|
+
} finally {
|
|
171
|
+
await fs.unlink(reclaimPath).catch(() => void 0);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function unlinkPartialSprintLock(lockPath) {
|
|
175
|
+
await fs.unlink(lockPath).catch((unlinkError) => {
|
|
176
|
+
if (unlinkError.code !== "ENOENT") throw unlinkError;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
async function acquireNamedLock(lockKey, lockPath, busyLabel) {
|
|
180
|
+
const heldLock = heldSprintLocks.get(lockKey);
|
|
181
|
+
if (heldLock) {
|
|
182
|
+
heldLock.count += 1;
|
|
183
|
+
return {
|
|
184
|
+
ownerToken: heldLock.ownerToken,
|
|
185
|
+
release: async () => {
|
|
186
|
+
heldLock.count -= 1;
|
|
187
|
+
if (heldLock.count <= 0) {
|
|
188
|
+
heldSprintLocks.delete(lockKey);
|
|
189
|
+
await heldLock.releaseFileLock();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
195
|
+
const retryDeadlineMs = Date.now() + getSprintLockRetryTimeoutMs();
|
|
196
|
+
const createHeldLease = (ownerToken, identity) => {
|
|
197
|
+
const cleanupName = `planning-sprint-lock:${lockKey}:${ownerToken}`;
|
|
198
|
+
const releaseFileLock = async () => {
|
|
199
|
+
try {
|
|
200
|
+
await unlinkSprintLockIfOwnerStillMatches(lockPath, ownerToken, identity);
|
|
201
|
+
} catch {
|
|
202
|
+
} finally {
|
|
203
|
+
signalCoordinator.unregister(cleanupName);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const held = {
|
|
207
|
+
count: 1,
|
|
208
|
+
ownerToken,
|
|
209
|
+
releaseFileLock
|
|
210
|
+
};
|
|
211
|
+
heldSprintLocks.set(lockKey, held);
|
|
212
|
+
const release = async () => {
|
|
213
|
+
held.count -= 1;
|
|
214
|
+
if (held.count <= 0) {
|
|
215
|
+
heldSprintLocks.delete(lockKey);
|
|
216
|
+
await held.releaseFileLock();
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
signalCoordinator.register(cleanupName, release, {
|
|
220
|
+
priority: "critical",
|
|
221
|
+
phase: "artifact-cleanup"
|
|
222
|
+
});
|
|
223
|
+
return { ownerToken, release };
|
|
224
|
+
};
|
|
225
|
+
while (true) {
|
|
226
|
+
try {
|
|
227
|
+
const handle = await fs.open(lockPath, "wx");
|
|
228
|
+
const ownerToken = `${process.pid}:${randomUUID()}`;
|
|
229
|
+
try {
|
|
230
|
+
await handle.writeFile(`${ownerToken}
|
|
231
|
+
${(/* @__PURE__ */ new Date()).toISOString()}
|
|
232
|
+
`, "utf-8");
|
|
233
|
+
await handle.close();
|
|
234
|
+
} catch (writeError) {
|
|
235
|
+
await handle.close().catch(() => {
|
|
236
|
+
});
|
|
237
|
+
await unlinkPartialSprintLock(lockPath);
|
|
238
|
+
throw writeError;
|
|
239
|
+
}
|
|
240
|
+
const stat2 = await fs.stat(lockPath);
|
|
241
|
+
return createHeldLease(ownerToken, { dev: stat2.dev, ino: stat2.ino });
|
|
242
|
+
} catch (err) {
|
|
243
|
+
if (err.code !== "EEXIST") throw err;
|
|
244
|
+
const snapshot = await readSprintLockSnapshot(lockPath);
|
|
245
|
+
const firstLine = snapshot?.ownerToken ?? null;
|
|
246
|
+
const parsedPid = firstLine === null ? Number.NaN : Number.parseInt(firstLine.split(":")[0] ?? "", 10);
|
|
247
|
+
const existingPid = Number.isFinite(parsedPid) && parsedPid > 0 ? parsedPid : null;
|
|
248
|
+
const handoffToken = process.env[SPRINT_LOCK_HANDOFF_ENV];
|
|
249
|
+
if (handoffToken && firstLine === handoffToken) {
|
|
250
|
+
await unlinkSprintLockIfOwnerStillMatches(lockPath, handoffToken, snapshot ?? void 0);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
let ownerAlive = false;
|
|
254
|
+
if (existingPid !== null) {
|
|
255
|
+
try {
|
|
256
|
+
process.kill(existingPid, 0);
|
|
257
|
+
ownerAlive = true;
|
|
258
|
+
} catch (killError) {
|
|
259
|
+
ownerAlive = killError.code === "EPERM";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
const ageMs = Date.now() - (snapshot?.mtimeMs ?? Date.now());
|
|
264
|
+
if (firstLine !== null && shouldReclaimSprintLock({
|
|
265
|
+
ageMs,
|
|
266
|
+
ownerToken: firstLine,
|
|
267
|
+
existingPid,
|
|
268
|
+
ownerAlive
|
|
269
|
+
})) {
|
|
270
|
+
if (existingPid === null) {
|
|
271
|
+
emitSprintLockWarning(
|
|
272
|
+
`[sprint-lock] Reclaiming stale lock ${path.basename(lockPath)} (unknown owner, lock age ${Math.round(ageMs / 1e3)}s exceeds threshold).`,
|
|
273
|
+
{
|
|
274
|
+
lockKey,
|
|
275
|
+
lockPath,
|
|
276
|
+
ageMs,
|
|
277
|
+
operation: "reclaim-stale-lock"
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
await unlinkSprintLockIfOwnerStillMatches(lockPath, firstLine, snapshot ?? void 0);
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
} catch {
|
|
285
|
+
}
|
|
286
|
+
if (Date.now() < retryDeadlineMs) {
|
|
287
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
throw new Error(`${busyLabel} is busy (PID ${existingPid ?? "unknown"})`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async function acquireSprintLock(sprintId) {
|
|
295
|
+
const lease = await acquireSprintLockLease(sprintId);
|
|
296
|
+
return lease.release;
|
|
297
|
+
}
|
|
298
|
+
async function acquireSprintLockLease(sprintId) {
|
|
299
|
+
return acquireNamedLock(`sprint:${sprintId}`, getSprintLockPath(sprintId), `Sprint ${sprintId}`);
|
|
300
|
+
}
|
|
301
|
+
async function withSprintLock(sprintId, fn) {
|
|
302
|
+
const release = await acquireSprintLock(sprintId);
|
|
303
|
+
try {
|
|
304
|
+
return await fn();
|
|
305
|
+
} finally {
|
|
306
|
+
await release();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
async function withSprintActivationLock(fn) {
|
|
310
|
+
const lease = await acquireNamedLock(
|
|
311
|
+
"activation",
|
|
312
|
+
getSprintActivationLockPath(),
|
|
313
|
+
"Sprint activation"
|
|
314
|
+
);
|
|
315
|
+
try {
|
|
316
|
+
return await fn();
|
|
317
|
+
} finally {
|
|
318
|
+
await lease.release();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function getSprintLockHandoffEnv(ownerToken) {
|
|
322
|
+
return { [SPRINT_LOCK_HANDOFF_ENV]: ownerToken };
|
|
323
|
+
}
|
|
324
|
+
async function getSprintLockOwnerToken(sprintId) {
|
|
325
|
+
return readSprintLockOwnerToken(getSprintLockPath(sprintId));
|
|
326
|
+
}
|
|
327
|
+
async function refreshSprintLock(sprintId) {
|
|
328
|
+
const lockKey = `sprint:${sprintId}`;
|
|
329
|
+
const heldLock = heldSprintLocks.get(lockKey);
|
|
330
|
+
if (!heldLock) return false;
|
|
331
|
+
const lockPath = getSprintLockPath(sprintId);
|
|
332
|
+
try {
|
|
333
|
+
const currentOwnerToken = await readSprintLockOwnerToken(lockPath);
|
|
334
|
+
if (currentOwnerToken !== heldLock.ownerToken) return false;
|
|
335
|
+
const now = /* @__PURE__ */ new Date();
|
|
336
|
+
await fs.utimes(lockPath, now, now);
|
|
337
|
+
return true;
|
|
338
|
+
} catch {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
async function unlockSprintLock(sprintId, options = {}) {
|
|
343
|
+
const lockPath = getSprintLockPath(sprintId);
|
|
344
|
+
const snapshot = await readSprintLockSnapshot(lockPath);
|
|
345
|
+
let ownerToken = "";
|
|
346
|
+
if (!snapshot) {
|
|
347
|
+
return { success: true, message: `Sprint ${sprintId} is not locked` };
|
|
348
|
+
}
|
|
349
|
+
ownerToken = snapshot.ownerToken;
|
|
350
|
+
const ownerPid = Number.parseInt(ownerToken.split(":")[0] ?? "0", 10);
|
|
351
|
+
let ownerAlive = false;
|
|
352
|
+
if (ownerPid > 0) {
|
|
353
|
+
try {
|
|
354
|
+
process.kill(ownerPid, 0);
|
|
355
|
+
ownerAlive = true;
|
|
356
|
+
} catch (killError) {
|
|
357
|
+
ownerAlive = killError.code === "EPERM";
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (ownerAlive && !options.force) {
|
|
361
|
+
return {
|
|
362
|
+
success: false,
|
|
363
|
+
message: `Sprint ${sprintId} is locked by live PID ${ownerPid}; rerun with --force only after confirming the run is hung`
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
await unlinkSprintLockIfOwnerStillMatches(lockPath, ownerToken, {
|
|
367
|
+
dev: snapshot.dev,
|
|
368
|
+
ino: snapshot.ino
|
|
369
|
+
});
|
|
370
|
+
return { success: true, message: `Unlocked sprint ${sprintId}` };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/planning/sprint-manager.ts
|
|
374
|
+
function isGenuinelyAbsentTaskError(error) {
|
|
375
|
+
if (error && typeof error === "object" && "code" in error) {
|
|
376
|
+
const code = error.code;
|
|
377
|
+
if (code === "ENOENT") return true;
|
|
378
|
+
}
|
|
379
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
380
|
+
return message.includes("Task not found");
|
|
381
|
+
}
|
|
382
|
+
var DEFAULT_SPRINT_CAPACITY_HOURS = 40;
|
|
383
|
+
function parseTaskNumber(value, fallback) {
|
|
384
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
385
|
+
if (typeof value === "string") {
|
|
386
|
+
const parsed = Number.parseFloat(value);
|
|
387
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
388
|
+
}
|
|
389
|
+
return fallback;
|
|
390
|
+
}
|
|
391
|
+
function coerceSprintTaskFrontMatter(frontMatter) {
|
|
392
|
+
const id = typeof frontMatter.id === "string" ? frontMatter.id : "";
|
|
393
|
+
if (!id) {
|
|
394
|
+
throw new Error('Invalid task frontmatter: missing required field "id"');
|
|
395
|
+
}
|
|
396
|
+
const originalSprintId = typeof frontMatter.originalSprintId === "string" && frontMatter.originalSprintId ? frontMatter.originalSprintId : void 0;
|
|
397
|
+
const sprintId = typeof frontMatter.sprintId === "string" && frontMatter.sprintId || typeof frontMatter.sprint_id === "string" && frontMatter.sprint_id || originalSprintId || "";
|
|
398
|
+
if (!sprintId) {
|
|
399
|
+
throw new Error(`Invalid task frontmatter for '${id}': missing required field "sprintId"`);
|
|
400
|
+
}
|
|
401
|
+
const statusRaw = typeof frontMatter.status === "string" ? frontMatter.status : "todo";
|
|
402
|
+
const statusMap = {
|
|
403
|
+
todo: "todo",
|
|
404
|
+
in_progress: "in_progress",
|
|
405
|
+
review: "review",
|
|
406
|
+
done: "done",
|
|
407
|
+
blocked: "blocked",
|
|
408
|
+
cancelled: "cancelled",
|
|
409
|
+
canceled: "cancelled",
|
|
410
|
+
pending: "todo",
|
|
411
|
+
"in-progress": "in_progress",
|
|
412
|
+
completed: "done",
|
|
413
|
+
archived: "done"
|
|
414
|
+
};
|
|
415
|
+
const priorityRaw = typeof frontMatter.priority === "string" ? frontMatter.priority : void 0;
|
|
416
|
+
const priority = priorityRaw === "P0" || priorityRaw === "P1" || priorityRaw === "P2" || priorityRaw === "P3" ? priorityRaw : void 0;
|
|
417
|
+
return {
|
|
418
|
+
id,
|
|
419
|
+
title: typeof frontMatter.title === "string" && frontMatter.title ? frontMatter.title : id,
|
|
420
|
+
priority,
|
|
421
|
+
estimatedHours: parseTaskNumber(frontMatter.estimatedHours, 0),
|
|
422
|
+
...typeof frontMatter.expectedRuntimeMinutes === "number" && Number.isFinite(frontMatter.expectedRuntimeMinutes) && frontMatter.expectedRuntimeMinutes > 0 ? { expectedRuntimeMinutes: frontMatter.expectedRuntimeMinutes } : {},
|
|
423
|
+
status: statusMap[statusRaw] ?? "todo",
|
|
424
|
+
sprintId,
|
|
425
|
+
originalSprintId,
|
|
426
|
+
backlogItemId: typeof frontMatter.backlogItemId === "string" ? frontMatter.backlogItemId : void 0,
|
|
427
|
+
planArtifactPath: typeof frontMatter.planArtifactPath === "string" ? frontMatter.planArtifactPath : void 0,
|
|
428
|
+
resultArtifactPath: typeof frontMatter.resultArtifactPath === "string" ? frontMatter.resultArtifactPath : void 0,
|
|
429
|
+
planStageId: typeof frontMatter.planStageId === "string" ? frontMatter.planStageId : void 0,
|
|
430
|
+
dependencies: Array.isArray(frontMatter.dependencies) ? frontMatter.dependencies.filter((value) => typeof value === "string") : [],
|
|
431
|
+
blockedReason: typeof frontMatter.blockedReason === "string" ? frontMatter.blockedReason : void 0,
|
|
432
|
+
version: parseTaskNumber(frontMatter.version, 0),
|
|
433
|
+
completionContract: normalizeCompletionContracts(frontMatter.completionContract)
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
async function loadTaskLookupFromFile(filePath, isArchived) {
|
|
437
|
+
const { frontMatter } = await readMarkdownFile(filePath);
|
|
438
|
+
return {
|
|
439
|
+
yaml: coerceSprintTaskFrontMatter(frontMatter),
|
|
440
|
+
filePath,
|
|
441
|
+
isArchived
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function resolveTaskIdFromPrefix(candidateIds, query) {
|
|
445
|
+
const uniqueIds = [...new Set(candidateIds)];
|
|
446
|
+
const matches = uniqueIds.filter((id) => id === query || id.startsWith(query));
|
|
447
|
+
if (matches.length === 0) return null;
|
|
448
|
+
if (matches.length === 1) return matches[0] ?? null;
|
|
449
|
+
throw new Error(
|
|
450
|
+
`Ambiguous task ID prefix "${query}": matches ${matches.length} tasks. Use a longer prefix or the full ID.`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
async function collectKnownTaskIds(tasksDir, includeArchive) {
|
|
454
|
+
const activeFiles = await listMarkdownFiles(tasksDir).catch(() => []);
|
|
455
|
+
const ids = activeFiles.map((file) => path2.basename(file, ".md")).filter((id) => id && id.toLowerCase() !== "readme");
|
|
456
|
+
if (includeArchive) {
|
|
457
|
+
const archiveDir = getBucketArchivePathByKind("task", getPlanningRoot());
|
|
458
|
+
const archivedFiles = await listMarkdownFilesRecursive(archiveDir).catch(() => []);
|
|
459
|
+
ids.push(
|
|
460
|
+
...archivedFiles.map((file) => path2.basename(file, ".md")).filter((id) => id && id.toLowerCase() !== "readme")
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
return ids;
|
|
464
|
+
}
|
|
465
|
+
async function findArchivedTaskFile(taskId) {
|
|
466
|
+
const archiveDir = getBucketArchivePathByKind("task", getPlanningRoot());
|
|
467
|
+
const archivedFiles = await listMarkdownFilesRecursive(archiveDir).catch(() => []);
|
|
468
|
+
return archivedFiles.find((file) => path2.basename(file) === `${taskId}.md`) ?? null;
|
|
469
|
+
}
|
|
470
|
+
async function getTask(taskId, options = {}) {
|
|
471
|
+
const includeArchive = options.includeArchive ?? false;
|
|
472
|
+
const tasksDir = getBucketPathByKind("task", getPlanningRoot());
|
|
473
|
+
const activeTaskFile = resolvePlanningItemPath(tasksDir, taskId, { label: "taskId" });
|
|
474
|
+
try {
|
|
475
|
+
await fs2.access(activeTaskFile);
|
|
476
|
+
return loadTaskLookupFromFile(activeTaskFile, false);
|
|
477
|
+
} catch (error) {
|
|
478
|
+
if (error.code !== "ENOENT") {
|
|
479
|
+
throw error;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (includeArchive) {
|
|
483
|
+
const archivedTaskFile = await findArchivedTaskFile(taskId);
|
|
484
|
+
if (archivedTaskFile) {
|
|
485
|
+
return loadTaskLookupFromFile(archivedTaskFile, true);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
const resolvedId = resolveTaskIdFromPrefix(
|
|
489
|
+
await collectKnownTaskIds(tasksDir, includeArchive),
|
|
490
|
+
taskId
|
|
491
|
+
);
|
|
492
|
+
if (resolvedId && resolvedId !== taskId) {
|
|
493
|
+
return getTask(resolvedId, options);
|
|
494
|
+
}
|
|
495
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
496
|
+
}
|
|
497
|
+
async function archiveTaskForSprintClose(taskId, options) {
|
|
498
|
+
const planningRoot = getPlanningRoot();
|
|
499
|
+
const bucket = getBucketByName("task", planningRoot);
|
|
500
|
+
if (!bucket) {
|
|
501
|
+
throw new Error("Bucket not configured: task");
|
|
502
|
+
}
|
|
503
|
+
const lookup = await getTask(taskId, { includeArchive: true });
|
|
504
|
+
if (lookup.isArchived) {
|
|
505
|
+
return {};
|
|
506
|
+
}
|
|
507
|
+
const { frontMatter, body } = await readMarkdownFile(lookup.filePath);
|
|
508
|
+
const rawTask = frontMatter;
|
|
509
|
+
const task = coerceSprintTaskFrontMatter(rawTask);
|
|
510
|
+
const forceCompletion = options.force ?? false;
|
|
511
|
+
if (!forceCompletion && (task.status === "blocked" || task.status === "cancelled")) {
|
|
512
|
+
throw new Error(
|
|
513
|
+
`Cannot complete task ${task.id} from status "${task.status}" without --force. Move it to in_progress/review first.`
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
if (task.status !== "done" && !(forceCompletion && (task.status === "blocked" || task.status === "cancelled"))) {
|
|
517
|
+
const transition = await validateStateTransition(planningRoot, taskId, task.status, "done");
|
|
518
|
+
if (!transition.success) {
|
|
519
|
+
const details = transition.errors?.length ? `: ${transition.errors.join("; ")}` : "";
|
|
520
|
+
throw new Error(`${transition.message}${details}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
await assertCompletionContractsMet({
|
|
524
|
+
contracts: task.completionContract,
|
|
525
|
+
workingDir: options.completionContractRoot
|
|
526
|
+
});
|
|
527
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
528
|
+
if (forceCompletion && task.status !== "done") {
|
|
529
|
+
rawTask.forceCompletedFrom = task.status;
|
|
530
|
+
if (options.preserveBlockedReason && task.status === "blocked" && task.blockedReason) {
|
|
531
|
+
rawTask.originalBlockedReason = task.blockedReason;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
rawTask.status = "done";
|
|
535
|
+
rawTask.version = task.version + 1;
|
|
536
|
+
rawTask.completed_at = now;
|
|
537
|
+
rawTask.updated_at = now;
|
|
538
|
+
rawTask.archivedAt = now;
|
|
539
|
+
rawTask.originalSprintId = task.sprintId;
|
|
540
|
+
delete rawTask.sprintId;
|
|
541
|
+
delete rawTask.sprint_id;
|
|
542
|
+
const archivedPath = getArchivedFilePath(bucket, task.id, now, planningRoot);
|
|
543
|
+
await ensureDirectoryExists(path2.dirname(archivedPath));
|
|
544
|
+
await writeMarkdownFile(archivedPath, rawTask, body);
|
|
545
|
+
await fs2.unlink(lookup.filePath);
|
|
546
|
+
return { archivedPath };
|
|
547
|
+
}
|
|
548
|
+
async function findSprintOverlapConflict(sprintsDir, startDate, endDate) {
|
|
549
|
+
const start = parseStrictDateOnly(startDate);
|
|
550
|
+
const end = parseStrictDateOnly(endDate);
|
|
551
|
+
if (!start || !end) return null;
|
|
552
|
+
const existing = await listMarkdownFiles(sprintsDir).catch(() => []);
|
|
553
|
+
for (const file of existing) {
|
|
554
|
+
try {
|
|
555
|
+
const { frontMatter } = await readMarkdownFile(file);
|
|
556
|
+
const fm = frontMatter;
|
|
557
|
+
if (fm.status === "completed") continue;
|
|
558
|
+
const existingId = typeof fm.id === "string" ? fm.id : "";
|
|
559
|
+
const existingStart = typeof fm.startDate === "string" ? fm.startDate : typeof fm.start_date === "string" ? fm.start_date : "";
|
|
560
|
+
const existingEnd = typeof fm.endDate === "string" ? fm.endDate : typeof fm.end_date === "string" ? fm.end_date : "";
|
|
561
|
+
if (!existingStart) continue;
|
|
562
|
+
if (existingStart === startDate) {
|
|
563
|
+
return {
|
|
564
|
+
conflictingSprintId: existingId,
|
|
565
|
+
conflictingStartDate: existingStart,
|
|
566
|
+
conflictingEndDate: existingEnd,
|
|
567
|
+
kind: "exact"
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
if (existingStart && existingEnd) {
|
|
571
|
+
const existingStartDate = parseStrictDateOnly(existingStart);
|
|
572
|
+
const existingEndDate = parseStrictDateOnly(existingEnd);
|
|
573
|
+
if (existingStartDate && existingEndDate && start.getTime() < existingEndDate.getTime() && end.getTime() > existingStartDate.getTime()) {
|
|
574
|
+
return {
|
|
575
|
+
conflictingSprintId: existingId,
|
|
576
|
+
conflictingStartDate: existingStart,
|
|
577
|
+
conflictingEndDate: existingEnd,
|
|
578
|
+
kind: "range"
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
} catch {
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
function emitSprintManagerWarning(message, metadata, severity = "WARN" /* WARN */) {
|
|
588
|
+
emitCustomWarning(
|
|
589
|
+
severity,
|
|
590
|
+
"configuration" /* CONFIGURATION */,
|
|
591
|
+
message,
|
|
592
|
+
{
|
|
593
|
+
subsystem: "planning/sprint-manager",
|
|
594
|
+
executionPhase: "execution"
|
|
595
|
+
},
|
|
596
|
+
{ metadata, skipConsole: true }
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
function parseStrictDateOnly(value) {
|
|
600
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
601
|
+
if (!match) return null;
|
|
602
|
+
const [, yearText, monthText, dayText] = match;
|
|
603
|
+
const year = Number(yearText);
|
|
604
|
+
const month = Number(monthText);
|
|
605
|
+
const day = Number(dayText);
|
|
606
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
607
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) {
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
return date;
|
|
611
|
+
}
|
|
612
|
+
async function ensureSprintDoesNotAlreadyLinkBacklogItem(sprintId, taskIds, backlogItemId) {
|
|
613
|
+
if (!backlogItemId) return;
|
|
614
|
+
for (const existingTaskId of taskIds) {
|
|
615
|
+
try {
|
|
616
|
+
const linkedTask = await getTask(existingTaskId, {
|
|
617
|
+
includeArchive: true
|
|
618
|
+
});
|
|
619
|
+
if (linkedTask.yaml.backlogItemId === backlogItemId) {
|
|
620
|
+
throw new Error(
|
|
621
|
+
`Backlog item ${backlogItemId} is already represented in sprint ${sprintId} by task ${existingTaskId}`
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
} catch (error) {
|
|
625
|
+
if (error instanceof Error && error.message.includes("already represented in sprint")) {
|
|
626
|
+
throw error;
|
|
627
|
+
}
|
|
628
|
+
if (error instanceof Error && error.message.startsWith("Task not found:")) {
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
emitSprintManagerWarning(
|
|
632
|
+
`Could not inspect task ${existingTaskId} while checking sprint ${sprintId}`,
|
|
633
|
+
{
|
|
634
|
+
sprintId,
|
|
635
|
+
taskId: existingTaskId,
|
|
636
|
+
error: error instanceof Error ? error.message : String(error)
|
|
637
|
+
}
|
|
638
|
+
);
|
|
639
|
+
throw new Error(
|
|
640
|
+
`Cannot safely add task to sprint ${sprintId}: existing task ${existingTaskId} could not be inspected`
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
function formatSprintStatusLabel(status) {
|
|
646
|
+
switch (status) {
|
|
647
|
+
case "planning":
|
|
648
|
+
return "Planning";
|
|
649
|
+
case "active":
|
|
650
|
+
return "Active";
|
|
651
|
+
case "completed":
|
|
652
|
+
return "Completed";
|
|
653
|
+
default:
|
|
654
|
+
return "Planning";
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function formatSprintTaskStatusLabel(status) {
|
|
658
|
+
switch (status) {
|
|
659
|
+
case "todo":
|
|
660
|
+
return "todo";
|
|
661
|
+
case "in_progress":
|
|
662
|
+
return "in_progress";
|
|
663
|
+
case "review":
|
|
664
|
+
return "review";
|
|
665
|
+
case "done":
|
|
666
|
+
return "done";
|
|
667
|
+
case "blocked":
|
|
668
|
+
return "blocked";
|
|
669
|
+
case "cancelled":
|
|
670
|
+
return "cancelled";
|
|
671
|
+
default:
|
|
672
|
+
return "todo";
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
function renderSprintTasksSummaryTable(tasks) {
|
|
676
|
+
const header = "| Task | Estimated Hours | Status |\n|------|-----------------|--------|";
|
|
677
|
+
if (tasks.length === 0) {
|
|
678
|
+
return `${header}
|
|
679
|
+
| (No tasks yet) | - | - |`;
|
|
680
|
+
}
|
|
681
|
+
const rows = tasks.map((t) => {
|
|
682
|
+
const taskCell = `${t.id}: ${t.title}`.replaceAll("\n", " ").trim();
|
|
683
|
+
const estimateCell = t.estimatedHours > 0 ? `${t.estimatedHours}h` : "-";
|
|
684
|
+
return `| ${taskCell} | ${estimateCell} | ${formatSprintTaskStatusLabel(t.status)} |`;
|
|
685
|
+
}).join("\n");
|
|
686
|
+
return `${header}
|
|
687
|
+
${rows}`;
|
|
688
|
+
}
|
|
689
|
+
function updateSprintBodyOverview(body, sprint, tasks) {
|
|
690
|
+
const total = tasks.length;
|
|
691
|
+
const done = tasks.filter((t) => t.status === "done").length;
|
|
692
|
+
const progressLine = total === 0 ? "**Progress**: 0 tasks" : `**Progress**: ${done}/${total} tasks (${(done / total * 100).toFixed(1)}%)`;
|
|
693
|
+
let updated = body;
|
|
694
|
+
if (/^\*\*Status\*\*:/m.test(updated)) {
|
|
695
|
+
updated = updated.replace(
|
|
696
|
+
/^\*\*Status\*\*:\s*.*$/m,
|
|
697
|
+
`**Status**: ${formatSprintStatusLabel(sprint.status)}`
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
if (/^\*\*Progress\*\*:/m.test(updated)) {
|
|
701
|
+
updated = updated.replace(/^\*\*Progress\*\*:\s*.*$/m, progressLine);
|
|
702
|
+
}
|
|
703
|
+
return updated;
|
|
704
|
+
}
|
|
705
|
+
function updateSprintBodyTasksSummary(body, tasks) {
|
|
706
|
+
const table = renderSprintTasksSummaryTable(tasks);
|
|
707
|
+
const replacement = `## Tasks Summary
|
|
708
|
+
|
|
709
|
+
${table}
|
|
710
|
+
`;
|
|
711
|
+
if (/^## Tasks Summary\s*$/m.test(body)) {
|
|
712
|
+
const headingMatch = body.match(/^## Tasks Summary\s*$/m);
|
|
713
|
+
if (headingMatch?.index !== void 0) {
|
|
714
|
+
const sectionStart = headingMatch.index;
|
|
715
|
+
const afterHeadingIndex = sectionStart + headingMatch[0].length;
|
|
716
|
+
const rest = body.slice(afterHeadingIndex);
|
|
717
|
+
const nextSectionMatch = rest.match(/\n## /);
|
|
718
|
+
const sectionEnd = nextSectionMatch && nextSectionMatch.index !== void 0 ? afterHeadingIndex + nextSectionMatch.index + 1 : body.length;
|
|
719
|
+
return `${body.slice(0, sectionStart)}${replacement.trimEnd()}
|
|
720
|
+
${body.slice(sectionEnd)}`;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (/^## Notes\s*$/m.test(body)) {
|
|
724
|
+
return body.replace(/^## Notes\s*$/m, `${replacement}
|
|
725
|
+
## Notes`);
|
|
726
|
+
}
|
|
727
|
+
return `${body.trimEnd()}
|
|
728
|
+
|
|
729
|
+
${replacement}
|
|
730
|
+
`;
|
|
731
|
+
}
|
|
732
|
+
async function sumArchivedDoneHoursForSprint(sprintId) {
|
|
733
|
+
const planningRoot = getPlanningRoot();
|
|
734
|
+
const archiveRoot = getBucketArchivePathByKind("task", planningRoot);
|
|
735
|
+
const files = await listMarkdownFilesRecursive(archiveRoot).catch(() => []);
|
|
736
|
+
let total = 0;
|
|
737
|
+
for (const file of files) {
|
|
738
|
+
if (path2.basename(file).toLowerCase() === "readme.md") {
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
try {
|
|
742
|
+
const { frontMatter } = await readMarkdownFile(file);
|
|
743
|
+
const fm = frontMatter;
|
|
744
|
+
const originalSprintId = typeof fm.originalSprintId === "string" ? fm.originalSprintId : void 0;
|
|
745
|
+
if (originalSprintId !== sprintId) {
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
if (fm.status !== "done") {
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
total += typeof fm.estimatedHours === "number" ? fm.estimatedHours : 0;
|
|
752
|
+
} catch {
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return total;
|
|
756
|
+
}
|
|
757
|
+
function applyDerivedSprintMetrics(sprint, tasks, archivedDoneHours = 0) {
|
|
758
|
+
if (!sprint.velocity) {
|
|
759
|
+
sprint.velocity = {
|
|
760
|
+
forecast: sprint.capacity.totalHours,
|
|
761
|
+
actual: 0
|
|
762
|
+
};
|
|
763
|
+
} else if (!Number.isFinite(sprint.velocity.forecast)) {
|
|
764
|
+
sprint.velocity.forecast = sprint.capacity.totalHours;
|
|
765
|
+
}
|
|
766
|
+
const activeDoneHours = tasks.filter((task) => task.status === "done").reduce((sum, task) => sum + task.estimatedHours, 0);
|
|
767
|
+
sprint.velocity.actual = activeDoneHours + archivedDoneHours;
|
|
768
|
+
}
|
|
769
|
+
async function refreshSprintMarkdownById(sprintId, options = {}) {
|
|
770
|
+
const sprintPath = await resolveSprintPath(sprintId, {
|
|
771
|
+
includeArchive: options.includeArchive
|
|
772
|
+
});
|
|
773
|
+
const { frontMatter, body } = await readMarkdownFile(sprintPath);
|
|
774
|
+
const sprint = coerceSprintYAML(frontMatter);
|
|
775
|
+
const tasks = await listTasksInSprint(sprintId);
|
|
776
|
+
const archivedDoneHours = await sumArchivedDoneHoursForSprint(sprintId);
|
|
777
|
+
applyDerivedSprintMetrics(sprint, tasks, archivedDoneHours);
|
|
778
|
+
const updatedBody = updateSprintBodyTasksSummary(
|
|
779
|
+
updateSprintBodyOverview(body, sprint, tasks),
|
|
780
|
+
tasks
|
|
781
|
+
);
|
|
782
|
+
sprint.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
783
|
+
await writeMarkdownFile(
|
|
784
|
+
sprintPath,
|
|
785
|
+
sprint,
|
|
786
|
+
updatedBody
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
async function fileExists(filePath) {
|
|
790
|
+
try {
|
|
791
|
+
await fs2.access(filePath);
|
|
792
|
+
return true;
|
|
793
|
+
} catch {
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
async function assertSprintFileExists(sprintId, options = {}) {
|
|
798
|
+
const filePath = await resolveSprintPath(sprintId, options);
|
|
799
|
+
if (!await fileExists(filePath)) {
|
|
800
|
+
throw new Error(`Sprint not found: ${sprintId}`);
|
|
801
|
+
}
|
|
802
|
+
return filePath;
|
|
803
|
+
}
|
|
804
|
+
async function resolveSprintPath(sprintId, options = {}) {
|
|
805
|
+
const planningRoot = getPlanningRoot();
|
|
806
|
+
const sprintsDir = getBucketPathByKind("sprint", planningRoot);
|
|
807
|
+
const activePath = resolvePlanningItemPath(sprintsDir, sprintId, { label: "sprintId" });
|
|
808
|
+
if (await fileExists(activePath)) {
|
|
809
|
+
return activePath;
|
|
810
|
+
}
|
|
811
|
+
if (!options.includeArchive) {
|
|
812
|
+
return activePath;
|
|
813
|
+
}
|
|
814
|
+
const archiveRoot = path2.join(sprintsDir, "archive");
|
|
815
|
+
if (!await fileExists(archiveRoot)) {
|
|
816
|
+
return activePath;
|
|
817
|
+
}
|
|
818
|
+
const archivedFiles = await listMarkdownFilesRecursive(archiveRoot).catch(() => []);
|
|
819
|
+
const match = archivedFiles.find((file) => path2.basename(file) === `${sprintId}.md`);
|
|
820
|
+
if (!match) {
|
|
821
|
+
return activePath;
|
|
822
|
+
}
|
|
823
|
+
return resolvePlanningItemPath(sprintsDir, sprintId, {
|
|
824
|
+
label: "sprintId",
|
|
825
|
+
extraSegments: path2.relative(sprintsDir, path2.dirname(match)).split(path2.sep).filter(Boolean)
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
function parseNumber(value, fallback) {
|
|
829
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
830
|
+
if (typeof value === "string") {
|
|
831
|
+
const parsed = Number.parseFloat(value);
|
|
832
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
833
|
+
}
|
|
834
|
+
return fallback;
|
|
835
|
+
}
|
|
836
|
+
function coerceDateString(value) {
|
|
837
|
+
if (typeof value === "string") return value;
|
|
838
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) {
|
|
839
|
+
return formatLocalDateString(value);
|
|
840
|
+
}
|
|
841
|
+
return "";
|
|
842
|
+
}
|
|
843
|
+
function formatLocalDateString(value) {
|
|
844
|
+
const year = value.getFullYear();
|
|
845
|
+
const month = `${value.getMonth() + 1}`.padStart(2, "0");
|
|
846
|
+
const day = `${value.getDate()}`.padStart(2, "0");
|
|
847
|
+
return `${year}-${month}-${day}`;
|
|
848
|
+
}
|
|
849
|
+
function coerceSprintCapacity(frontMatter) {
|
|
850
|
+
const capacity = frontMatter.capacity;
|
|
851
|
+
if (capacity && typeof capacity === "object") {
|
|
852
|
+
const totalHours2 = parseNumber(capacity.totalHours, 0);
|
|
853
|
+
const allocatedHours2 = parseNumber(capacity.allocatedHours, 0);
|
|
854
|
+
const remainingHours2 = parseNumber(capacity.remainingHours, totalHours2 - allocatedHours2);
|
|
855
|
+
const utilizationPercent2 = totalHours2 > 0 ? parseNumber(capacity.utilizationPercent, allocatedHours2 / totalHours2 * 100) : 0;
|
|
856
|
+
return { totalHours: totalHours2, allocatedHours: allocatedHours2, remainingHours: remainingHours2, utilizationPercent: utilizationPercent2 };
|
|
857
|
+
}
|
|
858
|
+
const totalHours = parseNumber(
|
|
859
|
+
frontMatter.total_capacity_hours ?? frontMatter.totalHours ?? frontMatter.total_capacity,
|
|
860
|
+
0
|
|
861
|
+
);
|
|
862
|
+
const allocatedHours = parseNumber(
|
|
863
|
+
frontMatter.planned_capacity_hours ?? frontMatter.allocated_hours ?? frontMatter.allocatedHours,
|
|
864
|
+
0
|
|
865
|
+
);
|
|
866
|
+
const remainingHours = totalHours > 0 ? Math.max(0, totalHours - allocatedHours) : 0;
|
|
867
|
+
const utilizationPercent = totalHours > 0 ? allocatedHours / totalHours * 100 : 0;
|
|
868
|
+
return { totalHours, allocatedHours, remainingHours, utilizationPercent };
|
|
869
|
+
}
|
|
870
|
+
function coerceSprintYAML(frontMatter) {
|
|
871
|
+
const id = typeof frontMatter.id === "string" ? frontMatter.id : "";
|
|
872
|
+
if (!id) {
|
|
873
|
+
throw new Error('Invalid sprint frontmatter: missing required field "id"');
|
|
874
|
+
}
|
|
875
|
+
assertSafePlanningFileId(id, "sprintId");
|
|
876
|
+
const kindRaw = typeof frontMatter.kind === "string" ? frontMatter.kind : "sprint";
|
|
877
|
+
const kind = kindRaw === "sprint" ? "sprint" : "sprint";
|
|
878
|
+
const title = typeof frontMatter.title === "string" && frontMatter.title || typeof frontMatter.name === "string" && frontMatter.name || id;
|
|
879
|
+
const name = typeof frontMatter.name === "string" && frontMatter.name || typeof frontMatter.title === "string" && frontMatter.title || id;
|
|
880
|
+
const startDate = coerceDateString(frontMatter.startDate) || coerceDateString(frontMatter.start_date) || "";
|
|
881
|
+
const endDate = coerceDateString(frontMatter.endDate) || coerceDateString(frontMatter.end_date) || "";
|
|
882
|
+
if (!startDate || !endDate) {
|
|
883
|
+
throw new Error(
|
|
884
|
+
`Invalid sprint frontmatter for '${id}': missing "startDate"/"endDate" (or legacy start_date/end_date)`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
const goal = typeof frontMatter.goal === "string" ? frontMatter.goal : typeof frontMatter.goal_summary === "string" ? frontMatter.goal_summary : void 0;
|
|
888
|
+
const statusRaw = typeof frontMatter.status === "string" ? frontMatter.status : "planning";
|
|
889
|
+
const status = statusRaw === "active" || statusRaw === "planning" || statusRaw === "completed" ? statusRaw : "planning";
|
|
890
|
+
const taskIds = Array.isArray(frontMatter.taskIds) ? frontMatter.taskIds.filter((v) => typeof v === "string") : [];
|
|
891
|
+
const completedTaskIds = Array.isArray(frontMatter.completedTaskIds) ? frontMatter.completedTaskIds.filter((v) => typeof v === "string") : [];
|
|
892
|
+
const createdAt = typeof frontMatter.created_at === "string" && frontMatter.created_at || typeof frontMatter.createdAt === "string" && frontMatter.createdAt || typeof frontMatter.created === "string" && frontMatter.created || (/* @__PURE__ */ new Date()).toISOString();
|
|
893
|
+
const updatedAt = typeof frontMatter.updated_at === "string" && frontMatter.updated_at || typeof frontMatter.updatedAt === "string" && frontMatter.updatedAt || typeof frontMatter.updated === "string" && frontMatter.updated || createdAt;
|
|
894
|
+
const velocityRaw = frontMatter.velocity;
|
|
895
|
+
const velocity = velocityRaw && typeof velocityRaw === "object" ? {
|
|
896
|
+
forecast: parseNumber(velocityRaw.forecast, 0),
|
|
897
|
+
actual: parseNumber(velocityRaw.actual, 0)
|
|
898
|
+
} : void 0;
|
|
899
|
+
return {
|
|
900
|
+
...frontMatter,
|
|
901
|
+
id,
|
|
902
|
+
kind,
|
|
903
|
+
title,
|
|
904
|
+
name,
|
|
905
|
+
startDate,
|
|
906
|
+
endDate,
|
|
907
|
+
goal,
|
|
908
|
+
status,
|
|
909
|
+
capacity: coerceSprintCapacity(frontMatter),
|
|
910
|
+
taskIds,
|
|
911
|
+
completedTaskIds,
|
|
912
|
+
velocity,
|
|
913
|
+
created_at: createdAt,
|
|
914
|
+
updated_at: updatedAt,
|
|
915
|
+
completed_at: typeof frontMatter.completed_at === "string" ? frontMatter.completed_at : void 0
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
async function createSprint(name, startDate, endDate, options = {}) {
|
|
919
|
+
const planningRoot = getPlanningRoot();
|
|
920
|
+
const sprintsDir = getBucketPathByKind("sprint", planningRoot);
|
|
921
|
+
await ensureDirectoryExists(sprintsDir);
|
|
922
|
+
const start = parseStrictDateOnly(startDate);
|
|
923
|
+
const end = parseStrictDateOnly(endDate);
|
|
924
|
+
if (!start || !end) {
|
|
925
|
+
throw new Error(`Invalid sprint dates: start=${startDate} end=${endDate}`);
|
|
926
|
+
}
|
|
927
|
+
if (end.getTime() <= start.getTime()) {
|
|
928
|
+
throw new Error("Invalid sprint date range: endDate must be after startDate");
|
|
929
|
+
}
|
|
930
|
+
return withSprintActivationLock(async () => {
|
|
931
|
+
if (!options.allowDuplicateStartDate) {
|
|
932
|
+
const conflict = await findSprintOverlapConflict(sprintsDir, startDate, endDate);
|
|
933
|
+
if (conflict) {
|
|
934
|
+
const idSuffix = conflict.conflictingSprintId ? ` (${conflict.conflictingSprintId})` : "";
|
|
935
|
+
if (conflict.kind === "exact") {
|
|
936
|
+
throw new Error(
|
|
937
|
+
`Sprint already active for date ${startDate}${idSuffix}. View it: poetic sprint status. Close it: poetic sprint close${conflict.conflictingSprintId ? ` ${conflict.conflictingSprintId}` : ""}.`
|
|
938
|
+
);
|
|
939
|
+
} else {
|
|
940
|
+
throw new Error(
|
|
941
|
+
`Sprint date range overlaps existing sprint${idSuffix} (${conflict.conflictingStartDate} to ${conflict.conflictingEndDate}). View it: poetic sprint status. Close it: poetic sprint close${conflict.conflictingSprintId ? ` ${conflict.conflictingSprintId}` : ""}.`
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
const sprintId = generateId(/* @__PURE__ */ new Date());
|
|
947
|
+
const filePath = resolvePlanningItemPath(sprintsDir, sprintId, { label: "sprintId" });
|
|
948
|
+
try {
|
|
949
|
+
await fs2.access(filePath);
|
|
950
|
+
throw new Error(
|
|
951
|
+
`Sprint already active for date ${startDate}. View it: poetic sprint status. Close it: poetic sprint close.`
|
|
952
|
+
);
|
|
953
|
+
} catch (error) {
|
|
954
|
+
if (error.code !== "ENOENT") {
|
|
955
|
+
throw error;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
const totalHours = options.totalHours ?? DEFAULT_SPRINT_CAPACITY_HOURS;
|
|
959
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
960
|
+
const sprint = {
|
|
961
|
+
id: sprintId,
|
|
962
|
+
kind: "sprint",
|
|
963
|
+
title: name,
|
|
964
|
+
name,
|
|
965
|
+
startDate,
|
|
966
|
+
endDate,
|
|
967
|
+
goal: options.goal,
|
|
968
|
+
status: "planning",
|
|
969
|
+
capacity: {
|
|
970
|
+
totalHours,
|
|
971
|
+
allocatedHours: 0,
|
|
972
|
+
remainingHours: totalHours,
|
|
973
|
+
utilizationPercent: 0
|
|
974
|
+
},
|
|
975
|
+
taskIds: [],
|
|
976
|
+
velocity: {
|
|
977
|
+
forecast: totalHours,
|
|
978
|
+
actual: 0
|
|
979
|
+
},
|
|
980
|
+
created_at: now,
|
|
981
|
+
updated_at: now
|
|
982
|
+
};
|
|
983
|
+
const body = `# Sprint: ${name}
|
|
984
|
+
|
|
985
|
+
**Duration**: ${startDate} to ${endDate}
|
|
986
|
+
**Status**: ${formatSprintStatusLabel(sprint.status)}
|
|
987
|
+
**Progress**: 0 tasks
|
|
988
|
+
|
|
989
|
+
## Sprint Goal
|
|
990
|
+
|
|
991
|
+
${options.goal || "No specific goal set"}
|
|
992
|
+
|
|
993
|
+
## Tasks Summary
|
|
994
|
+
|
|
995
|
+
${renderSprintTasksSummaryTable([])}
|
|
996
|
+
|
|
997
|
+
## Notes
|
|
998
|
+
|
|
999
|
+
Sprint created on ${now}
|
|
1000
|
+
`;
|
|
1001
|
+
await withIndexRegeneration(planningRoot, async () => {
|
|
1002
|
+
await writeMarkdownFile(
|
|
1003
|
+
filePath,
|
|
1004
|
+
sprint,
|
|
1005
|
+
body
|
|
1006
|
+
);
|
|
1007
|
+
});
|
|
1008
|
+
return sprint;
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
async function getCurrentSprint() {
|
|
1012
|
+
const planningRoot = getPlanningRoot();
|
|
1013
|
+
const sprintsDir = getBucketPathByKind("sprint", planningRoot);
|
|
1014
|
+
try {
|
|
1015
|
+
const files = await listMarkdownFiles(sprintsDir);
|
|
1016
|
+
if (files.length === 0) return null;
|
|
1017
|
+
let activeCandidate = null;
|
|
1018
|
+
let planningCandidate = null;
|
|
1019
|
+
for (const file of files) {
|
|
1020
|
+
let sprint;
|
|
1021
|
+
try {
|
|
1022
|
+
const { frontMatter } = await readMarkdownFile(file);
|
|
1023
|
+
sprint = coerceSprintYAML(frontMatter);
|
|
1024
|
+
} catch {
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
if (sprint.status === "active") {
|
|
1028
|
+
activeCandidate = { yaml: sprint, filePath: file };
|
|
1029
|
+
} else if (sprint.status === "planning") {
|
|
1030
|
+
planningCandidate = { yaml: sprint, filePath: file };
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return activeCandidate ?? planningCandidate;
|
|
1034
|
+
} catch {
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
async function addTaskToSprint(sprintId, taskId, estimatedHours) {
|
|
1039
|
+
const planningRoot = getPlanningRoot();
|
|
1040
|
+
const filePath = await assertSprintFileExists(sprintId);
|
|
1041
|
+
const tasksDir = getBucketPathByKind("task", planningRoot);
|
|
1042
|
+
return withSprintLock(sprintId, async () => {
|
|
1043
|
+
const { frontMatter, body } = await readMarkdownFile(filePath);
|
|
1044
|
+
const sprint = coerceSprintYAML(frontMatter);
|
|
1045
|
+
if (sprint.status === "completed") {
|
|
1046
|
+
throw new Error(`Cannot add task ${taskId} to completed sprint ${sprintId}`);
|
|
1047
|
+
}
|
|
1048
|
+
if (sprint.taskIds.includes(taskId)) {
|
|
1049
|
+
throw new Error(`Task ${taskId} is already in sprint ${sprintId}`);
|
|
1050
|
+
}
|
|
1051
|
+
const taskLookup = await getTask(taskId, { includeArchive: true });
|
|
1052
|
+
if (taskLookup.isArchived) {
|
|
1053
|
+
throw new Error(
|
|
1054
|
+
`Cannot add archived task ${taskId} to sprint ${sprintId}; restore the task before assigning it`
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
await ensureSprintDoesNotAlreadyLinkBacklogItem(
|
|
1058
|
+
sprintId,
|
|
1059
|
+
sprint.taskIds,
|
|
1060
|
+
taskLookup.yaml.backlogItemId
|
|
1061
|
+
);
|
|
1062
|
+
const taskDependencies = Array.isArray(taskLookup?.yaml?.dependencies) ? taskLookup.yaml.dependencies : [];
|
|
1063
|
+
if (taskDependencies.length > 0) {
|
|
1064
|
+
const unsatisfiedDependencies = [];
|
|
1065
|
+
const completedTaskIds = new Set(sprint.completedTaskIds ?? []);
|
|
1066
|
+
for (const dependencyId of taskDependencies) {
|
|
1067
|
+
try {
|
|
1068
|
+
const dependencyLookup = await getTask(dependencyId, {
|
|
1069
|
+
includeArchive: true
|
|
1070
|
+
});
|
|
1071
|
+
if (!dependencyLookup) {
|
|
1072
|
+
unsatisfiedDependencies.push(dependencyId);
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
const classification = classifyTaskDependency(dependencyId, {
|
|
1076
|
+
task: {
|
|
1077
|
+
status: dependencyLookup.yaml.status,
|
|
1078
|
+
isArchived: dependencyLookup.isArchived
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
const isInSameSprint = dependencyLookup.yaml.sprintId === sprintId && sprint.taskIds.includes(dependencyId);
|
|
1082
|
+
if (!classification.satisfied && !isInSameSprint) {
|
|
1083
|
+
unsatisfiedDependencies.push(dependencyId);
|
|
1084
|
+
}
|
|
1085
|
+
} catch {
|
|
1086
|
+
const classification = classifyTaskDependency(dependencyId, {
|
|
1087
|
+
task: null,
|
|
1088
|
+
recordedComplete: completedTaskIds.has(dependencyId)
|
|
1089
|
+
});
|
|
1090
|
+
if (!classification.satisfied) {
|
|
1091
|
+
unsatisfiedDependencies.push(dependencyId);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
if (unsatisfiedDependencies.length > 0) {
|
|
1096
|
+
throw new Error(
|
|
1097
|
+
`Task ${taskId} has unsatisfied dependency/dependencies not in sprint ${sprintId}: ${unsatisfiedDependencies.join(", ")}`
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
sprint.taskIds.push(taskId);
|
|
1102
|
+
sprint.completedTaskIds = (sprint.completedTaskIds ?? []).filter((id) => id !== taskId);
|
|
1103
|
+
sprint.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1104
|
+
let allocatedHours = 0;
|
|
1105
|
+
for (const id of sprint.taskIds) {
|
|
1106
|
+
try {
|
|
1107
|
+
const taskPath = resolvePlanningItemPath(tasksDir, id, { label: "taskId" });
|
|
1108
|
+
const { frontMatter: taskFrontMatter } = await readMarkdownFile(taskPath);
|
|
1109
|
+
const tfm = taskFrontMatter;
|
|
1110
|
+
const taskHours = typeof tfm.estimatedHours === "number" ? tfm.estimatedHours : 0;
|
|
1111
|
+
allocatedHours += taskHours;
|
|
1112
|
+
} catch {
|
|
1113
|
+
if (id === taskId) {
|
|
1114
|
+
allocatedHours += estimatedHours;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
sprint.capacity.allocatedHours = allocatedHours;
|
|
1119
|
+
sprint.capacity.remainingHours = Math.max(0, sprint.capacity.totalHours - allocatedHours);
|
|
1120
|
+
sprint.capacity.utilizationPercent = sprint.capacity.totalHours > 0 ? allocatedHours / sprint.capacity.totalHours * 100 : 0;
|
|
1121
|
+
await withIndexRegeneration(planningRoot, async () => {
|
|
1122
|
+
const { frontMatter: taskFrontMatter, body: taskBody } = await readMarkdownFile(
|
|
1123
|
+
taskLookup.filePath
|
|
1124
|
+
);
|
|
1125
|
+
const rawTask = taskFrontMatter;
|
|
1126
|
+
const currentSprintId = typeof rawTask.sprintId === "string" ? rawTask.sprintId : typeof rawTask.sprint_id === "string" ? rawTask.sprint_id : void 0;
|
|
1127
|
+
if (currentSprintId && currentSprintId !== sprintId) {
|
|
1128
|
+
let stillLinkedToCurrentSprint = false;
|
|
1129
|
+
try {
|
|
1130
|
+
const currentSprintPath = await resolveSprintPath(currentSprintId, {
|
|
1131
|
+
includeArchive: false
|
|
1132
|
+
});
|
|
1133
|
+
const { frontMatter: currentSprintFrontMatter } = await readMarkdownFile(currentSprintPath);
|
|
1134
|
+
const currentSprint = coerceSprintYAML(
|
|
1135
|
+
currentSprintFrontMatter
|
|
1136
|
+
);
|
|
1137
|
+
stillLinkedToCurrentSprint = currentSprint.taskIds.includes(taskId);
|
|
1138
|
+
} catch {
|
|
1139
|
+
stillLinkedToCurrentSprint = false;
|
|
1140
|
+
}
|
|
1141
|
+
if (stillLinkedToCurrentSprint) {
|
|
1142
|
+
throw new Error(
|
|
1143
|
+
`Task ${taskId} is already assigned to sprint ${currentSprintId}; remove it before adding to ${sprintId}`
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
rawTask.sprintId = sprintId;
|
|
1148
|
+
delete rawTask.sprint_id;
|
|
1149
|
+
rawTask.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1150
|
+
rawTask.version = parseTaskNumber(rawTask.version, 0) + 1;
|
|
1151
|
+
await writeMarkdownFile(
|
|
1152
|
+
taskLookup.filePath,
|
|
1153
|
+
rawTask,
|
|
1154
|
+
taskBody
|
|
1155
|
+
);
|
|
1156
|
+
await writeMarkdownFile(
|
|
1157
|
+
filePath,
|
|
1158
|
+
sprint,
|
|
1159
|
+
body
|
|
1160
|
+
);
|
|
1161
|
+
try {
|
|
1162
|
+
await refreshSprintMarkdownById(sprintId, { includeArchive: false });
|
|
1163
|
+
} catch {
|
|
1164
|
+
}
|
|
1165
|
+
});
|
|
1166
|
+
return sprint;
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
async function removeTaskFromSprint(sprintId, taskId, _estimatedHours) {
|
|
1170
|
+
const planningRoot = getPlanningRoot();
|
|
1171
|
+
const filePath = resolvePlanningItemPath(getBucketPathByKind("sprint", planningRoot), sprintId, {
|
|
1172
|
+
label: "sprintId"
|
|
1173
|
+
});
|
|
1174
|
+
const tasksDir = getBucketPathByKind("task", planningRoot);
|
|
1175
|
+
return withSprintLock(sprintId, async () => {
|
|
1176
|
+
const { frontMatter, body } = await readMarkdownFile(filePath);
|
|
1177
|
+
const sprint = coerceSprintYAML(frontMatter);
|
|
1178
|
+
if (sprint.status === "completed") {
|
|
1179
|
+
throw new Error(`Cannot remove task ${taskId} from completed sprint ${sprintId}`);
|
|
1180
|
+
}
|
|
1181
|
+
if (!sprint.taskIds.includes(taskId)) {
|
|
1182
|
+
return sprint;
|
|
1183
|
+
}
|
|
1184
|
+
sprint.taskIds = sprint.taskIds.filter((id) => id !== taskId);
|
|
1185
|
+
sprint.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1186
|
+
let allocatedHours = 0;
|
|
1187
|
+
for (const id of sprint.taskIds) {
|
|
1188
|
+
try {
|
|
1189
|
+
const taskPath = resolvePlanningItemPath(tasksDir, id, { label: "taskId" });
|
|
1190
|
+
const { frontMatter: taskFrontMatter } = await readMarkdownFile(taskPath);
|
|
1191
|
+
const tfm = taskFrontMatter;
|
|
1192
|
+
const taskHours = typeof tfm.estimatedHours === "number" ? tfm.estimatedHours : 0;
|
|
1193
|
+
allocatedHours += taskHours;
|
|
1194
|
+
} catch {
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
sprint.capacity.allocatedHours = allocatedHours;
|
|
1198
|
+
sprint.capacity.remainingHours = Math.max(0, sprint.capacity.totalHours - allocatedHours);
|
|
1199
|
+
sprint.capacity.utilizationPercent = sprint.capacity.totalHours > 0 ? allocatedHours / sprint.capacity.totalHours * 100 : 0;
|
|
1200
|
+
await withIndexRegeneration(planningRoot, async () => {
|
|
1201
|
+
try {
|
|
1202
|
+
const taskLookup = await getTask(taskId, { includeArchive: true });
|
|
1203
|
+
const { frontMatter: taskFrontMatter, body: taskBody } = await readMarkdownFile(
|
|
1204
|
+
taskLookup.filePath
|
|
1205
|
+
);
|
|
1206
|
+
const rawTask = taskFrontMatter;
|
|
1207
|
+
if (rawTask.sprintId === sprintId || rawTask.sprint_id === sprintId) {
|
|
1208
|
+
if (!rawTask.originalSprintId) {
|
|
1209
|
+
rawTask.originalSprintId = rawTask.sprintId ?? rawTask.sprint_id;
|
|
1210
|
+
}
|
|
1211
|
+
delete rawTask.sprintId;
|
|
1212
|
+
delete rawTask.sprint_id;
|
|
1213
|
+
rawTask.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1214
|
+
rawTask.version = parseTaskNumber(rawTask.version, 0) + 1;
|
|
1215
|
+
await writeMarkdownFile(
|
|
1216
|
+
taskLookup.filePath,
|
|
1217
|
+
rawTask,
|
|
1218
|
+
taskBody
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
} catch {
|
|
1222
|
+
}
|
|
1223
|
+
await writeMarkdownFile(
|
|
1224
|
+
filePath,
|
|
1225
|
+
sprint,
|
|
1226
|
+
body
|
|
1227
|
+
);
|
|
1228
|
+
try {
|
|
1229
|
+
await refreshSprintMarkdownById(sprintId, { includeArchive: false });
|
|
1230
|
+
} catch {
|
|
1231
|
+
}
|
|
1232
|
+
});
|
|
1233
|
+
return sprint;
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
async function detachArchivedTaskFromSprint(sprintId, taskId, options = {}) {
|
|
1237
|
+
const planningRoot = getPlanningRoot();
|
|
1238
|
+
let filePath;
|
|
1239
|
+
try {
|
|
1240
|
+
filePath = await resolveSprintPath(sprintId, { includeArchive: true });
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
if (error instanceof Error && error.message.includes(`Sprint not found: ${sprintId}`)) {
|
|
1243
|
+
const notFound = new Error(error.message);
|
|
1244
|
+
notFound.code = "ENOENT";
|
|
1245
|
+
throw notFound;
|
|
1246
|
+
}
|
|
1247
|
+
throw error;
|
|
1248
|
+
}
|
|
1249
|
+
const tasksDir = getBucketPathByKind("task", planningRoot);
|
|
1250
|
+
return withSprintLock(sprintId, async () => {
|
|
1251
|
+
const { frontMatter, body } = await readMarkdownFile(filePath);
|
|
1252
|
+
const sprint = coerceSprintYAML(frontMatter);
|
|
1253
|
+
if (!sprint.taskIds.includes(taskId)) {
|
|
1254
|
+
return sprint;
|
|
1255
|
+
}
|
|
1256
|
+
sprint.taskIds = sprint.taskIds.filter((id) => id !== taskId);
|
|
1257
|
+
if (options.completed) {
|
|
1258
|
+
sprint.completedTaskIds = [.../* @__PURE__ */ new Set([...sprint.completedTaskIds ?? [], taskId])];
|
|
1259
|
+
}
|
|
1260
|
+
sprint.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1261
|
+
let allocatedHours = 0;
|
|
1262
|
+
for (const id of sprint.taskIds) {
|
|
1263
|
+
try {
|
|
1264
|
+
const taskPath = resolvePlanningItemPath(tasksDir, id, { label: "taskId" });
|
|
1265
|
+
const { frontMatter: taskFrontMatter } = await readMarkdownFile(taskPath);
|
|
1266
|
+
const tfm = taskFrontMatter;
|
|
1267
|
+
const taskHours = typeof tfm.estimatedHours === "number" ? tfm.estimatedHours : 0;
|
|
1268
|
+
allocatedHours += taskHours;
|
|
1269
|
+
} catch {
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
sprint.capacity.allocatedHours = allocatedHours;
|
|
1273
|
+
sprint.capacity.remainingHours = Math.max(0, sprint.capacity.totalHours - allocatedHours);
|
|
1274
|
+
sprint.capacity.utilizationPercent = sprint.capacity.totalHours > 0 ? allocatedHours / sprint.capacity.totalHours * 100 : 0;
|
|
1275
|
+
await withIndexRegeneration(planningRoot, async () => {
|
|
1276
|
+
await writeMarkdownFile(
|
|
1277
|
+
filePath,
|
|
1278
|
+
sprint,
|
|
1279
|
+
body
|
|
1280
|
+
);
|
|
1281
|
+
try {
|
|
1282
|
+
await refreshSprintMarkdownById(sprintId, { includeArchive: true });
|
|
1283
|
+
} catch {
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
return sprint;
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
async function isTaskRecordedCompletedInSprint(sprintId, taskId) {
|
|
1290
|
+
const sprintPath = await resolveSprintPath(sprintId, { includeArchive: true });
|
|
1291
|
+
const { frontMatter } = await readMarkdownFile(sprintPath);
|
|
1292
|
+
const sprint = coerceSprintYAML(frontMatter);
|
|
1293
|
+
return (sprint.completedTaskIds ?? []).includes(taskId);
|
|
1294
|
+
}
|
|
1295
|
+
async function assertSprintCanAcceptRestoredTask(sprintId, taskId) {
|
|
1296
|
+
const sprintPath = await resolveSprintPath(sprintId, { includeArchive: true });
|
|
1297
|
+
if (!await fileExists(sprintPath)) {
|
|
1298
|
+
return { sprintExists: false, alreadyMember: false };
|
|
1299
|
+
}
|
|
1300
|
+
const { frontMatter } = await readMarkdownFile(sprintPath);
|
|
1301
|
+
const sprint = coerceSprintYAML(frontMatter);
|
|
1302
|
+
if (sprint.status === "completed") {
|
|
1303
|
+
throw new Error(
|
|
1304
|
+
`Cannot restore task ${taskId} into completed sprint ${sprintId}; reopen the sprint or restore the task into an active sprint first.`
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
return { sprintExists: true, alreadyMember: sprint.taskIds.includes(taskId) };
|
|
1308
|
+
}
|
|
1309
|
+
async function calculateCapacity(sprintId) {
|
|
1310
|
+
const filePath = await assertSprintFileExists(sprintId, { includeArchive: true });
|
|
1311
|
+
const { frontMatter } = await readMarkdownFile(filePath);
|
|
1312
|
+
const sprint = coerceSprintYAML(frontMatter);
|
|
1313
|
+
let allocatedHours = 0;
|
|
1314
|
+
for (const id of sprint.taskIds) {
|
|
1315
|
+
try {
|
|
1316
|
+
const taskHours = (await getTask(id, { includeArchive: true })).yaml.estimatedHours ?? 0;
|
|
1317
|
+
allocatedHours += taskHours;
|
|
1318
|
+
} catch {
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
const remainingHours = Math.max(0, sprint.capacity.totalHours - allocatedHours);
|
|
1322
|
+
const utilizationPercent = sprint.capacity.totalHours > 0 ? allocatedHours / sprint.capacity.totalHours * 100 : 0;
|
|
1323
|
+
return {
|
|
1324
|
+
totalHours: sprint.capacity.totalHours,
|
|
1325
|
+
allocatedHours,
|
|
1326
|
+
remainingHours,
|
|
1327
|
+
utilizationPercent
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
function checkCapacityWarning(capacity, additionalHours = 0) {
|
|
1331
|
+
if (capacity.totalHours <= 0) {
|
|
1332
|
+
return null;
|
|
1333
|
+
}
|
|
1334
|
+
const projectedAllocation = capacity.allocatedHours + additionalHours;
|
|
1335
|
+
const projectedUtilization = projectedAllocation / capacity.totalHours * 100;
|
|
1336
|
+
if (projectedUtilization > 100) {
|
|
1337
|
+
return {
|
|
1338
|
+
level: "error",
|
|
1339
|
+
message: `Sprint overcommitted: ${projectedAllocation}h / ${capacity.totalHours}h (${projectedUtilization.toFixed(1)}% utilization)`
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
if (projectedUtilization >= 80) {
|
|
1343
|
+
return {
|
|
1344
|
+
level: "warning",
|
|
1345
|
+
message: `Approaching capacity: ${projectedAllocation}h / ${capacity.totalHours}h (${projectedUtilization.toFixed(1)}% utilization)`
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
return null;
|
|
1349
|
+
}
|
|
1350
|
+
async function listTasksInSprint(sprintId) {
|
|
1351
|
+
const sprintPath = await assertSprintFileExists(sprintId, {
|
|
1352
|
+
includeArchive: true
|
|
1353
|
+
});
|
|
1354
|
+
const { frontMatter } = await readMarkdownFile(sprintPath);
|
|
1355
|
+
const sprint = coerceSprintYAML(frontMatter);
|
|
1356
|
+
const tasks = [];
|
|
1357
|
+
for (const taskId of sprint.taskIds) {
|
|
1358
|
+
try {
|
|
1359
|
+
const result = await getTask(taskId, { includeArchive: true });
|
|
1360
|
+
if (result.isArchived) {
|
|
1361
|
+
continue;
|
|
1362
|
+
}
|
|
1363
|
+
const taskFrontMatter = result.yaml;
|
|
1364
|
+
const rawStatus = taskFrontMatter.status;
|
|
1365
|
+
tasks.push({
|
|
1366
|
+
id: taskFrontMatter.id ?? taskId,
|
|
1367
|
+
title: taskFrontMatter.title ?? taskId,
|
|
1368
|
+
priority: taskFrontMatter.priority,
|
|
1369
|
+
estimatedHours: taskFrontMatter.estimatedHours ?? 0,
|
|
1370
|
+
...typeof taskFrontMatter.expectedRuntimeMinutes === "number" && taskFrontMatter.expectedRuntimeMinutes > 0 ? { expectedRuntimeMinutes: taskFrontMatter.expectedRuntimeMinutes } : {},
|
|
1371
|
+
status: typeof rawStatus === "string" && ["todo", "in_progress", "review", "done", "blocked", "cancelled"].includes(rawStatus) ? rawStatus : "todo",
|
|
1372
|
+
planArtifactPath: taskFrontMatter.planArtifactPath,
|
|
1373
|
+
resultArtifactPath: taskFrontMatter.resultArtifactPath,
|
|
1374
|
+
planStageId: taskFrontMatter.planStageId
|
|
1375
|
+
});
|
|
1376
|
+
} catch (error) {
|
|
1377
|
+
if (!isGenuinelyAbsentTaskError(error)) {
|
|
1378
|
+
throw error;
|
|
1379
|
+
}
|
|
1380
|
+
tasks.push({
|
|
1381
|
+
id: taskId,
|
|
1382
|
+
title: `(Missing Task: ${taskId})`,
|
|
1383
|
+
estimatedHours: 0,
|
|
1384
|
+
status: "todo",
|
|
1385
|
+
missing: true
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
return tasks;
|
|
1390
|
+
}
|
|
1391
|
+
function computeSprintTerminalReadiness(tasks) {
|
|
1392
|
+
const missingTaskIds = tasks.filter((t) => t.missing === true).map((t) => t.id);
|
|
1393
|
+
const realTasks = tasks.filter((t) => t.missing !== true);
|
|
1394
|
+
const incompleteTaskIds = realTasks.filter((t) => t.status !== "done").map((t) => t.id);
|
|
1395
|
+
const allDone = realTasks.length > 0 && incompleteTaskIds.length === 0;
|
|
1396
|
+
const ready = allDone && missingTaskIds.length === 0;
|
|
1397
|
+
return { ready, allDone, incompleteTaskIds, missingTaskIds };
|
|
1398
|
+
}
|
|
1399
|
+
function assertSprintTerminalReady(sprintId, tasks, options = {}) {
|
|
1400
|
+
const readiness = computeSprintTerminalReadiness(tasks);
|
|
1401
|
+
if (options.force) return readiness;
|
|
1402
|
+
const issues = [];
|
|
1403
|
+
if (readiness.incompleteTaskIds.length > 0) {
|
|
1404
|
+
issues.push(
|
|
1405
|
+
`${readiness.incompleteTaskIds.length} task(s) not completed: ${readiness.incompleteTaskIds.join(", ")}`
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
if (readiness.missingTaskIds.length > 0) {
|
|
1409
|
+
issues.push(
|
|
1410
|
+
`${readiness.missingTaskIds.length} missing task ref(s): ${readiness.missingTaskIds.join(", ")} (run \`poetic sprint prune-missing ${sprintId}\` or pass --force)`
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
if (issues.length > 0) {
|
|
1414
|
+
throw new Error(
|
|
1415
|
+
`Cannot close sprint ${sprintId}: ${issues.join("; ")}. Use --force to override.`
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
return readiness;
|
|
1419
|
+
}
|
|
1420
|
+
async function countArchivedTasksForSprint(sprintId) {
|
|
1421
|
+
const planningRoot = getPlanningRoot();
|
|
1422
|
+
const archiveRoot = getBucketArchivePathByKind("task", planningRoot);
|
|
1423
|
+
const files = await listMarkdownFilesRecursive(archiveRoot).catch(() => []);
|
|
1424
|
+
let count = 0;
|
|
1425
|
+
for (const file of files) {
|
|
1426
|
+
if (path2.basename(file).toLowerCase() === "readme.md") {
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
try {
|
|
1430
|
+
const { frontMatter } = await readMarkdownFile(file);
|
|
1431
|
+
const fm = frontMatter;
|
|
1432
|
+
const originalSprintId = typeof fm.originalSprintId === "string" ? fm.originalSprintId : void 0;
|
|
1433
|
+
if (originalSprintId === sprintId) {
|
|
1434
|
+
count++;
|
|
1435
|
+
}
|
|
1436
|
+
} catch {
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
return count;
|
|
1440
|
+
}
|
|
1441
|
+
async function findReadySprintsForBatchClose() {
|
|
1442
|
+
const sprints = await listSprints(false);
|
|
1443
|
+
const ready = [];
|
|
1444
|
+
const skipped = [];
|
|
1445
|
+
for (const sprint of sprints) {
|
|
1446
|
+
if (sprint.status !== "active" && sprint.status !== "planning") {
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
const tasks = await listTasksInSprint(sprint.id);
|
|
1450
|
+
const readiness = computeSprintTerminalReadiness(tasks);
|
|
1451
|
+
if (readiness.ready) {
|
|
1452
|
+
ready.push({ sprintId: sprint.id, totalTasks: tasks.length });
|
|
1453
|
+
continue;
|
|
1454
|
+
}
|
|
1455
|
+
if (tasks.length === 0) {
|
|
1456
|
+
const archivedTaskCount = await countArchivedTasksForSprint(sprint.id);
|
|
1457
|
+
if (archivedTaskCount > 0 || sprint.taskIds.length > 0) {
|
|
1458
|
+
ready.push({
|
|
1459
|
+
sprintId: sprint.id,
|
|
1460
|
+
totalTasks: archivedTaskCount > 0 ? archivedTaskCount : sprint.taskIds.length
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
continue;
|
|
1464
|
+
}
|
|
1465
|
+
if (readiness.allDone && readiness.missingTaskIds.length > 0) {
|
|
1466
|
+
skipped.push({
|
|
1467
|
+
sprintId: sprint.id,
|
|
1468
|
+
reason: `${readiness.missingTaskIds.length} missing task ref(s); run prune-missing or close --force`
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
return { ready, skipped };
|
|
1473
|
+
}
|
|
1474
|
+
async function refreshSprintMarkdown(sprintId) {
|
|
1475
|
+
await refreshSprintMarkdownById(sprintId, { includeArchive: false });
|
|
1476
|
+
}
|
|
1477
|
+
async function activateSprint(sprintId) {
|
|
1478
|
+
await withSprintActivationLock(async () => {
|
|
1479
|
+
const sprintPath = await resolveSprintPath(sprintId, {
|
|
1480
|
+
includeArchive: false
|
|
1481
|
+
});
|
|
1482
|
+
const { frontMatter, body } = await readMarkdownFile(sprintPath);
|
|
1483
|
+
const sprint = coerceSprintYAML(frontMatter);
|
|
1484
|
+
if (sprint.status !== "planning") {
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
sprint.status = "active";
|
|
1488
|
+
sprint.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1489
|
+
const tasks = await listTasksInSprint(sprintId);
|
|
1490
|
+
const updatedBody = updateSprintBodyTasksSummary(
|
|
1491
|
+
updateSprintBodyOverview(body, sprint, tasks),
|
|
1492
|
+
tasks
|
|
1493
|
+
);
|
|
1494
|
+
await withIndexRegeneration(getPlanningRoot(), async () => {
|
|
1495
|
+
await writeMarkdownFile(
|
|
1496
|
+
sprintPath,
|
|
1497
|
+
sprint,
|
|
1498
|
+
updatedBody
|
|
1499
|
+
);
|
|
1500
|
+
});
|
|
1501
|
+
const planningRoot = getPlanningRoot();
|
|
1502
|
+
const sprintsDir = getBucketPathByKind("sprint", planningRoot);
|
|
1503
|
+
const allFiles = await listMarkdownFiles(sprintsDir).catch(() => []);
|
|
1504
|
+
for (const file of allFiles) {
|
|
1505
|
+
if (file === sprintPath) continue;
|
|
1506
|
+
try {
|
|
1507
|
+
await withSprintLock(path2.basename(file, ".md"), async () => {
|
|
1508
|
+
const { frontMatter: otherFm, body: otherBody } = await readMarkdownFile(file);
|
|
1509
|
+
const other = coerceSprintYAML(otherFm);
|
|
1510
|
+
if (other.status === "active") {
|
|
1511
|
+
other.status = "planning";
|
|
1512
|
+
other.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1513
|
+
await writeMarkdownFile(
|
|
1514
|
+
file,
|
|
1515
|
+
other,
|
|
1516
|
+
otherBody
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1519
|
+
});
|
|
1520
|
+
} catch (err) {
|
|
1521
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1522
|
+
if (message.includes("is busy") || message.includes("Timed out")) {
|
|
1523
|
+
throw new Error(
|
|
1524
|
+
`Cannot activate sprint ${sprintId}: another sprint file is locked (${path2.basename(file)}): ${message}`
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
emitSprintManagerWarning("Skipped sprint deactivation check during activation", {
|
|
1528
|
+
sprintId,
|
|
1529
|
+
file: path2.basename(file),
|
|
1530
|
+
error: message
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
async function completeSprint(sprintId, options = {}) {
|
|
1537
|
+
const forceTaskCompletion = options.forceTaskCompletion ?? false;
|
|
1538
|
+
const planningRoot = getPlanningRoot();
|
|
1539
|
+
const currentPath = resolvePlanningItemPath(
|
|
1540
|
+
getBucketPathByKind("sprint", planningRoot),
|
|
1541
|
+
sprintId,
|
|
1542
|
+
{ label: "sprintId" }
|
|
1543
|
+
);
|
|
1544
|
+
const archivedTaskPaths = [];
|
|
1545
|
+
return withSprintLock(sprintId, async () => {
|
|
1546
|
+
const sprintPath = await resolveSprintPath(sprintId, {
|
|
1547
|
+
includeArchive: true
|
|
1548
|
+
});
|
|
1549
|
+
const { frontMatter, body } = await readMarkdownFile(sprintPath);
|
|
1550
|
+
const sprint = coerceSprintYAML(frontMatter);
|
|
1551
|
+
if (sprintPath !== currentPath && sprint.status === "completed") {
|
|
1552
|
+
return { sprint, archivedPath: sprintPath, archivedTaskPaths: [], touchedPaths: [] };
|
|
1553
|
+
}
|
|
1554
|
+
sprint.status = "completed";
|
|
1555
|
+
sprint.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1556
|
+
sprint.updated_at = sprint.completed_at;
|
|
1557
|
+
const completedAt = sprint.completed_at;
|
|
1558
|
+
const tasks = await listTasksInSprint(sprintId);
|
|
1559
|
+
const archivedDoneHours = await sumArchivedDoneHoursForSprint(sprintId);
|
|
1560
|
+
applyDerivedSprintMetrics(sprint, tasks, archivedDoneHours);
|
|
1561
|
+
assertSprintTerminalReady(sprintId, tasks, { force: forceTaskCompletion });
|
|
1562
|
+
const sprintBucket = getBucketByName("sprint", planningRoot);
|
|
1563
|
+
if (!sprintBucket) throw new Error("Bucket not configured: sprint");
|
|
1564
|
+
const archivedPath = getArchivedFilePath(sprintBucket, sprintId, completedAt, planningRoot);
|
|
1565
|
+
const updatedBody = updateSprintBodyTasksSummary(
|
|
1566
|
+
updateSprintBodyOverview(body, sprint, tasks),
|
|
1567
|
+
tasks
|
|
1568
|
+
);
|
|
1569
|
+
await withIndexRegeneration(planningRoot, async () => {
|
|
1570
|
+
const sprintRollbackSnapshot = {
|
|
1571
|
+
filePath: currentPath,
|
|
1572
|
+
frontMatter,
|
|
1573
|
+
body
|
|
1574
|
+
};
|
|
1575
|
+
const completedTaskSnapshots = [];
|
|
1576
|
+
const rollbackCompletedTasks = async () => {
|
|
1577
|
+
const rollbackFailures = [];
|
|
1578
|
+
for (const snapshot of completedTaskSnapshots.reverse()) {
|
|
1579
|
+
try {
|
|
1580
|
+
await ensureDirectoryExists(path2.dirname(snapshot.activePath));
|
|
1581
|
+
await writeMarkdownFile(snapshot.activePath, snapshot.frontMatter, snapshot.body);
|
|
1582
|
+
await fs2.unlink(snapshot.archivedPath).catch(() => void 0);
|
|
1583
|
+
} catch (rollbackErr) {
|
|
1584
|
+
rollbackFailures.push(
|
|
1585
|
+
`${snapshot.taskId}: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}`
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
try {
|
|
1590
|
+
await writeMarkdownFile(
|
|
1591
|
+
sprintRollbackSnapshot.filePath,
|
|
1592
|
+
sprintRollbackSnapshot.frontMatter,
|
|
1593
|
+
sprintRollbackSnapshot.body
|
|
1594
|
+
);
|
|
1595
|
+
} catch (rollbackErr) {
|
|
1596
|
+
rollbackFailures.push(
|
|
1597
|
+
`${sprintId}: ${rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr)}`
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
return rollbackFailures;
|
|
1601
|
+
};
|
|
1602
|
+
const warnRollbackFailures = (rollbackFailures) => {
|
|
1603
|
+
if (rollbackFailures.length > 0) {
|
|
1604
|
+
emitSprintManagerWarning(
|
|
1605
|
+
"Partial rollback during completeSprint; manual recovery required",
|
|
1606
|
+
{
|
|
1607
|
+
sprintId,
|
|
1608
|
+
failures: rollbackFailures
|
|
1609
|
+
},
|
|
1610
|
+
"ERROR" /* ERROR */
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
for (const taskId of sprint.taskIds) {
|
|
1615
|
+
try {
|
|
1616
|
+
let taskSnapshot;
|
|
1617
|
+
try {
|
|
1618
|
+
const taskLookup = await getTask(taskId, { includeArchive: true });
|
|
1619
|
+
if (!taskLookup.isArchived) {
|
|
1620
|
+
const { frontMatter: taskFrontMatter, body: taskBody } = await readMarkdownFile(
|
|
1621
|
+
taskLookup.filePath
|
|
1622
|
+
);
|
|
1623
|
+
taskSnapshot = {
|
|
1624
|
+
activePath: taskLookup.filePath,
|
|
1625
|
+
frontMatter: taskFrontMatter,
|
|
1626
|
+
body: taskBody
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
} catch (snapshotError) {
|
|
1630
|
+
const errorMessage = snapshotError instanceof Error ? snapshotError.message : String(snapshotError);
|
|
1631
|
+
if (errorMessage.includes("Task not found")) {
|
|
1632
|
+
continue;
|
|
1633
|
+
}
|
|
1634
|
+
throw snapshotError;
|
|
1635
|
+
}
|
|
1636
|
+
const completed = await archiveTaskForSprintClose(taskId, {
|
|
1637
|
+
force: forceTaskCompletion,
|
|
1638
|
+
preserveBlockedReason: true,
|
|
1639
|
+
completionContractRoot: planningRoot
|
|
1640
|
+
});
|
|
1641
|
+
if (taskSnapshot) {
|
|
1642
|
+
const archivedPath2 = completed?.archivedPath;
|
|
1643
|
+
if (archivedPath2) {
|
|
1644
|
+
completedTaskSnapshots.push({
|
|
1645
|
+
taskId,
|
|
1646
|
+
activePath: taskSnapshot.activePath,
|
|
1647
|
+
archivedPath: archivedPath2,
|
|
1648
|
+
frontMatter: taskSnapshot.frontMatter,
|
|
1649
|
+
body: taskSnapshot.body
|
|
1650
|
+
});
|
|
1651
|
+
archivedTaskPaths.push({
|
|
1652
|
+
id: taskId,
|
|
1653
|
+
activePath: taskSnapshot.activePath,
|
|
1654
|
+
archivedPath: archivedPath2
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
} catch (error) {
|
|
1659
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1660
|
+
if (!errorMessage.includes("Task not found")) {
|
|
1661
|
+
warnRollbackFailures(await rollbackCompletedTasks());
|
|
1662
|
+
throw error;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
try {
|
|
1667
|
+
await ensureDirectoryExists(path2.dirname(archivedPath));
|
|
1668
|
+
await writeMarkdownFile(
|
|
1669
|
+
archivedPath,
|
|
1670
|
+
sprint,
|
|
1671
|
+
updatedBody
|
|
1672
|
+
);
|
|
1673
|
+
await fs2.unlink(currentPath);
|
|
1674
|
+
} catch (error) {
|
|
1675
|
+
await fs2.unlink(archivedPath).catch(() => void 0);
|
|
1676
|
+
warnRollbackFailures(await rollbackCompletedTasks());
|
|
1677
|
+
throw error;
|
|
1678
|
+
}
|
|
1679
|
+
});
|
|
1680
|
+
return {
|
|
1681
|
+
sprint,
|
|
1682
|
+
archivedPath,
|
|
1683
|
+
archivedTaskPaths,
|
|
1684
|
+
touchedPaths: [
|
|
1685
|
+
currentPath,
|
|
1686
|
+
archivedPath,
|
|
1687
|
+
...archivedTaskPaths.flatMap((entry) => [entry.activePath, entry.archivedPath])
|
|
1688
|
+
]
|
|
1689
|
+
};
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
async function closeSprint(sprintId, options = {}) {
|
|
1693
|
+
const result = await closeSprintWithSyncPaths(sprintId, options);
|
|
1694
|
+
return result.sprint;
|
|
1695
|
+
}
|
|
1696
|
+
async function closeSprintWithSyncPaths(sprintId, options = {}) {
|
|
1697
|
+
const planningRoot = getPlanningRoot();
|
|
1698
|
+
const currentPath = await resolveSprintPath(sprintId, {
|
|
1699
|
+
includeArchive: true
|
|
1700
|
+
});
|
|
1701
|
+
const { frontMatter } = await readMarkdownFile(currentPath);
|
|
1702
|
+
const sprint = coerceSprintYAML(frontMatter);
|
|
1703
|
+
if (options.dryRun) {
|
|
1704
|
+
const tasks = await listTasksInSprint(sprintId);
|
|
1705
|
+
assertSprintTerminalReady(sprintId, tasks, { force: options.force });
|
|
1706
|
+
return { sprint, touchedPaths: [] };
|
|
1707
|
+
}
|
|
1708
|
+
if (options.noArchive) {
|
|
1709
|
+
return withSprintLock(sprintId, async () => {
|
|
1710
|
+
const { frontMatter: lockedFrontMatter, body: lockedBody } = await readMarkdownFile(currentPath);
|
|
1711
|
+
const lockedSprint = coerceSprintYAML(lockedFrontMatter);
|
|
1712
|
+
if (lockedSprint.status === "completed") {
|
|
1713
|
+
return { sprint: lockedSprint, touchedPaths: [] };
|
|
1714
|
+
}
|
|
1715
|
+
const lockedTasks = await listTasksInSprint(sprintId);
|
|
1716
|
+
assertSprintTerminalReady(sprintId, lockedTasks, { force: options.force });
|
|
1717
|
+
lockedSprint.status = "completed";
|
|
1718
|
+
lockedSprint.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1719
|
+
lockedSprint.updated_at = lockedSprint.completed_at;
|
|
1720
|
+
const lockedArchivedDoneHours = await sumArchivedDoneHoursForSprint(sprintId);
|
|
1721
|
+
applyDerivedSprintMetrics(lockedSprint, lockedTasks, lockedArchivedDoneHours);
|
|
1722
|
+
const updatedBody = updateSprintBodyTasksSummary(
|
|
1723
|
+
updateSprintBodyOverview(lockedBody, lockedSprint, lockedTasks),
|
|
1724
|
+
lockedTasks
|
|
1725
|
+
);
|
|
1726
|
+
await withIndexRegeneration(planningRoot, async () => {
|
|
1727
|
+
await writeMarkdownFile(
|
|
1728
|
+
currentPath,
|
|
1729
|
+
lockedSprint,
|
|
1730
|
+
updatedBody
|
|
1731
|
+
);
|
|
1732
|
+
});
|
|
1733
|
+
return { sprint: lockedSprint, touchedPaths: [currentPath] };
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
const result = await completeSprint(sprintId, {
|
|
1737
|
+
forceTaskCompletion: Boolean(options.force)
|
|
1738
|
+
});
|
|
1739
|
+
return {
|
|
1740
|
+
sprint: result.sprint,
|
|
1741
|
+
touchedPaths: result.touchedPaths
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
async function listSprints(archived = false) {
|
|
1745
|
+
const planningRoot = getPlanningRoot();
|
|
1746
|
+
const sprintsDir = archived ? path2.join(getBucketPathByKind("sprint", planningRoot), "archive") : getBucketPathByKind("sprint", planningRoot);
|
|
1747
|
+
const files = archived ? await listMarkdownFilesRecursive(sprintsDir) : await listMarkdownFiles(sprintsDir);
|
|
1748
|
+
const sprints = [];
|
|
1749
|
+
for (const file of files) {
|
|
1750
|
+
try {
|
|
1751
|
+
const { frontMatter } = await readMarkdownFile(file);
|
|
1752
|
+
sprints.push(coerceSprintYAML(frontMatter));
|
|
1753
|
+
} catch (error) {
|
|
1754
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1755
|
+
emitSprintManagerWarning("Skipping malformed sprint file", {
|
|
1756
|
+
file,
|
|
1757
|
+
error: msg,
|
|
1758
|
+
archived
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
return sprints;
|
|
1763
|
+
}
|
|
1764
|
+
async function renameSprint(sprintId, newName) {
|
|
1765
|
+
const trimmedName = newName.trim();
|
|
1766
|
+
if (!trimmedName) {
|
|
1767
|
+
throw new Error("Sprint name cannot be empty");
|
|
1768
|
+
}
|
|
1769
|
+
const planningRoot = getPlanningRoot();
|
|
1770
|
+
return withSprintLock(sprintId, async () => {
|
|
1771
|
+
const sprintPath = await resolveSprintPath(sprintId, {
|
|
1772
|
+
includeArchive: false
|
|
1773
|
+
});
|
|
1774
|
+
const { frontMatter, body } = await readMarkdownFile(sprintPath);
|
|
1775
|
+
const sprint = coerceSprintYAML(frontMatter);
|
|
1776
|
+
if (sprint.status === "completed") {
|
|
1777
|
+
throw new Error(
|
|
1778
|
+
`Cannot rename sprint '${sprintId}': completed sprints are read-only (assumption A2)`
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
sprint.name = trimmedName;
|
|
1782
|
+
sprint.title = trimmedName;
|
|
1783
|
+
sprint.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1784
|
+
const updatedBody = body.replace(/^# Sprint:.*$/m, `# Sprint: ${trimmedName}`);
|
|
1785
|
+
await withIndexRegeneration(planningRoot, async () => {
|
|
1786
|
+
await writeMarkdownFile(
|
|
1787
|
+
sprintPath,
|
|
1788
|
+
sprint,
|
|
1789
|
+
updatedBody
|
|
1790
|
+
);
|
|
1791
|
+
});
|
|
1792
|
+
return sprint;
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
async function findFinishedSprints(options = {}) {
|
|
1796
|
+
const referenceDate = options.referenceDate ?? /* @__PURE__ */ new Date();
|
|
1797
|
+
const activeSprints = await listSprints(false);
|
|
1798
|
+
const finishedSprints = [];
|
|
1799
|
+
for (const sprint of activeSprints) {
|
|
1800
|
+
if (sprint.status === "completed") {
|
|
1801
|
+
continue;
|
|
1802
|
+
}
|
|
1803
|
+
if (!sprint.endDate) {
|
|
1804
|
+
continue;
|
|
1805
|
+
}
|
|
1806
|
+
const refDateStr = formatLocalDateString(referenceDate);
|
|
1807
|
+
if (sprint.endDate >= refDateStr) {
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
const tasks = await listTasksInSprint(sprint.id);
|
|
1811
|
+
const readiness = computeSprintTerminalReadiness(tasks);
|
|
1812
|
+
const completedTasks = tasks.filter((t) => t.missing !== true && t.status === "done").length;
|
|
1813
|
+
const allTasksCompleted = tasks.length === 0 || readiness.ready;
|
|
1814
|
+
if (allTasksCompleted) {
|
|
1815
|
+
finishedSprints.push({
|
|
1816
|
+
sprintId: sprint.id,
|
|
1817
|
+
endDate: sprint.endDate,
|
|
1818
|
+
totalTasks: tasks.length,
|
|
1819
|
+
completedTasks
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
return finishedSprints;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
export {
|
|
1827
|
+
acquireSprintLock,
|
|
1828
|
+
acquireSprintLockLease,
|
|
1829
|
+
withSprintLock,
|
|
1830
|
+
getSprintLockHandoffEnv,
|
|
1831
|
+
getSprintLockOwnerToken,
|
|
1832
|
+
refreshSprintLock,
|
|
1833
|
+
unlockSprintLock,
|
|
1834
|
+
DEFAULT_SPRINT_CAPACITY_HOURS,
|
|
1835
|
+
findSprintOverlapConflict,
|
|
1836
|
+
createSprint,
|
|
1837
|
+
getCurrentSprint,
|
|
1838
|
+
addTaskToSprint,
|
|
1839
|
+
removeTaskFromSprint,
|
|
1840
|
+
detachArchivedTaskFromSprint,
|
|
1841
|
+
isTaskRecordedCompletedInSprint,
|
|
1842
|
+
assertSprintCanAcceptRestoredTask,
|
|
1843
|
+
calculateCapacity,
|
|
1844
|
+
checkCapacityWarning,
|
|
1845
|
+
listTasksInSprint,
|
|
1846
|
+
computeSprintTerminalReadiness,
|
|
1847
|
+
assertSprintTerminalReady,
|
|
1848
|
+
countArchivedTasksForSprint,
|
|
1849
|
+
findReadySprintsForBatchClose,
|
|
1850
|
+
refreshSprintMarkdown,
|
|
1851
|
+
activateSprint,
|
|
1852
|
+
completeSprint,
|
|
1853
|
+
closeSprint,
|
|
1854
|
+
closeSprintWithSyncPaths,
|
|
1855
|
+
listSprints,
|
|
1856
|
+
renameSprint,
|
|
1857
|
+
findFinishedSprints
|
|
1858
|
+
};
|
|
1859
|
+
//# sourceMappingURL=chunk-4YX7J6QL.js.map
|