arcature-cli 2026.1.1

Developer lifecycle CLI for Arcature applications.
Documentation
use crate::cli::OutputFormat;
use crate::error::CommandError;
use crate::project;
use crate::tool::{HealthReport, HealthStatus, Tool, certification_status, probe};

/// `arc doctor` — environment + certified-stack diagnostics.
///
/// With `--checks`, runs ONLY the AP2.1-7 system-check framework instead of
/// the environment/certification diagnostics (delegated to the `system_check`
/// consumer). Without `--checks`, runs the existing environment/certification
/// diagnostics (unchanged). The two surfaces are deliberately separate: the
/// environment/certification diagnostics inspect the developer's machine and
/// the certified-stack matrix (a `HealthReport`); the system-check framework
/// is the extensible `&'static`-registered `SystemCheck` layer subsystems and
/// the application contribute to. `arc doctor` (no flag) keeps its existing
/// behavior so existing tooling and docs stay valid; `arc doctor --checks`
/// is the new framework's entry point.
pub(crate) fn execute(format: OutputFormat, checks_only: bool) -> Result<(), CommandError> {
    if checks_only {
        return crate::system_check::execute(format);
    }
    execute_environment(format)
}

fn execute_environment(format: OutputFormat) -> Result<(), CommandError> {
    let mut report = HealthReport::default();
    report.push(
        "platform",
        HealthStatus::Ok,
        format!("{} {}", std::env::consts::OS, std::env::consts::ARCH),
    );
    add_tool(&mut report, Tool::Rustc);
    add_tool(&mut report, Tool::Cargo);
    add_tool(&mut report, Tool::Node);
    add_tool(&mut report, Tool::Pnpm);
    match project::discover() {
        Ok(project) => {
            add_project(&mut report, &project);
            // Certified Stack Contract: compare the project's Cargo.lock and
            // frontend package.json against the machine-readable contract. An
            // override outside the certified matrix is reported as UNVERIFIED
            // (not an error) — certification is information policy, not
            // ecosystem lock-in.
            add_certified_stack(&mut report, &project);
        }
        Err(error) => report.push("project", HealthStatus::Warning, error.to_string()),
    }
    output(&report, format)?;
    if report.has_error() {
        Err(CommandError::Unhealthy("environment"))
    } else {
        Ok(())
    }
}

fn add_certified_stack(report: &mut HealthReport, project: &project::ProjectConfig) {
    let contract = crate::stack::load();
    report.push(
        "certified stack",
        HealthStatus::Ok,
        format!(
            "snapshot {} (arcature {})",
            contract.snapshot.date, contract.snapshot.arcature_version
        ),
    );
    let lockfile = project.root().join("Cargo.lock");
    crate::stack::check_cargo_lock(&contract, &lockfile, report);
    let package_json = project.frontend_root().join("package.json");
    crate::stack::check_frontend(&contract, &package_json, report);
    // Report the certified service matrix (informational — services are
    // environment-driven, not manifest-driven; this is a reference of what
    // governance CI tested).
    for (name, service) in &contract.services {
        report.push(
            "service",
            HealthStatus::Ok,
            format!(
                "{} {} (certified: {})",
                name,
                service.role,
                service.certified_versions.join(", ")
            ),
        );
    }
}

fn add_tool(report: &mut HealthReport, tool: Tool) {
    match probe(tool) {
        Ok(version) => report.push(
            tool.program(),
            certification_status(tool, version),
            version.to_string(),
        ),
        Err(error) => report.push(tool.program(), HealthStatus::Error, error.to_string()),
    }
}

fn add_project(report: &mut HealthReport, project: &project::ProjectConfig) {
    report.push(
        "project",
        HealthStatus::Ok,
        format!(
            "{} ({})",
            project.root().display(),
            project.frontend.as_str()
        ),
    );
    let lockfile = project.frontend_root().join("pnpm-lock.yaml");
    report.push(
        "lockfile",
        if lockfile.is_file() {
            HealthStatus::Ok
        } else {
            HealthStatus::Error
        },
        lockfile.display().to_string(),
    );
    let manifest = project.frontend_root().join("package.json");
    let vite = std::fs::read_to_string(&manifest)
        .map_err(|error| error.to_string())
        .and_then(|contents| {
            serde_json::from_str::<serde_json::Value>(&contents).map_err(|error| error.to_string())
        })
        .ok()
        .and_then(|value| {
            value
                .get("devDependencies")?
                .get("vite")?
                .as_str()
                .map(str::to_owned)
        });
    match vite {
        Some(version) => report.push(
            "vite",
            if version == "8.2.1" {
                HealthStatus::Ok
            } else {
                HealthStatus::Unverified
            },
            version,
        ),
        None => report.push(
            "vite",
            HealthStatus::Error,
            format!("missing or invalid dependency in {}", manifest.display()),
        ),
    }
}

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