testguard-cli 0.3.0__tar.gz → 0.4.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.
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.github/workflows/ci.yml +20 -0
- testguard_cli-0.4.0/.gitignore +22 -0
- testguard_cli-0.4.0/.testguard/baseline.json +11 -0
- testguard_cli-0.4.0/.testguard/status.json +32 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/AGENTS.md +10 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/CHANGELOG.md +36 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/PKG-INFO +12 -7
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/README.md +11 -6
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/action.yml +1 -1
- testguard_cli-0.4.0/fixtures/known-answer-jest/.gitignore +2 -0
- testguard_cli-0.4.0/fixtures/known-answer-jest/README.md +7 -0
- testguard_cli-0.4.0/fixtures/known-answer-jest/expected.json +54 -0
- testguard_cli-0.4.0/fixtures/known-answer-jest/jest.config.js +1 -0
- testguard_cli-0.4.0/fixtures/known-answer-jest/package.json +7 -0
- testguard_cli-0.4.0/fixtures/known-answer-jest/src/export.js +5 -0
- testguard_cli-0.4.0/fixtures/known-answer-jest/src/redact.js +62 -0
- testguard_cli-0.4.0/fixtures/known-answer-jest/test/flaky.test.js +13 -0
- testguard_cli-0.4.0/fixtures/known-answer-jest/test/redact.test.js +35 -0
- testguard_cli-0.4.0/fixtures/known-answer-jest/testguard.claims.json +297 -0
- testguard_cli-0.4.0/package-lock.json +5700 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/package.json +2 -1
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/packaging/homebrew/testguard.rb +1 -1
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/pyproject.toml +1 -1
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/brief/brief.mjs +1 -1
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/cli.mjs +6 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/commands/claims.mjs +1 -1
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/commands/probe.mjs +1 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/init/init.mjs +6 -2
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/probe/discover.mjs +1 -1
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/probe/probe.mjs +17 -13
- testguard_cli-0.4.0/src/probe/runners/index.mjs +20 -0
- testguard_cli-0.4.0/src/probe/runners/jest.mjs +9 -0
- testguard_cli-0.3.0/src/probe/runner-vitest.mjs → testguard_cli-0.4.0/src/probe/runners/shared.mjs +29 -21
- testguard_cli-0.4.0/src/probe/runners/vitest.mjs +7 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/status/status.mjs +1 -1
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/init.test.mjs +8 -0
- testguard_cli-0.4.0/test/probe.jest.test.mjs +52 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/runner-command.test.mjs +3 -3
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/runner-vitest.test.mjs +9 -3
- testguard_cli-0.3.0/.gitignore +0 -18
- testguard_cli-0.3.0/package-lock.json +0 -1231
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.codex/hooks.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.gitattributes +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.github/dependabot.yml +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.github/scripts/install-smoke.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.github/scripts/sync-release-version.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.github/workflows/auto-merge.yml +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.github/workflows/release.yml +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.github/workflows/scheduled-release.yml +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.github/workflows/supply-chain.yml +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.npmignore +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.npmrc +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/.pre-commit-hooks.yaml +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/CLAUDE.md +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/CODE_OF_CONDUCT.md +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/CONTRIBUTING.md +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/LICENSE +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/PRIVACY.md +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/SECURITY.md +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/SUPPORT.md +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/bench/README.md +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/cli/testguard.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/fixtures/known-answer/.gitignore +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/fixtures/known-answer/README.md +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/fixtures/known-answer/expected.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/fixtures/known-answer/package.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/fixtures/known-answer/src/export.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/fixtures/known-answer/src/redact.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/fixtures/known-answer/test/flaky.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/fixtures/known-answer/test/redact.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/fixtures/known-answer/testguard.claims.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/fixtures/known-answer/vitest.config.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/GATE-SEMANTICS.md +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/README.md +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/examples/baseline.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/examples/brief.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/examples/calibration.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/examples/claims.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/examples/evidence-provisional.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/examples/evidence.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/examples/ignore.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/examples/status.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/baseline.zero-count.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/claims.absolute-path.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/claims.noop-fault.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/ignore.short-reason.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/conformance/schemas.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/lib/fingerprint.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/lib/validate.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/schemas/baseline.schema.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/schemas/brief.schema.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/schemas/calibration.schema.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/schemas/claims.schema.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/schemas/common.schema.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/schemas/evidence.schema.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/schemas/ignore.schema.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/spec/schemas/status.schema.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/baseline/baseline.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/claims/annotations.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/claims/load.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/commands/baseline.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/commands/brief.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/commands/init.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/commands/scaffold.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/commands/status.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/evidence/writer.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/git.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/init/templates/SKILL.md +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/probe/classify.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/probe/inject.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/probe/rank.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/probe/worktree.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/render.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/scaffold/producers.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/scaffold/scaffold.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/util/glob.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/src/util/hash.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/annotations.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/baseline.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/brief.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/classify.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/discover.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/glob.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/inject.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/probe-preconditions.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/probe.fixture.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/rank-aliases.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/scaffold.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/snapshot.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/status.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/worktree.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/test/writer.test.mjs +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/testguard.claims.json +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/testguard_cli/__init__.py +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/testguard_cli/wrapper.py +0 -0
- {testguard_cli-0.3.0 → testguard_cli-0.4.0}/vitest.config.mjs +0 -0
|
@@ -102,6 +102,26 @@ jobs:
|
|
|
102
102
|
console.log('fixture verdicts match expected.json:', ev.records.length);
|
|
103
103
|
"
|
|
104
104
|
|
|
105
|
+
- name: probe the jest fixture (through the bin, --runner jest)
|
|
106
|
+
shell: bash
|
|
107
|
+
run: |
|
|
108
|
+
S=$(mktemp -d)
|
|
109
|
+
cp -R fixtures/known-answer-jest/. "$S/"
|
|
110
|
+
rm -rf "$S/node_modules" "$S/.flake-counter" "$S/.testguard"
|
|
111
|
+
ln -s "$GITHUB_WORKSPACE/node_modules" "$S/node_modules"
|
|
112
|
+
git -C "$S" init -q && git -C "$S" -c user.email=ci@example.invalid -c user.name=ci add -A && git -C "$S" -c user.email=ci@example.invalid -c user.name=ci commit -qm fixture
|
|
113
|
+
status=0; node cli/testguard.mjs probe "$S" --runner jest --budget 60000 --quiet --no-escalate || status=$?
|
|
114
|
+
[ "$status" -eq 1 ] || { echo "::error::expected exit 1, got $status"; exit 1; }
|
|
115
|
+
node --input-type=module -e "
|
|
116
|
+
import { readFileSync } from 'node:fs';
|
|
117
|
+
const ev = JSON.parse(readFileSync('$S/.testguard/evidence.json','utf8'));
|
|
118
|
+
const exp = JSON.parse(readFileSync('fixtures/known-answer-jest/expected.json','utf8')).expected;
|
|
119
|
+
if (ev.run.runner.name !== 'jest') { console.error('runner was', ev.run.runner.name); process.exit(1); }
|
|
120
|
+
const bad = ev.records.filter(r => exp[r.claim.id+'/'+r.subject.id].verdict !== r.verdict);
|
|
121
|
+
if (bad.length) { console.error('MISMATCH', bad.map(r => r.claim.id+'/'+r.subject.id+':'+r.verdict)); process.exit(1); }
|
|
122
|
+
console.log('jest fixture verdicts match expected.json:', ev.records.length);
|
|
123
|
+
"
|
|
124
|
+
|
|
105
125
|
# ── Self-verification: TestGuard is governed by the standard it defines.
|
|
106
126
|
# testguard.claims.json states invariants of the tool itself; every
|
|
107
127
|
# fault must be killed by the tool's own tests. Exit 0 = all defended. ──
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
dist/
|
|
3
|
+
coverage/
|
|
4
|
+
.DS_Store
|
|
5
|
+
*.log
|
|
6
|
+
# TestGuard: baseline.json and status.json ARE committed (the frozen contract and its snapshot); the rest is regenerated per run
|
|
7
|
+
.testguard/evidence.json
|
|
8
|
+
.testguard/evidence-provisional.json
|
|
9
|
+
.testguard/evidence-partial.json
|
|
10
|
+
.testguard/brief.json
|
|
11
|
+
.testguard/scaffold-*.json
|
|
12
|
+
# Local agent tooling — not part of the public project
|
|
13
|
+
.wolf/
|
|
14
|
+
.claude/
|
|
15
|
+
CLAUDE.local.md
|
|
16
|
+
graphify-out/
|
|
17
|
+
.vitest/
|
|
18
|
+
# Python build artefacts
|
|
19
|
+
dist/
|
|
20
|
+
build/
|
|
21
|
+
*.egg-info/
|
|
22
|
+
__pycache__/
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"tool": {
|
|
4
|
+
"name": "testguard",
|
|
5
|
+
"version": "0.3.0"
|
|
6
|
+
},
|
|
7
|
+
"generatedAt": "2026-09-17T15:24:13.654Z",
|
|
8
|
+
"state": "clean",
|
|
9
|
+
"next": {
|
|
10
|
+
"action": "none",
|
|
11
|
+
"command": "testguard probe",
|
|
12
|
+
"why": "Every claim is defended. Re-probe after changing code, tests or claims; add a claim for every new invariant."
|
|
13
|
+
},
|
|
14
|
+
"provisional": false,
|
|
15
|
+
"counts": {
|
|
16
|
+
"claims": 12,
|
|
17
|
+
"faults": 12,
|
|
18
|
+
"byVerdict": {
|
|
19
|
+
"killed": 12
|
|
20
|
+
},
|
|
21
|
+
"new": 0,
|
|
22
|
+
"baselined": 0
|
|
23
|
+
},
|
|
24
|
+
"stale": [],
|
|
25
|
+
"changedFaults": [],
|
|
26
|
+
"findings": [],
|
|
27
|
+
"paths": {
|
|
28
|
+
"claims": "testguard.claims.json",
|
|
29
|
+
"evidence": ".testguard/evidence.json",
|
|
30
|
+
"baseline": ".testguard/baseline.json"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -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/probe/runners/` — one module per runner (`name`, `testGlobs`, `check`, `tests`, `run`) over `shared.mjs` (jest-compatible report parsing, budgeted process runner). A new runner needs its own fixture copy with the same `expected.json`.
|
|
34
35
|
- `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.
|
|
35
36
|
|
|
36
37
|
## Rules
|
|
@@ -56,3 +57,12 @@ node cli/testguard.mjs scaffold src/probe/classify.mjs --json # what the produ
|
|
|
56
57
|
Dependabot minor/patch PRs and automated release PRs auto-merge when the exact
|
|
57
58
|
CI run is green on all three Node legs. Majors and anything else wait for a
|
|
58
59
|
human. Release publishing is OIDC-only; there are no tokens in this repo.
|
|
60
|
+
|
|
61
|
+
<!-- testguard:begin -->
|
|
62
|
+
## TestGuard
|
|
63
|
+
|
|
64
|
+
This project's tests are verified by [TestGuard](https://github.com/raccioly/testguard).
|
|
65
|
+
Before writing or changing tests, run `testguard status --json` and follow `next`.
|
|
66
|
+
The full operating loop and the verdict table are in `.claude/skills/testguard/SKILL.md`.
|
|
67
|
+
Never make a fault die by editing `testguard.claims.json`; write the test. Claim edits are recorded in the evidence.
|
|
68
|
+
<!-- testguard:end -->
|
|
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2026-09-17
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **jest runner.** `--runner vitest|jest|auto` (default `auto`: the first
|
|
15
|
+
that resolves in the project). jest runs with `--ci --json --runTestsByPath`
|
|
16
|
+
so file arguments are exact paths, not regexes; its `Exceeded timeout`
|
|
17
|
+
wording is recognised as a timeout, never a kill; `__tests__/` files are
|
|
18
|
+
collected for escalation. `run.runner.name` records which runner produced
|
|
19
|
+
the evidence.
|
|
20
|
+
- A CommonJS edition of the known-answer fixture (`fixtures/known-answer-jest`)
|
|
21
|
+
with the same claims and the same expected verdicts; the acceptance suite
|
|
22
|
+
and CI prove jest against it exactly as vitest is proven against the ESM
|
|
23
|
+
one.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Runners are now modules behind one interface (`src/probe/runners/`); the
|
|
28
|
+
vitest path is unchanged and still reproduces its oracle.
|
|
29
|
+
|
|
30
|
+
## [0.3.1] - 2026-09-17
|
|
31
|
+
|
|
32
|
+
Dogfooding `init` and `status` on this repository.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- `init` no longer adds per-file `.gitignore` lines to a project that already
|
|
37
|
+
ignores `.testguard/` wholesale; it notes instead that `baseline.json` is
|
|
38
|
+
meant to be committed.
|
|
39
|
+
- The brief no longer says "everything is new" when nothing is unproven.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- This repository now carries its own `AGENTS.md` TestGuard section, written
|
|
44
|
+
by `testguard init`.
|
|
45
|
+
|
|
10
46
|
## [0.3.0] - 2026-09-17
|
|
11
47
|
|
|
12
48
|
The agent operating layer. TestGuard is meant to be run by AI agents; this
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: testguard-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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.
|
|
79
|
+
| GitHub Action | `uses: raccioly/testguard@v0.4.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
|
|
@@ -135,6 +135,11 @@ npx testguard-cli scaffold src/x.ts # propose faults for a file, as a draft to
|
|
|
135
135
|
files that import the fault's target, by relative path or resolved alias.
|
|
136
136
|
`NOCOVER` then means exactly "no test file imports this source".
|
|
137
137
|
|
|
138
|
+
Runners: **vitest** and **jest** (`--runner auto` picks the first that
|
|
139
|
+
resolves; both read the same jest-compatible JSON report). Anything else
|
|
140
|
+
goes through `--runner-cmd`. Each runner is proven against its own copy
|
|
141
|
+
of the known-answer fixture.
|
|
142
|
+
|
|
138
143
|
Practical loop: first pass `--no-escalate` (escalation re-runs the whole
|
|
139
144
|
suite N times per survivor); iterate on one claim with `--claim <ID>` and
|
|
140
145
|
either `--include-dirty` or `--in-place` (only fault target files must be
|
|
@@ -239,14 +244,14 @@ through every verdict.
|
|
|
239
244
|
|
|
240
245
|
## Status
|
|
241
246
|
|
|
242
|
-
**v0.
|
|
243
|
-
mechanical scaffold, and an agent operating layer (`status`, `init`). The contract
|
|
247
|
+
**v0.4.** Seven commands, vitest and jest runners, hand-authored faults plus
|
|
248
|
+
a mechanical scaffold, and an agent operating layer (`status`, `init`). The contract
|
|
244
249
|
spine — six JSON Schemas shared with the other Guard tools — is under
|
|
245
250
|
[`spec/`](spec/). One exact-pinned runtime dependency (`ajv`, for schema validation); Node ≥ 20.
|
|
246
251
|
|
|
247
|
-
Not yet: test generation (the two-gate acceptance loop),
|
|
248
|
-
AST-aware producers, and calibration of fault classes
|
|
249
|
-
bugs. Each is designed for; none is claimed.
|
|
252
|
+
Not yet: test generation (the two-gate acceptance loop), runners beyond
|
|
253
|
+
vitest and jest, AST-aware producers, and calibration of fault classes
|
|
254
|
+
against real escaped bugs. Each is designed for; none is claimed.
|
|
250
255
|
|
|
251
256
|
## Licence
|
|
252
257
|
|
|
@@ -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.
|
|
55
|
+
| GitHub Action | `uses: raccioly/testguard@v0.4.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
|
|
@@ -111,6 +111,11 @@ npx testguard-cli scaffold src/x.ts # propose faults for a file, as a draft to
|
|
|
111
111
|
files that import the fault's target, by relative path or resolved alias.
|
|
112
112
|
`NOCOVER` then means exactly "no test file imports this source".
|
|
113
113
|
|
|
114
|
+
Runners: **vitest** and **jest** (`--runner auto` picks the first that
|
|
115
|
+
resolves; both read the same jest-compatible JSON report). Anything else
|
|
116
|
+
goes through `--runner-cmd`. Each runner is proven against its own copy
|
|
117
|
+
of the known-answer fixture.
|
|
118
|
+
|
|
114
119
|
Practical loop: first pass `--no-escalate` (escalation re-runs the whole
|
|
115
120
|
suite N times per survivor); iterate on one claim with `--claim <ID>` and
|
|
116
121
|
either `--include-dirty` or `--in-place` (only fault target files must be
|
|
@@ -215,14 +220,14 @@ through every verdict.
|
|
|
215
220
|
|
|
216
221
|
## Status
|
|
217
222
|
|
|
218
|
-
**v0.
|
|
219
|
-
mechanical scaffold, and an agent operating layer (`status`, `init`). The contract
|
|
223
|
+
**v0.4.** Seven commands, vitest and jest runners, hand-authored faults plus
|
|
224
|
+
a mechanical scaffold, and an agent operating layer (`status`, `init`). The contract
|
|
220
225
|
spine — six JSON Schemas shared with the other Guard tools — is under
|
|
221
226
|
[`spec/`](spec/). One exact-pinned runtime dependency (`ajv`, for schema validation); Node ≥ 20.
|
|
222
227
|
|
|
223
|
-
Not yet: test generation (the two-gate acceptance loop),
|
|
224
|
-
AST-aware producers, and calibration of fault classes
|
|
225
|
-
bugs. Each is designed for; none is claimed.
|
|
228
|
+
Not yet: test generation (the two-gate acceptance loop), runners beyond
|
|
229
|
+
vitest and jest, AST-aware producers, and calibration of fault classes
|
|
230
|
+
against real escaped bugs. Each is designed for; none is claimed.
|
|
226
231
|
|
|
227
232
|
## Licence
|
|
228
233
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Known-answer fixture — jest edition
|
|
2
|
+
|
|
3
|
+
The same nine claims and eleven faults as [`../known-answer`](../known-answer), in
|
|
4
|
+
CommonJS, run by **jest** (`jest.config.js`: `testTimeout: 1000`). Every verdict in
|
|
5
|
+
`expected.json` is the same. Its test suite is flaky by design (`test/flaky.test.js`).
|
|
6
|
+
|
|
7
|
+
It proves the jest runner adapter reproduces the oracle; the ESM fixture proves vitest.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"confirmRuns": 3,
|
|
3
|
+
"expected": {
|
|
4
|
+
"REDACT-001/F1": {
|
|
5
|
+
"verdict": "survived",
|
|
6
|
+
"why": "The audit-row test asserts with objectContaining and omits `content`; a row carrying the raw input passes."
|
|
7
|
+
},
|
|
8
|
+
"REDACT-001/F2": {
|
|
9
|
+
"verdict": "killed",
|
|
10
|
+
"why": "The masked-output test asserts the secret is absent."
|
|
11
|
+
},
|
|
12
|
+
"REDACT-002/F1": {
|
|
13
|
+
"verdict": "killed",
|
|
14
|
+
"why": "The invalid-rule test asserts compileRules does not throw."
|
|
15
|
+
},
|
|
16
|
+
"REDACT-003/F1": {
|
|
17
|
+
"verdict": "survived",
|
|
18
|
+
"why": "No test exercises a missing scope; the fail-closed guard is undefended."
|
|
19
|
+
},
|
|
20
|
+
"REDACT-004/F1": {
|
|
21
|
+
"verdict": "timeout",
|
|
22
|
+
"why": "Tests awaiting redact() hit the 1s jest testTimeout; a timeout is not an assertion failure and is not a kill."
|
|
23
|
+
},
|
|
24
|
+
"REDACT-005/F1": {
|
|
25
|
+
"verdict": "unverifiable",
|
|
26
|
+
"reason": "anchor-missing",
|
|
27
|
+
"why": "The find string is not present in the source."
|
|
28
|
+
},
|
|
29
|
+
"REDACT-005/F2": {
|
|
30
|
+
"verdict": "unverifiable",
|
|
31
|
+
"reason": "anchor-ambiguous",
|
|
32
|
+
"why": "`return null;` occurs twice; expectHits is 1."
|
|
33
|
+
},
|
|
34
|
+
"REDACT-006/F1": {
|
|
35
|
+
"verdict": "fault-invalid",
|
|
36
|
+
"reason": "replacement-does-not-compile",
|
|
37
|
+
"why": "The replacement introduces a syntax error; the suite cannot load."
|
|
38
|
+
},
|
|
39
|
+
"EXPORT-001/F1": {
|
|
40
|
+
"verdict": "nocover",
|
|
41
|
+
"why": "test/export.test.js does not exist; no defender resolves."
|
|
42
|
+
},
|
|
43
|
+
"FLAKY-001/F1": {
|
|
44
|
+
"verdict": "flaky-defender",
|
|
45
|
+
"reason": "defenders-not-green",
|
|
46
|
+
"why": "test/flaky.test.js fails every other run; the baseline is not green N/N."
|
|
47
|
+
},
|
|
48
|
+
"DISCOVER-001/F1": {
|
|
49
|
+
"verdict": "killed",
|
|
50
|
+
"why": "No defendedBy; test/redact.test.js is discovered because it imports src/redact.js, and its masked-output test fails."
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"runner": "jest"
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = { testEnvironment: 'node', testTimeout: 1000, testMatch: ['**/test/**/*.test.js'] };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "testguard-known-answer-fixture-jest",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"description": "The known-answer fixture in CommonJS for jest. Same claims, same expected verdicts. FLAKY BY DESIGN (test/flaky.test.js).",
|
|
6
|
+
"scripts": { "test": "jest" }
|
|
7
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Redaction engine for the known-answer fixture (CommonJS, for jest).
|
|
2
|
+
// Every claim in ../testguard.claims.json is about this file.
|
|
3
|
+
|
|
4
|
+
/** Compile a rule's pattern; an invalid pattern yields null and is skipped. */
|
|
5
|
+
function safeRegex(pattern) {
|
|
6
|
+
try {
|
|
7
|
+
return new RegExp(pattern, 'g');
|
|
8
|
+
} catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Find a rule by id. */
|
|
14
|
+
function findRule(rules, id) {
|
|
15
|
+
for (const rule of rules) {
|
|
16
|
+
if (rule.id === id) return rule;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Compile all rules. An invalid pattern is skipped; it never aborts the scan. */
|
|
22
|
+
function compileRules(rules) {
|
|
23
|
+
const compiled = [];
|
|
24
|
+
for (const rule of rules) {
|
|
25
|
+
const re = safeRegex(rule.pattern);
|
|
26
|
+
if (re === null) continue;
|
|
27
|
+
compiled.push({ ...rule, re });
|
|
28
|
+
}
|
|
29
|
+
return compiled;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Replace every match of every rule with asterisks of equal length. */
|
|
33
|
+
function mask(input, rules) {
|
|
34
|
+
let out = input;
|
|
35
|
+
for (const rule of compileRules(rules)) {
|
|
36
|
+
out = out.replace(rule.re, (m) => '*'.repeat(m.length));
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Redact a message and write an audit row when anything was masked.
|
|
43
|
+
* @claim REDACT-003 Fails closed: a missing scope is an error, never an unscoped scan.
|
|
44
|
+
* The audit row must never carry the original input.
|
|
45
|
+
*/
|
|
46
|
+
async function redact(input, rules, ctx, store) {
|
|
47
|
+
if (!ctx || !ctx.scope) {
|
|
48
|
+
throw new Error('redact: scope is required');
|
|
49
|
+
}
|
|
50
|
+
const redacted = mask(input, rules);
|
|
51
|
+
if (redacted !== input) {
|
|
52
|
+
await store.writeAudit({
|
|
53
|
+
action: 'MASK',
|
|
54
|
+
scope: ctx.scope,
|
|
55
|
+
ruleCount: rules.length,
|
|
56
|
+
content: redacted,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return redacted;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { findRule, compileRules, mask, redact };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const { readFileSync, writeFileSync } = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
// Deterministically flaky: fails on odd-numbered runs (the `flaky-defender` case).
|
|
5
|
+
const counterFile = path.join(__dirname, '..', '.flake-counter');
|
|
6
|
+
|
|
7
|
+
it('is flaky by design (fails every other run)', () => {
|
|
8
|
+
let n = 0;
|
|
9
|
+
try { n = Number(readFileSync(counterFile, 'utf8')) || 0; } catch {}
|
|
10
|
+
n += 1;
|
|
11
|
+
writeFileSync(counterFile, String(n));
|
|
12
|
+
expect(n % 2).toBe(0);
|
|
13
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const { mask, redact, compileRules } = require('../src/redact');
|
|
2
|
+
|
|
3
|
+
const SECRET = 'FIXTURE_SECRET_7731';
|
|
4
|
+
const RULES = [{ id: 'r1', pattern: 'FIXTURE_SECRET_\\d+' }];
|
|
5
|
+
const CTX = { scope: 'g1' };
|
|
6
|
+
|
|
7
|
+
describe('redaction', () => {
|
|
8
|
+
it('masks the secret out of the returned text', () => {
|
|
9
|
+
const out = mask(`hello ${SECRET} bye`, RULES);
|
|
10
|
+
expect(out).not.toContain(SECRET);
|
|
11
|
+
expect(out).toBe('hello ******************* bye');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('skips a rule whose pattern is invalid without throwing', () => {
|
|
15
|
+
const rules = [{ id: 'bad', pattern: '(' }, ...RULES];
|
|
16
|
+
expect(() => compileRules(rules)).not.toThrow();
|
|
17
|
+
expect(compileRules(rules)).toHaveLength(1);
|
|
18
|
+
expect(mask(SECRET, rules)).not.toContain(SECRET);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('writes an audit row when something was masked', async () => {
|
|
22
|
+
const store = { writeAudit: jest.fn().mockResolvedValue(undefined) };
|
|
23
|
+
await redact(`hello ${SECRET}`, RULES, CTX, store);
|
|
24
|
+
// THE BLIND SPOT: objectContaining ignores unlisted keys, and `content` is not listed.
|
|
25
|
+
expect(store.writeAudit).toHaveBeenCalledTimes(1);
|
|
26
|
+
expect(store.writeAudit).toHaveBeenCalledWith(expect.objectContaining({ action: 'MASK', scope: 'g1', ruleCount: 1 }));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('does not write an audit row when nothing matched', async () => {
|
|
30
|
+
const store = { writeAudit: jest.fn() };
|
|
31
|
+
const out = await redact('nothing here', RULES, CTX, store);
|
|
32
|
+
expect(out).toBe('nothing here');
|
|
33
|
+
expect(store.writeAudit).not.toHaveBeenCalled();
|
|
34
|
+
});
|
|
35
|
+
});
|