code_mesh_core/tool/
todo.rs

1//! Todo management tool for task tracking and dependency management
2
3use super::{Tool, ToolContext, ToolError, ToolResult};
4use async_trait::async_trait;
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use serde_json::{json, Value};
8use std::collections::{HashMap, HashSet};
9use std::sync::Arc;
10use tokio::sync::RwLock;
11use uuid::Uuid;
12
13/// Task status enumeration
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub enum TaskStatus {
17    Pending,
18    InProgress,
19    Completed,
20    Cancelled,
21    Blocked,
22}
23
24/// Task priority enumeration
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "lowercase")]
27pub enum TaskPriority {
28    Low,
29    Medium,
30    High,
31    Critical,
32}
33
34/// Task dependency type
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "snake_case")]
37pub enum DependencyType {
38    /// Task must complete before this task can start
39    BlocksStart,
40    /// Task must complete before this task can complete
41    BlocksCompletion,
42    /// Tasks should be worked on together
43    Related,
44}
45
46/// Task dependency
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct TaskDependency {
49    pub task_id: String,
50    pub dependency_type: DependencyType,
51    pub description: Option<String>,
52}
53
54/// Task metadata for analytics and tracking
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct TaskMetadata {
57    pub estimated_duration: Option<chrono::Duration>,
58    pub actual_duration: Option<chrono::Duration>,
59    pub tags: Vec<String>,
60    pub assignee: Option<String>,
61    pub project: Option<String>,
62    pub milestone: Option<String>,
63}
64
65/// Individual task item
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct Task {
68    pub id: String,
69    pub content: String,
70    pub status: TaskStatus,
71    pub priority: TaskPriority,
72    pub created_at: DateTime<Utc>,
73    pub updated_at: DateTime<Utc>,
74    pub completed_at: Option<DateTime<Utc>>,
75    pub due_date: Option<DateTime<Utc>>,
76    pub dependencies: Vec<TaskDependency>,
77    pub metadata: TaskMetadata,
78    pub progress: f32, // 0.0 to 1.0
79    pub notes: Vec<String>,
80}
81
82impl Task {
83    pub fn new(id: String, content: String, priority: TaskPriority) -> Self {
84        let now = Utc::now();
85        Self {
86            id,
87            content,
88            status: TaskStatus::Pending,
89            priority,
90            created_at: now,
91            updated_at: now,
92            completed_at: None,
93            due_date: None,
94            dependencies: Vec::new(),
95            metadata: TaskMetadata {
96                estimated_duration: None,
97                actual_duration: None,
98                tags: Vec::new(),
99                assignee: None,
100                project: None,
101                milestone: None,
102            },
103            progress: 0.0,
104            notes: Vec::new(),
105        }
106    }
107
108    pub fn update_status(&mut self, status: TaskStatus) {
109        self.status = status;
110        self.updated_at = Utc::now();
111        
112        if self.status == TaskStatus::Completed {
113            self.completed_at = Some(Utc::now());
114            self.progress = 1.0;
115            
116            // Calculate actual duration if we have a created_at time
117            self.metadata.actual_duration = Some(
118                self.updated_at.signed_duration_since(self.created_at)
119            );
120        }
121    }
122
123    pub fn add_note(&mut self, note: String) {
124        self.notes.push(note);
125        self.updated_at = Utc::now();
126    }
127
128    pub fn add_dependency(&mut self, dependency: TaskDependency) {
129        self.dependencies.push(dependency);
130        self.updated_at = Utc::now();
131    }
132
133    pub fn is_blocked_by(&self, other_task: &Task) -> bool {
134        self.dependencies.iter().any(|dep| {
135            dep.task_id == other_task.id && 
136            matches!(dep.dependency_type, DependencyType::BlocksStart) &&
137            other_task.status != TaskStatus::Completed
138        })
139    }
140
141    pub fn can_start(&self, all_tasks: &HashMap<String, Task>) -> bool {
142        // Check if all blocking dependencies are completed
143        for dep in &self.dependencies {
144            if matches!(dep.dependency_type, DependencyType::BlocksStart) {
145                if let Some(blocking_task) = all_tasks.get(&dep.task_id) {
146                    if blocking_task.status != TaskStatus::Completed {
147                        return false;
148                    }
149                }
150            }
151        }
152        true
153    }
154}
155
156/// Task list for a session
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct TaskList {
159    pub session_id: String,
160    pub tasks: HashMap<String, Task>,
161    pub created_at: DateTime<Utc>,
162    pub updated_at: DateTime<Utc>,
163}
164
165impl TaskList {
166    pub fn new(session_id: String) -> Self {
167        let now = Utc::now();
168        Self {
169            session_id,
170            tasks: HashMap::new(),
171            created_at: now,
172            updated_at: now,
173        }
174    }
175
176    pub fn add_task(&mut self, task: Task) {
177        self.tasks.insert(task.id.clone(), task);
178        self.updated_at = Utc::now();
179    }
180
181    pub fn get_task(&self, id: &str) -> Option<&Task> {
182        self.tasks.get(id)
183    }
184
185    pub fn get_task_mut(&mut self, id: &str) -> Option<&mut Task> {
186        if self.tasks.contains_key(id) {
187            self.updated_at = Utc::now();
188        }
189        self.tasks.get_mut(id)
190    }
191
192    pub fn remove_task(&mut self, id: &str) -> Option<Task> {
193        let task = self.tasks.remove(id);
194        if task.is_some() {
195            self.updated_at = Utc::now();
196        }
197        task
198    }
199
200    pub fn get_tasks_by_status(&self, status: &TaskStatus) -> Vec<&Task> {
201        self.tasks.values().filter(|t| &t.status == status).collect()
202    }
203
204    pub fn get_tasks_by_priority(&self, priority: &TaskPriority) -> Vec<&Task> {
205        self.tasks.values().filter(|t| &t.priority == priority).collect()
206    }
207
208    pub fn get_available_tasks(&self) -> Vec<&Task> {
209        self.tasks.values()
210            .filter(|t| t.status == TaskStatus::Pending && t.can_start(&self.tasks))
211            .collect()
212    }
213
214    pub fn get_blocked_tasks(&self) -> Vec<&Task> {
215        self.tasks.values()
216            .filter(|t| t.status == TaskStatus::Pending && !t.can_start(&self.tasks))
217            .collect()
218    }
219
220    pub fn get_completion_stats(&self) -> TaskStats {
221        let total = self.tasks.len();
222        let completed = self.get_tasks_by_status(&TaskStatus::Completed).len();
223        let in_progress = self.get_tasks_by_status(&TaskStatus::InProgress).len();
224        let pending = self.get_tasks_by_status(&TaskStatus::Pending).len();
225        let blocked = self.get_blocked_tasks().len();
226        let cancelled = self.get_tasks_by_status(&TaskStatus::Cancelled).len();
227
228        TaskStats {
229            total,
230            completed,
231            in_progress,
232            pending,
233            blocked,
234            cancelled,
235            completion_rate: if total > 0 { completed as f32 / total as f32 } else { 0.0 },
236        }
237    }
238}
239
240/// Task statistics
241#[derive(Debug, Serialize, Deserialize)]
242pub struct TaskStats {
243    pub total: usize,
244    pub completed: usize,
245    pub in_progress: usize,
246    pub pending: usize,
247    pub blocked: usize,
248    pub cancelled: usize,
249    pub completion_rate: f32,
250}
251
252/// In-memory storage for task lists
253#[derive(Debug)]
254pub struct TaskStorage {
255    task_lists: Arc<RwLock<HashMap<String, TaskList>>>,
256}
257
258impl TaskStorage {
259    pub fn new() -> Self {
260        Self {
261            task_lists: Arc::new(RwLock::new(HashMap::new())),
262        }
263    }
264
265    pub async fn get_or_create_list(&self, session_id: &str) -> TaskList {
266        let mut lists = self.task_lists.write().await;
267        lists.entry(session_id.to_string())
268            .or_insert_with(|| TaskList::new(session_id.to_string()))
269            .clone()
270    }
271
272    pub async fn save_list(&self, list: TaskList) {
273        let mut lists = self.task_lists.write().await;
274        lists.insert(list.session_id.clone(), list);
275    }
276
277    pub async fn get_all_sessions(&self) -> Vec<String> {
278        let lists = self.task_lists.read().await;
279        lists.keys().cloned().collect()
280    }
281}
282
283/// Todo management tool
284pub struct TodoTool {
285    storage: TaskStorage,
286}
287
288impl TodoTool {
289    pub fn new() -> Self {
290        Self {
291            storage: TaskStorage::new(),
292        }
293    }
294}
295
296#[derive(Debug, Deserialize)]
297#[serde(tag = "action")]
298#[serde(rename_all = "snake_case")]
299enum TodoAction {
300    List,
301    Add { content: String, priority: Option<TaskPriority> },
302    Update { 
303        id: String, 
304        status: Option<TaskStatus>, 
305        priority: Option<TaskPriority>,
306        content: Option<String>,
307        progress: Option<f32>,
308    },
309    Remove { id: String },
310    AddDependency { 
311        task_id: String, 
312        depends_on: String, 
313        dependency_type: DependencyType,
314        description: Option<String>,
315    },
316    AddNote { id: String, note: String },
317    Stats,
318    Export { format: Option<String> },
319}
320
321#[async_trait]
322impl Tool for TodoTool {
323    fn id(&self) -> &str {
324        "todo"
325    }
326    
327    fn description(&self) -> &str {
328        "Comprehensive task management tool with dependency tracking, progress reporting, and analytics"
329    }
330    
331    fn parameters_schema(&self) -> Value {
332        json!({
333            "type": "object",
334            "properties": {
335                "action": {
336                    "type": "string",
337                    "enum": ["list", "add", "update", "remove", "add_dependency", "add_note", "stats", "export"],
338                    "description": "The action to perform"
339                },
340                "content": {
341                    "type": "string",
342                    "description": "Task content (for add action)"
343                },
344                "priority": {
345                    "type": "string",
346                    "enum": ["low", "medium", "high", "critical"],
347                    "description": "Task priority"
348                },
349                "id": {
350                    "type": "string",
351                    "description": "Task ID (for update, remove, add_note actions)"
352                },
353                "status": {
354                    "type": "string",
355                    "enum": ["pending", "in_progress", "completed", "cancelled", "blocked"],
356                    "description": "Task status (for update action)"
357                },
358                "progress": {
359                    "type": "number",
360                    "minimum": 0.0,
361                    "maximum": 1.0,
362                    "description": "Task progress from 0.0 to 1.0 (for update action)"
363                },
364                "depends_on": {
365                    "type": "string",
366                    "description": "ID of task this depends on (for add_dependency action)"
367                },
368                "dependency_type": {
369                    "type": "string",
370                    "enum": ["blocks_start", "blocks_completion", "related"],
371                    "description": "Type of dependency (for add_dependency action)"
372                },
373                "note": {
374                    "type": "string",
375                    "description": "Note to add to task (for add_note action)"
376                },
377                "format": {
378                    "type": "string",
379                    "enum": ["json", "markdown", "csv"],
380                    "description": "Export format (for export action)"
381                },
382                "description": {
383                    "type": "string",
384                    "description": "Description for dependency (for add_dependency action)"
385                }
386            },
387            "required": ["action"]
388        })
389    }
390    
391    async fn execute(&self, args: Value, ctx: ToolContext) -> Result<ToolResult, ToolError> {
392        let action: TodoAction = serde_json::from_value(args)
393            .map_err(|e| ToolError::InvalidParameters(e.to_string()))?;
394
395        let mut task_list = self.storage.get_or_create_list(&ctx.session_id).await;
396
397        match action {
398            TodoAction::List => {
399                let output = format_task_list(&task_list);
400                let stats = task_list.get_completion_stats();
401                
402                Ok(ToolResult {
403                    title: format!("Task List ({} tasks)", task_list.tasks.len()),
404                    output,
405                    metadata: json!({
406                        "stats": stats,
407                        "session_id": ctx.session_id,
408                        "task_count": task_list.tasks.len()
409                    }),
410                })
411            },
412
413            TodoAction::Add { content, priority } => {
414                let task_id = Uuid::new_v4().to_string();
415                let priority_value = priority.unwrap_or(TaskPriority::Medium);
416                let task = Task::new(task_id.clone(), content.clone(), priority_value.clone());
417                
418                task_list.add_task(task);
419                self.storage.save_list(task_list.clone()).await;
420
421                Ok(ToolResult {
422                    title: "Task Added".to_string(),
423                    output: format!("Added task: {} (ID: {})", content, task_id),
424                    metadata: json!({
425                        "task_id": task_id,
426                        "content": content,
427                        "priority": priority_value
428                    }),
429                })
430            },
431
432            TodoAction::Update { id, status, priority, content, progress } => {
433                if let Some(task) = task_list.get_task_mut(&id) {
434                    if let Some(new_status) = status {
435                        task.update_status(new_status);
436                    }
437                    if let Some(new_priority) = priority {
438                        task.priority = new_priority;
439                        task.updated_at = Utc::now();
440                    }
441                    if let Some(new_content) = content {
442                        task.content = new_content;
443                        task.updated_at = Utc::now();
444                    }
445                    if let Some(new_progress) = progress {
446                        task.progress = new_progress.clamp(0.0, 1.0);
447                        task.updated_at = Utc::now();
448                    }
449                    
450                    let task_content = task.content.clone();
451                    let task_status = task.status.clone();
452                    let task_priority = task.priority.clone();
453                    let task_progress = task.progress;
454
455                    self.storage.save_list(task_list).await;
456
457                    Ok(ToolResult {
458                        title: "Task Updated".to_string(),
459                        output: format!("Updated task: {}", task_content),
460                        metadata: json!({
461                            "task_id": id,
462                            "status": task_status,
463                            "priority": task_priority,
464                            "progress": task_progress
465                        }),
466                    })
467                } else {
468                    Err(ToolError::InvalidParameters(format!("Task not found: {}", id)))
469                }
470            },
471
472            TodoAction::Remove { id } => {
473                if let Some(task) = task_list.remove_task(&id) {
474                    self.storage.save_list(task_list).await;
475
476                    Ok(ToolResult {
477                        title: "Task Removed".to_string(),
478                        output: format!("Removed task: {}", task.content),
479                        metadata: json!({
480                            "task_id": id,
481                            "content": task.content
482                        }),
483                    })
484                } else {
485                    Err(ToolError::InvalidParameters(format!("Task not found: {}", id)))
486                }
487            },
488
489            TodoAction::AddDependency { task_id, depends_on, dependency_type, description } => {
490                // Verify both tasks exist
491                if !task_list.tasks.contains_key(&task_id) {
492                    return Err(ToolError::InvalidParameters(format!("Task not found: {}", task_id)));
493                }
494                if !task_list.tasks.contains_key(&depends_on) {
495                    return Err(ToolError::InvalidParameters(format!("Dependency task not found: {}", depends_on)));
496                }
497
498                // Check for circular dependencies
499                if would_create_cycle(&task_list, &task_id, &depends_on) {
500                    return Err(ToolError::InvalidParameters("Adding this dependency would create a circular dependency".to_string()));
501                }
502
503                if let Some(task) = task_list.get_task_mut(&task_id) {
504                    let dependency = TaskDependency {
505                        task_id: depends_on.clone(),
506                        dependency_type: dependency_type.clone(),
507                        description,
508                    };
509                    task.add_dependency(dependency);
510                    self.storage.save_list(task_list).await;
511
512                    Ok(ToolResult {
513                        title: "Dependency Added".to_string(),
514                        output: format!("Added dependency: {} depends on {}", task_id, depends_on),
515                        metadata: json!({
516                            "task_id": task_id,
517                            "depends_on": depends_on,
518                            "dependency_type": dependency_type
519                        }),
520                    })
521                } else {
522                    Err(ToolError::InvalidParameters(format!("Task not found: {}", task_id)))
523                }
524            },
525
526            TodoAction::AddNote { id, note } => {
527                if let Some(task) = task_list.get_task_mut(&id) {
528                    task.add_note(note.clone());
529                    let task_content = task.content.clone();
530                    self.storage.save_list(task_list).await;
531
532                    Ok(ToolResult {
533                        title: "Note Added".to_string(),
534                        output: format!("Added note to task {}: {}", task_content, note),
535                        metadata: json!({
536                            "task_id": id,
537                            "note": note
538                        }),
539                    })
540                } else {
541                    Err(ToolError::InvalidParameters(format!("Task not found: {}", id)))
542                }
543            },
544
545            TodoAction::Stats => {
546                let stats = task_list.get_completion_stats();
547                let available_tasks = task_list.get_available_tasks();
548                let blocked_tasks = task_list.get_blocked_tasks();
549
550                let output = format!(
551                    "Task Statistics:\n\
552                     Total tasks: {}\n\
553                     Completed: {}\n\
554                     In progress: {}\n\
555                     Pending: {}\n\
556                     Blocked: {}\n\
557                     Cancelled: {}\n\
558                     Completion rate: {:.1}%\n\n\
559                     Available tasks (can start now): {}\n\
560                     Blocked tasks (waiting on dependencies): {}",
561                    stats.total,
562                    stats.completed,
563                    stats.in_progress,
564                    stats.pending,
565                    stats.blocked,
566                    stats.cancelled,
567                    stats.completion_rate * 100.0,
568                    available_tasks.len(),
569                    blocked_tasks.len()
570                );
571
572                Ok(ToolResult {
573                    title: "Task Statistics".to_string(),
574                    output,
575                    metadata: json!({
576                        "stats": stats,
577                        "available_tasks": available_tasks.len(),
578                        "blocked_tasks": blocked_tasks.len()
579                    }),
580                })
581            },
582
583            TodoAction::Export { format } => {
584                let format = format.as_deref().unwrap_or("json");
585                let output = match format {
586                    "json" => serde_json::to_string_pretty(&task_list.tasks)
587                        .map_err(|e| ToolError::ExecutionFailed(format!("JSON serialization failed: {}", e)))?,
588                    "markdown" => export_to_markdown(&task_list),
589                    "csv" => export_to_csv(&task_list)?,
590                    _ => return Err(ToolError::InvalidParameters("Unsupported export format".to_string())),
591                };
592
593                Ok(ToolResult {
594                    title: format!("Task Export ({})", format),
595                    output,
596                    metadata: json!({
597                        "format": format,
598                        "task_count": task_list.tasks.len(),
599                        "exported_at": Utc::now()
600                    }),
601                })
602            },
603        }
604    }
605}
606
607/// Format task list for display
608fn format_task_list(task_list: &TaskList) -> String {
609    if task_list.tasks.is_empty() {
610        return "No tasks found.".to_string();
611    }
612
613    let mut output = String::new();
614    let stats = task_list.get_completion_stats();
615    
616    output.push_str(&format!(
617        "Task List ({}% complete)\n\n",
618        (stats.completion_rate * 100.0) as u32
619    ));
620
621    // Group by status
622    let statuses = [
623        TaskStatus::InProgress,
624        TaskStatus::Pending,
625        TaskStatus::Blocked,
626        TaskStatus::Completed,
627        TaskStatus::Cancelled,
628    ];
629
630    for status in &statuses {
631        let tasks = task_list.get_tasks_by_status(status);
632        if !tasks.is_empty() {
633            output.push_str(&format!("## {:?} ({})\n", status, tasks.len()));
634            
635            for task in tasks {
636                let progress_bar = create_progress_bar(task.progress);
637                let dependencies = if task.dependencies.is_empty() {
638                    String::new()
639                } else {
640                    format!(" (depends on: {})", 
641                        task.dependencies.iter()
642                            .map(|d| &d.task_id[..8])
643                            .collect::<Vec<_>>()
644                            .join(", ")
645                    )
646                };
647
648                output.push_str(&format!(
649                    "- [{}] {} {} {}{}\n",
650                    if task.status == TaskStatus::Completed { "x" } else { " " },
651                    task.content,
652                    format!("({:?})", task.priority),
653                    progress_bar,
654                    dependencies
655                ));
656
657                if !task.notes.is_empty() {
658                    for note in &task.notes {
659                        output.push_str(&format!("  📝 {}\n", note));
660                    }
661                }
662            }
663            output.push('\n');
664        }
665    }
666
667    output
668}
669
670/// Create a simple progress bar
671fn create_progress_bar(progress: f32) -> String {
672    let width = 10;
673    let filled = (progress * width as f32) as usize;
674    let empty = width - filled;
675    format!("[{}{}] {:.0}%", "█".repeat(filled), "░".repeat(empty), progress * 100.0)
676}
677
678/// Export task list to markdown format
679fn export_to_markdown(task_list: &TaskList) -> String {
680    let mut output = format!("# Task List - {}\n\n", task_list.session_id);
681    
682    for task in task_list.tasks.values() {
683        output.push_str(&format!(
684            "## {} ({})\n\n",
685            task.content,
686            task.id
687        ));
688        
689        output.push_str(&format!("- **Status**: {:?}\n", task.status));
690        output.push_str(&format!("- **Priority**: {:?}\n", task.priority));
691        output.push_str(&format!("- **Progress**: {:.1}%\n", task.progress * 100.0));
692        output.push_str(&format!("- **Created**: {}\n", task.created_at.format("%Y-%m-%d %H:%M:%S")));
693        
694        if let Some(completed_at) = task.completed_at {
695            output.push_str(&format!("- **Completed**: {}\n", completed_at.format("%Y-%m-%d %H:%M:%S")));
696        }
697        
698        if !task.dependencies.is_empty() {
699            output.push_str("- **Dependencies**:\n");
700            for dep in &task.dependencies {
701                output.push_str(&format!("  - {} ({:?})\n", dep.task_id, dep.dependency_type));
702            }
703        }
704        
705        if !task.notes.is_empty() {
706            output.push_str("- **Notes**:\n");
707            for note in &task.notes {
708                output.push_str(&format!("  - {}\n", note));
709            }
710        }
711        
712        output.push('\n');
713    }
714    
715    output
716}
717
718/// Export task list to CSV format
719fn export_to_csv(task_list: &TaskList) -> Result<String, ToolError> {
720    let mut output = "ID,Content,Status,Priority,Progress,Created,Updated,Completed,Dependencies,Notes\n".to_string();
721    
722    for task in task_list.tasks.values() {
723        let dependencies = task.dependencies.iter()
724            .map(|d| format!("{}:{:?}", d.task_id, d.dependency_type))
725            .collect::<Vec<_>>()
726            .join(";");
727        
728        let notes = task.notes.join(";");
729        
730        let completed = task.completed_at
731            .map(|t| t.format("%Y-%m-%d %H:%M:%S").to_string())
732            .unwrap_or_default();
733        
734        output.push_str(&format!(
735            "{},{},{:?},{:?},{:.2},{},{},{},{},{}\n",
736            task.id,
737            task.content,
738            task.status,
739            task.priority,
740            task.progress,
741            task.created_at.format("%Y-%m-%d %H:%M:%S"),
742            task.updated_at.format("%Y-%m-%d %H:%M:%S"),
743            completed,
744            dependencies,
745            notes
746        ));
747    }
748    
749    Ok(output)
750}
751
752/// Check if adding a dependency would create a circular dependency
753fn would_create_cycle(task_list: &TaskList, from_task: &str, to_task: &str) -> bool {
754    let mut visited = HashSet::new();
755    let mut stack = vec![to_task];
756    
757    while let Some(current) = stack.pop() {
758        if current == from_task {
759            return true; // Cycle detected
760        }
761        
762        if visited.contains(current) {
763            continue;
764        }
765        visited.insert(current);
766        
767        if let Some(task) = task_list.tasks.get(current) {
768            for dep in &task.dependencies {
769                if matches!(dep.dependency_type, DependencyType::BlocksStart | DependencyType::BlocksCompletion) {
770                    stack.push(&dep.task_id);
771                }
772            }
773        }
774    }
775    
776    false
777}
778
779impl Default for TodoTool {
780    fn default() -> Self {
781        Self::new()
782    }
783}
784
785#[cfg(test)]
786mod tests {
787    use super::*;
788
789    #[test]
790    fn test_task_creation() {
791        let task = Task::new("test-1".to_string(), "Test task".to_string(), TaskPriority::High);
792        assert_eq!(task.id, "test-1");
793        assert_eq!(task.content, "Test task");
794        assert_eq!(task.priority, TaskPriority::High);
795        assert_eq!(task.status, TaskStatus::Pending);
796        assert_eq!(task.progress, 0.0);
797    }
798
799    #[test]
800    fn test_task_status_update() {
801        let mut task = Task::new("test-1".to_string(), "Test task".to_string(), TaskPriority::Medium);
802        task.update_status(TaskStatus::Completed);
803        
804        assert_eq!(task.status, TaskStatus::Completed);
805        assert_eq!(task.progress, 1.0);
806        assert!(task.completed_at.is_some());
807        assert!(task.metadata.actual_duration.is_some());
808    }
809
810    #[test]
811    fn test_task_dependencies() {
812        let mut task1 = Task::new("task-1".to_string(), "First task".to_string(), TaskPriority::High);
813        let task2 = Task::new("task-2".to_string(), "Second task".to_string(), TaskPriority::Medium);
814        
815        let dependency = TaskDependency {
816            task_id: task2.id.clone(),
817            dependency_type: DependencyType::BlocksStart,
818            description: Some("Must complete first".to_string()),
819        };
820        
821        task1.add_dependency(dependency);
822        assert_eq!(task1.dependencies.len(), 1);
823        assert!(task1.is_blocked_by(&task2));
824    }
825
826    #[test]
827    fn test_cycle_detection() {
828        let mut task_list = TaskList::new("test-session".to_string());
829        
830        let task1 = Task::new("task-1".to_string(), "Task 1".to_string(), TaskPriority::Medium);
831        let mut task2 = Task::new("task-2".to_string(), "Task 2".to_string(), TaskPriority::Medium);
832        
833        // task2 depends on task1
834        task2.add_dependency(TaskDependency {
835            task_id: "task-1".to_string(),
836            dependency_type: DependencyType::BlocksStart,
837            description: None,
838        });
839        
840        task_list.add_task(task1);
841        task_list.add_task(task2);
842        
843        // Check if task1 depending on task2 would create a cycle
844        assert!(would_create_cycle(&task_list, "task-1", "task-2"));
845        assert!(!would_create_cycle(&task_list, "task-2", "task-1"));
846    }
847}