1use serde::{Deserialize, Serialize};
10use std::path::{Path, PathBuf};
11use thiserror::Error;
12
13use super::{ExploreAgent, ExploreOptions, ThoroughnessLevel};
14
15pub type PlanResult<T> = Result<T, PlanError>;
17
18#[derive(Debug, Error)]
20pub enum PlanError {
21 #[error("Invalid task: {0}")]
23 InvalidTask(String),
24
25 #[error("File not found: {0}")]
27 FileNotFound(String),
28
29 #[error("Analysis error: {0}")]
31 AnalysisError(String),
32
33 #[error("IO error: {0}")]
35 Io(#[from] std::io::Error),
36
37 #[error("Explore error: {0}")]
39 ExploreError(String),
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
44#[serde(rename_all = "camelCase")]
45pub enum Complexity {
46 Trivial,
48
49 Low,
51
52 #[default]
54 Medium,
55
56 High,
58
59 VeryHigh,
61}
62
63impl Complexity {
64 pub fn hours_multiplier(&self) -> f32 {
66 match self {
67 Complexity::Trivial => 0.5,
68 Complexity::Low => 1.0,
69 Complexity::Medium => 2.0,
70 Complexity::High => 4.0,
71 Complexity::VeryHigh => 8.0,
72 }
73 }
74
75 pub fn description(&self) -> &'static str {
77 match self {
78 Complexity::Trivial => "Simple changes with minimal risk",
79 Complexity::Low => "Straightforward implementation",
80 Complexity::Medium => "Moderate complexity with some considerations",
81 Complexity::High => "Complex implementation with multiple components",
82 Complexity::VeryHigh => "Significant architectural changes required",
83 }
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
89#[serde(rename_all = "camelCase")]
90pub enum RiskSeverity {
91 Low,
93
94 #[default]
96 Medium,
97
98 High,
100
101 Critical,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
107#[serde(rename_all = "camelCase")]
108pub enum RiskCategory {
109 #[default]
111 Technical,
112
113 Security,
115
116 Performance,
118
119 Compatibility,
121
122 Dependency,
124
125 Testing,
127
128 Other(String),
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
134#[serde(rename_all = "camelCase")]
135pub struct Risk {
136 pub id: String,
138
139 pub description: String,
141
142 pub category: RiskCategory,
144
145 pub severity: RiskSeverity,
147
148 pub likelihood: f32,
150
151 pub impact: String,
153
154 pub mitigation: Vec<String>,
156
157 pub related_files: Vec<PathBuf>,
159}
160
161impl Risk {
162 pub fn new(
164 id: impl Into<String>,
165 description: impl Into<String>,
166 category: RiskCategory,
167 severity: RiskSeverity,
168 ) -> Self {
169 Self {
170 id: id.into(),
171 description: description.into(),
172 category,
173 severity,
174 likelihood: 0.5,
175 impact: String::new(),
176 mitigation: Vec::new(),
177 related_files: Vec::new(),
178 }
179 }
180
181 pub fn with_likelihood(mut self, likelihood: f32) -> Self {
183 self.likelihood = likelihood.clamp(0.0, 1.0);
184 self
185 }
186
187 pub fn with_impact(mut self, impact: impl Into<String>) -> Self {
189 self.impact = impact.into();
190 self
191 }
192
193 pub fn with_mitigation(mut self, mitigation: Vec<String>) -> Self {
195 self.mitigation = mitigation;
196 self
197 }
198
199 pub fn with_related_files(mut self, files: Vec<PathBuf>) -> Self {
201 self.related_files = files;
202 self
203 }
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
208#[serde(rename_all = "camelCase")]
209pub struct CriticalFile {
210 pub path: PathBuf,
212
213 pub reason: String,
215
216 pub modification_type: ModificationType,
218
219 pub priority: u8,
221
222 pub dependencies: Vec<PathBuf>,
224
225 pub estimated_changes: Option<usize>,
227}
228
229impl CriticalFile {
230 pub fn new(
232 path: impl Into<PathBuf>,
233 reason: impl Into<String>,
234 modification_type: ModificationType,
235 ) -> Self {
236 Self {
237 path: path.into(),
238 reason: reason.into(),
239 modification_type,
240 priority: 5,
241 dependencies: Vec::new(),
242 estimated_changes: None,
243 }
244 }
245
246 pub fn with_priority(mut self, priority: u8) -> Self {
248 self.priority = priority.min(10);
249 self
250 }
251
252 pub fn with_dependencies(mut self, deps: Vec<PathBuf>) -> Self {
254 self.dependencies = deps;
255 self
256 }
257
258 pub fn with_estimated_changes(mut self, changes: usize) -> Self {
260 self.estimated_changes = Some(changes);
261 self
262 }
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
267#[serde(rename_all = "camelCase")]
268pub enum ModificationType {
269 Create,
271
272 #[default]
274 Modify,
275
276 Delete,
278
279 Rename,
281
282 Review,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
288#[serde(rename_all = "camelCase")]
289pub struct PlanStep {
290 pub step_number: usize,
292
293 pub title: String,
295
296 pub description: String,
298
299 pub files: Vec<PathBuf>,
301
302 pub dependencies: Vec<usize>,
304
305 pub estimated_hours: Option<f32>,
307
308 pub optional: bool,
310
311 pub verification: Vec<String>,
313}
314
315impl PlanStep {
316 pub fn new(
318 step_number: usize,
319 title: impl Into<String>,
320 description: impl Into<String>,
321 ) -> Self {
322 Self {
323 step_number,
324 title: title.into(),
325 description: description.into(),
326 files: Vec::new(),
327 dependencies: Vec::new(),
328 estimated_hours: None,
329 optional: false,
330 verification: Vec::new(),
331 }
332 }
333
334 pub fn with_files(mut self, files: Vec<PathBuf>) -> Self {
336 self.files = files;
337 self
338 }
339
340 pub fn with_dependencies(mut self, deps: Vec<usize>) -> Self {
342 self.dependencies = deps;
343 self
344 }
345
346 pub fn with_estimated_hours(mut self, hours: f32) -> Self {
348 self.estimated_hours = Some(hours);
349 self
350 }
351
352 pub fn as_optional(mut self) -> Self {
354 self.optional = true;
355 self
356 }
357
358 pub fn with_verification(mut self, criteria: Vec<String>) -> Self {
360 self.verification = criteria;
361 self
362 }
363}
364
365#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
367#[serde(rename_all = "camelCase")]
368pub struct Alternative {
369 pub id: String,
371
372 pub name: String,
374
375 pub description: String,
377
378 pub pros: Vec<String>,
380
381 pub cons: Vec<String>,
383
384 pub complexity: Complexity,
386
387 pub estimated_hours: Option<f32>,
389
390 pub recommended: bool,
392}
393
394impl Alternative {
395 pub fn new(
397 id: impl Into<String>,
398 name: impl Into<String>,
399 description: impl Into<String>,
400 ) -> Self {
401 Self {
402 id: id.into(),
403 name: name.into(),
404 description: description.into(),
405 pros: Vec::new(),
406 cons: Vec::new(),
407 complexity: Complexity::Medium,
408 estimated_hours: None,
409 recommended: false,
410 }
411 }
412
413 pub fn with_pros(mut self, pros: Vec<String>) -> Self {
415 self.pros = pros;
416 self
417 }
418
419 pub fn with_cons(mut self, cons: Vec<String>) -> Self {
421 self.cons = cons;
422 self
423 }
424
425 pub fn with_complexity(mut self, complexity: Complexity) -> Self {
427 self.complexity = complexity;
428 self
429 }
430
431 pub fn with_estimated_hours(mut self, hours: f32) -> Self {
433 self.estimated_hours = Some(hours);
434 self
435 }
436
437 pub fn as_recommended(mut self) -> Self {
439 self.recommended = true;
440 self
441 }
442}
443
444#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
446#[serde(rename_all = "camelCase")]
447pub struct ArchitecturalDecision {
448 pub id: String,
450
451 pub title: String,
453
454 pub context: String,
456
457 pub decision: String,
459
460 pub rationale: String,
462
463 pub consequences: Vec<String>,
465
466 pub related_decisions: Vec<String>,
468}
469
470impl ArchitecturalDecision {
471 pub fn new(
473 id: impl Into<String>,
474 title: impl Into<String>,
475 decision: impl Into<String>,
476 ) -> Self {
477 Self {
478 id: id.into(),
479 title: title.into(),
480 context: String::new(),
481 decision: decision.into(),
482 rationale: String::new(),
483 consequences: Vec::new(),
484 related_decisions: Vec::new(),
485 }
486 }
487
488 pub fn with_context(mut self, context: impl Into<String>) -> Self {
490 self.context = context.into();
491 self
492 }
493
494 pub fn with_rationale(mut self, rationale: impl Into<String>) -> Self {
496 self.rationale = rationale.into();
497 self
498 }
499
500 pub fn with_consequences(mut self, consequences: Vec<String>) -> Self {
502 self.consequences = consequences;
503 self
504 }
505
506 pub fn with_related_decisions(mut self, related: Vec<String>) -> Self {
508 self.related_decisions = related;
509 self
510 }
511}
512
513#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
515#[serde(rename_all = "camelCase")]
516pub struct RequirementsAnalysis {
517 pub original_task: String,
519
520 pub functional_requirements: Vec<String>,
522
523 pub non_functional_requirements: Vec<String>,
525
526 pub assumptions: Vec<String>,
528
529 pub questions: Vec<String>,
531
532 pub scope: ScopeDefinition,
534}
535
536impl RequirementsAnalysis {
537 pub fn new(task: impl Into<String>) -> Self {
539 Self {
540 original_task: task.into(),
541 ..Default::default()
542 }
543 }
544
545 pub fn with_functional_requirements(mut self, reqs: Vec<String>) -> Self {
547 self.functional_requirements = reqs;
548 self
549 }
550
551 pub fn with_non_functional_requirements(mut self, reqs: Vec<String>) -> Self {
553 self.non_functional_requirements = reqs;
554 self
555 }
556
557 pub fn with_assumptions(mut self, assumptions: Vec<String>) -> Self {
559 self.assumptions = assumptions;
560 self
561 }
562
563 pub fn with_questions(mut self, questions: Vec<String>) -> Self {
565 self.questions = questions;
566 self
567 }
568
569 pub fn with_scope(mut self, scope: ScopeDefinition) -> Self {
571 self.scope = scope;
572 self
573 }
574}
575
576#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
578#[serde(rename_all = "camelCase")]
579pub struct ScopeDefinition {
580 pub in_scope: Vec<String>,
582
583 pub out_of_scope: Vec<String>,
585
586 pub future_considerations: Vec<String>,
588}
589
590impl ScopeDefinition {
591 pub fn new() -> Self {
593 Self::default()
594 }
595
596 pub fn with_in_scope(mut self, items: Vec<String>) -> Self {
598 self.in_scope = items;
599 self
600 }
601
602 pub fn with_out_of_scope(mut self, items: Vec<String>) -> Self {
604 self.out_of_scope = items;
605 self
606 }
607
608 pub fn with_future_considerations(mut self, items: Vec<String>) -> Self {
610 self.future_considerations = items;
611 self
612 }
613}
614
615#[derive(Debug, Clone, Serialize, Deserialize)]
617#[serde(rename_all = "camelCase")]
618pub struct PlanOptions {
619 pub task: String,
621
622 pub context: Option<String>,
624
625 pub constraints: Option<Vec<String>>,
627
628 pub existing_code: Option<Vec<PathBuf>>,
630
631 pub perspective: Option<String>,
633
634 pub thoroughness: ThoroughnessLevel,
636
637 pub working_directory: Option<PathBuf>,
639}
640
641impl Default for PlanOptions {
642 fn default() -> Self {
643 Self {
644 task: String::new(),
645 context: None,
646 constraints: None,
647 existing_code: None,
648 perspective: None,
649 thoroughness: ThoroughnessLevel::Medium,
650 working_directory: None,
651 }
652 }
653}
654
655impl PlanOptions {
656 pub fn new(task: impl Into<String>) -> Self {
658 Self {
659 task: task.into(),
660 ..Default::default()
661 }
662 }
663
664 pub fn with_context(mut self, context: impl Into<String>) -> Self {
666 self.context = Some(context.into());
667 self
668 }
669
670 pub fn with_constraints(mut self, constraints: Vec<String>) -> Self {
672 self.constraints = Some(constraints);
673 self
674 }
675
676 pub fn with_existing_code(mut self, paths: Vec<PathBuf>) -> Self {
678 self.existing_code = Some(paths);
679 self
680 }
681
682 pub fn with_perspective(mut self, perspective: impl Into<String>) -> Self {
684 self.perspective = Some(perspective.into());
685 self
686 }
687
688 pub fn with_thoroughness(mut self, level: ThoroughnessLevel) -> Self {
690 self.thoroughness = level;
691 self
692 }
693
694 pub fn with_working_directory(mut self, dir: impl Into<PathBuf>) -> Self {
696 self.working_directory = Some(dir.into());
697 self
698 }
699}
700
701#[derive(Debug, Clone, Serialize, Deserialize)]
703#[serde(rename_all = "camelCase")]
704pub struct PlanResultData {
705 pub summary: String,
707
708 pub requirements_analysis: RequirementsAnalysis,
710
711 pub architectural_decisions: Vec<ArchitecturalDecision>,
713
714 pub steps: Vec<PlanStep>,
716
717 pub critical_files: Vec<CriticalFile>,
719
720 pub risks: Vec<Risk>,
722
723 pub alternatives: Vec<Alternative>,
725
726 pub estimated_complexity: Complexity,
728
729 pub estimated_hours: Option<f32>,
731
732 pub recommendations: Option<Vec<String>>,
734}
735
736impl Default for PlanResultData {
737 fn default() -> Self {
738 Self {
739 summary: String::new(),
740 requirements_analysis: RequirementsAnalysis::default(),
741 architectural_decisions: Vec::new(),
742 steps: Vec::new(),
743 critical_files: Vec::new(),
744 risks: Vec::new(),
745 alternatives: Vec::new(),
746 estimated_complexity: Complexity::Medium,
747 estimated_hours: None,
748 recommendations: None,
749 }
750 }
751}
752
753impl PlanResultData {
754 pub fn new() -> Self {
756 Self::default()
757 }
758
759 pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
761 self.summary = summary.into();
762 self
763 }
764
765 pub fn with_requirements_analysis(mut self, analysis: RequirementsAnalysis) -> Self {
767 self.requirements_analysis = analysis;
768 self
769 }
770
771 pub fn with_architectural_decisions(mut self, decisions: Vec<ArchitecturalDecision>) -> Self {
773 self.architectural_decisions = decisions;
774 self
775 }
776
777 pub fn with_steps(mut self, steps: Vec<PlanStep>) -> Self {
779 self.steps = steps;
780 self
781 }
782
783 pub fn with_critical_files(mut self, files: Vec<CriticalFile>) -> Self {
785 self.critical_files = files;
786 self
787 }
788
789 pub fn with_risks(mut self, risks: Vec<Risk>) -> Self {
791 self.risks = risks;
792 self
793 }
794
795 pub fn with_alternatives(mut self, alternatives: Vec<Alternative>) -> Self {
797 self.alternatives = alternatives;
798 self
799 }
800
801 pub fn with_estimated_complexity(mut self, complexity: Complexity) -> Self {
803 self.estimated_complexity = complexity;
804 self
805 }
806
807 pub fn with_estimated_hours(mut self, hours: f32) -> Self {
809 self.estimated_hours = Some(hours);
810 self
811 }
812
813 pub fn with_recommendations(mut self, recommendations: Vec<String>) -> Self {
815 self.recommendations = Some(recommendations);
816 self
817 }
818
819 pub fn calculate_total_hours(&self) -> f32 {
821 self.steps.iter().filter_map(|s| s.estimated_hours).sum()
822 }
823}
824
825pub struct PlanAgent {
836 options: PlanOptions,
837 files_read: std::cell::RefCell<Vec<PathBuf>>,
839}
840
841impl PlanAgent {
842 pub fn new(options: PlanOptions) -> Self {
844 Self {
845 options,
846 files_read: std::cell::RefCell::new(Vec::new()),
847 }
848 }
849
850 pub fn options(&self) -> &PlanOptions {
852 &self.options
853 }
854
855 fn working_directory(&self) -> PathBuf {
857 self.options
858 .working_directory
859 .clone()
860 .unwrap_or_else(|| PathBuf::from("."))
861 }
862
863 pub fn files_read(&self) -> Vec<PathBuf> {
865 self.files_read.borrow().clone()
866 }
867
868 fn record_file_read(&self, path: &Path) {
870 self.files_read.borrow_mut().push(path.to_path_buf());
871 }
872
873 fn read_file_content(&self, path: &Path) -> PlanResult<String> {
875 if !path.exists() {
876 return Err(PlanError::FileNotFound(path.display().to_string()));
877 }
878 self.record_file_read(path);
879 std::fs::read_to_string(path).map_err(PlanError::from)
880 }
881
882 pub async fn create_plan(&self) -> PlanResult<PlanResultData> {
884 if self.options.task.trim().is_empty() {
885 return Err(PlanError::InvalidTask("Task cannot be empty".to_string()));
886 }
887
888 let requirements_analysis = self.analyze_requirements().await?;
890
891 let critical_files = self.identify_files().await?;
893
894 let risks = self.assess_risks().await?;
896
897 let alternatives = self.generate_alternatives().await?;
899
900 let steps = self.generate_steps(&critical_files, &risks);
902
903 let estimated_complexity = self.estimate_complexity(&critical_files, &risks);
905 let estimated_hours = self.estimate_hours(&steps, &estimated_complexity);
906
907 let architectural_decisions = self.generate_architectural_decisions(&requirements_analysis);
909
910 let summary = self.generate_summary(
912 &requirements_analysis,
913 &critical_files,
914 &risks,
915 &estimated_complexity,
916 );
917
918 let recommendations = self.generate_recommendations(&risks, &alternatives);
920
921 Ok(PlanResultData::new()
922 .with_summary(summary)
923 .with_requirements_analysis(requirements_analysis)
924 .with_architectural_decisions(architectural_decisions)
925 .with_steps(steps)
926 .with_critical_files(critical_files)
927 .with_risks(risks)
928 .with_alternatives(alternatives)
929 .with_estimated_complexity(estimated_complexity)
930 .with_estimated_hours(estimated_hours)
931 .with_recommendations(recommendations))
932 }
933
934 pub async fn analyze_requirements(&self) -> PlanResult<RequirementsAnalysis> {
936 let task = &self.options.task;
937
938 let functional_requirements = self.extract_functional_requirements(task);
940
941 let non_functional_requirements = self.extract_non_functional_requirements(task);
943
944 let assumptions = self.generate_assumptions(task);
946
947 let questions = self.generate_questions(task);
949
950 let scope = self.define_scope(task);
952
953 Ok(RequirementsAnalysis::new(task)
954 .with_functional_requirements(functional_requirements)
955 .with_non_functional_requirements(non_functional_requirements)
956 .with_assumptions(assumptions)
957 .with_questions(questions)
958 .with_scope(scope))
959 }
960
961 fn extract_functional_requirements(&self, task: &str) -> Vec<String> {
963 let mut requirements = Vec::new();
964 let task_lower = task.to_lowercase();
965
966 let action_patterns = [
968 ("implement", "Implement"),
969 ("create", "Create"),
970 ("add", "Add"),
971 ("build", "Build"),
972 ("develop", "Develop"),
973 ("support", "Support"),
974 ("enable", "Enable"),
975 ("allow", "Allow"),
976 ("provide", "Provide"),
977 ];
978
979 for (pattern, prefix) in action_patterns {
980 if task_lower.contains(pattern) {
981 requirements.push(format!("{} the requested functionality", prefix));
982 break;
983 }
984 }
985
986 if requirements.is_empty() {
988 requirements.push(format!("Complete: {}", task));
989 }
990
991 if let Some(context) = &self.options.context {
993 requirements.push(format!("Consider context: {}", context));
994 }
995
996 requirements
997 }
998
999 fn extract_non_functional_requirements(&self, task: &str) -> Vec<String> {
1001 let mut requirements = Vec::new();
1002 let task_lower = task.to_lowercase();
1003
1004 if task_lower.contains("fast")
1006 || task_lower.contains("performance")
1007 || task_lower.contains("efficient")
1008 {
1009 requirements.push("Ensure optimal performance".to_string());
1010 }
1011
1012 if task_lower.contains("secure")
1014 || task_lower.contains("security")
1015 || task_lower.contains("auth")
1016 {
1017 requirements.push("Implement security best practices".to_string());
1018 }
1019
1020 if task_lower.contains("scale") || task_lower.contains("scalable") {
1022 requirements.push("Design for scalability".to_string());
1023 }
1024
1025 if task_lower.contains("test") || task_lower.contains("testing") {
1027 requirements.push("Include comprehensive tests".to_string());
1028 }
1029
1030 if let Some(perspective) = &self.options.perspective {
1032 requirements.push(format!("Focus on {} aspects", perspective));
1033 }
1034
1035 if let Some(constraints) = &self.options.constraints {
1037 for constraint in constraints {
1038 requirements.push(format!("Constraint: {}", constraint));
1039 }
1040 }
1041
1042 requirements
1043 }
1044
1045 fn generate_assumptions(&self, task: &str) -> Vec<String> {
1047 let mut assumptions = Vec::new();
1048
1049 assumptions.push("Existing codebase follows established patterns".to_string());
1051 assumptions.push("Required dependencies are available".to_string());
1052
1053 if task.to_lowercase().contains("api") {
1055 assumptions.push("API follows RESTful conventions".to_string());
1056 }
1057
1058 if task.to_lowercase().contains("database") || task.to_lowercase().contains("db") {
1059 assumptions.push("Database schema can be modified if needed".to_string());
1060 }
1061
1062 assumptions
1063 }
1064
1065 fn generate_questions(&self, task: &str) -> Vec<String> {
1067 let mut questions = Vec::new();
1068
1069 match self.options.thoroughness {
1071 ThoroughnessLevel::VeryThorough => {
1072 questions.push("What are the expected performance requirements?".to_string());
1073 questions.push("Are there any specific security considerations?".to_string());
1074 questions.push("What is the expected timeline for completion?".to_string());
1075 }
1076 ThoroughnessLevel::Medium => {
1077 questions.push("Are there any specific constraints to consider?".to_string());
1078 }
1079 ThoroughnessLevel::Quick => {}
1080 }
1081
1082 if task.to_lowercase().contains("integration") {
1084 questions.push("What external systems need to be integrated?".to_string());
1085 }
1086
1087 questions
1088 }
1089
1090 fn define_scope(&self, task: &str) -> ScopeDefinition {
1092 let mut in_scope = vec![task.to_string()];
1093 let mut out_of_scope = Vec::new();
1094 let mut future_considerations = Vec::new();
1095
1096 if let Some(context) = &self.options.context {
1098 in_scope.push(context.clone());
1099 }
1100
1101 out_of_scope.push("Major architectural changes unless required".to_string());
1103 out_of_scope.push("Unrelated feature modifications".to_string());
1104
1105 future_considerations.push("Performance optimization opportunities".to_string());
1107 future_considerations.push("Additional feature enhancements".to_string());
1108
1109 ScopeDefinition::new()
1110 .with_in_scope(in_scope)
1111 .with_out_of_scope(out_of_scope)
1112 .with_future_considerations(future_considerations)
1113 }
1114}
1115
1116impl PlanAgent {
1117 pub async fn identify_files(&self) -> PlanResult<Vec<CriticalFile>> {
1119 let mut critical_files = Vec::new();
1120 let working_dir = self.working_directory();
1121
1122 if let Some(existing_paths) = &self.options.existing_code {
1124 for path in existing_paths {
1125 let full_path = if path.is_absolute() {
1126 path.clone()
1127 } else {
1128 working_dir.join(path)
1129 };
1130
1131 if full_path.exists() {
1132 if let Ok(_content) = self.read_file_content(&full_path) {
1134 let file = CriticalFile::new(
1135 path.clone(),
1136 "Specified in existing code paths",
1137 ModificationType::Modify,
1138 )
1139 .with_priority(8);
1140 critical_files.push(file);
1141 }
1142 }
1143 }
1144 }
1145
1146 let keywords = self.extract_keywords(&self.options.task);
1148 if !keywords.is_empty() && working_dir.exists() {
1149 let explore_options = ExploreOptions::new(keywords.join(" "))
1150 .with_target_path(&working_dir)
1151 .with_thoroughness(self.options.thoroughness)
1152 .with_max_results(self.options.thoroughness.max_files() / 4);
1153
1154 let explore_agent = ExploreAgent::new(explore_options);
1155 if let Ok(result) = explore_agent.explore().await {
1156 for file_path in result.files.iter().take(10) {
1157 self.record_file_read(file_path);
1159
1160 let file = CriticalFile::new(
1161 file_path.clone(),
1162 "Found via keyword search",
1163 ModificationType::Review,
1164 )
1165 .with_priority(5);
1166
1167 if !critical_files.iter().any(|f| f.path == file.path) {
1169 critical_files.push(file);
1170 }
1171 }
1172 }
1173 }
1174
1175 critical_files.sort_by(|a, b| b.priority.cmp(&a.priority));
1177
1178 Ok(critical_files)
1179 }
1180
1181 fn extract_keywords(&self, task: &str) -> Vec<String> {
1183 let stop_words = [
1184 "the", "a", "an", "is", "are", "was", "were", "be", "been", "being", "have", "has",
1185 "had", "do", "does", "did", "will", "would", "could", "should", "may", "might", "must",
1186 "shall", "can", "need", "to", "of", "in", "for", "on", "with", "at", "by", "from",
1187 "as", "into", "through", "and", "or", "but", "if", "then", "else", "when", "where",
1188 "why", "how", "all", "each", "every", "both", "few", "more", "most", "other", "some",
1189 "such", "no", "not", "only", "own", "same", "so", "than", "too", "very", "just",
1190 "also", "now", "here", "there", "this", "that", "these", "those",
1191 ];
1192
1193 task.split_whitespace()
1194 .map(|w| w.to_lowercase())
1195 .map(|w| w.trim_matches(|c: char| !c.is_alphanumeric()).to_string())
1196 .filter(|w| w.len() > 2 && !stop_words.contains(&w.as_str()))
1197 .take(5)
1198 .collect()
1199 }
1200
1201 pub async fn assess_risks(&self) -> PlanResult<Vec<Risk>> {
1203 let mut risks = Vec::new();
1204 let task_lower = self.options.task.to_lowercase();
1205
1206 if task_lower.contains("refactor") || task_lower.contains("rewrite") {
1208 risks.push(
1209 Risk::new(
1210 "R001",
1211 "Refactoring may introduce regressions",
1212 RiskCategory::Technical,
1213 RiskSeverity::Medium,
1214 )
1215 .with_likelihood(0.4)
1216 .with_impact("Existing functionality may break")
1217 .with_mitigation(vec![
1218 "Write comprehensive tests before refactoring".to_string(),
1219 "Refactor in small, incremental steps".to_string(),
1220 "Use feature flags for gradual rollout".to_string(),
1221 ]),
1222 );
1223 }
1224
1225 if task_lower.contains("auth")
1227 || task_lower.contains("security")
1228 || task_lower.contains("password")
1229 {
1230 risks.push(
1231 Risk::new(
1232 "R002",
1233 "Security implementation may have vulnerabilities",
1234 RiskCategory::Security,
1235 RiskSeverity::High,
1236 )
1237 .with_likelihood(0.3)
1238 .with_impact("Potential security breach")
1239 .with_mitigation(vec![
1240 "Follow security best practices".to_string(),
1241 "Conduct security review".to_string(),
1242 "Use established security libraries".to_string(),
1243 ]),
1244 );
1245 }
1246
1247 if task_lower.contains("performance")
1249 || task_lower.contains("optimize")
1250 || task_lower.contains("scale")
1251 {
1252 risks.push(
1253 Risk::new(
1254 "R003",
1255 "Performance improvements may not meet targets",
1256 RiskCategory::Performance,
1257 RiskSeverity::Medium,
1258 )
1259 .with_likelihood(0.3)
1260 .with_impact("System may not meet performance requirements")
1261 .with_mitigation(vec![
1262 "Establish baseline metrics".to_string(),
1263 "Profile before and after changes".to_string(),
1264 "Set clear performance targets".to_string(),
1265 ]),
1266 );
1267 }
1268
1269 if task_lower.contains("api")
1271 || task_lower.contains("interface")
1272 || task_lower.contains("breaking")
1273 {
1274 risks.push(
1275 Risk::new(
1276 "R004",
1277 "API changes may break existing clients",
1278 RiskCategory::Compatibility,
1279 RiskSeverity::High,
1280 )
1281 .with_likelihood(0.4)
1282 .with_impact("Existing integrations may fail")
1283 .with_mitigation(vec![
1284 "Version the API".to_string(),
1285 "Provide migration guide".to_string(),
1286 "Maintain backward compatibility where possible".to_string(),
1287 ]),
1288 );
1289 }
1290
1291 if task_lower.contains("dependency")
1293 || task_lower.contains("upgrade")
1294 || task_lower.contains("library")
1295 {
1296 risks.push(
1297 Risk::new(
1298 "R005",
1299 "Dependency changes may cause conflicts",
1300 RiskCategory::Dependency,
1301 RiskSeverity::Medium,
1302 )
1303 .with_likelihood(0.3)
1304 .with_impact("Build or runtime failures")
1305 .with_mitigation(vec![
1306 "Test dependency updates in isolation".to_string(),
1307 "Review changelogs for breaking changes".to_string(),
1308 "Pin dependency versions".to_string(),
1309 ]),
1310 );
1311 }
1312
1313 if task_lower.contains("test") || task_lower.contains("coverage") {
1315 risks.push(
1316 Risk::new(
1317 "R006",
1318 "Test coverage may be insufficient",
1319 RiskCategory::Testing,
1320 RiskSeverity::Low,
1321 )
1322 .with_likelihood(0.2)
1323 .with_impact("Bugs may go undetected")
1324 .with_mitigation(vec![
1325 "Set coverage targets".to_string(),
1326 "Include edge cases in tests".to_string(),
1327 "Use property-based testing".to_string(),
1328 ]),
1329 );
1330 }
1331
1332 if risks.is_empty() {
1334 risks.push(
1335 Risk::new(
1336 "R000",
1337 "General implementation risk",
1338 RiskCategory::Technical,
1339 RiskSeverity::Low,
1340 )
1341 .with_likelihood(0.2)
1342 .with_impact("Minor issues during implementation")
1343 .with_mitigation(vec![
1344 "Follow coding standards".to_string(),
1345 "Review code before merging".to_string(),
1346 ]),
1347 );
1348 }
1349
1350 risks.sort_by(|a, b| {
1352 let severity_order = |s: &RiskSeverity| match s {
1353 RiskSeverity::Critical => 0,
1354 RiskSeverity::High => 1,
1355 RiskSeverity::Medium => 2,
1356 RiskSeverity::Low => 3,
1357 };
1358 severity_order(&a.severity).cmp(&severity_order(&b.severity))
1359 });
1360
1361 Ok(risks)
1362 }
1363}
1364
1365impl PlanAgent {
1366 pub async fn generate_alternatives(&self) -> PlanResult<Vec<Alternative>> {
1368 let mut alternatives = Vec::new();
1369 let task_lower = self.options.task.to_lowercase();
1370
1371 let standard = Alternative::new(
1373 "ALT001",
1374 "Standard Implementation",
1375 "Implement the feature using conventional patterns and practices",
1376 )
1377 .with_pros(vec![
1378 "Well-understood approach".to_string(),
1379 "Easier to maintain".to_string(),
1380 "Lower risk".to_string(),
1381 ])
1382 .with_cons(vec![
1383 "May not be optimal for all cases".to_string(),
1384 "Could be slower to implement".to_string(),
1385 ])
1386 .with_complexity(Complexity::Medium)
1387 .as_recommended();
1388
1389 alternatives.push(standard);
1390
1391 if task_lower.contains("refactor")
1393 || task_lower.contains("migrate")
1394 || task_lower.contains("upgrade")
1395 {
1396 let incremental = Alternative::new(
1397 "ALT002",
1398 "Incremental Migration",
1399 "Implement changes gradually with feature flags and parallel systems",
1400 )
1401 .with_pros(vec![
1402 "Lower risk of breaking changes".to_string(),
1403 "Easier rollback".to_string(),
1404 "Can validate at each step".to_string(),
1405 ])
1406 .with_cons(vec![
1407 "Takes longer to complete".to_string(),
1408 "Temporary complexity during transition".to_string(),
1409 ])
1410 .with_complexity(Complexity::High);
1411
1412 alternatives.push(incremental);
1413 }
1414
1415 if task_lower.contains("performance")
1417 || task_lower.contains("optimize")
1418 || task_lower.contains("fast")
1419 {
1420 let performance = Alternative::new(
1421 "ALT003",
1422 "Performance-Optimized",
1423 "Focus on performance from the start with optimized data structures and algorithms",
1424 )
1425 .with_pros(vec![
1426 "Better performance outcomes".to_string(),
1427 "Scalable from the start".to_string(),
1428 ])
1429 .with_cons(vec![
1430 "More complex implementation".to_string(),
1431 "May be premature optimization".to_string(),
1432 ])
1433 .with_complexity(Complexity::High);
1434
1435 alternatives.push(performance);
1436 }
1437
1438 let minimal = Alternative::new(
1440 "ALT004",
1441 "Minimal Viable Implementation",
1442 "Implement only the core functionality with minimal features",
1443 )
1444 .with_pros(vec![
1445 "Fastest to implement".to_string(),
1446 "Lower initial complexity".to_string(),
1447 "Quick feedback loop".to_string(),
1448 ])
1449 .with_cons(vec![
1450 "May need significant expansion later".to_string(),
1451 "Could accumulate technical debt".to_string(),
1452 ])
1453 .with_complexity(Complexity::Low);
1454
1455 alternatives.push(minimal);
1456
1457 Ok(alternatives)
1458 }
1459
1460 fn generate_steps(&self, critical_files: &[CriticalFile], risks: &[Risk]) -> Vec<PlanStep> {
1462 let mut steps = Vec::new();
1463 let mut step_number = 1;
1464
1465 steps.push(
1467 PlanStep::new(
1468 step_number,
1469 "Analysis and Preparation",
1470 "Review existing code and understand the current implementation",
1471 )
1472 .with_files(critical_files.iter().map(|f| f.path.clone()).collect())
1473 .with_estimated_hours(1.0)
1474 .with_verification(vec![
1475 "Understand current architecture".to_string(),
1476 "Identify integration points".to_string(),
1477 ]),
1478 );
1479 step_number += 1;
1480
1481 steps.push(
1483 PlanStep::new(
1484 step_number,
1485 "Design",
1486 "Create detailed design for the implementation",
1487 )
1488 .with_dependencies(vec![1])
1489 .with_estimated_hours(2.0)
1490 .with_verification(vec![
1491 "Design document reviewed".to_string(),
1492 "Edge cases identified".to_string(),
1493 ]),
1494 );
1495 step_number += 1;
1496
1497 let core_files: Vec<PathBuf> = critical_files
1499 .iter()
1500 .filter(|f| f.modification_type != ModificationType::Review)
1501 .map(|f| f.path.clone())
1502 .collect();
1503
1504 steps.push(
1505 PlanStep::new(
1506 step_number,
1507 "Core Implementation",
1508 "Implement the main functionality",
1509 )
1510 .with_files(core_files)
1511 .with_dependencies(vec![2])
1512 .with_estimated_hours(4.0)
1513 .with_verification(vec![
1514 "Core functionality works".to_string(),
1515 "Code compiles without errors".to_string(),
1516 ]),
1517 );
1518 step_number += 1;
1519
1520 steps.push(
1522 PlanStep::new(
1523 step_number,
1524 "Testing",
1525 "Write and run tests for the implementation",
1526 )
1527 .with_dependencies(vec![3])
1528 .with_estimated_hours(2.0)
1529 .with_verification(vec![
1530 "Unit tests pass".to_string(),
1531 "Integration tests pass".to_string(),
1532 "Edge cases covered".to_string(),
1533 ]),
1534 );
1535 step_number += 1;
1536
1537 let high_risks: Vec<&Risk> = risks
1539 .iter()
1540 .filter(|r| matches!(r.severity, RiskSeverity::High | RiskSeverity::Critical))
1541 .collect();
1542
1543 if !high_risks.is_empty() {
1544 let mitigation_desc = high_risks
1545 .iter()
1546 .map(|r| format!("- {}: {}", r.id, r.description))
1547 .collect::<Vec<_>>()
1548 .join("\n");
1549
1550 steps.push(
1551 PlanStep::new(
1552 step_number,
1553 "Risk Mitigation",
1554 format!(
1555 "Address identified high-severity risks:\n{}",
1556 mitigation_desc
1557 ),
1558 )
1559 .with_dependencies(vec![3])
1560 .with_estimated_hours(2.0)
1561 .with_verification(
1562 high_risks
1563 .iter()
1564 .map(|r| format!("Risk {} mitigated", r.id))
1565 .collect(),
1566 ),
1567 );
1568 step_number += 1;
1569 }
1570
1571 steps.push(
1573 PlanStep::new(
1574 step_number,
1575 "Documentation",
1576 "Update documentation and add code comments",
1577 )
1578 .with_dependencies(vec![step_number - 1])
1579 .with_estimated_hours(1.0)
1580 .as_optional()
1581 .with_verification(vec![
1582 "README updated".to_string(),
1583 "API documentation complete".to_string(),
1584 ]),
1585 );
1586 step_number += 1;
1587
1588 steps.push(
1590 PlanStep::new(
1591 step_number,
1592 "Review and Finalization",
1593 "Code review and final adjustments",
1594 )
1595 .with_dependencies(vec![step_number - 1])
1596 .with_estimated_hours(1.0)
1597 .with_verification(vec![
1598 "Code review completed".to_string(),
1599 "All feedback addressed".to_string(),
1600 ]),
1601 );
1602
1603 steps
1604 }
1605
1606 fn estimate_complexity(&self, critical_files: &[CriticalFile], risks: &[Risk]) -> Complexity {
1608 let file_count = critical_files.len();
1609 let high_risk_count = risks
1610 .iter()
1611 .filter(|r| matches!(r.severity, RiskSeverity::High | RiskSeverity::Critical))
1612 .count();
1613
1614 let mut score = 0;
1616
1617 score += match file_count {
1619 0..=2 => 1,
1620 3..=5 => 2,
1621 6..=10 => 3,
1622 11..=20 => 4,
1623 _ => 5,
1624 };
1625
1626 score += match high_risk_count {
1628 0 => 0,
1629 1 => 1,
1630 2..=3 => 2,
1631 _ => 3,
1632 };
1633
1634 score += match self.options.thoroughness {
1636 ThoroughnessLevel::Quick => 0,
1637 ThoroughnessLevel::Medium => 1,
1638 ThoroughnessLevel::VeryThorough => 2,
1639 };
1640
1641 match score {
1643 0..=2 => Complexity::Trivial,
1644 3..=4 => Complexity::Low,
1645 5..=6 => Complexity::Medium,
1646 7..=8 => Complexity::High,
1647 _ => Complexity::VeryHigh,
1648 }
1649 }
1650
1651 fn estimate_hours(&self, steps: &[PlanStep], complexity: &Complexity) -> f32 {
1653 let base_hours: f32 = steps.iter().filter_map(|s| s.estimated_hours).sum();
1654 base_hours * complexity.hours_multiplier()
1655 }
1656
1657 fn generate_architectural_decisions(
1659 &self,
1660 requirements: &RequirementsAnalysis,
1661 ) -> Vec<ArchitecturalDecision> {
1662 let mut decisions = Vec::new();
1663
1664 if !requirements.functional_requirements.is_empty() {
1666 decisions.push(
1667 ArchitecturalDecision::new(
1668 "AD001",
1669 "Implementation Approach",
1670 "Use modular design with clear separation of concerns",
1671 )
1672 .with_context("Need to implement new functionality while maintaining code quality")
1673 .with_rationale("Modular design allows for easier testing and maintenance")
1674 .with_consequences(vec![
1675 "Code will be more maintainable".to_string(),
1676 "May require additional abstraction layers".to_string(),
1677 ]),
1678 );
1679 }
1680
1681 if let Some(constraints) = &self.options.constraints {
1683 if !constraints.is_empty() {
1684 decisions.push(
1685 ArchitecturalDecision::new(
1686 "AD002",
1687 "Constraint Handling",
1688 format!(
1689 "Design to accommodate constraints: {}",
1690 constraints.join(", ")
1691 ),
1692 )
1693 .with_context("Implementation must work within specified constraints")
1694 .with_rationale("Constraints define the boundaries of acceptable solutions"),
1695 );
1696 }
1697 }
1698
1699 decisions
1700 }
1701
1702 fn generate_summary(
1704 &self,
1705 requirements: &RequirementsAnalysis,
1706 critical_files: &[CriticalFile],
1707 risks: &[Risk],
1708 complexity: &Complexity,
1709 ) -> String {
1710 let mut summary = String::new();
1711
1712 summary.push_str(&format!("# Implementation Plan: {}\n\n", self.options.task));
1713
1714 summary.push_str(&format!(
1715 "## Overview\n\nThis plan addresses {} functional requirements and {} non-functional requirements.\n\n",
1716 requirements.functional_requirements.len(),
1717 requirements.non_functional_requirements.len()
1718 ));
1719
1720 summary.push_str(&format!(
1721 "## Scope\n\n- {} critical files identified\n- {} risks assessed\n- Complexity: {:?} ({})\n\n",
1722 critical_files.len(),
1723 risks.len(),
1724 complexity,
1725 complexity.description()
1726 ));
1727
1728 if !risks.is_empty() {
1729 let high_risks = risks
1730 .iter()
1731 .filter(|r| matches!(r.severity, RiskSeverity::High | RiskSeverity::Critical))
1732 .count();
1733 if high_risks > 0 {
1734 summary.push_str(&format!(
1735 "## Risk Summary\n\n⚠️ {} high-severity risks identified that require attention.\n\n",
1736 high_risks
1737 ));
1738 }
1739 }
1740
1741 summary
1742 }
1743
1744 fn generate_recommendations(
1746 &self,
1747 risks: &[Risk],
1748 alternatives: &[Alternative],
1749 ) -> Vec<String> {
1750 let mut recommendations = Vec::new();
1751
1752 for risk in risks
1754 .iter()
1755 .filter(|r| matches!(r.severity, RiskSeverity::High | RiskSeverity::Critical))
1756 {
1757 recommendations.push(format!(
1758 "Address {} before proceeding: {}",
1759 risk.id, risk.description
1760 ));
1761 }
1762
1763 if let Some(recommended) = alternatives.iter().find(|a| a.recommended) {
1765 recommendations.push(format!(
1766 "Consider using '{}' approach: {}",
1767 recommended.name, recommended.description
1768 ));
1769 }
1770
1771 recommendations.push("Review the plan with stakeholders before implementation".to_string());
1773 recommendations.push("Set up monitoring for the implementation progress".to_string());
1774
1775 recommendations
1776 }
1777}
1778
1779#[cfg(test)]
1780mod tests {
1781 use super::*;
1782 use std::fs;
1783 use tempfile::TempDir;
1784
1785 #[test]
1786 fn test_complexity_hours_multiplier() {
1787 assert_eq!(Complexity::Trivial.hours_multiplier(), 0.5);
1788 assert_eq!(Complexity::Low.hours_multiplier(), 1.0);
1789 assert_eq!(Complexity::Medium.hours_multiplier(), 2.0);
1790 assert_eq!(Complexity::High.hours_multiplier(), 4.0);
1791 assert_eq!(Complexity::VeryHigh.hours_multiplier(), 8.0);
1792 }
1793
1794 #[test]
1795 fn test_risk_creation() {
1796 let risk = Risk::new(
1797 "R001",
1798 "Test risk",
1799 RiskCategory::Technical,
1800 RiskSeverity::High,
1801 )
1802 .with_likelihood(0.7)
1803 .with_impact("High impact")
1804 .with_mitigation(vec!["Mitigation 1".to_string()]);
1805
1806 assert_eq!(risk.id, "R001");
1807 assert_eq!(risk.description, "Test risk");
1808 assert_eq!(risk.likelihood, 0.7);
1809 assert_eq!(risk.impact, "High impact");
1810 assert_eq!(risk.mitigation.len(), 1);
1811 }
1812
1813 #[test]
1814 fn test_risk_likelihood_clamping() {
1815 let risk = Risk::new("R001", "Test", RiskCategory::Technical, RiskSeverity::Low)
1816 .with_likelihood(1.5);
1817 assert_eq!(risk.likelihood, 1.0);
1818
1819 let risk = Risk::new("R002", "Test", RiskCategory::Technical, RiskSeverity::Low)
1820 .with_likelihood(-0.5);
1821 assert_eq!(risk.likelihood, 0.0);
1822 }
1823
1824 #[test]
1825 fn test_critical_file_creation() {
1826 let file = CriticalFile::new("src/main.rs", "Main entry point", ModificationType::Modify)
1827 .with_priority(9)
1828 .with_estimated_changes(50);
1829
1830 assert_eq!(file.path, PathBuf::from("src/main.rs"));
1831 assert_eq!(file.reason, "Main entry point");
1832 assert_eq!(file.priority, 9);
1833 assert_eq!(file.estimated_changes, Some(50));
1834 }
1835
1836 #[test]
1837 fn test_critical_file_priority_clamping() {
1838 let file = CriticalFile::new("test.rs", "Test", ModificationType::Create).with_priority(15);
1839 assert_eq!(file.priority, 10);
1840 }
1841
1842 #[test]
1843 fn test_plan_step_creation() {
1844 let step = PlanStep::new(1, "Step 1", "Description")
1845 .with_files(vec![PathBuf::from("file.rs")])
1846 .with_dependencies(vec![])
1847 .with_estimated_hours(2.0)
1848 .with_verification(vec!["Test passes".to_string()]);
1849
1850 assert_eq!(step.step_number, 1);
1851 assert_eq!(step.title, "Step 1");
1852 assert_eq!(step.files.len(), 1);
1853 assert_eq!(step.estimated_hours, Some(2.0));
1854 assert!(!step.optional);
1855 }
1856
1857 #[test]
1858 fn test_plan_step_optional() {
1859 let step = PlanStep::new(1, "Optional Step", "Description").as_optional();
1860 assert!(step.optional);
1861 }
1862
1863 #[test]
1864 fn test_alternative_creation() {
1865 let alt = Alternative::new("ALT001", "Standard", "Standard approach")
1866 .with_pros(vec!["Pro 1".to_string()])
1867 .with_cons(vec!["Con 1".to_string()])
1868 .with_complexity(Complexity::Low)
1869 .as_recommended();
1870
1871 assert_eq!(alt.id, "ALT001");
1872 assert!(alt.recommended);
1873 assert_eq!(alt.complexity, Complexity::Low);
1874 }
1875
1876 #[test]
1877 fn test_architectural_decision_creation() {
1878 let decision = ArchitecturalDecision::new("AD001", "Title", "Decision")
1879 .with_context("Context")
1880 .with_rationale("Rationale")
1881 .with_consequences(vec!["Consequence".to_string()]);
1882
1883 assert_eq!(decision.id, "AD001");
1884 assert_eq!(decision.context, "Context");
1885 assert_eq!(decision.rationale, "Rationale");
1886 }
1887
1888 #[test]
1889 fn test_requirements_analysis_creation() {
1890 let analysis = RequirementsAnalysis::new("Test task")
1891 .with_functional_requirements(vec!["FR1".to_string()])
1892 .with_non_functional_requirements(vec!["NFR1".to_string()])
1893 .with_assumptions(vec!["Assumption".to_string()]);
1894
1895 assert_eq!(analysis.original_task, "Test task");
1896 assert_eq!(analysis.functional_requirements.len(), 1);
1897 assert_eq!(analysis.non_functional_requirements.len(), 1);
1898 }
1899
1900 #[test]
1901 fn test_scope_definition() {
1902 let scope = ScopeDefinition::new()
1903 .with_in_scope(vec!["In scope".to_string()])
1904 .with_out_of_scope(vec!["Out of scope".to_string()])
1905 .with_future_considerations(vec!["Future".to_string()]);
1906
1907 assert_eq!(scope.in_scope.len(), 1);
1908 assert_eq!(scope.out_of_scope.len(), 1);
1909 assert_eq!(scope.future_considerations.len(), 1);
1910 }
1911
1912 #[test]
1913 fn test_plan_options_builder() {
1914 let options = PlanOptions::new("Test task")
1915 .with_context("Context")
1916 .with_constraints(vec!["Constraint".to_string()])
1917 .with_perspective("security")
1918 .with_thoroughness(ThoroughnessLevel::VeryThorough);
1919
1920 assert_eq!(options.task, "Test task");
1921 assert_eq!(options.context, Some("Context".to_string()));
1922 assert_eq!(options.perspective, Some("security".to_string()));
1923 assert_eq!(options.thoroughness, ThoroughnessLevel::VeryThorough);
1924 }
1925
1926 #[test]
1927 fn test_plan_result_data_builder() {
1928 let result = PlanResultData::new()
1929 .with_summary("Summary")
1930 .with_estimated_complexity(Complexity::High)
1931 .with_estimated_hours(10.0);
1932
1933 assert_eq!(result.summary, "Summary");
1934 assert_eq!(result.estimated_complexity, Complexity::High);
1935 assert_eq!(result.estimated_hours, Some(10.0));
1936 }
1937
1938 #[test]
1939 fn test_plan_result_calculate_total_hours() {
1940 let mut result = PlanResultData::new();
1941 result.steps = vec![
1942 PlanStep::new(1, "Step 1", "Desc").with_estimated_hours(2.0),
1943 PlanStep::new(2, "Step 2", "Desc").with_estimated_hours(3.0),
1944 PlanStep::new(3, "Step 3", "Desc"), ];
1946
1947 assert_eq!(result.calculate_total_hours(), 5.0);
1948 }
1949
1950 #[test]
1951 fn test_plan_agent_creation() {
1952 let options = PlanOptions::new("Test task");
1953 let agent = PlanAgent::new(options);
1954
1955 assert_eq!(agent.options().task, "Test task");
1956 assert!(agent.files_read().is_empty());
1957 }
1958
1959 #[test]
1960 fn test_extract_keywords() {
1961 let options = PlanOptions::new("Implement user authentication with JWT tokens");
1962 let agent = PlanAgent::new(options);
1963
1964 let keywords = agent.extract_keywords("Implement user authentication with JWT tokens");
1965
1966 assert!(!keywords.is_empty());
1967 assert!(keywords.iter().any(|k| k == "implement"
1968 || k == "user"
1969 || k == "authentication"
1970 || k == "jwt"
1971 || k == "tokens"));
1972 }
1973
1974 #[tokio::test]
1975 async fn test_analyze_requirements() {
1976 let options = PlanOptions::new("Implement secure API endpoint")
1977 .with_context("REST API")
1978 .with_constraints(vec!["Must use HTTPS".to_string()]);
1979
1980 let agent = PlanAgent::new(options);
1981 let analysis = agent.analyze_requirements().await.unwrap();
1982
1983 assert_eq!(analysis.original_task, "Implement secure API endpoint");
1984 assert!(!analysis.functional_requirements.is_empty());
1985 assert!(!analysis.non_functional_requirements.is_empty());
1986 }
1987
1988 #[tokio::test]
1989 async fn test_assess_risks_security() {
1990 let options = PlanOptions::new("Implement authentication system");
1991 let agent = PlanAgent::new(options);
1992 let risks = agent.assess_risks().await.unwrap();
1993
1994 assert!(!risks.is_empty());
1995 assert!(risks
1996 .iter()
1997 .any(|r| matches!(r.category, RiskCategory::Security)));
1998 }
1999
2000 #[tokio::test]
2001 async fn test_assess_risks_performance() {
2002 let options = PlanOptions::new("Optimize database performance");
2003 let agent = PlanAgent::new(options);
2004 let risks = agent.assess_risks().await.unwrap();
2005
2006 assert!(!risks.is_empty());
2007 assert!(risks
2008 .iter()
2009 .any(|r| matches!(r.category, RiskCategory::Performance)));
2010 }
2011
2012 #[tokio::test]
2013 async fn test_generate_alternatives() {
2014 let options = PlanOptions::new("Refactor legacy code");
2015 let agent = PlanAgent::new(options);
2016 let alternatives = agent.generate_alternatives().await.unwrap();
2017
2018 assert!(!alternatives.is_empty());
2019 assert!(alternatives.iter().any(|a| a.recommended));
2020 assert!(alternatives.iter().any(|a| a.name.contains("Incremental")));
2022 }
2023
2024 #[tokio::test]
2025 async fn test_create_plan_empty_task() {
2026 let options = PlanOptions::new("");
2027 let agent = PlanAgent::new(options);
2028 let result = agent.create_plan().await;
2029
2030 assert!(result.is_err());
2031 assert!(matches!(result.unwrap_err(), PlanError::InvalidTask(_)));
2032 }
2033
2034 #[tokio::test]
2035 async fn test_create_plan_success() {
2036 let temp_dir = TempDir::new().unwrap();
2037 fs::write(temp_dir.path().join("main.rs"), "fn main() {}").unwrap();
2038
2039 let options = PlanOptions::new("Add logging to the application")
2040 .with_working_directory(temp_dir.path())
2041 .with_thoroughness(ThoroughnessLevel::Quick);
2042
2043 let agent = PlanAgent::new(options);
2044 let result = agent.create_plan().await.unwrap();
2045
2046 assert!(!result.summary.is_empty());
2047 assert!(!result.steps.is_empty());
2048 assert!(!result.alternatives.is_empty());
2049 }
2050
2051 #[tokio::test]
2052 async fn test_identify_files_with_existing_code() {
2053 let temp_dir = TempDir::new().unwrap();
2054 let file_path = temp_dir.path().join("test.rs");
2055 fs::write(&file_path, "// test file").unwrap();
2056
2057 let options = PlanOptions::new("Test task")
2058 .with_working_directory(temp_dir.path())
2059 .with_existing_code(vec![PathBuf::from("test.rs")]);
2060
2061 let agent = PlanAgent::new(options);
2062 let files = agent.identify_files().await.unwrap();
2063
2064 assert!(!files.is_empty());
2065 assert!(files
2066 .iter()
2067 .any(|f| f.path.to_string_lossy().contains("test.rs")));
2068 }
2069
2070 #[tokio::test]
2071 async fn test_read_only_mode() {
2072 let temp_dir = TempDir::new().unwrap();
2073 let file_path = temp_dir.path().join("readonly.rs");
2074 let original_content = "// original content";
2075 fs::write(&file_path, original_content).unwrap();
2076
2077 let options = PlanOptions::new("Analyze the code")
2078 .with_working_directory(temp_dir.path())
2079 .with_existing_code(vec![PathBuf::from("readonly.rs")]);
2080
2081 let agent = PlanAgent::new(options);
2082 let _ = agent.create_plan().await.unwrap();
2083
2084 let content = fs::read_to_string(&file_path).unwrap();
2086 assert_eq!(content, original_content);
2087
2088 let files_read = agent.files_read();
2090 assert!(!files_read.is_empty());
2091 }
2092
2093 #[test]
2094 fn test_estimate_complexity_low() {
2095 let options = PlanOptions::new("Simple task").with_thoroughness(ThoroughnessLevel::Quick);
2096 let agent = PlanAgent::new(options);
2097
2098 let files = vec![CriticalFile::new(
2099 "file1.rs",
2100 "Test",
2101 ModificationType::Modify,
2102 )];
2103 let risks = vec![];
2104
2105 let complexity = agent.estimate_complexity(&files, &risks);
2106 assert!(matches!(complexity, Complexity::Trivial | Complexity::Low));
2107 }
2108
2109 #[test]
2110 fn test_estimate_complexity_high() {
2111 let options =
2112 PlanOptions::new("Complex task").with_thoroughness(ThoroughnessLevel::VeryThorough);
2113 let agent = PlanAgent::new(options);
2114
2115 let files: Vec<CriticalFile> = (0..15)
2116 .map(|i| CriticalFile::new(format!("file{}.rs", i), "Test", ModificationType::Modify))
2117 .collect();
2118
2119 let risks = vec![
2120 Risk::new(
2121 "R1",
2122 "Risk 1",
2123 RiskCategory::Security,
2124 RiskSeverity::Critical,
2125 ),
2126 Risk::new("R2", "Risk 2", RiskCategory::Technical, RiskSeverity::High),
2127 ];
2128
2129 let complexity = agent.estimate_complexity(&files, &risks);
2130 assert!(matches!(
2131 complexity,
2132 Complexity::High | Complexity::VeryHigh
2133 ));
2134 }
2135
2136 #[test]
2137 fn test_generate_steps() {
2138 let options = PlanOptions::new("Test task");
2139 let agent = PlanAgent::new(options);
2140
2141 let files = vec![CriticalFile::new(
2142 "file1.rs",
2143 "Test",
2144 ModificationType::Modify,
2145 )];
2146 let risks = vec![Risk::new(
2147 "R1",
2148 "High risk",
2149 RiskCategory::Security,
2150 RiskSeverity::High,
2151 )];
2152
2153 let steps = agent.generate_steps(&files, &risks);
2154
2155 assert!(!steps.is_empty());
2156 assert!(steps.len() >= 5);
2158 for (i, step) in steps.iter().enumerate() {
2160 assert_eq!(step.step_number, i + 1);
2161 }
2162 }
2163
2164 #[test]
2165 fn test_modification_type_default() {
2166 assert_eq!(ModificationType::default(), ModificationType::Modify);
2167 }
2168
2169 #[test]
2170 fn test_risk_category_default() {
2171 assert_eq!(RiskCategory::default(), RiskCategory::Technical);
2172 }
2173
2174 #[test]
2175 fn test_risk_severity_default() {
2176 assert_eq!(RiskSeverity::default(), RiskSeverity::Medium);
2177 }
2178
2179 #[test]
2180 fn test_complexity_default() {
2181 assert_eq!(Complexity::default(), Complexity::Medium);
2182 }
2183}