statutor 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- statutor-0.4.0/MANIFEST.in +12 -0
- statutor-0.4.0/PKG-INFO +208 -0
- statutor-0.4.0/README.md +183 -0
- statutor-0.4.0/core/statutor.egg-info/PKG-INFO +208 -0
- {statutor-0.3.0 → statutor-0.4.0}/core/statutor.egg-info/SOURCES.txt +6 -6
- statutor-0.4.0/core/statutor.egg-info/top_level.txt +6 -0
- statutor-0.4.0/core/statutor_core.py +1428 -0
- {statutor-0.3.0 → statutor-0.4.0}/core/statutor_doctor.py +24 -24
- statutor-0.4.0/core/statutor_global.py +730 -0
- statutor-0.4.0/core/statutor_global_cli.py +724 -0
- statutor-0.4.0/core/statutor_global_status.py +788 -0
- statutor-0.4.0/core/statutor_skills.py +534 -0
- {statutor-0.3.0 → statutor-0.4.0}/pyproject.toml +2 -2
- statutor-0.3.0/PKG-INFO +0 -107
- statutor-0.3.0/README.md +0 -82
- statutor-0.3.0/core/statutor.egg-info/PKG-INFO +0 -107
- statutor-0.3.0/core/statutor.egg-info/top_level.txt +0 -2
- statutor-0.3.0/core/statutor_core.py +0 -582
- statutor-0.3.0/tests/test_conformance_rust.py +0 -117
- statutor-0.3.0/tests/test_doctor.py +0 -583
- statutor-0.3.0/tests/test_kernel.py +0 -1463
- statutor-0.3.0/tests/test_pre_commit.py +0 -116
- statutor-0.3.0/tests/test_stop_hook.py +0 -234
- {statutor-0.3.0 → statutor-0.4.0}/LICENSE +0 -0
- {statutor-0.3.0 → statutor-0.4.0}/core/statutor.egg-info/dependency_links.txt +0 -0
- {statutor-0.3.0 → statutor-0.4.0}/core/statutor.egg-info/entry_points.txt +0 -0
- {statutor-0.3.0 → statutor-0.4.0}/setup.cfg +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
include LICENSE
|
|
2
|
+
include MANIFEST.in
|
|
3
|
+
include README.md
|
|
4
|
+
include pyproject.toml
|
|
5
|
+
include core/statutor_core.py
|
|
6
|
+
include core/statutor_doctor.py
|
|
7
|
+
include core/statutor_global.py
|
|
8
|
+
include core/statutor_global_cli.py
|
|
9
|
+
include core/statutor_global_status.py
|
|
10
|
+
include core/statutor_skills.py
|
|
11
|
+
prune tests
|
|
12
|
+
global-exclude __pycache__ *.py[cod]
|
statutor-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: statutor
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Typed project-ledger framework for agentic repos: four planes, single-writer files, enforced mutation policies.
|
|
5
|
+
Author: Hoo
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/hoohugokim/statutor
|
|
8
|
+
Project-URL: Repository, https://github.com/hoohugokim/statutor
|
|
9
|
+
Project-URL: Issues, https://github.com/hoohugokim/statutor/issues
|
|
10
|
+
Keywords: agents,claude,opencode,codex,ledger,governance,hooks,pre-commit,adr,decision-records
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# statutor
|
|
27
|
+
|
|
28
|
+
Typed project-ledger framework for agentic repos. A *statutor* is one who
|
|
29
|
+
enacts (agent noun of *statuere*) — which is the thesis: repo instruction
|
|
30
|
+
files are a state machine of typed registers, each with a mutation policy
|
|
31
|
+
and exactly one writer, enforced by hooks and git, not by prose.
|
|
32
|
+
|
|
33
|
+
| Plane | Files | Policy | Enforced by |
|
|
34
|
+
|---|---|---|---|
|
|
35
|
+
| Constitution | AGENTS.md (+ CLAUDE.md = `@AGENTS.md`) | hard cap 200 lines | hook + git floor |
|
|
36
|
+
| State | HANDOFF.md | overwrite-only, ≤ 40 lines, required sections | hook + git floor |
|
|
37
|
+
| State | TASKS.md | stable T-NNNN ids; mutable checkbox/detail/order | hook + git floor + doctor |
|
|
38
|
+
| Log | DECISIONS.md | append-only, insertions only, supersede-never-edit | hook + git floor |
|
|
39
|
+
| Plan | ROADMAP.md, plans/ → plans/archive/ (frozen) | archive immutable | hook + git floor |
|
|
40
|
+
|
|
41
|
+
Plus a **bash guard** on every harness: shell writes to governed files
|
|
42
|
+
(`>>`, `sed -i`, `tee`, ...) are denied — the editor tools are the audited
|
|
43
|
+
path. Automatic adapters stay silent outside a repository explicitly marked
|
|
44
|
+
by `.statutor.yaml`. A complete quoted heredoc used as `git commit -F -`
|
|
45
|
+
message data is not scanned as shell code; its opener and all actual command
|
|
46
|
+
lines remain guarded. No hand-maintained CHANGELOG.md: git log + conventional
|
|
47
|
+
commits.
|
|
48
|
+
|
|
49
|
+
State task identities are durable: an existing ID cannot disappear or be
|
|
50
|
+
renamed, while its checkbox, detail, and position may change. New IDs advance
|
|
51
|
+
beyond the committed maximum. v0.4 intentionally has no pruning operation;
|
|
52
|
+
completed entries remain until a separately specified identity-preserving
|
|
53
|
+
archive exists (D-0016).
|
|
54
|
+
|
|
55
|
+
## Kernel / adapter architecture
|
|
56
|
+
|
|
57
|
+
core/statutor_core.py single-file kernel: validate() + embedded templates
|
|
58
|
+
modes: hook | check | staged | init | trust approve
|
|
59
|
+
(fail-open hooks; staged floor fails closed)
|
|
60
|
+
core/statutor_doctor.py drift linter (stale stamps, budgets, unarchived plans)
|
|
61
|
+
core/statutor_global.py global-layer roots, schemas, hashes, CAS, backups
|
|
62
|
+
core/statutor_global_cli.py opt-in global instruction lifecycle and CLI
|
|
63
|
+
core/statutor_skills.py portable Agent Skill validation and projections
|
|
64
|
+
hooks/stop_doctor.py Claude Code Stop hook: runs statutor-doctor after each
|
|
65
|
+
turn and surfaces its WARN/ERROR lines as
|
|
66
|
+
additionalContext — non-blocking, silent when the
|
|
67
|
+
ledger is clean or the cwd isn't a statutor ledger at all
|
|
68
|
+
pyproject.toml pipx install → `statutor`, `statutor-doctor` on PATH
|
|
69
|
+
|
|
70
|
+
| Adapter | Mechanism | Coverage |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| 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 |
|
|
73
|
+
| OpenCode (`adapters/opencode/statutor.ts`) | `tool.execute.before` → `statutor check --if-ledger` | in-loop (write/edit/bash)¹ |
|
|
74
|
+
| Codex CLI (`adapters/codex/`) | PreToolUse (Claude-compatible protocol) → `statutor hook` | bash guard + apply_patch² |
|
|
75
|
+
| git (`adapters/git/`, `.pre-commit-hooks.yaml`) | `statutor staged` in local pre-commit and CI | staged-index backstop |
|
|
76
|
+
| native (`crates/statutor/`) | `statutor-staged`, conformance-gated ≡ Python | local staged floor without Python |
|
|
77
|
+
| Custom harnesses (`statutor check`, or import `validate`) | embed in your own tool dispatch | full in-loop |
|
|
78
|
+
|
|
79
|
+
¹ in-loop for write/edit/bash/apply_patch; the kernel parses apply_patch
|
|
80
|
+
envelopes (T-0011), with two partial-diff blind spots: required sections
|
|
81
|
+
on an Update File and server-namespaced MCP tool ids — the git floor
|
|
82
|
+
covers both. Subagent tool calls
|
|
83
|
+
DO fire plugin hooks (verified opencode v1.18.21, 2026-08-21); the
|
|
84
|
+
opposite claim (sst/opencode#5894) was a misdiagnosis, stale-closed
|
|
85
|
+
2026-04-15.
|
|
86
|
+
² Codex hooks are on by default since rust-v0.124.0 (2026-04-23) — the old
|
|
87
|
+
`[features].codex_hooks` flag is a deprecated legacy alias, and hooks need
|
|
88
|
+
a one-time trust approval (`/hooks`). Codex sends edits as tool_name
|
|
89
|
+
`apply_patch` + `{"command":
|
|
90
|
+
"<envelope>"}`, which `guard_apply_patch()` parses (frozen/delete/
|
|
91
|
+
append-only/cap checks; see adapters/codex/). Residual gaps: MCP tools
|
|
92
|
+
and Update-File section checks — so the git floor remains
|
|
93
|
+
mandatory there.
|
|
94
|
+
|
|
95
|
+
## Install
|
|
96
|
+
|
|
97
|
+
pipx install statutor # or: pip install -e .
|
|
98
|
+
statutor init . # scaffold any repo, any harness
|
|
99
|
+
|
|
100
|
+
# Claude Code (this repo doubles as the plugin):
|
|
101
|
+
/plugin marketplace add https://github.com/hoohugokim/statutor
|
|
102
|
+
/plugin install statutor@hoo-plugins --scope project
|
|
103
|
+
# then: /statutor-init /handoff /decide /statutor-doctor
|
|
104
|
+
|
|
105
|
+
# .pre-commit-config.yaml
|
|
106
|
+
repos:
|
|
107
|
+
- repo: https://github.com/hoohugokim/statutor
|
|
108
|
+
rev: v0.4.0
|
|
109
|
+
hooks: [{id: statutor}]
|
|
110
|
+
|
|
111
|
+
Per-repo policy lives in `.statutor.yaml`. In-loop checks use the committed
|
|
112
|
+
HEAD snapshot; the git floor judges the transaction under both HEAD and the
|
|
113
|
+
candidate index snapshot, so an unstaged or co-staged weakening cannot disable
|
|
114
|
+
existing rules. The format is a strict, zero-dependency YAML subset shared by
|
|
115
|
+
Python and Rust; malformed or unsupported committed/candidate policy denies.
|
|
116
|
+
Before the marker's first commit, automatic hooks use embedded defaults. An
|
|
117
|
+
edited worktree policy does not change hook behavior until it passes trust
|
|
118
|
+
approval and becomes HEAD in a separate commit.
|
|
119
|
+
|
|
120
|
+
After bootstrap, changing `.statutor.yaml` or Statutor's exact
|
|
121
|
+
`CLAUDE.md` → `@AGENTS.md` bridge requires an exact-tree Git-local receipt:
|
|
122
|
+
|
|
123
|
+
git add .statutor.yaml
|
|
124
|
+
statutor trust approve . --decision D-0015 --reason "why this changes trust"
|
|
125
|
+
statutor staged .
|
|
126
|
+
|
|
127
|
+
Approval displays the reserved diff and all staged paths, then requires the
|
|
128
|
+
complete candidate tree ID. The mode-0600 receipt expires on any HEAD or index
|
|
129
|
+
change and is never committed.
|
|
130
|
+
|
|
131
|
+
## Release gate
|
|
132
|
+
|
|
133
|
+
With Python `build`/`pytest`, Cargo, and npm available, stage the candidate and
|
|
134
|
+
run `python scripts/release_gate.py`. The gate tests the exact Git index, audits
|
|
135
|
+
all package payloads, builds the Python sdist and wheel in scratch space,
|
|
136
|
+
installs the wheel into an isolated target, and smoke-tests both console
|
|
137
|
+
scripts. A tagged release additionally passes `--tag vX.Y.Z --dist-dir dist`;
|
|
138
|
+
verified artifacts are copied only after every check succeeds.
|
|
139
|
+
|
|
140
|
+
The v0.4 global layer also has a separate, opt-in current-host probe:
|
|
141
|
+
`python scripts/global_e2e.py --json`. It pins the tested Claude, Codex, and
|
|
142
|
+
OpenCode versions, rehomes every host under a temporary profile, performs no
|
|
143
|
+
model or network request, and proves native discovery where an offline surface
|
|
144
|
+
exists, modified-target refusal, and exact uninstall recovery. It is not part
|
|
145
|
+
of the hermetic release gate and never mutates the caller's real home.
|
|
146
|
+
|
|
147
|
+
## Portable user layer (v0.4)
|
|
148
|
+
|
|
149
|
+
The global layer is opt-in and separate from project ledgers. `init` creates
|
|
150
|
+
only human-owned canonical sources and versioned state; it does not change any
|
|
151
|
+
host file. `plan`, `status`, and `--json` are read-only. `apply` owns only
|
|
152
|
+
whole generated files it created, while `adopt HOST` backs up an existing
|
|
153
|
+
regular file and imports its bytes verbatim into that host's canonical overlay.
|
|
154
|
+
|
|
155
|
+
statutor global init
|
|
156
|
+
statutor global plan --json
|
|
157
|
+
statutor global apply --host codex
|
|
158
|
+
statutor global status --json
|
|
159
|
+
statutor global doctor --json
|
|
160
|
+
statutor global adopt claude
|
|
161
|
+
statutor global uninstall --host codex
|
|
162
|
+
|
|
163
|
+
statutor global skill import ./my-skill
|
|
164
|
+
statutor global skill plan --json
|
|
165
|
+
statutor global skill apply
|
|
166
|
+
statutor global skill status --json
|
|
167
|
+
statutor global skill sync
|
|
168
|
+
statutor global skill uninstall
|
|
169
|
+
|
|
170
|
+
Every mutation prints its resolved plan first. Existing unmanaged targets are
|
|
171
|
+
conflicts, managed hand edits are never overwritten or removed, and uninstall
|
|
172
|
+
restores the original backup. `--home`, `--config-root`, and `--state-root`
|
|
173
|
+
provide explicit isolated roots for tests and alternate profiles. Codex
|
|
174
|
+
overrides and OpenCode's Claude fallback are reported as precedence warnings;
|
|
175
|
+
Statutor never rewrites them.
|
|
176
|
+
|
|
177
|
+
Imported skills are copied to a new human-owned canonical source, then
|
|
178
|
+
projected as complete trees to `$HOME/.agents/skills` and Claude's personal
|
|
179
|
+
skills root. Core `SKILL.md` metadata and the complete tree are validated;
|
|
180
|
+
host-specific frontmatter is preserved. Identical unmanaged trees require
|
|
181
|
+
explicit `--adopt-identical`, differing or hand-edited trees are conflicts,
|
|
182
|
+
and names in a foreign `.agents/.skill-lock.json` remain entirely foreign.
|
|
183
|
+
OpenCode discovers the portable and Claude projections, so Statutor reports
|
|
184
|
+
whether those duplicates are identical instead of creating a third native
|
|
185
|
+
OpenCode copy.
|
|
186
|
+
|
|
187
|
+
Unified `global status` is a fast, read-only inventory of effective instruction
|
|
188
|
+
precedence, ownership, disabled/foreign skills, duplicate names, legacy/native/
|
|
189
|
+
admin/plugin roots, and catalog size. `global doctor` performs the deeper full-
|
|
190
|
+
tree audit: managed drift and receipt topology, generated headers, unsafe links,
|
|
191
|
+
active divergent duplicates, Codex's configured instruction cap, and Statutor's
|
|
192
|
+
32 KiB skill-description diagnostic budget (reported explicitly as a Statutor
|
|
193
|
+
budget, not a host limit). Status exits zero when inventory completes; doctor
|
|
194
|
+
exits one when the audit contains errors. Neither command invokes a host binary
|
|
195
|
+
or performs a network request.
|
|
196
|
+
|
|
197
|
+
## Provenance
|
|
198
|
+
|
|
199
|
+
Framework doctrine distilled from: the AGENTS.md open standard
|
|
200
|
+
<https://agents.md/>, Claude Code memory & hooks docs
|
|
201
|
+
<https://code.claude.com/docs/en/memory>, <https://code.claude.com/docs/en/hooks>,
|
|
202
|
+
Anthropic on long-running agent harnesses
|
|
203
|
+
<https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents>
|
|
204
|
+
and context engineering
|
|
205
|
+
<https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents>,
|
|
206
|
+
MADR <https://adr.github.io/madr/>, Keep a Changelog
|
|
207
|
+
<https://keepachangelog.com/>, Conventional Commits
|
|
208
|
+
<https://www.conventionalcommits.org/>, pre-commit <https://pre-commit.com/>.
|
statutor-0.4.0/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# statutor
|
|
2
|
+
|
|
3
|
+
Typed project-ledger framework for agentic repos. A *statutor* is one who
|
|
4
|
+
enacts (agent noun of *statuere*) — which is the thesis: repo instruction
|
|
5
|
+
files are a state machine of typed registers, each with a mutation policy
|
|
6
|
+
and exactly one writer, enforced by hooks and git, not by prose.
|
|
7
|
+
|
|
8
|
+
| Plane | Files | Policy | Enforced by |
|
|
9
|
+
|---|---|---|---|
|
|
10
|
+
| Constitution | AGENTS.md (+ CLAUDE.md = `@AGENTS.md`) | hard cap 200 lines | hook + git floor |
|
|
11
|
+
| State | HANDOFF.md | overwrite-only, ≤ 40 lines, required sections | hook + git floor |
|
|
12
|
+
| State | TASKS.md | stable T-NNNN ids; mutable checkbox/detail/order | hook + git floor + doctor |
|
|
13
|
+
| Log | DECISIONS.md | append-only, insertions only, supersede-never-edit | hook + git floor |
|
|
14
|
+
| Plan | ROADMAP.md, plans/ → plans/archive/ (frozen) | archive immutable | hook + git floor |
|
|
15
|
+
|
|
16
|
+
Plus a **bash guard** on every harness: shell writes to governed files
|
|
17
|
+
(`>>`, `sed -i`, `tee`, ...) are denied — the editor tools are the audited
|
|
18
|
+
path. Automatic adapters stay silent outside a repository explicitly marked
|
|
19
|
+
by `.statutor.yaml`. A complete quoted heredoc used as `git commit -F -`
|
|
20
|
+
message data is not scanned as shell code; its opener and all actual command
|
|
21
|
+
lines remain guarded. No hand-maintained CHANGELOG.md: git log + conventional
|
|
22
|
+
commits.
|
|
23
|
+
|
|
24
|
+
State task identities are durable: an existing ID cannot disappear or be
|
|
25
|
+
renamed, while its checkbox, detail, and position may change. New IDs advance
|
|
26
|
+
beyond the committed maximum. v0.4 intentionally has no pruning operation;
|
|
27
|
+
completed entries remain until a separately specified identity-preserving
|
|
28
|
+
archive exists (D-0016).
|
|
29
|
+
|
|
30
|
+
## Kernel / adapter architecture
|
|
31
|
+
|
|
32
|
+
core/statutor_core.py single-file kernel: validate() + embedded templates
|
|
33
|
+
modes: hook | check | staged | init | trust approve
|
|
34
|
+
(fail-open hooks; staged floor fails closed)
|
|
35
|
+
core/statutor_doctor.py drift linter (stale stamps, budgets, unarchived plans)
|
|
36
|
+
core/statutor_global.py global-layer roots, schemas, hashes, CAS, backups
|
|
37
|
+
core/statutor_global_cli.py opt-in global instruction lifecycle and CLI
|
|
38
|
+
core/statutor_skills.py portable Agent Skill validation and projections
|
|
39
|
+
hooks/stop_doctor.py Claude Code Stop hook: runs statutor-doctor after each
|
|
40
|
+
turn and surfaces its WARN/ERROR lines as
|
|
41
|
+
additionalContext — non-blocking, silent when the
|
|
42
|
+
ledger is clean or the cwd isn't a statutor ledger at all
|
|
43
|
+
pyproject.toml pipx install → `statutor`, `statutor-doctor` on PATH
|
|
44
|
+
|
|
45
|
+
| Adapter | Mechanism | Coverage |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| 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 |
|
|
48
|
+
| OpenCode (`adapters/opencode/statutor.ts`) | `tool.execute.before` → `statutor check --if-ledger` | in-loop (write/edit/bash)¹ |
|
|
49
|
+
| Codex CLI (`adapters/codex/`) | PreToolUse (Claude-compatible protocol) → `statutor hook` | bash guard + apply_patch² |
|
|
50
|
+
| git (`adapters/git/`, `.pre-commit-hooks.yaml`) | `statutor staged` in local pre-commit and CI | staged-index backstop |
|
|
51
|
+
| native (`crates/statutor/`) | `statutor-staged`, conformance-gated ≡ Python | local staged floor without Python |
|
|
52
|
+
| Custom harnesses (`statutor check`, or import `validate`) | embed in your own tool dispatch | full in-loop |
|
|
53
|
+
|
|
54
|
+
¹ in-loop for write/edit/bash/apply_patch; the kernel parses apply_patch
|
|
55
|
+
envelopes (T-0011), with two partial-diff blind spots: required sections
|
|
56
|
+
on an Update File and server-namespaced MCP tool ids — the git floor
|
|
57
|
+
covers both. Subagent tool calls
|
|
58
|
+
DO fire plugin hooks (verified opencode v1.18.21, 2026-08-21); the
|
|
59
|
+
opposite claim (sst/opencode#5894) was a misdiagnosis, stale-closed
|
|
60
|
+
2026-04-15.
|
|
61
|
+
² Codex hooks are on by default since rust-v0.124.0 (2026-04-23) — the old
|
|
62
|
+
`[features].codex_hooks` flag is a deprecated legacy alias, and hooks need
|
|
63
|
+
a one-time trust approval (`/hooks`). Codex sends edits as tool_name
|
|
64
|
+
`apply_patch` + `{"command":
|
|
65
|
+
"<envelope>"}`, which `guard_apply_patch()` parses (frozen/delete/
|
|
66
|
+
append-only/cap checks; see adapters/codex/). Residual gaps: MCP tools
|
|
67
|
+
and Update-File section checks — so the git floor remains
|
|
68
|
+
mandatory there.
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
pipx install statutor # or: pip install -e .
|
|
73
|
+
statutor init . # scaffold any repo, any harness
|
|
74
|
+
|
|
75
|
+
# Claude Code (this repo doubles as the plugin):
|
|
76
|
+
/plugin marketplace add https://github.com/hoohugokim/statutor
|
|
77
|
+
/plugin install statutor@hoo-plugins --scope project
|
|
78
|
+
# then: /statutor-init /handoff /decide /statutor-doctor
|
|
79
|
+
|
|
80
|
+
# .pre-commit-config.yaml
|
|
81
|
+
repos:
|
|
82
|
+
- repo: https://github.com/hoohugokim/statutor
|
|
83
|
+
rev: v0.4.0
|
|
84
|
+
hooks: [{id: statutor}]
|
|
85
|
+
|
|
86
|
+
Per-repo policy lives in `.statutor.yaml`. In-loop checks use the committed
|
|
87
|
+
HEAD snapshot; the git floor judges the transaction under both HEAD and the
|
|
88
|
+
candidate index snapshot, so an unstaged or co-staged weakening cannot disable
|
|
89
|
+
existing rules. The format is a strict, zero-dependency YAML subset shared by
|
|
90
|
+
Python and Rust; malformed or unsupported committed/candidate policy denies.
|
|
91
|
+
Before the marker's first commit, automatic hooks use embedded defaults. An
|
|
92
|
+
edited worktree policy does not change hook behavior until it passes trust
|
|
93
|
+
approval and becomes HEAD in a separate commit.
|
|
94
|
+
|
|
95
|
+
After bootstrap, changing `.statutor.yaml` or Statutor's exact
|
|
96
|
+
`CLAUDE.md` → `@AGENTS.md` bridge requires an exact-tree Git-local receipt:
|
|
97
|
+
|
|
98
|
+
git add .statutor.yaml
|
|
99
|
+
statutor trust approve . --decision D-0015 --reason "why this changes trust"
|
|
100
|
+
statutor staged .
|
|
101
|
+
|
|
102
|
+
Approval displays the reserved diff and all staged paths, then requires the
|
|
103
|
+
complete candidate tree ID. The mode-0600 receipt expires on any HEAD or index
|
|
104
|
+
change and is never committed.
|
|
105
|
+
|
|
106
|
+
## Release gate
|
|
107
|
+
|
|
108
|
+
With Python `build`/`pytest`, Cargo, and npm available, stage the candidate and
|
|
109
|
+
run `python scripts/release_gate.py`. The gate tests the exact Git index, audits
|
|
110
|
+
all package payloads, builds the Python sdist and wheel in scratch space,
|
|
111
|
+
installs the wheel into an isolated target, and smoke-tests both console
|
|
112
|
+
scripts. A tagged release additionally passes `--tag vX.Y.Z --dist-dir dist`;
|
|
113
|
+
verified artifacts are copied only after every check succeeds.
|
|
114
|
+
|
|
115
|
+
The v0.4 global layer also has a separate, opt-in current-host probe:
|
|
116
|
+
`python scripts/global_e2e.py --json`. It pins the tested Claude, Codex, and
|
|
117
|
+
OpenCode versions, rehomes every host under a temporary profile, performs no
|
|
118
|
+
model or network request, and proves native discovery where an offline surface
|
|
119
|
+
exists, modified-target refusal, and exact uninstall recovery. It is not part
|
|
120
|
+
of the hermetic release gate and never mutates the caller's real home.
|
|
121
|
+
|
|
122
|
+
## Portable user layer (v0.4)
|
|
123
|
+
|
|
124
|
+
The global layer is opt-in and separate from project ledgers. `init` creates
|
|
125
|
+
only human-owned canonical sources and versioned state; it does not change any
|
|
126
|
+
host file. `plan`, `status`, and `--json` are read-only. `apply` owns only
|
|
127
|
+
whole generated files it created, while `adopt HOST` backs up an existing
|
|
128
|
+
regular file and imports its bytes verbatim into that host's canonical overlay.
|
|
129
|
+
|
|
130
|
+
statutor global init
|
|
131
|
+
statutor global plan --json
|
|
132
|
+
statutor global apply --host codex
|
|
133
|
+
statutor global status --json
|
|
134
|
+
statutor global doctor --json
|
|
135
|
+
statutor global adopt claude
|
|
136
|
+
statutor global uninstall --host codex
|
|
137
|
+
|
|
138
|
+
statutor global skill import ./my-skill
|
|
139
|
+
statutor global skill plan --json
|
|
140
|
+
statutor global skill apply
|
|
141
|
+
statutor global skill status --json
|
|
142
|
+
statutor global skill sync
|
|
143
|
+
statutor global skill uninstall
|
|
144
|
+
|
|
145
|
+
Every mutation prints its resolved plan first. Existing unmanaged targets are
|
|
146
|
+
conflicts, managed hand edits are never overwritten or removed, and uninstall
|
|
147
|
+
restores the original backup. `--home`, `--config-root`, and `--state-root`
|
|
148
|
+
provide explicit isolated roots for tests and alternate profiles. Codex
|
|
149
|
+
overrides and OpenCode's Claude fallback are reported as precedence warnings;
|
|
150
|
+
Statutor never rewrites them.
|
|
151
|
+
|
|
152
|
+
Imported skills are copied to a new human-owned canonical source, then
|
|
153
|
+
projected as complete trees to `$HOME/.agents/skills` and Claude's personal
|
|
154
|
+
skills root. Core `SKILL.md` metadata and the complete tree are validated;
|
|
155
|
+
host-specific frontmatter is preserved. Identical unmanaged trees require
|
|
156
|
+
explicit `--adopt-identical`, differing or hand-edited trees are conflicts,
|
|
157
|
+
and names in a foreign `.agents/.skill-lock.json` remain entirely foreign.
|
|
158
|
+
OpenCode discovers the portable and Claude projections, so Statutor reports
|
|
159
|
+
whether those duplicates are identical instead of creating a third native
|
|
160
|
+
OpenCode copy.
|
|
161
|
+
|
|
162
|
+
Unified `global status` is a fast, read-only inventory of effective instruction
|
|
163
|
+
precedence, ownership, disabled/foreign skills, duplicate names, legacy/native/
|
|
164
|
+
admin/plugin roots, and catalog size. `global doctor` performs the deeper full-
|
|
165
|
+
tree audit: managed drift and receipt topology, generated headers, unsafe links,
|
|
166
|
+
active divergent duplicates, Codex's configured instruction cap, and Statutor's
|
|
167
|
+
32 KiB skill-description diagnostic budget (reported explicitly as a Statutor
|
|
168
|
+
budget, not a host limit). Status exits zero when inventory completes; doctor
|
|
169
|
+
exits one when the audit contains errors. Neither command invokes a host binary
|
|
170
|
+
or performs a network request.
|
|
171
|
+
|
|
172
|
+
## Provenance
|
|
173
|
+
|
|
174
|
+
Framework doctrine distilled from: the AGENTS.md open standard
|
|
175
|
+
<https://agents.md/>, Claude Code memory & hooks docs
|
|
176
|
+
<https://code.claude.com/docs/en/memory>, <https://code.claude.com/docs/en/hooks>,
|
|
177
|
+
Anthropic on long-running agent harnesses
|
|
178
|
+
<https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents>
|
|
179
|
+
and context engineering
|
|
180
|
+
<https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents>,
|
|
181
|
+
MADR <https://adr.github.io/madr/>, Keep a Changelog
|
|
182
|
+
<https://keepachangelog.com/>, Conventional Commits
|
|
183
|
+
<https://www.conventionalcommits.org/>, pre-commit <https://pre-commit.com/>.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: statutor
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Typed project-ledger framework for agentic repos: four planes, single-writer files, enforced mutation policies.
|
|
5
|
+
Author: Hoo
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/hoohugokim/statutor
|
|
8
|
+
Project-URL: Repository, https://github.com/hoohugokim/statutor
|
|
9
|
+
Project-URL: Issues, https://github.com/hoohugokim/statutor/issues
|
|
10
|
+
Keywords: agents,claude,opencode,codex,ledger,governance,hooks,pre-commit,adr,decision-records
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# statutor
|
|
27
|
+
|
|
28
|
+
Typed project-ledger framework for agentic repos. A *statutor* is one who
|
|
29
|
+
enacts (agent noun of *statuere*) — which is the thesis: repo instruction
|
|
30
|
+
files are a state machine of typed registers, each with a mutation policy
|
|
31
|
+
and exactly one writer, enforced by hooks and git, not by prose.
|
|
32
|
+
|
|
33
|
+
| Plane | Files | Policy | Enforced by |
|
|
34
|
+
|---|---|---|---|
|
|
35
|
+
| Constitution | AGENTS.md (+ CLAUDE.md = `@AGENTS.md`) | hard cap 200 lines | hook + git floor |
|
|
36
|
+
| State | HANDOFF.md | overwrite-only, ≤ 40 lines, required sections | hook + git floor |
|
|
37
|
+
| State | TASKS.md | stable T-NNNN ids; mutable checkbox/detail/order | hook + git floor + doctor |
|
|
38
|
+
| Log | DECISIONS.md | append-only, insertions only, supersede-never-edit | hook + git floor |
|
|
39
|
+
| Plan | ROADMAP.md, plans/ → plans/archive/ (frozen) | archive immutable | hook + git floor |
|
|
40
|
+
|
|
41
|
+
Plus a **bash guard** on every harness: shell writes to governed files
|
|
42
|
+
(`>>`, `sed -i`, `tee`, ...) are denied — the editor tools are the audited
|
|
43
|
+
path. Automatic adapters stay silent outside a repository explicitly marked
|
|
44
|
+
by `.statutor.yaml`. A complete quoted heredoc used as `git commit -F -`
|
|
45
|
+
message data is not scanned as shell code; its opener and all actual command
|
|
46
|
+
lines remain guarded. No hand-maintained CHANGELOG.md: git log + conventional
|
|
47
|
+
commits.
|
|
48
|
+
|
|
49
|
+
State task identities are durable: an existing ID cannot disappear or be
|
|
50
|
+
renamed, while its checkbox, detail, and position may change. New IDs advance
|
|
51
|
+
beyond the committed maximum. v0.4 intentionally has no pruning operation;
|
|
52
|
+
completed entries remain until a separately specified identity-preserving
|
|
53
|
+
archive exists (D-0016).
|
|
54
|
+
|
|
55
|
+
## Kernel / adapter architecture
|
|
56
|
+
|
|
57
|
+
core/statutor_core.py single-file kernel: validate() + embedded templates
|
|
58
|
+
modes: hook | check | staged | init | trust approve
|
|
59
|
+
(fail-open hooks; staged floor fails closed)
|
|
60
|
+
core/statutor_doctor.py drift linter (stale stamps, budgets, unarchived plans)
|
|
61
|
+
core/statutor_global.py global-layer roots, schemas, hashes, CAS, backups
|
|
62
|
+
core/statutor_global_cli.py opt-in global instruction lifecycle and CLI
|
|
63
|
+
core/statutor_skills.py portable Agent Skill validation and projections
|
|
64
|
+
hooks/stop_doctor.py Claude Code Stop hook: runs statutor-doctor after each
|
|
65
|
+
turn and surfaces its WARN/ERROR lines as
|
|
66
|
+
additionalContext — non-blocking, silent when the
|
|
67
|
+
ledger is clean or the cwd isn't a statutor ledger at all
|
|
68
|
+
pyproject.toml pipx install → `statutor`, `statutor-doctor` on PATH
|
|
69
|
+
|
|
70
|
+
| Adapter | Mechanism | Coverage |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| 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 |
|
|
73
|
+
| OpenCode (`adapters/opencode/statutor.ts`) | `tool.execute.before` → `statutor check --if-ledger` | in-loop (write/edit/bash)¹ |
|
|
74
|
+
| Codex CLI (`adapters/codex/`) | PreToolUse (Claude-compatible protocol) → `statutor hook` | bash guard + apply_patch² |
|
|
75
|
+
| git (`adapters/git/`, `.pre-commit-hooks.yaml`) | `statutor staged` in local pre-commit and CI | staged-index backstop |
|
|
76
|
+
| native (`crates/statutor/`) | `statutor-staged`, conformance-gated ≡ Python | local staged floor without Python |
|
|
77
|
+
| Custom harnesses (`statutor check`, or import `validate`) | embed in your own tool dispatch | full in-loop |
|
|
78
|
+
|
|
79
|
+
¹ in-loop for write/edit/bash/apply_patch; the kernel parses apply_patch
|
|
80
|
+
envelopes (T-0011), with two partial-diff blind spots: required sections
|
|
81
|
+
on an Update File and server-namespaced MCP tool ids — the git floor
|
|
82
|
+
covers both. Subagent tool calls
|
|
83
|
+
DO fire plugin hooks (verified opencode v1.18.21, 2026-08-21); the
|
|
84
|
+
opposite claim (sst/opencode#5894) was a misdiagnosis, stale-closed
|
|
85
|
+
2026-04-15.
|
|
86
|
+
² Codex hooks are on by default since rust-v0.124.0 (2026-04-23) — the old
|
|
87
|
+
`[features].codex_hooks` flag is a deprecated legacy alias, and hooks need
|
|
88
|
+
a one-time trust approval (`/hooks`). Codex sends edits as tool_name
|
|
89
|
+
`apply_patch` + `{"command":
|
|
90
|
+
"<envelope>"}`, which `guard_apply_patch()` parses (frozen/delete/
|
|
91
|
+
append-only/cap checks; see adapters/codex/). Residual gaps: MCP tools
|
|
92
|
+
and Update-File section checks — so the git floor remains
|
|
93
|
+
mandatory there.
|
|
94
|
+
|
|
95
|
+
## Install
|
|
96
|
+
|
|
97
|
+
pipx install statutor # or: pip install -e .
|
|
98
|
+
statutor init . # scaffold any repo, any harness
|
|
99
|
+
|
|
100
|
+
# Claude Code (this repo doubles as the plugin):
|
|
101
|
+
/plugin marketplace add https://github.com/hoohugokim/statutor
|
|
102
|
+
/plugin install statutor@hoo-plugins --scope project
|
|
103
|
+
# then: /statutor-init /handoff /decide /statutor-doctor
|
|
104
|
+
|
|
105
|
+
# .pre-commit-config.yaml
|
|
106
|
+
repos:
|
|
107
|
+
- repo: https://github.com/hoohugokim/statutor
|
|
108
|
+
rev: v0.4.0
|
|
109
|
+
hooks: [{id: statutor}]
|
|
110
|
+
|
|
111
|
+
Per-repo policy lives in `.statutor.yaml`. In-loop checks use the committed
|
|
112
|
+
HEAD snapshot; the git floor judges the transaction under both HEAD and the
|
|
113
|
+
candidate index snapshot, so an unstaged or co-staged weakening cannot disable
|
|
114
|
+
existing rules. The format is a strict, zero-dependency YAML subset shared by
|
|
115
|
+
Python and Rust; malformed or unsupported committed/candidate policy denies.
|
|
116
|
+
Before the marker's first commit, automatic hooks use embedded defaults. An
|
|
117
|
+
edited worktree policy does not change hook behavior until it passes trust
|
|
118
|
+
approval and becomes HEAD in a separate commit.
|
|
119
|
+
|
|
120
|
+
After bootstrap, changing `.statutor.yaml` or Statutor's exact
|
|
121
|
+
`CLAUDE.md` → `@AGENTS.md` bridge requires an exact-tree Git-local receipt:
|
|
122
|
+
|
|
123
|
+
git add .statutor.yaml
|
|
124
|
+
statutor trust approve . --decision D-0015 --reason "why this changes trust"
|
|
125
|
+
statutor staged .
|
|
126
|
+
|
|
127
|
+
Approval displays the reserved diff and all staged paths, then requires the
|
|
128
|
+
complete candidate tree ID. The mode-0600 receipt expires on any HEAD or index
|
|
129
|
+
change and is never committed.
|
|
130
|
+
|
|
131
|
+
## Release gate
|
|
132
|
+
|
|
133
|
+
With Python `build`/`pytest`, Cargo, and npm available, stage the candidate and
|
|
134
|
+
run `python scripts/release_gate.py`. The gate tests the exact Git index, audits
|
|
135
|
+
all package payloads, builds the Python sdist and wheel in scratch space,
|
|
136
|
+
installs the wheel into an isolated target, and smoke-tests both console
|
|
137
|
+
scripts. A tagged release additionally passes `--tag vX.Y.Z --dist-dir dist`;
|
|
138
|
+
verified artifacts are copied only after every check succeeds.
|
|
139
|
+
|
|
140
|
+
The v0.4 global layer also has a separate, opt-in current-host probe:
|
|
141
|
+
`python scripts/global_e2e.py --json`. It pins the tested Claude, Codex, and
|
|
142
|
+
OpenCode versions, rehomes every host under a temporary profile, performs no
|
|
143
|
+
model or network request, and proves native discovery where an offline surface
|
|
144
|
+
exists, modified-target refusal, and exact uninstall recovery. It is not part
|
|
145
|
+
of the hermetic release gate and never mutates the caller's real home.
|
|
146
|
+
|
|
147
|
+
## Portable user layer (v0.4)
|
|
148
|
+
|
|
149
|
+
The global layer is opt-in and separate from project ledgers. `init` creates
|
|
150
|
+
only human-owned canonical sources and versioned state; it does not change any
|
|
151
|
+
host file. `plan`, `status`, and `--json` are read-only. `apply` owns only
|
|
152
|
+
whole generated files it created, while `adopt HOST` backs up an existing
|
|
153
|
+
regular file and imports its bytes verbatim into that host's canonical overlay.
|
|
154
|
+
|
|
155
|
+
statutor global init
|
|
156
|
+
statutor global plan --json
|
|
157
|
+
statutor global apply --host codex
|
|
158
|
+
statutor global status --json
|
|
159
|
+
statutor global doctor --json
|
|
160
|
+
statutor global adopt claude
|
|
161
|
+
statutor global uninstall --host codex
|
|
162
|
+
|
|
163
|
+
statutor global skill import ./my-skill
|
|
164
|
+
statutor global skill plan --json
|
|
165
|
+
statutor global skill apply
|
|
166
|
+
statutor global skill status --json
|
|
167
|
+
statutor global skill sync
|
|
168
|
+
statutor global skill uninstall
|
|
169
|
+
|
|
170
|
+
Every mutation prints its resolved plan first. Existing unmanaged targets are
|
|
171
|
+
conflicts, managed hand edits are never overwritten or removed, and uninstall
|
|
172
|
+
restores the original backup. `--home`, `--config-root`, and `--state-root`
|
|
173
|
+
provide explicit isolated roots for tests and alternate profiles. Codex
|
|
174
|
+
overrides and OpenCode's Claude fallback are reported as precedence warnings;
|
|
175
|
+
Statutor never rewrites them.
|
|
176
|
+
|
|
177
|
+
Imported skills are copied to a new human-owned canonical source, then
|
|
178
|
+
projected as complete trees to `$HOME/.agents/skills` and Claude's personal
|
|
179
|
+
skills root. Core `SKILL.md` metadata and the complete tree are validated;
|
|
180
|
+
host-specific frontmatter is preserved. Identical unmanaged trees require
|
|
181
|
+
explicit `--adopt-identical`, differing or hand-edited trees are conflicts,
|
|
182
|
+
and names in a foreign `.agents/.skill-lock.json` remain entirely foreign.
|
|
183
|
+
OpenCode discovers the portable and Claude projections, so Statutor reports
|
|
184
|
+
whether those duplicates are identical instead of creating a third native
|
|
185
|
+
OpenCode copy.
|
|
186
|
+
|
|
187
|
+
Unified `global status` is a fast, read-only inventory of effective instruction
|
|
188
|
+
precedence, ownership, disabled/foreign skills, duplicate names, legacy/native/
|
|
189
|
+
admin/plugin roots, and catalog size. `global doctor` performs the deeper full-
|
|
190
|
+
tree audit: managed drift and receipt topology, generated headers, unsafe links,
|
|
191
|
+
active divergent duplicates, Codex's configured instruction cap, and Statutor's
|
|
192
|
+
32 KiB skill-description diagnostic budget (reported explicitly as a Statutor
|
|
193
|
+
budget, not a host limit). Status exits zero when inventory completes; doctor
|
|
194
|
+
exits one when the audit contains errors. Neither command invokes a host binary
|
|
195
|
+
or performs a network request.
|
|
196
|
+
|
|
197
|
+
## Provenance
|
|
198
|
+
|
|
199
|
+
Framework doctrine distilled from: the AGENTS.md open standard
|
|
200
|
+
<https://agents.md/>, Claude Code memory & hooks docs
|
|
201
|
+
<https://code.claude.com/docs/en/memory>, <https://code.claude.com/docs/en/hooks>,
|
|
202
|
+
Anthropic on long-running agent harnesses
|
|
203
|
+
<https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents>
|
|
204
|
+
and context engineering
|
|
205
|
+
<https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents>,
|
|
206
|
+
MADR <https://adr.github.io/madr/>, Keep a Changelog
|
|
207
|
+
<https://keepachangelog.com/>, Conventional Commits
|
|
208
|
+
<https://www.conventionalcommits.org/>, pre-commit <https://pre-commit.com/>.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
2
3
|
README.md
|
|
3
4
|
pyproject.toml
|
|
4
5
|
core/statutor_core.py
|
|
5
6
|
core/statutor_doctor.py
|
|
7
|
+
core/statutor_global.py
|
|
8
|
+
core/statutor_global_cli.py
|
|
9
|
+
core/statutor_global_status.py
|
|
10
|
+
core/statutor_skills.py
|
|
6
11
|
core/statutor.egg-info/PKG-INFO
|
|
7
12
|
core/statutor.egg-info/SOURCES.txt
|
|
8
13
|
core/statutor.egg-info/dependency_links.txt
|
|
9
14
|
core/statutor.egg-info/entry_points.txt
|
|
10
|
-
core/statutor.egg-info/top_level.txt
|
|
11
|
-
tests/test_conformance_rust.py
|
|
12
|
-
tests/test_doctor.py
|
|
13
|
-
tests/test_kernel.py
|
|
14
|
-
tests/test_pre_commit.py
|
|
15
|
-
tests/test_stop_hook.py
|
|
15
|
+
core/statutor.egg-info/top_level.txt
|