Write idiomatic Go
Add Go that reads as idiomatic, handles every error explicitly, and matches the repo's
existing patterns — simple and correct, the Go way.
Steps
- Read the lore first. Call
search_lore (Memory MCP) for the repo's Go
conventions and respect go.mod (module path, Go version), the linter config
(golangci-lint), and any architecture ADRs. Keep packages cohesive and named for
what they provide.
- Find a sibling package and copy its patterns — package layout, error handling,
how interfaces are defined and consumed, and how tests are organised.
- Handle every error explicitly. Never discard an
error with _ unless it is
genuinely ignorable and commented why. Wrap with context using fmt.Errorf("doing X: %w", err) so the chain is inspectable with errors.Is/errors.As. Return early
on error; avoid deep nesting.
- Keep interfaces small and define them at the consumer, not the producer.
Accept interfaces, return concrete types. Don't add an interface speculatively.
- Pointer-receiver rules: be consistent within a type. Use a pointer receiver when
the method mutates the receiver, the struct is large, or any method needs a pointer
receiver (so the method set stays consistent); use value receivers for small immutable
value types.
- Concurrency with care. Pass
context.Context as the first argument to anything
that blocks or spans a request; never store a Context in a struct. Guard shared state;
prefer channels/sync primitives over data races. Always have a defined goroutine exit.
- Idioms:
defer for cleanup, zero-value-useful structs, no naked returns in long
functions, no stuttering names (http.HTTPServer → http.Server). Run gofmt/goimports.
- Test table-driven. Use sub-tests (
t.Run) over a []struct of cases; cover error
paths; run with -race. Use t.Helper() in assertion helpers.
- Verify + evidence. Run
go vet, go build, and go test -race ./..., record
test_output via the record-evidence skill, and submit for review.
Build / Test
- Build/vet:
go build ./... and go vet ./....
- Lint:
golangci-lint run (when the repo configures it).
- Tests:
go test -race ./...; coverage via go test -cover ./....
- The DoD is verified by the repo's configured test/coverage commands — run them
(with
-race) and record the output; a clean vet + green race-tested run is the evidence.
Review checklist (a Go reviewer must check)
- Every error is checked — no silently dropped
err; ignores are explicit and commented.
- Errors are wrapped with
%w and context, so errors.Is/errors.As work; no
fmt.Errorf("%v", err) that breaks the chain.
- Interfaces are small and consumer-side; functions accept interfaces, return concrete types.
- Receiver types are consistent across a type's method set; pointer vs value chosen deliberately.
context.Context is the first param of blocking/request-scoped calls and never stored in a struct.
- No data races — shared state is guarded; goroutines have a defined exit;
-race passes.
gofmt/goimports clean and go vet reports nothing.
- Tests are table-driven with sub-tests and cover error cases, not only the happy path.
Rules
- Match the repo's Go version, module layout, and linter config exactly.
- Check and wrap every error (
%w); return early; no swallowed errors.
- Small consumer-side interfaces; consistent receivers;
context.Context first, never stored.
- Table-driven tests run with
-race; gofmt/go vet clean before review.
Capture lore
This skill is one of the places durable, reusable knowledge naturally surfaces:
A Go convention this repo enforces beyond the obvious — an error-wrapping pattern, a package-boundary rule, a concurrency invariant, or a linter constraint. That kind of fact is lore. Capture it via the lore-capture
protocol in your brief (CLAUDE.factory.md, step 11 "Memory contribution"):
call the Memory MCP suggest_lore once at the close of your work — reusable
conventions, gotchas, decisions, and boundaries only, never per-ticket trivia.
1---2name: go-conventions3description: Use when a ticket adds or changes Go code and it must follow the repo's Go conventions — idiomatic Go, explicit error handling and wrapping, small interfaces, correct pointer-receiver rules, and table-driven tests run with the race detector. Invoke for "add this in Go", "fix the go vet/build issues", "add the handler/service", or as the language pack for any Go change.4---56# Write idiomatic Go78Add Go that reads as idiomatic, handles every error explicitly, and matches the repo's9existing patterns — simple and correct, the Go way.1011## Steps12131. **Read the lore first.** Call `search_lore` (Memory MCP) for the repo's Go14 conventions and respect `go.mod` (module path, Go version), the linter config15 (`golangci-lint`), and any architecture ADRs. Keep packages cohesive and named for16 what they provide.172. **Find a sibling package** and copy its patterns — package layout, error handling,18 how interfaces are defined and consumed, and how tests are organised.193. **Handle every error explicitly.** Never discard an `error` with `_` unless it is20 genuinely ignorable and commented why. **Wrap with context** using `fmt.Errorf("doing21 X: %w", err)` so the chain is inspectable with `errors.Is`/`errors.As`. Return early22 on error; avoid deep nesting.234. **Keep interfaces small** and **define them at the consumer**, not the producer.24 Accept interfaces, return concrete types. Don't add an interface speculatively.255. **Pointer-receiver rules:** be consistent within a type. Use a pointer receiver when26 the method mutates the receiver, the struct is large, or any method needs a pointer27 receiver (so the method set stays consistent); use value receivers for small immutable28 value types.296. **Concurrency with care.** Pass `context.Context` as the first argument to anything30 that blocks or spans a request; never store a `Context` in a struct. Guard shared state;31 prefer channels/`sync` primitives over data races. Always have a defined goroutine exit.327. **Idioms:** `defer` for cleanup, zero-value-useful structs, no naked returns in long33 functions, no stuttering names (`http.HTTPServer` → `http.Server`). Run `gofmt`/`goimports`.348. **Test table-driven.** Use sub-tests (`t.Run`) over a `[]struct` of cases; cover error35 paths; run with `-race`. Use `t.Helper()` in assertion helpers.369. **Verify + evidence.** Run `go vet`, `go build`, and `go test -race ./...`, record37 `test_output` via the `record-evidence` skill, and submit for review.3839## Build / Test4041- **Build/vet:** `go build ./...` and `go vet ./...`.42- **Lint:** `golangci-lint run` (when the repo configures it).43- **Tests:** `go test -race ./...`; coverage via `go test -cover ./...`.44- The DoD is verified by the repo's configured test/coverage commands — run them45 (with `-race`) and record the output; a clean vet + green race-tested run is the evidence.4647## Review checklist (a Go reviewer must check)4849- **Every error is checked** — no silently dropped `err`; ignores are explicit and commented.50- **Errors are wrapped with `%w`** and context, so `errors.Is`/`errors.As` work; no51 `fmt.Errorf("%v", err)` that breaks the chain.52- **Interfaces are small and consumer-side**; functions accept interfaces, return concrete types.53- **Receiver types are consistent** across a type's method set; pointer vs value chosen deliberately.54- **`context.Context` is the first param** of blocking/request-scoped calls and never stored in a struct.55- **No data races** — shared state is guarded; goroutines have a defined exit; `-race` passes.56- **`gofmt`/`goimports` clean** and `go vet` reports nothing.57- **Tests are table-driven** with sub-tests and cover error cases, not only the happy path.5859## Rules6061- Match the repo's Go version, module layout, and linter config exactly.62- Check and wrap every error (`%w`); return early; no swallowed errors.63- Small consumer-side interfaces; consistent receivers; `context.Context` first, never stored.64- Table-driven tests run with `-race`; `gofmt`/`go vet` clean before review.6566## Capture lore6768This skill is one of the places durable, reusable knowledge naturally surfaces:69**A Go convention this repo enforces beyond the obvious — an error-wrapping pattern, a package-boundary rule, a concurrency invariant, or a linter constraint.** That kind of fact is *lore*. Capture it via the **lore-capture70protocol in your brief** (`CLAUDE.factory.md`, step 11 "Memory contribution"):71call the Memory MCP `suggest_lore` once at the close of your work — reusable72conventions, gotchas, decisions, and boundaries only, never per-ticket trivia.