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.27 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:
- 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
- Keep parameter lists explicit and intention-revealing; if many values travel together for one reason, introduce a small typed struct
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
- 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
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:
- deep layering in small services
internal/platform/ catch-all layers — keep cross-cutting concerns in focused packages
- packages that combine unrelated domains because they share a datastore or transport
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:
Project-Specific: Root-Package Facade
This project uses a facade pattern where the root dashboard package re-exports internal/ APIs via thin wrappers and type aliases.
When adding a new feature:
- Implement logic in
internal/app<domain>/ with exported names
- Add type aliases (
type X = appfoo.X) and wrapper functions in the root package
- Write tests at both the internal package level (unit) and root level (integration)
When modifying an existing internal API:
- If the signature changes, update the corresponding root-level wrapper
- Root-level wrappers must remain zero-logic forwarding — no business logic in the facade
Legacy note: CollectTokenUsage and CollectTokenUsageWithCache have 15+ map parameters. New code should use options structs per CLAUDE.md convention. These functions are candidates for future refactoring but are not blocking.
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
1---2name: go-rig-23description: 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.4---56# Go Rig78Strict design and testing discipline for Go projects.910This skill **complements** `CLAUDE.md`.1112`CLAUDE.md` owns:13- Go version, toolchain, and commands14- Key style, error, context, and concurrency rules1516`.claude/rules/` owns:17- Go 1.27 idioms and go fix modernizer catalog (`go-idioms.md`)18- Detailed style, API, documentation, and testing patterns (`go-patterns.md`)1920This skill adds:21- ATDD/TDD workflow22- design principles and abstraction discipline23- dependency injection discipline24- package-boundary judgment25- documentation discipline26- comment quality standards27- structured review process2829Do not restate or override version-specific guidance from `CLAUDE.md`. If `CLAUDE.md` is stricter on a shared point, follow `CLAUDE.md`.3031## When to Use3233Use this skill when:3435- implementing a new feature or behavior increment36- refactoring Go code for clearer ownership or testability37- reviewing package boundaries or dependency flow38- replacing hidden collaborator construction with explicit injection39- tightening tests around user-visible or integration behavior4041## ATDD/TDD Workflow4243Test-first is a design tool, not an afterthought.44451. **Acceptance first** — define the boundary behavior before writing code462. **Acceptance test** — add or update an acceptance-level test if the project has that layer; otherwise express boundary behavior in the closest consumer-level test473. **Smallest failing unit test** — for the next behavior increment484. **Minimal implementation** — only enough to pass495. **Refactor** — improve readability and cohesion while green506. **Repeat** — next behavior increment5152If repository policy does not allow automatic test execution, still design test-first and ask before running.5354### Test Coverage Expectations5556Every meaningful change should cover:57- expected behavior (happy path)58- invalid input and validation failures59- edge cases and boundary values60- error and failure paths61- concurrency behavior when relevant6263Use 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.6465### Definition Of Done6667A change is not done when the code "works on one path." It is done when:68- acceptance behavior is specified at the right boundary69- the smallest relevant unit behavior is covered70- failure and edge behavior are covered71- code was refactored back to clarity after going green72- repo test/lint/static-analysis expectations were satisfied or explicitly deferred7374## Design Principles7576Apply without ceremony — these guide decisions, not generate boilerplate.7778**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.7980**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.8182**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.8384When 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.8586## Abstraction Discipline8788- Start with concrete types and direct calls89- Introduce an interface only when a real consumer needs substitution90- Prefer one seam at a boundary over many tiny abstractions in the core91- If an abstraction adds files, wiring, and names but no clear testability or ownership win, do not add it9293Avoid:94- repositories or services that only forward calls95- configuration objects passed everywhere to avoid choosing explicit parameters96- “future-proofing” abstractions without a concrete second implementation or consumer9798## Function Design99100- A function should usually do one thing: validate, transform, orchestrate, persist, or render101- If a function mixes business rules with transport, storage, or logging details, split it102- Keep parameter lists explicit and intention-revealing; if many values travel together for one reason, introduce a small typed struct103104Refactor when a function:105- needs comments to explain the control flow106- mixes unrelated reasons to change107- carries mutable state across many screens of code108- repeats branching or validation logic that belongs in a helper or type method109110## Dependency Injection111112- **Constructors** for types that must enforce invariants or own long-lived collaborators113- **Function parameters** for short-lived collaborators and pure logic114- Never construct DB clients, HTTP clients, loggers, or repositories inside domain methods115- Prefer passing dependencies from the composition root (`main`, wiring package, or test setup) instead of looking them up deep inside the call stack116- Inject seams for time, randomness, process execution, filesystem, and external I/O when behavior depends on them117118## Package Design119120Organize by domain, not by technical layer.121122- Group related domain logic together until splitting clearly improves cohesion123- 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 details124- Split files when doing so improves readability; file count is not a goal by itself125- Split packages only when coupling pressure is real, not speculative126127Avoid:128- deep layering in small services129- `internal/platform/` catch-all layers — keep cross-cutting concerns in focused packages130- packages that combine unrelated domains because they share a datastore or transport131132## Hardcoding And Configuration133134- Do not hardcode URLs, ports, credentials, file paths, timeouts, feature flags, environment names, or dependency selection in core logic135- Domain invariants may be constants, but operational values should come from config, constructor parameters, or function arguments136- Prefer typed config structs validated at startup over scattered `os.Getenv` calls137- Keep configuration loading at the edge; pass validated values inward138139## Type Discipline140141- Model domain concepts with named types when that prevents invalid mixing and clarifies intent142- Prefer concrete structs over `map[string]any` for stable data143- Keep weakly typed data at the boundary and translate it into strict internal types quickly144- Avoid boolean parameter soup; use named option structs or dedicated methods when intent is unclear145146## Comment Quality147148**Write comments when they add**:149- why a tradeoff exists150- package-level intent151- non-obvious invariants or constraints152- concurrency ownership rules153- boundary assumptions154155**Do not write comments that**:156- restate the code157- narrate obvious assignments158- explain syntax instead of intent159- leave vague TODOs without reason or ticket reference160- duplicate the doc comment with less precision161162## Documentation Discipline163164- Exported names and packages need doc comments165- Public docs should describe contract, invariants, and caller-visible behavior166- When a change affects configuration, wire format, or API semantics, update docs in the same change167- Add or update examples when they materially improve discoverability of a public API168169## Test Quality170171- Prefer readable subtest names over encoded case IDs172- Failure messages should make `got` and `want` obvious173- Prefer semantic comparisons over formatting-sensitive comparisons174- Avoid asserting on exact human-readable error strings unless the exact string is part of the contract175- Use `t.Fatal` only when the test cannot continue meaningfully176- Acceptance tests should speak in business behavior, not internal implementation vocabulary177- Use table-driven tests where variation is the point; do not force tables when a direct narrative test is clearer178- Add test seams instead of using sleeps, global mutation, or network reliance to force determinism179180## Static Analysis Discipline181182Treat linting and static analysis as design feedback, not cosmetic cleanup.183184- Respect repo gates for `go vet`, `golangci-lint`, `staticcheck`, `govulncheck`, and related analyzers when configured185- Fix root causes instead of scattering ignores186- If an analyzer warning is intentionally ignored, leave a precise justification close to the suppression187- Do not weaken lint configuration casually to make a change pass188189## Review Checklist190191Before finishing any change, verify:192193- [ ] Package boundaries are coherent — no cross-domain leaks194- [ ] Dependencies injected explicitly — no hidden construction195- [ ] Functions are readable in one pass and do not mix unrelated responsibilities196- [ ] Tests cover acceptance behavior and unit behavior197- [ ] TDD/ATDD flow was followed as closely as the repo constraints allowed198- [ ] Behavioral compatibility checked where public APIs, JSON, or persistence shape changed199- [ ] Nil vs empty behavior is intentional for slices, maps, pointers, and JSON fields200- [ ] Concurrency changes have a shutdown path and observable ownership201- [ ] Root-package wrappers added/updated for any new `internal/` exports202- [ ] Lint and test gates (`make check`) have been run or consciously deferred203204## Project-Specific: Root-Package Facade205206This project uses a facade pattern where the root `dashboard` package re-exports `internal/` APIs via thin wrappers and type aliases.207208When adding a new feature:2091. Implement logic in `internal/app<domain>/` with exported names2102. Add type aliases (`type X = appfoo.X`) and wrapper functions in the root package2113. Write tests at both the internal package level (unit) and root level (integration)212213When modifying an existing internal API:214- If the signature changes, update the corresponding root-level wrapper215- Root-level wrappers must remain zero-logic forwarding — no business logic in the facade216217Legacy note: `CollectTokenUsage` and `CollectTokenUsageWithCache` have 15+ map parameters. New code should use options structs per CLAUDE.md convention. These functions are candidates for future refactoring but are not blocking.218219## Success Criteria220221This skill is being followed correctly when:222223- changes are small, test-backed, and easy to review224- dependency flow is explicit from the composition root225- package responsibilities are cleaner after the change, not blurrier226- the implementation follows the Go standards in `CLAUDE.md`227- tests speak in behavior terms, not implementation vocabulary228- the resulting code reads clearly without comments explaining the control flow