An agent toolkit for repositories: it sets up and maintains a repo's agent machinery — its covenant, and the blessing path work travels along.
Status: early.
initbootstraps a Linear team to the covenant and installs the guard hook;guardenforces the covenant's authorization rules;queueandticketare the read layer;doctorreports a team's drift from the covenant. Thecovenantandblessverbs are stubs.
brew install mattwalters/wand/wandOr with Go:
go install github.com/mattwalters/wand@latestThat puts wand in $(go env GOPATH)/bin, which needs to be on your PATH.
Prebuilt binaries for macOS, Linux and Windows (amd64 and arm64) are on the
releases page; the v1 tag
always points at the latest v1.x.y.
wand # help
wand ui # the interactive interface
wand queue --team-key WND # the ranked, vetted Todo queue
wand ticket WND-3 # one ticket whole, for a cold reader
wand doctor --team-key WND # report the team's drift from the covenant
wand version # build info, and the covenant schema this binary speakswand version reports the covenant schema version alongside the build:
a repo's covenant file declares the schema it was written against, and
comparing the two is how you learn whether a given binary can read it.
The state graph — Triage → Backlog → Scoping → Todo → Needs Input →
In Progress → In Review → Done — is wand's opinion, gofmt-style. What a repo
customizes are the parameters of the machine, never its shape: a checked-in
wand.toml at the repo root carries status names over the fixed semantics,
caps (review rounds, CI attempts, worker timeouts), the estimate scale,
toggles, the three pluggable commands (verify, provision, run agent), ticket
templates, and a schema version so topology upgrades ship centrally as wand
releases. TOML, so the rationale for a value survives as a comment next to
the value it justifies:
schema = 1
[statuses]
# Our board predates wand and the team reads "Ready" as blessed-to-build.
todo = "Ready"
[caps]
review_rounds = 5
[commands]
verify = "make check"wand init and wand doctor read it when present and fall back to the stock
covenant when absent. Validation refuses unknown keys loudly — a misspelled cap silently
defaulting is the failure mode — and an invalid file is an error, never
quietly the defaults.
The file must never contain a secret, a machine path, or a harness name: those are machine config, not covenant. The test for the split: if two clones could legitimately differ, it is config; if a difference means two different processes, it is covenant.
wand doctor --team-key WAND reads the team's statuses, labels, PR
automations and settings, diffs them against the covenant, and reports the
drift. It writes nothing — wand init is the verb that repairs — and it
exits diff-style, so scripts and CI can hold a team to the covenant
continuously instead of a human verifying the settings pages once:
0— the team satisfies the covenant1— drift found (each finding on its owndrift:line)2— the check could not run (no API key, no such team, API failure)
The covenant, not Linear's settings pages, is the source of truth — the API exposes everything doctor needs, git automations included. Extra statuses outside the machine's path (a team's own "Design" column upstream of Backlog), extra labels, and automations on events the covenant does not mention are tolerated strangers, not drift. Renamed or missing covenant statuses, repointed automations, missing labels, and changed team settings are drift.
Some ticket transitions hand out authorization an agent does not have:
promoting to Todo blesses building, promoting to Scoping blesses
research, and Done, Canceled and Duplicate close a ticket. Those
are a human's call, so wand guard refuses them — by status name or by
Linear state type — while leaving every legitimate agent move alone
(In Progress, In Review, Needs Input, Backlog, Triage).
It speaks the Claude Code PreToolUse hook protocol: the pending tool call arrives as JSON on stdin, and exit code 2 blocks it with the reason on stderr. Input that does not parse passes — a broken guard must never wedge a session.
wand init installs the hook: an entry in .claude/settings.json routing
every Linear save_issue through wand guard. That entry is a build
artifact — regenerated by init, never hand-edited — and it assumes wand
is on your PATH.
wand queue prints a team's Todo issues in start order: priority ascending
with "No priority" last, oldest first within a priority, identifier as the
final tiebreak so two racing readers agree. Issues an agent may not start
are vetted out and printed with the reason — labeled human-only, or
blocked by an issue not yet completed or canceled (a started blocker still
blocks). Nothing is dropped silently: a queue that quietly comes up short
reads as a queue in order.
wand ticket WND-3 renders one issue for someone with no context — a worker
being prompted, or a human catching up. The description is passed whole;
comments follow oldest-first, every page of them, each held to a per-comment
budget so a long early comment cannot crowd out the short answer a human
left last.
Both need LINEAR_API_KEY in the environment, and both respect a
wand.toml's status renames — a board whose blessed column is called
"Ready" queues from "Ready".
The doctrine — the why behind every covenant state and rule, which
deliberately does not live in the covenant file — is at
wandcli.com. The docs
are versioned with the tool: every release publishes a frozen copy under its
own tag next to the moving latest, so a repo pinned to wand vX.Y.Z reads
the doctrine matching its covenant schema.
The site lives in docs/ — plain Markdown built by Hugo (a single
Go binary, keeping with the no-Node rule) with a hand-rolled minimal theme.
make docs-serve previews it locally.
Publishing is two parts: the release workflow commits the built site to the
gh-pages branch on every tag, and a one-time repo setting (Settings →
Pages → deploy from the gh-pages branch, with wandcli.com as the
verified custom domain) tells GitHub to serve it — after that, every
release publishes on its own.
You do not need to install anything to try it:
go run . uimake run does the same. To get a binary instead, make build writes
bin/wand (gitignored), and make install puts wand on your PATH.
Terminal UIs are awkward to test and nearly impossible for an AI agent to review, because the thing under test is a picture. wand renders screens to plain text instead:
$ wand ui --script "j,enter" --dump-screen
wand covenant
Read and edit the repository covenant
"covenant" is not implemented yet.
esc back • q quit--script applies a key sequence first, so any screen is reachable from one
command, and no terminal is needed. From a clone that is
go run . ui --script "j,enter" --dump-screen, or make screen SCRIPT=j,enter.
The test suite renders through the same code path, so those snapshots are stored as golden files that read as pictures of the UI:
tuitest.AssertScreen(t, "detail", tui.New(80, 24), "j,enter")When a screen changes, the failure is a unified diff of the screen itself
rather than a wall of escape codes. Under the hood a real tea.Program runs
with its output captured, and those ANSI bytes are replayed through a virtual
terminal emulator to recover the grid of characters a user would see.
Four tiers cover the app: pure Update tests, runtime wiring tests, golden
screens, and one end-to-end test that drives the compiled binary through a real
pseudo-terminal. See CLAUDE.md for the details and the rules that
keep it deterministic.
go run . ui # run the TUI from source
make test # fast suite
make test-e2e # pty smoke test
make check # gofmt + vet + test
make update-goldens # regenerate screens (then read the diff)
make docs # build the docs site into docs/public (needs hugo: brew install hugo)
make docs-serve # serve the docs at http://localhost:1313/ with live reload
make release VERSION=v0.1.0 # tag and push a release (CI does the rest)
make help # every targetBuilt with Bubble Tea, Lip Gloss, Bubbles and fang.
MIT — see LICENSE.