Initial scaffold: GyanDoot v2.0 in Rust
Full Cargo workspace with 11 crates: - gyandoot-core: Shared types, config, error types - gyandoot-agent: Agent loop, tool execution, context compression - gyandoot-providers: LLM provider abstraction (OpenAI, Anthropic, Ollama) - gyandoot-memory: MemPalace persistent memory system - gyandoot-platform: Platform adapters (Telegram, Discord, Webhook, CLI) - gyandoot-security: Credential vault, sanitizer, threat detector - gyandoot-kanban: Task decomposition, swarm orchestration, diagnostics - gyandoot-subagents: Sub-agent spawning and orchestration - gyandoot-skills: Skills discovery, loading, preprocessing, bundles - gyandoot-profile: Multi-agent profile manager - gyandoot-gateway: Gateway sessions, routing, access control Plus: CLI entry point with clap, cross-platform installer script, KANBAN.md, .gnpl/AGENTS.md Workspace compiles clean: cargo check passes with 0 errors.
This commit is contained in:
118
crates/gyandoot-kanban/src/decompose.rs
Normal file
118
crates/gyandoot-kanban/src/decompose.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
//! Task decomposition — breaks complex tasks into smaller subtasks.
|
||||
|
||||
use gyandoot_core::{KanbanResult, Priority, Task, TaskStatus};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Decomposes a complex task into smaller, actionable subtasks.
|
||||
pub struct Decomposer {
|
||||
/// Maximum depth of decomposition.
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user