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,
})
}