testguard-cli 0.15.0__tar.gz → 0.15.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.15.0 → testguard_cli-0.15.1}/.github/scripts/install-smoke.mjs +29 -1
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/CHANGELOG.md +33 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/PKG-INFO +19 -5
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/README.md +18 -4
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/action.yml +6 -1
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/docs/testguard-explained.html +1 -1
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/docs/testguard-explained.pdf +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/package-lock.json +2 -2
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/package.json +1 -1
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/packaging/gitlab/testguard.gitlab-ci.yml +2 -2
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/packaging/homebrew/testguard.rb +2 -2
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/pyproject.toml +1 -1
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/GATE-SEMANTICS.md +34 -5
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/schemas.test.mjs +20 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/lib/validate.mjs +26 -1
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/schemas/common.schema.json +10 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/schemas/gate.schema.json +1 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/schemas/status.schema.json +2 -1
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/cli.mjs +30 -3
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/commands/probe.mjs +13 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/gate/changed.mjs +40 -3
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/init/templates/SKILL.md +1 -1
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/probe.mjs +1 -1
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/runners/discovery.mjs +4 -2
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/runners/shared.mjs +16 -1
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/status/status.mjs +6 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/status/surface.mjs +3 -2
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/claim-selection.test.mjs +52 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/gate.test.mjs +38 -2
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/runner-discovery.test.mjs +35 -2
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/runner-vitest.test.mjs +39 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/testguard.claims.json +61 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.agents/skills/testguard/SKILL.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.docguard.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.docguardignore +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.gitattributes +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.github/dependabot.yml +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.github/scripts/approve-held-runs.sh +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.github/scripts/build-one-pager.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.github/scripts/check-cost-budget.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.github/scripts/check-python-fixture.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.github/scripts/publish-homebrew-tap.sh +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.github/scripts/sync-release-version.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.github/workflows/auto-merge.yml +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.github/workflows/ci.yml +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.github/workflows/release.yml +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.github/workflows/scheduled-release.yml +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.github/workflows/supply-chain.yml +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.gitignore +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.npmignore +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.npmrc +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.pre-commit-config.yaml +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/.pre-commit-hooks.yaml +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/AGENTS.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/CLAUDE.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/CODE_OF_CONDUCT.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/CONTRIBUTING.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/DRIFT-LOG.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/LICENSE +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/PRIVACY.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/SECURITY.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/SUPPORT.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/bench/README.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/cli/testguard.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/docs-canonical/ARCHITECTURE.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/docs-canonical/DATA-MODEL.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/docs-canonical/ENVIRONMENT.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/docs-canonical/PRIOR-ART.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/docs-canonical/REQUIREMENTS.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/docs-canonical/SECURITY.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/docs-canonical/TEST-SPEC.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer/.gitignore +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer/README.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer/expected.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer/package.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer/src/export.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer/src/redact.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer/test/flaky.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer/test/redact.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer/testguard.claims.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer/vitest.config.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-jest/.gitignore +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-jest/README.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-jest/expected.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-jest/jest.config.js +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-jest/package.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-jest/src/export.js +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-jest/src/redact.js +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-jest/test/redact.test.js +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-jest/testguard.claims.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/.gitignore +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/README.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/expected.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/package-lock.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/package.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-python/.gitignore +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-python/README.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-python/demo/__init__.py +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-python/demo/export.py +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-python/demo/orphan.py +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-python/demo/pipeline.py +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-python/demo/redact.py +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-python/expected.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-python/testguard.claims.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-python/tests/test_patched.py +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/fixtures/known-answer-python/tests/test_redact.py +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/README.md +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/baseline.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/brief.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/calibration-websec.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/calibration.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/claims-methods.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/claims.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/evidence-negative-control.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/evidence-persistence-signal.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/evidence-provisional.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/evidence-python.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/evidence-scan-method.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/evidence.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/gate.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/ignore.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/replay.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/examples/status.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/baseline.zero-count.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.ci-not-wilson.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.fallback-without-basis.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.p-not-positives-over-n.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.absolute-path.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.noop-fault.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.persistence-signal-not-a-defender.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.persistence-signal-on-a-non-survivor.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.persistence-signal-without-reason.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/ignore.short-reason.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/replay.nocover-when-no-test-file-remained.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/status.claim-without-file.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/status.invalid-anchor-hidden.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/status.partial-scope-counts-disagree.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/status.surface-counts-disagree.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/lib/fingerprint.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/lib/wilson.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/schemas/baseline.schema.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/schemas/brief.schema.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/schemas/calibration.schema.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/schemas/claims.schema.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/schemas/concerns.schema.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/schemas/evidence.schema.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/schemas/ignore.schema.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/schemas/replay.schema.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/spec/schemas/sweep.schema.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/admit/admit.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/baseline/baseline.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/brief/brief.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/claims/anchors.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/claims/annotations.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/claims/load.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/claims/removed.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/command-budget.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/commands/admit.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/commands/baseline.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/commands/brief.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/commands/claims.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/commands/concerns.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/commands/gate.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/commands/init.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/commands/mcp.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/commands/replay.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/commands/scaffold.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/commands/status.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/commands/sweep.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/evidence/writer.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/git.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/init/init.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/mcp/server.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/mcp/tools.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/attribution.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/classify.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/contention.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/cost.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/discover.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/imports.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/independence.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/inject.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/mocks.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/progress.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/pyimports.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/rank.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/runners/index.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/runners/jest.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/runners/playwright.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/runners/python/_testguard_report.py +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/runners/python.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/runners/vitest.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/probe/worktree.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/render.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/replay/label.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/replay/replay.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/scaffold/producers.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/scaffold/producers.python.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/scaffold/scaffold.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/supply/concerns.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/supply/feedback.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/supply/persistence.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/supply/savepath.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/supply/select.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/sweep/sweep.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/util/glob.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/src/util/hash.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/admit-decision.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/admit.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/annotations.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/attribution.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/baseline.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/brief.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/calibration.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/claims-anchors.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/claims-since.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/classify.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/command-budget-write-boundaries.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/command-budget.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/contention.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/cost.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/discover.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/evidence-persistence-signal.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/gitlab-template.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/glob.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/imports.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/independence.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/init.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/inject.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/label.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/mcp.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/mocks.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/negative-control.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/one-pager.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/probe-dirty-paths.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/probe-error.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/probe-native-manifest.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/probe-persistence-signal.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/probe-preconditions.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/probe.fixture.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/probe.jest.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/probe.playwright.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/probe.python.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/progress.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/pyimports.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/rank-aliases.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/release-approval.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/release-sync.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/replay-mixed-runners.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/replay-no-test-file-left.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/replay-python-defenders.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/replay-verdict.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/replay.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/runner-argv.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/runner-command.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/runner-playwright.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/runner-python.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/runner-selection-blind.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/samples/playwright-report.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/samples/python-report-collect-error.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/samples/python-report.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/scaffold.python.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/scaffold.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/snapshot.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/status.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/supply-concerns.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/supply-feedback.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/supply-persistence.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/supply-savepath.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/supply-select.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/surface.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/sweep.fixture.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/sweep.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/vitest-config.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/worktree.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/test/writer.test.mjs +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/testguard.cost-budget.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/testguard.ignore.json +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/testguard_cli/__init__.py +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/testguard_cli/wrapper.py +0 -0
- {testguard_cli-0.15.0 → testguard_cli-0.15.1}/vitest.config.mjs +0 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* devDependency and crashed on startup for every npm/npx/pip/brew user.
|
|
8
8
|
*/
|
|
9
9
|
import { execFileSync } from 'node:child_process';
|
|
10
|
-
import { mkdtempSync, mkdirSync, rmSync, cpSync, readdirSync } from 'node:fs';
|
|
10
|
+
import { mkdtempSync, mkdirSync, rmSync, cpSync, readdirSync, writeFileSync, existsSync } from 'node:fs';
|
|
11
11
|
import { tmpdir } from 'node:os';
|
|
12
12
|
import { join, resolve } from 'node:path';
|
|
13
13
|
|
|
@@ -29,6 +29,29 @@ try {
|
|
|
29
29
|
|
|
30
30
|
const bin = join(consumer, 'node_modules', '.bin', process.platform === 'win32' ? 'testguard.cmd' : 'testguard');
|
|
31
31
|
const version = run(bin, ['--version'], consumer).trim();
|
|
32
|
+
const emptyProject = join(scratch, 'empty-adoption');
|
|
33
|
+
mkdirSync(emptyProject);
|
|
34
|
+
writeFileSync(join(emptyProject, 'testguard.claims.json'), JSON.stringify({ schemaVersion: 1, claims: [] }));
|
|
35
|
+
const emptyStatus = JSON.parse(run(bin, ['probe', emptyProject, '--allow-empty', '--json'], consumer));
|
|
36
|
+
if (emptyStatus.state !== 'no-claims' || emptyStatus.run || existsSync(join(emptyProject, '.testguard', 'evidence.json'))) throw new Error('installed empty-adoption probe invented verification evidence');
|
|
37
|
+
const replayHelp = run(bin, ['replay', '--help'], consumer);
|
|
38
|
+
if (!replayHelp.includes('--since') || replayHelp.includes('--node-modules')) throw new Error('installed command help is not command-specific');
|
|
39
|
+
writeFileSync(join(emptyProject, 'vite.config.mjs'), "export default { base: '/', plugins: [{ configureServer(s) { s.middlewares.use('/__rooms', () => {}); const parts = '/__room'.split('/'); } }] };\n");
|
|
40
|
+
const installedDiscovery = join(consumer, 'node_modules', 'testguard-cli', 'src', 'probe', 'runners', 'discovery.mjs');
|
|
41
|
+
run(process.execPath, ['--input-type=module', '-e',
|
|
42
|
+
"import { pathToFileURL } from 'node:url'; const { hashDiscoveryConfigs } = await import(pathToFileURL(process.argv[1])); hashDiscoveryConfigs(process.argv[2], ['vite.config.mjs']);",
|
|
43
|
+
installedDiscovery, emptyProject,
|
|
44
|
+
], consumer);
|
|
45
|
+
const installedRunner = join(consumer, 'node_modules', 'testguard-cli', 'src', 'probe', 'runners', 'shared.mjs');
|
|
46
|
+
run(process.execPath, ['--input-type=module', '-e',
|
|
47
|
+
`import { pathToFileURL } from 'node:url'; const { parseReport } = await import(pathToFileURL(process.argv[1]));
|
|
48
|
+
const result = parseReport({ success: false, numTotalTests: 2, numFailedTests: 2, testResults: [{ name: 'test.mjs', status: 'failed', assertionResults: [
|
|
49
|
+
{ status: 'failed', failureMessages: ['AssertionError: expected /specify timed out/'] },
|
|
50
|
+
{ status: 'failed', failureMessages: ['Error: Test timed out in 1000ms.'] },
|
|
51
|
+
] }] }, 1);
|
|
52
|
+
if (result.timeouts !== 1 || result.run.assertionFailures !== 1) throw new Error('installed runner confuses assertion text with timeout identity');`,
|
|
53
|
+
installedRunner,
|
|
54
|
+
], consumer);
|
|
32
55
|
const claims = run(bin, ['claims', '.'], consumer);
|
|
33
56
|
const draft = JSON.parse(run(bin, ['scaffold', 'src/redact.mjs', '--json'], consumer));
|
|
34
57
|
const installedValidator = join(consumer, 'node_modules', 'testguard-cli', 'spec', 'lib', 'validate.mjs');
|
|
@@ -40,9 +63,14 @@ try {
|
|
|
40
63
|
const { validate } = await import(pathToFileURL(process.argv[1]));
|
|
41
64
|
const result = validate('evidence', JSON.parse(readFileSync(process.argv[2], 'utf8')));
|
|
42
65
|
if (!result.ok) throw new Error(result.errors.map((error) => \`${'${error.path}: ${error.message}'}\`).join('; '));
|
|
66
|
+
const gate = JSON.parse(readFileSync(process.argv[3], 'utf8'));
|
|
67
|
+
gate.nested = [{ project: 'backend', files: ['backend/src/guard.mjs'] }]; gate.changed++;
|
|
68
|
+
const nestedResult = validate('gate', gate);
|
|
69
|
+
if (!nestedResult.ok) throw new Error('installed package rejected the nested-project gate contract');
|
|
43
70
|
process.stdout.write('ok');`,
|
|
44
71
|
installedValidator,
|
|
45
72
|
candidateEvidence,
|
|
73
|
+
join(root, 'spec', 'conformance', 'examples', 'gate.json'),
|
|
46
74
|
], consumer).trim();
|
|
47
75
|
if (!draft.claims?.length) throw new Error('scaffold produced no claims from the installed tarball');
|
|
48
76
|
if (!/^\d+\.\d+\.\d+/.test(version)) throw new Error(`unexpected --version output: ${version}`);
|
|
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.15.1] - 2026-09-30
|
|
11
|
+
|
|
12
|
+
Automated weekly release — everything merged since `v0.15.0`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- fix: runner timeout identity and reported adoption regressions (#180)
|
|
17
|
+
- chore(homebrew): sha256 for v0.15.0 (#177)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Assertion messages quoting "timed out" no longer become runner timeouts.
|
|
23
|
+
Native timeout headers and structured timeout failures remain non-detections
|
|
24
|
+
(#179).
|
|
25
|
+
- Configuration dependency hashing no longer rejects harmless absolute-looking
|
|
26
|
+
strings such as Vite URL bases, middleware routes, or `split('/')`. Existing
|
|
27
|
+
outside-project file dependencies and symlinks still fail closed (#178).
|
|
28
|
+
- Discovery failures no longer assert that dependencies are missing when the
|
|
29
|
+
runner resolved successfully but its configuration could not be discovered.
|
|
30
|
+
- Each subcommand's `--help` now shows its own options and an example; global
|
|
31
|
+
`--help` exits successfully and points to command-specific help (#120).
|
|
32
|
+
- Parent gates, status change reports, and claimed-surface counts respect valid
|
|
33
|
+
nested TestGuard project boundaries. Delegated files are explicitly reported
|
|
34
|
+
and require a separate child gate; invalid or symlinked markers fail (#150).
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- Explicit `probe --allow-empty` adoption mode, also available as an opt-in
|
|
39
|
+
GitHub Action input. A valid empty claims file skips verification without
|
|
40
|
+
writing evidence; JSON keeps the `no-claims` state. Default empty probes,
|
|
41
|
+
invalid claims, and explicit claim selections still fail (#176).
|
|
42
|
+
|
|
10
43
|
## [0.15.0] - 2026-09-29
|
|
11
44
|
|
|
12
45
|
Automated weekly release — everything merged since `v0.14.2`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: testguard-cli
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.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.15.
|
|
154
|
+
| GitHub Action | `uses: raccioly/testguard@v0.15.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.15.
|
|
156
|
+
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.15.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
|
|
@@ -431,6 +431,20 @@ is missing. TestGuard itself still makes no network calls.
|
|
|
431
431
|
|
|
432
432
|
### Starting from zero claims
|
|
433
433
|
|
|
434
|
+
During gradual adoption, use `testguard probe --allow-empty` to skip a valid
|
|
435
|
+
empty claims file without blocking a push. It explicitly reports that no
|
|
436
|
+
verification was performed and writes no evidence; `--json` retains the
|
|
437
|
+
`no-claims` state. Missing or invalid claims files still fail. Run `gate`
|
|
438
|
+
separately to enforce changed-file coverage. For the pre-commit probe hook,
|
|
439
|
+
set `args: [--allow-empty]`; for the GitHub Action, set `allow-empty: 'true'`.
|
|
440
|
+
The default probe still exits 2 when no claims exist.
|
|
441
|
+
|
|
442
|
+
In a monorepo, a valid nested `testguard.claims.json` marks a separate project.
|
|
443
|
+
The parent gate reports its changed files as delegated and does not count them
|
|
444
|
+
as parent coverage. Run `gate` in each child project as well; a passing parent
|
|
445
|
+
gate does not establish that child coverage passed. Invalid child claims fail
|
|
446
|
+
evaluation instead of hiding files.
|
|
447
|
+
|
|
434
448
|
`gate` names the changed files that carry no claim, and stops there —
|
|
435
449
|
correctly, because stating a claim is a human act. But a project adopting this
|
|
436
450
|
tool reads that list, has nothing to compare it against, and closes the tab.
|
|
@@ -687,12 +701,12 @@ detected base that does not resolve is a warning for `status` and `brief`
|
|
|
687
701
|
# GitHub Actions
|
|
688
702
|
- uses: actions/checkout@v4
|
|
689
703
|
with: { fetch-depth: 0 }
|
|
690
|
-
- uses: raccioly/testguard@v0.15.
|
|
704
|
+
- uses: raccioly/testguard@v0.15.1
|
|
691
705
|
with: { command: gate }
|
|
692
706
|
|
|
693
707
|
# GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
|
|
694
708
|
include:
|
|
695
|
-
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.15.
|
|
709
|
+
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.15.1/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
696
710
|
inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
|
|
697
711
|
```
|
|
698
712
|
|
|
@@ -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.15.
|
|
130
|
+
| GitHub Action | `uses: raccioly/testguard@v0.15.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.15.
|
|
132
|
+
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.15.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
|
|
@@ -407,6 +407,20 @@ is missing. TestGuard itself still makes no network calls.
|
|
|
407
407
|
|
|
408
408
|
### Starting from zero claims
|
|
409
409
|
|
|
410
|
+
During gradual adoption, use `testguard probe --allow-empty` to skip a valid
|
|
411
|
+
empty claims file without blocking a push. It explicitly reports that no
|
|
412
|
+
verification was performed and writes no evidence; `--json` retains the
|
|
413
|
+
`no-claims` state. Missing or invalid claims files still fail. Run `gate`
|
|
414
|
+
separately to enforce changed-file coverage. For the pre-commit probe hook,
|
|
415
|
+
set `args: [--allow-empty]`; for the GitHub Action, set `allow-empty: 'true'`.
|
|
416
|
+
The default probe still exits 2 when no claims exist.
|
|
417
|
+
|
|
418
|
+
In a monorepo, a valid nested `testguard.claims.json` marks a separate project.
|
|
419
|
+
The parent gate reports its changed files as delegated and does not count them
|
|
420
|
+
as parent coverage. Run `gate` in each child project as well; a passing parent
|
|
421
|
+
gate does not establish that child coverage passed. Invalid child claims fail
|
|
422
|
+
evaluation instead of hiding files.
|
|
423
|
+
|
|
410
424
|
`gate` names the changed files that carry no claim, and stops there —
|
|
411
425
|
correctly, because stating a claim is a human act. But a project adopting this
|
|
412
426
|
tool reads that list, has nothing to compare it against, and closes the tab.
|
|
@@ -663,12 +677,12 @@ detected base that does not resolve is a warning for `status` and `brief`
|
|
|
663
677
|
# GitHub Actions
|
|
664
678
|
- uses: actions/checkout@v4
|
|
665
679
|
with: { fetch-depth: 0 }
|
|
666
|
-
- uses: raccioly/testguard@v0.15.
|
|
680
|
+
- uses: raccioly/testguard@v0.15.1
|
|
667
681
|
with: { command: gate }
|
|
668
682
|
|
|
669
683
|
# GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
|
|
670
684
|
include:
|
|
671
|
-
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.15.
|
|
685
|
+
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.15.1/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
672
686
|
inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
|
|
673
687
|
```
|
|
674
688
|
|
|
@@ -35,6 +35,10 @@ inputs:
|
|
|
35
35
|
description: 'Skip re-running survivors against the whole suite'
|
|
36
36
|
required: false
|
|
37
37
|
default: 'false'
|
|
38
|
+
allow-empty:
|
|
39
|
+
description: 'probe only: explicitly skip a valid empty claims file during adoption; writes no evidence. Run gate separately.'
|
|
40
|
+
required: false
|
|
41
|
+
default: 'false'
|
|
38
42
|
changed-ref:
|
|
39
43
|
description: 'gate only: the reference the change is measured against (default: origin/<base branch> of the pull request). Check out with fetch-depth: 0 so it resolves.'
|
|
40
44
|
required: false
|
|
@@ -46,7 +50,7 @@ inputs:
|
|
|
46
50
|
version:
|
|
47
51
|
description: 'testguard-cli version to run'
|
|
48
52
|
required: false
|
|
49
|
-
default: '0.15.
|
|
53
|
+
default: '0.15.1'
|
|
50
54
|
node-version:
|
|
51
55
|
description: 'Node.js version (20+)'
|
|
52
56
|
required: false
|
|
@@ -82,6 +86,7 @@ runs:
|
|
|
82
86
|
if [ "${{ inputs.command }}" = "probe" ]; then
|
|
83
87
|
ARGS+=(--severity "${{ inputs.severity }}" --confirm "${{ inputs.confirm }}" --budget "${{ inputs.budget }}")
|
|
84
88
|
[ "${{ inputs.no-escalate }}" = "true" ] && ARGS+=(--no-escalate)
|
|
89
|
+
[ "${{ inputs.allow-empty }}" = "true" ] && ARGS+=(--allow-empty)
|
|
85
90
|
[ "${{ inputs.runner }}" != "auto" ] && ARGS+=(--runner "${{ inputs.runner }}")
|
|
86
91
|
[ -n "${{ inputs.python }}" ] && ARGS+=(--python "${{ inputs.python }}")
|
|
87
92
|
fi
|
|
@@ -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.15.
|
|
147
|
+
<div class="meta">Technical Brief · v0.15.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,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testguard-cli",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "testguard-cli",
|
|
9
|
-
"version": "0.15.
|
|
9
|
+
"version": "0.15.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.15.
|
|
3
|
+
"version": "0.15.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.15.
|
|
6
|
+
# - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.15.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.15.
|
|
32
|
+
default: "0.15.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.15.
|
|
19
|
-
sha256 "
|
|
18
|
+
url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.15.1.tgz"
|
|
19
|
+
sha256 "357f37905e99bf2d1a7487374bc2fc2b97983fd8d355b2e77d72e03fb0f2f8c7"
|
|
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.15.
|
|
7
|
+
version = "0.15.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" }
|
|
@@ -36,7 +36,11 @@ Rules that follow from the table:
|
|
|
36
36
|
that failed and then passed on retry (Playwright's `flaky`) is a
|
|
37
37
|
**non-green run** even when the runner exits 0, and a test the runner
|
|
38
38
|
reports as timed out (`timedOut`) is a timeout, never an assertion
|
|
39
|
-
failure.
|
|
39
|
+
failure. Jest-compatible timeout messages are recognized by native runner
|
|
40
|
+
error headers or structured timeout identity, not by words quoted in an
|
|
41
|
+
assertion, stack frame, or source excerpt. Structured assertion identity
|
|
42
|
+
does not hide a separate timeout in the same failed test.
|
|
43
|
+
Python is read the same way: an `AssertionError` and any other
|
|
40
44
|
exception raised by the test body both count, because both are the suite
|
|
41
45
|
rejecting the behaviour; a collection error — a module that will not
|
|
42
46
|
import, which is what a replacement that does not parse looks like from
|
|
@@ -172,6 +176,11 @@ Rules that follow from the table:
|
|
|
172
176
|
competing owned runners or a declared defender absent from every manifest
|
|
173
177
|
are precondition failures, never heuristic routing decisions.
|
|
174
178
|
|
|
179
|
+
Static string literals are conservative dependency candidates, not proof
|
|
180
|
+
of a filesystem dependency. Missing candidates and ordinary directories
|
|
181
|
+
(including `/` in URL bases and string separators) are ignored. An existing
|
|
182
|
+
candidate file outside the project, or a candidate symlink, is rejected.
|
|
183
|
+
|
|
175
184
|
14. **Barrel resolution follows symbols, not module reachability.** Automatic
|
|
176
185
|
discovery may traverse a re-export chain only for the binding the test
|
|
177
186
|
actually imports. Importing an unrelated symbol from a broad barrel does
|
|
@@ -494,10 +503,22 @@ Rules:
|
|
|
494
503
|
`unclaimed-changes` and its next action is to write the claim, before any
|
|
495
504
|
`unproven` finding is surfaced. The brief renders them first. The claim is
|
|
496
505
|
written before more code.
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
506
|
+
7. **A descendant claims file defines a separate project.** The first
|
|
507
|
+
descendant directory on a changed file's path with a valid, regular,
|
|
508
|
+
non-symlinked `testguard.claims.json` owns that subtree. Invalid or unreadable
|
|
509
|
+
markers fail evaluation; they cannot hide unclaimed files. Parent gate and
|
|
510
|
+
status report the delegation as optional `nested` entries with project and
|
|
511
|
+
file paths relative to the parent, and instruct the operator to run that
|
|
512
|
+
project's gate. Delegation is not coverage, an ignore reliance, or proof
|
|
513
|
+
that the child passed. Parent source-surface counts omit these subtrees.
|
|
514
|
+
Removing the marker restores parent evaluation. The accounting identity is
|
|
515
|
+
`changed = evaluated + excluded.length + sum(nested[].files.length)`;
|
|
516
|
+
`--strict` still fails a non-empty change with zero parent evaluation.
|
|
517
|
+
|
|
518
|
+
Exit codes: `0` every changed parent-owned source file is claimed or excused
|
|
519
|
+
(or nothing changed); delegated projects still require separate child gates.
|
|
520
|
+
`1` at least one uncovered file (or strict mode over an
|
|
521
|
+
all-excluded or all-delegated change); `2` the change cannot be evaluated (unresolvable
|
|
501
522
|
reference, invalid claims or ignore file, no repository); `3` no reference.
|
|
502
523
|
|
|
503
524
|
## Anchor preflight
|
|
@@ -548,3 +569,11 @@ evidence; they simply do not turn CI red.
|
|
|
548
569
|
A tool must never exit `0` because it had nothing to check. If the claims
|
|
549
570
|
file is empty or every claim is out of scope, that is reported explicitly and
|
|
550
571
|
the exit code is `2`.
|
|
572
|
+
|
|
573
|
+
`probe --allow-empty` is an explicit adoption exception for a valid claims
|
|
574
|
+
file containing zero claims, with no `--claim` selection. It exits `0`, says
|
|
575
|
+
that verification was skipped, and writes no evidence or baseline. Its JSON
|
|
576
|
+
output is the existing `no-claims` status document, without run metadata;
|
|
577
|
+
it never reports a clean measurement. Missing or invalid claims still fail,
|
|
578
|
+
and selected claims cannot be silently skipped. Run `gate` separately to
|
|
579
|
+
enforce coverage of changed files during adoption. Integrations must opt in.
|
|
@@ -13,6 +13,26 @@ const here = dirname(fileURLToPath(import.meta.url));
|
|
|
13
13
|
const load = (dir, f) => JSON.parse(readFileSync(join(here, dir, f), 'utf8'));
|
|
14
14
|
|
|
15
15
|
describe('claimspec v1 — conformance', () => {
|
|
16
|
+
it('accounts for descendant-project delegation without calling it coverage or double-counting it', () => {
|
|
17
|
+
const gate = load('examples', 'gate.json');
|
|
18
|
+
gate.nested = [{ project: 'backend', files: ['backend/src/guard.mjs'] }];
|
|
19
|
+
gate.changed++;
|
|
20
|
+
expect(validate('gate', gate).errors).toEqual([]);
|
|
21
|
+
gate.changed--;
|
|
22
|
+
expect(validate('gate', gate).errors.map((e) => e.path)).toContain('/changed');
|
|
23
|
+
gate.changed++;
|
|
24
|
+
gate.nested[0].files = ['src/guard.mjs'];
|
|
25
|
+
expect(validate('gate', gate).errors.map((e) => e.path)).toContain('/nested/0/files');
|
|
26
|
+
gate.nested[0].files = ['backend/src/guard.mjs'];
|
|
27
|
+
gate.covered.push({ file: 'backend/src/guard.mjs', by: 'fault', claimIds: ['C-1'] });
|
|
28
|
+
gate.evaluated++;
|
|
29
|
+
gate.changed++;
|
|
30
|
+
expect(validate('gate', gate).errors.map((e) => e.path)).toContain('/nested/0/files');
|
|
31
|
+
gate.covered.pop(); gate.evaluated--; gate.changed--;
|
|
32
|
+
gate.nested.push({ project: 'backend', files: ['backend/src/second.mjs'] });
|
|
33
|
+
gate.changed++;
|
|
34
|
+
expect(validate('gate', gate).errors.map((e) => e.path)).toContain('/nested/1/project');
|
|
35
|
+
});
|
|
16
36
|
describe('every valid example passes', () => {
|
|
17
37
|
for (const f of readdirSync(join(here, 'examples')).filter((f) => f.endsWith('.json'))) {
|
|
18
38
|
const kind = KINDS.find((k) => f.startsWith(k));
|
|
@@ -28,6 +28,24 @@ const schemaFor = (kind) => {
|
|
|
28
28
|
return v;
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
const nestedErrors = (nested = [], prefix = '/nested', occupied = []) => {
|
|
32
|
+
const errors = [];
|
|
33
|
+
const projects = new Set();
|
|
34
|
+
const files = new Set(occupied);
|
|
35
|
+
for (const [i, entry] of nested.entries()) {
|
|
36
|
+
const p = `${prefix}/${i}`;
|
|
37
|
+
if (entry.project.split('/').some((part) => !part || part === '.' || part === '..') || entry.project.includes('\\')) errors.push({ path: `${p}/project`, message: 'nested project must be a canonical descendant directory' });
|
|
38
|
+
if (projects.has(entry.project)) errors.push({ path: `${p}/project`, message: 'nested project is duplicated' });
|
|
39
|
+
projects.add(entry.project);
|
|
40
|
+
for (const file of entry.files) {
|
|
41
|
+
if (!file.startsWith(`${entry.project}/`) || file.split('/').some((part) => !part || part === '.' || part === '..') || file.includes('\\')) errors.push({ path: `${p}/files`, message: 'nested file must remain beneath its canonical project directory' });
|
|
42
|
+
if (files.has(file)) errors.push({ path: `${p}/files`, message: 'nested file is counted in more than one bucket' });
|
|
43
|
+
files.add(file);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return errors;
|
|
47
|
+
};
|
|
48
|
+
|
|
31
49
|
// Rules JSON Schema cannot express. Each returns an array of {path, message}.
|
|
32
50
|
const semantic = {
|
|
33
51
|
claims(doc) {
|
|
@@ -243,6 +261,11 @@ const semantic = {
|
|
|
243
261
|
|
|
244
262
|
status(doc) {
|
|
245
263
|
const errors = [];
|
|
264
|
+
if (doc.changes?.nested) {
|
|
265
|
+
errors.push(...nestedErrors(doc.changes.nested, '/changes/nested', doc.changes.uncovered.map((file) => file.file)));
|
|
266
|
+
const nestedCount = doc.changes.nested.reduce((count, entry) => count + entry.files.length, 0);
|
|
267
|
+
if (doc.changes.changed !== doc.changes.evaluated + doc.changes.excluded + nestedCount) errors.push({ path: '/changes/changed', message: 'changed must account for evaluated, excluded, and nested files' });
|
|
268
|
+
}
|
|
246
269
|
if (['write-test', 'review-fault-change', 'repair-fault'].includes(doc.next.action) && !doc.next.target) errors.push({ path: '/next/target', message: `${doc.next.action} requires a target` });
|
|
247
270
|
if (doc.next.action === 'claim' && !doc.next.file) errors.push({ path: '/next/file', message: 'claim requires the file the claim is about' });
|
|
248
271
|
if (doc.state === 'no-claims' && doc.counts.claims !== 0) errors.push({ path: '/counts/claims', message: 'no-claims with a non-zero claim count' });
|
|
@@ -311,8 +334,10 @@ const semantic = {
|
|
|
311
334
|
|
|
312
335
|
gate(doc) {
|
|
313
336
|
const errors = [];
|
|
337
|
+
const nestedCount = (doc.nested ?? []).reduce((count, entry) => count + entry.files.length, 0);
|
|
338
|
+
errors.push(...nestedErrors(doc.nested, '/nested', [...doc.covered, ...doc.uncovered, ...doc.excluded].map((entry) => entry.file)));
|
|
314
339
|
if (doc.evaluated !== doc.covered.length + doc.uncovered.length) errors.push({ path: '/evaluated', message: `evaluated (${doc.evaluated}) must equal covered (${doc.covered.length}) + uncovered (${doc.uncovered.length})` });
|
|
315
|
-
if (doc.changed !== doc.evaluated + doc.excluded.length) errors.push({ path: '/changed', message: `changed (${doc.changed}) must equal evaluated (${doc.evaluated}) + excluded (${doc.excluded.length})` });
|
|
340
|
+
if (doc.changed !== doc.evaluated + doc.excluded.length + nestedCount) errors.push({ path: '/changed', message: `changed (${doc.changed}) must equal evaluated (${doc.evaluated}) + excluded (${doc.excluded.length}) + nested (${nestedCount})` });
|
|
316
341
|
if (doc.uncovered.length > 0 && doc.exitCode !== 1) errors.push({ path: '/exitCode', message: 'an uncovered changed file must exit 1; the gate never passes over unclaimed code' });
|
|
317
342
|
if (doc.uncovered.length === 0 && doc.exitCode === 1 && !(doc.strict && doc.changed > 0 && doc.evaluated === 0)) errors.push({ path: '/exitCode', message: 'exit 1 without uncovered files is only valid under --strict when a non-empty change evaluated nothing' });
|
|
318
343
|
doc.covered.forEach((c, i) => {
|
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
"title": "claimspec v1 \u2014 shared definitions",
|
|
5
5
|
"description": "Definitions shared by every Guard-spec format. Referenced as urn:claimspec:v1:common#/$defs/<name>.",
|
|
6
6
|
"$defs": {
|
|
7
|
+
"nestedProject": {
|
|
8
|
+
"description": "Changed files delegated to a separately validated descendant TestGuard project. This is not coverage or evidence; run that project's gate separately.",
|
|
9
|
+
"type": "object",
|
|
10
|
+
"required": ["project", "files"],
|
|
11
|
+
"properties": {
|
|
12
|
+
"project": { "$ref": "#/$defs/repoPath" },
|
|
13
|
+
"files": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "$ref": "#/$defs/repoPath" } }
|
|
14
|
+
},
|
|
15
|
+
"additionalProperties": false
|
|
16
|
+
},
|
|
7
17
|
"sha256": {
|
|
8
18
|
"type": "string",
|
|
9
19
|
"pattern": "^[a-f0-9]{64}$"
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"strict": { "description": "True when a non-empty change with zero evaluated files fails instead of passing with a note.", "type": "boolean" },
|
|
18
18
|
"changed": { "description": "Files changed inside the project directory, deletions excluded.", "type": "integer", "minimum": 0 },
|
|
19
19
|
"evaluated": { "description": "covered + uncovered: the changed files that are source and not excluded.", "type": "integer", "minimum": 0 },
|
|
20
|
+
"nested": { "description": "Optional descendant-project delegations; absence means none. These files are neither covered nor evaluated by the parent.", "type": "array", "items": { "$ref": "urn:claimspec:v1:common#/$defs/nestedProject" } },
|
|
20
21
|
"excluded": {
|
|
21
22
|
"type": "array",
|
|
22
23
|
"items": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"$ref": "urn:claimspec:v1:common#/$defs/isoDateTime"
|
|
27
27
|
},
|
|
28
28
|
"state": {
|
|
29
|
-
"description": "no-claims: no claims file. unclaimed-changes: the change measured against a reference touches source files that carry no claim and no excusing ignore entry (only computed when a reference is known; it precedes every evidence state because the claim is written before more code). invalid-anchors: one or more exact fault anchors are missing or ambiguous, so probing would be unable to apply them. unprobed: claims but no confirmed evidence. evidence-stale: evidence exists but a claim, fault, target or defender changed since. provisional-only: only sub-N evidence exists. unproven: current evidence has findings not covered by the baseline. clean: every finding is killed or baselined.",
|
|
29
|
+
"description": "no-claims: no claims file or a valid empty claims file. unclaimed-changes: the change measured against a reference touches source files that carry no claim and no excusing ignore entry (only computed when a reference is known; it precedes every evidence state because the claim is written before more code). invalid-anchors: one or more exact fault anchors are missing or ambiguous, so probing would be unable to apply them. unprobed: claims but no confirmed evidence. evidence-stale: evidence exists but a claim, fault, target or defender changed since. provisional-only: only sub-N evidence exists. unproven: current evidence has findings not covered by the baseline. clean: every finding is killed or baselined.",
|
|
30
30
|
"enum": [
|
|
31
31
|
"no-claims",
|
|
32
32
|
"unclaimed-changes",
|
|
@@ -392,6 +392,7 @@
|
|
|
392
392
|
"expired"
|
|
393
393
|
],
|
|
394
394
|
"properties": {
|
|
395
|
+
"nested": { "type": "array", "items": { "$ref": "urn:claimspec:v1:common#/$defs/nestedProject" } },
|
|
395
396
|
"ref": {
|
|
396
397
|
"type": "string",
|
|
397
398
|
"minLength": 1,
|
|
@@ -75,6 +75,7 @@ probe
|
|
|
75
75
|
--in-place mutate the working tree instead of a scratch worktree
|
|
76
76
|
--no-escalate do not re-run survivors against the whole suite
|
|
77
77
|
--no-reuse re-probe claims whose inputs have not changed
|
|
78
|
+
--allow-empty adoption only: skip a valid empty claims file; writes no evidence; run gate separately
|
|
78
79
|
--quiet suppress the per-fault stream and ranked block; print only the summary and evidence path
|
|
79
80
|
--json the status document plus this run's result (records, newSinceBaseline, exitCode)
|
|
80
81
|
|
|
@@ -134,6 +135,27 @@ exit codes: 0 nothing new to prove · 1 unproven claims (or claim drift, or uncl
|
|
|
134
135
|
|
|
135
136
|
const COMMANDS = { mcp: mcpCommand, replay: replayCommand, probe: probeCommand, claims: claimsCommand, baseline: baselineCommand, brief: briefCommand, scaffold: scaffoldCommand, status: statusCommand, init: initCommand, gate: gateCommand, admit: admitCommand, sweep: sweepCommand, concerns: concernsCommand };
|
|
136
137
|
|
|
138
|
+
/** Derive command help from the same option descriptions as the overview. */
|
|
139
|
+
export function commandUsage(command) {
|
|
140
|
+
const lines = USAGE.split('\n');
|
|
141
|
+
const overview = lines.find((line) => line.startsWith(` testguard ${command} `)) ?? ` testguard ${command}`;
|
|
142
|
+
const sections = [];
|
|
143
|
+
let selected = false;
|
|
144
|
+
for (const line of lines) {
|
|
145
|
+
const heading = /^([a-z]+)(?:\s|$)/.exec(line)?.[1];
|
|
146
|
+
if (heading && Object.hasOwn(COMMANDS, heading)) selected = heading === command;
|
|
147
|
+
else if (line.startsWith('every command ') || line.startsWith('exit codes:')) selected = false;
|
|
148
|
+
if (selected) sections.push(line);
|
|
149
|
+
}
|
|
150
|
+
if (command === 'concerns') sections.push('concerns --concerns <path> read a custom concerns file; --json prints the validated inventory');
|
|
151
|
+
const examples = {
|
|
152
|
+
probe: 'probe . --confirm 3', gate: 'gate . --changed origin/main',
|
|
153
|
+
scaffold: 'scaffold src/example.mjs --json', replay: 'replay . --since HEAD~10..HEAD --max 3',
|
|
154
|
+
admit: 'admit test/example.test.mjs --claim EXAMPLE-001', sweep: 'sweep . --changed origin/main --cap 10',
|
|
155
|
+
};
|
|
156
|
+
return `${lines[0]}\n\n${overview}\n\n${sections.join('\n').trim()}\n\nCommon: --json, --help, --version\nExample: testguard ${examples[command] ?? `${command}${command === 'mcp' ? '' : ' .'}`}\n`;
|
|
157
|
+
}
|
|
158
|
+
|
|
137
159
|
export async function main(argv, io = { out: (s) => process.stdout.write(s + '\n'), err: (s) => process.stderr.write(s + '\n') }) {
|
|
138
160
|
let parsed;
|
|
139
161
|
try {
|
|
@@ -180,6 +202,7 @@ export async function main(argv, io = { out: (s) => process.stdout.write(s + '\n
|
|
|
180
202
|
'in-place': { type: 'boolean', default: false },
|
|
181
203
|
'no-escalate': { type: 'boolean', default: false },
|
|
182
204
|
'no-reuse': { type: 'boolean', default: false },
|
|
205
|
+
'allow-empty': { type: 'boolean', default: false },
|
|
183
206
|
quiet: { type: 'boolean', default: false },
|
|
184
207
|
json: { type: 'boolean', default: false },
|
|
185
208
|
text: { type: 'boolean', default: false },
|
|
@@ -202,9 +225,9 @@ export async function main(argv, io = { out: (s) => process.stdout.write(s + '\n
|
|
|
202
225
|
return 0;
|
|
203
226
|
}
|
|
204
227
|
const [command, dirArg] = positionals;
|
|
205
|
-
if (
|
|
206
|
-
io.out(USAGE);
|
|
207
|
-
return
|
|
228
|
+
if (!command) {
|
|
229
|
+
io.out(USAGE + "\nRun 'testguard <command> --help' for that command's options.");
|
|
230
|
+
return values.help ? 0 : 3;
|
|
208
231
|
}
|
|
209
232
|
const handler = COMMANDS[command];
|
|
210
233
|
if (!handler) {
|
|
@@ -212,6 +235,10 @@ export async function main(argv, io = { out: (s) => process.stdout.write(s + '\n
|
|
|
212
235
|
io.err(USAGE);
|
|
213
236
|
return 3;
|
|
214
237
|
}
|
|
238
|
+
if (values.help) {
|
|
239
|
+
io.out(commandUsage(command));
|
|
240
|
+
return 0;
|
|
241
|
+
}
|
|
215
242
|
if (![...RUNNER_NAMES, 'auto'].includes(values.runner)) {
|
|
216
243
|
io.err(`--runner must be one of ${RUNNER_NAMES.join(', ')} or auto`);
|
|
217
244
|
return 3;
|
|
@@ -57,6 +57,19 @@ export async function probeCommand({ projectDir, values, version }, io) {
|
|
|
57
57
|
const stageOut = stageReporter(progress, (s) => progressTo.write(s));
|
|
58
58
|
const claims = loadClaims(values.claims ? resolve(values.claims) : defaultClaimsPath(projectDir));
|
|
59
59
|
if (claims.claims.length === 0) {
|
|
60
|
+
if (values['allow-empty'] && values.claim === undefined) {
|
|
61
|
+
io.err('claims file declares no claims; verification skipped (--allow-empty); run gate to check changed-file coverage');
|
|
62
|
+
if (values.json) {
|
|
63
|
+
const doc = computeStatus({ projectDir, toolVersion: version, paths: {
|
|
64
|
+
claims: values.claims ? resolve(values.claims) : defaultClaimsPath(projectDir),
|
|
65
|
+
evidence: evidencePath(projectDir), provisional: provisionalEvidencePath(projectDir), baseline: baselinePath(projectDir),
|
|
66
|
+
} });
|
|
67
|
+
const result = validate('status', doc);
|
|
68
|
+
if (!result.ok) throw new Error(`empty probe JSON document does not conform: ${result.errors.map((e) => `${e.path}: ${e.message}`).join('; ')}`);
|
|
69
|
+
io.out(JSON.stringify(doc, null, 2));
|
|
70
|
+
}
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
60
73
|
io.err('claims file declares no claims; nothing to verify');
|
|
61
74
|
return 2;
|
|
62
75
|
}
|