ga-plugins-cli 0.1.0 → 0.1.2

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 (55) hide show
  1. package/dist/config-patcher.d.ts +20 -50
  2. package/dist/config-patcher.d.ts.map +1 -1
  3. package/dist/config-patcher.js +138 -102
  4. package/dist/config-patcher.js.map +1 -1
  5. package/dist/index.js +75 -22
  6. package/dist/index.js.map +1 -1
  7. package/dist/installer.d.ts +0 -18
  8. package/dist/installer.d.ts.map +1 -1
  9. package/dist/installer.js +19 -39
  10. package/dist/installer.js.map +1 -1
  11. package/dist/types.d.ts +10 -6
  12. package/dist/types.d.ts.map +1 -1
  13. package/dist/uninstaller.d.ts +0 -23
  14. package/dist/uninstaller.d.ts.map +1 -1
  15. package/dist/uninstaller.js +22 -68
  16. package/dist/uninstaller.js.map +1 -1
  17. package/package.json +3 -2
  18. package/plugins/go-reviewer/.claude-plugin/plugin.json +12 -0
  19. package/plugins/go-reviewer/commands/go-review.md +424 -0
  20. package/plugins/go-reviewer/mcp-servers/go-reviewer-mcp/README.md +236 -0
  21. package/plugins/go-reviewer/mcp-servers/go-reviewer-mcp/main.go +678 -0
  22. package/plugins/go-scaffolder/.claude-plugin/plugin.json +12 -0
  23. package/plugins/go-scaffolder/commands/scaffold-service.md +802 -0
  24. package/plugins/go-scaffolder/reference-service/.env.example +27 -0
  25. package/plugins/go-scaffolder/reference-service/Dockerfile +55 -0
  26. package/plugins/go-scaffolder/reference-service/REFERENCE-SERVICE-NOTICE.md +104 -0
  27. package/plugins/go-scaffolder/reference-service/cmd/server/main.go +266 -0
  28. package/plugins/go-scaffolder/reference-service/config/config.go +67 -0
  29. package/plugins/go-scaffolder/reference-service/go.mod +17 -0
  30. package/plugins/go-scaffolder/reference-service/internal/domain/booking.go +118 -0
  31. package/plugins/go-scaffolder/reference-service/internal/handler/booking.go +242 -0
  32. package/plugins/go-scaffolder/reference-service/internal/handler/booking_test.go +451 -0
  33. package/plugins/go-scaffolder/reference-service/internal/repository/booking_postgres.go +124 -0
  34. package/plugins/go-scaffolder/reference-service/internal/usecase/booking.go +181 -0
  35. package/plugins/go-standards/.claude-plugin/plugin.json +22 -0
  36. package/plugins/go-standards/commands/go-standards-check.md +232 -0
  37. package/plugins/go-standards/skills/concurrency.md +336 -0
  38. package/plugins/go-standards/skills/config.md +267 -0
  39. package/plugins/go-standards/skills/error-handling.md +286 -0
  40. package/plugins/go-standards/skills/http-chi.md +390 -0
  41. package/plugins/go-standards/skills/logging-observability.md +340 -0
  42. package/plugins/go-standards/skills/naming-and-style.md +315 -0
  43. package/plugins/go-standards/skills/project-layout.md +313 -0
  44. package/plugins/go-standards/skills/testing.md +366 -0
  45. package/plugins/java2go-porter/.claude-plugin/plugin.json +21 -0
  46. package/plugins/java2go-porter/agents/analyzer.md +232 -0
  47. package/plugins/java2go-porter/agents/reviewer.md +241 -0
  48. package/plugins/java2go-porter/agents/test-pairer.md +365 -0
  49. package/plugins/java2go-porter/agents/translator.md +419 -0
  50. package/plugins/java2go-porter/commands/port-java-service.md +149 -0
  51. package/plugins/java2go-porter/skills/idiom-mapping.md +75 -0
  52. package/plugins/migration-safety/.claude-plugin/plugin.json +20 -0
  53. package/plugins/migration-safety/commands/gen-characterization-test.md +452 -0
  54. package/plugins/migration-safety/commands/strangler-plan.md +356 -0
  55. package/plugins/migration-safety/skills/strangler-fig.md +382 -0
@@ -0,0 +1,313 @@
1
+ # Project Layout Standard
2
+
3
+ Canonical folder structure for every chi-based service in this migration.
4
+ All 30 services MUST follow this layout without exception — consistency is
5
+ what makes the consolidated codebase navigable.
6
+
7
+ ---
8
+
9
+ ## Canonical Tree
10
+
11
+ ```
12
+ my-service/
13
+ cmd/
14
+ server/
15
+ main.go # entrypoint only — wires deps, calls ListenAndServe
16
+ internal/
17
+ handler/ # HTTP handlers — thin, delegate immediately to usecase
18
+ user_handler.go
19
+ user_handler_test.go
20
+ usecase/ # business logic — the core of the service
21
+ user_usecase.go
22
+ user_usecase_test.go
23
+ repository/ # data-access implementations (DB, cache, external APIs)
24
+ user_repository.go
25
+ user_repository_test.go
26
+ domain/ # structs, interfaces, sentinel errors — zero external imports
27
+ user.go
28
+ errors.go
29
+ pkg/ # importable by external packages (shared clients, helpers)
30
+ config/ # env loading, Config struct
31
+ config.go
32
+ go.mod
33
+ go.sum
34
+ Dockerfile
35
+ .env.example
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Rule 1 — `cmd/server/main.go` wires dependencies only
41
+
42
+ `main.go` is a composition root. It reads config, constructs every concrete
43
+ type, wires them together, and starts the HTTP server. No business logic lives
44
+ here.
45
+
46
+ ### DO
47
+
48
+ ```go
49
+ // cmd/server/main.go
50
+ package main
51
+
52
+ import (
53
+ "context"
54
+ "log"
55
+ "net/http"
56
+ "os/signal"
57
+ "syscall"
58
+ "time"
59
+
60
+ "github.com/go-chi/chi/v5"
61
+ "github.com/go-chi/chi/v5/middleware"
62
+ "go.uber.org/zap"
63
+
64
+ "github.com/zokypesch/go-ga-lib/config"
65
+ "github.com/zokypesch/go-ga-lib/internal/handler"
66
+ "github.com/zokypesch/go-ga-lib/internal/repository"
67
+ "github.com/zokypesch/go-ga-lib/internal/usecase"
68
+ )
69
+
70
+ func main() {
71
+ cfg, err := config.Load()
72
+ if err != nil {
73
+ log.Fatalf("loading config: %v", err)
74
+ }
75
+
76
+ logger, err := zap.NewProduction()
77
+ if err != nil {
78
+ log.Fatalf("building logger: %v", err)
79
+ }
80
+ defer logger.Sync()
81
+
82
+ db, err := repository.OpenDB(cfg.DSN)
83
+ if err != nil {
84
+ log.Fatalf("opening db: %v", err)
85
+ }
86
+
87
+ userRepo := repository.NewUserRepository(db)
88
+ userUC := usecase.NewUserUsecase(userRepo, logger)
89
+ userH := handler.NewUserHandler(userUC, logger)
90
+
91
+ r := chi.NewRouter()
92
+ r.Use(middleware.RequestID)
93
+ r.Use(middleware.Logger)
94
+ r.Use(middleware.Recoverer)
95
+
96
+ r.Route("/api/v1", func(r chi.Router) {
97
+ r.Mount("/users", userH.Routes())
98
+ })
99
+
100
+ srv := &http.Server{Addr: ":" + cfg.Port, Handler: r}
101
+
102
+ ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
103
+ defer stop()
104
+
105
+ go func() {
106
+ if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
107
+ logger.Fatal("server error", zap.Error(err))
108
+ }
109
+ }()
110
+
111
+ <-ctx.Done()
112
+ shutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
113
+ defer cancel()
114
+ _ = srv.Shutdown(shutCtx)
115
+ }
116
+ ```
117
+
118
+ ### DO NOT
119
+
120
+ ```go
121
+ // cmd/server/main.go — WRONG: business logic leaking into main
122
+ func main() {
123
+ // WRONG: querying the DB directly in main
124
+ rows, _ := db.Query("SELECT * FROM users WHERE active = true")
125
+ // WRONG: inline HTTP handler in main
126
+ http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
127
+ // ... business logic here ...
128
+ })
129
+ http.ListenAndServe(":8080", nil)
130
+ }
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Rule 2 — `internal/` is private to this module
136
+
137
+ Go enforces this at the compiler level: packages under `internal/` cannot be
138
+ imported by any module outside the module root. Rely on this — never fight it.
139
+
140
+ ### DO
141
+
142
+ ```
143
+ // Correct: a sibling service imports only pkg/, never internal/
144
+ import "github.com/zokypesch/go-ga-lib/pkg/apierror" // ok — pkg is public
145
+ ```
146
+
147
+ ### DO NOT
148
+
149
+ ```
150
+ // WRONG: trying to import internal/ from another module
151
+ import "github.com/zokypesch/go-ga-lib/internal/domain" // compile error
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Rule 3 — `domain/` has zero external imports
157
+
158
+ `domain/` defines the vocabulary of the service: structs, interfaces, and
159
+ sentinel errors. It MUST import nothing outside the Go standard library. This
160
+ makes the domain portable and prevents import cycles.
161
+
162
+ ### DO
163
+
164
+ ```go
165
+ // internal/domain/user.go
166
+ package domain
167
+
168
+ import "time" // stdlib only
169
+
170
+ type User struct {
171
+ ID string
172
+ Email string
173
+ Name string
174
+ CreatedAt time.Time
175
+ }
176
+
177
+ // UserRepository is the storage contract — implemented in repository/.
178
+ type UserRepository interface {
179
+ FindByID(ctx context.Context, id string) (*User, error)
180
+ Create(ctx context.Context, u *User) error
181
+ Update(ctx context.Context, u *User) error
182
+ Delete(ctx context.Context, id string) error
183
+ }
184
+
185
+ // UserUsecase is the business contract — implemented in usecase/.
186
+ type UserUsecase interface {
187
+ Get(ctx context.Context, id string) (*User, error)
188
+ Create(ctx context.Context, input CreateUserInput) (*User, error)
189
+ }
190
+ ```
191
+
192
+ ```go
193
+ // internal/domain/errors.go
194
+ package domain
195
+
196
+ import "errors"
197
+
198
+ var (
199
+ ErrNotFound = errors.New("not found")
200
+ ErrAlreadyExists = errors.New("already exists")
201
+ ErrInvalidInput = errors.New("invalid input")
202
+ ErrUnauthorized = errors.New("unauthorized")
203
+ )
204
+ ```
205
+
206
+ ### DO NOT
207
+
208
+ ```go
209
+ // internal/domain/user.go — WRONG: external import in domain
210
+ package domain
211
+
212
+ import (
213
+ "gorm.io/gorm" // WRONG: ORM in domain
214
+ "github.com/google/uuid" // WRONG: external lib in domain
215
+ )
216
+
217
+ type User struct {
218
+ gorm.Model // WRONG: ORM base struct in domain
219
+ ID uuid.UUID
220
+ }
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Rule 4 — Handlers NEVER call repositories directly
226
+
227
+ The dependency flow is strict and unidirectional:
228
+
229
+ ```
230
+ handler → usecase → repository
231
+ ```
232
+
233
+ Handlers know about usecases. Usecases know about repositories. Nothing flows
234
+ backwards. Handlers NEVER import `repository/`.
235
+
236
+ ### DO
237
+
238
+ ```go
239
+ // internal/handler/user_handler.go
240
+ package handler
241
+
242
+ import "github.com/zokypesch/go-ga-lib/internal/domain"
243
+
244
+ type UserHandler struct {
245
+ uc domain.UserUsecase // depends on the interface, not the concrete type
246
+ logger *zap.Logger
247
+ }
248
+
249
+ func NewUserHandler(uc domain.UserUsecase, logger *zap.Logger) *UserHandler {
250
+ return &UserHandler{uc: uc, logger: logger}
251
+ }
252
+
253
+ func (h *UserHandler) Get(w http.ResponseWriter, r *http.Request) {
254
+ id := chi.URLParam(r, "id")
255
+ user, err := h.uc.Get(r.Context(), id) // calls usecase, not repository
256
+ if err != nil {
257
+ respondError(w, err)
258
+ return
259
+ }
260
+ respondJSON(w, http.StatusOK, user)
261
+ }
262
+ ```
263
+
264
+ ### DO NOT
265
+
266
+ ```go
267
+ // internal/handler/user_handler.go — WRONG: handler calls repository
268
+ package handler
269
+
270
+ import "github.com/zokypesch/go-ga-lib/internal/repository" // WRONG
271
+
272
+ type UserHandler struct {
273
+ repo *repository.UserRepository // WRONG: skip the usecase layer
274
+ }
275
+
276
+ func (h *UserHandler) Get(w http.ResponseWriter, r *http.Request) {
277
+ id := chi.URLParam(r, "id")
278
+ user, _ := h.repo.FindByID(r.Context(), id) // WRONG: bypasses business logic
279
+ json.NewEncoder(w).Encode(user)
280
+ }
281
+ ```
282
+
283
+ ---
284
+
285
+ ## Dependency Direction Summary
286
+
287
+ ```
288
+ cmd/server/main.go
289
+ └── internal/handler (imports domain interfaces)
290
+ └── internal/usecase (imports domain interfaces)
291
+ └── internal/repository (imports domain structs)
292
+ └── internal/domain (imports stdlib only)
293
+ ```
294
+
295
+ Breaking this chain in any direction is a FAIL in code review.
296
+
297
+ ---
298
+
299
+ ## File Naming Conventions
300
+
301
+ | Concern | File name |
302
+ |---------|-----------|
303
+ | HTTP handlers for User | `internal/handler/user_handler.go` |
304
+ | Usecase for User | `internal/usecase/user_usecase.go` |
305
+ | Repository for User | `internal/repository/user_repository.go` |
306
+ | Domain types for User | `internal/domain/user.go` |
307
+ | Sentinel errors | `internal/domain/errors.go` |
308
+ | Config struct | `config/config.go` |
309
+ | Test for usecase | `internal/usecase/user_usecase_test.go` |
310
+
311
+ One file per major type. Do not create `utils.go`, `helpers.go`, or
312
+ `common.go` files — if code is shared, put it in a purpose-named file or
313
+ in `pkg/`.
@@ -0,0 +1,366 @@
1
+ # Testing Standard
2
+
3
+ Tests are first-class citizens. Every usecase and repository must have unit
4
+ tests. Handlers get integration tests. The default pattern is table-driven.
5
+
6
+ ---
7
+
8
+ ## Rule 1 — Table-Driven Tests as Default
9
+
10
+ Table-driven tests make it easy to add cases without duplicating test
11
+ scaffolding and produce clear failure messages via `t.Run`.
12
+
13
+ ```go
14
+ // internal/usecase/user_usecase_test.go
15
+ package usecase_test
16
+
17
+ import (
18
+ "context"
19
+ "errors"
20
+ "testing"
21
+
22
+ "github.com/stretchr/testify/assert"
23
+ "github.com/stretchr/testify/require"
24
+ "go.uber.org/zap/zaptest"
25
+
26
+ "github.com/zokypesch/go-ga-lib/internal/domain"
27
+ "github.com/zokypesch/go-ga-lib/internal/usecase"
28
+ )
29
+
30
+ func TestUserUsecase_Create(t *testing.T) {
31
+ cases := []struct {
32
+ name string
33
+ input domain.CreateUserInput
34
+ repoErr error // error the mock repo returns
35
+ want *domain.User
36
+ wantErr error // sentinel error the usecase should wrap
37
+ }{
38
+ {
39
+ name: "valid input creates user",
40
+ input: domain.CreateUserInput{Email: "alice@example.com", Name: "Alice"},
41
+ want: &domain.User{ID: "u1", Email: "alice@example.com", Name: "Alice"},
42
+ },
43
+ {
44
+ name: "missing email returns invalid input",
45
+ input: domain.CreateUserInput{Name: "Alice"},
46
+ wantErr: domain.ErrInvalidInput,
47
+ },
48
+ {
49
+ name: "duplicate email returns already exists",
50
+ input: domain.CreateUserInput{Email: "alice@example.com", Name: "Alice"},
51
+ repoErr: domain.ErrAlreadyExists,
52
+ wantErr: domain.ErrAlreadyExists,
53
+ },
54
+ {
55
+ name: "repo error propagates",
56
+ input: domain.CreateUserInput{Email: "bob@example.com", Name: "Bob"},
57
+ repoErr: errors.New("connection refused"),
58
+ wantErr: errors.New("connection refused"), // non-sentinel: check message
59
+ },
60
+ }
61
+
62
+ for _, tc := range cases {
63
+ t.Run(tc.name, func(t *testing.T) {
64
+ t.Parallel() // safe — each test case uses its own mock
65
+
66
+ repo := &mockUserRepository{createErr: tc.repoErr, createUser: tc.want}
67
+ logger := zaptest.NewLogger(t)
68
+ uc := usecase.NewUserUsecase(repo, logger)
69
+
70
+ got, err := uc.Create(context.Background(), tc.input)
71
+
72
+ if tc.wantErr != nil {
73
+ require.Error(t, err)
74
+ if errors.Is(tc.wantErr, domain.ErrInvalidInput) ||
75
+ errors.Is(tc.wantErr, domain.ErrAlreadyExists) {
76
+ assert.ErrorIs(t, err, tc.wantErr) // sentinel: use errors.Is
77
+ } else {
78
+ assert.ErrorContains(t, err, tc.wantErr.Error()) // infra: check message
79
+ }
80
+ assert.Nil(t, got)
81
+ return
82
+ }
83
+
84
+ require.NoError(t, err)
85
+ assert.Equal(t, tc.want.Email, got.Email)
86
+ assert.Equal(t, tc.want.Name, got.Name)
87
+ })
88
+ }
89
+ }
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Rule 2 — File Naming
95
+
96
+ | Scenario | Package declaration | File name |
97
+ |----------|---------------------|-----------|
98
+ | White-box (access unexported symbols) | `package usecase` | `user_usecase_test.go` |
99
+ | Black-box (test exported API only) | `package usecase_test` | `user_usecase_test.go` |
100
+ | Integration test | `package usecase_test` | `user_usecase_integration_test.go` |
101
+
102
+ Prefer black-box tests (`_test` package suffix) — they test the public
103
+ contract and catch accidental leaks of internal details.
104
+
105
+ ---
106
+
107
+ ## Rule 3 — Use testify/assert and testify/require
108
+
109
+ - `require.*` — stops the test immediately on failure (use for preconditions)
110
+ - `assert.*` — records failure but continues (use for post-conditions)
111
+
112
+ ```go
113
+ import (
114
+ "github.com/stretchr/testify/assert"
115
+ "github.com/stretchr/testify/require"
116
+ )
117
+
118
+ // require for setup / preconditions
119
+ user, err := uc.Create(ctx, input)
120
+ require.NoError(t, err) // stop if creation failed — rest of test is meaningless
121
+
122
+ // assert for post-conditions — collect all failures
123
+ assert.Equal(t, "alice@example.com", user.Email)
124
+ assert.NotEmpty(t, user.ID)
125
+ assert.False(t, user.CreatedAt.IsZero())
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Rule 4 — Interfaces Enable Mocking
131
+
132
+ All dependencies are injected as interfaces. Mocks are hand-written structs
133
+ (or generated with `mockery`) that implement the interface.
134
+
135
+ ```go
136
+ // internal/usecase/user_usecase_test.go — hand-written mock
137
+ type mockUserRepository struct {
138
+ createUser *domain.User
139
+ createErr error
140
+ findUser *domain.User
141
+ findErr error
142
+ calls []string // optional: record calls for assertion
143
+ }
144
+
145
+ func (m *mockUserRepository) Create(_ context.Context, u *domain.User) (*domain.User, error) {
146
+ m.calls = append(m.calls, "Create")
147
+ if m.createErr != nil {
148
+ return nil, m.createErr
149
+ }
150
+ if m.createUser != nil {
151
+ return m.createUser, nil
152
+ }
153
+ return u, nil
154
+ }
155
+
156
+ func (m *mockUserRepository) FindByID(_ context.Context, id string) (*domain.User, error) {
157
+ m.calls = append(m.calls, "FindByID")
158
+ return m.findUser, m.findErr
159
+ }
160
+ ```
161
+
162
+ For complex mocks, use `github.com/vektra/mockery` to generate:
163
+ ```bash
164
+ go generate ./...
165
+ // File: internal/domain/user.go
166
+ //go:generate mockery --name=UserRepository --output=../mocks --outpkg=mocks
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Rule 5 — Integration Tests with Build Tags
172
+
173
+ Integration tests require real infrastructure (DB, cache, external APIs).
174
+ Gate them behind a build tag so `go test ./...` only runs unit tests in CI
175
+ by default.
176
+
177
+ ```go
178
+ // internal/repository/user_repository_integration_test.go
179
+ //go:build integration
180
+
181
+ package repository_test
182
+
183
+ import (
184
+ "context"
185
+ "database/sql"
186
+ "testing"
187
+
188
+ _ "github.com/lib/pq"
189
+ "github.com/stretchr/testify/assert"
190
+ "github.com/stretchr/testify/require"
191
+
192
+ "github.com/zokypesch/go-ga-lib/internal/domain"
193
+ "github.com/zokypesch/go-ga-lib/internal/repository"
194
+ )
195
+
196
+ func TestUserRepository_Create_Integration(t *testing.T) {
197
+ db, err := sql.Open("postgres", testDSN(t))
198
+ require.NoError(t, err)
199
+ t.Cleanup(func() { db.Close() })
200
+
201
+ repo := repository.NewUserRepository(db)
202
+
203
+ user, err := repo.Create(context.Background(), &domain.User{
204
+ Email: "integration@example.com",
205
+ Name: "Integration User",
206
+ })
207
+ require.NoError(t, err)
208
+ assert.NotEmpty(t, user.ID)
209
+ }
210
+
211
+ // testDSN reads the test DB connection string from env or t.Skip if absent
212
+ func testDSN(t *testing.T) string {
213
+ t.Helper()
214
+ dsn := os.Getenv("TEST_DB_DSN")
215
+ if dsn == "" {
216
+ t.Skip("TEST_DB_DSN not set — skipping integration test")
217
+ }
218
+ return dsn
219
+ }
220
+ ```
221
+
222
+ Run integration tests explicitly:
223
+ ```bash
224
+ go test -tags=integration ./internal/repository/...
225
+ ```
226
+
227
+ ---
228
+
229
+ ## Rule 6 — No `time.Sleep` in Tests
230
+
231
+ Sleep makes tests slow and flaky. Use channels, context timeouts, or
232
+ testify's `Eventually` helper to wait for asynchronous conditions.
233
+
234
+ ### DO
235
+
236
+ ```go
237
+ // Waiting for an async result with a channel
238
+ func TestWorker_ProcessesMessage(t *testing.T) {
239
+ processed := make(chan string, 1)
240
+ worker := NewWorker(func(msg string) {
241
+ processed <- msg
242
+ })
243
+ worker.Start(context.Background())
244
+
245
+ worker.Send("hello")
246
+
247
+ select {
248
+ case msg := <-processed:
249
+ assert.Equal(t, "hello", msg)
250
+ case <-time.After(2 * time.Second):
251
+ t.Fatal("timed out waiting for message to be processed")
252
+ }
253
+ }
254
+
255
+ // Polling with testify Eventually for eventually-consistent state
256
+ assert.Eventually(t,
257
+ func() bool { return cache.Has("key") },
258
+ 2*time.Second, // max wait
259
+ 10*time.Millisecond, // poll interval
260
+ "cache never populated",
261
+ )
262
+ ```
263
+
264
+ ### DO NOT
265
+
266
+ ```go
267
+ worker.Start(context.Background())
268
+ worker.Send("hello")
269
+ time.Sleep(500 * time.Millisecond) // WRONG: flaky, slow, arbitrary
270
+ assert.True(t, worker.Processed("hello"))
271
+ ```
272
+
273
+ ---
274
+
275
+ ## Rule 7 — Handler Tests use `httptest`
276
+
277
+ Test handlers with the standard `net/http/httptest` package. No need to
278
+ start a real server.
279
+
280
+ ```go
281
+ // internal/handler/user_handler_test.go
282
+ package handler_test
283
+
284
+ import (
285
+ "bytes"
286
+ "encoding/json"
287
+ "net/http"
288
+ "net/http/httptest"
289
+ "testing"
290
+
291
+ "github.com/stretchr/testify/assert"
292
+ "github.com/stretchr/testify/require"
293
+ "go.uber.org/zap/zaptest"
294
+
295
+ "github.com/zokypesch/go-ga-lib/internal/domain"
296
+ "github.com/zokypesch/go-ga-lib/internal/handler"
297
+ )
298
+
299
+ func TestUserHandler_Create(t *testing.T) {
300
+ cases := []struct {
301
+ name string
302
+ body any
303
+ ucErr error
304
+ ucUser *domain.User
305
+ wantStatus int
306
+ wantCode string // error code in JSON response
307
+ }{
308
+ {
309
+ name: "creates user — 201",
310
+ body: map[string]string{"email": "a@b.com", "name": "A"},
311
+ ucUser: &domain.User{ID: "u1", Email: "a@b.com"},
312
+ wantStatus: http.StatusCreated,
313
+ },
314
+ {
315
+ name: "invalid input — 400",
316
+ body: map[string]string{"name": "A"}, // missing email
317
+ ucErr: domain.ErrInvalidInput,
318
+ wantStatus: http.StatusBadRequest,
319
+ wantCode: "BAD_REQUEST",
320
+ },
321
+ {
322
+ name: "duplicate — 409",
323
+ body: map[string]string{"email": "a@b.com", "name": "A"},
324
+ ucErr: domain.ErrAlreadyExists,
325
+ wantStatus: http.StatusConflict,
326
+ wantCode: "CONFLICT",
327
+ },
328
+ }
329
+
330
+ for _, tc := range cases {
331
+ t.Run(tc.name, func(t *testing.T) {
332
+ bodyBytes, err := json.Marshal(tc.body)
333
+ require.NoError(t, err)
334
+
335
+ uc := &mockUserUsecase{createUser: tc.ucUser, createErr: tc.ucErr}
336
+ h := handler.NewUserHandler(uc, zaptest.NewLogger(t))
337
+
338
+ req := httptest.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewReader(bodyBytes))
339
+ req.Header.Set("Content-Type", "application/json")
340
+ rr := httptest.NewRecorder()
341
+
342
+ h.Create(rr, req)
343
+
344
+ assert.Equal(t, tc.wantStatus, rr.Code)
345
+ if tc.wantCode != "" {
346
+ var resp map[string]string
347
+ require.NoError(t, json.NewDecoder(rr.Body).Decode(&resp))
348
+ assert.Equal(t, tc.wantCode, resp["code"])
349
+ }
350
+ })
351
+ }
352
+ }
353
+ ```
354
+
355
+ ---
356
+
357
+ ## Test Coverage Targets
358
+
359
+ | Layer | Minimum coverage |
360
+ |-------|-----------------|
361
+ | `domain/` | 100% (pure logic, no I/O) |
362
+ | `usecase/` | 90% |
363
+ | `handler/` | 80% |
364
+ | `repository/` | covered by integration tests |
365
+
366
+ Run with: `go test -cover ./...`
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "java2go-porter",
3
+ "version": "0.1.0",
4
+ "description": "Java→Go translation accelerator (DRAFT only — requires human review + green tests). Runs analyzer→translator→test-pairer→reviewer pipeline.",
5
+ "commands": [
6
+ {
7
+ "name": "port-java-service",
8
+ "description": "Translate a Java/Spring Boot service to a Go draft using chi + go-ga-lib",
9
+ "path": "commands/port-java-service.md"
10
+ }
11
+ ],
12
+ "agents": [
13
+ { "name": "analyzer", "path": "agents/analyzer.md" },
14
+ { "name": "translator", "path": "agents/translator.md" },
15
+ { "name": "test-pairer", "path": "agents/test-pairer.md" },
16
+ { "name": "reviewer", "path": "agents/reviewer.md" }
17
+ ],
18
+ "skills": [
19
+ { "name": "idiom-mapping", "path": "skills/idiom-mapping.md" }
20
+ ]
21
+ }