Files
marketplace/cli/internal/AGENTS.md
T
naudachu 01fb5a2703 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>
2026-08-12 01:01:27 +05:00

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:

  1. What does it know that its neighbours must not? A package that cannot answer this is a file in an existing one.
  2. Which direction does it import? Draw it into the diagram in cli/AGENTS.md before writing code; an arrow that has to point both ways means the split is in the wrong place.
  3. 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: every layering_test.go, plus TestTransportDoesNotImportTheDomain in gitea/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.