1use anyhow::Result;
2use owo_colors::OwoColorize;
3
4use crate::detection;
5use crate::fix;
6use crate::rules;
7use crate::scan;
8use crate::scoring;
9
10pub fn show_diff(path: &std::path::Path) -> Result<()> {
12 let before = score_project(path)?;
13
14 let tmp = tempfile::TempDir::new()?;
15 copy_dir_recursive(path, tmp.path())?;
16 let fixes = apply_fixes_to_copy(tmp.path())?;
17
18 let after = score_project(tmp.path())?;
19 print_diff(&before, &after, &fixes);
20 Ok(())
21}
22
23fn score_project(path: &std::path::Path) -> Result<scoring::Scorecard> {
24 let mut ctx = scan::build_context(path)?;
25 let pt = detection::detect(&ctx);
26 ctx.project_type = Some(pt.clone());
27 let results: Vec<_> = rules::all_rules().iter()
28 .filter(|r| r.applies_to(&pt))
29 .map(|r| r.check(&ctx))
30 .collect();
31 Ok(scoring::calculate(results, &pt))
32}
33
34fn apply_fixes_to_copy(path: &std::path::Path) -> Result<Vec<String>> {
35 let mut ctx = scan::build_context(path)?;
36 let pt = detection::detect(&ctx);
37 ctx.project_type = Some(pt.clone());
38 let results: Vec<_> = rules::all_rules().iter()
39 .filter(|r| r.applies_to(&pt))
40 .map(|r| r.check(&ctx))
41 .collect();
42 fix::apply_fixes(&ctx, &results)
43}
44
45fn print_diff(before: &scoring::Scorecard, after: &scoring::Scorecard, fixes: &[String]) {
46 println!();
47 println!("{}", " Score Diff (current -> after --fix)".bold().underline());
48 println!();
49
50 for (b, a) in before.dimensions.iter().zip(after.dimensions.iter()) {
51 let delta = a.score - b.score;
52 let ds = format_delta(delta);
53 println!(" {:<22} {:.0} -> {:.0} ({})", b.dimension.to_string().bold(), b.score, a.score, ds);
54 }
55
56 println!();
57 let td = after.total_score - before.total_score;
58 println!(" {:<22} {:.0} -> {:.0} ({}) {} -> {}", "TOTAL".bold(),
59 before.total_score, after.total_score, format_delta(td), before.grade, after.grade);
60
61 if !fixes.is_empty() {
62 println!();
63 println!(" {} to apply:", "Fixes available".green().bold());
64 for f in fixes { println!(" {} {f}", "+".green()); }
65 println!();
66 println!(" Run {} to apply.", "claude-native --fix".bold());
67 } else {
68 println!();
69 println!(" {} — no fixable issues.", "Already optimized".green());
70 }
71}
72
73fn format_delta(delta: f64) -> String {
74 if delta > 0.0 { format!("+{:.0}", delta).green().to_string() }
75 else if delta < 0.0 { format!("{:.0}", delta).red().to_string() }
76 else { " 0".dimmed().to_string() }
77}
78
79fn copy_dir_recursive(src: &std::path::Path, dst: &std::path::Path) -> Result<()> {
80 for entry in walkdir::WalkDir::new(src).into_iter().filter_map(|e| e.ok()) {
81 let rel = entry.path().strip_prefix(src)?;
82 let target = dst.join(rel);
83 if entry.file_type().is_dir() {
84 std::fs::create_dir_all(&target)?;
85 } else {
86 if let Some(p) = target.parent() { std::fs::create_dir_all(p)?; }
87 std::fs::copy(entry.path(), &target)?;
88 }
89 }
90 Ok(())
91}