AGENTS.md · diff

git:20261010.e13e00d to git:20261013.9d97e67

49 added, 109 removed. Audit A to A.

- # radin — Agent Reference
+ # radin Agent Reference
- radin be Claude Code plugin: skills, CLI, install glue. Bash, plus two C file (TUI and JSON helper). macOS and Linux.
+ radin is a Claude Code plugin made of skills, a CLI, and install glue. It is written in Bash, plus two C files (the TUI and the JSON helper), and it runs on macOS and Linux.
## Hard rules
- - **bash 3.2** (macOS ship it): no associative array, no `mapfile`, no
- `${var,,}`. No `uname` branch — resolve through `command -v` /
- `brew shellenv`, branch only where tool differ (`md5` vs `md5sum`).
- Call out every script edit explicitly.
- - **`~/.claude` not ours.** `install.sh` only `cp` radin own named
- files and `mkdir -p`. Never `rm`, never wildcard-delete, never overwrite
- file radin not ship. Two exception: the
- `<!-- radin:begin -->`…`<!-- radin:end -->` block in `~/.claude/CLAUDE.md`,
- and `lib/radin-cbm-config.sh` snapshot/restore of `settings.json` +
- `~/.claude.json`. Call out every `install.sh` edit explicitly.
- - **Companion install be advisory.** Failure warn, run continue.
- Not optional. One question only: which package manager install them
- (`brew`/`mise`/`curl`), so radin add no manager user not already run.
+ - **bash 3.2** (macOS ships it): Do not use associative arrays, `mapfile`, or `${var,,}`. Do not add a `uname` branch. Resolve tools through `command -v` or `brew shellenv`, and branch only where a tool differs (`md5` versus `md5sum`). Call out every script edit explicitly.
+ - **`~/.claude` is not ours.** `install.sh` runs `cp` only on radin's own named files, and it runs `mkdir -p`. It never runs `rm`, never deletes with a wildcard, and never overwrites a file that radin does not ship. There are three exceptions. The first is the `<!-- radin:begin -->`…`<!-- radin:end -->` block in `~/.claude/CLAUDE.md`. The second is the `lib/radin-cbm-config.sh` snapshot and restore of `settings.json` and `~/.claude.json`. The third is `skills/unslop/SKILL.md` and `skills/technical-writing/SKILL.md`. `install.sh` fetches those two skills from upstream, and it overwrites them only when `manifest.json` records them as radin's own. Call out every `install.sh` edit explicitly.
+ - **Companion install is advisory.** When an install fails, it prints a warning, and the run continues. This behavior is not optional. The installer asks one question: which package manager installs the companions (`brew`, `mise`, or `curl`). radin adds no package manager that the user has not already run.
+ ## Rule enforcers
+
+ | Rule | Enforcer | Gap |
+ | --- | --- | --- |
+ | bash 3.2 | `tests/bash-compat.bats` (banned constructs) | Grep only; no 3.2 interpreter run. |
+ | `~/.claude` ownership | `tests/install.bats` ("never overwrites a skill radin did not install"), `tests/bash-compat.bats` (`rm` allowlist) | Foreign content in the `CLAUDE.md` block and settings restore is untested. `install.sh` removes `~/.claude/skills/thermo-nuclear`, a known exception. |
+ | Companion install is advisory | `tests/install.bats` ("a failing companion tool and a missing claude CLI both warn, and the install completes") | None. |
+ | One owner per delegation | `tests/skill-names.bats` (every name resolves) | Nothing for "one file per delegation" and graph tool names; review only. |
+
+ A rule with no enforcer is a repeat risk, so a new hard rule lands with its row.
+
## Where to edit what
| Change | Edit |
| --- | --- |
- | A skill's behavior | `skills/<name>/SKILL.md` — truth source, never installed copy |
- | Anything deterministic (install, update, doctor, uninstall, backlog, state, TUI) | a `bin/radin` subcommand in `lib/`, never skill |
- | Backlog loop `radin-execute` and `radin-implement` share | `lib/radin-run.md`; each `SKILL.md` keep only its no-plan rule |
- | Rare-need part of `radin-execute` | `lib/radin-execute-*.md`, read on demand |
- | Sub-agent model, how CLI invoked | none — write token, `install.sh` resolve it ([how](docs/architecture.md#install-time-substitution)) |
+ | A skill's behavior | `skills/<name>/SKILL.md`. It is the source of truth, never the installed copy. |
+ | Anything deterministic (install, update, doctor, uninstall, backlog, state, TUI) | A `bin/radin` subcommand in `lib/`, never a skill. |
+ | Backlog loop `radin-execute` and `radin-implement` share | `lib/radin-run.md`. Each `SKILL.md` keeps only its no-plan rule. |
+ | Rare-need part of `radin-execute` | `lib/radin-execute-*.md`, read on demand. |
+ | Sub-agent model, how CLI invoked | Nothing. Write a token, and `install.sh` resolves it ([how](docs/architecture.md#install-time-substitution)). |
- `install.sh` go one way, repo to `~/.claude`. New skill need
- `cp -r` line there, `lib/radin-doctor.sh` entry, README row and
- `docs/architecture.md` mention, or it ship nowhere.
+ `install.sh` goes one way, from the repo to `~/.claude`. A new skill needs a `cp -r` entry there, an entry in `lib/radin-doctor.sh`, a README row, and a `docs/architecture.md` layout entry. `tests/skill-names.bats` fails when one is missing.
- New companion that ship own stats or gain command need bullet in
- `skills/radin-stats/SKILL.md`, or roundup never see it.
+ A new companion that ships its own stats or a gain command needs a bullet in `skills/radin-stats/SKILL.md`. Without that bullet, the roundup never sees it.
## Storage
- `<repo-root>/.claude/.radin/` (outside repo, `$PWD`): `backlog/`
- (`index.jsonl` + `tasks/<id>.md`), `state/`, `plans/`, `reviews/`. Format in
- [docs/domain-models.md](docs/domain-models.md). Only thing radin write into
- consumer repo, and radin never touch their `.gitignore`.
+ The state directory is `<repo-root>/.claude/.radin/`. It sits outside the repo, under `$PWD`. It holds `backlog/` (`index.jsonl` + `tasks/<id>.md`), `state/`, `plans/`, and `reviews/`. The format is described in [docs/domain-models.md](docs/domain-models.md). It is the only thing radin writes into a consumer repo, and radin never touches that repo's `.gitignore`.
- Read and write it only through CLI (`radin backlog …`, `radin state …`).
- Nothing parse `index.jsonl` or `completed.json`, or grab task by line
- number. Data view need but no subcommand give mean add one.
+ Read and write this directory only through the CLI (`radin backlog …`, `radin state …`). No code parses `index.jsonl` or `completed.json`, and no code grabs a task by line number. If a data view is needed and no subcommand provides it, add a subcommand.
## Skills, agents, sub-agents
- Every entry point be skill, because it run in user thread and can
- talk to them. Sub-agent cannot: no prose to user, no `AskUserQuestion`,
- no background-task notification — all three look like hang. Sub-agent be
- leaf worker skill dispatch, so before point prompt at skill check
- it ask user nothing and spawn no agent
- ([constraints](docs/technical-constraints.md)). Every prompt name companion
- flat, no `command -v` gate and no escape clause: install be all-or-nothing,
- so missing tool fail loud and that failure be skip. Only guard stay: skill
- that ask human anything — sub-agent drop it, never wait on it.
+ Every entry point is a skill, because a skill runs in the user's thread and can talk to the user. A sub-agent cannot. It has no prose to the user, no `AskUserQuestion`, and no background-task notification. All three look like a hang. A sub-agent is a leaf worker that a skill dispatches. Before a prompt points at a skill, check that the skill asks the user nothing and spawns no agent ([constraints](docs/technical-constraints.md)). Every prompt names its companion directly, with no `command -v` gate and no escape clause. The install is all-or-nothing, so a missing tool fails loudly, and that failure counts as a skip. The only guard that stays is a skill that asks a human anything. A sub-agent drops that guard and never waits on it.
+ ## Autonomy
+
+ Routers and sub-agents proceed on reversible work without asking. Reversible work covers code edits, notes, task splits and backlog entries, anything that `git revert` or deleting a file radin wrote can undo. Irreversible work pauses: force-push, deploy, data deletion and external messages. A skill in the user's thread asks the user before it acts. A sub-agent cannot ask, so it stops and reports `STATUS: BLOCKED (DECISION)` with the question, the options and its recommendation. The human owns product direction, so a sub-agent that meets a product choice reports it rather than picking one. When an agent is asked to add scope, it replies with its real judgment, and "no" is an acceptable answer. Agreement the agent does not hold is not a reply.
+
## Writing prose for agents
- - **No-op test.** Sentence that not change what model do versus its default
- pay load and say nothing. Delete whole sentence, never trim word out of it.
- Test disputed: measure it
- ([how](CONTRIBUTING.md#measure-a-prose-edit)), not debate it.
- - **Positive over prohibition.** Ban drag banned behaviour into context and
- half-read as instruction to do it. State target behaviour. Keep prohibition
- only as hard guardrail with no positive phrasing, then pair it with target.
+ - **No-op test.** A sentence that does not change what the model does, compared with its default, costs payload and says nothing. Delete the whole sentence, and never trim a single word out of it. The test is disputed, so measure it ([how](CONTRIBUTING.md#measure-a-prose-edit)) instead of debating it.
+ - **Positive over prohibition.** Banned behavior dragged into context gets half-read as an instruction to do it. State the target behavior instead. Keep a prohibition only as a hard guardrail that has no positive phrasing, and then pair it with the target.
- Rest live elsewhere: `/mattpocock-skills:writing-for-agents` full rule set
- (leading word, completion criterion, progressive disclosure),
- `STE` output style sentence craft, `no-ai-slop` human-facing prose,
- [one rule, one file](docs/architecture.md#one-rule-one-file) where rule live.
+ The rest lives elsewhere. `/mattpocock-skills:writing-for-agents` holds the full rule set (leading word, completion criterion, progressive disclosure). The `STE` output style covers sentence craft. `unslop` covers human-facing prose, and `technical-writing` covers docs, READMEs, and PR and commit text. [One rule, one file](docs/architecture.md#one-rule-one-file) says where each rule lives.
## Delegation: one owner per job
- If shipped tool do job, name it; never write radin own version. Each
- delegation named in exactly one file — second copy drift silent, and
- [test pin every name](docs/architecture.md#delegation-is-pinned-by-a-test).
+ If a shipped tool does a job, name that tool. Never write a radin version of it. Each delegation is named in exactly one file, because a second copy drifts silently, and [a test pins every name](docs/architecture.md#delegation-is-pinned-by-a-test).
| Job | Owner |
| --- | --- |
| Settle a decision · API facts · module boundaries · diagnose a failure | `/mattpocock-skills:grilling` · `:research` · `:codebase-design` · `:diagnosing-bugs` |
| Minimal change · over-engineering · debt ledger | `/ponytail:ponytail` · `-review`/`-audit` · `-debt` |
| Maintainability review · commit message | `/thermo-nuclear` · `/caveman:caveman-commit` |
| Implementation discipline | `/caveman:surgical-patch` (fix), `safe-refactor`, `lean-build` (feat) |
| Code structure questions | codebase-memory-mcp's MCP tools |
| Output compression · structural search/diff | `rtk` · `headroom sg`/`diff`/`loc` |
| Agent-doc writing rules | `/mattpocock-skills:writing-for-agents` |
- Graph tool name live in one file only and must exist in upstream table —
- wrong one cost failed call in every sub-agent
- ([which files, and the pointer rule](docs/architecture.md#code-graph-wiring-codebase-memory-mcp)).
+ The graph tool name lives in one file only, and it must exist in the upstream table. A wrong name causes a failed call in every sub-agent ([which files, and the pointer rule](docs/architecture.md#code-graph-wiring-codebase-memory-mcp)).
## Decisions that look like bugs
- - **No per-task verification in `radin-execute`**, and `FAILED` task get
- Debug prompt once per session ([why](docs/architecture.md#verification-in-radin-execute)).
- - **What sub-agent learn stay on its task** — a `**Fact:**` or
- `**Root cause:**` line, long form in `state/facts/<id>.md`. No shared note
- file, no cross-task memory.
- - **`radin-execute` worktree/branch answer never reach prompt**;
- `radin-state.sh prepare` be only place they become git command. Model
- handed them eventually override a `no`.
- - **`lib/radin-cbm-config.sh` snapshot before upstream write**, because that
- installer replace whole hook array instead of merge; drop it and
- machine lose hooks silent ([limits](docs/technical-constraints.md)).
- - **Two C file only: TUI (`lib/radin-tui.c`) and JSON helper
- (`lib/radin-cbm-json.c`).** TUI draw and dispatch keys only — raw ANSI,
- `termios`, every mutation shell out to `radin-backlog.sh`. Helper hold every
- JSON rule `radin repair` and `radin hooks mcp` need; those script
- keep none, and radin need no python of own. `install.sh` build
- both with `cc`, advisory: no compiler mean no TUI, no
- `radin repair`, and `radin hooks mcp` print entry to paste by hand,
- rest still install
- ([rules](docs/architecture.md#the-human-tui)).
- - **`order` move dependency UP, never dependent down.** Human priority
- ranking survive as far as dep graph allow; Kahn with priority tie-break
- discard more of it.
- - **TUI load `backlog list --order created`**, so mutation reorder nothing and
- row stay put. `Shift-A`/`Shift-P`/`Shift-C`/`Shift-T` be only reorder,
- session-only, active one in header. CLI default stay `priority` — that be
- ordering contract `radin-prioritization.md` read.
- - **No batching several tasks into one sub-agent.** One sub-agent take one
- task, so each task return one `STATUS` line and a failure stay in its own
- task. Leaf be cold, so file-locality ordering save no token.
- - **No planning every task upfront as one wave.** A wave plan against a tree
- earlier commit then change. Plan-first JIT planning be deliberate
- anti-drift ([why](docs/architecture.md#verification-in-radin-execute)).
- - **No arena/judge at execution level.** N worktree, N full implementation,
- judge read N diff; break router low-resolution rule. At planning it stay
- open opt-in idea, tracked in own backlog entry and gated on review origin
- rollup.
- - **No model escalation on retry.** All four major role (planning, execution,
- review, debug) default sonnet, so escalate on retry change nothing by
- default.
+ - **No router-side per-task verification in `radin-execute`.** The leaf verifies and reviews its own staged diff before it commits. A `FAILED` task gets a Debug prompt once per session ([why](docs/architecture.md#verification-in-radin-execute)).
+ - **What a sub-agent learns stays on its task.** It records a `**Fact:**` or `**Root cause:**` line, and the long form goes in `state/facts/<id>.md`. There is no shared note file and no cross-task memory.
+ - **The `radin-execute` worktree and branch answers never reach a prompt.** Only `radin-state.sh prepare` turns those answers into git commands. A model that receives them can eventually override a `no`.
+ - **`lib/radin-cbm-config.sh` takes a snapshot before the upstream write.** The upstream installer replaces the whole hook array instead of merging into it. Without the snapshot, a machine loses its hooks silently ([limits](docs/technical-constraints.md)).
+ - **Two C files only: the TUI (`lib/radin-tui.c`) and the JSON helper (`lib/radin-cbm-json.c`).** The TUI draws and dispatches keys only. It uses raw ANSI and `termios`, and every mutation shells out to `radin-backlog.sh`. The helper holds every JSON rule that `radin repair` and `radin hooks mcp` need. Those scripts keep none of their own, and radin needs no Python of its own. `install.sh` builds both with `cc`, and that build is advisory. Without a compiler, there is no TUI and no `radin repair`, and `radin hooks mcp` prints an entry for the user to paste by hand. The rest still installs ([rules](docs/architecture.md#the-human-tui)).
+ - **`order` moves a dependency up, never a dependent down.** The human priority ranking survives as far as the dependency graph allows. Kahn's algorithm with a priority tie-break discards more of it.
+ - **The TUI loads `backlog list --order created`,** so a mutation reorders nothing and each row stays put. `Shift-A`, `Shift-P`, `Shift-C`, and `Shift-T` are the only reorder keys. They are session-only, and the active one appears in the header. The CLI default stays `priority`. That default is the ordering contract that `radin-prioritization.md` reads.
+ - **No batching of several tasks into one sub-agent.** One sub-agent takes one task. Each task returns one `STATUS` line, and a failure stays inside its own task. A leaf starts cold, so file-locality ordering saves no tokens.
+ - **No planning of every task up front as one wave.** A wave plan drifts when an earlier commit changes the tree. Just-in-time planning, one task at a time, is a deliberate defense against drift ([why](docs/architecture.md#verification-in-radin-execute)).
+ - **No arena or judge at execution level.** N worktrees, N full implementations, and a judge that reads N diffs break the router's low-resolution rule. At planning, the idea stays open as an opt-in. It is tracked in its own backlog entry and gated on a review-origin rollup.
+ - **No model escalation on retry.** All four major roles (planning, execution, review, and debug) default to sonnet. Escalating on retry therefore changes nothing by default.
## Before committing
- - `make lint` and `make test` clean, and `make test` stay under 60s. Keep it
- there: one recorded `install.sh` run every tree-only test replay, live run
- only for install-time behaviour, mock command be `tests/helpers/mock.c` (one
- compiled binary, not a shell stub per command — `/bin/sh` startup cost 20ms
- a call), and no test make network request.
- - Docs updated in same commit: `docs/architecture.md` (layout, namespace
- resolution), `docs/domain-models.md` (format), README (new skill or
- companion), `CHANGELOG.md` (user-facing change).
+ - `make lint` and `make test` must come back clean, and `make test` must stay under 60 seconds. Every tree-only test replays one recorded `install.sh` run. A live run serves only install-time behavior. The mock command is `tests/helpers/mock.c`, one compiled binary, not a shell stub per command, because `/bin/sh` startup costs 20 ms per call. No test makes a network request.
+ - Update the docs in the same commit: `docs/architecture.md` (layout, namespace resolution), `docs/domain-models.md` (format), the README (a new skill or companion), and `CHANGELOG.md` (a user-facing change).
- Details: [architecture](docs/architecture.md) ·
- [domain models](docs/domain-models.md) ·
- [constraints](docs/technical-constraints.md) · [contributing](CONTRIBUTING.md)
+ For details, see [architecture](docs/architecture.md) · [domain models](docs/domain-models.md) · [constraints](docs/technical-constraints.md) · [contributing](CONTRIBUTING.md).