clawft_kernel/assessment/
analyzer.rs1use std::path::{Path, PathBuf};
4
5use serde::{Deserialize, Serialize};
6
7use super::{AssessmentReport, Finding};
8
9#[derive(Debug, Clone, Default)]
11pub struct AnalysisContext {
12 pub scope: String,
14 pub previous_report: Option<AssessmentReport>,
16}
17
18pub trait Analyzer: Send + Sync {
24 fn id(&self) -> &str;
26 fn name(&self) -> &str;
28 fn categories(&self) -> &[&str];
30 fn analyze(&self, project: &Path, files: &[PathBuf], context: &AnalysisContext)
32 -> Vec<Finding>;
33}
34
35pub struct AnalyzerRegistry {
37 analyzers: Vec<Box<dyn Analyzer>>,
38}
39
40impl AnalyzerRegistry {
41 pub fn new() -> Self {
43 Self {
44 analyzers: Vec::new(),
45 }
46 }
47
48 pub fn with_defaults() -> Self {
50 let mut reg = Self::new();
51 reg.register(Box::new(super::analyzers::ComplexityAnalyzer::new()));
52 reg.register(Box::new(super::analyzers::DependencyAnalyzer));
53 reg.register(Box::new(super::analyzers::SecurityAnalyzer));
54 reg.register(Box::new(super::analyzers::TopologyAnalyzer));
55 reg.register(Box::new(super::analyzers::DataSourceAnalyzer));
56 reg.register(Box::new(super::analyzers::NetworkAnalyzer));
57 reg.register(Box::new(super::analyzers::RabbitMQAnalyzer));
58 reg.register(Box::new(super::analyzers::TerraformAnalyzer));
59 reg
60 }
61
62 pub fn register(&mut self, analyzer: Box<dyn Analyzer>) {
64 self.analyzers.push(analyzer);
65 }
66
67 pub fn run_all(
69 &self,
70 project: &Path,
71 files: &[PathBuf],
72 context: &AnalysisContext,
73 ) -> Vec<Finding> {
74 let mut findings = Vec::new();
75 for analyzer in &self.analyzers {
76 findings.extend(analyzer.analyze(project, files, context));
77 }
78 findings
79 }
80
81 pub fn analyzer_ids(&self) -> Vec<String> {
83 self.analyzers.iter().map(|a| a.id().to_string()).collect()
84 }
85}
86
87impl Default for AnalyzerRegistry {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct AssessmentDiff {
96 pub files_added: Vec<String>,
98 pub files_removed: Vec<String>,
100 pub findings_new: Vec<Finding>,
102 pub findings_resolved: Vec<Finding>,
104 pub complexity_delta: i64,
106 pub coherence_delta: f64,
108}
109
110pub fn diff_reports(current: &AssessmentReport, previous: &AssessmentReport) -> AssessmentDiff {
112 use std::collections::HashSet;
113
114 let current_files: HashSet<String> = current.findings.iter().map(|f| f.file.clone()).collect();
116 let previous_files: HashSet<String> =
117 previous.findings.iter().map(|f| f.file.clone()).collect();
118
119 let files_added: Vec<String> = current_files.difference(&previous_files).cloned().collect();
120 let files_removed: Vec<String> = previous_files.difference(¤t_files).cloned().collect();
121
122 type FindingKey = (String, String, String);
124 fn finding_key(f: &Finding) -> FindingKey {
125 (f.category.clone(), f.file.clone(), f.message.clone())
126 }
127
128 let prev_keys: HashSet<FindingKey> = previous.findings.iter().map(finding_key).collect();
129 let curr_keys: HashSet<FindingKey> = current.findings.iter().map(finding_key).collect();
130
131 let findings_new: Vec<Finding> = current
132 .findings
133 .iter()
134 .filter(|f| !prev_keys.contains(&finding_key(f)))
135 .cloned()
136 .collect();
137
138 let findings_resolved: Vec<Finding> = previous
139 .findings
140 .iter()
141 .filter(|f| !curr_keys.contains(&finding_key(f)))
142 .cloned()
143 .collect();
144
145 let complexity_delta =
146 current.summary.complexity_warnings as i64 - previous.summary.complexity_warnings as i64;
147
148 let coherence_delta = current.summary.coherence_score - previous.summary.coherence_score;
149
150 AssessmentDiff {
151 files_added,
152 files_removed,
153 findings_new,
154 findings_resolved,
155 complexity_delta,
156 coherence_delta,
157 }
158}