testguard-cli 0.10.1__tar.gz → 0.10.3__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.3/.github/scripts/publish-homebrew-tap.sh +68 -0
  2. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.github/workflows/release.yml +25 -13
  3. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.github/workflows/scheduled-release.yml +1 -1
  4. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/AGENTS.md +4 -2
  5. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/CHANGELOG.md +41 -0
  6. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/CONTRIBUTING.md +7 -3
  7. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/PKG-INFO +15 -7
  8. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/README.md +14 -6
  9. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/SECURITY.md +6 -4
  10. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/action.yml +1 -1
  11. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/docs/testguard-explained.html +1 -1
  12. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/docs/testguard-explained.pdf +0 -0
  13. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/docs-canonical/ARCHITECTURE.md +5 -5
  14. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/docs-canonical/ENVIRONMENT.md +12 -2
  15. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/docs-canonical/SECURITY.md +14 -5
  16. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/docs-canonical/TEST-SPEC.md +1 -1
  17. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/package-lock.json +2 -2
  18. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/package.json +1 -1
  19. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/packaging/gitlab/testguard.gitlab-ci.yml +2 -2
  20. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/packaging/homebrew/testguard.rb +2 -2
  21. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/pyproject.toml +1 -1
  22. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/GATE-SEMANTICS.md +8 -3
  23. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/commands/brief.mjs +1 -1
  24. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/commands/gate.mjs +3 -3
  25. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/commands/probe.mjs +1 -1
  26. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/commands/status.mjs +1 -1
  27. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/gate/changed.mjs +55 -10
  28. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/init/templates/SKILL.md +1 -1
  29. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/mcp/tools.mjs +1 -1
  30. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/gate.test.mjs +52 -2
  31. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/release-sync.test.mjs +74 -0
  32. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/testguard.claims.json +135 -1
  33. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/testguard.cost-budget.json +19 -14
  34. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.agents/skills/testguard/SKILL.md +0 -0
  35. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.codex/hooks.json +0 -0
  36. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.docguard.json +0 -0
  37. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.docguardignore +0 -0
  38. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.gitattributes +0 -0
  39. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  40. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  41. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  42. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.github/dependabot.yml +0 -0
  43. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.github/scripts/approve-held-runs.sh +0 -0
  44. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.github/scripts/build-one-pager.mjs +0 -0
  45. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.github/scripts/check-cost-budget.mjs +0 -0
  46. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.github/scripts/check-python-fixture.mjs +0 -0
  47. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.github/scripts/install-smoke.mjs +0 -0
  48. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.github/scripts/sync-release-version.mjs +0 -0
  49. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.github/workflows/auto-merge.yml +0 -0
  50. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.github/workflows/ci.yml +0 -0
  51. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.github/workflows/supply-chain.yml +0 -0
  52. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.gitignore +0 -0
  53. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.npmignore +0 -0
  54. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.npmrc +0 -0
  55. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.pre-commit-config.yaml +0 -0
  56. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/.pre-commit-hooks.yaml +0 -0
  57. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/CLAUDE.md +0 -0
  58. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/CODE_OF_CONDUCT.md +0 -0
  59. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/DRIFT-LOG.md +0 -0
  60. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/LICENSE +0 -0
  61. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/PRIVACY.md +0 -0
  62. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/SUPPORT.md +0 -0
  63. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/bench/README.md +0 -0
  64. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/cli/testguard.mjs +0 -0
  65. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/docs-canonical/DATA-MODEL.md +0 -0
  66. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/docs-canonical/REQUIREMENTS.md +0 -0
  67. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer/.gitignore +0 -0
  68. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer/README.md +0 -0
  69. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer/expected.json +0 -0
  70. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer/package.json +0 -0
  71. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer/src/export.mjs +0 -0
  72. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer/src/redact.mjs +0 -0
  73. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
  74. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer/test/flaky.test.mjs +0 -0
  75. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer/test/redact.test.mjs +0 -0
  76. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer/testguard.claims.json +0 -0
  77. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer/vitest.config.mjs +0 -0
  78. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-jest/.gitignore +0 -0
  79. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-jest/README.md +0 -0
  80. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-jest/expected.json +0 -0
  81. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-jest/jest.config.js +0 -0
  82. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-jest/package.json +0 -0
  83. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-jest/src/export.js +0 -0
  84. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-jest/src/redact.js +0 -0
  85. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
  86. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
  87. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-jest/test/redact.test.js +0 -0
  88. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-jest/testguard.claims.json +0 -0
  89. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-playwright/.gitignore +0 -0
  90. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-playwright/README.md +0 -0
  91. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
  92. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
  93. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-playwright/expected.json +0 -0
  94. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-playwright/package-lock.json +0 -0
  95. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-playwright/package.json +0 -0
  96. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
  97. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
  98. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
  99. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-playwright/test-results/.last-run.json +0 -0
  100. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
  101. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
  102. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-python/.gitignore +0 -0
  103. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-python/README.md +0 -0
  104. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-python/demo/__init__.py +0 -0
  105. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-python/demo/export.py +0 -0
  106. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-python/demo/orphan.py +0 -0
  107. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-python/demo/pipeline.py +0 -0
  108. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-python/demo/redact.py +0 -0
  109. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-python/expected.json +0 -0
  110. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-python/testguard.claims.json +0 -0
  111. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
  112. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
  113. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-python/tests/test_patched.py +0 -0
  114. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/fixtures/known-answer-python/tests/test_redact.py +0 -0
  115. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/README.md +0 -0
  116. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/baseline.json +0 -0
  117. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/brief.json +0 -0
  118. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/calibration-websec.json +0 -0
  119. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/calibration.json +0 -0
  120. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/claims-methods.json +0 -0
  121. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/claims.json +0 -0
  122. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
  123. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/evidence-negative-control.json +0 -0
  124. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/evidence-provisional.json +0 -0
  125. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/evidence-python.json +0 -0
  126. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/evidence-scan-method.json +0 -0
  127. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/evidence.json +0 -0
  128. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/gate.json +0 -0
  129. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/ignore.json +0 -0
  130. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/replay.json +0 -0
  131. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/examples/status.json +0 -0
  132. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
  133. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/baseline.zero-count.json +0 -0
  134. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
  135. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
  136. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +0 -0
  137. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +0 -0
  138. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/calibration.ci-not-wilson.json +0 -0
  139. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +0 -0
  140. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/calibration.fallback-without-basis.json +0 -0
  141. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/calibration.p-not-positives-over-n.json +0 -0
  142. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
  143. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
  144. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/claims.absolute-path.json +0 -0
  145. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
  146. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
  147. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +0 -0
  148. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
  149. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/claims.noop-fault.json +0 -0
  150. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
  151. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
  152. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
  153. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
  154. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
  155. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
  156. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
  157. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
  158. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
  159. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
  160. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
  161. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
  162. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
  163. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
  164. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +0 -0
  165. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
  166. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
  167. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
  168. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
  169. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
  170. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
  171. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
  172. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
  173. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
  174. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
  175. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
  176. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
  177. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
  178. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
  179. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
  180. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
  181. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/ignore.short-reason.json +0 -0
  182. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
  183. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
  184. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/status.claim-without-file.json +0 -0
  185. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
  186. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/status.invalid-anchor-hidden.json +0 -0
  187. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/status.partial-scope-counts-disagree.json +0 -0
  188. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/status.surface-counts-disagree.json +0 -0
  189. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
  190. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
  191. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/conformance/schemas.test.mjs +0 -0
  192. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/lib/fingerprint.mjs +0 -0
  193. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/lib/validate.mjs +0 -0
  194. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/lib/wilson.mjs +0 -0
  195. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/schemas/baseline.schema.json +0 -0
  196. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/schemas/brief.schema.json +0 -0
  197. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/schemas/calibration.schema.json +0 -0
  198. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/schemas/claims.schema.json +0 -0
  199. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/schemas/common.schema.json +0 -0
  200. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/schemas/evidence.schema.json +0 -0
  201. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/schemas/gate.schema.json +0 -0
  202. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/schemas/ignore.schema.json +0 -0
  203. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/schemas/replay.schema.json +0 -0
  204. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/spec/schemas/status.schema.json +0 -0
  205. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/admit/admit.mjs +0 -0
  206. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/baseline/baseline.mjs +0 -0
  207. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/brief/brief.mjs +0 -0
  208. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/claims/anchors.mjs +0 -0
  209. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/claims/annotations.mjs +0 -0
  210. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/claims/load.mjs +0 -0
  211. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/claims/removed.mjs +0 -0
  212. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/cli.mjs +0 -0
  213. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/commands/admit.mjs +0 -0
  214. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/commands/baseline.mjs +0 -0
  215. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/commands/claims.mjs +0 -0
  216. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/commands/init.mjs +0 -0
  217. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/commands/mcp.mjs +0 -0
  218. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/commands/replay.mjs +0 -0
  219. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/commands/scaffold.mjs +0 -0
  220. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/evidence/writer.mjs +0 -0
  221. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/git.mjs +0 -0
  222. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/init/init.mjs +0 -0
  223. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/mcp/server.mjs +0 -0
  224. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/attribution.mjs +0 -0
  225. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/classify.mjs +0 -0
  226. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/contention.mjs +0 -0
  227. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/cost.mjs +0 -0
  228. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/discover.mjs +0 -0
  229. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/independence.mjs +0 -0
  230. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/inject.mjs +0 -0
  231. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/mocks.mjs +0 -0
  232. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/probe.mjs +0 -0
  233. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/progress.mjs +0 -0
  234. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/pyimports.mjs +0 -0
  235. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/rank.mjs +0 -0
  236. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/runners/index.mjs +0 -0
  237. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/runners/jest.mjs +0 -0
  238. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/runners/playwright.mjs +0 -0
  239. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
  240. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/runners/python/_testguard_report.py +0 -0
  241. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
  242. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/runners/python.mjs +0 -0
  243. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/runners/shared.mjs +0 -0
  244. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/runners/vitest.mjs +0 -0
  245. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/probe/worktree.mjs +0 -0
  246. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/render.mjs +0 -0
  247. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/replay/label.mjs +0 -0
  248. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/replay/replay.mjs +0 -0
  249. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/scaffold/producers.mjs +0 -0
  250. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/scaffold/producers.python.mjs +0 -0
  251. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/scaffold/scaffold.mjs +0 -0
  252. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/status/status.mjs +0 -0
  253. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/status/surface.mjs +0 -0
  254. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/util/glob.mjs +0 -0
  255. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/src/util/hash.mjs +0 -0
  256. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/admit.test.mjs +0 -0
  257. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/annotations.test.mjs +0 -0
  258. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/attribution.test.mjs +0 -0
  259. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/baseline.test.mjs +0 -0
  260. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/brief.test.mjs +0 -0
  261. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/calibration.test.mjs +0 -0
  262. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/claim-selection.test.mjs +0 -0
  263. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/claims-anchors.test.mjs +0 -0
  264. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/claims-since.test.mjs +0 -0
  265. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/classify.test.mjs +0 -0
  266. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/contention.test.mjs +0 -0
  267. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/cost.test.mjs +0 -0
  268. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/discover.test.mjs +0 -0
  269. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/gitlab-template.test.mjs +0 -0
  270. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/glob.test.mjs +0 -0
  271. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/independence.test.mjs +0 -0
  272. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/init.test.mjs +0 -0
  273. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/inject.test.mjs +0 -0
  274. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/label.test.mjs +0 -0
  275. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/mcp.test.mjs +0 -0
  276. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/mocks.test.mjs +0 -0
  277. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/negative-control.test.mjs +0 -0
  278. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/one-pager.test.mjs +0 -0
  279. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/probe-error.test.mjs +0 -0
  280. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/probe-preconditions.test.mjs +0 -0
  281. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/probe.fixture.test.mjs +0 -0
  282. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/probe.jest.test.mjs +0 -0
  283. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/probe.playwright.test.mjs +0 -0
  284. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/probe.python.test.mjs +0 -0
  285. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/progress.test.mjs +0 -0
  286. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/pyimports.test.mjs +0 -0
  287. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/rank-aliases.test.mjs +0 -0
  288. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/release-approval.test.mjs +0 -0
  289. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/replay-verdict.test.mjs +0 -0
  290. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/replay.test.mjs +0 -0
  291. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/runner-argv.test.mjs +0 -0
  292. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/runner-command.test.mjs +0 -0
  293. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/runner-playwright.test.mjs +0 -0
  294. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/runner-python.test.mjs +0 -0
  295. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/runner-vitest.test.mjs +0 -0
  296. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/samples/playwright-report.json +0 -0
  297. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/samples/python-report-collect-error.json +0 -0
  298. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/samples/python-report.json +0 -0
  299. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/scaffold.python.test.mjs +0 -0
  300. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/scaffold.test.mjs +0 -0
  301. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/snapshot.test.mjs +0 -0
  302. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/status.test.mjs +0 -0
  303. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/surface.test.mjs +0 -0
  304. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/vitest-config.test.mjs +0 -0
  305. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/worktree.test.mjs +0 -0
  306. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/test/writer.test.mjs +0 -0
  307. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/testguard.ignore.json +0 -0
  308. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/testguard_cli/__init__.py +0 -0
  309. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/testguard_cli/wrapper.py +0 -0
  310. {testguard_cli-0.10.1 → testguard_cli-0.10.3}/vitest.config.mjs +0 -0
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ fail() {
6
+ echo "::error::$*" >&2
7
+ exit 1
8
+ }
9
+
10
+ VERSION="${1:-}"
11
+ [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || fail "usage: publish-homebrew-tap.sh <stable-semver>"
12
+
13
+ SOURCE_FORMULA="${HOMEBREW_TAP_SOURCE:-packaging/homebrew/testguard.rb}"
14
+ TAP_REMOTE="${HOMEBREW_TAP_REMOTE:-git@github.com:raccioly/homebrew-tap.git}"
15
+ [[ -f "$SOURCE_FORMULA" ]] || fail "source formula not found: $SOURCE_FORMULA"
16
+
17
+ FORMULA_VERSION=$(sed -nE 's#^[[:space:]]*url ".*testguard-cli-([0-9]+\.[0-9]+\.[0-9]+)\.tgz"#\1#p' "$SOURCE_FORMULA")
18
+ FORMULA_SHA=$(sed -nE 's/^[[:space:]]*sha256 "([0-9a-f]+)"/\1/p' "$SOURCE_FORMULA")
19
+ [[ "$FORMULA_VERSION" = "$VERSION" ]] || fail "formula version $FORMULA_VERSION does not match release $VERSION"
20
+ [[ "$FORMULA_SHA" =~ ^[0-9a-f]{64}$ ]] || fail "formula sha256 is not 64 lowercase hex characters"
21
+
22
+ TEMP_ROOT="${RUNNER_TEMP:-${TMPDIR:-/tmp}}"
23
+ [[ -d "$TEMP_ROOT" ]] || fail "temporary root does not exist: $TEMP_ROOT"
24
+ WORK_DIR=$(mktemp -d "$TEMP_ROOT/testguard-homebrew-tap.XXXXXX")
25
+
26
+ cleanup() {
27
+ case "$WORK_DIR" in
28
+ "$TEMP_ROOT"/testguard-homebrew-tap.*) rm -rf -- "$WORK_DIR" ;;
29
+ *) echo "::warning::refusing to remove unexpected temporary path: $WORK_DIR" >&2 ;;
30
+ esac
31
+ }
32
+ trap cleanup EXIT
33
+
34
+ if [[ "$TAP_REMOTE" = git@github.com:* ]]; then
35
+ [[ -n "${HOMEBREW_TAP_DEPLOY_KEY:-}" ]] || fail "HOMEBREW_TAP_DEPLOY_KEY is required for the GitHub tap"
36
+ command -v gh >/dev/null || fail "gh is required to verify GitHub's SSH host keys"
37
+ KEY_FILE="$WORK_DIR/deploy_key"
38
+ KNOWN_HOSTS="$WORK_DIR/known_hosts"
39
+ umask 077
40
+ printf '%s\n' "$HOMEBREW_TAP_DEPLOY_KEY" > "$KEY_FILE"
41
+ gh api meta --jq '.ssh_keys[] | "github.com " + .' > "$KNOWN_HOSTS"
42
+ [[ -s "$KNOWN_HOSTS" ]] || fail "GitHub API returned no SSH host keys"
43
+ export GIT_SSH_COMMAND="ssh -i \"$KEY_FILE\" -o IdentitiesOnly=yes -o UserKnownHostsFile=\"$KNOWN_HOSTS\" -o StrictHostKeyChecking=yes"
44
+ fi
45
+
46
+ TAP_DIR="$WORK_DIR/tap"
47
+ git clone --quiet --depth 1 "$TAP_REMOTE" "$TAP_DIR"
48
+ TAP_FORMULA="$TAP_DIR/Formula/testguard.rb"
49
+ [[ -f "$TAP_FORMULA" ]] || fail "tap is missing Formula/testguard.rb"
50
+
51
+ if cmp -s "$SOURCE_FORMULA" "$TAP_FORMULA"; then
52
+ echo "✅ raccioly/tap already carries TestGuard v$VERSION ($FORMULA_SHA)"
53
+ exit 0
54
+ fi
55
+
56
+ install -m 0644 "$SOURCE_FORMULA" "$TAP_FORMULA"
57
+ ruby -c "$TAP_FORMULA" >/dev/null
58
+ git -C "$TAP_DIR" diff --check
59
+ git -C "$TAP_DIR" config user.name "github-actions[bot]"
60
+ git -C "$TAP_DIR" config user.email "github-actions[bot]@users.noreply.github.com"
61
+ git -C "$TAP_DIR" add Formula/testguard.rb
62
+ git -C "$TAP_DIR" commit --quiet -m "testguard $VERSION" -m "Published from raccioly/testguard's release workflow."
63
+ git -C "$TAP_DIR" push --quiet origin HEAD:main
64
+ git -C "$TAP_DIR" fetch --quiet origin main
65
+ cmp "$SOURCE_FORMULA" <(git -C "$TAP_DIR" show origin/main:Formula/testguard.rb) \
66
+ || fail "live tap does not match the validated source formula after push"
67
+
68
+ echo "✅ published TestGuard v$VERSION to raccioly/tap ($FORMULA_SHA)"
@@ -1,8 +1,9 @@
1
- name: Auto Release — Tag, GitHub Release, npm, PyPI
1
+ name: Auto Release — GitHub, npm, PyPI, Homebrew
2
2
 
3
3
  # TAG-DRIVEN and idempotent: a version needs releasing iff no tag v<version>
4
- # exists. Re-runs never double-publish. No long-lived secrets anywhere —
5
- # npm and PyPI both authenticate with OIDC Trusted Publishing.
4
+ # exists. Re-runs never double-publish. npm and PyPI authenticate with OIDC
5
+ # Trusted Publishing. Homebrew uses one long-lived SSH deploy key scoped only
6
+ # to the distribution tap; it cannot access this repository or any other one.
6
7
  #
7
8
  # ONE-TIME SETUP (maintainer, cannot be automated):
8
9
  # npm: the first `npm publish` of testguard-cli is manual (Trusted Publishing
@@ -12,6 +13,9 @@ name: Auto Release — Tag, GitHub Release, npm, PyPI
12
13
  # PyPI: pypi.org → Your account → Publishing → add a PENDING publisher for
13
14
  # project testguard-cli: owner raccioly, repo testguard, workflow
14
15
  # release.yml, environment pypi. The first release creates the project.
16
+ # Brew: add a write deploy key named testguard-release-homebrew-tap to
17
+ # raccioly/homebrew-tap and store its private half here as the Actions
18
+ # secret HOMEBREW_TAP_DEPLOY_KEY.
15
19
 
16
20
  on:
17
21
  workflow_dispatch: # manual recovery
@@ -231,15 +235,17 @@ jobs:
231
235
  # for byte from `npm pack` — after npm serves it (a publish is `PUT 202`;
232
236
  # the file can take minutes to appear, so the script polls, bounded).
233
237
  #
234
- # It cannot commit to main: branch protection requires the three CI legs
235
- # on every commit and github-actions[bot] is no admin. So the one-line
236
- # change lands as a bot PR with native auto-merge armed, and this job
237
- # dispatches ci.yml on the branch itself — a PR opened with GITHUB_TOKEN
238
- # gets no pull_request run, but workflow_dispatch is the documented
239
- # exception. The PR's own runs may still appear held for first-time-
240
- # contributor approval, so this job clears those holds before returning.
241
- # That also means the merge triggers nothing here; the hourly sweep
242
- # observes the merged formula and finds brew_missing=false.
238
+ # The validated formula is also copied to the live raccioly/homebrew-tap
239
+ # repository with a write deploy key scoped only to that tap. The source
240
+ # copy cannot commit to main directly: branch protection requires the
241
+ # three CI legs on every commit and github-actions[bot] is no admin. So
242
+ # that one-line change lands as a bot PR with native auto-merge armed, and
243
+ # this job dispatches ci.yml on the branch itself — a PR opened with
244
+ # GITHUB_TOKEN gets no pull_request run, but workflow_dispatch is the
245
+ # documented exception. The PR's own runs may still appear held for
246
+ # first-time-contributor approval, so this job clears those holds before
247
+ # returning. That also means the merge triggers nothing here; the hourly
248
+ # sweep observes the merged source formula and finds brew_missing=false.
243
249
  #
244
250
  # Idempotent: an existing branch is reused, an existing open PR is left
245
251
  # alone (CI re-dispatched only if its head has no check runs), a closed
@@ -265,9 +271,10 @@ jobs:
265
271
  - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
266
272
  with:
267
273
  node-version: 24
268
- - name: Set sha256 from the published tarball; land it as an auto-merging PR
274
+ - name: Set sha256, publish the live tap, and land the source formula PR
269
275
  env:
270
276
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
277
+ HOMEBREW_TAP_DEPLOY_KEY: ${{ secrets.HOMEBREW_TAP_DEPLOY_KEY }}
271
278
  run: |
272
279
  set -euo pipefail
273
280
  VERSION="${{ needs.detect-version.outputs.version }}"
@@ -290,6 +297,11 @@ jobs:
290
297
  # other surface drifted. Nothing is pushed unless this passes.
291
298
  node .github/scripts/sync-release-version.mjs --check --online
292
299
 
300
+ # Homebrew users install from raccioly/homebrew-tap, not this staged
301
+ # copy. Publish the exact formula that passed the online check. The
302
+ # helper is idempotent and fails closed if the tap cannot be updated.
303
+ .github/scripts/publish-homebrew-tap.sh "$VERSION"
304
+
293
305
  if git diff --quiet -- packaging/homebrew/testguard.rb; then
294
306
  echo "✅ $BRANCH already carries the published sha256"
295
307
  else
@@ -148,7 +148,7 @@ jobs:
148
148
  git add -A
149
149
  git commit -m "release: v${NEW_VERSION} — automated weekly batch"
150
150
  git push origin "$BRANCH"
151
- printf 'Automated weekly release. Version bump + CHANGELOG entry + synced version surfaces. Merging tags and publishes to npm and PyPI via release.yml.\n\nTwo things a GITHUB_TOKEN PR needs done for it, both handled by this workflow: it raises no `pull_request` event, so ci.yml was dispatched on `%s` itself; and the `pull_request` runs it does create are held at `action_required` (github-actions[bot] is permanently a first-time contributor), so those were approved too. Auto-merge is armed and completes once the three Node legs pass.\n\nIf it sits anyway: `gh workflow run ci.yml --ref %s` for missing checks, or check for runs still awaiting approval on the head sha.\n' "$BRANCH" "$BRANCH" > /tmp/pr-body.md
151
+ printf 'Automated weekly release. Version bump + CHANGELOG entry + synced version surfaces. Merging tags and publishes to GitHub, npm, PyPI, and Homebrew via release.yml.\n\nTwo things a GITHUB_TOKEN PR needs done for it, both handled by this workflow: it raises no `pull_request` event, so ci.yml was dispatched on `%s` itself; and the `pull_request` runs it does create are held at `action_required` (github-actions[bot] is permanently a first-time contributor), so those were approved too. Auto-merge is armed and completes once the three Node legs pass.\n\nIf it sits anyway: `gh workflow run ci.yml --ref %s` for missing checks, or check for runs still awaiting approval on the head sha.\n' "$BRANCH" "$BRANCH" > /tmp/pr-body.md
152
152
  # A bare `gh pr create` failure here reads as a GraphQL error with no
153
153
  # hint of the cause, which is how v0.8.1 lost its release PR after
154
154
  # everything else in the release had succeeded.
@@ -1,6 +1,6 @@
1
1
  # AI Agent Instructions — TestGuard
2
2
 
3
- <!-- docguard:last-reviewed 2026-09-18 -->
3
+ <!-- docguard:last-reviewed 2026-09-19 -->
4
4
 
5
5
  Read this before changing anything. It is short on purpose.
6
6
 
@@ -63,7 +63,9 @@ node cli/testguard.mjs scaffold src/probe/classify.mjs --json # what the produ
63
63
 
64
64
  Dependabot minor/patch PRs and automated release PRs auto-merge when the exact
65
65
  CI run is green on all three Node legs. Majors and anything else wait for a
66
- human. Release publishing is OIDC-only; there are no tokens in this repo.
66
+ human. npm and PyPI publishing is OIDC-only; Homebrew uses a dedicated SSH
67
+ deploy key scoped only to `raccioly/homebrew-tap`. There are no personal or
68
+ registry tokens in this repo.
67
69
 
68
70
  <!-- testguard:begin -->
69
71
  ## TestGuard
@@ -7,6 +7,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.10.3] - 2026-09-20
11
+
12
+ Automated weekly release — everything merged since `v0.10.2`.
13
+
14
+ ### Changed
15
+
16
+ - fix(gate): infer safe local change base (#145)
17
+ - chore(homebrew): sha256 for v0.10.2 (#144)
18
+
19
+
20
+ ### Fixed
21
+
22
+ - Bare `testguard gate` now discovers an ordinary clone's remote default
23
+ branch and reports how it chose the comparison. It still fails closed on
24
+ ambiguous Git state: the default branch is not compared to itself, and a
25
+ feature branch's same-name tracking ref is never accepted as its base.
26
+
27
+ ## [0.10.2] - 2026-09-20
28
+
29
+ Automated weekly release — everything merged since `v0.10.1`.
30
+
31
+ ### Changed
32
+
33
+ - fix(release): publish the live Homebrew tap (#142)
34
+ - chore(homebrew): sha256 for v0.10.1 (#141)
35
+
36
+
37
+ ### Fixed
38
+
39
+ - Releases now publish the registry-verified Homebrew formula to the live
40
+ `raccioly/tap` repository with a dedicated, tap-scoped deploy key. The
41
+ release fails closed if that copy cannot be made, instead of updating only
42
+ the staging formula in this repository while users remain on an old version.
43
+
44
+ ### Changed
45
+
46
+ - The self-probe cost budget is 1440 s, from 1230. The cold v0.10.1 release
47
+ measured 1210 s before the four-fault live-tap claim added about 53 s; 1440
48
+ restores the established ~14% CI headroom. The 100 s per-claim ceiling is
49
+ unchanged, so an individually expensive claim still fails the gate.
50
+
10
51
  ## [0.10.1] - 2026-09-19
11
52
 
12
53
  Automated weekly release — everything merged since `v0.10.0`.
@@ -79,12 +79,16 @@ on the fault.
79
79
  `package.json` is the single source of truth for the version;
80
80
  `.github/scripts/sync-release-version.mjs` propagates it. A weekly workflow
81
81
  opens a release PR; merging it tags and publishes to npm and PyPI via OIDC
82
- Trusted Publishing. There are no tokens to rotate.
82
+ Trusted Publishing. There are no npm or PyPI tokens to rotate. Homebrew uses a
83
+ dedicated SSH deploy key that can write only to `raccioly/homebrew-tap`; its
84
+ private half is stored as the `HOMEBREW_TAP_DEPLOY_KEY` Actions secret.
83
85
 
84
86
  The Homebrew formula's `sha256` is the one surface that cannot be known before
85
87
  publishing: `release.yml` sets it after `npm publish`, from the registry
86
- tarball (`sync-release-version.mjs --sha256`), and lands it as a bot PR with
87
- auto-merge armed — `main` is protected, so no job commits to it directly.
88
+ tarball (`sync-release-version.mjs --sha256`), copies that verified formula
89
+ byte-for-byte to the live tap, and lands the source copy as a bot PR with
90
+ auto-merge armed — this repository's `main` is protected, so no job commits to
91
+ it directly.
88
92
  `--check` (offline) refuses a placeholder hash; `--check --online` compares
89
93
  against the tarball and is what decides, on every hourly sweep, whether the
90
94
  formula is still owed.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: testguard-cli
3
- Version: 0.10.1
3
+ Version: 0.10.3
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.1` — see [`action.yml`](./action.yml) |
154
+ | GitHub Action | `uses: raccioly/testguard@v0.10.3` — 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.1/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.3/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.1
508
+ - uses: raccioly/testguard@v0.10.3
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.1/packaging/gitlab/testguard.gitlab-ci.yml'
513
+ - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.3/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.1` — see [`action.yml`](./action.yml) |
130
+ | GitHub Action | `uses: raccioly/testguard@v0.10.3` — 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.1/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.3/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.1
484
+ - uses: raccioly/testguard@v0.10.3
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.1/packaging/gitlab/testguard.gitlab-ci.yml'
489
+ - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.3/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
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  | Version | Supported |
6
6
  |---------|-----------|
7
- | 0.1.x | ✅ current |
7
+ | 0.10.x | ✅ current |
8
8
 
9
9
  ## Reporting a vulnerability
10
10
 
@@ -27,9 +27,11 @@ TestGuard is a local CLI with a deliberately small surface:
27
27
  scratch git worktrees under the system temp directory, which are removed
28
28
  after each run. With `--in-place`, the mutated files are restored — on
29
29
  success, on failure, and on `SIGINT`/`SIGTERM`.
30
- - **Publishing uses OIDC Trusted Publishing** to npm and PyPI. The release
31
- pipeline holds no long-lived tokens; npm provenance attestations are
32
- generated automatically.
30
+ - **Publishing uses OIDC Trusted Publishing** to npm and PyPI, so the release
31
+ pipeline holds no registry tokens and npm provenance attestations are
32
+ generated automatically. Homebrew publishing uses a dedicated SSH deploy
33
+ key scoped only to `raccioly/homebrew-tap`; it cannot access this repository
34
+ or any other repository.
33
35
 
34
36
  ## The claims file is code
35
37
 
@@ -46,7 +46,7 @@ inputs:
46
46
  version:
47
47
  description: 'testguard-cli version to run'
48
48
  required: false
49
- default: '0.10.1'
49
+ default: '0.10.3'
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.1 · MIT</div>
147
+ <div class="meta">Technical Brief · v0.10.3 · MIT</div>
148
148
  </div>
149
149
 
150
150
  <h1>Your tests pass. That is not the same as your tests working.</h1>
@@ -85,7 +85,7 @@ other tools, and it must remain portable out of this repository.
85
85
  | Supported runners | vitest, jest (project runners); Playwright (per file) | They share a JSON report shape; Playwright is selected per file, not per project |
86
86
  | Schemas | JSON Schema 2020-12 | Readable without the tool; portable to other languages |
87
87
  | Distribution | npm, PyPI, Homebrew, GitHub Action, pre-commit, GitLab component | Meet teams in the pipeline they already run |
88
- | Publishing | OIDC Trusted Publishing | No long-lived tokens exist to leak or rotate |
88
+ | 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 |
89
89
 
90
90
  ## Configuration Files
91
91
 
@@ -101,7 +101,7 @@ appears here but not on disk, or on disk but not here, is drift.
101
101
  | `.pre-commit-hooks.yaml` | Hook definitions **we publish for consumers** of TestGuard — a different file for a different audience | consumers' `.pre-commit-config.yaml` |
102
102
  | `action.yml` | The GitHub Action surface; `version` input synced from `package.json` | consumers' workflows |
103
103
  | `packaging/gitlab/testguard.gitlab-ci.yml` | The GitLab component; tag and `TESTGUARD_VERSION` synced | consumers' pipelines |
104
- | `packaging/homebrew/testguard.rb` | The Homebrew formula; tarball URL synced | `raccioly/homebrew-tap` |
104
+ | `packaging/homebrew/testguard.rb` | The staged Homebrew formula; tarball URL and registry-derived hash synced, then copied byte-for-byte | `raccioly/homebrew-tap` |
105
105
  | `.docguard.json`, `.docguardignore` | Which canonical documents exist and which validators run | DocGuard, in CI and pre-commit |
106
106
  | `.npmrc`, `.npmignore`, `.gitattributes` | Packaging and line-ending hygiene | npm, git |
107
107
 
@@ -115,7 +115,7 @@ and no license server.
115
115
  | `ajv` (npm, bundled) | Validate every document against the spec | none — local | Install-time only; the install smoke gate catches a missing runtime dep |
116
116
  | `git` (system binary) | Worktree isolation, diffs, snapshots, patch ids | none — local | Precondition failure with a stated reason, exit 2 |
117
117
  | The project's test runner | Runs the defenders | none — local | Precondition failure, exit 2; never a verdict about a claim |
118
- | npm / PyPI registries | Distribution only | build time | A publish fails; the tool itself never calls them at run time |
118
+ | npm / PyPI registries and Homebrew tap | Distribution only | build time | A publish fails; the tool itself never calls them at run time |
119
119
 
120
120
  ## Infrastructure (IaC)
121
121
 
@@ -133,8 +133,8 @@ to every other surface and `--check` fails the build on drift.
133
133
  |---|---|---|
134
134
  | Pull request | `ci.yml` | Suite on Node 20/22/24, syntax check of every source, install-from-tarball smoke, Python wrapper import, fixture oracles for vitest and jest, Playwright job, change gate, removed-claim check, anchor/replacement preflight before self-probe |
135
135
  | Release gate | `ci.yml` | TestGuard probes its own claims; every fault must be killed |
136
- | Merge to main | `release.yml` | A version with no tag is tagged, released, and published to npm and PyPI over OIDC |
137
- | Recovery | `release.yml` | Hourly sweep; each registry is gated on its own state, so a partial publish is fixed by re-running |
136
+ | Merge to main | `release.yml` | A version with no tag is tagged, released, and published to npm and PyPI over OIDC; the verified formula is pushed to the live Homebrew tap with its repository-scoped deploy key |
137
+ | Recovery | `release.yml` | Hourly sweep; each registry and the formula hash are gated on their own state, so a partial publish is fixed by re-running |
138
138
 
139
139
  ## Diagrams
140
140
 
@@ -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 |
@@ -2,9 +2,9 @@
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-19 -->
6
6
  <!-- docguard:owner @raccioly -->
7
- <!-- docguard:quality negation-load off — a security posture is a list of things that must not happen: no network, no secrets, no tokens, no telemetry. Stating them positively would weaken them. -->
7
+ <!-- docguard:quality negation-load off — a security posture is a list of things that must not happen: no runtime network, no CLI secrets, no registry tokens, no telemetry. Stating them positively would weaken them. -->
8
8
  <!-- docguard:quality passive-voice off — threat and control statements name what is protected, not who protects it; the actor is the system throughout. -->
9
9
 
10
10
  > Canonical. Code that contradicts this document is drift.
@@ -27,8 +27,14 @@ happens in CI, never on a developer machine:
27
27
  | npm | OIDC Trusted Publishing | none |
28
28
  | PyPI | OIDC Trusted Publishing (environment `pypi`) | none |
29
29
  | GitHub releases | `GITHUB_TOKEN`, scoped to the run | none stored |
30
+ | Homebrew tap | SSH deploy key scoped to `raccioly/homebrew-tap` | `HOMEBREW_TAP_DEPLOY_KEY` |
30
31
 
31
- There are no tokens in this repository to rotate or leak.
32
+ npm and PyPI have no registry tokens to rotate or leak. Homebrew publishing
33
+ uses one stored credential: a dedicated Ed25519 deploy key whose public half
34
+ has write access only to `raccioly/homebrew-tap` and whose private half is the
35
+ `HOMEBREW_TAP_DEPLOY_KEY` Actions secret in this repository. It is not a
36
+ personal key and cannot access another repository. Rotate it by replacing both
37
+ the tap deploy key and the Actions secret.
32
38
 
33
39
  ## Authorization
34
40
 
@@ -53,8 +59,11 @@ guarantees are about **what the tool is allowed to touch**:
53
59
 
54
60
  ## Secrets Management
55
61
 
56
- TestGuard neither reads nor stores secrets. It has no credential file, no
57
- keychain access and no environment variable holding a secret.
62
+ The TestGuard CLI neither reads nor stores secrets. It has no credential file,
63
+ no keychain access and no environment variable holding a secret. The release
64
+ workflow is the deliberate exception: its Homebrew step receives the
65
+ tap-scoped deploy key only while it copies a registry-verified formula to the
66
+ distribution repository. npm and PyPI publishing remain OIDC-only.
58
67
 
59
68
  Two consequences worth stating for reviewers:
60
69
 
@@ -21,7 +21,7 @@ the test that defends it, and CI fails if any injected fault survives.
21
21
  | Conformance | `spec/conformance/` | Every valid example validates; every must-reject document is rejected for the defect its filename names | ~1 s |
22
22
  | Anchor preflight | `test/claims-anchors.test.mjs` | Exact anchors still locate, supported replacements parse, the check stays below one second, and CI reaches it before self-probe | <1 s |
23
23
  | Fixture acceptance | `test/probe.fixture.test.mjs`, `test/probe.jest.test.mjs`, `test/probe.playwright.test.mjs` | The whole pipeline reproduces a known-answer oracle, one per runner | 10–50 s each |
24
- | Self-verification | `testguard.claims.json`, run by `npm run self:probe` | The tool's own invariants survive fault injection | **13.4 min** cold (measured 2026-09-18 at `cf5a083`: 96 faults, 576 defender runs, 804 s, `--serial`, idle machine); seconds when verdicts are reused, which is why CI restores the previous evidence from cache. Budgeted at 950 s in `testguard.cost-budget.json` and **gated** in CI — the figure above went 9.6 → 23.6 min across one merged change while `--cost` printed it into a log nobody read |
24
+ | Self-verification | `testguard.claims.json`, run by `npm run self:probe` | The tool's own invariants survive fault injection | **20.2 min** cold (measured by the v0.10.1 release job on 2026-09-19 at `70e9d61`: 154 faults, 924 defender runs, 1210 s); seconds when verdicts are reused, which is why PR CI restores previous evidence from cache. Budgeted at 1440 s in `testguard.cost-budget.json` for the additional four-fault live-tap claim and **gated** in CI — cost growth is reviewed instead of disappearing into a passing log |
25
25
  | Install smoke | `.github/scripts/install-smoke.mjs` | The **packed tarball** runs with production dependencies only | ~20 s |
26
26
  | Runtime budget | `ci.yml` | The suite has not silently started walking the wrong tree | gate, not a test |
27
27
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "testguard-cli",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "testguard-cli",
9
- "version": "0.10.1",
9
+ "version": "0.10.3",
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.1",
3
+ "version": "0.10.3",
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.1/packaging/gitlab/testguard.gitlab-ci.yml'
6
+ # - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.3/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.1"
32
+ default: "0.10.3"
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.1.tgz"
19
- sha256 "ebb9e4ac0370e0b2cbc52b3e5f24e8426020fd3b5ff9db6b2776ab2744874dab"
18
+ url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.10.3.tgz"
19
+ sha256 "073d225733ecf5ba3da9fecc5952236a3fa22c46cb5981def3f568d08e1ca044"
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.1"
7
+ version = "0.10.3"
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
  }