# 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.