testguard-cli 0.10.2__tar.gz → 0.10.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/CHANGELOG.md +26 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/PKG-INFO +15 -7
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/README.md +14 -6
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/action.yml +1 -1
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs/testguard-explained.html +1 -1
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs/testguard-explained.pdf +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs-canonical/ENVIRONMENT.md +12 -2
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/package-lock.json +2 -2
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/package.json +1 -1
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/packaging/gitlab/testguard.gitlab-ci.yml +2 -2
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/packaging/homebrew/testguard.rb +2 -2
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/pyproject.toml +1 -1
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/GATE-SEMANTICS.md +8 -3
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/brief.mjs +1 -1
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/gate.mjs +3 -3
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/probe.mjs +1 -1
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/status.mjs +1 -1
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/gate/changed.mjs +55 -10
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/init/templates/SKILL.md +1 -1
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/mcp/tools.mjs +1 -1
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/gate.test.mjs +52 -2
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/testguard.claims.json +59 -1
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.agents/skills/testguard/SKILL.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.codex/hooks.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.docguard.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.docguardignore +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.gitattributes +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/dependabot.yml +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/scripts/approve-held-runs.sh +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/scripts/build-one-pager.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/scripts/check-cost-budget.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/scripts/check-python-fixture.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/scripts/install-smoke.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/scripts/publish-homebrew-tap.sh +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/scripts/sync-release-version.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/workflows/auto-merge.yml +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/workflows/ci.yml +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/workflows/release.yml +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/workflows/scheduled-release.yml +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/workflows/supply-chain.yml +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.gitignore +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.npmignore +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.npmrc +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.pre-commit-config.yaml +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.pre-commit-hooks.yaml +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/AGENTS.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/CLAUDE.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/CODE_OF_CONDUCT.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/CONTRIBUTING.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/DRIFT-LOG.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/LICENSE +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/PRIVACY.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/SECURITY.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/SUPPORT.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/bench/README.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/cli/testguard.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs-canonical/ARCHITECTURE.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs-canonical/DATA-MODEL.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs-canonical/REQUIREMENTS.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs-canonical/SECURITY.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs-canonical/TEST-SPEC.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/.gitignore +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/README.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/expected.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/package.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/src/export.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/src/redact.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/test/flaky.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/test/redact.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/testguard.claims.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/vitest.config.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/.gitignore +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/README.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/expected.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/jest.config.js +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/package.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/src/export.js +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/src/redact.js +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/test/redact.test.js +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/testguard.claims.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/.gitignore +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/README.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/expected.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/package-lock.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/package.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/test-results/.last-run.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/.gitignore +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/README.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/demo/__init__.py +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/demo/export.py +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/demo/orphan.py +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/demo/pipeline.py +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/demo/redact.py +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/expected.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/testguard.claims.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/tests/test_patched.py +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/tests/test_redact.py +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/README.md +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/baseline.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/brief.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/calibration-websec.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/calibration.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/claims-methods.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/claims.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/evidence-negative-control.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/evidence-provisional.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/evidence-python.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/evidence-scan-method.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/evidence.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/gate.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/ignore.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/replay.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/status.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/baseline.zero-count.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.ci-not-wilson.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.fallback-without-basis.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.p-not-positives-over-n.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.absolute-path.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.noop-fault.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/ignore.short-reason.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/status.claim-without-file.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/status.invalid-anchor-hidden.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/status.partial-scope-counts-disagree.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/status.surface-counts-disagree.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/schemas.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/lib/fingerprint.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/lib/validate.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/lib/wilson.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/baseline.schema.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/brief.schema.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/calibration.schema.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/claims.schema.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/common.schema.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/evidence.schema.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/gate.schema.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/ignore.schema.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/replay.schema.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/status.schema.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/admit/admit.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/baseline/baseline.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/brief/brief.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/claims/anchors.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/claims/annotations.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/claims/load.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/claims/removed.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/cli.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/admit.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/baseline.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/claims.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/init.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/mcp.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/replay.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/scaffold.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/evidence/writer.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/git.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/init/init.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/mcp/server.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/attribution.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/classify.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/contention.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/cost.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/discover.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/independence.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/inject.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/mocks.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/probe.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/progress.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/pyimports.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/rank.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/index.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/jest.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/playwright.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/python/_testguard_report.py +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/python.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/shared.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/vitest.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/worktree.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/render.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/replay/label.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/replay/replay.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/scaffold/producers.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/scaffold/producers.python.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/scaffold/scaffold.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/status/status.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/status/surface.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/util/glob.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/util/hash.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/admit.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/annotations.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/attribution.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/baseline.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/brief.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/calibration.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/claim-selection.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/claims-anchors.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/claims-since.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/classify.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/contention.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/cost.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/discover.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/gitlab-template.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/glob.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/independence.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/init.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/inject.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/label.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/mcp.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/mocks.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/negative-control.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/one-pager.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/probe-error.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/probe-preconditions.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/probe.fixture.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/probe.jest.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/probe.playwright.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/probe.python.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/progress.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/pyimports.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/rank-aliases.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/release-approval.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/release-sync.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/replay-verdict.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/replay.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/runner-argv.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/runner-command.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/runner-playwright.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/runner-python.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/runner-vitest.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/samples/playwright-report.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/samples/python-report-collect-error.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/samples/python-report.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/scaffold.python.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/scaffold.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/snapshot.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/status.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/surface.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/vitest-config.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/worktree.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/writer.test.mjs +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/testguard.cost-budget.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/testguard.ignore.json +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/testguard_cli/__init__.py +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/testguard_cli/wrapper.py +0 -0
- {testguard_cli-0.10.2 → testguard_cli-0.10.4}/vitest.config.mjs +0 -0
|
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.10.4] - 2026-09-21
|
|
11
|
+
|
|
12
|
+
Automated weekly release — everything merged since `v0.10.3`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- chore(homebrew): sha256 for v0.10.3 (#147)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## [0.10.3] - 2026-09-20
|
|
20
|
+
|
|
21
|
+
Automated weekly release — everything merged since `v0.10.2`.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- fix(gate): infer safe local change base (#145)
|
|
26
|
+
- chore(homebrew): sha256 for v0.10.2 (#144)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Bare `testguard gate` now discovers an ordinary clone's remote default
|
|
32
|
+
branch and reports how it chose the comparison. It still fails closed on
|
|
33
|
+
ambiguous Git state: the default branch is not compared to itself, and a
|
|
34
|
+
feature branch's same-name tracking ref is never accepted as its base.
|
|
35
|
+
|
|
10
36
|
## [0.10.2] - 2026-09-20
|
|
11
37
|
|
|
12
38
|
Automated weekly release — everything merged since `v0.10.1`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: testguard-cli
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.4
|
|
4
4
|
Summary: Breaks your code on purpose and reports every promise your tests did not notice breaking. Mutation testing bound to the claims a project makes — vitest, jest, Playwright, pytest and unittest. Ships the Node.js CLI (requires Node.js 20+).
|
|
5
5
|
Project-URL: Homepage, https://github.com/raccioly/testguard
|
|
6
6
|
Project-URL: Documentation, https://github.com/raccioly/testguard#readme
|
|
@@ -151,9 +151,9 @@ raises coverage. TestGuard admits it because it fails when the claim is false.
|
|
|
151
151
|
| npm | `npm i -D testguard-cli` then `npx testguard probe` |
|
|
152
152
|
| pip | `pip install testguard-cli` then `testguard probe` (needs Node ≥ 20) |
|
|
153
153
|
| Homebrew | `brew tap raccioly/tap && brew install testguard` |
|
|
154
|
-
| GitHub Action | `uses: raccioly/testguard@v0.10.
|
|
154
|
+
| GitHub Action | `uses: raccioly/testguard@v0.10.4` — see [`action.yml`](./action.yml) |
|
|
155
155
|
| pre-commit | `repo: https://github.com/raccioly/testguard`, hooks `testguard-claims`, `testguard-probe` |
|
|
156
|
-
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.10.
|
|
156
|
+
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.10.4/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
|
|
@@ -484,9 +484,17 @@ state when an exact fault anchor is missing or ambiguous, and points at the
|
|
|
484
484
|
fault definition to repair. Repair preserves the fault's meaning and requires
|
|
485
485
|
a re-probe; there is deliberately no `--fix`.
|
|
486
486
|
|
|
487
|
-
**
|
|
487
|
+
**The base is detected when Git records it.** An explicit `--changed` wins,
|
|
488
|
+
then `TESTGUARD_CHANGED_REF`, GitHub Actions (`GITHUB_BASE_REF`) and GitLab
|
|
488
489
|
merge request pipelines (`CI_MERGE_REQUEST_DIFF_BASE_SHA`, then
|
|
489
|
-
`CI_MERGE_REQUEST_TARGET_BRANCH_NAME`)
|
|
490
|
+
`CI_MERGE_REQUEST_TARGET_BRANCH_NAME`). In an ordinary clone on a feature
|
|
491
|
+
branch, bare `testguard gate` uses the configured remote's symbolic default
|
|
492
|
+
branch (normally `origin/main`) and says how it chose it. If that symbolic ref
|
|
493
|
+
is absent, a configured upstream is used only when its branch name differs
|
|
494
|
+
from the current branch. A feature branch's same-name tracking ref and the
|
|
495
|
+
default branch itself are never selected: either could produce a misleading
|
|
496
|
+
empty diff. In those ambiguous cases, pass `--changed` explicitly.
|
|
497
|
+
|
|
490
498
|
The base must exist locally: GitHub — `actions/checkout` with `fetch-depth: 0`;
|
|
491
499
|
GitLab — the diff base sha needs nothing extra on a merge request pipeline,
|
|
492
500
|
the branch name needs `GIT_DEPTH: 0` or a `git fetch origin <target>`. A
|
|
@@ -497,12 +505,12 @@ detected base that does not resolve is a warning for `status` and `brief`
|
|
|
497
505
|
# GitHub Actions
|
|
498
506
|
- uses: actions/checkout@v4
|
|
499
507
|
with: { fetch-depth: 0 }
|
|
500
|
-
- uses: raccioly/testguard@v0.10.
|
|
508
|
+
- uses: raccioly/testguard@v0.10.4
|
|
501
509
|
with: { command: gate }
|
|
502
510
|
|
|
503
511
|
# GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
|
|
504
512
|
include:
|
|
505
|
-
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.
|
|
513
|
+
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.4/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
506
514
|
inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
|
|
507
515
|
```
|
|
508
516
|
|
|
@@ -127,9 +127,9 @@ raises coverage. TestGuard admits it because it fails when the claim is false.
|
|
|
127
127
|
| npm | `npm i -D testguard-cli` then `npx testguard probe` |
|
|
128
128
|
| pip | `pip install testguard-cli` then `testguard probe` (needs Node ≥ 20) |
|
|
129
129
|
| Homebrew | `brew tap raccioly/tap && brew install testguard` |
|
|
130
|
-
| GitHub Action | `uses: raccioly/testguard@v0.10.
|
|
130
|
+
| GitHub Action | `uses: raccioly/testguard@v0.10.4` — see [`action.yml`](./action.yml) |
|
|
131
131
|
| pre-commit | `repo: https://github.com/raccioly/testguard`, hooks `testguard-claims`, `testguard-probe` |
|
|
132
|
-
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.10.
|
|
132
|
+
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.10.4/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
|
|
@@ -460,9 +460,17 @@ state when an exact fault anchor is missing or ambiguous, and points at the
|
|
|
460
460
|
fault definition to repair. Repair preserves the fault's meaning and requires
|
|
461
461
|
a re-probe; there is deliberately no `--fix`.
|
|
462
462
|
|
|
463
|
-
**
|
|
463
|
+
**The base is detected when Git records it.** An explicit `--changed` wins,
|
|
464
|
+
then `TESTGUARD_CHANGED_REF`, GitHub Actions (`GITHUB_BASE_REF`) and GitLab
|
|
464
465
|
merge request pipelines (`CI_MERGE_REQUEST_DIFF_BASE_SHA`, then
|
|
465
|
-
`CI_MERGE_REQUEST_TARGET_BRANCH_NAME`)
|
|
466
|
+
`CI_MERGE_REQUEST_TARGET_BRANCH_NAME`). In an ordinary clone on a feature
|
|
467
|
+
branch, bare `testguard gate` uses the configured remote's symbolic default
|
|
468
|
+
branch (normally `origin/main`) and says how it chose it. If that symbolic ref
|
|
469
|
+
is absent, a configured upstream is used only when its branch name differs
|
|
470
|
+
from the current branch. A feature branch's same-name tracking ref and the
|
|
471
|
+
default branch itself are never selected: either could produce a misleading
|
|
472
|
+
empty diff. In those ambiguous cases, pass `--changed` explicitly.
|
|
473
|
+
|
|
466
474
|
The base must exist locally: GitHub — `actions/checkout` with `fetch-depth: 0`;
|
|
467
475
|
GitLab — the diff base sha needs nothing extra on a merge request pipeline,
|
|
468
476
|
the branch name needs `GIT_DEPTH: 0` or a `git fetch origin <target>`. A
|
|
@@ -473,12 +481,12 @@ detected base that does not resolve is a warning for `status` and `brief`
|
|
|
473
481
|
# GitHub Actions
|
|
474
482
|
- uses: actions/checkout@v4
|
|
475
483
|
with: { fetch-depth: 0 }
|
|
476
|
-
- uses: raccioly/testguard@v0.10.
|
|
484
|
+
- uses: raccioly/testguard@v0.10.4
|
|
477
485
|
with: { command: gate }
|
|
478
486
|
|
|
479
487
|
# GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
|
|
480
488
|
include:
|
|
481
|
-
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.
|
|
489
|
+
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.4/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
482
490
|
inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
|
|
483
491
|
```
|
|
484
492
|
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
<div class="page">
|
|
145
145
|
<div class="masthead">
|
|
146
146
|
<div class="wordmark">Test<span class="mark">Guard</span></div>
|
|
147
|
-
<div class="meta">Technical Brief · v0.10.
|
|
147
|
+
<div class="meta">Technical Brief · v0.10.4 · 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
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- docguard:version 1.0.0 -->
|
|
4
4
|
<!-- docguard:status approved -->
|
|
5
|
-
<!-- docguard:last-reviewed 2026-09-
|
|
5
|
+
<!-- docguard:last-reviewed 2026-09-20 -->
|
|
6
6
|
<!-- docguard:owner @raccioly -->
|
|
7
7
|
<!-- docguard:quality negation-load off — the environment is defined by absences: no required variables, no secrets, no network, no service. -->
|
|
8
8
|
<!-- docguard:quality passive-voice off — setup steps describe what is read and written, not who reads and writes it. -->
|
|
@@ -28,7 +28,7 @@ None are required. All are optional overrides.
|
|
|
28
28
|
| Variable | Read by | Effect |
|
|
29
29
|
|---|---|---|
|
|
30
30
|
| `TESTGUARD_NODE_MODULES` | `probe` | `node_modules` to link into the scratch worktree; same as `--node-modules` |
|
|
31
|
-
| `TESTGUARD_CHANGED_REF` | `gate`, `status --changed` | The reference a change is measured against; overrides CI detection |
|
|
31
|
+
| `TESTGUARD_CHANGED_REF` | `gate`, `status --changed` | The reference a change is measured against; overrides CI and local Git detection |
|
|
32
32
|
| `GITHUB_BASE_REF` | `gate` | GitHub Actions pull requests: the base branch |
|
|
33
33
|
| `CI_MERGE_REQUEST_DIFF_BASE_SHA` | `gate` | GitLab merge request pipelines: exact diff base, needs no fetch |
|
|
34
34
|
| `CI_MERGE_REQUEST_TARGET_BRANCH_NAME` | `gate` | GitLab fallback when the diff base sha is absent |
|
|
@@ -37,6 +37,16 @@ None are required. All are optional overrides.
|
|
|
37
37
|
The tool sets `PLAYWRIGHT_JSON_OUTPUT_FILE` and `FORCE_COLOR=0` on the
|
|
38
38
|
processes it spawns. It reads no secret, no token and no credential.
|
|
39
39
|
|
|
40
|
+
## Local Git Discovery
|
|
41
|
+
|
|
42
|
+
Without an explicit, environment or CI reference, TestGuard reads only local
|
|
43
|
+
Git metadata. On a feature branch it prefers the configured remote's symbolic
|
|
44
|
+
default branch, such as `origin/main`. If that ref is absent, it accepts the
|
|
45
|
+
branch's configured upstream only when the upstream branch name differs from
|
|
46
|
+
the current branch name. It never contacts the remote, never compares the
|
|
47
|
+
default branch to itself, and never treats `origin/feature` as the base of a
|
|
48
|
+
local `feature` branch; those ambiguous cases require `--changed`.
|
|
49
|
+
|
|
40
50
|
## Configuration Files
|
|
41
51
|
|
|
42
52
|
| File | Owner | Committed | Purpose |
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testguard-cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "testguard-cli",
|
|
9
|
-
"version": "0.10.
|
|
9
|
+
"version": "0.10.4",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"ajv": "8.20.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testguard-cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
4
4
|
"description": "Breaks your code on purpose and reports every promise your tests did not notice breaking. Mutation testing bound to the claims a project makes — vitest, jest, Playwright, pytest and unittest.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Include it from a project's .gitlab-ci.yml, pinned to a release:
|
|
4
4
|
#
|
|
5
5
|
# include:
|
|
6
|
-
# - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.
|
|
6
|
+
# - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.4/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
7
7
|
# inputs:
|
|
8
8
|
# dir: backend # where testguard.claims.json lives (default ".")
|
|
9
9
|
# post_note: true # post the brief as a merge-request note (needs TESTGUARD_GITLAB_TOKEN, api scope)
|
|
@@ -29,7 +29,7 @@ spec:
|
|
|
29
29
|
inputs:
|
|
30
30
|
version:
|
|
31
31
|
description: testguard-cli version to run
|
|
32
|
-
default: "0.10.
|
|
32
|
+
default: "0.10.4"
|
|
33
33
|
dir:
|
|
34
34
|
description: directory whose test runner is invoked (where testguard.claims.json lives)
|
|
35
35
|
default: "."
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
class Testguard < Formula
|
|
16
16
|
desc "Proves a test suite defends the claims a project makes"
|
|
17
17
|
homepage "https://github.com/raccioly/testguard"
|
|
18
|
-
url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.10.
|
|
19
|
-
sha256 "
|
|
18
|
+
url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.10.4.tgz"
|
|
19
|
+
sha256 "b0b73956e7d5c51b0ad40dcb1333a5e18b599e990e707d7ac90a87ce66156178"
|
|
20
20
|
license "MIT"
|
|
21
21
|
|
|
22
22
|
depends_on "node"
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "testguard-cli"
|
|
7
|
-
version = "0.10.
|
|
7
|
+
version = "0.10.4"
|
|
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" }
|
|
@@ -402,9 +402,14 @@ Rules:
|
|
|
402
402
|
4. **Never a silent pass on nothing.** A non-empty change whose files were
|
|
403
403
|
all excluded passes with an explicit "0 evaluated" line; a tool offers a
|
|
404
404
|
strict mode that fails it instead. An empty change is an honest `0`.
|
|
405
|
-
5. **The reference is
|
|
406
|
-
|
|
407
|
-
|
|
405
|
+
5. **The reference is inferred only from recorded intent.** Resolution stops
|
|
406
|
+
at the first available source: an explicit flag, `TESTGUARD_CHANGED_REF`, a
|
|
407
|
+
CI-provided base, the configured remote's symbolic default branch, then a
|
|
408
|
+
configured upstream whose branch name differs from the current branch.
|
|
409
|
+
Local discovery never compares a branch to its same-name remote-tracking
|
|
410
|
+
ref: that ref may already contain the whole change and yield a false empty
|
|
411
|
+
diff. It also declines to compare the default branch to itself. When Git
|
|
412
|
+
records no safe local base, the gate exits `3` and asks for `--changed`.
|
|
408
413
|
6. **Unclaimed changes precede every evidence state.** When the status
|
|
409
414
|
document knows a reference and finds uncovered files, its state is
|
|
410
415
|
`unclaimed-changes` and its next action is to write the claim, before any
|
|
@@ -13,7 +13,7 @@ export async function briefCommand({ projectDir, values, version }, io) {
|
|
|
13
13
|
// A detected base that does not resolve is silent here: the brief is a
|
|
14
14
|
// session-start hook's output and must never add noise; `status` and
|
|
15
15
|
// `gate` are where that warning is printed.
|
|
16
|
-
status = withChangedRef(resolveChangedRef({ explicit: values.changed }), (changedRef) => computeStatus({ projectDir, toolVersion: version, changedRef, includeDirty: values['include-dirty'], evidence: values.evidence ? evPath : undefined }));
|
|
16
|
+
status = withChangedRef(resolveChangedRef({ explicit: values.changed, projectDir }), (changedRef) => computeStatus({ projectDir, toolVersion: version, changedRef, includeDirty: values['include-dirty'], evidence: values.evidence ? evPath : undefined }));
|
|
17
17
|
} catch {
|
|
18
18
|
status = undefined; // a brief must never fail because status could not be computed
|
|
19
19
|
}
|
|
@@ -16,14 +16,14 @@ export async function gateCommand({ projectDir, values, version }, io) {
|
|
|
16
16
|
io.out('Non-source files (anything but .js .mjs .cjs .ts .mts .cts .jsx .tsx) are excluded before these patterns apply.');
|
|
17
17
|
return 0;
|
|
18
18
|
}
|
|
19
|
-
const resolved = resolveChangedRef({ explicit: values.changed });
|
|
19
|
+
const resolved = resolveChangedRef({ explicit: values.changed, projectDir });
|
|
20
20
|
if (!resolved) {
|
|
21
|
-
io.err('gate needs a reference to measure the change against: --changed <ref> (e.g. origin/main), or set TESTGUARD_CHANGED_REF.
|
|
21
|
+
io.err('gate needs a reference to measure the change against: --changed <ref> (e.g. origin/main), or set TESTGUARD_CHANGED_REF. CI bases and a safe local remote default or differently named upstream are detected automatically.');
|
|
22
22
|
return 3;
|
|
23
23
|
}
|
|
24
24
|
const ref = resolved.ref;
|
|
25
25
|
// The gate itself never degrades: a detected reference that does not resolve is exit 2 here, because the gate's whole job is the measurement.
|
|
26
|
-
if (!resolved.required && !values.json && !values.quiet) io.err(
|
|
26
|
+
if (!resolved.required && !values.json && !values.quiet) io.err(`gate: comparing against ${ref} (${resolved.from})`);
|
|
27
27
|
const doc = computeChangedGate({
|
|
28
28
|
projectDir,
|
|
29
29
|
ref,
|
|
@@ -106,7 +106,7 @@ export async function probeCommand({ projectDir, values, version }, io) {
|
|
|
106
106
|
const g = gate(evidence.records, baseline, { severityFloor: values.severity });
|
|
107
107
|
const scope = partialScope(only, evidence.records);
|
|
108
108
|
if (values.json) {
|
|
109
|
-
const status = withChangedRef(resolveChangedRef({ explicit: values.changed }), (changedRef) => computeStatus({ projectDir, toolVersion: version, changedRef, includeDirty: values['include-dirty'], evidence: outPath }), io.err);
|
|
109
|
+
const status = withChangedRef(resolveChangedRef({ explicit: values.changed, projectDir }), (changedRef) => computeStatus({ projectDir, toolVersion: version, changedRef, includeDirty: values['include-dirty'], evidence: outPath }), io.err);
|
|
110
110
|
const doc = { ...status, run: { id: evidence.run.id, evidence: outPath, provisional, records: evidence.records.length, newSinceBaseline: g.new.length, exitCode: g.new.length > 0 ? 1 : 0, ...(scope ? { scope } : {}) }, ...(values.cost ? { cost: costReport(evidence.records) } : {}) };
|
|
111
111
|
const result = validate('status', doc);
|
|
112
112
|
if (!result.ok) throw new Error(`probe JSON document does not conform: ${result.errors.map((e) => `${e.path}: ${e.message}`).join('; ')}`);
|
|
@@ -5,7 +5,7 @@ import { resolveChangedRef, withChangedRef } from '../gate/changed.mjs';
|
|
|
5
5
|
|
|
6
6
|
export async function statusCommand({ projectDir, values, version }, io) {
|
|
7
7
|
const evidence = values.evidence ? resolve(values.evidence) : undefined;
|
|
8
|
-
const doc = withChangedRef(resolveChangedRef({ explicit: values.changed }), (changedRef) => computeStatus({ projectDir, toolVersion: version, changedRef, includeDirty: values['include-dirty'], evidence }), io.err);
|
|
8
|
+
const doc = withChangedRef(resolveChangedRef({ explicit: values.changed, projectDir }), (changedRef) => computeStatus({ projectDir, toolVersion: version, changedRef, includeDirty: values['include-dirty'], evidence }), io.err);
|
|
9
9
|
const result = validate('status', doc);
|
|
10
10
|
if (!result.ok) throw new Error(`status document does not conform: ${result.errors.map((e) => `${e.path}: ${e.message}`).join('; ')}`);
|
|
11
11
|
io.out(values.json ? JSON.stringify(doc, null, 2) : renderStatus(doc));
|
|
@@ -50,10 +50,10 @@ export const isDefaultExcluded = (file) => DEFAULT_EXCLUDE_RES.some((re) => re.t
|
|
|
50
50
|
export const defaultIgnorePath = (projectDir) => join(projectDir, 'testguard.ignore.json');
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
* The reference the change is measured against.
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
53
|
+
* The reference the change is measured against. Environment overrides and CI
|
|
54
|
+
* bases are handled here; local Git discovery is separate because it needs a
|
|
55
|
+
* project directory and must distinguish a base branch from a feature branch's
|
|
56
|
+
* same-name tracking ref.
|
|
57
57
|
*/
|
|
58
58
|
export function detectChangedRef(env = process.env) {
|
|
59
59
|
if (env.TESTGUARD_CHANGED_REF) return { ref: env.TESTGUARD_CHANGED_REF, from: 'TESTGUARD_CHANGED_REF' };
|
|
@@ -68,18 +68,63 @@ export function detectChangedRef(env = process.env) {
|
|
|
68
68
|
return null;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
const tryGit = (args, cwd) => {
|
|
72
|
+
try {
|
|
73
|
+
return git(args, cwd) || null;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Infer a local change base only when Git records enough intent to do so
|
|
81
|
+
* without turning an empty feature-branch tracking diff into a false pass.
|
|
82
|
+
*
|
|
83
|
+
* The configured upstream selects the remote whose symbolic HEAD we inspect.
|
|
84
|
+
* Without one, exactly one remote HEAD is unambiguous. A branch never compares
|
|
85
|
+
* against that remote's same-named branch: on the default branch that asks no
|
|
86
|
+
* useful question, and on a feature branch its tracking ref may already contain
|
|
87
|
+
* the entire change. An upstream is a safe fallback only when its branch name
|
|
88
|
+
* differs from the local branch name (for example topic -> origin/main).
|
|
89
|
+
*/
|
|
90
|
+
export function detectLocalChangedRef(projectDir) {
|
|
91
|
+
if (!projectDir) return null;
|
|
92
|
+
const root = tryGit(['rev-parse', '--show-toplevel'], projectDir);
|
|
93
|
+
if (!root) return null;
|
|
94
|
+
const branch = tryGit(['symbolic-ref', '--quiet', '--short', 'HEAD'], root);
|
|
95
|
+
if (!branch) return null;
|
|
96
|
+
|
|
97
|
+
const tracking = tryGit(['for-each-ref', '--format=%(upstream:short)%00%(upstream:remotename)', `refs/heads/${branch}`], root);
|
|
98
|
+
const [upstream, upstreamRemote] = tracking?.split('\0') ?? [];
|
|
99
|
+
let remote = upstreamRemote && upstreamRemote !== '.' ? upstreamRemote : null;
|
|
100
|
+
|
|
101
|
+
const remoteHeads = (tryGit(['for-each-ref', '--format=%(refname)', 'refs/remotes'], root) ?? '')
|
|
102
|
+
.split('\n')
|
|
103
|
+
.filter((ref) => ref.endsWith('/HEAD'));
|
|
104
|
+
if (!remote && remoteHeads.length === 1) remote = remoteHeads[0].slice('refs/remotes/'.length, -'/HEAD'.length);
|
|
105
|
+
|
|
106
|
+
const defaultRef = remote
|
|
107
|
+
? tryGit(['symbolic-ref', '--quiet', '--short', `refs/remotes/${remote}/HEAD`], root)
|
|
108
|
+
: null;
|
|
109
|
+
if (defaultRef && headSha(root, defaultRef) && defaultRef !== `${remote}/${branch}`) return { ref: defaultRef, from: 'remote default branch' };
|
|
110
|
+
if (upstream && headSha(root, upstream) && upstream !== `${upstreamRemote}/${branch}`) return { ref: upstream, from: 'branch upstream' };
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
71
114
|
/**
|
|
72
115
|
* Resolve the reference a command should measure against, and whether it may
|
|
73
116
|
* be dropped on failure. An explicit `--changed` is the user's word: if it
|
|
74
|
-
* does not resolve, that is an error.
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* measurement, because `status` and `brief` must keep working everywhere.
|
|
117
|
+
* does not resolve, that is an error. An automatically detected reference is
|
|
118
|
+
* a convenience: if it does not resolve (shallow clone, stale local metadata,
|
|
119
|
+
* or a temp directory) the command says so on stderr and continues without a
|
|
120
|
+
* change measurement, because `status` and `brief` must keep working everywhere.
|
|
78
121
|
*/
|
|
79
|
-
export function resolveChangedRef({ explicit, env = process.env } = {}) {
|
|
122
|
+
export function resolveChangedRef({ explicit, env = process.env, projectDir } = {}) {
|
|
80
123
|
if (explicit) return { ref: explicit, from: '--changed', required: true };
|
|
81
124
|
const d = detectChangedRef(env);
|
|
82
|
-
|
|
125
|
+
if (d) return { ...d, required: false };
|
|
126
|
+
const local = detectLocalChangedRef(projectDir);
|
|
127
|
+
return local ? { ...local, required: false } : null;
|
|
83
128
|
}
|
|
84
129
|
|
|
85
130
|
/**
|
|
@@ -21,7 +21,7 @@ which files exist. `state` is one of:
|
|
|
21
21
|
| state | meaning | you do |
|
|
22
22
|
|---|---|---|
|
|
23
23
|
| `no-claims` | no `testguard.claims.json` | `testguard scaffold <file>` for a file with guards; replace every `TODO:` statement with what the code guarantees; keep or drop each proposal; move the claims into `testguard.claims.json` |
|
|
24
|
-
| `unclaimed-changes` | files you changed carry no claim (
|
|
24
|
+
| `unclaimed-changes` | files you changed carry no claim (when a reference is explicit, supplied by CI, or safely inferred from local Git's remote default/upstream metadata) | `next.file` names the first; `testguard scaffold <file>` and state the claim, or add a `testguard.ignore.json` path entry with a reason a reviewer will accept. **Before** writing more code. |
|
|
25
25
|
| `invalid-anchors` | an exact fault anchor moved or became ambiguous | run `testguard claims --check-anchors`; repair `next.target` in the claims file without changing what the fault means, then re-probe. Never guess or auto-fix an anchor. |
|
|
26
26
|
| `unprobed` | claims never probed | `testguard probe` |
|
|
27
27
|
| `evidence-stale` | code, tests or claims changed since the evidence | `testguard probe --include-dirty` (or `--claim <ID>` for one) |
|
|
@@ -29,7 +29,7 @@ const CHANGED = { type: 'string', description: 'A git reference to measure the c
|
|
|
29
29
|
|
|
30
30
|
const statusFor = (args) => {
|
|
31
31
|
const projectDir = projectOf(args);
|
|
32
|
-
return withChangedRef(resolveChangedRef({ explicit: args?.changed }), (changedRef) =>
|
|
32
|
+
return withChangedRef(resolveChangedRef({ explicit: args?.changed, projectDir }), (changedRef) =>
|
|
33
33
|
computeStatus({ projectDir, toolVersion: args?.__version ?? '0.0.0', changedRef, evidence: evidenceOf(args) }));
|
|
34
34
|
};
|
|
35
35
|
|
|
@@ -6,7 +6,7 @@ import { tmpdir } from 'node:os';
|
|
|
6
6
|
import { dirname, join } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { spawnSync } from 'node:child_process';
|
|
9
|
-
import { computeChangedGate, changedFiles, detectChangedRef, DEFAULT_EXCLUDES } from '../src/gate/changed.mjs';
|
|
9
|
+
import { computeChangedGate, changedFiles, detectChangedRef, detectLocalChangedRef, DEFAULT_EXCLUDES } from '../src/gate/changed.mjs';
|
|
10
10
|
import { validate } from '../spec/lib/validate.mjs';
|
|
11
11
|
import { readSpecDoc } from '../src/evidence/writer.mjs';
|
|
12
12
|
import { main } from '../src/cli.mjs';
|
|
@@ -170,7 +170,7 @@ describe('gate --changed: claim coverage of a change', () => {
|
|
|
170
170
|
expect(c.lines.err.join('\n')).toMatch(/cannot resolve --changed no-such-ref/);
|
|
171
171
|
|
|
172
172
|
const saved = { ...process.env };
|
|
173
|
-
delete process.env.TESTGUARD_CHANGED_REF; delete process.env.GITHUB_BASE_REF; delete process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME;
|
|
173
|
+
delete process.env.TESTGUARD_CHANGED_REF; delete process.env.GITHUB_BASE_REF; delete process.env.CI_MERGE_REQUEST_DIFF_BASE_SHA; delete process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME;
|
|
174
174
|
try {
|
|
175
175
|
const d = capture();
|
|
176
176
|
expect(await main(['gate', r.project], d.io)).toBe(3);
|
|
@@ -203,6 +203,56 @@ describe('detectChangedRef', () => {
|
|
|
203
203
|
});
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
+
describe('detectLocalChangedRef', () => {
|
|
207
|
+
it('uses a remote default from a feature branch, but never the current branch\'s same-name remote', () => {
|
|
208
|
+
const r = repo();
|
|
209
|
+
try {
|
|
210
|
+
r.g('branch', '-M', 'main');
|
|
211
|
+
r.g('remote', 'add', 'origin', 'https://example.invalid/repo.git');
|
|
212
|
+
r.g('update-ref', 'refs/remotes/origin/main', 'HEAD');
|
|
213
|
+
r.g('symbolic-ref', 'refs/remotes/origin/HEAD', 'refs/remotes/origin/main');
|
|
214
|
+
expect(detectLocalChangedRef(r.project)).toBeNull();
|
|
215
|
+
|
|
216
|
+
r.g('switch', '-q', '-c', 'feature');
|
|
217
|
+
r.g('update-ref', 'refs/remotes/origin/feature', 'HEAD');
|
|
218
|
+
r.g('branch', '--set-upstream-to', 'origin/feature');
|
|
219
|
+
expect(detectLocalChangedRef(r.project)).toEqual({ ref: 'origin/main', from: 'remote default branch' });
|
|
220
|
+
|
|
221
|
+
r.g('update-ref', '-d', 'refs/remotes/origin/main');
|
|
222
|
+
expect(detectLocalChangedRef(r.project)).toBeNull();
|
|
223
|
+
r.g('update-ref', 'refs/remotes/origin/main', 'HEAD');
|
|
224
|
+
r.g('symbolic-ref', '--delete', 'refs/remotes/origin/HEAD');
|
|
225
|
+
expect(detectLocalChangedRef(r.project)).toBeNull();
|
|
226
|
+
r.g('branch', '--set-upstream-to', 'origin/main');
|
|
227
|
+
expect(detectLocalChangedRef(r.project)).toEqual({ ref: 'origin/main', from: 'branch upstream' });
|
|
228
|
+
} finally {
|
|
229
|
+
rmSync(r.root, { recursive: true, force: true });
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('makes a bare gate work in an ordinary clone and says how the base was chosen', async () => {
|
|
234
|
+
const r = repo();
|
|
235
|
+
const saved = { ...process.env };
|
|
236
|
+
try {
|
|
237
|
+
delete process.env.TESTGUARD_CHANGED_REF; delete process.env.GITHUB_BASE_REF; delete process.env.CI_MERGE_REQUEST_DIFF_BASE_SHA; delete process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME;
|
|
238
|
+
r.g('branch', '-M', 'main');
|
|
239
|
+
r.g('remote', 'add', 'origin', 'https://example.invalid/repo.git');
|
|
240
|
+
r.g('update-ref', 'refs/remotes/origin/main', 'HEAD');
|
|
241
|
+
r.g('symbolic-ref', 'refs/remotes/origin/HEAD', 'refs/remotes/origin/main');
|
|
242
|
+
r.g('switch', '-q', '-c', 'feature');
|
|
243
|
+
r.write('src/newfeature.mjs', 'export const f = () => 1;\n');
|
|
244
|
+
const a = capture();
|
|
245
|
+
expect(await main(['gate', r.project, '--include-dirty'], a.io)).toBe(1);
|
|
246
|
+
expect(a.lines.err).toEqual(['gate: comparing against origin/main (remote default branch)']);
|
|
247
|
+
expect(a.lines.out.join('\n')).toMatch(/UNCLAIMED\s+src\/newfeature\.mjs/);
|
|
248
|
+
} finally {
|
|
249
|
+
for (const k of Object.keys(process.env)) if (!(k in saved)) delete process.env[k];
|
|
250
|
+
Object.assign(process.env, saved);
|
|
251
|
+
rmSync(r.root, { recursive: true, force: true });
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
206
256
|
describe('resolveChangedRef / withChangedRef', () => {
|
|
207
257
|
it('an explicit reference is required: failure propagates; a detected one degrades to a warning and no measurement', async () => {
|
|
208
258
|
const { resolveChangedRef, withChangedRef } = await import('../src/gate/changed.mjs');
|
|
@@ -671,7 +671,7 @@
|
|
|
671
671
|
},
|
|
672
672
|
{
|
|
673
673
|
"id": "TG-CHANGED-REF-EXPLICIT-IS-REQUIRED",
|
|
674
|
-
"statement": "An explicit --changed reference that does not resolve is an error; only
|
|
674
|
+
"statement": "An explicit --changed reference that does not resolve is an error; only an automatically detected reference may be dropped, with a warning.",
|
|
675
675
|
"source": {
|
|
676
676
|
"kind": "spec",
|
|
677
677
|
"ref": "spec/GATE-SEMANTICS.md#claim-coverage-of-a-change"
|
|
@@ -701,6 +701,64 @@
|
|
|
701
701
|
}
|
|
702
702
|
]
|
|
703
703
|
},
|
|
704
|
+
{
|
|
705
|
+
"id": "TG-CHANGED-REF-LOCAL-IS-SAFE",
|
|
706
|
+
"statement": "Without an explicit, environment or CI base, change coverage uses a configured remote's default branch or a differently named upstream, but never the current branch's same-name tracking ref.",
|
|
707
|
+
"source": {
|
|
708
|
+
"kind": "spec",
|
|
709
|
+
"ref": "spec/GATE-SEMANTICS.md#claim-coverage-of-a-change"
|
|
710
|
+
},
|
|
711
|
+
"severity": "high",
|
|
712
|
+
"producedBy": {
|
|
713
|
+
"producer": "human",
|
|
714
|
+
"by": "maintainer",
|
|
715
|
+
"at": "2026-09-20T00:00:00Z"
|
|
716
|
+
},
|
|
717
|
+
"defendedBy": [
|
|
718
|
+
"test/gate.test.mjs"
|
|
719
|
+
],
|
|
720
|
+
"faults": [
|
|
721
|
+
{
|
|
722
|
+
"id": "F1",
|
|
723
|
+
"description": "The default branch is allowed to compare against its own remote-tracking ref, producing an empty diff.",
|
|
724
|
+
"faultClass": "condition-forced",
|
|
725
|
+
"file": "src/gate/changed.mjs",
|
|
726
|
+
"find": "if (defaultRef && headSha(root, defaultRef) && defaultRef !== `${remote}/${branch}`) return { ref: defaultRef, from: 'remote default branch' };",
|
|
727
|
+
"replace": "if (defaultRef && headSha(root, defaultRef)) return { ref: defaultRef, from: 'remote default branch' };",
|
|
728
|
+
"producedBy": {
|
|
729
|
+
"producer": "human",
|
|
730
|
+
"by": "maintainer",
|
|
731
|
+
"at": "2026-09-20T00:00:00Z"
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
"id": "F2",
|
|
736
|
+
"description": "A feature branch's same-name tracking ref is accepted as its base, hiding the whole pushed change.",
|
|
737
|
+
"faultClass": "condition-forced",
|
|
738
|
+
"file": "src/gate/changed.mjs",
|
|
739
|
+
"find": "if (upstream && headSha(root, upstream) && upstream !== `${upstreamRemote}/${branch}`) return { ref: upstream, from: 'branch upstream' };",
|
|
740
|
+
"replace": "if (upstream && headSha(root, upstream)) return { ref: upstream, from: 'branch upstream' };",
|
|
741
|
+
"producedBy": {
|
|
742
|
+
"producer": "human",
|
|
743
|
+
"by": "maintainer",
|
|
744
|
+
"at": "2026-09-20T00:00:00Z"
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
"id": "F3",
|
|
749
|
+
"description": "Local Git discovery is bypassed, restoring the bare gate's usage failure in ordinary feature-branch clones.",
|
|
750
|
+
"faultClass": "return-altered",
|
|
751
|
+
"file": "src/gate/changed.mjs",
|
|
752
|
+
"find": " const local = detectLocalChangedRef(projectDir);",
|
|
753
|
+
"replace": " const local = null;",
|
|
754
|
+
"producedBy": {
|
|
755
|
+
"producer": "human",
|
|
756
|
+
"by": "maintainer",
|
|
757
|
+
"at": "2026-09-20T00:00:00Z"
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
]
|
|
761
|
+
},
|
|
704
762
|
{
|
|
705
763
|
"id": "TG-RELEASE-SYNC-CHECK-FAILS-ON-DRIFT",
|
|
706
764
|
"statement": "The release version check exits non-zero when any version surface is out of step with package.json — including a Homebrew formula whose sha256 is the known-stale placeholder or, checked online, does not match the published tarball — the sha256 mode writes the hash of the registry tarball into the formula, and --list-surfaces names every file a release writes so the release workflows allow exactly those; a release can never ship with drifting version strings, an unverifiable formula, or be blocked by a surface its own allow-list forgot.",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|