testguard-cli 0.10.0__tar.gz → 0.10.1__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.10.0 → testguard_cli-0.10.1}/CHANGELOG.md +19 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/PKG-INFO +11 -7
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/README.md +10 -6
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/action.yml +1 -1
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/docs/testguard-explained.html +1 -1
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/docs/testguard-explained.pdf +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/docs-canonical/REQUIREMENTS.md +4 -1
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/package-lock.json +2 -2
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/package.json +1 -1
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/packaging/gitlab/testguard.gitlab-ci.yml +2 -2
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/packaging/homebrew/testguard.rb +2 -2
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/pyproject.toml +1 -1
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/GATE-SEMANTICS.md +10 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/status.json +14 -0
- testguard_cli-0.10.1/spec/conformance/invalid/status.partial-scope-counts-disagree.json +31 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/lib/validate.mjs +7 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/schemas/status.schema.json +93 -1
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/cli.mjs +2 -2
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/commands/admit.mjs +10 -2
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/commands/probe.mjs +30 -3
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/commands/scaffold.mjs +10 -1
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/probe.mjs +14 -11
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/admit.test.mjs +4 -1
- testguard_cli-0.10.1/test/claim-selection.test.mjs +74 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/probe.fixture.test.mjs +23 -5
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/scaffold.test.mjs +3 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/testguard.claims.json +128 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.agents/skills/testguard/SKILL.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.codex/hooks.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.docguard.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.docguardignore +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.gitattributes +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.github/dependabot.yml +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.github/scripts/approve-held-runs.sh +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.github/scripts/build-one-pager.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.github/scripts/check-cost-budget.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.github/scripts/check-python-fixture.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.github/scripts/install-smoke.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.github/scripts/sync-release-version.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.github/workflows/auto-merge.yml +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.github/workflows/ci.yml +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.github/workflows/release.yml +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.github/workflows/scheduled-release.yml +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.github/workflows/supply-chain.yml +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.gitignore +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.npmignore +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.npmrc +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.pre-commit-config.yaml +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/.pre-commit-hooks.yaml +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/AGENTS.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/CLAUDE.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/CODE_OF_CONDUCT.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/CONTRIBUTING.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/DRIFT-LOG.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/LICENSE +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/PRIVACY.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/SECURITY.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/SUPPORT.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/bench/README.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/cli/testguard.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/docs-canonical/ARCHITECTURE.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/docs-canonical/DATA-MODEL.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/docs-canonical/ENVIRONMENT.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/docs-canonical/SECURITY.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/docs-canonical/TEST-SPEC.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer/.gitignore +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer/README.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer/expected.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer/package.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer/src/export.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer/src/redact.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer/test/flaky.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer/test/redact.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer/testguard.claims.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer/vitest.config.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-jest/.gitignore +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-jest/README.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-jest/expected.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-jest/jest.config.js +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-jest/package.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-jest/src/export.js +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-jest/src/redact.js +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-jest/test/redact.test.js +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-jest/testguard.claims.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-playwright/.gitignore +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-playwright/README.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-playwright/expected.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-playwright/package-lock.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-playwright/package.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-playwright/test-results/.last-run.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-python/.gitignore +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-python/README.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-python/demo/__init__.py +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-python/demo/export.py +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-python/demo/orphan.py +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-python/demo/pipeline.py +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-python/demo/redact.py +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-python/expected.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-python/testguard.claims.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-python/tests/test_patched.py +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/fixtures/known-answer-python/tests/test_redact.py +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/README.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/baseline.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/brief.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/calibration-websec.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/calibration.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/claims-methods.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/claims.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/evidence-negative-control.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/evidence-provisional.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/evidence-python.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/evidence-scan-method.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/evidence.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/gate.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/ignore.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/examples/replay.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/baseline.zero-count.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/calibration.ci-not-wilson.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/calibration.fallback-without-basis.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/calibration.p-not-positives-over-n.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/claims.absolute-path.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/claims.noop-fault.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/ignore.short-reason.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/status.claim-without-file.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/status.invalid-anchor-hidden.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/status.surface-counts-disagree.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/conformance/schemas.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/lib/fingerprint.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/lib/wilson.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/schemas/baseline.schema.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/schemas/brief.schema.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/schemas/calibration.schema.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/schemas/claims.schema.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/schemas/common.schema.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/schemas/evidence.schema.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/schemas/gate.schema.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/schemas/ignore.schema.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/spec/schemas/replay.schema.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/admit/admit.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/baseline/baseline.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/brief/brief.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/claims/anchors.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/claims/annotations.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/claims/load.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/claims/removed.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/commands/baseline.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/commands/brief.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/commands/claims.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/commands/gate.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/commands/init.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/commands/mcp.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/commands/replay.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/commands/status.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/evidence/writer.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/gate/changed.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/git.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/init/init.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/init/templates/SKILL.md +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/mcp/server.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/mcp/tools.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/attribution.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/classify.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/contention.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/cost.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/discover.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/independence.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/inject.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/mocks.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/progress.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/pyimports.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/rank.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/runners/index.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/runners/jest.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/runners/playwright.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/runners/python/_testguard_report.py +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/runners/python.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/runners/shared.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/runners/vitest.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/probe/worktree.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/render.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/replay/label.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/replay/replay.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/scaffold/producers.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/scaffold/producers.python.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/scaffold/scaffold.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/status/status.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/status/surface.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/util/glob.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/src/util/hash.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/annotations.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/attribution.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/baseline.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/brief.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/calibration.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/claims-anchors.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/claims-since.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/classify.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/contention.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/cost.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/discover.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/gate.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/gitlab-template.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/glob.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/independence.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/init.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/inject.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/label.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/mcp.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/mocks.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/negative-control.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/one-pager.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/probe-error.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/probe-preconditions.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/probe.jest.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/probe.playwright.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/probe.python.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/progress.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/pyimports.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/rank-aliases.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/release-approval.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/release-sync.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/replay-verdict.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/replay.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/runner-argv.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/runner-command.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/runner-playwright.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/runner-python.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/runner-vitest.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/samples/playwright-report.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/samples/python-report-collect-error.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/samples/python-report.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/scaffold.python.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/snapshot.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/status.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/surface.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/vitest-config.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/worktree.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/test/writer.test.mjs +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/testguard.cost-budget.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/testguard.ignore.json +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/testguard_cli/__init__.py +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/testguard_cli/wrapper.py +0 -0
- {testguard_cli-0.10.0 → testguard_cli-0.10.1}/vitest.config.mjs +0 -0
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.10.1] - 2026-09-19
|
|
11
|
+
|
|
12
|
+
Automated weekly release — everything merged since `v0.10.0`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- fix(probe): preserve repeated claim selections (#139)
|
|
17
|
+
- chore(homebrew): sha256 for v0.10.0 (#138)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Repeated `probe --claim` flags no longer collapse silently to the last value.
|
|
23
|
+
Repeated and comma-separated ids are flattened in first-seen order,
|
|
24
|
+
duplicates are collapsed, empty selections fail, and both human and JSON
|
|
25
|
+
partial-run output show requested versus probed ids and counts. Probe JSON is
|
|
26
|
+
now validated against the published status schema before it is emitted. The
|
|
27
|
+
singular `scaffold` and `admit` forms reject ambiguous repeated values.
|
|
28
|
+
|
|
10
29
|
## [0.10.0] - 2026-09-19
|
|
11
30
|
|
|
12
31
|
Automated weekly release — everything merged since `v0.9.4`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: testguard-cli
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.1
|
|
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.10.
|
|
154
|
+
| GitHub Action | `uses: raccioly/testguard@v0.10.1` — 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.10.
|
|
156
|
+
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.10.1/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
|
|
@@ -303,8 +303,12 @@ npx testguard-cli admit test/x.test.ts --claim X # is this test green on HEAD
|
|
|
303
303
|
because it fails when the claim is false.
|
|
304
304
|
|
|
305
305
|
Practical loop: first pass `--no-escalate` (escalation re-runs the whole
|
|
306
|
-
suite N times per survivor); iterate on one claim with `--claim <ID
|
|
307
|
-
|
|
306
|
+
suite N times per survivor); iterate on one claim with `--claim <ID>`, or
|
|
307
|
+
select a batch with commas or repeated flags (`--claim A,B --claim C`).
|
|
308
|
+
Every occurrence is retained in first-seen order, duplicates are collapsed,
|
|
309
|
+
and partial human/JSON output names both the requested and probed counts, so
|
|
310
|
+
a green subset cannot masquerade as the selection the operator asked for.
|
|
311
|
+
Use either `--include-dirty` or `--in-place` (only fault target files must be
|
|
308
312
|
clean there; test files may be dirty), optionally `--confirm 1` for a fast
|
|
309
313
|
**provisional** signal — verdicts print with a `?`, evidence goes to
|
|
310
314
|
`evidence-provisional.json`, and `baseline` refuses it; final pass with
|
|
@@ -493,12 +497,12 @@ detected base that does not resolve is a warning for `status` and `brief`
|
|
|
493
497
|
# GitHub Actions
|
|
494
498
|
- uses: actions/checkout@v4
|
|
495
499
|
with: { fetch-depth: 0 }
|
|
496
|
-
- uses: raccioly/testguard@v0.10.
|
|
500
|
+
- uses: raccioly/testguard@v0.10.1
|
|
497
501
|
with: { command: gate }
|
|
498
502
|
|
|
499
503
|
# GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
|
|
500
504
|
include:
|
|
501
|
-
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.
|
|
505
|
+
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.1/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
502
506
|
inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
|
|
503
507
|
```
|
|
504
508
|
|
|
@@ -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.10.
|
|
130
|
+
| GitHub Action | `uses: raccioly/testguard@v0.10.1` — 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.10.
|
|
132
|
+
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.10.1/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
|
|
@@ -279,8 +279,12 @@ npx testguard-cli admit test/x.test.ts --claim X # is this test green on HEAD
|
|
|
279
279
|
because it fails when the claim is false.
|
|
280
280
|
|
|
281
281
|
Practical loop: first pass `--no-escalate` (escalation re-runs the whole
|
|
282
|
-
suite N times per survivor); iterate on one claim with `--claim <ID
|
|
283
|
-
|
|
282
|
+
suite N times per survivor); iterate on one claim with `--claim <ID>`, or
|
|
283
|
+
select a batch with commas or repeated flags (`--claim A,B --claim C`).
|
|
284
|
+
Every occurrence is retained in first-seen order, duplicates are collapsed,
|
|
285
|
+
and partial human/JSON output names both the requested and probed counts, so
|
|
286
|
+
a green subset cannot masquerade as the selection the operator asked for.
|
|
287
|
+
Use either `--include-dirty` or `--in-place` (only fault target files must be
|
|
284
288
|
clean there; test files may be dirty), optionally `--confirm 1` for a fast
|
|
285
289
|
**provisional** signal — verdicts print with a `?`, evidence goes to
|
|
286
290
|
`evidence-provisional.json`, and `baseline` refuses it; final pass with
|
|
@@ -469,12 +473,12 @@ detected base that does not resolve is a warning for `status` and `brief`
|
|
|
469
473
|
# GitHub Actions
|
|
470
474
|
- uses: actions/checkout@v4
|
|
471
475
|
with: { fetch-depth: 0 }
|
|
472
|
-
- uses: raccioly/testguard@v0.10.
|
|
476
|
+
- uses: raccioly/testguard@v0.10.1
|
|
473
477
|
with: { command: gate }
|
|
474
478
|
|
|
475
479
|
# GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
|
|
476
480
|
include:
|
|
477
|
-
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.
|
|
481
|
+
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.1/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
478
482
|
inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
|
|
479
483
|
```
|
|
480
484
|
|
|
@@ -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.10.
|
|
147
|
+
<div class="meta">Technical Brief · v0.10.1 · 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,6 +1,6 @@
|
|
|
1
1
|
# Requirements
|
|
2
2
|
|
|
3
|
-
<!-- docguard:version 1.
|
|
3
|
+
<!-- docguard:version 1.1.0 -->
|
|
4
4
|
<!-- docguard:status approved -->
|
|
5
5
|
<!-- docguard:last-reviewed 2026-09-19 -->
|
|
6
6
|
<!-- docguard:owner @raccioly -->
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
| FR-13 | Replay historical fix commits to measure whether the suite would have caught them, and calibrate by fault class | P3 |
|
|
28
28
|
| FR-14 | Serve the read-only operating loop over MCP so the loop survives a change of agent harness | P3 |
|
|
29
29
|
| FR-15 | Validate every exact fault anchor and the syntax of supported in-memory replacements before starting a test runner (`claims --check-anchors`), without guessing repairs | P1 |
|
|
30
|
+
| FR-16 | Preserve every repeated or comma-separated `probe --claim` selection in first-seen order, collapse duplicates, and report requested versus probed scope without allowing an empty selection to pass | P1 |
|
|
30
31
|
|
|
31
32
|
## Non-Functional Requirements
|
|
32
33
|
|
|
@@ -101,6 +102,7 @@ fails the pull request naming the unclaimed file, and `status` reports
|
|
|
101
102
|
| FR-13 | `src/replay/` | `test/replay.test.mjs`, `test/calibration.test.mjs`, `TG-REPLAY-FLAKY-IS-NEVER-CAUGHT`, `TG-CALIBRATION-COUNTS-NOCOVER-AS-A-MISS`, `TG-CALIBRATION-COUNTS-ONLY-BUGS` |
|
|
102
103
|
| FR-14 | `src/mcp/` | `test/mcp.test.mjs` |
|
|
103
104
|
| FR-15 | `src/claims/anchors.mjs`, `src/commands/claims.mjs`, `src/status/status.mjs` | `test/claims-anchors.test.mjs`, `test/status.test.mjs`, `TG-ANCHOR-PREFLIGHT-FAILS-FAST` |
|
|
105
|
+
| FR-16 | `src/cli.mjs`, `src/commands/probe.mjs`, `src/probe/probe.mjs`, `src/commands/scaffold.mjs`, `src/commands/admit.mjs` | `test/claim-selection.test.mjs`, `test/probe.fixture.test.mjs`, `TG-REPEATED-CLAIMS-ARE-ALL-PROBED` |
|
|
104
106
|
| NFR-01 | `src/init/init.mjs`, runner resolution | `TG-INIT-HOOK-NO-NETWORK`, `TG-README-HOOK-MATCHES-THE-CODE` (every surface, not only the README), `TG-GITIGNORE-ADVICE-IS-ONE-LIST` |
|
|
105
107
|
| NFR-02 | `package.json` | `npm run test:install` in CI |
|
|
106
108
|
| NFR-03 | `src/probe/inject.mjs`, `src/probe/probe.mjs` | `TG-DIRTY-DEFENDERS-REFUSED`, `TG-IGNORED-DIRTY-RECORDED`, `TG-IN-PLACE-STILL-RAISES-A-MISSING-TARGET`, `TG-CONTROL-RESTORES-THE-SUBJECT` |
|
|
@@ -114,4 +116,5 @@ fails the pull request naming the unclaimed file, and `status` reports
|
|
|
114
116
|
|
|
115
117
|
| Version | Date | Change | Author |
|
|
116
118
|
|---|---|---|---|
|
|
119
|
+
| 1.1.0 | 2026-09-19 | Require lossless, visible partial claim selection | @raccioly |
|
|
117
120
|
| 1.0.0 | 2026-09-18 | First canonical requirements, written against v0.6.0 | @raccioly |
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testguard-cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "testguard-cli",
|
|
9
|
-
"version": "0.10.
|
|
9
|
+
"version": "0.10.1",
|
|
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.10.
|
|
3
|
+
"version": "0.10.1",
|
|
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.10.
|
|
6
|
+
# - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.1/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.10.
|
|
32
|
+
default: "0.10.1"
|
|
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.10.
|
|
19
|
-
sha256 "
|
|
18
|
+
url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.10.1.tgz"
|
|
19
|
+
sha256 "ebb9e4ac0370e0b2cbc52b3e5f24e8426020fd3b5ff9db6b2776ab2744874dab"
|
|
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.10.
|
|
7
|
+
version = "0.10.1"
|
|
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" }
|
|
@@ -436,6 +436,16 @@ new anchor is a semantic edit to the claim and must be reviewed and re-probed.
|
|
|
436
436
|
`unclaimed-changes`, when a change reference is known). Its next action is
|
|
437
437
|
`repair-fault`, naming the first fault and `testguard claims --check-anchors`.
|
|
438
438
|
|
|
439
|
+
## Partial probe scope
|
|
440
|
+
|
|
441
|
+
Every `--claim` occurrence contributes to the selection. Comma-separated ids
|
|
442
|
+
inside each occurrence are flattened in first-seen order and duplicates are
|
|
443
|
+
collapsed. An explicitly empty selection is a usage error, never a successful
|
|
444
|
+
zero-record probe. Partial human output and the `probe --json` run wrapper name
|
|
445
|
+
the requested and actually probed claim ids and counts. Commands whose
|
|
446
|
+
`--claim` is singular (`scaffold` and `admit`) reject repeated or comma-separated
|
|
447
|
+
values instead of silently choosing one.
|
|
448
|
+
|
|
439
449
|
## Severity floor
|
|
440
450
|
|
|
441
451
|
`--severity <level>` gates only findings whose claim severity is at or above
|
|
@@ -18,6 +18,20 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"provisional": false,
|
|
21
|
+
"run": {
|
|
22
|
+
"id": "run-20260917T020000",
|
|
23
|
+
"evidence": ".testguard/evidence-partial.json",
|
|
24
|
+
"provisional": false,
|
|
25
|
+
"records": 4,
|
|
26
|
+
"newSinceBaseline": 1,
|
|
27
|
+
"exitCode": 1,
|
|
28
|
+
"scope": {
|
|
29
|
+
"requestedClaims": ["REDACT-002", "REDACT-001"],
|
|
30
|
+
"requestedCount": 2,
|
|
31
|
+
"probedClaims": ["REDACT-002", "REDACT-001"],
|
|
32
|
+
"probedCount": 2
|
|
33
|
+
}
|
|
34
|
+
},
|
|
21
35
|
"surface": {
|
|
22
36
|
"sourceModules": 3,
|
|
23
37
|
"claimedModules": 2,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"tool": { "name": "testguard", "version": "0.10.0" },
|
|
4
|
+
"generatedAt": "2026-09-19T22:00:00Z",
|
|
5
|
+
"state": "unprobed",
|
|
6
|
+
"next": {
|
|
7
|
+
"action": "probe",
|
|
8
|
+
"command": "testguard probe",
|
|
9
|
+
"why": "Claims have not been probed."
|
|
10
|
+
},
|
|
11
|
+
"counts": { "claims": 2, "faults": 2 },
|
|
12
|
+
"stale": [],
|
|
13
|
+
"invalidFaults": [],
|
|
14
|
+
"changedFaults": [],
|
|
15
|
+
"findings": [],
|
|
16
|
+
"paths": {},
|
|
17
|
+
"run": {
|
|
18
|
+
"id": "run-20260919T220000",
|
|
19
|
+
"evidence": ".testguard/evidence-partial.json",
|
|
20
|
+
"provisional": false,
|
|
21
|
+
"records": 1,
|
|
22
|
+
"newSinceBaseline": 0,
|
|
23
|
+
"exitCode": 0,
|
|
24
|
+
"scope": {
|
|
25
|
+
"requestedClaims": ["A", "B"],
|
|
26
|
+
"requestedCount": 1,
|
|
27
|
+
"probedClaims": ["A"],
|
|
28
|
+
"probedCount": 1
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -250,6 +250,13 @@ const semantic = {
|
|
|
250
250
|
if (!doc.surface.history.available && doc.surface.history.commitsRead !== 0) errors.push({ path: '/surface/history/commitsRead', message: 'unavailable history must report zero commits read' });
|
|
251
251
|
if (new Set(doc.surface.rankedUnclaimed.map((item) => item.file)).size !== doc.surface.rankedUnclaimed.length) errors.push({ path: '/surface/rankedUnclaimed', message: 'ranked unclaimed modules must be unique' });
|
|
252
252
|
}
|
|
253
|
+
if (doc.run?.scope) {
|
|
254
|
+
const scope = doc.run.scope;
|
|
255
|
+
if (scope.requestedCount !== scope.requestedClaims.length) errors.push({ path: '/run/scope/requestedCount', message: 'requestedCount must equal requestedClaims.length' });
|
|
256
|
+
if (scope.probedCount !== scope.probedClaims.length) errors.push({ path: '/run/scope/probedCount', message: 'probedCount must equal probedClaims.length' });
|
|
257
|
+
const requested = new Set(scope.requestedClaims);
|
|
258
|
+
if (scope.probedClaims.some((id) => !requested.has(id))) errors.push({ path: '/run/scope/probedClaims', message: 'every probed claim must have been requested' });
|
|
259
|
+
}
|
|
253
260
|
return errors;
|
|
254
261
|
},
|
|
255
262
|
|
|
@@ -152,6 +152,80 @@
|
|
|
152
152
|
},
|
|
153
153
|
"additionalProperties": false
|
|
154
154
|
},
|
|
155
|
+
"run": {
|
|
156
|
+
"description": "Present on probe --json: the validated status document plus the completed invocation's result. scope is present only for a partial --claim run.",
|
|
157
|
+
"type": "object",
|
|
158
|
+
"required": ["id", "evidence", "provisional", "records", "newSinceBaseline", "exitCode"],
|
|
159
|
+
"properties": {
|
|
160
|
+
"id": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
161
|
+
"evidence": { "type": "string", "minLength": 1, "maxLength": 4096 },
|
|
162
|
+
"provisional": { "type": "boolean" },
|
|
163
|
+
"records": { "type": "integer", "minimum": 0 },
|
|
164
|
+
"newSinceBaseline": { "type": "integer", "minimum": 0 },
|
|
165
|
+
"exitCode": { "enum": [0, 1] },
|
|
166
|
+
"scope": {
|
|
167
|
+
"type": "object",
|
|
168
|
+
"required": ["requestedClaims", "requestedCount", "probedClaims", "probedCount"],
|
|
169
|
+
"properties": {
|
|
170
|
+
"requestedClaims": {
|
|
171
|
+
"type": "array",
|
|
172
|
+
"minItems": 1,
|
|
173
|
+
"uniqueItems": true,
|
|
174
|
+
"items": { "$ref": "urn:claimspec:v1:common#/$defs/identifier" }
|
|
175
|
+
},
|
|
176
|
+
"requestedCount": { "type": "integer", "minimum": 1 },
|
|
177
|
+
"probedClaims": {
|
|
178
|
+
"type": "array",
|
|
179
|
+
"uniqueItems": true,
|
|
180
|
+
"items": { "$ref": "urn:claimspec:v1:common#/$defs/identifier" }
|
|
181
|
+
},
|
|
182
|
+
"probedCount": { "type": "integer", "minimum": 0 }
|
|
183
|
+
},
|
|
184
|
+
"additionalProperties": false
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
"additionalProperties": false
|
|
188
|
+
},
|
|
189
|
+
"cost": {
|
|
190
|
+
"description": "Present when probe --json --cost is requested; derived only from the runs in the emitted evidence.",
|
|
191
|
+
"type": "object",
|
|
192
|
+
"required": ["totalMs", "totalRuns", "reusedRecords", "records", "claims", "defenders", "sharedDefenders"],
|
|
193
|
+
"properties": {
|
|
194
|
+
"totalMs": { "type": "number", "minimum": 0 },
|
|
195
|
+
"totalRuns": { "type": "integer", "minimum": 0 },
|
|
196
|
+
"reusedRecords": { "type": "integer", "minimum": 0 },
|
|
197
|
+
"records": { "type": "integer", "minimum": 0 },
|
|
198
|
+
"claims": {
|
|
199
|
+
"type": "array",
|
|
200
|
+
"items": {
|
|
201
|
+
"type": "object",
|
|
202
|
+
"required": ["claimId", "ms", "runs", "faults", "reused", "defenders"],
|
|
203
|
+
"properties": {
|
|
204
|
+
"claimId": { "$ref": "urn:claimspec:v1:common#/$defs/identifier" },
|
|
205
|
+
"ms": { "type": "number", "minimum": 0 },
|
|
206
|
+
"runs": { "type": "integer", "minimum": 0 },
|
|
207
|
+
"faults": { "type": "integer", "minimum": 0 },
|
|
208
|
+
"reused": { "type": "integer", "minimum": 0 },
|
|
209
|
+
"defenders": {
|
|
210
|
+
"type": "array",
|
|
211
|
+
"uniqueItems": true,
|
|
212
|
+
"items": { "$ref": "urn:claimspec:v1:common#/$defs/repoPath" }
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
"additionalProperties": false
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
"defenders": {
|
|
219
|
+
"type": "array",
|
|
220
|
+
"items": { "$ref": "#/$defs/costDefender" }
|
|
221
|
+
},
|
|
222
|
+
"sharedDefenders": {
|
|
223
|
+
"type": "array",
|
|
224
|
+
"items": { "$ref": "#/$defs/costDefender" }
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
"additionalProperties": false
|
|
228
|
+
},
|
|
155
229
|
"counts": {
|
|
156
230
|
"type": "object",
|
|
157
231
|
"required": [
|
|
@@ -399,5 +473,23 @@
|
|
|
399
473
|
"$ref": "urn:claimspec:v1:common#/$defs/gitSha"
|
|
400
474
|
}
|
|
401
475
|
},
|
|
402
|
-
"additionalProperties": false
|
|
476
|
+
"additionalProperties": false,
|
|
477
|
+
"$defs": {
|
|
478
|
+
"costDefender": {
|
|
479
|
+
"type": "object",
|
|
480
|
+
"required": ["file", "ms", "runs", "claims"],
|
|
481
|
+
"properties": {
|
|
482
|
+
"file": { "$ref": "urn:claimspec:v1:common#/$defs/repoPath" },
|
|
483
|
+
"ms": { "type": "number", "minimum": 0 },
|
|
484
|
+
"runs": { "type": "integer", "minimum": 0 },
|
|
485
|
+
"claims": {
|
|
486
|
+
"type": "array",
|
|
487
|
+
"minItems": 1,
|
|
488
|
+
"uniqueItems": true,
|
|
489
|
+
"items": { "$ref": "urn:claimspec:v1:common#/$defs/identifier" }
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
"additionalProperties": false
|
|
493
|
+
}
|
|
494
|
+
}
|
|
403
495
|
}
|
|
@@ -46,7 +46,7 @@ probe
|
|
|
46
46
|
--ref <commit> probe this commit in the scratch worktree (default: HEAD). An explicit --ref is honoured even when
|
|
47
47
|
defender/target files are dirty: a warning names them, the evidence records them (repo.ignoredDirty)
|
|
48
48
|
--ignore-dirty probe HEAD as committed although defender/target files are dirty (same warning and record)
|
|
49
|
-
--claim <ID,ID> probe only these claims; writes .testguard/evidence-partial.json
|
|
49
|
+
--claim <ID,ID> probe only these claims; repeatable, order-preserving, and writes .testguard/evidence-partial.json
|
|
50
50
|
--include-dirty probe the working tree (a snapshot commit) instead of HEAD; uncommitted tests count
|
|
51
51
|
(without it, a dirty defender/target with the implicit HEAD is refused: the silent-mismatch trap)
|
|
52
52
|
--verbose also print each killed fault (default: only unproven ones, plus a count)
|
|
@@ -137,7 +137,7 @@ export async function main(argv, io = { out: (s) => process.stdout.write(s + '\n
|
|
|
137
137
|
ref: { type: 'string' },
|
|
138
138
|
'ignore-dirty': { type: 'boolean', default: false },
|
|
139
139
|
serial: { type: 'boolean', default: false },
|
|
140
|
-
claim: { type: 'string' },
|
|
140
|
+
claim: { type: 'string', multiple: true },
|
|
141
141
|
fault: { type: 'string' },
|
|
142
142
|
'include-dirty': { type: 'boolean', default: false },
|
|
143
143
|
changed: { type: 'string' },
|
|
@@ -29,10 +29,19 @@ function findProjectDir(testAbs, claimsFlag) {
|
|
|
29
29
|
* decision here can never disagree with the gate.
|
|
30
30
|
*/
|
|
31
31
|
export async function admitCommand({ file, values, version }, io) {
|
|
32
|
-
if (!file || !values.claim) {
|
|
32
|
+
if (!file || !values.claim?.length) {
|
|
33
33
|
io.err('usage: testguard admit <test-file> --claim <ID> [--fault <FID>] [--confirm <n>] [--json]');
|
|
34
34
|
return 3;
|
|
35
35
|
}
|
|
36
|
+
if (values.claim.length > 1 || values.claim[0].includes(',')) {
|
|
37
|
+
io.err('admit accepts exactly one --claim <ID>');
|
|
38
|
+
return 3;
|
|
39
|
+
}
|
|
40
|
+
const claimId = values.claim[0].trim();
|
|
41
|
+
if (!claimId) {
|
|
42
|
+
io.err('--claim must name one claim id');
|
|
43
|
+
return 3;
|
|
44
|
+
}
|
|
36
45
|
const confirmRuns = Number(values.confirm);
|
|
37
46
|
const budgetMs = Number(values.budget);
|
|
38
47
|
if (!Number.isInteger(confirmRuns) || confirmRuns < 1 || !Number.isInteger(budgetMs) || budgetMs < 1000) {
|
|
@@ -46,7 +55,6 @@ export async function admitCommand({ file, values, version }, io) {
|
|
|
46
55
|
if (testRel.startsWith('..')) throw new PreconditionError(`${file} is outside the project directory ${projectDir}`);
|
|
47
56
|
|
|
48
57
|
const all = loadClaims(values.claims ? resolve(values.claims) : defaultClaimsPath(projectDir));
|
|
49
|
-
const claimId = values.claim.trim();
|
|
50
58
|
const claim = all.claims.find((c) => c.id === claimId);
|
|
51
59
|
if (!claim) throw new PreconditionError(`--claim: unknown claim id ${claimId}`);
|
|
52
60
|
const faults = values.fault ? claim.faults.filter((f) => f.id === values.fault) : claim.faults;
|
|
@@ -9,11 +9,30 @@ import { computeStatus } from '../status/status.mjs';
|
|
|
9
9
|
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
|
+
import { validate } from '../../spec/lib/validate.mjs';
|
|
12
13
|
export const provisionalEvidencePath = (projectDir) => join(projectDir, '.testguard', 'evidence-provisional.json');
|
|
13
14
|
|
|
14
15
|
export const evidencePath = (projectDir) => join(projectDir, '.testguard', 'evidence.json');
|
|
15
16
|
export const baselinePath = (projectDir) => join(projectDir, '.testguard', 'baseline.json');
|
|
16
17
|
|
|
18
|
+
/** Every repeated/comma-separated --claim value, in first-seen order. */
|
|
19
|
+
export function claimSelection(values) {
|
|
20
|
+
if (values === undefined) return undefined;
|
|
21
|
+
return [...new Set(values.flatMap((value) => value.split(',')).map((id) => id.trim()).filter(Boolean))];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Scope is invocation metadata, kept outside the evidence contract. */
|
|
25
|
+
export function partialScope(only, records) {
|
|
26
|
+
if (!only) return undefined;
|
|
27
|
+
const probedClaims = [...new Set(records.map((record) => record.claim.id))];
|
|
28
|
+
return {
|
|
29
|
+
requestedClaims: only,
|
|
30
|
+
requestedCount: only.length,
|
|
31
|
+
probedClaims,
|
|
32
|
+
probedCount: probedClaims.length,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
17
36
|
export async function probeCommand({ projectDir, values, version }, io) {
|
|
18
37
|
const confirmRuns = Number(values.confirm);
|
|
19
38
|
const budgetMs = Number(values.budget);
|
|
@@ -34,7 +53,11 @@ export async function probeCommand({ projectDir, values, version }, io) {
|
|
|
34
53
|
io.err('claims file declares no claims; nothing to verify');
|
|
35
54
|
return 2;
|
|
36
55
|
}
|
|
37
|
-
const only = values.claim
|
|
56
|
+
const only = claimSelection(values.claim);
|
|
57
|
+
if (only?.length === 0) {
|
|
58
|
+
io.err('--claim must name at least one claim id');
|
|
59
|
+
return 3;
|
|
60
|
+
}
|
|
38
61
|
// A --claim run is partial evidence; keep it away from the canonical file unless --out says otherwise.
|
|
39
62
|
const provisional = confirmRuns < 3;
|
|
40
63
|
// Provisional and partial runs never overwrite the canonical evidence: only confirmed, complete runs may feed a baseline.
|
|
@@ -81,9 +104,13 @@ export async function probeCommand({ projectDir, values, version }, io) {
|
|
|
81
104
|
writeSpecDoc('evidence', outPath, evidence);
|
|
82
105
|
|
|
83
106
|
const g = gate(evidence.records, baseline, { severityFloor: values.severity });
|
|
107
|
+
const scope = partialScope(only, evidence.records);
|
|
84
108
|
if (values.json) {
|
|
85
109
|
const status = withChangedRef(resolveChangedRef({ explicit: values.changed }), (changedRef) => computeStatus({ projectDir, toolVersion: version, changedRef, includeDirty: values['include-dirty'], evidence: outPath }), io.err);
|
|
86
|
-
|
|
110
|
+
const doc = { ...status, run: { id: evidence.run.id, evidence: outPath, provisional, records: evidence.records.length, newSinceBaseline: g.new.length, exitCode: g.new.length > 0 ? 1 : 0, ...(scope ? { scope } : {}) }, ...(values.cost ? { cost: costReport(evidence.records) } : {}) };
|
|
111
|
+
const result = validate('status', doc);
|
|
112
|
+
if (!result.ok) throw new Error(`probe JSON document does not conform: ${result.errors.map((e) => `${e.path}: ${e.message}`).join('; ')}`);
|
|
113
|
+
io.out(JSON.stringify(doc, null, 2));
|
|
87
114
|
return g.new.length > 0 ? 1 : 0;
|
|
88
115
|
}
|
|
89
116
|
if (!values.quiet && baseline) {
|
|
@@ -102,7 +129,7 @@ export async function probeCommand({ projectDir, values, version }, io) {
|
|
|
102
129
|
io.out(renderCost(costReport(evidence.records)));
|
|
103
130
|
io.out('');
|
|
104
131
|
}
|
|
105
|
-
io.out(`evidence: ${outPath}${
|
|
132
|
+
io.out(`evidence: ${outPath}${scope ? ` (partial: ${scope.requestedCount} claim${scope.requestedCount === 1 ? '' : 's'} requested, ${scope.probedCount} probed: --claim ${scope.requestedClaims.join(',')}; not the canonical evidence file)` : provisional ? ' (provisional; not the canonical evidence file)' : ''}`);
|
|
106
133
|
if (provisional && !values.quiet) io.err(PROVISIONAL_WARNING(confirmRuns));
|
|
107
134
|
return g.new.length > 0 ? 1 : 0;
|
|
108
135
|
}
|
|
@@ -10,6 +10,15 @@ export async function scaffoldCommand({ projectDir, file, values, version }, io)
|
|
|
10
10
|
io.err('usage: testguard scaffold <source-file> [--claim <ID>] [--out <path>] [--json]');
|
|
11
11
|
return 3;
|
|
12
12
|
}
|
|
13
|
+
if (values.claim?.length > 1 || values.claim?.[0]?.includes(',')) {
|
|
14
|
+
io.err('scaffold accepts exactly one --claim <ID>');
|
|
15
|
+
return 3;
|
|
16
|
+
}
|
|
17
|
+
const claimId = values.claim?.[0]?.trim();
|
|
18
|
+
if (values.claim && !claimId) {
|
|
19
|
+
io.err('--claim must name one claim id');
|
|
20
|
+
return 3;
|
|
21
|
+
}
|
|
13
22
|
const abs = resolve(file);
|
|
14
23
|
if (!existsSync(abs)) throw new PreconditionError(`no such file: ${file}`);
|
|
15
24
|
const rel = relative(projectDir, abs);
|
|
@@ -17,7 +26,7 @@ export async function scaffoldCommand({ projectDir, file, values, version }, io)
|
|
|
17
26
|
|
|
18
27
|
const claimsPath = values.claims ? resolve(values.claims) : defaultClaimsPath(projectDir);
|
|
19
28
|
const existingClaims = existsSync(claimsPath) ? loadClaims(claimsPath) : undefined;
|
|
20
|
-
const { doc, stats } = scaffoldFile({ projectDir, file: rel, claimId
|
|
29
|
+
const { doc, stats } = scaffoldFile({ projectDir, file: rel, claimId, existingClaims, toolVersion: version });
|
|
21
30
|
|
|
22
31
|
if (values.json) {
|
|
23
32
|
io.out(JSON.stringify(doc, null, 2));
|
|
@@ -40,6 +40,16 @@ function readRunnerVersion(projectDir, name = 'vitest') {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/** Validate and order a partial claim selection without losing caller order. */
|
|
44
|
+
export function selectClaims(claims, only) {
|
|
45
|
+
if (!only) return claims;
|
|
46
|
+
if (only.length === 0) throw new PreconditionError('--claim must name at least one claim id');
|
|
47
|
+
const byId = new Map(claims.map((claim) => [claim.id, claim]));
|
|
48
|
+
const unknown = only.filter((id) => !byId.has(id));
|
|
49
|
+
if (unknown.length) throw new PreconditionError(`--claim: unknown claim id(s) ${unknown.join(', ')}`);
|
|
50
|
+
return only.map((id) => byId.get(id));
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
/**
|
|
44
54
|
* Probe every fault of every claim. Returns a spec-conformant evidence
|
|
45
55
|
* document; writing it is the caller's job.
|
|
@@ -76,18 +86,13 @@ export async function probe({
|
|
|
76
86
|
const head = headSha(root, ref);
|
|
77
87
|
if (!head) throw new PreconditionError(ref === 'HEAD' ? 'repository has no commits; every verdict is tied to a commit' : `ref ${ref} does not resolve to a commit`);
|
|
78
88
|
|
|
79
|
-
const
|
|
89
|
+
const scopedClaims = selectClaims(claims.claims, only);
|
|
90
|
+
const targets = [...new Set(scopedClaims.flatMap((c) => c.faults.map((f) => relative(root, join(projectDir, f.file)))))];
|
|
80
91
|
if (mode === 'in-place' && isDirty(root, targets)) {
|
|
81
92
|
throw new PreconditionError(`uncommitted changes in fault target files (${targets.join(', ')}); commit or stash them, or drop --in-place. Only the files faults are applied to must be clean — test files may be dirty, which is what makes --in-place usable while writing tests.`);
|
|
82
93
|
}
|
|
83
94
|
if (includeDirty && mode !== 'worktree') throw new PreconditionError('--include-dirty applies to worktree mode; drop --in-place');
|
|
84
95
|
if (includeDirty && ref !== 'HEAD') throw new PreconditionError('--include-dirty snapshots the working tree; it cannot be combined with --ref');
|
|
85
|
-
const selected = only ? new Set(only) : null;
|
|
86
|
-
if (selected) {
|
|
87
|
-
const known = new Set(claims.claims.map((c) => c.id));
|
|
88
|
-
const unknown = [...selected].filter((id) => !known.has(id));
|
|
89
|
-
if (unknown.length) throw new PreconditionError(`--claim: unknown claim id(s) ${unknown.join(', ')}`);
|
|
90
|
-
}
|
|
91
96
|
|
|
92
97
|
// Worktree mode probes a commit, not the working tree. Uncommitted defender
|
|
93
98
|
// or target edits would be silently absent — the same survivors, no hint why.
|
|
@@ -103,8 +108,7 @@ export async function probe({
|
|
|
103
108
|
if (isDirty(root)) snapshot = snapshotWorkingTree(root);
|
|
104
109
|
} else {
|
|
105
110
|
const watched = new Set(targets);
|
|
106
|
-
for (const claim of
|
|
107
|
-
if (selected && !selected.has(claim.id)) continue;
|
|
111
|
+
for (const claim of scopedClaims) {
|
|
108
112
|
const declared = claim.defendedBy?.length ? resolveDefenders(projectDir, claim.defendedBy) : claim.faults.flatMap((f) => discoverDefenders(projectDir, f.file));
|
|
109
113
|
for (const d of declared) watched.add(relative(root, join(projectDir, d)));
|
|
110
114
|
for (const g of claim.defendedBy ?? []) if (!g.includes('*')) watched.add(relative(root, join(projectDir, g)));
|
|
@@ -193,8 +197,7 @@ export async function probe({
|
|
|
193
197
|
return Object.fromEntries([...groups].map(([r, group]) => [labelOf(r), group]));
|
|
194
198
|
};
|
|
195
199
|
|
|
196
|
-
for (const claim of
|
|
197
|
-
if (selected && !selected.has(claim.id)) continue;
|
|
200
|
+
for (const claim of scopedClaims) {
|
|
198
201
|
const declared = claim.defendedBy?.length ? resolveDefenders(iso.projectDir, claim.defendedBy) : null;
|
|
199
202
|
for (const fault of claim.faults) {
|
|
200
203
|
// Mock-awareness: a discovered file that mocks the target is not a
|