agentic_codebase/semantic/
pattern_detector.rs1use crate::types::{AcbResult, CodeUnitType, Visibility};
7
8use super::resolver::ResolvedUnit;
9
10pub struct PatternDetector {
12 matchers: Vec<Box<dyn PatternMatcher>>,
14}
15
16#[derive(Debug, Clone)]
18pub struct PatternInstance {
19 pub pattern_name: String,
21 pub primary_unit: u64,
23 pub participating_units: Vec<u64>,
25 pub confidence: f32,
27}
28
29trait PatternMatcher: Send + Sync {
31 fn detect(&self, units: &[ResolvedUnit]) -> Vec<PatternInstance>;
33}
34
35impl PatternDetector {
36 pub fn new() -> Self {
38 let matchers: Vec<Box<dyn PatternMatcher>> = vec![
39 Box::new(SingletonMatcher),
40 Box::new(FactoryMatcher),
41 Box::new(RepositoryMatcher),
42 Box::new(DecoratorMatcher),
43 ];
44 Self { matchers }
45 }
46
47 pub fn detect(&self, units: &[ResolvedUnit]) -> AcbResult<Vec<PatternInstance>> {
49 let mut instances = Vec::new();
50
51 for matcher in &self.matchers {
52 instances.extend(matcher.detect(units));
53 }
54
55 Ok(instances)
56 }
57}
58
59impl Default for PatternDetector {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65struct SingletonMatcher;
68
69impl PatternMatcher for SingletonMatcher {
70 fn detect(&self, units: &[ResolvedUnit]) -> Vec<PatternInstance> {
71 let mut instances = Vec::new();
72
73 for unit in units {
74 if unit.unit.unit_type != CodeUnitType::Type {
75 continue;
76 }
77
78 let type_name = &unit.unit.name;
80 let type_name_lower = type_name.to_lowercase();
81
82 let mut has_instance_method = false;
83 let mut has_private_constructor = false;
84 let mut participants = vec![unit.unit.temp_id];
85
86 for other in units {
87 if other.unit.unit_type != CodeUnitType::Function {
88 continue;
89 }
90
91 let other_qname_lower = other.unit.qualified_name.to_lowercase();
92 let other_name_lower = other.unit.name.to_lowercase();
93
94 if other_qname_lower.contains(&type_name_lower) {
96 if other_name_lower.contains("instance")
98 || other_name_lower.contains("shared")
99 || other_name_lower == "default"
100 {
101 has_instance_method = true;
102 participants.push(other.unit.temp_id);
103 }
104
105 if (other_name_lower == "__init__"
107 || other_name_lower == "new"
108 || other_name_lower == "constructor")
109 && other.unit.visibility == Visibility::Private
110 {
111 has_private_constructor = true;
112 participants.push(other.unit.temp_id);
113 }
114 }
115 }
116
117 let score = (has_instance_method as u8 + has_private_constructor as u8) as f32 / 2.0;
118 if score > 0.0 {
119 instances.push(PatternInstance {
120 pattern_name: "Singleton".to_string(),
121 primary_unit: unit.unit.temp_id,
122 participating_units: participants,
123 confidence: score,
124 });
125 }
126 }
127
128 instances
129 }
130}
131
132struct FactoryMatcher;
135
136impl PatternMatcher for FactoryMatcher {
137 fn detect(&self, units: &[ResolvedUnit]) -> Vec<PatternInstance> {
138 let mut instances = Vec::new();
139
140 for unit in units {
141 if unit.unit.unit_type != CodeUnitType::Function
142 && unit.unit.unit_type != CodeUnitType::Type
143 {
144 continue;
145 }
146
147 let name_lower = unit.unit.name.to_lowercase();
148
149 if name_lower.contains("factory")
151 || name_lower.starts_with("create_")
152 || name_lower.starts_with("make_")
153 || name_lower.starts_with("build_")
154 || name_lower == "new"
155 {
156 instances.push(PatternInstance {
157 pattern_name: "Factory".to_string(),
158 primary_unit: unit.unit.temp_id,
159 participating_units: vec![unit.unit.temp_id],
160 confidence: if name_lower.contains("factory") {
161 0.9
162 } else {
163 0.5
164 },
165 });
166 }
167 }
168
169 instances
170 }
171}
172
173struct RepositoryMatcher;
175
176impl PatternMatcher for RepositoryMatcher {
177 fn detect(&self, units: &[ResolvedUnit]) -> Vec<PatternInstance> {
178 let mut instances = Vec::new();
179
180 for unit in units {
181 if unit.unit.unit_type != CodeUnitType::Type {
182 continue;
183 }
184
185 let name_lower = unit.unit.name.to_lowercase();
186
187 if name_lower.contains("repository")
188 || name_lower.contains("repo")
189 || name_lower.contains("dao")
190 || name_lower.contains("store")
191 {
192 let mut crud_count = 0;
194 for other in units {
195 if other.unit.unit_type == CodeUnitType::Function {
196 let method_lower = other.unit.name.to_lowercase();
197 let in_type = other
198 .unit
199 .qualified_name
200 .to_lowercase()
201 .contains(&name_lower);
202 if in_type
203 && (method_lower.starts_with("get")
204 || method_lower.starts_with("find")
205 || method_lower.starts_with("create")
206 || method_lower.starts_with("update")
207 || method_lower.starts_with("delete")
208 || method_lower.starts_with("save")
209 || method_lower.starts_with("list"))
210 {
211 crud_count += 1;
212 }
213 }
214 }
215
216 let confidence = if crud_count >= 3 {
217 0.9
218 } else if crud_count >= 1 {
219 0.6
220 } else {
221 0.4
222 };
223
224 instances.push(PatternInstance {
225 pattern_name: "Repository".to_string(),
226 primary_unit: unit.unit.temp_id,
227 participating_units: vec![unit.unit.temp_id],
228 confidence,
229 });
230 }
231 }
232
233 instances
234 }
235}
236
237struct DecoratorMatcher;
239
240impl PatternMatcher for DecoratorMatcher {
241 fn detect(&self, units: &[ResolvedUnit]) -> Vec<PatternInstance> {
242 let mut instances = Vec::new();
243
244 for unit in units {
245 let name_lower = unit.unit.name.to_lowercase();
246
247 if name_lower.contains("decorator")
248 || name_lower.contains("wrapper")
249 || name_lower.contains("middleware")
250 {
251 instances.push(PatternInstance {
252 pattern_name: "Decorator".to_string(),
253 primary_unit: unit.unit.temp_id,
254 participating_units: vec![unit.unit.temp_id],
255 confidence: if name_lower.contains("decorator") {
256 0.8
257 } else {
258 0.5
259 },
260 });
261 }
262 }
263
264 instances
265 }
266}