ga-plugins-cli 0.1.0 → 0.1.1
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/dist/config-patcher.d.ts +20 -50
- package/dist/config-patcher.d.ts.map +1 -1
- package/dist/config-patcher.js +138 -102
- package/dist/config-patcher.js.map +1 -1
- package/dist/index.js +41 -5
- package/dist/index.js.map +1 -1
- package/dist/installer.d.ts +0 -18
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +19 -39
- package/dist/installer.js.map +1 -1
- package/dist/types.d.ts +10 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/uninstaller.d.ts +0 -23
- package/dist/uninstaller.d.ts.map +1 -1
- package/dist/uninstaller.js +22 -68
- package/dist/uninstaller.js.map +1 -1
- package/package.json +3 -2
- package/plugins/go-reviewer/.claude-plugin/plugin.json +12 -0
- package/plugins/go-reviewer/commands/go-review.md +424 -0
- package/plugins/go-reviewer/mcp-servers/go-reviewer-mcp/README.md +236 -0
- package/plugins/go-reviewer/mcp-servers/go-reviewer-mcp/main.go +678 -0
- package/plugins/go-scaffolder/.claude-plugin/plugin.json +12 -0
- package/plugins/go-scaffolder/commands/scaffold-service.md +802 -0
- package/plugins/go-scaffolder/reference-service/.env.example +27 -0
- package/plugins/go-scaffolder/reference-service/Dockerfile +55 -0
- package/plugins/go-scaffolder/reference-service/REFERENCE-SERVICE-NOTICE.md +104 -0
- package/plugins/go-scaffolder/reference-service/cmd/server/main.go +266 -0
- package/plugins/go-scaffolder/reference-service/config/config.go +67 -0
- package/plugins/go-scaffolder/reference-service/go.mod +17 -0
- package/plugins/go-scaffolder/reference-service/internal/domain/booking.go +118 -0
- package/plugins/go-scaffolder/reference-service/internal/handler/booking.go +242 -0
- package/plugins/go-scaffolder/reference-service/internal/handler/booking_test.go +451 -0
- package/plugins/go-scaffolder/reference-service/internal/repository/booking_postgres.go +124 -0
- package/plugins/go-scaffolder/reference-service/internal/usecase/booking.go +181 -0
- package/plugins/go-standards/.claude-plugin/plugin.json +22 -0
- package/plugins/go-standards/commands/go-standards-check.md +232 -0
- package/plugins/go-standards/skills/concurrency.md +336 -0
- package/plugins/go-standards/skills/config.md +267 -0
- package/plugins/go-standards/skills/error-handling.md +286 -0
- package/plugins/go-standards/skills/http-chi.md +390 -0
- package/plugins/go-standards/skills/logging-observability.md +340 -0
- package/plugins/go-standards/skills/naming-and-style.md +315 -0
- package/plugins/go-standards/skills/project-layout.md +313 -0
- package/plugins/go-standards/skills/testing.md +366 -0
- package/plugins/java2go-porter/.claude-plugin/plugin.json +21 -0
- package/plugins/java2go-porter/agents/analyzer.md +232 -0
- package/plugins/java2go-porter/agents/reviewer.md +241 -0
- package/plugins/java2go-porter/agents/test-pairer.md +365 -0
- package/plugins/java2go-porter/agents/translator.md +419 -0
- package/plugins/java2go-porter/commands/port-java-service.md +149 -0
- package/plugins/java2go-porter/skills/idiom-mapping.md +75 -0
- package/plugins/migration-safety/.claude-plugin/plugin.json +20 -0
- package/plugins/migration-safety/commands/gen-characterization-test.md +452 -0
- package/plugins/migration-safety/commands/strangler-plan.md +356 -0
- package/plugins/migration-safety/skills/strangler-fig.md +382 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
package repository
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"database/sql"
|
|
6
|
+
"errors"
|
|
7
|
+
"fmt"
|
|
8
|
+
|
|
9
|
+
"github.com/jmoiron/sqlx"
|
|
10
|
+
|
|
11
|
+
"github.com/zokypesch/booking-service/internal/domain"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// postgresBookingRepo implements domain.BookingRepository using PostgreSQL via sqlx.
|
|
15
|
+
// All methods accept a context.Context and propagate cancellation and deadlines to the DB.
|
|
16
|
+
type postgresBookingRepo struct {
|
|
17
|
+
db *sqlx.DB
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// NewPostgresBookingRepo constructs a BookingRepository backed by the given *sqlx.DB.
|
|
21
|
+
// db must not be nil.
|
|
22
|
+
func NewPostgresBookingRepo(db *sqlx.DB) domain.BookingRepository {
|
|
23
|
+
if db == nil {
|
|
24
|
+
panic("repository: db must not be nil")
|
|
25
|
+
}
|
|
26
|
+
return &postgresBookingRepo{db: db}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Create inserts a new booking row. The booking's ID must already be set by the caller.
|
|
30
|
+
func (r *postgresBookingRepo) Create(ctx context.Context, booking *domain.Booking) error {
|
|
31
|
+
query := `
|
|
32
|
+
INSERT INTO bookings (id, customer_id, flight_id, status, seat_count, total_price, created_at, updated_at)
|
|
33
|
+
VALUES (:id, :customer_id, :flight_id, :status, :seat_count, :total_price, :created_at, :updated_at)`
|
|
34
|
+
|
|
35
|
+
_, err := r.db.NamedExecContext(ctx, query, booking)
|
|
36
|
+
if err != nil {
|
|
37
|
+
return fmt.Errorf("repository.Create: %w", err)
|
|
38
|
+
}
|
|
39
|
+
return nil
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// GetByID retrieves a single booking by primary key.
|
|
43
|
+
// Returns domain.ErrBookingNotFound when no matching row exists.
|
|
44
|
+
func (r *postgresBookingRepo) GetByID(ctx context.Context, id string) (*domain.Booking, error) {
|
|
45
|
+
query := `
|
|
46
|
+
SELECT id, customer_id, flight_id, status, seat_count, total_price, created_at, updated_at
|
|
47
|
+
FROM bookings
|
|
48
|
+
WHERE id = $1`
|
|
49
|
+
|
|
50
|
+
booking := &domain.Booking{}
|
|
51
|
+
err := r.db.GetContext(ctx, booking, query, id)
|
|
52
|
+
if err != nil {
|
|
53
|
+
if errors.Is(err, sql.ErrNoRows) {
|
|
54
|
+
return nil, domain.ErrBookingNotFound
|
|
55
|
+
}
|
|
56
|
+
return nil, fmt.Errorf("repository.GetByID: %w", err)
|
|
57
|
+
}
|
|
58
|
+
return booking, nil
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Update replaces all mutable columns of an existing booking row.
|
|
62
|
+
// Returns domain.ErrBookingNotFound when no matching row exists.
|
|
63
|
+
func (r *postgresBookingRepo) Update(ctx context.Context, booking *domain.Booking) error {
|
|
64
|
+
query := `
|
|
65
|
+
UPDATE bookings
|
|
66
|
+
SET customer_id = :customer_id,
|
|
67
|
+
flight_id = :flight_id,
|
|
68
|
+
status = :status,
|
|
69
|
+
seat_count = :seat_count,
|
|
70
|
+
total_price = :total_price,
|
|
71
|
+
updated_at = :updated_at
|
|
72
|
+
WHERE id = :id`
|
|
73
|
+
|
|
74
|
+
result, err := r.db.NamedExecContext(ctx, query, booking)
|
|
75
|
+
if err != nil {
|
|
76
|
+
return fmt.Errorf("repository.Update: %w", err)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
rows, err := result.RowsAffected()
|
|
80
|
+
if err != nil {
|
|
81
|
+
return fmt.Errorf("repository.Update rows affected: %w", err)
|
|
82
|
+
}
|
|
83
|
+
if rows == 0 {
|
|
84
|
+
return domain.ErrBookingNotFound
|
|
85
|
+
}
|
|
86
|
+
return nil
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// List returns a paginated slice of bookings ordered by created_at descending.
|
|
90
|
+
func (r *postgresBookingRepo) List(ctx context.Context, limit, offset int) ([]*domain.Booking, error) {
|
|
91
|
+
query := `
|
|
92
|
+
SELECT id, customer_id, flight_id, status, seat_count, total_price, created_at, updated_at
|
|
93
|
+
FROM bookings
|
|
94
|
+
ORDER BY created_at DESC
|
|
95
|
+
LIMIT $1
|
|
96
|
+
OFFSET $2`
|
|
97
|
+
|
|
98
|
+
bookings := []*domain.Booking{}
|
|
99
|
+
err := r.db.SelectContext(ctx, &bookings, query, limit, offset)
|
|
100
|
+
if err != nil {
|
|
101
|
+
return nil, fmt.Errorf("repository.List: %w", err)
|
|
102
|
+
}
|
|
103
|
+
return bookings, nil
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Delete permanently removes a booking row.
|
|
107
|
+
// Returns domain.ErrBookingNotFound when no matching row exists.
|
|
108
|
+
func (r *postgresBookingRepo) Delete(ctx context.Context, id string) error {
|
|
109
|
+
query := `DELETE FROM bookings WHERE id = $1`
|
|
110
|
+
|
|
111
|
+
result, err := r.db.ExecContext(ctx, query, id)
|
|
112
|
+
if err != nil {
|
|
113
|
+
return fmt.Errorf("repository.Delete: %w", err)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
rows, err := result.RowsAffected()
|
|
117
|
+
if err != nil {
|
|
118
|
+
return fmt.Errorf("repository.Delete rows affected: %w", err)
|
|
119
|
+
}
|
|
120
|
+
if rows == 0 {
|
|
121
|
+
return domain.ErrBookingNotFound
|
|
122
|
+
}
|
|
123
|
+
return nil
|
|
124
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
package usecase
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"time"
|
|
7
|
+
|
|
8
|
+
"github.com/google/uuid"
|
|
9
|
+
"go.uber.org/zap"
|
|
10
|
+
|
|
11
|
+
"github.com/zokypesch/booking-service/internal/domain"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
type bookingUsecase struct {
|
|
15
|
+
repo domain.BookingRepository
|
|
16
|
+
logger *zap.Logger
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// NewBookingUsecase constructs a BookingUsecase backed by the given repository.
|
|
20
|
+
// Both repo and logger are required; panics if either is nil.
|
|
21
|
+
func NewBookingUsecase(repo domain.BookingRepository, logger *zap.Logger) domain.BookingUsecase {
|
|
22
|
+
if repo == nil {
|
|
23
|
+
panic("usecase: repo must not be nil")
|
|
24
|
+
}
|
|
25
|
+
if logger == nil {
|
|
26
|
+
panic("usecase: logger must not be nil")
|
|
27
|
+
}
|
|
28
|
+
return &bookingUsecase{repo: repo, logger: logger}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// CreateBooking validates input, assigns a new UUID, and persists a PENDING booking.
|
|
32
|
+
func (uc *bookingUsecase) CreateBooking(ctx context.Context, input domain.CreateBookingInput) (*domain.Booking, error) {
|
|
33
|
+
if err := uc.validateCreateInput(input); err != nil {
|
|
34
|
+
return nil, err // already wrapped as domain.ErrInvalidInput
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
now := time.Now().UTC()
|
|
38
|
+
booking := &domain.Booking{
|
|
39
|
+
ID: uuid.NewString(),
|
|
40
|
+
CustomerID: input.CustomerID,
|
|
41
|
+
FlightID: input.FlightID,
|
|
42
|
+
SeatCount: input.SeatCount,
|
|
43
|
+
TotalPrice: input.TotalPrice,
|
|
44
|
+
Status: domain.StatusPending,
|
|
45
|
+
CreatedAt: now,
|
|
46
|
+
UpdatedAt: now,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if err := uc.repo.Create(ctx, booking); err != nil {
|
|
50
|
+
return nil, fmt.Errorf("usecase.CreateBooking: %w", err)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
uc.logger.Info("booking created",
|
|
54
|
+
zap.String("booking_id", booking.ID),
|
|
55
|
+
zap.String("customer_id", booking.CustomerID),
|
|
56
|
+
zap.String("flight_id", booking.FlightID),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return booking, nil
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// GetBooking retrieves a single booking by its ID.
|
|
63
|
+
func (uc *bookingUsecase) GetBooking(ctx context.Context, id string) (*domain.Booking, error) {
|
|
64
|
+
if id == "" {
|
|
65
|
+
return nil, fmt.Errorf("%w: id is required", domain.ErrInvalidInput)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
booking, err := uc.repo.GetByID(ctx, id)
|
|
69
|
+
if err != nil {
|
|
70
|
+
return nil, fmt.Errorf("usecase.GetBooking: %w", err)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return booking, nil
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// UpdateBooking applies a partial update to a booking's mutable fields.
|
|
77
|
+
// Status transitions are NOT allowed through this method; use CancelBooking.
|
|
78
|
+
func (uc *bookingUsecase) UpdateBooking(ctx context.Context, id string, input domain.UpdateBookingInput) (*domain.Booking, error) {
|
|
79
|
+
if id == "" {
|
|
80
|
+
return nil, fmt.Errorf("%w: id is required", domain.ErrInvalidInput)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
booking, err := uc.repo.GetByID(ctx, id)
|
|
84
|
+
if err != nil {
|
|
85
|
+
return nil, fmt.Errorf("usecase.UpdateBooking: %w", err)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Apply only the fields provided in the input (pointer-based partial update).
|
|
89
|
+
if input.SeatCount != nil {
|
|
90
|
+
if *input.SeatCount < 1 {
|
|
91
|
+
return nil, fmt.Errorf("%w: seat_count must be at least 1", domain.ErrInvalidInput)
|
|
92
|
+
}
|
|
93
|
+
booking.SeatCount = *input.SeatCount
|
|
94
|
+
}
|
|
95
|
+
if input.TotalPrice != nil {
|
|
96
|
+
if *input.TotalPrice < 0 {
|
|
97
|
+
return nil, fmt.Errorf("%w: total_price must not be negative", domain.ErrInvalidInput)
|
|
98
|
+
}
|
|
99
|
+
booking.TotalPrice = *input.TotalPrice
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
booking.UpdatedAt = time.Now().UTC()
|
|
103
|
+
|
|
104
|
+
if err := uc.repo.Update(ctx, booking); err != nil {
|
|
105
|
+
return nil, fmt.Errorf("usecase.UpdateBooking: %w", err)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
uc.logger.Info("booking updated",
|
|
109
|
+
zap.String("booking_id", booking.ID),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return booking, nil
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ListBookings returns a paginated list of bookings.
|
|
116
|
+
// limit is capped at 100 to prevent unbounded queries.
|
|
117
|
+
func (uc *bookingUsecase) ListBookings(ctx context.Context, limit, offset int) ([]*domain.Booking, error) {
|
|
118
|
+
const maxLimit = 100
|
|
119
|
+
if limit <= 0 || limit > maxLimit {
|
|
120
|
+
limit = maxLimit
|
|
121
|
+
}
|
|
122
|
+
if offset < 0 {
|
|
123
|
+
offset = 0
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
bookings, err := uc.repo.List(ctx, limit, offset)
|
|
127
|
+
if err != nil {
|
|
128
|
+
return nil, fmt.Errorf("usecase.ListBookings: %w", err)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return bookings, nil
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// CancelBooking transitions a booking from its current status to CANCELLED.
|
|
135
|
+
// Returns domain.ErrInvalidStatus if the transition is not allowed.
|
|
136
|
+
func (uc *bookingUsecase) CancelBooking(ctx context.Context, id string) (*domain.Booking, error) {
|
|
137
|
+
if id == "" {
|
|
138
|
+
return nil, fmt.Errorf("%w: id is required", domain.ErrInvalidInput)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
booking, err := uc.repo.GetByID(ctx, id)
|
|
142
|
+
if err != nil {
|
|
143
|
+
return nil, fmt.Errorf("usecase.CancelBooking: %w", err)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if !booking.Status.CanTransitionTo(domain.StatusCancelled) {
|
|
147
|
+
return nil, fmt.Errorf("%w: cannot cancel a booking with status %s",
|
|
148
|
+
domain.ErrInvalidStatus, booking.Status)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
booking.Status = domain.StatusCancelled
|
|
152
|
+
booking.UpdatedAt = time.Now().UTC()
|
|
153
|
+
|
|
154
|
+
if err := uc.repo.Update(ctx, booking); err != nil {
|
|
155
|
+
return nil, fmt.Errorf("usecase.CancelBooking: %w", err)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
uc.logger.Info("booking cancelled",
|
|
159
|
+
zap.String("booking_id", booking.ID),
|
|
160
|
+
zap.String("customer_id", booking.CustomerID),
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return booking, nil
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// validateCreateInput checks that all required fields are present and valid.
|
|
167
|
+
func (uc *bookingUsecase) validateCreateInput(input domain.CreateBookingInput) error {
|
|
168
|
+
if input.CustomerID == "" {
|
|
169
|
+
return fmt.Errorf("%w: customer_id is required", domain.ErrInvalidInput)
|
|
170
|
+
}
|
|
171
|
+
if input.FlightID == "" {
|
|
172
|
+
return fmt.Errorf("%w: flight_id is required", domain.ErrInvalidInput)
|
|
173
|
+
}
|
|
174
|
+
if input.SeatCount < 1 {
|
|
175
|
+
return fmt.Errorf("%w: seat_count must be at least 1", domain.ErrInvalidInput)
|
|
176
|
+
}
|
|
177
|
+
if input.TotalPrice < 0 {
|
|
178
|
+
return fmt.Errorf("%w: total_price must not be negative", domain.ErrInvalidInput)
|
|
179
|
+
}
|
|
180
|
+
return nil
|
|
181
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "go-standards",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Single source of truth for Go coding standards across all 30 migrated services.",
|
|
5
|
+
"skills": [
|
|
6
|
+
{ "name": "project-layout", "path": "skills/project-layout.md" },
|
|
7
|
+
{ "name": "error-handling", "path": "skills/error-handling.md" },
|
|
8
|
+
{ "name": "http-chi", "path": "skills/http-chi.md" },
|
|
9
|
+
{ "name": "config", "path": "skills/config.md" },
|
|
10
|
+
{ "name": "logging-observability", "path": "skills/logging-observability.md" },
|
|
11
|
+
{ "name": "concurrency", "path": "skills/concurrency.md" },
|
|
12
|
+
{ "name": "testing", "path": "skills/testing.md" },
|
|
13
|
+
{ "name": "naming-and-style", "path": "skills/naming-and-style.md" }
|
|
14
|
+
],
|
|
15
|
+
"commands": [
|
|
16
|
+
{
|
|
17
|
+
"name": "go-standards-check",
|
|
18
|
+
"description": "Assess a Go file against GA coding standards",
|
|
19
|
+
"path": "commands/go-standards-check.md"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# Command: go-standards-check
|
|
2
|
+
|
|
3
|
+
Assess a Go source file against the GA coding standards for all 30 migrated
|
|
4
|
+
services. Produces a structured checklist report with PASS / WARN / FAIL per
|
|
5
|
+
category and actionable line-level feedback.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
/go-standards-check [path/to/file.go]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
If no path is provided as an argument, ask the user:
|
|
16
|
+
|
|
17
|
+
> Which Go file would you like me to assess? Please provide the absolute or
|
|
18
|
+
> relative path.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Instructions for Claude Code
|
|
23
|
+
|
|
24
|
+
When this command is invoked, follow these steps exactly:
|
|
25
|
+
|
|
26
|
+
### Step 1 — Resolve the target file
|
|
27
|
+
|
|
28
|
+
If the user provided a path as an argument, use it directly.
|
|
29
|
+
If not, prompt the user for a path before proceeding.
|
|
30
|
+
|
|
31
|
+
### Step 2 — Read the file
|
|
32
|
+
|
|
33
|
+
Read the entire contents of the specified `.go` file using the Read tool.
|
|
34
|
+
Also scan for related files that might be relevant to the assessment:
|
|
35
|
+
- The directory listing of the file's parent folder (to check project layout)
|
|
36
|
+
- Any sibling `*_test.go` file for the same type (to check test coverage)
|
|
37
|
+
|
|
38
|
+
### Step 3 — Assess against each of the 8 standard categories
|
|
39
|
+
|
|
40
|
+
For each category below, apply the described checks and assign a rating:
|
|
41
|
+
|
|
42
|
+
- **PASS** — fully compliant, no issues found
|
|
43
|
+
- **WARN** — minor deviation; does not break the standard but should be addressed
|
|
44
|
+
- **FAIL** — clear violation of the standard; must be fixed before merge
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
#### Category 1: Project Layout
|
|
49
|
+
|
|
50
|
+
Checks:
|
|
51
|
+
- Is the file in the correct directory for its role?
|
|
52
|
+
- Handlers → `internal/handler/`
|
|
53
|
+
- Usecases → `internal/usecase/`
|
|
54
|
+
- Repositories → `internal/repository/`
|
|
55
|
+
- Domain types/errors → `internal/domain/`
|
|
56
|
+
- Config → `config/`
|
|
57
|
+
- Entrypoint → `cmd/server/main.go`
|
|
58
|
+
- Does `main.go` contain any business logic (queries, HTTP handler logic, if/else on domain data)?
|
|
59
|
+
- Does a handler file import `repository/` directly (bypassing usecase)?
|
|
60
|
+
- Does a domain file import any non-stdlib package?
|
|
61
|
+
|
|
62
|
+
#### Category 2: Error Handling
|
|
63
|
+
|
|
64
|
+
Checks:
|
|
65
|
+
- Are there any `panic()` calls outside of `main.go` init code?
|
|
66
|
+
- Are errors wrapped with `%w` (not `%v` or `errors.New` around another error)?
|
|
67
|
+
- Are sentinel errors defined in `domain/errors.go` and used for expected cases?
|
|
68
|
+
- Is there any function that both logs an error AND returns it (log-and-return anti-pattern)?
|
|
69
|
+
- Are `sql.ErrNoRows` (and similar infra errors) mapped to domain sentinels at the repository layer?
|
|
70
|
+
- Does any error message start with a capital letter or end with a period or exclamation mark?
|
|
71
|
+
- Are there any ignored errors (`_ = someFunc()` on error-returning calls that are not intentional)?
|
|
72
|
+
|
|
73
|
+
#### Category 3: HTTP / chi Conventions
|
|
74
|
+
|
|
75
|
+
Checks:
|
|
76
|
+
- Does every handler method have the signature `func (h *XHandler) Method(w http.ResponseWriter, r *http.Request)`?
|
|
77
|
+
- Is `json.NewDecoder(r.Body).Decode()` called without a `MaxBytesReader` wrapper?
|
|
78
|
+
- Are response helpers (`respondJSON`, `respondError`) used consistently, or is `json.NewEncoder(w).Encode()` called ad hoc?
|
|
79
|
+
- Is the error response body in the form `{"error": "...", "code": "..."}` with a machine-readable code?
|
|
80
|
+
- Are string-keyed context values used (instead of typed keys)?
|
|
81
|
+
- Does any handler contain business logic (validation rules, calculations, data transformation beyond HTTP concerns)?
|
|
82
|
+
- Are URL parameters extracted via `chi.URLParam(r, "id")` (not `r.FormValue` or manual path splitting)?
|
|
83
|
+
- Are `/health` and `/ready` endpoints present and unprotected by auth middleware?
|
|
84
|
+
|
|
85
|
+
#### Category 4: Config Management
|
|
86
|
+
|
|
87
|
+
Checks:
|
|
88
|
+
- Are there any calls to `os.Getenv()` outside of `config/config.go`?
|
|
89
|
+
- Are there any hardcoded hostnames, ports, credentials, or API keys?
|
|
90
|
+
- Is a `Config` struct present with env tags?
|
|
91
|
+
- Is the config loaded once and passed to constructors (not re-read in middleware or handlers)?
|
|
92
|
+
- Are secrets (passwords, tokens) included in any log call or error message?
|
|
93
|
+
- Is there a `.env.example` in the service root? (Check via directory listing if possible.)
|
|
94
|
+
- Does the `Config` struct have a safe `String()` method that omits secrets?
|
|
95
|
+
|
|
96
|
+
#### Category 5: Logging and Observability
|
|
97
|
+
|
|
98
|
+
Checks:
|
|
99
|
+
- Are there any `fmt.Println`, `fmt.Printf`, `log.Println`, or `log.Printf` calls in non-main code?
|
|
100
|
+
- Are all log calls using structured fields (`zap.String(...)`, `zap.Error(...)`, etc.) rather than interpolated strings?
|
|
101
|
+
- Is `zap.Error(err)` always included when logging an error-level event?
|
|
102
|
+
- Are trace IDs or request IDs included in error-level log calls?
|
|
103
|
+
- Are Prometheus metrics registered with `promauto` (package-level vars) rather than inside functions?
|
|
104
|
+
- Are metric names following the pattern `<service>_<subsystem>_<metric>_<unit>`?
|
|
105
|
+
- Are OpenTelemetry spans created for usecase operations with `.Start()` / `defer span.End()`?
|
|
106
|
+
- Are errors recorded on spans via `span.RecordError(err)`?
|
|
107
|
+
|
|
108
|
+
#### Category 6: Concurrency Safety
|
|
109
|
+
|
|
110
|
+
Checks:
|
|
111
|
+
- Does every function that performs I/O accept `context.Context` as its first argument?
|
|
112
|
+
- Is there any `context.WithTimeout` or `context.WithCancel` where the cancel function is not called (or discarded with `_`)?
|
|
113
|
+
- Are there any goroutines launched without a clear termination mechanism (no `ctx.Done()`, no done channel)?
|
|
114
|
+
- Is any goroutine launched inside an HTTP handler using `r.Context()` without `context.WithoutCancel`?
|
|
115
|
+
- Are shared maps or slices accessed from multiple goroutines without `sync.Mutex` or `sync.RWMutex`?
|
|
116
|
+
- Is `sync.WaitGroup.Add()` called outside the goroutine (correctly)?
|
|
117
|
+
- Are loop variables passed to goroutines via explicit capture (`u := u`)?
|
|
118
|
+
|
|
119
|
+
#### Category 7: Testing Patterns
|
|
120
|
+
|
|
121
|
+
Checks (assess the paired `_test.go` file if it exists; if absent, flag):
|
|
122
|
+
- Is there a `_test.go` file for this file?
|
|
123
|
+
- Are tests table-driven (slice of test cases + `t.Run`)?
|
|
124
|
+
- Is `testify/assert` and `testify/require` used (not bare `t.Errorf`)?
|
|
125
|
+
- Are dependencies injected as interfaces so they can be mocked?
|
|
126
|
+
- Is `time.Sleep` called in any test?
|
|
127
|
+
- Are integration tests guarded with `//go:build integration`?
|
|
128
|
+
- Is `t.Parallel()` called in table-driven sub-tests where safe?
|
|
129
|
+
- Are handler tests using `httptest.NewRecorder` and `httptest.NewRequest`?
|
|
130
|
+
|
|
131
|
+
#### Category 8: Naming and Style
|
|
132
|
+
|
|
133
|
+
Checks:
|
|
134
|
+
- Are package names lowercase, single-word, no underscores?
|
|
135
|
+
- Are there any `Manager`, `Helper`, `Util`, `Abstract`, `Base`, or `Impl` suffixes on exported types?
|
|
136
|
+
- Do interface names follow the `er` suffix (single method) or capability-noun (multi-method) convention?
|
|
137
|
+
- Are receiver names short and consistent (not `self`, not `this`)?
|
|
138
|
+
- Are constructors named `NewX`?
|
|
139
|
+
- Are there getters named `GetFoo()` or setters named `SetFoo()`?
|
|
140
|
+
- Is the `I` prefix used on any interface name?
|
|
141
|
+
- Are there any `utils.go`, `helpers.go`, `common.go`, or `models.go` files visible in the directory?
|
|
142
|
+
- Do error messages follow lowercase, no trailing period convention?
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### Step 4 — Output the Report
|
|
147
|
+
|
|
148
|
+
Format the report exactly as shown below. Fill in findings with specific
|
|
149
|
+
line numbers wherever possible.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
## go-standards-check: <filename>
|
|
155
|
+
|
|
156
|
+
Assessed: <absolute file path>
|
|
157
|
+
Companion test file: <path or "NOT FOUND — test file missing">
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
### [PASS|WARN|FAIL] 1. Project Layout
|
|
162
|
+
<One-line summary>
|
|
163
|
+
- Line N: <specific finding, or "No issues found.">
|
|
164
|
+
|
|
165
|
+
### [PASS|WARN|FAIL] 2. Error Handling
|
|
166
|
+
<One-line summary>
|
|
167
|
+
- Line N: <specific finding>
|
|
168
|
+
|
|
169
|
+
### [PASS|WARN|FAIL] 3. HTTP / chi Conventions
|
|
170
|
+
<One-line summary>
|
|
171
|
+
- Line N: <specific finding>
|
|
172
|
+
|
|
173
|
+
### [PASS|WARN|FAIL] 4. Config Management
|
|
174
|
+
<One-line summary>
|
|
175
|
+
- Line N: <specific finding>
|
|
176
|
+
|
|
177
|
+
### [PASS|WARN|FAIL] 5. Logging and Observability
|
|
178
|
+
<One-line summary>
|
|
179
|
+
- Line N: <specific finding>
|
|
180
|
+
|
|
181
|
+
### [PASS|WARN|FAIL] 6. Concurrency Safety
|
|
182
|
+
<One-line summary>
|
|
183
|
+
- Line N: <specific finding>
|
|
184
|
+
|
|
185
|
+
### [PASS|WARN|FAIL] 7. Testing Patterns
|
|
186
|
+
<One-line summary>
|
|
187
|
+
- Line N: <specific finding or note about missing test file>
|
|
188
|
+
|
|
189
|
+
### [PASS|WARN|FAIL] 8. Naming and Style
|
|
190
|
+
<One-line summary>
|
|
191
|
+
- Line N: <specific finding>
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
### Summary
|
|
196
|
+
|
|
197
|
+
| Category | Rating |
|
|
198
|
+
|-----------------------|--------|
|
|
199
|
+
| Project Layout | PASS |
|
|
200
|
+
| Error Handling | FAIL |
|
|
201
|
+
| HTTP / chi | WARN |
|
|
202
|
+
| Config Management | PASS |
|
|
203
|
+
| Logging/Observability | WARN |
|
|
204
|
+
| Concurrency Safety | PASS |
|
|
205
|
+
| Testing Patterns | FAIL |
|
|
206
|
+
| Naming and Style | PASS |
|
|
207
|
+
|
|
208
|
+
**Overall: <PASS if all PASS | WARN if any WARN and no FAIL | FAIL if any FAIL>**
|
|
209
|
+
|
|
210
|
+
### Required Changes Before Merge
|
|
211
|
+
<Numbered list of FAIL items only. If none, write "None — ready to merge.">
|
|
212
|
+
1. ...
|
|
213
|
+
2. ...
|
|
214
|
+
|
|
215
|
+
### Recommended Improvements
|
|
216
|
+
<Numbered list of WARN items. If none, omit this section.>
|
|
217
|
+
1. ...
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Behavior Notes for Claude Code
|
|
223
|
+
|
|
224
|
+
- Be precise with line numbers — read the file carefully before assigning ratings.
|
|
225
|
+
- If a category is not applicable to the file type (e.g., HTTP checks on a domain struct file),
|
|
226
|
+
mark it PASS with a note: "N/A — domain-only file, no HTTP concerns."
|
|
227
|
+
- If the paired test file is missing, always mark Category 7 as FAIL.
|
|
228
|
+
- Do not fabricate findings. If a check cannot be determined from the file alone
|
|
229
|
+
(e.g., whether Prometheus metrics are registered elsewhere), note the limitation:
|
|
230
|
+
"Cannot verify — metric registration not visible in this file."
|
|
231
|
+
- After the report, offer to fix any FAIL items:
|
|
232
|
+
> Would you like me to fix the FAIL items now?
|