testguard-cli 0.12.0__tar.gz → 0.13.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.12.0 → testguard_cli-0.13.0}/CHANGELOG.md +57 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/PKG-INFO +87 -9
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/README.md +86 -8
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/action.yml +1 -1
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs/testguard-explained.html +1 -1
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs/testguard-explained.pdf +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs-canonical/ARCHITECTURE.md +2 -1
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/package-lock.json +2 -2
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/package.json +1 -1
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/packaging/gitlab/testguard.gitlab-ci.yml +2 -2
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/packaging/homebrew/testguard.rb +2 -2
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/pyproject.toml +1 -1
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/lib/validate.mjs +38 -2
- testguard_cli-0.13.0/spec/schemas/concerns.schema.json +41 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/sweep.schema.json +56 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/cli.mjs +7 -1
- testguard_cli-0.13.0/src/commands/concerns.mjs +32 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/sweep.mjs +52 -23
- testguard_cli-0.13.0/src/supply/concerns.mjs +162 -0
- testguard_cli-0.13.0/src/supply/feedback.mjs +146 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/supply/persistence.mjs +53 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/sweep/sweep.mjs +87 -11
- testguard_cli-0.13.0/test/supply-concerns.test.mjs +124 -0
- testguard_cli-0.13.0/test/supply-feedback.test.mjs +134 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/supply-persistence.test.mjs +41 -1
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/sweep.test.mjs +29 -1
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/testguard.claims.json +130 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.agents/skills/testguard/SKILL.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.codex/hooks.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.docguard.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.docguardignore +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.gitattributes +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/dependabot.yml +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/scripts/approve-held-runs.sh +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/scripts/build-one-pager.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/scripts/check-cost-budget.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/scripts/check-python-fixture.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/scripts/install-smoke.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/scripts/publish-homebrew-tap.sh +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/scripts/sync-release-version.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/workflows/auto-merge.yml +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/workflows/ci.yml +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/workflows/release.yml +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/workflows/scheduled-release.yml +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/workflows/supply-chain.yml +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.gitignore +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.npmignore +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.npmrc +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.pre-commit-config.yaml +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.pre-commit-hooks.yaml +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/AGENTS.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/CLAUDE.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/CODE_OF_CONDUCT.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/CONTRIBUTING.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/DRIFT-LOG.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/LICENSE +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/PRIVACY.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/SECURITY.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/SUPPORT.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/bench/README.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/cli/testguard.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs-canonical/DATA-MODEL.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs-canonical/ENVIRONMENT.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs-canonical/PRIOR-ART.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs-canonical/REQUIREMENTS.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs-canonical/SECURITY.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs-canonical/TEST-SPEC.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/.gitignore +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/README.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/expected.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/package.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/src/export.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/src/redact.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/test/flaky.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/test/redact.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/testguard.claims.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/vitest.config.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/.gitignore +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/README.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/expected.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/jest.config.js +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/package.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/src/export.js +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/src/redact.js +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/test/redact.test.js +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/testguard.claims.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/.gitignore +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/README.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/expected.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/package-lock.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/package.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/test-results/.last-run.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/.gitignore +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/README.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/demo/__init__.py +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/demo/export.py +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/demo/orphan.py +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/demo/pipeline.py +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/demo/redact.py +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/expected.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/testguard.claims.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/tests/test_patched.py +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/tests/test_redact.py +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/GATE-SEMANTICS.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/README.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/baseline.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/brief.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/calibration-websec.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/calibration.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/claims-methods.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/claims.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/evidence-negative-control.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/evidence-provisional.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/evidence-python.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/evidence-scan-method.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/evidence.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/gate.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/ignore.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/replay.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/status.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/baseline.zero-count.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.ci-not-wilson.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.fallback-without-basis.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.p-not-positives-over-n.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.absolute-path.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.noop-fault.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/ignore.short-reason.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/status.claim-without-file.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/status.invalid-anchor-hidden.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/status.partial-scope-counts-disagree.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/status.surface-counts-disagree.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/schemas.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/lib/fingerprint.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/lib/wilson.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/baseline.schema.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/brief.schema.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/calibration.schema.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/claims.schema.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/common.schema.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/evidence.schema.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/gate.schema.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/ignore.schema.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/replay.schema.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/status.schema.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/admit/admit.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/baseline/baseline.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/brief/brief.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/claims/anchors.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/claims/annotations.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/claims/load.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/claims/removed.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/admit.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/baseline.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/brief.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/claims.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/gate.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/init.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/mcp.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/probe.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/replay.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/scaffold.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/status.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/evidence/writer.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/gate/changed.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/git.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/init/init.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/init/templates/SKILL.md +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/mcp/server.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/mcp/tools.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/attribution.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/classify.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/contention.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/cost.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/discover.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/independence.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/inject.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/mocks.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/probe.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/progress.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/pyimports.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/rank.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/index.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/jest.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/playwright.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/python/_testguard_report.py +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/python.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/shared.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/vitest.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/worktree.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/render.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/replay/label.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/replay/replay.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/scaffold/producers.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/scaffold/producers.python.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/scaffold/scaffold.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/status/status.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/status/surface.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/supply/savepath.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/supply/select.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/util/glob.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/util/hash.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/admit.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/annotations.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/attribution.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/baseline.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/brief.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/calibration.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/claim-selection.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/claims-anchors.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/claims-since.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/classify.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/contention.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/cost.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/discover.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/gate.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/gitlab-template.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/glob.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/independence.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/init.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/inject.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/label.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/mcp.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/mocks.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/negative-control.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/one-pager.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/probe-error.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/probe-preconditions.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/probe.fixture.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/probe.jest.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/probe.playwright.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/probe.python.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/progress.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/pyimports.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/rank-aliases.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/release-approval.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/release-sync.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/replay-verdict.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/replay.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/runner-argv.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/runner-command.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/runner-playwright.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/runner-python.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/runner-vitest.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/samples/playwright-report.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/samples/python-report-collect-error.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/samples/python-report.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/scaffold.python.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/scaffold.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/snapshot.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/status.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/supply-savepath.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/supply-select.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/surface.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/sweep.fixture.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/vitest-config.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/worktree.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/writer.test.mjs +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/testguard.cost-budget.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/testguard.ignore.json +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/testguard_cli/__init__.py +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/testguard_cli/wrapper.py +0 -0
- {testguard_cli-0.12.0 → testguard_cli-0.13.0}/vitest.config.mjs +0 -0
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.13.0] - 2026-09-22
|
|
11
|
+
|
|
12
|
+
Automated weekly release — everything merged since `v0.12.0`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- chore(homebrew): sha256 for v0.12.0 (#159)
|
|
17
|
+
- feat: persistence provability, and an ordering that learns from this project's own verdicts (#157)
|
|
18
|
+
|
|
19
|
+
|
|
10
20
|
## [0.12.0] - 2026-09-22
|
|
11
21
|
|
|
12
22
|
Automated weekly release — everything merged since `v0.11.0`.
|
|
@@ -18,6 +28,53 @@ Automated weekly release — everything merged since `v0.11.0`.
|
|
|
18
28
|
- chore(homebrew): sha256 for v0.11.0 (#153)
|
|
19
29
|
|
|
20
30
|
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **Concerns** — one sentence a human writes that generates faults across a
|
|
34
|
+
whole surface, the rung between writing nothing and writing a claim per
|
|
35
|
+
behaviour. A concern says *where* to look and *which shapes of break* are
|
|
36
|
+
relevant: `"every write persists the data it was given"` targets the write
|
|
37
|
+
sites and applies the payload producers. `testguard concerns` lists them,
|
|
38
|
+
`sweep --concern <ID>` aims a sweep by one, and `--save-paths` is now sugar
|
|
39
|
+
for the built-in `SAVE-PERSISTS`.
|
|
40
|
+
- Two built-ins ship, and only two: an authorization concern would need a
|
|
41
|
+
heuristic for "which files check permissions", and a guess there is the
|
|
42
|
+
non-actionable noise that gets a check switched off. A project that knows
|
|
43
|
+
its own auth layer says so with a `glob` target in three lines.
|
|
44
|
+
- A project concern with the same id **replaces** a built-in, so a team can
|
|
45
|
+
retune `SAVE-PERSISTS` for an ORM this tool does not recognise without
|
|
46
|
+
forking anything. The replacement is reported, never silent.
|
|
47
|
+
- `urn:claimspec:v1:concerns`, with rules that refuse a glob target naming no
|
|
48
|
+
globs (it would match nothing and report clean) and an empty fault-class
|
|
49
|
+
list (a typo for "every class", every time).
|
|
50
|
+
- **No model is required, deliberately.** A concern's useful core is a named
|
|
51
|
+
scope plus a producer selection, which the mechanical producers already
|
|
52
|
+
satisfy — so every verifying command stays offline and NFR-01 is unchanged.
|
|
53
|
+
An LLM can later propose faults a line-oriented producer cannot see, as an
|
|
54
|
+
additional source for a concern that already works.
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- A save-path sweep now reports **what its defenders could prove**, before it
|
|
59
|
+
reports what they did: per file, whether every defender mocks the persistence
|
|
60
|
+
layer (call shape only), at least one does not (a real write is possible), or
|
|
61
|
+
there is no defender at all. When nothing is unmocked the report says so
|
|
62
|
+
outright — a clean probe there is not evidence of persistence, it is evidence
|
|
63
|
+
that nothing could have measured it. Measured on a real AI-authored
|
|
64
|
+
application: of 37 files that write to storage, 32 were defended only by
|
|
65
|
+
mocking tests.
|
|
66
|
+
- `scope.provability` on the sweep document, with a validator rule that every
|
|
67
|
+
target is classified exactly once.
|
|
68
|
+
- The candidate ordering now **learns from this project's own probed verdicts**
|
|
69
|
+
instead of only the shipped productivity prior. Every evidence record already
|
|
70
|
+
carries a `faultClass` and a `verdict`, so "how often does a fault of this
|
|
71
|
+
class survive *here*" is a tally rather than a new thing to capture. A sweep
|
|
72
|
+
persists its own evidence to `.testguard/sweep-evidence.json` — beside the
|
|
73
|
+
canonical one, never replacing it — so the next run learns from
|
|
74
|
+
machine-proposed faults, the same distribution the ranker orders. The sweep
|
|
75
|
+
document records `ordering.observed` and `ordering.sources`, because an
|
|
76
|
+
ordering nobody can trace is a number nobody should trust.
|
|
77
|
+
|
|
21
78
|
### Added
|
|
22
79
|
|
|
23
80
|
- `sweep --save-paths` — sweep every file that writes to storage instead of the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: testguard-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.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.13.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.13.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
|
|
@@ -475,6 +475,14 @@ Three rules make it safe to run on a repository that has never seen this tool:
|
|
|
475
475
|
is reported and never gates — a tool that fails because its own guess was bad
|
|
476
476
|
is a tool people switch off.
|
|
477
477
|
|
|
478
|
+
The ordering **learns from your own runs**. Every evidence record already
|
|
479
|
+
carries a fault class and a verdict, so "how often does a fault of this class
|
|
480
|
+
survive here" is a tally, not a new thing to collect — a sweep persists its
|
|
481
|
+
evidence and the next one reads it. A project with no evidence falls back to
|
|
482
|
+
the shipped prior and says so; one with its own records overrides it in
|
|
483
|
+
proportion to how many it has. The report names which, because an ordering
|
|
484
|
+
nobody can trace is a number nobody should trust.
|
|
485
|
+
|
|
478
486
|
The cap and the ordering are Google's: their mutation service surfaces at most
|
|
479
487
|
7 × |files| mutants per change and orders candidates on the measured
|
|
480
488
|
productivity of their operator in similar context, which took their productive
|
|
@@ -490,6 +498,60 @@ A sweep is weaker evidence than a probe, and says so. Nobody stated that the
|
|
|
490
498
|
behaviour mattered. It is stronger than nothing, which is what a repository
|
|
491
499
|
with no claims has — and the survivors worth defending become the first claims.
|
|
492
500
|
|
|
501
|
+
#### Concerns: one sentence, two hundred screens
|
|
502
|
+
|
|
503
|
+
A claim names one promise precisely, which is why a repository with two hundred
|
|
504
|
+
screens never finishes writing them. A **concern** names a *kind* of promise and
|
|
505
|
+
says where to look for it:
|
|
506
|
+
|
|
507
|
+
```bash
|
|
508
|
+
npx testguard-cli concerns # what this project can be swept by
|
|
509
|
+
npx testguard-cli sweep --concern ADMIN-GUARDS # aim a sweep at one
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
```json
|
|
513
|
+
{
|
|
514
|
+
"schemaVersion": 1,
|
|
515
|
+
"concerns": [
|
|
516
|
+
{
|
|
517
|
+
"id": "ADMIN-GUARDS",
|
|
518
|
+
"statement": "Every admin route refuses a caller without the admin role.",
|
|
519
|
+
"severity": "critical",
|
|
520
|
+
"targets": { "kind": "glob", "globs": ["src/app/api/admin/**"] },
|
|
521
|
+
"faultClasses": ["guard-removed", "condition-forced", "return-altered"]
|
|
522
|
+
}
|
|
523
|
+
]
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
Five lines, and the sweep reports:
|
|
528
|
+
|
|
529
|
+
```
|
|
530
|
+
concern ADMIN-GUARDS matches 24 files. Swept 23.
|
|
531
|
+
158 further proposals were outside this concern's fault classes.
|
|
532
|
+
|
|
533
|
+
SURVIVED src/app/api/admin/claims/[id]/route.ts
|
|
534
|
+
[line 41] Guard never triggers: `if (!membership || (membership.role !== 'owner'
|
|
535
|
+
&& membership.role !== 'manager'))` becomes `if (false)`.
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
The idea is Meta's: ACH has an engineer describe an area of concern in plain
|
|
539
|
+
text and gates every generated step by execution. What is taken here is the
|
|
540
|
+
**unit** — the concern as the thing a human writes — not their generator, and
|
|
541
|
+
**no model is required**. A concern's useful core is a named scope plus a
|
|
542
|
+
producer selection, which the mechanical producers already satisfy, so every
|
|
543
|
+
verifying command stays offline.
|
|
544
|
+
|
|
545
|
+
Two concerns ship built in, and only two. An authorization concern would need a
|
|
546
|
+
heuristic for *"which files check permissions"*, and a guess there is exactly
|
|
547
|
+
the noise that gets a check switched off — so `SAVE-PERSISTS` and
|
|
548
|
+
`CHANGED-CODE` are the defaults, and a project that knows its own auth layer
|
|
549
|
+
says so in three lines. Declaring a concern with a built-in's id replaces it,
|
|
550
|
+
and the replacement is reported rather than silent.
|
|
551
|
+
|
|
552
|
+
A concern is **not** a claim and never becomes one by itself. It says where to
|
|
553
|
+
look; the probe says what it found; a human states the sentence worth defending.
|
|
554
|
+
|
|
493
555
|
#### Sweeping the save surface instead of the diff
|
|
494
556
|
|
|
495
557
|
A diff cannot answer *"when the user clicks save, does it actually save?"* — it
|
|
@@ -518,10 +580,26 @@ denominator comes first and the document carries `writeSites` and
|
|
|
518
580
|
AI-authored application: **12 of 26 probed persistence faults survived a fully
|
|
519
581
|
green suite, and all 12 were a dropped payload field.**
|
|
520
582
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
583
|
+
It also states, before any verdict, **what the defenders could prove at all**:
|
|
584
|
+
|
|
585
|
+
```
|
|
586
|
+
of those 37 files: 32 defended only by tests that mock the persistence layer,
|
|
587
|
+
1 with an unmocked defender, 4 with no defender at all.
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
That line is the read-back answer. A test that replaced the database can prove
|
|
591
|
+
the *call shape* and nothing beyond it — a `where` that matches no rows, a
|
|
592
|
+
rolled-back transaction and a rejected constraint all pass against a mock that
|
|
593
|
+
recorded the arguments and returned a plausible object. So rather than pretend
|
|
594
|
+
to measure persistence, TestGuard states the limit, the same way `nocover` says
|
|
595
|
+
"no test imports this" instead of guessing.
|
|
596
|
+
|
|
597
|
+
When every defender mocks the layer, the report says so outright: *nothing in
|
|
598
|
+
this suite can prove a write reached storage*. A clean probe there is not
|
|
599
|
+
evidence of persistence — it is evidence that nothing could have measured it.
|
|
600
|
+
|
|
601
|
+
`unmocked` is deliberately weaker than "proves persistence": a test that does
|
|
602
|
+
not replace the database may simply never reach it. Possible is the honest word.
|
|
525
603
|
|
|
526
604
|
### Every change needs a claim
|
|
527
605
|
|
|
@@ -604,12 +682,12 @@ detected base that does not resolve is a warning for `status` and `brief`
|
|
|
604
682
|
# GitHub Actions
|
|
605
683
|
- uses: actions/checkout@v4
|
|
606
684
|
with: { fetch-depth: 0 }
|
|
607
|
-
- uses: raccioly/testguard@v0.
|
|
685
|
+
- uses: raccioly/testguard@v0.13.0
|
|
608
686
|
with: { command: gate }
|
|
609
687
|
|
|
610
688
|
# GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
|
|
611
689
|
include:
|
|
612
|
-
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.
|
|
690
|
+
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.13.0/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
613
691
|
inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
|
|
614
692
|
```
|
|
615
693
|
|
|
@@ -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.13.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.13.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
|
|
@@ -451,6 +451,14 @@ Three rules make it safe to run on a repository that has never seen this tool:
|
|
|
451
451
|
is reported and never gates — a tool that fails because its own guess was bad
|
|
452
452
|
is a tool people switch off.
|
|
453
453
|
|
|
454
|
+
The ordering **learns from your own runs**. Every evidence record already
|
|
455
|
+
carries a fault class and a verdict, so "how often does a fault of this class
|
|
456
|
+
survive here" is a tally, not a new thing to collect — a sweep persists its
|
|
457
|
+
evidence and the next one reads it. A project with no evidence falls back to
|
|
458
|
+
the shipped prior and says so; one with its own records overrides it in
|
|
459
|
+
proportion to how many it has. The report names which, because an ordering
|
|
460
|
+
nobody can trace is a number nobody should trust.
|
|
461
|
+
|
|
454
462
|
The cap and the ordering are Google's: their mutation service surfaces at most
|
|
455
463
|
7 × |files| mutants per change and orders candidates on the measured
|
|
456
464
|
productivity of their operator in similar context, which took their productive
|
|
@@ -466,6 +474,60 @@ A sweep is weaker evidence than a probe, and says so. Nobody stated that the
|
|
|
466
474
|
behaviour mattered. It is stronger than nothing, which is what a repository
|
|
467
475
|
with no claims has — and the survivors worth defending become the first claims.
|
|
468
476
|
|
|
477
|
+
#### Concerns: one sentence, two hundred screens
|
|
478
|
+
|
|
479
|
+
A claim names one promise precisely, which is why a repository with two hundred
|
|
480
|
+
screens never finishes writing them. A **concern** names a *kind* of promise and
|
|
481
|
+
says where to look for it:
|
|
482
|
+
|
|
483
|
+
```bash
|
|
484
|
+
npx testguard-cli concerns # what this project can be swept by
|
|
485
|
+
npx testguard-cli sweep --concern ADMIN-GUARDS # aim a sweep at one
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
```json
|
|
489
|
+
{
|
|
490
|
+
"schemaVersion": 1,
|
|
491
|
+
"concerns": [
|
|
492
|
+
{
|
|
493
|
+
"id": "ADMIN-GUARDS",
|
|
494
|
+
"statement": "Every admin route refuses a caller without the admin role.",
|
|
495
|
+
"severity": "critical",
|
|
496
|
+
"targets": { "kind": "glob", "globs": ["src/app/api/admin/**"] },
|
|
497
|
+
"faultClasses": ["guard-removed", "condition-forced", "return-altered"]
|
|
498
|
+
}
|
|
499
|
+
]
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Five lines, and the sweep reports:
|
|
504
|
+
|
|
505
|
+
```
|
|
506
|
+
concern ADMIN-GUARDS matches 24 files. Swept 23.
|
|
507
|
+
158 further proposals were outside this concern's fault classes.
|
|
508
|
+
|
|
509
|
+
SURVIVED src/app/api/admin/claims/[id]/route.ts
|
|
510
|
+
[line 41] Guard never triggers: `if (!membership || (membership.role !== 'owner'
|
|
511
|
+
&& membership.role !== 'manager'))` becomes `if (false)`.
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
The idea is Meta's: ACH has an engineer describe an area of concern in plain
|
|
515
|
+
text and gates every generated step by execution. What is taken here is the
|
|
516
|
+
**unit** — the concern as the thing a human writes — not their generator, and
|
|
517
|
+
**no model is required**. A concern's useful core is a named scope plus a
|
|
518
|
+
producer selection, which the mechanical producers already satisfy, so every
|
|
519
|
+
verifying command stays offline.
|
|
520
|
+
|
|
521
|
+
Two concerns ship built in, and only two. An authorization concern would need a
|
|
522
|
+
heuristic for *"which files check permissions"*, and a guess there is exactly
|
|
523
|
+
the noise that gets a check switched off — so `SAVE-PERSISTS` and
|
|
524
|
+
`CHANGED-CODE` are the defaults, and a project that knows its own auth layer
|
|
525
|
+
says so in three lines. Declaring a concern with a built-in's id replaces it,
|
|
526
|
+
and the replacement is reported rather than silent.
|
|
527
|
+
|
|
528
|
+
A concern is **not** a claim and never becomes one by itself. It says where to
|
|
529
|
+
look; the probe says what it found; a human states the sentence worth defending.
|
|
530
|
+
|
|
469
531
|
#### Sweeping the save surface instead of the diff
|
|
470
532
|
|
|
471
533
|
A diff cannot answer *"when the user clicks save, does it actually save?"* — it
|
|
@@ -494,10 +556,26 @@ denominator comes first and the document carries `writeSites` and
|
|
|
494
556
|
AI-authored application: **12 of 26 probed persistence faults survived a fully
|
|
495
557
|
green suite, and all 12 were a dropped payload field.**
|
|
496
558
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
559
|
+
It also states, before any verdict, **what the defenders could prove at all**:
|
|
560
|
+
|
|
561
|
+
```
|
|
562
|
+
of those 37 files: 32 defended only by tests that mock the persistence layer,
|
|
563
|
+
1 with an unmocked defender, 4 with no defender at all.
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
That line is the read-back answer. A test that replaced the database can prove
|
|
567
|
+
the *call shape* and nothing beyond it — a `where` that matches no rows, a
|
|
568
|
+
rolled-back transaction and a rejected constraint all pass against a mock that
|
|
569
|
+
recorded the arguments and returned a plausible object. So rather than pretend
|
|
570
|
+
to measure persistence, TestGuard states the limit, the same way `nocover` says
|
|
571
|
+
"no test imports this" instead of guessing.
|
|
572
|
+
|
|
573
|
+
When every defender mocks the layer, the report says so outright: *nothing in
|
|
574
|
+
this suite can prove a write reached storage*. A clean probe there is not
|
|
575
|
+
evidence of persistence — it is evidence that nothing could have measured it.
|
|
576
|
+
|
|
577
|
+
`unmocked` is deliberately weaker than "proves persistence": a test that does
|
|
578
|
+
not replace the database may simply never reach it. Possible is the honest word.
|
|
501
579
|
|
|
502
580
|
### Every change needs a claim
|
|
503
581
|
|
|
@@ -580,12 +658,12 @@ detected base that does not resolve is a warning for `status` and `brief`
|
|
|
580
658
|
# GitHub Actions
|
|
581
659
|
- uses: actions/checkout@v4
|
|
582
660
|
with: { fetch-depth: 0 }
|
|
583
|
-
- uses: raccioly/testguard@v0.
|
|
661
|
+
- uses: raccioly/testguard@v0.13.0
|
|
584
662
|
with: { command: gate }
|
|
585
663
|
|
|
586
664
|
# GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
|
|
587
665
|
include:
|
|
588
|
-
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.
|
|
666
|
+
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.13.0/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
589
667
|
inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
|
|
590
668
|
```
|
|
591
669
|
|
|
@@ -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.13.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
|
|
@@ -50,7 +50,7 @@ code were wrong, would anything fail?*
|
|
|
50
50
|
| Progress | How a running probe reports itself, per reader: tty, plain, ndjson | `src/probe/progress.mjs` |
|
|
51
51
|
| Change gate | Claim coverage of a diff | `src/gate/changed.mjs` |
|
|
52
52
|
| Sweep | The cold start: propose faults for changed files that carry no claim, probe a bounded selection, report what nothing noticed | `src/sweep/sweep.mjs` |
|
|
53
|
-
| Claim supply |
|
|
53
|
+
| Claim supply | The concerns a sweep can be aimed by, which proposals are worth probing, what this project's own runs say about that, where it writes to storage, and why a survivor on a write path was missed | `src/supply/concerns.mjs`, `src/supply/select.mjs`, `src/supply/feedback.mjs`, `src/supply/savepath.mjs`, `src/supply/persistence.mjs` |
|
|
54
54
|
| Baseline | Freeze debt; gate the delta; re-stamp | `src/baseline/baseline.mjs` |
|
|
55
55
|
| Status | The single state machine every rendering derives from | `src/status/status.mjs` |
|
|
56
56
|
| Brief | The agent's session-start blind-spot block | `src/brief/brief.mjs` |
|
|
@@ -105,6 +105,7 @@ appears here but not on disk, or on disk but not here, is drift.
|
|
|
105
105
|
| `action.yml` | The GitHub Action surface; `version` input synced from `package.json` | consumers' workflows |
|
|
106
106
|
| `packaging/gitlab/testguard.gitlab-ci.yml` | The GitLab component; tag and `TESTGUARD_VERSION` synced | consumers' pipelines |
|
|
107
107
|
| `packaging/homebrew/testguard.rb` | The staged Homebrew formula; tarball URL and registry-derived hash synced, then copied byte-for-byte | `raccioly/homebrew-tap` |
|
|
108
|
+
| `testguard.concerns.json` | The one-sentence scopes a sweep can be aimed by; optional, with two built-ins behind it | `testguard concerns`, `sweep --concern` |
|
|
108
109
|
| `.docguard.json`, `.docguardignore` | Which canonical documents exist and which validators run | DocGuard, in CI and pre-commit |
|
|
109
110
|
| `.npmrc`, `.npmignore`, `.gitattributes` | Packaging and line-ending hygiene | npm, git |
|
|
110
111
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testguard-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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.13.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.13.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.13.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.13.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.13.0.tgz"
|
|
19
|
+
sha256 "27079e0d5870ad86ccad4df967e50898eddacd9d66b2391120da58970ce57a34"
|
|
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.13.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" }
|
|
@@ -7,7 +7,7 @@ import { reproduces, decimals, MAX_PLACES } from './wilson.mjs';
|
|
|
7
7
|
|
|
8
8
|
const schemaDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'schemas');
|
|
9
9
|
|
|
10
|
-
export const KINDS = Object.freeze(['claims', 'evidence', 'baseline', 'ignore', 'calibration', 'brief', 'status', 'gate', 'replay', 'sweep']);
|
|
10
|
+
export const KINDS = Object.freeze(['claims', 'evidence', 'baseline', 'ignore', 'calibration', 'brief', 'status', 'gate', 'replay', 'sweep', 'concerns']);
|
|
11
11
|
/**
|
|
12
12
|
* How a document says it tried to falsify its claims. Absent means
|
|
13
13
|
* `fault-injection`, so every document written before the field existed is
|
|
@@ -305,6 +305,29 @@ const semantic = {
|
|
|
305
305
|
return errors;
|
|
306
306
|
},
|
|
307
307
|
|
|
308
|
+
concerns(doc) {
|
|
309
|
+
const errors = [];
|
|
310
|
+
const seen = new Set();
|
|
311
|
+
doc.concerns.forEach((c, i) => {
|
|
312
|
+
if (seen.has(c.id)) errors.push({ path: `/concerns/${i}/id`, message: `duplicate concern id "${c.id}"` });
|
|
313
|
+
seen.add(c.id);
|
|
314
|
+
// A glob target with no globs matches nothing, which reads as "this
|
|
315
|
+
// concern found no problems" rather than "this concern was never aimed".
|
|
316
|
+
if (c.targets?.kind === 'glob' && !(c.targets.globs?.length > 0)) {
|
|
317
|
+
errors.push({ path: `/concerns/${i}/targets/globs`, message: `concern "${c.id}" targets a glob but names none; it would match nothing and report clean` });
|
|
318
|
+
}
|
|
319
|
+
if (c.targets?.kind !== 'glob' && c.targets?.globs) {
|
|
320
|
+
errors.push({ path: `/concerns/${i}/targets/globs`, message: `concern "${c.id}" names globs but does not target them` });
|
|
321
|
+
}
|
|
322
|
+
// An empty list is not "every class"; it is a concern that can never
|
|
323
|
+
// propose anything, which is a typo for `null` every time.
|
|
324
|
+
if (Array.isArray(c.faultClasses) && c.faultClasses.length === 0) {
|
|
325
|
+
errors.push({ path: `/concerns/${i}/faultClasses`, message: `concern "${c.id}" allows no fault class; omit the field for every class` });
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
return errors;
|
|
329
|
+
},
|
|
330
|
+
|
|
308
331
|
sweep(doc) {
|
|
309
332
|
const errors = [];
|
|
310
333
|
const s = doc.selection;
|
|
@@ -318,10 +341,17 @@ const semantic = {
|
|
|
318
341
|
// measured, and its absence on a save-paths sweep hides the denominator
|
|
319
342
|
// entirely — which is the whole reason that mode exists.
|
|
320
343
|
const saves = doc.scope.mode === 'save-paths';
|
|
321
|
-
for (const k of ['writeSites', 'payloadFields']) {
|
|
344
|
+
for (const k of ['writeSites', 'payloadFields', 'provability']) {
|
|
322
345
|
if (saves && doc.scope[k] === undefined) errors.push({ path: `/scope/${k}`, message: `a save-paths sweep must report ${k}: the surface is the denominator its findings are read against` });
|
|
323
346
|
if (!saves && doc.scope[k] !== undefined) errors.push({ path: `/scope/${k}`, message: `${k} belongs to a save-paths sweep; a changed sweep did not measure the write surface` });
|
|
324
347
|
}
|
|
348
|
+
if (saves && doc.scope.provability) {
|
|
349
|
+
const pv = doc.scope.provability;
|
|
350
|
+
const total = pv.mocked + pv.unmocked + pv.none;
|
|
351
|
+
// Every target is classified exactly once. A surface whose provability
|
|
352
|
+
// does not account for all of it has a bucket nobody looked in.
|
|
353
|
+
if (total !== doc.scope.targets) errors.push({ path: '/scope/provability', message: `provability accounts for ${total} files; targets is ${doc.scope.targets}` });
|
|
354
|
+
}
|
|
325
355
|
if (saves && doc.scope.writeSites !== undefined && doc.scope.targets > doc.scope.writeSites) {
|
|
326
356
|
errors.push({ path: '/scope/targets', message: `targets (${doc.scope.targets}) exceeds writeSites (${doc.scope.writeSites}); every target is a file with at least one write` });
|
|
327
357
|
}
|
|
@@ -343,6 +373,12 @@ const semantic = {
|
|
|
343
373
|
const notKilled = probed - (doc.counts.killed ?? 0);
|
|
344
374
|
if (doc.findings.length !== notKilled) errors.push({ path: '/findings', message: `${doc.findings.length} findings for ${notKilled} records that were not killed; findings are every non-killed record` });
|
|
345
375
|
|
|
376
|
+
// An ordering that claims to have learned from something must name it.
|
|
377
|
+
if (doc.ordering) {
|
|
378
|
+
if (doc.ordering.observed > 0 && doc.ordering.sources.length === 0) errors.push({ path: '/ordering/sources', message: 'an ordering learned from evidence must name the documents it was learned from' });
|
|
379
|
+
if (doc.ordering.observed === 0 && doc.ordering.sources.length > 0) errors.push({ path: '/ordering/observed', message: 'sources are named but nothing was observed; the ordering used the shipped prior' });
|
|
380
|
+
}
|
|
381
|
+
|
|
346
382
|
const gating = doc.findings.filter((f) => f.verdict === 'survived' || f.verdict === 'nocover').length;
|
|
347
383
|
if (gating > 0 && doc.exitCode !== 1) errors.push({ path: '/exitCode', message: 'a survived or nocover finding must exit 1; a sweep never passes over a deliberate break nothing noticed' });
|
|
348
384
|
if (gating === 0 && doc.exitCode !== 0) errors.push({ path: '/exitCode', message: 'exit 1 requires a survived or nocover finding; a sweep does not fail on its own unanchorable proposal' });
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$id": "urn:claimspec:v1:concerns",
|
|
3
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
|
+
"title": "claimspec v1 — concerns",
|
|
5
|
+
"description": "A concern is one sentence a human writes that generates faults across a whole surface: it says WHERE to look and WHICH shapes of break are relevant. It is the rung between writing nothing and writing a claim per behaviour. A concern is never a claim and never becomes one by itself — it says where to look, the probe says what it found, and a human states the sentence worth defending. Nothing in this document is a promise about the code; only a claim is that.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["schemaVersion", "concerns"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"$schema": { "type": "string" },
|
|
11
|
+
"schemaVersion": { "const": 1 },
|
|
12
|
+
"concerns": {
|
|
13
|
+
"type": "array",
|
|
14
|
+
"items": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"required": ["id", "statement"],
|
|
17
|
+
"additionalProperties": false,
|
|
18
|
+
"properties": {
|
|
19
|
+
"id": { "$ref": "urn:claimspec:v1:common#/$defs/identifier", "description": "Declaring an id that matches a built-in REPLACES it, which is how a project retunes a default for an ORM or a layout this tool does not recognise. The replacement is reported, never silent." },
|
|
20
|
+
"statement": { "type": "string", "minLength": 1, "description": "The sentence, in plain language. It describes a KIND of promise, not one behaviour: \"every write persists the data it was given\", not \"the register action stores password_hash\". The second is a claim." },
|
|
21
|
+
"severity": { "$ref": "urn:claimspec:v1:common#/$defs/severity" },
|
|
22
|
+
"targets": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"required": ["kind"],
|
|
25
|
+
"additionalProperties": false,
|
|
26
|
+
"description": "Where this concern applies. `write-sites` asks the write enumerator. `glob` matches paths. `changed` defers to whatever diff the caller is already measuring — a concern does not re-derive what the gate computed.",
|
|
27
|
+
"properties": {
|
|
28
|
+
"kind": { "enum": ["write-sites", "glob", "changed"] },
|
|
29
|
+
"globs": { "type": "array", "items": { "type": "string", "minLength": 1 } }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"faultClasses": {
|
|
33
|
+
"type": ["array", "null"],
|
|
34
|
+
"items": { "$ref": "urn:claimspec:v1:common#/$defs/faultClass" },
|
|
35
|
+
"description": "Which producers are relevant. `null` or absent means every class, the honest default for a concern with no shape to narrow by. Narrowing is what makes one sentence useful across two hundred files: a persistence concern that also reported every altered return value would bury the payload findings it exists to surface."
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|