statutor
Statutor keeps a typed project ledger for repos that more than one coding
agent edits. Four planes each have one writer and a mutation policy. Hooks
apply that policy before a tool writes, and statutor staged applies it
again to the git index. The active policy is the committed .statutor.yaml.
The name is the agent noun of statuere: one who enacts. One kernel serves
Claude Code, Grok Build, OpenCode, Codex, Google Antigravity, the git
pre-commit floor, and any harness that calls validate. Release notes live
on GitHub Releases. There is no hand-maintained CHANGELOG.md.
| Plane | Files | Policy | Enforced by |
|---|---|---|---|
| Constitution | AGENTS.md (+ CLAUDE.md = @AGENTS.md) |
hard cap 200 lines | hook + git floor |
| State | HANDOFF.md | overwrite-only, ≤ 40 lines, required sections | hook + git floor |
| State | TASKS.md | stable T-NNNN ids; mutable checkbox/detail/order | hook + git floor + doctor |
| Log | DECISIONS.md | append-only, insertions only, supersede-never-edit | hook + git floor |
| Plan | ROADMAP.md, plans/ → plans/archive/ (frozen) | archive immutable | hook + git floor |
Plus a bash guard on every harness: shell writes to governed files
(>>, sed -i, tee, ...) are denied — the editor tools are the audited
path. Automatic adapters stay silent outside a repository explicitly marked
by .statutor.yaml. A complete quoted heredoc used as git commit -F -
message data is not scanned as shell code; its opener and all actual command
lines remain guarded.
The policies are ordered by reversibility and the guardrails follow that
order: a HANDOFF overwrite is reversible, a TASKS checkbox or detail edit is
reversible with history, a DECISIONS append is irreversible by policy, and an
archived plan is frozen (only git mv INTO plans/archive/ is allowed). Text
an agent reads or quotes — a note, a heredoc, a tool result — informs its
proposal but never authorizes a mutation: only the declared policy under the
committed HEAD snapshot gates a write, in-loop and at the git floor alike.
State task identities are durable: an existing ID cannot disappear or be renamed, while its checkbox, detail, and position may change. New IDs advance beyond the committed maximum. v0.4 intentionally has no pruning operation; completed entries remain until a separately specified identity-preserving archive exists (D-0016).
Kernel / adapter architecture
core/statutor_core.py single-file kernel: validate() + embedded templates
modes: hook | check | staged | init | trust approve
(fail-open hooks; staged floor fails closed)
core/statutor_doctor.py drift linter (stale stamps, budgets, unarchived plans)
core/statutor_global.py global-layer roots, schemas, hashes, CAS, backups
core/statutor_global_cli.py opt-in global instruction lifecycle and CLI
core/statutor_skills.py portable Agent Skill validation and projections
hooks/stop_doctor.py Claude Code Stop hook: runs statutor-doctor after each
turn and surfaces its WARN/ERROR lines as
additionalContext — non-blocking, silent when the
ledger is clean or the cwd isn't a statutor ledger at all
pyproject.toml pipx install → `statutor`, `statutor-doctor` on PATH
| Adapter | Mechanism | Coverage |
|---|---|---|
| Claude Code (repo root is the plugin) | PreToolUse Write|Edit|Bash|apply_patch → statutor hook; Stop → hooks/stop_doctor.py |
full in-loop + drift surfacing |
Google Antigravity (adapters/antigravity/) |
PreToolUse (run_command|write_to_file|replace_file_content) → statutor hook; Stop → statutor hook --stop |
full in-loop + drift surfacing |
OpenCode (adapters/opencode/statutor.ts) |
2.x ctx.tool.hook("execute.before"), 1.x tool.execute.before → statutor check --if-ledger |
in-loop (shell/write/edit/patch)¹ |
Codex CLI (adapters/codex/) |
PreToolUse (Claude-compatible protocol) → statutor hook |
bash guard + apply_patch² |
Grok Build (repo root is the plugin; adapters/grok/) |
PreToolUse search_replace|write|run_terminal_command → statutor hook; Stop → hooks/stop_doctor.py |
editor + shell in-loop, drift once per turn³ |
git (adapters/git/, .pre-commit-hooks.yaml) |
statutor staged in local pre-commit and CI |
staged-index backstop |
native (crates/statutor/) |
statutor-staged, conformance-gated ≡ Python |
local staged floor without Python |
Custom harnesses (statutor check, or import validate) |
embed in your own tool dispatch | full in-loop |
¹ in-loop for OpenCode 2.x shell/write/edit/patch and 1.x
bash/write/edit/apply_patch; the kernel parses patch envelopes
(T-0011), with two partial-diff blind spots: required sections
on an Update File and server-namespaced MCP tool ids — the git floor
covers both. Subagent tool calls
DO fire plugin hooks (verified on the 1.x hook surface, opencode v1.18.21,
2026-08-21); the
opposite claim (sst/opencode#5894) was a misdiagnosis, stale-closed
2026-04-15.
² Codex hooks are on by default since rust-v0.124.0 (2026-04-23) — the old
[features].codex_hooks flag is a deprecated legacy alias, and hooks need
a one-time trust approval (/hooks). Codex sends edits as tool_name
apply_patch + {"command": "<envelope>"}, which guard_apply_patch() parses (frozen/delete/
append-only/cap checks; see adapters/codex/). Residual gaps: MCP tools
and Update-File section checks — so the git floor remains
mandatory there.
³ Grok Build reuses the Claude hook protocol. The kernel canonicalizes
search_replace, write, run_terminal_command, and the Cursor-preset
names StrReplace and Shell. A truncated tool body, or a hashline_edit
with no old_string, is denied when the path is governed. Register the
root plugin once (grok plugin install) or the ~/.grok/hooks/ snippet in
adapters/grok/, not both. MCP tool ids and bash-mode ! commands stay
with the git floor. The capability pin is Grok Build 1.0.62.
Worker provenance (v0.5, statutor machine/statutor worker): automatic
hooks on every host prove an attempt only and record nothing by
themselves — the hook path stays fail-open. Ingress is executor- or
custom-run (begin → record --event attempt → attributed rewrite →
complete --session); --event mutation under an automatic harness is
denied. worker capabilities reports proven surfaces and gaps per host
(verified against Claude Code 2.1.258, Codex CLI 0.152.1, OpenCode 1.18.20,
Grok Build 1.0.62 baselines); worker compare <ref> explains sibling handoffs read-only. The
machine-local registry is a bounded ring (50 sessions per project, 20 leases
per worktree), not an audit trail; durable provenance is the attributed
HANDOFF lineage in Git history, which lives as long as the repository does.
Install
pipx install statutor # or: pip install -e .
statutor init . # scaffold any repo, any harness
statutor init . --type none # governed files only; env: STATUTOR_INIT_TYPE
# Claude Code (this repo doubles as the plugin):
/plugin marketplace add https://github.com/hoohugokim/statutor
/plugin install statutor@hoo-plugins --scope project
# then: /statutor-init /handoff /decide /statutor-doctor
# Grok Build — same repo, one registration (see adapters/grok/):
grok plugin marketplace add https://github.com/hoohugokim/statutor
grok plugin install statutor --trust
# .pre-commit-config.yaml
repos:
- repo: https://github.com/hoohugokim/statutor
rev: v0.7.3
hooks: [{id: statutor}]
init never mandates a layout (D-0024). It selects a profile — --type NAME,
else STATUTOR_INIT_TYPE, else the first marker found in the order below, else
min — and adds only empty conventional directories on top of the governed
files, plans/archive/, notes/, and the CLAUDE.md bridge. Present paths
are skipped, never overwritten, adopted, or followed; no profile writes
policy, constitution content, or ecosystem files, and the min fallback
prints and creates exactly what the pre-v0.6 init did.
| Profile | Adds | Selected by |
|---|---|---|
python |
tests/ |
pyproject.toml, setup.py, setup.cfg |
rust |
tests/ |
Cargo.toml |
node |
test/ |
package.json |
docs |
docs/ |
mkdocs.yml, _quarto.yml, an existing docs/ |
min |
nothing further | fallback when no marker matches, or explicitly |
none |
nothing, not even plans/archive/ or notes/ |
--type none or STATUTOR_INIT_TYPE=none only |
Per-repo policy lives in .statutor.yaml. In-loop checks use the committed
HEAD snapshot; the git floor judges the transaction under both HEAD and the
candidate index snapshot, so an unstaged or co-staged weakening cannot disable
existing rules. The format is a strict, zero-dependency YAML subset shared by
Python and Rust; malformed or unsupported committed/candidate policy denies.
Before the marker's first commit, automatic hooks use embedded defaults. An
edited worktree policy does not change hook behavior until it passes trust
approval and becomes HEAD in a separate commit.
After bootstrap, changing .statutor.yaml or Statutor's exact
CLAUDE.md → @AGENTS.md bridge requires an exact-tree Git-local receipt:
git add .statutor.yaml
statutor trust approve . --decision D-0015 --reason "why this changes trust"
statutor staged .
Approval displays the reserved diff and all staged paths, then requires the complete candidate tree ID. The mode-0600 receipt expires on any HEAD or index change and is never committed.
Release gate
With Python build/pytest, Cargo, and npm available, stage the candidate and
run python scripts/release_gate.py. The gate tests the exact Git index, audits
all package payloads, builds the Python sdist and wheel in scratch space,
installs the wheel into an isolated target, and smoke-tests both console
scripts. A tagged release additionally passes --tag vX.Y.Z --dist-dir dist;
verified artifacts are copied only after every check succeeds.
The v0.4 global layer also has a separate, opt-in current-host probe:
python scripts/global_e2e.py --json. It pins the tested Claude, Codex, and
OpenCode versions, rehomes every host under a temporary profile, performs no
model or network request, and proves native discovery where an offline surface
exists, modified-target refusal, and exact uninstall recovery. It is not part
of the hermetic release gate and never mutates the caller's real home.
Portable user layer (v0.4)
The global layer is opt-in and separate from project ledgers. init creates
only human-owned canonical sources and versioned state; it does not change any
host file. plan, status, and --json are read-only. apply owns only
whole generated files it created, while adopt HOST backs up an existing
regular file and imports its bytes verbatim into that host's canonical overlay.
statutor global init
statutor global plan --json
statutor global apply --host codex
statutor global status --json
statutor global doctor --json
statutor global adopt claude
statutor global uninstall --host codex
statutor global skill import ./my-skill
statutor global skill plan --json
statutor global skill apply
statutor global skill status --json
statutor global skill sync
statutor global skill uninstall
Every mutation prints its resolved plan first. Existing unmanaged targets are
conflicts, managed hand edits are never overwritten or removed, and uninstall
restores the original backup. --home, --config-root, and --state-root
provide explicit isolated roots for tests and alternate profiles. Codex
overrides and OpenCode's Claude fallback are reported as precedence warnings;
Statutor never rewrites them.
Imported skills are copied to a new human-owned canonical source, then
projected as complete trees to $HOME/.agents/skills and Claude's personal
skills root. Core SKILL.md metadata and the complete tree are validated;
host-specific frontmatter is preserved. Identical unmanaged trees require
explicit --adopt-identical, differing or hand-edited trees are conflicts,
and names in a foreign .agents/.skill-lock.json remain entirely foreign.
OpenCode discovers the portable and Claude projections, so Statutor reports
whether those duplicates are identical instead of creating a third native
OpenCode copy.
Unified global status is a fast, read-only inventory of effective instruction
precedence, ownership, disabled/foreign skills, duplicate names, legacy/native/
admin/plugin roots, and catalog size. global doctor performs the deeper full-
tree audit: managed drift and receipt topology, generated headers, unsafe links,
active divergent duplicates, Codex's configured instruction cap, and Statutor's
32 KiB skill-description diagnostic budget (reported explicitly as a Statutor
budget, not a host limit). Status exits zero when inventory completes; doctor
exits one when the audit contains errors. Neither command invokes a host binary
or performs a network request.
Provenance
Framework doctrine distilled from: the AGENTS.md open standard https://agents.md/, Claude Code memory & hooks docs https://code.claude.com/docs/en/memory, https://code.claude.com/docs/en/hooks, Anthropic on long-running agent harnesses https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents and context engineering https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents, MADR https://adr.github.io/madr/, Keep a Changelog https://keepachangelog.com/, Conventional Commits https://www.conventionalcommits.org/, pre-commit https://pre-commit.com/.
Metadata
Release files for statutor 0.7.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| statutor-0.7.3.tar.gz | 66.6 kB | With provenance | Details for statutor-0.7.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| statutor-0.7.3-py3-none-any.whl | Python 3 | none | any | Details for statutor-0.7.3-py3-none-any.whl |
Total release size: 137.4 kB
Release files / statutor-0.7.3.tar.gz
| Download URL | statutor-0.7.3.tar.gz |
|---|---|
| Size | 66.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
96222f1983c97b554ef9b9511fef057d31a8f840c85d4afac05c7e7f7703d3b0
|
|
BLAKE2b-256 checksum How to use checksums |
fd1195f0dd2945f1e3e116306de7247bcbc5209a35c7e85d52d0be948dc51aca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logRelease files / statutor-0.7.3-py3-none-any.whl
| Download URL | statutor-0.7.3-py3-none-any.whl |
|---|---|
| Size | 70.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8a3585fa98ae678cedd5088c25b26178231f43c33f27e4fcc40dd4ce92833d6d
|
|
BLAKE2b-256 checksum How to use checksums |
80b4a52fa9c42dd84b1f1de2ad0ebcd682cfda40c841be985dabbac09e96ed74
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logSecurity for statutor 0.7.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for statutor-0.7.3.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.