devflow-kit 1.1.0 → 1.2.0

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 (107) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +23 -6
  3. package/dist/plugins.js +67 -3
  4. package/package.json +2 -1
  5. package/plugins/devflow-accessibility/.claude-plugin/plugin.json +15 -0
  6. package/plugins/devflow-ambient/.claude-plugin/plugin.json +1 -1
  7. package/plugins/devflow-ambient/skills/ambient-router/SKILL.md +1 -1
  8. package/plugins/devflow-ambient/skills/ambient-router/references/skill-catalog.md +4 -0
  9. package/plugins/devflow-audit-claude/.claude-plugin/plugin.json +1 -1
  10. package/plugins/devflow-code-review/.claude-plugin/plugin.json +1 -4
  11. package/plugins/devflow-code-review/agents/reviewer.md +8 -0
  12. package/plugins/devflow-code-review/commands/code-review-teams.md +11 -1
  13. package/plugins/devflow-code-review/commands/code-review.md +12 -2
  14. package/plugins/devflow-core-skills/.claude-plugin/plugin.json +2 -6
  15. package/plugins/devflow-debug/.claude-plugin/plugin.json +1 -1
  16. package/plugins/devflow-frontend-design/.claude-plugin/plugin.json +15 -0
  17. package/plugins/devflow-go/.claude-plugin/plugin.json +15 -0
  18. package/plugins/devflow-go/skills/go/SKILL.md +187 -0
  19. package/plugins/devflow-go/skills/go/references/concurrency.md +312 -0
  20. package/plugins/devflow-go/skills/go/references/detection.md +129 -0
  21. package/plugins/devflow-go/skills/go/references/patterns.md +232 -0
  22. package/plugins/devflow-go/skills/go/references/violations.md +205 -0
  23. package/plugins/devflow-implement/.claude-plugin/plugin.json +1 -3
  24. package/plugins/devflow-implement/agents/coder.md +11 -6
  25. package/plugins/devflow-java/.claude-plugin/plugin.json +15 -0
  26. package/plugins/devflow-java/skills/java/SKILL.md +183 -0
  27. package/plugins/devflow-java/skills/java/references/detection.md +120 -0
  28. package/plugins/devflow-java/skills/java/references/modern-java.md +270 -0
  29. package/plugins/devflow-java/skills/java/references/patterns.md +235 -0
  30. package/plugins/devflow-java/skills/java/references/violations.md +213 -0
  31. package/plugins/devflow-python/.claude-plugin/plugin.json +15 -0
  32. package/plugins/devflow-python/skills/python/SKILL.md +188 -0
  33. package/plugins/devflow-python/skills/python/references/async.md +220 -0
  34. package/plugins/devflow-python/skills/python/references/detection.md +128 -0
  35. package/plugins/devflow-python/skills/python/references/patterns.md +226 -0
  36. package/plugins/devflow-python/skills/python/references/violations.md +204 -0
  37. package/plugins/devflow-react/.claude-plugin/plugin.json +15 -0
  38. package/plugins/{devflow-core-skills → devflow-react}/skills/react/SKILL.md +1 -1
  39. package/plugins/{devflow-core-skills → devflow-react}/skills/react/references/patterns.md +3 -3
  40. package/plugins/devflow-resolve/.claude-plugin/plugin.json +1 -1
  41. package/plugins/devflow-rust/.claude-plugin/plugin.json +15 -0
  42. package/plugins/devflow-rust/skills/rust/SKILL.md +193 -0
  43. package/plugins/devflow-rust/skills/rust/references/detection.md +131 -0
  44. package/plugins/devflow-rust/skills/rust/references/ownership.md +242 -0
  45. package/plugins/devflow-rust/skills/rust/references/patterns.md +210 -0
  46. package/plugins/devflow-rust/skills/rust/references/violations.md +191 -0
  47. package/plugins/devflow-self-review/.claude-plugin/plugin.json +1 -1
  48. package/plugins/devflow-specify/.claude-plugin/plugin.json +1 -1
  49. package/plugins/devflow-typescript/.claude-plugin/plugin.json +15 -0
  50. package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/references/patterns.md +3 -3
  51. package/shared/agents/coder.md +11 -6
  52. package/shared/agents/reviewer.md +8 -0
  53. package/shared/skills/ambient-router/SKILL.md +1 -1
  54. package/shared/skills/ambient-router/references/skill-catalog.md +4 -0
  55. package/shared/skills/go/SKILL.md +187 -0
  56. package/shared/skills/go/references/concurrency.md +312 -0
  57. package/shared/skills/go/references/detection.md +129 -0
  58. package/shared/skills/go/references/patterns.md +232 -0
  59. package/shared/skills/go/references/violations.md +205 -0
  60. package/shared/skills/java/SKILL.md +183 -0
  61. package/shared/skills/java/references/detection.md +120 -0
  62. package/shared/skills/java/references/modern-java.md +270 -0
  63. package/shared/skills/java/references/patterns.md +235 -0
  64. package/shared/skills/java/references/violations.md +213 -0
  65. package/shared/skills/python/SKILL.md +188 -0
  66. package/shared/skills/python/references/async.md +220 -0
  67. package/shared/skills/python/references/detection.md +128 -0
  68. package/shared/skills/python/references/patterns.md +226 -0
  69. package/shared/skills/python/references/violations.md +204 -0
  70. package/shared/skills/react/SKILL.md +1 -1
  71. package/shared/skills/react/references/patterns.md +3 -3
  72. package/shared/skills/rust/SKILL.md +193 -0
  73. package/shared/skills/rust/references/detection.md +131 -0
  74. package/shared/skills/rust/references/ownership.md +242 -0
  75. package/shared/skills/rust/references/patterns.md +210 -0
  76. package/shared/skills/rust/references/violations.md +191 -0
  77. package/shared/skills/typescript/references/patterns.md +3 -3
  78. package/plugins/devflow-code-review/skills/react/SKILL.md +0 -276
  79. package/plugins/devflow-code-review/skills/react/references/patterns.md +0 -1331
  80. package/plugins/devflow-core-skills/skills/accessibility/SKILL.md +0 -229
  81. package/plugins/devflow-core-skills/skills/accessibility/references/detection.md +0 -171
  82. package/plugins/devflow-core-skills/skills/accessibility/references/patterns.md +0 -670
  83. package/plugins/devflow-core-skills/skills/accessibility/references/violations.md +0 -419
  84. package/plugins/devflow-core-skills/skills/frontend-design/SKILL.md +0 -254
  85. package/plugins/devflow-core-skills/skills/frontend-design/references/detection.md +0 -184
  86. package/plugins/devflow-core-skills/skills/frontend-design/references/patterns.md +0 -511
  87. package/plugins/devflow-core-skills/skills/frontend-design/references/violations.md +0 -453
  88. package/plugins/devflow-core-skills/skills/react/references/violations.md +0 -565
  89. package/plugins/devflow-implement/skills/accessibility/SKILL.md +0 -229
  90. package/plugins/devflow-implement/skills/accessibility/references/detection.md +0 -171
  91. package/plugins/devflow-implement/skills/accessibility/references/patterns.md +0 -670
  92. package/plugins/devflow-implement/skills/accessibility/references/violations.md +0 -419
  93. package/plugins/devflow-implement/skills/frontend-design/SKILL.md +0 -254
  94. package/plugins/devflow-implement/skills/frontend-design/references/detection.md +0 -184
  95. package/plugins/devflow-implement/skills/frontend-design/references/patterns.md +0 -511
  96. package/plugins/devflow-implement/skills/frontend-design/references/violations.md +0 -453
  97. /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/SKILL.md +0 -0
  98. /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/detection.md +0 -0
  99. /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/patterns.md +0 -0
  100. /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/violations.md +0 -0
  101. /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/SKILL.md +0 -0
  102. /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/detection.md +0 -0
  103. /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/patterns.md +0 -0
  104. /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/violations.md +0 -0
  105. /package/plugins/{devflow-code-review → devflow-react}/skills/react/references/violations.md +0 -0
  106. /package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/SKILL.md +0 -0
  107. /package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/references/violations.md +0 -0
@@ -0,0 +1,312 @@
1
+ # Go Concurrency Patterns
2
+
3
+ Advanced concurrency patterns for Go. Reference from main SKILL.md.
4
+
5
+ ## errgroup for Structured Concurrency
6
+
7
+ ```go
8
+ import "golang.org/x/sync/errgroup"
9
+
10
+ func FetchAll(ctx context.Context, urls []string) ([]Response, error) {
11
+ g, ctx := errgroup.WithContext(ctx)
12
+ responses := make([]Response, len(urls))
13
+
14
+ for i, url := range urls {
15
+ g.Go(func() error {
16
+ resp, err := fetch(ctx, url)
17
+ if err != nil {
18
+ return fmt.Errorf("fetching %s: %w", url, err)
19
+ }
20
+ responses[i] = resp // Safe: each goroutine writes to unique index
21
+ return nil
22
+ })
23
+ }
24
+
25
+ if err := g.Wait(); err != nil {
26
+ return nil, err
27
+ }
28
+ return responses, nil
29
+ }
30
+ ```
31
+
32
+ ### errgroup with Concurrency Limit
33
+
34
+ ```go
35
+ func ProcessItems(ctx context.Context, items []Item) error {
36
+ g, ctx := errgroup.WithContext(ctx)
37
+ g.SetLimit(10) // Maximum 10 concurrent goroutines
38
+
39
+ for _, item := range items {
40
+ g.Go(func() error {
41
+ return processItem(ctx, item)
42
+ })
43
+ }
44
+
45
+ return g.Wait()
46
+ }
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Worker Pool
52
+
53
+ ```go
54
+ func WorkerPool(ctx context.Context, jobs <-chan Job, workers int) <-chan Result {
55
+ results := make(chan Result, workers)
56
+
57
+ var wg sync.WaitGroup
58
+ for range workers {
59
+ wg.Add(1)
60
+ go func() {
61
+ defer wg.Done()
62
+ for {
63
+ select {
64
+ case job, ok := <-jobs:
65
+ if !ok {
66
+ return
67
+ }
68
+ results <- process(ctx, job)
69
+ case <-ctx.Done():
70
+ return
71
+ }
72
+ }
73
+ }()
74
+ }
75
+
76
+ go func() {
77
+ wg.Wait()
78
+ close(results)
79
+ }()
80
+
81
+ return results
82
+ }
83
+
84
+ // Usage
85
+ jobs := make(chan Job, 100)
86
+ results := WorkerPool(ctx, jobs, 5)
87
+
88
+ // Send jobs
89
+ go func() {
90
+ defer close(jobs)
91
+ for _, j := range allJobs {
92
+ jobs <- j
93
+ }
94
+ }()
95
+
96
+ // Collect results
97
+ for r := range results {
98
+ fmt.Println(r)
99
+ }
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Fan-Out / Fan-In
105
+
106
+ ```go
107
+ // Fan-out: one source, multiple workers
108
+ func fanOut(ctx context.Context, input <-chan int, workers int) []<-chan int {
109
+ channels := make([]<-chan int, workers)
110
+ for i := range workers {
111
+ channels[i] = worker(ctx, input)
112
+ }
113
+ return channels
114
+ }
115
+
116
+ func worker(ctx context.Context, input <-chan int) <-chan int {
117
+ out := make(chan int)
118
+ go func() {
119
+ defer close(out)
120
+ for n := range input {
121
+ select {
122
+ case out <- n * n:
123
+ case <-ctx.Done():
124
+ return
125
+ }
126
+ }
127
+ }()
128
+ return out
129
+ }
130
+
131
+ // Fan-in: multiple sources, one destination
132
+ func fanIn(ctx context.Context, channels ...<-chan int) <-chan int {
133
+ merged := make(chan int)
134
+ var wg sync.WaitGroup
135
+
136
+ for _, ch := range channels {
137
+ wg.Add(1)
138
+ go func() {
139
+ defer wg.Done()
140
+ for val := range ch {
141
+ select {
142
+ case merged <- val:
143
+ case <-ctx.Done():
144
+ return
145
+ }
146
+ }
147
+ }()
148
+ }
149
+
150
+ go func() {
151
+ wg.Wait()
152
+ close(merged)
153
+ }()
154
+
155
+ return merged
156
+ }
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Select with Timeout
162
+
163
+ ```go
164
+ func fetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
165
+ ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
166
+ defer cancel()
167
+
168
+ ch := make(chan result, 1)
169
+ go func() {
170
+ data, err := doFetch(ctx, url)
171
+ ch <- result{data, err}
172
+ }()
173
+
174
+ select {
175
+ case r := <-ch:
176
+ return r.data, r.err
177
+ case <-ctx.Done():
178
+ return nil, fmt.Errorf("fetch %s: %w", url, ctx.Err())
179
+ }
180
+ }
181
+
182
+ type result struct {
183
+ data []byte
184
+ err error
185
+ }
186
+ ```
187
+
188
+ ### Select for Multiple Sources
189
+
190
+ ```go
191
+ func merge(ctx context.Context, primary, fallback <-chan Event) <-chan Event {
192
+ out := make(chan Event)
193
+ go func() {
194
+ defer close(out)
195
+ for {
196
+ select {
197
+ case e, ok := <-primary:
198
+ if !ok {
199
+ return
200
+ }
201
+ out <- e
202
+ case e, ok := <-fallback:
203
+ if !ok {
204
+ return
205
+ }
206
+ out <- e
207
+ case <-ctx.Done():
208
+ return
209
+ }
210
+ }
211
+ }()
212
+ return out
213
+ }
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Mutex vs Channels
219
+
220
+ ### Use Mutex When
221
+
222
+ ```go
223
+ // Protecting shared state with simple read/write
224
+ type SafeCounter struct {
225
+ mu sync.RWMutex
226
+ v map[string]int
227
+ }
228
+
229
+ func (c *SafeCounter) Inc(key string) {
230
+ c.mu.Lock()
231
+ defer c.mu.Unlock()
232
+ c.v[key]++
233
+ }
234
+
235
+ func (c *SafeCounter) Get(key string) int {
236
+ c.mu.RLock()
237
+ defer c.mu.RUnlock()
238
+ return c.v[key]
239
+ }
240
+ ```
241
+
242
+ ### Use Channels When
243
+
244
+ ```go
245
+ // Communicating between goroutines / coordinating work
246
+ func pipeline(ctx context.Context, input []int) <-chan int {
247
+ out := make(chan int)
248
+ go func() {
249
+ defer close(out)
250
+ for _, n := range input {
251
+ select {
252
+ case out <- transform(n):
253
+ case <-ctx.Done():
254
+ return
255
+ }
256
+ }
257
+ }()
258
+ return out
259
+ }
260
+ ```
261
+
262
+ ### Decision Guide
263
+
264
+ | Scenario | Use |
265
+ |----------|-----|
266
+ | Guarding shared state | `sync.Mutex` or `sync.RWMutex` |
267
+ | Passing ownership of data | Channels |
268
+ | Coordinating goroutine lifecycle | `context.Context` + channels |
269
+ | Waiting for N goroutines | `sync.WaitGroup` or `errgroup` |
270
+ | One-time initialization | `sync.Once` |
271
+ | Concurrent map access | `sync.Map` (high read, low write) |
272
+
273
+ ---
274
+
275
+ ## sync.Once for Initialization
276
+
277
+ ```go
278
+ type Client struct {
279
+ once sync.Once
280
+ conn *grpc.ClientConn
281
+ err error
282
+ }
283
+
284
+ func (c *Client) connection() (*grpc.ClientConn, error) {
285
+ c.once.Do(func() {
286
+ // requires: "google.golang.org/grpc/credentials/insecure"
287
+ c.conn, c.err = grpc.Dial("localhost:50051",
288
+ grpc.WithTransportCredentials(insecure.NewCredentials()))
289
+ })
290
+ return c.conn, c.err
291
+ }
292
+ ```
293
+
294
+ ---
295
+
296
+ ## Rate Limiting
297
+
298
+ ```go
299
+ func rateLimited(ctx context.Context, items []Item, rps int) error {
300
+ limiter := rate.NewLimiter(rate.Limit(rps), 1)
301
+
302
+ for _, item := range items {
303
+ if err := limiter.Wait(ctx); err != nil {
304
+ return fmt.Errorf("rate limiter: %w", err)
305
+ }
306
+ if err := process(ctx, item); err != nil {
307
+ return fmt.Errorf("processing item %s: %w", item.ID, err)
308
+ }
309
+ }
310
+ return nil
311
+ }
312
+ ```
@@ -0,0 +1,129 @@
1
+ # Go Issue Detection
2
+
3
+ Grep and regex patterns for finding common Go issues. Reference from main SKILL.md.
4
+
5
+ ## Error Handling Detection
6
+
7
+ ### Unchecked Errors
8
+
9
+ ```bash
10
+ # Find ignored error returns (blank identifier)
11
+ grep -rn ', _ := ' --include='*.go' .
12
+ grep -rn ', _ = ' --include='*.go' .
13
+
14
+ # Find bare error returns without wrapping
15
+ grep -rn 'return.*err$' --include='*.go' . | grep -v 'fmt.Errorf' | grep -v ':= err'
16
+
17
+ # Find deferred calls that return errors (Close, Flush, Sync)
18
+ grep -rn 'defer.*\.Close()' --include='*.go' .
19
+ grep -rn 'defer.*\.Flush()' --include='*.go' .
20
+ ```
21
+
22
+ ### Missing Error Context
23
+
24
+ ```bash
25
+ # Find raw error returns (no wrapping)
26
+ grep -rn 'return nil, err$' --include='*.go' .
27
+ grep -rn 'return err$' --include='*.go' .
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Concurrency Detection
33
+
34
+ ### Goroutine Without Context
35
+
36
+ ```bash
37
+ # Find goroutines that don't reference ctx
38
+ grep -rn 'go func()' --include='*.go' .
39
+
40
+ # Find goroutines without done/cancel/ctx
41
+ grep -B5 -A10 'go func' --include='*.go' . | grep -L 'ctx\|done\|cancel\|errgroup'
42
+ ```
43
+
44
+ ### Potential Goroutine Leaks
45
+
46
+ ```bash
47
+ # Find unbuffered channel creation followed by goroutine
48
+ grep -rn 'make(chan ' --include='*.go' . | grep -v ', [0-9]'
49
+
50
+ # Find goroutines with infinite loops
51
+ grep -A5 'go func' --include='*.go' . | grep 'for {'
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Interface Detection
57
+
58
+ ### Empty Interface Usage
59
+
60
+ ```bash
61
+ # Find empty interface (pre-1.18 style)
62
+ grep -rn 'interface{}' --include='*.go' .
63
+
64
+ # Find any type (1.18+ style) - may be intentional, review context
65
+ grep -rn '\bany\b' --include='*.go' . | grep -v '_test.go' | grep -v 'vendor/'
66
+ ```
67
+
68
+ ### Large Interfaces
69
+
70
+ ```bash
71
+ # Find interface declarations and count methods (interfaces > 5 methods)
72
+ grep -B1 -A20 'type.*interface {' --include='*.go' .
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Panic Detection
78
+
79
+ ### Panic in Library Code
80
+
81
+ ```bash
82
+ # Find panic calls outside main/test
83
+ grep -rn 'panic(' --include='*.go' . | grep -v '_test.go' | grep -v 'main.go'
84
+
85
+ # Find log.Fatal (calls os.Exit) in library code
86
+ grep -rn 'log.Fatal' --include='*.go' . | grep -v 'main.go' | grep -v '_test.go'
87
+
88
+ # Find os.Exit in library code
89
+ grep -rn 'os.Exit' --include='*.go' . | grep -v 'main.go' | grep -v '_test.go'
90
+ ```
91
+
92
+ ---
93
+
94
+ ## init() Detection
95
+
96
+ ```bash
97
+ # Find all init functions
98
+ grep -rn '^func init()' --include='*.go' .
99
+
100
+ # Find init functions with side effects (network, file, global state)
101
+ grep -A10 '^func init()' --include='*.go' . | grep -E 'http\.|sql\.|os\.|Open|Dial|Connect'
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Mutex and Race Detection
107
+
108
+ ```bash
109
+ # Find mutex passed by value (function params with Mutex, not pointer)
110
+ grep -rn 'func.*sync\.Mutex[^*]' --include='*.go' .
111
+
112
+ # Find Lock without corresponding Unlock
113
+ grep -rn '\.Lock()' --include='*.go' . | grep -v 'defer.*Unlock'
114
+
115
+ # Find RLock without RUnlock
116
+ grep -rn '\.RLock()' --include='*.go' . | grep -v 'defer.*RUnlock'
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Map and Slice Safety
122
+
123
+ ```bash
124
+ # Find uninitialized map declarations (potential nil map panic)
125
+ grep -rn 'var.*map\[' --include='*.go' . | grep -v 'make\|:='
126
+
127
+ # Find slice append without pre-allocation hint
128
+ grep -rn 'append(' --include='*.go' . | head -20
129
+ ```
@@ -0,0 +1,232 @@
1
+ # Go Correct Patterns
2
+
3
+ Extended correct patterns for Go development. Reference from main SKILL.md.
4
+
5
+ ## Table-Driven Tests
6
+
7
+ ```go
8
+ func TestAdd(t *testing.T) {
9
+ tests := []struct {
10
+ name string
11
+ a, b int
12
+ expected int
13
+ }{
14
+ {"positive numbers", 2, 3, 5},
15
+ {"negative numbers", -1, -2, -3},
16
+ {"zero", 0, 0, 0},
17
+ {"mixed signs", -1, 3, 2},
18
+ }
19
+
20
+ for _, tt := range tests {
21
+ t.Run(tt.name, func(t *testing.T) {
22
+ result := Add(tt.a, tt.b)
23
+ if result != tt.expected {
24
+ t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, result, tt.expected)
25
+ }
26
+ })
27
+ }
28
+ }
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Functional Options
34
+
35
+ ```go
36
+ type Server struct {
37
+ addr string
38
+ timeout time.Duration
39
+ logger *slog.Logger
40
+ }
41
+
42
+ type Option func(*Server)
43
+
44
+ func WithAddr(addr string) Option {
45
+ return func(s *Server) { s.addr = addr }
46
+ }
47
+
48
+ func WithTimeout(d time.Duration) Option {
49
+ return func(s *Server) { s.timeout = d }
50
+ }
51
+
52
+ func WithLogger(l *slog.Logger) Option {
53
+ return func(s *Server) { s.logger = l }
54
+ }
55
+
56
+ func NewServer(opts ...Option) *Server {
57
+ s := &Server{
58
+ addr: ":8080", // sensible default
59
+ timeout: 30 * time.Second, // sensible default
60
+ logger: slog.Default(),
61
+ }
62
+ for _, opt := range opts {
63
+ opt(s)
64
+ }
65
+ return s
66
+ }
67
+
68
+ // Usage
69
+ srv := NewServer(
70
+ WithAddr(":9090"),
71
+ WithTimeout(60 * time.Second),
72
+ )
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Custom Error Types
78
+
79
+ ```go
80
+ type ValidationError struct {
81
+ Field string
82
+ Message string
83
+ }
84
+
85
+ func (e *ValidationError) Error() string {
86
+ return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
87
+ }
88
+
89
+ type NotFoundError struct {
90
+ Resource string
91
+ ID string
92
+ }
93
+
94
+ func (e *NotFoundError) Error() string {
95
+ return fmt.Sprintf("%s %s not found", e.Resource, e.ID)
96
+ }
97
+
98
+ // Usage with errors.As
99
+ func handleErr(err error) {
100
+ var validErr *ValidationError
101
+ if errors.As(err, &validErr) {
102
+ log.Printf("bad input: field=%s msg=%s", validErr.Field, validErr.Message)
103
+ return
104
+ }
105
+ var notFound *NotFoundError
106
+ if errors.As(err, &notFound) {
107
+ log.Printf("missing: %s/%s", notFound.Resource, notFound.ID)
108
+ return
109
+ }
110
+ log.Printf("unexpected: %v", err)
111
+ }
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Middleware Pattern
117
+
118
+ ```go
119
+ type Middleware func(http.Handler) http.Handler
120
+
121
+ func Logging(logger *slog.Logger) Middleware {
122
+ return func(next http.Handler) http.Handler {
123
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
124
+ start := time.Now()
125
+ next.ServeHTTP(w, r)
126
+ logger.Info("request",
127
+ "method", r.Method,
128
+ "path", r.URL.Path,
129
+ "duration", time.Since(start),
130
+ )
131
+ })
132
+ }
133
+ }
134
+
135
+ func Recovery() Middleware {
136
+ return func(next http.Handler) http.Handler {
137
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
138
+ defer func() {
139
+ if err := recover(); err != nil {
140
+ w.WriteHeader(http.StatusInternalServerError)
141
+ slog.Error("panic recovered", "error", err)
142
+ }
143
+ }()
144
+ next.ServeHTTP(w, r)
145
+ })
146
+ }
147
+ }
148
+
149
+ // Chain composes middleware in order
150
+ func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
151
+ for i := len(middlewares) - 1; i >= 0; i-- {
152
+ handler = middlewares[i](handler)
153
+ }
154
+ return handler
155
+ }
156
+
157
+ // Usage
158
+ mux := http.NewServeMux()
159
+ mux.HandleFunc("/api/users", handleUsers)
160
+ handler := Chain(mux, Recovery(), Logging(logger))
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Graceful Shutdown
166
+
167
+ ```go
168
+ func main() {
169
+ srv := &http.Server{Addr: ":8080", Handler: mux}
170
+
171
+ go func() {
172
+ if err := srv.ListenAndServe(); err != http.ErrServerClosed {
173
+ slog.Error("server error", "error", err)
174
+ }
175
+ }()
176
+
177
+ quit := make(chan os.Signal, 1)
178
+ signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
179
+ <-quit
180
+
181
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
182
+ defer cancel()
183
+
184
+ if err := srv.Shutdown(ctx); err != nil {
185
+ slog.Error("shutdown error", "error", err)
186
+ }
187
+ slog.Info("server stopped")
188
+ }
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Constructor Pattern
194
+
195
+ ```go
196
+ // Validate at construction - enforce invariants
197
+ func NewUser(name, email string) (*User, error) {
198
+ if name == "" {
199
+ return nil, &ValidationError{Field: "name", Message: "required"}
200
+ }
201
+ if !strings.Contains(email, "@") {
202
+ return nil, &ValidationError{Field: "email", Message: "invalid format"}
203
+ }
204
+ return &User{
205
+ ID: uuid.New().String(),
206
+ Name: name,
207
+ Email: email,
208
+ CreatedAt: time.Now(),
209
+ }, nil
210
+ }
211
+ ```
212
+
213
+ ---
214
+
215
+ ## Structured Logging with slog
216
+
217
+ ```go
218
+ func ProcessOrder(ctx context.Context, orderID string) error {
219
+ logger := slog.With("order_id", orderID, "trace_id", traceID(ctx))
220
+
221
+ logger.Info("processing order")
222
+
223
+ items, err := fetchItems(ctx, orderID)
224
+ if err != nil {
225
+ logger.Error("failed to fetch items", "error", err)
226
+ return fmt.Errorf("fetching items for order %s: %w", orderID, err)
227
+ }
228
+
229
+ logger.Info("items fetched", "count", len(items))
230
+ return nil
231
+ }
232
+ ```