use std::path::Path;
use anyhow::{Context, Result};
use serde::Serialize;
use crate::core::calibration::Calibration;
use crate::core::config::load_from_project;
use crate::core::HealPaths;
use crate::observers::{build_calibration, run_all};
pub fn run(project: &Path, force: bool, as_json: bool) -> Result<()> {
let paths = HealPaths::new(project);
let cfg = load_from_project(project).with_context(|| {
format!(
"loading {} (run `heal init` first?)",
paths.config().display(),
)
})?;
let calibration_path = paths.calibration();
if !force && calibration_path.exists() {
run_status(&paths, as_json);
return Ok(());
}
let reports = run_all(project, &cfg, None, None);
let calibration = build_calibration(project, &reports, &cfg);
calibration.save(&calibration_path)?;
if as_json {
super::emit_json(&CalibrateReport {
kind: "recalibrated",
path: calibration_path.display().to_string(),
calibration: Some(&calibration),
});
return Ok(());
}
println!("Recalibrated {}", calibration_path.display());
println!(" codebase_files: {}", calibration.meta.codebase_files);
if let Some(sha) = calibration.meta.calibrated_at_sha.as_deref() {
println!(" calibrated_at: {}", &sha[..sha.len().min(12)]);
}
if let Some(c) = calibration.calibration.ccn.as_ref() {
println!(" ccn p95: {:.1}", c.p95);
}
if let Some(c) = calibration.calibration.cognitive.as_ref() {
println!(" cognitive p95: {:.1}", c.p95);
}
if let Some(c) = calibration.calibration.hotspot.as_ref() {
println!(" hotspot p90: {:.1}", c.p90);
}
Ok(())
}
fn run_status(paths: &HealPaths, as_json: bool) {
let calibration_path = paths.calibration();
let Ok(calibration) = Calibration::load(&calibration_path) else {
if as_json {
super::emit_json(&CalibrateReport {
kind: "missing",
path: calibration_path.display().to_string(),
calibration: None,
});
} else {
println!(
"no calibration at {} — re-run `heal calibrate --force` to create one",
calibration_path.display(),
);
}
return;
};
if as_json {
super::emit_json(&CalibrateReport {
kind: "ok",
path: calibration_path.display().to_string(),
calibration: Some(&calibration),
});
return;
}
println!(
"calibration present at {} (created {})",
calibration_path.display(),
calibration.meta.created_at.format("%Y-%m-%d"),
);
if let Some(sha) = calibration.meta.calibrated_at_sha.as_deref() {
println!(" calibrated_at_sha: {}", &sha[..sha.len().min(12)]);
}
println!(
" calibrated_at_files: {}",
calibration.meta.codebase_files,
);
println!(
"Run `heal calibrate --force` to rebuild the percentile breaks from the current codebase."
);
}
#[derive(Debug, Serialize)]
struct CalibrateReport<'a> {
kind: &'static str,
path: String,
#[serde(skip_serializing_if = "Option::is_none")]
calibration: Option<&'a Calibration>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::init_project_with_config;
use tempfile::TempDir;
fn init_project(dir: &Path) {
let _ = init_project_with_config(dir, "fn main() {}\n");
}
#[test]
fn calibrate_writes_calibration_toml_when_missing() {
let dir = TempDir::new().unwrap();
init_project(dir.path());
let paths = HealPaths::new(dir.path());
run(dir.path(), false, false).unwrap();
assert!(
paths.calibration().exists(),
"calibration.toml must be written"
);
let calibration = Calibration::load(&paths.calibration()).unwrap();
assert!(
calibration.meta.calibrated_at_sha.is_some(),
"calibrated_at_sha must be captured from HEAD"
);
}
#[test]
fn calibrate_default_does_not_rewrite_when_calibration_exists() {
let dir = TempDir::new().unwrap();
init_project(dir.path());
let paths = HealPaths::new(dir.path());
run(dir.path(), true, false).unwrap();
let mtime_before = std::fs::metadata(paths.calibration())
.unwrap()
.modified()
.unwrap();
run(dir.path(), false, false).unwrap();
let mtime_after = std::fs::metadata(paths.calibration())
.unwrap()
.modified()
.unwrap();
assert_eq!(
mtime_before, mtime_after,
"default run must not rewrite calibration.toml"
);
}
#[test]
fn calibrate_force_rewrites_existing_calibration() {
let dir = TempDir::new().unwrap();
init_project(dir.path());
let paths = HealPaths::new(dir.path());
run(dir.path(), false, false).unwrap();
let first_created = Calibration::load(&paths.calibration())
.unwrap()
.meta
.created_at;
run(dir.path(), true, false).unwrap();
let second_created = Calibration::load(&paths.calibration())
.unwrap()
.meta
.created_at;
assert!(
second_created >= first_created,
"--force must produce a fresh calibration"
);
}
}