testguard-cli 0.14.1__tar.gz → 0.15.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/scripts/install-smoke.mjs +14 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/CHANGELOG.md +46 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/PKG-INFO +17 -5
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/README.md +16 -4
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/action.yml +1 -1
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/docs/testguard-explained.html +1 -1
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/docs/testguard-explained.pdf +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/package-lock.json +2 -2
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/package.json +1 -1
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/packaging/gitlab/testguard.gitlab-ci.yml +2 -2
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/packaging/homebrew/testguard.rb +2 -2
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/pyproject.toml +1 -1
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/GATE-SEMANTICS.md +65 -3
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/README.md +7 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/evidence.json +3 -1
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/schemas/evidence.schema.json +14 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/cli.mjs +4 -1
- testguard_cli-0.15.0/src/command-budget.mjs +52 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/commands/probe.mjs +9 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/commands/replay.mjs +14 -1
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/commands/sweep.mjs +15 -6
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/git.mjs +4 -2
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/attribution.mjs +2 -0
- testguard_cli-0.15.0/src/probe/discover.mjs +65 -0
- testguard_cli-0.15.0/src/probe/imports.mjs +785 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/mocks.mjs +13 -4
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/probe.mjs +176 -19
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/rank.mjs +28 -5
- testguard_cli-0.15.0/src/probe/runners/discovery.mjs +634 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/runners/index.mjs +51 -19
- testguard_cli-0.15.0/src/probe/runners/jest.mjs +52 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/runners/playwright.mjs +58 -2
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/runners/python.mjs +7 -7
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/runners/shared.mjs +55 -6
- testguard_cli-0.15.0/src/probe/runners/vitest.mjs +41 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/replay/replay.mjs +108 -13
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/sweep/sweep.mjs +5 -0
- testguard_cli-0.15.0/test/admit-decision.test.mjs +18 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/attribution.test.mjs +20 -0
- testguard_cli-0.15.0/test/command-budget-write-boundaries.test.mjs +67 -0
- testguard_cli-0.15.0/test/command-budget.test.mjs +40 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/discover.test.mjs +41 -0
- testguard_cli-0.15.0/test/imports.test.mjs +321 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/mocks.test.mjs +14 -0
- testguard_cli-0.15.0/test/probe-dirty-paths.test.mjs +45 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/probe-error.test.mjs +38 -12
- testguard_cli-0.15.0/test/probe-native-manifest.test.mjs +78 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/probe-preconditions.test.mjs +4 -4
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/rank-aliases.test.mjs +28 -1
- testguard_cli-0.15.0/test/replay-mixed-runners.test.mjs +116 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/runner-argv.test.mjs +9 -6
- testguard_cli-0.15.0/test/runner-discovery.test.mjs +294 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/runner-python.test.mjs +45 -1
- testguard_cli-0.15.0/test/runner-selection-blind.test.mjs +127 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/runner-vitest.test.mjs +59 -1
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/testguard.claims.json +526 -15
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/testguard.cost-budget.json +17 -6
- testguard_cli-0.14.1/src/probe/discover.mjs +0 -29
- testguard_cli-0.14.1/src/probe/runners/jest.mjs +0 -21
- testguard_cli-0.14.1/src/probe/runners/vitest.mjs +0 -20
- testguard_cli-0.14.1/test/runner-selection-blind.test.mjs +0 -77
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.agents/skills/testguard/SKILL.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.docguard.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.docguardignore +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.gitattributes +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/dependabot.yml +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/scripts/approve-held-runs.sh +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/scripts/build-one-pager.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/scripts/check-cost-budget.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/scripts/check-python-fixture.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/scripts/publish-homebrew-tap.sh +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/scripts/sync-release-version.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/workflows/auto-merge.yml +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/workflows/ci.yml +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/workflows/release.yml +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/workflows/scheduled-release.yml +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.github/workflows/supply-chain.yml +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.gitignore +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.npmignore +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.npmrc +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.pre-commit-config.yaml +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/.pre-commit-hooks.yaml +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/AGENTS.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/CLAUDE.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/CODE_OF_CONDUCT.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/CONTRIBUTING.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/DRIFT-LOG.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/LICENSE +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/PRIVACY.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/SECURITY.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/SUPPORT.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/bench/README.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/cli/testguard.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/docs-canonical/ARCHITECTURE.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/docs-canonical/DATA-MODEL.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/docs-canonical/ENVIRONMENT.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/docs-canonical/PRIOR-ART.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/docs-canonical/REQUIREMENTS.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/docs-canonical/SECURITY.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/docs-canonical/TEST-SPEC.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer/.gitignore +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer/README.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer/expected.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer/package.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer/src/export.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer/src/redact.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer/test/flaky.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer/test/redact.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer/testguard.claims.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer/vitest.config.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-jest/.gitignore +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-jest/README.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-jest/expected.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-jest/jest.config.js +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-jest/package.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-jest/src/export.js +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-jest/src/redact.js +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-jest/test/redact.test.js +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-jest/testguard.claims.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-playwright/.gitignore +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-playwright/README.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-playwright/expected.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-playwright/package-lock.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-playwright/package.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-python/.gitignore +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-python/README.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-python/demo/__init__.py +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-python/demo/export.py +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-python/demo/orphan.py +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-python/demo/pipeline.py +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-python/demo/redact.py +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-python/expected.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-python/testguard.claims.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-python/tests/test_patched.py +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/fixtures/known-answer-python/tests/test_redact.py +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/baseline.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/brief.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/calibration-websec.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/calibration.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/claims-methods.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/claims.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/evidence-negative-control.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/evidence-persistence-signal.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/evidence-provisional.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/evidence-python.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/evidence-scan-method.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/gate.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/ignore.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/replay.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/examples/status.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/baseline.zero-count.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/calibration.ci-not-wilson.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/calibration.fallback-without-basis.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/calibration.p-not-positives-over-n.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/claims.absolute-path.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/claims.noop-fault.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.persistence-signal-not-a-defender.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.persistence-signal-on-a-non-survivor.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.persistence-signal-without-reason.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/ignore.short-reason.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/replay.nocover-when-no-test-file-remained.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/status.claim-without-file.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/status.invalid-anchor-hidden.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/status.partial-scope-counts-disagree.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/status.surface-counts-disagree.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/conformance/schemas.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/lib/fingerprint.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/lib/validate.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/lib/wilson.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/schemas/baseline.schema.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/schemas/brief.schema.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/schemas/calibration.schema.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/schemas/claims.schema.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/schemas/common.schema.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/schemas/concerns.schema.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/schemas/gate.schema.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/schemas/ignore.schema.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/schemas/replay.schema.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/schemas/status.schema.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/spec/schemas/sweep.schema.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/admit/admit.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/baseline/baseline.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/brief/brief.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/claims/anchors.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/claims/annotations.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/claims/load.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/claims/removed.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/commands/admit.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/commands/baseline.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/commands/brief.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/commands/claims.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/commands/concerns.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/commands/gate.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/commands/init.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/commands/mcp.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/commands/scaffold.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/commands/status.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/evidence/writer.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/gate/changed.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/init/init.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/init/templates/SKILL.md +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/mcp/server.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/mcp/tools.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/classify.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/contention.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/cost.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/independence.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/inject.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/progress.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/pyimports.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/runners/python/_testguard_report.py +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/probe/worktree.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/render.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/replay/label.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/scaffold/producers.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/scaffold/producers.python.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/scaffold/scaffold.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/status/status.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/status/surface.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/supply/concerns.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/supply/feedback.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/supply/persistence.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/supply/savepath.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/supply/select.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/util/glob.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/src/util/hash.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/admit.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/annotations.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/baseline.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/brief.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/calibration.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/claim-selection.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/claims-anchors.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/claims-since.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/classify.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/contention.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/cost.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/evidence-persistence-signal.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/gate.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/gitlab-template.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/glob.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/independence.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/init.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/inject.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/label.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/mcp.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/negative-control.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/one-pager.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/probe-persistence-signal.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/probe.fixture.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/probe.jest.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/probe.playwright.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/probe.python.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/progress.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/pyimports.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/release-approval.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/release-sync.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/replay-no-test-file-left.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/replay-python-defenders.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/replay-verdict.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/replay.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/runner-command.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/runner-playwright.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/samples/playwright-report.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/samples/python-report-collect-error.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/samples/python-report.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/scaffold.python.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/scaffold.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/snapshot.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/status.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/supply-concerns.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/supply-feedback.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/supply-persistence.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/supply-savepath.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/supply-select.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/surface.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/sweep.fixture.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/sweep.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/vitest-config.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/worktree.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/test/writer.test.mjs +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/testguard.ignore.json +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/testguard_cli/__init__.py +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/testguard_cli/wrapper.py +0 -0
- {testguard_cli-0.14.1 → testguard_cli-0.15.0}/vitest.config.mjs +0 -0
|
@@ -31,9 +31,23 @@ try {
|
|
|
31
31
|
const version = run(bin, ['--version'], consumer).trim();
|
|
32
32
|
const claims = run(bin, ['claims', '.'], consumer);
|
|
33
33
|
const draft = JSON.parse(run(bin, ['scaffold', 'src/redact.mjs', '--json'], consumer));
|
|
34
|
+
const installedValidator = join(consumer, 'node_modules', 'testguard-cli', 'spec', 'lib', 'validate.mjs');
|
|
35
|
+
const candidateEvidence = join(root, 'spec', 'conformance', 'examples', 'evidence.json');
|
|
36
|
+
const validatesCandidateEvidence = run(process.execPath, [
|
|
37
|
+
'--input-type=module',
|
|
38
|
+
'-e',
|
|
39
|
+
`import { pathToFileURL } from 'node:url'; import { readFileSync } from 'node:fs';
|
|
40
|
+
const { validate } = await import(pathToFileURL(process.argv[1]));
|
|
41
|
+
const result = validate('evidence', JSON.parse(readFileSync(process.argv[2], 'utf8')));
|
|
42
|
+
if (!result.ok) throw new Error(result.errors.map((error) => \`${'${error.path}: ${error.message}'}\`).join('; '));
|
|
43
|
+
process.stdout.write('ok');`,
|
|
44
|
+
installedValidator,
|
|
45
|
+
candidateEvidence,
|
|
46
|
+
], consumer).trim();
|
|
34
47
|
if (!draft.claims?.length) throw new Error('scaffold produced no claims from the installed tarball');
|
|
35
48
|
if (!/^\d+\.\d+\.\d+/.test(version)) throw new Error(`unexpected --version output: ${version}`);
|
|
36
49
|
if (!/^\d+ claims in /.test(claims)) throw new Error(`claims did not list the fixture:\n${claims}`);
|
|
50
|
+
if (validatesCandidateEvidence !== 'ok') throw new Error('installed package did not validate candidate-format evidence');
|
|
37
51
|
const deps = Object.keys(JSON.parse(run(npm, ['ls', '--omit=dev', '--json', '--depth=0'], consumer)).dependencies ?? {});
|
|
38
52
|
console.log(`install smoke OK — testguard ${version} runs from the installed tarball; consumer deps: ${deps.join(', ')}`);
|
|
39
53
|
} finally {
|
|
@@ -7,6 +7,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.15.0] - 2026-09-29
|
|
11
|
+
|
|
12
|
+
Automated weekly release — everything merged since `v0.14.2`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- feat: harden discovery, replay, and command budgets (#174)
|
|
17
|
+
- chore(homebrew): sha256 for v0.14.2 (#173)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- Runner-native test discovery for Vitest, Jest, and Playwright, with bounded
|
|
23
|
+
output, path validation, immutable universe manifests, exact mixed-runner
|
|
24
|
+
routing, and configuration-closure hashes (including workspace configs,
|
|
25
|
+
package helpers, setup files, and Python collection hooks) that prevent stale
|
|
26
|
+
evidence reuse.
|
|
27
|
+
- Symbol-aware JavaScript/TypeScript defender discovery through named,
|
|
28
|
+
default, namespace, CommonJS, and barrel re-exports. Ambiguity and bounded
|
|
29
|
+
resolver exhaustion fail closed as `defender-discovery-indeterminate`.
|
|
30
|
+
- `probe`, `sweep`, and `replay` accept `--command-budget <ms>` as a
|
|
31
|
+
cooperative measurement deadline distinct from the existing per-run
|
|
32
|
+
`--budget`. It caps asynchronous children and is checked at stage and write
|
|
33
|
+
boundaries; synchronous setup may overrun but cannot publish a partial
|
|
34
|
+
result, so an unattempted suffix can never be reported as clean.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- Runner timeouts now hard-kill the original process group and descendants
|
|
39
|
+
still attributable at timeout. Diagnostics explicitly say cleanup is
|
|
40
|
+
unverified because a previously reparented daemon requires external OS or
|
|
41
|
+
container containment.
|
|
42
|
+
- Generated CommonJS export mutations and unresolved symbol/configuration
|
|
43
|
+
paths fail closed instead of allowing a false `nocover` result.
|
|
44
|
+
- Dirty-tree preflight now resolves the same full primary-plus-owned runner
|
|
45
|
+
universe as measurement, including custom Playwright names and imported
|
|
46
|
+
configuration helpers; discovery failure blocks rather than hiding edits.
|
|
47
|
+
|
|
48
|
+
## [0.14.2] - 2026-09-28
|
|
49
|
+
|
|
50
|
+
Automated weekly release — everything merged since `v0.14.1`.
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- chore(homebrew): sha256 for v0.14.1 (#171)
|
|
55
|
+
|
|
10
56
|
## [0.14.1] - 2026-09-27
|
|
11
57
|
|
|
12
58
|
Automated weekly release — everything merged since `v0.14.0`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: testguard-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.0
|
|
4
4
|
Summary: Breaks your code on purpose and reports every promise your tests did not notice breaking. Mutation testing bound to the claims a project makes — vitest, jest, Playwright, pytest and unittest. Ships the Node.js CLI (requires Node.js 20+).
|
|
5
5
|
Project-URL: Homepage, https://github.com/raccioly/testguard
|
|
6
6
|
Project-URL: Documentation, https://github.com/raccioly/testguard#readme
|
|
@@ -151,9 +151,9 @@ raises coverage. TestGuard admits it because it fails when the claim is false.
|
|
|
151
151
|
| npm | `npm i -D testguard-cli` then `npx testguard probe` |
|
|
152
152
|
| pip | `pip install testguard-cli` then `testguard probe` (needs Node ≥ 20) |
|
|
153
153
|
| Homebrew | `brew tap raccioly/tap && brew install testguard` |
|
|
154
|
-
| GitHub Action | `uses: raccioly/testguard@v0.
|
|
154
|
+
| GitHub Action | `uses: raccioly/testguard@v0.15.0` — see [`action.yml`](./action.yml) |
|
|
155
155
|
| pre-commit | `repo: https://github.com/raccioly/testguard`, hooks `testguard-claims`, `testguard-probe` |
|
|
156
|
-
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.
|
|
156
|
+
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.15.0/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
|
|
157
157
|
|
|
158
158
|
Projects that set `min-release-age` in `.npmrc` cannot see a version published
|
|
159
159
|
less than that many days ago (`ENOVERSIONS`); install that one with
|
|
@@ -687,12 +687,12 @@ detected base that does not resolve is a warning for `status` and `brief`
|
|
|
687
687
|
# GitHub Actions
|
|
688
688
|
- uses: actions/checkout@v4
|
|
689
689
|
with: { fetch-depth: 0 }
|
|
690
|
-
- uses: raccioly/testguard@v0.
|
|
690
|
+
- uses: raccioly/testguard@v0.15.0
|
|
691
691
|
with: { command: gate }
|
|
692
692
|
|
|
693
693
|
# GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
|
|
694
694
|
include:
|
|
695
|
-
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.
|
|
695
|
+
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.15.0/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
696
696
|
inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
|
|
697
697
|
```
|
|
698
698
|
|
|
@@ -718,6 +718,18 @@ talks to the network; only the job posts, and only when told to.
|
|
|
718
718
|
- **Never optimistic.** A timeout, a load failure, a mixed N-run result, a
|
|
719
719
|
flaky defender or a missing anchor is reported as unproven, never rounded
|
|
720
720
|
toward green. The one pass is `killed`; everything else gates.
|
|
721
|
+
- **Per-run and whole-command time are separate.** `--budget` limits each
|
|
722
|
+
runner invocation. `--command-budget` is a cooperative deadline for the
|
|
723
|
+
complete `probe`, `sweep`, or `replay` measurement: asynchronous children
|
|
724
|
+
receive only the remaining time, and stage/write boundaries re-check it.
|
|
725
|
+
Synchronous setup may overrun the nominal deadline, but once expiry is
|
|
726
|
+
observed TestGuard exits `2` and writes no new result document; a completed
|
|
727
|
+
prefix is not presented as though the unattempted suffix were clean.
|
|
728
|
+
- **Configured discovery is evidence.** Vitest, Jest, and Playwright enumerate
|
|
729
|
+
their own configured files; TestGuard validates and hashes that immutable
|
|
730
|
+
universe instead of guessing with filename globs. Automatic JavaScript and
|
|
731
|
+
TypeScript discovery follows the imported symbol through barrels. Ambiguous
|
|
732
|
+
or unbounded resolution is `unverifiable`, never a flattering `nocover`.
|
|
721
733
|
- **One exact-pinned runtime dependency** (`ajv`), Node ≥ 20, MIT.
|
|
722
734
|
|
|
723
735
|
### Prior art, and what was taken from where
|
|
@@ -127,9 +127,9 @@ raises coverage. TestGuard admits it because it fails when the claim is false.
|
|
|
127
127
|
| npm | `npm i -D testguard-cli` then `npx testguard probe` |
|
|
128
128
|
| pip | `pip install testguard-cli` then `testguard probe` (needs Node ≥ 20) |
|
|
129
129
|
| Homebrew | `brew tap raccioly/tap && brew install testguard` |
|
|
130
|
-
| GitHub Action | `uses: raccioly/testguard@v0.
|
|
130
|
+
| GitHub Action | `uses: raccioly/testguard@v0.15.0` — see [`action.yml`](./action.yml) |
|
|
131
131
|
| pre-commit | `repo: https://github.com/raccioly/testguard`, hooks `testguard-claims`, `testguard-probe` |
|
|
132
|
-
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.
|
|
132
|
+
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.15.0/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
|
|
133
133
|
|
|
134
134
|
Projects that set `min-release-age` in `.npmrc` cannot see a version published
|
|
135
135
|
less than that many days ago (`ENOVERSIONS`); install that one with
|
|
@@ -663,12 +663,12 @@ detected base that does not resolve is a warning for `status` and `brief`
|
|
|
663
663
|
# GitHub Actions
|
|
664
664
|
- uses: actions/checkout@v4
|
|
665
665
|
with: { fetch-depth: 0 }
|
|
666
|
-
- uses: raccioly/testguard@v0.
|
|
666
|
+
- uses: raccioly/testguard@v0.15.0
|
|
667
667
|
with: { command: gate }
|
|
668
668
|
|
|
669
669
|
# GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
|
|
670
670
|
include:
|
|
671
|
-
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.
|
|
671
|
+
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.15.0/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
672
672
|
inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
|
|
673
673
|
```
|
|
674
674
|
|
|
@@ -694,6 +694,18 @@ talks to the network; only the job posts, and only when told to.
|
|
|
694
694
|
- **Never optimistic.** A timeout, a load failure, a mixed N-run result, a
|
|
695
695
|
flaky defender or a missing anchor is reported as unproven, never rounded
|
|
696
696
|
toward green. The one pass is `killed`; everything else gates.
|
|
697
|
+
- **Per-run and whole-command time are separate.** `--budget` limits each
|
|
698
|
+
runner invocation. `--command-budget` is a cooperative deadline for the
|
|
699
|
+
complete `probe`, `sweep`, or `replay` measurement: asynchronous children
|
|
700
|
+
receive only the remaining time, and stage/write boundaries re-check it.
|
|
701
|
+
Synchronous setup may overrun the nominal deadline, but once expiry is
|
|
702
|
+
observed TestGuard exits `2` and writes no new result document; a completed
|
|
703
|
+
prefix is not presented as though the unattempted suffix were clean.
|
|
704
|
+
- **Configured discovery is evidence.** Vitest, Jest, and Playwright enumerate
|
|
705
|
+
their own configured files; TestGuard validates and hashes that immutable
|
|
706
|
+
universe instead of guessing with filename globs. Automatic JavaScript and
|
|
707
|
+
TypeScript discovery follows the imported symbol through barrels. Ambiguous
|
|
708
|
+
or unbounded resolution is `unverifiable`, never a flattering `nocover`.
|
|
697
709
|
- **One exact-pinned runtime dependency** (`ajv`), Node ≥ 20, MIT.
|
|
698
710
|
|
|
699
711
|
### Prior art, and what was taken from where
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
<div class="page">
|
|
145
145
|
<div class="masthead">
|
|
146
146
|
<div class="wordmark">Test<span class="mark">Guard</span></div>
|
|
147
|
-
<div class="meta">Technical Brief · v0.
|
|
147
|
+
<div class="meta">Technical Brief · v0.15.0 · MIT</div>
|
|
148
148
|
</div>
|
|
149
149
|
|
|
150
150
|
<h1>Your tests pass. That is not the same as your tests working.</h1>
|
|
Binary file
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testguard-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "testguard-cli",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.15.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"ajv": "8.20.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testguard-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Breaks your code on purpose and reports every promise your tests did not notice breaking. Mutation testing bound to the claims a project makes — vitest, jest, Playwright, pytest and unittest.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Include it from a project's .gitlab-ci.yml, pinned to a release:
|
|
4
4
|
#
|
|
5
5
|
# include:
|
|
6
|
-
# - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.
|
|
6
|
+
# - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.15.0/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
7
7
|
# inputs:
|
|
8
8
|
# dir: backend # where testguard.claims.json lives (default ".")
|
|
9
9
|
# post_note: true # post the brief as a merge-request note (needs TESTGUARD_GITLAB_TOKEN, api scope)
|
|
@@ -29,7 +29,7 @@ spec:
|
|
|
29
29
|
inputs:
|
|
30
30
|
version:
|
|
31
31
|
description: testguard-cli version to run
|
|
32
|
-
default: "0.
|
|
32
|
+
default: "0.15.0"
|
|
33
33
|
dir:
|
|
34
34
|
description: directory whose test runner is invoked (where testguard.claims.json lives)
|
|
35
35
|
default: "."
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
class Testguard < Formula
|
|
16
16
|
desc "Proves a test suite defends the claims a project makes"
|
|
17
17
|
homepage "https://github.com/raccioly/testguard"
|
|
18
|
-
url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.
|
|
19
|
-
sha256 "
|
|
18
|
+
url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.15.0.tgz"
|
|
19
|
+
sha256 "89faee9b6045550c33cd75e634215c9c698e29361aa32f5cc2b896e82a894e87"
|
|
20
20
|
license "MIT"
|
|
21
21
|
|
|
22
22
|
depends_on "node"
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "testguard-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.15.0"
|
|
8
8
|
description = "Breaks your code on purpose and reports every promise your tests did not notice breaking. Mutation testing bound to the claims a project makes — vitest, jest, Playwright, pytest and unittest. Ships the Node.js CLI (requires Node.js 20+)."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -12,7 +12,7 @@ The verdict set is closed. Only one value is a pass.
|
|
|
12
12
|
| `killed` | Fault applied; every one of N probe runs failed with a genuine assertion failure, on a defender set that was green N/N unmodified. | no |
|
|
13
13
|
| `survived` | Fault applied; every one of N probe runs passed. The claim is **unproven**. | **yes** |
|
|
14
14
|
| `nocover` | No defending test exists: the declared globs resolve to nothing, or no test imports the subject. Worse than `survived` — nothing was even tried. | **yes** |
|
|
15
|
-
| `unverifiable` | The claim could not be probed: the fault's anchor is missing or ambiguous, its defenders failed to load (`defenders-failed-to-load`), probing it threw (`probe-error`), or the defenders do not execute the subject at all (`subject-not-executed`). Carries a `reason`. A loud, gating verdict — a claim that cannot be probed is not "skipped", it is undefended until someone fixes the fault or the defenders. Never confused with `flaky-defender`, which requires tests that *ran*. | **yes** |
|
|
15
|
+
| `unverifiable` | The claim could not be probed: the fault's anchor is missing or ambiguous, configured or symbol-level defender discovery was indeterminate (`defender-discovery-indeterminate`), its defenders failed to load (`defenders-failed-to-load`), probing it threw (`probe-error`), or the defenders do not execute the subject at all (`subject-not-executed`). Carries a `reason`. A loud, gating verdict — a claim that cannot be probed is not "skipped", it is undefended until someone fixes the fault or the defenders. Never confused with `flaky-defender`, which requires tests that *ran*. | **yes** |
|
|
16
16
|
| `timeout` | Probe run exceeded its budget. Not counted as a kill; the pessimistic reading is the safe one because flakiness biases the metric optimistically. | **yes** |
|
|
17
17
|
| `fault-invalid` | The replacement does not load or compile. A bad fault, not a detection. | **yes** |
|
|
18
18
|
| `flaky-defender` | Defenders were not green N/N on unmodified source (`defenders-not-green`), or the N probe runs disagreed with each other (`inconsistent-probe`). Either way no verdict about the fault can be trusted; fix the defenders first. | **yes** |
|
|
@@ -151,7 +151,43 @@ Rules that follow from the table:
|
|
|
151
151
|
can name the file the interpreter loaded) answers the same question more
|
|
152
152
|
precisely for one language; where both are present they must agree.
|
|
153
153
|
|
|
154
|
-
13. **
|
|
154
|
+
13. **The configured runner defines the test universe.** For JavaScript and
|
|
155
|
+
TypeScript, defender discovery, baseline runs and escalation use the exact
|
|
156
|
+
file set returned by the resolved runner's native listing protocol. A
|
|
157
|
+
resolvable runner whose configuration cannot load, whose listing times out
|
|
158
|
+
or is malformed, or whose reported paths escape the project is a
|
|
159
|
+
precondition failure. It never falls back to built-in filename globs and
|
|
160
|
+
never becomes `nocover`. An explicitly selected runner that lists zero
|
|
161
|
+
files is also a precondition failure; `auto` may continue past a successful
|
|
162
|
+
empty listing, but must fail when every eligible runner is empty.
|
|
163
|
+
`inputs.testUniverseHash` binds the normalized file set, runner identity,
|
|
164
|
+
and hashed configuration dependencies. Those dependencies include absent
|
|
165
|
+
root candidates, nested workspace configs, Python collection hooks, and the
|
|
166
|
+
bounded static closure of local or package-imported config helpers and setup
|
|
167
|
+
files; creating or editing any of them forces remeasurement. Evidence that
|
|
168
|
+
predates this hash, or whose hash
|
|
169
|
+
changed, is readable but never reusable for a current native discovery run.
|
|
170
|
+
When several configured runners list defenders, exact manifest membership
|
|
171
|
+
assigns each file. An owned runner takes precedence over the project runner;
|
|
172
|
+
competing owned runners or a declared defender absent from every manifest
|
|
173
|
+
are precondition failures, never heuristic routing decisions.
|
|
174
|
+
|
|
175
|
+
14. **Barrel resolution follows symbols, not module reachability.** Automatic
|
|
176
|
+
discovery may traverse a re-export chain only for the binding the test
|
|
177
|
+
actually imports. Importing an unrelated symbol from a broad barrel does
|
|
178
|
+
not defend every file the barrel exports. Renames, explicit exports and
|
|
179
|
+
star exports are resolved deterministically; type-only paths do not count.
|
|
180
|
+
Ambiguous origins, unsupported runtime-generated exports, graph-limit
|
|
181
|
+
exhaustion, and originless cycles are `unverifiable` with reason
|
|
182
|
+
`defender-discovery-indeterminate`, never `nocover`. A declared
|
|
183
|
+
`defendedBy` remains authoritative. `inputs.discoveryHashes` binds every
|
|
184
|
+
candidate test, intermediate barrel, and resolver-configuration file
|
|
185
|
+
consulted by automatic discovery. Negative candidates are dependencies too:
|
|
186
|
+
editing a previously unrelated test so it begins importing the target must
|
|
187
|
+
invalidate an earlier `nocover`. Absent or changed dependencies force
|
|
188
|
+
remeasurement.
|
|
189
|
+
|
|
190
|
+
15. **Fault injection is one method, not the definition.** A claim declares
|
|
155
191
|
what must be true; a probe declares how a tool would try to make it false,
|
|
156
192
|
and `method` says which way. `fault-injection` — mutate the source, run the
|
|
157
193
|
claim's defenders, confirm over N runs — is the only method specified in
|
|
@@ -184,6 +220,32 @@ Rules that follow from the table:
|
|
|
184
220
|
The verdict set stays closed and shared. What each method may legitimately
|
|
185
221
|
report is part of defining it.
|
|
186
222
|
|
|
223
|
+
## Per-run and whole-command budgets are different
|
|
224
|
+
|
|
225
|
+
`--budget` remains the wall-clock ceiling for one runner invocation. An
|
|
226
|
+
optional `--command-budget` is a cooperative deadline for the complete
|
|
227
|
+
`probe`, `sweep`, or `replay` measurement. Every asynchronous child runner and
|
|
228
|
+
discovery process is given no more than the command's remaining time, and the
|
|
229
|
+
deadline is checked again at stage boundaries and immediately before any
|
|
230
|
+
result is written. Synchronous filesystem, Git, parsing, and cleanup work is
|
|
231
|
+
not preemptible in portable Node.js and may finish after the nominal deadline;
|
|
232
|
+
that overrun grants no authority to publish a result.
|
|
233
|
+
|
|
234
|
+
Exhausting the whole-command budget is not a test verdict and is never a
|
|
235
|
+
successful partial measurement. The command exits `2` and writes no new
|
|
236
|
+
result document; any previous document remains untouched. Cleanup may finish
|
|
237
|
+
after the deadline, but no record from the incomplete operation becomes
|
|
238
|
+
evidence. This is deliberately stricter than a per-run timeout: publishing
|
|
239
|
+
the completed prefix would let the unattempted suffix look clean.
|
|
240
|
+
|
|
241
|
+
Timeout cleanup is best effort, not portable process containment. TestGuard
|
|
242
|
+
hard-kills the runner's original process group and every descendant still
|
|
243
|
+
attributable through the process table. A daemon that detached, reparented,
|
|
244
|
+
and disappeared from that ancestry before timeout may survive, so timeout
|
|
245
|
+
diagnostics state `cleanup-unverified`. Such a run remains a timeout and never
|
|
246
|
+
becomes evidence of detection. Daemonizing or untrusted commands require an
|
|
247
|
+
OS or container boundary that TestGuard does not claim to provide.
|
|
248
|
+
|
|
187
249
|
## Replay reports; it never gates
|
|
188
250
|
|
|
189
251
|
A replayed bug is history. It escaped, by definition, which means the suite
|
|
@@ -198,7 +260,7 @@ Its verdicts mirror the probe's, for the same reasons:
|
|
|
198
260
|
| `caught` | a remaining test failed **by assertion** on the reverted source, every run. The suite knew. |
|
|
199
261
|
| `blind` | the suite stayed green on known-broken code. |
|
|
200
262
|
| `nocover` | no test imports the reverted files, **and at least one test file remained to ask**. Worse than `blind`: nothing was even tried. |
|
|
201
|
-
| `unverifiable` | the revert did not apply, the suite failed to load, it timed out, or removing the fix's own test left **no test file at all** (`the-fix-shipped-the-only-test-file`). Carries a `reason`. |
|
|
263
|
+
| `unverifiable` | the revert did not apply, configured or symbol-level defender discovery was indeterminate (`defender-discovery-indeterminate`), the suite failed to load, it timed out, or removing the fix's own test left **no test file at all** (`the-fix-shipped-the-only-test-file`). Carries a `reason`. |
|
|
202
264
|
| `flaky` | the runs disagreed. A flaky failure reads as "the suite caught it", so flakiness biases this metric **optimistically** — mixed runs are never `caught`. |
|
|
203
265
|
|
|
204
266
|
Two rules that follow:
|
|
@@ -39,6 +39,13 @@ Shared definitions (verdicts, fault classes, provenance, annotations) live in
|
|
|
39
39
|
which verdicts turn CI red, baseline and delta rules, exit codes — is in
|
|
40
40
|
[`GATE-SEMANTICS.md`](GATE-SEMANTICS.md).
|
|
41
41
|
|
|
42
|
+
Operational completeness is contractual too. `--budget` bounds one runner
|
|
43
|
+
invocation; `--command-budget` is a cooperative deadline for a complete probe,
|
|
44
|
+
sweep, or replay measurement. It caps asynchronous child processes, is checked
|
|
45
|
+
at stage and write boundaries, and produces no document once observed expired.
|
|
46
|
+
Synchronous setup may overrun but cannot authorize a partial result. The exact
|
|
47
|
+
rule is in `GATE-SEMANTICS.md`.
|
|
48
|
+
|
|
42
49
|
Schemas are JSON Schema 2020-12 and identified as `urn:claimspec:v1:<kind>`.
|
|
43
50
|
|
|
44
51
|
## Conformance
|
|
@@ -165,7 +165,9 @@
|
|
|
165
165
|
"targetHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
166
166
|
"defenderHashes": {
|
|
167
167
|
"test/redact.test.mjs": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
|
168
|
-
}
|
|
168
|
+
},
|
|
169
|
+
"testUniverseHash": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
|
170
|
+
"discoveryHashes": {}
|
|
169
171
|
},
|
|
170
172
|
"rank": {
|
|
171
173
|
"score": 9.5,
|
|
@@ -641,6 +641,20 @@
|
|
|
641
641
|
"additionalProperties": {
|
|
642
642
|
"$ref": "urn:claimspec:v1:common#/$defs/sha256"
|
|
643
643
|
}
|
|
644
|
+
},
|
|
645
|
+
"testUniverseHash": {
|
|
646
|
+
"description": "Canonical hash of the runner-native test universe and the configuration files that produced it. Optional for backward compatibility; current probes emit it, and evidence without it is never reusable against a configured discovery run.",
|
|
647
|
+
"$ref": "urn:claimspec:v1:common#/$defs/sha256"
|
|
648
|
+
},
|
|
649
|
+
"discoveryHashes": {
|
|
650
|
+
"description": "Content hashes of candidate tests, intermediate barrels, and resolver-configuration files consulted by automatic defender discovery. This includes negative candidates: a test edited to begin importing the target must invalidate an earlier `nocover`. Optional for backward compatibility; a changed or absent dependency forces remeasurement.",
|
|
651
|
+
"type": "object",
|
|
652
|
+
"propertyNames": {
|
|
653
|
+
"$ref": "urn:claimspec:v1:common#/$defs/repoPath"
|
|
654
|
+
},
|
|
655
|
+
"additionalProperties": {
|
|
656
|
+
"$ref": "urn:claimspec:v1:common#/$defs/sha256"
|
|
657
|
+
}
|
|
644
658
|
}
|
|
645
659
|
},
|
|
646
660
|
"additionalProperties": false
|
|
@@ -44,6 +44,7 @@ probe
|
|
|
44
44
|
--claims <path> claims file (default: <dir>/testguard.claims.json)
|
|
45
45
|
--confirm <n> runs per verdict (default: 3)
|
|
46
46
|
--budget <ms> wall clock per run (default: 120000)
|
|
47
|
+
--command-budget <ms> cooperative measurement deadline for probe, sweep, or replay. Caps async children; expiry exits 2 and writes no partial result
|
|
47
48
|
--out <path> evidence file (default: <dir>/.testguard/evidence.json)
|
|
48
49
|
--baseline <path> baseline to gate against (default: <dir>/.testguard/baseline.json if present)
|
|
49
50
|
--severity <level> gate only at or above (default: low)
|
|
@@ -84,7 +85,7 @@ sweep --changed <ref> (required unless --save-paths; CI bases and a safe lo
|
|
|
84
85
|
--save-paths sweep every file that WRITES TO STORAGE instead of the diff, and report the surface as the
|
|
85
86
|
denominator: "120 writes across 38 files, swept 12". A clean sweep over an unstated
|
|
86
87
|
denominator is a sample of unknown size, not a guarantee that saving works
|
|
87
|
-
--include-dirty --exclude <glob> --confirm <n> --budget <ms> --max <n> --out <path> --json
|
|
88
|
+
--include-dirty --exclude <glob> --confirm <n> --budget <ms> --command-budget <ms> --max <n> --out <path> --json
|
|
88
89
|
the cold start: no claim and no concern needed. Everything it proposes is a DRAFT — it never writes testguard.claims.json,
|
|
89
90
|
and its evidence never replaces .testguard/evidence.json. exit 1 on a fault that survived, or a file no test imports
|
|
90
91
|
shapes: if-guard → if (false) · single-line guard/mutation removed · return <check> → return true
|
|
@@ -96,6 +97,7 @@ mcp no options. JSON-RPC 2.0 over stdio; five READ-ONLY tools (status, br
|
|
|
96
97
|
replay --since <range> commit range to search for fix commits (HEAD~50..HEAD, a tag, origin/main..HEAD)
|
|
97
98
|
--max <n> replay at most n fixes (they are slow: one worktree and N runs each)
|
|
98
99
|
--confirm <n> runs per verdict (default 3); a flaky failure reads as "the suite caught it", so mixed runs are never caught
|
|
100
|
+
--command-budget <ms> cooperative measurement deadline; expiry writes neither replay nor calibration, so an unmeasured suffix cannot look clean
|
|
99
101
|
--out <path> replay document (default: <dir>/.testguard/replay.json); the calibration goes beside it
|
|
100
102
|
reports, never gates: a bug that escaped is history, not a regression in this change
|
|
101
103
|
· one-line JSX element removed · on<Event> handler prop dropped
|
|
@@ -142,6 +144,7 @@ export async function main(argv, io = { out: (s) => process.stdout.write(s + '\n
|
|
|
142
144
|
claims: { type: 'string' },
|
|
143
145
|
confirm: { type: 'string', default: '3' },
|
|
144
146
|
budget: { type: 'string', default: '120000' },
|
|
147
|
+
'command-budget': { type: 'string' },
|
|
145
148
|
out: { type: 'string' },
|
|
146
149
|
evidence: { type: 'string' },
|
|
147
150
|
baseline: { type: 'string' },
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { PreconditionError } from './probe/worktree.mjs';
|
|
2
|
+
import { performance } from 'node:perf_hooks';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A cooperative measurement deadline shared by every stage of one command.
|
|
6
|
+
*
|
|
7
|
+
* Per-run budgets answer "how long may this runner invocation take?". This
|
|
8
|
+
* deadline answers the different question "may the whole command still
|
|
9
|
+
* produce a result?". Async child processes receive no more than the
|
|
10
|
+
* remaining time; synchronous setup can overrun because JavaScript cannot
|
|
11
|
+
* preempt it, so every stage boundary and every write boundary checks again.
|
|
12
|
+
* Expiry is deliberately not a verdict: a partial result would make the
|
|
13
|
+
* unattempted suffix look clean.
|
|
14
|
+
*/
|
|
15
|
+
export class CommandBudget {
|
|
16
|
+
constructor(limitMs, { now = () => performance.now() } = {}) {
|
|
17
|
+
if (!Number.isInteger(limitMs) || limitMs < 1) throw new TypeError('command budget must be a positive integer');
|
|
18
|
+
this.limitMs = limitMs;
|
|
19
|
+
this.now = now;
|
|
20
|
+
this.startedAtMs = now();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
elapsedMs() {
|
|
24
|
+
return Math.max(0, this.now() - this.startedAtMs);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
remainingMs() {
|
|
28
|
+
return Math.max(0, this.limitMs - this.elapsedMs());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
assertOpen() {
|
|
32
|
+
if (this.remainingMs() === 0) {
|
|
33
|
+
throw new PreconditionError(`command budget of ${this.limitMs}ms exhausted; no partial result was written`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Never let a child process outlive the command's remaining measurement window. */
|
|
38
|
+
runBudget(perRunMs) {
|
|
39
|
+
this.assertOpen();
|
|
40
|
+
return Math.max(1, Math.min(perRunMs, Math.floor(this.remainingMs())));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createCommandBudget(limitMs, options) {
|
|
45
|
+
return limitMs === undefined ? undefined : new CommandBudget(limitMs, options);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function parseCommandBudget(value) {
|
|
49
|
+
if (value === undefined) return undefined;
|
|
50
|
+
const parsed = Number(value);
|
|
51
|
+
return Number.isInteger(parsed) && parsed >= 1000 ? parsed : null;
|
|
52
|
+
}
|
|
@@ -10,6 +10,7 @@ import { resolveChangedRef, withChangedRef } from '../gate/changed.mjs';
|
|
|
10
10
|
import { costReport, renderCost } from '../probe/cost.mjs';
|
|
11
11
|
import { progressMode, stageReporter, clearStageLine, recordEvent, isProgressMode, progressStream } from '../probe/progress.mjs';
|
|
12
12
|
import { validate } from '../../spec/lib/validate.mjs';
|
|
13
|
+
import { createCommandBudget, parseCommandBudget } from '../command-budget.mjs';
|
|
13
14
|
export const provisionalEvidencePath = (projectDir) => join(projectDir, '.testguard', 'evidence-provisional.json');
|
|
14
15
|
|
|
15
16
|
export const evidencePath = (projectDir) => join(projectDir, '.testguard', 'evidence.json');
|
|
@@ -36,10 +37,16 @@ export function partialScope(only, records) {
|
|
|
36
37
|
export async function probeCommand({ projectDir, values, version }, io) {
|
|
37
38
|
const confirmRuns = Number(values.confirm);
|
|
38
39
|
const budgetMs = Number(values.budget);
|
|
40
|
+
const commandBudgetMs = parseCommandBudget(values['command-budget']);
|
|
39
41
|
if (!Number.isInteger(confirmRuns) || confirmRuns < 1 || !Number.isInteger(budgetMs) || budgetMs < 1000) {
|
|
40
42
|
io.err('--confirm must be a positive integer and --budget at least 1000');
|
|
41
43
|
return 3;
|
|
42
44
|
}
|
|
45
|
+
if (commandBudgetMs === null) {
|
|
46
|
+
io.err('--command-budget must be at least 1000 milliseconds');
|
|
47
|
+
return 3;
|
|
48
|
+
}
|
|
49
|
+
const commandBudget = createCommandBudget(commandBudgetMs);
|
|
43
50
|
if (values.progress && !isProgressMode(values.progress)) {
|
|
44
51
|
io.err(`--progress must be one of auto, tty, plain, ndjson, none (got ${values.progress})`);
|
|
45
52
|
return 3;
|
|
@@ -74,6 +81,7 @@ export async function probeCommand({ projectDir, values, version }, io) {
|
|
|
74
81
|
previous,
|
|
75
82
|
confirmRuns,
|
|
76
83
|
budgetMs,
|
|
84
|
+
commandBudget,
|
|
77
85
|
mode: values['in-place'] ? 'in-place' : 'worktree',
|
|
78
86
|
ref: values.ref ?? 'HEAD',
|
|
79
87
|
refExplicit: values.ref !== undefined,
|
|
@@ -101,6 +109,7 @@ export async function probeCommand({ projectDir, values, version }, io) {
|
|
|
101
109
|
if (values.verbose || r.verdict !== 'killed') io.out(renderRecord(r, { provisional, showDiscovered: firstOfClaim }) + (r.reusedFrom ? ' (reused)' : ''));
|
|
102
110
|
},
|
|
103
111
|
});
|
|
112
|
+
commandBudget?.assertOpen();
|
|
104
113
|
writeSpecDoc('evidence', outPath, evidence);
|
|
105
114
|
|
|
106
115
|
const g = gate(evidence.records, baseline, { severityFloor: values.severity });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { dirname, join, resolve } from 'node:path';
|
|
2
2
|
import { replay, calibrationFrom, renderReplay } from '../replay/replay.mjs';
|
|
3
3
|
import { writeSpecDoc } from '../evidence/writer.mjs';
|
|
4
|
+
import { createCommandBudget, parseCommandBudget } from '../command-budget.mjs';
|
|
4
5
|
|
|
5
6
|
export const replayPath = (projectDir) => join(projectDir, '.testguard', 'replay.json');
|
|
6
7
|
export const calibrationPath = (projectDir) => join(projectDir, '.testguard', 'calibration.json');
|
|
@@ -13,17 +14,24 @@ export async function replayCommand({ projectDir, values, version }, io) {
|
|
|
13
14
|
}
|
|
14
15
|
const confirmRuns = Number(values.confirm);
|
|
15
16
|
const budgetMs = Number(values.budget);
|
|
17
|
+
const commandBudgetMs = parseCommandBudget(values['command-budget']);
|
|
16
18
|
const limit = values.max ? Number(values.max) : undefined;
|
|
17
19
|
if (!Number.isInteger(confirmRuns) || confirmRuns < 1 || !Number.isInteger(budgetMs) || budgetMs < 1000) {
|
|
18
20
|
io.err('--confirm must be a positive integer and --budget at least 1000');
|
|
19
21
|
return 3;
|
|
20
22
|
}
|
|
23
|
+
if (commandBudgetMs === null) {
|
|
24
|
+
io.err('--command-budget must be at least 1000 milliseconds');
|
|
25
|
+
return 3;
|
|
26
|
+
}
|
|
27
|
+
const commandBudget = createCommandBudget(commandBudgetMs);
|
|
21
28
|
|
|
22
29
|
const doc = await replay({
|
|
23
30
|
projectDir,
|
|
24
31
|
range,
|
|
25
32
|
confirmRuns,
|
|
26
33
|
budgetMs,
|
|
34
|
+
commandBudget,
|
|
27
35
|
runnerCommand: values['runner-cmd'],
|
|
28
36
|
runnerName: values.runner,
|
|
29
37
|
nodeModules: values['node-modules'] ? resolve(values['node-modules']) : process.env.TESTGUARD_NODE_MODULES,
|
|
@@ -36,12 +44,17 @@ export async function replayCommand({ projectDir, values, version }, io) {
|
|
|
36
44
|
});
|
|
37
45
|
|
|
38
46
|
const outPath = values.out ? resolve(values.out) : replayPath(projectDir);
|
|
39
|
-
writeSpecDoc('replay', outPath, doc);
|
|
40
47
|
const calibration = calibrationFrom(doc, { toolVersion: version });
|
|
41
48
|
// Beside the replay document, whatever --out says: the two are one result,
|
|
42
49
|
// and splitting them across directories loses the pairing — and leaves a
|
|
43
50
|
// file behind in a repository the run is only meant to read.
|
|
44
51
|
const calPath = values.baseline ? resolve(values.baseline) : values.out ? join(dirname(outPath), 'calibration.json') : calibrationPath(projectDir);
|
|
52
|
+
// Replay and calibration are one result. Cross the deadline boundary only
|
|
53
|
+
// after both complete documents exist in memory; serialization is then the
|
|
54
|
+
// documented non-interruptible final step, so expiry can never leave half
|
|
55
|
+
// of the pair as though it were complete.
|
|
56
|
+
commandBudget?.assertOpen();
|
|
57
|
+
writeSpecDoc('replay', outPath, doc);
|
|
45
58
|
writeSpecDoc('calibration', calPath, calibration);
|
|
46
59
|
|
|
47
60
|
if (values.json) {
|