apo 0.1.0

APO — Engineering Evidence Platform. Repository Hygiene analyzer.
Documentation
//! Markdown report writer.

use std::fmt::Write as _;
use std::path::Path;

use crate::error::Result;
use crate::evidence::Status;
use crate::report::Report;

pub fn write_markdown(report: &Report, path: &Path) -> Result<()> {
    std::fs::write(path, render_markdown(report))?;
    Ok(())
}

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

    let _ = writeln!(out, "# Repository Hygiene Report");
    let _ = writeln!(out);
    let _ = writeln!(out, "- **Analyzer:** {}", report.analyzer);
    let _ = writeln!(out, "- **APO version:** {}", report.apo_version);
    let _ = writeln!(out, "- **Repository:** `{}`", report.repository);
    if let Some(uri) = &report.source_uri {
        let _ = writeln!(out, "- **Source URI:** `{uri}`");
    }
    if let Some(path) = &report.checkout_path
        && report.source_uri.is_some()
    {
        let _ = writeln!(out, "- **Checkout path:** `{path}`");
    }
    let _ = writeln!(out, "- **Generated:** {}", report.generated_at);
    if let Some(score) = report.policy.overall_score {
        let _ = writeln!(out, "- **Overall score:** {score:.1}/100");
    }
    let _ = writeln!(out);

    let _ = writeln!(out, "## Executive summary");
    let _ = writeln!(out);
    let _ = writeln!(out, "{}", report.executive_summary);
    let _ = writeln!(out);

    let _ = writeln!(out, "## Category scores");
    let _ = writeln!(out);
    let _ = writeln!(
        out,
        "| Category | Weight | Score | Contribution | Enforced | Present | Partial | Missing | Unknown |"
    );
    let _ = writeln!(
        out,
        "|----------|-------:|------:|-------------:|---------:|--------:|--------:|--------:|--------:|"
    );
    for c in &report.policy.categories {
        let score = c
            .score
            .map(|s| format!("{s:.1}"))
            .unwrap_or_else(|| "n/a".into());
        let contrib = c
            .weighted_contribution
            .map(|s| format!("{s:.1}"))
            .unwrap_or_else(|| "".into());
        let _ = writeln!(
            out,
            "| {} | {:.0}% | {} | {} | {} | {} | {} | {} | {} |",
            c.name,
            c.weight * 100.0,
            score,
            contrib,
            c.enforced,
            c.present,
            c.partial,
            c.missing,
            c.unknown
        );
    }
    let _ = writeln!(out);
    let _ = writeln!(
        out,
        "_Rubric `{}`: weighted overall from category scores. Notes: {}._",
        report.policy.rubric,
        report
            .policy
            .categories
            .iter()
            .map(|c| format!("{}{}", c.name, c.note))
            .collect::<Vec<_>>()
            .join("; ")
    );
    let _ = writeln!(out);

    let _ = writeln!(out, "## Findings");
    let _ = writeln!(out);
    for f in &report.findings {
        let _ = writeln!(out, "### `{}`", f.rule);
        let _ = writeln!(out);
        let _ = writeln!(out, "- **Category:** {}", f.category.display_name());
        let _ = writeln!(out, "- **Status:** {:?}", f.status);
        let _ = writeln!(out, "- **Confidence:** {:?}", f.confidence);
        let _ = writeln!(out, "- **Summary:** {}", f.summary);
        if let Some(r) = &f.remediation {
            let _ = writeln!(out, "- **Remediation:** {r}");
        }
        if !f.evidence.is_empty() {
            let _ = writeln!(out, "- **Evidence:**");
            for e in &f.evidence {
                match (&e.path, &e.detail) {
                    (Some(p), Some(d)) => {
                        let _ = writeln!(out, "  - `{p}` — {d}");
                    }
                    (Some(p), None) => {
                        let _ = writeln!(out, "  - `{p}`");
                    }
                    (None, Some(d)) => {
                        let _ = writeln!(out, "  - {d}");
                    }
                    (None, None) => {}
                }
            }
        }
        let _ = writeln!(out);
    }

    let _ = writeln!(out, "## Missing controls");
    let _ = writeln!(out);
    if report.missing_controls.is_empty() {
        let _ = writeln!(out, "No gap signals recorded.");
    } else {
        for g in &report.missing_controls {
            let _ = writeln!(out, "- `{g}`");
        }
    }
    let _ = writeln!(out);

    let _ = writeln!(out, "## Recommendations");
    let _ = writeln!(out);
    if report.recommendations.is_empty() {
        let _ = writeln!(out, "No recommendations.");
    } else {
        for r in &report.recommendations {
            let _ = writeln!(out, "- {r}");
        }
    }
    let _ = writeln!(out);

    let _ = writeln!(out, "## Evidence appendix");
    let _ = writeln!(out);
    let _ = writeln!(out, "| Rule | Status | Paths |");
    let _ = writeln!(out, "|------|--------|-------|");
    for f in &report.findings {
        let paths: Vec<_> = f
            .evidence
            .iter()
            .filter_map(|e| e.path.as_deref())
            .collect();
        let path_str = if paths.is_empty() {
            "".into()
        } else {
            paths.join(", ")
        };
        let status = match f.status {
            Status::Enforced => "Enforced",
            Status::Present => "Present",
            Status::Partial => "Partial",
            Status::Missing => "Missing",
            Status::NotApplicable => "NotApplicable",
            Status::Unknown => "Unknown",
        };
        let _ = writeln!(out, "| `{}` | {status} | {path_str} |", f.rule);
    }
    let _ = writeln!(out);

    out
}