fixed regressed parsing, tool sanitation
This commit is contained in:
308
bun.lock
Normal file
308
bun.lock
Normal file
@@ -0,0 +1,308 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "@mikefreno/ralpi",
|
||||
"dependencies": {
|
||||
"yaml": "^2.4.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"bun-types": "^1.3.14",
|
||||
"typescript": "^5.3.0",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@earendil-works/pi-coding-agent": "*",
|
||||
"@earendil-works/pi-tui": "*",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.91.1", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw=="],
|
||||
|
||||
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
|
||||
|
||||
"@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="],
|
||||
|
||||
"@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="],
|
||||
|
||||
"@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="],
|
||||
|
||||
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
|
||||
|
||||
"@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/eventstream-handler-node": "^3.972.16", "@aws-sdk/middleware-eventstream": "^3.972.12", "@aws-sdk/middleware-websocket": "^3.972.19", "@aws-sdk/token-providers": "3.1048.0", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ=="],
|
||||
|
||||
"@aws-sdk/core": ["@aws-sdk/core@3.974.19", "", { "dependencies": { "@aws-sdk/types": "^3.973.12", "@aws-sdk/xml-builder": "^3.972.29", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/core": "^3.24.6", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-SMNfLCU/41xxfFaC5Slwy8V/f1FRhakvyeeMeDeIxqNF0DzhDlXsXnJDELJYke1EtnJbfzfilW7tvulGfxMY6A=="],
|
||||
|
||||
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.45", "", { "dependencies": { "@aws-sdk/core": "^3.974.19", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ZPsnLyrpDRmojKrBbJykASyLLVFkjyD+fWATeSuYgaqablijGOzxPxEKyrwUvNg+bgSQ7PkW2FTu65Xco19Gag=="],
|
||||
|
||||
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.47", "", { "dependencies": { "@aws-sdk/core": "^3.974.19", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-1XdgHDIPbARHuzZXM7ouzIbSUZFU9dTi9k+ryMhiZU4QCam4dvwOyUEFjEHNxAZehCYUIOmsSUZ2un6BIgUkWg=="],
|
||||
|
||||
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.51", "", { "dependencies": { "@aws-sdk/core": "^3.974.19", "@aws-sdk/credential-provider-env": "^3.972.45", "@aws-sdk/credential-provider-http": "^3.972.47", "@aws-sdk/credential-provider-login": "^3.972.50", "@aws-sdk/credential-provider-process": "^3.972.45", "@aws-sdk/credential-provider-sso": "^3.972.50", "@aws-sdk/credential-provider-web-identity": "^3.972.50", "@aws-sdk/nested-clients": "^3.997.18", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-f8sRTVyM+9BbzQKPlUP9dVVpgNEu65jFckNAAGzRfCrlaSi5AWUbCKEHIMcIYokv8pWblSKEqHkqKYZtwINnhw=="],
|
||||
|
||||
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.50", "", { "dependencies": { "@aws-sdk/core": "^3.974.19", "@aws-sdk/nested-clients": "^3.997.18", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-NHHsKoMhw6UylSU0XDnDc87+IQW8tRBTIe6vnOX12GSIlBDtoce6bSzONleIglCyu8d3H9bmTSfk+sIN5yh3WA=="],
|
||||
|
||||
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.53", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.45", "@aws-sdk/credential-provider-http": "^3.972.47", "@aws-sdk/credential-provider-ini": "^3.972.51", "@aws-sdk/credential-provider-process": "^3.972.45", "@aws-sdk/credential-provider-sso": "^3.972.50", "@aws-sdk/credential-provider-web-identity": "^3.972.50", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/credential-provider-imds": "^4.3.7", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-z/JJ8Qvf2GiTn4bw+x8k7wQjxmPpNsiwZ7ls/h1cZHikrSpS0+65lB+lafnXZlxv1lqH4k6rQwh+2UsycC662g=="],
|
||||
|
||||
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.45", "", { "dependencies": { "@aws-sdk/core": "^3.974.19", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-QMJXjTGLmHE4Ie03T5H4hHOLfcvMc9DaODO6b5dgte3S8ECf5bBuHUJW4cQREcYZyRkOU8iymqtiBxqF4icxZg=="],
|
||||
|
||||
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.50", "", { "dependencies": { "@aws-sdk/core": "^3.974.19", "@aws-sdk/nested-clients": "^3.997.18", "@aws-sdk/token-providers": "3.1064.0", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-pQ9ww4G53gwHlon1NMz25JhaBo13E9Jv+VVgjh39C/yzvby+xhSnEOb+VDYShKNCh1TbttMF/5CFCHkZrIqOcA=="],
|
||||
|
||||
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.50", "", { "dependencies": { "@aws-sdk/core": "^3.974.19", "@aws-sdk/nested-clients": "^3.997.18", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-9DbaPaT2aMbz18wtSpq9HVBErjBQwxykqTFgG6n8Bn05GN68mITz+G1869ekYx0mVT/BDjETj5czz/3cPgLwxA=="],
|
||||
|
||||
"@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.21", "", { "dependencies": { "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-mVC0hOmwGJmNFezZ+wM8Sqfap/LjsMavEf2Evl0YWrLAcrdZOEdjnY8nRvgakVViWJSGm2eJxLuPVHGdeV06kA=="],
|
||||
|
||||
"@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.17", "", { "dependencies": { "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-tdbnXbw73ww62ABWP0G0Z/euvFowEEvAoi/zG4NaZo7HJFpfGho/Z65HyVzkJLT1cMsUregr4pTyxljlarT0wA=="],
|
||||
|
||||
"@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.27", "", { "dependencies": { "@aws-sdk/core": "^3.974.19", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-V/IgUogQm/NSGlNglDCkREirQXgjyrrq64vPt5qcRTGhEJJwPcUB3RyYE6iZ63WNGp4Ezc+JtVRA4QlSbhDvVQ=="],
|
||||
|
||||
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.18", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.19", "@aws-sdk/signature-v4-multi-region": "^3.996.33", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/fetch-http-handler": "^5.4.6", "@smithy/node-http-handler": "^4.7.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-xBWrodBvW5SHCZV11UZUJG0pSHkLCEREIBoNbff1C1sacOUCmxJnTCPE80sCGLCtqgXg98I2MQJe2z28tcZSsw=="],
|
||||
|
||||
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.33", "", { "dependencies": { "@aws-sdk/types": "^3.973.12", "@smithy/signature-v4": "^5.4.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-Hn0RThJEbyOZWV2PV9Z4YD3nitGPxybmyU17dSe9b61WOBcKnqS0WTtM3c1zyZq9WnGiyrfi/i+UBPUk7cM8Ug=="],
|
||||
|
||||
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1048.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA=="],
|
||||
|
||||
"@aws-sdk/types": ["@aws-sdk/types@3.973.12", "", { "dependencies": { "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-43ajd1NF0RMgX5k0hxCNUyEdrtFUsb2aHT2QvpktSC/2Eyb2Jr/JPVqdp0XIoaHWikZJq5tNWSLO6kB5q2eMCA=="],
|
||||
|
||||
"@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.7", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-M0D6oIpohdNHjc7udzTHEQyot0+0iuA36jc2I9Hps+f/GtKi2HO/pyijQnCnNcwZqLB5+rtn81z3eZK/GyjAmA=="],
|
||||
|
||||
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.29", "", { "dependencies": { "@smithy/types": "^4.14.3", "fast-xml-parser": "5.7.3", "tslib": "^2.6.2" } }, "sha512-fk0niuGFxfi8yIJuMVM4mhwObkiQSuwZFj3tAPrLVx64Pk3BkrEIpqjzHKY4hKoEBUD6Jg/S74Zj9jy+5F3DnQ=="],
|
||||
|
||||
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
|
||||
|
||||
"@earendil-works/pi-agent-core": ["@earendil-works/pi-agent-core@0.79.0", "", { "dependencies": { "@earendil-works/pi-ai": "^0.79.0", "ignore": "7.0.5", "typebox": "1.1.38", "yaml": "2.9.0" } }, "sha512-jQOtYjRGZ7+XC/olw9euLd2V03vkAPO8u0sSnQoLbyOQZz66dEBZrklTESk34Sf3AaeBSua28wjZR48ch1aXJQ=="],
|
||||
|
||||
"@earendil-works/pi-ai": ["@earendil-works/pi-ai@0.79.0", "", { "dependencies": { "@anthropic-ai/sdk": "0.91.1", "@aws-sdk/client-bedrock-runtime": "3.1048.0", "@google/genai": "1.52.0", "@mistralai/mistralai": "2.2.1", "@smithy/node-http-handler": "4.7.3", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.6", "openai": "6.26.0", "partial-json": "0.1.7", "typebox": "1.1.38" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-D/2aDoe9vcCbqAztALQcKkdqXGuaQcqAzLm8LfUhNaorwoIHkwnaAuDVlo+OkF5clpEwS8Z1bk2o8NiSrwEdsA=="],
|
||||
|
||||
"@earendil-works/pi-coding-agent": ["@earendil-works/pi-coding-agent@0.79.0", "", { "dependencies": { "@earendil-works/pi-agent-core": "^0.79.0", "@earendil-works/pi-ai": "^0.79.0", "@earendil-works/pi-tui": "^0.79.0", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "5.6.2", "cross-spawn": "7.0.6", "diff": "8.0.4", "glob": "13.0.6", "highlight.js": "10.7.3", "hosted-git-info": "9.0.3", "ignore": "7.0.5", "jiti": "2.7.0", "minimatch": "10.2.5", "proper-lockfile": "4.1.2", "typebox": "1.1.38", "undici": "8.3.0", "yaml": "2.9.0" }, "optionalDependencies": { "@mariozechner/clipboard": "0.3.9" }, "bin": { "pi": "dist/cli.js" } }, "sha512-pZoXk65vFR3dAzzmPNWEX61aHnT6+BaVhTyFDQAs1DyumaMeWpvzRV9ZrGxqlbVLwhrq+0LnXbaqDAFkhe2+MQ=="],
|
||||
|
||||
"@earendil-works/pi-tui": ["@earendil-works/pi-tui@0.79.0", "", { "dependencies": { "get-east-asian-width": "1.6.0", "marked": "15.0.12" } }, "sha512-qAQWMruW7YKbk2hPcTD4INtXfvIySXifbPQ+mFY5j3J8yf2tfElkh+gGPuBvgPKPT0z9WiAkd7iySCuQq0txuQ=="],
|
||||
|
||||
"@google/genai": ["@google/genai@1.52.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q=="],
|
||||
|
||||
"@mariozechner/clipboard": ["@mariozechner/clipboard@0.3.9", "", { "optionalDependencies": { "@mariozechner/clipboard-darwin-arm64": "0.3.9", "@mariozechner/clipboard-darwin-universal": "0.3.9", "@mariozechner/clipboard-darwin-x64": "0.3.9", "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-musl": "0.3.9", "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" } }, "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA=="],
|
||||
|
||||
"@mariozechner/clipboard-darwin-arm64": ["@mariozechner/clipboard-darwin-arm64@0.3.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ=="],
|
||||
|
||||
"@mariozechner/clipboard-darwin-universal": ["@mariozechner/clipboard-darwin-universal@0.3.9", "", { "os": "darwin" }, "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ=="],
|
||||
|
||||
"@mariozechner/clipboard-darwin-x64": ["@mariozechner/clipboard-darwin-x64@0.3.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg=="],
|
||||
|
||||
"@mariozechner/clipboard-linux-arm64-gnu": ["@mariozechner/clipboard-linux-arm64-gnu@0.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw=="],
|
||||
|
||||
"@mariozechner/clipboard-linux-arm64-musl": ["@mariozechner/clipboard-linux-arm64-musl@0.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ=="],
|
||||
|
||||
"@mariozechner/clipboard-linux-riscv64-gnu": ["@mariozechner/clipboard-linux-riscv64-gnu@0.3.9", "", { "os": "linux", "cpu": "none" }, "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw=="],
|
||||
|
||||
"@mariozechner/clipboard-linux-x64-gnu": ["@mariozechner/clipboard-linux-x64-gnu@0.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw=="],
|
||||
|
||||
"@mariozechner/clipboard-linux-x64-musl": ["@mariozechner/clipboard-linux-x64-musl@0.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ=="],
|
||||
|
||||
"@mariozechner/clipboard-win32-arm64-msvc": ["@mariozechner/clipboard-win32-arm64-msvc@0.3.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ=="],
|
||||
|
||||
"@mariozechner/clipboard-win32-x64-msvc": ["@mariozechner/clipboard-win32-x64-msvc@0.3.9", "", { "os": "win32", "cpu": "x64" }, "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA=="],
|
||||
|
||||
"@mistralai/mistralai": ["@mistralai/mistralai@2.2.1", "", { "dependencies": { "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.25.0" } }, "sha512-uKU8CZmL2RzYKmplsU01hii4p3pe4HqJefpWNRWXm1Tcm0Sm4xXfwSLIy4k7ZCPlbETCGcp69E7hZs+WOJ5itQ=="],
|
||||
|
||||
"@nodable/entities": ["@nodable/entities@2.1.1", "", {}, "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg=="],
|
||||
|
||||
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
|
||||
|
||||
"@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
|
||||
|
||||
"@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="],
|
||||
|
||||
"@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="],
|
||||
|
||||
"@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="],
|
||||
|
||||
"@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
|
||||
|
||||
"@protobufjs/inquire": ["@protobufjs/inquire@1.1.2", "", {}, "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw=="],
|
||||
|
||||
"@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
|
||||
|
||||
"@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
|
||||
|
||||
"@protobufjs/utf8": ["@protobufjs/utf8@1.1.1", "", {}, "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg=="],
|
||||
|
||||
"@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="],
|
||||
|
||||
"@smithy/core": ["@smithy/core@3.24.6", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug=="],
|
||||
|
||||
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.3.8", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-5cAM+KZC02sTqDt6NaLXyu50M/GNMd1eTzDVR8Lb0BBsVtu7RWHo47VPPEEv1vt3Yub6uzr+M5FHC+GtoT0USg=="],
|
||||
|
||||
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.4.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-FEwEYJ1jlBKdhe9TPzfghEi1bP55ZeEImlDkEa62bBBYzUcnB6RUCyuiS2mqKt6ZVjUbBgcNhzfIctH+Hevx9g=="],
|
||||
|
||||
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||
|
||||
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA=="],
|
||||
|
||||
"@smithy/signature-v4": ["@smithy/signature-v4@5.4.6", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-Ojg4B6oIDlIr1R86xCDJt1zJWnYa0VINmqdjfe9qxWjdRivHalZ3iSlQgVqYbW0MdpFOC5XfHEWsnbmdnpIILQ=="],
|
||||
|
||||
"@smithy/types": ["@smithy/types@4.14.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ=="],
|
||||
|
||||
"@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||
|
||||
"@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
"@types/node": ["@types/node@20.19.42", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-5L7SUaFC1RyDraj2yRhyBzHTobyXHmohD100CChNtyPyleoq37Mqab5Gn8XEKI04dfN/oqPdpHk38MgcQWHbZg=="],
|
||||
|
||||
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
|
||||
|
||||
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||
|
||||
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
|
||||
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
|
||||
|
||||
"bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
|
||||
|
||||
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
||||
|
||||
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="],
|
||||
|
||||
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
|
||||
|
||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||
|
||||
"fast-xml-builder": ["fast-xml-builder@1.2.0", "", { "dependencies": { "path-expression-matcher": "^1.5.0", "xml-naming": "^0.1.0" } }, "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q=="],
|
||||
|
||||
"fast-xml-parser": ["fast-xml-parser@5.7.3", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.7", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg=="],
|
||||
|
||||
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
|
||||
|
||||
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
||||
|
||||
"gaxios": ["gaxios@7.1.5", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg=="],
|
||||
|
||||
"gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
|
||||
|
||||
"get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="],
|
||||
|
||||
"glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
||||
|
||||
"google-auth-library": ["google-auth-library@10.7.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-QpTAbNJ36TliZLx3TTtahR8HG0hN9RllL1e3FymOvQSIKK8JmgV58H924ub2wa2DsS3ANjjP1Aw1N+Ramc8hqQ=="],
|
||||
|
||||
"google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="],
|
||||
|
||||
"hosted-git-info": ["hosted-git-info@9.0.3", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg=="],
|
||||
|
||||
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
|
||||
|
||||
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
||||
|
||||
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
|
||||
|
||||
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
|
||||
|
||||
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
|
||||
|
||||
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
|
||||
|
||||
"jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
|
||||
|
||||
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||
|
||||
"lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="],
|
||||
|
||||
"marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
|
||||
|
||||
"minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
|
||||
|
||||
"node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
||||
|
||||
"openai": ["openai@6.26.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA=="],
|
||||
|
||||
"p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
|
||||
|
||||
"partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="],
|
||||
|
||||
"path-expression-matcher": ["path-expression-matcher@1.5.0", "", {}, "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
|
||||
|
||||
"proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="],
|
||||
|
||||
"protobufjs": ["protobufjs@7.6.2", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-N9EiLovGEQOJSPF26Ij7qUGvahfEnq0eeYZ02aigIedkmz1qZSwjnP9SBITHJuF/6MYbIW4HDN8zdYjsjqJKXQ=="],
|
||||
|
||||
"retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"strnum": ["strnum@2.3.0", "", {}, "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q=="],
|
||||
|
||||
"ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"typebox": ["typebox@1.1.38", "", {}, "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"undici": ["undici@8.3.0", "", {}, "sha512-TkUDgb6tl7KOGZ+7e8E3d2FYgUQgF6z5YypqjWmixVQSQERFcVrVg0ySADm2LVLRh5ljAaHTCR5Fmz3Q34rB7Q=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
|
||||
|
||||
"xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="],
|
||||
|
||||
"yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
|
||||
|
||||
"zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
||||
|
||||
"zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
|
||||
|
||||
"@aws-sdk/credential-provider-http/@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.7", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A=="],
|
||||
|
||||
"@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1064.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.19", "@aws-sdk/nested-clients": "^3.997.18", "@aws-sdk/types": "^3.973.12", "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-sjI+iA4JtgeckBgKwPQF7KzWillRoNDmtpiM0TRa0syiAKFHKUSf84kPXSO3+gA7aMMSxrcxzOM2oPSecaJvEA=="],
|
||||
|
||||
"@aws-sdk/nested-clients/@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.7", "", { "dependencies": { "@smithy/core": "^3.24.6", "@smithy/types": "^4.14.3", "tslib": "^2.6.2" } }, "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A=="],
|
||||
|
||||
"p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
|
||||
}
|
||||
}
|
||||
32
index.ts
32
index.ts
@@ -194,7 +194,11 @@ async function executePlanBatches(
|
||||
|
||||
// In parallel mode, rebuild the plan to filter out newly blocked tasks
|
||||
if (mode === "parallel") {
|
||||
const completed = new Set(progress.getCompletedTaskIds());
|
||||
// Use buildCompletedSet to include file-based [x] completions
|
||||
// (progress.getCompletedTaskIds() only knows about tasks completed
|
||||
// during THIS execution session — tasks that were already [x] in the
|
||||
// file before the run started would be re-included and re-executed).
|
||||
const completed = buildCompletedSet(progress, project);
|
||||
const newPlan = buildExecutionPlan(
|
||||
project,
|
||||
completed,
|
||||
@@ -354,17 +358,33 @@ export default function ralpiLoopExtension(pi: ExtensionAPI): void {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Strip control characters and newlines from a display label so it
|
||||
* does not break TUI layout (tree branches, text width calculation).
|
||||
*/
|
||||
function sanitizeLabel(s: string): string {
|
||||
return s
|
||||
.replace(/\r?\n/g, " ")
|
||||
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
/** Format a tool call argument into a short label. */
|
||||
function formatToolLabel(name: string, args: unknown): string {
|
||||
const a = args as Record<string, unknown> | undefined;
|
||||
if (!a) return name;
|
||||
if (name === "bash") return String(a.command ?? "").slice(0, 70);
|
||||
if (name === "bash")
|
||||
return sanitizeLabel(String(a.command ?? "").slice(0, 70));
|
||||
if (name === "write" || name === "read" || name === "edit")
|
||||
return String(a.path ?? "").slice(0, 60);
|
||||
return sanitizeLabel(String(a.path ?? "").slice(0, 60));
|
||||
if (name === "grep")
|
||||
return `${a.pattern ?? "?"} — ${String(a.path ?? "").slice(0, 40)}`;
|
||||
if (name === "find") return `${a.path ?? "."} — ${a.glob ?? "*"}`;
|
||||
if (name === "ls") return String(a.path ?? ".").slice(0, 60);
|
||||
return sanitizeLabel(
|
||||
`${a.pattern ?? "?"} — ${String(a.path ?? "").slice(0, 40)}`,
|
||||
);
|
||||
if (name === "find")
|
||||
return sanitizeLabel(`${a.path ?? "."} — ${a.glob ?? "*"}`);
|
||||
if (name === "ls")
|
||||
return sanitizeLabel(String(a.path ?? ".").slice(0, 60));
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@mikefreno/ralpi",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.2",
|
||||
"description": "Execute tasks from task files/PRD's using DAG-based dependency resolution with persistent progress tracking",
|
||||
"keywords": [
|
||||
"pi-package",
|
||||
@@ -31,7 +31,8 @@
|
||||
],
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit",
|
||||
"prepublishOnly": "tsc --noEmit"
|
||||
"prepublishOnly": "tsc --noEmit",
|
||||
"test": "bun test"
|
||||
},
|
||||
"engines": {
|
||||
"bun": ">=1.1.0"
|
||||
@@ -59,6 +60,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"bun-types": "^1.3.14",
|
||||
"typescript": "^5.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
14
src/dag.ts
14
src/dag.ts
@@ -24,7 +24,7 @@ export function getBlockedTasks(
|
||||
for (const task of pendingTasks) {
|
||||
if (blocked.has(task.id)) continue;
|
||||
const deps = task.dependencies || [];
|
||||
if (deps.some((dep) => failedTaskIds.has(dep))) {
|
||||
if (deps.some((dep) => failedTaskIds.has(dep) || blocked.has(dep))) {
|
||||
blocked.add(task.id);
|
||||
changed = true;
|
||||
}
|
||||
@@ -46,9 +46,15 @@ export function buildExecutionPlan(
|
||||
parallelGroup?: number,
|
||||
failedTaskIds: Set<string> = new Set(),
|
||||
): ExecutionPlan {
|
||||
// Filter out already completed tasks
|
||||
const pendingTasks = project.tasks.filter((t) => !completed.has(t.id));
|
||||
const skippedTasks = project.tasks.filter((t) => completed.has(t.id));
|
||||
// Filter out already completed AND failed tasks
|
||||
// Failed tasks should not be re-scheduled — they're only re-attempted
|
||||
// via the retry mechanism inside executeTask, not via the DAG.
|
||||
const pendingTasks = project.tasks.filter(
|
||||
(t) => !completed.has(t.id) && !failedTaskIds.has(t.id),
|
||||
);
|
||||
const skippedTasks = project.tasks.filter(
|
||||
(t) => completed.has(t.id) || failedTaskIds.has(t.id),
|
||||
);
|
||||
|
||||
// With explicitly declared parallel groups, all groups are independent.
|
||||
// Since there are no cross-group dependencies by definition, standard
|
||||
|
||||
@@ -866,6 +866,20 @@ function sleep(ms: number): Promise<void> {
|
||||
|
||||
// ─── Tool Call Formatting ────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Strip control characters and newlines from a display label so it
|
||||
* does not break TUI layout (tree branches, text width calculation).
|
||||
*/
|
||||
function sanitizeLabel(s: string): string {
|
||||
// Replace newlines/carriage returns with spaces (multi-line commands
|
||||
// must fit on a single tree-branch line), then strip ASCII control
|
||||
// characters except \t (which is harmless) and keep printable chars.
|
||||
return s
|
||||
.replace(/\r?\n/g, " ")
|
||||
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a tool call argument into a short label.
|
||||
*/
|
||||
@@ -873,21 +887,20 @@ function formatToolArg(name: string, args: unknown): string {
|
||||
const a = args as Record<string, unknown>;
|
||||
switch (name) {
|
||||
case "bash":
|
||||
return truncateMiddle(String(a.command ?? ""), 70);
|
||||
return sanitizeLabel(truncateMiddle(String(a.command ?? ""), 70));
|
||||
case "write":
|
||||
case "read":
|
||||
return truncateMiddle(String(a.path ?? ""), 60);
|
||||
return sanitizeLabel(truncateMiddle(String(a.path ?? ""), 60));
|
||||
case "edit":
|
||||
return truncateMiddle(String(a.path ?? ""), 60);
|
||||
return sanitizeLabel(truncateMiddle(String(a.path ?? ""), 60));
|
||||
case "grep":
|
||||
return `${a.pattern ?? "?"} — ${truncateMiddle(
|
||||
String(a.path ?? ""),
|
||||
40,
|
||||
)}`;
|
||||
return sanitizeLabel(
|
||||
`${a.pattern ?? "?"} — ${truncateMiddle(String(a.path ?? ""), 40)}`,
|
||||
);
|
||||
case "find":
|
||||
return `${a.path ?? "."} — ${a.glob ?? "*"}`;
|
||||
return sanitizeLabel(`${a.path ?? "."} — ${a.glob ?? "*"}`);
|
||||
case "ls":
|
||||
return truncateMiddle(String(a.path ?? "."), 60);
|
||||
return sanitizeLabel(truncateMiddle(String(a.path ?? "."), 60));
|
||||
default:
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -44,8 +44,27 @@ export function parseTaskFile(filePath: string): Project {
|
||||
|
||||
// ─── Fio Format Parser ───────────────────────────────────────────────────────
|
||||
|
||||
/** Match both markdown heading (## Dependencies) and plain heading (Dependencies). */
|
||||
const DEP_HEADING_RE = /^(?:##\s+)?Dependencies\s*$/m;
|
||||
/** Match both markdown heading (## Tasks) and plain heading (Tasks). */
|
||||
const TASK_HEADING_RE = /^(?:##\s+)?Tasks\s*$/m;
|
||||
/** Match other markdown headings (## Something). */
|
||||
const ANY_MD_HEADING_RE = /^##\s/;
|
||||
/**
|
||||
* Detect a plain (non-markdown) section heading like "Exit criteria".
|
||||
* A plain heading must:
|
||||
* - Start with a letter
|
||||
* - Contain only letters and spaces
|
||||
* - Have no colons (avoids matching "Objective:" and "Status legend:")
|
||||
* - Not be a task/dep line (doesn't start with "-")
|
||||
*/
|
||||
function isPlainSectionHeader(line: string): boolean {
|
||||
const trimmed = line.trim();
|
||||
return trimmed.length > 0 && /^[A-Za-z][A-Za-z\s]*$/.test(trimmed);
|
||||
}
|
||||
|
||||
function hasDependenciesSection(content: string): boolean {
|
||||
return /^##\s+Dependencies\s*$/m.test(content);
|
||||
return DEP_HEADING_RE.test(content);
|
||||
}
|
||||
|
||||
function parseFioFormat(
|
||||
@@ -61,20 +80,21 @@ function parseFioFormat(
|
||||
let inDeps = false;
|
||||
|
||||
for (const line of lines) {
|
||||
if (/^##\s+Tasks\s*$/m.test(line)) {
|
||||
if (TASK_HEADING_RE.test(line)) {
|
||||
inTasks = true;
|
||||
inDeps = false;
|
||||
continue;
|
||||
}
|
||||
if (/^##\s+Dependencies\s*$/m.test(line)) {
|
||||
if (DEP_HEADING_RE.test(line)) {
|
||||
inTasks = false;
|
||||
inDeps = true;
|
||||
continue;
|
||||
}
|
||||
// Reset state on any other section heading — both ##-style and plain
|
||||
if (
|
||||
/^##\s/.test(line) &&
|
||||
!/^##\s+Tasks/.test(line) &&
|
||||
!/^##\s+Dependencies/.test(line)
|
||||
(ANY_MD_HEADING_RE.test(line) || isPlainSectionHeader(line)) &&
|
||||
!TASK_HEADING_RE.test(line) &&
|
||||
!DEP_HEADING_RE.test(line)
|
||||
) {
|
||||
inTasks = false;
|
||||
inDeps = false;
|
||||
@@ -272,12 +292,14 @@ function parseFioFormat(
|
||||
}
|
||||
}
|
||||
|
||||
// Extract exit criteria
|
||||
// Extract exit criteria — detect both ## Exit Criteria and plain Exit criteria
|
||||
const exitCriteria: string[] = [];
|
||||
const exitIdx = lines.findIndex((l) => /^##\s+Exit\s+Criteria/i.test(l));
|
||||
const exitCriteriaRe = /^(?:##\s+)?Exit\s+Criteria/i;
|
||||
const exitIdx = lines.findIndex((l) => exitCriteriaRe.test(l));
|
||||
if (exitIdx >= 0) {
|
||||
for (let i = exitIdx + 1; i < lines.length; i++) {
|
||||
if (/^##\s/.test(lines[i])) break;
|
||||
// Stop at any new section heading (##-style or plain)
|
||||
if (/^##\s/.test(lines[i]) || isPlainSectionHeader(lines[i])) break;
|
||||
const m = lines[i].match(/^-\s+(.+)$/);
|
||||
if (m) exitCriteria.push(m[1].trim());
|
||||
}
|
||||
@@ -419,9 +441,14 @@ export function updateTaskInFile(
|
||||
const char = statusToChar(status);
|
||||
|
||||
// Strategy 1: Fio numbered format — match by explicit task ID in the file
|
||||
// Try both padded (01) and raw (1) variations
|
||||
// Try both padded (01) and raw (1) variations.
|
||||
// When the task ID is already zero-padded (e.g., "01"), skip the raw ID
|
||||
// to avoid partial matches ("1" matching the second digit of "01").
|
||||
const idPatterns = new Set([escapeRegex(taskId)]);
|
||||
if (!taskId.startsWith("0")) {
|
||||
const rawId = parseInt(taskId, 10).toString();
|
||||
const idPatterns = new Set([escapeRegex(taskId), escapeRegex(rawId)]);
|
||||
idPatterns.add(escapeRegex(rawId));
|
||||
}
|
||||
|
||||
for (const idPattern of idPatterns) {
|
||||
const fioRegex = new RegExp(
|
||||
|
||||
674
tests/dag-construction.test.ts
Normal file
674
tests/dag-construction.test.ts
Normal file
@@ -0,0 +1,674 @@
|
||||
/// <reference types="bun-types" />
|
||||
import { describe, it, expect } from "bun:test";
|
||||
import type { Project, Task } from "../src/types";
|
||||
import {
|
||||
buildExecutionPlan,
|
||||
buildSequentialPlan,
|
||||
getBlockedTasks,
|
||||
detectCycles,
|
||||
getCriticalPath,
|
||||
formatDependencyChain,
|
||||
formatExecutionPlan,
|
||||
} from "../src/dag";
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
function makeProject(overrides?: Partial<Project>): Project {
|
||||
return {
|
||||
tasks: [],
|
||||
dependencies: {},
|
||||
sourcePath: "/tmp/test.md",
|
||||
sourceDir: "/tmp",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function task(
|
||||
id: string,
|
||||
dependencies: string[] = [],
|
||||
status: Task["status"] = "pending",
|
||||
parallelGroup?: number,
|
||||
): Task {
|
||||
return { id, title: `Task ${id}`, status, dependencies, parallelGroup };
|
||||
}
|
||||
|
||||
function tasksFrom(...args: Task[]): Task[] {
|
||||
return args;
|
||||
}
|
||||
|
||||
// ─── Basic DAG Construction ──────────────────────────────────────────────────
|
||||
|
||||
describe("buildExecutionPlan (Kahn's algorithm)", () => {
|
||||
it("handles empty task list", () => {
|
||||
const project = makeProject({ tasks: [] });
|
||||
const plan = buildExecutionPlan(project, new Set());
|
||||
expect(plan.batches).toEqual([]);
|
||||
expect(plan.totalTasks).toBe(0);
|
||||
});
|
||||
|
||||
it("puts all root tasks in batch 0", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(task("01"), task("02"), task("03")),
|
||||
});
|
||||
const plan = buildExecutionPlan(project, new Set());
|
||||
expect(plan.batches).toHaveLength(1);
|
||||
expect(plan.batches[0].tasks.map((t) => t.id).sort()).toEqual([
|
||||
"01",
|
||||
"02",
|
||||
"03",
|
||||
]);
|
||||
});
|
||||
|
||||
it("builds correct linear dependency chain", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["02"]),
|
||||
task("04", ["03"]),
|
||||
),
|
||||
});
|
||||
const plan = buildExecutionPlan(project, new Set());
|
||||
expect(plan.batches).toHaveLength(4);
|
||||
expect(plan.batches[0].tasks.map((t) => t.id)).toEqual(["01"]);
|
||||
expect(plan.batches[1].tasks.map((t) => t.id)).toEqual(["02"]);
|
||||
expect(plan.batches[2].tasks.map((t) => t.id)).toEqual(["03"]);
|
||||
expect(plan.batches[3].tasks.map((t) => t.id)).toEqual(["04"]);
|
||||
});
|
||||
|
||||
it("groups parallelizable tasks in the same batch", () => {
|
||||
// Diamond: 01 -> 02, 03 -> 04
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["01"]),
|
||||
task("04", ["02", "03"]),
|
||||
),
|
||||
});
|
||||
const plan = buildExecutionPlan(project, new Set());
|
||||
// Batch 0: [01], Batch 1: [02, 03], Batch 2: [04]
|
||||
expect(plan.batches).toHaveLength(3);
|
||||
expect(plan.batches[0].tasks.map((t) => t.id)).toEqual(["01"]);
|
||||
expect(plan.batches[1].tasks.map((t) => t.id).sort()).toEqual(["02", "03"]);
|
||||
expect(plan.batches[2].tasks.map((t) => t.id)).toEqual(["04"]);
|
||||
});
|
||||
|
||||
it("assigns correct batchIndex values", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["01"]),
|
||||
task("04", ["02", "03"]),
|
||||
),
|
||||
});
|
||||
const plan = buildExecutionPlan(project, new Set());
|
||||
expect(plan.batches[0].batchIndex).toBe(0);
|
||||
expect(plan.batches[1].batchIndex).toBe(1);
|
||||
expect(plan.batches[2].batchIndex).toBe(2);
|
||||
});
|
||||
|
||||
it("skips completed tasks and includes them in skippedTasks", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01", [], "completed"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["02"]),
|
||||
),
|
||||
});
|
||||
const plan = buildExecutionPlan(project, new Set(["01"]));
|
||||
expect(plan.totalTasks).toBe(2);
|
||||
expect(plan.skippedTasks.map((t) => t.id)).toEqual(["01"]);
|
||||
expect(plan.batches).toHaveLength(2);
|
||||
expect(plan.batches[0].tasks.map((t) => t.id)).toEqual(["02"]);
|
||||
expect(plan.batches[1].tasks.map((t) => t.id)).toEqual(["03"]);
|
||||
});
|
||||
|
||||
it("throws on dependency cycle", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01", ["03"]),
|
||||
task("02", ["01"]),
|
||||
task("03", ["02"]),
|
||||
),
|
||||
});
|
||||
expect(() => buildExecutionPlan(project, new Set())).toThrow(
|
||||
/dependency cycle/i,
|
||||
);
|
||||
});
|
||||
|
||||
it("blocks tasks that depend on failed tasks", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["02"]),
|
||||
task("04", ["03"]),
|
||||
),
|
||||
});
|
||||
const plan = buildExecutionPlan(
|
||||
project,
|
||||
new Set(),
|
||||
undefined,
|
||||
new Set(["01"]),
|
||||
);
|
||||
// 01 is excluded from pending (failed). 02, 03, 04 are pending but
|
||||
// transitively blocked — they don't appear in batches.
|
||||
expect(plan.skippedTasks.map((t) => t.id)).toEqual(["01"]);
|
||||
expect(plan.totalTasks).toBe(3); // 02, 03, 04 are pending but blocked
|
||||
expect(plan.batches).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("blocks immediate dependents when task fails", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["01"]),
|
||||
task("04"), // independent
|
||||
),
|
||||
});
|
||||
const plan = buildExecutionPlan(
|
||||
project,
|
||||
new Set(),
|
||||
undefined,
|
||||
new Set(["01"]),
|
||||
);
|
||||
// 01 is excluded from pending (failed). 02, 03 are pending but blocked
|
||||
// (depend on 01). 04 is independent and ready.
|
||||
expect(plan.skippedTasks.map((t) => t.id)).toEqual(["01"]);
|
||||
expect(plan.batches[0].tasks.map((t) => t.id)).toEqual(["04"]);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Complex DAGs ───────────────────────────────────────────────────────────
|
||||
|
||||
describe("Complex DAG batching", () => {
|
||||
it("builds the OAuth PRD example correctly", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["02"]),
|
||||
task("04", ["01"]),
|
||||
task("05", ["03", "04"]),
|
||||
task("06", ["03", "04"]),
|
||||
task("07", ["03"]),
|
||||
task("08", ["05", "06", "07"]),
|
||||
),
|
||||
});
|
||||
const plan = buildExecutionPlan(project, new Set());
|
||||
// Expected batches: [01], [02,04], [03], [05,06,07], [08]
|
||||
expect(plan.batches).toHaveLength(5);
|
||||
expect(plan.batches[0].tasks.map((t) => t.id)).toEqual(["01"]);
|
||||
expect(plan.batches[1].tasks.map((t) => t.id).sort()).toEqual(["02", "04"]);
|
||||
expect(plan.batches[2].tasks.map((t) => t.id)).toEqual(["03"]);
|
||||
expect(plan.batches[3].tasks.map((t) => t.id).sort()).toEqual([
|
||||
"05",
|
||||
"06",
|
||||
"07",
|
||||
]);
|
||||
expect(plan.batches[4].tasks.map((t) => t.id)).toEqual(["08"]);
|
||||
});
|
||||
|
||||
it("builds the Design Token PRD example correctly", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["01"]),
|
||||
task("04", ["02", "03"]),
|
||||
task("05", ["04", "01"]),
|
||||
),
|
||||
});
|
||||
const plan = buildExecutionPlan(project, new Set());
|
||||
// Expected batches: [01], [02,03], [04], [05]
|
||||
expect(plan.batches).toHaveLength(4);
|
||||
expect(plan.batches[0].tasks.map((t) => t.id)).toEqual(["01"]);
|
||||
expect(plan.batches[1].tasks.map((t) => t.id).sort()).toEqual(["02", "03"]);
|
||||
expect(plan.batches[2].tasks.map((t) => t.id)).toEqual(["04"]);
|
||||
expect(plan.batches[3].tasks.map((t) => t.id)).toEqual(["05"]);
|
||||
});
|
||||
|
||||
it("handles a 3-tier diamond", () => {
|
||||
// 01
|
||||
// / \
|
||||
// 02 03
|
||||
// / \ / \
|
||||
// 04 05 06
|
||||
// \ | /
|
||||
// 07
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["01"]),
|
||||
task("04", ["02"]),
|
||||
task("05", ["02", "03"]),
|
||||
task("06", ["03"]),
|
||||
task("07", ["04", "05", "06"]),
|
||||
),
|
||||
});
|
||||
const plan = buildExecutionPlan(project, new Set());
|
||||
expect(plan.batches).toHaveLength(4);
|
||||
expect(plan.batches[0].tasks.map((t) => t.id)).toEqual(["01"]);
|
||||
expect(plan.batches[1].tasks.map((t) => t.id).sort()).toEqual(["02", "03"]);
|
||||
expect(plan.batches[2].tasks.map((t) => t.id).sort()).toEqual([
|
||||
"04",
|
||||
"05",
|
||||
"06",
|
||||
]);
|
||||
expect(plan.batches[3].tasks.map((t) => t.id)).toEqual(["07"]);
|
||||
});
|
||||
|
||||
it("handles a wide fan-out with delayed convergence", () => {
|
||||
// 01 -> 02,03,04,05,06
|
||||
// 02,03 -> 07
|
||||
// 04,05 -> 08
|
||||
// 06 -> 09
|
||||
// 07,08,09 -> 10
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["01"]),
|
||||
task("04", ["01"]),
|
||||
task("05", ["01"]),
|
||||
task("06", ["01"]),
|
||||
task("07", ["02", "03"]),
|
||||
task("08", ["04", "05"]),
|
||||
task("09", ["06"]),
|
||||
task("10", ["07", "08", "09"]),
|
||||
),
|
||||
});
|
||||
const plan = buildExecutionPlan(project, new Set());
|
||||
expect(plan.batches).toHaveLength(4);
|
||||
expect(plan.batches[0].tasks.map((t) => t.id)).toEqual(["01"]);
|
||||
expect(plan.batches[1].tasks.map((t) => t.id).sort()).toEqual([
|
||||
"02",
|
||||
"03",
|
||||
"04",
|
||||
"05",
|
||||
"06",
|
||||
]);
|
||||
expect(plan.batches[2].tasks.map((t) => t.id).sort()).toEqual([
|
||||
"07",
|
||||
"08",
|
||||
"09",
|
||||
]);
|
||||
expect(plan.batches[3].tasks.map((t) => t.id)).toEqual(["10"]);
|
||||
});
|
||||
|
||||
it("handles multiple independent subgraphs", () => {
|
||||
// Two completely independent chains:
|
||||
// Chain A: 01 -> 02 -> 03
|
||||
// Chain B: 04 -> 05
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["02"]),
|
||||
task("04"),
|
||||
task("05", ["04"]),
|
||||
),
|
||||
});
|
||||
const plan = buildExecutionPlan(project, new Set());
|
||||
// Batch 0: [01, 04] (both roots)
|
||||
// Batch 1: [02, 05]
|
||||
// Batch 2: [03]
|
||||
expect(plan.batches[0].tasks.map((t) => t.id).sort()).toEqual(["01", "04"]);
|
||||
expect(plan.batches[1].tasks.map((t) => t.id).sort()).toEqual(["02", "05"]);
|
||||
expect(plan.batches[2].tasks.map((t) => t.id)).toEqual(["03"]);
|
||||
});
|
||||
|
||||
it("batches tasks respecting fan-in convergence", () => {
|
||||
// 01 -> 03, 02 -> 03 (03 depends on both 01 AND 02)
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(task("01"), task("02"), task("03", ["01", "02"])),
|
||||
});
|
||||
const plan = buildExecutionPlan(project, new Set());
|
||||
expect(plan.batches[0].tasks.map((t) => t.id).sort()).toEqual(["01", "02"]);
|
||||
expect(plan.batches[1].tasks.map((t) => t.id)).toEqual(["03"]);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Sequential Plan ─────────────────────────────────────────────────────────
|
||||
|
||||
describe("buildSequentialPlan", () => {
|
||||
it("puts each task in its own batch", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(task("01"), task("02", ["01"]), task("03", ["01"])),
|
||||
});
|
||||
const plan = buildSequentialPlan(project, new Set());
|
||||
expect(plan.batches).toHaveLength(3);
|
||||
plan.batches.forEach((b, i) => {
|
||||
expect(b.tasks).toHaveLength(1);
|
||||
expect(b.batchIndex).toBe(i);
|
||||
});
|
||||
});
|
||||
|
||||
it("skips completed tasks and blocks transitively failed tasks", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["02"]),
|
||||
task("04"),
|
||||
),
|
||||
});
|
||||
const plan = buildSequentialPlan(project, new Set(["01"]), new Set(["01"]));
|
||||
// 01 failed => 02, 03 blocked. 04 independent, runs.
|
||||
expect(plan.skippedTasks.map((t) => t.id).sort()).toEqual([
|
||||
"01",
|
||||
"02",
|
||||
"03",
|
||||
]);
|
||||
expect(plan.totalTasks).toBe(3);
|
||||
});
|
||||
|
||||
it("maintains task order in sequential batches", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(task("01"), task("02", ["01"]), task("03", ["01"])),
|
||||
});
|
||||
const plan = buildSequentialPlan(project, new Set());
|
||||
expect(plan.batches.map((b) => b.tasks[0].id)).toEqual(["01", "02", "03"]);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── getBlockedTasks ─────────────────────────────────────────────────────────
|
||||
|
||||
describe("getBlockedTasks", () => {
|
||||
it("returns direct dependents of failed tasks", () => {
|
||||
const pending = tasksFrom(task("01"), task("02", ["01"]), task("03"));
|
||||
const blocked = getBlockedTasks(pending, new Set(["01"]));
|
||||
expect([...blocked]).toEqual(["02"]);
|
||||
});
|
||||
|
||||
it("returns transitive dependents (chain reaction)", () => {
|
||||
const pending = tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["02"]),
|
||||
task("04", ["03"]),
|
||||
);
|
||||
const blocked = getBlockedTasks(pending, new Set(["01"]));
|
||||
expect([...blocked].sort()).toEqual(["02", "03", "04"]);
|
||||
});
|
||||
|
||||
it("does not affect tasks in separate subgraphs", () => {
|
||||
const pending = tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("10"),
|
||||
task("11", ["10"]),
|
||||
);
|
||||
const blocked = getBlockedTasks(pending, new Set(["01"]));
|
||||
expect([...blocked].sort()).toEqual(["02"]);
|
||||
});
|
||||
|
||||
it("returns empty set when no tasks depend on failed tasks", () => {
|
||||
const pending = tasksFrom(task("01"), task("02"), task("03"));
|
||||
const blocked = getBlockedTasks(pending, new Set(["99"]));
|
||||
expect(blocked.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── detectCycles ────────────────────────────────────────────────────────────
|
||||
|
||||
describe("detectCycles", () => {
|
||||
it("returns empty for acyclic graph", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(task("01"), task("02", ["01"]), task("03", ["02"])),
|
||||
});
|
||||
expect(detectCycles(project)).toEqual([]);
|
||||
});
|
||||
|
||||
it("detects a 3-node cycle", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01", ["03"]),
|
||||
task("02", ["01"]),
|
||||
task("03", ["02"]),
|
||||
),
|
||||
});
|
||||
const cycles = detectCycles(project);
|
||||
expect(cycles.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("detects a self-loop", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(task("01", ["01"])),
|
||||
});
|
||||
const cycles = detectCycles(project);
|
||||
expect(cycles.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("detects cycle in disconnected subgraph", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"), // isolated
|
||||
task("02", ["03"]),
|
||||
task("03", ["02"]), // cycle
|
||||
),
|
||||
});
|
||||
const cycles = detectCycles(project);
|
||||
expect(cycles.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("returns empty for graph with only diamond patterns", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["01"]),
|
||||
task("04", ["02", "03"]),
|
||||
),
|
||||
});
|
||||
expect(detectCycles(project)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── getCriticalPath ─────────────────────────────────────────────────────────
|
||||
|
||||
describe("getCriticalPath", () => {
|
||||
it("returns the longest path through the DAG", () => {
|
||||
// 01 -> 02 -> 03 -> 04 (long = 4)
|
||||
// 01 -> 05 -> 04 (short = 3)
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["02"]),
|
||||
task("04", ["03", "05"]),
|
||||
task("05", ["01"]),
|
||||
),
|
||||
});
|
||||
const path = getCriticalPath(project);
|
||||
expect(path.length).toBe(4);
|
||||
expect(path[0].id).toBe("01");
|
||||
expect(path[path.length - 1].id).toBe("04");
|
||||
});
|
||||
|
||||
it("returns single-node path for roots", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(task("01"), task("02"), task("03")),
|
||||
});
|
||||
const path = getCriticalPath(project);
|
||||
expect(path.length).toBe(1);
|
||||
});
|
||||
|
||||
it("handles complex branching by picking the longest chain", () => {
|
||||
// 01 -> 02 -> 03 -> 04 -> 05 (long = 5)
|
||||
// 01 -> 06 -> 05 (short = 3)
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["02"]),
|
||||
task("04", ["03"]),
|
||||
task("05", ["04", "06"]),
|
||||
task("06", ["01"]),
|
||||
),
|
||||
});
|
||||
const path = getCriticalPath(project);
|
||||
// Should pick 01 -> 02 -> 03 -> 04 -> 05 (length 5)
|
||||
expect(path.length).toBe(5);
|
||||
expect(path.map((t) => t.id)).toEqual(["01", "02", "03", "04", "05"]);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── formatDependencyChain ───────────────────────────────────────────────────
|
||||
|
||||
describe("formatDependencyChain", () => {
|
||||
it("renders a simple tree", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(task("01"), task("02", ["01"])),
|
||||
});
|
||||
const formatted = formatDependencyChain(project);
|
||||
expect(formatted).toContain("01");
|
||||
expect(formatted).toContain("02");
|
||||
});
|
||||
|
||||
it("mentions root tasks", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(task("01"), task("02")),
|
||||
});
|
||||
const formatted = formatDependencyChain(project);
|
||||
expect(formatted).toMatch(/01.*root|root.*01/i);
|
||||
});
|
||||
|
||||
it("handles empty task list", () => {
|
||||
const project = makeProject({ tasks: [] });
|
||||
const formatted = formatDependencyChain(project);
|
||||
expect(formatted).toContain("no tasks");
|
||||
});
|
||||
|
||||
it("shows orphan tasks when dependencies reference non-existent IDs", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(task("01", ["99"])),
|
||||
});
|
||||
const formatted = formatDependencyChain(project);
|
||||
expect(formatted).toMatch(/orphan|unreached/i);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── formatExecutionPlan ─────────────────────────────────────────────────────
|
||||
|
||||
describe("formatExecutionPlan", () => {
|
||||
it("displays task counts and batches", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(task("01"), task("02", ["01"])),
|
||||
});
|
||||
const plan = buildExecutionPlan(project, new Set());
|
||||
const formatted = formatExecutionPlan(plan);
|
||||
expect(formatted).toContain("Total tasks");
|
||||
expect(formatted).toContain("Batches");
|
||||
expect(formatted).toContain("01");
|
||||
expect(formatted).toContain("02");
|
||||
});
|
||||
|
||||
it("shows skipped tasks", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(task("01", [], "completed"), task("02", ["01"])),
|
||||
});
|
||||
const plan = buildExecutionPlan(project, new Set(["01"]));
|
||||
const formatted = formatExecutionPlan(plan);
|
||||
expect(formatted).toContain("completed");
|
||||
});
|
||||
|
||||
it("shows parallel group annotations when provided", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(task("01"), task("02", ["01"]), task("03", ["01"])),
|
||||
parallelGroups: [{ index: 0, label: "UI sprint", taskIds: ["02", "03"] }],
|
||||
});
|
||||
const plan = buildExecutionPlan(project, new Set());
|
||||
const formatted = formatExecutionPlan(plan, project.parallelGroups);
|
||||
expect(formatted).toContain("UI sprint");
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Group-Aware Batching ────────────────────────────────────────────────────
|
||||
|
||||
describe("Parallel group batching", () => {
|
||||
it("builds batches when parallel groups are defined", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["01"]),
|
||||
task("04", ["02", "03"]),
|
||||
),
|
||||
parallelGroups: [
|
||||
{ index: 0, label: "Frontend", taskIds: ["01", "02", "03", "04"] },
|
||||
],
|
||||
});
|
||||
// Should route through buildGroupAwareBatches
|
||||
const plan = buildExecutionPlan(project, new Set());
|
||||
expect(plan.batches.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("respects intra-group dependencies in parallel groups", () => {
|
||||
// Tasks: 01 -> 02, 01 -> 03, 02 -> 04, 03 -> 04
|
||||
// With parallel groups, there are no cross-group dependencies by definition.
|
||||
// Intra-group deps are respected by Kahn's algorithm.
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01"),
|
||||
task("02", ["01"]),
|
||||
task("03", ["01"]),
|
||||
task("04", ["02", "03"]),
|
||||
),
|
||||
parallelGroups: [
|
||||
{ index: 0, label: "All", taskIds: ["01", "02", "03", "04"] },
|
||||
],
|
||||
});
|
||||
const plan = buildExecutionPlan(project, new Set());
|
||||
// Batch 0: [01], Batch 1: [02, 03], Batch 2: [04]
|
||||
expect(plan.batches[0].tasks.map((t) => t.id)).toEqual(["01"]);
|
||||
expect(plan.batches[1].tasks.map((t) => t.id).sort()).toEqual(["02", "03"]);
|
||||
expect(plan.batches[2].tasks.map((t) => t.id)).toEqual(["04"]);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Real-World Scenario: Resume with completed tasks ───────────────────────
|
||||
|
||||
describe("Real-world resume scenarios", () => {
|
||||
it("buildExecutionPlan correctly excludes file-based [x] completions", () => {
|
||||
// Design Token PRD resume: 01,02,03 [x] in file, 04 [~], 05 [ ]
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01", [], "completed"),
|
||||
task("02", ["01"], "completed"),
|
||||
task("03", ["01"], "completed"),
|
||||
task("04", ["02", "03"], "in_progress"),
|
||||
task("05", ["04", "01"], "pending"),
|
||||
),
|
||||
});
|
||||
// buildCompletedSet in index.ts produces {01, 02, 03} from file + progress
|
||||
// This simulates what happens after buildCompletedSet is called
|
||||
const completedFromFile = new Set(
|
||||
project.tasks.filter((t) => t.status === "completed").map((t) => t.id),
|
||||
);
|
||||
const plan = buildExecutionPlan(project, completedFromFile);
|
||||
|
||||
// Only 04 and 05 should be pending
|
||||
expect(plan.totalTasks).toBe(2);
|
||||
expect(plan.batches).toHaveLength(2);
|
||||
expect(plan.batches[0].tasks.map((t) => t.id)).toEqual(["04"]);
|
||||
expect(plan.batches[1].tasks.map((t) => t.id)).toEqual(["05"]);
|
||||
});
|
||||
|
||||
it("skipsTasks includes both progress-completed and file-completed tasks", () => {
|
||||
const project = makeProject({
|
||||
tasks: tasksFrom(
|
||||
task("01", [], "completed"),
|
||||
task("02", ["01"], "pending"),
|
||||
),
|
||||
});
|
||||
// Simulate: 01 completed in file AND in progress
|
||||
const plan = buildExecutionPlan(project, new Set(["01"]));
|
||||
expect(plan.skippedTasks.map((t) => t.id)).toEqual(["01"]);
|
||||
expect(plan.batches[0].tasks.map((t) => t.id)).toEqual(["02"]);
|
||||
});
|
||||
});
|
||||
30
tests/helpers.ts
Normal file
30
tests/helpers.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as os from "node:os";
|
||||
|
||||
// ─── Helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Create a temporary directory for test files.
|
||||
* Returns the path and a cleanup function.
|
||||
*/
|
||||
export function tempDir(): { dir: string; cleanup: () => void } {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ralpi-test-"));
|
||||
return {
|
||||
dir,
|
||||
cleanup: () => fs.rmSync(dir, { recursive: true, force: true }),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Write content to a temp markdown file and return its path.
|
||||
*/
|
||||
export function writeTaskFile(
|
||||
dir: string,
|
||||
name: string,
|
||||
content: string,
|
||||
): string {
|
||||
const filePath = path.join(dir, name);
|
||||
fs.writeFileSync(filePath, content, "utf-8");
|
||||
return filePath;
|
||||
}
|
||||
1119
tests/parser-dag.test.ts
Normal file
1119
tests/parser-dag.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
1030
tests/parser-formats.test.ts
Normal file
1030
tests/parser-formats.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user