use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use uuid::Uuid;
pub type TaskId = Uuid;
pub type ClaudeSessionId = Uuid;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Task {
pub id: TaskId,
pub title: String,
pub description: String,
pub status: TaskStatus,
pub parent_id: Option<TaskId>,
pub children: Vec<TaskId>,
pub dependencies: Vec<TaskId>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub metadata: TaskMetadata,
pub execution_history: Vec<ExecutionRecord>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum TaskStatus {
Pending,
InProgress {
started_at: DateTime<Utc>,
estimated_completion: Option<DateTime<Utc>>,
},
Blocked {
reason: String,
blocked_at: DateTime<Utc>,
retry_after: Option<DateTime<Utc>>,
},
Completed {
completed_at: DateTime<Utc>,
result: TaskResult,
},
Failed {
failed_at: DateTime<Utc>,
error: TaskError,
retry_count: u32,
},
Skipped {
reason: String,
skipped_at: DateTime<Utc>,
},
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TaskMetadata {
pub priority: TaskPriority,
pub estimated_complexity: Option<ComplexityLevel>,
pub estimated_duration: Option<Duration>,
pub repository_refs: Vec<RepositoryRef>,
pub file_refs: Vec<FileRef>,
pub tags: Vec<String>,
pub context_requirements: ContextRequirements,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum TaskPriority {
Critical = 10,
High = 8,
Normal = 5,
Low = 3,
Background = 1,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ComplexityLevel {
Trivial, Simple, Moderate, Complex, Epic, }
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct RepositoryRef {
pub name: String,
pub url: String,
pub branch: Option<String>,
pub commit: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct FileRef {
pub path: PathBuf,
pub repository: String,
pub line_range: Option<(u32, u32)>,
pub importance: FileImportance,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum FileImportance {
Critical, High, Medium, Low, }
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ContextRequirements {
pub required_files: Vec<PathBuf>,
pub required_repositories: Vec<String>,
pub build_dependencies: Vec<String>,
pub environment_vars: HashMap<String, String>,
pub claude_context_keys: Vec<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TaskDependency {
pub task_id: TaskId,
pub dependency_type: DependencyType,
pub required_status: Vec<TaskStatus>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum DependencyType {
Prerequisite,
Preferred,
Concurrent,
Exclusive,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ExecutionRecord {
pub started_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
pub status: TaskStatus,
pub claude_session_id: Option<ClaudeSessionId>,
pub resources_used: ResourceUsage,
pub files_modified: Vec<PathBuf>,
pub errors: Vec<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ResourceUsage {
pub max_memory_mb: u64,
pub cpu_time_seconds: f64,
pub disk_io_mb: u64,
pub network_requests: u32,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum TaskResult {
Success {
output: serde_json::Value,
files_created: Vec<PathBuf>,
files_modified: Vec<PathBuf>,
build_artifacts: Vec<PathBuf>,
},
Partial {
completed_work: serde_json::Value,
remaining_work: Vec<TaskSpec>,
files_modified: Vec<PathBuf>,
},
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum TaskError {
ClaudeError {
message: String,
error_code: Option<String>,
retry_possible: bool,
},
BuildError {
exit_code: i32,
stdout: String,
stderr: String,
affected_files: Vec<PathBuf>,
},
FileSystemError {
message: String,
path: Option<PathBuf>,
operation: String,
},
ResourceError {
resource_type: String,
limit_exceeded: String,
current_usage: String,
},
DependencyError {
message: String,
missing_dependencies: Vec<String>,
conflict_dependencies: Vec<String>,
},
TimeoutError {
operation: String,
timeout_duration: Duration,
elapsed_time: Duration,
},
Other {
message: String,
source: Option<String>,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
pub struct TaskSpec {
pub title: String,
pub description: String,
pub metadata: TaskMetadata,
pub dependencies: Vec<TaskId>,
}
impl Task {
pub fn new(spec: TaskSpec, parent_id: Option<TaskId>) -> Self {
let now = Utc::now();
Self {
id: Uuid::new_v4(),
title: spec.title,
description: spec.description,
status: TaskStatus::Pending,
parent_id,
children: Vec::new(),
dependencies: spec.dependencies,
created_at: now,
updated_at: now,
metadata: spec.metadata,
execution_history: Vec::new(),
}
}
pub fn is_terminal(&self) -> bool {
matches!(
self.status,
TaskStatus::Completed { .. } | TaskStatus::Failed { .. } | TaskStatus::Skipped { .. }
)
}
pub fn is_runnable(&self) -> bool {
matches!(self.status, TaskStatus::Pending)
}
pub fn is_running(&self) -> bool {
matches!(self.status, TaskStatus::InProgress { .. })
}
pub fn is_blocked(&self) -> bool {
matches!(self.status, TaskStatus::Blocked { .. })
}
pub fn age(&self) -> Duration {
Utc::now().signed_duration_since(self.created_at)
}
pub fn runtime(&self) -> Option<Duration> {
if let TaskStatus::InProgress { started_at, .. } = self.status {
Some(Utc::now().signed_duration_since(started_at))
} else {
None
}
}
pub fn update_status(&mut self, status: TaskStatus) {
self.status = status;
self.updated_at = Utc::now();
}
pub fn add_execution_record(&mut self, record: ExecutionRecord) {
self.execution_history.push(record);
self.updated_at = Utc::now();
}
pub fn priority_value(&self) -> u8 {
self.metadata.priority.clone() as u8
}
}
impl ContextRequirements {
pub fn new() -> Self {
Self {
required_files: Vec::new(),
required_repositories: Vec::new(),
build_dependencies: Vec::new(),
environment_vars: HashMap::new(),
claude_context_keys: Vec::new(),
}
}
pub fn merge_with(&mut self, other: &ContextRequirements) {
self.required_files
.extend(other.required_files.iter().cloned());
self.required_repositories
.extend(other.required_repositories.iter().cloned());
self.build_dependencies
.extend(other.build_dependencies.iter().cloned());
self.environment_vars.extend(
other
.environment_vars
.iter()
.map(|(k, v)| (k.clone(), v.clone())),
);
self.claude_context_keys
.extend(other.claude_context_keys.iter().cloned());
self.required_files.sort();
self.required_files.dedup();
self.required_repositories.sort();
self.required_repositories.dedup();
self.build_dependencies.sort();
self.build_dependencies.dedup();
self.claude_context_keys.sort();
self.claude_context_keys.dedup();
}
pub fn is_empty(&self) -> bool {
self.required_files.is_empty()
&& self.required_repositories.is_empty()
&& self.build_dependencies.is_empty()
&& self.environment_vars.is_empty()
&& self.claude_context_keys.is_empty()
}
}
impl Default for ContextRequirements {
fn default() -> Self {
Self::new()
}
}
impl TaskPriority {
pub fn value(&self) -> u8 {
self.clone() as u8
}
}
impl ComplexityLevel {
pub fn estimated_duration(&self) -> Duration {
match self {
ComplexityLevel::Trivial => Duration::minutes(5),
ComplexityLevel::Simple => Duration::minutes(15),
ComplexityLevel::Moderate => Duration::minutes(60),
ComplexityLevel::Complex => Duration::hours(4),
ComplexityLevel::Epic => Duration::hours(8),
}
}
pub fn value(&self) -> u8 {
match self {
ComplexityLevel::Trivial => 0,
ComplexityLevel::Simple => 1,
ComplexityLevel::Moderate => 2,
ComplexityLevel::Complex => 3,
ComplexityLevel::Epic => 4,
}
}
}
impl std::fmt::Display for TaskError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TaskError::ClaudeError {
message,
error_code,
retry_possible,
} => {
if let Some(code) = error_code {
write!(
f,
"Claude API error [{}]: {} (retry: {})",
code, message, retry_possible
)
} else {
write!(
f,
"Claude API error: {} (retry: {})",
message, retry_possible
)
}
}
TaskError::BuildError {
exit_code, stderr, ..
} => {
write!(
f,
"Build failed with exit code {}: {}",
exit_code,
stderr.lines().next().unwrap_or("No error details")
)
}
TaskError::FileSystemError {
message,
path,
operation,
} => {
if let Some(path) = path {
write!(
f,
"File system error during {}: {} (path: {})",
operation,
message,
path.display()
)
} else {
write!(f, "File system error during {}: {}", operation, message)
}
}
TaskError::ResourceError {
resource_type,
limit_exceeded,
..
} => {
write!(
f,
"Resource exhausted: {} limit exceeded ({})",
resource_type, limit_exceeded
)
}
TaskError::DependencyError {
message,
missing_dependencies,
..
} => {
if missing_dependencies.is_empty() {
write!(f, "Dependency error: {}", message)
} else {
write!(
f,
"Dependency error: {} (missing: {})",
message,
missing_dependencies.join(", ")
)
}
}
TaskError::TimeoutError {
operation,
timeout_duration,
elapsed_time,
} => {
write!(
f,
"Timeout during {}: {}s exceeded (elapsed: {}s)",
operation,
timeout_duration.num_seconds(),
elapsed_time.num_seconds()
)
}
TaskError::Other { message, source } => {
if let Some(source) = source {
write!(f, "{} (source: {})", message, source)
} else {
write!(f, "{}", message)
}
}
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SetupCommand {
pub id: Uuid,
pub name: String,
pub command: String, pub args: Vec<String>, pub working_dir: Option<PathBuf>, pub timeout: Option<Duration>, pub required: bool, pub error_handler: Option<ErrorHandler>, }
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SetupResult {
pub command_id: Uuid,
pub success: bool,
pub exit_code: i32,
pub stdout: String,
pub stderr: String,
pub duration: Duration,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ErrorHandler {
pub name: String,
pub strategy: ErrorStrategy,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum ErrorStrategy {
Skip,
Retry { max_attempts: u32, delay: Duration },
Backup {
condition: OutputCondition,
backup_command: String,
backup_args: Vec<String>,
},
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct OutputCondition {
pub check_stdout: bool, pub check_stderr: bool, pub contains: Option<String>, pub not_contains: Option<String>, pub exit_code_range: Option<(i32, i32)>, }
impl Default for SetupCommand {
fn default() -> Self {
Self {
id: Uuid::new_v4(),
name: String::new(),
command: String::new(),
args: Vec::new(),
working_dir: None,
timeout: Some(Duration::seconds(30)), required: true,
error_handler: None,
}
}
}
impl Default for OutputCondition {
fn default() -> Self {
Self {
check_stdout: false,
check_stderr: true, contains: None,
not_contains: None,
exit_code_range: None,
}
}
}
impl SetupCommand {
pub fn new(name: &str, command: &str) -> Self {
Self {
name: name.to_string(),
command: command.to_string(),
..Default::default()
}
}
pub fn with_args(mut self, args: Vec<String>) -> Self {
self.args = args;
self
}
pub fn with_working_dir(mut self, dir: PathBuf) -> Self {
self.working_dir = Some(dir);
self
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn optional(mut self) -> Self {
self.required = false;
self
}
pub fn with_error_handler(mut self, handler: ErrorHandler) -> Self {
self.error_handler = Some(handler);
self
}
}
impl ErrorHandler {
pub fn skip(name: &str) -> Self {
Self {
name: name.to_string(),
strategy: ErrorStrategy::Skip,
}
}
pub fn retry(name: &str, max_attempts: u32, delay: Duration) -> Self {
Self {
name: name.to_string(),
strategy: ErrorStrategy::Retry {
max_attempts,
delay,
},
}
}
pub fn backup(
name: &str,
condition: OutputCondition,
backup_command: &str,
backup_args: Vec<String>,
) -> Self {
Self {
name: name.to_string(),
strategy: ErrorStrategy::Backup {
condition,
backup_command: backup_command.to_string(),
backup_args,
},
}
}
}
impl OutputCondition {
pub fn stderr_contains(text: &str) -> Self {
Self {
check_stdout: false,
check_stderr: true,
contains: Some(text.to_string()),
not_contains: None,
exit_code_range: None,
}
}
pub fn stdout_contains(text: &str) -> Self {
Self {
check_stdout: true,
check_stderr: false,
contains: Some(text.to_string()),
not_contains: None,
exit_code_range: None,
}
}
pub fn exit_code_range(min: i32, max: i32) -> Self {
Self {
exit_code_range: Some((min, max)),
..Default::default()
}
}
}
impl Default for TaskMetadata {
fn default() -> Self {
Self {
priority: TaskPriority::Normal,
estimated_complexity: None,
estimated_duration: None,
repository_refs: Vec::new(),
file_refs: Vec::new(),
tags: Vec::new(),
context_requirements: ContextRequirements::default(),
}
}
}