# AGENTS.md — plugins/ One directory per plugin, and each is self-contained: **a plugin's root is its directory here**, so `${CLAUDE_PLUGIN_ROOT}` resolves inside it and every path a plugin uses stays relative to itself. | plugin | what it is | |---|---| | [`tdl/`](tdl/AGENTS.md) | Three Dots Labs Go conventions as an enforceable rule set. One skill, no binary, no state | **There used to be a second one, and where it went is the useful part.** `kettle` was a plugin wrapping a binary, and the two shipped on separate cadences with nothing checking that an installed plugin described the installed binary. Its documents are embedded in the binary now ([`cli/internal/scaffold`](../cli/internal/scaffold/AGENTS.md)) and `kettle init` writes them into a project's own `.claude/`. That is not a reason to stop writing plugins here. It is a test to apply to the next one: **does this plugin describe something whose version it cannot see?** If it does, whatever ships that thing should ship the documents too. `tdl` passes easily — it has no binary, no state and no network, so there is no second version for it to disagree with. ## Adding one Two edits, and nothing else in the repository needs to know: 1. a directory here holding `.claude-plugin/plugin.json`, plus whatever of `skills/`, `agents/`, `hooks/`, `commands/` it needs; 2. one entry in [`.claude-plugin/marketplace.json`](../.claude-plugin/marketplace.json) whose `source` is the path into this directory. **The catalog entry's `name` and the directory name must match, and both must match `plugin.json`.** They are three spellings of one identity: the catalog resolves a `source` path, the manifest names the plugin, and the directory is what `${CLAUDE_PLUGIN_ROOT}` becomes. A rename that misses one of the three installs a plugin that cannot find its own files — which is exactly what happened when `tea` became `kettle`. The catalog's `description` is what somebody reads in `/plugin`; the manifest's is what the installed plugin reports. Keep them saying the same thing. ## What a plugin may and may not do - **A skill is prose an agent loads on demand.** Its frontmatter `description:` is the only thing that decides whether it gets loaded at all, which makes it the one line worth tuning against real failures — and the one line no generator may write. - **Heavy reference material goes in `references/`**, not in the SKILL.md. The skill says when to read it. - **A hook must fail open.** Both of `kettle`'s exit 0 on every path, including their own bugs: a plugin that breaks a user's Bash call because its documentation helper crashed is worse than no plugin. - **State belongs to the project, never to the plugin directory.** A plugin lives in a versioned cache that changes path on every update; anything written inside it is lost or invisible from the next project. This is not hypothetical — it is the bug that motivated rewriting `kettle`'s Python scripts as a binary, and the whole reason a project carries a `.kettle/` marker. ## Keeping this file true - **Scope:** what a plugin directory is, how the catalog points at it, and the rules common to all of them. The table names every plugin here. - **Update it when** a plugin is added or removed or renamed, or when the catalog gains a field a plugin author has to fill in. If the last one ever goes, this directory goes with it. - **Do not** describe an individual plugin's skills or design; each has its own file.