testguard-cli 0.2.0__tar.gz → 0.3.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 (144) hide show
  1. testguard_cli-0.3.0/.codex/hooks.json +71 -0
  2. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/AGENTS.md +1 -0
  3. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/CHANGELOG.md +39 -0
  4. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/PKG-INFO +31 -5
  5. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/README.md +30 -4
  6. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/action.yml +1 -1
  7. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/package-lock.json +2 -2
  8. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/package.json +1 -1
  9. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/packaging/homebrew/testguard.rb +1 -1
  10. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/pyproject.toml +1 -1
  11. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/GATE-SEMANTICS.md +11 -1
  12. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/README.md +2 -1
  13. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/examples/brief.json +6 -1
  14. testguard_cli-0.3.0/spec/conformance/examples/evidence-provisional.json +276 -0
  15. testguard_cli-0.3.0/spec/conformance/examples/evidence.json +540 -0
  16. testguard_cli-0.3.0/spec/conformance/examples/status.json +60 -0
  17. testguard_cli-0.2.0/spec/conformance/examples/evidence.json → testguard_cli-0.3.0/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +2 -1
  18. testguard_cli-0.3.0/spec/conformance/invalid/evidence.single-run-without-provisional.json +271 -0
  19. testguard_cli-0.3.0/spec/conformance/invalid/status.clean-with-new-findings.json +60 -0
  20. testguard_cli-0.3.0/spec/conformance/invalid/status.write-test-without-target.json +54 -0
  21. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/schemas.test.mjs +6 -4
  22. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/lib/validate.mjs +11 -1
  23. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/schemas/brief.schema.json +11 -0
  24. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/schemas/evidence.schema.json +9 -1
  25. testguard_cli-0.3.0/spec/schemas/status.schema.json +110 -0
  26. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/brief/brief.mjs +5 -3
  27. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/cli.mjs +12 -2
  28. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/commands/baseline.mjs +11 -2
  29. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/commands/brief.mjs +10 -3
  30. testguard_cli-0.3.0/src/commands/init.mjs +15 -0
  31. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/commands/probe.mjs +18 -7
  32. testguard_cli-0.3.0/src/commands/status.mjs +10 -0
  33. testguard_cli-0.3.0/src/init/init.mjs +78 -0
  34. testguard_cli-0.3.0/src/init/templates/SKILL.md +63 -0
  35. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/probe/probe.mjs +2 -1
  36. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/render.mjs +7 -5
  37. testguard_cli-0.3.0/src/status/status.mjs +163 -0
  38. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/brief.test.mjs +15 -0
  39. testguard_cli-0.3.0/test/init.test.mjs +49 -0
  40. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/probe.fixture.test.mjs +46 -0
  41. testguard_cli-0.3.0/test/status.test.mjs +118 -0
  42. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/testguard.claims.json +32 -0
  43. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.gitattributes +0 -0
  44. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  45. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  46. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  47. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.github/dependabot.yml +0 -0
  48. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.github/scripts/install-smoke.mjs +0 -0
  49. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.github/scripts/sync-release-version.mjs +0 -0
  50. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.github/workflows/auto-merge.yml +0 -0
  51. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.github/workflows/ci.yml +0 -0
  52. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.github/workflows/release.yml +0 -0
  53. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.github/workflows/scheduled-release.yml +0 -0
  54. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.github/workflows/supply-chain.yml +0 -0
  55. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.gitignore +0 -0
  56. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.npmignore +0 -0
  57. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.npmrc +0 -0
  58. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/.pre-commit-hooks.yaml +0 -0
  59. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/CLAUDE.md +0 -0
  60. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  61. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/CONTRIBUTING.md +0 -0
  62. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/LICENSE +0 -0
  63. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/PRIVACY.md +0 -0
  64. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/SECURITY.md +0 -0
  65. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/SUPPORT.md +0 -0
  66. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/bench/README.md +0 -0
  67. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/cli/testguard.mjs +0 -0
  68. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/fixtures/known-answer/.gitignore +0 -0
  69. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/fixtures/known-answer/README.md +0 -0
  70. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/fixtures/known-answer/expected.json +0 -0
  71. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/fixtures/known-answer/package.json +0 -0
  72. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/fixtures/known-answer/src/export.mjs +0 -0
  73. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/fixtures/known-answer/src/redact.mjs +0 -0
  74. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/fixtures/known-answer/test/flaky.test.mjs +0 -0
  75. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/fixtures/known-answer/test/redact.test.mjs +0 -0
  76. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/fixtures/known-answer/testguard.claims.json +0 -0
  77. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/fixtures/known-answer/vitest.config.mjs +0 -0
  78. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/examples/baseline.json +0 -0
  79. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/examples/calibration.json +0 -0
  80. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/examples/claims.json +0 -0
  81. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/examples/ignore.json +0 -0
  82. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
  83. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/baseline.zero-count.json +0 -0
  84. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
  85. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
  86. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
  87. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
  88. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/claims.absolute-path.json +0 -0
  89. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
  90. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
  91. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/claims.noop-fault.json +0 -0
  92. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
  93. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
  94. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
  95. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
  96. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
  97. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
  98. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
  99. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
  100. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
  101. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
  102. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
  103. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
  104. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/conformance/invalid/ignore.short-reason.json +0 -0
  105. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/lib/fingerprint.mjs +0 -0
  106. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/schemas/baseline.schema.json +0 -0
  107. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/schemas/calibration.schema.json +0 -0
  108. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/schemas/claims.schema.json +0 -0
  109. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/schemas/common.schema.json +0 -0
  110. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/spec/schemas/ignore.schema.json +0 -0
  111. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/baseline/baseline.mjs +0 -0
  112. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/claims/annotations.mjs +0 -0
  113. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/claims/load.mjs +0 -0
  114. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/commands/claims.mjs +0 -0
  115. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/commands/scaffold.mjs +0 -0
  116. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/evidence/writer.mjs +0 -0
  117. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/git.mjs +0 -0
  118. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/probe/classify.mjs +0 -0
  119. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/probe/discover.mjs +0 -0
  120. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/probe/inject.mjs +0 -0
  121. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/probe/rank.mjs +0 -0
  122. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/probe/runner-vitest.mjs +0 -0
  123. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/probe/worktree.mjs +0 -0
  124. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/scaffold/producers.mjs +0 -0
  125. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/scaffold/scaffold.mjs +0 -0
  126. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/util/glob.mjs +0 -0
  127. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/src/util/hash.mjs +0 -0
  128. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/annotations.test.mjs +0 -0
  129. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/baseline.test.mjs +0 -0
  130. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/classify.test.mjs +0 -0
  131. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/discover.test.mjs +0 -0
  132. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/glob.test.mjs +0 -0
  133. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/inject.test.mjs +0 -0
  134. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/probe-preconditions.test.mjs +0 -0
  135. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/rank-aliases.test.mjs +0 -0
  136. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/runner-command.test.mjs +0 -0
  137. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/runner-vitest.test.mjs +0 -0
  138. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/scaffold.test.mjs +0 -0
  139. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/snapshot.test.mjs +0 -0
  140. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/worktree.test.mjs +0 -0
  141. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/test/writer.test.mjs +0 -0
  142. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/testguard_cli/__init__.py +0 -0
  143. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/testguard_cli/wrapper.py +0 -0
  144. {testguard_cli-0.2.0 → testguard_cli-0.3.0}/vitest.config.mjs +0 -0
@@ -0,0 +1,71 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "Read",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "node \"$CLAUDE_PROJECT_DIR/.wolf/hooks/pre-read.js\"",
10
+ "timeout": 5
11
+ }
12
+ ]
13
+ },
14
+ {
15
+ "matcher": "Write|Edit|MultiEdit",
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "node \"$CLAUDE_PROJECT_DIR/.wolf/hooks/pre-write.js\"",
20
+ "timeout": 5
21
+ }
22
+ ]
23
+ }
24
+ ],
25
+ "PostToolUse": [
26
+ {
27
+ "matcher": "Read",
28
+ "hooks": [
29
+ {
30
+ "type": "command",
31
+ "command": "node \"$CLAUDE_PROJECT_DIR/.wolf/hooks/post-read.js\"",
32
+ "timeout": 5
33
+ }
34
+ ]
35
+ },
36
+ {
37
+ "matcher": "Write|Edit|MultiEdit",
38
+ "hooks": [
39
+ {
40
+ "type": "command",
41
+ "command": "node \"$CLAUDE_PROJECT_DIR/.wolf/hooks/post-write.js\"",
42
+ "timeout": 10
43
+ }
44
+ ]
45
+ }
46
+ ],
47
+ "SessionStart": [
48
+ {
49
+ "matcher": "",
50
+ "hooks": [
51
+ {
52
+ "type": "command",
53
+ "command": "node \"$CLAUDE_PROJECT_DIR/.wolf/hooks/session-start.js\"",
54
+ "timeout": 5
55
+ }
56
+ ]
57
+ }
58
+ ],
59
+ "Stop": [
60
+ {
61
+ "hooks": [
62
+ {
63
+ "type": "command",
64
+ "command": "node \"$CLAUDE_PROJECT_DIR/.wolf/hooks/stop.js\"",
65
+ "timeout": 10
66
+ }
67
+ ]
68
+ }
69
+ ]
70
+ }
71
+ }
@@ -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
 
@@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-09-17
11
+
12
+ The agent operating layer. TestGuard is meant to be run by AI agents; this
13
+ release gives them one source of truth, an installable loop, and makes the
14
+ one cheap way to game it visible.
15
+
16
+ ### Added
17
+
18
+ - **`testguard status --json`** — the machine entry point. A new spec kind
19
+ (`status.schema.json`): `state` (no-claims · unprobed · evidence-stale ·
20
+ provisional-only · unproven · clean) and the one `next` action, computed
21
+ from claims, evidence, baseline and the working tree. Exit 0 clean, 1
22
+ unproven/stale, 2 nothing to probe yet.
23
+ - **`testguard init`** — installs `.claude/skills/testguard/SKILL.md`, the
24
+ `brief --text` SessionStart hook (merged into an existing
25
+ `.claude/settings.json`), an `AGENTS.md` section and the `.gitignore`
26
+ lines. Idempotent; `--force` replaces the skill.
27
+ - **Fault edits are visible.** Evidence records `subject.contentHash`;
28
+ `status` lists `changedFaults` (a fault edited after it was probed, with
29
+ its previous verdict) and makes `review-fault-change` the next action when
30
+ the edited fault had survived. Gate rule 8. Self-claim
31
+ `TG-FAULT-EDIT-VISIBLE`.
32
+ - `--json` on every command; `probe` and `baseline` emit the status document
33
+ plus their result. `brief` carries `next` and prints it.
34
+
35
+ ## [0.2.1] - 2026-09-17
36
+
37
+ ### Added
38
+
39
+ - **Provisional verdicts** (#5). A run with `--confirm` below 3 declares
40
+ `run.provisional: true` — a spec field whose equivalence with
41
+ `confirmRuns < 3` the validator enforces both ways. Provisional verdicts
42
+ print with a `?`, the summary is prefixed `PROVISIONAL`, a warning is
43
+ printed on stderr, evidence is written to `.testguard/evidence-provisional.json`
44
+ so the canonical file only ever holds confirmed runs, `brief` warns at the
45
+ top, and `baseline` refuses provisional evidence unless
46
+ `--allow-provisional`. A confirmed prior verdict is never reused by a
47
+ provisional run, nor the reverse.
48
+
10
49
  ## [0.2.0] - 2026-09-17
11
50
 
12
51
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: testguard-cli
3
- Version: 0.2.0
3
+ Version: 0.3.0
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.0` — see [`action.yml`](./action.yml) |
79
+ | GitHub Action | `uses: raccioly/testguard@v0.3.0` — 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
@@ -136,7 +138,10 @@ npx testguard-cli scaffold src/x.ts # propose faults for a file, as a draft to
136
138
  Practical loop: first pass `--no-escalate` (escalation re-runs the whole
137
139
  suite N times per survivor); iterate on one claim with `--claim <ID>` and
138
140
  either `--include-dirty` or `--in-place` (only fault target files must be
139
- clean there; test files may be dirty); final pass with defaults. By default
141
+ clean there; test files may be dirty), optionally `--confirm 1` for a fast
142
+ **provisional** signal — verdicts print with a `?`, evidence goes to
143
+ `evidence-provisional.json`, and `baseline` refuses it; final pass with
144
+ defaults. By default
140
145
  the stream shows only unproven faults plus a killed count — `--verbose`
141
146
  shows every fault. A custom
142
147
  runner (`pnpm --filter`, a specific config) goes in
@@ -160,6 +165,27 @@ npx testguard-cli scaffold src/x.ts # propose faults for a file, as a draft to
160
165
  `--text` prints only, and exits 0 silently when there is no evidence yet,
161
166
  so the hook can never break a session.
162
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
+
163
189
  ### Authoring faults mechanically
164
190
 
165
191
  Writing faults by hand means reading the code to find exact anchors. Two
@@ -213,8 +239,8 @@ through every verdict.
213
239
 
214
240
  ## Status
215
241
 
216
- **v0.2.** Five commands, vitest runner, hand-authored faults plus a
217
- 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
218
244
  spine — six JSON Schemas shared with the other Guard tools — is under
219
245
  [`spec/`](spec/). One exact-pinned runtime dependency (`ajv`, for schema validation); Node ≥ 20.
220
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.0` — see [`action.yml`](./action.yml) |
55
+ | GitHub Action | `uses: raccioly/testguard@v0.3.0` — 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
@@ -112,7 +114,10 @@ npx testguard-cli scaffold src/x.ts # propose faults for a file, as a draft to
112
114
  Practical loop: first pass `--no-escalate` (escalation re-runs the whole
113
115
  suite N times per survivor); iterate on one claim with `--claim <ID>` and
114
116
  either `--include-dirty` or `--in-place` (only fault target files must be
115
- clean there; test files may be dirty); final pass with defaults. By default
117
+ clean there; test files may be dirty), optionally `--confirm 1` for a fast
118
+ **provisional** signal — verdicts print with a `?`, evidence goes to
119
+ `evidence-provisional.json`, and `baseline` refuses it; final pass with
120
+ defaults. By default
116
121
  the stream shows only unproven faults plus a killed count — `--verbose`
117
122
  shows every fault. A custom
118
123
  runner (`pnpm --filter`, a specific config) goes in
@@ -136,6 +141,27 @@ npx testguard-cli scaffold src/x.ts # propose faults for a file, as a draft to
136
141
  `--text` prints only, and exits 0 silently when there is no evidence yet,
137
142
  so the hook can never break a session.
138
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
+
139
165
  ### Authoring faults mechanically
140
166
 
141
167
  Writing faults by hand means reading the code to find exact anchors. Two
@@ -189,8 +215,8 @@ through every verdict.
189
215
 
190
216
  ## Status
191
217
 
192
- **v0.2.** Five commands, vitest runner, hand-authored faults plus a
193
- 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
194
220
  spine — six JSON Schemas shared with the other Guard tools — is under
195
221
  [`spec/`](spec/). One exact-pinned runtime dependency (`ajv`, for schema validation); Node ≥ 20.
196
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.0'
41
+ default: '0.3.0'
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.0",
3
+ "version": "0.3.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "testguard-cli",
9
- "version": "0.2.0",
9
+ "version": "0.3.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.2.0",
3
+ "version": "0.3.0",
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.0.tgz"
16
+ url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.3.0.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.0"
7
+ version = "0.3.0"
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" }
@@ -22,7 +22,11 @@ Rules that follow from the table:
22
22
  1. **Green baseline first.** Defenders run N times unmodified before any fault
23
23
  is applied. Anything short of N/N pass is `flaky-defender` and stops there.
24
24
  2. **Confirm over N runs.** `killed` and `survived` both require exactly N
25
- probe runs, all agreeing. Default N is 3.
25
+ probe runs, all agreeing. Default N is 3. **Fewer than three runs is
26
+ provisional**: the evidence declares `run.provisional: true`, every
27
+ rendering marks the verdicts as unconfirmed, and a provisional run is
28
+ never frozen into a baseline. Provisional runs exist for the fix loop —
29
+ a fast signal while writing a test — not for a gate.
26
30
  3. **Only a test body rejecting the behaviour kills.** A test that fails by
27
31
  assertion — or by an exception the fault provoked inside it — counts. A
28
32
  timeout does not, and a suite that fails to load does not: neither is
@@ -46,6 +50,12 @@ Rules that follow from the table:
46
50
  snapshot the working tree — otherwise the answer looks right and is not.
47
51
  7. **Never a single global score.** Output is per claim, ranked. Blindness is
48
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.
49
59
 
50
60
  ## Baseline and delta
51
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
  }
@@ -0,0 +1,276 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "tool": {
4
+ "name": "testguard",
5
+ "version": "0.1.0"
6
+ },
7
+ "run": {
8
+ "id": "run-provisional",
9
+ "startedAt": "2026-09-17T02:00:00Z",
10
+ "finishedAt": "2026-09-17T02:00:41Z",
11
+ "repo": {
12
+ "head": "deadbeefcafe",
13
+ "dirty": false,
14
+ "snapshot": "c0ffeeaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
15
+ },
16
+ "runner": {
17
+ "name": "vitest",
18
+ "version": "5.0.1"
19
+ },
20
+ "confirmRuns": 1,
21
+ "mode": "worktree",
22
+ "provisional": true
23
+ },
24
+ "records": [
25
+ {
26
+ "fingerprint": "01876384493be6809da455cb1a1c4bc92e7564e9898506d790b9589200502163",
27
+ "claim": {
28
+ "id": "REDACT-001",
29
+ "statement": "The audit record written for a redacted message never contains the original secret.",
30
+ "severity": "critical",
31
+ "source": {
32
+ "kind": "spec",
33
+ "ref": "docs/spec.md#audit-records"
34
+ },
35
+ "producedBy": {
36
+ "producer": "human",
37
+ "by": "maintainer",
38
+ "at": "2026-09-17T02:00:00Z"
39
+ }
40
+ },
41
+ "subject": {
42
+ "kind": "fault",
43
+ "id": "F1",
44
+ "description": "Audit row is written with the raw input.",
45
+ "file": "src/redact.mjs",
46
+ "faultClass": "variable-swap",
47
+ "producedBy": {
48
+ "producer": "human",
49
+ "by": "maintainer",
50
+ "at": "2026-09-17T02:00:00Z"
51
+ },
52
+ "contentHash": "f232f31d9e6eecc8e952f0da1fb0878c5060e1a4dc19724874b041a4ab265aaf"
53
+ },
54
+ "verdict": "survived",
55
+ "detail": {
56
+ "baselineRuns": [
57
+ {
58
+ "outcome": "pass",
59
+ "tests": {
60
+ "total": 4,
61
+ "passed": 4,
62
+ "failed": 0
63
+ },
64
+ "assertionFailures": 0,
65
+ "durationMs": 310
66
+ }
67
+ ],
68
+ "probeRuns": [
69
+ {
70
+ "outcome": "pass",
71
+ "tests": {
72
+ "total": 4,
73
+ "passed": 4,
74
+ "failed": 0
75
+ },
76
+ "assertionFailures": 0,
77
+ "durationMs": 310
78
+ }
79
+ ],
80
+ "escalated": true,
81
+ "escalationRuns": [
82
+ {
83
+ "outcome": "pass",
84
+ "tests": {
85
+ "total": 4,
86
+ "passed": 4,
87
+ "failed": 0
88
+ },
89
+ "assertionFailures": 0,
90
+ "durationMs": 310
91
+ }
92
+ ]
93
+ },
94
+ "defenders": {
95
+ "requested": [
96
+ "test/redact.test.mjs"
97
+ ],
98
+ "resolved": [
99
+ "test/redact.test.mjs"
100
+ ],
101
+ "nocover": false,
102
+ "discovered": false
103
+ },
104
+ "inputs": {
105
+ "targetHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
106
+ "defenderHashes": {
107
+ "test/redact.test.mjs": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
108
+ }
109
+ },
110
+ "rank": {
111
+ "score": 9.5,
112
+ "blastRadius": 7,
113
+ "tier": "critical"
114
+ },
115
+ "annotations": [
116
+ {
117
+ "kind": "tracked",
118
+ "text": "Assertion uses objectContaining without the content key.",
119
+ "by": "maintainer",
120
+ "at": "2026-09-17T02:00:00Z",
121
+ "ref": "issue#12"
122
+ }
123
+ ]
124
+ },
125
+ {
126
+ "fingerprint": "10d526ffb9535c8d99c39dcea29a8d6fa86c3370a2a5061de5320e028ba79b1f",
127
+ "claim": {
128
+ "id": "REDACT-001",
129
+ "statement": "The audit record written for a redacted message never contains the original secret.",
130
+ "severity": "critical",
131
+ "source": {
132
+ "kind": "spec",
133
+ "ref": "docs/spec.md#audit-records"
134
+ },
135
+ "producedBy": {
136
+ "producer": "human",
137
+ "by": "maintainer",
138
+ "at": "2026-09-17T02:00:00Z"
139
+ }
140
+ },
141
+ "subject": {
142
+ "kind": "fault",
143
+ "id": "F2",
144
+ "description": "Redaction skipped.",
145
+ "file": "src/redact.mjs",
146
+ "faultClass": "statement-deleted",
147
+ "contentHash": "f232f31d9e6eecc8e952f0da1fb0878c5060e1a4dc19724874b041a4ab265aaf"
148
+ },
149
+ "verdict": "killed",
150
+ "detail": {
151
+ "baselineRuns": [
152
+ {
153
+ "outcome": "pass",
154
+ "tests": {
155
+ "total": 4,
156
+ "passed": 4,
157
+ "failed": 0
158
+ },
159
+ "assertionFailures": 0,
160
+ "durationMs": 310
161
+ }
162
+ ],
163
+ "probeRuns": [
164
+ {
165
+ "outcome": "fail",
166
+ "tests": {
167
+ "total": 4,
168
+ "passed": 3,
169
+ "failed": 1
170
+ },
171
+ "assertionFailures": 1,
172
+ "durationMs": 322
173
+ }
174
+ ]
175
+ },
176
+ "defenders": {
177
+ "requested": [
178
+ "test/redact.test.mjs"
179
+ ],
180
+ "resolved": [
181
+ "test/redact.test.mjs"
182
+ ],
183
+ "nocover": false
184
+ },
185
+ "inputs": {
186
+ "targetHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
187
+ "defenderHashes": {
188
+ "test/redact.test.mjs": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
189
+ }
190
+ }
191
+ },
192
+ {
193
+ "fingerprint": "0a0767ff8b124883e0bc042094c8494027b3d6d45bb8f4e40eda0930214a8970",
194
+ "claim": {
195
+ "id": "REDACT-002",
196
+ "statement": "A rule with an invalid pattern is skipped; it never aborts the scan.",
197
+ "severity": "high",
198
+ "source": {
199
+ "kind": "comment",
200
+ "ref": "src/redact.mjs:41"
201
+ }
202
+ },
203
+ "subject": {
204
+ "kind": "fault",
205
+ "id": "F1",
206
+ "description": "Invalid pattern rethrows.",
207
+ "file": "src/redact.mjs",
208
+ "faultClass": "exception-swallowed",
209
+ "contentHash": "f232f31d9e6eecc8e952f0da1fb0878c5060e1a4dc19724874b041a4ab265aaf"
210
+ },
211
+ "verdict": "unverifiable",
212
+ "detail": {
213
+ "reason": "anchor-missing",
214
+ "baselineRuns": [],
215
+ "probeRuns": [],
216
+ "anchor": {
217
+ "hits": 0,
218
+ "expected": 1
219
+ }
220
+ },
221
+ "defenders": {
222
+ "requested": [
223
+ "test/redact.test.mjs"
224
+ ],
225
+ "resolved": [
226
+ "test/redact.test.mjs"
227
+ ],
228
+ "nocover": false
229
+ },
230
+ "inputs": {
231
+ "targetHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
232
+ "defenderHashes": {
233
+ "test/redact.test.mjs": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
234
+ }
235
+ }
236
+ },
237
+ {
238
+ "fingerprint": "81cb777d4cba8dd856ad3f0626fe58186906e5c18a01425d82957adaa963cb28",
239
+ "claim": {
240
+ "id": "REDACT-002",
241
+ "statement": "A rule with an invalid pattern is skipped; it never aborts the scan.",
242
+ "severity": "high",
243
+ "source": {
244
+ "kind": "comment",
245
+ "ref": "src/redact.mjs:41"
246
+ }
247
+ },
248
+ "subject": {
249
+ "kind": "fault",
250
+ "id": "F1",
251
+ "description": "Invalid pattern rethrows.",
252
+ "file": "src/redact.mjs",
253
+ "faultClass": "exception-swallowed",
254
+ "contentHash": "f232f31d9e6eecc8e952f0da1fb0878c5060e1a4dc19724874b041a4ab265aaf"
255
+ },
256
+ "verdict": "nocover",
257
+ "detail": {
258
+ "baselineRuns": [],
259
+ "probeRuns": []
260
+ },
261
+ "defenders": {
262
+ "requested": [
263
+ "test/rules.test.mjs"
264
+ ],
265
+ "resolved": [],
266
+ "nocover": true
267
+ },
268
+ "inputs": {
269
+ "targetHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
270
+ "defenderHashes": {
271
+ "test/redact.test.mjs": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
272
+ }
273
+ }
274
+ }
275
+ ]
276
+ }