- all nine files: trailing "Related" sections removed, their unique links inlined where the topic actually arises (FFI boundary -> c-cpp, embedded frontend -> js-typescript, build scripts -> bash, etc.) - README conventions: rule 2 mandated a Related Skills section, which now contradicts practice — replaced with inline-linking and one-home-per-fact rules. The <repo>/<rel path> rule was appended after the wrong section last commit; moved into the list where it belongs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.3 KiB
name, description
| name | description |
|---|---|
| go | Use this skill when writing, building, linting, testing, or releasing Go — including golangci-lint configuration, strict typing over map[string]interface{}, official SDK preference, CGO-free static builds, context handling, and the build gate. |
Go — Rules & Preferences
Extends coding-dev.md. Everything there applies; this adds Go specifics.
1. The Gate
gofmt -l . # must print nothing
go vet ./...
golangci-lint run
go test -race ./...
go build ./...
Wire this into a single entry point (make check, scripts/build.sh) so it cannot be
partially run. A skipped test fails the gate. Add govulncheck for binary vulnerability
scanning on anything that ships.
2. Linting — Strict Set
golangci-lint with a deliberately broad selection, not the defaults:
linters:
enable:
- staticcheck
- errcheck # every returned error is handled
- gosec # security findings
- noctx # no HTTP request without a context
- revive
- misspell
- unconvert
- bodyclose # every response body is closed
- goconst
- ineffassign
- nilerr
errcheck and bodyclose catch the two most common real Go bugs. As always: fix rather than
suppress, and when suppressing use a specific //nolint:name // reason with the reason.
3. Typing — Concrete, Not Dynamic
- Strongly typed structs everywhere. Avoid
map[string]interface{}and reflection when a struct can be defined — including for JSON, config, and API payloads. - Typed constants/enums, not bare strings:
type SyncStatus string const ( StatusSynced SyncStatus = "synced" StatusOutdated SyncStatus = "outdated" StatusDirty SyncStatus = "dirty" ) - Concrete error types with
errors.Is/errors.As, not string comparison. - Keep types shared with a frontend structurally in sync with that frontend's types; when both exist, a change to one is incomplete without the other.
4. Errors
- Every error is handled.
errcheckenforces it; do not_ =an error away without a comment explaining why it cannot matter. - Wrap with context:
fmt.Errorf("fetching %s: %w", name, err). Always%w, never%v, when the caller might need to inspect it. - Sentinel errors as package-level
var ErrNotFound = errors.New(...); typed errors when the caller needs data from them. paniconly for genuinely unrecoverable programmer error, never for expected runtime conditions. A library never panics on input it was designed to receive.deferthe cleanup immediately after acquiring the resource, always.
5. Context
- Every function that does I/O takes
ctx context.Contextas its first parameter. - Never store a context in a struct field.
- Never pass
nil—context.TODO()marks a genuine gap. - Honor cancellation: select on
ctx.Done()in loops, and pass ctx down to every HTTP call (noctxenforces this). - Set timeouts on outbound requests. A default
http.Clienthas no timeout at all.
6. Dependencies — Official SDKs First
Follow the global reuse-first ordering. In Go, strongly prefer official/first-party SDKs over third-party wrappers — they track API changes and handle auth and pagination correctly.
Choose pure-Go implementations over CGO ones whenever the trade is acceptable: a CGO
dependency turns a static single-binary build into a toolchain and glibc problem, and rules
out a FROM scratch/Alpine container image.
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o bin/app ./cmd/app
go mod tidy before committing, and commit go.sum.
7. Concurrency
- Share memory by communicating. Channels for handoff;
sync.Mutexfor guarding a struct's own fields. Don't mix both for the same state. go test -raceis not optional — it belongs in the gate, not in an occasional manual run.- Every goroutine has a defined exit path.
errgroup.Groupfor a set of related goroutines so errors propagate and cancellation fans out. - Events over polling — subscribe to a watch/notify API rather than looping on a ticker. Where a ticker is genuinely necessary, make the interval configurable and document why.
- Never leak a goroutine on a channel nobody reads.
8. Structure
cmd/<binary>/for entry points;pkg/orinternal/for the actual code.internal/for anything that must not become public API — it is compiler-enforced.- One package per concern; package names are lowercase, single-word, no underscores, and
never
utilorcommon. - Keep the exported surface small. Unexported by default.
- Embed frontend/static assets with
//go:embedfor single-binary distribution — the frontend itself followsjs-typescript.md. - File and function size limits from the general rules apply.
9. Testing
- Table-driven tests are the default idiom.
t.Parallel()where tests are genuinely independent.t.TempDir()for filesystem work — it cleans itself up.httptestfor HTTP handlers and for faking upstreams; do not hit real services in unit tests.- Regression test every fixed bug.
- Never run destructive operations against real accounts or user data during validation — temporary fixtures only.