testguard-cli 0.8.0__tar.gz → 0.8.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. testguard_cli-0.8.1/.github/scripts/sync-release-version.mjs +195 -0
  2. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.github/workflows/auto-merge.yml +8 -1
  3. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.github/workflows/release.yml +130 -1
  4. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.github/workflows/scheduled-release.yml +38 -8
  5. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/CHANGELOG.md +69 -0
  6. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/CONTRIBUTING.md +8 -0
  7. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/PKG-INFO +5 -5
  8. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/README.md +4 -4
  9. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/action.yml +1 -1
  10. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/package-lock.json +2 -2
  11. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/package.json +1 -1
  12. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/packaging/gitlab/testguard.gitlab-ci.yml +2 -2
  13. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/packaging/homebrew/testguard.rb +7 -5
  14. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/pyproject.toml +1 -1
  15. testguard_cli-0.8.1/test/release-sync.test.mjs +318 -0
  16. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/testguard.claims.json +53 -1
  17. testguard_cli-0.8.0/.github/scripts/sync-release-version.mjs +0 -50
  18. testguard_cli-0.8.0/test/release-sync.test.mjs +0 -118
  19. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.agents/skills/testguard/SKILL.md +0 -0
  20. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.codex/hooks.json +0 -0
  21. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.docguard.json +0 -0
  22. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.docguardignore +0 -0
  23. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.gitattributes +0 -0
  24. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  25. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  26. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  27. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.github/dependabot.yml +0 -0
  28. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.github/scripts/build-one-pager.mjs +0 -0
  29. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.github/scripts/check-cost-budget.mjs +0 -0
  30. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.github/scripts/check-python-fixture.mjs +0 -0
  31. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.github/scripts/install-smoke.mjs +0 -0
  32. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.github/workflows/ci.yml +0 -0
  33. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.github/workflows/supply-chain.yml +0 -0
  34. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.gitignore +0 -0
  35. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.npmignore +0 -0
  36. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.npmrc +0 -0
  37. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.pre-commit-config.yaml +0 -0
  38. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/.pre-commit-hooks.yaml +0 -0
  39. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/AGENTS.md +0 -0
  40. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/CLAUDE.md +0 -0
  41. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/CODE_OF_CONDUCT.md +0 -0
  42. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/DRIFT-LOG.md +0 -0
  43. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/LICENSE +0 -0
  44. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/PRIVACY.md +0 -0
  45. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/SECURITY.md +0 -0
  46. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/SUPPORT.md +0 -0
  47. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/bench/README.md +0 -0
  48. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/cli/testguard.mjs +0 -0
  49. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/docs/testguard-explained.html +0 -0
  50. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/docs/testguard-explained.pdf +0 -0
  51. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/docs-canonical/ARCHITECTURE.md +0 -0
  52. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/docs-canonical/DATA-MODEL.md +0 -0
  53. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/docs-canonical/ENVIRONMENT.md +0 -0
  54. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/docs-canonical/REQUIREMENTS.md +0 -0
  55. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/docs-canonical/SECURITY.md +0 -0
  56. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/docs-canonical/TEST-SPEC.md +0 -0
  57. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer/.gitignore +0 -0
  58. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer/README.md +0 -0
  59. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer/expected.json +0 -0
  60. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer/package.json +0 -0
  61. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer/src/export.mjs +0 -0
  62. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer/src/redact.mjs +0 -0
  63. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
  64. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer/test/flaky.test.mjs +0 -0
  65. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer/test/redact.test.mjs +0 -0
  66. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer/testguard.claims.json +0 -0
  67. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer/vitest.config.mjs +0 -0
  68. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-jest/.gitignore +0 -0
  69. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-jest/README.md +0 -0
  70. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-jest/expected.json +0 -0
  71. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-jest/jest.config.js +0 -0
  72. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-jest/package.json +0 -0
  73. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-jest/src/export.js +0 -0
  74. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-jest/src/redact.js +0 -0
  75. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
  76. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
  77. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-jest/test/redact.test.js +0 -0
  78. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-jest/testguard.claims.json +0 -0
  79. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-playwright/.gitignore +0 -0
  80. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-playwright/README.md +0 -0
  81. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
  82. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
  83. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-playwright/expected.json +0 -0
  84. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-playwright/package-lock.json +0 -0
  85. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-playwright/package.json +0 -0
  86. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
  87. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
  88. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
  89. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-playwright/test-results/.last-run.json +0 -0
  90. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
  91. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
  92. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-python/.gitignore +0 -0
  93. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-python/README.md +0 -0
  94. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-python/demo/__init__.py +0 -0
  95. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-python/demo/export.py +0 -0
  96. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-python/demo/orphan.py +0 -0
  97. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-python/demo/pipeline.py +0 -0
  98. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-python/demo/redact.py +0 -0
  99. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-python/expected.json +0 -0
  100. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-python/testguard.claims.json +0 -0
  101. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
  102. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
  103. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-python/tests/test_patched.py +0 -0
  104. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/fixtures/known-answer-python/tests/test_redact.py +0 -0
  105. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/GATE-SEMANTICS.md +0 -0
  106. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/README.md +0 -0
  107. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/baseline.json +0 -0
  108. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/brief.json +0 -0
  109. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/calibration-websec.json +0 -0
  110. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/calibration.json +0 -0
  111. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/claims-methods.json +0 -0
  112. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/claims.json +0 -0
  113. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
  114. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/evidence-negative-control.json +0 -0
  115. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/evidence-provisional.json +0 -0
  116. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/evidence-python.json +0 -0
  117. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/evidence-scan-method.json +0 -0
  118. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/evidence.json +0 -0
  119. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/gate.json +0 -0
  120. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/ignore.json +0 -0
  121. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/replay.json +0 -0
  122. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/examples/status.json +0 -0
  123. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
  124. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/baseline.zero-count.json +0 -0
  125. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
  126. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
  127. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +0 -0
  128. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +0 -0
  129. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/calibration.ci-not-wilson.json +0 -0
  130. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +0 -0
  131. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/calibration.fallback-without-basis.json +0 -0
  132. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/calibration.p-not-positives-over-n.json +0 -0
  133. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
  134. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
  135. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/claims.absolute-path.json +0 -0
  136. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
  137. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
  138. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +0 -0
  139. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
  140. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/claims.noop-fault.json +0 -0
  141. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
  142. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
  143. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
  144. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
  145. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
  146. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
  147. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
  148. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
  149. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
  150. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
  151. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
  152. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
  153. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
  154. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
  155. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +0 -0
  156. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
  157. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
  158. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
  159. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
  160. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
  161. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
  162. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
  163. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
  164. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
  165. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
  166. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
  167. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
  168. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
  169. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
  170. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
  171. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
  172. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/ignore.short-reason.json +0 -0
  173. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
  174. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
  175. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/status.claim-without-file.json +0 -0
  176. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
  177. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
  178. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
  179. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/conformance/schemas.test.mjs +0 -0
  180. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/lib/fingerprint.mjs +0 -0
  181. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/lib/validate.mjs +0 -0
  182. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/lib/wilson.mjs +0 -0
  183. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/schemas/baseline.schema.json +0 -0
  184. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/schemas/brief.schema.json +0 -0
  185. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/schemas/calibration.schema.json +0 -0
  186. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/schemas/claims.schema.json +0 -0
  187. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/schemas/common.schema.json +0 -0
  188. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/schemas/evidence.schema.json +0 -0
  189. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/schemas/gate.schema.json +0 -0
  190. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/schemas/ignore.schema.json +0 -0
  191. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/schemas/replay.schema.json +0 -0
  192. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/spec/schemas/status.schema.json +0 -0
  193. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/admit/admit.mjs +0 -0
  194. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/baseline/baseline.mjs +0 -0
  195. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/brief/brief.mjs +0 -0
  196. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/claims/annotations.mjs +0 -0
  197. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/claims/load.mjs +0 -0
  198. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/claims/removed.mjs +0 -0
  199. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/cli.mjs +0 -0
  200. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/commands/admit.mjs +0 -0
  201. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/commands/baseline.mjs +0 -0
  202. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/commands/brief.mjs +0 -0
  203. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/commands/claims.mjs +0 -0
  204. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/commands/gate.mjs +0 -0
  205. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/commands/init.mjs +0 -0
  206. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/commands/mcp.mjs +0 -0
  207. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/commands/probe.mjs +0 -0
  208. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/commands/replay.mjs +0 -0
  209. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/commands/scaffold.mjs +0 -0
  210. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/commands/status.mjs +0 -0
  211. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/evidence/writer.mjs +0 -0
  212. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/gate/changed.mjs +0 -0
  213. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/git.mjs +0 -0
  214. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/init/init.mjs +0 -0
  215. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/init/templates/SKILL.md +0 -0
  216. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/mcp/server.mjs +0 -0
  217. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/mcp/tools.mjs +0 -0
  218. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/attribution.mjs +0 -0
  219. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/classify.mjs +0 -0
  220. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/contention.mjs +0 -0
  221. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/cost.mjs +0 -0
  222. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/discover.mjs +0 -0
  223. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/independence.mjs +0 -0
  224. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/inject.mjs +0 -0
  225. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/mocks.mjs +0 -0
  226. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/probe.mjs +0 -0
  227. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/progress.mjs +0 -0
  228. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/pyimports.mjs +0 -0
  229. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/rank.mjs +0 -0
  230. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/runners/index.mjs +0 -0
  231. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/runners/jest.mjs +0 -0
  232. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/runners/playwright.mjs +0 -0
  233. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
  234. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/runners/python/_testguard_report.py +0 -0
  235. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
  236. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/runners/python.mjs +0 -0
  237. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/runners/shared.mjs +0 -0
  238. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/runners/vitest.mjs +0 -0
  239. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/probe/worktree.mjs +0 -0
  240. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/render.mjs +0 -0
  241. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/replay/label.mjs +0 -0
  242. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/replay/replay.mjs +0 -0
  243. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/scaffold/producers.mjs +0 -0
  244. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/scaffold/producers.python.mjs +0 -0
  245. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/scaffold/scaffold.mjs +0 -0
  246. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/status/status.mjs +0 -0
  247. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/util/glob.mjs +0 -0
  248. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/src/util/hash.mjs +0 -0
  249. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/admit.test.mjs +0 -0
  250. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/annotations.test.mjs +0 -0
  251. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/attribution.test.mjs +0 -0
  252. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/baseline.test.mjs +0 -0
  253. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/brief.test.mjs +0 -0
  254. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/calibration.test.mjs +0 -0
  255. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/claims-since.test.mjs +0 -0
  256. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/classify.test.mjs +0 -0
  257. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/contention.test.mjs +0 -0
  258. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/cost.test.mjs +0 -0
  259. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/discover.test.mjs +0 -0
  260. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/gate.test.mjs +0 -0
  261. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/gitlab-template.test.mjs +0 -0
  262. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/glob.test.mjs +0 -0
  263. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/independence.test.mjs +0 -0
  264. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/init.test.mjs +0 -0
  265. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/inject.test.mjs +0 -0
  266. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/mcp.test.mjs +0 -0
  267. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/mocks.test.mjs +0 -0
  268. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/negative-control.test.mjs +0 -0
  269. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/probe-error.test.mjs +0 -0
  270. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/probe-preconditions.test.mjs +0 -0
  271. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/probe.fixture.test.mjs +0 -0
  272. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/probe.jest.test.mjs +0 -0
  273. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/probe.playwright.test.mjs +0 -0
  274. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/probe.python.test.mjs +0 -0
  275. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/progress.test.mjs +0 -0
  276. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/pyimports.test.mjs +0 -0
  277. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/rank-aliases.test.mjs +0 -0
  278. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/replay.test.mjs +0 -0
  279. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/runner-argv.test.mjs +0 -0
  280. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/runner-command.test.mjs +0 -0
  281. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/runner-playwright.test.mjs +0 -0
  282. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/runner-python.test.mjs +0 -0
  283. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/runner-vitest.test.mjs +0 -0
  284. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/samples/playwright-report.json +0 -0
  285. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/samples/python-report-collect-error.json +0 -0
  286. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/samples/python-report.json +0 -0
  287. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/scaffold.python.test.mjs +0 -0
  288. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/scaffold.test.mjs +0 -0
  289. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/snapshot.test.mjs +0 -0
  290. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/status.test.mjs +0 -0
  291. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/worktree.test.mjs +0 -0
  292. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/test/writer.test.mjs +0 -0
  293. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/testguard.cost-budget.json +0 -0
  294. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/testguard.ignore.json +0 -0
  295. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/testguard_cli/__init__.py +0 -0
  296. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/testguard_cli/wrapper.py +0 -0
  297. {testguard_cli-0.8.0 → testguard_cli-0.8.1}/vitest.config.mjs +0 -0
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * package.json is the single source of truth for the version. Every other
4
+ * surface that carries it is synced from there:
5
+ * - pyproject.toml `version = "x.y.z"`
6
+ * - action.yml default of the `version` input
7
+ * - packaging/homebrew/*.rb the tarball URL; the `sha256` is set by `--sha256`
8
+ * from the PUBLISHED tarball (release.yml, after npm)
9
+ * - packaging/gitlab/*.yml the include URL tag and the `version` input default
10
+ * - README.md the GitHub Action reference AND the GitLab include URL
11
+ *
12
+ * A file may carry more than one form, and each needs its own entry: README
13
+ * holds both the `raccioly/testguard@vX.Y.Z` action reference and the
14
+ * `testguard/vX.Y.Z/packaging` template URL. Only the first was listed, so
15
+ * v0.6.0 shipped with both README copies of the GitLab URL left at v0.5.0
16
+ * while `--check` reported "all version surfaces at 0.6.0" — a surface this
17
+ * script was never told to look at cannot drift in its eyes.
18
+ *
19
+ * The Homebrew `sha256` is the same lesson one step further: the URL was a
20
+ * surface, the hash beside it was a comment telling a human to run curl. It
21
+ * carried 385d69f9… unchanged across v0.6.0, v0.7.0 and v0.8.0 — a hash
22
+ * matching none of them. So the hash is now a surface too, with three modes:
23
+ *
24
+ * --check offline: every version surface at package.json's version,
25
+ * the formula's sha256 is 64 hex and not the known-stale
26
+ * placeholder. Used by release.yml's test job (before the
27
+ * tag, when the tarball cannot exist yet) and prepublishOnly.
28
+ * --check --online additionally downloads the tarball the formula names and
29
+ * requires its sha256 to match. Used by release.yml's
30
+ * detect-version (is the formula still owed?) and by the
31
+ * sha job as a post-condition before it opens the PR.
32
+ * --sha256 waits for the tarball to become downloadable (bounded by
33
+ * --wait), computes its sha256 and writes it into the
34
+ * formula — only when the value changes. Exit 1 on timeout
35
+ * or on a 200 that is not a gzip tarball: a hashed error
36
+ * page would poison the formula, and "never optimistic".
37
+ *
38
+ * The hash is always computed from the REGISTRY tarball — what Homebrew will
39
+ * download — never from a local `npm pack`: a provenance-signed publish can
40
+ * differ byte for byte from the pack output.
41
+ *
42
+ * --list-surfaces print every file this script may write, one per line. The
43
+ * release workflows allow-list exactly these (plus
44
+ * package.json, package-lock.json and CHANGELOG.md, which
45
+ * the bump writes directly), so a surface added above can
46
+ * never again be rejected as an "unexpected file in the
47
+ * release diff" — which is what stopped every weekly
48
+ * release after the GitLab template became a surface.
49
+ * --registry <url> registry base (default https://registry.npmjs.org); the
50
+ * tests point it at a local server so `npm test` is offline.
51
+ * --wait <s> how long --sha256 keeps polling (default 900).
52
+ * --interval <s> seconds between polls (default 15).
53
+ * --timeout <s> per-request timeout (default 60): a registry that accepts the
54
+ * connection and never answers is a failed attempt, not a hang.
55
+ */
56
+ import { createHash } from 'node:crypto';
57
+ import { readFileSync, writeFileSync } from 'node:fs';
58
+ import { resolve } from 'node:path';
59
+
60
+ const root = resolve(process.argv[1], '..', '..', '..');
61
+ const argv = process.argv.slice(2);
62
+ const flag = (name) => argv.includes(name);
63
+ const option = (name, fallback) => {
64
+ const i = argv.indexOf(name);
65
+ return i === -1 ? fallback : argv[i + 1];
66
+ };
67
+ const check = flag('--check');
68
+ const listSurfaces = flag('--list-surfaces');
69
+ const online = flag('--online');
70
+ const setSha = flag('--sha256');
71
+ const registry = (option('--registry', 'https://registry.npmjs.org')).replace(/\/+$/, '');
72
+ const waitSeconds = Number(option('--wait', '900'));
73
+ const intervalSeconds = Number(option('--interval', '15'));
74
+ const timeoutSeconds = Number(option('--timeout', '60'));
75
+ if (!Number.isFinite(waitSeconds) || waitSeconds < 0) throw new Error(`--wait must be a non-negative number of seconds, got ${option('--wait')}`);
76
+ if (!Number.isFinite(intervalSeconds) || intervalSeconds < 0) throw new Error(`--interval must be a non-negative number of seconds, got ${option('--interval')}`);
77
+ if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) throw new Error(`--timeout must be a positive number of seconds, got ${option('--timeout')}`);
78
+
79
+ const version = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8')).version;
80
+ if (!/^\d+\.\d+\.\d+$/.test(version)) throw new Error(`package.json version is not a stable semver: ${version}`);
81
+
82
+ const FORMULA = 'packaging/homebrew/testguard.rb';
83
+ const formulaPath = resolve(root, FORMULA);
84
+ /** The hash the formula carried, unchanged, from v0.6.0 through v0.8.0. It matches no published tarball. */
85
+ const KNOWN_STALE_SHA256 = '385d69f9d3c153b934d9c1cb6a2c754eb9b221b0a8c0ba8b805858384d2d4678';
86
+ const SHA_LINE = /^(\s*sha256 ")([^"]*)(")/m;
87
+ const TARBALL = `testguard-cli-${version}.tgz`;
88
+ const tarballUrl = `${registry}/testguard-cli/-/${TARBALL}`;
89
+
90
+ const surfaces = [
91
+ ['pyproject.toml', /^version = "[^"]+"/m, `version = "${version}"`],
92
+ ['action.yml', /(\n version:\n description: [^\n]*\n required: false\n default: ')[^']*(')/, `$1${version}$2`],
93
+ [FORMULA, /testguard-cli-\d+\.\d+\.\d+\.tgz/g, TARBALL],
94
+ ['README.md', /raccioly\/testguard@v\d+\.\d+\.\d+/g, `raccioly/testguard@v${version}`],
95
+ ['README.md', /testguard\/v\d+\.\d+\.\d+\/packaging/g, `testguard/v${version}/packaging`],
96
+ ['packaging/gitlab/testguard.gitlab-ci.yml', /testguard\/v\d+\.\d+\.\d+\/packaging/g, `testguard/v${version}/packaging`],
97
+ ['packaging/gitlab/testguard.gitlab-ci.yml', /(\n version:\n description: [^\n]*\n default: ")\d+\.\d+\.\d+(")/, `$1${version}$2`],
98
+ ];
99
+
100
+ if (listSurfaces) {
101
+ // Derived from the same table the writes use, so the two cannot disagree.
102
+ for (const rel of [...new Set(surfaces.map(([rel]) => rel))]) console.log(rel);
103
+ process.exit(0);
104
+ }
105
+
106
+ /** The formula's current `sha256 "…"` value, or a throw when the line is missing. */
107
+ function formulaSha256() {
108
+ const m = SHA_LINE.exec(readFileSync(formulaPath, 'utf8'));
109
+ if (!m) throw new Error(`${FORMULA}: sha256 line not found`);
110
+ return m[2];
111
+ }
112
+
113
+ /**
114
+ * One GET of the tarball. Returns `{ status, sha256 }`; `sha256` is set only
115
+ * for a 200 whose body starts with the gzip magic (1f 8b) — a registry error
116
+ * page with a 200 status is treated as "not a tarball", never hashed.
117
+ */
118
+ async function fetchTarball() {
119
+ const res = await fetch(tarballUrl, { redirect: 'follow', signal: AbortSignal.timeout(timeoutSeconds * 1000) });
120
+ if (res.status !== 200) return { status: res.status };
121
+ const body = Buffer.from(await res.arrayBuffer());
122
+ if (body.length < 2 || body[0] !== 0x1f || body[1] !== 0x8b) return { status: 200, notTarball: true, bytes: body.length };
123
+ return { status: 200, sha256: createHash('sha256').update(body).digest('hex'), bytes: body.length };
124
+ }
125
+
126
+ const sleep = (s) => new Promise((r) => setTimeout(r, s * 1000));
127
+
128
+ /**
129
+ * Polls until the tarball downloads as a gzip, or the wait runs out. Every
130
+ * non-200 and every network error counts as "not yet": the registry answers
131
+ * `PUT 202` to a publish and can take minutes to serve the file.
132
+ */
133
+ async function waitForTarball() {
134
+ const deadline = Date.now() + waitSeconds * 1000;
135
+ let attempt = 0;
136
+ for (;;) {
137
+ attempt++;
138
+ let result;
139
+ try {
140
+ result = await fetchTarball();
141
+ } catch (e) {
142
+ result = { status: 0, error: e.message };
143
+ }
144
+ if (result.sha256) return result;
145
+ const why = result.notTarball ? `200 but not a gzip tarball (${result.bytes} bytes)` : result.error ? `network error: ${result.error}` : `HTTP ${result.status}`;
146
+ if (Date.now() >= deadline) throw new Error(`${tarballUrl}: not downloadable after ${attempt} attempt(s) over ${waitSeconds}s — last: ${why}`);
147
+ console.error(`${TARBALL}: ${why}; retrying in ${intervalSeconds}s (attempt ${attempt})`);
148
+ await sleep(intervalSeconds);
149
+ }
150
+ }
151
+
152
+ if (setSha) {
153
+ const { sha256, bytes } = await waitForTarball();
154
+ const before = readFileSync(formulaPath, 'utf8');
155
+ const current = formulaSha256();
156
+ if (current === sha256) {
157
+ console.log(`${FORMULA}: sha256 unchanged (${sha256}, ${bytes} bytes)`);
158
+ } else {
159
+ writeFileSync(formulaPath, before.replace(SHA_LINE, `$1${sha256}$3`));
160
+ if (formulaSha256() !== sha256) throw new Error(`${FORMULA}: sha256 line did not take the new value`);
161
+ console.log(`${FORMULA}: sha256 ${current} -> ${sha256} (${bytes} bytes from ${tarballUrl})`);
162
+ }
163
+ process.exit(0);
164
+ }
165
+
166
+ let drift = 0;
167
+ for (const [rel, pattern, replacement] of surfaces) {
168
+ const path = resolve(root, rel);
169
+ const before = readFileSync(path, 'utf8');
170
+ if (!pattern.test(before)) throw new Error(`${rel}: version pattern not found`);
171
+ const after = before.replace(pattern, replacement);
172
+ if (after === before) continue;
173
+ drift++;
174
+ if (check) console.error(`${rel}: out of sync with package.json ${version}`);
175
+ else { writeFileSync(path, after); console.log(`${rel}: synced to ${version}`); }
176
+ }
177
+
178
+ if (check) {
179
+ const sha = formulaSha256();
180
+ if (!/^[0-9a-f]{64}$/.test(sha)) { drift++; console.error(`${FORMULA}: sha256 is not 64 hex characters: "${sha}"`); }
181
+ else if (sha === KNOWN_STALE_SHA256) { drift++; console.error(`${FORMULA}: sha256 is the known-stale placeholder ${sha.slice(0, 8)}… (matches no published tarball); run --sha256 after publishing`); }
182
+ if (online) {
183
+ let result;
184
+ try {
185
+ result = await fetchTarball();
186
+ } catch (e) {
187
+ result = { status: 0, error: e.message };
188
+ }
189
+ if (!result.sha256) { drift++; console.error(`${FORMULA}: tarball not downloadable — ${tarballUrl}: ${result.error ?? (result.notTarball ? '200 but not a gzip tarball' : `HTTP ${result.status}`)}`); }
190
+ else if (result.sha256 !== sha) { drift++; console.error(`${FORMULA}: sha256 ${sha} does not match the published tarball ${result.sha256} (${tarballUrl})`); }
191
+ else console.log(`${FORMULA}: sha256 matches ${tarballUrl}`);
192
+ }
193
+ }
194
+ if (check && drift) process.exit(1);
195
+ if (check) console.log(`all version surfaces at ${version}`);
@@ -46,7 +46,14 @@ jobs:
46
46
 
47
47
  const { data: files } = await github.rest.pulls.listFiles({ owner, repo, pull_number: num, per_page: 300 });
48
48
  const paths = files.map((f) => f.filename);
49
- const RELEASE_SURFACES = new Set(['package.json', 'package-lock.json', 'pyproject.toml', 'CHANGELOG.md', 'action.yml', 'packaging/homebrew/testguard.rb', 'README.md']);
49
+ // Deliberately spelled out, not derived: this is the gate that merges
50
+ // without human review, so what a bot may touch stays readable here
51
+ // and is not computed from code the PR itself could change. Keep it
52
+ // in step with `sync-release-version.mjs --list-surfaces` plus the
53
+ // three files the bump writes — test/release-sync.test.mjs fails if
54
+ // a surface is missing, which is how the GitLab template's absence
55
+ // (it would have held every release PR for review) was found.
56
+ const RELEASE_SURFACES = new Set(['package.json', 'package-lock.json', 'pyproject.toml', 'CHANGELOG.md', 'action.yml', 'packaging/homebrew/testguard.rb', 'README.md', 'packaging/gitlab/testguard.gitlab-ci.yml']);
50
57
 
51
58
  if (author === 'dependabot[bot]') {
52
59
  const m = /from (\d+)\.(\d+)\.\S+ to (\d+)\.(\d+)\./.exec(pr.title || '');
@@ -36,6 +36,7 @@ jobs:
36
36
  tag_missing: ${{ steps.check.outputs.tag_missing }}
37
37
  npm_missing: ${{ steps.check.outputs.npm_missing }}
38
38
  pypi_missing: ${{ steps.check.outputs.pypi_missing }}
39
+ brew_missing: ${{ steps.check.outputs.brew_missing }}
39
40
  steps:
40
41
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
41
42
  with:
@@ -45,6 +46,13 @@ jobs:
45
46
  # Each surface is gated on ITS OWN registry, not on the tag. A tag
46
47
  # whose npm or PyPI publish failed (e.g. Trusted Publishing not yet
47
48
  # configured) is retried by simply re-running this workflow.
49
+ #
50
+ # The Homebrew formula's sha256 is a surface too, gated on the registry
51
+ # tarball it names: `--check --online` downloads it and compares. It is
52
+ # owed (brew_missing=true) until npm serves the tarball AND the formula
53
+ # on main carries its hash — so a failed sha job is retried by the
54
+ # hourly sweep exactly like a failed publish. It does not set `changed`:
55
+ # only tag/npm/PyPI need the test job and the publish jobs.
48
56
  run: |
49
57
  CURRENT=$(node -p "require('./package.json').version")
50
58
  TAG="v${CURRENT}"
@@ -53,12 +61,17 @@ jobs:
53
61
  if npm view "testguard-cli@${CURRENT}" version >/dev/null 2>&1; then NPMM=false; else NPMM=true; fi
54
62
  CODE=$(curl -s -o /dev/null -w '%{http_code}' "https://pypi.org/pypi/testguard-cli/${CURRENT}/json")
55
63
  if [ "$CODE" = "200" ]; then PYPIM=false; else PYPIM=true; fi
64
+ if node .github/scripts/sync-release-version.mjs --check --online; then BREWM=false; else BREWM=true; fi
56
65
  echo "tag_missing=$TAGM" >> "$GITHUB_OUTPUT"
57
66
  echo "npm_missing=$NPMM" >> "$GITHUB_OUTPUT"
58
67
  echo "pypi_missing=$PYPIM" >> "$GITHUB_OUTPUT"
68
+ echo "brew_missing=$BREWM" >> "$GITHUB_OUTPUT"
59
69
  if [ "$TAGM" = true ] || [ "$NPMM" = true ] || [ "$PYPIM" = true ]; then
60
70
  echo "changed=true" >> "$GITHUB_OUTPUT"
61
- echo "✅ $TAG — tag missing: $TAGM · npm missing: $NPMM · PyPI missing: $PYPIM"
71
+ echo "✅ $TAG — tag missing: $TAGM · npm missing: $NPMM · PyPI missing: $PYPIM · Homebrew sha256 owed: $BREWM"
72
+ elif [ "$BREWM" = true ]; then
73
+ echo "changed=false" >> "$GITHUB_OUTPUT"
74
+ echo "✅ $TAG is published everywhere; only the Homebrew sha256 is owed"
62
75
  else
63
76
  echo "changed=false" >> "$GITHUB_OUTPUT"
64
77
  echo "⏭️ $TAG is fully released everywhere — nothing to do"
@@ -82,6 +95,12 @@ jobs:
82
95
  - name: Self-probe must be fully defended before anything ships
83
96
  run: node cli/testguard.mjs probe . --budget 180000 --out "$RUNNER_TEMP/self-evidence.json" --quiet --cost
84
97
  - name: Version surfaces are in sync
98
+ # Offline on purpose. Before the tag the tarball for this version
99
+ # cannot exist, so `--online` would say nothing in the normal path and
100
+ # would DEADLOCK the recovery path: npm published, the sha job failed,
101
+ # the formula still stale — an online check here fails and the job
102
+ # that fixes the formula never runs. The online comparison lives in
103
+ # detect-version (is it owed?) and in homebrew-sha (did it take?).
85
104
  run: node .github/scripts/sync-release-version.mjs --check
86
105
 
87
106
  create-release:
@@ -177,3 +196,113 @@ jobs:
177
196
  - name: Assert OIDC token is available
178
197
  run: '[ -n "$ACTIONS_ID_TOKEN_REQUEST_URL" ] || { echo "::error::no OIDC token endpoint"; exit 1; }'
179
198
  - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
199
+
200
+ homebrew-sha:
201
+ # The formula's sha256 is set from the PUBLISHED tarball — what Homebrew
202
+ # will download, which a provenance-signed publish can make differ byte
203
+ # for byte from `npm pack` — after npm serves it (a publish is `PUT 202`;
204
+ # the file can take minutes to appear, so the script polls, bounded).
205
+ #
206
+ # It cannot commit to main: branch protection requires the three CI legs
207
+ # on every commit and github-actions[bot] is no admin. So the one-line
208
+ # change lands as a bot PR with native auto-merge armed, and this job
209
+ # dispatches ci.yml on the branch itself — a PR opened with GITHUB_TOKEN
210
+ # gets no pull_request run, but workflow_dispatch is the documented
211
+ # exception. That also means the merge triggers nothing here; the hourly
212
+ # sweep observes the merged formula and finds brew_missing=false.
213
+ #
214
+ # Idempotent: an existing branch is reused, an existing open PR is left
215
+ # alone (CI re-dispatched only if its head has no check runs), a closed
216
+ # PR is respected as a human decision, and a formula that already carries
217
+ # the published hash lands nothing.
218
+ needs: [detect-version, test, create-release, publish-npm]
219
+ if: >-
220
+ !cancelled()
221
+ && needs.detect-version.outputs.brew_missing == 'true'
222
+ && needs.test.result != 'failure'
223
+ && needs.create-release.result != 'failure'
224
+ && (needs.publish-npm.result == 'success' || (needs.publish-npm.result == 'skipped' && needs.detect-version.outputs.npm_missing == 'false'))
225
+ runs-on: ubuntu-latest
226
+ permissions:
227
+ contents: write # pushes the chore/homebrew-sha-v* branch
228
+ pull-requests: write # opens the PR and arms auto-merge
229
+ actions: write # dispatches ci.yml on that branch
230
+ steps:
231
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
232
+ with:
233
+ ref: main
234
+ fetch-depth: 0
235
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
236
+ with:
237
+ node-version: 24
238
+ - name: Set sha256 from the published tarball; land it as an auto-merging PR
239
+ env:
240
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
241
+ run: |
242
+ set -euo pipefail
243
+ VERSION="${{ needs.detect-version.outputs.version }}"
244
+ ON_MAIN=$(node -p "require('./package.json').version")
245
+ if [ "$ON_MAIN" != "$VERSION" ]; then
246
+ echo "⏭️ main is already at v${ON_MAIN}, not v${VERSION}; that release sets its own sha256"; exit 0
247
+ fi
248
+ BRANCH="chore/homebrew-sha-v${VERSION}"
249
+ git config user.name "github-actions[bot]"
250
+ git config user.email "github-actions[bot]@users.noreply.github.com"
251
+ if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then
252
+ git fetch origin "$BRANCH"
253
+ git checkout -B "$BRANCH" "origin/$BRANCH"
254
+ else
255
+ git checkout -b "$BRANCH"
256
+ fi
257
+
258
+ node .github/scripts/sync-release-version.mjs --sha256 --wait 900
259
+ # Post-condition: the file now on disk matches the tarball, and no
260
+ # other surface drifted. Nothing is pushed unless this passes.
261
+ node .github/scripts/sync-release-version.mjs --check --online
262
+
263
+ if git diff --quiet -- packaging/homebrew/testguard.rb; then
264
+ echo "✅ $BRANCH already carries the published sha256"
265
+ else
266
+ git add packaging/homebrew/testguard.rb
267
+ git commit -m "chore(homebrew): sha256 for v${VERSION}, from the published tarball"
268
+ git push origin "$BRANCH"
269
+ fi
270
+ if [ -z "$(git log --oneline origin/main..HEAD)" ]; then
271
+ echo "✅ main already matches the published tarball; nothing to land"; exit 0
272
+ fi
273
+
274
+ CLOSED=$(gh pr list --head "$BRANCH" --base main --state closed --json url,mergedAt --jq '[.[] | select(.mergedAt == null)][0].url')
275
+ if [ -n "$CLOSED" ]; then
276
+ echo "::warning::$CLOSED was closed without merging; leaving that decision alone. Delete branch $BRANCH to let the sweep retry."; exit 0
277
+ fi
278
+ PR=$(gh pr list --head "$BRANCH" --base main --state open --json url --jq '.[0].url')
279
+ if [ -z "$PR" ]; then
280
+ {
281
+ printf 'Automated: `sha256` of `packaging/homebrew/testguard.rb` set from the published tarball\n\n'
282
+ printf ' https://registry.npmjs.org/testguard-cli/-/testguard-cli-%s.tgz\n\n' "$VERSION"
283
+ printf 'Computed by `.github/scripts/sync-release-version.mjs --sha256` in release.yml after `npm publish`, from the registry — never from a local `npm pack`. `--check --online` confirmed the value before this PR was opened.\n\n'
284
+ printf 'Auto-merge is armed; CI was dispatched on this branch by the release workflow. If the checks never appear: `gh workflow run ci.yml --ref %s`.\n' "$BRANCH"
285
+ } > /tmp/pr-body.md
286
+ if PR=$(gh pr create --base main --head "$BRANCH" --title "chore(homebrew): sha256 for v${VERSION}" --body-file /tmp/pr-body.md 2>/tmp/pr-err); then
287
+ gh pr merge --auto --squash "$PR"
288
+ echo "Opened $PR"
289
+ else
290
+ cat /tmp/pr-err
291
+ if grep -q 'not permitted to create or approve pull requests' /tmp/pr-err; then
292
+ echo "::error::GITHUB_TOKEN may not open a pull request: \"GitHub Actions is not permitted to create or approve pull requests\". The branch is pushed and correct; only the PR is missing. Fix it once, in Settings -> Actions -> General -> Workflow permissions, by ticking \"Allow GitHub Actions to create and approve pull requests\" (equivalently: gh api -X PUT repos/$GITHUB_REPOSITORY/actions/permissions/workflow -F default_workflow_permissions=read -F can_approve_pull_request_reviews=true), then re-run this workflow. Until then, open it by hand: gh pr create --base main --head $BRANCH The formula on main still carries the previous release's hash until then, and detect-version will keep reporting it owed on every hourly sweep."
293
+ else
294
+ echo "::error::gh pr create failed for $BRANCH — see the output above."
295
+ fi
296
+ exit 1
297
+ fi
298
+ else
299
+ echo "✅ PR already open: $PR"
300
+ fi
301
+ HEAD_SHA=$(git rev-parse HEAD)
302
+ RUNS=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${HEAD_SHA}/check-runs" --jq '.total_count')
303
+ if [ "${RUNS:-0}" = "0" ]; then
304
+ gh workflow run ci.yml --ref "$BRANCH"
305
+ echo "Dispatched ci.yml on $BRANCH ($HEAD_SHA)"
306
+ else
307
+ echo "✅ $HEAD_SHA already has $RUNS check run(s)"
308
+ fi
@@ -88,10 +88,23 @@ jobs:
88
88
 
89
89
  - name: Verify the tree is still green
90
90
  if: steps.check.outputs.changed == 'true'
91
+ # --include-dirty is REQUIRED here, not a convenience. The step above
92
+ # bumped the version and synced every surface, and those writes are
93
+ # still uncommitted: the commit happens later, in the PR step. One of
94
+ # those surfaces, packaging/gitlab/testguard.gitlab-ci.yml, is the
95
+ # fault target of TG-GITLAB-PROBE-EXIT-PRESERVED, so the default
96
+ # worktree mode refuses to run at all — "1 defender/target file has
97
+ # uncommitted changes … worktree mode probes HEAD, so those changes
98
+ # would be silently ignored" (exit 2). That refusal is correct; the
99
+ # answer is to probe the tree we are about to release, which is what
100
+ # --include-dirty does (it snapshots the working tree into a scratch
101
+ # commit). --ignore-dirty would probe HEAD and quietly verify the
102
+ # PREVIOUS version instead, which is the optimistic reading this
103
+ # project does not take.
91
104
  run: |
92
105
  npm ci
93
106
  npm test
94
- node cli/testguard.mjs probe . --budget 180000 --quiet --out "$RUNNER_TEMP/self.json"
107
+ node cli/testguard.mjs probe . --budget 180000 --quiet --include-dirty --out "$RUNNER_TEMP/self.json"
95
108
 
96
109
  - name: Open release PR with auto-merge armed
97
110
  if: steps.check.outputs.changed == 'true'
@@ -99,12 +112,17 @@ jobs:
99
112
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
100
113
  run: |
101
114
  BRANCH="release/v${NEW_VERSION}"
102
- # Only release surfaces may change in a release PR.
115
+ # Only release surfaces may change in a release PR. The list is
116
+ # DERIVED from the sync script, never retyped here: when the GitLab
117
+ # template became a surface it was left out of the hand-written list,
118
+ # so the first release to reach this guard would have been rejected
119
+ # for writing a file the release is supposed to write. The three
120
+ # extras are the ones the bump step writes directly.
121
+ ALLOWED=$(node .github/scripts/sync-release-version.mjs --list-surfaces; printf 'package.json\npackage-lock.json\nCHANGELOG.md\n')
122
+ echo "Release surfaces allowed in this diff:"; printf '%s\n' "$ALLOWED" | sed 's/^/ /'
103
123
  CHANGED=$(git diff --name-only HEAD --)
104
124
  for f in $CHANGED; do
105
- case "$f" in package.json|package-lock.json|pyproject.toml|CHANGELOG.md|action.yml|packaging/homebrew/testguard.rb|README.md) ;;
106
- *) echo "::error::unexpected file in release diff: $f"; exit 1;;
107
- esac
125
+ printf '%s\n' "$ALLOWED" | grep -qxF "$f" || { echo "::error::unexpected file in release diff: $f"; exit 1; }
108
126
  done
109
127
  if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then
110
128
  echo "::error::branch $BRANCH already exists; refusing to overwrite"; exit 1
@@ -116,6 +134,18 @@ jobs:
116
134
  git commit -m "release: v${NEW_VERSION} — automated weekly batch"
117
135
  git push origin "$BRANCH"
118
136
  printf 'Automated weekly release. Version bump + CHANGELOG entry + synced version surfaces. Merging tags and publishes to npm and PyPI via release.yml.\n\nCI does not start automatically on PRs opened by GITHUB_TOKEN. Run:\n\n gh workflow run ci.yml --ref %s\n\nAuto-merge is armed; it completes once the required checks pass.\n' "$BRANCH" > /tmp/pr-body.md
119
- PR=$(gh pr create --base main --head "$BRANCH" --title "release: v${NEW_VERSION} — automated weekly batch" --body-file /tmp/pr-body.md)
120
- gh pr merge --auto --squash "$PR"
121
- echo "Release PR: $PR"
137
+ # A bare `gh pr create` failure here reads as a GraphQL error with no
138
+ # hint of the cause, which is how v0.8.1 lost its release PR after
139
+ # everything else in the release had succeeded.
140
+ if PR=$(gh pr create --base main --head "$BRANCH" --title "release: v${NEW_VERSION} — automated weekly batch" --body-file /tmp/pr-body.md 2>/tmp/pr-err); then
141
+ gh pr merge --auto --squash "$PR"
142
+ echo "Release PR: $PR"
143
+ else
144
+ cat /tmp/pr-err
145
+ if grep -q 'not permitted to create or approve pull requests' /tmp/pr-err; then
146
+ echo "::error::GITHUB_TOKEN may not open a pull request: \"GitHub Actions is not permitted to create or approve pull requests\". The branch is pushed and correct; only the PR is missing. Fix it once, in Settings -> Actions -> General -> Workflow permissions, by ticking \"Allow GitHub Actions to create and approve pull requests\" (equivalently: gh api -X PUT repos/$GITHUB_REPOSITORY/actions/permissions/workflow -F default_workflow_permissions=read -F can_approve_pull_request_reviews=true), then re-run this workflow. Until then, open it by hand: gh pr create --base main --head $BRANCH"
147
+ else
148
+ echo "::error::gh pr create failed for $BRANCH — see the output above."
149
+ fi
150
+ exit 1
151
+ fi
@@ -7,6 +7,75 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Fixed
11
+ - **A release that cannot open its pull request says why.** `GITHUB_TOKEN`
12
+ cannot call `createPullRequest` unless "Allow GitHub Actions to create and
13
+ approve pull requests" is enabled, and v0.8.1 hit exactly that: the branch,
14
+ the bump and the diff were all correct and the run still ended in a bare
15
+ `GraphQL: GitHub Actions is not permitted to create or approve pull
16
+ requests`. Both `scheduled-release.yml` and `release.yml`'s `homebrew-sha`
17
+ job now name the setting, give the one-line `gh api` equivalent and the
18
+ manual `gh pr create` fallback, and say what stays stale until it is fixed.
19
+ The failure is still a failure: the branch exists without a PR, and for the
20
+ formula `detect-version` keeps reporting the hash owed on every sweep.
21
+
22
+ ## [0.8.1] - 2026-09-18
23
+
24
+ Automated weekly release — everything merged since `v0.8.0`.
25
+
26
+ ### Changed
27
+
28
+ - fix(release): the release allow-list is derived, not retyped (#100)
29
+ - fix(release): the weekly release verifies the tree it is about to ship (#99)
30
+ - ci(release): set the Homebrew sha256 from the published tarball (#98)
31
+ - chore(homebrew): sha256 for v0.8.0, from the published tarball (#97)
32
+
33
+
34
+ ### Fixed
35
+ - **A release is no longer blocked by an allow-list that forgot one of its own
36
+ surfaces.** `sync-release-version.mjs` rewrites
37
+ `packaging/gitlab/testguard.gitlab-ci.yml`, and that path was missing from
38
+ both release guards: `scheduled-release.yml` would have failed with
39
+ "unexpected file in release diff" for a file the release exists to write,
40
+ and `auto-merge.yml` would have held every release PR for human review. The
41
+ script gained `--list-surfaces`, derived from the same table the writes use;
42
+ `scheduled-release.yml` now builds its allow-list from that instead of a
43
+ retyped copy. `auto-merge.yml` keeps an explicit list on purpose — it is the
44
+ gate that merges without review — and a test now fails when that list omits
45
+ any release surface. Claim `TG-RELEASE-SYNC-CHECK-FAILS-ON-DRIFT` gains a
46
+ fifth fault.
47
+ - **The weekly release workflow can actually cut a release.** Its "verify the
48
+ tree is still green" step probed in worktree mode straight after syncing the
49
+ version surfaces, so the probe refused to run: the sync leaves
50
+ `packaging/gitlab/testguard.gitlab-ci.yml` uncommitted and that file is the
51
+ fault target of `TG-GITLAB-PROBE-EXIT-PRESERVED`, which worktree mode
52
+ correctly treats as a dirty defender (exit 2). It now probes with
53
+ `--include-dirty`, which verifies the bumped tree that is about to be
54
+ released rather than the version before it. `scheduled-release.yml` had
55
+ never completed a run since the GitLab template became both a sync surface
56
+ and a fault target on 2026-09-17; every release so far was cut by hand.
57
+ - **The Homebrew formula's `sha256` matches its own `url`.** It had carried
58
+ `385d69f9…` unchanged across v0.6.0, v0.7.0 and v0.8.0 while
59
+ `release:sync` bumped the `url` each time — a hash that matches none of
60
+ the 0.4.0–0.7.0 tarballs, so `brew install` from the staged formula could
61
+ never have verified. Set to the sha256 of the published
62
+ `testguard-cli-0.8.0.tgz` (`5305ce22…`), computed from the registry, not
63
+ from a local `npm pack`.
64
+ - **`release.yml` sets the Homebrew `sha256` itself, after publishing to npm.**
65
+ The formula header used to tell a human to run `curl | shasum` each release,
66
+ and nobody did. `sync-release-version.mjs --sha256` now waits for the
67
+ registry to serve the tarball (a publish is `PUT 202`; the file can lag by
68
+ minutes), hashes exactly the bytes Homebrew will download, and writes the
69
+ formula only when the value changes; a `200` that is not a gzip tarball is
70
+ refused, never hashed. `main` is protected, so the change lands as a bot PR
71
+ with auto-merge armed and CI dispatched by the workflow. The hash is a
72
+ release surface like the URL beside it: `detect-version` compares the
73
+ formula against the tarball (`--check --online`) on every hourly sweep, so
74
+ a failed run is retried the way a failed publish is, and `--check` refuses
75
+ the `385d69f9…` placeholder outright. Claim
76
+ `TG-RELEASE-SYNC-CHECK-FAILS-ON-DRIFT` gains three faults for the new modes.
77
+ Only the next release exercises the workflow path end to end.
78
+
10
79
  ## [0.8.0] - 2026-09-18
11
80
 
12
81
  A calibration you can quote.
@@ -81,6 +81,14 @@ on the fault.
81
81
  opens a release PR; merging it tags and publishes to npm and PyPI via OIDC
82
82
  Trusted Publishing. There are no tokens to rotate.
83
83
 
84
+ The Homebrew formula's `sha256` is the one surface that cannot be known before
85
+ 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
+ `--check` (offline) refuses a placeholder hash; `--check --online` compares
89
+ against the tarball and is what decides, on every hourly sweep, whether the
90
+ formula is still owed.
91
+
84
92
  ## Licence
85
93
 
86
94
  MIT. By contributing you agree your contribution is licensed the same way.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: testguard-cli
3
- Version: 0.8.0
3
+ Version: 0.8.1
4
4
  Summary: Breaks your code on purpose and reports every promise your tests did not notice breaking. Mutation testing bound to the claims a project makes — vitest, jest, Playwright, pytest and unittest. Ships the Node.js CLI (requires Node.js 20+).
5
5
  Project-URL: Homepage, https://github.com/raccioly/testguard
6
6
  Project-URL: Documentation, https://github.com/raccioly/testguard#readme
@@ -150,9 +150,9 @@ raises coverage. TestGuard admits it because it fails when the claim is false.
150
150
  | npm | `npm i -D testguard-cli` then `npx testguard probe` |
151
151
  | pip | `pip install testguard-cli` then `testguard probe` (needs Node ≥ 20) |
152
152
  | Homebrew | `brew tap raccioly/tap && brew install testguard` |
153
- | GitHub Action | `uses: raccioly/testguard@v0.8.0` — see [`action.yml`](./action.yml) |
153
+ | GitHub Action | `uses: raccioly/testguard@v0.8.1` — see [`action.yml`](./action.yml) |
154
154
  | pre-commit | `repo: https://github.com/raccioly/testguard`, hooks `testguard-claims`, `testguard-probe` |
155
- | GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.8.0/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
155
+ | GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.8.1/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
156
156
 
157
157
  Projects that set `min-release-age` in `.npmrc` cannot see a version published
158
158
  less than that many days ago (`ENOVERSIONS`); install that one with
@@ -451,12 +451,12 @@ detected base that does not resolve is a warning for `status` and `brief`
451
451
  # GitHub Actions
452
452
  - uses: actions/checkout@v4
453
453
  with: { fetch-depth: 0 }
454
- - uses: raccioly/testguard@v0.8.0
454
+ - uses: raccioly/testguard@v0.8.1
455
455
  with: { command: gate }
456
456
 
457
457
  # GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
458
458
  include:
459
- - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.8.0/packaging/gitlab/testguard.gitlab-ci.yml'
459
+ - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.8.1/packaging/gitlab/testguard.gitlab-ci.yml'
460
460
  inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
461
461
  ```
462
462
 
@@ -126,9 +126,9 @@ raises coverage. TestGuard admits it because it fails when the claim is false.
126
126
  | npm | `npm i -D testguard-cli` then `npx testguard probe` |
127
127
  | pip | `pip install testguard-cli` then `testguard probe` (needs Node ≥ 20) |
128
128
  | Homebrew | `brew tap raccioly/tap && brew install testguard` |
129
- | GitHub Action | `uses: raccioly/testguard@v0.8.0` — see [`action.yml`](./action.yml) |
129
+ | GitHub Action | `uses: raccioly/testguard@v0.8.1` — see [`action.yml`](./action.yml) |
130
130
  | pre-commit | `repo: https://github.com/raccioly/testguard`, hooks `testguard-claims`, `testguard-probe` |
131
- | GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.8.0/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
131
+ | GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.8.1/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
132
132
 
133
133
  Projects that set `min-release-age` in `.npmrc` cannot see a version published
134
134
  less than that many days ago (`ENOVERSIONS`); install that one with
@@ -427,12 +427,12 @@ detected base that does not resolve is a warning for `status` and `brief`
427
427
  # GitHub Actions
428
428
  - uses: actions/checkout@v4
429
429
  with: { fetch-depth: 0 }
430
- - uses: raccioly/testguard@v0.8.0
430
+ - uses: raccioly/testguard@v0.8.1
431
431
  with: { command: gate }
432
432
 
433
433
  # GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
434
434
  include:
435
- - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.8.0/packaging/gitlab/testguard.gitlab-ci.yml'
435
+ - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.8.1/packaging/gitlab/testguard.gitlab-ci.yml'
436
436
  inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
437
437
  ```
438
438
 
@@ -46,7 +46,7 @@ inputs:
46
46
  version:
47
47
  description: 'testguard-cli version to run'
48
48
  required: false
49
- default: '0.8.0'
49
+ default: '0.8.1'
50
50
  node-version:
51
51
  description: 'Node.js version (20+)'
52
52
  required: false
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "testguard-cli",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "testguard-cli",
9
- "version": "0.8.0",
9
+ "version": "0.8.1",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "ajv": "8.20.0"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testguard-cli",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Breaks your code on purpose and reports every promise your tests did not notice breaking. Mutation testing bound to the claims a project makes — vitest, jest, Playwright, pytest and unittest.",
5
5
  "type": "module",
6
6
  "license": "MIT",