This repository has been archived on 2026-07-17. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
codespace/docs/superpowers/plans/2026-07-02-gin-mode-config.md
T
2026-07-02 17:46:04 +08:00

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.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:

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.Mode from 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.mode and CODESPACE_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, 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.