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-codespace-backend-skeleton.md
T
2026-07-02 15:07:54 +08:00

29 KiB

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

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:

module codespace

go 1.22

require gopkg.in/yaml.v3 v3.0.1

Create configs/config.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:

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:

type ErrorBody struct {
    Error ErrorDetail `json:"error"`
}

type ErrorDetail struct {
    Code    string `json:"code"`
    Message string `json:"message"`
}

Functions:

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:

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:

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

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:

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:

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:

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:

type Workspace struct {
    ID   string `json:"id"`
    Root string `json:"root"`
}

Create internal/workspace/path.go:

func ValidID(id string) bool
func RootFor(baseRoot, id string) string

Create internal/workspace/manager.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:

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:

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:

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:

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:

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:

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:

func TestStatusBeforeStartIsNotRunning(t *testing.T)
func TestStartRejectsDuplicateRunningSession(t *testing.T)
func TestStopMissingSessionIsNotFound(t *testing.T)

Use a safe long-running command:

cmd := os.Args[0]
args := []string{"-test.run=TestHelperProcess", "--"}

And helper pattern:

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:

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:

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=<workspaceRoot> 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:

const DefaultOpenCodeCommand = "opencode"

func normalizeCommand(command string) string
  • Step 5: Run focused tests

Run:

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:

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:

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:

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:

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:

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:

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:

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:

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:

func NewRouter(workspaces *service.WorkspaceService, files *service.FileService, processes *service.ProcessService) http.Handler

Required routes:

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:

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:

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:

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:

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:

go run ./cmd/server

Expected:

  • Server logs that it is listening on :8080.
  • Stop it manually after confirming startup.

In another shell, if needed:

curl -s http://localhost:8080/healthz

Expected response:

{"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:

gofmt -w cmd internal pkg

Expected:

  • Command exits successfully.

  • Step 2: Tidy modules

Run:

go mod tidy

Expected:

  • go.sum is created or updated if required by gopkg.in/yaml.v3.

  • Step 3: Run all tests

Run:

go test ./...

Expected:

  • All tests pass.

  • Step 4: Check handler boundary rule

Run:

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:

go run ./cmd/server

In another terminal:

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:

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