Implementation Plan
Dev Console — Phased Implementation Plan#
This document translates the product design (see DESIGN.md) into an ordered sequence of incremental milestones. Each phase ends with a working, deployable slice of the system that can be used and tested end-to-end before the next phase begins.
The guiding principle is thin vertical slices over horizontal layers: every phase ships something a real user can interact with, even if the feature set is narrow.
Phase 1 — Minimal E2E Terminal#
Goal: A user can open a browser, authenticate with GitHub, pick a workspace, and get a fully functional terminal in that workspace. Nothing else.
This is the foundational slice. Every later phase builds on top of the server, the auth middleware, and the client shell created here.
1.1 Server Scaffolding ✅#
go.mod/go.sumwith initial dependencies (gorilla/mux,gorilla/websocket,creack/pty,golang-jwt/jwt,gopkg.in/yaml.v3)cmd/dev-console/main.go— flag parsing, config loading,httpServer.ListenAndServeinternal/config/config.go— YAML config struct +Load(path)function (supports${ENV_VAR}substitution for secrets)- Graceful shutdown on
SIGINT/SIGTERM
Acceptance: go build ./... succeeds; ./dev-console --config dev-console.yaml
starts and listens.
1.2 GitHub OAuth Authentication ✅#
internal/auth/package- OAuth redirect handler (
GET /auth/login) — initiates the GitHub OAuth flow - OAuth callback handler (
GET /auth/callback) — exchanges code for token, fetches GitHub user, sets signed HTTP-only session cookie - Logout handler (
POST /auth/logout) — clears the session cookie RequireAuthmiddleware that validates the session cookie on every protected route; redirects unauthenticated requests to/login
- OAuth redirect handler (
- JWT-based stateless sessions signed with operator-supplied
sessionSecret; configurable TTL (default 24 h) - Operator-configured allowlist of GitHub user IDs; returns 403 for users not on the list
Acceptance: Hitting GET /auth/login redirects to GitHub; after authorization
the user lands back on the server with a valid session cookie. A user not on the
allowlist sees a 403. A /api/whoami endpoint returns { login, id } for the
authenticated user.
1.3 Auth Validation Site ✅#
A minimal server-rendered HTML site (no external JS dependencies) served directly by the Go backend to allow manual testing and validation of the auth system before the full React SPA exists.
Pages / endpoints:
GET /— if the session cookie is absent or invalid, redirect to/login; otherwise render a simple HTML page showing:- The authenticated user’s GitHub login and ID (same data as
/api/whoami, resolved server-side from the session) - A “Sign out” form that posts to
/auth/logout - A brief confirmation that the session is valid
- The authenticated user’s GitHub login and ID (same data as
GET /login— renders a page with a “Sign in with GitHub” link pointing toGET /auth/login(the OAuth redirect handler from step 1.2)- The
/api/whoami,/auth/login,/auth/callback, and/auth/logoutendpoints from step 1.2 remain unchanged
Templates are Go html/template files stored in internal/templates/ and
embedded in the binary via go:embed; styling uses only inline CSS so no
additional build step is required. This site is intentionally replaced by the
React SPA in step 1.7.
Acceptance: After completing step 1.2 configuration, an operator can:
- Open the server URL in a browser; when unauthenticated, be redirected to
/loginand see a page with a “Sign in with GitHub” link. - Click the “Sign in with GitHub” link and be redirected to the GitHub OAuth flow.
- Complete the OAuth flow and land on the index page (
/) showing their GitHub login name. - Confirm that visiting
/with no or an invalid cookie redirects to/login. - Confirm that a GitHub login not on the allowlist receives a 403 page after completing OAuth.
- Click “Sign out” and confirm the session cookie is cleared and the browser returns to the login page.
1.4 Demo Login Page#
The earliest deliverable with a previewable frontend: a minimal Vite + React +
TypeScript SPA that renders only the LoginPage. This phase ships the entire
demo-mode infrastructure and the per-PR CI preview workflow before any real
backend plumbing beyond auth exists.
Deployment model for this phase: The React SPA is built as a standalone
static bundle deployed as part of the existing documentation site on Cloudflare
Pages — it is not embedded in the Go binary at this stage. Vite builds with
--base /demo/, and the output is copied into site/static/demo/ before Hugo
runs. Hugo treats it as static content and includes it verbatim in site/public/demo/.
The demo is therefore accessible at the /demo/ path on the same Cloudflare
Pages origin as the documentation, and no separate Cloudflare Pages project is
required. The Go-embedded HTML templates from Phase 1.3 (internal/templates/)
continue to serve /login and / for any running Go server instance. The SPA
does not interfere with Go-served routes because it is hosted on the Cloudflare
Pages documentation origin (a separate domain). The compiled SPA replaces the
Go templates in Phase 1.7, when it is embedded in the binary via go:embed.
Client setup:
- Vite project bootstrapped in
client/with TypeScript, React, and Tailwind CSS (dark theme)
LoginPage component:
- Demo mode (
VITE_DEMO_MODE === 'true'): renders a “Try Demo” heading, a password field, and a “Log in” button. Entering the static passworddemonavigates to a placeholder/demopage that reads “You’re in — demo mode active”. Entering any other password shows an inline error message. No network call is made. - Production mode: renders a “Sign in with GitHub” button pointing to
/auth/login
MSW infrastructure (created here, extended in later phases):
client/src/mocks/
handlers.ts # starts empty except for GET /api/whoami → { login: "demo", id: 0 }
browser.ts # startWorker() — MSW Service Worker bootstrap
server.ts # setupServer() — Vitest / Node.js mock serverclient/src/main.tsx conditionally starts the worker:
if (import.meta.env.VITE_DEMO_MODE === 'true') {
const { startWorker } = await import('./mocks/browser')
await startWorker()
}DemoBanner component rendered at the app root whenever
VITE_DEMO_MODE === 'true': a persistent bar reading “Demo mode — no data is
saved”.
Cloudflare Pages build configuration:
The demo SPA is built as part of the Cloudflare Pages build — no separate CI
workflow is needed. Configure the dev-console Cloudflare Pages project with:
| Setting | Value |
|---|---|
| Build command | make site-build-with-demo |
| Output directory | site/public |
| Node.js version | 22 |
| Environment variable | VITE_DEMO_MODE=true |
Cloudflare Pages’ native GitHub integration triggers a build on every commit and
PR, and posts a deployment status with a preview URL directly on the PR. The demo
is accessible at <preview-url>/demo/ alongside the documentation.
When client/ does not yet exist (before Phase 1.4 is implemented), the build
command degrades gracefully: the @if [ -d "client" ] && [ -f "client/package.json" ]
guard in make site-build-with-demo is a no-op and only the Hugo docs are deployed.
Acceptance:
VITE_DEMO_MODE=true npm run buildproduces a static bundle with no server dependency.npm run previewshows the login page; enteringdemoas the password navigates to the placeholder page; entering anything else shows an error.- Cloudflare Pages posts a preview URL on the PR; the demo is accessible at
<preview-url>/demo/alongside the documentation.
1.5 Project and Workspace Registration#
internal/project/package —Projectstruct, in-memory store; projects are created at runtime via the API (not from config)internal/workspace/package —Workspacestruct, in-memory store keyed by project; workspaces are created at runtime (not from config)GET /api/projects— returns the list of projects (id, name, repoURL)POST /api/projects— creates a new project from a GitHub repository URL; server clones the repo intostorage.projectsDir/<id>; returns 400 ifrepoURLis missing or malformed, 502 if the clone fails (network error, invalid credentials, or repo not found)GET /api/projects/:pid— returns metadata for a single project; 404 if unknownDELETE /api/projects/:pid— removes a project and all its workspaces (cascade delete) and deletes the on-disk clone; 404 if unknownGET /api/github/repos— proxies the GitHub API to list repos accessible to the authenticated user; used by the “Add Project” UI to populate a repo pickerGET /api/projects/:pid/workspaces— returns the list of workspaces for a projectPOST /api/projects/:pid/workspaces— creates a new workspace (branch, optional prNumber); 404 if project unknownGET /api/projects/:pid/workspaces/:wid— returns workspace metadata; 404 if unknown
Acceptance: POST /api/projects with { "repoURL": "…" } creates a project
and GET /api/projects returns it. GET /api/github/repos returns a list of
repos for the authenticated user. POST /api/projects/:pid/workspaces creates a
workspace and GET /api/projects/:pid/workspaces lists it.
1.6 Terminal Backend#
internal/terminal/packageSession— wraps acreack/ptyPTY +exec.Cmd(shell) + mutex-protected reference count; shell starts withcwdset to the workspace rootManager— creates, retrieves, and destroys terminal sessions; enforces per-workspace session limit (initially unlimited)
- REST endpoints (behind
RequireAuth):POST /api/projects/:pid/workspaces/:wid/terminals→{ terminalId }DELETE /api/projects/:pid/workspaces/:wid/terminals/:tid
- WebSocket endpoint
WS /api/projects/:pid/workspaces/:wid/terminals/:tid:- Reads JSON
{ "type": "resize", "cols": N, "rows": N }control frame on connect; subsequent binary/text frames are raw PTY stdin - Pumps PTY stdout as binary WebSocket frames to the client
- Cleans up PTY on WebSocket close
- Reads JSON
Acceptance: websocat or a small test harness can attach to the WebSocket,
send resize + input, and receive shell output.
1.7 Minimal Web Client#
A React + TypeScript SPA (bootstrapped with Vite) that provides exactly:
LoginPage— shows a “Sign in with GitHub” button; shown when the user has no valid sessionProjectListPage— lists available projects (fetched fromGET /api/projects) and provides a "+ New Project" button that opens a repository-picker dialog: the dialog callsGET /api/github/reposto list the user’s GitHub repos, lets the user select one, and callsPOST /api/projectsto register and clone itWorkspaceListPage— lists workspaces for a selected project; fetched fromGET /api/projects/:pid/workspaces; includes a button to create a new workspaceTerminalPage— creates a terminal session, opens the WebSocket, and renders anxterm.jsTerminalinstance attached to it; sends resize events when the window resizes; cleans up on unmount
The compiled SPA is embedded in the Go binary via go:embed and served from
/, replacing the Go-embedded HTML templates from Phase 1.3
(internal/templates/). The internal/templates/ package and its routes
(GET / and GET /login) are removed in this phase once the SPA takes over.
Styling is minimal — Tailwind CSS with a dark theme matching a terminal aesthetic. No design polish is required at this stage.
Demo mode is a first-class deliverable of this phase, not an afterthought. MSW handlers for every new endpoint must be committed alongside the components — not in a follow-up PR. See the Testing & Validation Strategy for the full pattern.
Demo mode for this phase (extends the handlers from Phase 1.4):
- Add to
src/mocks/handlers.ts:GET /api/projects→ two hard-coded projects (demo-web,demo-api)POST /api/projects→ creates a new project stub from the suppliedrepoURL; returns it as JSONGET /api/projects/:pid→ metadata for the matching projectGET /api/github/repos→ a short hard-coded list of GitHub repository stubs (used by the repo-picker dialog)GET /api/projects/:pid/workspaces→ one pre-seeded workspace per project (mainbranch)POST /api/projects/:pid/workspaces→ returns a new workspace stub- WebSocket
WS /api/projects/:pid/workspaces/:wid/terminals/:tid→ in-process echo handler that prints a welcome banner and echoes input; no system processes are spawned POST /api/projects/:pid/workspaces/:wid/terminals→ returns{ terminalId: "demo-term" }
- Update
LoginPagein demo mode: replace the password form from Phase 1.4 with a “Try Demo” button that navigates directly toProjectListPage(no password prompt needed once the full flow is wired up)
Per-PR Cloudflare Pages deployment is already active from Phase 1.4 — this phase’s changes will automatically trigger a preview.
Acceptance: The full flow works in Chrome and Safari on both desktop and a 375 px wide mobile viewport:
VITE_DEMO_MODE=true npm run buildproduces a static bundle with no server dependency.npm run previewshows the demo banner and the full flow (login → project list → workspace list → open terminal → interactive echo session) without any backend.- Cloudflare Pages posts a preview URL on the PR; the demo is accessible at
<preview-url>/demo/alongside the documentation. - Against a real server (no
VITE_DEMO_MODE): login → project list → workspace list → open terminal → interactive shell session.
Phase 1 Deliverables#
| Artifact | Description |
|---|---|
go.mod | Go module definition |
cmd/dev-console/ | Server entry point |
internal/config/ | Config loading |
internal/auth/ | GitHub OAuth + session middleware |
internal/templates/ | html/template files (embedded via go:embed) for the auth validation site |
internal/project/ | Project registry |
internal/workspace/ | Workspace store (in-memory, created at runtime) |
internal/terminal/ | PTY session management |
client/ | Vite + React + TypeScript SPA (bootstrapped in Phase 1.4) |
client/src/mocks/ | MSW handlers and browser/server worker entry points (created in Phase 1.4) |
docs/examples/dev-console.yaml.example | Annotated sample configuration |
Makefile | make build, make dev, make test, make site-build-with-demo targets |
Phase 2 — File Browsing#
Goal: Users can browse the workspace file tree and read file contents in the browser.
2.1 File API#
Add to internal/workspace/:
GET /api/projects/:pid/workspaces/:wid/files?path=<dir>— returns a JSON list of directory entries (name, type, size, modTime) forpath; defaults to workspace rootGET /api/projects/:pid/workspaces/:wid/file?path=<file>— returns raw file contents
Both endpoints validate that the resolved path does not escape the workspace root (path-traversal protection).
Acceptance: curl returns directory listings and file contents; requests for
paths outside the root return 400.
2.2 File Browser UI#
FileTreecomponent — collapsible tree loaded lazily one directory at a time viaGET /api/projects/:pid/workspaces/:wid/filesFileViewercomponent — displays file contents with syntax highlighting (highlight.js)WorkspacePage— split layout: file tree on the left, viewer/terminal on the right; terminal from Phase 1 is accessible via a tab or panel- MSW handlers added/updated in
src/mocks/handlers.tsfor all new endpoints:GET /api/projects/:pid/workspaces/:wid/files→ a small hard-coded directory treeGET /api/projects/:pid/workspaces/:wid/file→ a few sample source files
Acceptance: User can expand directories, click files, and read their contents. The terminal panel remains accessible. Demo build works end-to-end without a backend.
Phase 3 — Basic Agent Chat#
Goal: Users can open a chat session with an AI assistant that can read files and answer questions about the workspace. No file writes yet.
3.1 LLM Client#
internal/llm/package — thin HTTP client over the OpenAI Chat Completions API (streaming); configurable base URL, model, and API key so it works with OpenAI, Anthropic (OpenAI-compatible endpoint), or Ollama
Acceptance: Unit test that stubs the HTTP response and verifies streamed chunks are reassembled correctly.
3.2 Agent Session Backend (read-only)#
internal/agent/packageSession— holds conversation history (messages slice), in-flight cancel function, and statusManager— CRUD for sessions scoped to a workspace within a project
- REST endpoints:
GET /api/projects/:pid/workspaces/:wid/sessionsPOST /api/projects/:pid/workspaces/:wid/sessionsDELETE /api/projects/:pid/workspaces/:wid/sessions/:sidGET /api/projects/:pid/workspaces/:wid/sessions/:sid/messages
- WebSocket
WS /api/projects/:pid/workspaces/:wid/sessions/:sid/chat- Accepts
user_messageframes - Runs agent turn: streams
assistant_chunkframes; interleavestool_call/tool_resultframes for read-only tools (read_file,list_files) - Sends
assistant_donewhen the turn completes
- Accepts
Tools enabled in this phase: read_file, list_files (no write_file or
run_command)
Acceptance: User can ask “What files are in the root of this workspace?” and receive a correct streamed answer.
3.3 Chat UI#
ChatPanelcomponent — message history list, user input box, streaming assistant response rendered in real time (markdown viareact-markdown)- Tool calls and results shown as collapsible “tool use” blocks so the user can see what the agent is doing
- Cancel button that sends
{ "type": "cancel" }to the WebSocket - MSW handlers added/updated in
src/mocks/handlers.tsfor all new endpoints:GET /api/projects/:pid/workspaces/:wid/sessions→ empty list initiallyPOST /api/projects/:pid/workspaces/:wid/sessions→ returns a new session stub- WebSocket
WS /api/projects/:pid/workspaces/:wid/sessions/:sid/chat→ scripted handler that emits a fixed sequence ofassistant_chunkframes followed byassistant_done, simulating a real streaming turn
Acceptance: Full conversational loop visible in the browser; streaming text appears word-by-word. Demo build works end-to-end without a backend.
Phase 4 — Change Proposal & Review#
Goal: The agent can propose file edits; the user reviews diffs and accepts or rejects them before any file is written to disk.
4.1 Pending Change Backend#
PendingChangemodel (in-memory, keyed by session)write_fileagent tool — diffs proposed content against current file, stores asPendingChange, emitschange_proposedWebSocket frame- REST endpoints:
GET /api/projects/:pid/workspaces/:wid/sessions/:sid/changesPOST /api/projects/:pid/workspaces/:wid/sessions/:sid/changes/:cid/accept— atomically writes file and marks change acceptedPOST /api/projects/:pid/workspaces/:wid/sessions/:sid/changes/:cid/reject— marks change rejected without touching the file
Acceptance: Agent proposes a change; the in-memory store holds it; accept endpoint writes the file.
4.2 Git Status & Diff API#
GET /api/projects/:pid/workspaces/:wid/git/status— runsgit status --porcelainand returns structured JSONGET /api/projects/:pid/workspaces/:wid/git/diff?path=— returns unified diff for the given path (or all changes if path omitted)
Acceptance: After accepting a change, git/status reflects the modification.
4.3 Diff Review UI#
- Badge on changed files in the
FileTree DiffViewercomponent (usingreact-diff-viewer) for side-by-side or unified view- Accept / Reject buttons per pending change
ChangesPanellisting all pending changes for the session- MSW handlers added/updated in
src/mocks/handlers.tsfor all new endpoints:GET /api/projects/:pid/workspaces/:wid/sessions/:sid/changes→ one pre-seeded pending change that modifies a file in the demo file treePOST …/changes/:cid/acceptand…/changes/:cid/reject→ success stubsGET /api/projects/:pid/workspaces/:wid/git/status→ single modified file matching the pending changeGET /api/projects/:pid/workspaces/:wid/git/diff→ a small hard-coded unified diff
Acceptance: User can trigger a file edit via chat, review the diff, and accept or reject it. File tree updates after acceptance. Demo build works end-to-end without a backend.
Phase 5 — Full Agent Toolset & Manual Editing#
Goal: The agent has its complete tool set; users can also manually edit files.
5.1 run_command Tool#
- Executes a subprocess inside the workspace directory
- Command allowlist enforced from config
- Captures stdout/stderr (bounded buffer); returns to agent as tool result
- Timeout to prevent runaway processes
Acceptance: Agent can run go test ./... or npm test and report results.
5.2 git_diff Tool#
- Returns current
git diff(optionally scoped to a path) - Agent can use it to verify its own changes before proposing them
5.3 Manual File Editor#
FileEditorcomponent — contenteditable / textarea-based minimal editor loaded fromGET /api/projects/:pid/workspaces/:wid/filePUT /api/projects/:pid/workspaces/:wid/file?path=— saves file contents directly (bypasses pending-change flow; immediate write)- Dirty-state indicator; confirm before discarding unsaved changes
- MSW handler added/updated in
src/mocks/handlers.ts:PUT /api/projects/:pid/workspaces/:wid/file→ success stub; in demo mode the edit is visible within the session but nothing is persisted
Acceptance: User can open a file, edit it, save it, and see the change
reflected in git/status. Demo build works end-to-end without a backend.
Phase 6 — Polish & Production Readiness#
Goal: The system is ready for daily use by a small trusted team.
6.1 Error Handling & Resilience#
- Client-side reconnection logic for WebSocket drops (terminal and chat)
- Server-side recovery from panicking agent goroutines (recover + log)
- Graceful error messages in the UI (toast notifications or inline banners)
- LLM API error propagation as
{ "type": "error" }WebSocket frames
6.2 Session Persistence (optional)#
- Embed SQLite (
mattn/go-sqlite3ormodernc.org/sqlitefor CGo-free builds) - Persist agent session history and pending changes across server restarts
- Config flag to opt-in (
persistence.enabled: true)
6.3 PWA & Mobile UX#
manifest.jsonand service worker for installable PWA- Responsive layout that collapses file tree behind a drawer on small screens
- Virtual keyboard awareness in the terminal (viewport resize handling on iOS/Android)
- Touch-friendly tap targets throughout
6.4 TLS & Deployment#
- Document direct TLS configuration (cert/key)
- Document reverse-proxy setup (nginx / Caddy example)
- Provide a sample
systemdunit file - Dockerfile for containerised deployment (optional)
6.5 Observability#
- Structured JSON request logging (method, path, status, latency, user)
- Health-check endpoint
GET /healthz - Version endpoint
GET /api/versionreturning build commit and date
Testing & Validation Strategy#
Rule: Demo mode is mandatory for all frontend functionality#
Every piece of frontend functionality must work in demo mode before it may be merged. This is not optional. A PR that adds or modifies frontend components without shipping the corresponding MSW handlers will be rejected.
Demo mode serves two purposes:
- Per-PR previews — reviewers can interact with any UI change via the auto-deployed Cloudflare Pages URL without running a server.
- Unit testing — the same MSW handlers are reused in Vitest tests, so there is one source of truth for mock behaviour.
Pattern: Mock Service Worker (MSW)#
The project uses Mock Service Worker as the seam between
real backend calls and demo/test stubs. MSW intercepts fetch and WebSocket
calls at the browser’s Service Worker layer, which means:
- Component code is identical in real mode and demo mode — no
if (demo)branches in components or hooks. - Handlers live in one place (
src/mocks/handlers.ts) and are reused by both the in-browser demo and Vitest unit tests. - Adding support for a new API endpoint means adding one handler; there is no parallel service-layer abstraction to maintain.
File layout:
client/src/mocks/
handlers.ts # All MSW request/WebSocket handlers
browser.ts # MSW browser worker setup (startWorker())
server.ts # MSW Node.js server setup for Vitest (setupServer())Enabling demo mode:
In client/src/main.tsx:
if (import.meta.env.VITE_DEMO_MODE === 'true') {
const { startWorker } = await import('./mocks/browser')
await startWorker()
}The VITE_DEMO_MODE variable is set to 'true' only in the CI demo build and
local npm run demo convenience script. It is never set in the production build.
Handler conventions:
- HTTP handlers use
http.get,http.post,http.put,http.deletefrommsw. - WebSocket handlers use
wsfrommsw(requiresmsw≥ 2.x). - Mock data is deterministic and minimal — just enough to exercise the UI flow.
- Delay utilities (
delay()frommsw) may be used to simulate realistic latency (e.g. a 200–400 ms delay on the workspace list, token-by-token streaming on chat).
Demo-specific UI:
- A persistent banner
DemoBannercomponent, rendered at the root of the app whenVITE_DEMO_MODEis'true', reads “Demo mode — no data is saved”. LoginPagein demo mode uses a static-password form (Phase 1.4 through 1.6): entering the passworddemonavigates into the app; any other input shows an inline error message. No network call is made — the SPA runs completely without a backend during these phases because it is deployed only to Cloudflare Pages and the Go-embedded HTML templates still serve the real/loginand/routes. In Phase 1.7, when the SPA is embedded in the Go binary, the password form is replaced by a “Try Demo” button that navigates directly toWorkspaceListPage.
PR Preview Deployments#
The dev-console Cloudflare Pages project is configured to use
make site-build-with-demo as its build command (site/public as the output
directory). Cloudflare Pages’ native GitHub integration takes care of the rest:
- On every PR commit, Cloudflare Pages runs
make site-build-with-demo. - If
client/exists, the target builds the SPA withVITE_DEMO_MODE=trueand--base /demo/, copies the output tosite/static/demo/, then runs Hugo to producesite/public/with the documentation and the demo at/demo/. - If
client/does not yet exist (or has nopackage.json), the guard inmake site-build-with-demois a no-op and only the Hugo docs are built and deployed. - Cloudflare Pages posts a deployment status with the preview URL directly on
the PR. The demo is accessible at
<preview-url>/demo/.
No GitHub Actions workflow or repository secrets are required for deployments.
PRs that include user-visible UI changes must include a screenshot or screen
recording taken from the <preview-url>/demo/ Cloudflare Pages URL in the PR
description.
Acceptance Gate (applies to every phase with frontend work)#
A phase is not done until all of the following hold:
VITE_DEMO_MODE=true npm run buildsucceeds with no errors or warnings.npm run preview(or the Cloudflare Pages URL) shows a fully functional demo covering all UI flows introduced in the phase — no backend required.npm test(Vitest) passes; tests exercise components using the same MSW handlers as the demo.- Cloudflare Pages posts a preview URL on the PR with the demo accessible at
<preview-url>/demo/.
Dependency Map#
Phase 1 ──► Phase 2 ──► Phase 3 ──► Phase 4 ──► Phase 5
│ │
└──────────────────────────┘
(file APIs reused by agent tools)
Phase 6 can be worked on incrementally alongside Phase 4 and 5.Phase 1 is a hard prerequisite for everything else because it establishes:
- The Go module and server structure all other packages plug into
- The authentication middleware that guards every API route
- The project registry and workspace store that scope all subsequent resources
- The client shell and build pipeline that all UI components extend
Work Sizing Guidance#
| Phase | Estimated Effort | Primary Risk |
|---|---|---|
| 1 | ~3–4 days | OAuth flow edge cases; PTY on macOS vs Linux |
| 2 | ~1–2 days | Path-traversal validation; large binary files |
| 3 | ~2–3 days | LLM streaming; tool-call parsing across providers |
| 4 | ~2–3 days | Diff rendering; atomic file writes |
| 5 | ~1–2 days | Command sandboxing; editor UX |
| 6 | ~2–3 days | PWA quirks on iOS; SQLite CGo build |
Estimates assume a single focused engineer. They grow if providers other than OpenAI are tested simultaneously.
Definition of Done for Each Phase#
make buildsucceeds with no warnings.make testpasses with no failures.- The acceptance criteria listed in each section are manually verified.
- For phases with frontend work: all items in the Acceptance Gate in the
Testing & Validation Strategy are satisfied —
in particular, the demo build works without a backend and the
demo-previewCI workflow produces a preview URL. - A brief entry is added to
CHANGELOG.mddescribing what shipped.