foundry_mcp/models/
task.rs

1//! Task and note models for project management and collaboration
2//!
3//! This module provides data structures for managing tasks and notes within project specifications.
4//! Tasks represent actionable work items with dependencies and priorities, while notes capture
5//! important information, decisions, and observations during development.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use project_manager_mcp::models::task::{Task, TaskStatus, TaskPriority, Note, NoteCategory, TaskList};
11//! use chrono::Utc;
12//!
13//! // Create a high-priority task
14//! let task = Task {
15//!     id: "task_001".to_string(),
16//!     title: "Implement user authentication".to_string(),
17//!     description: "Add JWT-based authentication with OAuth2 support".to_string(),
18//!     status: TaskStatus::Todo,
19//!     priority: TaskPriority::High,
20//!     dependencies: vec!["task_database_setup".to_string()],
21//!     created_at: Utc::now(),
22//!     updated_at: Utc::now(),
23//! };
24//!
25//! // Create an implementation note
26//! let note = Note {
27//!     id: "note_001".to_string(),
28//!     content: "Consider using bcrypt for password hashing".to_string(),
29//!     category: NoteCategory::Implementation,
30//!     created_at: Utc::now(),
31//! };
32//!
33//! // Create a task list
34//! let task_list = TaskList {
35//!     tasks: vec![task],
36//!     last_updated: Utc::now(),
37//! };
38//! ```
39
40use chrono::{DateTime, Utc};
41use serde::{Deserialize, Serialize};
42
43/// Status of a task throughout its lifecycle.
44///
45/// Tasks progress through different states as they are worked on. This enum tracks
46/// the current state to help teams coordinate work and understand progress.
47///
48/// # States
49///
50/// * `Todo` - Task is ready to be started but not yet begun
51/// * `InProgress` - Task is actively being worked on
52/// * `Completed` - Task has been finished successfully
53/// * `Blocked` - Task cannot proceed due to dependencies or external factors
54///
55/// # Examples
56///
57/// ```rust
58/// use project_manager_mcp::models::task::TaskStatus;
59///
60/// // New tasks start as Todo
61/// let status = TaskStatus::Todo;
62///
63/// // Mark as in progress when work begins
64/// let status = TaskStatus::InProgress;
65///
66/// // Complete when finished
67/// let status = TaskStatus::Completed;
68///
69/// // Block if dependencies are missing
70/// let status = TaskStatus::Blocked;
71/// ```
72#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
73pub enum TaskStatus {
74    /// Task is ready to be started but not yet begun
75    Todo,
76    /// Task is actively being worked on
77    InProgress,
78    /// Task has been finished successfully
79    Completed,
80    /// Task cannot proceed due to dependencies or external factors
81    Blocked,
82}
83
84/// Priority level of a task for work scheduling and resource allocation.
85///
86/// Task priorities help teams decide which work to focus on first and how to
87/// allocate resources effectively. Higher priority tasks should generally be
88/// completed before lower priority ones.
89///
90/// # Priority Levels
91///
92/// * `Low` - Non-urgent tasks that can be done when time permits
93/// * `Medium` - Standard priority for regular development work
94/// * `High` - Important tasks that should be prioritized over medium/low
95/// * `Critical` - Urgent tasks that need immediate attention (bugs, blockers)
96///
97/// # Examples
98///
99/// ```rust
100/// use project_manager_mcp::models::task::TaskPriority;
101///
102/// // Regular feature work
103/// let priority = TaskPriority::Medium;
104///
105/// // Important customer request
106/// let priority = TaskPriority::High;
107///
108/// // Production bug fix
109/// let priority = TaskPriority::Critical;
110///
111/// // Nice-to-have improvement
112/// let priority = TaskPriority::Low;
113/// ```
114#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
115pub enum TaskPriority {
116    /// Non-urgent tasks that can be done when time permits
117    Low,
118    /// Standard priority for regular development work
119    Medium,
120    /// Important tasks that should be prioritized over medium/low
121    High,
122    /// Urgent tasks that need immediate attention (bugs, blockers)
123    Critical,
124}
125
126/// Represents a task in a specification with dependencies and tracking information.
127///
128/// A `Task` represents a specific piece of work that needs to be completed as part
129/// of implementing a specification. Tasks can depend on other tasks and are tracked
130/// through various status states with assigned priorities.
131///
132/// # Task Dependencies
133///
134/// Tasks can depend on other tasks being completed first. The `dependencies` field
135/// contains a list of task IDs that must be completed before this task can begin.
136/// This enables proper work sequencing and prevents conflicts.
137///
138/// # Fields
139///
140/// * `id` - Unique identifier for the task (e.g., "task_001", "auth_implementation")
141/// * `title` - Short, descriptive name for the task
142/// * `description` - Detailed explanation of what needs to be done
143/// * `status` - Current progress state (Todo, InProgress, Completed, Blocked)
144/// * `priority` - Importance level for scheduling (Low, Medium, High, Critical)
145/// * `dependencies` - List of task IDs that must be completed first
146/// * `created_at` - Timestamp when the task was created
147/// * `updated_at` - Timestamp of the last modification
148///
149/// # Examples
150///
151/// ```rust
152/// use project_manager_mcp::models::task::{Task, TaskStatus, TaskPriority};
153/// use chrono::Utc;
154///
155/// // Simple independent task
156/// let setup_task = Task {
157///     id: "setup_database".to_string(),
158///     title: "Set up PostgreSQL database".to_string(),
159///     description: "Install and configure PostgreSQL with initial schema".to_string(),
160///     status: TaskStatus::Todo,
161///     priority: TaskPriority::High,
162///     dependencies: vec![], // No dependencies
163///     created_at: Utc::now(),
164///     updated_at: Utc::now(),
165/// };
166///
167/// // Task with dependencies
168/// let api_task = Task {
169///     id: "implement_api".to_string(),
170///     title: "Implement REST API endpoints".to_string(),
171///     description: "Create CRUD endpoints for user management".to_string(),
172///     status: TaskStatus::Todo,
173///     priority: TaskPriority::Medium,
174///     dependencies: vec!["setup_database".to_string()], // Depends on database
175///     created_at: Utc::now(),
176///     updated_at: Utc::now(),
177/// };
178///
179/// // Critical bug fix
180/// let bug_task = Task {
181///     id: "fix_memory_leak".to_string(),
182///     title: "Fix memory leak in user session handling".to_string(),
183///     description: "Investigate and fix memory leak causing OOM errors".to_string(),
184///     status: TaskStatus::InProgress,
185///     priority: TaskPriority::Critical,
186///     dependencies: vec![],
187///     created_at: Utc::now(),
188///     updated_at: Utc::now(),
189/// };
190/// ```
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct Task {
193    /// Unique identifier for the task
194    pub id: String,
195    /// Short, descriptive name for the task
196    pub title: String,
197    /// Detailed explanation of what needs to be done
198    pub description: String,
199    /// Current progress state of the task
200    pub status: TaskStatus,
201    /// Importance level for scheduling work
202    pub priority: TaskPriority,
203    /// List of task IDs that must be completed first
204    pub dependencies: Vec<String>,
205    /// Timestamp when the task was created
206    pub created_at: DateTime<Utc>,
207    /// Timestamp of the last modification
208    pub updated_at: DateTime<Utc>,
209}
210
211/// Category for organizing notes by type and purpose.
212///
213/// Note categories help organize and filter notes based on their content and purpose.
214/// This makes it easier to find relevant information when working on specific aspects
215/// of a project.
216///
217/// # Categories
218///
219/// * `Implementation` - Technical implementation details, code notes, and how-to information
220/// * `Decision` - Architectural decisions, trade-offs, and rationale for choices made
221/// * `Question` - Open questions, uncertainties, and items needing clarification
222/// * `Bug` - Bug reports, issues found, and troubleshooting information
223/// * `Enhancement` - Ideas for improvements, feature requests, and optimizations
224/// * `Other` - General notes that don't fit into specific categories
225///
226/// # Examples
227///
228/// ```rust
229/// use project_manager_mcp::models::task::NoteCategory;
230///
231/// // Technical implementation note
232/// let category = NoteCategory::Implementation;
233///
234/// // Architectural decision
235/// let category = NoteCategory::Decision;
236///
237/// // Bug report
238/// let category = NoteCategory::Bug;
239///
240/// // Feature request
241/// let category = NoteCategory::Enhancement;
242/// ```
243#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
244pub enum NoteCategory {
245    /// Technical implementation details, code notes, and how-to information
246    Implementation,
247    /// Architectural decisions, trade-offs, and rationale for choices made
248    Decision,
249    /// Open questions, uncertainties, and items needing clarification
250    Question,
251    /// Bug reports, issues found, and troubleshooting information
252    Bug,
253    /// Ideas for improvements, feature requests, and optimizations
254    Enhancement,
255    /// General notes that don't fit into specific categories
256    Other,
257}
258
259/// Represents a note in a specification for capturing important information.
260///
261/// A `Note` captures important information, decisions, observations, or questions
262/// that arise during the development of a specification. Notes are categorized
263/// to help organize and filter information effectively.
264///
265/// # Use Cases
266///
267/// * Document implementation decisions and rationale
268/// * Record questions and uncertainties for later resolution
269/// * Track bugs and issues discovered during development
270/// * Capture ideas for future enhancements
271/// * Store important technical details and observations
272///
273/// # Fields
274///
275/// * `id` - Unique identifier for the note
276/// * `content` - The note content (supports Markdown formatting)
277/// * `category` - Type of note for organization and filtering
278/// * `created_at` - Timestamp when the note was created
279///
280/// # Examples
281///
282/// ```rust
283/// use project_manager_mcp::models::task::{Note, NoteCategory};
284/// use chrono::Utc;
285///
286/// // Implementation note with code example
287/// let impl_note = Note {
288///     id: "note_auth_impl".to_string(),
289///     content: "## JWT Implementation\n\nUsing the `jsonwebtoken` crate for JWT handling:\n\n```rust\nuse jsonwebtoken::{encode, decode, Header, Validation};\n\nlet token = encode(&Header::default(), &claims, &encoding_key)?;\n```\n\n**Note**: Remember to set appropriate expiration times.".to_string(),
290///     category: NoteCategory::Implementation,
291///     created_at: Utc::now(),
292/// };
293///
294/// // Decision note
295/// let decision_note = Note {
296///     id: "note_db_choice".to_string(),
297///     content: "Chose PostgreSQL over MongoDB for ACID compliance and complex queries".to_string(),
298///     category: NoteCategory::Decision,
299///     created_at: Utc::now(),
300/// };
301///
302/// // Question note
303/// let question_note = Note {
304///     id: "note_scaling_question".to_string(),
305///     content: "Should we implement horizontal scaling now or wait for user growth?".to_string(),
306///     category: NoteCategory::Question,
307///     created_at: Utc::now(),
308/// };
309///
310/// // Bug report note
311/// let bug_note = Note {
312///     id: "note_session_bug".to_string(),
313///     content: "Session tokens not being invalidated on logout - security risk!".to_string(),
314///     category: NoteCategory::Bug,
315///     created_at: Utc::now(),
316/// };
317/// ```
318#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct Note {
320    /// Unique identifier for the note
321    pub id: String,
322    /// The note content (supports Markdown formatting)
323    pub content: String,
324    /// Type of note for organization and filtering
325    pub category: NoteCategory,
326    /// Timestamp when the note was created
327    pub created_at: DateTime<Utc>,
328}
329
330/// Represents a list of tasks for a specification with tracking metadata.
331///
332/// A `TaskList` contains all tasks associated with a particular specification
333/// along with metadata about when the list was last updated. This provides
334/// a complete view of work items and their current state.
335///
336/// # Fields
337///
338/// * `tasks` - Vector of all tasks in the specification
339/// * `last_updated` - Timestamp of the most recent modification to any task
340///
341/// # Examples
342///
343/// ```rust
344/// use project_manager_mcp::models::task::{TaskList, Task, TaskStatus, TaskPriority};
345/// use chrono::Utc;
346///
347/// // Create a task list for a feature specification
348/// let task_list = TaskList {
349///     tasks: vec![
350///         Task {
351///             id: "task_001".to_string(),
352///             title: "Design user interface".to_string(),
353///             description: "Create mockups and wireframes".to_string(),
354///             status: TaskStatus::Completed,
355///             priority: TaskPriority::Medium,
356///             dependencies: vec![],
357///             created_at: Utc::now(),
358///             updated_at: Utc::now(),
359///         },
360///         Task {
361///             id: "task_002".to_string(),
362///             title: "Implement API endpoints".to_string(),
363///             description: "Create REST API for user management".to_string(),
364///             status: TaskStatus::InProgress,
365///             priority: TaskPriority::High,
366///             dependencies: vec!["task_001".to_string()],
367///             created_at: Utc::now(),
368///             updated_at: Utc::now(),
369///         },
370///     ],
371///     last_updated: Utc::now(),
372/// };
373///
374/// // Check task progress
375/// let total_tasks = task_list.tasks.len();
376/// let completed_tasks = task_list.tasks.iter()
377///     .filter(|t| t.status == TaskStatus::Completed)
378///     .count();
379/// println!("Progress: {}/{} tasks completed", completed_tasks, total_tasks);
380/// ```
381#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct TaskList {
383    /// Vector of all tasks in the specification
384    pub tasks: Vec<Task>,
385    /// Timestamp of the most recent modification to any task
386    pub last_updated: DateTime<Utc>,
387}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392    use chrono::TimeZone;
393
394    fn create_sample_task() -> Task {
395        Task {
396            id: "task_001".to_string(),
397            title: "Implement user authentication".to_string(),
398            description: "Add JWT-based authentication system".to_string(),
399            status: TaskStatus::Todo,
400            priority: TaskPriority::High,
401            dependencies: vec!["task_000".to_string()],
402            created_at: Utc.with_ymd_and_hms(2024, 1, 1, 12, 0, 0).unwrap(),
403            updated_at: Utc.with_ymd_and_hms(2024, 1, 2, 12, 0, 0).unwrap(),
404        }
405    }
406
407    fn create_sample_note() -> Note {
408        Note {
409            id: "note_001".to_string(),
410            content: "Consider using OAuth 2.0 for third-party authentication".to_string(),
411            category: NoteCategory::Implementation,
412            created_at: Utc.with_ymd_and_hms(2024, 1, 1, 12, 0, 0).unwrap(),
413        }
414    }
415
416    fn create_sample_task_list() -> TaskList {
417        TaskList {
418            tasks: vec![create_sample_task()],
419            last_updated: Utc.with_ymd_and_hms(2024, 1, 2, 12, 0, 0).unwrap(),
420        }
421    }
422
423    #[test]
424    fn test_task_status_serialization() {
425        let statuses = [
426            TaskStatus::Todo,
427            TaskStatus::InProgress,
428            TaskStatus::Completed,
429            TaskStatus::Blocked,
430        ];
431
432        for status in &statuses {
433            let serialized = serde_json::to_string(status).expect("Failed to serialize TaskStatus");
434            let deserialized: TaskStatus = serde_json::from_str(&serialized).expect("Failed to deserialize TaskStatus");
435            assert_eq!(*status, deserialized);
436        }
437    }
438
439    #[test]
440    fn test_task_priority_serialization() {
441        let priorities = [
442            TaskPriority::Low,
443            TaskPriority::Medium,
444            TaskPriority::High,
445            TaskPriority::Critical,
446        ];
447
448        for priority in &priorities {
449            let serialized = serde_json::to_string(priority).expect("Failed to serialize TaskPriority");
450            let deserialized: TaskPriority = serde_json::from_str(&serialized).expect("Failed to deserialize TaskPriority");
451            assert_eq!(*priority, deserialized);
452        }
453    }
454
455    #[test]
456    fn test_note_category_serialization() {
457        let categories = [
458            NoteCategory::Implementation,
459            NoteCategory::Decision,
460            NoteCategory::Question,
461            NoteCategory::Bug,
462            NoteCategory::Enhancement,
463            NoteCategory::Other,
464        ];
465
466        for category in &categories {
467            let serialized = serde_json::to_string(category).expect("Failed to serialize NoteCategory");
468            let deserialized: NoteCategory = serde_json::from_str(&serialized).expect("Failed to deserialize NoteCategory");
469            assert_eq!(*category, deserialized);
470        }
471    }
472
473    #[test]
474    fn test_task_serialization() {
475        let task = create_sample_task();
476        let serialized = serde_json::to_string(&task).expect("Failed to serialize Task");
477        let deserialized: Task = serde_json::from_str(&serialized).expect("Failed to deserialize Task");
478
479        assert_eq!(task.id, deserialized.id);
480        assert_eq!(task.title, deserialized.title);
481        assert_eq!(task.description, deserialized.description);
482        assert_eq!(task.status, deserialized.status);
483        assert_eq!(task.priority, deserialized.priority);
484        assert_eq!(task.dependencies, deserialized.dependencies);
485        assert_eq!(task.created_at, deserialized.created_at);
486        assert_eq!(task.updated_at, deserialized.updated_at);
487    }
488
489    #[test]
490    fn test_task_empty_dependencies() {
491        let mut task = create_sample_task();
492        task.dependencies = vec![];
493
494        let serialized = serde_json::to_string(&task).expect("Failed to serialize Task with empty dependencies");
495        let deserialized: Task = serde_json::from_str(&serialized).expect("Failed to deserialize Task with empty dependencies");
496
497        assert!(deserialized.dependencies.is_empty());
498    }
499
500    #[test]
501    fn test_task_multiple_dependencies() {
502        let mut task = create_sample_task();
503        task.dependencies = vec![
504            "task_001".to_string(),
505            "task_002".to_string(),
506            "task_003".to_string(),
507        ];
508
509        let serialized = serde_json::to_string(&task).expect("Failed to serialize Task with multiple dependencies");
510        let deserialized: Task = serde_json::from_str(&serialized).expect("Failed to deserialize Task with multiple dependencies");
511
512        assert_eq!(deserialized.dependencies.len(), 3);
513        assert_eq!(deserialized.dependencies[0], "task_001");
514        assert_eq!(deserialized.dependencies[1], "task_002");
515        assert_eq!(deserialized.dependencies[2], "task_003");
516    }
517
518    #[test]
519    fn test_note_serialization() {
520        let note = create_sample_note();
521        let serialized = serde_json::to_string(&note).expect("Failed to serialize Note");
522        let deserialized: Note = serde_json::from_str(&serialized).expect("Failed to deserialize Note");
523
524        assert_eq!(note.id, deserialized.id);
525        assert_eq!(note.content, deserialized.content);
526        assert_eq!(note.category, deserialized.category);
527        assert_eq!(note.created_at, deserialized.created_at);
528    }
529
530    #[test]
531    fn test_note_long_content() {
532        let long_content = "A".repeat(10000);
533        let note = Note {
534            id: "note_long".to_string(),
535            content: long_content.clone(),
536            category: NoteCategory::Other,
537            created_at: Utc::now(),
538        };
539
540        let serialized = serde_json::to_string(&note).expect("Failed to serialize Note with long content");
541        let deserialized: Note = serde_json::from_str(&serialized).expect("Failed to deserialize Note with long content");
542
543        assert_eq!(note.content, deserialized.content);
544        assert_eq!(deserialized.content.len(), 10000);
545    }
546
547    #[test]
548    fn test_note_markdown_content() {
549        let note = Note {
550            id: "note_markdown".to_string(),
551            content: r#"# Implementation Note
552
553## Code Example
554```rust
555fn main() {
556    println!("Hello, world!");
557}
558```
559
560**Important**: This needs to be tested thoroughly.
561
562- Item 1
563- Item 2"#.to_string(),
564            category: NoteCategory::Implementation,
565            created_at: Utc::now(),
566        };
567
568        let serialized = serde_json::to_string(&note).expect("Failed to serialize Note with markdown");
569        let deserialized: Note = serde_json::from_str(&serialized).expect("Failed to deserialize Note with markdown");
570
571        assert_eq!(note.content, deserialized.content);
572        assert!(deserialized.content.contains("# Implementation Note"));
573        assert!(deserialized.content.contains("```rust"));
574    }
575
576    #[test]
577    fn test_task_list_serialization() {
578        let task_list = create_sample_task_list();
579        let serialized = serde_json::to_string(&task_list).expect("Failed to serialize TaskList");
580        let deserialized: TaskList = serde_json::from_str(&serialized).expect("Failed to deserialize TaskList");
581
582        assert_eq!(task_list.tasks.len(), deserialized.tasks.len());
583        assert_eq!(task_list.last_updated, deserialized.last_updated);
584        assert_eq!(task_list.tasks[0].id, deserialized.tasks[0].id);
585    }
586
587    #[test]
588    fn test_task_list_empty() {
589        let task_list = TaskList {
590            tasks: vec![],
591            last_updated: Utc::now(),
592        };
593
594        let serialized = serde_json::to_string(&task_list).expect("Failed to serialize empty TaskList");
595        let deserialized: TaskList = serde_json::from_str(&serialized).expect("Failed to deserialize empty TaskList");
596
597        assert!(deserialized.tasks.is_empty());
598    }
599
600    #[test]
601    fn test_task_list_multiple_tasks() {
602        let tasks = vec![
603            create_sample_task(),
604            Task {
605                id: "task_002".to_string(),
606                title: "Database setup".to_string(),
607                description: "Configure PostgreSQL database".to_string(),
608                status: TaskStatus::InProgress,
609                priority: TaskPriority::Medium,
610                dependencies: vec![],
611                created_at: Utc::now(),
612                updated_at: Utc::now(),
613            },
614            Task {
615                id: "task_003".to_string(),
616                title: "Frontend components".to_string(),
617                description: "Create React components".to_string(),
618                status: TaskStatus::Completed,
619                priority: TaskPriority::Low,
620                dependencies: vec!["task_002".to_string()],
621                created_at: Utc::now(),
622                updated_at: Utc::now(),
623            },
624        ];
625
626        let task_list = TaskList {
627            tasks,
628            last_updated: Utc::now(),
629        };
630
631        let serialized = serde_json::to_string(&task_list).expect("Failed to serialize TaskList with multiple tasks");
632        let deserialized: TaskList = serde_json::from_str(&serialized).expect("Failed to deserialize TaskList with multiple tasks");
633
634        assert_eq!(deserialized.tasks.len(), 3);
635        assert_eq!(deserialized.tasks[0].status, TaskStatus::Todo);
636        assert_eq!(deserialized.tasks[1].status, TaskStatus::InProgress);
637        assert_eq!(deserialized.tasks[2].status, TaskStatus::Completed);
638    }
639
640    #[test]
641    fn test_enum_hash_and_equality() {
642        use std::collections::HashMap;
643
644        let mut status_count = HashMap::new();
645        status_count.insert(TaskStatus::Todo, 5);
646        status_count.insert(TaskStatus::InProgress, 2);
647        status_count.insert(TaskStatus::Completed, 10);
648
649        assert_eq!(status_count.get(&TaskStatus::Todo), Some(&5));
650        assert_eq!(status_count.get(&TaskStatus::Completed), Some(&10));
651
652        let mut priority_count = HashMap::new();
653        priority_count.insert(TaskPriority::High, 3);
654        priority_count.insert(TaskPriority::Critical, 1);
655
656        assert_eq!(priority_count.get(&TaskPriority::High), Some(&3));
657
658        let mut category_count = HashMap::new();
659        category_count.insert(NoteCategory::Bug, 2);
660        category_count.insert(NoteCategory::Enhancement, 4);
661
662        assert_eq!(category_count.get(&NoteCategory::Bug), Some(&2));
663    }
664}