testguard-cli 0.9.3__tar.gz → 0.10.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 (308) hide show
  1. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/CHANGELOG.md +36 -0
  2. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/PKG-INFO +14 -6
  3. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/README.md +13 -5
  4. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/action.yml +1 -1
  5. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/docs/testguard-explained.html +1 -1
  6. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/docs/testguard-explained.pdf +0 -0
  7. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/package-lock.json +2 -2
  8. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/package.json +1 -1
  9. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/packaging/gitlab/testguard.gitlab-ci.yml +2 -2
  10. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/packaging/homebrew/testguard.rb +2 -2
  11. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/pyproject.toml +1 -1
  12. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/GATE-SEMANTICS.md +21 -0
  13. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/status.json +21 -0
  14. testguard_cli-0.10.0/spec/conformance/invalid/status.surface-counts-disagree.json +28 -0
  15. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/lib/validate.mjs +6 -0
  16. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/schemas/status.schema.json +50 -0
  17. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/brief/brief.mjs +2 -2
  18. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/gate/changed.mjs +6 -2
  19. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/render.mjs +6 -1
  20. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/status/status.mjs +30 -1
  21. testguard_cli-0.10.0/src/status/surface.mjs +72 -0
  22. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/brief.test.mjs +5 -0
  23. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/status.test.mjs +17 -0
  24. testguard_cli-0.10.0/test/surface.test.mjs +53 -0
  25. testguard_cli-0.10.0/test/vitest-config.test.mjs +11 -0
  26. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/testguard.claims.json +152 -0
  27. testguard_cli-0.10.0/vitest.config.mjs +11 -0
  28. testguard_cli-0.9.3/vitest.config.mjs +0 -9
  29. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.agents/skills/testguard/SKILL.md +0 -0
  30. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.codex/hooks.json +0 -0
  31. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.docguard.json +0 -0
  32. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.docguardignore +0 -0
  33. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.gitattributes +0 -0
  34. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  35. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  36. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  37. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.github/dependabot.yml +0 -0
  38. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.github/scripts/approve-held-runs.sh +0 -0
  39. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.github/scripts/build-one-pager.mjs +0 -0
  40. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.github/scripts/check-cost-budget.mjs +0 -0
  41. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.github/scripts/check-python-fixture.mjs +0 -0
  42. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.github/scripts/install-smoke.mjs +0 -0
  43. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.github/scripts/sync-release-version.mjs +0 -0
  44. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.github/workflows/auto-merge.yml +0 -0
  45. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.github/workflows/ci.yml +0 -0
  46. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.github/workflows/release.yml +0 -0
  47. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.github/workflows/scheduled-release.yml +0 -0
  48. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.github/workflows/supply-chain.yml +0 -0
  49. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.gitignore +0 -0
  50. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.npmignore +0 -0
  51. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.npmrc +0 -0
  52. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.pre-commit-config.yaml +0 -0
  53. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/.pre-commit-hooks.yaml +0 -0
  54. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/AGENTS.md +0 -0
  55. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/CLAUDE.md +0 -0
  56. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/CODE_OF_CONDUCT.md +0 -0
  57. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/CONTRIBUTING.md +0 -0
  58. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/DRIFT-LOG.md +0 -0
  59. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/LICENSE +0 -0
  60. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/PRIVACY.md +0 -0
  61. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/SECURITY.md +0 -0
  62. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/SUPPORT.md +0 -0
  63. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/bench/README.md +0 -0
  64. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/cli/testguard.mjs +0 -0
  65. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/docs-canonical/ARCHITECTURE.md +0 -0
  66. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/docs-canonical/DATA-MODEL.md +0 -0
  67. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/docs-canonical/ENVIRONMENT.md +0 -0
  68. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/docs-canonical/REQUIREMENTS.md +0 -0
  69. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/docs-canonical/SECURITY.md +0 -0
  70. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/docs-canonical/TEST-SPEC.md +0 -0
  71. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer/.gitignore +0 -0
  72. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer/README.md +0 -0
  73. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer/expected.json +0 -0
  74. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer/package.json +0 -0
  75. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer/src/export.mjs +0 -0
  76. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer/src/redact.mjs +0 -0
  77. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
  78. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer/test/flaky.test.mjs +0 -0
  79. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer/test/redact.test.mjs +0 -0
  80. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer/testguard.claims.json +0 -0
  81. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer/vitest.config.mjs +0 -0
  82. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-jest/.gitignore +0 -0
  83. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-jest/README.md +0 -0
  84. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-jest/expected.json +0 -0
  85. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-jest/jest.config.js +0 -0
  86. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-jest/package.json +0 -0
  87. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-jest/src/export.js +0 -0
  88. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-jest/src/redact.js +0 -0
  89. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
  90. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
  91. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-jest/test/redact.test.js +0 -0
  92. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-jest/testguard.claims.json +0 -0
  93. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-playwright/.gitignore +0 -0
  94. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-playwright/README.md +0 -0
  95. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
  96. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
  97. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-playwright/expected.json +0 -0
  98. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-playwright/package-lock.json +0 -0
  99. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-playwright/package.json +0 -0
  100. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
  101. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
  102. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
  103. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-playwright/test-results/.last-run.json +0 -0
  104. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
  105. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
  106. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-python/.gitignore +0 -0
  107. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-python/README.md +0 -0
  108. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-python/demo/__init__.py +0 -0
  109. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-python/demo/export.py +0 -0
  110. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-python/demo/orphan.py +0 -0
  111. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-python/demo/pipeline.py +0 -0
  112. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-python/demo/redact.py +0 -0
  113. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-python/expected.json +0 -0
  114. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-python/testguard.claims.json +0 -0
  115. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
  116. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
  117. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-python/tests/test_patched.py +0 -0
  118. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/fixtures/known-answer-python/tests/test_redact.py +0 -0
  119. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/README.md +0 -0
  120. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/baseline.json +0 -0
  121. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/brief.json +0 -0
  122. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/calibration-websec.json +0 -0
  123. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/calibration.json +0 -0
  124. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/claims-methods.json +0 -0
  125. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/claims.json +0 -0
  126. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
  127. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/evidence-negative-control.json +0 -0
  128. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/evidence-provisional.json +0 -0
  129. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/evidence-python.json +0 -0
  130. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/evidence-scan-method.json +0 -0
  131. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/evidence.json +0 -0
  132. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/gate.json +0 -0
  133. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/ignore.json +0 -0
  134. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/examples/replay.json +0 -0
  135. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
  136. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/baseline.zero-count.json +0 -0
  137. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
  138. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
  139. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +0 -0
  140. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +0 -0
  141. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/calibration.ci-not-wilson.json +0 -0
  142. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +0 -0
  143. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/calibration.fallback-without-basis.json +0 -0
  144. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/calibration.p-not-positives-over-n.json +0 -0
  145. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
  146. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
  147. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/claims.absolute-path.json +0 -0
  148. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
  149. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
  150. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +0 -0
  151. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
  152. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/claims.noop-fault.json +0 -0
  153. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
  154. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
  155. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
  156. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
  157. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
  158. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
  159. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
  160. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
  161. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
  162. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
  163. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
  164. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
  165. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
  166. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
  167. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +0 -0
  168. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
  169. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
  170. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
  171. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
  172. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
  173. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
  174. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
  175. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
  176. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
  177. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
  178. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
  179. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
  180. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
  181. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
  182. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
  183. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
  184. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/ignore.short-reason.json +0 -0
  185. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
  186. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
  187. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/status.claim-without-file.json +0 -0
  188. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
  189. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/status.invalid-anchor-hidden.json +0 -0
  190. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
  191. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
  192. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/conformance/schemas.test.mjs +0 -0
  193. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/lib/fingerprint.mjs +0 -0
  194. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/lib/wilson.mjs +0 -0
  195. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/schemas/baseline.schema.json +0 -0
  196. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/schemas/brief.schema.json +0 -0
  197. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/schemas/calibration.schema.json +0 -0
  198. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/schemas/claims.schema.json +0 -0
  199. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/schemas/common.schema.json +0 -0
  200. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/schemas/evidence.schema.json +0 -0
  201. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/schemas/gate.schema.json +0 -0
  202. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/schemas/ignore.schema.json +0 -0
  203. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/spec/schemas/replay.schema.json +0 -0
  204. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/admit/admit.mjs +0 -0
  205. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/baseline/baseline.mjs +0 -0
  206. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/claims/anchors.mjs +0 -0
  207. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/claims/annotations.mjs +0 -0
  208. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/claims/load.mjs +0 -0
  209. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/claims/removed.mjs +0 -0
  210. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/cli.mjs +0 -0
  211. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/commands/admit.mjs +0 -0
  212. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/commands/baseline.mjs +0 -0
  213. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/commands/brief.mjs +0 -0
  214. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/commands/claims.mjs +0 -0
  215. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/commands/gate.mjs +0 -0
  216. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/commands/init.mjs +0 -0
  217. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/commands/mcp.mjs +0 -0
  218. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/commands/probe.mjs +0 -0
  219. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/commands/replay.mjs +0 -0
  220. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/commands/scaffold.mjs +0 -0
  221. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/commands/status.mjs +0 -0
  222. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/evidence/writer.mjs +0 -0
  223. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/git.mjs +0 -0
  224. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/init/init.mjs +0 -0
  225. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/init/templates/SKILL.md +0 -0
  226. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/mcp/server.mjs +0 -0
  227. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/mcp/tools.mjs +0 -0
  228. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/attribution.mjs +0 -0
  229. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/classify.mjs +0 -0
  230. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/contention.mjs +0 -0
  231. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/cost.mjs +0 -0
  232. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/discover.mjs +0 -0
  233. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/independence.mjs +0 -0
  234. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/inject.mjs +0 -0
  235. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/mocks.mjs +0 -0
  236. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/probe.mjs +0 -0
  237. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/progress.mjs +0 -0
  238. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/pyimports.mjs +0 -0
  239. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/rank.mjs +0 -0
  240. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/runners/index.mjs +0 -0
  241. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/runners/jest.mjs +0 -0
  242. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/runners/playwright.mjs +0 -0
  243. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
  244. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/runners/python/_testguard_report.py +0 -0
  245. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
  246. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/runners/python.mjs +0 -0
  247. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/runners/shared.mjs +0 -0
  248. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/runners/vitest.mjs +0 -0
  249. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/probe/worktree.mjs +0 -0
  250. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/replay/label.mjs +0 -0
  251. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/replay/replay.mjs +0 -0
  252. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/scaffold/producers.mjs +0 -0
  253. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/scaffold/producers.python.mjs +0 -0
  254. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/scaffold/scaffold.mjs +0 -0
  255. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/util/glob.mjs +0 -0
  256. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/src/util/hash.mjs +0 -0
  257. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/admit.test.mjs +0 -0
  258. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/annotations.test.mjs +0 -0
  259. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/attribution.test.mjs +0 -0
  260. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/baseline.test.mjs +0 -0
  261. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/calibration.test.mjs +0 -0
  262. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/claims-anchors.test.mjs +0 -0
  263. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/claims-since.test.mjs +0 -0
  264. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/classify.test.mjs +0 -0
  265. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/contention.test.mjs +0 -0
  266. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/cost.test.mjs +0 -0
  267. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/discover.test.mjs +0 -0
  268. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/gate.test.mjs +0 -0
  269. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/gitlab-template.test.mjs +0 -0
  270. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/glob.test.mjs +0 -0
  271. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/independence.test.mjs +0 -0
  272. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/init.test.mjs +0 -0
  273. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/inject.test.mjs +0 -0
  274. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/label.test.mjs +0 -0
  275. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/mcp.test.mjs +0 -0
  276. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/mocks.test.mjs +0 -0
  277. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/negative-control.test.mjs +0 -0
  278. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/one-pager.test.mjs +0 -0
  279. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/probe-error.test.mjs +0 -0
  280. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/probe-preconditions.test.mjs +0 -0
  281. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/probe.fixture.test.mjs +0 -0
  282. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/probe.jest.test.mjs +0 -0
  283. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/probe.playwright.test.mjs +0 -0
  284. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/probe.python.test.mjs +0 -0
  285. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/progress.test.mjs +0 -0
  286. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/pyimports.test.mjs +0 -0
  287. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/rank-aliases.test.mjs +0 -0
  288. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/release-approval.test.mjs +0 -0
  289. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/release-sync.test.mjs +0 -0
  290. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/replay-verdict.test.mjs +0 -0
  291. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/replay.test.mjs +0 -0
  292. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/runner-argv.test.mjs +0 -0
  293. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/runner-command.test.mjs +0 -0
  294. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/runner-playwright.test.mjs +0 -0
  295. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/runner-python.test.mjs +0 -0
  296. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/runner-vitest.test.mjs +0 -0
  297. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/samples/playwright-report.json +0 -0
  298. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/samples/python-report-collect-error.json +0 -0
  299. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/samples/python-report.json +0 -0
  300. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/scaffold.python.test.mjs +0 -0
  301. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/scaffold.test.mjs +0 -0
  302. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/snapshot.test.mjs +0 -0
  303. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/worktree.test.mjs +0 -0
  304. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/test/writer.test.mjs +0 -0
  305. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/testguard.cost-budget.json +0 -0
  306. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/testguard.ignore.json +0 -0
  307. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/testguard_cli/__init__.py +0 -0
  308. {testguard_cli-0.9.3 → testguard_cli-0.10.0}/testguard_cli/wrapper.py +0 -0
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.10.0] - 2026-09-19
11
+
12
+ Automated weekly release — everything merged since `v0.9.4`.
13
+
14
+ ### Changed
15
+
16
+ - feat(status): expose claimed source surface (#135)
17
+ - chore(homebrew): sha256 for v0.9.4 (#136)
18
+
19
+
20
+ ### Added
21
+
22
+ - `status` now makes the claim denominator visible: claimed versus current
23
+ source modules, coverage of the 20 highest-churn modules over a bounded
24
+ 200-commit window, and specific unclaimed modules ranked by churn with
25
+ explicit path-risk signals. A clean project whose majority of modules are
26
+ still unclaimed is directed to scaffold the highest-churn gap. The existing
27
+ co-authorship caveat is also printed by both `status` and `probe`, rather
28
+ than remaining available only in JSON and the agent brief.
29
+
30
+ ## [0.9.4] - 2026-09-19
31
+
32
+ Automated weekly release — everything merged since `v0.9.3`.
33
+
34
+ ### Changed
35
+
36
+ - fix(test): ignore nested agent worktrees (#133)
37
+ - chore(homebrew): sha256 for v0.9.3 (#131)
38
+
39
+
40
+ ### Fixed
41
+
42
+ - The canonical `npm test` command no longer collects suites from live nested
43
+ worktrees under `.claude/`. A parked checkout can remain safely available
44
+ without its unfinished tests or fixtures contaminating the main tree's gate.
45
+
10
46
  ## [0.9.3] - 2026-09-19
11
47
 
12
48
  Automated weekly release — everything merged since `v0.9.2`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: testguard-cli
3
- Version: 0.9.3
3
+ Version: 0.10.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.9.3` — see [`action.yml`](./action.yml) |
154
+ | GitHub Action | `uses: raccioly/testguard@v0.10.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.9.3/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
156
+ | GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.10.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
@@ -493,12 +493,12 @@ detected base that does not resolve is a warning for `status` and `brief`
493
493
  # GitHub Actions
494
494
  - uses: actions/checkout@v4
495
495
  with: { fetch-depth: 0 }
496
- - uses: raccioly/testguard@v0.9.3
496
+ - uses: raccioly/testguard@v0.10.0
497
497
  with: { command: gate }
498
498
 
499
499
  # GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
500
500
  include:
501
- - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.9.3/packaging/gitlab/testguard.gitlab-ci.yml'
501
+ - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.0/packaging/gitlab/testguard.gitlab-ci.yml'
502
502
  inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
503
503
  ```
504
504
 
@@ -657,9 +657,17 @@ things make that safe:
657
657
  *should* ship with its regression test — but a repository where every kill
658
658
  is co-authored has no independent verification, whatever its claim
659
659
  verification rate says. It is a **signal**: ranking reads it, verdicts never
660
- do, and the brief says it in one line. Nothing else in this category
660
+ do, and `probe`, `status` and the brief all say it in one line. Nothing else in this category
661
661
  measures it, and agents saturate the tests they can see
662
662
  ([SpecBench](https://arxiv.org/abs/2605.21384)).
663
+ - **Claimed surface is visible.** A clean probe is only about claims that
664
+ exist. `status` therefore reports claimed source modules over the current
665
+ module denominator, how many of the 20 highest-churn modules are claimed,
666
+ and the concrete unclaimed modules worth examining next over a bounded
667
+ 200-commit window. Churn and path-risk signals stay separate; there is no
668
+ synthetic health score. When most modules remain unclaimed and the existing
669
+ evidence is clean, the next action expands the denominator instead of
670
+ congratulating the same small set of claims again.
663
671
  - **Gaming is visible.** The cheapest way to make a survivor disappear is to
664
672
  weaken its fault, not to write a test. Evidence records every fault's
665
673
  content hash; `status` lists any fault edited after it survived, with its
@@ -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.9.3` — see [`action.yml`](./action.yml) |
130
+ | GitHub Action | `uses: raccioly/testguard@v0.10.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.9.3/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
132
+ | GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.10.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
@@ -469,12 +469,12 @@ detected base that does not resolve is a warning for `status` and `brief`
469
469
  # GitHub Actions
470
470
  - uses: actions/checkout@v4
471
471
  with: { fetch-depth: 0 }
472
- - uses: raccioly/testguard@v0.9.3
472
+ - uses: raccioly/testguard@v0.10.0
473
473
  with: { command: gate }
474
474
 
475
475
  # GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
476
476
  include:
477
- - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.9.3/packaging/gitlab/testguard.gitlab-ci.yml'
477
+ - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.0/packaging/gitlab/testguard.gitlab-ci.yml'
478
478
  inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
479
479
  ```
480
480
 
@@ -633,9 +633,17 @@ things make that safe:
633
633
  *should* ship with its regression test — but a repository where every kill
634
634
  is co-authored has no independent verification, whatever its claim
635
635
  verification rate says. It is a **signal**: ranking reads it, verdicts never
636
- do, and the brief says it in one line. Nothing else in this category
636
+ do, and `probe`, `status` and the brief all say it in one line. Nothing else in this category
637
637
  measures it, and agents saturate the tests they can see
638
638
  ([SpecBench](https://arxiv.org/abs/2605.21384)).
639
+ - **Claimed surface is visible.** A clean probe is only about claims that
640
+ exist. `status` therefore reports claimed source modules over the current
641
+ module denominator, how many of the 20 highest-churn modules are claimed,
642
+ and the concrete unclaimed modules worth examining next over a bounded
643
+ 200-commit window. Churn and path-risk signals stay separate; there is no
644
+ synthetic health score. When most modules remain unclaimed and the existing
645
+ evidence is clean, the next action expands the denominator instead of
646
+ congratulating the same small set of claims again.
639
647
  - **Gaming is visible.** The cheapest way to make a survivor disappear is to
640
648
  weaken its fault, not to write a test. Evidence records every fault's
641
649
  content hash; `status` lists any fault edited after it survived, with its
@@ -46,7 +46,7 @@ inputs:
46
46
  version:
47
47
  description: 'testguard-cli version to run'
48
48
  required: false
49
- default: '0.9.3'
49
+ default: '0.10.0'
50
50
  node-version:
51
51
  description: 'Node.js version (20+)'
52
52
  required: false
@@ -144,7 +144,7 @@
144
144
  <div class="page">
145
145
  <div class="masthead">
146
146
  <div class="wordmark">Test<span class="mark">Guard</span></div>
147
- <div class="meta">Technical Brief · v0.9.3 · MIT</div>
147
+ <div class="meta">Technical Brief · v0.10.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,12 +1,12 @@
1
1
  {
2
2
  "name": "testguard-cli",
3
- "version": "0.9.3",
3
+ "version": "0.10.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "testguard-cli",
9
- "version": "0.9.3",
9
+ "version": "0.10.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.9.3",
3
+ "version": "0.10.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.9.3/packaging/gitlab/testguard.gitlab-ci.yml'
6
+ # - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.10.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.9.3"
32
+ default: "0.10.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.9.3.tgz"
19
- sha256 "33e88eaa37c34744c08c581a64b4939472e22e898ce1dabf15507fd77229d4e0"
18
+ url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.10.0.tgz"
19
+ sha256 "ab42501d45ba0a71b534e12aa86da2a9fc2f664a1da4f38a4e436dfd30c02c3c"
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.9.3"
7
+ version = "0.10.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" }
@@ -344,6 +344,27 @@ the suppress-up-to-count semantics are identical.
344
344
  hidden. A file that mocks the subject is never a discovered defender: a
345
345
  mock cannot detect a fault in what it replaced.
346
346
 
347
+ ## Claimed source surface
348
+
349
+ `status` reports the denominator that `probe` cannot: current, non-test source
350
+ modules with a supported extension and outside the change gate's documented
351
+ default exclusions. A module is claimed when at least one fault anchors to it.
352
+ The document carries the claimed, unclaimed and total module counts.
353
+
354
+ The report also names concrete unclaimed modules. It counts touches over at
355
+ most the latest 200 commits, reports how many of the 20 highest-churn modules
356
+ are claimed, and lists up to ten unclaimed modules in descending churn order.
357
+ Security- and money-shaped path names are explicit tie-breaking signals, never
358
+ semantic claims about a file. Churn, path risk and claim coverage remain raw
359
+ facts: they are never collapsed into one health score.
360
+
361
+ When existing evidence is clean but more source modules are unclaimed than
362
+ claimed, the next action is to scaffold the highest-churn unclaimed module.
363
+ Earlier correctness states still win: invalid anchors, stale evidence and
364
+ unproven faults are repaired before expanding the denominator. Without git
365
+ history the counts and concrete files remain available, while the document
366
+ says history is unavailable and reports zero commits read.
367
+
347
368
  ## Claim coverage of a change
348
369
 
349
370
  `probe` asks whether the tests defend the claims that exist. It says nothing
@@ -18,6 +18,27 @@
18
18
  }
19
19
  },
20
20
  "provisional": false,
21
+ "surface": {
22
+ "sourceModules": 3,
23
+ "claimedModules": 2,
24
+ "unclaimedModules": 1,
25
+ "history": {
26
+ "available": true,
27
+ "maxCommits": 200,
28
+ "commitsRead": 40
29
+ },
30
+ "highChurn": {
31
+ "modules": 3,
32
+ "claimed": 2
33
+ },
34
+ "rankedUnclaimed": [
35
+ {
36
+ "file": "src/session.mjs",
37
+ "changes": 8,
38
+ "riskSignals": ["security"]
39
+ }
40
+ ]
41
+ },
21
42
  "counts": {
22
43
  "claims": 2,
23
44
  "faults": 6,
@@ -0,0 +1,28 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "tool": { "name": "testguard", "version": "0.10.0" },
4
+ "generatedAt": "2026-09-19T21:00:00Z",
5
+ "state": "no-claims",
6
+ "next": {
7
+ "action": "scaffold",
8
+ "command": "testguard scaffold src/a.mjs",
9
+ "why": "No claims yet.",
10
+ "file": "src/a.mjs"
11
+ },
12
+ "provisional": false,
13
+ "surface": {
14
+ "sourceModules": 2,
15
+ "claimedModules": 1,
16
+ "unclaimedModules": 0,
17
+ "history": { "available": true, "maxCommits": 200, "commitsRead": 12 },
18
+ "highChurn": { "modules": 2, "claimed": 1 },
19
+ "rankedUnclaimed": []
20
+ },
21
+ "counts": { "claims": 0, "faults": 0 },
22
+ "stale": [],
23
+ "invalidFaults": [],
24
+ "changedFaults": [],
25
+ "findings": [],
26
+ "notes": [],
27
+ "paths": {}
28
+ }
@@ -244,6 +244,12 @@ const semantic = {
244
244
  if (doc.invalidFaults?.length > 0 && doc.state !== 'invalid-anchors') errors.push({ path: '/state', message: 'invalid fault anchors are hidden behind a later state; invalid-anchors precedes every evidence state' });
245
245
  if (doc.evidenceSource && !doc.evidenceHead) errors.push({ path: '/evidenceHead', message: 'evidence was read, so the commit it describes must be recorded' });
246
246
  if (doc.changes && doc.changes.uncovered.length > 0 && !['no-claims', 'unclaimed-changes'].includes(doc.state)) errors.push({ path: '/state', message: 'uncovered changed files are hidden behind a later state; unclaimed-changes precedes every evidence state' });
247
+ if (doc.surface) {
248
+ if (doc.surface.claimedModules + doc.surface.unclaimedModules !== doc.surface.sourceModules) errors.push({ path: '/surface', message: 'claimedModules + unclaimedModules must equal sourceModules' });
249
+ if (doc.surface.highChurn.claimed > doc.surface.highChurn.modules) errors.push({ path: '/surface/highChurn/claimed', message: 'claimed high-churn modules cannot exceed the high-churn window' });
250
+ if (!doc.surface.history.available && doc.surface.history.commitsRead !== 0) errors.push({ path: '/surface/history/commitsRead', message: 'unavailable history must report zero commits read' });
251
+ if (new Set(doc.surface.rankedUnclaimed.map((item) => item.file)).size !== doc.surface.rankedUnclaimed.length) errors.push({ path: '/surface/rankedUnclaimed', message: 'ranked unclaimed modules must be unique' });
252
+ }
247
253
  return errors;
248
254
  },
249
255
 
@@ -102,6 +102,56 @@
102
102
  "provisional": {
103
103
  "type": "boolean"
104
104
  },
105
+ "surface": {
106
+ "description": "The current claim denominator. A source module is a non-test file with a supported source extension that is not covered by the documented default exclusions. Churn is bounded recent git history and remains separate from path-risk signals; neither is collapsed into a score.",
107
+ "type": "object",
108
+ "required": ["sourceModules", "claimedModules", "unclaimedModules", "history", "highChurn", "rankedUnclaimed"],
109
+ "properties": {
110
+ "sourceModules": { "type": "integer", "minimum": 0 },
111
+ "claimedModules": { "type": "integer", "minimum": 0 },
112
+ "unclaimedModules": { "type": "integer", "minimum": 0 },
113
+ "history": {
114
+ "type": "object",
115
+ "required": ["available", "maxCommits", "commitsRead"],
116
+ "properties": {
117
+ "available": { "type": "boolean" },
118
+ "maxCommits": { "type": "integer", "minimum": 1 },
119
+ "commitsRead": { "type": "integer", "minimum": 0 }
120
+ },
121
+ "additionalProperties": false
122
+ },
123
+ "highChurn": {
124
+ "description": "How many of the up-to-20 most changed source modules carry a claim.",
125
+ "type": "object",
126
+ "required": ["modules", "claimed"],
127
+ "properties": {
128
+ "modules": { "type": "integer", "minimum": 0, "maximum": 20 },
129
+ "claimed": { "type": "integer", "minimum": 0, "maximum": 20 }
130
+ },
131
+ "additionalProperties": false
132
+ },
133
+ "rankedUnclaimed": {
134
+ "description": "Up to ten concrete unclaimed modules, ordered by changes in the bounded history window; path-risk signals break ties and are reported explicitly.",
135
+ "type": "array",
136
+ "maxItems": 10,
137
+ "items": {
138
+ "type": "object",
139
+ "required": ["file", "changes", "riskSignals"],
140
+ "properties": {
141
+ "file": { "$ref": "urn:claimspec:v1:common#/$defs/repoPath" },
142
+ "changes": { "type": "integer", "minimum": 0 },
143
+ "riskSignals": {
144
+ "type": "array",
145
+ "uniqueItems": true,
146
+ "items": { "enum": ["security", "money"] }
147
+ }
148
+ },
149
+ "additionalProperties": false
150
+ }
151
+ }
152
+ },
153
+ "additionalProperties": false
154
+ },
105
155
  "counts": {
106
156
  "type": "object",
107
157
  "required": [
@@ -1,5 +1,5 @@
1
1
  import { gate } from '../baseline/baseline.mjs';
2
- import { summarize, formatVerdict } from '../render.mjs';
2
+ import { coAuthorshipWarning, summarize, formatVerdict } from '../render.mjs';
3
3
 
4
4
  export const HEADING = '## TEST BLINDSPOT CONTEXT';
5
5
  /** First line of every markdown brief, so a poster can find and update its own note instead of adding another. */
@@ -98,7 +98,7 @@ export function renderBriefText(brief, { hasBaseline, total, independence }) {
98
98
  }
99
99
  // L3: a kill written in the same change as the code it guards is not
100
100
  // independent evidence. One line, no per-item noise.
101
- if (independence?.coAuthored) lines.push('', `${independence.coAuthored} of ${independence.kills} kills are co-authored with the code they defend — the test and the code were written in the same change, so those kills are not independent evidence.`);
101
+ if (independence?.coAuthored) lines.push('', coAuthorshipWarning(independence.coAuthored, independence.kills));
102
102
  if (brief.next) lines.push('', `NEXT [${brief.next.action}]: ${brief.next.command}`, ` why: ${brief.next.why}`);
103
103
  if (brief.items.length === 0) {
104
104
  lines.push('', 'Every probed claim is defended. Keep it that way: new claims need a fault and a test that fails on it.');
@@ -28,7 +28,7 @@ import { globToRegExp } from '../util/glob.mjs';
28
28
  export const SOURCE_EXT = new Set(['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts', '.jsx', '.tsx', '.py']);
29
29
  const TEST_RE = /\.(test|spec)\.[cm]?[jt]sx?$/;
30
30
  // Python names tests by file, not by a dotted suffix: `test_foo.py`, `foo_test.py`.
31
- const isTestFile = (f) => TEST_RE.test(f) || IS_PY_TEST.test(f);
31
+ export const isTestFile = (f) => TEST_RE.test(f) || IS_PY_TEST.test(f);
32
32
 
33
33
  /** Files that are source by extension but never carry claims. Printed by --explain. */
34
34
  export const DEFAULT_EXCLUDES = Object.freeze([
@@ -42,6 +42,10 @@ export const DEFAULT_EXCLUDES = Object.freeze([
42
42
  '**/__snapshots__/**',
43
43
  '.testguard/**',
44
44
  ]);
45
+ const DEFAULT_EXCLUDE_RES = DEFAULT_EXCLUDES.map(globToRegExp);
46
+
47
+ /** Whether a source-shaped path is outside TestGuard's claimable module surface. */
48
+ export const isDefaultExcluded = (file) => DEFAULT_EXCLUDE_RES.some((re) => re.test(file));
45
49
 
46
50
  export const defaultIgnorePath = (projectDir) => join(projectDir, 'testguard.ignore.json');
47
51
 
@@ -158,7 +162,7 @@ export function computeChangedGate({ projectDir, ref, includeDirty = false, excl
158
162
 
159
163
  const pathEntries = ignore.entries.filter((e) => e.kind === 'path').map((e) => ({ entry: e, re: globToRegExp(e.pattern), expired: isExpired(e, now), files: [] }));
160
164
  const userExcludes = exclude.map((g) => ({ glob: g, re: globToRegExp(g) }));
161
- const defaultExcludes = DEFAULT_EXCLUDES.map((g) => ({ glob: g, re: globToRegExp(g) }));
165
+ const defaultExcludes = DEFAULT_EXCLUDES.map((g, i) => ({ glob: g, re: DEFAULT_EXCLUDE_RES[i] }));
162
166
 
163
167
  const excluded = [];
164
168
  const covered = [];
@@ -5,6 +5,8 @@ export const formatVerdict = (v, provisional = false) => (v === 'killed' ? 'kill
5
5
 
6
6
  export const PROVISIONAL_WARNING = (n) => `PROVISIONAL — confirmRuns ${n} (< 3): nothing below is confirmed. Verdicts carry a "?"; this evidence cannot be frozen into a baseline. Re-run with --confirm 3 before trusting it.`;
7
7
 
8
+ export const coAuthorshipWarning = (coAuthored, kills) => `${coAuthored} of ${kills} kills are co-authored with the code they defend — the test and the code were written in the same change, so those kills are not independent evidence.`;
9
+
8
10
  export function renderRecord(r, { provisional = false, showDiscovered = true } = {}) {
9
11
  const head = `${formatVerdict(r.verdict, provisional).padEnd(15)} ${r.claim.id}/${r.subject.id}`.padEnd(38);
10
12
  let why = '';
@@ -32,7 +34,10 @@ export function renderSummary(records, run) {
32
34
  const unproven = records.filter((r) => r.verdict !== 'killed');
33
35
  const claims = new Set(unproven.map((r) => r.claim.id)).size;
34
36
  const where = run ? ` Probed ${run.repo.snapshot ? `working tree (snapshot ${run.repo.snapshot.slice(0, 7)} of ${run.repo.head.slice(0, 7)})` : run.mode === 'in-place' ? `in place at ${run.repo.head.slice(0, 7)}${run.repo.dirty ? ' (dirty)' : ''}` : run.repo.head.slice(0, 7)}.` : '';
35
- return `${run?.provisional ? 'PROVISIONAL: ' : ''}${records.length} faults probed: ${parts.join(', ')}. ${unproven.length} unproven fault${unproven.length === 1 ? '' : 's'} across ${claims} claim${claims === 1 ? '' : 's'}.${where}`;
37
+ const kills = records.filter((r) => r.verdict === 'killed');
38
+ const coAuthored = kills.filter((r) => r.detail.independence?.class === 'co-authored').length;
39
+ const quality = coAuthored ? `\n${coAuthorshipWarning(coAuthored, kills.length)}` : '';
40
+ return `${run?.provisional ? 'PROVISIONAL: ' : ''}${records.length} faults probed: ${parts.join(', ')}. ${unproven.length} unproven fault${unproven.length === 1 ? '' : 's'} across ${claims} claim${claims === 1 ? '' : 's'}.${where}${quality}`;
36
41
  }
37
42
 
38
43
  /** Survivors first, then by rank score; killed last. */
@@ -6,13 +6,18 @@ import { gate } from '../baseline/baseline.mjs';
6
6
  import { hashFile, sha256 } from '../util/hash.mjs';
7
7
  import { resolveDefenders } from '../probe/runners/shared.mjs';
8
8
  import { discoverDefenders } from '../probe/discover.mjs';
9
- import { sortForReport } from '../render.mjs';
9
+ import { coAuthorshipWarning, sortForReport } from '../render.mjs';
10
10
  import { computeChangedGate, defaultIgnorePath } from '../gate/changed.mjs';
11
11
  import { headSha, isAncestor } from '../git.mjs';
12
12
  import { checkAnchorLocations } from '../claims/anchors.mjs';
13
+ import { computeClaimedSurface, needsClaimExpansion } from './surface.mjs';
13
14
 
14
15
  export const faultContentHash = (fault) => sha256(`${fault.find}\n${fault.replace}`);
15
16
 
17
+ const candidateReason = (surface, candidate) => surface.history.available
18
+ ? `${candidate.file}, the highest-churn unclaimed module in the last ${surface.history.commitsRead} commit${surface.history.commitsRead === 1 ? '' : 's'}`
19
+ : `${candidate.file}, the first unclaimed module by path because git history is unavailable`;
20
+
16
21
  const P = (projectDir) => ({
17
22
  claims: defaultClaimsPath(projectDir),
18
23
  evidence: join(projectDir, '.testguard', 'evidence.json'),
@@ -68,6 +73,11 @@ export function computeStatus({ projectDir, toolVersion = '0.0.0', generatedAt =
68
73
  };
69
74
 
70
75
  if (!existsSync(paths.claims)) {
76
+ doc.surface = computeClaimedSurface({ projectDir });
77
+ const candidate = doc.surface.rankedUnclaimed[0];
78
+ if (candidate && !changes?.uncovered.length) {
79
+ doc.next = { action: 'scaffold', command: `testguard scaffold ${candidate.file}`, why: `${doc.surface.sourceModules} source module${doc.surface.sourceModules === 1 ? '' : 's'} exist and none carries a claim. Start with ${candidateReason(doc.surface, candidate)}.`, file: candidate.file };
80
+ }
71
81
  if (changes?.uncovered.length) {
72
82
  const u = changes.uncovered[0];
73
83
  doc.next = { action: 'scaffold', command: `testguard scaffold ${u.file}`, why: `No testguard.claims.json in this project, and ${unclaimedWhy()}`, file: u.file };
@@ -79,6 +89,7 @@ export function computeStatus({ projectDir, toolVersion = '0.0.0', generatedAt =
79
89
  const claims = loadClaims(paths.claims);
80
90
  doc.counts.claims = claims.claims.length;
81
91
  doc.counts.faults = claims.claims.reduce((n, c) => n + c.faults.length, 0);
92
+ doc.surface = computeClaimedSurface({ projectDir, claims });
82
93
  const faultIndex = new Map();
83
94
  for (const c of claims.claims) for (const f of c.faults) faultIndex.set(`${c.id}/${f.id}`, { claim: c, fault: f });
84
95
 
@@ -236,6 +247,15 @@ export function computeStatus({ projectDir, toolVersion = '0.0.0', generatedAt =
236
247
  } else {
237
248
  doc.next = { action: 'none', command: 'testguard probe', why: unprovenTotal ? `${unprovenTotal} baselined finding${unprovenTotal === 1 ? '' : 's'} remain; nothing new. Re-probe after changing code, tests or claims.` : 'Every claim is defended. Re-probe after changing code, tests or claims; add a claim for every new invariant.' };
238
249
  }
250
+ if (needsClaimExpansion(doc.surface)) {
251
+ const candidate = doc.surface.rankedUnclaimed[0];
252
+ doc.next = {
253
+ action: 'claim',
254
+ command: `testguard scaffold ${candidate.file}`,
255
+ why: `The existing claims are defended, but only ${doc.surface.claimedModules} of ${doc.surface.sourceModules} source modules carry a claim. Expand the denominator at ${candidateReason(doc.surface, candidate)}.`,
256
+ file: candidate.file,
257
+ };
258
+ }
239
259
  return doc;
240
260
  }
241
261
 
@@ -246,6 +266,15 @@ export function renderStatus(doc) {
246
266
  }
247
267
  lines.push(`state: ${doc.state}${doc.provisional ? ' (provisional)' : ''} — ${doc.counts.claims} claims / ${doc.counts.faults} faults` + (doc.counts.byVerdict ? `; ${Object.entries(doc.counts.byVerdict).map(([k, v]) => `${v} ${k}`).join(', ')}; ${doc.counts.new ?? 0} new, ${doc.counts.baselined ?? 0} baselined` : '')
248
268
  );
269
+ if (doc.surface) {
270
+ const s = doc.surface;
271
+ const history = s.history.available ? `last ${s.history.commitsRead} commit${s.history.commitsRead === 1 ? '' : 's'}` : 'git history unavailable';
272
+ lines.push(`surface: ${s.claimedModules} of ${s.sourceModules} source modules carry a claim; ${s.highChurn.claimed} of ${s.highChurn.modules} highest-churn modules claimed (${history})`);
273
+ for (const item of s.rankedUnclaimed.slice(0, 3)) lines.push(`UNCLAIMED ${item.file} — ${item.changes} change${item.changes === 1 ? '' : 's'} in history window${item.riskSignals.length ? `; path risk: ${item.riskSignals.join(', ')}` : ''}`);
274
+ }
275
+ const coAuthored = doc.counts.killedCoAuthored ?? 0;
276
+ const kills = doc.counts.byVerdict?.killed ?? 0;
277
+ if (coAuthored) lines.push(`evidence: ${coAuthorshipWarning(coAuthored, kills)}`);
249
278
  if (doc.changes) {
250
279
  const c = doc.changes;
251
280
  lines.push(`changes: ${c.changed} file${c.changed === 1 ? '' : 's'} since ${c.ref}; ${c.evaluated} evaluated, ${c.excluded} excluded, ${c.uncovered.length} unclaimed`);
@@ -0,0 +1,72 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { extname, join } from 'node:path';
3
+ import { git } from '../git.mjs';
4
+ import { SOURCE_EXT, isDefaultExcluded, isTestFile } from '../gate/changed.mjs';
5
+ import { walk } from '../util/glob.mjs';
6
+
7
+ export const HISTORY_COMMITS = 200;
8
+ export const HIGH_CHURN_MODULES = 20;
9
+ export const RANKED_UNCLAIMED = 10;
10
+
11
+ const RISK_SIGNALS = Object.freeze([
12
+ ['security', /(^|[._/-])(auth|authn|authz|authorization|credential|crypto|login|password|permission|rbac|secret|security|session|token)([._/-]|$)/i],
13
+ ['money', /(^|[._/-])(billing|checkout|invoice|money|payment|payout|price|pricing|refund)([._/-]|$)/i],
14
+ ]);
15
+
16
+ const pathRiskSignals = (file) => RISK_SIGNALS.filter(([, re]) => re.test(file)).map(([name]) => name);
17
+
18
+ function listedFiles(projectDir) {
19
+ try {
20
+ return git(['ls-files', '-co', '--exclude-standard', '-z', '--', '.'], projectDir).split('\0').filter(Boolean);
21
+ } catch {
22
+ return walk(projectDir);
23
+ }
24
+ }
25
+
26
+ function claimableModules(projectDir) {
27
+ return listedFiles(projectDir).filter((file) =>
28
+ existsSync(join(projectDir, file)) &&
29
+ SOURCE_EXT.has(extname(file)) &&
30
+ !isTestFile(file) &&
31
+ !isDefaultExcluded(file));
32
+ }
33
+
34
+ function recentChanges(projectDir, sourceModules) {
35
+ const counts = new Map();
36
+ try {
37
+ const commitsRead = Number(git(['rev-list', '--count', `--max-count=${HISTORY_COMMITS}`, 'HEAD', '--', '.'], projectDir));
38
+ const paths = git(['log', '--relative', '-n', String(HISTORY_COMMITS), '--pretty=format:', '--name-only', '-z', '--', '.'], projectDir).split('\0').filter(Boolean);
39
+ for (const file of paths) if (sourceModules.has(file)) counts.set(file, (counts.get(file) ?? 0) + 1);
40
+ return { available: true, commitsRead, counts };
41
+ } catch {
42
+ return { available: false, commitsRead: 0, counts };
43
+ }
44
+ }
45
+
46
+ /**
47
+ * The denominator `probe` cannot provide: current source modules carrying at
48
+ * least one fault, plus concrete unclaimed modules ranked by bounded git
49
+ * history. Churn and path-risk remain separate facts; there is no score.
50
+ */
51
+ export function computeClaimedSurface({ projectDir, claims }) {
52
+ const modules = new Set(claimableModules(projectDir));
53
+ const claimedFiles = new Set((claims?.claims ?? []).flatMap((claim) => claim.faults.map((fault) => fault.file)));
54
+ const claimed = new Set([...modules].filter((file) => claimedFiles.has(file)));
55
+ const history = recentChanges(projectDir, modules);
56
+ const describe = (file) => ({ file, changes: history.counts.get(file) ?? 0, riskSignals: pathRiskSignals(file) });
57
+ const ranked = [...modules].map(describe).sort((a, b) =>
58
+ b.changes - a.changes || b.riskSignals.length - a.riskSignals.length || a.file.localeCompare(b.file));
59
+ const highChurn = ranked.slice(0, Math.min(HIGH_CHURN_MODULES, ranked.length));
60
+ const rankedUnclaimed = ranked.filter((item) => !claimed.has(item.file)).slice(0, RANKED_UNCLAIMED);
61
+ return {
62
+ sourceModules: modules.size,
63
+ claimedModules: claimed.size,
64
+ unclaimedModules: modules.size - claimed.size,
65
+ history: { available: history.available, maxCommits: HISTORY_COMMITS, commitsRead: history.commitsRead },
66
+ highChurn: { modules: highChurn.length, claimed: highChurn.filter((item) => claimed.has(item.file)).length },
67
+ rankedUnclaimed,
68
+ };
69
+ }
70
+
71
+ /** Expand the claim surface only when most current source modules are unseen. */
72
+ export const needsClaimExpansion = (surface) => surface.unclaimedModules > surface.claimedModules && surface.rankedUnclaimed.length > 0;
@@ -58,6 +58,11 @@ describe('renderSummary', () => {
58
58
  expect(line).toContain(`${unproven.length} unproven fault`);
59
59
  expect(line).toContain(`across ${new Set(unproven.map((r) => r.claim.id)).size} claim`);
60
60
  });
61
+ it('prints the co-authorship caveat on the human probe surface', () => {
62
+ const records = structuredClone(evidence.records.filter((r) => r.verdict === 'killed'));
63
+ records[0].detail.independence = { class: 'co-authored', defenderCommit: 'a'.repeat(40), targetCommit: 'a'.repeat(40), sameAuthor: true };
64
+ expect(renderSummary(records)).toContain(`1 of ${records.length} kills are co-authored with the code they defend`);
65
+ });
61
66
  });
62
67
 
63
68
  describe('renderRecord', () => {