statutor 0.3.0__tar.gz → 0.5.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.
@@ -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,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: statutor
3
+ Version: 0.5.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
+ Worker provenance (v0.5, `statutor machine`/`statutor worker`): automatic
96
+ hooks on every host prove an **attempt** only and record nothing by
97
+ themselves — the hook path stays fail-open. Ingress is executor- or
98
+ custom-run (`begin` → `record --event attempt` → attributed rewrite →
99
+ `complete --session`); `--event mutation` under an automatic harness is
100
+ denied. `worker capabilities` reports proven surfaces and gaps per host
101
+ (verified against Claude Code 2.1.258, Codex CLI 0.152.1, OpenCode 1.18.20
102
+ baselines); `worker compare <ref>` explains sibling handoffs read-only.
103
+
104
+ ## Install
105
+
106
+ pipx install statutor # or: pip install -e .
107
+ statutor init . # scaffold any repo, any harness
108
+
109
+ # Claude Code (this repo doubles as the plugin):
110
+ /plugin marketplace add https://github.com/hoohugokim/statutor
111
+ /plugin install statutor@hoo-plugins --scope project
112
+ # then: /statutor-init /handoff /decide /statutor-doctor
113
+
114
+ # .pre-commit-config.yaml
115
+ repos:
116
+ - repo: https://github.com/hoohugokim/statutor
117
+ rev: v0.5.0
118
+ hooks: [{id: statutor}]
119
+
120
+ Per-repo policy lives in `.statutor.yaml`. In-loop checks use the committed
121
+ HEAD snapshot; the git floor judges the transaction under both HEAD and the
122
+ candidate index snapshot, so an unstaged or co-staged weakening cannot disable
123
+ existing rules. The format is a strict, zero-dependency YAML subset shared by
124
+ Python and Rust; malformed or unsupported committed/candidate policy denies.
125
+ Before the marker's first commit, automatic hooks use embedded defaults. An
126
+ edited worktree policy does not change hook behavior until it passes trust
127
+ approval and becomes HEAD in a separate commit.
128
+
129
+ After bootstrap, changing `.statutor.yaml` or Statutor's exact
130
+ `CLAUDE.md` → `@AGENTS.md` bridge requires an exact-tree Git-local receipt:
131
+
132
+ git add .statutor.yaml
133
+ statutor trust approve . --decision D-0015 --reason "why this changes trust"
134
+ statutor staged .
135
+
136
+ Approval displays the reserved diff and all staged paths, then requires the
137
+ complete candidate tree ID. The mode-0600 receipt expires on any HEAD or index
138
+ change and is never committed.
139
+
140
+ ## Release gate
141
+
142
+ With Python `build`/`pytest`, Cargo, and npm available, stage the candidate and
143
+ run `python scripts/release_gate.py`. The gate tests the exact Git index, audits
144
+ all package payloads, builds the Python sdist and wheel in scratch space,
145
+ installs the wheel into an isolated target, and smoke-tests both console
146
+ scripts. A tagged release additionally passes `--tag vX.Y.Z --dist-dir dist`;
147
+ verified artifacts are copied only after every check succeeds.
148
+
149
+ The v0.4 global layer also has a separate, opt-in current-host probe:
150
+ `python scripts/global_e2e.py --json`. It pins the tested Claude, Codex, and
151
+ OpenCode versions, rehomes every host under a temporary profile, performs no
152
+ model or network request, and proves native discovery where an offline surface
153
+ exists, modified-target refusal, and exact uninstall recovery. It is not part
154
+ of the hermetic release gate and never mutates the caller's real home.
155
+
156
+ ## Portable user layer (v0.4)
157
+
158
+ The global layer is opt-in and separate from project ledgers. `init` creates
159
+ only human-owned canonical sources and versioned state; it does not change any
160
+ host file. `plan`, `status`, and `--json` are read-only. `apply` owns only
161
+ whole generated files it created, while `adopt HOST` backs up an existing
162
+ regular file and imports its bytes verbatim into that host's canonical overlay.
163
+
164
+ statutor global init
165
+ statutor global plan --json
166
+ statutor global apply --host codex
167
+ statutor global status --json
168
+ statutor global doctor --json
169
+ statutor global adopt claude
170
+ statutor global uninstall --host codex
171
+
172
+ statutor global skill import ./my-skill
173
+ statutor global skill plan --json
174
+ statutor global skill apply
175
+ statutor global skill status --json
176
+ statutor global skill sync
177
+ statutor global skill uninstall
178
+
179
+ Every mutation prints its resolved plan first. Existing unmanaged targets are
180
+ conflicts, managed hand edits are never overwritten or removed, and uninstall
181
+ restores the original backup. `--home`, `--config-root`, and `--state-root`
182
+ provide explicit isolated roots for tests and alternate profiles. Codex
183
+ overrides and OpenCode's Claude fallback are reported as precedence warnings;
184
+ Statutor never rewrites them.
185
+
186
+ Imported skills are copied to a new human-owned canonical source, then
187
+ projected as complete trees to `$HOME/.agents/skills` and Claude's personal
188
+ skills root. Core `SKILL.md` metadata and the complete tree are validated;
189
+ host-specific frontmatter is preserved. Identical unmanaged trees require
190
+ explicit `--adopt-identical`, differing or hand-edited trees are conflicts,
191
+ and names in a foreign `.agents/.skill-lock.json` remain entirely foreign.
192
+ OpenCode discovers the portable and Claude projections, so Statutor reports
193
+ whether those duplicates are identical instead of creating a third native
194
+ OpenCode copy.
195
+
196
+ Unified `global status` is a fast, read-only inventory of effective instruction
197
+ precedence, ownership, disabled/foreign skills, duplicate names, legacy/native/
198
+ admin/plugin roots, and catalog size. `global doctor` performs the deeper full-
199
+ tree audit: managed drift and receipt topology, generated headers, unsafe links,
200
+ active divergent duplicates, Codex's configured instruction cap, and Statutor's
201
+ 32 KiB skill-description diagnostic budget (reported explicitly as a Statutor
202
+ budget, not a host limit). Status exits zero when inventory completes; doctor
203
+ exits one when the audit contains errors. Neither command invokes a host binary
204
+ or performs a network request.
205
+
206
+ ## Provenance
207
+
208
+ Framework doctrine distilled from: the AGENTS.md open standard
209
+ <https://agents.md/>, Claude Code memory & hooks docs
210
+ <https://code.claude.com/docs/en/memory>, <https://code.claude.com/docs/en/hooks>,
211
+ Anthropic on long-running agent harnesses
212
+ <https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents>
213
+ and context engineering
214
+ <https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents>,
215
+ MADR <https://adr.github.io/madr/>, Keep a Changelog
216
+ <https://keepachangelog.com/>, Conventional Commits
217
+ <https://www.conventionalcommits.org/>, pre-commit <https://pre-commit.com/>.
@@ -0,0 +1,192 @@
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
+ Worker provenance (v0.5, `statutor machine`/`statutor worker`): automatic
71
+ hooks on every host prove an **attempt** only and record nothing by
72
+ themselves — the hook path stays fail-open. Ingress is executor- or
73
+ custom-run (`begin` → `record --event attempt` → attributed rewrite →
74
+ `complete --session`); `--event mutation` under an automatic harness is
75
+ denied. `worker capabilities` reports proven surfaces and gaps per host
76
+ (verified against Claude Code 2.1.258, Codex CLI 0.152.1, OpenCode 1.18.20
77
+ baselines); `worker compare <ref>` explains sibling handoffs read-only.
78
+
79
+ ## Install
80
+
81
+ pipx install statutor # or: pip install -e .
82
+ statutor init . # scaffold any repo, any harness
83
+
84
+ # Claude Code (this repo doubles as the plugin):
85
+ /plugin marketplace add https://github.com/hoohugokim/statutor
86
+ /plugin install statutor@hoo-plugins --scope project
87
+ # then: /statutor-init /handoff /decide /statutor-doctor
88
+
89
+ # .pre-commit-config.yaml
90
+ repos:
91
+ - repo: https://github.com/hoohugokim/statutor
92
+ rev: v0.5.0
93
+ hooks: [{id: statutor}]
94
+
95
+ Per-repo policy lives in `.statutor.yaml`. In-loop checks use the committed
96
+ HEAD snapshot; the git floor judges the transaction under both HEAD and the
97
+ candidate index snapshot, so an unstaged or co-staged weakening cannot disable
98
+ existing rules. The format is a strict, zero-dependency YAML subset shared by
99
+ Python and Rust; malformed or unsupported committed/candidate policy denies.
100
+ Before the marker's first commit, automatic hooks use embedded defaults. An
101
+ edited worktree policy does not change hook behavior until it passes trust
102
+ approval and becomes HEAD in a separate commit.
103
+
104
+ After bootstrap, changing `.statutor.yaml` or Statutor's exact
105
+ `CLAUDE.md` → `@AGENTS.md` bridge requires an exact-tree Git-local receipt:
106
+
107
+ git add .statutor.yaml
108
+ statutor trust approve . --decision D-0015 --reason "why this changes trust"
109
+ statutor staged .
110
+
111
+ Approval displays the reserved diff and all staged paths, then requires the
112
+ complete candidate tree ID. The mode-0600 receipt expires on any HEAD or index
113
+ change and is never committed.
114
+
115
+ ## Release gate
116
+
117
+ With Python `build`/`pytest`, Cargo, and npm available, stage the candidate and
118
+ run `python scripts/release_gate.py`. The gate tests the exact Git index, audits
119
+ all package payloads, builds the Python sdist and wheel in scratch space,
120
+ installs the wheel into an isolated target, and smoke-tests both console
121
+ scripts. A tagged release additionally passes `--tag vX.Y.Z --dist-dir dist`;
122
+ verified artifacts are copied only after every check succeeds.
123
+
124
+ The v0.4 global layer also has a separate, opt-in current-host probe:
125
+ `python scripts/global_e2e.py --json`. It pins the tested Claude, Codex, and
126
+ OpenCode versions, rehomes every host under a temporary profile, performs no
127
+ model or network request, and proves native discovery where an offline surface
128
+ exists, modified-target refusal, and exact uninstall recovery. It is not part
129
+ of the hermetic release gate and never mutates the caller's real home.
130
+
131
+ ## Portable user layer (v0.4)
132
+
133
+ The global layer is opt-in and separate from project ledgers. `init` creates
134
+ only human-owned canonical sources and versioned state; it does not change any
135
+ host file. `plan`, `status`, and `--json` are read-only. `apply` owns only
136
+ whole generated files it created, while `adopt HOST` backs up an existing
137
+ regular file and imports its bytes verbatim into that host's canonical overlay.
138
+
139
+ statutor global init
140
+ statutor global plan --json
141
+ statutor global apply --host codex
142
+ statutor global status --json
143
+ statutor global doctor --json
144
+ statutor global adopt claude
145
+ statutor global uninstall --host codex
146
+
147
+ statutor global skill import ./my-skill
148
+ statutor global skill plan --json
149
+ statutor global skill apply
150
+ statutor global skill status --json
151
+ statutor global skill sync
152
+ statutor global skill uninstall
153
+
154
+ Every mutation prints its resolved plan first. Existing unmanaged targets are
155
+ conflicts, managed hand edits are never overwritten or removed, and uninstall
156
+ restores the original backup. `--home`, `--config-root`, and `--state-root`
157
+ provide explicit isolated roots for tests and alternate profiles. Codex
158
+ overrides and OpenCode's Claude fallback are reported as precedence warnings;
159
+ Statutor never rewrites them.
160
+
161
+ Imported skills are copied to a new human-owned canonical source, then
162
+ projected as complete trees to `$HOME/.agents/skills` and Claude's personal
163
+ skills root. Core `SKILL.md` metadata and the complete tree are validated;
164
+ host-specific frontmatter is preserved. Identical unmanaged trees require
165
+ explicit `--adopt-identical`, differing or hand-edited trees are conflicts,
166
+ and names in a foreign `.agents/.skill-lock.json` remain entirely foreign.
167
+ OpenCode discovers the portable and Claude projections, so Statutor reports
168
+ whether those duplicates are identical instead of creating a third native
169
+ OpenCode copy.
170
+
171
+ Unified `global status` is a fast, read-only inventory of effective instruction
172
+ precedence, ownership, disabled/foreign skills, duplicate names, legacy/native/
173
+ admin/plugin roots, and catalog size. `global doctor` performs the deeper full-
174
+ tree audit: managed drift and receipt topology, generated headers, unsafe links,
175
+ active divergent duplicates, Codex's configured instruction cap, and Statutor's
176
+ 32 KiB skill-description diagnostic budget (reported explicitly as a Statutor
177
+ budget, not a host limit). Status exits zero when inventory completes; doctor
178
+ exits one when the audit contains errors. Neither command invokes a host binary
179
+ or performs a network request.
180
+
181
+ ## Provenance
182
+
183
+ Framework doctrine distilled from: the AGENTS.md open standard
184
+ <https://agents.md/>, Claude Code memory & hooks docs
185
+ <https://code.claude.com/docs/en/memory>, <https://code.claude.com/docs/en/hooks>,
186
+ Anthropic on long-running agent harnesses
187
+ <https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents>
188
+ and context engineering
189
+ <https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents>,
190
+ MADR <https://adr.github.io/madr/>, Keep a Changelog
191
+ <https://keepachangelog.com/>, Conventional Commits
192
+ <https://www.conventionalcommits.org/>, pre-commit <https://pre-commit.com/>.
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.4
2
+ Name: statutor
3
+ Version: 0.5.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
+ Worker provenance (v0.5, `statutor machine`/`statutor worker`): automatic
96
+ hooks on every host prove an **attempt** only and record nothing by
97
+ themselves — the hook path stays fail-open. Ingress is executor- or
98
+ custom-run (`begin` → `record --event attempt` → attributed rewrite →
99
+ `complete --session`); `--event mutation` under an automatic harness is
100
+ denied. `worker capabilities` reports proven surfaces and gaps per host
101
+ (verified against Claude Code 2.1.258, Codex CLI 0.152.1, OpenCode 1.18.20
102
+ baselines); `worker compare <ref>` explains sibling handoffs read-only.
103
+
104
+ ## Install
105
+
106
+ pipx install statutor # or: pip install -e .
107
+ statutor init . # scaffold any repo, any harness
108
+
109
+ # Claude Code (this repo doubles as the plugin):
110
+ /plugin marketplace add https://github.com/hoohugokim/statutor
111
+ /plugin install statutor@hoo-plugins --scope project
112
+ # then: /statutor-init /handoff /decide /statutor-doctor
113
+
114
+ # .pre-commit-config.yaml
115
+ repos:
116
+ - repo: https://github.com/hoohugokim/statutor
117
+ rev: v0.5.0
118
+ hooks: [{id: statutor}]
119
+
120
+ Per-repo policy lives in `.statutor.yaml`. In-loop checks use the committed
121
+ HEAD snapshot; the git floor judges the transaction under both HEAD and the
122
+ candidate index snapshot, so an unstaged or co-staged weakening cannot disable
123
+ existing rules. The format is a strict, zero-dependency YAML subset shared by
124
+ Python and Rust; malformed or unsupported committed/candidate policy denies.
125
+ Before the marker's first commit, automatic hooks use embedded defaults. An
126
+ edited worktree policy does not change hook behavior until it passes trust
127
+ approval and becomes HEAD in a separate commit.
128
+
129
+ After bootstrap, changing `.statutor.yaml` or Statutor's exact
130
+ `CLAUDE.md` → `@AGENTS.md` bridge requires an exact-tree Git-local receipt:
131
+
132
+ git add .statutor.yaml
133
+ statutor trust approve . --decision D-0015 --reason "why this changes trust"
134
+ statutor staged .
135
+
136
+ Approval displays the reserved diff and all staged paths, then requires the
137
+ complete candidate tree ID. The mode-0600 receipt expires on any HEAD or index
138
+ change and is never committed.
139
+
140
+ ## Release gate
141
+
142
+ With Python `build`/`pytest`, Cargo, and npm available, stage the candidate and
143
+ run `python scripts/release_gate.py`. The gate tests the exact Git index, audits
144
+ all package payloads, builds the Python sdist and wheel in scratch space,
145
+ installs the wheel into an isolated target, and smoke-tests both console
146
+ scripts. A tagged release additionally passes `--tag vX.Y.Z --dist-dir dist`;
147
+ verified artifacts are copied only after every check succeeds.
148
+
149
+ The v0.4 global layer also has a separate, opt-in current-host probe:
150
+ `python scripts/global_e2e.py --json`. It pins the tested Claude, Codex, and
151
+ OpenCode versions, rehomes every host under a temporary profile, performs no
152
+ model or network request, and proves native discovery where an offline surface
153
+ exists, modified-target refusal, and exact uninstall recovery. It is not part
154
+ of the hermetic release gate and never mutates the caller's real home.
155
+
156
+ ## Portable user layer (v0.4)
157
+
158
+ The global layer is opt-in and separate from project ledgers. `init` creates
159
+ only human-owned canonical sources and versioned state; it does not change any
160
+ host file. `plan`, `status`, and `--json` are read-only. `apply` owns only
161
+ whole generated files it created, while `adopt HOST` backs up an existing
162
+ regular file and imports its bytes verbatim into that host's canonical overlay.
163
+
164
+ statutor global init
165
+ statutor global plan --json
166
+ statutor global apply --host codex
167
+ statutor global status --json
168
+ statutor global doctor --json
169
+ statutor global adopt claude
170
+ statutor global uninstall --host codex
171
+
172
+ statutor global skill import ./my-skill
173
+ statutor global skill plan --json
174
+ statutor global skill apply
175
+ statutor global skill status --json
176
+ statutor global skill sync
177
+ statutor global skill uninstall
178
+
179
+ Every mutation prints its resolved plan first. Existing unmanaged targets are
180
+ conflicts, managed hand edits are never overwritten or removed, and uninstall
181
+ restores the original backup. `--home`, `--config-root`, and `--state-root`
182
+ provide explicit isolated roots for tests and alternate profiles. Codex
183
+ overrides and OpenCode's Claude fallback are reported as precedence warnings;
184
+ Statutor never rewrites them.
185
+
186
+ Imported skills are copied to a new human-owned canonical source, then
187
+ projected as complete trees to `$HOME/.agents/skills` and Claude's personal
188
+ skills root. Core `SKILL.md` metadata and the complete tree are validated;
189
+ host-specific frontmatter is preserved. Identical unmanaged trees require
190
+ explicit `--adopt-identical`, differing or hand-edited trees are conflicts,
191
+ and names in a foreign `.agents/.skill-lock.json` remain entirely foreign.
192
+ OpenCode discovers the portable and Claude projections, so Statutor reports
193
+ whether those duplicates are identical instead of creating a third native
194
+ OpenCode copy.
195
+
196
+ Unified `global status` is a fast, read-only inventory of effective instruction
197
+ precedence, ownership, disabled/foreign skills, duplicate names, legacy/native/
198
+ admin/plugin roots, and catalog size. `global doctor` performs the deeper full-
199
+ tree audit: managed drift and receipt topology, generated headers, unsafe links,
200
+ active divergent duplicates, Codex's configured instruction cap, and Statutor's
201
+ 32 KiB skill-description diagnostic budget (reported explicitly as a Statutor
202
+ budget, not a host limit). Status exits zero when inventory completes; doctor
203
+ exits one when the audit contains errors. Neither command invokes a host binary
204
+ or performs a network request.
205
+
206
+ ## Provenance
207
+
208
+ Framework doctrine distilled from: the AGENTS.md open standard
209
+ <https://agents.md/>, Claude Code memory & hooks docs
210
+ <https://code.claude.com/docs/en/memory>, <https://code.claude.com/docs/en/hooks>,
211
+ Anthropic on long-running agent harnesses
212
+ <https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents>
213
+ and context engineering
214
+ <https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents>,
215
+ MADR <https://adr.github.io/madr/>, Keep a Changelog
216
+ <https://keepachangelog.com/>, Conventional Commits
217
+ <https://www.conventionalcommits.org/>, pre-commit <https://pre-commit.com/>.
@@ -1,15 +1,16 @@
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
11
+ core/statutor_worker.py
6
12
  core/statutor.egg-info/PKG-INFO
7
13
  core/statutor.egg-info/SOURCES.txt
8
14
  core/statutor.egg-info/dependency_links.txt
9
15
  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
16
+ core/statutor.egg-info/top_level.txt