statutor 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hoo (github.com/hoohugokim)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -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]
@@ -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/>.
@@ -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,12 +1,15 @@
1
+ LICENSE
2
+ MANIFEST.in
1
3
  README.md
2
4
  pyproject.toml
3
5
  core/statutor_core.py
4
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
5
11
  core/statutor.egg-info/PKG-INFO
6
12
  core/statutor.egg-info/SOURCES.txt
7
13
  core/statutor.egg-info/dependency_links.txt
8
14
  core/statutor.egg-info/entry_points.txt
9
- core/statutor.egg-info/top_level.txt
10
- tests/test_doctor.py
11
- tests/test_kernel.py
12
- tests/test_stop_hook.py
15
+ core/statutor.egg-info/top_level.txt
@@ -0,0 +1,6 @@
1
+ statutor_core
2
+ statutor_doctor
3
+ statutor_global
4
+ statutor_global_cli
5
+ statutor_global_status
6
+ statutor_skills