repopilot 0.15.0

Local-first CLI for repository audit, architecture risk detection, baseline tracking, and CI-friendly code review.
Documentation
pub(crate) fn render_risk_summary(output: &mut String, summary: &ScanSummary, stats: &ReportStats) {
    output.push_str("## Risk Summary\n\n");

    if summary.artifacts.findings.is_empty() {
        output.push_str("No findings found.\n\n");
        return;
    }

    writeln!(
        output,
        "- **Severity:** {}",
        markdown_severity_counts_text(stats)
    )
    .unwrap();
    writeln!(
        output,
        "- **Categories:** {}",
        named_counts_text(&stats.category_counts)
    )
    .unwrap();
    if !stats.top_paths.is_empty() {
        writeln!(
            output,
            "- **Top paths:** {}",
            named_counts_text(&stats.top_paths)
        )
        .unwrap();
    }
    if !stats.top_packages.is_empty() {
        writeln!(
            output,
            "- **Top packages:** {}",
            named_counts_text(&stats.top_packages)
        )
        .unwrap();
    }
    output.push('\n');
}

pub(crate) fn render_signal_quality(output: &mut String, summary: &ScanSummary) {
    let quality = &summary.artifacts.signal_quality;
    output.push_str("## Signal Quality\n\n");
    if summary.metrics.raw_findings_count > summary.metrics.visible_findings_count {
        writeln!(
            output,
            "- **Visible findings:** {}",
            summary.metrics.visible_findings_count
        )
        .unwrap();
        writeln!(
            output,
            "- **Raw findings:** {}",
            summary.metrics.raw_findings_count
        )
        .unwrap();
        writeln!(
            output,
            "- **Raw high confidence:** {}",
            summary.artifacts.raw_signal_quality.by_confidence.high
        )
        .unwrap();
    }
    writeln!(
        output,
        "- **High confidence:** {}",
        quality.by_confidence.high
    )
    .unwrap();
    writeln!(
        output,
        "- **Medium confidence:** {}",
        quality.by_confidence.medium
    )
    .unwrap();
    writeln!(
        output,
        "- **Low confidence:** {}",
        quality.by_confidence.low
    )
    .unwrap();
    writeln!(
        output,
        "- **Evidence coverage:** {}%",
        quality.evidence_coverage_percent
    )
    .unwrap();
    writeln!(
        output,
        "- **Recommendation coverage:** {}%",
        quality.recommendation_coverage_percent
    )
    .unwrap();
    writeln!(
        output,
        "- **Contract warnings:** {}",
        quality.contract_violations
    )
    .unwrap();
    output.push('\n');
}

pub(crate) fn render_top_rules(output: &mut String, stats: &ReportStats) {
    output.push_str("## Top Rules\n\n");

    if stats.top_rules.is_empty() {
        output.push_str("No rules triggered.\n\n");
        return;
    }

    output.push_str("| Rule | Count | Max severity |\n");
    output.push_str("| --- | ---: | --- |\n");
    for rule in &stats.top_rules {
        let severity = rule
            .severity
            .map(|severity| severity.label())
            .unwrap_or("INFO");
        writeln!(
            output,
            "| `{}` | {} | {} |",
            escape_table_cell(&rule.label),
            rule.count,
            severity
        )
        .unwrap();
    }
    output.push('\n');
}

pub(crate) fn render_top_risk_clusters(output: &mut String, findings: &[Finding]) {
    output.push_str("## Top Risk Clusters\n\n");

    if findings.is_empty() {
        output.push_str("No risk clusters found.\n\n");
        return;
    }

    let finding_refs = findings.iter().collect::<Vec<_>>();
    let mut clusters = clusters_by_rule_scope(&finding_refs);
    clusters.sort_by(|left, right| {
        left.priority
            .rank()
            .cmp(&right.priority.rank())
            .then_with(|| right.max_score.cmp(&left.max_score))
            .then_with(|| right.findings.len().cmp(&left.findings.len()))
            .then_with(|| left.rule_id.cmp(right.rule_id))
            .then_with(|| left.scope.cmp(&right.scope))
    });
    clusters.truncate(8);

    output.push_str("| Priority | Area | Rule | Count | Max severity | Max risk | Examples |\n");
    output.push_str("| --- | --- | --- | ---: | --- | ---: | --- |\n");
    for cluster in clusters {
        let area = cluster.scope.as_deref().unwrap_or(".");
        let examples = example_locations(&cluster.findings, 2).join(", ");
        writeln!(
            output,
            "| {} | `{}` | `{}` | {} | {} | {} | {} |",
            cluster.priority.label(),
            escape_table_cell(area),
            escape_table_cell(cluster.rule_id),
            cluster.findings.len(),
            cluster.severity.label(),
            cluster.max_score,
            escape_table_cell(&examples)
        )
        .unwrap();
    }
    output.push('\n');
}