Files
marketplace/AGENTS.md
T
naudachu 8b1b11001a feat: drop the kettle plugin; the binary writes its own skills
The plugin and the binary shipped on two release cadences and nothing on an
operator's machine ever checked that the one they installed described the other.
The generated flag block existed precisely so a renamed flag could not ship with
documentation recommending the old one — and then shipped one version behind the
registry it came from, which is the same bug one hop downstream.

So the prose moved into the binary. `internal/scaffold` embeds every document;
`kettle init` and `kettle gen scaffold` write them into a project's own
`.claude/`. The two cannot disagree because there is one artefact.

The namespace survived the move. A project's skills are flat, so the prefix is
spelled into the directory name (`kettle-issue`); a project's *commands* take
their namespace from a subdirectory, so `commands/kettle/init.md` is still
`/kettle:init`. Four of the six command files are thin pointers at a skill, and
that is what kept ~1,600 lines of `/kettle:…` cross-references true without a
rewrite. `init` and `auth` lost `disable-model-invocation: true` — being a
command is that property — and `auth` now restricts `allowed-tools` so a model
cannot reach `kettle auth add` at all.

`gen scaffold` writes files whole rather than splicing a region. The old
refusal protected somebody's hand-written prose around the block; that prose is
embedded now, so there is none to protect, and preserving local edits would
freeze a project's documentation at whatever version first initialized it.
`--check` warns before an upgrade discards one.

The plugin's `agents-sync.sh` — 141 lines of Python behind a filename that said
`.sh` — became `internal/mirror` and `kettle mirror`. Same seven branches, same
refusal to merge two real files that differ, now with a table test per branch
and a check that a repair converges in one pass. `--hook` is the PreToolUse
form and exits 0 on every path including a panic. It is opt-in per project,
which is strictly narrower than the plugin hook that was on for everybody who
installed it.

`kettle init --interactive` walks a person through the login, the token (read
with the echo off, so it lands in no history and no file), the repository, the
`.claude/` tree and the mirror hook. It refuses a stdin that is not a terminal
and names the flags instead: every question it asks has one, and it performs
nothing itself, so an interactive run and a flag run are one code path.

Two rules that used to be prose are now the binary's: init refuses a linked
worktree and names the main checkout, and writing into an existing
`.claude/settings.json` is refused with the snippet printed rather than
reformatting a file the operator commits.

The scaffold version stamp went to its own `.kettle/scaffold.yaml` rather than
into `config.yaml`, because unknown keys there are a hard error and that file
may be committed and read by whatever build each machine has.

golang.org/x/term becomes a direct dependency; it was already in the tree
indirectly, so no module was added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 16:17:24 +05:00

121 lines
6.3 KiB
Markdown

# 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.