use anyhow::Result;
use camino::Utf8PathBuf;
use crate::config::Config;
struct Check {
label: &'static str,
status: Status,
detail: String,
}
#[derive(PartialEq, Eq)]
enum Status {
Pass,
Warn,
Fail,
}
#[allow(clippy::unnecessary_wraps)]
pub fn run(cfg: &Config, target: &Utf8PathBuf) -> Result<()> {
println!("cordance doctor — pre-flight checks\n");
let checks = [
check_cordance_toml(target),
check_doctrine(cfg, target),
check_axiom(cfg, target),
check_axiom_latest(cfg, target),
check_sources_lock(target),
check_ollama(cfg),
];
let mut any_fail = false;
for c in &checks {
let sym = match c.status {
Status::Pass => "✓",
Status::Warn => "⚠",
Status::Fail => "✗",
};
println!(" {sym} {:<28} {}", c.label, c.detail);
if c.status == Status::Fail {
any_fail = true;
}
}
println!();
if any_fail {
println!(
"cordance doctor: some checks failed — run `cordance init` to create cordance.toml"
);
std::process::exit(1);
} else {
println!("cordance doctor: all checks passed");
}
Ok(())
}
fn check_cordance_toml(target: &Utf8PathBuf) -> Check {
let path = target.join("cordance.toml");
if path.exists() {
Check {
label: "cordance.toml",
status: Status::Pass,
detail: format!("found at {path}"),
}
} else {
Check {
label: "cordance.toml",
status: Status::Warn,
detail: format!("{path} not found (using defaults)"),
}
}
}
fn check_doctrine(cfg: &Config, target: &Utf8PathBuf) -> Check {
let root = cfg.doctrine_root(target);
if root.exists() {
Check {
label: "doctrine source",
status: Status::Pass,
detail: format!("{root} (exists)"),
}
} else {
Check {
label: "doctrine source",
status: Status::Fail,
detail: format!("{root} (not found)"),
}
}
}
fn check_axiom(cfg: &Config, target: &Utf8PathBuf) -> Check {
let root = cfg.axiom_root(target);
if root.exists() {
Check {
label: "axiom source",
status: Status::Pass,
detail: format!("{root} (exists)"),
}
} else {
Check {
label: "axiom source",
status: Status::Fail,
detail: format!("{root} (not found)"),
}
}
}
fn check_axiom_latest(cfg: &Config, target: &Utf8PathBuf) -> Check {
let root = cfg.axiom_root(target);
if !root.exists() {
return Check {
label: "axiom algorithm LATEST",
status: Status::Warn,
detail: "axiom source not found — skipping".into(),
};
}
let candidates = [
root.join("PAI/Algorithm/LATEST"),
root.join("axiom/Algorithm/LATEST"),
];
let found = candidates.iter().any(|p| p.exists());
if found {
let version = cfg.axiom_version(target);
Check {
label: "axiom algorithm LATEST",
status: Status::Pass,
detail: version,
}
} else {
Check {
label: "axiom algorithm LATEST",
status: Status::Warn,
detail: "not found".into(),
}
}
}
fn check_sources_lock(target: &Utf8PathBuf) -> Check {
let lock = target.join(".cordance/sources.lock");
if lock.exists() {
Check {
label: "sources.lock",
status: Status::Pass,
detail: "present".into(),
}
} else {
Check {
label: "sources.lock",
status: Status::Warn,
detail: "run 'cordance pack' first".into(),
}
}
}
fn check_ollama(cfg: &Config) -> Check {
match cfg.llm.provider.as_str() {
"none" => Check {
label: "Ollama (LLM)",
status: Status::Pass,
detail: "disabled (provider = none)".into(),
},
"ollama" => {
let adapter = cordance_llm::OllamaAdapter::from_config(&cfg.llm.ollama);
let base_url = adapter.base_url.clone();
let model = adapter.model.clone();
if adapter.is_available() {
Check {
label: "Ollama (LLM)",
status: Status::Pass,
detail: format!("reachable at {base_url} (model: {model})"),
}
} else {
Check {
label: "Ollama (LLM)",
status: Status::Fail,
detail: format!("not reachable at {base_url} — start Ollama first"),
}
}
}
other => Check {
label: "Ollama (LLM)",
status: Status::Warn,
detail: format!("unknown provider '{other}' — skipping"),
},
}
}