ccd-cli 1.0.0-beta.3

Bootstrap and validate Continuous Context Development repositories
use std::path::Path;
use std::process::ExitCode;

use anyhow::{bail, Result};
use serde::Serialize;

use crate::output::CommandReport;
use crate::paths::state::StateLayout;
use crate::profile;
use crate::repo::marker as repo_marker;
use crate::state::consistency;

#[derive(Serialize)]
pub struct ConsistencyReport {
    command: &'static str,
    ok: bool,
    repo_root: String,
    profile: String,
    locality_id: String,
    status: consistency::ConsistencyAggregateStatus,
    summary: String,
    evidence: Vec<String>,
    recommended_corrections: Vec<String>,
    axes: Vec<consistency::ConsistencyAxis>,
}

impl CommandReport for ConsistencyReport {
    fn exit_code(&self) -> ExitCode {
        if self.ok {
            ExitCode::SUCCESS
        } else {
            ExitCode::from(1)
        }
    }

    fn render_text(&self) {
        for axis in &self.axes {
            let label = match axis.status {
                consistency::ConsistencyStatus::Aligned => "ALIGNED",
                consistency::ConsistencyStatus::Drift => "DRIFT",
                consistency::ConsistencyStatus::NoSignal => "NO SIGNAL",
            };
            println!("[{label}] {}: {}", axis.id, axis.summary);
            for line in &axis.evidence {
                println!("  {line}");
            }
        }
        println!("Consistency summary: {}", self.summary);
    }
}

pub fn run(repo_root: &Path, explicit_profile: Option<&str>) -> Result<ConsistencyReport> {
    let profile = profile::resolve(explicit_profile)?;
    let layout = StateLayout::resolve(repo_root, profile.clone())?;
    if !layout.profile_root().is_dir() {
        bail!(
            "profile `{}` does not exist at {}; bootstrap it with `ccd attach` before using `ccd consistency`",
            layout.profile(),
            layout.profile_root().display()
        );
    }

    let marker = repo_marker::load(repo_root)?.ok_or_else(|| {
        anyhow::anyhow!(
            "repo is not linked: {} is missing; bootstrap this clone with `ccd attach` or `ccd link` first",
            repo_root.join(repo_marker::MARKER_FILE).display()
        )
    })?;

    let evaluation = consistency::evaluate(repo_root, &layout, &marker.locality_id)?;
    Ok(ConsistencyReport {
        command: "consistency",
        ok: !evaluation.has_drift(),
        repo_root: repo_root.display().to_string(),
        profile: profile.to_string(),
        locality_id: marker.locality_id,
        status: evaluation.status,
        summary: evaluation.summary,
        evidence: evaluation.evidence,
        recommended_corrections: evaluation.recommended_corrections,
        axes: evaluation.axes,
    })
}