Implements horizontally-scalable worker pool architecture: - claudebox-sidecar: HTTP server for Claude Code, git, and SDLC ops - rdev-worker: standalone worker binary polling rdev-api for tasks - HTTP client adapter for sidecar communication - HPA with custom Prometheus metrics for autoscaling - ServiceMonitor for metrics scraping Code review fixes applied: - URL-encode query parameters in GitStatus (Critical #1) - Remove unused shellQuote function (Critical #2) - Use stdlib strings.Split/TrimSpace (Critical #3) - Add version injection via ldflags (Warning #4) - Add debug logging for swallowed git/sdlc errors (Warning #5, #6) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1385 lines
42 KiB
Markdown
1385 lines
42 KiB
Markdown
# Orchard Studio: Implementation Roadmap
|
|
|
|
This roadmap converts the vision into executable phases with clear deliverables.
|
|
|
|
---
|
|
|
|
## Phase 0: Engine Validation (Current)
|
|
|
|
**Goal:** Prove the SDLC engine can autonomously build complex systems.
|
|
|
|
**Status:** In Progress (2 of 4 trees passing)
|
|
|
|
### 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
|
|
<!-- .claude/agents/architect.md -->
|
|
|
|
# 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<void>;
|
|
startBuild: () => Promise<void>;
|
|
subscribeToOperation: (operationId: string) => void;
|
|
}
|
|
|
|
export const useStudio = create<StudioState>((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 (
|
|
<div className="flex flex-col h-full">
|
|
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
|
{messages.map((msg, i) => (
|
|
<div key={i} className={cn(
|
|
"p-3 rounded-lg",
|
|
msg.role === 'user' ? "bg-blue-100 ml-8" : "bg-gray-100 mr-8"
|
|
)}>
|
|
{msg.content}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="p-4 border-t">
|
|
<input
|
|
value={input}
|
|
onChange={(e) => setInput(e.target.value)}
|
|
placeholder="Describe what you want to build..."
|
|
className="w-full p-2 border rounded"
|
|
/>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
#### Plan Pane Component
|
|
|
|
```typescript
|
|
// components/workspace/PlanPane.tsx
|
|
export function PlanPane() {
|
|
const { blueprint } = useStudio();
|
|
|
|
if (!blueprint) return <EmptyState />;
|
|
|
|
return (
|
|
<div className="p-4 space-y-6">
|
|
<h2 className="text-xl font-bold">{blueprint.feature}</h2>
|
|
|
|
{/* Design References */}
|
|
{blueprint.references?.items?.length > 0 && (
|
|
<Section title="Design References" status="complete">
|
|
<div className="flex gap-2 overflow-x-auto">
|
|
{blueprint.references.items.map(ref => (
|
|
<ReferenceCard key={ref.id} reference={ref} />
|
|
))}
|
|
</div>
|
|
</Section>
|
|
)}
|
|
|
|
{/* Design System (extracted from references) */}
|
|
{blueprint.sections.designSystem?.status !== 'empty' && (
|
|
<Section
|
|
title="Design System"
|
|
status={blueprint.sections.designSystem.status}
|
|
>
|
|
<DesignTokens tokens={blueprint.sections.designSystem} />
|
|
{blueprint.sections.designSystem.inspirationNotes && (
|
|
<p className="text-sm text-gray-600 mt-2 italic">
|
|
{blueprint.sections.designSystem.inspirationNotes}
|
|
</p>
|
|
)}
|
|
</Section>
|
|
)}
|
|
|
|
<Section
|
|
title="Data Model"
|
|
status={blueprint.sections.dataModel.status}
|
|
>
|
|
{blueprint.sections.dataModel.entities.map(entity => (
|
|
<EntityCard key={entity.name} entity={entity} />
|
|
))}
|
|
</Section>
|
|
|
|
<Section
|
|
title="API Endpoints"
|
|
status={blueprint.sections.apiEndpoints.status}
|
|
>
|
|
{blueprint.sections.apiEndpoints.endpoints.map(ep => (
|
|
<EndpointRow key={ep.path} endpoint={ep} />
|
|
))}
|
|
</Section>
|
|
|
|
<Section
|
|
title="UI Components"
|
|
status={blueprint.sections.uiComponents.status}
|
|
>
|
|
{blueprint.sections.uiComponents.components.map(comp => (
|
|
<ComponentCard key={comp.name} component={comp} />
|
|
))}
|
|
</Section>
|
|
|
|
{blueprint.openQuestions.length > 0 && (
|
|
<div className="bg-yellow-50 p-4 rounded">
|
|
<h3 className="font-semibold text-yellow-800">Open Questions</h3>
|
|
<ul className="list-disc list-inside">
|
|
{blueprint.openQuestions.map(q => (
|
|
<li key={q.id}>{q.question}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
|
|
<BuildButton
|
|
ready={blueprint.readyToBuild}
|
|
blockers={blueprint.blockers}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ReferenceCard({ reference }: { reference: Reference }) {
|
|
return (
|
|
<div className="flex-shrink-0 w-32">
|
|
<img
|
|
src={reference.thumbnail}
|
|
alt={reference.source}
|
|
className="w-full h-20 object-cover rounded border"
|
|
/>
|
|
<p className="text-xs text-gray-500 truncate mt-1">
|
|
{reference.type === 'url' ? new URL(reference.source).hostname : 'Uploaded'}
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function DesignTokens({ tokens }: { tokens: DesignSystem }) {
|
|
return (
|
|
<div className="space-y-3">
|
|
{tokens.colors?.length > 0 && (
|
|
<div>
|
|
<p className="text-xs font-medium text-gray-500 mb-1">Colors</p>
|
|
<div className="flex gap-1">
|
|
{tokens.colors.map(c => (
|
|
<div
|
|
key={c.name}
|
|
className="w-6 h-6 rounded border"
|
|
style={{ backgroundColor: c.value }}
|
|
title={`${c.name}: ${c.value}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
{tokens.spacing?.length > 0 && (
|
|
<div>
|
|
<p className="text-xs font-medium text-gray-500 mb-1">Spacing</p>
|
|
<p className="text-sm">{tokens.spacing.join('px, ')}px</p>
|
|
</div>
|
|
)}
|
|
{tokens.borderRadius && (
|
|
<div>
|
|
<p className="text-xs font-medium text-gray-500 mb-1">Border Radius</p>
|
|
<p className="text-sm">{tokens.borderRadius}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 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<string>();
|
|
|
|
// Update preview URL when build completes
|
|
useEffect(() => {
|
|
const completeEvent = events.find(e => e.type === 'complete');
|
|
if (completeEvent?.url) {
|
|
setPreviewUrl(completeEvent.url);
|
|
}
|
|
}, [events]);
|
|
|
|
return (
|
|
<div className="flex flex-col h-full">
|
|
{/* Preview iframe */}
|
|
<div className="flex-1 relative">
|
|
{previewUrl ? (
|
|
<iframe
|
|
src={previewUrl}
|
|
className="w-full h-full border-0"
|
|
sandbox="allow-scripts allow-same-origin allow-forms"
|
|
/>
|
|
) : (
|
|
<div className="flex items-center justify-center h-full bg-gray-50">
|
|
<p className="text-gray-500">Preview will appear after first build</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Build progress overlay */}
|
|
{operation && operation.status === 'running' && (
|
|
<BuildProgress events={events} />
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function BuildProgress({ events }: { events: OperationEvent[] }) {
|
|
const phases = ['spec', 'design', 'implement', 'test', 'deploy'];
|
|
|
|
return (
|
|
<div className="absolute inset-0 bg-black/50 flex items-center justify-center">
|
|
<div className="bg-white p-6 rounded-lg shadow-xl w-80">
|
|
<h3 className="font-bold mb-4">Building...</h3>
|
|
<ul className="space-y-2">
|
|
{phases.map(phase => {
|
|
const event = events.find(e => e.phase === phase);
|
|
const status = event?.status || 'pending';
|
|
|
|
return (
|
|
<li key={phase} className="flex items-center gap-2">
|
|
{status === 'complete' && <CheckIcon className="text-green-500" />}
|
|
{status === 'in_progress' && <Spinner />}
|
|
{status === 'pending' && <Circle className="text-gray-300" />}
|
|
<span className={cn(
|
|
status === 'in_progress' && 'font-semibold'
|
|
)}>
|
|
{phase.charAt(0).toUpperCase() + phase.slice(1)}
|
|
</span>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Phase 4 Exit Criteria
|
|
|
|
- [ ] Template selection page renders available seeds
|
|
- [ ] Three-pane workspace renders correctly
|
|
- [ ] Chat sends messages and displays responses
|
|
- [ ] Chat supports URL/image reference attachments (drag/drop, paste)
|
|
- [ ] Plan pane updates on each chat turn
|
|
- [ ] Plan pane displays reference thumbnails
|
|
- [ ] Plan pane renders extracted design tokens
|
|
- [ ] Build progress shows SSE events
|
|
- [ ] Preview iframe loads deployed app
|
|
- [ ] Preview refreshes on build complete
|
|
|
|
---
|
|
|
|
## Phase 5: Aeries Demo (Week 6)
|
|
|
|
**Goal:** Build Aeries (social simulation world) entirely through Studio.
|
|
|
|
### Demo Script
|
|
|
|
```
|
|
1. Open Orchard Studio
|
|
2. Click "Social World" template
|
|
3. Wait for project to spawn (shows "Live" badge)
|
|
|
|
4. Chat with Architect:
|
|
"I want agents that walk around a 2D world and have conversations"
|
|
|
|
5. Architect asks:
|
|
"How should agents decide who to talk to? Proximity-based, or interest matching?"
|
|
|
|
6. User: "Proximity - they talk to whoever is nearby"
|
|
|
|
7. Architect asks:
|
|
"Should conversations be visible to all users, or only when you click on an agent?"
|
|
|
|
8. User: "Visible as speech bubbles above agents"
|
|
|
|
9. Architect: "Plan looks complete. Ready to build?"
|
|
|
|
10. User: "Yes, build it"
|
|
|
|
11. Watch build progress:
|
|
✓ Creating spec
|
|
✓ Designing schema (agents, conversations, positions)
|
|
✓ Writing handlers (agent CRUD, movement, chat)
|
|
→ Running tests
|
|
○ Deploying
|
|
|
|
12. Build completes. Preview refreshes.
|
|
|
|
13. See agents walking and chatting in the preview.
|
|
```
|
|
|
|
### Success Criteria
|
|
|
|
- [ ] Social World template exists and provisions correctly
|
|
- [ ] Architect conversation produces coherent simulation spec
|
|
- [ ] Build executes without manual intervention
|
|
- [ ] Deployed app shows agents with movement and chat
|
|
- [ ] Total time from spawn to working demo < 15 minutes
|
|
|
|
---
|
|
|
|
## Phase 6: Platform Services (Parallel Track)
|
|
|
|
**Goal:** Add shared platform services that projects can opt into.
|
|
|
|
This work runs **parallel to Phases 1-5**. It focuses on the "upgrade existing projects" use case first.
|
|
|
|
### Service Rollout Order
|
|
|
|
Build infrastructure with the simplest service, then add complexity:
|
|
|
|
```
|
|
Logging ──► Email ──► Stats ──► Auth
|
|
│ │ │ │
|
|
│ │ │ └── Complex (middleware, user flows)
|
|
│ │ └── Frontend SDK + backend events
|
|
│ └── Simple API, clear success/failure
|
|
└── Pure infrastructure, no user code changes
|
|
```
|
|
|
|
### Phase 6a: Service Infrastructure (Week 3-4)
|
|
|
|
**Goal:** Build the provisioning and injection infrastructure.
|
|
|
|
```go
|
|
// internal/port/platform_provisioner.go
|
|
type PlatformProvisioner interface {
|
|
Provision(ctx context.Context, req ProvisionRequest) (*ProvisionResult, error)
|
|
Verify(ctx context.Context, projectID string, creds map[string]string) error
|
|
Deprovision(ctx context.Context, projectID string) error
|
|
}
|
|
```
|
|
|
|
**Deliverables:**
|
|
|
|
1. `project_services` table and domain model
|
|
2. Service catalog (YAML definition of available services)
|
|
3. Provisioner interface and base implementation
|
|
4. Credential encryption/storage
|
|
5. K8s secret injection
|
|
6. Integration PR creation flow
|
|
7. `POST /projects/{id}/services` endpoint
|
|
|
|
### Phase 6b: Logging Service (Week 4)
|
|
|
|
**Goal:** First concrete service - ship logs to centralized Loki.
|
|
|
|
```go
|
|
// internal/adapter/loki/provisioner.go
|
|
type LokiProvisioner struct {
|
|
lokiURL string
|
|
}
|
|
|
|
func (p *LokiProvisioner) Provision(ctx context.Context, req ProvisionRequest) (*ProvisionResult, error) {
|
|
tenantID := fmt.Sprintf("project-%s", req.ProjectID)
|
|
return &ProvisionResult{
|
|
Credentials: map[string]string{
|
|
"LOKI_URL": p.lokiURL,
|
|
"LOKI_TENANT_ID": tenantID,
|
|
},
|
|
}, nil
|
|
}
|
|
```
|
|
|
|
**Integration templates:**
|
|
|
|
- Go: Update slog config to ship to Loki
|
|
- Node: Add pino-loki transport
|
|
|
|
**Exit Criteria:**
|
|
|
|
- [ ] `POST /projects/{id}/services` with `type: logging` works
|
|
- [ ] Credentials injected into K8s secrets
|
|
- [ ] Integration PR created with logger config
|
|
- [ ] After merge, logs appear in Loki
|
|
|
|
### Phase 6c: Email Service (Week 5)
|
|
|
|
**Goal:** Transactional email via Resend.
|
|
|
|
```go
|
|
// internal/adapter/resend/provisioner.go
|
|
type ResendProvisioner struct {
|
|
masterKey string
|
|
}
|
|
|
|
func (p *ResendProvisioner) Provision(ctx context.Context, req ProvisionRequest) (*ProvisionResult, error) {
|
|
// Create scoped API key (or use master with project tracking)
|
|
apiKey := p.createAPIKey(req.ProjectName)
|
|
return &ProvisionResult{
|
|
Credentials: map[string]string{
|
|
"RESEND_API_KEY": apiKey,
|
|
},
|
|
Config: map[string]string{
|
|
"RESEND_FROM_DOMAIN": fmt.Sprintf("%s.threesix.ai", req.ProjectName),
|
|
},
|
|
}, nil
|
|
}
|
|
```
|
|
|
|
**Integration templates:**
|
|
|
|
- Go: `internal/email/client.go` with Resend SDK
|
|
- Node: `lib/email.ts` with Resend SDK
|
|
|
|
**Exit Criteria:**
|
|
|
|
- [ ] Email service can be added to existing projects
|
|
- [ ] API key provisioned and injected
|
|
- [ ] Integration PR includes email client code
|
|
- [ ] Test email sends successfully
|
|
|
|
### Phase 6d: Stats Service (Week 5-6)
|
|
|
|
**Goal:** Product analytics via PostHog.
|
|
|
|
**Integration templates:**
|
|
|
|
- Node: AnalyticsProvider component, posthog-js setup
|
|
- Go: Backend event tracking
|
|
|
|
**Exit Criteria:**
|
|
|
|
- [ ] PostHog project created per rdev project
|
|
- [ ] Frontend tracking code added
|
|
- [ ] Events flowing to PostHog dashboard
|
|
|
|
### Phase 6e: Auth Service (Week 6-7)
|
|
|
|
**Goal:** User authentication via Clerk.
|
|
|
|
This is the most complex service - affects routes, middleware, user model.
|
|
|
|
**Integration templates:**
|
|
|
|
- Middleware for protected routes
|
|
- ClerkProvider component
|
|
- User model integration
|
|
|
|
**Exit Criteria:**
|
|
|
|
- [ ] Clerk application created per project
|
|
- [ ] Auth middleware added
|
|
- [ ] Sign-in/sign-up flows working
|
|
|
|
---
|
|
|
|
## Phase 7: Dual Environments (Week 6-7)
|
|
|
|
**Goal:** Add staging/production environment separation.
|
|
|
|
**Depends on:** Phase 6 (services need to be environment-aware)
|
|
|
|
### Week 6: Infrastructure
|
|
|
|
**Deliverables:**
|
|
|
|
1. DNS: Create both `staging.X` and `X` records per project
|
|
2. K8s: Two deployments per project (or two namespaces)
|
|
3. Database: Separate staging/production databases in CockroachDB
|
|
4. Secrets: Environment-scoped secret management
|
|
|
|
```sql
|
|
ALTER TABLE projects ADD COLUMN environments JSONB NOT NULL DEFAULT '{
|
|
"staging": {"enabled": true, "deployed_at": null},
|
|
"production": {"enabled": false, "deployed_at": null}
|
|
}';
|
|
```
|
|
|
|
### Week 7: Publish Flow
|
|
|
|
**Deliverables:**
|
|
|
|
1. `POST /projects/{id}/publish` endpoint
|
|
2. Publish flow: validate → provision → migrate → deploy → verify
|
|
3. Studio UI: Publish button, environment switcher
|
|
|
|
```
|
|
POST /projects/{id}/publish
|
|
{
|
|
"fromEnvironment": "staging",
|
|
"toEnvironment": "production"
|
|
}
|
|
```
|
|
|
|
**Exit Criteria:**
|
|
|
|
- [ ] New projects get both environments
|
|
- [ ] "Build It" deploys to staging only
|
|
- [ ] "Publish" promotes staging to production
|
|
- [ ] Services have separate credentials per environment
|
|
- [ ] Environment switcher in Preview pane
|
|
|
|
---
|
|
|
|
## Milestone Summary
|
|
|
|
| Phase | Duration | Key Deliverable |
|
|
| -------------------- | -------- | ----------------------------------------------- |
|
|
| 0. Engine | Now | `slackpath-1` works |
|
|
| 1. Blueprint API | Week 1-2 | Chat endpoint + storage + **design references** |
|
|
| 2. Operations | Week 2-3 | SSE streaming + DB tracking |
|
|
| 3. Integration | Week 3 | Blueprint → SDLC → Build |
|
|
| 4. Frontend | Week 4-5 | Three-pane Studio UI (with reference display) |
|
|
| 5. Demo | Week 6 | Aeries via Studio |
|
|
| 6. Platform Services | Week 3-7 | Logging → Email → Stats → Auth (parallel track) |
|
|
| 7. Dual Environments | Week 6-7 | Staging + Production, Publish flow |
|
|
|
|
**Total:** ~7 weeks to full platform
|
|
|
|
### Parallel Tracks
|
|
|
|
```
|
|
Week: 1 2 3 4 5 6 7
|
|
│ │ │ │ │ │ │
|
|
├────┴────┴────┴────┴────┴────┤ Studio Track (Phases 0-5)
|
|
│ │
|
|
│ ├────┴────┴────┴────┤ Services Track (Phase 6)
|
|
│ │ │ │ │ │
|
|
│ │ │ │ ├────┤ Environments (Phase 7)
|
|
```
|
|
|
|
- **Studio Track:** Core product experience (Blueprint → Build → Preview)
|
|
- **Services Track:** Platform capabilities (Logging → Email → Stats → Auth)
|
|
- **Environments Track:** Staging/Production separation (depends on Services)
|
|
|
|
**Design Reference Flow:** Integrated into Phase 1 (backend) and Phase 4 (frontend).
|
|
|
|
**Service Rollout:** Each service builds on the infrastructure. Start with Logging (simplest), end with Auth (most complex).
|
|
|
|
---
|
|
|
|
## Definition of Done: Full Platform
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────────────┐
|
|
│ User clicks "Social World" template │
|
|
│ ↓ │
|
|
│ Project spawns: K8s namespace, DB, git repo, DNS │
|
|
│ Live URL: https://aeries-demo.threesix.ai (shows skeleton) │
|
|
│ ↓ │
|
|
│ User chats with Architect to define agent simulation │
|
|
│ Plan pane shows data model, endpoints, components │
|
|
│ ↓ │
|
|
│ User clicks "Build It" │
|
|
│ Progress shows: spec → design → implement → test → deploy │
|
|
│ ↓ │
|
|
│ Build completes. Preview refreshes. │
|
|
│ User sees agents walking and chatting. │
|
|
│ ↓ │
|
|
│ User continues: "Add a weather system" │
|
|
│ Architect updates plan. Another build cycle. │
|
|
│ Weather appears in preview. │
|
|
│ ↓ │
|
|
│ 🎉 Software built through conversation. │
|
|
└─────────────────────────────────────────────────────────────────────────────────┘
|
|
```
|