testguard-cli 0.15.1__tar.gz → 0.16.0__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 (349) hide show
  1. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/scripts/install-smoke.mjs +12 -1
  2. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/CHANGELOG.md +31 -0
  3. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/PKG-INFO +49 -9
  4. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/README.md +48 -8
  5. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/action.yml +1 -1
  6. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/docs/testguard-explained.html +1 -1
  7. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/docs/testguard-explained.pdf +0 -0
  8. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/docs-canonical/DATA-MODEL.md +20 -6
  9. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/docs-canonical/REQUIREMENTS.md +5 -2
  10. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/docs-canonical/TEST-SPEC.md +13 -6
  11. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/package-lock.json +2 -2
  12. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/package.json +1 -1
  13. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/packaging/gitlab/testguard.gitlab-ci.yml +2 -2
  14. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/packaging/homebrew/testguard.rb +2 -2
  15. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/pyproject.toml +1 -1
  16. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/GATE-SEMANTICS.md +21 -0
  17. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/schemas.test.mjs +44 -0
  18. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/lib/validate.mjs +9 -0
  19. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/schemas/claims.schema.json +6 -0
  20. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/schemas/evidence.schema.json +4 -0
  21. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/schemas/status.schema.json +25 -0
  22. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/commands/admit.mjs +8 -3
  23. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/commands/claims.mjs +40 -4
  24. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/gate/changed.mjs +5 -1
  25. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/init/init.mjs +6 -0
  26. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/init/templates/SKILL.md +43 -0
  27. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/attribution.mjs +9 -0
  28. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/cost.mjs +16 -8
  29. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/probe.mjs +27 -23
  30. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/status/status.mjs +9 -2
  31. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/attribution.test.mjs +20 -1
  32. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/cost.test.mjs +20 -3
  33. testguard_cli-0.16.0/test/fault-defender-orchestration.test.mjs +44 -0
  34. testguard_cli-0.16.0/test/fault-defenders.test.mjs +95 -0
  35. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/gate.test.mjs +15 -0
  36. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/init.test.mjs +36 -0
  37. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/runner-vitest.test.mjs +12 -3
  38. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/status.test.mjs +22 -3
  39. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/testguard.claims.json +29 -4
  40. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.agents/skills/testguard/SKILL.md +0 -0
  41. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.docguard.json +0 -0
  42. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.docguardignore +0 -0
  43. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.gitattributes +0 -0
  44. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  45. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  46. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  47. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/dependabot.yml +0 -0
  48. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/scripts/approve-held-runs.sh +0 -0
  49. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/scripts/build-one-pager.mjs +0 -0
  50. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/scripts/check-cost-budget.mjs +0 -0
  51. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/scripts/check-python-fixture.mjs +0 -0
  52. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/scripts/publish-homebrew-tap.sh +0 -0
  53. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/scripts/sync-release-version.mjs +0 -0
  54. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/workflows/auto-merge.yml +0 -0
  55. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/workflows/ci.yml +0 -0
  56. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/workflows/release.yml +0 -0
  57. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/workflows/scheduled-release.yml +0 -0
  58. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.github/workflows/supply-chain.yml +0 -0
  59. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.gitignore +0 -0
  60. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.npmignore +0 -0
  61. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.npmrc +0 -0
  62. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.pre-commit-config.yaml +0 -0
  63. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/.pre-commit-hooks.yaml +0 -0
  64. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/AGENTS.md +0 -0
  65. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/CLAUDE.md +0 -0
  66. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/CODE_OF_CONDUCT.md +0 -0
  67. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/CONTRIBUTING.md +0 -0
  68. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/DRIFT-LOG.md +0 -0
  69. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/LICENSE +0 -0
  70. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/PRIVACY.md +0 -0
  71. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/SECURITY.md +0 -0
  72. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/SUPPORT.md +0 -0
  73. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/bench/README.md +0 -0
  74. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/cli/testguard.mjs +0 -0
  75. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/docs-canonical/ARCHITECTURE.md +0 -0
  76. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/docs-canonical/ENVIRONMENT.md +0 -0
  77. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/docs-canonical/PRIOR-ART.md +0 -0
  78. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/docs-canonical/SECURITY.md +0 -0
  79. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer/.gitignore +0 -0
  80. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer/README.md +0 -0
  81. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer/expected.json +0 -0
  82. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer/package.json +0 -0
  83. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer/src/export.mjs +0 -0
  84. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer/src/redact.mjs +0 -0
  85. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
  86. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer/test/flaky.test.mjs +0 -0
  87. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer/test/redact.test.mjs +0 -0
  88. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer/testguard.claims.json +0 -0
  89. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer/vitest.config.mjs +0 -0
  90. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-jest/.gitignore +0 -0
  91. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-jest/README.md +0 -0
  92. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-jest/expected.json +0 -0
  93. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-jest/jest.config.js +0 -0
  94. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-jest/package.json +0 -0
  95. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-jest/src/export.js +0 -0
  96. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-jest/src/redact.js +0 -0
  97. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
  98. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
  99. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-jest/test/redact.test.js +0 -0
  100. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-jest/testguard.claims.json +0 -0
  101. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-playwright/.gitignore +0 -0
  102. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-playwright/README.md +0 -0
  103. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
  104. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
  105. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-playwright/expected.json +0 -0
  106. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-playwright/package-lock.json +0 -0
  107. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-playwright/package.json +0 -0
  108. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
  109. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
  110. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
  111. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
  112. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
  113. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-python/.gitignore +0 -0
  114. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-python/README.md +0 -0
  115. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-python/demo/__init__.py +0 -0
  116. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-python/demo/export.py +0 -0
  117. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-python/demo/orphan.py +0 -0
  118. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-python/demo/pipeline.py +0 -0
  119. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-python/demo/redact.py +0 -0
  120. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-python/expected.json +0 -0
  121. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-python/testguard.claims.json +0 -0
  122. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
  123. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
  124. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-python/tests/test_patched.py +0 -0
  125. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/fixtures/known-answer-python/tests/test_redact.py +0 -0
  126. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/README.md +0 -0
  127. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/baseline.json +0 -0
  128. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/brief.json +0 -0
  129. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/calibration-websec.json +0 -0
  130. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/calibration.json +0 -0
  131. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/claims-methods.json +0 -0
  132. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/claims.json +0 -0
  133. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
  134. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/evidence-negative-control.json +0 -0
  135. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/evidence-persistence-signal.json +0 -0
  136. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/evidence-provisional.json +0 -0
  137. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/evidence-python.json +0 -0
  138. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/evidence-scan-method.json +0 -0
  139. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/evidence.json +0 -0
  140. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/gate.json +0 -0
  141. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/ignore.json +0 -0
  142. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/replay.json +0 -0
  143. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/examples/status.json +0 -0
  144. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
  145. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/baseline.zero-count.json +0 -0
  146. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
  147. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
  148. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +0 -0
  149. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +0 -0
  150. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/calibration.ci-not-wilson.json +0 -0
  151. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +0 -0
  152. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/calibration.fallback-without-basis.json +0 -0
  153. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/calibration.p-not-positives-over-n.json +0 -0
  154. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
  155. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
  156. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/claims.absolute-path.json +0 -0
  157. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
  158. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
  159. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +0 -0
  160. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
  161. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/claims.noop-fault.json +0 -0
  162. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
  163. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
  164. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
  165. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
  166. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
  167. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
  168. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
  169. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
  170. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
  171. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
  172. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
  173. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
  174. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
  175. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
  176. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +0 -0
  177. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
  178. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
  179. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.persistence-signal-not-a-defender.json +0 -0
  180. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.persistence-signal-on-a-non-survivor.json +0 -0
  181. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.persistence-signal-without-reason.json +0 -0
  182. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
  183. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
  184. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
  185. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
  186. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
  187. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
  188. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
  189. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
  190. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
  191. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
  192. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
  193. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
  194. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
  195. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
  196. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/ignore.short-reason.json +0 -0
  197. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
  198. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
  199. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/replay.nocover-when-no-test-file-remained.json +0 -0
  200. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/status.claim-without-file.json +0 -0
  201. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
  202. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/status.invalid-anchor-hidden.json +0 -0
  203. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/status.partial-scope-counts-disagree.json +0 -0
  204. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/status.surface-counts-disagree.json +0 -0
  205. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
  206. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
  207. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/lib/fingerprint.mjs +0 -0
  208. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/lib/wilson.mjs +0 -0
  209. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/schemas/baseline.schema.json +0 -0
  210. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/schemas/brief.schema.json +0 -0
  211. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/schemas/calibration.schema.json +0 -0
  212. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/schemas/common.schema.json +0 -0
  213. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/schemas/concerns.schema.json +0 -0
  214. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/schemas/gate.schema.json +0 -0
  215. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/schemas/ignore.schema.json +0 -0
  216. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/schemas/replay.schema.json +0 -0
  217. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/spec/schemas/sweep.schema.json +0 -0
  218. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/admit/admit.mjs +0 -0
  219. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/baseline/baseline.mjs +0 -0
  220. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/brief/brief.mjs +0 -0
  221. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/claims/anchors.mjs +0 -0
  222. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/claims/annotations.mjs +0 -0
  223. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/claims/load.mjs +0 -0
  224. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/claims/removed.mjs +0 -0
  225. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/cli.mjs +0 -0
  226. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/command-budget.mjs +0 -0
  227. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/commands/baseline.mjs +0 -0
  228. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/commands/brief.mjs +0 -0
  229. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/commands/concerns.mjs +0 -0
  230. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/commands/gate.mjs +0 -0
  231. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/commands/init.mjs +0 -0
  232. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/commands/mcp.mjs +0 -0
  233. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/commands/probe.mjs +0 -0
  234. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/commands/replay.mjs +0 -0
  235. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/commands/scaffold.mjs +0 -0
  236. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/commands/status.mjs +0 -0
  237. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/commands/sweep.mjs +0 -0
  238. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/evidence/writer.mjs +0 -0
  239. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/git.mjs +0 -0
  240. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/mcp/server.mjs +0 -0
  241. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/mcp/tools.mjs +0 -0
  242. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/classify.mjs +0 -0
  243. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/contention.mjs +0 -0
  244. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/discover.mjs +0 -0
  245. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/imports.mjs +0 -0
  246. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/independence.mjs +0 -0
  247. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/inject.mjs +0 -0
  248. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/mocks.mjs +0 -0
  249. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/progress.mjs +0 -0
  250. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/pyimports.mjs +0 -0
  251. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/rank.mjs +0 -0
  252. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/runners/discovery.mjs +0 -0
  253. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/runners/index.mjs +0 -0
  254. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/runners/jest.mjs +0 -0
  255. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/runners/playwright.mjs +0 -0
  256. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
  257. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/runners/python/_testguard_report.py +0 -0
  258. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
  259. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/runners/python.mjs +0 -0
  260. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/runners/shared.mjs +0 -0
  261. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/runners/vitest.mjs +0 -0
  262. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/probe/worktree.mjs +0 -0
  263. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/render.mjs +0 -0
  264. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/replay/label.mjs +0 -0
  265. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/replay/replay.mjs +0 -0
  266. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/scaffold/producers.mjs +0 -0
  267. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/scaffold/producers.python.mjs +0 -0
  268. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/scaffold/scaffold.mjs +0 -0
  269. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/status/surface.mjs +0 -0
  270. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/supply/concerns.mjs +0 -0
  271. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/supply/feedback.mjs +0 -0
  272. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/supply/persistence.mjs +0 -0
  273. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/supply/savepath.mjs +0 -0
  274. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/supply/select.mjs +0 -0
  275. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/sweep/sweep.mjs +0 -0
  276. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/util/glob.mjs +0 -0
  277. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/src/util/hash.mjs +0 -0
  278. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/admit-decision.test.mjs +0 -0
  279. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/admit.test.mjs +0 -0
  280. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/annotations.test.mjs +0 -0
  281. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/baseline.test.mjs +0 -0
  282. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/brief.test.mjs +0 -0
  283. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/calibration.test.mjs +0 -0
  284. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/claim-selection.test.mjs +0 -0
  285. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/claims-anchors.test.mjs +0 -0
  286. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/claims-since.test.mjs +0 -0
  287. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/classify.test.mjs +0 -0
  288. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/command-budget-write-boundaries.test.mjs +0 -0
  289. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/command-budget.test.mjs +0 -0
  290. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/contention.test.mjs +0 -0
  291. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/discover.test.mjs +0 -0
  292. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/evidence-persistence-signal.test.mjs +0 -0
  293. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/gitlab-template.test.mjs +0 -0
  294. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/glob.test.mjs +0 -0
  295. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/imports.test.mjs +0 -0
  296. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/independence.test.mjs +0 -0
  297. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/inject.test.mjs +0 -0
  298. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/label.test.mjs +0 -0
  299. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/mcp.test.mjs +0 -0
  300. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/mocks.test.mjs +0 -0
  301. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/negative-control.test.mjs +0 -0
  302. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/one-pager.test.mjs +0 -0
  303. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/probe-dirty-paths.test.mjs +0 -0
  304. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/probe-error.test.mjs +0 -0
  305. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/probe-native-manifest.test.mjs +0 -0
  306. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/probe-persistence-signal.test.mjs +0 -0
  307. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/probe-preconditions.test.mjs +0 -0
  308. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/probe.fixture.test.mjs +0 -0
  309. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/probe.jest.test.mjs +0 -0
  310. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/probe.playwright.test.mjs +0 -0
  311. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/probe.python.test.mjs +0 -0
  312. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/progress.test.mjs +0 -0
  313. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/pyimports.test.mjs +0 -0
  314. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/rank-aliases.test.mjs +0 -0
  315. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/release-approval.test.mjs +0 -0
  316. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/release-sync.test.mjs +0 -0
  317. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/replay-mixed-runners.test.mjs +0 -0
  318. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/replay-no-test-file-left.test.mjs +0 -0
  319. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/replay-python-defenders.test.mjs +0 -0
  320. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/replay-verdict.test.mjs +0 -0
  321. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/replay.test.mjs +0 -0
  322. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/runner-argv.test.mjs +0 -0
  323. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/runner-command.test.mjs +0 -0
  324. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/runner-discovery.test.mjs +0 -0
  325. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/runner-playwright.test.mjs +0 -0
  326. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/runner-python.test.mjs +0 -0
  327. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/runner-selection-blind.test.mjs +0 -0
  328. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/samples/playwright-report.json +0 -0
  329. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/samples/python-report-collect-error.json +0 -0
  330. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/samples/python-report.json +0 -0
  331. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/scaffold.python.test.mjs +0 -0
  332. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/scaffold.test.mjs +0 -0
  333. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/snapshot.test.mjs +0 -0
  334. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/supply-concerns.test.mjs +0 -0
  335. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/supply-feedback.test.mjs +0 -0
  336. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/supply-persistence.test.mjs +0 -0
  337. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/supply-savepath.test.mjs +0 -0
  338. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/supply-select.test.mjs +0 -0
  339. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/surface.test.mjs +0 -0
  340. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/sweep.fixture.test.mjs +0 -0
  341. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/sweep.test.mjs +0 -0
  342. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/vitest-config.test.mjs +0 -0
  343. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/worktree.test.mjs +0 -0
  344. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/test/writer.test.mjs +0 -0
  345. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/testguard.cost-budget.json +0 -0
  346. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/testguard.ignore.json +0 -0
  347. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/testguard_cli/__init__.py +0 -0
  348. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/testguard_cli/wrapper.py +0 -0
  349. {testguard_cli-0.15.1 → testguard_cli-0.16.0}/vitest.config.mjs +0 -0
@@ -61,8 +61,18 @@ try {
61
61
  '-e',
62
62
  `import { pathToFileURL } from 'node:url'; import { readFileSync } from 'node:fs';
63
63
  const { validate } = await import(pathToFileURL(process.argv[1]));
64
- const result = validate('evidence', JSON.parse(readFileSync(process.argv[2], 'utf8')));
64
+ const candidate = JSON.parse(readFileSync(process.argv[2], 'utf8'));
65
+ for (const r of candidate.records) {
66
+ r.defenders.selectionSource = r.defenders.requested.length ? 'claim' : 'discovery';
67
+ if (!r.defenders.requested.length) r.defenders.discovered = true;
68
+ }
69
+ const result = validate('evidence', candidate);
65
70
  if (!result.ok) throw new Error(result.errors.map((error) => \`${'${error.path}: ${error.message}'}\`).join('; '));
71
+ const claims = JSON.parse(readFileSync(process.argv[4], 'utf8'));
72
+ claims.claims[0].faults[0].defendedBy = [];
73
+ if (!validate('claims', claims).ok) throw new Error('installed package rejected a fault discovery override');
74
+ claims.claims[0].faults[0].defendedBy = ['test/unit.test.mjs'];
75
+ if (!validate('claims', claims).ok) throw new Error('installed package rejected a fault defender override');
66
76
  const gate = JSON.parse(readFileSync(process.argv[3], 'utf8'));
67
77
  gate.nested = [{ project: 'backend', files: ['backend/src/guard.mjs'] }]; gate.changed++;
68
78
  const nestedResult = validate('gate', gate);
@@ -71,6 +81,7 @@ try {
71
81
  installedValidator,
72
82
  candidateEvidence,
73
83
  join(root, 'spec', 'conformance', 'examples', 'gate.json'),
84
+ join(root, 'spec', 'conformance', 'examples', 'claims.json'),
74
85
  ], consumer).trim();
75
86
  if (!draft.claims?.length) throw new Error('scaffold produced no claims from the installed tarball');
76
87
  if (!/^\d+\.\d+\.\d+/.test(version)) throw new Error(`unexpected --version output: ${version}`);
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.16.0] - 2026-09-30
11
+
12
+ Automated weekly release — everything merged since `v0.15.1`.
13
+
14
+ ### Changed
15
+
16
+ - feat: fault-specific defenders, provenance and cost reporting (#184)
17
+ - Add advisory AI update guidance and stabilize cleanup regression (#183)
18
+ - chore(homebrew): sha256 for v0.15.1 (#182)
19
+
20
+
21
+ ### Added
22
+
23
+ - Fault-specific `defendedBy` overrides, with explicit empty-list discovery,
24
+ evidence selection provenance, per-fault cost reporting and prior-kill
25
+ narrowing warnings (#90). Selection changes invalidate reuse and status;
26
+ admission and changed-file coverage use the actual selected defenders.
27
+
28
+ ### Changed
29
+
30
+ - New AI integrations include permission-aware, once-per-session update advice
31
+ that identifies the actual CLI and requests approval before any upgrade.
32
+ CLI commands and session-start hooks remain offline. Existing customized
33
+ instructions remain untouched; safe manual refresh is documented.
34
+
35
+ ### Fixed
36
+
37
+ - The daemon-cleanup regression test now requires explicit startup and
38
+ post-cleanup release signals instead of assuming a detached process starts
39
+ and writes within fixed sleeps. Timeout and cleanup semantics are unchanged.
40
+
10
41
  ## [0.15.1] - 2026-09-30
11
42
 
12
43
  Automated weekly release — everything merged since `v0.15.0`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: testguard-cli
3
- Version: 0.15.1
3
+ Version: 0.16.0
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.15.1` — see [`action.yml`](./action.yml) |
154
+ | GitHub Action | `uses: raccioly/testguard@v0.16.0` — 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.15.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.16.0/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
@@ -245,6 +245,14 @@ npx testguard-cli admit test/x.test.ts --claim X # is this test green on HEAD
245
245
  the evidence records them as `repo.ignoredDirty`, so the run says what it
246
246
  did not look at. Every summary names the commit probed.
247
247
 
248
+ A fault may declare its own `defendedBy` to override the claim's set.
249
+ Omit the fault field to inherit; set it to `[]` to request discovery rather
250
+ than inheritance. Evidence names the selection origin and selected files.
251
+ Changing either invalidates reuse and makes status stale. A narrowed fault
252
+ set after a prior kill is warned about by `claims`; re-probe before trusting
253
+ it. For example, a unit-level fault can select `["test/unit.test.mjs"]`
254
+ while an integration fault inherits the claim's broader set.
255
+
248
256
  A claim with no `defendedBy` has its defenders **discovered**: the test
249
257
  files that import the fault's target, by relative path or resolved alias
250
258
  (tsconfig `paths`, through `extends` and `references`, vite/vitest
@@ -352,6 +360,34 @@ npx testguard-cli admit test/x.test.ts --claim X # is this test green on HEAD
352
360
  offline. The brief's first line says which install answered
353
361
  (`local install` or `global`), so a stale one is visible.
354
362
 
363
+ ### Advisory updates for AI sessions
364
+
365
+ New integrations created by `testguard init` tell the AI to identify the actual
366
+ CLI (project-local before global) and, once per session when network policy
367
+ permits, read the registry's stable version:
368
+
369
+ ```bash
370
+ npm view testguard-cli dist-tags.latest --json --fetch-retries=0 --fetch-timeout=5000
371
+ ```
372
+
373
+ This is an optional check performed by the AI, not by TestGuard's CLI or hook.
374
+ A newer stable version produces a suggestion and a request for approval, never
375
+ an automatic install. Project pins and the original package manager still
376
+ apply; prereleases and development checkouts are not replaced. Offline or
377
+ failed checks do not change verdicts, evidence or exit codes, and do not prove
378
+ that an installation is current. AI instructions are guidance, not a guarantee
379
+ that every harness performs the check.
380
+
381
+ Upgrading the package does **not** refresh an existing copied skill or an
382
+ existing managed section already listing the project. Compare the upgraded
383
+ package's `src/init/templates/SKILL.md` with
384
+ `.claude/skills/testguard/SKILL.md` and merge approved changes, preserving local
385
+ customizations. For an existing `AGENTS.md` section, add the same advisory
386
+ policy after reviewing it. `testguard init --force` replaces the whole skill
387
+ but does not refresh an already-listed managed section; use it only with
388
+ explicit overwrite approval and review the diff. No new automatic refresh
389
+ mechanism is introduced.
390
+
355
391
  ### Would this suite have caught the bugs that already escaped?
356
392
 
357
393
  An injected fault is a fault somebody thought of. A bug that actually shipped
@@ -701,12 +737,12 @@ detected base that does not resolve is a warning for `status` and `brief`
701
737
  # GitHub Actions
702
738
  - uses: actions/checkout@v4
703
739
  with: { fetch-depth: 0 }
704
- - uses: raccioly/testguard@v0.15.1
740
+ - uses: raccioly/testguard@v0.16.0
705
741
  with: { command: gate }
706
742
 
707
743
  # GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
708
744
  include:
709
- - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.15.1/packaging/gitlab/testguard.gitlab-ci.yml'
745
+ - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.16.0/packaging/gitlab/testguard.gitlab-ci.yml'
710
746
  inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
711
747
  ```
712
748
 
@@ -979,19 +1015,23 @@ most expensive claims
979
1015
  4m 58s TG-FAULT-EDIT-VISIBLE 1 fault · 6 runs
980
1016
 
981
1017
  defender files, by the time of the runs that included them
982
- (a claim runs its whole defender set at once, so these overlap and do not sum to the total)
1018
+ (a fault runs its selected defender set at once, so these overlap and do not sum to the total)
983
1019
  24m 3s test/probe.fixture.test.mjs 5 claims
984
1020
 
985
- shared defenders — each claim pays the file's full cost again
1021
+ shared defenders — each fault selects the file; durations include recorded baseline sharing
986
1022
  24m 3s test/probe.fixture.test.mjs
987
- named by TG-ESCALATION-N-RUNS, TG-FAULT-EDIT-VISIBLE, …
1023
+ named by TG-ESCALATION-N-RUNS/F1, TG-FAULT-EDIT-VISIBLE/F1, …
988
1024
  ```
989
1025
 
990
1026
  Every number is read back out of the `durationMs` the probe already records.
991
1027
  `--cost` re-measures nothing, spawns nothing, and costs the price of reading
992
1028
  one JSON file.
993
1029
 
994
- **The per-file figures overlap.** A run executes a claim's whole defender set
1030
+ Cost output also lists individual faults, their selected defenders and selection
1031
+ origin. Sharing is counted across distinct fault identities, including siblings
1032
+ within one claim; repeated confirmation runs alone are not sharing.
1033
+
1034
+ **The per-file figures overlap.** A run executes a fault's whole defender set
995
1035
  at once, so the runner never says how much of it belonged to which file. A
996
1036
  file's `ms` is the time of every run that *included* it: an upper bound on
997
1037
  what removing it could save, which is the decision you are making. Only
@@ -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.15.1` — see [`action.yml`](./action.yml) |
130
+ | GitHub Action | `uses: raccioly/testguard@v0.16.0` — 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.15.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.16.0/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
@@ -221,6 +221,14 @@ npx testguard-cli admit test/x.test.ts --claim X # is this test green on HEAD
221
221
  the evidence records them as `repo.ignoredDirty`, so the run says what it
222
222
  did not look at. Every summary names the commit probed.
223
223
 
224
+ A fault may declare its own `defendedBy` to override the claim's set.
225
+ Omit the fault field to inherit; set it to `[]` to request discovery rather
226
+ than inheritance. Evidence names the selection origin and selected files.
227
+ Changing either invalidates reuse and makes status stale. A narrowed fault
228
+ set after a prior kill is warned about by `claims`; re-probe before trusting
229
+ it. For example, a unit-level fault can select `["test/unit.test.mjs"]`
230
+ while an integration fault inherits the claim's broader set.
231
+
224
232
  A claim with no `defendedBy` has its defenders **discovered**: the test
225
233
  files that import the fault's target, by relative path or resolved alias
226
234
  (tsconfig `paths`, through `extends` and `references`, vite/vitest
@@ -328,6 +336,34 @@ npx testguard-cli admit test/x.test.ts --claim X # is this test green on HEAD
328
336
  offline. The brief's first line says which install answered
329
337
  (`local install` or `global`), so a stale one is visible.
330
338
 
339
+ ### Advisory updates for AI sessions
340
+
341
+ New integrations created by `testguard init` tell the AI to identify the actual
342
+ CLI (project-local before global) and, once per session when network policy
343
+ permits, read the registry's stable version:
344
+
345
+ ```bash
346
+ npm view testguard-cli dist-tags.latest --json --fetch-retries=0 --fetch-timeout=5000
347
+ ```
348
+
349
+ This is an optional check performed by the AI, not by TestGuard's CLI or hook.
350
+ A newer stable version produces a suggestion and a request for approval, never
351
+ an automatic install. Project pins and the original package manager still
352
+ apply; prereleases and development checkouts are not replaced. Offline or
353
+ failed checks do not change verdicts, evidence or exit codes, and do not prove
354
+ that an installation is current. AI instructions are guidance, not a guarantee
355
+ that every harness performs the check.
356
+
357
+ Upgrading the package does **not** refresh an existing copied skill or an
358
+ existing managed section already listing the project. Compare the upgraded
359
+ package's `src/init/templates/SKILL.md` with
360
+ `.claude/skills/testguard/SKILL.md` and merge approved changes, preserving local
361
+ customizations. For an existing `AGENTS.md` section, add the same advisory
362
+ policy after reviewing it. `testguard init --force` replaces the whole skill
363
+ but does not refresh an already-listed managed section; use it only with
364
+ explicit overwrite approval and review the diff. No new automatic refresh
365
+ mechanism is introduced.
366
+
331
367
  ### Would this suite have caught the bugs that already escaped?
332
368
 
333
369
  An injected fault is a fault somebody thought of. A bug that actually shipped
@@ -677,12 +713,12 @@ detected base that does not resolve is a warning for `status` and `brief`
677
713
  # GitHub Actions
678
714
  - uses: actions/checkout@v4
679
715
  with: { fetch-depth: 0 }
680
- - uses: raccioly/testguard@v0.15.1
716
+ - uses: raccioly/testguard@v0.16.0
681
717
  with: { command: gate }
682
718
 
683
719
  # GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
684
720
  include:
685
- - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.15.1/packaging/gitlab/testguard.gitlab-ci.yml'
721
+ - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.16.0/packaging/gitlab/testguard.gitlab-ci.yml'
686
722
  inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
687
723
  ```
688
724
 
@@ -955,19 +991,23 @@ most expensive claims
955
991
  4m 58s TG-FAULT-EDIT-VISIBLE 1 fault · 6 runs
956
992
 
957
993
  defender files, by the time of the runs that included them
958
- (a claim runs its whole defender set at once, so these overlap and do not sum to the total)
994
+ (a fault runs its selected defender set at once, so these overlap and do not sum to the total)
959
995
  24m 3s test/probe.fixture.test.mjs 5 claims
960
996
 
961
- shared defenders — each claim pays the file's full cost again
997
+ shared defenders — each fault selects the file; durations include recorded baseline sharing
962
998
  24m 3s test/probe.fixture.test.mjs
963
- named by TG-ESCALATION-N-RUNS, TG-FAULT-EDIT-VISIBLE, …
999
+ named by TG-ESCALATION-N-RUNS/F1, TG-FAULT-EDIT-VISIBLE/F1, …
964
1000
  ```
965
1001
 
966
1002
  Every number is read back out of the `durationMs` the probe already records.
967
1003
  `--cost` re-measures nothing, spawns nothing, and costs the price of reading
968
1004
  one JSON file.
969
1005
 
970
- **The per-file figures overlap.** A run executes a claim's whole defender set
1006
+ Cost output also lists individual faults, their selected defenders and selection
1007
+ origin. Sharing is counted across distinct fault identities, including siblings
1008
+ within one claim; repeated confirmation runs alone are not sharing.
1009
+
1010
+ **The per-file figures overlap.** A run executes a fault's whole defender set
971
1011
  at once, so the runner never says how much of it belonged to which file. A
972
1012
  file's `ms` is the time of every run that *included* it: an upper bound on
973
1013
  what removing it could save, which is the decision you are making. Only
@@ -50,7 +50,7 @@ inputs:
50
50
  version:
51
51
  description: 'testguard-cli version to run'
52
52
  required: false
53
- default: '0.15.1'
53
+ default: '0.16.0'
54
54
  node-version:
55
55
  description: 'Node.js version (20+)'
56
56
  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.15.1 · MIT</div>
147
+ <div class="meta">Technical Brief · v0.16.0 · MIT</div>
148
148
  </div>
149
149
 
150
150
  <h1>Your tests pass. That is not the same as your tests working.</h1>
@@ -1,8 +1,8 @@
1
1
  # Data Model
2
2
 
3
- <!-- docguard:version 1.0.0 -->
3
+ <!-- docguard:version 1.1.0 -->
4
4
  <!-- docguard:status approved -->
5
- <!-- docguard:last-reviewed 2026-09-19 -->
5
+ <!-- docguard:last-reviewed 2026-09-30 -->
6
6
  <!-- docguard:owner @raccioly -->
7
7
  <!-- docguard:quality negation-load off — the data model is largely a set of prohibitions (no database, no server state, fingerprints never derived from fault text). -->
8
8
  <!-- docguard:quality passive-voice off — this document describes what happens to documents as they flow through validation, where the artifact is the subject and the actor is irrelevant. -->
@@ -60,12 +60,23 @@ be told from a mechanically derived one.
60
60
  `{find, replace}` against one file, with `expectHits` and `occurrence` so the
61
61
  anchor is exact. `faultClass` is one of thirteen values and exists so that
62
62
  calibration (fault class → real-bug predictiveness) can be computed later.
63
+ An optional fault-level `defendedBy` overrides the claim's declaration.
64
+ Presence matters: `[]` requests discovery, while absence inherits a non-empty
65
+ claim list or discovers when the claim has none.
63
66
 
64
67
  ### Evidence record
65
68
 
66
69
  The verdict plus everything it rests on: every baseline run, every probe run,
67
70
  the resolved defenders, which of them merely mock the subject, the input
68
71
  hashes that make verdict reuse sound, and the rank.
72
+ New records carry `defenders.selectionSource` (`fault`, `claim`, `discovery`)
73
+ alongside requested and resolved selections. Empty selections carry
74
+ `discovered: true`. Origin or selection changes invalidate reuse and status;
75
+ the field remains optional so new readers can validate legacy evidence.
76
+
77
+ Status cost reports include per-fault recorded durations and the selected
78
+ defenders. File-level entries list distinct `claimId/faultId` identities;
79
+ multiple confirmation runs for one fault do not constitute sharing.
69
80
 
70
81
  ### Verdict
71
82
 
@@ -78,7 +89,7 @@ Closed set of seven. Only `killed` is a pass:
78
89
  ```
79
90
  claims.json ──1:N──> claim ──1:N──> fault ──1:1──> evidence record
80
91
  │ │
81
- └── defendedBy ──> test files ─┘
92
+ └── default defendedBy ─> fault selection ─> test files
82
93
  │
83
94
  baseline.json ──suppresses by fingerprint────────────┘
84
95
  │
@@ -107,9 +118,11 @@ target file to importing tests for defender discovery.
107
118
  The contract is versioned by `schemaVersion` inside every document and by the
108
119
  `urn:claimspec:v1:` identifier on every schema.
109
120
 
110
- - **Additive change** (a new optional field): no migration; older readers
111
- ignore it, and `additionalProperties: false` means the field must be added to
112
- the schema in the same change that emits it.
121
+ - **Additive change** (a new optional field): new readers accept old documents
122
+ without migration. Older strict readers may reject the new field because
123
+ schemas use `additionalProperties: false`; upgrade those consumers before
124
+ asking them to validate new-format artifacts. Add the field to the schema
125
+ in the same change that emits it.
113
126
  - **Semantic change** (a new verdict, a new fault class, a changed rule): a
114
127
  spec change. The schema, `GATE-SEMANTICS.md`, the validator's semantic rules,
115
128
  a conformance example, a must-reject document and the known-answer fixture
@@ -125,4 +138,5 @@ The contract is versioned by `schemaVersion` inside every document and by the
125
138
 
126
139
  | Version | Date | Change | Author |
127
140
  |---|---|---|---|
141
+ | 1.1.0 | 2026-09-30 | Fault defender overrides, selection provenance and per-fault costs; clarify strict-reader compatibility | @raccioly |
128
142
  | 1.0.0 | 2026-09-18 | First canonical data model, written against v0.6.0 | @raccioly |
@@ -1,8 +1,8 @@
1
1
  # Requirements
2
2
 
3
- <!-- docguard:version 1.1.0 -->
3
+ <!-- docguard:version 1.2.0 -->
4
4
  <!-- docguard:status approved -->
5
- <!-- docguard:last-reviewed 2026-09-19 -->
5
+ <!-- docguard:last-reviewed 2026-09-30 -->
6
6
  <!-- docguard:owner @raccioly -->
7
7
  <!-- docguard:quality passive-voice off — requirements are written in the standard "the system shall ..." register, where the agent is the system throughout. -->
8
8
 
@@ -28,6 +28,7 @@
28
28
  | FR-14 | Serve the read-only operating loop over MCP so the loop survives a change of agent harness | P3 |
29
29
  | FR-15 | Validate every exact fault anchor and the syntax of supported in-memory replacements before starting a test runner (`claims --check-anchors`), without guessing repairs | P1 |
30
30
  | FR-16 | Preserve every repeated or comma-separated `probe --claim` selection in first-seen order, collapse duplicates, and report requested versus probed scope without allowing an empty selection to pass | P1 |
31
+ | FR-17 | Select defenders per fault: an explicit override takes precedence, an empty override requests discovery, and absence inherits the claim. Record the origin, invalidate stale/reused selections, report per-fault costs and warn on narrowing after a prior kill | P1 |
31
32
 
32
33
  ## Non-Functional Requirements
33
34
 
@@ -103,6 +104,7 @@ fails the pull request naming the unclaimed file, and `status` reports
103
104
  | FR-14 | `src/mcp/` | `test/mcp.test.mjs` |
104
105
  | FR-15 | `src/claims/anchors.mjs`, `src/commands/claims.mjs`, `src/status/status.mjs` | `test/claims-anchors.test.mjs`, `test/status.test.mjs`, `TG-ANCHOR-PREFLIGHT-FAILS-FAST` |
105
106
  | FR-16 | `src/cli.mjs`, `src/commands/probe.mjs`, `src/probe/probe.mjs`, `src/commands/scaffold.mjs`, `src/commands/admit.mjs` | `test/claim-selection.test.mjs`, `test/probe.fixture.test.mjs`, `TG-REPEATED-CLAIMS-ARE-ALL-PROBED` |
107
+ | FR-17 | `src/probe/attribution.mjs`, `src/probe/probe.mjs`, `src/commands/claims.mjs`, `src/status/status.mjs`, `src/probe/cost.mjs` | `test/fault-defenders.test.mjs`, `test/attribution.test.mjs`, `test/status.test.mjs`, `test/cost.test.mjs`, `TG-FAULT-DEFENDER-SELECTION`, `TG-FAULT-DEFENDER-ORCHESTRATION` |
106
108
  | NFR-01 | `src/init/init.mjs`, runner resolution | `TG-INIT-HOOK-NO-NETWORK`, `TG-README-HOOK-MATCHES-THE-CODE` (every surface, not only the README), `TG-GITIGNORE-ADVICE-IS-ONE-LIST` |
107
109
  | NFR-02 | `package.json` | `npm run test:install` in CI |
108
110
  | NFR-03 | `src/probe/inject.mjs`, `src/probe/probe.mjs` | `TG-DIRTY-DEFENDERS-REFUSED`, `TG-IGNORED-DIRTY-RECORDED`, `TG-IN-PLACE-STILL-RAISES-A-MISSING-TARGET`, `TG-CONTROL-RESTORES-THE-SUBJECT` |
@@ -116,5 +118,6 @@ fails the pull request naming the unclaimed file, and `status` reports
116
118
 
117
119
  | Version | Date | Change | Author |
118
120
  |---|---|---|---|
121
+ | 1.2.0 | 2026-09-30 | Approved fault-specific defender selection and attributable costs | @raccioly |
119
122
  | 1.1.0 | 2026-09-19 | Require lossless, visible partial claim selection | @raccioly |
120
123
  | 1.0.0 | 2026-09-18 | First canonical requirements, written against v0.6.0 | @raccioly |
@@ -1,8 +1,8 @@
1
1
  # Test Specification
2
2
 
3
- <!-- docguard:version 1.0.0 -->
3
+ <!-- docguard:version 1.1.0 -->
4
4
  <!-- docguard:status approved -->
5
- <!-- docguard:last-reviewed 2026-09-22 -->
5
+ <!-- docguard:last-reviewed 2026-09-30 -->
6
6
  <!-- docguard:owner @raccioly -->
7
7
  <!-- docguard:quality negation-load off — the governing rule is a prohibition (a test that would pass with the behaviour broken is not a test), and the verdict rules are defined by what does not count as detection. -->
8
8
 
@@ -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. The Playwright fixture carries its own dependencies: without `npm ci --prefix fixtures/known-answer-playwright` that file skips, and a skipped acceptance is not a passing one | 10–50 s each |
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 |
24
+ | Self-verification | `testguard.claims.json`, run by `npm run self:probe` | The tool's own invariants survive fault injection | Historical cold measurement: **20.2 min** in the v0.10.1 release job on 2026-09-19 at `70e9d61` (154 faults, 924 defender runs, 1210 s). PR CI restores previous evidence for reuse. Current committed ceilings in `testguard.cost-budget.json`: 2250 s total and 100 s per claim; both gate CI, and neither changes for fault-specific selection |
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
 
@@ -31,7 +31,7 @@ the test that defends it, and CI fails if any injected fault survives.
31
31
  `durationMs` already on every run in an evidence document. They re-measure
32
32
  nothing.
33
33
 
34
- A probe's wall clock is, per claim,
34
+ A probe's historical wall-clock model, before fault-specific selection, was per claim:
35
35
  `(baseline runs + faults × --confirm) × the cost of that claim's defender
36
36
  SET`. One slow acceptance test named by several claims is therefore paid for
37
37
  once per claim, which is what made the gate take 24 minutes: a single 49.5 s
@@ -39,8 +39,15 @@ fixture test defended five claims. Those claims now point at pure functions
39
39
  with unit-test defenders (issue #67), and the gate measures **9.6 minutes**
40
40
  from scratch, serially, for 68 faults.
41
41
 
42
+ Fault-specific selection runs each fault's selected set. Baselines are cached
43
+ by that set, not claim identity; per-fault cost records expose which files
44
+ actually ran. Repeated confirmations alone are not shared-defender usage.
45
+ `test/fault-defenders.test.mjs` covers inheritance, overrides, explicit-empty
46
+ discovery, origin-sensitive reuse, dirty-input refusal, runner ownership,
47
+ narrowing warnings and admission through the actual selected set.
48
+
42
49
  Per-file cost figures are an **upper bound, not a share**: a run executes a
43
- claim's whole defender set at once, so the runner never attributes time to
50
+ fault's whole selected defender set at once, so the runner never attributes time to
44
51
  one file. They overlap and do not sum to the total.
45
52
 
46
53
  ## Coverage Rules
@@ -69,7 +76,7 @@ What is gated instead:
69
76
  | `src/probe/classify.mjs` | `test/classify.test.mjs`, `test/negative-control.test.mjs` |
70
77
  | `src/probe/inject.mjs` | `test/inject.test.mjs` |
71
78
  | `src/claims/anchors.mjs` | `test/claims-anchors.test.mjs` |
72
- | `src/probe/probe.mjs` | `test/probe.fixture.test.mjs`, `test/probe-preconditions.test.mjs`, `test/probe-error.test.mjs` |
79
+ | `src/probe/probe.mjs` | `test/probe.fixture.test.mjs`, `test/probe-preconditions.test.mjs`, `test/probe-error.test.mjs`, `test/fault-defenders.test.mjs` |
73
80
  | `src/probe/discover.mjs`, `src/probe/mocks.mjs` | `test/discover.test.mjs`, `test/mocks.test.mjs` |
74
81
  | `src/probe/rank.mjs` | `test/rank-aliases.test.mjs`, `test/discover.test.mjs` |
75
82
  | `src/probe/runners/` | `test/runner-vitest.test.mjs`, `test/runner-command.test.mjs`, `test/runner-playwright.test.mjs` |
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "testguard-cli",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "testguard-cli",
9
- "version": "0.15.1",
9
+ "version": "0.16.0",
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.15.1",
3
+ "version": "0.16.0",
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.15.1/packaging/gitlab/testguard.gitlab-ci.yml'
6
+ # - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.16.0/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.15.1"
32
+ default: "0.16.0"
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.15.1.tgz"
19
- sha256 "357f37905e99bf2d1a7487374bc2fc2b97983fd8d355b2e77d72e03fb0f2f8c7"
18
+ url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.16.0.tgz"
19
+ sha256 "f352a8092263cbc949d41af3453e57e90882eebceb04658c1848564246ee18d7"
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.15.1"
7
+ version = "0.16.0"
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" }
@@ -1,5 +1,26 @@
1
1
  # Gate semantics
2
2
 
3
+ ## Fault-specific defender selection
4
+
5
+ A fault's `defendedBy`, when present, overrides its claim's declaration.
6
+ An explicit empty fault list requests discovery and never falls back to the
7
+ claim. Without a fault override, a non-empty claim list is inherited; otherwise
8
+ defenders are discovered. New evidence records `defenders.selectionSource` as
9
+ `fault`, `claim`, or `discovery`, alongside the actual requested and resolved
10
+ sets. Empty requests use discovery and carry `discovered: true`, including an
11
+ explicit empty fault override. Non-empty requests are never marked discovered.
12
+ Dirty-input preflight, runner ownership, mock-awareness, input hashing and
13
+ baseline caching all operate on each fault's selected defender set.
14
+
15
+ Changing requested globs, resolved files or selection origin invalidates verdict
16
+ reuse. Legacy evidence remains readable but cannot authorize a new fault-level
17
+ override. A strict resolved subset after a previous kill is reported by `claims`
18
+ as a narrowing warning and requires re-probing; it is not a new verdict.
19
+ Cost reports include per-fault recorded run durations and selection origin.
20
+ Shared defenders identify the actual fault records that selected each file,
21
+ including multiple faults within one claim. File costs remain overlapping upper
22
+ bounds, not additive shares. Existing total/per-claim budget ceilings are unchanged.
23
+
3
24
  How a Guard-spec tool decides whether CI goes red. Shared by every tool that
4
25
  adopts the spec; each tool may add stricter rules, never looser ones.
5
26
 
@@ -8,11 +8,55 @@ import { dirname, join, basename } from 'node:path';
8
8
  import { validate, KINDS } from '../lib/validate.mjs';
9
9
  import { fingerprint } from '../lib/fingerprint.mjs';
10
10
  import { probit, zCandidates, wilsonInterval, roundTo } from '../lib/wilson.mjs';
11
+ import { costReport } from '../../src/probe/cost.mjs';
11
12
 
12
13
  const here = dirname(fileURLToPath(import.meta.url));
13
14
  const load = (dir, f) => JSON.parse(readFileSync(join(here, dir, f), 'utf8'));
14
15
 
15
16
  describe('claimspec v1 — conformance', () => {
17
+ it('admits per-fault cost output in status and rejects invalid selection origins', () => {
18
+ const status = load('examples', 'status.json');
19
+ const evidence = load('examples', 'evidence.json');
20
+ status.cost = costReport(evidence.records);
21
+ expect(validate('status', status).errors).toEqual([]);
22
+ status.cost.faults[0].selectionSource = 'fault';
23
+ expect(validate('status', status).errors).toEqual([]);
24
+ status.cost.faults[0].selectionSource = 'guessed';
25
+ expect(validate('status', status).ok).toBe(false);
26
+ delete status.cost.faults;
27
+ for (const d of [...status.cost.defenders, ...status.cost.sharedDefenders]) delete d.faults;
28
+ expect(validate('status', status).errors).toEqual([]);
29
+ });
30
+ it('admits fault overrides and rejects duplicate or malformed defender declarations', () => {
31
+ const claims = load('examples', 'claims.json');
32
+ const fault = claims.claims[0].faults[0];
33
+ fault.defendedBy = ['test/unit.test.mjs'];
34
+ expect(validate('claims', claims).errors).toEqual([]);
35
+ fault.defendedBy = [];
36
+ expect(validate('claims', claims).errors).toEqual([]);
37
+ fault.defendedBy = ['same', 'same'];
38
+ expect(validate('claims', claims).ok).toBe(false);
39
+ fault.defendedBy = [42];
40
+ expect(validate('claims', claims).ok).toBe(false);
41
+ });
42
+
43
+ it('validates defender selection origin without rejecting legacy evidence', () => {
44
+ const evidence = load('examples', 'evidence.json');
45
+ const d = evidence.records[0].defenders;
46
+ delete d.selectionSource;
47
+ expect(validate('evidence', evidence).errors).toEqual([]);
48
+ d.selectionSource = 'discovery';
49
+ d.discovered = false;
50
+ expect(validate('evidence', evidence).ok).toBe(false);
51
+ d.selectionSource = 'claim'; d.requested = []; delete d.discovered;
52
+ expect(validate('evidence', evidence).ok).toBe(false);
53
+ d.selectionSource = 'fault'; d.discovered = true;
54
+ expect(validate('evidence', evidence).errors).toEqual([]);
55
+ d.requested = ['test/unit.test.mjs'];
56
+ expect(validate('evidence', evidence).ok).toBe(false);
57
+ delete d.discovered;
58
+ expect(validate('evidence', evidence).errors).toEqual([]);
59
+ });
16
60
  it('accounts for descendant-project delegation without calling it coverage or double-counting it', () => {
17
61
  const gate = load('examples', 'gate.json');
18
62
  gate.nested = [{ project: 'backend', files: ['backend/src/guard.mjs'] }];
@@ -146,6 +146,15 @@ const semantic = {
146
146
  if (r.detail.undeclaredKillers && r.detail.reason !== 'killed-by-undeclared-tests') {
147
147
  errors.push({ path: `${p}/detail/undeclaredKillers`, message: 'undeclaredKillers is only meaningful with reason killed-by-undeclared-tests' });
148
148
  }
149
+ if (r.defenders.selectionSource) {
150
+ const empty = r.defenders.requested.length === 0;
151
+ if ((r.defenders.selectionSource === 'discovery' && !empty)
152
+ || (r.defenders.selectionSource === 'claim' && empty)
153
+ || (empty && r.defenders.discovered !== true)
154
+ || (!empty && r.defenders.discovered === true)) {
155
+ errors.push({ path: `${p}/defenders/selectionSource`, message: 'defender selection origin, requested set and discovery flag disagree' });
156
+ }
157
+ }
149
158
  if (r.defenders.byRunner) {
150
159
  const resolved = new Set(r.defenders.resolved);
151
160
  for (const [runner, files] of Object.entries(r.defenders.byRunner)) for (const f of files) if (!resolved.has(f)) errors.push({ path: `${p}/defenders/byRunner/${runner}`, message: `${f} ran under ${runner} but is not a resolved defender` });