ferrous_forge/ai_analyzer/
analyzer.rs

1use std::fs;
2use std::path::PathBuf;
3
4use anyhow::Result;
5use chrono::Utc;
6
7use super::context::extract_code_context;
8use super::semantic::{assess_fix_complexity, perform_semantic_analysis};
9use super::strategies::{
10    generate_ai_instructions, generate_fix_strategies, identify_code_patterns,
11};
12use super::types::*;
13use crate::validation::Violation;
14
15/// AI analyzer for automated violation analysis
16pub struct AIAnalyzer {
17    project_root: PathBuf,
18}
19
20impl AIAnalyzer {
21    /// Create a new AI analyzer
22    pub fn new(project_root: PathBuf) -> Self {
23        Self { project_root }
24    }
25
26    /// Analyze violations and generate report
27    pub fn analyze_violations(&self, violations: Vec<Violation>) -> Result<AIAnalysisReport> {
28        let mut violation_analyses = Vec::new();
29        let mut analyzable_count = 0;
30
31        for violation in &violations {
32            if let Ok(analysis) = self.analyze_single_violation(&violation) {
33                if analysis.ai_fixable {
34                    analyzable_count += 1;
35                }
36                violation_analyses.push(analysis);
37            }
38        }
39
40        let code_patterns = self.analyze_project_patterns()?;
41        let fix_strategies = generate_fix_strategies(&violation_analyses);
42        let ai_instructions = generate_ai_instructions(&violation_analyses, &fix_strategies);
43
44        let metadata = AnalysisMetadata {
45            total_violations: violations.len(),
46            analyzable_violations: analyzable_count,
47            project_path: self.project_root.display().to_string(),
48            analysis_depth: AnalysisDepth::Semantic,
49        };
50
51        Ok(AIAnalysisReport {
52            metadata,
53            violation_analyses,
54            code_patterns,
55            fix_strategies,
56            ai_instructions,
57        })
58    }
59
60    fn analyze_single_violation(&self, violation: &Violation) -> Result<ViolationAnalysis> {
61        let content = fs::read_to_string(&violation.file)?;
62        let code_context = extract_code_context(violation.line, &content);
63        let semantic_analysis = perform_semantic_analysis(violation, &code_context, &content);
64        let fix_complexity = assess_fix_complexity(violation, &code_context, &semantic_analysis);
65
66        let (ai_fixable, confidence_score) = self.assess_fixability(
67            violation,
68            &code_context,
69            &semantic_analysis,
70            &fix_complexity,
71        );
72
73        let fix_recommendation = if ai_fixable {
74            self.generate_fix_recommendation(&violation, &code_context, &semantic_analysis)
75        } else {
76            None
77        };
78
79        let side_effects = self.identify_side_effects(&violation, &code_context);
80
81        Ok(ViolationAnalysis {
82            violation: violation.clone(),
83            code_context,
84            semantic_analysis,
85            fix_complexity,
86            ai_fixable,
87            fix_recommendation,
88            side_effects,
89            confidence_score,
90        })
91    }
92
93    fn assess_fixability(
94        &self,
95        violation: &Violation,
96        context: &CodeContext,
97        _semantic: &SemanticAnalysis,
98        complexity: &FixComplexity,
99    ) -> (bool, f32) {
100        match (&violation.violation_type, complexity) {
101            (crate::validation::ViolationType::UnwrapInProduction, FixComplexity::Trivial) => {
102                if context
103                    .return_type
104                    .as_ref()
105                    .is_some_and(|r| r.contains("Result"))
106                {
107                    (true, 0.95)
108                } else {
109                    (true, 0.75)
110                }
111            }
112            (crate::validation::ViolationType::UnwrapInProduction, FixComplexity::Simple) => {
113                (true, 0.65)
114            }
115            (crate::validation::ViolationType::LineTooLong, _) => (true, 1.0),
116            (crate::validation::ViolationType::UnderscoreBandaid, _) => (true, 0.85),
117            (crate::validation::ViolationType::FunctionTooLarge, _) => (false, 0.3),
118            (crate::validation::ViolationType::FileTooLarge, _) => (false, 0.2),
119            _ => (false, 0.0),
120        }
121    }
122
123    fn generate_fix_recommendation(
124        &self,
125        violation: &Violation,
126        context: &CodeContext,
127        _semantic: &SemanticAnalysis,
128    ) -> Option<String> {
129        match violation.violation_type {
130            crate::validation::ViolationType::UnwrapInProduction => {
131                if context
132                    .return_type
133                    .as_ref()
134                    .is_some_and(|r| r.contains("Result"))
135                {
136                    Some("Replace ? with ? operator".to_string())
137                } else {
138                    Some("Change function return type to Result and use ?".to_string())
139                }
140            }
141            crate::validation::ViolationType::LineTooLong => {
142                Some("Break line at appropriate point (e.g., after comma, operator)".to_string())
143            }
144            crate::validation::ViolationType::UnderscoreBandaid => {
145                Some("Either use the parameter or remove it from function signature".to_string())
146            }
147            _ => None,
148        }
149    }
150
151    fn identify_side_effects(&self, violation: &Violation, context: &CodeContext) -> Vec<String> {
152        let mut effects = Vec::new();
153
154        match violation.violation_type {
155            crate::validation::ViolationType::UnwrapInProduction => {
156                if !context
157                    .return_type
158                    .as_ref()
159                    .is_some_and(|r| r.contains("Result"))
160                {
161                    effects.push("Function signature change required".to_string());
162                    effects.push("All callers must be updated".to_string());
163                }
164            }
165            crate::validation::ViolationType::FunctionTooLarge => {
166                effects.push("May require creating new helper functions".to_string());
167                effects.push("Could affect function testing".to_string());
168            }
169            _ => {}
170        }
171
172        effects
173    }
174
175    fn analyze_project_patterns(&self) -> Result<CodePatterns> {
176        let mut all_content = String::new();
177
178        // Sample a few files for pattern analysis
179        use std::fs;
180        let mut count = 0;
181
182        // Simple file traversal
183        fn visit_dir(
184            dir: &std::path::Path,
185            content: &mut String,
186            count: &mut usize,
187            max: usize,
188        ) -> Result<()> {
189            if *count >= max {
190                return Ok(());
191            }
192
193            for entry in fs::read_dir(dir)? {
194                let entry = entry?;
195                let path = entry.path();
196
197                if path.is_dir()
198                    && !path
199                        .file_name()
200                        .unwrap_or_default()
201                        .to_string_lossy()
202                        .starts_with('.')
203                {
204                    visit_dir(&path, content, count, max)?;
205                } else if path.extension().is_some_and(|ext| ext == "rs") {
206                    if let Ok(file_content) = fs::read_to_string(&path) {
207                        content.push_str(&file_content);
208                        *count += 1;
209                        if *count >= max {
210                            break;
211                        }
212                    }
213                }
214            }
215            Ok(())
216        }
217
218        visit_dir(
219            &self.project_root.join("src"),
220            &mut all_content,
221            &mut count,
222            10,
223        )?;
224
225        Ok(identify_code_patterns(&all_content))
226    }
227
228    /// Async version of analyze_violations
229    pub async fn analyze_violations_async(
230        &self,
231        violations: Vec<Violation>,
232    ) -> Result<AIAnalysisReport> {
233        // For now, just call the sync version
234        // In future could parallelize with tokio
235        self.analyze_violations(violations)
236    }
237
238    /// Save analysis report to disk
239    pub fn save_analysis(&self, report: &AIAnalysisReport) -> Result<()> {
240        let analysis_dir = self.project_root.join(".ferrous-forge").join("ai-analysis");
241        fs::create_dir_all(&analysis_dir)?;
242
243        let timestamp = Utc::now().format("%Y%m%d_%H%M%S");
244        let filename = format!("ai_analysis_{}.json", timestamp);
245        let filepath = analysis_dir.join(&filename);
246
247        let json = serde_json::to_string_pretty(&report)?;
248        fs::write(&filepath, json)?;
249
250        println!("📊 AI analysis saved to: {}", filepath.display());
251
252        // Also save orchestrator instructions
253        self.save_orchestrator_instructions(report)?;
254
255        Ok(())
256    }
257
258    /// Save orchestrator instructions to file
259    pub fn save_orchestrator_instructions(&self, report: &AIAnalysisReport) -> Result<()> {
260        let analysis_dir = self.project_root.join(".ferrous-forge").join("ai-analysis");
261        let timestamp = Utc::now().format("%Y%m%d_%H%M%S");
262        let filename = format!("orchestrator_instructions_{}.md", timestamp);
263        let filepath = analysis_dir.join(&filename);
264
265        let mut instructions = String::new();
266        instructions.push_str("# AI Orchestrator Instructions\n\n");
267        instructions.push_str(&format!(
268            "## Summary\n{}\n\n",
269            report.ai_instructions.summary
270        ));
271
272        instructions.push_str("## Prioritized Fixes\n");
273        for fix in &report.ai_instructions.prioritized_fixes {
274            instructions.push_str(&format!("- {}\n", fix));
275        }
276
277        instructions.push_str("\n## Architectural Recommendations\n");
278        for rec in &report.ai_instructions.architectural_recommendations {
279            instructions.push_str(&format!("- {}\n", rec));
280        }
281
282        fs::write(&filepath, instructions)?;
283        println!(
284            "📝 Orchestrator instructions saved to: {}",
285            filepath.display()
286        );
287
288        Ok(())
289    }
290}