@baselane/packs 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/aliases.js +1 -0
  2. package/package.json +2 -2
  3. package/packs/database-review/pack.json +75 -9
  4. package/packs/database-review/rendered/.claude/agents/database-reviewer.md +75 -2
  5. package/packs/database-review/rendered/.claude/commands/migration-review.md +6 -0
  6. package/packs/database-review/rendered/.claude/settings.json +11 -0
  7. package/packs/database-review/rendered/.claude/skills/database-migrations/SKILL.md +431 -0
  8. package/packs/database-review/rendered/.claude/skills/mysql-patterns/SKILL.md +414 -0
  9. package/packs/database-review/rendered/.claude/skills/postgres-patterns/SKILL.md +149 -0
  10. package/packs/database-review/rendered/.github/copilot-instructions.md +1096 -14
  11. package/packs/database-review/rendered/AGENTS.md +45 -16
  12. package/packs/database-review/rendered/CLAUDE.md +4 -0
  13. package/packs/database-review/rendered/GEMINI.md +987 -0
  14. package/packs/go-rules/pack.json +65 -9
  15. package/packs/go-rules/rendered/.claude/agents/go-build-resolver.md +90 -0
  16. package/packs/go-rules/rendered/.claude/agents/go-reviewer.md +72 -2
  17. package/packs/go-rules/rendered/.claude/commands/go-build.md +6 -0
  18. package/packs/go-rules/rendered/.claude/commands/go-review.md +1 -1
  19. package/packs/go-rules/rendered/.claude/commands/go-test.md +6 -0
  20. package/packs/go-rules/rendered/.claude/settings.json +9 -0
  21. package/packs/go-rules/rendered/.claude/skills/golang-patterns/SKILL.md +673 -0
  22. package/packs/go-rules/rendered/.claude/skills/golang-testing/SKILL.md +719 -0
  23. package/packs/go-rules/rendered/.github/copilot-instructions.md +1783 -7
  24. package/packs/go-rules/rendered/AGENTS.md +244 -8
  25. package/packs/go-rules/rendered/CLAUDE.md +5 -0
  26. package/packs/go-rules/rendered/GEMINI.md +1394 -0
  27. package/packs/python-rules/pack.json +87 -10
  28. package/packs/python-rules/rendered/.claude/agents/django-reviewer.md +159 -0
  29. package/packs/python-rules/rendered/.claude/agents/fastapi-reviewer.md +70 -0
  30. package/packs/python-rules/rendered/.claude/agents/python-reviewer.md +92 -2
  31. package/packs/python-rules/rendered/.claude/commands/django-review.md +6 -0
  32. package/packs/python-rules/rendered/.claude/commands/fastapi-review.md +6 -0
  33. package/packs/python-rules/rendered/.claude/commands/python-review.md +1 -1
  34. package/packs/python-rules/rendered/.claude/settings.json +1 -1
  35. package/packs/python-rules/rendered/.claude/skills/django-security/SKILL.md +642 -0
  36. package/packs/python-rules/rendered/.claude/skills/django-tdd/SKILL.md +728 -0
  37. package/packs/python-rules/rendered/.claude/skills/fastapi-patterns/SKILL.md +512 -0
  38. package/packs/python-rules/rendered/.claude/skills/python-patterns/SKILL.md +749 -0
  39. package/packs/python-rules/rendered/.claude/skills/python-testing/SKILL.md +815 -0
  40. package/packs/python-rules/rendered/.github/copilot-instructions.md +3815 -12
  41. package/packs/python-rules/rendered/AGENTS.md +73 -13
  42. package/packs/python-rules/rendered/CLAUDE.md +9 -0
  43. package/packs/python-rules/rendered/GEMINI.md +3448 -0
  44. package/packs/security-review/pack.json +51 -7
  45. package/packs/security-review/rendered/.claude/agents/security-reviewer.md +29 -2
  46. package/packs/security-review/rendered/.claude/settings.json +11 -0
  47. package/packs/security-review/rendered/.claude/skills/security-review/SKILL.md +302 -0
  48. package/packs/security-review/rendered/.claude/skills/security-review/cloud-infrastructure-security.md +171 -0
  49. package/packs/security-review/rendered/.claude/skills/security-scan-config/SKILL.md +56 -0
  50. package/packs/security-review/rendered/.github/copilot-instructions.md +658 -15
  51. package/packs/security-review/rendered/AGENTS.md +283 -17
  52. package/packs/security-review/rendered/CLAUDE.md +2 -0
  53. package/packs/security-review/rendered/GEMINI.md +352 -0
  54. package/packs/software-engineer-harness/pack.json +249 -13
  55. package/packs/software-engineer-harness/rendered/.claude/agents/code-explorer.md +67 -0
  56. package/packs/software-engineer-harness/rendered/.claude/agents/performance-optimizer.md +444 -0
  57. package/packs/software-engineer-harness/rendered/.claude/agents/pr-test-analyzer.md +43 -0
  58. package/packs/software-engineer-harness/rendered/.claude/commands/feature-dev.md +6 -0
  59. package/packs/software-engineer-harness/rendered/.claude/settings.json +29 -0
  60. package/packs/software-engineer-harness/rendered/.claude/skills/api-design/SKILL.md +522 -0
  61. package/packs/software-engineer-harness/rendered/.claude/skills/architecture-decision-records/SKILL.md +178 -0
  62. package/packs/software-engineer-harness/rendered/.claude/skills/backend-patterns/SKILL.md +560 -0
  63. package/packs/software-engineer-harness/rendered/.claude/skills/codebase-onboarding/SKILL.md +232 -0
  64. package/packs/software-engineer-harness/rendered/.claude/skills/deployment-patterns/SKILL.md +426 -0
  65. package/packs/software-engineer-harness/rendered/.claude/skills/docker-patterns/SKILL.md +363 -0
  66. package/packs/software-engineer-harness/rendered/.claude/skills/e2e-testing/SKILL.md +325 -0
  67. package/packs/software-engineer-harness/rendered/.claude/skills/tdd-workflow/SKILL.md +580 -0
  68. package/packs/software-engineer-harness/rendered/.github/copilot-instructions.md +3948 -2
  69. package/packs/software-engineer-harness/rendered/AGENTS.md +194 -4
  70. package/packs/software-engineer-harness/rendered/CLAUDE.md +20 -0
  71. package/packs/software-engineer-harness/rendered/GEMINI.md +3208 -0
  72. package/packs/typescript-rules/pack.json +68 -5
  73. package/packs/typescript-rules/rendered/.claude/agents/react-reviewer.md +165 -0
  74. package/packs/typescript-rules/rendered/.claude/agents/type-design-analyzer.md +48 -0
  75. package/packs/typescript-rules/rendered/.claude/commands/react-review.md +6 -0
  76. package/packs/typescript-rules/rendered/.claude/settings.json +9 -0
  77. package/packs/typescript-rules/rendered/.claude/skills/react-patterns/SKILL.md +340 -0
  78. package/packs/typescript-rules/rendered/.claude/skills/react-performance/SKILL.md +573 -0
  79. package/packs/typescript-rules/rendered/.claude/skills/react-testing/SKILL.md +422 -0
  80. package/packs/typescript-rules/rendered/.github/copilot-instructions.md +2459 -53
  81. package/packs/typescript-rules/rendered/AGENTS.md +893 -17
  82. package/packs/typescript-rules/rendered/CLAUDE.md +6 -0
  83. package/packs/typescript-rules/rendered/GEMINI.md +1335 -0
  84. package/packs/ecc-essentials/pack.json +0 -93
  85. package/packs/ecc-essentials/rendered/.claude/settings.json +0 -37
  86. package/packs/ecc-essentials/rendered/.github/copilot-instructions.md +0 -110
  87. package/packs/ecc-essentials/rendered/AGENTS.md +0 -74
  88. package/packs/ecc-essentials/rendered/CLAUDE.md +0 -14
  89. package/packs/ecc-essentials/rendered/GEMINI.md +0 -25
  90. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/agents/build-error-resolver.md +0 -0
  91. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/agents/refactor-cleaner.md +0 -0
  92. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/agents/silent-failure-hunter.md +0 -0
  93. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/agents/tdd-guide.md +0 -0
  94. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/commands/fix-build.md +0 -0
  95. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/commands/hunt-silent-failures.md +0 -0
  96. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/commands/tdd.md +0 -0
  97. /package/packs/{ecc-essentials → software-engineer-harness}/rendered/.claude/skills/tdd-red-green-refactor/SKILL.md +0 -0
@@ -2,43 +2,1819 @@
2
2
 
3
3
  ## Go conventions (beyond what the linter enforces)
4
4
 
5
- `gofmt`, `goimports`, and `go vet` already catch formatting and the obvious mistakes — this pack is the judgment they can't encode. Keep the mechanical checks in CI (`gofmt -l .`, `go vet ./...`, `go test -race ./...`, `gosec ./...`); the reviewer covers the rest.
5
+ `gofmt`, `goimports`, and `go vet` already catch formatting and the obvious mistakes — this is the judgment they can't encode: API shape, error handling, concurrency safety, interface design, package organization, and the anti-patterns a linter waves through. Keep the mechanical checks in CI (`gofmt -l .`, `go vet ./...`, `go test -race ./...`, `gosec ./...`); the reviewer and skills below cover the rest.
6
+
7
+ ### Formatting & tooling
8
+
9
+ - `gofmt` and `goimports` are mandatory — no style debates.
10
+ - Recommended `.golangci.yml`:
11
+
12
+ ```yaml
13
+ linters:
14
+ enable:
15
+ - errcheck
16
+ - gosimple
17
+ - govet
18
+ - ineffassign
19
+ - staticcheck
20
+ - unused
21
+ - gofmt
22
+ - goimports
23
+ - misspell
24
+ - unconvert
25
+ - unparam
26
+
27
+ linters-settings:
28
+ errcheck:
29
+ check-type-assertions: true
30
+ govet:
31
+ enable:
32
+ - shadow
33
+
34
+ issues:
35
+ exclude-use-default: false
36
+ ```
6
37
 
7
38
  ### API shape
8
39
 
9
40
  - Accept interfaces, return structs. Define an interface where it is consumed (1-3 methods), never next to the implementation.
10
41
  - Use functional options (`type Option func(*Server)`) for constructors with many optional parameters, not a giant config struct or a long positional list.
11
42
  - Inject dependencies through constructors (`NewUserService(repo, logger)`), never package-level globals.
43
+ - Make the zero value useful — design types so their zero value is immediately usable without initialization (e.g. `bytes.Buffer`, a mutex-guarded counter).
44
+ - Context is always the first parameter, never a struct field: `func ProcessRequest(ctx context.Context, id string) error`.
45
+
46
+ ```go
47
+ // Functional options
48
+ type Option func(*Server)
49
+
50
+ func WithTimeout(d time.Duration) Option {
51
+ return func(s *Server) { s.timeout = d }
52
+ }
53
+
54
+ func NewServer(addr string, opts ...Option) *Server {
55
+ s := &Server{addr: addr, timeout: 30 * time.Second}
56
+ for _, opt := range opts {
57
+ opt(s)
58
+ }
59
+ return s
60
+ }
61
+ ```
62
+
63
+ ```go
64
+ // Consumer-defined interface, not provider-defined
65
+ package service
66
+
67
+ type UserStore interface {
68
+ GetUser(id string) (*User, error)
69
+ SaveUser(user *User) error
70
+ }
71
+
72
+ type Service struct{ store UserStore }
73
+ ```
12
74
 
13
75
  ### Errors
14
76
 
15
77
  - Wrap every propagated error with context and `%w` (`fmt.Errorf("create user: %w", err)`). A bare returned `err` with no origin is a defect, not a shortcut.
78
+ - Never discard an error with `_` unless it is truly best-effort and the reason is commented (`_ = writer.Close() // best-effort cleanup, error logged elsewhere`).
79
+ - Use `errors.Is`/`errors.As` to check sentinel/typed errors, not `==` or type assertions.
80
+ - Define sentinel errors (`var ErrNotFound = errors.New(...)`) or custom error types for domain-specific failures the caller needs to branch on.
81
+ - Never `panic` for a recoverable error — return it.
82
+
83
+ ```go
84
+ func LoadConfig(path string) (*Config, error) {
85
+ data, err := os.ReadFile(path)
86
+ if err != nil {
87
+ return nil, fmt.Errorf("load config %s: %w", path, err)
88
+ }
89
+ var cfg Config
90
+ if err := json.Unmarshal(data, &cfg); err != nil {
91
+ return nil, fmt.Errorf("parse config %s: %w", path, err)
92
+ }
93
+ return &cfg, nil
94
+ }
95
+
96
+ func HandleError(err error) {
97
+ if errors.Is(err, sql.ErrNoRows) {
98
+ log.Println("no records found")
99
+ return
100
+ }
101
+ var validationErr *ValidationError
102
+ if errors.As(err, &validationErr) {
103
+ log.Printf("validation error on %s: %s", validationErr.Field, validationErr.Message)
104
+ return
105
+ }
106
+ log.Printf("unexpected error: %v", err)
107
+ }
108
+ ```
16
109
 
17
110
  ### Concurrency
18
111
 
19
112
  - Thread `context.Context` through call chains for cancellation and timeout (`ctx, cancel := context.WithTimeout(ctx, 5*time.Second)`; `defer cancel()`).
20
113
  - Every piece of shared state has an explicit owner — a mutex or a channel — or a comment proving why it is safe. "Probably fine" is a race.
114
+ - Always `defer mu.Unlock()` immediately after `mu.Lock()`; never leave an unlock path implicit.
115
+ - Coordinate goroutines with `sync.WaitGroup` or `golang.org/x/sync/errgroup`; a bare `go func(){...}()` with no join point is a leak waiting to happen.
116
+ - A goroutine that sends on an unbuffered channel with no guaranteed receiver blocks forever if the reader gives up — buffer the channel or `select` on `ctx.Done()`.
117
+
118
+ ```go
119
+ func WorkerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
120
+ var wg sync.WaitGroup
121
+ for i := 0; i < numWorkers; i++ {
122
+ wg.Add(1)
123
+ go func() {
124
+ defer wg.Done()
125
+ for job := range jobs {
126
+ results <- process(job)
127
+ }
128
+ }()
129
+ }
130
+ wg.Wait()
131
+ close(results)
132
+ }
133
+
134
+ // Good: handles cancellation instead of leaking
135
+ func safeFetch(ctx context.Context, url string) <-chan []byte {
136
+ ch := make(chan []byte, 1)
137
+ go func() {
138
+ data, err := fetch(url)
139
+ if err != nil {
140
+ return
141
+ }
142
+ select {
143
+ case ch <- data:
144
+ case <-ctx.Done():
145
+ }
146
+ }()
147
+ return ch
148
+ }
149
+ ```
150
+
151
+ ```go
152
+ import "golang.org/x/sync/errgroup"
153
+
154
+ func FetchAll(ctx context.Context, urls []string) ([][]byte, error) {
155
+ g, ctx := errgroup.WithContext(ctx)
156
+ results := make([][]byte, len(urls))
157
+ for i, url := range urls {
158
+ i, url := i, url // capture loop variables
159
+ g.Go(func() error {
160
+ data, err := FetchWithTimeout(ctx, url)
161
+ if err != nil {
162
+ return err
163
+ }
164
+ results[i] = data
165
+ return nil
166
+ })
167
+ }
168
+ if err := g.Wait(); err != nil {
169
+ return nil, err
170
+ }
171
+ return results, nil
172
+ }
173
+ ```
174
+
175
+ ### Interface design
176
+
177
+ - Keep interfaces small (1-3 methods) and compose them (`ReadWriteCloser` from `Reader`+`Writer`+`Closer`), rather than one large interface.
178
+ - Define the interface in the consumer's package, not the provider's — the implementation doesn't need to know the interface exists.
179
+ - Optional behavior can be probed with a type assertion (`if f, ok := w.(Flusher); ok { ... }`) instead of a bloated required interface.
180
+
181
+ ### Package organization
182
+
183
+ - Standard layout: `cmd/<app>/main.go` (entry point), `internal/` (handler/service/repository/config), `pkg/` (public client), `api/` (proto/OpenAPI), `testdata/`.
184
+ - Package names: short, lowercase, no underscores, no redundant suffix (`user`, not `userService`).
185
+ - Avoid package-level mutable state (`var db *sql.DB` + `init()`) — inject dependencies through a constructor instead.
186
+ - Prefer embedding for composition (`type Server struct { *Logger; addr string }`) over duplicating methods.
187
+
188
+ ### Memory & performance
21
189
 
22
- ### Boundaries
190
+ - Preallocate slices when the size is known: `make([]T, 0, len(items))` instead of an unbounded `append` loop.
191
+ - Use `strings.Builder` (or `strings.Join`) instead of `+=` string concatenation in a loop.
192
+ - Use `sync.Pool` for frequently allocated short-lived buffers in hot paths.
193
+ - Avoid unnecessary allocations in hot paths; N+1 query patterns and per-item deferred calls in a loop are red flags too.
194
+
195
+ ### Boundaries & secrets
23
196
 
24
197
  - Validate external input at the boundary before it reaches business logic; fail fast with a clear message.
25
198
  - Read required secrets from the environment and `log.Fatal` on an empty value at startup — never a silent zero value.
26
199
 
27
- ### Flag in review (a linter won't)
200
+ ```go
201
+ apiKey := os.Getenv("OPENAI_API_KEY")
202
+ if apiKey == "" {
203
+ log.Fatal("OPENAI_API_KEY not configured")
204
+ }
205
+ ```
206
+
207
+ ### Security (flag in review — a linter won't catch these)
208
+
209
+ - SQL injection: string concatenation in `database/sql` queries instead of parameterized placeholders.
210
+ - Command injection: unvalidated input passed into `os/exec`.
211
+ - Path traversal: user-controlled file paths used without `filepath.Clean` + a prefix check.
212
+ - `unsafe` package usage without justification.
213
+ - Hardcoded secrets (API keys, passwords) in source.
214
+ - Insecure TLS (`InsecureSkipVerify: true`).
215
+ - Run `gosec ./...` for static security analysis in CI.
28
216
 
29
- - A propagated error with no `%w` and no context string.
217
+ ### Anti-patterns to flag in review (a linter won't)
218
+
219
+ - A propagated error with no `%w` and no context string, or an error discarded with `_`.
220
+ - `panic` used for a recoverable error instead of an error return.
30
221
  - An interface defined beside its implementation instead of its consumer.
31
222
  - Shared state touched by multiple goroutines with no mutex/channel and no safety comment.
32
223
  - A required env var read without an empty-check before use.
224
+ - Naked returns in long functions, mixed value/pointer receivers on the same type, `context.Context` stored in a struct field instead of passed as the first parameter.
225
+ - Functions over ~50 lines or nested more than 4 levels deep — prefer early return over `if/else` chains.
226
+
227
+ ### Testing & tooling
228
+
229
+ - Standard `go test` with **table-driven tests**; always run `go test -race ./...` and check coverage with `go test -cover ./...`. See skill `golang-testing` for the full pattern set (TDD workflow, subtests, benchmarks, fuzzing, golden files, HTTP handler tests).
230
+
231
+ ```bash
232
+ go build ./...
233
+ go vet ./...
234
+ staticcheck ./...
235
+ golangci-lint run
236
+ go test -race ./...
237
+ go test -cover ./...
238
+ gofmt -l .
239
+ goimports -l .
240
+ go mod tidy
241
+ go mod verify
242
+ ```
243
+
244
+ ### Quick reference: Go idioms
245
+
246
+ | Idiom | Description |
247
+ |-------|-------------|
248
+ | Accept interfaces, return structs | Functions accept interface params, return concrete types |
249
+ | Errors are values | Treat errors as first-class values, not exceptions |
250
+ | Don't communicate by sharing memory | Use channels for coordination between goroutines |
251
+ | Make the zero value useful | Types should work without explicit initialization |
252
+ | A little copying is better than a little dependency | Avoid unnecessary external dependencies |
253
+ | Clear is better than clever | Prioritize readability over cleverness |
254
+ | gofmt is no one's favorite but everyone's friend | Always format with gofmt/goimports |
255
+ | Return early | Handle errors first, keep happy path unindented |
256
+
257
+ See skills `golang-patterns` (comprehensive idioms, concurrency, package organization) and `golang-testing` (TDD workflow, table-driven tests, benchmarks, fuzzing) for full worked examples.
33
258
 
34
259
  ## Workflow pack: Go rules
35
260
 
36
- Go conventions a linter can't check interfaces accepted not returned, errors wrapped with context, functional options, owned shared statewith a reviewer and a gofmt guard; formatting, vet, and races belong in CI.
261
+ Comprehensive Go conventions, reviewer, build-error resolver, and skills covering API shape, errors, concurrency, interfaces, package layout, security, and testingmechanical checks (gofmt/vet/race) stay in CI.
262
+
263
+ When acting as the go-reviewer role: You are a senior Go code reviewer ensuring high standards of idiomatic Go and best practices.
264
+
265
+ When invoked:
266
+ 1. Run `git diff -- '*.go'` to see recent Go file changes.
267
+ 2. Run `go vet ./...` and `staticcheck ./...` if available.
268
+ 3. Focus on modified `.go` files.
269
+ 4. Begin review immediately.
270
+
271
+ ## Review priorities
272
+
273
+ ### CRITICAL — Security
274
+ - SQL injection: string concatenation in `database/sql` queries.
275
+ - Command injection: unvalidated input in `os/exec`.
276
+ - Path traversal: user-controlled file paths without `filepath.Clean` + prefix check.
277
+ - Race conditions: shared state without synchronization.
278
+ - `unsafe` package use without justification.
279
+ - Hardcoded secrets: API keys, passwords in source.
280
+ - Insecure TLS: `InsecureSkipVerify: true`.
281
+
282
+ ### CRITICAL — Error handling
283
+ - Ignored errors: using `_` to discard errors.
284
+ - Missing error wrapping: `return err` without `fmt.Errorf("context: %w", err)`.
285
+ - Panic for recoverable errors: use error returns instead.
286
+ - Missing `errors.Is`/`errors.As`: use `errors.Is(err, target)` not `err == target`.
287
+
288
+ ### HIGH — Concurrency
289
+ - Goroutine leaks: no cancellation mechanism (use `context.Context`).
290
+ - Unbuffered channel deadlock: sending without a guaranteed receiver.
291
+ - Missing `sync.WaitGroup`/errgroup: goroutines without coordination.
292
+ - Mutex misuse: not using `defer mu.Unlock()`.
293
+
294
+ ### HIGH — Code quality
295
+ - Large functions: over 50 lines.
296
+ - Deep nesting: more than 4 levels.
297
+ - Non-idiomatic: `if/else` instead of early return.
298
+ - Package-level variables: mutable global state.
299
+ - Interface pollution: defining unused abstractions.
300
+
301
+ ### MEDIUM — Performance
302
+ - String concatenation in loops: use `strings.Builder`.
303
+ - Missing slice pre-allocation: `make([]T, 0, cap)`.
304
+ - N+1 queries: database queries in loops.
305
+ - Unnecessary allocations: objects in hot paths.
306
+
307
+ ### MEDIUM — Best practices
308
+ - Context first: `ctx context.Context` should be the first parameter.
309
+ - Table-driven tests: tests should use the table-driven pattern.
310
+ - Error messages: lowercase, no punctuation.
311
+ - Package naming: short, lowercase, no underscores.
312
+ - Deferred call in a loop: resource accumulation risk.
313
+
314
+ ## Diagnostic commands
315
+
316
+ ```bash
317
+ go vet ./...
318
+ staticcheck ./...
319
+ golangci-lint run
320
+ go build -race ./...
321
+ go test -race ./...
322
+ govulncheck ./...
323
+ ```
37
324
 
38
- When acting as the go-reviewer role: You are a Go reviewer. Read the changed Go files and check them against the conventions in context: small consumer-defined interfaces, errors wrapped with `%w` and context, functional options, dependency injection through constructors, context threading, explicitly owned shared state, boundary input validation, and env-var secrets that fail fast. Formatting, `go vet`, and races are the linter/CI's job — don't re-litigate them. Cite the exact file:line for each finding, name the concrete failure mode, and give severity. If the change is sound, say so plainly rather than inventing findings.
325
+ ## Approval criteria
326
+
327
+ - Approve: no CRITICAL or HIGH issues.
328
+ - Warning: MEDIUM issues only.
329
+ - Block: CRITICAL or HIGH issues found.
330
+
331
+ Cite the exact file:line for each finding, name the concrete failure mode, and give severity. Formatting itself (gofmt) is the linter/CI's job — don't re-litigate it. If the change is sound, say so plainly rather than inventing findings.
332
+
333
+ For detailed Go code examples and anti-patterns, see skill `golang-patterns`.
334
+
335
+ When acting as the go-build-resolver role: You are an expert Go build error resolution specialist. Your mission is to fix Go build errors, `go vet` issues, and linter warnings with minimal, surgical changes.
336
+
337
+ ## Core responsibilities
338
+
339
+ 1. Diagnose Go compilation errors.
340
+ 2. Fix `go vet` warnings.
341
+ 3. Resolve `staticcheck`/`golangci-lint` issues.
342
+ 4. Handle module dependency problems.
343
+ 5. Fix type errors and interface mismatches.
344
+
345
+ ## Diagnostic commands (run in order)
346
+
347
+ ```bash
348
+ go build ./...
349
+ go vet ./...
350
+ staticcheck ./... 2>/dev/null || echo "staticcheck not installed"
351
+ golangci-lint run 2>/dev/null || echo "golangci-lint not installed"
352
+ go mod verify
353
+ go mod tidy -v
354
+ ```
355
+
356
+ ## Resolution workflow
357
+
358
+ ```text
359
+ 1. go build ./... -> Parse error message
360
+ 2. Read affected file -> Understand context
361
+ 3. Apply minimal fix -> Only what's needed
362
+ 4. go build ./... -> Verify fix
363
+ 5. go vet ./... -> Check for warnings
364
+ 6. go test ./... -> Ensure nothing broke
365
+ ```
366
+
367
+ ## Common fix patterns
368
+
369
+ | Error | Cause | Fix |
370
+ |-------|-------|-----|
371
+ | `undefined: X` | Missing import, typo, unexported | Add import or fix casing |
372
+ | `cannot use X as type Y` | Type mismatch, pointer/value | Type conversion or dereference |
373
+ | `X does not implement Y` | Missing method | Implement method with correct receiver |
374
+ | `import cycle not allowed` | Circular dependency | Extract shared types to new package |
375
+ | `cannot find package` | Missing dependency | `go get pkg@version` or `go mod tidy` |
376
+ | `missing return` | Incomplete control flow | Add return statement |
377
+ | `declared but not used` | Unused var/import | Remove or use blank identifier |
378
+ | `multiple-value in single-value context` | Unhandled return | `result, err := func()` |
379
+ | `cannot assign to struct field in map` | Map value mutation | Use pointer map or copy-modify-reassign |
380
+ | `invalid type assertion` | Assert on non-interface | Only assert from `interface{}` |
381
+
382
+ ## Module troubleshooting
383
+
384
+ ```bash
385
+ grep "replace" go.mod # Check local replaces
386
+ go mod why -m package # Why a version is selected
387
+ go get package@v1.2.3 # Pin specific version
388
+ go clean -modcache && go mod download # Fix checksum issues
389
+ ```
390
+
391
+ ## Key principles
392
+
393
+ - Surgical fixes only — don't refactor, just fix the error.
394
+ - Never add `//nolint` without explicit approval.
395
+ - Never change function signatures unless necessary.
396
+ - Always run `go mod tidy` after adding/removing imports.
397
+ - Fix root cause over suppressing symptoms.
398
+
399
+ ## Stop conditions
400
+
401
+ Stop and report if:
402
+ - The same error persists after 3 fix attempts.
403
+ - A fix introduces more errors than it resolves.
404
+ - The error requires architectural changes beyond scope.
405
+
406
+ ## Output format
407
+
408
+ ```text
409
+ [FIXED] internal/handler/user.go:42
410
+ Error: undefined: UserService
411
+ Fix: Added import "project/internal/service"
412
+ Remaining errors: 3
413
+ ```
414
+
415
+ Final line: `Build Status: SUCCESS/FAILED | Errors Fixed: N | Files Modified: list`
416
+
417
+ For detailed Go error patterns and code examples, see skill `golang-patterns`.
39
418
 
40
419
  Workflow steps:
41
420
 
42
- - go-review: Invoke the go-reviewer subagent on the changed Go files (the diff since $ARGUMENTS, or the working tree if no ref is given). It checks this pack's conventions consumer-defined interfaces, errors wrapped with %w and context, functional options, context threading, explicitly owned shared state, boundary validation, and fail-fast env-var secrets and reports file:line + severity. Formatting, go vet, and races are CI's job; don't re-litigate them. Fix any Critical or Important findings before merging.
421
+ - go-review: Invoke the go-reviewer subagent on the changed Go files (the diff since $ARGUMENTS, or the working tree if no ref is given). It checks this pack's conventions -- consumer-defined interfaces, errors wrapped with %w and context, functional options, context threading, explicitly owned shared state, boundary validation, and fail-fast env-var secrets -- plus the security/concurrency/quality priorities in its CRITICAL/HIGH/MEDIUM rubric, and reports file:line + severity. Formatting itself (gofmt) is CI's job; don't re-litigate it. Fix any Critical or High findings before merging.
422
+ - go-build: Invoke the go-build-resolver subagent to incrementally fix Go build errors, go vet warnings, and linter issues with minimal, surgical changes. It runs go build/go vet/staticcheck/golangci-lint/go mod verify in order, fixes one error at a time, and re-verifies after each change. It will not refactor beyond the fix or change function signatures unless necessary, and stops to report if the same error persists after 3 attempts or the fix requires an architectural change. Run /go-review after the build is green.
423
+ - go-test: Invoke the go-reviewer subagent to drive a TDD session for $ARGUMENTS: scaffold the function/interface signature first, write comprehensive table-driven tests (RED), verify they fail for the right reason, implement the minimal code to pass (GREEN), then refactor while keeping tests green (REFACTOR). Use go test -race ./... and go test -cover ./... to verify; target 80%+ coverage on general code and 100% on critical business logic. Use /go-build first if the package doesn't compile yet.
43
424
 
44
425
  - After an edit, check Go formatting with gofmt when it is installed.
426
+ - After an edit in a Go module, run gofmt and go vet and flag any issues.
427
+
428
+ ## Skills
429
+
430
+ ### golang-patterns
431
+
432
+ Idiomatic Go patterns, best practices, and conventions for building robust, efficient, and maintainable Go applications — API shape, error handling, concurrency, interface design, package organization, and performance. Use when writing, reviewing, or refactoring Go code, or designing Go packages/modules.
433
+
434
+ # Go Development Patterns
435
+
436
+ Idiomatic Go patterns and best practices for building robust, efficient, and maintainable applications.
437
+
438
+ ## When to use
439
+
440
+ - Writing new Go code.
441
+ - Reviewing Go code.
442
+ - Refactoring existing Go code.
443
+ - Designing Go packages/modules.
444
+
445
+ ## Core principles
446
+
447
+ ### 1. Simplicity and clarity
448
+
449
+ Go favors simplicity over cleverness. Code should be obvious and easy to read.
450
+
451
+ \`\`\`go
452
+ // Good: clear and direct
453
+ func GetUser(id string) (*User, error) {
454
+ user, err := db.FindUser(id)
455
+ if err != nil {
456
+ return nil, fmt.Errorf("get user %s: %w", id, err)
457
+ }
458
+ return user, nil
459
+ }
460
+
461
+ // Bad: overly clever
462
+ func GetUser(id string) (*User, error) {
463
+ return func() (*User, error) {
464
+ if u, e := db.FindUser(id); e == nil {
465
+ return u, nil
466
+ } else {
467
+ return nil, e
468
+ }
469
+ }()
470
+ }
471
+ \`\`\`
472
+
473
+ ### 2. Make the zero value useful
474
+
475
+ Design types so their zero value is immediately usable without initialization.
476
+
477
+ \`\`\`go
478
+ // Good: zero value is useful
479
+ type Counter struct {
480
+ mu sync.Mutex
481
+ count int // zero value is 0, ready to use
482
+ }
483
+
484
+ func (c *Counter) Inc() {
485
+ c.mu.Lock()
486
+ c.count++
487
+ c.mu.Unlock()
488
+ }
489
+
490
+ // Good: bytes.Buffer works with zero value
491
+ var buf bytes.Buffer
492
+ buf.WriteString("hello")
493
+
494
+ // Bad: requires initialization
495
+ type BadCounter struct {
496
+ counts map[string]int // nil map will panic
497
+ }
498
+ \`\`\`
499
+
500
+ ### 3. Accept interfaces, return structs
501
+
502
+ Functions should accept interface parameters and return concrete types.
503
+
504
+ \`\`\`go
505
+ // Good: accepts interface, returns concrete type
506
+ func ProcessData(r io.Reader) (*Result, error) {
507
+ data, err := io.ReadAll(r)
508
+ if err != nil {
509
+ return nil, err
510
+ }
511
+ return &Result{Data: data}, nil
512
+ }
513
+
514
+ // Bad: returns interface (hides implementation details unnecessarily)
515
+ func ProcessData(r io.Reader) (io.Reader, error) {
516
+ // ...
517
+ }
518
+ \`\`\`
519
+
520
+ ## Error handling patterns
521
+
522
+ ### Error wrapping with context
523
+
524
+ \`\`\`go
525
+ func LoadConfig(path string) (*Config, error) {
526
+ data, err := os.ReadFile(path)
527
+ if err != nil {
528
+ return nil, fmt.Errorf("load config %s: %w", path, err)
529
+ }
530
+
531
+ var cfg Config
532
+ if err := json.Unmarshal(data, &cfg); err != nil {
533
+ return nil, fmt.Errorf("parse config %s: %w", path, err)
534
+ }
535
+
536
+ return &cfg, nil
537
+ }
538
+ \`\`\`
539
+
540
+ ### Custom error types
541
+
542
+ \`\`\`go
543
+ // Define domain-specific errors
544
+ type ValidationError struct {
545
+ Field string
546
+ Message string
547
+ }
548
+
549
+ func (e *ValidationError) Error() string {
550
+ return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
551
+ }
552
+
553
+ // Sentinel errors for common cases
554
+ var (
555
+ ErrNotFound = errors.New("resource not found")
556
+ ErrUnauthorized = errors.New("unauthorized")
557
+ ErrInvalidInput = errors.New("invalid input")
558
+ )
559
+ \`\`\`
560
+
561
+ ### Error checking with errors.Is and errors.As
562
+
563
+ \`\`\`go
564
+ func HandleError(err error) {
565
+ // Check for specific error
566
+ if errors.Is(err, sql.ErrNoRows) {
567
+ log.Println("No records found")
568
+ return
569
+ }
570
+
571
+ // Check for error type
572
+ var validationErr *ValidationError
573
+ if errors.As(err, &validationErr) {
574
+ log.Printf("Validation error on field %s: %s",
575
+ validationErr.Field, validationErr.Message)
576
+ return
577
+ }
578
+
579
+ // Unknown error
580
+ log.Printf("Unexpected error: %v", err)
581
+ }
582
+ \`\`\`
583
+
584
+ ### Never ignore errors
585
+
586
+ \`\`\`go
587
+ // Bad: ignoring error with blank identifier
588
+ result, _ := doSomething()
589
+
590
+ // Good: handle or explicitly document why it's safe to ignore
591
+ result, err := doSomething()
592
+ if err != nil {
593
+ return err
594
+ }
595
+
596
+ // Acceptable: when error truly doesn't matter (rare)
597
+ _ = writer.Close() // Best-effort cleanup, error logged elsewhere
598
+ \`\`\`
599
+
600
+ ## Concurrency patterns
601
+
602
+ ### Worker pool
603
+
604
+ \`\`\`go
605
+ func WorkerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
606
+ var wg sync.WaitGroup
607
+
608
+ for i := 0; i < numWorkers; i++ {
609
+ wg.Add(1)
610
+ go func() {
611
+ defer wg.Done()
612
+ for job := range jobs {
613
+ results <- process(job)
614
+ }
615
+ }()
616
+ }
617
+
618
+ wg.Wait()
619
+ close(results)
620
+ }
621
+ \`\`\`
622
+
623
+ ### Context for cancellation and timeouts
624
+
625
+ \`\`\`go
626
+ func FetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
627
+ ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
628
+ defer cancel()
629
+
630
+ req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
631
+ if err != nil {
632
+ return nil, fmt.Errorf("create request: %w", err)
633
+ }
634
+
635
+ resp, err := http.DefaultClient.Do(req)
636
+ if err != nil {
637
+ return nil, fmt.Errorf("fetch %s: %w", url, err)
638
+ }
639
+ defer resp.Body.Close()
640
+
641
+ return io.ReadAll(resp.Body)
642
+ }
643
+ \`\`\`
644
+
645
+ ### Graceful shutdown
646
+
647
+ \`\`\`go
648
+ func GracefulShutdown(server *http.Server) {
649
+ quit := make(chan os.Signal, 1)
650
+ signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
651
+
652
+ <-quit
653
+ log.Println("Shutting down server...")
654
+
655
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
656
+ defer cancel()
657
+
658
+ if err := server.Shutdown(ctx); err != nil {
659
+ log.Fatalf("Server forced to shutdown: %v", err)
660
+ }
661
+
662
+ log.Println("Server exited")
663
+ }
664
+ \`\`\`
665
+
666
+ ### errgroup for coordinated goroutines
667
+
668
+ \`\`\`go
669
+ import "golang.org/x/sync/errgroup"
670
+
671
+ func FetchAll(ctx context.Context, urls []string) ([][]byte, error) {
672
+ g, ctx := errgroup.WithContext(ctx)
673
+ results := make([][]byte, len(urls))
674
+
675
+ for i, url := range urls {
676
+ i, url := i, url // Capture loop variables
677
+ g.Go(func() error {
678
+ data, err := FetchWithTimeout(ctx, url)
679
+ if err != nil {
680
+ return err
681
+ }
682
+ results[i] = data
683
+ return nil
684
+ })
685
+ }
686
+
687
+ if err := g.Wait(); err != nil {
688
+ return nil, err
689
+ }
690
+ return results, nil
691
+ }
692
+ \`\`\`
693
+
694
+ ### Avoiding goroutine leaks
695
+
696
+ \`\`\`go
697
+ // Bad: goroutine leak if context is cancelled
698
+ func leakyFetch(ctx context.Context, url string) <-chan []byte {
699
+ ch := make(chan []byte)
700
+ go func() {
701
+ data, _ := fetch(url)
702
+ ch <- data // Blocks forever if no receiver
703
+ }()
704
+ return ch
705
+ }
706
+
707
+ // Good: properly handles cancellation
708
+ func safeFetch(ctx context.Context, url string) <-chan []byte {
709
+ ch := make(chan []byte, 1) // Buffered channel
710
+ go func() {
711
+ data, err := fetch(url)
712
+ if err != nil {
713
+ return
714
+ }
715
+ select {
716
+ case ch <- data:
717
+ case <-ctx.Done():
718
+ }
719
+ }()
720
+ return ch
721
+ }
722
+ \`\`\`
723
+
724
+ ## Interface design
725
+
726
+ ### Small, focused interfaces
727
+
728
+ \`\`\`go
729
+ // Good: single-method interfaces
730
+ type Reader interface {
731
+ Read(p []byte) (n int, err error)
732
+ }
733
+
734
+ type Writer interface {
735
+ Write(p []byte) (n int, err error)
736
+ }
737
+
738
+ type Closer interface {
739
+ Close() error
740
+ }
741
+
742
+ // Compose interfaces as needed
743
+ type ReadWriteCloser interface {
744
+ Reader
745
+ Writer
746
+ Closer
747
+ }
748
+ \`\`\`
749
+
750
+ ### Define interfaces where they're used
751
+
752
+ \`\`\`go
753
+ // In the consumer package, not the provider
754
+ package service
755
+
756
+ // UserStore defines what this service needs
757
+ type UserStore interface {
758
+ GetUser(id string) (*User, error)
759
+ SaveUser(user *User) error
760
+ }
761
+
762
+ type Service struct {
763
+ store UserStore
764
+ }
765
+
766
+ // Concrete implementation can be in another package
767
+ // It doesn't need to know about this interface
768
+ \`\`\`
769
+
770
+ ### Optional behavior with type assertions
771
+
772
+ \`\`\`go
773
+ type Flusher interface {
774
+ Flush() error
775
+ }
776
+
777
+ func WriteAndFlush(w io.Writer, data []byte) error {
778
+ if _, err := w.Write(data); err != nil {
779
+ return err
780
+ }
781
+
782
+ // Flush if supported
783
+ if f, ok := w.(Flusher); ok {
784
+ return f.Flush()
785
+ }
786
+ return nil
787
+ }
788
+ \`\`\`
789
+
790
+ ## Package organization
791
+
792
+ ### Standard project layout
793
+
794
+ \`\`\`text
795
+ myproject/
796
+ ├── cmd/
797
+ │ └── myapp/
798
+ │ └── main.go # Entry point
799
+ ├── internal/
800
+ │ ├── handler/ # HTTP handlers
801
+ │ ├── service/ # Business logic
802
+ │ ├── repository/ # Data access
803
+ │ └── config/ # Configuration
804
+ ├── pkg/
805
+ │ └── client/ # Public API client
806
+ ├── api/
807
+ │ └── v1/ # API definitions (proto, OpenAPI)
808
+ ├── testdata/ # Test fixtures
809
+ ├── go.mod
810
+ ├── go.sum
811
+ └── Makefile
812
+ \`\`\`
813
+
814
+ ### Package naming
815
+
816
+ \`\`\`go
817
+ // Good: short, lowercase, no underscores
818
+ package http
819
+ package json
820
+ package user
821
+
822
+ // Bad: verbose, mixed case, or redundant
823
+ package httpHandler
824
+ package json_parser
825
+ package userService // Redundant 'Service' suffix
826
+ \`\`\`
827
+
828
+ ### Avoid package-level state
829
+
830
+ \`\`\`go
831
+ // Bad: global mutable state
832
+ var db *sql.DB
833
+
834
+ func init() {
835
+ db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))
836
+ }
837
+
838
+ // Good: dependency injection
839
+ type Server struct {
840
+ db *sql.DB
841
+ }
842
+
843
+ func NewServer(db *sql.DB) *Server {
844
+ return &Server{db: db}
845
+ }
846
+ \`\`\`
847
+
848
+ ## Struct design
849
+
850
+ ### Functional options pattern
851
+
852
+ \`\`\`go
853
+ type Server struct {
854
+ addr string
855
+ timeout time.Duration
856
+ logger *log.Logger
857
+ }
858
+
859
+ type Option func(*Server)
860
+
861
+ func WithTimeout(d time.Duration) Option {
862
+ return func(s *Server) {
863
+ s.timeout = d
864
+ }
865
+ }
866
+
867
+ func WithLogger(l *log.Logger) Option {
868
+ return func(s *Server) {
869
+ s.logger = l
870
+ }
871
+ }
872
+
873
+ func NewServer(addr string, opts ...Option) *Server {
874
+ s := &Server{
875
+ addr: addr,
876
+ timeout: 30 * time.Second, // default
877
+ logger: log.Default(), // default
878
+ }
879
+ for _, opt := range opts {
880
+ opt(s)
881
+ }
882
+ return s
883
+ }
884
+
885
+ // Usage
886
+ server := NewServer(":8080",
887
+ WithTimeout(60*time.Second),
888
+ WithLogger(customLogger),
889
+ )
890
+ \`\`\`
891
+
892
+ ### Embedding for composition
893
+
894
+ \`\`\`go
895
+ type Logger struct {
896
+ prefix string
897
+ }
898
+
899
+ func (l *Logger) Log(msg string) {
900
+ fmt.Printf("[%s] %s\n", l.prefix, msg)
901
+ }
902
+
903
+ type Server struct {
904
+ *Logger // Embedding - Server gets Log method
905
+ addr string
906
+ }
907
+
908
+ func NewServer(addr string) *Server {
909
+ return &Server{
910
+ Logger: &Logger{prefix: "SERVER"},
911
+ addr: addr,
912
+ }
913
+ }
914
+
915
+ // Usage
916
+ s := NewServer(":8080")
917
+ s.Log("Starting...") // Calls embedded Logger.Log
918
+ \`\`\`
919
+
920
+ ## Memory and performance
921
+
922
+ ### Preallocate slices when size is known
923
+
924
+ \`\`\`go
925
+ // Bad: grows slice multiple times
926
+ func processItems(items []Item) []Result {
927
+ var results []Result
928
+ for _, item := range items {
929
+ results = append(results, process(item))
930
+ }
931
+ return results
932
+ }
933
+
934
+ // Good: single allocation
935
+ func processItems(items []Item) []Result {
936
+ results := make([]Result, 0, len(items))
937
+ for _, item := range items {
938
+ results = append(results, process(item))
939
+ }
940
+ return results
941
+ }
942
+ \`\`\`
943
+
944
+ ### Use sync.Pool for frequent allocations
945
+
946
+ \`\`\`go
947
+ var bufferPool = sync.Pool{
948
+ New: func() interface{} {
949
+ return new(bytes.Buffer)
950
+ },
951
+ }
952
+
953
+ func ProcessRequest(data []byte) []byte {
954
+ buf := bufferPool.Get().(*bytes.Buffer)
955
+ defer func() {
956
+ buf.Reset()
957
+ bufferPool.Put(buf)
958
+ }()
959
+
960
+ buf.Write(data)
961
+ // Process...
962
+ return buf.Bytes()
963
+ }
964
+ \`\`\`
965
+
966
+ ### Avoid string concatenation in loops
967
+
968
+ \`\`\`go
969
+ // Bad: creates many string allocations
970
+ func join(parts []string) string {
971
+ var result string
972
+ for _, p := range parts {
973
+ result += p + ","
974
+ }
975
+ return result
976
+ }
977
+
978
+ // Good: single allocation with strings.Builder
979
+ func join(parts []string) string {
980
+ var sb strings.Builder
981
+ for i, p := range parts {
982
+ if i > 0 {
983
+ sb.WriteString(",")
984
+ }
985
+ sb.WriteString(p)
986
+ }
987
+ return sb.String()
988
+ }
989
+
990
+ // Best: use standard library
991
+ func join(parts []string) string {
992
+ return strings.Join(parts, ",")
993
+ }
994
+ \`\`\`
995
+
996
+ ## Go tooling integration
997
+
998
+ ### Essential commands
999
+
1000
+ \`\`\`bash
1001
+ # Build and run
1002
+ go build ./...
1003
+ go run ./cmd/myapp
1004
+
1005
+ # Testing
1006
+ go test ./...
1007
+ go test -race ./...
1008
+ go test -cover ./...
1009
+
1010
+ # Static analysis
1011
+ go vet ./...
1012
+ staticcheck ./...
1013
+ golangci-lint run
1014
+
1015
+ # Module management
1016
+ go mod tidy
1017
+ go mod verify
1018
+
1019
+ # Formatting
1020
+ gofmt -w .
1021
+ goimports -w .
1022
+ \`\`\`
1023
+
1024
+ ### Recommended linter configuration (.golangci.yml)
1025
+
1026
+ \`\`\`yaml
1027
+ linters:
1028
+ enable:
1029
+ - errcheck
1030
+ - gosimple
1031
+ - govet
1032
+ - ineffassign
1033
+ - staticcheck
1034
+ - unused
1035
+ - gofmt
1036
+ - goimports
1037
+ - misspell
1038
+ - unconvert
1039
+ - unparam
1040
+
1041
+ linters-settings:
1042
+ errcheck:
1043
+ check-type-assertions: true
1044
+ govet:
1045
+ enable:
1046
+ - shadow
1047
+
1048
+ issues:
1049
+ exclude-use-default: false
1050
+ \`\`\`
1051
+
1052
+ ## Quick reference: Go idioms
1053
+
1054
+ | Idiom | Description |
1055
+ |-------|-------------|
1056
+ | Accept interfaces, return structs | Functions accept interface params, return concrete types |
1057
+ | Errors are values | Treat errors as first-class values, not exceptions |
1058
+ | Don't communicate by sharing memory | Use channels for coordination between goroutines |
1059
+ | Make the zero value useful | Types should work without explicit initialization |
1060
+ | A little copying is better than a little dependency | Avoid unnecessary external dependencies |
1061
+ | Clear is better than clever | Prioritize readability over cleverness |
1062
+ | gofmt is no one's favorite but everyone's friend | Always format with gofmt/goimports |
1063
+ | Return early | Handle errors first, keep happy path unindented |
1064
+
1065
+ ## Anti-patterns to avoid
1066
+
1067
+ \`\`\`go
1068
+ // Bad: naked returns in long functions
1069
+ func process() (result int, err error) {
1070
+ // ... 50 lines ...
1071
+ return // What is being returned?
1072
+ }
1073
+
1074
+ // Bad: using panic for control flow
1075
+ func GetUser(id string) *User {
1076
+ user, err := db.Find(id)
1077
+ if err != nil {
1078
+ panic(err) // Don't do this
1079
+ }
1080
+ return user
1081
+ }
1082
+
1083
+ // Bad: passing context in struct
1084
+ type Request struct {
1085
+ ctx context.Context // Context should be first param
1086
+ ID string
1087
+ }
1088
+
1089
+ // Good: context as first parameter
1090
+ func ProcessRequest(ctx context.Context, id string) error {
1091
+ // ...
1092
+ }
1093
+
1094
+ // Bad: mixing value and pointer receivers
1095
+ type Counter struct{ n int }
1096
+ func (c Counter) Value() int { return c.n } // Value receiver
1097
+ func (c *Counter) Increment() { c.n++ } // Pointer receiver
1098
+ // Pick one style and be consistent
1099
+ \`\`\`
1100
+
1101
+ **Remember**: Go code should be boring in the best way — predictable, consistent, and easy to understand. When in doubt, keep it simple.
1102
+
1103
+ ### golang-testing
1104
+
1105
+ Go testing patterns including TDD workflow, table-driven tests, subtests, benchmarks, fuzzing, golden files, HTTP handler testing, mocking with interfaces, and coverage targets. Use when writing new Go functions, adding test coverage, creating benchmarks, or following TDD in a Go project.
1106
+
1107
+ # Go Testing Patterns
1108
+
1109
+ Comprehensive Go testing patterns for writing reliable, maintainable tests following TDD methodology.
1110
+
1111
+ ## When to use
1112
+
1113
+ - Writing new Go functions or methods.
1114
+ - Adding test coverage to existing code.
1115
+ - Creating benchmarks for performance-critical code.
1116
+ - Implementing fuzz tests for input validation.
1117
+ - Following a TDD workflow in Go projects.
1118
+
1119
+ ## TDD workflow for Go
1120
+
1121
+ ### The RED-GREEN-REFACTOR cycle
1122
+
1123
+ \`\`\`
1124
+ RED → Write a failing test first
1125
+ GREEN → Write minimal code to pass the test
1126
+ REFACTOR → Improve code while keeping tests green
1127
+ REPEAT → Continue with next requirement
1128
+ \`\`\`
1129
+
1130
+ ### Step-by-step TDD in Go
1131
+
1132
+ \`\`\`go
1133
+ // Step 1: Define the interface/signature
1134
+ // calculator.go
1135
+ package calculator
1136
+
1137
+ func Add(a, b int) int {
1138
+ panic("not implemented") // Placeholder
1139
+ }
1140
+
1141
+ // Step 2: Write failing test (RED)
1142
+ // calculator_test.go
1143
+ package calculator
1144
+
1145
+ import "testing"
1146
+
1147
+ func TestAdd(t *testing.T) {
1148
+ got := Add(2, 3)
1149
+ want := 5
1150
+ if got != want {
1151
+ t.Errorf("Add(2, 3) = %d; want %d", got, want)
1152
+ }
1153
+ }
1154
+
1155
+ // Step 3: Run test - verify FAIL
1156
+ // $ go test
1157
+ // --- FAIL: TestAdd (0.00s)
1158
+ // panic: not implemented
1159
+
1160
+ // Step 4: Implement minimal code (GREEN)
1161
+ func Add(a, b int) int {
1162
+ return a + b
1163
+ }
1164
+
1165
+ // Step 5: Run test - verify PASS
1166
+ // $ go test
1167
+ // PASS
1168
+
1169
+ // Step 6: Refactor if needed, verify tests still pass
1170
+ \`\`\`
1171
+
1172
+ ## Table-driven tests
1173
+
1174
+ The standard pattern for Go tests. Enables comprehensive coverage with minimal code.
1175
+
1176
+ \`\`\`go
1177
+ func TestAdd(t *testing.T) {
1178
+ tests := []struct {
1179
+ name string
1180
+ a, b int
1181
+ expected int
1182
+ }{
1183
+ {"positive numbers", 2, 3, 5},
1184
+ {"negative numbers", -1, -2, -3},
1185
+ {"zero values", 0, 0, 0},
1186
+ {"mixed signs", -1, 1, 0},
1187
+ {"large numbers", 1000000, 2000000, 3000000},
1188
+ }
1189
+
1190
+ for _, tt := range tests {
1191
+ t.Run(tt.name, func(t *testing.T) {
1192
+ got := Add(tt.a, tt.b)
1193
+ if got != tt.expected {
1194
+ t.Errorf("Add(%d, %d) = %d; want %d",
1195
+ tt.a, tt.b, got, tt.expected)
1196
+ }
1197
+ })
1198
+ }
1199
+ }
1200
+ \`\`\`
1201
+
1202
+ ### Table-driven tests with error cases
1203
+
1204
+ \`\`\`go
1205
+ func TestParseConfig(t *testing.T) {
1206
+ tests := []struct {
1207
+ name string
1208
+ input string
1209
+ want *Config
1210
+ wantErr bool
1211
+ }{
1212
+ {
1213
+ name: "valid config",
1214
+ input: \`{"host": "localhost", "port": 8080}\`,
1215
+ want: &Config{Host: "localhost", Port: 8080},
1216
+ },
1217
+ {
1218
+ name: "invalid JSON",
1219
+ input: \`{invalid}\`,
1220
+ wantErr: true,
1221
+ },
1222
+ {
1223
+ name: "empty input",
1224
+ input: "",
1225
+ wantErr: true,
1226
+ },
1227
+ {
1228
+ name: "minimal config",
1229
+ input: \`{}\`,
1230
+ want: &Config{}, // Zero value config
1231
+ },
1232
+ }
1233
+
1234
+ for _, tt := range tests {
1235
+ t.Run(tt.name, func(t *testing.T) {
1236
+ got, err := ParseConfig(tt.input)
1237
+
1238
+ if tt.wantErr {
1239
+ if err == nil {
1240
+ t.Error("expected error, got nil")
1241
+ }
1242
+ return
1243
+ }
1244
+
1245
+ if err != nil {
1246
+ t.Fatalf("unexpected error: %v", err)
1247
+ }
1248
+
1249
+ if !reflect.DeepEqual(got, tt.want) {
1250
+ t.Errorf("got %+v; want %+v", got, tt.want)
1251
+ }
1252
+ })
1253
+ }
1254
+ }
1255
+ \`\`\`
1256
+
1257
+ ## Subtests and sub-benchmarks
1258
+
1259
+ ### Organizing related tests
1260
+
1261
+ \`\`\`go
1262
+ func TestUser(t *testing.T) {
1263
+ // Setup shared by all subtests
1264
+ db := setupTestDB(t)
1265
+
1266
+ t.Run("Create", func(t *testing.T) {
1267
+ user := &User{Name: "Alice"}
1268
+ err := db.CreateUser(user)
1269
+ if err != nil {
1270
+ t.Fatalf("CreateUser failed: %v", err)
1271
+ }
1272
+ if user.ID == "" {
1273
+ t.Error("expected user ID to be set")
1274
+ }
1275
+ })
1276
+
1277
+ t.Run("Get", func(t *testing.T) {
1278
+ user, err := db.GetUser("alice-id")
1279
+ if err != nil {
1280
+ t.Fatalf("GetUser failed: %v", err)
1281
+ }
1282
+ if user.Name != "Alice" {
1283
+ t.Errorf("got name %q; want %q", user.Name, "Alice")
1284
+ }
1285
+ })
1286
+
1287
+ t.Run("Update", func(t *testing.T) {
1288
+ // ...
1289
+ })
1290
+
1291
+ t.Run("Delete", func(t *testing.T) {
1292
+ // ...
1293
+ })
1294
+ }
1295
+ \`\`\`
1296
+
1297
+ ### Parallel subtests
1298
+
1299
+ \`\`\`go
1300
+ func TestParallel(t *testing.T) {
1301
+ tests := []struct {
1302
+ name string
1303
+ input string
1304
+ }{
1305
+ {"case1", "input1"},
1306
+ {"case2", "input2"},
1307
+ {"case3", "input3"},
1308
+ }
1309
+
1310
+ for _, tt := range tests {
1311
+ tt := tt // Capture range variable
1312
+ t.Run(tt.name, func(t *testing.T) {
1313
+ t.Parallel() // Run subtests in parallel
1314
+ result := Process(tt.input)
1315
+ // assertions...
1316
+ _ = result
1317
+ })
1318
+ }
1319
+ }
1320
+ \`\`\`
1321
+
1322
+ ## Test helpers
1323
+
1324
+ ### Helper functions
1325
+
1326
+ \`\`\`go
1327
+ func setupTestDB(t *testing.T) *sql.DB {
1328
+ t.Helper() // Marks this as a helper function
1329
+
1330
+ db, err := sql.Open("sqlite3", ":memory:")
1331
+ if err != nil {
1332
+ t.Fatalf("failed to open database: %v", err)
1333
+ }
1334
+
1335
+ // Cleanup when test finishes
1336
+ t.Cleanup(func() {
1337
+ db.Close()
1338
+ })
1339
+
1340
+ // Run migrations
1341
+ if _, err := db.Exec(schema); err != nil {
1342
+ t.Fatalf("failed to create schema: %v", err)
1343
+ }
1344
+
1345
+ return db
1346
+ }
1347
+
1348
+ func assertNoError(t *testing.T, err error) {
1349
+ t.Helper()
1350
+ if err != nil {
1351
+ t.Fatalf("unexpected error: %v", err)
1352
+ }
1353
+ }
1354
+
1355
+ func assertEqual[T comparable](t *testing.T, got, want T) {
1356
+ t.Helper()
1357
+ if got != want {
1358
+ t.Errorf("got %v; want %v", got, want)
1359
+ }
1360
+ }
1361
+ \`\`\`
1362
+
1363
+ ### Temporary files and directories
1364
+
1365
+ \`\`\`go
1366
+ func TestFileProcessing(t *testing.T) {
1367
+ // Create temp directory - automatically cleaned up
1368
+ tmpDir := t.TempDir()
1369
+
1370
+ // Create test file
1371
+ testFile := filepath.Join(tmpDir, "test.txt")
1372
+ err := os.WriteFile(testFile, []byte("test content"), 0644)
1373
+ if err != nil {
1374
+ t.Fatalf("failed to create test file: %v", err)
1375
+ }
1376
+
1377
+ // Run test
1378
+ result, err := ProcessFile(testFile)
1379
+ if err != nil {
1380
+ t.Fatalf("ProcessFile failed: %v", err)
1381
+ }
1382
+
1383
+ // Assert...
1384
+ _ = result
1385
+ }
1386
+ \`\`\`
1387
+
1388
+ ## Golden files
1389
+
1390
+ Testing against expected output files stored in \`testdata/\`.
1391
+
1392
+ \`\`\`go
1393
+ var update = flag.Bool("update", false, "update golden files")
1394
+
1395
+ func TestRender(t *testing.T) {
1396
+ tests := []struct {
1397
+ name string
1398
+ input Template
1399
+ }{
1400
+ {"simple", Template{Name: "test"}},
1401
+ {"complex", Template{Name: "test", Items: []string{"a", "b"}}},
1402
+ }
1403
+
1404
+ for _, tt := range tests {
1405
+ t.Run(tt.name, func(t *testing.T) {
1406
+ got := Render(tt.input)
1407
+
1408
+ golden := filepath.Join("testdata", tt.name+".golden")
1409
+
1410
+ if *update {
1411
+ // Update golden file: go test -update
1412
+ err := os.WriteFile(golden, got, 0644)
1413
+ if err != nil {
1414
+ t.Fatalf("failed to update golden file: %v", err)
1415
+ }
1416
+ }
1417
+
1418
+ want, err := os.ReadFile(golden)
1419
+ if err != nil {
1420
+ t.Fatalf("failed to read golden file: %v", err)
1421
+ }
1422
+
1423
+ if !bytes.Equal(got, want) {
1424
+ t.Errorf("output mismatch:\ngot:\n%s\nwant:\n%s", got, want)
1425
+ }
1426
+ })
1427
+ }
1428
+ }
1429
+ \`\`\`
1430
+
1431
+ ## Mocking with interfaces
1432
+
1433
+ ### Interface-based mocking
1434
+
1435
+ \`\`\`go
1436
+ // Define interface for dependencies
1437
+ type UserRepository interface {
1438
+ GetUser(id string) (*User, error)
1439
+ SaveUser(user *User) error
1440
+ }
1441
+
1442
+ // Production implementation
1443
+ type PostgresUserRepository struct {
1444
+ db *sql.DB
1445
+ }
1446
+
1447
+ func (r *PostgresUserRepository) GetUser(id string) (*User, error) {
1448
+ // Real database query
1449
+ }
1450
+
1451
+ // Mock implementation for tests
1452
+ type MockUserRepository struct {
1453
+ GetUserFunc func(id string) (*User, error)
1454
+ SaveUserFunc func(user *User) error
1455
+ }
1456
+
1457
+ func (m *MockUserRepository) GetUser(id string) (*User, error) {
1458
+ return m.GetUserFunc(id)
1459
+ }
1460
+
1461
+ func (m *MockUserRepository) SaveUser(user *User) error {
1462
+ return m.SaveUserFunc(user)
1463
+ }
1464
+
1465
+ // Test using mock
1466
+ func TestUserService(t *testing.T) {
1467
+ mock := &MockUserRepository{
1468
+ GetUserFunc: func(id string) (*User, error) {
1469
+ if id == "123" {
1470
+ return &User{ID: "123", Name: "Alice"}, nil
1471
+ }
1472
+ return nil, ErrNotFound
1473
+ },
1474
+ }
1475
+
1476
+ service := NewUserService(mock)
1477
+
1478
+ user, err := service.GetUserProfile("123")
1479
+ if err != nil {
1480
+ t.Fatalf("unexpected error: %v", err)
1481
+ }
1482
+ if user.Name != "Alice" {
1483
+ t.Errorf("got name %q; want %q", user.Name, "Alice")
1484
+ }
1485
+ }
1486
+ \`\`\`
1487
+
1488
+ ## Benchmarks
1489
+
1490
+ ### Basic benchmarks
1491
+
1492
+ \`\`\`go
1493
+ func BenchmarkProcess(b *testing.B) {
1494
+ data := generateTestData(1000)
1495
+ b.ResetTimer() // Don't count setup time
1496
+
1497
+ for i := 0; i < b.N; i++ {
1498
+ Process(data)
1499
+ }
1500
+ }
1501
+
1502
+ // Run: go test -bench=BenchmarkProcess -benchmem
1503
+ // Output: BenchmarkProcess-8 10000 105234 ns/op 4096 B/op 10 allocs/op
1504
+ \`\`\`
1505
+
1506
+ ### Benchmark with different sizes
1507
+
1508
+ \`\`\`go
1509
+ func BenchmarkSort(b *testing.B) {
1510
+ sizes := []int{100, 1000, 10000, 100000}
1511
+
1512
+ for _, size := range sizes {
1513
+ b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
1514
+ data := generateRandomSlice(size)
1515
+ b.ResetTimer()
1516
+
1517
+ for i := 0; i < b.N; i++ {
1518
+ // Make a copy to avoid sorting already sorted data
1519
+ tmp := make([]int, len(data))
1520
+ copy(tmp, data)
1521
+ sort.Ints(tmp)
1522
+ }
1523
+ })
1524
+ }
1525
+ }
1526
+ \`\`\`
1527
+
1528
+ ### Memory allocation benchmarks
1529
+
1530
+ \`\`\`go
1531
+ func BenchmarkStringConcat(b *testing.B) {
1532
+ parts := []string{"hello", "world", "foo", "bar", "baz"}
1533
+
1534
+ b.Run("plus", func(b *testing.B) {
1535
+ for i := 0; i < b.N; i++ {
1536
+ var s string
1537
+ for _, p := range parts {
1538
+ s += p
1539
+ }
1540
+ _ = s
1541
+ }
1542
+ })
1543
+
1544
+ b.Run("builder", func(b *testing.B) {
1545
+ for i := 0; i < b.N; i++ {
1546
+ var sb strings.Builder
1547
+ for _, p := range parts {
1548
+ sb.WriteString(p)
1549
+ }
1550
+ _ = sb.String()
1551
+ }
1552
+ })
1553
+
1554
+ b.Run("join", func(b *testing.B) {
1555
+ for i := 0; i < b.N; i++ {
1556
+ _ = strings.Join(parts, "")
1557
+ }
1558
+ })
1559
+ }
1560
+ \`\`\`
1561
+
1562
+ ## Fuzzing (Go 1.18+)
1563
+
1564
+ ### Basic fuzz test
1565
+
1566
+ \`\`\`go
1567
+ func FuzzParseJSON(f *testing.F) {
1568
+ // Add seed corpus
1569
+ f.Add(\`{"name": "test"}\`)
1570
+ f.Add(\`{"count": 123}\`)
1571
+ f.Add(\`[]\`)
1572
+ f.Add(\`""\`)
1573
+
1574
+ f.Fuzz(func(t *testing.T, input string) {
1575
+ var result map[string]interface{}
1576
+ err := json.Unmarshal([]byte(input), &result)
1577
+
1578
+ if err != nil {
1579
+ // Invalid JSON is expected for random input
1580
+ return
1581
+ }
1582
+
1583
+ // If parsing succeeded, re-encoding should work
1584
+ _, err = json.Marshal(result)
1585
+ if err != nil {
1586
+ t.Errorf("Marshal failed after successful Unmarshal: %v", err)
1587
+ }
1588
+ })
1589
+ }
1590
+
1591
+ // Run: go test -fuzz=FuzzParseJSON -fuzztime=30s
1592
+ \`\`\`
1593
+
1594
+ ### Fuzz test with multiple inputs
1595
+
1596
+ \`\`\`go
1597
+ func FuzzCompare(f *testing.F) {
1598
+ f.Add("hello", "world")
1599
+ f.Add("", "")
1600
+ f.Add("abc", "abc")
1601
+
1602
+ f.Fuzz(func(t *testing.T, a, b string) {
1603
+ result := Compare(a, b)
1604
+
1605
+ // Property: Compare(a, a) should always equal 0
1606
+ if a == b && result != 0 {
1607
+ t.Errorf("Compare(%q, %q) = %d; want 0", a, b, result)
1608
+ }
1609
+
1610
+ // Property: Compare(a, b) and Compare(b, a) should have opposite signs
1611
+ reverse := Compare(b, a)
1612
+ if (result > 0 && reverse >= 0) || (result < 0 && reverse <= 0) {
1613
+ if result != 0 || reverse != 0 {
1614
+ t.Errorf("Compare(%q, %q) = %d, Compare(%q, %q) = %d; inconsistent",
1615
+ a, b, result, b, a, reverse)
1616
+ }
1617
+ }
1618
+ })
1619
+ }
1620
+ \`\`\`
1621
+
1622
+ ## Test coverage
1623
+
1624
+ ### Running coverage
1625
+
1626
+ \`\`\`bash
1627
+ # Basic coverage
1628
+ go test -cover ./...
1629
+
1630
+ # Generate coverage profile
1631
+ go test -coverprofile=coverage.out ./...
1632
+
1633
+ # View coverage in browser
1634
+ go tool cover -html=coverage.out
1635
+
1636
+ # View coverage by function
1637
+ go tool cover -func=coverage.out
1638
+
1639
+ # Coverage with race detection
1640
+ go test -race -coverprofile=coverage.out ./...
1641
+ \`\`\`
1642
+
1643
+ ### Coverage targets
1644
+
1645
+ | Code Type | Target |
1646
+ |-----------|--------|
1647
+ | Critical business logic | 100% |
1648
+ | Public APIs | 90%+ |
1649
+ | General code | 80%+ |
1650
+ | Generated code | Exclude |
1651
+
1652
+ ### Excluding generated code from coverage
1653
+
1654
+ \`\`\`go
1655
+ //go:generate mockgen -source=interface.go -destination=mock_interface.go
1656
+
1657
+ // In coverage profile, exclude with build tags:
1658
+ // go test -cover -tags=!generate ./...
1659
+ \`\`\`
1660
+
1661
+ ## HTTP handler testing
1662
+
1663
+ \`\`\`go
1664
+ func TestHealthHandler(t *testing.T) {
1665
+ // Create request
1666
+ req := httptest.NewRequest(http.MethodGet, "/health", nil)
1667
+ w := httptest.NewRecorder()
1668
+
1669
+ // Call handler
1670
+ HealthHandler(w, req)
1671
+
1672
+ // Check response
1673
+ resp := w.Result()
1674
+ defer resp.Body.Close()
1675
+
1676
+ if resp.StatusCode != http.StatusOK {
1677
+ t.Errorf("got status %d; want %d", resp.StatusCode, http.StatusOK)
1678
+ }
1679
+
1680
+ body, _ := io.ReadAll(resp.Body)
1681
+ if string(body) != "OK" {
1682
+ t.Errorf("got body %q; want %q", body, "OK")
1683
+ }
1684
+ }
1685
+
1686
+ func TestAPIHandler(t *testing.T) {
1687
+ tests := []struct {
1688
+ name string
1689
+ method string
1690
+ path string
1691
+ body string
1692
+ wantStatus int
1693
+ wantBody string
1694
+ }{
1695
+ {
1696
+ name: "get user",
1697
+ method: http.MethodGet,
1698
+ path: "/users/123",
1699
+ wantStatus: http.StatusOK,
1700
+ wantBody: \`{"id":"123","name":"Alice"}\`,
1701
+ },
1702
+ {
1703
+ name: "not found",
1704
+ method: http.MethodGet,
1705
+ path: "/users/999",
1706
+ wantStatus: http.StatusNotFound,
1707
+ },
1708
+ {
1709
+ name: "create user",
1710
+ method: http.MethodPost,
1711
+ path: "/users",
1712
+ body: \`{"name":"Bob"}\`,
1713
+ wantStatus: http.StatusCreated,
1714
+ },
1715
+ }
1716
+
1717
+ handler := NewAPIHandler()
1718
+
1719
+ for _, tt := range tests {
1720
+ t.Run(tt.name, func(t *testing.T) {
1721
+ var body io.Reader
1722
+ if tt.body != "" {
1723
+ body = strings.NewReader(tt.body)
1724
+ }
1725
+
1726
+ req := httptest.NewRequest(tt.method, tt.path, body)
1727
+ req.Header.Set("Content-Type", "application/json")
1728
+ w := httptest.NewRecorder()
1729
+
1730
+ handler.ServeHTTP(w, req)
1731
+
1732
+ if w.Code != tt.wantStatus {
1733
+ t.Errorf("got status %d; want %d", w.Code, tt.wantStatus)
1734
+ }
1735
+
1736
+ if tt.wantBody != "" && w.Body.String() != tt.wantBody {
1737
+ t.Errorf("got body %q; want %q", w.Body.String(), tt.wantBody)
1738
+ }
1739
+ })
1740
+ }
1741
+ }
1742
+ \`\`\`
1743
+
1744
+ ## Testing commands
1745
+
1746
+ \`\`\`bash
1747
+ # Run all tests
1748
+ go test ./...
1749
+
1750
+ # Run tests with verbose output
1751
+ go test -v ./...
1752
+
1753
+ # Run specific test
1754
+ go test -run TestAdd ./...
1755
+
1756
+ # Run tests matching pattern
1757
+ go test -run "TestUser/Create" ./...
1758
+
1759
+ # Run tests with race detector
1760
+ go test -race ./...
1761
+
1762
+ # Run tests with coverage
1763
+ go test -cover -coverprofile=coverage.out ./...
1764
+
1765
+ # Run short tests only
1766
+ go test -short ./...
1767
+
1768
+ # Run tests with timeout
1769
+ go test -timeout 30s ./...
1770
+
1771
+ # Run benchmarks
1772
+ go test -bench=. -benchmem ./...
1773
+
1774
+ # Run fuzzing
1775
+ go test -fuzz=FuzzParse -fuzztime=30s ./...
1776
+
1777
+ # Count test runs (for flaky test detection)
1778
+ go test -count=10 ./...
1779
+ \`\`\`
1780
+
1781
+ ## Best practices
1782
+
1783
+ **DO:**
1784
+ - Write tests FIRST (TDD).
1785
+ - Use table-driven tests for comprehensive coverage.
1786
+ - Test behavior, not implementation.
1787
+ - Use \`t.Helper()\` in helper functions.
1788
+ - Use \`t.Parallel()\` for independent tests.
1789
+ - Clean up resources with \`t.Cleanup()\`.
1790
+ - Use meaningful test names that describe the scenario.
1791
+
1792
+ **DON'T:**
1793
+ - Test private functions directly (test through the public API).
1794
+ - Use \`time.Sleep()\` in tests (use channels or conditions).
1795
+ - Ignore flaky tests (fix or remove them).
1796
+ - Mock everything (prefer integration tests when possible).
1797
+ - Skip error path testing.
1798
+
1799
+ ## Integration with CI/CD
1800
+
1801
+ \`\`\`yaml
1802
+ # GitHub Actions example
1803
+ test:
1804
+ runs-on: ubuntu-latest
1805
+ steps:
1806
+ - uses: actions/checkout@v4
1807
+ - uses: actions/setup-go@v5
1808
+ with:
1809
+ go-version: '1.22'
1810
+
1811
+ - name: Run tests
1812
+ run: go test -race -coverprofile=coverage.out ./...
1813
+
1814
+ - name: Check coverage
1815
+ run: |
1816
+ go tool cover -func=coverage.out | grep total | awk '{print $3}' | \
1817
+ awk -F'%' '{if ($1 < 80) exit 1}'
1818
+ \`\`\`
1819
+
1820
+ **Remember**: Tests are documentation. They show how your code is meant to be used. Write them clearly and keep them up to date.