use crate::error::FlowGuardError;
use ahash::AHashMap as HashMap;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeReviewConfig {
pub security_audit: bool,
pub performance_analysis: bool,
pub code_quality_check: bool,
pub architecture_review: bool,
pub paths: Vec<PathBuf>,
pub exclude_patterns: Vec<String>,
pub severity_threshold: Severity,
pub parallel_agents: usize,
pub detailed_report: bool,
pub output_path: Option<PathBuf>,
}
impl Default for CodeReviewConfig {
fn default() -> Self {
Self {
security_audit: true,
performance_analysis: true,
code_quality_check: true,
architecture_review: true,
paths: vec![PathBuf::from("src/")],
exclude_patterns: vec![
"target/".to_string(),
"*.generated.rs".to_string(),
"*.pb.rs".to_string(),
],
severity_threshold: Severity::Info,
parallel_agents: 4,
detailed_report: false,
output_path: None,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Severity {
Critical = 4,
High = 3,
Medium = 2,
Low = 1,
Info = 0,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum IssueCategory {
Security,
Performance,
CodeQuality,
Architecture,
BestPractice,
Documentation,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeReviewIssue {
pub id: String,
pub category: IssueCategory,
pub severity: Severity,
pub title: String,
pub description: String,
pub file_path: PathBuf,
pub start_line: Option<u32>,
pub end_line: Option<u32>,
pub suggestion: String,
pub code_snippet: Option<String>,
pub rule: Option<String>,
pub auto_fixable: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentReviewResult {
pub agent_name: String,
pub agent_type: AgentType,
pub issues: Vec<CodeReviewIssue>,
pub duration_ms: u128,
pub status: ReviewStatus,
pub error_message: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum AgentType {
SecurityAuditor,
PerformanceEngineer,
CodeReviewer,
ArchitectReviewer,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ReviewStatus {
Success,
Partial,
Failed,
Skipped,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeReviewReport {
pub timestamp: chrono::DateTime<chrono::Utc>,
pub config: CodeReviewConfig,
pub files_reviewed: usize,
pub agent_results: Vec<AgentReviewResult>,
pub summary: ReviewSummary,
pub issues_by_category: HashMap<IssueCategory, Vec<CodeReviewIssue>>,
pub issues_by_severity: HashMap<Severity, Vec<CodeReviewIssue>>,
pub conclusion: ReviewConclusion,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReviewSummary {
pub total_issues: usize,
pub critical_count: usize,
pub high_count: usize,
pub medium_count: usize,
pub low_count: usize,
pub info_count: usize,
pub auto_fixable_count: usize,
pub files_reviewed: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ReviewConclusion {
Passed,
PassedWithWarnings,
ConditionalPass,
Failed,
}
#[derive(Debug)]
pub struct CodeReviewManager {
config: Arc<RwLock<CodeReviewConfig>>,
results: Arc<RwLock<Vec<AgentReviewResult>>>,
stats: Arc<RwLock<CodeReviewStats>>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct CodeReviewStats {
pub total_runs: u64,
pub successful_runs: u64,
pub failed_runs: u64,
pub total_duration_ms: u128,
pub total_issues_found: u64,
pub critical_issues_found: u64,
}
impl CodeReviewManager {
pub fn new(config: CodeReviewConfig) -> Self {
Self {
config: Arc::new(RwLock::new(config)),
results: Arc::new(RwLock::new(Vec::new())),
stats: Arc::new(RwLock::new(CodeReviewStats::default())),
}
}
pub async fn run_review(&self) -> Result<CodeReviewReport, FlowGuardError> {
let start_time = std::time::Instant::now();
let config = self.config.read().await.clone();
let mut agent_results = Vec::new();
let mut tasks: Vec<_> = Vec::new();
if config.security_audit {
let paths = config.paths.clone();
let exclude_patterns = config.exclude_patterns.clone();
tasks.push(tokio::spawn(async move {
Self::run_agent_review(AgentType::SecurityAuditor, paths, exclude_patterns).await
}));
}
if config.performance_analysis {
let paths = config.paths.clone();
let exclude_patterns = config.exclude_patterns.clone();
tasks.push(tokio::spawn(async move {
Self::run_agent_review(AgentType::PerformanceEngineer, paths, exclude_patterns)
.await
}));
}
if config.code_quality_check {
let paths = config.paths.clone();
let exclude_patterns = config.exclude_patterns.clone();
tasks.push(tokio::spawn(async move {
Self::run_agent_review(AgentType::CodeReviewer, paths, exclude_patterns).await
}));
}
if config.architecture_review {
let paths = config.paths.clone();
let exclude_patterns = config.exclude_patterns.clone();
tasks.push(tokio::spawn(async move {
Self::run_agent_review(AgentType::ArchitectReviewer, paths, exclude_patterns).await
}));
}
for task in tasks {
match task.await {
Ok(result) => agent_results.push(result),
Err(e) => {
tracing::error!("Agent task failed: {}", e);
}
}
}
let duration_ms = start_time.elapsed().as_millis();
let report = self.aggregate_results(agent_results, duration_ms).await;
self.update_stats(&report).await;
Ok(report)
}
async fn run_agent_review(
agent_type: AgentType,
paths: Vec<PathBuf>,
exclude_patterns: Vec<String>,
) -> AgentReviewResult {
let start_time = std::time::Instant::now();
let (agent_name, issues) = match agent_type {
AgentType::SecurityAuditor => (
"SecurityAuditor".to_string(),
Self::run_security_audit(&paths, &exclude_patterns).await,
),
AgentType::PerformanceEngineer => (
"PerformanceEngineer".to_string(),
Self::run_performance_analysis(&paths, &exclude_patterns).await,
),
AgentType::CodeReviewer => (
"CodeReviewer".to_string(),
Self::run_code_quality_check(&paths, &exclude_patterns).await,
),
AgentType::ArchitectReviewer => (
"ArchitectReviewer".to_string(),
Self::run_architecture_review(&paths, &exclude_patterns).await,
),
};
let duration_ms = start_time.elapsed().as_millis();
let is_empty = issues.is_empty();
AgentReviewResult {
agent_name,
agent_type,
issues,
duration_ms,
status: if is_empty {
ReviewStatus::Success
} else {
ReviewStatus::Partial
},
error_message: None,
}
}
async fn run_security_audit(
_paths: &[PathBuf],
_exclude_patterns: &[String],
) -> Vec<CodeReviewIssue> {
Vec::new()
}
async fn run_performance_analysis(
_paths: &[PathBuf],
_exclude_patterns: &[String],
) -> Vec<CodeReviewIssue> {
Vec::new()
}
async fn run_code_quality_check(
_paths: &[PathBuf],
_exclude_patterns: &[String],
) -> Vec<CodeReviewIssue> {
Vec::new()
}
async fn run_architecture_review(
_paths: &[PathBuf],
_exclude_patterns: &[String],
) -> Vec<CodeReviewIssue> {
Vec::new()
}
async fn aggregate_results(
&self,
agent_results: Vec<AgentReviewResult>,
_total_duration_ms: u128,
) -> CodeReviewReport {
let mut all_issues = Vec::new();
let mut issues_by_category: HashMap<IssueCategory, Vec<CodeReviewIssue>> = HashMap::new();
let mut issues_by_severity: HashMap<Severity, Vec<CodeReviewIssue>> = HashMap::new();
for result in &agent_results {
all_issues.extend(result.issues.clone());
for issue in &result.issues {
issues_by_category
.entry(issue.category.clone())
.or_default()
.push(issue.clone());
issues_by_severity
.entry(issue.severity)
.or_default()
.push(issue.clone());
}
}
let mut sorted_issues = all_issues.clone();
sorted_issues.sort_by_key(|i| std::cmp::Reverse(i.severity));
let summary = ReviewSummary {
total_issues: all_issues.len(),
critical_count: issues_by_severity
.get(&Severity::Critical)
.map(|v| v.len())
.unwrap_or(0),
high_count: issues_by_severity
.get(&Severity::High)
.map(|v| v.len())
.unwrap_or(0),
medium_count: issues_by_severity
.get(&Severity::Medium)
.map(|v| v.len())
.unwrap_or(0),
low_count: issues_by_severity
.get(&Severity::Low)
.map(|v| v.len())
.unwrap_or(0),
info_count: issues_by_severity
.get(&Severity::Info)
.map(|v| v.len())
.unwrap_or(0),
auto_fixable_count: all_issues.iter().filter(|i| i.auto_fixable).count(),
files_reviewed: 0, };
let conclusion = Self::determine_conclusion(&summary);
let config = self.config.read().await.clone();
CodeReviewReport {
timestamp: chrono::Utc::now(),
config,
files_reviewed: summary.files_reviewed,
agent_results,
summary,
issues_by_category,
issues_by_severity,
conclusion,
}
}
fn determine_conclusion(summary: &ReviewSummary) -> ReviewConclusion {
if summary.critical_count > 0 {
ReviewConclusion::Failed
} else if summary.high_count > 0 {
ReviewConclusion::ConditionalPass
} else if summary.medium_count > 0 {
ReviewConclusion::PassedWithWarnings
} else {
ReviewConclusion::Passed
}
}
async fn update_stats(&self, report: &CodeReviewReport) {
let mut stats = self.stats.write().await;
stats.total_runs += 1;
let agent_duration_sum: u128 = report.agent_results.iter().map(|r| r.duration_ms).sum();
stats.total_duration_ms += agent_duration_sum;
match report.conclusion {
ReviewConclusion::Failed => stats.failed_runs += 1,
_ => stats.successful_runs += 1,
}
stats.total_issues_found += report.summary.total_issues as u64;
stats.critical_issues_found += report.summary.critical_count as u64;
}
pub async fn get_stats(&self) -> CodeReviewStats {
self.stats.read().await.clone()
}
pub async fn get_recent_results(&self) -> Vec<AgentReviewResult> {
self.results.read().await.clone()
}
pub async fn update_config(&self, config: CodeReviewConfig) {
*self.config.write().await = config;
}
}
pub fn format_report_as_markdown(report: &CodeReviewReport) -> String {
let mut md = String::new();
md.push_str("# 代码审查报告\n\n");
md.push_str(&format!("**生成时间:** {}\n\n", report.timestamp));
md.push_str(&format!(
"**审查结论:** {}\n\n",
match report.conclusion {
ReviewConclusion::Passed => "✅ 通过",
ReviewConclusion::PassedWithWarnings => "⚠️ 通过(有警告)",
ReviewConclusion::ConditionalPass => "⚡ 有条件通过",
ReviewConclusion::Failed => "❌ 失败",
}
));
md.push_str("## 摘要\n\n");
md.push_str("| 指标 | 数量 |\n");
md.push_str("|------|------|\n");
md.push_str(&format!("| 总问题数 | {} |\n", report.summary.total_issues));
md.push_str(&format!(
"| 🔴 严重 | {} |\n",
report.summary.critical_count
));
md.push_str(&format!("| 🟠 高 | {} |\n", report.summary.high_count));
md.push_str(&format!("| 🟡 中 | {} |\n", report.summary.medium_count));
md.push_str(&format!("| 🟢 低 | {} |\n", report.summary.low_count));
md.push_str(&format!("| 🔵 信息 | {} |\n", report.summary.info_count));
md.push_str(&format!(
"| 可自动修复 | {} |\n\n",
report.summary.auto_fixable_count
));
md.push_str("## 详细问题\n\n");
for (category, issues) in &report.issues_by_category {
md.push_str(&format!("### {:?}\n\n", category));
for issue in issues {
md.push_str(&format!("#### {}\n", issue.title));
md.push_str(&format!("**严重性:** {:?}\n\n", issue.severity));
md.push_str(&format!("**描述:** {}\n\n", issue.description));
md.push_str(&format!("**位置:** {:?}\n\n", issue.file_path));
if let Some(snippet) = &issue.code_snippet {
md.push_str("```rust\n");
md.push_str(snippet);
md.push_str("\n```\n\n");
}
md.push_str(&format!("**建议:** {}\n\n", issue.suggestion));
}
}
md
}
pub fn format_report_as_json(report: &CodeReviewReport) -> Result<String, FlowGuardError> {
serde_json::to_string_pretty(report).map_err(FlowGuardError::SerdeError)
}