01fb5a2703
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>
88 lines
5.2 KiB
Markdown
88 lines
5.2 KiB
Markdown
# AGENTS.md — internal/, and the boundaries between the packages in it
|
|
|
|
Seven packages, one direction of knowledge. The diagram is in
|
|
[`cli/AGENTS.md`](../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`](issue/AGENTS.md) is in the wrong place, and a domain concept — a
|
|
section, an acceptance criterion, a type taxonomy — that shows up in
|
|
[`gitea`](gitea/AGENTS.md) 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`](issue/AGENTS.md) may import [`project`](project/AGENTS.md) 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`](wire/AGENTS.md) imports **only** the standard library | `TestWireDependsOnNothing` and `TestWireReachesNeitherTheNetworkNorTheDisk`, the same two checks |
|
|
| [`gitea`](gitea/AGENTS.md) must not import [`issue`](issue/AGENTS.md) **or** [`mapping`](mapping/AGENTS.md) | `TestTransportDoesNotImportTheDomain` — the transport knows numbers, logins, HTTP and JSON, and none of what they mean |
|
|
| [`mapping`](mapping/AGENTS.md) 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`](../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`](../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.
|