//! Database models for the files table. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use sqlx::FromRow; use utoipa::ToSchema; use uuid::Uuid; /// TranscriptEntry stored in JSONB - matches frontend TranscriptEntry #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct TranscriptEntry { pub id: String, pub speaker: String, pub start: f32, pub end: f32, pub text: String, pub is_final: bool, } /// Chart type for visualization elements #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "lowercase")] pub enum ChartType { Line, Bar, Pie, Area, } /// Body element types for structured file content #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(tag = "type", rename_all = "camelCase")] pub enum BodyElement { /// Heading element (h1-h6) Heading { level: u8, text: String }, /// Paragraph text Paragraph { text: String }, /// Code block with optional language Code { language: Option, content: String, }, /// List (ordered or unordered) List { ordered: bool, items: Vec, }, /// Chart visualization Chart { #[serde(rename = "chartType")] chart_type: ChartType, title: Option, data: serde_json::Value, config: Option, }, /// Image element (deferred for MVP) Image { src: String, alt: Option, caption: Option, }, /// Raw markdown content - renders as formatted markdown, edits as raw text Markdown { content: String }, } /// File record from the database. #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct File { pub id: Uuid, pub owner_id: Uuid, /// Contract this file belongs to (optional) pub contract_id: Option, /// Phase of the contract when file was added (e.g., "research", "specify") pub contract_phase: Option, pub name: String, pub description: Option, #[sqlx(json)] pub transcript: Vec, pub location: Option, /// AI-generated summary of the transcript pub summary: Option, /// Structured body content (headings, paragraphs, charts) #[sqlx(json)] pub body: Vec, /// Version number for optimistic locking pub version: i32, /// Path to linked repository file (e.g., "README.md", "docs/design.md") pub repo_file_path: Option, /// When the file was last synced from the repository pub repo_synced_at: Option>, /// Sync status: 'none', 'synced', 'modified', 'conflict' pub repo_sync_status: Option, pub created_at: DateTime, pub updated_at: DateTime, } /// Request payload for creating a new file. #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct CreateFileRequest { /// Contract this file belongs to (required - files must belong to a contract) pub contract_id: Uuid, /// Name of the file (auto-generated if not provided) pub name: Option, /// Optional description pub description: Option, /// Transcript entries (default to empty) #[serde(default)] pub transcript: Vec, /// Storage location (e.g., s3://bucket/path) - not used yet pub location: Option, /// Initial body elements (e.g., from a template) #[serde(default)] pub body: Vec, /// Path to linked repository file (e.g., "README.md") pub repo_file_path: Option, /// Contract phase this file belongs to (for deliverable tracking) pub contract_phase: Option, } /// Request payload for updating an existing file. #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct UpdateFileRequest { /// New name (optional) pub name: Option, /// New description (optional) pub description: Option, /// New transcript (optional) pub transcript: Option>, /// AI-generated summary (optional) pub summary: Option, /// Structured body content (optional) pub body: Option>, /// Version for optimistic locking (required for updates from frontend) pub version: Option, /// Path to linked repository file (e.g., "README.md") pub repo_file_path: Option, } /// Response for file list endpoint. #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct FileListResponse { pub files: Vec, pub total: i64, } /// Summary of a file for list views (excludes full transcript). #[derive(Debug, Clone, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct FileSummary { pub id: Uuid, /// Contract this file belongs to pub contract_id: Option, /// Contract name (joined from contracts table) pub contract_name: Option, /// Phase when file was added to contract pub contract_phase: Option, pub name: String, pub description: Option, pub transcript_count: usize, /// Duration derived from last transcript end time pub duration: Option, /// Version number for optimistic locking pub version: i32, /// Path to linked repository file pub repo_file_path: Option, /// Sync status with repository pub repo_sync_status: Option, pub created_at: DateTime, pub updated_at: DateTime, } impl From for FileSummary { fn from(file: File) -> Self { let duration = file .transcript .iter() .map(|t| t.end) .fold(0.0_f32, f32::max); Self { id: file.id, contract_id: file.contract_id, contract_name: None, // Not available from File alone, requires JOIN contract_phase: file.contract_phase, name: file.name, description: file.description, transcript_count: file.transcript.len(), duration: if duration > 0.0 { Some(duration) } else { None }, version: file.version, repo_file_path: file.repo_file_path, repo_sync_status: file.repo_sync_status, created_at: file.created_at, updated_at: file.updated_at, } } } // ============================================================================= // Version History Types // ============================================================================= /// Source of a version change #[derive(Debug, Clone, Serialize, Deserialize, ToSchema, sqlx::Type)] #[sqlx(type_name = "varchar")] #[serde(rename_all = "lowercase")] pub enum VersionSource { #[sqlx(rename = "user")] User, #[sqlx(rename = "llm")] Llm, #[sqlx(rename = "system")] System, } impl std::fmt::Display for VersionSource { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { VersionSource::User => write!(f, "user"), VersionSource::Llm => write!(f, "llm"), VersionSource::System => write!(f, "system"), } } } impl std::str::FromStr for VersionSource { type Err = String; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "user" => Ok(VersionSource::User), "llm" => Ok(VersionSource::Llm), "system" => Ok(VersionSource::System), _ => Err(format!("Unknown version source: {}", s)), } } } /// Full version record from the database #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct FileVersion { pub id: Uuid, pub file_id: Uuid, pub version: i32, pub name: String, pub description: Option, pub summary: Option, #[sqlx(json)] pub body: Vec, pub source: String, pub change_description: Option, pub created_at: DateTime, } /// Summary of a version for list views #[derive(Debug, Clone, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct FileVersionSummary { pub version: i32, pub source: String, pub created_at: DateTime, pub change_description: Option, } impl From for FileVersionSummary { fn from(v: FileVersion) -> Self { Self { version: v.version, source: v.source, created_at: v.created_at, change_description: v.change_description, } } } /// Response for version list endpoint #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct FileVersionListResponse { pub versions: Vec, pub total: i64, } /// Request to restore a file to a previous version #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct RestoreVersionRequest { /// The version to restore to pub target_version: i32, /// The current version (for optimistic locking) pub current_version: i32, } // ============================================================================= // Mesh/Task Types // ============================================================================= /// Task status for orchestrating Claude Code instances #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "lowercase")] pub enum TaskStatus { Pending, Running, Paused, Blocked, Done, Failed, Merged, } impl std::fmt::Display for TaskStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { TaskStatus::Pending => write!(f, "pending"), TaskStatus::Running => write!(f, "running"), TaskStatus::Paused => write!(f, "paused"), TaskStatus::Blocked => write!(f, "blocked"), TaskStatus::Done => write!(f, "done"), TaskStatus::Failed => write!(f, "failed"), TaskStatus::Merged => write!(f, "merged"), } } } impl std::str::FromStr for TaskStatus { type Err = String; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "pending" => Ok(TaskStatus::Pending), "running" => Ok(TaskStatus::Running), "paused" => Ok(TaskStatus::Paused), "blocked" => Ok(TaskStatus::Blocked), "done" => Ok(TaskStatus::Done), "failed" => Ok(TaskStatus::Failed), "merged" => Ok(TaskStatus::Merged), _ => Err(format!("Unknown task status: {}", s)), } } } /// Merge mode for task completion #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "lowercase")] pub enum MergeMode { /// Create a PR for review Pr, /// Auto-merge to target branch Auto, /// Manual merge by user Manual, } impl std::fmt::Display for MergeMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { MergeMode::Pr => write!(f, "pr"), MergeMode::Auto => write!(f, "auto"), MergeMode::Manual => write!(f, "manual"), } } } impl std::str::FromStr for MergeMode { type Err = String; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "pr" => Ok(MergeMode::Pr), "auto" => Ok(MergeMode::Auto), "manual" => Ok(MergeMode::Manual), _ => Err(format!("Unknown merge mode: {}", s)), } } } /// Task record from the database #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct Task { pub id: Uuid, pub owner_id: Uuid, /// Contract this task belongs to (required for new tasks) pub contract_id: Option, pub parent_task_id: Option, /// Depth in task hierarchy (no longer constrained) pub depth: i32, pub name: String, pub description: Option, pub status: String, pub priority: i32, pub plan: String, // Supervisor flag /// True for contract supervisor tasks. Only supervisors can spawn new tasks. #[serde(default)] pub is_supervisor: bool, // Daemon/container info pub daemon_id: Option, pub container_id: Option, pub overlay_path: Option, // Repository info pub repository_url: Option, pub base_branch: Option, pub target_branch: Option, // Merge settings pub merge_mode: Option, pub pr_url: Option, // Completion action settings /// Path to user's local repository (outside ~/.makima) pub target_repo_path: Option, /// Action on completion: "none", "branch", "merge", "pr" pub completion_action: Option, // Progress tracking pub progress_summary: Option, pub last_output: Option, pub error_message: Option, // Git checkpoint tracking /// Git commit SHA of the most recent checkpoint #[serde(skip_serializing_if = "Option::is_none")] pub last_checkpoint_sha: Option, /// Number of checkpoints created by this task #[serde(default)] pub checkpoint_count: i32, /// Message from the most recent checkpoint #[serde(skip_serializing_if = "Option::is_none")] pub checkpoint_message: Option, // Conversation state for resumption /// Saved conversation context for task/supervisor resumption #[serde(skip_serializing_if = "Option::is_none")] pub conversation_state: Option, // Daemon migration tracking /// Previous daemon if task was migrated #[serde(skip_serializing_if = "Option::is_none")] pub migrated_from_daemon_id: Option, /// Most recent daemon that worked on this task #[serde(skip_serializing_if = "Option::is_none")] pub last_active_daemon_id: Option, // Timestamps pub started_at: Option>, pub completed_at: Option>, pub version: i32, pub created_at: DateTime, pub updated_at: DateTime, // Task continuation /// Task ID to continue from (copy worktree from this task when starting). /// Used for sequential subtask dependencies. #[serde(skip_serializing_if = "Option::is_none")] pub continue_from_task_id: Option, /// Files to copy from parent task's worktree when starting. #[serde(skip_serializing_if = "Option::is_none")] pub copy_files: Option, } impl Task { /// Parse status string to TaskStatus enum pub fn status_enum(&self) -> Result { self.status.parse() } /// Parse merge_mode string to MergeMode enum pub fn merge_mode_enum(&self) -> Option> { self.merge_mode.as_ref().map(|s| s.parse()) } } /// Summary of a task for list views #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct TaskSummary { pub id: Uuid, /// Contract this task belongs to pub contract_id: Option, /// Contract name (joined from contracts table) pub contract_name: Option, /// Contract phase (joined from contracts table) pub contract_phase: Option, pub parent_task_id: Option, /// Depth in task hierarchy: 0=orchestrator (top-level), 1=subtask (max) pub depth: i32, pub name: String, pub status: String, pub priority: i32, pub progress_summary: Option, pub subtask_count: i64, pub version: i32, /// True for contract supervisor tasks #[serde(default)] pub is_supervisor: bool, pub created_at: DateTime, pub updated_at: DateTime, } /// Convert a full Task to a TaskSummary impl From for TaskSummary { fn from(task: Task) -> Self { Self { id: task.id, contract_id: task.contract_id, contract_name: None, // Not available from Task directly contract_phase: None, // Not available from Task directly parent_task_id: task.parent_task_id, depth: task.depth, name: task.name, status: task.status, priority: task.priority, progress_summary: task.progress_summary, subtask_count: 0, // Would need separate query version: task.version, is_supervisor: task.is_supervisor, created_at: task.created_at, updated_at: task.updated_at, } } } /// Response for task list endpoint #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct TaskListResponse { pub tasks: Vec, pub total: i64, } /// Request payload for creating a new task #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct CreateTaskRequest { /// Contract this task belongs to (required) pub contract_id: Uuid, /// Name of the task pub name: String, /// Optional description pub description: Option, /// The plan/instructions for Claude Code pub plan: String, /// Parent task ID (for subtasks) pub parent_task_id: Option, /// True for contract supervisor tasks. Only supervisors can spawn new tasks. #[serde(default)] pub is_supervisor: bool, /// Priority (higher = more urgent) #[serde(default)] pub priority: i32, /// Repository URL pub repository_url: Option, /// Base branch for overlay pub base_branch: Option, /// Target branch to merge into pub target_branch: Option, /// Merge mode (pr, auto, manual) pub merge_mode: Option, /// Path to user's local repository (outside ~/.makima) pub target_repo_path: Option, /// Action on completion: "none", "branch", "merge", "pr" pub completion_action: Option, /// Task ID to continue from (copy worktree from this task when starting) pub continue_from_task_id: Option, /// Files to copy from parent task's worktree when starting #[serde(skip_serializing_if = "Option::is_none")] pub copy_files: Option>, /// Checkpoint SHA to branch from (optional) pub checkpoint_sha: Option, } /// Request payload for updating a task #[derive(Debug, Default, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct UpdateTaskRequest { pub name: Option, pub description: Option, pub plan: Option, pub status: Option, pub priority: Option, pub progress_summary: Option, pub last_output: Option, pub error_message: Option, pub merge_mode: Option, pub pr_url: Option, /// Repository URL for the task (e.g., when updating supervisor with repo info) pub repository_url: Option, /// Path to user's local repository (outside ~/.makima) pub target_repo_path: Option, /// Action on completion: "none", "branch", "merge", "pr" pub completion_action: Option, /// The daemon currently running this task pub daemon_id: Option, /// Explicitly clear daemon_id (set to NULL) #[serde(default)] pub clear_daemon_id: bool, /// Version for optimistic locking pub version: Option, } /// Task with its subtasks for detail view #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct TaskWithSubtasks { #[serde(flatten)] pub task: Task, pub subtasks: Vec, } /// Request to send a message to a running task's stdin. #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct SendMessageRequest { /// The message to send to the task's stdin. pub message: String, } // ============================================================================= // Daemon Types // ============================================================================= /// Daemon status #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "lowercase")] pub enum DaemonStatus { Connected, Disconnected, Unhealthy, } impl std::fmt::Display for DaemonStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { DaemonStatus::Connected => write!(f, "connected"), DaemonStatus::Disconnected => write!(f, "disconnected"), DaemonStatus::Unhealthy => write!(f, "unhealthy"), } } } impl std::str::FromStr for DaemonStatus { type Err = String; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "connected" => Ok(DaemonStatus::Connected), "disconnected" => Ok(DaemonStatus::Disconnected), "unhealthy" => Ok(DaemonStatus::Unhealthy), _ => Err(format!("Unknown daemon status: {}", s)), } } } /// Connected daemon record from the database #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct Daemon { pub id: Uuid, pub owner_id: Uuid, pub connection_id: String, pub hostname: Option, pub machine_id: Option, pub max_concurrent_tasks: i32, pub current_task_count: i32, pub status: String, pub last_heartbeat_at: DateTime, pub connected_at: DateTime, pub disconnected_at: Option>, } impl Daemon { /// Parse status string to DaemonStatus enum pub fn status_enum(&self) -> Result { self.status.parse() } } /// Response for daemon list endpoint #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct DaemonListResponse { pub daemons: Vec, pub total: i64, } /// Response for daemon directories endpoint #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct DaemonDirectoriesResponse { /// List of suggested directories from connected daemons pub directories: Vec, } /// A suggested directory from a daemon #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct DaemonDirectory { /// Path to the directory pub path: String, /// Display label for the directory pub label: String, /// Type of directory: "working", "makima", "worktrees" pub directory_type: String, /// Daemon hostname this directory is from pub hostname: Option, /// Whether the directory already exists (for validation) #[serde(skip_serializing_if = "Option::is_none")] pub exists: Option, } // ============================================================================= // Task Event Types // ============================================================================= /// Task event record from the database #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct TaskEvent { pub id: Uuid, pub task_id: Uuid, pub event_type: String, pub previous_status: Option, pub new_status: Option, #[sqlx(json)] pub event_data: Option, pub created_at: DateTime, } /// Response for task events list endpoint #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct TaskEventListResponse { pub events: Vec, pub total: i64, } /// A single output entry from a Claude Code task #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct TaskOutputEntry { pub id: Uuid, pub task_id: Uuid, /// Message type: "assistant", "tool_use", "tool_result", "result", "system", "error", "raw" pub message_type: String, /// Main text content pub content: String, /// Tool name if tool_use message #[serde(skip_serializing_if = "Option::is_none")] pub tool_name: Option, /// Tool input JSON if tool_use message #[serde(skip_serializing_if = "Option::is_none")] pub tool_input: Option, /// Whether tool result was an error #[serde(skip_serializing_if = "Option::is_none")] pub is_error: Option, /// Cost in USD if result message #[serde(skip_serializing_if = "Option::is_none")] pub cost_usd: Option, /// Duration in ms if result message #[serde(skip_serializing_if = "Option::is_none")] pub duration_ms: Option, /// Timestamp when this output was recorded pub created_at: DateTime, } impl TaskOutputEntry { /// Convert a TaskEvent with event_type='output' to a TaskOutputEntry pub fn from_task_event(event: TaskEvent) -> Option { if event.event_type != "output" { return None; } let data = event.event_data?; Some(Self { id: event.id, task_id: event.task_id, message_type: data.get("messageType")?.as_str()?.to_string(), content: data.get("content")?.as_str().unwrap_or("").to_string(), tool_name: data.get("toolName").and_then(|v| v.as_str()).map(|s| s.to_string()), tool_input: data.get("toolInput").cloned(), is_error: data.get("isError").and_then(|v| v.as_bool()), cost_usd: data.get("costUsd").and_then(|v| v.as_f64()), duration_ms: data.get("durationMs").and_then(|v| v.as_u64()), created_at: event.created_at, }) } } /// Response for task output history endpoint #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct TaskOutputResponse { pub entries: Vec, pub total: usize, pub task_id: Uuid, } // ============================================================================= // Mesh Chat History Types // ============================================================================= /// Mesh chat conversation for persisting history #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct MeshChatConversation { pub id: Uuid, pub owner_id: Uuid, pub name: Option, pub is_active: bool, pub created_at: DateTime, pub updated_at: DateTime, } /// Individual message in a mesh chat conversation #[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct MeshChatMessageRecord { pub id: Uuid, pub conversation_id: Uuid, pub role: String, pub content: String, pub context_type: String, pub context_task_id: Option, /// Tool calls made during this message (JSON, nullable) pub tool_calls: Option, /// Pending questions requiring user response (JSON, nullable) pub pending_questions: Option, pub created_at: DateTime, } /// Response for chat history endpoint #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct MeshChatHistoryResponse { pub conversation_id: Uuid, pub messages: Vec, } // ============================================================================= // Contract Chat History Types // ============================================================================= /// Conversation thread for contract chat (scoped to a specific contract) #[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct ContractChatConversation { pub id: Uuid, pub contract_id: Uuid, pub owner_id: Uuid, pub name: Option, pub is_active: bool, pub created_at: DateTime, pub updated_at: DateTime, } /// Individual message in a contract chat conversation #[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct ContractChatMessageRecord { pub id: Uuid, pub conversation_id: Uuid, pub role: String, pub content: String, /// Tool calls made during this message (JSON, nullable) pub tool_calls: Option, /// Pending questions requiring user response (JSON, nullable) pub pending_questions: Option, pub created_at: DateTime, } /// Response for contract chat history endpoint #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct ContractChatHistoryResponse { pub contract_id: Uuid, pub conversation_id: Uuid, pub messages: Vec, } // ============================================================================= // Merge API Types // ============================================================================= /// Information about a task branch #[derive(Debug, Clone, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct BranchInfo { /// Full branch name pub name: String, /// Task ID extracted from branch name (if parseable) pub task_id: Option, /// Whether this branch has been merged pub is_merged: bool, /// Short SHA of the last commit pub last_commit: String, /// Subject line of the last commit pub last_commit_message: String, } /// Response for branch list endpoint #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct BranchListResponse { pub branches: Vec, } /// Request to start a merge #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct MergeStartRequest { /// Branch name to merge pub source_branch: String, } /// Current merge state #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct MergeStatusResponse { /// Whether a merge is in progress pub in_progress: bool, /// Branch being merged (if in progress) pub source_branch: Option, /// Files with unresolved conflicts pub conflicted_files: Vec, } /// Request to resolve a conflict #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct MergeResolveRequest { /// File path to resolve pub file: String, /// Resolution strategy: "ours" or "theirs" pub strategy: String, } /// Request to commit a merge #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct MergeCommitRequest { /// Commit message pub message: String, } /// Request to skip a subtask branch #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct MergeSkipRequest { /// Subtask ID to skip pub subtask_id: Uuid, /// Reason for skipping pub reason: String, } /// Result of a merge operation #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct MergeResultResponse { /// Whether the operation succeeded pub success: bool, /// Human-readable message pub message: String, /// Commit SHA (if a commit was created) pub commit_sha: Option, /// Conflicted files (if conflicts occurred) pub conflicts: Option>, } /// Response to check if all branches are merged #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct MergeCompleteCheckResponse { /// Whether the orchestrator can mark itself as complete pub can_complete: bool, /// Branches not yet merged or skipped pub unmerged_branches: Vec, /// Count of merged branches pub merged_count: u32, /// Count of skipped branches pub skipped_count: u32, } // ============================================================================= // Contract Types // ============================================================================= /// Contract type determines the workflow and required documents #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "lowercase")] pub enum ContractType { /// Simple Plan -> Execute workflow (default) /// - Plan phase: requires a "Plan" document /// - Execute phase: no documents, fulfills the plan Simple, /// Specification-based development with TDD /// - Research: gather requirements and context /// - Specify: write specifications and test cases /// - Plan: create implementation plan /// - Execute: implement according to specs /// - Review: verify against specifications Specification, } impl Default for ContractType { fn default() -> Self { ContractType::Simple } } impl std::fmt::Display for ContractType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ContractType::Simple => write!(f, "simple"), ContractType::Specification => write!(f, "specification"), } } } impl std::str::FromStr for ContractType { type Err = String; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "simple" => Ok(ContractType::Simple), "specification" => Ok(ContractType::Specification), _ => Err(format!("Unknown contract type: {}", s)), } } } /// Contract phase for workflow progression #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "lowercase")] pub enum ContractPhase { Research, Specify, Plan, Execute, Review, } impl std::fmt::Display for ContractPhase { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ContractPhase::Research => write!(f, "research"), ContractPhase::Specify => write!(f, "specify"), ContractPhase::Plan => write!(f, "plan"), ContractPhase::Execute => write!(f, "execute"), ContractPhase::Review => write!(f, "review"), } } } impl std::str::FromStr for ContractPhase { type Err = String; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "research" => Ok(ContractPhase::Research), "specify" => Ok(ContractPhase::Specify), "plan" => Ok(ContractPhase::Plan), "execute" => Ok(ContractPhase::Execute), "review" => Ok(ContractPhase::Review), _ => Err(format!("Unknown contract phase: {}", s)), } } } /// Contract status #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "lowercase")] pub enum ContractStatus { Active, Completed, Archived, } impl std::fmt::Display for ContractStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ContractStatus::Active => write!(f, "active"), ContractStatus::Completed => write!(f, "completed"), ContractStatus::Archived => write!(f, "archived"), } } } impl std::str::FromStr for ContractStatus { type Err = String; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "active" => Ok(ContractStatus::Active), "completed" => Ok(ContractStatus::Completed), "archived" => Ok(ContractStatus::Archived), _ => Err(format!("Unknown contract status: {}", s)), } } } /// Repository source type #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "lowercase")] pub enum RepositorySourceType { /// Existing remote repo (GitHub, GitLab, etc) Remote, /// Existing local repo Local, /// New repo created/managed by Makima daemon Managed, } impl std::fmt::Display for RepositorySourceType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RepositorySourceType::Remote => write!(f, "remote"), RepositorySourceType::Local => write!(f, "local"), RepositorySourceType::Managed => write!(f, "managed"), } } } impl std::str::FromStr for RepositorySourceType { type Err = String; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "remote" => Ok(RepositorySourceType::Remote), "local" => Ok(RepositorySourceType::Local), "managed" => Ok(RepositorySourceType::Managed), _ => Err(format!("Unknown repository source type: {}", s)), } } } /// Repository status (for managed repos) #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "lowercase")] pub enum RepositoryStatus { /// Repo is usable Ready, /// Waiting for daemon to create Pending, /// Daemon is creating the repo Creating, /// Creation failed Failed, } impl std::fmt::Display for RepositoryStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RepositoryStatus::Ready => write!(f, "ready"), RepositoryStatus::Pending => write!(f, "pending"), RepositoryStatus::Creating => write!(f, "creating"), RepositoryStatus::Failed => write!(f, "failed"), } } } impl std::str::FromStr for RepositoryStatus { type Err = String; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "ready" => Ok(RepositoryStatus::Ready), "pending" => Ok(RepositoryStatus::Pending), "creating" => Ok(RepositoryStatus::Creating), "failed" => Ok(RepositoryStatus::Failed), _ => Err(format!("Unknown repository status: {}", s)), } } } /// Contract record from the database #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct Contract { pub id: Uuid, pub owner_id: Uuid, pub name: String, pub description: Option, /// Contract type: "simple" or "specification" pub contract_type: String, pub phase: String, pub status: String, /// The long-running supervisor task that orchestrates this contract #[serde(skip_serializing_if = "Option::is_none")] pub supervisor_task_id: Option, /// Whether tasks for this contract should run in autonomous loop mode. /// When enabled, tasks will automatically restart with --continue if they exit /// without a COMPLETION_GATE indicating ready: true. #[serde(default)] pub autonomous_loop: bool, pub version: i32, pub created_at: DateTime, pub updated_at: DateTime, } impl Contract { /// Parse contract_type string to ContractType enum pub fn contract_type_enum(&self) -> Result { self.contract_type.parse() } /// Parse phase string to ContractPhase enum pub fn phase_enum(&self) -> Result { self.phase.parse() } /// Parse status string to ContractStatus enum pub fn status_enum(&self) -> Result { self.status.parse() } /// Get valid phases for this contract type pub fn valid_phases(&self) -> Vec { match self.contract_type.as_str() { "simple" => vec![ContractPhase::Plan, ContractPhase::Execute], "specification" => vec![ ContractPhase::Research, ContractPhase::Specify, ContractPhase::Plan, ContractPhase::Execute, ContractPhase::Review, ], _ => vec![ContractPhase::Plan, ContractPhase::Execute], // Default to simple } } } /// Contract repository record from the database #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct ContractRepository { pub id: Uuid, pub contract_id: Uuid, pub name: String, pub repository_url: Option, pub local_path: Option, pub source_type: String, pub status: String, pub is_primary: bool, pub created_at: DateTime, pub updated_at: DateTime, } impl ContractRepository { /// Parse source_type string to RepositorySourceType enum pub fn source_type_enum(&self) -> Result { self.source_type.parse() } /// Parse status string to RepositoryStatus enum pub fn status_enum(&self) -> Result { self.status.parse() } } /// Summary of a contract for list views #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct ContractSummary { pub id: Uuid, pub name: String, pub description: Option, /// Contract type: "simple" or "specification" pub contract_type: String, pub phase: String, pub status: String, pub file_count: i64, pub task_count: i64, pub repository_count: i64, pub version: i32, pub created_at: DateTime, } /// Contract with all relations for detail view #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct ContractWithRelations { #[serde(flatten)] pub contract: Contract, pub repositories: Vec, pub files: Vec, pub tasks: Vec, } /// Response for contract list endpoint #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct ContractListResponse { pub contracts: Vec, pub total: i64, } /// Request payload for creating a new contract #[derive(Debug, Clone, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct CreateContractRequest { /// Name of the contract pub name: String, /// Optional description pub description: Option, /// Contract type: "simple" (default) or "specification" /// - simple: Plan -> Execute workflow /// - specification: Research -> Specify -> Plan -> Execute -> Review #[serde(default)] pub contract_type: Option, /// Initial phase to start in (defaults based on contract_type) /// - simple: defaults to "plan" /// - specification: defaults to "research" #[serde(default)] pub initial_phase: Option, /// Enable autonomous loop mode for tasks in this contract. /// When enabled, tasks automatically restart with --continue if they exit /// without a COMPLETION_GATE indicating ready: true. #[serde(default)] pub autonomous_loop: Option, } /// Request payload for updating a contract #[derive(Debug, Default, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct UpdateContractRequest { pub name: Option, pub description: Option, pub phase: Option, pub status: Option, /// Supervisor task ID for contract orchestration #[serde(skip_serializing_if = "Option::is_none")] pub supervisor_task_id: Option, /// Enable or disable autonomous loop mode for tasks in this contract. #[serde(default)] pub autonomous_loop: Option, /// Version for optimistic locking pub version: Option, } /// Request to add a remote repository to a contract #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct AddRemoteRepositoryRequest { pub name: String, pub repository_url: String, #[serde(default)] pub is_primary: bool, } /// Request to add a local repository to a contract #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct AddLocalRepositoryRequest { pub name: String, pub local_path: String, #[serde(default)] pub is_primary: bool, } /// Request to create a managed repository (daemon will create it) #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct CreateManagedRepositoryRequest { pub name: String, #[serde(default)] pub is_primary: bool, } /// Request to change contract phase #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct ChangePhaseRequest { pub phase: String, } /// Contract event record from the database #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct ContractEvent { pub id: Uuid, pub contract_id: Uuid, pub event_type: String, pub previous_phase: Option, pub new_phase: Option, #[sqlx(json)] pub event_data: Option, pub created_at: DateTime, } /// Response for contract events list endpoint #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct ContractEventListResponse { pub events: Vec, pub total: i64, } // ============================================================================ // Task Checkpoints (for git checkpoint tracking) // ============================================================================ /// Task checkpoint record - represents a git commit checkpoint #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct TaskCheckpoint { pub id: Uuid, pub task_id: Uuid, /// Sequential checkpoint number within this task pub checkpoint_number: i32, /// Git commit SHA pub commit_sha: String, /// Git branch name pub branch_name: String, /// Commit message pub message: String, /// Files changed in this commit: [{path, action: 'A'|'M'|'D'}] #[sqlx(json)] pub files_changed: Option, /// Lines added in this commit pub lines_added: Option, /// Lines removed in this commit pub lines_removed: Option, pub created_at: DateTime, } /// Request to create a checkpoint #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct CreateCheckpointRequest { /// Commit message pub message: String, } /// Response for checkpoint list endpoint #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct CheckpointListResponse { pub checkpoints: Vec, pub total: i64, } // ============================================================================ // Supervisor State (for supervisor resumability) // ============================================================================ /// Supervisor state for contract supervisor tasks /// Enables resumption after interruption #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct SupervisorState { pub id: Uuid, pub contract_id: Uuid, pub task_id: Uuid, /// Full Claude conversation history for resumption #[sqlx(json)] pub conversation_history: serde_json::Value, /// Last checkpoint this supervisor created pub last_checkpoint_id: Option, /// Tasks the supervisor is waiting on #[sqlx(try_from = "Vec")] pub pending_task_ids: Vec, /// Current contract phase when supervisor was last active pub phase: String, /// When supervisor was last active pub last_activity: DateTime, pub created_at: DateTime, pub updated_at: DateTime, } /// Request to update supervisor state #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct UpdateSupervisorStateRequest { /// Updated conversation history pub conversation_history: Option, /// Tasks the supervisor is waiting on pub pending_task_ids: Option>, /// Current contract phase pub phase: Option, } // ============================================================================ // Daemon Task Assignments (for multi-daemon support) // ============================================================================ /// Daemon task assignment record #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct DaemonTaskAssignment { pub id: Uuid, pub daemon_id: Uuid, pub task_id: Uuid, pub assigned_at: DateTime, /// Status: 'active', 'migrating', 'completed' pub status: String, } /// Extended daemon info for selection #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct DaemonWithCapacity { pub id: Uuid, pub owner_id: Uuid, pub connection_id: String, pub hostname: Option, pub machine_id: Option, pub max_concurrent_tasks: i32, pub current_task_count: i32, pub capacity_score: Option, pub task_queue_length: Option, pub supports_migration: Option, pub status: String, pub last_heartbeat_at: DateTime, pub connected_at: DateTime, } // ============================================================================ // Repository History (for storing and suggesting previously used repositories) // ============================================================================ /// Repository history entry - tracks previously used repositories for suggestions #[derive(Debug, Clone, FromRow, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct RepositoryHistoryEntry { pub id: Uuid, pub owner_id: Uuid, pub name: String, pub repository_url: Option, pub local_path: Option, pub source_type: String, pub use_count: i32, pub last_used_at: DateTime, pub created_at: DateTime, } /// Response for repository history list endpoint #[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct RepositoryHistoryListResponse { pub entries: Vec, pub total: i64, } /// Request for getting repository suggestions #[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct RepositorySuggestionsQuery { /// Filter by source type: 'remote' or 'local' pub source_type: Option, /// Optional search query to filter by name or URL/path pub query: Option, /// Limit results (default: 10) pub limit: Option, }