9.6 KiB
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.modeconfig block; do not put Gin mode underserver. - Supported Gin modes are exactly
debug,release, andtest. - Default Gin mode is
release. CODESPACE_GIN_MODEmust 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 addfmt.Print*,log.Printf,gin.Default, orgin.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", andapproval-policy: "on-failure". - Do not commit unless the user explicitly asks.
File Structure
Modify
configs/config.yaml— addgin.mode: "release".pkg/config/config.go— addGinConfig, default, YAML/env load, validation.pkg/config/config_test.go— add default/YAML/env/invalid mode tests.internal/api/router.go— changeNewRoutersignature to acceptginMode stringand callgin.SetMode(ginMode).internal/api/router_test.go— passgin.TestModeintoNewRouter.cmd/server/main.go— passcfg.Gin.Modeintoapi.NewRouter.README.md— documentgin.modeandCODESPACE_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 stringyaml:"mode"}. -
Produces:
Config.Gin GinConfig. -
Produces:
CODESPACE_GIN_MODEenv override. -
Produces: config-load validation for
debug|release|test. -
Step 1: Add failing config tests
Add tests to pkg/config/config_test.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:
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:
Gin: GinConfig{Mode: "release"},
Raw/env handling:
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:
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:
if err := validateGinMode(cfg.Gin.Mode); err != nil { return Config{}, err }
- Step 3: Update config YAML
Add to configs/config.yaml:
gin:
mode: "release"
- Step 4: Verify config package
Run:
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.Modefrom main. -
Tests consume:
gin.TestMode. -
Step 1: Update router signature
Modify internal/api/router.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:
router := api.NewRouter(workspaceSvc, fileSvc, processSvc, lg, cfg.Gin.Mode)
- Step 3: Update router tests
Modify internal/api/router_test.go:
return NewRouter(wsSvc, fileSvc, procSvc, nil, gin.TestMode)
Add Gin import if needed:
"github.com/gin-gonic/gin"
- Step 4: Verify API and server compile
Run:
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.modeandCODESPACE_GIN_MODE. -
Step 1: Update README
Add a short config note:
### 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:
(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:
{"status":"ok"}
Codex Execution Prompt
Use this exact structure when dispatching Codex:
## 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, andapi.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.