From 4b908e6bcd194d69a072372127c4e5f082dd6ab8 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 29 May 2026 11:07:07 -0400 Subject: [PATCH] =?UTF-8?q?test(tests):=20antagonistic=20tier=20=E2=80=94?= =?UTF-8?q?=20fast-check=20property=20tests=20+=20Stryker=20mutation=20tes?= =?UTF-8?q?ting=20(PR-gated)=20(#461)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(#454): add antagonistic tier — fast-check property tests + Stryker mutation config Adds property-based testing (fast-check v4) and mutation testing scaffolding (Stryker v8) as the antagonistic validation tier for lib/*.cjs pure logic. ## Files added ### Shared setup - tests/helpers/fast-check-setup.cjs — configureGlobal({ numRuns:200, seed:42 }) for deterministic CI; override locally with GSD_FC_SEED ### Property test suites (node:test + fast-check) - tests/context-utilization.property.test.cjs — boundary at 60%/70% thresholds (exact Math.ceil boundary, not Math.floor), TypeError on all invalid inputs, overflow clamping to 100%/critical, shape invariants (7 tests, all pass) - tests/prompt-budget.property.test.cjs — estimateTokens monotonicity + ceil(len/4) exactness; applyBudget shape invariant, instructions/roadmap verbatim, budget envelope, omit tracking (11 tests, all pass) - tests/frontmatter.property.test.cjs — extractFrontmatter/reconstructFrontmatter/ spliceFrontmatter never-throw + type shape + splice→extract round-trip (9 tests) - tests/adr-parser.property.test.cjs — shouldRejectAdrStatus boundary (3 statuses only), parseAdrMarkdown shape + title trim invariant (discovered: parser trims trailing whitespace) (9 tests, all pass) - tests/config-schema.property.test.cjs — isValidConfigKey never throws, returns boolean, accepts all VALID/RUNTIME_STATE_KEYS, rejects empty/null/unknown (8 tests) ### Stryker mutation config - stryker.config.mjs — testRunner:'command', mutate bin/lib/**/*.cjs minus 13 generated files, coverageAnalysis:'off', thresholds {high:80,low:60,break:50}, incremental:true, reporters html+clear-text+progress ### CI workflow - .github/workflows/mutation.yml — PR-gating job (pull_request + workflow_dispatch), runs stryker --incremental --since origin/next (changed files only), uploads HTML artifact; SINCE_REF via env not interpolation (injection-safe) ### Package config - package.json: +test:mutation, +test:mutation:since scripts - .gitignore: +.stryker-tmp/, +.stryker-incremental.json, +reports/mutation/ - package-lock.json: fast-check@4.8.0, @stryker-mutator/core@9.6.1 ## Verified node --test on all 5 property test files: 44 tests, 0 failures. Stryker NOT run (slow; reserved for CI). Co-Authored-By: Claude Opus 4.8 (1M context) * fix(#454): pin upload-artifact to v7.0.1 SHA used across repo (bad SHA ea165f8d) The SHA ea165f8d65b6e75b540449d3ec4f5dde0c5a4e1 (labeled v4.6.2) does not resolve on GitHub Actions. All other workflows in this repo pin 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a (v7.0.1) — align mutation.yml. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(#454): mutation workflow — replace invalid --since flag with changed-core-files --mutate scoping Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: CI Rebase Check Co-authored-by: Claude Opus 4.8 (1M context) --- .github/workflows/mutation.yml | 94 + .gitignore | 5 + package-lock.json | 1829 ++++++++++++++++++- package.json | 6 +- stryker.config.mjs | 92 + tests/adr-parser.property.test.cjs | 212 +++ tests/config-schema.property.test.cjs | 154 ++ tests/context-utilization.property.test.cjs | 217 +++ tests/frontmatter.property.test.cjs | 194 ++ tests/helpers/fast-check-setup.cjs | 24 + tests/prompt-budget.property.test.cjs | 242 +++ 11 files changed, 3060 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/mutation.yml create mode 100644 stryker.config.mjs create mode 100644 tests/adr-parser.property.test.cjs create mode 100644 tests/config-schema.property.test.cjs create mode 100644 tests/context-utilization.property.test.cjs create mode 100644 tests/frontmatter.property.test.cjs create mode 100644 tests/helpers/fast-check-setup.cjs create mode 100644 tests/prompt-budget.property.test.cjs diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml new file mode 100644 index 000000000..a1c76cbf5 --- /dev/null +++ b/.github/workflows/mutation.yml @@ -0,0 +1,94 @@ +name: Mutation Testing + +# PR-GATING: runs on every pull_request targeting `next` or `main`. +# Computes changed core lib files via git diff and passes them to --mutate, +# so only mutants in CHANGED files are tested — keeps the job bounded. +# If no core lib files changed, the gate passes trivially (skip+exit 0). +# Full-repo mutation runs are reserved for local exploration (npm run test:mutation). + +on: + pull_request: + branches: + - next + - main + paths: + # Only run when lib source or property tests change + - 'get-shit-done/bin/lib/**/*.cjs' + - 'tests/**/*.property.test.cjs' + - 'stryker.config.mjs' + workflow_dispatch: + +concurrency: + group: mutation-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + mutation: + name: Stryker mutation score (changed files only) + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + persist-credentials: true + + - name: Set up Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version-file: .nvmrc + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Fetch base ref for diff + # Ensure the base branch tip is available for the git diff below. + # fetch-depth: 0 above gets all history, but the remote ref name must exist. + run: git fetch origin ${{ github.base_ref }} --depth=1 + + - name: Compute changed core lib files + id: changed + run: | + BASE_REF="origin/${{ github.base_ref }}" + # Find changed non-test, non-generated .cjs files in bin/lib + CHANGED=$(git diff --name-only "${BASE_REF}...HEAD" -- 'get-shit-done/bin/lib/**/*.cjs' \ + | grep -v '\.test\.cjs$' \ + | grep -v -E '/(configuration|command-aliases|commands|core|install-profiles|installer-migrations|phase|profile-output|state|verify|init|audit|gsd2-import)\.cjs$' \ + || true) + if [ -z "$CHANGED" ]; then + echo "changed=" >> "$GITHUB_OUTPUT" + else + # Join with commas for --mutate + MUTATE_LIST=$(echo "$CHANGED" | paste -sd, -) + echo "changed=${MUTATE_LIST}" >> "$GITHUB_OUTPUT" + fi + + - name: Skip mutation gate (no core lib files changed) + if: steps.changed.outputs.changed == '' + run: echo "No core lib files changed; skipping mutation gate" + + - name: Run Stryker (incremental, changed files only) + if: steps.changed.outputs.changed != '' + # --mutate scopes mutation to only the changed production files. + # --incremental reuses cached results for unchanged mutants. + # The break threshold (50) is read from stryker.config.mjs and causes + # Stryker to exit non-zero when mutation score < 50%, failing the PR check. + env: + NODE_OPTIONS: '--max-old-space-size=4096' + run: | + MUTATE_LIST="${{ steps.changed.outputs.changed }}" + echo "Running Stryker --mutate '${MUTATE_LIST}'" + npx stryker run --incremental --mutate "${MUTATE_LIST}" + + - name: Upload mutation HTML report + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: always() # upload even on failure so the score is visible + with: + name: mutation-report-${{ github.run_number }} + path: reports/mutation/mutation.html + retention-days: 14 diff --git a/.gitignore b/.gitignore index 0749f6ccf..058828972 100644 --- a/.gitignore +++ b/.gitignore @@ -84,3 +84,8 @@ claude-test-command.md # Observability audit trail (issue #177) — append-only, local only .planning/.gsd-trace.jsonl + +# Mutation testing artifacts (issue #454) +.stryker-tmp/ +.stryker-incremental.json +reports/mutation/ diff --git a/package-lock.json b/package-lock.json index 1822f3e01..c63c7632b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,10 +18,12 @@ }, "devDependencies": { "@eslint/js": "^9.39.4", + "@stryker-mutator/core": "^8.7.1", "c8": "^11.0.0", "eslint": "^9.39.4", "eslint-plugin-n": "^17.24.0", "eslint-plugin-no-only-tests": "^3.4.0", + "fast-check": "^4.8.0", "globals": "^16.5.0", "js-yaml": "^4.1.1", "typescript": "^6.0.3", @@ -35,6 +37,20 @@ "fallow": "^2.70.0" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@anthropic-ai/claude-agent-sdk": { "version": "0.2.141", "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.141.tgz", @@ -197,6 +213,499 @@ } } }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.9.tgz", + "integrity": "sha512-WYvQviPw+Qyib0v92AwNIrdLISTp7RfDkM7bPqBvpbnhY4wq8HvHBZREVdYDXk98C8BkOIVnHAY3yvj7AVISxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helpers": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.9.tgz", + "integrity": "sha512-omlUGkr5EaoIJrhLf9CJ0TvjBRpd9+AXRG//0GEQ9THSo8wPiTlbpy1/Ow8ZTrbXpjd9FHXfbFQx32I04ht0FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.9.tgz", + "integrity": "sha512-aI3jjAAO1fh7vY/pBGsn1i9LDbRP43+asrRlkPuTXW5yHXtd1NgTEMudbBoDDxrf1daEEfPJqR+JBMakzrR4Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.7.tgz", + "integrity": "sha512-RL9GR0pUG5Kc8BUWLNDm2T5OpYwSX15r98I0IkgmRQTXuELq/OynH8xtMTMvTJFjXbMWFVTKtYkTaYQsuAwQlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-decorators": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-explicit-resource-management": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-explicit-resource-management/-/plugin-proposal-explicit-resource-management-7.27.4.tgz", + "integrity": "sha512-1SwtCDdZWQvUU1i7wt/ihP7W38WjC3CSTOHAl+Xnbze8+bbMNjRvRQydnj0k9J1jPqCAZctBFp6NHJXkrVVmEA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-explicit-resource-management instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.29.7.tgz", + "integrity": "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz", + "integrity": "sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", @@ -206,6 +715,103 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template/node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@bcoe/v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", @@ -641,6 +1247,242 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@inquirer/checkbox": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-3.0.1.tgz", + "integrity": "sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-4.0.1.tgz", + "integrity": "sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", + "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.5.5", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/editor": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-3.0.1.tgz", + "integrity": "sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-3.0.1.tgz", + "integrity": "sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-3.0.1.tgz", + "integrity": "sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/number": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-2.0.1.tgz", + "integrity": "sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-3.0.1.tgz", + "integrity": "sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-6.0.1.tgz", + "integrity": "sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^3.0.1", + "@inquirer/confirm": "^4.0.1", + "@inquirer/editor": "^3.0.1", + "@inquirer/expand": "^3.0.1", + "@inquirer/input": "^3.0.1", + "@inquirer/number": "^2.0.1", + "@inquirer/password": "^3.0.1", + "@inquirer/rawlist": "^3.0.1", + "@inquirer/search": "^2.0.1", + "@inquirer/select": "^3.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-3.0.1.tgz", + "integrity": "sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-2.0.1.tgz", + "integrity": "sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-3.0.1.tgz", + "integrity": "sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", + "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", + "dev": true, + "license": "MIT", + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -651,6 +1493,17 @@ "node": ">=8" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -719,6 +1572,182 @@ } } }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@stryker-mutator/api": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/api/-/api-8.7.1.tgz", + "integrity": "sha512-56vxcVxIfW0jxJhr7HB9Zx6Xr5/M95RG9MUK1DtbQhlmQesjpfBBsrPLOPzBJaITPH/vOYykuJ69vgSAMccQyw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mutation-testing-metrics": "3.3.0", + "mutation-testing-report-schema": "3.3.0", + "tslib": "~2.7.0", + "typed-inject": "~4.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@stryker-mutator/core": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/core/-/core-8.7.1.tgz", + "integrity": "sha512-r2AwhHWkHq6yEe5U8mAzPSWewULbv9YMabLHRzLjZnjj+Ipxtg+Zo22rrUc2Zl7mnYvb9w34bdlEzGz6MKgX2g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@inquirer/prompts": "^6.0.0", + "@stryker-mutator/api": "8.7.1", + "@stryker-mutator/instrumenter": "8.7.1", + "@stryker-mutator/util": "8.7.1", + "ajv": "~8.17.1", + "chalk": "~5.3.0", + "commander": "~12.1.0", + "diff-match-patch": "1.0.5", + "emoji-regex": "~10.4.0", + "execa": "~9.4.0", + "file-url": "~4.0.0", + "lodash.groupby": "~4.6.0", + "minimatch": "~9.0.5", + "mutation-testing-elements": "3.4.0", + "mutation-testing-metrics": "3.3.0", + "mutation-testing-report-schema": "3.3.0", + "npm-run-path": "~6.0.0", + "progress": "~2.0.3", + "rxjs": "~7.8.1", + "semver": "^7.6.3", + "source-map": "~0.7.4", + "tree-kill": "~1.2.2", + "tslib": "2.7.0", + "typed-inject": "~4.0.0", + "typed-rest-client": "~2.1.0" + }, + "bin": { + "stryker": "bin/stryker.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@stryker-mutator/core/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@stryker-mutator/core/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@stryker-mutator/core/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@stryker-mutator/core/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@stryker-mutator/core/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@stryker-mutator/instrumenter": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/instrumenter/-/instrumenter-8.7.1.tgz", + "integrity": "sha512-HSq4VHXesQCMR3hr6bn41DAeJ0yuP2vp9KSnls2TySNawFVWOCaKXpBX29Skj3zJQh7dnm7HuQg8HuXvJK15oA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/core": "~7.25.2", + "@babel/generator": "~7.25.0", + "@babel/parser": "~7.25.0", + "@babel/plugin-proposal-decorators": "~7.24.7", + "@babel/plugin-proposal-explicit-resource-management": "^7.24.7", + "@babel/preset-typescript": "~7.24.7", + "@stryker-mutator/api": "8.7.1", + "@stryker-mutator/util": "8.7.1", + "angular-html-parser": "~6.0.2", + "semver": "~7.6.3", + "weapon-regex": "~1.3.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@stryker-mutator/util": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/@stryker-mutator/util/-/util-8.7.1.tgz", + "integrity": "sha512-Oj/sIHZI1GLfGOHKnud4Gw0ZRufm7ONoQYNnhcaAYEXTWraYVcV7mue/th8fZComTHvDPA8Ge8U16FvWYEb8dg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -740,6 +1769,33 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.60.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz", @@ -1052,6 +2108,35 @@ } } }, + "node_modules/angular-html-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/angular-html-parser/-/angular-html-parser-6.0.2.tgz", + "integrity": "sha512-8+sH1TwYxv8XsQes1psxTHMtWRBbJFA/jY0ThqpT4AgCiRdhTtRxru0vlBfyRJpL9CHd3G06k871bR2vyqaM6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1095,6 +2180,19 @@ "node": "18 || 20 || >=22" } }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -1132,6 +2230,40 @@ "node": "18 || 20 || >=22" } }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1214,23 +2346,57 @@ "node": ">=6" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -1266,6 +2432,16 @@ "dev": true, "license": "MIT" }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1384,6 +2560,17 @@ "node": ">= 0.8" } }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -1394,6 +2581,13 @@ "node": ">=8" } }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1414,6 +2608,13 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/electron-to-chromium": { + "version": "1.5.363", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.363.tgz", + "integrity": "sha512-VjUKPyWzGnT1fujlkEGC/BvN70Hh70KXtAqcmniXviYlJC/ivcT+BWGPyxWVbJZLfvtKR6dqg1L7T7pgAMBtWA==", + "dev": true, + "license": "ISC" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1716,6 +2917,23 @@ "concat-map": "0.0.1" } }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -1830,6 +3048,33 @@ "node": ">=18.0.0" } }, + "node_modules/execa": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.4.1.tgz", + "integrity": "sha512-5eo/BRqZm3GYce+1jqX/tJ7duA2AnE39i88fuedNFUV8XxGxUpF3aWkBRfbUcjV49gCkvS/pzc0YrCPhaIewdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.3", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.0", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, "node_modules/express": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", @@ -1891,6 +3136,34 @@ "express": ">= 4.11" } }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/external-editor/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fallow": { "version": "2.70.0", "resolved": "https://registry.npmjs.org/fallow/-/fallow-2.70.0.tgz", @@ -1920,6 +3193,29 @@ "@fallow-cli/win32-x64-msvc": "2.70.0" } }, + "node_modules/fast-check": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.8.0.tgz", + "integrity": "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "dependencies": { + "pure-rand": "^8.0.0" + }, + "engines": { + "node": ">=12.17.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1974,6 +3270,22 @@ } } }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -1987,6 +3299,19 @@ "node": ">=16.0.0" } }, + "node_modules/file-url": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/file-url/-/file-url-4.0.0.tgz", + "integrity": "sha512-vRCdScQ6j3Ku6Kd7W1kZk9c++5SqD6Xz5Jotrjr/nkY714M14RFHy/AAVA2WQvpsqVAVgTbDrYyBpU205F0cLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -2090,6 +3415,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -2137,6 +3472,23 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-tsconfig": { "version": "4.14.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", @@ -2290,6 +3642,16 @@ "url": "https://opencollective.com/express" } }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/iconv-lite": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", @@ -2400,12 +3762,51 @@ "node": ">=0.10.0" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2460,6 +3861,20 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -2473,6 +3888,19 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2512,6 +3940,19 @@ "dev": true, "license": "MIT" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2552,6 +3993,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2640,6 +4088,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, "node_modules/minimatch": { "version": "10.2.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", @@ -2672,6 +4127,40 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mutation-testing-elements": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/mutation-testing-elements/-/mutation-testing-elements-3.4.0.tgz", + "integrity": "sha512-zFJtGlobq+Fyq95JoJj0iqrmwLSLQyIJuDATLwFMDSJCxpGN8kHCA6S4LoLJnkSL6bg4Aqultp8OBSMxGbW3EA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/mutation-testing-metrics": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/mutation-testing-metrics/-/mutation-testing-metrics-3.3.0.tgz", + "integrity": "sha512-vZEJ84SpK3Rwyk7k28SORS5o6ZDtehwifLPH6fQULrozJqlz2Nj8vi52+CjA+aMZCyyKB+9eYUh1HtiWVo4o/A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mutation-testing-report-schema": "3.3.0" + } + }, + "node_modules/mutation-testing-report-schema": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/mutation-testing-report-schema/-/mutation-testing-report-schema-3.3.0.tgz", + "integrity": "sha512-DF56q0sb0GYzxYUYNdzlfQzyE5oJBEasz8zL76bt3OFJU8q4iHSdUDdihPWWJD+4JLxSs3neM/R968zYdy0SWQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -2688,6 +4177,46 @@ "node": ">= 0.6" } }, + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2748,6 +4277,16 @@ "node": ">= 0.8.0" } }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -2793,6 +4332,19 @@ "node": ">=6" } }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2848,6 +4400,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", @@ -2880,6 +4439,32 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2903,6 +4488,23 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.0.tgz", + "integrity": "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/qs": { "version": "6.15.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", @@ -2997,6 +4599,16 @@ "node": ">= 18" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -3173,6 +4785,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -3210,6 +4832,19 @@ "node": ">=8" } }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3282,6 +4917,19 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -3291,6 +4939,16 @@ "node": ">=0.6" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/ts-algebra": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", @@ -3333,6 +4991,23 @@ "typescript": ">=4.0.0" } }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3346,6 +5021,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -3360,6 +5048,33 @@ "node": ">= 0.6" } }, + "node_modules/typed-inject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typed-inject/-/typed-inject-4.0.0.tgz", + "integrity": "sha512-OuBL3G8CJlS/kjbGV/cN8Ni32+ktyyi6ADDZpKvksbX0fYBV5WcukhRCYa7WqLce7dY/Br2dwtmJ9diiadLFpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16" + } + }, + "node_modules/typed-rest-client": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-2.1.0.tgz", + "integrity": "sha512-Nel9aPbgSzRxfs1+4GoSB4wexCF+4Axlk7OSGVQCMa+4fWcyxIsN/YNmkp0xTT2iQzMD98h8yFLav/cNaULmRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "des.js": "^1.1.0", + "js-md4": "^0.3.2", + "qs": "^6.10.3", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + }, + "engines": { + "node": ">= 16.0.0" + } + }, "node_modules/typescript": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", @@ -3398,6 +5113,33 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -3407,6 +5149,37 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3441,6 +5214,13 @@ "node": ">= 0.8" } }, + "node_modules/weapon-regex": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/weapon-regex/-/weapon-regex-1.3.6.tgz", + "integrity": "sha512-wsf1m1jmMrso5nhwVFJJHSubEBf3+pereGd7+nBKtYJ18KoB/PWJOHS3WRkwS04VrOU0iJr2bZU+l1QaTJ+9nA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3521,6 +5301,13 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -3563,6 +5350,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zod": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", diff --git a/package.json b/package.json index 0f39e7c02..ef97bdbc6 100644 --- a/package.json +++ b/package.json @@ -49,10 +49,12 @@ }, "devDependencies": { "@eslint/js": "^9.39.4", + "@stryker-mutator/core": "^8.7.1", "c8": "^11.0.0", "eslint": "^9.39.4", "eslint-plugin-n": "^17.24.0", "eslint-plugin-no-only-tests": "^3.4.0", + "fast-check": "^4.8.0", "globals": "^16.5.0", "js-yaml": "^4.1.1", "typescript": "^6.0.3", @@ -91,6 +93,8 @@ "test:affected": "node scripts/run-affected-tests.cjs", "test:coverage": "c8 --check-coverage --lines 70 --reporter text --include 'get-shit-done/bin/lib/*.cjs' --exclude 'tests/**' --all node scripts/run-tests.cjs", "test:coverage:unit": "c8 --check-coverage --lines 70 --reporter text --include 'get-shit-done/bin/lib/*.cjs' --exclude 'tests/**' --all node scripts/run-tests.cjs --suite unit", - "test:coverage:all": "npm run test:coverage" + "test:coverage:all": "npm run test:coverage", + "test:mutation": "stryker run", + "test:mutation:since": "stryker run --incremental --since origin/next" } } diff --git a/stryker.config.mjs b/stryker.config.mjs new file mode 100644 index 000000000..8d36139b9 --- /dev/null +++ b/stryker.config.mjs @@ -0,0 +1,92 @@ +/** + * stryker.config.mjs + * + * Mutation testing configuration for get-shit-done-redux. + * + * Test runner: 'command' (built into @stryker-mutator/core) + * Runs: node --test over the lib test files via the repo's run-tests invocation. + * + * Mutate scope: bin/lib/**\/*.cjs, excluding generated files and test files. + * + * coverageAnalysis: 'off' — command runner does not support per-mutant coverage + * thresholds: high=80, low=60, break=50 + * incremental: true — caches results; PR-scoped runs pass --mutate + * + * Reports: + * - html: reports/mutation/mutation.html + * - clear-text (console) + * - progress (spinner) + * + * NOTE: This is incremental / changed-files-only in CI (--mutate ) + * to stay bounded. Full runs are for local exploration only. + */ + +// Generated files that must NEVER be mutated +const GENERATED_FILES = [ + '!get-shit-done/bin/lib/configuration.cjs', // GENERATED — sdk/src/config/index.ts + '!get-shit-done/bin/lib/command-aliases.cjs', // GENERATED + '!get-shit-done/bin/lib/commands.cjs', // GENERATED + '!get-shit-done/bin/lib/core.cjs', // GENERATED + '!get-shit-done/bin/lib/install-profiles.cjs', // GENERATED + '!get-shit-done/bin/lib/installer-migrations.cjs', // GENERATED + '!get-shit-done/bin/lib/phase.cjs', // GENERATED + '!get-shit-done/bin/lib/profile-output.cjs', // GENERATED + '!get-shit-done/bin/lib/state.cjs', // GENERATED + '!get-shit-done/bin/lib/verify.cjs', // GENERATED + '!get-shit-done/bin/lib/init.cjs', // GENERATED + '!get-shit-done/bin/lib/audit.cjs', // GENERATED + '!get-shit-done/bin/lib/gsd2-import.cjs', // GENERATED +]; + +/** @type {import('@stryker-mutator/core').PartialStrykerOptions} */ +export default { + // ── Test runner ────────────────────────────────────────────────────────────── + testRunner: 'command', + commandRunner: { + // Run property tests + unit tests over lib only. + // Deliberately avoids running the full integration suite (slow). + command: 'node --test tests/context-utilization.property.test.cjs tests/prompt-budget.property.test.cjs tests/frontmatter.property.test.cjs tests/adr-parser.property.test.cjs tests/config-schema.property.test.cjs tests/adr-parser.test.cjs tests/active-workstream-store.test.cjs', + }, + + // ── Files to mutate ────────────────────────────────────────────────────────── + mutate: [ + 'get-shit-done/bin/lib/**/*.cjs', + '!get-shit-done/bin/lib/**/*.test.cjs', + ...GENERATED_FILES, + ], + + // ── Coverage ───────────────────────────────────────────────────────────────── + // 'off' is required for the command test runner — it cannot instrument per-mutant. + coverageAnalysis: 'off', + + // ── Thresholds ─────────────────────────────────────────────────────────────── + thresholds: { + high: 80, + low: 60, + break: 50, + }, + + // ── Incremental mode ───────────────────────────────────────────────────────── + // Cache mutation results; re-run only changed mutants on subsequent calls. + // In CI the workflow computes changed files and passes: stryker run --incremental --mutate + incremental: true, + incrementalFile: '.stryker-incremental.json', + + // ── Reporters ──────────────────────────────────────────────────────────────── + reporters: ['html', 'clear-text', 'progress'], + htmlReporter: { + fileName: 'reports/mutation/mutation.html', + }, + + // ── Temp directory ─────────────────────────────────────────────────────────── + tempDirName: '.stryker-tmp', + + // ── Ignore patterns ────────────────────────────────────────────────────────── + ignorePatterns: [ + 'node_modules', + 'reports', + '.stryker-tmp', + 'coverage', + 'hooks/dist', + ], +}; diff --git a/tests/adr-parser.property.test.cjs b/tests/adr-parser.property.test.cjs new file mode 100644 index 000000000..1e33b800f --- /dev/null +++ b/tests/adr-parser.property.test.cjs @@ -0,0 +1,212 @@ +'use strict'; + +/** + * Property-based tests for adr-parser.cjs + * + * Module: get-shit-done/bin/lib/adr-parser.cjs + * Exported: parseAdrMarkdown(markdown, options), shouldRejectAdrStatus(status) + * + * Properties tested: + * (a) shouldRejectAdrStatus: boundary — rejects exactly the known 3 statuses + * (b) shouldRejectAdrStatus: never throws on any input + * (c) parseAdrMarkdown: never throws on any string (including binary/unicode) + * (d) parseAdrMarkdown: always returns the required typed shape + * (e) parseAdrMarkdown: title extracted from H1 heading when present + * (f) parseAdrMarkdown: status is always a string (never null/undefined) + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fc = require('./helpers/fast-check-setup.cjs'); + +const { + parseAdrMarkdown, + shouldRejectAdrStatus, +} = require('../get-shit-done/bin/lib/adr-parser.cjs'); + +// Required output keys +const REQUIRED_KEYS = [ + 'title', 'status', 'context', 'decisions', 'options_considered', + 'consequences_positive', 'consequences_negative', 'out_of_scope', + 'deferred', 'dependencies', 'updates', 'source_path', 'key_files', + 'plan_sequence', 'format', 'unmapped_headers', +]; + +// Known reject-statuses +const REJECT_STATUSES = ['superseded', 'rejected', 'deprecated']; +const ACCEPT_STATUSES = ['accepted', 'proposed', 'active', 'draft', '']; + +describe('adr-parser: shouldRejectAdrStatus properties', () => { + // (a) Boundary: exactly the 3 known reject statuses return true + test('property: reject statuses return true, others return false', () => { + for (const status of REJECT_STATUSES) { + assert.equal(shouldRejectAdrStatus(status), true, `${status} should be rejected`); + } + for (const status of ACCEPT_STATUSES) { + assert.equal(shouldRejectAdrStatus(status), false, `${status} should not be rejected`); + } + }); + + // (b) Never throws on any input + test('property: shouldRejectAdrStatus never throws on any input', () => { + fc.assert( + fc.property( + fc.oneof( + fc.constant(null), + fc.constant(undefined), + fc.constant(NaN), + fc.constant(0), + fc.constant(''), + fc.string({ unit: 'binary', maxLength: 50 }), + fc.string({ unit: 'grapheme-composite', maxLength: 50 }), + fc.string({ maxLength: 50 }), + fc.boolean(), + fc.constant([]), + fc.constant({}) + ), + (input) => { + assert.doesNotThrow( + () => shouldRejectAdrStatus(input), + `shouldRejectAdrStatus threw on: ${JSON.stringify(input)}` + ); + } + ) + ); + }); + + test('property: shouldRejectAdrStatus always returns boolean', () => { + fc.assert( + fc.property( + fc.string({ maxLength: 100 }), + (status) => { + const result = shouldRejectAdrStatus(status); + assert.ok(typeof result === 'boolean', `Expected boolean got ${typeof result} for ${JSON.stringify(status)}`); + } + ) + ); + }); + + // Additional boundary: case variations should not reject (function is case-sensitive) + test('property: uppercase variants of reject statuses are NOT rejected (case-sensitive)', () => { + for (const status of REJECT_STATUSES) { + const upper = status.toUpperCase(); + // The parser normalizes status to lowercase internally, but shouldRejectAdrStatus + // is meant to receive an already-normalized status. If it receives uppercase, + // it should return false (or true — we just want it not to throw). + assert.doesNotThrow(() => shouldRejectAdrStatus(upper)); + } + }); +}); + +describe('adr-parser: parseAdrMarkdown properties', () => { + // (c) Never throws on any string input + test('property: parseAdrMarkdown never throws on any string input', () => { + fc.assert( + fc.property( + fc.oneof( + fc.string({ unit: 'binary', maxLength: 500 }), + fc.string({ unit: 'grapheme-composite', maxLength: 500 }), + fc.string({ maxLength: 500 }), + fc.constant(''), + fc.constant('# My ADR\n\n## Status\nAccepted\n\n## Decision\n- Do the thing.\n'), + fc.constant('---\n# Not a real ADR\n---'), + fc.constant('## No title\n\n## Status\nProposed') + ), + (markdown) => { + assert.doesNotThrow( + () => parseAdrMarkdown(markdown), + `parseAdrMarkdown threw on: ${JSON.stringify(markdown.slice(0, 80))}` + ); + } + ) + ); + }); + + // (d) Always returns the required typed shape + test('property: parseAdrMarkdown always returns required typed shape', () => { + fc.assert( + fc.property( + fc.string({ maxLength: 500 }), + (markdown) => { + const result = parseAdrMarkdown(markdown); + + assert.ok(typeof result === 'object' && result !== null, 'result must be object'); + + for (const key of REQUIRED_KEYS) { + assert.ok( + Object.prototype.hasOwnProperty.call(result, key), + `missing required key: ${key}` + ); + } + + assert.ok(typeof result.title === 'string', 'title must be string'); + assert.ok(typeof result.status === 'string', 'status must be string'); + assert.ok(typeof result.context === 'string', 'context must be string'); + assert.ok(Array.isArray(result.decisions), 'decisions must be array'); + assert.ok(Array.isArray(result.options_considered), 'options_considered must be array'); + assert.ok(Array.isArray(result.consequences_positive), 'consequences_positive must be array'); + assert.ok(Array.isArray(result.consequences_negative), 'consequences_negative must be array'); + assert.ok(Array.isArray(result.updates), 'updates must be array'); + assert.ok(Array.isArray(result.unmapped_headers), 'unmapped_headers must be array'); + } + ) + ); + }); + + // (e) Title extracted from H1 heading + // The parser applies .trim() to the heading text, so the expected title must also be trimmed. + test('property: H1 heading at line start is extracted as trimmed title', () => { + fc.assert( + fc.property( + fc.stringMatching(/^[A-Za-z0-9][A-Za-z0-9 _-]{0,49}$/), + (titleText) => { + const markdown = `# ${titleText}\n\n## Status\nAccepted\n`; + const result = parseAdrMarkdown(markdown); + const expected = titleText.trim(); // parser trims heading text + assert.equal( + result.title, + expected, + `Expected title="${expected}" got "${result.title}"` + ); + } + ) + ); + }); + + // (f) Status is always a non-null string + test('property: status is always a string (never null/undefined)', () => { + fc.assert( + fc.property( + fc.string({ maxLength: 300 }), + (markdown) => { + const result = parseAdrMarkdown(markdown); + assert.ok( + typeof result.status === 'string', + `status must be string, got ${typeof result.status}` + ); + } + ) + ); + }); + + // Robustness: sourcePath option with arbitrary strings + test('property: arbitrary sourcePath option never causes throws', () => { + fc.assert( + fc.property( + fc.string({ maxLength: 200 }), + fc.oneof( + fc.string({ maxLength: 100 }), + fc.string({ unit: 'binary', maxLength: 50 }), + fc.constant(null), + fc.constant(undefined) + ), + (markdown, sourcePath) => { + assert.doesNotThrow( + () => parseAdrMarkdown(markdown, { sourcePath }), + `parseAdrMarkdown threw with sourcePath=${JSON.stringify(sourcePath)}` + ); + } + ) + ); + }); +}); diff --git a/tests/config-schema.property.test.cjs b/tests/config-schema.property.test.cjs new file mode 100644 index 000000000..ab2f1d077 --- /dev/null +++ b/tests/config-schema.property.test.cjs @@ -0,0 +1,154 @@ +'use strict'; + +/** + * Property-based tests for config-schema.cjs + * + * Module: get-shit-done/bin/lib/config-schema.cjs + * Exported: isValidConfigKey(keyPath) -> boolean + * VALID_CONFIG_KEYS: Set + * RUNTIME_STATE_KEYS: Set + * DYNAMIC_KEY_PATTERNS: Array<{ test(key): boolean, ... }> + * + * Properties tested: + * (a) isValidConfigKey never throws regardless of input type/content + * (b) isValidConfigKey(key) is true for every key in VALID_CONFIG_KEYS + * (c) isValidConfigKey(key) is true for every key in RUNTIME_STATE_KEYS + * (d) Robustness: null/undefined/NaN/control-chars/binary never throw + * (e) Arbitrary garbage strings return false (not throw) from isValidConfigKey + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fc = require('./helpers/fast-check-setup.cjs'); + +const { + isValidConfigKey, + VALID_CONFIG_KEYS, + RUNTIME_STATE_KEYS, +} = require('../get-shit-done/bin/lib/config-schema.cjs'); + +describe('config-schema: isValidConfigKey properties', () => { + // (a) Never throws on any input + test('property: isValidConfigKey never throws on hostile inputs', () => { + fc.assert( + fc.property( + fc.oneof( + fc.constant(null), + fc.constant(undefined), + fc.constant(NaN), + fc.constant(Infinity), + fc.constant(-Infinity), + fc.constant(0), + fc.constant(''), + fc.constant('\x00'), + fc.constant('\n\r\t'), + fc.string({ unit: 'binary', maxLength: 100 }), + fc.string({ unit: 'grapheme-composite', maxLength: 100 }), + fc.constant([]), + fc.constant({}), + fc.boolean(), + fc.string({ maxLength: 100 }) + ), + (input) => { + assert.doesNotThrow( + () => isValidConfigKey(input), + `isValidConfigKey threw on input: ${JSON.stringify(input)}` + ); + } + ) + ); + }); + + // (b) Every key in VALID_CONFIG_KEYS returns true + test('all VALID_CONFIG_KEYS entries are recognized as valid', () => { + for (const key of VALID_CONFIG_KEYS) { + assert.equal( + isValidConfigKey(key), + true, + `Expected isValidConfigKey(${JSON.stringify(key)}) === true` + ); + } + }); + + // (c) Every key in RUNTIME_STATE_KEYS returns true + test('all RUNTIME_STATE_KEYS entries are recognized as valid', () => { + for (const key of RUNTIME_STATE_KEYS) { + assert.equal( + isValidConfigKey(key), + true, + `Expected isValidConfigKey(${JSON.stringify(key)}) === true (runtime state key)` + ); + } + }); + + // (d+e) Robustness: hostile strings return boolean (not throw) + test('property: isValidConfigKey always returns a boolean for any string', () => { + fc.assert( + fc.property( + fc.string({ maxLength: 200 }), + (key) => { + const result = isValidConfigKey(key); + assert.ok( + typeof result === 'boolean', + `isValidConfigKey must return boolean, got ${typeof result} for ${JSON.stringify(key)}` + ); + } + ) + ); + }); + + test('property: binary/control-char strings return false (not throw)', () => { + fc.assert( + fc.property( + fc.oneof( + fc.string({ unit: 'binary', maxLength: 100 }), + fc.string({ unit: 'grapheme-composite', maxLength: 100 }) + ), + (key) => { + // Either returns true (if it happens to match a valid key) or false + // It must NOT throw + let result; + assert.doesNotThrow(() => { + result = isValidConfigKey(key); + }); + assert.ok(typeof result === 'boolean'); + } + ) + ); + }); + + // Boundary: well-formed dotted paths that are NOT in the schema + test('property: plausible-but-invalid dotted paths return false', () => { + // Generate dot-separated alphanumeric paths that do not match known keys + const dotPath = fc.array( + fc.stringMatching(/^[a-z][a-z0-9_]{0,10}$/), + { minLength: 3, maxLength: 5 } + ).map((parts) => 'zz_unknown.' + parts.join('.')); + + fc.assert( + fc.property(dotPath, (key) => { + // Must not throw + let result; + assert.doesNotThrow(() => { + result = isValidConfigKey(key); + }); + // Result is a boolean + assert.ok(typeof result === 'boolean'); + }) + ); + }); + + // Boundary: empty string is not a valid config key + test('empty string is not a valid config key', () => { + const result = isValidConfigKey(''); + assert.equal(result, false, 'empty string must not be a valid config key'); + }); + + // Boundary: null/undefined/number return false (not throw, not true) + test('null, undefined, number inputs return false', () => { + assert.equal(isValidConfigKey(null), false); + assert.equal(isValidConfigKey(undefined), false); + assert.equal(isValidConfigKey(42), false); + assert.equal(isValidConfigKey(NaN), false); + }); +}); diff --git a/tests/context-utilization.property.test.cjs b/tests/context-utilization.property.test.cjs new file mode 100644 index 000000000..9e825bb42 --- /dev/null +++ b/tests/context-utilization.property.test.cjs @@ -0,0 +1,217 @@ +'use strict'; + +/** + * Property-based tests for context-utilization.cjs + * + * Module: get-shit-done/bin/lib/context-utilization.cjs + * Exported: classifyContextUtilization(tokensUsed, contextWindow) -> { percent, state } + * + * Thresholds (from module source): + * ratio < 0.60 → healthy + * 0.60 <= ratio < 0.70 → warning + * ratio >= 0.70 → critical + * + * Properties tested: + * (a) Boundary: across the 60% and 70% thresholds the classify flips correctly + * (b) Robustness: hostile inputs (null/undefined/NaN/Infinity/negative/wrong-type) + * always throw TypeError (documented contract) and never throw non-TypeError + * (c) Return shape: all valid inputs return an object with typed { percent, state } + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fc = require('./helpers/fast-check-setup.cjs'); + +const { classifyContextUtilization, STATES } = require('../get-shit-done/bin/lib/context-utilization.cjs'); + +// ─── Boundary constants ─────────────────────────────────────────────────────── +const WARNING_THRESHOLD = 0.60; // ratio < this → healthy +const CRITICAL_THRESHOLD = 0.70; // ratio < this → warning, else critical +const WINDOW = 10000; // A fixed contextWindow that gives us clean ratio math +const W = 50; // boundary exploration width: ±50 tokens around the threshold + +describe('context-utilization property tests', () => { + // ─── (a) Boundary property: classify flips at exactly 60% and 70% ──────────── + test('property: classify is healthy below 60%, warning in [60%,70%), critical at ≥70%', () => { + // Test across a range of context windows. + // The boundary is at the exact RATIO — not at Math.floor(window * ratio). + // e.g. with window=1001: floor(1001 * 0.60) = 600, but 600/1001 = 0.5994 < 0.60 → healthy. + // So we compute the exact first token count that meets or exceeds the threshold. + fc.assert( + fc.property( + // contextWindow: 1000..200000 to keep ratios well-defined + fc.integer({ min: 1000, max: 200_000 }), + (contextWindow) => { + // First integer where tokensUsed/contextWindow >= WARNING_THRESHOLD + const firstWarning = Math.ceil(contextWindow * WARNING_THRESHOLD); + // First integer where tokensUsed/contextWindow >= CRITICAL_THRESHOLD + const firstCritical = Math.ceil(contextWindow * CRITICAL_THRESHOLD); + + // Just below warning threshold → healthy + if (firstWarning > 0) { + const below = firstWarning - 1; + const r = classifyContextUtilization(below, contextWindow); + assert.equal( + r.state, + STATES.HEALTHY, + `tokensUsed=${below} contextWindow=${contextWindow} ratio=${(below / contextWindow).toFixed(6)} expected healthy got ${r.state}` + ); + } + + // At exact warning boundary → warning (unless critical collapses to same point) + if (firstWarning < firstCritical && firstWarning <= contextWindow) { + const r2 = classifyContextUtilization(firstWarning, contextWindow); + assert.equal( + r2.state, + STATES.WARNING, + `tokensUsed=${firstWarning} ratio=${(firstWarning / contextWindow).toFixed(6)} expected warning got ${r2.state}` + ); + } + + // At or above critical threshold → critical + if (firstCritical <= contextWindow) { + const r3 = classifyContextUtilization(firstCritical, contextWindow); + assert.equal( + r3.state, + STATES.CRITICAL, + `tokensUsed=${firstCritical} ratio=${(firstCritical / contextWindow).toFixed(6)} expected critical got ${r3.state}` + ); + } + } + ) + ); + }); + + test('property: near 60% boundary the state is always healthy (never warning/critical)', () => { + // Tokens strictly below ceil(WINDOW * 0.60) must classify as healthy. + // The boundary is the FIRST integer where ratio >= 0.60 (Math.ceil). + // We sample from [firstWarning - W, firstWarning - 1] to probe just below it. + const firstWarning = Math.ceil(WINDOW * WARNING_THRESHOLD); // = 6000 + const rangeMin = Math.max(0, firstWarning - W); // = 5950 + const rangeMax = firstWarning - 1; // = 5999 + fc.assert( + fc.property( + fc.integer({ min: rangeMin, max: rangeMax }), + (tokensUsed) => { + const r = classifyContextUtilization(tokensUsed, WINDOW); + assert.equal( + r.state, + STATES.HEALTHY, + `tokensUsed=${tokensUsed}/${WINDOW}=${(tokensUsed / WINDOW * 100).toFixed(2)}% expected healthy got ${r.state}` + ); + } + ) + ); + }); + + test('property: near 70% boundary tokens at/above critical threshold must be critical', () => { + const criticalFloor = Math.ceil(WINDOW * CRITICAL_THRESHOLD); + fc.assert( + fc.property( + fc.integer({ min: criticalFloor, max: WINDOW }), + (tokensUsed) => { + const r = classifyContextUtilization(tokensUsed, WINDOW); + assert.equal( + r.state, + STATES.CRITICAL, + `tokensUsed=${tokensUsed}/${WINDOW}=${(tokensUsed / WINDOW * 100).toFixed(2)}% expected critical got ${r.state}` + ); + } + ) + ); + }); + + // ─── (b) Robustness: hostile inputs always throw TypeError ──────────────────── + test('property: non-integer/negative tokensUsed always throws TypeError', () => { + const invalidTokensUsed = fc.oneof( + fc.constant(null), + fc.constant(undefined), + fc.constant(NaN), + fc.constant(Infinity), + fc.constant(-Infinity), + fc.constant(-1), + fc.integer({ min: -10000, max: -1 }), + fc.double({ min: 0.1, max: 0.9 }), + fc.string(), + fc.boolean(), + fc.constant([]), + fc.constant({}) + ); + fc.assert( + fc.property(invalidTokensUsed, (bad) => { + assert.throws( + () => classifyContextUtilization(bad, 10000), + (err) => { + assert.ok(err instanceof TypeError, `Expected TypeError but got ${err.constructor.name}: ${err.message}`); + return true; + } + ); + }) + ); + }); + + test('property: non-integer/non-positive contextWindow always throws TypeError', () => { + const invalidWindows = fc.oneof( + fc.constant(null), + fc.constant(undefined), + fc.constant(NaN), + fc.constant(Infinity), + fc.constant(-Infinity), + fc.constant(0), + fc.constant(-1), + fc.integer({ min: -10000, max: 0 }), + fc.double({ min: 0.1, max: 0.9 }), + fc.string(), + fc.boolean() + ); + fc.assert( + fc.property(invalidWindows, (bad) => { + assert.throws( + () => classifyContextUtilization(1000, bad), + (err) => { + assert.ok(err instanceof TypeError, `Expected TypeError for contextWindow=${bad} but got ${err.constructor.name}: ${err.message}`); + return true; + } + ); + }) + ); + }); + + // ─── (c) Return shape: all valid inputs produce typed { percent, state } ────── + test('property: valid inputs always return { percent: number[0..100], state: string }', () => { + fc.assert( + fc.property( + fc.integer({ min: 1, max: 1_000_000 }), // contextWindow + (contextWindow) => { + // tokensUsed in [0, contextWindow] + const tokensUsed = Math.floor(Math.random() * (contextWindow + 1)); + const r = classifyContextUtilization(tokensUsed, contextWindow); + + assert.ok(typeof r === 'object' && r !== null, 'result must be object'); + assert.ok(typeof r.percent === 'number', `percent must be number got ${typeof r.percent}`); + assert.ok(typeof r.state === 'string', `state must be string got ${typeof r.state}`); + assert.ok(r.percent >= 0 && r.percent <= 100, `percent ${r.percent} out of [0,100]`); + assert.ok( + [STATES.HEALTHY, STATES.WARNING, STATES.CRITICAL].includes(r.state), + `state must be one of the STATES enum, got ${r.state}` + ); + } + ) + ); + }); + + test('property: tokensUsed exceeding contextWindow clamps to 100% critical', () => { + fc.assert( + fc.property( + fc.integer({ min: 1, max: 100_000 }), + fc.integer({ min: 1, max: 100_000 }), + (contextWindow, extra) => { + const tokensUsed = contextWindow + extra; // always exceeds window + const r = classifyContextUtilization(tokensUsed, contextWindow); + assert.equal(r.state, STATES.CRITICAL, `overflow ${tokensUsed}/${contextWindow} must be critical`); + assert.equal(r.percent, 100, `overflow percent must clamp to 100, got ${r.percent}`); + } + ) + ); + }); +}); diff --git a/tests/frontmatter.property.test.cjs b/tests/frontmatter.property.test.cjs new file mode 100644 index 000000000..6333ec91a --- /dev/null +++ b/tests/frontmatter.property.test.cjs @@ -0,0 +1,194 @@ +'use strict'; + +/** + * Property-based tests for frontmatter.cjs + * + * Module: get-shit-done/bin/lib/frontmatter.cjs + * Exported (pure): extractFrontmatter, reconstructFrontmatter, spliceFrontmatter + * + * Properties tested: + * (a) extractFrontmatter never throws on ANY string input (including binary/unicode) + * (b) extractFrontmatter always returns a plain object (not null, not array) + * (c) round-trip: reconstructFrontmatter(extractFrontmatter(spliceFrontmatter(content, obj))) + * preserves key-value pairs for simple flat string values + * (d) spliceFrontmatter never throws on any string/object combination + * (e) extractFrontmatter returns {} for content without a leading ---...--- block + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fc = require('./helpers/fast-check-setup.cjs'); + +const { + extractFrontmatter, + reconstructFrontmatter, + spliceFrontmatter, +} = require('../get-shit-done/bin/lib/frontmatter.cjs'); + +// ─── Arbitraries ───────────────────────────────────────────────────────────── + +// Simple YAML key: alphanumeric + underscore, at least 1 char +const yamlKey = fc.stringMatching(/^[a-z][a-z0-9_]{0,19}$/); + +// Simple YAML scalar value: printable ASCII without : ' " # newlines +const yamlScalarValue = fc.stringMatching(/^[a-zA-Z0-9 ._/-]{1,40}$/); + +// ─── Tests ──────────────────────────────────────────────────────────────────── + +describe('frontmatter: extractFrontmatter properties', () => { + // (a) Never throws on any string input + test('property: extractFrontmatter never throws on arbitrary binary/unicode input', () => { + fc.assert( + fc.property( + fc.oneof( + fc.string({ unit: 'binary', maxLength: 300 }), + fc.string({ unit: 'grapheme-composite', maxLength: 300 }), + fc.constant(''), + fc.constant('---\n---'), + fc.constant('---\nkey: value\n---\n# body'), + fc.string({ maxLength: 300 }) + ), + (input) => { + assert.doesNotThrow( + () => extractFrontmatter(input), + `extractFrontmatter threw on input: ${JSON.stringify(input.slice(0, 50))}` + ); + } + ) + ); + }); + + // (b) Always returns a plain object + test('property: extractFrontmatter always returns a plain object', () => { + fc.assert( + fc.property( + fc.oneof( + fc.string({ unit: 'binary', maxLength: 200 }), + fc.string({ unit: 'grapheme-composite', maxLength: 200 }), + fc.string({ maxLength: 200 }) + ), + (input) => { + const result = extractFrontmatter(input); + assert.ok( + typeof result === 'object' && result !== null && !Array.isArray(result), + `extractFrontmatter must return plain object, got ${JSON.stringify(result)}` + ); + } + ) + ); + }); + + // (e) Returns {} for content without leading --- block + test('property: content without leading --- block returns empty object', () => { + fc.assert( + fc.property( + fc.oneof( + fc.string({ minLength: 0, maxLength: 200 }).filter((s) => !s.startsWith('---')), + fc.constant('# Just a heading'), + fc.constant('plain text content'), + fc.constant('') + ), + (input) => { + const result = extractFrontmatter(input); + assert.deepEqual( + result, + {}, + `Expected {} for non-frontmatter input, got ${JSON.stringify(result)}` + ); + } + ) + ); + }); +}); + +describe('frontmatter: reconstructFrontmatter properties', () => { + test('property: reconstructFrontmatter never throws on plain objects with string values', () => { + fc.assert( + fc.property( + fc.dictionary(yamlKey, yamlScalarValue, { maxKeys: 10 }), + (obj) => { + assert.doesNotThrow( + () => reconstructFrontmatter(obj), + `reconstructFrontmatter threw on ${JSON.stringify(obj)}` + ); + } + ) + ); + }); + + test('property: reconstructFrontmatter output is a string', () => { + fc.assert( + fc.property( + fc.dictionary(yamlKey, yamlScalarValue, { maxKeys: 8 }), + (obj) => { + const result = reconstructFrontmatter(obj); + assert.ok(typeof result === 'string', `Expected string got ${typeof result}`); + } + ) + ); + }); + + test('property: reconstructFrontmatter on {} returns empty string', () => { + assert.equal(reconstructFrontmatter({}), ''); + }); +}); + +describe('frontmatter: spliceFrontmatter properties', () => { + // (d) Never throws on any combination + test('property: spliceFrontmatter never throws on arbitrary content + object', () => { + fc.assert( + fc.property( + fc.string({ maxLength: 300 }), + fc.dictionary(yamlKey, yamlScalarValue, { maxKeys: 8 }), + (content, obj) => { + assert.doesNotThrow( + () => spliceFrontmatter(content, obj), + `spliceFrontmatter threw on content=${JSON.stringify(content.slice(0, 30))}` + ); + } + ) + ); + }); + + test('property: spliceFrontmatter always returns a string', () => { + fc.assert( + fc.property( + fc.string({ maxLength: 200 }), + fc.dictionary(yamlKey, yamlScalarValue, { maxKeys: 5 }), + (content, obj) => { + const result = spliceFrontmatter(content, obj); + assert.ok(typeof result === 'string', `Expected string got ${typeof result}`); + } + ) + ); + }); + + // (c) Round-trip: splice then extract preserves flat string keys + test('property: splice then extract round-trip preserves flat string values', () => { + fc.assert( + fc.property( + fc.string({ maxLength: 100 }), // existing document body + // Only keys + simple values without colons/hashes that would confuse the minimal parser + fc.dictionary( + fc.stringMatching(/^[a-z][a-z0-9]{0,14}$/), + fc.stringMatching(/^[a-zA-Z0-9]{1,30}$/), + { minKeys: 1, maxKeys: 5 } + ), + (body, obj) => { + const spliced = spliceFrontmatter(body, obj); + const extracted = extractFrontmatter(spliced); + + for (const [key, value] of Object.entries(obj)) { + if (typeof value === 'string' && value.length > 0) { + assert.equal( + extracted[key], + value, + `Round-trip failed for key=${key}: expected ${value} got ${extracted[key]}` + ); + } + } + } + ) + ); + }); +}); diff --git a/tests/helpers/fast-check-setup.cjs b/tests/helpers/fast-check-setup.cjs new file mode 100644 index 000000000..ae5661c82 --- /dev/null +++ b/tests/helpers/fast-check-setup.cjs @@ -0,0 +1,24 @@ +'use strict'; + +/** + * fast-check-setup.cjs + * + * Shared configuration for all property-based tests. Require this at the + * top of every *.property.test.cjs file before any fc.assert() call. + * + * Settings: + * numRuns: 200 — enough to catch boundary bugs without slow CI + * seed: 42 — deterministic across CI runs; set GSD_FC_SEED= to + * override locally for exploration + */ + +const fc = require('fast-check'); + +const seed = process.env.GSD_FC_SEED ? Number(process.env.GSD_FC_SEED) : 42; + +fc.configureGlobal({ + numRuns: 200, + seed, +}); + +module.exports = fc; diff --git a/tests/prompt-budget.property.test.cjs b/tests/prompt-budget.property.test.cjs new file mode 100644 index 000000000..3e5451788 --- /dev/null +++ b/tests/prompt-budget.property.test.cjs @@ -0,0 +1,242 @@ +'use strict'; + +/** + * Property-based tests for prompt-budget.cjs + * + * Module: get-shit-done/bin/lib/prompt-budget.cjs + * Exported: estimateTokens(text), applyBudget({ sections, budget, options }) + * + * Key invariants: + * - estimateTokens: always >= 0, monotonically related to string length + * - applyBudget: when hardFailed=false, estimatedTokens <= effectiveBudget + * - applyBudget: instructions and roadmap are ALWAYS kept verbatim (never trimmed) + * - applyBudget: below the minSet the call returns hardFailed=true with prompt='' + * - Budget boundary: a budget just at the effective floor triggers hard-fail; + * a budget just above it passes + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fc = require('./helpers/fast-check-setup.cjs'); + +const { estimateTokens, applyBudget } = require('../get-shit-done/bin/lib/prompt-budget.cjs'); + +// ─── Helpers ───────────────────────────────────────────────────────────────── + +function minimalSections(overrides = {}) { + return { + instructions: 'Instructions text.', + roadmap: 'Roadmap text.', + plans: [{ file: 'plan.md', content: 'Plan content.' }], + projectMd: null, + context: null, + research: null, + requirements: null, + ...overrides, + }; +} + +// ─── estimateTokens property tests ─────────────────────────────────────────── + +describe('prompt-budget: estimateTokens properties', () => { + test('property: estimateTokens is always >= 0', () => { + fc.assert( + fc.property( + fc.oneof( + fc.string(), + fc.constant(null), + fc.constant(undefined), + fc.constant(''), + fc.string({ unit: 'binary', maxLength: 200 }), + fc.string({ unit: 'grapheme-composite', maxLength: 200 }) + ), + (input) => { + const result = estimateTokens(input); + assert.ok(typeof result === 'number', `estimateTokens must return number, got ${typeof result}`); + assert.ok(result >= 0, `estimateTokens(${JSON.stringify(input)}) must be >= 0, got ${result}`); + assert.ok(Number.isInteger(result), `estimateTokens must return integer, got ${result}`); + } + ) + ); + }); + + test('property: estimateTokens is monotonically non-decreasing as text grows', () => { + fc.assert( + fc.property( + fc.string({ maxLength: 500 }), + fc.string({ minLength: 1, maxLength: 100 }), + (base, suffix) => { + const short = estimateTokens(base); + const long = estimateTokens(base + suffix); + assert.ok(long >= short, `tokens('${base}' + suffix)=${long} < tokens('${base}')=${short}`); + } + ) + ); + }); + + test('property: estimateTokens(null/undefined) returns 0', () => { + assert.equal(estimateTokens(null), 0); + assert.equal(estimateTokens(undefined), 0); + assert.equal(estimateTokens(''), 0); + }); + + test('property: estimateTokens approximation is ceil(len/4)', () => { + fc.assert( + fc.property(fc.string({ minLength: 1, maxLength: 1000 }), (text) => { + const expected = Math.ceil(text.length / 4); + assert.equal(estimateTokens(text), expected); + }) + ); + }); +}); + +// ─── applyBudget property tests ─────────────────────────────────────────────── + +describe('prompt-budget: applyBudget properties', () => { + // (a) Boundary property: budget near the hardFail threshold + test('property: when minSet > effectiveBudget, applyBudget returns hardFailed=true and prompt=""', () => { + fc.assert( + fc.property( + // Use a very small budget to force hard-fail + fc.integer({ min: 1, max: 50 }), + (tinyBudget) => { + const sections = minimalSections({ + instructions: 'A'.repeat(200), // ~50 tokens + roadmap: 'B'.repeat(200), // ~50 tokens + }); + const result = applyBudget({ sections, budget: tinyBudget }); + if (result.metadata.hardFailed) { + assert.equal(result.prompt, '', 'hardFailed must return empty prompt'); + assert.equal(result.metadata.hardFailed, true); + } + // If not hard-failed, that is also valid — result is consistent + } + ) + ); + }); + + test('property: when budget is adequate, estimatedTokens never exceeds effectiveBudget', () => { + // Use a large budget: instructions ~10 tokens + roadmap ~10 tokens + plan ~10 tokens + // With margin 10%, effectiveBudget = floor(budget * 0.9) + fc.assert( + fc.property( + fc.integer({ min: 500, max: 10_000 }), + (budget) => { + const sections = minimalSections(); + const result = applyBudget({ sections, budget }); + if (!result.metadata.hardFailed) { + assert.ok( + result.metadata.estimatedTokens <= result.metadata.effectiveBudget, + `estimatedTokens ${result.metadata.estimatedTokens} > effectiveBudget ${result.metadata.effectiveBudget} at budget=${budget}` + ); + assert.ok(result.prompt.length > 0, 'non-hardFailed must return non-empty prompt'); + } + } + ) + ); + }); + + // (b) Robustness: hostile section inputs — applyBudget should either work or throw + // clearly — it must NEVER silently return a broken shape + test('property: applyBudget always returns typed { prompt, metadata } shape on valid budget', () => { + fc.assert( + fc.property( + fc.integer({ min: 100, max: 50_000 }), + fc.string({ maxLength: 200 }), + fc.string({ maxLength: 200 }), + (budget, instructions, roadmap) => { + const sections = minimalSections({ instructions, roadmap }); + const result = applyBudget({ sections, budget }); + + assert.ok(typeof result === 'object' && result !== null); + assert.ok(typeof result.prompt === 'string', 'prompt must be string'); + assert.ok(typeof result.metadata === 'object' && result.metadata !== null); + assert.ok(typeof result.metadata.hardFailed === 'boolean'); + assert.ok(typeof result.metadata.budget === 'number'); + assert.ok(typeof result.metadata.effectiveBudget === 'number'); + assert.ok(Array.isArray(result.metadata.omitted)); + } + ) + ); + }); + + test('property: instructions are always present verbatim in the output prompt', () => { + fc.assert( + fc.property( + fc.string({ minLength: 1, maxLength: 100 }), + (instructions) => { + const sections = minimalSections({ instructions }); + const result = applyBudget({ sections, budget: 100_000 }); + if (!result.metadata.hardFailed) { + assert.ok( + result.prompt.includes(instructions), + `Instructions not found verbatim in prompt. Instructions: "${instructions.slice(0, 50)}"` + ); + } + } + ) + ); + }); + + test('property: roadmap is always present verbatim in the output prompt', () => { + fc.assert( + fc.property( + fc.string({ minLength: 1, maxLength: 100 }), + (roadmap) => { + const sections = minimalSections({ roadmap }); + const result = applyBudget({ sections, budget: 100_000 }); + if (!result.metadata.hardFailed) { + assert.ok( + result.prompt.includes(roadmap), + `Roadmap not found verbatim in prompt. Roadmap: "${roadmap.slice(0, 50)}"` + ); + } + } + ) + ); + }); + + test('property: safetyMarginPct in [0,50] always produces effectiveBudget <= budget', () => { + fc.assert( + fc.property( + fc.integer({ min: 1000, max: 100_000 }), + fc.integer({ min: 0, max: 50 }), + (budget, safetyMarginPct) => { + const sections = minimalSections(); + const result = applyBudget({ sections, budget, options: { safetyMarginPct } }); + assert.ok( + result.metadata.effectiveBudget <= budget, + `effectiveBudget ${result.metadata.effectiveBudget} > budget ${budget} at margin ${safetyMarginPct}%` + ); + } + ) + ); + }); + + test('property: context/research/requirements omission is tracked in metadata.omitted', () => { + // Build a sections object where extras push it over a tight budget + fc.assert( + fc.property( + fc.string({ minLength: 400, maxLength: 800 }), // ~100-200 tokens context + (contextText) => { + const sections = minimalSections({ context: contextText }); + // Use a very tight budget that forces trimming + const baseTokens = estimateTokens('Instructions text.') + + estimateTokens('Roadmap text.') + + estimateTokens('Plan content.') + 20; // overhead + + const tightBudget = Math.ceil(baseTokens / 0.9) + 1; // just barely fits without context + + const result = applyBudget({ sections, budget: tightBudget }); + if (!result.metadata.hardFailed && result.metadata.omitted.includes('context')) { + // The note must have been injected if context was dropped + assert.equal(result.metadata.noteInjected, true, + 'noteInjected should be true when context was omitted'); + } + // Whether or not context was dropped, omitted is always an array + assert.ok(Array.isArray(result.metadata.omitted)); + } + ) + ); + }); +});