# booping plugin — project guide

Claude Code plugin that grooms and executes plans across user projects. Plans and other run artifacts live in the per-project **vault** — `~/Claude/{project}/` by default, or a repo-local directory via the `.booping` marker's `vault_path:` key. Skills, agents, playbooks, templates, and config live in this repo.

One shipped skill (`/playbook`); everything procedural is a **playbook** it drives (`groom`, `develop`, `code-review`, `retro`, `learn`, `specs`, `project-manager`, `setup`, `migrate`, `makemigration`, `playbook-authoring`).

## Commands

- `just lint` / `just typecheck` / `just pytest` — ruff / basedpyright / pytest over both uv projects, `booping-python/` then `booping-tracker/`.
- `just snapshots` — diff committed playbook reports against a fresh hermetic render (writes nothing); a playbook in this repo's `vault/_playbooks/` joins once it commits a `_reports/output.md`, rendered against a stub-pinned temp copy of the vault. `just snapshots-accept [playbook]` — the **only** writer of the committed reports.
- `just mdcheck` — structural rule checks over the rendered reports. Needs the `mdcheck` binary: `cargo install markdown-checker --version 0.1.2 --locked` (the crate named `mdcheck` is unrelated). It is a user-facing runtime dependency too: groom checks every milestone file with it against `playbooks/groom/_schemas/milestone.yaml`.
- `just e2e [pytest args…]` — run both txtar contract corpora through pytest, passing any arguments straight to it (`just e2e -k create`, `just e2e --txtar-update`); `--txtar-update` rebaselines the selected cases. Cases run in parallel (pytest-xdist, `-n auto`); `--txtar-update` runs serially. A `-k` expression that selects in one corpus only leaves the other collecting nothing, which is not a failure.
- `just specs-check` — `bin/booping specs check` over the core tier, `specs/` (records shipped to every user), and this repo's project tier, the type directories in `vault/`; needs `mdcheck` too.
- `just ci` — everything CI runs, in order: `lint typecheck pytest snapshots mdcheck specs-check e2e`. Run before committing.
- `just eval|smoke|regress <playbook>/<step>` (or `all`) — promptfoo eval suites; `just suites` lists them. Runs on subscription auth (`claude -p`), never in CI; each run posts a sticky, advisory PR comment via `scripts/eval-pr-comment.sh` (`EVAL_PR=0` opts out) — no commit status, no merge gate.
- `just docs` / `just docs-serve` — build / preview the public docs site.
- `bin/booping <subcommand> --help` — the runtime CLI. Reach for these primitives before adding a command:
  - `create <config.path> <dest> --set k=v` — writes any config-defined file tree from Jinja templates, filled by `--set` vars, macros (time, session id, commit) and `next_id(prefix)` (the next free numbered name in the destination); `--stub-macro` pins the macros.
  - `query` — filters, sorts and projects markdown files by frontmatter (`--config <spec>` or `--glob`/`--where`/`--columns`); the same specs run at render time as the `| query` filter.
  - `set <file> k=v` — sets, appends (`--append`) or removes (`--remove`) frontmatter keys of any file, or keys of the `.booping` marker when the file is one.
  - `render` / `render-playbook` — render a template, or a playbook's composed procedure, with full project context.
  - `transition <file> <status>` / `state <file>` — move a file along the `states:` machine its path matches, hooks included (`--playbook`/`--state` narrow it); report its run's frontier.
  - `config <key>` — print a merged config value by dotted path. `debug-context` — dump the assembled context.
  - `session-stats` — mine active minutes, tokens and models from artefacts' stamped sessions.
  - `status` — the project status computed from the vault, short or per-section detail.
  - `run` (`groom`, `develop`, `code-review`, `ack`) — launch a recorded headless run, ack a finished one.
  - `plan` (`split`) — cut a one-file draft into milestone files; check milestones against the contract.
  - `specs` (`ls`, `next`, `check`, `scan`, `init`, `site`, `site-index`) — list, number, check and publish typed spec records of a tier; `scan` files an open item per mark and check finding.
- `bin/booping-tracker <verb> --help` — the task-tracker CLI: `states`, `show`, `comment`, `issue-create`, `issue-update`, `relate`, `sync`. Reference: [booping-tracker/README.md](booping-tracker/README.md).

## Layout

- `booping-python/` — uv Python project with the `booping` CLI. Source `src/booping/`, tests `tests/`, contract corpus `e2e/` — cases plus a `conftest.py` configuring the `pytest-txtar` plugin, which owns the runner and the case-format spec (https://github.com/A/pytest-txtar); `create` is verified there, not by the unit tests.
- `booping-tracker/` — second uv project with the `booping-tracker` CLI, holding every tracker concern (and `httpx`) out of `booping-python`. Source `src/booping_tracker/` — `cli.py`, `config.py`, `facade.py` (the operation set) and one module per provider under `providers/` — tests `tests/`, contract corpus `e2e/` with its own `conftest.py` and `README.md`. Config is not re-implemented: it shells to `bin/booping config core.tracker`.
- `bin/booping`, `bin/booping-tracker` — the two product entry points: shell wrappers exec'ing `uv run --project {booping-python|booping-tracker} … "$@"`.
- `scripts/` — dev tooling behind `just`: `snapshots.py`, `mdcheck.py` (uv inline Python), and the eval harness (`eval-*.sh`, `report-*.jq`). Not shipped to users.
- `src/ci/` — shipped CI templates and their helper script (`gitlab/`: the specs-site and specs-index `pages` jobs, `list-specs-projects.py`), consumed by URL, not by the CLI.
- `src/config.yaml` — runtime config, single source of truth for structured data (macros, query specs, template trees, task types, sprint scale, per-playbook agents). Heavily commented — read it for key semantics.
- `src/templates/` — runtime skill/agent templates + `_partials/`, rendered at skill-load time. Edits are **live**, no rebuild.
- `hooks/` — plugin hooks declared in `.claude-plugin/plugin.json`: `session-start.sh` (`SessionStart`) walks up for `.booping` and injects the `src/templates/hooks/session_start.md.j2` render, silent outside a project and on any failure.
- `skills/<name>/SKILL.md`, `agents/<name>.md` — hand-authored thin shells: frontmatter (including `effort:`) plus one `!`booping render src/templates/…`` line that pulls in the body at load time. Agents use the same shape with a STOP line telling the agent to run the render; `agents/adr-scorer.md` allows only its own render command (`tools: Bash(booping render …)`), and since a Bash pattern exposes Bash without restricting the command, its isolation — judge from the prompt alone — is the core rule targeted at `agent:adr-scorer`.
- `src/specs/` — the shipped specs format: `README.md` (the guide) and `schemas/<type>.yaml` (one mdcheck rule file per shipped record type). A tier overlays its own `{tier root}/_schemas/<type>.yaml` (override or add, no cascade) and may carry `tags.yaml`; no translated `schemas-{lang}/` sets — structural headings stay English.
- `playbooks/<name>/` — core playbooks. Also: `_partials/` (shared fragments), `_scripts/` (shared hook scripts), `_lib/` (eval harness), `_fixtures/vault/` (hermetic render fixture). Each playbook commits its rendered report at `playbooks/<name>/_reports/output.md`.
- `specs/` — the core specs tier (`core.specs.tiers.core.path`): rules shipped with the plugin whose `targets` steer core playbooks for every user. Their `## Follows from` links point into `vault/value/`.
- `playbooks/*/_specs/` — playbook-authoring run artefacts and design history; intentionally stale, not a spec of current behaviour.
- `migrations/<NNN>_<slug>/migration.md` — plugin-shipped vault migrations. Frontmatter `id` is the authority; a vault's applied watermark is the `.booping` marker's `latest_migration` key, and every render surface gates on it — with `core.auto_migrate` on (the default) the notice orders the agent to run `migrate` unattended and resume, off it stops for the user. Authoring primitives: query specs with `root: core` scan plugin-shipped files, numeric `--where` ordering (`gt`/`lt`), `{{ booping.latest_migration }}` in templates, marker writes via `booping set .booping`.
- `docs/` — hand-authored plugin-internal fragments, lazy-loaded by skills via `${CLAUDE_PLUGIN_ROOT}/docs/<name>.md` links. No build step.
- `documentation/` — public docs site source (MkDocs → gh-pages on push to `master`). Hand-authored.

## Rendering pipeline

One stage, at **runtime** (skill load): a thin shell's `!`booping render src/templates/…`` line renders the body with full project context — config merge, vault, rules, playbooks.

## Config

- Three-tier deep merge, **core → global → project**: `src/config.yaml` ← `${XDG_CONFIG_HOME:-~/.config}/booping/config.yaml` ← `{vault}/config.yaml`. Later tiers win; lists replace wholesale. No schema gate and no tier restriction — any key loads in any tier.
- Top level is exactly `home_dir` + `core`. **Placement rule** under `core`: a key one playbook owns lives at `core.{name}_playbook`; a shared key sits directly under `core`. A user's own playbook namespace copies this shape.
- Any mapping in the merged config can be a **query spec** (`booping query --config <dotted.path>`, `| query` filter) or a **template tree** (`booping create <dotted.path> <dest>`) — the value at the dotted path is the spec/tree, no wrapper key. Specs live beside their consumer (`core.{name}_playbook.queries.<id>`), never in a central registry.
- `core.plans` — plan shape shared by groom and develop: `glob` (plan discovery) and `milestones` (`glob` relative to the plan dir, matching the milestone file inside each milestone directory and never a sidecar beside it, + `table_columns` for the generated table). Never restate either literal in prose.
- `core.tracker` — task-tracker surface, directly under `core` because several playbooks mirror outward through it: `driver` (`cli` is the one shipped driver, `--driver` overrides per invocation, an unknown name is a hard error) plus one settings block per driver. Secrets are named, never carried: `api_key_env` holds the *name* of the environment variable the token lives in — **no config value anywhere is env-interpolated**, in any tier.
- `core.status` — the status report's query specs (`active_plans`, `unreviewed_plans`), directly under `core` because the CLI and the project-manager share it.
- `core.corrections` / `core.agent_decisions` — one vault file per entry: a `template` tree keyed `{{ name }}.md` (the caller passes `--set name=<ts>-<step>`, macros stamp `session` and `logged_at`) and an `open` query spec (`state: open`). Shared keys, directly under `core`, because several playbooks write them and retro / the project-manager read them.
- `core.run` — the launch shape of `booping run groom|develop|code-review`: `command` (the child's argv, `--session-id` and the prompt appended), `env` (merged over the inherited environment), `prompt` (`{playbook}`, `{target}` filled), `preamble` (appended under `--autonomous`) and `dir` (the records' vault directory). A shared key, directly under `core`; like every config value, none of it is env-interpolated.
- `core.specs` — the specs surface, directly under `core` because `booping specs` and rule injection share it: `format` (plugin-root-relative dir of the guide + schemas; types are per tier — the shipped stems overlaid by the tier's `_schemas/` stems) and `tiers.{core,global,project}` with `path` (anchored at plugin root / `home_dir` / repo) and `namespace` (`booping`; global `null` → `$USER`; project `path: null` → the vault root, its type directories beside `plans/`; `specs` with no vault), and `open_items` — the item store: `dir`, the item `template` keyed by `next_id('OI-')`, the `index` query spec (`booping query --config core.specs.open_items.index`).
- `core.project_manager_playbook` — the project-manager's `agents` (the worker for tiny fixes and review fixes, the researcher), shaped like `core.develop_playbook.agents`, and `batch` — the item `sizes` with their SP, the `direct` sizes that skip grooming, the `max_sp` cap the `work` step packs a batch under.
- `core.macros.<name>` — an argv list, or a mapping with `command:` plus `cwd: repo|vault` scoping; rendered bodies call them via the `macro()` global; a non-zero exit is a render error, so `session` prints an empty string outside a Claude Code session; `--stub-macro` (or a vault `macro_stubs:` mapping) pins them for reproducible renders.
- Full reference: [documentation/project_config.md](documentation/project_config.md).

## Playbooks

Multi-step guided procedures discovered from three roots — core `playbooks/`, global `<home_dir>/_playbooks/`, local `{vault}/_playbooks/` — names unique across roots. Each dir: `playbook.md` (identity frontmatter + preamble body), optional `playbook.yaml` (`graph:` + optional `state:`/`states:`), and one directory per step holding `prompt.md`.

- `graph:` defines order and parallelism via topological waves; a mapping node is a one-level **subgraph**. Parallel co-members must be `detached:`.
- Delegation per step via the single `detached:` frontmatter field: absent → runner-performed; `<model>:<effort>` → generic sub-agent; other string → named sub-agent.
- `jinja: true` opts all bodies into Jinja through the full context env, each with a loader chain body → step dir → playbook dir → discovery roots (local → global → core) → `src/templates/`.
- **Run state** is opt-in via `states:` (named state machines: `artifact`, `initial`, `statuses`, transitions with `when`/`gates`/`hooks`). `booping transition <file> <status>` is the **only** writer of run state, the machine inferred from the file's path; its printed mutation report is authoritative — never re-read to verify. `booping state <file>` is the read-only frontier report used to resume; both need `--playbook` when the machine declares no `artifact:`. Hooks: `set` and `script <name> [args…]` (argv passed through; playbook `_scripts/` first, then the discovery roots' `_scripts/`, most specific wins).
- **Tracker mirroring**: `playbooks/_scripts/tracker-sync` (push the artifact's status) and `tracker-comment <workdir-relative-path>` (post a file as a comment) are `script` hooks on groom's edges. Both absorb the tracker's exit code and warn on stderr — a hook failing after `status:` is written would abort the transition with the vault advanced and the tracker behind; `booping-tracker sync --artifact …` by hand is the reconcile.
- **Rules**: `status: accepted` spec rules with `targets:` (`{playbook}`, `{playbook}/{step}`, `agent:{id}`, `skill:{name}`) and `brief:`, read from the three `core.specs` tiers and injected as brief plus link in a `## Rules` section by `render-playbook` and by internal agent and skill bodies; an untargeted rule injects nowhere; `--no-rules` opts out of render-playbook's. See [documentation/playbook.md#rules](documentation/playbook.md#rules).
- Graph/render problems surface as in-band `**STOP — tell the user:**` / `**Note — tell the user:**` notices at exit 0, never crashes.
- The driving protocol lives in `src/templates/_partials/_playbook_driving.j2`. Full mechanics: [documentation/playbook.md](documentation/playbook.md).

## Lifecycle

Each playbook owns its own status vocabulary in its `states:` block — there is no shared lifecycle, and those blocks are the authoritative sets.

- **Plan track** (`groom` + `develop`): the plan is a directory `{vault}/plans/{slug}/` whose `index.md` is both run artifact and plan document; groom ends at `ready-for-dev`, develop at `done` | `fail`, both also `cancelled`. Shape is the `core.plans.glob` config key (`plans/*/index.md`).
- **Parked groom runs**: `awaiting-clarification` holds a groom run that raised a question it cannot answer itself — entered from `framing` and from `drafting`, each entering edge stamping `return_to` with its own source and each return edge clearing it, so the mapping is static per edge rather than computed. The questions are prose in the `clarifications.md` sidecar beside `index.md`, written by hand to `docs/clarifications.md` (one H2 per question, `open`/`answered`); a later invocation resumes from it, and `tracker-comment` posts it verbatim. `tracker_provider` / `tracker_request` / `tracker_issue` on `index.md` record the mirror, hook- and CLI-written only.
- **Corrections**: a user's correction during a run is one file under `{vault}/corrections/`, written by `booping create core.corrections.template` with the user's words verbatim (step, did, said, why, `state: open`, session, time, artefact link); core rule `booping:rule/0031` tells the driver when. Retro's `prepare` renders the `core.corrections.open` query and sets each handled entry `state=handled` with `booping set`.
- **Autonomy** is prose, not config: a run is autonomous when the user told the session so or `booping run … --autonomous` launched it (`core.run.preamble`, opening `AUTONOMOUS RUN`), otherwise supervised. Autonomous auto-confirms review gates and settles step questions; a STOP notice and a question a step marks always-asked (migrate's uncommitted-work question) still stop.
- **Agent decisions**: every gate and question an autonomous run settled itself is one file under `{vault}/agent-decisions/`, written by `booping create core.agent_decisions.template` (step, asked, decision, why, `state: open`, session, time, artefact link); the project-manager lists `core.agent_decisions.open` at its merge gate and sets each `state=reviewed`.
- **Milestone directories**: each milestone is a directory `milestones/M{nn}-{kebab}/` under the plan directory, holding the milestone file named after it (`core.plans.milestones.glob`, `milestones/*/M*.md`) — frontmatter `id`/`title`/`sp`/`status`/`plan` plus tasks, DoD and Verify — and any sidecar the sprint writes beside it, currently `feedback.md`, the runner's findings on a rejected attempt and the only place attempts are counted. The milestone file is the contract handed to a worker by path — `index.md` is context only, its `## Milestones` table (`core.plans.milestones.table_columns`) and the plan's `sp` are generated. Milestone `status:` is develop's per-instance run state (`pending` → `in-progress` → `done`, `blocked` off in-progress), instance `M{nn}-{kebab}`; every edge runs the `refresh-milestone-table` hook. The worker runs its milestone's `## Verify` and leaves the work uncommitted; the runner validates the working tree against the DoD, makes the repo commit (scoped staging, never `git add -A`), flips the checkboxes and takes the transitions — no worker of any harness touches git history.
- **Retro track** (`retro` + `learn`): artifact is a standalone `{vault}/retrospectives/{slug}.md` (`awaiting-retro` → `awaiting-learning` → `done`); plans stay at `done` throughout. Addressed with `--target` since the machines declare no `artifact:`.
- **Code-review track** (`code-review`): artifact is `{vault}/codereviews/{plan-dirname}/{ts}.md` (ad-hoc scopes: `codereviews/{target-slug}/{ts}.md`, `plan: null`), machine `in-agent-review` → `human-review` → `done`; plans stay at `done`. Addressed with `--target` since the machine declares no `artifact:`.
- The tracks join through **plan frontmatter, not status**: `retro:` is null until covered, so `{status: done, retro: null}` is the retro queue; `code_reviews:` is a list of every review that closed on the plan — history, not a queue flag — so the review queue is every `{status: done}` plan, re-review included.
- **Open-items track** (`specs`): artifact is one `{vault}/open-items/OI-NNNN.md` per item — a user request (`source: request`/`backlog`, verbatim text, no direction) or a spec gap — under the `open_item` machine in `playbooks/specs/playbook.yaml` — a `standalone: true` state, exempt from the subgraph and orphan checks, created by `booping create core.specs.open_items.template`, sized by `booping set … size=` (checked by `playbooks/specs/_schemas/open_item.yaml`), moved by `booping transition`, never by a `specs` run; `open → done` closes an item settled without a plan. An item that asks the user a question goes `open → clarify` and back once answered, the question and answer prose in its body, moved by `booping transition {vault}/open-items/OI-NNNN.md <to>`; `booping status` lists `clarify` items as waiting on the user. The plan link lives only on the item (`plan:`), never on the plan. Groom intake links the items a request names (`booping set {item} plan=…`, then `booping transition {item} planned`); the shared `open-items-follow-plan` script hook, on develop's terminal edges and groom's cancel edges, closes or reopens a plan's linked items and absorbs failure the way the tracker hooks do — `booping transition {item} done|open` by hand is the reconcile.
- **Run records**: `booping run groom|develop|code-review` writes `{vault}/runs/{id}.md` before launching the child (`running`, the wrapper's `pid`) and its outcome when the child ends (`finished` | `failed`, `exit_code`); it is the only writer, no state machine. `lost` is computed at read time — a `running` record whose pid names no live process. `booping run ack` stamps `acked_at`, hiding a run from `booping status` without deleting it.
- Vault commits are each playbook's own business, via `script` hooks on its edges.

## Vault (out of framework scope, authored per project)

`plans/`, `retrospectives/`, `codereviews/`, `open-items/` (created on the first item), `runs/` (run records plus their gitignored logs, written only by `booping run`), `corrections/` and `agent-decisions/` (one file per entry, written by `booping create`), `_playbooks/` (user playbooks), `plan_templates/`, `review_templates/`, `notes/` (gitignored here: the one place for private notes and references — other projects' data, local paths, raw experiment inputs; never linked from a tracked record), `sprints.md` (Obsidian Bases fence, seeded once, written by nothing), `.booping.log` (CLI log at the vault root, gitignored there). Obsidian-ready: markdown + YAML frontmatter only. See [documentation/vault.md](documentation/vault.md). Everything benchmark — registry, runbook, method, `bench-score`, mutations, fixtures, run artifacts, and the scorecard table in its README — lives in the separate [booping-benchmarks](https://github.com/A/booping-benchmarks) repo; only the `model-benchmark` playbook that drives a run stays in this vault's `_playbooks/`.

This repo's own vault is checked in at `vault/` without its run history — no plans, open items, reviews, retros, corrections, agent decisions or run records; only the spec records, `_playbooks/`, `docs/`, `config.yaml`, `tags.yaml` and `sprints.md`. Its type directories (`vault/value/`, `vault/rule/`, …) are booping's project tier: values, rules, ADRs, stories, ideas, references and runbooks as typed records about developing booping (format: `src/specs/README.md`, check: `just specs-check`) — the authority on why booping is the way it is; read the values and rules before a design change. The core tier, `specs/` at the repo root, holds only the rules shipped to and injected into every user's runs; a rule that concerns booping's own development stays in `vault/rule/`. The `docs` playbook reads and keeps current the value records, reference/0001–0003 and the story records, its feature index rendered from the stories.

## Principles

- **Minimum useful context**: a skill or step body carries only what it needs; lazy-link route-specific detail via `docs/` instead of inlining.
- **Less prose, less drift**: cut motivation, restated context, and preambles. The schema is the source of truth; prose decays.
- **Schema over prose**: structured data lives in `src/config.yaml` and is rendered from there — never restated in body prose.
- **Reference the source, never the literal**: dynamic values (statuses, queues, agent ids) render from config/query specs.
- **Wide-domain**: skills and playbooks stay stack-agnostic. Project specifics live in the project specs tier (rule records) and the attached repo's own `CLAUDE.md` — the only two destinations learn writes.
- **Agent wiring**: the driver owns all vault reads/writes, run state and every commit; worker agents edit code — the attached repo's, or code outside it once the user confirms that for the plan — never the vault or git history. Project-local shaping of an agent or a skill is a targeted rule (`agent:{id}`, `skill:{name}`), not a separate extension file. External global agents at `~/.claude/agents/` wire in via config — see [documentation/integrating-external-agents.md](documentation/integrating-external-agents.md).

## Editing conventions

- `src/templates/**` edits are live — no rebuild step anywhere in the repo.
- Playbook source edits (manifests, step `prompt.md`, shared partials) drift the committed reports. Loop: edit → `just snapshots` (read the diff) → `just snapshots-accept` → commit source and report together.
- Eval suites sit beside their step (`tests.yaml` + `promptfooconfig.yaml` + `_fixtures/`); two tiers per fixture, `smoke` (deterministic) and `regress` (llm-rubric judges). Shared asserts, graders and rubric prompts live in `playbooks/_lib/`. An agent's own suite sits at `playbooks/_agents/{agent}/` (`just smoke _agents/{agent}`), never under `agents/`, where Claude Code would load its fixture `.md` files as agents.
- No comments that restate code — only WHY for non-obvious bits.
- Conventional commits with scope: `feat(booping): …`, `fix(groom): …`.
- README's Statuses section is hand-maintained narrative over the per-playbook `states:` blocks — revisit it when one changes.
- New procedures should be playbooks, not skills; a skill is only for a surface `/playbook` cannot drive. Reference for the rare new skill: `src/templates/skills/playbook.md.j2` + its thin shell at `skills/playbook/SKILL.md`.
