Traceability gate for coding agents: blocks "done" until every requirement has a passing test. Works with Claude Code, Cursor, pytest, Jest, Vitest, JUnit 5, Go.
Go
1
22 commits
updated Sep 19, 2026
A gate that stops a coding agent from saying "done" while a requirement has no passing test.

Your specs give each requirement an ID. Tests say which requirement they verify. shallnot reads the specs, the test sources and the JUnit XML, and fails unless every requirement is cited by a test that actually ran and passed. Hooked into the agent, it sends the findings back and the agent keeps working until the gate is green.
It is one static binary. It never touches the network or a model, so the same inputs always give the same report.
Install the binary:
go install github.com/RachidChabane/shallnot/cmd/shallnot@latest
or take a prebuilt one from the
releases page
(Linux, macOS, Windows; amd64 and arm64; checksums.txt alongside).
Then, in your project:
shallnot init
This writes:
shallnot.yaml, filled in for the test runner it finds (pytest, Jest,
Vitest, Maven, Gradle, Go)AGENTS.md, a CLAUDE.md that imports it, and project skills
(.agents/skills, .claude/skills): how to write a requirement, tag a
test, run the gate, review a tagged testCommit those files. From then on you ask for features the usual way and never mention shallnot. The agent writes the requirement, the code and the tagged tests. If it tries to finish on a blocked gate, the hook hands it the findings. After three blocks in a row the hook lets go and tells you.
VS Code Copilot picks up the Claude Code hook. Harnesses that cannot hold the
end of a turn (Windsurf, Kiro, Amp, Zed, Cline) still read AGENTS.md and
the skills, and CI catches what they let through. The full table is in
docs/agents.md.
There is also a Claude Code plugin, for using the skills in every project without committing anything:
/plugin marketplace add RachidChabane/shallnot
/plugin install shallnot@shallnot
A requirement in a Markdown (or YAML) spec, as ID~REVISION:
- **PWD-2~1**: WHEN a password contains the account's username THE SYSTEM
SHALL reject it.
A test bound to it. The tag goes wherever the runner will report it:
@pytest.mark.verifies("PWD-2~1") # pytest
it("rejects the username [verifies PWD-2~1]", ...) // Jest, Vitest
@DisplayName("rejects the username [verifies PWD-2~1]") // JUnit 5
t.Run("rejects the username [verifies PWD-2~1]", ...) // Go
The gate:
$ shallnot check --specs spec.md --tests tests --results junit.xml
shallnot: FAIL
focus: every known requirement
requirements: 3 known, 3 in focus (1 covered, 0 failed, 0 skipped, 0 not run, 1 uncovered, 1 non-testable)
tests: 2 in results, 2 bound, 0 untagged
findings: 1 error, 0 warning, 0 info (1 blocking)
REQUIREMENTS
PWD-1~1 covered spec.md:3
passed tests.test_password › test_long_passwords_are_accepted tests/test_password.py:11
passed tests.test_password › test_short_passwords_are_rejected tests/test_password.py:6
PWD-2~1 uncovered spec.md:5
PWD-3~1 non_testable spec.md:7
FINDINGS
error uncovered_requirement spec.md:5 requirement PWD-2~1 has no bound test
A tag only counts if it shows up in the results file, so a tag in a comment or on a test that never ran binds nothing. When the meaning of a requirement changes, bump its revision: every test citing the old one fails the gate until someone re-checks it.
shallnot gate runs your test commands and then checks. shallnot check
only reads results that already exist.
| Exit code | Meaning |
|---|---|
0 | clean |
1 | blocked: some requirement lacks a passing test, or a tag is wrong |
2 | no verdict (missing results, bad config). Says nothing about coverage. |
Try it on a tiny project: examples/quickstart.
- run: pytest --junitxml=junit.xml
continue-on-error: true
- uses: RachidChabane/shallnot@v0.4.1
with:
args: --specs specs --tests tests --results junit.xml
The action writes a job summary and annotates the offending lines. shallnot-demo is a small repository gated this way.
A pass means every requirement is cited by a test that ran and passed. It
does not mean the test is any good. A tagged test that asserts nothing still
passes the gate. Judging that is a review job, and the shallnot-review
skill tells the agent how to do it.
init, the hooks, the pluginshallnot.yaml, flags, severitiesBug reports and pull requests are welcome. CONTRIBUTING.md has the build and test commands. Security issues: SECURITY.md.
22 commits
Go
83.5%
Shell
5.4%
Java
4.4%
Python
2.2%
Kotlin
1.9%
TypeScript
1.3%
JavaScript
1.2%
Traceability gate for coding agents: blocks "done" until every requirement has a passing test. Works with Claude Code, Cursor, pytest, Jest, Vitest, JUnit 5, Go.
Go
1
22 commits
updated Sep 19, 2026
A gate that stops a coding agent from saying "done" while a requirement has no passing test.

Your specs give each requirement an ID. Tests say which requirement they verify. shallnot reads the specs, the test sources and the JUnit XML, and fails unless every requirement is cited by a test that actually ran and passed. Hooked into the agent, it sends the findings back and the agent keeps working until the gate is green.
It is one static binary. It never touches the network or a model, so the same inputs always give the same report.
Install the binary:
go install github.com/RachidChabane/shallnot/cmd/shallnot@latest
or take a prebuilt one from the
releases page
(Linux, macOS, Windows; amd64 and arm64; checksums.txt alongside).
Then, in your project:
shallnot init
This writes:
shallnot.yaml, filled in for the test runner it finds (pytest, Jest,
Vitest, Maven, Gradle, Go)AGENTS.md, a CLAUDE.md that imports it, and project skills
(.agents/skills, .claude/skills): how to write a requirement, tag a
test, run the gate, review a tagged testCommit those files. From then on you ask for features the usual way and never mention shallnot. The agent writes the requirement, the code and the tagged tests. If it tries to finish on a blocked gate, the hook hands it the findings. After three blocks in a row the hook lets go and tells you.
VS Code Copilot picks up the Claude Code hook. Harnesses that cannot hold the
end of a turn (Windsurf, Kiro, Amp, Zed, Cline) still read AGENTS.md and
the skills, and CI catches what they let through. The full table is in
docs/agents.md.
There is also a Claude Code plugin, for using the skills in every project without committing anything:
/plugin marketplace add RachidChabane/shallnot
/plugin install shallnot@shallnot
A requirement in a Markdown (or YAML) spec, as ID~REVISION:
- **PWD-2~1**: WHEN a password contains the account's username THE SYSTEM
SHALL reject it.
A test bound to it. The tag goes wherever the runner will report it:
@pytest.mark.verifies("PWD-2~1") # pytest
it("rejects the username [verifies PWD-2~1]", ...) // Jest, Vitest
@DisplayName("rejects the username [verifies PWD-2~1]") // JUnit 5
t.Run("rejects the username [verifies PWD-2~1]", ...) // Go
The gate:
$ shallnot check --specs spec.md --tests tests --results junit.xml
shallnot: FAIL
focus: every known requirement
requirements: 3 known, 3 in focus (1 covered, 0 failed, 0 skipped, 0 not run, 1 uncovered, 1 non-testable)
tests: 2 in results, 2 bound, 0 untagged
findings: 1 error, 0 warning, 0 info (1 blocking)
REQUIREMENTS
PWD-1~1 covered spec.md:3
passed tests.test_password › test_long_passwords_are_accepted tests/test_password.py:11
passed tests.test_password › test_short_passwords_are_rejected tests/test_password.py:6
PWD-2~1 uncovered spec.md:5
PWD-3~1 non_testable spec.md:7
FINDINGS
error uncovered_requirement spec.md:5 requirement PWD-2~1 has no bound test
A tag only counts if it shows up in the results file, so a tag in a comment or on a test that never ran binds nothing. When the meaning of a requirement changes, bump its revision: every test citing the old one fails the gate until someone re-checks it.
shallnot gate runs your test commands and then checks. shallnot check
only reads results that already exist.
| Exit code | Meaning |
|---|---|
0 | clean |
1 | blocked: some requirement lacks a passing test, or a tag is wrong |
2 | no verdict (missing results, bad config). Says nothing about coverage. |
Try it on a tiny project: examples/quickstart.
- run: pytest --junitxml=junit.xml
continue-on-error: true
- uses: RachidChabane/shallnot@v0.4.1
with:
args: --specs specs --tests tests --results junit.xml
The action writes a job summary and annotates the offending lines. shallnot-demo is a small repository gated this way.
A pass means every requirement is cited by a test that ran and passed. It
does not mean the test is any good. A tagged test that asserts nothing still
passes the gate. Judging that is a review job, and the shallnot-review
skill tells the agent how to do it.
init, the hooks, the pluginshallnot.yaml, flags, severitiesBug reports and pull requests are welcome. CONTRIBUTING.md has the build and test commands. Security issues: SECURITY.md.
22 commits
Go
83.5%
Shell
5.4%
Java
4.4%
Python
2.2%
Kotlin
1.9%
TypeScript
1.3%
JavaScript
1.2%