Skip to main content

claude_native/
diff.rs

1use anyhow::Result;
2use owo_colors::OwoColorize;
3
4use crate::detection;
5use crate::fix;
6use crate::rules;
7use crate::scan;
8use crate::scoring;
9
10/// Show before/after score comparison.
11pub 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}