autoworkflow 3.1.5 → 3.6.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 (124) hide show
  1. package/.claude/commands/analyze.md +19 -0
  2. package/.claude/commands/audit.md +26 -0
  3. package/.claude/commands/build.md +39 -0
  4. package/.claude/commands/commit.md +25 -0
  5. package/.claude/commands/fix.md +23 -0
  6. package/.claude/commands/plan.md +18 -0
  7. package/.claude/commands/suggest.md +23 -0
  8. package/.claude/commands/verify.md +18 -0
  9. package/.claude/hooks/post-bash-router.sh +20 -0
  10. package/.claude/hooks/post-commit.sh +140 -0
  11. package/.claude/hooks/post-edit.sh +190 -17
  12. package/.claude/hooks/pre-edit.sh +221 -0
  13. package/.claude/hooks/session-check.sh +90 -0
  14. package/.claude/settings.json +56 -6
  15. package/.claude/settings.local.json +5 -1
  16. package/.claude/skills/actix.md +337 -0
  17. package/.claude/skills/alembic.md +504 -0
  18. package/.claude/skills/angular.md +237 -0
  19. package/.claude/skills/api-design.md +187 -0
  20. package/.claude/skills/aspnet-core.md +377 -0
  21. package/.claude/skills/astro.md +245 -0
  22. package/.claude/skills/auth-clerk.md +327 -0
  23. package/.claude/skills/auth-firebase.md +367 -0
  24. package/.claude/skills/auth-nextauth.md +359 -0
  25. package/.claude/skills/auth-supabase.md +368 -0
  26. package/.claude/skills/axum.md +386 -0
  27. package/.claude/skills/blazor.md +456 -0
  28. package/.claude/skills/chi.md +348 -0
  29. package/.claude/skills/code-review.md +133 -0
  30. package/.claude/skills/csharp.md +296 -0
  31. package/.claude/skills/css-modules.md +325 -0
  32. package/.claude/skills/cypress.md +343 -0
  33. package/.claude/skills/debugging.md +133 -0
  34. package/.claude/skills/diesel.md +392 -0
  35. package/.claude/skills/django.md +301 -0
  36. package/.claude/skills/docker.md +319 -0
  37. package/.claude/skills/doctrine.md +473 -0
  38. package/.claude/skills/documentation.md +182 -0
  39. package/.claude/skills/dotnet.md +409 -0
  40. package/.claude/skills/drizzle.md +293 -0
  41. package/.claude/skills/echo.md +321 -0
  42. package/.claude/skills/eloquent.md +256 -0
  43. package/.claude/skills/emotion.md +426 -0
  44. package/.claude/skills/entity-framework.md +370 -0
  45. package/.claude/skills/express.md +316 -0
  46. package/.claude/skills/fastapi.md +329 -0
  47. package/.claude/skills/fastify.md +299 -0
  48. package/.claude/skills/fiber.md +315 -0
  49. package/.claude/skills/flask.md +322 -0
  50. package/.claude/skills/gin.md +342 -0
  51. package/.claude/skills/git.md +116 -0
  52. package/.claude/skills/github-actions.md +353 -0
  53. package/.claude/skills/go.md +377 -0
  54. package/.claude/skills/gorm.md +409 -0
  55. package/.claude/skills/graphql.md +478 -0
  56. package/.claude/skills/hibernate.md +379 -0
  57. package/.claude/skills/hono.md +306 -0
  58. package/.claude/skills/java.md +400 -0
  59. package/.claude/skills/jest.md +313 -0
  60. package/.claude/skills/jpa.md +282 -0
  61. package/.claude/skills/kotlin.md +347 -0
  62. package/.claude/skills/kubernetes.md +363 -0
  63. package/.claude/skills/laravel.md +414 -0
  64. package/.claude/skills/mcp-browser.md +320 -0
  65. package/.claude/skills/mcp-database.md +219 -0
  66. package/.claude/skills/mcp-fetch.md +241 -0
  67. package/.claude/skills/mcp-filesystem.md +204 -0
  68. package/.claude/skills/mcp-github.md +217 -0
  69. package/.claude/skills/mcp-memory.md +240 -0
  70. package/.claude/skills/mcp-search.md +218 -0
  71. package/.claude/skills/mcp-slack.md +262 -0
  72. package/.claude/skills/micronaut.md +388 -0
  73. package/.claude/skills/mongodb.md +319 -0
  74. package/.claude/skills/mongoose.md +355 -0
  75. package/.claude/skills/mysql.md +281 -0
  76. package/.claude/skills/nestjs.md +335 -0
  77. package/.claude/skills/nextjs-app-router.md +260 -0
  78. package/.claude/skills/nextjs-pages.md +172 -0
  79. package/.claude/skills/nuxt.md +202 -0
  80. package/.claude/skills/openapi.md +489 -0
  81. package/.claude/skills/performance.md +199 -0
  82. package/.claude/skills/php.md +398 -0
  83. package/.claude/skills/playwright.md +371 -0
  84. package/.claude/skills/postgresql.md +257 -0
  85. package/.claude/skills/prisma.md +293 -0
  86. package/.claude/skills/pydantic.md +304 -0
  87. package/.claude/skills/pytest.md +313 -0
  88. package/.claude/skills/python.md +272 -0
  89. package/.claude/skills/quarkus.md +377 -0
  90. package/.claude/skills/react.md +230 -0
  91. package/.claude/skills/redis.md +391 -0
  92. package/.claude/skills/refactoring.md +143 -0
  93. package/.claude/skills/remix.md +246 -0
  94. package/.claude/skills/rest-api.md +490 -0
  95. package/.claude/skills/rocket.md +366 -0
  96. package/.claude/skills/rust.md +341 -0
  97. package/.claude/skills/sass.md +380 -0
  98. package/.claude/skills/sea-orm.md +382 -0
  99. package/.claude/skills/security.md +167 -0
  100. package/.claude/skills/sequelize.md +395 -0
  101. package/.claude/skills/spring-boot.md +416 -0
  102. package/.claude/skills/sqlalchemy.md +269 -0
  103. package/.claude/skills/sqlx-rust.md +408 -0
  104. package/.claude/skills/state-jotai.md +346 -0
  105. package/.claude/skills/state-mobx.md +353 -0
  106. package/.claude/skills/state-pinia.md +431 -0
  107. package/.claude/skills/state-redux.md +337 -0
  108. package/.claude/skills/state-tanstack-query.md +434 -0
  109. package/.claude/skills/state-zustand.md +340 -0
  110. package/.claude/skills/styled-components.md +403 -0
  111. package/.claude/skills/svelte.md +238 -0
  112. package/.claude/skills/sveltekit.md +207 -0
  113. package/.claude/skills/symfony.md +437 -0
  114. package/.claude/skills/tailwind.md +279 -0
  115. package/.claude/skills/terraform.md +394 -0
  116. package/.claude/skills/testing-library.md +371 -0
  117. package/.claude/skills/trpc.md +426 -0
  118. package/.claude/skills/typeorm.md +368 -0
  119. package/.claude/skills/vitest.md +330 -0
  120. package/.claude/skills/vue.md +202 -0
  121. package/.claude/skills/warp.md +365 -0
  122. package/README.md +163 -52
  123. package/package.json +1 -1
  124. package/system/triggers.md +256 -17
@@ -0,0 +1,353 @@
1
+ # GitHub Actions Skill
2
+
3
+ ## Complete CI/CD Workflow
4
+ \`\`\`yaml
5
+ name: CI/CD
6
+
7
+ on:
8
+ push:
9
+ branches: [main, develop]
10
+ pull_request:
11
+ branches: [main]
12
+
13
+ env:
14
+ NODE_VERSION: 20
15
+ REGISTRY: ghcr.io
16
+ IMAGE_NAME: \${{ github.repository }}
17
+
18
+ jobs:
19
+ lint:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - uses: actions/setup-node@v4
24
+ with:
25
+ node-version: \${{ env.NODE_VERSION }}
26
+ cache: 'npm'
27
+ - run: npm ci
28
+ - run: npm run lint
29
+ - run: npm run typecheck
30
+
31
+ test:
32
+ runs-on: ubuntu-latest
33
+ needs: lint
34
+ services:
35
+ postgres:
36
+ image: postgres:16
37
+ env:
38
+ POSTGRES_PASSWORD: postgres
39
+ POSTGRES_DB: test
40
+ ports:
41
+ - 5432:5432
42
+ options: >-
43
+ --health-cmd pg_isready
44
+ --health-interval 10s
45
+ --health-timeout 5s
46
+ --health-retries 5
47
+ steps:
48
+ - uses: actions/checkout@v4
49
+ - uses: actions/setup-node@v4
50
+ with:
51
+ node-version: \${{ env.NODE_VERSION }}
52
+ cache: 'npm'
53
+ - run: npm ci
54
+ - run: npm test -- --coverage
55
+ env:
56
+ DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
57
+ - uses: codecov/codecov-action@v4
58
+ with:
59
+ token: \${{ secrets.CODECOV_TOKEN }}
60
+
61
+ build:
62
+ runs-on: ubuntu-latest
63
+ needs: test
64
+ steps:
65
+ - uses: actions/checkout@v4
66
+ - uses: actions/setup-node@v4
67
+ with:
68
+ node-version: \${{ env.NODE_VERSION }}
69
+ cache: 'npm'
70
+ - run: npm ci
71
+ - run: npm run build
72
+ - uses: actions/upload-artifact@v4
73
+ with:
74
+ name: build
75
+ path: dist/
76
+ retention-days: 7
77
+
78
+ deploy:
79
+ runs-on: ubuntu-latest
80
+ needs: build
81
+ if: github.ref == 'refs/heads/main'
82
+ environment: production
83
+ steps:
84
+ - uses: actions/checkout@v4
85
+ - uses: actions/download-artifact@v4
86
+ with:
87
+ name: build
88
+ path: dist/
89
+ - name: Deploy to Vercel
90
+ uses: amondnet/vercel-action@v25
91
+ with:
92
+ vercel-token: \${{ secrets.VERCEL_TOKEN }}
93
+ vercel-org-id: \${{ secrets.VERCEL_ORG_ID }}
94
+ vercel-project-id: \${{ secrets.VERCEL_PROJECT_ID }}
95
+ vercel-args: '--prod'
96
+ \`\`\`
97
+
98
+ ## Matrix Testing
99
+ \`\`\`yaml
100
+ jobs:
101
+ test:
102
+ runs-on: \${{ matrix.os }}
103
+ strategy:
104
+ fail-fast: false
105
+ matrix:
106
+ os: [ubuntu-latest, macos-latest, windows-latest]
107
+ node-version: [18, 20, 22]
108
+ exclude:
109
+ - os: windows-latest
110
+ node-version: 18
111
+ steps:
112
+ - uses: actions/checkout@v4
113
+ - uses: actions/setup-node@v4
114
+ with:
115
+ node-version: \${{ matrix.node-version }}
116
+ cache: 'npm'
117
+ - run: npm ci
118
+ - run: npm test
119
+ \`\`\`
120
+
121
+ ## Caching Strategies
122
+ \`\`\`yaml
123
+ # npm cache (built into setup-node)
124
+ - uses: actions/setup-node@v4
125
+ with:
126
+ node-version: 20
127
+ cache: 'npm'
128
+
129
+ # pnpm cache
130
+ - uses: pnpm/action-setup@v3
131
+ with:
132
+ version: 9
133
+ - uses: actions/setup-node@v4
134
+ with:
135
+ node-version: 20
136
+ cache: 'pnpm'
137
+
138
+ # Custom caching
139
+ - uses: actions/cache@v4
140
+ with:
141
+ path: |
142
+ ~/.npm
143
+ node_modules
144
+ .next/cache
145
+ key: \${{ runner.os }}-node-\${{ hashFiles('**/package-lock.json') }}
146
+ restore-keys: |
147
+ \${{ runner.os }}-node-
148
+
149
+ # Turborepo remote cache
150
+ - name: Turbo Cache
151
+ uses: actions/cache@v4
152
+ with:
153
+ path: .turbo
154
+ key: \${{ runner.os }}-turbo-\${{ github.sha }}
155
+ restore-keys: |
156
+ \${{ runner.os }}-turbo-
157
+ \`\`\`
158
+
159
+ ## Docker Build & Push
160
+ \`\`\`yaml
161
+ jobs:
162
+ docker:
163
+ runs-on: ubuntu-latest
164
+ permissions:
165
+ contents: read
166
+ packages: write
167
+ steps:
168
+ - uses: actions/checkout@v4
169
+
170
+ - name: Set up Docker Buildx
171
+ uses: docker/setup-buildx-action@v3
172
+
173
+ - name: Login to Container Registry
174
+ uses: docker/login-action@v3
175
+ with:
176
+ registry: \${{ env.REGISTRY }}
177
+ username: \${{ github.actor }}
178
+ password: \${{ secrets.GITHUB_TOKEN }}
179
+
180
+ - name: Extract metadata
181
+ id: meta
182
+ uses: docker/metadata-action@v5
183
+ with:
184
+ images: \${{ env.REGISTRY }}/\${{ env.IMAGE_NAME }}
185
+ tags: |
186
+ type=sha
187
+ type=ref,event=branch
188
+ type=semver,pattern={{version}}
189
+
190
+ - name: Build and push
191
+ uses: docker/build-push-action@v5
192
+ with:
193
+ context: .
194
+ push: true
195
+ tags: \${{ steps.meta.outputs.tags }}
196
+ labels: \${{ steps.meta.outputs.labels }}
197
+ cache-from: type=gha
198
+ cache-to: type=gha,mode=max
199
+ \`\`\`
200
+
201
+ ## Reusable Workflow
202
+ \`\`\`yaml
203
+ # .github/workflows/reusable-test.yml
204
+ name: Reusable Test
205
+
206
+ on:
207
+ workflow_call:
208
+ inputs:
209
+ node-version:
210
+ required: false
211
+ type: string
212
+ default: '20'
213
+ secrets:
214
+ NPM_TOKEN:
215
+ required: false
216
+
217
+ jobs:
218
+ test:
219
+ runs-on: ubuntu-latest
220
+ steps:
221
+ - uses: actions/checkout@v4
222
+ - uses: actions/setup-node@v4
223
+ with:
224
+ node-version: \${{ inputs.node-version }}
225
+ - run: npm ci
226
+ - run: npm test
227
+
228
+ # Usage in another workflow
229
+ jobs:
230
+ call-tests:
231
+ uses: ./.github/workflows/reusable-test.yml
232
+ with:
233
+ node-version: '20'
234
+ secrets:
235
+ NPM_TOKEN: \${{ secrets.NPM_TOKEN }}
236
+ \`\`\`
237
+
238
+ ## Release Workflow
239
+ \`\`\`yaml
240
+ name: Release
241
+
242
+ on:
243
+ push:
244
+ tags:
245
+ - 'v*'
246
+
247
+ jobs:
248
+ release:
249
+ runs-on: ubuntu-latest
250
+ permissions:
251
+ contents: write
252
+ steps:
253
+ - uses: actions/checkout@v4
254
+ with:
255
+ fetch-depth: 0
256
+
257
+ - name: Generate changelog
258
+ id: changelog
259
+ uses: orhun/git-cliff-action@v3
260
+ with:
261
+ args: --latest
262
+
263
+ - name: Create Release
264
+ uses: softprops/action-gh-release@v2
265
+ with:
266
+ body: \${{ steps.changelog.outputs.content }}
267
+ draft: false
268
+ prerelease: \${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }}
269
+ \`\`\`
270
+
271
+ ## Scheduled Jobs
272
+ \`\`\`yaml
273
+ name: Scheduled Tasks
274
+
275
+ on:
276
+ schedule:
277
+ - cron: '0 0 * * *' # Daily at midnight UTC
278
+ workflow_dispatch: # Allow manual trigger
279
+
280
+ jobs:
281
+ dependency-check:
282
+ runs-on: ubuntu-latest
283
+ steps:
284
+ - uses: actions/checkout@v4
285
+ - run: npm audit --audit-level=high
286
+ - run: npx npm-check-updates -u
287
+ continue-on-error: true
288
+ \`\`\`
289
+
290
+ ## Environment & Secrets
291
+ \`\`\`yaml
292
+ jobs:
293
+ deploy:
294
+ runs-on: ubuntu-latest
295
+ environment:
296
+ name: production
297
+ url: https://myapp.com
298
+ steps:
299
+ - name: Deploy
300
+ env:
301
+ # Repository secrets
302
+ API_KEY: \${{ secrets.API_KEY }}
303
+ # Environment secrets
304
+ DATABASE_URL: \${{ secrets.DATABASE_URL }}
305
+ # GitHub token (auto-provided)
306
+ GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
307
+ run: ./deploy.sh
308
+ \`\`\`
309
+
310
+ ## Conditional Execution
311
+ \`\`\`yaml
312
+ jobs:
313
+ deploy:
314
+ if: github.ref == 'refs/heads/main'
315
+
316
+ deploy-preview:
317
+ if: github.event_name == 'pull_request'
318
+
319
+ skip-ci:
320
+ if: "!contains(github.event.head_commit.message, '[skip ci]')"
321
+
322
+ only-docs:
323
+ if: contains(github.event.head_commit.modified, 'docs/')
324
+ \`\`\`
325
+
326
+ ## Common Actions Reference
327
+ \`\`\`yaml
328
+ actions/checkout@v4 # Clone repository
329
+ actions/setup-node@v4 # Setup Node.js
330
+ actions/cache@v4 # Cache dependencies
331
+ actions/upload-artifact@v4 # Upload build artifacts
332
+ actions/download-artifact@v4
333
+ docker/build-push-action@v5
334
+ aws-actions/configure-aws-credentials@v4
335
+ azure/webapps-deploy@v3
336
+ google-github-actions/auth@v2
337
+ \`\`\`
338
+
339
+ ## ❌ DON'T
340
+ - Commit secrets to workflow files
341
+ - Use \`pull_request_target\` without understanding security
342
+ - Skip checkout before using repo files
343
+ - Ignore failing steps with \`continue-on-error\` carelessly
344
+ - Use deprecated action versions
345
+
346
+ ## ✅ DO
347
+ - Pin action versions (use SHA for critical actions)
348
+ - Use job dependencies with \`needs\`
349
+ - Cache dependencies for faster builds
350
+ - Use matrix for cross-platform/version testing
351
+ - Set appropriate permissions
352
+ - Use environments for deployments
353
+ - Add workflow_dispatch for manual runs
@@ -0,0 +1,377 @@
1
+ # Go Skill
2
+
3
+ ## Project Structure
4
+ \`\`\`
5
+ myapp/
6
+ ├── cmd/
7
+ │ └── api/
8
+ │ └── main.go # Entry point
9
+ ├── internal/
10
+ │ ├── config/ # Configuration
11
+ │ ├── handler/ # HTTP handlers
12
+ │ ├── middleware/ # HTTP middleware
13
+ │ ├── model/ # Domain models
14
+ │ ├── repository/ # Data access
15
+ │ └── service/ # Business logic
16
+ ├── pkg/ # Public packages
17
+ ├── go.mod
18
+ └── go.sum
19
+ \`\`\`
20
+
21
+ ## Structs and Methods
22
+ \`\`\`go
23
+ package model
24
+
25
+ import (
26
+ "time"
27
+ )
28
+
29
+ type User struct {
30
+ ID string \`json:"id"\`
31
+ Email string \`json:"email"\`
32
+ Name string \`json:"name"\`
33
+ CreatedAt time.Time \`json:"created_at"\`
34
+ }
35
+
36
+ // NewUser creates a User with generated ID and timestamp
37
+ func NewUser(email, name string) *User {
38
+ return &User{
39
+ ID: generateID(),
40
+ Email: email,
41
+ Name: name,
42
+ CreatedAt: time.Now(),
43
+ }
44
+ }
45
+
46
+ // Validate checks if user data is valid
47
+ func (u *User) Validate() error {
48
+ if u.Email == "" {
49
+ return errors.New("email is required")
50
+ }
51
+ if !strings.Contains(u.Email, "@") {
52
+ return errors.New("invalid email format")
53
+ }
54
+ return nil
55
+ }
56
+ \`\`\`
57
+
58
+ ## Interfaces and Dependency Injection
59
+ \`\`\`go
60
+ package repository
61
+
62
+ import "context"
63
+
64
+ // UserRepository defines the contract for user data access
65
+ type UserRepository interface {
66
+ GetByID(ctx context.Context, id string) (*model.User, error)
67
+ GetByEmail(ctx context.Context, email string) (*model.User, error)
68
+ Create(ctx context.Context, user *model.User) error
69
+ Update(ctx context.Context, user *model.User) error
70
+ Delete(ctx context.Context, id string) error
71
+ }
72
+
73
+ // PostgresUserRepository implements UserRepository with PostgreSQL
74
+ type PostgresUserRepository struct {
75
+ db *sql.DB
76
+ }
77
+
78
+ func NewPostgresUserRepository(db *sql.DB) *PostgresUserRepository {
79
+ return &PostgresUserRepository{db: db}
80
+ }
81
+
82
+ func (r *PostgresUserRepository) GetByID(ctx context.Context, id string) (*model.User, error) {
83
+ var user model.User
84
+ err := r.db.QueryRowContext(ctx,
85
+ "SELECT id, email, name, created_at FROM users WHERE id = $1",
86
+ id,
87
+ ).Scan(&user.ID, &user.Email, &user.Name, &user.CreatedAt)
88
+
89
+ if err == sql.ErrNoRows {
90
+ return nil, ErrNotFound
91
+ }
92
+ if err != nil {
93
+ return nil, fmt.Errorf("query user: %w", err)
94
+ }
95
+ return &user, nil
96
+ }
97
+ \`\`\`
98
+
99
+ ## Error Handling
100
+ \`\`\`go
101
+ package apperror
102
+
103
+ import (
104
+ "errors"
105
+ "fmt"
106
+ )
107
+
108
+ // Sentinel errors
109
+ var (
110
+ ErrNotFound = errors.New("not found")
111
+ ErrUnauthorized = errors.New("unauthorized")
112
+ ErrForbidden = errors.New("forbidden")
113
+ ErrConflict = errors.New("resource already exists")
114
+ )
115
+
116
+ // AppError wraps errors with additional context
117
+ type AppError struct {
118
+ Err error
119
+ Message string
120
+ Code string
121
+ }
122
+
123
+ func (e *AppError) Error() string {
124
+ return e.Message
125
+ }
126
+
127
+ func (e *AppError) Unwrap() error {
128
+ return e.Err
129
+ }
130
+
131
+ // Wrap creates an AppError with context
132
+ func Wrap(err error, message string) *AppError {
133
+ return &AppError{
134
+ Err: err,
135
+ Message: message,
136
+ }
137
+ }
138
+
139
+ // Usage with error wrapping
140
+ func GetUser(ctx context.Context, id string) (*User, error) {
141
+ user, err := repo.GetByID(ctx, id)
142
+ if err != nil {
143
+ if errors.Is(err, ErrNotFound) {
144
+ return nil, ErrNotFound // Propagate sentinel error
145
+ }
146
+ return nil, fmt.Errorf("get user %s: %w", id, err) // Wrap with context
147
+ }
148
+ return user, nil
149
+ }
150
+ \`\`\`
151
+
152
+ ## Context and Cancellation
153
+ \`\`\`go
154
+ package main
155
+
156
+ import (
157
+ "context"
158
+ "time"
159
+ )
160
+
161
+ func processRequest(ctx context.Context) error {
162
+ // Create timeout context
163
+ ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
164
+ defer cancel() // Always call cancel to release resources
165
+
166
+ // Pass context to operations
167
+ result, err := fetchData(ctx)
168
+ if err != nil {
169
+ return err
170
+ }
171
+
172
+ // Check for cancellation in long operations
173
+ select {
174
+ case <-ctx.Done():
175
+ return ctx.Err()
176
+ default:
177
+ // Continue processing
178
+ }
179
+
180
+ return process(ctx, result)
181
+ }
182
+
183
+ // Context with values (use sparingly, prefer explicit parameters)
184
+ type contextKey string
185
+
186
+ const userIDKey contextKey = "userID"
187
+
188
+ func WithUserID(ctx context.Context, userID string) context.Context {
189
+ return context.WithValue(ctx, userIDKey, userID)
190
+ }
191
+
192
+ func UserIDFromContext(ctx context.Context) (string, bool) {
193
+ userID, ok := ctx.Value(userIDKey).(string)
194
+ return userID, ok
195
+ }
196
+ \`\`\`
197
+
198
+ ## Goroutines and Channels
199
+ \`\`\`go
200
+ package main
201
+
202
+ import (
203
+ "context"
204
+ "sync"
205
+ )
206
+
207
+ // Worker pool pattern
208
+ func processItems(ctx context.Context, items []Item) error {
209
+ numWorkers := 5
210
+ jobs := make(chan Item, len(items))
211
+ results := make(chan Result, len(items))
212
+ errs := make(chan error, numWorkers)
213
+
214
+ // Start workers
215
+ var wg sync.WaitGroup
216
+ for i := 0; i < numWorkers; i++ {
217
+ wg.Add(1)
218
+ go func() {
219
+ defer wg.Done()
220
+ for item := range jobs {
221
+ select {
222
+ case <-ctx.Done():
223
+ return
224
+ default:
225
+ result, err := processItem(ctx, item)
226
+ if err != nil {
227
+ errs <- err
228
+ return
229
+ }
230
+ results <- result
231
+ }
232
+ }
233
+ }()
234
+ }
235
+
236
+ // Send jobs
237
+ for _, item := range items {
238
+ jobs <- item
239
+ }
240
+ close(jobs)
241
+
242
+ // Wait for completion
243
+ go func() {
244
+ wg.Wait()
245
+ close(results)
246
+ close(errs)
247
+ }()
248
+
249
+ // Collect results
250
+ var allResults []Result
251
+ for result := range results {
252
+ allResults = append(allResults, result)
253
+ }
254
+
255
+ // Check for errors
256
+ if err := <-errs; err != nil {
257
+ return err
258
+ }
259
+
260
+ return nil
261
+ }
262
+
263
+ // errgroup for simpler error handling
264
+ import "golang.org/x/sync/errgroup"
265
+
266
+ func fetchAll(ctx context.Context, urls []string) ([]Response, error) {
267
+ g, ctx := errgroup.WithContext(ctx)
268
+ responses := make([]Response, len(urls))
269
+
270
+ for i, url := range urls {
271
+ i, url := i, url // Capture loop variables
272
+ g.Go(func() error {
273
+ resp, err := fetch(ctx, url)
274
+ if err != nil {
275
+ return err
276
+ }
277
+ responses[i] = resp
278
+ return nil
279
+ })
280
+ }
281
+
282
+ if err := g.Wait(); err != nil {
283
+ return nil, err
284
+ }
285
+ return responses, nil
286
+ }
287
+ \`\`\`
288
+
289
+ ## Testing
290
+ \`\`\`go
291
+ package service_test
292
+
293
+ import (
294
+ "context"
295
+ "testing"
296
+ )
297
+
298
+ // Table-driven tests
299
+ func TestValidateEmail(t *testing.T) {
300
+ tests := []struct {
301
+ name string
302
+ email string
303
+ wantErr bool
304
+ }{
305
+ {"valid email", "user@example.com", false},
306
+ {"empty email", "", true},
307
+ {"missing @", "invalid-email", true},
308
+ {"missing domain", "user@", true},
309
+ }
310
+
311
+ for _, tt := range tests {
312
+ t.Run(tt.name, func(t *testing.T) {
313
+ err := ValidateEmail(tt.email)
314
+ if (err != nil) != tt.wantErr {
315
+ t.Errorf("ValidateEmail(%q) error = %v, wantErr %v", tt.email, err, tt.wantErr)
316
+ }
317
+ })
318
+ }
319
+ }
320
+
321
+ // Testing with mocks
322
+ type mockUserRepository struct {
323
+ users map[string]*model.User
324
+ }
325
+
326
+ func (m *mockUserRepository) GetByID(ctx context.Context, id string) (*model.User, error) {
327
+ user, ok := m.users[id]
328
+ if !ok {
329
+ return nil, ErrNotFound
330
+ }
331
+ return user, nil
332
+ }
333
+
334
+ func TestUserService_GetUser(t *testing.T) {
335
+ repo := &mockUserRepository{
336
+ users: map[string]*model.User{
337
+ "123": {ID: "123", Email: "test@example.com"},
338
+ },
339
+ }
340
+ svc := NewUserService(repo)
341
+
342
+ t.Run("existing user", func(t *testing.T) {
343
+ user, err := svc.GetUser(context.Background(), "123")
344
+ if err != nil {
345
+ t.Fatalf("unexpected error: %v", err)
346
+ }
347
+ if user.Email != "test@example.com" {
348
+ t.Errorf("got email %q, want %q", user.Email, "test@example.com")
349
+ }
350
+ })
351
+
352
+ t.Run("non-existing user", func(t *testing.T) {
353
+ _, err := svc.GetUser(context.Background(), "999")
354
+ if !errors.Is(err, ErrNotFound) {
355
+ t.Errorf("got error %v, want ErrNotFound", err)
356
+ }
357
+ })
358
+ }
359
+ \`\`\`
360
+
361
+ ## ✅ DO
362
+ - Handle all errors explicitly - never ignore with \`_\`
363
+ - Use \`context.Context\` as first parameter
364
+ - Wrap errors with \`fmt.Errorf("context: %w", err)\`
365
+ - Use interfaces for dependency injection
366
+ - Prefer composition over inheritance
367
+ - Use \`defer\` for cleanup (close files, unlock mutexes)
368
+ - Run \`go vet\` and \`golangci-lint\`
369
+ - Use table-driven tests
370
+
371
+ ## ❌ DON'T
372
+ - Don't use \`panic\` for error handling (only for programming errors)
373
+ - Don't pass context in structs, pass as function parameter
374
+ - Don't use \`context.Background()\` in request handlers (use request context)
375
+ - Don't ignore goroutine leaks - always ensure goroutines can exit
376
+ - Don't use naked returns in long functions
377
+ - Don't use \`init()\` for complex initialization