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

367 lines
9.6 KiB
Markdown

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