use crate::cli::args::CorpusOutputFormat;
use crate::cli::logic::truncate_str;
use crate::models::{Config, Error, Result};
use std::path::PathBuf;
pub(crate) fn corpus_print_score(
score: &crate::corpus::runner::CorpusScore,
format: &CorpusOutputFormat,
) -> Result<()> {
use crate::cli::color::*;
match format {
CorpusOutputFormat::Human => {
let grade_str = score.grade.to_string();
let gc = grade_color(&grade_str);
let fail_color = if score.failed == 0 { GREEN } else { BRIGHT_RED };
let score_str = format!("{:.1}", score.score);
let pad_len = 18_usize.saturating_sub(score_str.len() + grade_str.len());
println!("{DIM}╭──────────────────────────────────────────────╮{RESET}");
println!(
"{DIM}│{RESET} V2 Corpus Score: {WHITE}{}/100{RESET} ({gc}{grade_str}{RESET}){:>pad$}{DIM}│{RESET}",
score_str, "",
pad = pad_len
);
println!(
"{DIM}│{RESET} Entries: {} total, {GREEN}{} passed{RESET}, {fail_color}{} failed{RESET} ({:.1}%) {DIM}│{RESET}",
score.total, score.passed, score.failed, score.rate * 100.0
);
println!("{DIM}╰──────────────────────────────────────────────╯{RESET}");
println!();
for fs in &score.format_scores {
let fgs = fs.grade.to_string();
let fgc = grade_color(&fgs);
let pc = pct_color(fs.passed as f64 / fs.total.max(1) as f64 * 100.0);
println!(
" {CYAN}{:<12}{RESET} {WHITE}{:.1}/100{RESET} ({fgc}{fgs}{RESET}) — {pc}{}/{} passed{RESET}",
format!("{}:", fs.format), fs.score, fs.passed, fs.total
);
}
if !score.results.is_empty() {
let n = score.results.len();
let a_pass = score.results.iter().filter(|r| r.transpiled).count();
let b1_pass = score.results.iter().filter(|r| r.output_contains).count();
let b2_pass = score.results.iter().filter(|r| r.output_exact).count();
let b3_pass = score.results.iter().filter(|r| r.output_behavioral).count();
let d_pass = score.results.iter().filter(|r| r.lint_clean).count();
let e_pass = score.results.iter().filter(|r| r.deterministic).count();
let f_pass = score
.results
.iter()
.filter(|r| r.metamorphic_consistent)
.count();
let g_pass = score.results.iter().filter(|r| r.cross_shell_agree).count();
let c_avg: f64 =
score.results.iter().map(|r| r.coverage_ratio).sum::<f64>() / n as f64;
let pct_val = |pass: usize| -> f64 { pass as f64 / n as f64 * 100.0 };
let pts = |pass: usize, max: f64| -> f64 { pass as f64 / n as f64 * max };
println!();
println!("{BOLD}V2 Component Breakdown:{RESET}");
let print_dim = |label: &str, pass: usize, max_pts: f64| {
let p = pct_val(pass);
let pc = pct_color(p);
let bar = progress_bar(pass, n, 16);
println!(
" {WHITE}{:<2} {:<14}{RESET} {pc}{:>4}/{}{RESET} ({pc}{:.1}%{RESET}) {bar} {WHITE}{:.1}/{}{RESET} pts",
label.split_whitespace().next().unwrap_or(""),
label.split_whitespace().skip(1).collect::<Vec<_>>().join(" "),
pass, n, p, pts(pass, max_pts), max_pts as u32
);
};
print_dim("A Transpilation", a_pass, 30.0);
print_dim("B1 Containment", b1_pass, 10.0);
print_dim("B2 Exact match", b2_pass, 8.0);
print_dim("B3 Behavioral", b3_pass, 7.0);
let c_pct = c_avg * 100.0;
let cc = pct_color(c_pct);
let c_bar = progress_bar((c_avg * n as f64) as usize, n, 16);
println!(
" {WHITE}C Coverage {RESET} {cc}avg {:.1}%{RESET} {c_bar} {WHITE}{:.1}/15{RESET} pts",
c_pct, c_avg * 15.0
);
print_dim("D Lint clean", d_pass, 10.0);
print_dim("E Deterministic", e_pass, 10.0);
print_dim("F Metamorphic", f_pass, 5.0);
print_dim("G Cross-shell", g_pass, 5.0);
}
let failures: Vec<_> = score.results.iter().filter(|r| !r.transpiled).collect();
if !failures.is_empty() {
println!();
println!("{BRIGHT_RED}Failed entries ({}):{RESET}", failures.len());
for f in &failures {
let err = f.error.as_deref().unwrap_or("unknown error");
println!(
" {CYAN}{}{RESET} — {DIM}{}{RESET}",
f.id,
truncate_str(err, 80)
);
}
}
}
CorpusOutputFormat::Json => {
let json = serde_json::to_string_pretty(score)
.map_err(|e| Error::Internal(format!("JSON serialization failed: {e}")))?;
println!("{json}");
}
}
Ok(())
}
pub(crate) fn corpus_write_convergence_log(
runner: &crate::corpus::runner::CorpusRunner,
score: &crate::corpus::runner::CorpusScore,
) -> Result<()> {
use crate::corpus::runner::CorpusRunner;
let log_path = PathBuf::from(".quality/convergence.log");
let previous = CorpusRunner::load_convergence_log(&log_path).unwrap_or_default();
let iteration = previous.len() as u32 + 1;
let prev_rate = previous.last().map_or(0.0, |e| e.rate);
let date = super::corpus_diff_commands::chrono_free_date();
let entry = runner.convergence_entry(score, iteration, &date, prev_rate, "CLI corpus run");
CorpusRunner::append_convergence_log(&entry, &log_path)
.map_err(|e| Error::Internal(format!("Failed to write convergence log: {e}")))?;
use crate::cli::color::*;
println!();
let dc = delta_color(entry.delta);
let sc = pct_color(entry.score);
println!(
"{DIM}Convergence log:{RESET} iteration {}, {sc}{:.1}/100 {}{RESET}, delta {dc}",
iteration, entry.score, entry.grade
);
if entry.bash_total > 0 || entry.makefile_total > 0 || entry.dockerfile_total > 0 {
let fmt_part = |name: &str, passed: usize, total: usize| -> String {
if total > 0 {
format!("{name} {passed}/{total}")
} else {
String::new()
}
};
let parts: Vec<String> = [
fmt_part("Bash", entry.bash_passed, entry.bash_total),
fmt_part("Make", entry.makefile_passed, entry.makefile_total),
fmt_part("Docker", entry.dockerfile_passed, entry.dockerfile_total),
]
.into_iter()
.filter(|s| !s.is_empty())
.collect();
if !parts.is_empty() {
println!("{DIM} Per-format:{RESET} {}", parts.join(", "));
}
}
if entry.lint_passed > 0 {
let lint_pct = entry.lint_rate * 100.0;
let lc = pct_color(lint_pct);
let gap = ((entry.rate - entry.lint_rate) * 100.0).abs();
let gap_str = if gap > 0.1 {
format!(" {DIM}(gap: {gap:.1}%){RESET}")
} else {
String::new()
};
println!(
"{DIM} Lint rate:{RESET} {lc}{lint_pct:.1}%{RESET} ({}/{}){}",
entry.lint_passed, entry.total, gap_str
);
}
if let Some(prev) = previous.last() {
let report = entry.detect_regressions(prev);
if report.has_regressions() {
println!();
println!("{BRIGHT_RED}ANDON CORD: Corpus regression detected!{RESET}");
for r in &report.regressions {
println!(" {BRIGHT_RED}• {}{RESET}", r.message);
}
println!("{BRIGHT_RED}STOP THE LINE — investigate before proceeding.{RESET}");
}
}
Ok(())
}
pub(crate) fn stats_bar(pct: f64, width: usize) -> String {
let filled = ((pct / 100.0) * width as f64).round() as usize;
let empty = width.saturating_sub(filled);
format!("{}{}", "█".repeat(filled), "░".repeat(empty))
}
pub(crate) fn corpus_show_stats(format: &CorpusOutputFormat) -> Result<()> {
use crate::corpus::registry::CorpusRegistry;
use crate::corpus::runner::CorpusRunner;
let config = Config::default();
let registry = CorpusRegistry::load_full();
let runner = CorpusRunner::new(config);
let score = runner.run(®istry);
match format {
CorpusOutputFormat::Human => {
use crate::cli::color::*;
println!("{BOLD}Corpus Statistics{RESET}");
println!("{DIM}═══════════════════════════════════════════════════{RESET}");
println!(
"{DIM}{:<12} {:>7} {:>10} {:>5} {:>16}{RESET}",
"Format", "Entries", "Pass Rate", "Grade", "Bar"
);
println!("{DIM}───────────────────────────────────────────────────{RESET}");
for fs in &score.format_scores {
let pct = fs.rate * 100.0;
let rc = pct_color(pct);
let gc = grade_color(&fs.grade.to_string());
let bar = stats_bar(pct, 16);
println!(
"{:<12} {:>7} {rc}{:>9.1}%{RESET} {gc}{:>5}{RESET} {rc}{bar}{RESET}",
fs.format, fs.total, pct, fs.grade,
);
}
println!("{DIM}───────────────────────────────────────────────────{RESET}");
let total_pct = score.rate * 100.0;
let tc = pct_color(total_pct);
let tg = grade_color(&score.grade.to_string());
let tbar = stats_bar(total_pct, 16);
println!(
"{BOLD}{:<12}{RESET} {:>7} {tc}{:>9.1}%{RESET} {tg}{:>5}{RESET} {tc}{tbar}{RESET}",
"Total", score.total, total_pct, score.grade,
);
let sc = pct_color(score.score);
println!();
println!(
"{BOLD}V2 Score:{RESET} {sc}{:.1}/100{RESET} ({tg}{}{RESET})",
score.score, score.grade
);
let log_path = PathBuf::from(".quality/convergence.log");
if let Ok(entries) = CorpusRunner::load_convergence_log(&log_path) {
if entries.len() >= 2 {
println!();
println!(
"{BOLD}Convergence Trend{RESET} (last {} runs):",
entries.len().min(10)
);
let recent: &[_] = if entries.len() > 10 {
&entries[entries.len() - 10..]
} else {
&entries
};
corpus_stats_sparkline(recent);
}
}
let failures: Vec<_> = score.results.iter().filter(|r| !r.transpiled).collect();
if !failures.is_empty() {
println!();
println!("{BOLD}Failing Entries{RESET} ({}):", failures.len());
for r in failures.iter().take(10) {
println!(" {BRIGHT_RED}• {}{RESET}", r.id);
}
if failures.len() > 10 {
println!(" {DIM}... and {} more{RESET}", failures.len() - 10);
}
}
}
CorpusOutputFormat::Json => {
#[derive(serde::Serialize)]
struct StatsJson {
total: usize,
passed: usize,
failed: usize,
rate: f64,
score: f64,
grade: String,
formats: Vec<FormatStats>,
}
#[derive(serde::Serialize)]
struct FormatStats {
format: String,
total: usize,
passed: usize,
rate: f64,
score: f64,
grade: String,
}
let stats = StatsJson {
total: score.total,
passed: score.passed,
failed: score.failed,
rate: score.rate,
score: score.score,
grade: score.grade.to_string(),
formats: score
.format_scores
.iter()
.map(|fs| FormatStats {
format: fs.format.to_string(),
total: fs.total,
passed: fs.passed,
rate: fs.rate,
score: fs.score,
grade: fs.grade.to_string(),
})
.collect(),
};
let json = serde_json::to_string_pretty(&stats)
.map_err(|e| Error::Internal(format!("JSON: {e}")))?;
println!("{json}");
}
}
Ok(())
}
pub(crate) fn corpus_stats_sparkline(entries: &[crate::corpus::runner::ConvergenceEntry]) {
use crate::cli::color::*;
let bars = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
let scores: Vec<f64> = entries.iter().map(|e| e.score).collect();
let min = scores.iter().copied().fold(f64::INFINITY, f64::min);
let max = scores.iter().copied().fold(f64::NEG_INFINITY, f64::max);
let range = (max - min).max(0.1);
let sparkline: String = scores
.iter()
.map(|&s| {
let idx = (((s - min) / range) * 7.0).round() as usize;
bars[idx.min(7)]
})
.collect();
let first = scores.first().copied().unwrap_or(0.0);
let last = scores.last().copied().unwrap_or(0.0);
let trend = if last > first {
GREEN
} else if last < first {
BRIGHT_RED
} else {
DIM
};
println!(
" {DIM}Score:{RESET} {trend}{sparkline}{RESET} ({:.1} → {:.1})",
first, last
);
}
pub(crate) const CORPUS_CACHE_PATH: &str = ".quality/last-corpus-run.json";
pub(crate) fn corpus_save_last_run(score: &crate::corpus::runner::CorpusScore) {
let path = std::path::Path::new(CORPUS_CACHE_PATH);
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
if let Ok(json) = serde_json::to_string(score) {
let _ = std::fs::write(path, json);
}
}
pub(crate) fn corpus_load_last_run() -> Option<crate::corpus::runner::CorpusScore> {
let data = std::fs::read_to_string(CORPUS_CACHE_PATH).ok()?;
serde_json::from_str(&data).ok()
}