| name | go-rig |
| description | Use this skill when building, reviewing, or refactoring Go code that must follow strict design discipline — ATDD/TDD workflow, explicit dependency injection, package-boundary discipline, and structured code review. Complements CLAUDE.md by focusing on process and design judgment rather than version-specific Go features. |
| metadata | {"short-description":"Go design, workflow, and review discipline","slash-command":"enabled"} |
Go Rig
Strict design and testing discipline for Go projects.
This skill complements CLAUDE.md.
CLAUDE.md owns:
- Go version, toolchain, and commands
- Key style, error, context, and concurrency rules
.claude/rules/ owns:
- Go 1.26 idioms and go fix modernizer catalog (
go-idioms.md)
- Detailed style, API, documentation, and testing patterns (
go-patterns.md)
This skill adds:
- ATDD/TDD workflow
- design principles and abstraction discipline
- dependency injection discipline
- package-boundary judgment
- documentation discipline
- comment quality standards
- structured review process
Do not restate or override version-specific guidance from CLAUDE.md. If CLAUDE.md is stricter on a shared point, follow CLAUDE.md.
When to Use
Use this skill when:
- implementing a new feature or behavior increment
- refactoring Go code for clearer ownership or testability
- reviewing package boundaries or dependency flow
- replacing hidden collaborator construction with explicit injection
- tightening tests around user-visible or integration behavior
ATDD/TDD Workflow
Test-first is a design tool, not an afterthought.
- Acceptance first — define the boundary behavior before writing code
- Acceptance test — add or update an acceptance-level test if the project has that layer; otherwise express boundary behavior in the closest consumer-level test
- Smallest failing unit test — for the next behavior increment
- Minimal implementation — only enough to pass
- Refactor — improve readability and cohesion while green
- Repeat — next behavior increment
If repository policy does not allow automatic test execution, still design test-first and ask before running.
Test Coverage Expectations
Every meaningful change should cover:
- expected behavior (happy path)
- invalid input and validation failures
- edge cases and boundary values
- error and failure paths
- concurrency behavior when relevant
Use the project’s existing test layers where possible. Reach for acceptance tests when the change is user-visible or integration-heavy, and unit tests when isolating business rules or edge cases.
Definition Of Done
A change is not done when the code "works on one path." It is done when:
- acceptance behavior is specified at the right boundary
- the smallest relevant unit behavior is covered
- failure and edge behavior are covered
- code was refactored back to clarity after going green
- repo test/lint/static-analysis expectations were satisfied or explicitly deferred
Design Principles
Apply without ceremony — these guide decisions, not generate boilerplate.
SRP — each package, type, and function has one clear reason to change. Split when a change in one concern forces changes in an unrelated concern.
DRY — extract repeated validation, mapping, branching, and business rules. Do not DRY away incidental similarity — two things that look alike but change for different reasons should stay separate.
OCP — extend stable areas carefully, but do not invent indirection to satisfy the idea of extensibility. In Go, a concrete type with a small seam at the consumer is usually better than an abstract framework.
When applying SRP/DRY/OCP in Go, prefer deleting duplication caused by mixed responsibilities before introducing new abstractions. The first move is usually better boundaries, not more interfaces.
Abstraction Discipline
- Start with concrete types and direct calls
- Introduce an interface only when a real consumer needs substitution
- Prefer one seam at a boundary over many tiny abstractions in the core
- If an abstraction adds files, wiring, and names but no clear testability or ownership win, do not add it
Avoid:
- interface-per-struct
- repositories or services that only forward calls
- configuration objects passed everywhere to avoid choosing explicit parameters
- “future-proofing” abstractions without a concrete second implementation or consumer
Function Design
- A function should usually do one thing: validate, transform, orchestrate, persist, or render
- If a function mixes business rules with transport, storage, or logging details, split it
- Prefer early returns over nested condition pyramids
- Keep parameter lists explicit and intention-revealing; if many values travel together for one reason, introduce a small typed struct
- Use whitespace to separate logical phases so the control flow reads top to bottom
Refactor when a function:
- needs comments to explain the control flow
- mixes unrelated reasons to change
- carries mutable state across many screens of code
- repeats branching or validation logic that belongs in a helper or type method
Dependency Injection
- Constructors for types that must enforce invariants or own long-lived collaborators
- Function parameters for short-lived collaborators and pure logic
- Never construct DB clients, HTTP clients, loggers, or repositories inside domain methods
- No DI frameworks — explicit wiring only
- No hidden globals or singletons
- Prefer passing dependencies from the composition root (
main, wiring package, or test setup) instead of looking them up deep inside the call stack
- Inject seams for time, randomness, process execution, filesystem, and external I/O when behavior depends on them
- Do not hide dependencies behind package-level variables except in rare compatibility shims
func NewOrderService(store OrderStore, clock Clock) *OrderService {
return &OrderService{store: store, clock: clock}
}
func ValidateOrder(order Order, now time.Time) error {
if order.ExpiresAt.Before(now) {
return fmt.Errorf("order %s expired: %w", order.ID, ErrExpired)
}
return nil
}
Package Design
Organize by domain, not by technical layer.
- Group related domain logic together until splitting clearly improves cohesion
- Keep transport and storage near the owning domain in the repo when the service is small, but do not let core business logic depend on transport details
- Split files when doing so improves readability; file count is not a goal by itself
- Split packages only when coupling pressure is real, not speculative
Avoid:
- interface-per-struct without a consumer need
- deep layering in small services
internal/platform/ catch-all layers — keep cross-cutting concerns in focused packages (internal/config/, internal/db/)
- packages that combine unrelated domains because they share a datastore or transport
- "shared" packages that centralize unrelated helpers and create import gravity
Hardcoding And Configuration
- Do not hardcode URLs, ports, credentials, file paths, timeouts, feature flags, environment names, or dependency selection in core logic
- Domain invariants may be constants, but operational values should come from config, constructor parameters, or function arguments
- Prefer typed config structs validated at startup over scattered
os.Getenv calls
- Keep configuration loading at the edge; pass validated values inward
Type Discipline
- Model domain concepts with named types when that prevents invalid mixing and clarifies intent
- Prefer concrete structs over
map[string]any for stable data
- Keep weakly typed data at the boundary and translate it into strict internal types quickly
- Avoid boolean parameter soup; use named option structs or dedicated methods when intent is unclear
Comment Quality
Write comments when they add:
- why a tradeoff exists
- package-level intent
- non-obvious invariants or constraints
- concurrency ownership rules
- boundary assumptions
Do not write comments that:
- restate the code
- narrate obvious assignments
- explain syntax instead of intent
- leave vague TODOs without reason or ticket reference
- duplicate the doc comment with less precision
Documentation Discipline
- Exported names and packages need doc comments
- Public docs should describe contract, invariants, and caller-visible behavior
- When a change affects configuration, wire format, or API semantics, update docs in the same change
- Add or update examples when they materially improve discoverability of a public API
Test Quality
- Prefer readable subtest names over encoded case IDs
- Failure messages should make
got and want obvious
- Prefer semantic comparisons over formatting-sensitive comparisons
- Avoid asserting on exact human-readable error strings unless the exact string is part of the contract
- Use
t.Fatal only when the test cannot continue meaningfully
- Acceptance tests should speak in business behavior, not internal implementation vocabulary
- Use table-driven tests where variation is the point; do not force tables when a direct narrative test is clearer
- Add test seams instead of using sleeps, global mutation, or network reliance to force determinism
Static Analysis Discipline
Treat linting and static analysis as design feedback, not cosmetic cleanup.
- Respect repo gates for
go vet, golangci-lint, staticcheck, govulncheck, and related analyzers when configured
- Fix root causes instead of scattering ignores
- If an analyzer warning is intentionally ignored, leave a precise justification close to the suppression
- Do not weaken lint configuration casually to make a change pass
Review Checklist
Before finishing any change, verify:
Reject These Patterns
- Interface-per-struct without consumer need
- Giant functions mixing validation, orchestration, and persistence
- Hardcoded configuration or collaborator selection
- Weakly typed domain data kept as raw maps or generic blobs without need
- Comments that restate code
- Brittle mock-only tests — prefer fakes with real behavior
- Transport concerns embedded in core domain logic
- Production design distorted to satisfy a mocking framework
- Refactors that add indirection without improving correctness, ownership, or testability
Success Criteria
This skill is being followed correctly when:
- changes are small, test-backed, and easy to review
- dependency flow is explicit from the composition root
- package responsibilities are cleaner after the change, not blurrier
- the implementation follows the Go standards in
CLAUDE.md
- tests speak in behavior terms, not implementation vocabulary
- the resulting code reads clearly without comments explaining the control flow