ferrous_forge/ai_analyzer/
analyzer.rs1use 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
15pub struct AIAnalyzer {
17 project_root: PathBuf,
18}
19
20impl AIAnalyzer {
21 pub fn new(project_root: PathBuf) -> Self {
23 Self { project_root }
24 }
25
26 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 use std::fs;
180 let mut count = 0;
181
182 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 pub async fn analyze_violations_async(
230 &self,
231 violations: Vec<Violation>,
232 ) -> Result<AIAnalysisReport> {
233 self.analyze_violations(violations)
236 }
237
238 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 self.save_orchestrator_instructions(report)?;
254
255 Ok(())
256 }
257
258 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}