{"_id":"@avesbox/magpie","_rev":"15-62e2640f1c35811a74d8ceade539eee2","name":"@avesbox/magpie","dist-tags":{"latest":"0.4.0"},"versions":{"0.1.0":{"name":"@avesbox/magpie","version":"0.1.0","keywords":["acceptance-testing","vitest","testing","traceability"],"author":{"name":"Francesco Vallone"},"license":"MIT","_id":"@avesbox/magpie@0.1.0","maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"homepage":"https://github.com/francescovallone/magpie#readme","bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"dist":{"shasum":"daa5ede8293a0cf2cdbb0b8297a82ff0780b6853","tarball":"https://registry.npmjs.org/@avesbox/magpie/-/magpie-0.1.0.tgz","fileCount":26,"integrity":"sha512-w1Ipv2UO1cA8C24BY/CMXK90q84vsjQGCWf+lBbNgogrDtYB9AoPn7lA8ia2SZfqse7+Wn7moOVzKCbjicRKKw==","signatures":[{"sig":"MEUCIQD5rW/Drk8G/laEjHU2BitX2wXBW9B4RD5AR7A/Kl1KsgIgcCVEeXxBoVYUr+U8+ils9KZPlHI2y43Q77Uc1TbkUq4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":154393},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","gitHead":"5c73bca74de30cda7113da0f15449dc527df5052","scripts":{"test":"vitest run","build":"tsc --project tsconfig.build.json","test:unit":"vitest run --project unit","typecheck":"tsc --project tsconfig.json --noEmit","test:watch":"vitest","test:acceptance":"vitest run --project acceptance"},"_npmUser":{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"},"repository":{"url":"git+https://github.com/francescovallone/magpie.git","type":"git"},"_npmVersion":"10.9.3","description":"Acceptance criteria scenarios on top of Vitest.","directories":{"test":"tests"},"_nodeVersion":"22.20.0","dependencies":{"@cucumber/gherkin":"^41.0.0","@cucumber/messages":"^33.0.4","@cucumber/cucumber-expressions":"^20.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.1.9","typescript":"^6.0.3","@types/node":"^26.1.0"},"_npmOperationalInternal":{"tmp":"tmp/magpie_0.1.0_1783354364268_0.9871435218001261","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@avesbox/magpie","version":"0.1.1","keywords":["acceptance-testing","vitest","testing","traceability"],"author":{"name":"Francesco Vallone"},"license":"MIT","_id":"@avesbox/magpie@0.1.1","maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"homepage":"https://github.com/francescovallone/magpie#readme","bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"dist":{"shasum":"a6cb99df5759cb435803e37cfa635142327edd91","tarball":"https://registry.npmjs.org/@avesbox/magpie/-/magpie-0.1.1.tgz","fileCount":27,"integrity":"sha512-VVfyG/IGUIZPlNjg0rMAV4V14VbD7hKCnG0AJfXVoxePmnyxFNFWXTRKyn1sfpVyU1cm1WZa0FnQX5hX8+Anjg==","signatures":[{"sig":"MEUCIHslycVOG1Y8v3+S2DbHS7nZtqggncLGCyaz/tk3ch+jAiEAjwR1EwcNrxHdmP/Y4Mq6CU9MmCFdIXPMz1ocv3+0jn0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":163047},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":"./dist/index.js"},"gitHead":"dcb9b0f90c7f35ae965fa812b4a284c31a82adfb","scripts":{"test":"vitest run","build":"tsc --project tsconfig.build.json","test:unit":"vitest run --project unit","typecheck":"tsc --project tsconfig.json --noEmit","test:watch":"vitest","test:acceptance":"vitest run --project acceptance"},"_npmUser":{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"},"repository":{"url":"git+https://github.com/francescovallone/magpie.git","type":"git"},"_npmVersion":"10.9.3","description":"Acceptance criteria scenarios on top of Vitest.","directories":{"test":"tests"},"_nodeVersion":"22.20.0","dependencies":{"@cucumber/gherkin":"^41.0.0","@cucumber/messages":"^33.0.4","@cucumber/cucumber-expressions":"^20.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0","typescript":"^6.0.3","@types/node":"^26.1.0"},"peerDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0"},"peerDependenciesMeta":{"vite":{"optional":true},"vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/magpie_0.1.1_1783457540252_0.5952979532530116","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@avesbox/magpie","version":"0.1.2","keywords":["acceptance-testing","vitest","testing","traceability"],"author":{"name":"Francesco Vallone"},"license":"MIT","_id":"@avesbox/magpie@0.1.2","maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"homepage":"https://github.com/francescovallone/magpie#readme","bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"dist":{"shasum":"40f0c7f69552a2a57480ec529ef3c0a7af487692","tarball":"https://registry.npmjs.org/@avesbox/magpie/-/magpie-0.1.2.tgz","fileCount":27,"integrity":"sha512-N5s9SG0ekVMtUIRV3FpkEvdlu9OZZDzoUGmIERTgnqIQGJamYZn1DqEyM74ya/SUZdvJUt4wKPtLFNOnAzR04Q==","signatures":[{"sig":"MEQCIBE0iQRwWO4cCEljQX3pQojIkMvMgQ960HJYJVu0c3BwAiBYU3nN0ezdC70HGls0vdZcQwwLUaC0x3QhPH2vCI1VDw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":182609},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":"./dist/index.js"},"gitHead":"f23ac085944957afd4138f877aa44c511f97ebb4","scripts":{"test":"vitest run","build":"tsc --project tsconfig.build.json","test:unit":"vitest run --project unit","typecheck":"tsc --project tsconfig.json --noEmit","test:watch":"vitest","test:acceptance":"vitest run --project acceptance"},"_npmUser":{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"},"repository":{"url":"git+https://github.com/francescovallone/magpie.git","type":"git"},"_npmVersion":"10.9.3","description":"Acceptance criteria scenarios on top of Vitest.","directories":{"test":"tests"},"_nodeVersion":"22.20.0","dependencies":{"@cucumber/gherkin":"^41.0.0","@cucumber/messages":"^33.0.4","@cucumber/cucumber-expressions":"^20.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0","typescript":"^6.0.3","@types/node":"^26.1.0"},"peerDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0"},"peerDependenciesMeta":{"vite":{"optional":true},"vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/magpie_0.1.2_1783497684697_0.7492458425851665","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@avesbox/magpie","version":"0.1.3","keywords":["acceptance-testing","vitest","testing","traceability"],"author":{"name":"Francesco Vallone"},"license":"MIT","_id":"@avesbox/magpie@0.1.3","maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"homepage":"https://github.com/francescovallone/magpie#readme","bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"dist":{"shasum":"03ea03c7973a0ab39a50691084ba800d1befcc16","tarball":"https://registry.npmjs.org/@avesbox/magpie/-/magpie-0.1.3.tgz","fileCount":29,"integrity":"sha512-q27Nt/UCfdSQ5eShB26hTJQtxswMHOb1wotMlBkeld4w4Gui/u0ydx1HowRkUbbw5Eli53EQpFfn9u9LkyPHDg==","signatures":[{"sig":"MEQCIAS8VOgGj50Xxn50JjPhLMoA4njHbhF+17RyaZO+wsOzAiA5TG69bJlDm6+nfDvrBJ1TIv4TRwzb58ZMwiob2aue4w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":127339},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":"./dist/index.js"},"gitHead":"0d5d668dc694aa3b81c6c3febb6a90676b78e5d6","scripts":{"test":"vitest run","build":"tsc --project tsconfig.build.json","test:unit":"vitest run --project unit","typecheck":"tsc --project tsconfig.json --noEmit","test:watch":"vitest","prepublishOnly":"npm run build","test:acceptance":"vitest run --project acceptance"},"_npmUser":{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"},"repository":{"url":"git+https://github.com/francescovallone/magpie.git","type":"git"},"_npmVersion":"10.9.3","description":"Acceptance criteria scenarios on top of Vitest.","directories":{"test":"tests"},"_nodeVersion":"22.20.0","dependencies":{"@cucumber/gherkin":"^41.0.0","@cucumber/messages":"^33.0.4","@cucumber/cucumber-expressions":"^20.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0","typescript":"^6.0.3","@types/node":"^26.1.0"},"peerDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0"},"peerDependenciesMeta":{"vite":{"optional":true},"vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/magpie_0.1.3_1783500800974_0.2176648611117742","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@avesbox/magpie","version":"0.1.4","keywords":["acceptance-testing","vitest","testing","traceability"],"author":{"name":"Francesco Vallone"},"license":"MIT","_id":"@avesbox/magpie@0.1.4","maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"homepage":"https://github.com/francescovallone/magpie#readme","bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"dist":{"shasum":"98a1d3c448e6c675b6a35606303bcdf8508764f1","tarball":"https://registry.npmjs.org/@avesbox/magpie/-/magpie-0.1.4.tgz","fileCount":29,"integrity":"sha512-LNHB7OdeRnhsGX2Tp68kQHlJ8pjHquhfMAVquGwLwKiV8BFTMOZwZP27qeSiPb/rT2/UUvaAqRBzj/U/ZbqzLQ==","signatures":[{"sig":"MEYCIQD53eOudhLylnDxQSG4BvAajiPo8Wkqgg96jN9JJ2tQrAIhAPpAaCMqOp2/zVkufRphHpmao6pK2KW4eiVxRuAiaLFk","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":127935},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":"./dist/index.js"},"gitHead":"39fb708fb91dcbc9954e45f44bdeed1d934da914","scripts":{"test":"vitest run","build":"tsc --project tsconfig.build.json","test:unit":"vitest run --project unit","typecheck":"tsc --project tsconfig.json --noEmit","test:watch":"vitest","prepublishOnly":"npm run build","test:acceptance":"vitest run --project acceptance"},"_npmUser":{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"},"repository":{"url":"git+https://github.com/francescovallone/magpie.git","type":"git"},"_npmVersion":"10.9.3","description":"Acceptance criteria scenarios on top of Vitest.","directories":{"test":"tests"},"_nodeVersion":"22.20.0","dependencies":{"@cucumber/gherkin":"^41.0.0","@cucumber/messages":"^33.0.4","@cucumber/cucumber-expressions":"^20.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0","typescript":"^6.0.3","@types/node":"^26.1.0"},"peerDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0"},"peerDependenciesMeta":{"vite":{"optional":true},"vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/magpie_0.1.4_1783522990261_0.12932260973987852","host":"s3://npm-registry-packages-npm-production"}},"0.1.5":{"name":"@avesbox/magpie","version":"0.1.5","keywords":["acceptance-testing","vitest","testing","traceability"],"author":{"name":"Francesco Vallone"},"license":"MIT","_id":"@avesbox/magpie@0.1.5","maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"homepage":"https://github.com/francescovallone/magpie#readme","bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"dist":{"shasum":"948bbf67e2dfef2f8b5388262573253553d713d1","tarball":"https://registry.npmjs.org/@avesbox/magpie/-/magpie-0.1.5.tgz","fileCount":29,"integrity":"sha512-rTF0+smhq04o9a2ntXNCiWryQsa7hir3MvYnKJlMSXX52pTCcs6HVNg6PGZ+FqHuokn85ZV7TqNsM9t88fQREQ==","signatures":[{"sig":"MEYCIQCQHLMk3r9R8bkIjf+E9JPG2ND3eQrkl4iV3qxSdfFzawIhALhYYOBveOyurNQZ6fVk6atB/XKSYEDqEy8/nefete6i","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":148431},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":"./dist/index.js"},"gitHead":"5d79993f2ed2da76a64892dcc3609e56467b1975","scripts":{"test":"vitest run","build":"tsc --project tsconfig.build.json","test:unit":"vitest run --project unit","typecheck":"tsc --project tsconfig.json --noEmit","test:watch":"vitest","prepublishOnly":"npm run build","test:acceptance":"vitest run --project acceptance"},"_npmUser":{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"},"repository":{"url":"git+https://github.com/francescovallone/magpie.git","type":"git"},"_npmVersion":"10.9.3","description":"Acceptance criteria scenarios on top of Vitest.","directories":{"test":"tests"},"_nodeVersion":"22.20.0","dependencies":{"@cucumber/gherkin":"^41.0.0","@cucumber/messages":"^33.0.4","@cucumber/cucumber-expressions":"^20.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0","typescript":"^6.0.3","@types/node":"^26.1.0"},"peerDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0"},"peerDependenciesMeta":{"vite":{"optional":true},"vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/magpie_0.1.5_1783632547477_0.3117322517318357","host":"s3://npm-registry-packages-npm-production"}},"0.1.6":{"name":"@avesbox/magpie","version":"0.1.6","keywords":["acceptance-testing","vitest","testing","traceability"],"author":{"name":"Francesco Vallone"},"license":"MIT","_id":"@avesbox/magpie@0.1.6","maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"homepage":"https://github.com/francescovallone/magpie#readme","bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"bin":{"magpie":"dist/bin.js"},"dist":{"shasum":"850af8e800b1693d3bb0f3f70bb512ecb2e59190","tarball":"https://registry.npmjs.org/@avesbox/magpie/-/magpie-0.1.6.tgz","fileCount":35,"integrity":"sha512-tTOAtDjL5sOltRjHa1oM56VJvLMfOCy0sCRiCYPQnBJ8sh8k2LMLs7Qc1YW6dS8dYKlIm00PxBIxldBU/TxYow==","signatures":[{"sig":"MEUCICG6wjs0S5FPTccMV4ry51ReCKnXsDf7HF8hd614PUexAiEAkOXEJP3bYCHsFwn02nVx8ZvgCkN+xyd3wFWylDJtEcw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":185820},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":"./dist/index.js"},"gitHead":"448e23ac3c67ddff0cfa187ef20aef9886797954","scripts":{"test":"vitest run","build":"tsc --project tsconfig.build.json","test:unit":"vitest run --project unit","typecheck":"tsc --project tsconfig.json --noEmit","test:watch":"vitest","prepublishOnly":"npm run build","test:acceptance":"vitest run --project acceptance"},"_npmUser":{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"},"repository":{"url":"git+https://github.com/francescovallone/magpie.git","type":"git"},"_npmVersion":"10.9.3","description":"Acceptance criteria scenarios on top of Vitest.","directories":{"test":"tests"},"_nodeVersion":"22.20.0","dependencies":{"@cucumber/gherkin":"^41.0.0","@cucumber/messages":"^33.0.4","@cucumber/cucumber-expressions":"^20.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0","typescript":"^6.0.3","@types/node":"^26.1.0"},"peerDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0"},"peerDependenciesMeta":{"vite":{"optional":true},"vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/magpie_0.1.6_1783709019026_0.963891869792266","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@avesbox/magpie","version":"0.2.0","keywords":["acceptance-testing","vitest","testing","traceability"],"author":{"name":"Francesco Vallone"},"license":"MIT","_id":"@avesbox/magpie@0.2.0","maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"homepage":"https://github.com/francescovallone/magpie#readme","bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"bin":{"magpie":"dist/bin.js"},"dist":{"shasum":"c7e1b4b24407e528fb46f8188c0f3559d6589945","tarball":"https://registry.npmjs.org/@avesbox/magpie/-/magpie-0.2.0.tgz","fileCount":37,"integrity":"sha512-TX6eUs/cWt1WJkXNDYQWcz/Ft3hZQzNka5lthVPig4mCi2n6z/C0qtbWNEcKadltWWFSEQ8LRWOg+gpwIovKww==","signatures":[{"sig":"MEYCIQDEKCLAPE3VV60T3ivUl8kf6kTpljNQOyNe456bnxHhfgIhAN43o4X8zK7V8coLRJnKB9Dr8kyU/xvca6wjEEnroMuv","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":207478},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":"./dist/index.js"},"gitHead":"e0bc10038c038416e0842378f516b1b25fc5e471","scripts":{"test":"vitest run","build":"tsc --project tsconfig.build.json","test:unit":"vitest run --project unit","typecheck":"tsc --project tsconfig.json --noEmit","test:watch":"vitest","prepublishOnly":"npm run build","test:acceptance":"vitest run --project acceptance"},"_npmUser":{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"},"repository":{"url":"git+https://github.com/francescovallone/magpie.git","type":"git"},"_npmVersion":"10.9.3","description":"Acceptance criteria scenarios on top of Vitest.","directories":{"test":"tests"},"_nodeVersion":"22.20.0","dependencies":{"@cucumber/gherkin":"^41.0.0","@cucumber/messages":"^33.0.4","@cucumber/cucumber-expressions":"^20.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0","typescript":"^6.0.3","@types/node":"^26.1.0"},"peerDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0"},"peerDependenciesMeta":{"vite":{"optional":true},"vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/magpie_0.2.0_1783963277447_0.9700995147579652","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@avesbox/magpie","version":"0.3.0","keywords":["acceptance-testing","vitest","testing","traceability"],"author":{"name":"Francesco Vallone"},"license":"MIT","_id":"@avesbox/magpie@0.3.0","maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"homepage":"https://github.com/francescovallone/magpie#readme","bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"bin":{"magpie":"dist/bin.js"},"dist":{"shasum":"1a4ffaa22c7114c83a7b0d9e55ba206b82b38a61","tarball":"https://registry.npmjs.org/@avesbox/magpie/-/magpie-0.3.0.tgz","fileCount":41,"integrity":"sha512-EZHz6/Vga8k/Q81nph1hciZMUB8MwsIaCIgclVKpQ3bwB+FIKw2uhDCTJxxa26AJT9lxFlL/35CwrkwqvUn/Iw==","signatures":[{"sig":"MEUCIAWOXKcI4cEPLLb39/2MlFwoI2a/P0IRvqSqxO8yxNyQAiEAzFxwQUHWkzBT5V33zydBUCSfLYLy5vS7oK0bPC5YaNk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":215548},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.19.0"},"exports":{".":"./dist/index.js"},"gitHead":"36ac8436e44c5b59f658e24242a55dbbe7d77678","scripts":{"lint":"eslint .","test":"vitest run","build":"tsc --project tsconfig.build.json","format":"prettier --write .","test:unit":"vitest run --project unit","typecheck":"tsc --project tsconfig.json --noEmit","test:watch":"vitest","format:check":"prettier --check .","prepublishOnly":"npm run build","test:acceptance":"vitest run --project acceptance"},"_npmUser":{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"},"repository":{"url":"git+https://github.com/francescovallone/magpie.git","type":"git"},"_npmVersion":"10.9.3","description":"Acceptance criteria scenarios on top of Vitest.","directories":{"test":"tests"},"_nodeVersion":"22.20.0","dependencies":{"@cucumber/gherkin":"^41.0.0","@cucumber/messages":"^33.0.4","@cucumber/cucumber-expressions":"^20.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","eslint":"^10.7.0","vitest":">=4.0.0 < 5.0.0","prettier":"^3.9.5","typescript":"^6.0.3","@types/node":"^26.1.0","typescript-eslint":"^8.64.0"},"peerDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0"},"peerDependenciesMeta":{"vite":{"optional":true},"vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/magpie_0.3.0_1783977112615_0.35854040695447753","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@avesbox/magpie","version":"0.3.1","keywords":["acceptance-testing","vitest","testing","traceability"],"author":{"name":"Francesco Vallone"},"license":"MIT","_id":"@avesbox/magpie@0.3.1","maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"homepage":"https://github.com/francescovallone/magpie#readme","bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"bin":{"magpie":"dist/bin.js"},"dist":{"shasum":"f3e76b28077ab6d1c98b07fc6aad471e83698e4d","tarball":"https://registry.npmjs.org/@avesbox/magpie/-/magpie-0.3.1.tgz","fileCount":41,"integrity":"sha512-INj9oLaNyO2PIYL3WvvrGBQLEol1yeF4ATfKQ+sSUpqU3IeaqiUFl8N0a8fKNDAFpyLmTwZCLlQWvmQzJRwNYQ==","signatures":[{"sig":"MEYCIQC1oXgCUq7bwmDsV05LPYigUGu3CWnOjgYlkyWiMcrLZQIhAIKSaUblJ4q4Nz69t3i9K3/1z/l2D0zJVAc9xkTRPiVM","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":215541},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.19.0"},"exports":{".":"./dist/index.js"},"gitHead":"d4f809544a3b42917d4593b4fb0b83f7dde2b572","scripts":{"lint":"eslint .","test":"vitest run","build":"tsc --project tsconfig.build.json","format":"prettier --write .","prepack":"npm run build","test:unit":"vitest run --project unit","typecheck":"tsc --project tsconfig.json --noEmit","test:watch":"vitest","format:check":"prettier --check .","test:acceptance":"vitest run --project acceptance"},"_npmUser":{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"},"repository":{"url":"git+https://github.com/francescovallone/magpie.git","type":"git"},"_npmVersion":"10.9.3","description":"Acceptance criteria scenarios on top of Vitest.","directories":{"test":"tests"},"_nodeVersion":"22.20.0","dependencies":{"@cucumber/gherkin":"^41.0.0","@cucumber/messages":"^33.0.4","@cucumber/cucumber-expressions":"^20.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","eslint":"^10.7.0","vitest":">=4.0.0 < 5.0.0","prettier":"^3.9.5","typescript":"^6.0.3","@types/node":"^26.1.0","typescript-eslint":"^8.64.0"},"peerDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0"},"peerDependenciesMeta":{"vite":{"optional":true},"vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/magpie_0.3.1_1783977385175_0.07650810800759067","host":"s3://npm-registry-packages-npm-production"}},"0.3.2":{"name":"@avesbox/magpie","version":"0.3.2","keywords":["acceptance-testing","vitest","testing","traceability"],"author":{"name":"Francesco Vallone"},"license":"MIT","_id":"@avesbox/magpie@0.3.2","maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"homepage":"https://github.com/francescovallone/magpie#readme","bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"bin":{"magpie":"dist/bin.js"},"dist":{"shasum":"cbc3924136ac30c45199eb72cf211d5d9b5ccfe7","tarball":"https://registry.npmjs.org/@avesbox/magpie/-/magpie-0.3.2.tgz","fileCount":41,"integrity":"sha512-E9QgGSCx/j9r/S7DWoiCvuQa4v4Fz5l79NAc0WmMGxWHuevv8/am3zOcUXJlQ086cuBdNCWbbFjT3PoTyCnpYQ==","signatures":[{"sig":"MEYCIQDnSVAONxshmhhz9zSjY/MD/fvdBWt8KA5ekVedVcUFCAIhAOcmSDYTijhELHW1wC7TieY/t/C9ujDi5cjIDWi/J2r4","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":218025},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.19.0"},"exports":{".":"./dist/index.js"},"gitHead":"54ea346d896ce2b08f368c2c00d071476bfe3e0d","scripts":{"lint":"eslint .","test":"vitest run","build":"tsc --project tsconfig.build.json","format":"prettier --write .","prepack":"npm run build","test:unit":"vitest run --project unit","typecheck":"tsc --project tsconfig.json --noEmit","test:watch":"vitest","format:check":"prettier --check .","test:acceptance":"vitest run --project acceptance"},"_npmUser":{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"},"repository":{"url":"git+https://github.com/francescovallone/magpie.git","type":"git"},"_npmVersion":"10.9.3","description":"Acceptance criteria scenarios on top of Vitest.","directories":{"test":"tests"},"_nodeVersion":"22.20.0","dependencies":{"@cucumber/gherkin":"^41.0.0","@cucumber/messages":"^33.0.4","@cucumber/cucumber-expressions":"^20.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","eslint":"^10.7.0","vitest":">=4.0.0 < 5.0.0","prettier":"^3.9.5","typescript":"^6.0.3","@types/node":"^26.1.0","typescript-eslint":"^8.64.0"},"peerDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0"},"peerDependenciesMeta":{"vite":{"optional":true},"vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/magpie_0.3.2_1784106862114_0.17227198682156097","host":"s3://npm-registry-packages-npm-production"}},"0.3.3":{"name":"@avesbox/magpie","version":"0.3.3","keywords":["acceptance-testing","vitest","testing","traceability"],"author":{"name":"Francesco Vallone"},"license":"MIT","_id":"@avesbox/magpie@0.3.3","maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"homepage":"https://github.com/francescovallone/magpie#readme","bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"bin":{"magpie":"dist/bin.js"},"dist":{"shasum":"b90a9883012699b3c6d2edc35ff8d9ca8be79245","tarball":"https://registry.npmjs.org/@avesbox/magpie/-/magpie-0.3.3.tgz","fileCount":41,"integrity":"sha512-iQ0e2Q0sVxi89vAX/lPKU2I4s19V2bcP+gyuHBnZM2cBA7zvemjljlCfD5V5sKelG+QU7n4/Yx98eja8cU3srw==","signatures":[{"sig":"MEYCIQCj2cQ4pSin1K51BvZ/0ZtCIngARAKcwXFiFhBWtgzrVAIhAIu4A5gLM12BA+QeLQAZKvKyoManFzJGIWMt0ga7DHcd","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":219060},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.19.0"},"exports":{".":"./dist/index.js"},"gitHead":"d77a259bf19ead17d183c1f4ba756695b4cf8911","scripts":{"lint":"eslint .","test":"vitest run","build":"tsc --project tsconfig.build.json","format":"prettier --write .","prepack":"npm run build","test:unit":"vitest run --project unit","typecheck":"tsc --project tsconfig.json --noEmit","test:watch":"vitest","format:check":"prettier --check .","test:acceptance":"vitest run --project acceptance"},"_npmUser":{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"},"repository":{"url":"git+https://github.com/francescovallone/magpie.git","type":"git"},"_npmVersion":"10.9.3","description":"Acceptance criteria scenarios on top of Vitest.","directories":{"test":"tests"},"_nodeVersion":"22.20.0","dependencies":{"@cucumber/gherkin":"^41.0.0","@cucumber/messages":"^33.0.4","@cucumber/cucumber-expressions":"^20.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","eslint":"^10.7.0","vitest":">=4.0.0 < 5.0.0","prettier":"^3.9.5","typescript":"^6.0.3","@types/node":"^26.1.0","typescript-eslint":"^8.64.0"},"peerDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0"},"peerDependenciesMeta":{"vite":{"optional":true},"vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/magpie_0.3.3_1784110811890_0.6264157217602571","host":"s3://npm-registry-packages-npm-production"}},"0.3.4":{"name":"@avesbox/magpie","version":"0.3.4","keywords":["acceptance-testing","vitest","testing","traceability"],"author":{"name":"Francesco Vallone"},"license":"MIT","_id":"@avesbox/magpie@0.3.4","maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"homepage":"https://github.com/francescovallone/magpie#readme","bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"bin":{"magpie":"dist/bin.js"},"dist":{"shasum":"b94bdfebd3b58a82788e03668d3fccf541afbeed","tarball":"https://registry.npmjs.org/@avesbox/magpie/-/magpie-0.3.4.tgz","fileCount":41,"integrity":"sha512-3mKPmg3g6Q2cO9ADRHXOOwFQ0CocbQyYNdDe7EN92Z070PM44a2SXr+Bbh6SRlp+CoUA6O6GyB4lR3i78kObpw==","signatures":[{"sig":"MEQCIGt4xci+LX2cGOu85n/3cFC+TYbrIY7dR8mT17Wj6e2fAiAbeS/b6KjD8nNQ6z93BXBOoX+6QBeBmmCq7lhSUeBQ4g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":220047},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.19.0"},"exports":{".":"./dist/index.js"},"gitHead":"84a00f6bbc7720b9c4a2e861901e57e7e6cf3761","scripts":{"lint":"eslint .","test":"vitest run","build":"tsc --project tsconfig.build.json","format":"prettier --write .","prepack":"npm run build","test:unit":"vitest run --project unit","typecheck":"tsc --project tsconfig.json --noEmit","test:watch":"vitest","format:check":"prettier --check .","test:acceptance":"vitest run --project acceptance"},"_npmUser":{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"},"repository":{"url":"git+https://github.com/francescovallone/magpie.git","type":"git"},"_npmVersion":"10.9.3","description":"Acceptance criteria scenarios on top of Vitest.","directories":{"test":"tests"},"_nodeVersion":"22.20.0","dependencies":{"@cucumber/gherkin":"^41.0.0","@cucumber/messages":"^33.0.4","@cucumber/cucumber-expressions":"^20.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","eslint":"^10.7.0","vitest":">=4.0.0 < 5.0.0","prettier":"^3.9.5","typescript":"^6.0.3","@types/node":"^26.1.0","typescript-eslint":"^8.64.0"},"peerDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0"},"peerDependenciesMeta":{"vite":{"optional":true},"vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/magpie_0.3.4_1784111836717_0.07299215139512616","host":"s3://npm-registry-packages-npm-production"}},"0.3.5":{"name":"@avesbox/magpie","version":"0.3.5","keywords":["acceptance-testing","vitest","testing","traceability"],"author":{"name":"Francesco Vallone"},"license":"MIT","_id":"@avesbox/magpie@0.3.5","maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"homepage":"https://github.com/francescovallone/magpie#readme","bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"bin":{"magpie":"dist/bin.js"},"dist":{"shasum":"507175878abae1060d5a9d6d8438107b65e5b1d8","tarball":"https://registry.npmjs.org/@avesbox/magpie/-/magpie-0.3.5.tgz","fileCount":41,"integrity":"sha512-5EMVuZ5c3iI02jlorGnOxkJolcUlWDOUl/IOWjEsGelWkQQf8np8jVO/mcMEV186kcegFS0rk/VYghWD2gTVgA==","signatures":[{"sig":"MEUCIQDloIGKA+AEtPtt82wuc8k7mI30OWF+8NS6pEUM/dlDMgIgZcLWmb01hX3lG0VJGid8xqKYl73xiIiIGEuUEYJCU+o=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":220667},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20.19.0"},"exports":{".":"./dist/index.js"},"gitHead":"23b0a7669d4fef7c39c12935ad7fcd863b3fbc09","scripts":{"lint":"eslint .","test":"vitest run","build":"tsc --project tsconfig.build.json","format":"prettier --write .","prepack":"npm run build","test:unit":"vitest run --project unit","typecheck":"tsc --project tsconfig.json --noEmit","test:watch":"vitest","format:check":"prettier --check .","test:acceptance":"vitest run --project acceptance"},"_npmUser":{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"},"repository":{"url":"git+https://github.com/francescovallone/magpie.git","type":"git"},"_npmVersion":"10.9.3","description":"Acceptance criteria scenarios on top of Vitest.","directories":{"test":"tests"},"_nodeVersion":"22.20.0","dependencies":{"@cucumber/gherkin":"^41.0.0","@cucumber/messages":"^33.0.4","@cucumber/cucumber-expressions":"^20.0.0"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","eslint":"^10.7.0","vitest":">=4.0.0 < 5.0.0","prettier":"^3.9.5","typescript":"^6.0.3","@types/node":"^26.1.0","typescript-eslint":"^8.64.0"},"peerDependencies":{"vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0"},"peerDependenciesMeta":{"vite":{"optional":true},"vitest":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/magpie_0.3.5_1784112652356_0.8105762311211564","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@avesbox/magpie","version":"0.4.0","description":"Acceptance criteria scenarios on top of Vitest.","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","bin":{"magpie":"dist/bin.js"},"scripts":{"build":"tsc --project tsconfig.build.json","prepack":"npm run build","test":"vitest run","test:acceptance":"vitest run --project acceptance","test:unit":"vitest run --project unit","test:jest-e2e":"npm run build && node --experimental-vm-modules node_modules/jest/bin/jest.js --rootDir fixtures/jest-e2e","test:watch":"vitest","typecheck":"tsc --project tsconfig.json --noEmit","lint":"eslint .","format":"prettier --write .","format:check":"prettier --check ."},"engines":{"node":">=20.19.0"},"keywords":["acceptance-testing","vitest","jest","testing","traceability"],"author":{"name":"Francesco Vallone"},"license":"MIT","devDependencies":{"@types/node":"^26.1.0","eslint":"^10.7.0","jest":"^30.4.2","prettier":"^3.9.5","typescript":"^6.0.3","typescript-eslint":"^8.64.0","vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0"},"peerDependencies":{"jest":">=29.0.0","vite":"^6.0.0 || ^7.0.0 || ^8.0.0","vitest":">=4.0.0 < 5.0.0"},"peerDependenciesMeta":{"vite":{"optional":true},"vitest":{"optional":true},"jest":{"optional":true}},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./vitest":{"types":"./dist/adapters/vitest.d.ts","default":"./dist/adapters/vitest.js"},"./vitest-reporter":{"types":"./dist/vitest-reporter.d.ts","default":"./dist/vitest-reporter.js"},"./plugin":{"types":"./dist/plugin.d.ts","default":"./dist/plugin.js"},"./jest":{"types":"./dist/adapters/jest.d.ts","default":"./dist/adapters/jest.js"},"./jest-reporter":{"types":"./dist/jest-reporter.d.ts","default":"./dist/jest-reporter.js"},"./package.json":"./package.json"},"dependencies":{"@cucumber/cucumber-expressions":"^20.0.0","@cucumber/gherkin":"^41.0.0","@cucumber/messages":"^33.0.4"},"directories":{"test":"tests"},"repository":{"type":"git","url":"git+https://github.com/francescovallone/magpie.git"},"bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"homepage":"https://github.com/francescovallone/magpie#readme","_id":"@avesbox/magpie@0.4.0","gitHead":"64a004db30a37dd436a46dadcd284c1742e1423f","_nodeVersion":"22.20.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-KPFn1zsdMJFve+xgj1wC102CMdwgc7PNDaD2zUxoHjomS5uo/SopeednV0QO0PZRPOcvAagVxMxW9CDKS72eFw==","shasum":"7e1a6bc89a17e66617e1c968b366a8e50029785f","tarball":"https://registry.npmjs.org/@avesbox/magpie/-/magpie-0.4.0.tgz","fileCount":63,"unpackedSize":307868,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQC+nbGv0HDzBpwYc4CdmLgD/WQQ0k2DBlMQPQkmyD7dCAIhAMkI++XJQOLDoqSUw1Wt1Azj+q3zxNVVogh2cKMpENHI"}]},"_npmUser":{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"},"maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/magpie_0.4.0_1785689998205_0.47310766406686455"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-06T16:12:44.094Z","modified":"2026-08-02T16:59:58.522Z","0.1.0":"2026-07-06T16:12:44.405Z","0.1.1":"2026-07-07T20:52:20.453Z","0.1.2":"2026-07-08T08:01:24.822Z","0.1.3":"2026-07-08T08:53:21.098Z","0.1.4":"2026-07-08T15:03:10.404Z","0.1.5":"2026-07-09T21:29:07.635Z","0.1.6":"2026-07-10T18:43:39.165Z","0.2.0":"2026-07-13T17:21:17.603Z","0.3.0":"2026-07-13T21:11:52.781Z","0.3.1":"2026-07-13T21:16:25.309Z","0.3.2":"2026-07-15T09:14:22.274Z","0.3.3":"2026-07-15T10:20:12.025Z","0.3.4":"2026-07-15T10:37:16.849Z","0.3.5":"2026-07-15T10:50:52.479Z","0.4.0":"2026-08-02T16:59:58.342Z"},"bugs":{"url":"https://github.com/francescovallone/magpie/issues"},"author":{"name":"Francesco Vallone"},"license":"MIT","homepage":"https://github.com/francescovallone/magpie#readme","keywords":["acceptance-testing","vitest","jest","testing","traceability"],"repository":{"type":"git","url":"git+https://github.com/francescovallone/magpie.git"},"description":"Acceptance criteria scenarios on top of Vitest.","maintainers":[{"name":"francescovallone","email":"vallonefrancesco587@gmail.com"}],"readme":"# Magpie\r\n\r\nMagpie is an acceptance-scenario framework built on top of [Vitest](https://vitest.dev).\r\n\r\nIt is not an assertion library and it does not replace Vitest. Its job is to model acceptance criteria as immutable scenario data, execute them through a runner-agnostic engine, and report the results with traceability back to requirements — so \"which requirements are actually covered, and did they pass?\" has an answer your CI can print.\r\n\r\n```text\r\nScenario data ──▶ execution engine ──▶ Vitest adapter ──▶ reporters (console / JSON / HTML)\r\n      ▲                                                        │\r\n      └── Gherkin .feature files (optional) ───────────────────┘ traceability back to acceptance ids\r\n```\r\n\r\n## Table of contents\r\n\r\n- [Installation](#installation)\r\n- [Quick start](#quick-start)\r\n- [Core concepts](#core-concepts)\r\n- [Defining scenarios](#defining-scenarios)\r\n  - [Typed context](#typed-context)\r\n  - [Step types and lifecycle](#step-types-and-lifecycle)\r\n  - [The fluent builder](#the-fluent-builder)\r\n  - [Sub-scenarios](#sub-scenarios)\r\n- [Running scenarios](#running-scenarios)\r\n  - [Through Vitest](#through-vitest)\r\n  - [Through Jest](#through-jest)\r\n  - [Choosing a runner from the CLI](#choosing-a-runner-from-the-cli)\r\n  - [Filtering from the CLI](#filtering-from-the-cli)\r\n  - [Directly through the engine](#directly-through-the-engine)\r\n  - [Batch execution and dependencies](#batch-execution-and-dependencies)\r\n- [Gherkin and Cucumber](#gherkin-and-cucumber)\r\n  - [Scenario Outlines and stable ids](#scenario-outlines-and-stable-ids)\r\n- [Importing acceptance criteria from DevOps](#importing-acceptance-criteria-from-devops)\r\n  - [Customizing the parsing process](#customizing-the-parsing-process)\r\n- [Reporting](#reporting)\r\n  - [The Vitest reporter](#the-vitest-reporter)\r\n  - [Standalone reporters](#standalone-reporters)\r\n  - [Debugging a failed scenario](#debugging-a-failed-scenario)\r\n  - [Error verbosity](#error-verbosity)\r\n  - [Execution logs in reports](#execution-logs-in-reports)\r\n  - [Attachments in reports](#attachments-in-reports)\r\n- [Retries and quarantine](#retries-and-quarantine)\r\n- [Scenario lifecycle](#scenario-lifecycle)\r\n  - [Pending: the feature is not built yet](#pending-the-feature-is-not-built-yet)\r\n  - [Deprecated: superseded, scheduled for removal](#deprecated-superseded-scheduled-for-removal)\r\n  - [Retired: the behaviour is gone](#retired-the-behaviour-is-gone)\r\n  - [The baseline](#the-baseline)\r\n  - [magpie audit](#magpie-audit)\r\n  - [Recommended workflow](#recommended-workflow)\r\n- [Hooks](#hooks)\r\n- [Playwright](#playwright)\r\n- [Acceptance traceability](#acceptance-traceability)\r\n- [Recipes](#recipes)\r\n- [Contributing](#contributing)\r\n\r\n## Installation\r\n\r\n```bash\r\nnpm install --save-dev @avesbox/magpie vitest\r\n```\r\n\r\nVitest (`>=4 <5`) and Jest (`>=29`) are both optional peer dependencies. Install whichever runner you use — the scenario model, execution engine, Gherkin importer, lifecycle, baseline and standalone reporters work without either.\r\n\r\nMagpie's entry points are split so a project only ever loads its own runner:\r\n\r\n| Import                            | Contains                                                                                |\r\n| --------------------------------- | --------------------------------------------------------------------------------------- |\r\n| `@avesbox/magpie`                 | scenario model, engine, DSL, Gherkin, lifecycle, baseline, audit, reporters — no runner |\r\n| `@avesbox/magpie/vitest`          | `registerScenario`, `registerStory`, `registerFilteredStory` for Vitest                 |\r\n| `@avesbox/magpie/vitest-reporter` | `MagpieVitestReporter`                                                                  |\r\n| `@avesbox/magpie/plugin`          | `magpiePlugin()` for `vitest.config.ts`                                                 |\r\n| `@avesbox/magpie/jest`            | the same register functions for Jest                                                    |\r\n| `@avesbox/magpie/jest-reporter`   | the Jest reporter, referenced by path in `reporters`                                    |\r\n\r\n## Quick start\r\n\r\nThree files get you from zero to a passing acceptance report.\r\n\r\n**1. Wire the reporter** — `vitest.config.ts`:\r\n\r\n```ts\r\nimport { defineConfig } from \"vitest/config\";\r\nimport { magpiePlugin } from \"@avesbox/magpie\";\r\n\r\nexport default defineConfig({\r\n  plugins: [\r\n    magpiePlugin({\r\n      jsonOutputFile: \".magpie/reports/latest.json\",\r\n    }),\r\n  ],\r\n});\r\n```\r\n\r\n**2. Write a scenario** — `login.acceptance.test.ts`:\r\n\r\n```ts\r\nimport { defineAcceptanceScenario, registerScenario } from \"@avesbox/magpie\";\r\n\r\ninterface LoginContext {\r\n  user?: { username: string };\r\n  response?: { status: number; token?: string };\r\n}\r\n\r\nconst login = defineAcceptanceScenario<LoginContext>({\r\n  id: \"auth-login\",\r\n  title: \"Registered user logs in\",\r\n  acceptance: [\"AUTH-001\"],\r\n  tags: [\"auth\", \"critical\"],\r\n  story: { title: \"Authentication\" },\r\n  steps: [\r\n    {\r\n      name: \"registered user exists\",\r\n      type: \"given\",\r\n      execute: (context) => {\r\n        context.user = { username: \"alice\" };\r\n      },\r\n    },\r\n    {\r\n      name: \"credentials are submitted\",\r\n      type: \"when\",\r\n      execute: async (context) => {\r\n        context.response = { status: 200, token: \"token-123\" };\r\n      },\r\n    },\r\n    {\r\n      name: \"token is returned\",\r\n      type: \"then\",\r\n      execute: (context) => {\r\n        if (!context.response?.token) {\r\n          throw new Error(\"Expected a token\");\r\n        }\r\n      },\r\n    },\r\n  ],\r\n});\r\n\r\nregisterScenario(login, { reportToVitest: true });\r\n```\r\n\r\n**3. Run it:**\r\n\r\n```bash\r\nnpx vitest run\r\n```\r\n\r\nVitest runs the scenario as a regular `describe`/`it` block, and the Magpie reporter prints an acceptance summary at the end of the run and writes `.magpie/reports/latest.json`:\r\n\r\n```text\r\nExecution Report\r\n  Scenarios: 1/1 passed\r\n  Steps: 3/3 passed\r\n  Duration: 2ms\r\n\r\nStory\r\n  Authentication\r\n\r\n  Scenario\r\n    Registered user logs in\r\n      ✓ given registered user exists\r\n      ✓ when credentials are submitted\r\n      ✓ then token is returned\r\n\r\nAcceptance\r\n  Implemented: AUTH-001\r\n  Missing: none\r\n```\r\n\r\nFrom here: [filter scenarios from the CLI](#filtering-from-the-cli), [import Gherkin `.feature` files](#gherkin-and-cucumber), [add an HTML report](#the-vitest-reporter), or [track which acceptance ids are still missing](#acceptance-traceability).\r\n\r\n## Core concepts\r\n\r\n| Concept           | What it is                                                                                                                     | Where it appears                                   |\r\n| ----------------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------- |\r\n| **Scenario**      | An immutable, executable acceptance criterion: id, title, tags, acceptance ids, and ordered steps.                             | `defineAcceptanceScenario()`, `scenario()` builder |\r\n| **Step**          | One unit of work inside a scenario (`given`/`when`/`then`/`setup`/`cleanup`). Failing = throwing.                              | `steps: [...]`, `defineStep()`                     |\r\n| **Context**       | A plain object threaded through every step of one scenario execution. You type it.                                             | `execute: (context, api) => ...`                   |\r\n| **Story**         | A named group of scenarios (maps to a Gherkin `Feature`).                                                                      | `defineStory()`                                    |\r\n| **Acceptance id** | A requirement reference (e.g. `AUTH-001`) attached to scenarios; reports show which ids are implemented and which are missing. | `acceptance: [...]`, traceability report           |\r\n| **Reporter**      | Collects scenario results and emits console text, JSON, or HTML.                                                               | `magpiePlugin()`, `createConsoleReporter()`, ...   |\r\n\r\nDescription, execution, and reporting are deliberately separate: scenarios are pure data, so the same scenario can run through Vitest today and through another runner tomorrow, and reports are built from results rather than from runner internals.\r\n\r\n## Defining scenarios\r\n\r\n### Typed context\r\n\r\nEvery scenario execution starts from a context object shared by its steps. Type it via the generic parameter — steps get full inference:\r\n\r\n```ts\r\nimport { defineAcceptanceScenario } from \"@avesbox/magpie\";\r\n\r\ninterface CheckoutContext {\r\n  cart?: { items: number };\r\n  receipt?: { total: number };\r\n}\r\n\r\nconst checkout = defineAcceptanceScenario<CheckoutContext>({\r\n  id: \"checkout-happy-path\",\r\n  title: \"Customer checks out\",\r\n  acceptance: [\"SHOP-042\"],\r\n  steps: [\r\n    {\r\n      id: \"given-cart\",\r\n      name: \"a cart with two items\",\r\n      type: \"given\",\r\n      execute: (context) => {\r\n        context.cart = { items: 2 };\r\n      },\r\n    },\r\n    {\r\n      id: \"then-receipt\",\r\n      name: \"a receipt is produced\",\r\n      type: \"then\",\r\n      execute: (context, api) => {\r\n        api.log(\"cart at checkout\", context.cart); // shows up in results and (optionally) reports\r\n        if (!context.cart) throw new Error(\"no cart\");\r\n        context.receipt = { total: 42 };\r\n      },\r\n    },\r\n  ],\r\n});\r\n```\r\n\r\nSteps receive `(context, api)` where `api.log(message, data?)` records structured diagnostics onto the execution result (see [Execution logs in reports](#execution-logs-in-reports)).\r\n\r\nIds are optional everywhere they can be derived: a step without an `id` gets one slugified from its `name` (`\"a cart with two items\"` → `a-cart-with-two-items`), and a scenario without an `id` gets one slugified from its `title`. Steps in the same scenario that end up with the same id are disambiguated with their 1-based occurrence (`pay-1`, `pay-2`). Provide explicit ids when a name is expected to change but the id must stay stable (e.g. for `dependsOn` or report-history comparisons).\r\n\r\n### Step types and lifecycle\r\n\r\nThe standard step types are `setup`, `given`, `when`, `then`, and `cleanup`. All are ordinary steps — the type is metadata used for reporting and sub-scenario splitting — except `cleanup`, which has a distinct lifecycle: cleanup steps **always run**, even when a main step failed, and are appended to the result after the failure. Use them for teardown that must not be skipped:\r\n\r\n```ts\r\nsteps: [\r\n  { id: \"given-db\", name: \"database is seeded\", type: \"given\", execute: seed },\r\n  { id: \"then-query\", name: \"query returns rows\", type: \"then\", execute: assertRows },\r\n  {\r\n    id: \"cleanup-db\",\r\n    name: \"database is wiped\",\r\n    type: \"cleanup\",\r\n    lifecycle: \"cleanup\",\r\n    execute: wipe,\r\n  },\r\n];\r\n```\r\n\r\nCustom step types can be registered with `createStepTypeRegistry()` / `standardStepTypes.extend()` if your domain needs more than the Gherkin five.\r\n\r\n### The fluent builder\r\n\r\n`scenario()` is a thin wrapper producing the same immutable model, if you prefer chaining over one literal. Every step method accepts a `(name, execute)` shorthand, and the scenario id can be omitted (it is derived from the title):\r\n\r\n```ts\r\nimport { scenario } from \"@avesbox/magpie\";\r\n\r\nconst login = scenario<{ response?: { status: number; token?: string } }>(\"Registered user logs in\")\r\n  .acceptance(\"AUTH-001\")\r\n  .tag(\"auth\", \"critical\")\r\n  .given(\"registered user exists\", () => undefined)\r\n  .when(\"credentials are submitted\", (context) => {\r\n    context.response = { status: 200, token: \"token-123\" };\r\n  })\r\n  .then(\"token is returned\", (context) => {\r\n    if (!context.response?.token) throw new Error(\"Expected a token\");\r\n  })\r\n  .build();\r\n```\r\n\r\nThe object form is still available when a step needs an explicit `id`, `metadata`, or a custom `lifecycle` — `.given({ id: \"given-user\", name: \"registered user exists\", execute: ... })`. The builder also exposes `.setup()`, `.cleanup()`, `.step()` (raw step input), `.description()`, `.dependsOn()`, and `.metadata()`.\r\n\r\n### Sub-scenarios\r\n\r\nA scenario with more than one `given` step is automatically split into independent sub-scenarios: each `given` starts a new sub-scenario made up of that `given` and every step up to (but excluding) the next `given`, plus any steps before the first `given` (e.g. `setup`). Sub-scenarios run independently — each gets a fresh context and its own result — but if any sub-scenario fails, the parent scenario is reported as failed.\r\n\r\nThe split can be disabled per scenario with `splitOnGiven: false` (or `.splitOnGiven(false)` on the builder, or the `splitOnGiven` option on the Gherkin importer): all steps then run as one linear scenario sharing a single context, and no sub-scenario ids are generated. In Gherkin, only explicit repeated `Given` keywords split — `And`/`But` continuation steps never do.\r\n\r\nEach sub-scenario gets an acceptance id derived from the parent's by appending a two-digit index (`AC-001` → `AC-001-01`, `AC-001-02`, ...). Override per `given` with the second argument:\r\n\r\n```ts\r\nconst checkout = scenario<{ status?: number }>(\"checkout\", \"Checkout flows\")\r\n  .acceptance(\"AC-001\")\r\n  .given({ id: \"given-valid-card\", name: \"customer has a valid card\", execute: () => undefined }) // -> AC-001-01\r\n  .when({ id: \"when-pay\", name: \"customer pays\", execute: () => undefined })\r\n  .then({ id: \"then-success\", name: \"payment succeeds\", execute: () => undefined })\r\n  .given(\r\n    { id: \"given-expired-card\", name: \"customer has an expired card\", execute: () => undefined },\r\n    { acceptance: \"AC-001-EXPIRED\" }, // custom id instead of AC-001-02\r\n  )\r\n  .when({ id: \"when-pay-2\", name: \"customer pays\", execute: () => undefined })\r\n  .then({ id: \"then-decline\", name: \"payment is declined\", execute: () => undefined })\r\n  .build();\r\n```\r\n\r\n`ScenarioExecutionResult.subScenarios` and `ScenarioReport.subScenarios` expose per-sub-scenario results, and traceability reports use the granular sub-scenario ids instead of the parent's when present.\r\n\r\n## Running scenarios\r\n\r\n### Through Vitest\r\n\r\nThe adapter maps scenario data onto `describe()` and `it()`:\r\n\r\n```ts\r\nimport { defineStory } from \"@avesbox/magpie\";\r\nimport { registerScenario, registerStory } from \"@avesbox/magpie/vitest\";\r\n\r\nregisterScenario(login, { reportToVitest: true });\r\n\r\n// or group scenarios into a story:\r\nconst story = defineStory({ title: \"Authentication\", scenarios: [login] });\r\nregisterStory(story, { reportToVitest: true });\r\n```\r\n\r\n`reportToVitest: true` records each result for the [Magpie Vitest reporter](#the-vitest-reporter); a failing scenario throws the original error inside its `it()` block, so Vitest's own failure output (stack trace, diff) still appears alongside the Magpie report.\r\n\r\nThe adapter options also accept `hooks`, `context`/`createContext`, `retries`, `quarantineTags`, an `executor` override, and a `filter`:\r\n\r\n### Through Jest\r\n\r\nMagpie supports Jest as an alternative runner. The scenario model, engine, lifecycle, baseline and reports are identical — only the adapter and the reporter differ.\r\n\r\n|                                                 | Vitest                                         | Jest                                                       |\r\n| ----------------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------- |\r\n| Adapter                                         | `@avesbox/magpie/vitest`                       | `@avesbox/magpie/jest`                                     |\r\n| Reporter                                        | `@avesbox/magpie/vitest-reporter`              | `@avesbox/magpie/jest-reporter`                            |\r\n| Config helper                                   | `magpiePlugin()` from `@avesbox/magpie/plugin` | none — Jest has no plugin hook; list the reporter directly |\r\n| Scenario model, engine, sub-scenarios, retries  | ✅                                             | ✅                                                         |\r\n| Lifecycle, baseline, `magpie audit`             | ✅                                             | ✅                                                         |\r\n| Console / JSON / HTML / JUnit reports           | ✅                                             | ✅                                                         |\r\n| Gherkin import, DevOps import, Playwright hooks | ✅                                             | ✅                                                         |\r\n| Module format                                   | ESM                                            | ESM (`--experimental-vm-modules`); CJS best-effort         |\r\n\r\n```ts\r\n// login.acceptance.test.ts\r\nimport { registerScenario } from \"@avesbox/magpie/jest\";\r\n\r\nregisterScenario(login, { reportToVitest: true });\r\n```\r\n\r\n```js\r\n// jest.config.js\r\nexport default {\r\n  reporters: [\r\n    \"default\",\r\n    [\"@avesbox/magpie/jest-reporter\", { jsonOutputFile: \".magpie/reports/latest.json\" }],\r\n  ],\r\n};\r\n```\r\n\r\nJest has no plugin hook, so there is no counterpart to `magpiePlugin()` — the reporter goes in the `reporters` array directly. Jest resolves reporters by module path, which is why `@avesbox/magpie/jest-reporter` is a published subpath rather than an instance you construct.\r\n\r\nEverything else works the same: `reportToVitest` (the option keeps its name for now) writes one record per scenario, Jest's worker processes each write their own file, and the reporter aggregates them in the main process at run end.\r\n\r\n**ESM.** Magpie is ESM-only, and Jest's ESM support is still experimental, so Jest needs to be started with:\r\n\r\n```bash\r\nNODE_OPTIONS=--experimental-vm-modules npx jest\r\n```\r\n\r\nCJS Jest projects (ts-jest or babel-jest transpiling to CommonJS) are best-effort: they can reach Magpie through dynamic `import()`, but there is no CJS build. If that matters for your project, open an issue — a dual build is deliberately deferred until there is demand.\r\n\r\n**`injectGlobals: false`.** By default Magpie picks up Jest's `describe`/`it` globals. If you disable them, build the adapter explicitly:\r\n\r\n```ts\r\nimport { describe, it } from \"@jest/globals\";\r\nimport { createJestAdapter } from \"@avesbox/magpie/jest\";\r\n\r\nconst { registerScenario } = createJestAdapter({ describe, it });\r\n```\r\n\r\n### Choosing a runner from the CLI\r\n\r\nThe `magpie` wrapper drives whichever runner your project uses:\r\n\r\n```bash\r\nmagpie run                     # auto-detected\r\nmagpie run --runner jest       # explicit\r\nMAGPIE_RUNNER=jest magpie run  # explicit, via the environment\r\n```\r\n\r\nDetection order: the `--runner` flag, then `MAGPIE_RUNNER`, then whichever runner is installed. When both are installed, the tie is broken by which config file the project has (`vitest.config.*`, or `jest.config.*` / a `jest` key in `package.json`); a genuine tie resolves to Vitest.\r\n\r\nWhen it starts Jest, the wrapper adds `--experimental-vm-modules` to `NODE_OPTIONS` for you, preserving anything already there. Running `jest` directly still needs you to set it yourself.\r\n\r\n`magpie --help-runner` forwards to the underlying runner's own help.\r\n\r\n### Filtering from the CLI\r\n\r\n`registerFilteredStory()` plus `resolveScenarioFilter()` turn CLI flags and environment variables into a scenario filter:\r\n\r\n```ts\r\nimport { defineStory, registerFilteredStory, resolveScenarioFilter } from \"@avesbox/magpie\";\r\n\r\nconst story = defineStory({ title: \"Authentication\", scenarios: [login] });\r\n\r\nregisterFilteredStory(story, {\r\n  filter: resolveScenarioFilter({ argv: process.argv.slice(2), env: process.env }),\r\n  reportToVitest: true,\r\n});\r\n```\r\n\r\n| CLI flag                               | Environment variable          | Matches                                         |\r\n| -------------------------------------- | ----------------------------- | ----------------------------------------------- |\r\n| `--tag auth`                           | `MAGPIE_TAGS=auth,critical`   | scenario tags                                   |\r\n| `--acceptance AUTH-*`                  | `MAGPIE_ACCEPTANCE=AUTH-*`    | acceptance ids (glob `*` supported)             |\r\n| `--story Authentication`               | `MAGPIE_STORY=Authentication` | story title                                     |\r\n| `--scenario \"Registered user logs in\"` | `MAGPIE_SCENARIO=...`         | scenario title                                  |\r\n| `--regex critical` / `--grep critical` | `MAGPIE_REGEX=critical`       | id, title, description, tags, acceptance, story |\r\n\r\n**The easiest way to pass these flags is the `magpie` CLI**, a thin wrapper installed with the package. It extracts the Magpie flags, converts them to `MAGPIE_*` environment variables (the transport that reliably reaches Vitest's worker processes), and forwards everything else to Vitest unchanged — no `--` passthrough needed:\r\n\r\n```bash\r\nnpx magpie run --tag auth\r\nnpx magpie run --coverage --acceptance \"AUTH-*\"\r\nnpx magpie watch --story Authentication\r\n```\r\n\r\nEnvironment variables work everywhere too, including through npm scripts: `MAGPIE_TAGS=auth npm test`.\r\n\r\nIf you call Vitest directly instead, its CLI rejects flags it does not know, so Magpie flags must come after a `--` that reaches Vitest — `vitest run -- --tag auth`, or through npm: `npm test -- -- --tag auth`.\r\n\r\nFor programmatic filtering, build a filter object directly:\r\n\r\n```ts\r\nimport { filterScenarios } from \"@avesbox/magpie\";\r\n\r\nconst critical = filterScenarios(story.scenarios, { tags: [\"critical\"] });\r\n```\r\n\r\n### Directly through the engine\r\n\r\nNo Vitest required — `executeScenario()` returns a rich result:\r\n\r\n```ts\r\nimport { executeScenario } from \"@avesbox/magpie\";\r\n\r\nconst result = await executeScenario(login, {\r\n  createContext: () => ({}),\r\n});\r\n\r\nresult.success; // boolean\r\nresult.steps; // per-step status, duration, logs, error\r\nresult.failure; // which step threw, serialized error, original cause\r\nresult.logs; // everything emitted via api.log\r\n```\r\n\r\n### Batch execution and dependencies\r\n\r\n`executeScenarios()` runs a set with dependency-aware scheduling:\r\n\r\n```ts\r\nimport { executeScenarios } from \"@avesbox/magpie\";\r\n\r\nconst batch = await executeScenarios([seedInventory, loadPricing, openCheckout], {\r\n  maxConcurrency: 2,\r\n  createContext: () => ({}),\r\n});\r\n\r\nbatch.results; // finished scenarios, in input order\r\nbatch.skipped; // scenarios skipped because a dependency failed\r\n```\r\n\r\nDeclare dependencies with `dependsOn: [\"other-scenario-id\"]`. Independent scenarios run in parallel up to `maxConcurrency`, dependents wait for all prerequisites, and downstream scenarios are skipped (with `reason: \"dependency_failed\"`) when an upstream fails. Cycles and references to missing ids throw upfront. For parallel runs, prefer `createContext()` so each scenario gets its own context object — a shared `context` with `maxConcurrency > 1` is rejected.\r\n\r\n## Gherkin and Cucumber\r\n\r\nGenerate Magpie scenarios from Gherkin feature text and resolve steps with [Cucumber expressions](https://github.com/cucumber/cucumber-expressions):\r\n\r\n```ts\r\nimport { createGherkinStory, defineGherkinStep, registerFilteredStory } from \"@avesbox/magpie\";\r\n\r\nconst story = createGherkinStory(\r\n  `\r\nFeature: Authentication\r\n\r\n  @auth @AUTH-001\r\n  Scenario: Registered user logs in\r\n    Given a registered user \"alice\"\r\n    When the user logs in with password secret\r\n    Then the response status is 200\r\n`,\r\n  {\r\n    uri: \"authentication.feature\",\r\n    stepDefinitions: [\r\n      defineGherkinStep({\r\n        expression: \"a registered user {string}\",\r\n        execute: ({ arguments: [username], context }) => {\r\n          context.user = username;\r\n        },\r\n      }),\r\n      defineGherkinStep({\r\n        expression: \"the user logs in with password {word}\",\r\n        execute: ({ arguments: [password], context }) => {\r\n          context.response = { status: password === \"secret\" ? 200 : 401 };\r\n        },\r\n      }),\r\n      defineGherkinStep({\r\n        expression: \"the response status is {int}\",\r\n        execute: ({ arguments: [status], context }) => {\r\n          if (context.response?.status !== status) {\r\n            throw new Error(\"Unexpected response status\");\r\n          }\r\n        },\r\n      }),\r\n    ],\r\n  },\r\n);\r\n\r\nregisterFilteredStory(story, { reportToVitest: true });\r\n```\r\n\r\nOr load `.feature` files from disk with `createGherkinStoryFromFile(filePath, options)` / `createGherkinScenariosFromFile(filePath, options)`.\r\n\r\n### Step registries and feature discovery\r\n\r\nFor anything beyond a single feature, define steps once in a shared registry and load a whole directory of `.feature` files:\r\n\r\n```ts\r\n// steps/registry.ts — modules add their steps to one shared registry\r\nimport { createGherkinStepRegistry } from \"@avesbox/magpie\";\r\n\r\nexport const steps = createGherkinStepRegistry<{ user?: string }>();\r\n\r\nsteps\r\n  .define({\r\n    expression: \"a registered user {string}\",\r\n    execute: ({ arguments: [username], context }) => {\r\n      context.user = String(username);\r\n    },\r\n  })\r\n  .define({\r\n    expression: \"the login succeeds\",\r\n    execute: () => undefined,\r\n  });\r\n```\r\n\r\n```ts\r\n// features.acceptance.test.ts — one story per .feature file, recursively\r\nimport {\r\n  createGherkinStoriesFromDirectory,\r\n  registerFilteredStory,\r\n  resolveScenarioFilter,\r\n} from \"@avesbox/magpie\";\r\nimport { steps } from \"./steps/registry.js\";\r\n\r\nconst stories = await createGherkinStoriesFromDirectory(\"./features\", { stepDefinitions: steps });\r\nconst filter = resolveScenarioFilter({ argv: process.argv.slice(2), env: process.env });\r\n\r\nfor (const story of stories) {\r\n  registerFilteredStory(story, { filter, reportToVitest: true });\r\n}\r\n```\r\n\r\nRegistries are mergeable (`registry.merge(other)`, `registry.add(...defs)`) and accepted anywhere `stepDefinitions` takes an array. `findFeatureFiles(directory)` is exported separately if you need the file list; discovery throws when a directory contains no feature files, so an empty suite never passes silently.\r\n\r\n### Undefined step snippets\r\n\r\nWhen feature text references steps that have no matching definition, the importer fails upfront with **every** undefined step of the feature and a ready-to-paste snippet for each:\r\n\r\n```text\r\n2 Gherkin step(s) in auth.feature have no matching step definition:\r\n\r\n  - a registered user \"alice\"\r\n  - the login succeeds\r\n\r\nImplement them with:\r\n\r\ndefineGherkinStep({\r\n  expression: \"a registered user {string}\",\r\n  execute: ({ arguments: [string1], context }) => {\r\n    throw new Error(\"Step not implemented yet\");\r\n  },\r\n});\r\n...\r\n```\r\n\r\nQuoted values are suggested as `{string}`, whole numbers as `{int}`, decimals as `{float}`. `generateGherkinStepSnippet(text)` is exported for tooling.\r\n\r\nThe importer:\r\n\r\n- includes `Background` steps (feature- and rule-level) in every scenario\r\n- preserves doc strings and data tables — available to step definitions as `argumentData.docString` / `argumentData.dataTable` and in step metadata\r\n- maps `Rule:` blocks to stories\r\n- extracts acceptance ids from tags by prefix (default `AUTH-`, override with `acceptanceTagPrefix`), by tag pattern, or from description text:\r\n\r\n```ts\r\nconst story = await createGherkinStoryFromFile(\"./features/payments.feature\", {\r\n  acceptanceTagPattern: /acceptance\\(([^)]+)\\)/, // @acceptance(PAY-123)\r\n  acceptanceMetadataPattern: /PAY-\\d+/g, // \"Acceptance: PAY-123\" in descriptions\r\n  stepDefinitions,\r\n});\r\n```\r\n\r\n### Scenario Outlines and stable ids\r\n\r\n`Scenario Outline` / `Examples` tables are fully expanded: each example row becomes an independent Magpie scenario with `<placeholders>` substituted in the title and step texts, and tags on the `Examples:` block (including acceptance tags) are inherited by the generated scenarios.\r\n\r\nGenerated scenario ids are **stable and deterministic** — derived from the feature and scenario names rather than from the parser's random per-run ids:\r\n\r\n```text\r\nFeature: Withdrawals                        id\r\n  Scenario: Balance is shown            →   withdrawals:balance-is-shown\r\n  Scenario Outline: Withdraw <amount>   →   withdrawals:withdraw-20\r\n    Examples: | amount | → 20, 50       →   withdrawals:withdraw-50\r\n```\r\n\r\nWhen several generated scenarios share a name (an outline whose title has no placeholder, or two scenarios named identically), every occurrence is disambiguated with its 1-based position — ids become `withdrawals:withdraw-from-account:1`, `:2`, ... and titles become `Withdraw from account #1`, `#2`, ... Step ids are positional (`<scenario-id>:step-1`).\r\n\r\nBecause ids survive re-parsing, they are safe to use in `dependsOn`, in id-based filtering, and for comparing archived report runs. Note that ids are derived from names: renaming a feature or scenario changes its id, which is the intended trade-off (the id follows the requirement, not the file).\r\n\r\n## Importing acceptance criteria from DevOps\r\n\r\nWork items in tools like Azure DevOps carry their Acceptance Criteria as **HTML** (the rich-text editor's storage format) or, if a team pastes it directly, plain **Markdown**. `createScenariosFromAcceptanceCriteria` reads either — both are normalized to the same plain text before parsing, so a bullet list renders identically whether it arrived as `<ul><li>` or `-`:\r\n\r\n```ts\r\nimport { createScenariosFromAcceptanceCriteria } from \"@avesbox/magpie\";\r\n\r\nconst acceptanceCriteria = /* the work item's Acceptance Criteria field, HTML or Markdown */ `\r\n  <p><strong>Scenario: Successful login</strong></p>\r\n  <ul>\r\n    <li>Given a registered user exists</li>\r\n    <li>When they submit valid credentials</li>\r\n    <li>Then a token is returned</li>\r\n  </ul>\r\n`;\r\n\r\nconst scenarios = createScenariosFromAcceptanceCriteria(acceptanceCriteria, {\r\n  title: \"User login\",\r\n  workItemId: \"AUTH-1234\",\r\n  stepDefinitions,\r\n});\r\n```\r\n\r\nThe default parser reads `Given`/`When`/`Then`/`And`/`But` bullet lines (optionally grouped under `Scenario: <title>` headings) and — reusing the Gherkin importer under the hood — resolves each step against `stepDefinitions` exactly like a `.feature` file would, so the same step registry works for both. `workItemId` tags every generated scenario with `@<workItemId>`, which flows through the existing Gherkin acceptance-tag extraction (`acceptanceTagPrefix` / `acceptanceTagPattern`, both accepted here too) for free.\r\n\r\nContent with more than one `Scenario:` heading — or more than one `Given` when headings are absent — returns **a list of scenarios**, one per block; content with a single block still returns a one-element list, so callers never need to branch on shape.\r\n\r\n### Customizing the parsing process\r\n\r\nNot every team writes acceptance criteria as Given/When/Then. Pass `parser` to fully replace the default: it receives the normalized (HTML/Markdown-agnostic) text plus the same import options, and may return a single `Scenario` or a list — both are accepted:\r\n\r\n```ts\r\nconst scenarios = createScenariosFromAcceptanceCriteria(acceptanceCriteria, {\r\n  stepDefinitions,\r\n  parser: (normalizedText, options) => {\r\n    // e.g. a team that writes plain checklists instead of Given/When/Then\r\n    return myOwnParser(normalizedText).map((block) => defineScenario({ ...block, ... }));\r\n  },\r\n});\r\n```\r\n\r\n`normalizeAcceptanceCriteriaContent(content, contentType?)` is exported separately if you only need the HTML/Markdown normalization step (content type is auto-detected from the presence of HTML tags when omitted).\r\n\r\n## Reporting\r\n\r\n### The Vitest reporter\r\n\r\nVitest has no auto-discovery for reporters — `test.reporters` is a plain array read from your config — so Magpie ships both the reporter and a Vite plugin that wires it for you.\r\n\r\n**Option 1: `magpiePlugin()` (recommended).** Its `config()` hook merges a `MagpieVitestReporter` into `test.reporters`, composing with reporters you already list:\r\n\r\n```ts\r\nimport { defineConfig } from \"vitest/config\";\r\nimport { magpiePlugin } from \"@avesbox/magpie\";\r\n\r\nexport default defineConfig({\r\n  plugins: [\r\n    magpiePlugin({\r\n      jsonOutputFile: \".magpie/reports/latest.json\",\r\n      jsonArchiveDirectory: \".magpie/reports/history\",\r\n    }),\r\n  ],\r\n});\r\n```\r\n\r\n**Option 2: `createMagpieVitestReporter()` directly**, if you control the `reporters` array yourself:\r\n\r\n```ts\r\nimport { defineConfig } from \"vitest/config\";\r\nimport { createMagpieVitestReporter } from \"@avesbox/magpie\";\r\n\r\nexport default defineConfig({\r\n  test: {\r\n    reporters: [\r\n      \"default\",\r\n      createMagpieVitestReporter({ jsonOutputFile: \".magpie/reports/latest.json\" }),\r\n    ],\r\n  },\r\n});\r\n```\r\n\r\nEither way, once configured:\r\n\r\n- the acceptance report is printed at the end of every run\r\n- a JSON artifact is written to `jsonOutputFile`, and every run is archived under `jsonArchiveDirectory` (default: `history/` next to the output file), keeping the 3 most recent by default (`jsonHistoryLimit`)\r\n- pass `htmlOutputFile` (and optionally `htmlArchiveDirectory`, `htmlHistoryLimit`) to also write a self-contained HTML report, archived the same way\r\n- pass `junitOutputFile` (and optionally `junitSuiteName`) to also write a JUnit XML report for the test-result panes of Jenkins, GitLab, Azure DevOps, and similar CI systems\r\n- suites participate by passing `reportToVitest: true` (or an options object) to `registerScenario` / `registerStory` / `registerFilteredStory`\r\n\r\nTo make the HTML report opt-in from the command line, gate it on `isOutputEnabled()` in your config:\r\n\r\n```ts\r\nimport { isOutputEnabled } from \"@avesbox/magpie\";\r\n\r\nconst htmlEnabled = isOutputEnabled(\"html\", { argv: process.argv, env: process.env });\r\n// vitest run -- --output html    or    MAGPIE_OUTPUT=html npm test\r\n```\r\n\r\n(As with filter flags, `--output` must come after the `--` that reaches Vitest, so through npm it is `npm test -- -- --output html`.)\r\n\r\n### Standalone reporters\r\n\r\nOutside Vitest, reporters collect results incrementally and emit a final run report:\r\n\r\n```ts\r\nimport {\r\n  createConsoleReporter,\r\n  createJsonReporter,\r\n  createHtmlReporter,\r\n  createReportingHooks,\r\n  defineStory,\r\n  executeScenario,\r\n} from \"@avesbox/magpie\";\r\n\r\nconst story = defineStory({ title: \"Authentication\", scenarios: [login] });\r\n\r\nconst reporter = createConsoleReporter({\r\n  stories: [story],\r\n  expectedAcceptanceIds: [\"AUTH-001\", \"AUTH-007\"],\r\n});\r\n\r\nfor (const scenario of story.scenarios) {\r\n  await executeScenario(scenario, { hooks: createReportingHooks(reporter) });\r\n}\r\n\r\nconst report = await reporter.flush(); // prints the text report, returns the ExecutionRunReport\r\n```\r\n\r\n`createJsonReporter({ outputPath })`, `createHtmlReporter({ outputPath })`, and `createJUnitReporter({ outputPath })` are drop-in equivalents that write a JSON artifact, a self-contained HTML page (inline CSS, no external dependencies), or a JUnit XML file on `flush()`. In the JUnit output each story becomes a `<testsuite>` and each scenario a `<testcase>`; failed quarantined scenarios are reported as skipped so they do not fail the CI stage. They share the recorded entries API, so one execution pass can feed several reporters:\r\n\r\n```ts\r\nfor (const entry of reporter.entries) {\r\n  jsonReporter.recordScenario(entry.scenario, entry.result);\r\n}\r\nawait jsonReporter.flush();\r\n```\r\n\r\nLower-level building blocks are exported too: `buildExecutionRunReport()`, `createStoryReport()`, `formatExecutionRunReport()`, `formatExecutionRunReportAsHtml()`, `formatExecutionRunReportAsJUnitXml()`, `writeHtmlReport()`, `writeJUnitReport()`, and `writeJsonReport()`.\r\n\r\n### Debugging a failed scenario\r\n\r\nWhen a step throws, execution stops after that step (cleanup steps still run) and the failure is captured on the result:\r\n\r\n```ts\r\nconst result = await executeScenario(login);\r\n\r\n// result.success === false\r\n// result.failure === {\r\n//   step: { id: \"then-token\", name: \"token is returned\", ... },\r\n//   error: { name: \"Error\", message: \"Expected a token\", stack: \"...\" },\r\n//   cause: Error: Expected a token       // the original thrown value\r\n// }\r\n```\r\n\r\nOnly steps that actually ran appear in `result.steps` — steps after the failure are not executed; cleanup steps are appended after the failure. **Reports** still show the scenario's full declared shape: steps that never ran are rendered as skipped (`○` in the text and HTML output, status `\"skipped\"` in JSON, counted in `skippedStepCount`), so a failing scenario never looks like it \"lost\" steps:\r\n\r\n```text\r\n  Scenario\r\n    Registered user logs in\r\n      ✓ given registered user exists\r\n      ✗ when credentials are submitted\r\n        ↳ Login service unavailable\r\n      ○ then token is returned\r\n```\r\n\r\nand the JSON artifact carries the same information structurally, with `error` on both the failing step and the scenario.\r\n\r\n### Error verbosity\r\n\r\nBy default reports contain only the **first line** of an error message. Enable `errors: { verbose: true }` to include the full error (stack trace when available):\r\n\r\n```ts\r\nconst reporter = createConsoleReporter({\r\n  errors: { verbose: true },\r\n});\r\n```\r\n\r\nThe option is part of `ReportBuildOptions`, so it works identically with `createJsonReporter`, `createHtmlReporter`, `buildExecutionRunReport`, and — for reports assembled by the Magpie Vitest reporter — via the adapter's bridge options: `reportToVitest: { errors: { verbose: true } }`.\r\n\r\nThe HTML report always keeps the full error too, independent of this option: every `StepReport`/`ScenarioReport` carries an `errorDetail` field with the full stack, and the HTML renderer shows it in a collapsible `<details>` under the one-line summary. `error` (used by the text/JSON/JUnit output) still respects `errors.verbose` as above.\r\n\r\n### Execution logs in reports\r\n\r\nSteps can emit diagnostics through the execution API (`api.log(message, data?)`). Logs are always captured on the execution result; to also include them in reports, enable `logs: { enabled: true }`:\r\n\r\n```ts\r\nconst reporter = createConsoleReporter({\r\n  logs: { enabled: true },\r\n});\r\n```\r\n\r\nEach step report then carries its own `logs` array (message, timestamp, optional structured `data`), and the scenario report carries scenario-level entries (such as the engine's `scenario.started` / `scenario.finished` markers). The console and HTML reporters render step logs beneath each step:\r\n\r\n```text\r\n✓ when credentials are submitted\r\n  · fetching token {\"url\":\"https://api.example.test/login\"}\r\n  · token received\r\n```\r\n\r\nLike `errors`, this works with every reporter and with the bridge: `reportToVitest: { logs: { enabled: true } }`.\r\n\r\n### Attachments in reports\r\n\r\nSteps can attach a file through the execution API — a screenshot, a trace, a log dump:\r\n\r\n```ts\r\nexecute: (context, api) => {\r\n  api.attach(\"screenshot.png\", screenshotBuffer);\r\n  api.attach(\"trace.zip\", { path: \"/tmp/trace.zip\" });\r\n  api.attach(\"notes.txt\", \"some diagnostic text\", \"text/plain\");\r\n},\r\n```\r\n\r\n`attach(name, body, contentType?)` takes inline content (`string` or `Uint8Array`) or a reference to a file already on disk (`{ path }`). `contentType` is inferred from `name`'s extension (`.png`, `.jpg`/`.jpeg`, `.webm`, `.zip`, `.json`, `.txt`) when omitted, falling back to `application/octet-stream`.\r\n\r\nAttachments are always captured on the execution result; to include them in reports, enable `attachments: { enabled: true }`:\r\n\r\n```ts\r\nconst reporter = createHtmlReporter({\r\n  outputPath: \"report.html\",\r\n  attachments: { enabled: true, directory: \"report-attachments\" },\r\n});\r\n```\r\n\r\nInline bodies are written as files under `directory` (defaults to `\"attachments\"`, relative to the process cwd); `{ path }` attachments are referenced as-is. Each step report then carries an `attachments` array of `{ name, contentType, path }`. The HTML reporter renders images inline and other attachments as a download link; the console reporter prints one `📎 name (path)` line per attachment; the JUnit reporter emits `[[ATTACHMENT|path]]` in `<system-out>`, the convention Jenkins/GitLab already parse.\r\n\r\nLike `errors` and `logs`, this works with every reporter and with the bridge: `reportToVitest: { attachments: { enabled: true } }`.\r\n\r\n## Retries and quarantine\r\n\r\n### Retries\r\n\r\nA scenario can declare how many times a failing execution is retried before being reported as failed:\r\n\r\n```ts\r\nconst checkout = defineAcceptanceScenario({\r\n  id: \"checkout\",\r\n  title: \"Checkout completes\",\r\n  retries: 2, // up to 3 attempts in total\r\n  steps: [/* ... */],\r\n});\r\n```\r\n\r\nA default for scenarios without their own `retries` can be set at execution time — `executeScenario(scenario, { retries: 1 })` or via the adapter options — and a scenario-level `retries` always wins. When a scenario has sub-scenarios, each sub-scenario is retried independently, so a stable sub-scenario is not re-run because a sibling flaked.\r\n\r\nThe result reflects the last attempt and carries `attempts` when more than one ran; reports show `[attempts: N]` next to the scenario title. `afterScenario` hooks (including reporting hooks) fire once per scenario with the final result, not once per attempt. When a shared `context` object is passed explicitly, retried attempts reuse it as-is; use `createContext` for a fresh context per attempt.\r\n\r\n### Quarantine\r\n\r\nTag a scenario `quarantine` to keep it running and reported without letting its failure break the build:\r\n\r\n```ts\r\nconst flaky = defineAcceptanceScenario({\r\n  id: \"flaky-search\",\r\n  title: \"Search returns suggestions\",\r\n  tags: [\"quarantine\"],\r\n  steps: [/* ... */],\r\n});\r\n```\r\n\r\nIn the Vitest adapter, a failing quarantined scenario no longer throws inside its `it()` block, so the run stays green. Reports mark it with `quarantined: true` and `[quarantined]` in the text output, and totals count it separately: quarantined scenarios are excluded from both `passedScenarioCount` and `failedScenarioCount` and appear in `quarantinedScenarioCount` (`Quarantined: N` in the summary), so `passed + failed + quarantined = total`.\r\n\r\nThe tag set is configurable everywhere the feature applies — `quarantineTags: [\"known-flaky\"]` on the adapter options, on `ReportBuildOptions`, or in the bridge options — and defaults to `DEFAULT_QUARANTINE_TAGS` (`[\"quarantine\"]`).\r\n\r\n## Scenario lifecycle\r\n\r\nQuarantine answers \"this scenario is flaky\". The lifecycle answers a different and harder question: **what do you do with a scenario when the feature moves under it?**\r\n\r\nA scenario is a durable claim about behaviour, tied to an acceptance id. When the feature changes, a scenario can end up in a state where it cannot pass yet, has been superseded, or no longer describes anything real. The two things people reach for — `.skip` and deleting the file — both lose information. `.skip` rots into a graveyard of permanently-disabled tests; deleting the file silently drops an acceptance id off the traceability report, and nobody sees it in review.\r\n\r\nEvery scenario therefore carries a `lifecycle`, defaulting to `{ status: \"active\" }`. Every non-active state requires a **reason** and a **date**, because an undated, unowned marker is exactly the failure this feature exists to prevent.\r\n\r\n| Status       | Executes? | Failure fails the run? | Counts as coverage? | Requires                |\r\n| ------------ | --------- | ---------------------- | ------------------- | ----------------------- |\r\n| `active`     | yes       | yes                    | yes                 | —                       |\r\n| `pending`    | yes       | **no**                 | no                  | `reason`, `until`       |\r\n| `deprecated` | yes       | yes                    | yes                 | `reason`, `removeAfter` |\r\n| `retired`    | **no**    | —                      | no                  | `reason`, `retiredAt`   |\r\n\r\n### Pending: the feature is not built yet\r\n\r\nWrite the scenario first, mark it pending, and keep CI green while you build:\r\n\r\n```ts\r\nconst sso = defineAcceptanceScenario({\r\n  id: \"auth-sso\",\r\n  title: \"User signs in through the identity provider\",\r\n  acceptance: [\"AUTH-010\"],\r\n  lifecycle: {\r\n    status: \"pending\",\r\n    reason: \"identity provider integration lands in sprint 24\",\r\n    until: \"2026-09-01\",\r\n    issue: \"AUTH-1042\",\r\n  },\r\n  steps: [/* ... */],\r\n});\r\n```\r\n\r\nA pending scenario runs, and its failure does not fail the run. **A pending scenario that _passes_ does fail the run** — the behaviour landed and the marker is stale, so the build tells you to promote it to `active`. Set `strict: false` to opt out while a feature is genuinely in flux.\r\n\r\nIn reports, pending scenarios are excluded from both `passedScenarioCount` and `failedScenarioCount` and appear in `pendingScenarioCount`; in JUnit they emit `<skipped>` rather than `<failure>`, so CI panes stay green.\r\n\r\n### Deprecated: superseded, scheduled for removal\r\n\r\nThe expand half of an expand–contract change: the new scenario lands, the old one keeps running and keeps its acceptance ids until you are ready to drop it.\r\n\r\n```ts\r\nlifecycle: {\r\n  status: \"deprecated\",\r\n  reason: \"replaced by the SSO flow\",\r\n  supersededBy: \"auth-sso\",\r\n  removeAfter: \"2026-10-01\",\r\n}\r\n```\r\n\r\nDeprecated scenarios still run and still have to pass. `magpie audit` fails once `removeAfter` passes, or if `supersededBy` is missing or names a scenario that does not exist — that is the contract half, enforced rather than remembered.\r\n\r\n### Retired: the behaviour is gone\r\n\r\n```ts\r\nlifecycle: {\r\n  status: \"retired\",\r\n  reason: \"password login removed in favour of SSO\",\r\n  retiredAt: \"2026-07-01\",\r\n  supersededBy: \"auth-sso\",\r\n}\r\n```\r\n\r\nA retired scenario does not execute. Mark it retired, run `magpie baseline --update` once to record the tombstone, then **delete the code**. The baseline keeps the id, its acceptance ids, the date and the reason, so traceability reports say \"retired\" instead of quietly losing the requirement. `magpie audit` reminds you to delete a retired scenario that is still registered.\r\n\r\n### The baseline\r\n\r\n`magpie.baseline.json` is a committed inventory of every scenario, its acceptance ids and its lifecycle state. It lives at the project root, not under `.magpie/` — reports are generated and gitignored, the baseline is source and must be reviewed.\r\n\r\nAfter each unfiltered run, Magpie reconciles what actually ran against the baseline and reports five kinds of drift: `added`, `removed`, `lifecycle-changed`, `acceptance-changed`, and `acceptance-orphaned` (an acceptance id no live scenario covers any more).\r\n\r\nThis is the piece that does the real work, and the payoff is not the check — it is that **retiring a scenario becomes a diff line a reviewer can see**:\r\n\r\n```diff\r\n-  { \"id\": \"auth-login\", \"acceptance\": [\"AUTH-001\"], \"lifecycle\": { \"status\": \"active\" } }\r\n+  { \"id\": \"auth-login\", \"acceptance\": [\"AUTH-001\"],\r\n+    \"lifecycle\": { \"status\": \"retired\", \"retiredAt\": \"2026-07-28\",\r\n+                   \"reason\": \"password login removed in favour of SSO\",\r\n+                   \"supersededBy\": \"auth-sso\" } }\r\n```\r\n\r\nEnable drift reporting on the reporter, and accept changes explicitly:\r\n\r\n```ts\r\nmagpiePlugin({\r\n  jsonOutputFile: \".magpie/reports/latest.json\",\r\n  baseline: { enabled: true },\r\n});\r\n```\r\n\r\n```bash\r\nmagpie baseline            # show drift\r\nmagpie baseline --update   # accept it, then commit magpie.baseline.json\r\n```\r\n\r\nReconciliation is skipped automatically when the run is filtered (`--tag`, `--story`, ...), because a filtered run legitimately reports a subset and every excluded scenario would look deleted. For the same reason `magpie baseline --update` refuses to run under a filter.\r\n\r\n### magpie audit\r\n\r\nThe job that stops non-running scenarios from accumulating. Run it in CI after your tests:\r\n\r\n```bash\r\nmagpie run\r\nmagpie audit               # exits non-zero on any finding\r\n```\r\n\r\nIt reads the JSON report (`.magpie/reports/latest.json` by default, `--report` to change it) and fails on:\r\n\r\n| Finding                        | Meaning                                                              |\r\n| ------------------------------ | -------------------------------------------------------------------- |\r\n| `pending-expired`              | past `until` — promote it, retire it, or move the date with a reason |\r\n| `pending-passed`               | marked pending but passing — promote it to active                    |\r\n| `deprecated-expired`           | past `removeAfter` — retire and delete it                            |\r\n| `deprecated-without-successor` | nothing named to take over its acceptance ids                        |\r\n| `successor-missing`            | `supersededBy` names a scenario that does not exist                  |\r\n| `retired-not-deleted`          | tombstone recorded, code still registered                            |\r\n| `vacuous-scenario`             | the scenario declares no steps, so it asserts nothing                |\r\n| `baseline-drift`               | the run does not match the committed baseline                        |\r\n\r\n`--today YYYY-MM-DD` overrides the date used for expiry checks, which is what makes the expiry rules testable.\r\n\r\n```yaml\r\n# .github/workflows/acceptance.yml\r\n- run: npx magpie run\r\n- run: npx magpie audit\r\n```\r\n\r\nBecause expiry is time-based, a repo that goes quiet still accumulates debt — run `magpie audit` on a schedule as well as per pull request.\r\n\r\n### Recommended workflow\r\n\r\nThe lifecycle gives you four states, but knowing _which_ one to reach for is the part that actually decides whether your suite stays honest. This section is the decision procedure.\r\n\r\n#### Which state do I need?\r\n\r\nStart from what is true about the behaviour, not from what is true about the test. A failing test is a symptom; the question is what the feature is doing.\r\n\r\n```mermaid\r\nflowchart TD\r\n    START[\"A scenario no longer fits the feature\"] --> Q1{\"Does the behaviour<br/>it describes still exist?\"}\r\n\r\n    Q1 -->|\"No — it was removed\"| RETIRED[\"retired<br/>+ delete the code\"]\r\n    Q1 -->|\"Yes, unchanged —<br/>the test is just broken\"| FIX[\"Fix the scenario.<br/>No lifecycle state needed.\"]\r\n    Q1 -->|\"Yes, but it works<br/>differently now\"| Q2\r\n\r\n    Q2{\"Can you write the<br/>new scenario today?\"} -->|\"Yes\"| Q3\r\n    Q2 -->|\"No — the new behaviour<br/>is not built yet\"| Q4\r\n\r\n    Q3{\"Must the old behaviour<br/>keep working for now?\"} -->|\"Yes — clients still<br/>depend on it\"| EXPAND[\"Add the new scenario.<br/>Mark the old one deprecated.\"]\r\n    Q3 -->|\"No — it is replaced<br/>outright\"| REPLACE[\"Rewrite the scenario<br/>in the same pull request.\"]\r\n\r\n    Q4{\"Is the scenario flaky,<br/>or genuinely unbuilt?\"} -->|\"Flaky\"| QUAR[\"Use a quarantine tag.<br/>Not the lifecycle.\"]\r\n    Q4 -->|\"Genuinely unbuilt\"| PENDING[\"pending<br/>with a reason and an until date\"]\r\n\r\n    style RETIRED fill:#7c3aed,stroke:#5b21b6,color:#fff\r\n    style PENDING fill:#b45309,stroke:#92400e,color:#fff\r\n    style EXPAND fill:#0369a1,stroke:#075985,color:#fff\r\n    style QUAR fill:#4b5563,stroke:#374151,color:#fff\r\n```\r\n\r\nTwo branches on that chart are the ones people get wrong.\r\n\r\n**\"The test is just broken.\"** Most failing scenarios need no lifecycle state at all — the behaviour is fine and the scenario drifted. Fix it. Reaching for `pending` to silence a red test is how a suite starts lying to you, and strict pending exists specifically to catch that: the moment it passes again, the build fails and tells you to remove the marker.\r\n\r\n**\"Flaky, or genuinely unbuilt?\"** These feel similar (both are \"it fails and I do not want to deal with it\") and need opposite treatment. Flakiness is a property of the _test_; use a quarantine tag. Unbuilt behaviour is a property of the _product_; use `pending`. Mixing them is how quarantine turns into a graveyard, because a quarantine tag has no expiry and `pending` does.\r\n\r\n#### The state machine\r\n\r\nTransitions are deliberately narrow. Every arrow that leaves a non-active state is enforced by tooling, not by memory:\r\n\r\n```mermaid\r\nstateDiagram-v2\r\n    direction TB\r\n\r\n    [*] --> active: scenario written\r\n\r\n    active --> pending: behaviour not built yet\r\n    pending --> active: it passed — audit forces this\r\n    pending --> retired: the plan changed\r\n\r\n    active --> deprecated: superseded by a new scenario\r\n    deprecated --> retired: removeAfter reached\r\n\r\n    retired --> [*]: tombstone in baseline, code deleted\r\n\r\n    note left of pending\r\n        Requires reason + until.\r\n        Failure does not fail the run.\r\n        A PASS does fail the run.\r\n    end note\r\n\r\n    note right of deprecated\r\n        Requires reason + removeAfter.\r\n        Still runs, still must pass,\r\n        still counts for coverage.\r\n    end note\r\n\r\n    note right of retired\r\n        Does not execute.\r\n        Baseline keeps id, acceptance\r\n        ids, date and reason forever.\r\n    end note\r\n```\r\n\r\nThere is no arrow into a \"disabled forever\" state, because there is no such state. Every path out of `active` has a date attached, and `magpie audit` fails once that date passes. That is the entire anti-graveyard mechanism.\r\n\r\n#### Prefer expand–contract over pending\r\n\r\nWhen you _can_ write the new scenario, do that instead of marking the old one pending. Pending means a period where nothing is asserted; expand–contract means the suite is meaningful at every commit.\r\n\r\n```mermaid\r\nflowchart LR\r\n    subgraph PR1[\"Pull request 1 — Expand\"]\r\n        direction TB\r\n        A1[\"Add auth-sso (new scenario)\"] --> A2[\"Mark auth-login deprecated<br/>supersededBy: auth-sso<br/>removeAfter: 2026-10-01\"]\r\n        A2 --> A3[\"magpie baseline --update\"]\r\n        A3 --> A4[\"✅ both scenarios pass<br/>AUTH-001 and AUTH-010 covered\"]\r\n    end\r\n\r\n    subgraph PR2[\"Meanwhile — Migrate\"]\r\n        direction TB\r\n        B1[\"Both behaviours live<br/>behind a feature flag\"] --> B2[\"Both scenario sets pass\"]\r\n        B2 --> B3[\"Clients move to SSO<br/>at their own pace\"]\r\n    end\r\n\r\n    subgraph PR3[\"Pull request 2 — Contract\"]\r\n        direction TB\r\n        C1[\"Mark auth-login retired\"] --> C2[\"magpie baseline --update<br/>writes the tombstone\"]\r\n        C2 --> C3[\"Delete the scenario code\"]\r\n        C3 --> C4[\"✅ reviewer sees the retirement<br/>as a baseline diff line\"]\r\n    end\r\n\r\n    PR1 --> PR2 --> PR3\r\n```\r\n\r\nThe critical property is that **at no point is a requirement silently uncovered**. In PR 1 both acceptance ids are covered. During migration both are covered. In PR 2, `AUTH-001` moves from \"covered by an active scenario\" to \"retired, superseded by `auth-sso`\" — a state the traceability report can show, rather than an id that simply stops appearing.\r\n\r\n#### Worked example\r\n\r\nPassword login is being replaced by SSO. Here is the whole change, end to end.\r\n\r\n**PR 1 — expand.** The new scenario lands; the old one is marked for removal.\r\n\r\n```ts\r\n// The new behaviour, asserted properly from day one.\r\nconst sso = defineAcceptanceScenario({\r\n  id: \"auth-sso\",\r\n  title: \"User signs in through the identity provider\",\r\n  acceptance: [\"AUTH-010\"],\r\n  steps: [/* ... */],\r\n});\r\n\r\n// The old behaviour still works, and still has to keep working.\r\nconst login = defineAcceptanceScenario({\r\n  id: \"auth-login\",\r\n  title: \"Registered user logs in with a password\",\r\n  acceptance: [\"AUTH-001\"],\r\n  lifecycle: {\r\n    status: \"deprecated\",\r\n    reason: \"replaced by SSO; password login is removed once all tenants migrate\",\r\n    supersededBy: \"auth-sso\",\r\n    removeAfter: \"2026-10-01\",\r\n  },\r\n  steps: [/* ... */],\r\n});\r\n```\r\n\r\nThe run reports the drift, and you accept it in the same pull request:\r\n\r\n```text\r\nBaseline drift\r\n  added auth-sso: new scenario \"User signs in through the identity provider\" covering AUTH-010\r\n  lifecycle-changed auth-login: active -> deprecated\r\n\r\n  Resolve by restoring the scenarios, or accept the change with:\r\n    magpie baseline --update\r\n```\r\n\r\n```bash\r\nmagpie baseline --update   # then commit magpie.baseline.json alongside the code\r\n```\r\n\r\n**If the new behaviour is not built yet**, PR 1 becomes a pending scenario instead — write the assertion now, mark it pending, and let CI tell you when it starts working:\r\n\r\n```ts\r\nlifecycle: {\r\n  status: \"pending\",\r\n  reason: \"identity provider integration lands in sprint 24\",\r\n  until: \"2026-09-01\",\r\n  issue: \"AUTH-1042\",\r\n}\r\n```\r\n\r\nThe day the integration lands, the build fails on its own:\r\n\r\n```text\r\nAudit — 1 problem\r\n  pending-passed auth-sso: marked pending (\"identity provider integration lands in sprint 24\") but passed — promote it to active\r\n```\r\n\r\nThat failure is the feature working. Nobody had to remember to check.\r\n\r\n**PR 2 — contract.** Once the migration is done, retire the old scenario:\r\n\r\n```ts\r\nlifecycle: {\r\n  status: \"retired\",\r\n  reason: \"password login removed in favour of SSO\",\r\n  retiredAt: \"2026-09-28\",\r\n  supersededBy: \"auth-sso\",\r\n}\r\n```\r\n\r\nRun `magpie baseline --update` once — this writes the tombstone — then **delete the scenario code**. `magpie audit` will nag you until you do:\r\n\r\n```text\r\nAudit — 1 problem\r\n  retired-not-deleted auth-login: retired on 2026-09-28 (\"password login removed in favour of SSO\") and still registered — its tombstone is in the baseline, so delete the code\r\n```\r\n\r\nWhat the reviewer sees in the diff is the whole point:\r\n\r\n```diff\r\n-  { \"id\": \"auth-login\", \"acceptance\": [\"AUTH-001\"], \"lifecycle\": { \"status\": \"active\" } }\r\n+  { \"id\": \"auth-login\", \"acceptance\": [\"AUTH-001\"],\r\n+    \"lifecycle\": { \"status\": \"retired\", \"retiredAt\": \"2026-09-28\",\r\n+                   \"reason\": \"password login removed in favour of SSO\",\r\n+                   \"supersededBy\": \"auth-sso\" } }\r\n```\r\n\r\nWithout a baseline, that same change is a deleted file and an acceptance id that quietly stops appearing in the coverage report — invisible in review, and exactly the thing this system exists to prevent.\r\n\r\n#### Where feature flags fit\r\n\r\nIf old and new behaviour must genuinely coexist, a feature flag is a better tool than any lifecycle state: both scenario sets execute and pass simultaneously, each asserting the behaviour under its own flag value. The old scenario then retires _with the flag_, in the same cleanup.\r\n\r\nThis is worth reaching for because it removes the uncertainty window entirely. `pending` says \"nothing is asserted here right now\"; a flag says \"both things are asserted, and one of them is on its way out\". Prefer the second whenever the product can support it.\r\n\r\n#### Two rules that carry most of the weight\r\n\r\n1. **Change the scenario in the same pull request as the behaviour.** An obsolete scenario is a documentation bug, not test debt. Deferring it means the next person cannot tell whether a failure means the code is wrong or the scenario is stale — and that ambiguity is what makes people start ignoring failures.\r\n2. **Never let a non-active state be undated.** Every `pending` and `deprecated` carries a date, `magpie audit` enforces it, and the audit runs on a schedule as well as per pull request so a quiet repo still surfaces its debt.\r\n\r\n#### Anti-patterns\r\n\r\n| Instead of…                                        | Do this                                                 | Because                                                                                                   |\r\n| -------------------------------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |\r\n| `it.skip` / commenting out a scenario              | `pending` with a reason and an `until`                  | A skip has no owner, no date and no exit condition; it is invisible to the reports and to `magpie audit`. |\r\n| Deleting a scenario outright                       | `retired`, `baseline --update`, then delete             | Deleting drops the acceptance id from traceability with no record. The tombstone keeps the history.       |\r\n| Marking something `pending` to silence a red build | Fix the scenario, or `retired` if the behaviour is gone | Pending means \"not built yet\". Using it as a mute button is how a suite stops meaning anything.           |\r\n| A quarantine tag for an obsolete scenario          | The lifecycle                                           | Quarantine has no expiry by design — it is for flakiness. Obsolescence needs a deadline.                  |\r\n| Running `magpie baseline --update` in CI           | Run it locally, commit the diff                         | Auto-accepting drift in CI defeats the purpose: the review is the control, not the check.                 |\r\n| Bumping `until` / `removeAfter` when audit fails   | Promote, retire, or extend _with a written reason_      | A date silently pushed out is a skip with extra steps.                                                    |\r\n\r\nQuarantine is about flakiness. The lifecycle is about truth.\r\n\r\n## Hooks\r\n\r\nThe engine supports `beforeScenario`, `afterScenario`, `beforeStep`, and `afterStep`:\r\n\r\n```ts\r\nimport { executeScenario } from \"@avesbox/magpie\";\r\n\r\nawait executeScenario(login, {\r\n  hooks: {\r\n    beforeScenario: (_scenario, context) => {\r\n      context.started = true;\r\n    },\r\n    beforeStep: (step) => console.log(`starting ${step.name}`),\r\n    afterStep: (step, _context, result) => console.log(`${step.name}: ${result.status}`),\r\n    afterScenario: (_scenario, _context, result) =>\r\n      console.log(result.success ? \"scenario passed\" : \"scenario failed\"),\r\n  },\r\n});\r\n```\r\n\r\nCombine hook sets with `mergeExecutionHooks()` — each hook runs in order:\r\n\r\n```ts\r\nimport { createReportingHooks, executeScenario, mergeExecutionHooks } from \"@avesbox/magpie\";\r\n\r\nconst hooks = mergeExecutionHooks(createReportingHooks(reporter), {\r\n  beforeScenario: () => console.log(\"scenario starting\"),\r\n});\r\n\r\nawait executeScenario(login, { hooks });\r\n```\r\n\r\nThe same `hooks` option is accepted by `executeScenarios()` and by the Vitest adapter functions.\r\n\r\n## Playwright\r\n\r\n`createPlaywrightHooks()` manages a Playwright page per scenario: a fresh browser context and page are created before each scenario (on `context.page`, `context.browserContext`, `context.browser`) and closed after it, and when a scenario fails a full-page screenshot is attached to the failing step. Magpie has no dependency on Playwright — you pass the launch function:\r\n\r\n```ts\r\nimport { chromium } from \"playwright\";\r\nimport {\r\n  createPlaywrightHooks,\r\n  defineStory,\r\n  registerStory,\r\n  scenario,\r\n  type PlaywrightScenarioContext,\r\n} from \"@avesbox/magpie\";\r\n\r\nconst playwright = createPlaywrightHooks<PlaywrightScenarioContext>({\r\n  launch: () => chromium.launch(),\r\n  contextOptions: { viewport: { width: 1280, height: 720 } },\r\n});\r\n\r\nconst login = scenario<PlaywrightScenarioContext>(\"Registered user logs in\")\r\n  .given(\"the login page is open\", async ({ page }) => {\r\n    await page!.goto(\"https://example.test/login\");\r\n  })\r\n  .then(\"the form is visible\", async ({ page }) => {\r\n    await page!.locator(\"form#login\").waitFor();\r\n  })\r\n  .build();\r\n\r\nregisterStory(defineStory({ title: \"Authentication\", scenarios: [login] }), {\r\n  hooks: playwright,\r\n  reportToVitest: { attachments: { enabled: true } }, // failure screenshots land in reports\r\n});\r\n```\r\n\r\nThe browser is launched lazily once and shared across scenarios; call `playwright.close()` when the run is over (e.g. in a Vitest `afterAll`). When combining with other hooks, put the Playwright hooks first — `mergeExecutionHooks(playwright, otherHooks)` — so the failure screenshot is captured before reporters record the result. Disable screenshots with `screenshotOnFailure: false`.\r\n\r\nExtend `PlaywrightScenarioContext` for your own context fields: `interface MyContext extends PlaywrightScenarioContext { user?: string }`.\r\n\r\n## Acceptance traceability\r\n\r\nGive the reporter the full list of acceptance ids you expect to be covered, and the report splits them into implemented and missing:\r\n\r\n```ts\r\nconst reporter = createConsoleReporter({\r\n  expectedAcceptanceIds: [\"AUTH-001\", \"AUTH-002\", \"AUTH-007\"],\r\n});\r\n\r\n// ...record scenarios...\r\n\r\nconst report = await reporter.flush();\r\nreport.traceability.implemented; // e.g. [\"AUTH-001\", \"AUTH-002\"]\r\nreport.traceability.missing; // e.g. [\"AUTH-007\"]\r\n```\r\n\r\nTo fail CI when requirements have no covering scenario:\r\n\r\n```ts\r\nif (report.traceability.missing.length > 0) {\r\n  throw new Error(`Uncovered acceptance criteria: ${report.traceability.missing.join(\", \")}`);\r\n}\r\n```\r\n\r\nWhen scenarios have sub-scenarios, traceability uses the granular sub-scenario ids (`AC-001-01`, ...) instead of the parent's, and `createAcceptanceTraceabilityReport(scenarios, expectedIds)` is available for computing the split without running anything.\r\n\r\n### Loading acceptance ids from a file\r\n\r\nHardcoding `expectedAcceptanceIds` drifts as requirements change. `loadAcceptanceIds(filePath)` reads them from a `.json` file (a bare `[\"AUTH-001\", ...]` array) or a `.csv`/text export (one id per line; a header row like \"Issue key\" is skipped, first column used if the line has commas) — the shape a Jira or Azure Boards issue-key export already has once you keep just the id column:\r\n\r\n```ts\r\nimport { createConsoleReporter, loadAcceptanceIds } from \"@avesbox/magpie\";\r\n\r\nconst reporter = createConsoleReporter({\r\n  expectedAcceptanceIds: await loadAcceptanceIds(\"./requirements/AUTH.csv\"),\r\n});\r\n```\r\n\r\n## Recipes\r\n\r\nShort answers to \"how do I ...\":\r\n\r\n- **Run only critical scenarios locally** — `npx magpie run --tag critical` (with `registerFilteredStory` + `resolveScenarioFilter` wired as shown [above](#filtering-from-the-cli)).\r\n- **Show scenario results in the CI test pane (Jenkins, Git","readmeFilename":"README.md"}