style
esc
  • Loading…

For agentsGo Style Guide

Conventions for writing, structuring, testing and shipping Go 1.27 services, agents and CLIs that stay simple, explicit and easy to read.

Go 1.27golangci-lintslog · synctest

The same guide as plain Markdown, built from the same source as the web edition. Rules stay machine-readable: each one starts with its stable ID and level, such as `GO-02.1` [MUST], so an agent can cite exactly the rule it means.

Rules digest

~9.3k tokens

237 rules, grouped by chapter, no prose or examples. Small enough to keep in an agent's context on every task.

Full guide

~13.3k tokens

Every chapter, table, code sample and drop-in config as one Markdown file. Best when the agent can read files on demand.

Pin a revision

The URLs above always serve the latest text, so an edit to the guide changes what your agent follows. Add @2026.10 after the guide name for a copy that never changes; the snippets below already do. To upgrade, read the changelog for the rules that were added, removed or changed, then bump the revision.

# Revision 2026.10: these files never change
curl -fsSL https://style.chopper.dev/go@2026.10/rules.md
curl -fsSL https://style.chopper.dev/go@2026.10/llms.txt

# What changed between revisions
curl -fsSL https://style.chopper.dev/go/changelog.md

Template project

A ready-to-run invoice service with the guide's cmd/internal layout, a pgx and sqlc PostgreSQL store with goose migrations, a stdlib HTTP API with problem+json errors, slog logging, Prometheus RED metrics and pprof on an internal listener, graceful shutdown, the appendix golangci-lint and Taskfile configs, table, synctest, fuzz and integration tests, Docker, GoReleaser and CI. Both Markdown files above describe it, so an agent asked to start a new Go codebase can fetch a renamed copy instead of scaffolding one by hand.

curl -fsSL "https://style.chopper.dev/go/template/download?name=Project" \
  -o project.zip && unzip project.zip
ParameterDefaultMeaning
nameProjectPascalCase. Lowercased, it names the binary (name + d) and the default module path.
modulederivedWhere the module will live, e.g. github.com/you/invoicer. Defaults to example.com/<name>.

Use it with your tools

MCP server / Claude Code plugin

Connect the remote MCP server, or install the plugin, which adds the server plus a go-style skill that loads these rules whenever Claude writes or reviews Go.

claude mcp add --transport http chopper-style https://style.chopper.dev/mcp

# or the plugin: MCP server + one skill per guide
claude plugin marketplace add carson-hopper/chopper.dev
claude plugin install chopper-style@chopper-dev

Claude Code / AGENTS.md

Save the guide into the repo, then point the project instructions file (CLAUDE.md or AGENTS.md) at it.

curl -fsSL https://style.chopper.dev/go@2026.10/llms.txt -o docs/GO_STYLE.md

# CLAUDE.md / AGENTS.md
## Go style
All Go code follows docs/GO_STYLE.md (rev. 2026.10). Rules marked
[MUST] / [MUST NOT] are non-negotiable; [SHOULD] rules need a comment when broken.
Cite rules by ID (GO-02.1) in reviews and comments.

Cursor

A project rule that applies the digest whenever a Go file is in play.

mkdir -p .cursor/rules
{ printf -- '---\ndescription: Go Style Guide\nglobs: "**/*.go"\nalwaysApply: false\n---\n\n'
  curl -fsSL https://style.chopper.dev/go@2026.10/rules.md; } > .cursor/rules/go-style.mdc

Any chat assistant

Paste this ahead of the request, or attach the digest file.

Follow the Go Style Guide at https://style.chopper.dev/go@2026.10/rules.md for all Go code you write or review. Treat MUST / MUST NOT as hard constraints and cite the rule's ID (such as GO-02.1) when you flag a violation.

Digest preview

# Go Style Guide: rules digest

> Every rule from the Go Style Guide (rev. 2026.10), without the prose, tables and examples. The full guide: https://style.chopper.dev/go/llms.txt

Revision 2026.10. This URL always serves the latest text; pin a revision with https://style.chopper.dev/go@2026.10/rules.md. Changelog: https://style.chopper.dev/go/changelog.md.

MUST / MUST NOT are non-negotiable. SHOULD / SHOULD NOT are the default; deviate only with a short comment saying why. MAY is allowed when it helps. AVOID is allowed but a smell. Cite a rule by its ID (GO-02.1): IDs never change or get reused, and https://style.chopper.dev/go#GO-02.1 links to the rule. The guide is generic. Examples use the module path `example.com/project`, the binary `projectd` and an `invoice` domain; swap in your own. When the guide is silent: match the surrounding code, then this guide, then the wider Go community.

## Starter project

A ready-to-run invoice service with the guide's cmd/internal layout, a pgx and sqlc PostgreSQL store with goose migrations, a stdlib HTTP API with problem+json errors, slog logging, Prometheus RED metrics and pprof on an internal listener, graceful shutdown, the appendix golangci-lint and Taskfile configs, table, synctest, fuzz and integration tests, Docker, GoReleaser and CI. It is generated from this guide and follows it, so prefer it over scaffolding a new codebase by hand.

Download it as a zip, renamed to the user's names, with a GET request (query parameters are optional; missing ones take their defaults, invalid ones return HTTP 400 with a message):

```bash
curl -fsSL "https://style.chopper.dev/go/template/download?name=Project" -o project.zip && unzip project.zip
```

Parameters:

- `name`: Project name. PascalCase. Lowercased, it names the binary (name + d) and the default module path. Must match `^(?:[A-Z][A-Za-z0-9]{1,31})$`. Default `Project`.
- `module`: Module path. Where the module will live, e.g. github.com/you/invoicer. Defaults to example.com/<name>. Must match `^(?:[a-z0-9.-]+(/[A-Za-z0-9._~-]+)+)$`.

After unzipping:

1. **Install.** Install Go 1.27, Task and Docker; every other tool runs through go tool from go.mod, nothing global.
2. **Run.** Run task lint and task test, then docker compose -f deploy/compose.yaml up --build to serve on :8080.

## 02. Baseline and toolchain

- `GO-02.1` **MUST** declare the language version with a `go 1.27.0` line in `go.mod`, and pin the toolchain with a `toolchain` line so every machine and CI builds with the same compiler.
- `GO-02.2` **MUST** commit `go.sum`. [MUST NOT] commit a `vendor/` directory unless the build environment has no module proxy.
- `GO-02.3` **MUST** manage developer tools with `go tool` (the `tool` directive in `go.mod`), not with a `tools.go` file or global installs: `go get -tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint`.
- `GO-02.4` **SHOULD** upgrade to each new Go release within one minor release, running `go fix ./...` to apply the modernizers.
- `GO-02.5` **MUST NOT** add a dependency for something the standard library does well (HTTP routing, structured logging, UUIDs, JSON, slices and maps helpers).
- `GO-02.6` **SHOULD** prefer small, well-maintained dependencies; every new direct dependency is called out in the pull request description.
- `GO-02.7` **MUST** build release binaries with `CGO_ENABLED=0` unless the project needs cgo, and with `-trimpath` and version information injected (§21).

## 03. Project layout

- `GO-03.1` **MUST** put each binary in `cmd/<name>/`, with `main.go` limited to parsing config, building dependencies and calling `run(ctx)`. All logic lives in `internal/`.
- `GO-03.2` **MUST** put code in `internal/` unless another module is meant to import it. [MUST NOT] create a `pkg/` directory by default; a public package lives at the module root or under a named directory and is documented as public API.
- `GO-03.3` **MUST** keep tests beside the code they test, in `<name>_test.go`. Black-box tests use the `<pkg>_test` package; white-box tests use the same package.
- `GO-03.4` **MUST** organise packages by **what they provide** (domain or capability: `invoice`, `postgres`, `httpapi`), not by kind (`models`, `controllers`, `utils`).
- `GO-03.5` **MUST NOT** create `util`, `common`, `helpers`, `misc` or `base` packages.
- `GO-03.6` **MUST NOT** create import cycles; when two packages need each other, move the shared types into the domain package or define a small interface on the consumer side.
- `GO-03.7` **SHOULD** keep test fixtures in a `testdata/` directory next to the test (the go tool ignores it).
- `GO-03.8` **SHOULD** keep generated code in its own files ending in `.gen.go` or under a `gen/` directory, with the generator command in a `//go:generate` line.

## 04. Naming

- `GO-04.1` **MUST** avoid stutter: the package name is part of every call site. `invoice.Service`, not `invoice.InvoiceService`; `postgres.NewStore`, not `postgres.NewPostgresStore`.
- `GO-04.2` **MUST** keep names short in proportion to their scope: `i`, `n`, `r` in a three-line loop; descriptive names for package-level identifiers.
- `GO-04.3` **MUST NOT** use `this` or `self` as a receiver name, and [MUST] use the same receiver name in every method of a type.
- `GO-04.4` **MUST NOT** shadow package names or built-ins (`len`, `new`, `error`, `url`) with local variables.
- `GO-04.5` **SHOULD** name booleans as states or questions: `enabled`, `isLeader`, `hasLines`.
- `GO-04.6` **SHOULD** name functions that return a boolean as questions (`IsOverdue`), and functions with side effects as verbs (`Send`, `Close`).
- `GO-04.7` **AVOID** naming a package after its pattern (`manager`, `handler`, `service`); name it after what it manages.

## 05. Formatting and linting

- `GO-05.1` **MUST** format every file with `gofumpt` (a stricter `gofmt`) and group imports with `goimports -local example.com/project`. CI fails on any diff.
- `GO-05.2` **MUST** indent with **tabs**, as `gofmt` does. This is the one place the 2-space rule from other guides does not apply; Go's formatter is not configurable and that is the point.
- `GO-05.3` **MUST** group imports in three blocks: standard library, third-party, then this module.
- `GO-05.4` **SHOULD** keep lines under about **100** characters. There is no hard limit; break a long call one argument per line with a trailing comma rather than leaving a 200-character line.
- `GO-05.5` **MUST NOT** use dot imports outside tests, or blank imports anywhere but `main` and tests, where database drivers are registered.
- `GO-05.6` **SHOULD** order a file: package doc, imports, constants, variables, types (each followed by its constructor and methods), then free functions.
- `GO-05.7` **MUST** run `go vet ./...` and `golangci-lint run` in CI with the committed `.golangci.yml` (Appendix A). The build fails on any finding.
- `GO-05.8` **MUST NOT** silence a linter without a reason: `//nolint:gosec // path is from a constant, not user input`. Blanket `//nolint` is forbidden.
- `GO-05.9` **MUST** run `govulncheck ./...` in CI and on a schedule.
- `GO-05.10` **SHOULD** run `go fix ./...` (the Go 1.26+ modernizers) when upgrading Go, and commit the result in its own `refactor:` commit.
- `GO-05.11` **SHOULD** add `//go:fix inline` to deprecated wrapper functions in public packages so callers can migrate automatically.
- `GO-05.12` **MUST** expose `task lint` and `task test` (or `make lint` / `make test`), and run exactly those in CI (Appendix B).

## 06. Packages, APIs and interfaces

- `GO-06.1` **MUST** give each package one clear purpose that its name states; if the package doc needs "and", consider splitting it.
- `GO-06.2` **MUST** keep the exported surface small. Export a name only when another package uses it today.
- `GO-06.3` **MUST NOT** use package-level mutable state (globals, `init()` side effects) beyond registering drivers or metrics. Pass dependencies explicitly.
- `GO-06.4` **AVOID** `init()` functions; when one is unavoidable, it must not do I/O or depend on flags or environment.
- `GO-06.5` **SHOULD** keep a package's dependencies pointing inward: `httpapi` and `postgres` import `invoice`; `invoice` imports neither.
- `GO-06.6` **MUST** define interfaces in the package that **uses** them, not the package that implements them. Producers return concrete types.
- `GO-06.7` **MUST** keep interfaces small — usually one to three methods — and declare only the methods the consumer calls.
- `GO-06.8` **MUST NOT** create an interface for a single implementation "for testing" when a real or in-memory implementation would do. Introduce the interface when the second implementation or a test double is actually needed.
- `GO-06.9` **SHOULD** follow "accept interfaces, return structs".
- `GO-06.10` **SHOULD** assert that a type satisfies an interface at compile time where the relationship matters: `var _ invoice.Store = (*Store)(nil)`.
- `GO-06.11` **MUST NOT** embed an interface in a struct to "implement" it partially; a nil method call panics at runtime.
- `GO-06.12` **MUST** follow semantic versioning for any module others import, and bump the major version (`/v2` module path) for breaking changes.
- `GO-06.13` **MUST** mark replaced APIs with a `// Deprecated:` paragraph naming the replacement, and keep them for at least one minor release.

## 07. Types, structs, generics and zero values

- `GO-07.1` **SHOULD** make the zero value useful: a `sync.Mutex`, `bytes.Buffer` or `Config` with sensible defaults needs no constructor. When the zero value cannot be valid, provide `New...` and document that the zero value must not be used.
- `GO-07.2` **MUST** use field names in struct literals for types from other packages: `http.Server{Addr: addr}`, never positional.
- `GO-07.3` **MUST** use pointer receivers when a method mutates the receiver, when the struct holds a `sync.Mutex` or similar, or when it is large; otherwise value receivers. [MUST NOT] mix receiver kinds on one type without reason.
- `GO-07.4` **MUST NOT** copy a struct that contains a mutex, `sync.WaitGroup` or `atomic` value (`go vet` copylocks enforces this).
- `GO-07.5` **SHOULD** use `new(expr)` (Go 1.26) for optional pointer fields instead of a temporary variable: `Age: new(yearsSince(born))`.
- `GO-07.6` **SHOULD** use typed IDs and units to prevent mix-ups: `type InvoiceID uuid.UUID`, `type Cents int64`.
- `GO-07.7` **MUST** store money as integer minor units (`int64` cents) with a currency code, never as `float64`.
- `GO-07.8` **MUST** use UUIDv7 for generated identifiers so they sort by creation time, using the standard `uuid` package (Go 1.27) rather than a third-party module.
- `GO-07.9` **MUST** declare enumerations as a named type with `iota` constants, starting from a meaningful zero (`StatusUnknown`) so an unset value is detectable.
- `GO-07.10` **SHOULD** generate `String()` with `stringer` and implement `encoding.TextMarshaler` / `TextUnmarshaler` so the value round-trips through JSON and config as text.
- `GO-07.11` **MUST** handle every case in a `switch` over an enum and return an error (or panic in truly impossible cases) in `default`; the `exhaustive` linter enforces this.
- `GO-07.12` **SHOULD** reach for generics when the same code would otherwise be written for several types (containers, `slices`/`maps`-style helpers, typed caches, result sets). [AVOID] generics for code that has one caller.
- `GO-07.13` **MUST** prefer the standard `slices`, `maps`, `cmp` and `iter` packages to hand-written loops or third-party helpers.
- `GO-07.14` **MAY** use generic methods (Go 1.27) when a method genuinely needs its own type parameter; remember that interface methods cannot be generic, so such a method can never satisfy an interface.
- `GO-07.15` **SHOULD** keep constraints as loose as the code allows (`any`, `comparable`, `cmp.Ordered`) and name type parameters with one capital letter or a short word (`T`, `K`, `V`, `Item`).
- `GO-07.16` **SHOULD** return iterators (`iter.Seq[T]`, `iter.Seq2[K, V]`) from collections or paged sources that callers range over, instead of building a full slice.

## 08. Error handling

- `GO-08.1` **MUST** check every returned error. [MUST NOT] discard one with `_` unless a comment says why it cannot matter.
- `GO-08.2` **MUST** handle an error once: either log it **or** return it, never both. The top of the call stack (the handler, the job runner, `main`) logs.
- `GO-08.3` **MUST** add context when returning an error from another package, using `%w`: `fmt.Errorf("load invoice %s: %w", id, err)`. Context describes what **this** function was doing, in lower case, with no "failed to" or "error" prefix.
- `GO-08.4` **MUST** keep the happy path left-aligned: handle the error and return early; no `else` after a `return`.
- `GO-08.5` **SHOULD** use `%v` instead of `%w` when callers must not depend on the underlying error (for example, hiding a driver error behind a domain boundary).
- `GO-08.6` **MUST NOT** wrap a pointer to an error value type (`*MyErr` when `MyErr` implements `error`); Go 1.27's vet flags it because `errors.Is` and type checks stop working.
- `GO-08.7` **MUST** compare errors with `errors.Is` and extract them with `errors.AsType[T]` (Go 1.26), never with `==` on wrapped errors or string matching.
- `GO-08.8` **SHOULD** expose **sentinel errors** (`var ErrNotFound = errors.New("invoice: not found")`) for conditions callers branch on, and **error types** when callers need fields.
- `GO-08.9` **MUST** translate storage errors into domain errors at the boundary: `postgres` turns `pgx.ErrNoRows` into `invoice.ErrNotFound`.
- `GO-08.10` **SHOULD** use `errors.Join` to return several independent errors (validation, closing multiple resources).
- `GO-08.11` **MUST NOT** panic for expected failures (bad input, missing rows, network errors). Return an error.
- `GO-08.12` **MAY** panic for programmer errors that make continuing unsafe (an impossible state, a broken invariant), and in `Must...` helpers used only at startup with constant input (`regexp.MustCompile`).
- `GO-08.13` **MUST** recover panics at goroutine and request boundaries (HTTP middleware, worker loops), log them with the stack, and turn them into errors. [MUST NOT] use `recover` for control flow.
- `GO-08.14` **MUST NOT** call `log.Fatal` or `os.Exit` outside `main`; they skip deferred cleanup.

## 09. Functions, methods and control flow

- `GO-09.1` **MUST** put `ctx context.Context` first and `error` last.
- `GO-09.2` **SHOULD** keep functions under about 60 lines and four parameters. When parameters grow, group them in a struct (`Params`, `Options`) or use functional options.
- `GO-09.3` **MUST NOT** use named results except to document two results of the same type or when a deferred function must set the error. [MUST NOT] use naked `return` in functions longer than a few lines.
- `GO-09.4` **MUST NOT** use boolean flag parameters that change behaviour (`Send(inv, true)`); use two functions or an options struct.
- `GO-09.5` **SHOULD** return a concrete value and an error, not a pointer to signal "maybe": `(Invoice, error)` with `ErrNotFound`, rather than `(*Invoice, error)` with `nil, nil`.
- `GO-09.6` **MUST NOT** return `nil, nil`.
- `GO-09.7` **SHOULD** use an options struct for constructors with several optional settings; it is easy to read, document and extend.
- `GO-09.8` **MAY** use functional options (`WithTimeout(d)`) for public APIs where defaults matter and the set of options will grow.
- `GO-09.9` **MUST** return early to keep the main path at the lowest indentation.
- `GO-09.10` **SHOULD** use `switch` with no condition instead of long `if`/`else if` chains.
- `GO-09.11` **SHOULD** range over integers (`for i := range n`) and over functions (`for v := range seq`) instead of three-clause loops where it reads better.
- `GO-09.12` **MUST** use `defer` to release resources right after acquiring them, and check the error from `Close` on writers: `defer func() { err = errors.Join(err, f.Close()) }()`.
- `GO-09.13` **MUST NOT** `defer` inside a loop that runs many times; move the body into a function.
- `GO-09.14` **SHOULD** declare variables close to first use, and prefer `:=` except when the zero value is the point (`var buf bytes.Buffer`).
- `GO-09.15` **AVOID** `goto` and labelled `break`/`continue` except to leave a nested loop or `select`.

## 10. Context and cancellation

- `GO-10.1` **MUST** pass `context.Context` as the first parameter, named `ctx`, to every function that does I/O, blocks or calls something that does.
- `GO-10.2` **MUST NOT** store a context in a struct field. The only exception is a type that represents one request's lifetime and documents it.
- `GO-10.3` **MUST NOT** pass `nil` as a context. Use `context.TODO()` while migrating, and `context.Background()` only in `main`, tests and top-level goroutines.
- `GO-10.4` **MUST** derive the root context from signals in `main` with `signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)`; since Go 1.26 its cancel cause names the signal.
- `GO-10.5` **MUST** set deadlines on every outbound call: `context.WithTimeout` per call, or a client-level timeout. A call with no deadline is a bug waiting for a slow dependency.
- `GO-10.6` **SHOULD** use `context.WithCancelCause` / `WithTimeoutCause` and read `context.Cause(ctx)` so logs say **why** work stopped.
- `GO-10.7` **MUST** call the `cancel` function returned by `WithCancel`/`WithTimeout`, normally with `defer cancel()`.
- `GO-10.8` **SHOULD** use `context.WithoutCancel(ctx)` for work that must finish after the request ends (audit writes), and `context.AfterFunc` instead of a goroutine waiting on `ctx.Done()`.
- `GO-10.9` **MAY** carry request-scoped values (request ID, authenticated principal, trace span) in a context with unexported key types and typed accessors. [MUST NOT] use context values for optional parameters or dependencies.

## 11. Concurrency

- `GO-11.1` **MUST** know how every goroutine ends before starting it: it stops when its context is cancelled, its input channel closes, or its work completes. A goroutine with no exit is a leak.
- `GO-11.2` **MUST** wait for goroutines you start before returning from the function or shutting down: `sync.WaitGroup.Go` (Go 1.25+) or `errgroup.Group`.
- `GO-11.3` **SHOULD** use `golang.org/x/sync/errgroup` with `errgroup.WithContext` for a set of tasks where the first error cancels the rest, and `g.SetLimit(n)` to bound parallelism.
- `GO-11.4` **MUST NOT** start unbounded goroutines per item (one per row, per request to a dependency); bound them with a limit or a worker pool.
- `GO-11.5` **MUST** recover panics in long-lived goroutines that are not covered by a request middleware, and report them.
- `GO-11.6` **SHOULD** check for leaks in tests with the `goroutineleak` profile (generally available in Go 1.27) or `go.uber.org/goleak`.
- `GO-11.7` **SHOULD** use channels to pass ownership of data or signal events between goroutines, and a `sync.Mutex` to protect shared state. Pick the one that makes the code simpler.
- `GO-11.8` **MUST** close a channel only from the sending side, and only once; document which goroutine owns it.
- `GO-11.9` **SHOULD** keep channels unbuffered or size 1 unless a measured reason calls for more; a large buffer usually hides a back-pressure problem.
- `GO-11.10` **MUST** use `select` with `ctx.Done()` on every blocking send or receive that can outlive the caller.
- `GO-11.11` **MUST** keep the mutex next to the fields it guards, declared just above them, and keep critical sections short with no I/O inside.
- `GO-11.12` **SHOULD** use `atomic.Int64`, `atomic.Bool`, `atomic.Pointer[T]` (the typed atomics) instead of the `atomic.AddInt64` functions; `go fix` migrates old code.
- `GO-11.13` **SHOULD** use `sync.OnceValue` / `OnceValues` for lazy initialization.
- `GO-11.14` **MUST** run every test suite with `-race`; a race report is a bug, never flaky.

## 12. Logging and observability

- `GO-12.1` **MUST** log with `log/slog`. [MUST NOT] use `log.Printf`, `fmt.Println` or a third-party logger in new code.
- `GO-12.2` **MUST** inject a `*slog.Logger` into types that log; [AVOID] `slog.Default()` outside `main`.
- `GO-12.3` **MUST** log structured attributes, not formatted strings: `log.InfoContext(ctx, "invoice sent", "invoice_id", id, "attempt", n)`. Keys are `snake_case` and stable.
- `GO-12.4` **SHOULD** use the `...Context` methods so handlers can add request IDs and trace IDs from the context.
- `GO-12.5` **MUST** use levels consistently: `Debug` for developer detail, `Info` for business events and lifecycle, `Warn` for degraded but handled conditions, `Error` for failures someone must look at.
- `GO-12.6` **MUST** emit JSON (`slog.NewJSONHandler`) in production and text locally. Use `slog.NewMultiHandler` (Go 1.26) to send to more than one destination.
- `GO-12.7` **MUST NOT** log secrets, tokens, passwords or personal data; implement `slog.LogValuer` on sensitive types to redact them.
- `GO-12.8` **SHOULD** log an error once, at the boundary, with the full wrapped message under the `err` key.
- `GO-12.9` **SHOULD** instrument services with OpenTelemetry (traces + metrics) or Prometheus metrics; pick one per project and use it everywhere.
- `GO-12.10` **MUST** export the RED metrics for every server (rate, errors, duration) and the USE metrics for every worker pool or queue (utilization, saturation, errors).
- `GO-12.11` **MUST** keep metric label values bounded: no IDs, emails or URLs with parameters as labels.
- `GO-12.12` **SHOULD** expose `net/http/pprof` on a separate, internal-only listener, including the `goroutineleak` profile.
- `GO-12.13` **MUST** expose `/healthz` (process is alive) and `/readyz` (dependencies are reachable) on services.

## 13. Configuration, CLIs and startup

- `GO-13.1` **MUST** load configuration once in `main` into a typed `Config` struct and pass the parts each component needs. [MUST NOT] call `os.Getenv` deep in the code.
- `GO-13.2` **MUST** validate configuration at startup and exit with a clear message listing every problem (use `errors.Join`).
- `GO-13.3` **SHOULD** read configuration from environment variables (twelve-factor) with an optional file for local development; precedence is flags > environment > file > defaults.
- `GO-13.4` **MUST** give every setting a safe default or make it required; document both in a `Config` doc comment or `config.example.toml`.
- `GO-13.5` **MUST NOT** commit secrets. Read them from the environment, a mounted file or a secret manager, and keep them in typed values that redact themselves in logs.
- `GO-13.6` **SHOULD** use `time.Duration` and typed sizes in config (`30s`, `512MiB`), not bare integers with units in the name.
- `GO-13.7` **MUST** use the standard `flag` package for small tools, and `spf13/cobra` (or `urfave/cli`) only when there are subcommands.
- `GO-13.8` **MUST** print usage to stderr, results to stdout, and exit with `0` on success, `1` on failure and `2` on bad usage.
- `GO-13.9` **SHOULD** support `--json` output for any command whose output another tool may read.
- `GO-13.10` **MUST** include a `version` command or `--version` flag that prints the version, commit and build date injected at build time.
- `GO-13.11` **MUST** keep `main` tiny: it calls `run`, prints the error and sets the exit code. `run` takes its inputs (args, env, stdout) as parameters so it is testable.

## 14. HTTP services

- `GO-14.1` **MUST** route with the standard `http.ServeMux` patterns (method + path + wildcards: `"GET /invoices/{id}"`) and read wildcards with `r.PathValue("id")`. [MUST NOT] add a router dependency for routing alone.
- `GO-14.2` **MUST** register all routes in one `routes()` function per server so the whole API is visible in one place.
- `GO-14.3` **MUST** keep handlers thin: decode and validate input, call one service method, encode the response. Business rules live in the domain package.
- `GO-14.4` **SHOULD** write handlers as methods on a server struct that holds dependencies, or as functions returning `http.Handler` that close over them. [MUST NOT] reach for globals.
- `GO-14.5` **SHOULD** use a small adapter so handlers return an error and a single place maps errors to responses.
- `GO-14.6` **MUST** write middleware as `func(http.Handler) http.Handler` and compose it in `routes()` in a visible order: recover → request ID → logging → metrics → auth → handler.
- `GO-14.7` **MUST** protect browser-facing state-changing routes with `http.CrossOriginProtection` (Go 1.25+) or an equivalent CSRF defence.
- `GO-14.8` **MUST** bound request bodies with `http.MaxBytesReader` and reject unknown JSON fields on write endpoints.
- `GO-14.9` **SHOULD** use `encoding/json/v2` (Go 1.27) in new code: it rejects invalid UTF-8 and duplicate keys by default and unmarshals much faster. Existing `encoding/json` code keeps working and is now backed by v2.
- `GO-14.10` **MUST** return errors as RFC 9457 problem details (`application/problem+json`) with the right status code; never leak internal error text on 5xx.
- `GO-14.11` **MUST** set `Content-Type` on every response and write the status before the body.
- `GO-14.12` **MUST** set `ReadHeaderTimeout`, `ReadTimeout`, `WriteTimeout` and `IdleTimeout` on every `http.Server`. [MUST NOT] use `http.ListenAndServe` with the default server in production.
- `GO-14.13` **MUST** shut down gracefully: on context cancellation call `srv.Shutdown` with a deadline, then close dependencies.
- `GO-14.14` **MUST** reuse one `http.Client` per dependency with `Timeout` set. [MUST NOT] use `http.DefaultClient` or `http.Get` for outbound calls.
- `GO-14.15` **MUST** close every response body (`defer resp.Body.Close()`), and check the status code before decoding.
- `GO-14.16` **SHOULD** retry only idempotent requests, with exponential backoff and jitter, honouring `Retry-After`.

## 15. Data access

- `GO-15.1` **MUST** use PostgreSQL through `jackc/pgx/v5` (`pgxpool`) for new services; `database/sql` is fine for other databases or libraries that must stay driver-neutral.
- `GO-15.2` **SHOULD** write SQL by hand and generate type-safe Go with **sqlc**. [AVOID] ORMs; a query builder (`squirrel`) is allowed only for genuinely dynamic filters.
- `GO-15.3` **MUST** pass every value as a bind parameter (`$1`). [MUST NOT] build SQL with `fmt.Sprintf` or string concatenation on input.
- `GO-15.4` **MUST** pass `ctx` to every query so cancellation and deadlines reach the database.
- `GO-15.5` **MUST** keep SQL types out of the domain: the `postgres` package maps rows to `invoice.Invoice` and storage errors to domain errors.
- `GO-15.6` **MUST** run multi-statement writes in a transaction through one helper that commits on success, rolls back on error or panic, and retries serialization failures.
- `GO-15.7` **MUST** use UUIDv7 primary keys generated in Go (or with `uuidv7()` in PostgreSQL 18), stored as the native `uuid` type.
- `GO-15.8` **MUST** close `rows` and check `rows.Err()`; prefer pgx's `pgx.CollectRows` helpers, which do both.
- `GO-15.9` **MUST** configure pool limits (`MaxConns`, `MaxConnLifetime`, `MaxConnIdleTime`) explicitly.
- `GO-15.10` **MUST** manage schema with numbered SQL migrations (`goose` or `golang-migrate`) committed in `migrations/`; each runs in a transaction and is never edited after it ships.
- `GO-15.11` **SHOULD** test stores against a real PostgreSQL started by `testcontainers-go`, not mocks.

## 16. Agents and daemons

- `GO-16.1` **MUST** run every component as `Run(ctx) error` that returns when `ctx` is cancelled, and start them together with an `errgroup` so one failure stops the rest cleanly.
- `GO-16.2` **MUST** handle `SIGTERM` and `SIGINT` by cancelling the root context, then give in-flight work a bounded grace period (for example 15 seconds) before exiting.
- `GO-16.3` **SHOULD** reload configuration on `SIGHUP` without restarting, when the agent supports it.
- `GO-16.4` **MUST** run under a supervisor (systemd with `Restart=on-failure`, a container runtime, or a Windows service) and [MUST NOT] daemonize itself.
- `GO-16.5` **SHOULD** notify systemd of readiness and watchdog pings (`sd_notify`) when deployed with `Type=notify`.
- `GO-16.6` **MUST** drive periodic work with `time.Ticker` (stopped with `defer t.Stop()`) inside a `select` on `ctx.Done()`.
- `GO-16.7` **MUST** retry failed connections with capped exponential backoff and jitter; [MUST NOT] spin in a tight retry loop.
- `GO-16.8` **MUST** buffer reports locally (bounded queue or on-disk spool) while the control plane is unreachable, and drop the oldest data with a counter when the buffer fills.
- `GO-16.9` **MUST** authenticate to the control plane with mTLS or a per-agent token, and pin the control plane's CA.
- `GO-16.10` **MUST** keep resource use bounded and visible: cap goroutines, buffers and file handles, and report memory, goroutine count and queue depth as metrics.
- `GO-16.11` **MUST** verify the signature (or checksum from a signed manifest) of any self-update before swapping binaries, and keep the previous binary for rollback.
- `GO-16.12` **MUST** generate a stable agent ID (UUIDv7) on first start and persist it; never derive identity from hostname or IP alone.
- `GO-16.13` **SHOULD** report version, commit, OS, architecture and uptime with every heartbeat.

## 17. Performance and memory

- `GO-17.1` **MUST** measure before optimizing: a benchmark (`b.Loop()`) or a profile (`pprof` CPU, heap, mutex, block) must show the problem, and the pull request includes before and after numbers from `benchstat`.
- `GO-17.2` **SHOULD** build release binaries with profile-guided optimization (`default.pgo` from a production CPU profile) for hot services.
- `GO-17.3` **SHOULD** preallocate slices and maps when the size is known: `make([]Line, 0, len(rows))`.
- `GO-17.4` **SHOULD** use `strings.Builder` / `bytes.Buffer` for building strings in loops, and `strconv` over `fmt.Sprintf` on hot paths.
- `GO-17.5` **SHOULD** stream large payloads (`io.Reader`, `json.NewDecoder`, iterators) instead of loading them fully into memory.
- `GO-17.6` **MAY** use `sync.Pool` for frequently allocated, short-lived buffers; [MUST] reset objects before returning them and never pool objects that hold references to request data.
- `GO-17.7` **SHOULD** set `GOMEMLIMIT` in containers (around 90% of the memory limit) and leave `GOGC` at its default unless a profile says otherwise. Go 1.26+'s Green Tea collector already lowers GC cost.
- `GO-17.8` **MUST NOT** use `unsafe` or `reflect` for speed without a benchmark, a comment explaining the invariant, and a test that guards it.
- `GO-17.9` **AVOID** premature concurrency: a goroutine per item is often slower than a simple loop for small, CPU-cheap work.
- `GO-17.10` **SHOULD** keep struct fields ordered for readability first; reorder for alignment only in types allocated millions of times, with a comment.

## 18. Security

- `GO-18.1` **MUST** run `govulncheck` in CI and fix reachable vulnerabilities before release; keep dependencies current with Renovate or Dependabot.
- `GO-18.2` **MUST** validate all input at the boundary (lengths, ranges, formats, enums) before it reaches the domain.
- `GO-18.3` **MUST** use bind parameters for SQL (§15) and `exec.CommandContext` with an argument list — never a shell string — for subprocesses.
- `GO-18.4` **MUST** open user-influenced paths through `os.Root` (Go 1.24+) or `filepath.IsLocal` checks to prevent path traversal.
- `GO-18.5` **MUST** use `crypto/rand` for tokens, keys and IDs; `math/rand/v2` is for simulations and jitter only.
- `GO-18.6` **MUST** compare secrets with `crypto/subtle.ConstantTimeCompare` or `hmac.Equal`.
- `GO-18.7` **MUST** hash passwords with `argon2id` or `bcrypt` (`golang.org/x/crypto`), never with a fast hash.
- `GO-18.8` **MUST** use TLS 1.3 by default with the standard library's defaults (post-quantum hybrid key exchange is on by default since Go 1.26). [MUST NOT] set `InsecureSkipVerify` outside tests.
- `GO-18.9` **MUST** use mTLS for service-to-service and agent-to-control-plane traffic on untrusted networks.
- `GO-18.10` **MUST** set HTTP security headers on browser-facing services and protect state-changing routes from cross-origin requests (§14).
- `GO-18.11` **MUST NOT** log or return secrets, stack traces or internal errors to clients.
- `GO-18.12` **SHOULD** run services as a non-root user in a minimal image (`distroless/static` or `scratch`) with a read-only root filesystem.
- `GO-18.13` **SHOULD** enable FIPS 140-3 mode (`GOFIPS140`) only when a customer requires it, and test the build with it.

## 19. Testing

- `GO-19.1` **MUST** keep tests beside the code in `<name>_test.go`, and shared fixtures in `testdata/`.
- `GO-19.2` **MUST** use the standard `testing` package. [MAY] use `go-cmp` (`cmp.Diff`) for comparing structs; [AVOID] assertion frameworks that hide the failing value.
- `GO-19.3` **MUST** write table-driven tests with named subtests (`t.Run(tc.name, ...)`) when a function has more than two interesting cases.
- `GO-19.4` **MUST** write failure messages as `Func(input) = got, want want`: `t.Errorf("Parse(%q) = %v, want %v", in, got, want)`.
- `GO-19.5` **MUST** call `t.Helper()` in test helpers, use `t.Cleanup` for teardown, `t.TempDir()` for files and `t.Context()` (Go 1.24+) for contexts.
- `GO-19.6` **SHOULD** mark independent tests `t.Parallel()`.
- `GO-19.7` **MUST** run the suite with `go test -race -shuffle=on ./...` in CI.
- `GO-19.8` **MUST** test time-dependent and concurrent code with `testing/synctest`: inside `synctest.Test`, time is virtual and `synctest.Wait` / `synctest.Sleep` (Go 1.27) make goroutine scheduling deterministic. [MUST NOT] use real `time.Sleep` in tests.
- `GO-19.9` **SHOULD** use `httptest.NewServer` (or `httptest.NewTestServer` with synctest, Go 1.27) for HTTP clients, and `httptest.NewRecorder` for handlers.
- `GO-19.10` **SHOULD** test stores against real PostgreSQL via `testcontainers-go`, gated by a build tag or `-short` so unit runs stay fast.
- `GO-19.11` **SHOULD** prefer hand-written fakes (an in-memory `Store`) over generated mocks.
- `GO-19.12` **SHOULD** add fuzz tests (`func FuzzParse(f *testing.F)`) for parsers, decoders and anything that reads untrusted input, and commit the crashers they find to `testdata/fuzz`.
- `GO-19.13` **SHOULD** write benchmarks with `for b.Loop() { ... }` and compare runs with `benchstat`.
- `GO-19.14` **MAY** use golden files for large outputs, regenerated with a `-update` flag; save debug output in `t.ArtifactDir()` (Go 1.26).

## 20. Documentation and comments

- `GO-20.1` **MUST** give every package a package comment (`// Package invoice ...`) in one file, usually `doc.go` for larger packages.
- `GO-20.2` **MUST** document every exported identifier with a full sentence that starts with its name: `// NewService returns a Service backed by store.`
- `GO-20.3` **SHOULD** document what is not obvious from the signature: concurrency safety, ownership of returned slices, units, zero-value behaviour and the errors a caller can expect.
- `GO-20.4` **MUST** write comments that explain **why**, not what the next line does.
- `GO-20.5` **MUST** use the doc-comment syntax `go doc` renders: `[Service.MarkPaid]` links, `# Headings`, indented code blocks and lists.
- `GO-20.6` **SHOULD** add runnable `Example` functions for public APIs; they are compiled and checked by `go test`.
- `GO-20.7` **MUST** format TODOs as `// TODO(owner): what and why`, with an issue link for anything longer than a day.
- `GO-20.8` **MUST** mark deprecated APIs with a `// Deprecated: use X instead.` paragraph.
- `GO-20.9` **MUST** keep a `README.md` per repository with: what it is, how to run it locally (one command), how to test, configuration and how it is deployed.
- `GO-20.10` **SHOULD** record significant design decisions as short ADRs in `docs/adr/`.

## 21. Git, commits, releases and review

- `GO-21.1` **MUST** follow **Conventional Commits**: `type(scope): summary` in the imperative, lower case, no trailing period, under 72 characters.
- `GO-21.2` **MUST** mark breaking changes with `!` and a `BREAKING CHANGE:` footer.
- `GO-21.3` **SHOULD** explain the *why* in the body when it is not obvious from the summary.
- `GO-21.4` **MUST** keep every commit building and passing tests; squash fix-ups before merging.
- `GO-21.5` **MUST** branch from `main` with a short typed name: `feat/agent-spool`, `fix/invoice-rounding`.
- `GO-21.6` **SHOULD** keep pull requests under about 400 changed lines, excluding generated code.
- `GO-21.7` **MUST** pass `task lint` and `task test` before review, and keep `go.mod` tidy (`go mod tidy` produces no diff).
- `GO-21.8` **MUST** include tests and migrations in the same pull request as the change they support.
- `GO-21.9` **MUST** tag releases with semantic versions (`v1.4.0`) and generate the changelog from Conventional Commits.
- `GO-21.10` **MUST** inject version information at build time: `-ldflags "-X example.com/project/internal/version.Version={{.Version}} -X ...Commit={{.Commit}}"`, and fall back to `debug.ReadBuildInfo()` for `go install` builds.
- `GO-21.11` **SHOULD** build with GoReleaser: reproducible (`-trimpath`, `CGO_ENABLED=0`), multi-platform, checksummed and signed, with container images and an SBOM.