# codespace Backend Skeleton 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:** Build a runnable Go backend MVP in the current project root that manages workspaces, file operations through an abstract filesystem, and OpenCode processes. **Architecture:** HTTP handlers decode requests and delegate to service objects. Services coordinate `workspace.Manager`, `fs.FileSystem`, and `process.Manager`. Local filesystem and OpenCode process execution are implementation details hidden behind focused interfaces. **Tech Stack:** Go 1.22+, standard library HTTP router, YAML config via `gopkg.in/yaml.v3`, local filesystem, OS processes. ## Global Constraints - Current directory `/Users/taochen/llm/codespace` is the project root; do not create a nested `codespace/` directory. - First version is a runnable backend MVP, not a complete IDE. - Do not add Docker. - Do not implement authentication, Git integration, LSP, AI Agent orchestration, web frontend, or full watcher/WebSocket event streaming. - Handlers must not call `os.*` file APIs or `exec.*`; file and process work goes through services. - File API paths are workspace-relative; never expose host absolute paths in file responses. - Workspace IDs allow only letters, digits, `-`, `_`, and `.`. - Prevent `../` and absolute-path escape from workspace roots. - Process command defaults to `opencode`, but must be configurable. - Current directory is not a git repository, so this plan does not include commit steps unless the user initializes git separately. - 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"`. --- ## File Structure ### Create - `go.mod` — module declaration and dependencies. - `Makefile` — local development commands. - `README.md` — project overview, API examples, run/test instructions. - `configs/config.yaml` — default runtime config. - `cmd/server/main.go` — app entrypoint, config load, dependency wiring, HTTP server start. - `pkg/config/config.go` — config structs and load logic. - `pkg/response/response.go` — consistent JSON success/error responses. - `pkg/logger/logger.go` — tiny standard-library logger wrapper. - `internal/util/path.go` — reusable path validation helpers. - `internal/util/errors.go` — typed app errors and helpers. - `internal/workspace/workspace.go` — `Workspace` model. - `internal/workspace/path.go` — workspace ID validation and root path construction. - `internal/workspace/manager.go` — local workspace manager implementation. - `internal/workspace/path_test.go` — workspace ID/path tests. - `internal/fs/fileinfo.go` — file metadata model. - `internal/fs/filesystem.go` — `FileSystem` interface. - `internal/fs/localfs.go` — local filesystem implementation bound to one workspace root. - `internal/fs/localfs_test.go` — path escape and file operation tests. - `internal/process/session.go` — session/status structs. - `internal/process/manager.go` — process manager interface and implementation. - `internal/process/opencode.go` — OpenCode command/session creation details. - `internal/process/manager_test.go` — status/start/stop tests with safe test command. - `internal/model/workspace.go` — workspace API request/response structs. - `internal/model/file.go` — file API request/response structs. - `internal/model/process.go` — process API response structs. - `internal/service/workspace_service.go` — workspace use cases. - `internal/service/file_service.go` — file use cases. - `internal/service/process_service.go` — process use cases. - `internal/api/router.go` — HTTP router and route registration. - `internal/api/workspace_handler.go` — workspace endpoints. - `internal/api/file_handler.go` — file endpoints. - `internal/api/process_handler.go` — process endpoints. - `internal/api/router_test.go` — health/workspace/file/process route smoke tests. - `internal/watcher/watcher.go` — narrow watcher interface placeholder. - `web/.gitkeep` — keeps frontend placeholder directory. - `docs/superpowers/specs/2026-07-02-codespace-design.md` — already created design spec. - `docs/superpowers/plans/2026-07-02-codespace-backend-skeleton.md` — this plan. ### External dependency Only one non-stdlib dependency is allowed for the first version: ```text gopkg.in/yaml.v3 ``` Everything else must use the Go standard library. --- ## Task 1: Initialize Go module, config, responses, and docs **Files:** - Create: `go.mod` - Create: `Makefile` - Create: `README.md` - Create: `configs/config.yaml` - Create: `pkg/config/config.go` - Create: `pkg/response/response.go` - Create: `pkg/logger/logger.go` - Create: `web/.gitkeep` **Interfaces:** - Produces: `config.Config`, `config.Load(path string) (Config, error)`. - Produces: `response.JSON(w http.ResponseWriter, status int, value any)`. - Produces: `response.Error(w http.ResponseWriter, status int, code string, err error)`. - Produces: `logger.New() *log.Logger`. - [ ] **Step 1: Create module and default config** Create `go.mod`: ```go module codespace go 1.22 require gopkg.in/yaml.v3 v3.0.1 ``` Create `configs/config.yaml`: ```yaml server: addr: ":8080" workspace: root: "./workspaces" process: opencodeCommand: "opencode" ``` Create `web/.gitkeep` as an empty file. - [ ] **Step 2: Implement config loader** Create `pkg/config/config.go` with these exported names: ```go package config type Config struct { Server ServerConfig `yaml:"server"` Workspace WorkspaceConfig `yaml:"workspace"` Process ProcessConfig `yaml:"process"` } type ServerConfig struct { Addr string `yaml:"addr"` } type WorkspaceConfig struct { Root string `yaml:"root"` } type ProcessConfig struct { OpenCodeCommand string `yaml:"opencodeCommand"` } func Default() Config func Load(path string) (Config, error) ``` Required behavior: - `Default()` returns `:8080`, `./workspaces`, and `opencode`. - `Load(path)` starts from defaults. - If `path` exists, YAML values override defaults. - If `path` does not exist, return defaults and no error. - Environment variables override both defaults and file values: - `CODESPACE_ADDR` - `CODESPACE_WORKSPACE_ROOT` - `CODESPACE_OPENCODE_COMMAND` - [ ] **Step 3: Implement response helpers** Create `pkg/response/response.go` with this response shape: ```go type ErrorBody struct { Error ErrorDetail `json:"error"` } type ErrorDetail struct { Code string `json:"code"` Message string `json:"message"` } ``` Functions: ```go func JSON(w http.ResponseWriter, status int, value any) func Error(w http.ResponseWriter, status int, code string, err error) ``` Required behavior: - Always set `Content-Type: application/json`. - `JSON` writes the status code and JSON-encodes `value`. - `Error` writes `{"error":{"code":"...","message":"..."}}`. - [ ] **Step 4: Implement logger helper** Create `pkg/logger/logger.go`: ```go package logger func New() *log.Logger ``` Required behavior: - Use standard library `log.New(os.Stdout, "codespace ", log.LstdFlags|log.Lshortfile)`. - [ ] **Step 5: Create README and Makefile** Create `Makefile` with these targets: ```makefile .PHONY: test run fmt test: go test ./... run: go run ./cmd/server fmt: gofmt -w cmd internal pkg ``` Create `README.md` with: - Project purpose. - First-version scope. - `make test` and `make run` instructions. - Example curl for `POST /api/workspaces` and `GET /healthz`. - Explicit note that Docker/auth/Git/LSP/frontend are not implemented in v1. - [ ] **Step 6: Validate task** Run: ```sh go mod tidy go test ./... ``` Expected: - `go mod tidy` succeeds. - `go test ./...` succeeds or reports no test files for created packages. --- ## Task 2: Implement typed errors and workspace manager **Files:** - Create: `internal/util/errors.go` - Create: `internal/util/path.go` - Create: `internal/workspace/workspace.go` - Create: `internal/workspace/path.go` - Create: `internal/workspace/manager.go` - Create: `internal/workspace/path_test.go` **Interfaces:** - Produces: `util.AppError`, `util.Code`, `util.New(code Code, message string) error`. - Produces: `workspace.Workspace`. - Produces: `workspace.Manager` interface. - Produces: `workspace.LocalManager` implementation. - Produces: `workspace.ValidID(id string) bool`. - [ ] **Step 1: Write workspace path tests first** Create `internal/workspace/path_test.go` with tests covering: ```go func TestValidIDAcceptsSafeSegments(t *testing.T) func TestValidIDRejectsUnsafeSegments(t *testing.T) func TestRootForUsesConfiguredRoot(t *testing.T) ``` Cases: - valid: `user1`, `project-A`, `project_A`, `project.1` - invalid: empty string, `/tmp/x`, `../x`, `a/b`, `a b`, `中文`, `*` - `RootFor("/tmp/workspaces", "user1")` returns `/tmp/workspaces/user1` after filepath cleaning. - [ ] **Step 2: Implement typed errors** Create `internal/util/errors.go` with codes: ```go type Code string const ( CodeBadRequest Code = "bad_request" CodeNotFound Code = "not_found" CodeConflict Code = "conflict" CodeInternal Code = "internal" ) type AppError struct { Code Code Message string Err error } func (e *AppError) Error() string func (e *AppError) Unwrap() error func New(code Code, message string) error func Wrap(code Code, message string, err error) error func CodeOf(err error) Code ``` Required behavior: - `CodeOf(nil)` returns `CodeInternal`. - `CodeOf(err)` returns embedded `AppError.Code` if present. - Non-`AppError` defaults to `CodeInternal`. - [ ] **Step 3: Implement path helper** Create `internal/util/path.go` with: ```go func CleanRelative(path string) (string, error) ``` Required behavior: - Empty path and `.` return `.`. - Absolute paths are rejected with bad request. - Paths that clean to `..` or start with `../` are rejected with bad request. - Backslash should be normalized by filepath semantics on the current platform; do not implement Windows-specific policy in v1. - [ ] **Step 4: Implement workspace model and manager** Create `internal/workspace/workspace.go`: ```go type Workspace struct { ID string `json:"id"` Root string `json:"root"` } ``` Create `internal/workspace/path.go`: ```go func ValidID(id string) bool func RootFor(baseRoot, id string) string ``` Create `internal/workspace/manager.go`: ```go type Manager interface { Create(id string) (*Workspace, error) Get(id string) (*Workspace, error) Delete(id string) error } type LocalManager struct { root string } func NewLocalManager(root string) *LocalManager ``` Required behavior: - `NewLocalManager` stores an absolute, cleaned root if possible. - `Create` validates ID, creates the directory with `0755`, and returns workspace. - `Get` validates ID and returns not found if directory does not exist. - `Delete` validates ID and removes the workspace directory recursively. - Manager must never accept unsafe IDs. - [ ] **Step 5: Run focused tests** Run: ```sh go test ./internal/workspace ./internal/util ``` Expected: - All tests pass. --- ## Task 3: Implement FileSystem interface and LocalFS **Files:** - Create: `internal/fs/fileinfo.go` - Create: `internal/fs/filesystem.go` - Create: `internal/fs/localfs.go` - Create: `internal/fs/localfs_test.go` **Interfaces:** - Produces: `fs.FileInfo`. - Produces: `fs.FileSystem`. - Produces: `fs.NewLocal(root string) FileSystem`. - [ ] **Step 1: Write LocalFS tests first** Create `internal/fs/localfs_test.go` with tests: ```go func TestLocalFSWriteReadListStat(t *testing.T) func TestLocalFSRejectsPathEscape(t *testing.T) func TestLocalFSRenameAndRemove(t *testing.T) ``` Required assertions: - Write `dir/main.go` with content `package main\n`. - Read returns exact bytes. - List `dir` returns one item named `main.go` with path `dir/main.go` and `IsDir == false`. - Stat `dir/main.go` returns size equal to content length. - Read `../outside`, Write `../outside`, Mkdir `../outside`, Remove `../outside`, Rename `file` to `../outside` all fail. - Rename `old.txt` to `new.txt` makes `old.txt` missing and `new.txt` readable. - [ ] **Step 2: Define file models and interface** Create `internal/fs/fileinfo.go`: ```go type FileInfo struct { Name string `json:"name"` Path string `json:"path"` IsDir bool `json:"isDir"` Size int64 `json:"size"` ModTime time.Time `json:"modTime"` } ``` Create `internal/fs/filesystem.go`: ```go type FileSystem interface { List(path string) ([]FileInfo, error) Read(path string) ([]byte, error) Write(path string, data []byte) error Mkdir(path string) error Remove(path string) error Rename(oldPath, newPath string) error Stat(path string) (*FileInfo, error) } ``` - [ ] **Step 3: Implement LocalFS safely** Create `internal/fs/localfs.go` with: ```go type LocalFS struct { root string } func NewLocal(root string) *LocalFS ``` Required behavior: - Store root as an absolute cleaned path if possible. - A private `resolve(path string) (absolute string, relative string, error)` must call `util.CleanRelative` and ensure the final absolute path stays under `root`. - `Write` creates parent directories with `0755` and writes file with `0644`. - `Mkdir` creates directories with `0755`. - `List` returns sorted entries by name for deterministic tests. - `Read`, `List`, and `Stat` map missing files to `CodeNotFound`. - `Remove` removes files or directories recursively. - `Rename` creates destination parent directories and rejects destination escape. - [ ] **Step 4: Run focused tests** Run: ```sh go test ./internal/fs ./internal/util ``` Expected: - All tests pass. --- ## Task 4: Implement process manager for OpenCode sessions **Files:** - Create: `internal/process/session.go` - Create: `internal/process/manager.go` - Create: `internal/process/opencode.go` - Create: `internal/process/manager_test.go` **Interfaces:** - Produces: `process.Status`. - Produces: `process.Manager` interface. - Produces: `process.NewManager(command string) *LocalManager`. - [ ] **Step 1: Write process manager tests first** Create `internal/process/manager_test.go` with tests: ```go func TestStatusBeforeStartIsNotRunning(t *testing.T) func TestStartRejectsDuplicateRunningSession(t *testing.T) func TestStopMissingSessionIsNotFound(t *testing.T) ``` Use a safe long-running command: ```go cmd := os.Args[0] args := []string{"-test.run=TestHelperProcess", "--"} ``` And helper pattern: ```go func TestHelperProcess(t *testing.T) { if os.Getenv("CODESPACE_HELPER_PROCESS") != "1" { return } select {} } ``` The implementation must allow tests to override command and args, either through a constructor option or an unexported test helper in the same package. - [ ] **Step 2: Define session/status structs** Create `internal/process/session.go`: ```go type Status struct { WorkspaceID string `json:"workspaceId"` Running bool `json:"running"` PID int `json:"pid,omitempty"` } type Session struct { WorkspaceID string Root string Cmd *exec.Cmd } ``` - [ ] **Step 3: Define manager interface and implementation** Create `internal/process/manager.go`: ```go type Manager interface { Start(workspaceID string, workspaceRoot string) error Stop(workspaceID string) error Restart(workspaceID string, workspaceRoot string) error Status(workspaceID string) Status } type LocalManager struct { command string args []string mu sync.Mutex sessions map[string]*Session } func NewManager(command string) *LocalManager ``` Required behavior: - Empty command defaults to `opencode`. - `Start` rejects duplicate running session with `CodeConflict`. - `Start` uses `exec.Command(command, args...)`. - `Start` sets `cmd.Dir = workspaceRoot`. - `Start` appends `HOME=` to environment. - `Start` starts the process and stores session by workspace ID. - `Stop` returns not found if no running session exists. - `Stop` kills the process and removes the session. - `Restart` calls `Stop` if running, then `Start`. - `Status` returns running and PID only if a session exists and process is started. - [ ] **Step 4: Implement OpenCode command helper** Create `internal/process/opencode.go` with small helpers only: ```go const DefaultOpenCodeCommand = "opencode" func normalizeCommand(command string) string ``` - [ ] **Step 5: Run focused tests** Run: ```sh go test ./internal/process ``` Expected: - All tests pass. - No real `opencode` binary is required for tests. --- ## Task 5: Implement services and API models **Files:** - Create: `internal/model/workspace.go` - Create: `internal/model/file.go` - Create: `internal/model/process.go` - Create: `internal/service/workspace_service.go` - Create: `internal/service/file_service.go` - Create: `internal/service/process_service.go` **Interfaces:** - Produces: model request/response structs used by handlers. - Produces: `service.WorkspaceService`. - Produces: `service.FileService`. - Produces: `service.ProcessService`. - [ ] **Step 1: Create API models** Create `internal/model/workspace.go`: ```go type CreateWorkspaceRequest struct { ID string `json:"id"` } type WorkspaceResponse struct { ID string `json:"id"` } ``` Do not include host root path in API responses. Create `internal/model/file.go`: ```go type WriteFileRequest struct { Path string `json:"path"` Content string `json:"content"` } type MkdirRequest struct { Path string `json:"path"` } type RenameRequest struct { OldPath string `json:"oldPath"` NewPath string `json:"newPath"` } type ReadFileResponse struct { Path string `json:"path"` Content string `json:"content"` } ``` Create `internal/model/process.go`: ```go type ProcessStatusResponse struct { WorkspaceID string `json:"workspaceId"` Running bool `json:"running"` PID int `json:"pid,omitempty"` } ``` - [ ] **Step 2: Implement WorkspaceService** Create `internal/service/workspace_service.go`: ```go type WorkspaceService struct { workspaces workspace.Manager processes process.Manager } func NewWorkspaceService(workspaces workspace.Manager, processes process.Manager) *WorkspaceService func (s *WorkspaceService) Create(id string) (*workspace.Workspace, error) func (s *WorkspaceService) Get(id string) (*workspace.Workspace, error) func (s *WorkspaceService) Delete(id string) error ``` Required behavior: - `Delete` checks process status and stops it if running before deleting workspace. - [ ] **Step 3: Implement FileService** Create `internal/service/file_service.go`: ```go type FileService struct { workspaces workspace.Manager } func NewFileService(workspaces workspace.Manager) *FileService func (s *FileService) List(workspaceID, path string) ([]fs.FileInfo, error) func (s *FileService) Read(workspaceID, path string) ([]byte, error) func (s *FileService) Write(workspaceID, path string, data []byte) error func (s *FileService) Mkdir(workspaceID, path string) error func (s *FileService) Remove(workspaceID, path string) error func (s *FileService) Rename(workspaceID, oldPath, newPath string) error func (s *FileService) Stat(workspaceID, path string) (*fs.FileInfo, error) ``` Required behavior: - Each method calls `workspaces.Get(workspaceID)` first. - Each method constructs `fs.NewLocal(ws.Root)` and delegates. - [ ] **Step 4: Implement ProcessService** Create `internal/service/process_service.go`: ```go type ProcessService struct { workspaces workspace.Manager processes process.Manager } func NewProcessService(workspaces workspace.Manager, processes process.Manager) *ProcessService func (s *ProcessService) Start(workspaceID string) error func (s *ProcessService) Stop(workspaceID string) error func (s *ProcessService) Restart(workspaceID string) error func (s *ProcessService) Status(workspaceID string) (process.Status, error) ``` Required behavior: - `Start`, `Restart`, and `Status` verify workspace exists. - `Stop` should return process manager's result directly; if no process exists, return not found. - `Restart` uses workspace root from manager. - [ ] **Step 5: Run package tests** Run: ```sh go test ./internal/service ./internal/model ./internal/workspace ./internal/fs ./internal/process ``` Expected: - All packages compile and tests pass. --- ## Task 6: Implement HTTP router and handlers **Files:** - Create: `internal/api/router.go` - Create: `internal/api/workspace_handler.go` - Create: `internal/api/file_handler.go` - Create: `internal/api/process_handler.go` - Create: `internal/api/router_test.go` **Interfaces:** - Produces: `api.NewRouter(workspaces *service.WorkspaceService, files *service.FileService, processes *service.ProcessService) http.Handler`. - [ ] **Step 1: Write HTTP smoke tests first** Create `internal/api/router_test.go` with tests: ```go func TestHealthz(t *testing.T) func TestCreateWorkspace(t *testing.T) func TestWriteReadFile(t *testing.T) func TestProcessStatus(t *testing.T) ``` Required assertions: - `GET /healthz` returns `200` and `{"status":"ok"}`. - `POST /api/workspaces` with `{"id":"user1"}` returns `201` and `{"id":"user1"}`. - `PUT /api/workspaces/user1/files/write` with `{"path":"main.go","content":"package main\n"}` returns `204`. - `GET /api/workspaces/user1/files/read?path=main.go` returns content. - `GET /api/workspaces/user1/process/status` returns `200`, workspace ID, and `running:false`. Test setup must use `t.TempDir()` for workspace root and a process manager with a harmless command. Do not require real OpenCode. - [ ] **Step 2: Implement router** Create `internal/api/router.go`: ```go func NewRouter(workspaces *service.WorkspaceService, files *service.FileService, processes *service.ProcessService) http.Handler ``` Required routes: ```text GET /healthz POST /api/workspaces GET /api/workspaces/{id} DELETE /api/workspaces/{id} GET /api/workspaces/{id}/files GET /api/workspaces/{id}/files/read PUT /api/workspaces/{id}/files/write POST /api/workspaces/{id}/files/mkdir DELETE /api/workspaces/{id}/files POST /api/workspaces/{id}/files/rename POST /api/workspaces/{id}/process/start POST /api/workspaces/{id}/process/stop POST /api/workspaces/{id}/process/restart GET /api/workspaces/{id}/process/status ``` Use only the standard library. Since Go 1.22 `http.ServeMux` supports method/path patterns, use patterns like: ```go mux.HandleFunc("GET /healthz", healthHandler) mux.HandleFunc("POST /api/workspaces", workspaceHandler.create) mux.HandleFunc("GET /api/workspaces/{id}", workspaceHandler.get) ``` Use `r.PathValue("id")` for path variables. - [ ] **Step 3: Implement workspace handler** Create `internal/api/workspace_handler.go`. Required behavior: - Decode JSON for create. - Empty or invalid JSON returns `400`. - Create success returns `201` with `model.WorkspaceResponse{ID: ws.ID}`. - Get success returns `200` with ID only. - Delete success returns `204`. - Map `util.CodeBadRequest` to HTTP 400, `CodeNotFound` to 404, `CodeConflict` to 409, default to 500. - [ ] **Step 4: Implement file handler** Create `internal/api/file_handler.go`. Required behavior: - `List` reads query `path`; default `.`. - `Read` reads query `path`; missing path returns `400`. - `Write` decodes `model.WriteFileRequest`; missing path returns `400`; success returns `204`. - `Mkdir` decodes `model.MkdirRequest`; missing path returns `400`; success returns `204`. - `Remove` reads query `path`; missing path returns `400`; success returns `204`. - `Rename` decodes `model.RenameRequest`; missing old or new path returns `400`; success returns `204`. - File read returns `model.ReadFileResponse` with content as UTF-8 string. - [ ] **Step 5: Implement process handler** Create `internal/api/process_handler.go`. Required behavior: - Start/stop/restart return `204` on success. - Status returns `model.ProcessStatusResponse`. - Error mapping matches workspace handler. - [ ] **Step 6: Run HTTP tests** Run: ```sh go test ./internal/api ``` Expected: - All tests pass. --- ## Task 7: Wire server entrypoint and watcher placeholder **Files:** - Create: `cmd/server/main.go` - Create: `internal/watcher/watcher.go` **Interfaces:** - Produces runnable server with `go run ./cmd/server`. - Produces placeholder `watcher.Watcher` interface for future expansion. - [ ] **Step 1: Implement watcher placeholder** Create `internal/watcher/watcher.go`: ```go type Event struct { Type string `json:"type"` Path string `json:"path"` } type Watcher interface { Start(root string) error Stop() error Events() <-chan Event } ``` Do not wire this into API in v1. - [ ] **Step 2: Implement server main** Create `cmd/server/main.go`. Required behavior: - Load config from `configs/config.yaml` with `config.Load`. - Create logger with `logger.New()`. - Create workspace manager with configured root. - Create process manager with configured OpenCode command. - Create services. - Create router. - Start HTTP server at configured address. - Log address and workspace root. Main dependency flow: ```go cfg, err := config.Load("configs/config.yaml") workspaces := workspace.NewLocalManager(cfg.Workspace.Root) processes := process.NewManager(cfg.Process.OpenCodeCommand) workspaceSvc := service.NewWorkspaceService(workspaces, processes) fileSvc := service.NewFileService(workspaces) processSvc := service.NewProcessService(workspaces, processes) handler := api.NewRouter(workspaceSvc, fileSvc, processSvc) server := &http.Server{Addr: cfg.Server.Addr, Handler: handler} ``` - [ ] **Step 3: Verify server starts** Run: ```sh go run ./cmd/server ``` Expected: - Server logs that it is listening on `:8080`. - Stop it manually after confirming startup. In another shell, if needed: ```sh curl -s http://localhost:8080/healthz ``` Expected response: ```json {"status":"ok"} ``` --- ## Task 8: Final validation and cleanup **Files:** - Modify only files created by Tasks 1-7 if validation exposes issues. **Interfaces:** - Produces fully validated backend skeleton. - [ ] **Step 1: Format all Go code** Run: ```sh gofmt -w cmd internal pkg ``` Expected: - Command exits successfully. - [ ] **Step 2: Tidy modules** Run: ```sh go mod tidy ``` Expected: - `go.sum` is created or updated if required by `gopkg.in/yaml.v3`. - [ ] **Step 3: Run all tests** Run: ```sh go test ./... ``` Expected: - All tests pass. - [ ] **Step 4: Check handler boundary rule** Run: ```sh grep -R "os\.\|exec\." internal/api || true ``` Expected: - No matches. If matches exist, move those calls out of `internal/api` into services or lower-level packages. - [ ] **Step 5: Run server smoke test** Run server: ```sh go run ./cmd/server ``` In another terminal: ```sh curl -s http://localhost:8080/healthz curl -s -X POST http://localhost:8080/api/workspaces \ -H 'Content-Type: application/json' \ -d '{"id":"user1"}' curl -s -X PUT http://localhost:8080/api/workspaces/user1/files/write \ -H 'Content-Type: application/json' \ -d '{"path":"main.go","content":"package main\n"}' curl -s 'http://localhost:8080/api/workspaces/user1/files/read?path=main.go' ``` Expected: - Health returns `{"status":"ok"}`. - Workspace create returns `{"id":"user1"}`. - Write returns no body with HTTP 204. - Read returns `{"path":"main.go","content":"package main\n"}`. - [ ] **Step 6: Document final result** Update `README.md` only if the actual commands or routes differ from the planned ones. Do not add future-roadmap promises beyond the explicit v1 non-goals. --- ## Codex Execution Prompt Use this exact structure when dispatching Codex for implementation: ```text ## Context - Project root: /Users/taochen/llm/codespace - Tech Stack: Go 1.22+, standard library HTTP router, gopkg.in/yaml.v3 for config. - Design spec: docs/superpowers/specs/2026-07-02-codespace-design.md - Implementation plan: docs/superpowers/plans/2026-07-02-codespace-backend-skeleton.md - Current directory is the project root. Do not create a nested codespace directory. ## Task Generate the runnable Go backend MVP skeleton described by the implementation plan. Steps: 1. Create the files listed in Tasks 1-7. 2. Implement tests before or alongside implementation for workspace path validation, LocalFS, process manager, and HTTP routes. 3. Run formatting, module tidy, tests, and boundary checks from Task 8. ## Constraints - Do not add Docker, auth, Git, LSP, AI Agent orchestration, frontend, or full watcher/WebSocket streaming. - Do not expose host absolute paths in API responses. - Handlers must not call os.* file APIs or exec.*; use services. - Prevent workspace path escape and unsafe workspace IDs. - Use only standard library plus gopkg.in/yaml.v3. - If an existing file has meaningful content, stop and report before overwriting it. ## Acceptance - gofmt -w cmd internal pkg succeeds. - go mod tidy succeeds. - go test ./... passes. - grep -R "os\.\|exec\." internal/api || true returns no matches. - go run ./cmd/server starts and /healthz returns {"status":"ok"}. ``` --- ## Self-Review - Spec coverage: The plan covers project root handling, runnable backend, workspace manager, FileSystem abstraction, LocalFS, process manager, services, API handlers, config, docs, tests, and watcher placeholder. Non-goals are explicitly excluded. - Placeholder scan: No `TBD`, `TODO`, or undefined future implementation steps remain. - Type consistency: Function names and signatures are consistent across file structure, task interfaces, and wiring steps. - Scope check: The plan is one cohesive MVP backend skeleton. Git, LSP, AI Agent, frontend, and watcher streaming remain outside v1 as required.