Metadata-Version: 2.4
Name: barony
Version: 0.5.3
Summary: Git-native governance for teams of AI coding agents — the baron CLI over Barony's markdown/git collab substrate.
Project-URL: Homepage, https://github.com/vggg/barony
Project-URL: Repository, https://github.com/vggg/barony
Project-URL: Documentation, https://github.com/vggg/barony/blob/main/docs/concepts.md
Project-URL: Changelog, https://github.com/vggg/barony/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/vggg/barony/issues
Requires-Python: >=3.10
Requires-Dist: pyyaml>=6.0
Requires-Dist: typer>=0.12
Provides-Extra: pydantic-ai
Requires-Dist: pydantic-ai-harness<0.11,>=0.10; extra == 'pydantic-ai'
Requires-Dist: pydantic-ai-slim<3,>=2.14.1; extra == 'pydantic-ai'
Description-Content-Type: text/markdown

# barony — git-native governance for teams of AI coding agents

> Install `barony`, run `baron` (or `barony` — both work), import `baron`.
> Part of **Barony** —
> [github.com/vggg/barony](https://github.com/vggg/barony) — where the full
> framework, concepts, and adapters live. This page is the CLI reference.

`baron` is **Barony**'s CLI. It
turns the multi-agent coordination *conventions* that
Barony emits into *mechanisms*: a small CLI that scaffolds new projects
(`baron init`), validates the canonical specs, reports clone/branch/ledger
divergence, and performs the race-prone record-keeping operations
(finding/decision numbering, handoff lifecycle) safely.

**Design principle (ADR-003, `../docs/adr/ADR-003-baron-cli.md`): the
markdown/git substrate IS the database.** baron is a disciplined reader/writer
over the same human-legible collab-repo files the personas use
(`manifest.yaml`, `persona.yaml`, `findings/index.md`, `decisions/index.md`,
`_handoff/*.md`, `wiki/status.md`). It never introduces another store, and every
file it writes remains fully human/agent-legible.

Dependencies: **typer + pyyaml only**. `git` is driven via subprocess. `gh` is an
accepted prerequisite for forge features only (`baron lock`) — everything else
below works without `gh` installed. The pydantic-ai runtime adapter is an
**optional extra** (`barony[pydantic-ai]`, pinned) — see `baron hydrate` below.

## Install

Live on PyPI (distribution name `barony`, console script `baron`):

```bash
uv tool install barony                  # installs the `baron` console script
uv tool install 'barony[pydantic-ai]'   # + the pydantic-ai runtime adapter
# or:
pip install barony
baron --version
```

From a clone (development):

```bash
uv run --project cli baron --help       # run without installing
uv tool install ./cli                   # install the local copy
```

## Quickstart

Verified end-to-end on a bare venv (`pip install` the wheel, no repo checkout):

```bash
baron init gardenkit --dir gardenkit-collab --code-repo ./gardenkit \
  --personas dev:fern,dev:moss,librarian:iris
cd gardenkit-collab
baron validate .          # 0 errors
baron status              # green when fresh
baron finding new --title "First finding" --author fern --no-push
HANDOFF=$(baron handoff create --for moss --from fern --title "Review the seam")
baron handoff close "$HANDOFF" --note "Done, see F1."
baron index               # regenerates _handoff/README.md — commit it
baron worktree add fern   # ../gardenkit-worktrees/fern, branch persona/fern
```

## Commands

### `baron init <project-name>` (ADR-006)

The deterministic, non-conversational scaffold — the mechanical subset of the
skill's `ORCHESTRATE.md` recipe.

```bash
baron init gardenkit --dir gardenkit-collab --code-repo ./gardenkit \
  --personas dev:fern,dev:moss,librarian:iris --runtime claude
```

Flags: `--dir` (default `./<project-name>`), `--code-repo` (local path or git
URL, recorded in `manifest.yaml` with a relative path + `workspace.worktrees_root`),
`--personas archetype:slug,...` (archetypes: `dev`, `librarian`,
`autonomous-event`, `autonomous-cron`, `reviewer`, `merger`; a librarian is
appended when missing), `--runtime claude|generic|pydantic-ai|code-puppy`,
`--no-git`. Refuses a non-empty target directory.

Emits the canonical layout — `CONVENTIONS.md`/`COORDINATION.md` (filled),
`manifest.yaml`, `canon/` + `adapters/` copied verbatim from the **packaged
templates** (vendored from `skills/barony/assets/collab-repo/` per ADR-006;
drift-guarded byte-for-byte by `tests/test_template_sync.py`), hydrated
`agents/<slug>/persona.yaml` (identity `<slug>@<project>.local`, commit prefix,
routing label; generic edit-me scope), a genesis handoff, `findings/index.md` +
`decisions/index.md` ledger headers, the wiki stub, the lock-guard CI template,
and a per-persona **runtime kit** under `agents/<slug>/runtime/`:

| `--runtime` | Kit contents |
|---|---|
| `claude` (default) | Tier-2 persona `CLAUDE.md` + `.claude/settings.json` wiring the `baron guard` PreToolUse hook |
| `generic`, `code-puppy` | Tier-1 `AGENTS.md` (instruction-only; code-puppy's documented fallback) |
| `pydantic-ai` | `agent_setup.py` bootstrap (running it needs `barony[pydantic-ai]`) |

The scaffold must pass `baron validate` with zero errors before init reports
success; then `git init -b main` + a first commit of exactly the files written
(never `git add -A`), unless `--no-git`. Dates come from the injectable clock.
Persona scope prose, `AGENT.md` manuals, and Tier-3 hydration (Claude
subagents, code-puppy JSON agents) stay on the conversational path — init
prints the pointers instead of pretending.

### `baron validate [PATH]` (M1)

Validate `persona.yaml` / `manifest.yaml` — a single file, or every one
discovered under `PATH`. Schemas are declarative Python data
(`src/baron/schemas.py`) formalized from the prose specs in
`../skills/barony/references/`; the FROZEN 10-verb capability
vocabulary is embedded and drift-guarded by a test that re-parses
`capability-vocab.v1.md`.

Checks: YAML parse, missing/unknown fields, types, verbs outside the vocabulary,
allow/deny overlap (including `write_path` scope overlap), unfilled
`{{PLACEHOLDER}}` tokens.

**Template rule:** directory discovery skips files whose path contains a
template marker directory (`assets/collab-repo/` or `legacy/`) — emit-time
templates legitimately carry placeholders and often aren't valid YAML at all.
Fixture paths (`tests/examples/`) are validated but exempt from the placeholder
check only. An explicitly named file is always validated.

Exit 0 = no errors (warnings allowed) / 1 = errors. `--json` for machines.

```bash
baron validate tests/examples/tess/persona.yaml
baron validate . --json
```

### `baron status [--fetch] [--sla N] [--json]` (M2)

Run from a collab repo (or `--collab PATH`). Reads `manifest.yaml` — including
the optional `workspace.clones` / `workspace.worktrees_root` fields (see
`../skills/barony/references/manifest.schema.md`) — and
reports, with severity:

| Check | Severity | Meaning |
|---|---|---|
| `ahead` | red | commits never pushed to origin (stranded work) |
| `behind` | red | origin commits never pulled (stale canonical clone) |
| `unmerged-branch` | red | local branch not merged to origin default, with last-commit age |
| `handoff-overdue` | red | `status: open` handoff older than the SLA (default 14 days) |
| `dirty` | warn | uncommitted paths |
| `ledger-stale` | warn | newest F/D entry date older than the newest `docs/`/`src/` commit in the code repo (**heuristic**, labeled as such) |
| `wiki-stale` | warn | `wiki/status.md` `updated:` older than the newest finding entry |

Use `--fetch` to refresh each working copy's origin refs first — without it,
remote-side divergence (the `behind` class) is invisible. Exit 0 = green
(warnings allowed) / 1 = any red (CI-usable).

### `baron finding new` / `baron decision new` (M3)

```bash
baron finding new --title "Tracker-gated recall" --author carson
baron decision new --title "Adopt fps-aware segmentation" --author terrence --body-file body.md
```

Parses the index for the max ID (both `### F<N>` headings and `| F<N> |` table
rows), allocates the next, appends a house-style entry
(`### F<N> — <title> (<date>, <author>)`), commits, and pushes. **On push
rejection** (someone else claimed the number first): roll back, `pull --rebase`,
re-parse, renumber, retry — bounded (`--retries`, default 3). git's push
atomicity is the lock; there is no other store. `--no-push` for offline work.
Dates come from a single injectable clock (`src/baron/clock.py`).

### `baron handoff create|close|list` (M3)

```bash
baron handoff create --for tess --from rex --title "Review the seam" --priority high
baron handoff close _handoff/2026-07-22-review-the-seam.md --note "Done, see F9."
baron handoff list --open
```

`create` writes `_handoff/YYYY-MM-DD-<slug>.md` with the standard frontmatter
(`created` / `status: open` / `for` / `from` / `priority`). `close` flips
`status` to `done`, adds a `closed:` date and an optional blockquote note, then
`git mv`s the file to `_handoff/archive/YYYY/` — **archive, never delete**, with
history preserved. Status edits are textual so prose is never reflowed.

### `baron index` (M3)

Regenerates a marker-delimited summary block (`BEGIN/END BARON INDEX` HTML
comments) in `_handoff/README.md` (creating it if absent): open/done/archived
counts plus a table of open handoffs (file, for, from, age). Also verifies
finding/decision numbering: duplicates are errors (exit 1); gaps and
out-of-order headings are **report-only** warnings — baron never renumbers
history.

### `baron guard --persona-file <persona.yaml>` (M4)

Deterministic capability enforcement as a **Claude Code PreToolUse hook**
(ADR-004). Implements the documented hooks contract
(https://code.claude.com/docs/en/hooks — the canonical target of the old
docs.anthropic.com hooks URL): reads the hook JSON from stdin (`tool_name`,
`tool_input`, `cwd`), maps the call to the frozen v1 capability verbs, and
either stays silent (exit 0 — the normal permission flow applies) or blocks
(exit 2 with the reason on stderr, which the contract feeds to the model).
`--persona-file` may also come from env `BARON_PERSONA_FILE`.

What it decides:

- **Bash** — `git push` targeting the default branch → `push_main`;
  `--force`/`-f`/`--force-with-lease`/`+refspec` → `force_push`;
  `gh pr merge` → `merge_pr`; `git merge` while ON the default branch →
  `push_main`. Parsing is **conservative**: an undeterminable push target
  (e.g. bare `git push` outside a repo) is treated as the enforcement-relevant
  verb and denied for personas lacking it, with stderr naming the inference;
  personas holding the verb always pass. Non-git/gh commands pass — guard
  governs capability verbs, not general shell.
- **Edit / Write / NotebookEdit** — `_handoff/` is universally writable;
  `agents/<other-slug>/` needs `edit_other_personas` (a persona's own
  `agents/<slug>/` dir is its own surface); denied `write_path` scopes always
  block; otherwise `write_code` grants the write, and without it only the
  persona's declared `write_path` scopes remain.
- **Unknown tools** — pass (a capability gate, not an allowlist).

**Policy source (since v0.3.0):** guard's rule table — the command patterns and
file-op scoping semantics above, plus the conservative-deny ambiguity policy —
is NOT hardcoded: it lives in the versioned artifact
`src/baron/data/capability-rules.v1.yaml` (package data, loaded by
`src/baron/rules.py`; `rules_version: 1`). It is THE single source for
enforcement rules; the pydantic-ai adapter below consumes the same table, so
decisions are identical across runtimes. A missing/unsupported artifact fails
CLOSED. Prose contract:
`../skills/barony/references/capability-rules.md`.

**Fail-closed but not brick:** unreadable persona file / malformed stdin →
DENY with actionable stderr. Escape hatch: `BARON_GUARD_OVERRIDE=<reason>`
allows the call BUT appends timestamp/tool/target/reason to
`.baron/guard-override.log` — a **tracked** file (deliberately not gitignored:
overrides are visible in diffs); each override is expected to become a
`_handoff/`. Wire-up (`.claude/settings.json`, matcher
`Bash|Edit|Write|NotebookEdit`): the Claude adapter's HYDRATE.md step 3c emits
it. Without baron installed the hook fails non-blocking and denials degrade to
instructed — honest degradation, never a bricked session.

### `baron lock claim|release|list` (M5)

PR-as-lock (ADR-002 §3), replacing the race-prone markdown LOCK-commit
protocol — **the open PR is the lock**, the forge's PR list is the only state.

```bash
baron lock claim contracts/models.py --reason "tightening the stage protocol"
baron lock list
baron lock release contracts/models.py
```

`claim` creates branch `lock/<slug>` with one empty commit (via
`git commit-tree` — the local checkout is never touched; the empty commit is
load-bearing, GitHub refuses a PR whose head equals its base), opens a draft
PR titled `lock: <path>` labeled `lock:<path>` with the reason in the body,
and **refuses if an open lock PR for the path exists** (showing the holder).
`release` closes the lock PR and deletes the branch. `list` prints
path/holder/age/PR#. Requires `gh` (raises a clean `ForgeUnavailable`
otherwise); all forge calls go through the Forge Protocol, so tests run
against a fake. The CI side is the emitted
`.github/workflows/lock-guard.yml` template (bash + `gh`), which fails any
*other* PR touching a locked path.

### `baron worktree add|list|remove` (M6 tooling)

The branch-per-persona worktree topology (ADR-003 §2.7): one shared object
store, worktrees under the manifest's `workspace.worktrees_root`.

```bash
baron worktree add fern --collab .        # <worktrees_root>/fern on branch persona/fern
baron worktree add fern --repo ../code --root ../worktrees   # explicit paths
baron worktree list
baron worktree remove fern [--force]
```

`add` creates `<root>/<persona>` on branch `persona/<persona>` (created from
the default branch if missing; an existing branch is reused). Defaults resolve
from the manifest (`workspace.worktrees_root`, `repos[role=code]`); `--root` /
`--repo` override. `list` shows each worktree's branch with ahead/behind vs
the default branch. `remove` refuses while the worktree is dirty or its branch
holds unmerged commits unless `--force` — and NEVER deletes the
`persona/<persona>` branch (removing a working copy must not destroy history).
`baron status` sweeps worktrees like clones (each reports its checked-out
HEAD's divergence; the repo-wide branch sweep runs once, on the shared repo).
Converting an existing clone-per-persona workspace:
`../docs/worktree-migration.md`.

### `baron waiver add|list` + `.baron-waivers.yaml`

Deliberately-parked `baron status` reds, with mandatory expiry.

```bash
baron waiver add "clone:rex *" --reason "kept for the vNext experiment" \
  --handoff _handoff/2026-07-23-parked-branch.md --expires 2026-08-15
baron waiver list
```

`.baron-waivers.yaml` (collab root, human-legible, baron-managed via `waiver
add`) holds `{subject, reason, handoff, expires}` entries; `subject` is an
fnmatch pattern on the status SUBJECT column. A matching, unexpired waiver
downgrades a red to warn with `(waived: <reason>)` appended — parked work
stays visible, just not alarm-red. **Expiry keeps waivers honest:** an expired
waiver stops matching (the red resurfaces) and is itself reported as an
`expired-waiver` warn; malformed entries are reported, never silently dropped.

### `baron hydrate pydantic-ai --persona-file F [--out agent_setup.py]`

Emit a ready-to-edit bootstrap script hydrating one persona onto
**pydantic-ai** (the fourth runtime adapter,
`../skills/barony/assets/collab-repo/adapters/pydantic-ai/HYDRATE.md`).

```bash
baron hydrate pydantic-ai --persona-file agents/fern/persona.yaml --out agent_setup.py
```

The emitted script imports `baron.runtimes.pydantic_ai.build_agent` and
carries a model placeholder (`"test"` — pydantic-ai's offline TestModel —
until you pick a real model). Emission needs only baron; **running** it needs
the optional extra:

```bash
pip install 'barony[pydantic-ai]'   # pins pydantic-ai-harness>=0.10,<0.11
                                       #      + pydantic-ai-slim>=2.14.1,<3
```

`build_agent(persona_file, collab_root=None, model=...)` returns a live
`pydantic_ai.Agent`: instructions composed from the persona spec; harness
`FileSystem` scoped per write verbs (natively read-only when the persona holds
no write verb); harness `Shell` only when a shell-granting verb is allowed
(with test runners denied when `run_tests` is denied); and an in-process guard
capability (`before_tool_execute` + `ModelRetry` veto — the documented
interception seam) consuming the same `capability-rules.v1.yaml` as
`baron guard`, which makes the five guard-covered sub-tool denials natively
**enforced** on this runtime. Without the extra, importing
`baron.runtimes.pydantic_ai` raises a clean ImportError with these install
instructions. Verified against pydantic-ai-harness 0.10.0 +
pydantic-ai-slim 2.16.0 (2026-07-23).

## Forges

`src/baron/forge/` holds a small `Protocol` (`base.py`) with one built-in
implementation, GitHub via `gh` subprocess (`github.py`) — first consumed by
`baron lock` (M5: `create_branch`, label-aware `open_pr`/`list_open_prs`,
`close_pr`). Other forges are plugins discovered through the `baron.forges`
entry-point group; GitLab is backlog — design sketch in `../docs/BACKLOG.md`.

## Development

```bash
uv run --project cli pytest cli/tests    # from the repo root
```

The suite includes the capability-vocabulary drift guard, a synthetic divergent
git topology reproducing the 2026-07-22 triple-stranding incident classes, the
ledger push-rejection race test, subprocess-driven guard hook tests (synthetic
PreToolUse JSON on stdin), a recorded fake forge for the lock lifecycle, a real
two-persona worktree fixture, the waiver downgrade/expiry cases, the
capability-rules artifact tests (packaged + versioned, verb set ≡ the frozen
vocabulary, guard-consumes-the-data mutation test), and the pydantic-ai
adapter tests (offline TestModel/FunctionModel: capability omission, write
scoping, a scripted-and-vetoed `git push origin main`, the clean import-error
path), the `baron init` acceptance tests (layout + self-validation via the real
schemas + runtime kits + the non-empty-dir refusal), and the template drift
guard (`test_template_sync.py`: the vendored `src/baron/data/templates/` must
stay byte-identical to `skills/barony/assets/collab-repo/` — re-vendor with
`python cli/scripts/sync_templates.py`). The dev dependency group repeats the
pydantic-ai extra's pins so those tests run for real.
