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.
Files changed (310) hide show
  1. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/CHANGELOG.md +26 -0
  2. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/PKG-INFO +15 -7
  3. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/README.md +14 -6
  4. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/action.yml +1 -1
  5. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs/testguard-explained.html +1 -1
  6. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs/testguard-explained.pdf +0 -0
  7. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs-canonical/ENVIRONMENT.md +12 -2
  8. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/package-lock.json +2 -2
  9. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/package.json +1 -1
  10. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/packaging/gitlab/testguard.gitlab-ci.yml +2 -2
  11. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/packaging/homebrew/testguard.rb +2 -2
  12. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/pyproject.toml +1 -1
  13. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/GATE-SEMANTICS.md +8 -3
  14. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/brief.mjs +1 -1
  15. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/gate.mjs +3 -3
  16. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/probe.mjs +1 -1
  17. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/status.mjs +1 -1
  18. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/gate/changed.mjs +55 -10
  19. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/init/templates/SKILL.md +1 -1
  20. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/mcp/tools.mjs +1 -1
  21. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/gate.test.mjs +52 -2
  22. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/testguard.claims.json +59 -1
  23. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.agents/skills/testguard/SKILL.md +0 -0
  24. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.codex/hooks.json +0 -0
  25. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.docguard.json +0 -0
  26. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.docguardignore +0 -0
  27. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.gitattributes +0 -0
  28. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  29. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  30. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  31. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/dependabot.yml +0 -0
  32. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/scripts/approve-held-runs.sh +0 -0
  33. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/scripts/build-one-pager.mjs +0 -0
  34. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/scripts/check-cost-budget.mjs +0 -0
  35. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/scripts/check-python-fixture.mjs +0 -0
  36. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/scripts/install-smoke.mjs +0 -0
  37. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/scripts/publish-homebrew-tap.sh +0 -0
  38. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/scripts/sync-release-version.mjs +0 -0
  39. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/workflows/auto-merge.yml +0 -0
  40. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/workflows/ci.yml +0 -0
  41. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/workflows/release.yml +0 -0
  42. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/workflows/scheduled-release.yml +0 -0
  43. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.github/workflows/supply-chain.yml +0 -0
  44. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.gitignore +0 -0
  45. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.npmignore +0 -0
  46. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.npmrc +0 -0
  47. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.pre-commit-config.yaml +0 -0
  48. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/.pre-commit-hooks.yaml +0 -0
  49. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/AGENTS.md +0 -0
  50. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/CLAUDE.md +0 -0
  51. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/CODE_OF_CONDUCT.md +0 -0
  52. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/CONTRIBUTING.md +0 -0
  53. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/DRIFT-LOG.md +0 -0
  54. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/LICENSE +0 -0
  55. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/PRIVACY.md +0 -0
  56. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/SECURITY.md +0 -0
  57. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/SUPPORT.md +0 -0
  58. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/bench/README.md +0 -0
  59. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/cli/testguard.mjs +0 -0
  60. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs-canonical/ARCHITECTURE.md +0 -0
  61. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs-canonical/DATA-MODEL.md +0 -0
  62. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs-canonical/REQUIREMENTS.md +0 -0
  63. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs-canonical/SECURITY.md +0 -0
  64. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/docs-canonical/TEST-SPEC.md +0 -0
  65. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/.gitignore +0 -0
  66. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/README.md +0 -0
  67. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/expected.json +0 -0
  68. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/package.json +0 -0
  69. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/src/export.mjs +0 -0
  70. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/src/redact.mjs +0 -0
  71. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
  72. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/test/flaky.test.mjs +0 -0
  73. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/test/redact.test.mjs +0 -0
  74. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/testguard.claims.json +0 -0
  75. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer/vitest.config.mjs +0 -0
  76. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/.gitignore +0 -0
  77. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/README.md +0 -0
  78. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/expected.json +0 -0
  79. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/jest.config.js +0 -0
  80. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/package.json +0 -0
  81. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/src/export.js +0 -0
  82. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/src/redact.js +0 -0
  83. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
  84. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
  85. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/test/redact.test.js +0 -0
  86. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-jest/testguard.claims.json +0 -0
  87. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/.gitignore +0 -0
  88. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/README.md +0 -0
  89. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
  90. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
  91. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/expected.json +0 -0
  92. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/package-lock.json +0 -0
  93. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/package.json +0 -0
  94. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
  95. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
  96. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
  97. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/test-results/.last-run.json +0 -0
  98. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
  99. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
  100. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/.gitignore +0 -0
  101. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/README.md +0 -0
  102. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/demo/__init__.py +0 -0
  103. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/demo/export.py +0 -0
  104. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/demo/orphan.py +0 -0
  105. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/demo/pipeline.py +0 -0
  106. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/demo/redact.py +0 -0
  107. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/expected.json +0 -0
  108. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/testguard.claims.json +0 -0
  109. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
  110. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
  111. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/tests/test_patched.py +0 -0
  112. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/fixtures/known-answer-python/tests/test_redact.py +0 -0
  113. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/README.md +0 -0
  114. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/baseline.json +0 -0
  115. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/brief.json +0 -0
  116. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/calibration-websec.json +0 -0
  117. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/calibration.json +0 -0
  118. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/claims-methods.json +0 -0
  119. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/claims.json +0 -0
  120. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
  121. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/evidence-negative-control.json +0 -0
  122. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/evidence-provisional.json +0 -0
  123. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/evidence-python.json +0 -0
  124. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/evidence-scan-method.json +0 -0
  125. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/evidence.json +0 -0
  126. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/gate.json +0 -0
  127. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/ignore.json +0 -0
  128. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/replay.json +0 -0
  129. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/examples/status.json +0 -0
  130. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
  131. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/baseline.zero-count.json +0 -0
  132. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
  133. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
  134. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +0 -0
  135. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +0 -0
  136. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.ci-not-wilson.json +0 -0
  137. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +0 -0
  138. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.fallback-without-basis.json +0 -0
  139. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.p-not-positives-over-n.json +0 -0
  140. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
  141. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
  142. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.absolute-path.json +0 -0
  143. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
  144. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
  145. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +0 -0
  146. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
  147. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.noop-fault.json +0 -0
  148. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
  149. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
  150. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
  151. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
  152. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
  153. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
  154. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
  155. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
  156. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
  157. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
  158. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
  159. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
  160. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
  161. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
  162. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +0 -0
  163. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
  164. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
  165. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
  166. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
  167. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
  168. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
  169. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
  170. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
  171. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
  172. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
  173. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
  174. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
  175. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
  176. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
  177. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
  178. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
  179. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/ignore.short-reason.json +0 -0
  180. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
  181. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
  182. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/status.claim-without-file.json +0 -0
  183. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
  184. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/status.invalid-anchor-hidden.json +0 -0
  185. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/status.partial-scope-counts-disagree.json +0 -0
  186. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/status.surface-counts-disagree.json +0 -0
  187. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
  188. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
  189. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/conformance/schemas.test.mjs +0 -0
  190. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/lib/fingerprint.mjs +0 -0
  191. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/lib/validate.mjs +0 -0
  192. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/lib/wilson.mjs +0 -0
  193. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/baseline.schema.json +0 -0
  194. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/brief.schema.json +0 -0
  195. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/calibration.schema.json +0 -0
  196. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/claims.schema.json +0 -0
  197. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/common.schema.json +0 -0
  198. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/evidence.schema.json +0 -0
  199. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/gate.schema.json +0 -0
  200. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/ignore.schema.json +0 -0
  201. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/replay.schema.json +0 -0
  202. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/spec/schemas/status.schema.json +0 -0
  203. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/admit/admit.mjs +0 -0
  204. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/baseline/baseline.mjs +0 -0
  205. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/brief/brief.mjs +0 -0
  206. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/claims/anchors.mjs +0 -0
  207. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/claims/annotations.mjs +0 -0
  208. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/claims/load.mjs +0 -0
  209. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/claims/removed.mjs +0 -0
  210. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/cli.mjs +0 -0
  211. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/admit.mjs +0 -0
  212. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/baseline.mjs +0 -0
  213. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/claims.mjs +0 -0
  214. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/init.mjs +0 -0
  215. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/mcp.mjs +0 -0
  216. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/replay.mjs +0 -0
  217. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/commands/scaffold.mjs +0 -0
  218. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/evidence/writer.mjs +0 -0
  219. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/git.mjs +0 -0
  220. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/init/init.mjs +0 -0
  221. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/mcp/server.mjs +0 -0
  222. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/attribution.mjs +0 -0
  223. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/classify.mjs +0 -0
  224. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/contention.mjs +0 -0
  225. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/cost.mjs +0 -0
  226. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/discover.mjs +0 -0
  227. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/independence.mjs +0 -0
  228. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/inject.mjs +0 -0
  229. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/mocks.mjs +0 -0
  230. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/probe.mjs +0 -0
  231. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/progress.mjs +0 -0
  232. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/pyimports.mjs +0 -0
  233. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/rank.mjs +0 -0
  234. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/index.mjs +0 -0
  235. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/jest.mjs +0 -0
  236. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/playwright.mjs +0 -0
  237. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
  238. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/python/_testguard_report.py +0 -0
  239. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
  240. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/python.mjs +0 -0
  241. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/shared.mjs +0 -0
  242. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/runners/vitest.mjs +0 -0
  243. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/probe/worktree.mjs +0 -0
  244. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/render.mjs +0 -0
  245. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/replay/label.mjs +0 -0
  246. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/replay/replay.mjs +0 -0
  247. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/scaffold/producers.mjs +0 -0
  248. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/scaffold/producers.python.mjs +0 -0
  249. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/scaffold/scaffold.mjs +0 -0
  250. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/status/status.mjs +0 -0
  251. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/status/surface.mjs +0 -0
  252. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/util/glob.mjs +0 -0
  253. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/src/util/hash.mjs +0 -0
  254. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/admit.test.mjs +0 -0
  255. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/annotations.test.mjs +0 -0
  256. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/attribution.test.mjs +0 -0
  257. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/baseline.test.mjs +0 -0
  258. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/brief.test.mjs +0 -0
  259. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/calibration.test.mjs +0 -0
  260. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/claim-selection.test.mjs +0 -0
  261. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/claims-anchors.test.mjs +0 -0
  262. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/claims-since.test.mjs +0 -0
  263. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/classify.test.mjs +0 -0
  264. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/contention.test.mjs +0 -0
  265. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/cost.test.mjs +0 -0
  266. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/discover.test.mjs +0 -0
  267. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/gitlab-template.test.mjs +0 -0
  268. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/glob.test.mjs +0 -0
  269. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/independence.test.mjs +0 -0
  270. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/init.test.mjs +0 -0
  271. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/inject.test.mjs +0 -0
  272. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/label.test.mjs +0 -0
  273. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/mcp.test.mjs +0 -0
  274. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/mocks.test.mjs +0 -0
  275. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/negative-control.test.mjs +0 -0
  276. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/one-pager.test.mjs +0 -0
  277. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/probe-error.test.mjs +0 -0
  278. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/probe-preconditions.test.mjs +0 -0
  279. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/probe.fixture.test.mjs +0 -0
  280. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/probe.jest.test.mjs +0 -0
  281. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/probe.playwright.test.mjs +0 -0
  282. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/probe.python.test.mjs +0 -0
  283. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/progress.test.mjs +0 -0
  284. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/pyimports.test.mjs +0 -0
  285. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/rank-aliases.test.mjs +0 -0
  286. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/release-approval.test.mjs +0 -0
  287. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/release-sync.test.mjs +0 -0
  288. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/replay-verdict.test.mjs +0 -0
  289. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/replay.test.mjs +0 -0
  290. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/runner-argv.test.mjs +0 -0
  291. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/runner-command.test.mjs +0 -0
  292. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/runner-playwright.test.mjs +0 -0
  293. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/runner-python.test.mjs +0 -0
  294. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/runner-vitest.test.mjs +0 -0
  295. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/samples/playwright-report.json +0 -0
  296. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/samples/python-report-collect-error.json +0 -0
  297. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/samples/python-report.json +0 -0
  298. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/scaffold.python.test.mjs +0 -0
  299. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/scaffold.test.mjs +0 -0
  300. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/snapshot.test.mjs +0 -0
  301. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/status.test.mjs +0 -0
  302. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/surface.test.mjs +0 -0
  303. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/vitest-config.test.mjs +0 -0
  304. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/worktree.test.mjs +0 -0
  305. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/test/writer.test.mjs +0 -0
  306. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/testguard.cost-budget.json +0 -0
  307. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/testguard.ignore.json +0 -0
  308. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/testguard_cli/__init__.py +0 -0
  309. {testguard_cli-0.10.2 → testguard_cli-0.10.4}/testguard_cli/wrapper.py +0 -0
  310. {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.2
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.2` — see [`action.yml`](./action.yml) |
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.2/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
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
- **In CI the base is detected** — GitHub Actions (`GITHUB_BASE_REF`) and GitLab
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`); `TESTGUARD_CHANGED_REF` overrides both.
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.2
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.2/packaging/gitlab/testguard.gitlab-ci.yml'
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.2` — see [`action.yml`](./action.yml) |
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.2/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
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
- **In CI the base is detected** — GitHub Actions (`GITHUB_BASE_REF`) and GitLab
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`); `TESTGUARD_CHANGED_REF` overrides both.
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.2
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.2/packaging/gitlab/testguard.gitlab-ci.yml'
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
 
@@ -46,7 +46,7 @@ inputs:
46
46
  version:
47
47
  description: 'testguard-cli version to run'
48
48
  required: false
49
- default: '0.10.2'
49
+ default: '0.10.4'
50
50
  node-version:
51
51
  description: 'Node.js version (20+)'
52
52
  required: false
@@ -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.2 · MIT</div>
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>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <!-- docguard:version 1.0.0 -->
4
4
  <!-- docguard:status approved -->
5
- <!-- docguard:last-reviewed 2026-09-18 -->
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.2",
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.2",
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.2",
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.2/packaging/gitlab/testguard.gitlab-ci.yml'
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.2"
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.2.tgz"
19
- sha256 "dae61a90d2eb57f116597f3bc7734d3bb9759cdcfbf00a6447b9607b87933e88"
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.2"
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 never guessed.** An explicit flag, or a CI-provided base
406
- branch, or an error. An upstream that already contains the change has an
407
- empty diff and would pass trivially.
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. In GitHub Actions and GitLab CI the base branch is detected automatically.');
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(`--changed not given; using ${ref} from ${resolved.from}`);
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. Explicit flag first, then the
54
- * environments CI sets, then nothing: a wrong default would make the gate pass
55
- * trivially (an upstream that already contains the change has an empty diff),
56
- * so no ref is an error the caller reports, never a silent guess.
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. A reference detected from CI variables
75
- * is a convenience: if it does not resolve (shallow clone, temp directory,
76
- * no remote) the command says so on stderr and continues without a change
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
- return d ? { ...d, required: false } : null;
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 (only when a reference is known: `--changed <ref>`, or CI's base branch) | `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. |
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 a reference detected from CI variables may be dropped, with a warning.",
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.",