detached f03e6ca

docs: add AGENTS.md for AI coding agents

commit
f03e6ca512c5d4e5eb9ae7158fe1b4606e6383ad
author
brice · · 2026-03-14 03:51 UTC
parent
265ee4d
signature
unsigned
1 file · +161 -0
A AGENTS.md +161 -0
@@ -0,0 +1,161 @@
1+# AGENTS.md — Gordon
2+
3+Guidelines for AI coding agents operating in this repository.
4+
5+## Project
6+
7+Gordon is a self-hosted container deployment platform: Docker registry + reverse proxy + auto-deploy.
8+Module: `github.com/bnema/gordon`. Go 1.25+. Single binary, no external database.
9+
10+## Build & Test Commands
11+
12+```bash
13+# Build
14+go build ./...
15+
16+# Test — full suite (~1800 tests, 48 packages)
17+go test ./...
18+
19+# Test — single package
20+go test ./internal/usecase/container/...
21+
22+# Test — single test function
23+go test ./internal/usecase/container/... -run TestService_ListNetworks -v
24+
25+# Test — by layer
26+go test ./internal/adapters/... # adapters only
27+go test ./internal/usecase/... # use cases only
28+
29+# Lint (MUST pass before any commit)
30+golangci-lint run ./...
31+
32+# Generate mocks (after changing boundaries/in or boundaries/out interfaces)
33+mockery
34+```
35+
36+Lint config is in `.golangci.yml` (v2): errcheck, gocyclo (max 15), gosec, govet, staticcheck.
37+Errcheck is relaxed for `Close`, `Write`, `Publish`, `Stop`, `RemoveContainer`, and zerowrap log methods.
38+Test files are exempt from gocyclo, errcheck, and gosec.
39+
40+## Architecture — Hexagonal
41+
42+```
43+internal/
44+ domain/ # Entities, value objects, sentinel errors, labels
45+ boundaries/ # Port interfaces (in = driving, out = driven)
46+ in/ # Service interfaces (ConfigService, ContainerService, etc.)
47+ out/ # Infrastructure interfaces (ContainerRuntime, BlobStorage, etc.)
48+ usecase/ # Business logic (container/, config/, registry/, proxy/, etc.)
49+ adapters/ # Implementations
50+ in/ # Driving adapters
51+ http/admin/ # Admin HTTP API (handler.go)
52+ cli/ # Cobra CLI commands
53+ remote/ # HTTP client for remote Gordon instances
54+ ui/ # Terminal UI components (tables, spinners, styles)
55+ out/ # Driven adapters (docker/, filesystem/, sqlite/)
56+ dto/ # Data transfer objects (request/response structs)
57+ app/ # Application bootstrap (Kernel wiring)
58+```
59+
60+Dependencies flow inward: adapters → boundaries → domain. Never import adapters from domain/usecase.
61+
62+## Code Style
63+
64+### Imports
65+
66+Three groups separated by blank lines: stdlib, external, internal.
67+
68+```go
69+import (
70+ "context"
71+ "fmt"
72+
73+ "github.com/spf13/cobra"
74+
75+ "github.com/bnema/gordon/internal/domain"
76+)
77+```
78+
79+### Error Handling
80+
81+- Sentinel errors in `internal/domain/errors.go` — use `errors.New`, check with `errors.Is`.
82+- Wrap errors with context: `fmt.Errorf("failed to list routes: %w", err)`.
83+- Usecase/service methods return `error`. Handlers translate to HTTP status codes.
84+- CLI commands return `error` from `RunE` — cobra prints it.
85+
86+### Logging
87+
88+Use `zerowrap` (structured zerolog wrapper) in usecase and adapter layers:
89+
90+```go
91+ctx = zerowrap.CtxWithFields(ctx, map[string]any{
92+ zerowrap.FieldLayer: "usecase",
93+ zerowrap.FieldUseCase: "AddRoute",
94+ "domain": route.Domain,
95+})
96+log := zerowrap.FromCtx(ctx)
97+log.Info().Str("image", route.Image).Msg("route added")
98+```
99+
100+CLI commands do NOT use zerowrap — they use `cliWriteLine`/`cliWritef` for output.
101+
102+### CLI Commands (Cobra)
103+
104+- Each command lives in its own file under `internal/adapters/in/cli/`.
105+- Use `cmd.OutOrStdout()` for output (testable), never raw `fmt.Println`.
106+- Use helpers: `cliWriteLine(out, msg)`, `cliWritef(out, fmt, args...)`, `cliRenderTitle`, `cliRenderMeta`, `cliRenderMuted`, `cliRenderSuccess`, `cliRenderWarning`.
107+- All list/show commands support `--json` via `writeJSON(out, v)`.
108+- Use `cmd.Context()` for context (not `context.Background()`).
109+- Commands register in `root.go` under `groupServer`, `groupManage`, or `groupClient`.
110+- Local/remote dispatch via `resolveControlPlane(configPath)` or `GetRemoteClient()`.
111+
112+### ControlPlane Pattern
113+
114+`ControlPlane` interface (`controlplane.go`) abstracts local vs remote operations.
115+- `controlplane_remote.go` — delegates to `remote.Client` HTTP methods.
116+- `controlplane_local.go` — calls service interfaces directly.
117+- Test fakes in `push_test.go` — update when adding interface methods.
118+
119+### HTTP Admin Handlers
120+
121+- Single `Handler` struct in `handler.go` with `matchRoute` dispatcher.
122+- Permission checks: `HasAccess(ctx, resource, action)` at the top of each handler.
123+- Response via `h.sendJSON(w, status, payload)` or `h.sendError(w, status, msg)`.
124+- DTOs in `internal/adapters/dto/` with JSON tags.
125+
126+### Mocks
127+
128+Generated by mockery (config in `.mockery.yaml`). Output: `internal/boundaries/in/mocks/`.
129+Only boundary interfaces are mocked. Run `mockery` after modifying any interface in `boundaries/`.
130+
131+### Testing
132+
133+- Framework: `testing` + `testify` (assert/require).
134+- Test files: `*_test.go` in the same package.
135+- Use `httptest.NewServer` for HTTP handler tests.
136+- Use mockery-generated mocks for boundary interfaces.
137+- Hand-rolled fakes for CLI `ControlPlane` in test files.
138+
139+### Naming
140+
141+- Files: `snake_case.go`.
142+- Types: `PascalCase`. Interfaces describe capability (`ConfigService`, `ContainerRuntime`).
143+- Functions: `PascalCase` exported, `camelCase` unexported.
144+- CLI constructors: `newXxxCmd()` returns `*cobra.Command`.
145+- Runner functions: `runXxx(ctx, ..., out, jsonOut)`.
146+- Domain labels: constants in `domain/labels.go` prefixed with `Label`.
147+
148+### Documentation
149+
150+- CLI docs in `docs/cli/`. Every command page has `## Related` links at the bottom.
151+- Docs follow the pattern: header, subcommands table, usage examples, flags table, JSON output example.
152+
153+## Key Rules
154+
155+1. **Run `golangci-lint run ./...` before committing** — CI enforces it.
156+2. **gocyclo max 15** — extract helpers to reduce complexity.
157+3. **errcheck enabled** — handle or explicitly discard (`_, _ =`) all error returns.
158+4. **`AddRoute` is an upsert** — same domain+image = noop, different image = overwrite. No `ErrRouteExists` in practice.
159+5. **Never expose Docker IDs or raw labels** in CLI/API output — security hardening.
160+6. **Networks are filtered** by both name prefix AND `gordon.managed=true` label.
161+7. **Secrets list shows names only** — values are never exposed via CLI or API (by design).
@@ -0,0 +1,161 @@
1+# AGENTS.md — Gordon
2+
3+Guidelines for AI coding agents operating in this repository.
4+
5+## Project
6+
7+Gordon is a self-hosted container deployment platform: Docker registry + reverse proxy + auto-deploy.
8+Module: `github.com/bnema/gordon`. Go 1.25+. Single binary, no external database.
9+
10+## Build & Test Commands
11+
12+```bash
13+# Build
14+go build ./...
15+
16+# Test — full suite (~1800 tests, 48 packages)
17+go test ./...
18+
19+# Test — single package
20+go test ./internal/usecase/container/...
21+
22+# Test — single test function
23+go test ./internal/usecase/container/... -run TestService_ListNetworks -v
24+
25+# Test — by layer
26+go test ./internal/adapters/... # adapters only
27+go test ./internal/usecase/... # use cases only
28+
29+# Lint (MUST pass before any commit)
30+golangci-lint run ./...
31+
32+# Generate mocks (after changing boundaries/in or boundaries/out interfaces)
33+mockery
34+```
35+
36+Lint config is in `.golangci.yml` (v2): errcheck, gocyclo (max 15), gosec, govet, staticcheck.
37+Errcheck is relaxed for `Close`, `Write`, `Publish`, `Stop`, `RemoveContainer`, and zerowrap log methods.
38+Test files are exempt from gocyclo, errcheck, and gosec.
39+
40+## Architecture — Hexagonal
41+
42+```
43+internal/
44+ domain/ # Entities, value objects, sentinel errors, labels
45+ boundaries/ # Port interfaces (in = driving, out = driven)
46+ in/ # Service interfaces (ConfigService, ContainerService, etc.)
47+ out/ # Infrastructure interfaces (ContainerRuntime, BlobStorage, etc.)
48+ usecase/ # Business logic (container/, config/, registry/, proxy/, etc.)
49+ adapters/ # Implementations
50+ in/ # Driving adapters
51+ http/admin/ # Admin HTTP API (handler.go)
52+ cli/ # Cobra CLI commands
53+ remote/ # HTTP client for remote Gordon instances
54+ ui/ # Terminal UI components (tables, spinners, styles)
55+ out/ # Driven adapters (docker/, filesystem/, sqlite/)
56+ dto/ # Data transfer objects (request/response structs)
57+ app/ # Application bootstrap (Kernel wiring)
58+```
59+
60+Dependencies flow inward: adapters → boundaries → domain. Never import adapters from domain/usecase.
61+
62+## Code Style
63+
64+### Imports
65+
66+Three groups separated by blank lines: stdlib, external, internal.
67+
68+```go
69+import (
70+ "context"
71+ "fmt"
72+
73+ "github.com/spf13/cobra"
74+
75+ "github.com/bnema/gordon/internal/domain"
76+)
77+```
78+
79+### Error Handling
80+
81+- Sentinel errors in `internal/domain/errors.go` — use `errors.New`, check with `errors.Is`.
82+- Wrap errors with context: `fmt.Errorf("failed to list routes: %w", err)`.
83+- Usecase/service methods return `error`. Handlers translate to HTTP status codes.
84+- CLI commands return `error` from `RunE` — cobra prints it.
85+
86+### Logging
87+
88+Use `zerowrap` (structured zerolog wrapper) in usecase and adapter layers:
89+
90+```go
91+ctx = zerowrap.CtxWithFields(ctx, map[string]any{
92+ zerowrap.FieldLayer: "usecase",
93+ zerowrap.FieldUseCase: "AddRoute",
94+ "domain": route.Domain,
95+})
96+log := zerowrap.FromCtx(ctx)
97+log.Info().Str("image", route.Image).Msg("route added")
98+```
99+
100+CLI commands do NOT use zerowrap — they use `cliWriteLine`/`cliWritef` for output.
101+
102+### CLI Commands (Cobra)
103+
104+- Each command lives in its own file under `internal/adapters/in/cli/`.
105+- Use `cmd.OutOrStdout()` for output (testable), never raw `fmt.Println`.
106+- Use helpers: `cliWriteLine(out, msg)`, `cliWritef(out, fmt, args...)`, `cliRenderTitle`, `cliRenderMeta`, `cliRenderMuted`, `cliRenderSuccess`, `cliRenderWarning`.
107+- All list/show commands support `--json` via `writeJSON(out, v)`.
108+- Use `cmd.Context()` for context (not `context.Background()`).
109+- Commands register in `root.go` under `groupServer`, `groupManage`, or `groupClient`.
110+- Local/remote dispatch via `resolveControlPlane(configPath)` or `GetRemoteClient()`.
111+
112+### ControlPlane Pattern
113+
114+`ControlPlane` interface (`controlplane.go`) abstracts local vs remote operations.
115+- `controlplane_remote.go` — delegates to `remote.Client` HTTP methods.
116+- `controlplane_local.go` — calls service interfaces directly.
117+- Test fakes in `push_test.go` — update when adding interface methods.
118+
119+### HTTP Admin Handlers
120+
121+- Single `Handler` struct in `handler.go` with `matchRoute` dispatcher.
122+- Permission checks: `HasAccess(ctx, resource, action)` at the top of each handler.
123+- Response via `h.sendJSON(w, status, payload)` or `h.sendError(w, status, msg)`.
124+- DTOs in `internal/adapters/dto/` with JSON tags.
125+
126+### Mocks
127+
128+Generated by mockery (config in `.mockery.yaml`). Output: `internal/boundaries/in/mocks/`.
129+Only boundary interfaces are mocked. Run `mockery` after modifying any interface in `boundaries/`.
130+
131+### Testing
132+
133+- Framework: `testing` + `testify` (assert/require).
134+- Test files: `*_test.go` in the same package.
135+- Use `httptest.NewServer` for HTTP handler tests.
136+- Use mockery-generated mocks for boundary interfaces.
137+- Hand-rolled fakes for CLI `ControlPlane` in test files.
138+
139+### Naming
140+
141+- Files: `snake_case.go`.
142+- Types: `PascalCase`. Interfaces describe capability (`ConfigService`, `ContainerRuntime`).
143+- Functions: `PascalCase` exported, `camelCase` unexported.
144+- CLI constructors: `newXxxCmd()` returns `*cobra.Command`.
145+- Runner functions: `runXxx(ctx, ..., out, jsonOut)`.
146+- Domain labels: constants in `domain/labels.go` prefixed with `Label`.
147+
148+### Documentation
149+
150+- CLI docs in `docs/cli/`. Every command page has `## Related` links at the bottom.
151+- Docs follow the pattern: header, subcommands table, usage examples, flags table, JSON output example.
152+
153+## Key Rules
154+
155+1. **Run `golangci-lint run ./...` before committing** — CI enforces it.
156+2. **gocyclo max 15** — extract helpers to reduce complexity.
157+3. **errcheck enabled** — handle or explicitly discard (`_, _ =`) all error returns.
158+4. **`AddRoute` is an upsert** — same domain+image = noop, different image = overwrite. No `ErrRouteExists` in practice.
159+5. **Never expose Docker IDs or raw labels** in CLI/API output — security hardening.
160+6. **Networks are filtered** by both name prefix AND `gordon.managed=true` label.
161+7. **Secrets list shows names only** — values are never exposed via CLI or API (by design).

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close