//! Task decomposition — breaks complex tasks into smaller subtasks. use gyandoot_core::{KanbanResult, Task, TaskStatus}; use uuid::Uuid; /// Decomposes a complex task into smaller, actionable subtasks. pub struct Decomposer { /// Maximum depth of decomposition. #[allow(dead_code)] max_depth: usize, } impl Decomposer { /// Create a new decomposer. pub fn new() -> Self { Self { max_depth: 3 } } /// Decompose a task into subtasks using heuristics. /// /// In production, this would use the LLM to intelligently break down /// tasks. For now, it uses a simple heuristic based on the description. pub fn decompose(&self, task: &Task) -> KanbanResult> { let mut subtasks = Vec::new(); // Heuristic: if the task mentions "and" or lists, create subtasks let description_lower = task.description.to_lowercase(); if description_lower.contains(" and ") { subtasks.push(self.make_subtask(task, &format!("Part 1 of: {}", task.title), 1)); subtasks.push(self.make_subtask(task, &format!("Part 2 of: {}", task.title), 2)); } if description_lower.contains("first") || description_lower.contains("step 1") { subtasks.push(self.make_subtask(task, &format!("Step 1: {}", task.title), 1)); } if description_lower.contains("second") || description_lower.contains("step 2") { subtasks.push(self.make_subtask(task, &format!("Step 2: {}", task.title), 2)); } // If no specific decomposition found, create a default 3-part split if subtasks.is_empty() && task.description.len() > 100 { subtasks.push(self.make_subtask(task, &format!("Research: {}", task.title), 1)); subtasks.push(self.make_subtask(task, &format!("Implement: {}", task.title), 2)); subtasks.push(self.make_subtask(task, &format!("Verify: {}", task.title), 3)); } Ok(subtasks) } /// Create a single subtask. fn make_subtask(&self, parent: &Task, title: &str, _index: usize) -> Task { Task { id: Uuid::new_v4(), title: title.to_string(), description: format!("Subtask of '{}': {}", parent.title, parent.description), priority: parent.priority, status: TaskStatus::Backlog, assigned_agent: None, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), subtasks: Vec::new(), dependencies: vec![parent.id], tags: parent.tags.clone(), } } } impl Default for Decomposer { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_decompose_simple() { let decomposer = Decomposer::new(); let task = Task { id: Uuid::new_v4(), title: "Build feature".to_string(), description: "Implement the login system and add tests".to_string(), priority: Priority::High, status: TaskStatus::Backlog, assigned_agent: None, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), subtasks: Vec::new(), dependencies: Vec::new(), tags: Vec::new(), }; let subtasks = decomposer.decompose(&task).unwrap(); assert!(subtasks.len() >= 2); } #[test] fn test_decompose_short_description() { let decomposer = Decomposer::new(); let task = Task { id: Uuid::new_v4(), title: "Simple task".to_string(), description: "Do the thing".to_string(), priority: Priority::Low, status: TaskStatus::Backlog, assigned_agent: None, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), subtasks: Vec::new(), dependencies: Vec::new(), tags: Vec::new(), }; let subtasks = decomposer.decompose(&task).unwrap(); assert!(subtasks.is_empty()); // too short to decompose } }