package main import ( "strings" "github.com/orchard9/rdev/pkg/api" ) // summaryToOperationID converts a human-readable summary to a camelCase operationId. // "List API keys" → "listAPIKeys", "Health check" → "healthCheck" func summaryToOperationID(summary string) string { words := strings.Fields(summary) var sb strings.Builder first := true for _, word := range words { clean := strings.Map(func(r rune) rune { if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') { return r } return -1 }, word) if clean == "" { continue } if first { sb.WriteString(strings.ToLower(clean[:1]) + clean[1:]) first = false } else { sb.WriteString(strings.ToUpper(clean[:1]) + clean[1:]) } } return sb.String() } func registerAgentPaths(spec *api.OpenAPISpec) { spec.AddPath("/agents", "get", withAuth( "List code agents", `Returns all registered code agent providers and their status. Shows which agents are available, their supported models, and the current default.`, "Code Agents", "projects:read", `{ "agents": [ { "provider": "claudecode", "name": "Claude Code", "available": true, "default": true, "supported_models": ["claude-sonnet-4-20250514"], "default_model": "claude-sonnet-4-20250514" }, { "provider": "opencode", "name": "OpenCode", "available": false, "default": false, "supported_models": ["gpt-4o", "claude-sonnet-4-20250514"], "default_model": "claude-sonnet-4-20250514" } ], "default_agent": "claudecode", "total_agents": 2, "available_count": 1 }`, )) spec.AddPath("/agents/health", "get", withAuth( "Get agent health status", `Returns the health status of all registered code agents. Checks connectivity to each agent backend and reports availability.`, "Code Agents", "projects:read", `{ "agents": [ { "provider": "claudecode", "name": "Claude Code", "healthy": true, "message": "available", "latency": "125ms", "checked_at": "2026-01-27T12:00:00Z" }, { "provider": "opencode", "name": "OpenCode", "healthy": false, "message": "unavailable", "latency": "5.002s", "checked_at": "2026-01-27T12:00:00Z" } ], "healthy_count": 1, "total_count": 2, "default_agent": "claudecode", "default_healthy": true }`, )) spec.AddPath("/agents/{provider}", "get", withAuthAndParams( "Get agent capabilities", `Returns detailed capabilities for a specific code agent provider. Includes supported features, models, and configuration options.`, "Code Agents", "projects:read", []param{{Name: "provider", In: "path", Description: "Agent provider ID (e.g., 'claudecode', 'opencode')", Required: true}}, )) spec.AddPath("/agents/default", "post", withAuthAndBody( "Set default agent", `Changes the default code agent used for command execution. The specified provider must be registered and ideally available.`, "Code Agents", "admin", `{"provider": "opencode"}`, `{ "default_agent": "opencode", "message": "default agent updated" }`, )) } // param represents an OpenAPI parameter. type param struct { Name string In string Description string Required bool } // withAuth creates an operation that requires authentication. func withAuth(summary, description, tag, scope, example string) map[string]any { return map[string]any{ "operationId": summaryToOperationID(summary), "summary": summary, "description": description + "\n\n**Required scope**: `" + scope + "`", "tags": []string{tag}, "security": []map[string]any{ {"ApiKeyAuth": []string{}}, }, "responses": map[string]any{ "200": map[string]any{ "description": "Success", "content": map[string]any{ "application/json": map[string]any{ "example": example, }, }, }, "401": map[string]any{"description": "Unauthorized - Missing or invalid API key"}, "403": map[string]any{"description": "Forbidden - Insufficient permissions"}, }, } } // withAuthAndBody creates an operation with auth and request body. func withAuthAndBody(summary, description, tag, scope, requestExample, responseExample string) map[string]any { return map[string]any{ "operationId": summaryToOperationID(summary), "summary": summary, "description": description + "\n\n**Required scope**: `" + scope + "`", "tags": []string{tag}, "security": []map[string]any{ {"ApiKeyAuth": []string{}}, }, "requestBody": map[string]any{ "required": true, "content": map[string]any{ "application/json": map[string]any{ "example": requestExample, }, }, }, "responses": map[string]any{ "201": map[string]any{ "description": "Created", "content": map[string]any{ "application/json": map[string]any{ "example": responseExample, }, }, }, "400": map[string]any{"description": "Bad Request - Invalid input"}, "401": map[string]any{"description": "Unauthorized - Missing or invalid API key"}, "403": map[string]any{"description": "Forbidden - Insufficient permissions"}, }, } } // withAuthAndParams creates an operation with auth and path parameters. func withAuthAndParams(summary, description, tag, scope string, params []param) map[string]any { parameters := make([]map[string]any, len(params)) for i, p := range params { parameters[i] = map[string]any{ "name": p.Name, "in": p.In, "description": p.Description, "required": p.Required, "schema": map[string]any{"type": "string"}, } } return map[string]any{ "operationId": summaryToOperationID(summary), "summary": summary, "description": description + "\n\n**Required scope**: `" + scope + "`", "tags": []string{tag}, "security": []map[string]any{ {"ApiKeyAuth": []string{}}, }, "parameters": parameters, "responses": map[string]any{ "200": map[string]any{"description": "Success"}, "401": map[string]any{"description": "Unauthorized - Missing or invalid API key"}, "403": map[string]any{"description": "Forbidden - Insufficient permissions"}, "404": map[string]any{"description": "Not Found"}, }, } } // withAuthBodyAndParams creates an operation with auth, body, and params. func withAuthBodyAndParams(summary, description, tag, scope string, params []param, requestExample, responseExample string) map[string]any { parameters := make([]map[string]any, len(params)) for i, p := range params { parameters[i] = map[string]any{ "name": p.Name, "in": p.In, "description": p.Description, "required": p.Required, "schema": map[string]any{"type": "string"}, } } return map[string]any{ "operationId": summaryToOperationID(summary), "summary": summary, "description": description + "\n\n**Required scope**: `" + scope + "`", "tags": []string{tag}, "security": []map[string]any{ {"ApiKeyAuth": []string{}}, }, "parameters": parameters, "requestBody": map[string]any{ "required": true, "content": map[string]any{ "application/json": map[string]any{ "example": requestExample, }, }, }, "responses": map[string]any{ "201": map[string]any{ "description": "Created", "content": map[string]any{ "application/json": map[string]any{ "example": responseExample, }, }, }, "400": map[string]any{"description": "Bad Request - Invalid input"}, "401": map[string]any{"description": "Unauthorized - Missing or invalid API key"}, "403": map[string]any{"description": "Forbidden - Insufficient permissions"}, }, } } func registerWorkerPaths(spec *api.OpenAPISpec) { spec.AddPath("/workers", "get", withAuth( "List workers", "Returns all registered workers in the pool with status summary.", "Workers", "admin", `{ "workers": [ { "id": "rdev-worker-0", "hostname": "rdev-worker-0.rdev.svc", "status": "idle", "capabilities": ["build", "test", "deploy"], "registered_at": "2026-01-27T12:00:00Z", "last_heartbeat": "2026-01-27T12:05:00Z", "version": "1.0.0" } ], "total": 1, "summary": {"idle": 1, "busy": 0, "draining": 0, "offline": 0} }`, )) spec.AddPath("/workers/{workerId}", "get", withAuthAndParams( "Get worker", "Returns details for a specific worker.", "Workers", "admin", []param{{Name: "workerId", In: "path", Description: "Worker ID", Required: true}}, )) spec.AddPath("/workers/{workerId}/drain", "post", withAuthAndParams( "Drain worker", "Sets a worker to draining status. It will finish its current task but stop accepting new work.", "Workers", "admin", []param{{Name: "workerId", In: "path", Description: "Worker ID", Required: true}}, )) } func registerSDLCPaths(spec *api.OpenAPISpec) { // State and classifier spec.AddPath("/projects/{id}/sdlc/state", "get", withAuthAndParams( "Get SDLC state", "Returns the global SDLC state for a project.", "SDLC", "projects:read", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/projects/{id}/sdlc/next", "get", map[string]any{ "operationId": "getSdlcNextAction", "summary": "Get next action", "description": "Returns the classifier's recommended next action.\n\n**Required scope**: `projects:read`", "tags": []string{"SDLC"}, "security": []map[string]any{{"ApiKeyAuth": []string{}}}, "parameters": []map[string]any{ {"name": "id", "in": "path", "description": "Project ID", "required": true, "schema": map[string]any{"type": "string"}}, {"name": "feature", "in": "query", "description": "Feature slug (optional)", "schema": map[string]any{"type": "string"}}, }, "responses": map[string]any{ "200": map[string]any{"description": "Success"}, "401": map[string]any{"description": "Unauthorized"}, }, }) // Features spec.AddPath("/projects/{id}/sdlc/features", "get", withAuthAndParams( "List features", "Returns all features in the project.", "SDLC", "projects:read", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/projects/{id}/sdlc/features", "post", withAuthBodyAndParams( "Create feature", "Creates a new feature in the draft phase.", "SDLC", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, `{"slug": "auth-flow", "title": "User Authentication Flow"}`, `{"slug": "auth-flow", "title": "User Authentication Flow", "phase": "draft"}`, )) spec.AddPath("/projects/{id}/sdlc/features/{slug}", "get", withAuthAndParams( "Get feature", "Returns details for a specific feature.", "SDLC", "projects:read", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, }, )) spec.AddPath("/projects/{id}/sdlc/features/{slug}", "delete", withAuthAndParams( "Delete feature", "Deletes a feature from the project.", "SDLC", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, }, )) spec.AddPath("/projects/{id}/sdlc/features/{slug}/transition", "post", withAuthBodyAndParams( "Transition feature", "Moves a feature to a new phase.", "SDLC", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, }, `{"phase": "specified"}`, `{"status": "transitioned", "phase": "specified"}`, )) spec.AddPath("/projects/{id}/sdlc/features/{slug}/block", "post", withAuthBodyAndParams( "Block feature", "Adds a blocker reason to a feature.", "SDLC", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, }, `{"reason": "Waiting for API design"}`, `{"status": "blocked"}`, )) spec.AddPath("/projects/{id}/sdlc/features/{slug}/unblock", "post", withAuthAndParams( "Unblock feature", "Removes all blockers from a feature.", "SDLC", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, }, )) // Artifacts spec.AddPath("/projects/{id}/sdlc/features/{slug}/artifacts", "get", withAuthAndParams( "Get artifact status", "Returns artifact statuses for a feature.", "SDLC", "projects:read", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, }, )) spec.AddPath("/projects/{id}/sdlc/features/{slug}/artifacts/{type}/approve", "post", withAuthAndParams( "Approve artifact", "Approves a feature artifact.", "SDLC", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, {Name: "type", In: "path", Description: "Artifact type (spec, design, tasks, qa_plan, review, audit, qa_results)", Required: true}, }, )) spec.AddPath("/projects/{id}/sdlc/features/{slug}/artifacts/{type}/reject", "post", withAuthAndParams( "Reject artifact", "Rejects a feature artifact.", "SDLC", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, {Name: "type", In: "path", Description: "Artifact type", Required: true}, }, )) // Tasks spec.AddPath("/projects/{id}/sdlc/features/{slug}/tasks", "get", withAuthAndParams( "List tasks", "Returns all tasks for a feature.", "SDLC", "projects:read", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, }, )) spec.AddPath("/projects/{id}/sdlc/features/{slug}/tasks", "post", withAuthBodyAndParams( "Add task", "Adds a new task to a feature.", "SDLC", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, }, `{"title": "Implement login handler"}`, `{"id": "task-001", "title": "Implement login handler", "status": "pending"}`, )) spec.AddPath("/projects/{id}/sdlc/features/{slug}/tasks/{taskId}/start", "post", withAuthAndParams( "Start task", "Marks a task as in-progress.", "SDLC", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, {Name: "taskId", In: "path", Description: "Task ID", Required: true}, }, )) spec.AddPath("/projects/{id}/sdlc/features/{slug}/tasks/{taskId}/complete", "post", withAuthAndParams( "Complete task", "Marks a task as complete.", "SDLC", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, {Name: "taskId", In: "path", Description: "Task ID", Required: true}, }, )) spec.AddPath("/projects/{id}/sdlc/features/{slug}/tasks/{taskId}/block", "post", withAuthAndParams( "Block task", "Marks a task as blocked.", "SDLC", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, {Name: "taskId", In: "path", Description: "Task ID", Required: true}, }, )) // Branch management spec.AddPath("/projects/{id}/sdlc/features/{slug}/branch", "post", withAuthAndParams( "Create branch", "Creates a feature branch and its manifest.", "SDLC", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, }, )) spec.AddPath("/projects/{id}/sdlc/features/{slug}/branch", "get", withAuthAndParams( "Get branch status", "Returns branch status and merge checklist.", "SDLC", "projects:read", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, }, )) spec.AddPath("/projects/{id}/sdlc/features/{slug}/branch/sync", "post", withAuthAndParams( "Sync branch", "Syncs feature branch with base branch.", "SDLC", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, }, )) // Merge and archive spec.AddPath("/projects/{id}/sdlc/features/{slug}/merge", "post", withAuthBodyAndParams( "Merge feature", "Merges a feature branch after all gates pass.", "SDLC", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, }, `{"strategy": "squash"}`, `{"status": "merged", "strategy": "squash"}`, )) spec.AddPath("/projects/{id}/sdlc/features/{slug}/archive", "post", withAuthAndParams( "Archive feature", "Archives a released feature.", "SDLC", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "slug", In: "path", Description: "Feature slug", Required: true}, }, )) // Query endpoints spec.AddPath("/projects/{id}/sdlc/query/blocked", "get", withAuthAndParams( "Query blocked features", "Returns all blocked features.", "SDLC", "projects:read", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/projects/{id}/sdlc/query/ready", "get", withAuthAndParams( "Query ready features", "Returns features ready for work.", "SDLC", "projects:read", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/projects/{id}/sdlc/query/needs-approval", "get", withAuthAndParams( "Query features needing approval", "Returns features awaiting approval.", "SDLC", "projects:read", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) // Orchestrator endpoints spec.AddPath("/projects/{id}/sdlc/execute", "post", withAuthBodyAndParams( "Execute action", "Executes the classifier's recommended next action.", "SDLC", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, `{"feature": "auth-flow", "provider": "claude"}`, `{"action": "CREATE_SPEC", "success": true, "output": "..."}`, )) spec.AddPath("/projects/{id}/sdlc/resolve", "post", withAuthBodyAndParams( "Resolve blocker", "Resolves a feature blocker and re-classifies.", "SDLC", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, `{"feature": "auth-flow"}`, `{"action": "TRANSITION", "success": true, "output": "Feature unblocked"}`, )) spec.AddPath("/projects/{id}/sdlc/commit", "post", withAuthBodyAndParams( "Commit changes", "Commits and optionally pushes changes in the project.", "SDLC", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, `{"feature": "auth-flow", "message": "feat: implement login", "push": true}`, `{"commit_sha": "abc123", "files_changed": ["handler.go"], "pushed": true}`, )) } func registerBuildPaths(spec *api.OpenAPISpec) { spec.AddPath("/projects/{id}/builds", "post", withAuthBodyAndParams( "Start build", "Enqueues a build task for a project. The build will be picked up by an available worker.", "Builds", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, `{ "prompt": "Build a landing page with Next.js and Tailwind CSS", "template": "nextjs-landing", "auto_commit": true, "auto_push": true, "callback_url": "https://example.com/webhook" }`, `{ "task_id": "task-abc123", "project_id": "my-project", "status": "pending", "status_url": "/builds/task-abc123" }`, )) spec.AddPath("/projects/{id}/builds", "get", withAuthAndParams( "List builds", "Returns build history for a project.", "Builds", "projects:read", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/builds/{taskId}", "get", withAuthAndParams( "Get build status", "Returns the status and result of a specific build.", "Builds", "projects:read", []param{{Name: "taskId", In: "path", Description: "Build task ID", Required: true}}, )) spec.AddPath("/project/create-and-build", "post", withAuthAndBody( "Create project and build", `Creates a new project and immediately enqueues a build task. Combines project creation (git repo, DNS, CI activation) with build submission in a single call.`, "Builds", "admin", `{ "name": "my-landing-page", "description": "Landing page for product launch", "template": "nextjs-landing", "prompt": "Build a modern landing page with hero, features, and CTA sections", "auto_commit": true, "auto_push": true }`, `{ "project_id": "my-landing-page", "name": "my-landing-page", "domain": "my-landing-page.threesix.ai", "url": "https://my-landing-page.threesix.ai", "git": { "owner": "jordan", "name": "my-landing-page", "clone_http": "https://git.threesix.ai/jordan/my-landing-page.git" }, "task_id": "task-abc123", "status": "pending", "status_url": "/builds/task-abc123" }`, )) } func registerComponentPaths(spec *api.OpenAPISpec) { spec.AddPath("/projects/{id}/components", "get", withAuthAndParams( "List components", `Returns all components in a project's composable monorepo. Each project can contain multiple components (services, workers, apps, CLIs) organized in a monorepo structure.`, "Components", "projects:read", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/projects/{id}/components", "post", withAuthBodyAndParams( "Add component", `Adds a new component to a project's monorepo. Generates component skeleton from template, updates configuration, and commits changes.`, "Components", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, `{ "type": "service", "name": "api-gateway", "template": "go-rest-api", "port": 8080 }`, `{ "type": "service", "name": "api-gateway", "path": "services/api-gateway", "port": 8080, "template": "go-rest-api", "dependencies": [], "operation_id": "op-abc123" }`, )) spec.AddPath("/projects/{id}/components/batch", "post", withAuthBodyAndParams( "Add multiple components", `Adds multiple components to a project's monorepo atomically. All components are created in a single transaction. If any component fails, the entire operation is rolled back.`, "Components", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, `{ "components": [ {"type": "service", "name": "auth", "port": 8081}, {"type": "service", "name": "users", "port": 8082}, {"type": "worker", "name": "email-processor"} ] }`, `{ "components": [ { "type": "service", "name": "auth", "path": "services/auth", "port": 8081, "template": "go-rest-api", "dependencies": [] }, { "type": "service", "name": "users", "path": "services/users", "port": 8082, "template": "go-rest-api", "dependencies": [] }, { "type": "worker", "name": "email-processor", "path": "workers/email-processor", "port": 0, "template": "go-worker", "dependencies": [] } ], "operation_id": "op-abc123" }`, )) // Use wildcard for dynamic component path spec.AddPath("/projects/{id}/components/{path}", "delete", map[string]any{ "operationId": "removeComponent", "summary": "Remove component", "description": "Removes a component from the project's monorepo.\n\n**Required scope**: `projects:execute`", "tags": []string{"Components"}, "security": []map[string]any{ {"ApiKeyAuth": []string{}}, }, "parameters": []map[string]any{ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": map[string]any{"type": "string"}, }, { "name": "path", "in": "path", "description": "Component path (e.g., services/api-gateway, workers/email-processor)", "required": true, "schema": map[string]any{"type": "string"}, }, }, "responses": map[string]any{ "204": map[string]any{"description": "No Content - Component removed"}, "401": map[string]any{"description": "Unauthorized - Missing or invalid API key"}, "403": map[string]any{"description": "Forbidden - Insufficient permissions"}, "404": map[string]any{"description": "Not Found - Component not found"}, }, }) } func registerCredentialPaths(spec *api.OpenAPISpec) { spec.AddPath("/credentials", "get", map[string]any{ "operationId": "listCredentials", "summary": "List credentials", "description": `Returns all infrastructure credentials with values masked. Optionally filter by category using ?category= query parameter. **Required scope**: ` + "`admin`", "tags": []string{"Credentials"}, "security": []map[string]any{ {"ApiKeyAuth": []string{}}, }, "parameters": []map[string]any{ { "name": "category", "in": "query", "description": "Filter by category (e.g., git, ci, dns, registry)", "required": false, "schema": map[string]any{"type": "string"}, }, }, "responses": map[string]any{ "200": map[string]any{ "description": "Success", "content": map[string]any{ "application/json": map[string]any{ "example": `[ { "key": "GITEA_TOKEN", "value": "***", "description": "Gitea API token for repo operations", "category": "git", "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-15T10:30:00Z", "updated_by": "admin" } ]`, }, }, }, "401": map[string]any{"description": "Unauthorized - Missing or invalid API key"}, "403": map[string]any{"description": "Forbidden - Admin scope required"}, }, }) spec.AddPath("/credentials/{key}", "get", withAuthAndParams( "Get credential", `Returns a single credential by key with full unmasked value. **Security**: Only admin scope can retrieve unmasked credentials.`, "Credentials", "admin", []param{{Name: "key", In: "path", Description: "Credential key (e.g., GITEA_TOKEN, WOODPECKER_SECRET)", Required: true}}, )) spec.AddPath("/credentials", "post", withAuthAndBody( "Set credential", `Creates or updates a single credential. Credentials are encrypted at rest in PostgreSQL.`, "Credentials", "admin", `{ "key": "GITEA_TOKEN", "value": "abc123xyz", "description": "Gitea API token for repo operations", "category": "git" }`, `{ "status": "stored", "key": "GITEA_TOKEN" }`, )) spec.AddPath("/credentials/batch", "post", withAuthAndBody( "Set multiple credentials", `Creates or updates multiple credentials atomically. Useful for bulk credential loading from configuration files.`, "Credentials", "admin", `{ "credentials": [ {"key": "GITEA_TOKEN", "value": "abc123", "category": "git"}, {"key": "WOODPECKER_SECRET", "value": "xyz789", "category": "ci"} ] }`, `{ "status": "stored", "count": 2, "keys": ["GITEA_TOKEN", "WOODPECKER_SECRET"] }`, )) spec.AddPath("/credentials/{key}", "delete", map[string]any{ "operationId": "deleteCredential", "summary": "Delete credential", "description": "Removes a credential permanently.\n\n**Required scope**: `admin`", "tags": []string{"Credentials"}, "security": []map[string]any{ {"ApiKeyAuth": []string{}}, }, "parameters": []map[string]any{ { "name": "key", "in": "path", "description": "Credential key to delete", "required": true, "schema": map[string]any{"type": "string"}, }, }, "responses": map[string]any{ "200": map[string]any{ "description": "Success", "content": map[string]any{ "application/json": map[string]any{ "example": `{"status": "deleted", "key": "GITEA_TOKEN"}`, }, }, }, "401": map[string]any{"description": "Unauthorized - Missing or invalid API key"}, "403": map[string]any{"description": "Forbidden - Admin scope required"}, "404": map[string]any{"description": "Not Found - Credential not found"}, }, }) } func registerDiagnosticsPaths(spec *api.OpenAPISpec) { spec.AddPath("/projects/{projectId}/diagnostics", "get", withAuthAndParams( "Get project diagnostics", `Returns comprehensive health and diagnostic information for a project. Includes pod status, resource usage, recent errors, external system connectivity, and deployment health.`, "Diagnostics", "projects:read", []param{{Name: "projectId", In: "path", Description: "Project ID", Required: true}}, )) } func registerVerifyPaths(spec *api.OpenAPISpec) { spec.AddPath("/verify", "post", withAuthAndBody( "Submit verification task", `Submits a visual verification task using Playwright. Captures screenshots at multiple viewports and optionally records video. Results include screenshot URLs and AI analysis of visual quality.`, "Verify", "verify:write", `{ "project_id": "my-landing-page", "url": "https://my-landing-page.threesix.ai", "viewports": ["desktop", "tablet", "mobile"], "wait_for": "networkidle", "wait_timeout": 30000, "full_page": true, "video": true, "callback_url": "https://myapp.com/webhooks/verify" }`, `{ "task_id": "verify-abc123", "status_url": "/verify/verify-abc123", "stream_url": "/verify/verify-abc123/stream" }`, )) spec.AddPath("/verify/{taskId}", "get", withAuthAndParams( "Get verification status", `Returns the status and results of a verification task. Includes screenshot URLs, video URL (if recorded), and duration.`, "Verify", "verify:read", []param{{Name: "taskId", In: "path", Description: "Verification task ID", Required: true}}, )) spec.AddPath("/verify/{taskId}/stream", "get", map[string]any{ "operationId": "streamVerifyEvents", "summary": "Stream verification events", "description": `Streams real-time verification task events via Server-Sent Events (SSE). Events include task started, screenshot captured, video recorded, task completed/failed. **Required scope**: ` + "`verify:read`", "tags": []string{"Verify"}, "security": []map[string]any{ {"ApiKeyAuth": []string{}}, }, "parameters": []map[string]any{ { "name": "taskId", "in": "path", "description": "Verification task ID", "required": true, "schema": map[string]any{"type": "string"}, }, }, "responses": map[string]any{ "200": map[string]any{ "description": "Success - SSE stream", "content": map[string]any{ "text/event-stream": map[string]any{ "example": `event: connected data: {"task_id":"verify-abc123"} event: started data: {"viewport":"desktop","url":"https://my-landing-page.threesix.ai"} event: screenshot_captured data: {"viewport":"desktop","url":"https://storage/verify-abc123-desktop.png"} event: completed data: {"task_id":"verify-abc123","duration_ms":5432}`, }, }, }, "401": map[string]any{"description": "Unauthorized - Missing or invalid API key"}, "403": map[string]any{"description": "Forbidden - Insufficient permissions"}, "404": map[string]any{"description": "Not Found - Task not found"}, }, }) spec.AddPath("/verify/{taskId}", "delete", withAuthAndParams( "Cancel verification task", `Cancels a pending or running verification task. Stops Playwright browser and marks task as cancelled.`, "Verify", "verify:write", []param{{Name: "taskId", In: "path", Description: "Verification task ID", Required: true}}, )) spec.AddPath("/projects/{id}/verify", "get", map[string]any{ "operationId": "listProjectVerifications", "summary": "List project verifications", "description": `Returns verification tasks for a project with pagination. **Required scope**: ` + "`verify:read`", "tags": []string{"Verify"}, "security": []map[string]any{ {"ApiKeyAuth": []string{}}, }, "parameters": []map[string]any{ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": map[string]any{"type": "string"}, }, { "name": "limit", "in": "query", "description": "Maximum number of results (default: 50)", "required": false, "schema": map[string]any{"type": "integer", "default": 50}, }, { "name": "offset", "in": "query", "description": "Pagination offset (default: 0)", "required": false, "schema": map[string]any{"type": "integer", "default": 0}, }, }, "responses": map[string]any{ "200": map[string]any{"description": "Success"}, "401": map[string]any{"description": "Unauthorized - Missing or invalid API key"}, "403": map[string]any{"description": "Forbidden - Insufficient permissions"}, }, }) } func registerInfrastructurePaths(spec *api.OpenAPISpec) { // Git repository endpoints spec.AddPath("/projects/{id}/repo", "post", withAuthBodyAndParams( "Create repository", `Creates a git repository for a project in Gitea. Repository is created under the configured git owner (e.g., threesix organization).`, "Infrastructure", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID (will be repository name)", Required: true}}, `{ "description": "Landing page for product launch", "private": false }`, `{ "id": 123, "owner": "threesix", "name": "my-landing-page", "full_name": "threesix/my-landing-page", "description": "Landing page for product launch", "private": false, "clone_ssh": "git@git.threesix.ai:threesix/my-landing-page.git", "clone_http": "https://git.threesix.ai/threesix/my-landing-page.git", "html_url": "https://git.threesix.ai/threesix/my-landing-page" }`, )) spec.AddPath("/projects/{id}/repo", "get", withAuthAndParams( "Get repository", `Returns repository information for a project. Includes clone URLs, visibility, and metadata.`, "Infrastructure", "projects:read", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/projects/{id}/repo", "delete", withAuthAndParams( "Delete repository", `Deletes the git repository for a project. **Warning**: This permanently deletes all git history and branches.`, "Infrastructure", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) // Deployment endpoints spec.AddPath("/projects/{id}/deploy", "post", withAuthAndParams( "Deploy project", `Deploys a project to Kubernetes. Creates deployment, service, and ingress resources.`, "Infrastructure", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/projects/{id}/deploy/status", "get", withAuthAndParams( "Get deployment status", `Returns the current deployment status for a project. Includes pod status, replica counts, and health information.`, "Infrastructure", "projects:read", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/projects/{id}/deploy", "delete", withAuthAndParams( "Undeploy project", `Removes deployment, service, and ingress resources for a project. Pods are terminated and all Kubernetes resources are deleted.`, "Infrastructure", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/projects/{id}/deploy/restart", "post", withAuthAndParams( "Restart deployment", `Restarts a project's deployment by recreating all pods. Useful for applying configuration changes or recovering from errors.`, "Infrastructure", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/projects/{id}/deploy/scale", "post", withAuthBodyAndParams( "Scale deployment", `Scales a project's deployment to the specified replica count.`, "Infrastructure", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, `{"replicas": 3}`, `{"status": "scaled", "replicas": 3}`, )) spec.AddPath("/projects/{id}/deploy/logs", "get", map[string]any{ "operationId": "getDeploymentLogs", "summary": "Get deployment logs", "description": `Returns recent logs from a project's deployment pods. **Required scope**: ` + "`projects:read`", "tags": []string{"Infrastructure"}, "security": []map[string]any{ {"ApiKeyAuth": []string{}}, }, "parameters": []map[string]any{ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": map[string]any{"type": "string"}, }, { "name": "tail", "in": "query", "description": "Number of recent log lines to retrieve (default: 100)", "required": false, "schema": map[string]any{"type": "integer", "default": 100}, }, }, "responses": map[string]any{ "200": map[string]any{"description": "Success"}, "401": map[string]any{"description": "Unauthorized - Missing or invalid API key"}, "403": map[string]any{"description": "Forbidden - Insufficient permissions"}, "404": map[string]any{"description": "Not Found - Project not found"}, }, }) // Domain endpoints spec.AddPath("/projects/{id}/domain", "post", withAuthBodyAndParams( "Add domain", `Adds a custom domain to a project. Creates DNS A record if domain is a subdomain of the configured base domain (e.g., threesix.ai).`, "Infrastructure", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, `{"domain": "custom.example.com"}`, `{ "project": "my-landing-page", "domain": "custom.example.com", "status": "configured", "note": "External domain configured. Point your DNS to 208.122.204.172" }`, )) spec.AddPath("/projects/{id}/domain", "delete", map[string]any{ "operationId": "removeProjectDomain", "summary": "Remove domain", "description": `Removes a custom domain from a project. Deletes DNS A record if domain was a managed subdomain. **Required scope**: ` + "`projects:execute`", "tags": []string{"Infrastructure"}, "security": []map[string]any{ {"ApiKeyAuth": []string{}}, }, "parameters": []map[string]any{ { "name": "id", "in": "path", "description": "Project ID", "required": true, "schema": map[string]any{"type": "string"}, }, { "name": "domain", "in": "query", "description": "Domain to remove (e.g., custom.example.com)", "required": true, "schema": map[string]any{"type": "string"}, }, }, "responses": map[string]any{ "200": map[string]any{"description": "Success"}, "400": map[string]any{"description": "Bad Request - Missing domain parameter"}, "401": map[string]any{"description": "Unauthorized - Missing or invalid API key"}, "403": map[string]any{"description": "Forbidden - Insufficient permissions"}, }, }) spec.AddPath("/projects/{id}/domains", "get", withAuthAndParams( "List domains", `Returns all domains (primary and aliases) for a project.`, "Infrastructure", "projects:read", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/projects/{id}/domains", "post", withAuthBodyAndParams( "Add domain alias", `Adds an additional domain alias to a project. Projects can have multiple domains pointing to the same deployment.`, "Infrastructure", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, `{"domain": "alias.example.com", "proxied": false}`, `{"status": "added", "domain": "alias.example.com"}`, )) spec.AddPath("/projects/{id}/domains/{domain}", "delete", withAuthAndParams( "Remove domain alias", `Removes a domain alias from a project. Primary domain cannot be removed via this endpoint.`, "Infrastructure", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "domain", In: "path", Description: "Domain to remove", Required: true}, }, )) // CI pipeline endpoints spec.AddPath("/projects/{id}/pipelines", "get", withAuthAndParams( "List CI pipelines", `Returns recent CI pipeline runs for a project. Includes build status, branch, commit info, and duration.`, "Infrastructure", "projects:read", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/projects/{id}/pipelines/{number}", "get", withAuthAndParams( "Get pipeline details", `Returns detailed information for a specific pipeline run.`, "Infrastructure", "projects:read", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "number", In: "path", Description: "Pipeline number", Required: true}, }, )) spec.AddPath("/projects/{id}/pipelines/{number}/steps", "get", withAuthAndParams( "Get pipeline steps", `Returns the steps (stages) of a pipeline run with status and logs.`, "Infrastructure", "projects:read", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "number", In: "path", Description: "Pipeline number", Required: true}, }, )) spec.AddPath("/projects/{id}/pipelines/{number}/retry", "post", withAuthAndParams( "Retry pipeline", `Retries a failed pipeline from the failed step. Part of the enterprise resilience architecture for handling transient failures.`, "Infrastructure", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "number", In: "path", Description: "Pipeline number to retry", Required: true}, }, )) } func registerWebhookPaths(spec *api.OpenAPISpec) { spec.AddPath("/webhooks/woodpecker", "post", map[string]any{ "operationId": "receiveWoodpeckerWebhook", "summary": "Woodpecker CI webhook", "description": `Receives build event webhooks from Woodpecker CI. **Authentication**: Uses HMAC-SHA256 signature verification (X-Woodpecker-Signature header), not API key auth. Processes successful builds on main/master branches to trigger deployments and DNS updates.`, "tags": []string{"Webhooks"}, "parameters": []map[string]any{ { "name": "X-Woodpecker-Signature", "in": "header", "description": "HMAC-SHA256 signature of request body", "required": false, "schema": map[string]any{"type": "string"}, }, }, "requestBody": map[string]any{ "required": true, "content": map[string]any{ "application/json": map[string]any{ "example": `{ "event": "push", "repo": { "owner": "threesix", "name": "my-landing-page", "full_name": "threesix/my-landing-page" }, "build": { "number": 42, "status": "success", "branch": "main", "commit": "abc123def456", "message": "Add feature X", "author": "jordan" } }`, }, }, }, "responses": map[string]any{ "200": map[string]any{ "description": "Success - Event processed", "content": map[string]any{ "application/json": map[string]any{ "example": `{ "status": "success", "project": "my-landing-page", "image": "registry.threesix.ai/my-landing-page:abc123de", "commit": "abc123def456", "note": "component deployments managed by CI pipeline" }`, }, }, }, "400": map[string]any{"description": "Bad Request - Invalid payload"}, "401": map[string]any{"description": "Unauthorized - Invalid signature"}, }, }) } func registerSagaPaths(spec *api.OpenAPISpec) { spec.AddPath("/sagas", "post", withAuthAndBody( "Create and start saga", `Creates a new saga workflow and starts execution. Sagas are distributed workflows with automatic compensation on failure. Useful for multi-step operations like project creation with rollback support.`, "Sagas", "projects:execute", `{ "name": "create-full-stack-project", "max_retries": 3, "steps": [ { "name": "create-repo", "action": "api", "compensate": "delete-repo", "config": {"method": "POST", "endpoint": "/projects/{id}/repo"} }, { "name": "create-db", "action": "api", "depends_on": ["create-repo"], "compensate": "delete-db", "config": {"method": "POST", "endpoint": "/projects/{id}/database"} } ] }`, `{ "id": "saga-abc123", "name": "create-full-stack-project", "status": "running", "current_step": "create-repo", "retry_count": 0, "max_retries": 3, "created_at": "2026-02-08T10:00:00Z", "updated_at": "2026-02-08T10:00:00Z" }`, )) spec.AddPath("/sagas", "get", map[string]any{ "operationId": "listSagas", "summary": "List sagas", "description": `Returns sagas with optional filtering. **Query parameters**: ?name=&status= **Required scope**: ` + "`projects:read`", "tags": []string{"Sagas"}, "security": []map[string]any{ {"ApiKeyAuth": []string{}}, }, "parameters": []map[string]any{ { "name": "name", "in": "query", "description": "Filter by saga name", "required": false, "schema": map[string]any{"type": "string"}, }, { "name": "status", "in": "query", "description": "Filter by status (pending, running, completed, failed, compensating, compensated)", "required": false, "schema": map[string]any{"type": "string"}, }, }, "responses": map[string]any{ "200": map[string]any{"description": "Success"}, "401": map[string]any{"description": "Unauthorized - Missing or invalid API key"}, "403": map[string]any{"description": "Forbidden - Insufficient permissions"}, }, }) spec.AddPath("/sagas/{id}", "get", withAuthAndParams( "Get saga", `Returns detailed saga execution state including all steps, outputs, and errors.`, "Sagas", "projects:read", []param{{Name: "id", In: "path", Description: "Saga ID", Required: true}}, )) spec.AddPath("/sagas/{id}", "delete", withAuthAndParams( "Delete saga", `Deletes a saga and all associated state. Running sagas should be cancelled before deletion.`, "Sagas", "projects:execute", []param{{Name: "id", In: "path", Description: "Saga ID", Required: true}}, )) spec.AddPath("/sagas/{id}/retry", "post", withAuthAndParams( "Retry failed saga", `Resumes a failed saga from the last failed step. Increments retry count and attempts to continue execution.`, "Sagas", "projects:execute", []param{{Name: "id", In: "path", Description: "Saga ID", Required: true}}, )) spec.AddPath("/sagas/{id}/rollback", "post", withAuthAndParams( "Rollback saga", `Triggers compensation for a failed saga. Runs compensation actions in reverse order to undo completed steps.`, "Sagas", "projects:execute", []param{{Name: "id", In: "path", Description: "Saga ID", Required: true}}, )) spec.AddPath("/sagas/{id}/steps/{step}/retry", "post", withAuthAndParams( "Retry specific step", `Retries a specific failed step within a saga. Does not increment saga-level retry count.`, "Sagas", "projects:execute", []param{ {Name: "id", In: "path", Description: "Saga ID", Required: true}, {Name: "step", In: "path", Description: "Step name", Required: true}, }, )) spec.AddPath("/sagas/{id}/steps/{step}/skip", "post", withAuthAndParams( "Skip step", `Skips a failed step and continues saga execution. Marks step as skipped and allows dependent steps to proceed.`, "Sagas", "projects:execute", []param{ {Name: "id", In: "path", Description: "Saga ID", Required: true}, {Name: "step", In: "path", Description: "Step name to skip", Required: true}, }, )) } func registerConversationPaths(spec *api.OpenAPISpec) { spec.AddPath("/projects/{id}/conversations", "post", withAuthBodyAndParams( "Create conversation", `Creates a new conversation for conversational project design. Part of the Foundary Studio system for interactive requirements gathering.`, "Foundary", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, `{"title": "Landing page design discussion"}`, `{ "id": "conv-abc123", "project_id": "my-project", "title": "Landing page design discussion", "created_at": "2026-02-09T00:00:00Z", "updated_at": "2026-02-09T00:00:00Z" }`, )) spec.AddPath("/projects/{id}/conversations", "get", withAuthAndParams( "List conversations", `Returns all conversations for a project.`, "Foundary", "projects:read", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/projects/{id}/conversations/{conversationId}", "get", withAuthAndParams( "Get conversation", `Returns a single conversation with all messages.`, "Foundary", "projects:read", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "conversationId", In: "path", Description: "Conversation ID", Required: true}, }, )) spec.AddPath("/projects/{id}/conversations/{conversationId}/messages", "post", withAuthBodyAndParams( "Add message", `Adds a message to a conversation.`, "Foundary", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "conversationId", In: "path", Description: "Conversation ID", Required: true}, }, `{"role": "user", "content": "I need a modern landing page with dark mode"}`, `{ "id": "msg-abc123", "conversation_id": "conv-abc123", "role": "user", "content": "I need a modern landing page with dark mode", "created_at": "2026-02-09T00:00:00Z" }`, )) spec.AddPath("/projects/{id}/conversations/{conversationId}/messages", "get", withAuthAndParams( "List messages", `Returns all messages in a conversation.`, "Foundary", "projects:read", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "conversationId", In: "path", Description: "Conversation ID", Required: true}, }, )) spec.AddPath("/projects/{id}/conversations/{conversationId}", "delete", withAuthAndParams( "Delete conversation", `Deletes a conversation and all its messages.`, "Foundary", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "conversationId", In: "path", Description: "Conversation ID", Required: true}, }, )) } func registerBlueprintPaths(spec *api.OpenAPISpec) { spec.AddPath("/projects/{id}/blueprints", "post", withAuthBodyAndParams( "Create blueprint", `Creates a structured project blueprint with JSONB spec storage. Blueprints capture the technical specification extracted from conversations.`, "Foundary", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, `{ "name": "Landing Page v1", "description": "Modern landing page with Astro and Tailwind", "spec": { "stack": ["astro", "tailwind", "typescript"], "components": ["hero", "features", "cta"], "features": {"dark_mode": true, "responsive": true} } }`, `{ "id": "bp-abc123", "project_id": "my-project", "name": "Landing Page v1", "description": "Modern landing page with Astro and Tailwind", "spec": { "stack": ["astro", "tailwind", "typescript"], "components": ["hero", "features", "cta"], "features": {"dark_mode": true, "responsive": true} }, "created_at": "2026-02-09T00:00:00Z", "updated_at": "2026-02-09T00:00:00Z" }`, )) spec.AddPath("/projects/{id}/blueprints", "get", withAuthAndParams( "List blueprints", `Returns all blueprints for a project.`, "Foundary", "projects:read", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/projects/{id}/blueprints/{blueprintId}", "get", withAuthAndParams( "Get blueprint", `Returns a single blueprint with full spec.`, "Foundary", "projects:read", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "blueprintId", In: "path", Description: "Blueprint ID", Required: true}, }, )) spec.AddPath("/projects/{id}/blueprints/{blueprintId}", "put", withAuthBodyAndParams( "Update blueprint", `Updates a blueprint's metadata or spec.`, "Foundary", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "blueprintId", In: "path", Description: "Blueprint ID", Required: true}, }, `{ "name": "Landing Page v2", "spec": { "stack": ["astro", "tailwind", "typescript"], "features": {"dark_mode": true, "animations": true} } }`, `{"message": "blueprint updated"}`, )) spec.AddPath("/projects/{id}/blueprints/{blueprintId}", "delete", withAuthAndParams( "Delete blueprint", `Deletes a blueprint permanently.`, "Foundary", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "blueprintId", In: "path", Description: "Blueprint ID", Required: true}, }, )) } func registerArchitectPaths(spec *api.OpenAPISpec) { spec.AddPath("/projects/{id}/architect/start", "post", withAuthBodyAndParams( "Start architect conversation", `Starts a new conversational design session with the AI architect. The architect asks clarifying questions and guides requirements gathering.`, "Foundary", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, `{ "title": "E-commerce platform", "initial_message": "I want to build a marketplace for handmade goods" }`, `{ "conversation_id": "conv-abc123", "title": "E-commerce platform", "response": "Great! A marketplace for handmade goods. Let me ask some questions to understand your vision better. What scale are you targeting? Will this be for local artisans or a global marketplace?" }`, )) spec.AddPath("/projects/{id}/architect/continue/{conversationId}", "post", withAuthBodyAndParams( "Continue architect conversation", `Continues an existing conversation with the AI architect.`, "Foundary", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "conversationId", In: "path", Description: "Conversation ID", Required: true}, }, `{"message": "Global marketplace, starting with 100 sellers"}`, `{ "conversation_id": "conv-abc123", "response": "Understood. For a global marketplace with 100 sellers initially, we'll need to consider international payments, multi-currency support, and shipping logistics. What's your timeline for launch?" }`, )) spec.AddPath("/projects/{id}/architect/generate-blueprint/{conversationId}", "post", withAuthAndParams( "Generate blueprint from conversation", `Extracts a structured blueprint from the conversation history. Uses AI to parse requirements into a technical specification.`, "Foundary", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "conversationId", In: "path", Description: "Conversation ID", Required: true}, }, )) } func registerQuestionPaths(spec *api.OpenAPISpec) { spec.AddPath("/projects/{id}/questions", "post", withAuthBodyAndParams( "Create question", `Creates a structured question for the architect to ask the user. Supports text, choice, multichoice, and yes/no question types.`, "Foundary", "projects:execute", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, `{ "conversation_id": "conv-abc123", "type": "choice", "text": "What authentication method would you prefer?", "choices": ["Email/Password", "OAuth (Google, GitHub)", "Magic Link", "Phone/SMS"], "metadata": {"category": "auth"} }`, `{ "id": "q-abc123", "conversation_id": "conv-abc123", "project_id": "my-project", "type": "choice", "text": "What authentication method would you prefer?", "choices": ["Email/Password", "OAuth (Google, GitHub)", "Magic Link", "Phone/SMS"], "metadata": {"category": "auth"}, "created_at": "2026-02-09T00:00:00Z" }`, )) spec.AddPath("/projects/{id}/questions", "get", withAuthAndParams( "List unanswered questions", `Returns all unanswered questions for a project.`, "Foundary", "projects:read", []param{{Name: "id", In: "path", Description: "Project ID", Required: true}}, )) spec.AddPath("/projects/{id}/questions/{questionId}", "get", withAuthAndParams( "Get question", `Returns a single question by ID.`, "Foundary", "projects:read", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "questionId", In: "path", Description: "Question ID", Required: true}, }, )) spec.AddPath("/projects/{id}/questions/conversation/{conversationId}", "get", withAuthAndParams( "List questions by conversation", `Returns all questions (answered and unanswered) for a conversation.`, "Foundary", "projects:read", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "conversationId", In: "path", Description: "Conversation ID", Required: true}, }, )) spec.AddPath("/projects/{id}/questions/{questionId}/answer", "post", withAuthBodyAndParams( "Answer question", `Records an answer to a question. Answer format depends on question type: - text/yesno: single string in "answer" field - choice: single string in "answer" field (must match a choice) - multichoice: array of strings in "answer_choices" field`, "Foundary", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "questionId", In: "path", Description: "Question ID", Required: true}, }, `{"answer": "OAuth (Google, GitHub)"}`, `{"message": "question answered"}`, )) spec.AddPath("/projects/{id}/questions/{questionId}", "delete", withAuthAndParams( "Delete question", `Deletes a question permanently.`, "Foundary", "projects:execute", []param{ {Name: "id", In: "path", Description: "Project ID", Required: true}, {Name: "questionId", In: "path", Description: "Question ID", Required: true}, }, )) }