1use super::types::{SystemModule, TaskNode, TaskTree};
16
17#[derive(Debug, Clone)]
23pub struct GranularityConfig {
24 pub min_task_complexity: f64,
26 pub max_task_complexity: f64,
28 pub ideal_task_duration: u32,
30 pub min_task_duration: u32,
32 pub max_task_duration: u32,
34 pub max_depth: u32,
36 pub min_depth: u32,
38 pub max_children_per_node: u32,
40 pub min_children_per_node: u32,
42 pub estimated_lines_per_task: u32,
44 pub max_lines_per_task: u32,
46 pub min_lines_per_task: u32,
48}
49
50impl Default for GranularityConfig {
51 fn default() -> Self {
52 Self {
53 min_task_complexity: 15.0,
54 max_task_complexity: 75.0,
55 ideal_task_duration: 30,
56 min_task_duration: 10,
57 max_task_duration: 120,
58 max_depth: 5,
59 min_depth: 2,
60 max_children_per_node: 10,
61 min_children_per_node: 2,
62 estimated_lines_per_task: 100,
63 max_lines_per_task: 300,
64 min_lines_per_task: 20,
65 }
66 }
67}
68
69#[derive(Debug, Clone, Default)]
75pub struct ComplexityFactors {
76 pub code_size: f64,
78 pub dependencies: f64,
80 pub interfaces: f64,
82 pub test_coverage: f64,
84 pub description_length: f64,
86 pub children_count: f64,
88}
89
90#[derive(Debug, Clone)]
92pub struct ComplexityWeights {
93 pub code_size: f64,
94 pub dependencies: f64,
95 pub interfaces: f64,
96 pub test_coverage: f64,
97 pub description_length: f64,
98 pub children_count: f64,
99}
100
101impl Default for ComplexityWeights {
102 fn default() -> Self {
103 Self {
104 code_size: 0.3,
105 dependencies: 0.2,
106 interfaces: 0.15,
107 test_coverage: 0.15,
108 description_length: 0.1,
109 children_count: 0.1,
110 }
111 }
112}
113
114#[derive(Debug, Clone, Default)]
116pub struct ComplexityDiagnostic {
117 pub estimated_lines: u32,
119 pub estimated_duration: u32,
121 pub has_dependencies: bool,
123 pub has_interfaces: bool,
125 pub has_tests: bool,
127 pub depth: u32,
129 pub children_count: usize,
131}
132
133#[derive(Debug, Clone)]
135pub struct ComplexityScore {
136 pub total: f64,
138 pub factors: ComplexityFactors,
140 pub weights: ComplexityWeights,
142 pub diagnostic: ComplexityDiagnostic,
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub enum SplitStrategy {
153 ByFunction,
155 ByLayer,
157 ByDependency,
159 ByInterface,
161}
162
163#[derive(Debug, Clone)]
165pub struct SuggestedSplit {
166 pub name: String,
167 pub description: String,
168 pub strategy: SplitStrategy,
169}
170
171#[derive(Debug, Clone)]
173pub struct SplitSuggestion {
174 pub task_id: String,
175 pub task_name: String,
176 pub reason: String,
177 pub complexity: f64,
178 pub suggested_splits: Vec<SuggestedSplit>,
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum MergeStrategy {
184 RelatedFunctions,
186 SimpleBatch,
188 SameFile,
190}
191
192#[derive(Debug, Clone)]
194pub struct MergeSuggestion {
195 pub task_ids: Vec<String>,
196 pub task_names: Vec<String>,
197 pub reason: String,
198 pub avg_complexity: f64,
199 pub suggested_name: String,
200 pub suggested_description: String,
201 pub strategy: MergeStrategy,
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
206pub enum IssueSeverity {
207 High,
208 Medium,
209 Low,
210}
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub enum IssueType {
215 TooDeep,
216 TooShallow,
217 TooManyChildren,
218 TooFewChildren,
219 Unbalanced,
220}
221
222#[derive(Debug, Clone)]
224pub struct StructureIssue {
225 pub issue_type: IssueType,
226 pub task_id: Option<String>,
227 pub task_name: Option<String>,
228 pub description: String,
229 pub severity: IssueSeverity,
230}
231
232#[derive(Debug, Clone, Default)]
234pub struct AdjustmentStats {
235 pub total_tasks: u32,
236 pub too_simple: u32,
237 pub too_complex: u32,
238 pub just_right: u32,
239 pub avg_complexity: f64,
240 pub avg_depth: f64,
241 pub max_depth: u32,
242 pub avg_children: f64,
243 pub max_children: u32,
244}
245
246#[derive(Debug, Clone, Default)]
248pub struct AdjustmentResult {
249 pub needs_adjustment: bool,
251 pub split_suggestions: Vec<SplitSuggestion>,
253 pub merge_suggestions: Vec<MergeSuggestion>,
255 pub stats: AdjustmentStats,
257 pub issues: Vec<StructureIssue>,
259}
260
261pub struct TaskGranularityController {
267 config: GranularityConfig,
268}
269
270impl TaskGranularityController {
271 pub fn new(config: GranularityConfig) -> Self {
273 Self { config }
274 }
275
276 pub fn update_config(&mut self, config: GranularityConfig) {
278 self.config = config;
279 }
280
281 pub fn config(&self) -> &GranularityConfig {
283 &self.config
284 }
285
286 pub fn assess_complexity(
292 &self,
293 task: &TaskNode,
294 module: Option<&SystemModule>,
295 ) -> ComplexityScore {
296 let factors = ComplexityFactors {
297 code_size: self.assess_code_size_factor(task, module),
298 dependencies: self.assess_dependencies_factor(task, module),
299 interfaces: self.assess_interfaces_factor(module),
300 test_coverage: self.assess_test_coverage_factor(task),
301 description_length: self.assess_description_length_factor(task),
302 children_count: self.assess_children_count_factor(task),
303 };
304
305 let weights = ComplexityWeights::default();
306 let total = factors.code_size * weights.code_size
307 + factors.dependencies * weights.dependencies
308 + factors.interfaces * weights.interfaces
309 + factors.test_coverage * weights.test_coverage
310 + factors.description_length * weights.description_length
311 + factors.children_count * weights.children_count;
312
313 let estimated_lines = self.estimate_code_lines(task, module);
314 let estimated_duration = self.estimate_duration(estimated_lines, &factors);
315
316 ComplexityScore {
317 total: (total * 100.0 * 100.0).round() / 100.0,
318 factors,
319 weights,
320 diagnostic: ComplexityDiagnostic {
321 estimated_lines,
322 estimated_duration,
323 has_dependencies: !task.dependencies.is_empty(),
324 has_interfaces: module.is_some_and(|m| !m.interfaces.is_empty()),
325 has_tests: !task.acceptance_tests.is_empty() || task.test_spec.is_some(),
326 depth: task.depth,
327 children_count: task.children.len(),
328 },
329 }
330 }
331
332 fn assess_code_size_factor(&self, task: &TaskNode, module: Option<&SystemModule>) -> f64 {
334 let estimated_lines = self.estimate_code_lines(task, module) as f64;
335 let normalized = estimated_lines / self.config.estimated_lines_per_task as f64;
336 (1.0 / (1.0 + (-2.0 * (normalized - 1.0)).exp())).min(1.0)
337 }
338
339 fn assess_dependencies_factor(&self, task: &TaskNode, module: Option<&SystemModule>) -> f64 {
341 let task_deps = task.dependencies.len();
342 let module_deps = module.map_or(0, |m| m.dependencies.len());
343 let total_deps = task_deps + module_deps;
344 (total_deps as f64 / 10.0).min(1.0)
345 }
346
347 fn assess_interfaces_factor(&self, module: Option<&SystemModule>) -> f64 {
349 module.map_or(0.0, |m| (m.interfaces.len() as f64 / 6.0).min(1.0))
350 }
351
352 fn assess_test_coverage_factor(&self, task: &TaskNode) -> f64 {
354 let test_factor = (task.acceptance_tests.len() as f64 / 6.0).min(1.0);
355 let has_test_spec = if task.test_spec.is_some() { 0.2 } else { 0.0 };
356 (test_factor + has_test_spec).min(1.0)
357 }
358
359 fn assess_description_length_factor(&self, task: &TaskNode) -> f64 {
361 (task.description.len() as f64 / 300.0).min(1.0)
362 }
363
364 fn assess_children_count_factor(&self, task: &TaskNode) -> f64 {
366 if task.children.is_empty() {
367 0.3
368 } else {
369 0.3 + (task.children.len() as f64 / 10.0 * 0.7).min(0.7)
370 }
371 }
372
373 fn estimate_code_lines(&self, task: &TaskNode, module: Option<&SystemModule>) -> u32 {
375 let mut base_lines = self.config.estimated_lines_per_task as f64;
376
377 if task.name.contains("设计") {
379 base_lines *= 0.3;
380 } else if task.name.contains("测试") {
381 base_lines *= 0.6;
382 } else if task.name.contains("实现") || task.name.contains("功能") {
383 base_lines *= 1.2;
384 } else if task.name.contains("接口") {
385 base_lines *= 0.8;
386 }
387
388 if let Some(m) = module {
390 match m.module_type {
391 super::types::ModuleType::Frontend => base_lines *= 1.3,
392 super::types::ModuleType::Backend => base_lines *= 1.1,
393 super::types::ModuleType::Database => base_lines *= 0.7,
394 _ => {}
395 }
396 }
397
398 let dep_multiplier = 1.0 + (task.dependencies.len() as f64 * 0.1);
400 base_lines *= dep_multiplier;
401
402 let desc_multiplier = (1.0 + task.description.len() as f64 / 1000.0).min(1.5);
404 base_lines *= desc_multiplier;
405
406 base_lines.round() as u32
407 }
408
409 fn estimate_duration(&self, estimated_lines: u32, factors: &ComplexityFactors) -> u32 {
411 let mut duration = estimated_lines as f64 / 10.0;
412 duration *= 1.0 + (factors.dependencies * 0.5);
413 duration *= 1.0 + (factors.interfaces * 0.3);
414 duration *= 1.0 + (factors.test_coverage * 0.4);
415 duration.round() as u32
416 }
417
418 pub fn should_split(&self, task: &TaskNode, module: Option<&SystemModule>) -> SplitCheck {
424 let score = self.assess_complexity(task, module);
425
426 if score.total > self.config.max_task_complexity {
428 return SplitCheck {
429 should_split: true,
430 reason: format!(
431 "任务复杂度过高({:.1} > {})",
432 score.total, self.config.max_task_complexity
433 ),
434 complexity: score.total,
435 };
436 }
437
438 if score.diagnostic.estimated_duration > self.config.max_task_duration {
440 return SplitCheck {
441 should_split: true,
442 reason: format!(
443 "估算执行时间过长({} 分钟 > {} 分钟)",
444 score.diagnostic.estimated_duration, self.config.max_task_duration
445 ),
446 complexity: score.total,
447 };
448 }
449
450 if task.children.len() as u32 > self.config.max_children_per_node {
452 return SplitCheck {
453 should_split: true,
454 reason: format!(
455 "子任务数量过多({} > {})",
456 task.children.len(),
457 self.config.max_children_per_node
458 ),
459 complexity: score.total,
460 };
461 }
462
463 if task.depth < self.config.min_depth && score.total > 50.0 && task.children.is_empty() {
465 return SplitCheck {
466 should_split: true,
467 reason: format!(
468 "任务深度不够且复杂度较高(depth={}, complexity={:.1})",
469 task.depth, score.total
470 ),
471 complexity: score.total,
472 };
473 }
474
475 SplitCheck {
476 should_split: false,
477 reason: "任务粒度合适".to_string(),
478 complexity: score.total,
479 }
480 }
481}
482
483#[derive(Debug, Clone)]
485pub struct SplitCheck {
486 pub should_split: bool,
487 pub reason: String,
488 pub complexity: f64,
489}
490
491impl TaskGranularityController {
492 pub fn should_merge(&self, tasks: &[TaskNode], modules: Option<&[SystemModule]>) -> MergeCheck {
494 if tasks.len() < 2 {
495 return MergeCheck {
496 should_merge: false,
497 reason: "任务数量不足 2 个".to_string(),
498 task_ids: Vec::new(),
499 };
500 }
501
502 let parent_ids: std::collections::HashSet<_> =
504 tasks.iter().filter_map(|t| t.parent_id.clone()).collect();
505 if parent_ids.len() > 1 {
506 return MergeCheck {
507 should_merge: false,
508 reason: "任务不是兄弟节点".to_string(),
509 task_ids: Vec::new(),
510 };
511 }
512
513 let complexities: Vec<_> = tasks
515 .iter()
516 .map(|t| {
517 let module = modules.and_then(|ms| {
518 ms.iter()
519 .find(|m| Some(&m.id) == t.blueprint_module_id.as_ref())
520 });
521 self.assess_complexity(t, module)
522 })
523 .collect();
524
525 let avg_complexity =
526 complexities.iter().map(|s| s.total).sum::<f64>() / complexities.len() as f64;
527
528 if avg_complexity < self.config.min_task_complexity {
530 let too_simple: Vec<_> = tasks
531 .iter()
532 .zip(complexities.iter())
533 .filter(|(_, s)| s.total < self.config.min_task_complexity)
534 .map(|(t, _)| t.id.clone())
535 .collect();
536
537 if too_simple.len() >= 2 {
538 return MergeCheck {
539 should_merge: true,
540 reason: format!(
541 "多个任务复杂度过低(平均 {:.1} < {})",
542 avg_complexity, self.config.min_task_complexity
543 ),
544 task_ids: too_simple,
545 };
546 }
547 }
548
549 if tasks.len() as u32 > self.config.max_children_per_node && avg_complexity < 30.0 {
551 return MergeCheck {
552 should_merge: true,
553 reason: format!(
554 "任务数量过多({} > {})且复杂度较低",
555 tasks.len(),
556 self.config.max_children_per_node
557 ),
558 task_ids: tasks.iter().map(|t| t.id.clone()).collect(),
559 };
560 }
561
562 MergeCheck {
563 should_merge: false,
564 reason: "任务粒度合适".to_string(),
565 task_ids: Vec::new(),
566 }
567 }
568}
569
570#[derive(Debug, Clone)]
572pub struct MergeCheck {
573 pub should_merge: bool,
574 pub reason: String,
575 pub task_ids: Vec<String>,
576}
577
578impl TaskGranularityController {
579 pub fn auto_adjust(
585 &self,
586 tree: &TaskTree,
587 modules: Option<&[SystemModule]>,
588 ) -> AdjustmentResult {
589 let mut result = AdjustmentResult::default();
590
591 let mut all_tasks = Vec::new();
593 self.collect_all_tasks(&tree.root, &mut all_tasks);
594
595 let mut total_complexity = 0.0;
596 let mut total_depth = 0u32;
597 let mut total_children = 0usize;
598
599 for task in &all_tasks {
600 let module = modules.and_then(|ms| {
601 ms.iter()
602 .find(|m| Some(&m.id) == task.blueprint_module_id.as_ref())
603 });
604 let complexity = self.assess_complexity(task, module);
605
606 total_complexity += complexity.total;
607 total_depth += task.depth;
608 total_children += task.children.len();
609
610 if complexity.total < self.config.min_task_complexity {
612 result.stats.too_simple += 1;
613 } else if complexity.total > self.config.max_task_complexity {
614 result.stats.too_complex += 1;
615 } else {
616 result.stats.just_right += 1;
617 }
618
619 if task.depth > result.stats.max_depth {
621 result.stats.max_depth = task.depth;
622 }
623 if task.children.len() as u32 > result.stats.max_children {
624 result.stats.max_children = task.children.len() as u32;
625 }
626
627 let split_check = self.should_split(task, module);
629 if split_check.should_split {
630 result
631 .split_suggestions
632 .push(self.generate_split_suggestion(task, module, &split_check));
633 }
634 }
635
636 let task_count = all_tasks.len() as f64;
638 result.stats.total_tasks = all_tasks.len() as u32;
639 result.stats.avg_complexity = total_complexity / task_count;
640 result.stats.avg_depth = total_depth as f64 / task_count;
641 result.stats.avg_children = total_children as f64 / task_count;
642
643 self.detect_structure_issues(&result.stats, &mut result.issues);
645
646 result.needs_adjustment = !result.split_suggestions.is_empty()
648 || !result.merge_suggestions.is_empty()
649 || result
650 .issues
651 .iter()
652 .any(|i| i.severity == IssueSeverity::High);
653
654 result
655 }
656
657 fn collect_all_tasks<'a>(&self, node: &'a TaskNode, result: &mut Vec<&'a TaskNode>) {
659 result.push(node);
660 for child in &node.children {
661 self.collect_all_tasks(child, result);
662 }
663 }
664
665 fn generate_split_suggestion(
667 &self,
668 task: &TaskNode,
669 module: Option<&SystemModule>,
670 split_check: &SplitCheck,
671 ) -> SplitSuggestion {
672 let mut suggested_splits = Vec::new();
673
674 if task.description.contains("和") || task.description.contains("及") {
676 suggested_splits.push(SuggestedSplit {
677 name: format!("{} - 功能A", task.name),
678 description: "拆分为独立的功能点".to_string(),
679 strategy: SplitStrategy::ByFunction,
680 });
681 suggested_splits.push(SuggestedSplit {
682 name: format!("{} - 功能B", task.name),
683 description: "拆分为独立的功能点".to_string(),
684 strategy: SplitStrategy::ByFunction,
685 });
686 }
687
688 if let Some(m) = module {
690 match m.module_type {
691 super::types::ModuleType::Frontend => {
692 suggested_splits.push(SuggestedSplit {
693 name: format!("{} - UI组件", task.name),
694 description: "实现用户界面组件".to_string(),
695 strategy: SplitStrategy::ByLayer,
696 });
697 suggested_splits.push(SuggestedSplit {
698 name: format!("{} - 业务逻辑", task.name),
699 description: "实现业务逻辑处理".to_string(),
700 strategy: SplitStrategy::ByLayer,
701 });
702 }
703 super::types::ModuleType::Backend => {
704 suggested_splits.push(SuggestedSplit {
705 name: format!("{} - API接口", task.name),
706 description: "实现 API 接口定义".to_string(),
707 strategy: SplitStrategy::ByLayer,
708 });
709 suggested_splits.push(SuggestedSplit {
710 name: format!("{} - 业务逻辑", task.name),
711 description: "实现核心业务逻辑".to_string(),
712 strategy: SplitStrategy::ByLayer,
713 });
714 }
715 _ => {}
716 }
717 }
718
719 if suggested_splits.is_empty() {
721 suggested_splits.push(SuggestedSplit {
722 name: format!("{} - 第一部分", task.name),
723 description: "拆分任务的第一部分".to_string(),
724 strategy: SplitStrategy::ByFunction,
725 });
726 suggested_splits.push(SuggestedSplit {
727 name: format!("{} - 第二部分", task.name),
728 description: "拆分任务的第二部分".to_string(),
729 strategy: SplitStrategy::ByFunction,
730 });
731 }
732
733 SplitSuggestion {
734 task_id: task.id.clone(),
735 task_name: task.name.clone(),
736 reason: split_check.reason.clone(),
737 complexity: split_check.complexity,
738 suggested_splits: suggested_splits.into_iter().take(5).collect(),
739 }
740 }
741
742 fn detect_structure_issues(&self, stats: &AdjustmentStats, issues: &mut Vec<StructureIssue>) {
744 if stats.max_depth > self.config.max_depth {
746 issues.push(StructureIssue {
747 issue_type: IssueType::TooDeep,
748 task_id: None,
749 task_name: None,
750 description: format!(
751 "任务树过深({} > {}),建议减少层级",
752 stats.max_depth, self.config.max_depth
753 ),
754 severity: IssueSeverity::High,
755 });
756 } else if stats.max_depth < self.config.min_depth {
757 issues.push(StructureIssue {
758 issue_type: IssueType::TooShallow,
759 task_id: None,
760 task_name: None,
761 description: format!(
762 "任务树过浅({} < {}),建议增加细化",
763 stats.max_depth, self.config.min_depth
764 ),
765 severity: IssueSeverity::Medium,
766 });
767 }
768
769 if stats.max_children > self.config.max_children_per_node {
771 issues.push(StructureIssue {
772 issue_type: IssueType::TooManyChildren,
773 task_id: None,
774 task_name: None,
775 description: format!(
776 "某些节点子任务过多(最多 {} > {})",
777 stats.max_children, self.config.max_children_per_node
778 ),
779 severity: IssueSeverity::High,
780 });
781 }
782
783 if stats.too_simple > stats.total_tasks * 30 / 100 {
785 issues.push(StructureIssue {
786 issue_type: IssueType::TooShallow,
787 task_id: None,
788 task_name: None,
789 description: format!(
790 "{} 个任务({}%)复杂度过低,建议合并",
791 stats.too_simple,
792 stats.too_simple * 100 / stats.total_tasks
793 ),
794 severity: IssueSeverity::High,
795 });
796 }
797
798 if stats.too_complex > stats.total_tasks * 20 / 100 {
799 issues.push(StructureIssue {
800 issue_type: IssueType::TooDeep,
801 task_id: None,
802 task_name: None,
803 description: format!(
804 "{} 个任务({}%)复杂度过高,建议拆分",
805 stats.too_complex,
806 stats.too_complex * 100 / stats.total_tasks
807 ),
808 severity: IssueSeverity::High,
809 });
810 }
811 }
812}
813
814impl Default for TaskGranularityController {
815 fn default() -> Self {
816 Self::new(GranularityConfig::default())
817 }
818}
819
820pub fn create_task_granularity_controller(
826 config: Option<GranularityConfig>,
827) -> TaskGranularityController {
828 TaskGranularityController::new(config.unwrap_or_default())
829}
830
831#[cfg(test)]
832mod tests {
833 use super::*;
834
835 #[test]
836 fn test_config_default() {
837 let config = GranularityConfig::default();
838 assert_eq!(config.min_task_complexity, 15.0);
839 assert_eq!(config.max_task_complexity, 75.0);
840 assert_eq!(config.ideal_task_duration, 30);
841 }
842
843 #[test]
844 fn test_complexity_weights_default() {
845 let weights = ComplexityWeights::default();
846 let total = weights.code_size
847 + weights.dependencies
848 + weights.interfaces
849 + weights.test_coverage
850 + weights.description_length
851 + weights.children_count;
852 assert!((total - 1.0).abs() < 0.001);
853 }
854
855 #[test]
856 fn test_assess_complexity() {
857 let controller = TaskGranularityController::default();
858 let task = TaskNode::new(
859 "测试任务".to_string(),
860 "这是一个测试任务描述".to_string(),
861 1,
862 );
863
864 let score = controller.assess_complexity(&task, None);
865
866 assert!(score.total >= 0.0);
867 assert!(score.total <= 100.0);
868 assert_eq!(score.diagnostic.depth, 1);
869 }
870
871 #[test]
872 fn test_should_split_simple_task() {
873 let controller = TaskGranularityController::default();
874 let task = TaskNode::new("简单任务".to_string(), "描述".to_string(), 2);
875
876 let check = controller.should_split(&task, None);
877
878 assert!(!check.should_split);
879 }
880
881 #[test]
882 fn test_should_merge_few_tasks() {
883 let controller = TaskGranularityController::default();
884 let task = TaskNode::new("任务".to_string(), "描述".to_string(), 1);
885
886 let check = controller.should_merge(&[task], None);
887
888 assert!(!check.should_merge);
889 assert_eq!(check.reason, "任务数量不足 2 个");
890 }
891}