shallnot

module
v0.4.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 19, 2026 License: Apache-2.0

README

shallnot logo

shallnot

A gate that stops a coding agent from saying "done" while a requirement has no passing test.

CI Latest release Go reference

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.

Use it with your agent

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)
  • a section in 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 test
  • an end-of-turn hook for Claude Code, and for each other harness the project is set up for: Codex, Copilot, Cursor, Gemini CLI, OpenCode, Qwen Code, Factory Droid, Goose

Commit 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

What it looks like

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.

In CI

- run: pytest --junitxml=junit.xml
  continue-on-error: true
- uses: RachidChabane/shallnot@v0.4.2
  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.

What a pass does not prove

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.

Documentation

Contributing

Bug reports and pull requests are welcome. CONTRIBUTING.md has the build and test commands. Security issues: SECURITY.md.

License

Apache-2.0

Directories

Path Synopsis
cmd
shallnot command
Command shallnot is a spec-to-test traceability gate.
Command shallnot is a spec-to-test traceability gate.
internal
adapters/agenthook
Package agenthook adapts the gate to the end-of-turn hooks of agent harnesses: it reads the harness's hook input and answers in the harness's own protocol, so that an agent cannot finish its turn on a blocked gate.
Package agenthook adapts the gate to the end-of-turn hooks of agent harnesses: it reads the harness's hook input and answers in the harness's own protocol, so that an agent cannot finish its turn on a blocked gate.
adapters/config
Package config reads the optional shallnot.yaml configuration file.
Package config reads the optional shallnot.yaml configuration file.
adapters/junitxml
Package junitxml reads test results in the JUnit XML format emitted by pytest, jest-junit, Vitest, Surefire, Gradle and gotestsum.
Package junitxml reads test results in the JUnit XML format emitted by pytest, jest-junit, Vitest, Surefire, Gradle and gotestsum.
adapters/report
Package report renders a completed run for its different readers.
Package report renders a completed run for its different readers.
adapters/scaffold
Package scaffold equips a repository for shallnot: a starter configuration for the test runners it finds, the agent instructions, and the end-of-turn hooks.
Package scaffold equips a repository for shallnot: a starter configuration for the test runners it finds, the agent instructions, and the end-of-turn hooks.
adapters/scan
Package scan finds binding tags in test source files.
Package scan finds binding tags in test source files.
adapters/spec
Package spec loads requirements from Markdown and YAML specifications.
Package spec loads requirements from Markdown and YAML specifications.
adapters/testrun
Package testrun runs a project's test commands through the platform shell.
Package testrun runs a project's test commands through the platform shell.
analysis
Package analysis joins requirements, source tags and test results into a coverage matrix and classified findings.
Package analysis joins requirements, source tags and test results into a coverage matrix and classified findings.
app
Package app orchestrates a run: it resolves inputs, loads them through the adapters and hands them to the analysis.
Package app orchestrates a run: it resolves inputs, loads them through the adapters and hands them to the analysis.
cli
Package cli is the command-line front end.
Package cli is the command-line front end.
testkit
Package testkit holds helpers shared by the tests.
Package testkit holds helpers shared by the tests.
version
Package version identifies the build.
Package version identifies the build.
Package plugin embeds the agent skills that the plugin package distributes, so that `shallnot init` installs the same text.
Package plugin embeds the agent skills that the plugin package distributes, so that `shallnot init` installs the same text.
Package schemas embeds the JSON Schemas of the tool's public formats.
Package schemas embeds the JSON Schemas of the tool's public formats.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL