arcature-cli 2026.1.1

Developer lifecycle CLI for Arcature applications.
Documentation
//! The `arc doctor --checks` execution + report printing (AP2.1-7).
//!
//! Aggregation logic that runs the framework's own checks and prints the
//! report. Separated from `mod.rs` so `mod.rs` carries only the module
//! declaration (AGENTS.md §1).

use crate::cli::OutputFormat;
use crate::error::CommandError;
use arcature::system_check::SystemCheckReport;
use arcature::{FRAMEWORK_CHECKS, run_checks};

/// Run the system-check framework and print the report.
///
/// Runs the framework's own `FRAMEWORK_CHECKS` slice (and, in a later wave,
/// any application-contributed slices collected from the app binary). The
/// report never aborts on a single check's failure (see
/// `system_check::run`); a check with `status = Fail` and `severity =
/// Error` causes the command to return `Err` so CI / `arc doctor --checks`
/// in a pre-commit hook exits non-zero on a real problem.
pub(crate) fn execute(format: OutputFormat) -> Result<(), CommandError> {
    // This wave runs only the framework's own checks. The application's
    // contributed slices are collected from the app binary in a later wave
    // (AP2.1-10); for now, the framework checks exercise the full
    // run → report → output path end to end.
    let slices: &[&'static [&'static dyn arcature::SystemCheck]] = &[FRAMEWORK_CHECKS];
    let report = run_checks(slices);
    print(&report, format)?;
    if report.has_error() {
        Err(CommandError::Unhealthy("system checks"))
    } else {
        Ok(())
    }
}

fn print(report: &SystemCheckReport, format: OutputFormat) -> Result<(), serde_json::Error> {
    match format {
        OutputFormat::Human => {
            print_human(report);
            Ok(())
        }
        OutputFormat::Json => print_json(report),
    }
}

fn print_human(report: &SystemCheckReport) {
    if report.is_empty() {
        println!("no system checks registered");
        return;
    }
    for r in &report.results {
        println!(
            "{:<8} {:<8} {:<14} {}",
            r.id, r.status, r.severity, r.description,
        );
        println!("         context: {}", r.context);
        if let Some(hint) = &r.fix_hint {
            println!("         fix:    {hint}");
        }
    }
    let errors = report
        .results
        .iter()
        .filter(|r| r.status == arcature::CheckStatus::Fail)
        .count();
    println!(
        "\n{} check(s): {} passed, {} failed, {} skipped",
        report.len(),
        report
            .results
            .iter()
            .filter(|r| r.status == arcature::CheckStatus::Pass)
            .count(),
        errors,
        report
            .results
            .iter()
            .filter(|r| r.status == arcature::CheckStatus::Skip)
            .count(),
    );
}

fn print_json(report: &SystemCheckReport) -> Result<(), serde_json::Error> {
    let view = report.to_json_view();
    println!("{}", serde_json::to_string_pretty(&view)?);
    Ok(())
}