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(¬e).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(¬e).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(¬e).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}