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>
5.2 KiB
AGENTS.md — internal/, and the boundaries between the packages in it
Seven packages, one direction of knowledge. The diagram is in
cli/AGENTS.md; this file owns the rules that hold it and the
tests that fail when one is broken. Each package's own document owns what is
inside it.
The rule in one sentence: read the diagram bottom-up and each layer knows
strictly less about trackers than the one above it. A tracker concept — an issue
number, a login, an HTTP call, a label colour — that shows up in
issue is in the wrong place, and a domain concept — a
section, an acceptance criterion, a type taxonomy — that shows up in
gitea is in the wrong place too.
Four rules, seven tests
Each test fails on a real mistake rather than on a naming convention.
| rule | enforced by |
|---|---|
issue may import project and the standard library, and nothing else |
TestDomainDependsOnNothing walks go list -deps and fails on any import path with a dot in its first element — which is what keeps yaml and the SDK out of the domain; TestDomainDoesNotReachTheNetworkOrTheShell names net/http, net, os/exec and encoding/json, standard library the first test cannot catch |
wire imports only the standard library |
TestWireDependsOnNothing and TestWireReachesNeitherTheNetworkNorTheDisk, the same two checks |
gitea must not import issue or mapping |
TestTransportDoesNotImportTheDomain — the transport knows numbers, logins, HTTP and JSON, and none of what they mean |
mapping reaches for nothing but the domain, wire and the SDK, and does no I/O |
TestTheBridgeTranslatesAndNothingElse on its direct imports, with os, net/http, internal/gitea, internal/config and internal/project named; TestTheBridgeHasNoClock greps its sources for time.Now |
The domain's two tests were untouched by the migration to the Gitea SDK, and that is the point: the domain did not notice it happened.
The one rule that got weaker, and why the trade was taken
The payload shapes used to live in 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. Note
the deliberate asymmetry with the domain's test: this one checks direct
imports, because the domain reaches os through project and that is the
domain's business. time is allowed here 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.
Why wire still exists
It existed 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.
Adding a package here
Three questions, in order:
- What does it know that its neighbours must not? A package that cannot answer this is a file in an existing one.
- Which direction does it import? Draw it into the diagram in
cli/AGENTS.mdbefore writing code; an arrow that has to point both ways means the split is in the wrong place. - What test fails when the boundary is crossed? Write it with the package, not after. Every rule above has one, and each of them exists because the equivalent convention in the Python version was a grep somebody eventually forgot to run.
Then give it an AGENTS.md, add it to the table in cli/AGENTS.md,
and add its rule to the table above.
Keeping this file true
- Scope: the boundaries between the packages under
internal/— the four rules, the seven tests that hold them, and the history of the one that changed. Files: everylayering_test.go, plusTestTransportDoesNotImportTheDomainingitea/client_test.go. - Update it when a layering test is added, renamed, removed or weakened; when a package is added or removed; or when an import that was forbidden becomes allowed — that last one always comes with a reason, and the reason is what this file is for.
- Do not restate what a package does. The table links to the file that says so.