testguard-cli 0.2.1__tar.gz → 0.3.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 (144) hide show
  1. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/AGENTS.md +10 -0
  2. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/CHANGELOG.md +41 -0
  3. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/PKG-INFO +27 -4
  4. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/README.md +26 -3
  5. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/action.yml +1 -1
  6. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/package-lock.json +2 -2
  7. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/package.json +1 -1
  8. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/packaging/homebrew/testguard.rb +1 -1
  9. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/pyproject.toml +1 -1
  10. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/GATE-SEMANTICS.md +6 -0
  11. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/README.md +2 -1
  12. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/examples/brief.json +6 -1
  13. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/examples/evidence-provisional.json +8 -4
  14. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/examples/evidence.json +12 -6
  15. testguard_cli-0.3.1/spec/conformance/examples/status.json +60 -0
  16. testguard_cli-0.3.1/spec/conformance/invalid/status.clean-with-new-findings.json +60 -0
  17. testguard_cli-0.3.1/spec/conformance/invalid/status.write-test-without-target.json +54 -0
  18. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/lib/validate.mjs +9 -1
  19. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/schemas/brief.schema.json +11 -0
  20. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/schemas/evidence.schema.json +5 -1
  21. testguard_cli-0.3.1/spec/schemas/status.schema.json +110 -0
  22. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/brief/brief.mjs +4 -3
  23. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/cli.mjs +9 -1
  24. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/commands/baseline.mjs +6 -1
  25. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/commands/brief.mjs +10 -3
  26. testguard_cli-0.3.1/src/commands/init.mjs +15 -0
  27. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/commands/probe.mjs +8 -2
  28. testguard_cli-0.3.1/src/commands/status.mjs +10 -0
  29. testguard_cli-0.3.1/src/init/init.mjs +82 -0
  30. testguard_cli-0.3.1/src/init/templates/SKILL.md +63 -0
  31. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/probe/probe.mjs +1 -1
  32. testguard_cli-0.3.1/src/status/status.mjs +163 -0
  33. testguard_cli-0.3.1/test/init.test.mjs +57 -0
  34. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/probe.fixture.test.mjs +25 -0
  35. testguard_cli-0.3.1/test/status.test.mjs +118 -0
  36. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/testguard.claims.json +32 -0
  37. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.codex/hooks.json +0 -0
  38. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.gitattributes +0 -0
  39. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  40. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  41. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  42. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.github/dependabot.yml +0 -0
  43. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.github/scripts/install-smoke.mjs +0 -0
  44. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.github/scripts/sync-release-version.mjs +0 -0
  45. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.github/workflows/auto-merge.yml +0 -0
  46. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.github/workflows/ci.yml +0 -0
  47. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.github/workflows/release.yml +0 -0
  48. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.github/workflows/scheduled-release.yml +0 -0
  49. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.github/workflows/supply-chain.yml +0 -0
  50. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.gitignore +0 -0
  51. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.npmignore +0 -0
  52. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.npmrc +0 -0
  53. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/.pre-commit-hooks.yaml +0 -0
  54. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/CLAUDE.md +0 -0
  55. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/CODE_OF_CONDUCT.md +0 -0
  56. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/CONTRIBUTING.md +0 -0
  57. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/LICENSE +0 -0
  58. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/PRIVACY.md +0 -0
  59. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/SECURITY.md +0 -0
  60. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/SUPPORT.md +0 -0
  61. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/bench/README.md +0 -0
  62. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/cli/testguard.mjs +0 -0
  63. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/fixtures/known-answer/.gitignore +0 -0
  64. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/fixtures/known-answer/README.md +0 -0
  65. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/fixtures/known-answer/expected.json +0 -0
  66. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/fixtures/known-answer/package.json +0 -0
  67. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/fixtures/known-answer/src/export.mjs +0 -0
  68. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/fixtures/known-answer/src/redact.mjs +0 -0
  69. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/fixtures/known-answer/test/flaky.test.mjs +0 -0
  70. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/fixtures/known-answer/test/redact.test.mjs +0 -0
  71. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/fixtures/known-answer/testguard.claims.json +0 -0
  72. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/fixtures/known-answer/vitest.config.mjs +0 -0
  73. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/examples/baseline.json +0 -0
  74. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/examples/calibration.json +0 -0
  75. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/examples/claims.json +0 -0
  76. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/examples/ignore.json +0 -0
  77. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
  78. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/baseline.zero-count.json +0 -0
  79. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
  80. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
  81. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
  82. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
  83. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/claims.absolute-path.json +0 -0
  84. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
  85. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
  86. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/claims.noop-fault.json +0 -0
  87. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
  88. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
  89. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
  90. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
  91. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
  92. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
  93. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
  94. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
  95. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
  96. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
  97. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
  98. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
  99. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
  100. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
  101. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/invalid/ignore.short-reason.json +0 -0
  102. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/conformance/schemas.test.mjs +0 -0
  103. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/lib/fingerprint.mjs +0 -0
  104. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/schemas/baseline.schema.json +0 -0
  105. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/schemas/calibration.schema.json +0 -0
  106. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/schemas/claims.schema.json +0 -0
  107. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/schemas/common.schema.json +0 -0
  108. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/spec/schemas/ignore.schema.json +0 -0
  109. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/baseline/baseline.mjs +0 -0
  110. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/claims/annotations.mjs +0 -0
  111. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/claims/load.mjs +0 -0
  112. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/commands/claims.mjs +0 -0
  113. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/commands/scaffold.mjs +0 -0
  114. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/evidence/writer.mjs +0 -0
  115. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/git.mjs +0 -0
  116. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/probe/classify.mjs +0 -0
  117. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/probe/discover.mjs +0 -0
  118. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/probe/inject.mjs +0 -0
  119. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/probe/rank.mjs +0 -0
  120. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/probe/runner-vitest.mjs +0 -0
  121. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/probe/worktree.mjs +0 -0
  122. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/render.mjs +0 -0
  123. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/scaffold/producers.mjs +0 -0
  124. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/scaffold/scaffold.mjs +0 -0
  125. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/util/glob.mjs +0 -0
  126. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/src/util/hash.mjs +0 -0
  127. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/annotations.test.mjs +0 -0
  128. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/baseline.test.mjs +0 -0
  129. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/brief.test.mjs +0 -0
  130. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/classify.test.mjs +0 -0
  131. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/discover.test.mjs +0 -0
  132. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/glob.test.mjs +0 -0
  133. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/inject.test.mjs +0 -0
  134. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/probe-preconditions.test.mjs +0 -0
  135. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/rank-aliases.test.mjs +0 -0
  136. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/runner-command.test.mjs +0 -0
  137. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/runner-vitest.test.mjs +0 -0
  138. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/scaffold.test.mjs +0 -0
  139. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/snapshot.test.mjs +0 -0
  140. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/worktree.test.mjs +0 -0
  141. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/test/writer.test.mjs +0 -0
  142. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/testguard_cli/__init__.py +0 -0
  143. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/testguard_cli/wrapper.py +0 -0
  144. {testguard_cli-0.2.1 → testguard_cli-0.3.1}/vitest.config.mjs +0 -0
@@ -31,6 +31,7 @@ node cli/testguard.mjs scaffold src/probe/classify.mjs --json # what the produ
31
31
  - `fixtures/known-answer/expected.json` — the oracle. Never edit it to match output.
32
32
  - `testguard.claims.json` — claims about this codebase, probed in CI.
33
33
  - `src/scaffold/producers.mjs` — the five fault shapes. Deterministic line heuristics; no AST, no LLM. A new shape needs a synthetic-file test and a README row.
34
+ - `src/status/status.mjs` — the state machine every rendering derives from. A new state or action is a spec change (`status.schema.json`) and a skill-template change (`src/init/templates/SKILL.md`) in the same PR.
34
35
 
35
36
  ## Rules
36
37
 
@@ -55,3 +56,12 @@ node cli/testguard.mjs scaffold src/probe/classify.mjs --json # what the produ
55
56
  Dependabot minor/patch PRs and automated release PRs auto-merge when the exact
56
57
  CI run is green on all three Node legs. Majors and anything else wait for a
57
58
  human. Release publishing is OIDC-only; there are no tokens in this repo.
59
+
60
+ <!-- testguard:begin -->
61
+ ## TestGuard
62
+
63
+ This project's tests are verified by [TestGuard](https://github.com/raccioly/testguard).
64
+ Before writing or changing tests, run `testguard status --json` and follow `next`.
65
+ The full operating loop and the verdict table are in `.claude/skills/testguard/SKILL.md`.
66
+ Never make a fault die by editing `testguard.claims.json`; write the test. Claim edits are recorded in the evidence.
67
+ <!-- testguard:end -->
@@ -7,6 +7,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.1] - 2026-09-17
11
+
12
+ Dogfooding `init` and `status` on this repository.
13
+
14
+ ### Fixed
15
+
16
+ - `init` no longer adds per-file `.gitignore` lines to a project that already
17
+ ignores `.testguard/` wholesale; it notes instead that `baseline.json` is
18
+ meant to be committed.
19
+ - The brief no longer says "everything is new" when nothing is unproven.
20
+
21
+ ### Changed
22
+
23
+ - This repository now carries its own `AGENTS.md` TestGuard section, written
24
+ by `testguard init`.
25
+
26
+ ## [0.3.0] - 2026-09-17
27
+
28
+ The agent operating layer. TestGuard is meant to be run by AI agents; this
29
+ release gives them one source of truth, an installable loop, and makes the
30
+ one cheap way to game it visible.
31
+
32
+ ### Added
33
+
34
+ - **`testguard status --json`** — the machine entry point. A new spec kind
35
+ (`status.schema.json`): `state` (no-claims · unprobed · evidence-stale ·
36
+ provisional-only · unproven · clean) and the one `next` action, computed
37
+ from claims, evidence, baseline and the working tree. Exit 0 clean, 1
38
+ unproven/stale, 2 nothing to probe yet.
39
+ - **`testguard init`** — installs `.claude/skills/testguard/SKILL.md`, the
40
+ `brief --text` SessionStart hook (merged into an existing
41
+ `.claude/settings.json`), an `AGENTS.md` section and the `.gitignore`
42
+ lines. Idempotent; `--force` replaces the skill.
43
+ - **Fault edits are visible.** Evidence records `subject.contentHash`;
44
+ `status` lists `changedFaults` (a fault edited after it was probed, with
45
+ its previous verdict) and makes `review-fault-change` the next action when
46
+ the edited fault had survived. Gate rule 8. Self-claim
47
+ `TG-FAULT-EDIT-VISIBLE`.
48
+ - `--json` on every command; `probe` and `baseline` emit the status document
49
+ plus their result. `brief` carries `next` and prints it.
50
+
10
51
  ## [0.2.1] - 2026-09-17
11
52
 
12
53
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: testguard-cli
3
- Version: 0.2.1
3
+ Version: 0.3.1
4
4
  Summary: Proves a test suite actually defends the claims a project makes: injects the faults those claims forbid and reports every one the tests miss. Python wrapper for 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
@@ -76,7 +76,7 @@ tests were written against the survivors, 39/39 were killed.
76
76
  | npm | `npm i -D testguard-cli` then `npx testguard probe` |
77
77
  | pip | `pip install testguard-cli` then `testguard probe` (needs Node ≥ 20) |
78
78
  | Homebrew | `brew tap raccioly/tap && brew install testguard` |
79
- | GitHub Action | `uses: raccioly/testguard@v0.2.1` — see [`action.yml`](./action.yml) |
79
+ | GitHub Action | `uses: raccioly/testguard@v0.3.1` — see [`action.yml`](./action.yml) |
80
80
  | pre-commit | `repo: https://github.com/raccioly/testguard`, hooks `testguard-claims`, `testguard-probe` |
81
81
 
82
82
  Projects that set `min-release-age` in `.npmrc` cannot see a version published
@@ -86,6 +86,8 @@ less than that many days ago (`ENOVERSIONS`); install that one with
86
86
  ## How it works
87
87
 
88
88
  ```bash
89
+ npx testguard-cli init # install the agent layer: skill, session-start hook, AGENTS.md section
90
+ npx testguard-cli status --json # where the project is and the ONE next action — the machine entry point
89
91
  npx testguard-cli claims # what does this project claim, and is every claim probeable?
90
92
  npx testguard-cli probe # try to falsify each claim; report what the tests missed
91
93
  npx testguard-cli baseline # freeze today's unproven findings; from now on only new ones gate
@@ -163,6 +165,27 @@ npx testguard-cli scaffold src/x.ts # propose faults for a file, as a draft to
163
165
  `--text` prints only, and exits 0 silently when there is no evidence yet,
164
166
  so the hook can never break a session.
165
167
 
168
+ ### Built for agents to run
169
+
170
+ TestGuard is meant to be driven by an AI agent, not typed by a person. Three
171
+ things make that safe:
172
+
173
+ - **One source of truth.** `testguard status --json` computes `state` and the
174
+ one `next` action from the claims file, the evidence, the baseline and the
175
+ working tree. Every human rendering — the CLI text, the session-start
176
+ brief, the skill — derives from it, so they cannot disagree. Every command
177
+ accepts `--json`.
178
+ - **An installable operating loop.** `testguard init` writes
179
+ `.claude/skills/testguard/SKILL.md` (state → action, verdict → the only
180
+ acceptable fix, the two-gate rule for any test the agent writes), the
181
+ `brief --text` session-start hook, an `AGENTS.md` section and the
182
+ `.gitignore` lines. Idempotent.
183
+ - **Gaming is visible.** The cheapest way to make a survivor disappear is to
184
+ weaken its fault, not to write a test. Evidence records every fault's
185
+ content hash; `status` lists any fault edited after it survived, with its
186
+ previous verdict, and makes reviewing that edit the next action. Editing a
187
+ claim is allowed — claims can be wrong — but it is never invisible.
188
+
166
189
  ### Authoring faults mechanically
167
190
 
168
191
  Writing faults by hand means reading the code to find exact anchors. Two
@@ -216,8 +239,8 @@ through every verdict.
216
239
 
217
240
  ## Status
218
241
 
219
- **v0.2.** Five commands, vitest runner, hand-authored faults plus a
220
- mechanical scaffold for the five common shapes. The contract
242
+ **v0.3.** Seven commands, vitest runner, hand-authored faults plus a
243
+ mechanical scaffold, and an agent operating layer (`status`, `init`). The contract
221
244
  spine — six JSON Schemas shared with the other Guard tools — is under
222
245
  [`spec/`](spec/). One exact-pinned runtime dependency (`ajv`, for schema validation); Node ≥ 20.
223
246
 
@@ -52,7 +52,7 @@ tests were written against the survivors, 39/39 were killed.
52
52
  | npm | `npm i -D testguard-cli` then `npx testguard probe` |
53
53
  | pip | `pip install testguard-cli` then `testguard probe` (needs Node ≥ 20) |
54
54
  | Homebrew | `brew tap raccioly/tap && brew install testguard` |
55
- | GitHub Action | `uses: raccioly/testguard@v0.2.1` — see [`action.yml`](./action.yml) |
55
+ | GitHub Action | `uses: raccioly/testguard@v0.3.1` — see [`action.yml`](./action.yml) |
56
56
  | pre-commit | `repo: https://github.com/raccioly/testguard`, hooks `testguard-claims`, `testguard-probe` |
57
57
 
58
58
  Projects that set `min-release-age` in `.npmrc` cannot see a version published
@@ -62,6 +62,8 @@ less than that many days ago (`ENOVERSIONS`); install that one with
62
62
  ## How it works
63
63
 
64
64
  ```bash
65
+ npx testguard-cli init # install the agent layer: skill, session-start hook, AGENTS.md section
66
+ npx testguard-cli status --json # where the project is and the ONE next action — the machine entry point
65
67
  npx testguard-cli claims # what does this project claim, and is every claim probeable?
66
68
  npx testguard-cli probe # try to falsify each claim; report what the tests missed
67
69
  npx testguard-cli baseline # freeze today's unproven findings; from now on only new ones gate
@@ -139,6 +141,27 @@ npx testguard-cli scaffold src/x.ts # propose faults for a file, as a draft to
139
141
  `--text` prints only, and exits 0 silently when there is no evidence yet,
140
142
  so the hook can never break a session.
141
143
 
144
+ ### Built for agents to run
145
+
146
+ TestGuard is meant to be driven by an AI agent, not typed by a person. Three
147
+ things make that safe:
148
+
149
+ - **One source of truth.** `testguard status --json` computes `state` and the
150
+ one `next` action from the claims file, the evidence, the baseline and the
151
+ working tree. Every human rendering — the CLI text, the session-start
152
+ brief, the skill — derives from it, so they cannot disagree. Every command
153
+ accepts `--json`.
154
+ - **An installable operating loop.** `testguard init` writes
155
+ `.claude/skills/testguard/SKILL.md` (state → action, verdict → the only
156
+ acceptable fix, the two-gate rule for any test the agent writes), the
157
+ `brief --text` session-start hook, an `AGENTS.md` section and the
158
+ `.gitignore` lines. Idempotent.
159
+ - **Gaming is visible.** The cheapest way to make a survivor disappear is to
160
+ weaken its fault, not to write a test. Evidence records every fault's
161
+ content hash; `status` lists any fault edited after it survived, with its
162
+ previous verdict, and makes reviewing that edit the next action. Editing a
163
+ claim is allowed — claims can be wrong — but it is never invisible.
164
+
142
165
  ### Authoring faults mechanically
143
166
 
144
167
  Writing faults by hand means reading the code to find exact anchors. Two
@@ -192,8 +215,8 @@ through every verdict.
192
215
 
193
216
  ## Status
194
217
 
195
- **v0.2.** Five commands, vitest runner, hand-authored faults plus a
196
- mechanical scaffold for the five common shapes. The contract
218
+ **v0.3.** Seven commands, vitest runner, hand-authored faults plus a
219
+ mechanical scaffold, and an agent operating layer (`status`, `init`). The contract
197
220
  spine — six JSON Schemas shared with the other Guard tools — is under
198
221
  [`spec/`](spec/). One exact-pinned runtime dependency (`ajv`, for schema validation); Node ≥ 20.
199
222
 
@@ -38,7 +38,7 @@ inputs:
38
38
  version:
39
39
  description: 'testguard-cli version to run'
40
40
  required: false
41
- default: '0.2.1'
41
+ default: '0.3.1'
42
42
  node-version:
43
43
  description: 'Node.js version (20+)'
44
44
  required: false
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "testguard-cli",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "testguard-cli",
9
- "version": "0.2.1",
9
+ "version": "0.3.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.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Proves a test suite defends the claims a project makes: injects the faults those claims forbid and reports every one the tests fail to detect.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -13,7 +13,7 @@
13
13
  class Testguard < Formula
14
14
  desc "Proves a test suite defends the claims a project makes"
15
15
  homepage "https://github.com/raccioly/testguard"
16
- url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.2.1.tgz"
16
+ url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.3.1.tgz"
17
17
  sha256 "385d69f9d3c153b934d9c1cb6a2c754eb9b221b0a8c0ba8b805858384d2d4678"
18
18
  license "MIT"
19
19
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "testguard-cli"
7
- version = "0.2.1"
7
+ version = "0.3.1"
8
8
  description = "Proves a test suite actually defends the claims a project makes: injects the faults those claims forbid and reports every one the tests miss. Python wrapper for the Node.js CLI (requires Node.js 20+)."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -50,6 +50,12 @@ Rules that follow from the table:
50
50
  snapshot the working tree — otherwise the answer looks right and is not.
51
51
  7. **Never a single global score.** Output is per claim, ranked. Blindness is
52
52
  concentrated, and one number hides where.
53
+ 8. **A changed fault is a finding.** Editing a claim is legitimate — claims
54
+ can be wrong — but the cheapest way to make a survivor disappear without
55
+ writing a test is to weaken its fault. Evidence records each fault's
56
+ content hash; the status document lists every fault whose content
57
+ changed since it was probed, with its previous verdict. The change is
58
+ allowed; it is never invisible.
53
59
 
54
60
  ## Baseline and delta
55
61
 
@@ -20,7 +20,8 @@ The pattern all such tools run:
20
20
  | `baseline` | [`schemas/baseline.schema.json`](schemas/baseline.schema.json) | Frozen fingerprints of existing debt. Gate only what is new. |
21
21
  | `ignore` | [`schemas/ignore.schema.json`](schemas/ignore.schema.json) | Reviewable scoping. Every entry has a reason. |
22
22
  | `calibration` | [`schemas/calibration.schema.json`](schemas/calibration.schema.json) | P(finding is real) per bucket, with a Wilson interval and the sample size behind it. |
23
- | `brief` | [`schemas/brief.schema.json`](schemas/brief.schema.json) | What to tell an agent before it writes code — ranked, capped, never a single score. |
23
+ | `brief` | [`schemas/brief.schema.json`](schemas/brief.schema.json) | What to tell an agent before it writes code — ranked, capped, never a single score, and carrying the one next action. |
24
+ | `status` | [`schemas/status.schema.json`](schemas/status.schema.json) | Where the project is and what happens next — the single machine-readable truth every human rendering derives from. Surfaces faults whose content changed since they were probed. |
24
25
 
25
26
  Shared definitions (verdicts, fault classes, provenance, annotations) live in
26
27
  [`schemas/common.schema.json`](schemas/common.schema.json). Gate behaviour —
@@ -44,5 +44,10 @@
44
44
  "isNew": true
45
45
  }
46
46
  ],
47
- "text": "## TEST BLINDSPOT CONTEXT\n\nSource: testguard 0.1.0 @ deadbeefcafe. 2 claims; 1 killed, 1 survived, 1 unverifiable, 1 nocover, 1 flaky-defender. 1 new since baseline.\n\n1. [NEW] REDACT-002 unverifiable (high) src/redact.mjs — Anchor not found; re-author the fault.\n2. REDACT-001 SURVIVED (critical) src/redact.mjs — test/redact.test.mjs asserts with objectContaining and omits the `content` key.\n"
47
+ "text": "## TEST BLINDSPOT CONTEXT\n\nSource: testguard 0.1.0 @ deadbeefcafe. 2 claims; 1 killed, 1 survived, 1 unverifiable, 1 nocover, 1 flaky-defender. 1 new since baseline.\n\n1. [NEW] REDACT-002 unverifiable (high) src/redact.mjs — Anchor not found; re-author the fault.\n2. REDACT-001 SURVIVED (critical) src/redact.mjs — test/redact.test.mjs asserts with objectContaining and omits the `content` key.\n",
48
+ "next": {
49
+ "action": "write-test",
50
+ "command": "write a test in test/redact.test.mjs that fails on REDACT-001/F1 and passes on HEAD, then: testguard probe --claim REDACT-001 --include-dirty",
51
+ "why": "REDACT-001/F1 (critical) survived: the audit row can carry the raw input and no test notices."
52
+ }
48
53
  }
@@ -48,7 +48,8 @@
48
48
  "producer": "human",
49
49
  "by": "maintainer",
50
50
  "at": "2026-09-17T02:00:00Z"
51
- }
51
+ },
52
+ "contentHash": "f232f31d9e6eecc8e952f0da1fb0878c5060e1a4dc19724874b041a4ab265aaf"
52
53
  },
53
54
  "verdict": "survived",
54
55
  "detail": {
@@ -142,7 +143,8 @@
142
143
  "id": "F2",
143
144
  "description": "Redaction skipped.",
144
145
  "file": "src/redact.mjs",
145
- "faultClass": "statement-deleted"
146
+ "faultClass": "statement-deleted",
147
+ "contentHash": "f232f31d9e6eecc8e952f0da1fb0878c5060e1a4dc19724874b041a4ab265aaf"
146
148
  },
147
149
  "verdict": "killed",
148
150
  "detail": {
@@ -203,7 +205,8 @@
203
205
  "id": "F1",
204
206
  "description": "Invalid pattern rethrows.",
205
207
  "file": "src/redact.mjs",
206
- "faultClass": "exception-swallowed"
208
+ "faultClass": "exception-swallowed",
209
+ "contentHash": "f232f31d9e6eecc8e952f0da1fb0878c5060e1a4dc19724874b041a4ab265aaf"
207
210
  },
208
211
  "verdict": "unverifiable",
209
212
  "detail": {
@@ -247,7 +250,8 @@
247
250
  "id": "F1",
248
251
  "description": "Invalid pattern rethrows.",
249
252
  "file": "src/redact.mjs",
250
- "faultClass": "exception-swallowed"
253
+ "faultClass": "exception-swallowed",
254
+ "contentHash": "f232f31d9e6eecc8e952f0da1fb0878c5060e1a4dc19724874b041a4ab265aaf"
251
255
  },
252
256
  "verdict": "nocover",
253
257
  "detail": {
@@ -47,7 +47,8 @@
47
47
  "producer": "human",
48
48
  "by": "maintainer",
49
49
  "at": "2026-09-17T02:00:00Z"
50
- }
50
+ },
51
+ "contentHash": "f232f31d9e6eecc8e952f0da1fb0878c5060e1a4dc19724874b041a4ab265aaf"
51
52
  },
52
53
  "verdict": "survived",
53
54
  "detail": {
@@ -181,7 +182,8 @@
181
182
  "id": "F2",
182
183
  "description": "Redaction skipped.",
183
184
  "file": "src/redact.mjs",
184
- "faultClass": "statement-deleted"
185
+ "faultClass": "statement-deleted",
186
+ "contentHash": "f232f31d9e6eecc8e952f0da1fb0878c5060e1a4dc19724874b041a4ab265aaf"
185
187
  },
186
188
  "verdict": "killed",
187
189
  "detail": {
@@ -282,7 +284,8 @@
282
284
  "id": "F1",
283
285
  "description": "Invalid pattern rethrows.",
284
286
  "file": "src/redact.mjs",
285
- "faultClass": "exception-swallowed"
287
+ "faultClass": "exception-swallowed",
288
+ "contentHash": "f232f31d9e6eecc8e952f0da1fb0878c5060e1a4dc19724874b041a4ab265aaf"
286
289
  },
287
290
  "verdict": "unverifiable",
288
291
  "detail": {
@@ -326,7 +329,8 @@
326
329
  "id": "F1",
327
330
  "description": "Invalid pattern rethrows.",
328
331
  "file": "src/redact.mjs",
329
- "faultClass": "exception-swallowed"
332
+ "faultClass": "exception-swallowed",
333
+ "contentHash": "f232f31d9e6eecc8e952f0da1fb0878c5060e1a4dc19724874b041a4ab265aaf"
330
334
  },
331
335
  "verdict": "nocover",
332
336
  "detail": {
@@ -368,7 +372,8 @@
368
372
  "id": "F2",
369
373
  "description": "Redaction skipped.",
370
374
  "file": "src/redact.mjs",
371
- "faultClass": "statement-deleted"
375
+ "faultClass": "statement-deleted",
376
+ "contentHash": "f232f31d9e6eecc8e952f0da1fb0878c5060e1a4dc19724874b041a4ab265aaf"
372
377
  },
373
378
  "verdict": "flaky-defender",
374
379
  "detail": {
@@ -444,7 +449,8 @@
444
449
  "id": "F3",
445
450
  "description": "Rule order reversed.",
446
451
  "file": "src/redact.mjs",
447
- "faultClass": "other"
452
+ "faultClass": "other",
453
+ "contentHash": "f232f31d9e6eecc8e952f0da1fb0878c5060e1a4dc19724874b041a4ab265aaf"
448
454
  },
449
455
  "verdict": "flaky-defender",
450
456
  "detail": {
@@ -0,0 +1,60 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "tool": {
4
+ "name": "testguard",
5
+ "version": "0.1.0"
6
+ },
7
+ "generatedAt": "2026-09-17T02:00:00Z",
8
+ "state": "unproven",
9
+ "next": {
10
+ "action": "write-test",
11
+ "command": "write a test in test/redact.test.mjs that fails on REDACT-001/F1 and passes on HEAD, then: testguard probe --claim REDACT-001 --include-dirty",
12
+ "why": "REDACT-001/F1 (critical) survived.",
13
+ "target": {
14
+ "claimId": "REDACT-001",
15
+ "subjectId": "F1",
16
+ "file": "src/redact.mjs",
17
+ "verdict": "survived"
18
+ }
19
+ },
20
+ "provisional": false,
21
+ "counts": {
22
+ "claims": 2,
23
+ "faults": 6,
24
+ "byVerdict": {
25
+ "killed": 1,
26
+ "survived": 1,
27
+ "unverifiable": 1,
28
+ "nocover": 1,
29
+ "flaky-defender": 2
30
+ },
31
+ "new": 1,
32
+ "baselined": 4
33
+ },
34
+ "stale": [],
35
+ "changedFaults": [
36
+ {
37
+ "claimId": "REDACT-002",
38
+ "subjectId": "F1",
39
+ "previousVerdict": "unverifiable",
40
+ "file": "src/redact.mjs"
41
+ }
42
+ ],
43
+ "findings": [
44
+ {
45
+ "fingerprint": "01876384493be6809da455cb1a1c4bc92e7564e9898506d790b9589200502163",
46
+ "claimId": "REDACT-001",
47
+ "subjectId": "F1",
48
+ "verdict": "survived",
49
+ "severity": "critical",
50
+ "file": "src/redact.mjs",
51
+ "rank": 9.5,
52
+ "isNew": true
53
+ }
54
+ ],
55
+ "paths": {
56
+ "claims": "testguard.claims.json",
57
+ "evidence": ".testguard/evidence.json",
58
+ "baseline": ".testguard/baseline.json"
59
+ }
60
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "tool": {
4
+ "name": "testguard",
5
+ "version": "0.1.0"
6
+ },
7
+ "generatedAt": "2026-09-17T02:00:00Z",
8
+ "state": "clean",
9
+ "next": {
10
+ "action": "write-test",
11
+ "command": "write a test in test/redact.test.mjs that fails on REDACT-001/F1 and passes on HEAD, then: testguard probe --claim REDACT-001 --include-dirty",
12
+ "why": "REDACT-001/F1 (critical) survived.",
13
+ "target": {
14
+ "claimId": "REDACT-001",
15
+ "subjectId": "F1",
16
+ "file": "src/redact.mjs",
17
+ "verdict": "survived"
18
+ }
19
+ },
20
+ "provisional": false,
21
+ "counts": {
22
+ "claims": 2,
23
+ "faults": 6,
24
+ "byVerdict": {
25
+ "killed": 1,
26
+ "survived": 1,
27
+ "unverifiable": 1,
28
+ "nocover": 1,
29
+ "flaky-defender": 2
30
+ },
31
+ "new": 1,
32
+ "baselined": 4
33
+ },
34
+ "stale": [],
35
+ "changedFaults": [
36
+ {
37
+ "claimId": "REDACT-002",
38
+ "subjectId": "F1",
39
+ "previousVerdict": "unverifiable",
40
+ "file": "src/redact.mjs"
41
+ }
42
+ ],
43
+ "findings": [
44
+ {
45
+ "fingerprint": "01876384493be6809da455cb1a1c4bc92e7564e9898506d790b9589200502163",
46
+ "claimId": "REDACT-001",
47
+ "subjectId": "F1",
48
+ "verdict": "survived",
49
+ "severity": "critical",
50
+ "file": "src/redact.mjs",
51
+ "rank": 9.5,
52
+ "isNew": true
53
+ }
54
+ ],
55
+ "paths": {
56
+ "claims": "testguard.claims.json",
57
+ "evidence": ".testguard/evidence.json",
58
+ "baseline": ".testguard/baseline.json"
59
+ }
60
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "tool": {
4
+ "name": "testguard",
5
+ "version": "0.1.0"
6
+ },
7
+ "generatedAt": "2026-09-17T02:00:00Z",
8
+ "state": "unproven",
9
+ "next": {
10
+ "action": "write-test",
11
+ "command": "write a test in test/redact.test.mjs that fails on REDACT-001/F1 and passes on HEAD, then: testguard probe --claim REDACT-001 --include-dirty",
12
+ "why": "REDACT-001/F1 (critical) survived."
13
+ },
14
+ "provisional": false,
15
+ "counts": {
16
+ "claims": 2,
17
+ "faults": 6,
18
+ "byVerdict": {
19
+ "killed": 1,
20
+ "survived": 1,
21
+ "unverifiable": 1,
22
+ "nocover": 1,
23
+ "flaky-defender": 2
24
+ },
25
+ "new": 1,
26
+ "baselined": 4
27
+ },
28
+ "stale": [],
29
+ "changedFaults": [
30
+ {
31
+ "claimId": "REDACT-002",
32
+ "subjectId": "F1",
33
+ "previousVerdict": "unverifiable",
34
+ "file": "src/redact.mjs"
35
+ }
36
+ ],
37
+ "findings": [
38
+ {
39
+ "fingerprint": "01876384493be6809da455cb1a1c4bc92e7564e9898506d790b9589200502163",
40
+ "claimId": "REDACT-001",
41
+ "subjectId": "F1",
42
+ "verdict": "survived",
43
+ "severity": "critical",
44
+ "file": "src/redact.mjs",
45
+ "rank": 9.5,
46
+ "isNew": true
47
+ }
48
+ ],
49
+ "paths": {
50
+ "claims": "testguard.claims.json",
51
+ "evidence": ".testguard/evidence.json",
52
+ "baseline": ".testguard/baseline.json"
53
+ }
54
+ }
@@ -6,7 +6,7 @@ import { fingerprint } from './fingerprint.mjs';
6
6
 
7
7
  const schemaDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'schemas');
8
8
 
9
- export const KINDS = Object.freeze(['claims', 'evidence', 'baseline', 'ignore', 'calibration', 'brief']);
9
+ export const KINDS = Object.freeze(['claims', 'evidence', 'baseline', 'ignore', 'calibration', 'brief', 'status']);
10
10
  export const PASSING_VERDICTS = Object.freeze(new Set(['killed']));
11
11
 
12
12
  const ajv = new Ajv2020({ strict: true, allErrors: true });
@@ -96,6 +96,14 @@ const semantic = {
96
96
  return errors;
97
97
  },
98
98
 
99
+ status(doc) {
100
+ const errors = [];
101
+ if (['write-test', 'review-fault-change'].includes(doc.next.action) && !doc.next.target) errors.push({ path: '/next/target', message: `${doc.next.action} requires a target` });
102
+ if (doc.state === 'no-claims' && doc.counts.claims !== 0) errors.push({ path: '/counts/claims', message: 'no-claims with a non-zero claim count' });
103
+ if (doc.state === 'clean' && (doc.counts.new ?? 0) > 0) errors.push({ path: '/state', message: 'clean with new findings' });
104
+ return errors;
105
+ },
106
+
99
107
  brief(doc) {
100
108
  const errors = [];
101
109
  if (!doc.text.startsWith(doc.heading)) errors.push({ path: '/text', message: 'text must begin with heading' });
@@ -34,6 +34,17 @@
34
34
  },
35
35
  "additionalProperties": false
36
36
  },
37
+ "next": {
38
+ "description": "The one action that moves the project forward, as computed by the status document; the brief carries it so an agent reading its session-start context knows what to do, not only what is wrong.",
39
+ "type": "object",
40
+ "required": ["action", "command", "why"],
41
+ "properties": {
42
+ "action": { "enum": ["scaffold", "probe", "write-test", "baseline", "review-fault-change", "none"] },
43
+ "command": { "type": "string", "minLength": 1, "maxLength": 1024 },
44
+ "why": { "type": "string", "minLength": 1, "maxLength": 2000 }
45
+ },
46
+ "additionalProperties": false
47
+ },
37
48
  "items": {
38
49
  "type": "array",
39
50
  "maxItems": 50,
@@ -126,7 +126,11 @@
126
126
  "description": { "type": "string", "maxLength": 2000 },
127
127
  "file": { "$ref": "urn:guard-spec:v1:common#/$defs/repoPath" },
128
128
  "faultClass": { "$ref": "urn:guard-spec:v1:common#/$defs/faultClass" },
129
- "producedBy": { "$ref": "urn:guard-spec:v1:common#/$defs/provenance" }
129
+ "producedBy": { "$ref": "urn:guard-spec:v1:common#/$defs/provenance" },
130
+ "contentHash": {
131
+ "description": "sha256 of the subject's content (for a fault: find + newline + replace). Excluded from the fingerprint on purpose, so repairing an anchor does not churn a baseline — and recorded here on purpose, so weakening a fault to make a finding disappear is visible.",
132
+ "$ref": "urn:guard-spec:v1:common#/$defs/sha256"
133
+ }
130
134
  },
131
135
  "additionalProperties": true
132
136
  },