# AGENTS.md — the kettle plugin This plugin is a **thin wrapper over the `kettle` binary**, which lives in this same repository at [`cli/`](../../cli) and is documented in [`cli/AGENTS.md`](../../cli/AGENTS.md). The binary owns everything mechanical: what an issue is, where the store lives, who this machine is, and how issues move to and from Gitea. Its layering, its walk, its round-trip guarantees and its tests are described there and are **not repeated here** — one design, one place. What a plugin can carry that a binary cannot is the reason this directory still exists: 1. **The rules an operator states.** A binary can refuse to evict an `origin: local` issue; it cannot refuse to be run in the wrong directory, or decide that a migration clash is not a model's to resolve. 2. **Routing.** A `description:` in a SKILL.md frontmatter is the only thing that decides whether an agent loads a skill at all, and no generator can write it. 3. **Procedures.** How to turn a one-line request into a properly filled template, what to ask the user and what to never invent. ## Installing the binary **`kettle` is not on anybody's PATH by default.** Nothing in this plugin ships it, and a skill that assumes it exists fails with `command not found: kettle` — which is the clearest failure available, and every skill says what to do about it rather than falling back to something else. ```bash cd cli && go build -o ~/.local/bin/kettle ./cmd/kettle # from this repository go install git.noodles.cam/claude-skills/marketplace/cli/cmd/kettle@latest ``` `cli/go.mod` says **go 1.26** — the Gitea SDK requires it, so that is the minimum for anybody building this. `vendor/` is committed, so what compiles is what is in this repository's history; what that does and does not buy is in [`cli/AGENTS.md`](../../cli/AGENTS.md). An operator who sees `command not found: kettle` installs it and re-runs; there is nothing to configure in this plugin either way. `kettle config` is the command that explains where a run resolved to once it does exist. ## The generated command reference Every command line in `skills/{project,issue,sync}/SKILL.md` between ``` ``` is written by `kettle gen skills` from the command registry the binary is built from, and **must not be edited by hand** — the next run replaces it. Everything outside the markers is prose and comes back byte for byte, which is why the frontmatter is safe. ```bash cd cli && go build -o /tmp/kettle ./cmd/kettle /tmp/kettle gen skills --out plugins/kettle/skills # rewrite the blocks /tmp/kettle gen skills --out plugins/kettle/skills --check # exit 1 if stale ``` `--check` is what a pre-commit hook or a CI step calls; it writes nothing. A file the run reports as `without a region` is one where somebody dropped the markers — it is left alone, never overwritten, and the fix is to put them back. **Groups and skills are not the same set, and that is the one seam.** The binary groups its commands `project`, `issue`, `sync`, `api`; the plugin's skills are `init`, `auth`, `project`, `issue`, `sync`, `api`. The generator writes one `/SKILL.md`, so: | skill | generated region | why | |---|---|---| | `issue`, `sync`, `api` | yes — the group of the same name | the skill and the group are the same subject | | `project` | yes — `init`, `auth`, `config`, `gen` | the flag table `init` and `auth` point at | | `init` | no | it is a *procedure* around one command, and it is operator-only | | `auth` | no | it is a *procedure* around two, and it must not tempt a model into typing a token | `api` is the newest of those and the one that shows what the seam is for: the group exists because `kettle api` is not an issue command, and the skill exists because "which endpoint, and does it paginate" is a map somebody loads on its own. The group was named `api` rather than left as `use` precisely so that the three spellings — group, directory, `/kettle:api` — are one word. The two skills with no region hold no flag tables of their own. They name a command and send the reader to `/kettle:project`, which is the point: a file that hand-copies a flag list is a file that will disagree with the binary in a month. If the generator ever cannot express what a skill needs, the answer is to change the generator, not to paste a block that will rot. ## Layout ``` .claude-plugin/plugin.json the manifest (the marketplace catalog is one level up, in the repo root's .claude-plugin/) agents/ kettle-runner.md subagent (Haiku): runs kettle commands, reports a receipt. Batches only, and no opinions about content hooks/ hooks.json registers the one hook: PreToolUse(Bash) agents-sync.sh keeps every directory canonical: AGENTS.md a real file, CLAUDE.md a symlink to it skills/ init/ SKILL.md /kettle:init — operator-only; the rules around `kettle init` auth/ SKILL.md /kettle:auth — the credential workflow around `kettle auth` and `kettle init --login` project/ SKILL.md generated flag reference: init, auth, config, gen issue/ SKILL.md /kettle:issue — the offline commands references/format.md THE canonical issue format; source of truth sync/ SKILL.md /kettle:sync — the tracker commands api/ SKILL.md /kettle:api — every Gitea entity with no command of its own: pull requests, releases, tags, milestones, branches, actions, webhooks. The map of endpoints, and which of them paginate ``` `references/format.md` is the one document here that the binary does not generate and does not own a copy of. It is the format's statement of intent — identity, metadata, label namespaces, the per-type templates, the language rule — and it stays hand-written. ## The rules that must survive, because a binary cannot state them - **An `origin: local` issue is the only copy of that work.** A push deletes the local file only after the tracker confirms the write and the number → slug ledger is written; nothing else deletes it, ever. - **A closed issue is evicted, not archived** — and a local one is never evicted, in any state, not even when it is named on the command line. - **Body prose is Russian; the title and the section headers are English.** - **Do not run `init` inside a linked worktree.** `.kettle/` is gitignored, a worktree reaches the main checkout's store on its own, and a marker there gives one project two stores — the second of which disappears with the branch. - **Which directory is the project is a statement a person makes.** `/kettle:init` keeps `disable-model-invocation: true` for that reason, and never invents an `--at`. - **A migration clash is the operator's to resolve.** The binary stops and names both files; one of them may be somebody's only copy. ## What this plugin no longer ships, and why | gone | replaced by | |---|---| | every Python script under `skills/*/scripts/` — the whole domain, bridge and transport | the `kettle` binary; see `cli/AGENTS.md` for the three runtime failures that motivated it | | `tests/` — the stdlib `unittest` suite | `cd cli && go test ./...` | | the guard hook and its entry in `hooks/hooks.json` | nothing. It existed to stop a `tea` command running under a login the model picked; the binary holds its own credentials and reads the login out of the project's config, so the failure is not expressible any more | | `.claude/settings.local.json` → `env.GITEA_LOGIN`, the login pin | `/.kettle/config.yaml` (a login **name**) plus `~/.config/kettle/logins.yaml` (the tokens, 0600, outside every working tree) | | the tea plugin's own store marker | `.kettle/`; `kettle init` migrates an older layout in, and each migration is a move | | `tea`, Gitea's own CLI, as an external requirement — a skill of reference docs for somebody else's flags, and a second set of logins nothing here could see | `kettle api`, one request through the transport the binary already had. What was 400 lines documenting another tool is now a map of endpoints; what was two credential stores is one | `hooks/agents-sync.sh` is unrelated to any of that and stays. It maintains the `AGENTS.md` convention by repairing the filesystem layout — a real file, with `CLAUDE.md` a symlink to it — and the convention itself is documented once, in [the repository root's AGENTS.md](../../AGENTS.md#the-agentsmd-convention). It cannot fail a tool call: it exits 0 on every path, including its own bugs — documentation maintenance is not permitted to break a build. All of it is in git history. `git log --diff-filter=D` finds it if a decision needs to be re-read rather than re-derived. ## Tests The plugin has no test suite of its own; the binary's is the suite. ```bash cd cli && go test ./... /tmp/kettle gen skills --out plugins/kettle/skills --check ``` The second line is the plugin's only mechanical invariant: the documentation an agent reads agrees with the binary it is documenting. ## Keeping this file true - **Scope:** everything under `plugins/kettle/` — the skills, the runner subagent, the two hooks, and the group ⇄ skill seam. `skills/` and `agents/` carry no AGENTS.md of their own on purpose: a SKILL.md already states its own procedure, and a second document beside it would be the copy that goes stale. - **Update it when** a skill is added or removed (the layout and the generated-region table both name every one), a hook is added or its event changes, a rule in *the rules that must survive* changes, or the binary takes over something this plugin used to state. - **Do not** restate the binary's design. [`cli/AGENTS.md`](../../cli/AGENTS.md) and the eight package files under it own that; if the two ever disagree, the binary is right and the prose is stale.