# Coding Guidelines

## Tech Stack

| Component | Required | Forbidden |
|-----------|----------|-----------|
| HTTP Router | chi/v5 | gin, echo, fiber |
| Database | sqlx | GORM, raw sql |
| Logging | slog | log, logrus, zap |
| Config | Viper + env vars | os.Getenv directly |
| Testing | testing + testify | ginkgo, gomega |
| Kubernetes | client-go | kubectl subprocess |

## File Structure

```
cmd/{service}/         # Entry points
internal/              # Private code (hexagonal)
├── domain/            # Pure models, no deps
├── port/              # Interfaces only
├── service/           # Business logic
├── handlers/          # HTTP handlers
├── adapter/           # Infrastructure
├── auth/              # Authentication
├── middleware/        # HTTP middleware
├── worker/            # Background jobs
└── webhook/           # Event dispatch
pkg/                   # Public packages
```

## Handler Pattern

All handlers implement the `Mount` interface and use `pkg/api` for responses:

```go
type UsersHandler struct {
    service *service.UserService
}

func (h *UsersHandler) Mount(r chi.Router) {
    r.Route("/users", func(r chi.Router) {
        r.Get("/", h.List)
        r.Post("/", h.Create)
        r.Get("/{id}", h.Get)
    })
}

func (h *UsersHandler) Create(w http.ResponseWriter, r *http.Request) {
    var req CreateUserRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        api.BadRequest(w, "invalid request body")
        return
    }

    user, err := h.service.Create(r.Context(), req)
    if err != nil {
        api.Error(w, err)
        return
    }

    api.Created(w, user)
}
```

## Response Helpers (`pkg/api`)

```go
api.OK(w, data)           // 200 with JSON body
api.Created(w, data)      // 201 with JSON body
api.NoContent(w)          // 204
api.BadRequest(w, msg)    // 400
api.Unauthorized(w, msg)  // 401
api.Forbidden(w, msg)     // 403
api.NotFound(w, msg)      // 404
api.Conflict(w, msg)      // 409
api.Error(w, err)         // 500 (or mapped status)
```

## Port/Adapter Pattern

Domain interfaces in `internal/port/`:

```go
// port/project.go
type ProjectRepository interface {
    List(ctx context.Context) ([]domain.Project, error)
    Get(ctx context.Context, id string) (*domain.Project, error)
}

type CommandExecutor interface {
    Execute(ctx context.Context, project string, cmd domain.Command) (*domain.CommandResult, error)
}
```

Implementations in `internal/adapter/{name}/`:

```go
// adapter/kubernetes/repository.go
type Repository struct {
    client kubernetes.Interface
}

func (r *Repository) List(ctx context.Context) ([]domain.Project, error) {
    // Implementation using client-go
}
```

## Domain Models

Pure structs in `internal/domain/`, NO external dependencies:

```go
// domain/project.go
package domain

type Project struct {
    ID        string
    Name      string
    Status    ProjectStatus
    Workspace string
}

type ProjectStatus string

const (
    StatusRunning  ProjectStatus = "running"
    StatusPending  ProjectStatus = "pending"
    StatusFailed   ProjectStatus = "failed"
)
```

## Error Handling

Use wrapped errors with context:

```go
if err != nil {
    return fmt.Errorf("create project %s: %w", name, err)
}
```

Define domain errors in `internal/domain/errors.go`:

```go
var (
    ErrNotFound     = errors.New("not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrConflict     = errors.New("already exists")
)
```

## Testing

- Unit tests next to source: `foo_test.go`
- Table-driven tests preferred
- Mocks implement port interfaces
- Test files in `testutil/` for shared helpers

```go
func TestHandler_Create(t *testing.T) {
    tests := []struct {
        name       string
        input      CreateRequest
        wantStatus int
    }{
        {"valid", CreateRequest{Name: "test"}, 201},
        {"empty name", CreateRequest{}, 400},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            // ...
        })
    }
}
```

## Naming Conventions

- Files: `snake_case.go`
- Packages: `lowercase` (no underscores)
- Types: `PascalCase`
- Functions/Methods: `PascalCase` (exported), `camelCase` (private)
- Constants: `PascalCase` or `SCREAMING_SNAKE` for env vars
- Interfaces: Don't prefix with `I`, suffix with role (`Repository`, `Service`)

## Size Limits

- **500 lines max per file** - split when exceeded
- **100 lines max per function** - extract helpers
- **5 parameters max per function** - use struct if more
