weeder is a gate for code an agent wrote. It reads the diff, refuses dishonest growth, and leaves with an exit code that says whether the change goes through. Deleted or weakened tests, skips, stubs, swallowed errors, secrets, guardrail edits and dependency-direction violations are the shapes it refuses. A static Rust binary, milliseconds, no tokens spent, every finding as SARIF 2.1.0.
$ weeder check
error G1 src/parser.ts:2 a merge conflict marker (<<<<<<<) was added.
why the file carries both sides of a merge nobody finished, so it does not
parse and does not run.
next finish the merge, delete the markers, and stage the file again.
error G1 src/parser.ts:4 a merge conflict marker (=======) was added.
why the file carries both sides of a merge nobody finished, so it does not
parse and does not run.
next finish the merge, delete the markers, and stage the file again.
error G1 src/parser.ts:6 a merge conflict marker (>>>>>>>) was added.
why the file carries both sides of a merge nobody finished, so it does not
parse and does not run.
next finish the merge, delete the markers, and stage the file again.
3 errors, 0 warnings, 0 notes
Exit 2. The commit does not happen.
An agent that has just written a change is the worst available judge of it. It will report success, and it has every incentive to reach success by the shortest road: delete the failing test, add a skip, catch the exception and move on, widen the timeout until the flake stops. Those are cheap to do and expensive to notice, and a reviewer reading a thousand-line diff notices them last.
weeder notices them first, deterministically, before a human opens the pull request. It has no
model, no prompt and no opinion about style. It reads what changed against what was there and
reports shapes: a test that is gone, an assertion that was dropped, a TODO where an
implementation belongs.
Release binaries are built for linux and macos on x86_64 and aarch64 and for windows on x86_64, one weeder-<target>.tar.gz
per platform on the releases page, with its SHA-256
beside it. The tarball holds the executable, garden.json and SKILL.md.
target=aarch64-apple-darwin # or x86_64-apple-darwin, x86_64-unknown-linux-musl, aarch64-unknown-linux-musl, x86_64-pc-windows-msvc
base=https://github.com/jahala/weeder/releases/latest/download
curl -fsSLO "$base/weeder-$target.tar.gz" -O "$base/weeder-$target.tar.gz.sha256"
shasum -a 256 -c "weeder-$target.tar.gz.sha256"
tar xzf "weeder-$target.tar.gz" weeder
install -m 755 weeder ~/.local/bin/weederThe windows tarball holds weeder.exe. The crate on crates.io and the npm wrapper
(@plotplot/weeder) publish from the same release workflow once the registries are set up;
until then the tarball is the install.
Installing the binary guards nothing yet. weeder judges a repository once one of three places calls it, and each one is set up on purpose.
In git, per repository. This writes hooks into .githooks and points git at them, so every
commit and push is judged. It refuses where another tool already owns the hooks, such as husky,
and names the way forward.
cd your-repository
weeder guard install --protect main
weeder guard statusIn Claude Code, per machine or per project. Add this to ~/.claude/settings.json, or to
.claude/settings.json in a repository, so a commit over a blocking change is refused and a
turn cannot end over one. Gemini CLI takes weeder hook gemini on BeforeTool and
AfterAgent; Codex takes weeder hook codex on PreToolUse and Stop.
{
"hooks": {
"PreToolUse": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "weeder hook claude" }] }],
"Stop": [{ "matcher": "", "hooks": [{ "type": "command", "command": "weeder hook claude" }] }]
}
}In CI, per repository: copy examples/ci/github.yml (below). This is the one that holds. The
hooks above run on a machine the agent can also write to, so they catch honest mistakes early,
and an agent working against them can get past them. CI judges a fresh checkout against the
protected branch, under the weeder.toml that branch carries.
| Face | What it does |
|---|---|
weeder check |
judges a diff and may block. This is the one wired into hooks and CI |
weeder scan |
judges the repository as it is and never blocks: rotted docs, dead exports, stale work markers, lagging pins |
weeder guard |
puts that judgement inside git, through hooks that run on every commit and push |
weeder hook |
answers an agent harness's hook event, so a turn ends against weeder's verdict |
weeder rules |
prints the catalogue, the level each rule carries, and what it may reach over the network |
weeder check --staged
weeder check --base origin/main --strict --format sarif
weeder scan --format sarif
weeder guard install --protect main
weeder guard status
weeder hook claude
weeder rules --format jsonweeder check with no arguments judges the index and the working tree against HEAD, including
the files git has never been told about, which is most of what an agent writes. On a terminal it
writes a table; on a pipe it writes SARIF. --strict reports suppressed findings at their own
level, which is how a reviewer sees what an agent waved through, and --untracked exclude leaves
the unstaged files out.
A finding is allowed through on the record and never in silence: a Weeder-allow: <RULE> <reason> trailer on the commit being prepared, or a weeder-allow <RULE>: <reason> comment
on the line. Both need a reason, both stay in the log as a note carrying it, and --strict
hands the finding back the level its rule carries. docs/rules.md says the rest.
| Code | Meaning |
|---|---|
| 0 | clean, or warnings only |
| 2 | at least one block-level finding |
| 3 | weeder could not run, and the message says why |
Exit 3 is a refusal, not a pass: weeder judged nothing, so nothing was cleared. Only the unambiguous rules block by default. The rest warn, and warnings are for the human at the pull request rather than for the agent.
examples/ci/github.yml is a workflow you can copy: it judges the pull request against the
branch it is opening onto, writes SARIF, and hands that to GitHub's code scanning, so every
finding lands as an annotation on the diff a reviewer is already reading.
- run: weeder check --base origin/${{ github.base_ref }} --strict --format sarif > weeder.sarif
- uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: weeder.sarifFor a pleach plan, examples/pleach/plan.json gates every node on weeder and docs/pleach.md
explains why that gate needs no base argument.
The SARIF weeder writes is plain SARIF 2.1.0: one run per invocation, one result per finding,
rules declared in the tool component, suppressions carried as SARIF suppressions rather than
dropped. docs/sarif.md names every convention and the test that pins it, and
schemas/sarif-schema-2.1.0.json is the official schema the tests validate against.
SKILL.md is the whole binary in one file: every command, every flag, and how to work
against a gate rather than around it. Install it into your harness's skill directory, or read
it as-is.
cargo fmt --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspaceAGENTS.md is the working brief: the layout, the dependency direction weeder enforces on
itself, and the rules any change here is held to. Every rule is built from the adversarial
fixtures under fixtures/adversarial/, one minimal repository pe
7595
r rule per language.
MIT. See LICENSE.
plotplot is a garden of small, sharp tools for building with AI: plotplot.ai
tilth · tend · petals · pleach · umbel · copeca · pollen · weeder
© 2026 · a plotplot garden tool · MIT
