use crate::cli::args::ReportFormat;
use crate::models::{Error, Result};
pub(crate) fn handle_gate_command(tier: u8, report: ReportFormat) -> Result<()> {
use crate::gates::GateConfig;
if !matches!(report, ReportFormat::Human) {
eprintln!(
"Warning: --report {:?} is not yet implemented for gate command. Using human format.",
report
);
}
let config = GateConfig::load()?;
let gates_to_run = match tier {
1 => &config.tiers.tier1_gates,
2 => &config.tiers.tier2_gates,
3 => &config.tiers.tier3_gates,
_ => {
return Err(Error::Validation(format!(
"Invalid tier: {}. Must be 1, 2, or 3.",
tier
)))
}
};
eprintln!("Executing Tier {} Quality Gates...", tier);
eprintln!("Gates enabled: {}", gates_to_run.join(", "));
eprintln!("----------------------------------------");
let mut failures = Vec::new();
for gate in gates_to_run {
eprint!("Checking {}... ", gate);
use std::io::Write;
let _ = std::io::stderr().flush();
let success = match gate.as_str() {
"clippy" => run_clippy_gate(&config),
"tests" => run_tests_gate(&config),
"coverage" => run_coverage_gate(&config),
"complexity" => run_complexity_gate(&config),
"security" => run_security_gate(&config),
"satd" => run_satd_gate(&config),
"mutation" => run_mutation_gate(&config),
_ => {
eprintln!("⚠️ Unknown gate");
continue;
}
};
if success {
eprintln!("✅ PASS");
} else {
eprintln!("❌ FAIL");
failures.push(gate.clone());
}
}
eprintln!("----------------------------------------");
if failures.is_empty() {
eprintln!("✅ Tier {} Gates Passed!", tier);
Ok(())
} else {
eprintln!("❌ Tier {} Gates Failed: {}", tier, failures.join(", "));
std::process::exit(1);
}
}
fn run_clippy_gate(config: &crate::gates::GateConfig) -> bool {
let mut cmd = std::process::Command::new("cargo");
cmd.arg("clippy");
if config.gates.clippy_strict {
cmd.args(["--", "-D", "warnings"]);
}
let status = cmd
.status()
.unwrap_or_else(|_| std::process::ExitStatus::default());
status.success()
}
fn run_tests_gate(config: &crate::gates::GateConfig) -> bool {
let timeout_secs = config.gates.test_timeout;
let mut child = match std::process::Command::new("cargo").arg("test").spawn() {
Ok(c) => c,
Err(_) => return false,
};
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(timeout_secs);
loop {
match child.try_wait() {
Ok(Some(status)) => return status.success(),
Ok(None) => {
if std::time::Instant::now() >= deadline {
let _ = child.kill();
let _ = child.wait();
eprintln!("(tests exceeded {}s timeout) ", timeout_secs);
return false;
}
std::thread::sleep(std::time::Duration::from_millis(250));
}
Err(_) => return false,
}
}
}
fn run_coverage_gate(config: &crate::gates::GateConfig) -> bool {
if !config.gates.check_coverage {
return true;
}
let status = std::process::Command::new("cargo")
.args(["llvm-cov", "--version"])
.output();
if status.is_ok() {
let cov_status = std::process::Command::new("cargo")
.args([
"llvm-cov",
"--fail-under-lines",
&config.gates.min_coverage.to_string(),
])
.status()
.unwrap_or_else(|_| std::process::ExitStatus::default());
cov_status.success()
} else {
eprintln!("(cargo-llvm-cov not found, skipping) ");
true
}
}
fn run_complexity_gate(config: &crate::gates::GateConfig) -> bool {
if !config.gates.check_complexity {
return true;
}
let max = config.gates.max_complexity;
let status = std::process::Command::new("pmat")
.args([
"analyze",
"complexity",
"--max-cyclomatic",
&max.to_string(),
])
.status();
match status {
Ok(s) => s.success(),
Err(_) => {
eprintln!(
"(pmat not found, complexity gate skipped — max_complexity={}) ",
max
);
true
}
}
}
fn run_security_gate(config: &crate::gates::GateConfig) -> bool {
if let Some(ref security) = config.gates.security {
if !security.enabled {
return true;
}
}
let status = std::process::Command::new("cargo")
.args(["deny", "check"])
.status();
match status {
Ok(s) => s.success(),
Err(_) => {
eprintln!("(cargo-deny not found, skipping) ");
true
}
}
}
fn run_satd_gate(config: &crate::gates::GateConfig) -> bool {
if let Some(satd) = &config.gates.satd {
if !satd.enabled {
return true;
}
let patterns = &satd.patterns;
if patterns.is_empty() {
return true;
}
true
} else {
true
}
}
fn run_mutation_gate(config: &crate::gates::GateConfig) -> bool {
if let Some(mutation) = &config.gates.mutation {
if !mutation.enabled {
return true;
}
let status = std::process::Command::new("cargo")
.args(["mutants", "--score", &mutation.min_score.to_string()])
.status();
match status {
Ok(s) => s.success(),
Err(_) => {
eprintln!("(cargo-mutants not found, skipping) ");
true
}
}
} else {
true
}
}