repopilot 0.11.0

Local-first CLI for repository audit, architecture risk detection, baseline tracking, and CI-friendly code review.
Documentation
use crate::output::OutputFormat;

use super::diff::CompareSummary;

pub fn render_json(summary: &CompareSummary) -> Result<String, serde_json::Error> {
    serde_json::to_string_pretty(summary)
}

pub fn render_console(summary: &CompareSummary) -> String {
    let mut out = String::new();

    out.push_str("RepoPilot Compare Report\n");
    out.push_str(&"".repeat(40));
    out.push('\n');

    let files_delta = summary.after_files as i64 - summary.before_files as i64;
    let loc_delta = summary.after_loc as i64 - summary.before_loc as i64;

    out.push_str(&format!(
        "Files:       {}{}  ({:+})\n",
        summary.before_files, summary.after_files, files_delta
    ));
    out.push_str(&format!(
        "LOC:         {}{}  ({:+})\n",
        summary.before_loc, summary.after_loc, loc_delta
    ));
    out.push('\n');
    out.push_str(&format!(
        "New findings:       {:>4}\n",
        summary.new_findings.len()
    ));
    out.push_str(&format!(
        "Resolved:           {:>4}\n",
        summary.resolved_findings.len()
    ));
    out.push_str(&format!(
        "Severity increased: {:>4}\n",
        summary.severity_increased.len()
    ));

    if !summary.new_findings.is_empty() {
        out.push_str("\n── New findings ──────────────────────────\n");
        for f in &summary.new_findings {
            out.push_str(&format!(
                "  [{}] {}{}\n",
                f.severity_label(),
                f.rule_id,
                f.title
            ));
        }
    }

    if !summary.resolved_findings.is_empty() {
        out.push_str("\n── Resolved findings ─────────────────────\n");
        for f in &summary.resolved_findings {
            out.push_str(&format!(
                "  [{}] {}{}\n",
                f.severity_label(),
                f.rule_id,
                f.title
            ));
        }
    }

    if !summary.severity_increased.is_empty() {
        out.push_str("\n── Severity increased ────────────────────\n");
        for (f, old_sev) in &summary.severity_increased {
            out.push_str(&format!(
                "  {} → [{}] {}\n",
                old_sev.label(),
                f.severity_label(),
                f.rule_id
            ));
        }
    }

    out
}

pub fn render_markdown(summary: &CompareSummary) -> String {
    let mut out = String::new();

    out.push_str("# RepoPilot Compare Report\n\n");
    out.push_str("## Overview\n\n");
    out.push_str("| Metric | Before | After | Delta |\n| --- | ---: | ---: | ---: |\n");

    let files_delta = summary.after_files as i64 - summary.before_files as i64;
    let loc_delta = summary.after_loc as i64 - summary.before_loc as i64;
    let new_count = summary.new_findings.len() as i64;
    let resolved_count = summary.resolved_findings.len() as i64;

    out.push_str(&format!(
        "| Files | {} | {} | {:+} |\n",
        summary.before_files, summary.after_files, files_delta
    ));
    out.push_str(&format!(
        "| LOC | {} | {} | {:+} |\n",
        summary.before_loc, summary.after_loc, loc_delta
    ));
    out.push_str(&format!(
        "| Findings | — | — | {:+} |\n\n",
        new_count - resolved_count
    ));

    if !summary.new_findings.is_empty() {
        out.push_str("## New findings\n\n");
        out.push_str("| Severity | Rule | Title |\n| --- | --- | --- |\n");
        for f in &summary.new_findings {
            out.push_str(&format!(
                "| {} | `{}` | {} |\n",
                f.severity_label(),
                f.rule_id,
                f.title
            ));
        }
        out.push('\n');
    }

    if !summary.resolved_findings.is_empty() {
        out.push_str("## Resolved findings\n\n");
        out.push_str("| Severity | Rule | Title |\n| --- | --- | --- |\n");
        for f in &summary.resolved_findings {
            out.push_str(&format!(
                "| {} | `{}` | {} |\n",
                f.severity_label(),
                f.rule_id,
                f.title
            ));
        }
        out.push('\n');
    }

    out
}

pub fn render(summary: &CompareSummary, format: OutputFormat) -> Result<String, serde_json::Error> {
    match format {
        OutputFormat::Console => Ok(render_console(summary)),
        OutputFormat::Json => render_json(summary),
        OutputFormat::Markdown => Ok(render_markdown(summary)),
        OutputFormat::Html | OutputFormat::Sarif => {
            unreachable!("HTML and SARIF are not supported for the compare command")
        }
    }
}