8b1b11001a
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>
121 lines
6.3 KiB
Markdown
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.
|