code-buster 0.11.0

Repository-aware static analysis for AI-assisted development
Documentation
use std::collections::{BTreeMap, BTreeSet};
use std::process::Command;

use serde::Serialize;

use crate::cli::{PreviewOptions, ReportFormat};
use crate::discovery::prepare;

pub enum HotspotsOutcome {
    Success,
    Empty,
}

#[derive(Clone, Debug, Serialize)]
struct Hotspot {
    path: String,
    commits: usize,
    added: usize,
    deleted: usize,
    churn: usize,
    risk: f64,
}

#[derive(Default)]
struct HotspotTotals {
    commits: usize,
    added: usize,
    deleted: usize,
}

#[derive(Serialize)]
struct HotspotsReport<'a> {
    version: u8,
    command: &'static str,
    view: &'static str,
    root: &'a str,
    hotspots: &'a [Hotspot],
}

pub fn execute(options: &PreviewOptions) -> Result<HotspotsOutcome, String> {
    let prepared = prepare(options)?;
    if prepared.files.is_empty() && !options.allow_empty {
        eprintln!("no source files found (use --allow-empty to allow this)");
        return Ok(HotspotsOutcome::Empty);
    }

    let allowed = prepared.sources.keys().cloned().collect::<BTreeSet<_>>();
    let mut hotspots = from_git(&prepared.root, &allowed);
    if options.top > 0 && hotspots.len() > options.top as usize {
        hotspots.truncate(options.top as usize);
    }

    if matches!(options.format, ReportFormat::Json) {
        let output = serde_json::to_string(&HotspotsReport {
            version: 1,
            command: "quality",
            view: "hotspots",
            root: &prepared.root,
            hotspots: &hotspots,
        })
        .map_err(|error| format!("failed to encode hotspots report: {error}"))?;
        println!("{output}");
    } else {
        print_text(&hotspots);
    }
    Ok(HotspotsOutcome::Success)
}

fn from_git(root: &str, allowed: &BTreeSet<String>) -> Vec<Hotspot> {
    let Ok(output) = Command::new("git")
        .args([
            "-C",
            root,
            "log",
            "--no-merges",
            "--numstat",
            "--format=format:__CODE_BUSTER_COMMIT__",
        ])
        .output()
    else {
        return Vec::new();
    };
    if !output.status.success() {
        return Vec::new();
    }
    parse_numstat(&String::from_utf8_lossy(&output.stdout), allowed)
}

fn parse_numstat(text: &str, allowed: &BTreeSet<String>) -> Vec<Hotspot> {
    let mut totals = BTreeMap::<String, HotspotTotals>::new();
    let mut touched = BTreeSet::<String>::new();

    for line in text.split('\n') {
        if line.trim() == "__CODE_BUSTER_COMMIT__" {
            finish_commit(&mut totals, &mut touched);
            continue;
        }
        let fields = line.split('\t').collect::<Vec<_>>();
        if fields.len() < 3 || fields[0] == "-" || fields[1] == "-" {
            continue;
        }
        let path = fields[2].replace('\\', "/");
        if !allowed.is_empty() && !allowed.contains(&path) {
            continue;
        }
        let (Ok(added), Ok(deleted)) = (fields[0].parse::<usize>(), fields[1].parse::<usize>())
        else {
            continue;
        };
        let total = totals.entry(path.clone()).or_default();
        total.added += added;
        total.deleted += deleted;
        touched.insert(path);
    }
    finish_commit(&mut totals, &mut touched);

    let mut hotspots = totals
        .into_iter()
        .map(|(path, total)| {
            let churn = total.added + total.deleted;
            Hotspot {
                path,
                commits: total.commits,
                added: total.added,
                deleted: total.deleted,
                churn,
                risk: churn as f64 * (1.0 + (1.0 + total.commits as f64).ln()),
            }
        })
        .collect::<Vec<_>>();
    hotspots.sort_by(|left, right| {
        right
            .risk
            .total_cmp(&left.risk)
            .then_with(|| left.path.cmp(&right.path))
    });
    hotspots
}

fn finish_commit(totals: &mut BTreeMap<String, HotspotTotals>, touched: &mut BTreeSet<String>) {
    for path in touched.iter() {
        totals.entry(path.clone()).or_default().commits += 1;
    }
    touched.clear();
}

fn print_text(hotspots: &[Hotspot]) {
    println!("Code Buster hotspots: {}", hotspots.len());
    if hotspots.is_empty() {
        println!("No Git history available for discovered files.");
    }
    for hotspot in hotspots {
        println!(
            "{} — risk {:.1}, churn {}, commits {} (+{}/-{})",
            hotspot.path,
            hotspot.risk,
            hotspot.churn,
            hotspot.commits,
            hotspot.added,
            hotspot.deleted
        );
    }
}