use crate::models::{Config, Error, Result};
pub(crate) fn score_impact_color(score: f64) -> (String, &'static str) {
use crate::cli::color::*;
if score >= 0.8 {
(format!("{RED}HIGH{RESET}"), RED)
} else if score >= 0.5 {
(format!("{YELLOW}MEDIUM{RESET}"), YELLOW)
} else {
(format!("{DIM}LOW{RESET}"), DIM)
}
}
pub(crate) fn accumulate_decision_stats(
result: &crate::corpus::runner::CorpusResult,
stats: &mut std::collections::HashMap<String, (usize, usize, usize)>,
) -> bool {
let passed = result.transpiled
&& result.output_contains
&& result.schema_valid
&& result.lint_clean
&& result.deterministic;
let trace = match &result.decision_trace {
Some(t) => t,
None => return false,
};
for d in trace {
let key = format!("{}:{}", d.decision_type, d.choice);
let entry = stats.entry(key).or_insert((0, 0, 0));
entry.0 += 1;
if passed {
entry.1 += 1;
} else {
entry.2 += 1;
}
}
!trace.is_empty()
}
pub(crate) fn corpus_decisions() -> Result<()> {
use crate::cli::color::*;
use crate::corpus::registry::CorpusRegistry;
use crate::corpus::runner::CorpusRunner;
use std::collections::HashMap;
let registry = CorpusRegistry::load_full();
let runner = CorpusRunner::new(Config::default());
let mut stats: HashMap<String, (usize, usize, usize)> = HashMap::new();
let mut total_entries = 0usize;
let mut traced_entries = 0usize;
for entry in ®istry.entries {
let result = runner.run_entry_with_trace(entry);
total_entries += 1;
if accumulate_decision_stats(&result, &mut stats) {
traced_entries += 1;
}
}
let mut sorted: Vec<_> = stats.into_iter().collect();
sorted.sort_by(|a, b| b.1 .0.cmp(&a.1 .0));
println!(
"\n {BOLD}Decision Frequency Summary{RESET} ({traced_entries}/{total_entries} entries traced)"
);
println!(" {DIM}{}{RESET}", "─".repeat(78));
println!(
" {DIM}{:<36} {:>8} {:>10} {:>10} {:>8}{RESET}",
"Decision", "Count", "In Pass", "In Fail", "Fail %"
);
println!(" {DIM}{}{RESET}", "─".repeat(78));
for (key, (total, in_pass, in_fail)) in &sorted {
let fail_pct = if *total > 0 {
(*in_fail as f64 / *total as f64) * 100.0
} else {
0.0
};
let color = if fail_pct >= 50.0 {
RED
} else if fail_pct >= 20.0 {
YELLOW
} else {
""
};
let end = if color.is_empty() { "" } else { RESET };
println!(
" {color}{:<36} {:>8} {:>10} {:>10} {:>7.1}%{end}",
key, total, in_pass, in_fail, fail_pct
);
}
println!("\n {DIM}Total unique decisions: {}{RESET}", sorted.len());
println!();
Ok(())
}
pub(crate) fn corpus_patterns() -> Result<()> {
use crate::cli::color::*;
use crate::corpus::pattern_store::mine_patterns;
use crate::corpus::registry::CorpusRegistry;
use crate::corpus::runner::CorpusRunner;
let registry = CorpusRegistry::load_full();
let runner = CorpusRunner::new(Config::default());
let store = mine_patterns(®istry, &runner);
println!(
"\n {BOLD}CITL Pattern Store{RESET} ({} traced, {} failures)",
store.total_entries, store.total_failures
);
println!(" {DIM}{}{RESET}", "─".repeat(76));
if store.patterns.is_empty() {
println!(" {BRIGHT_GREEN}No failure patterns — all entries pass{RESET}");
println!();
return Ok(());
}
println!(
" {DIM}{:<22} {:<30} {:>10} {:<12}{RESET}",
"Signal", "Decision", "Confidence", "Evidence"
);
println!(" {DIM}{}{RESET}", "─".repeat(76));
for pattern in &store.patterns {
let (_, color) = score_impact_color(pattern.confidence);
let evidence = if pattern.evidence_ids.len() <= 3 {
pattern.evidence_ids.join(", ")
} else {
format!(
"{}, ... +{}",
pattern.evidence_ids[..2].join(", "),
pattern.evidence_ids.len() - 2
)
};
println!(
" {:<22} {color}{:<30}{RESET} {:>10.4} {DIM}{:<12}{RESET}",
pattern.error_signal, pattern.causal_decision, pattern.confidence, evidence
);
}
println!(
"\n {DIM}Total patterns: {} (version {}){RESET}",
store.patterns.len(),
store.version
);
println!();
Ok(())
}
pub(crate) fn corpus_pattern_query(signal: &str) -> Result<()> {
use crate::cli::color::*;
use crate::corpus::pattern_store::mine_patterns;
use crate::corpus::registry::CorpusRegistry;
use crate::corpus::runner::CorpusRunner;
let registry = CorpusRegistry::load_full();
let runner = CorpusRunner::new(Config::default());
let store = mine_patterns(®istry, &runner);
let matching: Vec<_> = store
.patterns
.iter()
.filter(|p| p.error_signal == signal)
.collect();
println!("\n {BOLD}Patterns for:{RESET} {CYAN}{signal}{RESET}");
println!(" {DIM}{}{RESET}", "─".repeat(72));
if matching.is_empty() {
println!(" {DIM}No patterns found for signal '{signal}'{RESET}");
println!(" {DIM}Known signals: A_transpile_fail, B1_containment_fail, B2_exact_fail,");
println!(" B3_behavioral_fail, D_lint_fail, G_cross_shell_fail{RESET}");
println!();
return Ok(());
}
println!(
" {DIM}{:<30} {:>10} {:<16} {:<12}{RESET}",
"Decision", "Confidence", "Fix Type", "Evidence"
);
println!(" {DIM}{}{RESET}", "─".repeat(72));
for pattern in &matching {
let (_, color) = score_impact_color(pattern.confidence);
let evidence = pattern.evidence_ids.join(", ");
println!(
" {color}{:<30}{RESET} {:>10.4} {:<16} {DIM}{:<12}{RESET}",
pattern.causal_decision, pattern.confidence, pattern.fix_type, evidence
);
}
println!();
Ok(())
}
pub(crate) fn corpus_fix_suggest(id: &str) -> Result<()> {
use crate::cli::color::*;
use crate::corpus::pattern_store::{classify_failure_signals, mine_patterns, suggest_fixes};
use crate::corpus::registry::CorpusRegistry;
use crate::corpus::runner::CorpusRunner;
let registry = CorpusRegistry::load_full();
let runner = CorpusRunner::new(Config::default());
let entry = registry
.entries
.iter()
.find(|e| e.id == id)
.ok_or_else(|| Error::Validation(format!("Corpus entry '{id}' not found")))?;
let result = runner.run_entry_with_trace(entry);
let signals = classify_failure_signals(&result);
if signals.is_empty() {
println!("\n {BRIGHT_GREEN}{id} passes all checks — no fixes needed{RESET}\n");
return Ok(());
}
let signal_list = signals.join(", ");
println!("\n {BOLD}Fix Suggestions for {CYAN}{id}{RESET} ({BRIGHT_RED}{signal_list}{RESET})");
println!(" {DIM}{}{RESET}", "─".repeat(72));
let store = mine_patterns(®istry, &runner);
let suggestions = suggest_fixes(id, ®istry, &runner, &store);
if suggestions.is_empty() {
println!(" {DIM}No pattern-based suggestions available for this entry{RESET}");
println!(" {DIM}(decision trace may not match any known failure patterns){RESET}");
println!();
return Ok(());
}
println!(
" {DIM}{:<4} {:<30} {:<18} {:>10}{RESET}",
"#", "Decision", "Fix Type", "Confidence"
);
println!(" {DIM}{}{RESET}", "─".repeat(72));
for (i, suggestion) in suggestions.iter().enumerate() {
let (_, color) = score_impact_color(suggestion.confidence);
println!(
" {WHITE}#{:<3}{RESET} {color}{:<30}{RESET} {:<18} {:>10.4}",
i + 1,
suggestion.causal_decision,
suggestion.fix_type,
suggestion.confidence
);
}
println!();
Ok(())
}