The shape of the code
The layers, what may import what, the linters that hold the line, and where tests live.
The Go code is arranged in layers. Tools enforce the arrangement, so nobody has to remember it.
The layers
cli
|
server
|
rpc, mcp handlers
|
services agents, mail, reviews, ...
| |
store adapters: git, tmux, harness, forge
| |
Postgres the outside world
ui a client, talks over Connect only
Handlers turn a request into one call on a service. Services hold the rules. Stores read and write their own tables. Anything komrad does not own, like tmux or git, sits behind an adapter. The harness adapter is the only place that knows how Claude and Codex differ. The panel is a client and has no database connection.
Imports
The arrows above are enforced by depguard. For instance, the panel may not import tmux or the store. A service may not import the generated protos, so wire shapes cannot leak into the rules. A store may not import a service. The generated query package may be imported only by stores. Cross a line and the linter fails.
Linters
Komrad uses a lot of golangci-lint linters to enforce structure. Many are standard. Some are ours, in a plugin under tools/komradlint, written when the same review comment came up one too many times.
For instance, every sentinel error must be made with a fail constructor that names its kind, so its RPC code is decided where it is declared. A handler body must be a single call. A constructor must be NewT(NewTParams). Only config may read the environment. No bare HTTP client is allowed outside one package. A forge secret may be exposed in three named places and nowhere else.
Tests have rules too. A test file must sit beside a source file of the same name. A test may not declare a method that matches a store method, which keeps services tested against the real store. Unit tests may not run git directly. They take the seam and hand in a fake.
Tests
Each behaviour is tested at the lowest layer that can see it, and only there.
store real Postgres queries, ordering
service real store, fakes the rules
adapter a fake service validation, error codes
scenario real everything a feature end to end
panel frame and requests keys, screens, forms
There is one shared fake per outside seam: a fake tmux, a fake harness, fake git. A regression is a new case in the table that owns the behaviour, not a new test function.
The check
mise run check runs everything: formatting, the linters, every test, and the generated-file drift check. It reports every failure at the end instead of stopping at the first. CI runs the same tasks. There are no git hooks.