# AGENTS.md — the repository root One repository holding the **`kettle` binary** and a Claude Code **plugin marketplace** with one plugin left in it. Three things live here and nothing else does: ``` cli/ the kettle binary — Go, no cobra, 9 packages plugins/ one directory per plugin .claude-plugin/marketplace.json the catalog: one entry per plugin ``` Where to go from here, and each of these directories documents itself: | directory | what it is | |---|---| | [`cli/`](cli/AGENTS.md) | one Go module, two binaries: `kettle`, which owns this project's connection to its tracker — the format, the store, the credentials, the transport, through `kettle api` every Gitea entity that has no command of its own, and the skills and commands it writes into a project's `.claude/` — and [`cmd/release`](cli/cmd/release/AGENTS.md), which publishes this repository's own releases. `make check` is the gate; there is no CI here | | [`plugins/`](plugins/AGENTS.md) | what a plugin is here, and what the catalog entry has to match | | [`plugins/tdl/`](plugins/tdl/AGENTS.md) | the Three Dots Labs Go rule set — no binary, no state, just rules and templates | ## The one architectural fact worth carrying **A binary holds what can be enforced; prose holds what can only be stated.** Anything mechanical belongs in Go where a test can hold it down; anything that is a judgement an operator makes belongs in a SKILL.md. There used to be a second half to that sentence — *and a plugin holds the prose* — and it is gone, because it was wrong in a way that took a year to show. `kettle` shipped as a plugin wrapping a binary, on two release cadences, and **nothing on an operator's machine ever checked that the plugin they installed described the binary they installed.** The generated flag block existed precisely so a renamed flag could not ship with documentation recommending the old one, and it shipped one version behind the registry it came from — the same bug, one hop downstream. So the prose moved *into* the binary. `cli/internal/scaffold` embeds it, `kettle init` and `kettle gen scaffold` write it into a project's own `.claude/`, and the two cannot disagree because there is only one artefact. What a project gets: ``` .claude/commands/kettle/*.md /kettle:init, /kettle:auth, /kettle:issue, … .claude/skills/kettle-*/ loaded by description, never by name .claude/agents/kettle-runner.md the batch runner, on a cheap model ``` The same rule made `kettle api` (one door for every request, rather than requiring `tea` and its second set of invisible logins) and `kettle mirror` (below). Each time, something mechanical was sitting where no test could reach it, and each time it came into Go. ## The AGENTS.md convention **Every directory with a story documents itself, in that directory.** This file is a map, not a manual — it says what lives where and sends you down. A reader who opens `cli/internal/gitea/` gets the transport's rules from `cli/internal/gitea/AGENTS.md` and does not have to load the whole repository's design to change one request. Three rules make that work: 1. **`AGENTS.md` is the real file; `CLAUDE.md` beside it is a symlink to it.** `kettle mirror` enforces that and repairs any directory that drifted — it renames, re-points and swaps, and it never deletes content. Two real files with different content is the one case it refuses to resolve and reports instead. `CLAUDE.md` is gitignored, because it is generated. 2. **A directory's file describes that directory only.** What a parent or a child owns gets a link, never a second copy — the copy is what goes stale. If a sentence is true of the whole binary it belongs in `cli/AGENTS.md`; if it is true of one package it belongs in that package's file. 3. **Every file ends with its own maintenance contract** — the *Keeping this file true* section. It names the files the document covers and what kind of change obliges an edit. ### What keeps them true Nothing automatic, and that is a choice. Keeping these files honest is the job of whoever changes the code they describe, which is what the *Keeping this file true* section at the bottom of each one is for. A hook that nagged after every write was written and then removed: it would have fired for every user of the `kettle` plugin, on every edit in every repository they touched, to enforce a documentation convention that is this repository's and nobody else's. **That argument survived the plugin.** `kettle mirror --hook` is what stayed, and it is narrower on both axes: it repairs the filesystem layout rather than asking anybody for anything, and it is **opt-in per project** — `kettle init --mirror-hook`, or one question in `kettle init --interactive`. It cannot fail a tool call; it exits 0 on every path, including its own bugs, because documentation maintenance is not permitted to break a build. This repository registers it in `.claude/settings.json`, which is the one thing under `.claude/` that is not gitignored here — a fresh clone should get the convention without anyone remembering to wire it up. ## Development ```bash cd cli && make check # fmt, vet, test, go mod verify, build, docs — the gate cd cli && make help # install, dist, release ``` **There is no CI on the instance this lives on**, so `make check` is the only thing between a mistake and the tracker, and it is on whoever is committing to run it. Its last step is the repository's one mechanical documentation invariant, and it is circular on purpose: the documents an operator will be given are embedded in the binary with the registry's flag tables already spliced in, so a flag change makes them stale and `gen scaffold --check` exits 1. Regenerate, then **rebuild** — the binary has to pick the new bytes up. Everything else in this tree — including every `AGENTS.md` — is prose, held true by `kettle mirror` and by whoever is editing. ## Keeping this file true - **Scope:** the repository layout, the binary/prose split, and the AGENTS.md convention itself. Every deeper subject belongs to a deeper file. - **Update it when** a top-level directory appears or goes, a plugin is added or removed, the marketplace catalog changes shape, or the mirror hook's behaviour changes. - **Do not** put a command reference, a package's rules, or a skill's procedure here. Link to the file that owns it.