1use crate::{
4 doc_coverage, formatting, security,
5 validation::{RustValidator, Violation},
6 Result,
7};
8use console::style;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::path::PathBuf;
12use tokio::fs;
13
14#[derive(Serialize, Deserialize)]
16struct AIReport {
17 metadata: AIMetadata,
18 summary: AISummary,
19 violations: Vec<AIViolation>,
20 fix_instructions: Vec<FixInstruction>,
21}
22
23#[derive(Serialize, Deserialize)]
24struct AIMetadata {
25 timestamp: String,
26 project_path: String,
27 ferrous_forge_version: String,
28 total_violations: usize,
29 report_version: String,
30}
31
32#[derive(Serialize, Deserialize)]
33struct AISummary {
34 compliance_percentage: f64,
35 files_analyzed: usize,
36 most_critical_issues: Vec<String>,
37 estimated_fix_time_hours: f64,
38}
39
40#[derive(Serialize, Deserialize)]
41struct AIViolation {
42 violation_type: String,
43 file: String,
44 line: usize,
45 message: String,
46 code_snippet: String,
47 suggested_fix: String,
48 auto_fixable: bool,
49 priority: u8,
50}
51
52#[derive(Serialize, Deserialize)]
53struct FixInstruction {
54 violation_type: String,
55 count: usize,
56 fix_strategy: String,
57 example_fix: String,
58 effort_level: String,
59}
60
61pub async fn execute(
63 path: Option<PathBuf>,
64 ai_report: bool,
65 _compare_previous: bool,
66) -> Result<()> {
67 let project_path = path.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
68
69 println!(
70 "{}",
71 style("š¦ Running Ferrous Forge validation...")
72 .bold()
73 .cyan()
74 );
75 println!("š Project: {}", project_path.display());
76 println!();
77
78 let validator = RustValidator::new(project_path.clone())?;
80
81 let violations = validator.validate_project().await?;
83
84 let report = validator.generate_report(&violations);
86 println!("{}", report);
87
88 if ai_report {
90 println!("\nš¤ Generating AI-friendly compliance report...");
91 generate_ai_report(&project_path, &violations).await?;
92 }
93
94 println!(
96 "{}",
97 style("š§ Running Clippy with strict configuration...")
98 .bold()
99 .yellow()
100 );
101 let clippy_result = validator.run_clippy().await?;
102
103 if !clippy_result.success {
104 println!("{}", style("ā Clippy found issues:").red());
105 println!("{}", clippy_result.output);
106 } else {
107 println!("{}", style("ā
Clippy validation passed!").green());
108 }
109
110 println!();
112 println!(
113 "{}",
114 style("š Checking documentation coverage...")
115 .bold()
116 .yellow()
117 );
118 match doc_coverage::check_documentation_coverage(&project_path).await {
119 Ok(coverage) => {
120 println!("{}", coverage.report());
121 if coverage.coverage_percent < 80.0 {
122 println!("{}", style("ā ļø Documentation coverage below 80%").yellow());
123 }
124 }
125 Err(e) => {
126 println!(
127 "{}",
128 style(format!("ā ļø Could not check documentation: {}", e)).yellow()
129 );
130 }
131 }
132
133 println!();
135 println!(
136 "{}",
137 style("š Checking code formatting...").bold().yellow()
138 );
139 match formatting::check_formatting(&project_path).await {
140 Ok(format_result) => {
141 println!("{}", format_result.report());
142 }
143 Err(e) => {
144 println!(
145 "{}",
146 style(format!("ā ļø Could not check formatting: {}", e)).yellow()
147 );
148 }
149 }
150
151 println!();
153 println!("{}", style("š Running security audit...").bold().yellow());
154 match security::run_security_audit(&project_path).await {
155 Ok(audit_report) => {
156 println!("{}", audit_report.report());
157 }
158 Err(e) => {
159 println!(
160 "{}",
161 style(format!("ā ļø Could not run security audit: {}", e)).yellow()
162 );
163 }
164 }
165
166 if !violations.is_empty() || !clippy_result.success {
168 std::process::exit(1);
169 } else {
170 println!();
171 println!(
172 "{}",
173 style("š All validations passed! Code meets Ferrous Forge standards.")
174 .bold()
175 .green()
176 );
177 }
178
179 Ok(())
180}
181
182async fn generate_ai_report(project_path: &PathBuf, violations: &[Violation]) -> Result<()> {
184 use chrono::Utc;
185
186 let reports_dir = project_path.join(".ferrous-forge").join("reports");
188 fs::create_dir_all(&reports_dir).await?;
189
190 let timestamp = Utc::now();
192 let timestamp_str = timestamp.format("%Y%m%d_%H%M%S").to_string();
193
194 let mut violation_counts = HashMap::new();
196 for violation in violations {
197 *violation_counts
198 .entry(format!("{:?}", violation.violation_type))
199 .or_insert(0) += 1;
200 }
201
202 let mut ai_violations = Vec::new();
204 for violation in violations.iter().take(50) {
205 let code_snippet = get_code_snippet(&violation.file, violation.line)
207 .await
208 .unwrap_or_else(|_| "Could not read file".to_string());
209
210 let (suggested_fix, auto_fixable, priority) = match violation.violation_type {
211 crate::validation::ViolationType::UnderscoreBandaid => {
212 if violation.message.contains("parameter") {
213 (
214 "Remove unused parameter or implement missing functionality".to_string(),
215 false,
216 2,
217 )
218 } else {
219 (
220 "Replace `let _ =` with proper error handling using `?`".to_string(),
221 true,
222 1,
223 )
224 }
225 }
226 crate::validation::ViolationType::UnwrapInProduction => (
227 "Replace `.unwrap()` with `?` or proper error handling".to_string(),
228 true,
229 1,
230 ),
231 crate::validation::ViolationType::FileTooLarge => (
232 "Split file into smaller modules following single responsibility principle"
233 .to_string(),
234 false,
235 4,
236 ),
237 crate::validation::ViolationType::FunctionTooLarge => (
238 "Extract helper functions or split into smaller, focused functions".to_string(),
239 false,
240 3,
241 ),
242 _ => (
243 "Review and fix according to Ferrous Forge standards".to_string(),
244 false,
245 3,
246 ),
247 };
248
249 ai_violations.push(AIViolation {
250 violation_type: format!("{:?}", violation.violation_type),
251 file: violation.file.display().to_string(),
252 line: violation.line + 1, message: violation.message.clone(),
254 code_snippet,
255 suggested_fix,
256 auto_fixable,
257 priority,
258 });
259 }
260
261 let mut fix_instructions = Vec::new();
263 for (vtype, count) in violation_counts {
264 let (strategy, example, effort) = match vtype.as_str() {
265 "UnderscoreBandaid" => (
266 "1. Identify what functionality the parameter should provide\n2. Either implement the functionality or remove the parameter\n3. Update function signature and callers".to_string(),
267 "// Before: fn process(_unused: String, data: Data)\n// After: fn process(data: Data) or implement the unused parameter".to_string(),
268 "Moderate".to_string(),
269 ),
270 "UnwrapInProduction" => (
271 "1. Change function to return Result<T, Error>\n2. Replace .unwrap() with ?\n3. Handle errors at call sites".to_string(),
272 "// Before: value.unwrap()\n// After: value?".to_string(),
273 "Easy".to_string(),
274 ),
275 "FileTooLarge" => (
276 "1. Identify logical boundaries in the file\n2. Create new module directory\n3. Split into focused modules\n4. Update imports".to_string(),
277 "// Split validation.rs into validation/mod.rs, validation/core.rs, validation/types.rs".to_string(),
278 "Hard".to_string(),
279 ),
280 _ => ("Review and fix manually".to_string(), "".to_string(), "Moderate".to_string()),
281 };
282
283 fix_instructions.push(FixInstruction {
284 violation_type: vtype,
285 count,
286 fix_strategy: strategy,
287 example_fix: example,
288 effort_level: effort,
289 });
290 }
291
292 let total_files = count_rust_files(project_path).await?;
294 let files_with_violations = violations
295 .iter()
296 .map(|v| &v.file)
297 .collect::<std::collections::HashSet<_>>()
298 .len();
299
300 let compliance_percentage = if total_files > 0 && files_with_violations <= total_files {
301 ((total_files - files_with_violations) as f64 / total_files as f64) * 100.0
302 } else {
303 0.0 };
305
306 let report = AIReport {
308 metadata: AIMetadata {
309 timestamp: timestamp.to_rfc3339(),
310 project_path: project_path.display().to_string(),
311 ferrous_forge_version: env!("CARGO_PKG_VERSION").to_string(),
312 total_violations: violations.len(),
313 report_version: "1.0.0".to_string(),
314 },
315 summary: AISummary {
316 compliance_percentage,
317 files_analyzed: total_files,
318 most_critical_issues: vec![
319 "UnderscoreBandaid violations (implement missing functionality)".to_string(),
320 "Large files need splitting (validation.rs: 1133 lines)".to_string(),
321 "Large functions need refactoring".to_string(),
322 ],
323 estimated_fix_time_hours: violations.len() as f64 * 0.25, },
325 violations: ai_violations,
326 fix_instructions,
327 };
328
329 let json_path = reports_dir.join(format!("ai_compliance_{}.json", timestamp_str));
331 let json_content = serde_json::to_string_pretty(&report)
332 .map_err(|e| crate::Error::config(format!("Failed to serialize AI report: {}", e)))?;
333 fs::write(&json_path, json_content).await?;
334
335 let md_path = reports_dir.join(format!("ai_compliance_{}.md", timestamp_str));
337 let md_content = generate_markdown_report(&report);
338 fs::write(&md_path, md_content).await?;
339
340 let latest_json = reports_dir.join("latest_ai_report.json");
342 let latest_md = reports_dir.join("latest_ai_report.md");
343 fs::copy(&json_path, &latest_json).await?;
344 fs::copy(&md_path, &latest_md).await?;
345
346 println!("š AI Compliance Report Generated:");
347 println!(" š JSON: {}", json_path.display());
348 println!(" š Markdown: {}", md_path.display());
349 println!(" š Latest JSON: {}", latest_json.display());
350 println!(" š Latest MD: {}", latest_md.display());
351 println!("\nš¤ This report is optimized for AI assistant consumption");
352 println!(" Use the JSON file for automated processing and fix suggestions");
353
354 Ok(())
355}
356
357async fn get_code_snippet(file_path: &PathBuf, line: usize) -> Result<String> {
359 if !file_path.exists() {
360 return Ok("File not found".to_string());
361 }
362
363 let contents = fs::read_to_string(file_path).await?;
364 let lines: Vec<&str> = contents.lines().collect();
365
366 if line < lines.len() {
367 Ok(lines[line].to_string())
368 } else {
369 Ok("Line not found".to_string())
370 }
371}
372
373async fn count_rust_files(project_path: &PathBuf) -> Result<usize> {
375 let mut count = 0;
376
377 let mut entries = fs::read_dir(project_path).await?;
379 while let Some(entry) = entries.next_entry().await? {
380 if let Some(ext) = entry.path().extension() {
381 if ext == "rs" {
382 count += 1;
383 }
384 }
385 }
386
387 Ok(count.max(1))
388}
389
390fn generate_markdown_report(report: &AIReport) -> String {
392 let mut md = String::new();
393
394 md.push_str("# š¤ AI-Friendly Compliance Report\n\n");
395 md.push_str(&format!("**Generated**: {}\n", report.metadata.timestamp));
396 md.push_str(&format!("**Project**: {}\n", report.metadata.project_path));
397 md.push_str(&format!(
398 "**Total Violations**: {}\n",
399 report.metadata.total_violations
400 ));
401 md.push_str(&format!(
402 "**Compliance**: {:.1}%\n\n",
403 report.summary.compliance_percentage
404 ));
405
406 md.push_str("## šÆ Fix Priority Order\n\n");
407 md.push_str("1. **UnwrapInProduction** - Critical for safety\n");
408 md.push_str("2. **UnderscoreBandaid** - Implement missing functionality\n");
409 md.push_str("3. **FunctionTooLarge** - Refactor for maintainability\n");
410 md.push_str("4. **FileTooLarge** - Split into modules\n\n");
411
412 md.push_str("## š§ Automated Fix Commands\n\n");
413 md.push_str("```bash\n");
414 md.push_str("# Generate this report\n");
415 md.push_str("ferrous-forge validate . --ai-report\n\n");
416 md.push_str("# Use AI assistant with the JSON report to implement fixes\n");
417 md.push_str("# The JSON contains structured data for automated processing\n");
418 md.push_str("```\n\n");
419
420 md.push_str("## š Violation Summary\n\n");
421 for instruction in &report.fix_instructions {
422 md.push_str(&format!(
423 "### {} ({} violations)\n",
424 instruction.violation_type, instruction.count
425 ));
426 md.push_str(&format!("**Strategy**: {}\n\n", instruction.fix_strategy));
427 md.push_str(&format!(
428 "**Example**: \n```rust\n{}\n```\n\n",
429 instruction.example_fix
430 ));
431 }
432
433 md
434}