use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FindingSeverity {
Info,
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FindingCategory {
Security,
Architecture,
Performance,
Quality,
Domain,
DesignQuality,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReviewFinding {
pub file: String,
#[serde(default)]
pub line: u32,
pub severity: FindingSeverity,
pub category: FindingCategory,
pub finding: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub suggestion: Option<String>,
#[serde(default = "default_confidence")]
pub confidence: f64,
}
fn default_confidence() -> f64 {
0.5
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReviewAgentOutput {
pub agent: String,
pub findings: Vec<ReviewFinding>,
pub summary: String,
pub pass: bool,
}
pub fn deduplicate_findings(findings: Vec<ReviewFinding>) -> Vec<ReviewFinding> {
use std::collections::HashMap;
let mut best: HashMap<(String, u32, FindingCategory), ReviewFinding> = HashMap::new();
for finding in findings {
let key = (finding.file.clone(), finding.line, finding.category);
best.entry(key)
.and_modify(|existing| {
if finding.severity > existing.severity {
*existing = finding.clone();
}
})
.or_insert(finding);
}
let mut result: Vec<ReviewFinding> = best.into_values().collect();
result.sort_by(|a, b| {
b.severity
.cmp(&a.severity)
.then_with(|| a.file.cmp(&b.file))
.then_with(|| a.line.cmp(&b.line))
});
result
}