rta-core 0.1.0

Core analyzers and report model for the Rust Technical Audit Toolkit
Documentation
use crate::model::{AuditReport, RiskFinding};

pub fn render_markdown(report: &AuditReport) -> String {
    let mut out = String::new();
    out.push_str("# Rust Technical Audit Report\n\n");
    out.push_str("## Executive Summary\n\n");
    out.push_str(&format!(
        "Repository `{}` received an overall technical due diligence score of **{}/100**.\n\n",
        report.repository_path, report.overall_score
    ));
    out.push_str(&format!("{}\n\n", report.overview.summary));
    out.push_str("### Weighted Scoring Model\n\n");
    out.push_str("| Area | Weight | Score |\n| --- | ---: | ---: |\n");
    out.push_str(&format!(
        "| Dependency Health | 20% | {} |\n",
        report.dependencies.score
    ));
    out.push_str(&format!(
        "| Code Quality | 25% | {} |\n",
        report.code_quality.score
    ));
    out.push_str(&format!(
        "| Architecture | 25% | {} |\n",
        report.architecture.score
    ));
    out.push_str(&format!("| Testing | 15% | {} |\n", report.testing.score));
    out.push_str(&format!(
        "| Risk Posture | 15% | {} |\n\n",
        report.risks.score
    ));

    out.push_str("## Architecture\n\n");
    out.push_str(&format!(
        "- Style: {}\n- Separation of concerns: {}\n- Detected layers: {}\n- Domain boundaries: {}\n\n",
        report.architecture.architecture_style,
        report.architecture.separation_of_concerns,
        list_or_none(&report.architecture.detected_layers),
        list_or_none(&report.architecture.domain_boundaries)
    ));

    out.push_str("## Dependency Health\n\n");
    out.push_str(&format!(
        "- Total direct dependencies: {}\n- Critical dependencies: {}\n- Maintenance risks: {}\n- Outdated indicators: {}\n\n",
        report.dependencies.direct_dependencies,
        list_or_none(&report.dependencies.critical_dependencies),
        list_or_none(&report.dependencies.maintenance_risks),
        list_or_none(&report.dependencies.outdated_indicators)
    ));

    out.push_str("## Code Quality\n\n");
    out.push_str(&format!(
        "- Lines of Rust code: {}\n- Rust modules: {}\n- Function count: {}\n- Average function size: {:.1} lines\n- Large modules: {}\n- Potential God modules: {}\n\n",
        report.code_quality.lines_of_code,
        report.code_quality.module_count,
        report.code_quality.function_count,
        report.code_quality.average_function_size,
        list_or_none(&report.code_quality.large_modules),
        list_or_none(&report.code_quality.god_module_candidates)
    ));

    out.push_str("## Testing\n\n");
    out.push_str(&format!(
        "- {}\n- Unit test files: {}\n- Integration test files: {}\n- Test functions: {}\n\n",
        report.testing.testing_structure,
        report.testing.unit_test_files,
        report.testing.integration_test_files,
        report.testing.test_function_count
    ));

    out.push_str("## Risks\n\n");
    if report.risks.findings.is_empty() {
        out.push_str(
            "No material engineering risk findings were generated by the current rule set.\n\n",
        );
    } else {
        for finding in &report.risks.findings {
            push_finding(&mut out, finding);
        }
    }

    out.push_str("## Recommendations\n\n");
    out.push_str(recommendations(report).as_str());
    out.push('\n');
    out
}

fn push_finding(out: &mut String, finding: &RiskFinding) {
    out.push_str(&format!(
        "### {} ({})\n\nEvidence: {}\n\nRecommendation: {}\n\n",
        finding.title,
        finding.severity.as_str(),
        finding.evidence,
        finding.recommendation
    ));
}

fn recommendations(report: &AuditReport) -> String {
    let mut recommendations = Vec::new();
    if report.testing.score < 70 {
        recommendations.push("Increase automated test coverage around critical business flows.");
    }
    if report.architecture.score < 75 {
        recommendations.push("Document intended architectural boundaries and enforce them through crate or module ownership.");
    }
    if report.dependencies.score < 80 {
        recommendations.push("Create a dependency ownership and upgrade policy.");
    }
    if report.code_quality.score < 80 {
        recommendations
            .push("Prioritize decomposition of large modules and high-branching functions.");
    }
    if recommendations.is_empty() {
        recommendations.push(
            "Maintain current engineering practices and use periodic audits to watch trend lines.",
        );
    }

    recommendations
        .into_iter()
        .map(|item| format!("- {item}"))
        .collect::<Vec<_>>()
        .join("\n")
}

fn list_or_none(values: &[String]) -> String {
    if values.is_empty() {
        "none detected".to_string()
    } else {
        values.join(", ")
    }
}