# AGENTS.md — Gordon Guidelines for AI coding agents operating in this repository. ## Project Gordon is a self-hosted container deployment platform: Docker registry + reverse proxy + auto-deploy. Module: `github.com/bnema/gordon`. Go 1.25+. Single binary, no external database. ## Build & Test Commands ```bash # Build go build ./... # Test — full suite (~1800 tests, 48 packages) go test ./... # Test — single package go test ./internal/usecase/container/... # Test — single test function go test ./internal/usecase/container/... -run TestService_ListNetworks -v # Test — by layer go test ./internal/adapters/... # adapters only go test ./internal/usecase/... # use cases only # Lint (MUST pass before any commit) golangci-lint run ./... # Generate mocks (after changing boundaries/in or boundaries/out interfaces) mockery ``` Lint config is in `.golangci.yml` (v2): errcheck, gocyclo (max 15), gosec, govet, staticcheck. Errcheck is relaxed for `Close`, `Write`, `Publish`, `Stop`, `RemoveContainer`, and zerowrap log methods. Test files are exempt from gocyclo, errcheck, and gosec. ## Architecture — Hexagonal ``` internal/ domain/ # Entities, value objects, sentinel errors, labels boundaries/ # Port interfaces (in = driving, out = driven) in/ # Service interfaces (ConfigService, ContainerService, etc.) out/ # Infrastructure interfaces (ContainerRuntime, BlobStorage, etc.) usecase/ # Business logic (container/, config/, registry/, proxy/, etc.) adapters/ # Implementations in/ # Driving adapters http/admin/ # Admin HTTP API (handler.go) cli/ # Cobra CLI commands remote/ # HTTP client for remote Gordon instances ui/ # Terminal UI components (tables, spinners, styles) out/ # Driven adapters (docker/, filesystem/, sqlite/) dto/ # Data transfer objects (request/response structs) app/ # Application bootstrap (Kernel wiring) ``` Dependencies flow inward: adapters → boundaries → domain. Never import adapters from domain/usecase. ## Code Style ### Imports Three groups separated by blank lines: stdlib, external, internal. ```go import ( "context" "fmt" "github.com/spf13/cobra" "github.com/bnema/gordon/internal/domain" ) ``` ### Error Handling - Sentinel errors in `internal/domain/errors.go` — use `errors.New`, check with `errors.Is`. - Wrap errors with context: `fmt.Errorf("failed to list routes: %w", err)`. - Usecase/service methods return `error`. Handlers translate to HTTP status codes. - CLI commands return `error` from `RunE` — cobra prints it. ### Logging Use `zerowrap` (structured zerolog wrapper) in usecase and adapter layers: ```go ctx = zerowrap.CtxWithFields(ctx, map[string]any{ zerowrap.FieldLayer: "usecase", zerowrap.FieldUseCase: "AddRoute", "domain": route.Domain, }) log := zerowrap.FromCtx(ctx) log.Info().Str("image", route.Image).Msg("route added") ``` CLI commands do NOT use zerowrap — they use `cliWriteLine`/`cliWritef` for output. ### CLI Commands (Cobra) - Each command lives in its own file under `internal/adapters/in/cli/`. - Use `cmd.OutOrStdout()` for output (testable), never raw `fmt.Println`. - Use helpers: `cliWriteLine(out, msg)`, `cliWritef(out, fmt, args...)`, `cliRenderTitle`, `cliRenderMeta`, `cliRenderMuted`, `cliRenderSuccess`, `cliRenderWarning`. - All list/show commands support `--json` via `writeJSON(out, v)`. - Use `cmd.Context()` for context (not `context.Background()`). - Commands register in `root.go` under `groupServer`, `groupManage`, or `groupClient`. - Local/remote dispatch via `resolveControlPlane(configPath)` or `GetRemoteClient()`. ### ControlPlane Pattern `ControlPlane` interface (`controlplane.go`) abstracts local vs remote operations. - `controlplane_remote.go` — delegates to `remote.Client` HTTP methods. - `controlplane_local.go` — calls service interfaces directly. - Test fakes in `push_test.go` — update when adding interface methods. ### HTTP Admin Handlers - Single `Handler` struct in `handler.go` with `matchRoute` dispatcher. - Permission checks: `HasAccess(ctx, resource, action)` at the top of each handler. - Response via `h.sendJSON(w, status, payload)` or `h.sendError(w, status, msg)`. - DTOs in `internal/adapters/dto/` with JSON tags. ### Mocks Generated by mockery (config in `.mockery.yaml`). Output: `internal/boundaries/in/mocks/`. Only boundary interfaces are mocked. Run `mockery` after modifying any interface in `boundaries/`. ### Testing - Framework: `testing` + `testify` (assert/require). - Test files: `*_test.go` in the same package. - Use `httptest.NewServer` for HTTP handler tests. - Use mockery-generated mocks for boundary interfaces. - Hand-rolled fakes for CLI `ControlPlane` in test files. ### Naming - Files: `snake_case.go`. - Types: `PascalCase`. Interfaces describe capability (`ConfigService`, `ContainerRuntime`). - Functions: `PascalCase` exported, `camelCase` unexported. - CLI constructors: `newXxxCmd()` returns `*cobra.Command`. - Runner functions: `runXxx(ctx, ..., out, jsonOut)`. - Domain labels: constants in `domain/labels.go` prefixed with `Label`. ### Documentation - CLI docs in `docs/cli/`. Every command page has `## Related` links at the bottom. - Docs follow the pattern: header, subcommands table, usage examples, flags table, JSON output example. ## Key Rules 1. **Run `golangci-lint run ./...` before committing** — CI enforces it. 2. **gocyclo max 15** — extract helpers to reduce complexity. 3. **errcheck enabled** — always handle all error returns gracefully by wrapping with context (fmt.Errorf) and logging critical errors with zerowrap.