Skip to main content

edda_core/
bundle.rs

1//! Review bundle types for compact decision cards.
2//!
3//! A review bundle aggregates change summary, test results, and risk assessment
4//! into a single decision card for rapid approval/rejection.
5
6use serde::{Deserialize, Serialize};
7
8/// Risk level for a review bundle.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum RiskLevel {
12    Low,
13    Medium,
14    High,
15    Critical,
16}
17
18/// A single file change entry from git diff.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct FileChange {
21    pub path: String,
22    pub added: u32,
23    pub deleted: u32,
24}
25
26/// Summary of code changes (from git diff --numstat).
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct ChangeSummary {
29    pub files: Vec<FileChange>,
30    pub total_added: u32,
31    pub total_deleted: u32,
32    pub diff_ref: String,
33}
34
35/// Parsed test execution results.
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct TestResults {
38    pub passed: u32,
39    pub failed: u32,
40    pub ignored: u32,
41    pub total: u32,
42    pub failures: Vec<String>,
43    pub command: String,
44}
45
46/// A single risk factor contributing to the overall assessment.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct RiskFactor {
49    pub signal: String,
50    pub level: RiskLevel,
51    pub detail: String,
52}
53
54/// Aggregated risk assessment from multiple signals.
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct RiskAssessment {
57    pub level: RiskLevel,
58    pub factors: Vec<RiskFactor>,
59}
60
61/// Suggested action based on risk and test results.
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
63#[serde(rename_all = "snake_case")]
64pub enum SuggestedAction {
65    Approve,
66    Review,
67    RequestChanges,
68    Reject,
69}
70
71/// A complete review bundle aggregating all assessment data.
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct ReviewBundle {
74    pub bundle_id: String,
75    pub change_summary: ChangeSummary,
76    pub test_results: TestResults,
77    pub risk_assessment: RiskAssessment,
78    pub suggested_action: SuggestedAction,
79    pub suggested_reason: String,
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn risk_level_ordering() {
88        assert!(RiskLevel::Low < RiskLevel::Medium);
89        assert!(RiskLevel::Medium < RiskLevel::High);
90        assert!(RiskLevel::High < RiskLevel::Critical);
91    }
92
93    #[test]
94    fn risk_level_json_roundtrip() {
95        let level = RiskLevel::High;
96        let json = serde_json::to_string(&level).unwrap();
97        assert_eq!(json, "\"high\"");
98        let back: RiskLevel = serde_json::from_str(&json).unwrap();
99        assert_eq!(back, level);
100    }
101
102    #[test]
103    fn suggested_action_json_roundtrip() {
104        let action = SuggestedAction::RequestChanges;
105        let json = serde_json::to_string(&action).unwrap();
106        assert_eq!(json, "\"request_changes\"");
107        let back: SuggestedAction = serde_json::from_str(&json).unwrap();
108        assert_eq!(back, action);
109    }
110
111    #[test]
112    fn review_bundle_json_roundtrip() {
113        let bundle = ReviewBundle {
114            bundle_id: "bun_test123".into(),
115            change_summary: ChangeSummary {
116                files: vec![FileChange {
117                    path: "src/main.rs".into(),
118                    added: 10,
119                    deleted: 3,
120                }],
121                total_added: 10,
122                total_deleted: 3,
123                diff_ref: "HEAD~1".into(),
124            },
125            test_results: TestResults {
126                passed: 50,
127                failed: 0,
128                ignored: 2,
129                total: 52,
130                failures: vec![],
131                command: "cargo test".into(),
132            },
133            risk_assessment: RiskAssessment {
134                level: RiskLevel::Low,
135                factors: vec![],
136            },
137            suggested_action: SuggestedAction::Approve,
138            suggested_reason: "All tests pass, low risk".into(),
139        };
140
141        let json = serde_json::to_value(&bundle).unwrap();
142        let back: ReviewBundle = serde_json::from_value(json).unwrap();
143        assert_eq!(back.bundle_id, "bun_test123");
144        assert_eq!(back.change_summary.total_added, 10);
145        assert_eq!(back.test_results.passed, 50);
146        assert_eq!(back.risk_assessment.level, RiskLevel::Low);
147        assert_eq!(back.suggested_action, SuggestedAction::Approve);
148    }
149}