# Gin Mode Config Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Move Gin mode selection out of `internal/api.NewRouter` hardcoding and into project configuration. **Architecture:** Add a dedicated `GinConfig` to `pkg/config`, validate Gin mode during `config.Load`, pass `cfg.Gin.Mode` from `cmd/server/main.go` into `api.NewRouter`, and use that value in `gin.SetMode`. Router tests should pass `gin.TestMode` explicitly. **Tech Stack:** Go 1.25, Gin, existing YAML config via `gopkg.in/yaml.v3`, existing Gin + slog backend. ## Global Constraints - Do not hardcode `gin.SetMode(gin.ReleaseMode)` in router code. - Add a dedicated `gin.mode` config block; do not put Gin mode under `server`. - Supported Gin modes are exactly `debug`, `release`, and `test`. - Default Gin mode is `release`. - `CODESPACE_GIN_MODE` must override YAML/default config. - Invalid Gin mode must fail during `config.Load`. - Do not change API paths, methods, response JSON, middleware behavior, service interfaces, or logging policy except for passing Gin mode into router construction. - All application logs must continue to use `log/slog`; do not add `fmt.Print*`, `log.Printf`, `gin.Default`, or `gin.Logger`. - Do not add dependencies. - Code generation/refactoring must be delegated to Codex MCP using exactly `model: "kimi/kimi-k2.7-code"`, `sandbox: "danger-full-access"`, and `approval-policy: "on-failure"`. - Do not commit unless the user explicitly asks. --- ## File Structure ### Modify - `configs/config.yaml` — add `gin.mode: "release"`. - `pkg/config/config.go` — add `GinConfig`, default, YAML/env load, validation. - `pkg/config/config_test.go` — add default/YAML/env/invalid mode tests. - `internal/api/router.go` — change `NewRouter` signature to accept `ginMode string` and call `gin.SetMode(ginMode)`. - `internal/api/router_test.go` — pass `gin.TestMode` into `NewRouter`. - `cmd/server/main.go` — pass `cfg.Gin.Mode` into `api.NewRouter`. - `README.md` — document `gin.mode` and `CODESPACE_GIN_MODE`. --- ## Task 1: Add Gin mode config and validation **Files:** - Modify: `pkg/config/config.go` - Modify: `pkg/config/config_test.go` - Modify: `configs/config.yaml` **Interfaces:** - Produces: `type GinConfig struct { Mode string `yaml:"mode"` }`. - Produces: `Config.Gin GinConfig`. - Produces: `CODESPACE_GIN_MODE` env override. - Produces: config-load validation for `debug|release|test`. - [ ] **Step 1: Add failing config tests** Add tests to `pkg/config/config_test.go`: ```go func TestDefaultIncludesGinConfig(t *testing.T) { cfg := Default() if cfg.Gin.Mode != "release" { t.Fatalf("Gin.Mode = %q, want release", cfg.Gin.Mode) } } func TestLoadParsesGinConfigFromYAML(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "config.yaml") data := []byte(`gin: mode: "debug" `) if err := os.WriteFile(path, data, 0o644); err != nil { t.Fatal(err) } cfg, err := Load(path) if err != nil { t.Fatalf("Load returned error: %v", err) } if cfg.Gin.Mode != "debug" { t.Fatalf("Gin.Mode = %q, want debug", cfg.Gin.Mode) } } func TestLoadAppliesGinModeEnvironmentOverride(t *testing.T) { t.Setenv("CODESPACE_GIN_MODE", "test") cfg, err := Load(filepath.Join(t.TempDir(), "missing.yaml")) if err != nil { t.Fatalf("Load returned error: %v", err) } if cfg.Gin.Mode != "test" { t.Fatalf("Gin.Mode = %q, want test", cfg.Gin.Mode) } } func TestLoadRejectsInvalidGinMode(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "config.yaml") data := []byte(`gin: mode: "production" `) if err := os.WriteFile(path, data, 0o644); err != nil { t.Fatal(err) } if _, err := Load(path); err == nil { t.Fatal("expected invalid gin mode error") } } ``` - [ ] **Step 2: Implement config fields and validation** Update `pkg/config/config.go`: ```go type Config struct { Server ServerConfig `yaml:"server"` Workspace WorkspaceConfig `yaml:"workspace"` Process ProcessConfig `yaml:"process"` File FileConfig `yaml:"file"` Log LogConfig `yaml:"log"` Gin GinConfig `yaml:"gin"` } type GinConfig struct { Mode string `yaml:"mode"` } ``` Update `rawConfig` with `Gin GinConfig`. Default: ```go Gin: GinConfig{Mode: "release"}, ``` Raw/env handling: ```go if raw.Gin.Mode != "" { cfg.Gin.Mode = raw.Gin.Mode } if v := os.Getenv("CODESPACE_GIN_MODE"); v != "" { cfg.Gin.Mode = v } ``` Add validation after env overrides, before returning from `Load`: ```go func validateGinMode(mode string) error { switch mode { case "debug", "release", "test": return nil default: return fmt.Errorf("invalid gin mode %q", mode) } } ``` Call it from `Load`: ```go if err := validateGinMode(cfg.Gin.Mode); err != nil { return Config{}, err } ``` - [ ] **Step 3: Update config YAML** Add to `configs/config.yaml`: ```yaml gin: mode: "release" ``` - [ ] **Step 4: Verify config package** Run: ```sh gofmt -w pkg/config go test ./pkg/config ``` Expected: config tests pass. --- ## Task 2: Pass Gin mode into router **Files:** - Modify: `internal/api/router.go` - Modify: `internal/api/router_test.go` - Modify: `cmd/server/main.go` **Interfaces:** - Changes: `api.NewRouter(workspaces, files, processes, lg, ginMode string) *gin.Engine`. - Consumes: `cfg.Gin.Mode` from main. - Tests consume: `gin.TestMode`. - [ ] **Step 1: Update router signature** Modify `internal/api/router.go`: ```go func NewRouter(workspaces *service.WorkspaceService, files *service.FileService, processes *service.ProcessService, lg *slog.Logger, ginMode string) *gin.Engine { gin.SetMode(ginMode) r := gin.New() ... } ``` Remove `gin.SetMode(gin.ReleaseMode)`. - [ ] **Step 2: Update main wiring** Modify `cmd/server/main.go`: ```go router := api.NewRouter(workspaceSvc, fileSvc, processSvc, lg, cfg.Gin.Mode) ``` - [ ] **Step 3: Update router tests** Modify `internal/api/router_test.go`: ```go return NewRouter(wsSvc, fileSvc, procSvc, nil, gin.TestMode) ``` Add Gin import if needed: ```go "github.com/gin-gonic/gin" ``` - [ ] **Step 4: Verify API and server compile** Run: ```sh gofmt -w cmd/server internal/api go test ./internal/api ./cmd/server ``` Expected: tests/compile pass. --- ## Task 3: Documentation and final validation **Files:** - Modify: `README.md` **Interfaces:** - Documents `gin.mode` and `CODESPACE_GIN_MODE`. - [ ] **Step 1: Update README** Add a short config note: ```markdown ### Gin mode Gin mode is configured through `config.yaml`: ```yaml gin: mode: "release" ``` Supported values: `debug`, `release`, `test`. Environment override: - `CODESPACE_GIN_MODE` ``` - [ ] **Step 2: Run final validation** Run: ```sh gofmt -w cmd internal pkg go mod tidy go test ./... grep -R "gin.SetMode(gin.ReleaseMode)" cmd internal pkg || true grep -R "gin\.Default\|gin\.Logger" cmd internal pkg || true grep -R "log\.Printf\|log\.Println\|log\.Fatal\|fmt\.Print" cmd internal pkg || true ``` Expected: - Tests pass. - No hardcoded `gin.SetMode(gin.ReleaseMode)`. - No `gin.Default` / `gin.Logger`. - No disallowed runtime logging. - [ ] **Step 3: Server smoke test** Run: ```sh (go run ./cmd/server > /tmp/codespace-server.log 2>&1 & pid=$!; \ for i in $(seq 1 30); do \ if curl -fsS http://localhost:8080/healthz; then \ kill $pid; wait $pid 2>/dev/null || true; exit 0; \ fi; \ sleep 0.2; \ done; \ kill $pid 2>/dev/null || true; \ wait $pid 2>/dev/null || true; \ cat /tmp/codespace-server.log; exit 1) ``` Expected: ```json {"status":"ok"} ``` --- ## Codex Execution Prompt Use this exact structure when dispatching Codex: ```text ## Context - Project root: /Users/taochen/llm/codespace - Current branch: feat/gin-mode-config - Go baseline: 1.25 - Existing backend uses Gin, explicit http.Server, slog logging, config.yaml. - Design spec: docs/superpowers/specs/2026-07-02-gin-mode-config-design.md - Implementation plan: docs/superpowers/plans/2026-07-02-gin-mode-config.md ## Task Move Gin mode from hardcoded `gin.SetMode(gin.ReleaseMode)` into config. Steps: 1. Add `gin.mode` to config.yaml. 2. Add `config.GinConfig` with default `release`. 3. Support `CODESPACE_GIN_MODE` override. 4. Validate allowed modes: `debug`, `release`, `test` during `config.Load`. 5. Change `api.NewRouter` to accept `ginMode string` and call `gin.SetMode(ginMode)`. 6. Pass `cfg.Gin.Mode` from main and `gin.TestMode` from tests. 7. Update README and run validation. ## Constraints - Do not change API paths, methods, responses, services, middleware behavior, or logging strategy. - Do not add dependencies. - Do not use gin.Default or gin.Logger. - Do not add disallowed runtime logging. - Do not commit. ## Acceptance - gofmt -w cmd internal pkg succeeds. - go mod tidy succeeds. - go test ./... passes. - grep for `gin.SetMode(gin.ReleaseMode)` returns no output. - go run ./cmd/server starts and /healthz returns {"status":"ok"}. ``` --- ## Self-Review - Spec coverage: Plan covers config field, default, YAML/env load, validation, router signature, main wiring, tests, docs, and validation. - Placeholder scan: No TBD/TODO/ambiguous implementation steps remain. - Type consistency: `GinConfig`, `Config.Gin`, `cfg.Gin.Mode`, and `api.NewRouter(..., ginMode string)` are consistent. - Scope check: Plan is limited to Gin mode configuration. It does not change APIs, middleware behavior, services, logging strategy, or dependencies.