1use anyhow::{anyhow, Result};
12use chrono::Utc;
13use std::collections::HashMap;
14use std::path::PathBuf;
15use std::sync::Arc;
16use tokio::sync::RwLock;
17use uuid::Uuid;
18
19use super::types::*;
20
21#[allow(dead_code)]
27pub struct TaskTreeManager {
28 task_trees: Arc<RwLock<HashMap<String, TaskTree>>>,
30 current_tree_id: Arc<RwLock<Option<String>>>,
32 current_blueprint: Arc<RwLock<Option<Blueprint>>>,
34 storage_dir: PathBuf,
36}
37
38impl TaskTreeManager {
39 pub fn new(storage_dir: PathBuf) -> Self {
41 Self {
42 task_trees: Arc::new(RwLock::new(HashMap::new())),
43 current_tree_id: Arc::new(RwLock::new(None)),
44 current_blueprint: Arc::new(RwLock::new(None)),
45 storage_dir,
46 }
47 }
48
49 pub fn with_default_dir() -> Self {
51 let storage_dir = dirs::home_dir()
52 .unwrap_or_else(|| PathBuf::from("."))
53 .join(".aster")
54 .join("task-trees");
55 Self::new(storage_dir)
56 }
57
58 pub async fn set_current_blueprint(&self, blueprint: Blueprint) {
60 *self.current_blueprint.write().await = Some(blueprint);
61 }
62
63 pub async fn get_current_blueprint(&self) -> Option<Blueprint> {
65 self.current_blueprint.read().await.clone()
66 }
67
68 pub async fn generate_from_blueprint(&self, blueprint: &Blueprint) -> Result<TaskTree> {
74 *self.current_blueprint.write().await = Some(blueprint.clone());
76
77 let mut root_task = self.create_root_task(blueprint);
79
80 for module in &blueprint.modules {
82 let module_task = self.create_module_task(module, &root_task.id, 1);
83 root_task.children.push(module_task);
84 }
85
86 self.resolve_dependencies(&mut root_task, &blueprint.modules);
88
89 let mut task_tree = TaskTree::new(blueprint.id.clone(), root_task);
91 task_tree.stats = self.calculate_stats(&task_tree.root);
92
93 let tree_id = task_tree.id.clone();
95 self.task_trees
96 .write()
97 .await
98 .insert(tree_id.clone(), task_tree.clone());
99 *self.current_tree_id.write().await = Some(tree_id);
100
101 Ok(task_tree)
102 }
103
104 fn create_root_task(&self, blueprint: &Blueprint) -> TaskNode {
106 let mut task = TaskNode::new(
107 format!("项目:{}", blueprint.name),
108 blueprint.description.clone(),
109 0,
110 );
111 task.priority = 100;
112 task
113 }
114
115 fn create_module_task(&self, module: &SystemModule, parent_id: &str, depth: u32) -> TaskNode {
117 let mut module_task = TaskNode::new(
118 format!("模块:{}", module.name),
119 module.description.clone(),
120 depth,
121 );
122 module_task.parent_id = Some(parent_id.to_string());
123 module_task.blueprint_module_id = Some(module.id.clone());
124 module_task.priority = self.calculate_module_priority(module);
125 module_task.metadata = Some(serde_json::json!({
126 "moduleType": format!("{:?}", module.module_type),
127 "techStack": module.tech_stack,
128 }));
129
130 for (i, responsibility) in module.responsibilities.iter().enumerate() {
132 let resp_task =
133 self.create_responsibility_task(responsibility, &module_task.id, depth + 1, i);
134 module_task.children.push(resp_task);
135 }
136
137 for iface in &module.interfaces {
139 let iface_task = self.create_interface_task(iface, &module_task.id, depth + 1);
140 module_task.children.push(iface_task);
141 }
142
143 module_task
144 }
145
146 fn create_responsibility_task(
148 &self,
149 responsibility: &str,
150 parent_id: &str,
151 depth: u32,
152 index: usize,
153 ) -> TaskNode {
154 let mut task = TaskNode::new(
155 format!("功能:{}", responsibility),
156 responsibility.to_string(),
157 depth,
158 );
159 task.parent_id = Some(parent_id.to_string());
160 task.priority = 50 - index as i32;
161
162 let subtasks = self.decompose_responsibility(responsibility, &task.id, depth + 1);
164 task.children = subtasks;
165
166 task
167 }
168
169 fn decompose_responsibility(
171 &self,
172 responsibility: &str,
173 parent_id: &str,
174 depth: u32,
175 ) -> Vec<TaskNode> {
176 let subtask_templates = [
177 ("设计", format!("设计 {} 的实现方案", responsibility)),
178 ("测试用例", format!("编写 {} 的测试用例", responsibility)),
179 ("实现", format!("实现 {}", responsibility)),
180 ("集成测试", format!("{} 的集成测试", responsibility)),
181 ];
182
183 subtask_templates
184 .iter()
185 .enumerate()
186 .map(|(i, (name, desc))| {
187 let short_resp = if responsibility.len() > 20 {
188 let truncate_at = responsibility
190 .char_indices()
191 .take_while(|(idx, _)| *idx < 20)
192 .last()
193 .map(|(idx, c)| idx + c.len_utf8())
194 .unwrap_or(0);
195 format!(
196 "{}...",
197 responsibility.get(..truncate_at).unwrap_or(responsibility)
198 )
199 } else {
200 responsibility.to_string()
201 };
202
203 let mut task =
204 TaskNode::new(format!("{}:{}", name, short_resp), desc.clone(), depth);
205 task.parent_id = Some(parent_id.to_string());
206 task.priority = 40 - (i as i32 * 10);
207 task
208 })
209 .collect()
210 }
211
212 fn create_interface_task(
214 &self,
215 iface: &ModuleInterface,
216 parent_id: &str,
217 depth: u32,
218 ) -> TaskNode {
219 let mut task = TaskNode::new(
220 format!("接口:{}", iface.name),
221 format!("{:?} 接口 - {}", iface.interface_type, iface.description),
222 depth,
223 );
224 task.parent_id = Some(parent_id.to_string());
225 task.priority = 30;
226 task.metadata = Some(serde_json::json!({
227 "interfaceType": format!("{:?}", iface.interface_type),
228 }));
229 task
230 }
231
232 fn calculate_module_priority(&self, module: &SystemModule) -> i32 {
234 let type_priority = match module.module_type {
235 ModuleType::Infrastructure => 90,
236 ModuleType::Database => 85,
237 ModuleType::Backend => 80,
238 ModuleType::Service => 70,
239 ModuleType::Frontend => 60,
240 ModuleType::Other => 50,
241 };
242
243 let dep_penalty = module.dependencies.len() as i32 * 5;
245 type_priority - dep_penalty
246 }
247
248 fn resolve_dependencies(&self, root_task: &mut TaskNode, modules: &[SystemModule]) {
250 let module_to_task: HashMap<String, String> = root_task
252 .children
253 .iter()
254 .filter_map(|child| {
255 child
256 .blueprint_module_id
257 .as_ref()
258 .map(|mid| (mid.clone(), child.id.clone()))
259 })
260 .collect();
261
262 for child in &mut root_task.children {
264 if let Some(module_id) = &child.blueprint_module_id {
265 if let Some(module) = modules.iter().find(|m| &m.id == module_id) {
266 for dep_module_id in &module.dependencies {
267 if let Some(dep_task_id) = module_to_task.get(dep_module_id) {
268 child.dependencies.push(dep_task_id.clone());
269 }
270 }
271 }
272 }
273 }
274 }
275
276 pub async fn update_task_status(
282 &self,
283 tree_id: &str,
284 task_id: &str,
285 status: TaskStatus,
286 ) -> Result<TaskNode> {
287 let mut trees = self.task_trees.write().await;
288 let tree = trees
289 .get_mut(tree_id)
290 .ok_or_else(|| anyhow!("Task tree {} not found", tree_id))?;
291
292 let task = Self::find_task_mut(&mut tree.root, task_id)
293 .ok_or_else(|| anyhow!("Task {} not found", task_id))?;
294
295 let _previous_status = task.status;
296 task.status = status;
297
298 match status {
300 TaskStatus::Coding | TaskStatus::TestWriting => {
301 if task.started_at.is_none() {
302 task.started_at = Some(Utc::now());
303 }
304 }
305 TaskStatus::Passed | TaskStatus::Approved => {
306 task.completed_at = Some(Utc::now());
307 }
308 _ => {}
309 }
310
311 let task_clone = task.clone();
312
313 tree.stats = self.calculate_stats(&tree.root);
315
316 Self::propagate_status(&mut tree.root);
318
319 Ok(task_clone)
320 }
321
322 fn find_task_mut<'a>(node: &'a mut TaskNode, task_id: &str) -> Option<&'a mut TaskNode> {
324 if node.id == task_id {
325 return Some(node);
326 }
327
328 for child in &mut node.children {
329 if let Some(found) = Self::find_task_mut(child, task_id) {
330 return Some(found);
331 }
332 }
333
334 None
335 }
336
337 pub fn find_task<'a>(node: &'a TaskNode, task_id: &str) -> Option<&'a TaskNode> {
339 if node.id == task_id {
340 return Some(node);
341 }
342
343 for child in &node.children {
344 if let Some(found) = Self::find_task(child, task_id) {
345 return Some(found);
346 }
347 }
348
349 None
350 }
351
352 fn propagate_status(node: &mut TaskNode) {
354 if node.children.is_empty() {
355 return;
356 }
357
358 for child in &mut node.children {
360 Self::propagate_status(child);
361 }
362
363 let all_passed = node
365 .children
366 .iter()
367 .all(|c| c.status == TaskStatus::Passed || c.status == TaskStatus::Approved);
368 let any_failed = node
369 .children
370 .iter()
371 .any(|c| c.status == TaskStatus::TestFailed || c.status == TaskStatus::Rejected);
372 let any_running = node.children.iter().any(|c| {
373 matches!(
374 c.status,
375 TaskStatus::Coding | TaskStatus::Testing | TaskStatus::TestWriting
376 )
377 });
378
379 if all_passed && node.status != TaskStatus::Approved {
381 node.status = TaskStatus::Passed;
382 node.completed_at = Some(Utc::now());
383 } else if any_failed && node.status != TaskStatus::TestFailed {
384 node.status = TaskStatus::TestFailed;
385 } else if any_running && node.status == TaskStatus::Pending {
386 node.status = TaskStatus::Coding;
387 if node.started_at.is_none() {
388 node.started_at = Some(Utc::now());
389 }
390 }
391 }
392
393 pub async fn can_start_task(&self, tree_id: &str, task_id: &str) -> (bool, Vec<String>) {
395 let trees = self.task_trees.read().await;
396 let tree = match trees.get(tree_id) {
397 Some(t) => t,
398 None => return (false, vec!["任务树不存在".to_string()]),
399 };
400
401 let task = match Self::find_task(&tree.root, task_id) {
402 Some(t) => t,
403 None => return (false, vec!["任务不存在".to_string()]),
404 };
405
406 if task.status != TaskStatus::Pending && task.status != TaskStatus::Blocked {
407 return (
408 false,
409 vec![format!("任务状态为 {:?},不能开始", task.status)],
410 );
411 }
412
413 let mut blockers = Vec::new();
414
415 for dep_id in &task.dependencies {
417 if let Some(dep_task) = Self::find_task(&tree.root, dep_id) {
418 if dep_task.status != TaskStatus::Passed && dep_task.status != TaskStatus::Approved
419 {
420 blockers.push(format!(
421 "依赖任务 \"{}\" 尚未完成 ({:?})",
422 dep_task.name, dep_task.status
423 ));
424 }
425 }
426 }
427
428 (blockers.is_empty(), blockers)
429 }
430
431 pub async fn get_executable_tasks(&self, tree_id: &str) -> Vec<TaskNode> {
433 let trees = self.task_trees.read().await;
434 let tree = match trees.get(tree_id) {
435 Some(t) => t,
436 None => return Vec::new(),
437 };
438
439 let mut executable = Vec::new();
440 self.collect_executable_tasks(&tree.root, &mut executable, tree_id, &trees);
441
442 executable.sort_by(|a, b| b.priority.cmp(&a.priority));
444 executable
445 }
446
447 fn collect_executable_tasks(
448 &self,
449 node: &TaskNode,
450 result: &mut Vec<TaskNode>,
451 tree_id: &str,
452 trees: &HashMap<String, TaskTree>,
453 ) {
454 if node.status == TaskStatus::Pending || node.status == TaskStatus::Blocked {
455 let can_start = node.dependencies.iter().all(|dep_id| {
457 if let Some(tree) = trees.get(tree_id) {
458 if let Some(dep_task) = Self::find_task(&tree.root, dep_id) {
459 return dep_task.status == TaskStatus::Passed
460 || dep_task.status == TaskStatus::Approved;
461 }
462 }
463 false
464 }) || node.dependencies.is_empty();
465
466 if can_start {
467 result.push(node.clone());
468 }
469 }
470
471 for child in &node.children {
472 self.collect_executable_tasks(child, result, tree_id, trees);
473 }
474 }
475
476 pub async fn create_task_checkpoint(
482 &self,
483 tree_id: &str,
484 task_id: &str,
485 name: String,
486 description: Option<String>,
487 ) -> Result<Checkpoint> {
488 let mut trees = self.task_trees.write().await;
489 let tree = trees
490 .get_mut(tree_id)
491 .ok_or_else(|| anyhow!("Task tree {} not found", tree_id))?;
492
493 let task = Self::find_task_mut(&mut tree.root, task_id)
494 .ok_or_else(|| anyhow!("Task {} not found", task_id))?;
495
496 let code_snapshot: Vec<CodeSnapshot> = task
498 .code_artifacts
499 .iter()
500 .filter_map(|artifact| {
501 if let (Some(path), Some(content)) = (&artifact.file_path, &artifact.content) {
502 Some(CodeSnapshot {
503 file_path: path.clone(),
504 content: content.clone(),
505 hash: Self::hash_content(content),
506 })
507 } else {
508 None
509 }
510 })
511 .collect();
512
513 let checkpoint = Checkpoint {
514 id: Uuid::new_v4().to_string(),
515 task_id: task_id.to_string(),
516 timestamp: Utc::now(),
517 name,
518 description,
519 task_status: task.status,
520 test_result: task.test_spec.as_ref().and_then(|s| s.last_result.clone()),
521 code_snapshot,
522 can_restore: true,
523 metadata: None,
524 };
525
526 task.checkpoints.push(checkpoint.clone());
527
528 Ok(checkpoint)
529 }
530
531 pub async fn create_global_checkpoint(
533 &self,
534 tree_id: &str,
535 name: String,
536 description: Option<String>,
537 ) -> Result<GlobalCheckpoint> {
538 let mut trees = self.task_trees.write().await;
539 let tree = trees
540 .get_mut(tree_id)
541 .ok_or_else(|| anyhow!("Task tree {} not found", tree_id))?;
542
543 let tree_snapshot = serde_json::to_string(&tree.root)?;
545
546 let mut file_changes = Vec::new();
548 Self::collect_file_changes(&tree.root, &mut file_changes);
549
550 let checkpoint = GlobalCheckpoint {
551 id: Uuid::new_v4().to_string(),
552 tree_id: tree_id.to_string(),
553 timestamp: Utc::now(),
554 name,
555 description,
556 tree_snapshot,
557 file_changes,
558 can_restore: true,
559 };
560
561 tree.global_checkpoints.push(checkpoint.clone());
562
563 Ok(checkpoint)
564 }
565
566 fn collect_file_changes(node: &TaskNode, changes: &mut Vec<FileChange>) {
567 for artifact in &node.code_artifacts {
568 if let Some(path) = &artifact.file_path {
569 if artifact.artifact_type == ArtifactType::File {
570 changes.push(FileChange {
571 file_path: path.clone(),
572 change_type: FileChangeType::Create,
573 previous_content: None,
574 new_content: artifact.content.clone(),
575 });
576 }
577 }
578 }
579
580 for child in &node.children {
581 Self::collect_file_changes(child, changes);
582 }
583 }
584
585 fn hash_content(content: &str) -> String {
586 use std::collections::hash_map::DefaultHasher;
587 use std::hash::{Hash, Hasher};
588
589 let mut hasher = DefaultHasher::new();
590 content.hash(&mut hasher);
591 format!("{:x}", hasher.finish())
592 }
593
594 pub async fn rollback_to_checkpoint(
596 &self,
597 tree_id: &str,
598 task_id: &str,
599 checkpoint_id: &str,
600 ) -> Result<TaskNode> {
601 let mut trees = self.task_trees.write().await;
602 let tree = trees
603 .get_mut(tree_id)
604 .ok_or_else(|| anyhow!("Task tree {} not found", tree_id))?;
605
606 let task_clone = {
608 let task = Self::find_task_mut(&mut tree.root, task_id)
609 .ok_or_else(|| anyhow!("Task {} not found", task_id))?;
610
611 let checkpoint = task
612 .checkpoints
613 .iter()
614 .find(|c| c.id == checkpoint_id)
615 .ok_or_else(|| anyhow!("Checkpoint {} not found", checkpoint_id))?
616 .clone();
617
618 if !checkpoint.can_restore {
619 return Err(anyhow!("Checkpoint {} cannot be restored", checkpoint_id));
620 }
621
622 task.status = checkpoint.task_status;
624
625 if let Some(ref mut test_spec) = task.test_spec {
627 test_spec.last_result = checkpoint.test_result.clone();
628 }
629
630 for snapshot in &checkpoint.code_snapshot {
632 task.code_artifacts.push(CodeArtifact {
633 id: Uuid::new_v4().to_string(),
634 artifact_type: ArtifactType::File,
635 file_path: Some(snapshot.file_path.clone()),
636 content: Some(snapshot.content.clone()),
637 command: None,
638 created_at: Utc::now(),
639 checkpoint_id: Some(checkpoint.id.clone()),
640 });
641 }
642
643 let checkpoint_index = task
645 .checkpoints
646 .iter()
647 .position(|c| c.id == checkpoint_id)
648 .unwrap_or(0);
649 task.checkpoints.truncate(checkpoint_index + 1);
650
651 task.clone()
652 };
653
654 tree.stats = self.calculate_stats(&tree.root);
656
657 Ok(task_clone)
658 }
659
660 pub async fn rollback_to_global_checkpoint(
662 &self,
663 tree_id: &str,
664 checkpoint_id: &str,
665 ) -> Result<TaskTree> {
666 let mut trees = self.task_trees.write().await;
667 let tree = trees
668 .get_mut(tree_id)
669 .ok_or_else(|| anyhow!("Task tree {} not found", tree_id))?;
670
671 let checkpoint = tree
672 .global_checkpoints
673 .iter()
674 .find(|c| c.id == checkpoint_id)
675 .ok_or_else(|| anyhow!("Global checkpoint {} not found", checkpoint_id))?
676 .clone();
677
678 if !checkpoint.can_restore {
679 return Err(anyhow!(
680 "Global checkpoint {} cannot be restored",
681 checkpoint_id
682 ));
683 }
684
685 let restored_root: TaskNode = serde_json::from_str(&checkpoint.tree_snapshot)?;
687 tree.root = restored_root;
688
689 let checkpoint_index = tree
691 .global_checkpoints
692 .iter()
693 .position(|c| c.id == checkpoint_id)
694 .unwrap_or(0);
695 tree.global_checkpoints.truncate(checkpoint_index + 1);
696
697 tree.stats = self.calculate_stats(&tree.root);
699
700 Ok(tree.clone())
701 }
702
703 pub async fn add_sub_task(
709 &self,
710 tree_id: &str,
711 parent_task_id: &str,
712 name: String,
713 description: String,
714 priority: i32,
715 ) -> Result<TaskNode> {
716 let mut trees = self.task_trees.write().await;
717 let tree = trees
718 .get_mut(tree_id)
719 .ok_or_else(|| anyhow!("Task tree {} not found", tree_id))?;
720
721 let parent_task = Self::find_task_mut(&mut tree.root, parent_task_id)
722 .ok_or_else(|| anyhow!("Parent task {} not found", parent_task_id))?;
723
724 let mut new_task = TaskNode::new(name, description, parent_task.depth + 1);
725 new_task.parent_id = Some(parent_task_id.to_string());
726 new_task.priority = priority;
727
728 let task_clone = new_task.clone();
729 parent_task.children.push(new_task);
730
731 tree.stats = self.calculate_stats(&tree.root);
733
734 Ok(task_clone)
735 }
736
737 pub fn calculate_stats(&self, root: &TaskNode) -> TaskTreeStats {
743 let mut stats = TaskTreeStats::default();
744 let mut total_depth = 0u64;
745
746 fn traverse(node: &TaskNode, stats: &mut TaskTreeStats, total_depth: &mut u64) {
747 stats.total_tasks += 1;
748 *total_depth += node.depth as u64;
749
750 if node.depth > stats.max_depth {
751 stats.max_depth = node.depth;
752 }
753
754 match node.status {
755 TaskStatus::Pending => stats.pending_tasks += 1,
756 TaskStatus::Blocked => stats.blocked_tasks += 1,
757 TaskStatus::Coding | TaskStatus::Testing | TaskStatus::TestWriting => {
758 stats.running_tasks += 1
759 }
760 TaskStatus::Passed | TaskStatus::Approved => stats.passed_tasks += 1,
761 TaskStatus::TestFailed | TaskStatus::Rejected => stats.failed_tasks += 1,
762 _ => {}
763 }
764
765 if node.test_spec.is_some() {
766 stats.total_tests += 1;
767 if let Some(ref spec) = node.test_spec {
768 if let Some(ref result) = spec.last_result {
769 if result.passed {
770 stats.passed_tests += 1;
771 } else {
772 stats.failed_tests += 1;
773 }
774 }
775 }
776 }
777
778 for child in &node.children {
779 traverse(child, stats, total_depth);
780 }
781 }
782
783 traverse(root, &mut stats, &mut total_depth);
784
785 stats.avg_depth = if stats.total_tasks > 0 {
786 total_depth as f64 / stats.total_tasks as f64
787 } else {
788 0.0
789 };
790
791 stats.progress_percentage = if stats.total_tasks > 0 {
792 ((stats.passed_tasks + stats.failed_tasks) as f64 / stats.total_tasks as f64) * 100.0
793 } else {
794 0.0
795 };
796
797 stats
798 }
799
800 pub async fn get_task_tree(&self, id: &str) -> Option<TaskTree> {
806 let trees = self.task_trees.read().await;
807 trees.get(id).cloned()
808 }
809
810 pub async fn get_current_task_tree(&self) -> Option<TaskTree> {
812 let current_id = self.current_tree_id.read().await;
813 if let Some(id) = current_id.as_ref() {
814 return self.get_task_tree(id).await;
815 }
816 None
817 }
818
819 pub async fn get_task_path(&self, tree_id: &str, task_id: &str) -> Vec<TaskNode> {
821 let trees = self.task_trees.read().await;
822 let tree = match trees.get(tree_id) {
823 Some(t) => t,
824 None => return Vec::new(),
825 };
826
827 let mut path = Vec::new();
828 Self::find_task_path(&tree.root, task_id, &mut path);
829 path
830 }
831
832 fn find_task_path(node: &TaskNode, task_id: &str, path: &mut Vec<TaskNode>) -> bool {
833 path.push(node.clone());
834
835 if node.id == task_id {
836 return true;
837 }
838
839 for child in &node.children {
840 if Self::find_task_path(child, task_id, path) {
841 return true;
842 }
843 }
844
845 path.pop();
846 false
847 }
848
849 pub async fn get_leaf_tasks(&self, tree_id: &str) -> Vec<TaskNode> {
851 let trees = self.task_trees.read().await;
852 let tree = match trees.get(tree_id) {
853 Some(t) => t,
854 None => return Vec::new(),
855 };
856
857 let mut leaves = Vec::new();
858 Self::collect_leaf_tasks(&tree.root, &mut leaves);
859 leaves
860 }
861
862 fn collect_leaf_tasks(node: &TaskNode, result: &mut Vec<TaskNode>) {
863 if node.children.is_empty() {
864 result.push(node.clone());
865 } else {
866 for child in &node.children {
867 Self::collect_leaf_tasks(child, result);
868 }
869 }
870 }
871}
872
873impl Default for TaskTreeManager {
874 fn default() -> Self {
875 Self::with_default_dir()
876 }
877}
878
879#[cfg(test)]
880mod tests {
881 use super::*;
882
883 #[tokio::test]
884 async fn test_generate_from_blueprint() {
885 let manager = TaskTreeManager::default();
886
887 let mut blueprint = Blueprint::new("测试项目".to_string(), "测试描述".to_string());
888
889 blueprint.modules.push(SystemModule {
890 id: Uuid::new_v4().to_string(),
891 name: "后端模块".to_string(),
892 description: "后端服务".to_string(),
893 module_type: ModuleType::Backend,
894 responsibilities: vec!["用户认证".to_string(), "数据存储".to_string()],
895 dependencies: Vec::new(),
896 interfaces: Vec::new(),
897 tech_stack: Some(vec!["Rust".to_string()]),
898 root_path: Some("src/backend".to_string()),
899 });
900
901 let tree = manager.generate_from_blueprint(&blueprint).await.unwrap();
902
903 assert_eq!(tree.blueprint_id, blueprint.id);
904 assert!(!tree.root.children.is_empty());
905 assert!(tree.stats.total_tasks > 0);
906 }
907
908 #[tokio::test]
909 async fn test_task_status_update() {
910 let manager = TaskTreeManager::default();
911
912 let blueprint = Blueprint::new("测试".to_string(), "描述".to_string());
913 let tree = manager.generate_from_blueprint(&blueprint).await.unwrap();
914
915 let leaves = manager.get_leaf_tasks(&tree.id).await;
917 if let Some(leaf) = leaves.first() {
918 let updated = manager
919 .update_task_status(&tree.id, &leaf.id, TaskStatus::Coding)
920 .await
921 .unwrap();
922
923 assert_eq!(updated.status, TaskStatus::Coding);
924 assert!(updated.started_at.is_some());
925 }
926 }
927}