testguard-cli 0.14.2__tar.gz → 0.15.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 (352) hide show
  1. testguard_cli-0.15.1/.github/scripts/install-smoke.mjs +83 -0
  2. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/CHANGELOG.md +71 -1
  3. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/PKG-INFO +31 -5
  4. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/README.md +30 -4
  5. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/action.yml +6 -1
  6. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/docs/testguard-explained.html +1 -1
  7. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/docs/testguard-explained.pdf +0 -0
  8. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/package-lock.json +2 -2
  9. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/package.json +1 -1
  10. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/packaging/gitlab/testguard.gitlab-ci.yml +2 -2
  11. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/packaging/homebrew/testguard.rb +2 -2
  12. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/pyproject.toml +1 -1
  13. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/GATE-SEMANTICS.md +99 -8
  14. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/README.md +7 -0
  15. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/evidence.json +3 -1
  16. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/schemas.test.mjs +20 -0
  17. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/lib/validate.mjs +26 -1
  18. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/schemas/common.schema.json +10 -0
  19. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/schemas/evidence.schema.json +14 -0
  20. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/schemas/gate.schema.json +1 -0
  21. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/schemas/status.schema.json +2 -1
  22. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/cli.mjs +34 -4
  23. testguard_cli-0.15.1/src/command-budget.mjs +52 -0
  24. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/commands/probe.mjs +22 -0
  25. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/commands/replay.mjs +14 -1
  26. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/commands/sweep.mjs +15 -6
  27. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/gate/changed.mjs +40 -3
  28. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/git.mjs +4 -2
  29. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/init/templates/SKILL.md +1 -1
  30. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/attribution.mjs +2 -0
  31. testguard_cli-0.15.1/src/probe/discover.mjs +65 -0
  32. testguard_cli-0.15.1/src/probe/imports.mjs +785 -0
  33. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/mocks.mjs +13 -4
  34. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/probe.mjs +177 -20
  35. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/rank.mjs +28 -5
  36. testguard_cli-0.15.1/src/probe/runners/discovery.mjs +636 -0
  37. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/runners/index.mjs +51 -19
  38. testguard_cli-0.15.1/src/probe/runners/jest.mjs +52 -0
  39. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/runners/playwright.mjs +58 -2
  40. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/runners/python.mjs +7 -7
  41. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/runners/shared.mjs +71 -7
  42. testguard_cli-0.15.1/src/probe/runners/vitest.mjs +41 -0
  43. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/replay/replay.mjs +108 -13
  44. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/status/status.mjs +6 -0
  45. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/status/surface.mjs +3 -2
  46. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/sweep/sweep.mjs +5 -0
  47. testguard_cli-0.15.1/test/admit-decision.test.mjs +18 -0
  48. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/attribution.test.mjs +20 -0
  49. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/claim-selection.test.mjs +52 -0
  50. testguard_cli-0.15.1/test/command-budget-write-boundaries.test.mjs +67 -0
  51. testguard_cli-0.15.1/test/command-budget.test.mjs +40 -0
  52. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/discover.test.mjs +41 -0
  53. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/gate.test.mjs +38 -2
  54. testguard_cli-0.15.1/test/imports.test.mjs +321 -0
  55. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/mocks.test.mjs +14 -0
  56. testguard_cli-0.15.1/test/probe-dirty-paths.test.mjs +45 -0
  57. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/probe-error.test.mjs +38 -12
  58. testguard_cli-0.15.1/test/probe-native-manifest.test.mjs +78 -0
  59. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/probe-preconditions.test.mjs +4 -4
  60. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/rank-aliases.test.mjs +28 -1
  61. testguard_cli-0.15.1/test/replay-mixed-runners.test.mjs +116 -0
  62. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/runner-argv.test.mjs +9 -6
  63. testguard_cli-0.15.1/test/runner-discovery.test.mjs +327 -0
  64. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/runner-python.test.mjs +45 -1
  65. testguard_cli-0.15.1/test/runner-selection-blind.test.mjs +127 -0
  66. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/runner-vitest.test.mjs +98 -1
  67. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/testguard.claims.json +587 -15
  68. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/testguard.cost-budget.json +17 -6
  69. testguard_cli-0.14.2/.github/scripts/install-smoke.mjs +0 -41
  70. testguard_cli-0.14.2/src/probe/discover.mjs +0 -29
  71. testguard_cli-0.14.2/src/probe/runners/jest.mjs +0 -21
  72. testguard_cli-0.14.2/src/probe/runners/vitest.mjs +0 -20
  73. testguard_cli-0.14.2/test/runner-selection-blind.test.mjs +0 -77
  74. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.agents/skills/testguard/SKILL.md +0 -0
  75. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.docguard.json +0 -0
  76. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.docguardignore +0 -0
  77. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.gitattributes +0 -0
  78. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  79. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  80. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  81. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.github/dependabot.yml +0 -0
  82. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.github/scripts/approve-held-runs.sh +0 -0
  83. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.github/scripts/build-one-pager.mjs +0 -0
  84. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.github/scripts/check-cost-budget.mjs +0 -0
  85. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.github/scripts/check-python-fixture.mjs +0 -0
  86. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.github/scripts/publish-homebrew-tap.sh +0 -0
  87. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.github/scripts/sync-release-version.mjs +0 -0
  88. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.github/workflows/auto-merge.yml +0 -0
  89. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.github/workflows/ci.yml +0 -0
  90. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.github/workflows/release.yml +0 -0
  91. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.github/workflows/scheduled-release.yml +0 -0
  92. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.github/workflows/supply-chain.yml +0 -0
  93. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.gitignore +0 -0
  94. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.npmignore +0 -0
  95. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.npmrc +0 -0
  96. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.pre-commit-config.yaml +0 -0
  97. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/.pre-commit-hooks.yaml +0 -0
  98. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/AGENTS.md +0 -0
  99. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/CLAUDE.md +0 -0
  100. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/CODE_OF_CONDUCT.md +0 -0
  101. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/CONTRIBUTING.md +0 -0
  102. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/DRIFT-LOG.md +0 -0
  103. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/LICENSE +0 -0
  104. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/PRIVACY.md +0 -0
  105. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/SECURITY.md +0 -0
  106. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/SUPPORT.md +0 -0
  107. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/bench/README.md +0 -0
  108. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/cli/testguard.mjs +0 -0
  109. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/docs-canonical/ARCHITECTURE.md +0 -0
  110. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/docs-canonical/DATA-MODEL.md +0 -0
  111. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/docs-canonical/ENVIRONMENT.md +0 -0
  112. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/docs-canonical/PRIOR-ART.md +0 -0
  113. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/docs-canonical/REQUIREMENTS.md +0 -0
  114. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/docs-canonical/SECURITY.md +0 -0
  115. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/docs-canonical/TEST-SPEC.md +0 -0
  116. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer/.gitignore +0 -0
  117. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer/README.md +0 -0
  118. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer/expected.json +0 -0
  119. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer/package.json +0 -0
  120. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer/src/export.mjs +0 -0
  121. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer/src/redact.mjs +0 -0
  122. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
  123. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer/test/flaky.test.mjs +0 -0
  124. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer/test/redact.test.mjs +0 -0
  125. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer/testguard.claims.json +0 -0
  126. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer/vitest.config.mjs +0 -0
  127. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-jest/.gitignore +0 -0
  128. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-jest/README.md +0 -0
  129. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-jest/expected.json +0 -0
  130. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-jest/jest.config.js +0 -0
  131. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-jest/package.json +0 -0
  132. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-jest/src/export.js +0 -0
  133. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-jest/src/redact.js +0 -0
  134. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
  135. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
  136. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-jest/test/redact.test.js +0 -0
  137. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-jest/testguard.claims.json +0 -0
  138. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/.gitignore +0 -0
  139. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/README.md +0 -0
  140. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
  141. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
  142. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/expected.json +0 -0
  143. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/package-lock.json +0 -0
  144. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/package.json +0 -0
  145. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
  146. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
  147. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
  148. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
  149. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
  150. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-python/.gitignore +0 -0
  151. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-python/README.md +0 -0
  152. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-python/demo/__init__.py +0 -0
  153. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-python/demo/export.py +0 -0
  154. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-python/demo/orphan.py +0 -0
  155. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-python/demo/pipeline.py +0 -0
  156. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-python/demo/redact.py +0 -0
  157. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-python/expected.json +0 -0
  158. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-python/testguard.claims.json +0 -0
  159. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
  160. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
  161. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-python/tests/test_patched.py +0 -0
  162. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/fixtures/known-answer-python/tests/test_redact.py +0 -0
  163. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/baseline.json +0 -0
  164. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/brief.json +0 -0
  165. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/calibration-websec.json +0 -0
  166. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/calibration.json +0 -0
  167. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/claims-methods.json +0 -0
  168. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/claims.json +0 -0
  169. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
  170. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/evidence-negative-control.json +0 -0
  171. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/evidence-persistence-signal.json +0 -0
  172. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/evidence-provisional.json +0 -0
  173. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/evidence-python.json +0 -0
  174. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/evidence-scan-method.json +0 -0
  175. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/gate.json +0 -0
  176. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/ignore.json +0 -0
  177. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/replay.json +0 -0
  178. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/examples/status.json +0 -0
  179. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
  180. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/baseline.zero-count.json +0 -0
  181. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
  182. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
  183. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +0 -0
  184. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +0 -0
  185. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.ci-not-wilson.json +0 -0
  186. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +0 -0
  187. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.fallback-without-basis.json +0 -0
  188. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.p-not-positives-over-n.json +0 -0
  189. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
  190. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
  191. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.absolute-path.json +0 -0
  192. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
  193. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
  194. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +0 -0
  195. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
  196. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.noop-fault.json +0 -0
  197. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
  198. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
  199. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
  200. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
  201. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
  202. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
  203. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
  204. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
  205. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
  206. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
  207. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
  208. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
  209. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
  210. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
  211. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +0 -0
  212. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
  213. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
  214. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.persistence-signal-not-a-defender.json +0 -0
  215. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.persistence-signal-on-a-non-survivor.json +0 -0
  216. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.persistence-signal-without-reason.json +0 -0
  217. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
  218. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
  219. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
  220. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
  221. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
  222. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
  223. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
  224. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
  225. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
  226. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
  227. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
  228. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
  229. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
  230. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
  231. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/ignore.short-reason.json +0 -0
  232. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
  233. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
  234. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/replay.nocover-when-no-test-file-remained.json +0 -0
  235. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/status.claim-without-file.json +0 -0
  236. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
  237. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/status.invalid-anchor-hidden.json +0 -0
  238. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/status.partial-scope-counts-disagree.json +0 -0
  239. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/status.surface-counts-disagree.json +0 -0
  240. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
  241. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
  242. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/lib/fingerprint.mjs +0 -0
  243. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/lib/wilson.mjs +0 -0
  244. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/schemas/baseline.schema.json +0 -0
  245. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/schemas/brief.schema.json +0 -0
  246. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/schemas/calibration.schema.json +0 -0
  247. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/schemas/claims.schema.json +0 -0
  248. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/schemas/concerns.schema.json +0 -0
  249. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/schemas/ignore.schema.json +0 -0
  250. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/schemas/replay.schema.json +0 -0
  251. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/spec/schemas/sweep.schema.json +0 -0
  252. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/admit/admit.mjs +0 -0
  253. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/baseline/baseline.mjs +0 -0
  254. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/brief/brief.mjs +0 -0
  255. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/claims/anchors.mjs +0 -0
  256. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/claims/annotations.mjs +0 -0
  257. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/claims/load.mjs +0 -0
  258. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/claims/removed.mjs +0 -0
  259. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/commands/admit.mjs +0 -0
  260. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/commands/baseline.mjs +0 -0
  261. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/commands/brief.mjs +0 -0
  262. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/commands/claims.mjs +0 -0
  263. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/commands/concerns.mjs +0 -0
  264. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/commands/gate.mjs +0 -0
  265. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/commands/init.mjs +0 -0
  266. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/commands/mcp.mjs +0 -0
  267. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/commands/scaffold.mjs +0 -0
  268. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/commands/status.mjs +0 -0
  269. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/evidence/writer.mjs +0 -0
  270. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/init/init.mjs +0 -0
  271. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/mcp/server.mjs +0 -0
  272. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/mcp/tools.mjs +0 -0
  273. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/classify.mjs +0 -0
  274. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/contention.mjs +0 -0
  275. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/cost.mjs +0 -0
  276. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/independence.mjs +0 -0
  277. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/inject.mjs +0 -0
  278. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/progress.mjs +0 -0
  279. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/pyimports.mjs +0 -0
  280. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
  281. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/runners/python/_testguard_report.py +0 -0
  282. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
  283. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/probe/worktree.mjs +0 -0
  284. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/render.mjs +0 -0
  285. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/replay/label.mjs +0 -0
  286. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/scaffold/producers.mjs +0 -0
  287. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/scaffold/producers.python.mjs +0 -0
  288. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/scaffold/scaffold.mjs +0 -0
  289. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/supply/concerns.mjs +0 -0
  290. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/supply/feedback.mjs +0 -0
  291. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/supply/persistence.mjs +0 -0
  292. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/supply/savepath.mjs +0 -0
  293. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/supply/select.mjs +0 -0
  294. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/util/glob.mjs +0 -0
  295. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/src/util/hash.mjs +0 -0
  296. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/admit.test.mjs +0 -0
  297. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/annotations.test.mjs +0 -0
  298. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/baseline.test.mjs +0 -0
  299. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/brief.test.mjs +0 -0
  300. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/calibration.test.mjs +0 -0
  301. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/claims-anchors.test.mjs +0 -0
  302. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/claims-since.test.mjs +0 -0
  303. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/classify.test.mjs +0 -0
  304. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/contention.test.mjs +0 -0
  305. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/cost.test.mjs +0 -0
  306. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/evidence-persistence-signal.test.mjs +0 -0
  307. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/gitlab-template.test.mjs +0 -0
  308. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/glob.test.mjs +0 -0
  309. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/independence.test.mjs +0 -0
  310. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/init.test.mjs +0 -0
  311. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/inject.test.mjs +0 -0
  312. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/label.test.mjs +0 -0
  313. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/mcp.test.mjs +0 -0
  314. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/negative-control.test.mjs +0 -0
  315. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/one-pager.test.mjs +0 -0
  316. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/probe-persistence-signal.test.mjs +0 -0
  317. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/probe.fixture.test.mjs +0 -0
  318. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/probe.jest.test.mjs +0 -0
  319. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/probe.playwright.test.mjs +0 -0
  320. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/probe.python.test.mjs +0 -0
  321. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/progress.test.mjs +0 -0
  322. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/pyimports.test.mjs +0 -0
  323. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/release-approval.test.mjs +0 -0
  324. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/release-sync.test.mjs +0 -0
  325. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/replay-no-test-file-left.test.mjs +0 -0
  326. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/replay-python-defenders.test.mjs +0 -0
  327. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/replay-verdict.test.mjs +0 -0
  328. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/replay.test.mjs +0 -0
  329. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/runner-command.test.mjs +0 -0
  330. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/runner-playwright.test.mjs +0 -0
  331. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/samples/playwright-report.json +0 -0
  332. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/samples/python-report-collect-error.json +0 -0
  333. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/samples/python-report.json +0 -0
  334. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/scaffold.python.test.mjs +0 -0
  335. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/scaffold.test.mjs +0 -0
  336. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/snapshot.test.mjs +0 -0
  337. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/status.test.mjs +0 -0
  338. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/supply-concerns.test.mjs +0 -0
  339. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/supply-feedback.test.mjs +0 -0
  340. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/supply-persistence.test.mjs +0 -0
  341. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/supply-savepath.test.mjs +0 -0
  342. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/supply-select.test.mjs +0 -0
  343. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/surface.test.mjs +0 -0
  344. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/sweep.fixture.test.mjs +0 -0
  345. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/sweep.test.mjs +0 -0
  346. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/vitest-config.test.mjs +0 -0
  347. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/worktree.test.mjs +0 -0
  348. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/test/writer.test.mjs +0 -0
  349. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/testguard.ignore.json +0 -0
  350. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/testguard_cli/__init__.py +0 -0
  351. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/testguard_cli/wrapper.py +0 -0
  352. {testguard_cli-0.14.2 → testguard_cli-0.15.1}/vitest.config.mjs +0 -0
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Prove the package works AS INSTALLED, not as checked out: pack it, install
4
+ * the tarball into a scratch project with production dependencies only, and
5
+ * run the CLI from there. This is the gate that would have caught 0.1.0,
6
+ * which shipped with its schema validator's dependency declared as a
7
+ * devDependency and crashed on startup for every npm/npx/pip/brew user.
8
+ */
9
+ import { execFileSync } from 'node:child_process';
10
+ import { mkdtempSync, mkdirSync, rmSync, cpSync, readdirSync, writeFileSync, existsSync } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { join, resolve } from 'node:path';
13
+
14
+ const root = resolve(process.argv[1], '..', '..', '..');
15
+ const run = (cmd, args, cwd) => execFileSync(cmd, args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'] });
16
+ const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
17
+
18
+ const scratch = mkdtempSync(join(tmpdir(), 'testguard-install-smoke-'));
19
+ try {
20
+ const packDir = join(scratch, 'pack');
21
+ mkdirSync(packDir);
22
+ run(npm, ['pack', '--pack-destination', packDir, '--ignore-scripts'], root);
23
+ const tgz = join(packDir, readdirSync(packDir).find((f) => f.endsWith('.tgz')));
24
+
25
+ const consumer = join(scratch, 'consumer');
26
+ cpSync(join(root, 'fixtures', 'known-answer'), consumer, { recursive: true, filter: (s) => !/node_modules|\.flake-counter|\.testguard/.test(s) });
27
+ run(npm, ['init', '-y'], consumer);
28
+ run(npm, ['install', '--omit=dev', '--ignore-scripts', '--no-audit', '--no-fund', tgz], consumer);
29
+
30
+ const bin = join(consumer, 'node_modules', '.bin', process.platform === 'win32' ? 'testguard.cmd' : 'testguard');
31
+ const version = run(bin, ['--version'], consumer).trim();
32
+ const emptyProject = join(scratch, 'empty-adoption');
33
+ mkdirSync(emptyProject);
34
+ writeFileSync(join(emptyProject, 'testguard.claims.json'), JSON.stringify({ schemaVersion: 1, claims: [] }));
35
+ const emptyStatus = JSON.parse(run(bin, ['probe', emptyProject, '--allow-empty', '--json'], consumer));
36
+ if (emptyStatus.state !== 'no-claims' || emptyStatus.run || existsSync(join(emptyProject, '.testguard', 'evidence.json'))) throw new Error('installed empty-adoption probe invented verification evidence');
37
+ const replayHelp = run(bin, ['replay', '--help'], consumer);
38
+ if (!replayHelp.includes('--since') || replayHelp.includes('--node-modules')) throw new Error('installed command help is not command-specific');
39
+ writeFileSync(join(emptyProject, 'vite.config.mjs'), "export default { base: '/', plugins: [{ configureServer(s) { s.middlewares.use('/__rooms', () => {}); const parts = '/__room'.split('/'); } }] };\n");
40
+ const installedDiscovery = join(consumer, 'node_modules', 'testguard-cli', 'src', 'probe', 'runners', 'discovery.mjs');
41
+ run(process.execPath, ['--input-type=module', '-e',
42
+ "import { pathToFileURL } from 'node:url'; const { hashDiscoveryConfigs } = await import(pathToFileURL(process.argv[1])); hashDiscoveryConfigs(process.argv[2], ['vite.config.mjs']);",
43
+ installedDiscovery, emptyProject,
44
+ ], consumer);
45
+ const installedRunner = join(consumer, 'node_modules', 'testguard-cli', 'src', 'probe', 'runners', 'shared.mjs');
46
+ run(process.execPath, ['--input-type=module', '-e',
47
+ `import { pathToFileURL } from 'node:url'; const { parseReport } = await import(pathToFileURL(process.argv[1]));
48
+ const result = parseReport({ success: false, numTotalTests: 2, numFailedTests: 2, testResults: [{ name: 'test.mjs', status: 'failed', assertionResults: [
49
+ { status: 'failed', failureMessages: ['AssertionError: expected /specify timed out/'] },
50
+ { status: 'failed', failureMessages: ['Error: Test timed out in 1000ms.'] },
51
+ ] }] }, 1);
52
+ if (result.timeouts !== 1 || result.run.assertionFailures !== 1) throw new Error('installed runner confuses assertion text with timeout identity');`,
53
+ installedRunner,
54
+ ], consumer);
55
+ const claims = run(bin, ['claims', '.'], consumer);
56
+ const draft = JSON.parse(run(bin, ['scaffold', 'src/redact.mjs', '--json'], consumer));
57
+ const installedValidator = join(consumer, 'node_modules', 'testguard-cli', 'spec', 'lib', 'validate.mjs');
58
+ const candidateEvidence = join(root, 'spec', 'conformance', 'examples', 'evidence.json');
59
+ const validatesCandidateEvidence = run(process.execPath, [
60
+ '--input-type=module',
61
+ '-e',
62
+ `import { pathToFileURL } from 'node:url'; import { readFileSync } from 'node:fs';
63
+ const { validate } = await import(pathToFileURL(process.argv[1]));
64
+ const result = validate('evidence', JSON.parse(readFileSync(process.argv[2], 'utf8')));
65
+ if (!result.ok) throw new Error(result.errors.map((error) => \`${'${error.path}: ${error.message}'}\`).join('; '));
66
+ const gate = JSON.parse(readFileSync(process.argv[3], 'utf8'));
67
+ gate.nested = [{ project: 'backend', files: ['backend/src/guard.mjs'] }]; gate.changed++;
68
+ const nestedResult = validate('gate', gate);
69
+ if (!nestedResult.ok) throw new Error('installed package rejected the nested-project gate contract');
70
+ process.stdout.write('ok');`,
71
+ installedValidator,
72
+ candidateEvidence,
73
+ join(root, 'spec', 'conformance', 'examples', 'gate.json'),
74
+ ], consumer).trim();
75
+ if (!draft.claims?.length) throw new Error('scaffold produced no claims from the installed tarball');
76
+ if (!/^\d+\.\d+\.\d+/.test(version)) throw new Error(`unexpected --version output: ${version}`);
77
+ if (!/^\d+ claims in /.test(claims)) throw new Error(`claims did not list the fixture:\n${claims}`);
78
+ if (validatesCandidateEvidence !== 'ok') throw new Error('installed package did not validate candidate-format evidence');
79
+ const deps = Object.keys(JSON.parse(run(npm, ['ls', '--omit=dev', '--json', '--depth=0'], consumer)).dependencies ?? {});
80
+ console.log(`install smoke OK — testguard ${version} runs from the installed tarball; consumer deps: ${deps.join(', ')}`);
81
+ } finally {
82
+ rmSync(scratch, { recursive: true, force: true });
83
+ }
@@ -7,6 +7,77 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.15.1] - 2026-09-30
11
+
12
+ Automated weekly release — everything merged since `v0.15.0`.
13
+
14
+ ### Changed
15
+
16
+ - fix: runner timeout identity and reported adoption regressions (#180)
17
+ - chore(homebrew): sha256 for v0.15.0 (#177)
18
+
19
+
20
+ ### Fixed
21
+
22
+ - Assertion messages quoting "timed out" no longer become runner timeouts.
23
+ Native timeout headers and structured timeout failures remain non-detections
24
+ (#179).
25
+ - Configuration dependency hashing no longer rejects harmless absolute-looking
26
+ strings such as Vite URL bases, middleware routes, or `split('/')`. Existing
27
+ outside-project file dependencies and symlinks still fail closed (#178).
28
+ - Discovery failures no longer assert that dependencies are missing when the
29
+ runner resolved successfully but its configuration could not be discovered.
30
+ - Each subcommand's `--help` now shows its own options and an example; global
31
+ `--help` exits successfully and points to command-specific help (#120).
32
+ - Parent gates, status change reports, and claimed-surface counts respect valid
33
+ nested TestGuard project boundaries. Delegated files are explicitly reported
34
+ and require a separate child gate; invalid or symlinked markers fail (#150).
35
+
36
+ ### Added
37
+
38
+ - Explicit `probe --allow-empty` adoption mode, also available as an opt-in
39
+ GitHub Action input. A valid empty claims file skips verification without
40
+ writing evidence; JSON keeps the `no-claims` state. Default empty probes,
41
+ invalid claims, and explicit claim selections still fail (#176).
42
+
43
+ ## [0.15.0] - 2026-09-29
44
+
45
+ Automated weekly release — everything merged since `v0.14.2`.
46
+
47
+ ### Changed
48
+
49
+ - feat: harden discovery, replay, and command budgets (#174)
50
+ - chore(homebrew): sha256 for v0.14.2 (#173)
51
+
52
+
53
+ ### Added
54
+
55
+ - Runner-native test discovery for Vitest, Jest, and Playwright, with bounded
56
+ output, path validation, immutable universe manifests, exact mixed-runner
57
+ routing, and configuration-closure hashes (including workspace configs,
58
+ package helpers, setup files, and Python collection hooks) that prevent stale
59
+ evidence reuse.
60
+ - Symbol-aware JavaScript/TypeScript defender discovery through named,
61
+ default, namespace, CommonJS, and barrel re-exports. Ambiguity and bounded
62
+ resolver exhaustion fail closed as `defender-discovery-indeterminate`.
63
+ - `probe`, `sweep`, and `replay` accept `--command-budget <ms>` as a
64
+ cooperative measurement deadline distinct from the existing per-run
65
+ `--budget`. It caps asynchronous children and is checked at stage and write
66
+ boundaries; synchronous setup may overrun but cannot publish a partial
67
+ result, so an unattempted suffix can never be reported as clean.
68
+
69
+ ### Fixed
70
+
71
+ - Runner timeouts now hard-kill the original process group and descendants
72
+ still attributable at timeout. Diagnostics explicitly say cleanup is
73
+ unverified because a previously reparented daemon requires external OS or
74
+ container containment.
75
+ - Generated CommonJS export mutations and unresolved symbol/configuration
76
+ paths fail closed instead of allowing a false `nocover` result.
77
+ - Dirty-tree preflight now resolves the same full primary-plus-owned runner
78
+ universe as measurement, including custom Playwright names and imported
79
+ configuration helpers; discovery failure blocks rather than hiding edits.
80
+
10
81
  ## [0.14.2] - 2026-09-28
11
82
 
12
83
  Automated weekly release — everything merged since `v0.14.1`.
@@ -15,7 +86,6 @@ Automated weekly release — everything merged since `v0.14.1`.
15
86
 
16
87
  - chore(homebrew): sha256 for v0.14.1 (#171)
17
88
 
18
-
19
89
  ## [0.14.1] - 2026-09-27
20
90
 
21
91
  Automated weekly release — everything merged since `v0.14.0`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: testguard-cli
3
- Version: 0.14.2
3
+ Version: 0.15.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
@@ -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.14.2` — see [`action.yml`](./action.yml) |
154
+ | GitHub Action | `uses: raccioly/testguard@v0.15.1` — 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.14.2/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
156
+ | GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.15.1/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
@@ -431,6 +431,20 @@ is missing. TestGuard itself still makes no network calls.
431
431
 
432
432
  ### Starting from zero claims
433
433
 
434
+ During gradual adoption, use `testguard probe --allow-empty` to skip a valid
435
+ empty claims file without blocking a push. It explicitly reports that no
436
+ verification was performed and writes no evidence; `--json` retains the
437
+ `no-claims` state. Missing or invalid claims files still fail. Run `gate`
438
+ separately to enforce changed-file coverage. For the pre-commit probe hook,
439
+ set `args: [--allow-empty]`; for the GitHub Action, set `allow-empty: 'true'`.
440
+ The default probe still exits 2 when no claims exist.
441
+
442
+ In a monorepo, a valid nested `testguard.claims.json` marks a separate project.
443
+ The parent gate reports its changed files as delegated and does not count them
444
+ as parent coverage. Run `gate` in each child project as well; a passing parent
445
+ gate does not establish that child coverage passed. Invalid child claims fail
446
+ evaluation instead of hiding files.
447
+
434
448
  `gate` names the changed files that carry no claim, and stops there —
435
449
  correctly, because stating a claim is a human act. But a project adopting this
436
450
  tool reads that list, has nothing to compare it against, and closes the tab.
@@ -687,12 +701,12 @@ detected base that does not resolve is a warning for `status` and `brief`
687
701
  # GitHub Actions
688
702
  - uses: actions/checkout@v4
689
703
  with: { fetch-depth: 0 }
690
- - uses: raccioly/testguard@v0.14.2
704
+ - uses: raccioly/testguard@v0.15.1
691
705
  with: { command: gate }
692
706
 
693
707
  # GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
694
708
  include:
695
- - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.14.2/packaging/gitlab/testguard.gitlab-ci.yml'
709
+ - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.15.1/packaging/gitlab/testguard.gitlab-ci.yml'
696
710
  inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
697
711
  ```
698
712
 
@@ -718,6 +732,18 @@ talks to the network; only the job posts, and only when told to.
718
732
  - **Never optimistic.** A timeout, a load failure, a mixed N-run result, a
719
733
  flaky defender or a missing anchor is reported as unproven, never rounded
720
734
  toward green. The one pass is `killed`; everything else gates.
735
+ - **Per-run and whole-command time are separate.** `--budget` limits each
736
+ runner invocation. `--command-budget` is a cooperative deadline for the
737
+ complete `probe`, `sweep`, or `replay` measurement: asynchronous children
738
+ receive only the remaining time, and stage/write boundaries re-check it.
739
+ Synchronous setup may overrun the nominal deadline, but once expiry is
740
+ observed TestGuard exits `2` and writes no new result document; a completed
741
+ prefix is not presented as though the unattempted suffix were clean.
742
+ - **Configured discovery is evidence.** Vitest, Jest, and Playwright enumerate
743
+ their own configured files; TestGuard validates and hashes that immutable
744
+ universe instead of guessing with filename globs. Automatic JavaScript and
745
+ TypeScript discovery follows the imported symbol through barrels. Ambiguous
746
+ or unbounded resolution is `unverifiable`, never a flattering `nocover`.
721
747
  - **One exact-pinned runtime dependency** (`ajv`), Node ≥ 20, MIT.
722
748
 
723
749
  ### Prior art, and what was taken from where
@@ -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.14.2` — see [`action.yml`](./action.yml) |
130
+ | GitHub Action | `uses: raccioly/testguard@v0.15.1` — 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.14.2/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
132
+ | GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.15.1/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
@@ -407,6 +407,20 @@ is missing. TestGuard itself still makes no network calls.
407
407
 
408
408
  ### Starting from zero claims
409
409
 
410
+ During gradual adoption, use `testguard probe --allow-empty` to skip a valid
411
+ empty claims file without blocking a push. It explicitly reports that no
412
+ verification was performed and writes no evidence; `--json` retains the
413
+ `no-claims` state. Missing or invalid claims files still fail. Run `gate`
414
+ separately to enforce changed-file coverage. For the pre-commit probe hook,
415
+ set `args: [--allow-empty]`; for the GitHub Action, set `allow-empty: 'true'`.
416
+ The default probe still exits 2 when no claims exist.
417
+
418
+ In a monorepo, a valid nested `testguard.claims.json` marks a separate project.
419
+ The parent gate reports its changed files as delegated and does not count them
420
+ as parent coverage. Run `gate` in each child project as well; a passing parent
421
+ gate does not establish that child coverage passed. Invalid child claims fail
422
+ evaluation instead of hiding files.
423
+
410
424
  `gate` names the changed files that carry no claim, and stops there —
411
425
  correctly, because stating a claim is a human act. But a project adopting this
412
426
  tool reads that list, has nothing to compare it against, and closes the tab.
@@ -663,12 +677,12 @@ detected base that does not resolve is a warning for `status` and `brief`
663
677
  # GitHub Actions
664
678
  - uses: actions/checkout@v4
665
679
  with: { fetch-depth: 0 }
666
- - uses: raccioly/testguard@v0.14.2
680
+ - uses: raccioly/testguard@v0.15.1
667
681
  with: { command: gate }
668
682
 
669
683
  # GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
670
684
  include:
671
- - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.14.2/packaging/gitlab/testguard.gitlab-ci.yml'
685
+ - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.15.1/packaging/gitlab/testguard.gitlab-ci.yml'
672
686
  inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
673
687
  ```
674
688
 
@@ -694,6 +708,18 @@ talks to the network; only the job posts, and only when told to.
694
708
  - **Never optimistic.** A timeout, a load failure, a mixed N-run result, a
695
709
  flaky defender or a missing anchor is reported as unproven, never rounded
696
710
  toward green. The one pass is `killed`; everything else gates.
711
+ - **Per-run and whole-command time are separate.** `--budget` limits each
712
+ runner invocation. `--command-budget` is a cooperative deadline for the
713
+ complete `probe`, `sweep`, or `replay` measurement: asynchronous children
714
+ receive only the remaining time, and stage/write boundaries re-check it.
715
+ Synchronous setup may overrun the nominal deadline, but once expiry is
716
+ observed TestGuard exits `2` and writes no new result document; a completed
717
+ prefix is not presented as though the unattempted suffix were clean.
718
+ - **Configured discovery is evidence.** Vitest, Jest, and Playwright enumerate
719
+ their own configured files; TestGuard validates and hashes that immutable
720
+ universe instead of guessing with filename globs. Automatic JavaScript and
721
+ TypeScript discovery follows the imported symbol through barrels. Ambiguous
722
+ or unbounded resolution is `unverifiable`, never a flattering `nocover`.
697
723
  - **One exact-pinned runtime dependency** (`ajv`), Node ≥ 20, MIT.
698
724
 
699
725
  ### Prior art, and what was taken from where
@@ -35,6 +35,10 @@ inputs:
35
35
  description: 'Skip re-running survivors against the whole suite'
36
36
  required: false
37
37
  default: 'false'
38
+ allow-empty:
39
+ description: 'probe only: explicitly skip a valid empty claims file during adoption; writes no evidence. Run gate separately.'
40
+ required: false
41
+ default: 'false'
38
42
  changed-ref:
39
43
  description: 'gate only: the reference the change is measured against (default: origin/<base branch> of the pull request). Check out with fetch-depth: 0 so it resolves.'
40
44
  required: false
@@ -46,7 +50,7 @@ inputs:
46
50
  version:
47
51
  description: 'testguard-cli version to run'
48
52
  required: false
49
- default: '0.14.2'
53
+ default: '0.15.1'
50
54
  node-version:
51
55
  description: 'Node.js version (20+)'
52
56
  required: false
@@ -82,6 +86,7 @@ runs:
82
86
  if [ "${{ inputs.command }}" = "probe" ]; then
83
87
  ARGS+=(--severity "${{ inputs.severity }}" --confirm "${{ inputs.confirm }}" --budget "${{ inputs.budget }}")
84
88
  [ "${{ inputs.no-escalate }}" = "true" ] && ARGS+=(--no-escalate)
89
+ [ "${{ inputs.allow-empty }}" = "true" ] && ARGS+=(--allow-empty)
85
90
  [ "${{ inputs.runner }}" != "auto" ] && ARGS+=(--runner "${{ inputs.runner }}")
86
91
  [ -n "${{ inputs.python }}" ] && ARGS+=(--python "${{ inputs.python }}")
87
92
  fi
@@ -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.14.2 · MIT</div>
147
+ <div class="meta">Technical Brief · v0.15.1 · MIT</div>
148
148
  </div>
149
149
 
150
150
  <h1>Your tests pass. That is not the same as your tests working.</h1>
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "testguard-cli",
3
- "version": "0.14.2",
3
+ "version": "0.15.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "testguard-cli",
9
- "version": "0.14.2",
9
+ "version": "0.15.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.14.2",
3
+ "version": "0.15.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",
@@ -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.14.2/packaging/gitlab/testguard.gitlab-ci.yml'
6
+ # - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.15.1/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.14.2"
32
+ default: "0.15.1"
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.14.2.tgz"
19
- sha256 "42651c054396fdf7506605ee19c198611713f698931755d3acf3656d7b96d8cb"
18
+ url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.15.1.tgz"
19
+ sha256 "357f37905e99bf2d1a7487374bc2fc2b97983fd8d355b2e77d72e03fb0f2f8c7"
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.14.2"
7
+ version = "0.15.1"
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" }
@@ -12,7 +12,7 @@ The verdict set is closed. Only one value is a pass.
12
12
  | `killed` | Fault applied; every one of N probe runs failed with a genuine assertion failure, on a defender set that was green N/N unmodified. | no |
13
13
  | `survived` | Fault applied; every one of N probe runs passed. The claim is **unproven**. | **yes** |
14
14
  | `nocover` | No defending test exists: the declared globs resolve to nothing, or no test imports the subject. Worse than `survived` — nothing was even tried. | **yes** |
15
- | `unverifiable` | The claim could not be probed: the fault's anchor is missing or ambiguous, its defenders failed to load (`defenders-failed-to-load`), probing it threw (`probe-error`), or the defenders do not execute the subject at all (`subject-not-executed`). Carries a `reason`. A loud, gating verdict — a claim that cannot be probed is not "skipped", it is undefended until someone fixes the fault or the defenders. Never confused with `flaky-defender`, which requires tests that *ran*. | **yes** |
15
+ | `unverifiable` | The claim could not be probed: the fault's anchor is missing or ambiguous, configured or symbol-level defender discovery was indeterminate (`defender-discovery-indeterminate`), its defenders failed to load (`defenders-failed-to-load`), probing it threw (`probe-error`), or the defenders do not execute the subject at all (`subject-not-executed`). Carries a `reason`. A loud, gating verdict — a claim that cannot be probed is not "skipped", it is undefended until someone fixes the fault or the defenders. Never confused with `flaky-defender`, which requires tests that *ran*. | **yes** |
16
16
  | `timeout` | Probe run exceeded its budget. Not counted as a kill; the pessimistic reading is the safe one because flakiness biases the metric optimistically. | **yes** |
17
17
  | `fault-invalid` | The replacement does not load or compile. A bad fault, not a detection. | **yes** |
18
18
  | `flaky-defender` | Defenders were not green N/N on unmodified source (`defenders-not-green`), or the N probe runs disagreed with each other (`inconsistent-probe`). Either way no verdict about the fault can be trusted; fix the defenders first. | **yes** |
@@ -36,7 +36,11 @@ Rules that follow from the table:
36
36
  that failed and then passed on retry (Playwright's `flaky`) is a
37
37
  **non-green run** even when the runner exits 0, and a test the runner
38
38
  reports as timed out (`timedOut`) is a timeout, never an assertion
39
- failure. Python is read the same way: an `AssertionError` and any other
39
+ failure. Jest-compatible timeout messages are recognized by native runner
40
+ error headers or structured timeout identity, not by words quoted in an
41
+ assertion, stack frame, or source excerpt. Structured assertion identity
42
+ does not hide a separate timeout in the same failed test.
43
+ Python is read the same way: an `AssertionError` and any other
40
44
  exception raised by the test body both count, because both are the suite
41
45
  rejecting the behaviour; a collection error — a module that will not
42
46
  import, which is what a replacement that does not parse looks like from
@@ -151,7 +155,48 @@ Rules that follow from the table:
151
155
  can name the file the interpreter loaded) answers the same question more
152
156
  precisely for one language; where both are present they must agree.
153
157
 
154
- 13. **Fault injection is one method, not the definition.** A claim declares
158
+ 13. **The configured runner defines the test universe.** For JavaScript and
159
+ TypeScript, defender discovery, baseline runs and escalation use the exact
160
+ file set returned by the resolved runner's native listing protocol. A
161
+ resolvable runner whose configuration cannot load, whose listing times out
162
+ or is malformed, or whose reported paths escape the project is a
163
+ precondition failure. It never falls back to built-in filename globs and
164
+ never becomes `nocover`. An explicitly selected runner that lists zero
165
+ files is also a precondition failure; `auto` may continue past a successful
166
+ empty listing, but must fail when every eligible runner is empty.
167
+ `inputs.testUniverseHash` binds the normalized file set, runner identity,
168
+ and hashed configuration dependencies. Those dependencies include absent
169
+ root candidates, nested workspace configs, Python collection hooks, and the
170
+ bounded static closure of local or package-imported config helpers and setup
171
+ files; creating or editing any of them forces remeasurement. Evidence that
172
+ predates this hash, or whose hash
173
+ changed, is readable but never reusable for a current native discovery run.
174
+ When several configured runners list defenders, exact manifest membership
175
+ assigns each file. An owned runner takes precedence over the project runner;
176
+ competing owned runners or a declared defender absent from every manifest
177
+ are precondition failures, never heuristic routing decisions.
178
+
179
+ Static string literals are conservative dependency candidates, not proof
180
+ of a filesystem dependency. Missing candidates and ordinary directories
181
+ (including `/` in URL bases and string separators) are ignored. An existing
182
+ candidate file outside the project, or a candidate symlink, is rejected.
183
+
184
+ 14. **Barrel resolution follows symbols, not module reachability.** Automatic
185
+ discovery may traverse a re-export chain only for the binding the test
186
+ actually imports. Importing an unrelated symbol from a broad barrel does
187
+ not defend every file the barrel exports. Renames, explicit exports and
188
+ star exports are resolved deterministically; type-only paths do not count.
189
+ Ambiguous origins, unsupported runtime-generated exports, graph-limit
190
+ exhaustion, and originless cycles are `unverifiable` with reason
191
+ `defender-discovery-indeterminate`, never `nocover`. A declared
192
+ `defendedBy` remains authoritative. `inputs.discoveryHashes` binds every
193
+ candidate test, intermediate barrel, and resolver-configuration file
194
+ consulted by automatic discovery. Negative candidates are dependencies too:
195
+ editing a previously unrelated test so it begins importing the target must
196
+ invalidate an earlier `nocover`. Absent or changed dependencies force
197
+ remeasurement.
198
+
199
+ 15. **Fault injection is one method, not the definition.** A claim declares
155
200
  what must be true; a probe declares how a tool would try to make it false,
156
201
  and `method` says which way. `fault-injection` — mutate the source, run the
157
202
  claim's defenders, confirm over N runs — is the only method specified in
@@ -184,6 +229,32 @@ Rules that follow from the table:
184
229
  The verdict set stays closed and shared. What each method may legitimately
185
230
  report is part of defining it.
186
231
 
232
+ ## Per-run and whole-command budgets are different
233
+
234
+ `--budget` remains the wall-clock ceiling for one runner invocation. An
235
+ optional `--command-budget` is a cooperative deadline for the complete
236
+ `probe`, `sweep`, or `replay` measurement. Every asynchronous child runner and
237
+ discovery process is given no more than the command's remaining time, and the
238
+ deadline is checked again at stage boundaries and immediately before any
239
+ result is written. Synchronous filesystem, Git, parsing, and cleanup work is
240
+ not preemptible in portable Node.js and may finish after the nominal deadline;
241
+ that overrun grants no authority to publish a result.
242
+
243
+ Exhausting the whole-command budget is not a test verdict and is never a
244
+ successful partial measurement. The command exits `2` and writes no new
245
+ result document; any previous document remains untouched. Cleanup may finish
246
+ after the deadline, but no record from the incomplete operation becomes
247
+ evidence. This is deliberately stricter than a per-run timeout: publishing
248
+ the completed prefix would let the unattempted suffix look clean.
249
+
250
+ Timeout cleanup is best effort, not portable process containment. TestGuard
251
+ hard-kills the runner's original process group and every descendant still
252
+ attributable through the process table. A daemon that detached, reparented,
253
+ and disappeared from that ancestry before timeout may survive, so timeout
254
+ diagnostics state `cleanup-unverified`. Such a run remains a timeout and never
255
+ becomes evidence of detection. Daemonizing or untrusted commands require an
256
+ OS or container boundary that TestGuard does not claim to provide.
257
+
187
258
  ## Replay reports; it never gates
188
259
 
189
260
  A replayed bug is history. It escaped, by definition, which means the suite
@@ -198,7 +269,7 @@ Its verdicts mirror the probe's, for the same reasons:
198
269
  | `caught` | a remaining test failed **by assertion** on the reverted source, every run. The suite knew. |
199
270
  | `blind` | the suite stayed green on known-broken code. |
200
271
  | `nocover` | no test imports the reverted files, **and at least one test file remained to ask**. Worse than `blind`: nothing was even tried. |
201
- | `unverifiable` | the revert did not apply, the suite failed to load, it timed out, or removing the fix's own test left **no test file at all** (`the-fix-shipped-the-only-test-file`). Carries a `reason`. |
272
+ | `unverifiable` | the revert did not apply, configured or symbol-level defender discovery was indeterminate (`defender-discovery-indeterminate`), the suite failed to load, it timed out, or removing the fix's own test left **no test file at all** (`the-fix-shipped-the-only-test-file`). Carries a `reason`. |
202
273
  | `flaky` | the runs disagreed. A flaky failure reads as "the suite caught it", so flakiness biases this metric **optimistically** — mixed runs are never `caught`. |
203
274
 
204
275
  Two rules that follow:
@@ -432,10 +503,22 @@ Rules:
432
503
  `unclaimed-changes` and its next action is to write the claim, before any
433
504
  `unproven` finding is surfaced. The brief renders them first. The claim is
434
505
  written before more code.
435
-
436
- Exit codes: `0` every changed source file is claimed or excused (or nothing
437
- changed); `1` at least one uncovered file (or strict mode over an
438
- all-excluded change); `2` the change cannot be evaluated (unresolvable
506
+ 7. **A descendant claims file defines a separate project.** The first
507
+ descendant directory on a changed file's path with a valid, regular,
508
+ non-symlinked `testguard.claims.json` owns that subtree. Invalid or unreadable
509
+ markers fail evaluation; they cannot hide unclaimed files. Parent gate and
510
+ status report the delegation as optional `nested` entries with project and
511
+ file paths relative to the parent, and instruct the operator to run that
512
+ project's gate. Delegation is not coverage, an ignore reliance, or proof
513
+ that the child passed. Parent source-surface counts omit these subtrees.
514
+ Removing the marker restores parent evaluation. The accounting identity is
515
+ `changed = evaluated + excluded.length + sum(nested[].files.length)`;
516
+ `--strict` still fails a non-empty change with zero parent evaluation.
517
+
518
+ Exit codes: `0` every changed parent-owned source file is claimed or excused
519
+ (or nothing changed); delegated projects still require separate child gates.
520
+ `1` at least one uncovered file (or strict mode over an
521
+ all-excluded or all-delegated change); `2` the change cannot be evaluated (unresolvable
439
522
  reference, invalid claims or ignore file, no repository); `3` no reference.
440
523
 
441
524
  ## Anchor preflight
@@ -486,3 +569,11 @@ evidence; they simply do not turn CI red.
486
569
  A tool must never exit `0` because it had nothing to check. If the claims
487
570
  file is empty or every claim is out of scope, that is reported explicitly and
488
571
  the exit code is `2`.
572
+
573
+ `probe --allow-empty` is an explicit adoption exception for a valid claims
574
+ file containing zero claims, with no `--claim` selection. It exits `0`, says
575
+ that verification was skipped, and writes no evidence or baseline. Its JSON
576
+ output is the existing `no-claims` status document, without run metadata;
577
+ it never reports a clean measurement. Missing or invalid claims still fail,
578
+ and selected claims cannot be silently skipped. Run `gate` separately to
579
+ enforce coverage of changed files during adoption. Integrations must opt in.
@@ -39,6 +39,13 @@ Shared definitions (verdicts, fault classes, provenance, annotations) live in
39
39
  which verdicts turn CI red, baseline and delta rules, exit codes — is in
40
40
  [`GATE-SEMANTICS.md`](GATE-SEMANTICS.md).
41
41
 
42
+ Operational completeness is contractual too. `--budget` bounds one runner
43
+ invocation; `--command-budget` is a cooperative deadline for a complete probe,
44
+ sweep, or replay measurement. It caps asynchronous child processes, is checked
45
+ at stage and write boundaries, and produces no document once observed expired.
46
+ Synchronous setup may overrun but cannot authorize a partial result. The exact
47
+ rule is in `GATE-SEMANTICS.md`.
48
+
42
49
  Schemas are JSON Schema 2020-12 and identified as `urn:claimspec:v1:<kind>`.
43
50
 
44
51
  ## Conformance
@@ -165,7 +165,9 @@
165
165
  "targetHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
166
166
  "defenderHashes": {
167
167
  "test/redact.test.mjs": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
168
- }
168
+ },
169
+ "testUniverseHash": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
170
+ "discoveryHashes": {}
169
171
  },
170
172
  "rank": {
171
173
  "score": 9.5,
@@ -13,6 +13,26 @@ const here = dirname(fileURLToPath(import.meta.url));
13
13
  const load = (dir, f) => JSON.parse(readFileSync(join(here, dir, f), 'utf8'));
14
14
 
15
15
  describe('claimspec v1 — conformance', () => {
16
+ it('accounts for descendant-project delegation without calling it coverage or double-counting it', () => {
17
+ const gate = load('examples', 'gate.json');
18
+ gate.nested = [{ project: 'backend', files: ['backend/src/guard.mjs'] }];
19
+ gate.changed++;
20
+ expect(validate('gate', gate).errors).toEqual([]);
21
+ gate.changed--;
22
+ expect(validate('gate', gate).errors.map((e) => e.path)).toContain('/changed');
23
+ gate.changed++;
24
+ gate.nested[0].files = ['src/guard.mjs'];
25
+ expect(validate('gate', gate).errors.map((e) => e.path)).toContain('/nested/0/files');
26
+ gate.nested[0].files = ['backend/src/guard.mjs'];
27
+ gate.covered.push({ file: 'backend/src/guard.mjs', by: 'fault', claimIds: ['C-1'] });
28
+ gate.evaluated++;
29
+ gate.changed++;
30
+ expect(validate('gate', gate).errors.map((e) => e.path)).toContain('/nested/0/files');
31
+ gate.covered.pop(); gate.evaluated--; gate.changed--;
32
+ gate.nested.push({ project: 'backend', files: ['backend/src/second.mjs'] });
33
+ gate.changed++;
34
+ expect(validate('gate', gate).errors.map((e) => e.path)).toContain('/nested/1/project');
35
+ });
16
36
  describe('every valid example passes', () => {
17
37
  for (const f of readdirSync(join(here, 'examples')).filter((f) => f.endsWith('.json'))) {
18
38
  const kind = KINDS.find((k) => f.startsWith(k));