statutor 0.5.0__tar.gz → 0.6.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: statutor
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Typed project-ledger framework for agentic repos: four planes, single-writer files, enforced mutation policies.
5
5
  Author: Hoo
6
6
  License-Expression: MIT
@@ -46,6 +46,14 @@ message data is not scanned as shell code; its opener and all actual command
46
46
  lines remain guarded. No hand-maintained CHANGELOG.md: git log + conventional
47
47
  commits.
48
48
 
49
+ The policies are ordered by reversibility and the guardrails follow that
50
+ order: a HANDOFF overwrite is reversible, a TASKS checkbox or detail edit is
51
+ reversible with history, a DECISIONS append is irreversible by policy, and an
52
+ archived plan is frozen (only `git mv` INTO `plans/archive/` is allowed). Text
53
+ an agent reads or quotes — a note, a heredoc, a tool result — informs its
54
+ proposal but never authorizes a mutation: only the declared policy under the
55
+ committed HEAD snapshot gates a write, in-loop and at the git floor alike.
56
+
49
57
  State task identities are durable: an existing ID cannot disappear or be
50
58
  renamed, while its checkbox, detail, and position may change. New IDs advance
51
59
  beyond the committed maximum. v0.4 intentionally has no pruning operation;
@@ -99,12 +107,16 @@ custom-run (`begin` → `record --event attempt` → attributed rewrite →
99
107
  `complete --session`); `--event mutation` under an automatic harness is
100
108
  denied. `worker capabilities` reports proven surfaces and gaps per host
101
109
  (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.
110
+ baselines); `worker compare <ref>` explains sibling handoffs read-only. The
111
+ machine-local registry is a bounded ring (50 sessions per project, 20 leases
112
+ per worktree), not an audit trail; durable provenance is the attributed
113
+ HANDOFF lineage in Git history, which lives as long as the repository does.
103
114
 
104
115
  ## Install
105
116
 
106
117
  pipx install statutor # or: pip install -e .
107
118
  statutor init . # scaffold any repo, any harness
119
+ statutor init . --type none # governed files only; env: STATUTOR_INIT_TYPE
108
120
 
109
121
  # Claude Code (this repo doubles as the plugin):
110
122
  /plugin marketplace add https://github.com/hoohugokim/statutor
@@ -114,9 +126,26 @@ baselines); `worker compare <ref>` explains sibling handoffs read-only.
114
126
  # .pre-commit-config.yaml
115
127
  repos:
116
128
  - repo: https://github.com/hoohugokim/statutor
117
- rev: v0.5.0
129
+ rev: v0.6.0
118
130
  hooks: [{id: statutor}]
119
131
 
132
+ `init` never mandates a layout (D-0024). It selects a profile — `--type NAME`,
133
+ else `STATUTOR_INIT_TYPE`, else the first marker found in the order below, else
134
+ `min` — and adds only empty conventional directories on top of the governed
135
+ files, `plans/archive/`, `notes/`, and the `CLAUDE.md` bridge. Present paths
136
+ are skipped, never overwritten, adopted, or followed; no profile writes
137
+ policy, constitution content, or ecosystem files, and the `min` fallback
138
+ prints and creates exactly what the pre-v0.6 `init` did.
139
+
140
+ | Profile | Adds | Selected by |
141
+ |---|---|---|
142
+ | `python` | `tests/` | `pyproject.toml`, `setup.py`, `setup.cfg` |
143
+ | `rust` | `tests/` | `Cargo.toml` |
144
+ | `node` | `test/` | `package.json` |
145
+ | `docs` | `docs/` | `mkdocs.yml`, `_quarto.yml`, an existing `docs/` |
146
+ | `min` | nothing further | fallback when no marker matches, or explicitly |
147
+ | `none` | nothing, not even `plans/archive/` or `notes/` | `--type none` or `STATUTOR_INIT_TYPE=none` only |
148
+
120
149
  Per-repo policy lives in `.statutor.yaml`. In-loop checks use the committed
121
150
  HEAD snapshot; the git floor judges the transaction under both HEAD and the
122
151
  candidate index snapshot, so an unstaged or co-staged weakening cannot disable
@@ -21,6 +21,14 @@ message data is not scanned as shell code; its opener and all actual command
21
21
  lines remain guarded. No hand-maintained CHANGELOG.md: git log + conventional
22
22
  commits.
23
23
 
24
+ The policies are ordered by reversibility and the guardrails follow that
25
+ order: a HANDOFF overwrite is reversible, a TASKS checkbox or detail edit is
26
+ reversible with history, a DECISIONS append is irreversible by policy, and an
27
+ archived plan is frozen (only `git mv` INTO `plans/archive/` is allowed). Text
28
+ an agent reads or quotes — a note, a heredoc, a tool result — informs its
29
+ proposal but never authorizes a mutation: only the declared policy under the
30
+ committed HEAD snapshot gates a write, in-loop and at the git floor alike.
31
+
24
32
  State task identities are durable: an existing ID cannot disappear or be
25
33
  renamed, while its checkbox, detail, and position may change. New IDs advance
26
34
  beyond the committed maximum. v0.4 intentionally has no pruning operation;
@@ -74,12 +82,16 @@ custom-run (`begin` → `record --event attempt` → attributed rewrite →
74
82
  `complete --session`); `--event mutation` under an automatic harness is
75
83
  denied. `worker capabilities` reports proven surfaces and gaps per host
76
84
  (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.
85
+ baselines); `worker compare <ref>` explains sibling handoffs read-only. The
86
+ machine-local registry is a bounded ring (50 sessions per project, 20 leases
87
+ per worktree), not an audit trail; durable provenance is the attributed
88
+ HANDOFF lineage in Git history, which lives as long as the repository does.
78
89
 
79
90
  ## Install
80
91
 
81
92
  pipx install statutor # or: pip install -e .
82
93
  statutor init . # scaffold any repo, any harness
94
+ statutor init . --type none # governed files only; env: STATUTOR_INIT_TYPE
83
95
 
84
96
  # Claude Code (this repo doubles as the plugin):
85
97
  /plugin marketplace add https://github.com/hoohugokim/statutor
@@ -89,9 +101,26 @@ baselines); `worker compare <ref>` explains sibling handoffs read-only.
89
101
  # .pre-commit-config.yaml
90
102
  repos:
91
103
  - repo: https://github.com/hoohugokim/statutor
92
- rev: v0.5.0
104
+ rev: v0.6.0
93
105
  hooks: [{id: statutor}]
94
106
 
107
+ `init` never mandates a layout (D-0024). It selects a profile — `--type NAME`,
108
+ else `STATUTOR_INIT_TYPE`, else the first marker found in the order below, else
109
+ `min` — and adds only empty conventional directories on top of the governed
110
+ files, `plans/archive/`, `notes/`, and the `CLAUDE.md` bridge. Present paths
111
+ are skipped, never overwritten, adopted, or followed; no profile writes
112
+ policy, constitution content, or ecosystem files, and the `min` fallback
113
+ prints and creates exactly what the pre-v0.6 `init` did.
114
+
115
+ | Profile | Adds | Selected by |
116
+ |---|---|---|
117
+ | `python` | `tests/` | `pyproject.toml`, `setup.py`, `setup.cfg` |
118
+ | `rust` | `tests/` | `Cargo.toml` |
119
+ | `node` | `test/` | `package.json` |
120
+ | `docs` | `docs/` | `mkdocs.yml`, `_quarto.yml`, an existing `docs/` |
121
+ | `min` | nothing further | fallback when no marker matches, or explicitly |
122
+ | `none` | nothing, not even `plans/archive/` or `notes/` | `--type none` or `STATUTOR_INIT_TYPE=none` only |
123
+
95
124
  Per-repo policy lives in `.statutor.yaml`. In-loop checks use the committed
96
125
  HEAD snapshot; the git floor judges the transaction under both HEAD and the
97
126
  candidate index snapshot, so an unstaged or co-staged weakening cannot disable
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: statutor
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Typed project-ledger framework for agentic repos: four planes, single-writer files, enforced mutation policies.
5
5
  Author: Hoo
6
6
  License-Expression: MIT
@@ -46,6 +46,14 @@ message data is not scanned as shell code; its opener and all actual command
46
46
  lines remain guarded. No hand-maintained CHANGELOG.md: git log + conventional
47
47
  commits.
48
48
 
49
+ The policies are ordered by reversibility and the guardrails follow that
50
+ order: a HANDOFF overwrite is reversible, a TASKS checkbox or detail edit is
51
+ reversible with history, a DECISIONS append is irreversible by policy, and an
52
+ archived plan is frozen (only `git mv` INTO `plans/archive/` is allowed). Text
53
+ an agent reads or quotes — a note, a heredoc, a tool result — informs its
54
+ proposal but never authorizes a mutation: only the declared policy under the
55
+ committed HEAD snapshot gates a write, in-loop and at the git floor alike.
56
+
49
57
  State task identities are durable: an existing ID cannot disappear or be
50
58
  renamed, while its checkbox, detail, and position may change. New IDs advance
51
59
  beyond the committed maximum. v0.4 intentionally has no pruning operation;
@@ -99,12 +107,16 @@ custom-run (`begin` → `record --event attempt` → attributed rewrite →
99
107
  `complete --session`); `--event mutation` under an automatic harness is
100
108
  denied. `worker capabilities` reports proven surfaces and gaps per host
101
109
  (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.
110
+ baselines); `worker compare <ref>` explains sibling handoffs read-only. The
111
+ machine-local registry is a bounded ring (50 sessions per project, 20 leases
112
+ per worktree), not an audit trail; durable provenance is the attributed
113
+ HANDOFF lineage in Git history, which lives as long as the repository does.
103
114
 
104
115
  ## Install
105
116
 
106
117
  pipx install statutor # or: pip install -e .
107
118
  statutor init . # scaffold any repo, any harness
119
+ statutor init . --type none # governed files only; env: STATUTOR_INIT_TYPE
108
120
 
109
121
  # Claude Code (this repo doubles as the plugin):
110
122
  /plugin marketplace add https://github.com/hoohugokim/statutor
@@ -114,9 +126,26 @@ baselines); `worker compare <ref>` explains sibling handoffs read-only.
114
126
  # .pre-commit-config.yaml
115
127
  repos:
116
128
  - repo: https://github.com/hoohugokim/statutor
117
- rev: v0.5.0
129
+ rev: v0.6.0
118
130
  hooks: [{id: statutor}]
119
131
 
132
+ `init` never mandates a layout (D-0024). It selects a profile — `--type NAME`,
133
+ else `STATUTOR_INIT_TYPE`, else the first marker found in the order below, else
134
+ `min` — and adds only empty conventional directories on top of the governed
135
+ files, `plans/archive/`, `notes/`, and the `CLAUDE.md` bridge. Present paths
136
+ are skipped, never overwritten, adopted, or followed; no profile writes
137
+ policy, constitution content, or ecosystem files, and the `min` fallback
138
+ prints and creates exactly what the pre-v0.6 `init` did.
139
+
140
+ | Profile | Adds | Selected by |
141
+ |---|---|---|
142
+ | `python` | `tests/` | `pyproject.toml`, `setup.py`, `setup.cfg` |
143
+ | `rust` | `tests/` | `Cargo.toml` |
144
+ | `node` | `test/` | `package.json` |
145
+ | `docs` | `docs/` | `mkdocs.yml`, `_quarto.yml`, an existing `docs/` |
146
+ | `min` | nothing further | fallback when no marker matches, or explicitly |
147
+ | `none` | nothing, not even `plans/archive/` or `notes/` | `--type none` or `STATUTOR_INIT_TYPE=none` only |
148
+
120
149
  Per-repo policy lives in `.statutor.yaml`. In-loop checks use the committed
121
150
  HEAD snapshot; the git floor judges the transaction under both HEAD and the
122
151
  candidate index snapshot, so an unstaged or co-staged weakening cannot disable
@@ -25,7 +25,10 @@ Entry modes (all share the same validate() core):
25
25
  ledger, or stay silent when none exists.
26
26
  statutor staged [CWD] Git floor: validate staged changes (pre-commit).
27
27
  exit 1 on violations.
28
- statutor init [DIR] Scaffold governed files from embedded templates.
28
+ statutor init [DIR] [--type NAME]
29
+ Scaffold governed files from embedded templates.
30
+ Profiles (D-0024) add conventional directories only:
31
+ --type > STATUTOR_INIT_TYPE > marker detection > min.
29
32
  statutor global ... Opt-in global instruction and skill projection manager.
30
33
  statutor machine ... Random machine identity (mode 0600, rotation).
31
34
  statutor worker ... Machine-local worker provenance (begin/record/show/active/complete/compare).
@@ -787,6 +790,13 @@ def _automatic_reason(tool: str, payload: dict, cwd: str) -> str | None:
787
790
  return None
788
791
 
789
792
  def run_hook() -> int:
793
+ if sys.stdin.isatty():
794
+ # A TTY means a human ran the hook with no piped event (harnesses
795
+ # always pipe JSON). Fail fast with usage instead of blocking on
796
+ # stdin forever; fail-open applies to hook *events*, not misuse.
797
+ print("statutor hook expects piped hook JSON on stdin; "
798
+ "run `statutor --help` for modes.", file=sys.stderr)
799
+ return 64
790
800
  try:
791
801
  event = json.load(sys.stdin)
792
802
  tool = event.get("tool_name", "")
@@ -1389,30 +1399,208 @@ governed:
1389
1399
  }
1390
1400
 
1391
1401
 
1392
- def run_init(target: str) -> int:
1393
- os.makedirs(os.path.join(target, "plans", "archive"), exist_ok=True)
1394
- os.makedirs(os.path.join(target, "notes"), exist_ok=True)
1395
- for name, body in TEMPLATES.items():
1396
- path = os.path.join(target, name)
1397
- if os.path.exists(path):
1398
- print(f"skip {name} (exists)")
1402
+ # Init profiles (D-0024): Statutor never mandates a project layout. A profile
1403
+ # adds empty *conventional* directories on top of the governed files — never
1404
+ # policy, constitution content, or ecosystem files — and every create is
1405
+ # additive and idempotent (present paths are skipped, never overwritten,
1406
+ # adopted, or followed). Definitions live here beside TEMPLATES for the same
1407
+ # reason the templates do (D-0003): a profiles/ directory would fork the
1408
+ # source of truth.
1409
+ #
1410
+ # Precedence, highest first: `--type NAME` > `STATUTOR_INIT_TYPE` > the first
1411
+ # marker hit in INIT_DETECT_ORDER > `min`. A marker written `name/` must be a
1412
+ # directory; any other marker must be a regular file. The `min` fallback prints
1413
+ # and creates exactly what pre-v0.6 `init` did. `none` adds no directory at
1414
+ # all, not even the ledger skeleton.
1415
+
1416
+ INIT_SKELETON_DIRS: tuple[str, ...] = ("plans/archive", "notes")
1417
+ INIT_PROFILES: dict[str, dict[str, tuple[str, ...]]] = {
1418
+ "none": {"dirs": (), "markers": ()},
1419
+ "min": {"dirs": (), "markers": ()},
1420
+ "python": {"dirs": ("tests",),
1421
+ "markers": ("pyproject.toml", "setup.py", "setup.cfg")},
1422
+ "rust": {"dirs": ("tests",), "markers": ("Cargo.toml",)},
1423
+ "node": {"dirs": ("test",), "markers": ("package.json",)},
1424
+ "docs": {"dirs": ("docs",), "markers": ("mkdocs.yml", "_quarto.yml", "docs/")},
1425
+ }
1426
+ INIT_DETECT_ORDER: tuple[str, ...] = ("python", "rust", "node", "docs")
1427
+ INIT_TYPE_ENV = "STATUTOR_INIT_TYPE"
1428
+ INIT_USAGE = (f"usage: statutor init [DIR] [--type {'|'.join(INIT_PROFILES)}]"
1429
+ f" (env: {INIT_TYPE_ENV}=NAME)")
1430
+
1431
+
1432
+ def _init_profile_name(raw: str, source: str) -> str:
1433
+ name = raw.strip().lower()
1434
+ if name not in INIT_PROFILES:
1435
+ raise ValueError(
1436
+ f"unknown init profile {raw!r} from {source}; "
1437
+ f"choose one of {', '.join(INIT_PROFILES)}")
1438
+ return name
1439
+
1440
+
1441
+ def _marker_present(target: str, marker: str) -> bool:
1442
+ """`name/` markers need a directory, others a regular file (links followed)."""
1443
+ path = os.path.join(target, marker.rstrip("/"))
1444
+ return os.path.isdir(path) if marker.endswith("/") else os.path.isfile(path)
1445
+
1446
+
1447
+ def resolve_init_profile(
1448
+ target: str, explicit: str | None = None, env: dict | None = None,
1449
+ ) -> tuple[str, str]:
1450
+ """Return `(profile, source)` for `target` without touching the disk.
1451
+
1452
+ `source` is `--type`, the environment variable name, `detected <marker>`,
1453
+ or `fallback`. An empty environment value counts as unset (dotfiles often
1454
+ export empty strings); an explicit empty `--type` is an error. `env` of
1455
+ None consults nothing: only the CLI reads the process environment, so
1456
+ in-process callers stay deterministic.
1457
+ """
1458
+ if explicit is not None:
1459
+ return _init_profile_name(explicit, "--type"), "--type"
1460
+ raw = (env or {}).get(INIT_TYPE_ENV, "")
1461
+ if isinstance(raw, str) and raw.strip():
1462
+ return _init_profile_name(raw, INIT_TYPE_ENV), INIT_TYPE_ENV
1463
+ for name in INIT_DETECT_ORDER:
1464
+ for marker in INIT_PROFILES[name]["markers"]:
1465
+ if _marker_present(target, marker):
1466
+ return name, f"detected {marker}"
1467
+ return "min", "fallback"
1468
+
1469
+
1470
+ def _ensure_dir(root: str, rel: str) -> bool:
1471
+ """Create `root/rel` one component at a time; True when the leaf was created.
1472
+
1473
+ Any present component that is a symlink or not a directory blocks the
1474
+ whole path (returned as a skip): init never follows a link out of the
1475
+ target and never adopts a file. A concurrent creator is a skip too.
1476
+ Permission failures still propagate.
1477
+ """
1478
+ current = root
1479
+ created = False
1480
+ for part in rel.split("/"):
1481
+ current = os.path.join(current, part)
1482
+ if os.path.lexists(current):
1483
+ if os.path.islink(current) or not os.path.isdir(current):
1484
+ return False
1485
+ created = False
1399
1486
  continue
1400
- with open(path, "w", encoding="utf-8") as fh:
1401
- fh.write(body)
1402
- print(f"write {name}")
1403
- claude_md = os.path.join(target, "CLAUDE.md")
1404
- if not os.path.exists(claude_md):
1405
- with open(claude_md, "w", encoding="utf-8") as fh:
1406
- fh.write("@AGENTS.md\n")
1407
- print("write CLAUDE.md (@AGENTS.md import)")
1487
+ try:
1488
+ os.mkdir(current)
1489
+ created = True
1490
+ except FileExistsError:
1491
+ created = False
1492
+ return created
1493
+
1494
+
1495
+ def _create_file(path: str, body: str) -> bool:
1496
+ """Exclusively create `path`; False when any entry (even a dangling link) exists."""
1497
+ try:
1498
+ fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o666)
1499
+ except FileExistsError:
1500
+ return False
1501
+ with os.fdopen(fd, "w", encoding="utf-8") as fh:
1502
+ fh.write(body)
1503
+ return True
1504
+
1505
+
1506
+ def _present_kind(path: str) -> str:
1507
+ if os.path.isdir(path) and not os.path.islink(path):
1508
+ return "exists"
1509
+ return "present, left alone"
1510
+
1511
+
1512
+ def run_init(target: str, profile: str | None = None, env: dict | None = None) -> int:
1513
+ try:
1514
+ name, source = resolve_init_profile(target, profile, env)
1515
+ except ValueError as exc:
1516
+ print(f"statutor init: {exc}", file=sys.stderr)
1517
+ print(INIT_USAGE, file=sys.stderr)
1518
+ return 64
1519
+ if source != "fallback":
1520
+ print(f"profile {name} ({source})")
1521
+ if os.path.lexists(target) and not os.path.isdir(target):
1522
+ print(f"statutor init: target {target!r} exists and is not a directory",
1523
+ file=sys.stderr)
1524
+ return 1
1525
+ try:
1526
+ os.makedirs(target, exist_ok=True)
1527
+ if name != "none":
1528
+ for rel in INIT_SKELETON_DIRS:
1529
+ _ensure_dir(target, rel) # silent: pre-v0.6 behavior
1530
+ for filename, body in TEMPLATES.items():
1531
+ if _create_file(os.path.join(target, filename), body):
1532
+ print(f"write {filename}")
1533
+ else:
1534
+ print(f"skip {filename} (exists)")
1535
+ if _create_file(os.path.join(target, "CLAUDE.md"), "@AGENTS.md\n"):
1536
+ print("write CLAUDE.md (@AGENTS.md import)")
1537
+ for rel in INIT_PROFILES[name]["dirs"]:
1538
+ if _ensure_dir(target, rel):
1539
+ print(f"mkdir {rel}/")
1540
+ else:
1541
+ print(f"skip {rel}/ ({_present_kind(os.path.join(target, rel))})")
1542
+ except OSError as exc:
1543
+ print(f"statutor init: {exc}", file=sys.stderr)
1544
+ return 1
1408
1545
  return 0
1409
1546
 
1410
1547
 
1548
+ def run_init_cli(argv: list[str], env: dict | None = None) -> int:
1549
+ """Parse `statutor init [DIR] [--type NAME] [-- DIR]`; the CLI alone reads the env."""
1550
+ target: str | None = None
1551
+ profile: str | None = None
1552
+ positional_only = False
1553
+ i = 0
1554
+
1555
+ def usage_error(message: str) -> int:
1556
+ print(f"statutor init: {message}", file=sys.stderr)
1557
+ print(INIT_USAGE, file=sys.stderr)
1558
+ return 64
1559
+
1560
+ while i < len(argv):
1561
+ arg = argv[i]
1562
+ if not positional_only:
1563
+ if arg in ("-h", "--help"):
1564
+ print(INIT_USAGE)
1565
+ return 0
1566
+ if arg == "--":
1567
+ positional_only = True
1568
+ i += 1
1569
+ continue
1570
+ if arg == "--type" or arg.startswith("--type="):
1571
+ if profile is not None:
1572
+ return usage_error("--type given more than once")
1573
+ if arg == "--type":
1574
+ if i + 1 >= len(argv):
1575
+ return usage_error("missing value for --type")
1576
+ profile = argv[i + 1]
1577
+ i += 2
1578
+ else:
1579
+ profile = arg[len("--type="):]
1580
+ i += 1
1581
+ continue
1582
+ if arg.startswith("-"):
1583
+ return usage_error(f"unexpected argument: {arg}")
1584
+ if not arg:
1585
+ return usage_error("DIR must not be empty")
1586
+ if target is not None:
1587
+ return usage_error(f"unexpected argument: {arg}")
1588
+ target = arg
1589
+ i += 1
1590
+ environment = dict(os.environ) if env is None else env
1591
+ return run_init(target if target is not None else os.getcwd(), profile, environment)
1592
+
1593
+
1411
1594
  # --------------------------------------------------------------------------
1412
1595
 
1413
1596
  def main() -> None:
1414
1597
  argv = sys.argv[1:]
1415
- mode = argv[0] if argv else "hook"
1598
+ if not argv or argv[0] in ("--help", "-h"):
1599
+ # Bare invocation must never fall through to hook mode (which blocks
1600
+ # on stdin); print usage instead. --help exits clean, bare exits 64.
1601
+ print(__doc__)
1602
+ sys.exit(0 if argv else 64)
1603
+ mode = argv[0]
1416
1604
  if mode in ("hook", "--claude-hook"):
1417
1605
  sys.exit(run_hook())
1418
1606
  if mode == "check":
@@ -1422,7 +1610,7 @@ def main() -> None:
1422
1610
  if mode == "trust" and len(argv) > 1 and argv[1] == "approve":
1423
1611
  sys.exit(run_trust_approve(argv[2:]))
1424
1612
  if mode == "init":
1425
- sys.exit(run_init(argv[1] if len(argv) > 1 else os.getcwd()))
1613
+ sys.exit(run_init_cli(argv[1:]))
1426
1614
  if mode == "global":
1427
1615
  import statutor_global_cli
1428
1616
  sys.exit(statutor_global_cli.main(argv[1:]))
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "statutor"
7
- version = "0.5.0"
7
+ version = "0.6.0"
8
8
  description = "Typed project-ledger framework for agentic repos: four planes, single-writer files, enforced mutation policies."
9
9
  readme = "README.md"
10
10
  license = "MIT"
File without changes
File without changes
File without changes