diff --git a/CLAUDE.md b/CLAUDE.md index f4584a1..a32aa4e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -70,6 +70,7 @@ When discussing code: "add to **platform**" = edit rdev; "add to **skeleton**" = - **Migrations:** NEVER modify committed migrations. Create NEW ones. - **500-line limit:** Files exceeding 500 lines must be split - **Tests:** All handlers and services require tests +- **No fallbacks:** NEVER design "try X, fall back to Y" flows — fix X. Fallbacks hide errors and deliver inferior experiences. - **Multi-step ops:** NEVER log-and-continue after partial failure. Rollback or document partial state. - **Logging:** Use `logging.FromContext(ctx)` or injected `*slog.Logger`. NEVER `fmt.Println`, `log.Fatal`, `log.Printf`, or bare `slog.Info()`. Error key is ALWAYS `"error"` (not `"err"`). Use field constants from `internal/logging/fields.go` (e.g., `logging.FieldProjectID`, `logging.FieldError`). Log once at boundary (handlers/workers log, services return errors). Sensitive data (passwords, tokens, keys) is auto-redacted. - **HTTP clients:** NEVER create `&http.Client{}` without a `Timeout` field. All HTTP clients must have explicit timeouts (30s standard, 5s for health checks). A bare client can hang indefinitely. diff --git a/cmd/rdev-api/main.go b/cmd/rdev-api/main.go index a039aaa..a301c66 100644 --- a/cmd/rdev-api/main.go +++ b/cmd/rdev-api/main.go @@ -276,6 +276,7 @@ func main() { blueprintService, agentRegistry, projectRepo, + nil, // uses defaults: claudebox-0, rdev namespace ) // Create question service (for Foundary structured questions) diff --git a/cookbooks/trees/foundary.yaml b/cookbooks/trees/foundary.yaml index 110faf9..f85c4ad 100644 --- a/cookbooks/trees/foundary.yaml +++ b/cookbooks/trees/foundary.yaml @@ -1,13 +1,9 @@ name: foundary -description: "Foundary Studio: Conversational product development lifecycle. Bootstraps a React+API+DB project, specs two features via SDLC, implements with build agents, reviews, merges, and releases." -version: 1 +description: "Foundary Studio: Bootstrap a React+API+DB project, design via architect conversation, build with natural language prompts, and verify the live site." +version: 2 vars: project_name: "" - feature_1_slug: "data-models" - feature_1_title: "Core Data Models & Persistence" - feature_2_slug: "task-management-ui" - feature_2_title: "Task Management UI" steps: # ============================================================ @@ -21,9 +17,9 @@ steps: endpoint: /project/create-and-build body: name: "{{ .vars.project_name }}" - description: "Foundary Studio: Conversational product development" + description: "Foundary Studio: Task management with Kanban board" template: "skeleton" - prompt: "Set up the monorepo workspace. Ensure the root README describes a product studio for conversational product development." + prompt: "Set up the monorepo workspace. Ensure the root README describes a task management studio with Kanban board, REST API, and Postgres persistence." auto_commit: true auto_push: true outputs: @@ -71,25 +67,14 @@ steps: max_attempts: 60 on_error: continue - verify-sdlc: - description: "Verify SDLC state is initialized" - depends_on: [verify-site] - action: api - method: GET - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/state" - outputs: - - sdlc_initialized: .data.initialized - # ============================================================ - # SECTION 2: ARCHITECT - # Conversational product design via architect API. - # Tries the full conversational flow (start → refine → generate). - # Falls back to direct blueprint creation if agent isn't available. + # SECTION 2: DESIGN & BUILD + # Architect conversation flow, then 3 iterative builds with + # natural language prompts (no slash commands). # ============================================================ architect-start: description: "Start architect conversation about product goals" - depends_on: [verify-sdlc] - on_error: continue + depends_on: [verify-site] action: api method: POST endpoint: "/projects/{{ .outputs.create-project.project_id }}/architect/start" @@ -101,17 +86,15 @@ steps: architect-refine: description: "Refine architecture with component details" depends_on: [architect-start] - on_error: continue action: api method: POST endpoint: "/projects/{{ .outputs.create-project.project_id }}/architect/continue/{{ .outputs.architect-start.conversation_id }}" body: - message: "Good. Let's define exactly two features for the MVP: Feature 1 'data-models' covers the persistence layer (Task, Project, Label, Assignment entities, migrations, repository layer, service layer, handler tests). Feature 2 'task-management-ui' covers the React frontend (Kanban board, task CRUD modals, label/assignee filters). Feature 2 depends on Feature 1 being complete since it consumes the API. Please confirm this breakdown and note any architectural considerations." + message: "Good. Let's define exactly two features for the MVP: Feature 1 covers the persistence layer (Task, Project, Label, Assignment entities, migrations, repository layer, service layer, handler tests). Feature 2 covers the React frontend (Kanban board, task CRUD modals, label/assignee filters). Feature 2 depends on Feature 1 being complete since it consumes the API. Please confirm this breakdown and note any architectural considerations." architect-generate-blueprint: description: "Generate structured blueprint from conversation" depends_on: [architect-refine] - on_error: continue action: api method: POST endpoint: "/projects/{{ .outputs.create-project.project_id }}/architect/generate-blueprint/{{ .outputs.architect-start.conversation_id }}" @@ -120,903 +103,212 @@ steps: outputs: - blueprint_id: .data.blueprint.id - architect-create-blueprint-fallback: - description: "Fallback: create blueprint directly if conversational flow unavailable" + # --- Build 1: Data models, migrations, repository, handlers on studio-api --- + build-api: + description: "Build studio-api: data models, migrations, repository, handlers" depends_on: [architect-generate-blueprint] - on_error: continue - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/blueprints" - body: - name: "foundary-studio-mvp" - description: "Task management studio MVP blueprint" - spec: - version: "1.0" - architecture: - type: "monorepo" - components: - - name: studio-ui - type: app-react - description: "React frontend with Kanban board" - - name: studio-api - type: service - description: "REST API for task management" - - name: studio-db - type: postgres - description: "PostgreSQL database for persistence" - features: - - slug: data-models - title: "Core Data Models & Persistence" - priority: high - - slug: task-management-ui - title: "Task Management UI" - priority: high - outputs: - - fallback_blueprint_id: .data.id - - # ============================================================ - # SECTION 3: FEATURE 1 — Core Data Models (draft → released) - # Full 10-phase SDLC lifecycle - # ============================================================ - - # --- Phase 1: Draft --- - f1-create-feature: - description: "Create data-models feature in draft phase" - depends_on: [architect-create-blueprint-fallback] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features" - body: - slug: "{{ .vars.feature_1_slug }}" - title: "{{ .vars.feature_1_title }}" - outputs: - - feature_phase: .data.phase - - f1-verify-draft: - description: "Verify feature 1 is in draft phase" - depends_on: [f1-create-feature] - action: shell - command: | - PHASE="{{ .outputs.f1-create-feature.feature_phase }}" - if [ "$PHASE" == "draft" ]; then - echo "Feature 1 created in draft phase" - exit 0 - else - echo "Expected draft, got $PHASE" - exit 1 - fi - - # --- Phase 2: Draft → Specified --- - f1-write-spec: - description: "Agent writes spec for data models" - depends_on: [f1-verify-draft] action: api method: POST endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" body: - prompt: "/spec-feature {{ .vars.feature_1_slug }} --requirements 'Define Task, Project, Label, and Assignment entities with full CRUD. Postgres storage via studio-db. REST endpoints on studio-api. Include migrations, repository layer, service layer, and handler tests.'" + prompt: | + Build the studio-api service with these requirements: + + 1. Data models: Define Task, Project, Label, and Assignment entities. + - Task: id, title, description, status (todo/in_progress/done), project_id, assignee_id, created_at, updated_at + - Project: id, name, description, created_at + - Label: id, name, color, project_id + - Assignment: id, task_id, label_id (many-to-many join) + + 2. Database: Create SQL migrations for all tables with foreign keys and indexes. + Use the studio-db Postgres connection (DATABASE_URL env var). + + 3. Repository layer: Implement CRUD operations for each entity using sqlx. + + 4. Service layer: Business logic for task lifecycle (create, update status, assign labels). + + 5. HTTP handlers: RESTful endpoints mounted at /api/: + - GET/POST /api/projects, GET/PUT/DELETE /api/projects/:id + - GET/POST /api/tasks, GET/PUT/DELETE /api/tasks/:id + - GET/POST /api/labels, GET/PUT/DELETE /api/labels/:id + - POST/DELETE /api/tasks/:id/labels/:label_id + + 6. Tests: Add handler tests for the task CRUD endpoints. + + Work in the components/studio-api/ directory. auto_commit: true auto_push: true git_clone_url: "{{ .outputs.create-project.git_clone_http }}" outputs: - build_id: .data.task_id - f1-wait-spec: - depends_on: [f1-write-spec] + wait-build-api: + description: "Wait for API build to complete" + depends_on: [build-api] action: wait_build - build_id: "{{ .outputs.f1-write-spec.build_id }}" + build_id: "{{ .outputs.build-api.build_id }}" max_attempts: 720 poll_interval: 5 - f1-approve-spec: - description: "Approve spec artifact" - depends_on: [f1-wait-spec] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/artifacts/spec/approve" - body: - comment: "Spec approved by foundary automation" - - f1-transition-to-specified: - description: "Transition from draft to specified" - depends_on: [f1-approve-spec] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/transition" - body: - phase: "specified" - outputs: - - new_phase: .data.phase - - # --- Phase 3: Specified → Planned --- - f1-write-design: - description: "Agent writes design for data models" - depends_on: [f1-transition-to-specified] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" - body: - prompt: "/design-feature {{ .vars.feature_1_slug }}" - auto_commit: true - auto_push: true - git_clone_url: "{{ .outputs.create-project.git_clone_http }}" - outputs: - - build_id: .data.task_id - - f1-wait-design: - depends_on: [f1-write-design] - action: wait_build - build_id: "{{ .outputs.f1-write-design.build_id }}" - max_attempts: 720 - poll_interval: 5 - - f1-approve-design: - description: "Approve design artifact" - depends_on: [f1-wait-design] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/artifacts/design/approve" - body: - comment: "Design approved by foundary automation" - - f1-write-tasks: - description: "Agent breaks down into tasks" - depends_on: [f1-approve-design] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" - body: - prompt: "/breakdown-feature {{ .vars.feature_1_slug }}" - auto_commit: true - auto_push: true - git_clone_url: "{{ .outputs.create-project.git_clone_http }}" - outputs: - - build_id: .data.task_id - - f1-wait-tasks: - depends_on: [f1-write-tasks] - action: wait_build - build_id: "{{ .outputs.f1-write-tasks.build_id }}" - max_attempts: 720 - poll_interval: 5 - - f1-approve-tasks: - description: "Approve tasks artifact" - depends_on: [f1-wait-tasks] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/artifacts/tasks/approve" - body: - comment: "Tasks approved by foundary automation" - - f1-write-qa-plan: - description: "Agent writes QA plan" - depends_on: [f1-approve-tasks] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" - body: - prompt: "/create-qa-plan {{ .vars.feature_1_slug }}" - auto_commit: true - auto_push: true - git_clone_url: "{{ .outputs.create-project.git_clone_http }}" - outputs: - - build_id: .data.task_id - - f1-wait-qa-plan: - depends_on: [f1-write-qa-plan] - action: wait_build - build_id: "{{ .outputs.f1-write-qa-plan.build_id }}" - max_attempts: 720 - poll_interval: 5 - - f1-approve-qa-plan: - description: "Approve QA plan artifact" - depends_on: [f1-wait-qa-plan] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/artifacts/qa_plan/approve" - body: - comment: "QA plan approved by foundary automation" - - f1-transition-to-planned: - description: "Transition from specified to planned" - depends_on: [f1-approve-qa-plan] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/transition" - body: - phase: "planned" - outputs: - - new_phase: .data.phase - - # --- Phase 4: Planned → Ready --- - f1-create-branch: - description: "Create feature branch for data-models" - depends_on: [f1-transition-to-planned] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/branches" - outputs: - - branch_name: .data.name - - f1-transition-to-ready: - description: "Transition from planned to ready" - depends_on: [f1-create-branch] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/transition" - body: - phase: "ready" - outputs: - - new_phase: .data.phase - - # --- Phase 5: Ready → Implementation --- - f1-implement: - description: "Agent implements data models feature" - depends_on: [f1-transition-to-ready] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" - body: - prompt: "/implement-feature {{ .vars.feature_1_slug }}" - auto_commit: true - auto_push: true - git_clone_url: "{{ .outputs.create-project.git_clone_http }}" - outputs: - - build_id: .data.task_id - - f1-wait-implement: - depends_on: [f1-implement] - action: wait_build - build_id: "{{ .outputs.f1-implement.build_id }}" - max_attempts: 720 - poll_interval: 5 - - f1-wait-deploy-impl: - description: "Wait for implementation to deploy" - depends_on: [f1-wait-implement] + wait-deploy-api: + description: "Wait for API deployment pipeline" + depends_on: [wait-build-api] action: wait_pipeline project_id: "{{ .outputs.create-project.project_id }}" max_attempts: 720 - f1-transition-to-implementation: - description: "Transition to implementation phase" - depends_on: [f1-wait-deploy-impl] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/transition" - body: - phase: "implementation" - outputs: - - new_phase: .data.phase - - # --- Phase 6: Implementation → Review --- - f1-write-review: - description: "Agent writes code review" - depends_on: [f1-transition-to-implementation] + # --- Build 2: React Kanban UI, API client, task CRUD modals on studio-ui --- + build-ui: + description: "Build studio-ui: Kanban board, API client, task modals" + depends_on: [wait-deploy-api] action: api method: POST endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" body: - prompt: "/review-feature {{ .vars.feature_1_slug }}" + prompt: | + Build the studio-ui React frontend with these requirements: + + 1. API client: Create a typed fetch wrapper that calls the studio-api endpoints. + Base URL should be configurable via VITE_API_URL env var, defaulting to /api. + + 2. Kanban board: Three columns (To Do, In Progress, Done). + - Display tasks as cards with title, description preview, and labels. + - Implement drag-and-drop between columns using @dnd-kit/core. + - Moving a card between columns calls PUT /api/tasks/:id to update status. + + 3. Task modals: Create/Edit modal with fields for title, description, and label selection. + - Create: POST /api/tasks + - Edit: PUT /api/tasks/:id + - Delete: confirmation dialog, then DELETE /api/tasks/:id + + 4. Project selector: Dropdown to switch between projects (GET /api/projects). + + 5. Label/assignee filters: Filter bar above the Kanban board. + + 6. Layout: Clean dashboard layout with header, project selector, filter bar, and board. + + Work in the components/studio-ui/ directory. Use Tailwind CSS for styling. auto_commit: true auto_push: true git_clone_url: "{{ .outputs.create-project.git_clone_http }}" outputs: - build_id: .data.task_id - f1-wait-review: - depends_on: [f1-write-review] + wait-build-ui: + description: "Wait for UI build to complete" + depends_on: [build-ui] action: wait_build - build_id: "{{ .outputs.f1-write-review.build_id }}" + build_id: "{{ .outputs.build-ui.build_id }}" max_attempts: 720 poll_interval: 5 - f1-pass-review: - description: "Mark review as passed" - depends_on: [f1-wait-review] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/artifacts/review/pass" - - f1-transition-to-review: - description: "Transition to review phase" - depends_on: [f1-pass-review] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/transition" - body: - phase: "review" - outputs: - - new_phase: .data.phase - - # --- Phase 7: Review → Audit --- - f1-write-audit: - description: "Agent writes security audit" - depends_on: [f1-transition-to-review] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" - body: - prompt: "/audit-feature {{ .vars.feature_1_slug }}" - auto_commit: true - auto_push: true - git_clone_url: "{{ .outputs.create-project.git_clone_http }}" - outputs: - - build_id: .data.task_id - - f1-wait-audit: - depends_on: [f1-write-audit] - action: wait_build - build_id: "{{ .outputs.f1-write-audit.build_id }}" - max_attempts: 720 - poll_interval: 5 - - f1-pass-audit: - description: "Mark audit as passed" - depends_on: [f1-wait-audit] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/artifacts/audit/pass" - - f1-transition-to-audit: - description: "Transition to audit phase" - depends_on: [f1-pass-audit] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/transition" - body: - phase: "audit" - outputs: - - new_phase: .data.phase - - # --- Phase 8: Audit → QA --- - f1-run-qa: - description: "Agent runs QA plan" - depends_on: [f1-transition-to-audit] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" - body: - prompt: "/run-qa {{ .vars.feature_1_slug }}" - auto_commit: true - auto_push: true - git_clone_url: "{{ .outputs.create-project.git_clone_http }}" - outputs: - - build_id: .data.task_id - - f1-wait-qa: - depends_on: [f1-run-qa] - action: wait_build - build_id: "{{ .outputs.f1-run-qa.build_id }}" - max_attempts: 720 - poll_interval: 5 - - f1-pass-qa: - description: "Mark QA as passed" - depends_on: [f1-wait-qa] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/artifacts/qa_results/pass" - - f1-transition-to-qa: - description: "Transition to QA phase" - depends_on: [f1-pass-qa] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/transition" - body: - phase: "qa" - outputs: - - new_phase: .data.phase - - # --- Phase 9: QA → Merge --- - f1-transition-to-merge: - description: "Transition to merge phase" - depends_on: [f1-transition-to-qa] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/transition" - body: - phase: "merge" - outputs: - - new_phase: .data.phase - - f1-merge: - description: "Merge data-models feature branch to main" - depends_on: [f1-transition-to-merge] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/merge" - body: - strategy: "squash" - outputs: - - merge_commit: .data.commit_sha - - f1-wait-merge-deploy: - description: "Wait for merged code to deploy" - depends_on: [f1-merge] + wait-deploy-ui: + description: "Wait for UI deployment pipeline" + depends_on: [wait-build-ui] action: wait_pipeline project_id: "{{ .outputs.create-project.project_id }}" max_attempts: 720 - # --- Phase 10: Merge → Released --- - # Note: sdlc merge already transitions to released internally. - # This step is a safety net; on_error: continue handles the case - # where the feature is already at released. - f1-transition-to-released: - description: "Transition to released phase (may already be done by merge)" - depends_on: [f1-wait-merge-deploy] - on_error: continue - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/transition" - body: - phase: "released" - - f1-archive: - description: "Archive data-models feature" - depends_on: [f1-transition-to-released] - on_error: continue - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_1_slug }}/archive" - - # ============================================================ - # SECTION 4: FEATURE 2 — Task Management UI (draft → released) - # Spec starts after Feature 1 spec (independent) - # Design depends on Feature 1 reaching planned (schema defined) - # Implementation depends on Feature 1 released (schema in main) - # ============================================================ - - # --- Phase 1: Draft --- - # Feature 2 creation can start after Feature 1 spec is approved (parallel speccing) - f2-create-feature: - description: "Create task-management-ui feature in draft phase" - depends_on: [f1-approve-spec] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features" - body: - slug: "{{ .vars.feature_2_slug }}" - title: "{{ .vars.feature_2_title }}" - outputs: - - feature_phase: .data.phase - - f2-verify-draft: - description: "Verify feature 2 is in draft phase" - depends_on: [f2-create-feature] - action: shell - command: | - PHASE="{{ .outputs.f2-create-feature.feature_phase }}" - if [ "$PHASE" == "draft" ]; then - echo "Feature 2 created in draft phase" - exit 0 - else - echo "Expected draft, got $PHASE" - exit 1 - fi - - # --- Phase 2: Draft → Specified --- - f2-write-spec: - description: "Agent writes spec for task management UI" - depends_on: [f2-verify-draft] + # --- Build 3: Polish — CORS, error handling, health check, README --- + build-polish: + description: "Polish: CORS, error handling, health check, README" + depends_on: [wait-deploy-ui] action: api method: POST endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" body: - prompt: "/spec-feature {{ .vars.feature_2_slug }} --requirements 'React UI in studio-ui for managing tasks. Kanban board view with drag-and-drop columns (To Do, In Progress, Done). Task creation/edit modal with title, description, label, assignee fields. Filter by label and assignee. Connects to studio-api REST endpoints for Task CRUD.'" + prompt: | + Polish the Foundary Studio application: + + 1. CORS: Ensure studio-api allows requests from the studio-ui origin. + Add CORS middleware that allows GET, POST, PUT, DELETE, OPTIONS with credentials. + + 2. Error handling: Add proper error responses (400, 404, 500) with JSON error bodies + from studio-api handlers. Add error toast notifications in studio-ui. + + 3. Health check: Add GET /api/health endpoint on studio-api that checks database + connectivity and returns {"status": "ok", "service": "studio-api"}. + + 4. README: Update the root README.md with: + - Project description (task management studio) + - Architecture overview (monorepo with studio-ui, studio-api, studio-db) + - API endpoint reference + - Local development instructions auto_commit: true auto_push: true git_clone_url: "{{ .outputs.create-project.git_clone_http }}" outputs: - build_id: .data.task_id - f2-wait-spec: - depends_on: [f2-write-spec] + wait-build-polish: + description: "Wait for polish build to complete" + depends_on: [build-polish] action: wait_build - build_id: "{{ .outputs.f2-write-spec.build_id }}" + build_id: "{{ .outputs.build-polish.build_id }}" max_attempts: 720 poll_interval: 5 - f2-approve-spec: - description: "Approve spec artifact" - depends_on: [f2-wait-spec] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/artifacts/spec/approve" - body: - comment: "Spec approved by foundary automation" - - f2-transition-to-specified: - description: "Transition from draft to specified" - depends_on: [f2-approve-spec] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/transition" - body: - phase: "specified" - outputs: - - new_phase: .data.phase - - # --- Phase 3: Specified → Planned --- - # Design depends on Feature 1 reaching planned (schema must be defined first) - f2-write-design: - description: "Agent writes design for task management UI (after F1 schema planned)" - depends_on: [f2-transition-to-specified, f1-transition-to-planned] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" - body: - prompt: "/design-feature {{ .vars.feature_2_slug }}" - auto_commit: true - auto_push: true - git_clone_url: "{{ .outputs.create-project.git_clone_http }}" - outputs: - - build_id: .data.task_id - - f2-wait-design: - depends_on: [f2-write-design] - action: wait_build - build_id: "{{ .outputs.f2-write-design.build_id }}" - max_attempts: 720 - poll_interval: 5 - - f2-approve-design: - description: "Approve design artifact" - depends_on: [f2-wait-design] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/artifacts/design/approve" - body: - comment: "Design approved by foundary automation" - - f2-write-tasks: - description: "Agent breaks down into tasks" - depends_on: [f2-approve-design] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" - body: - prompt: "/breakdown-feature {{ .vars.feature_2_slug }}" - auto_commit: true - auto_push: true - git_clone_url: "{{ .outputs.create-project.git_clone_http }}" - outputs: - - build_id: .data.task_id - - f2-wait-tasks: - depends_on: [f2-write-tasks] - action: wait_build - build_id: "{{ .outputs.f2-write-tasks.build_id }}" - max_attempts: 720 - poll_interval: 5 - - f2-approve-tasks: - description: "Approve tasks artifact" - depends_on: [f2-wait-tasks] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/artifacts/tasks/approve" - body: - comment: "Tasks approved by foundary automation" - - f2-write-qa-plan: - description: "Agent writes QA plan" - depends_on: [f2-approve-tasks] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" - body: - prompt: "/create-qa-plan {{ .vars.feature_2_slug }}" - auto_commit: true - auto_push: true - git_clone_url: "{{ .outputs.create-project.git_clone_http }}" - outputs: - - build_id: .data.task_id - - f2-wait-qa-plan: - depends_on: [f2-write-qa-plan] - action: wait_build - build_id: "{{ .outputs.f2-write-qa-plan.build_id }}" - max_attempts: 720 - poll_interval: 5 - - f2-approve-qa-plan: - description: "Approve QA plan artifact" - depends_on: [f2-wait-qa-plan] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/artifacts/qa_plan/approve" - body: - comment: "QA plan approved by foundary automation" - - f2-transition-to-planned: - description: "Transition from specified to planned" - depends_on: [f2-approve-qa-plan] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/transition" - body: - phase: "planned" - outputs: - - new_phase: .data.phase - - # --- Phase 4: Planned → Ready --- - f2-create-branch: - description: "Create feature branch for task-management-ui" - depends_on: [f2-transition-to-planned] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/branches" - outputs: - - branch_name: .data.name - - f2-transition-to-ready: - description: "Transition from planned to ready" - depends_on: [f2-create-branch] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/transition" - body: - phase: "ready" - outputs: - - new_phase: .data.phase - - # --- Phase 5: Ready → Implementation --- - # Implementation depends on Feature 1 being released (schema in main) - f2-implement: - description: "Agent implements task management UI (after F1 released to main)" - depends_on: [f2-transition-to-ready, f1-transition-to-released] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" - body: - prompt: "/implement-feature {{ .vars.feature_2_slug }}" - auto_commit: true - auto_push: true - git_clone_url: "{{ .outputs.create-project.git_clone_http }}" - outputs: - - build_id: .data.task_id - - f2-wait-implement: - depends_on: [f2-implement] - action: wait_build - build_id: "{{ .outputs.f2-implement.build_id }}" - max_attempts: 720 - poll_interval: 5 - - f2-wait-deploy-impl: - description: "Wait for implementation to deploy" - depends_on: [f2-wait-implement] + wait-deploy-polish: + description: "Wait for polish deployment pipeline" + depends_on: [wait-build-polish] action: wait_pipeline project_id: "{{ .outputs.create-project.project_id }}" max_attempts: 720 - f2-transition-to-implementation: - description: "Transition to implementation phase" - depends_on: [f2-wait-deploy-impl] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/transition" - body: - phase: "implementation" - outputs: - - new_phase: .data.phase - - # --- Phase 6: Implementation → Review --- - f2-write-review: - description: "Agent writes code review" - depends_on: [f2-transition-to-implementation] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" - body: - prompt: "/review-feature {{ .vars.feature_2_slug }}" - auto_commit: true - auto_push: true - git_clone_url: "{{ .outputs.create-project.git_clone_http }}" - outputs: - - build_id: .data.task_id - - f2-wait-review: - depends_on: [f2-write-review] - action: wait_build - build_id: "{{ .outputs.f2-write-review.build_id }}" - max_attempts: 720 - poll_interval: 5 - - f2-pass-review: - description: "Mark review as passed" - depends_on: [f2-wait-review] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/artifacts/review/pass" - - f2-transition-to-review: - description: "Transition to review phase" - depends_on: [f2-pass-review] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/transition" - body: - phase: "review" - outputs: - - new_phase: .data.phase - - # --- Phase 7: Review → Audit --- - f2-write-audit: - description: "Agent writes security audit" - depends_on: [f2-transition-to-review] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" - body: - prompt: "/audit-feature {{ .vars.feature_2_slug }}" - auto_commit: true - auto_push: true - git_clone_url: "{{ .outputs.create-project.git_clone_http }}" - outputs: - - build_id: .data.task_id - - f2-wait-audit: - depends_on: [f2-write-audit] - action: wait_build - build_id: "{{ .outputs.f2-write-audit.build_id }}" - max_attempts: 720 - poll_interval: 5 - - f2-pass-audit: - description: "Mark audit as passed" - depends_on: [f2-wait-audit] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/artifacts/audit/pass" - - f2-transition-to-audit: - description: "Transition to audit phase" - depends_on: [f2-pass-audit] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/transition" - body: - phase: "audit" - outputs: - - new_phase: .data.phase - - # --- Phase 8: Audit → QA --- - f2-run-qa: - description: "Agent runs QA plan" - depends_on: [f2-transition-to-audit] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds" - body: - prompt: "/run-qa {{ .vars.feature_2_slug }}" - auto_commit: true - auto_push: true - git_clone_url: "{{ .outputs.create-project.git_clone_http }}" - outputs: - - build_id: .data.task_id - - f2-wait-qa: - depends_on: [f2-run-qa] - action: wait_build - build_id: "{{ .outputs.f2-run-qa.build_id }}" - max_attempts: 720 - poll_interval: 5 - - f2-pass-qa: - description: "Mark QA as passed" - depends_on: [f2-wait-qa] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/artifacts/qa_results/pass" - - f2-transition-to-qa: - description: "Transition to QA phase" - depends_on: [f2-pass-qa] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/transition" - body: - phase: "qa" - outputs: - - new_phase: .data.phase - - # --- Phase 9: QA → Merge --- - f2-transition-to-merge: - description: "Transition to merge phase" - depends_on: [f2-transition-to-qa] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/transition" - body: - phase: "merge" - outputs: - - new_phase: .data.phase - - f2-merge: - description: "Merge task-management-ui feature branch to main" - depends_on: [f2-transition-to-merge] - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/merge" - body: - strategy: "squash" - outputs: - - merge_commit: .data.commit_sha - - f2-wait-merge-deploy: - description: "Wait for merged code to deploy" - depends_on: [f2-merge] - action: wait_pipeline - project_id: "{{ .outputs.create-project.project_id }}" - max_attempts: 720 - - # --- Phase 10: Merge → Released --- - f2-transition-to-released: - description: "Transition to released phase (may already be done by merge)" - depends_on: [f2-wait-merge-deploy] - on_error: continue - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/transition" - body: - phase: "released" - - f2-archive: - description: "Archive task-management-ui feature" - depends_on: [f2-transition-to-released] - on_error: continue - action: api - method: POST - endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features/{{ .vars.feature_2_slug }}/archive" - # ============================================================ - # SECTION 5: VERIFICATION + # SECTION 3: VERIFY + # Confirm site is live and API responds # ============================================================ verify-site-live: - description: "Verify frontend and API are live after both features" - depends_on: [f2-archive] + description: "Verify site is live after all builds" + depends_on: [wait-deploy-polish] + action: wait_site + domain: "{{ .outputs.create-project.domain }}" + max_attempts: 120 + + verify-api-health: + description: "Verify API health endpoint responds" + depends_on: [verify-site-live] on_error: continue action: shell command: | DOMAIN="{{ .outputs.create-project.domain }}" - # Check frontend - UI_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://$DOMAIN") - echo "Frontend status: $UI_STATUS" - # Check API health - API_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://$DOMAIN/api/studio-api/health") - echo "API status: $API_STATUS" + HEALTH=$(curl -sf "https://$DOMAIN/api/studio-api/health" 2>/dev/null || echo '{}') + echo "API health response: $HEALTH" - if [ "$UI_STATUS" -ge 200 ] && [ "$UI_STATUS" -lt 400 ] && [ "$API_STATUS" -ge 200 ] && [ "$API_STATUS" -lt 400 ]; then - echo "Site is live: frontend and API responding" + STATUS=$(echo "$HEALTH" | grep -o '"status":"ok"' || true) + if [ -n "$STATUS" ]; then + echo "API health check passed" exit 0 else - echo "Site check failed" + echo "API health check failed (may not have health endpoint yet)" exit 1 fi - verify-sdlc-complete: - description: "Verify both features completed the full SDLC lifecycle" - depends_on: [verify-site-live] + verify-complete: + description: "Print success summary" + depends_on: [verify-api-health] action: shell command: | echo "" echo "============================================================" - echo "SUCCESS: Foundary Studio lifecycle complete" + echo "SUCCESS: Foundary Studio build complete" echo "============================================================" - echo "Both features traversed all 10 SDLC phases:" - echo " draft -> specified -> planned -> ready -> implementation ->" - echo " review -> audit -> qa -> merge -> released" echo "" - echo "Infrastructure: React + API + Postgres (batch provisioned)" - echo "Feature 1: Core Data Models & Persistence" - echo "Feature 2: Task Management UI (dependent on F1 schema)" + echo "Domain: {{ .outputs.create-project.domain }}" + echo "Project ID: {{ .outputs.create-project.project_id }}" + echo "" + echo "Architecture:" + echo " studio-ui — React Kanban board frontend" + echo " studio-api — REST API with Task/Project/Label CRUD" + echo " studio-db — PostgreSQL persistence" + echo "" + echo "Builds completed:" + echo " 1. API: data models, migrations, repository, handlers" + echo " 2. UI: Kanban board, drag-and-drop, task modals" + echo " 3. Polish: CORS, error handling, health check, README" echo "============================================================" exit 0 diff --git a/internal/service/architect_service.go b/internal/service/architect_service.go index 6c88730..5c183b1 100644 --- a/internal/service/architect_service.go +++ b/internal/service/architect_service.go @@ -12,12 +12,20 @@ import ( "github.com/orchard9/rdev/internal/port" ) +// ArchitectServiceConfig holds configuration for the architect service. +type ArchitectServiceConfig struct { + DefaultPodName string // Default pod for agent execution (e.g., "claudebox-0") + Namespace string // Kubernetes namespace (e.g., "rdev") +} + // ArchitectService orchestrates conversational project design with Claude. type ArchitectService struct { conversationService *ConversationService blueprintService *BlueprintService agentRegistry port.CodeAgentRegistry projectRepo port.ProjectRepository + defaultPodName string + namespace string } // NewArchitectService creates a new architect service. @@ -26,12 +34,21 @@ func NewArchitectService( blueprintService *BlueprintService, agentRegistry port.CodeAgentRegistry, projectRepo port.ProjectRepository, + cfg *ArchitectServiceConfig, ) *ArchitectService { + if cfg == nil { + cfg = &ArchitectServiceConfig{ + DefaultPodName: "claudebox-0", + Namespace: "rdev", + } + } return &ArchitectService{ conversationService: conversationService, blueprintService: blueprintService, agentRegistry: agentRegistry, projectRepo: projectRepo, + defaultPodName: cfg.DefaultPodName, + namespace: cfg.Namespace, } } @@ -158,6 +175,12 @@ Current conversation context:` fullPrompt := systemPrompt + "\n\n" + prompt + // Resolve pod: use project's pod if set, otherwise fall back to default. + podName := project.PodName + if podName == "" { + podName = s.defaultPodName + } + agentReq := &domain.AgentRequest{ Prompt: fullPrompt, ProjectID: project.ID, @@ -165,6 +188,8 @@ Current conversation context:` Metadata: map[string]string{ "conversation_id": string(conversationID), "purpose": "architect", + "pod_name": podName, + "namespace": s.namespace, }, } @@ -242,12 +267,20 @@ Extract and return ONLY a valid JSON object with this structure: Return ONLY the JSON, no other text.`, transcript) + // Resolve pod: use project's pod if set, otherwise fall back to default. + podName := project.PodName + if podName == "" { + podName = s.defaultPodName + } + agentReq := &domain.AgentRequest{ Prompt: extractionPrompt, ProjectID: project.ID, Timeout: 2 * time.Minute, Metadata: map[string]string{ - "purpose": "spec-extraction", + "purpose": "spec-extraction", + "pod_name": podName, + "namespace": s.namespace, }, } @@ -283,20 +316,7 @@ Return ONLY the JSON, no other text.`, transcript) var spec map[string]any if err := json.Unmarshal([]byte(response), &spec); err != nil { - // Fallback: create basic spec if parsing fails - log := logging.FromContext(ctx) - log.Warn("failed to parse agent spec extraction, using fallback", - logging.FieldError, err, - logging.FieldOperation, "extract_blueprint_spec", - "response_length", len(response), - "message_count", len(messages), - ) - spec = map[string]any{ - "version": "1.0", - "generated_at": time.Now().Format(time.RFC3339), - "message_count": len(messages), - "extraction_failed": true, - } + return nil, fmt.Errorf("parse spec extraction response: %w", err) } return spec, nil