@corbat-tech/coding-standards-mcp 1.0.3 → 2.0.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 (99) hide show
  1. package/README.md +233 -337
  2. package/dist/agent.d.ts +5 -6
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +95 -217
  5. package/dist/agent.js.map +1 -1
  6. package/dist/analysis/code-analyzer.d.ts +44 -0
  7. package/dist/analysis/code-analyzer.d.ts.map +1 -0
  8. package/dist/analysis/code-analyzer.js +528 -0
  9. package/dist/analysis/code-analyzer.js.map +1 -0
  10. package/dist/errors.d.ts +58 -0
  11. package/dist/errors.d.ts.map +1 -0
  12. package/dist/errors.js +112 -0
  13. package/dist/errors.js.map +1 -0
  14. package/dist/guardrails.d.ts +35 -0
  15. package/dist/guardrails.d.ts.map +1 -0
  16. package/dist/guardrails.js +303 -0
  17. package/dist/guardrails.js.map +1 -0
  18. package/dist/index.js +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/logger.d.ts +36 -0
  21. package/dist/logger.d.ts.map +1 -0
  22. package/dist/logger.js +63 -0
  23. package/dist/logger.js.map +1 -0
  24. package/dist/metrics.d.ts +40 -0
  25. package/dist/metrics.d.ts.map +1 -0
  26. package/dist/metrics.js +97 -0
  27. package/dist/metrics.js.map +1 -0
  28. package/dist/profiles.d.ts +1 -1
  29. package/dist/profiles.d.ts.map +1 -1
  30. package/dist/profiles.js +239 -108
  31. package/dist/profiles.js.map +1 -1
  32. package/dist/prompts.js +1 -1
  33. package/dist/prompts.js.map +1 -1
  34. package/dist/tools/definitions.d.ts +143 -0
  35. package/dist/tools/definitions.d.ts.map +1 -0
  36. package/dist/tools/definitions.js +229 -0
  37. package/dist/tools/definitions.js.map +1 -0
  38. package/dist/tools/handlers/get-context.d.ts +12 -0
  39. package/dist/tools/handlers/get-context.d.ts.map +1 -0
  40. package/dist/tools/handlers/get-context.js +233 -0
  41. package/dist/tools/handlers/get-context.js.map +1 -0
  42. package/dist/tools/handlers/health.d.ts +11 -0
  43. package/dist/tools/handlers/health.d.ts.map +1 -0
  44. package/dist/tools/handlers/health.js +57 -0
  45. package/dist/tools/handlers/health.js.map +1 -0
  46. package/dist/tools/handlers/index.d.ts +12 -0
  47. package/dist/tools/handlers/index.d.ts.map +1 -0
  48. package/dist/tools/handlers/index.js +12 -0
  49. package/dist/tools/handlers/index.js.map +1 -0
  50. package/dist/tools/handlers/init.d.ts +12 -0
  51. package/dist/tools/handlers/init.d.ts.map +1 -0
  52. package/dist/tools/handlers/init.js +102 -0
  53. package/dist/tools/handlers/init.js.map +1 -0
  54. package/dist/tools/handlers/profiles.d.ts +11 -0
  55. package/dist/tools/handlers/profiles.d.ts.map +1 -0
  56. package/dist/tools/handlers/profiles.js +25 -0
  57. package/dist/tools/handlers/profiles.js.map +1 -0
  58. package/dist/tools/handlers/search.d.ts +12 -0
  59. package/dist/tools/handlers/search.d.ts.map +1 -0
  60. package/dist/tools/handlers/search.js +58 -0
  61. package/dist/tools/handlers/search.js.map +1 -0
  62. package/dist/tools/handlers/validate.d.ts +15 -0
  63. package/dist/tools/handlers/validate.d.ts.map +1 -0
  64. package/dist/tools/handlers/validate.js +71 -0
  65. package/dist/tools/handlers/validate.js.map +1 -0
  66. package/dist/tools/handlers/verify.d.ts +38 -0
  67. package/dist/tools/handlers/verify.d.ts.map +1 -0
  68. package/dist/tools/handlers/verify.js +172 -0
  69. package/dist/tools/handlers/verify.js.map +1 -0
  70. package/dist/tools/index.d.ts +22 -0
  71. package/dist/tools/index.d.ts.map +1 -0
  72. package/dist/tools/index.js +75 -0
  73. package/dist/tools/index.js.map +1 -0
  74. package/dist/tools/schemas.d.ts +29 -0
  75. package/dist/tools/schemas.d.ts.map +1 -0
  76. package/dist/tools/schemas.js +20 -0
  77. package/dist/tools/schemas.js.map +1 -0
  78. package/dist/tools.js +2 -2
  79. package/dist/tools.js.map +1 -1
  80. package/dist/types.d.ts +141 -71
  81. package/dist/types.d.ts.map +1 -1
  82. package/dist/types.js +92 -40
  83. package/dist/types.js.map +1 -1
  84. package/package.json +2 -2
  85. package/profiles/examples/microservice-kafka.yaml +122 -0
  86. package/profiles/examples/startup-fast.yaml +67 -0
  87. package/profiles/examples/strict-enterprise.yaml +62 -0
  88. package/profiles/templates/angular.yaml +614 -0
  89. package/profiles/templates/csharp-dotnet.yaml +529 -0
  90. package/profiles/templates/flutter.yaml +547 -0
  91. package/profiles/templates/go.yaml +1276 -0
  92. package/profiles/templates/java-spring-backend.yaml +326 -0
  93. package/profiles/templates/kotlin-spring.yaml +417 -0
  94. package/profiles/templates/nextjs.yaml +536 -0
  95. package/profiles/templates/nodejs.yaml +594 -0
  96. package/profiles/templates/python.yaml +546 -0
  97. package/profiles/templates/react.yaml +456 -0
  98. package/profiles/templates/rust.yaml +508 -0
  99. package/profiles/templates/vue.yaml +483 -0
@@ -0,0 +1,1276 @@
1
+ # ============================================================================
2
+ # CORBAT MCP - Go Profile
3
+ # ============================================================================
4
+ # Production-ready standards for Go backend applications and microservices.
5
+ # Based on Effective Go, Go Code Review Comments, and idiomatic Go patterns.
6
+ # ============================================================================
7
+
8
+ name: "Go Backend Standards"
9
+ description: "Production-ready standards for Go microservices with idiomatic patterns, clean architecture, and comprehensive testing"
10
+
11
+ # ----------------------------------------------------------------------------
12
+ # ARCHITECTURE
13
+ # ----------------------------------------------------------------------------
14
+ architecture:
15
+ type: clean
16
+ enforceLayerDependencies: true
17
+ layers:
18
+ - name: domain
19
+ description: "Core business logic. Pure Go with no external dependencies. Contains entities, value objects, and domain interfaces."
20
+ allowedDependencies: []
21
+ packages:
22
+ - "internal/domain"
23
+ - "internal/domain/entity"
24
+ - "internal/domain/valueobject"
25
+ - "internal/domain/repository"
26
+ - "internal/domain/service"
27
+
28
+ - name: usecase
29
+ description: "Application use cases. Orchestrates domain objects. Contains business workflows and application services."
30
+ allowedDependencies:
31
+ - domain
32
+ packages:
33
+ - "internal/usecase"
34
+ - "internal/application"
35
+
36
+ - name: interface
37
+ description: "Delivery mechanisms: HTTP handlers, gRPC servers, CLI commands. Adapts external input to use cases."
38
+ allowedDependencies:
39
+ - domain
40
+ - usecase
41
+ packages:
42
+ - "internal/handler"
43
+ - "internal/transport"
44
+ - "internal/api"
45
+ - "cmd"
46
+
47
+ - name: infrastructure
48
+ description: "External adapters: database repositories, external APIs, messaging. Implements domain interfaces."
49
+ allowedDependencies:
50
+ - domain
51
+ - usecase
52
+ packages:
53
+ - "internal/infrastructure"
54
+ - "internal/repository"
55
+ - "internal/gateway"
56
+ - "pkg"
57
+
58
+ # ----------------------------------------------------------------------------
59
+ # GO IDIOMS
60
+ # ----------------------------------------------------------------------------
61
+ goIdioms:
62
+ enabled: true
63
+
64
+ naming:
65
+ packages: "lowercase, single word preferred"
66
+ exported: "PascalCase (exported)"
67
+ unexported: "camelCase (unexported)"
68
+ interfaces: "er suffix for single-method (Reader, Writer, Closer)"
69
+ acronyms: "ALL_CAPS (URL, HTTP, ID)"
70
+ receivers: "short, 1-2 letters (c for Customer)"
71
+ variables: "short in small scope, descriptive in large scope"
72
+ constants: "PascalCase for exported, camelCase for unexported"
73
+
74
+ errorHandling:
75
+ returnErrorLast: true
76
+ checkImmediately: true
77
+ wrapWithContext: true
78
+ useErrorsIs: true
79
+ useErrorsAs: true
80
+ sentinelErrors: true
81
+ customErrorTypes: true
82
+ neverPanicInLibraries: true
83
+ examples:
84
+ - "if err != nil { return fmt.Errorf(\"failed to create order: %w\", err) }"
85
+ - "var ErrNotFound = errors.New(\"entity not found\")"
86
+
87
+ patterns:
88
+ functionalOptions: true
89
+ tableDrivenTests: true
90
+ deferForCleanup: true
91
+ contextPropagation: true
92
+ interfaceSegregation: true
93
+ acceptInterfacesReturnStructs: true
94
+ earlyReturn: true
95
+ examples:
96
+ - "Functional Options: func NewServer(opts ...Option) *Server"
97
+ - "Accept interfaces: func Process(r io.Reader)"
98
+ - "Return structs: func NewService() *Service"
99
+
100
+ # ----------------------------------------------------------------------------
101
+ # CODE QUALITY
102
+ # ----------------------------------------------------------------------------
103
+ codeQuality:
104
+ maxMethodLines: 30
105
+ maxClassLines: 300
106
+ maxFileLines: 500
107
+ maxMethodParameters: 4
108
+ maxCyclomaticComplexity: 10
109
+ requireDocumentation: true
110
+ requireTests: true
111
+ minimumTestCoverage: 70
112
+
113
+ principles:
114
+ - "Clear is better than clever"
115
+ - "A little copying is better than a little dependency"
116
+ - "Errors are values"
117
+ - "Don't panic"
118
+ - "Accept interfaces, return structs"
119
+ - "Make the zero value useful"
120
+ - "Concurrency is not parallelism"
121
+
122
+ linting:
123
+ tools:
124
+ - "go fmt"
125
+ - "go vet"
126
+ - "staticcheck"
127
+ - "golangci-lint"
128
+ golangciLintConfig:
129
+ enabled:
130
+ - "errcheck"
131
+ - "gosimple"
132
+ - "govet"
133
+ - "ineffassign"
134
+ - "staticcheck"
135
+ - "unused"
136
+ - "gocyclo"
137
+ - "gofmt"
138
+ - "goimports"
139
+ - "misspell"
140
+ - "unconvert"
141
+ - "unparam"
142
+ - "gocritic"
143
+
144
+ # ----------------------------------------------------------------------------
145
+ # NAMING CONVENTIONS
146
+ # ----------------------------------------------------------------------------
147
+ naming:
148
+ general:
149
+ package: lowercase_single_word
150
+ struct: PascalCase
151
+ interface: PascalCase_er_suffix
152
+ function: PascalCase_or_camelCase
153
+ variable: camelCase
154
+ constant: PascalCase_or_camelCase
155
+ file: snake_case.go
156
+
157
+ suffixes:
158
+ handler: "Handler"
159
+ service: "Service"
160
+ repository: "Repository"
161
+ usecase: "UseCase"
162
+ mock: "Mock"
163
+ test: "_test.go"
164
+ options: "Option"
165
+
166
+ testing:
167
+ testFile: "*_test.go"
168
+ testFunction: "Test_FunctionName_Scenario"
169
+ benchmarkFunction: "Benchmark_FunctionName"
170
+ exampleFunction: "Example_FunctionName"
171
+
172
+ # ----------------------------------------------------------------------------
173
+ # TESTING
174
+ # ----------------------------------------------------------------------------
175
+ testing:
176
+ framework: "testing (stdlib)"
177
+ assertionLibrary: "testify/assert (optional)"
178
+ mockingLibrary: "gomock or mockery"
179
+
180
+ types:
181
+ unit:
182
+ suffix: "_test.go"
183
+ location: "same package"
184
+ coverage: 70
185
+ parallel: true
186
+
187
+ integration:
188
+ suffix: "_integration_test.go"
189
+ location: "same package or _test package"
190
+ buildTag: "integration"
191
+ useTestcontainers: true
192
+
193
+ benchmark:
194
+ prefix: "Benchmark_"
195
+ location: "*_test.go"
196
+
197
+ patterns:
198
+ tableDrivenTests: true
199
+ subtests: true
200
+ parallelTests: true
201
+ testHelpers: true
202
+ testFixtures: true
203
+ examples:
204
+ - |
205
+ func TestCalculateTotal(t *testing.T) {
206
+ tests := []struct {
207
+ name string
208
+ input []int
209
+ expected int
210
+ }{
211
+ {"empty slice", []int{}, 0},
212
+ {"single item", []int{5}, 5},
213
+ {"multiple items", []int{1, 2, 3}, 6},
214
+ }
215
+ for _, tt := range tests {
216
+ t.Run(tt.name, func(t *testing.T) {
217
+ t.Parallel()
218
+ got := CalculateTotal(tt.input)
219
+ if got != tt.expected {
220
+ t.Errorf("got %d, want %d", got, tt.expected)
221
+ }
222
+ })
223
+ }
224
+ }
225
+
226
+ testcontainers:
227
+ enabled: true
228
+ containers:
229
+ - "postgres"
230
+ - "redis"
231
+ - "kafka"
232
+ - "mongodb"
233
+
234
+ # ----------------------------------------------------------------------------
235
+ # CONCURRENCY
236
+ # ----------------------------------------------------------------------------
237
+ concurrency:
238
+ patterns:
239
+ goroutines:
240
+ alwaysOwnLifecycle: true
241
+ useWaitGroups: true
242
+ useErrgroups: true
243
+ avoidLeaks: true
244
+ channels:
245
+ preferUnbuffered: true
246
+ closeByProducer: true
247
+ nilChannelBlocks: true
248
+ context:
249
+ propagateAlways: true
250
+ useForCancellation: true
251
+ useForTimeout: true
252
+ firstParameter: true
253
+ mutexes:
254
+ embedPrivately: true
255
+ deferUnlock: true
256
+ avoidCopying: true
257
+
258
+ examples:
259
+ - "Always pass context.Context as first parameter"
260
+ - "Use errgroup.Group for concurrent error handling"
261
+ - "Close channels from producer side only"
262
+ - "Prefer select with default for non-blocking operations"
263
+
264
+ # ----------------------------------------------------------------------------
265
+ # ERROR HANDLING
266
+ # ----------------------------------------------------------------------------
267
+ errorHandling:
268
+ format: "Wrapped errors with context"
269
+
270
+ patterns:
271
+ sentinel:
272
+ - "var ErrNotFound = errors.New(\"not found\")"
273
+ - "var ErrInvalidInput = errors.New(\"invalid input\")"
274
+ - "var ErrUnauthorized = errors.New(\"unauthorized\")"
275
+
276
+ custom:
277
+ example: |
278
+ type ValidationError struct {
279
+ Field string
280
+ Message string
281
+ }
282
+ func (e *ValidationError) Error() string {
283
+ return fmt.Sprintf("%s: %s", e.Field, e.Message)
284
+ }
285
+
286
+ wrapping:
287
+ - "Always wrap with context: fmt.Errorf(\"failed to process order %s: %w\", orderID, err)"
288
+ - "Use errors.Is for sentinel errors"
289
+ - "Use errors.As for type assertions"
290
+
291
+ avoid:
292
+ - "panic in library code"
293
+ - "ignoring errors (even with _)"
294
+ - "error strings starting with capital or ending with punctuation"
295
+ - "naked returns in functions with named error return"
296
+
297
+ # ----------------------------------------------------------------------------
298
+ # OBSERVABILITY
299
+ # ----------------------------------------------------------------------------
300
+ observability:
301
+ enabled: true
302
+
303
+ logging:
304
+ framework: "slog (stdlib)"
305
+ format: "JSON"
306
+ structuredLogging: true
307
+ levels:
308
+ production: "Info"
309
+ development: "Debug"
310
+ contextual: true
311
+ avoid:
312
+ - "fmt.Println for logging"
313
+ - "log.Fatal in libraries"
314
+ - "logging sensitive data"
315
+ example: |
316
+ logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
317
+ logger.Info("order processed",
318
+ slog.String("order_id", order.ID),
319
+ slog.Int("items", len(order.Items)),
320
+ slog.Duration("duration", elapsed),
321
+ )
322
+
323
+ metrics:
324
+ framework: "prometheus/client_golang"
325
+ registry: "prometheus"
326
+ types:
327
+ - "Counter: requests_total, errors_total"
328
+ - "Gauge: connections_active, queue_length"
329
+ - "Histogram: request_duration_seconds"
330
+ naming: "snake_case with _total, _seconds suffixes"
331
+
332
+ tracing:
333
+ framework: "OpenTelemetry"
334
+ propagation: "W3C Trace Context"
335
+ exporters:
336
+ - "Jaeger"
337
+ - "OTLP"
338
+ spanAttributes:
339
+ - "http.method"
340
+ - "http.route"
341
+ - "http.status_code"
342
+ - "db.operation"
343
+
344
+ healthChecks:
345
+ endpoints:
346
+ - "/health"
347
+ - "/health/live"
348
+ - "/health/ready"
349
+ customChecks:
350
+ - "database"
351
+ - "redis"
352
+ - "external APIs"
353
+
354
+ # ----------------------------------------------------------------------------
355
+ # HTTP/API
356
+ # ----------------------------------------------------------------------------
357
+ httpApi:
358
+ router: "chi, gorilla/mux, or stdlib (1.22+)"
359
+ middleware:
360
+ - "logging"
361
+ - "recovery"
362
+ - "requestID"
363
+ - "timeout"
364
+ - "cors"
365
+
366
+ patterns:
367
+ requestValidation: "encoding/json + custom validators"
368
+ responseFormat: "JSON"
369
+ errorResponses: "RFC 7807 Problem Details"
370
+ pagination: "cursor-based or offset"
371
+ versioning: "/api/v1/"
372
+
373
+ gracefulShutdown:
374
+ enabled: true
375
+ timeout: "30s"
376
+ example: |
377
+ srv := &http.Server{Addr: ":8080", Handler: router}
378
+ go func() {
379
+ if err := srv.ListenAndServe(); err != http.ErrServerClosed {
380
+ log.Fatal(err)
381
+ }
382
+ }()
383
+ <-ctx.Done()
384
+ shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
385
+ defer cancel()
386
+ srv.Shutdown(shutdownCtx)
387
+
388
+ # ----------------------------------------------------------------------------
389
+ # DATABASE
390
+ # ----------------------------------------------------------------------------
391
+ database:
392
+ driver: "database/sql or sqlx"
393
+ orm: "sqlc (generated) or GORM"
394
+
395
+ migrations:
396
+ tool: "golang-migrate or goose"
397
+ location: "db/migrations"
398
+ naming: "{timestamp}_{description}.up.sql"
399
+
400
+ patterns:
401
+ repository: true
402
+ preparedStatements: true
403
+ connectionPooling: true
404
+ transactionHandling: true
405
+
406
+ queryBuilder: "sqlc (type-safe generated code)"
407
+
408
+ # ----------------------------------------------------------------------------
409
+ # PROJECT STRUCTURE
410
+ # ----------------------------------------------------------------------------
411
+ projectStructure:
412
+ layout: "Standard Go Project Layout"
413
+ directories:
414
+ cmd: "Main applications (cmd/api/, cmd/worker/)"
415
+ internal: "Private application code"
416
+ pkg: "Public library code (if any)"
417
+ api: "API definitions (OpenAPI, protobuf)"
418
+ configs: "Configuration files"
419
+ scripts: "Build and automation scripts"
420
+ test: "Additional test data and utilities"
421
+ docs: "Documentation"
422
+
423
+ example: |
424
+ myapp/
425
+ ├── cmd/
426
+ │ └── api/
427
+ │ └── main.go
428
+ ├── internal/
429
+ │ ├── domain/
430
+ │ │ ├── entity/
431
+ │ │ └── repository/
432
+ │ ├── usecase/
433
+ │ ├── handler/
434
+ │ └── infrastructure/
435
+ ├── pkg/
436
+ ├── api/
437
+ ├── configs/
438
+ └── go.mod
439
+
440
+ # ----------------------------------------------------------------------------
441
+ # DEPENDENCY INJECTION
442
+ # ----------------------------------------------------------------------------
443
+ dependencyInjection:
444
+ approach: "Constructor injection (manual)"
445
+ frameworks: "wire (optional), fx (optional)"
446
+
447
+ pattern: |
448
+ func NewOrderService(repo OrderRepository, logger *slog.Logger) *OrderService {
449
+ return &OrderService{
450
+ repo: repo,
451
+ logger: logger,
452
+ }
453
+ }
454
+
455
+ # ----------------------------------------------------------------------------
456
+ # TECHNOLOGIES
457
+ # ----------------------------------------------------------------------------
458
+ technologies:
459
+ - name: go
460
+ version: "1.22+"
461
+ specificRules:
462
+ useGoModules: true
463
+ minimumGoVersion: "1.22"
464
+ useGenerics: true
465
+ useRangeOverFunc: true
466
+ useSlicesPackage: true
467
+ useMapsPackage: true
468
+
469
+ - name: http
470
+ framework: "chi or stdlib mux"
471
+ specificRules:
472
+ useMiddleware: true
473
+ useGracefulShutdown: true
474
+ useContextTimeout: true
475
+
476
+ - name: database
477
+ driver: "pgx for PostgreSQL"
478
+ specificRules:
479
+ usePreparedStatements: true
480
+ useConnectionPool: true
481
+ handleTransactions: true
482
+
483
+ - name: testing
484
+ specificRules:
485
+ useTableDrivenTests: true
486
+ useParallelTests: true
487
+ useSubtests: true
488
+ useBenchmarks: true
489
+ useTestcontainers: true
490
+
491
+ # ----------------------------------------------------------------------------
492
+ # CODE EXAMPLES
493
+ # ----------------------------------------------------------------------------
494
+ codeExamples:
495
+ entity:
496
+ description: "Domain entity with business logic"
497
+ code: |
498
+ // internal/domain/entity/order.go
499
+ package entity
500
+
501
+ import (
502
+ "errors"
503
+ "time"
504
+
505
+ "github.com/google/uuid"
506
+ )
507
+
508
+ var (
509
+ ErrEmptyItems = errors.New("order must have at least one item")
510
+ ErrNotPending = errors.New("order is not pending")
511
+ ErrInvalidAmount = errors.New("amount must be positive")
512
+ )
513
+
514
+ type OrderStatus string
515
+
516
+ const (
517
+ OrderStatusPending OrderStatus = "PENDING"
518
+ OrderStatusConfirmed OrderStatus = "CONFIRMED"
519
+ OrderStatusCancelled OrderStatus = "CANCELLED"
520
+ )
521
+
522
+ type Order struct {
523
+ ID uuid.UUID
524
+ CustomerID uuid.UUID
525
+ Items []OrderItem
526
+ Status OrderStatus
527
+ CreatedAt time.Time
528
+ }
529
+
530
+ func NewOrder(customerID uuid.UUID, items []OrderItem) (*Order, error) {
531
+ if len(items) == 0 {
532
+ return nil, ErrEmptyItems
533
+ }
534
+
535
+ return &Order{
536
+ ID: uuid.New(),
537
+ CustomerID: customerID,
538
+ Items: items,
539
+ Status: OrderStatusPending,
540
+ CreatedAt: time.Now(),
541
+ }, nil
542
+ }
543
+
544
+ func (o *Order) Total() Money {
545
+ total := Money{Amount: 0, Currency: "USD"}
546
+ for _, item := range o.Items {
547
+ total = total.Add(item.Subtotal())
548
+ }
549
+ return total
550
+ }
551
+
552
+ func (o *Order) Confirm() error {
553
+ if o.Status != OrderStatusPending {
554
+ return ErrNotPending
555
+ }
556
+ o.Status = OrderStatusConfirmed
557
+ return nil
558
+ }
559
+
560
+ func (o *Order) AddItem(item OrderItem) error {
561
+ if o.Status != OrderStatusPending {
562
+ return ErrNotPending
563
+ }
564
+ o.Items = append(o.Items, item)
565
+ return nil
566
+ }
567
+
568
+ valueObject:
569
+ description: "Immutable value object"
570
+ code: |
571
+ // internal/domain/valueobject/money.go
572
+ package valueobject
573
+
574
+ import "fmt"
575
+
576
+ type Money struct {
577
+ Amount int64 // cents
578
+ Currency string
579
+ }
580
+
581
+ func NewMoney(amount int64, currency string) (Money, error) {
582
+ if amount < 0 {
583
+ return Money{}, fmt.Errorf("amount cannot be negative: %d", amount)
584
+ }
585
+ return Money{Amount: amount, Currency: currency}, nil
586
+ }
587
+
588
+ func (m Money) Add(other Money) Money {
589
+ if m.Currency != other.Currency {
590
+ panic(fmt.Sprintf("currency mismatch: %s vs %s", m.Currency, other.Currency))
591
+ }
592
+ return Money{Amount: m.Amount + other.Amount, Currency: m.Currency}
593
+ }
594
+
595
+ func (m Money) Multiply(factor int) Money {
596
+ return Money{Amount: m.Amount * int64(factor), Currency: m.Currency}
597
+ }
598
+
599
+ func (m Money) String() string {
600
+ return fmt.Sprintf("%s %.2f", m.Currency, float64(m.Amount)/100)
601
+ }
602
+
603
+ func (m Money) Equals(other Money) bool {
604
+ return m.Amount == other.Amount && m.Currency == other.Currency
605
+ }
606
+
607
+ repositoryInterface:
608
+ description: "Repository interface (port)"
609
+ code: |
610
+ // internal/domain/repository/order.go
611
+ package repository
612
+
613
+ import (
614
+ "context"
615
+
616
+ "github.com/google/uuid"
617
+ "myapp/internal/domain/entity"
618
+ )
619
+
620
+ type OrderRepository interface {
621
+ Save(ctx context.Context, order *entity.Order) error
622
+ FindByID(ctx context.Context, id uuid.UUID) (*entity.Order, error)
623
+ FindByCustomerID(ctx context.Context, customerID uuid.UUID) ([]*entity.Order, error)
624
+ }
625
+
626
+ useCase:
627
+ description: "Application use case"
628
+ code: |
629
+ // internal/usecase/create_order.go
630
+ package usecase
631
+
632
+ import (
633
+ "context"
634
+ "log/slog"
635
+
636
+ "github.com/google/uuid"
637
+ "myapp/internal/domain/entity"
638
+ "myapp/internal/domain/repository"
639
+ )
640
+
641
+ type CreateOrderInput struct {
642
+ CustomerID uuid.UUID
643
+ Items []CreateOrderItemInput
644
+ }
645
+
646
+ type CreateOrderItemInput struct {
647
+ ProductID uuid.UUID
648
+ Quantity int
649
+ UnitPrice int64
650
+ }
651
+
652
+ type CreateOrderUseCase struct {
653
+ orderRepo repository.OrderRepository
654
+ publisher EventPublisher
655
+ logger *slog.Logger
656
+ }
657
+
658
+ func NewCreateOrderUseCase(
659
+ orderRepo repository.OrderRepository,
660
+ publisher EventPublisher,
661
+ logger *slog.Logger,
662
+ ) *CreateOrderUseCase {
663
+ return &CreateOrderUseCase{
664
+ orderRepo: orderRepo,
665
+ publisher: publisher,
666
+ logger: logger,
667
+ }
668
+ }
669
+
670
+ func (uc *CreateOrderUseCase) Execute(ctx context.Context, input CreateOrderInput) (*entity.Order, error) {
671
+ uc.logger.Info("creating order", slog.String("customer_id", input.CustomerID.String()))
672
+
673
+ items := make([]entity.OrderItem, 0, len(input.Items))
674
+ for _, item := range input.Items {
675
+ orderItem, err := entity.NewOrderItem(item.ProductID, item.Quantity, item.UnitPrice)
676
+ if err != nil {
677
+ return nil, fmt.Errorf("invalid item: %w", err)
678
+ }
679
+ items = append(items, orderItem)
680
+ }
681
+
682
+ order, err := entity.NewOrder(input.CustomerID, items)
683
+ if err != nil {
684
+ return nil, fmt.Errorf("failed to create order: %w", err)
685
+ }
686
+
687
+ if err := uc.orderRepo.Save(ctx, order); err != nil {
688
+ return nil, fmt.Errorf("failed to save order: %w", err)
689
+ }
690
+
691
+ if err := uc.publisher.Publish(ctx, OrderCreatedEvent{OrderID: order.ID}); err != nil {
692
+ uc.logger.Error("failed to publish event", slog.Any("error", err))
693
+ }
694
+
695
+ uc.logger.Info("order created", slog.String("order_id", order.ID.String()))
696
+
697
+ return order, nil
698
+ }
699
+
700
+ handler:
701
+ description: "HTTP handler with chi router"
702
+ code: |
703
+ // internal/handler/order_handler.go
704
+ package handler
705
+
706
+ import (
707
+ "encoding/json"
708
+ "net/http"
709
+
710
+ "github.com/go-chi/chi/v5"
711
+ "github.com/google/uuid"
712
+ "myapp/internal/usecase"
713
+ )
714
+
715
+ type OrderHandler struct {
716
+ createOrderUC *usecase.CreateOrderUseCase
717
+ getOrderUC *usecase.GetOrderUseCase
718
+ }
719
+
720
+ func NewOrderHandler(
721
+ createOrderUC *usecase.CreateOrderUseCase,
722
+ getOrderUC *usecase.GetOrderUseCase,
723
+ ) *OrderHandler {
724
+ return &OrderHandler{
725
+ createOrderUC: createOrderUC,
726
+ getOrderUC: getOrderUC,
727
+ }
728
+ }
729
+
730
+ func (h *OrderHandler) Routes() chi.Router {
731
+ r := chi.NewRouter()
732
+ r.Post("/", h.Create)
733
+ r.Get("/{id}", h.GetByID)
734
+ return r
735
+ }
736
+
737
+ type CreateOrderRequest struct {
738
+ CustomerID string `json:"customer_id"`
739
+ Items []OrderItemRequest `json:"items"`
740
+ }
741
+
742
+ type OrderItemRequest struct {
743
+ ProductID string `json:"product_id"`
744
+ Quantity int `json:"quantity"`
745
+ UnitPrice int64 `json:"unit_price"`
746
+ }
747
+
748
+ func (h *OrderHandler) Create(w http.ResponseWriter, r *http.Request) {
749
+ var req CreateOrderRequest
750
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
751
+ respondError(w, http.StatusBadRequest, "invalid request body")
752
+ return
753
+ }
754
+
755
+ customerID, err := uuid.Parse(req.CustomerID)
756
+ if err != nil {
757
+ respondError(w, http.StatusBadRequest, "invalid customer_id")
758
+ return
759
+ }
760
+
761
+ input := usecase.CreateOrderInput{
762
+ CustomerID: customerID,
763
+ Items: mapItemsToInput(req.Items),
764
+ }
765
+
766
+ order, err := h.createOrderUC.Execute(r.Context(), input)
767
+ if err != nil {
768
+ handleUseCaseError(w, err)
769
+ return
770
+ }
771
+
772
+ respondJSON(w, http.StatusCreated, mapOrderToResponse(order))
773
+ }
774
+
775
+ func (h *OrderHandler) GetByID(w http.ResponseWriter, r *http.Request) {
776
+ idStr := chi.URLParam(r, "id")
777
+ id, err := uuid.Parse(idStr)
778
+ if err != nil {
779
+ respondError(w, http.StatusBadRequest, "invalid order id")
780
+ return
781
+ }
782
+
783
+ order, err := h.getOrderUC.Execute(r.Context(), id)
784
+ if err != nil {
785
+ handleUseCaseError(w, err)
786
+ return
787
+ }
788
+
789
+ respondJSON(w, http.StatusOK, mapOrderToResponse(order))
790
+ }
791
+
792
+ repositoryImpl:
793
+ description: "Repository implementation with PostgreSQL"
794
+ code: |
795
+ // internal/infrastructure/postgres/order_repository.go
796
+ package postgres
797
+
798
+ import (
799
+ "context"
800
+ "database/sql"
801
+ "fmt"
802
+
803
+ "github.com/google/uuid"
804
+ "myapp/internal/domain/entity"
805
+ "myapp/internal/domain/repository"
806
+ )
807
+
808
+ type OrderRepository struct {
809
+ db *sql.DB
810
+ }
811
+
812
+ func NewOrderRepository(db *sql.DB) *OrderRepository {
813
+ return &OrderRepository{db: db}
814
+ }
815
+
816
+ func (r *OrderRepository) Save(ctx context.Context, order *entity.Order) error {
817
+ tx, err := r.db.BeginTx(ctx, nil)
818
+ if err != nil {
819
+ return fmt.Errorf("begin transaction: %w", err)
820
+ }
821
+ defer tx.Rollback()
822
+
823
+ _, err = tx.ExecContext(ctx, `
824
+ INSERT INTO orders (id, customer_id, status, created_at)
825
+ VALUES ($1, $2, $3, $4)
826
+ ON CONFLICT (id) DO UPDATE SET status = $3
827
+ `, order.ID, order.CustomerID, order.Status, order.CreatedAt)
828
+ if err != nil {
829
+ return fmt.Errorf("insert order: %w", err)
830
+ }
831
+
832
+ for _, item := range order.Items {
833
+ _, err = tx.ExecContext(ctx, `
834
+ INSERT INTO order_items (id, order_id, product_id, quantity, unit_price)
835
+ VALUES ($1, $2, $3, $4, $5)
836
+ `, item.ID, order.ID, item.ProductID, item.Quantity, item.UnitPrice)
837
+ if err != nil {
838
+ return fmt.Errorf("insert item: %w", err)
839
+ }
840
+ }
841
+
842
+ if err := tx.Commit(); err != nil {
843
+ return fmt.Errorf("commit transaction: %w", err)
844
+ }
845
+
846
+ return nil
847
+ }
848
+
849
+ func (r *OrderRepository) FindByID(ctx context.Context, id uuid.UUID) (*entity.Order, error) {
850
+ row := r.db.QueryRowContext(ctx, `
851
+ SELECT id, customer_id, status, created_at
852
+ FROM orders WHERE id = $1
853
+ `, id)
854
+
855
+ var order entity.Order
856
+ if err := row.Scan(&order.ID, &order.CustomerID, &order.Status, &order.CreatedAt); err != nil {
857
+ if err == sql.ErrNoRows {
858
+ return nil, nil
859
+ }
860
+ return nil, fmt.Errorf("scan order: %w", err)
861
+ }
862
+
863
+ items, err := r.findItemsByOrderID(ctx, id)
864
+ if err != nil {
865
+ return nil, err
866
+ }
867
+ order.Items = items
868
+
869
+ return &order, nil
870
+ }
871
+
872
+ test:
873
+ description: "Table-driven test"
874
+ code: |
875
+ // internal/domain/entity/order_test.go
876
+ package entity_test
877
+
878
+ import (
879
+ "testing"
880
+
881
+ "github.com/google/uuid"
882
+ "github.com/stretchr/testify/assert"
883
+ "github.com/stretchr/testify/require"
884
+ "myapp/internal/domain/entity"
885
+ )
886
+
887
+ func TestNewOrder(t *testing.T) {
888
+ t.Parallel()
889
+
890
+ customerID := uuid.New()
891
+ validItem := entity.OrderItem{
892
+ ID: uuid.New(),
893
+ ProductID: uuid.New(),
894
+ Quantity: 2,
895
+ UnitPrice: 1000,
896
+ }
897
+
898
+ tests := []struct {
899
+ name string
900
+ customerID uuid.UUID
901
+ items []entity.OrderItem
902
+ wantErr error
903
+ wantStatus entity.OrderStatus
904
+ }{
905
+ {
906
+ name: "valid order",
907
+ customerID: customerID,
908
+ items: []entity.OrderItem{validItem},
909
+ wantStatus: entity.OrderStatusPending,
910
+ },
911
+ {
912
+ name: "empty items",
913
+ customerID: customerID,
914
+ items: []entity.OrderItem{},
915
+ wantErr: entity.ErrEmptyItems,
916
+ },
917
+ }
918
+
919
+ for _, tt := range tests {
920
+ t.Run(tt.name, func(t *testing.T) {
921
+ t.Parallel()
922
+
923
+ order, err := entity.NewOrder(tt.customerID, tt.items)
924
+
925
+ if tt.wantErr != nil {
926
+ require.ErrorIs(t, err, tt.wantErr)
927
+ return
928
+ }
929
+
930
+ require.NoError(t, err)
931
+ assert.Equal(t, tt.customerID, order.CustomerID)
932
+ assert.Equal(t, tt.wantStatus, order.Status)
933
+ assert.NotEqual(t, uuid.Nil, order.ID)
934
+ })
935
+ }
936
+ }
937
+
938
+ func TestOrder_Confirm(t *testing.T) {
939
+ t.Parallel()
940
+
941
+ t.Run("should confirm pending order", func(t *testing.T) {
942
+ t.Parallel()
943
+
944
+ order := createTestOrder(t)
945
+
946
+ err := order.Confirm()
947
+
948
+ require.NoError(t, err)
949
+ assert.Equal(t, entity.OrderStatusConfirmed, order.Status)
950
+ })
951
+
952
+ t.Run("should fail to confirm non-pending order", func(t *testing.T) {
953
+ t.Parallel()
954
+
955
+ order := createTestOrder(t)
956
+ _ = order.Confirm()
957
+
958
+ err := order.Confirm()
959
+
960
+ require.ErrorIs(t, err, entity.ErrNotPending)
961
+ })
962
+ }
963
+
964
+ func createTestOrder(t *testing.T) *entity.Order {
965
+ t.Helper()
966
+ order, err := entity.NewOrder(uuid.New(), []entity.OrderItem{
967
+ {ID: uuid.New(), ProductID: uuid.New(), Quantity: 1, UnitPrice: 1000},
968
+ })
969
+ require.NoError(t, err)
970
+ return order
971
+ }
972
+
973
+ functionalOptions:
974
+ description: "Functional options pattern"
975
+ code: |
976
+ // pkg/server/server.go
977
+ package server
978
+
979
+ import (
980
+ "log/slog"
981
+ "net/http"
982
+ "time"
983
+ )
984
+
985
+ type Server struct {
986
+ addr string
987
+ readTimeout time.Duration
988
+ writeTimeout time.Duration
989
+ logger *slog.Logger
990
+ handler http.Handler
991
+ }
992
+
993
+ type Option func(*Server)
994
+
995
+ func WithAddr(addr string) Option {
996
+ return func(s *Server) {
997
+ s.addr = addr
998
+ }
999
+ }
1000
+
1001
+ func WithReadTimeout(d time.Duration) Option {
1002
+ return func(s *Server) {
1003
+ s.readTimeout = d
1004
+ }
1005
+ }
1006
+
1007
+ func WithWriteTimeout(d time.Duration) Option {
1008
+ return func(s *Server) {
1009
+ s.writeTimeout = d
1010
+ }
1011
+ }
1012
+
1013
+ func WithLogger(logger *slog.Logger) Option {
1014
+ return func(s *Server) {
1015
+ s.logger = logger
1016
+ }
1017
+ }
1018
+
1019
+ func NewServer(handler http.Handler, opts ...Option) *Server {
1020
+ s := &Server{
1021
+ addr: ":8080",
1022
+ readTimeout: 15 * time.Second,
1023
+ writeTimeout: 15 * time.Second,
1024
+ logger: slog.Default(),
1025
+ handler: handler,
1026
+ }
1027
+
1028
+ for _, opt := range opts {
1029
+ opt(s)
1030
+ }
1031
+
1032
+ return s
1033
+ }
1034
+
1035
+ func (s *Server) Start() error {
1036
+ srv := &http.Server{
1037
+ Addr: s.addr,
1038
+ Handler: s.handler,
1039
+ ReadTimeout: s.readTimeout,
1040
+ WriteTimeout: s.writeTimeout,
1041
+ }
1042
+
1043
+ s.logger.Info("starting server", slog.String("addr", s.addr))
1044
+ return srv.ListenAndServe()
1045
+ }
1046
+
1047
+ # ----------------------------------------------------------------------------
1048
+ # ANTI-PATTERNS
1049
+ # ----------------------------------------------------------------------------
1050
+ antiPatterns:
1051
+ panicInLibraries:
1052
+ name: "Panic in Library Code"
1053
+ description: "Using panic instead of returning errors"
1054
+ bad: |
1055
+ // ❌ Panic in library code
1056
+ func ParseConfig(path string) Config {
1057
+ data, err := os.ReadFile(path)
1058
+ if err != nil {
1059
+ panic(err) // Don't panic!
1060
+ }
1061
+ var cfg Config
1062
+ if err := json.Unmarshal(data, &cfg); err != nil {
1063
+ panic(err)
1064
+ }
1065
+ return cfg
1066
+ }
1067
+ good: |
1068
+ // ✅ Return errors
1069
+ func ParseConfig(path string) (Config, error) {
1070
+ data, err := os.ReadFile(path)
1071
+ if err != nil {
1072
+ return Config{}, fmt.Errorf("read config file: %w", err)
1073
+ }
1074
+
1075
+ var cfg Config
1076
+ if err := json.Unmarshal(data, &cfg); err != nil {
1077
+ return Config{}, fmt.Errorf("parse config: %w", err)
1078
+ }
1079
+
1080
+ return cfg, nil
1081
+ }
1082
+
1083
+ ignoringErrors:
1084
+ name: "Ignoring Errors"
1085
+ description: "Discarding errors with _"
1086
+ bad: |
1087
+ // ❌ Ignoring errors
1088
+ data, _ := json.Marshal(order)
1089
+ _ = db.Save(order)
1090
+ resp, _ := http.Get(url)
1091
+ good: |
1092
+ // ❌ Handle errors properly
1093
+ data, err := json.Marshal(order)
1094
+ if err != nil {
1095
+ return fmt.Errorf("marshal order: %w", err)
1096
+ }
1097
+
1098
+ if err := db.Save(order); err != nil {
1099
+ return fmt.Errorf("save order: %w", err)
1100
+ }
1101
+
1102
+ resp, err := http.Get(url)
1103
+ if err != nil {
1104
+ return fmt.Errorf("fetch data: %w", err)
1105
+ }
1106
+ defer resp.Body.Close()
1107
+
1108
+ goroutineLeaks:
1109
+ name: "Goroutine Leaks"
1110
+ description: "Starting goroutines without proper lifecycle management"
1111
+ bad: |
1112
+ // ❌ Goroutine leak - no way to stop
1113
+ func StartWorker() {
1114
+ go func() {
1115
+ for {
1116
+ processItem(<-queue)
1117
+ }
1118
+ }()
1119
+ }
1120
+ good: |
1121
+ // ✅ Goroutine with context cancellation
1122
+ func StartWorker(ctx context.Context, queue <-chan Item) {
1123
+ go func() {
1124
+ for {
1125
+ select {
1126
+ case <-ctx.Done():
1127
+ return
1128
+ case item := <-queue:
1129
+ processItem(item)
1130
+ }
1131
+ }
1132
+ }()
1133
+ }
1134
+
1135
+ noContextPropagation:
1136
+ name: "Missing Context Propagation"
1137
+ description: "Not passing context through the call chain"
1138
+ bad: |
1139
+ // ❌ No context propagation
1140
+ func (s *Service) ProcessOrder(order Order) error {
1141
+ user := s.userRepo.FindByID(order.UserID) // No context!
1142
+ return s.orderRepo.Save(order) // No context!
1143
+ }
1144
+ good: |
1145
+ // ✅ Context propagation
1146
+ func (s *Service) ProcessOrder(ctx context.Context, order Order) error {
1147
+ user, err := s.userRepo.FindByID(ctx, order.UserID)
1148
+ if err != nil {
1149
+ return fmt.Errorf("find user: %w", err)
1150
+ }
1151
+
1152
+ if err := s.orderRepo.Save(ctx, order); err != nil {
1153
+ return fmt.Errorf("save order: %w", err)
1154
+ }
1155
+
1156
+ return nil
1157
+ }
1158
+
1159
+ globalState:
1160
+ name: "Global Mutable State"
1161
+ description: "Using global variables for state"
1162
+ bad: |
1163
+ // ❌ Global mutable state
1164
+ var db *sql.DB
1165
+ var logger *slog.Logger
1166
+
1167
+ func init() {
1168
+ db, _ = sql.Open("postgres", os.Getenv("DB_URL"))
1169
+ logger = slog.Default()
1170
+ }
1171
+
1172
+ func GetUser(id string) (*User, error) {
1173
+ return db.Query("SELECT * FROM users WHERE id = $1", id)
1174
+ }
1175
+ good: |
1176
+ // ✅ Dependency injection
1177
+ type UserRepository struct {
1178
+ db *sql.DB
1179
+ logger *slog.Logger
1180
+ }
1181
+
1182
+ func NewUserRepository(db *sql.DB, logger *slog.Logger) *UserRepository {
1183
+ return &UserRepository{db: db, logger: logger}
1184
+ }
1185
+
1186
+ func (r *UserRepository) GetUser(ctx context.Context, id string) (*User, error) {
1187
+ // ...
1188
+ }
1189
+
1190
+ nakedReturns:
1191
+ name: "Naked Returns in Long Functions"
1192
+ description: "Using naked returns in non-trivial functions"
1193
+ bad: |
1194
+ // ❌ Naked return in long function
1195
+ func ProcessOrder(id string) (order *Order, err error) {
1196
+ order, err = repo.FindByID(id)
1197
+ if err != nil {
1198
+ return // What are we returning?
1199
+ }
1200
+
1201
+ // ... 50 more lines ...
1202
+
1203
+ order.Status = "processed"
1204
+ return // Unclear what's returned
1205
+ }
1206
+ good: |
1207
+ // ✅ Explicit returns
1208
+ func ProcessOrder(id string) (*Order, error) {
1209
+ order, err := repo.FindByID(id)
1210
+ if err != nil {
1211
+ return nil, fmt.Errorf("find order: %w", err)
1212
+ }
1213
+
1214
+ // ...
1215
+
1216
+ order.Status = "processed"
1217
+ return order, nil
1218
+ }
1219
+
1220
+ interfaceForNoReason:
1221
+ name: "Premature Interface Abstraction"
1222
+ description: "Creating interfaces before there's a need for them"
1223
+ bad: |
1224
+ // ❌ Interface with single implementation
1225
+ type OrderServiceInterface interface {
1226
+ Create(order Order) error
1227
+ Get(id string) (*Order, error)
1228
+ }
1229
+
1230
+ type OrderService struct{}
1231
+
1232
+ func (s *OrderService) Create(order Order) error { ... }
1233
+ func (s *OrderService) Get(id string) (*Order, error) { ... }
1234
+
1235
+ // Only one implementation exists
1236
+ good: |
1237
+ // ✅ Define interface at consumer side when needed
1238
+ // internal/usecase/create_order.go
1239
+ type OrderRepository interface { // Defined where it's used
1240
+ Save(ctx context.Context, order *Order) error
1241
+ }
1242
+
1243
+ type CreateOrderUseCase struct {
1244
+ repo OrderRepository // Accept interface
1245
+ }
1246
+
1247
+ // internal/infrastructure/postgres/order_repo.go
1248
+ type OrderRepository struct { // Return concrete type
1249
+ db *sql.DB
1250
+ }
1251
+
1252
+ closingChannelsFromConsumer:
1253
+ name: "Closing Channels from Consumer"
1254
+ description: "Consumer closing a channel instead of producer"
1255
+ bad: |
1256
+ // ❌ Consumer closes channel
1257
+ func consumer(ch chan int) {
1258
+ for v := range ch {
1259
+ process(v)
1260
+ }
1261
+ close(ch) // Don't close from consumer!
1262
+ }
1263
+ good: |
1264
+ // ✅ Producer closes channel
1265
+ func producer(ch chan int) {
1266
+ defer close(ch) // Producer closes
1267
+ for i := 0; i < 10; i++ {
1268
+ ch <- i
1269
+ }
1270
+ }
1271
+
1272
+ func consumer(ch chan int) {
1273
+ for v := range ch { // Range handles closed channel
1274
+ process(v)
1275
+ }
1276
+ }