feat: publish releases with this repository's own SDK code
There is no CI: the instance has no act_runner and none is planned, so releases are cut by hand. That makes `make check` the only thing standing between a mistake and the tracker, and it is one command: gofmt, vet, the suite with the cache defeated, `go mod verify`, a vendored build, and `kettle gen skills --check`. The last one is the invariant worth having — the plugin's SKILL.md command reference is generated from the binary's registry, so a flag that changed cannot ship with documentation that recommends the old one. `cli/cmd/release` publishes to Gitea using the same SDK the binary already vendors, which is a pleasing thing to be able to say: nothing third-party handles the artifacts. It is a second binary rather than a `kettle` subcommand on purpose — `kettle`'s command tree is what generates the plugin's skills, so a verb there ships to every operator, and publishing a release is build infrastructure. It is idempotent end to end: an existing release for the tag is reused, an asset of the same name is replaced rather than doubled, and a retried run converges instead of duplicating. `make release` refuses three things, each with its own message: a dirty working tree, a TAG that is not what `git describe` reports, and a tag the remote does not have. A release built from uncommitted code is unreproducible and nobody finds out until they need to reproduce it. `kettle version` reports the stamp, the toolchain and the VCS revision. The default is `dev`, and a hand build says so and means it — a binary out of somebody's working tree is not a release and must not claim to be one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+145
-239
@@ -1,11 +1,15 @@
|
||||
# AGENTS.md — the kettle CLI
|
||||
|
||||
`kettle` is a globally installed binary. It owns everything that used to be a
|
||||
Python script under `plugins/tea/skills/*/scripts/`: what an issue is, where the
|
||||
store lives, who this machine is, and how issues move to and from Gitea.
|
||||
`kettle` is a globally installed binary. It owns everything mechanical about
|
||||
issues: what an issue is, where the store lives, who this machine is, and how
|
||||
issues move to and from Gitea. It replaced a set of Python scripts that used to
|
||||
ship inside the plugin.
|
||||
|
||||
The plugin keeps what only a plugin can carry — the rules an operator states and
|
||||
a binary cannot enforce. Everything mechanical is here.
|
||||
a binary cannot enforce. Everything else is here.
|
||||
|
||||
**This file is the binary's map.** Each package documents its own rules in its own
|
||||
directory; nothing below is repeated there and nothing there is repeated here.
|
||||
|
||||
## Why a binary
|
||||
|
||||
@@ -20,47 +24,17 @@ Three failures in the Python version were failures of *runtime*, not of logic:
|
||||
convention checked by grep.
|
||||
|
||||
A compiled binary answers all three by construction. There is one walk
|
||||
(`internal/project`), it is imported rather than re-derived, and the layering
|
||||
rule is a build graph a test walks.
|
||||
|
||||
## Two dependencies
|
||||
|
||||
`gopkg.in/yaml.v3` and `code.gitea.io/sdk/gitea`, both vendored, and that is the
|
||||
whole list — eight modules once the SDK's own are counted, 2.3 MB of `vendor/`.
|
||||
The CLI still has no cobra: commands are values in a registry, which is what
|
||||
lets the plugin's SKILL.md files be generated from the same struct that holds
|
||||
the code.
|
||||
|
||||
`vendor/` is committed, so a build needs no network. **`go.mod` says `go 1.26`**,
|
||||
which the SDK requires; that is now the minimum for anybody building this binary.
|
||||
|
||||
The transport was hand-rolled `net/http` against the REST API and is now the
|
||||
official SDK. What that bought:
|
||||
|
||||
- **the payload shapes are one vocabulary, maintained by the people who maintain
|
||||
the server.** They used to be ours, in `internal/wire`, and every field Gitea
|
||||
learned was a field somebody here had to notice.
|
||||
- **the server's version, for free.** The SDK negotiates it when a client is
|
||||
built, which is what lets the transport answer "does this instance have issue
|
||||
dependencies?" from the version instead of guessing from a status code — see
|
||||
`dependenciesSince`.
|
||||
|
||||
What it cost is written down where it happened: the shapes come with an HTTP
|
||||
client attached (see the layering note below), a run makes one extra request
|
||||
before it does anything (the version handshake), and Gitea's issue **edit**
|
||||
endpoint carries no labels, so an issue whose labels changed needs a second call
|
||||
— `push` makes it and says so.
|
||||
|
||||
The SDK is imported as `sdk` everywhere, so one type has one spelling across the
|
||||
tree. `internal/gitea` is the package named `gitea` and the SDK is `sdk` inside
|
||||
it; the same alias holds in `internal/mapping` and `internal/cmd`.
|
||||
([`internal/project`](internal/project/AGENTS.md)), it is imported rather than
|
||||
re-derived, and the layering rule is a build graph a test walks.
|
||||
|
||||
## Layers
|
||||
|
||||
Knowledge flows one way. The arrow means "imports".
|
||||
Knowledge flows one way. The arrow means "imports"; follow a name to that
|
||||
package's own AGENTS.md.
|
||||
|
||||
```
|
||||
cmd/kettle thin main; exit status only
|
||||
cmd/release build infrastructure, not a kettle verb — see its own file
|
||||
|
||||
internal/cmd the command tree: flags, receipts, exit codes
|
||||
│ │ │
|
||||
@@ -86,185 +60,92 @@ internal/project ROOT which directory is the project, and every path
|
||||
depends on nothing
|
||||
```
|
||||
|
||||
Both the transport and the bridge also import `code.gitea.io/sdk/gitea`, which
|
||||
is where the payloads now live. The arrow that used to point at `internal/wire`
|
||||
for them points there instead.
|
||||
| package | layer | what its own file opens with |
|
||||
|---|---|---|
|
||||
| [`cmd/kettle`](cmd/kettle/AGENTS.md) | entry point | `os.Exit(cmd.Main(os.Args[1:]))`, and why there is nothing else in it |
|
||||
| [`cmd/release`](cmd/release/AGENTS.md) | build tool | why publishing a release is not a `kettle` verb, and why it goes around the transport |
|
||||
| [`internal/cmd`](internal/cmd/AGENTS.md) | commands | the registry every command is a value in, and the generator that writes the plugin's docs from it |
|
||||
| [`internal/config`](internal/config/AGENTS.md) | configuration | two files, and why the tokens are not in the one inside the repository |
|
||||
| [`internal/gitea`](internal/gitea/AGENTS.md) | transport | the SDK, the payload scratchpad, the ledger, the dependency endpoint |
|
||||
| [`internal/mapping`](internal/mapping/AGENTS.md) | bridge | md ↔ payload, the id marker, label colours, the checkbox merge |
|
||||
| [`internal/wire`](internal/wire/AGENTS.md) | addresses | `42`, `#42`, `owner/repo#42`, a URL — four spellings of one thing |
|
||||
| [`internal/issue`](internal/issue/AGENTS.md) | domain | the format, the taxonomy, the store, eviction — all of it offline |
|
||||
| [`internal/project`](internal/project/AGENTS.md) | root | the walk, and every path resolved from its answer |
|
||||
|
||||
Read it bottom-up and each layer knows strictly less about trackers than the one
|
||||
above it. Four rules hold the line, seven tests between them, and each fails on
|
||||
a real mistake rather than on a naming convention:
|
||||
The rules that hold the layers apart, and the seven tests that fail when one
|
||||
breaks, are in [`internal/AGENTS.md`](internal/AGENTS.md). Read the diagram
|
||||
bottom-up: each layer knows strictly less about trackers than the one above it.
|
||||
|
||||
- `internal/issue` may import `internal/project` and the standard library, and
|
||||
nothing else. One test walks `go list -deps` and fails on any path with a dot
|
||||
in its first element — which is also what keeps yaml AND the SDK out of the
|
||||
domain — and another names `net/http`, `net`, `os/exec` and `encoding/json`,
|
||||
standard library the first test would not catch. **Untouched by the migration
|
||||
to the SDK, and that is the point: the domain did not notice it happened.**
|
||||
- `internal/wire` imports only the standard library, checked the same two ways.
|
||||
- `internal/gitea` must not import `internal/issue` **or** `internal/mapping`:
|
||||
the transport knows numbers, logins, HTTP and JSON, and none of what they mean.
|
||||
- `internal/mapping` reaches for nothing but the domain and the SDK — checked on
|
||||
its DIRECT imports, with `os`, `net/http` and `internal/gitea` named — and a
|
||||
second test greps its sources for `time.Now`.
|
||||
## Dependencies, and building
|
||||
|
||||
**That fourth rule was stronger before the SDK, and the trade is deliberate.**
|
||||
The payload shapes lived in `internal/wire`, a package that imported the
|
||||
standard library and nothing else, so "the bridge cannot reach a transport" was
|
||||
a fact about the import graph: there was nothing in its dependency closure that
|
||||
could open a socket. `code.gitea.io/sdk/gitea` is a client and a set of types in
|
||||
one package, so importing the types imports the client, and a test that walked
|
||||
the closure would now be asserting something false. What is still true, and what
|
||||
the test now says, is that **mapping performs no I/O** — no `os`, no `net/http`,
|
||||
no transport, no configuration, no clock. `time` is allowed where it was not,
|
||||
because the SDK hands over a `time.Time` and somebody has to format it back into
|
||||
the string an issue file holds; the clock itself is still the caller's, and the
|
||||
grep for `time.Now` is what says so.
|
||||
|
||||
`wire` used to exist because Go needs the JSON shapes to be one type — the
|
||||
transport and the bridge were written in parallel and each invented its own
|
||||
`Issue`, `Label`, `Milestone` and `Comment`. The SDK settles that argument for
|
||||
the shapes. **What survives is addressing**, which the SDK has no answer for at
|
||||
all: it takes an owner, a name and an `int64`, and never parses. `42`, `#42`,
|
||||
`owner/repo#42` and an issue URL are four spellings of one address, all four are
|
||||
what somebody has in hand, and `wire.Key` is what the ledger is keyed by and
|
||||
what the `gitea:` metadata field holds. So `wire` keeps `Repo`, `Key`, their
|
||||
parsing and their tests, and lost the payloads.
|
||||
|
||||
If a tracker concept — an issue number, a login, an HTTP call, a label colour —
|
||||
shows up in `internal/issue`, it is in the wrong place.
|
||||
|
||||
## The walk
|
||||
|
||||
`internal/project` answers one question and everything else reads the answer.
|
||||
|
||||
Anchors, first hit wins: `$CLAUDE_PROJECT_DIR`, then the working directory. Each
|
||||
is searched up its parent chain for a `.kettle/` marker, and then — only if that
|
||||
found nothing — up the parent chain of the **main working tree of any linked
|
||||
worktree** met on the way, reached by reading `gitdir:` out of a `.git` *file*
|
||||
and following `commondir`.
|
||||
|
||||
Nothing here resolves from the executable's own location. Where an installation
|
||||
keeps its files is a fact about the installation; whose issues a tree has is a
|
||||
fact about the tree, and a binary installed in one place and pointed at another
|
||||
must answer from the one it was pointed at.
|
||||
|
||||
**The marker is created by `kettle init`, never inferred.** `.git` was tried and
|
||||
is in every clone. No marker anywhere is an answer, not a fallback: the command
|
||||
reports which directories it searched and stops.
|
||||
|
||||
## Configuration, and where secrets are not
|
||||
|
||||
Two files, and the split is the whole design.
|
||||
|
||||
`<project>/.kettle/config.yaml` — written by `kettle init`, says which tracker
|
||||
repository the issues belong to and which login to reach it under. It pins a
|
||||
login by **name**. The name is worth nothing on its own, which is what makes it
|
||||
safe to keep in a file inside a working tree.
|
||||
|
||||
`~/.config/kettle/logins.yaml` (`$KETTLE_CONFIG_HOME` or `$XDG_CONFIG_HOME`
|
||||
override it) — one file per machine, mode 0600, holding the tokens. Managed with
|
||||
`kettle auth`, which reads the token from standard input by default because an
|
||||
argument is in the shell history the moment it is typed. Nothing prints a token
|
||||
back: `kettle config` shows `(set)`.
|
||||
|
||||
A token in a file inside a working tree ends up in a commit. Not always, not
|
||||
immediately, and not by anyone careless — but a project config is exactly the
|
||||
file somebody eventually decides to share, and a secret that has ever been
|
||||
pushed has to be rotated.
|
||||
|
||||
`KETTLE_LOGIN`, `KETTLE_REPO`, `KETTLE_URL` and `KETTLE_TOKEN` each override the
|
||||
file they shadow, for CI and for anyone who would rather have no token on disk.
|
||||
|
||||
Unknown keys in either file are an **error**, not a silent drop: an older binary
|
||||
reading a newer config would otherwise delete the setting it did not recognize
|
||||
the next time it wrote the file.
|
||||
|
||||
`init` gitignores `.kettle/` wholesale. An `origin: local` issue is the only copy
|
||||
of that work, and what goes into a shared history is the operator's call, not
|
||||
this binary's — drop the line if the team decides otherwise.
|
||||
|
||||
## The store
|
||||
|
||||
`<project root>/.kettle/issues`, one flat markdown file per issue, named by its
|
||||
slug, one metadata field per line so plain grep works without a parser.
|
||||
|
||||
It holds two kinds of file and only one of them is a store. An `origin: local`
|
||||
issue lives here and nowhere else — that file **is** the issue. Anything with a
|
||||
tracker origin is a working copy, deleted the moment a push confirms the tracker
|
||||
is up to date. Eviction makes the same trade one step earlier and asks the file
|
||||
instead of the tracker, which is why it lives in the domain.
|
||||
|
||||
`.kettle/payload/` is a sibling, never a child: request bodies are debris of the
|
||||
transport, and a scratchpad inside a store makes `ls .kettle/issues` lie about
|
||||
what exists. It is written by an `http.RoundTripper` installed on the SDK's
|
||||
client, so **every** request with a body is filed and not only the ones a call
|
||||
site remembered to name — a name only decides what the file is called. A run
|
||||
that sends nothing, which includes every read-only command, still leaves no
|
||||
directory: the first write creates it. The dump is the same JSON the wire
|
||||
carried, re-indented and with `<`, `>` and `&` left alone, because the SDK
|
||||
marshals with encoding/json's escaping and a dump nobody can read is a dump
|
||||
nobody reads.
|
||||
|
||||
`kettle init` migrates older layouts in, oldest first — `tmp/issues` and then
|
||||
`.tea/issues` — and each is a **move**. A store left behind at an old path is a
|
||||
store somebody will edit by accident months later. It refuses to pick a winner
|
||||
when both sides hold a file of the same name.
|
||||
|
||||
## Tests
|
||||
`gopkg.in/yaml.v3` and `code.gitea.io/sdk/gitea` — eight modules once the SDK's
|
||||
own are counted. No cobra: commands are values in a registry, which is what lets
|
||||
the plugin's SKILL.md files be generated from the same struct that holds the code.
|
||||
|
||||
```bash
|
||||
cd cli && go test ./...
|
||||
make install # build straight onto your PATH, version stamped
|
||||
make install BINDIR=$(go env GOPATH)/bin
|
||||
|
||||
go build -o ~/.local/bin/kettle ./cmd/kettle # the same thing, unstamped
|
||||
go install git.noodles.cam/claude-skills/marketplace/cli/cmd/kettle@latest
|
||||
```
|
||||
|
||||
`internal/cmd` builds the binary once in `TestMain` and runs it as a subprocess
|
||||
against a throwaway project in a temp directory — the same discipline the Python
|
||||
suite arrived at, for the same reason. **The binary is never run in the
|
||||
directory it was built in**, because that is exactly the arrangement that hid
|
||||
the `__file__` bug: a tool is installed in one place and used on projects in
|
||||
another, and a test that collapses the two proves nothing about resolution.
|
||||
**`make` is the build, and it is also the CI.** There is no act_runner on the
|
||||
instance this lives on and none is planned, so nothing runs on a push: `make check`
|
||||
— fmt, vet, test, `go mod verify`, build, `gen skills --check` — is the only thing
|
||||
standing between a mistake and the tracker, and it is on whoever is committing to run
|
||||
it. `make help` lists the rest; `make dist` cross-compiles four platforms with a
|
||||
`SHA256SUMS`, and `make release TAG=v1.2.3` publishes them through
|
||||
[`cmd/release`](cmd/release/AGENTS.md) after refusing a dirty tree, a `TAG` that is
|
||||
not what `git describe` reports, and a tag the remote does not have.
|
||||
|
||||
Every fixture strips `CLAUDE_PROJECT_DIR`. It is the first anchor of the walk,
|
||||
so the harness's own value would point every fixture at this repository.
|
||||
Anything touching credentials sets `KETTLE_CONFIG_HOME` at a temp directory, so
|
||||
a test run can neither read nor overwrite the developer's own tokens.
|
||||
A hand build says `dev` for `kettle version` and means it: a binary out of somebody's
|
||||
working tree is not a release and must not claim to be one. The version is stamped at
|
||||
link time into `internal/cmd.Version`, derived from `git describe` rather than kept in
|
||||
a file — a number somebody has to remember to bump is a number that will be wrong.
|
||||
|
||||
**Every fake tracker answers `/api/v1/version`**, because building a client asks
|
||||
for it: the SDK will not hand one back until the instance has said what it is,
|
||||
and that answer is what the dependency gate is decided on later. The fakes say
|
||||
1.26.1. One says 1.19.4, and that is a test — an instance too old for the issue
|
||||
dependency endpoints is answered from its version, with no request made.
|
||||
**`go.mod` says `go 1.26`**, which the SDK requires, and **`vendor/` is
|
||||
committed** — 281 files, 2.3 MB, which is the price of knowing exactly what
|
||||
compiled.
|
||||
|
||||
Be precise about what that buys, because it is easy to overclaim: vendoring
|
||||
pins the *contents* of every dependency in this repository's own history, so a
|
||||
dependency that is retagged, yanked or unreachable cannot change what this
|
||||
binary is built from. It does **not** by itself give a network-free build.
|
||||
`go 1.26` in `go.mod` means `GOTOOLCHAIN=auto` fetches a toolchain over the
|
||||
network on any machine whose local Go is older, which is most of them. If an
|
||||
air-gapped build is ever a requirement, pin the toolchain too — vendoring alone
|
||||
will not get you there.
|
||||
|
||||
`make check` runs both halves: `go mod verify` says the module cache matches
|
||||
`go.sum`, and `go build -mod=vendor ./...` says the committed tree is complete
|
||||
and is what actually compiles. A `vendor/` that has drifted from `go.mod` fails
|
||||
nothing until somebody builds with a cold cache, which is exactly when nobody
|
||||
wants to find out.
|
||||
|
||||
The transport is the official SDK rather than hand-rolled `net/http`. What that
|
||||
bought: the payload shapes are one vocabulary maintained by the people who
|
||||
maintain the server, and the instance's version arrives for free, which is what
|
||||
lets the transport answer "does this instance have issue dependencies?" from the
|
||||
version instead of guessing from a status code. What it cost is written down where
|
||||
it happened — see [`internal/gitea`](internal/gitea/AGENTS.md) and
|
||||
[`internal/mapping`](internal/mapping/AGENTS.md).
|
||||
|
||||
The SDK is imported as `sdk` everywhere, so one type has one spelling across the
|
||||
tree.
|
||||
|
||||
## The round trip
|
||||
|
||||
`push` and `pull` are the two halves of one rule, and the rule is that **the
|
||||
store holds what has not left this machine.**
|
||||
store holds what has not left this machine.** A successful push deletes the local
|
||||
file — on `--update` too, one rule with no exception — and only after the tracker
|
||||
confirms the write and the number → slug ledger is written. A pull is how the copy
|
||||
comes back, under the same slug, on a machine that has never seen it.
|
||||
|
||||
A successful push deletes `<id>.md` and every sidecar under that slug, on create
|
||||
and on `--update` alike, and prints the number and URL the issue now lives at.
|
||||
An `--update` can take one extra request with it: Gitea's edit endpoint carries
|
||||
no labels, so when the answer's label set and the issue's disagree — a label
|
||||
added or removed locally, or one a create silently dropped — the whole set goes
|
||||
up in a PUT, and a warning on stderr says which names moved.
|
||||
The deletion happens **only after a confirmed tracker response and only after the
|
||||
number -> slug ledger has been written** — network down, non-2xx, or an answer
|
||||
that does not carry the right number, and the file stays where it is while the
|
||||
run stops. A never-pushed `origin: local` issue is never touched by any of it.
|
||||
|
||||
The slug survives that round trip two ways over, and a test proves both: it goes
|
||||
up in the body as `<!-- kettle:id … -->`, and it is indexed by number in
|
||||
`.kettle/issues/.remote.json`. A rename in the web UI, a lost ledger, a fresh
|
||||
clone, another machine — the file comes back under the same name and every
|
||||
`depends:` pointing at it still resolves. The marker is written in that spelling
|
||||
and read in both it and the older `<!-- tea:id … -->`, because issues pushed
|
||||
before the rename are still in the tracker.
|
||||
|
||||
Pull by number fetches an issue in **any** state: a number is an address, not a
|
||||
query, and `42`, `#42`, `owner/repo#42` and a URL all name one. Only filter mode
|
||||
leaves closed issues out. A pull returns the unit of work rather than one row of
|
||||
it — blockers come down with it unless `--no-deps` says otherwise — and it
|
||||
overwrites the body, because it is a fetch and not a merge. The one exception is
|
||||
checkbox state, which is the local half of the work and is merged rather than
|
||||
clobbered.
|
||||
The mechanics of each half live with the commands that implement them, in
|
||||
[`internal/cmd`](internal/cmd/AGENTS.md); the ledger that survives the deletion is
|
||||
[`internal/gitea`](internal/gitea/AGENTS.md)'s, and the marker that survives a lost
|
||||
ledger is [`internal/mapping`](internal/mapping/AGENTS.md)'s.
|
||||
|
||||
## No guard hook
|
||||
|
||||
@@ -276,45 +157,70 @@ for the transport and the guard to disagree — the failure the hook existed to
|
||||
catch is not expressible any more.
|
||||
|
||||
There is also no `--login` and no `--repo` on any sync command bar `labels`.
|
||||
Which login a project runs under, and which repository its issues belong to, are
|
||||
facts about the project, stated once by `kettle init`. A cross-repository address
|
||||
is still an address: `kettle pull owner/repo#42` re-points the client for that
|
||||
one call — `Client.For(repo)`, which is bookkeeping and not a second connection,
|
||||
because the SDK takes the owner and the name per call. The credentials, the
|
||||
negotiated version and the scratchpad come along.
|
||||
A cross-repository address is still an address: `kettle pull owner/repo#42`
|
||||
re-points the client for that one call, which is bookkeeping and not a second
|
||||
connection.
|
||||
|
||||
## Issue dependencies
|
||||
## Tests
|
||||
|
||||
The one endpoint with a story. `depends:` becomes a native Gitea link, which is
|
||||
what makes the tracker show the blocking panel and refuse to close a blocked
|
||||
issue first, and it is read back the same way on a pull.
|
||||
```bash
|
||||
make check # fmt, vet, test, go mod verify, build, docs — the whole gate
|
||||
go test ./... # just the tests
|
||||
```
|
||||
|
||||
- **Reading** goes through the SDK (`ListIssueDependencies`).
|
||||
- **Writing does not.** Gitea's own `IssueMeta` is `{index, owner, repo}` and has
|
||||
been since the endpoint existed; the SDK's is `{index}`, which can only ever
|
||||
link inside one repository, and a `depends:` entry is allowed to live
|
||||
somewhere else. So one hand-rolled POST survives, through the same HTTP client
|
||||
as everything else — same payload dump, same `*APIError`.
|
||||
- **The version gates both.** The routes are absent from Gitea 1.19 and present
|
||||
in 1.20, checked against the release tags themselves, so an instance older
|
||||
than that is answered from the version it already gave us rather than from a
|
||||
404 — which on an old server is also what "no such issue" looks like.
|
||||
- **A tracker that answers with a status still means "no dependencies here"**,
|
||||
because an instance that HAS the endpoint can still have them turned off for a
|
||||
repository, and a pull must bring the issue back either way. A dead connection
|
||||
is not that answer and never was.
|
||||
Three disciplines every test follows:
|
||||
|
||||
- **`internal/cmd` builds the binary once in `TestMain` and runs it as a
|
||||
subprocess** against a throwaway project in a temp directory. The binary is
|
||||
never run in the directory it was built in, because that is exactly the
|
||||
arrangement that hid the `__file__` bug: a tool is installed in one place and
|
||||
used on projects in another, and a test that collapses the two proves nothing
|
||||
about resolution.
|
||||
- **Every fixture strips `CLAUDE_PROJECT_DIR`**, the first anchor of the walk, or
|
||||
the harness's own value would point every fixture at this repository. Anything
|
||||
touching credentials sets `KETTLE_CONFIG_HOME` at a temp directory, so a run can
|
||||
neither read nor overwrite the developer's own tokens.
|
||||
- **Every fake tracker answers `/api/v1/version`**, because building an SDK client
|
||||
asks for it. The fakes say 1.26.1; one says 1.19.4, and that one is a test — an
|
||||
instance too old for the dependency endpoints is answered from its version with
|
||||
no request made.
|
||||
|
||||
## Documentation that is generated
|
||||
|
||||
```bash
|
||||
kettle gen skills --out ../plugins/kettle/skills # rewrite the blocks
|
||||
kettle gen skills --out ../plugins/kettle/skills --check # exit 1 if stale
|
||||
```
|
||||
|
||||
Everything between `<!-- kettle:gen -->` and `<!-- /kettle:gen -->` in the
|
||||
plugin's SKILL.md files comes from the command registry, so a renamed flag cannot
|
||||
ship with documentation that recommends the old one. `--check` is what a
|
||||
pre-commit hook or a CI step calls. The generator is
|
||||
[`internal/cmd`](internal/cmd/AGENTS.md)'s, and the seam between command groups
|
||||
and plugin skills is [the plugin's](../plugins/kettle/AGENTS.md).
|
||||
|
||||
Note the asymmetry, because it is easy to get backwards: **a SKILL.md command
|
||||
block is generated and must never be hand-edited; every AGENTS.md in this tree is
|
||||
hand-written and must never be generated.** One is a flag table, the other is a
|
||||
reason.
|
||||
|
||||
## Status
|
||||
|
||||
Done and tested: all seven packages, and the commands `init`, `auth`, `config`,
|
||||
`new`, `check`, `ac`, `tree`, `index`, `evict`, `pull`, `push`, `remote`,
|
||||
`comment`, `close`, `labels`, `sync-evict`. 99 tests.
|
||||
Done and tested: every package in the table above, and the commands `init`, `auth`,
|
||||
`config`, `gen`, `version`, `new`, `check`, `ac`, `tree`, `index`, `evict`, `pull`,
|
||||
`push`, `remote`, `comment`, `close`, `labels`, `sync-evict` — plus `cmd/release`,
|
||||
against a fake Gitea.
|
||||
|
||||
The plugin is rewired: it lives at `plugins/kettle`, ships no Python and no guard
|
||||
hook, and its command reference is generated from this registry —
|
||||
`kettle gen skills --out plugins/kettle/skills`, with `--check` as the invariant.
|
||||
The generator writes one file per GROUP (`project`, `issue`, `sync`); the plugin
|
||||
also carries `init`, `auth` and `use`, which hold procedure rather than flags and
|
||||
point at the generated `project` block. Adding a group here adds a skill
|
||||
directory there, so name one only when it is a subject somebody would load on its
|
||||
own.
|
||||
The plugin is rewired: it lives at `plugins/kettle`, ships no Python domain code
|
||||
and no guard hook, and its command reference is generated from this registry.
|
||||
|
||||
## Keeping this file true
|
||||
|
||||
- **Scope:** the binary as a whole — why it exists, what it depends on, how it is
|
||||
built and tested, and where each package's own document is. Files: `go.mod`,
|
||||
`go.sum`, `Makefile`, and the shape of the tree.
|
||||
- **Update it when** a package is added or removed (the diagram and the table both
|
||||
name every one), a dependency changes, a `make` target is added or its meaning
|
||||
changes, or a cross-package contract like the round trip changes.
|
||||
- **Do not** describe a package's internals here. That is what the files it links to
|
||||
are for, and a summary that drifts is worse than a link.
|
||||
|
||||
Reference in New Issue
Block a user