v3.3.0 b164fe9

AGENTS.md RawHistoryFolder

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

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

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:

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) is the seam CLI commands depend on.

  • *remote.Client implements it for both the explicit remote and the local admin socket.
  • Tests use the mockery mock in cli/mocks/ — run mockery after 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.
source
1# AGENTS.md — Gordon
2
3Guidelines for AI coding agents operating in this repository.
4
5## Project
6
7Gordon is a self-hosted container deployment platform: Docker registry + reverse proxy + auto-deploy.
8Module: `github.com/bnema/gordon`. Go 1.25+. Single binary, no external database.
9
10## Build & Test Commands
11
12```bash
13# Build
14go build ./...
15
16# Test — full suite (~1800 tests, 48 packages)
17go test ./...
18
19# Test — single package
20go test ./internal/usecase/container/...
21
22# Test — single test function
23go test ./internal/usecase/container/... -run TestService_ListNetworks -v
24
25# Test — by layer
26go test ./internal/adapters/...     # adapters only
27go test ./internal/usecase/...      # use cases only
28
29# Lint (MUST pass before any commit)
30golangci-lint run ./...
31
32# Generate mocks (after changing boundaries/in or boundaries/out interfaces)
33mockery
34```
35
36Lint config is in `.golangci.yml` (v2): errcheck, gocyclo (max 15), gosec, govet, staticcheck.
37Errcheck is relaxed for `Close`, `Write`, `Publish`, `Stop`, `RemoveContainer`, and zerowrap log methods.
38Test files are exempt from gocyclo, errcheck, and gosec.
39
40## Architecture — Hexagonal
41
42```
43internal/
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
60Dependencies flow inward: adapters → boundaries → domain. Never import adapters from domain/usecase.
61
62## Code Style
63
64### Imports
65
66Three groups separated by blank lines: stdlib, external, internal.
67
68```go
69import (
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
88Use `zerowrap` (structured zerolog wrapper) in usecase and adapter layers:
89
90```go
91ctx = zerowrap.CtxWithFields(ctx, map[string]any{
92    zerowrap.FieldLayer:   "usecase",
93    zerowrap.FieldUseCase: "AddRoute",
94    "domain":              route.Domain,
95})
96log := zerowrap.FromCtx(ctx)
97log.Info().Str("image", route.Image).Msg("route added")
98```
99
100CLI 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`) is the seam CLI commands depend on.
115- `*remote.Client` implements it for both the explicit remote and the local admin socket.
116- Tests use the mockery mock in `cli/mocks/` — run `mockery` after adding interface methods.
117
118### HTTP Admin Handlers
119
120- Single `Handler` struct in `handler.go` with `matchRoute` dispatcher.
121- Permission checks: `HasAccess(ctx, resource, action)` at the top of each handler.
122- Response via `h.sendJSON(w, status, payload)` or `h.sendError(w, status, msg)`.
123- DTOs in `internal/adapters/dto/` with JSON tags.
124
125### Mocks
126
127Generated by mockery (config in `.mockery.yaml`). Output: `internal/boundaries/in/mocks/`.
128Only boundary interfaces are mocked. Run `mockery` after modifying any interface in `boundaries/`.
129
130### Testing
131
132- Framework: `testing` + `testify` (assert/require).
133- Test files: `*_test.go` in the same package.
134- Use `httptest.NewServer` for HTTP handler tests.
135- Use mockery-generated mocks for boundary interfaces.
136- Hand-rolled fakes for CLI `ControlPlane` in test files.
137
138### Naming
139
140- Files: `snake_case.go`.
141- Types: `PascalCase`. Interfaces describe capability (`ConfigService`, `ContainerRuntime`).
142- Functions: `PascalCase` exported, `camelCase` unexported.
143- CLI constructors: `newXxxCmd()` returns `*cobra.Command`.
144- Runner functions: `runXxx(ctx, ..., out, jsonOut)`.
145- Domain labels: constants in `domain/labels.go` prefixed with `Label`.
146
147### Documentation
148
149- CLI docs in `docs/cli/`. Every command page has `## Related` links at the bottom.
150- Docs follow the pattern: header, subcommands table, usage examples, flags table, JSON output example.
151
152## Key Rules
153
1541. **Run `golangci-lint run ./...` before committing** — CI enforces it.
1552. **gocyclo max 15** — extract helpers to reduce complexity.
1563. **errcheck enabled** — always handle all error returns gracefully by wrapping with context (fmt.Errorf) and logging critical errors with zerowrap.
157

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