diff --git a/Dockerfile b/Dockerfile index f4ed242..2fab4bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # rdev claudebox - Claude Code in a container -# v0.4 - Git integration + SDLC CLI +# v0.5 - HTTP sidecar mode (replaces kubectl exec) # Build stage for Go binaries FROM golang:1.25-alpine AS builder @@ -8,6 +8,7 @@ COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o sdlc ./cmd/sdlc +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o claudebox-sidecar ./cmd/claudebox-sidecar # Runtime stage FROM ubuntu:22.04 @@ -35,8 +36,9 @@ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ # Install Claude Code CLI RUN npm install -g @anthropic-ai/claude-code -# Copy sdlc binary from builder stage +# Copy Go binaries from builder stage COPY --from=builder /build/sdlc /usr/local/bin/sdlc +COPY --from=builder /build/claudebox-sidecar /usr/local/bin/claudebox-sidecar # Configure git for rdev-bot identity RUN git config --global user.name "rdev-bot" \ @@ -57,5 +59,8 @@ WORKDIR /workspace RUN echo '#!/bin/bash\nclaude --version > /dev/null 2>&1' > /healthcheck.sh \ && chmod +x /healthcheck.sh -# Keep container running (will exec into it) -CMD ["tail", "-f", "/dev/null"] +# Expose sidecar HTTP port +EXPOSE 8080 + +# Run claudebox-sidecar by default (HTTP server mode) +CMD ["claudebox-sidecar"] diff --git a/Dockerfile.worker b/Dockerfile.worker new file mode 100644 index 0000000..6b382e2 --- /dev/null +++ b/Dockerfile.worker @@ -0,0 +1,31 @@ +# rdev-worker - Standalone worker for the rdev platform +# Runs as a standalone container with a claudebox sidecar for execution. + +# Build stage +FROM golang:1.25-alpine AS builder +WORKDIR /build +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o rdev-worker ./cmd/rdev-worker + +# Runtime stage - minimal Alpine image +FROM alpine:3.19 + +# Install ca-certificates for HTTPS +RUN apk add --no-cache ca-certificates + +# Copy worker binary +COPY --from=builder /build/rdev-worker /usr/local/bin/rdev-worker + +# Create non-root user +RUN adduser -D -u 1000 worker +USER worker + +# Default environment +ENV RDEV_API_URL="http://rdev-api.rdev.svc.cluster.local:8080" +ENV CLAUDEBOX_URL="http://localhost:8080" +ENV WORKER_POLL_INTERVAL="5s" + +# Run worker +CMD ["rdev-worker"] diff --git a/app-vision-gaps.md b/app-vision-gaps.md index 577ef34..2a61c94 100644 --- a/app-vision-gaps.md +++ b/app-vision-gaps.md @@ -1,43 +1,928 @@ -# App Vision Gaps +# Orchard Studio: Gap Analysis -To realize **Orchard Studio** (the "Deploy First, Talk Later" UI), `rdev` needs to evolve from a CLI/Script-driven engine into a **Reactive Platform API**. +This document maps the delta between current `rdev` capabilities and what Orchard Studio requires. -## 1. The Interactivity Gap (State Management) -* **Current State:** `tree-runner.sh` manages state in a local JSON file (`.checkpoints/`). The process is synchronous and blocking. -* **Vision Requirement:** The UI needs to query "What is the status of the current build?" asynchronously. -* **Gap:** We need to move the "Tree Runner" logic **into the `rdev-api`**. - * *Missing:* `GET /projects/{id}/operations` (List active builds/deploys). - * *Missing:* Database schema for `operations` (replacing local checkpoints). +## Current Foundation (What We Have) -## 2. The Feedback Loop Gap (Streaming) -* **Current State:** We see logs in the terminal where `tree-runner` is running. -* **Vision Requirement:** The user sees "Designing Schema..." -> "Running Tests..." in the web UI. -* **Gap:** We need a **WebSocket / SSE** pipe from the Agent/CI -> `rdev-api` -> `orchard-studio`. - * *Missing:* `rdev-api` endpoint for agents to push progress updates (`POST /operations/{id}/log`). - * *Missing:* Frontend subscription endpoint (`GET /operations/{id}/stream`). +| Capability | Status | Location | +|------------|--------|----------| +| SDLC Classifier | ✅ Complete | `internal/sdlc/classifier.go` | +| Feature State Machine | ✅ Complete | `internal/sdlc/` (10 phases, 31 rules) | +| Composable Templates | ✅ Complete | `internal/adapter/templates/` | +| Worker Pod Execution | ✅ Complete | `internal/worker/sdlc_executor.go` | +| Webhook Dispatcher | ✅ Complete | `internal/webhook/dispatcher.go` | +| Project Provisioning | ✅ Complete | K8s namespace, DNS, git repo | +| Database Provisioning | ✅ Complete | CockroachDB adapter | +| Tree Workflows | ✅ Proven | `cookbooks/trees/*.yaml` | -## 3. The "Draft Mode" Gap (Blueprint API) -* **Current State:** We define features by immediately calling `/sdlc/features` and `POST /builds`. It's "fire and forget". -* **Vision Requirement:** The user and Architect iterate on a plan *before* commitment. -* **Gap:** We need a staging area for requirements. - * *Missing:* `POST /projects/{id}/blueprint/draft`. - * *Missing:* `POST /projects/{id}/blueprint/commit` (Triggers the build). +--- -## 4. The Template Gap (Genesis) -* **Current State:** We have a few raw templates (`go-api`, `astro-landing`). -* **Vision Requirement:** Rich "Seeds" (SaaS, Social, E-comm). -* **Gap:** Our templates are too primitive. - * *Missing:* A "Meta-Template" system that can combine `go-api` + `auth-pkg` + `postgres` + `react-admin` into a single "SaaS Starter" deployable. +## Gap 0: Design Reference Capture & Processing -## 5. The "Architect Persona" Gap -* **Current State:** We prompt Claude with `/implement-feature`. -* **Vision Requirement:** An agent that *asks clarifying questions* instead of just coding. -* **Gap:** We lack the "Consultant" system prompt. - * *Missing:* `.claude/agents/architect.md`. - * *Missing:* A workflow where the API returns a *Question* to the user instead of a *Result*. +**Current:** No mechanism for users to provide visual inspiration. Features are described purely in text. -## Summary of Work -1. **Port Tree Runner to Go:** Move orchestration logic into `rdev-api`. -2. **Build Event Bus:** Implement SSE/Websockets for real-time logs. -3. **Define Blueprint Resource:** Create DB tables for "Draft Features". -4. **Create Architect Agent:** Define the persona that interviews users. +**Required:** Users can provide URLs or screenshots as design references, which inform the Architect's questions and the Blueprint's design system section. + +### What's Missing + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ CURRENT FLOW │ +│ │ +│ User: "Build a pricing page" │ +│ Architect: *asks about data model, endpoints...* │ +│ (No visual context, design decisions are guesswork) │ +└─────────────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────────────┐ +│ REQUIRED FLOW │ +│ │ +│ User: "Build a pricing page like this" + [URL or screenshot] │ +│ System: Captures screenshot, stores with Blueprint │ +│ Architect: "I see a dark theme with 3 tiers..." → asks clarifying Qs │ +│ Blueprint: Populates designSystem section with extracted tokens │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Two Input Types + +| Input | Capture Method | Storage | +|-------|----------------|---------| +| **URL** | Playwright screenshots the page automatically | `/references/{blueprintId}/{refId}.png` | +| **Screenshot** | User uploads image (drag/drop, paste, file picker) | Same storage path | + +### Implementation Required + +1. **Reference Capture Service:** + - For URLs: Reuse `verify_executor.go` pattern (Playwright pod) + - For uploads: Standard file upload handling + - Store thumbnails alongside Blueprint + +2. **Chat Endpoint Enhancement:** + - Accept `references[]` array in request body + - Process references before LLM call + - Include reference images in Architect prompt context + +3. **Architect Prompt Updates:** + - Describe what it observes in natural language + - Ask clarifying questions about design intent + - Extract structured design tokens into Blueprint + +4. **Blueprint Schema:** + - Add `references.items[]` array + - Add `sections.designSystem` section + - Track which references informed which design decisions + +5. **Plan Pane Rendering:** + - Show reference thumbnails in UI + - Display extracted design tokens + - Allow user to add annotations + +### Complexity: Medium + +- URL capture reuses existing Playwright infrastructure +- File upload is standard pattern +- Main work is Architect prompt engineering for visual understanding +- LLM vision capabilities needed (Claude can see images natively) + +--- + +## Gap 1: Blueprint Storage & Chat API + +**Current:** Features are created via `POST /sdlc/features` with a complete spec. No iterative refinement. + +**Required:** Multi-turn conversation that builds a Blueprint incrementally. + +### What's Missing + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ CURRENT FLOW │ +│ │ +│ User writes spec → POST /sdlc/features → Feature created │ +│ (one shot, no iteration) │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ REQUIRED FLOW │ +│ │ +│ User message → Architect responds + updates Blueprint → │ +│ User message → Architect responds + updates Blueprint → │ +│ ...repeat until ready... │ +│ User: "build it" → Blueprint → SDLC Feature → Build │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Implementation Required + +1. **Database Tables:** + - `blueprints` - stores structured Blueprint JSON + - `blueprint_messages` - conversation history with snapshots + +2. **API Endpoints:** + - `POST /projects/{id}/blueprint/chat` - send message, get reply + updated blueprint + - `GET /projects/{id}/blueprints` - list blueprints + - `GET /projects/{id}/blueprints/{id}` - get specific blueprint + - `DELETE /projects/{id}/blueprints/{id}` - discard draft + +3. **Service Layer:** + - `ArchitectService` - manages conversation, calls LLM, updates Blueprint + +### Complexity: Medium +- Schema is defined (see app-vision.md) +- Standard CRUD + LLM integration +- Most work is in prompt engineering for Architect + +--- + +## Gap 2: Architect Agent Persona + +**Current:** We have coding agents (`/implement-feature`). They write code, not specs. + +**Required:** An agent that asks questions, fills in a structured Blueprint, knows when to stop. + +### What's Missing + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ CURRENT AGENTS │ +│ │ +│ User: "Add cat photos" │ +│ Agent: *immediately writes code* │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ ARCHITECT AGENT │ +│ │ +│ User: "Add cat photos" │ +│ Architect: "Should photos be public or friends-only?" │ +│ User: "Public" │ +│ Architect: "Got it. Do you want likes, comments, or neither?" │ +│ ...continues until Blueprint is complete... │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Implementation Required + +1. **System Prompt:** + - `.claude/agents/architect.md` - detailed persona + - Structured output format (reply + Blueprint JSON) + - Question strategy (when to ask vs assume) + +2. **Structured Output Parsing:** + - LLM returns `{reply: string, blueprint: Blueprint}` + - Validate Blueprint against schema + - Handle partial updates (delta vs full replacement) + +3. **Completeness Logic:** + - `isReadyToBuild(blueprint)` function + - Clear rules for when questions are resolved + - Override mechanism for user to force build + +### Complexity: Medium-High +- Prompt engineering is iterative +- Structured output from LLMs can be fragile +- Need fallback handling for malformed responses + +--- + +## Gap 3: Operation Tracking (Tree Runner in DB) + +**Current:** Tree workflows run via shell script (`tree-runner.sh`). State in local JSON files. + +**Required:** Operations tracked in database, queryable via API, streamable to UI. + +### What's Missing + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ CURRENT │ +│ │ +│ ./tree-runner.sh slackpath-1.yaml │ +│ → Runs in terminal │ +│ → State in .checkpoints/slackpath-1.json │ +│ → No API visibility │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ REQUIRED │ +│ │ +│ POST /operations/start {tree: "slackpath-1"} │ +│ → Returns operation_id │ +│ → State in operations table │ +│ → GET /operations/{id}/stream returns SSE events │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Implementation Required + +1. **Database Tables:** + - `operations` - tracks running/completed operations + - `operation_events` - event log for replay/streaming + +2. **Service Layer:** + - `OrchestratorService` - manages operation lifecycle + - Port tree-runner logic from bash to Go + - Event emission during execution + +3. **API Endpoints:** + - `POST /projects/{id}/operations` - start operation + - `GET /projects/{id}/operations/{id}` - get status + - `GET /projects/{id}/operations/{id}/stream` - SSE stream + +4. **Worker Integration:** + - SDLC executor emits events as it progresses + - Events written to `operation_events` table + - SSE handler reads from table and streams + +### Complexity: High +- Tree runner logic is non-trivial (dependencies, outputs, error handling) +- SSE streaming requires careful connection management +- Need to handle operation cancellation, resumption + +--- + +## Gap 4: Real-Time Progress Streaming + +**Current:** Webhooks fire on build complete. No per-step visibility. + +**Required:** SSE stream showing "Designing schema... Writing handlers... Running tests..." + +### What's Missing + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ CURRENT │ +│ │ +│ Build starts → ... silence ... → Webhook: "build complete" │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ REQUIRED │ +│ │ +│ Build starts → │ +│ event: {"phase": "spec", "status": "complete"} │ +│ event: {"phase": "design", "status": "in_progress"} │ +│ event: {"phase": "design", "status": "complete"} │ +│ event: {"phase": "implement", "progress": 0.5} │ +│ ... │ +│ event: {"status": "complete", "url": "..."} │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Implementation Required + +1. **SDLC Executor Changes:** + - Emit events at phase transitions + - Emit progress within phases (task completion) + - Write events to `operation_events` table + +2. **SSE Handler:** + - `GET /operations/{id}/stream` + - Long-lived connection + - Read events from DB (or Redis pub/sub) + - Handle client disconnection gracefully + +3. **Event Types:** + ```go + type OperationEvent struct { + Type string // "phase", "progress", "artifact", "error", "complete" + Phase string // "spec", "design", "implement", "test", "deploy" + Status string // "in_progress", "complete", "failed" + Message string // Human-readable + Progress float64 // 0.0 to 1.0 for granular progress + Timestamp time.Time + } + ``` + +### Complexity: Medium +- SSE is straightforward in Go +- Main work is instrumenting SDLC executor +- Need to balance granularity vs noise + +--- + +## Gap 5: Blueprint → SDLC Feature Conversion + +**Current:** SDLC features are created manually with spec documents. + +**Required:** Automated conversion from structured Blueprint to SDLC feature spec. + +### What's Missing + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ CURRENT │ +│ │ +│ Human writes: spec.md with prose description │ +│ → POST /sdlc/features │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ REQUIRED │ +│ │ +│ Blueprint JSON → Template rendering → spec.md │ +│ → Automated POST /sdlc/features │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Implementation Required + +1. **Spec Template:** + ```markdown + # Feature: {{.Feature}} + + ## Summary + {{.Summary}} + + ## Data Model + {{range .Sections.DataModel.Entities}} + ### {{.Name}} + | Field | Type | + |-------|------| + {{range .Fields}}| {{.Name}} | {{.Type}} | + {{end}} + {{end}} + + ## API Endpoints + {{range .Sections.APIEndpoints.Endpoints}} + - `{{.Method}} {{.Path}}` - {{.Description}} + {{end}} + + ## UI Components + {{range .Sections.UIComponents.Components}} + - **{{.Name}}**: {{.Purpose}} + {{end}} + + ## Assumptions + {{range .Assumptions}} + - {{.Assumption}} + {{end}} + ``` + +2. **Conversion Service:** + - Takes Blueprint, renders spec.md + - Creates SDLC feature via existing API + - Links Blueprint to created feature (`built_feature_slug`) + +### Complexity: Low +- Template rendering is straightforward +- SDLC feature creation already exists +- Main work is template design + +--- + +## Gap 6: Frontend (Next.js Studio) + +**Current:** No frontend. All interaction via API/CLI. + +**Required:** Three-pane interface (Chat, Plan, Preview). + +### What's Missing + +Everything. This is a new application. + +### Implementation Required + +1. **Project Setup:** + - Next.js 14 with App Router + - Tailwind CSS for styling + - Authentication (integrate with rdev auth) + +2. **Core Components:** + ``` + apps/studio/ + ├── app/ + │ ├── page.tsx # Template selection + │ ├── projects/ + │ │ └── [id]/ + │ │ └── page.tsx # Three-pane workspace + │ └── api/ # Proxy to rdev-api + ├── components/ + │ ├── ChatPane.tsx + │ ├── PlanPane.tsx + │ ├── PreviewPane.tsx + │ ├── ActivityFeed.tsx + │ └── BuildProgress.tsx + └── lib/ + ├── api.ts # rdev-api client + └── sse.ts # SSE connection manager + ``` + +3. **State Management:** + - Blueprint state (updated on each chat response) + - Operation state (updated via SSE) + - UI state (which pane is focused, etc.) + +4. **Key Interactions:** + - Send chat message → receive reply + blueprint + - Click "Build It" → start operation → show progress + - Operation complete → refresh preview iframe + +### Complexity: Medium +- Standard Next.js app +- SSE client requires careful handling +- Most complexity is in polish and UX + +--- + +## Gap 7: Platform Service Infrastructure + +**Current:** Projects manage their own integrations. No shared services, no credential management. + +**Required:** A service catalog with provisioning, credential injection, and upgrade paths for existing projects. + +### The "Upgrade" Problem + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ CURRENT │ +│ │ +│ Project created 3 months ago │ +│ → No centralized logging │ +│ → No analytics │ +│ → Rolling your own email │ +│ → No easy way to add platform services │ +└─────────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────────┐ +│ REQUIRED │ +│ │ +│ POST /projects/{id}/services │ +│ { "type": "logging", "provider": "loki" } │ +│ │ +│ → Provision credentials │ +│ → Inject into K8s secrets │ +│ → Create integration PR with config changes │ +│ → Project now ships logs to centralized system │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Service Rollout Order + +Build infrastructure with simplest service first, then add complexity: + +| Order | Service | Why This Order | +|-------|---------|----------------| +| 1 | **Logging** | Pure infrastructure, no user-facing code changes | +| 2 | **Email** | Simple API calls, clear success/failure | +| 3 | **Stats** | Frontend SDK + backend events | +| 4 | **Auth** | Most complex (middleware, user model, protected routes) | + +### Implementation Required + +#### 1. Service Catalog + +```yaml +# internal/platform/catalog.yaml +services: + logging: + description: "Centralized log aggregation" + providers: + loki: + name: "Grafana Loki" + credentials: + - LOKI_URL + - LOKI_TENANT_ID + integration: + go: + config_template: "loki-logger.go.tmpl" + env_example: ["LOKI_URL", "LOKI_TENANT_ID"] + node: + packages: ["pino", "pino-loki"] + config_template: "pino-loki.ts.tmpl" + + email: + description: "Transactional email" + providers: + resend: + name: "Resend" + credentials: + - RESEND_API_KEY + integration: + go: + packages: ["github.com/resendlabs/resend-go"] + service_template: "email-service.go.tmpl" + node: + packages: ["resend"] + service_template: "email-client.ts.tmpl" + + stats: + description: "Product analytics" + providers: + posthog: + name: "PostHog" + credentials: + - POSTHOG_API_KEY + - POSTHOG_HOST + integration: + go: + packages: ["github.com/posthog/posthog-go"] + node: + packages: ["posthog-js", "posthog-node"] + provider_template: "analytics-provider.tsx.tmpl" + + auth: + description: "User authentication" + providers: + clerk: + name: "Clerk" + credentials: + - CLERK_SECRET_KEY + - NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY + integration: + node: + packages: ["@clerk/nextjs"] + middleware_template: "clerk-middleware.ts.tmpl" + provider_template: "clerk-provider.tsx.tmpl" +``` + +#### 2. Database Schema + +```sql +-- Track which services a project uses +CREATE TABLE project_services ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + project_id UUID NOT NULL REFERENCES projects(id), + service_type TEXT NOT NULL, -- 'logging', 'email', 'stats', 'auth' + provider TEXT NOT NULL, -- 'loki', 'resend', 'posthog', 'clerk' + environment TEXT NOT NULL, -- 'staging', 'production', 'all' + + -- Encrypted credentials + credentials_encrypted BYTEA, + + -- Non-sensitive config + config JSONB NOT NULL DEFAULT '{}', + + -- Status tracking + status TEXT NOT NULL DEFAULT 'provisioning', + -- provisioning → active → needs_update → deprovisioned + + -- Integration tracking + integration_status TEXT DEFAULT 'pending', + -- pending → pr_created → integrated → needs_update + integration_pr_url TEXT, + integration_commit TEXT, + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + UNIQUE(project_id, service_type, environment) +); +``` + +#### 3. Provisioner Interface + +```go +// internal/port/platform_provisioner.go +type PlatformProvisioner interface { + // Provision creates credentials for a project + Provision(ctx context.Context, req ProvisionRequest) (*ProvisionResult, error) + + // Verify checks if credentials are still valid + Verify(ctx context.Context, projectID string, creds map[string]string) error + + // Deprovision cleans up (optional, for account removal) + Deprovision(ctx context.Context, projectID string) error +} + +type ProvisionRequest struct { + ProjectID uuid.UUID + ProjectName string + Environment string // "staging", "production" +} + +type ProvisionResult struct { + Credentials map[string]string // Encrypted before storage + Config map[string]string // Non-sensitive config +} +``` + +#### 4. Service Addition API + +``` +POST /projects/{projectId}/services +{ + "serviceType": "logging", + "provider": "loki" // Optional, uses platform default +} + +Response: +{ + "serviceId": "svc_abc123", + "status": "provisioning", + "integrationMethod": "pr", // or "direct" + "prUrl": null // Populated when PR is created +} + +GET /projects/{projectId}/services/{serviceId} +{ + "serviceId": "svc_abc123", + "serviceType": "logging", + "provider": "loki", + "status": "active", + "integrationStatus": "integrated", + "integrationCommit": "abc123...", + "credentials": { + "LOKI_URL": "[redacted]", + "LOKI_TENANT_ID": "project-xyz" + } +} +``` + +#### 5. Integration Flow + +``` +POST /projects/{id}/services {type: "logging", provider: "loki"} + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 1. PROVISION │ +│ │ +│ LokiProvisioner.Provision() │ +│ → Create tenant in Loki (or use shared with project prefix) │ +│ → Generate credentials │ +│ → Store encrypted in project_services │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 2. INJECT │ +│ │ +│ K8sSecretInjector.Inject() │ +│ → Add LOKI_URL, LOKI_TENANT_ID to project's K8s secret │ +│ → Trigger deployment restart to pick up new env vars │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 3. INTEGRATE │ +│ │ +│ IntegrationService.CreatePR() or .DirectCommit() │ +│ → Clone project repo │ +│ → Apply integration templates: │ +│ • Update logger config to ship to Loki │ +│ • Add env vars to .env.example │ +│ • Update deployment to mount secrets │ +│ → Create PR (or direct commit for new projects) │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 4. VERIFY │ +│ │ +│ After PR merge / deploy: │ +│ → Check logs appearing in Loki │ +│ → Update integration_status to "integrated" │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Complexity: High + +- Service catalog is straightforward (YAML/DB) +- Each provisioner is unique (Loki vs Resend vs PostHog) +- Credential encryption and management needs care +- Integration templates need to handle Go + Node + various frameworks +- PR creation requires git operations + +### Starting Point: Logging with Loki + +```go +// internal/adapter/loki/provisioner.go +type LokiProvisioner struct { + lokiURL string + adminToken string // For tenant creation if using multi-tenant Loki +} + +func (p *LokiProvisioner) Provision(ctx context.Context, req ProvisionRequest) (*ProvisionResult, error) { + // For single-tenant Loki, just create a unique label prefix + tenantID := fmt.Sprintf("project-%s", req.ProjectID) + + return &ProvisionResult{ + Credentials: map[string]string{ + "LOKI_URL": p.lokiURL, + "LOKI_TENANT_ID": tenantID, + }, + Config: map[string]string{ + "service_name": req.ProjectName, + }, + }, nil +} +``` + +--- + +## Gap 8: Dual Environment Support + +**Current:** Single deployment per project. Main branch = production. + +**Required:** Staging + Production environments. Build deploys to staging, "Publish" promotes to production. + +### The Environment Model + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Project: cool-project │ +│ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ STAGING │ │ +│ │ staging.cool-project.threesix.ai │ │ +│ │ │ │ +│ │ • Where development happens │ │ +│ │ • Preview pane shows this │ │ +│ │ • "Build It" deploys here │ │ +│ │ • May use test credentials for services │ │ +│ └─────────────────────────────────────────────────────────┘ │ +│ │ │ +│ [Publish] │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ PRODUCTION │ │ +│ │ cool-project.threesix.ai │ │ +│ │ │ │ +│ │ • User-facing, stable │ │ +│ │ • Only updated via explicit "Publish" │ │ +│ │ • Production credentials for services │ │ +│ │ • Enabled after first publish │ │ +│ └─────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Implementation Required + +#### 1. DNS Changes + +```go +// On project creation, create both records (prod may be placeholder) +CreateDNSRecord("staging.cool-project.threesix.ai", stagingIP) +CreateDNSRecord("cool-project.threesix.ai", prodIP) // Or placeholder until first publish +``` + +#### 2. K8s Deployment Model + +```yaml +# Option A: Two deployments in same namespace +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cool-project-staging + namespace: cool-project +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cool-project-production + namespace: cool-project + +# Option B: Two namespaces (cleaner isolation) +# cool-project-staging namespace +# cool-project-production namespace +``` + +**Recommendation:** Same namespace, two deployments. Simpler to manage, secrets can be shared or scoped. + +#### 3. Database Model + +Two options: + +**A. Same database, schema prefixes:** +```sql +-- Staging tables +staging_users, staging_posts, staging_... + +-- Production tables +prod_users, prod_posts, prod_... +``` + +**B. Separate databases (cleaner):** +``` +cool-project-staging (CockroachDB database) +cool-project-production (CockroachDB database) +``` + +**Recommendation:** Separate databases. Cleaner isolation, no risk of cross-env data access. + +#### 4. Project Schema Updates + +```sql +ALTER TABLE projects ADD COLUMN environments JSONB NOT NULL DEFAULT '{ + "staging": {"enabled": true, "deployed_at": null}, + "production": {"enabled": false, "deployed_at": null, "published_at": null} +}'; +``` + +#### 5. Publish API + +``` +POST /projects/{projectId}/publish +{ + "fromEnvironment": "staging", // Usually staging + "toEnvironment": "production" +} + +Response: +{ + "operationId": "op_xyz789", + "status": "publishing", + "streamUrl": "/operations/{operationId}/stream" +} +``` + +**Publish Flow:** +1. Validate staging is healthy +2. Provision production credentials for any services (if not exist) +3. Run migrations on production database +4. Deploy staging image to production deployment +5. Health check production +6. Update DNS if needed +7. Update project.environments.production + +### Complexity: Medium + +- DNS: Already have CloudflareAdapter, just create two records +- K8s: Straightforward deployment duplication +- Database: CockroachDB adapter supports multiple databases +- Main complexity is the publish flow coordination + +### Defer Until After Gap 7 + +Dual environments can work with platform services, but we can build Gap 7 (services) first: +- Services provision for a single environment initially +- Then extend to environment-aware provisioning +- Then add the publish flow that syncs services to production + +--- + +## Summary: Work Required + +| Gap | Effort | Dependencies | Critical Path | +|-----|--------|--------------|---------------| +| 0. Design References | 2-3 days | Gap 1 (storage) | Yes (for design flows) | +| 1. Blueprint Storage | 2-3 days | None | Yes | +| 2. Architect Agent | 3-5 days | Gap 1 | Yes | +| 3. Operation Tracking | 4-6 days | None | Yes | +| 4. Progress Streaming | 2-3 days | Gap 3 | Yes | +| 5. Blueprint → SDLC | 1-2 days | Gap 1 | Yes | +| 6. Frontend | 5-7 days | Gaps 1-5 | Yes | +| 7. Platform Services | 5-8 days | None (can start now) | Parallel track | +| 8. Dual Environments | 3-5 days | Gap 7 | After services work | + +**Total Estimate:** 4-5 weeks of focused work (Gaps 7-8 can parallel with 1-6) + +**Service Rollout (within Gap 7):** +1. Logging (Loki) - 2 days +2. Email (Resend) - 2 days +3. Stats (PostHog) - 2 days +4. Auth (Clerk) - 3 days + +**Note:** Gap 0 (Design References) can be implemented in parallel with Gap 2 (Architect Agent) since both involve Architect prompt engineering. The reference capture infrastructure (Gap 0) builds on Gap 1's storage layer. + +### Critical Path + +``` + ┌──► Gap 0 (References) ──┐ + │ │ +Gap 1 (Blueprint) ──┼──► Gap 2 (Architect) ───┼──► Gap 5 (Conversion) + │ │ + │ └──► Gap 6 (Frontend) + │ ▲ +Gap 3 (Operations) ─┴──► Gap 4 (Streaming) ────────┘ + + +Parallel Track: + +Gap 7 (Services) ──► Logging ──► Email ──► Stats ──► Auth + │ + └──► Gap 8 (Environments) ──► Publish Flow +``` + +Gap 7 can start immediately and run parallel to the Studio work. +Gap 8 depends on Gap 7 for service credential handling per environment. + +--- + +## Risk Assessment + +| Risk | Likelihood | Impact | Mitigation | +|------|------------|--------|------------| +| Architect outputs malformed JSON | High | Medium | JSON schema validation, retry logic | +| SSE connections drop | Medium | Low | Client-side reconnection, event replay from DB | +| Blueprint schema too restrictive | Medium | Medium | Start minimal, add sections iteratively | +| LLM latency affects chat UX | Low | High | Stream partial responses, show typing indicator | +| Build failures leave broken state | Low | Medium | SDLC already handles partial state | + +--- + +## What's NOT a Gap + +These are already solved by the current rdev foundation: + +- **Project provisioning** - K8s, DNS, git all work +- **Template seeding** - Composable monorepo templates +- **SDLC execution** - Classifier + worker + artifact tracking +- **CI/CD** - Woodpecker integration +- **Database provisioning** - CockroachDB adapter +- **Webhooks** - Event dispatcher with retry + +The foundation is solid. The gaps are about **exposing** existing capabilities through a conversational UI, not rebuilding core functionality. diff --git a/app-vision-roadmap.md b/app-vision-roadmap.md index 5cf0483..1823ba9 100644 --- a/app-vision-roadmap.md +++ b/app-vision-roadmap.md @@ -1,42 +1,1384 @@ -# App Vision Roadmap +# Orchard Studio: Implementation Roadmap -This roadmap bridges the gap between the current `rdev` CLI engine and the **Orchard Studio** vision. +This roadmap converts the vision into executable phases with clear deliverables. -## Phase 1: Engine Reliability (The "Slack Path") -**Goal:** Prove `rdev` can autonomously build complex systems via CLI/Scripts. -* *Why:* If the engine can't build Slack/Aeries, the UI is useless. -* **Tasks:** - 1. [ ] **Templates:** Complete `worker`, `postgres`, `redis`, `app-react`. - 2. **Shared Libs:** Implement `pkg/auth`, `pkg/queue`. - 3. **Validation:** successfully run `slackpath-1` (Auth) and `slackpath-2` (Async). - 4. **Refactor:** Ensure all templates use the "Secret-First" config pattern. +--- -## Phase 2: The API Lift (The "Tree Runner Migration") -**Goal:** Move orchestration state from local JSON files to the `rdev` Database. -* *Why:* The Web UI cannot read a JSON file on my laptop. It needs an API. -* **Tasks:** - 1. [ ] **Schema:** Create `operations` and `operation_steps` tables in CockroachDB. - 2. **Port Logic:** Rewrite `tree-runner.sh` logic into `internal/service/orchestrator.go`. - 3. **Endpoints:** Expose `POST /operations/start`, `GET /operations/{id}`. - 4. **Verify:** Run `slackpath-3` using `curl` calls to the new API instead of the shell script. +## Phase 0: Engine Validation (Current) -## Phase 3: The Architect & Blueprint (The "Brain") -**Goal:** Enable the "Conversation -> Spec" loop. -* **Tasks:** - 1. [ ] **Agent:** Create `architect` persona (specialized in requirements gathering). - 2. **API:** Create `POST /blueprint/chat` endpoint. - 3. **Logic:** Implement the "Clarification Loop" (Agent outputting questions vs Agent outputting specs). - 4. **Verify:** Have a conversation via `curl` that results in a validated Spec artifact. +**Goal:** Prove the SDLC engine can autonomously build complex systems. -## Phase 4: Orchard Studio UI (The "Face") -**Goal:** Build the Next.js Frontend. -* **Tasks:** - 1. [ ] **Scaffold:** Create `apps/studio` in the `rdev` repo. - 2. **UI:** Build the Split Screen (Chat + Preview). - 3. **Integration:** Connect to `rdev-api` (Auth, Operations, Blueprints). - 4. **Streaming:** Implement the WebSocket client for build logs. +**Status:** In Progress (2 of 4 trees passing) -## Phase 5: The "Aeries" Launch -**Goal:** Use Orchard Studio to build Aeries from scratch. -* **Action:** Click "Social World Seed". Chat with Architect. Watch Aeries deploy. -* **Success Criteria:** A working multi-agent simulation built without writing a single line of code manually. +### Slackpath Verification Results + +| Tree | Infrastructure | Build | Status | Notes | +|------|----------------|-------|--------|-------| +| `slackpath-1` | ✅ Pass | ✅ Pass | ✅ Complete | 5 polls (~30s) | +| `slackpath-2` | ✅ Pass | ✅ Pass | ✅ Complete | 111 polls (~9 min) | +| `slackpath-3` | ✅ Pass | ⚠️ Timeout | ❌ Incomplete | Build stayed "pending" - worker capacity | +| `slackpath-4` | ✅ Pass | ⚠️ Timeout | ❌ Incomplete | Build stayed "running" - long task | + +### Key Findings + +1. **Infrastructure provisioning works reliably** - all 4 trees pass `wait-infra` +2. **SDLC execution works** - slackpath-1 and slackpath-2 complete successfully +3. **Worker capacity issue** - slackpath-3 never started (stayed "pending") +4. **Long-running task issue** - slackpath-4 timed out while still "running" + +### Remaining Work + +| Task | Status | Notes | +|------|--------|-------| +| Worker pool scaling | 🔄 In Progress | Need capacity for parallel builds | +| Long-running task handling | 🔄 In Progress | Extend timeouts or checkpoint | +| Component templates complete | 🔄 In Progress | service ✅, worker 🔄, app-react 🔄 | + +### Success Criteria + +```bash +# All 4 slackpath trees complete successfully +./cookbooks/scripts/tree-runner.sh cookbooks/trees/slackpath-1-authenticated-service.yaml # ✅ +./cookbooks/scripts/tree-runner.sh cookbooks/trees/slackpath-2-*.yaml # ✅ +./cookbooks/scripts/tree-runner.sh cookbooks/trees/slackpath-3-*.yaml # 🔄 +./cookbooks/scripts/tree-runner.sh cookbooks/trees/slackpath-4-*.yaml # 🔄 +``` + +### Exit Condition + +All 4 slackpath trees complete autonomously without manual intervention. + +--- + +## Phase 1: Blueprint API (Week 1-2) + +**Goal:** Add Blueprint storage and chat endpoint for Architect conversations. + +### Week 1: Database & CRUD + +#### Day 1-2: Schema & Migrations + +```sql +-- migrations/000X_blueprints.up.sql +CREATE TABLE blueprints ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + project_id UUID NOT NULL REFERENCES projects(id), + feature_name TEXT NOT NULL, + summary TEXT, + sections JSONB NOT NULL DEFAULT '{}', + open_questions JSONB NOT NULL DEFAULT '[]', + assumptions JSONB NOT NULL DEFAULT '[]', + ready_to_build BOOLEAN NOT NULL DEFAULT FALSE, + blockers JSONB NOT NULL DEFAULT '[]', + status TEXT NOT NULL DEFAULT 'draft', + built_feature_slug TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE blueprint_messages ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + blueprint_id UUID NOT NULL REFERENCES blueprints(id) ON DELETE CASCADE, + role TEXT NOT NULL, + content TEXT NOT NULL, + blueprint_snapshot JSONB, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_blueprints_project ON blueprints(project_id); +CREATE INDEX idx_blueprint_messages_blueprint ON blueprint_messages(blueprint_id); +``` + +#### Day 3-4: Domain & Repository + +```go +// internal/domain/blueprint.go +type Blueprint struct { + ID uuid.UUID + ProjectID uuid.UUID + FeatureName string + Summary string + Sections BlueprintSections + OpenQuestions []OpenQuestion + Assumptions []Assumption + ReadyToBuild bool + Blockers []string + Status string // draft, building, built, archived + CreatedAt time.Time + UpdatedAt time.Time +} + +type BlueprintSections struct { + DataModel DataModelSection + APIEndpoints APIEndpointsSection + UIComponents UIComponentsSection + Dependencies DependenciesSection +} + +// internal/port/blueprint.go +type BlueprintRepository interface { + Create(ctx context.Context, bp *domain.Blueprint) error + Get(ctx context.Context, id uuid.UUID) (*domain.Blueprint, error) + Update(ctx context.Context, bp *domain.Blueprint) error + Delete(ctx context.Context, id uuid.UUID) error + ListByProject(ctx context.Context, projectID uuid.UUID) ([]domain.Blueprint, error) + AddMessage(ctx context.Context, msg *domain.BlueprintMessage) error + GetMessages(ctx context.Context, blueprintID uuid.UUID) ([]domain.BlueprintMessage, error) +} +``` + +#### Day 5: Handler & Routes + +```go +// internal/handlers/blueprints.go +func (h *BlueprintHandler) Mount(r chi.Router) { + r.Route("/projects/{projectId}/blueprints", func(r chi.Router) { + r.With(auth.RequireScope(auth.ScopeProjectsRead)).Get("/", h.List) + r.With(auth.RequireScope(auth.ScopeProjectsRead)).Get("/{blueprintId}", h.Get) + r.With(auth.RequireScope(auth.ScopeProjectsExecute)).Post("/", h.Create) + r.With(auth.RequireScope(auth.ScopeProjectsExecute)).Delete("/{blueprintId}", h.Delete) + r.With(auth.RequireScope(auth.ScopeProjectsExecute)).Post("/{blueprintId}/chat", h.Chat) + r.With(auth.RequireScope(auth.ScopeProjectsExecute)).Post("/{blueprintId}/build", h.Build) + }) +} +``` + +### Week 2: Architect Integration + Design References + +#### Day 6-7: Architect Service + Reference Handling + +The Architect service handles both text conversations and design reference processing. + +**Reference Capture (parallel track):** + +```go +// internal/service/reference_service.go +type ReferenceService struct { + verifyExecutor port.VerifyExecutor // Reuse Playwright infrastructure + storage port.FileStorage + logger *slog.Logger +} + +func (s *ReferenceService) CaptureURL(ctx context.Context, blueprintID uuid.UUID, url string) (*domain.Reference, error) { + // 1. Screenshot the URL using Playwright pod + result, err := s.verifyExecutor.Capture(ctx, domain.VerifySpec{ + URL: url, + Viewports: []string{"1920x1080"}, // Desktop only for references + FullPage: true, + }) + + // 2. Store screenshot + path := fmt.Sprintf("/references/%s/%s.png", blueprintID, uuid.New()) + s.storage.Save(ctx, path, result.Screenshots["1920x1080"]) + + // 3. Return reference metadata + return &domain.Reference{ + ID: uuid.New(), + Type: "url", + Source: url, + Thumbnail: path, + }, nil +} + +func (s *ReferenceService) ProcessUpload(ctx context.Context, blueprintID uuid.UUID, data []byte) (*domain.Reference, error) { + // Handle user-uploaded screenshots + path := fmt.Sprintf("/references/%s/%s.png", blueprintID, uuid.New()) + s.storage.Save(ctx, path, data) + + return &domain.Reference{ + ID: uuid.New(), + Type: "screenshot", + Source: path, + Thumbnail: path, + }, nil +} +``` + +#### Day 6-7: Architect Service + +```go +// internal/service/architect_service.go +type ArchitectService struct { + blueprintRepo port.BlueprintRepository + llmClient port.LLMClient + logger *slog.Logger +} + +func (s *ArchitectService) Chat(ctx context.Context, blueprintID uuid.UUID, message string) (*ChatResponse, error) { + // 1. Load blueprint and conversation history + bp, _ := s.blueprintRepo.Get(ctx, blueprintID) + messages, _ := s.blueprintRepo.GetMessages(ctx, blueprintID) + + // 2. Build prompt with Architect persona + prompt := s.buildArchitectPrompt(bp, messages, message) + + // 3. Call LLM + response, _ := s.llmClient.Complete(ctx, prompt) + + // 4. Parse structured response + reply, blueprintUpdate := s.parseResponse(response) + + // 5. Apply blueprint update + s.applyBlueprintUpdate(bp, blueprintUpdate) + + // 6. Save message and updated blueprint + s.blueprintRepo.AddMessage(ctx, &domain.BlueprintMessage{...}) + s.blueprintRepo.Update(ctx, bp) + + return &ChatResponse{Reply: reply, Blueprint: bp}, nil +} +``` + +#### Day 8-9: Architect Prompt Engineering + +````markdown + + +# Architect Agent + +You are the Architect for Orchard Studio. Your job is requirements engineering. + +## Response Format + +ALWAYS respond with valid JSON in this exact format: + +```json +{ + "reply": "Your conversational response to the user", + "blueprint": { + "feature": "Feature name", + "summary": "One-line summary", + "sections": { + "dataModel": { + "status": "empty|partial|complete", + "entities": [...] + }, + "designSystem": { + "status": "empty|partial|complete", + "colors": [...], + "typography": [...], + "spacing": [...], + "inspirationNotes": "..." + }, + ... + }, + "references": { + "items": [...] + }, + "openQuestions": [...], + "assumptions": [...], + "readyToBuild": false, + "blockers": [...] + } +} +``` +```` + +## Behavior Rules + +1. Start with the current blueprint (provided below) +2. Update it based on the user's message +3. Ask 1-2 clarifying questions if gaps exist +4. Suggest building when the plan is complete +5. NEVER write implementation code + +## Handling Design References + +When the user provides a URL or screenshot: + +1. **Describe what you observe** (conversational, natural language): + - Layout structure, visual hierarchy + - Color palette, theme (light/dark) + - Component patterns you recognize + - Typography and spacing feel + +2. **Ask clarifying questions about intent**: + - "Match exactly or use as inspiration?" + - "Keep these colors or use your brand?" + - "Include all elements or simplify for v1?" + +3. **Extract structured tokens into designSystem**: + - Colors: Primary, secondary, accent, background, text + - Typography: Font families, sizes, weights + - Spacing: Observed rhythm (4px, 8px, 16px, etc.) + - Border radius, shadows, other patterns + +4. **Document in inspirationNotes**: + - Which elements came from which reference + - What was adapted vs. copied + - User's stated preferences + +The conversation stays loose. The Blueprint stays precise. + +## Building the Plan Agentively + +The transition from loose conversation to structured Blueprint is the core challenge. +This is NOT a simple extraction—it requires judgment, interpretation, and iteration. + +Key principles: + +1. **Don't rush to structure.** Let the conversation develop naturally. +2. **Show your work.** "I'm inferring spacing of 16px from the card padding I see." +3. **Invite correction.** "Does this match what you had in mind?" +4. **Iterate incrementally.** Each turn refines the Blueprint slightly. +5. **Distinguish confidence levels.** "I'm confident about the 3-tier layout, less sure about the accent color." + +The Architect is not a form-filler. It's a collaborator that builds shared understanding. + +```` + +#### Day 10: Testing & Verification + +```bash +# Test conversation flow +curl -X POST $RDEV_API_URL/projects/$PROJECT_ID/blueprints \ + -H "X-API-Key: $RDEV_API_KEY" \ + -d '{"featureName": "Cat Photos"}' + +# Returns blueprint_id + +curl -X POST $RDEV_API_URL/projects/$PROJECT_ID/blueprints/$BLUEPRINT_ID/chat \ + -H "X-API-Key: $RDEV_API_KEY" \ + -d '{"message": "I want users to post cat photos"}' + +# Returns {reply: "Should photos be public or...", blueprint: {...}} + +# Test with design reference +curl -X POST $RDEV_API_URL/projects/$PROJECT_ID/blueprints/$BLUEPRINT_ID/chat \ + -H "X-API-Key: $RDEV_API_KEY" \ + -d '{ + "message": "Build a photo grid like this", + "references": [{"type": "url", "source": "https://unsplash.com/"}] + }' + +# Returns {reply: "I see a masonry grid with rounded corners...", blueprint: {...}} +# Blueprint now includes references.items[] and sections.designSystem +```` + +#### Cookbook Tree: design-from-reference + +```yaml +# cookbooks/trees/design-from-reference.yaml +name: design-from-reference +description: "E2E test: Build feature from visual inspiration" + +vars: + project_name: "" + reference_url: "https://stripe.com/pricing" + +steps: + create-project: + action: api + method: POST + endpoint: /project + body: + name: "{{ .vars.project_name }}" + outputs: + - project_id: .data.name + + start-blueprint: + depends_on: [create-project] + action: api + method: POST + endpoint: "/projects/{{ .outputs.create-project.project_id }}/blueprints" + body: + featureName: "Pricing Page" + outputs: + - blueprint_id: .data.id + + provide-reference: + depends_on: [start-blueprint] + action: api + method: POST + endpoint: "/projects/{{ .outputs.create-project.project_id }}/blueprints/{{ .outputs.start-blueprint.blueprint_id }}/chat" + body: + message: "Build a pricing page inspired by this" + references: + - type: url + source: "{{ .vars.reference_url }}" + outputs: + - has_design_system: .blueprint.sections.designSystem.status != "empty" + + clarify-intent: + depends_on: [provide-reference] + action: api + method: POST + endpoint: "/projects/{{ .outputs.create-project.project_id }}/blueprints/{{ .outputs.start-blueprint.blueprint_id }}/chat" + body: + message: "Use as inspiration. Match the 3-tier layout but use a light theme with blue accents." + + confirm-plan: + depends_on: [clarify-intent] + action: api + method: POST + endpoint: "/projects/{{ .outputs.create-project.project_id }}/blueprints/{{ .outputs.start-blueprint.blueprint_id }}/chat" + body: + message: "Looks good, build it" + + trigger-build: + depends_on: [confirm-plan] + action: api + method: POST + endpoint: "/projects/{{ .outputs.create-project.project_id }}/blueprints/{{ .outputs.start-blueprint.blueprint_id }}/build" + outputs: + - operation_id: .data.operationId + + wait-build: + depends_on: [trigger-build] + action: wait_build + build_id: "{{ .outputs.trigger-build.operation_id }}" + max_attempts: 120 + +teardown: + - action: api + method: DELETE + endpoint: "/project/{{ .outputs.create-project.project_id }}" +``` + +### Phase 1 Exit Criteria + +- [ ] Blueprints table created and migrated +- [ ] CRUD endpoints working +- [ ] Chat endpoint returns structured responses +- [ ] Chat endpoint accepts `references[]` array (URLs and uploads) +- [ ] URL references auto-screenshot via Playwright +- [ ] Architect describes visual references in conversation +- [ ] Design tokens extracted into `sections.designSystem` +- [ ] Conversation history persisted +- [ ] `readyToBuild` computed correctly + +--- + +## Phase 2: Operation Tracking (Week 2-3) + +**Goal:** Move tree/build orchestration from shell scripts to database-tracked operations. + +### Week 2 (overlap with Phase 1): Schema & Core + +#### Operations Schema + +```sql +-- migrations/000Y_operations.up.sql +CREATE TABLE operations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + project_id UUID NOT NULL REFERENCES projects(id), + blueprint_id UUID REFERENCES blueprints(id), + operation_type TEXT NOT NULL, + tree_name TEXT, + status TEXT NOT NULL DEFAULT 'pending', + current_phase TEXT, + progress JSONB NOT NULL DEFAULT '{}', + result JSONB, + error TEXT, + started_at TIMESTAMPTZ, + completed_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE operation_events ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + operation_id UUID NOT NULL REFERENCES operations(id) ON DELETE CASCADE, + event_type TEXT NOT NULL, + payload JSONB NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_operations_project ON operations(project_id); +CREATE INDEX idx_operations_status ON operations(status) WHERE status IN ('pending', 'running'); +CREATE INDEX idx_operation_events_operation ON operation_events(operation_id); +``` + +#### Orchestrator Service + +```go +// internal/service/orchestrator_service.go +type OrchestratorService struct { + operationRepo port.OperationRepository + eventRepo port.OperationEventRepository + sdlcService *SDLCService + workQueue port.WorkQueue + logger *slog.Logger +} + +func (s *OrchestratorService) StartBuild(ctx context.Context, blueprintID uuid.UUID) (*domain.Operation, error) { + // 1. Create operation record + op := &domain.Operation{ + ProjectID: projectID, + BlueprintID: &blueprintID, + OperationType: "build", + Status: "pending", + } + s.operationRepo.Create(ctx, op) + + // 2. Convert blueprint to SDLC feature spec + spec := s.blueprintToSpec(blueprint) + + // 3. Enqueue work item with operation ID + s.workQueue.Enqueue(ctx, &domain.WorkItem{ + Type: "sdlc", + OperationID: op.ID, + Spec: spec, + }) + + return op, nil +} + +func (s *OrchestratorService) EmitEvent(ctx context.Context, opID uuid.UUID, event OperationEvent) error { + // 1. Persist event + s.eventRepo.Create(ctx, opID, event) + + // 2. Update operation status + if event.Type == "phase" { + s.operationRepo.UpdatePhase(ctx, opID, event.Phase, event.Status) + } + + return nil +} +``` + +### Week 3: SSE Streaming + +#### SSE Handler + +```go +// internal/handlers/operations_stream.go +func (h *OperationsHandler) Stream(w http.ResponseWriter, r *http.Request) { + opID := chi.URLParam(r, "operationId") + + // Set SSE headers + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + + flusher, ok := w.(http.Flusher) + if !ok { + api.WriteInternalError(w, "streaming not supported") + return + } + + // Get existing events (for reconnection) + events, _ := h.eventRepo.GetByOperation(r.Context(), opID) + for _, e := range events { + fmt.Fprintf(w, "event: %s\ndata: %s\n\n", e.Type, e.Payload) + flusher.Flush() + } + + // Subscribe to new events + ch := h.eventBus.Subscribe(opID) + defer h.eventBus.Unsubscribe(opID, ch) + + for { + select { + case event := <-ch: + fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event.Type, event.Payload) + flusher.Flush() + if event.Type == "complete" || event.Type == "error" { + return + } + case <-r.Context().Done(): + return + } + } +} +``` + +#### SDLC Executor Instrumentation + +```go +// internal/worker/sdlc_executor.go +func (e *SDLCExecutor) Execute(ctx context.Context, task *domain.WorkItem) error { + // Emit start event + e.emitEvent(ctx, task.OperationID, OperationEvent{ + Type: "phase", + Phase: "started", + Status: "in_progress", + Message: "Starting build", + }) + + // ... existing execution logic ... + + // Emit phase transitions + for _, phase := range []string{"spec", "design", "implement", "test", "deploy"} { + e.emitEvent(ctx, task.OperationID, OperationEvent{ + Type: "phase", + Phase: phase, + Status: "in_progress", + Message: fmt.Sprintf("Starting %s phase", phase), + }) + + // Execute phase... + + e.emitEvent(ctx, task.OperationID, OperationEvent{ + Type: "phase", + Phase: phase, + Status: "complete", + Message: fmt.Sprintf("Completed %s phase", phase), + }) + } + + // Emit completion + e.emitEvent(ctx, task.OperationID, OperationEvent{ + Type: "complete", + Status: "success", + Message: "Build complete", + URL: deployedURL, + }) + + return nil +} +``` + +### Phase 2 Exit Criteria + +- [ ] Operations table created and migrated +- [ ] `POST /operations` starts tracked operation +- [ ] `GET /operations/{id}` returns current status +- [ ] `GET /operations/{id}/stream` returns SSE events +- [ ] SDLC executor emits phase events +- [ ] Events persist for replay on reconnect + +--- + +## Phase 3: Blueprint → Build Integration (Week 3) + +**Goal:** Connect "Build It" button to full SDLC execution. + +### Blueprint to Spec Conversion + +```go +// internal/service/architect_service.go +func (s *ArchitectService) Build(ctx context.Context, blueprintID uuid.UUID) (*domain.Operation, error) { + bp, _ := s.blueprintRepo.Get(ctx, blueprintID) + + // Validate readiness + if !bp.ReadyToBuild { + return nil, errors.New("blueprint not ready: " + strings.Join(bp.Blockers, ", ")) + } + + // Convert to spec markdown + spec := s.renderSpec(bp) + + // Create SDLC feature + feature, _ := s.sdlcService.CreateFeature(ctx, bp.ProjectID, domain.FeatureRequest{ + Name: bp.FeatureName, + Spec: spec, + }) + + // Link blueprint to feature + bp.Status = "building" + bp.BuiltFeatureSlug = feature.Slug + s.blueprintRepo.Update(ctx, bp) + + // Start operation + op, _ := s.orchestratorService.StartBuild(ctx, bp.ID, feature.Slug) + + return op, nil +} + +func (s *ArchitectService) renderSpec(bp *domain.Blueprint) string { + tmpl := `# Feature: {{.FeatureName}} + +## Summary +{{.Summary}} + +## Data Model +{{range .Sections.DataModel.Entities}} +### {{.Name}} +| Field | Type | +|-------|------| +{{range .Fields}}| {{.Name}} | {{.Type}} | +{{end}} +{{end}} + +## API Endpoints +{{range .Sections.APIEndpoints.Endpoints}} +- ` + "`{{.Method}} {{.Path}}`" + ` - {{.Description}} +{{end}} + +## UI Components +{{range .Sections.UIComponents.Components}} +- **{{.Name}}**: {{.Purpose}} +{{end}} +` + // ... render template ... +} +``` + +### Phase 3 Exit Criteria + +- [ ] `POST /blueprints/{id}/build` triggers SDLC feature creation +- [ ] Spec markdown generated from blueprint sections +- [ ] Operation created and trackable +- [ ] Blueprint status updated to "building" → "built" + +--- + +## Phase 4: Frontend (Week 4-5) + +**Goal:** Build the three-pane Studio interface. + +### Week 4: Project Setup & Core Components + +#### Project Structure + +``` +apps/studio/ +├── app/ +│ ├── layout.tsx +│ ├── page.tsx # Template selection +│ ├── auth/ +│ │ └── callback/page.tsx # OAuth callback +│ └── projects/ +│ └── [id]/ +│ └── page.tsx # Workspace +├── components/ +│ ├── templates/ +│ │ └── TemplateCard.tsx +│ ├── workspace/ +│ │ ├── ChatPane.tsx +│ │ ├── PlanPane.tsx +│ │ ├── PreviewPane.tsx +│ │ └── BuildProgress.tsx +│ └── ui/ # shadcn components +├── lib/ +│ ├── api.ts # rdev-api client +│ ├── sse.ts # SSE connection +│ └── store.ts # Zustand state +├── tailwind.config.ts +└── package.json +``` + +#### Core State Management + +```typescript +// lib/store.ts +import { create } from "zustand"; + +interface StudioState { + // Blueprint state + blueprint: Blueprint | null; + messages: Message[]; + + // Operation state + operation: Operation | null; + events: OperationEvent[]; + + // Actions + sendMessage: (message: string) => Promise; + startBuild: () => Promise; + subscribeToOperation: (operationId: string) => void; +} + +export const useStudio = create((set, get) => ({ + blueprint: null, + messages: [], + operation: null, + events: [], + + sendMessage: async (message) => { + const { blueprint } = get(); + const response = await api.chat(blueprint.id, message); + set({ + blueprint: response.blueprint, + messages: [ + ...get().messages, + { role: "user", content: message }, + { role: "architect", content: response.reply }, + ], + }); + }, + + startBuild: async () => { + const { blueprint } = get(); + const operation = await api.build(blueprint.id); + set({ operation }); + get().subscribeToOperation(operation.id); + }, + + subscribeToOperation: (operationId) => { + const eventSource = new EventSource( + `${API_URL}/operations/${operationId}/stream`, + ); + eventSource.onmessage = (e) => { + const event = JSON.parse(e.data); + set({ events: [...get().events, event] }); + if (event.type === "complete") { + eventSource.close(); + } + }; + }, +})); +``` + +#### Chat Pane Component + +```typescript +// components/workspace/ChatPane.tsx +export function ChatPane() { + const { messages, sendMessage, blueprint } = useStudio(); + const [input, setInput] = useState(''); + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + if (!input.trim()) return; + await sendMessage(input); + setInput(''); + }; + + return ( +
+
+ {messages.map((msg, i) => ( +
+ {msg.content} +
+ ))} +
+ +
+ setInput(e.target.value)} + placeholder="Describe what you want to build..." + className="w-full p-2 border rounded" + /> +
+
+ ); +} +``` + +#### Plan Pane Component + +```typescript +// components/workspace/PlanPane.tsx +export function PlanPane() { + const { blueprint } = useStudio(); + + if (!blueprint) return ; + + return ( +
+

{blueprint.feature}

+ + {/* Design References */} + {blueprint.references?.items?.length > 0 && ( +
+
+ {blueprint.references.items.map(ref => ( + + ))} +
+
+ )} + + {/* Design System (extracted from references) */} + {blueprint.sections.designSystem?.status !== 'empty' && ( +
+ + {blueprint.sections.designSystem.inspirationNotes && ( +

+ {blueprint.sections.designSystem.inspirationNotes} +

+ )} +
+ )} + +
+ {blueprint.sections.dataModel.entities.map(entity => ( + + ))} +
+ +
+ {blueprint.sections.apiEndpoints.endpoints.map(ep => ( + + ))} +
+ +
+ {blueprint.sections.uiComponents.components.map(comp => ( + + ))} +
+ + {blueprint.openQuestions.length > 0 && ( +
+

Open Questions

+
    + {blueprint.openQuestions.map(q => ( +
  • {q.question}
  • + ))} +
+
+ )} + + +
+ ); +} + +function ReferenceCard({ reference }: { reference: Reference }) { + return ( +
+ {reference.source} +

+ {reference.type === 'url' ? new URL(reference.source).hostname : 'Uploaded'} +

+
+ ); +} + +function DesignTokens({ tokens }: { tokens: DesignSystem }) { + return ( +
+ {tokens.colors?.length > 0 && ( +
+

Colors

+
+ {tokens.colors.map(c => ( +
+ ))} +
+
+ )} + {tokens.spacing?.length > 0 && ( +
+

Spacing

+

{tokens.spacing.join('px, ')}px

+
+ )} + {tokens.borderRadius && ( +
+

Border Radius

+

{tokens.borderRadius}

+
+ )} +
+ ); +} +``` + +### Week 5: Polish & Integration + +#### Preview Pane with Build Progress + +```typescript +// components/workspace/PreviewPane.tsx +export function PreviewPane() { + const { operation, events } = useStudio(); + const [previewUrl, setPreviewUrl] = useState(); + + // Update preview URL when build completes + useEffect(() => { + const completeEvent = events.find(e => e.type === 'complete'); + if (completeEvent?.url) { + setPreviewUrl(completeEvent.url); + } + }, [events]); + + return ( +
+ {/* Preview iframe */} +
+ {previewUrl ? ( +