code-buster 0.11.0

Repository-aware static analysis for AI-assisted development
Documentation
use serde_json::{Value, json};

use crate::cli::PreviewOptions;
use crate::findings::{Finding, Severity};
use crate::rule_catalog;
use crate::summary_command;

pub enum WorkflowOutcome {
    Success,
    Findings,
}

pub fn execute_plan(options: &PreviewOptions) -> Result<(), String> {
    let run = summary_command::analyze(options)?;
    let mut groups = Vec::<(&str, Vec<&Finding>)>::new();
    for finding in &run.findings {
        if let Some((_, findings)) = groups
            .iter_mut()
            .find(|(code, _)| *code == finding.code.as_str())
        {
            findings.push(finding);
        } else {
            groups.push((&finding.code, vec![finding]));
        }
    }
    let mut prioritized = (0..groups.len()).collect::<Vec<_>>();
    crate::analysis_engine::dart_sort_indices(&mut prioritized, &|left, right| {
        let (left_code, left_findings) = &groups[left];
        let (right_code, right_findings) = &groups[right];
        priority_rank(left_code)
            .cmp(&priority_rank(right_code))
            .then_with(|| right_findings.len().cmp(&left_findings.len()))
    });

    println!("Code Buster review plan");
    if prioritized.is_empty() {
        println!("No findings to plan from.");
        return Ok(());
    }
    for (index, group_index) in prioritized.iter().enumerate() {
        let (code, findings) = &groups[*group_index];
        let worst = worst_severity(findings);
        println!(
            "{}. {} ({} finding{}, severity: {}, confidence: {})",
            index + 1,
            plan_title(code),
            findings.len(),
            if findings.len() == 1 { "" } else { "s" },
            worst.as_str(),
            if *code == "feature-flag" {
                "medium"
            } else {
                "high"
            }
        );
        println!("   why: {}", action_why(code));
        println!("   suggestion: {}", action_suggestion(code));
        for finding in findings.iter().take(5) {
            println!("   - {}:{} {}", finding.path, finding.line, finding.message);
        }
        if findings.len() > 5 {
            println!("   - ... {} more", findings.len() - 5);
        }
    }
    Ok(())
}

pub fn execute_actions(options: &PreviewOptions) -> Result<WorkflowOutcome, String> {
    let run = summary_command::analyze(options)?;
    let actions = run
        .findings
        .iter()
        .map(action_value)
        .collect::<Vec<Value>>();
    println!(
        "{}",
        serde_json::to_string(&json!({
            "schemaVersion": 3,
            "version": 1,
            "command": "review",
            "view": "actions",
            "root": run.prepared.root,
            "actions": actions,
        }))
        .map_err(|error| format!("failed to encode actions report: {error}"))?
    );
    if options.fail_on_issues && !actions.is_empty() {
        Ok(WorkflowOutcome::Findings)
    } else {
        Ok(WorkflowOutcome::Success)
    }
}

fn action_value(finding: &Finding) -> Value {
    json!({
        "path": finding.path,
        "line": finding.line,
        "end_line": finding.end_line,
        "code": finding.code,
        "severity": finding.severity.as_str(),
        "risk": match finding.severity {
            Severity::Error => "high",
            Severity::Warn => "medium",
            Severity::Info => "low",
        },
        "problem": finding.message,
        "why": action_why(&finding.code),
        "suggested_action": action_suggestion(&finding.code),
        "related": finding.related_files,
    })
}

fn worst_severity(findings: &[&Finding]) -> Severity {
    if findings
        .iter()
        .any(|finding| finding.severity == Severity::Error)
    {
        Severity::Error
    } else if findings
        .iter()
        .any(|finding| finding.severity == Severity::Warn)
    {
        Severity::Warn
    } else {
        Severity::Info
    }
}

fn plan_title(code: &str) -> String {
    match code {
        "cycle" => "Break cycle".to_owned(),
        "duplicate-block" => "Extract shared logic".to_owned(),
        "feature-flag" => "Review flag".to_owned(),
        "repeated-condition" => "Repeated condition".to_owned(),
        "structure-missing-required-dir" => "Structure missing required dir".to_owned(),
        "structure-top-level-file" => "Structure top level file".to_owned(),
        "tab-indent" => "Replace tabs".to_owned(),
        "trailing-whitespace" => "Trim whitespace".to_owned(),
        _ => rule_catalog::metadata(code).map_or_else(
            || format!("Review {}", code.replace('-', " ")),
            |metadata| metadata.title.to_owned(),
        ),
    }
}

fn action_why(code: &str) -> &'static str {
    match code {
        "cycle" => "A circular dependency exists in the module graph.",
        "duplicate-block" => "The same normalized code block appears in more than one location.",
        "feature-flag" => "A feature flag-like reference was found.",
        "tab-indent" => "A line contains a tab character.",
        "trailing-whitespace" => "A line has trailing whitespace.",
        _ => "A Code Buster heuristic analyzer found a potential issue.",
    }
}

fn action_suggestion(code: &str) -> &'static str {
    match code {
        "cycle" => "Extract shared code into a third module or invert one dependency.",
        "duplicate-block" => {
            "Extract shared logic or raise min_duplication_lines if the duplication is intentional/noisy."
        }
        "feature-flag" => "Review whether the flag is still needed and documented.",
        "tab-indent" => "Replace tabs with spaces.",
        "trailing-whitespace" => "Trim trailing spaces before committing.",
        _ => "Review the finding and update code or configuration as appropriate.",
    }
}

const PREFIX_PRIORITY_RANKS: &[(&str, u16)] = &[
    ("cs-", 100),
    ("cpp-", 120),
    ("java-", 140),
    ("lua-", 150),
    ("ts-", 160),
    ("py-", 170),
    ("html-", 180),
    ("css-", 180),
    ("sql-", 190),
    ("regex-", 200),
    ("nim-", 300),
];

fn priority_rank(code: &str) -> u16 {
    structural_priority_rank(code)
        .or_else(|| prefixed_priority_rank(code))
        .or_else(|| cleanup_priority_rank(code))
        .unwrap_or(500)
}

fn structural_priority_rank(code: &str) -> Option<u16> {
    match code {
        "cycle" => Some(0),
        "dead-file" => Some(10),
        "large-file" | "long-function" | "goto-statement" | "complex-function" => Some(20),
        "duplicate-block" => Some(30),
        "dead-export" | "feature-flag" | "re-export" => Some(40),
        _ => None,
    }
}

fn prefixed_priority_rank(code: &str) -> Option<u16> {
    PREFIX_PRIORITY_RANKS
        .iter()
        .find_map(|(prefix, rank)| code.starts_with(prefix).then_some(*rank))
}

fn cleanup_priority_rank(code: &str) -> Option<u16> {
    match code {
        "tab-indent" | "trailing-whitespace" | "long-line" => Some(900),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::{action_suggestion, plan_title, priority_rank};

    #[test]
    fn prioritizes_structural_work_before_style_cleanup() {
        assert!(priority_rank("cycle") < priority_rank("duplicate-block"));
        assert!(priority_rank("duplicate-block") < priority_rank("tab-indent"));
    }

    #[test]
    fn uses_catalog_titles_and_specific_remediation() {
        assert_eq!(plan_title("dart-print"), "Review print");
        assert_eq!(action_suggestion("tab-indent"), "Replace tabs with spaces.");
    }
}