3468 lines
109 KiB
Markdown
3468 lines
109 KiB
Markdown
# GooseFactory — Interface Contracts
|
|
|
|
> **Version:** 1.0.0
|
|
> **Last Updated:** 2025-07-14
|
|
> **Author:** Architect Agent
|
|
> **Status:** CANONICAL — All agents build to these types.
|
|
|
|
Every boundary between agents is defined here. If it's not in this document, it's not a contract. If two agents disagree, this document wins.
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [API Contract (REST + WebSocket)](#1-api-contract)
|
|
2. [MCP Tool Schemas](#2-mcp-tool-schemas)
|
|
3. [Modal Data Contract](#3-modal-data-contract)
|
|
4. [UI Component Contracts](#4-ui-component-contracts)
|
|
5. [Rust Backend Contracts](#5-rust-backend-contracts)
|
|
6. [Learning System Contracts](#6-learning-system-contracts)
|
|
7. [Cross-Cutting Contracts](#7-cross-cutting-contracts)
|
|
|
|
---
|
|
|
|
## 1. API Contract
|
|
|
|
### 1.1 Base Configuration
|
|
|
|
```typescript
|
|
// Base URL
|
|
const API_BASE = "https://api.goosefactory.dev/v1";
|
|
const WS_BASE = "wss://api.goosefactory.dev/v1/ws";
|
|
|
|
// All requests require:
|
|
// Authorization: Bearer <jwt>
|
|
// Content-Type: application/json
|
|
// X-Request-Id: <uuid> (optional, for tracing)
|
|
|
|
// All responses include:
|
|
// X-Request-Id: <uuid>
|
|
// X-API-Version: "1"
|
|
```
|
|
|
|
### 1.2 Authentication Types
|
|
|
|
```typescript
|
|
// === JWT Payload ===
|
|
interface JWTPayload {
|
|
sub: string; // user ID (UUID)
|
|
email: string;
|
|
role: Role;
|
|
scopes: Scope[];
|
|
iat: number; // issued at (unix)
|
|
exp: number; // expires (unix, +15min)
|
|
}
|
|
|
|
type Role = "owner" | "admin" | "operator" | "viewer" | "agent";
|
|
|
|
type Scope =
|
|
| "pipelines:read" | "pipelines:write"
|
|
| "tasks:read" | "tasks:approve" | "tasks:reject"
|
|
| "deploy:staging" | "deploy:production"
|
|
| "agents:manage"
|
|
| "assets:read" | "assets:write"
|
|
| "audit:read"
|
|
| "feedback:read" | "feedback:write"
|
|
| "learning:read" | "learning:write";
|
|
|
|
// === API Key (server-to-server) ===
|
|
// Header: X-API-Key: <key>
|
|
// Keys are scoped to specific Scope[] subsets
|
|
// Keys are rotatable via POST /v1/auth/keys/rotate
|
|
|
|
// === Auth Endpoints ===
|
|
// POST /v1/auth/login → { access_token, refresh_token, expires_in }
|
|
// POST /v1/auth/refresh → { access_token, expires_in }
|
|
// POST /v1/auth/logout → 204
|
|
// POST /v1/auth/keys → { api_key, key_id, scopes }
|
|
// DELETE /v1/auth/keys/:id → 204
|
|
```
|
|
|
|
### 1.3 Standard Response Envelope
|
|
|
|
```typescript
|
|
// === Success Response ===
|
|
interface ApiResponse<T> {
|
|
ok: true;
|
|
data: T;
|
|
meta?: ResponseMeta;
|
|
}
|
|
|
|
interface ResponseMeta {
|
|
requestId: string;
|
|
timestamp: string; // ISO 8601
|
|
pagination?: PaginationMeta;
|
|
rateLimit?: RateLimitMeta;
|
|
}
|
|
|
|
interface PaginationMeta {
|
|
page: number;
|
|
limit: number;
|
|
total: number;
|
|
totalPages: number;
|
|
hasNext: boolean;
|
|
hasPrev: boolean;
|
|
}
|
|
|
|
interface RateLimitMeta {
|
|
limit: number;
|
|
remaining: number;
|
|
resetAt: string; // ISO 8601
|
|
}
|
|
|
|
// === Error Response ===
|
|
interface ApiError {
|
|
ok: false;
|
|
error: {
|
|
code: ErrorCode;
|
|
message: string; // Human-readable
|
|
details?: Record<string, unknown>; // Machine-readable context
|
|
field?: string; // For validation errors
|
|
requestId: string;
|
|
};
|
|
}
|
|
|
|
type ErrorCode =
|
|
// 4xx Client Errors
|
|
| "BAD_REQUEST" // 400 — malformed input
|
|
| "VALIDATION_ERROR" // 400 — schema validation failed
|
|
| "UNAUTHORIZED" // 401 — missing/invalid token
|
|
| "FORBIDDEN" // 403 — insufficient scopes
|
|
| "NOT_FOUND" // 404 — entity doesn't exist
|
|
| "CONFLICT" // 409 — state conflict (e.g., already approved)
|
|
| "GONE" // 410 — entity archived/deleted
|
|
| "RATE_LIMITED" // 429 — too many requests
|
|
// 5xx Server Errors
|
|
| "INTERNAL_ERROR" // 500 — unexpected failure
|
|
| "SERVICE_UNAVAILABLE" // 503 — dependency down
|
|
| "TIMEOUT" // 504 — upstream timeout
|
|
;
|
|
|
|
// === Pagination Request ===
|
|
interface PaginationParams {
|
|
page?: number; // default: 1
|
|
limit?: number; // default: 20, max: 100
|
|
sort?: string; // field name, prefix with - for desc: "-created_at"
|
|
}
|
|
```
|
|
|
|
### 1.4 Core Entity Types
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// PIPELINE
|
|
// ═══════════════════════════════════════
|
|
|
|
interface Pipeline {
|
|
id: string; // UUID
|
|
name: string;
|
|
slug: string; // URL-safe unique identifier
|
|
template: PipelineTemplate;
|
|
platform: string; // e.g., "go-high-level", "shopify"
|
|
|
|
// State
|
|
currentStage: PipelineStage;
|
|
status: PipelineStatus;
|
|
priority: Priority;
|
|
|
|
// Ownership
|
|
createdBy: string; // user ID
|
|
assigneeId: string | null; // user ID
|
|
|
|
// Config
|
|
config: Record<string, unknown>;
|
|
metadata: Record<string, unknown>;
|
|
|
|
// SLA
|
|
slaDeadline: string | null; // ISO 8601
|
|
startedAt: string;
|
|
completedAt: string | null;
|
|
|
|
// Timestamps
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
type PipelineStatus = "active" | "paused" | "completed" | "failed" | "archived";
|
|
type Priority = "critical" | "high" | "medium" | "low";
|
|
type PipelineTemplate = "mcp-server-standard" | "mcp-server-minimal" | "mcp-server-enterprise";
|
|
|
|
type PipelineStage =
|
|
| "intake"
|
|
| "scaffolding"
|
|
| "building"
|
|
| "testing"
|
|
| "review" // ★ HUMAN GATE
|
|
| "staging" // ★ HUMAN GATE
|
|
| "production" // ★ HUMAN GATE
|
|
| "published";
|
|
|
|
// ═══════════════════════════════════════
|
|
// PIPELINE STAGE RECORD
|
|
// ═══════════════════════════════════════
|
|
|
|
interface PipelineStageRecord {
|
|
id: string;
|
|
pipelineId: string;
|
|
stageName: PipelineStage;
|
|
stageOrder: number;
|
|
|
|
status: StageStatus;
|
|
requiresApproval: boolean;
|
|
approvalType: "manual" | "auto" | "conditional";
|
|
autoAdvance: boolean;
|
|
validationRules: ValidationRule[];
|
|
|
|
enteredAt: string | null;
|
|
completedAt: string | null;
|
|
durationSeconds: number | null;
|
|
|
|
createdAt: string;
|
|
}
|
|
|
|
type StageStatus = "pending" | "active" | "completed" | "skipped" | "failed";
|
|
|
|
interface ValidationRule {
|
|
type: "test_coverage" | "tests_passing" | "required_asset" | "approval_count" | "custom";
|
|
config: Record<string, unknown>;
|
|
// e.g., { type: "test_coverage", config: { minPercent: 80 } }
|
|
// e.g., { type: "required_asset", config: { assetType: "readme" } }
|
|
}
|
|
|
|
// ═══════════════════════════════════════
|
|
// TASK (Decision Queue Item)
|
|
// ═══════════════════════════════════════
|
|
|
|
interface Task {
|
|
id: string; // UUID
|
|
pipelineId: string | null;
|
|
stageName: PipelineStage | null;
|
|
|
|
// Details
|
|
type: TaskType;
|
|
title: string;
|
|
description: string | null;
|
|
context: TaskContext;
|
|
|
|
// Queue
|
|
status: TaskStatus;
|
|
priority: Priority;
|
|
|
|
// Assignment
|
|
assigneeId: string | null;
|
|
claimedAt: string | null;
|
|
claimedBy: string | null;
|
|
|
|
// Decision
|
|
decision: TaskDecision | null;
|
|
decisionNotes: string | null;
|
|
decisionData: Record<string, unknown>;
|
|
decidedAt: string | null;
|
|
decidedBy: string | null;
|
|
|
|
// SLA
|
|
slaDeadline: string | null;
|
|
slaWarningsSent: number;
|
|
slaBreached: boolean;
|
|
escalationLevel: number; // 0 = none, 1-4 = escalation tiers
|
|
|
|
// Blocking
|
|
blocksStageAdvance: boolean;
|
|
blocksPipelineId: string | null;
|
|
|
|
// Timestamps
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
type TaskType =
|
|
| "approval" // Gate approval (stage transition)
|
|
| "review" // Code/design/doc review
|
|
| "decision" // Choose between options
|
|
| "manual_action" // Something only a human can do
|
|
| "fix_required"; // Something broke, needs intervention
|
|
|
|
type TaskStatus =
|
|
| "pending" // Waiting for someone to act
|
|
| "claimed" // Someone is looking at it
|
|
| "in_progress" // Active work happening
|
|
| "completed" // Decision made
|
|
| "expired" // SLA breached, auto-escalated
|
|
| "escalated"; // Bumped to higher authority
|
|
|
|
type TaskDecision = "approved" | "rejected" | "deferred" | "escalated";
|
|
|
|
interface TaskContext {
|
|
// What's being decided
|
|
summary: string;
|
|
details?: string;
|
|
|
|
// Supporting data
|
|
testResults?: TestResults;
|
|
diffSummary?: DiffSummary;
|
|
metrics?: Record<string, number>;
|
|
relatedAssets?: string[]; // asset IDs
|
|
|
|
// For modals
|
|
modalType?: string; // Which HITL modal to show
|
|
modalConfig?: Record<string, unknown>;
|
|
}
|
|
|
|
interface TestResults {
|
|
total: number;
|
|
passed: number;
|
|
failed: number;
|
|
skipped: number;
|
|
coveragePercent: number;
|
|
failureDetails?: TestFailure[];
|
|
}
|
|
|
|
interface TestFailure {
|
|
testName: string;
|
|
error: string;
|
|
file?: string;
|
|
line?: number;
|
|
}
|
|
|
|
interface DiffSummary {
|
|
filesChanged: number;
|
|
insertions: number;
|
|
deletions: number;
|
|
files: DiffFile[];
|
|
}
|
|
|
|
interface DiffFile {
|
|
path: string;
|
|
status: "added" | "modified" | "deleted" | "renamed";
|
|
insertions: number;
|
|
deletions: number;
|
|
patch?: string; // Unified diff format
|
|
}
|
|
|
|
// ═══════════════════════════════════════
|
|
// APPROVAL
|
|
// ═══════════════════════════════════════
|
|
|
|
interface Approval {
|
|
id: string;
|
|
taskId: string;
|
|
pipelineId: string;
|
|
stageName: PipelineStage;
|
|
|
|
type: ApprovalType;
|
|
status: ApprovalStatus;
|
|
|
|
approvedBy: string | null;
|
|
approvedAt: string | null;
|
|
rejectionReason: string | null;
|
|
conditions: string[];
|
|
|
|
requiredApprovers: number;
|
|
currentApprovers: number;
|
|
|
|
createdAt: string;
|
|
}
|
|
|
|
type ApprovalType = "stage_gate" | "deploy" | "code_review" | "manual_check";
|
|
type ApprovalStatus = "pending" | "approved" | "rejected" | "expired";
|
|
|
|
// ═══════════════════════════════════════
|
|
// AGENT
|
|
// ═══════════════════════════════════════
|
|
|
|
interface Agent {
|
|
id: string;
|
|
name: string;
|
|
type: AgentType;
|
|
|
|
status: AgentStatus;
|
|
currentTaskId: string | null;
|
|
currentPipelineId: string | null;
|
|
|
|
health: AgentHealth;
|
|
capabilities: string[];
|
|
config: Record<string, unknown>;
|
|
|
|
tasksCompletedTotal: number;
|
|
avgTaskDurationSeconds: number | null;
|
|
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
type AgentType = "ai_builder" | "test_runner" | "deployer" | "monitor";
|
|
type AgentStatus = "idle" | "active" | "error" | "offline";
|
|
|
|
interface AgentHealth {
|
|
uptimeSeconds: number;
|
|
tasksCompleted24h: number;
|
|
errorRate: number; // 0.0 - 1.0
|
|
lastHeartbeat: string; // ISO 8601
|
|
}
|
|
|
|
// ═══════════════════════════════════════
|
|
// ASSET
|
|
// ═══════════════════════════════════════
|
|
|
|
interface Asset {
|
|
id: string;
|
|
pipelineId: string;
|
|
stageName: PipelineStage | null;
|
|
|
|
type: AssetType;
|
|
name: string;
|
|
path: string | null;
|
|
|
|
storageKey: string;
|
|
storageBucket: string | null;
|
|
sizeBytes: number;
|
|
contentType: string;
|
|
checksumSha256: string;
|
|
|
|
version: number;
|
|
previousVersionId: string | null;
|
|
|
|
generatedBy: string; // user or agent ID
|
|
generatorType: "user" | "agent" | "ci";
|
|
|
|
metadata: Record<string, unknown>;
|
|
createdAt: string;
|
|
}
|
|
|
|
type AssetType = "code" | "config" | "docs" | "build" | "test_report" | "screenshot";
|
|
|
|
// ═══════════════════════════════════════
|
|
// NOTIFICATION
|
|
// ═══════════════════════════════════════
|
|
|
|
interface Notification {
|
|
id: string;
|
|
userId: string;
|
|
|
|
type: NotificationType;
|
|
title: string;
|
|
body: string | null;
|
|
data: Record<string, unknown>;
|
|
|
|
channels: NotificationChannel[];
|
|
deliveredVia: NotificationChannel[];
|
|
|
|
read: boolean;
|
|
readAt: string | null;
|
|
dismissed: boolean;
|
|
|
|
entityType: string | null;
|
|
entityId: string | null;
|
|
actionUrl: string | null;
|
|
|
|
createdAt: string;
|
|
}
|
|
|
|
type NotificationType =
|
|
| "task_assigned"
|
|
| "approval_pending"
|
|
| "sla_warning"
|
|
| "sla_breach"
|
|
| "deploy_status"
|
|
| "pipeline_completed"
|
|
| "agent_error"
|
|
| "feedback_processed";
|
|
|
|
type NotificationChannel = "dashboard" | "discord" | "email" | "sms" | "push";
|
|
|
|
// ═══════════════════════════════════════
|
|
// AUDIT LOG ENTRY
|
|
// ═══════════════════════════════════════
|
|
|
|
interface AuditEntry {
|
|
id: string;
|
|
|
|
actorType: "user" | "agent" | "system" | "webhook";
|
|
actorId: string | null;
|
|
actorName: string | null;
|
|
|
|
action: string; // e.g., "pipeline.created", "task.approved"
|
|
|
|
entityType: string; // e.g., "pipeline", "task", "approval"
|
|
entityId: string;
|
|
|
|
changes: Record<string, { from: unknown; to: unknown }>;
|
|
metadata: Record<string, unknown>;
|
|
|
|
createdAt: string;
|
|
}
|
|
```
|
|
|
|
### 1.5 REST Endpoints
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// PIPELINES
|
|
// ═══════════════════════════════════════
|
|
|
|
// GET /v1/pipelines
|
|
interface ListPipelinesParams extends PaginationParams {
|
|
status?: PipelineStatus;
|
|
stage?: PipelineStage;
|
|
priority?: Priority;
|
|
assigneeId?: string;
|
|
search?: string;
|
|
}
|
|
// Response: ApiResponse<Pipeline[]>
|
|
|
|
// POST /v1/pipelines
|
|
interface CreatePipelineBody {
|
|
name: string;
|
|
template?: PipelineTemplate; // default: "mcp-server-standard"
|
|
platform: string;
|
|
config?: Record<string, unknown>;
|
|
priority?: Priority; // default: "medium"
|
|
assigneeId?: string;
|
|
}
|
|
// Response: ApiResponse<Pipeline> (201)
|
|
|
|
// GET /v1/pipelines/:pipelineId
|
|
// Response: ApiResponse<Pipeline>
|
|
|
|
// PATCH /v1/pipelines/:pipelineId
|
|
interface UpdatePipelineBody {
|
|
priority?: Priority;
|
|
status?: PipelineStatus;
|
|
config?: Record<string, unknown>;
|
|
assigneeId?: string | null;
|
|
metadata?: Record<string, unknown>;
|
|
}
|
|
// Response: ApiResponse<Pipeline>
|
|
|
|
// DELETE /v1/pipelines/:pipelineId?archive=true
|
|
// Response: 204
|
|
|
|
// GET /v1/pipelines/:pipelineId/stages
|
|
// Response: ApiResponse<PipelineStageRecord[]>
|
|
|
|
// POST /v1/pipelines/:pipelineId/stages/advance
|
|
interface AdvanceStageBody {
|
|
targetStage?: PipelineStage; // default: next in sequence
|
|
skipValidation?: boolean; // default: false (admin only)
|
|
notes?: string;
|
|
}
|
|
// Response: ApiResponse<{ stage: PipelineStageRecord; tasksCreated: Task[] }>
|
|
|
|
// ═══════════════════════════════════════
|
|
// TASKS (Decision Queue)
|
|
// ═══════════════════════════════════════
|
|
|
|
// GET /v1/tasks
|
|
interface ListTasksParams extends PaginationParams {
|
|
status?: TaskStatus;
|
|
priority?: Priority;
|
|
type?: TaskType;
|
|
pipelineId?: string;
|
|
assigneeId?: string; // use "me" for current user
|
|
slaBreached?: boolean;
|
|
}
|
|
// Response: ApiResponse<Task[]>
|
|
|
|
// GET /v1/tasks/:taskId
|
|
// Response: ApiResponse<Task>
|
|
|
|
// POST /v1/tasks/:taskId/claim
|
|
// Response: ApiResponse<Task>
|
|
|
|
// POST /v1/tasks/:taskId/complete
|
|
interface CompleteTaskBody {
|
|
decision: TaskDecision;
|
|
notes?: string;
|
|
decisionData?: Record<string, unknown>;
|
|
}
|
|
// Response: ApiResponse<Task>
|
|
|
|
// POST /v1/tasks/:taskId/reassign
|
|
interface ReassignTaskBody {
|
|
assigneeId: string;
|
|
reason?: string;
|
|
}
|
|
// Response: ApiResponse<Task>
|
|
|
|
// POST /v1/tasks/bulk
|
|
interface BulkTaskBody {
|
|
action: "approve" | "reject" | "defer";
|
|
taskIds: string[];
|
|
notes?: string;
|
|
}
|
|
// Response: ApiResponse<{ succeeded: string[]; failed: Array<{ taskId: string; error: string }> }>
|
|
|
|
// GET /v1/tasks/stats
|
|
// Response: ApiResponse<TaskStats>
|
|
|
|
interface TaskStats {
|
|
pending: number;
|
|
inProgress: number;
|
|
blocked: number;
|
|
avgWaitTimeMinutes: number;
|
|
slaBreaches: number;
|
|
byPriority: Record<Priority, number>;
|
|
byType: Record<TaskType, number>;
|
|
}
|
|
|
|
// ═══════════════════════════════════════
|
|
// APPROVALS
|
|
// ═══════════════════════════════════════
|
|
|
|
// GET /v1/approvals
|
|
interface ListApprovalsParams extends PaginationParams {
|
|
status?: ApprovalStatus;
|
|
pipelineId?: string;
|
|
assignee?: string; // "me" for current user
|
|
}
|
|
// Response: ApiResponse<Approval[]>
|
|
|
|
// GET /v1/approvals/:approvalId
|
|
// Response: ApiResponse<Approval>
|
|
|
|
// POST /v1/approvals/:approvalId/approve
|
|
interface ApproveBody {
|
|
notes?: string;
|
|
conditions?: string[];
|
|
}
|
|
// Response: ApiResponse<Approval>
|
|
|
|
// POST /v1/approvals/:approvalId/reject
|
|
interface RejectBody {
|
|
reason: string; // required
|
|
requestedChanges?: string[];
|
|
}
|
|
// Response: ApiResponse<Approval>
|
|
|
|
// POST /v1/approvals/:approvalId/escalate
|
|
interface EscalateBody {
|
|
escalateTo?: string; // user ID
|
|
reason: string;
|
|
}
|
|
// Response: ApiResponse<Approval>
|
|
|
|
// ═══════════════════════════════════════
|
|
// AGENTS
|
|
// ═══════════════════════════════════════
|
|
|
|
// GET /v1/agents
|
|
// Response: ApiResponse<Agent[]>
|
|
|
|
// GET /v1/agents/:agentId
|
|
// Response: ApiResponse<Agent>
|
|
|
|
// POST /v1/agents/:agentId/heartbeat
|
|
interface HeartbeatBody {
|
|
status: AgentStatus;
|
|
metrics?: Record<string, number>;
|
|
currentTaskId?: string | null;
|
|
}
|
|
// Response: 204
|
|
|
|
// GET /v1/agents/:agentId/activity
|
|
interface AgentActivityParams {
|
|
since?: string; // ISO 8601
|
|
limit?: number; // default: 50
|
|
}
|
|
// Response: ApiResponse<AuditEntry[]>
|
|
|
|
// ═══════════════════════════════════════
|
|
// ASSETS
|
|
// ═══════════════════════════════════════
|
|
|
|
// GET /v1/pipelines/:pipelineId/assets
|
|
interface ListAssetsParams extends PaginationParams {
|
|
type?: AssetType;
|
|
stage?: PipelineStage;
|
|
}
|
|
// Response: ApiResponse<Asset[]>
|
|
|
|
// POST /v1/assets (multipart/form-data)
|
|
interface CreateAssetBody {
|
|
pipelineId: string;
|
|
stage?: PipelineStage;
|
|
type: AssetType;
|
|
file: File; // binary upload
|
|
metadata?: Record<string, unknown>;
|
|
}
|
|
// Response: ApiResponse<Asset> (201)
|
|
|
|
// GET /v1/assets/:assetId
|
|
// Response: ApiResponse<Asset>
|
|
|
|
// GET /v1/assets/:assetId/download
|
|
// Response: binary stream with Content-Type and Content-Disposition headers
|
|
|
|
// GET /v1/assets/:assetId/diff
|
|
interface DiffParams {
|
|
fromVersion: number;
|
|
toVersion?: number; // default: latest
|
|
}
|
|
// Response: ApiResponse<DiffSummary>
|
|
|
|
// ═══════════════════════════════════════
|
|
// NOTIFICATIONS
|
|
// ═══════════════════════════════════════
|
|
|
|
// GET /v1/notifications
|
|
interface ListNotificationsParams extends PaginationParams {
|
|
unread?: boolean;
|
|
type?: NotificationType;
|
|
}
|
|
// Response: ApiResponse<Notification[]>
|
|
|
|
// POST /v1/notifications/:id/read
|
|
// Response: 204
|
|
|
|
// POST /v1/notifications/read-all
|
|
// Response: 204
|
|
|
|
// ═══════════════════════════════════════
|
|
// WEBHOOKS
|
|
// ═══════════════════════════════════════
|
|
|
|
// POST /v1/webhooks
|
|
interface CreateWebhookBody {
|
|
url: string;
|
|
events: WebSocketEventType[];
|
|
secret: string;
|
|
}
|
|
// Response: ApiResponse<Webhook> (201)
|
|
|
|
// GET /v1/webhooks
|
|
// Response: ApiResponse<Webhook[]>
|
|
|
|
// DELETE /v1/webhooks/:id
|
|
// Response: 204
|
|
|
|
// GET /v1/webhooks/events
|
|
// Response: ApiResponse<WebSocketEventType[]>
|
|
|
|
// ═══════════════════════════════════════
|
|
// AUDIT LOG
|
|
// ═══════════════════════════════════════
|
|
|
|
// GET /v1/audit
|
|
interface AuditParams extends PaginationParams {
|
|
entityType?: string;
|
|
entityId?: string;
|
|
actorId?: string;
|
|
action?: string;
|
|
since?: string; // ISO 8601
|
|
until?: string; // ISO 8601
|
|
}
|
|
// Response: ApiResponse<AuditEntry[]>
|
|
|
|
// ═══════════════════════════════════════
|
|
// FEEDBACK (Learning System)
|
|
// ═══════════════════════════════════════
|
|
|
|
// POST /v1/feedback
|
|
// Body: FeedbackEvent (see §3.2)
|
|
// Response: ApiResponse<{ id: string }> (201)
|
|
|
|
// GET /v1/feedback
|
|
interface FeedbackParams extends PaginationParams {
|
|
pipelineId?: string;
|
|
modalType?: string;
|
|
since?: string;
|
|
decision?: string;
|
|
}
|
|
// Response: ApiResponse<FeedbackEvent[]>
|
|
|
|
// GET /v1/feedback/stats
|
|
// Response: ApiResponse<FeedbackStats> (see §6)
|
|
|
|
// ═══════════════════════════════════════
|
|
// DASHBOARD (aggregated data)
|
|
// ═══════════════════════════════════════
|
|
|
|
// GET /v1/dashboard/summary
|
|
// Response: ApiResponse<DashboardSummary>
|
|
|
|
interface DashboardSummary {
|
|
pipelines: {
|
|
active: number;
|
|
paused: number;
|
|
completed: number;
|
|
failed: number;
|
|
};
|
|
tasks: TaskStats;
|
|
agents: {
|
|
total: number;
|
|
active: number;
|
|
idle: number;
|
|
error: number;
|
|
offline: number;
|
|
};
|
|
recentActivity: AuditEntry[]; // last 20 events
|
|
slaStatus: {
|
|
onTrack: number;
|
|
warning: number;
|
|
breached: number;
|
|
};
|
|
}
|
|
```
|
|
|
|
### 1.6 WebSocket Contract
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// CONNECTION
|
|
// ═══════════════════════════════════════
|
|
|
|
// Connect: wss://api.goosefactory.dev/v1/ws?token=<jwt>
|
|
// On connect error: { type: "error", error: { code: "UNAUTHORIZED", message: "..." } }
|
|
|
|
// ═══════════════════════════════════════
|
|
// CLIENT → SERVER MESSAGES
|
|
// ═══════════════════════════════════════
|
|
|
|
interface WsSubscribe {
|
|
type: "subscribe";
|
|
channels: WsChannel[];
|
|
}
|
|
|
|
interface WsUnsubscribe {
|
|
type: "unsubscribe";
|
|
channels: WsChannel[];
|
|
}
|
|
|
|
interface WsPing {
|
|
type: "ping";
|
|
}
|
|
|
|
type WsChannel =
|
|
| "pipeline:*" // All pipeline events
|
|
| `pipeline:${string}` // Specific pipeline by ID
|
|
| "tasks:*" // All task events
|
|
| "tasks:pending" // Only pending task events
|
|
| "agents:*" // All agent events
|
|
| "agents:health" // Only health/heartbeat events
|
|
| "deploys:*" // All deploy events
|
|
| "notifications:me" // Current user's notifications
|
|
;
|
|
|
|
// ═══════════════════════════════════════
|
|
// SERVER → CLIENT MESSAGES
|
|
// ═══════════════════════════════════════
|
|
|
|
interface WsEvent<T = unknown> {
|
|
type: WebSocketEventType;
|
|
timestamp: string; // ISO 8601
|
|
channel: WsChannel;
|
|
data: T;
|
|
}
|
|
|
|
interface WsSubscribed {
|
|
type: "subscribed";
|
|
channels: WsChannel[];
|
|
}
|
|
|
|
interface WsPong {
|
|
type: "pong";
|
|
}
|
|
|
|
// ═══════════════════════════════════════
|
|
// EVENT TYPES AND PAYLOADS
|
|
// ═══════════════════════════════════════
|
|
|
|
type WebSocketEventType =
|
|
// Pipeline lifecycle
|
|
| "pipeline.created"
|
|
| "pipeline.updated"
|
|
| "pipeline.stage_changed"
|
|
| "pipeline.completed"
|
|
| "pipeline.failed"
|
|
| "pipeline.blocked"
|
|
// Task lifecycle
|
|
| "task.created"
|
|
| "task.claimed"
|
|
| "task.completed"
|
|
| "task.sla_warning"
|
|
| "task.sla_breached"
|
|
| "task.escalated"
|
|
// Approval lifecycle
|
|
| "approval.pending"
|
|
| "approval.approved"
|
|
| "approval.rejected"
|
|
// Agent status
|
|
| "agent.status_changed"
|
|
| "agent.heartbeat"
|
|
// Asset events
|
|
| "asset.created"
|
|
| "asset.updated"
|
|
// Deploy events
|
|
| "deploy.started"
|
|
| "deploy.succeeded"
|
|
| "deploy.failed"
|
|
// Feedback events
|
|
| "feedback.received"
|
|
| "feedback.processed"
|
|
;
|
|
|
|
// Event payload types:
|
|
|
|
interface PipelineCreatedPayload {
|
|
pipeline: Pipeline;
|
|
}
|
|
|
|
interface PipelineStageChangedPayload {
|
|
pipelineId: string;
|
|
pipelineName: string;
|
|
fromStage: PipelineStage;
|
|
toStage: PipelineStage;
|
|
triggeredBy: string; // user or agent ID
|
|
}
|
|
|
|
interface PipelineBlockedPayload {
|
|
pipelineId: string;
|
|
pipelineName: string;
|
|
blockedAt: PipelineStage;
|
|
reason: string;
|
|
taskId: string; // The task that's blocking
|
|
}
|
|
|
|
interface TaskCreatedPayload {
|
|
task: Task;
|
|
}
|
|
|
|
interface TaskCompletedPayload {
|
|
task: Task;
|
|
decision: TaskDecision;
|
|
decidedBy: string;
|
|
}
|
|
|
|
interface TaskSlaPayload {
|
|
taskId: string;
|
|
taskTitle: string;
|
|
pipelineId: string | null;
|
|
slaDeadline: string;
|
|
minutesRemaining: number; // negative if breached
|
|
escalationLevel: number;
|
|
}
|
|
|
|
interface AgentStatusPayload {
|
|
agentId: string;
|
|
agentName: string;
|
|
fromStatus: AgentStatus;
|
|
toStatus: AgentStatus;
|
|
currentTaskId: string | null;
|
|
}
|
|
|
|
interface AgentHeartbeatPayload {
|
|
agentId: string;
|
|
agentName: string;
|
|
status: AgentStatus;
|
|
health: AgentHealth;
|
|
}
|
|
|
|
interface DeployPayload {
|
|
pipelineId: string;
|
|
pipelineName: string;
|
|
target: "staging" | "production";
|
|
version: string;
|
|
status: "started" | "succeeded" | "failed";
|
|
url?: string; // Deploy URL on success
|
|
error?: string; // Error message on failure
|
|
}
|
|
|
|
interface FeedbackReceivedPayload {
|
|
feedbackId: string;
|
|
pipelineId: string | null;
|
|
modalType: string;
|
|
decision: string | null;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 2. MCP Tool Schemas
|
|
|
|
### 2.1 Tool Definitions
|
|
|
|
Every tool follows MCP SDK conventions: name, description, inputSchema (JSON Schema), and returns content blocks.
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// TOOL: factory_get_pending_tasks
|
|
// ═══════════════════════════════════════
|
|
|
|
const factory_get_pending_tasks = {
|
|
name: "factory_get_pending_tasks",
|
|
description:
|
|
"Get all tasks requiring human attention, sorted by priority and SLA urgency. " +
|
|
"This is the operator's decision inbox — the most important view in the factory.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
pipeline_id: {
|
|
type: "string",
|
|
description: "Filter tasks to a specific pipeline (UUID)",
|
|
},
|
|
priority: {
|
|
type: "string",
|
|
enum: ["critical", "high", "medium", "low"],
|
|
description: "Filter by priority level",
|
|
},
|
|
assignee: {
|
|
type: "string",
|
|
description: "Filter by assignee. Use 'me' for current user.",
|
|
},
|
|
include_context: {
|
|
type: "boolean",
|
|
description: "Include full task context (diffs, test results). Default: true",
|
|
default: true,
|
|
},
|
|
limit: {
|
|
type: "number",
|
|
description: "Maximum tasks to return. Default: 20",
|
|
default: 20,
|
|
},
|
|
},
|
|
required: [],
|
|
},
|
|
};
|
|
|
|
// Return: text content with formatted task list + task count summary
|
|
|
|
// ═══════════════════════════════════════
|
|
// TOOL: factory_approve_task
|
|
// ═══════════════════════════════════════
|
|
|
|
const factory_approve_task = {
|
|
name: "factory_approve_task",
|
|
description:
|
|
"Approve a pending task or approval gate, allowing the pipeline to proceed. " +
|
|
"Use this when the operator says 'approve', 'LGTM', 'ship it', etc.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
task_id: {
|
|
type: "string",
|
|
description: "UUID of the task to approve",
|
|
},
|
|
notes: {
|
|
type: "string",
|
|
description: "Optional approval notes or conditions",
|
|
},
|
|
conditions: {
|
|
type: "array",
|
|
items: { type: "string" },
|
|
description: "Conditions that must be met post-approval",
|
|
},
|
|
},
|
|
required: ["task_id"],
|
|
},
|
|
};
|
|
|
|
// Return: text confirming approval + what pipeline action was triggered
|
|
|
|
// ═══════════════════════════════════════
|
|
// TOOL: factory_reject_task
|
|
// ═══════════════════════════════════════
|
|
|
|
const factory_reject_task = {
|
|
name: "factory_reject_task",
|
|
description:
|
|
"Reject a pending task with feedback. Sends pipeline back for rework. " +
|
|
"Use when the operator says 'reject', 'needs work', 'fix this', etc.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
task_id: {
|
|
type: "string",
|
|
description: "UUID of the task to reject",
|
|
},
|
|
reason: {
|
|
type: "string",
|
|
description: "Why this was rejected — required for feedback loop",
|
|
},
|
|
requested_changes: {
|
|
type: "array",
|
|
items: { type: "string" },
|
|
description: "Specific changes needed before re-submission",
|
|
},
|
|
severity: {
|
|
type: "string",
|
|
enum: ["minor", "major", "critical"],
|
|
description: "How serious the issues are. Default: major",
|
|
default: "major",
|
|
},
|
|
},
|
|
required: ["task_id", "reason"],
|
|
},
|
|
};
|
|
|
|
// Return: text confirming rejection + which stage the pipeline reverted to
|
|
|
|
// ═══════════════════════════════════════
|
|
// TOOL: factory_get_pipeline_status
|
|
// ═══════════════════════════════════════
|
|
|
|
const factory_get_pipeline_status = {
|
|
name: "factory_get_pipeline_status",
|
|
description:
|
|
"Get current state of all pipelines or a specific pipeline. " +
|
|
"Shows stage, progress, blockers, and timeline.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
pipeline_id: {
|
|
type: "string",
|
|
description: "Specific pipeline UUID. Omit for all active pipelines.",
|
|
},
|
|
status: {
|
|
type: "string",
|
|
enum: ["active", "paused", "completed", "failed", "all"],
|
|
description: "Filter by status. Default: active",
|
|
default: "active",
|
|
},
|
|
include_details: {
|
|
type: "boolean",
|
|
description: "Include tasks, assets, and stage history. Default: false",
|
|
default: false,
|
|
},
|
|
},
|
|
required: [],
|
|
},
|
|
};
|
|
|
|
// ═══════════════════════════════════════
|
|
// TOOL: factory_advance_stage
|
|
// ═══════════════════════════════════════
|
|
|
|
const factory_advance_stage = {
|
|
name: "factory_advance_stage",
|
|
description:
|
|
"Manually advance a pipeline to its next stage. Triggers validation checks. " +
|
|
"If validation fails, returns the failures instead of advancing.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
pipeline_id: {
|
|
type: "string",
|
|
description: "UUID of the pipeline to advance",
|
|
},
|
|
target_stage: {
|
|
type: "string",
|
|
enum: ["intake", "scaffolding", "building", "testing", "review", "staging", "production", "published"],
|
|
description: "Target stage. Default: next in sequence",
|
|
},
|
|
skip_validation: {
|
|
type: "boolean",
|
|
description: "Skip gate checks. Admin/owner only. Default: false",
|
|
default: false,
|
|
},
|
|
notes: {
|
|
type: "string",
|
|
description: "Notes for the stage transition",
|
|
},
|
|
},
|
|
required: ["pipeline_id"],
|
|
},
|
|
};
|
|
|
|
// ═══════════════════════════════════════
|
|
// TOOL: factory_assign_priority
|
|
// ═══════════════════════════════════════
|
|
|
|
const factory_assign_priority = {
|
|
name: "factory_assign_priority",
|
|
description: "Set or change priority on a pipeline or task. Recalculates SLA deadlines.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
entity_type: {
|
|
type: "string",
|
|
enum: ["pipeline", "task"],
|
|
},
|
|
entity_id: {
|
|
type: "string",
|
|
description: "UUID of the pipeline or task",
|
|
},
|
|
priority: {
|
|
type: "string",
|
|
enum: ["critical", "high", "medium", "low"],
|
|
},
|
|
reason: {
|
|
type: "string",
|
|
description: "Why the priority changed",
|
|
},
|
|
},
|
|
required: ["entity_type", "entity_id", "priority"],
|
|
},
|
|
};
|
|
|
|
// ═══════════════════════════════════════
|
|
// TOOL: factory_get_blockers
|
|
// ═══════════════════════════════════════
|
|
|
|
const factory_get_blockers = {
|
|
name: "factory_get_blockers",
|
|
description:
|
|
"Get all items that are blocked and why. The 'what's stuck' view. " +
|
|
"Includes suggested actions to unblock.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
pipeline_id: {
|
|
type: "string",
|
|
description: "Filter to a specific pipeline",
|
|
},
|
|
include_suggestions: {
|
|
type: "boolean",
|
|
description: "Include AI-generated suggestions for unblocking. Default: true",
|
|
default: true,
|
|
},
|
|
},
|
|
required: [],
|
|
},
|
|
};
|
|
|
|
// Return type:
|
|
interface BlockerInfo {
|
|
blockerType: "task" | "stage" | "agent" | "dependency";
|
|
entityId: string;
|
|
title: string;
|
|
pipelineId: string;
|
|
pipelineName: string;
|
|
priority: Priority;
|
|
hoursBlocked: number;
|
|
blockReason: string;
|
|
suggestedAction?: string;
|
|
}
|
|
|
|
// ═══════════════════════════════════════
|
|
// TOOL: factory_run_tests
|
|
// ═══════════════════════════════════════
|
|
|
|
const factory_run_tests = {
|
|
name: "factory_run_tests",
|
|
description: "Trigger test suite for a pipeline's current build.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
pipeline_id: {
|
|
type: "string",
|
|
description: "UUID of the pipeline",
|
|
},
|
|
test_type: {
|
|
type: "string",
|
|
enum: ["unit", "integration", "e2e", "all"],
|
|
description: "Which tests to run. Default: all",
|
|
default: "all",
|
|
},
|
|
environment: {
|
|
type: "string",
|
|
enum: ["local", "ci"],
|
|
description: "Where to run. Default: ci",
|
|
default: "ci",
|
|
},
|
|
},
|
|
required: ["pipeline_id"],
|
|
},
|
|
};
|
|
|
|
// ═══════════════════════════════════════
|
|
// TOOL: factory_deploy
|
|
// ═══════════════════════════════════════
|
|
|
|
const factory_deploy = {
|
|
name: "factory_deploy",
|
|
description:
|
|
"Deploy a pipeline's build to staging or production. " +
|
|
"Production requires prior staging deploy + approval.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
pipeline_id: {
|
|
type: "string",
|
|
description: "UUID of the pipeline to deploy",
|
|
},
|
|
target: {
|
|
type: "string",
|
|
enum: ["staging", "production"],
|
|
},
|
|
version: {
|
|
type: "string",
|
|
description: "Specific version. Default: latest build",
|
|
},
|
|
dry_run: {
|
|
type: "boolean",
|
|
description: "Preview what would happen without deploying. Default: false",
|
|
default: false,
|
|
},
|
|
},
|
|
required: ["pipeline_id", "target"],
|
|
},
|
|
};
|
|
|
|
// ═══════════════════════════════════════
|
|
// TOOL: factory_search
|
|
// ═══════════════════════════════════════
|
|
|
|
const factory_search = {
|
|
name: "factory_search",
|
|
description: "Search across pipelines, tasks, assets, and audit logs.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
query: {
|
|
type: "string",
|
|
description: "Search query text",
|
|
},
|
|
entity_types: {
|
|
type: "array",
|
|
items: { type: "string", enum: ["pipeline", "task", "asset", "audit"] },
|
|
description: "Which entity types to search. Default: all",
|
|
},
|
|
limit: {
|
|
type: "number",
|
|
description: "Max results. Default: 10",
|
|
default: 10,
|
|
},
|
|
},
|
|
required: ["query"],
|
|
},
|
|
};
|
|
|
|
// ═══════════════════════════════════════
|
|
// TOOL: factory_create_pipeline
|
|
// ═══════════════════════════════════════
|
|
|
|
const factory_create_pipeline = {
|
|
name: "factory_create_pipeline",
|
|
description: "Initialize a new MCP server pipeline from a template.",
|
|
inputSchema: {
|
|
type: "object" as const,
|
|
properties: {
|
|
name: {
|
|
type: "string",
|
|
description: "Pipeline name (e.g., 'ghl-mcp-server')",
|
|
},
|
|
platform: {
|
|
type: "string",
|
|
description: "Target platform (e.g., 'go-high-level', 'shopify')",
|
|
},
|
|
template: {
|
|
type: "string",
|
|
enum: ["mcp-server-standard", "mcp-server-minimal", "mcp-server-enterprise"],
|
|
description: "Pipeline template. Default: mcp-server-standard",
|
|
default: "mcp-server-standard",
|
|
},
|
|
priority: {
|
|
type: "string",
|
|
enum: ["critical", "high", "medium", "low"],
|
|
description: "Initial priority. Default: medium",
|
|
default: "medium",
|
|
},
|
|
},
|
|
required: ["name", "platform"],
|
|
},
|
|
};
|
|
```
|
|
|
|
### 2.2 MCP Resource URIs
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// RESOURCES (read-only, subscribable)
|
|
// ═══════════════════════════════════════
|
|
|
|
// Static resources (fixed URI):
|
|
|
|
// factory://dashboard/summary
|
|
// Returns: DashboardSummary (see §1.5)
|
|
// MIME: application/json
|
|
// Updates: on any pipeline/task/agent event
|
|
|
|
// factory://config/templates
|
|
// Returns: PipelineTemplateConfig[]
|
|
// MIME: application/json
|
|
// Updates: rarely (config changes)
|
|
|
|
interface PipelineTemplateConfig {
|
|
id: PipelineTemplate;
|
|
name: string;
|
|
description: string;
|
|
stages: PipelineStage[];
|
|
defaultConfig: Record<string, unknown>;
|
|
requiredAssets: AssetType[];
|
|
validationRules: ValidationRule[];
|
|
}
|
|
|
|
// Dynamic resources (parameterized URI):
|
|
|
|
// factory://pipelines/{pipelineId}/state
|
|
// Returns: PipelineDetailState
|
|
// MIME: application/json
|
|
// Updates: on pipeline.* events for this pipeline
|
|
|
|
interface PipelineDetailState {
|
|
pipeline: Pipeline;
|
|
stages: PipelineStageRecord[];
|
|
currentTasks: Task[];
|
|
recentAssets: Asset[];
|
|
blockers: BlockerInfo[];
|
|
timeline: AuditEntry[]; // last 50 events for this pipeline
|
|
}
|
|
|
|
// factory://servers/{serverName}/status
|
|
// Returns: ServerStatus
|
|
// MIME: application/json
|
|
|
|
interface ServerStatus {
|
|
name: string;
|
|
pipelineId: string;
|
|
stage: PipelineStage;
|
|
health: "healthy" | "degraded" | "failing" | "unknown";
|
|
deployments: {
|
|
staging: DeploymentInfo | null;
|
|
production: DeploymentInfo | null;
|
|
};
|
|
lastTestRun: TestResults | null;
|
|
metrics: Record<string, number>;
|
|
}
|
|
|
|
interface DeploymentInfo {
|
|
version: string;
|
|
deployedAt: string;
|
|
url: string;
|
|
status: "running" | "stopped" | "failed";
|
|
}
|
|
|
|
// factory://pipelines/{pipelineId}/test-results
|
|
// Returns: TestResults (see §1.4)
|
|
// MIME: application/json
|
|
|
|
// factory://pipelines/{pipelineId}/build-logs
|
|
// Returns: string (raw build output)
|
|
// MIME: text/plain
|
|
```
|
|
|
|
### 2.3 MCP Prompts
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// PROMPT: review_server
|
|
// ═══════════════════════════════════════
|
|
|
|
const review_server = {
|
|
name: "review_server",
|
|
description:
|
|
"Pull all context needed to review an MCP server: code quality, " +
|
|
"test results, docs, deployment readiness. Returns a structured review prompt.",
|
|
arguments: [
|
|
{
|
|
name: "server_name",
|
|
description: "Name of the MCP server to review",
|
|
required: true,
|
|
},
|
|
],
|
|
};
|
|
// Returns: messages[] containing pipeline status, test results, asset inventory,
|
|
// open tasks, quality standards comparison, review checklist
|
|
|
|
// ═══════════════════════════════════════
|
|
// PROMPT: whats_needs_attention
|
|
// ═══════════════════════════════════════
|
|
|
|
const whats_needs_attention = {
|
|
name: "whats_needs_attention",
|
|
description:
|
|
"Summary of everything needing human attention right now, prioritized by urgency.",
|
|
arguments: [
|
|
{
|
|
name: "scope",
|
|
description: "Filter scope: 'all' | 'my-tasks' | 'critical-only'. Default: all",
|
|
required: false,
|
|
},
|
|
],
|
|
};
|
|
// Returns: messages[] with SLA breaches, pending approvals, blocked pipelines,
|
|
// failed tests/deploys, agent health issues, suggested next actions
|
|
|
|
// ═══════════════════════════════════════
|
|
// PROMPT: deploy_checklist
|
|
// ═══════════════════════════════════════
|
|
|
|
const deploy_checklist = {
|
|
name: "deploy_checklist",
|
|
description: "Pre-deployment review checklist for an MCP server.",
|
|
arguments: [
|
|
{
|
|
name: "pipeline_id",
|
|
description: "UUID of the pipeline being deployed",
|
|
required: true,
|
|
},
|
|
{
|
|
name: "target",
|
|
description: "'staging' or 'production'",
|
|
required: true,
|
|
},
|
|
],
|
|
};
|
|
// Returns: messages[] with structured checklist:
|
|
// tests, code review, README, TOOLS.md, env vars, secrets, perf, rollback plan
|
|
|
|
// ═══════════════════════════════════════
|
|
// PROMPT: pipeline_retrospective
|
|
// ═══════════════════════════════════════
|
|
|
|
const pipeline_retrospective = {
|
|
name: "pipeline_retrospective",
|
|
description: "Generate a retrospective analysis of a completed pipeline.",
|
|
arguments: [
|
|
{
|
|
name: "pipeline_id",
|
|
description: "UUID of the completed pipeline",
|
|
required: true,
|
|
},
|
|
],
|
|
};
|
|
// Returns: messages[] with timeline, bottlenecks, quality metrics, lessons learned
|
|
```
|
|
|
|
### 2.4 MCP App Hosting Contract
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// MCP APP REGISTRATION
|
|
// ═══════════════════════════════════════
|
|
|
|
// MCP tools can return UI content using Goose's MCP UI rendering system.
|
|
// The Factory MCP server uses this for rich HITL modals.
|
|
|
|
// Tool response format for rendering a modal:
|
|
interface McpToolResponseWithUI {
|
|
_meta: {
|
|
goose: {
|
|
toolUI: {
|
|
displayType: "inline" | "sidecar"; // inline = in chat, sidecar = side panel
|
|
name: string; // Display name for the UI
|
|
renderer: "mcp-ui"; // Must be "mcp-ui"
|
|
};
|
|
};
|
|
};
|
|
content: McpUIResource[];
|
|
}
|
|
|
|
// UI resource format (one per content block):
|
|
interface McpUIResource {
|
|
uri: string; // e.g., "ui://hitl-modal"
|
|
mimeType: "text/html"; // Always HTML for our modals
|
|
text: string; // The HTML content (self-contained)
|
|
}
|
|
|
|
// The tool creates the resource using @mcp-ui/client:
|
|
// createUIResource({
|
|
// uri: "ui://factory-modal",
|
|
// content: { type: "rawHtml", htmlString: modalHtml },
|
|
// encoding: "text",
|
|
// })
|
|
|
|
// ═══════════════════════════════════════
|
|
// MODAL REGISTRATION (internal to MCP server)
|
|
// ═══════════════════════════════════════
|
|
|
|
// Each modal type is registered with its capabilities:
|
|
|
|
interface ModalRegistration {
|
|
id: string; // e.g., "traffic-light"
|
|
name: string; // e.g., "Traffic Light"
|
|
description: string;
|
|
version: string; // semver
|
|
|
|
// When to use this modal
|
|
suitableFor: ModalUseCase[];
|
|
|
|
// What data it needs
|
|
requiredContext: ModalContextField[];
|
|
optionalContext: ModalContextField[];
|
|
|
|
// What data it produces
|
|
outputSchema: {
|
|
feedbackTypes: FeedbackType[];
|
|
capturesMetrics: string[]; // e.g., ["response_time", "hover_journey"]
|
|
};
|
|
|
|
// Display preferences
|
|
display: {
|
|
preferredType: "inline" | "sidecar";
|
|
minWidth: number; // pixels
|
|
minHeight: number;
|
|
supportsDarkMode: boolean;
|
|
supportsMobile: boolean;
|
|
};
|
|
}
|
|
|
|
type ModalUseCase =
|
|
| "binary_decision" // pass/fail, approve/reject
|
|
| "batch_review" // multiple items at once
|
|
| "multi_dimensional" // rate across multiple axes
|
|
| "comparison" // A vs B
|
|
| "subjective" // emotional/gut reaction
|
|
| "high_stakes" // critical decisions
|
|
| "annotation" // mark specific regions
|
|
| "estimation" // effort/risk estimation
|
|
| "triage" // multi-pipeline overview
|
|
| "retrospective" // reflection/improvement
|
|
| "verification"; // checklist confirmation
|
|
|
|
type ModalContextField =
|
|
| "pipeline_id"
|
|
| "pipeline_name"
|
|
| "item_id"
|
|
| "item_name"
|
|
| "deliverable_preview"
|
|
| "deliverable_content"
|
|
| "test_results"
|
|
| "diff_summary"
|
|
| "metrics"
|
|
| "items_batch" // for batch modals: array of items
|
|
| "content_before" // for comparison modals
|
|
| "content_after"
|
|
| "checklist_items" // for verification modals
|
|
| "estimation_question"
|
|
| "persona" // for voice-of-customer
|
|
| "dimensions" // for multi-dimensional modals
|
|
;
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Modal Data Contract
|
|
|
|
### 3.1 Host → Modal Communication (Context Injection)
|
|
|
|
```typescript
|
|
// The MCP App iframe host passes context data INTO the modal via URL parameters
|
|
// and a global JavaScript object injected before the modal HTML loads.
|
|
|
|
// ═══════════════════════════════════════
|
|
// URL PARAMETERS (simple values)
|
|
// ═══════════════════════════════════════
|
|
|
|
// The modal's src URL includes query params:
|
|
// ui://factory-modal?pipeline_id=xxx&item_id=yyy&modal_type=traffic-light
|
|
|
|
// ═══════════════════════════════════════
|
|
// INJECTED CONTEXT OBJECT (complex data)
|
|
// ═══════════════════════════════════════
|
|
|
|
// The host injects a global object BEFORE the modal renders.
|
|
// The modal accesses it via window.__FACTORY_CONTEXT__
|
|
|
|
interface FactoryModalContext {
|
|
// Identity
|
|
modalType: string; // e.g., "traffic-light"
|
|
modalVersion: string; // semver
|
|
sessionId: string; // Factory session ID
|
|
|
|
// What's being reviewed
|
|
pipelineId: string;
|
|
pipelineName: string;
|
|
itemId: string;
|
|
itemName: string;
|
|
|
|
// Content (varies by modal type)
|
|
deliverablePreview?: string; // Short text preview
|
|
deliverableContent?: string; // Full content (code, text, etc.)
|
|
contentBefore?: string; // For comparison modals
|
|
contentAfter?: string;
|
|
|
|
// Supporting data
|
|
testResults?: TestResults;
|
|
diffSummary?: DiffSummary;
|
|
metrics?: Record<string, number>;
|
|
|
|
// Batch data (for multi-item modals)
|
|
items?: ModalItem[];
|
|
|
|
// Configuration
|
|
dimensions?: ModalDimension[]; // For multi-dimensional modals
|
|
checklistItems?: ChecklistItem[]; // For verification modals
|
|
estimationQuestion?: string;
|
|
persona?: ModalPersona;
|
|
timerSeconds?: number; // For timed modals
|
|
|
|
// Theme
|
|
theme: "dark" | "light";
|
|
accentColor: string; // hex color
|
|
}
|
|
|
|
interface ModalItem {
|
|
id: string;
|
|
name: string;
|
|
preview: string;
|
|
content?: string;
|
|
metadata?: Record<string, unknown>;
|
|
}
|
|
|
|
interface ModalDimension {
|
|
id: string; // e.g., "code_quality"
|
|
name: string; // e.g., "Code Quality"
|
|
description?: string;
|
|
weight: number; // Importance multiplier (1-3)
|
|
}
|
|
|
|
interface ChecklistItem {
|
|
id: string;
|
|
label: string;
|
|
description?: string;
|
|
aiAssessment?: string; // What the AI thinks about this item
|
|
aiPasses?: boolean; // AI's pre-assessment
|
|
}
|
|
|
|
interface ModalPersona {
|
|
name: string;
|
|
role: string;
|
|
context: string;
|
|
avatarEmoji: string;
|
|
}
|
|
```
|
|
|
|
### 3.2 Modal → Host Communication (postMessage API)
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// EVERY modal posts decisions back via:
|
|
// window.parent.postMessage(message, "*");
|
|
// ═══════════════════════════════════════
|
|
|
|
// === Discriminated Union of all message types ===
|
|
|
|
type ModalMessage =
|
|
| ModalResponse
|
|
| ModalReady
|
|
| ModalResize
|
|
| ModalClose
|
|
| ModalError;
|
|
|
|
// ─── READY (modal loaded and interactive) ───
|
|
|
|
interface ModalReady {
|
|
type: "factory_modal_ready";
|
|
modalType: string;
|
|
version: string;
|
|
}
|
|
|
|
// ─── RESIZE (modal wants to change size) ───
|
|
|
|
interface ModalResize {
|
|
type: "factory_modal_resize";
|
|
width?: number;
|
|
height: number;
|
|
}
|
|
|
|
// ─── CLOSE (modal wants to close without submitting) ───
|
|
|
|
interface ModalClose {
|
|
type: "factory_modal_close";
|
|
reason: "cancelled" | "timeout" | "error";
|
|
}
|
|
|
|
// ─── ERROR (modal hit an error) ───
|
|
|
|
interface ModalError {
|
|
type: "factory_modal_error";
|
|
error: string;
|
|
details?: unknown;
|
|
}
|
|
|
|
// ─── RESPONSE (the main data submission) ───
|
|
|
|
interface ModalResponse {
|
|
type: "factory_modal_response";
|
|
|
|
// Identity
|
|
modalType: string; // Which modal was used
|
|
modalVersion: string;
|
|
pipelineId: string;
|
|
itemId: string;
|
|
sessionId: string;
|
|
|
|
// Timing
|
|
timestamp: string; // ISO 8601
|
|
responseTimeMs: number; // Time from modal open to submission
|
|
|
|
// The feedback data (see FeedbackEvent below)
|
|
feedback: FeedbackPayload;
|
|
|
|
// Hidden behavioral metrics (ALWAYS collected)
|
|
meta: ModalMetrics;
|
|
}
|
|
|
|
// ═══════════════════════════════════════
|
|
// FEEDBACK PAYLOAD
|
|
// (the meaningful data from the modal)
|
|
// ═══════════════════════════════════════
|
|
|
|
interface FeedbackPayload {
|
|
// Decision (present in most modals)
|
|
decision?: DecisionFeedback;
|
|
|
|
// Scores (multi-dimensional modals)
|
|
scores?: DimensionScore[];
|
|
|
|
// Free text
|
|
freeText?: FreeTextFeedback;
|
|
|
|
// Comparison (A/B modals)
|
|
comparison?: ComparisonFeedback;
|
|
|
|
// Annotations (spotlight/code review)
|
|
annotations?: Annotation[];
|
|
|
|
// Confidence (confidence meter or paired)
|
|
confidence?: ConfidenceFeedback;
|
|
|
|
// Estimation (priority poker)
|
|
estimation?: EstimationFeedback;
|
|
|
|
// Batch decisions (swipe/speed round)
|
|
batchDecisions?: BatchDecision[];
|
|
|
|
// Retrospective (start/stop/continue)
|
|
retrospective?: RetrospectiveFeedback;
|
|
|
|
// Ranking (ranking arena)
|
|
ranking?: RankingFeedback;
|
|
|
|
// Checklist (checklist ceremony)
|
|
checklist?: ChecklistFeedback;
|
|
|
|
// Temperature (thermometer)
|
|
temperature?: TemperatureFeedback;
|
|
|
|
// Custom data (any modal-specific data)
|
|
custom?: Record<string, unknown>;
|
|
}
|
|
|
|
// ═══════════════════════════════════════
|
|
// THE 8 FEEDBACK TYPES (atomic units)
|
|
// ═══════════════════════════════════════
|
|
|
|
// 1. DECISION
|
|
interface DecisionFeedback {
|
|
decision: "approved" | "rejected" | "needs_work" | "deferred" | "skipped";
|
|
reason?: string;
|
|
severity?: "minor" | "moderate" | "major" | "critical";
|
|
blockers?: string[];
|
|
suggestions?: string[];
|
|
tags?: string[]; // e.g., ["naming", "logic", "style"]
|
|
}
|
|
|
|
// 2. DIMENSION SCORE
|
|
interface DimensionScore {
|
|
dimension: QualityDimension;
|
|
score: number; // 1-10 (integer or float)
|
|
weight?: number; // How much the operator cares (learned)
|
|
comment?: string;
|
|
}
|
|
|
|
type QualityDimension =
|
|
| "code_quality"
|
|
| "design_aesthetics"
|
|
| "design_ux"
|
|
| "test_coverage"
|
|
| "documentation"
|
|
| "architecture"
|
|
| "error_handling"
|
|
| "performance"
|
|
| "security"
|
|
| "creativity"
|
|
| "completeness"
|
|
| "naming"
|
|
| "dx";
|
|
|
|
// 3. FREE TEXT
|
|
interface FreeTextFeedback {
|
|
liked?: string;
|
|
disliked?: string;
|
|
generalNotes?: string;
|
|
}
|
|
|
|
// 4. COMPARISON
|
|
interface ComparisonFeedback {
|
|
preferred: "A" | "B" | "neither" | "both_good";
|
|
reason?: string;
|
|
preferenceStrength: "slight" | "moderate" | "strong" | "overwhelming";
|
|
winningFactors?: string[];
|
|
perDimension?: Array<{
|
|
dimension: QualityDimension;
|
|
winner: "A" | "B" | "tie";
|
|
}>;
|
|
timeOnA_ms?: number;
|
|
timeOnB_ms?: number;
|
|
}
|
|
|
|
// 5. ANNOTATION
|
|
interface Annotation {
|
|
type: "praise" | "criticism" | "question" | "suggestion";
|
|
target: {
|
|
file?: string;
|
|
lineStart?: number;
|
|
lineEnd?: number;
|
|
charStart?: number;
|
|
charEnd?: number;
|
|
selector?: string; // CSS selector for design
|
|
region?: string; // Named region
|
|
};
|
|
text: string;
|
|
severity?: "nit" | "minor" | "major" | "critical";
|
|
category?: string;
|
|
}
|
|
|
|
// 6. CONFIDENCE
|
|
interface ConfidenceFeedback {
|
|
confidencePercent: number; // 0-100
|
|
zone: "guess" | "educated_guess" | "confident" | "certain";
|
|
wouldDelegateToAI: boolean;
|
|
needsExpertReview: boolean;
|
|
lowConfidenceReason?: string;
|
|
isTrainingExample?: boolean; // High-confidence decisions can train
|
|
}
|
|
|
|
// 7. ESTIMATION
|
|
interface EstimationFeedback {
|
|
estimate: number | "unknown" | "break"; // Fibonacci: 1,2,3,5,8,13,21
|
|
context?: string;
|
|
hoveredValues?: number[]; // Which cards were considered
|
|
}
|
|
|
|
// 8. BATCH DECISION
|
|
interface BatchDecision {
|
|
itemId: string;
|
|
decision: "approve" | "reject" | "love" | "skip";
|
|
timeMs: number; // Time spent on this item
|
|
flippedForDetails: boolean; // Did they look at full details?
|
|
}
|
|
|
|
// ─── COMPOSITE TYPES ───
|
|
|
|
interface RankingFeedback {
|
|
finalRanking: string[]; // Ordered array of item IDs (best first)
|
|
rankingChanges: number; // Number of reorder operations
|
|
lockedItems: string[]; // Items operator was most confident about
|
|
expandedItems: string[]; // Items that needed closer inspection
|
|
perItemNotes?: Record<string, string>;
|
|
}
|
|
|
|
interface RetrospectiveFeedback {
|
|
start: RetrospectiveNote[];
|
|
stop: RetrospectiveNote[];
|
|
continue_: RetrospectiveNote[]; // "continue" is reserved word, use continue_
|
|
}
|
|
|
|
interface RetrospectiveNote {
|
|
text: string;
|
|
priority?: "urgent" | "important" | "idea";
|
|
}
|
|
|
|
interface ChecklistFeedback {
|
|
checks: Record<string, boolean>; // { criterionId: checked }
|
|
notes: Record<string, string>; // { criterionId: note } for unchecked items
|
|
checkOrder: string[]; // Order items were checked
|
|
allClear: boolean;
|
|
completionRate: number; // 0-1
|
|
}
|
|
|
|
interface TemperatureFeedback {
|
|
temperature: number; // 0-100
|
|
zone: "freezing" | "cold" | "lukewarm" | "warm" | "hot" | "blazing";
|
|
drivers: string[]; // What drove the rating
|
|
dragJourney: number[]; // Temperature values during drag
|
|
}
|
|
|
|
// ═══════════════════════════════════════
|
|
// HIDDEN BEHAVIORAL METRICS
|
|
// (collected by every modal automatically)
|
|
// ═══════════════════════════════════════
|
|
|
|
interface ModalMetrics {
|
|
// Timing
|
|
timeToFirstInteractionMs: number;
|
|
timeToDecisionMs: number;
|
|
|
|
// Engagement
|
|
fieldsModified: string[];
|
|
scrollDepth?: number; // 0-1 (how much content was viewed)
|
|
revisits: number; // Times the modal was re-focused
|
|
totalInteractions: number; // Click/tap count
|
|
|
|
// Context
|
|
viewportSize: { width: number; height: number };
|
|
deviceType: "desktop" | "mobile";
|
|
|
|
// Hover/attention data (modal-specific)
|
|
hoverJourney?: Array<{ target: string; durationMs: number }>;
|
|
hesitationPoints?: Array<{ target: string; durationMs: number }>;
|
|
}
|
|
```
|
|
|
|
### 3.3 FeedbackEvent (Full Storage Schema)
|
|
|
|
```typescript
|
|
// The complete event stored by the learning system.
|
|
// Combines ModalResponse with enriched metadata.
|
|
|
|
interface FeedbackEvent {
|
|
// Identity
|
|
id: string; // UUID v7 (time-sortable)
|
|
timestamp: string; // ISO 8601
|
|
sessionId: string;
|
|
modalType: string;
|
|
modalVersion: string;
|
|
|
|
// What was reviewed
|
|
workProduct: WorkProductRef;
|
|
pipelineId: string | null;
|
|
pipelineStage: PipelineStage | null;
|
|
mcpServerType?: string;
|
|
|
|
// The feedback (from ModalResponse.feedback)
|
|
feedback: FeedbackPayload;
|
|
|
|
// Behavioral metrics (from ModalResponse.meta)
|
|
meta: ModalMetrics & EnrichedMeta;
|
|
}
|
|
|
|
interface WorkProductRef {
|
|
type: "mcp_server" | "code_module" | "design" | "test_suite" | "documentation" | "pipeline_config";
|
|
id: string;
|
|
version: string; // Git SHA or version tag
|
|
path?: string;
|
|
summary: string;
|
|
bubaConfidence: number; // 0-1: AI confidence before review
|
|
bubaScorePrediction?: number; // 1-10: predicted human score
|
|
generationContext: {
|
|
promptHash: string;
|
|
memorySnapshot: string[];
|
|
modelUsed: string;
|
|
generationTimeMs: number;
|
|
iterationCount: number;
|
|
};
|
|
}
|
|
|
|
interface EnrichedMeta {
|
|
timeOfDay: "morning" | "afternoon" | "evening" | "night";
|
|
dayOfWeek: string;
|
|
sessionFatigue: number; // Nth modal in this session
|
|
concurrentModals: number;
|
|
// NLP enrichment (filled by processing pipeline)
|
|
extractedThemes?: string[];
|
|
sentiment?: number; // -1 to 1
|
|
actionableItems?: string[];
|
|
predictionDelta?: number; // actual - predicted score
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. UI Component Contracts
|
|
|
|
### 4.1 Decision Queue Sidebar
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// DecisionQueue Component
|
|
// Location: packages/desktop/ui/desktop/src/components/factory/DecisionQueue.tsx
|
|
// ═══════════════════════════════════════
|
|
|
|
interface DecisionQueueProps {
|
|
// Data
|
|
tasks: Task[];
|
|
selectedTaskId: string | null;
|
|
|
|
// Loading/error state
|
|
isLoading: boolean;
|
|
error: string | null;
|
|
|
|
// Callbacks
|
|
onSelectTask: (taskId: string) => void;
|
|
onApproveTask: (taskId: string) => void;
|
|
onRejectTask: (taskId: string, reason: string) => void;
|
|
onDeferTask: (taskId: string) => void;
|
|
onBatchApprove: (taskIds: string[]) => void;
|
|
onRefresh: () => void;
|
|
|
|
// Filters (controlled)
|
|
filters: DecisionQueueFilters;
|
|
onFiltersChange: (filters: DecisionQueueFilters) => void;
|
|
}
|
|
|
|
interface DecisionQueueFilters {
|
|
priority?: Priority;
|
|
type?: TaskType;
|
|
slaBreached?: boolean;
|
|
search?: string;
|
|
}
|
|
|
|
// ─── Data shape for queue items ───
|
|
|
|
interface DecisionQueueItem {
|
|
id: string;
|
|
title: string;
|
|
type: TaskType;
|
|
priority: Priority;
|
|
pipelineName: string;
|
|
stageName: PipelineStage;
|
|
|
|
// SLA
|
|
slaDeadline: string | null;
|
|
slaBreached: boolean;
|
|
minutesWaiting: number;
|
|
|
|
// Quick context
|
|
previewText: string;
|
|
testSummary?: string; // e.g., "47/47 ✅"
|
|
|
|
// Status indicators
|
|
isSelected: boolean;
|
|
isBatchSelected: boolean;
|
|
|
|
createdAt: string;
|
|
}
|
|
|
|
// ─── Keyboard shortcuts ───
|
|
|
|
// j / ArrowDown → select next task
|
|
// k / ArrowUp → select previous task
|
|
// a → approve selected task
|
|
// r → open reject dialog for selected
|
|
// d → defer selected task
|
|
// Enter → open context panel for selected
|
|
// Shift+a → toggle batch select
|
|
// Ctrl+Shift+a → batch approve all selected
|
|
```
|
|
|
|
### 4.2 Context Panel
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// ContextPanel Component
|
|
// Location: packages/desktop/ui/desktop/src/components/factory/ContextPanel.tsx
|
|
// ═══════════════════════════════════════
|
|
|
|
interface ContextPanelProps {
|
|
// Data
|
|
task: Task | null;
|
|
pipeline: Pipeline | null;
|
|
stages: PipelineStageRecord[];
|
|
assets: Asset[];
|
|
auditTrail: AuditEntry[];
|
|
|
|
// Loading
|
|
isLoading: boolean;
|
|
|
|
// Actions
|
|
onApprove: (notes?: string, conditions?: string[]) => void;
|
|
onReject: (reason: string, requestedChanges?: string[]) => void;
|
|
onDefer: () => void;
|
|
onEscalate: (reason: string) => void;
|
|
onRunTests: () => void;
|
|
onViewDiff: (assetId: string) => void;
|
|
onOpenModal: (modalType: string) => void;
|
|
|
|
// Tab navigation
|
|
activeTab: ContextTab;
|
|
onTabChange: (tab: ContextTab) => void;
|
|
}
|
|
|
|
type ContextTab = "overview" | "diff" | "tests" | "assets" | "timeline" | "modal";
|
|
|
|
// The context panel renders different content based on activeTab:
|
|
// - "overview" → Task summary, pipeline state, key metrics
|
|
// - "diff" → Code diff viewer (DiffSummary rendered)
|
|
// - "tests" → Test results detail (TestResults rendered)
|
|
// - "assets" → Asset list with download links
|
|
// - "timeline" → Audit trail for this pipeline
|
|
// - "modal" → Embedded HITL modal (iframe)
|
|
```
|
|
|
|
### 4.3 Pipeline Kanban
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// PipelineKanban Component
|
|
// Location: packages/desktop/ui/desktop/src/components/factory/PipelineKanban.tsx
|
|
// ═══════════════════════════════════════
|
|
|
|
interface PipelineKanbanProps {
|
|
// Data
|
|
pipelines: Pipeline[];
|
|
|
|
// Callbacks
|
|
onSelectPipeline: (pipelineId: string) => void;
|
|
onAdvanceStage: (pipelineId: string, targetStage: PipelineStage) => void;
|
|
onChangePriority: (pipelineId: string, priority: Priority) => void;
|
|
|
|
// View options
|
|
groupBy: "stage" | "priority" | "assignee";
|
|
onGroupByChange: (groupBy: string) => void;
|
|
}
|
|
|
|
// ─── Kanban stages (columns) ───
|
|
|
|
const KANBAN_STAGES: KanbanStageConfig[] = [
|
|
{ stage: "intake", label: "📥 Intake", color: "#666" },
|
|
{ stage: "scaffolding", label: "🏗️ Scaffolding", color: "#888" },
|
|
{ stage: "building", label: "🔨 Building", color: "#4488FF" },
|
|
{ stage: "testing", label: "🧪 Testing", color: "#FF8844" },
|
|
{ stage: "review", label: "👁️ Review ★", color: "#FFD700", isGate: true },
|
|
{ stage: "staging", label: "🚀 Staging ★", color: "#00AAFF", isGate: true },
|
|
{ stage: "production", label: "🌍 Production ★", color: "#FF4444", isGate: true },
|
|
{ stage: "published", label: "✅ Published", color: "#00CC55" },
|
|
];
|
|
|
|
interface KanbanStageConfig {
|
|
stage: PipelineStage;
|
|
label: string;
|
|
color: string;
|
|
isGate?: boolean; // Requires human approval
|
|
}
|
|
|
|
// ─── Kanban card data shape ───
|
|
|
|
interface KanbanCard {
|
|
id: string; // pipeline ID
|
|
name: string;
|
|
platform: string;
|
|
priority: Priority;
|
|
status: PipelineStatus;
|
|
|
|
// Progress
|
|
currentStage: PipelineStage;
|
|
stageIndex: number; // 0-7
|
|
progressPercent: number; // 0-100
|
|
|
|
// Indicators
|
|
hasBlockers: boolean;
|
|
hasSlaWarning: boolean;
|
|
hasSlaBreached: boolean;
|
|
pendingTaskCount: number;
|
|
|
|
// Quick stats
|
|
testsPassing?: string; // "47/47" or "38/47"
|
|
coveragePercent?: number;
|
|
lastActivity: string; // ISO 8601
|
|
assigneeName?: string;
|
|
}
|
|
```
|
|
|
|
### 4.4 MCP App Iframe Host
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// McpAppHost Component
|
|
// Location: packages/desktop/ui/desktop/src/components/factory/McpAppHost.tsx
|
|
// ═══════════════════════════════════════
|
|
|
|
interface McpAppHostProps {
|
|
// What to render
|
|
htmlContent: string; // Self-contained HTML from MCP tool response
|
|
context: FactoryModalContext; // Injected into iframe as window.__FACTORY_CONTEXT__
|
|
|
|
// Display
|
|
displayType: "inline" | "sidecar";
|
|
maxWidth?: number;
|
|
maxHeight?: number;
|
|
|
|
// Lifecycle callbacks
|
|
onReady: () => void; // Modal sent factory_modal_ready
|
|
onResponse: (response: ModalResponse) => void; // Modal submitted feedback
|
|
onResize: (height: number) => void; // Modal requested resize
|
|
onClose: (reason: string) => void; // Modal requested close
|
|
onError: (error: string) => void; // Modal reported error
|
|
}
|
|
|
|
// ─── Host implementation requirements ───
|
|
|
|
// 1. Create a sandboxed iframe:
|
|
// <iframe sandbox="allow-scripts allow-forms" srcdoc={processedHtml} />
|
|
//
|
|
// 2. Inject context BEFORE modal HTML:
|
|
// Prepend: <script>window.__FACTORY_CONTEXT__ = ${JSON.stringify(context)};</script>
|
|
//
|
|
// 3. Listen for postMessage events:
|
|
// window.addEventListener("message", (event) => {
|
|
// if (event.data.type === "factory_modal_response") { onResponse(event.data); }
|
|
// if (event.data.type === "factory_modal_ready") { onReady(); }
|
|
// if (event.data.type === "factory_modal_resize") { onResize(event.data.height); }
|
|
// if (event.data.type === "factory_modal_close") { onClose(event.data.reason); }
|
|
// if (event.data.type === "factory_modal_error") { onError(event.data.error); }
|
|
// });
|
|
//
|
|
// 4. On factory_modal_response:
|
|
// a. Validate response against FeedbackPayload schema
|
|
// b. Forward to API: POST /v1/feedback
|
|
// c. Close the modal iframe
|
|
// d. Advance to next task in queue
|
|
//
|
|
// 5. On timeout (no response within configurable limit):
|
|
// a. Post "factory_modal_timeout" into the iframe
|
|
// b. Wait 5s for graceful close
|
|
// c. Force close and mark task as "deferred"
|
|
```
|
|
|
|
### 4.5 WebSocket Client
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// useFactoryWebSocket Hook
|
|
// Location: packages/desktop/ui/desktop/src/hooks/useWebSocket.ts
|
|
// ═══════════════════════════════════════
|
|
|
|
interface UseFactoryWebSocketOptions {
|
|
token: string;
|
|
channels: WsChannel[];
|
|
onEvent: (event: WsEvent) => void;
|
|
|
|
// Reconnection
|
|
maxRetries?: number; // default: 10
|
|
retryDelayMs?: number; // default: 1000, doubles each retry
|
|
maxRetryDelayMs?: number; // default: 30000
|
|
}
|
|
|
|
interface UseFactoryWebSocketReturn {
|
|
isConnected: boolean;
|
|
subscribe: (channels: WsChannel[]) => void;
|
|
unsubscribe: (channels: WsChannel[]) => void;
|
|
disconnect: () => void;
|
|
}
|
|
|
|
// ─── How the WebSocket client updates state ───
|
|
|
|
// The hook is used in a FactoryContext provider that maintains all factory state.
|
|
// On each WsEvent, the provider updates its state:
|
|
|
|
type FactoryAction =
|
|
| { type: "PIPELINE_UPDATED"; pipeline: Pipeline }
|
|
| { type: "TASK_CREATED"; task: Task }
|
|
| { type: "TASK_COMPLETED"; task: Task }
|
|
| { type: "TASK_SLA_WARNING"; payload: TaskSlaPayload }
|
|
| { type: "AGENT_STATUS"; payload: AgentStatusPayload }
|
|
| { type: "DEPLOY_UPDATE"; payload: DeployPayload }
|
|
| { type: "NOTIFICATION"; notification: Notification }
|
|
| { type: "SET_TASKS"; tasks: Task[] }
|
|
| { type: "SET_PIPELINES"; pipelines: Pipeline[] }
|
|
| { type: "SET_AGENTS"; agents: Agent[] }
|
|
;
|
|
|
|
interface FactoryState {
|
|
// Core data
|
|
pipelines: Pipeline[];
|
|
tasks: Task[];
|
|
agents: Agent[];
|
|
notifications: Notification[];
|
|
|
|
// Derived
|
|
pendingTaskCount: number;
|
|
slaBreachCount: number;
|
|
|
|
// Connection
|
|
wsConnected: boolean;
|
|
|
|
// Loading
|
|
isInitialLoading: boolean;
|
|
lastUpdated: string;
|
|
}
|
|
|
|
// Event → State mapping:
|
|
//
|
|
// "pipeline.created" → add to pipelines[]
|
|
// "pipeline.stage_changed"→ update pipeline in pipelines[]
|
|
// "pipeline.completed" → update pipeline status
|
|
// "task.created" → add to tasks[], increment pendingTaskCount
|
|
// "task.completed" → update task in tasks[], decrement pendingTaskCount
|
|
// "task.sla_warning" → update task slaWarning flag
|
|
// "task.sla_breached" → update task, increment slaBreachCount
|
|
// "agent.status_changed" → update agent in agents[]
|
|
// "deploy.succeeded" → update pipeline deployments
|
|
// "feedback.received" → (optional) show toast notification
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Rust Backend Contracts
|
|
|
|
### 5.1 Factory Extension Registration
|
|
|
|
```rust
|
|
// ═══════════════════════════════════════
|
|
// How the Factory extension registers with goosed
|
|
// Location: crates/goose-mcp/src/factory/mod.rs
|
|
// ═══════════════════════════════════════
|
|
|
|
// The Factory MCP Server is registered as a built-in extension
|
|
// in the goosed binary, similar to how "developer" and "memory"
|
|
// extensions are registered.
|
|
|
|
// In crates/goose-mcp/src/lib.rs, add:
|
|
// pub mod factory;
|
|
|
|
// In ui/desktop/src/built-in-extensions.json, add:
|
|
```
|
|
|
|
```json
|
|
{
|
|
"id": "factory",
|
|
"name": "Factory Command Center",
|
|
"description": "AI Factory pipeline management — decision queue, approvals, and deployments",
|
|
"enabled": true,
|
|
"type": "builtin"
|
|
}
|
|
```
|
|
|
|
```typescript
|
|
// The Factory extension provides:
|
|
// - 11 MCP tools (§2.1)
|
|
// - 6 MCP resources (§2.2)
|
|
// - 4 MCP prompts (§2.3)
|
|
// - MCP App UI rendering for HITL modals (§2.4)
|
|
|
|
// Extension config in ~/.config/goose/config.yaml:
|
|
```
|
|
|
|
```yaml
|
|
# Factory extension configuration
|
|
extensions:
|
|
factory:
|
|
name: Factory Command Center
|
|
type: builtin
|
|
enabled: true
|
|
config:
|
|
api_base_url: "https://api.goosefactory.dev/v1"
|
|
ws_url: "wss://api.goosefactory.dev/v1/ws"
|
|
# Auth: uses the user's JWT from the desktop app login
|
|
default_priority: "medium"
|
|
auto_advance_low_risk: true
|
|
sla_defaults:
|
|
critical: 3600 # 1 hour
|
|
high: 14400 # 4 hours
|
|
medium: 86400 # 24 hours
|
|
low: 259200 # 72 hours
|
|
```
|
|
|
|
### 5.2 Permission/Approval Flow Modifications
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// GooseFactory modifies Goose's built-in permission system
|
|
// to route factory operations through the approval queue.
|
|
// ═══════════════════════════════════════
|
|
|
|
// Goose permission modes (existing):
|
|
// - auto: Full autonomy
|
|
// - approve: Confirm before any tool use
|
|
// - smart_approve: Risk-based (auto-approve low-risk, flag high-risk)
|
|
// - chat: No tool use
|
|
|
|
// GooseFactory additions:
|
|
// - factory_smart: Like smart_approve but routes high-risk decisions
|
|
// through the Factory decision queue instead of inline prompts.
|
|
|
|
// Risk classification for factory tools:
|
|
|
|
interface FactoryToolRiskClassification {
|
|
// LOW RISK — auto-execute in factory_smart mode
|
|
lowRisk: [
|
|
"factory_get_pending_tasks",
|
|
"factory_get_pipeline_status",
|
|
"factory_get_blockers",
|
|
"factory_search",
|
|
];
|
|
|
|
// MEDIUM RISK — queue for review but don't block
|
|
mediumRisk: [
|
|
"factory_approve_task", // Approving items
|
|
"factory_reject_task", // Rejecting items
|
|
"factory_advance_stage", // Stage advancement
|
|
"factory_assign_priority", // Priority changes
|
|
"factory_run_tests", // Triggering tests
|
|
"factory_create_pipeline", // Creating new pipelines
|
|
];
|
|
|
|
// HIGH RISK — always require explicit human approval
|
|
highRisk: [
|
|
"factory_deploy", // Deployment (especially production)
|
|
];
|
|
}
|
|
|
|
// The permission check flow:
|
|
//
|
|
// 1. Tool call received by goosed
|
|
// 2. goosed checks permission mode (factory_smart)
|
|
// 3. If lowRisk → execute immediately
|
|
// 4. If mediumRisk → execute, log to audit, notify operator
|
|
// 5. If highRisk → create Task in decision queue, block until approved
|
|
// 6. For "factory_deploy" with target="production":
|
|
// → ALWAYS creates a Task with type="approval"
|
|
// → ALWAYS requires explicit human approval
|
|
// → Approval task includes deploy checklist (prompt: deploy_checklist)
|
|
```
|
|
|
|
### 5.3 Config File Format Changes
|
|
|
|
```yaml
|
|
# ═══════════════════════════════════════
|
|
# ~/.config/goose/config.yaml — Factory additions
|
|
# ═══════════════════════════════════════
|
|
|
|
# Existing Goose config fields are preserved.
|
|
# Factory-specific fields are namespaced under `factory:`.
|
|
|
|
factory:
|
|
# API connection
|
|
api_url: "https://api.goosefactory.dev/v1"
|
|
ws_url: "wss://api.goosefactory.dev/v1/ws"
|
|
|
|
# Auth (managed by desktop app login flow)
|
|
# JWT stored in system keychain, not in config file
|
|
|
|
# Operator preferences
|
|
operator:
|
|
name: "Jake"
|
|
default_view: "decision-queue" # "decision-queue" | "kanban" | "chat"
|
|
keyboard_shortcuts: true
|
|
sound_notifications: true
|
|
desktop_notifications: true
|
|
|
|
# SLA configuration
|
|
sla:
|
|
critical_seconds: 3600 # 1 hour
|
|
high_seconds: 14400 # 4 hours
|
|
medium_seconds: 86400 # 24 hours
|
|
low_seconds: 259200 # 72 hours
|
|
warning_threshold_percent: 25 # Warn at 25% time remaining
|
|
|
|
# Escalation
|
|
escalation:
|
|
enabled: true
|
|
channels: ["dashboard", "discord"]
|
|
sms_on_breach: false
|
|
auto_escalate_after_breach_minutes: 120
|
|
|
|
# Learning system
|
|
learning:
|
|
enabled: true
|
|
feedback_storage_path: "~/.config/goose/factory/feedback"
|
|
memory_files_path: "~/.config/goose/factory/memory"
|
|
auto_update_goosehints: true
|
|
min_data_points_for_rules: 5
|
|
confidence_calibration: true
|
|
|
|
# Notification batching
|
|
notifications:
|
|
batch_interval_seconds: 300 # Batch similar notifications over 5min
|
|
quiet_hours_start: "22:00" # No non-critical notifications
|
|
quiet_hours_end: "08:00"
|
|
peak_hours: ["09:00-11:00", "14:00-16:00"] # Prefer sending during these
|
|
|
|
# Auto-approval (learned thresholds)
|
|
auto_approval:
|
|
enabled: false # Disabled until Level 2+ reached
|
|
min_approval_rate: 0.90 # 90% historical approval for this task type
|
|
min_data_points: 25
|
|
excluded_stages: ["production"] # Never auto-approve production
|
|
```
|
|
|
|
### 5.4 Protocol Handler Registration
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// factory:// Protocol Handler
|
|
// Location: ui/desktop/src/main.ts
|
|
// ═══════════════════════════════════════
|
|
|
|
// Replace goose:// with factory:// protocol handler.
|
|
// Supports deep links for quick actions.
|
|
|
|
// Protocol URI patterns:
|
|
|
|
type FactoryProtocolURI =
|
|
| `factory://approve?task_id=${string}`
|
|
| `factory://reject?task_id=${string}`
|
|
| `factory://pipeline?id=${string}`
|
|
| `factory://task?id=${string}`
|
|
| `factory://dashboard`
|
|
| `factory://review?server_name=${string}`
|
|
| `factory://deploy?pipeline_id=${string}&target=${"staging" | "production"}`
|
|
;
|
|
|
|
// Electron main process handler:
|
|
//
|
|
// app.setAsDefaultProtocolClient("factory");
|
|
//
|
|
// app.on("open-url", (event, url) => {
|
|
// event.preventDefault();
|
|
// const parsed = new URL(url);
|
|
// switch (parsed.hostname) {
|
|
// case "approve":
|
|
// handleApproveDeepLink(parsed.searchParams.get("task_id"));
|
|
// break;
|
|
// case "reject":
|
|
// handleRejectDeepLink(parsed.searchParams.get("task_id"));
|
|
// break;
|
|
// case "pipeline":
|
|
// navigateToPipeline(parsed.searchParams.get("id"));
|
|
// break;
|
|
// case "dashboard":
|
|
// navigateToDashboard();
|
|
// break;
|
|
// // ...
|
|
// }
|
|
// });
|
|
|
|
// Deep links are used in:
|
|
// - Discord message buttons (click → opens GooseFactory with context)
|
|
// - Email notifications
|
|
// - Mobile push notifications
|
|
// - Internal cross-references
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Learning System Contracts
|
|
|
|
### 6.1 Feedback Event Flow
|
|
|
|
```
|
|
┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
│ Modal │───▶│ McpApp │───▶│ API │───▶│ Validate │───▶│ Enrich │───▶│ Store │
|
|
│ iframe │ │ Host │ │ POST │ │ │ │ │ │ │
|
|
│ │ │(postMsg) │ │/v1/feed- │ │ Schema │ │ NLP │ │ JSONL │
|
|
│ │ │ │ │ back │ │ Dedup │ │ Themes │ │ Indexes │
|
|
│ │ │ │ │ │ │ Range │ │ Timing │ │ Aggs │
|
|
└──────────┘ └──────────┘ └───────────┘ └──────────┘ └──────────┘ └──────────┘
|
|
│
|
|
▼
|
|
┌──────────┐
|
|
│ Analyze │
|
|
│ │
|
|
│ Patterns │
|
|
│ Calibr. │
|
|
│ Rules │
|
|
└────┬─────┘
|
|
│
|
|
▼
|
|
┌──────────┐
|
|
│ Act │
|
|
│ │
|
|
│ Memory │
|
|
│ .hints │
|
|
│ Thresholds│
|
|
└──────────┘
|
|
```
|
|
|
|
### 6.2 Feedback Processing Pipeline Interfaces
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// STAGE 1: Validate
|
|
// ═══════════════════════════════════════
|
|
|
|
interface ValidationResult {
|
|
valid: boolean;
|
|
event?: FeedbackEvent; // Present if valid
|
|
error?: {
|
|
type: "schema" | "orphaned" | "range" | "duplicate";
|
|
message: string;
|
|
details?: unknown;
|
|
};
|
|
}
|
|
|
|
// function validate(raw: unknown): ValidationResult
|
|
|
|
// ═══════════════════════════════════════
|
|
// STAGE 2: Enrich
|
|
// ═══════════════════════════════════════
|
|
|
|
interface EnrichedFeedbackEvent extends FeedbackEvent {
|
|
// Added by enrichment
|
|
_enriched: true;
|
|
_themes?: string[];
|
|
_sentiment?: number; // -1 to 1
|
|
_actionableItems?: string[];
|
|
_predictionDelta?: number; // actual - predicted
|
|
_pipelineContext?: {
|
|
stage: PipelineStage;
|
|
stagesCompleted: number;
|
|
totalDuration: number;
|
|
};
|
|
}
|
|
|
|
// function enrich(event: FeedbackEvent): EnrichedFeedbackEvent
|
|
|
|
// ═══════════════════════════════════════
|
|
// STAGE 3: Store
|
|
// ═══════════════════════════════════════
|
|
|
|
// Storage locations:
|
|
// feedback/raw/{YYYY-MM-DD}.jsonl — Append-only daily logs
|
|
// feedback/indexes/by-server-type.json
|
|
// feedback/indexes/by-dimension.json
|
|
// feedback/indexes/by-decision.json
|
|
// feedback/indexes/by-stage.json
|
|
// feedback/aggregates/weekly/{YYYY-Www}.json
|
|
// feedback/aggregates/monthly/{YYYY-MM}.json
|
|
// feedback/aggregates/rolling-30d.json
|
|
|
|
// function store(event: EnrichedFeedbackEvent): Promise<void>
|
|
|
|
// ═══════════════════════════════════════
|
|
// STAGE 4: Analyze (batch, runs periodically)
|
|
// ═══════════════════════════════════════
|
|
|
|
interface AnalysisResults {
|
|
// Pattern detection
|
|
approvalPatterns: ApprovalPatterns;
|
|
dimensionTrends: DimensionTrend[];
|
|
|
|
// Theme extraction
|
|
themes: ThemeCluster[];
|
|
|
|
// Calibration
|
|
calibration: CalibrationCurve;
|
|
|
|
// Anomalies
|
|
anomalies: Anomaly[];
|
|
|
|
// Candidate rules
|
|
candidateRules: CandidateRule[];
|
|
}
|
|
|
|
interface ApprovalPatterns {
|
|
overall: {
|
|
approvalRate: number;
|
|
totalDecisions: number;
|
|
avgTimeToDecisionMs: number;
|
|
};
|
|
byServerType: Record<string, {
|
|
approved: number;
|
|
rejected: number;
|
|
needsWork: number;
|
|
approvalRate: number;
|
|
}>;
|
|
byStage: Record<PipelineStage, {
|
|
approvalRate: number;
|
|
avgTimeMs: number;
|
|
count: number;
|
|
}>;
|
|
}
|
|
|
|
interface DimensionTrend {
|
|
dimension: QualityDimension;
|
|
avgScore: number;
|
|
trend: "improving" | "stable" | "declining";
|
|
trendDelta: number; // Change over period
|
|
dataPoints: number;
|
|
minimumThreshold?: number; // Learned rejection threshold
|
|
}
|
|
|
|
interface ThemeCluster {
|
|
theme: string; // e.g., "missing error handling"
|
|
occurrences: number;
|
|
avgSeverity: number;
|
|
lastSeen: string;
|
|
relatedDimensions: QualityDimension[];
|
|
exampleFeedback: string[]; // Quotes from feedback
|
|
}
|
|
|
|
interface Anomaly {
|
|
type: "approval_rate_drop" | "score_spike" | "new_rejection_pattern" | "calibration_drift";
|
|
description: string;
|
|
severity: "info" | "warning" | "critical";
|
|
data: Record<string, unknown>;
|
|
detectedAt: string;
|
|
}
|
|
|
|
interface CandidateRule {
|
|
rule: string; // Human-readable rule
|
|
promptInstruction: string; // How to inject into system prompt
|
|
confidence: number; // 0-1
|
|
occurrences: number;
|
|
source: "feedback_pattern" | "rejection_analysis" | "comparison" | "annotation";
|
|
domain: QualityDimension;
|
|
appliesTo: string[]; // Server types or ["all"]
|
|
}
|
|
|
|
// function analyze(events: EnrichedFeedbackEvent[]): AnalysisResults
|
|
|
|
// ═══════════════════════════════════════
|
|
// STAGE 5: Act (apply learnings)
|
|
// ═══════════════════════════════════════
|
|
|
|
interface ApplyResult {
|
|
memoryFilesUpdated: string[];
|
|
rulesAdded: CandidateRule[];
|
|
thresholdsChanged: Array<{
|
|
dimension: QualityDimension;
|
|
oldThreshold: number;
|
|
newThreshold: number;
|
|
}>;
|
|
autonomyLevelChanges: Array<{
|
|
taskType: string;
|
|
oldLevel: number;
|
|
newLevel: number;
|
|
}>;
|
|
}
|
|
|
|
// function applyLearnings(analysis: AnalysisResults): Promise<ApplyResult>
|
|
```
|
|
|
|
### 6.3 Memory File Schemas
|
|
|
|
```markdown
|
|
# Memory file locations:
|
|
# ~/.config/goose/factory/memory/feedback-patterns.md
|
|
# ~/.config/goose/factory/memory/quality-standards.md
|
|
# ~/.config/goose/factory/memory/jake-preferences.md
|
|
# ~/.config/goose/factory/memory/improvement-log.md
|
|
```
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// feedback-patterns.md structure
|
|
// ═══════════════════════════════════════
|
|
|
|
interface FeedbackPatternsFile {
|
|
lastUpdated: string;
|
|
basedOnEventCount: number;
|
|
|
|
sections: {
|
|
recurringThemes: Array<{
|
|
rank: number;
|
|
theme: string;
|
|
mentionCount: number;
|
|
description: string;
|
|
}>;
|
|
antiPatterns: Array<{
|
|
pattern: string;
|
|
rejectionRate: number; // 0-1
|
|
}>;
|
|
positivePatterns: Array<{
|
|
pattern: string;
|
|
avgScoreBonus: number;
|
|
}>;
|
|
};
|
|
}
|
|
|
|
// ═══════════════════════════════════════
|
|
// quality-standards.md structure
|
|
// ═══════════════════════════════════════
|
|
|
|
interface QualityStandardsFile {
|
|
lastCalibrated: string;
|
|
confidence: "low" | "medium" | "high"; // Based on data point count
|
|
dataPoints: number;
|
|
|
|
sections: {
|
|
minimumScores: Array<{ // Hard gates
|
|
dimension: QualityDimension;
|
|
minimum: number;
|
|
basedOn: number; // review count
|
|
avgScore: number;
|
|
}>;
|
|
targetScores: Array<{ // What "great" looks like
|
|
dimension: QualityDimension;
|
|
target: number;
|
|
topQuartile: number;
|
|
}>;
|
|
calibration: CalibrationCurve;
|
|
};
|
|
}
|
|
|
|
// ═══════════════════════════════════════
|
|
// jake-preferences.md structure
|
|
// ═══════════════════════════════════════
|
|
|
|
interface JakePreferencesFile {
|
|
lastUpdated: string;
|
|
|
|
sections: {
|
|
decisionPatterns: {
|
|
quickApproves: string; // What gets approved fast
|
|
deliberatesOn: string; // What takes time
|
|
autoRejects: string; // Instant rejections
|
|
};
|
|
communicationPreferences: {
|
|
preferredFormats: string[];
|
|
highEngagementTimes: string[];
|
|
lowEngagementTimes: string[];
|
|
};
|
|
designTaste: Record<string, string>;
|
|
codeStyle: Record<string, string>;
|
|
};
|
|
}
|
|
|
|
// ═══════════════════════════════════════
|
|
// improvement-log.md structure
|
|
// ═══════════════════════════════════════
|
|
|
|
interface ImprovementLogEntry {
|
|
date: string;
|
|
changes: Array<{
|
|
type: "rule_added" | "threshold_adjusted" | "calibration_updated" |
|
|
"format_changed" | "autonomy_level_changed";
|
|
description: string;
|
|
source: string; // What triggered this change
|
|
confidence: number;
|
|
}>;
|
|
}
|
|
```
|
|
|
|
### 6.4 Prediction API
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// How the learning system exposes predictions
|
|
// Used by MCP server and UI to make smart decisions
|
|
// ═══════════════════════════════════════
|
|
|
|
// GET /v1/learning/predict
|
|
interface PredictionRequest {
|
|
workProductType: string;
|
|
mcpServerType?: string;
|
|
pipelineStage?: PipelineStage;
|
|
selfAssessedScores?: DimensionScore[];
|
|
rawConfidence?: number; // 0-1
|
|
}
|
|
|
|
interface PredictionResponse {
|
|
predictedScore: number; // 1-10
|
|
calibratedConfidence: number; // 0-1 (adjusted from raw)
|
|
predictedDecision: "approve" | "reject" | "needs_work";
|
|
decisionProbabilities: {
|
|
approve: number;
|
|
reject: number;
|
|
needsWork: number;
|
|
};
|
|
recommendedModal: string; // Best modal type for this review
|
|
riskFactors: string[]; // What might cause rejection
|
|
suggestedImprovements: string[];
|
|
}
|
|
|
|
// GET /v1/learning/autonomy/:taskType
|
|
interface AutonomyLevelResponse {
|
|
taskType: string;
|
|
currentLevel: number; // 0-4
|
|
approvalRate: number;
|
|
dataPoints: number;
|
|
nextLevelRequirements: {
|
|
approvalsNeeded: number;
|
|
minApprovalRate: number;
|
|
};
|
|
recentRegression: boolean;
|
|
}
|
|
|
|
// GET /v1/learning/stats
|
|
interface FeedbackStats {
|
|
totalEvents: number;
|
|
last30Days: {
|
|
approvalRate: number;
|
|
avgQualityScore: number;
|
|
avgTimeToDecisionMs: number;
|
|
predictionAccuracy: number; // ± delta
|
|
autoApproveRate: number;
|
|
feedbackVolume: number;
|
|
};
|
|
trends: {
|
|
approvalRate: "improving" | "stable" | "declining";
|
|
qualityScore: "improving" | "stable" | "declining";
|
|
decisionSpeed: "improving" | "stable" | "declining";
|
|
};
|
|
activeRules: number;
|
|
autonomyLevels: Record<string, number>;
|
|
}
|
|
```
|
|
|
|
### 6.5 Confidence Calibration Data Format
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// Calibration maps raw AI confidence to
|
|
// actual observed approval rates.
|
|
// ═══════════════════════════════════════
|
|
|
|
interface CalibrationCurve {
|
|
lastUpdated: string;
|
|
totalDataPoints: number;
|
|
|
|
buckets: CalibrationBucket[];
|
|
|
|
// Overall calibration error (lower = better)
|
|
expectedCalibrationError: number; // 0-1
|
|
}
|
|
|
|
interface CalibrationBucket {
|
|
// Bucket range
|
|
rawConfidenceLow: number; // e.g., 0.90
|
|
rawConfidenceHigh: number; // e.g., 1.00
|
|
|
|
// Observed
|
|
avgPredictedConfidence: number;
|
|
avgActualApprovalRate: number;
|
|
sampleCount: number;
|
|
|
|
// Correction
|
|
correctionDelta: number; // actual - predicted (negative = overconfident)
|
|
calibratedConfidence: number; // What to use instead of raw
|
|
}
|
|
|
|
// Usage:
|
|
// rawConfidence = 0.92
|
|
// bucket = findBucket(rawConfidence) → bucket for 0.90-1.00
|
|
// calibrated = rawConfidence + bucket.correctionDelta → 0.92 + (-0.12) = 0.80
|
|
// Decision: 80% calibrated confidence → "educated guess" zone, standard review
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Cross-Cutting Contracts
|
|
|
|
### 7.1 Error Handling Conventions
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// Error Code Registry
|
|
// ═══════════════════════════════════════
|
|
|
|
// All error codes are documented. Every API response error
|
|
// uses one of the ErrorCode values from §1.3.
|
|
|
|
// Additional factory-specific error details:
|
|
|
|
interface FactoryErrorDetails {
|
|
// For CONFLICT errors:
|
|
currentState?: string; // Current entity state
|
|
requiredState?: string; // State needed for this operation
|
|
|
|
// For VALIDATION_ERROR:
|
|
validationErrors?: Array<{
|
|
field: string;
|
|
message: string;
|
|
received: unknown;
|
|
expected: string;
|
|
}>;
|
|
|
|
// For FORBIDDEN:
|
|
requiredScope?: Scope;
|
|
userScopes?: Scope[];
|
|
}
|
|
|
|
// ═══════════════════════════════════════
|
|
// Retry Policy
|
|
// ═══════════════════════════════════════
|
|
|
|
interface RetryPolicy {
|
|
// Client-side retry policy for API calls:
|
|
maxRetries: 3;
|
|
retryableStatusCodes: [408, 429, 500, 502, 503, 504];
|
|
backoff: {
|
|
type: "exponential";
|
|
baseDelayMs: 1000;
|
|
maxDelayMs: 30000;
|
|
jitterMs: 500; // Random jitter to prevent thundering herd
|
|
};
|
|
|
|
// For 429 (Rate Limited):
|
|
// Use Retry-After header if present
|
|
// Otherwise use standard backoff
|
|
|
|
// Non-retryable:
|
|
// 400, 401, 403, 404, 409, 410 — fix the request, don't retry
|
|
}
|
|
|
|
// ═══════════════════════════════════════
|
|
// Idempotency
|
|
// ═══════════════════════════════════════
|
|
|
|
// Mutating endpoints support idempotency via:
|
|
// Header: Idempotency-Key: <uuid>
|
|
//
|
|
// The server stores the response for 24h.
|
|
// If the same key is sent again, the stored response is returned.
|
|
// This prevents duplicate approvals, deployments, etc.
|
|
```
|
|
|
|
### 7.2 Logging Format
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// Structured JSON Logging
|
|
// ═══════════════════════════════════════
|
|
|
|
interface LogEntry {
|
|
// Required fields
|
|
timestamp: string; // ISO 8601
|
|
level: "debug" | "info" | "warn" | "error" | "fatal";
|
|
message: string;
|
|
|
|
// Context
|
|
service: "api" | "mcp-server" | "learning" | "ws" | "worker";
|
|
requestId?: string;
|
|
userId?: string;
|
|
agentId?: string;
|
|
|
|
// Structured data
|
|
data?: Record<string, unknown>;
|
|
|
|
// Error info
|
|
error?: {
|
|
name: string;
|
|
message: string;
|
|
stack?: string;
|
|
code?: string;
|
|
};
|
|
|
|
// Performance
|
|
durationMs?: number;
|
|
|
|
// Tracing (OpenTelemetry)
|
|
traceId?: string;
|
|
spanId?: string;
|
|
}
|
|
|
|
// Example:
|
|
// {
|
|
// "timestamp": "2025-07-14T15:30:00.000Z",
|
|
// "level": "info",
|
|
// "message": "Task approved",
|
|
// "service": "api",
|
|
// "requestId": "req_abc123",
|
|
// "userId": "user_xyz",
|
|
// "data": {
|
|
// "taskId": "task_456",
|
|
// "pipelineId": "pipe_789",
|
|
// "decision": "approved",
|
|
// "durationMs": 45
|
|
// }
|
|
// }
|
|
|
|
// Log levels:
|
|
// debug → Development only, verbose trace info
|
|
// info → Normal operations: requests, state changes, events
|
|
// warn → Recoverable issues: retries, degraded performance, SLA warnings
|
|
// error → Failed operations: unhandled errors, integration failures
|
|
// fatal → System-level failures: cannot start, data corruption
|
|
```
|
|
|
|
### 7.3 Environment Variables
|
|
|
|
```bash
|
|
# ═══════════════════════════════════════
|
|
# API Server (packages/api)
|
|
# ═══════════════════════════════════════
|
|
|
|
FACTORY_API_PORT=3100
|
|
FACTORY_API_HOST=0.0.0.0
|
|
|
|
# Database
|
|
DATABASE_URL=postgresql://user:pass@localhost:5432/goosefactory
|
|
DATABASE_POOL_SIZE=20
|
|
|
|
# Redis
|
|
REDIS_URL=redis://localhost:6379
|
|
|
|
# Auth
|
|
JWT_SECRET=<256-bit secret>
|
|
JWT_EXPIRY_SECONDS=900 # 15 minutes
|
|
REFRESH_TOKEN_EXPIRY_SECONDS=604800 # 7 days
|
|
|
|
# Storage
|
|
S3_ENDPOINT=https://your-r2.r2.cloudflarestorage.com
|
|
S3_BUCKET=goosefactory-assets
|
|
S3_ACCESS_KEY_ID=<key>
|
|
S3_SECRET_ACCESS_KEY=<secret>
|
|
|
|
# External integrations
|
|
DISCORD_BOT_TOKEN=<token>
|
|
DISCORD_GUILD_ID=<guild_id>
|
|
DISCORD_TASKS_CHANNEL_ID=<channel_id>
|
|
GITHUB_APP_ID=<app_id>
|
|
GITHUB_PRIVATE_KEY=<base64-encoded>
|
|
|
|
# Observability
|
|
LOG_LEVEL=info # debug | info | warn | error
|
|
OTEL_EXPORTER_ENDPOINT=https://otel.example.com
|
|
OTEL_SERVICE_NAME=goosefactory-api
|
|
|
|
# ═══════════════════════════════════════
|
|
# MCP Server (packages/mcp-server)
|
|
# ═══════════════════════════════════════
|
|
|
|
FACTORY_MCP_TRANSPORT=stdio # stdio | sse
|
|
FACTORY_API_URL=http://localhost:3100/v1
|
|
FACTORY_API_KEY=<internal-api-key>
|
|
|
|
# ═══════════════════════════════════════
|
|
# Learning System (packages/learning)
|
|
# ═══════════════════════════════════════
|
|
|
|
FEEDBACK_STORAGE_PATH=~/.config/goose/factory/feedback
|
|
MEMORY_FILES_PATH=~/.config/goose/factory/memory
|
|
ANALYSIS_INTERVAL_MINUTES=60 # Run analysis every hour
|
|
MIN_DATA_POINTS_FOR_RULES=5
|
|
CONFIDENCE_CALIBRATION_ENABLED=true
|
|
|
|
# ═══════════════════════════════════════
|
|
# Desktop App (packages/desktop)
|
|
# ═══════════════════════════════════════
|
|
|
|
FACTORY_API_URL=https://api.goosefactory.dev/v1
|
|
FACTORY_WS_URL=wss://api.goosefactory.dev/v1/ws
|
|
# GOOSE_PROVIDER, GOOSE_MODEL etc. are inherited from Goose
|
|
```
|
|
|
|
### 7.4 Monorepo File/Folder Structure
|
|
|
|
```
|
|
goosefactory/
|
|
├── CONTRACTS.md # ← THIS FILE (the source of truth)
|
|
├── README.md # Project overview
|
|
├── package.json # Monorepo root (workspaces)
|
|
├── turbo.json # Turborepo config (or nx.json)
|
|
├── tsconfig.base.json # Shared TypeScript config
|
|
├── .env.example # Template env vars
|
|
├── .gitignore
|
|
│
|
|
├── packages/
|
|
│ ├── shared/ # ← SHARED TYPES (generated from CONTRACTS.md)
|
|
│ │ ├── package.json
|
|
│ │ ├── tsconfig.json
|
|
│ │ └── src/
|
|
│ │ ├── types/
|
|
│ │ │ ├── pipeline.ts # Pipeline, PipelineStage, etc.
|
|
│ │ │ ├── task.ts # Task, TaskStatus, etc.
|
|
│ │ │ ├── approval.ts # Approval, ApprovalType, etc.
|
|
│ │ │ ├── agent.ts # Agent, AgentHealth, etc.
|
|
│ │ │ ├── asset.ts # Asset, AssetType, etc.
|
|
│ │ │ ├── feedback.ts # FeedbackEvent, FeedbackPayload, etc.
|
|
│ │ │ ├── modal.ts # ModalMessage, ModalResponse, etc.
|
|
│ │ │ ├── api.ts # ApiResponse, ApiError, PaginationParams
|
|
│ │ │ ├── ws.ts # WsEvent, WsChannel, event payloads
|
|
│ │ │ └── index.ts # Re-exports everything
|
|
│ │ ├── constants/
|
|
│ │ │ ├── stages.ts # KANBAN_STAGES, stage order
|
|
│ │ │ ├── errors.ts # ErrorCode enum values
|
|
│ │ │ └── sla.ts # Default SLA values
|
|
│ │ └── schemas/ # Zod schemas for runtime validation
|
|
│ │ ├── feedback.schema.ts
|
|
│ │ ├── pipeline.schema.ts
|
|
│ │ └── task.schema.ts
|
|
│ │
|
|
│ ├── api/ # API Engineer's domain
|
|
│ │ ├── package.json
|
|
│ │ ├── tsconfig.json
|
|
│ │ ├── Dockerfile
|
|
│ │ └── src/
|
|
│ │ ├── server.ts
|
|
│ │ ├── routes/
|
|
│ │ ├── services/
|
|
│ │ ├── ws/
|
|
│ │ ├── middleware/
|
|
│ │ └── db/
|
|
│ │
|
|
│ ├── mcp-server/ # MCP Engineer's domain
|
|
│ │ ├── package.json
|
|
│ │ ├── tsconfig.json
|
|
│ │ └── src/
|
|
│ │ ├── index.ts
|
|
│ │ ├── tools/
|
|
│ │ ├── resources/
|
|
│ │ ├── prompts/
|
|
│ │ └── apps/
|
|
│ │
|
|
│ ├── modals/ # Modal Designer's domain
|
|
│ │ ├── package.json
|
|
│ │ ├── tsconfig.json
|
|
│ │ └── src/
|
|
│ │ ├── modals/ # 25 HTML modal files
|
|
│ │ │ ├── traffic-light.html
|
|
│ │ │ ├── tinder-swipe.html
|
|
│ │ │ └── ...
|
|
│ │ ├── shared/ # Common CSS/JS
|
|
│ │ │ ├── base-styles.css
|
|
│ │ │ ├── submit-handler.js
|
|
│ │ │ ├── timing-tracker.js
|
|
│ │ │ └── celebration-effects.js
|
|
│ │ └── registry.ts # Modal registrations
|
|
│ │
|
|
│ ├── learning/ # Learning Engineer's domain
|
|
│ │ ├── package.json
|
|
│ │ ├── tsconfig.json
|
|
│ │ └── src/
|
|
│ │ ├── pipeline/
|
|
│ │ ├── memory/
|
|
│ │ ├── calibration/
|
|
│ │ └── rules/
|
|
│ │
|
|
│ └── desktop/ # UI Engineer's domain (Goose fork)
|
|
│ ├── ... (forked Goose repo)
|
|
│ └── ui/desktop/src/
|
|
│ └── components/factory/ # Factory-specific UI components
|
|
│
|
|
└── infra/
|
|
├── db/
|
|
│ ├── migrations/
|
|
│ └── seed/
|
|
└── docker/
|
|
├── docker-compose.yml
|
|
├── docker-compose.dev.yml
|
|
├── Dockerfile.api
|
|
└── Dockerfile.learning
|
|
```
|
|
|
|
### 7.5 Shared Package Convention
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// packages/shared is the SINGLE source of
|
|
// TypeScript types. All other packages import from it.
|
|
// ═══════════════════════════════════════
|
|
|
|
// In each package's package.json:
|
|
// "dependencies": {
|
|
// "@goosefactory/shared": "workspace:*"
|
|
// }
|
|
|
|
// Import convention:
|
|
// import type { Pipeline, Task, FeedbackEvent } from "@goosefactory/shared";
|
|
// import { KANBAN_STAGES, DEFAULT_SLA } from "@goosefactory/shared/constants";
|
|
// import { feedbackSchema } from "@goosefactory/shared/schemas";
|
|
|
|
// RULE: If you need a new type that crosses package boundaries,
|
|
// add it to @goosefactory/shared first, then use it.
|
|
// Never define the same type in two packages.
|
|
```
|
|
|
|
### 7.6 API Versioning & Deprecation
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// Versioning rules
|
|
// ═══════════════════════════════════════
|
|
|
|
// 1. URL path versioning: /v1/, /v2/, etc.
|
|
// 2. Minor changes (new optional fields, new endpoints) → same version
|
|
// 3. Breaking changes (removed fields, changed types) → new version
|
|
// 4. Deprecated versions get a Sunset header with 90-day notice
|
|
// 5. Only 2 versions supported at any time (current + previous)
|
|
|
|
// Response headers on deprecated endpoints:
|
|
// Sunset: Sat, 14 Oct 2025 00:00:00 GMT
|
|
// Deprecation: true
|
|
// Link: <https://api.goosefactory.dev/v2/pipelines>; rel="successor-version"
|
|
```
|
|
|
|
### 7.7 Event Bus Contract
|
|
|
|
```typescript
|
|
// ═══════════════════════════════════════
|
|
// Redis Streams event bus
|
|
// Used for decoupled async processing
|
|
// ═══════════════════════════════════════
|
|
|
|
// Stream names:
|
|
const REDIS_STREAMS = {
|
|
pipelines: "factory.pipelines",
|
|
tasks: "factory.tasks",
|
|
agents: "factory.agents",
|
|
deploys: "factory.deploys",
|
|
notifications: "factory.notifications",
|
|
feedback: "factory.feedback",
|
|
} as const;
|
|
|
|
// Consumer groups (each independently processes events):
|
|
const CONSUMER_GROUPS = {
|
|
wsBroadcaster: "ws-broadcaster", // Fans out to WebSocket clients
|
|
notificationWorker: "notification-worker",
|
|
discordBridge: "discord-bridge",
|
|
slaMonitor: "sla-monitor",
|
|
auditWriter: "audit-writer",
|
|
mcpResourceUpdater: "mcp-resource-updater",
|
|
feedbackProcessor: "feedback-processor",
|
|
} as const;
|
|
|
|
// Event format in Redis Stream:
|
|
interface RedisStreamEvent {
|
|
id: string; // Redis stream ID (timestamp-sequence)
|
|
type: WebSocketEventType;
|
|
timestamp: string;
|
|
data: string; // JSON-serialized payload
|
|
source: string; // Which service produced this
|
|
}
|
|
|
|
// Each consumer group processes events independently.
|
|
// Consumer group acknowledges events after processing (exactly-once within group).
|
|
// Events are retained for 7 days, then trimmed.
|
|
```
|
|
|
|
---
|
|
|
|
## Appendix A: Stage Transition Matrix
|
|
|
|
```
|
|
FROM → TO | Trigger | Creates Task? | Validation
|
|
─────────────────────────────────────────────────────────────────────────────
|
|
intake → scaffolding | Pipeline created | No | None
|
|
scaffolding → building | Scaffold complete | No | Assets exist
|
|
building → testing | Build succeeds | No | Build artifact exists
|
|
testing → review | Tests pass | YES (review) | Coverage ≥ threshold
|
|
review → staging | Approval approved | YES (deploy) | Code review approved
|
|
staging → production | Staging tests pass | YES (deploy) | Staging deploy healthy
|
|
production → published | Production deploy OK | No | Deploy health check
|
|
ANY → failed | Unrecoverable error | YES (fix) | None
|
|
failed → {previous} | Manual retry | YES (approval)| None
|
|
```
|
|
|
|
## Appendix B: SLA Escalation Timeline
|
|
|
|
```
|
|
T+0min Task created → Dashboard notification + Discord embed
|
|
T+30min Reminder #1 → Discord DM + dashboard badge pulse
|
|
T+2h Reminder #2 → Discord @mention + push notification
|
|
T+SLA-25% SLA Warning → Discord @here + sound alert in app
|
|
T+SLA SLA Breach → All channels + SMS (if configured)
|
|
T+SLA+2h Critical → Phone + auto-escalate to admin
|
|
```
|
|
|
|
## Appendix C: Modal Type → Use Case Mapping
|
|
|
|
```
|
|
Modal Type | Use Case | Feedback Types Captured
|
|
────────────────────────────────────────────────────────────────────
|
|
traffic-light | binary_decision | decision, tags
|
|
tinder-swipe | batch_review | batchDecisions
|
|
report-card | multi_dimensional | scores, freeText
|
|
thermometer | subjective | temperature
|
|
spotlight | annotation | annotations
|
|
ranking-arena | comparison | ranking
|
|
speed-round | batch_review | batchDecisions
|
|
emoji-scale | subjective | decision (sentiment)
|
|
before-after | comparison | comparison
|
|
priority-poker | estimation | estimation
|
|
mission-briefing | high_stakes | decision, checklist, confidence
|
|
judges-scorecard | multi_dimensional | scores
|
|
quick-pulse | subjective | temperature (quick)
|
|
decision-tree | binary_decision | decision (branching)
|
|
hot-take | binary_decision | decision (timed)
|
|
side-by-side | comparison | comparison
|
|
checklist-ceremony | verification | checklist
|
|
confidence-meter | (paired w/ any) | confidence
|
|
voice-of-customer | subjective | scores, freeText
|
|
retrospective | retrospective | retrospective
|
|
slot-machine | subjective | scores (gamified)
|
|
mood-ring | subjective | custom (color/mood)
|
|
war-room | triage | batchDecisions
|
|
applause-meter | subjective | custom (tap count)
|
|
crystal-ball | estimation | estimation, custom
|
|
```
|
|
|
|
---
|
|
|
|
*This document is the single source of truth for all GooseFactory inter-agent contracts. Every type defined here is canonical. If implementation differs from this document, the implementation is wrong.*
|