cargo-governor 2.0.3

Machine-First, LLM-Ready, CI/CD-Native release automation tool for Rust crates
Documentation
//! Risk assessment for version releases

use governor_core::domain::version::{BreakingChange, Feature, Fix};

/// Risk assessment result
#[derive(Debug, Clone, serde::Serialize)]
pub struct RiskAssessment {
    pub level: String,
    pub score: u8,
    pub factors: Vec<RiskFactor>,
    pub recommendations: Vec<String>,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct RiskFactor {
    pub name: String,
    pub impact: String,
    pub weight: f64,
}

/// Calculate risk assessment
#[must_use]
pub fn calculate_risk_assessment(
    breaking: &[BreakingChange],
    features: &[Feature],
    fixes: &[Fix],
) -> RiskAssessment {
    let mut score: u8 = 0;
    let mut factors = Vec::new();
    let mut recommendations = Vec::new();

    if !breaking.is_empty() {
        let breaking_count = u32::try_from(breaking.len()).unwrap_or(u32::MAX);
        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
        let breaking_weight = (f64::from(breaking_count).min(5.0) * 15.0) as u8;
        score = score.saturating_add(breaking_weight);
        factors.push(RiskFactor {
            name: "breaking_changes".to_string(),
            impact: "high".to_string(),
            weight: 0.4,
        });
        recommendations.push("Add migration guide for breaking changes".to_string());
        recommendations.push("Consider beta release for community testing".to_string());
    }

    if features.len() > 5 {
        score = score.saturating_add(10);
        factors.push(RiskFactor {
            name: "many_features".to_string(),
            impact: "medium".to_string(),
            weight: 0.2,
        });
    }

    if fixes.len() > 10 {
        score = score.saturating_add(15);
        factors.push(RiskFactor {
            name: "many_fixes".to_string(),
            impact: "medium".to_string(),
            weight: 0.3,
        });
        recommendations.push("Review test coverage for fixed issues".to_string());
    }

    let level = if score >= 70 {
        "high"
    } else if score >= 40 {
        "medium"
    } else {
        "low"
    };

    RiskAssessment {
        level: level.to_string(),
        score: score.min(100),
        factors,
        recommendations,
    }
}