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.
- package/CHANGELOG.md +39 -0
- package/README.md +23 -6
- package/dist/plugins.js +67 -3
- package/package.json +2 -1
- package/plugins/devflow-accessibility/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-ambient/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-ambient/skills/ambient-router/SKILL.md +1 -1
- package/plugins/devflow-ambient/skills/ambient-router/references/skill-catalog.md +4 -0
- package/plugins/devflow-audit-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-code-review/.claude-plugin/plugin.json +1 -4
- package/plugins/devflow-code-review/agents/reviewer.md +8 -0
- package/plugins/devflow-code-review/commands/code-review-teams.md +11 -1
- package/plugins/devflow-code-review/commands/code-review.md +12 -2
- package/plugins/devflow-core-skills/.claude-plugin/plugin.json +2 -6
- package/plugins/devflow-debug/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-frontend-design/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-go/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-go/skills/go/SKILL.md +187 -0
- package/plugins/devflow-go/skills/go/references/concurrency.md +312 -0
- package/plugins/devflow-go/skills/go/references/detection.md +129 -0
- package/plugins/devflow-go/skills/go/references/patterns.md +232 -0
- package/plugins/devflow-go/skills/go/references/violations.md +205 -0
- package/plugins/devflow-implement/.claude-plugin/plugin.json +1 -3
- package/plugins/devflow-implement/agents/coder.md +11 -6
- package/plugins/devflow-java/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-java/skills/java/SKILL.md +183 -0
- package/plugins/devflow-java/skills/java/references/detection.md +120 -0
- package/plugins/devflow-java/skills/java/references/modern-java.md +270 -0
- package/plugins/devflow-java/skills/java/references/patterns.md +235 -0
- package/plugins/devflow-java/skills/java/references/violations.md +213 -0
- package/plugins/devflow-python/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-python/skills/python/SKILL.md +188 -0
- package/plugins/devflow-python/skills/python/references/async.md +220 -0
- package/plugins/devflow-python/skills/python/references/detection.md +128 -0
- package/plugins/devflow-python/skills/python/references/patterns.md +226 -0
- package/plugins/devflow-python/skills/python/references/violations.md +204 -0
- package/plugins/devflow-react/.claude-plugin/plugin.json +15 -0
- package/plugins/{devflow-core-skills → devflow-react}/skills/react/SKILL.md +1 -1
- package/plugins/{devflow-core-skills → devflow-react}/skills/react/references/patterns.md +3 -3
- package/plugins/devflow-resolve/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-rust/.claude-plugin/plugin.json +15 -0
- package/plugins/devflow-rust/skills/rust/SKILL.md +193 -0
- package/plugins/devflow-rust/skills/rust/references/detection.md +131 -0
- package/plugins/devflow-rust/skills/rust/references/ownership.md +242 -0
- package/plugins/devflow-rust/skills/rust/references/patterns.md +210 -0
- package/plugins/devflow-rust/skills/rust/references/violations.md +191 -0
- package/plugins/devflow-self-review/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-specify/.claude-plugin/plugin.json +1 -1
- package/plugins/devflow-typescript/.claude-plugin/plugin.json +15 -0
- package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/references/patterns.md +3 -3
- package/shared/agents/coder.md +11 -6
- package/shared/agents/reviewer.md +8 -0
- package/shared/skills/ambient-router/SKILL.md +1 -1
- package/shared/skills/ambient-router/references/skill-catalog.md +4 -0
- package/shared/skills/go/SKILL.md +187 -0
- package/shared/skills/go/references/concurrency.md +312 -0
- package/shared/skills/go/references/detection.md +129 -0
- package/shared/skills/go/references/patterns.md +232 -0
- package/shared/skills/go/references/violations.md +205 -0
- package/shared/skills/java/SKILL.md +183 -0
- package/shared/skills/java/references/detection.md +120 -0
- package/shared/skills/java/references/modern-java.md +270 -0
- package/shared/skills/java/references/patterns.md +235 -0
- package/shared/skills/java/references/violations.md +213 -0
- package/shared/skills/python/SKILL.md +188 -0
- package/shared/skills/python/references/async.md +220 -0
- package/shared/skills/python/references/detection.md +128 -0
- package/shared/skills/python/references/patterns.md +226 -0
- package/shared/skills/python/references/violations.md +204 -0
- package/shared/skills/react/SKILL.md +1 -1
- package/shared/skills/react/references/patterns.md +3 -3
- package/shared/skills/rust/SKILL.md +193 -0
- package/shared/skills/rust/references/detection.md +131 -0
- package/shared/skills/rust/references/ownership.md +242 -0
- package/shared/skills/rust/references/patterns.md +210 -0
- package/shared/skills/rust/references/violations.md +191 -0
- package/shared/skills/typescript/references/patterns.md +3 -3
- package/plugins/devflow-code-review/skills/react/SKILL.md +0 -276
- package/plugins/devflow-code-review/skills/react/references/patterns.md +0 -1331
- package/plugins/devflow-core-skills/skills/accessibility/SKILL.md +0 -229
- package/plugins/devflow-core-skills/skills/accessibility/references/detection.md +0 -171
- package/plugins/devflow-core-skills/skills/accessibility/references/patterns.md +0 -670
- package/plugins/devflow-core-skills/skills/accessibility/references/violations.md +0 -419
- package/plugins/devflow-core-skills/skills/frontend-design/SKILL.md +0 -254
- package/plugins/devflow-core-skills/skills/frontend-design/references/detection.md +0 -184
- package/plugins/devflow-core-skills/skills/frontend-design/references/patterns.md +0 -511
- package/plugins/devflow-core-skills/skills/frontend-design/references/violations.md +0 -453
- package/plugins/devflow-core-skills/skills/react/references/violations.md +0 -565
- package/plugins/devflow-implement/skills/accessibility/SKILL.md +0 -229
- package/plugins/devflow-implement/skills/accessibility/references/detection.md +0 -171
- package/plugins/devflow-implement/skills/accessibility/references/patterns.md +0 -670
- package/plugins/devflow-implement/skills/accessibility/references/violations.md +0 -419
- package/plugins/devflow-implement/skills/frontend-design/SKILL.md +0 -254
- package/plugins/devflow-implement/skills/frontend-design/references/detection.md +0 -184
- package/plugins/devflow-implement/skills/frontend-design/references/patterns.md +0 -511
- package/plugins/devflow-implement/skills/frontend-design/references/violations.md +0 -453
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/SKILL.md +0 -0
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/detection.md +0 -0
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/patterns.md +0 -0
- /package/plugins/{devflow-code-review → devflow-accessibility}/skills/accessibility/references/violations.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/SKILL.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/detection.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/patterns.md +0 -0
- /package/plugins/{devflow-code-review → devflow-frontend-design}/skills/frontend-design/references/violations.md +0 -0
- /package/plugins/{devflow-code-review → devflow-react}/skills/react/references/violations.md +0 -0
- /package/plugins/{devflow-core-skills → devflow-typescript}/skills/typescript/SKILL.md +0 -0
- /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, ¬Found) {
|
|
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
|
+
```
|