testguard-cli 0.1.0__py3-none-any.whl
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.
- testguard_cli/__init__.py +1 -0
- testguard_cli/wrapper.py +91 -0
- testguard_cli-0.1.0.dist-info/METADATA +158 -0
- testguard_cli-0.1.0.dist-info/RECORD +7 -0
- testguard_cli-0.1.0.dist-info/WHEEL +4 -0
- testguard_cli-0.1.0.dist-info/entry_points.txt +2 -0
- testguard_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""TestGuard CLI — Python wrapper around the Node.js `testguard-cli` package."""
|
testguard_cli/wrapper.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
TestGuard CLI — Python wrapper.
|
|
4
|
+
|
|
5
|
+
Runs the Node.js TestGuard CLI so Python-centric teams can `pip install
|
|
6
|
+
testguard-cli` and use `testguard` without touching npm directly.
|
|
7
|
+
Node.js 20+ is required.
|
|
8
|
+
|
|
9
|
+
Resolution order:
|
|
10
|
+
1. A locally installed `node_modules/testguard-cli/cli/testguard.mjs`,
|
|
11
|
+
searched upward from the current directory — so a project that pins the
|
|
12
|
+
package runs the PINNED version, offline and reproducibly.
|
|
13
|
+
2. `npx -y testguard-cli@latest`.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
pip install testguard-cli
|
|
17
|
+
testguard claims
|
|
18
|
+
testguard probe
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
import shutil
|
|
23
|
+
import subprocess
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
NODE_FLOOR = 20
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def find_node():
|
|
30
|
+
"""Find a usable Node.js binary (>= NODE_FLOOR)."""
|
|
31
|
+
for cmd in ("node", "node22", "node20"):
|
|
32
|
+
path = shutil.which(cmd)
|
|
33
|
+
if not path:
|
|
34
|
+
continue
|
|
35
|
+
try:
|
|
36
|
+
version = subprocess.check_output(
|
|
37
|
+
[path, "--version"], text=True, stderr=subprocess.DEVNULL
|
|
38
|
+
).strip()
|
|
39
|
+
if int(version.lstrip("v").split(".")[0]) >= NODE_FLOOR:
|
|
40
|
+
return path
|
|
41
|
+
except (subprocess.CalledProcessError, ValueError):
|
|
42
|
+
continue
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def find_local_cli():
|
|
47
|
+
"""Resolve a locally installed CLI entry, walking up from cwd."""
|
|
48
|
+
directory = os.getcwd()
|
|
49
|
+
while True:
|
|
50
|
+
entry = os.path.join(directory, "node_modules", "testguard-cli", "cli", "testguard.mjs")
|
|
51
|
+
if os.path.isfile(entry):
|
|
52
|
+
return entry
|
|
53
|
+
parent = os.path.dirname(directory)
|
|
54
|
+
if parent == directory:
|
|
55
|
+
return None
|
|
56
|
+
directory = parent
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def main():
|
|
60
|
+
"""Entry point for the `testguard` command."""
|
|
61
|
+
args = sys.argv[1:]
|
|
62
|
+
node = find_node()
|
|
63
|
+
if not node:
|
|
64
|
+
print(
|
|
65
|
+
f"Error: Node.js {NODE_FLOOR}+ is required but not found.\n"
|
|
66
|
+
"Install from https://nodejs.org/ or via nvm: nvm install 22",
|
|
67
|
+
file=sys.stderr,
|
|
68
|
+
)
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
|
|
71
|
+
local_cli = find_local_cli()
|
|
72
|
+
npx = shutil.which("npx")
|
|
73
|
+
if local_cli:
|
|
74
|
+
cmd = [node, local_cli] + args
|
|
75
|
+
elif npx:
|
|
76
|
+
cmd = [npx, "-y", "testguard-cli@latest"] + args
|
|
77
|
+
else:
|
|
78
|
+
print("Error: npx not found. Install Node.js 20+, which includes npm/npx.", file=sys.stderr)
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
sys.exit(subprocess.run(cmd, check=False).returncode)
|
|
83
|
+
except FileNotFoundError:
|
|
84
|
+
print(f"Error: could not execute: {' '.join(cmd)}", file=sys.stderr)
|
|
85
|
+
sys.exit(1)
|
|
86
|
+
except KeyboardInterrupt:
|
|
87
|
+
sys.exit(130)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
main()
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: testguard-cli
|
|
3
|
+
Version: 0.1.0
|
|
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
|
+
Project-URL: Homepage, https://github.com/raccioly/testguard
|
|
6
|
+
Project-URL: Documentation, https://github.com/raccioly/testguard#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/raccioly/testguard
|
|
8
|
+
Project-URL: Issues, https://github.com/raccioly/testguard/issues
|
|
9
|
+
Author-email: Ricardo Accioly <raccioly@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: ai-agents,claims,fault-injection,mutation-testing,quality-assurance,test-quality,testing,verification,vitest
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: JavaScript
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# TestGuard
|
|
26
|
+
|
|
27
|
+
[](https://github.com/raccioly/testguard/actions/workflows/ci.yml)
|
|
28
|
+
[](https://www.npmjs.com/package/testguard-cli)
|
|
29
|
+
[](https://pypi.org/project/testguard-cli/)
|
|
30
|
+
[](https://nodejs.org)
|
|
31
|
+
[](./LICENSE)
|
|
32
|
+
[](./package.json)
|
|
33
|
+
|
|
34
|
+
> Proves that a test suite actually defends the claims a project makes — by
|
|
35
|
+
> injecting the faults those claims say cannot happen, and reporting every
|
|
36
|
+
> fault the tests fail to detect.
|
|
37
|
+
|
|
38
|
+
**Not a test generator. A claim verifier.** Test generation is what happens
|
|
39
|
+
after a claim turns out to be unfalsifiable.
|
|
40
|
+
|
|
41
|
+
Third tool following the Guard pattern, alongside
|
|
42
|
+
[`docguard-cli`](https://www.npmjs.com/package/docguard-cli) (docs ↔ code) and
|
|
43
|
+
[`websec-validator`](https://pypi.org/project/websec-validator/) (attack
|
|
44
|
+
surface ↔ code). All three run one loop:
|
|
45
|
+
|
|
46
|
+
> declare what must be true → try mechanically to falsify it → freeze a
|
|
47
|
+
> baseline → gate only the delta → brief the agent before it writes code.
|
|
48
|
+
|
|
49
|
+
## Why
|
|
50
|
+
|
|
51
|
+
Coverage cannot tell a test that pins *correct* behaviour from one that pins
|
|
52
|
+
a *defect*. An agent that writes both the code and its tests encodes whatever
|
|
53
|
+
it believed — including its bugs — and the suite goes green.
|
|
54
|
+
|
|
55
|
+
Measured on a real, entirely AI-authored production codebase with ~4,900
|
|
56
|
+
disciplined tests (no snapshots, 0.4% zero-assertion): **8 of 9 real
|
|
57
|
+
historical bugs were invisible to the suite**, worst case 2,451 tests green
|
|
58
|
+
on known-broken code. The largest gap was a compliance-critical path with
|
|
59
|
+
100% coverage, where the one assertion that mattered used
|
|
60
|
+
`expect.objectContaining({...})` and omitted the field carrying the data.
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
| How | Command |
|
|
65
|
+
|---|---|
|
|
66
|
+
| npx (no install) | `npx testguard-cli probe` |
|
|
67
|
+
| npm | `npm i -D testguard-cli` then `npx testguard probe` |
|
|
68
|
+
| pip | `pip install testguard-cli` then `testguard probe` (needs Node ≥ 20) |
|
|
69
|
+
| Homebrew | `brew tap raccioly/tap && brew install testguard` |
|
|
70
|
+
| GitHub Action | `uses: raccioly/testguard@v0.1.0` — see [`action.yml`](./action.yml) |
|
|
71
|
+
| pre-commit | `repo: https://github.com/raccioly/testguard`, hooks `testguard-claims`, `testguard-probe` |
|
|
72
|
+
|
|
73
|
+
## How it works
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx testguard-cli claims # what does this project claim, and is every claim probeable?
|
|
77
|
+
npx testguard-cli probe # try to falsify each claim; report what the tests missed
|
|
78
|
+
npx testguard-cli baseline # freeze today's unproven findings; from now on only new ones gate
|
|
79
|
+
npx testguard-cli brief # tell the agent where the suite is blind, before it writes
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
1. **Claims** live in `testguard.claims.json`: a statement, where it comes
|
|
83
|
+
from, which tests supposedly defend it, and one or more *faults* — each a
|
|
84
|
+
deterministic source change that would make the statement false. Every
|
|
85
|
+
claim and every fault records who produced it. `testguard claims`
|
|
86
|
+
validates the file and reports drift against `@claim <ID>` annotations in
|
|
87
|
+
source.
|
|
88
|
+
2. **Probe** confirms the defenders are green N times unmodified, applies
|
|
89
|
+
each fault in a scratch git worktree (your tree is never touched), runs
|
|
90
|
+
the defenders N times, re-runs survivors against the whole suite with
|
|
91
|
+
N-run attribution, restores, and classifies. Verdicts are a closed set:
|
|
92
|
+
|
|
93
|
+
| Verdict | Meaning |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `killed` | a test body rejected the behaviour, N/N — the only pass |
|
|
96
|
+
| `SURVIVED` | the defenders stayed green while the claim was false |
|
|
97
|
+
| `NOCOVER` | no test file defends the claim at all |
|
|
98
|
+
| `UNVERIFIABLE` | the fault's anchor is missing or ambiguous — loud, never a skip |
|
|
99
|
+
| `TIMEOUT` | the defenders hung; a hang is not a detection |
|
|
100
|
+
| `FAULT-INVALID` | the replacement does not load — a bad fault, not a finding |
|
|
101
|
+
| `FLAKY-DEFENDER` | the defenders are not reliably green, or disagreed across runs |
|
|
102
|
+
|
|
103
|
+
Never a single score. Findings are ranked by severity, claim provenance
|
|
104
|
+
and blast radius, and written to `.testguard/evidence.json` — validated
|
|
105
|
+
against the spec before it is written.
|
|
106
|
+
3. **Baseline** freezes every non-passing fingerprint. Later probes suppress
|
|
107
|
+
what was already known and exit non-zero only on what is new. Claims whose
|
|
108
|
+
source and defenders are unchanged reuse their prior verdict, so a probe
|
|
109
|
+
in CI costs only what changed.
|
|
110
|
+
4. **Brief** turns evidence plus baseline into a ranked, capped
|
|
111
|
+
`## TEST BLINDSPOT CONTEXT` block. Wire it into an agent's session start
|
|
112
|
+
— for Claude Code, in `.claude/settings.json`:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{ "hooks": { "SessionStart": [ { "hooks": [
|
|
116
|
+
{ "type": "command", "command": "npx testguard-cli brief --text" }
|
|
117
|
+
] } ] } }
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`--text` prints only, and exits 0 silently when there is no evidence yet,
|
|
121
|
+
so the hook can never break a session.
|
|
122
|
+
|
|
123
|
+
**Commit `.testguard/baseline.json`; ignore `evidence.json` and `brief.json`.**
|
|
124
|
+
The baseline is the frozen contract; the other two are regenerated per run.
|
|
125
|
+
|
|
126
|
+
The fault model is the auditable artifact. You never reach 100% of
|
|
127
|
+
correctness; you reach **100% of stated claims verified**, and the statement
|
|
128
|
+
of claims is what an assessor reads. A claims file is code — its `replace`
|
|
129
|
+
strings run under your test runner — so review it like code.
|
|
130
|
+
|
|
131
|
+
## Try it
|
|
132
|
+
|
|
133
|
+
The repository ships a known-answer fixture with a real blind spot:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
git clone <this repo> && cd testguard && npm install
|
|
137
|
+
npm test # includes probing the fixture end to end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`fixtures/known-answer/` is a tiny project whose audit-row test asserts with
|
|
141
|
+
`expect.objectContaining({...})` and omits the `content` key. Swap the
|
|
142
|
+
redacted text for the raw input and the test stays green. `probe` reports it
|
|
143
|
+
as `SURVIVED`; the fixture's [README](fixtures/known-answer/README.md) walks
|
|
144
|
+
through every verdict.
|
|
145
|
+
|
|
146
|
+
## Status
|
|
147
|
+
|
|
148
|
+
**v0.1.** Four commands, vitest runner, hand-authored faults. The contract
|
|
149
|
+
spine — six JSON Schemas shared with the other Guard tools — is under
|
|
150
|
+
[`spec/`](spec/). Zero runtime dependencies; Node ≥ 20.
|
|
151
|
+
|
|
152
|
+
Not yet: test generation (the two-gate acceptance loop), other runners,
|
|
153
|
+
mechanical fault producers, and calibration of fault classes against real
|
|
154
|
+
escaped bugs. Each is designed for; none is claimed.
|
|
155
|
+
|
|
156
|
+
## Licence
|
|
157
|
+
|
|
158
|
+
MIT.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
testguard_cli/__init__.py,sha256=8sGzMplmVqpmyu5Q1OrgDrs-AUUBVLqVEqCyXBoV_3k,83
|
|
2
|
+
testguard_cli/wrapper.py,sha256=24oBMXAiHuFz9K32ggrD1tk_8oq1iBLcWXIdjrLowbk,2581
|
|
3
|
+
testguard_cli-0.1.0.dist-info/METADATA,sha256=cC5fZuV1VwmO--67BY5S2CRs_DwJGh1eDSS6bx1LeCk,7716
|
|
4
|
+
testguard_cli-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
5
|
+
testguard_cli-0.1.0.dist-info/entry_points.txt,sha256=b-DAHthznWkLRH9h5URsjvAJNFlyqbNENKAwVun81IY,57
|
|
6
|
+
testguard_cli-0.1.0.dist-info/licenses/LICENSE,sha256=gQ9PJNsgxXTDVYfhspPfwlj1ba3MeDnbL8G0dqN2jLE,1072
|
|
7
|
+
testguard_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ricardo Accioly
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|