Files
gyandoot-v2/crates/gyandoot-kanban/src/decompose.rs
Tara Menon 8c9a32075d Slash commands (/quit, /help) + zero compiler warnings
1. /quit and /help commands restored:
   - /quit: exits the interactive loop
   - /help: shows formatted help box with available commands & tips
   - Unknown commands get a friendly error pointing to /help
   - Slash commands intercepted before LLM processing (no token waste)

2. Zero warnings across entire workspace (13 files fixed):
   - Fixed ambiguous glob re-exports in gyandoot-core
   - Added #[allow(dead_code)] on intentional dead fields
   - Fixed unused imports, variables, and macros
   - Fixed unused assignments in subagents
   - cleaned up service.rs, profile, kanban warnings

install.sh now produces a completely clean build with zero warnings.
2026-06-12 12:15:01 +05:30

119 lines
4.0 KiB
Rust

//! 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<Vec<Task>> {
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
}
}