use crate::cli::DefectPredictionOutputFormat;
use crate::services::facades::defect_prediction_facade::{
DefectPredictionFacade, DefectPredictionRequest, DefectPredictionResult,
};
use crate::services::service_registry::ServiceRegistry;
use anyhow::Result;
use std::path::{Path, PathBuf};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct DefectPredictionConfig {
pub project_path: PathBuf,
pub confidence_threshold: f32,
pub min_lines: usize,
pub include_low_confidence: bool,
pub format: DefectPredictionOutputFormat,
pub high_risk_only: bool,
pub include_recommendations: bool,
pub include: Option<String>,
pub exclude: Option<String>,
pub output: Option<PathBuf>,
pub perf: bool,
pub top_files: usize,
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn handle_analyze_defect_prediction(config: DefectPredictionConfig) -> Result<()> {
print_analysis_header(
&config.project_path,
config.high_risk_only,
config.include_low_confidence,
);
let registry = Arc::new(ServiceRegistry::new());
let facade = DefectPredictionFacade::new(registry);
let request = DefectPredictionRequest {
project_path: config.project_path.clone(),
confidence_threshold: config.confidence_threshold,
min_lines: config.min_lines,
include_low_confidence: config.include_low_confidence,
high_risk_only: config.high_risk_only,
include_recommendations: config.include_recommendations,
include: config.include.map(|s| vec![s]),
exclude: config.exclude.map(|s| vec![s]),
top_files: config.top_files,
};
let result = facade.analyze_project(request).await?;
output_results(result, config.format, config.output).await?;
{
use crate::cli::colors as c;
eprintln!("{}", c::pass("Defect prediction analysis complete"));
}
Ok(())
}
fn print_analysis_header(project_path: &Path, high_risk_only: bool, include_low_confidence: bool) {
use crate::cli::colors as c;
eprintln!("{}", c::dim("Analyzing defect probability..."));
eprintln!(
" {}Project path:{} {}",
c::BOLD,
c::RESET,
c::path(&project_path.display().to_string())
);
eprintln!(
" {}High risk only:{} {}{}{}",
c::BOLD,
c::RESET,
c::BOLD_WHITE,
high_risk_only,
c::RESET
);
eprintln!(
" {}Include low confidence:{} {}{}{}",
c::BOLD,
c::RESET,
c::BOLD_WHITE,
include_low_confidence,
c::RESET
);
}
async fn output_results(
result: DefectPredictionResult,
format: DefectPredictionOutputFormat,
output: Option<PathBuf>,
) -> Result<()> {
let content = format_result(result, format)?;
if let Some(output_path) = output {
tokio::fs::write(&output_path, &content).await?;
eprintln!("📝 Written to {}", output_path.display());
} else {
println!("{content}");
}
Ok(())
}
fn format_result(
result: DefectPredictionResult,
format: DefectPredictionOutputFormat,
) -> Result<String> {
match format {
DefectPredictionOutputFormat::Summary => Ok(format_summary(&result)),
DefectPredictionOutputFormat::Detailed => Ok(format_detailed(&result)),
DefectPredictionOutputFormat::Json => {
serde_json::to_string_pretty(&result).map_err(Into::into)
}
DefectPredictionOutputFormat::Csv => Ok(format_csv(&result)),
DefectPredictionOutputFormat::Sarif => Ok(format_sarif(&result)),
}
}
fn format_summary(result: &DefectPredictionResult) -> String {
use crate::cli::colors as c;
use std::fmt::Write;
let mut output = String::new();
let _ = writeln!(output, "{}\n", c::header("Defect Prediction Summary"));
let _ = writeln!(output, " {}", result.summary);
let _ = writeln!(output, "\n{}", c::subheader("Top Risk Files"));
for (i, prediction) in result.predictions.iter().take(10).enumerate() {
let risk_color = match prediction.risk_level {
crate::services::facades::defect_prediction_facade::RiskLevel::Critical => c::RED,
crate::services::facades::defect_prediction_facade::RiskLevel::High => c::RED,
crate::services::facades::defect_prediction_facade::RiskLevel::Medium => c::YELLOW,
crate::services::facades::defect_prediction_facade::RiskLevel::Low => c::GREEN,
};
let _ = writeln!(
output,
" {}. {} - {}{:.1}% risk{} ({}{:?}{})",
c::number(&(i + 1).to_string()),
c::path(&prediction.file_path),
risk_color,
prediction.defect_probability * 100.0,
c::RESET,
risk_color,
prediction.risk_level,
c::RESET,
);
}
if !result.recommendations.is_empty() {
let _ = writeln!(output, "\n{}", c::subheader("Recommendations"));
for rec in &result.recommendations {
let _ = writeln!(output, " - {rec}");
}
}
output
}
fn format_detailed(result: &DefectPredictionResult) -> String {
use crate::cli::colors as c;
use std::fmt::Write;
let mut output = String::new();
let _ = writeln!(
output,
"{}\n",
c::header("Defect Prediction Detailed Report")
);
let _ = writeln!(
output,
" {}Total files analyzed:{} {}{}{}",
c::BOLD,
c::RESET,
c::BOLD_WHITE,
result.total_files_analyzed,
c::RESET
);
let _ = writeln!(
output,
" {}High risk files:{} {}{}{}",
c::BOLD,
c::RESET,
c::RED,
result.high_risk_files,
c::RESET
);
let _ = writeln!(
output,
" {}Medium risk files:{} {}{}{}",
c::BOLD,
c::RESET,
c::YELLOW,
result.medium_risk_files,
c::RESET
);
let _ = writeln!(
output,
" {}Low risk files:{} {}{}{}\n",
c::BOLD,
c::RESET,
c::GREEN,
result.low_risk_files,
c::RESET
);
let _ = writeln!(output, "{}", c::subheader("File Analysis"));
for prediction in &result.predictions {
let risk_color = match prediction.risk_level {
crate::services::facades::defect_prediction_facade::RiskLevel::Critical => c::RED,
crate::services::facades::defect_prediction_facade::RiskLevel::High => c::RED,
crate::services::facades::defect_prediction_facade::RiskLevel::Medium => c::YELLOW,
crate::services::facades::defect_prediction_facade::RiskLevel::Low => c::GREEN,
};
let _ = writeln!(output, "\n {}", c::path(&prediction.file_path));
let _ = writeln!(
output,
" {}Risk Level:{} {}{:?}{}",
c::BOLD,
c::RESET,
risk_color,
prediction.risk_level,
c::RESET
);
let _ = writeln!(
output,
" {}Defect Probability:{} {}{:.1}%{}",
c::BOLD,
c::RESET,
c::BOLD_WHITE,
prediction.defect_probability * 100.0,
c::RESET
);
let _ = writeln!(
output,
" {}Confidence:{} {}{:.1}%{}",
c::BOLD,
c::RESET,
c::BOLD_WHITE,
prediction.confidence * 100.0,
c::RESET
);
let _ = writeln!(output, " {}Risk Metrics:{}", c::BOLD, c::RESET);
let _ = writeln!(
output,
" {}Complexity:{} {}{:.1}{}",
c::BOLD,
c::RESET,
c::BOLD_WHITE,
prediction.metrics.complexity_score,
c::RESET
);
let _ = writeln!(
output,
" {}Churn:{} {}{:.1}{}",
c::BOLD,
c::RESET,
c::BOLD_WHITE,
prediction.metrics.churn_score,
c::RESET
);
let _ = writeln!(
output,
" {}Coupling:{} {}{:.1}{}",
c::BOLD,
c::RESET,
c::BOLD_WHITE,
prediction.metrics.coupling_score,
c::RESET
);
let _ = writeln!(
output,
" {}Size:{} {}{:.1}{}",
c::BOLD,
c::RESET,
c::BOLD_WHITE,
prediction.metrics.size_score,
c::RESET
);
let _ = writeln!(
output,
" {}Duplication:{} {}{:.1}{}",
c::BOLD,
c::RESET,
c::BOLD_WHITE,
prediction.metrics.duplication_score,
c::RESET
);
if !prediction.contributing_factors.is_empty() {
let _ = writeln!(output, " {}Contributing Factors:{}", c::BOLD, c::RESET);
for factor in &prediction.contributing_factors {
let _ = writeln!(output, " - {factor}");
}
}
}
output
}
fn format_csv(result: &DefectPredictionResult) -> String {
let mut output = String::new();
output.push_str("File,Risk Level,Defect Probability,Confidence,Complexity,Churn,Coupling,Size,Duplication\n");
for prediction in &result.predictions {
output.push_str(&format!(
"{},{:?},{:.3},{:.3},{:.3},{:.3},{:.3},{:.3},{:.3}\n",
prediction.file_path,
prediction.risk_level,
prediction.defect_probability,
prediction.confidence,
prediction.metrics.complexity_score,
prediction.metrics.churn_score,
prediction.metrics.coupling_score,
prediction.metrics.size_score,
prediction.metrics.duplication_score
));
}
output
}
fn format_sarif(result: &DefectPredictionResult) -> String {
let rules = vec![serde_json::json!({
"id": "high-defect-risk",
"shortDescription": {
"text": "High defect probability detected"
},
"fullDescription": {
"text": "Files with high defect probability require additional testing and review"
}
})];
let results: Vec<_> = result
.predictions
.iter()
.filter(|p| {
matches!(
p.risk_level,
crate::services::facades::defect_prediction_facade::RiskLevel::High
| crate::services::facades::defect_prediction_facade::RiskLevel::Critical
)
})
.map(|prediction| {
serde_json::json!({
"ruleId": "high-defect-risk",
"level": if matches!(prediction.risk_level,
crate::services::facades::defect_prediction_facade::RiskLevel::Critical) {
"error"
} else {
"warning"
},
"message": {
"text": format!(
"File has {:.1}% defect probability. Contributing factors: {}",
prediction.defect_probability * 100.0,
prediction.contributing_factors.join(", ")
)
},
"locations": [{
"physicalLocation": {
"artifactLocation": {
"uri": prediction.file_path.clone()
}
}
}],
"properties": {
"defectProbability": prediction.defect_probability,
"confidence": prediction.confidence,
"riskLevel": format!("{:?}", prediction.risk_level)
}
})
})
.collect();
serde_json::json!({
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
"version": "2.1.0",
"runs": [{
"tool": {
"driver": {
"name": "pmat-defect-prediction",
"version": env!("CARGO_PKG_VERSION"),
"informationUri": "https://github.com/paiml/paiml-mcp-agent-toolkit",
"rules": rules
}
},
"results": results
}]
})
.to_string()
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use crate::services::facades::defect_prediction_facade::{
FilePrediction, FileRiskMetrics, RiskLevel,
};
#[test]
fn test_format_summary() {
let result = DefectPredictionResult {
total_files_analyzed: 10,
high_risk_files: 3,
medium_risk_files: 4,
low_risk_files: 3,
predictions: vec![FilePrediction {
file_path: "test.rs".to_string(),
defect_probability: 0.8,
risk_level: RiskLevel::High,
confidence: 0.9,
metrics: FileRiskMetrics {
complexity_score: 0.8,
churn_score: 0.7,
coupling_score: 0.6,
size_score: 0.5,
duplication_score: 0.4,
},
contributing_factors: vec!["High complexity".to_string()],
}],
summary: "Test summary".to_string(),
recommendations: vec!["Test recommendation".to_string()],
};
let output = format_summary(&result);
assert!(output.contains("Test summary"));
assert!(output.contains("test.rs"));
assert!(output.contains("80.0%"));
assert!(output.contains("Test recommendation"));
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}