testguard-cli 0.18.1__tar.gz → 0.18.2__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.18.2/.github/scripts/benchmark-node-direct.mjs +106 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/workflows/ci.yml +26 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/CHANGELOG.md +16 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/PKG-INFO +5 -5
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/README.md +4 -4
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/action.yml +1 -1
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/docs/testguard-explained.html +1 -1
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/docs/testguard-explained.pdf +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/docs-canonical/ARCHITECTURE.md +1 -1
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/docs-canonical/REQUIREMENTS.md +3 -3
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/package-lock.json +2 -2
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/package.json +1 -1
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/packaging/gitlab/testguard.gitlab-ci.yml +2 -2
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/packaging/homebrew/testguard.rb +2 -2
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/pyproject.toml +1 -1
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/GATE-SEMANTICS.md +15 -1
- testguard_cli-0.18.2/src/probe/runners/node/direct.mjs +55 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/runners/node/entry.mjs +6 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/runners/node-test.mjs +20 -6
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/annotation-advisory.test.mjs +0 -17
- testguard_cli-0.18.2/test/annotation-id-bounds.test.mjs +33 -0
- testguard_cli-0.18.1/test/claim-selection.test.mjs → testguard_cli-0.18.2/test/claim-options.test.mjs +2 -51
- testguard_cli-0.18.2/test/claim-selection.test.mjs +62 -0
- testguard_cli-0.18.2/test/gate-coverage.test.mjs +71 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/gate.test.mjs +0 -54
- testguard_cli-0.18.2/test/runner-node-direct.test.mjs +98 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/testguard.claims.json +79 -7
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.agents/skills/testguard/SKILL.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.docguard.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.docguardignore +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.gitattributes +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/dependabot.yml +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/scripts/approve-held-runs.sh +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/scripts/benchmark-node-runner.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/scripts/build-one-pager.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/scripts/check-cost-budget.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/scripts/check-node-fixture.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/scripts/check-python-fixture.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/scripts/install-smoke.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/scripts/publish-homebrew-tap.sh +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/scripts/sync-release-version.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/workflows/auto-merge.yml +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/workflows/release.yml +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/workflows/scheduled-release.yml +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.github/workflows/supply-chain.yml +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.gitignore +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.npmignore +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.npmrc +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.pre-commit-config.yaml +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/.pre-commit-hooks.yaml +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/AGENTS.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/CLAUDE.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/CODE_OF_CONDUCT.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/CONTRIBUTING.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/DRIFT-LOG.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/LICENSE +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/PRIVACY.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/SECURITY.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/SUPPORT.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/bench/README.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/cli/testguard.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/docs-canonical/DATA-MODEL.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/docs-canonical/ENVIRONMENT.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/docs-canonical/PRIOR-ART.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/docs-canonical/SECURITY.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/docs-canonical/TEST-SPEC.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer/.gitignore +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer/README.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer/expected.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer/package.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer/src/export.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer/src/redact.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer/test/flaky.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer/test/redact.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer/testguard.claims.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer/vitest.config.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-jest/.gitignore +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-jest/README.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-jest/expected.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-jest/jest.config.js +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-jest/package.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-jest/src/export.js +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-jest/src/redact.js +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-jest/test/redact.test.js +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-jest/testguard.claims.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-node/.gitignore +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-node/README.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-node/expected.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-node/package.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-node/src/policy.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-node/src/unused.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-node/test/flaky.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-node/test/mixed.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-node/test/policy.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-node/testguard.claims.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-playwright/.gitignore +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-playwright/README.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-playwright/expected.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-playwright/package-lock.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-playwright/package.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-python/.gitignore +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-python/README.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-python/demo/__init__.py +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-python/demo/export.py +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-python/demo/orphan.py +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-python/demo/pipeline.py +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-python/demo/redact.py +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-python/expected.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-python/testguard.claims.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-python/tests/test_patched.py +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/fixtures/known-answer-python/tests/test_redact.py +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/README.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/annotations.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/authoring-input.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/draft-append.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/annotations.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/authoring-input.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/baseline.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/brief.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/calibration-websec.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/calibration.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/claims-methods.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/claims.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/evidence-negative-control.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/evidence-persistence-signal.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/evidence-provisional.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/evidence-python.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/evidence-resources.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/evidence-scan-method.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/evidence.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/gate.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/ignore.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/replay.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/status-resource-cost.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/examples/status.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/baseline.zero-count.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/calibration.ci-not-wilson.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/calibration.fallback-without-basis.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/calibration.p-not-positives-over-n.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/claims.absolute-path.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/claims.noop-fault.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.persistence-signal-not-a-defender.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.persistence-signal-on-a-non-survivor.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.persistence-signal-without-reason.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.resource-accounting.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.serial-worker-conflict.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/ignore.short-reason.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/replay.nocover-when-no-test-file-remained.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/status.claim-without-file.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/status.invalid-anchor-hidden.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/status.partial-scope-counts-disagree.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/status.resource-accounting.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/status.surface-counts-disagree.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/origin-policy.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/origin-summaries.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/origins.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/conformance/schemas.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/lib/authoring-input.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/lib/fingerprint.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/lib/origins.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/lib/validate.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/lib/wilson.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/schemas/annotations.schema.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/schemas/authoring-input.schema.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/schemas/baseline.schema.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/schemas/brief.schema.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/schemas/calibration.schema.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/schemas/claims.schema.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/schemas/common.schema.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/schemas/concerns.schema.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/schemas/evidence.schema.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/schemas/gate.schema.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/schemas/ignore.schema.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/schemas/replay.schema.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/schemas/status.schema.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/spec/schemas/sweep.schema.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/admit/admit.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/baseline/baseline.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/brief/brief.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/claims/anchors.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/claims/annotation-placement.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/claims/annotations.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/claims/load.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/claims/removed.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/cli.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/command-budget.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/commands/admit.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/commands/annotations.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/commands/baseline.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/commands/brief.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/commands/claims.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/commands/concerns.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/commands/gate.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/commands/init.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/commands/mcp.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/commands/probe.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/commands/replay.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/commands/scaffold.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/commands/status.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/commands/sweep.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/evidence/writer.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/gate/changed.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/git.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/init/init.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/init/templates/SKILL.md +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/mcp/server.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/mcp/tools.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/attribution.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/classify.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/contention.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/cost.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/discover.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/imports.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/independence.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/inject.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/mocks.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/origin-policy.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/policy-freshness.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/probe.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/progress.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/pyimports.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/rank.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/runners/discovery.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/runners/index.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/runners/jest.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/runners/lifecycle.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/runners/node/report.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/runners/node/runner.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/runners/playwright.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/runners/python/_testguard_report.py +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/runners/python.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/runners/shared.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/runners/vitest.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/universe.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/probe/worktree.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/render.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/replay/label.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/replay/replay.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/scaffold/admission.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/scaffold/append.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/scaffold/apply-append.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/scaffold/fix-input.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/scaffold/fix-paths.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/scaffold/intent.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/scaffold/open-append.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/scaffold/plan-append.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/scaffold/plan-intent-input.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/scaffold/prepare-append.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/scaffold/producers.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/scaffold/producers.python.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/scaffold/request.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/scaffold/scaffold.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/status/status.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/status/surface.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/supply/concerns.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/supply/feedback.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/supply/persistence.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/supply/savepath.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/supply/select.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/sweep/pool.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/sweep/sweep.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/util/glob.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/src/util/hash.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/admit-decision.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/admit.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/anchor-admission.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/annotation-advisory-limits.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/annotation-apply.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/annotation-cli.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/annotation-placement.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/annotations.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/attribution.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/baseline.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/brief.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/calibration.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/claims-anchors.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/claims-since.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/classify.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/cli-unexpected-error.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/command-budget-write-boundaries.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/command-budget.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/contention.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/cost.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/discover.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/discovery-config.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/draft-admission.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/draft-append-apply.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/draft-append-cli.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/draft-append-open.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/draft-append-plan.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/draft-append-prepare.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/draft-append.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/evidence-persistence-signal.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/fault-defender-orchestration.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/fault-defenders.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/fix-input.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/fix-inventory.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/fix-nested.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/fix-paths.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/fix-projects.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/gate-ref.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/gitlab-template.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/glob.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/helpers/gate-project.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/helpers/probe-assertions.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/helpers/release-project.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/imports.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/independence.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/init.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/inject.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/intent-authoring.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/intent-document.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/intent-input-cli.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/intent-input-plan.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/intent-input-request.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/intent-project.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/label.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/mcp-next.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/mcp-protocol.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/mcp.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/mocks.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/native-universe-hash.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/native-universe.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/negative-control-capabilities.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/negative-control.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/one-pager.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/origin-policy-admission.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/origin-policy-cli.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/origin-policy-projections.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/origin-policy-request.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/origin-policy.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/origin-reporting.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/origin-summary.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/policy-freshness.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/probe-control.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/probe-dirty-admission.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/probe-dirty-paths.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/probe-error-boundary.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/probe-error.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/probe-isolation.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/probe-native-manifest.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/probe-persistence-signal.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/probe-preconditions.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/probe-records.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/probe.fixture.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/probe.jest.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/probe.node.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/probe.playwright.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/probe.python.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/progress.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/pyimports.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/rank-aliases.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/release-approval.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/release-sync.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/release-tap.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/release-version.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/replay-mixed-runners.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/replay-no-test-file-left.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/replay-python-defenders.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/replay-selection.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/replay-verdict.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/replay.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/resource-safety.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/resource-timing.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/runner-argv.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/runner-command.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/runner-discovery.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/runner-node-admission.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/runner-node-controller.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/runner-node-report.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/runner-output-bounds.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/runner-playwright.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/runner-project-resolution.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/runner-python.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/runner-report-allocation.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/runner-report.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/runner-selection-blind.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/runner-vitest.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/runner-worker-argv.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/samples/playwright-report.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/samples/python-report-collect-error.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/samples/python-report.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/scaffold-admitted-source.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/scaffold-placeholder.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/scaffold.python.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/scaffold.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/snapshot.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/status.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/supply-concerns.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/supply-feedback.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/supply-persistence.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/supply-savepath.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/supply-select.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/surface.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/sweep-pool.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/sweep.fixture.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/sweep.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/vitest-config.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/worker-policy.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/worktree.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/test/writer.test.mjs +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/testguard.cost-budget.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/testguard.ignore.json +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/testguard_cli/__init__.py +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/testguard_cli/wrapper.py +0 -0
- {testguard_cli-0.18.1 → testguard_cli-0.18.2}/vitest.config.mjs +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Same fixture commit and recipes, N=3, fresh workers, no reused verdicts.
|
|
3
|
+
// Warmups excluded; ABBA timing is informational, mismatched proof fails.
|
|
4
|
+
// Run without competing test processes. Keep the receipt directory for audit.
|
|
5
|
+
// node .github/scripts/benchmark-node-direct.mjs --before-ref <SHA> --out <dir>
|
|
6
|
+
import assert from 'node:assert/strict';
|
|
7
|
+
import { cpSync, mkdtempSync, readFileSync, mkdirSync, writeFileSync, rmSync, readdirSync, symlinkSync } from 'node:fs';
|
|
8
|
+
import { execFileSync } from 'node:child_process';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { dirname, join, resolve } from 'node:path';
|
|
11
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
12
|
+
import { parseArgs } from 'node:util';
|
|
13
|
+
import { performance } from 'node:perf_hooks';
|
|
14
|
+
import { probe as after } from '../../src/probe/probe.mjs';
|
|
15
|
+
import { readSpecDoc, writeSpecDoc } from '../../src/evidence/writer.mjs';
|
|
16
|
+
import { detectContention } from '../../src/probe/contention.mjs';
|
|
17
|
+
|
|
18
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
|
19
|
+
const { values } = parseArgs({ options: { out: { type: 'string' }, 'before-ref': { type: 'string' } } });
|
|
20
|
+
assert(values.out && /^[0-9a-f]{40}$/.test(values['before-ref'] ?? ''), '--out and an exact --before-ref commit SHA are required');
|
|
21
|
+
const out = resolve(values.out);
|
|
22
|
+
mkdirSync(out, { recursive: true, mode: 0o700 });
|
|
23
|
+
mkdirSync(join(out, 'workers'), { mode: 0o700 }); // Refuse to overwrite a previous study.
|
|
24
|
+
const tool = mkdtempSync(join(tmpdir(), 'tg-direct-reference-'));
|
|
25
|
+
const dir = mkdtempSync(join(tmpdir(), 'tg-direct-bench-'));
|
|
26
|
+
const git = (...args) => execFileSync('git', args, { cwd: dir, encoding: 'utf8', timeout: 10000 }).trim();
|
|
27
|
+
const projection = (record) => ({
|
|
28
|
+
id: record.claim.id + '/' + record.subject.id,
|
|
29
|
+
subject: record.subject,
|
|
30
|
+
verdict: record.verdict,
|
|
31
|
+
reason: record.detail.reason ?? null,
|
|
32
|
+
defenders: record.defenders,
|
|
33
|
+
targetHash: record.inputs?.targetHash,
|
|
34
|
+
defenderHashes: record.inputs?.defenderHashes,
|
|
35
|
+
discoveryHashes: record.inputs?.discoveryHashes,
|
|
36
|
+
baseline: (record.detail.baselineRuns ?? []).map(({ durationMs, ...run }) => run),
|
|
37
|
+
probe: (record.detail.probeRuns ?? []).map(({ durationMs, ...run }) => run),
|
|
38
|
+
negativeControl: record.detail.negativeControl ?? null,
|
|
39
|
+
});
|
|
40
|
+
// testUniverseHash intentionally changes: it binds the changed adapter itself.
|
|
41
|
+
// The unchanged entries, source bytes, defender bytes and outcomes are checked.
|
|
42
|
+
try {
|
|
43
|
+
const archive = execFileSync('git', ['archive', '--format=tar', values['before-ref']], { cwd: root, maxBuffer: 32 * 1024 * 1024, timeout: 10000 });
|
|
44
|
+
execFileSync('tar', ['-xf', '-', '-C', tool], { input: archive, timeout: 10000 });
|
|
45
|
+
symlinkSync(join(root, 'node_modules'), join(tool, 'node_modules'), 'junction');
|
|
46
|
+
const { probe: before } = await import(pathToFileURL(join(tool, 'src/probe/probe.mjs')).href);
|
|
47
|
+
cpSync(join(root, 'fixtures/known-answer-node'), dir, { recursive: true });
|
|
48
|
+
for (const file of readdirSync(join(dir, 'test'))) {
|
|
49
|
+
const path = join(dir, 'test', file);
|
|
50
|
+
const receipt = `import {writeFileSync as receipt} from 'node:fs';if(process.env.TESTGUARD_NODE_COLLECT!=='1')receipt(${JSON.stringify(join(out, 'workers'))}+'/'+process.pid+'.json',JSON.stringify({pid:process.pid,ppid:process.ppid,version:process.versions.node,file:${JSON.stringify(file)}}),{flag:'wx'});\n`;
|
|
51
|
+
writeFileSync(path, receipt + readFileSync(path, 'utf8'));
|
|
52
|
+
}
|
|
53
|
+
for (const args of [['init', '-q'], ['add', '.'], ['-c', 'user.name=research', '-c', 'user.email=research@example.invalid', 'commit', '-qm', 'same inputs']]) git(...args);
|
|
54
|
+
const head = git('rev-parse', 'HEAD');
|
|
55
|
+
const claims = readSpecDoc('claims', join(dir, 'testguard.claims.json'));
|
|
56
|
+
const expected = JSON.parse(readFileSync(join(dir, 'expected.json'))).expected;
|
|
57
|
+
const results = [];
|
|
58
|
+
let reference;
|
|
59
|
+
async function wave(strategy, label, measured) {
|
|
60
|
+
assert(!detectContention().detected, 'foreign runner contention');
|
|
61
|
+
const ids = new Set(readdirSync(join(out, 'workers')));
|
|
62
|
+
const start = performance.now();
|
|
63
|
+
const evidence = await ({ before, after }[strategy])({ projectDir: dir, claims, runnerName: 'node-test', confirmRuns: 3, workers: 1, serial: true, budgetMs: 10000, escalate: false });
|
|
64
|
+
const elapsedMs = performance.now() - start;
|
|
65
|
+
assert.equal(evidence.run.repo.head, head);
|
|
66
|
+
assert.equal(git('status', '--porcelain'), '');
|
|
67
|
+
assert.equal(git('worktree', 'list', '--porcelain').split('worktree ').length - 1, 1);
|
|
68
|
+
assert.equal(evidence.records.length, Object.keys(expected).length);
|
|
69
|
+
assert.deepStrictEqual(evidence.records.map((record) => record.claim.id + '/' + record.subject.id), claims.claims.flatMap((claim) => claim.faults.map((fault) => claim.id + '/' + fault.id)));
|
|
70
|
+
for (const record of evidence.records) {
|
|
71
|
+
const oracle = expected[record.claim.id + '/' + record.subject.id];
|
|
72
|
+
assert.equal(record.verdict, oracle.verdict);
|
|
73
|
+
if (oracle.reason) assert.equal(record.detail.reason, oracle.reason);
|
|
74
|
+
assert(!record.reusedFrom);
|
|
75
|
+
if (record.verdict === 'killed') {
|
|
76
|
+
assert.equal(record.detail.probeRuns.length, 3);
|
|
77
|
+
assert.equal(record.detail.baselineRuns.length, 3);
|
|
78
|
+
assert(record.detail.baselineRuns.every((run) => run.outcome === 'pass'));
|
|
79
|
+
assert(record.detail.probeRuns.every((run) => run.outcome === 'fail' && run.assertionFailures > 0));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const projected = evidence.records.map(projection);
|
|
83
|
+
reference ??= projected;
|
|
84
|
+
assert.deepStrictEqual(projected, reference);
|
|
85
|
+
writeSpecDoc('evidence', join(out, label + '-' + strategy + '.json'), evidence);
|
|
86
|
+
const receipts = readdirSync(join(out, 'workers')).filter((file) => !ids.has(file)).map((file) => JSON.parse(readFileSync(join(out, 'workers', file))));
|
|
87
|
+
for (const receipt of receipts) assert.equal(receipt.version, process.versions.node);
|
|
88
|
+
if (measured) results.push({ strategy, label, elapsedMs, measurements: evidence.run.measurements, workerReceipts: receipts.length, parentPids: new Set(receipts.map((receipt) => receipt.ppid)).size });
|
|
89
|
+
}
|
|
90
|
+
await wave('before', 'warm', false);
|
|
91
|
+
await wave('after', 'warm', false);
|
|
92
|
+
for (const [i, strategy] of ['before', 'after', 'after', 'before'].entries()) await wave(strategy, String(i), true);
|
|
93
|
+
const mean = (strategy) => results.filter((result) => result.strategy === strategy).reduce((sum, result) => sum + result.elapsedMs, 0) / 2;
|
|
94
|
+
const summary = {
|
|
95
|
+
version: process.versions.node,
|
|
96
|
+
toolHead: execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim(),
|
|
97
|
+
beforeRef: values['before-ref'], head,
|
|
98
|
+
beforeMs: mean('before'), afterMs: mean('after'), speedup: mean('before') / mean('after'),
|
|
99
|
+
verifiedOutcomes: 40, results,
|
|
100
|
+
};
|
|
101
|
+
writeFileSync(join(out, 'summary.json'), JSON.stringify(summary, null, 2) + '\n');
|
|
102
|
+
console.log(JSON.stringify(summary));
|
|
103
|
+
} finally {
|
|
104
|
+
rmSync(dir, { recursive: true, force: true });
|
|
105
|
+
rmSync(tool, { recursive: true, force: true });
|
|
106
|
+
}
|
|
@@ -247,6 +247,32 @@ jobs:
|
|
|
247
247
|
path: .testguard/ci-self-evidence.json
|
|
248
248
|
if-no-files-found: warn
|
|
249
249
|
|
|
250
|
+
native-performance:
|
|
251
|
+
if: github.event_name == 'pull_request'
|
|
252
|
+
runs-on: ubuntu-latest
|
|
253
|
+
strategy:
|
|
254
|
+
matrix:
|
|
255
|
+
node-version: [22, 24]
|
|
256
|
+
steps:
|
|
257
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
258
|
+
with:
|
|
259
|
+
fetch-depth: 0
|
|
260
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
261
|
+
with:
|
|
262
|
+
node-version: ${{ matrix.node-version }}
|
|
263
|
+
- run: npm ci --omit=dev
|
|
264
|
+
- name: Matched native probes against the exact base commit
|
|
265
|
+
env:
|
|
266
|
+
BEFORE_REF: ${{ github.event.pull_request.base.sha }}
|
|
267
|
+
run: node .github/scripts/benchmark-node-direct.mjs --before-ref "$BEFORE_REF" --out "$RUNNER_TEMP/native-performance"
|
|
268
|
+
- name: Retain timings, worker receipts and every N=3 proof
|
|
269
|
+
if: always()
|
|
270
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
271
|
+
with:
|
|
272
|
+
name: native-performance-${{ matrix.node-version }}
|
|
273
|
+
path: ${{ runner.temp }}/native-performance
|
|
274
|
+
if-no-files-found: error
|
|
275
|
+
|
|
250
276
|
native-node-floor:
|
|
251
277
|
runs-on: ubuntu-latest
|
|
252
278
|
steps:
|
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.18.2] - 2026-10-02
|
|
11
|
+
|
|
12
|
+
Automated weekly release — everything merged since `v0.18.1`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- perf: use one fresh process for single-entry native probes (#200)
|
|
17
|
+
- perf: focus gate annotation and claim selection defenders (#198)
|
|
18
|
+
- chore(homebrew): sha256 for v0.18.1 (#197)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Performance
|
|
22
|
+
|
|
23
|
+
- Eligible single-file native Node confirmations use one fresh process, avoiding a second process launch while preserving real CLI entry identity, ordinary tests beside `.only`, import and late-error refusals, budgets, and process cleanup. Discovery, multiple files and older runtimes keep the existing isolated route.
|
|
24
|
+
- Focus gate coverage, repeated claim selection and annotation ID bounds defenders on their relevant checks, retaining all original assertions in the mandatory acceptance suite and all fault recipes.
|
|
25
|
+
|
|
10
26
|
## [0.18.1] - 2026-10-01
|
|
11
27
|
|
|
12
28
|
Automated weekly release — everything merged since `v0.18.0`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: testguard-cli
|
|
3
|
-
Version: 0.18.
|
|
3
|
+
Version: 0.18.2
|
|
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.18.
|
|
154
|
+
| GitHub Action | `uses: raccioly/testguard@v0.18.2` — 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.18.
|
|
156
|
+
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.18.2/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
|
|
@@ -809,12 +809,12 @@ detected base that does not resolve is a warning for `status` and `brief`
|
|
|
809
809
|
# GitHub Actions
|
|
810
810
|
- uses: actions/checkout@v4
|
|
811
811
|
with: { fetch-depth: 0 }
|
|
812
|
-
- uses: raccioly/testguard@v0.18.
|
|
812
|
+
- uses: raccioly/testguard@v0.18.2
|
|
813
813
|
with: { command: gate }
|
|
814
814
|
|
|
815
815
|
# GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
|
|
816
816
|
include:
|
|
817
|
-
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.18.
|
|
817
|
+
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.18.2/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
818
818
|
inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
|
|
819
819
|
```
|
|
820
820
|
|
|
@@ -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.18.
|
|
130
|
+
| GitHub Action | `uses: raccioly/testguard@v0.18.2` — 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.18.
|
|
132
|
+
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.18.2/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
|
|
@@ -785,12 +785,12 @@ detected base that does not resolve is a warning for `status` and `brief`
|
|
|
785
785
|
# GitHub Actions
|
|
786
786
|
- uses: actions/checkout@v4
|
|
787
787
|
with: { fetch-depth: 0 }
|
|
788
|
-
- uses: raccioly/testguard@v0.18.
|
|
788
|
+
- uses: raccioly/testguard@v0.18.2
|
|
789
789
|
with: { command: gate }
|
|
790
790
|
|
|
791
791
|
# GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
|
|
792
792
|
include:
|
|
793
|
-
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.18.
|
|
793
|
+
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.18.2/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
794
794
|
inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
|
|
795
795
|
```
|
|
796
796
|
|
|
@@ -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.18.
|
|
147
|
+
<div class="meta">Technical Brief · v0.18.2 · 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
|
|
@@ -85,7 +85,7 @@ other tools, and it must remain portable out of this repository.
|
|
|
85
85
|
| Runtime | Node.js >= 20, ESM | `node:util.parseArgs` is the floor; the first target runners are JavaScript |
|
|
86
86
|
| Runtime dependencies | exactly one, `ajv` 8.20.0, exact-pinned | Every document is validated before it is written; everything else is standard library |
|
|
87
87
|
| Test runner (ours) | vitest | Also our first supported runner, so we dogfood it |
|
|
88
|
-
| Supported runners | vitest, jest, explicit node-test (project runners); Playwright and Python (per file) | Native Node uses
|
|
88
|
+
| Supported runners | vitest, jest, explicit node-test (project runners); Playwright and Python (per file) | Native Node uses authenticated entry receipts and a bounded structured reporter, reusing shared process budgets. Eligible single-entry confirmations use one fresh process; discovery and multiple entries retain isolated controller/workers. Auto selection does not infer Node eligibility from its availability |
|
|
89
89
|
| Schemas | JSON Schema 2020-12 | Readable without the tool; portable to other languages |
|
|
90
90
|
| Distribution | npm, PyPI, Homebrew, GitHub Action, pre-commit, GitLab component | Meet teams in the pipeline they already run |
|
|
91
91
|
| Publishing | OIDC for npm/PyPI; tap-scoped SSH deploy key for Homebrew | Registry credentials remain tokenless; the only stored publishing secret cannot access any repository except the tap |
|
|
@@ -95,15 +95,15 @@ fails the pull request naming the unclaimed file, and `status` reports
|
|
|
95
95
|
| FR-05 | `src/probe/classify.mjs` | `TG-TIMEOUT-NEVER-KILLS`, `TG-PW-TIMEOUT-NEVER-KILLS` |
|
|
96
96
|
| FR-06 | `src/baseline/baseline.mjs` | `test/baseline.test.mjs`, `TG-RESTAMP-REQUIRES-SAME-FINGERPRINTS` |
|
|
97
97
|
| FR-07 | `src/brief/`, `src/status/`, `src/mcp/` | `test/brief.test.mjs`, `test/status.test.mjs`, `TG-MCP-IS-READ-ONLY` |
|
|
98
|
-
| FR-08 | `src/gate/changed.mjs` | `test/gate.test.mjs`, `TG-GATE-UNCLAIMED-EXITS-1` |
|
|
98
|
+
| FR-08 | `src/gate/changed.mjs` | `test/gate.test.mjs`, `test/gate-coverage.test.mjs`, `TG-GATE-UNCLAIMED-EXITS-1` |
|
|
99
99
|
| FR-09 | `src/probe/mocks.mjs` | `test/mocks.test.mjs`, `TG-MOCKING-FILE-IS-NOT-A-DEFENDER` |
|
|
100
|
-
| FR-10 | `src/probe/runners/` | fixture acceptance per runner, `TG-RUNNER-FROM-PROJECT-FIRST` |
|
|
100
|
+
| FR-10 | `src/probe/runners/` | fixture acceptance per runner, `TG-RUNNER-FROM-PROJECT-FIRST`, `test/runner-node-direct.test.mjs` and `TG-NODE-DIRECT-KEEPS-FAILURE-BOUNDARIES` |
|
|
101
101
|
| FR-11 | `src/scaffold/producers.mjs` | `test/scaffold.test.mjs`, `test/scaffold.python.test.mjs`, `TG-SCAFFOLD-ANCHORS-HIT`, `TG-SCAFFOLD-NEVER-PROPOSES-A-NO-OP`, `TG-SCAFFOLD-LOOP-GUARD-IS-A-GUARD` |
|
|
102
102
|
| FR-12 | `src/admit/admit.mjs` | `test/admit.test.mjs`, `TG-ADMIT-NEEDS-ALL-KILLED` |
|
|
103
103
|
| FR-13 | `src/replay/` | `test/replay.test.mjs`, `test/calibration.test.mjs`, `TG-REPLAY-FLAKY-IS-NEVER-CAUGHT`, `TG-CALIBRATION-COUNTS-NOCOVER-AS-A-MISS`, `TG-CALIBRATION-COUNTS-ONLY-BUGS` |
|
|
104
104
|
| FR-14 | `src/mcp/` | `test/mcp.test.mjs` |
|
|
105
105
|
| FR-15 | `src/claims/anchors.mjs`, `src/commands/claims.mjs`, `src/status/status.mjs` | `test/claims-anchors.test.mjs`, `test/status.test.mjs`, `TG-ANCHOR-PREFLIGHT-FAILS-FAST` |
|
|
106
|
-
| FR-16 | `src/cli.mjs`, `src/commands/probe.mjs`, `src/probe/probe.mjs`, `src/commands/scaffold.mjs`, `src/commands/admit.mjs` | `test/claim-selection.test.mjs`, `test/probe.fixture.test.mjs`, `TG-REPEATED-CLAIMS-ARE-ALL-PROBED` |
|
|
106
|
+
| FR-16 | `src/cli.mjs`, `src/commands/probe.mjs`, `src/probe/probe.mjs`, `src/commands/scaffold.mjs`, `src/commands/admit.mjs` | `test/claim-selection.test.mjs`, `test/claim-options.test.mjs`, `test/probe.fixture.test.mjs`, `TG-REPEATED-CLAIMS-ARE-ALL-PROBED` |
|
|
107
107
|
| FR-17 | `src/probe/attribution.mjs`, `src/probe/probe.mjs`, `src/commands/claims.mjs`, `src/status/status.mjs`, `src/probe/cost.mjs` | `test/fault-defenders.test.mjs`, `test/attribution.test.mjs`, `test/status.test.mjs`, `test/cost.test.mjs`, `TG-FAULT-DEFENDER-SELECTION`, `TG-FAULT-DEFENDER-ORCHESTRATION` |
|
|
108
108
|
| NFR-01 | `src/init/init.mjs`, runner resolution | `TG-INIT-HOOK-NO-NETWORK`, `TG-README-HOOK-MATCHES-THE-CODE` (every surface, not only the README), `TG-GITIGNORE-ADVICE-IS-ONE-LIST` |
|
|
109
109
|
| NFR-02 | `package.json` | `npm run test:install` in CI |
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testguard-cli",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "testguard-cli",
|
|
9
|
-
"version": "0.18.
|
|
9
|
+
"version": "0.18.2",
|
|
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.18.
|
|
3
|
+
"version": "0.18.2",
|
|
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.18.
|
|
6
|
+
# - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.18.2/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.18.
|
|
32
|
+
default: "0.18.2"
|
|
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.18.
|
|
19
|
-
sha256 "
|
|
18
|
+
url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.18.2.tgz"
|
|
19
|
+
sha256 "ce853672bf41f9570e21f661d8544c4dda6872317b4863f5aeccba1744c78c9c"
|
|
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.18.
|
|
7
|
+
version = "0.18.2"
|
|
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" }
|
|
@@ -321,7 +321,21 @@ Rules that follow from the table:
|
|
|
321
321
|
supports plain JavaScript under Node's default collection, without custom
|
|
322
322
|
loaders, `NODE_OPTIONS` or `NODE_PATH`; it is never selected by `auto`.
|
|
323
323
|
Each confirmation uses fresh, isolated Node test processes under the
|
|
324
|
-
existing worker ceiling.
|
|
324
|
+
existing worker ceiling. On Node22.8+, Node24 and Node26, a single selected
|
|
325
|
+
entry can run inside one fresh process through a reporting preload and the
|
|
326
|
+
public in-process API; Node evaluates the actual CLI entry, preserving
|
|
327
|
+
CommonJS `require.main`, ESM `import.meta.main` and argv identity. This process
|
|
328
|
+
serves no other entry or confirmation. Discovery, multiple
|
|
329
|
+
entries and other runtime families retain the isolated controller/worker
|
|
330
|
+
route. Single-entry execution preserves ordinary tests beside `.only`,
|
|
331
|
+
records entry provenance before evaluation, monitors entry-load failures
|
|
332
|
+
after test registration, and publishes only at process exit when no
|
|
333
|
+
out-of-test failure or incompatible exit code occurred. A complete stream
|
|
334
|
+
cannot turn a later exception, rejection or retained-handle timeout into a
|
|
335
|
+
kill. Report and entry destinations are captured before project evaluation;
|
|
336
|
+
project environment edits cannot redirect these owned outputs. Both routes
|
|
337
|
+
bind their implementation to native discovery identity.
|
|
338
|
+
Reports count executed test results, not individual
|
|
325
339
|
assertion calls: suite and file-container summaries are not extra tests,
|
|
326
340
|
and skipped/TODO results cannot establish a green defender by themselves.
|
|
327
341
|
A test-body failure counts as a rejection; a load error, hook failure,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { appendFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { readBoundedJsonFile } from '../discovery.mjs';
|
|
3
|
+
import { createReport } from './report.mjs';
|
|
4
|
+
|
|
5
|
+
// This preload returns before Node evaluates its actual CLI entry. Running the
|
|
6
|
+
// file as the entry preserves require.main, import.meta.main and process.argv.
|
|
7
|
+
const file = process.argv[1];
|
|
8
|
+
if (!file) throw new Error('direct node-test requires an entry');
|
|
9
|
+
const entriesFile = process.env.TESTGUARD_NODE_ENTRIES;
|
|
10
|
+
const reportFile = process.env.TESTGUARD_NODE_REPORT;
|
|
11
|
+
const diagnosticFile = `${entriesFile}.error`;
|
|
12
|
+
const report = createReport({ projectDir: process.cwd(), version: process.versions.node });
|
|
13
|
+
let document;
|
|
14
|
+
let outsideFailure = false;
|
|
15
|
+
process.on('uncaughtExceptionMonitor', (error) => {
|
|
16
|
+
outsideFailure = true;
|
|
17
|
+
writeFileSync(diagnosticFile, JSON.stringify({ name: String(error?.name ?? 'Error').slice(0, 100), message: String(error?.message ?? error).slice(0, 1000) }), { mode: 0o600 });
|
|
18
|
+
});
|
|
19
|
+
process.on('unhandledRejection', () => { outsideFailure = true; process.exitCode = 1; });
|
|
20
|
+
// A complete stream is insufficient: a retained timer can fail or hang later.
|
|
21
|
+
// Publish only at process exit, after CLI loading and out-of-test failures settle.
|
|
22
|
+
process.on('exit', (code) => {
|
|
23
|
+
if (!document || outsideFailure || code !== (document.failed ? 1 : 0)) return;
|
|
24
|
+
const text = JSON.stringify(document);
|
|
25
|
+
if (Buffer.byteLength(text) > 16 * 1024 * 1024) return;
|
|
26
|
+
writeFileSync(reportFile, text, { mode: 0o600 });
|
|
27
|
+
});
|
|
28
|
+
const receipt = JSON.stringify({ file, pid: process.pid, version: process.versions.node });
|
|
29
|
+
if (Buffer.byteLength(receipt) > 8192) throw new Error('node-test entry receipt exceeded its byte bound');
|
|
30
|
+
appendFileSync(entriesFile, `${receipt},`);
|
|
31
|
+
|
|
32
|
+
// Node24+ otherwise enables implicit .only filtering for isolation:none,
|
|
33
|
+
// even with run({only:false}). Preserve isolated-worker filtering semantics.
|
|
34
|
+
process.env.NODE_TEST_CONTEXT = 'child-v8';
|
|
35
|
+
const { run } = await import('node:test');
|
|
36
|
+
const stream = run({ files: [], isolation: 'none', only: false, setup: () => new Promise((resolve) => {
|
|
37
|
+
// Keep the public root open until Node has loaded the real CLI entry. An
|
|
38
|
+
// awaited stream here would block entry evaluation; the drain runs below.
|
|
39
|
+
process.once('beforeExit', resolve);
|
|
40
|
+
}) });
|
|
41
|
+
(async () => {
|
|
42
|
+
try {
|
|
43
|
+
for await (const event of stream) report.accept(event);
|
|
44
|
+
appendFileSync(entriesFile, 'null]}');
|
|
45
|
+
const entries = readBoundedJsonFile(entriesFile).entries;
|
|
46
|
+
if (!Array.isArray(entries) || entries.length !== 2 || entries.pop() !== null) throw new Error('incomplete direct node-test entry receipt');
|
|
47
|
+
document = report.finish(entries);
|
|
48
|
+
if (document.failed) process.exitCode = 1;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
outsideFailure = true;
|
|
51
|
+
stream.destroy();
|
|
52
|
+
process.stderr.write(`node-test reporter failed: ${String(error.message ?? error).slice(0, 1000)}\n`);
|
|
53
|
+
process.exitCode = 1;
|
|
54
|
+
}
|
|
55
|
+
})();
|
|
@@ -9,3 +9,9 @@ if (process.env.NODE_TEST_CONTEXT?.startsWith('child')) {
|
|
|
9
9
|
}
|
|
10
10
|
appendFileSync(process.env.TESTGUARD_NODE_ENTRIES, `${receipt},`);
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
// A fresh single-entry process initializes reporting before its real CLI
|
|
14
|
+
// entry. Inherited child context must not bootstrap a second reporter in a fork.
|
|
15
|
+
if (process.env.TESTGUARD_NODE_DIRECT === '1' && process.env.NODE_TEST_CONTEXT === '') {
|
|
16
|
+
await import('./direct.mjs');
|
|
17
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { closeSync, mkdtempSync, openSync, readFileSync, readSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { closeSync, existsSync, mkdtempSync, openSync, readFileSync, readSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { runProcess } from './shared.mjs';
|
|
7
|
-
import { createDiscoveryManifest, DiscoveryError, hashDiscoveryConfigs, normalizeDiscoveredFiles, runDiscoveryProcess } from './discovery.mjs';
|
|
7
|
+
import { createDiscoveryManifest, DiscoveryError, hashDiscoveryConfigs, normalizeDiscoveredFiles, readBoundedJsonFile, runDiscoveryProcess } from './discovery.mjs';
|
|
8
8
|
|
|
9
9
|
export const name = 'node-test';
|
|
10
10
|
export const testGlobs = []; // Native collection is authoritative; never guess from globs.
|
|
@@ -12,6 +12,11 @@ const HERE = join(dirname(fileURLToPath(import.meta.url)), 'node');
|
|
|
12
12
|
const RUNNER = join(HERE, 'runner.mjs');
|
|
13
13
|
const ENTRY = join(HERE, 'entry.mjs');
|
|
14
14
|
const VERSION = process.versions.node;
|
|
15
|
+
/** Admit only maintained runtime families with the in-process public API. */
|
|
16
|
+
export function supportsSingleProcess(version = VERSION) {
|
|
17
|
+
const [major, minor] = version.split('.').map(Number);
|
|
18
|
+
return major === 22 && minor >= 8 || major === 24 || major === 26;
|
|
19
|
+
}
|
|
15
20
|
const strategyHash = () => {
|
|
16
21
|
const digest = createHash('sha256');
|
|
17
22
|
const descriptor = openSync(process.execPath, 'r');
|
|
@@ -20,7 +25,7 @@ const strategyHash = () => {
|
|
|
20
25
|
let length;
|
|
21
26
|
while ((length = readSync(descriptor, bytes, 0, bytes.length, null)) > 0) digest.update(bytes.subarray(0, length));
|
|
22
27
|
} finally { closeSync(descriptor); }
|
|
23
|
-
for (const file of [fileURLToPath(import.meta.url), ...['runner.mjs', 'entry.mjs', 'report.mjs'].map((file) => join(HERE, file)), ...['shared.mjs', 'discovery.mjs', 'lifecycle.mjs'].map((file) => join(HERE, '..', file))]) digest.update(readFileSync(file));
|
|
28
|
+
for (const file of [fileURLToPath(import.meta.url), ...['runner.mjs', 'direct.mjs', 'entry.mjs', 'report.mjs'].map((file) => join(HERE, file)), ...['shared.mjs', 'discovery.mjs', 'lifecycle.mjs'].map((file) => join(HERE, '..', file))]) digest.update(readFileSync(file));
|
|
24
29
|
return digest.digest('hex');
|
|
25
30
|
};
|
|
26
31
|
const admissionError = () => Number(VERSION.split('.')[0]) < 20 ? 'node-test requires Node20 or newer'
|
|
@@ -37,15 +42,16 @@ const receipts = () => {
|
|
|
37
42
|
writeFileSync(file, '{"entries":[', { mode: 0o600 });
|
|
38
43
|
return { dir, file };
|
|
39
44
|
};
|
|
40
|
-
const environment = (file, collect, workers) => ({
|
|
45
|
+
const environment = (file, collect, workers, direct = false) => ({
|
|
41
46
|
TESTGUARD_NODE_ENTRIES: file,
|
|
42
47
|
TESTGUARD_NODE_COLLECT: collect ? '1' : '0',
|
|
43
48
|
TESTGUARD_NODE_WORKERS: String(workers),
|
|
44
49
|
TESTGUARD_NODE_REPORT: '{out}',
|
|
50
|
+
TESTGUARD_NODE_DIRECT: direct ? '1' : '0',
|
|
45
51
|
NODE_TEST_CONTEXT: '',
|
|
46
52
|
NODE_DISABLE_COMPILE_CACHE: '1',
|
|
47
53
|
});
|
|
48
|
-
export const argvFor = (projectDir, files) => [process.execPath, `--import=${ENTRY}`, RUNNER, ...files.map((file) => resolve(realpathSync(projectDir), file))];
|
|
54
|
+
export const argvFor = (projectDir, files, { direct = false } = {}) => [process.execPath, `--import=${ENTRY}`, ...(!direct ? [RUNNER] : []), ...files.map((file) => resolve(realpathSync(projectDir), file))];
|
|
49
55
|
|
|
50
56
|
/** Reject unsupported/incomplete envelopes before deriving any verdict inputs. */
|
|
51
57
|
export function validateReport(report) {
|
|
@@ -120,11 +126,19 @@ export async function run(opts) {
|
|
|
120
126
|
const files = normalizeDiscoveredFiles(opts.projectDir, opts.files);
|
|
121
127
|
const spool = receipts();
|
|
122
128
|
try {
|
|
123
|
-
|
|
129
|
+
const direct = files.length === 1 && supportsSingleProcess();
|
|
130
|
+
const result = await runProcess({ ...opts, files, cleanupOnClose: true, env: environment(spool.file, false, opts.serial ? 1 : opts.workers ?? 1, direct), argv: (selected) => argvFor(opts.projectDir, selected, { direct }), parse: (report, durationMs) => {
|
|
124
131
|
validateReport(report);
|
|
125
132
|
const entries = normalizeDiscoveredFiles(opts.projectDir, report.entries.map((entry) => entry.file));
|
|
126
133
|
if (entries.length !== report.entries.length || JSON.stringify(entries) !== JSON.stringify(files)) throw new DiscoveryError('node-test did not execute every selected entry exactly');
|
|
127
134
|
return parseReport(report, durationMs);
|
|
128
135
|
} });
|
|
136
|
+
// CLI loading can fail before a complete stream exists. Keep the actual
|
|
137
|
+
// bounded exception identity rather than Node's trailing version banner.
|
|
138
|
+
if (result.run.outcome === 'error' && existsSync(`${spool.file}.error`)) {
|
|
139
|
+
const diagnostic = readBoundedJsonFile(`${spool.file}.error`, 8192);
|
|
140
|
+
if (typeof diagnostic.name === 'string' && diagnostic.name.length <= 100 && typeof diagnostic.message === 'string' && diagnostic.message.length <= 1000) result.loadMessage = `${diagnostic.name}: ${diagnostic.message}`;
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
129
143
|
} finally { rmSync(spool.dir, { recursive: true, force: true }); }
|
|
130
144
|
}
|
|
@@ -2,8 +2,6 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
2
2
|
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { spawnSync } from 'node:child_process';
|
|
7
5
|
import { claimsCommand } from '../src/commands/claims.mjs';
|
|
8
6
|
import { computeStatus, renderStatus } from '../src/status/status.mjs';
|
|
9
7
|
import { validate } from '../spec/lib/validate.mjs';
|
|
@@ -88,21 +86,6 @@ describe('annotation guidance is not verification', () => {
|
|
|
88
86
|
expect(status.notes.join('\n')).not.toContain('EACCES');
|
|
89
87
|
expect(status.notes.join('\n')).not.toContain('Missing source annotation:');
|
|
90
88
|
});
|
|
91
|
-
it('finishes lexical scanning for long prose tokens without a hyphen', () => {
|
|
92
|
-
const dir = project(); writeFileSync(join(dir, 'prose.md'), `@claim ${'a'.repeat(256)}\n`);
|
|
93
|
-
const run = spawnSync(process.execPath, ['--input-type=module', '-e',
|
|
94
|
-
'const { projectAnnotationAdvisory, scanAnnotations } = await import(process.argv[1]); const advice = projectAnnotationAdvisory(process.argv[2], { claims: [{ id: "A-1" }] }); if (advice.missingIds.join() !== "A-1" || scanAnnotations(process.argv[2]).length) process.exit(1);',
|
|
95
|
-
new URL('../src/claims/annotations.mjs', import.meta.url).href, dir], { encoding: 'utf8', timeout: 2000 });
|
|
96
|
-
expect(run.error).toBeUndefined(); expect(run.status).toBe(0);
|
|
97
|
-
});
|
|
98
|
-
it('promptly refuses valid shared-format IDs outside placement vocabulary', () => {
|
|
99
|
-
const dir = project(); const path = join(dir, 'testguard.claims.json');
|
|
100
|
-
const claims = { schemaVersion: 1, claims: [{ id: 'a'.repeat(128), statement: 'Intended behavior.', severity: 'high', source: { kind: 'manual' }, producedBy: { producer: 'human' }, faults: [{ id: 'F1', file: 'a.mjs', find: 'true', replace: 'false', description: 'Break behavior.', faultClass: 'other', producedBy: { producer: 'human' } }] }] };
|
|
101
|
-
expect(validate('claims', claims).ok).toBe(true); writeFileSync(path, JSON.stringify(claims));
|
|
102
|
-
const run = spawnSync(process.execPath, [fileURLToPath(new URL('../cli/testguard.mjs', import.meta.url)), 'claims', dir, '--annotate', '--json'], { encoding: 'utf8', timeout: 2000 });
|
|
103
|
-
expect(run.error).toBeUndefined(); expect(run.status).toBe(2);
|
|
104
|
-
expect(JSON.parse(run.stdout).refused[0].reason).toBe('unsupported-claim-id');
|
|
105
|
-
});
|
|
106
89
|
it('explicitly preserves custom claims selection in preview advice', async () => {
|
|
107
90
|
const dir = project(); const custom = join(dir, 'custom.json');
|
|
108
91
|
writeFileSync(custom, JSON.stringify({ schemaVersion: 1, claims: [{ id: 'B-1', statement: 'Custom intent.', severity: 'high', source: { kind: 'manual' }, producedBy: { producer: 'human' }, faults: [{ id: 'F1', file: 'a.mjs', find: 'true', replace: 'false', description: 'Break condition.', faultClass: 'other', producedBy: { producer: 'human' } }] }] }));
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { validate } from '../spec/lib/validate.mjs';
|
|
8
|
+
|
|
9
|
+
const roots = [];
|
|
10
|
+
afterEach(() => roots.splice(0).forEach((dir) => rmSync(dir, { recursive: true, force: true })));
|
|
11
|
+
function project() {
|
|
12
|
+
const dir = mkdtempSync(join(tmpdir(), 'tg-annotation-bounds-')); roots.push(dir);
|
|
13
|
+
writeFileSync(join(dir, 'a.mjs'), 'export const allowed = true;\n');
|
|
14
|
+
return dir;
|
|
15
|
+
}
|
|
16
|
+
describe('bounded annotation ID matching', () => {
|
|
17
|
+
|
|
18
|
+
it('finishes lexical scanning for long prose tokens without a hyphen', () => {
|
|
19
|
+
const dir = project(); writeFileSync(join(dir, 'prose.md'), `@claim ${'a'.repeat(256)}\n`);
|
|
20
|
+
const run = spawnSync(process.execPath, ['--input-type=module', '-e',
|
|
21
|
+
'const { projectAnnotationAdvisory, scanAnnotations } = await import(process.argv[1]); const advice = projectAnnotationAdvisory(process.argv[2], { claims: [{ id: "A-1" }] }); if (advice.missingIds.join() !== "A-1" || scanAnnotations(process.argv[2]).length) process.exit(1);',
|
|
22
|
+
new URL('../src/claims/annotations.mjs', import.meta.url).href, dir], { encoding: 'utf8', timeout: 2000 });
|
|
23
|
+
expect(run.error).toBeUndefined(); expect(run.status).toBe(0);
|
|
24
|
+
});
|
|
25
|
+
it('promptly refuses valid shared-format IDs outside placement vocabulary', () => {
|
|
26
|
+
const dir = project(); const path = join(dir, 'testguard.claims.json');
|
|
27
|
+
const claims = { schemaVersion: 1, claims: [{ id: 'a'.repeat(128), statement: 'Intended behavior.', severity: 'high', source: { kind: 'manual' }, producedBy: { producer: 'human' }, faults: [{ id: 'F1', file: 'a.mjs', find: 'true', replace: 'false', description: 'Break behavior.', faultClass: 'other', producedBy: { producer: 'human' } }] }] };
|
|
28
|
+
expect(validate('claims', claims).ok).toBe(true); writeFileSync(path, JSON.stringify(claims));
|
|
29
|
+
const run = spawnSync(process.execPath, [fileURLToPath(new URL('../cli/testguard.mjs', import.meta.url)), 'claims', dir, '--annotate', '--json'], { encoding: 'utf8', timeout: 2000 });
|
|
30
|
+
expect(run.error).toBeUndefined(); expect(run.status).toBe(2);
|
|
31
|
+
expect(JSON.parse(run.stdout).refused[0].reason).toBe('unsupported-claim-id');
|
|
32
|
+
});
|
|
33
|
+
});
|